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 EggIconListItem *cursor_item;
88 char *typeahead_string;
92 GtkSortType sort_order;
94 EggIconListItemCompareFunc sort_func;
96 GDestroyNotify sort_destroy_notify;
98 EggIconListItem *last_single_clicked;
105 /* Layout used to draw icon text */
133 /* Icon List Item properties */
140 static void egg_icon_list_class_init (EggIconListClass *klass);
141 static void egg_icon_list_init (EggIconList *icon_list);
143 /* GObject signals */
144 static void egg_icon_list_finalize (GObject *object);
145 static void egg_icon_list_set_property (GObject *object,
149 static void egg_icon_list_get_property (GObject *object,
155 /* GtkWidget signals */
156 static void egg_icon_list_realize (GtkWidget *widget);
157 static void egg_icon_list_unrealize (GtkWidget *widget);
158 static void egg_icon_list_map (GtkWidget *widget);
159 static void egg_icon_list_size_request (GtkWidget *widget,
160 GtkRequisition *requisition);
161 static void egg_icon_list_size_allocate (GtkWidget *widget,
162 GtkAllocation *allocation);
163 static gboolean egg_icon_list_expose (GtkWidget *widget,
164 GdkEventExpose *expose);
165 static gboolean egg_icon_list_motion (GtkWidget *widget,
166 GdkEventMotion *event);
167 static gboolean egg_icon_list_button_press (GtkWidget *widget,
168 GdkEventButton *event);
169 static gboolean egg_icon_list_button_release (GtkWidget *widget,
170 GdkEventButton *event);
171 static gboolean egg_icon_list_key_press (GtkWidget *widget,
175 /* EggIconList signals */
176 static void egg_icon_list_set_adjustments (EggIconList *icon_list,
178 GtkAdjustment *vadj);
179 static void egg_icon_list_real_select_all (EggIconList *icon_list);
180 static void egg_icon_list_real_unselect_all (EggIconList *icon_list);
181 static void egg_icon_list_real_select_cursor_item (EggIconList *icon_list);
182 static void egg_icon_list_real_toggle_cursor_item (EggIconList *icon_list);
184 /* Internal functions */
185 static void egg_icon_list_adjustment_changed (GtkAdjustment *adjustment,
186 EggIconList *icon_list);
187 static void egg_icon_list_layout (EggIconList *icon_list);
188 static void egg_icon_list_paint_item (EggIconList *icon_list,
189 EggIconListItem *item,
191 static void egg_icon_list_paint_rubberband (EggIconList *icon_list,
193 static void egg_icon_list_queue_draw_item (EggIconList *icon_list,
194 EggIconListItem *item);
195 static void egg_icon_list_queue_layout (EggIconList *icon_list);
196 static void egg_icon_list_set_cursor_item (EggIconList *icon_list,
197 EggIconListItem *item);
198 static void egg_icon_list_append_typeahead_string (EggIconList *icon_list,
199 const gchar *string);
200 static void egg_icon_list_select_first_matching_item (EggIconList *icon_list,
201 const char *pattern);
202 static void egg_icon_list_start_rubberbanding (EggIconList *icon_list,
205 static void egg_icon_list_stop_rubberbanding (EggIconList *icon_list);
206 static void egg_icon_list_sort (EggIconList *icon_list);
207 static gint egg_icon_list_sort_func (EggIconListItem *a,
209 EggIconList *icon_list);
210 static void egg_icon_list_insert_item_sorted (EggIconList *icon_list,
211 EggIconListItem *item);
212 static void egg_icon_list_validate (EggIconList *icon_list);
213 static void egg_icon_list_update_rubberband_selection (EggIconList *icon_list);
214 static gboolean egg_icon_list_item_hit_test (EggIconListItem *item,
219 static gboolean egg_icon_list_maybe_begin_dragging_items (EggIconList *icon_list,
220 GdkEventMotion *event);
221 static gboolean egg_icon_list_unselect_all_internal (EggIconList *icon_list,
223 static void egg_icon_list_calculate_item_size (EggIconList *icon_list, EggIconListItem *item);
224 static void rubberbanding (gpointer data);
227 static void egg_icon_list_item_invalidate_size (EggIconListItem *item);
229 static GtkContainerClass *parent_class = NULL;
230 static guint icon_list_signals[LAST_SIGNAL] = { 0 };
233 egg_icon_list_item_get_type (void)
235 static GType boxed_type = 0;
238 boxed_type = g_boxed_type_register_static ("EggIconListItem",
239 (GBoxedCopyFunc) egg_icon_list_item_ref,
240 (GBoxedFreeFunc) egg_icon_list_item_unref);
246 egg_icon_list_get_type (void)
248 static GType object_type = 0;
252 static const GTypeInfo object_info =
254 sizeof (EggIconListClass),
255 NULL, /* base_init */
256 NULL, /* base_finalize */
257 (GClassInitFunc) egg_icon_list_class_init,
258 NULL, /* class_finalize */
259 NULL, /* class_data */
260 sizeof (EggIconList),
262 (GInstanceInitFunc) egg_icon_list_init
265 object_type = g_type_register_static (GTK_TYPE_CONTAINER, "EggIconList", &object_info, 0);
272 egg_icon_list_class_init (EggIconListClass *klass)
274 GObjectClass *gobject_class;
275 GtkWidgetClass *widget_class;
276 GtkBindingSet *binding_set;
278 parent_class = g_type_class_peek_parent (klass);
279 binding_set = gtk_binding_set_by_class (klass);
281 gobject_class = (GObjectClass *) klass;
282 widget_class = (GtkWidgetClass *) klass;
284 gobject_class->finalize = egg_icon_list_finalize;
285 gobject_class->set_property = egg_icon_list_set_property;
286 gobject_class->get_property = egg_icon_list_get_property;
288 widget_class->realize = egg_icon_list_realize;
289 widget_class->unrealize = egg_icon_list_unrealize;
290 widget_class->map = egg_icon_list_map;
291 widget_class->size_request = egg_icon_list_size_request;
292 widget_class->size_allocate = egg_icon_list_size_allocate;
293 widget_class->expose_event = egg_icon_list_expose;
294 widget_class->motion_notify_event = egg_icon_list_motion;
295 widget_class->button_press_event = egg_icon_list_button_press;
296 widget_class->button_release_event = egg_icon_list_button_release;
297 widget_class->key_press_event = egg_icon_list_key_press;
299 klass->set_scroll_adjustments = egg_icon_list_set_adjustments;
300 klass->select_all = egg_icon_list_real_select_all;
301 klass->unselect_all = egg_icon_list_real_unselect_all;
302 klass->select_cursor_item = egg_icon_list_real_select_cursor_item;
303 klass->toggle_cursor_item = egg_icon_list_real_toggle_cursor_item;
306 g_object_class_install_property (gobject_class,
308 g_param_spec_enum ("selection_mode",
310 _("The selection mode"),
311 GTK_TYPE_SELECTION_MODE,
312 GTK_SELECTION_SINGLE,
315 g_object_class_install_property (gobject_class,
317 g_param_spec_boolean ("sorted",
319 _("Icon list is sorted"),
322 g_object_class_install_property (gobject_class,
324 g_param_spec_enum ("sort_order",
326 _("Sort direction the icon list should use"),
329 G_PARAM_READABLE | G_PARAM_WRITABLE));
331 /* Style properties */
332 #define _ICON_LIST_TOP_MARGIN 6
333 #define _ICON_LIST_BOTTOM_MARGIN 6
334 #define _ICON_LIST_LEFT_MARGIN 6
335 #define _ICON_LIST_RIGHT_MARGIN 6
336 #define _ICON_LIST_ICON_PADDING 6
338 gtk_widget_class_install_style_property (widget_class,
339 g_param_spec_int ("icon_padding",
341 _("Number of pixels between icons"),
344 _ICON_LIST_ICON_PADDING,
346 gtk_widget_class_install_style_property (widget_class,
347 g_param_spec_int ("top_margin",
349 _("Number of pixels in top margin"),
352 _ICON_LIST_TOP_MARGIN,
354 gtk_widget_class_install_style_property (widget_class,
355 g_param_spec_int ("bottom_margin",
357 _("Number of pixels in bottom margin"),
360 _ICON_LIST_BOTTOM_MARGIN,
363 gtk_widget_class_install_style_property (widget_class,
364 g_param_spec_int ("left_margin",
366 _("Number of pixels in left margin"),
369 _ICON_LIST_LEFT_MARGIN,
371 gtk_widget_class_install_style_property (widget_class,
372 g_param_spec_int ("right_margin",
374 _("Number of pixels in right margin"),
377 _ICON_LIST_RIGHT_MARGIN,
380 gtk_widget_class_install_style_property (widget_class,
381 g_param_spec_boxed ("selection_box_color",
382 _("Selection Box Color"),
383 _("Color of the selection box"),
387 gtk_widget_class_install_style_property (widget_class,
388 g_param_spec_uchar ("selection_box_alpha",
389 _("Selection Box Alpha"),
390 _("Opacity of the selection box"),
396 widget_class->set_scroll_adjustments_signal =
397 g_signal_new ("set_scroll_adjustments",
398 G_TYPE_FROM_CLASS (gobject_class),
400 G_STRUCT_OFFSET (EggIconListClass, set_scroll_adjustments),
402 _egg_marshal_VOID__OBJECT_OBJECT,
404 GTK_TYPE_ADJUSTMENT, GTK_TYPE_ADJUSTMENT);
406 icon_list_signals[ITEM_ACTIVATED] =
407 g_signal_new ("item_activated",
408 G_TYPE_FROM_CLASS (gobject_class),
410 G_STRUCT_OFFSET (EggIconListClass, item_activated),
412 g_cclosure_marshal_VOID__BOXED,
414 EGG_TYPE_ICON_LIST_ITEM);
416 icon_list_signals[SELECTION_CHANGED] =
417 g_signal_new ("selection_changed",
418 G_TYPE_FROM_CLASS (gobject_class),
420 G_STRUCT_OFFSET (EggIconListClass, selection_changed),
422 g_cclosure_marshal_VOID__VOID,
425 icon_list_signals[ITEM_ADDED] =
426 g_signal_new ("item_added",
427 G_TYPE_FROM_CLASS (gobject_class),
429 G_STRUCT_OFFSET (EggIconListClass, item_added),
431 g_cclosure_marshal_VOID__BOXED,
432 G_TYPE_NONE, 1, EGG_TYPE_ICON_LIST_ITEM);
434 icon_list_signals[ITEM_REMOVED] =
435 g_signal_new ("item_removed",
436 G_TYPE_FROM_CLASS (gobject_class),
438 G_STRUCT_OFFSET (EggIconListClass, item_removed),
440 g_cclosure_marshal_VOID__BOXED,
441 G_TYPE_NONE, 1, EGG_TYPE_ICON_LIST_ITEM);
443 icon_list_signals[SELECT_ALL] =
444 g_signal_new ("select_all",
445 G_TYPE_FROM_CLASS (gobject_class),
446 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
447 G_STRUCT_OFFSET (EggIconListClass, select_all),
449 g_cclosure_marshal_VOID__VOID,
452 icon_list_signals[UNSELECT_ALL] =
453 g_signal_new ("unselect_all",
454 G_TYPE_FROM_CLASS (gobject_class),
455 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
456 G_STRUCT_OFFSET (EggIconListClass, unselect_all),
458 g_cclosure_marshal_VOID__VOID,
461 icon_list_signals[SELECT_CURSOR_ITEM] =
462 g_signal_new ("select_cursor_item",
463 G_TYPE_FROM_CLASS (gobject_class),
464 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
465 G_STRUCT_OFFSET (EggIconListClass, select_cursor_item),
467 g_cclosure_marshal_VOID__VOID,
470 icon_list_signals[SELECT_CURSOR_ITEM] =
471 g_signal_new ("toggle_cursor_item",
472 G_TYPE_FROM_CLASS (gobject_class),
473 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
474 G_STRUCT_OFFSET (EggIconListClass, toggle_cursor_item),
476 g_cclosure_marshal_VOID__VOID,
480 gtk_binding_entry_add_signal (binding_set, GDK_a, GDK_CONTROL_MASK, "select_all", 0);
481 gtk_binding_entry_add_signal (binding_set, GDK_a, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "unselect_all", 0);
482 gtk_binding_entry_add_signal (binding_set, GDK_space, 0, "select_cursor_item", 0);
483 gtk_binding_entry_add_signal (binding_set, GDK_space, GDK_CONTROL_MASK, "toggle_cursor_item", 0);
488 egg_icon_list_init (EggIconList *icon_list)
490 icon_list->priv = g_new0 (EggIconListPrivate, 1);
491 GTK_WIDGET_SET_FLAGS (icon_list, GTK_CAN_FOCUS);
493 icon_list->priv->width = 0;
494 icon_list->priv->height = 0;
495 icon_list->priv->selection_mode = GTK_SELECTION_SINGLE;
496 icon_list->priv->sort_order = GTK_SORT_ASCENDING;
497 icon_list->priv->pressed_button = -1;
498 icon_list->priv->press_start_x = -1;
499 icon_list->priv->press_start_y = -1;
500 icon_list->priv->layout = gtk_widget_create_pango_layout (GTK_WIDGET (icon_list), NULL);
501 pango_layout_set_wrap (icon_list->priv->layout, PANGO_WRAP_CHAR);
503 egg_icon_list_set_adjustments (icon_list, NULL, NULL);
507 /* GObject methods */
509 egg_icon_list_finalize (GObject *object)
511 EggIconList *icon_list;
513 icon_list = EGG_ICON_LIST (object);
515 if (icon_list->priv->layout_idle_id != 0)
516 g_source_remove (icon_list->priv->layout_idle_id);
518 g_free (icon_list->priv);
520 (G_OBJECT_CLASS (parent_class)->finalize) (object);
525 egg_icon_list_set_property (GObject *object,
530 EggIconList *icon_list;
532 icon_list = EGG_ICON_LIST (object);
536 case PROP_SELECTION_MODE:
537 egg_icon_list_set_selection_mode (icon_list, g_value_get_enum (value));
540 egg_icon_list_set_sorted (icon_list, g_value_get_boolean (value));
542 case PROP_SORT_ORDER:
543 egg_icon_list_set_sort_order (icon_list, g_value_get_enum (value));
546 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
552 egg_icon_list_get_property (GObject *object,
557 EggIconList *icon_list;
559 icon_list = EGG_ICON_LIST (object);
563 case PROP_SELECTION_MODE:
564 g_value_set_enum (value, icon_list->priv->selection_mode);
567 g_value_set_boolean (value, icon_list->priv->sorted);
569 case PROP_SORT_ORDER:
570 g_value_set_enum (value, icon_list->priv->sort_order);
573 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
578 /* GtkWidget signals */
580 egg_icon_list_realize (GtkWidget *widget)
582 EggIconList *icon_list;
583 GdkWindowAttr attributes;
584 gint attributes_mask;
586 icon_list = EGG_ICON_LIST (widget);
588 GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
590 /* Make the main, clipping window */
591 attributes.window_type = GDK_WINDOW_CHILD;
592 attributes.x = widget->allocation.x;
593 attributes.y = widget->allocation.y;
594 attributes.width = widget->allocation.width;
595 attributes.height = widget->allocation.height;
596 attributes.wclass = GDK_INPUT_OUTPUT;
597 attributes.visual = gtk_widget_get_visual (widget);
598 attributes.colormap = gtk_widget_get_colormap (widget);
599 attributes.event_mask = GDK_VISIBILITY_NOTIFY_MASK;
601 attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
603 widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
604 &attributes, attributes_mask);
605 gdk_window_set_user_data (widget->window, widget);
607 /* Make the window for the icon list */
610 attributes.width = MAX (icon_list->priv->width, widget->allocation.width);
611 attributes.height = MAX (icon_list->priv->height, widget->allocation.height);
612 attributes.event_mask = (GDK_EXPOSURE_MASK |
614 GDK_POINTER_MOTION_MASK |
615 GDK_BUTTON_PRESS_MASK |
616 GDK_BUTTON_RELEASE_MASK |
618 GDK_KEY_RELEASE_MASK) |
619 gtk_widget_get_events (widget);
621 icon_list->priv->bin_window = gdk_window_new (widget->window,
622 &attributes, attributes_mask);
623 gdk_window_set_user_data (icon_list->priv->bin_window, widget);
625 widget->style = gtk_style_attach (widget->style, widget->window);
626 gdk_window_set_background (icon_list->priv->bin_window, &widget->style->base[widget->state]);
627 gdk_window_set_background (widget->window, &widget->style->base[widget->state]);
633 egg_icon_list_unrealize (GtkWidget *widget)
635 EggIconList *icon_list;
637 icon_list = EGG_ICON_LIST (widget);
639 gdk_window_set_user_data (icon_list->priv->bin_window, NULL);
640 gdk_window_destroy (icon_list->priv->bin_window);
641 icon_list->priv->bin_window = NULL;
643 /* GtkWidget::unrealize destroys children and widget->window */
644 if (GTK_WIDGET_CLASS (parent_class)->unrealize)
645 (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
649 egg_icon_list_map (GtkWidget *widget)
651 EggIconList *icon_list;
653 icon_list = EGG_ICON_LIST (widget);
655 GTK_WIDGET_SET_FLAGS (widget, GTK_MAPPED);
657 gdk_window_show (icon_list->priv->bin_window);
658 gdk_window_show (widget->window);
662 egg_icon_list_size_request (GtkWidget *widget,
663 GtkRequisition *requisition)
665 EggIconList *icon_list;
667 icon_list = EGG_ICON_LIST (widget);
669 requisition->width = icon_list->priv->width;
670 requisition->height = icon_list->priv->height;
674 egg_icon_list_size_allocate (GtkWidget *widget,
675 GtkAllocation *allocation)
677 EggIconList *icon_list;
679 widget->allocation = *allocation;
681 icon_list = EGG_ICON_LIST (widget);
683 if (GTK_WIDGET_REALIZED (widget))
685 gdk_window_move_resize (widget->window,
686 allocation->x, allocation->y,
687 allocation->width, allocation->height);
688 gdk_window_resize (icon_list->priv->bin_window,
689 MAX (icon_list->priv->width, allocation->width),
690 MAX (icon_list->priv->height, allocation->height));
693 icon_list->priv->hadjustment->page_size = allocation->width;
694 icon_list->priv->hadjustment->page_increment = allocation->width * 0.9;
695 icon_list->priv->hadjustment->step_increment = allocation->width * 0.1;
696 icon_list->priv->hadjustment->lower = 0;
697 icon_list->priv->hadjustment->upper = MAX (allocation->width, icon_list->priv->width);
698 gtk_adjustment_changed (icon_list->priv->hadjustment);
700 icon_list->priv->vadjustment->page_size = allocation->height;
701 icon_list->priv->vadjustment->page_increment = allocation->height * 0.9;
702 icon_list->priv->vadjustment->step_increment = allocation->width * 0.1;
703 icon_list->priv->vadjustment->lower = 0;
704 icon_list->priv->vadjustment->upper = MAX (allocation->height, icon_list->priv->height);
705 gtk_adjustment_changed (icon_list->priv->vadjustment);
707 egg_icon_list_layout (icon_list);
711 egg_icon_list_expose (GtkWidget *widget,
712 GdkEventExpose *expose)
714 EggIconList *icon_list;
717 icon_list = EGG_ICON_LIST (widget);
719 if (expose->window != icon_list->priv->bin_window)
722 for (icons = icon_list->priv->items; icons; icons = icons->next) {
723 EggIconListItem *item = icons->data;
724 GdkRectangle item_rectangle;
726 item_rectangle.x = item->x;
727 item_rectangle.y = item->y;
728 item_rectangle.width = item->width;
729 item_rectangle.height = item->height;
731 if (gdk_region_rect_in (expose->region, &item_rectangle) == GDK_OVERLAP_RECTANGLE_OUT)
734 egg_icon_list_paint_item (icon_list, item, &expose->area);
737 if (icon_list->priv->rubberbanding)
739 GdkRectangle *rectangles;
742 gdk_region_get_rectangles (expose->region,
746 while (n_rectangles--)
747 egg_icon_list_paint_rubberband (icon_list, &rectangles[n_rectangles]);
756 egg_icon_list_motion (GtkWidget *widget,
757 GdkEventMotion *event)
759 EggIconList *icon_list;
761 icon_list = EGG_ICON_LIST (widget);
763 egg_icon_list_maybe_begin_dragging_items (icon_list, event);
765 if (icon_list->priv->rubberbanding)
766 rubberbanding (widget);
772 egg_icon_list_button_press (GtkWidget *widget,
773 GdkEventButton *event)
775 EggIconList *icon_list;
776 EggIconListItem *item;
777 gboolean dirty = FALSE;
779 icon_list = EGG_ICON_LIST (widget);
781 if (event->window != icon_list->priv->bin_window)
784 if (!GTK_WIDGET_HAS_FOCUS (widget))
785 gtk_widget_grab_focus (widget);
787 if (event->button == 1 && event->type == GDK_BUTTON_PRESS)
790 if (icon_list->priv->selection_mode == GTK_SELECTION_NONE)
793 item = egg_icon_list_get_item_at_pos (icon_list,
798 if (icon_list->priv->selection_mode == GTK_SELECTION_MULTIPLE &&
799 (event->state & GDK_CONTROL_MASK))
801 item->selected = !item->selected;
808 egg_icon_list_unselect_all_internal (icon_list, FALSE);
810 item->selected = TRUE;
815 egg_icon_list_set_cursor_item (icon_list, item);
816 egg_icon_list_queue_draw_item (icon_list, item);
818 /* Save press to possibly begin a drag */
819 if (icon_list->priv->pressed_button < 0)
821 icon_list->priv->pressed_button = event->button;
822 icon_list->priv->press_start_x = event->x;
823 icon_list->priv->press_start_y = event->y;
826 if (!icon_list->priv->last_single_clicked)
827 icon_list->priv->last_single_clicked = item;
831 if (icon_list->priv->selection_mode != GTK_SELECTION_BROWSE &&
832 !(event->state & GDK_CONTROL_MASK))
834 dirty = egg_icon_list_unselect_all_internal (icon_list, FALSE);
837 if (icon_list->priv->selection_mode == GTK_SELECTION_MULTIPLE)
838 egg_icon_list_start_rubberbanding (icon_list, event->x, event->y);
843 if (event->button == 1 && event->type == GDK_2BUTTON_PRESS)
845 item = egg_icon_list_get_item_at_pos (icon_list,
848 if (item && item == icon_list->priv->last_single_clicked)
850 egg_icon_list_item_activated (icon_list, item);
853 icon_list->priv->last_single_clicked = NULL;
857 g_signal_emit (icon_list, icon_list_signals[SELECTION_CHANGED], 0);
863 egg_icon_list_button_release (GtkWidget *widget,
864 GdkEventButton *event)
866 EggIconList *icon_list;
868 icon_list = EGG_ICON_LIST (widget);
870 if (icon_list->priv->pressed_button == event->button)
871 icon_list->priv->pressed_button = -1;
873 egg_icon_list_stop_rubberbanding (icon_list);
880 egg_icon_list_key_press (GtkWidget *widget,
883 if ((* GTK_WIDGET_CLASS (parent_class)->key_press_event) (widget, event))
888 if ((event->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK)) == 0)
889 egg_icon_list_append_typeahead_string (EGG_ICON_LIST (widget), event->string);
895 egg_icon_list_select_first_matching_item (EggIconList *icon_list,
903 for (items = icon_list->priv->items; items; items = items->next)
905 EggIconListItem *item = items->data;
907 if (strncmp (pattern, item->label, strlen (pattern)) == 0)
909 egg_icon_list_select_item (icon_list, item);
916 rubberbanding (gpointer data)
918 EggIconList *icon_list;
920 GdkRectangle old_area;
921 GdkRectangle new_area;
923 GdkRegion *invalid_region;
925 icon_list = EGG_ICON_LIST (data);
927 gdk_window_get_pointer (icon_list->priv->bin_window, &x, &y, NULL);
932 old_area.x = MIN (icon_list->priv->rubberband_x1,
933 icon_list->priv->rubberband_x2);
934 old_area.y = MIN (icon_list->priv->rubberband_y1,
935 icon_list->priv->rubberband_y2);
936 old_area.width = ABS (icon_list->priv->rubberband_x2 -
937 icon_list->priv->rubberband_x1) + 1;
938 old_area.height = ABS (icon_list->priv->rubberband_y2 -
939 icon_list->priv->rubberband_y1) + 1;
941 new_area.x = MIN (icon_list->priv->rubberband_x1, x);
942 new_area.y = MIN (icon_list->priv->rubberband_y1, y);
943 new_area.width = ABS (x - icon_list->priv->rubberband_x1) + 1;
944 new_area.height = ABS (y - icon_list->priv->rubberband_y1) + 1;
946 invalid_region = gdk_region_rectangle (&old_area);
947 gdk_region_union_with_rect (invalid_region, &new_area);
949 gdk_rectangle_intersect (&old_area, &new_area, &common);
950 if (common.width > 2 && common.height > 2)
952 GdkRegion *common_region;
954 /* make sure the border is invalidated */
960 common_region = gdk_region_rectangle (&common);
962 gdk_region_subtract (invalid_region, common_region);
963 gdk_region_destroy (common_region);
966 gdk_window_invalidate_region (icon_list->priv->bin_window, invalid_region, TRUE);
968 gdk_region_destroy (invalid_region);
970 icon_list->priv->rubberband_x2 = x;
971 icon_list->priv->rubberband_y2 = y;
973 egg_icon_list_update_rubberband_selection (icon_list);
977 egg_icon_list_start_rubberbanding (EggIconList *icon_list,
983 g_assert (!icon_list->priv->rubberbanding);
985 for (items = icon_list->priv->items; items; items = items->next)
987 EggIconListItem *item = items->data;
989 item->selected_before_rubberbanding = item->selected;
992 icon_list->priv->rubberband_x1 = x;
993 icon_list->priv->rubberband_y1 = y;
994 icon_list->priv->rubberband_x2 = x;
995 icon_list->priv->rubberband_y2 = y;
997 icon_list->priv->rubberbanding = TRUE;
999 gtk_grab_add (GTK_WIDGET (icon_list));
1003 egg_icon_list_stop_rubberbanding (EggIconList *icon_list)
1005 if (!icon_list->priv->rubberbanding)
1008 icon_list->priv->rubberbanding = FALSE;
1010 gtk_grab_remove (GTK_WIDGET (icon_list));
1012 gtk_widget_queue_draw (GTK_WIDGET (icon_list));
1016 egg_icon_list_sort_func (EggIconListItem *a,
1018 EggIconList *icon_list)
1022 result = (* icon_list->priv->sort_func) (icon_list, a, b,
1023 icon_list->priv->sort_data);
1025 if (icon_list->priv->sort_order == GTK_SORT_DESCENDING)
1032 egg_icon_list_insert_item_sorted (EggIconList *icon_list,
1033 EggIconListItem *item)
1036 GList *tmp_list = icon_list->priv->items;
1039 egg_icon_list_validate (icon_list);
1041 list = g_list_alloc ();
1043 item->icon_list = icon_list;
1045 egg_icon_list_item_ref (item);
1047 if (!icon_list->priv->items)
1049 icon_list->priv->items = list;
1050 icon_list->priv->last_item = list;
1051 icon_list->priv->item_count += 1;
1053 egg_icon_list_validate (icon_list);
1058 cmp = egg_icon_list_sort_func (item, tmp_list->data, icon_list);
1060 while ((tmp_list->next) && (cmp > 0))
1062 tmp_list = tmp_list->next;
1063 cmp = egg_icon_list_sort_func (item, tmp_list->data, icon_list);
1066 if ((!tmp_list->next) && (cmp > 0))
1068 tmp_list->next = list;
1069 list->prev = tmp_list;
1070 icon_list->priv->last_item = list;
1071 icon_list->priv->item_count += 1;
1072 egg_icon_list_validate (icon_list);
1079 tmp_list->prev->next = list;
1080 list->prev = tmp_list->prev;
1083 list->next = tmp_list;
1084 tmp_list->prev = list;
1086 if (tmp_list == icon_list->priv->items)
1087 icon_list->priv->items = list;
1089 icon_list->priv->item_count += 1;
1090 egg_icon_list_validate (icon_list);
1092 egg_icon_list_queue_layout (icon_list);
1097 egg_icon_list_sort (EggIconList *icon_list)
1099 egg_icon_list_validate (icon_list);
1101 /* FIXME: We can optimize this */
1102 icon_list->priv->items = g_list_sort_with_data (icon_list->priv->items,
1103 (GCompareDataFunc)egg_icon_list_sort_func,
1105 icon_list->priv->last_item = g_list_last (icon_list->priv->items);
1107 egg_icon_list_validate (icon_list);
1108 egg_icon_list_queue_layout (icon_list);
1113 egg_icon_list_validate (EggIconList *icon_list)
1119 for (list = icon_list->priv->items; list; list = list->next)
1121 EggIconListItem *item = list->data;
1123 g_print ("%s\n", egg_icon_list_item_get_label (item));
1128 g_assert (g_list_length (icon_list->priv->items) == icon_list->priv->item_count);
1129 g_assert (g_list_last (icon_list->priv->items) == icon_list->priv->last_item);
1130 g_assert (g_list_first (icon_list->priv->last_item) == icon_list->priv->items);
1134 egg_icon_list_update_rubberband_selection (EggIconList *icon_list)
1137 gint x, y, width, height;
1138 gboolean dirty = FALSE;
1140 x = MIN (icon_list->priv->rubberband_x1,
1141 icon_list->priv->rubberband_x2);
1142 y = MIN (icon_list->priv->rubberband_y1,
1143 icon_list->priv->rubberband_y2);
1144 width = ABS (icon_list->priv->rubberband_x1 -
1145 icon_list->priv->rubberband_x2);
1146 height = ABS (icon_list->priv->rubberband_y1 -
1147 icon_list->priv->rubberband_y2);
1149 for (items = icon_list->priv->items; items; items = items->next)
1151 EggIconListItem *item = items->data;
1155 is_in = egg_icon_list_item_hit_test (item, x, y, width, height);
1157 selected = is_in ^ item->selected_before_rubberbanding;
1159 if (item->selected != selected)
1161 item->selected = selected;
1163 egg_icon_list_queue_draw_item (icon_list, item);
1168 g_signal_emit (icon_list, icon_list_signals[SELECTION_CHANGED], 0);
1172 egg_icon_list_item_hit_test (EggIconListItem *item,
1178 /* First try the pixbuf */
1179 if (MIN (x + width, item->pixbuf_x + item->pixbuf_width) - MAX (x, item->pixbuf_x) > 0 &&
1180 MIN (y + height, item->pixbuf_y + item->pixbuf_height) - MAX (y, item->pixbuf_y) > 0)
1183 /* Then try the text */
1184 if (MIN (x + width, item->layout_x + item->layout_width) - MAX (x, item->layout_x) > 0 &&
1185 MIN (y + height, item->layout_y + item->layout_height) - MAX (y, item->layout_y) > 0)
1192 egg_icon_list_maybe_begin_dragging_items (EggIconList *icon_list,
1193 GdkEventMotion *event)
1195 gboolean retval = FALSE;
1197 if (icon_list->priv->pressed_button < 0)
1200 if (!gtk_drag_check_threshold (GTK_WIDGET (icon_list),
1201 icon_list->priv->press_start_x,
1202 icon_list->priv->press_start_y,
1203 event->x, event->y))
1206 button = icon_list->priv->pressed_button;
1207 icon_list->priv->pressed_button = -1;
1210 static GtkTargetEntry row_targets[] = {
1211 { "EGG_ICON_LIST_ITEMS", GTK_TARGET_SAME_APP, 0 }
1213 GtkTargetList *target_list;
1214 GdkDragContext *context;
1215 EggIconListItem *item;
1219 target_list = gtk_target_list_new (row_targets, G_N_ELEMENTS (row_targets));
1221 context = gtk_drag_begin (GTK_WIDGET (icon_list),
1222 target_list, GDK_ACTION_MOVE,
1226 item = egg_icon_list_get_item_at_pos (icon_list,
1227 icon_list->priv->press_start_x,
1228 icon_list->priv->press_start_y);
1229 g_assert (item != NULL);
1230 gtk_drag_set_icon_pixbuf (context, egg_icon_list_item_get_icon (item),
1232 event->y - item->y);
1240 egg_icon_list_unselect_all_internal (EggIconList *icon_list,
1243 gboolean dirty = FALSE;
1246 for (items = icon_list->priv->items; items; items = items->next)
1248 EggIconListItem *item = items->data;
1252 item->selected = FALSE;
1254 egg_icon_list_queue_draw_item (icon_list, item);
1259 g_signal_emit (icon_list, icon_list_signals[SELECTION_CHANGED], 0);
1265 /* EggIconList signals */
1267 egg_icon_list_set_adjustments (EggIconList *icon_list,
1268 GtkAdjustment *hadj,
1269 GtkAdjustment *vadj)
1271 gboolean need_adjust = FALSE;
1274 g_return_if_fail (GTK_IS_ADJUSTMENT (hadj));
1276 hadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
1278 g_return_if_fail (GTK_IS_ADJUSTMENT (vadj));
1280 vadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
1282 if (icon_list->priv->hadjustment && (icon_list->priv->hadjustment != hadj))
1284 g_signal_handlers_disconnect_matched (icon_list->priv->hadjustment, G_SIGNAL_MATCH_DATA,
1285 0, 0, NULL, NULL, icon_list);
1286 g_object_unref (icon_list->priv->hadjustment);
1289 if (icon_list->priv->vadjustment && (icon_list->priv->vadjustment != vadj))
1291 g_signal_handlers_disconnect_matched (icon_list->priv->vadjustment, G_SIGNAL_MATCH_DATA,
1292 0, 0, NULL, NULL, icon_list);
1293 g_object_unref (icon_list->priv->vadjustment);
1296 if (icon_list->priv->hadjustment != hadj)
1298 icon_list->priv->hadjustment = hadj;
1299 g_object_ref (icon_list->priv->hadjustment);
1300 gtk_object_sink (GTK_OBJECT (icon_list->priv->hadjustment));
1302 g_signal_connect (icon_list->priv->hadjustment, "value_changed",
1303 G_CALLBACK (egg_icon_list_adjustment_changed),
1308 if (icon_list->priv->vadjustment != vadj)
1310 icon_list->priv->vadjustment = vadj;
1311 g_object_ref (icon_list->priv->vadjustment);
1312 gtk_object_sink (GTK_OBJECT (icon_list->priv->vadjustment));
1314 g_signal_connect (icon_list->priv->vadjustment, "value_changed",
1315 G_CALLBACK (egg_icon_list_adjustment_changed),
1321 egg_icon_list_adjustment_changed (NULL, icon_list);
1325 egg_icon_list_real_select_all (EggIconList *icon_list)
1327 if (icon_list->priv->selection_mode != GTK_SELECTION_MULTIPLE)
1330 egg_icon_list_select_all (icon_list);
1334 egg_icon_list_real_unselect_all (EggIconList *icon_list)
1336 if (icon_list->priv->selection_mode == GTK_SELECTION_BROWSE)
1339 egg_icon_list_unselect_all (icon_list);
1343 egg_icon_list_real_select_cursor_item (EggIconList *icon_list)
1345 egg_icon_list_unselect_all (icon_list);
1347 if (icon_list->priv->cursor_item != NULL)
1348 egg_icon_list_select_item (icon_list, icon_list->priv->cursor_item);
1352 egg_icon_list_real_toggle_cursor_item (EggIconList *icon_list)
1354 if (icon_list->priv->selection_mode == GTK_SELECTION_NONE)
1357 /* FIXME: Use another function here */
1358 if (icon_list->priv->cursor_item != NULL)
1360 if (icon_list->priv->selection_mode == GTK_SELECTION_BROWSE)
1361 icon_list->priv->cursor_item->selected = TRUE;
1363 icon_list->priv->cursor_item->selected = !icon_list->priv->cursor_item->selected;
1365 egg_icon_list_queue_draw_item (icon_list, icon_list->priv->cursor_item);
1369 /* Internal functions */
1371 egg_icon_list_adjustment_changed (GtkAdjustment *adjustment,
1372 EggIconList *icon_list)
1374 if (GTK_WIDGET_REALIZED (icon_list))
1376 gdk_window_move (icon_list->priv->bin_window,
1377 - icon_list->priv->hadjustment->value,
1378 - icon_list->priv->vadjustment->value);
1379 gdk_window_process_updates (icon_list->priv->bin_window, TRUE);
1384 egg_icon_list_layout_single_row (EggIconList *icon_list, GList *first_item, gint *y, gint *maximum_width)
1386 gint x, current_width, max_height, max_pixbuf_height;
1387 GList *items, *last_item;
1389 gint left_margin, right_margin;
1390 gint maximum_layout_width;
1394 max_pixbuf_height = 0;
1398 gtk_widget_style_get (GTK_WIDGET (icon_list),
1399 "icon_padding", &icon_padding,
1400 "left_margin", &left_margin,
1401 "right_margin", &right_margin,
1405 current_width += left_margin + right_margin;
1410 EggIconListItem *item = items->data;
1412 egg_icon_list_calculate_item_size (icon_list, item);
1414 current_width += MAX (item->width, MINIMUM_ICON_ITEM_WIDTH);
1416 /* Don't add padding to the first or last icon */
1418 if (current_width > GTK_WIDGET (icon_list)->allocation.width &&
1419 items != first_item)
1422 maximum_layout_width = MAX (item->pixbuf_width, MINIMUM_ICON_ITEM_WIDTH);
1427 if (item->width < MINIMUM_ICON_ITEM_WIDTH) {
1428 item->x += (MINIMUM_ICON_ITEM_WIDTH - item->width) / 2;
1429 x += (MINIMUM_ICON_ITEM_WIDTH - item->width);
1432 item->pixbuf_x = item->x + (item->width - item->pixbuf_width) / 2;
1433 item->layout_x = item->x + (item->width - item->layout_width) / 2;
1437 max_height = MAX (max_height, item->height);
1438 max_pixbuf_height = MAX (max_pixbuf_height, item->pixbuf_height);
1440 if (current_width > *maximum_width)
1441 *maximum_width = current_width;
1443 items = items->next;
1448 *y += max_height + icon_padding;
1450 /* Now go through the row again and align the icons */
1451 for (items = first_item; items != last_item; items = items->next)
1453 EggIconListItem *item = items->data;
1455 item->pixbuf_y = item->y + (max_pixbuf_height - item->pixbuf_height);
1456 item->layout_y = item->pixbuf_y + item->pixbuf_height + ICON_TEXT_PADDING;
1458 /* Update the bounding box */
1459 item->y = item->pixbuf_y;
1461 /* We may want to readjust the new y coordinate. */
1462 if (item->y + item->height > *y)
1463 *y = item->y + item->height;
1470 egg_icon_list_set_adjustment_upper (GtkAdjustment *adj,
1473 if (upper != adj->upper)
1475 gdouble min = MAX (0.0, upper - adj->page_size);
1476 gboolean value_changed = FALSE;
1480 if (adj->value > min)
1483 value_changed = TRUE;
1486 gtk_adjustment_changed (adj);
1489 gtk_adjustment_value_changed (adj);
1494 egg_icon_list_layout (EggIconList *icon_list)
1496 gint y = 0, maximum_width = 0;
1499 gint top_margin, bottom_margin;
1501 widget = GTK_WIDGET (icon_list);
1502 icons = icon_list->priv->items;
1504 gtk_widget_style_get (widget,
1505 "top_margin", &top_margin,
1506 "bottom_margin", &bottom_margin,
1512 icons = egg_icon_list_layout_single_row (icon_list, icons, &y, &maximum_width);
1514 while (icons != NULL);
1516 if (maximum_width != icon_list->priv->width)
1518 icon_list->priv->width = maximum_width;
1522 if (y != icon_list->priv->height)
1524 icon_list->priv->height = y;
1527 egg_icon_list_set_adjustment_upper (icon_list->priv->hadjustment, icon_list->priv->width);
1528 egg_icon_list_set_adjustment_upper (icon_list->priv->vadjustment, icon_list->priv->height);
1530 if (GTK_WIDGET_REALIZED (icon_list))
1532 gdk_window_resize (icon_list->priv->bin_window,
1533 MAX (icon_list->priv->width, widget->allocation.width),
1534 MAX (icon_list->priv->height, widget->allocation.height));
1537 if (icon_list->priv->layout_idle_id != 0)
1539 g_source_remove (icon_list->priv->layout_idle_id);
1540 icon_list->priv->layout_idle_id = 0;
1543 gtk_widget_queue_draw (GTK_WIDGET (icon_list));
1546 /* Creates or updates the pango layout and calculates the size */
1548 egg_icon_list_calculate_item_size (EggIconList *icon_list, EggIconListItem *item)
1550 int layout_width, layout_height;
1551 int maximum_layout_width;
1553 if (item->width != -1 && item->width != -1)
1556 item->pixbuf_width = gdk_pixbuf_get_width (item->icon);
1557 item->pixbuf_height = gdk_pixbuf_get_height (item->icon);
1559 maximum_layout_width = MAX (item->pixbuf_width, MINIMUM_ICON_ITEM_WIDTH);
1561 pango_layout_set_text (icon_list->priv->layout, item->label, -1);
1563 pango_layout_set_alignment (icon_list->priv->layout, PANGO_ALIGN_CENTER);
1564 pango_layout_set_width (icon_list->priv->layout, maximum_layout_width * PANGO_SCALE);
1566 pango_layout_get_pixel_size (icon_list->priv->layout, &layout_width, &layout_height);
1568 item->width = MAX ((layout_width + 2 * ICON_TEXT_PADDING), item->pixbuf_width);
1569 item->height = layout_height + 2 * ICON_TEXT_PADDING + item->pixbuf_height;
1570 item->layout_width = layout_width;
1571 item->layout_height = layout_height;
1575 egg_icon_list_item_invalidate_size (EggIconListItem *item)
1582 create_colorized_pixbuf (GdkPixbuf *src, GdkColor *new_color)
1585 gint width, height, has_alpha, src_row_stride, dst_row_stride;
1586 gint red_value, green_value, blue_value;
1587 guchar *target_pixels;
1588 guchar *original_pixels;
1593 red_value = new_color->red / 255.0;
1594 green_value = new_color->green / 255.0;
1595 blue_value = new_color->blue / 255.0;
1597 dest = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (src),
1598 gdk_pixbuf_get_has_alpha (src),
1599 gdk_pixbuf_get_bits_per_sample (src),
1600 gdk_pixbuf_get_width (src),
1601 gdk_pixbuf_get_height (src));
1603 has_alpha = gdk_pixbuf_get_has_alpha (src);
1604 width = gdk_pixbuf_get_width (src);
1605 height = gdk_pixbuf_get_height (src);
1606 src_row_stride = gdk_pixbuf_get_rowstride (src);
1607 dst_row_stride = gdk_pixbuf_get_rowstride (dest);
1608 target_pixels = gdk_pixbuf_get_pixels (dest);
1609 original_pixels = gdk_pixbuf_get_pixels (src);
1611 for (i = 0; i < height; i++) {
1612 pixdest = target_pixels + i*dst_row_stride;
1613 pixsrc = original_pixels + i*src_row_stride;
1614 for (j = 0; j < width; j++) {
1615 *pixdest++ = (*pixsrc++ * red_value) >> 8;
1616 *pixdest++ = (*pixsrc++ * green_value) >> 8;
1617 *pixdest++ = (*pixsrc++ * blue_value) >> 8;
1619 *pixdest++ = *pixsrc++;
1627 egg_icon_list_paint_item (EggIconList *icon_list,
1628 EggIconListItem *item,
1634 if (GTK_WIDGET_HAS_FOCUS (icon_list))
1635 state = GTK_STATE_SELECTED;
1637 state = GTK_STATE_ACTIVE;
1640 pixbuf = create_colorized_pixbuf (item->icon,
1641 >K_WIDGET (icon_list)->style->base[state]);
1643 pixbuf = g_object_ref (item->icon);
1645 gdk_draw_pixbuf (icon_list->priv->bin_window, NULL, pixbuf,
1647 item->pixbuf_x, item->pixbuf_y,
1648 item->pixbuf_width, item->pixbuf_height,
1649 GDK_RGB_DITHER_NORMAL,
1650 item->pixbuf_width, item->pixbuf_height);
1651 g_object_unref (pixbuf);
1655 gdk_draw_rectangle (icon_list->priv->bin_window,
1656 GTK_WIDGET (icon_list)->style->base_gc[state],
1658 item->layout_x - ICON_TEXT_PADDING,
1659 item->layout_y - ICON_TEXT_PADDING,
1660 item->layout_width + 2 * ICON_TEXT_PADDING,
1661 item->layout_height + 2 * ICON_TEXT_PADDING);
1664 pango_layout_set_text (icon_list->priv->layout, item->label, -1);
1665 gdk_draw_layout (icon_list->priv->bin_window,
1666 GTK_WIDGET (icon_list)->style->text_gc[item->selected ? state : GTK_STATE_NORMAL],
1667 item->layout_x - ((item->width - item->layout_width) / 2) - (MAX (item->pixbuf_width, MINIMUM_ICON_ITEM_WIDTH) - item->width) / 2,
1669 icon_list->priv->layout);
1671 if (GTK_WIDGET_HAS_FOCUS (icon_list) &&
1672 item == icon_list->priv->cursor_item)
1673 gtk_paint_focus (GTK_WIDGET (icon_list)->style,
1674 icon_list->priv->bin_window,
1675 item->selected ? GTK_STATE_SELECTED : GTK_STATE_NORMAL,
1677 GTK_WIDGET (icon_list),
1679 item->layout_x - ICON_TEXT_PADDING,
1680 item->layout_y - ICON_TEXT_PADDING,
1681 item->layout_width + 2 * ICON_TEXT_PADDING,
1682 item->layout_height + 2 * ICON_TEXT_PADDING);
1686 egg_gdk_color_to_rgb (const GdkColor *color)
1689 result = (0xff0000 | (color->red & 0xff00));
1691 result |= ((color->green & 0xff00) | (color->blue >> 8));
1696 egg_icon_list_paint_rubberband (EggIconList *icon_list,
1702 GdkRectangle rubber_rect;
1703 GdkColor *fill_color_gdk;
1705 guchar fill_color_alpha;
1707 rubber_rect.x = MIN (icon_list->priv->rubberband_x1, icon_list->priv->rubberband_x2);
1708 rubber_rect.y = MIN (icon_list->priv->rubberband_y1, icon_list->priv->rubberband_y2);
1709 rubber_rect.width = ABS (icon_list->priv->rubberband_x1 - icon_list->priv->rubberband_x2) + 1;
1710 rubber_rect.height = ABS (icon_list->priv->rubberband_y1 - icon_list->priv->rubberband_y2) + 1;
1712 if (!gdk_rectangle_intersect (&rubber_rect, area, &rect))
1715 gtk_widget_style_get (GTK_WIDGET (icon_list),
1716 "selection_box_color", &fill_color_gdk,
1717 "selection_box_alpha", &fill_color_alpha,
1720 if (!fill_color_gdk) {
1721 fill_color_gdk = gdk_color_copy (>K_WIDGET (icon_list)->style->base[GTK_STATE_SELECTED]);
1724 fill_color = egg_gdk_color_to_rgb (fill_color_gdk) << 8 | fill_color_alpha;
1726 pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, rect.width, rect.height);
1727 gdk_pixbuf_fill (pixbuf, fill_color);
1729 gdk_draw_pixbuf (icon_list->priv->bin_window, NULL, pixbuf,
1732 rect.width, rect.height,
1733 GDK_RGB_DITHER_NONE,
1735 g_object_unref (pixbuf);
1736 gc = gdk_gc_new (icon_list->priv->bin_window);
1737 gdk_gc_set_rgb_fg_color (gc, fill_color_gdk);
1738 gdk_gc_set_clip_rectangle (gc, &rect);
1739 gdk_draw_rectangle (icon_list->priv->bin_window,
1741 rubber_rect.x, rubber_rect.y,
1742 rubber_rect.width - 1, rubber_rect.height - 1);
1743 gdk_color_free (fill_color_gdk);
1744 g_object_unref (gc);
1748 egg_icon_list_queue_draw_item (EggIconList *icon_list,
1749 EggIconListItem *item)
1755 rect.width = item->width;
1756 rect.height = item->height;
1758 gdk_window_invalidate_rect (icon_list->priv->bin_window, &rect, TRUE);
1762 layout_callback (gpointer user_data)
1764 EggIconList *icon_list;
1766 icon_list = EGG_ICON_LIST (user_data);
1768 icon_list->priv->layout_idle_id = 0;
1770 egg_icon_list_layout (icon_list);
1776 egg_icon_list_queue_layout (EggIconList *icon_list)
1778 if (icon_list->priv->layout_idle_id != 0)
1781 icon_list->priv->layout_idle_id = g_idle_add (layout_callback, icon_list);
1785 egg_icon_list_set_cursor_item (EggIconList *icon_list,
1786 EggIconListItem *item)
1788 if (icon_list->priv->cursor_item == item)
1791 if (icon_list->priv->cursor_item != NULL)
1792 egg_icon_list_queue_draw_item (icon_list, icon_list->priv->cursor_item);
1794 icon_list->priv->cursor_item = item;
1795 egg_icon_list_queue_draw_item (icon_list, item);
1799 egg_icon_list_append_typeahead_string (EggIconList *icon_list,
1800 const gchar *string)
1803 char *typeahead_string;
1805 if (strlen (string) == 0)
1808 for (i = 0; i < strlen (string); i++)
1810 if (!g_ascii_isprint (string[i]))
1814 typeahead_string = g_strconcat (icon_list->priv->typeahead_string ?
1815 icon_list->priv->typeahead_string : "",
1817 g_free (icon_list->priv->typeahead_string);
1818 icon_list->priv->typeahead_string = typeahead_string;
1820 egg_icon_list_select_first_matching_item (icon_list,
1821 icon_list->priv->typeahead_string);
1823 g_print ("wooo: \"%s\"\n", typeahead_string);
1828 egg_icon_list_new (void)
1830 EggIconList *icon_list;
1832 icon_list = g_object_new (EGG_TYPE_ICON_LIST, NULL);
1834 return GTK_WIDGET (icon_list);
1838 egg_icon_list_item_new (GdkPixbuf *icon,
1841 EggIconListItem *item;
1843 item = g_new0 (EggIconListItem, 1);
1845 item->ref_count = 1;
1848 item->label = g_strdup (label);
1849 item->icon = g_object_ref (icon);
1855 egg_icon_list_item_ref (EggIconListItem *item)
1857 g_return_if_fail (item != NULL);
1859 item->ref_count += 1;
1863 egg_icon_list_item_unref (EggIconListItem *item)
1865 g_return_if_fail (item != NULL);
1867 item->ref_count -= 1;
1869 if (item->ref_count == 0)
1871 if (item->destroy_notify)
1872 item->destroy_notify (item->user_data);
1874 g_free (item->label);
1875 g_object_unref (item->icon);
1882 egg_icon_list_item_set_data (EggIconListItem *item,
1885 egg_icon_list_item_set_data_full (item, data, NULL);
1889 egg_icon_list_item_set_data_full (EggIconListItem *item,
1891 GDestroyNotify destroy_notify)
1893 g_return_if_fail (item != NULL);
1895 if (item->destroy_notify)
1896 item->destroy_notify (item->user_data);
1898 item->destroy_notify = destroy_notify;
1899 item->user_data = data;
1903 egg_icon_list_item_get_data (EggIconListItem *item)
1905 g_return_val_if_fail (item != NULL, NULL);
1907 return item->user_data;
1911 egg_icon_list_item_set_label (EggIconListItem *item,
1914 g_return_if_fail (item != NULL);
1915 g_return_if_fail (label != NULL);
1917 if (strcmp (item->label, label) == 0)
1920 g_free (item->label);
1921 item->label = g_strdup (label);
1922 egg_icon_list_item_invalidate_size (item);
1924 egg_icon_list_queue_layout (item->icon_list);
1926 g_object_notify (G_OBJECT (item), "label");
1929 G_CONST_RETURN gchar *
1930 egg_icon_list_item_get_label (EggIconListItem *item)
1932 g_return_val_if_fail (item != NULL, NULL);
1938 egg_icon_list_item_set_icon (EggIconListItem *item,
1941 g_return_if_fail (item != NULL);
1943 if (icon == item->icon)
1946 g_object_unref (item->icon);
1947 item->icon = g_object_ref (icon);
1949 egg_icon_list_item_invalidate_size (item);
1951 egg_icon_list_queue_layout (item->icon_list);
1955 egg_icon_list_item_get_icon (EggIconListItem *item)
1957 g_return_val_if_fail (item != NULL, NULL);
1963 egg_icon_list_append_item (EggIconList *icon_list,
1964 EggIconListItem *item)
1968 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
1969 g_return_if_fail (item != NULL);
1970 g_return_if_fail (item->icon_list == NULL);
1972 if (icon_list->priv->sorted)
1974 egg_icon_list_insert_item_sorted (icon_list, item);
1978 egg_icon_list_validate (icon_list);
1980 list = g_list_alloc ();
1982 item->icon_list = icon_list;
1984 egg_icon_list_item_ref (item);
1986 if (icon_list->priv->last_item)
1988 icon_list->priv->last_item->next = list;
1989 list->prev = icon_list->priv->last_item;
1992 icon_list->priv->items = list;
1994 icon_list->priv->last_item = list;
1995 icon_list->priv->item_count += 1;
1997 egg_icon_list_validate (icon_list);
1999 g_signal_emit (icon_list, icon_list_signals[ITEM_ADDED], 0, item);
2001 egg_icon_list_queue_layout (icon_list);
2005 egg_icon_list_prepend_item (EggIconList *icon_list,
2006 EggIconListItem *item)
2010 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2011 g_return_if_fail (item != NULL);
2012 g_return_if_fail (item->icon_list == NULL);
2014 egg_icon_list_validate (icon_list);
2016 list = g_list_alloc ();
2018 item->icon_list = icon_list;
2020 egg_icon_list_item_ref (item);
2022 if (icon_list->priv->last_item == NULL)
2023 icon_list->priv->last_item = list;
2025 if (icon_list->priv->items)
2026 icon_list->priv->items->prev = list;
2028 list->next = icon_list->priv->items;
2029 icon_list->priv->items = list;
2030 icon_list->priv->item_count += 1;
2032 egg_icon_list_validate (icon_list);
2034 g_signal_emit (icon_list, icon_list_signals[ITEM_ADDED], 0, item);
2036 egg_icon_list_queue_layout (icon_list);
2042 egg_icon_list_insert_item_before (EggIconList *icon_list,
2043 EggIconListItem *sibling,
2044 EggIconListItem *item)
2048 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2049 g_return_if_fail (item != NULL);
2050 g_return_if_fail (item->icon_list == NULL);
2052 if (icon_list->priv->sorted)
2054 egg_icon_list_insert_item_sorted (icon_list, item);
2058 if (sibling == NULL)
2059 egg_icon_list_append_item (icon_list, item);
2061 egg_icon_list_validate (icon_list);
2063 list = g_list_alloc ();
2065 item->icon_list = icon_list;
2067 egg_icon_list_item_ref (item);
2069 list->prev = sibling->list->prev;
2070 list->next = sibling->list;
2071 sibling->list->prev->next = list;
2072 sibling->list->prev = list;
2074 if (sibling->list == icon_list->priv->items)
2075 icon_list->priv->items = list;
2077 icon_list->priv->item_count += 1;
2078 egg_icon_list_validate (icon_list);
2080 g_signal_emit (icon_list, icon_list_signals[ITEM_ADDED], 0, item);
2082 egg_icon_list_queue_layout (icon_list);
2086 egg_icon_list_insert_item_after (EggIconList *icon_list,
2087 EggIconListItem *sibling,
2088 EggIconListItem *item)
2092 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2093 g_return_if_fail (item != NULL);
2094 g_return_if_fail (item->icon_list == NULL);
2096 if (icon_list->priv->sorted)
2098 egg_icon_list_insert_item_sorted (icon_list, item);
2102 if (sibling == NULL)
2104 egg_icon_list_prepend_item (icon_list, item);
2108 egg_icon_list_validate (icon_list);
2110 list = g_list_alloc ();
2112 item->icon_list = icon_list;
2114 egg_icon_list_item_ref (item);
2116 list->next = sibling->list->next;
2117 list->prev = sibling->list;
2118 sibling->list->next->prev = list;
2119 sibling->list->next = list;
2121 if (sibling->list == icon_list->priv->last_item)
2122 icon_list->priv->last_item = list;
2124 icon_list->priv->item_count += 1;
2125 egg_icon_list_validate (icon_list);
2126 g_signal_emit (icon_list, icon_list_signals[ITEM_ADDED], 0, item);
2128 egg_icon_list_queue_layout (icon_list);
2132 egg_icon_list_remove_item (EggIconList *icon_list,
2133 EggIconListItem *item)
2135 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2136 g_return_if_fail (item != NULL);
2137 g_return_if_fail (item->icon_list == icon_list);
2139 egg_icon_list_validate (icon_list);
2141 if (item->list->prev)
2142 item->list->prev->next = item->list->next;
2143 if (item->list->next)
2144 item->list->next->prev = item->list->prev;
2146 if (item->list == icon_list->priv->items)
2147 icon_list->priv->items = item->list->next;
2148 if (item->list == icon_list->priv->last_item)
2149 icon_list->priv->last_item = item->list->prev;
2151 g_list_free_1 (item->list);
2153 item->icon_list = NULL;
2154 egg_icon_list_item_invalidate_size (item);
2156 icon_list->priv->item_count -= 1;
2157 egg_icon_list_validate (icon_list);
2159 g_signal_emit (icon_list, icon_list_signals[ITEM_REMOVED], 0, item);
2163 item->selected = FALSE;
2165 g_signal_emit (icon_list, icon_list_signals[SELECTION_CHANGED], 0);
2169 if (icon_list->priv->cursor_item == item)
2170 g_error ("FIXME: Move to first focused item");
2173 if (icon_list->priv->last_single_clicked == item)
2174 icon_list->priv->last_single_clicked = NULL;
2176 egg_icon_list_item_unref (item);
2178 egg_icon_list_queue_layout (icon_list);
2182 egg_icon_list_clear (EggIconList *icon_list)
2186 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2188 items = g_list_copy (icon_list->priv->items);
2192 EggIconListItem *item = items->data;
2194 egg_icon_list_remove_item (icon_list, item);
2195 items = items->next;
2202 egg_icon_list_get_item_at_pos (EggIconList *icon_list,
2208 g_return_val_if_fail (EGG_IS_ICON_LIST (icon_list), NULL);
2210 for (items = icon_list->priv->items; items; items = items->next)
2212 EggIconListItem *item = items->data;
2214 if (x > item->x && x < item->x + item->width &&
2215 y > item->y && y < item->y + item->height)
2217 gint layout_x = item->x + (item->width - item->layout_width) / 2;
2218 /* Check if the mouse is inside the icon or the label */
2219 if ((x > item->pixbuf_x && x < item->pixbuf_x + item->pixbuf_width &&
2220 y > item->pixbuf_y && y < item->pixbuf_y + item->pixbuf_height) ||
2221 (x > layout_x - ICON_TEXT_PADDING &&
2222 x < layout_x + item->layout_width + ICON_TEXT_PADDING * 2 &&
2223 y > item->layout_y - ICON_TEXT_PADDING
2224 && y < item->layout_y + item->layout_height + ICON_TEXT_PADDING * 2))
2233 egg_icon_list_get_item_count (EggIconList *icon_list)
2235 g_return_val_if_fail (EGG_IS_ICON_LIST (icon_list), 0);
2237 return icon_list->priv->item_count;
2241 egg_icon_list_foreach (EggIconList *icon_list,
2242 EggIconListForeachFunc func,
2247 for (list = icon_list->priv->items; list; list = list->next)
2248 (* func) (icon_list, list->data, data);
2252 egg_icon_list_selected_foreach (EggIconList *icon_list,
2253 EggIconListForeachFunc func,
2258 for (list = icon_list->priv->items; list; list = list->next)
2260 EggIconListItem *item = list->data;
2263 (* func) (icon_list, list->data, data);
2268 egg_icon_list_get_selected (EggIconList *icon_list)
2270 GList *list, *selected = NULL;
2272 g_return_val_if_fail (EGG_IS_ICON_LIST (icon_list), NULL);
2274 for (list = icon_list->priv->items; list; list = list->next)
2276 EggIconListItem *item = list->data;
2279 selected = g_list_prepend (selected, item);
2282 return g_list_reverse (selected);
2286 egg_icon_list_set_selection_mode (EggIconList *icon_list,
2287 GtkSelectionMode mode)
2289 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2291 if (mode == icon_list->priv->selection_mode)
2294 if (mode == GTK_SELECTION_NONE ||
2295 icon_list->priv->selection_mode == GTK_SELECTION_MULTIPLE)
2296 egg_icon_list_unselect_all (icon_list);
2298 icon_list->priv->selection_mode = mode;
2300 g_object_notify (G_OBJECT (icon_list), "selection_mode");
2304 egg_icon_list_get_selection_mode (EggIconList *icon_list)
2306 g_return_val_if_fail (EGG_IS_ICON_LIST (icon_list), GTK_SELECTION_SINGLE);
2308 return icon_list->priv->selection_mode;
2312 egg_icon_list_select_item (EggIconList *icon_list,
2313 EggIconListItem *item)
2315 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2316 g_return_if_fail (item != NULL);
2321 if (icon_list->priv->selection_mode == GTK_SELECTION_NONE)
2323 else if (icon_list->priv->selection_mode != GTK_SELECTION_MULTIPLE)
2324 egg_icon_list_unselect_all_internal (icon_list, FALSE);
2326 item->selected = TRUE;
2328 g_signal_emit (icon_list, icon_list_signals[SELECTION_CHANGED], 0);
2330 egg_icon_list_queue_draw_item (icon_list, item);
2335 egg_icon_list_unselect_item (EggIconList *icon_list,
2336 EggIconListItem *item)
2338 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2339 g_return_if_fail (item != NULL);
2341 if (!item->selected)
2344 if (icon_list->priv->selection_mode == GTK_SELECTION_NONE ||
2345 icon_list->priv->selection_mode == GTK_SELECTION_BROWSE)
2348 item->selected = FALSE;
2350 g_signal_emit (icon_list, icon_list_signals[SELECTION_CHANGED], 0);
2352 egg_icon_list_queue_draw_item (icon_list, item);
2356 egg_icon_list_item_is_selected (EggIconListItem *item)
2358 g_return_val_if_fail (item != NULL, FALSE);
2360 return item->selected;
2364 egg_icon_list_unselect_all (EggIconList *icon_list)
2366 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2368 egg_icon_list_unselect_all_internal (icon_list, TRUE);
2372 egg_icon_list_select_all (EggIconList *icon_list)
2375 gboolean dirty = FALSE;
2377 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2379 for (items = icon_list->priv->items; items; items = items->next)
2381 EggIconListItem *item = items->data;
2383 if (!item->selected)
2386 item->selected = TRUE;
2387 egg_icon_list_queue_draw_item (icon_list, item);
2392 g_signal_emit (icon_list, icon_list_signals[SELECTION_CHANGED], 0);
2396 egg_icon_list_set_sorted (EggIconList *icon_list,
2399 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2400 g_return_if_fail (icon_list->priv->sort_func != NULL);
2402 if (icon_list->priv->sorted == sorted)
2405 icon_list->priv->sorted = sorted;
2406 g_object_notify (G_OBJECT (icon_list), "sorted");
2408 if (icon_list->priv->sorted)
2409 egg_icon_list_sort (icon_list);
2413 egg_icon_list_get_sorted (EggIconList *icon_list)
2415 g_return_val_if_fail (EGG_IS_ICON_LIST (icon_list), FALSE);
2417 return icon_list->priv->sorted;
2421 egg_icon_list_set_sort_func (EggIconList *icon_list,
2422 EggIconListItemCompareFunc func,
2424 GDestroyNotify destroy_notify)
2426 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2427 g_return_if_fail (func != NULL);
2429 if (icon_list->priv->sort_destroy_notify &&
2430 icon_list->priv->sort_data)
2431 (* icon_list->priv->sort_destroy_notify) (icon_list->priv->sort_data);
2433 icon_list->priv->sort_func = func;
2434 icon_list->priv->sort_data = data;
2435 icon_list->priv->sort_destroy_notify = destroy_notify;
2439 egg_icon_list_set_sort_order (EggIconList *icon_list,
2442 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2444 if (icon_list->priv->sort_order == order)
2447 icon_list->priv->sort_order = order;
2449 if (icon_list->priv->sorted)
2450 egg_icon_list_sort (icon_list);
2452 g_object_notify (G_OBJECT (icon_list), "sort_order");
2456 egg_icon_list_get_sort_order (EggIconList *icon_list)
2458 g_return_val_if_fail (EGG_IS_ICON_LIST (icon_list), GTK_SORT_ASCENDING);
2460 return icon_list->priv->sort_order;
2464 egg_icon_list_item_activated (EggIconList *icon_list,
2465 EggIconListItem *item)
2467 g_signal_emit (G_OBJECT (icon_list), icon_list_signals[ITEM_ACTIVATED], 0, item);
2471 egg_icon_list_get_items (EggIconList *icon_list)
2473 g_return_val_if_fail (EGG_IS_ICON_LIST (icon_list), NULL);
2475 return icon_list->priv->items;
2479 egg_icon_list_item_get_icon_list (EggIconListItem *item)
2481 g_return_val_if_fail (item != NULL, NULL);
2483 return item->icon_list;