r171: Fixed a compile problem on IRIX. Updating items now resorts.
[rox-filer.git] / ROX-Filer / src / collection.c
blob75ef1678f04226c5a65e0a32ab737b959f3dda27
1 /*
2 * $Id$
4 * Collection - a GTK+ widget
5 * Copyright (C) 1999, Thomas Leonard, <tal197@ecs.soton.ac.uk>.
7 * The collection widget provides an area for displaying a collection of
8 * objects (such as files). It allows the user to choose a selection of
9 * them and provides signals to allow popping up menus, detecting
10 * double-clicks etc.
12 * This program is free software; you can redistribute it and/or modify it
13 * under the terms of the GNU General Public License as published by the Free
14 * Software Foundation; either version 2 of the License, or (at your option)
15 * any later version.
17 * This program is distributed in the hope that it will be useful, but WITHOUT
18 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
20 * more details.
22 * You should have received a copy of the GNU General Public License along with
23 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
24 * Place, Suite 330, Boston, MA 02111-1307 USA
27 #include <stdlib.h>
29 #include <gtk/gtk.h>
30 #include <gdk/gdkkeysyms.h>
31 #include "collection.h"
33 #define MIN_WIDTH 80
34 #define MIN_HEIGHT 60
35 #define MINIMUM_ITEMS 16
37 int collection_menu_button = 3;
38 gboolean collection_single_click = FALSE;
40 enum
42 ARG_0,
43 ARG_VADJUSTMENT
46 /* Signals:
48 * void open_item(collection, item, item_number, user_data)
49 * User has double clicked on this item.
51 * void drag_selection(collection, motion_event, number_selected, user_data)
52 * User has tried to drag the selection.
54 * void show_menu(collection, button_event, item, user_data)
55 * User has menu-clicked on the collection. 'item' is the number
56 * of the item clicked, or -1 if the click was over the background.
58 * void gain_selection(collection, time, user_data)
59 * We've gone from no selected items to having a selection.
60 * Time is the time of the event that caused the change, or
61 * GDK_CURRENT_TIME if not known.
63 * void lose_selection(collection, time, user_data)
64 * We've dropped to having no selected items.
65 * Time is the time of the event that caused the change, or
66 * GDK_CURRENT_TIME if not known.
68 enum
70 OPEN_ITEM,
71 DRAG_SELECTION,
72 SHOW_MENU,
73 GAIN_SELECTION,
74 LOSE_SELECTION,
75 LAST_SIGNAL
78 static guint collection_signals[LAST_SIGNAL] = { 0 };
80 static guint32 current_event_time = GDK_CURRENT_TIME;
82 static GtkWidgetClass *parent_class = NULL;
84 static GdkCursor *crosshair = NULL;
86 /* Static prototypes */
87 static void draw_one_item(Collection *collection,
88 int item,
89 GdkRectangle *area);
90 static void collection_class_init(CollectionClass *class);
91 static void collection_init(Collection *object);
92 static void collection_destroy(GtkObject *object);
93 static void collection_finalize(GtkObject *object);
94 static void collection_realize(GtkWidget *widget);
95 static gint collection_paint(Collection *collection,
96 GdkRectangle *area);
97 static void collection_size_request(GtkWidget *widget,
98 GtkRequisition *requisition);
99 static void collection_size_allocate(GtkWidget *widget,
100 GtkAllocation *allocation);
101 static void collection_set_adjustment(Collection *collection,
102 GtkAdjustment *vadj);
103 static void collection_set_arg( GtkObject *object,
104 GtkArg *arg,
105 guint arg_id);
106 static void collection_get_arg( GtkObject *object,
107 GtkArg *arg,
108 guint arg_id);
109 static void collection_adjustment(GtkAdjustment *adjustment,
110 Collection *collection);
111 static void collection_disconnect(GtkAdjustment *adjustment,
112 Collection *collection);
113 static void set_vadjustment(Collection *collection);
114 static void collection_draw(GtkWidget *widget, GdkRectangle *area);
115 static gint collection_expose(GtkWidget *widget, GdkEventExpose *event);
116 static void scroll_by(Collection *collection, gint diff);
117 static gint collection_button_press(GtkWidget *widget,
118 GdkEventButton *event);
119 static gint collection_button_release(GtkWidget *widget,
120 GdkEventButton *event);
121 static void default_draw_item(GtkWidget *widget,
122 CollectionItem *data,
123 GdkRectangle *area);
124 static gboolean default_test_point(Collection *collection,
125 int point_x, int point_y,
126 CollectionItem *data,
127 int width, int height);
128 static gint collection_motion_notify(GtkWidget *widget,
129 GdkEventMotion *event);
130 static void add_lasso_box(Collection *collection);
131 static void remove_lasso_box(Collection *collection);
132 static void draw_lasso_box(Collection *collection);
133 static int item_at_row_col(Collection *collection, int row, int col);
134 static void collection_clear_except(Collection *collection, gint exception);
135 static void cancel_wink(Collection *collection);
136 static gint collection_key_press(GtkWidget *widget, GdkEventKey *event);
137 static void get_visible_limits(Collection *collection, int *first, int *last);
138 static void scroll_to_show(Collection *collection, int item);
140 static void draw_one_item(Collection *collection, int item, GdkRectangle *area)
142 if (item < collection->number_of_items)
144 collection->draw_item((GtkWidget *) collection,
145 &collection->items[item],
146 area);
147 if (item == collection->wink_item)
148 gdk_draw_rectangle(((GtkWidget *) collection)->window,
149 ((GtkWidget *) collection)->style->black_gc,
150 FALSE,
151 area->x, area->y,
152 area->width - 1, area->height - 1);
154 if (item == collection->cursor_item)
156 gdk_draw_rectangle(((GtkWidget *) collection)->window,
157 collection->target_cb
158 ? ((GtkWidget *) collection)->style->white_gc
159 : ((GtkWidget *) collection)->style->black_gc,
160 FALSE,
161 area->x + 1, area->y + 1,
162 area->width - 3, area->height - 3);
166 GtkType collection_get_type(void)
168 static guint my_type = 0;
170 if (!my_type)
172 static const GtkTypeInfo my_info =
174 "Collection",
175 sizeof(Collection),
176 sizeof(CollectionClass),
177 (GtkClassInitFunc) collection_class_init,
178 (GtkObjectInitFunc) collection_init,
179 NULL, /* Reserved 1 */
180 NULL, /* Reserved 2 */
181 (GtkClassInitFunc) NULL /* base_class_init_func */
184 my_type = gtk_type_unique(gtk_widget_get_type(),
185 &my_info);
188 return my_type;
191 static void collection_class_init(CollectionClass *class)
193 GtkObjectClass *object_class;
194 GtkWidgetClass *widget_class;
196 object_class = (GtkObjectClass*) class;
197 widget_class = (GtkWidgetClass*) class;
199 parent_class = gtk_type_class(gtk_widget_get_type());
201 gtk_object_add_arg_type("Collection::vadjustment",
202 GTK_TYPE_ADJUSTMENT,
203 GTK_ARG_READWRITE | GTK_ARG_CONSTRUCT,
204 ARG_VADJUSTMENT);
206 object_class->destroy = collection_destroy;
207 object_class->finalize = collection_finalize;
209 widget_class->realize = collection_realize;
210 widget_class->draw = collection_draw;
211 widget_class->expose_event = collection_expose;
212 widget_class->size_request = collection_size_request;
213 widget_class->size_allocate = collection_size_allocate;
215 widget_class->key_press_event = collection_key_press;
216 widget_class->button_press_event = collection_button_press;
217 widget_class->button_release_event = collection_button_release;
218 widget_class->motion_notify_event = collection_motion_notify;
219 object_class->set_arg = collection_set_arg;
220 object_class->get_arg = collection_get_arg;
222 class->open_item = NULL;
223 class->drag_selection = NULL;
224 class->show_menu = NULL;
225 class->gain_selection = NULL;
226 class->lose_selection = NULL;
228 collection_signals[OPEN_ITEM] = gtk_signal_new("open_item",
229 GTK_RUN_FIRST,
230 object_class->type,
231 GTK_SIGNAL_OFFSET(CollectionClass,
232 open_item),
233 gtk_marshal_NONE__POINTER_UINT,
234 GTK_TYPE_NONE, 2,
235 GTK_TYPE_POINTER,
236 GTK_TYPE_UINT);
237 collection_signals[DRAG_SELECTION] = gtk_signal_new("drag_selection",
238 GTK_RUN_FIRST,
239 object_class->type,
240 GTK_SIGNAL_OFFSET(CollectionClass,
241 drag_selection),
242 gtk_marshal_NONE__POINTER_UINT,
243 GTK_TYPE_NONE, 2,
244 GTK_TYPE_POINTER,
245 GTK_TYPE_UINT);
246 collection_signals[SHOW_MENU] = gtk_signal_new("show_menu",
247 GTK_RUN_FIRST,
248 object_class->type,
249 GTK_SIGNAL_OFFSET(CollectionClass,
250 show_menu),
251 gtk_marshal_NONE__POINTER_INT,
252 GTK_TYPE_NONE, 2,
253 GTK_TYPE_POINTER,
254 GTK_TYPE_INT);
255 collection_signals[GAIN_SELECTION] = gtk_signal_new("gain_selection",
256 GTK_RUN_FIRST,
257 object_class->type,
258 GTK_SIGNAL_OFFSET(CollectionClass,
259 gain_selection),
260 gtk_marshal_NONE__UINT,
261 GTK_TYPE_NONE, 1,
262 GTK_TYPE_UINT);
263 collection_signals[LOSE_SELECTION] = gtk_signal_new("lose_selection",
264 GTK_RUN_FIRST,
265 object_class->type,
266 GTK_SIGNAL_OFFSET(CollectionClass,
267 lose_selection),
268 gtk_marshal_NONE__UINT,
269 GTK_TYPE_NONE, 1,
270 GTK_TYPE_UINT);
272 gtk_object_class_add_signals(object_class,
273 collection_signals, LAST_SIGNAL);
276 static void collection_init(Collection *object)
278 g_return_if_fail(object != NULL);
279 g_return_if_fail(IS_COLLECTION(object));
281 if (!crosshair)
282 crosshair = gdk_cursor_new(GDK_CROSSHAIR);
284 GTK_WIDGET_SET_FLAGS(GTK_WIDGET(object), GTK_CAN_FOCUS);
286 object->panel = FALSE;
287 object->number_of_items = 0;
288 object->number_selected = 0;
289 object->columns = 1;
290 object->item_width = 64;
291 object->item_height = 64;
292 object->vadj = NULL;
293 object->paint_level = PAINT_OVERWRITE;
294 object->last_scroll = 0;
296 object->items = g_malloc(sizeof(CollectionItem) * MINIMUM_ITEMS);
297 object->cursor_item = -1;
298 object->wink_item = -1;
299 object->array_size = MINIMUM_ITEMS;
300 object->draw_item = default_draw_item;
301 object->test_point = default_test_point;
303 object->buttons_pressed = 0;
304 object->may_drag = FALSE;
306 return;
309 GtkWidget* collection_new(GtkAdjustment *vadj)
311 if (vadj)
312 g_return_val_if_fail(GTK_IS_ADJUSTMENT(vadj), NULL);
314 return GTK_WIDGET(gtk_widget_new(collection_get_type(),
315 "vadjustment", vadj,
316 NULL));
319 void collection_set_functions(Collection *collection,
320 CollectionDrawFunc draw_item,
321 CollectionTestFunc test_point)
323 GtkWidget *widget;
325 g_return_if_fail(collection != NULL);
326 g_return_if_fail(IS_COLLECTION(collection));
328 widget = GTK_WIDGET(collection);
330 if (!draw_item)
331 draw_item = default_draw_item;
332 if (!test_point)
333 test_point = default_test_point;
335 collection->draw_item = draw_item;
336 collection->test_point = test_point;
338 if (GTK_WIDGET_REALIZED(widget))
340 collection->paint_level = PAINT_CLEAR;
341 gtk_widget_queue_clear(widget);
345 /* After this we are unusable, but our data (if any) is still hanging around.
346 * It will be freed later with finalize.
348 static void collection_destroy(GtkObject *object)
350 Collection *collection;
352 g_return_if_fail(object != NULL);
353 g_return_if_fail(IS_COLLECTION(object));
355 collection = COLLECTION(object);
357 if (collection->wink_item != -1)
359 collection->wink_item = -1;
360 gtk_timeout_remove(collection->wink_timeout);
363 gtk_signal_disconnect_by_data(GTK_OBJECT(collection->vadj),
364 collection);
366 if (GTK_OBJECT_CLASS(parent_class)->destroy)
367 (*GTK_OBJECT_CLASS(parent_class)->destroy)(object);
370 /* This is the last thing that happens to us. Free all data. */
371 static void collection_finalize(GtkObject *object)
373 Collection *collection;
375 collection = COLLECTION(object);
377 if (collection->vadj)
379 gtk_object_unref(GTK_OBJECT(collection->vadj));
382 g_free(collection->items);
385 static void collection_realize(GtkWidget *widget)
387 Collection *collection;
388 GdkWindowAttr attributes;
389 gint attributes_mask;
390 GdkGCValues xor_values;
392 g_return_if_fail(widget != NULL);
393 g_return_if_fail(IS_COLLECTION(widget));
394 g_return_if_fail(widget->parent != NULL);
396 GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);
397 collection = COLLECTION(widget);
399 attributes.x = widget->allocation.x;
400 attributes.y = widget->allocation.y;
401 attributes.width = widget->allocation.width;
402 attributes.height = widget->allocation.height;
403 attributes.wclass = GDK_INPUT_OUTPUT;
404 attributes.window_type = GDK_WINDOW_CHILD;
405 attributes.event_mask = gtk_widget_get_events(widget) |
406 GDK_EXPOSURE_MASK |
407 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
408 GDK_BUTTON1_MOTION_MASK | GDK_BUTTON2_MOTION_MASK |
409 GDK_BUTTON3_MOTION_MASK;
410 attributes.visual = gtk_widget_get_visual(widget);
411 attributes.colormap = gtk_widget_get_colormap(widget);
413 attributes_mask = GDK_WA_X | GDK_WA_Y |
414 GDK_WA_VISUAL | GDK_WA_COLORMAP;
415 widget->window = gdk_window_new(widget->parent->window,
416 &attributes, attributes_mask);
418 widget->style = gtk_style_attach(widget->style, widget->window);
420 gdk_window_set_user_data(widget->window, widget);
422 gdk_window_set_background(widget->window,
423 &widget->style->base[GTK_STATE_INSENSITIVE]);
425 /* Try to stop everything flickering horribly */
426 gdk_window_set_static_gravities(widget->window, TRUE);
428 set_vadjustment(collection);
430 xor_values.function = GDK_XOR;
431 xor_values.foreground.red = 0xffff;
432 xor_values.foreground.green = 0xffff;
433 xor_values.foreground.blue = 0xffff;
434 gdk_color_alloc(gtk_widget_get_colormap(widget),
435 &xor_values.foreground);
436 collection->xor_gc = gdk_gc_new_with_values(widget->window,
437 &xor_values,
438 GDK_GC_FOREGROUND
439 | GDK_GC_FUNCTION);
442 static void collection_size_request(GtkWidget *widget,
443 GtkRequisition *requisition)
445 requisition->width = MIN_WIDTH;
446 requisition->height = MIN_HEIGHT;
449 static void collection_size_allocate(GtkWidget *widget,
450 GtkAllocation *allocation)
452 Collection *collection;
453 int old_columns;
455 g_return_if_fail(widget != NULL);
456 g_return_if_fail(IS_COLLECTION(widget));
457 g_return_if_fail(allocation != NULL);
459 collection = COLLECTION(widget);
461 old_columns = collection->columns;
462 widget->allocation = *allocation;
464 collection->columns = allocation->width / collection->item_width;
465 if (collection->columns < 1)
466 collection->columns = 1;
468 if (GTK_WIDGET_REALIZED(widget))
470 gdk_window_move_resize(widget->window,
471 allocation->x, allocation->y,
472 allocation->width, allocation->height);
474 if (old_columns != collection->columns)
476 collection->paint_level = PAINT_CLEAR;
477 gtk_widget_queue_clear(widget);
480 set_vadjustment(collection);
482 if (collection->cursor_item != -1)
483 scroll_to_show(collection, collection->cursor_item);
487 static gint collection_paint(Collection *collection,
488 GdkRectangle *area)
490 GdkRectangle whole, item_area;
491 GtkWidget *widget;
492 int row, col;
493 int item;
494 int scroll;
495 int start_row, last_row;
496 int start_col, last_col;
497 int phys_last_col;
498 GdkRectangle clip;
500 scroll = collection->vadj->value;
502 widget = GTK_WIDGET(collection);
504 if (collection->paint_level > PAINT_NORMAL || area == NULL)
506 guint width, height;
507 gdk_window_get_size(widget->window, &width, &height);
509 whole.x = 0;
510 whole.y = 0;
511 whole.width = width;
512 whole.height = height;
514 area = &whole;
516 if (collection->paint_level == PAINT_CLEAR
517 && !collection->lasso_box)
518 gdk_window_clear(widget->window);
520 collection->paint_level = PAINT_NORMAL;
523 /* Calculate the ranges to plot */
524 start_row = (area->y + scroll) / collection->item_height;
525 last_row = (area->y + area->height - 1 + scroll)
526 / collection->item_height;
527 row = start_row;
529 start_col = area->x / collection->item_width;
530 phys_last_col = (area->x + area->width - 1) / collection->item_width;
532 if (collection->lasso_box)
534 /* You can't be too careful with lasso boxes...
536 * clip gives the total area drawn over (this may be larger
537 * than the requested area). It's used to redraw the lasso
538 * box.
540 clip.x = start_col * collection->item_width;
541 clip.y = start_row * collection->item_height - scroll;
542 clip.width = (phys_last_col - start_col + 1)
543 * collection->item_width;
544 clip.height = (last_row - start_row + 1)
545 * collection->item_height;
547 gdk_window_clear_area(widget->window,
548 clip.x, clip.y, clip.width, clip.height);
551 if (start_col < collection->columns)
553 if (phys_last_col >= collection->columns)
554 last_col = collection->columns - 1;
555 else
556 last_col = phys_last_col;
558 col = start_col;
560 item = row * collection->columns + col;
561 item_area.width = collection->item_width;
562 item_area.height = collection->item_height;
564 while ((item == 0 || item < collection->number_of_items)
565 && row <= last_row)
567 item_area.x = col * collection->item_width;
568 item_area.y = row * collection->item_height - scroll;
570 draw_one_item(collection, item, &item_area);
571 col++;
573 if (col > last_col)
575 col = start_col;
576 row++;
577 item = row * collection->columns + col;
579 else
580 item++;
584 if (collection->lasso_box)
586 gdk_gc_set_clip_rectangle(collection->xor_gc, &clip);
587 draw_lasso_box(collection);
588 gdk_gc_set_clip_rectangle(collection->xor_gc, NULL);
591 return FALSE;
594 static void default_draw_item( GtkWidget *widget,
595 CollectionItem *item,
596 GdkRectangle *area)
598 gdk_draw_arc(widget->window,
599 item->selected ? widget->style->white_gc
600 : widget->style->black_gc,
601 TRUE,
602 area->x, area->y,
603 area->width, area->height,
604 0, 360 * 64);
608 static gboolean default_test_point(Collection *collection,
609 int point_x, int point_y,
610 CollectionItem *item,
611 int width, int height)
613 float f_x, f_y;
615 /* Convert to point in unit circle */
616 f_x = ((float) point_x / width) - 0.5;
617 f_y = ((float) point_y / height) - 0.5;
619 return (f_x * f_x) + (f_y * f_y) <= .25;
622 static void collection_set_arg( GtkObject *object,
623 GtkArg *arg,
624 guint arg_id)
626 Collection *collection;
628 collection = COLLECTION(object);
630 switch (arg_id)
632 case ARG_VADJUSTMENT:
633 collection_set_adjustment(collection,
634 GTK_VALUE_POINTER(*arg));
635 break;
636 default:
637 break;
641 static void collection_set_adjustment( Collection *collection,
642 GtkAdjustment *vadj)
644 if (vadj)
645 g_return_if_fail (GTK_IS_ADJUSTMENT (vadj));
646 else
647 vadj = GTK_ADJUSTMENT(gtk_adjustment_new(0.0,
648 0.0, 0.0,
649 0.0, 0.0, 0.0));
650 if (collection->vadj && (collection->vadj != vadj))
652 gtk_signal_disconnect_by_data(GTK_OBJECT(collection->vadj),
653 collection);
654 gtk_object_unref(GTK_OBJECT(collection->vadj));
657 if (collection->vadj != vadj)
659 collection->vadj = vadj;
660 gtk_object_ref(GTK_OBJECT(collection->vadj));
661 gtk_object_sink(GTK_OBJECT(collection->vadj));
663 gtk_signal_connect(GTK_OBJECT(collection->vadj),
664 "changed",
665 (GtkSignalFunc) collection_adjustment,
666 collection);
667 gtk_signal_connect(GTK_OBJECT(collection->vadj),
668 "value_changed",
669 (GtkSignalFunc) collection_adjustment,
670 collection);
671 gtk_signal_connect(GTK_OBJECT(collection->vadj),
672 "disconnect",
673 (GtkSignalFunc) collection_disconnect,
674 collection);
675 collection_adjustment(vadj, collection);
679 static void collection_get_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 GTK_VALUE_POINTER(*arg) = collection->vadj;
691 break;
692 default:
693 arg->type = GTK_TYPE_INVALID;
694 break;
698 /* Something about the adjustment has changed */
699 static void collection_adjustment(GtkAdjustment *adjustment,
700 Collection *collection)
702 gint diff;
704 g_return_if_fail(adjustment != NULL);
705 g_return_if_fail(GTK_IS_ADJUSTMENT(adjustment));
706 g_return_if_fail(collection != NULL);
707 g_return_if_fail(IS_COLLECTION(collection));
709 diff = ((gint) adjustment->value) - collection->last_scroll;
711 if (diff)
713 collection->last_scroll = adjustment->value;
715 scroll_by(collection, diff);
719 static void collection_disconnect(GtkAdjustment *adjustment,
720 Collection *collection)
722 g_return_if_fail(adjustment != NULL);
723 g_return_if_fail(GTK_IS_ADJUSTMENT(adjustment));
724 g_return_if_fail(collection != NULL);
725 g_return_if_fail(IS_COLLECTION(collection));
727 collection_set_adjustment(collection, NULL);
730 static void set_vadjustment(Collection *collection)
732 GtkWidget *widget;
733 guint height;
734 int cols, rows;
736 widget = GTK_WIDGET(collection);
738 if (!GTK_WIDGET_REALIZED(widget))
739 return;
741 gdk_window_get_size(widget->window, NULL, &height);
742 cols = collection->columns;
743 rows = (collection->number_of_items + cols - 1) / cols;
745 collection->vadj->lower = 0.0;
746 collection->vadj->upper = collection->item_height * rows;
748 collection->vadj->step_increment =
749 MIN(collection->vadj->upper, collection->item_height / 4);
751 collection->vadj->page_increment =
752 MIN(collection->vadj->upper,
753 height - 5.0);
755 collection->vadj->page_size = MIN(collection->vadj->upper, height);
757 collection->vadj->value = MIN(collection->vadj->value,
758 collection->vadj->upper - collection->vadj->page_size);
760 collection->vadj->value = MAX(collection->vadj->value, 0.0);
762 gtk_signal_emit_by_name(GTK_OBJECT(collection->vadj), "changed");
765 static void collection_draw(GtkWidget *widget, GdkRectangle *area)
767 Collection *collection;
769 g_return_if_fail(widget != NULL);
770 g_return_if_fail(IS_COLLECTION(widget));
771 g_return_if_fail(area != NULL); /* Not actually used */
773 collection = COLLECTION(widget);
775 if (collection->paint_level > PAINT_NORMAL)
776 collection_paint(collection, area);
779 static gint collection_expose(GtkWidget *widget, GdkEventExpose *event)
781 g_return_val_if_fail(widget != NULL, FALSE);
782 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
783 g_return_val_if_fail(event != NULL, FALSE);
785 collection_paint(COLLECTION(widget), &event->area);
787 return FALSE;
790 /* Positive makes the contents go move up the screen */
791 static void scroll_by(Collection *collection, gint diff)
793 GtkWidget *widget;
794 guint width, height;
795 guint from_y, to_y;
796 guint amount;
797 GdkRectangle new_area;
799 if (diff == 0)
800 return;
802 widget = GTK_WIDGET(collection);
804 if (collection->lasso_box)
805 remove_lasso_box(collection);
807 gdk_window_get_size(widget->window, &width, &height);
808 new_area.x = 0;
809 new_area.width = width;
811 if (diff > 0)
813 amount = diff;
814 from_y = amount;
815 to_y = 0;
816 new_area.y = height - amount;
818 else
820 amount = -diff;
821 from_y = 0;
822 to_y = amount;
823 new_area.y = 0;
826 new_area.height = amount;
828 if (amount < height)
830 gdk_draw_pixmap(widget->window,
831 widget->style->white_gc,
832 widget->window,
834 from_y,
836 to_y,
837 width,
838 height - amount);
839 /* We have to redraw everything because any pending
840 * expose events now contain invalid areas.
841 * Don't need to clear the area first though...
843 if (collection->paint_level < PAINT_OVERWRITE)
844 collection->paint_level = PAINT_OVERWRITE;
846 else
847 collection->paint_level = PAINT_CLEAR;
849 gdk_window_clear_area(widget->window,
850 0, new_area.y,
851 width, new_area.height);
852 collection_paint(collection, NULL);
855 static void resize_arrays(Collection *collection, guint new_size)
857 g_return_if_fail(collection != NULL);
858 g_return_if_fail(IS_COLLECTION(collection));
859 g_return_if_fail(new_size >= collection->number_of_items);
861 collection->items = g_realloc(collection->items,
862 sizeof(CollectionItem) * new_size);
863 collection->array_size = new_size;
866 static void return_pressed(Collection *collection)
868 int item = collection->cursor_item;
869 CollectionTargetFunc cb = collection->target_cb;
870 gpointer data = collection->target_data;
872 collection_target(collection, NULL, NULL);
873 if (item < 0 || item >= collection->number_of_items)
874 return;
876 if (cb)
878 cb(collection, item, data);
879 return;
882 gtk_signal_emit(GTK_OBJECT(collection),
883 collection_signals[OPEN_ITEM],
884 collection->items[item].data,
885 item);
888 static gint collection_key_press(GtkWidget *widget, GdkEventKey *event)
890 Collection *collection;
891 int item;
893 g_return_val_if_fail(widget != NULL, FALSE);
894 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
895 g_return_val_if_fail(event != NULL, FALSE);
897 collection = (Collection *) widget;
898 item = collection->cursor_item;
900 switch (event->keyval)
902 case GDK_Left:
903 collection_move_cursor(collection, 0, -1);
904 break;
905 case GDK_Right:
906 collection_move_cursor(collection, 0, 1);
907 break;
908 case GDK_Up:
909 collection_move_cursor(collection, -1, 0);
910 break;
911 case GDK_Down:
912 collection_move_cursor(collection, 1, 0);
913 break;
914 case GDK_Home:
915 collection_set_cursor_item(collection, 0);
916 break;
917 case GDK_End:
918 collection_set_cursor_item(collection,
919 MAX((gint) collection->number_of_items - 1, 0));
920 break;
921 case GDK_Page_Up:
922 collection_move_cursor(collection, -10, 0);
923 break;
924 case GDK_Page_Down:
925 collection_move_cursor(collection, 10, 0);
926 break;
927 case GDK_Return:
928 return_pressed(collection);
929 break;
930 case GDK_Escape:
931 collection_target(collection, NULL, NULL);
932 break;
933 case ' ':
934 if (item >=0 && item < collection->number_of_items)
935 collection_toggle_item(collection, item);
936 break;
937 default:
938 return FALSE;
941 return TRUE;
944 static gint collection_button_press(GtkWidget *widget,
945 GdkEventButton *event)
947 Collection *collection;
948 int row, col;
949 int item;
950 int action;
951 int scroll;
952 guint stacked_time;
954 g_return_val_if_fail(widget != NULL, FALSE);
955 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
956 g_return_val_if_fail(event != NULL, FALSE);
958 collection = COLLECTION(widget);
960 collection->item_clicked = -1;
962 if (event->button > 3)
964 int diff;
966 /* Wheel mouse scrolling */
967 if (event->button == 4)
968 diff = -((signed int) collection->item_height) / 4;
969 else if (event->button == 5)
970 diff = collection->item_height / 4;
971 else
972 diff = 0;
974 if (diff)
976 int old_value = collection->vadj->value;
977 int new_value = 0;
978 gboolean box = collection->lasso_box;
980 new_value = CLAMP(old_value + diff, 0.0,
981 collection->vadj->upper
982 - collection->vadj->page_size);
983 diff = new_value - old_value;
984 if (diff)
986 if (box)
988 remove_lasso_box(collection);
989 collection->drag_box_y[0] -= diff;
991 collection->vadj->value = new_value;
992 gtk_signal_emit_by_name(
993 GTK_OBJECT(collection->vadj),
994 "changed");
995 if (box)
996 add_lasso_box(collection);
999 return FALSE;
1002 if (collection->cursor_item != -1)
1003 collection_set_cursor_item(collection, -1);
1005 scroll = collection->vadj->value;
1007 if (event->type == GDK_BUTTON_PRESS &&
1008 event->button != collection_menu_button)
1010 if (collection->buttons_pressed++ == 0)
1011 gtk_grab_add(widget);
1012 else
1013 return FALSE; /* Ignore extra presses */
1016 if (event->state & GDK_CONTROL_MASK && !collection_single_click)
1017 action = 2;
1018 else
1019 action = event->button;
1021 /* Ignore all clicks while we are dragging a lasso box */
1022 if (collection->lasso_box)
1023 return TRUE;
1025 col = event->x / collection->item_width;
1026 row = (event->y + scroll) / collection->item_height;
1028 if (col < 0 || row < 0 || col >= collection->columns)
1029 item = -1;
1030 else
1032 item = col + row * collection->columns;
1033 if (item >= collection->number_of_items
1035 !collection->test_point(collection,
1036 event->x - col * collection->item_width,
1037 event->y - row * collection->item_height
1038 + scroll,
1039 &collection->items[item],
1040 collection->item_width,
1041 collection->item_height))
1043 item = -1;
1047 if (collection->target_cb)
1049 CollectionTargetFunc cb = collection->target_cb;
1050 gpointer data = collection->target_data;
1052 collection_target(collection, NULL, NULL);
1053 if (collection->buttons_pressed)
1055 gtk_grab_remove(widget);
1056 collection->buttons_pressed = 0;
1058 if (item > -1 && event->button != collection_menu_button)
1059 cb(collection, item, data);
1060 return TRUE;
1063 collection->drag_box_x[0] = event->x;
1064 collection->drag_box_y[0] = event->y;
1065 collection->item_clicked = item;
1067 stacked_time = current_event_time;
1068 current_event_time = event->time;
1070 if (event->button == collection_menu_button)
1072 gtk_signal_emit(GTK_OBJECT(collection),
1073 collection_signals[SHOW_MENU],
1074 event,
1075 item);
1077 else if (event->type == GDK_2BUTTON_PRESS && collection->panel)
1079 /* Do nothing */
1081 else if ((event->type == GDK_2BUTTON_PRESS && !collection_single_click)
1082 || collection->panel)
1084 if (item >= 0)
1086 if (collection->buttons_pressed)
1088 gtk_grab_remove(widget);
1089 collection->buttons_pressed = 0;
1091 collection_unselect_item(collection, item);
1092 gtk_signal_emit(GTK_OBJECT(collection),
1093 collection_signals[OPEN_ITEM],
1094 collection->items[item].data,
1095 item);
1098 else if (event->type == GDK_BUTTON_PRESS)
1100 collection->may_drag = event->button < collection_menu_button;
1102 if (item >= 0)
1104 if (action == 1)
1106 if (!collection->items[item].selected)
1108 collection_select_item(collection,
1109 item);
1110 collection_clear_except(collection,
1111 item);
1114 else
1115 collection_toggle_item(collection, item);
1117 else if (action == 1)
1118 collection_clear_selection(collection);
1121 current_event_time = stacked_time;
1122 return FALSE;
1125 static gint collection_button_release(GtkWidget *widget,
1126 GdkEventButton *event)
1128 Collection *collection;
1129 int top, bottom;
1130 int row, last_row;
1131 int w, h;
1132 int col, start_col, last_col;
1133 int scroll;
1134 int item;
1135 guint stacked_time;
1136 int button;
1138 g_return_val_if_fail(widget != NULL, FALSE);
1139 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1140 g_return_val_if_fail(event != NULL, FALSE);
1142 collection = COLLECTION(widget);
1143 button = event->button;
1145 scroll = collection->vadj->value;
1147 if (event->button > 3 || event->button == collection_menu_button)
1148 return FALSE;
1149 if (collection->buttons_pressed == 0)
1150 return FALSE;
1151 if (--collection->buttons_pressed == 0)
1152 gtk_grab_remove(widget);
1153 else
1154 return FALSE; /* Wait until ALL buttons are up */
1156 if (!collection->lasso_box)
1158 int item = collection->item_clicked;
1160 if (collection_single_click && item > -1
1161 && item < collection->number_of_items
1162 && (event->state & GDK_CONTROL_MASK) == 0)
1164 int dx = event->x - collection->drag_box_x[0];
1165 int dy = event->y - collection->drag_box_y[0];
1167 if (ABS(dx) + ABS(dy) > 9)
1168 return FALSE;
1170 collection_unselect_item(collection, item);
1171 gtk_signal_emit(GTK_OBJECT(collection),
1172 collection_signals[OPEN_ITEM],
1173 collection->items[item].data,
1174 item);
1177 return FALSE;
1180 remove_lasso_box(collection);
1182 w = collection->item_width;
1183 h = collection->item_height;
1185 top = collection->drag_box_y[0] + scroll;
1186 bottom = collection->drag_box_y[1] + scroll;
1187 if (top > bottom)
1189 int tmp;
1190 tmp = top;
1191 top = bottom;
1192 bottom = tmp;
1194 top += h / 4;
1195 bottom -= h / 4;
1197 row = MAX(top / h, 0);
1198 last_row = bottom / h;
1200 top = collection->drag_box_x[0]; /* (left) */
1201 bottom = collection->drag_box_x[1];
1202 if (top > bottom)
1204 int tmp;
1205 tmp = top;
1206 top = bottom;
1207 bottom = tmp;
1209 top += w / 4;
1210 bottom -= w / 4;
1211 start_col = MAX(top / w, 0);
1212 last_col = bottom / w;
1213 if (last_col >= collection->columns)
1214 last_col = collection->columns - 1;
1216 stacked_time = current_event_time;
1217 current_event_time = event->time;
1219 while (row <= last_row)
1221 col = start_col;
1222 item = row * collection->columns + col;
1223 while (col <= last_col)
1225 if (item >= collection->number_of_items)
1227 current_event_time = stacked_time;
1228 return FALSE;
1231 if (button == 1)
1232 collection_select_item(collection, item);
1233 else
1234 collection_toggle_item(collection, item);
1235 col++;
1236 item++;
1238 row++;
1241 current_event_time = stacked_time;
1243 return FALSE;
1246 static gint collection_motion_notify(GtkWidget *widget,
1247 GdkEventMotion *event)
1249 Collection *collection;
1250 int x, y;
1251 guint stacked_time;
1253 g_return_val_if_fail(widget != NULL, FALSE);
1254 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1255 g_return_val_if_fail(event != NULL, FALSE);
1257 collection = COLLECTION(widget);
1259 if (collection->buttons_pressed == 0)
1260 return FALSE;
1262 stacked_time = current_event_time;
1263 current_event_time = event->time;
1265 if (event->window != widget->window)
1266 gdk_window_get_pointer(widget->window, &x, &y, NULL);
1267 else
1269 x = event->x;
1270 y = event->y;
1273 if (collection->lasso_box)
1275 int new_value = 0, diff;
1276 int height;
1278 gdk_window_get_size(widget->window, NULL, &height);
1280 if (y < 0)
1282 int old_value = collection->vadj->value;
1284 new_value = MAX(old_value + y / 10, 0.0);
1285 diff = new_value - old_value;
1287 else if (y > height)
1289 int old_value = collection->vadj->value;
1291 new_value = MIN(old_value + (y - height) / 10,
1292 collection->vadj->upper
1293 - collection->vadj->page_size);
1294 diff = new_value - old_value;
1296 else
1297 diff = 0;
1299 remove_lasso_box(collection);
1300 collection->drag_box_x[1] = x;
1301 collection->drag_box_y[1] = y;
1303 if (diff)
1305 collection->drag_box_y[0] -= diff;
1306 collection->vadj->value = new_value;
1307 gtk_signal_emit_by_name(GTK_OBJECT(collection->vadj),
1308 "changed");
1310 add_lasso_box(collection);
1312 else if (collection->may_drag)
1314 int dx = x - collection->drag_box_x[0];
1315 int dy = y - collection->drag_box_y[0];
1317 if (abs(dx) > 9 || abs(dy) > 9)
1319 int row, col, item;
1320 int scroll = collection->vadj->value;
1322 collection->may_drag = FALSE;
1324 col = collection->drag_box_x[0]
1325 / collection->item_width;
1326 row = (collection->drag_box_y[0] + scroll)
1327 / collection->item_height;
1328 item = item_at_row_col(collection, row, col);
1330 if (item != -1 && collection->test_point(collection,
1331 collection->drag_box_x[0] -
1332 col * collection->item_width,
1333 collection->drag_box_y[0]
1334 - row * collection->item_height
1335 + scroll,
1336 &collection->items[item],
1337 collection->item_width,
1338 collection->item_height))
1340 collection->buttons_pressed = 0;
1341 gtk_grab_remove(widget);
1342 collection_select_item(collection, item);
1343 gtk_signal_emit(GTK_OBJECT(collection),
1344 collection_signals[DRAG_SELECTION],
1345 event,
1346 collection->number_selected);
1348 else
1350 collection->drag_box_x[1] = x;
1351 collection->drag_box_y[1] = y;
1352 add_lasso_box(collection);
1357 current_event_time = stacked_time;
1358 return FALSE;
1361 static void add_lasso_box(Collection *collection)
1363 g_return_if_fail(collection != NULL);
1364 g_return_if_fail(IS_COLLECTION(collection));
1365 g_return_if_fail(collection->lasso_box == FALSE);
1367 collection->lasso_box = TRUE;
1368 draw_lasso_box(collection);
1371 static void draw_lasso_box(Collection *collection)
1373 GtkWidget *widget;
1374 int x, y, width, height;
1376 widget = GTK_WIDGET(collection);
1378 x = MIN(collection->drag_box_x[0], collection->drag_box_x[1]);
1379 y = MIN(collection->drag_box_y[0], collection->drag_box_y[1]);
1380 width = abs(collection->drag_box_x[1] - collection->drag_box_x[0]);
1381 height = abs(collection->drag_box_y[1] - collection->drag_box_y[0]);
1383 gdk_draw_rectangle(widget->window, collection->xor_gc, FALSE,
1384 x, y, width, height);
1387 static void remove_lasso_box(Collection *collection)
1389 g_return_if_fail(collection != NULL);
1390 g_return_if_fail(IS_COLLECTION(collection));
1391 g_return_if_fail(collection->lasso_box == TRUE);
1393 draw_lasso_box(collection);
1395 collection->lasso_box = FALSE;
1397 return;
1400 /* Convert a row,col address to an item number, or -1 if none */
1401 static int item_at_row_col(Collection *collection, int row, int col)
1403 int item;
1405 if (row < 0 || col < 0 || col >= collection->columns)
1406 return -1;
1408 item = col + row * collection->columns;
1410 if (item >= collection->number_of_items)
1411 return -1;
1412 return item;
1415 /* Make sure that 'item' is fully visible (vertically), scrolling if not. */
1416 static void scroll_to_show(Collection *collection, int item)
1418 int first, last, row;
1420 g_return_if_fail(collection != NULL);
1421 g_return_if_fail(IS_COLLECTION(collection));
1423 row = item / collection->columns;
1424 get_visible_limits(collection, &first, &last);
1426 if (row <= first)
1428 gtk_adjustment_set_value(collection->vadj,
1429 row * collection->item_height);
1431 else if (row >= last)
1433 GtkWidget *widget = (GtkWidget *) collection;
1434 int height;
1436 if (GTK_WIDGET_REALIZED(widget))
1438 gdk_window_get_size(widget->window, NULL, &height);
1439 gtk_adjustment_set_value(collection->vadj,
1440 (row + 1) * collection->item_height - height);
1445 /* Return the first and last rows which are [partly] visible. Does not
1446 * ensure that the rows actually exist (contain items).
1448 static void get_visible_limits(Collection *collection, int *first, int *last)
1450 GtkWidget *widget = (GtkWidget *) collection;
1451 int scroll, height;
1453 g_return_if_fail(collection != NULL);
1454 g_return_if_fail(IS_COLLECTION(collection));
1455 g_return_if_fail(first != NULL && last != NULL);
1457 if (!GTK_WIDGET_REALIZED(widget))
1459 *first = 0;
1460 *last = 0;
1462 else
1464 scroll = collection->vadj->value;
1465 gdk_window_get_size(widget->window, NULL, &height);
1467 *first = MAX(scroll / collection->item_height, 0);
1468 *last = (scroll + height - 1) /collection->item_height;
1470 if (*last < *first)
1471 *last = *first;
1475 /* Unselect all items except number item (-1 to unselect everything) */
1476 static void collection_clear_except(Collection *collection, gint exception)
1478 GtkWidget *widget;
1479 GdkRectangle area;
1480 int item = 0;
1481 int scroll;
1482 int end; /* Selected items to end up with */
1484 widget = GTK_WIDGET(collection);
1485 scroll = collection->vadj->value;
1487 end = exception >= 0 && exception < collection->number_of_items
1488 ? collection->items[exception].selected != 0 : 0;
1490 area.width = collection->item_width;
1491 area.height = collection->item_height;
1493 if (collection->number_selected == 0)
1494 return;
1496 while (collection->number_selected > end)
1498 while (item == exception || !collection->items[item].selected)
1499 item++;
1501 area.x = (item % collection->columns) * area.width;
1502 area.y = (item / collection->columns) * area.height
1503 - scroll;
1505 collection->items[item++].selected = FALSE;
1506 gdk_window_clear_area(widget->window,
1507 area.x, area.y, area.width, area.height);
1508 collection_paint(collection, &area);
1510 collection->number_selected--;
1513 if (end == 0)
1514 gtk_signal_emit(GTK_OBJECT(collection),
1515 collection_signals[LOSE_SELECTION],
1516 current_event_time);
1519 /* Cancel the current wink effect. */
1520 static void cancel_wink(Collection *collection)
1522 gint item;
1524 g_return_if_fail(collection != NULL);
1525 g_return_if_fail(IS_COLLECTION(collection));
1526 g_return_if_fail(collection->wink_item != -1);
1528 item = collection->wink_item;
1530 collection->wink_item = -1;
1531 gtk_timeout_remove(collection->wink_timeout);
1533 collection_draw_item(collection, item, TRUE);
1536 static gboolean cancel_wink_timeout(Collection *collection)
1538 gint item;
1540 g_return_val_if_fail(collection != NULL, FALSE);
1541 g_return_val_if_fail(IS_COLLECTION(collection), FALSE);
1542 g_return_val_if_fail(collection->wink_item != -1, FALSE);
1544 item = collection->wink_item;
1546 collection->wink_item = -1;
1548 collection_draw_item(collection, item, TRUE);
1550 return FALSE;
1553 /* Functions for managing collections */
1555 /* Remove all objects from the collection */
1556 void collection_clear(Collection *collection)
1558 int prev_selected;
1560 g_return_if_fail(IS_COLLECTION(collection));
1562 if (collection->number_of_items == 0)
1563 return;
1565 if (collection->wink_item != -1)
1567 collection->wink_item = -1;
1568 gtk_timeout_remove(collection->wink_timeout);
1571 collection_set_cursor_item(collection,
1572 collection->cursor_item == -1 ? -1: 0);
1573 prev_selected = collection->number_selected;
1574 collection->number_of_items = collection->number_selected = 0;
1576 resize_arrays(collection, MINIMUM_ITEMS);
1578 collection->paint_level = PAINT_CLEAR;
1580 gtk_widget_queue_clear(GTK_WIDGET(collection));
1582 if (prev_selected && collection->number_selected == 0)
1583 gtk_signal_emit(GTK_OBJECT(collection),
1584 collection_signals[LOSE_SELECTION],
1585 current_event_time);
1588 /* Inserts a new item at the end. The new item is unselected, and its
1589 * number is returned.
1591 gint collection_insert(Collection *collection, gpointer data)
1593 int item;
1595 g_return_val_if_fail(IS_COLLECTION(collection), -1);
1597 item = collection->number_of_items;
1599 if (item >= collection->array_size)
1600 resize_arrays(collection, item + (item >> 1));
1602 collection->items[item].data = data;
1603 collection->items[item].selected = FALSE;
1605 collection->number_of_items++;
1607 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection)))
1609 set_vadjustment(collection);
1610 collection_draw_item(collection,
1611 collection->number_of_items - 1,
1612 FALSE);
1615 return item;
1618 /* Unselect an item by number */
1619 void collection_unselect_item(Collection *collection, gint item)
1621 g_return_if_fail(collection != NULL);
1622 g_return_if_fail(IS_COLLECTION(collection));
1623 g_return_if_fail(item >= 0 && item < collection->number_of_items);
1625 if (collection->items[item].selected)
1627 collection->items[item].selected = FALSE;
1628 collection_draw_item(collection, item, TRUE);
1630 if (--collection->number_selected == 0)
1631 gtk_signal_emit(GTK_OBJECT(collection),
1632 collection_signals[LOSE_SELECTION],
1633 current_event_time);
1637 /* Select an item by number */
1638 void collection_select_item(Collection *collection, gint item)
1640 g_return_if_fail(collection != NULL);
1641 g_return_if_fail(IS_COLLECTION(collection));
1642 g_return_if_fail(item >= 0 && item < collection->number_of_items);
1644 if (collection->items[item].selected)
1645 return; /* Already selected */
1647 collection->items[item].selected = TRUE;
1648 collection_draw_item(collection, item, TRUE);
1650 if (collection->number_selected++ == 0)
1651 gtk_signal_emit(GTK_OBJECT(collection),
1652 collection_signals[GAIN_SELECTION],
1653 current_event_time);
1656 /* Toggle the selected state of an item (by number) */
1657 void collection_toggle_item(Collection *collection, gint item)
1659 g_return_if_fail(collection != NULL);
1660 g_return_if_fail(IS_COLLECTION(collection));
1661 g_return_if_fail(item >= 0 && item < collection->number_of_items);
1663 if (collection->items[item].selected)
1665 collection->items[item].selected = FALSE;
1666 if (--collection->number_selected == 0)
1667 gtk_signal_emit(GTK_OBJECT(collection),
1668 collection_signals[LOSE_SELECTION],
1669 current_event_time);
1671 else
1673 collection->items[item].selected = TRUE;
1674 if (collection->number_selected++ == 0)
1675 gtk_signal_emit(GTK_OBJECT(collection),
1676 collection_signals[GAIN_SELECTION],
1677 current_event_time);
1679 collection_draw_item(collection, item, TRUE);
1682 /* Select all items in the collection */
1683 void collection_select_all(Collection *collection)
1685 GtkWidget *widget;
1686 GdkRectangle area;
1687 int item = 0;
1688 int scroll;
1690 g_return_if_fail(collection != NULL);
1691 g_return_if_fail(IS_COLLECTION(collection));
1693 widget = GTK_WIDGET(collection);
1694 scroll = collection->vadj->value;
1696 area.width = collection->item_width;
1697 area.height = collection->item_height;
1699 if (collection->number_selected == collection->number_of_items)
1700 return; /* Nothing to do */
1702 while (collection->number_selected < collection->number_of_items)
1704 while (collection->items[item].selected)
1705 item++;
1707 area.x = (item % collection->columns) * area.width;
1708 area.y = (item / collection->columns) * area.height
1709 - scroll;
1711 collection->items[item++].selected = TRUE;
1712 gdk_window_clear_area(widget->window,
1713 area.x, area.y, area.width, area.height);
1714 collection_paint(collection, &area);
1716 collection->number_selected++;
1719 gtk_signal_emit(GTK_OBJECT(collection),
1720 collection_signals[GAIN_SELECTION],
1721 current_event_time);
1724 /* Unselect all items in the collection */
1725 void collection_clear_selection(Collection *collection)
1727 g_return_if_fail(collection != NULL);
1728 g_return_if_fail(IS_COLLECTION(collection));
1730 collection_clear_except(collection, -1);
1733 /* Force a redraw of the specified item, if it is visible */
1734 void collection_draw_item(Collection *collection, gint item, gboolean blank)
1736 int height;
1737 GdkRectangle area;
1738 GtkWidget *widget;
1739 int row, col;
1740 int scroll;
1741 int area_y, area_height; /* NOT shorts! */
1743 g_return_if_fail(collection != NULL);
1744 g_return_if_fail(IS_COLLECTION(collection));
1745 g_return_if_fail(item >= 0 &&
1746 (item == 0 || item < collection->number_of_items));
1748 widget = GTK_WIDGET(collection);
1749 if (!GTK_WIDGET_REALIZED(widget))
1750 return;
1752 col = item % collection->columns;
1753 row = item / collection->columns;
1754 scroll = collection->vadj->value; /* (round to int) */
1756 area.x = col * collection->item_width;
1757 area_y = row * collection->item_height - scroll;
1758 area.width = collection->item_width;
1759 area_height = collection->item_height;
1761 if (area_y + area_height < 0)
1762 return;
1764 gdk_window_get_size(widget->window, NULL, &height);
1766 if (area_y > height)
1767 return;
1769 area.y = area_y;
1770 area.height = area_height;
1772 if (blank || collection->lasso_box)
1773 gdk_window_clear_area(widget->window,
1774 area.x, area.y, area.width, area.height);
1776 draw_one_item(collection, item, &area);
1778 if (collection->lasso_box)
1780 gdk_gc_set_clip_rectangle(collection->xor_gc, &area);
1781 draw_lasso_box(collection);
1782 gdk_gc_set_clip_rectangle(collection->xor_gc, NULL);
1786 void collection_set_item_size(Collection *collection, int width, int height)
1788 GtkWidget *widget;
1790 g_return_if_fail(collection != NULL);
1791 g_return_if_fail(IS_COLLECTION(collection));
1792 g_return_if_fail(width > 4 && height > 4);
1794 widget = GTK_WIDGET(collection);
1796 collection->item_width = width;
1797 collection->item_height = height;
1799 if (GTK_WIDGET_REALIZED(widget))
1801 int window_width;
1803 collection->paint_level = PAINT_CLEAR;
1804 gdk_window_get_size(widget->window, &window_width, NULL);
1805 collection->columns = MAX(window_width / collection->item_width,
1808 set_vadjustment(collection);
1809 if (collection->cursor_item != -1)
1810 scroll_to_show(collection, collection->cursor_item);
1811 gtk_widget_queue_draw(widget);
1815 /* Cursor is positioned on item with the same data as before the sort.
1816 * Same for the wink item.
1818 void collection_qsort(Collection *collection,
1819 int (*compar)(const void *, const void *))
1821 int cursor, wink, items;
1822 gpointer cursor_data = NULL;
1823 gpointer wink_data = NULL;
1825 g_return_if_fail(collection != NULL);
1826 g_return_if_fail(IS_COLLECTION(collection));
1827 g_return_if_fail(compar != NULL);
1829 items = collection->number_of_items;
1831 wink = collection->wink_item;
1832 if (wink >= 0 && wink < items)
1833 wink_data = collection->items[wink].data;
1834 else
1835 wink = -1;
1837 cursor = collection->cursor_item;
1838 if (cursor >= 0 && cursor < items)
1839 cursor_data = collection->items[cursor].data;
1840 else
1841 cursor = -1;
1843 if (collection->wink_item != -1)
1845 collection->wink_item = -1;
1846 gtk_timeout_remove(collection->wink_timeout);
1849 qsort(collection->items, items, sizeof(collection->items[0]), compar);
1851 if (cursor > -1 || wink > -1)
1853 int item;
1855 for (item = 0; item < items; item++)
1857 if (collection->items[item].data == cursor_data)
1858 collection_set_cursor_item(collection, item);
1859 if (collection->items[item].data == wink_data)
1860 collection_wink_item(collection, item);
1864 collection->paint_level = PAINT_CLEAR;
1866 gtk_widget_queue_draw(GTK_WIDGET(collection));
1869 /* Find an item in an unsorted collection.
1870 * Returns the item number, or -1 if not found.
1872 int collection_find_item(Collection *collection, gpointer data,
1873 int (*compar)(const void *, const void *))
1875 int i;
1877 g_return_val_if_fail(collection != NULL, -1);
1878 g_return_val_if_fail(IS_COLLECTION(collection), -1);
1879 g_return_val_if_fail(compar != NULL, -1);
1881 for (i = 0; i < collection->number_of_items; i++)
1882 if (compar(&collection->items[i].data, &data) == 0)
1883 return i;
1885 return -1;
1888 /* The collection may be in either normal mode or panel mode.
1889 * In panel mode:
1890 * - a single click calls open_item
1891 * - items are never selected
1892 * - lasso boxes are disabled
1894 void collection_set_panel(Collection *collection, gboolean panel)
1896 g_return_if_fail(collection != NULL);
1897 g_return_if_fail(IS_COLLECTION(collection));
1899 collection->panel = panel == TRUE;
1901 if (collection->panel)
1903 collection_clear_selection(collection);
1904 if (collection->lasso_box)
1905 remove_lasso_box(collection);
1909 /* Return the number of the item under the point (x,y), or -1 for none.
1910 * This may call your test_point callback. The point is relative to the
1911 * collection's origin.
1913 int collection_get_item(Collection *collection, int x, int y)
1915 int scroll;
1916 int row, col;
1917 int item;
1919 g_return_val_if_fail(collection != NULL, -1);
1921 scroll = collection->vadj->value;
1922 col = x / collection->item_width;
1923 row = (y + scroll) / collection->item_height;
1925 if (col < 0 || row < 0 || col >= collection->columns)
1926 return -1;
1928 item = col + row * collection->columns;
1929 if (item >= collection->number_of_items
1931 !collection->test_point(collection,
1932 x - col * collection->item_width,
1933 y - row * collection->item_height
1934 + scroll,
1935 &collection->items[item],
1936 collection->item_width,
1937 collection->item_height))
1939 return -1;
1942 return item;
1945 /* Set the cursor/highlight over the given item. Passing -1
1946 * hides the cursor. As a special case, you may set the cursor item
1947 * to zero when there are no items.
1949 void collection_set_cursor_item(Collection *collection, gint item)
1951 int old_item;
1953 g_return_if_fail(collection != NULL);
1954 g_return_if_fail(IS_COLLECTION(collection));
1955 g_return_if_fail(item >= -1 &&
1956 (item < (int) collection->number_of_items || item == 0));
1958 old_item = collection->cursor_item;
1960 if (old_item == item)
1961 return;
1963 collection->cursor_item = item;
1965 if (old_item != -1)
1966 collection_draw_item(collection, old_item, TRUE);
1968 if (item != -1)
1970 collection_draw_item(collection, item, TRUE);
1971 scroll_to_show(collection, item);
1975 /* Briefly highlight an item to draw the user's attention to it.
1976 * -1 cancels the effect, as does deleting items, sorting the collection
1977 * or starting a new wink effect.
1978 * Otherwise, the effect will cancel itself after a short pause.
1979 * */
1980 void collection_wink_item(Collection *collection, gint item)
1982 g_return_if_fail(collection != NULL);
1983 g_return_if_fail(IS_COLLECTION(collection));
1984 g_return_if_fail(item >= -1 && item < collection->number_of_items);
1986 if (collection->wink_item != -1)
1987 cancel_wink(collection);
1988 if (item == -1)
1989 return;
1991 collection->wink_item = item;
1992 collection->wink_timeout = gtk_timeout_add(300,
1993 (GtkFunction) cancel_wink_timeout,
1994 collection);
1995 collection_draw_item(collection, item, TRUE);
1996 scroll_to_show(collection, item);
1998 gdk_flush();
2001 /* Call test(item, data) on each item in the collection.
2002 * Remove all items for which it returns TRUE. test() should
2003 * free the data before returning TRUE. The collection is in an
2004 * inconsistant state during this call (ie, when test() is called).
2006 void collection_delete_if(Collection *collection,
2007 gboolean (*test)(gpointer item, gpointer data),
2008 gpointer data)
2010 int in, out = 0;
2011 int selected = 0;
2012 int cursor;
2014 g_return_if_fail(collection != NULL);
2015 g_return_if_fail(IS_COLLECTION(collection));
2016 g_return_if_fail(test != NULL);
2018 cursor = collection->cursor_item;
2020 for (in = 0; in < collection->number_of_items; in++)
2022 if (!test(collection->items[in].data, data))
2024 if (collection->items[in].selected)
2026 collection->items[out].selected = TRUE;
2027 selected++;
2029 else
2030 collection->items[out].selected = FALSE;
2032 collection->items[out++].data =
2033 collection->items[in].data;
2035 else if (cursor >= in)
2036 cursor--;
2039 if (in != out)
2041 collection->cursor_item = cursor;
2043 if (collection->wink_item != -1)
2045 collection->wink_item = -1;
2046 gtk_timeout_remove(collection->wink_timeout);
2049 collection->number_of_items = out;
2050 collection->number_selected = selected;
2051 resize_arrays(collection,
2052 MAX(collection->number_of_items, MINIMUM_ITEMS));
2054 collection->paint_level = PAINT_CLEAR;
2056 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection)))
2058 set_vadjustment(collection);
2059 gtk_widget_queue_draw(GTK_WIDGET(collection));
2064 /* Display a cross-hair pointer and the next time an item is clicked,
2065 * call the callback function. If the background is clicked or NULL
2066 * is passed as the callback then revert to normal operation.
2068 void collection_target(Collection *collection,
2069 CollectionTargetFunc callback,
2070 gpointer user_data)
2072 g_return_if_fail(collection != NULL);
2073 g_return_if_fail(IS_COLLECTION(collection));
2075 if (callback != collection->target_cb)
2076 gdk_window_set_cursor(GTK_WIDGET(collection)->window,
2077 callback ? crosshair : NULL);
2079 collection->target_cb = callback;
2080 collection->target_data = user_data;
2082 if (collection->cursor_item != -1)
2083 collection_draw_item(collection, collection->cursor_item,
2084 FALSE);
2087 /* Move the cursor by the given row and column offsets. */
2088 void collection_move_cursor(Collection *collection, int drow, int dcol)
2090 int row, col, item;
2091 int first, last, total_rows;
2093 g_return_if_fail(collection != NULL);
2094 g_return_if_fail(IS_COLLECTION(collection));
2096 get_visible_limits(collection, &first, &last);
2098 item = collection->cursor_item;
2099 if (item == -1)
2101 col = 0;
2102 row = first;
2104 else
2106 row = item / collection->columns;
2107 col = item % collection->columns;
2109 if (row < first)
2110 row = first;
2111 else if (row > last)
2112 row = last;
2113 else
2114 row = MAX(row + drow, 0);
2116 col = MAX(col + dcol, 0);
2119 if (col >= collection->columns)
2120 col = collection->columns - 1;
2122 total_rows = (collection->number_of_items + collection->columns - 1)
2123 / collection->columns;
2125 if (row >= total_rows - 1)
2127 row = total_rows - 1;
2128 item = col + row * collection->columns;
2129 if (item >= collection->number_of_items)
2130 row--;
2132 if (row < 0)
2133 row = 0;
2135 item = col + row * collection->columns;
2137 if (item >= 0 && item < collection->number_of_items)
2138 collection_set_cursor_item(collection, item);