r3768: Updated years.
[rox-filer.git] / ROX-Filer / src / collection.c
blobb2e1ebf848c7b58bc64fea97133eaab468273694
1 /*
2 * $Id$
4 * Collection - a GTK+ widget
5 * Copyright (C) 2005, 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
41 #define MAX_WINKS 5 /* Should be an odd number */
43 /* Macro to emit the "selection_changed" signal only if allowed */
44 #define EMIT_SELECTION_CHANGED(collection, time) \
45 if (!collection->block_selection_changed) \
46 g_signal_emit(collection, \
47 collection_signals[SELECTION_CHANGED], 0, time)
49 enum
51 PROP_0,
52 PROP_VADJUSTMENT
55 /* Signals:
57 * void gain_selection(collection, time, user_data)
58 * We've gone from no selected items to having a selection.
59 * Time is the time of the event that caused the change, or
60 * GDK_CURRENT_TIME if not known.
62 * void lose_selection(collection, time, user_data)
63 * We've dropped to having no selected items.
64 * Time is the time of the event that caused the change, or
65 * GDK_CURRENT_TIME if not known.
67 * void selection_changed(collection, user_data)
68 * The set of selected items has changed.
69 * Time is the time of the event that caused the change, or
70 * GDK_CURRENT_TIME if not known.
72 enum
74 GAIN_SELECTION,
75 LOSE_SELECTION,
76 SELECTION_CHANGED,
77 LAST_SIGNAL
80 static guint collection_signals[LAST_SIGNAL] = { 0 };
82 static guint32 current_event_time = GDK_CURRENT_TIME;
84 static GtkWidgetClass *parent_class = NULL;
86 /* Static prototypes */
87 static void draw_one_item(Collection *collection,
88 int item,
89 GdkRectangle *area);
90 static void collection_class_init(GObjectClass *gclass, gpointer data);
91 static void collection_init(GTypeInstance *object, gpointer g_class);
92 static void collection_destroy(GtkObject *object);
93 static void collection_finalize(GObject *object);
94 static void collection_realize(GtkWidget *widget);
95 static void collection_map(GtkWidget *widget);
96 static void collection_size_request(GtkWidget *widget,
97 GtkRequisition *requisition);
98 static void collection_size_allocate(GtkWidget *widget,
99 GtkAllocation *allocation);
100 static void collection_set_adjustment(Collection *collection,
101 GtkAdjustment *vadj);
102 static void collection_get_property(GObject *object,
103 guint prop_id,
104 GValue *value,
105 GParamSpec *pspec);
106 static void collection_set_property(GObject *object,
107 guint prop_id,
108 const GValue *value,
109 GParamSpec *pspec);
110 static gint collection_expose(GtkWidget *widget, GdkEventExpose *event);
111 static void default_draw_item(GtkWidget *widget,
112 CollectionItem *data,
113 GdkRectangle *area,
114 gpointer user_data);
115 static gboolean default_test_point(Collection *collection,
116 int point_x, int point_y,
117 CollectionItem *data,
118 int width, int height,
119 gpointer user_data);
120 static gint collection_motion_notify(GtkWidget *widget,
121 GdkEventMotion *event);
122 static void add_lasso_box(Collection *collection);
123 static void abort_lasso(Collection *collection);
124 static void remove_lasso_box(Collection *collection);
125 static void draw_lasso_box(Collection *collection);
126 static void cancel_wink(Collection *collection);
127 static gint collection_key_press(GtkWidget *widget, GdkEventKey *event);
128 static void get_visible_limits(Collection *collection, int *first, int *last);
129 static void scroll_to_show(Collection *collection, int item);
130 static void collection_item_set_selected(Collection *collection,
131 gint item,
132 gboolean selected,
133 gboolean signal);
134 static gint collection_scroll_event(GtkWidget *widget, GdkEventScroll *event);
136 static void draw_focus_at(Collection *collection, GdkRectangle *area)
138 GtkWidget *widget;
139 GtkStateType state;
141 widget = GTK_WIDGET(collection);
143 if (GTK_WIDGET_FLAGS(widget) & GTK_HAS_FOCUS)
144 state = GTK_STATE_ACTIVE;
145 else
146 state = GTK_STATE_INSENSITIVE;
148 gtk_paint_focus(widget->style,
149 widget->window,
150 state,
151 NULL,
152 widget,
153 "collection",
154 area->x, area->y,
155 collection->item_width,
156 area->height);
159 static void draw_one_item(Collection *collection, int item, GdkRectangle *area)
161 if (item < collection->number_of_items)
163 collection->draw_item((GtkWidget *) collection,
164 &collection->items[item],
165 area, collection->cb_user_data);
168 if (item == collection->cursor_item)
169 draw_focus_at(collection, area);
172 GType collection_get_type(void)
174 static GType my_type = 0;
176 if (!my_type)
178 static const GTypeInfo info =
180 sizeof(CollectionClass),
181 NULL, /* base_init */
182 NULL, /* base_finalise */
183 (GClassInitFunc) collection_class_init,
184 NULL, /* class_finalise */
185 NULL, /* class_data */
186 sizeof(Collection),
187 0, /* n_preallocs */
188 collection_init
191 my_type = g_type_register_static(gtk_widget_get_type(),
192 "Collection", &info, 0);
195 return my_type;
198 typedef void (*FinalizeFn)(GObject *object);
200 static void collection_class_init(GObjectClass *gclass, gpointer data)
202 CollectionClass *collection_class = (CollectionClass *) gclass;
203 GtkObjectClass *object_class = (GtkObjectClass *) gclass;
204 GtkWidgetClass *widget_class = (GtkWidgetClass *) gclass;
206 parent_class = gtk_type_class(gtk_widget_get_type());
208 object_class->destroy = collection_destroy;
209 G_OBJECT_CLASS(object_class)->finalize =
210 (FinalizeFn) collection_finalize;
212 widget_class->realize = collection_realize;
213 widget_class->expose_event = collection_expose;
214 widget_class->size_request = collection_size_request;
215 widget_class->size_allocate = collection_size_allocate;
217 widget_class->key_press_event = collection_key_press;
219 widget_class->motion_notify_event = collection_motion_notify;
220 widget_class->map = collection_map;
221 widget_class->scroll_event = collection_scroll_event;
223 gclass->set_property = collection_set_property;
224 gclass->get_property = collection_get_property;
226 collection_class->gain_selection = NULL;
227 collection_class->lose_selection = NULL;
228 collection_class->selection_changed = NULL;
230 collection_signals[GAIN_SELECTION] = g_signal_new("gain_selection",
231 G_TYPE_FROM_CLASS(gclass),
232 G_SIGNAL_RUN_LAST,
233 G_STRUCT_OFFSET(CollectionClass,
234 gain_selection),
235 NULL, NULL,
236 g_cclosure_marshal_VOID__INT,
237 G_TYPE_NONE, 1,
238 G_TYPE_INT);
240 collection_signals[LOSE_SELECTION] = g_signal_new("lose_selection",
241 G_TYPE_FROM_CLASS(gclass),
242 G_SIGNAL_RUN_LAST,
243 G_STRUCT_OFFSET(CollectionClass,
244 lose_selection),
245 NULL, NULL,
246 g_cclosure_marshal_VOID__INT,
247 G_TYPE_NONE, 1,
248 G_TYPE_INT);
250 collection_signals[SELECTION_CHANGED] = g_signal_new(
251 "selection_changed",
252 G_TYPE_FROM_CLASS(gclass),
253 G_SIGNAL_RUN_LAST,
254 G_STRUCT_OFFSET(CollectionClass,
255 selection_changed),
256 NULL, NULL,
257 g_cclosure_marshal_VOID__INT,
258 G_TYPE_NONE, 1,
259 G_TYPE_INT);
261 g_object_class_install_property(gclass,
262 PROP_VADJUSTMENT,
263 g_param_spec_object("vadjustment",
264 "Vertical Adjustment",
265 "The GtkAdjustment for the vertical position.",
266 GTK_TYPE_ADJUSTMENT,
267 G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
270 static void collection_init(GTypeInstance *instance, gpointer g_class)
272 Collection *object = (Collection *) instance;
274 g_return_if_fail(object != NULL);
275 g_return_if_fail(IS_COLLECTION(object));
277 GTK_WIDGET_SET_FLAGS(GTK_WIDGET(object), GTK_CAN_FOCUS);
279 object->number_of_items = 0;
280 object->number_selected = 0;
281 object->block_selection_changed = 0;
282 object->columns = 1;
283 object->item_width = 64;
284 object->item_height = 64;
285 object->vadj = NULL;
287 object->items = g_new(CollectionItem, MINIMUM_ITEMS);
288 object->cursor_item = -1;
289 object->cursor_item_old = -1;
290 object->wink_item = -1;
291 object->wink_on_map = -1;
292 object->array_size = MINIMUM_ITEMS;
293 object->draw_item = default_draw_item;
294 object->test_point = default_test_point;
295 object->free_item = NULL;
298 GtkWidget* collection_new(void)
300 return GTK_WIDGET(gtk_widget_new(collection_get_type(), NULL));
303 /* After this we are unusable, but our data (if any) is still hanging around.
304 * It will be freed later with finalize.
306 static void collection_destroy(GtkObject *object)
308 Collection *collection;
310 g_return_if_fail(object != NULL);
311 g_return_if_fail(IS_COLLECTION(object));
313 collection = COLLECTION(object);
315 collection_clear(collection);
317 if (collection->vadj)
319 g_object_unref(G_OBJECT(collection->vadj));
320 collection->vadj = NULL;
323 if (GTK_OBJECT_CLASS(parent_class)->destroy)
324 (*GTK_OBJECT_CLASS(parent_class)->destroy)(object);
327 /* This is the last thing that happens to us. Free all data. */
328 static void collection_finalize(GObject *object)
330 Collection *collection;
332 collection = COLLECTION(object);
334 g_return_if_fail(collection->number_of_items == 0);
336 g_free(collection->items);
338 if (G_OBJECT_CLASS(parent_class)->finalize)
339 G_OBJECT_CLASS(parent_class)->finalize(object);
342 static void collection_map(GtkWidget *widget)
344 Collection *collection = COLLECTION(widget);
346 if (GTK_WIDGET_CLASS(parent_class)->map)
347 (*GTK_WIDGET_CLASS(parent_class)->map)(widget);
349 if (collection->wink_on_map >= 0)
351 collection_wink_item(collection, collection->wink_on_map);
352 collection->wink_on_map = -1;
356 static void collection_realize(GtkWidget *widget)
358 Collection *collection;
359 GdkWindowAttr attributes;
360 gint attributes_mask;
361 GdkGCValues xor_values;
362 GdkColor *bg, *fg;
364 g_return_if_fail(widget != NULL);
365 g_return_if_fail(IS_COLLECTION(widget));
366 g_return_if_fail(widget->parent != NULL);
368 GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);
369 collection = COLLECTION(widget);
371 attributes.x = widget->allocation.x;
372 attributes.y = widget->allocation.y;
373 attributes.width = widget->allocation.width;
374 attributes.height = widget->allocation.height;
375 attributes.wclass = GDK_INPUT_OUTPUT;
376 attributes.window_type = GDK_WINDOW_CHILD;
377 attributes.event_mask = gtk_widget_get_events(widget) |
378 GDK_EXPOSURE_MASK |
379 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
380 GDK_BUTTON1_MOTION_MASK | GDK_BUTTON2_MOTION_MASK |
381 GDK_BUTTON3_MOTION_MASK;
382 attributes.visual = gtk_widget_get_visual(widget);
383 attributes.colormap = gtk_widget_get_colormap(widget);
385 attributes_mask = GDK_WA_X | GDK_WA_Y |
386 GDK_WA_VISUAL | GDK_WA_COLORMAP;
387 widget->window = gdk_window_new(gtk_widget_get_parent_window(widget),
388 &attributes, attributes_mask);
390 widget->style = gtk_style_attach(widget->style, widget->window);
392 gdk_window_set_user_data(widget->window, widget);
393 gdk_window_set_background(widget->window,
394 &widget->style->bg[GTK_STATE_NORMAL]);
396 bg = &widget->style->bg[GTK_STATE_NORMAL];
397 fg = &widget->style->fg[GTK_STATE_NORMAL];
398 xor_values.function = GDK_XOR;
399 xor_values.foreground.pixel = fg->pixel ^ bg->pixel;
400 collection->xor_gc = gdk_gc_new_with_values(widget->window,
401 &xor_values,
402 GDK_GC_FOREGROUND
403 | GDK_GC_FUNCTION);
406 static void collection_size_request(GtkWidget *widget,
407 GtkRequisition *requisition)
409 Collection *collection = COLLECTION(widget);
410 int rows, cols = collection->columns;
412 /* We ask for the total size we need; our containing viewport
413 * will deal with scrolling.
415 requisition->width = MIN_WIDTH;
416 rows = (collection->number_of_items + cols - 1) / cols;
417 requisition->height = rows * collection->item_height;
420 static gboolean scroll_after_alloc(Collection *collection)
422 if (collection->wink_item != -1)
423 scroll_to_show(collection, collection->wink_item);
424 else if (collection->cursor_item != -1)
425 scroll_to_show(collection, collection->cursor_item);
426 g_object_unref(G_OBJECT(collection));
428 return FALSE;
431 static void collection_size_allocate(GtkWidget *widget,
432 GtkAllocation *allocation)
434 Collection *collection;
435 int old_columns;
436 gboolean cursor_visible = FALSE;
438 g_return_if_fail(widget != NULL);
439 g_return_if_fail(IS_COLLECTION(widget));
440 g_return_if_fail(allocation != NULL);
442 collection = COLLECTION(widget);
444 if (collection->cursor_item != -1)
446 int first, last;
447 int crow = collection->cursor_item / collection->columns;
449 get_visible_limits(collection, &first, &last);
451 cursor_visible = crow >= first && crow <= last;
454 old_columns = collection->columns;
456 widget->allocation = *allocation;
458 collection->columns = allocation->width / collection->item_width;
459 if (collection->columns < 1)
460 collection->columns = 1;
462 if (GTK_WIDGET_REALIZED(widget))
464 gdk_window_move_resize(widget->window,
465 allocation->x, allocation->y,
466 allocation->width, allocation->height);
468 if (cursor_visible)
469 scroll_to_show(collection, collection->cursor_item);
472 if (old_columns != collection->columns)
474 /* Need to go around again... */
475 gtk_widget_queue_resize(widget);
477 else if (collection->wink_item != -1 || collection->cursor_item != -1)
479 /* Viewport resets the adjustments after the alloc */
480 g_object_ref(G_OBJECT(collection));
481 g_idle_add((GSourceFunc) scroll_after_alloc, collection);
485 /* Return the area occupied by the item at (row, col) by filling
486 * in 'area'.
488 static void collection_get_item_area(Collection *collection,
489 int row, int col,
490 GdkRectangle *area)
493 area->x = col * collection->item_width;
494 area->y = row * collection->item_height;
496 area->width = collection->item_width;
497 area->height = collection->item_height;
498 if (col == collection->columns - 1)
499 area->width <<= 1;
502 static gint collection_expose(GtkWidget *widget, GdkEventExpose *event)
504 Collection *collection;
505 GdkRectangle item_area;
506 int row, col;
507 int item;
508 int start_row, last_row;
509 int start_col, last_col;
510 int phys_last_col;
512 g_return_val_if_fail(widget != NULL, FALSE);
513 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
514 g_return_val_if_fail(event != NULL, FALSE);
516 gtk_paint_flat_box(widget->style, widget->window, GTK_STATE_NORMAL,
517 GTK_SHADOW_NONE, &event->area,
518 widget, "base", 0, 0, -1, -1);
520 collection = COLLECTION(widget);
522 /* Calculate the ranges to plot */
523 start_row = event->area.y / collection->item_height;
524 last_row = (event->area.y + event->area.height - 1)
525 / collection->item_height;
526 row = start_row;
528 start_col = event->area.x / collection->item_width;
529 phys_last_col = (event->area.x + event->area.width - 1)
530 / collection->item_width;
532 /* The right-most column may be wider than the others.
533 * Therefore, to redraw the area after the last 'real' column
534 * we may have to draw the right-most column.
536 if (start_col >= collection->columns)
537 start_col = collection->columns - 1;
539 if (phys_last_col >= collection->columns)
540 last_col = collection->columns - 1;
541 else
542 last_col = phys_last_col;
544 col = start_col;
546 item = row * collection->columns + col;
548 while ((item == 0 || item < collection->number_of_items)
549 && row <= last_row)
551 collection_get_item_area(collection, row, col, &item_area);
553 draw_one_item(collection, item, &item_area);
554 col++;
556 if (col > last_col)
558 col = start_col;
559 row++;
560 item = row * collection->columns + col;
562 else
563 item++;
566 if (collection->lasso_box)
567 draw_lasso_box(collection);
569 return FALSE;
572 static void default_draw_item(GtkWidget *widget,
573 CollectionItem *item,
574 GdkRectangle *area,
575 gpointer user_data)
577 gdk_draw_arc(widget->window,
578 item->selected ? widget->style->white_gc
579 : widget->style->black_gc,
580 TRUE,
581 area->x, area->y,
582 COLLECTION(widget)->item_width, area->height,
583 0, 360 * 64);
587 static gboolean default_test_point(Collection *collection,
588 int point_x, int point_y,
589 CollectionItem *item,
590 int width, int height,
591 gpointer user_data)
593 float f_x, f_y;
595 /* Convert to point in unit circle */
596 f_x = ((float) point_x / width) - 0.5;
597 f_y = ((float) point_y / height) - 0.5;
599 return (f_x * f_x) + (f_y * f_y) <= .25;
602 static void collection_set_property(GObject *object,
603 guint prop_id,
604 const GValue *value,
605 GParamSpec *pspec)
607 Collection *collection;
609 collection = COLLECTION(object);
611 switch (prop_id)
613 case PROP_VADJUSTMENT:
614 collection_set_adjustment(collection,
615 g_value_get_object(value));
616 break;
617 default:
618 G_OBJECT_WARN_INVALID_PROPERTY_ID(object,
619 prop_id, pspec);
620 break;
624 static void collection_set_adjustment(Collection *collection,
625 GtkAdjustment *vadj)
627 if (vadj)
628 g_return_if_fail(GTK_IS_ADJUSTMENT(vadj));
629 else
630 vadj = GTK_ADJUSTMENT(gtk_adjustment_new(0.0,
631 0.0, 0.0,
632 0.0, 0.0, 0.0));
634 if (collection->vadj == vadj)
635 return;
637 if (collection->vadj)
638 g_object_unref(G_OBJECT(collection->vadj));
640 collection->vadj = vadj;
641 g_object_ref(G_OBJECT(collection->vadj));
642 gtk_object_sink(GTK_OBJECT(collection->vadj));
645 static void collection_get_property(GObject *object,
646 guint prop_id,
647 GValue *value,
648 GParamSpec *pspec)
650 Collection *collection;
652 collection = COLLECTION(object);
654 switch (prop_id)
656 case PROP_VADJUSTMENT:
657 g_value_set_object(value, G_OBJECT(collection->vadj));
658 break;
659 default:
660 G_OBJECT_WARN_INVALID_PROPERTY_ID(object,
661 prop_id, pspec);
662 break;
666 static void resize_arrays(Collection *collection, guint new_size)
668 g_return_if_fail(collection != NULL);
669 g_return_if_fail(IS_COLLECTION(collection));
670 g_return_if_fail(new_size >= collection->number_of_items);
672 collection->items = g_realloc(collection->items,
673 sizeof(CollectionItem) * new_size);
674 collection->array_size = new_size;
677 static gint collection_key_press(GtkWidget *widget, GdkEventKey *event)
679 Collection *collection;
680 int item;
681 int key;
683 g_return_val_if_fail(widget != NULL, FALSE);
684 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
685 g_return_val_if_fail(event != NULL, FALSE);
687 collection = (Collection *) widget;
688 item = collection->cursor_item;
690 key = event->keyval;
691 if (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK))
693 if (key == GDK_Left || key == GDK_Right || \
694 key == GDK_Up || key == GDK_Down)
695 return TRUE;
696 return FALSE;
699 switch (key)
701 case GDK_Left:
702 collection_move_cursor(collection, 0, -1);
703 break;
704 case GDK_Right:
705 collection_move_cursor(collection, 0, 1);
706 break;
707 case GDK_Up:
708 collection_move_cursor(collection, -1, 0);
709 break;
710 case GDK_Down:
711 collection_move_cursor(collection, 1, 0);
712 break;
713 case GDK_Home:
714 collection_set_cursor_item(collection, 0, TRUE);
715 break;
716 case GDK_End:
717 collection_set_cursor_item(collection,
718 MAX((gint) collection->number_of_items - 1, 0),
719 TRUE);
720 break;
721 case GDK_Page_Up:
723 int first, last;
724 get_visible_limits(collection, &first, &last);
725 collection_move_cursor(collection, first - last - 1, 0);
726 break;
728 case GDK_Page_Down:
730 int first, last;
731 get_visible_limits(collection, &first, &last);
732 collection_move_cursor(collection, last - first + 1, 0);
733 break;
735 default:
736 return FALSE;
739 return TRUE;
742 /* Wheel mouse scrolling */
743 static gint collection_scroll_event(GtkWidget *widget, GdkEventScroll *event)
745 Collection *collection;
746 int diff = 0;
748 g_return_val_if_fail(widget != NULL, FALSE);
749 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
750 g_return_val_if_fail(event != NULL, FALSE);
752 collection = COLLECTION(widget);
754 if (event->direction == GDK_SCROLL_UP)
755 diff = -1;
756 else if (event->direction == GDK_SCROLL_DOWN)
757 diff = 1;
758 else
759 return FALSE;
761 if (diff)
763 int old_value = collection->vadj->value;
764 int new_value = 0;
765 gboolean box = collection->lasso_box;
766 int step = collection->vadj->page_increment / 2;
768 new_value = CLAMP(old_value + diff * step, 0.0,
769 collection->vadj->upper
770 - collection->vadj->page_size);
771 diff = new_value - old_value;
772 if (diff)
774 if (box)
776 remove_lasso_box(collection);
777 collection->drag_box_y[0] -= diff;
779 gtk_adjustment_set_value(collection->vadj, new_value);
780 if (box)
781 add_lasso_box(collection);
785 return TRUE;
788 /* 'from' and 'to' are pixel positions. 'step' is the size of each item.
789 * Returns the index of the first item covered, and the number of items.
791 static void get_range(int from, int to, int step, gint *pos, gint *len)
793 int margin = MIN(step / 4, 40);
795 if (from > to)
797 int tmp = to;
798 to = from;
799 from = tmp;
802 from = (from + margin) / step; /* First item */
803 to = (to + step - margin) / step; /* Last item (inclusive) */
805 *pos = MAX(from, 0);
806 *len = to - *pos;
809 /* Fills in the area with a rectangle corresponding to the current
810 * size of the lasso box (units of items, not pixels).
812 * The box will only span valid columns, but the total number
813 * of items is not taken into account (rows or cols).
815 static void find_lasso_area(Collection *collection, GdkRectangle *area)
817 int cols = collection->columns;
818 int dx = collection->drag_box_x[0] - collection->drag_box_x[1];
819 int dy = collection->drag_box_y[0] - collection->drag_box_y[1];
821 if (ABS(dx) < 8 && ABS(dy) < 8)
823 /* Didn't move far enough - ignore */
824 area->x = area->y = 0;
825 area->width = 0;
826 area->height = 0;
827 return;
830 get_range(collection->drag_box_x[0], collection->drag_box_x[1],
831 collection->item_width, &area->x, &area->width);
833 if (area->x >= cols)
834 area->width = 0;
835 else if (area->x + area->width > cols)
836 area->width = cols - area->x;
838 get_range(collection->drag_box_y[0], collection->drag_box_y[1],
839 collection->item_height, &area->y, &area->height);
842 static void collection_process_area(Collection *collection,
843 GdkRectangle *area,
844 GdkFunction fn,
845 guint32 time)
847 int x, y;
848 guint32 stacked_time;
849 int item;
850 gboolean changed = FALSE;
851 guint old_selected;
853 g_return_if_fail(fn == GDK_SET || fn == GDK_INVERT);
855 old_selected = collection->number_selected;
857 stacked_time = current_event_time;
858 current_event_time = time;
860 collection->block_selection_changed++;
862 for (y = area->y; y < area->y + area->height; y++)
864 item = y * collection->columns + area->x;
866 for (x = area->x; x < area->x + area->width; x++)
868 if (item >= collection->number_of_items)
869 goto out;
871 if (fn == GDK_INVERT)
872 collection_item_set_selected(collection, item,
873 !collection->items[item].selected,
874 FALSE);
875 else
876 collection_item_set_selected(collection, item,
877 TRUE, FALSE);
879 changed = TRUE;
880 item++;
884 out:
885 if (collection->number_selected && !old_selected)
886 g_signal_emit(collection,
887 collection_signals[GAIN_SELECTION], 0,
888 current_event_time);
889 else if (!collection->number_selected && old_selected)
890 g_signal_emit(collection,
891 collection_signals[LOSE_SELECTION], 0,
892 current_event_time);
894 collection_unblock_selection_changed(collection,
895 current_event_time, changed);
896 current_event_time = stacked_time;
899 static gint collection_motion_notify(GtkWidget *widget,
900 GdkEventMotion *event)
902 Collection *collection;
903 gint x, y;
905 g_return_val_if_fail(widget != NULL, FALSE);
906 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
907 g_return_val_if_fail(event != NULL, FALSE);
909 collection = COLLECTION(widget);
911 if (!collection->lasso_box)
912 return FALSE;
914 if (event->window != widget->window)
915 gdk_window_get_pointer(widget->window, &x, &y, NULL);
916 else
918 x = event->x;
919 y = event->y;
922 remove_lasso_box(collection);
923 collection->drag_box_x[1] = x;
924 collection->drag_box_y[1] = y;
925 add_lasso_box(collection);
926 return TRUE;
929 static void add_lasso_box(Collection *collection)
931 g_return_if_fail(collection != NULL);
932 g_return_if_fail(IS_COLLECTION(collection));
933 g_return_if_fail(collection->lasso_box == FALSE);
935 collection->lasso_box = TRUE;
936 draw_lasso_box(collection);
939 static void draw_lasso_box(Collection *collection)
941 GtkWidget *widget;
942 int x, y, width, height;
944 widget = GTK_WIDGET(collection);
946 x = MIN(collection->drag_box_x[0], collection->drag_box_x[1]);
947 y = MIN(collection->drag_box_y[0], collection->drag_box_y[1]);
948 width = abs(collection->drag_box_x[1] - collection->drag_box_x[0]);
949 height = abs(collection->drag_box_y[1] - collection->drag_box_y[0]);
951 /* XXX: A redraw bug sometimes leaves a one-pixel dot on the screen.
952 * As a quick hack, don't draw boxes that small for now...
954 if (width || height)
955 gdk_draw_rectangle(widget->window, collection->xor_gc, FALSE,
956 x, y, width, height);
959 static void abort_lasso(Collection *collection)
961 if (collection->lasso_box)
962 remove_lasso_box(collection);
965 static void remove_lasso_box(Collection *collection)
967 g_return_if_fail(collection != NULL);
968 g_return_if_fail(IS_COLLECTION(collection));
969 g_return_if_fail(collection->lasso_box == TRUE);
971 draw_lasso_box(collection);
973 collection->lasso_box = FALSE;
975 return;
978 /* Make sure that 'item' is fully visible (vertically), scrolling if not. */
979 static void scroll_to_show(Collection *collection, int item)
981 int first, last, row;
983 g_return_if_fail(collection != NULL);
984 g_return_if_fail(IS_COLLECTION(collection));
986 row = item / collection->columns;
987 get_visible_limits(collection, &first, &last);
989 if (row <= first)
991 gtk_adjustment_set_value(collection->vadj,
992 row * collection->item_height);
994 else if (row >= last)
996 GtkWidget *widget = (GtkWidget *) collection;
997 gint height;
999 if (GTK_WIDGET_REALIZED(widget))
1001 height = collection->vadj->page_size;
1002 gtk_adjustment_set_value(collection->vadj,
1003 (row + 1) * collection->item_height - height);
1008 /* Return the first and last rows which are [partly] visible. Does not
1009 * ensure that the rows actually exist (contain items).
1011 static void get_visible_limits(Collection *collection, int *first, int *last)
1013 GtkWidget *widget = (GtkWidget *) collection;
1014 gint scroll = 0, height;
1016 g_return_if_fail(collection != NULL);
1017 g_return_if_fail(IS_COLLECTION(collection));
1018 g_return_if_fail(first != NULL && last != NULL);
1020 if (!GTK_WIDGET_REALIZED(widget))
1022 *first = 0;
1023 *last = 0;
1025 else
1027 scroll = collection->vadj->value;
1028 height = collection->vadj->page_size;
1030 *first = MAX(scroll / collection->item_height, 0);
1031 *last = (scroll + height - 1) /collection->item_height;
1033 if (*last < *first)
1034 *last = *first;
1038 /* Cancel the current wink effect. */
1039 static void cancel_wink(Collection *collection)
1041 gint item;
1043 g_return_if_fail(collection != NULL);
1044 g_return_if_fail(IS_COLLECTION(collection));
1045 g_return_if_fail(collection->wink_item != -1);
1047 item = collection->wink_item;
1049 collection->wink_item = -1;
1050 g_source_remove(collection->wink_timeout);
1052 collection_draw_item(collection, item, TRUE);
1055 /* Draw/undraw a box around collection->wink_item */
1056 static void invert_wink(Collection *collection)
1058 GdkRectangle area;
1059 gint row, col;
1061 g_return_if_fail(collection->wink_item >= 0);
1063 if (!GTK_WIDGET_REALIZED(GTK_WIDGET(collection)))
1064 return;
1066 col = collection->wink_item % collection->columns;
1067 row = collection->wink_item / collection->columns;
1068 collection_get_item_area(collection, row, col, &area);
1070 gdk_draw_rectangle(((GtkWidget *) collection)->window,
1071 collection->xor_gc, FALSE,
1072 area.x, area.y,
1073 collection->item_width - 1,
1074 area.height - 1);
1077 static gboolean wink_timeout(Collection *collection)
1079 gint item;
1081 g_return_val_if_fail(collection != NULL, FALSE);
1082 g_return_val_if_fail(IS_COLLECTION(collection), FALSE);
1083 g_return_val_if_fail(collection->wink_item != -1, FALSE);
1085 item = collection->wink_item;
1087 if (collection->winks_left-- > 0)
1089 invert_wink(collection);
1090 return TRUE;
1093 collection->wink_item = -1;
1095 collection_draw_item(collection, item, TRUE);
1097 return FALSE;
1100 /* Change the selected state of an item.
1101 * Send GAIN/LOSE signals if 'signal' is TRUE.
1102 * Send SELECTION_CHANGED unless blocked.
1103 * Updates number_selected and redraws the item.
1105 static void collection_item_set_selected(Collection *collection,
1106 gint item,
1107 gboolean selected,
1108 gboolean signal)
1110 g_return_if_fail(collection != NULL);
1111 g_return_if_fail(IS_COLLECTION(collection));
1112 g_return_if_fail(item >= 0 && item < collection->number_of_items);
1114 if (collection->items[item].selected == selected)
1115 return;
1117 collection->items[item].selected = selected;
1118 collection_draw_item(collection, item, TRUE);
1120 if (selected)
1122 collection->number_selected++;
1123 if (signal && collection->number_selected == 1)
1124 g_signal_emit(collection,
1125 collection_signals[GAIN_SELECTION], 0,
1126 current_event_time);
1128 else
1130 collection->number_selected--;
1131 if (signal && collection->number_selected == 0)
1132 g_signal_emit(collection,
1133 collection_signals[LOSE_SELECTION], 0,
1134 current_event_time);
1137 EMIT_SELECTION_CHANGED(collection, current_event_time);
1140 /* Functions for managing collections */
1142 /* Remove all objects from the collection */
1143 void collection_clear(Collection *collection)
1145 collection_delete_if(collection, NULL, NULL);
1148 /* Inserts a new item at the end. The new item is unselected, and its
1149 * number is returned.
1151 gint collection_insert(Collection *collection, gpointer data, gpointer view)
1153 int item;
1155 /* g_return_val_if_fail(IS_COLLECTION(collection), -1); (slow) */
1157 item = collection->number_of_items;
1159 if (item >= collection->array_size)
1160 resize_arrays(collection, item + (item >> 1));
1162 collection->items[item].data = data;
1163 collection->items[item].view_data = view;
1164 collection->items[item].selected = FALSE;
1166 collection->number_of_items++;
1168 gtk_widget_queue_resize(GTK_WIDGET(collection));
1170 collection_draw_item(collection, item, FALSE);
1172 return item;
1175 void collection_unselect_item(Collection *collection, gint item)
1177 collection_item_set_selected(collection, item, FALSE, TRUE);
1180 void collection_select_item(Collection *collection, gint item)
1182 collection_item_set_selected(collection, item, TRUE, TRUE);
1185 void collection_toggle_item(Collection *collection, gint item)
1187 collection_item_set_selected(collection, item,
1188 !collection->items[item].selected, TRUE);
1191 /* Select all items in the collection */
1192 void collection_select_all(Collection *collection)
1194 GtkWidget *widget;
1195 int item = 0;
1197 g_return_if_fail(collection != NULL);
1198 g_return_if_fail(IS_COLLECTION(collection));
1200 widget = GTK_WIDGET(collection);
1202 if (collection->number_selected == collection->number_of_items)
1203 return; /* Nothing to do */
1205 while (collection->number_selected < collection->number_of_items)
1207 while (collection->items[item].selected)
1208 item++;
1210 collection->items[item].selected = TRUE;
1211 collection_draw_item(collection, item, TRUE);
1212 item++;
1214 collection->number_selected++;
1217 g_signal_emit(collection, collection_signals[GAIN_SELECTION], 0,
1218 current_event_time);
1219 EMIT_SELECTION_CHANGED(collection, current_event_time);
1222 /* Toggle all items in the collection */
1223 void collection_invert_selection(Collection *collection)
1225 int item;
1227 g_return_if_fail(collection != NULL);
1228 g_return_if_fail(IS_COLLECTION(collection));
1230 if (collection->number_selected == 0)
1232 collection_select_all(collection);
1233 return;
1235 else if (collection->number_of_items == collection->number_selected)
1237 collection_clear_selection(collection);
1238 return;
1241 for (item = 0; item < collection->number_of_items; item++)
1242 collection->items[item].selected =
1243 !collection->items[item].selected;
1245 collection->number_selected = collection->number_of_items -
1246 collection->number_selected;
1248 /* Have to redraw everything... */
1249 gtk_widget_queue_draw(GTK_WIDGET(collection));
1251 EMIT_SELECTION_CHANGED(collection, current_event_time);
1254 /* Unselect all items except number item, which is selected (-1 to unselect
1255 * everything).
1257 void collection_clear_except(Collection *collection, gint item)
1259 GtkWidget *widget;
1260 int i = 0;
1261 int end; /* Selected items to end up with */
1263 g_return_if_fail(collection != NULL);
1264 g_return_if_fail(IS_COLLECTION(collection));
1265 g_return_if_fail(item >= -1 && item < collection->number_of_items);
1267 widget = GTK_WIDGET(collection);
1269 if (item == -1)
1270 end = 0;
1271 else
1273 collection_select_item(collection, item);
1274 end = 1;
1277 if (collection->number_selected == 0)
1278 return;
1280 while (collection->number_selected > end)
1282 while (i == item || !collection->items[i].selected)
1283 i++;
1285 collection->items[i].selected = FALSE;
1286 collection_draw_item(collection, i, TRUE);
1287 i++;
1289 collection->number_selected--;
1292 if (end == 0)
1293 g_signal_emit(collection, collection_signals[LOSE_SELECTION], 0,
1294 current_event_time);
1295 EMIT_SELECTION_CHANGED(collection, current_event_time);
1298 /* Unselect all items in the collection */
1299 void collection_clear_selection(Collection *collection)
1301 g_return_if_fail(collection != NULL);
1302 g_return_if_fail(IS_COLLECTION(collection));
1304 collection_clear_except(collection, -1);
1307 /* Force a redraw of the specified item, if it is visible */
1308 void collection_draw_item(Collection *collection, gint item, gboolean blank)
1310 GdkRectangle area;
1311 GtkWidget *widget;
1312 int row, col;
1314 g_return_if_fail(collection != NULL);
1315 /* g_return_if_fail(IS_COLLECTION(collection)); (slow) */
1316 g_return_if_fail(item >= 0 &&
1317 (item == 0 || item < collection->number_of_items));
1319 widget = GTK_WIDGET(collection);
1320 if (!GTK_WIDGET_REALIZED(widget))
1321 return;
1323 col = item % collection->columns;
1324 row = item / collection->columns;
1326 collection_get_item_area(collection, row, col, &area);
1328 gdk_window_invalidate_rect(widget->window, &area, FALSE);
1331 void collection_set_item_size(Collection *collection, int width, int height)
1333 GtkWidget *widget;
1335 g_return_if_fail(collection != NULL);
1336 g_return_if_fail(IS_COLLECTION(collection));
1337 g_return_if_fail(width > 4 && height > 4);
1339 if (collection->item_width == width &&
1340 collection->item_height == height)
1341 return;
1343 widget = GTK_WIDGET(collection);
1345 collection->item_width = width;
1346 collection->item_height = height;
1348 if (GTK_WIDGET_REALIZED(widget))
1350 gint window_width;
1352 gdk_drawable_get_size(widget->window, &window_width, NULL);
1353 collection->columns = MAX(window_width / collection->item_width,
1355 if (collection->cursor_item != -1)
1356 scroll_to_show(collection, collection->cursor_item);
1357 gtk_widget_queue_draw(widget);
1360 gtk_widget_queue_resize(GTK_WIDGET(collection));
1363 static int (*cmp_callback)(const void *a, const void *b) = NULL;
1364 static int collection_cmp(const void *a, const void *b)
1366 return cmp_callback(((CollectionItem *) a)->data,
1367 ((CollectionItem *) b)->data);
1369 static int collection_rcmp(const void *a, const void *b)
1371 return -cmp_callback(((CollectionItem *) a)->data,
1372 ((CollectionItem *) b)->data);
1375 /* Cursor is positioned on item with the same data as before the sort.
1376 * Same for the wink item.
1378 void collection_qsort(Collection *collection,
1379 int (*compar)(const void *, const void *),
1380 GtkSortType order)
1382 int cursor, wink, items, wink_on_map;
1383 gpointer cursor_data = NULL;
1384 gpointer wink_data = NULL;
1385 gpointer wink_on_map_data = NULL;
1386 CollectionItem *array;
1387 int i;
1388 int mul = order == GTK_SORT_ASCENDING ? 1 : -1;
1390 g_return_if_fail(collection != NULL);
1391 g_return_if_fail(IS_COLLECTION(collection));
1392 g_return_if_fail(compar != NULL);
1393 g_return_if_fail(cmp_callback == NULL);
1395 /* Check to see if it needs sorting (saves redrawing) */
1396 if (collection->number_of_items < 2)
1397 return;
1399 array = collection->items;
1400 for (i = 1; i < collection->number_of_items; i++)
1402 if (mul * compar(array[i - 1].data, array[i].data) > 0)
1403 break;
1405 if (i == collection->number_of_items)
1406 return; /* Already sorted */
1408 items = collection->number_of_items;
1410 wink_on_map = collection->wink_on_map;
1411 if (wink_on_map >= 0 && wink_on_map < items)
1413 wink_on_map_data = collection->items[wink_on_map].data;
1414 collection->wink_on_map = -1;
1416 else
1417 wink = -1;
1419 wink = collection->wink_item;
1420 if (wink >= 0 && wink < items)
1422 wink_data = collection->items[wink].data;
1423 collection->wink_item = -1;
1425 else
1426 wink = -1;
1428 cursor = collection->cursor_item;
1429 if (cursor >= 0 && cursor < items)
1430 cursor_data = collection->items[cursor].data;
1431 else
1432 cursor = -1;
1434 cmp_callback = compar;
1435 qsort(collection->items, items, sizeof(collection->items[0]),
1436 order == GTK_SORT_ASCENDING ? collection_cmp
1437 : collection_rcmp);
1438 cmp_callback = NULL;
1440 if (cursor > -1 || wink > -1 || wink_on_map > -1)
1442 int item;
1444 for (item = 0; item < items; item++)
1446 if (collection->items[item].data == cursor_data)
1447 collection_set_cursor_item(collection, item,
1448 TRUE);
1449 if (collection->items[item].data == wink_on_map_data)
1450 collection->wink_on_map = item;
1451 if (collection->items[item].data == wink_data)
1453 collection->cursor_item_old = item;
1454 collection->wink_item = item;
1455 scroll_to_show(collection, item);
1460 gtk_widget_queue_draw(GTK_WIDGET(collection));
1463 /* Find an item in a sorted collection.
1464 * Returns the item number, or -1 if not found.
1466 int collection_find_item(Collection *collection, gpointer data,
1467 int (*compar)(const void *, const void *),
1468 GtkSortType order)
1470 int lower, upper;
1471 int mul = order == GTK_SORT_ASCENDING ? 1 : -1;
1473 g_return_val_if_fail(collection != NULL, -1);
1474 g_return_val_if_fail(IS_COLLECTION(collection), -1);
1475 g_return_val_if_fail(compar != NULL, -1);
1477 /* If item is here, then: lower <= i < upper */
1478 lower = 0;
1479 upper = collection->number_of_items;
1481 while (lower < upper)
1483 int i, cmp;
1485 i = (lower + upper) >> 1;
1487 cmp = mul * compar(collection->items[i].data, data);
1488 if (cmp == 0)
1489 return i;
1491 if (cmp > 0)
1492 upper = i;
1493 else
1494 lower = i + 1;
1497 return -1;
1500 /* Return the number of the item under the point (x,y), or -1 for none.
1501 * This may call your test_point callback. The point is relative to the
1502 * collection's origin.
1504 int collection_get_item(Collection *collection, int x, int y)
1506 int row, col;
1507 int width;
1508 int item;
1510 g_return_val_if_fail(collection != NULL, -1);
1512 col = x / collection->item_width;
1513 row = y / collection->item_height;
1515 if (col >= collection->columns)
1516 col = collection->columns - 1;
1518 if (col < 0 || row < 0)
1519 return -1;
1521 if (col == collection->columns - 1)
1522 width = collection->item_width << 1;
1523 else
1524 width = collection->item_width;
1526 item = col + row * collection->columns;
1527 if (item >= collection->number_of_items)
1528 return -1;
1530 x -= col * collection->item_width;
1531 y -= row * collection->item_height;
1533 if (collection->test_point(collection, x, y,
1534 &collection->items[item], width, collection->item_height,
1535 collection->cb_user_data))
1536 return item;
1538 return -1;
1541 /* Set the cursor/highlight over the given item. Passing -1
1542 * hides the cursor. As a special case, you may set the cursor item
1543 * to zero when there are no items.
1545 void collection_set_cursor_item(Collection *collection, gint item,
1546 gboolean may_scroll)
1548 int old_item;
1550 g_return_if_fail(collection != NULL);
1551 g_return_if_fail(IS_COLLECTION(collection));
1552 g_return_if_fail(item >= -1 &&
1553 (item < collection->number_of_items || item == 0));
1555 old_item = collection->cursor_item;
1557 if (old_item == item)
1558 return;
1560 collection->cursor_item = item;
1562 if (old_item != -1)
1563 collection_draw_item(collection, old_item, TRUE);
1565 if (item != -1)
1567 collection_draw_item(collection, item, TRUE);
1568 if (may_scroll)
1569 scroll_to_show(collection, item);
1571 else if (old_item != -1)
1572 collection->cursor_item_old = old_item;
1575 /* Briefly highlight an item to draw the user's attention to it.
1576 * -1 cancels the effect, as does deleting items, sorting the collection
1577 * or starting a new wink effect.
1578 * Otherwise, the effect will cancel itself after a short pause.
1579 * */
1580 void collection_wink_item(Collection *collection, gint item)
1582 g_return_if_fail(collection != NULL);
1583 g_return_if_fail(IS_COLLECTION(collection));
1584 g_return_if_fail(item >= -1 && item < collection->number_of_items);
1586 if (collection->wink_item != -1)
1587 cancel_wink(collection);
1588 if (item == -1)
1589 return;
1591 if (!GTK_WIDGET_MAPPED(GTK_WIDGET(collection)))
1593 collection->wink_on_map = item;
1594 return;
1597 collection->cursor_item_old = collection->wink_item = item;
1598 collection->winks_left = MAX_WINKS;
1600 collection->wink_timeout = g_timeout_add(70,
1601 (GSourceFunc) wink_timeout,
1602 collection);
1603 scroll_to_show(collection, item);
1604 invert_wink(collection);
1606 gdk_flush();
1609 /* Call test(item, data) on each item in the collection.
1610 * Remove all items for which it returns TRUE. test() should
1611 * free the data before returning TRUE. The collection is in an
1612 * inconsistant state during this call (ie, when test() is called).
1614 * If test is NULL, remove all items.
1616 void collection_delete_if(Collection *collection,
1617 gboolean (*test)(gpointer item, gpointer data),
1618 gpointer data)
1620 int in, out = 0;
1621 int selected = 0;
1622 int cursor;
1624 g_return_if_fail(collection != NULL);
1625 g_return_if_fail(IS_COLLECTION(collection));
1627 cursor = collection->cursor_item;
1629 for (in = 0; in < collection->number_of_items; in++)
1631 if (test && !test(collection->items[in].data, data))
1633 /* Keep item */
1634 if (collection->items[in].selected)
1636 collection->items[out].selected = TRUE;
1637 selected++;
1639 else
1640 collection->items[out].selected = FALSE;
1642 collection->items[out].data =
1643 collection->items[in].data;
1644 collection->items[out].view_data =
1645 collection->items[in].view_data;
1646 out++;
1648 else
1650 /* Remove item */
1651 if (collection->free_item)
1652 collection->free_item(collection,
1653 &collection->items[in]);
1655 if (collection->cursor_item >= in)
1656 cursor--;
1660 if (in != out)
1662 collection->cursor_item = cursor;
1664 if (collection->wink_item != -1)
1666 collection->wink_item = -1;
1667 g_source_remove(collection->wink_timeout);
1670 collection->number_of_items = out;
1671 if (collection->number_selected && !selected)
1673 /* We've lost all the selected items */
1674 g_signal_emit(collection,
1675 collection_signals[LOSE_SELECTION], 0,
1676 current_event_time);
1679 collection->number_selected = selected;
1680 resize_arrays(collection,
1681 MAX(collection->number_of_items, MINIMUM_ITEMS));
1683 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection)))
1684 gtk_widget_queue_draw(GTK_WIDGET(collection));
1686 gtk_widget_queue_resize(GTK_WIDGET(collection));
1690 /* Move the cursor by the given row and column offsets.
1691 * Moving by (0,0) can be used to simply make the cursor appear.
1693 void collection_move_cursor(Collection *collection, int drow, int dcol)
1695 int row, col, item;
1696 int first, last, total_rows;
1698 g_return_if_fail(collection != NULL);
1699 g_return_if_fail(IS_COLLECTION(collection));
1701 if (!collection->number_of_items)
1703 /* Show the cursor, even though there are no items */
1704 collection_set_cursor_item(collection, 0, TRUE);
1705 return;
1708 get_visible_limits(collection, &first, &last);
1710 item = collection->cursor_item;
1711 if (item == -1)
1713 item = MIN(collection->cursor_item_old,
1714 collection->number_of_items - 1);
1717 if (item == -1)
1719 col = 0;
1720 row = first;
1722 else
1724 row = item / collection->columns;
1725 col = item % collection->columns + dcol;
1727 if (row < first)
1728 row = first;
1729 else if (row > last)
1730 row = last;
1731 else
1732 row = MAX(row + drow, 0);
1735 total_rows = (collection->number_of_items + collection->columns - 1)
1736 / collection->columns;
1738 if (row >= total_rows - 1 && drow > 0)
1740 row = total_rows - 1;
1741 item = col + row * collection->columns;
1742 if (item >= collection->number_of_items - 1)
1744 collection_set_cursor_item(collection,
1745 collection->number_of_items - 1,
1746 TRUE);
1747 return;
1750 if (row < 0)
1751 row = 0;
1753 item = col + row * collection->columns;
1755 if (item >= 0 && item < collection->number_of_items)
1756 collection_set_cursor_item(collection, item, TRUE);
1759 /* Start a lasso box drag */
1760 void collection_lasso_box(Collection *collection, int x, int y)
1762 collection->drag_box_x[0] = x;
1763 collection->drag_box_y[0] = y;
1764 collection->drag_box_x[1] = x;
1765 collection->drag_box_y[1] = y;
1767 add_lasso_box(collection);
1770 /* Remove the lasso box. Applies fn to each item inside the box.
1771 * fn may be GDK_INVERT, GDK_SET, GDK_NOOP or GDK_CLEAR.
1773 void collection_end_lasso(Collection *collection, GdkFunction fn)
1775 if (fn != GDK_CLEAR)
1777 GdkRectangle region;
1779 find_lasso_area(collection, &region);
1781 collection_process_area(collection, &region, fn,
1782 GDK_CURRENT_TIME);
1785 abort_lasso(collection);
1788 /* Unblock the selection_changed signal, emitting the signal if the
1789 * block counter reaches zero and emit is TRUE.
1791 void collection_unblock_selection_changed(Collection *collection,
1792 guint time,
1793 gboolean emit)
1795 g_return_if_fail(collection != NULL);
1796 g_return_if_fail(IS_COLLECTION(collection));
1797 g_return_if_fail(collection->block_selection_changed > 0);
1799 collection->block_selection_changed--;
1801 if (emit && !collection->block_selection_changed)
1802 g_signal_emit(collection,
1803 collection_signals[SELECTION_CHANGED], 0, time);