r1553: Created View interface and started moving collection specific stuff
[rox-filer.git] / ROX-Filer / src / collection.c
blob03eb38607c4a57f955a20f3a13fbe4b8e5597c53
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 gint collection_scroll_event(GtkWidget *widget, GdkEventScroll *event);
137 static void draw_focus_at(Collection *collection, GdkRectangle *area)
139 GtkWidget *widget;
140 GdkGC *gc;
142 widget = GTK_WIDGET(collection);
144 if (GTK_WIDGET_FLAGS(widget) & GTK_HAS_FOCUS)
145 gc = widget->style->fg_gc[GTK_STATE_ACTIVE];
146 else
147 gc = widget->style->fg_gc[GTK_STATE_INSENSITIVE];
149 gdk_draw_rectangle(widget->window, gc, FALSE,
150 area->x, area->y,
151 collection->item_width - 1,
152 area->height - 1);
155 static void draw_one_item(Collection *collection, int item, GdkRectangle *area)
157 if (item < collection->number_of_items)
159 collection->draw_item((GtkWidget *) collection,
160 &collection->items[item],
161 area, collection->cb_user_data);
164 if (item == collection->cursor_item)
165 draw_focus_at(collection, area);
168 GType collection_get_type(void)
170 static GType my_type = 0;
172 if (!my_type)
174 static const GTypeInfo info =
176 sizeof(CollectionClass),
177 NULL, /* base_init */
178 NULL, /* base_finalise */
179 (GClassInitFunc) collection_class_init,
180 NULL, /* class_finalise */
181 NULL, /* class_data */
182 sizeof(Collection),
183 0, /* n_preallocs */
184 collection_init
187 my_type = g_type_register_static(gtk_widget_get_type(),
188 "Collection", &info, 0);
191 return my_type;
194 typedef void (*FinalizeFn)(GObject *object);
196 static void collection_class_init(GObjectClass *gclass, gpointer data)
198 CollectionClass *collection_class = (CollectionClass *) gclass;
199 GtkObjectClass *object_class = (GtkObjectClass *) gclass;
200 GtkWidgetClass *widget_class = (GtkWidgetClass *) gclass;
202 parent_class = gtk_type_class(gtk_widget_get_type());
204 object_class->destroy = collection_destroy;
205 G_OBJECT_CLASS(object_class)->finalize =
206 (FinalizeFn) collection_finalize;
208 widget_class->realize = collection_realize;
209 widget_class->expose_event = collection_expose;
210 widget_class->size_request = collection_size_request;
211 widget_class->size_allocate = collection_size_allocate;
213 widget_class->key_press_event = collection_key_press;
215 widget_class->motion_notify_event = collection_motion_notify;
216 widget_class->map = collection_map;
217 widget_class->scroll_event = collection_scroll_event;
219 gclass->set_property = collection_set_property;
220 gclass->get_property = collection_get_property;
222 collection_class->gain_selection = NULL;
223 collection_class->lose_selection = NULL;
224 collection_class->selection_changed = NULL;
226 collection_signals[GAIN_SELECTION] = g_signal_new("gain_selection",
227 G_TYPE_FROM_CLASS(gclass),
228 G_SIGNAL_RUN_LAST,
229 G_STRUCT_OFFSET(CollectionClass,
230 gain_selection),
231 NULL, NULL,
232 g_cclosure_marshal_VOID__INT,
233 G_TYPE_NONE, 1,
234 G_TYPE_INT);
236 collection_signals[LOSE_SELECTION] = g_signal_new("lose_selection",
237 G_TYPE_FROM_CLASS(gclass),
238 G_SIGNAL_RUN_LAST,
239 G_STRUCT_OFFSET(CollectionClass,
240 lose_selection),
241 NULL, NULL,
242 g_cclosure_marshal_VOID__INT,
243 G_TYPE_NONE, 1,
244 G_TYPE_INT);
246 collection_signals[SELECTION_CHANGED] = g_signal_new(
247 "selection_changed",
248 G_TYPE_FROM_CLASS(gclass),
249 G_SIGNAL_RUN_LAST,
250 G_STRUCT_OFFSET(CollectionClass,
251 selection_changed),
252 NULL, NULL,
253 g_cclosure_marshal_VOID__INT,
254 G_TYPE_NONE, 1,
255 G_TYPE_INT);
257 g_object_class_install_property(gclass,
258 PROP_VADJUSTMENT,
259 g_param_spec_object("vadjustment",
260 _("Vertical Adjustment"),
261 _("The GtkAdjustment for the vertical position."),
262 GTK_TYPE_ADJUSTMENT,
263 G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
266 static void collection_init(GTypeInstance *instance, gpointer g_class)
268 Collection *object = (Collection *) instance;
270 g_return_if_fail(object != NULL);
271 g_return_if_fail(IS_COLLECTION(object));
273 GTK_WIDGET_SET_FLAGS(GTK_WIDGET(object), GTK_CAN_FOCUS);
275 object->number_of_items = 0;
276 object->number_selected = 0;
277 object->block_selection_changed = 0;
278 object->columns = 1;
279 object->item_width = 64;
280 object->item_height = 64;
281 object->vadj = NULL;
283 object->items = g_new(CollectionItem, MINIMUM_ITEMS);
284 object->cursor_item = -1;
285 object->cursor_item_old = -1;
286 object->wink_item = -1;
287 object->wink_on_map = -1;
288 object->array_size = MINIMUM_ITEMS;
289 object->draw_item = default_draw_item;
290 object->test_point = default_test_point;
291 object->free_item = NULL;
293 object->auto_scroll = -1;
296 GtkWidget* collection_new(void)
298 return GTK_WIDGET(gtk_widget_new(collection_get_type(), NULL));
301 /* Note: The draw_item call gives the maximum area that can be
302 * drawn to. For the column on the far right, this extends to the
303 * edge of the window. Normally, use collection->item_width instead
304 * of area->width to calculate the position.
306 * test_point does not use a larger value for the width, but the
307 * x point of the click may be larger than the width.
309 void collection_set_functions(Collection *collection,
310 CollectionDrawFunc draw_item,
311 CollectionTestFunc test_point,
312 gpointer user_data)
314 GtkWidget *widget;
316 g_return_if_fail(collection != NULL);
317 g_return_if_fail(IS_COLLECTION(collection));
319 widget = GTK_WIDGET(collection);
321 if (!draw_item)
322 draw_item = default_draw_item;
323 if (!test_point)
324 test_point = default_test_point;
326 collection->draw_item = draw_item;
327 collection->test_point = test_point;
328 collection->cb_user_data = user_data;
330 if (GTK_WIDGET_REALIZED(widget))
331 gtk_widget_queue_draw(widget);
334 /* After this we are unusable, but our data (if any) is still hanging around.
335 * It will be freed later with finalize.
337 static void collection_destroy(GtkObject *object)
339 Collection *collection;
341 g_return_if_fail(object != NULL);
342 g_return_if_fail(IS_COLLECTION(object));
344 collection = COLLECTION(object);
346 collection_clear(collection);
348 if (collection->auto_scroll != -1)
350 gtk_timeout_remove(collection->auto_scroll);
351 collection->auto_scroll = -1;
354 if (collection->vadj)
356 g_object_unref(G_OBJECT(collection->vadj));
357 collection->vadj = NULL;
360 if (GTK_OBJECT_CLASS(parent_class)->destroy)
361 (*GTK_OBJECT_CLASS(parent_class)->destroy)(object);
364 /* This is the last thing that happens to us. Free all data. */
365 static void collection_finalize(GObject *object)
367 Collection *collection;
369 collection = COLLECTION(object);
371 g_return_if_fail(collection->number_of_items == 0);
373 g_free(collection->items);
375 if (G_OBJECT_CLASS(parent_class)->finalize)
376 G_OBJECT_CLASS(parent_class)->finalize(object);
379 static void collection_map(GtkWidget *widget)
381 Collection *collection = COLLECTION(widget);
383 if (GTK_WIDGET_CLASS(parent_class)->map)
384 (*GTK_WIDGET_CLASS(parent_class)->map)(widget);
386 if (collection->wink_on_map >= 0)
388 collection_wink_item(collection, collection->wink_on_map);
389 collection->wink_on_map = -1;
393 static void collection_realize(GtkWidget *widget)
395 Collection *collection;
396 GdkWindowAttr attributes;
397 gint attributes_mask;
398 GdkGCValues xor_values;
399 GdkColor *bg, *fg;
401 g_return_if_fail(widget != NULL);
402 g_return_if_fail(IS_COLLECTION(widget));
403 g_return_if_fail(widget->parent != NULL);
405 GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);
406 collection = COLLECTION(widget);
408 attributes.x = widget->allocation.x;
409 attributes.y = widget->allocation.y;
410 attributes.width = widget->allocation.width;
411 attributes.height = widget->allocation.height;
412 attributes.wclass = GDK_INPUT_OUTPUT;
413 attributes.window_type = GDK_WINDOW_CHILD;
414 attributes.event_mask = gtk_widget_get_events(widget) |
415 GDK_EXPOSURE_MASK |
416 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
417 GDK_BUTTON1_MOTION_MASK | GDK_BUTTON2_MOTION_MASK |
418 GDK_BUTTON3_MOTION_MASK;
419 attributes.visual = gtk_widget_get_visual(widget);
420 attributes.colormap = gtk_widget_get_colormap(widget);
422 attributes_mask = GDK_WA_X | GDK_WA_Y |
423 GDK_WA_VISUAL | GDK_WA_COLORMAP;
424 widget->window = gdk_window_new(gtk_widget_get_parent_window(widget),
425 &attributes, attributes_mask);
427 widget->style = gtk_style_attach(widget->style, widget->window);
429 gdk_window_set_user_data(widget->window, widget);
430 gdk_window_set_background(widget->window,
431 &widget->style->bg[GTK_STATE_NORMAL]);
433 bg = &widget->style->bg[GTK_STATE_NORMAL];
434 fg = &widget->style->fg[GTK_STATE_NORMAL];
435 xor_values.function = GDK_XOR;
436 xor_values.foreground.pixel = fg->pixel ^ bg->pixel;
437 collection->xor_gc = gdk_gc_new_with_values(widget->window,
438 &xor_values,
439 GDK_GC_FOREGROUND
440 | GDK_GC_FUNCTION);
443 static void collection_size_request(GtkWidget *widget,
444 GtkRequisition *requisition)
446 Collection *collection = COLLECTION(widget);
447 int rows, cols = collection->columns;
449 /* We ask for the total size we need; our containing viewport
450 * will deal with scrolling.
452 requisition->width = MIN_WIDTH;
453 rows = (collection->number_of_items + cols - 1) / cols;
454 requisition->height = rows * collection->item_height;
457 static gboolean scroll_after_alloc(Collection *collection)
459 if (collection->wink_item != -1)
460 scroll_to_show(collection, collection->wink_item);
461 else if (collection->cursor_item != -1)
462 scroll_to_show(collection, collection->cursor_item);
463 g_object_unref(G_OBJECT(collection));
465 return FALSE;
468 static void collection_size_allocate(GtkWidget *widget,
469 GtkAllocation *allocation)
471 Collection *collection;
472 int old_columns;
473 gboolean cursor_visible = FALSE;
475 g_return_if_fail(widget != NULL);
476 g_return_if_fail(IS_COLLECTION(widget));
477 g_return_if_fail(allocation != NULL);
479 collection = COLLECTION(widget);
481 if (collection->cursor_item != -1)
483 int first, last;
484 int crow = collection->cursor_item / collection->columns;
486 get_visible_limits(collection, &first, &last);
488 cursor_visible = crow >= first && crow <= last;
491 old_columns = collection->columns;
493 widget->allocation = *allocation;
495 collection->columns = allocation->width / collection->item_width;
496 if (collection->columns < 1)
497 collection->columns = 1;
499 if (GTK_WIDGET_REALIZED(widget))
501 gdk_window_move_resize(widget->window,
502 allocation->x, allocation->y,
503 allocation->width, allocation->height);
505 if (cursor_visible)
506 scroll_to_show(collection, collection->cursor_item);
509 if (old_columns != collection->columns)
511 /* Need to go around again... */
512 gtk_widget_queue_resize(widget);
514 else if (collection->wink_item != -1 || collection->cursor_item != -1)
516 /* Viewport resets the adjustments after the alloc */
517 g_object_ref(G_OBJECT(collection));
518 g_idle_add((GSourceFunc) scroll_after_alloc, collection);
522 /* Return the area occupied by the item at (row, col) by filling
523 * in 'area'.
525 static void collection_get_item_area(Collection *collection,
526 int row, int col,
527 GdkRectangle *area)
530 area->x = col * collection->item_width;
531 area->y = row * collection->item_height;
533 area->width = collection->item_width;
534 area->height = collection->item_height;
535 if (col == collection->columns - 1)
536 area->width <<= 1;
539 static gint collection_expose(GtkWidget *widget, GdkEventExpose *event)
541 Collection *collection;
542 GdkRectangle item_area;
543 int row, col;
544 int item;
545 int start_row, last_row;
546 int start_col, last_col;
547 int phys_last_col;
549 g_return_val_if_fail(widget != NULL, FALSE);
550 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
551 g_return_val_if_fail(event != NULL, FALSE);
553 gtk_paint_flat_box(widget->style, widget->window, GTK_STATE_NORMAL,
554 GTK_SHADOW_NONE, &event->area,
555 widget, "base", 0, 0, -1, -1);
557 collection = COLLECTION(widget);
559 /* Calculate the ranges to plot */
560 start_row = event->area.y / collection->item_height;
561 last_row = (event->area.y + event->area.height - 1)
562 / collection->item_height;
563 row = start_row;
565 start_col = event->area.x / collection->item_width;
566 phys_last_col = (event->area.x + event->area.width - 1)
567 / collection->item_width;
569 /* The right-most column may be wider than the others.
570 * Therefore, to redraw the area after the last 'real' column
571 * we may have to draw the right-most column.
573 if (start_col >= collection->columns)
574 start_col = collection->columns - 1;
576 if (phys_last_col >= collection->columns)
577 last_col = collection->columns - 1;
578 else
579 last_col = phys_last_col;
581 col = start_col;
583 item = row * collection->columns + col;
585 while ((item == 0 || item < collection->number_of_items)
586 && row <= last_row)
588 collection_get_item_area(collection, row, col, &item_area);
590 draw_one_item(collection, item, &item_area);
591 col++;
593 if (col > last_col)
595 col = start_col;
596 row++;
597 item = row * collection->columns + col;
599 else
600 item++;
603 if (collection->lasso_box)
604 draw_lasso_box(collection);
606 return FALSE;
609 static void default_draw_item(GtkWidget *widget,
610 CollectionItem *item,
611 GdkRectangle *area,
612 gpointer user_data)
614 gdk_draw_arc(widget->window,
615 item->selected ? widget->style->white_gc
616 : widget->style->black_gc,
617 TRUE,
618 area->x, area->y,
619 COLLECTION(widget)->item_width, area->height,
620 0, 360 * 64);
624 static gboolean default_test_point(Collection *collection,
625 int point_x, int point_y,
626 CollectionItem *item,
627 int width, int height,
628 gpointer user_data)
630 float f_x, f_y;
632 /* Convert to point in unit circle */
633 f_x = ((float) point_x / width) - 0.5;
634 f_y = ((float) point_y / height) - 0.5;
636 return (f_x * f_x) + (f_y * f_y) <= .25;
639 static void collection_set_property(GObject *object,
640 guint prop_id,
641 const GValue *value,
642 GParamSpec *pspec)
644 Collection *collection;
646 collection = COLLECTION(object);
648 switch (prop_id)
650 case PROP_VADJUSTMENT:
651 collection_set_adjustment(collection,
652 g_value_get_object(value));
653 break;
654 default:
655 G_OBJECT_WARN_INVALID_PROPERTY_ID(object,
656 prop_id, pspec);
657 break;
661 static void collection_set_adjustment(Collection *collection,
662 GtkAdjustment *vadj)
664 if (vadj)
665 g_return_if_fail(GTK_IS_ADJUSTMENT(vadj));
666 else
667 vadj = GTK_ADJUSTMENT(gtk_adjustment_new(0.0,
668 0.0, 0.0,
669 0.0, 0.0, 0.0));
671 if (collection->vadj == vadj)
672 return;
674 if (collection->vadj)
675 g_object_unref(G_OBJECT(collection->vadj));
677 collection->vadj = vadj;
678 g_object_ref(G_OBJECT(collection->vadj));
679 gtk_object_sink(GTK_OBJECT(collection->vadj));
682 static void collection_get_property(GObject *object,
683 guint prop_id,
684 GValue *value,
685 GParamSpec *pspec)
687 Collection *collection;
689 collection = COLLECTION(object);
691 switch (prop_id)
693 case PROP_VADJUSTMENT:
694 g_value_set_object(value, G_OBJECT(collection->vadj));
695 break;
696 default:
697 G_OBJECT_WARN_INVALID_PROPERTY_ID(object,
698 prop_id, pspec);
699 break;
703 /* Change the adjustment by this amount. Bounded. */
704 static void diff_vpos(Collection *collection, int diff)
706 int value = collection->vadj->value + diff;
708 value = CLAMP(value, 0,
709 collection->vadj->upper - collection->vadj->page_size);
710 gtk_adjustment_set_value(collection->vadj, value);
713 static void resize_arrays(Collection *collection, guint new_size)
715 g_return_if_fail(collection != NULL);
716 g_return_if_fail(IS_COLLECTION(collection));
717 g_return_if_fail(new_size >= collection->number_of_items);
719 collection->items = g_realloc(collection->items,
720 sizeof(CollectionItem) * new_size);
721 collection->array_size = new_size;
724 static gint collection_key_press(GtkWidget *widget, GdkEventKey *event)
726 Collection *collection;
727 int item;
728 int key;
730 g_return_val_if_fail(widget != NULL, FALSE);
731 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
732 g_return_val_if_fail(event != NULL, FALSE);
734 collection = (Collection *) widget;
735 item = collection->cursor_item;
737 key = event->keyval;
738 if (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK))
740 if (key == GDK_Left || key == GDK_Right || \
741 key == GDK_Up || key == GDK_Down)
742 return TRUE;
743 return FALSE;
746 switch (key)
748 case GDK_Left:
749 collection_move_cursor(collection, 0, -1);
750 break;
751 case GDK_Right:
752 collection_move_cursor(collection, 0, 1);
753 break;
754 case GDK_Up:
755 collection_move_cursor(collection, -1, 0);
756 break;
757 case GDK_Down:
758 collection_move_cursor(collection, 1, 0);
759 break;
760 case GDK_Home:
761 collection_set_cursor_item(collection, 0);
762 break;
763 case GDK_End:
764 collection_set_cursor_item(collection,
765 MAX((gint) collection->number_of_items - 1, 0));
766 break;
767 case GDK_Page_Up:
769 int first, last;
770 get_visible_limits(collection, &first, &last);
771 collection_move_cursor(collection, first - last - 1, 0);
772 break;
774 case GDK_Page_Down:
776 int first, last;
777 get_visible_limits(collection, &first, &last);
778 collection_move_cursor(collection, last - first + 1, 0);
779 break;
781 case GDK_Escape:
782 collection_set_cursor_item(collection, -1);
783 collection_clear_selection(collection);
784 return FALSE; /* Pass it on */
785 case ' ':
786 if (item >=0 && item < collection->number_of_items)
788 collection_toggle_item(collection, item);
789 if (item < collection->number_of_items - 1)
790 collection_set_cursor_item(collection,
791 item + 1);
793 break;
794 default:
795 return FALSE;
798 return TRUE;
801 /* Wheel mouse scrolling */
802 static gint collection_scroll_event(GtkWidget *widget, GdkEventScroll *event)
804 Collection *collection;
805 int diff = 0;
807 g_return_val_if_fail(widget != NULL, FALSE);
808 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
809 g_return_val_if_fail(event != NULL, FALSE);
811 collection = COLLECTION(widget);
813 if (event->direction == GDK_SCROLL_UP)
814 diff = -1;
815 else if (event->direction == GDK_SCROLL_DOWN)
816 diff = 1;
817 else
818 return FALSE;
820 if (diff)
822 int old_value = collection->vadj->value;
823 int new_value = 0;
824 gboolean box = collection->lasso_box;
825 int step = collection->vadj->page_increment / 2;
827 new_value = CLAMP(old_value + diff * step, 0.0,
828 collection->vadj->upper
829 - collection->vadj->page_size);
830 diff = new_value - old_value;
831 if (diff)
833 if (box)
835 remove_lasso_box(collection);
836 collection->drag_box_y[0] -= diff;
838 gtk_adjustment_set_value(collection->vadj, new_value);
839 if (box)
840 add_lasso_box(collection);
844 return TRUE;
847 /* 'from' and 'to' are pixel positions. 'step' is the size of each item.
848 * Returns the index of the first item covered, and the number of items.
850 static void get_range(int from, int to, int step, gint *pos, gint *len)
852 int margin = MIN(step / 4, 40);
854 if (from > to)
856 int tmp = to;
857 to = from;
858 from = tmp;
861 from = (from + margin) / step; /* First item */
862 to = (to + step - margin) / step; /* Last item (inclusive) */
864 *pos = MAX(from, 0);
865 *len = to - *pos;
868 /* Fills in the area with a rectangle corresponding to the current
869 * size of the lasso box (units of items, not pixels).
871 * The box will only span valid columns, but the total number
872 * of items is not taken into account (rows or cols).
874 static void find_lasso_area(Collection *collection, GdkRectangle *area)
876 int cols = collection->columns;
877 int dx = collection->drag_box_x[0] - collection->drag_box_x[1];
878 int dy = collection->drag_box_y[0] - collection->drag_box_y[1];
880 if (ABS(dx) < 8 && ABS(dy) < 8)
882 /* Didn't move far enough - ignore */
883 area->x = area->y = 0;
884 area->width = 0;
885 area->height = 0;
886 return;
889 get_range(collection->drag_box_x[0], collection->drag_box_x[1],
890 collection->item_width, &area->x, &area->width);
892 if (area->x >= cols)
893 area->width = 0;
894 else if (area->x + area->width > cols)
895 area->width = cols - area->x;
897 get_range(collection->drag_box_y[0], collection->drag_box_y[1],
898 collection->item_height, &area->y, &area->height);
901 static void collection_process_area(Collection *collection,
902 GdkRectangle *area,
903 GdkFunction fn,
904 guint32 time)
906 int x, y;
907 guint32 stacked_time;
908 int item;
909 gboolean changed = FALSE;
910 guint old_selected;
912 g_return_if_fail(fn == GDK_SET || fn == GDK_INVERT);
914 old_selected = collection->number_selected;
916 stacked_time = current_event_time;
917 current_event_time = time;
919 collection->block_selection_changed++;
921 for (y = area->y; y < area->y + area->height; y++)
923 item = y * collection->columns + area->x;
925 for (x = area->x; x < area->x + area->width; x++)
927 if (item >= collection->number_of_items)
928 goto out;
930 if (fn == GDK_INVERT)
931 collection_item_set_selected(collection, item,
932 !collection->items[item].selected,
933 FALSE);
934 else
935 collection_item_set_selected(collection, item,
936 TRUE, FALSE);
938 changed = TRUE;
939 item++;
943 out:
944 if (collection->number_selected && !old_selected)
945 g_signal_emit(collection,
946 collection_signals[GAIN_SELECTION], 0,
947 current_event_time);
948 else if (!collection->number_selected && old_selected)
949 g_signal_emit(collection,
950 collection_signals[LOSE_SELECTION], 0,
951 current_event_time);
953 collection_unblock_selection_changed(collection,
954 current_event_time, changed);
955 current_event_time = stacked_time;
958 static gint collection_motion_notify(GtkWidget *widget,
959 GdkEventMotion *event)
961 Collection *collection;
962 gint x, y;
964 g_return_val_if_fail(widget != NULL, FALSE);
965 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
966 g_return_val_if_fail(event != NULL, FALSE);
968 collection = COLLECTION(widget);
970 if (!collection->lasso_box)
971 return FALSE;
973 if (event->window != widget->window)
974 gdk_window_get_pointer(widget->window, &x, &y, NULL);
975 else
977 x = event->x;
978 y = event->y;
981 remove_lasso_box(collection);
982 collection->drag_box_x[1] = x;
983 collection->drag_box_y[1] = y;
984 add_lasso_box(collection);
985 return TRUE;
988 static void add_lasso_box(Collection *collection)
990 g_return_if_fail(collection != NULL);
991 g_return_if_fail(IS_COLLECTION(collection));
992 g_return_if_fail(collection->lasso_box == FALSE);
994 collection->lasso_box = TRUE;
995 draw_lasso_box(collection);
998 static void draw_lasso_box(Collection *collection)
1000 GtkWidget *widget;
1001 int x, y, width, height;
1003 widget = GTK_WIDGET(collection);
1005 x = MIN(collection->drag_box_x[0], collection->drag_box_x[1]);
1006 y = MIN(collection->drag_box_y[0], collection->drag_box_y[1]);
1007 width = abs(collection->drag_box_x[1] - collection->drag_box_x[0]);
1008 height = abs(collection->drag_box_y[1] - collection->drag_box_y[0]);
1010 /* XXX: A redraw bug sometimes leaves a one-pixel dot on the screen.
1011 * As a quick hack, don't draw boxes that small for now...
1013 if (width || height)
1014 gdk_draw_rectangle(widget->window, collection->xor_gc, FALSE,
1015 x, y, width, height);
1018 static void abort_lasso(Collection *collection)
1020 if (collection->lasso_box)
1022 remove_lasso_box(collection);
1023 collection_set_autoscroll(collection, FALSE);
1027 static void remove_lasso_box(Collection *collection)
1029 g_return_if_fail(collection != NULL);
1030 g_return_if_fail(IS_COLLECTION(collection));
1031 g_return_if_fail(collection->lasso_box == TRUE);
1033 draw_lasso_box(collection);
1035 collection->lasso_box = FALSE;
1037 return;
1040 /* Make sure that 'item' is fully visible (vertically), scrolling if not. */
1041 static void scroll_to_show(Collection *collection, int item)
1043 int first, last, row;
1045 g_return_if_fail(collection != NULL);
1046 g_return_if_fail(IS_COLLECTION(collection));
1048 row = item / collection->columns;
1049 get_visible_limits(collection, &first, &last);
1051 if (row <= first)
1053 gtk_adjustment_set_value(collection->vadj,
1054 row * collection->item_height);
1056 else if (row >= last)
1058 GtkWidget *widget = (GtkWidget *) collection;
1059 gint height;
1061 if (GTK_WIDGET_REALIZED(widget))
1063 height = collection->vadj->page_size;
1064 gtk_adjustment_set_value(collection->vadj,
1065 (row + 1) * collection->item_height - height);
1070 /* Return the first and last rows which are [partly] visible. Does not
1071 * ensure that the rows actually exist (contain items).
1073 static void get_visible_limits(Collection *collection, int *first, int *last)
1075 GtkWidget *widget = (GtkWidget *) collection;
1076 gint scroll = 0, height;
1078 g_return_if_fail(collection != NULL);
1079 g_return_if_fail(IS_COLLECTION(collection));
1080 g_return_if_fail(first != NULL && last != NULL);
1082 if (!GTK_WIDGET_REALIZED(widget))
1084 *first = 0;
1085 *last = 0;
1087 else
1089 scroll = collection->vadj->value;
1090 height = collection->vadj->page_size;
1092 *first = MAX(scroll / collection->item_height, 0);
1093 *last = (scroll + height - 1) /collection->item_height;
1095 if (*last < *first)
1096 *last = *first;
1100 /* Cancel the current wink effect. */
1101 static void cancel_wink(Collection *collection)
1103 gint item;
1105 g_return_if_fail(collection != NULL);
1106 g_return_if_fail(IS_COLLECTION(collection));
1107 g_return_if_fail(collection->wink_item != -1);
1109 item = collection->wink_item;
1111 collection->wink_item = -1;
1112 gtk_timeout_remove(collection->wink_timeout);
1114 collection_draw_item(collection, item, TRUE);
1117 /* Draw/undraw a box around collection->wink_item */
1118 static void invert_wink(Collection *collection)
1120 GdkRectangle area;
1121 gint row, col;
1123 g_return_if_fail(collection->wink_item >= 0);
1125 if (!GTK_WIDGET_REALIZED(GTK_WIDGET(collection)))
1126 return;
1128 col = collection->wink_item % collection->columns;
1129 row = collection->wink_item / collection->columns;
1130 collection_get_item_area(collection, row, col, &area);
1132 gdk_draw_rectangle(((GtkWidget *) collection)->window,
1133 collection->xor_gc, FALSE,
1134 area.x, area.y,
1135 collection->item_width - 1,
1136 area.height - 1);
1139 static gboolean wink_timeout(Collection *collection)
1141 gint item;
1143 g_return_val_if_fail(collection != NULL, FALSE);
1144 g_return_val_if_fail(IS_COLLECTION(collection), FALSE);
1145 g_return_val_if_fail(collection->wink_item != -1, FALSE);
1147 item = collection->wink_item;
1149 if (collection->winks_left-- > 0)
1151 invert_wink(collection);
1152 return TRUE;
1155 collection->wink_item = -1;
1157 collection_draw_item(collection, item, TRUE);
1159 return FALSE;
1162 /* This is called frequently while auto_scroll is on.
1163 * Checks the pointer position and scrolls the window if it's
1164 * near the top or bottom.
1166 static gboolean as_timeout(Collection *collection)
1168 GdkWindow *window = GTK_WIDGET(collection)->window;
1169 gint x, y, w, h;
1170 GdkModifierType mask;
1171 int diff = 0;
1173 gdk_window_get_pointer(window, &x, &y, &mask);
1174 gdk_drawable_get_size(window, &w, NULL);
1176 h = collection->vadj->page_size;
1177 y -= collection->vadj->value;
1179 if ((x < 0 || x > w || y < 0 || y > h) && !collection->lasso_box)
1181 collection->auto_scroll = -1;
1182 return FALSE; /* Out of window - stop */
1185 if (y < AUTOSCROLL_STEP)
1186 diff = y - AUTOSCROLL_STEP;
1187 else if (y > h - AUTOSCROLL_STEP)
1188 diff = AUTOSCROLL_STEP + y - h;
1190 if (diff)
1191 diff_vpos(collection, diff);
1193 return TRUE;
1196 /* Change the selected state of an item.
1197 * Send GAIN/LOSE signals if 'signal' is TRUE.
1198 * Send SELECTION_CHANGED unless blocked.
1199 * Updates number_selected and redraws the item.
1201 static void collection_item_set_selected(Collection *collection,
1202 gint item,
1203 gboolean selected,
1204 gboolean signal)
1206 g_return_if_fail(collection != NULL);
1207 g_return_if_fail(IS_COLLECTION(collection));
1208 g_return_if_fail(item >= 0 && item < collection->number_of_items);
1210 if (collection->items[item].selected == selected)
1211 return;
1213 collection->items[item].selected = selected;
1214 collection_draw_item(collection, item, TRUE);
1216 if (selected)
1218 collection->number_selected++;
1219 if (signal && collection->number_selected == 1)
1220 g_signal_emit(collection,
1221 collection_signals[GAIN_SELECTION], 0,
1222 current_event_time);
1224 else
1226 collection->number_selected--;
1227 if (signal && collection->number_selected == 0)
1228 g_signal_emit(collection,
1229 collection_signals[LOSE_SELECTION], 0,
1230 current_event_time);
1233 EMIT_SELECTION_CHANGED(collection, current_event_time);
1236 /* Functions for managing collections */
1238 /* Remove all objects from the collection */
1239 void collection_clear(Collection *collection)
1241 collection_delete_if(collection, NULL, NULL);
1244 /* Inserts a new item at the end. The new item is unselected, and its
1245 * number is returned.
1247 gint collection_insert(Collection *collection, gpointer data, gpointer view)
1249 int item;
1251 /* g_return_val_if_fail(IS_COLLECTION(collection), -1); (slow) */
1253 item = collection->number_of_items;
1255 if (item >= collection->array_size)
1256 resize_arrays(collection, item + (item >> 1));
1258 collection->items[item].data = data;
1259 collection->items[item].view_data = view;
1260 collection->items[item].selected = FALSE;
1262 collection->number_of_items++;
1264 gtk_widget_queue_resize(GTK_WIDGET(collection));
1266 collection_draw_item(collection, item, FALSE);
1268 return item;
1271 void collection_unselect_item(Collection *collection, gint item)
1273 collection_item_set_selected(collection, item, FALSE, TRUE);
1276 void collection_select_item(Collection *collection, gint item)
1278 collection_item_set_selected(collection, item, TRUE, TRUE);
1281 void collection_toggle_item(Collection *collection, gint item)
1283 collection_item_set_selected(collection, item,
1284 !collection->items[item].selected, TRUE);
1287 /* Select all items in the collection */
1288 void collection_select_all(Collection *collection)
1290 GtkWidget *widget;
1291 int item = 0;
1293 g_return_if_fail(collection != NULL);
1294 g_return_if_fail(IS_COLLECTION(collection));
1296 widget = GTK_WIDGET(collection);
1298 if (collection->number_selected == collection->number_of_items)
1299 return; /* Nothing to do */
1301 while (collection->number_selected < collection->number_of_items)
1303 while (collection->items[item].selected)
1304 item++;
1306 collection->items[item].selected = TRUE;
1307 collection_draw_item(collection, item, TRUE);
1308 item++;
1310 collection->number_selected++;
1313 g_signal_emit(collection, collection_signals[GAIN_SELECTION], 0,
1314 current_event_time);
1315 EMIT_SELECTION_CHANGED(collection, current_event_time);
1318 /* Toggle all items in the collection */
1319 void collection_invert_selection(Collection *collection)
1321 int item;
1323 g_return_if_fail(collection != NULL);
1324 g_return_if_fail(IS_COLLECTION(collection));
1326 if (collection->number_selected == 0)
1328 collection_select_all(collection);
1329 return;
1331 else if (collection->number_of_items == collection->number_selected)
1333 collection_clear_selection(collection);
1334 return;
1337 for (item = 0; item < collection->number_of_items; item++)
1338 collection->items[item].selected =
1339 !collection->items[item].selected;
1341 collection->number_selected = collection->number_of_items -
1342 collection->number_selected;
1344 /* Have to redraw everything... */
1345 gtk_widget_queue_draw(GTK_WIDGET(collection));
1347 EMIT_SELECTION_CHANGED(collection, current_event_time);
1350 /* Unselect all items except number item, which is selected (-1 to unselect
1351 * everything).
1353 void collection_clear_except(Collection *collection, gint item)
1355 GtkWidget *widget;
1356 int i = 0;
1357 int end; /* Selected items to end up with */
1359 g_return_if_fail(collection != NULL);
1360 g_return_if_fail(IS_COLLECTION(collection));
1361 g_return_if_fail(item >= -1 && item < collection->number_of_items);
1363 widget = GTK_WIDGET(collection);
1365 if (item == -1)
1366 end = 0;
1367 else
1369 collection_select_item(collection, item);
1370 end = 1;
1373 if (collection->number_selected == 0)
1374 return;
1376 while (collection->number_selected > end)
1378 while (i == item || !collection->items[i].selected)
1379 i++;
1381 collection->items[i].selected = FALSE;
1382 collection_draw_item(collection, i, TRUE);
1383 i++;
1385 collection->number_selected--;
1388 if (end == 0)
1389 g_signal_emit(collection, collection_signals[LOSE_SELECTION], 0,
1390 current_event_time);
1391 EMIT_SELECTION_CHANGED(collection, current_event_time);
1394 /* Unselect all items in the collection */
1395 void collection_clear_selection(Collection *collection)
1397 g_return_if_fail(collection != NULL);
1398 g_return_if_fail(IS_COLLECTION(collection));
1400 collection_clear_except(collection, -1);
1403 /* Force a redraw of the specified item, if it is visible */
1404 void collection_draw_item(Collection *collection, gint item, gboolean blank)
1406 GdkRectangle area;
1407 GtkWidget *widget;
1408 int row, col;
1410 g_return_if_fail(collection != NULL);
1411 /* g_return_if_fail(IS_COLLECTION(collection)); (slow) */
1412 g_return_if_fail(item >= 0 &&
1413 (item == 0 || item < collection->number_of_items));
1415 widget = GTK_WIDGET(collection);
1416 if (!GTK_WIDGET_REALIZED(widget))
1417 return;
1419 col = item % collection->columns;
1420 row = item / collection->columns;
1422 collection_get_item_area(collection, row, col, &area);
1424 gdk_window_invalidate_rect(widget->window, &area, FALSE);
1427 void collection_set_item_size(Collection *collection, int width, int height)
1429 GtkWidget *widget;
1431 g_return_if_fail(collection != NULL);
1432 g_return_if_fail(IS_COLLECTION(collection));
1433 g_return_if_fail(width > 4 && height > 4);
1435 if (collection->item_width == width &&
1436 collection->item_height == height)
1437 return;
1439 widget = GTK_WIDGET(collection);
1441 collection->item_width = width;
1442 collection->item_height = height;
1444 if (GTK_WIDGET_REALIZED(widget))
1446 gint window_width;
1448 gdk_drawable_get_size(widget->window, &window_width, NULL);
1449 collection->columns = MAX(window_width / collection->item_width,
1451 if (collection->cursor_item != -1)
1452 scroll_to_show(collection, collection->cursor_item);
1453 gtk_widget_queue_draw(widget);
1456 gtk_widget_queue_resize(GTK_WIDGET(collection));
1459 static int (*cmp_callback)(const void *a, const void *b) = NULL;
1460 static int collection_cmp(const void *a, const void *b)
1462 return cmp_callback(((CollectionItem *) a)->data,
1463 ((CollectionItem *) b)->data);
1466 /* Cursor is positioned on item with the same data as before the sort.
1467 * Same for the wink item.
1469 void collection_qsort(Collection *collection,
1470 int (*compar)(const void *, const void *))
1472 int cursor, wink, items, wink_on_map;
1473 gpointer cursor_data = NULL;
1474 gpointer wink_data = NULL;
1475 gpointer wink_on_map_data = NULL;
1476 CollectionItem *array;
1477 int i;
1479 g_return_if_fail(collection != NULL);
1480 g_return_if_fail(IS_COLLECTION(collection));
1481 g_return_if_fail(compar != NULL);
1482 g_return_if_fail(cmp_callback == NULL);
1484 /* Check to see if it needs sorting (saves redrawing) */
1485 if (collection->number_of_items < 2)
1486 return;
1488 array = collection->items;
1489 for (i = 1; i < collection->number_of_items; i++)
1491 if (compar(array[i - 1].data, array[i].data) > 0)
1492 break;
1494 if (i == collection->number_of_items)
1495 return; /* Already sorted */
1497 items = collection->number_of_items;
1499 wink_on_map = collection->wink_on_map;
1500 if (wink_on_map >= 0 && wink_on_map < items)
1502 wink_on_map_data = collection->items[wink_on_map].data;
1503 collection->wink_on_map = -1;
1505 else
1506 wink = -1;
1508 wink = collection->wink_item;
1509 if (wink >= 0 && wink < items)
1511 wink_data = collection->items[wink].data;
1512 collection->wink_item = -1;
1514 else
1515 wink = -1;
1517 cursor = collection->cursor_item;
1518 if (cursor >= 0 && cursor < items)
1519 cursor_data = collection->items[cursor].data;
1520 else
1521 cursor = -1;
1523 cmp_callback = compar;
1524 qsort(collection->items, items, sizeof(collection->items[0]),
1525 collection_cmp);
1526 cmp_callback = NULL;
1528 if (cursor > -1 || wink > -1 || wink_on_map > -1)
1530 int item;
1532 for (item = 0; item < items; item++)
1534 if (collection->items[item].data == cursor_data)
1535 collection_set_cursor_item(collection, item);
1536 if (collection->items[item].data == wink_on_map_data)
1537 collection->wink_on_map = item;
1538 if (collection->items[item].data == wink_data)
1540 collection->cursor_item_old = item;
1541 collection->wink_item = item;
1542 scroll_to_show(collection, item);
1547 gtk_widget_queue_draw(GTK_WIDGET(collection));
1550 /* Find an item in a sorted collection.
1551 * Returns the item number, or -1 if not found.
1553 int collection_find_item(Collection *collection, gpointer data,
1554 int (*compar)(const void *, const void *))
1556 int lower, upper;
1558 g_return_val_if_fail(collection != NULL, -1);
1559 g_return_val_if_fail(IS_COLLECTION(collection), -1);
1560 g_return_val_if_fail(compar != NULL, -1);
1562 /* If item is here, then: lower <= i < upper */
1563 lower = 0;
1564 upper = collection->number_of_items;
1566 while (lower < upper)
1568 int i, cmp;
1570 i = (lower + upper) >> 1;
1572 cmp = compar(collection->items[i].data, data);
1573 if (cmp == 0)
1574 return i;
1576 if (cmp > 0)
1577 upper = i;
1578 else
1579 lower = i + 1;
1582 return -1;
1585 /* Return the number of the item under the point (x,y), or -1 for none.
1586 * This may call your test_point callback. The point is relative to the
1587 * collection's origin.
1589 int collection_get_item(Collection *collection, int x, int y)
1591 int row, col;
1592 int width;
1593 int item;
1595 g_return_val_if_fail(collection != NULL, -1);
1597 col = x / collection->item_width;
1598 row = y / collection->item_height;
1600 if (col >= collection->columns)
1601 col = collection->columns - 1;
1603 if (col < 0 || row < 0)
1604 return -1;
1606 if (col == collection->columns - 1)
1607 width = collection->item_width << 1;
1608 else
1609 width = collection->item_width;
1611 item = col + row * collection->columns;
1612 if (item >= collection->number_of_items)
1613 return -1;
1615 x -= col * collection->item_width;
1616 y -= row * collection->item_height;
1618 if (collection->test_point(collection, x, y,
1619 &collection->items[item], width, collection->item_height,
1620 collection->cb_user_data))
1621 return item;
1623 return -1;
1626 int collection_selected_item_number(Collection *collection)
1628 int i;
1630 g_return_val_if_fail(collection != NULL, -1);
1631 g_return_val_if_fail(IS_COLLECTION(collection), -1);
1632 g_return_val_if_fail(collection->number_selected == 1, -1);
1634 for (i = 0; i < collection->number_of_items; i++)
1635 if (collection->items[i].selected)
1636 return i;
1638 g_warning("collection_selected_item_number: number_selected is wrong");
1640 return -1;
1643 /* Set the cursor/highlight over the given item. Passing -1
1644 * hides the cursor. As a special case, you may set the cursor item
1645 * to zero when there are no items.
1647 void collection_set_cursor_item(Collection *collection, gint item)
1649 int old_item;
1651 g_return_if_fail(collection != NULL);
1652 g_return_if_fail(IS_COLLECTION(collection));
1653 g_return_if_fail(item >= -1 &&
1654 (item < collection->number_of_items || item == 0));
1656 old_item = collection->cursor_item;
1658 if (old_item == item)
1659 return;
1661 collection->cursor_item = item;
1663 if (old_item != -1)
1664 collection_draw_item(collection, old_item, TRUE);
1666 if (item != -1)
1668 collection_draw_item(collection, item, TRUE);
1669 if (collection->auto_scroll == -1)
1670 scroll_to_show(collection, item);
1672 else if (old_item != -1)
1673 collection->cursor_item_old = old_item;
1676 /* Briefly highlight an item to draw the user's attention to it.
1677 * -1 cancels the effect, as does deleting items, sorting the collection
1678 * or starting a new wink effect.
1679 * Otherwise, the effect will cancel itself after a short pause.
1680 * */
1681 void collection_wink_item(Collection *collection, gint item)
1683 g_return_if_fail(collection != NULL);
1684 g_return_if_fail(IS_COLLECTION(collection));
1685 g_return_if_fail(item >= -1 && item < collection->number_of_items);
1687 if (collection->wink_item != -1)
1688 cancel_wink(collection);
1689 if (item == -1)
1690 return;
1692 if (!GTK_WIDGET_MAPPED(GTK_WIDGET(collection)))
1694 collection->wink_on_map = item;
1695 return;
1698 collection->cursor_item_old = collection->wink_item = item;
1699 collection->winks_left = MAX_WINKS;
1701 collection->wink_timeout = gtk_timeout_add(70,
1702 (GtkFunction) wink_timeout,
1703 collection);
1704 scroll_to_show(collection, item);
1705 invert_wink(collection);
1707 gdk_flush();
1710 /* Call test(item, data) on each item in the collection.
1711 * Remove all items for which it returns TRUE. test() should
1712 * free the data before returning TRUE. The collection is in an
1713 * inconsistant state during this call (ie, when test() is called).
1715 * If test is NULL, remove all items.
1717 void collection_delete_if(Collection *collection,
1718 gboolean (*test)(gpointer item, gpointer data),
1719 gpointer data)
1721 int in, out = 0;
1722 int selected = 0;
1723 int cursor;
1725 g_return_if_fail(collection != NULL);
1726 g_return_if_fail(IS_COLLECTION(collection));
1728 cursor = collection->cursor_item;
1730 for (in = 0; in < collection->number_of_items; in++)
1732 if (test && !test(collection->items[in].data, data))
1734 /* Keep item */
1735 if (collection->items[in].selected)
1737 collection->items[out].selected = TRUE;
1738 selected++;
1740 else
1741 collection->items[out].selected = FALSE;
1743 collection->items[out].data =
1744 collection->items[in].data;
1745 collection->items[out].view_data =
1746 collection->items[in].view_data;
1747 out++;
1749 else
1751 /* Remove item */
1752 if (collection->free_item)
1753 collection->free_item(collection,
1754 &collection->items[in]);
1756 if (cursor >= in)
1757 cursor--;
1761 if (in != out)
1763 collection->cursor_item = cursor;
1765 if (collection->wink_item != -1)
1767 collection->wink_item = -1;
1768 gtk_timeout_remove(collection->wink_timeout);
1771 collection->number_of_items = out;
1772 if (collection->number_selected && !selected)
1774 /* We've lost all the selected items */
1775 g_signal_emit(collection,
1776 collection_signals[LOSE_SELECTION], 0,
1777 current_event_time);
1780 collection->number_selected = selected;
1781 resize_arrays(collection,
1782 MAX(collection->number_of_items, MINIMUM_ITEMS));
1784 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection)))
1785 gtk_widget_queue_draw(GTK_WIDGET(collection));
1787 gtk_widget_queue_resize(GTK_WIDGET(collection));
1791 /* Move the cursor by the given row and column offsets.
1792 * Moving by (0,0) can be used to simply make the cursor appear.
1794 void collection_move_cursor(Collection *collection, int drow, int dcol)
1796 int row, col, item;
1797 int first, last, total_rows;
1799 g_return_if_fail(collection != NULL);
1800 g_return_if_fail(IS_COLLECTION(collection));
1802 get_visible_limits(collection, &first, &last);
1804 item = collection->cursor_item;
1805 if (item == -1)
1807 item = MIN(collection->cursor_item_old,
1808 collection->number_of_items - 1);
1811 if (item == -1)
1813 col = 0;
1814 row = first;
1816 else
1818 row = item / collection->columns;
1819 col = item % collection->columns + dcol;
1821 if (row < first)
1822 row = first;
1823 else if (row > last)
1824 row = last;
1825 else
1826 row = MAX(row + drow, 0);
1829 total_rows = (collection->number_of_items + collection->columns - 1)
1830 / collection->columns;
1832 if (row >= total_rows - 1 && drow > 0)
1834 row = total_rows - 1;
1835 item = col + row * collection->columns;
1836 if (item >= collection->number_of_items - 1)
1838 collection_set_cursor_item(collection,
1839 collection->number_of_items - 1);
1840 return;
1843 if (row < 0)
1844 row = 0;
1846 item = col + row * collection->columns;
1848 if (item >= 0 && item < collection->number_of_items)
1849 collection_set_cursor_item(collection, item);
1852 /* When autoscroll is on, a timer keeps track of the pointer position.
1853 * While it's near the top or bottom of the window, the window scrolls.
1855 * If the mouse buttons are released, or the pointer leaves the window,
1856 * auto_scroll is turned off.
1858 void collection_set_autoscroll(Collection *collection, gboolean auto_scroll)
1860 g_return_if_fail(collection != NULL);
1861 g_return_if_fail(IS_COLLECTION(collection));
1863 if (auto_scroll)
1865 if (collection->auto_scroll != -1)
1866 return; /* Already on! */
1868 collection->auto_scroll = gtk_timeout_add(50,
1869 (GtkFunction) as_timeout,
1870 collection);
1872 else
1874 if (collection->auto_scroll == -1)
1875 return; /* Already off! */
1877 gtk_timeout_remove(collection->auto_scroll);
1878 collection->auto_scroll = -1;
1882 /* Start a lasso box drag */
1883 void collection_lasso_box(Collection *collection, int x, int y)
1885 collection->drag_box_x[0] = x;
1886 collection->drag_box_y[0] = y;
1887 collection->drag_box_x[1] = x;
1888 collection->drag_box_y[1] = y;
1890 collection_set_autoscroll(collection, TRUE);
1891 add_lasso_box(collection);
1894 /* Remove the lasso box. Applies fn to each item inside the box.
1895 * fn may be GDK_INVERT, GDK_SET, GDK_NOOP or GDK_CLEAR.
1897 void collection_end_lasso(Collection *collection, GdkFunction fn)
1899 if (fn != GDK_CLEAR)
1901 GdkRectangle region;
1903 find_lasso_area(collection, &region);
1905 collection_process_area(collection, &region, fn,
1906 GDK_CURRENT_TIME);
1909 abort_lasso(collection);
1912 /* Unblock the selection_changed signal, emitting the signal if the
1913 * block counter reaches zero and emit is TRUE.
1915 void collection_unblock_selection_changed(Collection *collection,
1916 guint time,
1917 gboolean emit)
1919 g_return_if_fail(collection != NULL);
1920 g_return_if_fail(IS_COLLECTION(collection));
1921 g_return_if_fail(collection->block_selection_changed > 0);
1923 collection->block_selection_changed--;
1925 if (emit && !collection->block_selection_changed)
1926 g_signal_emit(collection,
1927 collection_signals[SELECTION_CHANGED], 0, time);