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_pixbuf_render_to_drawable_alpha (pixbuf,
1646 icon_list->priv->bin_window,
1651 item->pixbuf_height,
1652 GDK_PIXBUF_ALPHA_FULL,
1654 GDK_RGB_DITHER_NORMAL,
1656 item->pixbuf_height);
1657 g_object_unref (pixbuf);
1661 gdk_draw_rectangle (icon_list->priv->bin_window,
1662 GTK_WIDGET (icon_list)->style->base_gc[state],
1664 item->layout_x - ICON_TEXT_PADDING,
1665 item->layout_y - ICON_TEXT_PADDING,
1666 item->layout_width + 2 * ICON_TEXT_PADDING,
1667 item->layout_height + 2 * ICON_TEXT_PADDING);
1670 pango_layout_set_text (icon_list->priv->layout, item->label, -1);
1671 gdk_draw_layout (icon_list->priv->bin_window,
1672 GTK_WIDGET (icon_list)->style->text_gc[item->selected ? state : GTK_STATE_NORMAL],
1673 item->layout_x - ((item->width - item->layout_width) / 2) - (MAX (item->pixbuf_width, MINIMUM_ICON_ITEM_WIDTH) - item->width) / 2,
1675 icon_list->priv->layout);
1677 if (GTK_WIDGET_HAS_FOCUS (icon_list) &&
1678 item == icon_list->priv->cursor_item)
1679 gtk_paint_focus (GTK_WIDGET (icon_list)->style,
1680 icon_list->priv->bin_window,
1681 item->selected ? GTK_STATE_SELECTED : GTK_STATE_NORMAL,
1683 GTK_WIDGET (icon_list),
1685 item->layout_x - ICON_TEXT_PADDING,
1686 item->layout_y - ICON_TEXT_PADDING,
1687 item->layout_width + 2 * ICON_TEXT_PADDING,
1688 item->layout_height + 2 * ICON_TEXT_PADDING);
1692 egg_gdk_color_to_rgb (const GdkColor *color)
1695 result = (0xff0000 | (color->red & 0xff00));
1697 result |= ((color->green & 0xff00) | (color->blue >> 8));
1702 egg_icon_list_paint_rubberband (EggIconList *icon_list,
1708 GdkRectangle rubber_rect;
1709 GdkColor *fill_color_gdk;
1711 guchar fill_color_alpha;
1713 rubber_rect.x = MIN (icon_list->priv->rubberband_x1, icon_list->priv->rubberband_x2);
1714 rubber_rect.y = MIN (icon_list->priv->rubberband_y1, icon_list->priv->rubberband_y2);
1715 rubber_rect.width = ABS (icon_list->priv->rubberband_x1 - icon_list->priv->rubberband_x2) + 1;
1716 rubber_rect.height = ABS (icon_list->priv->rubberband_y1 - icon_list->priv->rubberband_y2) + 1;
1718 if (!gdk_rectangle_intersect (&rubber_rect, area, &rect))
1721 gtk_widget_style_get (GTK_WIDGET (icon_list),
1722 "selection_box_color", &fill_color_gdk,
1723 "selection_box_alpha", &fill_color_alpha,
1726 if (!fill_color_gdk) {
1727 fill_color_gdk = gdk_color_copy (>K_WIDGET (icon_list)->style->base[GTK_STATE_SELECTED]);
1730 fill_color = egg_gdk_color_to_rgb (fill_color_gdk) << 8 | fill_color_alpha;
1732 pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, rect.width, rect.height);
1733 gdk_pixbuf_fill (pixbuf, fill_color);
1735 gdk_pixbuf_render_to_drawable_alpha (pixbuf,
1736 icon_list->priv->bin_window,
1739 rect.width, rect.height,
1740 GDK_PIXBUF_ALPHA_FULL,
1742 GDK_RGB_DITHER_NONE,
1744 g_object_unref (pixbuf);
1745 gc = gdk_gc_new (icon_list->priv->bin_window);
1746 gdk_gc_set_rgb_fg_color (gc, fill_color_gdk);
1747 gdk_gc_set_clip_rectangle (gc, &rect);
1748 gdk_draw_rectangle (icon_list->priv->bin_window,
1750 rubber_rect.x, rubber_rect.y,
1751 rubber_rect.width - 1, rubber_rect.height - 1);
1752 gdk_color_free (fill_color_gdk);
1753 g_object_unref (gc);
1757 egg_icon_list_queue_draw_item (EggIconList *icon_list,
1758 EggIconListItem *item)
1764 rect.width = item->width;
1765 rect.height = item->height;
1767 gdk_window_invalidate_rect (icon_list->priv->bin_window, &rect, TRUE);
1771 layout_callback (gpointer user_data)
1773 EggIconList *icon_list;
1775 icon_list = EGG_ICON_LIST (user_data);
1777 icon_list->priv->layout_idle_id = 0;
1779 egg_icon_list_layout (icon_list);
1785 egg_icon_list_queue_layout (EggIconList *icon_list)
1787 if (icon_list->priv->layout_idle_id != 0)
1790 icon_list->priv->layout_idle_id = g_idle_add (layout_callback, icon_list);
1794 egg_icon_list_set_cursor_item (EggIconList *icon_list,
1795 EggIconListItem *item)
1797 if (icon_list->priv->cursor_item == item)
1800 if (icon_list->priv->cursor_item != NULL)
1801 egg_icon_list_queue_draw_item (icon_list, icon_list->priv->cursor_item);
1803 icon_list->priv->cursor_item = item;
1804 egg_icon_list_queue_draw_item (icon_list, item);
1808 egg_icon_list_append_typeahead_string (EggIconList *icon_list,
1809 const gchar *string)
1812 char *typeahead_string;
1814 if (strlen (string) == 0)
1817 for (i = 0; i < strlen (string); i++)
1819 if (!g_ascii_isprint (string[i]))
1823 typeahead_string = g_strconcat (icon_list->priv->typeahead_string ?
1824 icon_list->priv->typeahead_string : "",
1826 g_free (icon_list->priv->typeahead_string);
1827 icon_list->priv->typeahead_string = typeahead_string;
1829 egg_icon_list_select_first_matching_item (icon_list,
1830 icon_list->priv->typeahead_string);
1832 g_print ("wooo: \"%s\"\n", typeahead_string);
1837 egg_icon_list_new (void)
1839 EggIconList *icon_list;
1841 icon_list = g_object_new (EGG_TYPE_ICON_LIST, NULL);
1843 return GTK_WIDGET (icon_list);
1847 egg_icon_list_item_new (GdkPixbuf *icon,
1850 EggIconListItem *item;
1852 item = g_new0 (EggIconListItem, 1);
1854 item->ref_count = 1;
1857 item->label = g_strdup (label);
1858 item->icon = g_object_ref (icon);
1864 egg_icon_list_item_ref (EggIconListItem *item)
1866 g_return_if_fail (item != NULL);
1868 item->ref_count += 1;
1872 egg_icon_list_item_unref (EggIconListItem *item)
1874 g_return_if_fail (item != NULL);
1876 item->ref_count -= 1;
1878 if (item->ref_count == 0)
1880 g_free (item->label);
1881 g_object_unref (item->icon);
1888 egg_icon_list_item_set_data (EggIconListItem *item,
1891 egg_icon_list_item_set_data_full (item, data, NULL);
1895 egg_icon_list_item_set_data_full (EggIconListItem *item,
1897 GDestroyNotify destroy_notify)
1899 g_return_if_fail (item != NULL);
1901 if (item->destroy_notify)
1902 item->destroy_notify (item->user_data);
1904 item->destroy_notify = destroy_notify;
1905 item->user_data = data;
1909 egg_icon_list_item_get_data (EggIconListItem *item)
1911 g_return_val_if_fail (item != NULL, NULL);
1913 return item->user_data;
1917 egg_icon_list_item_set_label (EggIconListItem *item,
1920 g_return_if_fail (item != NULL);
1921 g_return_if_fail (label != NULL);
1923 if (strcmp (item->label, label) == 0)
1926 g_free (item->label);
1927 item->label = g_strdup (label);
1928 egg_icon_list_item_invalidate_size (item);
1930 egg_icon_list_queue_layout (item->icon_list);
1932 g_object_notify (G_OBJECT (item), "label");
1935 G_CONST_RETURN gchar *
1936 egg_icon_list_item_get_label (EggIconListItem *item)
1938 g_return_val_if_fail (item != NULL, NULL);
1944 egg_icon_list_item_set_icon (EggIconListItem *item,
1947 g_return_if_fail (item != NULL);
1949 if (icon == item->icon)
1952 g_object_unref (item->icon);
1953 item->icon = g_object_ref (icon);
1955 egg_icon_list_item_invalidate_size (item);
1957 egg_icon_list_queue_layout (item->icon_list);
1961 egg_icon_list_item_get_icon (EggIconListItem *item)
1963 g_return_val_if_fail (item != NULL, NULL);
1969 egg_icon_list_append_item (EggIconList *icon_list,
1970 EggIconListItem *item)
1974 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
1975 g_return_if_fail (item != NULL);
1976 g_return_if_fail (item->icon_list == NULL);
1978 if (icon_list->priv->sorted)
1980 egg_icon_list_insert_item_sorted (icon_list, item);
1984 egg_icon_list_validate (icon_list);
1986 list = g_list_alloc ();
1988 item->icon_list = icon_list;
1990 egg_icon_list_item_ref (item);
1992 if (icon_list->priv->last_item)
1994 icon_list->priv->last_item->next = list;
1995 list->prev = icon_list->priv->last_item;
1998 icon_list->priv->items = list;
2000 icon_list->priv->last_item = list;
2001 icon_list->priv->item_count += 1;
2003 egg_icon_list_validate (icon_list);
2005 g_signal_emit (icon_list, icon_list_signals[ITEM_ADDED], 0, item);
2007 egg_icon_list_queue_layout (icon_list);
2011 egg_icon_list_prepend_item (EggIconList *icon_list,
2012 EggIconListItem *item)
2016 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2017 g_return_if_fail (item != NULL);
2018 g_return_if_fail (item->icon_list == NULL);
2020 egg_icon_list_validate (icon_list);
2022 list = g_list_alloc ();
2024 item->icon_list = icon_list;
2026 egg_icon_list_item_ref (item);
2028 if (icon_list->priv->last_item == NULL)
2029 icon_list->priv->last_item = list;
2031 if (icon_list->priv->items)
2032 icon_list->priv->items->prev = list;
2034 list->next = icon_list->priv->items;
2035 icon_list->priv->items = list;
2036 icon_list->priv->item_count += 1;
2038 egg_icon_list_validate (icon_list);
2040 g_signal_emit (icon_list, icon_list_signals[ITEM_ADDED], 0, item);
2042 egg_icon_list_queue_layout (icon_list);
2048 egg_icon_list_insert_item_before (EggIconList *icon_list,
2049 EggIconListItem *sibling,
2050 EggIconListItem *item)
2054 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2055 g_return_if_fail (item != NULL);
2056 g_return_if_fail (item->icon_list == NULL);
2058 if (icon_list->priv->sorted)
2060 egg_icon_list_insert_item_sorted (icon_list, item);
2064 if (sibling == NULL)
2065 egg_icon_list_append_item (icon_list, item);
2067 egg_icon_list_validate (icon_list);
2069 list = g_list_alloc ();
2071 item->icon_list = icon_list;
2073 egg_icon_list_item_ref (item);
2075 list->prev = sibling->list->prev;
2076 list->next = sibling->list;
2077 sibling->list->prev->next = list;
2078 sibling->list->prev = list;
2080 if (sibling->list == icon_list->priv->items)
2081 icon_list->priv->items = list;
2083 icon_list->priv->item_count += 1;
2084 egg_icon_list_validate (icon_list);
2086 g_signal_emit (icon_list, icon_list_signals[ITEM_ADDED], 0, item);
2088 egg_icon_list_queue_layout (icon_list);
2092 egg_icon_list_insert_item_after (EggIconList *icon_list,
2093 EggIconListItem *sibling,
2094 EggIconListItem *item)
2098 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2099 g_return_if_fail (item != NULL);
2100 g_return_if_fail (item->icon_list == NULL);
2102 if (icon_list->priv->sorted)
2104 egg_icon_list_insert_item_sorted (icon_list, item);
2108 if (sibling == NULL)
2110 egg_icon_list_prepend_item (icon_list, item);
2114 egg_icon_list_validate (icon_list);
2116 list = g_list_alloc ();
2118 item->icon_list = icon_list;
2120 egg_icon_list_item_ref (item);
2122 list->next = sibling->list->next;
2123 list->prev = sibling->list;
2124 sibling->list->next->prev = list;
2125 sibling->list->next = list;
2127 if (sibling->list == icon_list->priv->last_item)
2128 icon_list->priv->last_item = list;
2130 icon_list->priv->item_count += 1;
2131 egg_icon_list_validate (icon_list);
2132 g_signal_emit (icon_list, icon_list_signals[ITEM_ADDED], 0, item);
2134 egg_icon_list_queue_layout (icon_list);
2138 egg_icon_list_remove_item (EggIconList *icon_list,
2139 EggIconListItem *item)
2141 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2142 g_return_if_fail (item != NULL);
2143 g_return_if_fail (item->icon_list == icon_list);
2145 egg_icon_list_validate (icon_list);
2147 if (item->list->prev)
2148 item->list->prev->next = item->list->next;
2149 if (item->list->next)
2150 item->list->next->prev = item->list->prev;
2152 if (item->list == icon_list->priv->items)
2153 icon_list->priv->items = item->list->next;
2154 if (item->list == icon_list->priv->last_item)
2155 icon_list->priv->last_item = item->list->prev;
2157 g_list_free_1 (item->list);
2159 item->icon_list = NULL;
2160 egg_icon_list_item_invalidate_size (item);
2162 icon_list->priv->item_count -= 1;
2163 egg_icon_list_validate (icon_list);
2165 g_signal_emit (icon_list, icon_list_signals[ITEM_REMOVED], 0, item);
2169 item->selected = FALSE;
2171 g_signal_emit (icon_list, icon_list_signals[SELECTION_CHANGED], 0);
2175 if (icon_list->priv->cursor_item == item)
2176 g_error ("FIXME: Move to first focused item");
2179 if (icon_list->priv->last_single_clicked == item)
2180 icon_list->priv->last_single_clicked = NULL;
2182 egg_icon_list_item_unref (item);
2184 egg_icon_list_queue_layout (icon_list);
2188 egg_icon_list_clear (EggIconList *icon_list)
2192 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2194 items = g_list_copy (icon_list->priv->items);
2198 EggIconListItem *item = items->data;
2200 egg_icon_list_remove_item (icon_list, item);
2201 items = items->next;
2208 egg_icon_list_get_item_at_pos (EggIconList *icon_list,
2214 g_return_val_if_fail (EGG_IS_ICON_LIST (icon_list), NULL);
2216 for (items = icon_list->priv->items; items; items = items->next)
2218 EggIconListItem *item = items->data;
2220 if (x > item->x && x < item->x + item->width &&
2221 y > item->y && y < item->y + item->height)
2223 gint layout_x = item->x + (item->width - item->layout_width) / 2;
2224 /* Check if the mouse is inside the icon or the label */
2225 if ((x > item->pixbuf_x && x < item->pixbuf_x + item->pixbuf_width &&
2226 y > item->pixbuf_y && y < item->pixbuf_y + item->pixbuf_height) ||
2227 (x > layout_x - ICON_TEXT_PADDING &&
2228 x < layout_x + item->layout_width + ICON_TEXT_PADDING * 2 &&
2229 y > item->layout_y - ICON_TEXT_PADDING
2230 && y < item->layout_y + item->layout_height + ICON_TEXT_PADDING * 2))
2239 egg_icon_list_get_item_count (EggIconList *icon_list)
2241 g_return_val_if_fail (EGG_IS_ICON_LIST (icon_list), 0);
2243 return icon_list->priv->item_count;
2247 egg_icon_list_foreach (EggIconList *icon_list,
2248 EggIconListForeachFunc func,
2253 for (list = icon_list->priv->items; list; list = list->next)
2254 (* func) (icon_list, list->data, data);
2258 egg_icon_list_selected_foreach (EggIconList *icon_list,
2259 EggIconListForeachFunc func,
2264 for (list = icon_list->priv->items; list; list = list->next)
2266 EggIconListItem *item = list->data;
2269 (* func) (icon_list, list->data, data);
2274 egg_icon_list_get_selected (EggIconList *icon_list)
2276 GList *list, *selected = NULL;
2278 g_return_val_if_fail (EGG_IS_ICON_LIST (icon_list), NULL);
2280 for (list = icon_list->priv->items; list; list = list->next)
2282 EggIconListItem *item = list->data;
2285 selected = g_list_prepend (selected, item);
2288 return g_list_reverse (selected);
2292 egg_icon_list_set_selection_mode (EggIconList *icon_list,
2293 GtkSelectionMode mode)
2295 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2297 if (mode == icon_list->priv->selection_mode)
2300 if (mode == GTK_SELECTION_NONE ||
2301 icon_list->priv->selection_mode == GTK_SELECTION_MULTIPLE)
2302 egg_icon_list_unselect_all (icon_list);
2304 icon_list->priv->selection_mode = mode;
2306 g_object_notify (G_OBJECT (icon_list), "selection_mode");
2310 egg_icon_list_get_selection_mode (EggIconList *icon_list)
2312 g_return_val_if_fail (EGG_IS_ICON_LIST (icon_list), GTK_SELECTION_SINGLE);
2314 return icon_list->priv->selection_mode;
2318 egg_icon_list_select_item (EggIconList *icon_list,
2319 EggIconListItem *item)
2321 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2322 g_return_if_fail (item != NULL);
2327 if (icon_list->priv->selection_mode == GTK_SELECTION_NONE)
2329 else if (icon_list->priv->selection_mode != GTK_SELECTION_MULTIPLE)
2330 egg_icon_list_unselect_all_internal (icon_list, FALSE);
2332 item->selected = TRUE;
2334 g_signal_emit (icon_list, icon_list_signals[SELECTION_CHANGED], 0);
2336 egg_icon_list_queue_draw_item (icon_list, item);
2341 egg_icon_list_unselect_item (EggIconList *icon_list,
2342 EggIconListItem *item)
2344 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2345 g_return_if_fail (item != NULL);
2347 if (!item->selected)
2350 if (icon_list->priv->selection_mode == GTK_SELECTION_NONE ||
2351 icon_list->priv->selection_mode == GTK_SELECTION_BROWSE)
2354 item->selected = FALSE;
2356 g_signal_emit (icon_list, icon_list_signals[SELECTION_CHANGED], 0);
2358 egg_icon_list_queue_draw_item (icon_list, item);
2362 egg_icon_list_item_is_selected (EggIconListItem *item)
2364 g_return_val_if_fail (item != NULL, FALSE);
2366 return item->selected;
2370 egg_icon_list_unselect_all (EggIconList *icon_list)
2372 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2374 egg_icon_list_unselect_all_internal (icon_list, TRUE);
2378 egg_icon_list_select_all (EggIconList *icon_list)
2381 gboolean dirty = FALSE;
2383 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2385 for (items = icon_list->priv->items; items; items = items->next)
2387 EggIconListItem *item = items->data;
2389 if (!item->selected)
2392 item->selected = TRUE;
2393 egg_icon_list_queue_draw_item (icon_list, item);
2398 g_signal_emit (icon_list, icon_list_signals[SELECTION_CHANGED], 0);
2402 egg_icon_list_set_sorted (EggIconList *icon_list,
2405 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2406 g_return_if_fail (icon_list->priv->sort_func != NULL);
2408 if (icon_list->priv->sorted == sorted)
2411 icon_list->priv->sorted = sorted;
2412 g_object_notify (G_OBJECT (icon_list), "sorted");
2414 if (icon_list->priv->sorted)
2415 egg_icon_list_sort (icon_list);
2419 egg_icon_list_get_sorted (EggIconList *icon_list)
2421 g_return_val_if_fail (EGG_IS_ICON_LIST (icon_list), FALSE);
2423 return icon_list->priv->sorted;
2427 egg_icon_list_set_sort_func (EggIconList *icon_list,
2428 EggIconListItemCompareFunc func,
2430 GDestroyNotify destroy_notify)
2432 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2433 g_return_if_fail (func != NULL);
2435 if (icon_list->priv->sort_destroy_notify &&
2436 icon_list->priv->sort_data)
2437 (* icon_list->priv->sort_destroy_notify) (icon_list->priv->sort_data);
2439 icon_list->priv->sort_func = func;
2440 icon_list->priv->sort_data = data;
2441 icon_list->priv->sort_destroy_notify = destroy_notify;
2445 egg_icon_list_set_sort_order (EggIconList *icon_list,
2448 g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2450 if (icon_list->priv->sort_order == order)
2453 icon_list->priv->sort_order = order;
2455 if (icon_list->priv->sorted)
2456 egg_icon_list_sort (icon_list);
2458 g_object_notify (G_OBJECT (icon_list), "sort_order");
2462 egg_icon_list_get_sort_order (EggIconList *icon_list)
2464 g_return_val_if_fail (EGG_IS_ICON_LIST (icon_list), GTK_SORT_ASCENDING);
2466 return icon_list->priv->sort_order;
2470 egg_icon_list_item_activated (EggIconList *icon_list,
2471 EggIconListItem *item)
2473 g_signal_emit (G_OBJECT (icon_list), icon_list_signals[ITEM_ACTIVATED], 0, item);
2477 egg_icon_list_get_items (EggIconList *icon_list)
2479 g_return_val_if_fail (EGG_IS_ICON_LIST (icon_list), NULL);
2481 return icon_list->priv->items;
2485 egg_icon_list_item_get_icon_list (EggIconListItem *item)
2487 g_return_val_if_fail (item != NULL, NULL);
2489 return item->icon_list;