r243: Updated the version number and added features display.
[rox-filer.git] / ROX-Filer / src / collection.c
blob7c11dc20ce8e952730b4cfbf9f63be78022330a9
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;
334 object->items = g_malloc(sizeof(CollectionItem) * MINIMUM_ITEMS);
335 object->cursor_item = -1;
336 object->wink_item = -1;
337 object->array_size = MINIMUM_ITEMS;
338 object->draw_item = default_draw_item;
339 object->test_point = default_test_point;
341 object->buttons_pressed = 0;
342 object->may_drag = FALSE;
344 return;
347 GtkWidget* collection_new(GtkAdjustment *vadj)
349 if (vadj)
350 g_return_val_if_fail(GTK_IS_ADJUSTMENT(vadj), NULL);
352 return GTK_WIDGET(gtk_widget_new(collection_get_type(),
353 "vadjustment", vadj,
354 NULL));
357 void collection_set_functions(Collection *collection,
358 CollectionDrawFunc draw_item,
359 CollectionTestFunc test_point)
361 GtkWidget *widget;
363 g_return_if_fail(collection != NULL);
364 g_return_if_fail(IS_COLLECTION(collection));
366 widget = GTK_WIDGET(collection);
368 if (!draw_item)
369 draw_item = default_draw_item;
370 if (!test_point)
371 test_point = default_test_point;
373 collection->draw_item = draw_item;
374 collection->test_point = test_point;
376 if (GTK_WIDGET_REALIZED(widget))
378 collection->paint_level = PAINT_CLEAR;
379 gtk_widget_queue_clear(widget);
383 /* After this we are unusable, but our data (if any) is still hanging around.
384 * It will be freed later with finalize.
386 static void collection_destroy(GtkObject *object)
388 Collection *collection;
390 g_return_if_fail(object != NULL);
391 g_return_if_fail(IS_COLLECTION(object));
393 collection = COLLECTION(object);
395 if (collection->wink_item != -1)
397 collection->wink_item = -1;
398 gtk_timeout_remove(collection->wink_timeout);
401 gtk_signal_disconnect_by_data(GTK_OBJECT(collection->vadj),
402 collection);
404 if (GTK_OBJECT_CLASS(parent_class)->destroy)
405 (*GTK_OBJECT_CLASS(parent_class)->destroy)(object);
408 /* This is the last thing that happens to us. Free all data. */
409 static void collection_finalize(GtkObject *object)
411 Collection *collection;
413 collection = COLLECTION(object);
415 if (collection->vadj)
417 gtk_object_unref(GTK_OBJECT(collection->vadj));
420 g_free(collection->items);
423 static void collection_realize(GtkWidget *widget)
425 Collection *collection;
426 GdkWindowAttr attributes;
427 gint attributes_mask;
428 GdkGCValues xor_values;
430 g_return_if_fail(widget != NULL);
431 g_return_if_fail(IS_COLLECTION(widget));
432 g_return_if_fail(widget->parent != NULL);
434 GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);
435 collection = COLLECTION(widget);
437 attributes.x = widget->allocation.x;
438 attributes.y = widget->allocation.y;
439 attributes.width = widget->allocation.width;
440 attributes.height = widget->allocation.height;
441 attributes.wclass = GDK_INPUT_OUTPUT;
442 attributes.window_type = GDK_WINDOW_CHILD;
443 attributes.event_mask = gtk_widget_get_events(widget) |
444 GDK_EXPOSURE_MASK |
445 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
446 GDK_BUTTON1_MOTION_MASK | GDK_BUTTON2_MOTION_MASK |
447 GDK_BUTTON3_MOTION_MASK;
448 attributes.visual = gtk_widget_get_visual(widget);
449 attributes.colormap = gtk_widget_get_colormap(widget);
451 attributes_mask = GDK_WA_X | GDK_WA_Y |
452 GDK_WA_VISUAL | GDK_WA_COLORMAP;
453 widget->window = gdk_window_new(widget->parent->window,
454 &attributes, attributes_mask);
456 widget->style = gtk_style_attach(widget->style, widget->window);
458 gdk_window_set_user_data(widget->window, widget);
460 gdk_window_set_background(widget->window,
461 &widget->style->base[GTK_STATE_INSENSITIVE]);
463 /* Try to stop everything flickering horribly */
464 gdk_window_set_static_gravities(widget->window, TRUE);
466 set_vadjustment(collection);
468 xor_values.function = GDK_XOR;
469 xor_values.foreground.red = 0xffff;
470 xor_values.foreground.green = 0xffff;
471 xor_values.foreground.blue = 0xffff;
472 gdk_color_alloc(gtk_widget_get_colormap(widget),
473 &xor_values.foreground);
474 collection->xor_gc = gdk_gc_new_with_values(widget->window,
475 &xor_values,
476 GDK_GC_FOREGROUND
477 | GDK_GC_FUNCTION);
480 static void collection_size_request(GtkWidget *widget,
481 GtkRequisition *requisition)
483 requisition->width = MIN_WIDTH;
484 requisition->height = MIN_HEIGHT;
487 static void collection_size_allocate(GtkWidget *widget,
488 GtkAllocation *allocation)
490 Collection *collection;
491 int old_columns;
493 g_return_if_fail(widget != NULL);
494 g_return_if_fail(IS_COLLECTION(widget));
495 g_return_if_fail(allocation != NULL);
497 collection = COLLECTION(widget);
499 old_columns = collection->columns;
500 if (widget->allocation.x != allocation->x
501 || widget->allocation.y != allocation->y)
502 collection->paint_level = PAINT_CLEAR;
504 widget->allocation = *allocation;
506 collection->columns = allocation->width / collection->item_width;
507 if (collection->columns < 1)
508 collection->columns = 1;
510 if (GTK_WIDGET_REALIZED(widget))
512 gdk_window_move_resize(widget->window,
513 allocation->x, allocation->y,
514 allocation->width, allocation->height);
516 if (old_columns != collection->columns)
518 collection->paint_level = PAINT_CLEAR;
519 gtk_widget_queue_clear(widget);
522 set_vadjustment(collection);
524 if (collection->cursor_item != -1)
525 scroll_to_show(collection, collection->cursor_item);
529 static void collection_set_style(GtkWidget *widget,
530 GtkStyle *previous_style)
532 Collection *collection;
534 g_return_if_fail(IS_COLLECTION(widget));
536 collection = COLLECTION(widget);
538 collection->paint_level = PAINT_CLEAR;
540 if (parent_class->style_set)
541 (*parent_class->style_set)(widget, previous_style);
544 static gint collection_paint(Collection *collection,
545 GdkRectangle *area)
547 GdkRectangle whole, item_area;
548 GtkWidget *widget;
549 int row, col;
550 int item;
551 int scroll;
552 int start_row, last_row;
553 int start_col, last_col;
554 int phys_last_col;
555 GdkRectangle clip;
557 scroll = collection->vadj->value;
559 widget = GTK_WIDGET(collection);
561 if (collection->paint_level > PAINT_NORMAL || area == NULL)
563 guint width, height;
564 gdk_window_get_size(widget->window, &width, &height);
566 whole.x = 0;
567 whole.y = 0;
568 whole.width = width;
569 whole.height = height;
571 area = &whole;
573 if (collection->paint_level == PAINT_CLEAR
574 && !collection->lasso_box)
575 gdk_window_clear(widget->window);
577 collection->paint_level = PAINT_NORMAL;
580 /* Calculate the ranges to plot */
581 start_row = (area->y + scroll) / collection->item_height;
582 last_row = (area->y + area->height - 1 + scroll)
583 / collection->item_height;
584 row = start_row;
586 start_col = area->x / collection->item_width;
587 phys_last_col = (area->x + area->width - 1) / collection->item_width;
589 if (collection->lasso_box)
591 /* You can't be too careful with lasso boxes...
593 * clip gives the total area drawn over (this may be larger
594 * than the requested area). It's used to redraw the lasso
595 * box.
597 clip.x = start_col * collection->item_width;
598 clip.y = start_row * collection->item_height - scroll;
599 clip.width = (phys_last_col - start_col + 1)
600 * collection->item_width;
601 clip.height = (last_row - start_row + 1)
602 * collection->item_height;
604 gdk_window_clear_area(widget->window,
605 clip.x, clip.y, clip.width, clip.height);
608 if (start_col < collection->columns)
610 if (phys_last_col >= collection->columns)
611 last_col = collection->columns - 1;
612 else
613 last_col = phys_last_col;
615 col = start_col;
617 item = row * collection->columns + col;
618 item_area.width = collection->item_width;
619 item_area.height = collection->item_height;
621 while ((item == 0 || item < collection->number_of_items)
622 && row <= last_row)
624 item_area.x = col * collection->item_width;
625 item_area.y = row * collection->item_height - scroll;
627 draw_one_item(collection, item, &item_area);
628 col++;
630 if (col > last_col)
632 col = start_col;
633 row++;
634 item = row * collection->columns + col;
636 else
637 item++;
641 if (collection->lasso_box)
643 gdk_gc_set_clip_rectangle(collection->xor_gc, &clip);
644 draw_lasso_box(collection);
645 gdk_gc_set_clip_rectangle(collection->xor_gc, NULL);
648 return FALSE;
651 static void default_draw_item( GtkWidget *widget,
652 CollectionItem *item,
653 GdkRectangle *area)
655 gdk_draw_arc(widget->window,
656 item->selected ? widget->style->white_gc
657 : widget->style->black_gc,
658 TRUE,
659 area->x, area->y,
660 area->width, area->height,
661 0, 360 * 64);
665 static gboolean default_test_point(Collection *collection,
666 int point_x, int point_y,
667 CollectionItem *item,
668 int width, int height)
670 float f_x, f_y;
672 /* Convert to point in unit circle */
673 f_x = ((float) point_x / width) - 0.5;
674 f_y = ((float) point_y / height) - 0.5;
676 return (f_x * f_x) + (f_y * f_y) <= .25;
679 static void collection_set_arg( GtkObject *object,
680 GtkArg *arg,
681 guint arg_id)
683 Collection *collection;
685 collection = COLLECTION(object);
687 switch (arg_id)
689 case ARG_VADJUSTMENT:
690 collection_set_adjustment(collection,
691 GTK_VALUE_POINTER(*arg));
692 break;
693 default:
694 break;
698 static void collection_set_adjustment( Collection *collection,
699 GtkAdjustment *vadj)
701 if (vadj)
702 g_return_if_fail (GTK_IS_ADJUSTMENT (vadj));
703 else
704 vadj = GTK_ADJUSTMENT(gtk_adjustment_new(0.0,
705 0.0, 0.0,
706 0.0, 0.0, 0.0));
707 if (collection->vadj && (collection->vadj != vadj))
709 gtk_signal_disconnect_by_data(GTK_OBJECT(collection->vadj),
710 collection);
711 gtk_object_unref(GTK_OBJECT(collection->vadj));
714 if (collection->vadj != vadj)
716 collection->vadj = vadj;
717 gtk_object_ref(GTK_OBJECT(collection->vadj));
718 gtk_object_sink(GTK_OBJECT(collection->vadj));
720 gtk_signal_connect(GTK_OBJECT(collection->vadj),
721 "changed",
722 (GtkSignalFunc) collection_adjustment,
723 collection);
724 gtk_signal_connect(GTK_OBJECT(collection->vadj),
725 "value_changed",
726 (GtkSignalFunc) collection_adjustment,
727 collection);
728 gtk_signal_connect(GTK_OBJECT(collection->vadj),
729 "disconnect",
730 (GtkSignalFunc) collection_disconnect,
731 collection);
732 collection_adjustment(vadj, collection);
736 static void collection_get_arg( GtkObject *object,
737 GtkArg *arg,
738 guint arg_id)
740 Collection *collection;
742 collection = COLLECTION(object);
744 switch (arg_id)
746 case ARG_VADJUSTMENT:
747 GTK_VALUE_POINTER(*arg) = collection->vadj;
748 break;
749 default:
750 arg->type = GTK_TYPE_INVALID;
751 break;
755 /* Something about the adjustment has changed */
756 static void collection_adjustment(GtkAdjustment *adjustment,
757 Collection *collection)
759 gint diff;
761 g_return_if_fail(adjustment != NULL);
762 g_return_if_fail(GTK_IS_ADJUSTMENT(adjustment));
763 g_return_if_fail(collection != NULL);
764 g_return_if_fail(IS_COLLECTION(collection));
766 diff = ((gint) adjustment->value) - collection->last_scroll;
768 if (diff)
770 collection->last_scroll = adjustment->value;
772 scroll_by(collection, diff);
776 static void collection_disconnect(GtkAdjustment *adjustment,
777 Collection *collection)
779 g_return_if_fail(adjustment != NULL);
780 g_return_if_fail(GTK_IS_ADJUSTMENT(adjustment));
781 g_return_if_fail(collection != NULL);
782 g_return_if_fail(IS_COLLECTION(collection));
784 collection_set_adjustment(collection, NULL);
787 static void set_vadjustment(Collection *collection)
789 GtkWidget *widget;
790 guint height;
791 int cols, rows;
793 widget = GTK_WIDGET(collection);
795 if (!GTK_WIDGET_REALIZED(widget))
796 return;
798 gdk_window_get_size(widget->window, NULL, &height);
799 cols = collection->columns;
800 rows = (collection->number_of_items + cols - 1) / cols;
802 collection->vadj->lower = 0.0;
803 collection->vadj->upper = collection->item_height * rows;
805 collection->vadj->step_increment =
806 MIN(collection->vadj->upper, collection->item_height / 4);
808 collection->vadj->page_increment =
809 MIN(collection->vadj->upper,
810 height - 5.0);
812 collection->vadj->page_size = MIN(collection->vadj->upper, height);
814 collection->vadj->value = MIN(collection->vadj->value,
815 collection->vadj->upper - collection->vadj->page_size);
817 collection->vadj->value = MAX(collection->vadj->value, 0.0);
819 gtk_signal_emit_by_name(GTK_OBJECT(collection->vadj), "changed");
822 static void collection_draw(GtkWidget *widget, GdkRectangle *area)
824 Collection *collection;
826 g_return_if_fail(widget != NULL);
827 g_return_if_fail(IS_COLLECTION(widget));
828 g_return_if_fail(area != NULL); /* Not actually used */
830 collection = COLLECTION(widget);
832 if (collection->paint_level > PAINT_NORMAL)
833 collection_paint(collection, area);
836 static gint collection_expose(GtkWidget *widget, GdkEventExpose *event)
838 g_return_val_if_fail(widget != NULL, FALSE);
839 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
840 g_return_val_if_fail(event != NULL, FALSE);
842 collection_paint(COLLECTION(widget), &event->area);
844 return FALSE;
847 /* Positive makes the contents go move up the screen */
848 static void scroll_by(Collection *collection, gint diff)
850 GtkWidget *widget;
851 guint width, height;
852 guint from_y, to_y;
853 guint amount;
854 GdkRectangle new_area;
856 if (diff == 0)
857 return;
859 widget = GTK_WIDGET(collection);
861 if (collection->lasso_box)
862 remove_lasso_box(collection);
864 gdk_window_get_size(widget->window, &width, &height);
865 new_area.x = 0;
866 new_area.width = width;
868 if (diff > 0)
870 amount = diff;
871 from_y = amount;
872 to_y = 0;
873 new_area.y = height - amount;
875 else
877 amount = -diff;
878 from_y = 0;
879 to_y = amount;
880 new_area.y = 0;
883 new_area.height = amount;
885 if (amount < height)
887 gdk_draw_pixmap(widget->window,
888 widget->style->white_gc,
889 widget->window,
891 from_y,
893 to_y,
894 width,
895 height - amount);
896 /* We have to redraw everything because any pending
897 * expose events now contain invalid areas.
898 * Don't need to clear the area first though...
900 if (collection->paint_level < PAINT_OVERWRITE)
901 collection->paint_level = PAINT_OVERWRITE;
903 else
904 collection->paint_level = PAINT_CLEAR;
906 gdk_window_clear_area(widget->window,
907 0, new_area.y,
908 width, new_area.height);
909 collection_paint(collection, NULL);
912 static void resize_arrays(Collection *collection, guint new_size)
914 g_return_if_fail(collection != NULL);
915 g_return_if_fail(IS_COLLECTION(collection));
916 g_return_if_fail(new_size >= collection->number_of_items);
918 collection->items = g_realloc(collection->items,
919 sizeof(CollectionItem) * new_size);
920 collection->array_size = new_size;
923 static void return_pressed(Collection *collection)
925 int item = collection->cursor_item;
926 CollectionTargetFunc cb = collection->target_cb;
927 gpointer data = collection->target_data;
929 collection_target(collection, NULL, NULL);
930 if (item < 0 || item >= collection->number_of_items)
931 return;
933 if (cb)
935 cb(collection, item, data);
936 return;
939 gtk_signal_emit(GTK_OBJECT(collection),
940 collection_signals[OPEN_ITEM],
941 collection->items[item].data,
942 item);
945 static gint collection_key_press(GtkWidget *widget, GdkEventKey *event)
947 Collection *collection;
948 int item;
950 g_return_val_if_fail(widget != NULL, FALSE);
951 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
952 g_return_val_if_fail(event != NULL, FALSE);
954 collection = (Collection *) widget;
955 item = collection->cursor_item;
957 switch (event->keyval)
959 case GDK_Left:
960 collection_move_cursor(collection, 0, -1);
961 break;
962 case GDK_Right:
963 collection_move_cursor(collection, 0, 1);
964 break;
965 case GDK_Up:
966 collection_move_cursor(collection, -1, 0);
967 break;
968 case GDK_Down:
969 collection_move_cursor(collection, 1, 0);
970 break;
971 case GDK_Home:
972 collection_set_cursor_item(collection, 0);
973 break;
974 case GDK_End:
975 collection_set_cursor_item(collection,
976 MAX((gint) collection->number_of_items - 1, 0));
977 break;
978 case GDK_Page_Up:
979 collection_move_cursor(collection, -10, 0);
980 break;
981 case GDK_Page_Down:
982 collection_move_cursor(collection, 10, 0);
983 break;
984 case GDK_Return:
985 return_pressed(collection);
986 break;
987 case GDK_Escape:
988 if (!collection->target_cb)
989 return FALSE; /* Pass it on */
990 collection_target(collection, NULL, NULL);
991 break;
992 case ' ':
993 if (item >=0 && item < collection->number_of_items)
994 collection_toggle_item(collection, item);
995 break;
996 default:
997 return FALSE;
1000 return TRUE;
1003 static gint collection_button_press(GtkWidget *widget,
1004 GdkEventButton *event)
1006 Collection *collection;
1007 int row, col;
1008 int item;
1009 int action;
1010 int scroll;
1011 guint stacked_time;
1013 g_return_val_if_fail(widget != NULL, FALSE);
1014 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1015 g_return_val_if_fail(event != NULL, FALSE);
1017 collection = COLLECTION(widget);
1019 collection->item_clicked = -1;
1021 if (event->button > 3)
1023 int diff;
1025 /* Wheel mouse scrolling */
1026 if (event->button == 4)
1027 diff = -((signed int) collection->item_height) / 4;
1028 else if (event->button == 5)
1029 diff = collection->item_height / 4;
1030 else
1031 diff = 0;
1033 if (diff)
1035 int old_value = collection->vadj->value;
1036 int new_value = 0;
1037 gboolean box = collection->lasso_box;
1039 new_value = CLAMP(old_value + diff, 0.0,
1040 collection->vadj->upper
1041 - collection->vadj->page_size);
1042 diff = new_value - old_value;
1043 if (diff)
1045 if (box)
1047 remove_lasso_box(collection);
1048 collection->drag_box_y[0] -= diff;
1050 collection->vadj->value = new_value;
1051 gtk_signal_emit_by_name(
1052 GTK_OBJECT(collection->vadj),
1053 "changed");
1054 if (box)
1055 add_lasso_box(collection);
1058 return FALSE;
1061 if (collection->cursor_item != -1)
1062 collection_set_cursor_item(collection, -1);
1064 scroll = collection->vadj->value;
1066 if (event->type == GDK_BUTTON_PRESS &&
1067 event->button != collection_menu_button)
1069 if (collection->buttons_pressed++ == 0)
1070 gtk_grab_add(widget);
1071 else
1072 return FALSE; /* Ignore extra presses */
1075 if (event->state & GDK_CONTROL_MASK && !collection_single_click)
1076 action = 2;
1077 else
1078 action = event->button;
1080 /* Ignore all clicks while we are dragging a lasso box */
1081 if (collection->lasso_box)
1082 return TRUE;
1084 col = event->x / collection->item_width;
1085 row = (event->y + scroll) / collection->item_height;
1087 if (col < 0 || row < 0 || col >= collection->columns)
1088 item = -1;
1089 else
1091 item = col + row * collection->columns;
1092 if (item >= collection->number_of_items
1094 !collection->test_point(collection,
1095 event->x - col * collection->item_width,
1096 event->y - row * collection->item_height
1097 + scroll,
1098 &collection->items[item],
1099 collection->item_width,
1100 collection->item_height))
1102 item = -1;
1106 if (collection->target_cb)
1108 CollectionTargetFunc cb = collection->target_cb;
1109 gpointer data = collection->target_data;
1111 collection_target(collection, NULL, NULL);
1112 if (collection->buttons_pressed)
1114 gtk_grab_remove(widget);
1115 collection->buttons_pressed = 0;
1117 if (item > -1 && event->button != collection_menu_button)
1118 cb(collection, item, data);
1119 return TRUE;
1122 collection->drag_box_x[0] = event->x;
1123 collection->drag_box_y[0] = event->y;
1124 collection->item_clicked = item;
1126 stacked_time = current_event_time;
1127 current_event_time = event->time;
1129 if (event->button == collection_menu_button)
1131 gtk_signal_emit(GTK_OBJECT(collection),
1132 collection_signals[SHOW_MENU],
1133 event,
1134 item);
1136 else if (event->type == GDK_2BUTTON_PRESS && collection->panel)
1138 /* Do nothing */
1140 else if ((event->type == GDK_2BUTTON_PRESS && !collection_single_click)
1141 || collection->panel)
1143 if (item >= 0)
1145 if (collection->buttons_pressed)
1147 gtk_grab_remove(widget);
1148 collection->buttons_pressed = 0;
1150 collection_unselect_item(collection, item);
1151 gtk_signal_emit(GTK_OBJECT(collection),
1152 collection_signals[OPEN_ITEM],
1153 collection->items[item].data,
1154 item);
1157 else if (event->type == GDK_BUTTON_PRESS)
1159 collection->may_drag = event->button < collection_menu_button;
1161 if (item >= 0)
1163 if (action == 1)
1165 if (!collection->items[item].selected)
1167 collection_select_item(collection,
1168 item);
1169 collection_clear_except(collection,
1170 item);
1173 else
1174 collection_toggle_item(collection, item);
1176 else if (action == 1)
1177 collection_clear_selection(collection);
1180 current_event_time = stacked_time;
1181 return FALSE;
1184 static gint collection_button_release(GtkWidget *widget,
1185 GdkEventButton *event)
1187 Collection *collection;
1188 int top, bottom;
1189 int row, last_row;
1190 int w, h;
1191 int col, start_col, last_col;
1192 int scroll;
1193 int item;
1194 guint stacked_time;
1195 int button;
1197 g_return_val_if_fail(widget != NULL, FALSE);
1198 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1199 g_return_val_if_fail(event != NULL, FALSE);
1201 collection = COLLECTION(widget);
1202 button = event->button;
1204 scroll = collection->vadj->value;
1206 if (event->button > 3 || event->button == collection_menu_button)
1207 return FALSE;
1208 if (collection->buttons_pressed == 0)
1209 return FALSE;
1210 if (--collection->buttons_pressed == 0)
1211 gtk_grab_remove(widget);
1212 else
1213 return FALSE; /* Wait until ALL buttons are up */
1215 if (!collection->lasso_box)
1217 int item = collection->item_clicked;
1219 if (collection_single_click && item > -1
1220 && item < collection->number_of_items
1221 && (event->state & GDK_CONTROL_MASK) == 0)
1223 int dx = event->x - collection->drag_box_x[0];
1224 int dy = event->y - collection->drag_box_y[0];
1226 if (ABS(dx) + ABS(dy) > 9)
1227 return FALSE;
1229 collection_unselect_item(collection, item);
1230 gtk_signal_emit(GTK_OBJECT(collection),
1231 collection_signals[OPEN_ITEM],
1232 collection->items[item].data,
1233 item);
1236 return FALSE;
1239 remove_lasso_box(collection);
1241 w = collection->item_width;
1242 h = collection->item_height;
1244 top = collection->drag_box_y[0] + scroll;
1245 bottom = collection->drag_box_y[1] + scroll;
1246 if (top > bottom)
1248 int tmp;
1249 tmp = top;
1250 top = bottom;
1251 bottom = tmp;
1253 top += h / 4;
1254 bottom -= h / 4;
1256 row = MAX(top / h, 0);
1257 last_row = bottom / h;
1259 top = collection->drag_box_x[0]; /* (left) */
1260 bottom = collection->drag_box_x[1];
1261 if (top > bottom)
1263 int tmp;
1264 tmp = top;
1265 top = bottom;
1266 bottom = tmp;
1268 top += w / 4;
1269 bottom -= w / 4;
1270 start_col = MAX(top / w, 0);
1271 last_col = bottom / w;
1272 if (last_col >= collection->columns)
1273 last_col = collection->columns - 1;
1275 stacked_time = current_event_time;
1276 current_event_time = event->time;
1278 while (row <= last_row)
1280 col = start_col;
1281 item = row * collection->columns + col;
1282 while (col <= last_col)
1284 if (item >= collection->number_of_items)
1286 current_event_time = stacked_time;
1287 return FALSE;
1290 if (button == 1)
1291 collection_select_item(collection, item);
1292 else
1293 collection_toggle_item(collection, item);
1294 col++;
1295 item++;
1297 row++;
1300 current_event_time = stacked_time;
1302 return FALSE;
1305 static gint collection_motion_notify(GtkWidget *widget,
1306 GdkEventMotion *event)
1308 Collection *collection;
1309 int x, y;
1310 guint stacked_time;
1312 g_return_val_if_fail(widget != NULL, FALSE);
1313 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1314 g_return_val_if_fail(event != NULL, FALSE);
1316 collection = COLLECTION(widget);
1318 if (collection->buttons_pressed == 0)
1319 return FALSE;
1321 stacked_time = current_event_time;
1322 current_event_time = event->time;
1324 if (event->window != widget->window)
1325 gdk_window_get_pointer(widget->window, &x, &y, NULL);
1326 else
1328 x = event->x;
1329 y = event->y;
1332 if (collection->lasso_box)
1334 int new_value = 0, diff;
1335 int height;
1337 gdk_window_get_size(widget->window, NULL, &height);
1339 if (y < 0)
1341 int old_value = collection->vadj->value;
1343 new_value = MAX(old_value + y / 10, 0.0);
1344 diff = new_value - old_value;
1346 else if (y > height)
1348 int old_value = collection->vadj->value;
1350 new_value = MIN(old_value + (y - height) / 10,
1351 collection->vadj->upper
1352 - collection->vadj->page_size);
1353 diff = new_value - old_value;
1355 else
1356 diff = 0;
1358 remove_lasso_box(collection);
1359 collection->drag_box_x[1] = x;
1360 collection->drag_box_y[1] = y;
1362 if (diff)
1364 collection->drag_box_y[0] -= diff;
1365 collection->vadj->value = new_value;
1366 gtk_signal_emit_by_name(GTK_OBJECT(collection->vadj),
1367 "changed");
1369 add_lasso_box(collection);
1371 else if (collection->may_drag)
1373 int dx = x - collection->drag_box_x[0];
1374 int dy = y - collection->drag_box_y[0];
1376 if (abs(dx) > 9 || abs(dy) > 9)
1378 int row, col, item;
1379 int scroll = collection->vadj->value;
1381 collection->may_drag = FALSE;
1383 col = collection->drag_box_x[0]
1384 / collection->item_width;
1385 row = (collection->drag_box_y[0] + scroll)
1386 / collection->item_height;
1387 item = item_at_row_col(collection, row, col);
1389 if (item != -1 && collection->test_point(collection,
1390 collection->drag_box_x[0] -
1391 col * collection->item_width,
1392 collection->drag_box_y[0]
1393 - row * collection->item_height
1394 + scroll,
1395 &collection->items[item],
1396 collection->item_width,
1397 collection->item_height))
1399 collection->buttons_pressed = 0;
1400 gtk_grab_remove(widget);
1401 collection_select_item(collection, item);
1402 gtk_signal_emit(GTK_OBJECT(collection),
1403 collection_signals[DRAG_SELECTION],
1404 event,
1405 collection->number_selected);
1407 else
1409 collection->drag_box_x[1] = x;
1410 collection->drag_box_y[1] = y;
1411 add_lasso_box(collection);
1416 current_event_time = stacked_time;
1417 return FALSE;
1420 static void add_lasso_box(Collection *collection)
1422 g_return_if_fail(collection != NULL);
1423 g_return_if_fail(IS_COLLECTION(collection));
1424 g_return_if_fail(collection->lasso_box == FALSE);
1426 collection->lasso_box = TRUE;
1427 draw_lasso_box(collection);
1430 static void draw_lasso_box(Collection *collection)
1432 GtkWidget *widget;
1433 int x, y, width, height;
1435 widget = GTK_WIDGET(collection);
1437 x = MIN(collection->drag_box_x[0], collection->drag_box_x[1]);
1438 y = MIN(collection->drag_box_y[0], collection->drag_box_y[1]);
1439 width = abs(collection->drag_box_x[1] - collection->drag_box_x[0]);
1440 height = abs(collection->drag_box_y[1] - collection->drag_box_y[0]);
1442 gdk_draw_rectangle(widget->window, collection->xor_gc, FALSE,
1443 x, y, width, height);
1446 static void remove_lasso_box(Collection *collection)
1448 g_return_if_fail(collection != NULL);
1449 g_return_if_fail(IS_COLLECTION(collection));
1450 g_return_if_fail(collection->lasso_box == TRUE);
1452 draw_lasso_box(collection);
1454 collection->lasso_box = FALSE;
1456 return;
1459 /* Convert a row,col address to an item number, or -1 if none */
1460 static int item_at_row_col(Collection *collection, int row, int col)
1462 int item;
1464 if (row < 0 || col < 0 || col >= collection->columns)
1465 return -1;
1467 item = col + row * collection->columns;
1469 if (item >= collection->number_of_items)
1470 return -1;
1471 return item;
1474 /* Make sure that 'item' is fully visible (vertically), scrolling if not. */
1475 static void scroll_to_show(Collection *collection, int item)
1477 int first, last, row;
1479 g_return_if_fail(collection != NULL);
1480 g_return_if_fail(IS_COLLECTION(collection));
1482 row = item / collection->columns;
1483 get_visible_limits(collection, &first, &last);
1485 if (row <= first)
1487 gtk_adjustment_set_value(collection->vadj,
1488 row * collection->item_height);
1490 else if (row >= last)
1492 GtkWidget *widget = (GtkWidget *) collection;
1493 int height;
1495 if (GTK_WIDGET_REALIZED(widget))
1497 gdk_window_get_size(widget->window, NULL, &height);
1498 gtk_adjustment_set_value(collection->vadj,
1499 (row + 1) * collection->item_height - height);
1504 /* Return the first and last rows which are [partly] visible. Does not
1505 * ensure that the rows actually exist (contain items).
1507 static void get_visible_limits(Collection *collection, int *first, int *last)
1509 GtkWidget *widget = (GtkWidget *) collection;
1510 int scroll, height;
1512 g_return_if_fail(collection != NULL);
1513 g_return_if_fail(IS_COLLECTION(collection));
1514 g_return_if_fail(first != NULL && last != NULL);
1516 if (!GTK_WIDGET_REALIZED(widget))
1518 *first = 0;
1519 *last = 0;
1521 else
1523 scroll = collection->vadj->value;
1524 gdk_window_get_size(widget->window, NULL, &height);
1526 *first = MAX(scroll / collection->item_height, 0);
1527 *last = (scroll + height - 1) /collection->item_height;
1529 if (*last < *first)
1530 *last = *first;
1534 /* Unselect all items except number item (-1 to unselect everything) */
1535 static void collection_clear_except(Collection *collection, gint exception)
1537 GtkWidget *widget;
1538 GdkRectangle area;
1539 int item = 0;
1540 int scroll;
1541 int end; /* Selected items to end up with */
1543 widget = GTK_WIDGET(collection);
1544 scroll = collection->vadj->value;
1546 end = exception >= 0 && exception < collection->number_of_items
1547 ? collection->items[exception].selected != 0 : 0;
1549 area.width = collection->item_width;
1550 area.height = collection->item_height;
1552 if (collection->number_selected == 0)
1553 return;
1555 while (collection->number_selected > end)
1557 while (item == exception || !collection->items[item].selected)
1558 item++;
1560 area.x = (item % collection->columns) * area.width;
1561 area.y = (item / collection->columns) * area.height
1562 - scroll;
1564 collection->items[item++].selected = FALSE;
1565 gdk_window_clear_area(widget->window,
1566 area.x, area.y, area.width, area.height);
1567 collection_paint(collection, &area);
1569 collection->number_selected--;
1572 if (end == 0)
1573 gtk_signal_emit(GTK_OBJECT(collection),
1574 collection_signals[LOSE_SELECTION],
1575 current_event_time);
1578 /* Cancel the current wink effect. */
1579 static void cancel_wink(Collection *collection)
1581 gint item;
1583 g_return_if_fail(collection != NULL);
1584 g_return_if_fail(IS_COLLECTION(collection));
1585 g_return_if_fail(collection->wink_item != -1);
1587 item = collection->wink_item;
1589 collection->wink_item = -1;
1590 gtk_timeout_remove(collection->wink_timeout);
1592 collection_draw_item(collection, item, TRUE);
1595 static gboolean cancel_wink_timeout(Collection *collection)
1597 gint item;
1599 g_return_val_if_fail(collection != NULL, FALSE);
1600 g_return_val_if_fail(IS_COLLECTION(collection), FALSE);
1601 g_return_val_if_fail(collection->wink_item != -1, FALSE);
1603 item = collection->wink_item;
1605 collection->wink_item = -1;
1607 collection_draw_item(collection, item, TRUE);
1609 return FALSE;
1612 static gint focus_in(GtkWidget *widget, GdkEventFocus *event)
1614 g_return_val_if_fail(widget != NULL, FALSE);
1615 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1616 g_return_val_if_fail(event != NULL, FALSE);
1618 GTK_WIDGET_SET_FLAGS(widget, GTK_HAS_FOCUS);
1619 gtk_widget_draw_focus(widget);
1621 return FALSE;
1624 static gint focus_out(GtkWidget *widget, GdkEventFocus *event)
1626 g_return_val_if_fail(widget != NULL, FALSE);
1627 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1628 g_return_val_if_fail(event != NULL, FALSE);
1630 GTK_WIDGET_UNSET_FLAGS(widget, GTK_HAS_FOCUS);
1631 gtk_widget_draw_focus(widget);
1633 return FALSE;
1636 /* Functions for managing collections */
1638 /* Remove all objects from the collection */
1639 void collection_clear(Collection *collection)
1641 int prev_selected;
1643 g_return_if_fail(IS_COLLECTION(collection));
1645 if (collection->number_of_items == 0)
1646 return;
1648 if (collection->wink_item != -1)
1650 collection->wink_item = -1;
1651 gtk_timeout_remove(collection->wink_timeout);
1654 collection_set_cursor_item(collection,
1655 collection->cursor_item == -1 ? -1: 0);
1656 prev_selected = collection->number_selected;
1657 collection->number_of_items = collection->number_selected = 0;
1659 resize_arrays(collection, MINIMUM_ITEMS);
1661 collection->paint_level = PAINT_CLEAR;
1663 gtk_widget_queue_clear(GTK_WIDGET(collection));
1665 if (prev_selected && collection->number_selected == 0)
1666 gtk_signal_emit(GTK_OBJECT(collection),
1667 collection_signals[LOSE_SELECTION],
1668 current_event_time);
1671 /* Inserts a new item at the end. The new item is unselected, and its
1672 * number is returned.
1674 gint collection_insert(Collection *collection, gpointer data)
1676 int item;
1678 g_return_val_if_fail(IS_COLLECTION(collection), -1);
1680 item = collection->number_of_items;
1682 if (item >= collection->array_size)
1683 resize_arrays(collection, item + (item >> 1));
1685 collection->items[item].data = data;
1686 collection->items[item].selected = FALSE;
1688 collection->number_of_items++;
1690 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection)))
1692 set_vadjustment(collection);
1693 collection_draw_item(collection,
1694 collection->number_of_items - 1,
1695 FALSE);
1698 return item;
1701 /* Unselect an item by number */
1702 void collection_unselect_item(Collection *collection, gint item)
1704 g_return_if_fail(collection != NULL);
1705 g_return_if_fail(IS_COLLECTION(collection));
1706 g_return_if_fail(item >= 0 && item < collection->number_of_items);
1708 if (collection->items[item].selected)
1710 collection->items[item].selected = FALSE;
1711 collection_draw_item(collection, item, TRUE);
1713 if (--collection->number_selected == 0)
1714 gtk_signal_emit(GTK_OBJECT(collection),
1715 collection_signals[LOSE_SELECTION],
1716 current_event_time);
1720 /* Select an item by number */
1721 void collection_select_item(Collection *collection, gint item)
1723 g_return_if_fail(collection != NULL);
1724 g_return_if_fail(IS_COLLECTION(collection));
1725 g_return_if_fail(item >= 0 && item < collection->number_of_items);
1727 if (collection->items[item].selected)
1728 return; /* Already selected */
1730 collection->items[item].selected = TRUE;
1731 collection_draw_item(collection, item, TRUE);
1733 if (collection->number_selected++ == 0)
1734 gtk_signal_emit(GTK_OBJECT(collection),
1735 collection_signals[GAIN_SELECTION],
1736 current_event_time);
1739 /* Toggle the selected state of an item (by number) */
1740 void collection_toggle_item(Collection *collection, gint item)
1742 g_return_if_fail(collection != NULL);
1743 g_return_if_fail(IS_COLLECTION(collection));
1744 g_return_if_fail(item >= 0 && item < collection->number_of_items);
1746 if (collection->items[item].selected)
1748 collection->items[item].selected = FALSE;
1749 if (--collection->number_selected == 0)
1750 gtk_signal_emit(GTK_OBJECT(collection),
1751 collection_signals[LOSE_SELECTION],
1752 current_event_time);
1754 else
1756 collection->items[item].selected = TRUE;
1757 if (collection->number_selected++ == 0)
1758 gtk_signal_emit(GTK_OBJECT(collection),
1759 collection_signals[GAIN_SELECTION],
1760 current_event_time);
1762 collection_draw_item(collection, item, TRUE);
1765 /* Select all items in the collection */
1766 void collection_select_all(Collection *collection)
1768 GtkWidget *widget;
1769 GdkRectangle area;
1770 int item = 0;
1771 int scroll;
1773 g_return_if_fail(collection != NULL);
1774 g_return_if_fail(IS_COLLECTION(collection));
1776 widget = GTK_WIDGET(collection);
1777 scroll = collection->vadj->value;
1779 area.width = collection->item_width;
1780 area.height = collection->item_height;
1782 if (collection->number_selected == collection->number_of_items)
1783 return; /* Nothing to do */
1785 while (collection->number_selected < collection->number_of_items)
1787 while (collection->items[item].selected)
1788 item++;
1790 area.x = (item % collection->columns) * area.width;
1791 area.y = (item / collection->columns) * area.height
1792 - scroll;
1794 collection->items[item++].selected = TRUE;
1795 gdk_window_clear_area(widget->window,
1796 area.x, area.y, area.width, area.height);
1797 collection_paint(collection, &area);
1799 collection->number_selected++;
1802 gtk_signal_emit(GTK_OBJECT(collection),
1803 collection_signals[GAIN_SELECTION],
1804 current_event_time);
1807 /* Unselect all items in the collection */
1808 void collection_clear_selection(Collection *collection)
1810 g_return_if_fail(collection != NULL);
1811 g_return_if_fail(IS_COLLECTION(collection));
1813 collection_clear_except(collection, -1);
1816 /* Force a redraw of the specified item, if it is visible */
1817 void collection_draw_item(Collection *collection, gint item, gboolean blank)
1819 int height;
1820 GdkRectangle area;
1821 GtkWidget *widget;
1822 int row, col;
1823 int scroll;
1824 int area_y, area_height; /* NOT shorts! */
1826 g_return_if_fail(collection != NULL);
1827 g_return_if_fail(IS_COLLECTION(collection));
1828 g_return_if_fail(item >= 0 &&
1829 (item == 0 || item < collection->number_of_items));
1831 widget = GTK_WIDGET(collection);
1832 if (!GTK_WIDGET_REALIZED(widget))
1833 return;
1835 col = item % collection->columns;
1836 row = item / collection->columns;
1837 scroll = collection->vadj->value; /* (round to int) */
1839 area.x = col * collection->item_width;
1840 area_y = row * collection->item_height - scroll;
1841 area.width = collection->item_width;
1842 area_height = collection->item_height;
1844 if (area_y + area_height < 0)
1845 return;
1847 gdk_window_get_size(widget->window, NULL, &height);
1849 if (area_y > height)
1850 return;
1852 area.y = area_y;
1853 area.height = area_height;
1855 if (blank || collection->lasso_box)
1856 gdk_window_clear_area(widget->window,
1857 area.x, area.y, area.width, area.height);
1859 draw_one_item(collection, item, &area);
1861 if (collection->lasso_box)
1863 gdk_gc_set_clip_rectangle(collection->xor_gc, &area);
1864 draw_lasso_box(collection);
1865 gdk_gc_set_clip_rectangle(collection->xor_gc, NULL);
1869 void collection_set_item_size(Collection *collection, int width, int height)
1871 GtkWidget *widget;
1873 g_return_if_fail(collection != NULL);
1874 g_return_if_fail(IS_COLLECTION(collection));
1875 g_return_if_fail(width > 4 && height > 4);
1877 widget = GTK_WIDGET(collection);
1879 collection->item_width = width;
1880 collection->item_height = height;
1882 if (GTK_WIDGET_REALIZED(widget))
1884 int window_width;
1886 collection->paint_level = PAINT_CLEAR;
1887 gdk_window_get_size(widget->window, &window_width, NULL);
1888 collection->columns = MAX(window_width / collection->item_width,
1891 set_vadjustment(collection);
1892 if (collection->cursor_item != -1)
1893 scroll_to_show(collection, collection->cursor_item);
1894 gtk_widget_queue_draw(widget);
1898 /* Cursor is positioned on item with the same data as before the sort.
1899 * Same for the wink item.
1901 void collection_qsort(Collection *collection,
1902 int (*compar)(const void *, const void *))
1904 int cursor, wink, items;
1905 gpointer cursor_data = NULL;
1906 gpointer wink_data = NULL;
1908 g_return_if_fail(collection != NULL);
1909 g_return_if_fail(IS_COLLECTION(collection));
1910 g_return_if_fail(compar != NULL);
1912 items = collection->number_of_items;
1914 wink = collection->wink_item;
1915 if (wink >= 0 && wink < items)
1916 wink_data = collection->items[wink].data;
1917 else
1918 wink = -1;
1920 cursor = collection->cursor_item;
1921 if (cursor >= 0 && cursor < items)
1922 cursor_data = collection->items[cursor].data;
1923 else
1924 cursor = -1;
1926 if (collection->wink_item != -1)
1928 collection->wink_item = -1;
1929 gtk_timeout_remove(collection->wink_timeout);
1932 qsort(collection->items, items, sizeof(collection->items[0]), compar);
1934 if (cursor > -1 || wink > -1)
1936 int item;
1938 for (item = 0; item < items; item++)
1940 if (collection->items[item].data == cursor_data)
1941 collection_set_cursor_item(collection, item);
1942 if (collection->items[item].data == wink_data)
1943 collection_wink_item(collection, item);
1947 collection->paint_level = PAINT_CLEAR;
1949 gtk_widget_queue_draw(GTK_WIDGET(collection));
1952 /* Find an item in an unsorted collection.
1953 * Returns the item number, or -1 if not found.
1955 int collection_find_item(Collection *collection, gpointer data,
1956 int (*compar)(const void *, const void *))
1958 int i;
1960 g_return_val_if_fail(collection != NULL, -1);
1961 g_return_val_if_fail(IS_COLLECTION(collection), -1);
1962 g_return_val_if_fail(compar != NULL, -1);
1964 for (i = 0; i < collection->number_of_items; i++)
1965 if (compar(&collection->items[i].data, &data) == 0)
1966 return i;
1968 return -1;
1971 /* The collection may be in either normal mode or panel mode.
1972 * In panel mode:
1973 * - a single click calls open_item
1974 * - items are never selected
1975 * - lasso boxes are disabled
1977 void collection_set_panel(Collection *collection, gboolean panel)
1979 g_return_if_fail(collection != NULL);
1980 g_return_if_fail(IS_COLLECTION(collection));
1982 collection->panel = panel == TRUE;
1984 if (collection->panel)
1986 collection_clear_selection(collection);
1987 if (collection->lasso_box)
1988 remove_lasso_box(collection);
1992 /* Return the number of the item under the point (x,y), or -1 for none.
1993 * This may call your test_point callback. The point is relative to the
1994 * collection's origin.
1996 int collection_get_item(Collection *collection, int x, int y)
1998 int scroll;
1999 int row, col;
2000 int item;
2002 g_return_val_if_fail(collection != NULL, -1);
2004 scroll = collection->vadj->value;
2005 col = x / collection->item_width;
2006 row = (y + scroll) / collection->item_height;
2008 if (col < 0 || row < 0 || col >= collection->columns)
2009 return -1;
2011 item = col + row * collection->columns;
2012 if (item >= collection->number_of_items
2014 !collection->test_point(collection,
2015 x - col * collection->item_width,
2016 y - row * collection->item_height
2017 + scroll,
2018 &collection->items[item],
2019 collection->item_width,
2020 collection->item_height))
2022 return -1;
2025 return item;
2028 /* Set the cursor/highlight over the given item. Passing -1
2029 * hides the cursor. As a special case, you may set the cursor item
2030 * to zero when there are no items.
2032 void collection_set_cursor_item(Collection *collection, gint item)
2034 int old_item;
2036 g_return_if_fail(collection != NULL);
2037 g_return_if_fail(IS_COLLECTION(collection));
2038 g_return_if_fail(item >= -1 &&
2039 (item < collection->number_of_items || item == 0));
2041 old_item = collection->cursor_item;
2043 if (old_item == item)
2044 return;
2046 collection->cursor_item = item;
2048 if (old_item != -1)
2049 collection_draw_item(collection, old_item, TRUE);
2051 if (item != -1)
2053 collection_draw_item(collection, item, TRUE);
2054 scroll_to_show(collection, item);
2058 /* Briefly highlight an item to draw the user's attention to it.
2059 * -1 cancels the effect, as does deleting items, sorting the collection
2060 * or starting a new wink effect.
2061 * Otherwise, the effect will cancel itself after a short pause.
2062 * */
2063 void collection_wink_item(Collection *collection, gint item)
2065 g_return_if_fail(collection != NULL);
2066 g_return_if_fail(IS_COLLECTION(collection));
2067 g_return_if_fail(item >= -1 && item < collection->number_of_items);
2069 if (collection->wink_item != -1)
2070 cancel_wink(collection);
2071 if (item == -1)
2072 return;
2074 collection->wink_item = item;
2075 collection->wink_timeout = gtk_timeout_add(300,
2076 (GtkFunction) cancel_wink_timeout,
2077 collection);
2078 collection_draw_item(collection, item, TRUE);
2079 scroll_to_show(collection, item);
2081 gdk_flush();
2084 /* Call test(item, data) on each item in the collection.
2085 * Remove all items for which it returns TRUE. test() should
2086 * free the data before returning TRUE. The collection is in an
2087 * inconsistant state during this call (ie, when test() is called).
2089 void collection_delete_if(Collection *collection,
2090 gboolean (*test)(gpointer item, gpointer data),
2091 gpointer data)
2093 int in, out = 0;
2094 int selected = 0;
2095 int cursor;
2097 g_return_if_fail(collection != NULL);
2098 g_return_if_fail(IS_COLLECTION(collection));
2099 g_return_if_fail(test != NULL);
2101 cursor = collection->cursor_item;
2103 for (in = 0; in < collection->number_of_items; in++)
2105 if (!test(collection->items[in].data, data))
2107 if (collection->items[in].selected)
2109 collection->items[out].selected = TRUE;
2110 selected++;
2112 else
2113 collection->items[out].selected = FALSE;
2115 collection->items[out++].data =
2116 collection->items[in].data;
2118 else if (cursor >= in)
2119 cursor--;
2122 if (in != out)
2124 collection->cursor_item = cursor;
2126 if (collection->wink_item != -1)
2128 collection->wink_item = -1;
2129 gtk_timeout_remove(collection->wink_timeout);
2132 collection->number_of_items = out;
2133 collection->number_selected = selected;
2134 resize_arrays(collection,
2135 MAX(collection->number_of_items, MINIMUM_ITEMS));
2137 collection->paint_level = PAINT_CLEAR;
2139 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection)))
2141 set_vadjustment(collection);
2142 gtk_widget_queue_draw(GTK_WIDGET(collection));
2147 /* Display a cross-hair pointer and the next time an item is clicked,
2148 * call the callback function. If the background is clicked or NULL
2149 * is passed as the callback then revert to normal operation.
2151 void collection_target(Collection *collection,
2152 CollectionTargetFunc callback,
2153 gpointer user_data)
2155 g_return_if_fail(collection != NULL);
2156 g_return_if_fail(IS_COLLECTION(collection));
2158 if (callback != collection->target_cb)
2159 gdk_window_set_cursor(GTK_WIDGET(collection)->window,
2160 callback ? crosshair : NULL);
2162 collection->target_cb = callback;
2163 collection->target_data = user_data;
2165 if (collection->cursor_item != -1)
2166 collection_draw_item(collection, collection->cursor_item,
2167 FALSE);
2170 /* Move the cursor by the given row and column offsets. */
2171 void collection_move_cursor(Collection *collection, int drow, int dcol)
2173 int row, col, item;
2174 int first, last, total_rows;
2176 g_return_if_fail(collection != NULL);
2177 g_return_if_fail(IS_COLLECTION(collection));
2179 get_visible_limits(collection, &first, &last);
2181 item = collection->cursor_item;
2182 if (item == -1)
2184 col = 0;
2185 row = first;
2187 else
2189 row = item / collection->columns;
2190 col = item % collection->columns + dcol;
2192 if (row < first)
2193 row = first;
2194 else if (row > last)
2195 row = last;
2196 else
2197 row = MAX(row + drow, 0);
2200 total_rows = (collection->number_of_items + collection->columns - 1)
2201 / collection->columns;
2203 if (row >= total_rows - 1 && drow > 0)
2205 row = total_rows - 1;
2206 item = col + row * collection->columns;
2207 if (item >= collection->number_of_items)
2209 row--;
2210 scroll_to_show(collection, item);
2213 if (row < 0)
2214 row = 0;
2216 item = col + row * collection->columns;
2218 if (item >= 0 && item < collection->number_of_items)
2219 collection_set_cursor_item(collection, item);