file-roller r2098 - in trunk: . src



Author: paobac
Date: Wed Jan  9 15:56:13 2008
New Revision: 2098
URL: http://svn.gnome.org/viewvc/file-roller?rev=2098&view=rev

Log:
2008-01-09  Paolo Bacchilega  <paobac svn gnome org>

	* src/ui.h: 
	* src/fr-window.c: 
	* src/actions.h: 
	* src/actions.c: 
	
	Second batch of changes for the find/filter bar.

2008-01-08  Paolo Bacchilega  <paobac svn gnome org>
	
	* src/sexy-icon-entry.h: new file
	* src/sexy-icon-entry.c: new file
	* src/fr-list-model.c: 
	* src/fr-window.h: 
	* src/fr-window.c: 
	* src/Makefile.am: 
	
	Started work an a filter bar.

Added:
   trunk/src/sexy-icon-entry.c
   trunk/src/sexy-icon-entry.h
Modified:
   trunk/ChangeLog
   trunk/src/Makefile.am
   trunk/src/actions.c
   trunk/src/actions.h
   trunk/src/fr-list-model.c
   trunk/src/fr-window.c
   trunk/src/fr-window.h
   trunk/src/ui.h

Modified: trunk/src/Makefile.am
==============================================================================
--- trunk/src/Makefile.am	(original)
+++ trunk/src/Makefile.am	Wed Jan  9 15:56:13 2008
@@ -127,6 +127,8 @@
 	open-file.h			\
 	preferences.c			\
 	preferences.h			\
+	sexy-icon-entry.c		\
+	sexy-icon-entry.h		\
 	typedefs.h			\
 	ui.h				\
 	utf8-fnmatch.c			\

Modified: trunk/src/actions.c
==============================================================================
--- trunk/src/actions.c	(original)
+++ trunk/src/actions.c	Wed Jan  9 15:56:13 2008
@@ -768,6 +768,16 @@
 
 
 void
