r1601: Turned on more compiler warnings, and fixed some minor issues it threw up.
[rox-filer.git] / ROX-Filer / src / collection.c
blob756a21f8f78eaefd721fb18ee78599e8a5fc6283
1 /*
2 * $Id$
4 * Collection - a GTK+ widget
5 * Copyright (C) 2002, the ROX-Filer team.
7 * The collection widget provides an area for displaying a collection of
8 * objects (such as files). It allows the user to choose a selection of
9 * them and provides signals to allow popping up menus, detecting
10 * double-clicks etc.
12 * This program is free software; you can redistribute it and/or modify it
13 * under the terms of the GNU General Public License as published by the Free
14 * Software Foundation; either version 2 of the License, or (at your option)
15 * any later version.
17 * This program is distributed in the hope that it will be useful, but WITHOUT
18 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
20 * more details.
22 * You should have received a copy of the GNU General Public License along with
23 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
24 * Place, Suite 330, Boston, MA 02111-1307 USA
27 #include "config.h"
29 #include <stdlib.h>
31 #include <gtk/gtk.h>
32 #include <gdk/gdkkeysyms.h>
33 #include "global.h"
35 #include "collection.h"
37 #define MIN_WIDTH 80
38 #define MIN_HEIGHT 60
39 #define MINIMUM_ITEMS 16
40 #define AUTOSCROLL_STEP 20
42 #define MAX_WINKS 3 /* Should be an odd number */
44 /* Macro to emit the "selection_changed" signal only if allowed */
45 #define EMIT_SELECTION_CHANGED(collection, time) \
46 if (!collection->block_selection_changed) \
47 g_signal_emit(collection, \
48 collection_signals[SELECTION_CHANGED], 0, time)
50 enum
52 PROP_0,
53 PROP_VADJUSTMENT
56 /* Signals:
58 * void gain_selection(collection, time, user_data)
59 * We've gone from no selected items to having a selection.
60 * Time is the time of the event that caused the change, or
61 * GDK_CURRENT_TIME if not known.
63 * void lose_selection(collection, time, user_data)
64 * We've dropped to having no selected items.
65 * Time is the time of the event that caused the change, or
66 * GDK_CURRENT_TIME if not known.
68 * void selection_changed(collection, user_data)
69 * The set of selected items has changed.
70 * Time is the time of the event that caused the change, or
71 * GDK_CURRENT_TIME if not known.
73 enum
75 GAIN_SELECTION,
76 LOSE_SELECTION,
77 SELECTION_CHANGED,
78 LAST_SIGNAL
81 static guint collection_signals[LAST_SIGNAL] = { 0 };
83 static guint32 current_event_time = GDK_CURRENT_TIME;
85 static GtkWidgetClass *parent_class = NULL;
87 /* Static prototypes */
88 static void draw_one_item(Collection *collection,
89 int item,
90 GdkRectangle *area);
91 static void collection_class_init(GObjectClass *gclass, gpointer data);
92 static void collection_init(GTypeInstance *object, gpointer g_class);
93 static void collection_destroy(GtkObject *object);
94 static void collection_finalize(GObject *object);
95 static void collection_realize(GtkWidget *widget);
96 static void collection_map(GtkWidget *widget);
97 static void collection_size_request(GtkWidget *widget,
98 GtkRequisition *requisition);
99 static void collection_size_allocate(GtkWidget *widget,
100 GtkAllocation *allocation);
101 static void collection_set_adjustment(Collection *collection,
102 GtkAdjustment *vadj);
103 static void collection_get_property(GObject *object,
104 guint prop_id,
105 GValue *value,
106 GParamSpec *pspec);
107 static void collection_set_property(GObject *object,
108 guint prop_id,
109 const GValue *value,
110 GParamSpec *pspec);
111 static gint collection_expose(GtkWidget *widget, GdkEventExpose *event);
112 static void default_draw_item(GtkWidget *widget,
113 CollectionItem *data,
114 GdkRectangle *area,
115 gpointer user_data);
116 static gboolean default_test_point(Collection *collection,
117 int point_x, int point_y,
118 CollectionItem *data,
119 int width, int height,
120 gpointer user_data);
121 static gint collection_motion_notify(GtkWidget *widget,
122 GdkEventMotion *event);
123 static void add_lasso_box(Collection *collection);
124 static void abort_lasso(Collection *collection);
125 static void remove_lasso_box(Collection *collection);
126 static void draw_lasso_box(Collection *collection);
127 static void cancel_wink(Collection *collection);
128 static gint collection_key_press(GtkWidget *widget, GdkEventKey *event);
129 static void get_visible_limits(Collection *collection, int *first, int *last);
130 static void scroll_to_show(Collection *collection, int item);
131 static void collection_item_set_selected(Collection *collection,
132 gint item,
133 gboolean selected,
134 gboolean signal);
135 static void collection_drag_leave(GtkWidget *widget, GdkDragContext *context,
136 guint time);
137 static void collection_drag_end(GtkWidget *widget, GdkDragContext *context);
138 static gboolean collection_drag_motion(GtkWidget *widget, GdkDragContext
139 *context, gint x, gint y, guint time);
140 static gint collection_scroll_event(GtkWidget *widget, GdkEventScroll *event);
142 static void draw_focus_at(Collection *collection, GdkRectangle *area)
144 GtkWidget *widget;
145 GdkGC *gc;
147 widget = GTK_WIDGET(collection);
149 if (GTK_WIDGET_FLAGS(widget) & GTK_HAS_FOCUS)
150 gc = widget->style->fg_gc[GTK_STATE_ACTIVE];
151 else
152 gc = widget->style->fg_gc[GTK_STATE_INSENSITIVE];
154 gdk_draw_rectangle(widget->window, gc, FALSE,
155 area->x, area->y,
156 collection->item_width - 1,
157 area->height - 1);
160 static void draw_one_item(Collection *collection, int item, GdkRectangle *area)
162 if (item < collection->number_of_items)
164 collection->draw_item((GtkWidget *) collection,
165 &collection->items[item],
166 area, collection->cb_user_data);
169 if (item == collection->cursor_item)
170 draw_focus_at(collection, area);
173 GType collection_get_type(void)
175 static GType my_type = 0;
177 if (!my_type)
179 static const GTypeInfo info =
181 sizeof(CollectionClass),
182 NULL, /* base_init */
183 NULL, /* base_finalise */
184 (GClassInitFunc) collection_class_init,
185 NULL, /* class_finalise */
186 NULL, /* class_data */
187 sizeof(Collection),
188 0, /* n_preallocs */
189 collection_init
192 my_type = g_type_register_static(gtk_widget_get_type(),
193 "Collection", &info, 0);
196 return my_type;
199 typedef void (*FinalizeFn)(GObject *object);
201 static void collection_class_init(GObjectClass *gclass, gpointer data)
203 CollectionClass *collection_class = (CollectionClass *) gclass;
204 GtkObjectClass *object_class = (GtkObjectClass *) gclass;
205 GtkWidgetClass *widget_class = (GtkWidgetClass *) gclass;
207 parent_class = gtk_type_class(gtk_widget_get_type());
209 object_class->destroy = collection_destroy;
210 G_OBJECT_CLASS(object_class)->finalize =
211 (FinalizeFn) collection_finalize;
213 widget_class->realize = collection_realize;
214 widget_class->expose_event = collection_expose;
215 widget_class->size_request = collection_size_request;
216 widget_class->size_allocate = collection_size_allocate;
218 widget_class->key_press_event = collection_key_press;
220 widget_class->motion_notify_event = collection_motion_notify;
221 widget_class->map = collection_map;
222 widget_class->scroll_event = collection_scroll_event;
223 widget_class->drag_motion = collection_drag_motion;
224 widget_class->drag_leave = collection_drag_leave;
225 widget_class->drag_end = collection_drag_end;
227 gclass->set_property = collection_set_property;
228 gclass->get_property = collection_get_property;
230 collection_class->gain_selection = NULL;
231 collection_class->lose_selection = NULL;
232 collection_class->selection_changed = NULL;
234 collection_signals[GAIN_SELECTION] = g_signal_new("gain_selection",
235 G_TYPE_FROM_CLASS(gclass),
236 G_SIGNAL_RUN_LAST,
237 G_STRUCT_OFFSET(CollectionClass,
238 gain_selection),
239 NULL, NULL,
240 g_cclosure_marshal_VOID__INT,
241 G_TYPE_NONE, 1,
242 G_TYPE_INT);
244 collection_signals[LOSE_SELECTION] = g_signal_new("lose_selection",
245 G_TYPE_FROM_CLASS(gclass),
246 G_SIGNAL_RUN_LAST,
247 G_STRUCT_OFFSET(CollectionClass,
248 lose_selection),
249 NULL, NULL,
250 g_cclosure_marshal_VOID__INT,
251 G_TYPE_NONE, 1,
252 G_TYPE_INT);
254 collection_signals[SELECTION_CHANGED] = g_signal_new(
255 "selection_changed",
256 G_TYPE_FROM_CLASS(gclass),
257 G_SIGNAL_RUN_LAST,
258 G_STRUCT_OFFSET(CollectionClass,
259 selection_changed),
260 NULL, NULL,
261 g_cclosure_marshal_VOID__INT,
262 G_TYPE_NONE, 1,
263 G_TYPE_INT);
265 g_object_class_install_property(gclass,
266 PROP_VADJUSTMENT,
267 g_param_spec_object("vadjustment",
268 _("Vertical Adjustment"),
269 _("The GtkAdjustment for the vertical position."),
270 GTK_TYPE_ADJUSTMENT,
271 G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
274 static void collection_init(GTypeInstance *instance, gpointer g_class)
276 Collection *object = (Collection *) instance;
278 g_return_if_fail(object != NULL);
279 g_return_if_fail(IS_COLLECTION(object));
281 GTK_WIDGET_SET_FLAGS(GTK_WIDGET(object), GTK_CAN_FOCUS);
283 object->number_of_items = 0;
284 object->number_selected = 0;
285 object->block_selection_changed = 0;
286 object->columns = 1;
287 object->item_width = 64;
288 object->item_height = 64;
289 object->vadj = NULL;
291 object->items = g_new(CollectionItem, MINIMUM_ITEMS);
292 object->cursor_item = -1;
293 object->cursor_item_old = -1;
294 object->wink_item = -1;
295 object->wink_on_map = -1;
296 object->array_size = MINIMUM_ITEMS;
297 object->draw_item = default_draw_item;
298 object->test_point = default_test_point;
299 object->free_item = NULL;
301 object->auto_scroll = -1;
304 GtkWidget* collection_new(void)
306 return GTK_WIDGET(gtk_widget_new(collection_get_type(), NULL));
309 /* After this we are unusable, but our data (if any) is still hanging around.
310 * It will be freed later with finalize.
312 static void collection_destroy(GtkObject *object)
314 Collection *collection;
316 g_return_if_fail(object != NULL);
317 g_return_if_fail(IS_COLLECTION(object));
319 collection = COLLECTION(object);
321 collection_clear(collection);
323 if (collection->auto_scroll != -1)
325 gtk_timeout_remove(collection->auto_scroll);
326 collection->auto_scroll = -1;
329 if (collection->vadj)
331 g_object_unref(G_OBJECT(collection->vadj));
332 collection->vadj = NULL;
335 if (GTK_OBJECT_CLASS(parent_class)->destroy)
336 (*GTK_OBJECT_CLASS(parent_class)->destroy)(object);
339 /* This is the last thing that happens to us. Free all data. */
340 static void collection_finalize(GObject *object)
342 Collection *collection;
344 collection = COLLECTION(object);
346 g_return_if_fail(collection->number_of_items == 0);
348 g_free(collection->items);
350 if (G_OBJECT_CLASS(parent_class)->finalize)
351 G_OBJECT_CLASS(parent_class)->finalize(object);
354 static void collection_drag_leave(GtkWidget *widget, GdkDragContext *context,
355 guint time)
357 Collection *collection = COLLECTION(widget);
359 /* Note that this isn't always called when the pointer leaves the
360 * widget; only if we highlighted an item at some point.
363 /* collection_set_autoscroll(collection, FALSE); - not needed? */
364 collection_set_cursor_item(collection, -1);
366 if (GTK_WIDGET_CLASS(parent_class)->drag_leave)
367 (*GTK_WIDGET_CLASS(parent_class)->drag_leave)(widget, context,
368 time);
371 /* Turn on auto scrolling */
372 static gboolean collection_drag_motion(GtkWidget *widget, GdkDragContext
373 *context, gint x, gint y, guint time)
375 Collection *collection = COLLECTION(widget);
377 collection_set_autoscroll(collection, TRUE);
379 if (GTK_WIDGET_CLASS(parent_class)->drag_motion)
380 return (*GTK_WIDGET_CLASS(parent_class)->drag_motion)(widget,
381 context, x, y, time);
382 return FALSE;
385 /* Turn off auto scrolling.
386 * (do we need this AND drag_leave?)
388 static void collection_drag_end(GtkWidget *widget, GdkDragContext *context)
390 Collection *collection = COLLECTION(widget);
392 collection_set_autoscroll(collection, FALSE);
394 if (GTK_WIDGET_CLASS(parent_class)->drag_end)
395 (*GTK_WIDGET_CLASS(parent_class)->drag_end)(widget, context);
398 static void collection_map(GtkWidget *widget)
400 Collection *collection = COLLECTION(widget);
402 if (GTK_WIDGET_CLASS(parent_class)->map)
403 (*GTK_WIDGET_CLASS(parent_class)->map)(widget);
405 if (collection->wink_on_map >= 0)
407 collection_wink_item(collection, collection->wink_on_map);
408 collection->wink_on_map = -1;
412 static void collection_realize(GtkWidget *widget)
414 Collection *collection;
415 GdkWindowAttr attributes;
416 gint attributes_mask;
417 GdkGCValues xor_values;
418 GdkColor *bg, *fg;
420 g_return_if_fail(widget != NULL);
421 g_return_if_fail(IS_COLLECTION(widget));
422 g_return_if_fail(widget->parent != NULL);
424 GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);
425 collection = COLLECTION(widget);
427 attributes.x = widget->allocation.x;
428 attributes.y = widget->allocation.y;
429 attributes.width = widget->allocation.width;
430 attributes.height = widget->allocation.height;
431 attributes.wclass = GDK_INPUT_OUTPUT;
432 attributes.window_type = GDK_WINDOW_CHILD;
433 attributes.event_mask = gtk_widget_get_events(widget) |
434 GDK_EXPOSURE_MASK |
435 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
436 GDK_BUTTON1_MOTION_MASK | GDK_BUTTON2_MOTION_MASK |
437 GDK_BUTTON3_MOTION_MASK;
438 attributes.visual = gtk_widget_get_visual(widget);
439 attributes.colormap = gtk_widget_get_colormap(widget);
441 attributes_mask = GDK_WA_X | GDK_WA_Y |
442 GDK_WA_VISUAL | GDK_WA_COLORMAP;
443 widget->window = gdk_window_new(gtk_widget_get_parent_window(widget),
444 &attributes, attributes_mask);
446 widget->style = gtk_style_attach(widget->style, widget->window);
448 gdk_window_set_user_data(widget->window, widget);
449 gdk_window_set_background(widget->window,
450 &widget->style->bg[GTK_STATE_NORMAL]);
452 bg = &widget->style->bg[GTK_STATE_NORMAL];
453 fg = &widget->style->fg[GTK_STATE_NORMAL];
454 xor_values.function = GDK_XOR;
455 xor_values.foreground.pixel = fg->pixel ^ bg->pixel;
456 collection->xor_gc = gdk_gc_new_with_values(widget->window,
457 &xor_values,
458 GDK_GC_FOREGROUND
459 | GDK_GC_FUNCTION);
462 static void collection_size_request(GtkWidget *widget,
463 GtkRequisition *requisition)
465 Collection *collection = COLLECTION(widget);
466 int rows, cols = collection->columns;
468 /* We ask for the total size we need; our containing viewport
469 * will deal with scrolling.
471 requisition->width = MIN_WIDTH;
472 rows = (collection->number_of_items + cols - 1) / cols;
473 requisition->height = rows * collection->item_height;
476 static gboolean scroll_after_alloc(Collection *collection)
478 if (collection->wink_item != -1)
479 scroll_to_show(collection, collection->wink_item);
480 else if (collection->cursor_item != -1)
481 scroll_to_show(collection, collection->cursor_item);
482 g_object_unref(G_OBJECT(collection));
484 return FALSE;
487 static void collection_size_allocate(GtkWidget *widget,
488 GtkAllocation *allocation)
490 Collection *collection;
491 int old_columns;
492 gboolean cursor_visible = FALSE;
494 g_return_if_fail(widget != NULL);
495 g_return_if_fail(IS_COLLECTION(widget));
496 g_return_if_fail(allocation != NULL);
498 collection = COLLECTION(widget);
500 if (collection->cursor_item != -1)
502 int first, last;
503 int crow = collection->cursor_item / collection->columns;
505 get_visible_limits(collection, &first, &last);
507 cursor_visible = crow >= first && crow <= last;
510 old_columns = collection->columns;
512 widget->allocation = *allocation;
514 collection->columns = allocation->width / collection->item_width;
515 if (collection->columns < 1)
516 collection->columns = 1;
518 if (GTK_WIDGET_REALIZED(widget))
520 gdk_window_move_resize(widget->window,
521 allocation->x, allocation->y,
522 allocation->width, allocation->height);
524 if (cursor_visible)
525 scroll_to_show(collection, collection->cursor_item);
528 if (old_columns != collection->columns)
530 /* Need to go around again... */
531 gtk_widget_queue_resize(widget);
533 else if (collection->wink_item != -1 || collection->cursor_item != -1)
535 /* Viewport resets the adjustments after the alloc */
536 g_object_ref(G_OBJECT(collection));
537 g_idle_add((GSourceFunc) scroll_after_alloc, collection);
541 /* Return the area occupied by the item at (row, col) by filling
542 * in 'area'.
544 static void collection_get_item_area(Collection *collection,
545 int row, int col,
546 GdkRectangle *area)
549 area->x = col * collection->item_width;
550 area->y = row * collection->item_height;
552 area->width = collection->item_width;
553 area->height = collection->item_height;
554 if (col == collection->columns - 1)
555 area->width <<= 1;
558 static gint collection_expose(GtkWidget *widget, GdkEventExpose *event)
560 Collection *collection;
561 GdkRectangle item_area;
562 int row, col;
563 int item;
564 int start_row, last_row;
565 int start_col, last_col;
566 int phys_last_col;
568 g_return_val_if_fail(widget != NULL, FALSE);
569 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
570 g_return_val_if_fail(event != NULL, FALSE);
572 gtk_paint_flat_box(widget->style, widget->window, GTK_STATE_NORMAL,
573 GTK_SHADOW_NONE, &event->area,
574 widget, "base", 0, 0, -1, -1);
576 collection = COLLECTION(widget);
578 /* Calculate the ranges to plot */
579 start_row = event->area.y / collection->item_height;
580 last_row = (event->area.y + event->area.height - 1)
581 / collection->item_height;
582 row = start_row;
584 start_col = event->area.x / collection->item_width;
585 phys_last_col = (event->area.x + event->area.width - 1)
586 / collection->item_width;
588 /* The right-most column may be wider than the others.
589 * Therefore, to redraw the area after the last 'real' column
590 * we may have to draw the right-most column.
592 if (start_col >= collection->columns)
593 start_col = collection->columns - 1;
595 if (phys_last_col >= collection->columns)
596 last_col = collection->columns - 1;
597 else
598 last_col = phys_last_col;
600 col = start_col;
602 item = row * collection->columns + col;
604 while ((item == 0 || item < collection->number_of_items)
605 && row <= last_row)
607 collection_get_item_area(collection, row, col, &item_area);
609 draw_one_item(collection, item, &item_area);
610 col++;
612 if (col > last_col)
614 col = start_col;
615 row++;
616 item = row * collection->columns + col;
618 else
619 item++;
622 if (collection->lasso_box)
623 draw_lasso_box(collection);
625 return FALSE;
628 static void default_draw_item(GtkWidget *widget,
629 CollectionItem *item,
630 GdkRectangle *area,
631 gpointer user_data)
633 gdk_draw_arc(widget->window,
634 item->selected ? widget->style->white_gc
635 : widget->style->black_gc,
636 TRUE,
637 area->x, area->y,
638 COLLECTION(widget)->item_width, area->height,
639 0, 360 * 64);
643 static gboolean default_test_point(Collection *collection,
644 int point_x, int point_y,
645 CollectionItem *item,
646 int width, int height,
647 gpointer user_data)
649 float f_x, f_y;
651 /* Convert to point in unit circle */
652 f_x = ((float) point_x / width) - 0.5;
653 f_y = ((float) point_y / height) - 0.5;
655 return (f_x * f_x) + (f_y * f_y) <= .25;
658 static void collection_set_property(GObject *object,
659 guint prop_id,
660 const GValue *value,
661 GParamSpec *pspec)
663 Collection *collection;
665 collection = COLLECTION(object);
667 switch (prop_id)
669 case PROP_VADJUSTMENT:
670 collection_set_adjustment(collection,
671 g_value_get_object(value));
672 break;
673 default:
674 G_OBJECT_WARN_INVALID_PROPERTY_ID(object,
675 prop_id, pspec);
676 break;
680 static void collection_set_adjustment(Collection *collection,
681 GtkAdjustment *vadj)
683 if (vadj)
684 g_return_if_fail(GTK_IS_ADJUSTMENT(vadj));
685 else
686 vadj = GTK_ADJUSTMENT(gtk_adjustment_new(0.0,
687 0.0, 0.0,
688 0.0, 0.0, 0.0));
690 if (collection->vadj == vadj)
691 return;
693 if (collection->vadj)
694 g_object_unref(G_OBJECT(collection->vadj));
696 collection->vadj = vadj;
697 g_object_ref(G_OBJECT(collection->vadj));
698 gtk_object_sink(GTK_OBJECT(collection->vadj));
701 static void collection_get_property(GObject *object,
702 guint prop_id,
703 GValue *value,
704 GParamSpec *pspec)
706 Collection *collection;
708 collection = COLLECTION(object);
710 switch (prop_id)
712 case PROP_VADJUSTMENT:
713 g_value_set_object(value, G_OBJECT(collection->vadj));
714 break;
715 default:
716 G_OBJECT_WARN_INVALID_PROPERTY_ID(object,
717 prop_id, pspec);
718 break;
722 /* Change the adjustment by this amount. Bounded. */
723 static void diff_vpos(Collection *collection, int diff)
725 int value = collection->vadj->value + diff;
727 value = CLAMP(value, 0,
728 collection->vadj->upper - collection->vadj->page_size);
729 gtk_adjustment_set_value(collection->vadj, value);
732 static void resize_arrays(Collection *collection, guint new_size)
734 g_return_if_fail(collection != NULL);
735 g_return_if_fail(IS_COLLECTION(collection));
736 g_return_if_fail(new_size >= collection->number_of_items);
738 collection->items = g_realloc(collection->items,
739 sizeof(CollectionItem) * new_size);
740 collection->array_size = new_size;
743 static gint collection_key_press(GtkWidget *widget, GdkEventKey *event)
745 Collection *collection;
746 int item;
747 int key;
749 g_return_val_if_fail(widget != NULL, FALSE);
750 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
751 g_return_val_if_fail(event != NULL, FALSE);
753 collection = (Collection *) widget;
754 item = collection->cursor_item;
756 key = event->keyval;
757 if (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK))
759 if (key == GDK_Left || key == GDK_Right || \
760 key == GDK_Up || key == GDK_Down)
761 return TRUE;
762 return FALSE;
765 switch (key)
767 case GDK_Left:
768 collection_move_cursor(collection, 0, -1);
769 break;
770 case GDK_Right:
771 collection_move_cursor(collection, 0, 1);
772 break;
773 case GDK_Up:
774 collection_move_cursor(collection, -1, 0);
775 break;
776 case GDK_Down:
777 collection_move_cursor(collection, 1, 0);
778 break;
779 case GDK_Home:
780 collection_set_cursor_item(collection, 0);
781 break;
782 case GDK_End:
783 collection_set_cursor_item(collection,
784 MAX((gint) collection->number_of_items - 1, 0));
785 break;
786 case GDK_Page_Up:
788 int first, last;
789 get_visible_limits(collection, &first, &last);
790 collection_move_cursor(collection, first - last - 1, 0);
791 break;
793 case GDK_Page_Down:
795 int first, last;
796 get_visible_limits(collection, &first, &last);
797 collection_move_cursor(collection, last - first + 1, 0);
798 break;
800 case GDK_Escape:
801 collection_set_cursor_item(collection, -1);
802 collection_clear_selection(collection);
803 return FALSE; /* Pass it on */
804 case ' ':
805 if (item >=0 && item < collection->number_of_items)
807 collection_toggle_item(collection, item);
808 if (item < collection->number_of_items - 1)
809 collection_set_cursor_item(collection,
810 item + 1);
812 break;
813 default:
814 return FALSE;
817 return TRUE;
820 /* Wheel mouse scrolling */
821 static gint collection_scroll_event(GtkWidget *widget, GdkEventScroll *event)
823 Collection *collection;
824 int diff = 0;
826 g_return_val_if_fail(widget != NULL, FALSE);
827 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
828 g_return_val_if_fail(event != NULL, FALSE);
830 collection = COLLECTION(widget);
832 if (event->direction == GDK_SCROLL_UP)
833 diff = -1;
834 else if (event->direction == GDK_SCROLL_DOWN)
835 diff = 1;
836 else
837 return FALSE;
839 if (diff)
841 int old_value = collection->vadj->value;
842 int new_value = 0;
843 gboolean box = collection->lasso_box;
844 int step = collection->vadj->page_increment / 2;
846 new_value = CLAMP(old_value + diff * step, 0.0,
847 collection->vadj->upper
848 - collection->vadj->page_size);
849 diff = new_value - old_value;
850 if (diff)
852 if (box)
854 remove_lasso_box(collection);
855 collection->drag_box_y[0] -= diff;
857 gtk_adjustment_set_value(collection->vadj, new_value);
858 if (box)
859 add_lasso_box(collection);
863 return TRUE;
866 /* 'from' and 'to' are pixel positions. 'step' is the size of each item.
867 * Returns the index of the first item covered, and the number of items.
869 static void get_range(int from, int to, int step, gint *pos, gint *len)
871 int margin = MIN(step / 4, 40);
873 if (from > to)
875 int tmp = to;
876 to = from;
877 from = tmp;
880 from = (from + margin) / step; /* First item */
881 to = (to + step - margin) / step; /* Last item (inclusive) */
883 *pos = MAX(from, 0);
884 *len = to - *pos;
887 /* Fills in the area with a rectangle corresponding to the current
888 * size of the lasso box (units of items, not pixels).
890 * The box will only span valid columns, but the total number
891 * of items is not taken into account (rows or cols).
893 static void find_lasso_area(Collection *collection, GdkRectangle *area)
895 int cols = collection->columns;
896 int dx = collection->drag_box_x[0] - collection->drag_box_x[1];
897 int dy = collection->drag_box_y[0] - collection->drag_box_y[1];
899 if (ABS(dx) < 8 && ABS(dy) < 8)
901 /* Didn't move far enough - ignore */
902 area->x = area->y = 0;
903 area->width = 0;
904 area->height = 0;
905 return;
908 get_range(collection->drag_box_x[0], collection->drag_box_x[1],
909 collection->item_width, &area->x, &area->width);
911 if (area->x >= cols)
912 area->width = 0;
913 else if (area->x + area->width > cols)
914 area->width = cols - area->x;
916 get_range(collection->drag_box_y[0], collection->drag_box_y[1],
917 collection->item_height, &area->y, &area->height);
920 static void collection_process_area(Collection *collection,
921 GdkRectangle *area,
922 GdkFunction fn,
923 guint32 time)
925 int x, y;
926 guint32 stacked_time;
927 int item;
928 gboolean changed = FALSE;
929 guint old_selected;
931 g_return_if_fail(fn == GDK_SET || fn == GDK_INVERT);
933 old_selected = collection->number_selected;
935 stacked_time = current_event_time;
936 current_event_time = time;
938 collection->block_selection_changed++;
940 for (y = area->y; y < area->y + area->height; y++)
942 item = y * collection->columns + area->x;
944 for (x = area->x; x < area->x + area->width; x++)
946 if (item >= collection->number_of_items)
947 goto out;
949 if (fn == GDK_INVERT)
950 collection_item_set_selected(collection, item,
951 !collection->items[item].selected,
952 FALSE);
953 else
954 collection_item_set_selected(collection, item,
955 TRUE, FALSE);
957 changed = TRUE;
958 item++;
962 out:
963 if (collection->number_selected && !old_selected)
964 g_signal_emit(collection,
965 collection_signals[GAIN_SELECTION], 0,
966 current_event_time);
967 else if (!collection->number_selected && old_selected)
968 g_signal_emit(collection,
969 collection_signals[LOSE_SELECTION], 0,
970 current_event_time);
972 collection_unblock_selection_changed(collection,
973 current_event_time, changed);
974 current_event_time = stacked_time;
977 static gint collection_motion_notify(GtkWidget *widget,
978 GdkEventMotion *event)
980 Collection *collection;
981 gint x, y;
983 g_return_val_if_fail(widget != NULL, FALSE);
984 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
985 g_return_val_if_fail(event != NULL, FALSE);
987 collection = COLLECTION(widget);
989 if (!collection->lasso_box)
990 return FALSE;
992 if (event->window != widget->window)
993 gdk_window_get_pointer(widget->window, &x, &y, NULL);
994 else
996 x = event->x;
997 y = event->y;
1000 remove_lasso_box(collection);
1001 collection->drag_box_x[1] = x;
1002 collection->drag_box_y[1] = y;
1003 add_lasso_box(collection);
1004 return TRUE;
1007 static void add_lasso_box(Collection *collection)
1009 g_return_if_fail(collection != NULL);
1010 g_return_if_fail(IS_COLLECTION(collection));
1011 g_return_if_fail(collection->lasso_box == FALSE);
1013 collection->lasso_box = TRUE;
1014 draw_lasso_box(collection);
1017 static void draw_lasso_box(Collection *collection)
1019 GtkWidget *widget;
1020 int x, y, width, height;
1022 widget = GTK_WIDGET(collection);
1024 x = MIN(collection->drag_box_x[0], collection->drag_box_x[1]);
1025 y = MIN(collection->drag_box_y[0], collection->drag_box_y[1]);
1026 width = abs(collection->drag_box_x[1] - collection->drag_box_x[0]);
1027 height = abs(collection->drag_box_y[1] - collection->drag_box_y[0]);
1029 /* XXX: A redraw bug sometimes leaves a one-pixel dot on the screen.
1030 * As a quick hack, don't draw boxes that small for now...
1032 if (width || height)
1033 gdk_draw_rectangle(widget->window, collection->xor_gc, FALSE,
1034 x, y, width, height);
1037 static void abort_lasso(Collection *collection)
1039 if (collection->lasso_box)
1041 remove_lasso_box(collection);
1042 collection_set_autoscroll(collection, FALSE);
1046 static void remove_lasso_box(Collection *collection)
1048 g_return_if_fail(collection != NULL);
1049 g_return_if_fail(IS_COLLECTION(collection));
1050 g_return_if_fail(collection->lasso_box == TRUE);
1052 draw_lasso_box(collection);
1054 collection->lasso_box = FALSE;
1056 return;
1059 /* Make sure that 'item' is fully visible (vertically), scrolling if not. */
1060 static void scroll_to_show(Collection *collection, int item)
1062 int first, last, row;
1064 g_return_if_fail(collection != NULL);
1065 g_return_if_fail(IS_COLLECTION(collection));
1067 row = item / collection->columns;
1068 get_visible_limits(collection, &first, &last);
1070 if (row <= first)
1072 gtk_adjustment_set_value(collection->vadj,
1073 row * collection->item_height);
1075 else if (row >= last)
1077 GtkWidget *widget = (GtkWidget *) collection;
1078 gint height;
1080 if (GTK_WIDGET_REALIZED(widget))
1082 height = collection->vadj->page_size;
1083 gtk_adjustment_set_value(collection->vadj,
1084 (row + 1) * collection->item_height - height);
1089 /* Return the first and last rows which are [partly] visible. Does not
1090 * ensure that the rows actually exist (contain items).
1092 static void get_visible_limits(Collection *collection, int *first, int *last)
1094 GtkWidget *widget = (GtkWidget *) collection;
1095 gint scroll = 0, height;
1097 g_return_if_fail(collection != NULL);
1098 g_return_if_fail(IS_COLLECTION(collection));
1099 g_return_if_fail(first != NULL && last != NULL);
1101 if (!GTK_WIDGET_REALIZED(widget))
1103 *first = 0;
1104 *last = 0;
1106 else
1108 scroll = collection->vadj->value;
1109 height = collection->vadj->page_size;
1111 *first = MAX(scroll / collection->item_height, 0);
1112 *last = (scroll + height - 1) /collection->item_height;
1114 if (*last < *first)
1115 *last = *first;
1119 /* Cancel the current wink effect. */
1120 static void cancel_wink(Collection *collection)
1122 gint item;
1124 g_return_if_fail(collection != NULL);
1125 g_return_if_fail(IS_COLLECTION(collection));
1126 g_return_if_fail(collection->wink_item != -1);
1128 item = collection->wink_item;
1130 collection->wink_item = -1;
1131 gtk_timeout_remove(collection->wink_timeout);
1133 collection_draw_item(collection, item, TRUE);
1136 /* Draw/undraw a box around collection->wink_item */
1137 static void invert_wink(Collection *collection)
1139 GdkRectangle area;
1140 gint row, col;
1142 g_return_if_fail(collection->wink_item >= 0);
1144 if (!GTK_WIDGET_REALIZED(GTK_WIDGET(collection)))
1145 return;
1147 col = collection->wink_item % collection->columns;
1148 row = collection->wink_item / collection->columns;
1149 collection_get_item_area(collection, row, col, &area);
1151 gdk_draw_rectangle(((GtkWidget *) collection)->window,
1152 collection->xor_gc, FALSE,
1153 area.x, area.y,
1154 collection->item_width - 1,
1155 area.height - 1);
1158 static gboolean wink_timeout(Collection *collection)
1160 gint item;
1162 g_return_val_if_fail(collection != NULL, FALSE);
1163 g_return_val_if_fail(IS_COLLECTION(collection), FALSE);
1164 g_return_val_if_fail(collection->wink_item != -1, FALSE);
1166 item = collection->wink_item;
1168 if (collection->winks_left-- > 0)
1170 invert_wink(collection);
1171 return TRUE;
1174 collection->wink_item = -1;
1176 collection_draw_item(collection, item, TRUE);
1178 return FALSE;
1181 /* This is called frequently while auto_scroll is on.
1182 * Checks the pointer position and scrolls the window if it's
1183 * near the top or bottom.
1185 static gboolean as_timeout(Collection *collection)
1187 GdkWindow *window = GTK_WIDGET(collection)->window;
1188 gint x, y, w, h;
1189 GdkModifierType mask;
1190 int diff = 0;
1192 gdk_window_get_pointer(window, &x, &y, &mask);
1193 gdk_drawable_get_size(window, &w, NULL);
1195 h = collection->vadj->page_size;
1196 y -= collection->vadj->value;
1198 if ((x < 0 || x > w || y < 0 || y > h) && !collection->lasso_box)
1200 collection->auto_scroll = -1;
1201 return FALSE; /* Out of window - stop */
1204 if (y < AUTOSCROLL_STEP)
1205 diff = y - AUTOSCROLL_STEP;
1206 else if (y > h - AUTOSCROLL_STEP)
1207 diff = AUTOSCROLL_STEP + y - h;
1209 if (diff)
1210 diff_vpos(collection, diff);
1212 return TRUE;
1215 /* Change the selected state of an item.
1216 * Send GAIN/LOSE signals if 'signal' is TRUE.
1217 * Send SELECTION_CHANGED unless blocked.
1218 * Updates number_selected and redraws the item.
1220 static void collection_item_set_selected(Collection *collection,
1221 gint item,
1222 gboolean selected,
1223 gboolean signal)
1225 g_return_if_fail(collection != NULL);
1226 g_return_if_fail(IS_COLLECTION(collection));
1227 g_return_if_fail(item >= 0 && item < collection->number_of_items);
1229 if (collection->items[item].selected == selected)
1230 return;
1232 collection->items[item].selected = selected;
1233 collection_draw_item(collection, item, TRUE);
1235 if (selected)
1237 collection->number_selected++;
1238 if (signal && collection->number_selected == 1)
1239 g_signal_emit(collection,
1240 collection_signals[GAIN_SELECTION], 0,
1241 current_event_time);
1243 else
1245 collection->number_selected--;
1246 if (signal && collection->number_selected == 0)
1247 g_signal_emit(collection,
1248 collection_signals[LOSE_SELECTION], 0,
1249 current_event_time);
1252 EMIT_SELECTION_CHANGED(collection, current_event_time);
1255 /* Functions for managing collections */
1257 /* Remove all objects from the collection */
1258 void collection_clear(Collection *collection)
1260 collection_delete_if(collection, NULL, NULL);
1263 /* Inserts a new item at the end. The new item is unselected, and its
1264 * number is returned.
1266 gint collection_insert(Collection *collection, gpointer data, gpointer view)
1268 int item;
1270 /* g_return_val_if_fail(IS_COLLECTION(collection), -1); (slow) */
1272 item = collection->number_of_items;
1274 if (item >= collection->array_size)
1275 resize_arrays(collection, item + (item >> 1));
1277 collection->items[item].data = data;
1278 collection->items[item].view_data = view;
1279 collection->items[item].selected = FALSE;
1281 collection->number_of_items++;
1283 gtk_widget_queue_resize(GTK_WIDGET(collection));
1285 collection_draw_item(collection, item, FALSE);
1287 return item;
1290 void collection_unselect_item(Collection *collection, gint item)
1292 collection_item_set_selected(collection, item, FALSE, TRUE);
1295 void collection_select_item(Collection *collection, gint item)
1297 collection_item_set_selected(collection, item, TRUE, TRUE);
1300 void collection_toggle_item(Collection *collection, gint item)
1302 collection_item_set_selected(collection, item,
1303 !collection->items[item].selected, TRUE);
1306 /* Select all items in the collection */
1307 void collection_select_all(Collection *collection)
1309 GtkWidget *widget;
1310 int item = 0;
1312 g_return_if_fail(collection != NULL);
1313 g_return_if_fail(IS_COLLECTION(collection));
1315 widget = GTK_WIDGET(collection);
1317 if (collection->number_selected == collection->number_of_items)
1318 return; /* Nothing to do */
1320 while (collection->number_selected < collection->number_of_items)
1322 while (collection->items[item].selected)
1323 item++;
1325 collection->items[item].selected = TRUE;
1326 collection_draw_item(collection, item, TRUE);
1327 item++;
1329 collection->number_selected++;
1332 g_signal_emit(collection, collection_signals[GAIN_SELECTION], 0,
1333 current_event_time);
1334 EMIT_SELECTION_CHANGED(collection, current_event_time);
1337 /* Toggle all items in the collection */
1338 void collection_invert_selection(Collection *collection)
1340 int item;
1342 g_return_if_fail(collection != NULL);
1343 g_return_if_fail(IS_COLLECTION(collection));
1345 if (collection->number_selected == 0)
1347 collection_select_all(collection);
1348 return;
1350 else if (collection->number_of_items == collection->number_selected)
1352 collection_clear_selection(collection);
1353 return;
1356 for (item = 0; item < collection->number_of_items; item++)
1357 collection->items[item].selected =
1358 !collection->items[item].selected;
1360 collection->number_selected = collection->number_of_items -
1361 collection->number_selected;
1363 /* Have to redraw everything... */
1364 gtk_widget_queue_draw(GTK_WIDGET(collection));
1366 EMIT_SELECTION_CHANGED(collection, current_event_time);
1369 /* Unselect all items except number item, which is selected (-1 to unselect
1370 * everything).
1372 void collection_clear_except(Collection *collection, gint item)
1374 GtkWidget *widget;
1375 int i = 0;
1376 int end; /* Selected items to end up with */
1378 g_return_if_fail(collection != NULL);
1379 g_return_if_fail(IS_COLLECTION(collection));
1380 g_return_if_fail(item >= -1 && item < collection->number_of_items);
1382 widget = GTK_WIDGET(collection);
1384 if (item == -1)
1385 end = 0;
1386 else
1388 collection_select_item(collection, item);
1389 end = 1;
1392 if (collection->number_selected == 0)
1393 return;
1395 while (collection->number_selected > end)
1397 while (i == item || !collection->items[i].selected)
1398 i++;
1400 collection->items[i].selected = FALSE;
1401 collection_draw_item(collection, i, TRUE);
1402 i++;
1404 collection->number_selected--;
1407 if (end == 0)
1408 g_signal_emit(collection, collection_signals[LOSE_SELECTION], 0,
1409 current_event_time);
1410 EMIT_SELECTION_CHANGED(collection, current_event_time);
1413 /* Unselect all items in the collection */
1414 void collection_clear_selection(Collection *collection)
1416 g_return_if_fail(collection != NULL);
1417 g_return_if_fail(IS_COLLECTION(collection));
1419 collection_clear_except(collection, -1);
1422 /* Force a redraw of the specified item, if it is visible */
1423 void collection_draw_item(Collection *collection, gint item, gboolean blank)
1425 GdkRectangle area;
1426 GtkWidget *widget;
1427 int row, col;
1429 g_return_if_fail(collection != NULL);
1430 /* g_return_if_fail(IS_COLLECTION(collection)); (slow) */
1431 g_return_if_fail(item >= 0 &&
1432 (item == 0 || item < collection->number_of_items));
1434 widget = GTK_WIDGET(collection);
1435 if (!GTK_WIDGET_REALIZED(widget))
1436 return;
1438 col = item % collection->columns;
1439 row = item / collection->columns;
1441 collection_get_item_area(collection, row, col, &area);
1443 gdk_window_invalidate_rect(widget->window, &area, FALSE);
1446 void collection_set_item_size(Collection *collection, int width, int height)
1448 GtkWidget *widget;
1450 g_return_if_fail(collection != NULL);
1451 g_return_if_fail(IS_COLLECTION(collection));
1452 g_return_if_fail(width > 4 && height > 4);
1454 if (collection->item_width == width &&
1455 collection->item_height == height)
1456 return;
1458 widget = GTK_WIDGET(collection);
1460 collection->item_width = width;
1461 collection->item_height = height;
1463 if (GTK_WIDGET_REALIZED(widget))
1465 gint window_width;
1467 gdk_drawable_get_size(widget->window, &window_width, NULL);
1468 collection->columns = MAX(window_width / collection->item_width,
1470 if (collection->cursor_item != -1)
1471 scroll_to_show(collection, collection->cursor_item);
1472 gtk_widget_queue_draw(widget);
1475 gtk_widget_queue_resize(GTK_WIDGET(collection));
1478 static int (*cmp_callback)(const void *a, const void *b) = NULL;
1479 static int collection_cmp(const void *a, const void *b)
1481 return cmp_callback(((CollectionItem *) a)->data,
1482 ((CollectionItem *) b)->data);
1485 /* Cursor is positioned on item with the same data as before the sort.
1486 * Same for the wink item.
1488 void collection_qsort(Collection *collection,
1489 int (*compar)(const void *, const void *))
1491 int cursor, wink, items, wink_on_map;
1492 gpointer cursor_data = NULL;
1493 gpointer wink_data = NULL;
1494 gpointer wink_on_map_data = NULL;
1495 CollectionItem *array;
1496 int i;
1498 g_return_if_fail(collection != NULL);
1499 g_return_if_fail(IS_COLLECTION(collection));
1500 g_return_if_fail(compar != NULL);
1501 g_return_if_fail(cmp_callback == NULL);
1503 /* Check to see if it needs sorting (saves redrawing) */
1504 if (collection->number_of_items < 2)
1505 return;
1507 array = collection->items;
1508 for (i = 1; i < collection->number_of_items; i++)
1510 if (compar(array[i - 1].data, array[i].data) > 0)
1511 break;
1513 if (i == collection->number_of_items)
1514 return; /* Already sorted */
1516 items = collection->number_of_items;
1518 wink_on_map = collection->wink_on_map;
1519 if (wink_on_map >= 0 && wink_on_map < items)
1521 wink_on_map_data = collection->items[wink_on_map].data;
1522 collection->wink_on_map = -1;
1524 else
1525 wink = -1;
1527 wink = collection->wink_item;
1528 if (wink >= 0 && wink < items)
1530 wink_data = collection->items[wink].data;
1531 collection->wink_item = -1;
1533 else
1534 wink = -1;
1536 cursor = collection->cursor_item;
1537 if (cursor >= 0 && cursor < items)
1538 cursor_data = collection->items[cursor].data;
1539 else
1540 cursor = -1;
1542 cmp_callback = compar;
1543 qsort(collection->items, items, sizeof(collection->items[0]),
1544 collection_cmp);
1545 cmp_callback = NULL;
1547 if (cursor > -1 || wink > -1 || wink_on_map > -1)
1549 int item;
1551 for (item = 0; item < items; item++)
1553 if (collection->items[item].data == cursor_data)
1554 collection_set_cursor_item(collection, item);
1555 if (collection->items[item].data == wink_on_map_data)
1556 collection->wink_on_map = item;
1557 if (collection->items[item].data == wink_data)
1559 collection->cursor_item_old = item;
1560 collection->wink_item = item;
1561 scroll_to_show(collection, item);
1566 gtk_widget_queue_draw(GTK_WIDGET(collection));
1569 /* Find an item in a sorted collection.
1570 * Returns the item number, or -1 if not found.
1572 int collection_find_item(Collection *collection, gpointer data,
1573 int (*compar)(const void *, const void *))
1575 int lower, upper;
1577 g_return_val_if_fail(collection != NULL, -1);
1578 g_return_val_if_fail(IS_COLLECTION(collection), -1);
1579 g_return_val_if_fail(compar != NULL, -1);
1581 /* If item is here, then: lower <= i < upper */
1582 lower = 0;
1583 upper = collection->number_of_items;
1585 while (lower < upper)
1587 int i, cmp;
1589 i = (lower + upper) >> 1;
1591 cmp = compar(collection->items[i].data, data);
1592 if (cmp == 0)
1593 return i;
1595 if (cmp > 0)
1596 upper = i;
1597 else
1598 lower = i + 1;
1601 return -1;
1604 /* Return the number of the item under the point (x,y), or -1 for none.
1605 * This may call your test_point callback. The point is relative to the
1606 * collection's origin.
1608 int collection_get_item(Collection *collection, int x, int y)
1610 int row, col;
1611 int width;
1612 int item;
1614 g_return_val_if_fail(collection != NULL, -1);
1616 col = x / collection->item_width;
1617 row = y / collection->item_height;
1619 if (col >= collection->columns)
1620 col = collection->columns - 1;
1622 if (col < 0 || row < 0)
1623 return -1;
1625 if (col == collection->columns - 1)
1626 width = collection->item_width << 1;
1627 else
1628 width = collection->item_width;
1630 item = col + row * collection->columns;
1631 if (item >= collection->number_of_items)
1632 return -1;
1634 x -= col * collection->item_width;
1635 y -= row * collection->item_height;
1637 if (collection->test_point(collection, x, y,
1638 &collection->items[item], width, collection->item_height,
1639 collection->cb_user_data))
1640 return item;
1642 return -1;
1645 int collection_selected_item_number(Collection *collection)
1647 int i;
1649 g_return_val_if_fail(collection != NULL, -1);
1650 g_return_val_if_fail(IS_COLLECTION(collection), -1);
1651 g_return_val_if_fail(collection->number_selected == 1, -1);
1653 for (i = 0; i < collection->number_of_items; i++)
1654 if (collection->items[i].selected)
1655 return i;
1657 g_warning("collection_selected_item_number: number_selected is wrong");
1659 return -1;
1662 /* Set the cursor/highlight over the given item. Passing -1
1663 * hides the cursor. As a special case, you may set the cursor item
1664 * to zero when there are no items.
1666 void collection_set_cursor_item(Collection *collection, gint item)
1668 int old_item;
1670 g_return_if_fail(collection != NULL);
1671 g_return_if_fail(IS_COLLECTION(collection));
1672 g_return_if_fail(item >= -1 &&
1673 (item < collection->number_of_items || item == 0));
1675 old_item = collection->cursor_item;
1677 if (old_item == item)
1678 return;
1680 collection->cursor_item = item;
1682 if (old_item != -1)
1683 collection_draw_item(collection, old_item, TRUE);
1685 if (item != -1)
1687 collection_draw_item(collection, item, TRUE);
1688 if (collection->auto_scroll == -1)
1689 scroll_to_show(collection, item);
1691 else if (old_item != -1)
1692 collection->cursor_item_old = old_item;
1695 /* Briefly highlight an item to draw the user's attention to it.
1696 * -1 cancels the effect, as does deleting items, sorting the collection
1697 * or starting a new wink effect.
1698 * Otherwise, the effect will cancel itself after a short pause.
1699 * */
1700 void collection_wink_item(Collection *collection, gint item)
1702 g_return_if_fail(collection != NULL);
1703 g_return_if_fail(IS_COLLECTION(collection));
1704 g_return_if_fail(item >= -1 && item < collection->number_of_items);
1706 if (collection->wink_item != -1)
1707 cancel_wink(collection);
1708 if (item == -1)
1709 return;
1711 if (!GTK_WIDGET_MAPPED(GTK_WIDGET(collection)))
1713 collection->wink_on_map = item;
1714 return;
1717 collection->cursor_item_old = collection->wink_item = item;
1718 collection->winks_left = MAX_WINKS;
1720 collection->wink_timeout = gtk_timeout_add(70,
1721 (GtkFunction) wink_timeout,
1722 collection);
1723 scroll_to_show(collection, item);
1724 invert_wink(collection);
1726 gdk_flush();
1729 /* Call test(item, data) on each item in the collection.
1730 * Remove all items for which it returns TRUE. test() should
1731 * free the data before returning TRUE. The collection is in an
1732 * inconsistant state during this call (ie, when test() is called).
1734 * If test is NULL, remove all items.
1736 void collection_delete_if(Collection *collection,
1737 gboolean (*test)(gpointer item, gpointer data),
1738 gpointer data)
1740 int in, out = 0;
1741 int selected = 0;
1742 int cursor;
1744 g_return_if_fail(collection != NULL);
1745 g_return_if_fail(IS_COLLECTION(collection));
1747 cursor = collection->cursor_item;
1749 for (in = 0; in < collection->number_of_items; in++)
1751 if (test && !test(collection->items[in].data, data))
1753 /* Keep item */
1754 if (collection->items[in].selected)
1756 collection->items[out].selected = TRUE;
1757 selected++;
1759 else
1760 collection->items[out].selected = FALSE;
1762 collection->items[out].data =
1763 collection->items[in].data;
1764 collection->items[out].view_data =
1765 collection->items[in].view_data;
1766 out++;
1768 else
1770 /* Remove item */
1771 if (collection->free_item)
1772 collection->free_item(collection,
1773 &collection->items[in]);
1775 if (cursor >= in)
1776 cursor--;
1780 if (in != out)
1782 collection->cursor_item = cursor;
1784 if (collection->wink_item != -1)
1786 collection->wink_item = -1;
1787 gtk_timeout_remove(collection->wink_timeout);
1790 collection->number_of_items = out;
1791 if (collection->number_selected && !selected)
1793 /* We've lost all the selected items */
1794 g_signal_emit(collection,
1795 collection_signals[LOSE_SELECTION], 0,
1796 current_event_time);
1799 collection->number_selected = selected;
1800 resize_arrays(collection,
1801 MAX(collection->number_of_items, MINIMUM_ITEMS));
1803 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection)))
1804 gtk_widget_queue_draw(GTK_WIDGET(collection));
1806 gtk_widget_queue_resize(GTK_WIDGET(collection));
1810 /* Move the cursor by the given row and column offsets.
1811 * Moving by (0,0) can be used to simply make the cursor appear.
1813 void collection_move_cursor(Collection *collection, int drow, int dcol)
1815 int row, col, item;
1816 int first, last, total_rows;
1818 g_return_if_fail(collection != NULL);
1819 g_return_if_fail(IS_COLLECTION(collection));
1821 get_visible_limits(collection, &first, &last);
1823 item = collection->cursor_item;
1824 if (item == -1)
1826 item = MIN(collection->cursor_item_old,
1827 collection->number_of_items - 1);
1830 if (item == -1)
1832 col = 0;
1833 row = first;
1835 else
1837 row = item / collection->columns;
1838 col = item % collection->columns + dcol;
1840 if (row < first)
1841 row = first;
1842 else if (row > last)
1843 row = last;
1844 else
1845 row = MAX(row + drow, 0);
1848 total_rows = (collection->number_of_items + collection->columns - 1)
1849 / collection->columns;
1851 if (row >= total_rows - 1 && drow > 0)
1853 row = total_rows - 1;
1854 item = col + row * collection->columns;
1855 if (item >= collection->number_of_items - 1)
1857 collection_set_cursor_item(collection,
1858 collection->number_of_items - 1);
1859 return;
1862 if (row < 0)
1863 row = 0;
1865 item = col + row * collection->columns;
1867 if (item >= 0 && item < collection->number_of_items)
1868 collection_set_cursor_item(collection, item);
1871 /* When autoscroll is on, a timer keeps track of the pointer position.
1872 * While it's near the top or bottom of the window, the window scrolls.
1874 * If the mouse buttons are released, the pointer leaves the window, or
1875 * a drag-and-drop operation finishes, auto_scroll is turned off.
1877 void collection_set_autoscroll(Collection *collection, gboolean auto_scroll)
1879 g_return_if_fail(collection != NULL);
1880 g_return_if_fail(IS_COLLECTION(collection));
1882 if (auto_scroll)
1884 if (collection->auto_scroll != -1)
1885 return; /* Already on! */
1887 collection->auto_scroll = gtk_timeout_add(50,
1888 (GtkFunction) as_timeout,
1889 collection);
1891 else
1893 if (collection->auto_scroll == -1)
1894 return; /* Already off! */
1896 gtk_timeout_remove(collection->auto_scroll);
1897 collection->auto_scroll = -1;
1901 /* Start a lasso box drag */
1902 void collection_lasso_box(Collection *collection, int x, int y)
1904 collection->drag_box_x[0] = x;
1905 collection->drag_box_y[0] = y;
1906 collection->drag_box_x[1] = x;
1907 collection->drag_box_y[1] = y;
1909 collection_set_autoscroll(collection, TRUE);
1910 add_lasso_box(collection);
1913 /* Remove the lasso box. Applies fn to each item inside the box.
1914 * fn may be GDK_INVERT, GDK_SET, GDK_NOOP or GDK_CLEAR.
1916 void collection_end_lasso(Collection *collection, GdkFunction fn)
1918 if (fn != GDK_CLEAR)
1920 GdkRectangle region;
1922 find_lasso_area(collection, &region);
1924 collection_process_area(collection, &region, fn,
1925 GDK_CURRENT_TIME);
1928 abort_lasso(collection);
1931 /* Unblock the selection_changed signal, emitting the signal if the
1932 * block counter reaches zero and emit is TRUE.
1934 void collection_unblock_selection_changed(Collection *collection,
1935 guint time,
1936 gboolean emit)
1938 g_return_if_fail(collection != NULL);
1939 g_return_if_fail(IS_COLLECTION(collection));
1940 g_return_if_fail(collection->block_selection_changed > 0);
1942 collection->block_selection_changed--;
1944 if (emit && !collection->block_selection_changed)
1945 g_signal_emit(collection,
1946 collection_signals[SELECTION_CHANGED], 0, time);