r291: Improved theme support (pixmap backgrounds work now).
[rox-filer/ma.git] / ROX-Filer / src / collection.c
blob398764798d9a2a237f8a17aa49e3a582ae823873
1 /*
2 * $Id$
4 * Collection - a GTK+ widget
5 * Copyright (C) 2000, 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_style(GtkWidget *widget,
102 GtkStyle *previous_style);
103 static void collection_set_adjustment(Collection *collection,
104 GtkAdjustment *vadj);
105 static void collection_set_arg( GtkObject *object,
106 GtkArg *arg,
107 guint arg_id);
108 static void collection_get_arg( GtkObject *object,
109 GtkArg *arg,
110 guint arg_id);
111 static void collection_adjustment(GtkAdjustment *adjustment,
112 Collection *collection);
113 static void collection_disconnect(GtkAdjustment *adjustment,
114 Collection *collection);
115 static void set_vadjustment(Collection *collection);
116 static void collection_draw(GtkWidget *widget, GdkRectangle *area);
117 static gint collection_expose(GtkWidget *widget, GdkEventExpose *event);
118 static void scroll_by(Collection *collection, gint diff);
119 static gint collection_button_press(GtkWidget *widget,
120 GdkEventButton *event);
121 static gint collection_button_release(GtkWidget *widget,
122 GdkEventButton *event);
123 static void default_draw_item(GtkWidget *widget,
124 CollectionItem *data,
125 GdkRectangle *area);
126 static gboolean default_test_point(Collection *collection,
127 int point_x, int point_y,
128 CollectionItem *data,
129 int width, int height);
130 static gint collection_motion_notify(GtkWidget *widget,
131 GdkEventMotion *event);
132 static void add_lasso_box(Collection *collection);
133 static void remove_lasso_box(Collection *collection);
134 static void draw_lasso_box(Collection *collection);
135 static int item_at_row_col(Collection *collection, int row, int col);
136 static void collection_clear_except(Collection *collection, gint exception);
137 static void cancel_wink(Collection *collection);
138 static gint collection_key_press(GtkWidget *widget, GdkEventKey *event);
139 static void get_visible_limits(Collection *collection, int *first, int *last);
140 static void scroll_to_show(Collection *collection, int item);
141 static gint focus_in(GtkWidget *widget, GdkEventFocus *event);
142 static gint focus_out(GtkWidget *widget, GdkEventFocus *event);
143 static void draw_focus(GtkWidget *widget);
145 static void draw_focus_at(Collection *collection, GdkRectangle *area)
147 GtkWidget *widget;
148 GdkGC *gc;
150 widget = GTK_WIDGET(collection);
152 if (collection->target_cb)
153 gc = widget->style->white_gc;
154 else if (GTK_WIDGET_FLAGS(widget) & GTK_HAS_FOCUS)
155 gc = widget->style->black_gc;
156 else
157 gc = widget->style->fg_gc[GTK_STATE_INSENSITIVE];
159 gdk_draw_rectangle(widget->window, gc, FALSE,
160 area->x + 1, area->y + 1,
161 area->width - 3,
162 area->height - 3);
165 static void draw_one_item(Collection *collection, int item, GdkRectangle *area)
167 if (item < collection->number_of_items)
169 collection->draw_item((GtkWidget *) collection,
170 &collection->items[item],
171 area);
172 if (item == collection->wink_item)
173 gdk_draw_rectangle(((GtkWidget *) collection)->window,
174 ((GtkWidget *) collection)->style->black_gc,
175 FALSE,
176 area->x, area->y,
177 area->width - 1, area->height - 1);
180 if (item == collection->cursor_item)
181 draw_focus_at(collection, area);
184 static void draw_focus(GtkWidget *widget)
186 Collection *collection;
188 g_return_if_fail(widget != NULL);
189 g_return_if_fail(IS_COLLECTION(widget));
191 collection = COLLECTION(widget);
193 if (collection->cursor_item < 0 || !GTK_WIDGET_REALIZED(widget))
194 return;
196 collection_draw_item(collection, collection->cursor_item, FALSE);
199 GtkType collection_get_type(void)
201 static guint my_type = 0;
203 if (!my_type)
205 static const GtkTypeInfo my_info =
207 "Collection",
208 sizeof(Collection),
209 sizeof(CollectionClass),
210 (GtkClassInitFunc) collection_class_init,
211 (GtkObjectInitFunc) collection_init,
212 NULL, /* Reserved 1 */
213 NULL, /* Reserved 2 */
214 (GtkClassInitFunc) NULL /* base_class_init_func */
217 my_type = gtk_type_unique(gtk_widget_get_type(),
218 &my_info);
221 return my_type;
224 static void collection_class_init(CollectionClass *class)
226 GtkObjectClass *object_class;
227 GtkWidgetClass *widget_class;
229 object_class = (GtkObjectClass*) class;
230 widget_class = (GtkWidgetClass*) class;
232 parent_class = gtk_type_class(gtk_widget_get_type());
234 gtk_object_add_arg_type("Collection::vadjustment",
235 GTK_TYPE_ADJUSTMENT,
236 GTK_ARG_READWRITE | GTK_ARG_CONSTRUCT,
237 ARG_VADJUSTMENT);
239 object_class->destroy = collection_destroy;
240 object_class->finalize = collection_finalize;
242 widget_class->realize = collection_realize;
243 widget_class->draw = collection_draw;
244 widget_class->expose_event = collection_expose;
245 widget_class->size_request = collection_size_request;
246 widget_class->size_allocate = collection_size_allocate;
247 widget_class->style_set = collection_set_style;
249 widget_class->key_press_event = collection_key_press;
250 widget_class->button_press_event = collection_button_press;
251 widget_class->button_release_event = collection_button_release;
252 widget_class->motion_notify_event = collection_motion_notify;
253 widget_class->focus_in_event = focus_in;
254 widget_class->focus_out_event = focus_out;
255 widget_class->draw_focus = draw_focus;
257 object_class->set_arg = collection_set_arg;
258 object_class->get_arg = collection_get_arg;
260 class->open_item = NULL;
261 class->drag_selection = NULL;
262 class->show_menu = NULL;
263 class->gain_selection = NULL;
264 class->lose_selection = NULL;
266 collection_signals[OPEN_ITEM] = gtk_signal_new("open_item",
267 GTK_RUN_LAST,
268 object_class->type,
269 GTK_SIGNAL_OFFSET(CollectionClass,
270 open_item),
271 gtk_marshal_NONE__POINTER_UINT,
272 GTK_TYPE_NONE, 2,
273 GTK_TYPE_POINTER,
274 GTK_TYPE_UINT);
275 collection_signals[DRAG_SELECTION] = gtk_signal_new("drag_selection",
276 GTK_RUN_LAST,
277 object_class->type,
278 GTK_SIGNAL_OFFSET(CollectionClass,
279 drag_selection),
280 gtk_marshal_NONE__POINTER_UINT,
281 GTK_TYPE_NONE, 2,
282 GTK_TYPE_POINTER,
283 GTK_TYPE_UINT);
284 collection_signals[SHOW_MENU] = gtk_signal_new("show_menu",
285 GTK_RUN_LAST,
286 object_class->type,
287 GTK_SIGNAL_OFFSET(CollectionClass,
288 show_menu),
289 gtk_marshal_NONE__POINTER_INT,
290 GTK_TYPE_NONE, 2,
291 GTK_TYPE_POINTER,
292 GTK_TYPE_INT);
293 collection_signals[GAIN_SELECTION] = gtk_signal_new("gain_selection",
294 GTK_RUN_LAST,
295 object_class->type,
296 GTK_SIGNAL_OFFSET(CollectionClass,
297 gain_selection),
298 gtk_marshal_NONE__UINT,
299 GTK_TYPE_NONE, 1,
300 GTK_TYPE_UINT);
301 collection_signals[LOSE_SELECTION] = gtk_signal_new("lose_selection",
302 GTK_RUN_LAST,
303 object_class->type,
304 GTK_SIGNAL_OFFSET(CollectionClass,
305 lose_selection),
306 gtk_marshal_NONE__UINT,
307 GTK_TYPE_NONE, 1,
308 GTK_TYPE_UINT);
310 gtk_object_class_add_signals(object_class,
311 collection_signals, LAST_SIGNAL);
314 static void collection_init(Collection *object)
316 g_return_if_fail(object != NULL);
317 g_return_if_fail(IS_COLLECTION(object));
319 if (!crosshair)
320 crosshair = gdk_cursor_new(GDK_CROSSHAIR);
322 GTK_WIDGET_SET_FLAGS(GTK_WIDGET(object), GTK_CAN_FOCUS);
324 object->panel = FALSE;
325 object->number_of_items = 0;
326 object->number_selected = 0;
327 object->columns = 1;
328 object->item_width = 64;
329 object->item_height = 64;
330 object->vadj = NULL;
331 object->paint_level = PAINT_OVERWRITE;
332 object->last_scroll = 0;
333 object->bg_gc = NULL;
335 object->items = g_malloc(sizeof(CollectionItem) * MINIMUM_ITEMS);
336 object->cursor_item = -1;
337 object->cursor_item_old = -1;
338 object->wink_item = -1;
339 object->array_size = MINIMUM_ITEMS;
340 object->draw_item = default_draw_item;
341 object->test_point = default_test_point;
343 object->buttons_pressed = 0;
344 object->may_drag = FALSE;
346 return;
349 GtkWidget* collection_new(GtkAdjustment *vadj)
351 if (vadj)
352 g_return_val_if_fail(GTK_IS_ADJUSTMENT(vadj), NULL);
354 return GTK_WIDGET(gtk_widget_new(collection_get_type(),
355 "vadjustment", vadj,
356 NULL));
359 void collection_set_functions(Collection *collection,
360 CollectionDrawFunc draw_item,
361 CollectionTestFunc test_point)
363 GtkWidget *widget;
365 g_return_if_fail(collection != NULL);
366 g_return_if_fail(IS_COLLECTION(collection));
368 widget = GTK_WIDGET(collection);
370 if (!draw_item)
371 draw_item = default_draw_item;
372 if (!test_point)
373 test_point = default_test_point;
375 collection->draw_item = draw_item;
376 collection->test_point = test_point;
378 if (GTK_WIDGET_REALIZED(widget))
380 collection->paint_level = PAINT_CLEAR;
381 gtk_widget_queue_clear(widget);
385 /* After this we are unusable, but our data (if any) is still hanging around.
386 * It will be freed later with finalize.
388 static void collection_destroy(GtkObject *object)
390 Collection *collection;
392 g_return_if_fail(object != NULL);
393 g_return_if_fail(IS_COLLECTION(object));
395 collection = COLLECTION(object);
397 if (collection->wink_item != -1)
399 collection->wink_item = -1;
400 gtk_timeout_remove(collection->wink_timeout);
403 gtk_signal_disconnect_by_data(GTK_OBJECT(collection->vadj),
404 collection);
406 if (collection->bg_gc)
408 gdk_gc_destroy(collection->bg_gc);
409 collection->bg_gc = NULL;
412 if (GTK_OBJECT_CLASS(parent_class)->destroy)
413 (*GTK_OBJECT_CLASS(parent_class)->destroy)(object);
416 /* This is the last thing that happens to us. Free all data. */
417 static void collection_finalize(GtkObject *object)
419 Collection *collection;
421 collection = COLLECTION(object);
423 if (collection->vadj)
425 gtk_object_unref(GTK_OBJECT(collection->vadj));
428 g_free(collection->items);
431 static GdkGC *create_bg_gc(GtkWidget *widget)
433 GdkGCValues values;
435 values.tile = widget->style->bg_pixmap[GTK_STATE_INSENSITIVE];
436 values.fill = GDK_TILED;
438 return gdk_gc_new_with_values(widget->window, &values,
439 GDK_GC_FILL | GDK_GC_TILE);
442 static void collection_realize(GtkWidget *widget)
444 Collection *collection;
445 GdkWindowAttr attributes;
446 gint attributes_mask;
447 GdkGCValues xor_values;
449 g_return_if_fail(widget != NULL);
450 g_return_if_fail(IS_COLLECTION(widget));
451 g_return_if_fail(widget->parent != NULL);
453 GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);
454 collection = COLLECTION(widget);
456 attributes.x = widget->allocation.x;
457 attributes.y = widget->allocation.y;
458 attributes.width = widget->allocation.width;
459 attributes.height = widget->allocation.height;
460 attributes.wclass = GDK_INPUT_OUTPUT;
461 attributes.window_type = GDK_WINDOW_CHILD;
462 attributes.event_mask = gtk_widget_get_events(widget) |
463 GDK_EXPOSURE_MASK |
464 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
465 GDK_BUTTON1_MOTION_MASK | GDK_BUTTON2_MOTION_MASK |
466 GDK_BUTTON3_MOTION_MASK;
467 attributes.visual = gtk_widget_get_visual(widget);
468 attributes.colormap = gtk_widget_get_colormap(widget);
470 attributes_mask = GDK_WA_X | GDK_WA_Y |
471 GDK_WA_VISUAL | GDK_WA_COLORMAP;
472 widget->window = gdk_window_new(widget->parent->window,
473 &attributes, attributes_mask);
475 widget->style = gtk_style_attach(widget->style, widget->window);
477 gdk_window_set_user_data(widget->window, widget);
479 gdk_window_set_background(widget->window,
480 &widget->style->base[GTK_STATE_INSENSITIVE]);
481 if (widget->style->bg_pixmap[GTK_STATE_INSENSITIVE])
482 collection->bg_gc = create_bg_gc(widget);
484 /* Try to stop everything flickering horribly */
485 gdk_window_set_static_gravities(widget->window, TRUE);
487 set_vadjustment(collection);
489 xor_values.function = GDK_XOR;
490 xor_values.foreground.red = 0xffff;
491 xor_values.foreground.green = 0xffff;
492 xor_values.foreground.blue = 0xffff;
493 gdk_color_alloc(gtk_widget_get_colormap(widget),
494 &xor_values.foreground);
495 collection->xor_gc = gdk_gc_new_with_values(widget->window,
496 &xor_values,
497 GDK_GC_FOREGROUND
498 | GDK_GC_FUNCTION);
501 static void collection_size_request(GtkWidget *widget,
502 GtkRequisition *requisition)
504 requisition->width = MIN_WIDTH;
505 requisition->height = MIN_HEIGHT;
508 static void collection_size_allocate(GtkWidget *widget,
509 GtkAllocation *allocation)
511 Collection *collection;
512 int old_columns;
514 g_return_if_fail(widget != NULL);
515 g_return_if_fail(IS_COLLECTION(widget));
516 g_return_if_fail(allocation != NULL);
518 collection = COLLECTION(widget);
520 old_columns = collection->columns;
521 if (widget->allocation.x != allocation->x
522 || widget->allocation.y != allocation->y)
523 collection->paint_level = PAINT_CLEAR;
525 widget->allocation = *allocation;
527 collection->columns = allocation->width / collection->item_width;
528 if (collection->columns < 1)
529 collection->columns = 1;
531 if (GTK_WIDGET_REALIZED(widget))
533 gdk_window_move_resize(widget->window,
534 allocation->x, allocation->y,
535 allocation->width, allocation->height);
537 if (old_columns != collection->columns)
539 collection->paint_level = PAINT_CLEAR;
540 gtk_widget_queue_clear(widget);
543 set_vadjustment(collection);
545 if (collection->cursor_item != -1)
546 scroll_to_show(collection, collection->cursor_item);
550 static void collection_set_style(GtkWidget *widget,
551 GtkStyle *previous_style)
553 Collection *collection;
555 g_return_if_fail(IS_COLLECTION(widget));
557 collection = COLLECTION(widget);
559 collection->paint_level = PAINT_CLEAR;
561 if (GTK_WIDGET_REALIZED(widget))
563 gdk_window_set_background(widget->window,
564 &widget->style->base[GTK_STATE_INSENSITIVE]);
566 if (collection->bg_gc)
568 gdk_gc_destroy(collection->bg_gc);
569 collection->bg_gc = NULL;
572 if (widget->style->bg_pixmap[GTK_STATE_INSENSITIVE])
573 collection->bg_gc = create_bg_gc(widget);
577 static void clear_area(Collection *collection, GdkRectangle *area)
579 GtkWidget *widget = GTK_WIDGET(collection);
580 int scroll = collection->vadj->value;
582 if (collection->bg_gc)
584 gdk_gc_set_ts_origin(collection->bg_gc, 0, -scroll);
586 gdk_draw_rectangle(widget->window,
587 collection->bg_gc,
588 TRUE,
589 area->x, area->y,
590 area->width, area->height);
592 else
593 gdk_window_clear_area(widget->window,
594 area->x, area->y, area->width, area->height);
597 static gint collection_paint(Collection *collection,
598 GdkRectangle *area)
600 GdkRectangle whole, item_area;
601 GtkWidget *widget;
602 int row, col;
603 int item;
604 int scroll;
605 int start_row, last_row;
606 int start_col, last_col;
607 int phys_last_col;
608 GdkRectangle clip;
610 scroll = collection->vadj->value;
612 widget = GTK_WIDGET(collection);
614 if (collection->paint_level > PAINT_NORMAL || area == NULL)
616 guint width, height;
617 gdk_window_get_size(widget->window, &width, &height);
619 whole.x = 0;
620 whole.y = 0;
621 whole.width = width;
622 whole.height = height;
624 area = &whole;
626 if (collection->paint_level == PAINT_CLEAR
627 && !collection->lasso_box)
628 clear_area(collection, area);
630 collection->paint_level = PAINT_NORMAL;
633 /* Calculate the ranges to plot */
634 start_row = (area->y + scroll) / collection->item_height;
635 last_row = (area->y + area->height - 1 + scroll)
636 / collection->item_height;
637 row = start_row;
639 start_col = area->x / collection->item_width;
640 phys_last_col = (area->x + area->width - 1) / collection->item_width;
642 if (collection->lasso_box)
644 /* You can't be too careful with lasso boxes...
646 * clip gives the total area drawn over (this may be larger
647 * than the requested area). It's used to redraw the lasso
648 * box.
650 clip.x = start_col * collection->item_width;
651 clip.y = start_row * collection->item_height - scroll;
652 clip.width = (phys_last_col - start_col + 1)
653 * collection->item_width;
654 clip.height = (last_row - start_row + 1)
655 * collection->item_height;
657 clear_area(collection, &clip);
660 if (start_col < collection->columns)
662 if (phys_last_col >= collection->columns)
663 last_col = collection->columns - 1;
664 else
665 last_col = phys_last_col;
667 col = start_col;
669 item = row * collection->columns + col;
670 item_area.width = collection->item_width;
671 item_area.height = collection->item_height;
673 while ((item == 0 || item < collection->number_of_items)
674 && row <= last_row)
676 item_area.x = col * collection->item_width;
677 item_area.y = row * collection->item_height - scroll;
679 draw_one_item(collection, item, &item_area);
680 col++;
682 if (col > last_col)
684 col = start_col;
685 row++;
686 item = row * collection->columns + col;
688 else
689 item++;
693 if (collection->lasso_box)
695 gdk_gc_set_clip_rectangle(collection->xor_gc, &clip);
696 draw_lasso_box(collection);
697 gdk_gc_set_clip_rectangle(collection->xor_gc, NULL);
700 return FALSE;
703 static void default_draw_item( GtkWidget *widget,
704 CollectionItem *item,
705 GdkRectangle *area)
707 gdk_draw_arc(widget->window,
708 item->selected ? widget->style->white_gc
709 : widget->style->black_gc,
710 TRUE,
711 area->x, area->y,
712 area->width, area->height,
713 0, 360 * 64);
717 static gboolean default_test_point(Collection *collection,
718 int point_x, int point_y,
719 CollectionItem *item,
720 int width, int height)
722 float f_x, f_y;
724 /* Convert to point in unit circle */
725 f_x = ((float) point_x / width) - 0.5;
726 f_y = ((float) point_y / height) - 0.5;
728 return (f_x * f_x) + (f_y * f_y) <= .25;
731 static void collection_set_arg( GtkObject *object,
732 GtkArg *arg,
733 guint arg_id)
735 Collection *collection;
737 collection = COLLECTION(object);
739 switch (arg_id)
741 case ARG_VADJUSTMENT:
742 collection_set_adjustment(collection,
743 GTK_VALUE_POINTER(*arg));
744 break;
745 default:
746 break;
750 static void collection_set_adjustment( Collection *collection,
751 GtkAdjustment *vadj)
753 if (vadj)
754 g_return_if_fail (GTK_IS_ADJUSTMENT (vadj));
755 else
756 vadj = GTK_ADJUSTMENT(gtk_adjustment_new(0.0,
757 0.0, 0.0,
758 0.0, 0.0, 0.0));
759 if (collection->vadj && (collection->vadj != vadj))
761 gtk_signal_disconnect_by_data(GTK_OBJECT(collection->vadj),
762 collection);
763 gtk_object_unref(GTK_OBJECT(collection->vadj));
766 if (collection->vadj != vadj)
768 collection->vadj = vadj;
769 gtk_object_ref(GTK_OBJECT(collection->vadj));
770 gtk_object_sink(GTK_OBJECT(collection->vadj));
772 gtk_signal_connect(GTK_OBJECT(collection->vadj),
773 "changed",
774 (GtkSignalFunc) collection_adjustment,
775 collection);
776 gtk_signal_connect(GTK_OBJECT(collection->vadj),
777 "value_changed",
778 (GtkSignalFunc) collection_adjustment,
779 collection);
780 gtk_signal_connect(GTK_OBJECT(collection->vadj),
781 "disconnect",
782 (GtkSignalFunc) collection_disconnect,
783 collection);
784 collection_adjustment(vadj, collection);
788 static void collection_get_arg( GtkObject *object,
789 GtkArg *arg,
790 guint arg_id)
792 Collection *collection;
794 collection = COLLECTION(object);
796 switch (arg_id)
798 case ARG_VADJUSTMENT:
799 GTK_VALUE_POINTER(*arg) = collection->vadj;
800 break;
801 default:
802 arg->type = GTK_TYPE_INVALID;
803 break;
807 /* Something about the adjustment has changed */
808 static void collection_adjustment(GtkAdjustment *adjustment,
809 Collection *collection)
811 gint diff;
813 g_return_if_fail(adjustment != NULL);
814 g_return_if_fail(GTK_IS_ADJUSTMENT(adjustment));
815 g_return_if_fail(collection != NULL);
816 g_return_if_fail(IS_COLLECTION(collection));
818 diff = ((gint) adjustment->value) - collection->last_scroll;
820 if (diff)
822 collection->last_scroll = adjustment->value;
824 scroll_by(collection, diff);
828 static void collection_disconnect(GtkAdjustment *adjustment,
829 Collection *collection)
831 g_return_if_fail(adjustment != NULL);
832 g_return_if_fail(GTK_IS_ADJUSTMENT(adjustment));
833 g_return_if_fail(collection != NULL);
834 g_return_if_fail(IS_COLLECTION(collection));
836 collection_set_adjustment(collection, NULL);
839 static void set_vadjustment(Collection *collection)
841 GtkWidget *widget;
842 guint height;
843 int cols, rows;
845 widget = GTK_WIDGET(collection);
847 if (!GTK_WIDGET_REALIZED(widget))
848 return;
850 gdk_window_get_size(widget->window, NULL, &height);
851 cols = collection->columns;
852 rows = (collection->number_of_items + cols - 1) / cols;
854 collection->vadj->lower = 0.0;
855 collection->vadj->upper = collection->item_height * rows;
857 collection->vadj->step_increment =
858 MIN(collection->vadj->upper, collection->item_height / 4);
860 collection->vadj->page_increment =
861 MIN(collection->vadj->upper,
862 height - 5.0);
864 collection->vadj->page_size = MIN(collection->vadj->upper, height);
866 collection->vadj->value = MIN(collection->vadj->value,
867 collection->vadj->upper - collection->vadj->page_size);
869 collection->vadj->value = MAX(collection->vadj->value, 0.0);
871 gtk_signal_emit_by_name(GTK_OBJECT(collection->vadj), "changed");
874 static void collection_draw(GtkWidget *widget, GdkRectangle *area)
876 Collection *collection;
878 g_return_if_fail(widget != NULL);
879 g_return_if_fail(IS_COLLECTION(widget));
880 g_return_if_fail(area != NULL); /* Not actually used */
882 collection = COLLECTION(widget);
884 /* This doesn't always work - I think Gtk+ may be doing some
885 * kind of expose-event compression...
886 if (collection->paint_level > PAINT_NORMAL)
888 collection_paint(collection, area);
891 static gint collection_expose(GtkWidget *widget, GdkEventExpose *event)
893 Collection *collection;
895 g_return_val_if_fail(widget != NULL, FALSE);
896 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
897 g_return_val_if_fail(event != NULL, FALSE);
899 collection = COLLECTION(widget);
901 clear_area(collection, &event->area);
903 collection_paint(collection, &event->area);
905 return FALSE;
908 /* Positive makes the contents go move up the screen */
909 static void scroll_by(Collection *collection, gint diff)
911 GtkWidget *widget;
912 guint width, height;
913 guint from_y, to_y;
914 guint amount;
915 GdkRectangle new_area;
917 if (diff == 0)
918 return;
920 widget = GTK_WIDGET(collection);
922 if (collection->lasso_box)
923 remove_lasso_box(collection);
925 gdk_window_get_size(widget->window, &width, &height);
926 new_area.x = 0;
927 new_area.width = width;
929 if (diff > 0)
931 amount = diff;
932 from_y = amount;
933 to_y = 0;
934 new_area.y = height - amount;
936 else
938 amount = -diff;
939 from_y = 0;
940 to_y = amount;
941 new_area.y = 0;
944 new_area.height = amount;
946 if (amount < height)
948 gdk_draw_pixmap(widget->window,
949 widget->style->white_gc,
950 widget->window,
952 from_y,
954 to_y,
955 width,
956 height - amount);
957 /* We have to redraw everything because any pending
958 * expose events now contain invalid areas.
959 * Don't need to clear the area first though...
961 if (collection->paint_level < PAINT_OVERWRITE)
962 collection->paint_level = PAINT_OVERWRITE;
964 else
965 collection->paint_level = PAINT_CLEAR;
967 clear_area(collection, &new_area);
968 collection_paint(collection, NULL);
971 static void resize_arrays(Collection *collection, guint new_size)
973 g_return_if_fail(collection != NULL);
974 g_return_if_fail(IS_COLLECTION(collection));
975 g_return_if_fail(new_size >= collection->number_of_items);
977 collection->items = g_realloc(collection->items,
978 sizeof(CollectionItem) * new_size);
979 collection->array_size = new_size;
982 static void return_pressed(Collection *collection)
984 int item = collection->cursor_item;
985 CollectionTargetFunc cb = collection->target_cb;
986 gpointer data = collection->target_data;
988 collection_target(collection, NULL, NULL);
989 if (item < 0 || item >= collection->number_of_items)
990 return;
992 if (cb)
994 cb(collection, item, data);
995 return;
998 gtk_signal_emit(GTK_OBJECT(collection),
999 collection_signals[OPEN_ITEM],
1000 collection->items[item].data,
1001 item);
1004 static gint collection_key_press(GtkWidget *widget, GdkEventKey *event)
1006 Collection *collection;
1007 int item;
1009 g_return_val_if_fail(widget != NULL, FALSE);
1010 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1011 g_return_val_if_fail(event != NULL, FALSE);
1013 collection = (Collection *) widget;
1014 item = collection->cursor_item;
1016 switch (event->keyval)
1018 case GDK_Left:
1019 collection_move_cursor(collection, 0, -1);
1020 break;
1021 case GDK_Right:
1022 collection_move_cursor(collection, 0, 1);
1023 break;
1024 case GDK_Up:
1025 collection_move_cursor(collection, -1, 0);
1026 break;
1027 case GDK_Down:
1028 collection_move_cursor(collection, 1, 0);
1029 break;
1030 case GDK_Home:
1031 collection_set_cursor_item(collection, 0);
1032 break;
1033 case GDK_End:
1034 collection_set_cursor_item(collection,
1035 MAX((gint) collection->number_of_items - 1, 0));
1036 break;
1037 case GDK_Page_Up:
1038 collection_move_cursor(collection, -10, 0);
1039 break;
1040 case GDK_Page_Down:
1041 collection_move_cursor(collection, 10, 0);
1042 break;
1043 case GDK_Return:
1044 return_pressed(collection);
1045 break;
1046 case GDK_Escape:
1047 if (!collection->target_cb)
1049 collection_set_cursor_item(collection, -1);
1050 collection_clear_selection(collection);
1051 return FALSE; /* Pass it on */
1053 collection_target(collection, NULL, NULL);
1054 break;
1055 case ' ':
1056 if (item >=0 && item < collection->number_of_items)
1057 collection_toggle_item(collection, item);
1058 break;
1059 default:
1060 return FALSE;
1063 return TRUE;
1066 static gint collection_button_press(GtkWidget *widget,
1067 GdkEventButton *event)
1069 Collection *collection;
1070 int row, col;
1071 int item;
1072 int action;
1073 int scroll;
1074 guint stacked_time;
1076 g_return_val_if_fail(widget != NULL, FALSE);
1077 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1078 g_return_val_if_fail(event != NULL, FALSE);
1080 collection = COLLECTION(widget);
1082 collection->item_clicked = -1;
1084 if (event->button > 3)
1086 int diff;
1088 /* Wheel mouse scrolling */
1089 if (event->button == 4)
1090 diff = -((signed int) collection->item_height) / 4;
1091 else if (event->button == 5)
1092 diff = collection->item_height / 4;
1093 else
1094 diff = 0;
1096 if (diff)
1098 int old_value = collection->vadj->value;
1099 int new_value = 0;
1100 gboolean box = collection->lasso_box;
1102 new_value = CLAMP(old_value + diff, 0.0,
1103 collection->vadj->upper
1104 - collection->vadj->page_size);
1105 diff = new_value - old_value;
1106 if (diff)
1108 if (box)
1110 remove_lasso_box(collection);
1111 collection->drag_box_y[0] -= diff;
1113 collection->vadj->value = new_value;
1114 gtk_signal_emit_by_name(
1115 GTK_OBJECT(collection->vadj),
1116 "changed");
1117 if (box)
1118 add_lasso_box(collection);
1121 return FALSE;
1124 if (collection->cursor_item != -1)
1125 collection_set_cursor_item(collection, -1);
1127 scroll = collection->vadj->value;
1129 if (event->type == GDK_BUTTON_PRESS &&
1130 event->button != collection_menu_button)
1132 if (collection->buttons_pressed++ == 0)
1133 gtk_grab_add(widget);
1134 else
1135 return FALSE; /* Ignore extra presses */
1138 if (event->state & GDK_CONTROL_MASK)
1139 action = 2;
1140 else
1141 action = event->button;
1143 /* Ignore all clicks while we are dragging a lasso box */
1144 if (collection->lasso_box)
1145 return TRUE;
1147 col = event->x / collection->item_width;
1148 row = (event->y + scroll) / collection->item_height;
1150 if (col < 0 || row < 0 || col >= collection->columns)
1151 item = -1;
1152 else
1154 item = col + row * collection->columns;
1155 if (item >= collection->number_of_items
1157 !collection->test_point(collection,
1158 event->x - col * collection->item_width,
1159 event->y - row * collection->item_height
1160 + scroll,
1161 &collection->items[item],
1162 collection->item_width,
1163 collection->item_height))
1165 item = -1;
1169 if (collection->target_cb)
1171 CollectionTargetFunc cb = collection->target_cb;
1172 gpointer data = collection->target_data;
1174 collection_target(collection, NULL, NULL);
1175 if (collection->buttons_pressed)
1177 gtk_grab_remove(widget);
1178 collection->buttons_pressed = 0;
1180 if (item > -1 && event->button != collection_menu_button)
1181 cb(collection, item, data);
1182 return TRUE;
1185 collection->drag_box_x[0] = event->x;
1186 collection->drag_box_y[0] = event->y;
1187 collection->item_clicked = item;
1189 stacked_time = current_event_time;
1190 current_event_time = event->time;
1192 if (event->button == collection_menu_button)
1194 gtk_signal_emit(GTK_OBJECT(collection),
1195 collection_signals[SHOW_MENU],
1196 event,
1197 item);
1199 else if (event->type == GDK_2BUTTON_PRESS && collection->panel)
1201 /* Do nothing */
1203 else if ((event->type == GDK_2BUTTON_PRESS && !collection_single_click)
1204 || collection->panel)
1206 if (item >= 0)
1208 if (collection->buttons_pressed)
1210 gtk_grab_remove(widget);
1211 collection->buttons_pressed = 0;
1213 collection_unselect_item(collection, item);
1214 gtk_signal_emit(GTK_OBJECT(collection),
1215 collection_signals[OPEN_ITEM],
1216 collection->items[item].data,
1217 item);
1220 else if (event->type == GDK_BUTTON_PRESS)
1222 collection->may_drag = event->button != collection_menu_button;
1224 if (item >= 0)
1226 if (action == 1)
1228 if (!collection->items[item].selected)
1230 collection_select_item(collection,
1231 item);
1232 collection_clear_except(collection,
1233 item);
1236 else
1237 collection_toggle_item(collection, item);
1239 else if (action == 1)
1240 collection_clear_selection(collection);
1243 current_event_time = stacked_time;
1244 return FALSE;
1247 static gint collection_button_release(GtkWidget *widget,
1248 GdkEventButton *event)
1250 Collection *collection;
1251 int top, bottom;
1252 int row, last_row;
1253 int w, h;
1254 int col, start_col, last_col;
1255 int scroll;
1256 int item;
1257 guint stacked_time;
1258 int button;
1260 g_return_val_if_fail(widget != NULL, FALSE);
1261 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1262 g_return_val_if_fail(event != NULL, FALSE);
1264 collection = COLLECTION(widget);
1265 button = event->button;
1267 scroll = collection->vadj->value;
1269 if (event->button > 3 || event->button == collection_menu_button)
1270 return FALSE;
1271 if (collection->buttons_pressed == 0)
1272 return FALSE;
1273 if (--collection->buttons_pressed == 0)
1274 gtk_grab_remove(widget);
1275 else
1276 return FALSE; /* Wait until ALL buttons are up */
1278 if (!collection->lasso_box)
1280 int item = collection->item_clicked;
1282 if (collection_single_click && item > -1
1283 && item < collection->number_of_items
1284 && (event->state & GDK_CONTROL_MASK) == 0)
1286 int dx = event->x - collection->drag_box_x[0];
1287 int dy = event->y - collection->drag_box_y[0];
1289 if (ABS(dx) + ABS(dy) > 9)
1290 return FALSE;
1292 collection_unselect_item(collection, item);
1293 gtk_signal_emit(GTK_OBJECT(collection),
1294 collection_signals[OPEN_ITEM],
1295 collection->items[item].data,
1296 item);
1299 return FALSE;
1302 remove_lasso_box(collection);
1304 w = collection->item_width;
1305 h = collection->item_height;
1307 top = collection->drag_box_y[0] + scroll;
1308 bottom = collection->drag_box_y[1] + scroll;
1309 if (top > bottom)
1311 int tmp;
1312 tmp = top;
1313 top = bottom;
1314 bottom = tmp;
1316 top += h / 4;
1317 bottom -= h / 4;
1319 row = MAX(top / h, 0);
1320 last_row = bottom / h;
1322 top = collection->drag_box_x[0]; /* (left) */
1323 bottom = collection->drag_box_x[1];
1324 if (top > bottom)
1326 int tmp;
1327 tmp = top;
1328 top = bottom;
1329 bottom = tmp;
1331 top += w / 4;
1332 bottom -= w / 4;
1333 start_col = MAX(top / w, 0);
1334 last_col = bottom / w;
1335 if (last_col >= collection->columns)
1336 last_col = collection->columns - 1;
1338 stacked_time = current_event_time;
1339 current_event_time = event->time;
1341 while (row <= last_row)
1343 col = start_col;
1344 item = row * collection->columns + col;
1345 while (col <= last_col)
1347 if (item >= collection->number_of_items)
1349 current_event_time = stacked_time;
1350 return FALSE;
1353 if (button == 1)
1354 collection_select_item(collection, item);
1355 else
1356 collection_toggle_item(collection, item);
1357 col++;
1358 item++;
1360 row++;
1363 current_event_time = stacked_time;
1365 return FALSE;
1368 static gint collection_motion_notify(GtkWidget *widget,
1369 GdkEventMotion *event)
1371 Collection *collection;
1372 int x, y;
1373 guint stacked_time;
1375 g_return_val_if_fail(widget != NULL, FALSE);
1376 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1377 g_return_val_if_fail(event != NULL, FALSE);
1379 collection = COLLECTION(widget);
1381 if (collection->buttons_pressed == 0)
1382 return FALSE;
1384 stacked_time = current_event_time;
1385 current_event_time = event->time;
1387 if (event->window != widget->window)
1388 gdk_window_get_pointer(widget->window, &x, &y, NULL);
1389 else
1391 x = event->x;
1392 y = event->y;
1395 if (collection->lasso_box)
1397 int new_value = 0, diff;
1398 int height;
1400 gdk_window_get_size(widget->window, NULL, &height);
1402 if (y < 0)
1404 int old_value = collection->vadj->value;
1406 new_value = MAX(old_value + y / 10, 0.0);
1407 diff = new_value - old_value;
1409 else if (y > height)
1411 int old_value = collection->vadj->value;
1413 new_value = MIN(old_value + (y - height) / 10,
1414 collection->vadj->upper
1415 - collection->vadj->page_size);
1416 diff = new_value - old_value;
1418 else
1419 diff = 0;
1421 remove_lasso_box(collection);
1422 collection->drag_box_x[1] = x;
1423 collection->drag_box_y[1] = y;
1425 if (diff)
1427 collection->drag_box_y[0] -= diff;
1428 collection->vadj->value = new_value;
1429 gtk_signal_emit_by_name(GTK_OBJECT(collection->vadj),
1430 "changed");
1432 add_lasso_box(collection);
1434 else if (collection->may_drag)
1436 int dx = x - collection->drag_box_x[0];
1437 int dy = y - collection->drag_box_y[0];
1439 if (abs(dx) > 9 || abs(dy) > 9)
1441 int row, col, item;
1442 int scroll = collection->vadj->value;
1444 collection->may_drag = FALSE;
1446 col = collection->drag_box_x[0]
1447 / collection->item_width;
1448 row = (collection->drag_box_y[0] + scroll)
1449 / collection->item_height;
1450 item = item_at_row_col(collection, row, col);
1452 if (item != -1 && collection->test_point(collection,
1453 collection->drag_box_x[0] -
1454 col * collection->item_width,
1455 collection->drag_box_y[0]
1456 - row * collection->item_height
1457 + scroll,
1458 &collection->items[item],
1459 collection->item_width,
1460 collection->item_height))
1462 collection->buttons_pressed = 0;
1463 gtk_grab_remove(widget);
1464 collection_select_item(collection, item);
1465 gtk_signal_emit(GTK_OBJECT(collection),
1466 collection_signals[DRAG_SELECTION],
1467 event,
1468 collection->number_selected);
1470 else
1472 collection->drag_box_x[1] = x;
1473 collection->drag_box_y[1] = y;
1474 add_lasso_box(collection);
1479 current_event_time = stacked_time;
1480 return FALSE;
1483 static void add_lasso_box(Collection *collection)
1485 g_return_if_fail(collection != NULL);
1486 g_return_if_fail(IS_COLLECTION(collection));
1487 g_return_if_fail(collection->lasso_box == FALSE);
1489 collection->lasso_box = TRUE;
1490 draw_lasso_box(collection);
1493 static void draw_lasso_box(Collection *collection)
1495 GtkWidget *widget;
1496 int x, y, width, height;
1498 widget = GTK_WIDGET(collection);
1500 x = MIN(collection->drag_box_x[0], collection->drag_box_x[1]);
1501 y = MIN(collection->drag_box_y[0], collection->drag_box_y[1]);
1502 width = abs(collection->drag_box_x[1] - collection->drag_box_x[0]);
1503 height = abs(collection->drag_box_y[1] - collection->drag_box_y[0]);
1505 gdk_draw_rectangle(widget->window, collection->xor_gc, FALSE,
1506 x, y, width, height);
1509 static void remove_lasso_box(Collection *collection)
1511 g_return_if_fail(collection != NULL);
1512 g_return_if_fail(IS_COLLECTION(collection));
1513 g_return_if_fail(collection->lasso_box == TRUE);
1515 draw_lasso_box(collection);
1517 collection->lasso_box = FALSE;
1519 return;
1522 /* Convert a row,col address to an item number, or -1 if none */
1523 static int item_at_row_col(Collection *collection, int row, int col)
1525 int item;
1527 if (row < 0 || col < 0 || col >= collection->columns)
1528 return -1;
1530 item = col + row * collection->columns;
1532 if (item >= collection->number_of_items)
1533 return -1;
1534 return item;
1537 /* Make sure that 'item' is fully visible (vertically), scrolling if not. */
1538 static void scroll_to_show(Collection *collection, int item)
1540 int first, last, row;
1542 g_return_if_fail(collection != NULL);
1543 g_return_if_fail(IS_COLLECTION(collection));
1545 row = item / collection->columns;
1546 get_visible_limits(collection, &first, &last);
1548 if (row <= first)
1550 gtk_adjustment_set_value(collection->vadj,
1551 row * collection->item_height);
1553 else if (row >= last)
1555 GtkWidget *widget = (GtkWidget *) collection;
1556 int height;
1558 if (GTK_WIDGET_REALIZED(widget))
1560 gdk_window_get_size(widget->window, NULL, &height);
1561 gtk_adjustment_set_value(collection->vadj,
1562 (row + 1) * collection->item_height - height);
1567 /* Return the first and last rows which are [partly] visible. Does not
1568 * ensure that the rows actually exist (contain items).
1570 static void get_visible_limits(Collection *collection, int *first, int *last)
1572 GtkWidget *widget = (GtkWidget *) collection;
1573 int scroll, height;
1575 g_return_if_fail(collection != NULL);
1576 g_return_if_fail(IS_COLLECTION(collection));
1577 g_return_if_fail(first != NULL && last != NULL);
1579 if (!GTK_WIDGET_REALIZED(widget))
1581 *first = 0;
1582 *last = 0;
1584 else
1586 scroll = collection->vadj->value;
1587 gdk_window_get_size(widget->window, NULL, &height);
1589 *first = MAX(scroll / collection->item_height, 0);
1590 *last = (scroll + height - 1) /collection->item_height;
1592 if (*last < *first)
1593 *last = *first;
1597 /* Unselect all items except number item (-1 to unselect everything) */
1598 static void collection_clear_except(Collection *collection, gint exception)
1600 GtkWidget *widget;
1601 GdkRectangle area;
1602 int item = 0;
1603 int scroll;
1604 int end; /* Selected items to end up with */
1606 widget = GTK_WIDGET(collection);
1607 scroll = collection->vadj->value;
1609 end = exception >= 0 && exception < collection->number_of_items
1610 ? collection->items[exception].selected != 0 : 0;
1612 area.width = collection->item_width;
1613 area.height = collection->item_height;
1615 if (collection->number_selected == 0)
1616 return;
1618 while (collection->number_selected > end)
1620 while (item == exception || !collection->items[item].selected)
1621 item++;
1623 area.x = (item % collection->columns) * area.width;
1624 area.y = (item / collection->columns) * area.height
1625 - scroll;
1627 collection->items[item++].selected = FALSE;
1628 clear_area(collection, &area);
1629 collection_paint(collection, &area);
1631 collection->number_selected--;
1634 if (end == 0)
1635 gtk_signal_emit(GTK_OBJECT(collection),
1636 collection_signals[LOSE_SELECTION],
1637 current_event_time);
1640 /* Cancel the current wink effect. */
1641 static void cancel_wink(Collection *collection)
1643 gint item;
1645 g_return_if_fail(collection != NULL);
1646 g_return_if_fail(IS_COLLECTION(collection));
1647 g_return_if_fail(collection->wink_item != -1);
1649 item = collection->wink_item;
1651 collection->wink_item = -1;
1652 gtk_timeout_remove(collection->wink_timeout);
1654 collection_draw_item(collection, item, TRUE);
1657 static gboolean cancel_wink_timeout(Collection *collection)
1659 gint item;
1661 g_return_val_if_fail(collection != NULL, FALSE);
1662 g_return_val_if_fail(IS_COLLECTION(collection), FALSE);
1663 g_return_val_if_fail(collection->wink_item != -1, FALSE);
1665 item = collection->wink_item;
1667 collection->wink_item = -1;
1669 collection_draw_item(collection, item, TRUE);
1671 return FALSE;
1674 static gint focus_in(GtkWidget *widget, GdkEventFocus *event)
1676 g_return_val_if_fail(widget != NULL, FALSE);
1677 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1678 g_return_val_if_fail(event != NULL, FALSE);
1680 GTK_WIDGET_SET_FLAGS(widget, GTK_HAS_FOCUS);
1681 gtk_widget_draw_focus(widget);
1683 return FALSE;
1686 static gint focus_out(GtkWidget *widget, GdkEventFocus *event)
1688 g_return_val_if_fail(widget != NULL, FALSE);
1689 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1690 g_return_val_if_fail(event != NULL, FALSE);
1692 GTK_WIDGET_UNSET_FLAGS(widget, GTK_HAS_FOCUS);
1693 gtk_widget_draw_focus(widget);
1695 return FALSE;
1698 /* Functions for managing collections */
1700 /* Remove all objects from the collection */
1701 void collection_clear(Collection *collection)
1703 int prev_selected;
1705 g_return_if_fail(IS_COLLECTION(collection));
1707 if (collection->number_of_items == 0)
1708 return;
1710 if (collection->wink_item != -1)
1712 collection->wink_item = -1;
1713 gtk_timeout_remove(collection->wink_timeout);
1716 collection_set_cursor_item(collection,
1717 collection->cursor_item == -1 ? -1: 0);
1718 collection->cursor_item_old = -1;
1719 prev_selected = collection->number_selected;
1720 collection->number_of_items = collection->number_selected = 0;
1722 resize_arrays(collection, MINIMUM_ITEMS);
1724 collection->paint_level = PAINT_CLEAR;
1726 gtk_widget_queue_clear(GTK_WIDGET(collection));
1728 if (prev_selected && collection->number_selected == 0)
1729 gtk_signal_emit(GTK_OBJECT(collection),
1730 collection_signals[LOSE_SELECTION],
1731 current_event_time);
1734 /* Inserts a new item at the end. The new item is unselected, and its
1735 * number is returned.
1737 gint collection_insert(Collection *collection, gpointer data)
1739 int item;
1741 g_return_val_if_fail(IS_COLLECTION(collection), -1);
1743 item = collection->number_of_items;
1745 if (item >= collection->array_size)
1746 resize_arrays(collection, item + (item >> 1));
1748 collection->items[item].data = data;
1749 collection->items[item].selected = FALSE;
1751 collection->number_of_items++;
1753 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection)))
1755 set_vadjustment(collection);
1756 collection_draw_item(collection,
1757 collection->number_of_items - 1,
1758 FALSE);
1761 return item;
1764 /* Unselect an item by number */
1765 void collection_unselect_item(Collection *collection, gint item)
1767 g_return_if_fail(collection != NULL);
1768 g_return_if_fail(IS_COLLECTION(collection));
1769 g_return_if_fail(item >= 0 && item < collection->number_of_items);
1771 if (collection->items[item].selected)
1773 collection->items[item].selected = FALSE;
1774 collection_draw_item(collection, item, TRUE);
1776 if (--collection->number_selected == 0)
1777 gtk_signal_emit(GTK_OBJECT(collection),
1778 collection_signals[LOSE_SELECTION],
1779 current_event_time);
1783 /* Select an item by number */
1784 void collection_select_item(Collection *collection, gint item)
1786 g_return_if_fail(collection != NULL);
1787 g_return_if_fail(IS_COLLECTION(collection));
1788 g_return_if_fail(item >= 0 && item < collection->number_of_items);
1790 if (collection->items[item].selected)
1791 return; /* Already selected */
1793 collection->items[item].selected = TRUE;
1794 collection_draw_item(collection, item, TRUE);
1796 if (collection->number_selected++ == 0)
1797 gtk_signal_emit(GTK_OBJECT(collection),
1798 collection_signals[GAIN_SELECTION],
1799 current_event_time);
1802 /* Toggle the selected state of an item (by number) */
1803 void collection_toggle_item(Collection *collection, gint item)
1805 g_return_if_fail(collection != NULL);
1806 g_return_if_fail(IS_COLLECTION(collection));
1807 g_return_if_fail(item >= 0 && item < collection->number_of_items);
1809 if (collection->items[item].selected)
1811 collection->items[item].selected = FALSE;
1812 if (--collection->number_selected == 0)
1813 gtk_signal_emit(GTK_OBJECT(collection),
1814 collection_signals[LOSE_SELECTION],
1815 current_event_time);
1817 else
1819 collection->items[item].selected = TRUE;
1820 if (collection->number_selected++ == 0)
1821 gtk_signal_emit(GTK_OBJECT(collection),
1822 collection_signals[GAIN_SELECTION],
1823 current_event_time);
1825 collection_draw_item(collection, item, TRUE);
1828 /* Select all items in the collection */
1829 void collection_select_all(Collection *collection)
1831 GtkWidget *widget;
1832 GdkRectangle area;
1833 int item = 0;
1834 int scroll;
1836 g_return_if_fail(collection != NULL);
1837 g_return_if_fail(IS_COLLECTION(collection));
1839 widget = GTK_WIDGET(collection);
1840 scroll = collection->vadj->value;
1842 area.width = collection->item_width;
1843 area.height = collection->item_height;
1845 if (collection->number_selected == collection->number_of_items)
1846 return; /* Nothing to do */
1848 while (collection->number_selected < collection->number_of_items)
1850 while (collection->items[item].selected)
1851 item++;
1853 area.x = (item % collection->columns) * area.width;
1854 area.y = (item / collection->columns) * area.height
1855 - scroll;
1857 collection->items[item++].selected = TRUE;
1858 clear_area(collection, &area);
1859 collection_paint(collection, &area);
1861 collection->number_selected++;
1864 gtk_signal_emit(GTK_OBJECT(collection),
1865 collection_signals[GAIN_SELECTION],
1866 current_event_time);
1869 /* Unselect all items in the collection */
1870 void collection_clear_selection(Collection *collection)
1872 g_return_if_fail(collection != NULL);
1873 g_return_if_fail(IS_COLLECTION(collection));
1875 collection_clear_except(collection, -1);
1878 /* Force a redraw of the specified item, if it is visible */
1879 void collection_draw_item(Collection *collection, gint item, gboolean blank)
1881 int height;
1882 GdkRectangle area;
1883 GtkWidget *widget;
1884 int row, col;
1885 int scroll;
1886 int area_y, area_height; /* NOT shorts! */
1888 g_return_if_fail(collection != NULL);
1889 g_return_if_fail(IS_COLLECTION(collection));
1890 g_return_if_fail(item >= 0 &&
1891 (item == 0 || item < collection->number_of_items));
1893 widget = GTK_WIDGET(collection);
1894 if (!GTK_WIDGET_REALIZED(widget))
1895 return;
1897 col = item % collection->columns;
1898 row = item / collection->columns;
1899 scroll = collection->vadj->value; /* (round to int) */
1901 area.x = col * collection->item_width;
1902 area_y = row * collection->item_height - scroll;
1903 area.width = collection->item_width;
1904 area_height = collection->item_height;
1906 if (area_y + area_height < 0)
1907 return;
1909 gdk_window_get_size(widget->window, NULL, &height);
1911 if (area_y > height)
1912 return;
1914 area.y = area_y;
1915 area.height = area_height;
1917 if (blank || collection->lasso_box)
1918 clear_area(collection, &area);
1920 draw_one_item(collection, item, &area);
1922 if (collection->lasso_box)
1924 gdk_gc_set_clip_rectangle(collection->xor_gc, &area);
1925 draw_lasso_box(collection);
1926 gdk_gc_set_clip_rectangle(collection->xor_gc, NULL);
1930 void collection_set_item_size(Collection *collection, int width, int height)
1932 GtkWidget *widget;
1934 g_return_if_fail(collection != NULL);
1935 g_return_if_fail(IS_COLLECTION(collection));
1936 g_return_if_fail(width > 4 && height > 4);
1938 if (collection->item_width == width &&
1939 collection->item_height == height)
1940 return;
1942 widget = GTK_WIDGET(collection);
1944 collection->item_width = width;
1945 collection->item_height = height;
1947 if (GTK_WIDGET_REALIZED(widget))
1949 int window_width;
1951 collection->paint_level = PAINT_CLEAR;
1952 gdk_window_get_size(widget->window, &window_width, NULL);
1953 collection->columns = MAX(window_width / collection->item_width,
1956 set_vadjustment(collection);
1957 if (collection->cursor_item != -1)
1958 scroll_to_show(collection, collection->cursor_item);
1959 gtk_widget_queue_draw(widget);
1963 /* Cursor is positioned on item with the same data as before the sort.
1964 * Same for the wink item.
1966 void collection_qsort(Collection *collection,
1967 int (*compar)(const void *, const void *))
1969 int cursor, wink, items;
1970 gpointer cursor_data = NULL;
1971 gpointer wink_data = NULL;
1973 g_return_if_fail(collection != NULL);
1974 g_return_if_fail(IS_COLLECTION(collection));
1975 g_return_if_fail(compar != NULL);
1977 items = collection->number_of_items;
1979 wink = collection->wink_item;
1980 if (wink >= 0 && wink < items)
1981 wink_data = collection->items[wink].data;
1982 else
1983 wink = -1;
1985 cursor = collection->cursor_item;
1986 if (cursor >= 0 && cursor < items)
1987 cursor_data = collection->items[cursor].data;
1988 else
1989 cursor = -1;
1991 if (collection->wink_item != -1)
1993 collection->wink_item = -1;
1994 gtk_timeout_remove(collection->wink_timeout);
1997 qsort(collection->items, items, sizeof(collection->items[0]), compar);
1999 if (cursor > -1 || wink > -1)
2001 int item;
2003 for (item = 0; item < items; item++)
2005 if (collection->items[item].data == cursor_data)
2006 collection_set_cursor_item(collection, item);
2007 if (collection->items[item].data == wink_data)
2008 collection_wink_item(collection, item);
2012 collection->paint_level = PAINT_CLEAR;
2014 gtk_widget_queue_draw(GTK_WIDGET(collection));
2017 /* Find an item in an unsorted collection.
2018 * Returns the item number, or -1 if not found.
2020 int collection_find_item(Collection *collection, gpointer data,
2021 int (*compar)(const void *, const void *))
2023 int i;
2025 g_return_val_if_fail(collection != NULL, -1);
2026 g_return_val_if_fail(IS_COLLECTION(collection), -1);
2027 g_return_val_if_fail(compar != NULL, -1);
2029 for (i = 0; i < collection->number_of_items; i++)
2030 if (compar(&collection->items[i].data, &data) == 0)
2031 return i;
2033 return -1;
2036 /* The collection may be in either normal mode or panel mode.
2037 * In panel mode:
2038 * - a single click calls open_item
2039 * - items are never selected
2040 * - lasso boxes are disabled
2042 void collection_set_panel(Collection *collection, gboolean panel)
2044 g_return_if_fail(collection != NULL);
2045 g_return_if_fail(IS_COLLECTION(collection));
2047 collection->panel = panel == TRUE;
2049 if (collection->panel)
2051 collection_clear_selection(collection);
2052 if (collection->lasso_box)
2053 remove_lasso_box(collection);
2057 /* Return the number of the item under the point (x,y), or -1 for none.
2058 * This may call your test_point callback. The point is relative to the
2059 * collection's origin.
2061 int collection_get_item(Collection *collection, int x, int y)
2063 int scroll;
2064 int row, col;
2065 int item;
2067 g_return_val_if_fail(collection != NULL, -1);
2069 scroll = collection->vadj->value;
2070 col = x / collection->item_width;
2071 row = (y + scroll) / collection->item_height;
2073 if (col < 0 || row < 0 || col >= collection->columns)
2074 return -1;
2076 item = col + row * collection->columns;
2077 if (item >= collection->number_of_items
2079 !collection->test_point(collection,
2080 x - col * collection->item_width,
2081 y - row * collection->item_height
2082 + scroll,
2083 &collection->items[item],
2084 collection->item_width,
2085 collection->item_height))
2087 return -1;
2090 return item;
2093 /* Set the cursor/highlight over the given item. Passing -1
2094 * hides the cursor. As a special case, you may set the cursor item
2095 * to zero when there are no items.
2097 void collection_set_cursor_item(Collection *collection, gint item)
2099 int old_item;
2101 g_return_if_fail(collection != NULL);
2102 g_return_if_fail(IS_COLLECTION(collection));
2103 g_return_if_fail(item >= -1 &&
2104 (item < collection->number_of_items || item == 0));
2106 old_item = collection->cursor_item;
2108 if (old_item == item)
2109 return;
2111 collection->cursor_item = item;
2113 if (old_item != -1)
2114 collection_draw_item(collection, old_item, TRUE);
2116 if (item != -1)
2118 collection_draw_item(collection, item, TRUE);
2119 scroll_to_show(collection, item);
2121 else if (old_item != -1)
2122 collection->cursor_item_old = old_item;
2125 /* Briefly highlight an item to draw the user's attention to it.
2126 * -1 cancels the effect, as does deleting items, sorting the collection
2127 * or starting a new wink effect.
2128 * Otherwise, the effect will cancel itself after a short pause.
2129 * */
2130 void collection_wink_item(Collection *collection, gint item)
2132 g_return_if_fail(collection != NULL);
2133 g_return_if_fail(IS_COLLECTION(collection));
2134 g_return_if_fail(item >= -1 && item < collection->number_of_items);
2136 if (collection->wink_item != -1)
2137 cancel_wink(collection);
2138 if (item == -1)
2139 return;
2141 collection->cursor_item_old = collection->wink_item = item;
2142 collection->wink_timeout = gtk_timeout_add(300,
2143 (GtkFunction) cancel_wink_timeout,
2144 collection);
2145 collection_draw_item(collection, item, TRUE);
2146 scroll_to_show(collection, item);
2148 gdk_flush();
2151 /* Call test(item, data) on each item in the collection.
2152 * Remove all items for which it returns TRUE. test() should
2153 * free the data before returning TRUE. The collection is in an
2154 * inconsistant state during this call (ie, when test() is called).
2156 void collection_delete_if(Collection *collection,
2157 gboolean (*test)(gpointer item, gpointer data),
2158 gpointer data)
2160 int in, out = 0;
2161 int selected = 0;
2162 int cursor;
2164 g_return_if_fail(collection != NULL);
2165 g_return_if_fail(IS_COLLECTION(collection));
2166 g_return_if_fail(test != NULL);
2168 cursor = collection->cursor_item;
2170 for (in = 0; in < collection->number_of_items; in++)
2172 if (!test(collection->items[in].data, data))
2174 if (collection->items[in].selected)
2176 collection->items[out].selected = TRUE;
2177 selected++;
2179 else
2180 collection->items[out].selected = FALSE;
2182 collection->items[out++].data =
2183 collection->items[in].data;
2185 else if (cursor >= in)
2186 cursor--;
2189 if (in != out)
2191 collection->cursor_item = cursor;
2193 if (collection->wink_item != -1)
2195 collection->wink_item = -1;
2196 gtk_timeout_remove(collection->wink_timeout);
2199 collection->number_of_items = out;
2200 collection->number_selected = selected;
2201 resize_arrays(collection,
2202 MAX(collection->number_of_items, MINIMUM_ITEMS));
2204 collection->paint_level = PAINT_CLEAR;
2206 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection)))
2208 set_vadjustment(collection);
2209 gtk_widget_queue_draw(GTK_WIDGET(collection));
2214 /* Display a cross-hair pointer and the next time an item is clicked,
2215 * call the callback function. If the background is clicked or NULL
2216 * is passed as the callback then revert to normal operation.
2218 void collection_target(Collection *collection,
2219 CollectionTargetFunc callback,
2220 gpointer user_data)
2222 g_return_if_fail(collection != NULL);
2223 g_return_if_fail(IS_COLLECTION(collection));
2225 if (callback != collection->target_cb)
2226 gdk_window_set_cursor(GTK_WIDGET(collection)->window,
2227 callback ? crosshair : NULL);
2229 collection->target_cb = callback;
2230 collection->target_data = user_data;
2232 if (collection->cursor_item != -1)
2233 collection_draw_item(collection, collection->cursor_item,
2234 FALSE);
2237 /* Move the cursor by the given row and column offsets.
2238 * Moving by (0,0) can be used to simply make the cursor appear.
2240 void collection_move_cursor(Collection *collection, int drow, int dcol)
2242 int row, col, item;
2243 int first, last, total_rows;
2245 g_return_if_fail(collection != NULL);
2246 g_return_if_fail(IS_COLLECTION(collection));
2248 get_visible_limits(collection, &first, &last);
2250 item = collection->cursor_item;
2251 if (item == -1)
2253 item = MIN(collection->cursor_item_old,
2254 collection->number_of_items - 1);
2257 if (item == -1)
2259 col = 0;
2260 row = first;
2262 else
2264 row = item / collection->columns;
2265 col = item % collection->columns + dcol;
2267 if (row < first)
2268 row = first;
2269 else if (row > last)
2270 row = last;
2271 else
2272 row = MAX(row + drow, 0);
2275 total_rows = (collection->number_of_items + collection->columns - 1)
2276 / collection->columns;
2278 if (row >= total_rows - 1 && drow > 0)
2280 row = total_rows - 1;
2281 item = col + row * collection->columns;
2282 if (item >= collection->number_of_items)
2284 row--;
2285 scroll_to_show(collection, item);
2288 if (row < 0)
2289 row = 0;
2291 item = col + row * collection->columns;
2293 if (item >= 0 && item < collection->number_of_items)
2294 collection_set_cursor_item(collection, item);