r1286: Removed all old Gtk+-1.2 support.
[rox-filer.git] / ROX-Filer / src / collection.c
blobaed7725f657cd47632755a3a2b5700ed231105e7
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 gtk_signal_emit(GTK_OBJECT(collection), \
48 collection_signals[SELECTION_CHANGED], time)
50 enum
52 ARG_0,
53 ARG_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 clear_area(Collection *collection, GdkRectangle *area);
89 static void draw_one_item(Collection *collection,
90 int item,
91 GdkRectangle *area);
92 static void collection_class_init(CollectionClass *class);
93 static void collection_init(Collection *object);
94 static void collection_destroy(GtkObject *object);
95 static void collection_finalize(GtkObject *object);
96 static void collection_realize(GtkWidget *widget);
97 static void collection_map(GtkWidget *widget);
98 static gint collection_paint(Collection *collection,
99 GdkRectangle *area);
100 static void collection_size_request(GtkWidget *widget,
101 GtkRequisition *requisition);
102 static void collection_size_allocate(GtkWidget *widget,
103 GtkAllocation *allocation);
104 static void collection_set_adjustment(Collection *collection,
105 GtkAdjustment *vadj);
106 static void collection_set_arg( GtkObject *object,
107 GtkArg *arg,
108 guint arg_id);
109 static void collection_get_arg( GtkObject *object,
110 GtkArg *arg,
111 guint arg_id);
112 static gint collection_expose(GtkWidget *widget, GdkEventExpose *event);
113 static void default_draw_item(GtkWidget *widget,
114 CollectionItem *data,
115 GdkRectangle *area,
116 gpointer user_data);
117 static gboolean default_test_point(Collection *collection,
118 int point_x, int point_y,
119 CollectionItem *data,
120 int width, int height,
121 gpointer user_data);
122 static gint collection_motion_notify(GtkWidget *widget,
123 GdkEventMotion *event);
124 static void add_lasso_box(Collection *collection);
125 static void abort_lasso(Collection *collection);
126 static void remove_lasso_box(Collection *collection);
127 static void draw_lasso_box(Collection *collection);
128 static void cancel_wink(Collection *collection);
129 static gint collection_key_press(GtkWidget *widget, GdkEventKey *event);
130 static void get_visible_limits(Collection *collection, int *first, int *last);
131 static void scroll_to_show(Collection *collection, int item);
132 static void collection_item_set_selected(Collection *collection,
133 gint item,
134 gboolean selected,
135 gboolean signal);
136 static gint collection_scroll_event(GtkWidget *widget, GdkEventScroll *event);
138 static void draw_focus_at(Collection *collection, GdkRectangle *area)
140 GtkWidget *widget;
141 GdkGC *gc;
143 widget = GTK_WIDGET(collection);
145 if (GTK_WIDGET_FLAGS(widget) & GTK_HAS_FOCUS)
146 gc = widget->style->fg_gc[GTK_STATE_ACTIVE];
147 else
148 gc = widget->style->fg_gc[GTK_STATE_INSENSITIVE];
150 gdk_draw_rectangle(widget->window, gc, FALSE,
151 area->x, area->y,
152 collection->item_width - 1,
153 area->height - 1);
156 static void draw_one_item(Collection *collection, int item, GdkRectangle *area)
158 if (item < collection->number_of_items)
160 collection->draw_item((GtkWidget *) collection,
161 &collection->items[item],
162 area,
163 collection->cb_user_data);
166 if (item == collection->cursor_item)
167 draw_focus_at(collection, area);
170 GtkType collection_get_type(void)
172 static guint my_type = 0;
174 if (!my_type)
176 static const GtkTypeInfo my_info =
178 "Collection",
179 sizeof(Collection),
180 sizeof(CollectionClass),
181 (GtkClassInitFunc) collection_class_init,
182 (GtkObjectInitFunc) collection_init,
183 NULL, /* Reserved 1 */
184 NULL, /* Reserved 2 */
185 (GtkClassInitFunc) NULL /* base_class_init_func */
188 my_type = gtk_type_unique(gtk_widget_get_type(),
189 &my_info);
192 return my_type;
195 typedef void (*FinalizeFn)(GObject *object);
197 static void collection_class_init(CollectionClass *class)
199 GtkObjectClass *object_class;
200 GtkWidgetClass *widget_class;
201 GtkType type;
203 object_class = (GtkObjectClass*) class;
204 widget_class = (GtkWidgetClass*) class;
206 parent_class = gtk_type_class(gtk_widget_get_type());
208 gtk_object_add_arg_type("Collection::vadjustment",
209 GTK_TYPE_ADJUSTMENT,
210 GTK_ARG_READWRITE | GTK_ARG_CONSTRUCT,
211 ARG_VADJUSTMENT);
213 object_class->destroy = collection_destroy;
214 G_OBJECT_CLASS(object_class)->finalize =
215 (FinalizeFn) collection_finalize;
216 type = GTK_CLASS_TYPE(object_class);
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;
229 #if 0
230 widget_class->style_set = collection_set_style; /* XXX: Test for 2.0 */
231 #endif
233 object_class->set_arg = collection_set_arg;
234 object_class->get_arg = collection_get_arg;
236 class->gain_selection = NULL;
237 class->lose_selection = NULL;
238 class->selection_changed = NULL;
240 /* XXX - do these do anything? */
241 collection_signals[GAIN_SELECTION] = gtk_signal_new("gain_selection",
242 GTK_RUN_LAST,
243 type,
244 GTK_SIGNAL_OFFSET(CollectionClass,
245 gain_selection),
246 gtk_marshal_NONE__INT,
247 GTK_TYPE_NONE, 1,
248 GTK_TYPE_INT);
249 collection_signals[LOSE_SELECTION] = gtk_signal_new("lose_selection",
250 GTK_RUN_LAST,
251 type,
252 GTK_SIGNAL_OFFSET(CollectionClass,
253 lose_selection),
254 gtk_marshal_NONE__INT,
255 GTK_TYPE_NONE, 1,
256 GTK_TYPE_INT);
257 collection_signals[SELECTION_CHANGED] = gtk_signal_new(
258 "selection_changed",
259 GTK_RUN_LAST,
260 type,
261 GTK_SIGNAL_OFFSET(CollectionClass,
262 selection_changed),
263 gtk_marshal_NONE__INT,
264 GTK_TYPE_NONE, 1,
265 GTK_TYPE_INT);
268 static void collection_init(Collection *object)
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;
282 object->bg_gc = NULL;
284 object->items = g_new(CollectionItem, MINIMUM_ITEMS);
285 object->cursor_item = -1;
286 object->cursor_item_old = -1;
287 object->wink_item = -1;
288 object->wink_on_map = -1;
289 object->array_size = MINIMUM_ITEMS;
290 object->draw_item = default_draw_item;
291 object->test_point = default_test_point;
292 object->free_item = NULL;
294 object->auto_scroll = -1;
296 return;
299 GtkWidget* collection_new(void)
301 return GTK_WIDGET(gtk_widget_new(collection_get_type(), NULL));
304 /* Note: The draw_item call gives the maximum area that can be
305 * drawn to. For the column on the far right, this extends to the
306 * edge of the window. Normally, use collection->item_width instead
307 * of area->width to calculate the position.
309 * test_point does not use a larger value for the width, but the
310 * x point of the click may be larger than the width.
312 void collection_set_functions(Collection *collection,
313 CollectionDrawFunc draw_item,
314 CollectionTestFunc test_point,
315 gpointer user_data)
317 GtkWidget *widget;
319 g_return_if_fail(collection != NULL);
320 g_return_if_fail(IS_COLLECTION(collection));
322 widget = GTK_WIDGET(collection);
324 if (!draw_item)
325 draw_item = default_draw_item;
326 if (!test_point)
327 test_point = default_test_point;
329 collection->draw_item = draw_item;
330 collection->test_point = test_point;
331 collection->cb_user_data = user_data;
333 if (GTK_WIDGET_REALIZED(widget))
334 gtk_widget_queue_clear(widget);
337 /* After this we are unusable, but our data (if any) is still hanging around.
338 * It will be freed later with finalize.
340 static void collection_destroy(GtkObject *object)
342 Collection *collection;
344 g_return_if_fail(object != NULL);
345 g_return_if_fail(IS_COLLECTION(object));
347 collection = COLLECTION(object);
349 collection_clear(collection);
351 if (collection->auto_scroll != -1)
353 gtk_timeout_remove(collection->auto_scroll);
354 collection->auto_scroll = -1;
357 if (collection->bg_gc)
359 gdk_gc_destroy(collection->bg_gc);
360 collection->bg_gc = NULL;
363 if (collection->vadj)
365 gtk_object_unref(GTK_OBJECT(collection->vadj));
366 collection->vadj = NULL;
369 if (GTK_OBJECT_CLASS(parent_class)->destroy)
370 (*GTK_OBJECT_CLASS(parent_class)->destroy)(object);
373 /* This is the last thing that happens to us. Free all data. */
374 static void collection_finalize(GtkObject *object)
376 Collection *collection;
378 collection = COLLECTION(object);
380 g_return_if_fail(collection->number_of_items == 0);
382 g_free(collection->items);
385 static void collection_map(GtkWidget *widget)
387 Collection *collection = COLLECTION(widget);
389 if (GTK_WIDGET_CLASS(parent_class)->map)
390 (*GTK_WIDGET_CLASS(parent_class)->map)(widget);
392 if (collection->wink_on_map >= 0)
394 collection_wink_item(collection, collection->wink_on_map);
395 collection->wink_on_map = -1;
399 static GdkGC *create_bg_gc(GtkWidget *widget)
401 GdkGCValues values;
403 values.tile = widget->style->bg_pixmap[GTK_STATE_NORMAL];
404 values.fill = GDK_TILED;
406 return gdk_gc_new_with_values(widget->window, &values,
407 GDK_GC_FILL | GDK_GC_TILE);
410 static void collection_realize(GtkWidget *widget)
412 Collection *collection;
413 GdkWindowAttr attributes;
414 gint attributes_mask;
415 GdkGCValues xor_values;
416 GdkColor *bg, *fg;
418 g_return_if_fail(widget != NULL);
419 g_return_if_fail(IS_COLLECTION(widget));
420 g_return_if_fail(widget->parent != NULL);
422 GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);
423 collection = COLLECTION(widget);
425 attributes.x = widget->allocation.x;
426 attributes.y = widget->allocation.y;
427 attributes.width = widget->allocation.width;
428 attributes.height = widget->allocation.height;
429 attributes.wclass = GDK_INPUT_OUTPUT;
430 attributes.window_type = GDK_WINDOW_CHILD;
431 attributes.event_mask = gtk_widget_get_events(widget) |
432 GDK_EXPOSURE_MASK |
433 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
434 GDK_BUTTON1_MOTION_MASK | GDK_BUTTON2_MOTION_MASK |
435 GDK_BUTTON3_MOTION_MASK;
436 attributes.visual = gtk_widget_get_visual(widget);
437 attributes.colormap = gtk_widget_get_colormap(widget);
439 attributes_mask = GDK_WA_X | GDK_WA_Y |
440 GDK_WA_VISUAL | GDK_WA_COLORMAP;
441 widget->window = gdk_window_new(gtk_widget_get_parent_window(widget),
442 &attributes, attributes_mask);
444 widget->style = gtk_style_attach(widget->style, widget->window);
446 gdk_window_set_user_data(widget->window, widget);
448 gdk_window_set_background(widget->window,
449 &widget->style->bg[GTK_STATE_NORMAL]);
450 if (widget->style->bg_pixmap[GTK_STATE_NORMAL])
451 collection->bg_gc = create_bg_gc(widget);
453 bg = &widget->style->bg[GTK_STATE_NORMAL];
454 fg = &widget->style->fg[GTK_STATE_NORMAL];
455 xor_values.function = GDK_XOR;
456 xor_values.foreground.pixel = fg->pixel ^ bg->pixel;
457 collection->xor_gc = gdk_gc_new_with_values(widget->window,
458 &xor_values,
459 GDK_GC_FOREGROUND
460 | GDK_GC_FUNCTION);
463 static void collection_size_request(GtkWidget *widget,
464 GtkRequisition *requisition)
466 Collection *collection = COLLECTION(widget);
467 int rows, cols = collection->columns;
469 /* We ask for the total size we need; our containing viewport
470 * will deal with scrolling.
472 requisition->width = MIN_WIDTH;
473 rows = (collection->number_of_items + cols - 1) / cols;
474 requisition->height = rows * collection->item_height;
477 static gboolean scroll_after_alloc(Collection *collection)
479 if (collection->wink_item != -1)
480 scroll_to_show(collection, collection->wink_item);
481 else if (collection->cursor_item != -1)
482 scroll_to_show(collection, collection->cursor_item);
483 g_object_unref(G_OBJECT(collection));
485 return FALSE;
488 static void collection_size_allocate(GtkWidget *widget,
489 GtkAllocation *allocation)
491 Collection *collection;
492 int old_columns;
493 gboolean cursor_visible = FALSE;
495 g_return_if_fail(widget != NULL);
496 g_return_if_fail(IS_COLLECTION(widget));
497 g_return_if_fail(allocation != NULL);
499 collection = COLLECTION(widget);
501 if (collection->cursor_item != -1)
503 int first, last;
504 int crow = collection->cursor_item / collection->columns;
506 get_visible_limits(collection, &first, &last);
508 cursor_visible = crow >= first && crow <= last;
511 old_columns = collection->columns;
513 widget->allocation = *allocation;
515 collection->columns = allocation->width / collection->item_width;
516 if (collection->columns < 1)
517 collection->columns = 1;
519 if (GTK_WIDGET_REALIZED(widget))
521 gdk_window_move_resize(widget->window,
522 allocation->x, allocation->y,
523 allocation->width, allocation->height);
525 if (cursor_visible)
526 scroll_to_show(collection, collection->cursor_item);
529 if (old_columns != collection->columns)
531 /* Need to go around again... */
532 gtk_widget_queue_resize(widget);
534 else if (collection->wink_item != -1 || collection->cursor_item != -1)
536 /* Viewport resets the adjustments after the alloc */
537 g_object_ref(G_OBJECT(collection));
538 g_idle_add((GSourceFunc) scroll_after_alloc, collection);
542 #if 0
543 static void collection_set_style(GtkWidget *widget,
544 GtkStyle *previous_style)
546 Collection *collection;
548 g_return_if_fail(IS_COLLECTION(widget));
550 collection = COLLECTION(widget);
552 collection->paint_level = PAINT_CLEAR;
554 if (GTK_WIDGET_REALIZED(widget))
556 gdk_window_set_background(widget->window,
557 &widget->style->bg[GTK_STATE_NORMAL]);
559 if (collection->bg_gc)
561 gdk_gc_destroy(collection->bg_gc);
562 collection->bg_gc = NULL;
565 if (widget->style->bg_pixmap[GTK_STATE_NORMAL])
566 collection->bg_gc = create_bg_gc(widget);
569 #endif
571 static void clear_area(Collection *collection, GdkRectangle *area)
573 GtkWidget *widget = GTK_WIDGET(collection);
575 if (collection->bg_gc)
577 gdk_gc_set_ts_origin(collection->bg_gc, 0, 0);
579 gdk_draw_rectangle(widget->window,
580 collection->bg_gc,
581 TRUE,
582 area->x, area->y,
583 area->width, area->height);
585 else
587 gdk_window_set_background(widget->window,
588 &widget->style->bg[GTK_STATE_NORMAL]);
589 gdk_window_clear_area(widget->window,
590 area->x, area->y, area->width, area->height);
594 /* Return the area occupied by the item at (row, col) by filling
595 * in 'area'.
597 static void collection_get_item_area(Collection *collection,
598 int row, int col,
599 GdkRectangle *area)
602 area->x = col * collection->item_width;
603 area->y = row * collection->item_height;
605 area->width = collection->item_width;
606 area->height = collection->item_height;
607 if (col == collection->columns - 1)
608 area->width <<= 1;
611 static gint collection_paint(Collection *collection,
612 GdkRectangle *area)
614 GdkRectangle item_area;
615 int row, col;
616 int item;
617 int start_row, last_row;
618 int start_col, last_col;
619 int phys_last_col;
620 GdkRectangle clip;
622 /* Calculate the ranges to plot */
623 start_row = area->y / collection->item_height;
624 last_row = (area->y + area->height - 1) / collection->item_height;
625 row = start_row;
627 start_col = area->x / collection->item_width;
628 phys_last_col = (area->x + area->width - 1) / collection->item_width;
630 if (collection->lasso_box)
632 /* You can't be too careful with lasso boxes...
634 * clip gives the total area drawn over (this may be larger
635 * than the requested area). It's used to redraw the lasso
636 * box.
638 collection_get_item_area(collection,
639 start_row, start_col, &clip);
640 clip.width *= phys_last_col - start_col + 1;
641 clip.height *= last_row - start_row + 1;
643 clear_area(collection, &clip);
646 /* The right-most column may be wider than the others.
647 * Therefore, to redraw the area after the last 'real' column
648 * we may have to draw the right-most column.
650 if (start_col >= collection->columns)
651 start_col = collection->columns - 1;
653 if (phys_last_col >= collection->columns)
654 last_col = collection->columns - 1;
655 else
656 last_col = phys_last_col;
658 col = start_col;
660 item = row * collection->columns + col;
662 /* g_print("[ paint %d..%d ]\n", row, last_row); */
663 while ((item == 0 || item < collection->number_of_items)
664 && row <= last_row)
666 collection_get_item_area(collection, row, col, &item_area);
668 draw_one_item(collection, item, &item_area);
669 col++;
671 if (col > last_col)
673 col = start_col;
674 row++;
675 item = row * collection->columns + col;
677 else
678 item++;
681 if (collection->lasso_box)
683 gdk_gc_set_clip_rectangle(collection->xor_gc, &clip);
684 draw_lasso_box(collection);
685 gdk_gc_set_clip_rectangle(collection->xor_gc, NULL);
688 return FALSE;
691 static void default_draw_item( GtkWidget *widget,
692 CollectionItem *item,
693 GdkRectangle *area,
694 gpointer user_data)
696 gdk_draw_arc(widget->window,
697 item->selected ? widget->style->white_gc
698 : widget->style->black_gc,
699 TRUE,
700 area->x, area->y,
701 COLLECTION(widget)->item_width, area->height,
702 0, 360 * 64);
706 static gboolean default_test_point(Collection *collection,
707 int point_x, int point_y,
708 CollectionItem *item,
709 int width, int height,
710 gpointer user_data)
712 float f_x, f_y;
714 /* Convert to point in unit circle */
715 f_x = ((float) point_x / width) - 0.5;
716 f_y = ((float) point_y / height) - 0.5;
718 return (f_x * f_x) + (f_y * f_y) <= .25;
721 static void collection_set_arg( GtkObject *object,
722 GtkArg *arg,
723 guint arg_id)
725 Collection *collection;
727 collection = COLLECTION(object);
729 switch (arg_id)
731 case ARG_VADJUSTMENT:
732 collection_set_adjustment(collection,
733 GTK_VALUE_POINTER(*arg));
734 break;
735 default:
736 break;
740 static void collection_set_adjustment(Collection *collection,
741 GtkAdjustment *vadj)
743 if (vadj)
744 g_return_if_fail (GTK_IS_ADJUSTMENT (vadj));
745 else
746 vadj = GTK_ADJUSTMENT(gtk_adjustment_new(0.0,
747 0.0, 0.0,
748 0.0, 0.0, 0.0));
749 if (collection->vadj && (collection->vadj != vadj))
750 gtk_object_unref(GTK_OBJECT(collection->vadj));
752 if (collection->vadj != vadj)
754 collection->vadj = vadj;
755 gtk_object_ref(GTK_OBJECT(collection->vadj));
756 gtk_object_sink(GTK_OBJECT(collection->vadj));
760 static void collection_get_arg( GtkObject *object,
761 GtkArg *arg,
762 guint arg_id)
764 Collection *collection;
766 collection = COLLECTION(object);
768 switch (arg_id)
770 case ARG_VADJUSTMENT:
771 GTK_VALUE_POINTER(*arg) = collection->vadj;
772 break;
773 default:
774 arg->type = GTK_TYPE_INVALID;
775 break;
779 /* Change the adjustment by this amount. Bounded. */
780 static void diff_vpos(Collection *collection, int diff)
782 int value = collection->vadj->value + diff;
784 value = CLAMP(value, 0,
785 collection->vadj->upper - collection->vadj->page_size);
786 gtk_adjustment_set_value(collection->vadj, value);
789 static gint collection_expose(GtkWidget *widget, GdkEventExpose *event)
791 Collection *collection;
793 g_return_val_if_fail(widget != NULL, FALSE);
794 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
795 g_return_val_if_fail(event != NULL, FALSE);
797 collection = COLLECTION(widget);
799 gtk_paint_flat_box(widget->style, widget->window,
800 widget->state, GTK_SHADOW_NONE,
801 &event->area, widget, "collection",
802 0, 0, -1, -1);
804 collection_paint(collection, &event->area);
806 return FALSE;
809 static void resize_arrays(Collection *collection, guint new_size)
811 g_return_if_fail(collection != NULL);
812 g_return_if_fail(IS_COLLECTION(collection));
813 g_return_if_fail(new_size >= collection->number_of_items);
815 collection->items = g_realloc(collection->items,
816 sizeof(CollectionItem) * new_size);
817 collection->array_size = new_size;
820 static gint collection_key_press(GtkWidget *widget, GdkEventKey *event)
822 Collection *collection;
823 int item;
824 int key;
826 g_return_val_if_fail(widget != NULL, FALSE);
827 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
828 g_return_val_if_fail(event != NULL, FALSE);
830 collection = (Collection *) widget;
831 item = collection->cursor_item;
833 key = event->keyval;
834 if (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK))
836 if (key == GDK_Left || key == GDK_Right || \
837 key == GDK_Up || key == GDK_Down)
838 return TRUE;
839 return FALSE;
842 switch (key)
844 case GDK_Left:
845 collection_move_cursor(collection, 0, -1);
846 break;
847 case GDK_Right:
848 collection_move_cursor(collection, 0, 1);
849 break;
850 case GDK_Up:
851 collection_move_cursor(collection, -1, 0);
852 break;
853 case GDK_Down:
854 collection_move_cursor(collection, 1, 0);
855 break;
856 case GDK_Home:
857 collection_set_cursor_item(collection, 0);
858 break;
859 case GDK_End:
860 collection_set_cursor_item(collection,
861 MAX((gint) collection->number_of_items - 1, 0));
862 break;
863 case GDK_Page_Up:
865 int first, last;
866 get_visible_limits(collection, &first, &last);
867 collection_move_cursor(collection, first - last - 1, 0);
868 break;
870 case GDK_Page_Down:
872 int first, last;
873 get_visible_limits(collection, &first, &last);
874 collection_move_cursor(collection, last - first + 1, 0);
875 break;
877 case GDK_Escape:
878 collection_set_cursor_item(collection, -1);
879 collection_clear_selection(collection);
880 return FALSE; /* Pass it on */
881 case ' ':
882 if (item >=0 && item < collection->number_of_items)
884 collection_toggle_item(collection, item);
885 if (item < collection->number_of_items - 1)
886 collection_set_cursor_item(collection,
887 item + 1);
889 break;
890 default:
891 return FALSE;
894 return TRUE;
897 /* Wheel mouse scrolling */
898 static gint collection_scroll_event(GtkWidget *widget, GdkEventScroll *event)
900 Collection *collection;
901 int diff = 0;
903 g_return_val_if_fail(widget != NULL, FALSE);
904 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
905 g_return_val_if_fail(event != NULL, FALSE);
907 collection = COLLECTION(widget);
909 if (event->direction == GDK_SCROLL_UP)
910 diff = -1;
911 else if (event->direction == GDK_SCROLL_DOWN)
912 diff = 1;
913 else
914 return FALSE;
916 if (diff)
918 int old_value = collection->vadj->value;
919 int new_value = 0;
920 gboolean box = collection->lasso_box;
921 int step = collection->vadj->page_increment / 2;
923 new_value = CLAMP(old_value + diff * step, 0.0,
924 collection->vadj->upper
925 - collection->vadj->page_size);
926 diff = new_value - old_value;
927 if (diff)
929 if (box)
931 remove_lasso_box(collection);
932 collection->drag_box_y[0] -= diff;
934 gtk_adjustment_set_value(collection->vadj, new_value);
935 if (box)
936 add_lasso_box(collection);
940 return TRUE;
943 /* 'from' and 'to' are pixel positions. 'step' is the size of each item.
944 * Returns the index of the first item covered, and the number of items.
946 static void get_range(int from, int to, int step, gint *pos, gint *len)
948 int margin = MIN(step / 4, 40);
950 if (from > to)
952 int tmp = to;
953 to = from;
954 from = tmp;
957 from = (from + margin) / step; /* First item */
958 to = (to + step - margin) / step; /* Last item (inclusive) */
960 *pos = MAX(from, 0);
961 *len = to - *pos;
964 /* Fills in the area with a rectangle corresponding to the current
965 * size of the lasso box (units of items, not pixels).
967 * The box will only span valid columns, but the total number
968 * of items is not taken into account (rows or cols).
970 static void find_lasso_area(Collection *collection, GdkRectangle *area)
972 int cols = collection->columns;
973 int dx = collection->drag_box_x[0] - collection->drag_box_x[1];
974 int dy = collection->drag_box_y[0] - collection->drag_box_y[1];
976 if (ABS(dx) < 8 && ABS(dy) < 8)
978 /* Didn't move far enough - ignore */
979 area->x = area->y = 0;
980 area->width = 0;
981 area->height = 0;
982 return;
985 get_range(collection->drag_box_x[0], collection->drag_box_x[1],
986 collection->item_width, &area->x, &area->width);
988 if (area->x >= cols)
989 area->width = 0;
990 else if (area->x + area->width > cols)
991 area->width = cols - area->x;
993 get_range(collection->drag_box_y[0], collection->drag_box_y[1],
994 collection->item_height, &area->y, &area->height);
997 static void collection_process_area(Collection *collection,
998 GdkRectangle *area,
999 GdkFunction fn,
1000 guint32 time)
1002 int x, y;
1003 guint32 stacked_time;
1004 int item;
1005 gboolean changed = FALSE;
1006 guint old_selected;
1008 g_return_if_fail(fn == GDK_SET || fn == GDK_INVERT);
1010 old_selected = collection->number_selected;
1012 stacked_time = current_event_time;
1013 current_event_time = time;
1015 collection->block_selection_changed++;
1017 for (y = area->y; y < area->y + area->height; y++)
1019 item = y * collection->columns + area->x;
1021 for (x = area->x; x < area->x + area->width; x++)
1023 if (item >= collection->number_of_items)
1024 goto out;
1026 if (fn == GDK_INVERT)
1027 collection_item_set_selected(collection, item,
1028 !collection->items[item].selected,
1029 FALSE);
1030 else
1031 collection_item_set_selected(collection, item,
1032 TRUE, FALSE);
1034 changed = TRUE;
1035 item++;
1039 out:
1040 if (collection->number_selected && !old_selected)
1041 gtk_signal_emit(GTK_OBJECT(collection),
1042 collection_signals[GAIN_SELECTION],
1043 current_event_time);
1044 else if (!collection->number_selected && old_selected)
1045 gtk_signal_emit(GTK_OBJECT(collection),
1046 collection_signals[LOSE_SELECTION],
1047 current_event_time);
1049 collection_unblock_selection_changed(collection,
1050 current_event_time, changed);
1051 current_event_time = stacked_time;
1054 static gint collection_motion_notify(GtkWidget *widget,
1055 GdkEventMotion *event)
1057 Collection *collection;
1058 gint x, y;
1060 g_return_val_if_fail(widget != NULL, FALSE);
1061 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1062 g_return_val_if_fail(event != NULL, FALSE);
1064 collection = COLLECTION(widget);
1066 if (!collection->lasso_box)
1067 return FALSE;
1069 if (event->window != widget->window)
1070 gdk_window_get_pointer(widget->window, &x, &y, NULL);
1071 else
1073 x = event->x;
1074 y = event->y;
1077 remove_lasso_box(collection);
1078 collection->drag_box_x[1] = x;
1079 collection->drag_box_y[1] = y;
1080 add_lasso_box(collection);
1081 return TRUE;
1084 static void add_lasso_box(Collection *collection)
1086 g_return_if_fail(collection != NULL);
1087 g_return_if_fail(IS_COLLECTION(collection));
1088 g_return_if_fail(collection->lasso_box == FALSE);
1090 collection->lasso_box = TRUE;
1091 draw_lasso_box(collection);
1094 static void draw_lasso_box(Collection *collection)
1096 GtkWidget *widget;
1097 int x, y, width, height;
1099 widget = GTK_WIDGET(collection);
1101 x = MIN(collection->drag_box_x[0], collection->drag_box_x[1]);
1102 y = MIN(collection->drag_box_y[0], collection->drag_box_y[1]);
1103 width = abs(collection->drag_box_x[1] - collection->drag_box_x[0]);
1104 height = abs(collection->drag_box_y[1] - collection->drag_box_y[0]);
1106 gdk_draw_rectangle(widget->window, collection->xor_gc, FALSE,
1107 x, y, width, height);
1110 static void abort_lasso(Collection *collection)
1112 if (collection->lasso_box)
1114 remove_lasso_box(collection);
1115 collection_set_autoscroll(collection, FALSE);
1119 static void remove_lasso_box(Collection *collection)
1121 g_return_if_fail(collection != NULL);
1122 g_return_if_fail(IS_COLLECTION(collection));
1123 g_return_if_fail(collection->lasso_box == TRUE);
1125 draw_lasso_box(collection);
1127 collection->lasso_box = FALSE;
1129 return;
1132 /* Make sure that 'item' is fully visible (vertically), scrolling if not. */
1133 static void scroll_to_show(Collection *collection, int item)
1135 int first, last, row;
1137 g_return_if_fail(collection != NULL);
1138 g_return_if_fail(IS_COLLECTION(collection));
1140 row = item / collection->columns;
1141 get_visible_limits(collection, &first, &last);
1143 if (row <= first)
1145 gtk_adjustment_set_value(collection->vadj,
1146 row * collection->item_height);
1148 else if (row >= last)
1150 GtkWidget *widget = (GtkWidget *) collection;
1151 gint height;
1153 if (GTK_WIDGET_REALIZED(widget))
1155 height = collection->vadj->page_size;
1156 gtk_adjustment_set_value(collection->vadj,
1157 (row + 1) * collection->item_height - height);
1162 /* Return the first and last rows which are [partly] visible. Does not
1163 * ensure that the rows actually exist (contain items).
1165 static void get_visible_limits(Collection *collection, int *first, int *last)
1167 GtkWidget *widget = (GtkWidget *) collection;
1168 gint scroll = 0, height;
1170 g_return_if_fail(collection != NULL);
1171 g_return_if_fail(IS_COLLECTION(collection));
1172 g_return_if_fail(first != NULL && last != NULL);
1174 if (!GTK_WIDGET_REALIZED(widget))
1176 *first = 0;
1177 *last = 0;
1179 else
1181 scroll = collection->vadj->value;
1182 height = collection->vadj->page_size;
1184 *first = MAX(scroll / collection->item_height, 0);
1185 *last = (scroll + height - 1) /collection->item_height;
1187 if (*last < *first)
1188 *last = *first;
1192 /* Cancel the current wink effect. */
1193 static void cancel_wink(Collection *collection)
1195 gint item;
1197 g_return_if_fail(collection != NULL);
1198 g_return_if_fail(IS_COLLECTION(collection));
1199 g_return_if_fail(collection->wink_item != -1);
1201 item = collection->wink_item;
1203 collection->wink_item = -1;
1204 gtk_timeout_remove(collection->wink_timeout);
1206 collection_draw_item(collection, item, TRUE);
1209 /* Draw/undraw a box around collection->wink_item */
1210 static void invert_wink(Collection *collection)
1212 GdkRectangle area;
1213 gint row, col;
1215 g_return_if_fail(collection->wink_item >= 0);
1217 if (!GTK_WIDGET_REALIZED(GTK_WIDGET(collection)))
1218 return;
1220 col = collection->wink_item % collection->columns;
1221 row = collection->wink_item / collection->columns;
1222 collection_get_item_area(collection, row, col, &area);
1224 gdk_draw_rectangle(((GtkWidget *) collection)->window,
1225 collection->xor_gc, FALSE,
1226 area.x, area.y,
1227 collection->item_width - 1,
1228 area.height - 1);
1231 static gboolean wink_timeout(Collection *collection)
1233 gint item;
1235 g_return_val_if_fail(collection != NULL, FALSE);
1236 g_return_val_if_fail(IS_COLLECTION(collection), FALSE);
1237 g_return_val_if_fail(collection->wink_item != -1, FALSE);
1239 item = collection->wink_item;
1241 if (collection->winks_left-- > 0)
1243 invert_wink(collection);
1244 return TRUE;
1247 collection->wink_item = -1;
1249 collection_draw_item(collection, item, TRUE);
1251 return FALSE;
1254 /* This is called frequently while auto_scroll is on.
1255 * Checks the pointer position and scrolls the window if it's
1256 * near the top or bottom.
1258 static gboolean as_timeout(Collection *collection)
1260 GdkWindow *window = GTK_WIDGET(collection)->window;
1261 gint x, y, w, h;
1262 GdkModifierType mask;
1263 int diff = 0;
1265 gdk_window_get_pointer(window, &x, &y, &mask);
1266 gdk_window_get_size(window, &w, NULL);
1268 h = collection->vadj->page_size;
1269 y -= collection->vadj->value;
1271 if ((x < 0 || x > w || y < 0 || y > h) && !collection->lasso_box)
1273 collection->auto_scroll = -1;
1274 return FALSE; /* Out of window - stop */
1277 if (y < AUTOSCROLL_STEP)
1278 diff = y - AUTOSCROLL_STEP;
1279 else if (y > h - AUTOSCROLL_STEP)
1280 diff = AUTOSCROLL_STEP + y - h;
1282 if (diff)
1283 diff_vpos(collection, diff);
1285 return TRUE;
1288 /* Change the selected state of an item.
1289 * Send GAIN/LOSE signals if 'signal' is TRUE.
1290 * Send SELECTION_CHANGED unless blocked.
1291 * Updates number_selected and redraws the item.
1293 static void collection_item_set_selected(Collection *collection,
1294 gint item,
1295 gboolean selected,
1296 gboolean signal)
1298 g_return_if_fail(collection != NULL);
1299 g_return_if_fail(IS_COLLECTION(collection));
1300 g_return_if_fail(item >= 0 && item < collection->number_of_items);
1302 if (collection->items[item].selected == selected)
1303 return;
1305 collection->items[item].selected = selected;
1306 collection_draw_item(collection, item, TRUE);
1308 if (selected)
1310 collection->number_selected++;
1311 if (signal && collection->number_selected == 1)
1312 gtk_signal_emit(GTK_OBJECT(collection),
1313 collection_signals[GAIN_SELECTION],
1314 current_event_time);
1316 else
1318 collection->number_selected--;
1319 if (signal && collection->number_selected == 0)
1320 gtk_signal_emit(GTK_OBJECT(collection),
1321 collection_signals[LOSE_SELECTION],
1322 current_event_time);
1325 EMIT_SELECTION_CHANGED(collection, current_event_time);
1328 /* Functions for managing collections */
1330 /* Remove all objects from the collection */
1331 void collection_clear(Collection *collection)
1333 collection_delete_if(collection, NULL, NULL);
1336 /* Inserts a new item at the end. The new item is unselected, and its
1337 * number is returned.
1339 gint collection_insert(Collection *collection, gpointer data, gpointer view)
1341 int item;
1343 g_return_val_if_fail(IS_COLLECTION(collection), -1);
1345 item = collection->number_of_items;
1347 if (item >= collection->array_size)
1348 resize_arrays(collection, item + (item >> 1));
1350 collection->items[item].data = data;
1351 collection->items[item].view_data = view;
1352 collection->items[item].selected = FALSE;
1354 collection->number_of_items++;
1356 gtk_widget_queue_resize(GTK_WIDGET(collection));
1358 collection_draw_item(collection, item, FALSE);
1360 return item;
1363 void collection_unselect_item(Collection *collection, gint item)
1365 collection_item_set_selected(collection, item, FALSE, TRUE);
1368 void collection_select_item(Collection *collection, gint item)
1370 collection_item_set_selected(collection, item, TRUE, TRUE);
1373 void collection_toggle_item(Collection *collection, gint item)
1375 collection_item_set_selected(collection, item,
1376 !collection->items[item].selected, TRUE);
1379 /* Select all items in the collection */
1380 void collection_select_all(Collection *collection)
1382 GtkWidget *widget;
1383 int item = 0;
1385 g_return_if_fail(collection != NULL);
1386 g_return_if_fail(IS_COLLECTION(collection));
1388 widget = GTK_WIDGET(collection);
1390 if (collection->number_selected == collection->number_of_items)
1391 return; /* Nothing to do */
1393 while (collection->number_selected < collection->number_of_items)
1395 while (collection->items[item].selected)
1396 item++;
1398 collection->items[item].selected = TRUE;
1399 collection_draw_item(collection, item, TRUE);
1400 item++;
1402 collection->number_selected++;
1405 gtk_signal_emit(GTK_OBJECT(collection),
1406 collection_signals[GAIN_SELECTION],
1407 current_event_time);
1408 EMIT_SELECTION_CHANGED(collection, current_event_time);
1411 /* Toggle all items in the collection */
1412 void collection_invert_selection(Collection *collection)
1414 int item;
1416 g_return_if_fail(collection != NULL);
1417 g_return_if_fail(IS_COLLECTION(collection));
1419 if (collection->number_selected == 0)
1421 collection_select_all(collection);
1422 return;
1424 else if (collection->number_of_items == collection->number_selected)
1426 collection_clear_selection(collection);
1427 return;
1430 for (item = 0; item < collection->number_of_items; item++)
1431 collection->items[item].selected =
1432 !collection->items[item].selected;
1434 collection->number_selected = collection->number_of_items -
1435 collection->number_selected;
1437 /* Have to redraw everything... */
1438 gtk_widget_queue_clear(GTK_WIDGET(collection));
1440 EMIT_SELECTION_CHANGED(collection, current_event_time);
1443 /* Unselect all items except number item, which is selected (-1 to unselect
1444 * everything).
1446 void collection_clear_except(Collection *collection, gint item)
1448 GtkWidget *widget;
1449 int i = 0;
1450 int end; /* Selected items to end up with */
1452 g_return_if_fail(collection != NULL);
1453 g_return_if_fail(IS_COLLECTION(collection));
1454 g_return_if_fail(item >= -1 && item < collection->number_of_items);
1456 widget = GTK_WIDGET(collection);
1458 if (item == -1)
1459 end = 0;
1460 else
1462 collection_select_item(collection, item);
1463 end = 1;
1466 if (collection->number_selected == 0)
1467 return;
1469 while (collection->number_selected > end)
1471 while (i == item || !collection->items[i].selected)
1472 i++;
1474 collection->items[i].selected = FALSE;
1475 collection_draw_item(collection, i, TRUE);
1476 i++;
1478 collection->number_selected--;
1481 if (end == 0)
1482 gtk_signal_emit(GTK_OBJECT(collection),
1483 collection_signals[LOSE_SELECTION],
1484 current_event_time);
1485 EMIT_SELECTION_CHANGED(collection, current_event_time);
1488 /* Unselect all items in the collection */
1489 void collection_clear_selection(Collection *collection)
1491 g_return_if_fail(collection != NULL);
1492 g_return_if_fail(IS_COLLECTION(collection));
1494 collection_clear_except(collection, -1);
1497 /* Force a redraw of the specified item, if it is visible */
1498 void collection_draw_item(Collection *collection, gint item, gboolean blank)
1500 GdkRectangle area;
1501 GtkWidget *widget;
1502 int row, col;
1504 g_return_if_fail(collection != NULL);
1505 g_return_if_fail(IS_COLLECTION(collection));
1506 g_return_if_fail(item >= 0 &&
1507 (item == 0 || item < collection->number_of_items));
1509 widget = GTK_WIDGET(collection);
1510 if (!GTK_WIDGET_REALIZED(widget))
1511 return;
1513 col = item % collection->columns;
1514 row = item / collection->columns;
1516 collection_get_item_area(collection, row, col, &area);
1518 gdk_window_invalidate_rect(widget->window, &area, FALSE);
1521 void collection_set_item_size(Collection *collection, int width, int height)
1523 GtkWidget *widget;
1525 g_return_if_fail(collection != NULL);
1526 g_return_if_fail(IS_COLLECTION(collection));
1527 g_return_if_fail(width > 4 && height > 4);
1529 if (collection->item_width == width &&
1530 collection->item_height == height)
1531 return;
1533 widget = GTK_WIDGET(collection);
1535 collection->item_width = width;
1536 collection->item_height = height;
1538 if (GTK_WIDGET_REALIZED(widget))
1540 gint window_width;
1542 gdk_window_get_size(widget->window, &window_width, NULL);
1543 collection->columns = MAX(window_width / collection->item_width,
1545 if (collection->cursor_item != -1)
1546 scroll_to_show(collection, collection->cursor_item);
1547 gtk_widget_queue_draw(widget);
1550 gtk_widget_queue_resize(GTK_WIDGET(collection));
1553 /* Cursor is positioned on item with the same data as before the sort.
1554 * Same for the wink item.
1556 void collection_qsort(Collection *collection,
1557 int (*compar)(const void *, const void *))
1559 int cursor, wink, items, wink_on_map;
1560 gpointer cursor_data = NULL;
1561 gpointer wink_data = NULL;
1562 gpointer wink_on_map_data = NULL;
1563 CollectionItem *array;
1564 int i;
1566 g_return_if_fail(collection != NULL);
1567 g_return_if_fail(IS_COLLECTION(collection));
1568 g_return_if_fail(compar != NULL);
1570 /* Check to see if it needs sorting (saves redrawing) */
1571 if (collection->number_of_items < 2)
1572 return;
1574 array = collection->items;
1575 for (i = 1; i < collection->number_of_items; i++)
1577 if (compar(&array[i - 1], &array[i]) > 0)
1578 break;
1580 if (i == collection->number_of_items)
1581 return; /* Already sorted */
1583 items = collection->number_of_items;
1585 wink_on_map = collection->wink_on_map;
1586 if (wink_on_map >= 0 && wink_on_map < items)
1588 wink_on_map_data = collection->items[wink_on_map].data;
1589 collection->wink_on_map = -1;
1591 else
1592 wink = -1;
1594 wink = collection->wink_item;
1595 if (wink >= 0 && wink < items)
1597 wink_data = collection->items[wink].data;
1598 collection->wink_item = -1;
1600 else
1601 wink = -1;
1603 cursor = collection->cursor_item;
1604 if (cursor >= 0 && cursor < items)
1605 cursor_data = collection->items[cursor].data;
1606 else
1607 cursor = -1;
1609 qsort(collection->items, items, sizeof(collection->items[0]), compar);
1611 if (cursor > -1 || wink > -1 || wink_on_map > -1)
1613 int item;
1615 for (item = 0; item < items; item++)
1617 if (collection->items[item].data == cursor_data)
1618 collection_set_cursor_item(collection, item);
1619 if (collection->items[item].data == wink_on_map_data)
1620 collection->wink_on_map = item;
1621 if (collection->items[item].data == wink_data)
1623 collection->cursor_item_old = item;
1624 collection->wink_item = item;
1625 scroll_to_show(collection, item);
1630 gtk_widget_queue_draw(GTK_WIDGET(collection));
1633 /* Find an item in an unsorted collection.
1634 * Returns the item number, or -1 if not found.
1636 int collection_find_item(Collection *collection, gpointer data,
1637 int (*compar)(const void *, const void *))
1639 int i;
1641 g_return_val_if_fail(collection != NULL, -1);
1642 g_return_val_if_fail(IS_COLLECTION(collection), -1);
1643 g_return_val_if_fail(compar != NULL, -1);
1645 for (i = 0; i < collection->number_of_items; i++)
1646 if (compar(&collection->items[i].data, &data) == 0)
1647 return i;
1649 return -1;
1652 /* Return the number of the item under the point (x,y), or -1 for none.
1653 * This may call your test_point callback. The point is relative to the
1654 * collection's origin.
1656 int collection_get_item(Collection *collection, int x, int y)
1658 int row, col;
1659 int width;
1660 int item;
1662 g_return_val_if_fail(collection != NULL, -1);
1664 col = x / collection->item_width;
1665 row = y / collection->item_height;
1667 if (col >= collection->columns)
1668 col = collection->columns - 1;
1670 if (col < 0 || row < 0)
1671 return -1;
1673 if (col == collection->columns - 1)
1674 width = collection->item_width << 1;
1675 else
1676 width = collection->item_width;
1678 item = col + row * collection->columns;
1679 if (item >= collection->number_of_items
1681 !collection->test_point(collection,
1682 x - col * collection->item_width,
1683 y - row * collection->item_height,
1684 &collection->items[item],
1685 width,
1686 collection->item_height,
1687 collection->cb_user_data))
1689 return -1;
1692 return item;
1695 /* Set the cursor/highlight over the given item. Passing -1
1696 * hides the cursor. As a special case, you may set the cursor item
1697 * to zero when there are no items.
1699 void collection_set_cursor_item(Collection *collection, gint item)
1701 int old_item;
1703 g_return_if_fail(collection != NULL);
1704 g_return_if_fail(IS_COLLECTION(collection));
1705 g_return_if_fail(item >= -1 &&
1706 (item < collection->number_of_items || item == 0));
1708 old_item = collection->cursor_item;
1710 if (old_item == item)
1711 return;
1713 collection->cursor_item = item;
1715 if (old_item != -1)
1716 collection_draw_item(collection, old_item, TRUE);
1718 if (item != -1)
1720 collection_draw_item(collection, item, TRUE);
1721 if (collection->auto_scroll == -1)
1722 scroll_to_show(collection, item);
1724 else if (old_item != -1)
1725 collection->cursor_item_old = old_item;
1728 /* Briefly highlight an item to draw the user's attention to it.
1729 * -1 cancels the effect, as does deleting items, sorting the collection
1730 * or starting a new wink effect.
1731 * Otherwise, the effect will cancel itself after a short pause.
1732 * */
1733 void collection_wink_item(Collection *collection, gint item)
1735 g_return_if_fail(collection != NULL);
1736 g_return_if_fail(IS_COLLECTION(collection));
1737 g_return_if_fail(item >= -1 && item < collection->number_of_items);
1739 if (collection->wink_item != -1)
1740 cancel_wink(collection);
1741 if (item == -1)
1742 return;
1744 if (!GTK_WIDGET_MAPPED(GTK_WIDGET(collection)))
1746 collection->wink_on_map = item;
1747 return;
1750 collection->cursor_item_old = collection->wink_item = item;
1751 collection->winks_left = MAX_WINKS;
1753 collection->wink_timeout = gtk_timeout_add(70,
1754 (GtkFunction) wink_timeout,
1755 collection);
1756 scroll_to_show(collection, item);
1757 invert_wink(collection);
1759 gdk_flush();
1762 /* Call test(item, data) on each item in the collection.
1763 * Remove all items for which it returns TRUE. test() should
1764 * free the data before returning TRUE. The collection is in an
1765 * inconsistant state during this call (ie, when test() is called).
1767 * If test is NULL, remove all items.
1769 void collection_delete_if(Collection *collection,
1770 gboolean (*test)(gpointer item, gpointer data),
1771 gpointer data)
1773 int in, out = 0;
1774 int selected = 0;
1775 int cursor;
1777 g_return_if_fail(collection != NULL);
1778 g_return_if_fail(IS_COLLECTION(collection));
1780 cursor = collection->cursor_item;
1782 for (in = 0; in < collection->number_of_items; in++)
1784 if (test && !test(collection->items[in].data, data))
1786 /* Keep item */
1787 if (collection->items[in].selected)
1789 collection->items[out].selected = TRUE;
1790 selected++;
1792 else
1793 collection->items[out].selected = FALSE;
1795 collection->items[out].data =
1796 collection->items[in].data;
1797 collection->items[out].view_data =
1798 collection->items[in].view_data;
1799 out++;
1801 else
1803 /* Remove item */
1804 if (collection->free_item)
1805 collection->free_item(collection,
1806 &collection->items[in]);
1808 if (cursor >= in)
1809 cursor--;
1813 if (in != out)
1815 collection->cursor_item = cursor;
1817 if (collection->wink_item != -1)
1819 collection->wink_item = -1;
1820 gtk_timeout_remove(collection->wink_timeout);
1823 collection->number_of_items = out;
1824 if (collection->number_selected && !selected)
1826 /* We've lost all the selected items */
1827 gtk_signal_emit(GTK_OBJECT(collection),
1828 collection_signals[LOSE_SELECTION],
1829 current_event_time);
1832 collection->number_selected = selected;
1833 resize_arrays(collection,
1834 MAX(collection->number_of_items, MINIMUM_ITEMS));
1836 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection)))
1837 gtk_widget_queue_draw(GTK_WIDGET(collection));
1839 gtk_widget_queue_resize(GTK_WIDGET(collection));
1843 /* Move the cursor by the given row and column offsets.
1844 * Moving by (0,0) can be used to simply make the cursor appear.
1846 void collection_move_cursor(Collection *collection, int drow, int dcol)
1848 int row, col, item;
1849 int first, last, total_rows;
1851 g_return_if_fail(collection != NULL);
1852 g_return_if_fail(IS_COLLECTION(collection));
1854 get_visible_limits(collection, &first, &last);
1856 item = collection->cursor_item;
1857 if (item == -1)
1859 item = MIN(collection->cursor_item_old,
1860 collection->number_of_items - 1);
1863 if (item == -1)
1865 col = 0;
1866 row = first;
1868 else
1870 row = item / collection->columns;
1871 col = item % collection->columns + dcol;
1873 if (row < first)
1874 row = first;
1875 else if (row > last)
1876 row = last;
1877 else
1878 row = MAX(row + drow, 0);
1881 total_rows = (collection->number_of_items + collection->columns - 1)
1882 / collection->columns;
1884 if (row >= total_rows - 1 && drow > 0)
1886 row = total_rows - 1;
1887 item = col + row * collection->columns;
1888 if (item >= collection->number_of_items - 1)
1890 collection_set_cursor_item(collection,
1891 collection->number_of_items - 1);
1892 return;
1895 if (row < 0)
1896 row = 0;
1898 item = col + row * collection->columns;
1900 if (item >= 0 && item < collection->number_of_items)
1901 collection_set_cursor_item(collection, item);
1904 /* When autoscroll is on, a timer keeps track of the pointer position.
1905 * While it's near the top or bottom of the window, the window scrolls.
1907 * If the mouse buttons are released, or the pointer leaves the window,
1908 * auto_scroll is turned off.
1910 void collection_set_autoscroll(Collection *collection, gboolean auto_scroll)
1912 g_return_if_fail(collection != NULL);
1913 g_return_if_fail(IS_COLLECTION(collection));
1915 if (auto_scroll)
1917 if (collection->auto_scroll != -1)
1918 return; /* Already on! */
1920 collection->auto_scroll = gtk_timeout_add(50,
1921 (GtkFunction) as_timeout,
1922 collection);
1924 else
1926 if (collection->auto_scroll == -1)
1927 return; /* Already off! */
1929 gtk_timeout_remove(collection->auto_scroll);
1930 collection->auto_scroll = -1;
1934 /* Start a lasso box drag */
1935 void collection_lasso_box(Collection *collection, int x, int y)
1937 collection->drag_box_x[0] = x;
1938 collection->drag_box_y[0] = y;
1939 collection->drag_box_x[1] = x;
1940 collection->drag_box_y[1] = y;
1942 collection_set_autoscroll(collection, TRUE);
1943 add_lasso_box(collection);
1946 /* Remove the lasso box. Applies fn to each item inside the box.
1947 * fn may be GDK_INVERT, GDK_SET, GDK_NOOP or GDK_CLEAR.
1949 void collection_end_lasso(Collection *collection, GdkFunction fn)
1951 if (fn != GDK_CLEAR)
1953 GdkRectangle region;
1955 find_lasso_area(collection, &region);
1957 collection_process_area(collection, &region, fn,
1958 GDK_CURRENT_TIME);
1961 abort_lasso(collection);
1964 /* Unblock the selection_changed signal, emitting the signal if the
1965 * block counter reaches zero and emit is TRUE.
1967 void collection_unblock_selection_changed(Collection *collection,
1968 guint time,
1969 gboolean emit)
1971 g_return_if_fail(collection != NULL);
1972 g_return_if_fail(IS_COLLECTION(collection));
1973 g_return_if_fail(collection->block_selection_changed > 0);
1975 collection->block_selection_changed--;
1977 if (emit && !collection->block_selection_changed)
1978 gtk_signal_emit(GTK_OBJECT(collection),
1979 collection_signals[SELECTION_CHANGED], time);