r210: Added 'Permissions' (chmod) feature.
[rox-filer.git] / ROX-Filer / src / collection.c
blob8985f0ff6d8b32e5db0460c1b2211a9136dacbbf
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);
144 static void draw_one_item(Collection *collection, int item, GdkRectangle *area)
146 if (item < collection->number_of_items)
148 collection->draw_item((GtkWidget *) collection,
149 &collection->items[item],
150 area);
151 if (item == collection->wink_item)
152 gdk_draw_rectangle(((GtkWidget *) collection)->window,
153 ((GtkWidget *) collection)->style->black_gc,
154 FALSE,
155 area->x, area->y,
156 area->width - 1, area->height - 1);
158 if (item == collection->cursor_item)
160 gdk_draw_rectangle(((GtkWidget *) collection)->window,
161 collection->target_cb
162 ? ((GtkWidget *) collection)->style->white_gc
163 : ((GtkWidget *) collection)->style->black_gc,
164 FALSE,
165 area->x + 1, area->y + 1,
166 area->width - 3, area->height - 3);
170 GtkType collection_get_type(void)
172 static guint my_type = 0;
174 if (!my_type)
176 static const GtkTypeInfo my_info =
178 "Collection",
179 sizeof(Collection),
180 sizeof(CollectionClass),
181 (GtkClassInitFunc) collection_class_init,
182 (GtkObjectInitFunc) collection_init,
183 NULL, /* Reserved 1 */
184 NULL, /* Reserved 2 */
185 (GtkClassInitFunc) NULL /* base_class_init_func */
188 my_type = gtk_type_unique(gtk_widget_get_type(),
189 &my_info);
192 return my_type;
195 static void collection_class_init(CollectionClass *class)
197 GtkObjectClass *object_class;
198 GtkWidgetClass *widget_class;
200 object_class = (GtkObjectClass*) class;
201 widget_class = (GtkWidgetClass*) class;
203 parent_class = gtk_type_class(gtk_widget_get_type());
205 gtk_object_add_arg_type("Collection::vadjustment",
206 GTK_TYPE_ADJUSTMENT,
207 GTK_ARG_READWRITE | GTK_ARG_CONSTRUCT,
208 ARG_VADJUSTMENT);
210 object_class->destroy = collection_destroy;
211 object_class->finalize = collection_finalize;
213 widget_class->realize = collection_realize;
214 widget_class->draw = collection_draw;
215 widget_class->expose_event = collection_expose;
216 widget_class->size_request = collection_size_request;
217 widget_class->size_allocate = collection_size_allocate;
218 widget_class->style_set = collection_set_style;
220 widget_class->key_press_event = collection_key_press;
221 widget_class->button_press_event = collection_button_press;
222 widget_class->button_release_event = collection_button_release;
223 widget_class->motion_notify_event = collection_motion_notify;
224 widget_class->focus_in_event = focus_in;
225 widget_class->focus_out_event = focus_out;
227 object_class->set_arg = collection_set_arg;
228 object_class->get_arg = collection_get_arg;
230 class->open_item = NULL;
231 class->drag_selection = NULL;
232 class->show_menu = NULL;
233 class->gain_selection = NULL;
234 class->lose_selection = NULL;
236 collection_signals[OPEN_ITEM] = gtk_signal_new("open_item",
237 GTK_RUN_LAST,
238 object_class->type,
239 GTK_SIGNAL_OFFSET(CollectionClass,
240 open_item),
241 gtk_marshal_NONE__POINTER_UINT,
242 GTK_TYPE_NONE, 2,
243 GTK_TYPE_POINTER,
244 GTK_TYPE_UINT);
245 collection_signals[DRAG_SELECTION] = gtk_signal_new("drag_selection",
246 GTK_RUN_LAST,
247 object_class->type,
248 GTK_SIGNAL_OFFSET(CollectionClass,
249 drag_selection),
250 gtk_marshal_NONE__POINTER_UINT,
251 GTK_TYPE_NONE, 2,
252 GTK_TYPE_POINTER,
253 GTK_TYPE_UINT);
254 collection_signals[SHOW_MENU] = gtk_signal_new("show_menu",
255 GTK_RUN_LAST,
256 object_class->type,
257 GTK_SIGNAL_OFFSET(CollectionClass,
258 show_menu),
259 gtk_marshal_NONE__POINTER_INT,
260 GTK_TYPE_NONE, 2,
261 GTK_TYPE_POINTER,
262 GTK_TYPE_INT);
263 collection_signals[GAIN_SELECTION] = gtk_signal_new("gain_selection",
264 GTK_RUN_LAST,
265 object_class->type,
266 GTK_SIGNAL_OFFSET(CollectionClass,
267 gain_selection),
268 gtk_marshal_NONE__UINT,
269 GTK_TYPE_NONE, 1,
270 GTK_TYPE_UINT);
271 collection_signals[LOSE_SELECTION] = gtk_signal_new("lose_selection",
272 GTK_RUN_LAST,
273 object_class->type,
274 GTK_SIGNAL_OFFSET(CollectionClass,
275 lose_selection),
276 gtk_marshal_NONE__UINT,
277 GTK_TYPE_NONE, 1,
278 GTK_TYPE_UINT);
280 gtk_object_class_add_signals(object_class,
281 collection_signals, LAST_SIGNAL);
284 static void collection_init(Collection *object)
286 g_return_if_fail(object != NULL);
287 g_return_if_fail(IS_COLLECTION(object));
289 if (!crosshair)
290 crosshair = gdk_cursor_new(GDK_CROSSHAIR);
292 GTK_WIDGET_SET_FLAGS(GTK_WIDGET(object), GTK_CAN_FOCUS);
294 object->panel = FALSE;
295 object->number_of_items = 0;
296 object->number_selected = 0;
297 object->columns = 1;
298 object->item_width = 64;
299 object->item_height = 64;
300 object->vadj = NULL;
301 object->paint_level = PAINT_OVERWRITE;
302 object->last_scroll = 0;
304 object->items = g_malloc(sizeof(CollectionItem) * MINIMUM_ITEMS);
305 object->cursor_item = -1;
306 object->wink_item = -1;
307 object->array_size = MINIMUM_ITEMS;
308 object->draw_item = default_draw_item;
309 object->test_point = default_test_point;
311 object->buttons_pressed = 0;
312 object->may_drag = FALSE;
314 return;
317 GtkWidget* collection_new(GtkAdjustment *vadj)
319 if (vadj)
320 g_return_val_if_fail(GTK_IS_ADJUSTMENT(vadj), NULL);
322 return GTK_WIDGET(gtk_widget_new(collection_get_type(),
323 "vadjustment", vadj,
324 NULL));
327 void collection_set_functions(Collection *collection,
328 CollectionDrawFunc draw_item,
329 CollectionTestFunc test_point)
331 GtkWidget *widget;
333 g_return_if_fail(collection != NULL);
334 g_return_if_fail(IS_COLLECTION(collection));
336 widget = GTK_WIDGET(collection);
338 if (!draw_item)
339 draw_item = default_draw_item;
340 if (!test_point)
341 test_point = default_test_point;
343 collection->draw_item = draw_item;
344 collection->test_point = test_point;
346 if (GTK_WIDGET_REALIZED(widget))
348 collection->paint_level = PAINT_CLEAR;
349 gtk_widget_queue_clear(widget);
353 /* After this we are unusable, but our data (if any) is still hanging around.
354 * It will be freed later with finalize.
356 static void collection_destroy(GtkObject *object)
358 Collection *collection;
360 g_return_if_fail(object != NULL);
361 g_return_if_fail(IS_COLLECTION(object));
363 collection = COLLECTION(object);
365 if (collection->wink_item != -1)
367 collection->wink_item = -1;
368 gtk_timeout_remove(collection->wink_timeout);
371 gtk_signal_disconnect_by_data(GTK_OBJECT(collection->vadj),
372 collection);
374 if (GTK_OBJECT_CLASS(parent_class)->destroy)
375 (*GTK_OBJECT_CLASS(parent_class)->destroy)(object);
378 /* This is the last thing that happens to us. Free all data. */
379 static void collection_finalize(GtkObject *object)
381 Collection *collection;
383 collection = COLLECTION(object);
385 if (collection->vadj)
387 gtk_object_unref(GTK_OBJECT(collection->vadj));
390 g_free(collection->items);
393 static void collection_realize(GtkWidget *widget)
395 Collection *collection;
396 GdkWindowAttr attributes;
397 gint attributes_mask;
398 GdkGCValues xor_values;
400 g_return_if_fail(widget != NULL);
401 g_return_if_fail(IS_COLLECTION(widget));
402 g_return_if_fail(widget->parent != NULL);
404 GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);
405 collection = COLLECTION(widget);
407 attributes.x = widget->allocation.x;
408 attributes.y = widget->allocation.y;
409 attributes.width = widget->allocation.width;
410 attributes.height = widget->allocation.height;
411 attributes.wclass = GDK_INPUT_OUTPUT;
412 attributes.window_type = GDK_WINDOW_CHILD;
413 attributes.event_mask = gtk_widget_get_events(widget) |
414 GDK_EXPOSURE_MASK |
415 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
416 GDK_BUTTON1_MOTION_MASK | GDK_BUTTON2_MOTION_MASK |
417 GDK_BUTTON3_MOTION_MASK;
418 attributes.visual = gtk_widget_get_visual(widget);
419 attributes.colormap = gtk_widget_get_colormap(widget);
421 attributes_mask = GDK_WA_X | GDK_WA_Y |
422 GDK_WA_VISUAL | GDK_WA_COLORMAP;
423 widget->window = gdk_window_new(widget->parent->window,
424 &attributes, attributes_mask);
426 widget->style = gtk_style_attach(widget->style, widget->window);
428 gdk_window_set_user_data(widget->window, widget);
430 gdk_window_set_background(widget->window,
431 &widget->style->base[GTK_STATE_INSENSITIVE]);
433 /* Try to stop everything flickering horribly */
434 gdk_window_set_static_gravities(widget->window, TRUE);
436 set_vadjustment(collection);
438 xor_values.function = GDK_XOR;
439 xor_values.foreground.red = 0xffff;
440 xor_values.foreground.green = 0xffff;
441 xor_values.foreground.blue = 0xffff;
442 gdk_color_alloc(gtk_widget_get_colormap(widget),
443 &xor_values.foreground);
444 collection->xor_gc = gdk_gc_new_with_values(widget->window,
445 &xor_values,
446 GDK_GC_FOREGROUND
447 | GDK_GC_FUNCTION);
450 static void collection_size_request(GtkWidget *widget,
451 GtkRequisition *requisition)
453 requisition->width = MIN_WIDTH;
454 requisition->height = MIN_HEIGHT;
457 static void collection_size_allocate(GtkWidget *widget,
458 GtkAllocation *allocation)
460 Collection *collection;
461 int old_columns;
463 g_return_if_fail(widget != NULL);
464 g_return_if_fail(IS_COLLECTION(widget));
465 g_return_if_fail(allocation != NULL);
467 collection = COLLECTION(widget);
469 old_columns = collection->columns;
470 if (widget->allocation.x != allocation->x
471 || widget->allocation.y != allocation->y)
472 collection->paint_level = PAINT_CLEAR;
474 widget->allocation = *allocation;
476 collection->columns = allocation->width / collection->item_width;
477 if (collection->columns < 1)
478 collection->columns = 1;
480 if (GTK_WIDGET_REALIZED(widget))
482 gdk_window_move_resize(widget->window,
483 allocation->x, allocation->y,
484 allocation->width, allocation->height);
486 if (old_columns != collection->columns)
488 collection->paint_level = PAINT_CLEAR;
489 gtk_widget_queue_clear(widget);
492 set_vadjustment(collection);
494 if (collection->cursor_item != -1)
495 scroll_to_show(collection, collection->cursor_item);
499 static void collection_set_style(GtkWidget *widget,
500 GtkStyle *previous_style)
502 Collection *collection;
504 g_return_if_fail(IS_COLLECTION(widget));
506 collection = COLLECTION(widget);
508 collection->paint_level = PAINT_CLEAR;
510 if (parent_class->style_set)
511 (*parent_class->style_set)(widget, previous_style);
514 static gint collection_paint(Collection *collection,
515 GdkRectangle *area)
517 GdkRectangle whole, item_area;
518 GtkWidget *widget;
519 int row, col;
520 int item;
521 int scroll;
522 int start_row, last_row;
523 int start_col, last_col;
524 int phys_last_col;
525 GdkRectangle clip;
527 scroll = collection->vadj->value;
529 widget = GTK_WIDGET(collection);
531 if (collection->paint_level > PAINT_NORMAL || area == NULL)
533 guint width, height;
534 gdk_window_get_size(widget->window, &width, &height);
536 whole.x = 0;
537 whole.y = 0;
538 whole.width = width;
539 whole.height = height;
541 area = &whole;
543 if (collection->paint_level == PAINT_CLEAR
544 && !collection->lasso_box)
545 gdk_window_clear(widget->window);
547 collection->paint_level = PAINT_NORMAL;
550 /* Calculate the ranges to plot */
551 start_row = (area->y + scroll) / collection->item_height;
552 last_row = (area->y + area->height - 1 + scroll)
553 / collection->item_height;
554 row = start_row;
556 start_col = area->x / collection->item_width;
557 phys_last_col = (area->x + area->width - 1) / collection->item_width;
559 if (collection->lasso_box)
561 /* You can't be too careful with lasso boxes...
563 * clip gives the total area drawn over (this may be larger
564 * than the requested area). It's used to redraw the lasso
565 * box.
567 clip.x = start_col * collection->item_width;
568 clip.y = start_row * collection->item_height - scroll;
569 clip.width = (phys_last_col - start_col + 1)
570 * collection->item_width;
571 clip.height = (last_row - start_row + 1)
572 * collection->item_height;
574 gdk_window_clear_area(widget->window,
575 clip.x, clip.y, clip.width, clip.height);
578 if (start_col < collection->columns)
580 if (phys_last_col >= collection->columns)
581 last_col = collection->columns - 1;
582 else
583 last_col = phys_last_col;
585 col = start_col;
587 item = row * collection->columns + col;
588 item_area.width = collection->item_width;
589 item_area.height = collection->item_height;
591 while ((item == 0 || item < collection->number_of_items)
592 && row <= last_row)
594 item_area.x = col * collection->item_width;
595 item_area.y = row * collection->item_height - scroll;
597 draw_one_item(collection, item, &item_area);
598 col++;
600 if (col > last_col)
602 col = start_col;
603 row++;
604 item = row * collection->columns + col;
606 else
607 item++;
611 if (collection->lasso_box)
613 gdk_gc_set_clip_rectangle(collection->xor_gc, &clip);
614 draw_lasso_box(collection);
615 gdk_gc_set_clip_rectangle(collection->xor_gc, NULL);
618 return FALSE;
621 static void default_draw_item( GtkWidget *widget,
622 CollectionItem *item,
623 GdkRectangle *area)
625 gdk_draw_arc(widget->window,
626 item->selected ? widget->style->white_gc
627 : widget->style->black_gc,
628 TRUE,
629 area->x, area->y,
630 area->width, area->height,
631 0, 360 * 64);
635 static gboolean default_test_point(Collection *collection,
636 int point_x, int point_y,
637 CollectionItem *item,
638 int width, int height)
640 float f_x, f_y;
642 /* Convert to point in unit circle */
643 f_x = ((float) point_x / width) - 0.5;
644 f_y = ((float) point_y / height) - 0.5;
646 return (f_x * f_x) + (f_y * f_y) <= .25;
649 static void collection_set_arg( GtkObject *object,
650 GtkArg *arg,
651 guint arg_id)
653 Collection *collection;
655 collection = COLLECTION(object);
657 switch (arg_id)
659 case ARG_VADJUSTMENT:
660 collection_set_adjustment(collection,
661 GTK_VALUE_POINTER(*arg));
662 break;
663 default:
664 break;
668 static void collection_set_adjustment( Collection *collection,
669 GtkAdjustment *vadj)
671 if (vadj)
672 g_return_if_fail (GTK_IS_ADJUSTMENT (vadj));
673 else
674 vadj = GTK_ADJUSTMENT(gtk_adjustment_new(0.0,
675 0.0, 0.0,
676 0.0, 0.0, 0.0));
677 if (collection->vadj && (collection->vadj != vadj))
679 gtk_signal_disconnect_by_data(GTK_OBJECT(collection->vadj),
680 collection);
681 gtk_object_unref(GTK_OBJECT(collection->vadj));
684 if (collection->vadj != vadj)
686 collection->vadj = vadj;
687 gtk_object_ref(GTK_OBJECT(collection->vadj));
688 gtk_object_sink(GTK_OBJECT(collection->vadj));
690 gtk_signal_connect(GTK_OBJECT(collection->vadj),
691 "changed",
692 (GtkSignalFunc) collection_adjustment,
693 collection);
694 gtk_signal_connect(GTK_OBJECT(collection->vadj),
695 "value_changed",
696 (GtkSignalFunc) collection_adjustment,
697 collection);
698 gtk_signal_connect(GTK_OBJECT(collection->vadj),
699 "disconnect",
700 (GtkSignalFunc) collection_disconnect,
701 collection);
702 collection_adjustment(vadj, collection);
706 static void collection_get_arg( GtkObject *object,
707 GtkArg *arg,
708 guint arg_id)
710 Collection *collection;
712 collection = COLLECTION(object);
714 switch (arg_id)
716 case ARG_VADJUSTMENT:
717 GTK_VALUE_POINTER(*arg) = collection->vadj;
718 break;
719 default:
720 arg->type = GTK_TYPE_INVALID;
721 break;
725 /* Something about the adjustment has changed */
726 static void collection_adjustment(GtkAdjustment *adjustment,
727 Collection *collection)
729 gint diff;
731 g_return_if_fail(adjustment != NULL);
732 g_return_if_fail(GTK_IS_ADJUSTMENT(adjustment));
733 g_return_if_fail(collection != NULL);
734 g_return_if_fail(IS_COLLECTION(collection));
736 diff = ((gint) adjustment->value) - collection->last_scroll;
738 if (diff)
740 collection->last_scroll = adjustment->value;
742 scroll_by(collection, diff);
746 static void collection_disconnect(GtkAdjustment *adjustment,
747 Collection *collection)
749 g_return_if_fail(adjustment != NULL);
750 g_return_if_fail(GTK_IS_ADJUSTMENT(adjustment));
751 g_return_if_fail(collection != NULL);
752 g_return_if_fail(IS_COLLECTION(collection));
754 collection_set_adjustment(collection, NULL);
757 static void set_vadjustment(Collection *collection)
759 GtkWidget *widget;
760 guint height;
761 int cols, rows;
763 widget = GTK_WIDGET(collection);
765 if (!GTK_WIDGET_REALIZED(widget))
766 return;
768 gdk_window_get_size(widget->window, NULL, &height);
769 cols = collection->columns;
770 rows = (collection->number_of_items + cols - 1) / cols;
772 collection->vadj->lower = 0.0;
773 collection->vadj->upper = collection->item_height * rows;
775 collection->vadj->step_increment =
776 MIN(collection->vadj->upper, collection->item_height / 4);
778 collection->vadj->page_increment =
779 MIN(collection->vadj->upper,
780 height - 5.0);
782 collection->vadj->page_size = MIN(collection->vadj->upper, height);
784 collection->vadj->value = MIN(collection->vadj->value,
785 collection->vadj->upper - collection->vadj->page_size);
787 collection->vadj->value = MAX(collection->vadj->value, 0.0);
789 gtk_signal_emit_by_name(GTK_OBJECT(collection->vadj), "changed");
792 static void collection_draw(GtkWidget *widget, GdkRectangle *area)
794 Collection *collection;
796 g_return_if_fail(widget != NULL);
797 g_return_if_fail(IS_COLLECTION(widget));
798 g_return_if_fail(area != NULL); /* Not actually used */
800 collection = COLLECTION(widget);
802 if (collection->paint_level > PAINT_NORMAL)
803 collection_paint(collection, area);
806 static gint collection_expose(GtkWidget *widget, GdkEventExpose *event)
808 g_return_val_if_fail(widget != NULL, FALSE);
809 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
810 g_return_val_if_fail(event != NULL, FALSE);
812 collection_paint(COLLECTION(widget), &event->area);
814 return FALSE;
817 /* Positive makes the contents go move up the screen */
818 static void scroll_by(Collection *collection, gint diff)
820 GtkWidget *widget;
821 guint width, height;
822 guint from_y, to_y;
823 guint amount;
824 GdkRectangle new_area;
826 if (diff == 0)
827 return;
829 widget = GTK_WIDGET(collection);
831 if (collection->lasso_box)
832 remove_lasso_box(collection);
834 gdk_window_get_size(widget->window, &width, &height);
835 new_area.x = 0;
836 new_area.width = width;
838 if (diff > 0)
840 amount = diff;
841 from_y = amount;
842 to_y = 0;
843 new_area.y = height - amount;
845 else
847 amount = -diff;
848 from_y = 0;
849 to_y = amount;
850 new_area.y = 0;
853 new_area.height = amount;
855 if (amount < height)
857 gdk_draw_pixmap(widget->window,
858 widget->style->white_gc,
859 widget->window,
861 from_y,
863 to_y,
864 width,
865 height - amount);
866 /* We have to redraw everything because any pending
867 * expose events now contain invalid areas.
868 * Don't need to clear the area first though...
870 if (collection->paint_level < PAINT_OVERWRITE)
871 collection->paint_level = PAINT_OVERWRITE;
873 else
874 collection->paint_level = PAINT_CLEAR;
876 gdk_window_clear_area(widget->window,
877 0, new_area.y,
878 width, new_area.height);
879 collection_paint(collection, NULL);
882 static void resize_arrays(Collection *collection, guint new_size)
884 g_return_if_fail(collection != NULL);
885 g_return_if_fail(IS_COLLECTION(collection));
886 g_return_if_fail(new_size >= collection->number_of_items);
888 collection->items = g_realloc(collection->items,
889 sizeof(CollectionItem) * new_size);
890 collection->array_size = new_size;
893 static void return_pressed(Collection *collection)
895 int item = collection->cursor_item;
896 CollectionTargetFunc cb = collection->target_cb;
897 gpointer data = collection->target_data;
899 collection_target(collection, NULL, NULL);
900 if (item < 0 || item >= collection->number_of_items)
901 return;
903 if (cb)
905 cb(collection, item, data);
906 return;
909 gtk_signal_emit(GTK_OBJECT(collection),
910 collection_signals[OPEN_ITEM],
911 collection->items[item].data,
912 item);
915 static gint collection_key_press(GtkWidget *widget, GdkEventKey *event)
917 Collection *collection;
918 int item;
920 g_return_val_if_fail(widget != NULL, FALSE);
921 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
922 g_return_val_if_fail(event != NULL, FALSE);
924 collection = (Collection *) widget;
925 item = collection->cursor_item;
927 switch (event->keyval)
929 case GDK_Left:
930 collection_move_cursor(collection, 0, -1);
931 break;
932 case GDK_Right:
933 collection_move_cursor(collection, 0, 1);
934 break;
935 case GDK_Up:
936 collection_move_cursor(collection, -1, 0);
937 break;
938 case GDK_Down:
939 collection_move_cursor(collection, 1, 0);
940 break;
941 case GDK_Home:
942 collection_set_cursor_item(collection, 0);
943 break;
944 case GDK_End:
945 collection_set_cursor_item(collection,
946 MAX((gint) collection->number_of_items - 1, 0));
947 break;
948 case GDK_Page_Up:
949 collection_move_cursor(collection, -10, 0);
950 break;
951 case GDK_Page_Down:
952 collection_move_cursor(collection, 10, 0);
953 break;
954 case GDK_Return:
955 return_pressed(collection);
956 break;
957 case GDK_Escape:
958 if (!collection->target_cb)
959 return FALSE; /* Pass it on */
960 collection_target(collection, NULL, NULL);
961 break;
962 case ' ':
963 if (item >=0 && item < collection->number_of_items)
964 collection_toggle_item(collection, item);
965 break;
966 default:
967 return FALSE;
970 return TRUE;
973 static gint collection_button_press(GtkWidget *widget,
974 GdkEventButton *event)
976 Collection *collection;
977 int row, col;
978 int item;
979 int action;
980 int scroll;
981 guint stacked_time;
983 g_return_val_if_fail(widget != NULL, FALSE);
984 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
985 g_return_val_if_fail(event != NULL, FALSE);
987 collection = COLLECTION(widget);
989 collection->item_clicked = -1;
991 if (event->button > 3)
993 int diff;
995 /* Wheel mouse scrolling */
996 if (event->button == 4)
997 diff = -((signed int) collection->item_height) / 4;
998 else if (event->button == 5)
999 diff = collection->item_height / 4;
1000 else
1001 diff = 0;
1003 if (diff)
1005 int old_value = collection->vadj->value;
1006 int new_value = 0;
1007 gboolean box = collection->lasso_box;
1009 new_value = CLAMP(old_value + diff, 0.0,
1010 collection->vadj->upper
1011 - collection->vadj->page_size);
1012 diff = new_value - old_value;
1013 if (diff)
1015 if (box)
1017 remove_lasso_box(collection);
1018 collection->drag_box_y[0] -= diff;
1020 collection->vadj->value = new_value;
1021 gtk_signal_emit_by_name(
1022 GTK_OBJECT(collection->vadj),
1023 "changed");
1024 if (box)
1025 add_lasso_box(collection);
1028 return FALSE;
1031 if (collection->cursor_item != -1)
1032 collection_set_cursor_item(collection, -1);
1034 scroll = collection->vadj->value;
1036 if (event->type == GDK_BUTTON_PRESS &&
1037 event->button != collection_menu_button)
1039 if (collection->buttons_pressed++ == 0)
1040 gtk_grab_add(widget);
1041 else
1042 return FALSE; /* Ignore extra presses */
1045 if (event->state & GDK_CONTROL_MASK && !collection_single_click)
1046 action = 2;
1047 else
1048 action = event->button;
1050 /* Ignore all clicks while we are dragging a lasso box */
1051 if (collection->lasso_box)
1052 return TRUE;
1054 col = event->x / collection->item_width;
1055 row = (event->y + scroll) / collection->item_height;
1057 if (col < 0 || row < 0 || col >= collection->columns)
1058 item = -1;
1059 else
1061 item = col + row * collection->columns;
1062 if (item >= collection->number_of_items
1064 !collection->test_point(collection,
1065 event->x - col * collection->item_width,
1066 event->y - row * collection->item_height
1067 + scroll,
1068 &collection->items[item],
1069 collection->item_width,
1070 collection->item_height))
1072 item = -1;
1076 if (collection->target_cb)
1078 CollectionTargetFunc cb = collection->target_cb;
1079 gpointer data = collection->target_data;
1081 collection_target(collection, NULL, NULL);
1082 if (collection->buttons_pressed)
1084 gtk_grab_remove(widget);
1085 collection->buttons_pressed = 0;
1087 if (item > -1 && event->button != collection_menu_button)
1088 cb(collection, item, data);
1089 return TRUE;
1092 collection->drag_box_x[0] = event->x;
1093 collection->drag_box_y[0] = event->y;
1094 collection->item_clicked = item;
1096 stacked_time = current_event_time;
1097 current_event_time = event->time;
1099 if (event->button == collection_menu_button)
1101 gtk_signal_emit(GTK_OBJECT(collection),
1102 collection_signals[SHOW_MENU],
1103 event,
1104 item);
1106 else if (event->type == GDK_2BUTTON_PRESS && collection->panel)
1108 /* Do nothing */
1110 else if ((event->type == GDK_2BUTTON_PRESS && !collection_single_click)
1111 || collection->panel)
1113 if (item >= 0)
1115 if (collection->buttons_pressed)
1117 gtk_grab_remove(widget);
1118 collection->buttons_pressed = 0;
1120 collection_unselect_item(collection, item);
1121 gtk_signal_emit(GTK_OBJECT(collection),
1122 collection_signals[OPEN_ITEM],
1123 collection->items[item].data,
1124 item);
1127 else if (event->type == GDK_BUTTON_PRESS)
1129 collection->may_drag = event->button < collection_menu_button;
1131 if (item >= 0)
1133 if (action == 1)
1135 if (!collection->items[item].selected)
1137 collection_select_item(collection,
1138 item);
1139 collection_clear_except(collection,
1140 item);
1143 else
1144 collection_toggle_item(collection, item);
1146 else if (action == 1)
1147 collection_clear_selection(collection);
1150 current_event_time = stacked_time;
1151 return FALSE;
1154 static gint collection_button_release(GtkWidget *widget,
1155 GdkEventButton *event)
1157 Collection *collection;
1158 int top, bottom;
1159 int row, last_row;
1160 int w, h;
1161 int col, start_col, last_col;
1162 int scroll;
1163 int item;
1164 guint stacked_time;
1165 int button;
1167 g_return_val_if_fail(widget != NULL, FALSE);
1168 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1169 g_return_val_if_fail(event != NULL, FALSE);
1171 collection = COLLECTION(widget);
1172 button = event->button;
1174 scroll = collection->vadj->value;
1176 if (event->button > 3 || event->button == collection_menu_button)
1177 return FALSE;
1178 if (collection->buttons_pressed == 0)
1179 return FALSE;
1180 if (--collection->buttons_pressed == 0)
1181 gtk_grab_remove(widget);
1182 else
1183 return FALSE; /* Wait until ALL buttons are up */
1185 if (!collection->lasso_box)
1187 int item = collection->item_clicked;
1189 if (collection_single_click && item > -1
1190 && item < collection->number_of_items
1191 && (event->state & GDK_CONTROL_MASK) == 0)
1193 int dx = event->x - collection->drag_box_x[0];
1194 int dy = event->y - collection->drag_box_y[0];
1196 if (ABS(dx) + ABS(dy) > 9)
1197 return FALSE;
1199 collection_unselect_item(collection, item);
1200 gtk_signal_emit(GTK_OBJECT(collection),
1201 collection_signals[OPEN_ITEM],
1202 collection->items[item].data,
1203 item);
1206 return FALSE;
1209 remove_lasso_box(collection);
1211 w = collection->item_width;
1212 h = collection->item_height;
1214 top = collection->drag_box_y[0] + scroll;
1215 bottom = collection->drag_box_y[1] + scroll;
1216 if (top > bottom)
1218 int tmp;
1219 tmp = top;
1220 top = bottom;
1221 bottom = tmp;
1223 top += h / 4;
1224 bottom -= h / 4;
1226 row = MAX(top / h, 0);
1227 last_row = bottom / h;
1229 top = collection->drag_box_x[0]; /* (left) */
1230 bottom = collection->drag_box_x[1];
1231 if (top > bottom)
1233 int tmp;
1234 tmp = top;
1235 top = bottom;
1236 bottom = tmp;
1238 top += w / 4;
1239 bottom -= w / 4;
1240 start_col = MAX(top / w, 0);
1241 last_col = bottom / w;
1242 if (last_col >= collection->columns)
1243 last_col = collection->columns - 1;
1245 stacked_time = current_event_time;
1246 current_event_time = event->time;
1248 while (row <= last_row)
1250 col = start_col;
1251 item = row * collection->columns + col;
1252 while (col <= last_col)
1254 if (item >= collection->number_of_items)
1256 current_event_time = stacked_time;
1257 return FALSE;
1260 if (button == 1)
1261 collection_select_item(collection, item);
1262 else
1263 collection_toggle_item(collection, item);
1264 col++;
1265 item++;
1267 row++;
1270 current_event_time = stacked_time;
1272 return FALSE;
1275 static gint collection_motion_notify(GtkWidget *widget,
1276 GdkEventMotion *event)
1278 Collection *collection;
1279 int x, y;
1280 guint stacked_time;
1282 g_return_val_if_fail(widget != NULL, FALSE);
1283 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1284 g_return_val_if_fail(event != NULL, FALSE);
1286 collection = COLLECTION(widget);
1288 if (collection->buttons_pressed == 0)
1289 return FALSE;
1291 stacked_time = current_event_time;
1292 current_event_time = event->time;
1294 if (event->window != widget->window)
1295 gdk_window_get_pointer(widget->window, &x, &y, NULL);
1296 else
1298 x = event->x;
1299 y = event->y;
1302 if (collection->lasso_box)
1304 int new_value = 0, diff;
1305 int height;
1307 gdk_window_get_size(widget->window, NULL, &height);
1309 if (y < 0)
1311 int old_value = collection->vadj->value;
1313 new_value = MAX(old_value + y / 10, 0.0);
1314 diff = new_value - old_value;
1316 else if (y > height)
1318 int old_value = collection->vadj->value;
1320 new_value = MIN(old_value + (y - height) / 10,
1321 collection->vadj->upper
1322 - collection->vadj->page_size);
1323 diff = new_value - old_value;
1325 else
1326 diff = 0;
1328 remove_lasso_box(collection);
1329 collection->drag_box_x[1] = x;
1330 collection->drag_box_y[1] = y;
1332 if (diff)
1334 collection->drag_box_y[0] -= diff;
1335 collection->vadj->value = new_value;
1336 gtk_signal_emit_by_name(GTK_OBJECT(collection->vadj),
1337 "changed");
1339 add_lasso_box(collection);
1341 else if (collection->may_drag)
1343 int dx = x - collection->drag_box_x[0];
1344 int dy = y - collection->drag_box_y[0];
1346 if (abs(dx) > 9 || abs(dy) > 9)
1348 int row, col, item;
1349 int scroll = collection->vadj->value;
1351 collection->may_drag = FALSE;
1353 col = collection->drag_box_x[0]
1354 / collection->item_width;
1355 row = (collection->drag_box_y[0] + scroll)
1356 / collection->item_height;
1357 item = item_at_row_col(collection, row, col);
1359 if (item != -1 && collection->test_point(collection,
1360 collection->drag_box_x[0] -
1361 col * collection->item_width,
1362 collection->drag_box_y[0]
1363 - row * collection->item_height
1364 + scroll,
1365 &collection->items[item],
1366 collection->item_width,
1367 collection->item_height))
1369 collection->buttons_pressed = 0;
1370 gtk_grab_remove(widget);
1371 collection_select_item(collection, item);
1372 gtk_signal_emit(GTK_OBJECT(collection),
1373 collection_signals[DRAG_SELECTION],
1374 event,
1375 collection->number_selected);
1377 else
1379 collection->drag_box_x[1] = x;
1380 collection->drag_box_y[1] = y;
1381 add_lasso_box(collection);
1386 current_event_time = stacked_time;
1387 return FALSE;
1390 static void add_lasso_box(Collection *collection)
1392 g_return_if_fail(collection != NULL);
1393 g_return_if_fail(IS_COLLECTION(collection));
1394 g_return_if_fail(collection->lasso_box == FALSE);
1396 collection->lasso_box = TRUE;
1397 draw_lasso_box(collection);
1400 static void draw_lasso_box(Collection *collection)
1402 GtkWidget *widget;
1403 int x, y, width, height;
1405 widget = GTK_WIDGET(collection);
1407 x = MIN(collection->drag_box_x[0], collection->drag_box_x[1]);
1408 y = MIN(collection->drag_box_y[0], collection->drag_box_y[1]);
1409 width = abs(collection->drag_box_x[1] - collection->drag_box_x[0]);
1410 height = abs(collection->drag_box_y[1] - collection->drag_box_y[0]);
1412 gdk_draw_rectangle(widget->window, collection->xor_gc, FALSE,
1413 x, y, width, height);
1416 static void remove_lasso_box(Collection *collection)
1418 g_return_if_fail(collection != NULL);
1419 g_return_if_fail(IS_COLLECTION(collection));
1420 g_return_if_fail(collection->lasso_box == TRUE);
1422 draw_lasso_box(collection);
1424 collection->lasso_box = FALSE;
1426 return;
1429 /* Convert a row,col address to an item number, or -1 if none */
1430 static int item_at_row_col(Collection *collection, int row, int col)
1432 int item;
1434 if (row < 0 || col < 0 || col >= collection->columns)
1435 return -1;
1437 item = col + row * collection->columns;
1439 if (item >= collection->number_of_items)
1440 return -1;
1441 return item;
1444 /* Make sure that 'item' is fully visible (vertically), scrolling if not. */
1445 static void scroll_to_show(Collection *collection, int item)
1447 int first, last, row;
1449 g_return_if_fail(collection != NULL);
1450 g_return_if_fail(IS_COLLECTION(collection));
1452 row = item / collection->columns;
1453 get_visible_limits(collection, &first, &last);
1455 if (row <= first)
1457 gtk_adjustment_set_value(collection->vadj,
1458 row * collection->item_height);
1460 else if (row >= last)
1462 GtkWidget *widget = (GtkWidget *) collection;
1463 int height;
1465 if (GTK_WIDGET_REALIZED(widget))
1467 gdk_window_get_size(widget->window, NULL, &height);
1468 gtk_adjustment_set_value(collection->vadj,
1469 (row + 1) * collection->item_height - height);
1474 /* Return the first and last rows which are [partly] visible. Does not
1475 * ensure that the rows actually exist (contain items).
1477 static void get_visible_limits(Collection *collection, int *first, int *last)
1479 GtkWidget *widget = (GtkWidget *) collection;
1480 int scroll, height;
1482 g_return_if_fail(collection != NULL);
1483 g_return_if_fail(IS_COLLECTION(collection));
1484 g_return_if_fail(first != NULL && last != NULL);
1486 if (!GTK_WIDGET_REALIZED(widget))
1488 *first = 0;
1489 *last = 0;
1491 else
1493 scroll = collection->vadj->value;
1494 gdk_window_get_size(widget->window, NULL, &height);
1496 *first = MAX(scroll / collection->item_height, 0);
1497 *last = (scroll + height - 1) /collection->item_height;
1499 if (*last < *first)
1500 *last = *first;
1504 /* Unselect all items except number item (-1 to unselect everything) */
1505 static void collection_clear_except(Collection *collection, gint exception)
1507 GtkWidget *widget;
1508 GdkRectangle area;
1509 int item = 0;
1510 int scroll;
1511 int end; /* Selected items to end up with */
1513 widget = GTK_WIDGET(collection);
1514 scroll = collection->vadj->value;
1516 end = exception >= 0 && exception < collection->number_of_items
1517 ? collection->items[exception].selected != 0 : 0;
1519 area.width = collection->item_width;
1520 area.height = collection->item_height;
1522 if (collection->number_selected == 0)
1523 return;
1525 while (collection->number_selected > end)
1527 while (item == exception || !collection->items[item].selected)
1528 item++;
1530 area.x = (item % collection->columns) * area.width;
1531 area.y = (item / collection->columns) * area.height
1532 - scroll;
1534 collection->items[item++].selected = FALSE;
1535 gdk_window_clear_area(widget->window,
1536 area.x, area.y, area.width, area.height);
1537 collection_paint(collection, &area);
1539 collection->number_selected--;
1542 if (end == 0)
1543 gtk_signal_emit(GTK_OBJECT(collection),
1544 collection_signals[LOSE_SELECTION],
1545 current_event_time);
1548 /* Cancel the current wink effect. */
1549 static void cancel_wink(Collection *collection)
1551 gint item;
1553 g_return_if_fail(collection != NULL);
1554 g_return_if_fail(IS_COLLECTION(collection));
1555 g_return_if_fail(collection->wink_item != -1);
1557 item = collection->wink_item;
1559 collection->wink_item = -1;
1560 gtk_timeout_remove(collection->wink_timeout);
1562 collection_draw_item(collection, item, TRUE);
1565 static gboolean cancel_wink_timeout(Collection *collection)
1567 gint item;
1569 g_return_val_if_fail(collection != NULL, FALSE);
1570 g_return_val_if_fail(IS_COLLECTION(collection), FALSE);
1571 g_return_val_if_fail(collection->wink_item != -1, FALSE);
1573 item = collection->wink_item;
1575 collection->wink_item = -1;
1577 collection_draw_item(collection, item, TRUE);
1579 return FALSE;
1582 static gint focus_in(GtkWidget *widget, GdkEventFocus *event)
1584 g_return_val_if_fail(widget != NULL, FALSE);
1585 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1586 g_return_val_if_fail(event != NULL, FALSE);
1588 GTK_WIDGET_SET_FLAGS(widget, GTK_HAS_FOCUS);
1589 gtk_widget_draw_focus(widget);
1591 return FALSE;
1594 static gint focus_out(GtkWidget *widget, GdkEventFocus *event)
1596 g_return_val_if_fail(widget != NULL, FALSE);
1597 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1598 g_return_val_if_fail(event != NULL, FALSE);
1600 GTK_WIDGET_UNSET_FLAGS(widget, GTK_HAS_FOCUS);
1601 gtk_widget_draw_focus(widget);
1603 return FALSE;
1606 /* Functions for managing collections */
1608 /* Remove all objects from the collection */
1609 void collection_clear(Collection *collection)
1611 int prev_selected;
1613 g_return_if_fail(IS_COLLECTION(collection));
1615 if (collection->number_of_items == 0)
1616 return;
1618 if (collection->wink_item != -1)
1620 collection->wink_item = -1;
1621 gtk_timeout_remove(collection->wink_timeout);
1624 collection_set_cursor_item(collection,
1625 collection->cursor_item == -1 ? -1: 0);
1626 prev_selected = collection->number_selected;
1627 collection->number_of_items = collection->number_selected = 0;
1629 resize_arrays(collection, MINIMUM_ITEMS);
1631 collection->paint_level = PAINT_CLEAR;
1633 gtk_widget_queue_clear(GTK_WIDGET(collection));
1635 if (prev_selected && collection->number_selected == 0)
1636 gtk_signal_emit(GTK_OBJECT(collection),
1637 collection_signals[LOSE_SELECTION],
1638 current_event_time);
1641 /* Inserts a new item at the end. The new item is unselected, and its
1642 * number is returned.
1644 gint collection_insert(Collection *collection, gpointer data)
1646 int item;
1648 g_return_val_if_fail(IS_COLLECTION(collection), -1);
1650 item = collection->number_of_items;
1652 if (item >= collection->array_size)
1653 resize_arrays(collection, item + (item >> 1));
1655 collection->items[item].data = data;
1656 collection->items[item].selected = FALSE;
1658 collection->number_of_items++;
1660 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection)))
1662 set_vadjustment(collection);
1663 collection_draw_item(collection,
1664 collection->number_of_items - 1,
1665 FALSE);
1668 return item;
1671 /* Unselect an item by number */
1672 void collection_unselect_item(Collection *collection, gint item)
1674 g_return_if_fail(collection != NULL);
1675 g_return_if_fail(IS_COLLECTION(collection));
1676 g_return_if_fail(item >= 0 && item < collection->number_of_items);
1678 if (collection->items[item].selected)
1680 collection->items[item].selected = FALSE;
1681 collection_draw_item(collection, item, TRUE);
1683 if (--collection->number_selected == 0)
1684 gtk_signal_emit(GTK_OBJECT(collection),
1685 collection_signals[LOSE_SELECTION],
1686 current_event_time);
1690 /* Select an item by number */
1691 void collection_select_item(Collection *collection, gint item)
1693 g_return_if_fail(collection != NULL);
1694 g_return_if_fail(IS_COLLECTION(collection));
1695 g_return_if_fail(item >= 0 && item < collection->number_of_items);
1697 if (collection->items[item].selected)
1698 return; /* Already selected */
1700 collection->items[item].selected = TRUE;
1701 collection_draw_item(collection, item, TRUE);
1703 if (collection->number_selected++ == 0)
1704 gtk_signal_emit(GTK_OBJECT(collection),
1705 collection_signals[GAIN_SELECTION],
1706 current_event_time);
1709 /* Toggle the selected state of an item (by number) */
1710 void collection_toggle_item(Collection *collection, gint item)
1712 g_return_if_fail(collection != NULL);
1713 g_return_if_fail(IS_COLLECTION(collection));
1714 g_return_if_fail(item >= 0 && item < collection->number_of_items);
1716 if (collection->items[item].selected)
1718 collection->items[item].selected = FALSE;
1719 if (--collection->number_selected == 0)
1720 gtk_signal_emit(GTK_OBJECT(collection),
1721 collection_signals[LOSE_SELECTION],
1722 current_event_time);
1724 else
1726 collection->items[item].selected = TRUE;
1727 if (collection->number_selected++ == 0)
1728 gtk_signal_emit(GTK_OBJECT(collection),
1729 collection_signals[GAIN_SELECTION],
1730 current_event_time);
1732 collection_draw_item(collection, item, TRUE);
1735 /* Select all items in the collection */
1736 void collection_select_all(Collection *collection)
1738 GtkWidget *widget;
1739 GdkRectangle area;
1740 int item = 0;
1741 int scroll;
1743 g_return_if_fail(collection != NULL);
1744 g_return_if_fail(IS_COLLECTION(collection));
1746 widget = GTK_WIDGET(collection);
1747 scroll = collection->vadj->value;
1749 area.width = collection->item_width;
1750 area.height = collection->item_height;
1752 if (collection->number_selected == collection->number_of_items)
1753 return; /* Nothing to do */
1755 while (collection->number_selected < collection->number_of_items)
1757 while (collection->items[item].selected)
1758 item++;
1760 area.x = (item % collection->columns) * area.width;
1761 area.y = (item / collection->columns) * area.height
1762 - scroll;
1764 collection->items[item++].selected = TRUE;
1765 gdk_window_clear_area(widget->window,
1766 area.x, area.y, area.width, area.height);
1767 collection_paint(collection, &area);
1769 collection->number_selected++;
1772 gtk_signal_emit(GTK_OBJECT(collection),
1773 collection_signals[GAIN_SELECTION],
1774 current_event_time);
1777 /* Unselect all items in the collection */
1778 void collection_clear_selection(Collection *collection)
1780 g_return_if_fail(collection != NULL);
1781 g_return_if_fail(IS_COLLECTION(collection));
1783 collection_clear_except(collection, -1);
1786 /* Force a redraw of the specified item, if it is visible */
1787 void collection_draw_item(Collection *collection, gint item, gboolean blank)
1789 int height;
1790 GdkRectangle area;
1791 GtkWidget *widget;
1792 int row, col;
1793 int scroll;
1794 int area_y, area_height; /* NOT shorts! */
1796 g_return_if_fail(collection != NULL);
1797 g_return_if_fail(IS_COLLECTION(collection));
1798 g_return_if_fail(item >= 0 &&
1799 (item == 0 || item < collection->number_of_items));
1801 widget = GTK_WIDGET(collection);
1802 if (!GTK_WIDGET_REALIZED(widget))
1803 return;
1805 col = item % collection->columns;
1806 row = item / collection->columns;
1807 scroll = collection->vadj->value; /* (round to int) */
1809 area.x = col * collection->item_width;
1810 area_y = row * collection->item_height - scroll;
1811 area.width = collection->item_width;
1812 area_height = collection->item_height;
1814 if (area_y + area_height < 0)
1815 return;
1817 gdk_window_get_size(widget->window, NULL, &height);
1819 if (area_y > height)
1820 return;
1822 area.y = area_y;
1823 area.height = area_height;
1825 if (blank || collection->lasso_box)
1826 gdk_window_clear_area(widget->window,
1827 area.x, area.y, area.width, area.height);
1829 draw_one_item(collection, item, &area);
1831 if (collection->lasso_box)
1833 gdk_gc_set_clip_rectangle(collection->xor_gc, &area);
1834 draw_lasso_box(collection);
1835 gdk_gc_set_clip_rectangle(collection->xor_gc, NULL);
1839 void collection_set_item_size(Collection *collection, int width, int height)
1841 GtkWidget *widget;
1843 g_return_if_fail(collection != NULL);
1844 g_return_if_fail(IS_COLLECTION(collection));
1845 g_return_if_fail(width > 4 && height > 4);
1847 widget = GTK_WIDGET(collection);
1849 collection->item_width = width;
1850 collection->item_height = height;
1852 if (GTK_WIDGET_REALIZED(widget))
1854 int window_width;
1856 collection->paint_level = PAINT_CLEAR;
1857 gdk_window_get_size(widget->window, &window_width, NULL);
1858 collection->columns = MAX(window_width / collection->item_width,
1861 set_vadjustment(collection);
1862 if (collection->cursor_item != -1)
1863 scroll_to_show(collection, collection->cursor_item);
1864 gtk_widget_queue_draw(widget);
1868 /* Cursor is positioned on item with the same data as before the sort.
1869 * Same for the wink item.
1871 void collection_qsort(Collection *collection,
1872 int (*compar)(const void *, const void *))
1874 int cursor, wink, items;
1875 gpointer cursor_data = NULL;
1876 gpointer wink_data = NULL;
1878 g_return_if_fail(collection != NULL);
1879 g_return_if_fail(IS_COLLECTION(collection));
1880 g_return_if_fail(compar != NULL);
1882 items = collection->number_of_items;
1884 wink = collection->wink_item;
1885 if (wink >= 0 && wink < items)
1886 wink_data = collection->items[wink].data;
1887 else
1888 wink = -1;
1890 cursor = collection->cursor_item;
1891 if (cursor >= 0 && cursor < items)
1892 cursor_data = collection->items[cursor].data;
1893 else
1894 cursor = -1;
1896 if (collection->wink_item != -1)
1898 collection->wink_item = -1;
1899 gtk_timeout_remove(collection->wink_timeout);
1902 qsort(collection->items, items, sizeof(collection->items[0]), compar);
1904 if (cursor > -1 || wink > -1)
1906 int item;
1908 for (item = 0; item < items; item++)
1910 if (collection->items[item].data == cursor_data)
1911 collection_set_cursor_item(collection, item);
1912 if (collection->items[item].data == wink_data)
1913 collection_wink_item(collection, item);
1917 collection->paint_level = PAINT_CLEAR;
1919 gtk_widget_queue_draw(GTK_WIDGET(collection));
1922 /* Find an item in an unsorted collection.
1923 * Returns the item number, or -1 if not found.
1925 int collection_find_item(Collection *collection, gpointer data,
1926 int (*compar)(const void *, const void *))
1928 int i;
1930 g_return_val_if_fail(collection != NULL, -1);
1931 g_return_val_if_fail(IS_COLLECTION(collection), -1);
1932 g_return_val_if_fail(compar != NULL, -1);
1934 for (i = 0; i < collection->number_of_items; i++)
1935 if (compar(&collection->items[i].data, &data) == 0)
1936 return i;
1938 return -1;
1941 /* The collection may be in either normal mode or panel mode.
1942 * In panel mode:
1943 * - a single click calls open_item
1944 * - items are never selected
1945 * - lasso boxes are disabled
1947 void collection_set_panel(Collection *collection, gboolean panel)
1949 g_return_if_fail(collection != NULL);
1950 g_return_if_fail(IS_COLLECTION(collection));
1952 collection->panel = panel == TRUE;
1954 if (collection->panel)
1956 collection_clear_selection(collection);
1957 if (collection->lasso_box)
1958 remove_lasso_box(collection);
1962 /* Return the number of the item under the point (x,y), or -1 for none.
1963 * This may call your test_point callback. The point is relative to the
1964 * collection's origin.
1966 int collection_get_item(Collection *collection, int x, int y)
1968 int scroll;
1969 int row, col;
1970 int item;
1972 g_return_val_if_fail(collection != NULL, -1);
1974 scroll = collection->vadj->value;
1975 col = x / collection->item_width;
1976 row = (y + scroll) / collection->item_height;
1978 if (col < 0 || row < 0 || col >= collection->columns)
1979 return -1;
1981 item = col + row * collection->columns;
1982 if (item >= collection->number_of_items
1984 !collection->test_point(collection,
1985 x - col * collection->item_width,
1986 y - row * collection->item_height
1987 + scroll,
1988 &collection->items[item],
1989 collection->item_width,
1990 collection->item_height))
1992 return -1;
1995 return item;
1998 /* Set the cursor/highlight over the given item. Passing -1
1999 * hides the cursor. As a special case, you may set the cursor item
2000 * to zero when there are no items.
2002 void collection_set_cursor_item(Collection *collection, gint item)
2004 int old_item;
2006 g_return_if_fail(collection != NULL);
2007 g_return_if_fail(IS_COLLECTION(collection));
2008 g_return_if_fail(item >= -1 &&
2009 (item < collection->number_of_items || item == 0));
2011 old_item = collection->cursor_item;
2013 if (old_item == item)
2014 return;
2016 collection->cursor_item = item;
2018 if (old_item != -1)
2019 collection_draw_item(collection, old_item, TRUE);
2021 if (item != -1)
2023 collection_draw_item(collection, item, TRUE);
2024 scroll_to_show(collection, item);
2028 /* Briefly highlight an item to draw the user's attention to it.
2029 * -1 cancels the effect, as does deleting items, sorting the collection
2030 * or starting a new wink effect.
2031 * Otherwise, the effect will cancel itself after a short pause.
2032 * */
2033 void collection_wink_item(Collection *collection, gint item)
2035 g_return_if_fail(collection != NULL);
2036 g_return_if_fail(IS_COLLECTION(collection));
2037 g_return_if_fail(item >= -1 && item < collection->number_of_items);
2039 if (collection->wink_item != -1)
2040 cancel_wink(collection);
2041 if (item == -1)
2042 return;
2044 collection->wink_item = item;
2045 collection->wink_timeout = gtk_timeout_add(300,
2046 (GtkFunction) cancel_wink_timeout,
2047 collection);
2048 collection_draw_item(collection, item, TRUE);
2049 scroll_to_show(collection, item);
2051 gdk_flush();
2054 /* Call test(item, data) on each item in the collection.
2055 * Remove all items for which it returns TRUE. test() should
2056 * free the data before returning TRUE. The collection is in an
2057 * inconsistant state during this call (ie, when test() is called).
2059 void collection_delete_if(Collection *collection,
2060 gboolean (*test)(gpointer item, gpointer data),
2061 gpointer data)
2063 int in, out = 0;
2064 int selected = 0;
2065 int cursor;
2067 g_return_if_fail(collection != NULL);
2068 g_return_if_fail(IS_COLLECTION(collection));
2069 g_return_if_fail(test != NULL);
2071 cursor = collection->cursor_item;
2073 for (in = 0; in < collection->number_of_items; in++)
2075 if (!test(collection->items[in].data, data))
2077 if (collection->items[in].selected)
2079 collection->items[out].selected = TRUE;
2080 selected++;
2082 else
2083 collection->items[out].selected = FALSE;
2085 collection->items[out++].data =
2086 collection->items[in].data;
2088 else if (cursor >= in)
2089 cursor--;
2092 if (in != out)
2094 collection->cursor_item = cursor;
2096 if (collection->wink_item != -1)
2098 collection->wink_item = -1;
2099 gtk_timeout_remove(collection->wink_timeout);
2102 collection->number_of_items = out;
2103 collection->number_selected = selected;
2104 resize_arrays(collection,
2105 MAX(collection->number_of_items, MINIMUM_ITEMS));
2107 collection->paint_level = PAINT_CLEAR;
2109 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection)))
2111 set_vadjustment(collection);
2112 gtk_widget_queue_draw(GTK_WIDGET(collection));
2117 /* Display a cross-hair pointer and the next time an item is clicked,
2118 * call the callback function. If the background is clicked or NULL
2119 * is passed as the callback then revert to normal operation.
2121 void collection_target(Collection *collection,
2122 CollectionTargetFunc callback,
2123 gpointer user_data)
2125 g_return_if_fail(collection != NULL);
2126 g_return_if_fail(IS_COLLECTION(collection));
2128 if (callback != collection->target_cb)
2129 gdk_window_set_cursor(GTK_WIDGET(collection)->window,
2130 callback ? crosshair : NULL);
2132 collection->target_cb = callback;
2133 collection->target_data = user_data;
2135 if (collection->cursor_item != -1)
2136 collection_draw_item(collection, collection->cursor_item,
2137 FALSE);
2140 /* Move the cursor by the given row and column offsets. */
2141 void collection_move_cursor(Collection *collection, int drow, int dcol)
2143 int row, col, item;
2144 int first, last, total_rows;
2146 g_return_if_fail(collection != NULL);
2147 g_return_if_fail(IS_COLLECTION(collection));
2149 get_visible_limits(collection, &first, &last);
2151 item = collection->cursor_item;
2152 if (item == -1)
2154 col = 0;
2155 row = first;
2157 else
2159 row = item / collection->columns;
2160 col = item % collection->columns + dcol;
2162 if (row < first)
2163 row = first;
2164 else if (row > last)
2165 row = last;
2166 else
2167 row = MAX(row + drow, 0);
2170 total_rows = (collection->number_of_items + collection->columns - 1)
2171 / collection->columns;
2173 if (row >= total_rows - 1 && drow > 0)
2175 row = total_rows - 1;
2176 item = col + row * collection->columns;
2177 if (item >= collection->number_of_items)
2179 row--;
2180 scroll_to_show(collection, item);
2183 if (row < 0)
2184 row = 0;
2186 item = col + row * collection->columns;
2188 if (item >= 0 && item < collection->number_of_items)
2189 collection_set_cursor_item(collection, item);