r2596: Updated for new version.
[rox-filer.git] / ROX-Filer / src / collection.c
blobafb157da4328ad4037eccfca5c16ee4b2e87cd82
1 /*
2 * $Id$
4 * Collection - a GTK+ widget
5 * Copyright (C) 2003, 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 3 /* 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 #if 0
343 static void collection_drag_leave(GtkWidget *widget, GdkDragContext *context,
344 guint time)
346 Collection *collection = COLLECTION(widget);
348 /* Note that this isn't always called when the pointer leaves the
349 * widget; only if we highlighted an item at some point.
352 /* collection_set_autoscroll(collection, FALSE); - not needed? */
353 collection_set_cursor_item(collection, -1);
355 if (GTK_WIDGET_CLASS(parent_class)->drag_leave)
356 (*GTK_WIDGET_CLASS(parent_class)->drag_leave)(widget, context,
357 time);
359 #endif
361 static void collection_map(GtkWidget *widget)
363 Collection *collection = COLLECTION(widget);
365 if (GTK_WIDGET_CLASS(parent_class)->map)
366 (*GTK_WIDGET_CLASS(parent_class)->map)(widget);
368 if (collection->wink_on_map >= 0)
370 collection_wink_item(collection, collection->wink_on_map);
371 collection->wink_on_map = -1;
375 static void collection_realize(GtkWidget *widget)
377 Collection *collection;
378 GdkWindowAttr attributes;
379 gint attributes_mask;
380 GdkGCValues xor_values;
381 GdkColor *bg, *fg;
383 g_return_if_fail(widget != NULL);
384 g_return_if_fail(IS_COLLECTION(widget));
385 g_return_if_fail(widget->parent != NULL);
387 GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);
388 collection = COLLECTION(widget);
390 attributes.x = widget->allocation.x;
391 attributes.y = widget->allocation.y;
392 attributes.width = widget->allocation.width;
393 attributes.height = widget->allocation.height;
394 attributes.wclass = GDK_INPUT_OUTPUT;
395 attributes.window_type = GDK_WINDOW_CHILD;
396 attributes.event_mask = gtk_widget_get_events(widget) |
397 GDK_EXPOSURE_MASK |
398 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
399 GDK_BUTTON1_MOTION_MASK | GDK_BUTTON2_MOTION_MASK |
400 GDK_BUTTON3_MOTION_MASK;
401 attributes.visual = gtk_widget_get_visual(widget);
402 attributes.colormap = gtk_widget_get_colormap(widget);
404 attributes_mask = GDK_WA_X | GDK_WA_Y |
405 GDK_WA_VISUAL | GDK_WA_COLORMAP;
406 widget->window = gdk_window_new(gtk_widget_get_parent_window(widget),
407 &attributes, attributes_mask);
409 widget->style = gtk_style_attach(widget->style, widget->window);
411 gdk_window_set_user_data(widget->window, widget);
412 gdk_window_set_background(widget->window,
413 &widget->style->bg[GTK_STATE_NORMAL]);
415 bg = &widget->style->bg[GTK_STATE_NORMAL];
416 fg = &widget->style->fg[GTK_STATE_NORMAL];
417 xor_values.function = GDK_XOR;
418 xor_values.foreground.pixel = fg->pixel ^ bg->pixel;
419 collection->xor_gc = gdk_gc_new_with_values(widget->window,
420 &xor_values,
421 GDK_GC_FOREGROUND
422 | GDK_GC_FUNCTION);
425 static void collection_size_request(GtkWidget *widget,
426 GtkRequisition *requisition)
428 Collection *collection = COLLECTION(widget);
429 int rows, cols = collection->columns;
431 /* We ask for the total size we need; our containing viewport
432 * will deal with scrolling.
434 requisition->width = MIN_WIDTH;
435 rows = (collection->number_of_items + cols - 1) / cols;
436 requisition->height = rows * collection->item_height;
439 static gboolean scroll_after_alloc(Collection *collection)
441 if (collection->wink_item != -1)
442 scroll_to_show(collection, collection->wink_item);
443 else if (collection->cursor_item != -1)
444 scroll_to_show(collection, collection->cursor_item);
445 g_object_unref(G_OBJECT(collection));
447 return FALSE;
450 static void collection_size_allocate(GtkWidget *widget,
451 GtkAllocation *allocation)
453 Collection *collection;
454 int old_columns;
455 gboolean cursor_visible = FALSE;
457 g_return_if_fail(widget != NULL);
458 g_return_if_fail(IS_COLLECTION(widget));
459 g_return_if_fail(allocation != NULL);
461 collection = COLLECTION(widget);
463 if (collection->cursor_item != -1)
465 int first, last;
466 int crow = collection->cursor_item / collection->columns;
468 get_visible_limits(collection, &first, &last);
470 cursor_visible = crow >= first && crow <= last;
473 old_columns = collection->columns;
475 widget->allocation = *allocation;
477 collection->columns = allocation->width / collection->item_width;
478 if (collection->columns < 1)
479 collection->columns = 1;
481 if (GTK_WIDGET_REALIZED(widget))
483 gdk_window_move_resize(widget->window,
484 allocation->x, allocation->y,
485 allocation->width, allocation->height);
487 if (cursor_visible)
488 scroll_to_show(collection, collection->cursor_item);
491 if (old_columns != collection->columns)
493 /* Need to go around again... */
494 gtk_widget_queue_resize(widget);
496 else if (collection->wink_item != -1 || collection->cursor_item != -1)
498 /* Viewport resets the adjustments after the alloc */
499 g_object_ref(G_OBJECT(collection));
500 g_idle_add((GSourceFunc) scroll_after_alloc, collection);
504 /* Return the area occupied by the item at (row, col) by filling
505 * in 'area'.
507 static void collection_get_item_area(Collection *collection,
508 int row, int col,
509 GdkRectangle *area)
512 area->x = col * collection->item_width;
513 area->y = row * collection->item_height;
515 area->width = collection->item_width;
516 area->height = collection->item_height;
517 if (col == collection->columns - 1)
518 area->width <<= 1;
521 static gint collection_expose(GtkWidget *widget, GdkEventExpose *event)
523 Collection *collection;
524 GdkRectangle item_area;
525 int row, col;
526 int item;
527 int start_row, last_row;
528 int start_col, last_col;
529 int phys_last_col;
531 g_return_val_if_fail(widget != NULL, FALSE);
532 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
533 g_return_val_if_fail(event != NULL, FALSE);
535 gtk_paint_flat_box(widget->style, widget->window, GTK_STATE_NORMAL,
536 GTK_SHADOW_NONE, &event->area,
537 widget, "base", 0, 0, -1, -1);
539 collection = COLLECTION(widget);
541 /* Calculate the ranges to plot */
542 start_row = event->area.y / collection->item_height;
543 last_row = (event->area.y + event->area.height - 1)
544 / collection->item_height;
545 row = start_row;
547 start_col = event->area.x / collection->item_width;
548 phys_last_col = (event->area.x + event->area.width - 1)
549 / collection->item_width;
551 /* The right-most column may be wider than the others.
552 * Therefore, to redraw the area after the last 'real' column
553 * we may have to draw the right-most column.
555 if (start_col >= collection->columns)
556 start_col = collection->columns - 1;
558 if (phys_last_col >= collection->columns)
559 last_col = collection->columns - 1;
560 else
561 last_col = phys_last_col;
563 col = start_col;
565 item = row * collection->columns + col;
567 while ((item == 0 || item < collection->number_of_items)
568 && row <= last_row)
570 collection_get_item_area(collection, row, col, &item_area);
572 draw_one_item(collection, item, &item_area);
573 col++;
575 if (col > last_col)
577 col = start_col;
578 row++;
579 item = row * collection->columns + col;
581 else
582 item++;
585 if (collection->lasso_box)
586 draw_lasso_box(collection);
588 return FALSE;
591 static void default_draw_item(GtkWidget *widget,
592 CollectionItem *item,
593 GdkRectangle *area,
594 gpointer user_data)
596 gdk_draw_arc(widget->window,
597 item->selected ? widget->style->white_gc
598 : widget->style->black_gc,
599 TRUE,
600 area->x, area->y,
601 COLLECTION(widget)->item_width, area->height,
602 0, 360 * 64);
606 static gboolean default_test_point(Collection *collection,
607 int point_x, int point_y,
608 CollectionItem *item,
609 int width, int height,
610 gpointer user_data)
612 float f_x, f_y;
614 /* Convert to point in unit circle */
615 f_x = ((float) point_x / width) - 0.5;
616 f_y = ((float) point_y / height) - 0.5;
618 return (f_x * f_x) + (f_y * f_y) <= .25;
621 static void collection_set_property(GObject *object,
622 guint prop_id,
623 const GValue *value,
624 GParamSpec *pspec)
626 Collection *collection;
628 collection = COLLECTION(object);
630 switch (prop_id)
632 case PROP_VADJUSTMENT:
633 collection_set_adjustment(collection,
634 g_value_get_object(value));
635 break;
636 default:
637 G_OBJECT_WARN_INVALID_PROPERTY_ID(object,
638 prop_id, pspec);
639 break;
643 static void collection_set_adjustment(Collection *collection,
644 GtkAdjustment *vadj)
646 if (vadj)
647 g_return_if_fail(GTK_IS_ADJUSTMENT(vadj));
648 else
649 vadj = GTK_ADJUSTMENT(gtk_adjustment_new(0.0,
650 0.0, 0.0,
651 0.0, 0.0, 0.0));
653 if (collection->vadj == vadj)
654 return;
656 if (collection->vadj)
657 g_object_unref(G_OBJECT(collection->vadj));
659 collection->vadj = vadj;
660 g_object_ref(G_OBJECT(collection->vadj));
661 gtk_object_sink(GTK_OBJECT(collection->vadj));
664 static void collection_get_property(GObject *object,
665 guint prop_id,
666 GValue *value,
667 GParamSpec *pspec)
669 Collection *collection;
671 collection = COLLECTION(object);
673 switch (prop_id)
675 case PROP_VADJUSTMENT:
676 g_value_set_object(value, G_OBJECT(collection->vadj));
677 break;
678 default:
679 G_OBJECT_WARN_INVALID_PROPERTY_ID(object,
680 prop_id, pspec);
681 break;
685 static void resize_arrays(Collection *collection, guint new_size)
687 g_return_if_fail(collection != NULL);
688 g_return_if_fail(IS_COLLECTION(collection));
689 g_return_if_fail(new_size >= collection->number_of_items);
691 collection->items = g_realloc(collection->items,
692 sizeof(CollectionItem) * new_size);
693 collection->array_size = new_size;
696 static gint collection_key_press(GtkWidget *widget, GdkEventKey *event)
698 Collection *collection;
699 int item;
700 int key;
702 g_return_val_if_fail(widget != NULL, FALSE);
703 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
704 g_return_val_if_fail(event != NULL, FALSE);
706 collection = (Collection *) widget;
707 item = collection->cursor_item;
709 key = event->keyval;
710 if (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK))
712 if (key == GDK_Left || key == GDK_Right || \
713 key == GDK_Up || key == GDK_Down)
714 return TRUE;
715 return FALSE;
718 switch (key)
720 case GDK_Left:
721 collection_move_cursor(collection, 0, -1);
722 break;
723 case GDK_Right:
724 collection_move_cursor(collection, 0, 1);
725 break;
726 case GDK_Up:
727 collection_move_cursor(collection, -1, 0);
728 break;
729 case GDK_Down:
730 collection_move_cursor(collection, 1, 0);
731 break;
732 case GDK_Home:
733 collection_set_cursor_item(collection, 0, TRUE);
734 break;
735 case GDK_End:
736 collection_set_cursor_item(collection,
737 MAX((gint) collection->number_of_items - 1, 0),
738 TRUE);
739 break;
740 case GDK_Page_Up:
742 int first, last;
743 get_visible_limits(collection, &first, &last);
744 collection_move_cursor(collection, first - last - 1, 0);
745 break;
747 case GDK_Page_Down:
749 int first, last;
750 get_visible_limits(collection, &first, &last);
751 collection_move_cursor(collection, last - first + 1, 0);
752 break;
754 default:
755 return FALSE;
758 return TRUE;
761 /* Wheel mouse scrolling */
762 static gint collection_scroll_event(GtkWidget *widget, GdkEventScroll *event)
764 Collection *collection;
765 int diff = 0;
767 g_return_val_if_fail(widget != NULL, FALSE);
768 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
769 g_return_val_if_fail(event != NULL, FALSE);
771 collection = COLLECTION(widget);
773 if (event->direction == GDK_SCROLL_UP)
774 diff = -1;
775 else if (event->direction == GDK_SCROLL_DOWN)
776 diff = 1;
777 else
778 return FALSE;
780 if (diff)
782 int old_value = collection->vadj->value;
783 int new_value = 0;
784 gboolean box = collection->lasso_box;
785 int step = collection->vadj->page_increment / 2;
787 new_value = CLAMP(old_value + diff * step, 0.0,
788 collection->vadj->upper
789 - collection->vadj->page_size);
790 diff = new_value - old_value;
791 if (diff)
793 if (box)
795 remove_lasso_box(collection);
796 collection->drag_box_y[0] -= diff;
798 gtk_adjustment_set_value(collection->vadj, new_value);
799 if (box)
800 add_lasso_box(collection);
804 return TRUE;
807 /* 'from' and 'to' are pixel positions. 'step' is the size of each item.
808 * Returns the index of the first item covered, and the number of items.
810 static void get_range(int from, int to, int step, gint *pos, gint *len)
812 int margin = MIN(step / 4, 40);
814 if (from > to)
816 int tmp = to;
817 to = from;
818 from = tmp;
821 from = (from + margin) / step; /* First item */
822 to = (to + step - margin) / step; /* Last item (inclusive) */
824 *pos = MAX(from, 0);
825 *len = to - *pos;
828 /* Fills in the area with a rectangle corresponding to the current
829 * size of the lasso box (units of items, not pixels).
831 * The box will only span valid columns, but the total number
832 * of items is not taken into account (rows or cols).
834 static void find_lasso_area(Collection *collection, GdkRectangle *area)
836 int cols = collection->columns;
837 int dx = collection->drag_box_x[0] - collection->drag_box_x[1];
838 int dy = collection->drag_box_y[0] - collection->drag_box_y[1];
840 if (ABS(dx) < 8 && ABS(dy) < 8)
842 /* Didn't move far enough - ignore */
843 area->x = area->y = 0;
844 area->width = 0;
845 area->height = 0;
846 return;
849 get_range(collection->drag_box_x[0], collection->drag_box_x[1],
850 collection->item_width, &area->x, &area->width);
852 if (area->x >= cols)
853 area->width = 0;
854 else if (area->x + area->width > cols)
855 area->width = cols - area->x;
857 get_range(collection->drag_box_y[0], collection->drag_box_y[1],
858 collection->item_height, &area->y, &area->height);
861 static void collection_process_area(Collection *collection,
862 GdkRectangle *area,
863 GdkFunction fn,
864 guint32 time)
866 int x, y;
867 guint32 stacked_time;
868 int item;
869 gboolean changed = FALSE;
870 guint old_selected;
872 g_return_if_fail(fn == GDK_SET || fn == GDK_INVERT);
874 old_selected = collection->number_selected;
876 stacked_time = current_event_time;
877 current_event_time = time;
879 collection->block_selection_changed++;
881 for (y = area->y; y < area->y + area->height; y++)
883 item = y * collection->columns + area->x;
885 for (x = area->x; x < area->x + area->width; x++)
887 if (item >= collection->number_of_items)
888 goto out;
890 if (fn == GDK_INVERT)
891 collection_item_set_selected(collection, item,
892 !collection->items[item].selected,
893 FALSE);
894 else
895 collection_item_set_selected(collection, item,
896 TRUE, FALSE);
898 changed = TRUE;
899 item++;
903 out:
904 if (collection->number_selected && !old_selected)
905 g_signal_emit(collection,
906 collection_signals[GAIN_SELECTION], 0,
907 current_event_time);
908 else if (!collection->number_selected && old_selected)
909 g_signal_emit(collection,
910 collection_signals[LOSE_SELECTION], 0,
911 current_event_time);
913 collection_unblock_selection_changed(collection,
914 current_event_time, changed);
915 current_event_time = stacked_time;
918 static gint collection_motion_notify(GtkWidget *widget,
919 GdkEventMotion *event)
921 Collection *collection;
922 gint x, y;
924 g_return_val_if_fail(widget != NULL, FALSE);
925 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
926 g_return_val_if_fail(event != NULL, FALSE);
928 collection = COLLECTION(widget);
930 if (!collection->lasso_box)
931 return FALSE;
933 if (event->window != widget->window)
934 gdk_window_get_pointer(widget->window, &x, &y, NULL);
935 else
937 x = event->x;
938 y = event->y;
941 remove_lasso_box(collection);
942 collection->drag_box_x[1] = x;
943 collection->drag_box_y[1] = y;
944 add_lasso_box(collection);
945 return TRUE;
948 static void add_lasso_box(Collection *collection)
950 g_return_if_fail(collection != NULL);
951 g_return_if_fail(IS_COLLECTION(collection));
952 g_return_if_fail(collection->lasso_box == FALSE);
954 collection->lasso_box = TRUE;
955 draw_lasso_box(collection);
958 static void draw_lasso_box(Collection *collection)
960 GtkWidget *widget;
961 int x, y, width, height;
963 widget = GTK_WIDGET(collection);
965 x = MIN(collection->drag_box_x[0], collection->drag_box_x[1]);
966 y = MIN(collection->drag_box_y[0], collection->drag_box_y[1]);
967 width = abs(collection->drag_box_x[1] - collection->drag_box_x[0]);
968 height = abs(collection->drag_box_y[1] - collection->drag_box_y[0]);
970 /* XXX: A redraw bug sometimes leaves a one-pixel dot on the screen.
971 * As a quick hack, don't draw boxes that small for now...
973 if (width || height)
974 gdk_draw_rectangle(widget->window, collection->xor_gc, FALSE,
975 x, y, width, height);
978 static void abort_lasso(Collection *collection)
980 if (collection->lasso_box)
981 remove_lasso_box(collection);
984 static void remove_lasso_box(Collection *collection)
986 g_return_if_fail(collection != NULL);
987 g_return_if_fail(IS_COLLECTION(collection));
988 g_return_if_fail(collection->lasso_box == TRUE);
990 draw_lasso_box(collection);
992 collection->lasso_box = FALSE;
994 return;
997 /* Make sure that 'item' is fully visible (vertically), scrolling if not. */
998 static void scroll_to_show(Collection *collection, int item)
1000 int first, last, row;
1002 g_return_if_fail(collection != NULL);
1003 g_return_if_fail(IS_COLLECTION(collection));
1005 row = item / collection->columns;
1006 get_visible_limits(collection, &first, &last);
1008 if (row <= first)
1010 gtk_adjustment_set_value(collection->vadj,
1011 row * collection->item_height);
1013 else if (row >= last)
1015 GtkWidget *widget = (GtkWidget *) collection;
1016 gint height;
1018 if (GTK_WIDGET_REALIZED(widget))
1020 height = collection->vadj->page_size;
1021 gtk_adjustment_set_value(collection->vadj,
1022 (row + 1) * collection->item_height - height);
1027 /* Return the first and last rows which are [partly] visible. Does not
1028 * ensure that the rows actually exist (contain items).
1030 static void get_visible_limits(Collection *collection, int *first, int *last)
1032 GtkWidget *widget = (GtkWidget *) collection;
1033 gint scroll = 0, height;
1035 g_return_if_fail(collection != NULL);
1036 g_return_if_fail(IS_COLLECTION(collection));
1037 g_return_if_fail(first != NULL && last != NULL);
1039 if (!GTK_WIDGET_REALIZED(widget))
1041 *first = 0;
1042 *last = 0;
1044 else
1046 scroll = collection->vadj->value;
1047 height = collection->vadj->page_size;
1049 *first = MAX(scroll / collection->item_height, 0);
1050 *last = (scroll + height - 1) /collection->item_height;
1052 if (*last < *first)
1053 *last = *first;
1057 /* Cancel the current wink effect. */
1058 static void cancel_wink(Collection *collection)
1060 gint item;
1062 g_return_if_fail(collection != NULL);
1063 g_return_if_fail(IS_COLLECTION(collection));
1064 g_return_if_fail(collection->wink_item != -1);
1066 item = collection->wink_item;
1068 collection->wink_item = -1;
1069 gtk_timeout_remove(collection->wink_timeout);
1071 collection_draw_item(collection, item, TRUE);
1074 /* Draw/undraw a box around collection->wink_item */
1075 static void invert_wink(Collection *collection)
1077 GdkRectangle area;
1078 gint row, col;
1080 g_return_if_fail(collection->wink_item >= 0);
1082 if (!GTK_WIDGET_REALIZED(GTK_WIDGET(collection)))
1083 return;
1085 col = collection->wink_item % collection->columns;
1086 row = collection->wink_item / collection->columns;
1087 collection_get_item_area(collection, row, col, &area);
1089 gdk_draw_rectangle(((GtkWidget *) collection)->window,
1090 collection->xor_gc, FALSE,
1091 area.x, area.y,
1092 collection->item_width - 1,
1093 area.height - 1);
1096 static gboolean wink_timeout(Collection *collection)
1098 gint item;
1100 g_return_val_if_fail(collection != NULL, FALSE);
1101 g_return_val_if_fail(IS_COLLECTION(collection), FALSE);
1102 g_return_val_if_fail(collection->wink_item != -1, FALSE);
1104 item = collection->wink_item;
1106 if (collection->winks_left-- > 0)
1108 invert_wink(collection);
1109 return TRUE;
1112 collection->wink_item = -1;
1114 collection_draw_item(collection, item, TRUE);
1116 return FALSE;
1119 /* Change the selected state of an item.
1120 * Send GAIN/LOSE signals if 'signal' is TRUE.
1121 * Send SELECTION_CHANGED unless blocked.
1122 * Updates number_selected and redraws the item.
1124 static void collection_item_set_selected(Collection *collection,
1125 gint item,
1126 gboolean selected,
1127 gboolean signal)
1129 g_return_if_fail(collection != NULL);
1130 g_return_if_fail(IS_COLLECTION(collection));
1131 g_return_if_fail(item >= 0 && item < collection->number_of_items);
1133 if (collection->items[item].selected == selected)
1134 return;
1136 collection->items[item].selected = selected;
1137 collection_draw_item(collection, item, TRUE);
1139 if (selected)
1141 collection->number_selected++;
1142 if (signal && collection->number_selected == 1)
1143 g_signal_emit(collection,
1144 collection_signals[GAIN_SELECTION], 0,
1145 current_event_time);
1147 else
1149 collection->number_selected--;
1150 if (signal && collection->number_selected == 0)
1151 g_signal_emit(collection,
1152 collection_signals[LOSE_SELECTION], 0,
1153 current_event_time);
1156 EMIT_SELECTION_CHANGED(collection, current_event_time);
1159 /* Functions for managing collections */
1161 /* Remove all objects from the collection */
1162 void collection_clear(Collection *collection)
1164 collection_delete_if(collection, NULL, NULL);
1167 /* Inserts a new item at the end. The new item is unselected, and its
1168 * number is returned.
1170 gint collection_insert(Collection *collection, gpointer data, gpointer view)
1172 int item;
1174 /* g_return_val_if_fail(IS_COLLECTION(collection), -1); (slow) */
1176 item = collection->number_of_items;
1178 if (item >= collection->array_size)
1179 resize_arrays(collection, item + (item >> 1));
1181 collection->items[item].data = data;
1182 collection->items[item].view_data = view;
1183 collection->items[item].selected = FALSE;
1185 collection->number_of_items++;
1187 gtk_widget_queue_resize(GTK_WIDGET(collection));
1189 collection_draw_item(collection, item, FALSE);
1191 return item;
1194 void collection_unselect_item(Collection *collection, gint item)
1196 collection_item_set_selected(collection, item, FALSE, TRUE);
1199 void collection_select_item(Collection *collection, gint item)
1201 collection_item_set_selected(collection, item, TRUE, TRUE);
1204 void collection_toggle_item(Collection *collection, gint item)
1206 collection_item_set_selected(collection, item,
1207 !collection->items[item].selected, TRUE);
1210 /* Select all items in the collection */
1211 void collection_select_all(Collection *collection)
1213 GtkWidget *widget;
1214 int item = 0;
1216 g_return_if_fail(collection != NULL);
1217 g_return_if_fail(IS_COLLECTION(collection));
1219 widget = GTK_WIDGET(collection);
1221 if (collection->number_selected == collection->number_of_items)
1222 return; /* Nothing to do */
1224 while (collection->number_selected < collection->number_of_items)
1226 while (collection->items[item].selected)
1227 item++;
1229 collection->items[item].selected = TRUE;
1230 collection_draw_item(collection, item, TRUE);
1231 item++;
1233 collection->number_selected++;
1236 g_signal_emit(collection, collection_signals[GAIN_SELECTION], 0,
1237 current_event_time);
1238 EMIT_SELECTION_CHANGED(collection, current_event_time);
1241 /* Toggle all items in the collection */
1242 void collection_invert_selection(Collection *collection)
1244 int item;
1246 g_return_if_fail(collection != NULL);
1247 g_return_if_fail(IS_COLLECTION(collection));
1249 if (collection->number_selected == 0)
1251 collection_select_all(collection);
1252 return;
1254 else if (collection->number_of_items == collection->number_selected)
1256 collection_clear_selection(collection);
1257 return;
1260 for (item = 0; item < collection->number_of_items; item++)
1261 collection->items[item].selected =
1262 !collection->items[item].selected;
1264 collection->number_selected = collection->number_of_items -
1265 collection->number_selected;
1267 /* Have to redraw everything... */
1268 gtk_widget_queue_draw(GTK_WIDGET(collection));
1270 EMIT_SELECTION_CHANGED(collection, current_event_time);
1273 /* Unselect all items except number item, which is selected (-1 to unselect
1274 * everything).
1276 void collection_clear_except(Collection *collection, gint item)
1278 GtkWidget *widget;
1279 int i = 0;
1280 int end; /* Selected items to end up with */
1282 g_return_if_fail(collection != NULL);
1283 g_return_if_fail(IS_COLLECTION(collection));
1284 g_return_if_fail(item >= -1 && item < collection->number_of_items);
1286 widget = GTK_WIDGET(collection);
1288 if (item == -1)
1289 end = 0;
1290 else
1292 collection_select_item(collection, item);
1293 end = 1;
1296 if (collection->number_selected == 0)
1297 return;
1299 while (collection->number_selected > end)
1301 while (i == item || !collection->items[i].selected)
1302 i++;
1304 collection->items[i].selected = FALSE;
1305 collection_draw_item(collection, i, TRUE);
1306 i++;
1308 collection->number_selected--;
1311 if (end == 0)
1312 g_signal_emit(collection, collection_signals[LOSE_SELECTION], 0,
1313 current_event_time);
1314 EMIT_SELECTION_CHANGED(collection, current_event_time);
1317 /* Unselect all items in the collection */
1318 void collection_clear_selection(Collection *collection)
1320 g_return_if_fail(collection != NULL);
1321 g_return_if_fail(IS_COLLECTION(collection));
1323 collection_clear_except(collection, -1);
1326 /* Force a redraw of the specified item, if it is visible */
1327 void collection_draw_item(Collection *collection, gint item, gboolean blank)
1329 GdkRectangle area;
1330 GtkWidget *widget;
1331 int row, col;
1333 g_return_if_fail(collection != NULL);
1334 /* g_return_if_fail(IS_COLLECTION(collection)); (slow) */
1335 g_return_if_fail(item >= 0 &&
1336 (item == 0 || item < collection->number_of_items));
1338 widget = GTK_WIDGET(collection);
1339 if (!GTK_WIDGET_REALIZED(widget))
1340 return;
1342 col = item % collection->columns;
1343 row = item / collection->columns;
1345 collection_get_item_area(collection, row, col, &area);
1347 gdk_window_invalidate_rect(widget->window, &area, FALSE);
1350 void collection_set_item_size(Collection *collection, int width, int height)
1352 GtkWidget *widget;
1354 g_return_if_fail(collection != NULL);
1355 g_return_if_fail(IS_COLLECTION(collection));
1356 g_return_if_fail(width > 4 && height > 4);
1358 if (collection->item_width == width &&
1359 collection->item_height == height)
1360 return;
1362 widget = GTK_WIDGET(collection);
1364 collection->item_width = width;
1365 collection->item_height = height;
1367 if (GTK_WIDGET_REALIZED(widget))
1369 gint window_width;
1371 gdk_drawable_get_size(widget->window, &window_width, NULL);
1372 collection->columns = MAX(window_width / collection->item_width,
1374 if (collection->cursor_item != -1)
1375 scroll_to_show(collection, collection->cursor_item);
1376 gtk_widget_queue_draw(widget);
1379 gtk_widget_queue_resize(GTK_WIDGET(collection));
1382 static int (*cmp_callback)(const void *a, const void *b) = NULL;
1383 static int collection_cmp(const void *a, const void *b)
1385 return cmp_callback(((CollectionItem *) a)->data,
1386 ((CollectionItem *) b)->data);
1388 static int collection_rcmp(const void *a, const void *b)
1390 return -cmp_callback(((CollectionItem *) a)->data,
1391 ((CollectionItem *) b)->data);
1394 /* Cursor is positioned on item with the same data as before the sort.
1395 * Same for the wink item.
1397 void collection_qsort(Collection *collection,
1398 int (*compar)(const void *, const void *),
1399 GtkSortType order)
1401 int cursor, wink, items, wink_on_map;
1402 gpointer cursor_data = NULL;
1403 gpointer wink_data = NULL;
1404 gpointer wink_on_map_data = NULL;
1405 CollectionItem *array;
1406 int i;
1407 int mul = order == GTK_SORT_ASCENDING ? 1 : -1;
1409 g_return_if_fail(collection != NULL);
1410 g_return_if_fail(IS_COLLECTION(collection));
1411 g_return_if_fail(compar != NULL);
1412 g_return_if_fail(cmp_callback == NULL);
1414 /* Check to see if it needs sorting (saves redrawing) */
1415 if (collection->number_of_items < 2)
1416 return;
1418 array = collection->items;
1419 for (i = 1; i < collection->number_of_items; i++)
1421 if (mul * compar(array[i - 1].data, array[i].data) > 0)
1422 break;
1424 if (i == collection->number_of_items)
1425 return; /* Already sorted */
1427 items = collection->number_of_items;
1429 wink_on_map = collection->wink_on_map;
1430 if (wink_on_map >= 0 && wink_on_map < items)
1432 wink_on_map_data = collection->items[wink_on_map].data;
1433 collection->wink_on_map = -1;
1435 else
1436 wink = -1;
1438 wink = collection->wink_item;
1439 if (wink >= 0 && wink < items)
1441 wink_data = collection->items[wink].data;
1442 collection->wink_item = -1;
1444 else
1445 wink = -1;
1447 cursor = collection->cursor_item;
1448 if (cursor >= 0 && cursor < items)
1449 cursor_data = collection->items[cursor].data;
1450 else
1451 cursor = -1;
1453 cmp_callback = compar;
1454 qsort(collection->items, items, sizeof(collection->items[0]),
1455 order == GTK_SORT_ASCENDING ? collection_cmp
1456 : collection_rcmp);
1457 cmp_callback = NULL;
1459 if (cursor > -1 || wink > -1 || wink_on_map > -1)
1461 int item;
1463 for (item = 0; item < items; item++)
1465 if (collection->items[item].data == cursor_data)
1466 collection_set_cursor_item(collection, item,
1467 TRUE);
1468 if (collection->items[item].data == wink_on_map_data)
1469 collection->wink_on_map = item;
1470 if (collection->items[item].data == wink_data)
1472 collection->cursor_item_old = item;
1473 collection->wink_item = item;
1474 scroll_to_show(collection, item);
1479 gtk_widget_queue_draw(GTK_WIDGET(collection));
1482 /* Find an item in a sorted collection.
1483 * Returns the item number, or -1 if not found.
1485 int collection_find_item(Collection *collection, gpointer data,
1486 int (*compar)(const void *, const void *),
1487 GtkSortType order)
1489 int lower, upper;
1490 int mul = order == GTK_SORT_ASCENDING ? 1 : -1;
1492 g_return_val_if_fail(collection != NULL, -1);
1493 g_return_val_if_fail(IS_COLLECTION(collection), -1);
1494 g_return_val_if_fail(compar != NULL, -1);
1496 /* If item is here, then: lower <= i < upper */
1497 lower = 0;
1498 upper = collection->number_of_items;
1500 while (lower < upper)
1502 int i, cmp;
1504 i = (lower + upper) >> 1;
1506 cmp = mul * compar(collection->items[i].data, data);
1507 if (cmp == 0)
1508 return i;
1510 if (cmp > 0)
1511 upper = i;
1512 else
1513 lower = i + 1;
1516 return -1;
1519 /* Return the number of the item under the point (x,y), or -1 for none.
1520 * This may call your test_point callback. The point is relative to the
1521 * collection's origin.
1523 int collection_get_item(Collection *collection, int x, int y)
1525 int row, col;
1526 int width;
1527 int item;
1529 g_return_val_if_fail(collection != NULL, -1);
1531 col = x / collection->item_width;
1532 row = y / collection->item_height;
1534 if (col >= collection->columns)
1535 col = collection->columns - 1;
1537 if (col < 0 || row < 0)
1538 return -1;
1540 if (col == collection->columns - 1)
1541 width = collection->item_width << 1;
1542 else
1543 width = collection->item_width;
1545 item = col + row * collection->columns;
1546 if (item >= collection->number_of_items)
1547 return -1;
1549 x -= col * collection->item_width;
1550 y -= row * collection->item_height;
1552 if (collection->test_point(collection, x, y,
1553 &collection->items[item], width, collection->item_height,
1554 collection->cb_user_data))
1555 return item;
1557 return -1;
1560 /* Set the cursor/highlight over the given item. Passing -1
1561 * hides the cursor. As a special case, you may set the cursor item
1562 * to zero when there are no items.
1564 void collection_set_cursor_item(Collection *collection, gint item,
1565 gboolean may_scroll)
1567 int old_item;
1569 g_return_if_fail(collection != NULL);
1570 g_return_if_fail(IS_COLLECTION(collection));
1571 g_return_if_fail(item >= -1 &&
1572 (item < collection->number_of_items || item == 0));
1574 old_item = collection->cursor_item;
1576 if (old_item == item)
1577 return;
1579 collection->cursor_item = item;
1581 if (old_item != -1)
1582 collection_draw_item(collection, old_item, TRUE);
1584 if (item != -1)
1586 collection_draw_item(collection, item, TRUE);
1587 if (may_scroll)
1588 scroll_to_show(collection, item);
1590 else if (old_item != -1)
1591 collection->cursor_item_old = old_item;
1594 /* Briefly highlight an item to draw the user's attention to it.
1595 * -1 cancels the effect, as does deleting items, sorting the collection
1596 * or starting a new wink effect.
1597 * Otherwise, the effect will cancel itself after a short pause.
1598 * */
1599 void collection_wink_item(Collection *collection, gint item)
1601 g_return_if_fail(collection != NULL);
1602 g_return_if_fail(IS_COLLECTION(collection));
1603 g_return_if_fail(item >= -1 && item < collection->number_of_items);
1605 if (collection->wink_item != -1)
1606 cancel_wink(collection);
1607 if (item == -1)
1608 return;
1610 if (!GTK_WIDGET_MAPPED(GTK_WIDGET(collection)))
1612 collection->wink_on_map = item;
1613 return;
1616 collection->cursor_item_old = collection->wink_item = item;
1617 collection->winks_left = MAX_WINKS;
1619 collection->wink_timeout = gtk_timeout_add(70,
1620 (GtkFunction) wink_timeout,
1621 collection);
1622 scroll_to_show(collection, item);
1623 invert_wink(collection);
1625 gdk_flush();
1628 /* Call test(item, data) on each item in the collection.
1629 * Remove all items for which it returns TRUE. test() should
1630 * free the data before returning TRUE. The collection is in an
1631 * inconsistant state during this call (ie, when test() is called).
1633 * If test is NULL, remove all items.
1635 void collection_delete_if(Collection *collection,
1636 gboolean (*test)(gpointer item, gpointer data),
1637 gpointer data)
1639 int in, out = 0;
1640 int selected = 0;
1641 int cursor;
1643 g_return_if_fail(collection != NULL);
1644 g_return_if_fail(IS_COLLECTION(collection));
1646 cursor = collection->cursor_item;
1648 for (in = 0; in < collection->number_of_items; in++)
1650 if (test && !test(collection->items[in].data, data))
1652 /* Keep item */
1653 if (collection->items[in].selected)
1655 collection->items[out].selected = TRUE;
1656 selected++;
1658 else
1659 collection->items[out].selected = FALSE;
1661 collection->items[out].data =
1662 collection->items[in].data;
1663 collection->items[out].view_data =
1664 collection->items[in].view_data;
1665 out++;
1667 else
1669 /* Remove item */
1670 if (collection->free_item)
1671 collection->free_item(collection,
1672 &collection->items[in]);
1674 if (collection->cursor_item >= in)
1675 cursor--;
1679 if (in != out)
1681 collection->cursor_item = cursor;
1683 if (collection->wink_item != -1)
1685 collection->wink_item = -1;
1686 gtk_timeout_remove(collection->wink_timeout);
1689 collection->number_of_items = out;
1690 if (collection->number_selected && !selected)
1692 /* We've lost all the selected items */
1693 g_signal_emit(collection,
1694 collection_signals[LOSE_SELECTION], 0,
1695 current_event_time);
1698 collection->number_selected = selected;
1699 resize_arrays(collection,
1700 MAX(collection->number_of_items, MINIMUM_ITEMS));
1702 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection)))
1703 gtk_widget_queue_draw(GTK_WIDGET(collection));
1705 gtk_widget_queue_resize(GTK_WIDGET(collection));
1709 /* Move the cursor by the given row and column offsets.
1710 * Moving by (0,0) can be used to simply make the cursor appear.
1712 void collection_move_cursor(Collection *collection, int drow, int dcol)
1714 int row, col, item;
1715 int first, last, total_rows;
1717 g_return_if_fail(collection != NULL);
1718 g_return_if_fail(IS_COLLECTION(collection));
1720 if (!collection->number_of_items)
1722 /* Show the cursor, even though there are no items */
1723 collection_set_cursor_item(collection, 0, TRUE);
1724 return;
1727 get_visible_limits(collection, &first, &last);
1729 item = collection->cursor_item;
1730 if (item == -1)
1732 item = MIN(collection->cursor_item_old,
1733 collection->number_of_items - 1);
1736 if (item == -1)
1738 col = 0;
1739 row = first;
1741 else
1743 row = item / collection->columns;
1744 col = item % collection->columns + dcol;
1746 if (row < first)
1747 row = first;
1748 else if (row > last)
1749 row = last;
1750 else
1751 row = MAX(row + drow, 0);
1754 total_rows = (collection->number_of_items + collection->columns - 1)
1755 / collection->columns;
1757 if (row >= total_rows - 1 && drow > 0)
1759 row = total_rows - 1;
1760 item = col + row * collection->columns;
1761 if (item >= collection->number_of_items - 1)
1763 collection_set_cursor_item(collection,
1764 collection->number_of_items - 1,
1765 TRUE);
1766 return;
1769 if (row < 0)
1770 row = 0;
1772 item = col + row * collection->columns;
1774 if (item >= 0 && item < collection->number_of_items)
1775 collection_set_cursor_item(collection, item, TRUE);
1778 /* Start a lasso box drag */
1779 void collection_lasso_box(Collection *collection, int x, int y)
1781 collection->drag_box_x[0] = x;
1782 collection->drag_box_y[0] = y;
1783 collection->drag_box_x[1] = x;
1784 collection->drag_box_y[1] = y;
1786 add_lasso_box(collection);
1789 /* Remove the lasso box. Applies fn to each item inside the box.
1790 * fn may be GDK_INVERT, GDK_SET, GDK_NOOP or GDK_CLEAR.
1792 void collection_end_lasso(Collection *collection, GdkFunction fn)
1794 if (fn != GDK_CLEAR)
1796 GdkRectangle region;
1798 find_lasso_area(collection, &region);
1800 collection_process_area(collection, &region, fn,
1801 GDK_CURRENT_TIME);
1804 abort_lasso(collection);
1807 /* Unblock the selection_changed signal, emitting the signal if the
1808 * block counter reaches zero and emit is TRUE.
1810 void collection_unblock_selection_changed(Collection *collection,
1811 guint time,
1812 gboolean emit)
1814 g_return_if_fail(collection != NULL);
1815 g_return_if_fail(IS_COLLECTION(collection));
1816 g_return_if_fail(collection->block_selection_changed > 0);
1818 collection->block_selection_changed--;
1820 if (emit && !collection->block_selection_changed)
1821 g_signal_emit(collection,
1822 collection_signals[SELECTION_CHANGED], 0, time);