r196: Collection widget passes on unused Escape key press events.
[rox-filer.git] / ROX-Filer / src / collection.c
blob22737dcb7693a7ef11e2632e6bcb3ce6e904075e
1 /*
2 * $Id$
4 * Collection - a GTK+ widget
5 * Copyright (C) 1999, Thomas Leonard, <tal197@ecs.soton.ac.uk>.
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 <stdlib.h>
29 #include <gtk/gtk.h>
30 #include <gdk/gdkkeysyms.h>
31 #include "collection.h"
33 #define MIN_WIDTH 80
34 #define MIN_HEIGHT 60
35 #define MINIMUM_ITEMS 16
37 int collection_menu_button = 3;
38 gboolean collection_single_click = FALSE;
40 enum
42 ARG_0,
43 ARG_VADJUSTMENT
46 /* Signals:
48 * void open_item(collection, item, item_number, user_data)
49 * User has double clicked on this item.
51 * void drag_selection(collection, motion_event, number_selected, user_data)
52 * User has tried to drag the selection.
54 * void show_menu(collection, button_event, item, user_data)
55 * User has menu-clicked on the collection. 'item' is the number
56 * of the item clicked, or -1 if the click was over the background.
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 enum
70 OPEN_ITEM,
71 DRAG_SELECTION,
72 SHOW_MENU,
73 GAIN_SELECTION,
74 LOSE_SELECTION,
75 LAST_SIGNAL
78 static guint collection_signals[LAST_SIGNAL] = { 0 };
80 static guint32 current_event_time = GDK_CURRENT_TIME;
82 static GtkWidgetClass *parent_class = NULL;
84 static GdkCursor *crosshair = NULL;
86 /* Static prototypes */
87 static void draw_one_item(Collection *collection,
88 int item,
89 GdkRectangle *area);
90 static void collection_class_init(CollectionClass *class);
91 static void collection_init(Collection *object);
92 static void collection_destroy(GtkObject *object);
93 static void collection_finalize(GtkObject *object);
94 static void collection_realize(GtkWidget *widget);
95 static gint collection_paint(Collection *collection,
96 GdkRectangle *area);
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_set_arg( GtkObject *object,
104 GtkArg *arg,
105 guint arg_id);
106 static void collection_get_arg( GtkObject *object,
107 GtkArg *arg,
108 guint arg_id);
109 static void collection_adjustment(GtkAdjustment *adjustment,
110 Collection *collection);
111 static void collection_disconnect(GtkAdjustment *adjustment,
112 Collection *collection);
113 static void set_vadjustment(Collection *collection);
114 static void collection_draw(GtkWidget *widget, GdkRectangle *area);
115 static gint collection_expose(GtkWidget *widget, GdkEventExpose *event);
116 static void scroll_by(Collection *collection, gint diff);
117 static gint collection_button_press(GtkWidget *widget,
118 GdkEventButton *event);
119 static gint collection_button_release(GtkWidget *widget,
120 GdkEventButton *event);
121 static void default_draw_item(GtkWidget *widget,
122 CollectionItem *data,
123 GdkRectangle *area);
124 static gboolean default_test_point(Collection *collection,
125 int point_x, int point_y,
126 CollectionItem *data,
127 int width, int height);
128 static gint collection_motion_notify(GtkWidget *widget,
129 GdkEventMotion *event);
130 static void add_lasso_box(Collection *collection);
131 static void remove_lasso_box(Collection *collection);
132 static void draw_lasso_box(Collection *collection);
133 static int item_at_row_col(Collection *collection, int row, int col);
134 static void collection_clear_except(Collection *collection, gint exception);
135 static void cancel_wink(Collection *collection);
136 static gint collection_key_press(GtkWidget *widget, GdkEventKey *event);
137 static void get_visible_limits(Collection *collection, int *first, int *last);
138 static void scroll_to_show(Collection *collection, int item);
139 static gint focus_in(GtkWidget *widget, GdkEventFocus *event);
140 static gint focus_out(GtkWidget *widget, GdkEventFocus *event);
142 static void draw_one_item(Collection *collection, int item, GdkRectangle *area)
144 if (item < collection->number_of_items)
146 collection->draw_item((GtkWidget *) collection,
147 &collection->items[item],
148 area);
149 if (item == collection->wink_item)
150 gdk_draw_rectangle(((GtkWidget *) collection)->window,
151 ((GtkWidget *) collection)->style->black_gc,
152 FALSE,
153 area->x, area->y,
154 area->width - 1, area->height - 1);
156 if (item == collection->cursor_item)
158 gdk_draw_rectangle(((GtkWidget *) collection)->window,
159 collection->target_cb
160 ? ((GtkWidget *) collection)->style->white_gc
161 : ((GtkWidget *) collection)->style->black_gc,
162 FALSE,
163 area->x + 1, area->y + 1,
164 area->width - 3, area->height - 3);
168 GtkType collection_get_type(void)
170 static guint my_type = 0;
172 if (!my_type)
174 static const GtkTypeInfo my_info =
176 "Collection",
177 sizeof(Collection),
178 sizeof(CollectionClass),
179 (GtkClassInitFunc) collection_class_init,
180 (GtkObjectInitFunc) collection_init,
181 NULL, /* Reserved 1 */
182 NULL, /* Reserved 2 */
183 (GtkClassInitFunc) NULL /* base_class_init_func */
186 my_type = gtk_type_unique(gtk_widget_get_type(),
187 &my_info);
190 return my_type;
193 static void collection_class_init(CollectionClass *class)
195 GtkObjectClass *object_class;
196 GtkWidgetClass *widget_class;
198 object_class = (GtkObjectClass*) class;
199 widget_class = (GtkWidgetClass*) class;
201 parent_class = gtk_type_class(gtk_widget_get_type());
203 gtk_object_add_arg_type("Collection::vadjustment",
204 GTK_TYPE_ADJUSTMENT,
205 GTK_ARG_READWRITE | GTK_ARG_CONSTRUCT,
206 ARG_VADJUSTMENT);
208 object_class->destroy = collection_destroy;
209 object_class->finalize = collection_finalize;
211 widget_class->realize = collection_realize;
212 widget_class->draw = collection_draw;
213 widget_class->expose_event = collection_expose;
214 widget_class->size_request = collection_size_request;
215 widget_class->size_allocate = collection_size_allocate;
217 widget_class->key_press_event = collection_key_press;
218 widget_class->button_press_event = collection_button_press;
219 widget_class->button_release_event = collection_button_release;
220 widget_class->motion_notify_event = collection_motion_notify;
221 widget_class->focus_in_event = focus_in;
222 widget_class->focus_out_event = focus_out;
224 object_class->set_arg = collection_set_arg;
225 object_class->get_arg = collection_get_arg;
227 class->open_item = NULL;
228 class->drag_selection = NULL;
229 class->show_menu = NULL;
230 class->gain_selection = NULL;
231 class->lose_selection = NULL;
233 collection_signals[OPEN_ITEM] = gtk_signal_new("open_item",
234 GTK_RUN_LAST,
235 object_class->type,
236 GTK_SIGNAL_OFFSET(CollectionClass,
237 open_item),
238 gtk_marshal_NONE__POINTER_UINT,
239 GTK_TYPE_NONE, 2,
240 GTK_TYPE_POINTER,
241 GTK_TYPE_UINT);
242 collection_signals[DRAG_SELECTION] = gtk_signal_new("drag_selection",
243 GTK_RUN_LAST,
244 object_class->type,
245 GTK_SIGNAL_OFFSET(CollectionClass,
246 drag_selection),
247 gtk_marshal_NONE__POINTER_UINT,
248 GTK_TYPE_NONE, 2,
249 GTK_TYPE_POINTER,
250 GTK_TYPE_UINT);
251 collection_signals[SHOW_MENU] = gtk_signal_new("show_menu",
252 GTK_RUN_LAST,
253 object_class->type,
254 GTK_SIGNAL_OFFSET(CollectionClass,
255 show_menu),
256 gtk_marshal_NONE__POINTER_INT,
257 GTK_TYPE_NONE, 2,
258 GTK_TYPE_POINTER,
259 GTK_TYPE_INT);
260 collection_signals[GAIN_SELECTION] = gtk_signal_new("gain_selection",
261 GTK_RUN_LAST,
262 object_class->type,
263 GTK_SIGNAL_OFFSET(CollectionClass,
264 gain_selection),
265 gtk_marshal_NONE__UINT,
266 GTK_TYPE_NONE, 1,
267 GTK_TYPE_UINT);
268 collection_signals[LOSE_SELECTION] = gtk_signal_new("lose_selection",
269 GTK_RUN_LAST,
270 object_class->type,
271 GTK_SIGNAL_OFFSET(CollectionClass,
272 lose_selection),
273 gtk_marshal_NONE__UINT,
274 GTK_TYPE_NONE, 1,
275 GTK_TYPE_UINT);
277 gtk_object_class_add_signals(object_class,
278 collection_signals, LAST_SIGNAL);
281 static void collection_init(Collection *object)
283 g_return_if_fail(object != NULL);
284 g_return_if_fail(IS_COLLECTION(object));
286 if (!crosshair)
287 crosshair = gdk_cursor_new(GDK_CROSSHAIR);
289 GTK_WIDGET_SET_FLAGS(GTK_WIDGET(object), GTK_CAN_FOCUS);
291 object->panel = FALSE;
292 object->number_of_items = 0;
293 object->number_selected = 0;
294 object->columns = 1;
295 object->item_width = 64;
296 object->item_height = 64;
297 object->vadj = NULL;
298 object->paint_level = PAINT_OVERWRITE;
299 object->last_scroll = 0;
301 object->items = g_malloc(sizeof(CollectionItem) * MINIMUM_ITEMS);
302 object->cursor_item = -1;
303 object->wink_item = -1;
304 object->array_size = MINIMUM_ITEMS;
305 object->draw_item = default_draw_item;
306 object->test_point = default_test_point;
308 object->buttons_pressed = 0;
309 object->may_drag = FALSE;
311 return;
314 GtkWidget* collection_new(GtkAdjustment *vadj)
316 if (vadj)
317 g_return_val_if_fail(GTK_IS_ADJUSTMENT(vadj), NULL);
319 return GTK_WIDGET(gtk_widget_new(collection_get_type(),
320 "vadjustment", vadj,
321 NULL));
324 void collection_set_functions(Collection *collection,
325 CollectionDrawFunc draw_item,
326 CollectionTestFunc test_point)
328 GtkWidget *widget;
330 g_return_if_fail(collection != NULL);
331 g_return_if_fail(IS_COLLECTION(collection));
333 widget = GTK_WIDGET(collection);
335 if (!draw_item)
336 draw_item = default_draw_item;
337 if (!test_point)
338 test_point = default_test_point;
340 collection->draw_item = draw_item;
341 collection->test_point = test_point;
343 if (GTK_WIDGET_REALIZED(widget))
345 collection->paint_level = PAINT_CLEAR;
346 gtk_widget_queue_clear(widget);
350 /* After this we are unusable, but our data (if any) is still hanging around.
351 * It will be freed later with finalize.
353 static void collection_destroy(GtkObject *object)
355 Collection *collection;
357 g_return_if_fail(object != NULL);
358 g_return_if_fail(IS_COLLECTION(object));
360 collection = COLLECTION(object);
362 if (collection->wink_item != -1)
364 collection->wink_item = -1;
365 gtk_timeout_remove(collection->wink_timeout);
368 gtk_signal_disconnect_by_data(GTK_OBJECT(collection->vadj),
369 collection);
371 if (GTK_OBJECT_CLASS(parent_class)->destroy)
372 (*GTK_OBJECT_CLASS(parent_class)->destroy)(object);
375 /* This is the last thing that happens to us. Free all data. */
376 static void collection_finalize(GtkObject *object)
378 Collection *collection;
380 collection = COLLECTION(object);
382 if (collection->vadj)
384 gtk_object_unref(GTK_OBJECT(collection->vadj));
387 g_free(collection->items);
390 static void collection_realize(GtkWidget *widget)
392 Collection *collection;
393 GdkWindowAttr attributes;
394 gint attributes_mask;
395 GdkGCValues xor_values;
397 g_return_if_fail(widget != NULL);
398 g_return_if_fail(IS_COLLECTION(widget));
399 g_return_if_fail(widget->parent != NULL);
401 GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);
402 collection = COLLECTION(widget);
404 attributes.x = widget->allocation.x;
405 attributes.y = widget->allocation.y;
406 attributes.width = widget->allocation.width;
407 attributes.height = widget->allocation.height;
408 attributes.wclass = GDK_INPUT_OUTPUT;
409 attributes.window_type = GDK_WINDOW_CHILD;
410 attributes.event_mask = gtk_widget_get_events(widget) |
411 GDK_EXPOSURE_MASK |
412 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
413 GDK_BUTTON1_MOTION_MASK | GDK_BUTTON2_MOTION_MASK |
414 GDK_BUTTON3_MOTION_MASK;
415 attributes.visual = gtk_widget_get_visual(widget);
416 attributes.colormap = gtk_widget_get_colormap(widget);
418 attributes_mask = GDK_WA_X | GDK_WA_Y |
419 GDK_WA_VISUAL | GDK_WA_COLORMAP;
420 widget->window = gdk_window_new(widget->parent->window,
421 &attributes, attributes_mask);
423 widget->style = gtk_style_attach(widget->style, widget->window);
425 gdk_window_set_user_data(widget->window, widget);
427 gdk_window_set_background(widget->window,
428 &widget->style->base[GTK_STATE_INSENSITIVE]);
430 /* Try to stop everything flickering horribly */
431 gdk_window_set_static_gravities(widget->window, TRUE);
433 set_vadjustment(collection);
435 xor_values.function = GDK_XOR;
436 xor_values.foreground.red = 0xffff;
437 xor_values.foreground.green = 0xffff;
438 xor_values.foreground.blue = 0xffff;
439 gdk_color_alloc(gtk_widget_get_colormap(widget),
440 &xor_values.foreground);
441 collection->xor_gc = gdk_gc_new_with_values(widget->window,
442 &xor_values,
443 GDK_GC_FOREGROUND
444 | GDK_GC_FUNCTION);
447 static void collection_size_request(GtkWidget *widget,
448 GtkRequisition *requisition)
450 requisition->width = MIN_WIDTH;
451 requisition->height = MIN_HEIGHT;
454 static void collection_size_allocate(GtkWidget *widget,
455 GtkAllocation *allocation)
457 Collection *collection;
458 int old_columns;
460 g_return_if_fail(widget != NULL);
461 g_return_if_fail(IS_COLLECTION(widget));
462 g_return_if_fail(allocation != NULL);
464 collection = COLLECTION(widget);
466 old_columns = collection->columns;
467 if (widget->allocation.x != allocation->x
468 || widget->allocation.y != allocation->y)
469 collection->paint_level = PAINT_CLEAR;
471 widget->allocation = *allocation;
473 collection->columns = allocation->width / collection->item_width;
474 if (collection->columns < 1)
475 collection->columns = 1;
477 if (GTK_WIDGET_REALIZED(widget))
479 gdk_window_move_resize(widget->window,
480 allocation->x, allocation->y,
481 allocation->width, allocation->height);
483 if (old_columns != collection->columns)
485 collection->paint_level = PAINT_CLEAR;
486 gtk_widget_queue_clear(widget);
489 set_vadjustment(collection);
491 if (collection->cursor_item != -1)
492 scroll_to_show(collection, collection->cursor_item);
496 static gint collection_paint(Collection *collection,
497 GdkRectangle *area)
499 GdkRectangle whole, item_area;
500 GtkWidget *widget;
501 int row, col;
502 int item;
503 int scroll;
504 int start_row, last_row;
505 int start_col, last_col;
506 int phys_last_col;
507 GdkRectangle clip;
509 scroll = collection->vadj->value;
511 widget = GTK_WIDGET(collection);
513 if (collection->paint_level > PAINT_NORMAL || area == NULL)
515 guint width, height;
516 gdk_window_get_size(widget->window, &width, &height);
518 whole.x = 0;
519 whole.y = 0;
520 whole.width = width;
521 whole.height = height;
523 area = &whole;
525 if (collection->paint_level == PAINT_CLEAR
526 && !collection->lasso_box)
527 gdk_window_clear(widget->window);
529 collection->paint_level = PAINT_NORMAL;
532 /* Calculate the ranges to plot */
533 start_row = (area->y + scroll) / collection->item_height;
534 last_row = (area->y + area->height - 1 + scroll)
535 / collection->item_height;
536 row = start_row;
538 start_col = area->x / collection->item_width;
539 phys_last_col = (area->x + area->width - 1) / collection->item_width;
541 if (collection->lasso_box)
543 /* You can't be too careful with lasso boxes...
545 * clip gives the total area drawn over (this may be larger
546 * than the requested area). It's used to redraw the lasso
547 * box.
549 clip.x = start_col * collection->item_width;
550 clip.y = start_row * collection->item_height - scroll;
551 clip.width = (phys_last_col - start_col + 1)
552 * collection->item_width;
553 clip.height = (last_row - start_row + 1)
554 * collection->item_height;
556 gdk_window_clear_area(widget->window,
557 clip.x, clip.y, clip.width, clip.height);
560 if (start_col < collection->columns)
562 if (phys_last_col >= collection->columns)
563 last_col = collection->columns - 1;
564 else
565 last_col = phys_last_col;
567 col = start_col;
569 item = row * collection->columns + col;
570 item_area.width = collection->item_width;
571 item_area.height = collection->item_height;
573 while ((item == 0 || item < collection->number_of_items)
574 && row <= last_row)
576 item_area.x = col * collection->item_width;
577 item_area.y = row * collection->item_height - scroll;
579 draw_one_item(collection, item, &item_area);
580 col++;
582 if (col > last_col)
584 col = start_col;
585 row++;
586 item = row * collection->columns + col;
588 else
589 item++;
593 if (collection->lasso_box)
595 gdk_gc_set_clip_rectangle(collection->xor_gc, &clip);
596 draw_lasso_box(collection);
597 gdk_gc_set_clip_rectangle(collection->xor_gc, NULL);
600 return FALSE;
603 static void default_draw_item( GtkWidget *widget,
604 CollectionItem *item,
605 GdkRectangle *area)
607 gdk_draw_arc(widget->window,
608 item->selected ? widget->style->white_gc
609 : widget->style->black_gc,
610 TRUE,
611 area->x, area->y,
612 area->width, area->height,
613 0, 360 * 64);
617 static gboolean default_test_point(Collection *collection,
618 int point_x, int point_y,
619 CollectionItem *item,
620 int width, int height)
622 float f_x, f_y;
624 /* Convert to point in unit circle */
625 f_x = ((float) point_x / width) - 0.5;
626 f_y = ((float) point_y / height) - 0.5;
628 return (f_x * f_x) + (f_y * f_y) <= .25;
631 static void collection_set_arg( GtkObject *object,
632 GtkArg *arg,
633 guint arg_id)
635 Collection *collection;
637 collection = COLLECTION(object);
639 switch (arg_id)
641 case ARG_VADJUSTMENT:
642 collection_set_adjustment(collection,
643 GTK_VALUE_POINTER(*arg));
644 break;
645 default:
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));
659 if (collection->vadj && (collection->vadj != vadj))
661 gtk_signal_disconnect_by_data(GTK_OBJECT(collection->vadj),
662 collection);
663 gtk_object_unref(GTK_OBJECT(collection->vadj));
666 if (collection->vadj != vadj)
668 collection->vadj = vadj;
669 gtk_object_ref(GTK_OBJECT(collection->vadj));
670 gtk_object_sink(GTK_OBJECT(collection->vadj));
672 gtk_signal_connect(GTK_OBJECT(collection->vadj),
673 "changed",
674 (GtkSignalFunc) collection_adjustment,
675 collection);
676 gtk_signal_connect(GTK_OBJECT(collection->vadj),
677 "value_changed",
678 (GtkSignalFunc) collection_adjustment,
679 collection);
680 gtk_signal_connect(GTK_OBJECT(collection->vadj),
681 "disconnect",
682 (GtkSignalFunc) collection_disconnect,
683 collection);
684 collection_adjustment(vadj, collection);
688 static void collection_get_arg( GtkObject *object,
689 GtkArg *arg,
690 guint arg_id)
692 Collection *collection;
694 collection = COLLECTION(object);
696 switch (arg_id)
698 case ARG_VADJUSTMENT:
699 GTK_VALUE_POINTER(*arg) = collection->vadj;
700 break;
701 default:
702 arg->type = GTK_TYPE_INVALID;
703 break;
707 /* Something about the adjustment has changed */
708 static void collection_adjustment(GtkAdjustment *adjustment,
709 Collection *collection)
711 gint diff;
713 g_return_if_fail(adjustment != NULL);
714 g_return_if_fail(GTK_IS_ADJUSTMENT(adjustment));
715 g_return_if_fail(collection != NULL);
716 g_return_if_fail(IS_COLLECTION(collection));
718 diff = ((gint) adjustment->value) - collection->last_scroll;
720 if (diff)
722 collection->last_scroll = adjustment->value;
724 scroll_by(collection, diff);
728 static void collection_disconnect(GtkAdjustment *adjustment,
729 Collection *collection)
731 g_return_if_fail(adjustment != NULL);
732 g_return_if_fail(GTK_IS_ADJUSTMENT(adjustment));
733 g_return_if_fail(collection != NULL);
734 g_return_if_fail(IS_COLLECTION(collection));
736 collection_set_adjustment(collection, NULL);
739 static void set_vadjustment(Collection *collection)
741 GtkWidget *widget;
742 guint height;
743 int cols, rows;
745 widget = GTK_WIDGET(collection);
747 if (!GTK_WIDGET_REALIZED(widget))
748 return;
750 gdk_window_get_size(widget->window, NULL, &height);
751 cols = collection->columns;
752 rows = (collection->number_of_items + cols - 1) / cols;
754 collection->vadj->lower = 0.0;
755 collection->vadj->upper = collection->item_height * rows;
757 collection->vadj->step_increment =
758 MIN(collection->vadj->upper, collection->item_height / 4);
760 collection->vadj->page_increment =
761 MIN(collection->vadj->upper,
762 height - 5.0);
764 collection->vadj->page_size = MIN(collection->vadj->upper, height);
766 collection->vadj->value = MIN(collection->vadj->value,
767 collection->vadj->upper - collection->vadj->page_size);
769 collection->vadj->value = MAX(collection->vadj->value, 0.0);
771 gtk_signal_emit_by_name(GTK_OBJECT(collection->vadj), "changed");
774 static void collection_draw(GtkWidget *widget, GdkRectangle *area)
776 Collection *collection;
778 g_return_if_fail(widget != NULL);
779 g_return_if_fail(IS_COLLECTION(widget));
780 g_return_if_fail(area != NULL); /* Not actually used */
782 collection = COLLECTION(widget);
784 if (collection->paint_level > PAINT_NORMAL)
785 collection_paint(collection, area);
788 static gint collection_expose(GtkWidget *widget, GdkEventExpose *event)
790 g_return_val_if_fail(widget != NULL, FALSE);
791 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
792 g_return_val_if_fail(event != NULL, FALSE);
794 collection_paint(COLLECTION(widget), &event->area);
796 return FALSE;
799 /* Positive makes the contents go move up the screen */
800 static void scroll_by(Collection *collection, gint diff)
802 GtkWidget *widget;
803 guint width, height;
804 guint from_y, to_y;
805 guint amount;
806 GdkRectangle new_area;
808 if (diff == 0)
809 return;
811 widget = GTK_WIDGET(collection);
813 if (collection->lasso_box)
814 remove_lasso_box(collection);
816 gdk_window_get_size(widget->window, &width, &height);
817 new_area.x = 0;
818 new_area.width = width;
820 if (diff > 0)
822 amount = diff;
823 from_y = amount;
824 to_y = 0;
825 new_area.y = height - amount;
827 else
829 amount = -diff;
830 from_y = 0;
831 to_y = amount;
832 new_area.y = 0;
835 new_area.height = amount;
837 if (amount < height)
839 gdk_draw_pixmap(widget->window,
840 widget->style->white_gc,
841 widget->window,
843 from_y,
845 to_y,
846 width,
847 height - amount);
848 /* We have to redraw everything because any pending
849 * expose events now contain invalid areas.
850 * Don't need to clear the area first though...
852 if (collection->paint_level < PAINT_OVERWRITE)
853 collection->paint_level = PAINT_OVERWRITE;
855 else
856 collection->paint_level = PAINT_CLEAR;
858 gdk_window_clear_area(widget->window,
859 0, new_area.y,
860 width, new_area.height);
861 collection_paint(collection, NULL);
864 static void resize_arrays(Collection *collection, guint new_size)
866 g_return_if_fail(collection != NULL);
867 g_return_if_fail(IS_COLLECTION(collection));
868 g_return_if_fail(new_size >= collection->number_of_items);
870 collection->items = g_realloc(collection->items,
871 sizeof(CollectionItem) * new_size);
872 collection->array_size = new_size;
875 static void return_pressed(Collection *collection)
877 int item = collection->cursor_item;
878 CollectionTargetFunc cb = collection->target_cb;
879 gpointer data = collection->target_data;
881 collection_target(collection, NULL, NULL);
882 if (item < 0 || item >= collection->number_of_items)
883 return;
885 if (cb)
887 cb(collection, item, data);
888 return;
891 gtk_signal_emit(GTK_OBJECT(collection),
892 collection_signals[OPEN_ITEM],
893 collection->items[item].data,
894 item);
897 static gint collection_key_press(GtkWidget *widget, GdkEventKey *event)
899 Collection *collection;
900 int item;
902 g_return_val_if_fail(widget != NULL, FALSE);
903 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
904 g_return_val_if_fail(event != NULL, FALSE);
906 collection = (Collection *) widget;
907 item = collection->cursor_item;
909 switch (event->keyval)
911 case GDK_Left:
912 collection_move_cursor(collection, 0, -1);
913 break;
914 case GDK_Right:
915 collection_move_cursor(collection, 0, 1);
916 break;
917 case GDK_Up:
918 collection_move_cursor(collection, -1, 0);
919 break;
920 case GDK_Down:
921 collection_move_cursor(collection, 1, 0);
922 break;
923 case GDK_Home:
924 collection_set_cursor_item(collection, 0);
925 break;
926 case GDK_End:
927 collection_set_cursor_item(collection,
928 MAX((gint) collection->number_of_items - 1, 0));
929 break;
930 case GDK_Page_Up:
931 collection_move_cursor(collection, -10, 0);
932 break;
933 case GDK_Page_Down:
934 collection_move_cursor(collection, 10, 0);
935 break;
936 case GDK_Return:
937 return_pressed(collection);
938 break;
939 case GDK_Escape:
940 if (!collection->target_cb)
941 return FALSE; /* Pass it on */
942 collection_target(collection, NULL, NULL);
943 break;
944 case ' ':
945 if (item >=0 && item < collection->number_of_items)
946 collection_toggle_item(collection, item);
947 break;
948 default:
949 return FALSE;
952 return TRUE;
955 static gint collection_button_press(GtkWidget *widget,
956 GdkEventButton *event)
958 Collection *collection;
959 int row, col;
960 int item;
961 int action;
962 int scroll;
963 guint stacked_time;
965 g_return_val_if_fail(widget != NULL, FALSE);
966 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
967 g_return_val_if_fail(event != NULL, FALSE);
969 collection = COLLECTION(widget);
971 collection->item_clicked = -1;
973 if (event->button > 3)
975 int diff;
977 /* Wheel mouse scrolling */
978 if (event->button == 4)
979 diff = -((signed int) collection->item_height) / 4;
980 else if (event->button == 5)
981 diff = collection->item_height / 4;
982 else
983 diff = 0;
985 if (diff)
987 int old_value = collection->vadj->value;
988 int new_value = 0;
989 gboolean box = collection->lasso_box;
991 new_value = CLAMP(old_value + diff, 0.0,
992 collection->vadj->upper
993 - collection->vadj->page_size);
994 diff = new_value - old_value;
995 if (diff)
997 if (box)
999 remove_lasso_box(collection);
1000 collection->drag_box_y[0] -= diff;
1002 collection->vadj->value = new_value;
1003 gtk_signal_emit_by_name(
1004 GTK_OBJECT(collection->vadj),
1005 "changed");
1006 if (box)
1007 add_lasso_box(collection);
1010 return FALSE;
1013 if (collection->cursor_item != -1)
1014 collection_set_cursor_item(collection, -1);
1016 scroll = collection->vadj->value;
1018 if (event->type == GDK_BUTTON_PRESS &&
1019 event->button != collection_menu_button)
1021 if (collection->buttons_pressed++ == 0)
1022 gtk_grab_add(widget);
1023 else
1024 return FALSE; /* Ignore extra presses */
1027 if (event->state & GDK_CONTROL_MASK && !collection_single_click)
1028 action = 2;
1029 else
1030 action = event->button;
1032 /* Ignore all clicks while we are dragging a lasso box */
1033 if (collection->lasso_box)
1034 return TRUE;
1036 col = event->x / collection->item_width;
1037 row = (event->y + scroll) / collection->item_height;
1039 if (col < 0 || row < 0 || col >= collection->columns)
1040 item = -1;
1041 else
1043 item = col + row * collection->columns;
1044 if (item >= collection->number_of_items
1046 !collection->test_point(collection,
1047 event->x - col * collection->item_width,
1048 event->y - row * collection->item_height
1049 + scroll,
1050 &collection->items[item],
1051 collection->item_width,
1052 collection->item_height))
1054 item = -1;
1058 if (collection->target_cb)
1060 CollectionTargetFunc cb = collection->target_cb;
1061 gpointer data = collection->target_data;
1063 collection_target(collection, NULL, NULL);
1064 if (collection->buttons_pressed)
1066 gtk_grab_remove(widget);
1067 collection->buttons_pressed = 0;
1069 if (item > -1 && event->button != collection_menu_button)
1070 cb(collection, item, data);
1071 return TRUE;
1074 collection->drag_box_x[0] = event->x;
1075 collection->drag_box_y[0] = event->y;
1076 collection->item_clicked = item;
1078 stacked_time = current_event_time;
1079 current_event_time = event->time;
1081 if (event->button == collection_menu_button)
1083 gtk_signal_emit(GTK_OBJECT(collection),
1084 collection_signals[SHOW_MENU],
1085 event,
1086 item);
1088 else if (event->type == GDK_2BUTTON_PRESS && collection->panel)
1090 /* Do nothing */
1092 else if ((event->type == GDK_2BUTTON_PRESS && !collection_single_click)
1093 || collection->panel)
1095 if (item >= 0)
1097 if (collection->buttons_pressed)
1099 gtk_grab_remove(widget);
1100 collection->buttons_pressed = 0;
1102 collection_unselect_item(collection, item);
1103 gtk_signal_emit(GTK_OBJECT(collection),
1104 collection_signals[OPEN_ITEM],
1105 collection->items[item].data,
1106 item);
1109 else if (event->type == GDK_BUTTON_PRESS)
1111 collection->may_drag = event->button < collection_menu_button;
1113 if (item >= 0)
1115 if (action == 1)
1117 if (!collection->items[item].selected)
1119 collection_select_item(collection,
1120 item);
1121 collection_clear_except(collection,
1122 item);
1125 else
1126 collection_toggle_item(collection, item);
1128 else if (action == 1)
1129 collection_clear_selection(collection);
1132 current_event_time = stacked_time;
1133 return FALSE;
1136 static gint collection_button_release(GtkWidget *widget,
1137 GdkEventButton *event)
1139 Collection *collection;
1140 int top, bottom;
1141 int row, last_row;
1142 int w, h;
1143 int col, start_col, last_col;
1144 int scroll;
1145 int item;
1146 guint stacked_time;
1147 int button;
1149 g_return_val_if_fail(widget != NULL, FALSE);
1150 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1151 g_return_val_if_fail(event != NULL, FALSE);
1153 collection = COLLECTION(widget);
1154 button = event->button;
1156 scroll = collection->vadj->value;
1158 if (event->button > 3 || event->button == collection_menu_button)
1159 return FALSE;
1160 if (collection->buttons_pressed == 0)
1161 return FALSE;
1162 if (--collection->buttons_pressed == 0)
1163 gtk_grab_remove(widget);
1164 else
1165 return FALSE; /* Wait until ALL buttons are up */
1167 if (!collection->lasso_box)
1169 int item = collection->item_clicked;
1171 if (collection_single_click && item > -1
1172 && item < collection->number_of_items
1173 && (event->state & GDK_CONTROL_MASK) == 0)
1175 int dx = event->x - collection->drag_box_x[0];
1176 int dy = event->y - collection->drag_box_y[0];
1178 if (ABS(dx) + ABS(dy) > 9)
1179 return FALSE;
1181 collection_unselect_item(collection, item);
1182 gtk_signal_emit(GTK_OBJECT(collection),
1183 collection_signals[OPEN_ITEM],
1184 collection->items[item].data,
1185 item);
1188 return FALSE;
1191 remove_lasso_box(collection);
1193 w = collection->item_width;
1194 h = collection->item_height;
1196 top = collection->drag_box_y[0] + scroll;
1197 bottom = collection->drag_box_y[1] + scroll;
1198 if (top > bottom)
1200 int tmp;
1201 tmp = top;
1202 top = bottom;
1203 bottom = tmp;
1205 top += h / 4;
1206 bottom -= h / 4;
1208 row = MAX(top / h, 0);
1209 last_row = bottom / h;
1211 top = collection->drag_box_x[0]; /* (left) */
1212 bottom = collection->drag_box_x[1];
1213 if (top > bottom)
1215 int tmp;
1216 tmp = top;
1217 top = bottom;
1218 bottom = tmp;
1220 top += w / 4;
1221 bottom -= w / 4;
1222 start_col = MAX(top / w, 0);
1223 last_col = bottom / w;
1224 if (last_col >= collection->columns)
1225 last_col = collection->columns - 1;
1227 stacked_time = current_event_time;
1228 current_event_time = event->time;
1230 while (row <= last_row)
1232 col = start_col;
1233 item = row * collection->columns + col;
1234 while (col <= last_col)
1236 if (item >= collection->number_of_items)
1238 current_event_time = stacked_time;
1239 return FALSE;
1242 if (button == 1)
1243 collection_select_item(collection, item);
1244 else
1245 collection_toggle_item(collection, item);
1246 col++;
1247 item++;
1249 row++;
1252 current_event_time = stacked_time;
1254 return FALSE;
1257 static gint collection_motion_notify(GtkWidget *widget,
1258 GdkEventMotion *event)
1260 Collection *collection;
1261 int x, y;
1262 guint stacked_time;
1264 g_return_val_if_fail(widget != NULL, FALSE);
1265 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1266 g_return_val_if_fail(event != NULL, FALSE);
1268 collection = COLLECTION(widget);
1270 if (collection->buttons_pressed == 0)
1271 return FALSE;
1273 stacked_time = current_event_time;
1274 current_event_time = event->time;
1276 if (event->window != widget->window)
1277 gdk_window_get_pointer(widget->window, &x, &y, NULL);
1278 else
1280 x = event->x;
1281 y = event->y;
1284 if (collection->lasso_box)
1286 int new_value = 0, diff;
1287 int height;
1289 gdk_window_get_size(widget->window, NULL, &height);
1291 if (y < 0)
1293 int old_value = collection->vadj->value;
1295 new_value = MAX(old_value + y / 10, 0.0);
1296 diff = new_value - old_value;
1298 else if (y > height)
1300 int old_value = collection->vadj->value;
1302 new_value = MIN(old_value + (y - height) / 10,
1303 collection->vadj->upper
1304 - collection->vadj->page_size);
1305 diff = new_value - old_value;
1307 else
1308 diff = 0;
1310 remove_lasso_box(collection);
1311 collection->drag_box_x[1] = x;
1312 collection->drag_box_y[1] = y;
1314 if (diff)
1316 collection->drag_box_y[0] -= diff;
1317 collection->vadj->value = new_value;
1318 gtk_signal_emit_by_name(GTK_OBJECT(collection->vadj),
1319 "changed");
1321 add_lasso_box(collection);
1323 else if (collection->may_drag)
1325 int dx = x - collection->drag_box_x[0];
1326 int dy = y - collection->drag_box_y[0];
1328 if (abs(dx) > 9 || abs(dy) > 9)
1330 int row, col, item;
1331 int scroll = collection->vadj->value;
1333 collection->may_drag = FALSE;
1335 col = collection->drag_box_x[0]
1336 / collection->item_width;
1337 row = (collection->drag_box_y[0] + scroll)
1338 / collection->item_height;
1339 item = item_at_row_col(collection, row, col);
1341 if (item != -1 && collection->test_point(collection,
1342 collection->drag_box_x[0] -
1343 col * collection->item_width,
1344 collection->drag_box_y[0]
1345 - row * collection->item_height
1346 + scroll,
1347 &collection->items[item],
1348 collection->item_width,
1349 collection->item_height))
1351 collection->buttons_pressed = 0;
1352 gtk_grab_remove(widget);
1353 collection_select_item(collection, item);
1354 gtk_signal_emit(GTK_OBJECT(collection),
1355 collection_signals[DRAG_SELECTION],
1356 event,
1357 collection->number_selected);
1359 else
1361 collection->drag_box_x[1] = x;
1362 collection->drag_box_y[1] = y;
1363 add_lasso_box(collection);
1368 current_event_time = stacked_time;
1369 return FALSE;
1372 static void add_lasso_box(Collection *collection)
1374 g_return_if_fail(collection != NULL);
1375 g_return_if_fail(IS_COLLECTION(collection));
1376 g_return_if_fail(collection->lasso_box == FALSE);
1378 collection->lasso_box = TRUE;
1379 draw_lasso_box(collection);
1382 static void draw_lasso_box(Collection *collection)
1384 GtkWidget *widget;
1385 int x, y, width, height;
1387 widget = GTK_WIDGET(collection);
1389 x = MIN(collection->drag_box_x[0], collection->drag_box_x[1]);
1390 y = MIN(collection->drag_box_y[0], collection->drag_box_y[1]);
1391 width = abs(collection->drag_box_x[1] - collection->drag_box_x[0]);
1392 height = abs(collection->drag_box_y[1] - collection->drag_box_y[0]);
1394 gdk_draw_rectangle(widget->window, collection->xor_gc, FALSE,
1395 x, y, width, height);
1398 static void remove_lasso_box(Collection *collection)
1400 g_return_if_fail(collection != NULL);
1401 g_return_if_fail(IS_COLLECTION(collection));
1402 g_return_if_fail(collection->lasso_box == TRUE);
1404 draw_lasso_box(collection);
1406 collection->lasso_box = FALSE;
1408 return;
1411 /* Convert a row,col address to an item number, or -1 if none */
1412 static int item_at_row_col(Collection *collection, int row, int col)
1414 int item;
1416 if (row < 0 || col < 0 || col >= collection->columns)
1417 return -1;
1419 item = col + row * collection->columns;
1421 if (item >= collection->number_of_items)
1422 return -1;
1423 return item;
1426 /* Make sure that 'item' is fully visible (vertically), scrolling if not. */
1427 static void scroll_to_show(Collection *collection, int item)
1429 int first, last, row;
1431 g_return_if_fail(collection != NULL);
1432 g_return_if_fail(IS_COLLECTION(collection));
1434 row = item / collection->columns;
1435 get_visible_limits(collection, &first, &last);
1437 if (row <= first)
1439 gtk_adjustment_set_value(collection->vadj,
1440 row * collection->item_height);
1442 else if (row >= last)
1444 GtkWidget *widget = (GtkWidget *) collection;
1445 int height;
1447 if (GTK_WIDGET_REALIZED(widget))
1449 gdk_window_get_size(widget->window, NULL, &height);
1450 gtk_adjustment_set_value(collection->vadj,
1451 (row + 1) * collection->item_height - height);
1456 /* Return the first and last rows which are [partly] visible. Does not
1457 * ensure that the rows actually exist (contain items).
1459 static void get_visible_limits(Collection *collection, int *first, int *last)
1461 GtkWidget *widget = (GtkWidget *) collection;
1462 int scroll, height;
1464 g_return_if_fail(collection != NULL);
1465 g_return_if_fail(IS_COLLECTION(collection));
1466 g_return_if_fail(first != NULL && last != NULL);
1468 if (!GTK_WIDGET_REALIZED(widget))
1470 *first = 0;
1471 *last = 0;
1473 else
1475 scroll = collection->vadj->value;
1476 gdk_window_get_size(widget->window, NULL, &height);
1478 *first = MAX(scroll / collection->item_height, 0);
1479 *last = (scroll + height - 1) /collection->item_height;
1481 if (*last < *first)
1482 *last = *first;
1486 /* Unselect all items except number item (-1 to unselect everything) */
1487 static void collection_clear_except(Collection *collection, gint exception)
1489 GtkWidget *widget;
1490 GdkRectangle area;
1491 int item = 0;
1492 int scroll;
1493 int end; /* Selected items to end up with */
1495 widget = GTK_WIDGET(collection);
1496 scroll = collection->vadj->value;
1498 end = exception >= 0 && exception < collection->number_of_items
1499 ? collection->items[exception].selected != 0 : 0;
1501 area.width = collection->item_width;
1502 area.height = collection->item_height;
1504 if (collection->number_selected == 0)
1505 return;
1507 while (collection->number_selected > end)
1509 while (item == exception || !collection->items[item].selected)
1510 item++;
1512 area.x = (item % collection->columns) * area.width;
1513 area.y = (item / collection->columns) * area.height
1514 - scroll;
1516 collection->items[item++].selected = FALSE;
1517 gdk_window_clear_area(widget->window,
1518 area.x, area.y, area.width, area.height);
1519 collection_paint(collection, &area);
1521 collection->number_selected--;
1524 if (end == 0)
1525 gtk_signal_emit(GTK_OBJECT(collection),
1526 collection_signals[LOSE_SELECTION],
1527 current_event_time);
1530 /* Cancel the current wink effect. */
1531 static void cancel_wink(Collection *collection)
1533 gint item;
1535 g_return_if_fail(collection != NULL);
1536 g_return_if_fail(IS_COLLECTION(collection));
1537 g_return_if_fail(collection->wink_item != -1);
1539 item = collection->wink_item;
1541 collection->wink_item = -1;
1542 gtk_timeout_remove(collection->wink_timeout);
1544 collection_draw_item(collection, item, TRUE);
1547 static gboolean cancel_wink_timeout(Collection *collection)
1549 gint item;
1551 g_return_val_if_fail(collection != NULL, FALSE);
1552 g_return_val_if_fail(IS_COLLECTION(collection), FALSE);
1553 g_return_val_if_fail(collection->wink_item != -1, FALSE);
1555 item = collection->wink_item;
1557 collection->wink_item = -1;
1559 collection_draw_item(collection, item, TRUE);
1561 return FALSE;
1564 static gint focus_in(GtkWidget *widget, GdkEventFocus *event)
1566 g_return_val_if_fail(widget != NULL, FALSE);
1567 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1568 g_return_val_if_fail(event != NULL, FALSE);
1570 GTK_WIDGET_SET_FLAGS(widget, GTK_HAS_FOCUS);
1571 gtk_widget_draw_focus(widget);
1573 return FALSE;
1576 static gint focus_out(GtkWidget *widget, GdkEventFocus *event)
1578 g_return_val_if_fail(widget != NULL, FALSE);
1579 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1580 g_return_val_if_fail(event != NULL, FALSE);
1582 GTK_WIDGET_UNSET_FLAGS(widget, GTK_HAS_FOCUS);
1583 gtk_widget_draw_focus(widget);
1585 return FALSE;
1588 /* Functions for managing collections */
1590 /* Remove all objects from the collection */
1591 void collection_clear(Collection *collection)
1593 int prev_selected;
1595 g_return_if_fail(IS_COLLECTION(collection));
1597 if (collection->number_of_items == 0)
1598 return;
1600 if (collection->wink_item != -1)
1602 collection->wink_item = -1;
1603 gtk_timeout_remove(collection->wink_timeout);
1606 collection_set_cursor_item(collection,
1607 collection->cursor_item == -1 ? -1: 0);
1608 prev_selected = collection->number_selected;
1609 collection->number_of_items = collection->number_selected = 0;
1611 resize_arrays(collection, MINIMUM_ITEMS);
1613 collection->paint_level = PAINT_CLEAR;
1615 gtk_widget_queue_clear(GTK_WIDGET(collection));
1617 if (prev_selected && collection->number_selected == 0)
1618 gtk_signal_emit(GTK_OBJECT(collection),
1619 collection_signals[LOSE_SELECTION],
1620 current_event_time);
1623 /* Inserts a new item at the end. The new item is unselected, and its
1624 * number is returned.
1626 gint collection_insert(Collection *collection, gpointer data)
1628 int item;
1630 g_return_val_if_fail(IS_COLLECTION(collection), -1);
1632 item = collection->number_of_items;
1634 if (item >= collection->array_size)
1635 resize_arrays(collection, item + (item >> 1));
1637 collection->items[item].data = data;
1638 collection->items[item].selected = FALSE;
1640 collection->number_of_items++;
1642 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection)))
1644 set_vadjustment(collection);
1645 collection_draw_item(collection,
1646 collection->number_of_items - 1,
1647 FALSE);
1650 return item;
1653 /* Unselect an item by number */
1654 void collection_unselect_item(Collection *collection, gint item)
1656 g_return_if_fail(collection != NULL);
1657 g_return_if_fail(IS_COLLECTION(collection));
1658 g_return_if_fail(item >= 0 && item < collection->number_of_items);
1660 if (collection->items[item].selected)
1662 collection->items[item].selected = FALSE;
1663 collection_draw_item(collection, item, TRUE);
1665 if (--collection->number_selected == 0)
1666 gtk_signal_emit(GTK_OBJECT(collection),
1667 collection_signals[LOSE_SELECTION],
1668 current_event_time);
1672 /* Select an item by number */
1673 void collection_select_item(Collection *collection, gint item)
1675 g_return_if_fail(collection != NULL);
1676 g_return_if_fail(IS_COLLECTION(collection));
1677 g_return_if_fail(item >= 0 && item < collection->number_of_items);
1679 if (collection->items[item].selected)
1680 return; /* Already selected */
1682 collection->items[item].selected = TRUE;
1683 collection_draw_item(collection, item, TRUE);
1685 if (collection->number_selected++ == 0)
1686 gtk_signal_emit(GTK_OBJECT(collection),
1687 collection_signals[GAIN_SELECTION],
1688 current_event_time);
1691 /* Toggle the selected state of an item (by number) */
1692 void collection_toggle_item(Collection *collection, gint item)
1694 g_return_if_fail(collection != NULL);
1695 g_return_if_fail(IS_COLLECTION(collection));
1696 g_return_if_fail(item >= 0 && item < collection->number_of_items);
1698 if (collection->items[item].selected)
1700 collection->items[item].selected = FALSE;
1701 if (--collection->number_selected == 0)
1702 gtk_signal_emit(GTK_OBJECT(collection),
1703 collection_signals[LOSE_SELECTION],
1704 current_event_time);
1706 else
1708 collection->items[item].selected = TRUE;
1709 if (collection->number_selected++ == 0)
1710 gtk_signal_emit(GTK_OBJECT(collection),
1711 collection_signals[GAIN_SELECTION],
1712 current_event_time);
1714 collection_draw_item(collection, item, TRUE);
1717 /* Select all items in the collection */
1718 void collection_select_all(Collection *collection)
1720 GtkWidget *widget;
1721 GdkRectangle area;
1722 int item = 0;
1723 int scroll;
1725 g_return_if_fail(collection != NULL);
1726 g_return_if_fail(IS_COLLECTION(collection));
1728 widget = GTK_WIDGET(collection);
1729 scroll = collection->vadj->value;
1731 area.width = collection->item_width;
1732 area.height = collection->item_height;
1734 if (collection->number_selected == collection->number_of_items)
1735 return; /* Nothing to do */
1737 while (collection->number_selected < collection->number_of_items)
1739 while (collection->items[item].selected)
1740 item++;
1742 area.x = (item % collection->columns) * area.width;
1743 area.y = (item / collection->columns) * area.height
1744 - scroll;
1746 collection->items[item++].selected = TRUE;
1747 gdk_window_clear_area(widget->window,
1748 area.x, area.y, area.width, area.height);
1749 collection_paint(collection, &area);
1751 collection->number_selected++;
1754 gtk_signal_emit(GTK_OBJECT(collection),
1755 collection_signals[GAIN_SELECTION],
1756 current_event_time);
1759 /* Unselect all items in the collection */
1760 void collection_clear_selection(Collection *collection)
1762 g_return_if_fail(collection != NULL);
1763 g_return_if_fail(IS_COLLECTION(collection));
1765 collection_clear_except(collection, -1);
1768 /* Force a redraw of the specified item, if it is visible */
1769 void collection_draw_item(Collection *collection, gint item, gboolean blank)
1771 int height;
1772 GdkRectangle area;
1773 GtkWidget *widget;
1774 int row, col;
1775 int scroll;
1776 int area_y, area_height; /* NOT shorts! */
1778 g_return_if_fail(collection != NULL);
1779 g_return_if_fail(IS_COLLECTION(collection));
1780 g_return_if_fail(item >= 0 &&
1781 (item == 0 || item < collection->number_of_items));
1783 widget = GTK_WIDGET(collection);
1784 if (!GTK_WIDGET_REALIZED(widget))
1785 return;
1787 col = item % collection->columns;
1788 row = item / collection->columns;
1789 scroll = collection->vadj->value; /* (round to int) */
1791 area.x = col * collection->item_width;
1792 area_y = row * collection->item_height - scroll;
1793 area.width = collection->item_width;
1794 area_height = collection->item_height;
1796 if (area_y + area_height < 0)
1797 return;
1799 gdk_window_get_size(widget->window, NULL, &height);
1801 if (area_y > height)
1802 return;
1804 area.y = area_y;
1805 area.height = area_height;
1807 if (blank || collection->lasso_box)
1808 gdk_window_clear_area(widget->window,
1809 area.x, area.y, area.width, area.height);
1811 draw_one_item(collection, item, &area);
1813 if (collection->lasso_box)
1815 gdk_gc_set_clip_rectangle(collection->xor_gc, &area);
1816 draw_lasso_box(collection);
1817 gdk_gc_set_clip_rectangle(collection->xor_gc, NULL);
1821 void collection_set_item_size(Collection *collection, int width, int height)
1823 GtkWidget *widget;
1825 g_return_if_fail(collection != NULL);
1826 g_return_if_fail(IS_COLLECTION(collection));
1827 g_return_if_fail(width > 4 && height > 4);
1829 widget = GTK_WIDGET(collection);
1831 collection->item_width = width;
1832 collection->item_height = height;
1834 if (GTK_WIDGET_REALIZED(widget))
1836 int window_width;
1838 collection->paint_level = PAINT_CLEAR;
1839 gdk_window_get_size(widget->window, &window_width, NULL);
1840 collection->columns = MAX(window_width / collection->item_width,
1843 set_vadjustment(collection);
1844 if (collection->cursor_item != -1)
1845 scroll_to_show(collection, collection->cursor_item);
1846 gtk_widget_queue_draw(widget);
1850 /* Cursor is positioned on item with the same data as before the sort.
1851 * Same for the wink item.
1853 void collection_qsort(Collection *collection,
1854 int (*compar)(const void *, const void *))
1856 int cursor, wink, items;
1857 gpointer cursor_data = NULL;
1858 gpointer wink_data = NULL;
1860 g_return_if_fail(collection != NULL);
1861 g_return_if_fail(IS_COLLECTION(collection));
1862 g_return_if_fail(compar != NULL);
1864 items = collection->number_of_items;
1866 wink = collection->wink_item;
1867 if (wink >= 0 && wink < items)
1868 wink_data = collection->items[wink].data;
1869 else
1870 wink = -1;
1872 cursor = collection->cursor_item;
1873 if (cursor >= 0 && cursor < items)
1874 cursor_data = collection->items[cursor].data;
1875 else
1876 cursor = -1;
1878 if (collection->wink_item != -1)
1880 collection->wink_item = -1;
1881 gtk_timeout_remove(collection->wink_timeout);
1884 qsort(collection->items, items, sizeof(collection->items[0]), compar);
1886 if (cursor > -1 || wink > -1)
1888 int item;
1890 for (item = 0; item < items; item++)
1892 if (collection->items[item].data == cursor_data)
1893 collection_set_cursor_item(collection, item);
1894 if (collection->items[item].data == wink_data)
1895 collection_wink_item(collection, item);
1899 collection->paint_level = PAINT_CLEAR;
1901 gtk_widget_queue_draw(GTK_WIDGET(collection));
1904 /* Find an item in an unsorted collection.
1905 * Returns the item number, or -1 if not found.
1907 int collection_find_item(Collection *collection, gpointer data,
1908 int (*compar)(const void *, const void *))
1910 int i;
1912 g_return_val_if_fail(collection != NULL, -1);
1913 g_return_val_if_fail(IS_COLLECTION(collection), -1);
1914 g_return_val_if_fail(compar != NULL, -1);
1916 for (i = 0; i < collection->number_of_items; i++)
1917 if (compar(&collection->items[i].data, &data) == 0)
1918 return i;
1920 return -1;
1923 /* The collection may be in either normal mode or panel mode.
1924 * In panel mode:
1925 * - a single click calls open_item
1926 * - items are never selected
1927 * - lasso boxes are disabled
1929 void collection_set_panel(Collection *collection, gboolean panel)
1931 g_return_if_fail(collection != NULL);
1932 g_return_if_fail(IS_COLLECTION(collection));
1934 collection->panel = panel == TRUE;
1936 if (collection->panel)
1938 collection_clear_selection(collection);
1939 if (collection->lasso_box)
1940 remove_lasso_box(collection);
1944 /* Return the number of the item under the point (x,y), or -1 for none.
1945 * This may call your test_point callback. The point is relative to the
1946 * collection's origin.
1948 int collection_get_item(Collection *collection, int x, int y)
1950 int scroll;
1951 int row, col;
1952 int item;
1954 g_return_val_if_fail(collection != NULL, -1);
1956 scroll = collection->vadj->value;
1957 col = x / collection->item_width;
1958 row = (y + scroll) / collection->item_height;
1960 if (col < 0 || row < 0 || col >= collection->columns)
1961 return -1;
1963 item = col + row * collection->columns;
1964 if (item >= collection->number_of_items
1966 !collection->test_point(collection,
1967 x - col * collection->item_width,
1968 y - row * collection->item_height
1969 + scroll,
1970 &collection->items[item],
1971 collection->item_width,
1972 collection->item_height))
1974 return -1;
1977 return item;
1980 /* Set the cursor/highlight over the given item. Passing -1
1981 * hides the cursor. As a special case, you may set the cursor item
1982 * to zero when there are no items.
1984 void collection_set_cursor_item(Collection *collection, gint item)
1986 int old_item;
1988 g_return_if_fail(collection != NULL);
1989 g_return_if_fail(IS_COLLECTION(collection));
1990 g_return_if_fail(item >= -1 &&
1991 (item < collection->number_of_items || item == 0));
1993 old_item = collection->cursor_item;
1995 if (old_item == item)
1996 return;
1998 collection->cursor_item = item;
2000 if (old_item != -1)
2001 collection_draw_item(collection, old_item, TRUE);
2003 if (item != -1)
2005 collection_draw_item(collection, item, TRUE);
2006 scroll_to_show(collection, item);
2010 /* Briefly highlight an item to draw the user's attention to it.
2011 * -1 cancels the effect, as does deleting items, sorting the collection
2012 * or starting a new wink effect.
2013 * Otherwise, the effect will cancel itself after a short pause.
2014 * */
2015 void collection_wink_item(Collection *collection, gint item)
2017 g_return_if_fail(collection != NULL);
2018 g_return_if_fail(IS_COLLECTION(collection));
2019 g_return_if_fail(item >= -1 && item < collection->number_of_items);
2021 if (collection->wink_item != -1)
2022 cancel_wink(collection);
2023 if (item == -1)
2024 return;
2026 collection->wink_item = item;
2027 collection->wink_timeout = gtk_timeout_add(300,
2028 (GtkFunction) cancel_wink_timeout,
2029 collection);
2030 collection_draw_item(collection, item, TRUE);
2031 scroll_to_show(collection, item);
2033 gdk_flush();
2036 /* Call test(item, data) on each item in the collection.
2037 * Remove all items for which it returns TRUE. test() should
2038 * free the data before returning TRUE. The collection is in an
2039 * inconsistant state during this call (ie, when test() is called).
2041 void collection_delete_if(Collection *collection,
2042 gboolean (*test)(gpointer item, gpointer data),
2043 gpointer data)
2045 int in, out = 0;
2046 int selected = 0;
2047 int cursor;
2049 g_return_if_fail(collection != NULL);
2050 g_return_if_fail(IS_COLLECTION(collection));
2051 g_return_if_fail(test != NULL);
2053 cursor = collection->cursor_item;
2055 for (in = 0; in < collection->number_of_items; in++)
2057 if (!test(collection->items[in].data, data))
2059 if (collection->items[in].selected)
2061 collection->items[out].selected = TRUE;
2062 selected++;
2064 else
2065 collection->items[out].selected = FALSE;
2067 collection->items[out++].data =
2068 collection->items[in].data;
2070 else if (cursor >= in)
2071 cursor--;
2074 if (in != out)
2076 collection->cursor_item = cursor;
2078 if (collection->wink_item != -1)
2080 collection->wink_item = -1;
2081 gtk_timeout_remove(collection->wink_timeout);
2084 collection->number_of_items = out;
2085 collection->number_selected = selected;
2086 resize_arrays(collection,
2087 MAX(collection->number_of_items, MINIMUM_ITEMS));
2089 collection->paint_level = PAINT_CLEAR;
2091 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection)))
2093 set_vadjustment(collection);
2094 gtk_widget_queue_draw(GTK_WIDGET(collection));
2099 /* Display a cross-hair pointer and the next time an item is clicked,
2100 * call the callback function. If the background is clicked or NULL
2101 * is passed as the callback then revert to normal operation.
2103 void collection_target(Collection *collection,
2104 CollectionTargetFunc callback,
2105 gpointer user_data)
2107 g_return_if_fail(collection != NULL);
2108 g_return_if_fail(IS_COLLECTION(collection));
2110 if (callback != collection->target_cb)
2111 gdk_window_set_cursor(GTK_WIDGET(collection)->window,
2112 callback ? crosshair : NULL);
2114 collection->target_cb = callback;
2115 collection->target_data = user_data;
2117 if (collection->cursor_item != -1)
2118 collection_draw_item(collection, collection->cursor_item,
2119 FALSE);
2122 /* Move the cursor by the given row and column offsets. */
2123 void collection_move_cursor(Collection *collection, int drow, int dcol)
2125 int row, col, item;
2126 int first, last, total_rows;
2128 g_return_if_fail(collection != NULL);
2129 g_return_if_fail(IS_COLLECTION(collection));
2131 get_visible_limits(collection, &first, &last);
2133 item = collection->cursor_item;
2134 if (item == -1)
2136 col = 0;
2137 row = first;
2139 else
2141 row = item / collection->columns;
2142 col = item % collection->columns + dcol;
2144 if (row < first)
2145 row = first;
2146 else if (row > last)
2147 row = last;
2148 else
2149 row = MAX(row + drow, 0);
2152 total_rows = (collection->number_of_items + collection->columns - 1)
2153 / collection->columns;
2155 if (row >= total_rows - 1 && drow > 0)
2157 row = total_rows - 1;
2158 item = col + row * collection->columns;
2159 if (item >= collection->number_of_items)
2161 row--;
2162 scroll_to_show(collection, item);
2165 if (row < 0)
2166 row = 0;
2168 item = col + row * collection->columns;
2170 if (item >= 0 && item < collection->number_of_items)
2171 collection_set_cursor_item(collection, item);