r2232: Sort type is shared between Icon and List views. Sort order can be reversed.
[rox-filer.git] / ROX-Filer / src / collection.c
blob7f155d29c0a5108354cee882d02437172db667ac
1 /*
2 * $Id$
4 * Collection - a GTK+ widget
5 * Copyright (C) 2002, the ROX-Filer team.
7 * The collection widget provides an area for displaying a collection of
8 * objects (such as files). It allows the user to choose a selection of
9 * them and provides signals to allow popping up menus, detecting
10 * double-clicks etc.
12 * This program is free software; you can redistribute it and/or modify it
13 * under the terms of the GNU General Public License as published by the Free
14 * Software Foundation; either version 2 of the License, or (at your option)
15 * any later version.
17 * This program is distributed in the hope that it will be useful, but WITHOUT
18 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
20 * more details.
22 * You should have received a copy of the GNU General Public License along with
23 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
24 * Place, Suite 330, Boston, MA 02111-1307 USA
27 #include "config.h"
29 #include <stdlib.h>
31 #include <gtk/gtk.h>
32 #include <gdk/gdkkeysyms.h>
33 #include "global.h"
35 #include "collection.h"
37 #define MIN_WIDTH 80
38 #define MIN_HEIGHT 60
39 #define MINIMUM_ITEMS 16
40 #define AUTOSCROLL_STEP 20
42 #define MAX_WINKS 3 /* Should be an odd number */
44 /* Macro to emit the "selection_changed" signal only if allowed */
45 #define EMIT_SELECTION_CHANGED(collection, time) \
46 if (!collection->block_selection_changed) \
47 g_signal_emit(collection, \
48 collection_signals[SELECTION_CHANGED], 0, time)
50 enum
52 PROP_0,
53 PROP_VADJUSTMENT
56 /* Signals:
58 * void gain_selection(collection, time, user_data)
59 * We've gone from no selected items to having a selection.
60 * Time is the time of the event that caused the change, or
61 * GDK_CURRENT_TIME if not known.
63 * void lose_selection(collection, time, user_data)
64 * We've dropped to having no selected items.
65 * Time is the time of the event that caused the change, or
66 * GDK_CURRENT_TIME if not known.
68 * void selection_changed(collection, user_data)
69 * The set of selected items has changed.
70 * Time is the time of the event that caused the change, or
71 * GDK_CURRENT_TIME if not known.
73 enum
75 GAIN_SELECTION,
76 LOSE_SELECTION,
77 SELECTION_CHANGED,
78 LAST_SIGNAL
81 static guint collection_signals[LAST_SIGNAL] = { 0 };
83 static guint32 current_event_time = GDK_CURRENT_TIME;
85 static GtkWidgetClass *parent_class = NULL;
87 /* Static prototypes */
88 static void draw_one_item(Collection *collection,
89 int item,
90 GdkRectangle *area);
91 static void collection_class_init(GObjectClass *gclass, gpointer data);
92 static void collection_init(GTypeInstance *object, gpointer g_class);
93 static void collection_destroy(GtkObject *object);
94 static void collection_finalize(GObject *object);
95 static void collection_realize(GtkWidget *widget);
96 static void collection_map(GtkWidget *widget);
97 static void collection_size_request(GtkWidget *widget,
98 GtkRequisition *requisition);
99 static void collection_size_allocate(GtkWidget *widget,
100 GtkAllocation *allocation);
101 static void collection_set_adjustment(Collection *collection,
102 GtkAdjustment *vadj);
103 static void collection_get_property(GObject *object,
104 guint prop_id,
105 GValue *value,
106 GParamSpec *pspec);
107 static void collection_set_property(GObject *object,
108 guint prop_id,
109 const GValue *value,
110 GParamSpec *pspec);
111 static gint collection_expose(GtkWidget *widget, GdkEventExpose *event);
112 static void default_draw_item(GtkWidget *widget,
113 CollectionItem *data,
114 GdkRectangle *area,
115 gpointer user_data);
116 static gboolean default_test_point(Collection *collection,
117 int point_x, int point_y,
118 CollectionItem *data,
119 int width, int height,
120 gpointer user_data);
121 static gint collection_motion_notify(GtkWidget *widget,
122 GdkEventMotion *event);
123 static void add_lasso_box(Collection *collection);
124 static void abort_lasso(Collection *collection);
125 static void remove_lasso_box(Collection *collection);
126 static void draw_lasso_box(Collection *collection);
127 static void cancel_wink(Collection *collection);
128 static gint collection_key_press(GtkWidget *widget, GdkEventKey *event);
129 static void get_visible_limits(Collection *collection, int *first, int *last);
130 static void scroll_to_show(Collection *collection, int item);
131 static void collection_item_set_selected(Collection *collection,
132 gint item,
133 gboolean selected,
134 gboolean signal);
135 static void collection_drag_leave(GtkWidget *widget, GdkDragContext *context,
136 guint time);
137 static void collection_drag_end(GtkWidget *widget, GdkDragContext *context);
138 static gboolean collection_drag_motion(GtkWidget *widget, GdkDragContext
139 *context, gint x, gint y, guint time);
140 static gint collection_scroll_event(GtkWidget *widget, GdkEventScroll *event);
142 static void draw_focus_at(Collection *collection, GdkRectangle *area)
144 GtkWidget *widget;
145 GtkStateType state;
147 widget = GTK_WIDGET(collection);
149 if (GTK_WIDGET_FLAGS(widget) & GTK_HAS_FOCUS)
150 state = GTK_STATE_ACTIVE;
151 else
152 state = GTK_STATE_INSENSITIVE;
154 gtk_paint_focus(widget->style,
155 widget->window,
156 state,
157 NULL,
158 widget,
159 "collection",
160 area->x, area->y,
161 collection->item_width,
162 area->height);
165 static void draw_one_item(Collection *collection, int item, GdkRectangle *area)
167 if (item < collection->number_of_items)
169 collection->draw_item((GtkWidget *) collection,
170 &collection->items[item],
171 area, collection->cb_user_data);
174 if (item == collection->cursor_item)
175 draw_focus_at(collection, area);
178 GType collection_get_type(void)
180 static GType my_type = 0;
182 if (!my_type)
184 static const GTypeInfo info =
186 sizeof(CollectionClass),
187 NULL, /* base_init */
188 NULL, /* base_finalise */
189 (GClassInitFunc) collection_class_init,
190 NULL, /* class_finalise */
191 NULL, /* class_data */
192 sizeof(Collection),
193 0, /* n_preallocs */
194 collection_init
197 my_type = g_type_register_static(gtk_widget_get_type(),
198 "Collection", &info, 0);
201 return my_type;
204 typedef void (*FinalizeFn)(GObject *object);
206 static void collection_class_init(GObjectClass *gclass, gpointer data)
208 CollectionClass *collection_class = (CollectionClass *) gclass;
209 GtkObjectClass *object_class = (GtkObjectClass *) gclass;
210 GtkWidgetClass *widget_class = (GtkWidgetClass *) gclass;
212 parent_class = gtk_type_class(gtk_widget_get_type());
214 object_class->destroy = collection_destroy;
215 G_OBJECT_CLASS(object_class)->finalize =
216 (FinalizeFn) collection_finalize;
218 widget_class->realize = collection_realize;
219 widget_class->expose_event = collection_expose;
220 widget_class->size_request = collection_size_request;
221 widget_class->size_allocate = collection_size_allocate;
223 widget_class->key_press_event = collection_key_press;
225 widget_class->motion_notify_event = collection_motion_notify;
226 widget_class->map = collection_map;
227 widget_class->scroll_event = collection_scroll_event;
228 widget_class->drag_motion = collection_drag_motion;
229 widget_class->drag_leave = collection_drag_leave;
230 widget_class->drag_end = collection_drag_end;
232 gclass->set_property = collection_set_property;
233 gclass->get_property = collection_get_property;
235 collection_class->gain_selection = NULL;
236 collection_class->lose_selection = NULL;
237 collection_class->selection_changed = NULL;
239 collection_signals[GAIN_SELECTION] = g_signal_new("gain_selection",
240 G_TYPE_FROM_CLASS(gclass),
241 G_SIGNAL_RUN_LAST,
242 G_STRUCT_OFFSET(CollectionClass,
243 gain_selection),
244 NULL, NULL,
245 g_cclosure_marshal_VOID__INT,
246 G_TYPE_NONE, 1,
247 G_TYPE_INT);
249 collection_signals[LOSE_SELECTION] = g_signal_new("lose_selection",
250 G_TYPE_FROM_CLASS(gclass),
251 G_SIGNAL_RUN_LAST,
252 G_STRUCT_OFFSET(CollectionClass,
253 lose_selection),
254 NULL, NULL,
255 g_cclosure_marshal_VOID__INT,
256 G_TYPE_NONE, 1,
257 G_TYPE_INT);
259 collection_signals[SELECTION_CHANGED] = g_signal_new(
260 "selection_changed",
261 G_TYPE_FROM_CLASS(gclass),
262 G_SIGNAL_RUN_LAST,
263 G_STRUCT_OFFSET(CollectionClass,
264 selection_changed),
265 NULL, NULL,
266 g_cclosure_marshal_VOID__INT,
267 G_TYPE_NONE, 1,
268 G_TYPE_INT);
270 g_object_class_install_property(gclass,
271 PROP_VADJUSTMENT,
272 g_param_spec_object("vadjustment",
273 _("Vertical Adjustment"),
274 _("The GtkAdjustment for the vertical position."),
275 GTK_TYPE_ADJUSTMENT,
276 G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
279 static void collection_init(GTypeInstance *instance, gpointer g_class)
281 Collection *object = (Collection *) instance;
283 g_return_if_fail(object != NULL);
284 g_return_if_fail(IS_COLLECTION(object));
286 GTK_WIDGET_SET_FLAGS(GTK_WIDGET(object), GTK_CAN_FOCUS);
288 object->number_of_items = 0;
289 object->number_selected = 0;
290 object->block_selection_changed = 0;
291 object->columns = 1;
292 object->item_width = 64;
293 object->item_height = 64;
294 object->vadj = NULL;
296 object->items = g_new(CollectionItem, MINIMUM_ITEMS);
297 object->cursor_item = -1;
298 object->cursor_item_old = -1;
299 object->wink_item = -1;
300 object->wink_on_map = -1;
301 object->array_size = MINIMUM_ITEMS;
302 object->draw_item = default_draw_item;
303 object->test_point = default_test_point;
304 object->free_item = NULL;
306 object->auto_scroll = -1;
309 GtkWidget* collection_new(void)
311 return GTK_WIDGET(gtk_widget_new(collection_get_type(), NULL));
314 /* After this we are unusable, but our data (if any) is still hanging around.
315 * It will be freed later with finalize.
317 static void collection_destroy(GtkObject *object)
319 Collection *collection;
321 g_return_if_fail(object != NULL);
322 g_return_if_fail(IS_COLLECTION(object));
324 collection = COLLECTION(object);
326 collection_clear(collection);
328 if (collection->auto_scroll != -1)
330 gtk_timeout_remove(collection->auto_scroll);
331 collection->auto_scroll = -1;
334 if (collection->vadj)
336 g_object_unref(G_OBJECT(collection->vadj));
337 collection->vadj = NULL;
340 if (GTK_OBJECT_CLASS(parent_class)->destroy)
341 (*GTK_OBJECT_CLASS(parent_class)->destroy)(object);
344 /* This is the last thing that happens to us. Free all data. */
345 static void collection_finalize(GObject *object)
347 Collection *collection;
349 collection = COLLECTION(object);
351 g_return_if_fail(collection->number_of_items == 0);
353 g_free(collection->items);
355 if (G_OBJECT_CLASS(parent_class)->finalize)
356 G_OBJECT_CLASS(parent_class)->finalize(object);
359 static void collection_drag_leave(GtkWidget *widget, GdkDragContext *context,
360 guint time)
362 Collection *collection = COLLECTION(widget);
364 /* Note that this isn't always called when the pointer leaves the
365 * widget; only if we highlighted an item at some point.
368 /* collection_set_autoscroll(collection, FALSE); - not needed? */
369 collection_set_cursor_item(collection, -1);
371 if (GTK_WIDGET_CLASS(parent_class)->drag_leave)
372 (*GTK_WIDGET_CLASS(parent_class)->drag_leave)(widget, context,
373 time);
376 /* Turn on auto scrolling */
377 static gboolean collection_drag_motion(GtkWidget *widget, GdkDragContext
378 *context, gint x, gint y, guint time)
380 Collection *collection = COLLECTION(widget);
382 collection_set_autoscroll(collection, TRUE);
384 if (GTK_WIDGET_CLASS(parent_class)->drag_motion)
385 return (*GTK_WIDGET_CLASS(parent_class)->drag_motion)(widget,
386 context, x, y, time);
387 return FALSE;
390 /* Turn off auto scrolling.
391 * (do we need this AND drag_leave?)
393 static void collection_drag_end(GtkWidget *widget, GdkDragContext *context)
395 Collection *collection = COLLECTION(widget);
397 collection_set_autoscroll(collection, FALSE);
399 if (GTK_WIDGET_CLASS(parent_class)->drag_end)
400 (*GTK_WIDGET_CLASS(parent_class)->drag_end)(widget, context);
403 static void collection_map(GtkWidget *widget)
405 Collection *collection = COLLECTION(widget);
407 if (GTK_WIDGET_CLASS(parent_class)->map)
408 (*GTK_WIDGET_CLASS(parent_class)->map)(widget);
410 if (collection->wink_on_map >= 0)
412 collection_wink_item(collection, collection->wink_on_map);
413 collection->wink_on_map = -1;
417 static void collection_realize(GtkWidget *widget)
419 Collection *collection;
420 GdkWindowAttr attributes;
421 gint attributes_mask;
422 GdkGCValues xor_values;
423 GdkColor *bg, *fg;
425 g_return_if_fail(widget != NULL);
426 g_return_if_fail(IS_COLLECTION(widget));
427 g_return_if_fail(widget->parent != NULL);
429 GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);
430 collection = COLLECTION(widget);
432 attributes.x = widget->allocation.x;
433 attributes.y = widget->allocation.y;
434 attributes.width = widget->allocation.width;
435 attributes.height = widget->allocation.height;
436 attributes.wclass = GDK_INPUT_OUTPUT;
437 attributes.window_type = GDK_WINDOW_CHILD;
438 attributes.event_mask = gtk_widget_get_events(widget) |
439 GDK_EXPOSURE_MASK |
440 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
441 GDK_BUTTON1_MOTION_MASK | GDK_BUTTON2_MOTION_MASK |
442 GDK_BUTTON3_MOTION_MASK;
443 attributes.visual = gtk_widget_get_visual(widget);
444 attributes.colormap = gtk_widget_get_colormap(widget);
446 attributes_mask = GDK_WA_X | GDK_WA_Y |
447 GDK_WA_VISUAL | GDK_WA_COLORMAP;
448 widget->window = gdk_window_new(gtk_widget_get_parent_window(widget),
449 &attributes, attributes_mask);
451 widget->style = gtk_style_attach(widget->style, widget->window);
453 gdk_window_set_user_data(widget->window, widget);
454 gdk_window_set_background(widget->window,
455 &widget->style->bg[GTK_STATE_NORMAL]);
457 bg = &widget->style->bg[GTK_STATE_NORMAL];
458 fg = &widget->style->fg[GTK_STATE_NORMAL];
459 xor_values.function = GDK_XOR;
460 xor_values.foreground.pixel = fg->pixel ^ bg->pixel;
461 collection->xor_gc = gdk_gc_new_with_values(widget->window,
462 &xor_values,
463 GDK_GC_FOREGROUND
464 | GDK_GC_FUNCTION);
467 static void collection_size_request(GtkWidget *widget,
468 GtkRequisition *requisition)
470 Collection *collection = COLLECTION(widget);
471 int rows, cols = collection->columns;
473 /* We ask for the total size we need; our containing viewport
474 * will deal with scrolling.
476 requisition->width = MIN_WIDTH;
477 rows = (collection->number_of_items + cols - 1) / cols;
478 requisition->height = rows * collection->item_height;
481 static gboolean scroll_after_alloc(Collection *collection)
483 if (collection->wink_item != -1)
484 scroll_to_show(collection, collection->wink_item);
485 else if (collection->cursor_item != -1)
486 scroll_to_show(collection, collection->cursor_item);
487 g_object_unref(G_OBJECT(collection));
489 return FALSE;
492 static void collection_size_allocate(GtkWidget *widget,
493 GtkAllocation *allocation)
495 Collection *collection;
496 int old_columns;
497 gboolean cursor_visible = FALSE;
499 g_return_if_fail(widget != NULL);
500 g_return_if_fail(IS_COLLECTION(widget));
501 g_return_if_fail(allocation != NULL);
503 collection = COLLECTION(widget);
505 if (collection->cursor_item != -1)
507 int first, last;
508 int crow = collection->cursor_item / collection->columns;
510 get_visible_limits(collection, &first, &last);
512 cursor_visible = crow >= first && crow <= last;
515 old_columns = collection->columns;
517 widget->allocation = *allocation;
519 collection->columns = allocation->width / collection->item_width;
520 if (collection->columns < 1)
521 collection->columns = 1;
523 if (GTK_WIDGET_REALIZED(widget))
525 gdk_window_move_resize(widget->window,
526 allocation->x, allocation->y,
527 allocation->width, allocation->height);
529 if (cursor_visible)
530 scroll_to_show(collection, collection->cursor_item);
533 if (old_columns != collection->columns)
535 /* Need to go around again... */
536 gtk_widget_queue_resize(widget);
538 else if (collection->wink_item != -1 || collection->cursor_item != -1)
540 /* Viewport resets the adjustments after the alloc */
541 g_object_ref(G_OBJECT(collection));
542 g_idle_add((GSourceFunc) scroll_after_alloc, collection);
546 /* Return the area occupied by the item at (row, col) by filling
547 * in 'area'.
549 static void collection_get_item_area(Collection *collection,
550 int row, int col,
551 GdkRectangle *area)
554 area->x = col * collection->item_width;
555 area->y = row * collection->item_height;
557 area->width = collection->item_width;
558 area->height = collection->item_height;
559 if (col == collection->columns - 1)
560 area->width <<= 1;
563 static gint collection_expose(GtkWidget *widget, GdkEventExpose *event)
565 Collection *collection;
566 GdkRectangle item_area;
567 int row, col;
568 int item;
569 int start_row, last_row;
570 int start_col, last_col;
571 int phys_last_col;
573 g_return_val_if_fail(widget != NULL, FALSE);
574 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
575 g_return_val_if_fail(event != NULL, FALSE);
577 gtk_paint_flat_box(widget->style, widget->window, GTK_STATE_NORMAL,
578 GTK_SHADOW_NONE, &event->area,
579 widget, "base", 0, 0, -1, -1);
581 collection = COLLECTION(widget);
583 /* Calculate the ranges to plot */
584 start_row = event->area.y / collection->item_height;
585 last_row = (event->area.y + event->area.height - 1)
586 / collection->item_height;
587 row = start_row;
589 start_col = event->area.x / collection->item_width;
590 phys_last_col = (event->area.x + event->area.width - 1)
591 / collection->item_width;
593 /* The right-most column may be wider than the others.
594 * Therefore, to redraw the area after the last 'real' column
595 * we may have to draw the right-most column.
597 if (start_col >= collection->columns)
598 start_col = collection->columns - 1;
600 if (phys_last_col >= collection->columns)
601 last_col = collection->columns - 1;
602 else
603 last_col = phys_last_col;
605 col = start_col;
607 item = row * collection->columns + col;
609 while ((item == 0 || item < collection->number_of_items)
610 && row <= last_row)
612 collection_get_item_area(collection, row, col, &item_area);
614 draw_one_item(collection, item, &item_area);
615 col++;
617 if (col > last_col)
619 col = start_col;
620 row++;
621 item = row * collection->columns + col;
623 else
624 item++;
627 if (collection->lasso_box)
628 draw_lasso_box(collection);
630 return FALSE;
633 static void default_draw_item(GtkWidget *widget,
634 CollectionItem *item,
635 GdkRectangle *area,
636 gpointer user_data)
638 gdk_draw_arc(widget->window,
639 item->selected ? widget->style->white_gc
640 : widget->style->black_gc,
641 TRUE,
642 area->x, area->y,
643 COLLECTION(widget)->item_width, area->height,
644 0, 360 * 64);
648 static gboolean default_test_point(Collection *collection,
649 int point_x, int point_y,
650 CollectionItem *item,
651 int width, int height,
652 gpointer user_data)
654 float f_x, f_y;
656 /* Convert to point in unit circle */
657 f_x = ((float) point_x / width) - 0.5;
658 f_y = ((float) point_y / height) - 0.5;
660 return (f_x * f_x) + (f_y * f_y) <= .25;
663 static void collection_set_property(GObject *object,
664 guint prop_id,
665 const GValue *value,
666 GParamSpec *pspec)
668 Collection *collection;
670 collection = COLLECTION(object);
672 switch (prop_id)
674 case PROP_VADJUSTMENT:
675 collection_set_adjustment(collection,
676 g_value_get_object(value));
677 break;
678 default:
679 G_OBJECT_WARN_INVALID_PROPERTY_ID(object,
680 prop_id, pspec);
681 break;
685 static void collection_set_adjustment(Collection *collection,
686 GtkAdjustment *vadj)
688 if (vadj)
689 g_return_if_fail(GTK_IS_ADJUSTMENT(vadj));
690 else
691 vadj = GTK_ADJUSTMENT(gtk_adjustment_new(0.0,
692 0.0, 0.0,
693 0.0, 0.0, 0.0));
695 if (collection->vadj == vadj)
696 return;
698 if (collection->vadj)
699 g_object_unref(G_OBJECT(collection->vadj));
701 collection->vadj = vadj;
702 g_object_ref(G_OBJECT(collection->vadj));
703 gtk_object_sink(GTK_OBJECT(collection->vadj));
706 static void collection_get_property(GObject *object,
707 guint prop_id,
708 GValue *value,
709 GParamSpec *pspec)
711 Collection *collection;
713 collection = COLLECTION(object);
715 switch (prop_id)
717 case PROP_VADJUSTMENT:
718 g_value_set_object(value, G_OBJECT(collection->vadj));
719 break;
720 default:
721 G_OBJECT_WARN_INVALID_PROPERTY_ID(object,
722 prop_id, pspec);
723 break;
727 /* Change the adjustment by this amount. Bounded. */
728 static void diff_vpos(Collection *collection, int diff)
730 int value = collection->vadj->value + diff;
732 value = CLAMP(value, 0,
733 collection->vadj->upper - collection->vadj->page_size);
734 gtk_adjustment_set_value(collection->vadj, value);
737 static void resize_arrays(Collection *collection, guint new_size)
739 g_return_if_fail(collection != NULL);
740 g_return_if_fail(IS_COLLECTION(collection));
741 g_return_if_fail(new_size >= collection->number_of_items);
743 collection->items = g_realloc(collection->items,
744 sizeof(CollectionItem) * new_size);
745 collection->array_size = new_size;
748 static gint collection_key_press(GtkWidget *widget, GdkEventKey *event)
750 Collection *collection;
751 int item;
752 int key;
754 g_return_val_if_fail(widget != NULL, FALSE);
755 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
756 g_return_val_if_fail(event != NULL, FALSE);
758 collection = (Collection *) widget;
759 item = collection->cursor_item;
761 key = event->keyval;
762 if (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK))
764 if (key == GDK_Left || key == GDK_Right || \
765 key == GDK_Up || key == GDK_Down)
766 return TRUE;
767 return FALSE;
770 switch (key)
772 case GDK_Left:
773 collection_move_cursor(collection, 0, -1);
774 break;
775 case GDK_Right:
776 collection_move_cursor(collection, 0, 1);
777 break;
778 case GDK_Up:
779 collection_move_cursor(collection, -1, 0);
780 break;
781 case GDK_Down:
782 collection_move_cursor(collection, 1, 0);
783 break;
784 case GDK_Home:
785 collection_set_cursor_item(collection, 0);
786 break;
787 case GDK_End:
788 collection_set_cursor_item(collection,
789 MAX((gint) collection->number_of_items - 1, 0));
790 break;
791 case GDK_Page_Up:
793 int first, last;
794 get_visible_limits(collection, &first, &last);
795 collection_move_cursor(collection, first - last - 1, 0);
796 break;
798 case GDK_Page_Down:
800 int first, last;
801 get_visible_limits(collection, &first, &last);
802 collection_move_cursor(collection, last - first + 1, 0);
803 break;
805 default:
806 return FALSE;
809 return TRUE;
812 /* Wheel mouse scrolling */
813 static gint collection_scroll_event(GtkWidget *widget, GdkEventScroll *event)
815 Collection *collection;
816 int diff = 0;
818 g_return_val_if_fail(widget != NULL, FALSE);
819 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
820 g_return_val_if_fail(event != NULL, FALSE);
822 collection = COLLECTION(widget);
824 if (event->direction == GDK_SCROLL_UP)
825 diff = -1;
826 else if (event->direction == GDK_SCROLL_DOWN)
827 diff = 1;
828 else
829 return FALSE;
831 if (diff)
833 int old_value = collection->vadj->value;
834 int new_value = 0;
835 gboolean box = collection->lasso_box;
836 int step = collection->vadj->page_increment / 2;
838 new_value = CLAMP(old_value + diff * step, 0.0,
839 collection->vadj->upper
840 - collection->vadj->page_size);
841 diff = new_value - old_value;
842 if (diff)
844 if (box)
846 remove_lasso_box(collection);
847 collection->drag_box_y[0] -= diff;
849 gtk_adjustment_set_value(collection->vadj, new_value);
850 if (box)
851 add_lasso_box(collection);
855 return TRUE;
858 /* 'from' and 'to' are pixel positions. 'step' is the size of each item.
859 * Returns the index of the first item covered, and the number of items.
861 static void get_range(int from, int to, int step, gint *pos, gint *len)
863 int margin = MIN(step / 4, 40);
865 if (from > to)
867 int tmp = to;
868 to = from;
869 from = tmp;
872 from = (from + margin) / step; /* First item */
873 to = (to + step - margin) / step; /* Last item (inclusive) */
875 *pos = MAX(from, 0);
876 *len = to - *pos;
879 /* Fills in the area with a rectangle corresponding to the current
880 * size of the lasso box (units of items, not pixels).
882 * The box will only span valid columns, but the total number
883 * of items is not taken into account (rows or cols).
885 static void find_lasso_area(Collection *collection, GdkRectangle *area)
887 int cols = collection->columns;
888 int dx = collection->drag_box_x[0] - collection->drag_box_x[1];
889 int dy = collection->drag_box_y[0] - collection->drag_box_y[1];
891 if (ABS(dx) < 8 && ABS(dy) < 8)
893 /* Didn't move far enough - ignore */
894 area->x = area->y = 0;
895 area->width = 0;
896 area->height = 0;
897 return;
900 get_range(collection->drag_box_x[0], collection->drag_box_x[1],
901 collection->item_width, &area->x, &area->width);
903 if (area->x >= cols)
904 area->width = 0;
905 else if (area->x + area->width > cols)
906 area->width = cols - area->x;
908 get_range(collection->drag_box_y[0], collection->drag_box_y[1],
909 collection->item_height, &area->y, &area->height);
912 static void collection_process_area(Collection *collection,
913 GdkRectangle *area,
914 GdkFunction fn,
915 guint32 time)
917 int x, y;
918 guint32 stacked_time;
919 int item;
920 gboolean changed = FALSE;
921 guint old_selected;
923 g_return_if_fail(fn == GDK_SET || fn == GDK_INVERT);
925 old_selected = collection->number_selected;
927 stacked_time = current_event_time;
928 current_event_time = time;
930 collection->block_selection_changed++;
932 for (y = area->y; y < area->y + area->height; y++)
934 item = y * collection->columns + area->x;
936 for (x = area->x; x < area->x + area->width; x++)
938 if (item >= collection->number_of_items)
939 goto out;
941 if (fn == GDK_INVERT)
942 collection_item_set_selected(collection, item,
943 !collection->items[item].selected,
944 FALSE);
945 else
946 collection_item_set_selected(collection, item,
947 TRUE, FALSE);
949 changed = TRUE;
950 item++;
954 out:
955 if (collection->number_selected && !old_selected)
956 g_signal_emit(collection,
957 collection_signals[GAIN_SELECTION], 0,
958 current_event_time);
959 else if (!collection->number_selected && old_selected)
960 g_signal_emit(collection,
961 collection_signals[LOSE_SELECTION], 0,
962 current_event_time);
964 collection_unblock_selection_changed(collection,
965 current_event_time, changed);
966 current_event_time = stacked_time;
969 static gint collection_motion_notify(GtkWidget *widget,
970 GdkEventMotion *event)
972 Collection *collection;
973 gint x, y;
975 g_return_val_if_fail(widget != NULL, FALSE);
976 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
977 g_return_val_if_fail(event != NULL, FALSE);
979 collection = COLLECTION(widget);
981 if (!collection->lasso_box)
982 return FALSE;
984 if (event->window != widget->window)
985 gdk_window_get_pointer(widget->window, &x, &y, NULL);
986 else
988 x = event->x;
989 y = event->y;
992 remove_lasso_box(collection);
993 collection->drag_box_x[1] = x;
994 collection->drag_box_y[1] = y;
995 add_lasso_box(collection);
996 return TRUE;
999 static void add_lasso_box(Collection *collection)
1001 g_return_if_fail(collection != NULL);
1002 g_return_if_fail(IS_COLLECTION(collection));
1003 g_return_if_fail(collection->lasso_box == FALSE);
1005 collection->lasso_box = TRUE;
1006 draw_lasso_box(collection);
1009 static void draw_lasso_box(Collection *collection)
1011 GtkWidget *widget;
1012 int x, y, width, height;
1014 widget = GTK_WIDGET(collection);
1016 x = MIN(collection->drag_box_x[0], collection->drag_box_x[1]);
1017 y = MIN(collection->drag_box_y[0], collection->drag_box_y[1]);
1018 width = abs(collection->drag_box_x[1] - collection->drag_box_x[0]);
1019 height = abs(collection->drag_box_y[1] - collection->drag_box_y[0]);
1021 /* XXX: A redraw bug sometimes leaves a one-pixel dot on the screen.
1022 * As a quick hack, don't draw boxes that small for now...
1024 if (width || height)
1025 gdk_draw_rectangle(widget->window, collection->xor_gc, FALSE,
1026 x, y, width, height);
1029 static void abort_lasso(Collection *collection)
1031 if (collection->lasso_box)
1033 remove_lasso_box(collection);
1034 collection_set_autoscroll(collection, FALSE);
1038 static void remove_lasso_box(Collection *collection)
1040 g_return_if_fail(collection != NULL);
1041 g_return_if_fail(IS_COLLECTION(collection));
1042 g_return_if_fail(collection->lasso_box == TRUE);
1044 draw_lasso_box(collection);
1046 collection->lasso_box = FALSE;
1048 return;
1051 /* Make sure that 'item' is fully visible (vertically), scrolling if not. */
1052 static void scroll_to_show(Collection *collection, int item)
1054 int first, last, row;
1056 g_return_if_fail(collection != NULL);
1057 g_return_if_fail(IS_COLLECTION(collection));
1059 row = item / collection->columns;
1060 get_visible_limits(collection, &first, &last);
1062 if (row <= first)
1064 gtk_adjustment_set_value(collection->vadj,
1065 row * collection->item_height);
1067 else if (row >= last)
1069 GtkWidget *widget = (GtkWidget *) collection;
1070 gint height;
1072 if (GTK_WIDGET_REALIZED(widget))
1074 height = collection->vadj->page_size;
1075 gtk_adjustment_set_value(collection->vadj,
1076 (row + 1) * collection->item_height - height);
1081 /* Return the first and last rows which are [partly] visible. Does not
1082 * ensure that the rows actually exist (contain items).
1084 static void get_visible_limits(Collection *collection, int *first, int *last)
1086 GtkWidget *widget = (GtkWidget *) collection;
1087 gint scroll = 0, height;
1089 g_return_if_fail(collection != NULL);
1090 g_return_if_fail(IS_COLLECTION(collection));
1091 g_return_if_fail(first != NULL && last != NULL);
1093 if (!GTK_WIDGET_REALIZED(widget))
1095 *first = 0;
1096 *last = 0;
1098 else
1100 scroll = collection->vadj->value;
1101 height = collection->vadj->page_size;
1103 *first = MAX(scroll / collection->item_height, 0);
1104 *last = (scroll + height - 1) /collection->item_height;
1106 if (*last < *first)
1107 *last = *first;
1111 /* Cancel the current wink effect. */
1112 static void cancel_wink(Collection *collection)
1114 gint item;
1116 g_return_if_fail(collection != NULL);
1117 g_return_if_fail(IS_COLLECTION(collection));
1118 g_return_if_fail(collection->wink_item != -1);
1120 item = collection->wink_item;
1122 collection->wink_item = -1;
1123 gtk_timeout_remove(collection->wink_timeout);
1125 collection_draw_item(collection, item, TRUE);
1128 /* Draw/undraw a box around collection->wink_item */
1129 static void invert_wink(Collection *collection)
1131 GdkRectangle area;
1132 gint row, col;
1134 g_return_if_fail(collection->wink_item >= 0);
1136 if (!GTK_WIDGET_REALIZED(GTK_WIDGET(collection)))
1137 return;
1139 col = collection->wink_item % collection->columns;
1140 row = collection->wink_item / collection->columns;
1141 collection_get_item_area(collection, row, col, &area);
1143 gdk_draw_rectangle(((GtkWidget *) collection)->window,
1144 collection->xor_gc, FALSE,
1145 area.x, area.y,
1146 collection->item_width - 1,
1147 area.height - 1);
1150 static gboolean wink_timeout(Collection *collection)
1152 gint item;
1154 g_return_val_if_fail(collection != NULL, FALSE);
1155 g_return_val_if_fail(IS_COLLECTION(collection), FALSE);
1156 g_return_val_if_fail(collection->wink_item != -1, FALSE);
1158 item = collection->wink_item;
1160 if (collection->winks_left-- > 0)
1162 invert_wink(collection);
1163 return TRUE;
1166 collection->wink_item = -1;
1168 collection_draw_item(collection, item, TRUE);
1170 return FALSE;
1173 /* This is called frequently while auto_scroll is on.
1174 * Checks the pointer position and scrolls the window if it's
1175 * near the top or bottom.
1177 static gboolean as_timeout(Collection *collection)
1179 GdkWindow *window = GTK_WIDGET(collection)->window;
1180 gint x, y, w, h;
1181 GdkModifierType mask;
1182 int diff = 0;
1184 gdk_window_get_pointer(window, &x, &y, &mask);
1185 gdk_drawable_get_size(window, &w, NULL);
1187 h = collection->vadj->page_size;
1188 y -= collection->vadj->value;
1190 if ((x < 0 || x > w || y < 0 || y > h) && !collection->lasso_box)
1192 collection->auto_scroll = -1;
1193 return FALSE; /* Out of window - stop */
1196 if (y < AUTOSCROLL_STEP)
1197 diff = y - AUTOSCROLL_STEP;
1198 else if (y > h - AUTOSCROLL_STEP)
1199 diff = AUTOSCROLL_STEP + y - h;
1201 if (diff)
1202 diff_vpos(collection, diff);
1204 return TRUE;
1207 /* Change the selected state of an item.
1208 * Send GAIN/LOSE signals if 'signal' is TRUE.
1209 * Send SELECTION_CHANGED unless blocked.
1210 * Updates number_selected and redraws the item.
1212 static void collection_item_set_selected(Collection *collection,
1213 gint item,
1214 gboolean selected,
1215 gboolean signal)
1217 g_return_if_fail(collection != NULL);
1218 g_return_if_fail(IS_COLLECTION(collection));
1219 g_return_if_fail(item >= 0 && item < collection->number_of_items);
1221 if (collection->items[item].selected == selected)
1222 return;
1224 collection->items[item].selected = selected;
1225 collection_draw_item(collection, item, TRUE);
1227 if (selected)
1229 collection->number_selected++;
1230 if (signal && collection->number_selected == 1)
1231 g_signal_emit(collection,
1232 collection_signals[GAIN_SELECTION], 0,
1233 current_event_time);
1235 else
1237 collection->number_selected--;
1238 if (signal && collection->number_selected == 0)
1239 g_signal_emit(collection,
1240 collection_signals[LOSE_SELECTION], 0,
1241 current_event_time);
1244 EMIT_SELECTION_CHANGED(collection, current_event_time);
1247 /* Functions for managing collections */
1249 /* Remove all objects from the collection */
1250 void collection_clear(Collection *collection)
1252 collection_delete_if(collection, NULL, NULL);
1255 /* Inserts a new item at the end. The new item is unselected, and its
1256 * number is returned.
1258 gint collection_insert(Collection *collection, gpointer data, gpointer view)
1260 int item;
1262 /* g_return_val_if_fail(IS_COLLECTION(collection), -1); (slow) */
1264 item = collection->number_of_items;
1266 if (item >= collection->array_size)
1267 resize_arrays(collection, item + (item >> 1));
1269 collection->items[item].data = data;
1270 collection->items[item].view_data = view;
1271 collection->items[item].selected = FALSE;
1273 collection->number_of_items++;
1275 gtk_widget_queue_resize(GTK_WIDGET(collection));
1277 collection_draw_item(collection, item, FALSE);
1279 return item;
1282 void collection_unselect_item(Collection *collection, gint item)
1284 collection_item_set_selected(collection, item, FALSE, TRUE);
1287 void collection_select_item(Collection *collection, gint item)
1289 collection_item_set_selected(collection, item, TRUE, TRUE);
1292 void collection_toggle_item(Collection *collection, gint item)
1294 collection_item_set_selected(collection, item,
1295 !collection->items[item].selected, TRUE);
1298 /* Select all items in the collection */
1299 void collection_select_all(Collection *collection)
1301 GtkWidget *widget;
1302 int item = 0;
1304 g_return_if_fail(collection != NULL);
1305 g_return_if_fail(IS_COLLECTION(collection));
1307 widget = GTK_WIDGET(collection);
1309 if (collection->number_selected == collection->number_of_items)
1310 return; /* Nothing to do */
1312 while (collection->number_selected < collection->number_of_items)
1314 while (collection->items[item].selected)
1315 item++;
1317 collection->items[item].selected = TRUE;
1318 collection_draw_item(collection, item, TRUE);
1319 item++;
1321 collection->number_selected++;
1324 g_signal_emit(collection, collection_signals[GAIN_SELECTION], 0,
1325 current_event_time);
1326 EMIT_SELECTION_CHANGED(collection, current_event_time);
1329 /* Toggle all items in the collection */
1330 void collection_invert_selection(Collection *collection)
1332 int item;
1334 g_return_if_fail(collection != NULL);
1335 g_return_if_fail(IS_COLLECTION(collection));
1337 if (collection->number_selected == 0)
1339 collection_select_all(collection);
1340 return;
1342 else if (collection->number_of_items == collection->number_selected)
1344 collection_clear_selection(collection);
1345 return;
1348 for (item = 0; item < collection->number_of_items; item++)
1349 collection->items[item].selected =
1350 !collection->items[item].selected;
1352 collection->number_selected = collection->number_of_items -
1353 collection->number_selected;
1355 /* Have to redraw everything... */
1356 gtk_widget_queue_draw(GTK_WIDGET(collection));
1358 EMIT_SELECTION_CHANGED(collection, current_event_time);
1361 /* Unselect all items except number item, which is selected (-1 to unselect
1362 * everything).
1364 void collection_clear_except(Collection *collection, gint item)
1366 GtkWidget *widget;
1367 int i = 0;
1368 int end; /* Selected items to end up with */
1370 g_return_if_fail(collection != NULL);
1371 g_return_if_fail(IS_COLLECTION(collection));
1372 g_return_if_fail(item >= -1 && item < collection->number_of_items);
1374 widget = GTK_WIDGET(collection);
1376 if (item == -1)
1377 end = 0;
1378 else
1380 collection_select_item(collection, item);
1381 end = 1;
1384 if (collection->number_selected == 0)
1385 return;
1387 while (collection->number_selected > end)
1389 while (i == item || !collection->items[i].selected)
1390 i++;
1392 collection->items[i].selected = FALSE;
1393 collection_draw_item(collection, i, TRUE);
1394 i++;
1396 collection->number_selected--;
1399 if (end == 0)
1400 g_signal_emit(collection, collection_signals[LOSE_SELECTION], 0,
1401 current_event_time);
1402 EMIT_SELECTION_CHANGED(collection, current_event_time);
1405 /* Unselect all items in the collection */
1406 void collection_clear_selection(Collection *collection)
1408 g_return_if_fail(collection != NULL);
1409 g_return_if_fail(IS_COLLECTION(collection));
1411 collection_clear_except(collection, -1);
1414 /* Force a redraw of the specified item, if it is visible */
1415 void collection_draw_item(Collection *collection, gint item, gboolean blank)
1417 GdkRectangle area;
1418 GtkWidget *widget;
1419 int row, col;
1421 g_return_if_fail(collection != NULL);
1422 /* g_return_if_fail(IS_COLLECTION(collection)); (slow) */
1423 g_return_if_fail(item >= 0 &&
1424 (item == 0 || item < collection->number_of_items));
1426 widget = GTK_WIDGET(collection);
1427 if (!GTK_WIDGET_REALIZED(widget))
1428 return;
1430 col = item % collection->columns;
1431 row = item / collection->columns;
1433 collection_get_item_area(collection, row, col, &area);
1435 gdk_window_invalidate_rect(widget->window, &area, FALSE);
1438 void collection_set_item_size(Collection *collection, int width, int height)
1440 GtkWidget *widget;
1442 g_return_if_fail(collection != NULL);
1443 g_return_if_fail(IS_COLLECTION(collection));
1444 g_return_if_fail(width > 4 && height > 4);
1446 if (collection->item_width == width &&
1447 collection->item_height == height)
1448 return;
1450 widget = GTK_WIDGET(collection);
1452 collection->item_width = width;
1453 collection->item_height = height;
1455 if (GTK_WIDGET_REALIZED(widget))
1457 gint window_width;
1459 gdk_drawable_get_size(widget->window, &window_width, NULL);
1460 collection->columns = MAX(window_width / collection->item_width,
1462 if (collection->cursor_item != -1)
1463 scroll_to_show(collection, collection->cursor_item);
1464 gtk_widget_queue_draw(widget);
1467 gtk_widget_queue_resize(GTK_WIDGET(collection));
1470 static int (*cmp_callback)(const void *a, const void *b) = NULL;
1471 static int collection_cmp(const void *a, const void *b)
1473 return cmp_callback(((CollectionItem *) a)->data,
1474 ((CollectionItem *) b)->data);
1476 static int collection_rcmp(const void *a, const void *b)
1478 return -cmp_callback(((CollectionItem *) a)->data,
1479 ((CollectionItem *) b)->data);
1482 /* Cursor is positioned on item with the same data as before the sort.
1483 * Same for the wink item.
1485 void collection_qsort(Collection *collection,
1486 int (*compar)(const void *, const void *),
1487 GtkSortType order)
1489 int cursor, wink, items, wink_on_map;
1490 gpointer cursor_data = NULL;
1491 gpointer wink_data = NULL;
1492 gpointer wink_on_map_data = NULL;
1493 CollectionItem *array;
1494 int i;
1495 int mul = order == GTK_SORT_ASCENDING ? 1 : -1;
1497 g_return_if_fail(collection != NULL);
1498 g_return_if_fail(IS_COLLECTION(collection));
1499 g_return_if_fail(compar != NULL);
1500 g_return_if_fail(cmp_callback == NULL);
1502 /* Check to see if it needs sorting (saves redrawing) */
1503 if (collection->number_of_items < 2)
1504 return;
1506 array = collection->items;
1507 for (i = 1; i < collection->number_of_items; i++)
1509 if (mul * compar(array[i - 1].data, array[i].data) > 0)
1510 break;
1512 if (i == collection->number_of_items)
1513 return; /* Already sorted */
1515 items = collection->number_of_items;
1517 wink_on_map = collection->wink_on_map;
1518 if (wink_on_map >= 0 && wink_on_map < items)
1520 wink_on_map_data = collection->items[wink_on_map].data;
1521 collection->wink_on_map = -1;
1523 else
1524 wink = -1;
1526 wink = collection->wink_item;
1527 if (wink >= 0 && wink < items)
1529 wink_data = collection->items[wink].data;
1530 collection->wink_item = -1;
1532 else
1533 wink = -1;
1535 cursor = collection->cursor_item;
1536 if (cursor >= 0 && cursor < items)
1537 cursor_data = collection->items[cursor].data;
1538 else
1539 cursor = -1;
1541 cmp_callback = compar;
1542 qsort(collection->items, items, sizeof(collection->items[0]),
1543 order == GTK_SORT_ASCENDING ? collection_cmp
1544 : collection_rcmp);
1545 cmp_callback = NULL;
1547 if (cursor > -1 || wink > -1 || wink_on_map > -1)
1549 int item;
1551 for (item = 0; item < items; item++)
1553 if (collection->items[item].data == cursor_data)
1554 collection_set_cursor_item(collection, item);
1555 if (collection->items[item].data == wink_on_map_data)
1556 collection->wink_on_map = item;
1557 if (collection->items[item].data == wink_data)
1559 collection->cursor_item_old = item;
1560 collection->wink_item = item;
1561 scroll_to_show(collection, item);
1566 gtk_widget_queue_draw(GTK_WIDGET(collection));
1569 /* Find an item in a sorted collection.
1570 * Returns the item number, or -1 if not found.
1572 int collection_find_item(Collection *collection, gpointer data,
1573 int (*compar)(const void *, const void *),
1574 GtkSortType order)
1576 int lower, upper;
1577 int mul = order == GTK_SORT_ASCENDING ? 1 : -1;
1579 g_return_val_if_fail(collection != NULL, -1);
1580 g_return_val_if_fail(IS_COLLECTION(collection), -1);
1581 g_return_val_if_fail(compar != NULL, -1);
1583 /* If item is here, then: lower <= i < upper */
1584 lower = 0;
1585 upper = collection->number_of_items;
1587 while (lower < upper)
1589 int i, cmp;
1591 i = (lower + upper) >> 1;
1593 cmp = mul * compar(collection->items[i].data, data);
1594 if (cmp == 0)
1595 return i;
1597 if (cmp > 0)
1598 upper = i;
1599 else
1600 lower = i + 1;
1603 return -1;
1606 /* Return the number of the item under the point (x,y), or -1 for none.
1607 * This may call your test_point callback. The point is relative to the
1608 * collection's origin.
1610 int collection_get_item(Collection *collection, int x, int y)
1612 int row, col;
1613 int width;
1614 int item;
1616 g_return_val_if_fail(collection != NULL, -1);
1618 col = x / collection->item_width;
1619 row = y / collection->item_height;
1621 if (col >= collection->columns)
1622 col = collection->columns - 1;
1624 if (col < 0 || row < 0)
1625 return -1;
1627 if (col == collection->columns - 1)
1628 width = collection->item_width << 1;
1629 else
1630 width = collection->item_width;
1632 item = col + row * collection->columns;
1633 if (item >= collection->number_of_items)
1634 return -1;
1636 x -= col * collection->item_width;
1637 y -= row * collection->item_height;
1639 if (collection->test_point(collection, x, y,
1640 &collection->items[item], width, collection->item_height,
1641 collection->cb_user_data))
1642 return item;
1644 return -1;
1647 /* Set the cursor/highlight over the given item. Passing -1
1648 * hides the cursor. As a special case, you may set the cursor item
1649 * to zero when there are no items.
1651 void collection_set_cursor_item(Collection *collection, gint item)
1653 int old_item;
1655 g_return_if_fail(collection != NULL);
1656 g_return_if_fail(IS_COLLECTION(collection));
1657 g_return_if_fail(item >= -1 &&
1658 (item < collection->number_of_items || item == 0));
1660 old_item = collection->cursor_item;
1662 if (old_item == item)
1663 return;
1665 collection->cursor_item = item;
1667 if (old_item != -1)
1668 collection_draw_item(collection, old_item, TRUE);
1670 if (item != -1)
1672 collection_draw_item(collection, item, TRUE);
1673 if (collection->auto_scroll == -1)
1674 scroll_to_show(collection, item);
1676 else if (old_item != -1)
1677 collection->cursor_item_old = old_item;
1680 /* Briefly highlight an item to draw the user's attention to it.
1681 * -1 cancels the effect, as does deleting items, sorting the collection
1682 * or starting a new wink effect.
1683 * Otherwise, the effect will cancel itself after a short pause.
1684 * */
1685 void collection_wink_item(Collection *collection, gint item)
1687 g_return_if_fail(collection != NULL);
1688 g_return_if_fail(IS_COLLECTION(collection));
1689 g_return_if_fail(item >= -1 && item < collection->number_of_items);
1691 if (collection->wink_item != -1)
1692 cancel_wink(collection);
1693 if (item == -1)
1694 return;
1696 if (!GTK_WIDGET_MAPPED(GTK_WIDGET(collection)))
1698 collection->wink_on_map = item;
1699 return;
1702 collection->cursor_item_old = collection->wink_item = item;
1703 collection->winks_left = MAX_WINKS;
1705 collection->wink_timeout = gtk_timeout_add(70,
1706 (GtkFunction) wink_timeout,
1707 collection);
1708 scroll_to_show(collection, item);
1709 invert_wink(collection);
1711 gdk_flush();
1714 /* Call test(item, data) on each item in the collection.
1715 * Remove all items for which it returns TRUE. test() should
1716 * free the data before returning TRUE. The collection is in an
1717 * inconsistant state during this call (ie, when test() is called).
1719 * If test is NULL, remove all items.
1721 void collection_delete_if(Collection *collection,
1722 gboolean (*test)(gpointer item, gpointer data),
1723 gpointer data)
1725 int in, out = 0;
1726 int selected = 0;
1727 int cursor;
1729 g_return_if_fail(collection != NULL);
1730 g_return_if_fail(IS_COLLECTION(collection));
1732 cursor = collection->cursor_item;
1734 for (in = 0; in < collection->number_of_items; in++)
1736 if (test && !test(collection->items[in].data, data))
1738 /* Keep item */
1739 if (collection->items[in].selected)
1741 collection->items[out].selected = TRUE;
1742 selected++;
1744 else
1745 collection->items[out].selected = FALSE;
1747 collection->items[out].data =
1748 collection->items[in].data;
1749 collection->items[out].view_data =
1750 collection->items[in].view_data;
1751 out++;
1753 else
1755 /* Remove item */
1756 if (collection->free_item)
1757 collection->free_item(collection,
1758 &collection->items[in]);
1760 if (collection->cursor_item >= in)
1761 cursor--;
1765 if (in != out)
1767 collection->cursor_item = cursor;
1769 if (collection->wink_item != -1)
1771 collection->wink_item = -1;
1772 gtk_timeout_remove(collection->wink_timeout);
1775 collection->number_of_items = out;
1776 if (collection->number_selected && !selected)
1778 /* We've lost all the selected items */
1779 g_signal_emit(collection,
1780 collection_signals[LOSE_SELECTION], 0,
1781 current_event_time);
1784 collection->number_selected = selected;
1785 resize_arrays(collection,
1786 MAX(collection->number_of_items, MINIMUM_ITEMS));
1788 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection)))
1789 gtk_widget_queue_draw(GTK_WIDGET(collection));
1791 gtk_widget_queue_resize(GTK_WIDGET(collection));
1795 /* Move the cursor by the given row and column offsets.
1796 * Moving by (0,0) can be used to simply make the cursor appear.
1798 void collection_move_cursor(Collection *collection, int drow, int dcol)
1800 int row, col, item;
1801 int first, last, total_rows;
1803 g_return_if_fail(collection != NULL);
1804 g_return_if_fail(IS_COLLECTION(collection));
1806 if (!collection->number_of_items)
1808 /* Show the cursor, even though there are no items */
1809 collection_set_cursor_item(collection, 0);
1810 return;
1813 get_visible_limits(collection, &first, &last);
1815 item = collection->cursor_item;
1816 if (item == -1)
1818 item = MIN(collection->cursor_item_old,
1819 collection->number_of_items - 1);
1822 if (item == -1)
1824 col = 0;
1825 row = first;
1827 else
1829 row = item / collection->columns;
1830 col = item % collection->columns + dcol;
1832 if (row < first)
1833 row = first;
1834 else if (row > last)
1835 row = last;
1836 else
1837 row = MAX(row + drow, 0);
1840 total_rows = (collection->number_of_items + collection->columns - 1)
1841 / collection->columns;
1843 if (row >= total_rows - 1 && drow > 0)
1845 row = total_rows - 1;
1846 item = col + row * collection->columns;
1847 if (item >= collection->number_of_items - 1)
1849 collection_set_cursor_item(collection,
1850 collection->number_of_items - 1);
1851 return;
1854 if (row < 0)
1855 row = 0;
1857 item = col + row * collection->columns;
1859 if (item >= 0 && item < collection->number_of_items)
1860 collection_set_cursor_item(collection, item);
1863 /* When autoscroll is on, a timer keeps track of the pointer position.
1864 * While it's near the top or bottom of the window, the window scrolls.
1866 * If the mouse buttons are released, the pointer leaves the window, or
1867 * a drag-and-drop operation finishes, auto_scroll is turned off.
1869 void collection_set_autoscroll(Collection *collection, gboolean auto_scroll)
1871 g_return_if_fail(collection != NULL);
1872 g_return_if_fail(IS_COLLECTION(collection));
1874 if (auto_scroll)
1876 if (collection->auto_scroll != -1)
1877 return; /* Already on! */
1879 collection->auto_scroll = gtk_timeout_add(50,
1880 (GtkFunction) as_timeout,
1881 collection);
1883 else
1885 if (collection->auto_scroll == -1)
1886 return; /* Already off! */
1888 gtk_timeout_remove(collection->auto_scroll);
1889 collection->auto_scroll = -1;
1893 /* Start a lasso box drag */
1894 void collection_lasso_box(Collection *collection, int x, int y)
1896 collection->drag_box_x[0] = x;
1897 collection->drag_box_y[0] = y;
1898 collection->drag_box_x[1] = x;
1899 collection->drag_box_y[1] = y;
1901 collection_set_autoscroll(collection, TRUE);
1902 add_lasso_box(collection);
1905 /* Remove the lasso box. Applies fn to each item inside the box.
1906 * fn may be GDK_INVERT, GDK_SET, GDK_NOOP or GDK_CLEAR.
1908 void collection_end_lasso(Collection *collection, GdkFunction fn)
1910 if (fn != GDK_CLEAR)
1912 GdkRectangle region;
1914 find_lasso_area(collection, &region);
1916 collection_process_area(collection, &region, fn,
1917 GDK_CURRENT_TIME);
1920 abort_lasso(collection);
1923 /* Unblock the selection_changed signal, emitting the signal if the
1924 * block counter reaches zero and emit is TRUE.
1926 void collection_unblock_selection_changed(Collection *collection,
1927 guint time,
1928 gboolean emit)
1930 g_return_if_fail(collection != NULL);
1931 g_return_if_fail(IS_COLLECTION(collection));
1932 g_return_if_fail(collection->block_selection_changed > 0);
1934 collection->block_selection_changed--;
1936 if (emit && !collection->block_selection_changed)
1937 g_signal_emit(collection,
1938 collection_signals[SELECTION_CHANGED], 0, time);