r4269: Bugfix: New detail crashes cleanice theme. Try another value... (reported
[rox-filer/translations.git] / ROX-Filer / src / collection.c
blobf4c05033e78759252a00899e05b7922eaedbaf75
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);
135 static int collection_get_rows(const Collection *collection);
136 static int collection_get_cols(const Collection *collection);
139 /* The number of rows, at least 1. */
140 static inline int collection_get_rows(const Collection *collection)
142 int rows = (collection->number_of_items + collection->columns - 1) /
143 collection->columns;
144 return MAX(rows, 1);
147 /* The number of columns _actually_ displayed, at least 1. This
148 * function is required in vertical_order layout-based manipulation
149 * such as moving the cursor to detect the last column. */
150 static inline int collection_get_cols(const Collection *collection)
152 if (collection->vertical_order)
154 int rows = collection_get_rows(collection);
155 int cols = (collection->number_of_items + rows - 1) / rows;
156 return MAX(1, cols);
158 else
159 return collection->columns;
162 static void draw_focus_at(Collection *collection, GdkRectangle *area)
164 GtkWidget *widget;
165 GtkStateType state;
167 widget = GTK_WIDGET(collection);
169 if (GTK_WIDGET_FLAGS(widget) & GTK_HAS_FOCUS)
170 state = GTK_STATE_ACTIVE;
171 else
172 state = GTK_STATE_INSENSITIVE;
174 gtk_paint_focus(widget->style,
175 widget->window,
176 state,
177 NULL,
178 widget,
179 "collection",
180 area->x, area->y,
181 collection->item_width,
182 area->height);
185 static void draw_one_item(Collection *collection, int item, GdkRectangle *area)
187 if (item < collection->number_of_items)
189 collection->draw_item((GtkWidget *) collection,
190 &collection->items[item],
191 area, collection->cb_user_data);
194 if (item == collection->cursor_item)
195 draw_focus_at(collection, area);
198 GType collection_get_type(void)
200 static GType my_type = 0;
202 if (!my_type)
204 static const GTypeInfo info =
206 sizeof(CollectionClass),
207 NULL, /* base_init */
208 NULL, /* base_finalise */
209 (GClassInitFunc) collection_class_init,
210 NULL, /* class_finalise */
211 NULL, /* class_data */
212 sizeof(Collection),
213 0, /* n_preallocs */
214 collection_init
217 my_type = g_type_register_static(gtk_widget_get_type(),
218 "Collection", &info, 0);
221 return my_type;
224 typedef void (*FinalizeFn)(GObject *object);
226 static void collection_class_init(GObjectClass *gclass, gpointer data)
228 CollectionClass *collection_class = (CollectionClass *) gclass;
229 GtkObjectClass *object_class = (GtkObjectClass *) gclass;
230 GtkWidgetClass *widget_class = (GtkWidgetClass *) gclass;
232 parent_class = gtk_type_class(gtk_widget_get_type());
234 object_class->destroy = collection_destroy;
235 G_OBJECT_CLASS(object_class)->finalize =
236 (FinalizeFn) collection_finalize;
238 widget_class->realize = collection_realize;
239 widget_class->expose_event = collection_expose;
240 widget_class->size_request = collection_size_request;
241 widget_class->size_allocate = collection_size_allocate;
243 widget_class->key_press_event = collection_key_press;
245 widget_class->motion_notify_event = collection_motion_notify;
246 widget_class->map = collection_map;
247 widget_class->scroll_event = collection_scroll_event;
249 gclass->set_property = collection_set_property;
250 gclass->get_property = collection_get_property;
252 collection_class->gain_selection = NULL;
253 collection_class->lose_selection = NULL;
254 collection_class->selection_changed = NULL;
256 collection_signals[GAIN_SELECTION] = g_signal_new("gain_selection",
257 G_TYPE_FROM_CLASS(gclass),
258 G_SIGNAL_RUN_LAST,
259 G_STRUCT_OFFSET(CollectionClass,
260 gain_selection),
261 NULL, NULL,
262 g_cclosure_marshal_VOID__INT,
263 G_TYPE_NONE, 1,
264 G_TYPE_INT);
266 collection_signals[LOSE_SELECTION] = g_signal_new("lose_selection",
267 G_TYPE_FROM_CLASS(gclass),
268 G_SIGNAL_RUN_LAST,
269 G_STRUCT_OFFSET(CollectionClass,
270 lose_selection),
271 NULL, NULL,
272 g_cclosure_marshal_VOID__INT,
273 G_TYPE_NONE, 1,
274 G_TYPE_INT);
276 collection_signals[SELECTION_CHANGED] = g_signal_new(
277 "selection_changed",
278 G_TYPE_FROM_CLASS(gclass),
279 G_SIGNAL_RUN_LAST,
280 G_STRUCT_OFFSET(CollectionClass,
281 selection_changed),
282 NULL, NULL,
283 g_cclosure_marshal_VOID__INT,
284 G_TYPE_NONE, 1,
285 G_TYPE_INT);
287 g_object_class_install_property(gclass,
288 PROP_VADJUSTMENT,
289 g_param_spec_object("vadjustment",
290 "Vertical Adjustment",
291 "The GtkAdjustment for the vertical position.",
292 GTK_TYPE_ADJUSTMENT,
293 G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
296 static void collection_init(GTypeInstance *instance, gpointer g_class)
298 Collection *object = (Collection *) instance;
300 g_return_if_fail(object != NULL);
301 g_return_if_fail(IS_COLLECTION(object));
303 GTK_WIDGET_SET_FLAGS(GTK_WIDGET(object), GTK_CAN_FOCUS);
305 object->number_of_items = 0;
306 object->number_selected = 0;
307 object->block_selection_changed = 0;
308 object->columns = 1;
309 object->vertical_order = FALSE;
310 object->item_width = 64;
311 object->item_height = 64;
312 object->vadj = NULL;
314 object->items = g_new(CollectionItem, MINIMUM_ITEMS);
315 object->cursor_item = -1;
316 object->cursor_item_old = -1;
317 object->wink_item = -1;
318 object->wink_on_map = -1;
319 object->array_size = MINIMUM_ITEMS;
320 object->draw_item = default_draw_item;
321 object->test_point = default_test_point;
322 object->free_item = NULL;
325 GtkWidget* collection_new(void)
327 return GTK_WIDGET(gtk_widget_new(collection_get_type(), NULL));
330 /* After this we are unusable, but our data (if any) is still hanging around.
331 * It will be freed later with finalize.
333 static void collection_destroy(GtkObject *object)
335 Collection *collection;
337 g_return_if_fail(object != NULL);
338 g_return_if_fail(IS_COLLECTION(object));
340 collection = COLLECTION(object);
342 collection_clear(collection);
344 if (collection->vadj)
346 g_object_unref(G_OBJECT(collection->vadj));
347 collection->vadj = NULL;
350 if (GTK_OBJECT_CLASS(parent_class)->destroy)
351 (*GTK_OBJECT_CLASS(parent_class)->destroy)(object);
354 /* This is the last thing that happens to us. Free all data. */
355 static void collection_finalize(GObject *object)
357 Collection *collection;
359 collection = COLLECTION(object);
361 g_return_if_fail(collection->number_of_items == 0);
363 g_free(collection->items);
365 if (G_OBJECT_CLASS(parent_class)->finalize)
366 G_OBJECT_CLASS(parent_class)->finalize(object);
369 static void collection_map(GtkWidget *widget)
371 Collection *collection = COLLECTION(widget);
373 if (GTK_WIDGET_CLASS(parent_class)->map)
374 (*GTK_WIDGET_CLASS(parent_class)->map)(widget);
376 if (collection->wink_on_map >= 0)
378 collection_wink_item(collection, collection->wink_on_map);
379 collection->wink_on_map = -1;
383 static void collection_realize(GtkWidget *widget)
385 Collection *collection;
386 GdkWindowAttr attributes;
387 gint attributes_mask;
388 GdkGCValues xor_values;
389 GdkColor *bg, *fg;
391 g_return_if_fail(widget != NULL);
392 g_return_if_fail(IS_COLLECTION(widget));
393 g_return_if_fail(widget->parent != NULL);
395 GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);
396 collection = COLLECTION(widget);
398 attributes.x = widget->allocation.x;
399 attributes.y = widget->allocation.y;
400 attributes.width = widget->allocation.width;
401 attributes.height = widget->allocation.height;
402 attributes.wclass = GDK_INPUT_OUTPUT;
403 attributes.window_type = GDK_WINDOW_CHILD;
404 attributes.event_mask = gtk_widget_get_events(widget) |
405 GDK_EXPOSURE_MASK |
406 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
407 GDK_BUTTON1_MOTION_MASK | GDK_BUTTON2_MOTION_MASK |
408 GDK_BUTTON3_MOTION_MASK;
409 attributes.visual = gtk_widget_get_visual(widget);
410 attributes.colormap = gtk_widget_get_colormap(widget);
412 attributes_mask = GDK_WA_X | GDK_WA_Y |
413 GDK_WA_VISUAL | GDK_WA_COLORMAP;
414 widget->window = gdk_window_new(gtk_widget_get_parent_window(widget),
415 &attributes, attributes_mask);
417 widget->style = gtk_style_attach(widget->style, widget->window);
419 gdk_window_set_user_data(widget->window, widget);
420 gdk_window_set_background(widget->window,
421 &widget->style->bg[GTK_STATE_NORMAL]);
423 bg = &widget->style->bg[GTK_STATE_NORMAL];
424 fg = &widget->style->fg[GTK_STATE_NORMAL];
425 xor_values.function = GDK_XOR;
426 xor_values.foreground.pixel = fg->pixel ^ bg->pixel;
427 collection->xor_gc = gdk_gc_new_with_values(widget->window,
428 &xor_values,
429 GDK_GC_FOREGROUND
430 | GDK_GC_FUNCTION);
433 static void collection_size_request(GtkWidget *widget,
434 GtkRequisition *requisition)
436 Collection *collection = COLLECTION(widget);
437 int rows;
439 /* We ask for the total size we need; our containing viewport
440 * will deal with scrolling.
442 requisition->width = MIN_WIDTH;
443 rows = collection_get_rows(collection);
444 requisition->height = rows * collection->item_height;
447 static gboolean scroll_after_alloc(Collection *collection)
449 if (collection->wink_item != -1)
450 scroll_to_show(collection, collection->wink_item);
451 else if (collection->cursor_item != -1)
452 scroll_to_show(collection, collection->cursor_item);
453 g_object_unref(G_OBJECT(collection));
455 return FALSE;
458 static void collection_size_allocate(GtkWidget *widget,
459 GtkAllocation *allocation)
461 Collection *collection;
462 int old_columns;
463 gboolean cursor_visible = FALSE;
465 g_return_if_fail(widget != NULL);
466 g_return_if_fail(IS_COLLECTION(widget));
467 g_return_if_fail(allocation != NULL);
469 collection = COLLECTION(widget);
471 if (collection->cursor_item != -1)
473 int first, last;
474 int crow, ccol;
476 collection_item_to_rowcol(collection, collection->cursor_item,
477 &crow, &ccol);
479 get_visible_limits(collection, &first, &last);
481 cursor_visible = crow >= first && crow <= last;
484 old_columns = collection->columns;
486 widget->allocation = *allocation;
488 collection->columns = allocation->width / collection->item_width;
489 if (collection->columns < 1)
490 collection->columns = 1;
492 if (GTK_WIDGET_REALIZED(widget))
494 gdk_window_move_resize(widget->window,
495 allocation->x, allocation->y,
496 allocation->width, allocation->height);
498 if (cursor_visible)
499 scroll_to_show(collection, collection->cursor_item);
502 if (old_columns != collection->columns)
504 /* Need to go around again... */
505 gtk_widget_queue_resize(widget);
507 else if (collection->wink_item != -1 || collection->cursor_item != -1)
509 /* Viewport resets the adjustments after the alloc */
510 g_object_ref(G_OBJECT(collection));
511 g_idle_add((GSourceFunc) scroll_after_alloc, collection);
515 /* Return the area occupied by the item at (row, col) by filling
516 * in 'area'.
518 static void collection_get_item_area(Collection *collection,
519 int row, int col,
520 GdkRectangle *area)
523 area->x = col * collection->item_width;
524 area->y = row * collection->item_height;
526 area->width = collection->item_width;
527 area->height = collection->item_height;
528 if (col == collection->columns - 1)
529 area->width <<= 1;
532 static gint collection_expose(GtkWidget *widget, GdkEventExpose *event)
534 Collection *collection;
535 GdkRectangle item_area;
536 int row, col;
537 int item;
538 int start_row, last_row;
539 int start_col, last_col;
540 int phys_last_col;
542 g_return_val_if_fail(widget != NULL, FALSE);
543 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
544 g_return_val_if_fail(event != NULL, FALSE);
546 /* Note about 'detail' argument:
547 * - If set to "base", lighthouse theme will crash
548 * - If set to NULL, cleanice theme will crash
550 gtk_paint_flat_box(widget->style, widget->window, GTK_STATE_NORMAL,
551 GTK_SHADOW_NONE, &event->area,
552 widget, "collection", 0, 0, -1, -1);
554 collection = COLLECTION(widget);
556 /* Calculate the ranges to plot */
557 start_row = event->area.y / collection->item_height;
558 last_row = (event->area.y + event->area.height - 1)
559 / collection->item_height;
561 if (last_row >= collection_get_rows(collection))
562 last_row = collection_get_rows(collection) - 1;
564 start_col = event->area.x / collection->item_width;
565 phys_last_col = (event->area.x + event->area.width - 1)
566 / collection->item_width;
568 /* The right-most column may be wider than the others.
569 * Therefore, to redraw the area after the last 'real' column
570 * we may have to draw the right-most column.
572 if (start_col >= collection->columns)
573 start_col = collection->columns - 1;
575 if (phys_last_col >= collection->columns)
576 last_col = collection->columns - 1;
577 else
578 last_col = phys_last_col;
581 for(row = start_row; row <= last_row; row++)
582 for(col = start_col; col <= last_col; col++)
584 item = collection_rowcol_to_item(collection, row, col);
585 if (item == 0 || item < collection->number_of_items) {
586 collection_get_item_area(collection,
587 row, col, &item_area);
588 draw_one_item(collection, item, &item_area);
592 if (collection->lasso_box)
593 draw_lasso_box(collection);
595 return FALSE;
598 static void default_draw_item(GtkWidget *widget,
599 CollectionItem *item,
600 GdkRectangle *area,
601 gpointer user_data)
603 gdk_draw_arc(widget->window,
604 item->selected ? widget->style->white_gc
605 : widget->style->black_gc,
606 TRUE,
607 area->x, area->y,
608 COLLECTION(widget)->item_width, area->height,
609 0, 360 * 64);
613 static gboolean default_test_point(Collection *collection,
614 int point_x, int point_y,
615 CollectionItem *item,
616 int width, int height,
617 gpointer user_data)
619 float f_x, f_y;
621 /* Convert to point in unit circle */
622 f_x = ((float) point_x / width) - 0.5;
623 f_y = ((float) point_y / height) - 0.5;
625 return (f_x * f_x) + (f_y * f_y) <= .25;
628 static void collection_set_property(GObject *object,
629 guint prop_id,
630 const GValue *value,
631 GParamSpec *pspec)
633 Collection *collection;
635 collection = COLLECTION(object);
637 switch (prop_id)
639 case PROP_VADJUSTMENT:
640 collection_set_adjustment(collection,
641 g_value_get_object(value));
642 break;
643 default:
644 G_OBJECT_WARN_INVALID_PROPERTY_ID(object,
645 prop_id, pspec);
646 break;
650 static void collection_set_adjustment(Collection *collection,
651 GtkAdjustment *vadj)
653 if (vadj)
654 g_return_if_fail(GTK_IS_ADJUSTMENT(vadj));
655 else
656 vadj = GTK_ADJUSTMENT(gtk_adjustment_new(0.0,
657 0.0, 0.0,
658 0.0, 0.0, 0.0));
660 if (collection->vadj == vadj)
661 return;
663 if (collection->vadj)
664 g_object_unref(G_OBJECT(collection->vadj));
666 collection->vadj = vadj;
667 g_object_ref(G_OBJECT(collection->vadj));
668 gtk_object_sink(GTK_OBJECT(collection->vadj));
671 static void collection_get_property(GObject *object,
672 guint prop_id,
673 GValue *value,
674 GParamSpec *pspec)
676 Collection *collection;
678 collection = COLLECTION(object);
680 switch (prop_id)
682 case PROP_VADJUSTMENT:
683 g_value_set_object(value, G_OBJECT(collection->vadj));
684 break;
685 default:
686 G_OBJECT_WARN_INVALID_PROPERTY_ID(object,
687 prop_id, pspec);
688 break;
692 static void resize_arrays(Collection *collection, guint new_size)
694 g_return_if_fail(collection != NULL);
695 g_return_if_fail(IS_COLLECTION(collection));
696 g_return_if_fail(new_size >= collection->number_of_items);
698 collection->items = g_realloc(collection->items,
699 sizeof(CollectionItem) * new_size);
700 collection->array_size = new_size;
703 static gint collection_key_press(GtkWidget *widget, GdkEventKey *event)
705 Collection *collection;
706 int item;
707 int key;
709 g_return_val_if_fail(widget != NULL, FALSE);
710 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
711 g_return_val_if_fail(event != NULL, FALSE);
713 collection = (Collection *) widget;
714 item = collection->cursor_item;
716 key = event->keyval;
717 if (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK))
719 if (key == GDK_Left || key == GDK_Right || \
720 key == GDK_Up || key == GDK_Down)
721 return TRUE;
722 return FALSE;
725 switch (key)
727 case GDK_Left:
728 collection_move_cursor(collection, 0, -1);
729 break;
730 case GDK_Right:
731 collection_move_cursor(collection, 0, 1);
732 break;
733 case GDK_Up:
734 collection_move_cursor(collection, -1, 0);
735 break;
736 case GDK_Down:
737 collection_move_cursor(collection, 1, 0);
738 break;
739 case GDK_Home:
740 collection_set_cursor_item(collection, 0, TRUE);
741 break;
742 case GDK_End:
743 collection_set_cursor_item(collection,
744 MAX((gint) collection->number_of_items - 1, 0),
745 TRUE);
746 break;
747 case GDK_Page_Up:
749 int first, last;
750 get_visible_limits(collection, &first, &last);
751 collection_move_cursor(collection, first - last - 1, 0);
752 break;
754 case GDK_Page_Down:
756 int first, last;
757 get_visible_limits(collection, &first, &last);
758 collection_move_cursor(collection, last - first + 1, 0);
759 break;
761 default:
762 return FALSE;
765 return TRUE;
768 /* Wheel mouse scrolling */
769 static gint collection_scroll_event(GtkWidget *widget, GdkEventScroll *event)
771 Collection *collection;
772 int diff = 0;
774 g_return_val_if_fail(widget != NULL, FALSE);
775 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
776 g_return_val_if_fail(event != NULL, FALSE);
778 collection = COLLECTION(widget);
780 if (event->direction == GDK_SCROLL_UP)
781 diff = -1;
782 else if (event->direction == GDK_SCROLL_DOWN)
783 diff = 1;
784 else
785 return FALSE;
787 if (diff)
789 int old_value = collection->vadj->value;
790 int new_value = 0;
791 gboolean box = collection->lasso_box;
792 int step = collection->vadj->page_increment / 2;
794 new_value = CLAMP(old_value + diff * step, 0.0,
795 collection->vadj->upper
796 - collection->vadj->page_size);
797 diff = new_value - old_value;
798 if (diff)
800 if (box)
802 remove_lasso_box(collection);
803 collection->drag_box_y[0] -= diff;
805 gtk_adjustment_set_value(collection->vadj, new_value);
806 if (box)
807 add_lasso_box(collection);
811 return TRUE;
814 /* 'from' and 'to' are pixel positions. 'step' is the size of each item.
815 * Returns the index of the first item covered, and the number of items.
817 static void get_range(int from, int to, int step, gint *pos, gint *len)
819 int margin = MIN(step / 4, 40);
821 if (from > to)
823 int tmp = to;
824 to = from;
825 from = tmp;
828 from = (from + margin) / step; /* First item */
829 to = (to + step - margin) / step; /* Last item (inclusive) */
831 *pos = MAX(from, 0);
832 *len = to - *pos;
835 /* Fills in the area with a rectangle corresponding to the current
836 * size of the lasso box (units of items, not pixels).
838 * The box will only span valid columns, but the total number
839 * of items is not taken into account (rows or cols).
841 static void find_lasso_area(Collection *collection, GdkRectangle *area)
843 int cols = collection->columns;
844 int dx = collection->drag_box_x[0] - collection->drag_box_x[1];
845 int dy = collection->drag_box_y[0] - collection->drag_box_y[1];
847 if (ABS(dx) < 8 && ABS(dy) < 8)
849 /* Didn't move far enough - ignore */
850 area->x = area->y = 0;
851 area->width = 0;
852 area->height = 0;
853 return;
856 get_range(collection->drag_box_x[0], collection->drag_box_x[1],
857 collection->item_width, &area->x, &area->width);
859 if (area->x >= cols)
860 area->width = 0;
861 else if (area->x + area->width > cols)
862 area->width = cols - area->x;
864 get_range(collection->drag_box_y[0], collection->drag_box_y[1],
865 collection->item_height, &area->y, &area->height);
868 static void collection_process_area(Collection *collection,
869 GdkRectangle *area,
870 GdkFunction fn,
871 guint32 time)
873 int x, y;
874 int rows = collection_get_rows(collection);
875 int cols = collection->columns;
876 guint32 stacked_time;
877 int item;
878 gboolean changed = FALSE;
879 guint old_selected;
881 g_return_if_fail(fn == GDK_SET || fn == GDK_INVERT);
883 old_selected = collection->number_selected;
885 stacked_time = current_event_time;
886 current_event_time = time;
888 collection->block_selection_changed++;
890 for (y = area->y; y < area->y + area->height && y < rows; y++)
891 for (x = area->x; x < area->x + area->width && x < cols; x++)
893 item = collection_rowcol_to_item(collection, y, x);
894 if (item < collection->number_of_items) {
895 if (fn == GDK_INVERT)
896 collection_item_set_selected(
897 collection, item,
898 !collection-> items[item].selected,
899 FALSE);
900 else
901 collection_item_set_selected(
902 collection, item, TRUE, FALSE);
904 changed = TRUE;
908 if (collection->number_selected && !old_selected)
909 g_signal_emit(collection,
910 collection_signals[GAIN_SELECTION], 0,
911 current_event_time);
912 else if (!collection->number_selected && old_selected)
913 g_signal_emit(collection,
914 collection_signals[LOSE_SELECTION], 0,
915 current_event_time);
917 collection_unblock_selection_changed(collection,
918 current_event_time, changed);
919 current_event_time = stacked_time;
922 static gint collection_motion_notify(GtkWidget *widget,
923 GdkEventMotion *event)
925 Collection *collection;
926 gint x, y;
928 g_return_val_if_fail(widget != NULL, FALSE);
929 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
930 g_return_val_if_fail(event != NULL, FALSE);
932 collection = COLLECTION(widget);
934 if (!collection->lasso_box)
935 return FALSE;
937 if (event->window != widget->window)
938 gdk_window_get_pointer(widget->window, &x, &y, NULL);
939 else
941 x = event->x;
942 y = event->y;
945 remove_lasso_box(collection);
946 collection->drag_box_x[1] = x;
947 collection->drag_box_y[1] = y;
948 add_lasso_box(collection);
949 return TRUE;
952 static void add_lasso_box(Collection *collection)
954 g_return_if_fail(collection != NULL);
955 g_return_if_fail(IS_COLLECTION(collection));
956 g_return_if_fail(collection->lasso_box == FALSE);
958 collection->lasso_box = TRUE;
959 draw_lasso_box(collection);
962 static void draw_lasso_box(Collection *collection)
964 GtkWidget *widget;
965 int x, y, width, height;
967 widget = GTK_WIDGET(collection);
969 x = MIN(collection->drag_box_x[0], collection->drag_box_x[1]);
970 y = MIN(collection->drag_box_y[0], collection->drag_box_y[1]);
971 width = abs(collection->drag_box_x[1] - collection->drag_box_x[0]);
972 height = abs(collection->drag_box_y[1] - collection->drag_box_y[0]);
974 /* XXX: A redraw bug sometimes leaves a one-pixel dot on the screen.
975 * As a quick hack, don't draw boxes that small for now...
977 if (width || height)
978 gdk_draw_rectangle(widget->window, collection->xor_gc, FALSE,
979 x, y, width, height);
982 static void abort_lasso(Collection *collection)
984 if (collection->lasso_box)
985 remove_lasso_box(collection);
988 static void remove_lasso_box(Collection *collection)
990 g_return_if_fail(collection != NULL);
991 g_return_if_fail(IS_COLLECTION(collection));
992 g_return_if_fail(collection->lasso_box == TRUE);
994 draw_lasso_box(collection);
996 collection->lasso_box = FALSE;
998 return;
1001 /* Make sure that 'item' is fully visible (vertically), scrolling if not. */
1002 static void scroll_to_show(Collection *collection, int item)
1004 int first, last, row, col;
1006 g_return_if_fail(collection != NULL);
1007 g_return_if_fail(IS_COLLECTION(collection));
1009 collection_item_to_rowcol(collection, item, &row, &col);
1010 get_visible_limits(collection, &first, &last);
1012 if (row <= first)
1014 gtk_adjustment_set_value(collection->vadj,
1015 row * collection->item_height);
1017 else if (row >= last)
1019 GtkWidget *widget = (GtkWidget *) collection;
1020 gint height;
1022 if (GTK_WIDGET_REALIZED(widget))
1024 height = collection->vadj->page_size;
1025 gtk_adjustment_set_value(collection->vadj,
1026 (row + 1) * collection->item_height - height);
1031 /* Return the first and last rows which are [partly] visible. Does not
1032 * ensure that the rows actually exist (contain items).
1034 static void get_visible_limits(Collection *collection, int *first, int *last)
1036 GtkWidget *widget = (GtkWidget *) collection;
1037 gint scroll = 0, height;
1039 g_return_if_fail(collection != NULL);
1040 g_return_if_fail(IS_COLLECTION(collection));
1041 g_return_if_fail(first != NULL && last != NULL);
1043 if (!GTK_WIDGET_REALIZED(widget))
1045 *first = 0;
1046 *last = 0;
1048 else
1050 scroll = collection->vadj->value;
1051 height = collection->vadj->page_size;
1053 *first = MAX(scroll / collection->item_height, 0);
1054 *last = (scroll + height - 1) /collection->item_height;
1056 if (*last < *first)
1057 *last = *first;
1061 /* Cancel the current wink effect. */
1062 static void cancel_wink(Collection *collection)
1064 gint item;
1066 g_return_if_fail(collection != NULL);
1067 g_return_if_fail(IS_COLLECTION(collection));
1068 g_return_if_fail(collection->wink_item != -1);
1070 item = collection->wink_item;
1072 collection->wink_item = -1;
1073 g_source_remove(collection->wink_timeout);
1075 collection_draw_item(collection, item, TRUE);
1078 /* Draw/undraw a box around collection->wink_item */
1079 static void invert_wink(Collection *collection)
1081 GdkRectangle area;
1082 gint row, col;
1084 g_return_if_fail(collection->wink_item >= 0);
1086 if (!GTK_WIDGET_REALIZED(GTK_WIDGET(collection)))
1087 return;
1089 collection_item_to_rowcol(collection, collection->wink_item,
1090 &row, &col);
1091 collection_get_item_area(collection, row, col, &area);
1093 gdk_draw_rectangle(((GtkWidget *) collection)->window,
1094 collection->xor_gc, FALSE,
1095 area.x, area.y,
1096 collection->item_width - 1,
1097 area.height - 1);
1100 static gboolean wink_timeout(Collection *collection)
1102 gint item;
1104 g_return_val_if_fail(collection != NULL, FALSE);
1105 g_return_val_if_fail(IS_COLLECTION(collection), FALSE);
1106 g_return_val_if_fail(collection->wink_item != -1, FALSE);
1108 item = collection->wink_item;
1110 if (collection->winks_left-- > 0)
1112 invert_wink(collection);
1113 return TRUE;
1116 collection->wink_item = -1;
1118 collection_draw_item(collection, item, TRUE);
1120 return FALSE;
1123 /* Change the selected state of an item.
1124 * Send GAIN/LOSE signals if 'signal' is TRUE.
1125 * Send SELECTION_CHANGED unless blocked.
1126 * Updates number_selected and redraws the item.
1128 static void collection_item_set_selected(Collection *collection,
1129 gint item,
1130 gboolean selected,
1131 gboolean signal)
1133 g_return_if_fail(collection != NULL);
1134 g_return_if_fail(IS_COLLECTION(collection));
1135 g_return_if_fail(item >= 0 && item < collection->number_of_items);
1137 if (collection->items[item].selected == selected)
1138 return;
1140 collection->items[item].selected = selected;
1141 collection_draw_item(collection, item, TRUE);
1143 if (selected)
1145 collection->number_selected++;
1146 if (signal && collection->number_selected == 1)
1147 g_signal_emit(collection,
1148 collection_signals[GAIN_SELECTION], 0,
1149 current_event_time);
1151 else
1153 collection->number_selected--;
1154 if (signal && collection->number_selected == 0)
1155 g_signal_emit(collection,
1156 collection_signals[LOSE_SELECTION], 0,
1157 current_event_time);
1160 EMIT_SELECTION_CHANGED(collection, current_event_time);
1163 /* Functions for managing collections */
1165 /* Remove all objects from the collection */
1166 void collection_clear(Collection *collection)
1168 collection_delete_if(collection, NULL, NULL);
1171 /* Inserts a new item at the end. The new item is unselected, and its
1172 * number is returned.
1174 gint collection_insert(Collection *collection, gpointer data, gpointer view)
1176 int item;
1178 /* g_return_val_if_fail(IS_COLLECTION(collection), -1); (slow) */
1180 item = collection->number_of_items;
1182 if (item >= collection->array_size)
1183 resize_arrays(collection, item + (item >> 1));
1185 collection->items[item].data = data;
1186 collection->items[item].view_data = view;
1187 collection->items[item].selected = FALSE;
1189 collection->number_of_items++;
1191 gtk_widget_queue_resize(GTK_WIDGET(collection));
1193 collection_draw_item(collection, item, FALSE);
1195 return item;
1198 void collection_unselect_item(Collection *collection, gint item)
1200 collection_item_set_selected(collection, item, FALSE, TRUE);
1203 void collection_select_item(Collection *collection, gint item)
1205 collection_item_set_selected(collection, item, TRUE, TRUE);
1208 void collection_toggle_item(Collection *collection, gint item)
1210 collection_item_set_selected(collection, item,
1211 !collection->items[item].selected, TRUE);
1214 /* Select all items in the collection */
1215 void collection_select_all(Collection *collection)
1217 GtkWidget *widget;
1218 int item = 0;
1220 g_return_if_fail(collection != NULL);
1221 g_return_if_fail(IS_COLLECTION(collection));
1223 widget = GTK_WIDGET(collection);
1225 if (collection->number_selected == collection->number_of_items)
1226 return; /* Nothing to do */
1228 while (collection->number_selected < collection->number_of_items)
1230 while (collection->items[item].selected)
1231 item++;
1233 collection->items[item].selected = TRUE;
1234 collection_draw_item(collection, item, TRUE);
1235 item++;
1237 collection->number_selected++;
1240 g_signal_emit(collection, collection_signals[GAIN_SELECTION], 0,
1241 current_event_time);
1242 EMIT_SELECTION_CHANGED(collection, current_event_time);
1245 /* Toggle all items in the collection */
1246 void collection_invert_selection(Collection *collection)
1248 int item;
1250 g_return_if_fail(collection != NULL);
1251 g_return_if_fail(IS_COLLECTION(collection));
1253 if (collection->number_selected == 0)
1255 collection_select_all(collection);
1256 return;
1258 else if (collection->number_of_items == collection->number_selected)
1260 collection_clear_selection(collection);
1261 return;
1264 for (item = 0; item < collection->number_of_items; item++)
1265 collection->items[item].selected =
1266 !collection->items[item].selected;
1268 collection->number_selected = collection->number_of_items -
1269 collection->number_selected;
1271 /* Have to redraw everything... */
1272 gtk_widget_queue_draw(GTK_WIDGET(collection));
1274 EMIT_SELECTION_CHANGED(collection, current_event_time);
1277 /* Unselect all items except number item, which is selected (-1 to unselect
1278 * everything).
1280 void collection_clear_except(Collection *collection, gint item)
1282 GtkWidget *widget;
1283 int i = 0;
1284 int end; /* Selected items to end up with */
1286 g_return_if_fail(collection != NULL);
1287 g_return_if_fail(IS_COLLECTION(collection));
1288 g_return_if_fail(item >= -1 && item < collection->number_of_items);
1290 widget = GTK_WIDGET(collection);
1292 if (item == -1)
1293 end = 0;
1294 else
1296 collection_select_item(collection, item);
1297 end = 1;
1300 if (collection->number_selected == 0)
1301 return;
1303 while (collection->number_selected > end)
1305 while (i == item || !collection->items[i].selected)
1306 i++;
1308 collection->items[i].selected = FALSE;
1309 collection_draw_item(collection, i, TRUE);
1310 i++;
1312 collection->number_selected--;
1315 if (end == 0)
1316 g_signal_emit(collection, collection_signals[LOSE_SELECTION], 0,
1317 current_event_time);
1318 EMIT_SELECTION_CHANGED(collection, current_event_time);
1321 /* Unselect all items in the collection */
1322 void collection_clear_selection(Collection *collection)
1324 g_return_if_fail(collection != NULL);
1325 g_return_if_fail(IS_COLLECTION(collection));
1327 collection_clear_except(collection, -1);
1330 /* Force a redraw of the specified item, if it is visible */
1331 void collection_draw_item(Collection *collection, gint item, gboolean blank)
1333 GdkRectangle area;
1334 GtkWidget *widget;
1335 int row, col;
1337 g_return_if_fail(collection != NULL);
1338 /* g_return_if_fail(IS_COLLECTION(collection)); (slow) */
1339 g_return_if_fail(item >= 0 &&
1340 (item == 0 || item < collection->number_of_items));
1342 widget = GTK_WIDGET(collection);
1343 if (!GTK_WIDGET_REALIZED(widget))
1344 return;
1346 collection_item_to_rowcol(collection, item, &row, &col);
1348 collection_get_item_area(collection, row, col, &area);
1350 gdk_window_invalidate_rect(widget->window, &area, FALSE);
1353 void collection_set_item_size(Collection *collection, int width, int height)
1355 GtkWidget *widget;
1357 g_return_if_fail(collection != NULL);
1358 g_return_if_fail(IS_COLLECTION(collection));
1359 g_return_if_fail(width > 4 && height > 4);
1361 if (collection->item_width == width &&
1362 collection->item_height == height)
1363 return;
1365 widget = GTK_WIDGET(collection);
1367 collection->item_width = width;
1368 collection->item_height = height;
1370 if (GTK_WIDGET_REALIZED(widget))
1372 gint window_width;
1374 gdk_drawable_get_size(widget->window, &window_width, NULL);
1375 collection->columns = MAX(window_width / collection->item_width,
1377 if (collection->cursor_item != -1)
1378 scroll_to_show(collection, collection->cursor_item);
1379 gtk_widget_queue_draw(widget);
1382 gtk_widget_queue_resize(GTK_WIDGET(collection));
1385 static int (*cmp_callback)(const void *a, const void *b) = NULL;
1386 static int collection_cmp(const void *a, const void *b)
1388 return cmp_callback(((CollectionItem *) a)->data,
1389 ((CollectionItem *) b)->data);
1391 static int collection_rcmp(const void *a, const void *b)
1393 return -cmp_callback(((CollectionItem *) a)->data,
1394 ((CollectionItem *) b)->data);
1397 /* Cursor is positioned on item with the same data as before the sort.
1398 * Same for the wink item.
1400 void collection_qsort(Collection *collection,
1401 int (*compar)(const void *, const void *),
1402 GtkSortType order)
1404 int cursor, wink, items, wink_on_map;
1405 gpointer cursor_data = NULL;
1406 gpointer wink_data = NULL;
1407 gpointer wink_on_map_data = NULL;
1408 CollectionItem *array;
1409 int i;
1410 int mul = order == GTK_SORT_ASCENDING ? 1 : -1;
1412 g_return_if_fail(collection != NULL);
1413 g_return_if_fail(IS_COLLECTION(collection));
1414 g_return_if_fail(compar != NULL);
1415 g_return_if_fail(cmp_callback == NULL);
1417 /* Check to see if it needs sorting (saves redrawing) */
1418 if (collection->number_of_items < 2)
1419 return;
1421 array = collection->items;
1422 for (i = 1; i < collection->number_of_items; i++)
1424 if (mul * compar(array[i - 1].data, array[i].data) > 0)
1425 break;
1427 if (i == collection->number_of_items)
1428 return; /* Already sorted */
1430 items = collection->number_of_items;
1432 wink_on_map = collection->wink_on_map;
1433 if (wink_on_map >= 0 && wink_on_map < items)
1435 wink_on_map_data = collection->items[wink_on_map].data;
1436 collection->wink_on_map = -1;
1438 else
1439 wink = -1;
1441 wink = collection->wink_item;
1442 if (wink >= 0 && wink < items)
1444 wink_data = collection->items[wink].data;
1445 collection->wink_item = -1;
1447 else
1448 wink = -1;
1450 cursor = collection->cursor_item;
1451 if (cursor >= 0 && cursor < items)
1452 cursor_data = collection->items[cursor].data;
1453 else
1454 cursor = -1;
1456 cmp_callback = compar;
1457 qsort(collection->items, items, sizeof(collection->items[0]),
1458 order == GTK_SORT_ASCENDING ? collection_cmp
1459 : collection_rcmp);
1460 cmp_callback = NULL;
1462 if (cursor > -1 || wink > -1 || wink_on_map > -1)
1464 int item;
1466 for (item = 0; item < items; item++)
1468 if (collection->items[item].data == cursor_data)
1469 collection_set_cursor_item(collection, item,
1470 TRUE);
1471 if (collection->items[item].data == wink_on_map_data)
1472 collection->wink_on_map = item;
1473 if (collection->items[item].data == wink_data)
1475 collection->cursor_item_old = item;
1476 collection->wink_item = item;
1477 scroll_to_show(collection, item);
1482 gtk_widget_queue_draw(GTK_WIDGET(collection));
1485 /* Find an item in a sorted collection.
1486 * Returns the item number, or -1 if not found.
1488 int collection_find_item(Collection *collection, gpointer data,
1489 int (*compar)(const void *, const void *),
1490 GtkSortType order)
1492 int lower, upper;
1493 int mul = order == GTK_SORT_ASCENDING ? 1 : -1;
1495 g_return_val_if_fail(collection != NULL, -1);
1496 g_return_val_if_fail(IS_COLLECTION(collection), -1);
1497 g_return_val_if_fail(compar != NULL, -1);
1499 /* If item is here, then: lower <= i < upper */
1500 lower = 0;
1501 upper = collection->number_of_items;
1503 while (lower < upper)
1505 int i, cmp;
1507 i = (lower + upper) >> 1;
1509 cmp = mul * compar(collection->items[i].data, data);
1510 if (cmp == 0)
1511 return i;
1513 if (cmp > 0)
1514 upper = i;
1515 else
1516 lower = i + 1;
1519 return -1;
1522 /* Return the number of the item under the point (x,y), or -1 for none.
1523 * This may call your test_point callback. The point is relative to the
1524 * collection's origin.
1526 int collection_get_item(Collection *collection, int x, int y)
1528 int row, col;
1529 int width;
1530 int item;
1532 g_return_val_if_fail(collection != NULL, -1);
1534 col = x / collection->item_width;
1535 row = y / collection->item_height;
1537 if (col >= collection->columns)
1538 col = collection->columns - 1;
1540 if (col < 0 || row < 0)
1541 return -1;
1543 if (col == collection->columns - 1)
1544 width = collection->item_width << 1;
1545 else
1546 width = collection->item_width;
1548 item = collection_rowcol_to_item(collection, row, col);
1549 if (item >= collection->number_of_items)
1550 return -1;
1552 x -= col * collection->item_width;
1553 y -= row * collection->item_height;
1555 if (collection->test_point(collection, x, y,
1556 &collection->items[item], width, collection->item_height,
1557 collection->cb_user_data))
1558 return item;
1560 return -1;
1563 /* Set the cursor/highlight over the given item. Passing -1
1564 * hides the cursor. As a special case, you may set the cursor item
1565 * to zero when there are no items.
1567 void collection_set_cursor_item(Collection *collection, gint item,
1568 gboolean may_scroll)
1570 int old_item;
1572 g_return_if_fail(collection != NULL);
1573 g_return_if_fail(IS_COLLECTION(collection));
1574 g_return_if_fail(item >= -1 &&
1575 (item < collection->number_of_items || item == 0));
1577 old_item = collection->cursor_item;
1579 if (old_item == item)
1580 return;
1582 collection->cursor_item = item;
1584 if (old_item != -1)
1585 collection_draw_item(collection, old_item, TRUE);
1587 if (item != -1)
1589 collection_draw_item(collection, item, TRUE);
1590 if (may_scroll)
1591 scroll_to_show(collection, item);
1593 else if (old_item != -1)
1594 collection->cursor_item_old = old_item;
1597 /* Briefly highlight an item to draw the user's attention to it.
1598 * -1 cancels the effect, as does deleting items, sorting the collection
1599 * or starting a new wink effect.
1600 * Otherwise, the effect will cancel itself after a short pause.
1601 * */
1602 void collection_wink_item(Collection *collection, gint item)
1604 g_return_if_fail(collection != NULL);
1605 g_return_if_fail(IS_COLLECTION(collection));
1606 g_return_if_fail(item >= -1 && item < collection->number_of_items);
1608 if (collection->wink_item != -1)
1609 cancel_wink(collection);
1610 if (item == -1)
1611 return;
1613 if (!GTK_WIDGET_MAPPED(GTK_WIDGET(collection)))
1615 collection->wink_on_map = item;
1616 return;
1619 collection->cursor_item_old = collection->wink_item = item;
1620 collection->winks_left = MAX_WINKS;
1622 collection->wink_timeout = g_timeout_add(70,
1623 (GSourceFunc) wink_timeout,
1624 collection);
1625 scroll_to_show(collection, item);
1626 invert_wink(collection);
1628 gdk_flush();
1631 /* Call test(item, data) on each item in the collection.
1632 * Remove all items for which it returns TRUE. test() should
1633 * free the data before returning TRUE. The collection is in an
1634 * inconsistant state during this call (ie, when test() is called).
1636 * If test is NULL, remove all items.
1638 void collection_delete_if(Collection *collection,
1639 gboolean (*test)(gpointer item, gpointer data),
1640 gpointer data)
1642 int in, out = 0;
1643 int selected = 0;
1644 int cursor;
1646 g_return_if_fail(collection != NULL);
1647 g_return_if_fail(IS_COLLECTION(collection));
1649 cursor = collection->cursor_item;
1651 for (in = 0; in < collection->number_of_items; in++)
1653 if (test && !test(collection->items[in].data, data))
1655 /* Keep item */
1656 if (collection->items[in].selected)
1658 collection->items[out].selected = TRUE;
1659 selected++;
1661 else
1662 collection->items[out].selected = FALSE;
1664 collection->items[out].data =
1665 collection->items[in].data;
1666 collection->items[out].view_data =
1667 collection->items[in].view_data;
1668 out++;
1670 else
1672 /* Remove item */
1673 if (collection->free_item)
1674 collection->free_item(collection,
1675 &collection->items[in]);
1677 if (collection->cursor_item >= in)
1678 cursor--;
1682 if (in != out)
1684 collection->cursor_item = cursor;
1686 if (collection->wink_item != -1)
1688 collection->wink_item = -1;
1689 g_source_remove(collection->wink_timeout);
1692 collection->number_of_items = out;
1693 if (collection->number_selected && !selected)
1695 /* We've lost all the selected items */
1696 g_signal_emit(collection,
1697 collection_signals[LOSE_SELECTION], 0,
1698 current_event_time);
1701 collection->number_selected = selected;
1702 resize_arrays(collection,
1703 MAX(collection->number_of_items, MINIMUM_ITEMS));
1705 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection)))
1706 gtk_widget_queue_draw(GTK_WIDGET(collection));
1708 gtk_widget_queue_resize(GTK_WIDGET(collection));
1712 /* Move the cursor by the given row and column offsets.
1713 * Moving by (0,0) can be used to simply make the cursor appear.
1715 void collection_move_cursor(Collection *collection, int drow, int dcol)
1717 int row, col, item;
1718 int first, last, total_rows, total_cols;
1720 g_return_if_fail(collection != NULL);
1721 g_return_if_fail(IS_COLLECTION(collection));
1723 if (!collection->number_of_items)
1725 /* Show the cursor, even though there are no items */
1726 collection_set_cursor_item(collection, 0, TRUE);
1727 return;
1730 get_visible_limits(collection, &first, &last);
1731 total_rows = collection_get_rows(collection);
1732 total_cols = collection_get_cols(collection);
1734 item = collection->cursor_item;
1735 if (item == -1)
1737 item = MIN(collection->cursor_item_old,
1738 collection->number_of_items - 1);
1741 if (item == -1)
1743 col = 0;
1744 row = first;
1746 else
1748 collection_item_to_rowcol(collection, item, &row, &col);
1750 col += dcol;
1751 if (collection->vertical_order)
1753 col = MAX(0,col);
1754 col = MIN(col, total_cols - 1);
1757 if (row < first)
1758 row = first;
1759 else if (row > last)
1760 row = last;
1761 else {
1762 row += drow;
1763 if (!collection->vertical_order)
1765 row = MAX(row, 0);
1766 row = MIN(row, total_rows - 1);
1771 item = collection_rowcol_to_item(collection, row, col);
1773 item = MAX(item, 0);
1774 item = MIN(item, collection->number_of_items-1);
1776 collection_set_cursor_item(collection, item, TRUE);
1779 /* Start a lasso box drag */
1780 void collection_lasso_box(Collection *collection, int x, int y)
1782 collection->drag_box_x[0] = x;
1783 collection->drag_box_y[0] = y;
1784 collection->drag_box_x[1] = x;
1785 collection->drag_box_y[1] = y;
1787 add_lasso_box(collection);
1790 /* Remove the lasso box. Applies fn to each item inside the box.
1791 * fn may be GDK_INVERT, GDK_SET, GDK_NOOP or GDK_CLEAR.
1793 void collection_end_lasso(Collection *collection, GdkFunction fn)
1795 if (fn != GDK_CLEAR)
1797 GdkRectangle region;
1799 find_lasso_area(collection, &region);
1801 collection_process_area(collection, &region, fn,
1802 GDK_CURRENT_TIME);
1805 abort_lasso(collection);
1808 /* Unblock the selection_changed signal, emitting the signal if the
1809 * block counter reaches zero and emit is TRUE.
1811 void collection_unblock_selection_changed(Collection *collection,
1812 guint time,
1813 gboolean emit)
1815 g_return_if_fail(collection != NULL);
1816 g_return_if_fail(IS_COLLECTION(collection));
1817 g_return_if_fail(collection->block_selection_changed > 0);
1819 collection->block_selection_changed--;
1821 if (emit && !collection->block_selection_changed)
1822 g_signal_emit(collection,
1823 collection_signals[SELECTION_CHANGED], 0, time);
1826 /* Translate the item number to the (row, column) form */
1827 void collection_item_to_rowcol (const Collection *collection,
1828 int item, int *row, int *col)
1830 if (!collection->vertical_order)
1832 *row = item / collection->columns;
1833 *col = item % collection->columns;
1835 else
1837 int rows = collection_get_rows(collection);
1838 *row = item % rows;
1839 *col = item / rows;
1845 /* Translate the (row, column) form to the item number.
1846 * May return a number >= collection->number_of_items.
1848 int collection_rowcol_to_item(const Collection *collection, int row, int col)
1850 if (!collection->vertical_order)
1851 return row * collection->columns + col;
1852 else
1854 int rows = collection_get_rows(collection);
1855 if (row >= rows)
1856 return collection->number_of_items;
1857 return row + col * rows;