r4705: When moving the cursor down on the last row of a collection in vertical mode,
[rox-filer.git] / ROX-Filer / src / collection.c
blob8e4111d495f5e8b416891c25105f4f17be813c7c
1 /*
2 * ROX-Filer, filer for the ROX desktop project
3 * Copyright (C) 2006, Thomas Leonard and others (see changelog for details).
5 * The collection widget provides an area for displaying a collection of
6 * objects (such as files). It allows the user to choose a selection of
7 * them and provides signals to allow popping up menus, detecting
8 * double-clicks etc.
10 * This program is free software; you can redistribute it and/or modify it
11 * under the terms of the GNU General Public License as published by the Free
12 * Software Foundation; either version 2 of the License, or (at your option)
13 * any later version.
15 * This program is distributed in the hope that it will be useful, but WITHOUT
16 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
17 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
18 * more details.
20 * You should have received a copy of the GNU General Public License along with
21 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
22 * Place, Suite 330, Boston, MA 02111-1307 USA
25 #include "config.h"
27 #include <stdlib.h>
29 #include <gtk/gtk.h>
30 #include <gdk/gdkkeysyms.h>
31 #include "global.h"
33 #include "collection.h"
35 #define MIN_WIDTH 80
36 #define MIN_HEIGHT 60
37 #define MINIMUM_ITEMS 16
39 #define MAX_WINKS 5 /* Should be an odd number */
41 /* Macro to emit the "selection_changed" signal only if allowed */
42 #define EMIT_SELECTION_CHANGED(collection, time) \
43 if (!collection->block_selection_changed) \
44 g_signal_emit(collection, \
45 collection_signals[SELECTION_CHANGED], 0, time)
47 enum
49 PROP_0,
50 PROP_VADJUSTMENT
53 /* Signals:
55 * void gain_selection(collection, time, user_data)
56 * We've gone from no selected items to having a selection.
57 * Time is the time of the event that caused the change, or
58 * GDK_CURRENT_TIME if not known.
60 * void lose_selection(collection, time, user_data)
61 * We've dropped to having no selected items.
62 * Time is the time of the event that caused the change, or
63 * GDK_CURRENT_TIME if not known.
65 * void selection_changed(collection, user_data)
66 * The set of selected items has changed.
67 * Time is the time of the event that caused the change, or
68 * GDK_CURRENT_TIME if not known.
70 enum
72 GAIN_SELECTION,
73 LOSE_SELECTION,
74 SELECTION_CHANGED,
75 LAST_SIGNAL
78 static guint collection_signals[LAST_SIGNAL] = { 0 };
80 static guint32 current_event_time = GDK_CURRENT_TIME;
82 static GtkWidgetClass *parent_class = NULL;
84 /* Static prototypes */
85 static void draw_one_item(Collection *collection,
86 int item,
87 GdkRectangle *area);
88 static void collection_class_init(GObjectClass *gclass, gpointer data);
89 static void collection_init(GTypeInstance *object, gpointer g_class);
90 static void collection_destroy(GtkObject *object);
91 static void collection_finalize(GObject *object);
92 static void collection_realize(GtkWidget *widget);
93 static void collection_map(GtkWidget *widget);
94 static void collection_size_request(GtkWidget *widget,
95 GtkRequisition *requisition);
96 static void collection_size_allocate(GtkWidget *widget,
97 GtkAllocation *allocation);
98 static void collection_set_adjustment(Collection *collection,
99 GtkAdjustment *vadj);
100 static void collection_get_property(GObject *object,
101 guint prop_id,
102 GValue *value,
103 GParamSpec *pspec);
104 static void collection_set_property(GObject *object,
105 guint prop_id,
106 const GValue *value,
107 GParamSpec *pspec);
108 static gint collection_expose(GtkWidget *widget, GdkEventExpose *event);
109 static void default_draw_item(GtkWidget *widget,
110 CollectionItem *data,
111 GdkRectangle *area,
112 gpointer user_data);
113 static gboolean default_test_point(Collection *collection,
114 int point_x, int point_y,
115 CollectionItem *data,
116 int width, int height,
117 gpointer user_data);
118 static gint collection_motion_notify(GtkWidget *widget,
119 GdkEventMotion *event);
120 static void add_lasso_box(Collection *collection);
121 static void abort_lasso(Collection *collection);
122 static void remove_lasso_box(Collection *collection);
123 static void draw_lasso_box(Collection *collection);
124 static void cancel_wink(Collection *collection);
125 static gint collection_key_press(GtkWidget *widget, GdkEventKey *event);
126 static void get_visible_limits(Collection *collection, int *first, int *last);
127 static void scroll_to_show(Collection *collection, int item);
128 static void collection_item_set_selected(Collection *collection,
129 gint item,
130 gboolean selected,
131 gboolean signal);
132 static gint collection_scroll_event(GtkWidget *widget, GdkEventScroll *event);
133 static int collection_get_rows(const Collection *collection);
134 static int collection_get_cols(const Collection *collection);
137 /* The number of rows, at least 1. */
138 static inline int collection_get_rows(const Collection *collection)
140 int rows = (collection->number_of_items + collection->columns - 1) /
141 collection->columns;
142 return MAX(rows, 1);
145 /* The number of columns _actually_ displayed, at least 1. This
146 * function is required in vertical_order layout-based manipulation
147 * such as moving the cursor to detect the last column. */
148 static inline int collection_get_cols(const Collection *collection)
150 if (collection->vertical_order)
152 int rows = collection_get_rows(collection);
153 int cols = (collection->number_of_items + rows - 1) / rows;
154 return MAX(1, cols);
156 else
157 return collection->columns;
160 static void draw_focus_at(Collection *collection, GdkRectangle *area)
162 GtkWidget *widget;
163 GtkStateType state;
165 widget = GTK_WIDGET(collection);
167 if (GTK_WIDGET_FLAGS(widget) & GTK_HAS_FOCUS)
168 state = GTK_STATE_ACTIVE;
169 else
170 state = GTK_STATE_INSENSITIVE;
172 gtk_paint_focus(widget->style,
173 widget->window,
174 state,
175 NULL,
176 widget,
177 "collection",
178 area->x, area->y,
179 collection->item_width,
180 area->height);
183 static void draw_one_item(Collection *collection, int item, GdkRectangle *area)
185 if (item < collection->number_of_items)
187 collection->draw_item((GtkWidget *) collection,
188 &collection->items[item],
189 area, collection->cb_user_data);
192 if (item == collection->cursor_item)
193 draw_focus_at(collection, area);
196 GType collection_get_type(void)
198 static GType my_type = 0;
200 if (!my_type)
202 static const GTypeInfo info =
204 sizeof(CollectionClass),
205 NULL, /* base_init */
206 NULL, /* base_finalise */
207 (GClassInitFunc) collection_class_init,
208 NULL, /* class_finalise */
209 NULL, /* class_data */
210 sizeof(Collection),
211 0, /* n_preallocs */
212 collection_init
215 my_type = g_type_register_static(gtk_widget_get_type(),
216 "Collection", &info, 0);
219 return my_type;
222 typedef void (*FinalizeFn)(GObject *object);
224 static void collection_class_init(GObjectClass *gclass, gpointer data)
226 CollectionClass *collection_class = (CollectionClass *) gclass;
227 GtkObjectClass *object_class = (GtkObjectClass *) gclass;
228 GtkWidgetClass *widget_class = (GtkWidgetClass *) gclass;
230 parent_class = gtk_type_class(gtk_widget_get_type());
232 object_class->destroy = collection_destroy;
233 G_OBJECT_CLASS(object_class)->finalize =
234 (FinalizeFn) collection_finalize;
236 widget_class->realize = collection_realize;
237 widget_class->expose_event = collection_expose;
238 widget_class->size_request = collection_size_request;
239 widget_class->size_allocate = collection_size_allocate;
241 widget_class->key_press_event = collection_key_press;
243 widget_class->motion_notify_event = collection_motion_notify;
244 widget_class->map = collection_map;
245 widget_class->scroll_event = collection_scroll_event;
247 gclass->set_property = collection_set_property;
248 gclass->get_property = collection_get_property;
250 collection_class->gain_selection = NULL;
251 collection_class->lose_selection = NULL;
252 collection_class->selection_changed = NULL;
254 collection_signals[GAIN_SELECTION] = g_signal_new("gain_selection",
255 G_TYPE_FROM_CLASS(gclass),
256 G_SIGNAL_RUN_LAST,
257 G_STRUCT_OFFSET(CollectionClass,
258 gain_selection),
259 NULL, NULL,
260 g_cclosure_marshal_VOID__INT,
261 G_TYPE_NONE, 1,
262 G_TYPE_INT);
264 collection_signals[LOSE_SELECTION] = g_signal_new("lose_selection",
265 G_TYPE_FROM_CLASS(gclass),
266 G_SIGNAL_RUN_LAST,
267 G_STRUCT_OFFSET(CollectionClass,
268 lose_selection),
269 NULL, NULL,
270 g_cclosure_marshal_VOID__INT,
271 G_TYPE_NONE, 1,
272 G_TYPE_INT);
274 collection_signals[SELECTION_CHANGED] = g_signal_new(
275 "selection_changed",
276 G_TYPE_FROM_CLASS(gclass),
277 G_SIGNAL_RUN_LAST,
278 G_STRUCT_OFFSET(CollectionClass,
279 selection_changed),
280 NULL, NULL,
281 g_cclosure_marshal_VOID__INT,
282 G_TYPE_NONE, 1,
283 G_TYPE_INT);
285 g_object_class_install_property(gclass,
286 PROP_VADJUSTMENT,
287 g_param_spec_object("vadjustment",
288 "Vertical Adjustment",
289 "The GtkAdjustment for the vertical position.",
290 GTK_TYPE_ADJUSTMENT,
291 G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
294 static void collection_init(GTypeInstance *instance, gpointer g_class)
296 Collection *object = (Collection *) instance;
298 g_return_if_fail(object != NULL);
299 g_return_if_fail(IS_COLLECTION(object));
301 GTK_WIDGET_SET_FLAGS(GTK_WIDGET(object), GTK_CAN_FOCUS);
303 object->number_of_items = 0;
304 object->number_selected = 0;
305 object->block_selection_changed = 0;
306 object->columns = 1;
307 object->vertical_order = FALSE;
308 object->item_width = 64;
309 object->item_height = 64;
310 object->vadj = NULL;
312 object->items = g_new(CollectionItem, MINIMUM_ITEMS);
313 object->cursor_item = -1;
314 object->cursor_item_old = -1;
315 object->wink_item = -1;
316 object->wink_on_map = -1;
317 object->array_size = MINIMUM_ITEMS;
318 object->draw_item = default_draw_item;
319 object->test_point = default_test_point;
320 object->free_item = NULL;
323 GtkWidget* collection_new(void)
325 return GTK_WIDGET(gtk_widget_new(collection_get_type(), NULL));
328 /* After this we are unusable, but our data (if any) is still hanging around.
329 * It will be freed later with finalize.
331 static void collection_destroy(GtkObject *object)
333 Collection *collection;
335 g_return_if_fail(object != NULL);
336 g_return_if_fail(IS_COLLECTION(object));
338 collection = COLLECTION(object);
340 collection_clear(collection);
342 if (collection->vadj)
344 g_object_unref(G_OBJECT(collection->vadj));
345 collection->vadj = NULL;
348 if (GTK_OBJECT_CLASS(parent_class)->destroy)
349 (*GTK_OBJECT_CLASS(parent_class)->destroy)(object);
352 /* This is the last thing that happens to us. Free all data. */
353 static void collection_finalize(GObject *object)
355 Collection *collection;
357 collection = COLLECTION(object);
359 g_return_if_fail(collection->number_of_items == 0);
361 g_free(collection->items);
363 if (G_OBJECT_CLASS(parent_class)->finalize)
364 G_OBJECT_CLASS(parent_class)->finalize(object);
367 static void collection_map(GtkWidget *widget)
369 Collection *collection = COLLECTION(widget);
371 if (GTK_WIDGET_CLASS(parent_class)->map)
372 (*GTK_WIDGET_CLASS(parent_class)->map)(widget);
374 if (collection->wink_on_map >= 0)
376 collection_wink_item(collection, collection->wink_on_map);
377 collection->wink_on_map = -1;
381 static void collection_realize(GtkWidget *widget)
383 Collection *collection;
384 GdkWindowAttr attributes;
385 gint attributes_mask;
386 GdkGCValues xor_values;
387 GdkColor *bg, *fg;
389 g_return_if_fail(widget != NULL);
390 g_return_if_fail(IS_COLLECTION(widget));
391 g_return_if_fail(widget->parent != NULL);
393 GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);
394 collection = COLLECTION(widget);
396 attributes.x = widget->allocation.x;
397 attributes.y = widget->allocation.y;
398 attributes.width = widget->allocation.width;
399 attributes.height = widget->allocation.height;
400 attributes.wclass = GDK_INPUT_OUTPUT;
401 attributes.window_type = GDK_WINDOW_CHILD;
402 attributes.event_mask = gtk_widget_get_events(widget) |
403 GDK_EXPOSURE_MASK |
404 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
405 GDK_BUTTON1_MOTION_MASK | GDK_BUTTON2_MOTION_MASK |
406 GDK_BUTTON3_MOTION_MASK;
407 attributes.visual = gtk_widget_get_visual(widget);
408 attributes.colormap = gtk_widget_get_colormap(widget);
410 attributes_mask = GDK_WA_X | GDK_WA_Y |
411 GDK_WA_VISUAL | GDK_WA_COLORMAP;
412 widget->window = gdk_window_new(gtk_widget_get_parent_window(widget),
413 &attributes, attributes_mask);
415 widget->style = gtk_style_attach(widget->style, widget->window);
417 gdk_window_set_user_data(widget->window, widget);
418 gdk_window_set_background(widget->window,
419 &widget->style->bg[GTK_STATE_NORMAL]);
421 bg = &widget->style->bg[GTK_STATE_NORMAL];
422 fg = &widget->style->fg[GTK_STATE_NORMAL];
423 xor_values.function = GDK_XOR;
424 xor_values.foreground.pixel = fg->pixel ^ bg->pixel;
425 collection->xor_gc = gdk_gc_new_with_values(widget->window,
426 &xor_values,
427 GDK_GC_FOREGROUND
428 | GDK_GC_FUNCTION);
431 static void collection_size_request(GtkWidget *widget,
432 GtkRequisition *requisition)
434 Collection *collection = COLLECTION(widget);
435 int rows;
437 /* We ask for the total size we need; our containing viewport
438 * will deal with scrolling.
440 requisition->width = MIN_WIDTH;
441 rows = collection_get_rows(collection);
442 requisition->height = rows * collection->item_height;
445 static gboolean scroll_after_alloc(Collection *collection)
447 if (collection->wink_item != -1)
448 scroll_to_show(collection, collection->wink_item);
449 else if (collection->cursor_item != -1)
450 scroll_to_show(collection, collection->cursor_item);
451 g_object_unref(G_OBJECT(collection));
453 return FALSE;
456 static void collection_size_allocate(GtkWidget *widget,
457 GtkAllocation *allocation)
459 Collection *collection;
460 int old_columns;
461 gboolean cursor_visible = FALSE;
463 g_return_if_fail(widget != NULL);
464 g_return_if_fail(IS_COLLECTION(widget));
465 g_return_if_fail(allocation != NULL);
467 collection = COLLECTION(widget);
469 if (collection->cursor_item != -1)
471 int first, last;
472 int crow, ccol;
474 collection_item_to_rowcol(collection, collection->cursor_item,
475 &crow, &ccol);
477 get_visible_limits(collection, &first, &last);
479 cursor_visible = crow >= first && crow <= last;
482 old_columns = collection->columns;
484 widget->allocation = *allocation;
486 collection->columns = allocation->width / collection->item_width;
487 if (collection->columns < 1)
488 collection->columns = 1;
490 if (GTK_WIDGET_REALIZED(widget))
492 gdk_window_move_resize(widget->window,
493 allocation->x, allocation->y,
494 allocation->width, allocation->height);
496 if (cursor_visible)
497 scroll_to_show(collection, collection->cursor_item);
500 if (old_columns != collection->columns)
502 /* Need to go around again... */
503 gtk_widget_queue_resize(widget);
505 else if (collection->wink_item != -1 || collection->cursor_item != -1)
507 /* Viewport resets the adjustments after the alloc */
508 g_object_ref(G_OBJECT(collection));
509 g_idle_add((GSourceFunc) scroll_after_alloc, collection);
513 /* Return the area occupied by the item at (row, col) by filling
514 * in 'area'.
516 static void collection_get_item_area(Collection *collection,
517 int row, int col,
518 GdkRectangle *area)
521 area->x = col * collection->item_width;
522 area->y = row * collection->item_height;
524 area->width = collection->item_width;
525 area->height = collection->item_height;
526 if (col == collection->columns - 1)
527 area->width <<= 1;
530 static gint collection_expose(GtkWidget *widget, GdkEventExpose *event)
532 Collection *collection;
533 GdkRectangle item_area;
534 int row, col;
535 int item;
536 int start_row, last_row;
537 int start_col, last_col;
538 int phys_last_col;
540 g_return_val_if_fail(widget != NULL, FALSE);
541 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
542 g_return_val_if_fail(event != NULL, FALSE);
544 /* Note about 'detail' argument:
545 * - If set to "base", lighthouse theme will crash
546 * - If set to NULL, cleanice theme will crash
548 gtk_paint_flat_box(widget->style, widget->window, GTK_STATE_NORMAL,
549 GTK_SHADOW_NONE, &event->area,
550 widget, "collection", 0, 0, -1, -1);
552 collection = COLLECTION(widget);
554 /* Calculate the ranges to plot */
555 start_row = event->area.y / collection->item_height;
556 last_row = (event->area.y + event->area.height - 1)
557 / collection->item_height;
559 if (last_row >= collection_get_rows(collection))
560 last_row = collection_get_rows(collection) - 1;
562 start_col = event->area.x / collection->item_width;
563 phys_last_col = (event->area.x + event->area.width - 1)
564 / collection->item_width;
566 /* The right-most column may be wider than the others.
567 * Therefore, to redraw the area after the last 'real' column
568 * we may have to draw the right-most column.
570 if (start_col >= collection->columns)
571 start_col = collection->columns - 1;
573 if (phys_last_col >= collection->columns)
574 last_col = collection->columns - 1;
575 else
576 last_col = phys_last_col;
579 for(row = start_row; row <= last_row; row++)
580 for(col = start_col; col <= last_col; col++)
582 item = collection_rowcol_to_item(collection, row, col);
583 if (item == 0 || item < collection->number_of_items) {
584 collection_get_item_area(collection,
585 row, col, &item_area);
586 draw_one_item(collection, item, &item_area);
590 if (collection->lasso_box)
591 draw_lasso_box(collection);
593 return FALSE;
596 static void default_draw_item(GtkWidget *widget,
597 CollectionItem *item,
598 GdkRectangle *area,
599 gpointer user_data)
601 gdk_draw_arc(widget->window,
602 item->selected ? widget->style->white_gc
603 : widget->style->black_gc,
604 TRUE,
605 area->x, area->y,
606 COLLECTION(widget)->item_width, area->height,
607 0, 360 * 64);
611 static gboolean default_test_point(Collection *collection,
612 int point_x, int point_y,
613 CollectionItem *item,
614 int width, int height,
615 gpointer user_data)
617 float f_x, f_y;
619 /* Convert to point in unit circle */
620 f_x = ((float) point_x / width) - 0.5;
621 f_y = ((float) point_y / height) - 0.5;
623 return (f_x * f_x) + (f_y * f_y) <= .25;
626 static void collection_set_property(GObject *object,
627 guint prop_id,
628 const GValue *value,
629 GParamSpec *pspec)
631 Collection *collection;
633 collection = COLLECTION(object);
635 switch (prop_id)
637 case PROP_VADJUSTMENT:
638 collection_set_adjustment(collection,
639 g_value_get_object(value));
640 break;
641 default:
642 G_OBJECT_WARN_INVALID_PROPERTY_ID(object,
643 prop_id, pspec);
644 break;
648 static void collection_set_adjustment(Collection *collection,
649 GtkAdjustment *vadj)
651 if (vadj)
652 g_return_if_fail(GTK_IS_ADJUSTMENT(vadj));
653 else
654 vadj = GTK_ADJUSTMENT(gtk_adjustment_new(0.0,
655 0.0, 0.0,
656 0.0, 0.0, 0.0));
658 if (collection->vadj == vadj)
659 return;
661 if (collection->vadj)
662 g_object_unref(G_OBJECT(collection->vadj));
664 collection->vadj = vadj;
665 g_object_ref(G_OBJECT(collection->vadj));
666 gtk_object_sink(GTK_OBJECT(collection->vadj));
669 static void collection_get_property(GObject *object,
670 guint prop_id,
671 GValue *value,
672 GParamSpec *pspec)
674 Collection *collection;
676 collection = COLLECTION(object);
678 switch (prop_id)
680 case PROP_VADJUSTMENT:
681 g_value_set_object(value, G_OBJECT(collection->vadj));
682 break;
683 default:
684 G_OBJECT_WARN_INVALID_PROPERTY_ID(object,
685 prop_id, pspec);
686 break;
690 static void resize_arrays(Collection *collection, guint new_size)
692 g_return_if_fail(collection != NULL);
693 g_return_if_fail(IS_COLLECTION(collection));
694 g_return_if_fail(new_size >= collection->number_of_items);
696 collection->items = g_realloc(collection->items,
697 sizeof(CollectionItem) * new_size);
698 collection->array_size = new_size;
701 static gint collection_key_press(GtkWidget *widget, GdkEventKey *event)
703 Collection *collection;
704 int item;
705 int key;
707 g_return_val_if_fail(widget != NULL, FALSE);
708 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
709 g_return_val_if_fail(event != NULL, FALSE);
711 collection = (Collection *) widget;
712 item = collection->cursor_item;
714 key = event->keyval;
715 if (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK))
717 if (key == GDK_Left || key == GDK_Right || \
718 key == GDK_Up || key == GDK_Down)
719 return TRUE;
720 return FALSE;
723 switch (key)
725 case GDK_Left:
726 collection_move_cursor(collection, 0, -1);
727 break;
728 case GDK_Right:
729 collection_move_cursor(collection, 0, 1);
730 break;
731 case GDK_Up:
732 collection_move_cursor(collection, -1, 0);
733 break;
734 case GDK_Down:
735 collection_move_cursor(collection, 1, 0);
736 break;
737 case GDK_Home:
738 collection_set_cursor_item(collection, 0, TRUE);
739 break;
740 case GDK_End:
741 collection_set_cursor_item(collection,
742 MAX((gint) collection->number_of_items - 1, 0),
743 TRUE);
744 break;
745 case GDK_Page_Up:
747 int first, last;
748 get_visible_limits(collection, &first, &last);
749 collection_move_cursor(collection, first - last - 1, 0);
750 break;
752 case GDK_Page_Down:
754 int first, last;
755 get_visible_limits(collection, &first, &last);
756 collection_move_cursor(collection, last - first + 1, 0);
757 break;
759 default:
760 return FALSE;
763 return TRUE;
766 /* Wheel mouse scrolling */
767 static gint collection_scroll_event(GtkWidget *widget, GdkEventScroll *event)
769 Collection *collection;
770 int diff = 0;
772 g_return_val_if_fail(widget != NULL, FALSE);
773 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
774 g_return_val_if_fail(event != NULL, FALSE);
776 collection = COLLECTION(widget);
778 if (event->direction == GDK_SCROLL_UP)
779 diff = -1;
780 else if (event->direction == GDK_SCROLL_DOWN)
781 diff = 1;
782 else
783 return FALSE;
785 if (diff)
787 int old_value = collection->vadj->value;
788 int new_value = 0;
789 gboolean box = collection->lasso_box;
790 int step = collection->vadj->page_increment / 2;
792 new_value = CLAMP(old_value + diff * step, 0.0,
793 collection->vadj->upper
794 - collection->vadj->page_size);
795 diff = new_value - old_value;
796 if (diff)
798 if (box)
800 remove_lasso_box(collection);
801 collection->drag_box_y[0] -= diff;
803 gtk_adjustment_set_value(collection->vadj, new_value);
804 if (box)
805 add_lasso_box(collection);
809 return TRUE;
812 /* 'from' and 'to' are pixel positions. 'step' is the size of each item.
813 * Returns the index of the first item covered, and the number of items.
815 static void get_range(int from, int to, int step, gint *pos, gint *len)
817 int margin = MIN(step / 4, 40);
819 if (from > to)
821 int tmp = to;
822 to = from;
823 from = tmp;
826 from = (from + margin) / step; /* First item */
827 to = (to + step - margin) / step; /* Last item (inclusive) */
829 *pos = MAX(from, 0);
830 *len = to - *pos;
833 /* Fills in the area with a rectangle corresponding to the current
834 * size of the lasso box (units of items, not pixels).
836 * The box will only span valid columns, but the total number
837 * of items is not taken into account (rows or cols).
839 static void find_lasso_area(Collection *collection, GdkRectangle *area)
841 int cols = collection->columns;
842 int dx = collection->drag_box_x[0] - collection->drag_box_x[1];
843 int dy = collection->drag_box_y[0] - collection->drag_box_y[1];
845 if (ABS(dx) < 8 && ABS(dy) < 8)
847 /* Didn't move far enough - ignore */
848 area->x = area->y = 0;
849 area->width = 0;
850 area->height = 0;
851 return;
854 get_range(collection->drag_box_x[0], collection->drag_box_x[1],
855 collection->item_width, &area->x, &area->width);
857 if (area->x >= cols)
858 area->width = 0;
859 else if (area->x + area->width > cols)
860 area->width = cols - area->x;
862 get_range(collection->drag_box_y[0], collection->drag_box_y[1],
863 collection->item_height, &area->y, &area->height);
866 static void collection_process_area(Collection *collection,
867 GdkRectangle *area,
868 GdkFunction fn,
869 guint32 time)
871 int x, y;
872 int rows = collection_get_rows(collection);
873 int cols = collection->columns;
874 guint32 stacked_time;
875 int item;
876 gboolean changed = FALSE;
877 guint old_selected;
879 g_return_if_fail(fn == GDK_SET || fn == GDK_INVERT);
881 old_selected = collection->number_selected;
883 stacked_time = current_event_time;
884 current_event_time = time;
886 collection->block_selection_changed++;
888 for (y = area->y; y < area->y + area->height && y < rows; y++)
889 for (x = area->x; x < area->x + area->width && x < cols; x++)
891 item = collection_rowcol_to_item(collection, y, x);
892 if (item < collection->number_of_items) {
893 if (fn == GDK_INVERT)
894 collection_item_set_selected(
895 collection, item,
896 !collection-> items[item].selected,
897 FALSE);
898 else
899 collection_item_set_selected(
900 collection, item, TRUE, FALSE);
902 changed = TRUE;
906 if (collection->number_selected && !old_selected)
907 g_signal_emit(collection,
908 collection_signals[GAIN_SELECTION], 0,
909 current_event_time);
910 else if (!collection->number_selected && old_selected)
911 g_signal_emit(collection,
912 collection_signals[LOSE_SELECTION], 0,
913 current_event_time);
915 collection_unblock_selection_changed(collection,
916 current_event_time, changed);
917 current_event_time = stacked_time;
920 static gint collection_motion_notify(GtkWidget *widget,
921 GdkEventMotion *event)
923 Collection *collection;
924 gint x, y;
926 g_return_val_if_fail(widget != NULL, FALSE);
927 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
928 g_return_val_if_fail(event != NULL, FALSE);
930 collection = COLLECTION(widget);
932 if (!collection->lasso_box)
933 return FALSE;
935 if (event->window != widget->window)
936 gdk_window_get_pointer(widget->window, &x, &y, NULL);
937 else
939 x = event->x;
940 y = event->y;
943 remove_lasso_box(collection);
944 collection->drag_box_x[1] = x;
945 collection->drag_box_y[1] = y;
946 add_lasso_box(collection);
947 return TRUE;
950 static void add_lasso_box(Collection *collection)
952 g_return_if_fail(collection != NULL);
953 g_return_if_fail(IS_COLLECTION(collection));
954 g_return_if_fail(collection->lasso_box == FALSE);
956 collection->lasso_box = TRUE;
957 draw_lasso_box(collection);
960 static void draw_lasso_box(Collection *collection)
962 GtkWidget *widget;
963 int x, y, width, height;
965 widget = GTK_WIDGET(collection);
967 x = MIN(collection->drag_box_x[0], collection->drag_box_x[1]);
968 y = MIN(collection->drag_box_y[0], collection->drag_box_y[1]);
969 width = abs(collection->drag_box_x[1] - collection->drag_box_x[0]);
970 height = abs(collection->drag_box_y[1] - collection->drag_box_y[0]);
972 /* XXX: A redraw bug sometimes leaves a one-pixel dot on the screen.
973 * As a quick hack, don't draw boxes that small for now...
975 if (width || height)
976 gdk_draw_rectangle(widget->window, collection->xor_gc, FALSE,
977 x, y, width, height);
980 static void abort_lasso(Collection *collection)
982 if (collection->lasso_box)
983 remove_lasso_box(collection);
986 static void remove_lasso_box(Collection *collection)
988 g_return_if_fail(collection != NULL);
989 g_return_if_fail(IS_COLLECTION(collection));
990 g_return_if_fail(collection->lasso_box == TRUE);
992 draw_lasso_box(collection);
994 collection->lasso_box = FALSE;
996 return;
999 /* Make sure that 'item' is fully visible (vertically), scrolling if not. */
1000 static void scroll_to_show(Collection *collection, int item)
1002 int first, last, row, col;
1004 g_return_if_fail(collection != NULL);
1005 g_return_if_fail(IS_COLLECTION(collection));
1007 collection_item_to_rowcol(collection, item, &row, &col);
1008 get_visible_limits(collection, &first, &last);
1010 if (row <= first)
1012 gtk_adjustment_set_value(collection->vadj,
1013 row * collection->item_height);
1015 else if (row >= last)
1017 GtkWidget *widget = (GtkWidget *) collection;
1018 gint height;
1020 if (GTK_WIDGET_REALIZED(widget))
1022 height = collection->vadj->page_size;
1023 gtk_adjustment_set_value(collection->vadj,
1024 (row + 1) * collection->item_height - height);
1029 /* Return the first and last rows which are [partly] visible. Does not
1030 * ensure that the rows actually exist (contain items).
1032 static void get_visible_limits(Collection *collection, int *first, int *last)
1034 GtkWidget *widget = (GtkWidget *) collection;
1035 gint scroll = 0, height;
1037 g_return_if_fail(collection != NULL);
1038 g_return_if_fail(IS_COLLECTION(collection));
1039 g_return_if_fail(first != NULL && last != NULL);
1041 if (!GTK_WIDGET_REALIZED(widget))
1043 *first = 0;
1044 *last = 0;
1046 else
1048 scroll = collection->vadj->value;
1049 height = collection->vadj->page_size;
1051 *first = MAX(scroll / collection->item_height, 0);
1052 *last = (scroll + height - 1) /collection->item_height;
1054 if (*last < *first)
1055 *last = *first;
1059 /* Cancel the current wink effect. */
1060 static void cancel_wink(Collection *collection)
1062 gint item;
1064 g_return_if_fail(collection != NULL);
1065 g_return_if_fail(IS_COLLECTION(collection));
1066 g_return_if_fail(collection->wink_item != -1);
1068 item = collection->wink_item;
1070 collection->wink_item = -1;
1071 g_source_remove(collection->wink_timeout);
1073 collection_draw_item(collection, item, TRUE);
1076 /* Draw/undraw a box around collection->wink_item */
1077 static void invert_wink(Collection *collection)
1079 GdkRectangle area;
1080 gint row, col;
1082 g_return_if_fail(collection->wink_item >= 0);
1084 if (!GTK_WIDGET_REALIZED(GTK_WIDGET(collection)))
1085 return;
1087 collection_item_to_rowcol(collection, collection->wink_item,
1088 &row, &col);
1089 collection_get_item_area(collection, row, col, &area);
1091 gdk_draw_rectangle(((GtkWidget *) collection)->window,
1092 collection->xor_gc, FALSE,
1093 area.x, area.y,
1094 collection->item_width - 1,
1095 area.height - 1);
1098 static gboolean wink_timeout(Collection *collection)
1100 gint item;
1102 g_return_val_if_fail(collection != NULL, FALSE);
1103 g_return_val_if_fail(IS_COLLECTION(collection), FALSE);
1104 g_return_val_if_fail(collection->wink_item != -1, FALSE);
1106 item = collection->wink_item;
1108 if (collection->winks_left-- > 0)
1110 invert_wink(collection);
1111 return TRUE;
1114 collection->wink_item = -1;
1116 collection_draw_item(collection, item, TRUE);
1118 return FALSE;
1121 /* Change the selected state of an item.
1122 * Send GAIN/LOSE signals if 'signal' is TRUE.
1123 * Send SELECTION_CHANGED unless blocked.
1124 * Updates number_selected and redraws the item.
1126 static void collection_item_set_selected(Collection *collection,
1127 gint item,
1128 gboolean selected,
1129 gboolean signal)
1131 g_return_if_fail(collection != NULL);
1132 g_return_if_fail(IS_COLLECTION(collection));
1133 g_return_if_fail(item >= 0 && item < collection->number_of_items);
1135 if (collection->items[item].selected == selected)
1136 return;
1138 collection->items[item].selected = selected;
1139 collection_draw_item(collection, item, TRUE);
1141 if (selected)
1143 collection->number_selected++;
1144 if (signal && collection->number_selected == 1)
1145 g_signal_emit(collection,
1146 collection_signals[GAIN_SELECTION], 0,
1147 current_event_time);
1149 else
1151 collection->number_selected--;
1152 if (signal && collection->number_selected == 0)
1153 g_signal_emit(collection,
1154 collection_signals[LOSE_SELECTION], 0,
1155 current_event_time);
1158 EMIT_SELECTION_CHANGED(collection, current_event_time);
1161 /* Functions for managing collections */
1163 /* Remove all objects from the collection */
1164 void collection_clear(Collection *collection)
1166 collection_delete_if(collection, NULL, NULL);
1169 /* Inserts a new item at the end. The new item is unselected, and its
1170 * number is returned.
1172 gint collection_insert(Collection *collection, gpointer data, gpointer view)
1174 int item;
1176 /* g_return_val_if_fail(IS_COLLECTION(collection), -1); (slow) */
1178 item = collection->number_of_items;
1180 if (item >= collection->array_size)
1181 resize_arrays(collection, item + (item >> 1));
1183 collection->items[item].data = data;
1184 collection->items[item].view_data = view;
1185 collection->items[item].selected = FALSE;
1187 collection->number_of_items++;
1189 gtk_widget_queue_resize(GTK_WIDGET(collection));
1191 collection_draw_item(collection, item, FALSE);
1193 return item;
1196 void collection_unselect_item(Collection *collection, gint item)
1198 collection_item_set_selected(collection, item, FALSE, TRUE);
1201 void collection_select_item(Collection *collection, gint item)
1203 collection_item_set_selected(collection, item, TRUE, TRUE);
1206 void collection_toggle_item(Collection *collection, gint item)
1208 collection_item_set_selected(collection, item,
1209 !collection->items[item].selected, TRUE);
1212 /* Select all items in the collection */
1213 void collection_select_all(Collection *collection)
1215 GtkWidget *widget;
1216 int item = 0;
1218 g_return_if_fail(collection != NULL);
1219 g_return_if_fail(IS_COLLECTION(collection));
1221 widget = GTK_WIDGET(collection);
1223 if (collection->number_selected == collection->number_of_items)
1224 return; /* Nothing to do */
1226 while (collection->number_selected < collection->number_of_items)
1228 while (collection->items[item].selected)
1229 item++;
1231 collection->items[item].selected = TRUE;
1232 collection_draw_item(collection, item, TRUE);
1233 item++;
1235 collection->number_selected++;
1238 g_signal_emit(collection, collection_signals[GAIN_SELECTION], 0,
1239 current_event_time);
1240 EMIT_SELECTION_CHANGED(collection, current_event_time);
1243 /* Toggle all items in the collection */
1244 void collection_invert_selection(Collection *collection)
1246 int item;
1248 g_return_if_fail(collection != NULL);
1249 g_return_if_fail(IS_COLLECTION(collection));
1251 if (collection->number_selected == 0)
1253 collection_select_all(collection);
1254 return;
1256 else if (collection->number_of_items == collection->number_selected)
1258 collection_clear_selection(collection);
1259 return;
1262 for (item = 0; item < collection->number_of_items; item++)
1263 collection->items[item].selected =
1264 !collection->items[item].selected;
1266 collection->number_selected = collection->number_of_items -
1267 collection->number_selected;
1269 /* Have to redraw everything... */
1270 gtk_widget_queue_draw(GTK_WIDGET(collection));
1272 EMIT_SELECTION_CHANGED(collection, current_event_time);
1275 /* Unselect all items except number item, which is selected (-1 to unselect
1276 * everything).
1278 void collection_clear_except(Collection *collection, gint item)
1280 GtkWidget *widget;
1281 int i = 0;
1282 int end; /* Selected items to end up with */
1284 g_return_if_fail(collection != NULL);
1285 g_return_if_fail(IS_COLLECTION(collection));
1286 g_return_if_fail(item >= -1 && item < collection->number_of_items);
1288 widget = GTK_WIDGET(collection);
1290 if (item == -1)
1291 end = 0;
1292 else
1294 collection_select_item(collection, item);
1295 end = 1;
1298 if (collection->number_selected == 0)
1299 return;
1301 while (collection->number_selected > end)
1303 while (i == item || !collection->items[i].selected)
1304 i++;
1306 collection->items[i].selected = FALSE;
1307 collection_draw_item(collection, i, TRUE);
1308 i++;
1310 collection->number_selected--;
1313 if (end == 0)
1314 g_signal_emit(collection, collection_signals[LOSE_SELECTION], 0,
1315 current_event_time);
1316 EMIT_SELECTION_CHANGED(collection, current_event_time);
1319 /* Unselect all items in the collection */
1320 void collection_clear_selection(Collection *collection)
1322 g_return_if_fail(collection != NULL);
1323 g_return_if_fail(IS_COLLECTION(collection));
1325 collection_clear_except(collection, -1);
1328 /* Force a redraw of the specified item, if it is visible */
1329 void collection_draw_item(Collection *collection, gint item, gboolean blank)
1331 GdkRectangle area;
1332 GtkWidget *widget;
1333 int row, col;
1335 g_return_if_fail(collection != NULL);
1336 /* g_return_if_fail(IS_COLLECTION(collection)); (slow) */
1337 g_return_if_fail(item >= 0 &&
1338 (item == 0 || item < collection->number_of_items));
1340 widget = GTK_WIDGET(collection);
1341 if (!GTK_WIDGET_REALIZED(widget))
1342 return;
1344 collection_item_to_rowcol(collection, item, &row, &col);
1346 collection_get_item_area(collection, row, col, &area);
1348 gdk_window_invalidate_rect(widget->window, &area, FALSE);
1351 void collection_set_item_size(Collection *collection, int width, int height)
1353 GtkWidget *widget;
1355 g_return_if_fail(collection != NULL);
1356 g_return_if_fail(IS_COLLECTION(collection));
1357 g_return_if_fail(width > 4 && height > 4);
1359 if (collection->item_width == width &&
1360 collection->item_height == height)
1361 return;
1363 widget = GTK_WIDGET(collection);
1365 collection->item_width = width;
1366 collection->item_height = height;
1368 if (GTK_WIDGET_REALIZED(widget))
1370 gint window_width;
1372 gdk_drawable_get_size(widget->window, &window_width, NULL);
1373 collection->columns = MAX(window_width / collection->item_width,
1375 if (collection->cursor_item != -1)
1376 scroll_to_show(collection, collection->cursor_item);
1377 gtk_widget_queue_draw(widget);
1380 gtk_widget_queue_resize(GTK_WIDGET(collection));
1383 static int (*cmp_callback)(const void *a, const void *b) = NULL;
1384 static int collection_cmp(const void *a, const void *b)
1386 return cmp_callback(((CollectionItem *) a)->data,
1387 ((CollectionItem *) b)->data);
1389 static int collection_rcmp(const void *a, const void *b)
1391 return -cmp_callback(((CollectionItem *) a)->data,
1392 ((CollectionItem *) b)->data);
1395 /* Cursor is positioned on item with the same data as before the sort.
1396 * Same for the wink item.
1398 void collection_qsort(Collection *collection,
1399 int (*compar)(const void *, const void *),
1400 GtkSortType order)
1402 int cursor, wink, items, wink_on_map;
1403 gpointer cursor_data = NULL;
1404 gpointer wink_data = NULL;
1405 gpointer wink_on_map_data = NULL;
1406 CollectionItem *array;
1407 int i;
1408 int mul = order == GTK_SORT_ASCENDING ? 1 : -1;
1410 g_return_if_fail(collection != NULL);
1411 g_return_if_fail(IS_COLLECTION(collection));
1412 g_return_if_fail(compar != NULL);
1413 g_return_if_fail(cmp_callback == NULL);
1415 /* Check to see if it needs sorting (saves redrawing) */
1416 if (collection->number_of_items < 2)
1417 return;
1419 array = collection->items;
1420 for (i = 1; i < collection->number_of_items; i++)
1422 if (mul * compar(array[i - 1].data, array[i].data) > 0)
1423 break;
1425 if (i == collection->number_of_items)
1426 return; /* Already sorted */
1428 items = collection->number_of_items;
1430 wink_on_map = collection->wink_on_map;
1431 if (wink_on_map >= 0 && wink_on_map < items)
1433 wink_on_map_data = collection->items[wink_on_map].data;
1434 collection->wink_on_map = -1;
1436 else
1437 wink = -1;
1439 wink = collection->wink_item;
1440 if (wink >= 0 && wink < items)
1442 wink_data = collection->items[wink].data;
1443 collection->wink_item = -1;
1445 else
1446 wink = -1;
1448 cursor = collection->cursor_item;
1449 if (cursor >= 0 && cursor < items)
1450 cursor_data = collection->items[cursor].data;
1451 else
1452 cursor = -1;
1454 cmp_callback = compar;
1455 qsort(collection->items, items, sizeof(collection->items[0]),
1456 order == GTK_SORT_ASCENDING ? collection_cmp
1457 : collection_rcmp);
1458 cmp_callback = NULL;
1460 if (cursor > -1 || wink > -1 || wink_on_map > -1)
1462 int item;
1464 for (item = 0; item < items; item++)
1466 if (collection->items[item].data == cursor_data)
1467 collection_set_cursor_item(collection, item,
1468 TRUE);
1469 if (collection->items[item].data == wink_on_map_data)
1470 collection->wink_on_map = item;
1471 if (collection->items[item].data == wink_data)
1473 collection->cursor_item_old = item;
1474 collection->wink_item = item;
1475 scroll_to_show(collection, item);
1480 gtk_widget_queue_draw(GTK_WIDGET(collection));
1483 /* Find an item in a sorted collection.
1484 * Returns the item number, or -1 if not found.
1486 int collection_find_item(Collection *collection, gpointer data,
1487 int (*compar)(const void *, const void *),
1488 GtkSortType order)
1490 int lower, upper;
1491 int mul = order == GTK_SORT_ASCENDING ? 1 : -1;
1493 g_return_val_if_fail(collection != NULL, -1);
1494 g_return_val_if_fail(IS_COLLECTION(collection), -1);
1495 g_return_val_if_fail(compar != NULL, -1);
1497 /* If item is here, then: lower <= i < upper */
1498 lower = 0;
1499 upper = collection->number_of_items;
1501 while (lower < upper)
1503 int i, cmp;
1505 i = (lower + upper) >> 1;
1507 cmp = mul * compar(collection->items[i].data, data);
1508 if (cmp == 0)
1509 return i;
1511 if (cmp > 0)
1512 upper = i;
1513 else
1514 lower = i + 1;
1517 return -1;
1520 /* Return the number of the item under the point (x,y), or -1 for none.
1521 * This may call your test_point callback. The point is relative to the
1522 * collection's origin.
1524 int collection_get_item(Collection *collection, int x, int y)
1526 int row, col;
1527 int width;
1528 int item;
1530 g_return_val_if_fail(collection != NULL, -1);
1532 col = x / collection->item_width;
1533 row = y / collection->item_height;
1535 if (col >= collection->columns)
1536 col = collection->columns - 1;
1538 if (col < 0 || row < 0)
1539 return -1;
1541 if (col == collection->columns - 1)
1542 width = collection->item_width << 1;
1543 else
1544 width = collection->item_width;
1546 item = collection_rowcol_to_item(collection, row, col);
1547 if (item >= collection->number_of_items)
1548 return -1;
1550 x -= col * collection->item_width;
1551 y -= row * collection->item_height;
1553 if (collection->test_point(collection, x, y,
1554 &collection->items[item], width, collection->item_height,
1555 collection->cb_user_data))
1556 return item;
1558 return -1;
1561 /* Set the cursor/highlight over the given item. Passing -1
1562 * hides the cursor. As a special case, you may set the cursor item
1563 * to zero when there are no items.
1565 void collection_set_cursor_item(Collection *collection, gint item,
1566 gboolean may_scroll)
1568 int old_item;
1570 g_return_if_fail(collection != NULL);
1571 g_return_if_fail(IS_COLLECTION(collection));
1572 g_return_if_fail(item >= -1 &&
1573 (item < collection->number_of_items || item == 0));
1575 old_item = collection->cursor_item;
1577 if (old_item == item)
1578 return;
1580 collection->cursor_item = item;
1582 if (old_item != -1)
1583 collection_draw_item(collection, old_item, TRUE);
1585 if (item != -1)
1587 collection_draw_item(collection, item, TRUE);
1588 if (may_scroll)
1589 scroll_to_show(collection, item);
1591 else if (old_item != -1)
1592 collection->cursor_item_old = old_item;
1595 /* Briefly highlight an item to draw the user's attention to it.
1596 * -1 cancels the effect, as does deleting items, sorting the collection
1597 * or starting a new wink effect.
1598 * Otherwise, the effect will cancel itself after a short pause.
1599 * */
1600 void collection_wink_item(Collection *collection, gint item)
1602 g_return_if_fail(collection != NULL);
1603 g_return_if_fail(IS_COLLECTION(collection));
1604 g_return_if_fail(item >= -1 && item < collection->number_of_items);
1606 if (collection->wink_item != -1)
1607 cancel_wink(collection);
1608 if (item == -1)
1609 return;
1611 if (!GTK_WIDGET_MAPPED(GTK_WIDGET(collection)))
1613 collection->wink_on_map = item;
1614 return;
1617 collection->cursor_item_old = collection->wink_item = item;
1618 collection->winks_left = MAX_WINKS;
1620 collection->wink_timeout = g_timeout_add(70,
1621 (GSourceFunc) wink_timeout,
1622 collection);
1623 scroll_to_show(collection, item);
1624 invert_wink(collection);
1626 gdk_flush();
1629 /* Call test(item, data) on each item in the collection.
1630 * Remove all items for which it returns TRUE. test() should
1631 * free the data before returning TRUE. The collection is in an
1632 * inconsistant state during this call (ie, when test() is called).
1634 * If test is NULL, remove all items.
1636 void collection_delete_if(Collection *collection,
1637 gboolean (*test)(gpointer item, gpointer data),
1638 gpointer data)
1640 int in, out = 0;
1641 int selected = 0;
1642 int cursor;
1644 g_return_if_fail(collection != NULL);
1645 g_return_if_fail(IS_COLLECTION(collection));
1647 cursor = collection->cursor_item;
1649 for (in = 0; in < collection->number_of_items; in++)
1651 if (test && !test(collection->items[in].data, data))
1653 /* Keep item */
1654 if (collection->items[in].selected)
1656 collection->items[out].selected = TRUE;
1657 selected++;
1659 else
1660 collection->items[out].selected = FALSE;
1662 collection->items[out].data =
1663 collection->items[in].data;
1664 collection->items[out].view_data =
1665 collection->items[in].view_data;
1666 out++;
1668 else
1670 /* Remove item */
1671 if (collection->free_item)
1672 collection->free_item(collection,
1673 &collection->items[in]);
1675 if (collection->cursor_item >= in)
1676 cursor--;
1680 if (in != out)
1682 collection->cursor_item = cursor;
1684 if (collection->wink_item != -1)
1686 collection->wink_item = -1;
1687 g_source_remove(collection->wink_timeout);
1690 collection->number_of_items = out;
1691 if (collection->number_selected && !selected)
1693 /* We've lost all the selected items */
1694 g_signal_emit(collection,
1695 collection_signals[LOSE_SELECTION], 0,
1696 current_event_time);
1699 collection->number_selected = selected;
1700 resize_arrays(collection,
1701 MAX(collection->number_of_items, MINIMUM_ITEMS));
1703 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection)))
1704 gtk_widget_queue_draw(GTK_WIDGET(collection));
1706 gtk_widget_queue_resize(GTK_WIDGET(collection));
1710 /* Move the cursor by the given row and column offsets.
1711 * Moving by (0,0) can be used to simply make the cursor appear.
1713 void collection_move_cursor(Collection *collection, int drow, int dcol)
1715 int row, col, item;
1716 int first, last, total_rows, total_cols;
1718 g_return_if_fail(collection != NULL);
1719 g_return_if_fail(IS_COLLECTION(collection));
1721 if (!collection->number_of_items)
1723 /* Show the cursor, even though there are no items */
1724 collection_set_cursor_item(collection, 0, TRUE);
1725 return;
1728 get_visible_limits(collection, &first, &last);
1729 total_rows = collection_get_rows(collection);
1730 total_cols = collection_get_cols(collection);
1732 item = collection->cursor_item;
1733 if (item == -1)
1735 item = MIN(collection->cursor_item_old,
1736 collection->number_of_items - 1);
1739 if (item == -1)
1741 col = 0;
1742 row = first;
1744 else
1746 collection_item_to_rowcol(collection, item, &row, &col);
1748 col += dcol;
1749 if (collection->vertical_order)
1751 col = MAX(0,col);
1752 col = MIN(col, total_cols - 1);
1755 if (row < first)
1756 row = first;
1757 else if (row > last)
1758 row = last;
1759 else {
1760 row += drow;
1761 if (collection->vertical_order)
1763 if (row >= total_rows)
1765 row = 0;
1766 col += 1;
1769 else
1771 row = MAX(row, 0);
1772 row = MIN(row, total_rows - 1);
1777 item = collection_rowcol_to_item(collection, row, col);
1779 item = MAX(item, 0);
1780 item = MIN(item, collection->number_of_items-1);
1782 collection_set_cursor_item(collection, item, TRUE);
1785 /* Start a lasso box drag */
1786 void collection_lasso_box(Collection *collection, int x, int y)
1788 collection->drag_box_x[0] = x;
1789 collection->drag_box_y[0] = y;
1790 collection->drag_box_x[1] = x;
1791 collection->drag_box_y[1] = y;
1793 add_lasso_box(collection);
1796 /* Remove the lasso box. Applies fn to each item inside the box.
1797 * fn may be GDK_INVERT, GDK_SET, GDK_NOOP or GDK_CLEAR.
1799 void collection_end_lasso(Collection *collection, GdkFunction fn)
1801 if (fn != GDK_CLEAR)
1803 GdkRectangle region;
1805 find_lasso_area(collection, &region);
1807 collection_process_area(collection, &region, fn,
1808 GDK_CURRENT_TIME);
1811 abort_lasso(collection);
1814 /* Unblock the selection_changed signal, emitting the signal if the
1815 * block counter reaches zero and emit is TRUE.
1817 void collection_unblock_selection_changed(Collection *collection,
1818 guint time,
1819 gboolean emit)
1821 g_return_if_fail(collection != NULL);
1822 g_return_if_fail(IS_COLLECTION(collection));
1823 g_return_if_fail(collection->block_selection_changed > 0);
1825 collection->block_selection_changed--;
1827 if (emit && !collection->block_selection_changed)
1828 g_signal_emit(collection,
1829 collection_signals[SELECTION_CHANGED], 0, time);
1832 /* Translate the item number to the (row, column) form */
1833 void collection_item_to_rowcol (const Collection *collection,
1834 int item, int *row, int *col)
1836 if (!collection->vertical_order)
1838 *row = item / collection->columns;
1839 *col = item % collection->columns;
1841 else
1843 int rows = collection_get_rows(collection);
1844 *row = item % rows;
1845 *col = item / rows;
1851 /* Translate the (row, column) form to the item number.
1852 * May return a number >= collection->number_of_items.
1854 int collection_rowcol_to_item(const Collection *collection, int row, int col)
1856 if (!collection->vertical_order)
1857 return row * collection->columns + col;
1858 else
1860 int rows = collection_get_rows(collection);
1861 if (row >= rows)
1862 return collection->number_of_items;
1863 return row + col * rows;