+activate_action_find (GtkAction *action, 
+		      gpointer   data)
+{
+	FrWindow *window = data;
+
+	fr_window_find (window);
+}
+
+
+void
 activate_action_select_all (GtkAction *action,
 			    gpointer   data)
 {

Modified: trunk/src/actions.h
==============================================================================
--- trunk/src/actions.h	(original)
+++ trunk/src/actions.h	Wed Jan  9 15:56:13 2008
@@ -53,6 +53,7 @@
 void activate_action_rename_folder_from_sidebar (GtkAction *action, gpointer data);
 void activate_action_delete_folder_from_sidebar (GtkAction *action, gpointer data);
 
+void activate_action_find (GtkAction *action, gpointer data);
 void activate_action_select_all (GtkAction *action, gpointer data);
 void activate_action_deselect_all (GtkAction *action, gpointer data);
 void activate_action_open_with (GtkAction *action, gpointer data);

Modified: trunk/src/fr-list-model.c
==============================================================================
--- trunk/src/fr-list-model.c	(original)
+++ trunk/src/fr-list-model.c	Wed Jan  9 15:56:13 2008
@@ -40,7 +40,7 @@
 	window = g_object_get_data (G_OBJECT (drag_source), "FrWindow");
 	g_return_val_if_fail (window != NULL, FALSE);
 
-	model = GTK_TREE_MODEL (fr_window_get_list_store (window));
+	model = fr_window_get_list_store (window);
 
 	for (scan = path_list; scan; scan = scan->next) {
 		GtkTreeRowReference *reference = scan->data;

Modified: trunk/src/fr-window.c
==============================================================================
--- trunk/src/fr-window.c	(original)
+++ trunk/src/fr-window.c	Wed Jan  9 15:56:13 2008
@@ -57,6 +57,7 @@
 #include "gtk-utils.h"
 #include "gconf-utils.h"
 #include "open-file.h"
+#include "sexy-icon-entry.h"
 #include "typedefs.h"
 #include "ui.h"
 #include "utf8-fnmatch.h"
@@ -180,7 +181,7 @@
 }
 
 
-void
+static void
 fr_clipboard_data_unref (FrClipboardData *clipboard_data) 
 {
 	if (clipboard_data == NULL)
@@ -199,7 +200,7 @@
 }
 
 
-void
+static void
 fr_clipboard_data_set_password (FrClipboardData *clipboard_data,
 			        const char      *password) 
 {
@@ -221,26 +222,29 @@
 static guint fr_window_signals[LAST_SIGNAL] = { 0 };
 
 struct _FrWindowPrivateData {
-	GtkWidget *      list_view;
-	GtkListStore *   list_store;
-	GtkWidget *      tree_view;
-	GtkTreeStore *   tree_store;
-	GtkWidget *      toolbar;
-	GtkWidget *      statusbar;
-	GtkWidget *      progress_bar;
-	GtkWidget *      location_bar;
-	GtkWidget *      location_entry;
-	GtkWidget *      location_label;
-	GtkWidget *      up_button;
-	GtkWidget *      home_button;
-	GtkWidget *      back_button;
-	GtkWidget *      fwd_button;
-	GtkWidget       *paned;
-	GtkWidget       *sidepane;
-	GtkTreePath     *tree_hover_path;
-	GtkTreePath     *list_hover_path;
+	GtkWidget         *list_view;
+	GtkListStore      *list_store;
+	GtkWidget         *tree_view;
+	GtkTreeStore      *tree_store;
+	GtkWidget         *toolbar;
+	GtkWidget         *statusbar;
+	GtkWidget         *progress_bar;
+	GtkWidget         *location_bar;
+	GtkWidget         *location_entry;
+	GtkWidget         *location_label;
+	GtkWidget         *up_button;
+	GtkWidget         *home_button;
+	GtkWidget         *back_button;
+	GtkWidget         *fwd_button;
+	GtkWidget         *filter_bar;
+	GtkWidget         *filter_entry;
+	GtkWidget         *paned;
+	GtkWidget         *sidepane;
+	GtkTreePath       *tree_hover_path;
+	GtkTreePath       *list_hover_path;
 	GtkTreeViewColumn *filename_column;
 	
+	gboolean         filter_mode;
 	gint             current_view_length;
 
 	GtkTooltips     *tooltips;
@@ -285,6 +289,7 @@
 
 	gboolean         view_folders;
 	FRWindowListMode list_mode;
+	FRWindowListMode last_list_mode;
 	GList *          history;
 	GList *          history_current;
 	char *           password;
@@ -582,7 +587,7 @@
 
 	preferences_set_sort_method (priv->sort_method);
 	preferences_set_sort_type (priv->sort_type);
-	preferences_set_list_mode (priv->list_mode);
+	preferences_set_list_mode (priv->last_list_mode);
 }
 
 
@@ -693,7 +698,8 @@
 {
 	window->priv = g_new0 (FrWindowPrivateData, 1);
 	window->priv->update_dropped_files = FALSE;
-
+	window->priv->filter_mode = FALSE;
+	
 	WindowList = g_list_prepend (WindowList, window);
 }
 
@@ -975,6 +981,23 @@
 }
 
 
+static gboolean
+file_data_respects_filter (FrWindow *window,
+			   FileData *fdata)
+{
+	const char *filter;
+
+	filter = gtk_entry_get_text (GTK_ENTRY (window->priv->filter_entry));
+	if ((fdata == NULL) || (filter == NULL) || (*filter == '\0'))
+		return TRUE;
+
+	if (fdata->dir || (fdata->name == NULL))
+		return FALSE;
+		
+	return strncasecmp (fdata->name, filter, strlen (filter)) == 0;
+}
+
+
 static void
 compute_file_list_name (FrWindow   *window,
 			FileData   *fdata,
@@ -988,6 +1011,9 @@
 	fdata->list_name = NULL;
 	fdata->list_dir = FALSE;
 
+	if (! file_data_respects_filter (window, fdata))
+		return;
+
 	if (window->priv->list_mode == FR_WINDOW_LIST_MODE_FLAT) {
 		fdata->list_name = g_strdup (fdata->name);
 		if (fdata->dir)
@@ -1609,6 +1635,7 @@
 	gtk_widget_set_sensitive (window->priv->fwd_button, window->priv->archive_present && (current_dir != NULL) && (window->priv->history_current != NULL) && (window->priv->history_current->prev != NULL));
 	gtk_widget_set_sensitive (window->priv->location_entry, window->priv->archive_present);
 	gtk_widget_set_sensitive (window->priv->location_label, window->priv->archive_present);
+	gtk_widget_set_sensitive (window->priv->filter_entry, window->priv->archive_present);
 
 #if 0
 	fr_window_history_print (window);
@@ -1642,7 +1669,8 @@
 
 	if (! window->priv->view_folders  
 	    || ! window->priv->archive_present 
-	    || (window->priv->list_mode == FR_WINDOW_LIST_MODE_FLAT)) {
+	    || (window->priv->list_mode == FR_WINDOW_LIST_MODE_FLAT)) 
+	{
 		gtk_widget_set_sensitive (window->priv->tree_view, FALSE);
 		gtk_widget_hide (window->priv->sidepane);
 		return;
@@ -1665,6 +1693,11 @@
 		FileData *fdata = g_ptr_array_index (window->archive->command->files, i);
 		char     *dir;
 		
+		if (gtk_entry_get_text (GTK_ENTRY (window->priv->filter_entry)) != NULL) {
+			if (! file_data_respects_filter (window, fdata))
+				continue;
+		}
+		
 		if (fdata->dir)
 			dir = remove_ending_separator (fdata->full_path);
 		else 
@@ -1751,6 +1784,19 @@
 
 
 static void
+fr_window_update_filter_bar_visibility (FrWindow *window)
+{
+	const char *filter;
+
+	filter = gtk_entry_get_text (GTK_ENTRY (window->priv->filter_entry));
+	if ((filter == NULL) || (*filter == '\0'))
+		gtk_widget_hide (window->priv->filter_bar);
+	else
+		gtk_widget_show (window->priv->filter_bar);	
+}
+
+
+static void
 fr_window_update_file_list (FrWindow *window,
 			    gboolean  update_view)
 {
@@ -1985,6 +2031,7 @@
 	set_sensitive (window, "DeselectAll", ! no_archive && sel_not_null);
 	set_sensitive (window, "Extract", file_op);
 	set_sensitive (window, "Extract_Toolbar", file_op);
+	set_sensitive (window, "Find", ! no_archive);	
 	set_sensitive (window, "LastOutput", ((window->archive != NULL)
 					      && (window->archive->process != NULL)
 					      && (window->archive->process->raw_output != NULL)));
@@ -2017,6 +2064,9 @@
 	
 	set_sensitive (window, "ViewFolders", (window->priv->list_mode == FR_WINDOW_LIST_MODE_AS_DIR));
 	
+	set_sensitive (window, "ViewAllFiles", ! window->priv->filter_mode);
+	set_sensitive (window, "ViewAsFolder", ! window->priv->filter_mode);
+	
 	/**/
 	
 	if (! window->priv->closing && (window->priv->check_clipboard == 0))
@@ -2032,8 +2082,10 @@
 	if ((event->keyval == GDK_Return)
 	    || (event->keyval == GDK_KP_Enter)
 	    || (event->keyval == GDK_ISO_Enter))
+	{
 		fr_window_go_to_location (window, gtk_entry_get_text (GTK_ENTRY (window->priv->location_entry)), FALSE);
-
+	}
+	
 	return FALSE;
 }
 
@@ -3222,7 +3274,8 @@
 	/* only redraw if the hover row has changed */
 	if (!(last_hover_path == NULL && window->priv->list_hover_path == NULL) &&
 	    (!(last_hover_path != NULL && window->priv->list_hover_path != NULL) ||
-	     gtk_tree_path_compare (last_hover_path, window->priv->list_hover_path))) {
+	     gtk_tree_path_compare (last_hover_path, window->priv->list_hover_path))) 
+	{
 		if (last_hover_path) {
 			gtk_tree_model_get_iter (GTK_TREE_MODEL (window->priv->list_store),
 						 &iter, last_hover_path);
@@ -3712,6 +3765,24 @@
 /* -- window_new -- */
 
 
+static void
+fr_window_deactivate_filter (FrWindow *window)
+{
+	window->priv->filter_mode = FALSE;
+	window->priv->list_mode = window->priv->last_list_mode;
+	
+	gtk_entry_set_text (GTK_ENTRY (window->priv->filter_entry), "");
+	fr_window_update_filter_bar_visibility (window);
+	
+	gtk_list_store_clear (window->priv->list_store);
+	
+	fr_window_update_columns_visibility (window);
+	fr_window_update_file_list (window, TRUE);
+	fr_window_update_dir_tree (window);
+	fr_window_update_current_location (window);
+}
+
+
 static gboolean
 key_press_cb (GtkWidget   *widget,
 	      GdkEventKey *event,
@@ -3723,12 +3794,26 @@
 
 	if (GTK_WIDGET_HAS_FOCUS (window->priv->location_entry))
 		return FALSE;
+		
+	if (GTK_WIDGET_HAS_FOCUS (window->priv->filter_entry)) {
+		switch (event->keyval) {
+		case GDK_Escape:
+			fr_window_deactivate_filter (window);
+			retval = TRUE;
+			break;
+		default:
+			break;
+		}
+		return retval;
+	}
 
 	alt = (event->state & GDK_MOD1_MASK) == GDK_MOD1_MASK;
 
 	switch (event->keyval) {
 	case GDK_Escape:
 		activate_action_stop (NULL, window);
+		if (window->priv->filter_mode)
+			fr_window_deactivate_filter (window);
 		retval = TRUE;
 		break;
 
@@ -4145,6 +4230,8 @@
 	window->priv->view_folders = eel_gconf_get_boolean (PREF_UI_FOLDERS, FALSE);
 	set_active (window, "ViewFolders", window->priv->view_folders);	
 
+	fr_window_update_filter_bar_visibility (window);
+
 	return TRUE;
 }
 
@@ -4509,7 +4596,7 @@
 }
 
 
-void
+static void
 go_up_one_level_cb (GtkWidget *widget,
 		    void      *data)
 {
@@ -4517,7 +4604,7 @@
 }
 
 
-void
+static void
 go_home_cb (GtkWidget *widget,
 	    void      *data)
 {
@@ -4525,7 +4612,7 @@
 }
 
 
-void
+static void
 go_back_cb (GtkWidget *widget,
 	    void      *data)
 {
@@ -4533,7 +4620,7 @@
 }
 
 
-void
+static void
 go_forward_cb (GtkWidget *widget,
 	       void      *data)
 {
@@ -4592,12 +4679,54 @@
 
 
 static void
+fr_window_activate_filter (FrWindow *window)
+{
+	GtkTreeView       *tree_view = GTK_TREE_VIEW (window->priv->list_view);
+	GtkTreeViewColumn *column;
+
+	fr_window_update_filter_bar_visibility (window);
+	window->priv->list_mode = FR_WINDOW_LIST_MODE_FLAT;
+	
+	gtk_list_store_clear (window->priv->list_store);
+	
+	column = gtk_tree_view_get_column (tree_view, 4);
+	gtk_tree_view_column_set_visible (column, TRUE);	
+	
+	fr_window_update_file_list (window, TRUE);
+	fr_window_update_dir_tree (window);
+	fr_window_update_current_location (window);
+}
+
+
+static void
+filter_entry_activate_cb (GtkEntry *entry,
+                          FrWindow *window)
+{
+	fr_window_activate_filter (window);
+}
+
+
+static void
+filter_entry_icon_released_cb (SexyIconEntry         *entry, 
+			       SexyIconEntryPosition  icon_pos,
+			       int                    button,
+			       gpointer               user_data)
+{
+	FrWindow *window = FR_WINDOW (user_data);
+	
+	if ((button == 1) && (icon_pos == SEXY_ICON_ENTRY_SECONDARY)) 		
+		fr_window_deactivate_filter (window);
+}
+
+
+static void
 fr_window_construct (FrWindow *window)
 {
 	GtkWidget        *toolbar;
 	GtkWidget        *list_scrolled_window;
 	GtkWidget        *vbox;
 	GtkWidget        *location_box;
+	GtkWidget        *filter_box;
 	GtkWidget        *tree_scrolled_window;
 	GtkWidget        *sidepane_title;
 	GtkWidget        *sidepane_title_box;
@@ -4710,7 +4839,7 @@
 	window->priv->sort_method = preferences_get_sort_method ();
 	window->priv->sort_type = preferences_get_sort_type ();
 
-	window->priv->list_mode = preferences_get_list_mode ();
+	window->priv->list_mode = window->priv->last_list_mode = preferences_get_list_mode ();
 	window->priv->history = NULL;
 	window->priv->history_current = NULL;
 
@@ -4778,13 +4907,13 @@
 						      G_TYPE_STRING,
 						      G_TYPE_STRING,
 						      G_TYPE_STRING);
-	g_object_set_data (G_OBJECT (window->priv->list_store), "FrWindow", window);
+	g_object_set_data (G_OBJECT (window->priv->list_store), "FrWindow", window);		
 	window->priv->list_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (window->priv->list_store));
 
 	gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (window->priv->list_view), TRUE);
 	add_file_list_columns (window, GTK_TREE_VIEW (window->priv->list_view));
 	gtk_tree_view_set_enable_search (GTK_TREE_VIEW (window->priv->list_view),
-					 TRUE);
+					 FALSE);
 	gtk_tree_view_set_search_column (GTK_TREE_VIEW (window->priv->list_view),
 					 COLUMN_NAME);
 
@@ -4927,7 +5056,11 @@
 	gtk_box_pack_start (GTK_BOX (location_box),
 			    window->priv->location_label, FALSE, FALSE, 5);
 
-	window->priv->location_entry = gtk_entry_new ();
+	window->priv->location_entry = sexy_icon_entry_new ();
+	sexy_icon_entry_set_icon (SEXY_ICON_ENTRY (window->priv->location_entry),
+				  SEXY_ICON_ENTRY_PRIMARY,
+				  GTK_IMAGE (gtk_image_new_from_stock (GTK_STOCK_DIRECTORY, GTK_ICON_SIZE_MENU)));
+
 	gtk_box_pack_start (GTK_BOX (location_box),
 			    window->priv->location_entry, TRUE, TRUE, 5);
 
@@ -4935,9 +5068,49 @@
 			  "key_press_event",
 			  G_CALLBACK (location_entry_key_press_event_cb),
 			  window);
-
+			  
 	gtk_widget_show_all (window->priv->location_bar);
 
+	/* filter bar */
+
+	filter_box = gtk_hbox_new (FALSE, 6);
+	gtk_container_set_border_width (GTK_CONTAINER (filter_box), 3);
+
+	window->priv->filter_bar = gnome_app_add_docked (GNOME_APP (window),
+						         filter_box,
+						         "FilterBar",
+						         (BONOBO_DOCK_ITEM_BEH_NEVER_VERTICAL
+						          | BONOBO_DOCK_ITEM_BEH_EXCLUSIVE
+						          | (eel_gconf_get_boolean (PREF_DESKTOP_TOOLBAR_DETACHABLE, TRUE) ? BONOBO_DOCK_ITEM_BEH_NORMAL : BONOBO_DOCK_ITEM_BEH_LOCKED)),
+						         BONOBO_DOCK_BOTTOM,
+						         4, 1, 0);
+	
+	gtk_box_pack_start (GTK_BOX (filter_box),
+			    gtk_label_new (_("Find:")), FALSE, FALSE, 0);
+
+	/* * filter entry */
+	
+	window->priv->filter_entry = sexy_icon_entry_new ();
+	/*sexy_icon_entry_set_icon (SEXY_ICON_ENTRY (window->priv->filter_entry),
+				  SEXY_ICON_ENTRY_PRIMARY,
+				  GTK_IMAGE (gtk_image_new_from_stock (GTK_STOCK_FIND, GTK_ICON_SIZE_MENU)));*/
+	sexy_icon_entry_add_clear_button (SEXY_ICON_ENTRY (window->priv->filter_entry));
+	
+	gtk_widget_set_size_request (window->priv->filter_entry, 300, -1);
+	gtk_box_pack_start (GTK_BOX (filter_box),
+			    window->priv->filter_entry, FALSE, FALSE, 6);
+
+	g_signal_connect (G_OBJECT (window->priv->filter_entry),
+			  "activate",
+			  G_CALLBACK (filter_entry_activate_cb),
+			  window);
+	g_signal_connect (G_OBJECT (window->priv->filter_entry),
+			  "icon_released",
+			  G_CALLBACK (filter_entry_icon_released_cb),
+			  window);
+
+	gtk_widget_show_all (filter_box);
+
 	/* tree view */
 
 	window->priv->tree_store = gtk_tree_store_new (TREE_NUMBER_OF_COLUMNS,
@@ -5986,13 +6159,13 @@
 {
 	g_return_if_fail (window != NULL);
 
-	window->priv->list_mode = list_mode;
+	window->priv->list_mode = window->priv->last_list_mode = list_mode;
 	if (window->priv->list_mode == FR_WINDOW_LIST_MODE_FLAT) {
 		fr_window_history_clear (window);
 		fr_window_history_add (window, "/");
 	}
 
-	preferences_set_list_mode (window->priv->list_mode);
+	preferences_set_list_mode (window->priv->last_list_mode);
 	eel_gconf_set_boolean (PREF_LIST_SHOW_PATH, (window->priv->list_mode == FR_WINDOW_LIST_MODE_FLAT));
 
 	fr_window_update_file_list (window, TRUE);
@@ -6229,10 +6402,19 @@
 }
 
 
-GtkListStore *
+GtkTreeModel *
 fr_window_get_list_store (FrWindow *window)
 {
-	return window->priv->list_store;
+	return GTK_TREE_MODEL (window->priv->list_store);
+}
+
+
+void
+fr_window_find (FrWindow *window)
+{
+	window->priv->filter_mode = TRUE;
+	gtk_widget_show (window->priv->filter_bar);
+	gtk_widget_grab_focus (window->priv->filter_entry);
 }
 
 

Modified: trunk/src/fr-window.h
==============================================================================
--- trunk/src/fr-window.h	(original)
+++ trunk/src/fr-window.h	Wed Jan  9 15:56:13 2008
@@ -207,8 +207,9 @@
 GList *    fr_window_get_selection                (FrWindow    *window,
 		  	 			   gboolean     from_sidebar,
 		  	 			   char       **return_base_dir);				     		   
-GtkListStore *
+GtkTreeModel *
 	   fr_window_get_list_store               (FrWindow    *window);
+void       fr_window_find                         (FrWindow    *window);	   
 void       fr_window_select_all                   (FrWindow    *window);
 void       fr_window_unselect_all                 (FrWindow    *window);
 void       fr_window_set_sort_type                (FrWindow    *window,

Added: trunk/src/sexy-icon-entry.c
==============================================================================
--- (empty file)
+++ trunk/src/sexy-icon-entry.c	Wed Jan  9 15:56:13 2008
@@ -0,0 +1,1045 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  File-Roller
+ *
+ *  Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+/* Based on libsexy 0.1.11.  Original copyright note follows:
+ * 
+ * @file libsexy/sexy-icon-entry.c Entry widget
+ *
+ * @Copyright (C) 2004-2006 Christian Hammond.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA  02111-1307, USA.
+ */
+  
+#include <libsexy/sexy-icon-entry.h>
+#include <string.h>
+#include <gtk/gtk.h>
+
+#define ICON_MARGIN 2
+#define MAX_ICONS 2
+
+#define IS_VALID_ICON_ENTRY_POSITION(pos) \
+	((pos) == SEXY_ICON_ENTRY_PRIMARY || \
+	 (pos) == SEXY_ICON_ENTRY_SECONDARY)
+
+typedef struct
+{
+	GtkImage *icon;
+	gboolean highlight;
+	gboolean hovered;
+	gboolean hide_if_empty;
+	GdkWindow *window;
+
+} SexyIconInfo;
+
+struct _SexyIconEntryPriv
+{
+	SexyIconInfo icons[MAX_ICONS];
+
+	gulong icon_released_id;
+	gboolean empty;
+};
+
+enum
+{
+	ICON_PRESSED,
+	ICON_RELEASED,
+	LAST_SIGNAL
+};
+
+static void sexy_icon_entry_class_init(SexyIconEntryClass *klass);
+static void sexy_icon_entry_editable_init(GtkEditableClass *iface);
+static void sexy_icon_entry_init(SexyIconEntry *entry);
+static void sexy_icon_entry_finalize(GObject *obj);
+static void sexy_icon_entry_destroy(GtkObject *obj);
+static void sexy_icon_entry_map(GtkWidget *widget);
+static void sexy_icon_entry_unmap(GtkWidget *widget);
+static void sexy_icon_entry_realize(GtkWidget *widget);
+static void sexy_icon_entry_unrealize(GtkWidget *widget);
+static void sexy_icon_entry_size_request(GtkWidget *widget,
+										  GtkRequisition *requisition);
+static void sexy_icon_entry_size_allocate(GtkWidget *widget,
+										   GtkAllocation *allocation);
+static gint sexy_icon_entry_expose(GtkWidget *widget, GdkEventExpose *event);
+static gint sexy_icon_entry_enter_notify(GtkWidget *widget,
+											   GdkEventCrossing *event);
+static gint sexy_icon_entry_leave_notify(GtkWidget *widget,
+											   GdkEventCrossing *event);
+static gint sexy_icon_entry_button_press(GtkWidget *widget,
+											   GdkEventButton *event);
+static gint sexy_icon_entry_button_release(GtkWidget *widget,
+												 GdkEventButton *event);
+
+static void sexy_icon_entry_changed (GtkEditable *editable);
+
+static GtkEntryClass *parent_class = NULL;
+static guint signals[LAST_SIGNAL] = {0};
+
+G_DEFINE_TYPE_EXTENDED(SexyIconEntry, sexy_icon_entry, GTK_TYPE_ENTRY,
+					   0,
+					   G_IMPLEMENT_INTERFACE(GTK_TYPE_EDITABLE,
+											 sexy_icon_entry_editable_init));
+
+static void
+sexy_icon_entry_class_init(SexyIconEntryClass *klass)
+{
+	GObjectClass *gobject_class;
+	GtkObjectClass *object_class;
+	GtkWidgetClass *widget_class;
+	GtkEntryClass *entry_class;
+
+	parent_class = g_type_class_peek_parent(klass);
+
+	gobject_class  = G_OBJECT_CLASS(klass);
+	object_class   = GTK_OBJECT_CLASS(klass);
+	widget_class   = GTK_WIDGET_CLASS(klass);
+	entry_class    = GTK_ENTRY_CLASS(klass);
+	
+	gobject_class->finalize = sexy_icon_entry_finalize;
+
+	object_class->destroy = sexy_icon_entry_destroy;
+
+	widget_class->map = sexy_icon_entry_map;
+	widget_class->unmap = sexy_icon_entry_unmap;
+	widget_class->realize = sexy_icon_entry_realize;
+	widget_class->unrealize = sexy_icon_entry_unrealize;
+	widget_class->size_request = sexy_icon_entry_size_request;
+	widget_class->size_allocate = sexy_icon_entry_size_allocate;
+	widget_class->expose_event = sexy_icon_entry_expose;
+	widget_class->enter_notify_event = sexy_icon_entry_enter_notify;
+	widget_class->leave_notify_event = sexy_icon_entry_leave_notify;
+	widget_class->button_press_event = sexy_icon_entry_button_press;
+	widget_class->button_release_event = sexy_icon_entry_button_release;
+
+	/**
+	 * SexyIconEntry::icon-pressed:
+	 * @entry: The entry on which the signal is emitted.
+	 * @icon_pos: The position of the clicked icon.
+	 * @button: The mouse button clicked.
+	 *
+	 * The ::icon-pressed signal is emitted when an icon is clicked.
+	 */
+	signals[ICON_PRESSED] =
+		g_signal_new("icon_pressed",
+					 G_TYPE_FROM_CLASS(gobject_class),
+					 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+					 G_STRUCT_OFFSET(SexyIconEntryClass, icon_pressed),
+					 NULL, NULL,
+					 gtk_marshal_VOID__INT_INT,
+					 G_TYPE_NONE, 2,
+					 G_TYPE_INT,
+					 G_TYPE_INT);
+
+	/**
+	 * SexyIconEntry::icon-released:
+	 * @entry: The entry on which the signal is emitted.
+	 * @icon_pos: The position of the clicked icon.
+	 * @button: The mouse button clicked.
+	 *
+	 * The ::icon-released signal is emitted on the button release from a
+	 * mouse click.
+	 */
+	signals[ICON_RELEASED] =
+		g_signal_new("icon_released",
+					 G_TYPE_FROM_CLASS(gobject_class),
+					 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+					 G_STRUCT_OFFSET(SexyIconEntryClass, icon_released),
+					 NULL, NULL,
+					 gtk_marshal_VOID__INT_INT,
+					 G_TYPE_NONE, 2,
+					 G_TYPE_INT,
+					 G_TYPE_INT);
+}
+
+static void
+sexy_icon_entry_editable_init(GtkEditableClass *iface)
+{
+		iface->changed = sexy_icon_entry_changed;
+}
+
+static void
+sexy_icon_entry_init(SexyIconEntry *entry)
+{
+	entry->priv = g_new0(SexyIconEntryPriv, 1);
+	entry->priv->empty = TRUE;
+}
+
+static void
+sexy_icon_entry_finalize(GObject *obj)
+{
+	SexyIconEntry *entry;
+
+	g_return_if_fail(obj != NULL);
+	g_return_if_fail(SEXY_IS_ICON_ENTRY(obj));
+
+	entry = SEXY_ICON_ENTRY(obj);
+
+	g_free(entry->priv);
+
+	if (G_OBJECT_CLASS(parent_class)->finalize)
+		G_OBJECT_CLASS(parent_class)->finalize(obj);
+}
+
+static void
+sexy_icon_entry_destroy(GtkObject *obj)
+{
+	SexyIconEntry *entry;
+
+	entry = SEXY_ICON_ENTRY(obj);
+
+	sexy_icon_entry_set_icon(entry, SEXY_ICON_ENTRY_PRIMARY, NULL);
+	sexy_icon_entry_set_icon(entry, SEXY_ICON_ENTRY_SECONDARY, NULL);
+
+	if (GTK_OBJECT_CLASS(parent_class)->destroy)
+		GTK_OBJECT_CLASS(parent_class)->destroy(obj);
+}
+
+static void
+sexy_icon_entry_map(GtkWidget *widget)
+{
+	if (GTK_WIDGET_REALIZED(widget) && !GTK_WIDGET_MAPPED(widget))
+	{
+		SexyIconEntry *entry = SEXY_ICON_ENTRY(widget);
+		int i;
+
+		GTK_WIDGET_CLASS(parent_class)->map(widget);
+
+		for (i = 0; i < MAX_ICONS; i++)
+		{
+			if (entry->priv->icons[i].icon != NULL)
+				gdk_window_show(entry->priv->icons[i].window);
+		}
+	}
+}
+
+static void
+sexy_icon_entry_unmap(GtkWidget *widget)
+{
+	if (GTK_WIDGET_MAPPED(widget))
+	{
+		SexyIconEntry *entry = SEXY_ICON_ENTRY(widget);
+		int i;
+
+		for (i = 0; i < MAX_ICONS; i++)
+		{
+			if (entry->priv->icons[i].icon != NULL)
+				gdk_window_hide(entry->priv->icons[i].window);
+		}
+
+		GTK_WIDGET_CLASS(parent_class)->unmap(widget);
+	}
+}
+
+static gint
+get_icon_width(SexyIconEntry *entry, SexyIconEntryPosition icon_pos)
+{
+	GtkRequisition requisition;
+	gint menu_icon_width;
+	gint width;
+	SexyIconInfo *icon_info = &entry->priv->icons[icon_pos];
+
+	if (icon_info->icon == NULL)
+		return 0;
+
+	gtk_widget_size_request(GTK_WIDGET(icon_info->icon), &requisition);
+	gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &menu_icon_width, NULL);
+
+	width = MAX(requisition.width, menu_icon_width);
+
+	return width;
+}
+
+static void
+get_borders(SexyIconEntry *entry, gint *xborder, gint *yborder)
+{
+	GtkWidget *widget = GTK_WIDGET(entry);
+	gint focus_width;
+	gboolean interior_focus;
+
+	gtk_widget_style_get(widget,
+						 "interior-focus", &interior_focus,
+						 "focus-line-width", &focus_width,
+						 NULL);
+
+	if (gtk_entry_get_has_frame(GTK_ENTRY(entry)))
+	{
+		*xborder = widget->style->xthickness;
+		*yborder = widget->style->ythickness;
+	}
+	else
+	{
+		*xborder = 0;
+		*yborder = 0;
+	}
+
+	if (!interior_focus)
+	{
+		*xborder += focus_width;
+		*yborder += focus_width;
+	}
+}
+
+static void
+get_text_area_size(SexyIconEntry *entry, GtkAllocation *alloc)
+{
+	GtkWidget *widget = GTK_WIDGET(entry);
+	GtkRequisition requisition;
+	gint xborder, yborder;
+
+	gtk_widget_get_child_requisition(widget, &requisition);
+	get_borders(entry, &xborder, &yborder);
+
+	alloc->x      = xborder;
+	alloc->y      = yborder;
+	alloc->width  = widget->allocation.width - xborder * 2;
+	alloc->height = requisition.height       - yborder * 2;
+}
+
+static void
+get_icon_allocation(SexyIconEntry *icon_entry,
+					gboolean left,
+					GtkAllocation *widget_alloc,
+					GtkAllocation *text_area_alloc,
+					GtkAllocation *allocation,
+					SexyIconEntryPosition *icon_pos)
+{
+	gboolean rtl;
+
+	rtl = (gtk_widget_get_direction(GTK_WIDGET(icon_entry)) ==
+		   GTK_TEXT_DIR_RTL);
+
+	if (left)
+		*icon_pos = (rtl ? SEXY_ICON_ENTRY_SECONDARY : SEXY_ICON_ENTRY_PRIMARY);
+	else
+		*icon_pos = (rtl ? SEXY_ICON_ENTRY_PRIMARY : SEXY_ICON_ENTRY_SECONDARY);
+
+	allocation->y = text_area_alloc->y;
+	allocation->width = get_icon_width(icon_entry, *icon_pos);
+	allocation->height = text_area_alloc->height;
+
+	if (left)
+		allocation->x = text_area_alloc->x + ICON_MARGIN;
+	else
+	{
+		allocation->x = text_area_alloc->x + text_area_alloc->width -
+		                allocation->width - ICON_MARGIN;
+	}
+}
+
+static void
+sexy_icon_entry_realize(GtkWidget *widget)
+{
+	SexyIconEntry *entry = SEXY_ICON_ENTRY(widget);
+	GdkWindowAttr attributes;
+	gint attributes_mask;
+	int i;
+
+	GTK_WIDGET_CLASS(parent_class)->realize(widget);
+
+	attributes.x = 0;
+	attributes.y = 0;
+	attributes.width = 1;
+	attributes.height = 1;
+	attributes.window_type = GDK_WINDOW_CHILD;
+	attributes.wclass = GDK_INPUT_OUTPUT;
+	attributes.visual = gtk_widget_get_visual(widget);
+	attributes.colormap = gtk_widget_get_colormap(widget);
+	attributes.event_mask = gtk_widget_get_events(widget);
+	attributes.event_mask |=
+		(GDK_EXPOSURE_MASK
+		 | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
+		 | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
+
+	attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
+
+	for (i = 0; i < MAX_ICONS; i++)
+	{
+		SexyIconInfo *icon_info;
+
+		icon_info = &entry->priv->icons[i];
+		icon_info->window = gdk_window_new(widget->window, &attributes,
+										   attributes_mask);
+		gdk_window_set_user_data(icon_info->window, widget);
+
+		gdk_window_set_background(icon_info->window,
+			&widget->style->base[GTK_WIDGET_STATE(widget)]);
+	}
+
+	gtk_widget_queue_resize(widget);
+}
+
+static void
+sexy_icon_entry_unrealize(GtkWidget *widget)
+{
+	SexyIconEntry *entry = SEXY_ICON_ENTRY(widget);
+	int i;
+
+	GTK_WIDGET_CLASS(parent_class)->unrealize(widget);
+
+	for (i = 0; i < MAX_ICONS; i++)
+	{
+		SexyIconInfo *icon_info = &entry->priv->icons[i];
+
+		gdk_window_destroy(icon_info->window);
+		icon_info->window = NULL;
+	}
+}
+
+static void
+sexy_icon_entry_size_request(GtkWidget *widget, GtkRequisition *requisition)
+{
+	GtkEntry *gtkentry;
+	SexyIconEntry *entry;
+	gint icon_widths = 0;
+	int i;
+
+	gtkentry = GTK_ENTRY(widget);
+	entry    = SEXY_ICON_ENTRY(widget);
+
+	for (i = 0; i < MAX_ICONS; i++)
+	{
+		int icon_width = get_icon_width(entry, i);
+
+		if (icon_width > 0)
+			icon_widths += icon_width + ICON_MARGIN;
+	}
+
+	GTK_WIDGET_CLASS(parent_class)->size_request(widget, requisition);
+
+	if (icon_widths > requisition->width)
+		requisition->width += icon_widths;
+}
+
+static void
+place_windows(SexyIconEntry *icon_entry, GtkAllocation *widget_alloc)
+{
+	SexyIconEntryPosition left_icon_pos;
+	SexyIconEntryPosition right_icon_pos;
+	GtkAllocation left_icon_alloc;
+	GtkAllocation right_icon_alloc;
+	GtkAllocation text_area_alloc;
+
+	get_text_area_size(icon_entry, &text_area_alloc);
+	get_icon_allocation(icon_entry, TRUE, widget_alloc, &text_area_alloc,
+						&left_icon_alloc, &left_icon_pos);
+	get_icon_allocation(icon_entry, FALSE, widget_alloc, &text_area_alloc,
+						&right_icon_alloc, &right_icon_pos);
+
+	if (left_icon_alloc.width > 0)
+	{
+		text_area_alloc.x = left_icon_alloc.x + left_icon_alloc.width +
+		                    ICON_MARGIN;
+	}
+
+	if (right_icon_alloc.width > 0)
+		text_area_alloc.width -= right_icon_alloc.width + ICON_MARGIN;
+
+	text_area_alloc.width -= text_area_alloc.x;
+
+	gdk_window_move_resize(icon_entry->priv->icons[left_icon_pos].window,
+						   left_icon_alloc.x, left_icon_alloc.y,
+						   left_icon_alloc.width, left_icon_alloc.height);
+
+	gdk_window_move_resize(icon_entry->priv->icons[right_icon_pos].window,
+						   right_icon_alloc.x, right_icon_alloc.y,
+						   right_icon_alloc.width, right_icon_alloc.height);
+
+	gdk_window_move_resize(GTK_ENTRY(icon_entry)->text_area,
+						   text_area_alloc.x, text_area_alloc.y,
+						   text_area_alloc.width, text_area_alloc.height);
+}
+
+static void
+sexy_icon_entry_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
+{
+	g_return_if_fail(SEXY_IS_ICON_ENTRY(widget));
+	g_return_if_fail(allocation != NULL);
+
+	widget->allocation = *allocation;
+
+	GTK_WIDGET_CLASS(parent_class)->size_allocate(widget, allocation);
+
+	if (GTK_WIDGET_REALIZED(widget))
+		place_windows(SEXY_ICON_ENTRY(widget), allocation);
+}
+
+static GdkPixbuf *
+get_pixbuf_from_icon(SexyIconEntry *entry, SexyIconEntryPosition icon_pos)
+{
+	GdkPixbuf *pixbuf = NULL;
+	gchar *stock_id;
+	SexyIconInfo *icon_info = &entry->priv->icons[icon_pos];
+	GtkIconSize size;
+
+	switch (gtk_image_get_storage_type(GTK_IMAGE(icon_info->icon)))
+	{
+		case GTK_IMAGE_PIXBUF:
+			pixbuf = gtk_image_get_pixbuf(GTK_IMAGE(icon_info->icon));
+			g_object_ref(pixbuf);
+			break;
+
+		case GTK_IMAGE_STOCK:
+			gtk_image_get_stock(GTK_IMAGE(icon_info->icon), &stock_id, &size);
+			pixbuf = gtk_widget_render_icon(GTK_WIDGET(entry),
+											stock_id, size, NULL);
+			break;
+
+		default:
+			return NULL;
+	}
+
+	return pixbuf;
+}
+
+/* Kudos to the gnome-panel guys. */
+static void
+colorshift_pixbuf(GdkPixbuf *dest, GdkPixbuf *src, int shift)
+{
+	gint i, j;
+	gint width, height, has_alpha, src_rowstride, dest_rowstride;
+	guchar *target_pixels;
+	guchar *original_pixels;
+	guchar *pix_src;
+	guchar *pix_dest;
+	int val;
+	guchar r, g, b;
+
+	has_alpha       = gdk_pixbuf_get_has_alpha(src);
+	width           = gdk_pixbuf_get_width(src);
+	height          = gdk_pixbuf_get_height(src);
+	src_rowstride   = gdk_pixbuf_get_rowstride(src);
+	dest_rowstride  = gdk_pixbuf_get_rowstride(dest);
+	original_pixels = gdk_pixbuf_get_pixels(src);
+	target_pixels   = gdk_pixbuf_get_pixels(dest);
+
+	for (i = 0; i < height; i++)
+	{
+		pix_dest = target_pixels   + i * dest_rowstride;
+		pix_src  = original_pixels + i * src_rowstride;
+
+		for (j = 0; j < width; j++)
+		{
+			r = *(pix_src++);
+			g = *(pix_src++);
+			b = *(pix_src++);
+
+			val = r + shift;
+			*(pix_dest++) = CLAMP(val, 0, 255);
+
+			val = g + shift;
+			*(pix_dest++) = CLAMP(val, 0, 255);
+
+			val = b + shift;
+			*(pix_dest++) = CLAMP(val, 0, 255);
+
+			if (has_alpha)
+				*(pix_dest++) = *(pix_src++);
+		}
+	}
+}
+
+static void
+draw_icon(GtkWidget *widget, SexyIconEntryPosition icon_pos)
+{
+	SexyIconEntry *entry = SEXY_ICON_ENTRY(widget);
+	SexyIconInfo *icon_info = &entry->priv->icons[icon_pos];
+	GdkPixbuf *pixbuf;
+	gint x, y, width, height;
+
+	if (icon_info->icon == NULL || !GTK_WIDGET_REALIZED(widget))
+		return;
+
+	if (entry->priv->empty && icon_info->hide_if_empty)
+		return;
+
+	if ((pixbuf = get_pixbuf_from_icon(entry, icon_pos)) == NULL)
+		return;
+
+	gdk_drawable_get_size(icon_info->window, &width, &height);
+
+	if (width == 1 || height == 1)
+	{
+		/*
+		 * size_allocate hasn't been called yet. These are the default values.
+		 */
+		return;
+	}
+
+	if (gdk_pixbuf_get_height(pixbuf) > height)
+	{
+		GdkPixbuf *temp_pixbuf;
+		int scale;
+
+		scale = height - (2 * ICON_MARGIN);
+
+		temp_pixbuf = gdk_pixbuf_scale_simple(pixbuf, scale, scale,
+											  GDK_INTERP_BILINEAR);
+
+		g_object_unref(pixbuf);
+
+		pixbuf = temp_pixbuf;
+	}
+
+	x = (width  - gdk_pixbuf_get_width(pixbuf)) / 2;
+	y = (height - gdk_pixbuf_get_height(pixbuf)) / 2;
+
+	if (icon_info->hovered)
+	{
+		GdkPixbuf *temp_pixbuf;
+
+		temp_pixbuf = gdk_pixbuf_copy(pixbuf);
+
+		colorshift_pixbuf(temp_pixbuf, pixbuf, 30);
+
+		g_object_unref(pixbuf);
+
+		pixbuf = temp_pixbuf;
+	}
+
+	gdk_draw_pixbuf(icon_info->window, widget->style->black_gc, pixbuf,
+					0, 0, x, y, -1, -1,
+					GDK_RGB_DITHER_NORMAL, 0, 0);
+
+	g_object_unref(pixbuf);
+}
+
+static gint
+sexy_icon_entry_expose(GtkWidget *widget, GdkEventExpose *event)
+{
+	SexyIconEntry *entry;
+
+	g_return_val_if_fail(SEXY_IS_ICON_ENTRY(widget), FALSE);
+	g_return_val_if_fail(event != NULL, FALSE);
+
+	entry = SEXY_ICON_ENTRY(widget);
+
+	if (GTK_WIDGET_DRAWABLE(widget))
+	{
+		gboolean found = FALSE;
+		int i;
+
+		for (i = 0; i < MAX_ICONS && !found; i++)
+		{
+			SexyIconInfo *icon_info = &entry->priv->icons[i];
+
+			if (event->window == icon_info->window)
+			{
+				gint width;
+				GtkAllocation text_area_alloc;
+
+				get_text_area_size(entry, &text_area_alloc);
+				gdk_drawable_get_size(icon_info->window, &width, NULL);
+
+				gtk_paint_flat_box(widget->style, icon_info->window,
+								   GTK_WIDGET_STATE(widget), GTK_SHADOW_NONE,
+								   NULL, widget, "entry_bg",
+								   0, 0, width, text_area_alloc.height);
+
+				draw_icon(widget, i);
+
+				found = TRUE;
+			}
+		}
+
+		if (!found)
+			GTK_WIDGET_CLASS(parent_class)->expose_event(widget, event);
+	}
+
+	return FALSE;
+}
+
+static void
+update_icon(GObject *obj, GParamSpec *param, SexyIconEntry *entry)
+{
+	if (param != NULL)
+	{
+		const char *name = g_param_spec_get_name(param);
+
+		if (strcmp(name, "pixbuf")   && strcmp(name, "stock")  &&
+			strcmp(name, "image")    && strcmp(name, "pixmap") &&
+			strcmp(name, "icon_set") && strcmp(name, "pixbuf_animation"))
+		{
+			return;
+		}
+	}
+
+	gtk_widget_queue_resize(GTK_WIDGET(entry));
+}
+
+static gint
+sexy_icon_entry_enter_notify(GtkWidget *widget, GdkEventCrossing *event)
+{
+	SexyIconEntry *entry = SEXY_ICON_ENTRY(widget);
+	int i;
+
+	for (i = 0; i < MAX_ICONS; i++)
+	{
+		if (event->window == entry->priv->icons[i].window)
+		{
+			if (sexy_icon_entry_get_icon_highlight(entry, i))
+			{
+				entry->priv->icons[i].hovered = TRUE;
+
+				update_icon(NULL, NULL, entry);
+
+				break;
+			}
+		}
+	}
+
+	return FALSE;
+}
+
+static gint
+sexy_icon_entry_leave_notify(GtkWidget *widget, GdkEventCrossing *event)
+{
+	SexyIconEntry *entry = SEXY_ICON_ENTRY(widget);
+	int i;
+
+	for (i = 0; i < MAX_ICONS; i++)
+	{
+		if (event->window == entry->priv->icons[i].window)
+		{
+			if (sexy_icon_entry_get_icon_highlight(entry, i))
+			{
+				entry->priv->icons[i].hovered = FALSE;
+
+				update_icon(NULL, NULL, entry);
+
+				break;
+			}
+		}
+	}
+
+	return FALSE;
+}
+
+static gint
+sexy_icon_entry_button_press(GtkWidget *widget, GdkEventButton *event)
+{
+	SexyIconEntry *entry = SEXY_ICON_ENTRY(widget);
+	int i;
+
+	for (i = 0; i < MAX_ICONS; i++)
+	{
+		if (event->window == entry->priv->icons[i].window)
+		{
+			if (event->button == 1 &&
+				sexy_icon_entry_get_icon_highlight(entry, i))
+			{
+				entry->priv->icons[i].hovered = FALSE;
+
+				update_icon(NULL, NULL, entry);
+			}
+
+			g_signal_emit(entry, signals[ICON_PRESSED], 0, i, event->button);
+
+			return TRUE;
+		}
+	}
+
+	if (GTK_WIDGET_CLASS(parent_class)->button_press_event)
+		return GTK_WIDGET_CLASS(parent_class)->button_press_event(widget,
+																  event);
+
+	return FALSE;
+}
+
+static gint
+sexy_icon_entry_button_release(GtkWidget *widget, GdkEventButton *event)
+{
+	SexyIconEntry *entry = SEXY_ICON_ENTRY(widget);
+	int i;
+
+	for (i = 0; i < MAX_ICONS; i++)
+	{
+		GdkWindow *icon_window = entry->priv->icons[i].window;
+
+		if (event->window == icon_window)
+		{
+			int width, height;
+			gdk_drawable_get_size(icon_window, &width, &height);
+
+			if (event->button == 1 &&
+				sexy_icon_entry_get_icon_highlight(entry, i) &&
+				event->x >= 0     && event->y >= 0 &&
+				event->x <= width && event->y <= height)
+			{
+				entry->priv->icons[i].hovered = TRUE;
+
+				update_icon(NULL, NULL, entry);
+			}
+
+			g_signal_emit(entry, signals[ICON_RELEASED], 0, i, event->button);
+
+			return TRUE;
+		}
+	}
+
+	if (GTK_WIDGET_CLASS(parent_class)->button_release_event)
+		return GTK_WIDGET_CLASS(parent_class)->button_release_event(widget,
+																	event);
+
+	return FALSE;
+}
+
+
+static void
+sexy_icon_entry_changed (GtkEditable *editable)
+{
+	SexyIconEntry *entry = SEXY_ICON_ENTRY(editable);
+	char          *value;
+	gboolean       empty;
+	
+	value = gtk_editable_get_chars (editable, 0, -1);
+	empty = (value == NULL) || (*value == '\0');
+	if (empty != entry->priv->empty) {
+		entry->priv->empty = empty;
+		gtk_widget_queue_resize (GTK_WIDGET (entry));
+	}	
+	g_free (value);
+}
+
+
+/**
+ * sexy_icon_entry_new
+ *
+ * Creates a new SexyIconEntry widget.
+ *
+ * Returns a new #SexyIconEntry.
+ */
+GtkWidget *
+sexy_icon_entry_new(void)
+{
+	return GTK_WIDGET(g_object_new(SEXY_TYPE_ICON_ENTRY, NULL));
+}
+
+/**
+ * sexy_icon_entry_set_icon
+ * @entry: A #SexyIconEntry.
+ * @position: Icon position.
+ * @icon: A #GtkImage to set as the icon.
+ *
+ * Sets the icon shown in the entry
+ */
+void
+sexy_icon_entry_set_icon(SexyIconEntry *entry, SexyIconEntryPosition icon_pos,
+						 GtkImage *icon)
+{
+	SexyIconInfo *icon_info;
+
+	g_return_if_fail(entry != NULL);
+	g_return_if_fail(SEXY_IS_ICON_ENTRY(entry));
+	g_return_if_fail(IS_VALID_ICON_ENTRY_POSITION(icon_pos));
+	g_return_if_fail(icon == NULL || GTK_IS_IMAGE(icon));
+
+	icon_info = &entry->priv->icons[icon_pos];
+
+	if (icon == icon_info->icon)
+		return;
+
+	icon_info->hide_if_empty = FALSE;
+
+	if (icon_pos == SEXY_ICON_ENTRY_SECONDARY &&
+		entry->priv->icon_released_id != 0)
+	{
+		g_signal_handler_disconnect(entry, entry->priv->icon_released_id);
+		entry->priv->icon_released_id = 0;
+	}
+
+	if (icon == NULL)
+	{
+		if (icon_info->icon != NULL)
+		{
+			gtk_widget_destroy(GTK_WIDGET(icon_info->icon));
+			icon_info->icon = NULL;
+
+			/*
+			 * Explicitly check, as the pointer may become invalidated
+			 * during destruction.
+			 */
+			if (icon_info->window != NULL && GDK_IS_WINDOW(icon_info->window))
+				gdk_window_hide(icon_info->window);
+		}
+	}
+	else
+	{
+		if (icon_info->window != NULL && icon_info->icon == NULL)
+			gdk_window_show(icon_info->window);
+
+		g_signal_connect(G_OBJECT(icon), "notify",
+						 G_CALLBACK(update_icon), entry);
+
+		icon_info->icon = icon;
+		g_object_ref(icon);
+	}
+
+	update_icon(NULL, NULL, entry);
+}
+
+/**
+ * sexy_icon_entry_set_icon_highlight
+ * @entry: A #SexyIconEntry;
+ * @position: Icon position.
+ * @highlight: TRUE if the icon should highlight on mouse-over
+ *
+ * Determines whether the icon will highlight on mouse-over.
+ */
+void
+sexy_icon_entry_set_icon_highlight(SexyIconEntry *entry,
+								   SexyIconEntryPosition icon_pos,
+								   gboolean highlight)
+{
+	SexyIconInfo *icon_info;
+
+	g_return_if_fail(entry != NULL);
+	g_return_if_fail(SEXY_IS_ICON_ENTRY(entry));
+	g_return_if_fail(IS_VALID_ICON_ENTRY_POSITION(icon_pos));
+
+	icon_info = &entry->priv->icons[icon_pos];
+
+	if (icon_info->highlight == highlight)
+		return;
+
+	icon_info->highlight = highlight;
+}
+
+/**
+ * sexy_icon_entry_set_hide_if_empty
+ * @entry: A #SexyIconEntry;
+ * @position: Icon position.
+ * @hide: TRUE if the icon should hidden when the entry is empty
+ *
+ * Determines whether the icon will be hidden when the entry is empty.
+ */
+void 
+sexy_icon_entry_set_hide_if_empty (SexyIconEntry *entry,
+				   SexyIconEntryPosition icon_pos,
+				   gboolean hide)
+{
+	SexyIconInfo *icon_info;
+
+	g_return_if_fail(entry != NULL);
+	g_return_if_fail(SEXY_IS_ICON_ENTRY(entry));
+	g_return_if_fail(IS_VALID_ICON_ENTRY_POSITION(icon_pos));
+
+	icon_info = &entry->priv->icons[icon_pos];
+	icon_info->hide_if_empty = hide;
+}
+
+/**
+ * sexy_icon_entry_get_icon
+ * @entry: A #SexyIconEntry.
+ * @position: Icon position.
+ *
+ * Retrieves the image used for the icon
+ *
+ * Returns: A #GtkImage.
+ */
+GtkImage *
+sexy_icon_entry_get_icon(const SexyIconEntry *entry,
+						 SexyIconEntryPosition icon_pos)
+{
+	g_return_val_if_fail(entry != NULL, NULL);
+	g_return_val_if_fail(SEXY_IS_ICON_ENTRY(entry), NULL);
+	g_return_val_if_fail(IS_VALID_ICON_ENTRY_POSITION(icon_pos), NULL);
+
+	return entry->priv->icons[icon_pos].icon;
+}
+
+/**
+ * sexy_icon_entry_get_icon_highlight
+ * @entry: A #SexyIconEntry.
+ * @position: Icon position.
+ *
+ * Retrieves whether entry will highlight the icon on mouseover.
+ *
+ * Returns: TRUE if icon highlights.
+ */
+gboolean
+sexy_icon_entry_get_icon_highlight(const SexyIconEntry *entry,
+								   SexyIconEntryPosition icon_pos)
+{
+	g_return_val_if_fail(entry != NULL, FALSE);
+	g_return_val_if_fail(SEXY_IS_ICON_ENTRY(entry), FALSE);
+	g_return_val_if_fail(IS_VALID_ICON_ENTRY_POSITION(icon_pos), FALSE);
+
+	return entry->priv->icons[icon_pos].highlight;
+}
+
+static void
+clear_button_clicked_cb(SexyIconEntry *icon_entry,
+						SexyIconEntryPosition icon_pos,
+						int button)
+{
+	if (icon_pos != SEXY_ICON_ENTRY_SECONDARY || button != 1)
+		return;
+
+	gtk_entry_set_text(GTK_ENTRY(icon_entry), "");
+}
+
+/**
+ * sexy_icon_entry_add_clear_button
+ * @icon_entry: A #SexyIconEntry.
+ *
+ * A convenience function to add a clear button to the end of the entry.
+ * This is useful for search boxes.
+ */
+void
+sexy_icon_entry_add_clear_button(SexyIconEntry *icon_entry)
+{
+	GtkWidget *icon;
+
+	g_return_if_fail(icon_entry != NULL);
+	g_return_if_fail(SEXY_IS_ICON_ENTRY(icon_entry));
+
+	icon = gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU);
+	gtk_widget_show(icon);
+	sexy_icon_entry_set_icon(SEXY_ICON_ENTRY(icon_entry),
+							 SEXY_ICON_ENTRY_SECONDARY,
+							 GTK_IMAGE(icon));
+	sexy_icon_entry_set_icon_highlight(SEXY_ICON_ENTRY(icon_entry),
+									   SEXY_ICON_ENTRY_SECONDARY, TRUE);
+	sexy_icon_entry_set_hide_if_empty(SEXY_ICON_ENTRY(icon_entry),
+									   SEXY_ICON_ENTRY_SECONDARY, TRUE);
+
+	if (icon_entry->priv->icon_released_id != 0)
+	{
+		g_signal_handler_disconnect(icon_entry,
+									icon_entry->priv->icon_released_id);
+	}
+
+	icon_entry->priv->icon_released_id =
+		g_signal_connect(G_OBJECT(icon_entry), "icon_released",
+						 G_CALLBACK(clear_button_clicked_cb), NULL);
+}

Added: trunk/src/sexy-icon-entry.h
==============================================================================
--- (empty file)
+++ trunk/src/sexy-icon-entry.h	Wed Jan  9 15:56:13 2008
@@ -0,0 +1,127 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  File-Roller
+ *
+ *  Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+ 
+/* Based on libsexy 0.1.11.  Original copyright note follows:
+ *  
+ * @file libsexy/sexy-icon-entry.h Entry widget
+ *
+ * @Copyright (C) 2004-2006 Christian Hammond.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA  02111-1307, USA.
+ */
+#ifndef _SEXY_ICON_ENTRY_H_
+#define _SEXY_ICON_ENTRY_H_
+
+typedef struct _SexyIconEntry      SexyIconEntry;
+typedef struct _SexyIconEntryClass SexyIconEntryClass;
+typedef struct _SexyIconEntryPriv  SexyIconEntryPriv;
+
+#include <gtk/gtkentry.h>
+#include <gtk/gtkimage.h>
+
+#define SEXY_TYPE_ICON_ENTRY (sexy_icon_entry_get_type())
+#define SEXY_ICON_ENTRY(obj) \
+		(G_TYPE_CHECK_INSTANCE_CAST((obj), SEXY_TYPE_ICON_ENTRY, SexyIconEntry))
+#define SEXY_ICON_ENTRY_CLASS(klass) \
+		(G_TYPE_CHECK_CLASS_CAST((klass), SEXY_TYPE_ICON_ENTRY, SexyIconEntryClass))
+#define SEXY_IS_ICON_ENTRY(obj) \
+		(G_TYPE_CHECK_INSTANCE_TYPE((obj), SEXY_TYPE_ICON_ENTRY))
+#define SEXY_IS_ICON_ENTRY_CLASS(klass) \
+		(G_TYPE_CHECK_CLASS_TYPE((klass), SEXY_TYPE_ICON_ENTRY))
+#define SEXY_ICON_ENTRY_GET_CLASS(obj) \
+		(G_TYPE_INSTANCE_GET_CLASS ((obj), SEXY_TYPE_ICON_ENTRY, SexyIconEntryClass))
+
+typedef enum
+{
+	SEXY_ICON_ENTRY_PRIMARY,
+	SEXY_ICON_ENTRY_SECONDARY
+
+} SexyIconEntryPosition;
+
+struct _SexyIconEntry
+{
+	GtkEntry parent_object;
+
+	SexyIconEntryPriv *priv;
+
+	void (*gtk_reserved1)(void);
+	void (*gtk_reserved2)(void);
+	void (*gtk_reserved3)(void);
+	void (*gtk_reserved4)(void);
+};
+
+struct _SexyIconEntryClass
+{
+	GtkEntryClass parent_class;
+
+	/* Signals */
+	void (*icon_pressed)(SexyIconEntry *entry, SexyIconEntryPosition icon_pos,
+						 int button);
+	void (*icon_released)(SexyIconEntry *entry, SexyIconEntryPosition icon_pos,
+						  int button);
+
+	void (*gtk_reserved1)(void);
+	void (*gtk_reserved2)(void);
+	void (*gtk_reserved3)(void);
+	void (*gtk_reserved4)(void);
+};
+
+G_BEGIN_DECLS
+
+GType sexy_icon_entry_get_type(void);
+
+GtkWidget *sexy_icon_entry_new(void);
+
+void sexy_icon_entry_set_icon(SexyIconEntry *entry,
+							  SexyIconEntryPosition position,
+							  GtkImage *icon);
+
+void sexy_icon_entry_set_icon_highlight(SexyIconEntry *entry,
+										SexyIconEntryPosition position,
+										gboolean highlight);
+
+void sexy_icon_entry_set_hide_if_empty(SexyIconEntry *entry,
+										SexyIconEntryPosition position,
+										gboolean hide);
+
+GtkImage *sexy_icon_entry_get_icon(const SexyIconEntry *entry,
+								   SexyIconEntryPosition position);
+
+gboolean sexy_icon_entry_get_icon_highlight(const SexyIconEntry *entry,
+											SexyIconEntryPosition position);
+void sexy_icon_entry_add_clear_button(SexyIconEntry *icon_entry);
+
+G_END_DECLS
+
+#endif

Modified: trunk/src/ui.h
==============================================================================
--- trunk/src/ui.h	(original)
+++ trunk/src/ui.h	Wed Jan  9 15:56:13 2008
@@ -124,6 +124,11 @@
 	  N_("Extract"), NULL,
 	  N_("Extract files from the archive"),
 	  G_CALLBACK (activate_action_extract) },
+	{ "Find", GTK_STOCK_FIND,
+	  NULL, NULL,
+	  NULL,
+	  G_CALLBACK (activate_action_find) },
+	  
 	{ "LastOutput", NULL,
 	  N_("_Last Output"), NULL,
 	  N_("View the output produced by the last executed command"),
@@ -279,6 +284,8 @@
 "      <menuitem action='SelectAll'/>"
 "      <menuitem action='DeselectAll'/>"
 "      <separator/>"
+"      <menuitem action='Find'/>"
+"      <separator/>"
 "      <menuitem action='Password'/>"
 "    </menu>"
 "    <menu action='ViewMenu'>"



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]