2 * Copyright (C) 2002 Anders Carlsson <andersca@gnu.org>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 * Boston, MA 02111-1307, USA.
20 #include "eggiconlist.h"
23 #include <gdk/gdkkeysyms.h>
24 #include <gtk/gtkbindings.h>
25 #include <gtk/gtkdnd.h>
26 #include <gtk/gtkmain.h>
27 #include <gtk/gtksignal.h>
30 #include "eggmarshalers.h"
33 #define MINIMUM_ICON_ITEM_WIDTH 100
34 #define ICON_TEXT_PADDING 3
36 #define ICON_LIST_ITEM_DATA "egg-icon-list-item-data"
38 struct _EggIconListItem
42 EggIconList *icon_list;
49 GDestroyNotify destroy_notify;
55 gint pixbuf_x, pixbuf_y;
56 gint pixbuf_height, pixbuf_width;
58 gint layout_x, layout_y;
59 gint layout_width, layout_height;
62 guint selected_before_rubberbanding : 1;
65 struct _EggIconListPrivate
69 GtkSelectionMode selection_mode;
71 GdkWindow *bin_window;
77 GtkAdjustment *hadjustment;
78 GtkAdjustment *vadjustment;
82 gboolean rubberbanding;
83 gint rubberband_x1, rubberband_y1;
84 gint rubberband_x2, rubberband_y2;
86 guint scroll_timeout_id;
87 gint scroll_value_diff;
88 gint event_last_x, event_last_y;
90 EggIconListItem *cursor_item;
92 char *typeahead_string;
96 GtkSortType sort_order;
98 EggIconListItemCompareFunc sort_func;
100 GDestroyNotify sort_destroy_notify;
102 EggIconListItem *last_single_clicked;
109 /* Layout used to draw icon text */
137 /* Icon List Item properties */
144 static void egg_icon_list_class_init (EggIconListClass *klass);
145 static void egg_icon_list_init (EggIconList *icon_list);
147 /* GObject signals */
148 static void egg_icon_list_finalize (GObject *object);
149 static void egg_icon_list_set_property (GObject *object,
153 static void egg_icon_list_get_property (GObject *object,
159 /* GtkWidget signals */
160 static void egg_icon_list_realize (GtkWidget *widget);
161 static void egg_icon_list_unrealize (GtkWidget *widget);
162 static void egg_icon_list_map (GtkWidget *widget);
163 static void egg_icon_list_size_request (GtkWidget *widget,
164 GtkRequisition *requisition);
165 static void egg_icon_list_size_allocate (GtkWidget *widget,
166 GtkAllocation *allocation);
167 static gboolean egg_icon_list_expose (GtkWidget *widget,
168 GdkEventExpose *expose);
169 static gboolean egg_icon_list_motion (GtkWidget *widget,
170 GdkEventMotion *event);
171 static gboolean egg_icon_list_button_press (GtkWidget *widget,
172 GdkEventButton *event);
173 static gboolean egg_icon_list_button_release (GtkWidget *widget,
174 GdkEventButton *event);
175 static gboolean egg_icon_list_key_press (GtkWidget *widget,
179 /* EggIconList signals */
180 static void egg_icon_list_set_adjustments (EggIconList *icon_list,
182 GtkAdjustment *vadj);
183 static void egg_icon_list_real_select_all (EggIconList *icon_list);
184 static void egg_icon_list_real_unselect_all (EggIconList *icon_list);
185 static void egg_icon_list_real_select_cursor_item (EggIconList *icon_list);
186 static void egg_icon_list_real_toggle_cursor_item (EggIconList *icon_list);
188 /* Internal functions */
189 static void egg_icon_list_adjustment_changed (GtkAdjustment *adjustment,
190 EggIconList *icon_list);
191 static void egg_icon_list_layout (EggIconList *icon_list);
192 static void egg_icon_list_paint_item (EggIconList *icon_list,
193 EggIconListItem *item,
195 static void egg_icon_list_paint_rubberband (EggIconList *icon_list,
197 static void egg_icon_list_queue_draw_item (EggIconList *icon_list,
198 EggIconListItem *item);
199 static void egg_icon_list_queue_layout (EggIconList *icon_list);
200 static void egg_icon_list_set_cursor_item (EggIconList *icon_list,
201 EggIconListItem *item);
202 static void egg_icon_list_append_typeahead_string (EggIconList *icon_list,
203 const gchar *string);
204 static void egg_icon_list_select_first_matching_item (EggIconList *icon_list,
205 const char *pattern);
206 static void egg_icon_list_start_rubberbanding (EggIconList *icon_list,
209 static void egg_icon_list_stop_rubberbanding (EggIconList *icon_list);
210 static void egg_icon_list_sort (EggIconList *icon_list);
211 static gint egg_icon_list_sort_func (EggIconListItem *a,
213 EggIconList *icon_list);
214 static void egg_icon_list_insert_item_sorted (EggIconList *icon_list,
215 EggIconListItem *item);
216 static void egg_icon_list_validate (EggIconList *icon_list);
217 static void egg_icon_list_update_rubberband_selection (EggIconList *icon_list);
218 static gboolean egg_icon_list_item_hit_test (EggIconListItem *item,
223 static gboolean egg_icon_list_maybe_begin_dragging_items (EggIconList *icon_list,
224 GdkEventMotion *event);
225 static gboolean egg_icon_list_unselect_all_internal (EggIconList *icon_list,
227 static void egg_icon_list_calculate_item_size (EggIconList *icon_list, EggIconListItem *item);
228 static void rubberbanding (gpointer data);
231 static void egg_icon_list_item_invalidate_size (EggIconListItem *item);
233 static GtkContainerClass *parent_class = NULL;
234 static guint icon_list_signals[LAST_SIGNAL] = { 0 };
237 egg_icon_list_item_get_type (void)
239 static GType boxed_type = 0;
242 boxed_type = g_boxed_type_register_static ("EggIconListItem",
243 (GBoxedCopyFunc) egg_icon_list_item_ref,
244 (GBoxedFreeFunc) egg_icon_list_item_unref);
250 egg_icon_list_get_type (void)
252 static GType object_type = 0;
256 static const GTypeInfo object_info =
258 sizeof (EggIconListClass),
259 NULL, /* base_init */
260 NULL, /* base_finalize */
261 (GClassInitFunc) egg_icon_list_class_init,
262 NULL, /* class_finalize */
263 NULL, /* class_data */
264 sizeof (EggIconList),
266 (GInstanceInitFunc) egg_icon_list_init
269 object_type = g_type_register_static (GTK_TYPE_CONTAINER, "EggIconList", &object_info, 0);
276 egg_icon_list_class_init (EggIconListClass *klass)
278 GObjectClass *gobject_class;
279 GtkWidgetClass *widget_class;
280 GtkBindingSet *binding_set;
282 parent_class = g_type_class_peek_parent (klass);
283 binding_set = gtk_binding_set_by_class (klass);
285 gobject_class = (GObjectClass *) klass;
286 widget_class = (GtkWidgetClass *) klass;
288 gobject_class->finalize = egg_icon_list_finalize;
289 gobject_class->set_property = egg_icon_list_set_property;
290 gobject_class->get_property = egg_icon_list_get_property;
292 widget_class->realize = egg_icon_list_realize;
293 widget_class->unrealize = egg_icon_list_unrealize;
294 widget_class->map = egg_icon_list_map;
295 widget_class->size_request = egg_icon_list_size_request;
296 widget_class->size_allocate = egg_icon_list_size_allocate;
297 widget_class->expose_event = egg_icon_list_expose;
298 widget_class->motion_notify_event = egg_icon_list_motion;
299 widget_class->button_press_event = egg_icon_list_button_press;
300 widget_class->button_release_event = egg_icon_list_button_release;
301 widget_class->key_press_event = egg_icon_list_key_press;
303 klass->set_scroll_adjustments = egg_icon_list_set_adjustments;
304 klass->select_all = egg_icon_list_real_select_all;
305 klass->unselect_all = egg_icon_list_real_unselect_all;
306 klass->select_cursor_item = egg_icon_list_real_select_cursor_item;
307 klass->toggle_cursor_item = egg_icon_list_real_toggle_cursor_item;
310 g_object_class_install_property (gobject_class,
312 g_param_spec_enum ("selection_mode",
314 _("The selection mode"),
315 GTK_TYPE_SELECTION_MODE,
316 GTK_SELECTION_SINGLE,
319 g_object_class_install_property (gobject_class,
321 g_param_spec_boolean ("sorted",
323 _("Icon list is sorted"),
326 g_object_class_install_property (gobject_class,
328 g_param_spec_enum ("sort_order",
330 _("Sort direction the icon list should use"),
333 G_PARAM_READABLE | G_PARAM_WRITABLE));
335 /* Style properties */
336 #define _ICON_LIST_TOP_MARGIN 6
337 #define _ICON_LIST_BOTTOM_MARGIN 6
338 #define _ICON_LIST_LEFT_MARGIN 6
339 #define _ICON_LIST_RIGHT_MARGIN 6
340 #define _ICON_LIST_ICON_PADDING 6
342 gtk_widget_class_install_style_property (widget_class,
343 g_param_spec_int ("icon_padding",
345 _("Number of pixels between icons"),
348 _ICON_LIST_ICON_PADDING,
350 gtk_widget_class_install_style_property (widget_class,
351 g_param_spec_int ("top_margin",
353 _("Number of pixels in top margin"),
356 _ICON_LIST_TOP_MARGIN,
358 gtk_widget_class_install_style_property (widget_class,
359 g_param_spec_int ("bottom_margin",
361 _("Number of pixels in bottom margin"),
364 _ICON_LIST_BOTTOM_MARGIN,
367 gtk_widget_class_install_style_property (widget_class,
368 g_param_spec_int ("left_margin",
370 _("Number of pixels in left margin"),
373 _ICON_LIST_LEFT_MARGIN,
375 gtk_widget_class_install_style_property (widget_class,
376 g_param_spec_int ("right_margin",
378 _("Number of pixels in right margin"),
381 _ICON_LIST_RIGHT_MARGIN,
384 gtk_widget_class_install_style_property (widget_class,
385 g_param_spec_boxed ("selection_box_color",
386 _("Selection Box Color"),
387 _("Color of the selection box"),
391 gtk_widget_class_install_style_property (widget_class,
392 g_param_spec_uchar ("selection_box_alpha",
393 _("Selection Box Alpha"),
394 _("Opacity of the selection box"),
400 widget_class->set_scroll_adjustments_signal =
401 g_signal_new ("set_scroll_adjustments",
402 G_TYPE_FROM_CLASS (gobject_class),
404 G_STRUCT_OFFSET (EggIconListClass, set_scroll_adjustments),
406 _egg_marshal_VOID__OBJECT_OBJECT,
408 GTK_TYPE_ADJUSTMENT, GTK_TYPE_ADJUSTMENT);
410 icon_list_signals[ITEM_ACTIVATED] =
411 g_signal_new ("item_activated",
412 G_TYPE_FROM_CLASS (gobject_class),
414 G_STRUCT_OFFSET (EggIconListClass, item_activated),
416 g_cclosure_marshal_VOID__BOXED,
418 EGG_TYPE_ICON_LIST_ITEM);
420 icon_list_signals[SELECTION_CHANGED] =
421 g_signal_new ("selection_changed",
422 G_TYPE_FROM_CLASS (gobject_class),
424 G_STRUCT_OFFSET (EggIconListClass, selection_changed),
426 g_cclosure_marshal_VOID__VOID,
429 icon_list_signals[ITEM_ADDED] =
430 g_signal_new ("item_added",
431 G_TYPE_FROM_CLASS (gobject_class),
433 G_STRUCT_OFFSET (EggIconListClass, item_added),
435 g_cclosure_marshal_VOID__BOXED,
436 G_TYPE_NONE, 1, EGG_TYPE_ICON_LIST_ITEM);
438 icon_list_signals[ITEM_REMOVED] =
439 g_signal_new ("item_removed",
440 G_TYPE_FROM_CLASS (gobject_class),
442 G_STRUCT_OFFSET (EggIconListClass, item_removed),
444 g_cclosure_marshal_VOID__BOXED,
445 G_TYPE_NONE, 1, EGG_TYPE_ICON_LIST_ITEM);
447 icon_list_signals[SELECT_ALL] =
448 g_signal_new ("select_all",
449 G_TYPE_FROM_CLASS (gobject_class),
450 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
451 G_STRUCT_OFFSET (EggIconListClass, select_all),
453 g_cclosure_marshal_VOID__VOID,
456 icon_list_signals[UNSELECT_ALL] =
457 g_signal_new ("unselect_all",
458 G_TYPE_FROM_CLASS (gobject_class),
459 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
460 G_STRUCT_OFFSET (EggIconListClass, unselect_all),
462 g_cclosure_marshal_VOID__VOID,
465 icon_list_signals[SELECT_CURSOR_ITEM] =
466 g_signal_new ("select_cursor_item",
467 G_TYPE_FROM_CLASS (gobject_class),
468 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
469 G_STRUCT_OFFSET (EggIconListClass, select_cursor_item),
471 g_cclosure_marshal_VOID__VOID,
474 icon_list_signals[SELECT_CURSOR_ITEM] =
475 g_signal_new ("toggle_cursor_item",
476 G_TYPE_FROM_CLASS (gobject_class),
477 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
478 G_STRUCT_OFFSET (EggIconListClass, toggle_cursor_item),
480 g_cclosure_marshal_VOID__VOID,
484 gtk_binding_entry_add_signal (binding_set, GDK_a, GDK_CONTROL_MASK, "select_all", 0);
485 gtk_binding_entry_add_signal (binding_set, GDK_a, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "unselect_all", 0);
486 gtk_binding_entry_add_signal (binding_set, GDK_space, 0, "select_cursor_item", 0);
487 gtk_binding_entry_add_signal (binding_set, GDK_space, GDK_CONTROL_MASK, "toggle_cursor_item", 0);
492 egg_icon_list_init (EggIconList *icon_list)
494 icon_list->priv = g_new0 (EggIconListPrivate, 1);
495 GTK_WIDGET_SET_FLAGS (icon_list, GTK_CAN_FOCUS);
497 icon_list->priv->width = 0;
498 icon_list->priv->height = 0;
499 icon_list->priv->selection_mode = GTK_SELECTION_SINGLE;
500 icon_list->priv->sort_order = GTK_SORT_ASCENDING;
501 icon_list->priv->pressed_button = -1;
502 icon_list->priv->press_start_x = -1;
503 icon_list->priv->press_start_y = -1;
504 icon_list->priv->layout = gtk_widget_create_pango_layout (GTK_WIDGET (icon_list), NULL);
505 pango_layout_set_wrap (icon_list->priv->layout, PANGO_WRAP_CHAR);
507 egg_icon_list_set_adjustments (icon_list, NULL, NULL);
511 /* GObject methods */
513 egg_icon_list_finalize (GObject *object)
515 EggIconList *icon_list;
517 icon_list = EGG_ICON_LIST (object);
519 /* FIXME: Put in destroy */
521 if (icon_list->priv->layout_idle_id != 0)
522 g_source_remove (icon_list->priv->layout_idle_id);
524 if (icon_list->priv->scroll_timeout_id != 0)
525 g_source_remove (icon_list->priv->scroll_timeout_id);
527 g_free (icon_list->priv);
529 (G_OBJECT_CLASS (parent_class)->finalize) (object);
534 egg_icon_list_set_property (GObject *object,
539 EggIconList *icon_list;
541 icon_list = EGG_ICON_LIST (object);
545 case PROP_SELECTION_MODE:
546 egg_icon_list_set_selection_mode (icon_list, g_value_get_enum (value));
549 egg_icon_list_set_sorted (icon_list, g_value_get_boolean (value));
551 case PROP_SORT_ORDER:
552 egg_icon_list_set_sort_order (icon_list, g_value_get_enum (value));
555 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
561 egg_icon_list_get_property (GObject *object,
566 EggIconList *icon_list;
568 icon_list = EGG_ICON_LIST (object);
572 case PROP_SELECTION_MODE:
573 g_value_set_enum (value, icon_list->priv->selection_mode);
576 g_value_set_boolean (value, icon_list->priv->sorted);
578 case PROP_SORT_ORDER:
579 g_value_set_enum (value, icon_list->priv->sort_order);
582 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
587 /* GtkWidget signals */
589 egg_icon_list_realize (GtkWidget *widget)
591 EggIconList *icon_list;
592 GdkWindowAttr attributes;
593 gint attributes_mask;
595 icon_list = EGG_ICON_LIST (widget);
597 GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
599 /* Make the main, clipping window */
600 attributes.window_type = GDK_WINDOW_CHILD;
601 attributes.x = widget->allocation.x;
602 attributes.y = widget->allocation.y;
603 attributes.width = widget->allocation.width;
604 attributes.height = widget->allocation.height;
605 attributes.wclass = GDK_INPUT_OUTPUT;
606 attributes.visual = gtk_widget_get_visual (widget);
607 attributes.colormap = gtk_widget_get_colormap (widget);
608 attributes.event_mask = GDK_VISIBILITY_NOTIFY_MASK;
610 attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
612 widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
613 &attributes, attributes_mask);
614 gdk_window_set_user_data (widget->window, widget);
616 /* Make the window for the icon list */
619 attributes.width = MAX (icon_list->priv->width, widget->allocation.width);
620 attributes.height = MAX (icon_list->priv->height, widget->allocation.height);
621 attributes.event_mask = (GDK_EXPOSURE_MASK |
623 GDK_POINTER_MOTION_MASK |
624 GDK_BUTTON_PRESS_MASK |
625 GDK_BUTTON_RELEASE_MASK |
627 GDK_KEY_RELEASE_MASK) |
628 gtk_widget_get_events (widget);
630 icon_list->priv->bin_window = gdk_window_new (widget->window,
631 &attributes, attributes_mask);
632 gdk_window_set_user_data (icon_list->priv->bin_window, widget);
634 widget->style = gtk_style_attach (widget->style, widget->window);
635 gdk_window_set_background (icon_list->priv->bin_window, &widget->style->base[widget->state]);
636 gdk_window_set_background (widget->window, &widget->style->base[widget->state]);
642 egg_icon_list_unrealize (GtkWidget *widget)
644 EggIconList *icon_list;
646 icon_list = EGG_ICON_LIST (widget);
648 gdk_window_set_user_data (icon_list->priv->bin_window, NULL);
649 gdk_window_destroy (icon_list->priv->bin_window);
650 icon_list->priv->bin_window = NULL;
652 /* GtkWidget::unrealize destroys children and widget->window */
653 if (GTK_WIDGET_CLASS (parent_class)->unrealize)
654 (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
658 egg_icon_list_map (GtkWidget *widget)
660 EggIconList *icon_list;
662 icon_list = EGG_ICON_LIST (widget);
664 GTK_WIDGET_SET_FLAGS (widget, GTK_MAPPED);
666 gdk_window_show (icon_list->priv->bin_window);
667 gdk_window_show (widget->window);
671 egg_icon_list_size_request (GtkWidget *widget,
672 GtkRequisition *requisition)
674 EggIconList *icon_list;
676 icon_list = EGG_ICON_LIST (widget);
678 requisition->width = icon_list->priv->width;
679 requisition->height = icon_list->priv->height;
683 egg_icon_list_size_allocate (GtkWidget *widget,
684 GtkAllocation *allocation)
686 EggIconList *icon_list;
688 widget->allocation = *allocation;
690 icon_list = EGG_ICON_LIST (widget);
692 if (GTK_WIDGET_REALIZED (widget))
694 gdk_window_move_resize (widget->window,
695 allocation->x, allocation->y,
696 allocation->width, allocation->height);
697 gdk_window_resize (icon_list->priv->bin_window,
698 MAX (icon_list->priv->width, allocation->width),
699 MAX (icon_list->priv->height, allocation->height));
702 icon_list->priv->hadjustment->page_size = allocation->width;
703 icon_list->priv->hadjustment->page_increment = allocation->width * 0.9;
704 icon_list->priv->hadjustment->step_increment = allocation->width * 0.1;
705 icon_list->priv->hadjustment->lower = 0;
706 icon_list->priv->hadjustment->upper = MAX (allocation->width, icon_list->priv->width);
707 gtk_adjustment_changed (icon_list->priv->hadjustment);
709 icon_list->priv->vadjustment->page_size = allocation->height;
710 icon_list->priv->vadjustment->page_increment = allocation->height * 0.9;
711 icon_list->priv->vadjustment->step_increment = allocation->width * 0.1;
712 icon_list->priv->vadjustment->lower = 0;
713 icon_list->priv->vadjustment->upper = MAX (allocation->height, icon_list->priv->height);
714 gtk_adjustment_changed (icon_list->priv->vadjustment);
716 egg_icon_list_layout (icon_list);
720 egg_icon_list_expose (GtkWidget *widget,
721 GdkEventExpose *expose)
723 EggIconList *icon_list;
726 icon_list = EGG_ICON_LIST (widget);
728 if (expose->window != icon_list->priv->bin_window)
731 for (icons = icon_list->priv->items; icons; icons = icons->next) {
732 EggIconListItem *item = icons->data;
733 GdkRectangle item_rectangle;
735 item_rectangle.x = item->x;
736 item_rectangle.y = item->y;
737 item_rectangle.width = item->width;
738 item_rectangle.height = item->height;
740 if (gdk_region_rect_in (expose->region, &item_rectangle) == GDK_OVERLAP_RECTANGLE_OUT)
743 egg_icon_list_paint_item (icon_list, item, &expose->area);
746 if (icon_list->priv->rubberbanding)
748 GdkRectangle *rectangles;
751 gdk_region_get_rectangles (expose->region,
755 while (n_rectangles--)
756 egg_icon_list_paint_rubberband (icon_list, &rectangles[n_rectangles]);
765 scroll_timeout (gpointer data)
767 EggIconList *icon_list;
772 value = MIN (icon_list->priv->vadjustment->value +
773 icon_list->priv->scroll_value_diff,
774 icon_list->priv->vadjustment->upper -
775 icon_list->priv->vadjustment->page_size);
777 gtk_adjustment_set_value (icon_list->priv->vadjustment,
780 rubberbanding (icon_list);
786 egg_icon_list_motion (GtkWidget *widget,
787 GdkEventMotion *event)
789 EggIconList *icon_list;
792 icon_list = EGG_ICON_LIST (widget);
794 egg_icon_list_maybe_begin_dragging_items (icon_list, event);
796 if (icon_list->priv->rubberbanding)
798 rubberbanding (widget);
800 abs_y = event->y - icon_list->priv->height *
801 (icon_list->priv->vadjustment->value /
802 (icon_list->priv->vadjustment->upper -
803 icon_list->priv->vadjustment->lower));
805 if (abs_y < 0 || abs_y > widget->allocation.height)
807 if (icon_list->priv->scroll_timeout_id == 0)
808 icon_list->priv->scroll_timeout_id = g_timeout_add (30, scroll_timeout, icon_list);
811 icon_list->priv->scroll_value_diff = abs_y;
813 icon_list->priv->scroll_value_diff = abs_y - widget->allocation.height;
815 icon_list->priv->event_last_x = event->x;
816 icon_list->priv->event_last_y = event->y;
818 else if (icon_list->priv->scroll_timeout_id != 0)
820 g_source_remove (icon_list->priv->scroll_timeout_id);
822 icon_list->priv->scroll_timeout_id = 0;
830 egg_icon_list_button_press (GtkWidget *widget,
831 GdkEventButton *event)
833 EggIconList *icon_list;
834 EggIconListItem *item;
835 gboolean dirty = FALSE;
837 icon_list = EGG_ICON_LIST (widget);
839 if (event->window != icon_list->priv->bin_window)
842 if (!GTK_WIDGET_HAS_FOCUS (widget))
843 gtk_widget_grab_focus (widget);
845 if (event->button == 1 && event->type == GDK_BUTTON_PRESS)
848 if (icon_list->priv->selection_mode == GTK_SELECTION_NONE)
851 item = egg_icon_list_get_item_at_pos (icon_list,
856 if (icon_list->priv->selection_mode == GTK_SELECTION_MULTIPLE &&
857 (event->state & GDK_CONTROL_MASK))
859 item->selected = !item->selected;
866 egg_icon_list_unselect_all_internal (icon_list, FALSE);
868 item->selected = TRUE;
873 egg_icon_list_set_cursor_item (icon_list, item);
874 egg_icon_list_queue_draw_item (icon_list, item);
876 /* Save press to possibly begin a drag */
877 if (icon_list->priv->pressed_button < 0)
879 icon_list->priv->pressed_button = event->button;
880 icon_list->priv->press_start_x = event->x;
881 icon_list->priv->press_start_y = event->y;
884 if (!icon_list->priv->last_single_clicked)
885 icon_list->priv->last_single_clicked = item;
889 if (icon_list->priv->selection_mode != GTK_SELECTION_BROWSE &&
890 !(event->state & GDK_CONTROL_MASK))
892 dirty = egg_icon_list_unselect_all_internal (icon_list, FALSE);
895 if (icon_list->priv->selection_mode == GTK_SELECTION_MULTIPLE)
896 egg_icon_list_start_rubberbanding (icon_list, event->x, event->y);
901 if (event->button == 1 && event->type == GDK_2BUTTON_PRESS)
903 item = egg_icon_list_get_item_at_pos (icon_list,
906 if (item && item == icon_list->priv->last_single_clicked)
908 egg_icon_list_item_activated (icon_list, item);
911 icon_list->priv->last_single_clicked = NULL;
915 g_signal_emit (icon_list, icon_list_signals[SELECTION_CHANGED], 0);
921 egg_icon_list_button_release (GtkWidget *widget,
922 GdkEventButton *event)
924 EggIconList *icon_list;
926 icon_list = EGG_ICON_LIST (widget);
928 if (icon_list->priv->pressed_button == event->button)
929 icon_list->priv->pressed_button = -1;
931 egg_icon_list_stop_rubberbanding (icon_list);
933 if (icon_list->priv->scroll_timeout_id != 0)
935 g_source_remove (icon_list->priv->scroll_timeout_id);
936 icon_list->priv->scroll_timeout_id = 0;
944 egg_icon_list_key_press (GtkWidget *widget,
947 if ((* GTK_WIDGET_CLASS (parent_class)->key_press_event) (widget, event))
952 if ((event->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK)) == 0)
953 egg_icon_list_append_typeahead_string (EGG_ICON_LIST (widget), event->string);
959 egg_icon_list_select_first_matching_item (EggIconList *icon_list,
967 for (items = icon_list->priv->items; items; items = items->next)
969 EggIconListItem *item = items->data;
971 if (strncmp (pattern, item->label, strlen (pattern)) == 0)
973 egg_icon_list_select_item (icon_list, item);
980 rubberbanding (gpointer data)
982 EggIconList *icon_list;
984 GdkRectangle old_area;
985 GdkRectangle new_area;
987 GdkRegion *invalid_region;
989 icon_list = EGG_ICON_LIST (data);
991 gdk_window_get_pointer (icon_list->priv->bin_window, &x, &y, NULL);
996 old_area.x = MIN (icon_list->priv->rubberband_x1,
997 icon_list->priv->rubberband_x2);
998 old_area.y = MIN (icon_list->priv->rubberband_y1,
999 icon_list->priv->rubberband_y2);
1000 old_area.width = ABS (icon_list->priv->rubberband_x2 -
1001 icon_list->priv->rubberband_x1) + 1;
1002 old_area.height = ABS (icon_list->priv->rubberband_y2 -
1003 icon_list->priv->rubberband_y1) + 1;
1005 new_area.x = MIN (icon_list->priv->rubberband_x1, x);
1006 new_area.y = MIN (icon_list->priv->rubberband_y1, y);
1007 new_area.width = ABS (x - icon_list->priv->rubberband_x1) + 1;
1008 new_area.height = ABS (y - icon_list->priv->rubberband_y1) + 1;
1010 invalid_region = gdk_region_rectangle (&old_area);
1011 gdk_region_union_with_rect (invalid_region, &new_area);
1013 gdk_rectangle_intersect (&old_area, &new_area, &common);
1014 if (common.width > 2 && common.height > 2)
1016 GdkRegion *common_region;
1018 /* make sure the border is invalidated */
1024 common_region = gdk_region_rectangle (&common);
1026 gdk_region_subtract (invalid_region, common_region);
1027 gdk_region_destroy (common_region);
1030 gdk_window_invalidate_region (icon_list->priv->bin_window, invalid_region, TRUE);
1032 gdk_region_destroy (invalid_region);
1034 icon_list->priv->rubberband_x2 = x;
1035 icon_list->priv->rubberband_y2 = y;
1037 egg_icon_list_update_rubberband_selection (icon_list);
1041 egg_icon_list_start_rubberbanding (EggIconList *icon_list,
1047 g_assert (!icon_list->priv->rubberbanding);
1049 for (items = icon_list->priv->items; items; items = items->next)
1051 EggIconListItem *item = items->data;
1053 item->selected_before_rubberbanding = item->selected;
1056 icon_list->priv->rubberband_x1 = x;
1057 icon_list->priv->rubberband_y1 = y;
1058 icon_list->priv->rubberband_x2 = x;
1059 icon_list->priv->rubberband_y2 = y;
1061 icon_list->priv->rubberbanding = TRUE;
1063 gtk_grab_add (GTK_WIDGET (icon_list));
1067 egg_icon_list_stop_rubberbanding (EggIconList *icon_list)
1069 if (!icon_list->priv->rubberbanding)
1072 icon_list->priv->rubberbanding = FALSE;
1074 gtk_grab_remove (GTK_WIDGET (icon_list));
1076 gtk_widget_queue_draw (GTK_WIDGET (icon_list));
1080 egg_icon_list_sort_func (EggIconListItem *a,
1082 EggIconList *icon_list)
1086 result = (* icon_list->priv->sort_func) (icon_list, a, b,
1087 icon_list->priv->sort_data);
1089 if (icon_list->priv->sort_order == GTK_SORT_DESCENDING)
1096 egg_icon_list_insert_item_sorted (EggIconList *icon_list,
1097 EggIconListItem *item)
1100 GList *tmp_list = icon_list->priv->items;
1103 egg_icon_list_validate (icon_list);
1105 list = g_list_alloc ();
1107 item->icon_list = icon_list;
1109 egg_icon_list_item_ref (item);
1111 if (!icon_list->priv->items)
1113 icon_list->priv->items = list;
1114 icon_list->priv->last_item = list;
1115 icon_list->priv->item_count += 1;
1117 egg_icon_list_validate (icon_list);
1122 cmp = egg_icon_list_sort_func (item, tmp_list->data, icon_list);
1124 while ((tmp_list->next) && (cmp > 0))
1126 tmp_list = tmp_list->next;
1127 cmp = egg_icon_list_sort_func (item, tmp_list->data, icon_list);
1130 if ((!tmp_list->next) && (cmp > 0))
1132 tmp_list->next = list;
1133 list->prev = tmp_list;
1134 icon_list->priv->last_item = list;
1135 icon_list->priv->item_count += 1;
1136 egg_icon_list_validate (icon_list);
1143 tmp_list->prev->next = list;
1144 list->prev = tmp_list->prev;
1147 list->next = tmp_list;
1148 tmp_list->prev = list;
1150 if (tmp_list == icon_list->priv->items)
1151 icon_list->priv->items = list;
1153 icon_list->priv->item_count += 1;
1154 egg_icon_list_validate (icon_list);
1156 egg_icon_list_queue_layout (icon_list);
1161 egg_icon_list_sort (EggIconList *icon_list)
1163 egg_icon_list_validate (icon_list);
1165 /* FIXME: We can optimize this */
1166 icon_list->priv->items = g_list_sort_with_data (icon_list->priv->items,
1167 (GCompareDataFunc)egg_icon_list_sort_func,
1169 icon_list->priv->last_item = g_list_last (icon_list->priv->items);
1171 egg_icon_list_validate (icon_list);
1172 egg_icon_list_queue_layout (icon_list);
1177 egg_icon_list_validate (EggIconList *icon_list)
1183 for (list = icon_list->priv->items; list; list = list->next)
1185 EggIconListItem *item = list->data;
1187 g_print ("%s\n", egg_icon_list_item_get_label (item));
1192 g_assert (g_list_length (icon_list->priv->items) == icon_list->priv->item_count);
1193 g_assert (g_list_last (icon_list->priv->items) == icon_list->priv->last_item);
1194 g_assert (g_list_first (icon_list->priv->last_item) == icon_list->priv->items);
1198 egg_icon_list_update_rubberband_selection (EggIconList *icon_list)
1201 gint x, y, width, height;
1202 gboolean dirty = FALSE;
1204 x = MIN (icon_list->priv->rubberband_x1,
1205 icon_list->priv->rubberband_x2);
1206 y = MIN (icon_list->priv->rubberband_y1,
1207 icon_list->priv->rubberband_y2);
1208 width = ABS (icon_list->priv->rubberband_x1 -
1209 icon_list->priv->rubberband_x2);
1210 height = ABS (icon_list->priv->rubberband_y1 -
1211 icon_list->priv->rubberband_y2);
1213 for (items = icon_list->priv->items; items; items = items->next)
1215 EggIconListItem *item = items->data;
1219 is_in = egg_icon_list_item_hit_test (item, x, y, width, height);
1221 selected = is_in ^ item->selected_before_rubberbanding;
1223 if (item->selected != selected)
1225 item->selected = selected;
1227 egg_icon_list_queue_draw_item (icon_list, item);
1232 g_signal_emit (icon_list, icon_list_signals[SELECTION_CHANGED], 0);
1236 egg_icon_list_item_hit_test (EggIconListItem *item,
1242 /* First try the pixbuf */
1243 if (MIN (x + width, item->pixbuf_x + item->pixbuf_width) - MAX (x, item->pixbuf_x) > 0 &&
1244 MIN (y + height, item->pixbuf_y + item->pixbuf_height) - MAX (y, item->pixbuf_y) > 0)
1247 /* Then try the text */
1248 if (MIN (x + width, item->layout_x + item->layout_width) - MAX (x, item->layout_x) > 0 &&
1249 MIN (y + height, item->layout_y + item->layout_height) - MAX (y, item->layout_y) > 0)
1256 egg_icon_list_maybe_begin_dragging_items (EggIconList *icon_list,
1257 GdkEventMotion *event)
1259 gboolean retval = FALSE;
1261 if (icon_list->priv->pressed_button < 0)
1264 if (!gtk_drag_check_threshold (GTK_WIDGET (icon_list),
1265 icon_list->priv->press_start_x,
1266 icon_list->priv->press_start_y,
1267 event->x, event->y))
1270 button = icon_list->priv->pressed_button;
1271 icon_list->priv->pressed_button = -1;
1274 static GtkTargetEntry row_targets[] = {
1275 { "EGG_ICON_LIST_ITEMS", GTK_TARGET_SAME_APP, 0 }
1277 GtkTargetList *target_list;
1278 GdkDragContext *context;
1279 EggIconListItem *item;
1283 target_list = gtk_target_list_new (row_targets, G_N_ELEMENTS (row_targets));
1285 context = gtk_drag_begin (GTK_WIDGET (icon_list),
1286 target_list, GDK_ACTION_MOVE,
1290 item = egg_icon_list_get_item_at_pos (icon_list,
1291 icon_list->priv->press_start_x,
1292 icon_list->priv->press_start_y);
1293 g_assert (item != NULL);
1294 gtk_drag_set_icon_pixbuf (context, egg_icon_list_item_get_icon (item),
1296 event->y - item->y);
1304 egg_icon_list_unselect_all_internal (EggIconList *icon_list,
1307 gboolean dirty = FALSE;
1310 for (items = icon_list->priv->items; items; items = items->next)
1312 EggIconListItem *item = items->data;
1316 item->selected = FALSE;
1318 egg_icon_list_queue_draw_item (icon_list, item);
1323 g_signal_emit (icon_list, icon_list_signals[SELECTION_CHANGED], 0);
1329 /* EggIconList signals */
1331 egg_icon_list_set_adjustments (EggIconList *icon_list,
1332 GtkAdjustment *hadj,
1333 GtkAdjustment *vadj)
1335 gboolean need_adjust = FALSE;
1338 g_return_if_fail (GTK_IS_ADJUSTMENT (hadj));
1340 hadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
1342 g_return_if_fail (GTK_IS_ADJUSTMENT (vadj));
1344 vadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
1346 if (icon_list->priv->hadjustment && (icon_list->priv->hadjustment != hadj))
1348 g_signal_handlers_disconnect_matched (icon_list->priv->hadjustment, G_SIGNAL_MATCH_DATA,
1349 0, 0, NULL, NULL, icon_list);
1350 g_object_unref (icon_list->priv->hadjustment);
1353 if (icon_list->priv->vadjustment && (icon_list->priv->vadjustment != vadj))
1355 g_signal_handlers_disconnect_matched (icon_list->priv->vadjustment, G_SIGNAL_MATCH_DATA,
1356 0, 0, NULL, NULL, icon_list);
1357 g_object_unref (icon_list->priv->vadjustment);
1360 if (icon_list->priv->hadjustment != hadj)
1362 icon_list->priv->hadjustment = hadj;
1363 g_object_ref (icon_list->priv->hadjustment);
1364 gtk_object_sink (GTK_OBJECT (icon_list->priv->hadjustment));
1366 g_signal_connect (icon_list->priv->hadjustment, "value_changed",
1367 G_CALLBACK (egg_icon_list_adjustment_changed),
1372 if (icon_list->priv->vadjustment != vadj)
1374 icon_list->priv->vadjustment = vadj;
1375 g_object_ref (icon_list->priv->vadjustment);
1376 gtk_object_sink (GTK_OBJECT (icon_list->priv->vadjustment));
1378 g_signal_connect (icon_list->priv->vadjustment, "value_changed",
1379 G_CALLBACK (egg_icon_list_adjustment_changed),
1385 egg_icon_list_adjustment_changed (NULL, icon_list);
1389 egg_icon_list_real_select_all (EggIconList *icon_list)
1391 if (icon_list->priv->selection_mode != GTK_SELECTION_MULTIPLE)
1394 egg_icon_list_select_all (icon_list);
1398 egg_icon_list_real_unselect_all (EggIconList *icon_list)
1400 if (icon_list->priv->selection_mode == GTK_SELECTION_BROWSE)
1403 egg_icon_list_unselect_all (icon_list);
1407 egg_icon_list_real_select_cursor_item (EggIconList *icon_list)
1409 egg_icon_list_unselect_all (icon_list);
1411 if (icon_list->priv->cursor_item != NULL)
1412 egg_icon_list_select_item (icon_list, icon_list->priv->cursor_item);
1416 egg_icon_list_real_toggle_cursor_item (EggIconList *icon_list)
1418 if (icon_list->priv->selection_mode == GTK_SELECTION_NONE)
1421 /* FIXME: Use another function here */
1422 if (icon_list->priv->cursor_item != NULL)
1424 if (icon_list->priv->selection_mode == GTK_SELECTION_BROWSE)
1425 icon_list->priv->cursor_item->selected = TRUE;
1427 icon_list->priv->cursor_item->selected = !icon_list->priv->cursor_item->selected;
1429 egg_icon_list_queue_draw_item (icon_list, icon_list->priv->cursor_item);
1433 /* Internal functions */
1435 egg_icon_list_adjustment_changed (GtkAdjustment *adjustment,
1436 EggIconList *icon_list)
1438 if (GTK_WIDGET_REALIZED (icon_list))
1440 gdk_window_move (icon_list->priv->bin_window,
1441 - icon_list->priv->hadjustment->value,
1442 - icon_list->priv->vadjustment->value);
1443 gdk_window_process_updates (icon_list->priv->bin_window, TRUE);
1448 egg_icon_list_layout_single_row (EggIconList *icon_list, GList *first_item, gint *y, gint *maximum_width)
1450 gint x, current_width, max_height, max_pixbuf_height;
1451 GList *items, *last_item;
1453 gint left_margin, right_margin;
1454 gint maximum_layout_width;
1455 gboolean rtl = gtk_widget_get_direction (GTK_WIDGET (icon_list)) == GTK_TEXT_DIR_RTL;
1459 max_pixbuf_height = 0;
1463 gtk_widget_style_get (GTK_WIDGET (icon_list),
1464 "icon_padding", &icon_padding,
1465 "left_margin", &left_margin,
1466 "right_margin", &right_margin,
1470 current_width += left_margin + right_margin;
1475 EggIconListItem *item = items->data;
1477 egg_icon_list_calculate_item_size (icon_list, item);
1479 current_width += MAX (item->width, MINIMUM_ICON_ITEM_WIDTH);
1481 /* Don't add padding to the first or last icon */
1483 if (current_width > GTK_WIDGET (icon_list)->allocation.width &&
1484 items != first_item)
1487 maximum_layout_width = MAX (item->pixbuf_width, MINIMUM_ICON_ITEM_WIDTH);
1490 item->x = rtl ? GTK_WIDGET (icon_list)->allocation.width - item->width - x : x;
1492 if (item->width < MINIMUM_ICON_ITEM_WIDTH) {
1494 item->x -= (MINIMUM_ICON_ITEM_WIDTH - item->width) / 2;
1496 item->x += (MINIMUM_ICON_ITEM_WIDTH - item->width) / 2;
1497 x += (MINIMUM_ICON_ITEM_WIDTH - item->width);
1500 item->pixbuf_x = item->x + (item->width - item->pixbuf_width) / 2;
1501 item->layout_x = item->x + (item->width - item->layout_width) / 2;
1505 max_height = MAX (max_height, item->height);
1506 max_pixbuf_height = MAX (max_pixbuf_height, item->pixbuf_height);
1508 if (current_width > *maximum_width)
1509 *maximum_width = current_width;
1511 items = items->next;
1516 *y += max_height + icon_padding;
1518 /* Now go through the row again and align the icons */
1519 for (items = first_item; items != last_item; items = items->next)
1521 EggIconListItem *item = items->data;
1523 item->pixbuf_y = item->y + (max_pixbuf_height - item->pixbuf_height);
1524 item->layout_y = item->pixbuf_y + item->pixbuf_height + ICON_TEXT_PADDING;
1526 /* Update the bounding box */
1527 item->y = item->pixbuf_y;
1529 /* We may want to readjust the new y coordinate. */
1530 if (item->y + item->height > *y)
1531 *y = item->y + item->height;
1538 egg_icon_list_set_adjustment_upper (GtkAdjustment *adj,
1541 if (upper != adj->upper)
1543 gdouble min = MAX (0.0, upper - adj->page_size);
1544 gboolean value_changed = FALSE;
1548 if (adj->value > min)
1551 value_changed = TRUE;
1554 gtk_adjustment_changed (adj);
1557 gtk_adjustment_value_changed (adj);
1562 egg_icon_list_layout (EggIconList *icon_list)
1564 gint y = 0, maximum_width = 0;
1567 gint top_margin, bottom_margin;
1569 widget = GTK_WIDGET (icon_list);
1570 icons = icon_list->priv->items;
1572 gtk_widget_style_get (widget,
1573 "top_margin", &top_margin,
1574 "bottom_margin", &bottom_margin,
1580 icons = egg_icon_list_layout_single_row (icon_list, icons, &y, &maximum_width);
1582 while (icons != NULL);
1584 if (maximum_width != icon_list->priv->width)
1586 icon_list->priv->width = maximum_width;
1590 if (y != icon_list->priv->height)
1592 icon_list->priv->height = y;
1595 egg_icon_list_set_adjustment_upper (icon_list->priv->hadjustment, icon_list->priv->width);
1596 egg_icon_list_set_adjustment_upper (icon_list->priv->vadjustment, icon_list->priv->height);
1598 if (GTK_WIDGET_REALIZED (icon_list))
1600 gdk_window_resize (icon_list->priv->bin_window,
1601 MAX (icon_list->priv->width, widget->allocation.width),
1602 MAX (icon_list->priv->height, widget->allocation.height));
1605 if (icon_list->priv->layout_idle_id != 0)
1607 g_source_remove (icon_list->priv->layout_idle_id);
1608 icon_list->priv->layout_idle_id = 0;
1611 gtk_widget_queue_draw (GTK_WIDGET (icon_list));
1614 /* Creates or updates the pango layout and calculates the size */
1616 egg_icon_list_calculate_item_size (EggIconList *icon_list, EggIconListItem *item)
1618 int layout_width, layout_height;
1619 int maximum_layout_width;
1621 if (item->width != -1 && item->width != -1)
1624 item->pixbuf_width = gdk_pixbuf_get_width (item->icon);
1625 item->pixbuf_height = gdk_pixbuf_get_height (item->icon);
1627 maximum_layout_width = MAX (item->pixbuf_width, MINIMUM_ICON_ITEM_WIDTH);
1629 pango_layout_set_text (icon_list->priv->layout, item->label, -1);
1631 pango_layout_set_alignment (icon_list->priv->layout, PANGO_ALIGN_CENTER);
1632 pango_layout_set_width (icon_list->priv->layout, maximum_layout_width * PANGO_SCALE);
1634 pango_layout_get_pixel_size (icon_list->priv->layout, &layout_width, &layout_height);
1636 item->width = MAX ((layout_width + 2 * ICON_TEXT_PADDING), item->pixbuf_width);
1637 item->height = layout_height + 2 * ICON_TEXT_PADDING + item->pixbuf_height;
1638 item->layout_width = layout_width;
1639 item->layout_height = layout_height;
1643 egg_icon_list_item_invalidate_size (EggIconListItem *item)
1650 create_colorized_pixbuf (GdkPixbuf *src, GdkColor *new_color)
1653 gint width, height, has_alpha, src_row_stride, dst_row_stride;
1654 gint red_value, green_value, blue_value;
1655 guchar *target_pixels;
1656 guchar *original_pixels;
1661 red_value = new_color->red / 255.0;
1662 green_value = new_color->green / 255.0;
1663 blue_value = new_color->blue / 255.0;
1665 dest = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (src),
1666 gdk_pixbuf_get_has_alpha (src),
1667 gdk_pixbuf_get_bits_per_sample (src),
1668 gdk_pixbuf_get_width (src),
1669 gdk_pixbuf_get_height (src));
1671 has_alpha = gdk_pixbuf_get_has_alpha (src);
1672 width = gdk_pixbuf_get_width (src);
1673 height = gdk_pixbuf_get_height (src);
1674 src_row_stride = gdk_pixbuf_get_rowstride (src);
1675 dst_row_stride = gdk_pixbuf_get_rowstride (dest);
1676 target_pixels = gdk_pixbuf_get_pixels (dest);
1677 original_pixels = gdk_pixbuf_get_pixels (src);
1679 for (i = 0; i < height; i++) {
1680 pixdest = target_pixels + i*dst_row_stride;
1681 pixsrc = original_pixels + i*src_row_stride;
1682 for (j = 0; j < width; j++) {
1683 *pixdest++ = (*pixsrc++ * red_value) >> 8;
1684 *pixdest++ = (*pixsrc++ * green_value) >> 8;
1685 *pixdest++ = (*pixsrc++ * blue_value) >> 8;
1687 *pixdest++ = *pixsrc++;
1695 egg_icon_list_paint_item (EggIconList *icon_list,
1696 EggIconListItem *item,
1702 if (GTK_WIDGET_HAS_FOCUS (icon_list))
1703 state = GTK_STATE_SELECTED;
1705 state = GTK_STATE_ACTIVE;
1708 pixbuf = create_colorized_pixbuf (item->icon,
1709 >K_WIDGET (icon_list)->style->base[state]);
1711 pixbuf = g_object_ref (item->icon);
1713 gdk_draw_pixbuf (icon_list->priv->bin_window, NULL, pixbuf,
1715 item->pixbuf_x, item->pixbuf_y,
1716 item->pixbuf_width, item->pixbuf_height,
1717 GDK_RGB_DITHER_NORMAL,
1718 item->pixbuf_width, item->pixbuf_height);
1719 g_object_unref (pixbuf);
1723 gdk_draw_rectangle (icon_list->priv->bin_window,
1724 GTK_WIDGET (icon_list)->style->base_gc[state],
1726 item->layout_x - ICON_TEXT_PADDING,
1727 item->layout_y - ICON_TEXT_PADDING,
1728 item->layout_width + 2 * ICON_TEXT_PADDING,
1729 item->layout_height + 2 * ICON_TEXT_PADDING);
1732 pango_layout_set_text (icon_list->priv->layout, item->label, -1);
1733 gdk_draw_layout (icon_list->priv->bin_window,
1734 GTK_WIDGET (icon_list)->style->text_gc[item->selected ? state : GTK_STATE_NORMAL],
1735 item->layout_x - ((item->width - item->layout_width) / 2) - (MAX (item->pixbuf_width, MINIMUM_ICON_ITEM_WIDTH) - item->width) / 2,
1737 icon_list->priv->layout);
1739 if (GTK_WIDGET_HAS_FOCUS (icon_list) &&
1740 item == icon_list->priv->cursor_item)
1741 gtk_paint_focus (GTK_WIDGET (icon_list)->style,
1742 icon_list->priv->bin_window,
1743 item->selected ? GTK_STATE_SELECTED : GTK_STATE_NORMAL,
1745 GTK_WIDGET (icon_list),
1747 item->layout_x - ICON_TEXT_PADDING,
1748 item->layout_y - ICON_TEXT_PADDING,
1749 item->layout_width + 2 * ICON_TEXT_PADDING,
1750 item->layout_height + 2 * ICON_TEXT_PADDING);
1754 egg_gdk_color_to_rgb (const GdkColor *color)
1757 result = (0xff0000 | (color->red & 0xff00));
1759 result |= ((color->green & 0xff00) | (color->blue >> 8));
1764 egg_icon_list_paint_rubberband (EggIconList *icon_list,
1770 GdkRectangle rubber_rect;
1771 GdkColor *fill_color_gdk;
1773 guchar fill_color_alpha;
1775 rubber_rect.x = MIN (icon_list->priv->rubberband_x1, icon_list->priv->rubberband_x2);
1776 rubber_rect.y = MIN (icon_list->priv->rubberband_y1, icon_list->priv->rubberband_y2);
1777 rubber_rect.width = ABS (icon_list->priv->rubberband_x1 - icon_list->priv->rubberband_x2) + 1;
1778 rubber_rect.height = ABS (icon_list->priv->rubberband_y1 - icon_list->priv->rubberband_y2) + 1;
1780 if (!gdk_rectangle_intersect (&rubber_rect, area, &rect))
1783 gtk_widget_style_get (GTK_WIDGET (icon_list),
1784 "selection_box_color", &fill_color_gdk,
1785 "selection_box_alpha", &fill_color_alpha,
1788 if (!fill_color_gdk) {
1789 fill_color_gdk = gdk_color_copy (>K_WIDGET (icon_list)->style->base[GTK_STATE_SELECTED]);
1792 fill_color = egg_gdk_color_to_rgb (fill_color_gdk) << 8 | fill_color_alpha;
1794 pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, rect.width, rect.height);
1795 gdk_pixbuf_fill (pixbuf, fill_color);
1797 gdk_draw_pixbuf (icon_list->priv->bin_window, NULL, pixbuf,
1800 rect.width, rect.height,
1801 GDK_RGB_DITHER_NONE,
1803 g_object_unref (pixbuf);
1804 gc = gdk_gc_new (icon_list->priv->bin_window);
1805 gdk_gc_set_rgb_fg_color (gc, fill_color_gdk);
1806 gdk_gc_set_clip_rectangle (gc, &rect);
1807 gdk_draw_rectangle (icon_list->priv->bin_window,
1809 rubber_rect.x, rubber_rect.y,
1810 rubber_rect.width - 1, rubber_rect.height - 1);
1811 gdk_color_free (fill_color_gdk);
1812 g_object_unref (gc);
1816 egg_icon_list_queue_draw_item (EggIconList *icon_list,
1817 EggIconListItem *item)
1823 rect.width = item->width;
1824 rect.height = item->height;
1826 gdk_window_invalidate_rect (icon_list->priv->bin_window, &rect, TRUE);
1830 layout_callback (gpointer user_data)
1832 EggIconList *icon_list;
1834 icon_list = EGG_ICON_LIST (user_data);
1836 icon_list->priv->layout_idle_id = 0;
1838 egg_icon_list_layout (icon_list);
1844 egg_icon_list_queue_layout (EggIconList *icon_list)
1846 if (icon_list->priv->layout_idle_id != 0)
1849 icon_list->priv->layout_idle_id = g_idle_add (layout_callback, icon_list);
1853 egg_icon_list_set_cursor_item (EggIconList *icon_list,
1854 EggIconListItem *item)
1856 if (icon_list->priv->cursor_item == item)
1859 if (icon_list->priv->cursor_item != NULL)
1860 egg_icon_list_queue_draw_item (icon_list, icon_list->priv->cursor_item);
1862 icon_list->priv->cursor_item = item;
1863 egg_icon_list_queue_draw_item (icon_list, item);
1867 egg_icon_list_append_typeahead_string (EggIconList *icon_list,
1868 const gchar *string)
1871 char *typeahead_string;
1873 if (strlen (string) == 0)
1876 for (i = 0; i < strlen (string); i++)
1878 if (!g_ascii_isprint (string[i]))
1882 typeahead_string = g_strconcat (icon_list->priv->typeahead_string ?
1883 icon_list->priv->typeahead_string : "",
1885 g_free (icon_list->priv->typeahead_string);
1886 icon_list->priv->typeahead_string = typeahead_string;
1888 egg_icon_list_select_first_matching_item (icon_list,
1889 icon_list->priv->typeahead_string);
1891 g_print ("wooo: \"%s\"\n", typeahead_string);
1896 egg_icon_list_new (void)
1898 EggIconList *icon_list;
1900 icon_list = g_object_new (EGG_TYPE_ICON_LIST, NULL);
1902 return GTK_WIDGET (icon_list);
1906 egg_icon_list_item_new (GdkPixbuf *icon,
1909 EggIconListItem *item;
1911 item = g_new0 (EggIconListItem, 1);
1913 item->ref_count = 1;
1916 item->label = g_strdup (label);
1917 item->icon = g_object_ref (icon);
1923 egg_icon_list_item_ref (EggIconListItem *item)
1925 g_return_if_fail (item != NULL);
1927 item->ref_count += 1;
1931 egg_icon_list_item_unref (EggIconListItem *item)
1933 g_return_if_fail (item != NULL);
1935 item->ref_count -= 1;
1937 if (item->ref_count == 0)
1939 if (item->destroy_notify)
1940 item->destroy_notify (item->user_data);
1942 g_free (item->label);
1943 g_object_unref (item->icon);
1950 egg_icon_list_item_set_data (EggIconListItem *item,
1953 egg_icon_list_item_set_data_full (item, data, NULL);
1957 egg_icon_list_item_set_data_full (EggIconListItem *item,
1959 GDestroyNotify destroy_notify)
1961 g_return_if_fail (item != NULL);
1963 if (item->destroy_notify)
1964 item->destroy_notify (item->user_data);
1966 item->destroy_notify = destroy_notify;
1967 item->user_data = data;
1971 egg_icon_list_item_get_data (EggIconListItem *item)
1973 g_return_val_if_fail (item != NULL, NULL);
1975 return item->user_data;
1979 egg_icon_list_item_set_label (EggIconListItem *item,
1982 g_return_if_fail (item != NULL);
1983 g_return_if_fail (label != NULL);
1985 if (strcmp (item->label, label) == 0)
1988 g_free (item->label);
1989 item->label = g_strdup (label);
1990 egg_icon_list_item_invalidate_size (item);
1992 egg_icon_list_queue_layout (item->icon_list);
1994 g_object_notify (G_OBJECT (item), "label");
1997 G_CONST_RETURN gchar *
1998 egg_icon_list_item_get_label (EggIconListItem *item)
2000 g_return_val_if_fail (item != NULL, NULL);
2006 egg_icon_list_item_set_icon (EggIconListItem *item,
2009 g_return_if_fail (item != NULL);
2011 if (icon == item->icon)
2014 g_object_unref (item->icon);
2015 item->icon = g_object_ref (icon);
2017 egg_icon_list_item_invalidate_size (item);
2019 egg_icon_list_queue_layout (item->icon_list);
2023 egg_icon_list_item_get_icon (EggIconListItem *item)
2025 g_return_val_if_fail (item != NULL, NULL);
2031 egg_icon_list_append_item (EggIconList *icon_list,
2032 EggIconListItem *item)
2036 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2037 g_return_if_fail (item != NULL);
2038 g_return_if_fail (item->icon_list == NULL);
2040 if (icon_list->priv->sorted)
2042 egg_icon_list_insert_item_sorted (icon_list, item);
2046 egg_icon_list_validate (icon_list);
2048 list = g_list_alloc ();
2050 item->icon_list = icon_list;
2052 egg_icon_list_item_ref (item);
2054 if (icon_list->priv->last_item)
2056 icon_list->priv->last_item->next = list;
2057 list->prev = icon_list->priv->last_item;
2060 icon_list->priv->items = list;
2062 icon_list->priv->last_item = list;
2063 icon_list->priv->item_count += 1;
2065 egg_icon_list_validate (icon_list);
2067 g_signal_emit (icon_list, icon_list_signals[ITEM_ADDED], 0, item);
2069 egg_icon_list_queue_layout (icon_list);
2073 egg_icon_list_prepend_item (EggIconList *icon_list,
2074 EggIconListItem *item)
2078 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2079 g_return_if_fail (item != NULL);
2080 g_return_if_fail (item->icon_list == NULL);
2082 egg_icon_list_validate (icon_list);
2084 list = g_list_alloc ();
2086 item->icon_list = icon_list;
2088 egg_icon_list_item_ref (item);
2090 if (icon_list->priv->last_item == NULL)
2091 icon_list->priv->last_item = list;
2093 if (icon_list->priv->items)
2094 icon_list->priv->items->prev = list;
2096 list->next = icon_list->priv->items;
2097 icon_list->priv->items = list;
2098 icon_list->priv->item_count += 1;
2100 egg_icon_list_validate (icon_list);
2102 g_signal_emit (icon_list, icon_list_signals[ITEM_ADDED], 0, item);
2104 egg_icon_list_queue_layout (icon_list);
2110 egg_icon_list_insert_item_before (EggIconList *icon_list,
2111 EggIconListItem *sibling,
2112 EggIconListItem *item)
2116 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2117 g_return_if_fail (item != NULL);
2118 g_return_if_fail (item->icon_list == NULL);
2120 if (icon_list->priv->sorted)
2122 egg_icon_list_insert_item_sorted (icon_list, item);
2126 if (sibling == NULL)
2127 egg_icon_list_append_item (icon_list, item);
2129 egg_icon_list_validate (icon_list);
2131 list = g_list_alloc ();
2133 item->icon_list = icon_list;
2135 egg_icon_list_item_ref (item);
2137 list->prev = sibling->list->prev;
2138 list->next = sibling->list;
2139 sibling->list->prev->next = list;
2140 sibling->list->prev = list;
2142 if (sibling->list == icon_list->priv->items)
2143 icon_list->priv->items = list;
2145 icon_list->priv->item_count += 1;
2146 egg_icon_list_validate (icon_list);
2148 g_signal_emit (icon_list, icon_list_signals[ITEM_ADDED], 0, item);
2150 egg_icon_list_queue_layout (icon_list);
2154 egg_icon_list_insert_item_after (EggIconList *icon_list,
2155 EggIconListItem *sibling,
2156 EggIconListItem *item)
2160 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2161 g_return_if_fail (item != NULL);
2162 g_return_if_fail (item->icon_list == NULL);
2164 if (icon_list->priv->sorted)
2166 egg_icon_list_insert_item_sorted (icon_list, item);
2170 if (sibling == NULL)
2172 egg_icon_list_prepend_item (icon_list, item);
2176 egg_icon_list_validate (icon_list);
2178 list = g_list_alloc ();
2180 item->icon_list = icon_list;
2182 egg_icon_list_item_ref (item);
2184 list->next = sibling->list->next;
2185 list->prev = sibling->list;
2186 sibling->list->next->prev = list;
2187 sibling->list->next = list;
2189 if (sibling->list == icon_list->priv->last_item)
2190 icon_list->priv->last_item = list;
2192 icon_list->priv->item_count += 1;
2193 egg_icon_list_validate (icon_list);
2194 g_signal_emit (icon_list, icon_list_signals[ITEM_ADDED], 0, item);
2196 egg_icon_list_queue_layout (icon_list);
2200 egg_icon_list_remove_item (EggIconList *icon_list,
2201 EggIconListItem *item)
2203 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2204 g_return_if_fail (item != NULL);
2205 g_return_if_fail (item->icon_list == icon_list);
2207 egg_icon_list_validate (icon_list);
2209 if (item->list->prev)
2210 item->list->prev->next = item->list->next;
2211 if (item->list->next)
2212 item->list->next->prev = item->list->prev;
2214 if (item->list == icon_list->priv->items)
2215 icon_list->priv->items = item->list->next;
2216 if (item->list == icon_list->priv->last_item)
2217 icon_list->priv->last_item = item->list->prev;
2219 g_list_free_1 (item->list);
2221 item->icon_list = NULL;
2222 egg_icon_list_item_invalidate_size (item);
2224 icon_list->priv->item_count -= 1;
2225 egg_icon_list_validate (icon_list);
2227 g_signal_emit (icon_list, icon_list_signals[ITEM_REMOVED], 0, item);
2231 item->selected = FALSE;
2233 g_signal_emit (icon_list, icon_list_signals[SELECTION_CHANGED], 0);
2237 if (icon_list->priv->cursor_item == item)
2238 g_error ("FIXME: Move to first focused item");
2241 if (icon_list->priv->last_single_clicked == item)
2242 icon_list->priv->last_single_clicked = NULL;
2244 egg_icon_list_item_unref (item);
2246 egg_icon_list_queue_layout (icon_list);
2250 egg_icon_list_clear (EggIconList *icon_list)
2254 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2256 items = g_list_copy (icon_list->priv->items);
2260 EggIconListItem *item = items->data;
2262 egg_icon_list_remove_item (icon_list, item);
2263 items = items->next;
2270 egg_icon_list_get_item_at_pos (EggIconList *icon_list,
2276 g_return_val_if_fail (EGG_IS_ICON_LIST (icon_list), NULL);
2278 for (items = icon_list->priv->items; items; items = items->next)
2280 EggIconListItem *item = items->data;
2282 if (x > item->x && x < item->x + item->width &&
2283 y > item->y && y < item->y + item->height)
2285 gint layout_x = item->x + (item->width - item->layout_width) / 2;
2286 /* Check if the mouse is inside the icon or the label */
2287 if ((x > item->pixbuf_x && x < item->pixbuf_x + item->pixbuf_width &&
2288 y > item->pixbuf_y && y < item->pixbuf_y + item->pixbuf_height) ||
2289 (x > layout_x - ICON_TEXT_PADDING &&
2290 x < layout_x + item->layout_width + ICON_TEXT_PADDING * 2 &&
2291 y > item->layout_y - ICON_TEXT_PADDING
2292 && y < item->layout_y + item->layout_height + ICON_TEXT_PADDING * 2))
2301 egg_icon_list_get_item_count (EggIconList *icon_list)
2303 g_return_val_if_fail (EGG_IS_ICON_LIST (icon_list), 0);
2305 return icon_list->priv->item_count;
2309 egg_icon_list_foreach (EggIconList *icon_list,
2310 EggIconListForeachFunc func,
2315 for (list = icon_list->priv->items; list; list = list->next)
2316 (* func) (icon_list, list->data, data);
2320 egg_icon_list_selected_foreach (EggIconList *icon_list,
2321 EggIconListForeachFunc func,
2326 for (list = icon_list->priv->items; list; list = list->next)
2328 EggIconListItem *item = list->data;
2331 (* func) (icon_list, list->data, data);
2336 egg_icon_list_get_selected (EggIconList *icon_list)
2338 GList *list, *selected = NULL;
2340 g_return_val_if_fail (EGG_IS_ICON_LIST (icon_list), NULL);
2342 for (list = icon_list->priv->items; list; list = list->next)
2344 EggIconListItem *item = list->data;
2347 selected = g_list_prepend (selected, item);
2350 return g_list_reverse (selected);
2354 egg_icon_list_set_selection_mode (EggIconList *icon_list,
2355 GtkSelectionMode mode)
2357 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2359 if (mode == icon_list->priv->selection_mode)
2362 if (mode == GTK_SELECTION_NONE ||
2363 icon_list->priv->selection_mode == GTK_SELECTION_MULTIPLE)
2364 egg_icon_list_unselect_all (icon_list);
2366 icon_list->priv->selection_mode = mode;
2368 g_object_notify (G_OBJECT (icon_list), "selection_mode");
2372 egg_icon_list_get_selection_mode (EggIconList *icon_list)
2374 g_return_val_if_fail (EGG_IS_ICON_LIST (icon_list), GTK_SELECTION_SINGLE);
2376 return icon_list->priv->selection_mode;
2380 egg_icon_list_select_item (EggIconList *icon_list,
2381 EggIconListItem *item)
2383 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2384 g_return_if_fail (item != NULL);
2389 if (icon_list->priv->selection_mode == GTK_SELECTION_NONE)
2391 else if (icon_list->priv->selection_mode != GTK_SELECTION_MULTIPLE)
2392 egg_icon_list_unselect_all_internal (icon_list, FALSE);
2394 item->selected = TRUE;
2396 g_signal_emit (icon_list, icon_list_signals[SELECTION_CHANGED], 0);
2398 egg_icon_list_queue_draw_item (icon_list, item);
2403 egg_icon_list_unselect_item (EggIconList *icon_list,
2404 EggIconListItem *item)
2406 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2407 g_return_if_fail (item != NULL);
2409 if (!item->selected)
2412 if (icon_list->priv->selection_mode == GTK_SELECTION_NONE ||
2413 icon_list->priv->selection_mode == GTK_SELECTION_BROWSE)
2416 item->selected = FALSE;
2418 g_signal_emit (icon_list, icon_list_signals[SELECTION_CHANGED], 0);
2420 egg_icon_list_queue_draw_item (icon_list, item);
2424 egg_icon_list_item_is_selected (EggIconListItem *item)
2426 g_return_val_if_fail (item != NULL, FALSE);
2428 return item->selected;
2432 egg_icon_list_unselect_all (EggIconList *icon_list)
2434 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2436 egg_icon_list_unselect_all_internal (icon_list, TRUE);
2440 egg_icon_list_select_all (EggIconList *icon_list)
2443 gboolean dirty = FALSE;
2445 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2447 for (items = icon_list->priv->items; items; items = items->next)
2449 EggIconListItem *item = items->data;
2451 if (!item->selected)
2454 item->selected = TRUE;
2455 egg_icon_list_queue_draw_item (icon_list, item);
2460 g_signal_emit (icon_list, icon_list_signals[SELECTION_CHANGED], 0);
2464 egg_icon_list_set_sorted (EggIconList *icon_list,
2467 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2468 g_return_if_fail (icon_list->priv->sort_func != NULL);
2470 if (icon_list->priv->sorted == sorted)
2473 icon_list->priv->sorted = sorted;
2474 g_object_notify (G_OBJECT (icon_list), "sorted");
2476 if (icon_list->priv->sorted)
2477 egg_icon_list_sort (icon_list);
2481 egg_icon_list_get_sorted (EggIconList *icon_list)
2483 g_return_val_if_fail (EGG_IS_ICON_LIST (icon_list), FALSE);
2485 return icon_list->priv->sorted;
2489 egg_icon_list_set_sort_func (EggIconList *icon_list,
2490 EggIconListItemCompareFunc func,
2492 GDestroyNotify destroy_notify)
2494 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2495 g_return_if_fail (func != NULL);
2497 if (icon_list->priv->sort_destroy_notify &&
2498 icon_list->priv->sort_data)
2499 (* icon_list->priv->sort_destroy_notify) (icon_list->priv->sort_data);
2501 icon_list->priv->sort_func = func;
2502 icon_list->priv->sort_data = data;
2503 icon_list->priv->sort_destroy_notify = destroy_notify;
2507 egg_icon_list_set_sort_order (EggIconList *icon_list,
2510 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2512 if (icon_list->priv->sort_order == order)
2515 icon_list->priv->sort_order = order;
2517 if (icon_list->priv->sorted)
2518 egg_icon_list_sort (icon_list);
2520 g_object_notify (G_OBJECT (icon_list), "sort_order");
2524 egg_icon_list_get_sort_order (EggIconList *icon_list)
2526 g_return_val_if_fail (EGG_IS_ICON_LIST (icon_list), GTK_SORT_ASCENDING);
2528 return icon_list->priv->sort_order;
2532 egg_icon_list_item_activated (EggIconList *icon_list,
2533 EggIconListItem *item)
2535 g_signal_emit (G_OBJECT (icon_list), icon_list_signals[ITEM_ACTIVATED], 0, item);
2539 egg_icon_list_get_items (EggIconList *icon_list)
2541 g_return_val_if_fail (EGG_IS_ICON_LIST (icon_list), NULL);
2543 return icon_list->priv->items;
2547 egg_icon_list_item_get_icon_list (EggIconListItem *item)
2549 g_return_val_if_fail (item != NULL, NULL);
2551 return item->icon_list;