r339: The menu key bindings are now only saved if they actually changed.
[rox-filer/ma.git] / ROX-Filer / src / collection.c
blob72a8db5845cb86aa1c9805922b50df1f4eeb619c
1 /*
2 * $Id$
4 * Collection - a GTK+ widget
5 * Copyright (C) 2000, Thomas Leonard, <tal197@users.sourceforge.net>.
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 = TRUE;
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 gpointer user_data);
127 static gboolean default_test_point(Collection *collection,
128 int point_x, int point_y,
129 CollectionItem *data,
130 int width, int height,
131 gpointer user_data);
132 static gint collection_motion_notify(GtkWidget *widget,
133 GdkEventMotion *event);
134 static void add_lasso_box(Collection *collection);
135 static void abort_lasso(Collection *collection);
136 static void remove_lasso_box(Collection *collection);
137 static void draw_lasso_box(Collection *collection);
138 static int item_at_row_col(Collection *collection, int row, int col);
139 static void collection_clear_except(Collection *collection, gint exception);
140 static void cancel_wink(Collection *collection);
141 static gint collection_key_press(GtkWidget *widget, GdkEventKey *event);
142 static void get_visible_limits(Collection *collection, int *first, int *last);
143 static void scroll_to_show(Collection *collection, int item);
144 static gint focus_in(GtkWidget *widget, GdkEventFocus *event);
145 static gint focus_out(GtkWidget *widget, GdkEventFocus *event);
146 static void draw_focus(GtkWidget *widget);
148 static void draw_focus_at(Collection *collection, GdkRectangle *area)
150 GtkWidget *widget;
151 GdkGC *gc;
153 widget = GTK_WIDGET(collection);
155 if (collection->target_cb)
156 gc = widget->style->white_gc;
157 else if (GTK_WIDGET_FLAGS(widget) & GTK_HAS_FOCUS)
158 gc = widget->style->black_gc;
159 else
160 gc = widget->style->fg_gc[GTK_STATE_INSENSITIVE];
162 gdk_draw_rectangle(widget->window, gc, FALSE,
163 area->x + 1, area->y + 1,
164 area->width - 3,
165 area->height - 3);
168 static void draw_one_item(Collection *collection, int item, GdkRectangle *area)
170 if (item < collection->number_of_items)
172 collection->draw_item((GtkWidget *) collection,
173 &collection->items[item],
174 area,
175 collection->cb_user_data);
176 if (item == collection->wink_item)
177 gdk_draw_rectangle(((GtkWidget *) collection)->window,
178 ((GtkWidget *) collection)->style->black_gc,
179 FALSE,
180 area->x, area->y,
181 area->width - 1, area->height - 1);
184 if (item == collection->cursor_item)
185 draw_focus_at(collection, area);
188 static void draw_focus(GtkWidget *widget)
190 Collection *collection;
192 g_return_if_fail(widget != NULL);
193 g_return_if_fail(IS_COLLECTION(widget));
195 collection = COLLECTION(widget);
197 if (collection->cursor_item < 0 || !GTK_WIDGET_REALIZED(widget))
198 return;
200 collection_draw_item(collection, collection->cursor_item, FALSE);
203 GtkType collection_get_type(void)
205 static guint my_type = 0;
207 if (!my_type)
209 static const GtkTypeInfo my_info =
211 "Collection",
212 sizeof(Collection),
213 sizeof(CollectionClass),
214 (GtkClassInitFunc) collection_class_init,
215 (GtkObjectInitFunc) collection_init,
216 NULL, /* Reserved 1 */
217 NULL, /* Reserved 2 */
218 (GtkClassInitFunc) NULL /* base_class_init_func */
221 my_type = gtk_type_unique(gtk_widget_get_type(),
222 &my_info);
225 return my_type;
228 static void collection_class_init(CollectionClass *class)
230 GtkObjectClass *object_class;
231 GtkWidgetClass *widget_class;
233 object_class = (GtkObjectClass*) class;
234 widget_class = (GtkWidgetClass*) class;
236 parent_class = gtk_type_class(gtk_widget_get_type());
238 gtk_object_add_arg_type("Collection::vadjustment",
239 GTK_TYPE_ADJUSTMENT,
240 GTK_ARG_READWRITE | GTK_ARG_CONSTRUCT,
241 ARG_VADJUSTMENT);
243 object_class->destroy = collection_destroy;
244 object_class->finalize = collection_finalize;
246 widget_class->realize = collection_realize;
247 widget_class->draw = collection_draw;
248 widget_class->expose_event = collection_expose;
249 widget_class->size_request = collection_size_request;
250 widget_class->size_allocate = collection_size_allocate;
251 widget_class->style_set = collection_set_style;
253 widget_class->key_press_event = collection_key_press;
254 widget_class->button_press_event = collection_button_press;
255 widget_class->button_release_event = collection_button_release;
256 widget_class->motion_notify_event = collection_motion_notify;
257 widget_class->focus_in_event = focus_in;
258 widget_class->focus_out_event = focus_out;
259 widget_class->draw_focus = draw_focus;
261 object_class->set_arg = collection_set_arg;
262 object_class->get_arg = collection_get_arg;
264 class->open_item = NULL;
265 class->drag_selection = NULL;
266 class->show_menu = NULL;
267 class->gain_selection = NULL;
268 class->lose_selection = NULL;
270 collection_signals[OPEN_ITEM] = gtk_signal_new("open_item",
271 GTK_RUN_LAST,
272 object_class->type,
273 GTK_SIGNAL_OFFSET(CollectionClass,
274 open_item),
275 gtk_marshal_NONE__POINTER_UINT,
276 GTK_TYPE_NONE, 2,
277 GTK_TYPE_POINTER,
278 GTK_TYPE_UINT);
279 collection_signals[DRAG_SELECTION] = gtk_signal_new("drag_selection",
280 GTK_RUN_LAST,
281 object_class->type,
282 GTK_SIGNAL_OFFSET(CollectionClass,
283 drag_selection),
284 gtk_marshal_NONE__POINTER_UINT,
285 GTK_TYPE_NONE, 2,
286 GTK_TYPE_POINTER,
287 GTK_TYPE_UINT);
288 collection_signals[SHOW_MENU] = gtk_signal_new("show_menu",
289 GTK_RUN_LAST,
290 object_class->type,
291 GTK_SIGNAL_OFFSET(CollectionClass,
292 show_menu),
293 gtk_marshal_NONE__POINTER_INT,
294 GTK_TYPE_NONE, 2,
295 GTK_TYPE_POINTER,
296 GTK_TYPE_INT);
297 collection_signals[GAIN_SELECTION] = gtk_signal_new("gain_selection",
298 GTK_RUN_LAST,
299 object_class->type,
300 GTK_SIGNAL_OFFSET(CollectionClass,
301 gain_selection),
302 gtk_marshal_NONE__UINT,
303 GTK_TYPE_NONE, 1,
304 GTK_TYPE_UINT);
305 collection_signals[LOSE_SELECTION] = gtk_signal_new("lose_selection",
306 GTK_RUN_LAST,
307 object_class->type,
308 GTK_SIGNAL_OFFSET(CollectionClass,
309 lose_selection),
310 gtk_marshal_NONE__UINT,
311 GTK_TYPE_NONE, 1,
312 GTK_TYPE_UINT);
314 gtk_object_class_add_signals(object_class,
315 collection_signals, LAST_SIGNAL);
318 static void collection_init(Collection *object)
320 g_return_if_fail(object != NULL);
321 g_return_if_fail(IS_COLLECTION(object));
323 if (!crosshair)
324 crosshair = gdk_cursor_new(GDK_CROSSHAIR);
326 GTK_WIDGET_SET_FLAGS(GTK_WIDGET(object), GTK_CAN_FOCUS);
328 object->panel = FALSE;
329 object->number_of_items = 0;
330 object->number_selected = 0;
331 object->columns = 1;
332 object->item_width = 64;
333 object->item_height = 64;
334 object->vadj = NULL;
335 object->paint_level = PAINT_OVERWRITE;
336 object->last_scroll = 0;
337 object->bg_gc = NULL;
339 object->items = g_malloc(sizeof(CollectionItem) * MINIMUM_ITEMS);
340 object->cursor_item = -1;
341 object->cursor_item_old = -1;
342 object->wink_item = -1;
343 object->array_size = MINIMUM_ITEMS;
344 object->draw_item = default_draw_item;
345 object->test_point = default_test_point;
347 object->buttons_pressed = 0;
348 object->may_drag = FALSE;
350 object->auto_scroll = -1;
352 return;
355 GtkWidget* collection_new(GtkAdjustment *vadj)
357 if (vadj)
358 g_return_val_if_fail(GTK_IS_ADJUSTMENT(vadj), NULL);
360 return GTK_WIDGET(gtk_widget_new(collection_get_type(),
361 "vadjustment", vadj,
362 NULL));
365 void collection_set_functions(Collection *collection,
366 CollectionDrawFunc draw_item,
367 CollectionTestFunc test_point,
368 gpointer user_data)
370 GtkWidget *widget;
372 g_return_if_fail(collection != NULL);
373 g_return_if_fail(IS_COLLECTION(collection));
375 widget = GTK_WIDGET(collection);
377 if (!draw_item)
378 draw_item = default_draw_item;
379 if (!test_point)
380 test_point = default_test_point;
382 collection->draw_item = draw_item;
383 collection->test_point = test_point;
384 collection->cb_user_data = user_data;
386 if (GTK_WIDGET_REALIZED(widget))
388 collection->paint_level = PAINT_CLEAR;
389 gtk_widget_queue_clear(widget);
393 /* After this we are unusable, but our data (if any) is still hanging around.
394 * It will be freed later with finalize.
396 static void collection_destroy(GtkObject *object)
398 Collection *collection;
400 g_return_if_fail(object != NULL);
401 g_return_if_fail(IS_COLLECTION(object));
403 collection = COLLECTION(object);
405 if (collection->wink_item != -1)
407 collection->wink_item = -1;
408 gtk_timeout_remove(collection->wink_timeout);
411 if (collection->auto_scroll != -1)
413 gtk_timeout_remove(collection->auto_scroll);
414 collection->auto_scroll = -1;
417 gtk_signal_disconnect_by_data(GTK_OBJECT(collection->vadj),
418 collection);
420 if (collection->bg_gc)
422 gdk_gc_destroy(collection->bg_gc);
423 collection->bg_gc = NULL;
426 if (GTK_OBJECT_CLASS(parent_class)->destroy)
427 (*GTK_OBJECT_CLASS(parent_class)->destroy)(object);
430 /* This is the last thing that happens to us. Free all data. */
431 static void collection_finalize(GtkObject *object)
433 Collection *collection;
435 collection = COLLECTION(object);
437 if (collection->vadj)
439 gtk_object_unref(GTK_OBJECT(collection->vadj));
442 g_free(collection->items);
445 static GdkGC *create_bg_gc(GtkWidget *widget)
447 GdkGCValues values;
449 values.tile = widget->style->bg_pixmap[GTK_STATE_INSENSITIVE];
450 values.fill = GDK_TILED;
452 return gdk_gc_new_with_values(widget->window, &values,
453 GDK_GC_FILL | GDK_GC_TILE);
456 static void collection_realize(GtkWidget *widget)
458 Collection *collection;
459 GdkWindowAttr attributes;
460 gint attributes_mask;
461 GdkGCValues xor_values;
463 g_return_if_fail(widget != NULL);
464 g_return_if_fail(IS_COLLECTION(widget));
465 g_return_if_fail(widget->parent != NULL);
467 GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);
468 collection = COLLECTION(widget);
470 attributes.x = widget->allocation.x;
471 attributes.y = widget->allocation.y;
472 attributes.width = widget->allocation.width;
473 attributes.height = widget->allocation.height;
474 attributes.wclass = GDK_INPUT_OUTPUT;
475 attributes.window_type = GDK_WINDOW_CHILD;
476 attributes.event_mask = gtk_widget_get_events(widget) |
477 GDK_EXPOSURE_MASK |
478 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
479 GDK_BUTTON1_MOTION_MASK | GDK_BUTTON2_MOTION_MASK |
480 GDK_BUTTON3_MOTION_MASK;
481 attributes.visual = gtk_widget_get_visual(widget);
482 attributes.colormap = gtk_widget_get_colormap(widget);
484 attributes_mask = GDK_WA_X | GDK_WA_Y |
485 GDK_WA_VISUAL | GDK_WA_COLORMAP;
486 widget->window = gdk_window_new(widget->parent->window,
487 &attributes, attributes_mask);
489 widget->style = gtk_style_attach(widget->style, widget->window);
491 gdk_window_set_user_data(widget->window, widget);
493 gdk_window_set_background(widget->window,
494 &widget->style->base[GTK_STATE_INSENSITIVE]);
495 if (widget->style->bg_pixmap[GTK_STATE_INSENSITIVE])
496 collection->bg_gc = create_bg_gc(widget);
498 /* Try to stop everything flickering horribly */
499 gdk_window_set_static_gravities(widget->window, TRUE);
501 set_vadjustment(collection);
503 xor_values.function = GDK_XOR;
504 xor_values.foreground.red = 0xffff;
505 xor_values.foreground.green = 0xffff;
506 xor_values.foreground.blue = 0xffff;
507 gdk_color_alloc(gtk_widget_get_colormap(widget),
508 &xor_values.foreground);
509 collection->xor_gc = gdk_gc_new_with_values(widget->window,
510 &xor_values,
511 GDK_GC_FOREGROUND
512 | GDK_GC_FUNCTION);
515 static void collection_size_request(GtkWidget *widget,
516 GtkRequisition *requisition)
518 requisition->width = MIN_WIDTH;
519 requisition->height = MIN_HEIGHT;
522 static void collection_size_allocate(GtkWidget *widget,
523 GtkAllocation *allocation)
525 Collection *collection;
526 int old_columns;
528 g_return_if_fail(widget != NULL);
529 g_return_if_fail(IS_COLLECTION(widget));
530 g_return_if_fail(allocation != NULL);
532 collection = COLLECTION(widget);
534 old_columns = collection->columns;
535 if (widget->allocation.x != allocation->x
536 || widget->allocation.y != allocation->y)
537 collection->paint_level = PAINT_CLEAR;
539 widget->allocation = *allocation;
541 collection->columns = allocation->width / collection->item_width;
542 if (collection->columns < 1)
543 collection->columns = 1;
545 if (GTK_WIDGET_REALIZED(widget))
547 gdk_window_move_resize(widget->window,
548 allocation->x, allocation->y,
549 allocation->width, allocation->height);
551 if (old_columns != collection->columns)
553 collection->paint_level = PAINT_CLEAR;
554 gtk_widget_queue_clear(widget);
557 set_vadjustment(collection);
559 if (collection->cursor_item != -1)
560 scroll_to_show(collection, collection->cursor_item);
564 static void collection_set_style(GtkWidget *widget,
565 GtkStyle *previous_style)
567 Collection *collection;
569 g_return_if_fail(IS_COLLECTION(widget));
571 collection = COLLECTION(widget);
573 collection->paint_level = PAINT_CLEAR;
575 if (GTK_WIDGET_REALIZED(widget))
577 gdk_window_set_background(widget->window,
578 &widget->style->base[GTK_STATE_INSENSITIVE]);
580 if (collection->bg_gc)
582 gdk_gc_destroy(collection->bg_gc);
583 collection->bg_gc = NULL;
586 if (widget->style->bg_pixmap[GTK_STATE_INSENSITIVE])
587 collection->bg_gc = create_bg_gc(widget);
591 static void clear_area(Collection *collection, GdkRectangle *area)
593 GtkWidget *widget = GTK_WIDGET(collection);
594 int scroll = collection->vadj->value;
596 if (collection->bg_gc)
598 gdk_gc_set_ts_origin(collection->bg_gc, 0, -scroll);
600 gdk_draw_rectangle(widget->window,
601 collection->bg_gc,
602 TRUE,
603 area->x, area->y,
604 area->width, area->height);
606 else
607 gdk_window_clear_area(widget->window,
608 area->x, area->y, area->width, area->height);
611 static gint collection_paint(Collection *collection,
612 GdkRectangle *area)
614 GdkRectangle whole, item_area;
615 GtkWidget *widget;
616 int row, col;
617 int item;
618 int scroll;
619 int start_row, last_row;
620 int start_col, last_col;
621 int phys_last_col;
622 GdkRectangle clip;
624 scroll = collection->vadj->value;
626 widget = GTK_WIDGET(collection);
628 if (collection->paint_level > PAINT_NORMAL || area == NULL)
630 guint width, height;
631 gdk_window_get_size(widget->window, &width, &height);
633 whole.x = 0;
634 whole.y = 0;
635 whole.width = width;
636 whole.height = height;
638 area = &whole;
640 if (collection->paint_level == PAINT_CLEAR
641 && !collection->lasso_box)
642 clear_area(collection, area);
644 collection->paint_level = PAINT_NORMAL;
647 /* Calculate the ranges to plot */
648 start_row = (area->y + scroll) / collection->item_height;
649 last_row = (area->y + area->height - 1 + scroll)
650 / collection->item_height;
651 row = start_row;
653 start_col = area->x / collection->item_width;
654 phys_last_col = (area->x + area->width - 1) / collection->item_width;
656 if (collection->lasso_box)
658 /* You can't be too careful with lasso boxes...
660 * clip gives the total area drawn over (this may be larger
661 * than the requested area). It's used to redraw the lasso
662 * box.
664 clip.x = start_col * collection->item_width;
665 clip.y = start_row * collection->item_height - scroll;
666 clip.width = (phys_last_col - start_col + 1)
667 * collection->item_width;
668 clip.height = (last_row - start_row + 1)
669 * collection->item_height;
671 clear_area(collection, &clip);
674 if (start_col < collection->columns)
676 if (phys_last_col >= collection->columns)
677 last_col = collection->columns - 1;
678 else
679 last_col = phys_last_col;
681 col = start_col;
683 item = row * collection->columns + col;
684 item_area.width = collection->item_width;
685 item_area.height = collection->item_height;
687 while ((item == 0 || item < collection->number_of_items)
688 && row <= last_row)
690 item_area.x = col * collection->item_width;
691 item_area.y = row * collection->item_height - scroll;
693 draw_one_item(collection, item, &item_area);
694 col++;
696 if (col > last_col)
698 col = start_col;
699 row++;
700 item = row * collection->columns + col;
702 else
703 item++;
707 if (collection->lasso_box)
709 gdk_gc_set_clip_rectangle(collection->xor_gc, &clip);
710 draw_lasso_box(collection);
711 gdk_gc_set_clip_rectangle(collection->xor_gc, NULL);
714 return FALSE;
717 static void default_draw_item( GtkWidget *widget,
718 CollectionItem *item,
719 GdkRectangle *area,
720 gpointer user_data)
722 gdk_draw_arc(widget->window,
723 item->selected ? widget->style->white_gc
724 : widget->style->black_gc,
725 TRUE,
726 area->x, area->y,
727 area->width, area->height,
728 0, 360 * 64);
732 static gboolean default_test_point(Collection *collection,
733 int point_x, int point_y,
734 CollectionItem *item,
735 int width, int height,
736 gpointer user_data)
738 float f_x, f_y;
740 /* Convert to point in unit circle */
741 f_x = ((float) point_x / width) - 0.5;
742 f_y = ((float) point_y / height) - 0.5;
744 return (f_x * f_x) + (f_y * f_y) <= .25;
747 static void collection_set_arg( GtkObject *object,
748 GtkArg *arg,
749 guint arg_id)
751 Collection *collection;
753 collection = COLLECTION(object);
755 switch (arg_id)
757 case ARG_VADJUSTMENT:
758 collection_set_adjustment(collection,
759 GTK_VALUE_POINTER(*arg));
760 break;
761 default:
762 break;
766 static void collection_set_adjustment( Collection *collection,
767 GtkAdjustment *vadj)
769 if (vadj)
770 g_return_if_fail (GTK_IS_ADJUSTMENT (vadj));
771 else
772 vadj = GTK_ADJUSTMENT(gtk_adjustment_new(0.0,
773 0.0, 0.0,
774 0.0, 0.0, 0.0));
775 if (collection->vadj && (collection->vadj != vadj))
777 gtk_signal_disconnect_by_data(GTK_OBJECT(collection->vadj),
778 collection);
779 gtk_object_unref(GTK_OBJECT(collection->vadj));
782 if (collection->vadj != vadj)
784 collection->vadj = vadj;
785 gtk_object_ref(GTK_OBJECT(collection->vadj));
786 gtk_object_sink(GTK_OBJECT(collection->vadj));
788 gtk_signal_connect(GTK_OBJECT(collection->vadj),
789 "changed",
790 (GtkSignalFunc) collection_adjustment,
791 collection);
792 gtk_signal_connect(GTK_OBJECT(collection->vadj),
793 "value_changed",
794 (GtkSignalFunc) collection_adjustment,
795 collection);
796 gtk_signal_connect(GTK_OBJECT(collection->vadj),
797 "disconnect",
798 (GtkSignalFunc) collection_disconnect,
799 collection);
800 collection_adjustment(vadj, collection);
804 static void collection_get_arg( GtkObject *object,
805 GtkArg *arg,
806 guint arg_id)
808 Collection *collection;
810 collection = COLLECTION(object);
812 switch (arg_id)
814 case ARG_VADJUSTMENT:
815 GTK_VALUE_POINTER(*arg) = collection->vadj;
816 break;
817 default:
818 arg->type = GTK_TYPE_INVALID;
819 break;
823 /* Something about the adjustment has changed */
824 static void collection_adjustment(GtkAdjustment *adjustment,
825 Collection *collection)
827 gint diff;
829 g_return_if_fail(adjustment != NULL);
830 g_return_if_fail(GTK_IS_ADJUSTMENT(adjustment));
831 g_return_if_fail(collection != NULL);
832 g_return_if_fail(IS_COLLECTION(collection));
834 diff = ((gint) adjustment->value) - collection->last_scroll;
836 if (diff)
838 collection->last_scroll = adjustment->value;
840 if (collection->lasso_box)
842 remove_lasso_box(collection);
843 collection->drag_box_y[0] -= diff;
844 scroll_by(collection, diff);
845 add_lasso_box(collection);
847 else
848 scroll_by(collection, diff);
852 static void collection_disconnect(GtkAdjustment *adjustment,
853 Collection *collection)
855 g_return_if_fail(adjustment != NULL);
856 g_return_if_fail(GTK_IS_ADJUSTMENT(adjustment));
857 g_return_if_fail(collection != NULL);
858 g_return_if_fail(IS_COLLECTION(collection));
860 collection_set_adjustment(collection, NULL);
863 static void set_vadjustment(Collection *collection)
865 GtkWidget *widget;
866 guint height;
867 int cols, rows;
869 widget = GTK_WIDGET(collection);
871 if (!GTK_WIDGET_REALIZED(widget))
872 return;
874 gdk_window_get_size(widget->window, NULL, &height);
875 cols = collection->columns;
876 rows = (collection->number_of_items + cols - 1) / cols;
878 collection->vadj->lower = 0.0;
879 collection->vadj->upper = collection->item_height * rows;
881 collection->vadj->step_increment =
882 MIN(collection->vadj->upper, collection->item_height / 4);
884 collection->vadj->page_increment =
885 MIN(collection->vadj->upper,
886 height - 5.0);
888 collection->vadj->page_size = MIN(collection->vadj->upper, height);
890 collection->vadj->value = MIN(collection->vadj->value,
891 collection->vadj->upper - collection->vadj->page_size);
893 collection->vadj->value = MAX(collection->vadj->value, 0.0);
895 gtk_signal_emit_by_name(GTK_OBJECT(collection->vadj), "changed");
898 /* Change the adjustment by this amount. Bounded. */
899 static void diff_vpos(Collection *collection, int diff)
901 int value = collection->vadj->value + diff;
903 value = CLAMP(value, 0,
904 collection->vadj->upper - collection->vadj->page_size);
905 gtk_adjustment_set_value(collection->vadj, value);
908 static void collection_draw(GtkWidget *widget, GdkRectangle *area)
910 Collection *collection;
912 g_return_if_fail(widget != NULL);
913 g_return_if_fail(IS_COLLECTION(widget));
914 g_return_if_fail(area != NULL); /* Not actually used */
916 collection = COLLECTION(widget);
918 /* This doesn't always work - I think Gtk+ may be doing some
919 * kind of expose-event compression...
920 if (collection->paint_level > PAINT_NORMAL)
922 collection_paint(collection, area);
925 static gint collection_expose(GtkWidget *widget, GdkEventExpose *event)
927 Collection *collection;
929 g_return_val_if_fail(widget != NULL, FALSE);
930 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
931 g_return_val_if_fail(event != NULL, FALSE);
933 collection = COLLECTION(widget);
935 clear_area(collection, &event->area);
937 collection_paint(collection, &event->area);
939 return FALSE;
942 /* Positive makes the contents go move up the screen */
943 static void scroll_by(Collection *collection, gint diff)
945 GtkWidget *widget;
946 guint width, height;
947 guint from_y, to_y;
948 guint amount;
949 GdkRectangle new_area;
951 if (diff == 0)
952 return;
954 widget = GTK_WIDGET(collection);
956 if (collection->lasso_box)
957 abort_lasso(collection);
959 gdk_window_get_size(widget->window, &width, &height);
960 new_area.x = 0;
961 new_area.width = width;
963 if (diff > 0)
965 amount = diff;
966 from_y = amount;
967 to_y = 0;
968 new_area.y = height - amount;
970 else
972 amount = -diff;
973 from_y = 0;
974 to_y = amount;
975 new_area.y = 0;
978 new_area.height = amount;
980 if (amount < height)
982 gdk_draw_pixmap(widget->window,
983 widget->style->white_gc,
984 widget->window,
986 from_y,
988 to_y,
989 width,
990 height - amount);
991 /* We have to redraw everything because any pending
992 * expose events now contain invalid areas.
993 * Don't need to clear the area first though...
995 if (collection->paint_level < PAINT_OVERWRITE)
996 collection->paint_level = PAINT_OVERWRITE;
998 else
999 collection->paint_level = PAINT_CLEAR;
1001 clear_area(collection, &new_area);
1002 collection_paint(collection, NULL);
1005 static void resize_arrays(Collection *collection, guint new_size)
1007 g_return_if_fail(collection != NULL);
1008 g_return_if_fail(IS_COLLECTION(collection));
1009 g_return_if_fail(new_size >= collection->number_of_items);
1011 collection->items = g_realloc(collection->items,
1012 sizeof(CollectionItem) * new_size);
1013 collection->array_size = new_size;
1016 static void return_pressed(Collection *collection)
1018 int item = collection->cursor_item;
1019 CollectionTargetFunc cb = collection->target_cb;
1020 gpointer data = collection->target_data;
1022 collection_target(collection, NULL, NULL);
1023 if (item < 0 || item >= collection->number_of_items)
1024 return;
1026 if (cb)
1028 cb(collection, item, data);
1029 return;
1032 gtk_signal_emit(GTK_OBJECT(collection),
1033 collection_signals[OPEN_ITEM],
1034 collection->items[item].data,
1035 item);
1038 static gint collection_key_press(GtkWidget *widget, GdkEventKey *event)
1040 Collection *collection;
1041 int item;
1043 g_return_val_if_fail(widget != NULL, FALSE);
1044 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1045 g_return_val_if_fail(event != NULL, FALSE);
1047 collection = (Collection *) widget;
1048 item = collection->cursor_item;
1050 switch (event->keyval)
1052 case GDK_Left:
1053 collection_move_cursor(collection, 0, -1);
1054 break;
1055 case GDK_Right:
1056 collection_move_cursor(collection, 0, 1);
1057 break;
1058 case GDK_Up:
1059 collection_move_cursor(collection, -1, 0);
1060 break;
1061 case GDK_Down:
1062 collection_move_cursor(collection, 1, 0);
1063 break;
1064 case GDK_Home:
1065 collection_set_cursor_item(collection, 0);
1066 break;
1067 case GDK_End:
1068 collection_set_cursor_item(collection,
1069 MAX((gint) collection->number_of_items - 1, 0));
1070 break;
1071 case GDK_Page_Up:
1072 collection_move_cursor(collection, -10, 0);
1073 break;
1074 case GDK_Page_Down:
1075 collection_move_cursor(collection, 10, 0);
1076 break;
1077 case GDK_Return:
1078 return_pressed(collection);
1079 break;
1080 case GDK_Escape:
1081 if (!collection->target_cb)
1083 collection_set_cursor_item(collection, -1);
1084 collection_clear_selection(collection);
1085 return FALSE; /* Pass it on */
1087 collection_target(collection, NULL, NULL);
1088 break;
1089 case ' ':
1090 if (item >=0 && item < collection->number_of_items)
1091 collection_toggle_item(collection, item);
1092 break;
1093 default:
1094 return FALSE;
1097 return TRUE;
1100 static gint collection_button_press(GtkWidget *widget,
1101 GdkEventButton *event)
1103 Collection *collection;
1104 int row, col;
1105 int item;
1106 int action;
1107 int scroll;
1108 guint stacked_time;
1110 g_return_val_if_fail(widget != NULL, FALSE);
1111 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1112 g_return_val_if_fail(event != NULL, FALSE);
1114 collection = COLLECTION(widget);
1116 collection->item_clicked = -1;
1118 if (event->button > 3)
1120 int diff;
1122 /* Wheel mouse scrolling */
1123 if (event->button == 4)
1124 diff = -((signed int) collection->item_height) / 4;
1125 else if (event->button == 5)
1126 diff = collection->item_height / 4;
1127 else
1128 diff = 0;
1130 if (diff)
1132 int old_value = collection->vadj->value;
1133 int new_value = 0;
1134 gboolean box = collection->lasso_box;
1136 new_value = CLAMP(old_value + diff, 0.0,
1137 collection->vadj->upper
1138 - collection->vadj->page_size);
1139 diff = new_value - old_value;
1140 if (diff)
1142 if (box)
1144 remove_lasso_box(collection);
1145 collection->drag_box_y[0] -= diff;
1147 collection->vadj->value = new_value;
1148 gtk_signal_emit_by_name(
1149 GTK_OBJECT(collection->vadj),
1150 "changed");
1151 if (box)
1152 add_lasso_box(collection);
1155 return FALSE;
1158 if (collection->cursor_item != -1)
1159 collection_set_cursor_item(collection, -1);
1161 scroll = collection->vadj->value;
1163 if (event->type == GDK_BUTTON_PRESS &&
1164 event->button != collection_menu_button)
1166 if (collection->buttons_pressed++ == 0)
1167 gtk_grab_add(widget);
1168 else
1169 return FALSE; /* Ignore extra presses */
1172 if (event->state & GDK_CONTROL_MASK)
1173 action = 2;
1174 else
1175 action = event->button;
1177 /* Ignore all clicks while we are dragging a lasso box */
1178 if (collection->lasso_box)
1179 return TRUE;
1181 col = event->x / collection->item_width;
1182 row = (event->y + scroll) / collection->item_height;
1184 if (col < 0 || row < 0 || col >= collection->columns)
1185 item = -1;
1186 else
1188 item = col + row * collection->columns;
1189 if (item >= collection->number_of_items
1191 !collection->test_point(collection,
1192 event->x - col * collection->item_width,
1193 event->y - row * collection->item_height
1194 + scroll,
1195 &collection->items[item],
1196 collection->item_width,
1197 collection->item_height,
1198 collection->cb_user_data))
1200 item = -1;
1204 if (collection->target_cb)
1206 CollectionTargetFunc cb = collection->target_cb;
1207 gpointer data = collection->target_data;
1209 collection_target(collection, NULL, NULL);
1210 if (collection->buttons_pressed)
1212 gtk_grab_remove(widget);
1213 collection->buttons_pressed = 0;
1215 if (item > -1 && event->button != collection_menu_button)
1216 cb(collection, item, data);
1217 return TRUE;
1220 collection->drag_box_x[0] = event->x;
1221 collection->drag_box_y[0] = event->y;
1222 collection->item_clicked = item;
1224 stacked_time = current_event_time;
1225 current_event_time = event->time;
1227 if (event->button == collection_menu_button)
1229 gtk_signal_emit(GTK_OBJECT(collection),
1230 collection_signals[SHOW_MENU],
1231 event,
1232 item);
1234 else if (event->type == GDK_2BUTTON_PRESS && collection->panel)
1236 /* Do nothing */
1238 else if ((event->type == GDK_2BUTTON_PRESS && !collection_single_click)
1239 || collection->panel)
1241 if (item >= 0)
1243 if (collection->buttons_pressed)
1245 gtk_grab_remove(widget);
1246 collection->buttons_pressed = 0;
1248 collection_unselect_item(collection, item);
1249 gtk_signal_emit(GTK_OBJECT(collection),
1250 collection_signals[OPEN_ITEM],
1251 collection->items[item].data,
1252 item);
1255 else if (event->type == GDK_BUTTON_PRESS)
1257 collection->may_drag = event->button != collection_menu_button;
1259 if (item >= 0)
1261 if (action == 1)
1263 if (!collection->items[item].selected)
1265 collection_select_item(collection,
1266 item);
1267 collection_clear_except(collection,
1268 item);
1271 else
1272 collection_toggle_item(collection, item);
1274 else if (action == 1)
1275 collection_clear_selection(collection);
1278 current_event_time = stacked_time;
1279 return FALSE;
1282 static gint collection_button_release(GtkWidget *widget,
1283 GdkEventButton *event)
1285 Collection *collection;
1286 int top, bottom;
1287 int row, last_row;
1288 int w, h;
1289 int col, start_col, last_col;
1290 int scroll;
1291 int item;
1292 guint stacked_time;
1293 int button;
1295 g_return_val_if_fail(widget != NULL, FALSE);
1296 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1297 g_return_val_if_fail(event != NULL, FALSE);
1299 collection = COLLECTION(widget);
1300 button = event->button;
1302 scroll = collection->vadj->value;
1304 if (event->button > 3 || event->button == collection_menu_button)
1305 return FALSE;
1306 if (collection->buttons_pressed == 0)
1307 return FALSE;
1308 if (--collection->buttons_pressed == 0)
1309 gtk_grab_remove(widget);
1310 else
1311 return FALSE; /* Wait until ALL buttons are up */
1313 if (!collection->lasso_box)
1315 int item = collection->item_clicked;
1317 if (collection_single_click && item > -1
1318 && item < collection->number_of_items
1319 && (event->state & GDK_CONTROL_MASK) == 0)
1321 int dx = event->x - collection->drag_box_x[0];
1322 int dy = event->y - collection->drag_box_y[0];
1324 if (ABS(dx) + ABS(dy) > 9)
1325 return FALSE;
1327 collection_unselect_item(collection, item);
1328 gtk_signal_emit(GTK_OBJECT(collection),
1329 collection_signals[OPEN_ITEM],
1330 collection->items[item].data,
1331 item);
1334 return FALSE;
1337 abort_lasso(collection);
1339 w = collection->item_width;
1340 h = collection->item_height;
1342 top = collection->drag_box_y[0] + scroll;
1343 bottom = collection->drag_box_y[1] + scroll;
1344 if (top > bottom)
1346 int tmp;
1347 tmp = top;
1348 top = bottom;
1349 bottom = tmp;
1351 top += h / 4;
1352 bottom -= h / 4;
1354 row = MAX(top / h, 0);
1355 last_row = bottom / h;
1357 top = collection->drag_box_x[0]; /* (left) */
1358 bottom = collection->drag_box_x[1];
1359 if (top > bottom)
1361 int tmp;
1362 tmp = top;
1363 top = bottom;
1364 bottom = tmp;
1366 top += w / 4;
1367 bottom -= w / 4;
1368 start_col = MAX(top / w, 0);
1369 last_col = bottom / w;
1370 if (last_col >= collection->columns)
1371 last_col = collection->columns - 1;
1373 stacked_time = current_event_time;
1374 current_event_time = event->time;
1376 while (row <= last_row)
1378 col = start_col;
1379 item = row * collection->columns + col;
1380 while (col <= last_col)
1382 if (item >= collection->number_of_items)
1384 current_event_time = stacked_time;
1385 return FALSE;
1388 if (button == 1)
1389 collection_select_item(collection, item);
1390 else
1391 collection_toggle_item(collection, item);
1392 col++;
1393 item++;
1395 row++;
1398 current_event_time = stacked_time;
1400 return FALSE;
1403 static gint collection_motion_notify(GtkWidget *widget,
1404 GdkEventMotion *event)
1406 Collection *collection;
1407 int x, y;
1408 guint stacked_time;
1410 g_return_val_if_fail(widget != NULL, FALSE);
1411 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1412 g_return_val_if_fail(event != NULL, FALSE);
1414 collection = COLLECTION(widget);
1416 if (collection->buttons_pressed == 0)
1417 return FALSE;
1419 stacked_time = current_event_time;
1420 current_event_time = event->time;
1422 if (event->window != widget->window)
1423 gdk_window_get_pointer(widget->window, &x, &y, NULL);
1424 else
1426 x = event->x;
1427 y = event->y;
1430 if (collection->lasso_box)
1432 remove_lasso_box(collection);
1433 collection->drag_box_x[1] = x;
1434 collection->drag_box_y[1] = y;
1435 add_lasso_box(collection);
1437 else if (collection->may_drag)
1439 int dx = x - collection->drag_box_x[0];
1440 int dy = y - collection->drag_box_y[0];
1442 if (abs(dx) > 9 || abs(dy) > 9)
1444 int row, col, item;
1445 int scroll = collection->vadj->value;
1447 collection->may_drag = FALSE;
1449 col = collection->drag_box_x[0]
1450 / collection->item_width;
1451 row = (collection->drag_box_y[0] + scroll)
1452 / collection->item_height;
1453 item = item_at_row_col(collection, row, col);
1455 if (item != -1 && collection->test_point(collection,
1456 collection->drag_box_x[0] -
1457 col * collection->item_width,
1458 collection->drag_box_y[0]
1459 - row * collection->item_height
1460 + scroll,
1461 &collection->items[item],
1462 collection->item_width,
1463 collection->item_height,
1464 collection->cb_user_data))
1466 collection->buttons_pressed = 0;
1467 gtk_grab_remove(widget);
1468 collection_select_item(collection, item);
1469 gtk_signal_emit(GTK_OBJECT(collection),
1470 collection_signals[DRAG_SELECTION],
1471 event,
1472 collection->number_selected);
1474 else
1476 collection->drag_box_x[1] = x;
1477 collection->drag_box_y[1] = y;
1478 collection_set_autoscroll(collection, TRUE);
1479 add_lasso_box(collection);
1484 current_event_time = stacked_time;
1485 return FALSE;
1488 static void add_lasso_box(Collection *collection)
1490 g_return_if_fail(collection != NULL);
1491 g_return_if_fail(IS_COLLECTION(collection));
1492 g_return_if_fail(collection->lasso_box == FALSE);
1494 collection->lasso_box = TRUE;
1495 draw_lasso_box(collection);
1498 static void draw_lasso_box(Collection *collection)
1500 GtkWidget *widget;
1501 int x, y, width, height;
1503 widget = GTK_WIDGET(collection);
1505 x = MIN(collection->drag_box_x[0], collection->drag_box_x[1]);
1506 y = MIN(collection->drag_box_y[0], collection->drag_box_y[1]);
1507 width = abs(collection->drag_box_x[1] - collection->drag_box_x[0]);
1508 height = abs(collection->drag_box_y[1] - collection->drag_box_y[0]);
1510 gdk_draw_rectangle(widget->window, collection->xor_gc, FALSE,
1511 x, y, width, height);
1514 static void abort_lasso(Collection *collection)
1516 if (collection->lasso_box)
1518 remove_lasso_box(collection);
1519 collection_set_autoscroll(collection, FALSE);
1523 static void remove_lasso_box(Collection *collection)
1525 g_return_if_fail(collection != NULL);
1526 g_return_if_fail(IS_COLLECTION(collection));
1527 g_return_if_fail(collection->lasso_box == TRUE);
1529 draw_lasso_box(collection);
1531 collection->lasso_box = FALSE;
1533 return;
1536 /* Convert a row,col address to an item number, or -1 if none */
1537 static int item_at_row_col(Collection *collection, int row, int col)
1539 int item;
1541 if (row < 0 || col < 0 || col >= collection->columns)
1542 return -1;
1544 item = col + row * collection->columns;
1546 if (item >= collection->number_of_items)
1547 return -1;
1548 return item;
1551 /* Make sure that 'item' is fully visible (vertically), scrolling if not. */
1552 static void scroll_to_show(Collection *collection, int item)
1554 int first, last, row;
1556 g_return_if_fail(collection != NULL);
1557 g_return_if_fail(IS_COLLECTION(collection));
1559 row = item / collection->columns;
1560 get_visible_limits(collection, &first, &last);
1562 if (row <= first)
1564 gtk_adjustment_set_value(collection->vadj,
1565 row * collection->item_height);
1567 else if (row >= last)
1569 GtkWidget *widget = (GtkWidget *) collection;
1570 int height;
1572 if (GTK_WIDGET_REALIZED(widget))
1574 gdk_window_get_size(widget->window, NULL, &height);
1575 gtk_adjustment_set_value(collection->vadj,
1576 (row + 1) * collection->item_height - height);
1581 /* Return the first and last rows which are [partly] visible. Does not
1582 * ensure that the rows actually exist (contain items).
1584 static void get_visible_limits(Collection *collection, int *first, int *last)
1586 GtkWidget *widget = (GtkWidget *) collection;
1587 int scroll, height;
1589 g_return_if_fail(collection != NULL);
1590 g_return_if_fail(IS_COLLECTION(collection));
1591 g_return_if_fail(first != NULL && last != NULL);
1593 if (!GTK_WIDGET_REALIZED(widget))
1595 *first = 0;
1596 *last = 0;
1598 else
1600 scroll = collection->vadj->value;
1601 gdk_window_get_size(widget->window, NULL, &height);
1603 *first = MAX(scroll / collection->item_height, 0);
1604 *last = (scroll + height - 1) /collection->item_height;
1606 if (*last < *first)
1607 *last = *first;
1611 /* Unselect all items except number item (-1 to unselect everything) */
1612 static void collection_clear_except(Collection *collection, gint exception)
1614 GtkWidget *widget;
1615 GdkRectangle area;
1616 int item = 0;
1617 int scroll;
1618 int end; /* Selected items to end up with */
1620 widget = GTK_WIDGET(collection);
1621 scroll = collection->vadj->value;
1623 end = exception >= 0 && exception < collection->number_of_items
1624 ? collection->items[exception].selected != 0 : 0;
1626 area.width = collection->item_width;
1627 area.height = collection->item_height;
1629 if (collection->number_selected == 0)
1630 return;
1632 while (collection->number_selected > end)
1634 while (item == exception || !collection->items[item].selected)
1635 item++;
1637 area.x = (item % collection->columns) * area.width;
1638 area.y = (item / collection->columns) * area.height
1639 - scroll;
1641 collection->items[item++].selected = FALSE;
1642 clear_area(collection, &area);
1643 collection_paint(collection, &area);
1645 collection->number_selected--;
1648 if (end == 0)
1649 gtk_signal_emit(GTK_OBJECT(collection),
1650 collection_signals[LOSE_SELECTION],
1651 current_event_time);
1654 /* Cancel the current wink effect. */
1655 static void cancel_wink(Collection *collection)
1657 gint item;
1659 g_return_if_fail(collection != NULL);
1660 g_return_if_fail(IS_COLLECTION(collection));
1661 g_return_if_fail(collection->wink_item != -1);
1663 item = collection->wink_item;
1665 collection->wink_item = -1;
1666 gtk_timeout_remove(collection->wink_timeout);
1668 collection_draw_item(collection, item, TRUE);
1671 static gboolean cancel_wink_timeout(Collection *collection)
1673 gint item;
1675 g_return_val_if_fail(collection != NULL, FALSE);
1676 g_return_val_if_fail(IS_COLLECTION(collection), FALSE);
1677 g_return_val_if_fail(collection->wink_item != -1, FALSE);
1679 item = collection->wink_item;
1681 collection->wink_item = -1;
1683 collection_draw_item(collection, item, TRUE);
1685 return FALSE;
1688 static gint focus_in(GtkWidget *widget, GdkEventFocus *event)
1690 g_return_val_if_fail(widget != NULL, FALSE);
1691 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1692 g_return_val_if_fail(event != NULL, FALSE);
1694 GTK_WIDGET_SET_FLAGS(widget, GTK_HAS_FOCUS);
1695 gtk_widget_draw_focus(widget);
1697 return FALSE;
1700 static gint focus_out(GtkWidget *widget, GdkEventFocus *event)
1702 g_return_val_if_fail(widget != NULL, FALSE);
1703 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1704 g_return_val_if_fail(event != NULL, FALSE);
1706 GTK_WIDGET_UNSET_FLAGS(widget, GTK_HAS_FOCUS);
1707 gtk_widget_draw_focus(widget);
1709 return FALSE;
1712 /* This is called frequently while auto_scroll is on.
1713 * Checks the pointer position and scrolls the window if it's
1714 * near the top or bottom.
1716 static gboolean as_timeout(Collection *collection)
1718 GdkWindow *window = GTK_WIDGET(collection)->window;
1719 gint x, y, w, h;
1720 GdkModifierType mask;
1721 int diff = 0;
1723 gdk_window_get_pointer(window, &x, &y, &mask);
1724 gdk_window_get_size(window, &w, &h);
1726 if ((x < 0 || x > w || y < 0 || y > h) && !collection->lasso_box)
1728 collection->auto_scroll = -1;
1729 return FALSE; /* Out of window - stop */
1732 if (y < 20)
1733 diff = y - 20;
1734 else if (y > h - 20)
1735 diff = 20 + y - h;
1737 if (diff)
1738 diff_vpos(collection, diff);
1740 return TRUE;
1743 /* Functions for managing collections */
1745 /* Remove all objects from the collection */
1746 void collection_clear(Collection *collection)
1748 int prev_selected;
1750 g_return_if_fail(IS_COLLECTION(collection));
1752 if (collection->number_of_items == 0)
1753 return;
1755 if (collection->wink_item != -1)
1757 collection->wink_item = -1;
1758 gtk_timeout_remove(collection->wink_timeout);
1761 collection_set_cursor_item(collection,
1762 collection->cursor_item == -1 ? -1: 0);
1763 collection->cursor_item_old = -1;
1764 prev_selected = collection->number_selected;
1765 collection->number_of_items = collection->number_selected = 0;
1767 resize_arrays(collection, MINIMUM_ITEMS);
1769 collection->paint_level = PAINT_CLEAR;
1771 gtk_widget_queue_clear(GTK_WIDGET(collection));
1773 if (prev_selected && collection->number_selected == 0)
1774 gtk_signal_emit(GTK_OBJECT(collection),
1775 collection_signals[LOSE_SELECTION],
1776 current_event_time);
1779 /* Inserts a new item at the end. The new item is unselected, and its
1780 * number is returned.
1782 gint collection_insert(Collection *collection, gpointer data)
1784 int item;
1786 g_return_val_if_fail(IS_COLLECTION(collection), -1);
1788 item = collection->number_of_items;
1790 if (item >= collection->array_size)
1791 resize_arrays(collection, item + (item >> 1));
1793 collection->items[item].data = data;
1794 collection->items[item].selected = FALSE;
1796 collection->number_of_items++;
1798 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection)))
1800 set_vadjustment(collection);
1801 collection_draw_item(collection,
1802 collection->number_of_items - 1,
1803 FALSE);
1806 return item;
1809 /* Unselect an item by number */
1810 void collection_unselect_item(Collection *collection, gint item)
1812 g_return_if_fail(collection != NULL);
1813 g_return_if_fail(IS_COLLECTION(collection));
1814 g_return_if_fail(item >= 0 && item < collection->number_of_items);
1816 if (collection->items[item].selected)
1818 collection->items[item].selected = FALSE;
1819 collection_draw_item(collection, item, TRUE);
1821 if (--collection->number_selected == 0)
1822 gtk_signal_emit(GTK_OBJECT(collection),
1823 collection_signals[LOSE_SELECTION],
1824 current_event_time);
1828 /* Select an item by number */
1829 void collection_select_item(Collection *collection, gint item)
1831 g_return_if_fail(collection != NULL);
1832 g_return_if_fail(IS_COLLECTION(collection));
1833 g_return_if_fail(item >= 0 && item < collection->number_of_items);
1835 if (collection->items[item].selected)
1836 return; /* Already selected */
1838 collection->items[item].selected = TRUE;
1839 collection_draw_item(collection, item, TRUE);
1841 if (collection->number_selected++ == 0)
1842 gtk_signal_emit(GTK_OBJECT(collection),
1843 collection_signals[GAIN_SELECTION],
1844 current_event_time);
1847 /* Toggle the selected state of an item (by number) */
1848 void collection_toggle_item(Collection *collection, gint item)
1850 g_return_if_fail(collection != NULL);
1851 g_return_if_fail(IS_COLLECTION(collection));
1852 g_return_if_fail(item >= 0 && item < collection->number_of_items);
1854 if (collection->items[item].selected)
1856 collection->items[item].selected = FALSE;
1857 if (--collection->number_selected == 0)
1858 gtk_signal_emit(GTK_OBJECT(collection),
1859 collection_signals[LOSE_SELECTION],
1860 current_event_time);
1862 else
1864 collection->items[item].selected = TRUE;
1865 if (collection->number_selected++ == 0)
1866 gtk_signal_emit(GTK_OBJECT(collection),
1867 collection_signals[GAIN_SELECTION],
1868 current_event_time);
1870 collection_draw_item(collection, item, TRUE);
1873 /* Select all items in the collection */
1874 void collection_select_all(Collection *collection)
1876 GtkWidget *widget;
1877 GdkRectangle area;
1878 int item = 0;
1879 int scroll;
1881 g_return_if_fail(collection != NULL);
1882 g_return_if_fail(IS_COLLECTION(collection));
1884 widget = GTK_WIDGET(collection);
1885 scroll = collection->vadj->value;
1887 area.width = collection->item_width;
1888 area.height = collection->item_height;
1890 if (collection->number_selected == collection->number_of_items)
1891 return; /* Nothing to do */
1893 while (collection->number_selected < collection->number_of_items)
1895 while (collection->items[item].selected)
1896 item++;
1898 area.x = (item % collection->columns) * area.width;
1899 area.y = (item / collection->columns) * area.height
1900 - scroll;
1902 collection->items[item++].selected = TRUE;
1903 clear_area(collection, &area);
1904 collection_paint(collection, &area);
1906 collection->number_selected++;
1909 gtk_signal_emit(GTK_OBJECT(collection),
1910 collection_signals[GAIN_SELECTION],
1911 current_event_time);
1914 /* Unselect all items in the collection */
1915 void collection_clear_selection(Collection *collection)
1917 g_return_if_fail(collection != NULL);
1918 g_return_if_fail(IS_COLLECTION(collection));
1920 collection_clear_except(collection, -1);
1923 /* Force a redraw of the specified item, if it is visible */
1924 void collection_draw_item(Collection *collection, gint item, gboolean blank)
1926 int height;
1927 GdkRectangle area;
1928 GtkWidget *widget;
1929 int row, col;
1930 int scroll;
1931 int area_y, area_height; /* NOT shorts! */
1933 g_return_if_fail(collection != NULL);
1934 g_return_if_fail(IS_COLLECTION(collection));
1935 g_return_if_fail(item >= 0 &&
1936 (item == 0 || item < collection->number_of_items));
1938 widget = GTK_WIDGET(collection);
1939 if (!GTK_WIDGET_REALIZED(widget))
1940 return;
1942 col = item % collection->columns;
1943 row = item / collection->columns;
1944 scroll = collection->vadj->value; /* (round to int) */
1946 area.x = col * collection->item_width;
1947 area_y = row * collection->item_height - scroll;
1948 area.width = collection->item_width;
1949 area_height = collection->item_height;
1951 if (area_y + area_height < 0)
1952 return;
1954 gdk_window_get_size(widget->window, NULL, &height);
1956 if (area_y > height)
1957 return;
1959 area.y = area_y;
1960 area.height = area_height;
1962 if (blank || collection->lasso_box)
1963 clear_area(collection, &area);
1965 draw_one_item(collection, item, &area);
1967 if (collection->lasso_box)
1969 gdk_gc_set_clip_rectangle(collection->xor_gc, &area);
1970 draw_lasso_box(collection);
1971 gdk_gc_set_clip_rectangle(collection->xor_gc, NULL);
1975 void collection_set_item_size(Collection *collection, int width, int height)
1977 GtkWidget *widget;
1979 g_return_if_fail(collection != NULL);
1980 g_return_if_fail(IS_COLLECTION(collection));
1981 g_return_if_fail(width > 4 && height > 4);
1983 if (collection->item_width == width &&
1984 collection->item_height == height)
1985 return;
1987 widget = GTK_WIDGET(collection);
1989 collection->item_width = width;
1990 collection->item_height = height;
1992 if (GTK_WIDGET_REALIZED(widget))
1994 int window_width;
1996 collection->paint_level = PAINT_CLEAR;
1997 gdk_window_get_size(widget->window, &window_width, NULL);
1998 collection->columns = MAX(window_width / collection->item_width,
2001 set_vadjustment(collection);
2002 if (collection->cursor_item != -1)
2003 scroll_to_show(collection, collection->cursor_item);
2004 gtk_widget_queue_draw(widget);
2008 /* Cursor is positioned on item with the same data as before the sort.
2009 * Same for the wink item.
2011 void collection_qsort(Collection *collection,
2012 int (*compar)(const void *, const void *))
2014 int cursor, wink, items;
2015 gpointer cursor_data = NULL;
2016 gpointer wink_data = NULL;
2018 g_return_if_fail(collection != NULL);
2019 g_return_if_fail(IS_COLLECTION(collection));
2020 g_return_if_fail(compar != NULL);
2022 items = collection->number_of_items;
2024 wink = collection->wink_item;
2025 if (wink >= 0 && wink < items)
2026 wink_data = collection->items[wink].data;
2027 else
2028 wink = -1;
2030 cursor = collection->cursor_item;
2031 if (cursor >= 0 && cursor < items)
2032 cursor_data = collection->items[cursor].data;
2033 else
2034 cursor = -1;
2036 if (collection->wink_item != -1)
2038 collection->wink_item = -1;
2039 gtk_timeout_remove(collection->wink_timeout);
2042 qsort(collection->items, items, sizeof(collection->items[0]), compar);
2044 if (cursor > -1 || wink > -1)
2046 int item;
2048 for (item = 0; item < items; item++)
2050 if (collection->items[item].data == cursor_data)
2051 collection_set_cursor_item(collection, item);
2052 if (collection->items[item].data == wink_data)
2053 collection_wink_item(collection, item);
2057 collection->paint_level = PAINT_CLEAR;
2059 gtk_widget_queue_draw(GTK_WIDGET(collection));
2062 /* Find an item in an unsorted collection.
2063 * Returns the item number, or -1 if not found.
2065 int collection_find_item(Collection *collection, gpointer data,
2066 int (*compar)(const void *, const void *))
2068 int i;
2070 g_return_val_if_fail(collection != NULL, -1);
2071 g_return_val_if_fail(IS_COLLECTION(collection), -1);
2072 g_return_val_if_fail(compar != NULL, -1);
2074 for (i = 0; i < collection->number_of_items; i++)
2075 if (compar(&collection->items[i].data, &data) == 0)
2076 return i;
2078 return -1;
2081 /* The collection may be in either normal mode or panel mode.
2082 * In panel mode:
2083 * - a single click calls open_item
2084 * - items are never selected
2085 * - lasso boxes are disabled
2087 void collection_set_panel(Collection *collection, gboolean panel)
2089 g_return_if_fail(collection != NULL);
2090 g_return_if_fail(IS_COLLECTION(collection));
2092 collection->panel = panel == TRUE;
2094 if (collection->panel)
2096 collection_clear_selection(collection);
2097 abort_lasso(collection);
2101 /* Return the number of the item under the point (x,y), or -1 for none.
2102 * This may call your test_point callback. The point is relative to the
2103 * collection's origin.
2105 int collection_get_item(Collection *collection, int x, int y)
2107 int scroll;
2108 int row, col;
2109 int item;
2111 g_return_val_if_fail(collection != NULL, -1);
2113 scroll = collection->vadj->value;
2114 col = x / collection->item_width;
2115 row = (y + scroll) / collection->item_height;
2117 if (col < 0 || row < 0 || col >= collection->columns)
2118 return -1;
2120 item = col + row * collection->columns;
2121 if (item >= collection->number_of_items
2123 !collection->test_point(collection,
2124 x - col * collection->item_width,
2125 y - row * collection->item_height
2126 + scroll,
2127 &collection->items[item],
2128 collection->item_width,
2129 collection->item_height,
2130 collection->cb_user_data))
2132 return -1;
2135 return item;
2138 /* Set the cursor/highlight over the given item. Passing -1
2139 * hides the cursor. As a special case, you may set the cursor item
2140 * to zero when there are no items.
2142 void collection_set_cursor_item(Collection *collection, gint item)
2144 int old_item;
2146 g_return_if_fail(collection != NULL);
2147 g_return_if_fail(IS_COLLECTION(collection));
2148 g_return_if_fail(item >= -1 &&
2149 (item < collection->number_of_items || item == 0));
2151 old_item = collection->cursor_item;
2153 if (old_item == item)
2154 return;
2156 collection->cursor_item = item;
2158 if (old_item != -1)
2159 collection_draw_item(collection, old_item, TRUE);
2161 if (item != -1)
2163 collection_draw_item(collection, item, TRUE);
2164 if (collection->auto_scroll == -1)
2165 scroll_to_show(collection, item);
2167 else if (old_item != -1)
2168 collection->cursor_item_old = old_item;
2171 /* Briefly highlight an item to draw the user's attention to it.
2172 * -1 cancels the effect, as does deleting items, sorting the collection
2173 * or starting a new wink effect.
2174 * Otherwise, the effect will cancel itself after a short pause.
2175 * */
2176 void collection_wink_item(Collection *collection, gint item)
2178 g_return_if_fail(collection != NULL);
2179 g_return_if_fail(IS_COLLECTION(collection));
2180 g_return_if_fail(item >= -1 && item < collection->number_of_items);
2182 if (collection->wink_item != -1)
2183 cancel_wink(collection);
2184 if (item == -1)
2185 return;
2187 collection->cursor_item_old = collection->wink_item = item;
2188 collection->wink_timeout = gtk_timeout_add(300,
2189 (GtkFunction) cancel_wink_timeout,
2190 collection);
2191 collection_draw_item(collection, item, TRUE);
2192 scroll_to_show(collection, item);
2194 gdk_flush();
2197 /* Call test(item, data) on each item in the collection.
2198 * Remove all items for which it returns TRUE. test() should
2199 * free the data before returning TRUE. The collection is in an
2200 * inconsistant state during this call (ie, when test() is called).
2202 void collection_delete_if(Collection *collection,
2203 gboolean (*test)(gpointer item, gpointer data),
2204 gpointer data)
2206 int in, out = 0;
2207 int selected = 0;
2208 int cursor;
2210 g_return_if_fail(collection != NULL);
2211 g_return_if_fail(IS_COLLECTION(collection));
2212 g_return_if_fail(test != NULL);
2214 cursor = collection->cursor_item;
2216 for (in = 0; in < collection->number_of_items; in++)
2218 if (!test(collection->items[in].data, data))
2220 if (collection->items[in].selected)
2222 collection->items[out].selected = TRUE;
2223 selected++;
2225 else
2226 collection->items[out].selected = FALSE;
2228 collection->items[out++].data =
2229 collection->items[in].data;
2231 else if (cursor >= in)
2232 cursor--;
2235 if (in != out)
2237 collection->cursor_item = cursor;
2239 if (collection->wink_item != -1)
2241 collection->wink_item = -1;
2242 gtk_timeout_remove(collection->wink_timeout);
2245 collection->number_of_items = out;
2246 collection->number_selected = selected;
2247 resize_arrays(collection,
2248 MAX(collection->number_of_items, MINIMUM_ITEMS));
2250 collection->paint_level = PAINT_CLEAR;
2252 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection)))
2254 set_vadjustment(collection);
2255 gtk_widget_queue_draw(GTK_WIDGET(collection));
2260 /* Display a cross-hair pointer and the next time an item is clicked,
2261 * call the callback function. If the background is clicked or NULL
2262 * is passed as the callback then revert to normal operation.
2264 void collection_target(Collection *collection,
2265 CollectionTargetFunc callback,
2266 gpointer user_data)
2268 g_return_if_fail(collection != NULL);
2269 g_return_if_fail(IS_COLLECTION(collection));
2271 if (callback != collection->target_cb)
2272 gdk_window_set_cursor(GTK_WIDGET(collection)->window,
2273 callback ? crosshair : NULL);
2275 collection->target_cb = callback;
2276 collection->target_data = user_data;
2278 if (collection->cursor_item != -1)
2279 collection_draw_item(collection, collection->cursor_item,
2280 FALSE);
2283 /* Move the cursor by the given row and column offsets.
2284 * Moving by (0,0) can be used to simply make the cursor appear.
2286 void collection_move_cursor(Collection *collection, int drow, int dcol)
2288 int row, col, item;
2289 int first, last, total_rows;
2291 g_return_if_fail(collection != NULL);
2292 g_return_if_fail(IS_COLLECTION(collection));
2294 get_visible_limits(collection, &first, &last);
2296 item = collection->cursor_item;
2297 if (item == -1)
2299 item = MIN(collection->cursor_item_old,
2300 collection->number_of_items - 1);
2303 if (item == -1)
2305 col = 0;
2306 row = first;
2308 else
2310 row = item / collection->columns;
2311 col = item % collection->columns + dcol;
2313 if (row < first)
2314 row = first;
2315 else if (row > last)
2316 row = last;
2317 else
2318 row = MAX(row + drow, 0);
2321 total_rows = (collection->number_of_items + collection->columns - 1)
2322 / collection->columns;
2324 if (row >= total_rows - 1 && drow > 0)
2326 row = total_rows - 1;
2327 item = col + row * collection->columns;
2328 if (item >= collection->number_of_items)
2330 row--;
2331 scroll_to_show(collection, item);
2334 if (row < 0)
2335 row = 0;
2337 item = col + row * collection->columns;
2339 if (item >= 0 && item < collection->number_of_items)
2340 collection_set_cursor_item(collection, item);
2343 /* When autoscroll is on, a timer keeps track of the pointer position.
2344 * While it's near the top or bottom of the window, the window scrolls.
2346 * If the mouse buttons are released, or the pointer leaves the window,
2347 * auto_scroll is turned off.
2349 void collection_set_autoscroll(Collection *collection, gboolean auto_scroll)
2351 g_return_if_fail(collection != NULL);
2352 g_return_if_fail(IS_COLLECTION(collection));
2354 if (auto_scroll)
2356 if (collection->auto_scroll != -1)
2357 return; /* Already on! */
2359 collection->auto_scroll = gtk_timeout_add(50,
2360 (GtkFunction) as_timeout,
2361 collection);
2363 else
2365 if (collection->auto_scroll == -1)
2366 return; /* Already off! */
2368 gtk_timeout_remove(collection->auto_scroll);
2369 collection->auto_scroll = -1;