r286: Added some truncation code (not finished yet).
[rox-filer/ma.git] / ROX-Filer / src / collection.c
blob8aa7f08a0622b88ce24c4ca195840c381a8bc9b4
1 /*
2 * $Id$
4 * Collection - a GTK+ widget
5 * Copyright (C) 2000, Thomas Leonard, <tal197@ecs.soton.ac.uk>.
7 * The collection widget provides an area for displaying a collection of
8 * objects (such as files). It allows the user to choose a selection of
9 * them and provides signals to allow popping up menus, detecting
10 * double-clicks etc.
12 * This program is free software; you can redistribute it and/or modify it
13 * under the terms of the GNU General Public License as published by the Free
14 * Software Foundation; either version 2 of the License, or (at your option)
15 * any later version.
17 * This program is distributed in the hope that it will be useful, but WITHOUT
18 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
20 * more details.
22 * You should have received a copy of the GNU General Public License along with
23 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
24 * Place, Suite 330, Boston, MA 02111-1307 USA
27 #include <stdlib.h>
29 #include <gtk/gtk.h>
30 #include <gdk/gdkkeysyms.h>
31 #include "collection.h"
33 #define MIN_WIDTH 80
34 #define MIN_HEIGHT 60
35 #define MINIMUM_ITEMS 16
37 int collection_menu_button = 3;
38 gboolean collection_single_click = FALSE;
40 enum
42 ARG_0,
43 ARG_VADJUSTMENT
46 /* Signals:
48 * void open_item(collection, item, item_number, user_data)
49 * User has double clicked on this item.
51 * void drag_selection(collection, motion_event, number_selected, user_data)
52 * User has tried to drag the selection.
54 * void show_menu(collection, button_event, item, user_data)
55 * User has menu-clicked on the collection. 'item' is the number
56 * of the item clicked, or -1 if the click was over the background.
58 * void gain_selection(collection, time, user_data)
59 * We've gone from no selected items to having a selection.
60 * Time is the time of the event that caused the change, or
61 * GDK_CURRENT_TIME if not known.
63 * void lose_selection(collection, time, user_data)
64 * We've dropped to having no selected items.
65 * Time is the time of the event that caused the change, or
66 * GDK_CURRENT_TIME if not known.
68 enum
70 OPEN_ITEM,
71 DRAG_SELECTION,
72 SHOW_MENU,
73 GAIN_SELECTION,
74 LOSE_SELECTION,
75 LAST_SIGNAL
78 static guint collection_signals[LAST_SIGNAL] = { 0 };
80 static guint32 current_event_time = GDK_CURRENT_TIME;
82 static GtkWidgetClass *parent_class = NULL;
84 static GdkCursor *crosshair = NULL;
86 /* Static prototypes */
87 static void draw_one_item(Collection *collection,
88 int item,
89 GdkRectangle *area);
90 static void collection_class_init(CollectionClass *class);
91 static void collection_init(Collection *object);
92 static void collection_destroy(GtkObject *object);
93 static void collection_finalize(GtkObject *object);
94 static void collection_realize(GtkWidget *widget);
95 static gint collection_paint(Collection *collection,
96 GdkRectangle *area);
97 static void collection_size_request(GtkWidget *widget,
98 GtkRequisition *requisition);
99 static void collection_size_allocate(GtkWidget *widget,
100 GtkAllocation *allocation);
101 static void collection_set_style(GtkWidget *widget,
102 GtkStyle *previous_style);
103 static void collection_set_adjustment(Collection *collection,
104 GtkAdjustment *vadj);
105 static void collection_set_arg( GtkObject *object,
106 GtkArg *arg,
107 guint arg_id);
108 static void collection_get_arg( GtkObject *object,
109 GtkArg *arg,
110 guint arg_id);
111 static void collection_adjustment(GtkAdjustment *adjustment,
112 Collection *collection);
113 static void collection_disconnect(GtkAdjustment *adjustment,
114 Collection *collection);
115 static void set_vadjustment(Collection *collection);
116 static void collection_draw(GtkWidget *widget, GdkRectangle *area);
117 static gint collection_expose(GtkWidget *widget, GdkEventExpose *event);
118 static void scroll_by(Collection *collection, gint diff);
119 static gint collection_button_press(GtkWidget *widget,
120 GdkEventButton *event);
121 static gint collection_button_release(GtkWidget *widget,
122 GdkEventButton *event);
123 static void default_draw_item(GtkWidget *widget,
124 CollectionItem *data,
125 GdkRectangle *area);
126 static gboolean default_test_point(Collection *collection,
127 int point_x, int point_y,
128 CollectionItem *data,
129 int width, int height);
130 static gint collection_motion_notify(GtkWidget *widget,
131 GdkEventMotion *event);
132 static void add_lasso_box(Collection *collection);
133 static void remove_lasso_box(Collection *collection);
134 static void draw_lasso_box(Collection *collection);
135 static int item_at_row_col(Collection *collection, int row, int col);
136 static void collection_clear_except(Collection *collection, gint exception);
137 static void cancel_wink(Collection *collection);
138 static gint collection_key_press(GtkWidget *widget, GdkEventKey *event);
139 static void get_visible_limits(Collection *collection, int *first, int *last);
140 static void scroll_to_show(Collection *collection, int item);
141 static gint focus_in(GtkWidget *widget, GdkEventFocus *event);
142 static gint focus_out(GtkWidget *widget, GdkEventFocus *event);
143 static void draw_focus(GtkWidget *widget);
145 static void draw_focus_at(Collection *collection, GdkRectangle *area)
147 GtkWidget *widget;
148 GdkGC *gc;
150 widget = GTK_WIDGET(collection);
152 if (collection->target_cb)
153 gc = widget->style->white_gc;
154 else if (GTK_WIDGET_FLAGS(widget) & GTK_HAS_FOCUS)
155 gc = widget->style->black_gc;
156 else
157 gc = widget->style->fg_gc[GTK_STATE_INSENSITIVE];
159 gdk_draw_rectangle(widget->window, gc, FALSE,
160 area->x + 1, area->y + 1,
161 area->width - 3,
162 area->height - 3);
165 static void draw_one_item(Collection *collection, int item, GdkRectangle *area)
167 if (item < collection->number_of_items)
169 collection->draw_item((GtkWidget *) collection,
170 &collection->items[item],
171 area);
172 if (item == collection->wink_item)
173 gdk_draw_rectangle(((GtkWidget *) collection)->window,
174 ((GtkWidget *) collection)->style->black_gc,
175 FALSE,
176 area->x, area->y,
177 area->width - 1, area->height - 1);
180 if (item == collection->cursor_item)
181 draw_focus_at(collection, area);
184 static void draw_focus(GtkWidget *widget)
186 Collection *collection;
188 g_return_if_fail(widget != NULL);
189 g_return_if_fail(IS_COLLECTION(widget));
191 collection = COLLECTION(widget);
193 if (collection->cursor_item < 0 || !GTK_WIDGET_REALIZED(widget))
194 return;
196 collection_draw_item(collection, collection->cursor_item, FALSE);
199 GtkType collection_get_type(void)
201 static guint my_type = 0;
203 if (!my_type)
205 static const GtkTypeInfo my_info =
207 "Collection",
208 sizeof(Collection),
209 sizeof(CollectionClass),
210 (GtkClassInitFunc) collection_class_init,
211 (GtkObjectInitFunc) collection_init,
212 NULL, /* Reserved 1 */
213 NULL, /* Reserved 2 */
214 (GtkClassInitFunc) NULL /* base_class_init_func */
217 my_type = gtk_type_unique(gtk_widget_get_type(),
218 &my_info);
221 return my_type;
224 static void collection_class_init(CollectionClass *class)
226 GtkObjectClass *object_class;
227 GtkWidgetClass *widget_class;
229 object_class = (GtkObjectClass*) class;
230 widget_class = (GtkWidgetClass*) class;
232 parent_class = gtk_type_class(gtk_widget_get_type());
234 gtk_object_add_arg_type("Collection::vadjustment",
235 GTK_TYPE_ADJUSTMENT,
236 GTK_ARG_READWRITE | GTK_ARG_CONSTRUCT,
237 ARG_VADJUSTMENT);
239 object_class->destroy = collection_destroy;
240 object_class->finalize = collection_finalize;
242 widget_class->realize = collection_realize;
243 widget_class->draw = collection_draw;
244 widget_class->expose_event = collection_expose;
245 widget_class->size_request = collection_size_request;
246 widget_class->size_allocate = collection_size_allocate;
247 widget_class->style_set = collection_set_style;
249 widget_class->key_press_event = collection_key_press;
250 widget_class->button_press_event = collection_button_press;
251 widget_class->button_release_event = collection_button_release;
252 widget_class->motion_notify_event = collection_motion_notify;
253 widget_class->focus_in_event = focus_in;
254 widget_class->focus_out_event = focus_out;
255 widget_class->draw_focus = draw_focus;
257 object_class->set_arg = collection_set_arg;
258 object_class->get_arg = collection_get_arg;
260 class->open_item = NULL;
261 class->drag_selection = NULL;
262 class->show_menu = NULL;
263 class->gain_selection = NULL;
264 class->lose_selection = NULL;
266 collection_signals[OPEN_ITEM] = gtk_signal_new("open_item",
267 GTK_RUN_LAST,
268 object_class->type,
269 GTK_SIGNAL_OFFSET(CollectionClass,
270 open_item),
271 gtk_marshal_NONE__POINTER_UINT,
272 GTK_TYPE_NONE, 2,
273 GTK_TYPE_POINTER,
274 GTK_TYPE_UINT);
275 collection_signals[DRAG_SELECTION] = gtk_signal_new("drag_selection",
276 GTK_RUN_LAST,
277 object_class->type,
278 GTK_SIGNAL_OFFSET(CollectionClass,
279 drag_selection),
280 gtk_marshal_NONE__POINTER_UINT,
281 GTK_TYPE_NONE, 2,
282 GTK_TYPE_POINTER,
283 GTK_TYPE_UINT);
284 collection_signals[SHOW_MENU] = gtk_signal_new("show_menu",
285 GTK_RUN_LAST,
286 object_class->type,
287 GTK_SIGNAL_OFFSET(CollectionClass,
288 show_menu),
289 gtk_marshal_NONE__POINTER_INT,
290 GTK_TYPE_NONE, 2,
291 GTK_TYPE_POINTER,
292 GTK_TYPE_INT);
293 collection_signals[GAIN_SELECTION] = gtk_signal_new("gain_selection",
294 GTK_RUN_LAST,
295 object_class->type,
296 GTK_SIGNAL_OFFSET(CollectionClass,
297 gain_selection),
298 gtk_marshal_NONE__UINT,
299 GTK_TYPE_NONE, 1,
300 GTK_TYPE_UINT);
301 collection_signals[LOSE_SELECTION] = gtk_signal_new("lose_selection",
302 GTK_RUN_LAST,
303 object_class->type,
304 GTK_SIGNAL_OFFSET(CollectionClass,
305 lose_selection),
306 gtk_marshal_NONE__UINT,
307 GTK_TYPE_NONE, 1,
308 GTK_TYPE_UINT);
310 gtk_object_class_add_signals(object_class,
311 collection_signals, LAST_SIGNAL);
314 static void collection_init(Collection *object)
316 g_return_if_fail(object != NULL);
317 g_return_if_fail(IS_COLLECTION(object));
319 if (!crosshair)
320 crosshair = gdk_cursor_new(GDK_CROSSHAIR);
322 GTK_WIDGET_SET_FLAGS(GTK_WIDGET(object), GTK_CAN_FOCUS);
324 object->panel = FALSE;
325 object->number_of_items = 0;
326 object->number_selected = 0;
327 object->columns = 1;
328 object->item_width = 64;
329 object->item_height = 64;
330 object->vadj = NULL;
331 object->paint_level = PAINT_OVERWRITE;
332 object->last_scroll = 0;
334 object->items = g_malloc(sizeof(CollectionItem) * MINIMUM_ITEMS);
335 object->cursor_item = -1;
336 object->cursor_item_old = -1;
337 object->wink_item = -1;
338 object->array_size = MINIMUM_ITEMS;
339 object->draw_item = default_draw_item;
340 object->test_point = default_test_point;
342 object->buttons_pressed = 0;
343 object->may_drag = FALSE;
345 return;
348 GtkWidget* collection_new(GtkAdjustment *vadj)
350 if (vadj)
351 g_return_val_if_fail(GTK_IS_ADJUSTMENT(vadj), NULL);
353 return GTK_WIDGET(gtk_widget_new(collection_get_type(),
354 "vadjustment", vadj,
355 NULL));
358 void collection_set_functions(Collection *collection,
359 CollectionDrawFunc draw_item,
360 CollectionTestFunc test_point)
362 GtkWidget *widget;
364 g_return_if_fail(collection != NULL);
365 g_return_if_fail(IS_COLLECTION(collection));
367 widget = GTK_WIDGET(collection);
369 if (!draw_item)
370 draw_item = default_draw_item;
371 if (!test_point)
372 test_point = default_test_point;
374 collection->draw_item = draw_item;
375 collection->test_point = test_point;
377 if (GTK_WIDGET_REALIZED(widget))
379 collection->paint_level = PAINT_CLEAR;
380 gtk_widget_queue_clear(widget);
384 /* After this we are unusable, but our data (if any) is still hanging around.
385 * It will be freed later with finalize.
387 static void collection_destroy(GtkObject *object)
389 Collection *collection;
391 g_return_if_fail(object != NULL);
392 g_return_if_fail(IS_COLLECTION(object));
394 collection = COLLECTION(object);
396 if (collection->wink_item != -1)
398 collection->wink_item = -1;
399 gtk_timeout_remove(collection->wink_timeout);
402 gtk_signal_disconnect_by_data(GTK_OBJECT(collection->vadj),
403 collection);
405 if (GTK_OBJECT_CLASS(parent_class)->destroy)
406 (*GTK_OBJECT_CLASS(parent_class)->destroy)(object);
409 /* This is the last thing that happens to us. Free all data. */
410 static void collection_finalize(GtkObject *object)
412 Collection *collection;
414 collection = COLLECTION(object);
416 if (collection->vadj)
418 gtk_object_unref(GTK_OBJECT(collection->vadj));
421 g_free(collection->items);
424 static void collection_realize(GtkWidget *widget)
426 Collection *collection;
427 GdkWindowAttr attributes;
428 gint attributes_mask;
429 GdkGCValues xor_values;
431 g_return_if_fail(widget != NULL);
432 g_return_if_fail(IS_COLLECTION(widget));
433 g_return_if_fail(widget->parent != NULL);
435 GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);
436 collection = COLLECTION(widget);
438 attributes.x = widget->allocation.x;
439 attributes.y = widget->allocation.y;
440 attributes.width = widget->allocation.width;
441 attributes.height = widget->allocation.height;
442 attributes.wclass = GDK_INPUT_OUTPUT;
443 attributes.window_type = GDK_WINDOW_CHILD;
444 attributes.event_mask = gtk_widget_get_events(widget) |
445 GDK_EXPOSURE_MASK |
446 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
447 GDK_BUTTON1_MOTION_MASK | GDK_BUTTON2_MOTION_MASK |
448 GDK_BUTTON3_MOTION_MASK;
449 attributes.visual = gtk_widget_get_visual(widget);
450 attributes.colormap = gtk_widget_get_colormap(widget);
452 attributes_mask = GDK_WA_X | GDK_WA_Y |
453 GDK_WA_VISUAL | GDK_WA_COLORMAP;
454 widget->window = gdk_window_new(widget->parent->window,
455 &attributes, attributes_mask);
457 widget->style = gtk_style_attach(widget->style, widget->window);
459 gdk_window_set_user_data(widget->window, widget);
461 gdk_window_set_background(widget->window,
462 &widget->style->base[GTK_STATE_INSENSITIVE]);
464 /* Try to stop everything flickering horribly */
465 gdk_window_set_static_gravities(widget->window, TRUE);
467 set_vadjustment(collection);
469 xor_values.function = GDK_XOR;
470 xor_values.foreground.red = 0xffff;
471 xor_values.foreground.green = 0xffff;
472 xor_values.foreground.blue = 0xffff;
473 gdk_color_alloc(gtk_widget_get_colormap(widget),
474 &xor_values.foreground);
475 collection->xor_gc = gdk_gc_new_with_values(widget->window,
476 &xor_values,
477 GDK_GC_FOREGROUND
478 | GDK_GC_FUNCTION);
481 static void collection_size_request(GtkWidget *widget,
482 GtkRequisition *requisition)
484 requisition->width = MIN_WIDTH;
485 requisition->height = MIN_HEIGHT;
488 static void collection_size_allocate(GtkWidget *widget,
489 GtkAllocation *allocation)
491 Collection *collection;
492 int old_columns;
494 g_return_if_fail(widget != NULL);
495 g_return_if_fail(IS_COLLECTION(widget));
496 g_return_if_fail(allocation != NULL);
498 collection = COLLECTION(widget);
500 old_columns = collection->columns;
501 if (widget->allocation.x != allocation->x
502 || widget->allocation.y != allocation->y)
503 collection->paint_level = PAINT_CLEAR;
505 widget->allocation = *allocation;
507 collection->columns = allocation->width / collection->item_width;
508 if (collection->columns < 1)
509 collection->columns = 1;
511 if (GTK_WIDGET_REALIZED(widget))
513 gdk_window_move_resize(widget->window,
514 allocation->x, allocation->y,
515 allocation->width, allocation->height);
517 if (old_columns != collection->columns)
519 collection->paint_level = PAINT_CLEAR;
520 gtk_widget_queue_clear(widget);
523 set_vadjustment(collection);
525 if (collection->cursor_item != -1)
526 scroll_to_show(collection, collection->cursor_item);
530 static void collection_set_style(GtkWidget *widget,
531 GtkStyle *previous_style)
533 Collection *collection;
535 g_return_if_fail(IS_COLLECTION(widget));
537 collection = COLLECTION(widget);
539 collection->paint_level = PAINT_CLEAR;
541 if (parent_class->style_set)
542 (*parent_class->style_set)(widget, previous_style);
545 static gint collection_paint(Collection *collection,
546 GdkRectangle *area)
548 GdkRectangle whole, item_area;
549 GtkWidget *widget;
550 int row, col;
551 int item;
552 int scroll;
553 int start_row, last_row;
554 int start_col, last_col;
555 int phys_last_col;
556 GdkRectangle clip;
558 scroll = collection->vadj->value;
560 widget = GTK_WIDGET(collection);
562 if (collection->paint_level > PAINT_NORMAL || area == NULL)
564 guint width, height;
565 gdk_window_get_size(widget->window, &width, &height);
567 whole.x = 0;
568 whole.y = 0;
569 whole.width = width;
570 whole.height = height;
572 area = &whole;
574 if (collection->paint_level == PAINT_CLEAR
575 && !collection->lasso_box)
576 gdk_window_clear(widget->window);
578 collection->paint_level = PAINT_NORMAL;
581 /* Calculate the ranges to plot */
582 start_row = (area->y + scroll) / collection->item_height;
583 last_row = (area->y + area->height - 1 + scroll)
584 / collection->item_height;
585 row = start_row;
587 start_col = area->x / collection->item_width;
588 phys_last_col = (area->x + area->width - 1) / collection->item_width;
590 if (collection->lasso_box)
592 /* You can't be too careful with lasso boxes...
594 * clip gives the total area drawn over (this may be larger
595 * than the requested area). It's used to redraw the lasso
596 * box.
598 clip.x = start_col * collection->item_width;
599 clip.y = start_row * collection->item_height - scroll;
600 clip.width = (phys_last_col - start_col + 1)
601 * collection->item_width;
602 clip.height = (last_row - start_row + 1)
603 * collection->item_height;
605 gdk_window_clear_area(widget->window,
606 clip.x, clip.y, clip.width, clip.height);
609 if (start_col < collection->columns)
611 if (phys_last_col >= collection->columns)
612 last_col = collection->columns - 1;
613 else
614 last_col = phys_last_col;
616 col = start_col;
618 item = row * collection->columns + col;
619 item_area.width = collection->item_width;
620 item_area.height = collection->item_height;
622 while ((item == 0 || item < collection->number_of_items)
623 && row <= last_row)
625 item_area.x = col * collection->item_width;
626 item_area.y = row * collection->item_height - scroll;
628 draw_one_item(collection, item, &item_area);
629 col++;
631 if (col > last_col)
633 col = start_col;
634 row++;
635 item = row * collection->columns + col;
637 else
638 item++;
642 if (collection->lasso_box)
644 gdk_gc_set_clip_rectangle(collection->xor_gc, &clip);
645 draw_lasso_box(collection);
646 gdk_gc_set_clip_rectangle(collection->xor_gc, NULL);
649 return FALSE;
652 static void default_draw_item( GtkWidget *widget,
653 CollectionItem *item,
654 GdkRectangle *area)
656 gdk_draw_arc(widget->window,
657 item->selected ? widget->style->white_gc
658 : widget->style->black_gc,
659 TRUE,
660 area->x, area->y,
661 area->width, area->height,
662 0, 360 * 64);
666 static gboolean default_test_point(Collection *collection,
667 int point_x, int point_y,
668 CollectionItem *item,
669 int width, int height)
671 float f_x, f_y;
673 /* Convert to point in unit circle */
674 f_x = ((float) point_x / width) - 0.5;
675 f_y = ((float) point_y / height) - 0.5;
677 return (f_x * f_x) + (f_y * f_y) <= .25;
680 static void collection_set_arg( GtkObject *object,
681 GtkArg *arg,
682 guint arg_id)
684 Collection *collection;
686 collection = COLLECTION(object);
688 switch (arg_id)
690 case ARG_VADJUSTMENT:
691 collection_set_adjustment(collection,
692 GTK_VALUE_POINTER(*arg));
693 break;
694 default:
695 break;
699 static void collection_set_adjustment( Collection *collection,
700 GtkAdjustment *vadj)
702 if (vadj)
703 g_return_if_fail (GTK_IS_ADJUSTMENT (vadj));
704 else
705 vadj = GTK_ADJUSTMENT(gtk_adjustment_new(0.0,
706 0.0, 0.0,
707 0.0, 0.0, 0.0));
708 if (collection->vadj && (collection->vadj != vadj))
710 gtk_signal_disconnect_by_data(GTK_OBJECT(collection->vadj),
711 collection);
712 gtk_object_unref(GTK_OBJECT(collection->vadj));
715 if (collection->vadj != vadj)
717 collection->vadj = vadj;
718 gtk_object_ref(GTK_OBJECT(collection->vadj));
719 gtk_object_sink(GTK_OBJECT(collection->vadj));
721 gtk_signal_connect(GTK_OBJECT(collection->vadj),
722 "changed",
723 (GtkSignalFunc) collection_adjustment,
724 collection);
725 gtk_signal_connect(GTK_OBJECT(collection->vadj),
726 "value_changed",
727 (GtkSignalFunc) collection_adjustment,
728 collection);
729 gtk_signal_connect(GTK_OBJECT(collection->vadj),
730 "disconnect",
731 (GtkSignalFunc) collection_disconnect,
732 collection);
733 collection_adjustment(vadj, collection);
737 static void collection_get_arg( GtkObject *object,
738 GtkArg *arg,
739 guint arg_id)
741 Collection *collection;
743 collection = COLLECTION(object);
745 switch (arg_id)
747 case ARG_VADJUSTMENT:
748 GTK_VALUE_POINTER(*arg) = collection->vadj;
749 break;
750 default:
751 arg->type = GTK_TYPE_INVALID;
752 break;
756 /* Something about the adjustment has changed */
757 static void collection_adjustment(GtkAdjustment *adjustment,
758 Collection *collection)
760 gint diff;
762 g_return_if_fail(adjustment != NULL);
763 g_return_if_fail(GTK_IS_ADJUSTMENT(adjustment));
764 g_return_if_fail(collection != NULL);
765 g_return_if_fail(IS_COLLECTION(collection));
767 diff = ((gint) adjustment->value) - collection->last_scroll;
769 if (diff)
771 collection->last_scroll = adjustment->value;
773 scroll_by(collection, diff);
777 static void collection_disconnect(GtkAdjustment *adjustment,
778 Collection *collection)
780 g_return_if_fail(adjustment != NULL);
781 g_return_if_fail(GTK_IS_ADJUSTMENT(adjustment));
782 g_return_if_fail(collection != NULL);
783 g_return_if_fail(IS_COLLECTION(collection));
785 collection_set_adjustment(collection, NULL);
788 static void set_vadjustment(Collection *collection)
790 GtkWidget *widget;
791 guint height;
792 int cols, rows;
794 widget = GTK_WIDGET(collection);
796 if (!GTK_WIDGET_REALIZED(widget))
797 return;
799 gdk_window_get_size(widget->window, NULL, &height);
800 cols = collection->columns;
801 rows = (collection->number_of_items + cols - 1) / cols;
803 collection->vadj->lower = 0.0;
804 collection->vadj->upper = collection->item_height * rows;
806 collection->vadj->step_increment =
807 MIN(collection->vadj->upper, collection->item_height / 4);
809 collection->vadj->page_increment =
810 MIN(collection->vadj->upper,
811 height - 5.0);
813 collection->vadj->page_size = MIN(collection->vadj->upper, height);
815 collection->vadj->value = MIN(collection->vadj->value,
816 collection->vadj->upper - collection->vadj->page_size);
818 collection->vadj->value = MAX(collection->vadj->value, 0.0);
820 gtk_signal_emit_by_name(GTK_OBJECT(collection->vadj), "changed");
823 static void collection_draw(GtkWidget *widget, GdkRectangle *area)
825 Collection *collection;
827 g_return_if_fail(widget != NULL);
828 g_return_if_fail(IS_COLLECTION(widget));
829 g_return_if_fail(area != NULL); /* Not actually used */
831 collection = COLLECTION(widget);
833 if (collection->paint_level > PAINT_NORMAL)
834 collection_paint(collection, area);
837 static gint collection_expose(GtkWidget *widget, GdkEventExpose *event)
839 g_return_val_if_fail(widget != NULL, FALSE);
840 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
841 g_return_val_if_fail(event != NULL, FALSE);
843 collection_paint(COLLECTION(widget), &event->area);
845 return FALSE;
848 /* Positive makes the contents go move up the screen */
849 static void scroll_by(Collection *collection, gint diff)
851 GtkWidget *widget;
852 guint width, height;
853 guint from_y, to_y;
854 guint amount;
855 GdkRectangle new_area;
857 if (diff == 0)
858 return;
860 widget = GTK_WIDGET(collection);
862 if (collection->lasso_box)
863 remove_lasso_box(collection);
865 gdk_window_get_size(widget->window, &width, &height);
866 new_area.x = 0;
867 new_area.width = width;
869 if (diff > 0)
871 amount = diff;
872 from_y = amount;
873 to_y = 0;
874 new_area.y = height - amount;
876 else
878 amount = -diff;
879 from_y = 0;
880 to_y = amount;
881 new_area.y = 0;
884 new_area.height = amount;
886 if (amount < height)
888 gdk_draw_pixmap(widget->window,
889 widget->style->white_gc,
890 widget->window,
892 from_y,
894 to_y,
895 width,
896 height - amount);
897 /* We have to redraw everything because any pending
898 * expose events now contain invalid areas.
899 * Don't need to clear the area first though...
901 if (collection->paint_level < PAINT_OVERWRITE)
902 collection->paint_level = PAINT_OVERWRITE;
904 else
905 collection->paint_level = PAINT_CLEAR;
907 gdk_window_clear_area(widget->window,
908 0, new_area.y,
909 width, new_area.height);
910 collection_paint(collection, NULL);
913 static void resize_arrays(Collection *collection, guint new_size)
915 g_return_if_fail(collection != NULL);
916 g_return_if_fail(IS_COLLECTION(collection));
917 g_return_if_fail(new_size >= collection->number_of_items);
919 collection->items = g_realloc(collection->items,
920 sizeof(CollectionItem) * new_size);
921 collection->array_size = new_size;
924 static void return_pressed(Collection *collection)
926 int item = collection->cursor_item;
927 CollectionTargetFunc cb = collection->target_cb;
928 gpointer data = collection->target_data;
930 collection_target(collection, NULL, NULL);
931 if (item < 0 || item >= collection->number_of_items)
932 return;
934 if (cb)
936 cb(collection, item, data);
937 return;
940 gtk_signal_emit(GTK_OBJECT(collection),
941 collection_signals[OPEN_ITEM],
942 collection->items[item].data,
943 item);
946 static gint collection_key_press(GtkWidget *widget, GdkEventKey *event)
948 Collection *collection;
949 int item;
951 g_return_val_if_fail(widget != NULL, FALSE);
952 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
953 g_return_val_if_fail(event != NULL, FALSE);
955 collection = (Collection *) widget;
956 item = collection->cursor_item;
958 switch (event->keyval)
960 case GDK_Left:
961 collection_move_cursor(collection, 0, -1);
962 break;
963 case GDK_Right:
964 collection_move_cursor(collection, 0, 1);
965 break;
966 case GDK_Up:
967 collection_move_cursor(collection, -1, 0);
968 break;
969 case GDK_Down:
970 collection_move_cursor(collection, 1, 0);
971 break;
972 case GDK_Home:
973 collection_set_cursor_item(collection, 0);
974 break;
975 case GDK_End:
976 collection_set_cursor_item(collection,
977 MAX((gint) collection->number_of_items - 1, 0));
978 break;
979 case GDK_Page_Up:
980 collection_move_cursor(collection, -10, 0);
981 break;
982 case GDK_Page_Down:
983 collection_move_cursor(collection, 10, 0);
984 break;
985 case GDK_Return:
986 return_pressed(collection);
987 break;
988 case GDK_Escape:
989 if (!collection->target_cb)
991 collection_set_cursor_item(collection, -1);
992 collection_clear_selection(collection);
993 return FALSE; /* Pass it on */
995 collection_target(collection, NULL, NULL);
996 break;
997 case ' ':
998 if (item >=0 && item < collection->number_of_items)
999 collection_toggle_item(collection, item);
1000 break;
1001 default:
1002 return FALSE;
1005 return TRUE;
1008 static gint collection_button_press(GtkWidget *widget,
1009 GdkEventButton *event)
1011 Collection *collection;
1012 int row, col;
1013 int item;
1014 int action;
1015 int scroll;
1016 guint stacked_time;
1018 g_return_val_if_fail(widget != NULL, FALSE);
1019 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1020 g_return_val_if_fail(event != NULL, FALSE);
1022 collection = COLLECTION(widget);
1024 collection->item_clicked = -1;
1026 if (event->button > 3)
1028 int diff;
1030 /* Wheel mouse scrolling */
1031 if (event->button == 4)
1032 diff = -((signed int) collection->item_height) / 4;
1033 else if (event->button == 5)
1034 diff = collection->item_height / 4;
1035 else
1036 diff = 0;
1038 if (diff)
1040 int old_value = collection->vadj->value;
1041 int new_value = 0;
1042 gboolean box = collection->lasso_box;
1044 new_value = CLAMP(old_value + diff, 0.0,
1045 collection->vadj->upper
1046 - collection->vadj->page_size);
1047 diff = new_value - old_value;
1048 if (diff)
1050 if (box)
1052 remove_lasso_box(collection);
1053 collection->drag_box_y[0] -= diff;
1055 collection->vadj->value = new_value;
1056 gtk_signal_emit_by_name(
1057 GTK_OBJECT(collection->vadj),
1058 "changed");
1059 if (box)
1060 add_lasso_box(collection);
1063 return FALSE;
1066 if (collection->cursor_item != -1)
1067 collection_set_cursor_item(collection, -1);
1069 scroll = collection->vadj->value;
1071 if (event->type == GDK_BUTTON_PRESS &&
1072 event->button != collection_menu_button)
1074 if (collection->buttons_pressed++ == 0)
1075 gtk_grab_add(widget);
1076 else
1077 return FALSE; /* Ignore extra presses */
1080 if (event->state & GDK_CONTROL_MASK)
1081 action = 2;
1082 else
1083 action = event->button;
1085 /* Ignore all clicks while we are dragging a lasso box */
1086 if (collection->lasso_box)
1087 return TRUE;
1089 col = event->x / collection->item_width;
1090 row = (event->y + scroll) / collection->item_height;
1092 if (col < 0 || row < 0 || col >= collection->columns)
1093 item = -1;
1094 else
1096 item = col + row * collection->columns;
1097 if (item >= collection->number_of_items
1099 !collection->test_point(collection,
1100 event->x - col * collection->item_width,
1101 event->y - row * collection->item_height
1102 + scroll,
1103 &collection->items[item],
1104 collection->item_width,
1105 collection->item_height))
1107 item = -1;
1111 if (collection->target_cb)
1113 CollectionTargetFunc cb = collection->target_cb;
1114 gpointer data = collection->target_data;
1116 collection_target(collection, NULL, NULL);
1117 if (collection->buttons_pressed)
1119 gtk_grab_remove(widget);
1120 collection->buttons_pressed = 0;
1122 if (item > -1 && event->button != collection_menu_button)
1123 cb(collection, item, data);
1124 return TRUE;
1127 collection->drag_box_x[0] = event->x;
1128 collection->drag_box_y[0] = event->y;
1129 collection->item_clicked = item;
1131 stacked_time = current_event_time;
1132 current_event_time = event->time;
1134 if (event->button == collection_menu_button)
1136 gtk_signal_emit(GTK_OBJECT(collection),
1137 collection_signals[SHOW_MENU],
1138 event,
1139 item);
1141 else if (event->type == GDK_2BUTTON_PRESS && collection->panel)
1143 /* Do nothing */
1145 else if ((event->type == GDK_2BUTTON_PRESS && !collection_single_click)
1146 || collection->panel)
1148 if (item >= 0)
1150 if (collection->buttons_pressed)
1152 gtk_grab_remove(widget);
1153 collection->buttons_pressed = 0;
1155 collection_unselect_item(collection, item);
1156 gtk_signal_emit(GTK_OBJECT(collection),
1157 collection_signals[OPEN_ITEM],
1158 collection->items[item].data,
1159 item);
1162 else if (event->type == GDK_BUTTON_PRESS)
1164 collection->may_drag = event->button != collection_menu_button;
1166 if (item >= 0)
1168 if (action == 1)
1170 if (!collection->items[item].selected)
1172 collection_select_item(collection,
1173 item);
1174 collection_clear_except(collection,
1175 item);
1178 else
1179 collection_toggle_item(collection, item);
1181 else if (action == 1)
1182 collection_clear_selection(collection);
1185 current_event_time = stacked_time;
1186 return FALSE;
1189 static gint collection_button_release(GtkWidget *widget,
1190 GdkEventButton *event)
1192 Collection *collection;
1193 int top, bottom;
1194 int row, last_row;
1195 int w, h;
1196 int col, start_col, last_col;
1197 int scroll;
1198 int item;
1199 guint stacked_time;
1200 int button;
1202 g_return_val_if_fail(widget != NULL, FALSE);
1203 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1204 g_return_val_if_fail(event != NULL, FALSE);
1206 collection = COLLECTION(widget);
1207 button = event->button;
1209 scroll = collection->vadj->value;
1211 if (event->button > 3 || event->button == collection_menu_button)
1212 return FALSE;
1213 if (collection->buttons_pressed == 0)
1214 return FALSE;
1215 if (--collection->buttons_pressed == 0)
1216 gtk_grab_remove(widget);
1217 else
1218 return FALSE; /* Wait until ALL buttons are up */
1220 if (!collection->lasso_box)
1222 int item = collection->item_clicked;
1224 if (collection_single_click && item > -1
1225 && item < collection->number_of_items
1226 && (event->state & GDK_CONTROL_MASK) == 0)
1228 int dx = event->x - collection->drag_box_x[0];
1229 int dy = event->y - collection->drag_box_y[0];
1231 if (ABS(dx) + ABS(dy) > 9)
1232 return FALSE;
1234 collection_unselect_item(collection, item);
1235 gtk_signal_emit(GTK_OBJECT(collection),
1236 collection_signals[OPEN_ITEM],
1237 collection->items[item].data,
1238 item);
1241 return FALSE;
1244 remove_lasso_box(collection);
1246 w = collection->item_width;
1247 h = collection->item_height;
1249 top = collection->drag_box_y[0] + scroll;
1250 bottom = collection->drag_box_y[1] + scroll;
1251 if (top > bottom)
1253 int tmp;
1254 tmp = top;
1255 top = bottom;
1256 bottom = tmp;
1258 top += h / 4;
1259 bottom -= h / 4;
1261 row = MAX(top / h, 0);
1262 last_row = bottom / h;
1264 top = collection->drag_box_x[0]; /* (left) */
1265 bottom = collection->drag_box_x[1];
1266 if (top > bottom)
1268 int tmp;
1269 tmp = top;
1270 top = bottom;
1271 bottom = tmp;
1273 top += w / 4;
1274 bottom -= w / 4;
1275 start_col = MAX(top / w, 0);
1276 last_col = bottom / w;
1277 if (last_col >= collection->columns)
1278 last_col = collection->columns - 1;
1280 stacked_time = current_event_time;
1281 current_event_time = event->time;
1283 while (row <= last_row)
1285 col = start_col;
1286 item = row * collection->columns + col;
1287 while (col <= last_col)
1289 if (item >= collection->number_of_items)
1291 current_event_time = stacked_time;
1292 return FALSE;
1295 if (button == 1)
1296 collection_select_item(collection, item);
1297 else
1298 collection_toggle_item(collection, item);
1299 col++;
1300 item++;
1302 row++;
1305 current_event_time = stacked_time;
1307 return FALSE;
1310 static gint collection_motion_notify(GtkWidget *widget,
1311 GdkEventMotion *event)
1313 Collection *collection;
1314 int x, y;
1315 guint stacked_time;
1317 g_return_val_if_fail(widget != NULL, FALSE);
1318 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1319 g_return_val_if_fail(event != NULL, FALSE);
1321 collection = COLLECTION(widget);
1323 if (collection->buttons_pressed == 0)
1324 return FALSE;
1326 stacked_time = current_event_time;
1327 current_event_time = event->time;
1329 if (event->window != widget->window)
1330 gdk_window_get_pointer(widget->window, &x, &y, NULL);
1331 else
1333 x = event->x;
1334 y = event->y;
1337 if (collection->lasso_box)
1339 int new_value = 0, diff;
1340 int height;
1342 gdk_window_get_size(widget->window, NULL, &height);
1344 if (y < 0)
1346 int old_value = collection->vadj->value;
1348 new_value = MAX(old_value + y / 10, 0.0);
1349 diff = new_value - old_value;
1351 else if (y > height)
1353 int old_value = collection->vadj->value;
1355 new_value = MIN(old_value + (y - height) / 10,
1356 collection->vadj->upper
1357 - collection->vadj->page_size);
1358 diff = new_value - old_value;
1360 else
1361 diff = 0;
1363 remove_lasso_box(collection);
1364 collection->drag_box_x[1] = x;
1365 collection->drag_box_y[1] = y;
1367 if (diff)
1369 collection->drag_box_y[0] -= diff;
1370 collection->vadj->value = new_value;
1371 gtk_signal_emit_by_name(GTK_OBJECT(collection->vadj),
1372 "changed");
1374 add_lasso_box(collection);
1376 else if (collection->may_drag)
1378 int dx = x - collection->drag_box_x[0];
1379 int dy = y - collection->drag_box_y[0];
1381 if (abs(dx) > 9 || abs(dy) > 9)
1383 int row, col, item;
1384 int scroll = collection->vadj->value;
1386 collection->may_drag = FALSE;
1388 col = collection->drag_box_x[0]
1389 / collection->item_width;
1390 row = (collection->drag_box_y[0] + scroll)
1391 / collection->item_height;
1392 item = item_at_row_col(collection, row, col);
1394 if (item != -1 && collection->test_point(collection,
1395 collection->drag_box_x[0] -
1396 col * collection->item_width,
1397 collection->drag_box_y[0]
1398 - row * collection->item_height
1399 + scroll,
1400 &collection->items[item],
1401 collection->item_width,
1402 collection->item_height))
1404 collection->buttons_pressed = 0;
1405 gtk_grab_remove(widget);
1406 collection_select_item(collection, item);
1407 gtk_signal_emit(GTK_OBJECT(collection),
1408 collection_signals[DRAG_SELECTION],
1409 event,
1410 collection->number_selected);
1412 else
1414 collection->drag_box_x[1] = x;
1415 collection->drag_box_y[1] = y;
1416 add_lasso_box(collection);
1421 current_event_time = stacked_time;
1422 return FALSE;
1425 static void add_lasso_box(Collection *collection)
1427 g_return_if_fail(collection != NULL);
1428 g_return_if_fail(IS_COLLECTION(collection));
1429 g_return_if_fail(collection->lasso_box == FALSE);
1431 collection->lasso_box = TRUE;
1432 draw_lasso_box(collection);
1435 static void draw_lasso_box(Collection *collection)
1437 GtkWidget *widget;
1438 int x, y, width, height;
1440 widget = GTK_WIDGET(collection);
1442 x = MIN(collection->drag_box_x[0], collection->drag_box_x[1]);
1443 y = MIN(collection->drag_box_y[0], collection->drag_box_y[1]);
1444 width = abs(collection->drag_box_x[1] - collection->drag_box_x[0]);
1445 height = abs(collection->drag_box_y[1] - collection->drag_box_y[0]);
1447 gdk_draw_rectangle(widget->window, collection->xor_gc, FALSE,
1448 x, y, width, height);
1451 static void remove_lasso_box(Collection *collection)
1453 g_return_if_fail(collection != NULL);
1454 g_return_if_fail(IS_COLLECTION(collection));
1455 g_return_if_fail(collection->lasso_box == TRUE);
1457 draw_lasso_box(collection);
1459 collection->lasso_box = FALSE;
1461 return;
1464 /* Convert a row,col address to an item number, or -1 if none */
1465 static int item_at_row_col(Collection *collection, int row, int col)
1467 int item;
1469 if (row < 0 || col < 0 || col >= collection->columns)
1470 return -1;
1472 item = col + row * collection->columns;
1474 if (item >= collection->number_of_items)
1475 return -1;
1476 return item;
1479 /* Make sure that 'item' is fully visible (vertically), scrolling if not. */
1480 static void scroll_to_show(Collection *collection, int item)
1482 int first, last, row;
1484 g_return_if_fail(collection != NULL);
1485 g_return_if_fail(IS_COLLECTION(collection));
1487 row = item / collection->columns;
1488 get_visible_limits(collection, &first, &last);
1490 if (row <= first)
1492 gtk_adjustment_set_value(collection->vadj,
1493 row * collection->item_height);
1495 else if (row >= last)
1497 GtkWidget *widget = (GtkWidget *) collection;
1498 int height;
1500 if (GTK_WIDGET_REALIZED(widget))
1502 gdk_window_get_size(widget->window, NULL, &height);
1503 gtk_adjustment_set_value(collection->vadj,
1504 (row + 1) * collection->item_height - height);
1509 /* Return the first and last rows which are [partly] visible. Does not
1510 * ensure that the rows actually exist (contain items).
1512 static void get_visible_limits(Collection *collection, int *first, int *last)
1514 GtkWidget *widget = (GtkWidget *) collection;
1515 int scroll, height;
1517 g_return_if_fail(collection != NULL);
1518 g_return_if_fail(IS_COLLECTION(collection));
1519 g_return_if_fail(first != NULL && last != NULL);
1521 if (!GTK_WIDGET_REALIZED(widget))
1523 *first = 0;
1524 *last = 0;
1526 else
1528 scroll = collection->vadj->value;
1529 gdk_window_get_size(widget->window, NULL, &height);
1531 *first = MAX(scroll / collection->item_height, 0);
1532 *last = (scroll + height - 1) /collection->item_height;
1534 if (*last < *first)
1535 *last = *first;
1539 /* Unselect all items except number item (-1 to unselect everything) */
1540 static void collection_clear_except(Collection *collection, gint exception)
1542 GtkWidget *widget;
1543 GdkRectangle area;
1544 int item = 0;
1545 int scroll;
1546 int end; /* Selected items to end up with */
1548 widget = GTK_WIDGET(collection);
1549 scroll = collection->vadj->value;
1551 end = exception >= 0 && exception < collection->number_of_items
1552 ? collection->items[exception].selected != 0 : 0;
1554 area.width = collection->item_width;
1555 area.height = collection->item_height;
1557 if (collection->number_selected == 0)
1558 return;
1560 while (collection->number_selected > end)
1562 while (item == exception || !collection->items[item].selected)
1563 item++;
1565 area.x = (item % collection->columns) * area.width;
1566 area.y = (item / collection->columns) * area.height
1567 - scroll;
1569 collection->items[item++].selected = FALSE;
1570 gdk_window_clear_area(widget->window,
1571 area.x, area.y, area.width, area.height);
1572 collection_paint(collection, &area);
1574 collection->number_selected--;
1577 if (end == 0)
1578 gtk_signal_emit(GTK_OBJECT(collection),
1579 collection_signals[LOSE_SELECTION],
1580 current_event_time);
1583 /* Cancel the current wink effect. */
1584 static void cancel_wink(Collection *collection)
1586 gint item;
1588 g_return_if_fail(collection != NULL);
1589 g_return_if_fail(IS_COLLECTION(collection));
1590 g_return_if_fail(collection->wink_item != -1);
1592 item = collection->wink_item;
1594 collection->wink_item = -1;
1595 gtk_timeout_remove(collection->wink_timeout);
1597 collection_draw_item(collection, item, TRUE);
1600 static gboolean cancel_wink_timeout(Collection *collection)
1602 gint item;
1604 g_return_val_if_fail(collection != NULL, FALSE);
1605 g_return_val_if_fail(IS_COLLECTION(collection), FALSE);
1606 g_return_val_if_fail(collection->wink_item != -1, FALSE);
1608 item = collection->wink_item;
1610 collection->wink_item = -1;
1612 collection_draw_item(collection, item, TRUE);
1614 return FALSE;
1617 static gint focus_in(GtkWidget *widget, GdkEventFocus *event)
1619 g_return_val_if_fail(widget != NULL, FALSE);
1620 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1621 g_return_val_if_fail(event != NULL, FALSE);
1623 GTK_WIDGET_SET_FLAGS(widget, GTK_HAS_FOCUS);
1624 gtk_widget_draw_focus(widget);
1626 return FALSE;
1629 static gint focus_out(GtkWidget *widget, GdkEventFocus *event)
1631 g_return_val_if_fail(widget != NULL, FALSE);
1632 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1633 g_return_val_if_fail(event != NULL, FALSE);
1635 GTK_WIDGET_UNSET_FLAGS(widget, GTK_HAS_FOCUS);
1636 gtk_widget_draw_focus(widget);
1638 return FALSE;
1641 /* Functions for managing collections */
1643 /* Remove all objects from the collection */
1644 void collection_clear(Collection *collection)
1646 int prev_selected;
1648 g_return_if_fail(IS_COLLECTION(collection));
1650 if (collection->number_of_items == 0)
1651 return;
1653 if (collection->wink_item != -1)
1655 collection->wink_item = -1;
1656 gtk_timeout_remove(collection->wink_timeout);
1659 collection_set_cursor_item(collection,
1660 collection->cursor_item == -1 ? -1: 0);
1661 collection->cursor_item_old = -1;
1662 prev_selected = collection->number_selected;
1663 collection->number_of_items = collection->number_selected = 0;
1665 resize_arrays(collection, MINIMUM_ITEMS);
1667 collection->paint_level = PAINT_CLEAR;
1669 gtk_widget_queue_clear(GTK_WIDGET(collection));
1671 if (prev_selected && collection->number_selected == 0)
1672 gtk_signal_emit(GTK_OBJECT(collection),
1673 collection_signals[LOSE_SELECTION],
1674 current_event_time);
1677 /* Inserts a new item at the end. The new item is unselected, and its
1678 * number is returned.
1680 gint collection_insert(Collection *collection, gpointer data)
1682 int item;
1684 g_return_val_if_fail(IS_COLLECTION(collection), -1);
1686 item = collection->number_of_items;
1688 if (item >= collection->array_size)
1689 resize_arrays(collection, item + (item >> 1));
1691 collection->items[item].data = data;
1692 collection->items[item].selected = FALSE;
1694 collection->number_of_items++;
1696 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection)))
1698 set_vadjustment(collection);
1699 collection_draw_item(collection,
1700 collection->number_of_items - 1,
1701 FALSE);
1704 return item;
1707 /* Unselect an item by number */
1708 void collection_unselect_item(Collection *collection, gint item)
1710 g_return_if_fail(collection != NULL);
1711 g_return_if_fail(IS_COLLECTION(collection));
1712 g_return_if_fail(item >= 0 && item < collection->number_of_items);
1714 if (collection->items[item].selected)
1716 collection->items[item].selected = FALSE;
1717 collection_draw_item(collection, item, TRUE);
1719 if (--collection->number_selected == 0)
1720 gtk_signal_emit(GTK_OBJECT(collection),
1721 collection_signals[LOSE_SELECTION],
1722 current_event_time);
1726 /* Select an item by number */
1727 void collection_select_item(Collection *collection, gint item)
1729 g_return_if_fail(collection != NULL);
1730 g_return_if_fail(IS_COLLECTION(collection));
1731 g_return_if_fail(item >= 0 && item < collection->number_of_items);
1733 if (collection->items[item].selected)
1734 return; /* Already selected */
1736 collection->items[item].selected = TRUE;
1737 collection_draw_item(collection, item, TRUE);
1739 if (collection->number_selected++ == 0)
1740 gtk_signal_emit(GTK_OBJECT(collection),
1741 collection_signals[GAIN_SELECTION],
1742 current_event_time);
1745 /* Toggle the selected state of an item (by number) */
1746 void collection_toggle_item(Collection *collection, gint item)
1748 g_return_if_fail(collection != NULL);
1749 g_return_if_fail(IS_COLLECTION(collection));
1750 g_return_if_fail(item >= 0 && item < collection->number_of_items);
1752 if (collection->items[item].selected)
1754 collection->items[item].selected = FALSE;
1755 if (--collection->number_selected == 0)
1756 gtk_signal_emit(GTK_OBJECT(collection),
1757 collection_signals[LOSE_SELECTION],
1758 current_event_time);
1760 else
1762 collection->items[item].selected = TRUE;
1763 if (collection->number_selected++ == 0)
1764 gtk_signal_emit(GTK_OBJECT(collection),
1765 collection_signals[GAIN_SELECTION],
1766 current_event_time);
1768 collection_draw_item(collection, item, TRUE);
1771 /* Select all items in the collection */
1772 void collection_select_all(Collection *collection)
1774 GtkWidget *widget;
1775 GdkRectangle area;
1776 int item = 0;
1777 int scroll;
1779 g_return_if_fail(collection != NULL);
1780 g_return_if_fail(IS_COLLECTION(collection));
1782 widget = GTK_WIDGET(collection);
1783 scroll = collection->vadj->value;
1785 area.width = collection->item_width;
1786 area.height = collection->item_height;
1788 if (collection->number_selected == collection->number_of_items)
1789 return; /* Nothing to do */
1791 while (collection->number_selected < collection->number_of_items)
1793 while (collection->items[item].selected)
1794 item++;
1796 area.x = (item % collection->columns) * area.width;
1797 area.y = (item / collection->columns) * area.height
1798 - scroll;
1800 collection->items[item++].selected = TRUE;
1801 gdk_window_clear_area(widget->window,
1802 area.x, area.y, area.width, area.height);
1803 collection_paint(collection, &area);
1805 collection->number_selected++;
1808 gtk_signal_emit(GTK_OBJECT(collection),
1809 collection_signals[GAIN_SELECTION],
1810 current_event_time);
1813 /* Unselect all items in the collection */
1814 void collection_clear_selection(Collection *collection)
1816 g_return_if_fail(collection != NULL);
1817 g_return_if_fail(IS_COLLECTION(collection));
1819 collection_clear_except(collection, -1);
1822 /* Force a redraw of the specified item, if it is visible */
1823 void collection_draw_item(Collection *collection, gint item, gboolean blank)
1825 int height;
1826 GdkRectangle area;
1827 GtkWidget *widget;
1828 int row, col;
1829 int scroll;
1830 int area_y, area_height; /* NOT shorts! */
1832 g_return_if_fail(collection != NULL);
1833 g_return_if_fail(IS_COLLECTION(collection));
1834 g_return_if_fail(item >= 0 &&
1835 (item == 0 || item < collection->number_of_items));
1837 widget = GTK_WIDGET(collection);
1838 if (!GTK_WIDGET_REALIZED(widget))
1839 return;
1841 col = item % collection->columns;
1842 row = item / collection->columns;
1843 scroll = collection->vadj->value; /* (round to int) */
1845 area.x = col * collection->item_width;
1846 area_y = row * collection->item_height - scroll;
1847 area.width = collection->item_width;
1848 area_height = collection->item_height;
1850 if (area_y + area_height < 0)
1851 return;
1853 gdk_window_get_size(widget->window, NULL, &height);
1855 if (area_y > height)
1856 return;
1858 area.y = area_y;
1859 area.height = area_height;
1861 if (blank || collection->lasso_box)
1862 gdk_window_clear_area(widget->window,
1863 area.x, area.y, area.width, area.height);
1865 draw_one_item(collection, item, &area);
1867 if (collection->lasso_box)
1869 gdk_gc_set_clip_rectangle(collection->xor_gc, &area);
1870 draw_lasso_box(collection);
1871 gdk_gc_set_clip_rectangle(collection->xor_gc, NULL);
1875 void collection_set_item_size(Collection *collection, int width, int height)
1877 GtkWidget *widget;
1879 g_return_if_fail(collection != NULL);
1880 g_return_if_fail(IS_COLLECTION(collection));
1881 g_return_if_fail(width > 4 && height > 4);
1883 if (collection->item_width == width &&
1884 collection->item_height == height)
1885 return;
1887 widget = GTK_WIDGET(collection);
1889 collection->item_width = width;
1890 collection->item_height = height;
1892 if (GTK_WIDGET_REALIZED(widget))
1894 int window_width;
1896 collection->paint_level = PAINT_CLEAR;
1897 gdk_window_get_size(widget->window, &window_width, NULL);
1898 collection->columns = MAX(window_width / collection->item_width,
1901 set_vadjustment(collection);
1902 if (collection->cursor_item != -1)
1903 scroll_to_show(collection, collection->cursor_item);
1904 gtk_widget_queue_draw(widget);
1908 /* Cursor is positioned on item with the same data as before the sort.
1909 * Same for the wink item.
1911 void collection_qsort(Collection *collection,
1912 int (*compar)(const void *, const void *))
1914 int cursor, wink, items;
1915 gpointer cursor_data = NULL;
1916 gpointer wink_data = NULL;
1918 g_return_if_fail(collection != NULL);
1919 g_return_if_fail(IS_COLLECTION(collection));
1920 g_return_if_fail(compar != NULL);
1922 items = collection->number_of_items;
1924 wink = collection->wink_item;
1925 if (wink >= 0 && wink < items)
1926 wink_data = collection->items[wink].data;
1927 else
1928 wink = -1;
1930 cursor = collection->cursor_item;
1931 if (cursor >= 0 && cursor < items)
1932 cursor_data = collection->items[cursor].data;
1933 else
1934 cursor = -1;
1936 if (collection->wink_item != -1)
1938 collection->wink_item = -1;
1939 gtk_timeout_remove(collection->wink_timeout);
1942 qsort(collection->items, items, sizeof(collection->items[0]), compar);
1944 if (cursor > -1 || wink > -1)
1946 int item;
1948 for (item = 0; item < items; item++)
1950 if (collection->items[item].data == cursor_data)
1951 collection_set_cursor_item(collection, item);
1952 if (collection->items[item].data == wink_data)
1953 collection_wink_item(collection, item);
1957 collection->paint_level = PAINT_CLEAR;
1959 gtk_widget_queue_draw(GTK_WIDGET(collection));
1962 /* Find an item in an unsorted collection.
1963 * Returns the item number, or -1 if not found.
1965 int collection_find_item(Collection *collection, gpointer data,
1966 int (*compar)(const void *, const void *))
1968 int i;
1970 g_return_val_if_fail(collection != NULL, -1);
1971 g_return_val_if_fail(IS_COLLECTION(collection), -1);
1972 g_return_val_if_fail(compar != NULL, -1);
1974 for (i = 0; i < collection->number_of_items; i++)
1975 if (compar(&collection->items[i].data, &data) == 0)
1976 return i;
1978 return -1;
1981 /* The collection may be in either normal mode or panel mode.
1982 * In panel mode:
1983 * - a single click calls open_item
1984 * - items are never selected
1985 * - lasso boxes are disabled
1987 void collection_set_panel(Collection *collection, gboolean panel)
1989 g_return_if_fail(collection != NULL);
1990 g_return_if_fail(IS_COLLECTION(collection));
1992 collection->panel = panel == TRUE;
1994 if (collection->panel)
1996 collection_clear_selection(collection);
1997 if (collection->lasso_box)
1998 remove_lasso_box(collection);
2002 /* Return the number of the item under the point (x,y), or -1 for none.
2003 * This may call your test_point callback. The point is relative to the
2004 * collection's origin.
2006 int collection_get_item(Collection *collection, int x, int y)
2008 int scroll;
2009 int row, col;
2010 int item;
2012 g_return_val_if_fail(collection != NULL, -1);
2014 scroll = collection->vadj->value;
2015 col = x / collection->item_width;
2016 row = (y + scroll) / collection->item_height;
2018 if (col < 0 || row < 0 || col >= collection->columns)
2019 return -1;
2021 item = col + row * collection->columns;
2022 if (item >= collection->number_of_items
2024 !collection->test_point(collection,
2025 x - col * collection->item_width,
2026 y - row * collection->item_height
2027 + scroll,
2028 &collection->items[item],
2029 collection->item_width,
2030 collection->item_height))
2032 return -1;
2035 return item;
2038 /* Set the cursor/highlight over the given item. Passing -1
2039 * hides the cursor. As a special case, you may set the cursor item
2040 * to zero when there are no items.
2042 void collection_set_cursor_item(Collection *collection, gint item)
2044 int old_item;
2046 g_return_if_fail(collection != NULL);
2047 g_return_if_fail(IS_COLLECTION(collection));
2048 g_return_if_fail(item >= -1 &&
2049 (item < collection->number_of_items || item == 0));
2051 old_item = collection->cursor_item;
2053 if (old_item == item)
2054 return;
2056 collection->cursor_item = item;
2058 if (old_item != -1)
2059 collection_draw_item(collection, old_item, TRUE);
2061 if (item != -1)
2063 collection_draw_item(collection, item, TRUE);
2064 scroll_to_show(collection, item);
2066 else if (old_item != -1)
2067 collection->cursor_item_old = old_item;
2070 /* Briefly highlight an item to draw the user's attention to it.
2071 * -1 cancels the effect, as does deleting items, sorting the collection
2072 * or starting a new wink effect.
2073 * Otherwise, the effect will cancel itself after a short pause.
2074 * */
2075 void collection_wink_item(Collection *collection, gint item)
2077 g_return_if_fail(collection != NULL);
2078 g_return_if_fail(IS_COLLECTION(collection));
2079 g_return_if_fail(item >= -1 && item < collection->number_of_items);
2081 if (collection->wink_item != -1)
2082 cancel_wink(collection);
2083 if (item == -1)
2084 return;
2086 collection->cursor_item_old = collection->wink_item = item;
2087 collection->wink_timeout = gtk_timeout_add(300,
2088 (GtkFunction) cancel_wink_timeout,
2089 collection);
2090 collection_draw_item(collection, item, TRUE);
2091 scroll_to_show(collection, item);
2093 gdk_flush();
2096 /* Call test(item, data) on each item in the collection.
2097 * Remove all items for which it returns TRUE. test() should
2098 * free the data before returning TRUE. The collection is in an
2099 * inconsistant state during this call (ie, when test() is called).
2101 void collection_delete_if(Collection *collection,
2102 gboolean (*test)(gpointer item, gpointer data),
2103 gpointer data)
2105 int in, out = 0;
2106 int selected = 0;
2107 int cursor;
2109 g_return_if_fail(collection != NULL);
2110 g_return_if_fail(IS_COLLECTION(collection));
2111 g_return_if_fail(test != NULL);
2113 cursor = collection->cursor_item;
2115 for (in = 0; in < collection->number_of_items; in++)
2117 if (!test(collection->items[in].data, data))
2119 if (collection->items[in].selected)
2121 collection->items[out].selected = TRUE;
2122 selected++;
2124 else
2125 collection->items[out].selected = FALSE;
2127 collection->items[out++].data =
2128 collection->items[in].data;
2130 else if (cursor >= in)
2131 cursor--;
2134 if (in != out)
2136 collection->cursor_item = cursor;
2138 if (collection->wink_item != -1)
2140 collection->wink_item = -1;
2141 gtk_timeout_remove(collection->wink_timeout);
2144 collection->number_of_items = out;
2145 collection->number_selected = selected;
2146 resize_arrays(collection,
2147 MAX(collection->number_of_items, MINIMUM_ITEMS));
2149 collection->paint_level = PAINT_CLEAR;
2151 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection)))
2153 set_vadjustment(collection);
2154 gtk_widget_queue_draw(GTK_WIDGET(collection));
2159 /* Display a cross-hair pointer and the next time an item is clicked,
2160 * call the callback function. If the background is clicked or NULL
2161 * is passed as the callback then revert to normal operation.
2163 void collection_target(Collection *collection,
2164 CollectionTargetFunc callback,
2165 gpointer user_data)
2167 g_return_if_fail(collection != NULL);
2168 g_return_if_fail(IS_COLLECTION(collection));
2170 if (callback != collection->target_cb)
2171 gdk_window_set_cursor(GTK_WIDGET(collection)->window,
2172 callback ? crosshair : NULL);
2174 collection->target_cb = callback;
2175 collection->target_data = user_data;
2177 if (collection->cursor_item != -1)
2178 collection_draw_item(collection, collection->cursor_item,
2179 FALSE);
2182 /* Move the cursor by the given row and column offsets.
2183 * Moving by (0,0) can be used to simply make the cursor appear.
2185 void collection_move_cursor(Collection *collection, int drow, int dcol)
2187 int row, col, item;
2188 int first, last, total_rows;
2190 g_return_if_fail(collection != NULL);
2191 g_return_if_fail(IS_COLLECTION(collection));
2193 get_visible_limits(collection, &first, &last);
2195 item = collection->cursor_item;
2196 if (item == -1)
2198 item = MIN(collection->cursor_item_old,
2199 collection->number_of_items - 1);
2202 if (item == -1)
2204 col = 0;
2205 row = first;
2207 else
2209 row = item / collection->columns;
2210 col = item % collection->columns + dcol;
2212 if (row < first)
2213 row = first;
2214 else if (row > last)
2215 row = last;
2216 else
2217 row = MAX(row + drow, 0);
2220 total_rows = (collection->number_of_items + collection->columns - 1)
2221 / collection->columns;
2223 if (row >= total_rows - 1 && drow > 0)
2225 row = total_rows - 1;
2226 item = col + row * collection->columns;
2227 if (item >= collection->number_of_items)
2229 row--;
2230 scroll_to_show(collection, item);
2233 if (row < 0)
2234 row = 0;
2236 item = col + row * collection->columns;
2238 if (item >= 0 && item < collection->number_of_items)
2239 collection_set_cursor_item(collection, item);