r898: Applied Bernard Jungen's latest patch:
[rox-filer.git] / ROX-Filer / src / collection.c
blob6423058bc696379aa2c20495aad7a39d0ef6fb22
1 /*
2 * $Id$
4 * Collection - a GTK+ widget
5 * Copyright (C) 2001, the ROX-Filer team.
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 "config.h"
29 #include <stdlib.h>
31 #include <gtk/gtk.h>
32 #include <gdk/gdkkeysyms.h>
33 #include "collection.h"
35 #define MIN_WIDTH 80
36 #define MIN_HEIGHT 60
37 #define MINIMUM_ITEMS 16
38 #define AUTOSCROLL_STEP 20
40 /* Macro to emit the "selection_changed" signal only if allowed */
41 #define EMIT_SELECTION_CHANGED(collection, time) \
42 if (!collection->block_selection_changed) \
43 gtk_signal_emit(GTK_OBJECT(collection), \
44 collection_signals[SELECTION_CHANGED], time)
46 enum
48 ARG_0,
49 ARG_VADJUSTMENT
52 /* Signals:
54 * void gain_selection(collection, time, user_data)
55 * We've gone from no selected items to having a selection.
56 * Time is the time of the event that caused the change, or
57 * GDK_CURRENT_TIME if not known.
59 * void lose_selection(collection, time, user_data)
60 * We've dropped to having no selected items.
61 * Time is the time of the event that caused the change, or
62 * GDK_CURRENT_TIME if not known.
64 * void selection_changed(collection, user_data)
65 * The set of selected items has changed.
66 * Time is the time of the event that caused the change, or
67 * GDK_CURRENT_TIME if not known.
69 enum
71 GAIN_SELECTION,
72 LOSE_SELECTION,
73 SELECTION_CHANGED,
74 LAST_SIGNAL
77 static guint collection_signals[LAST_SIGNAL] = { 0 };
79 static guint32 current_event_time = GDK_CURRENT_TIME;
81 static GtkWidgetClass *parent_class = NULL;
83 /* Static prototypes */
84 static void clear_area(Collection *collection, GdkRectangle *area);
85 static void draw_one_item(Collection *collection,
86 int item,
87 GdkRectangle *area);
88 static void collection_class_init(CollectionClass *class);
89 static void collection_init(Collection *object);
90 static void collection_destroy(GtkObject *object);
91 static void collection_finalize(GtkObject *object);
92 static void collection_realize(GtkWidget *widget);
93 static gint collection_paint(Collection *collection,
94 GdkRectangle *area);
95 static void collection_size_request(GtkWidget *widget,
96 GtkRequisition *requisition);
97 static void collection_size_allocate(GtkWidget *widget,
98 GtkAllocation *allocation);
99 static void collection_set_style(GtkWidget *widget,
100 GtkStyle *previous_style);
101 static void collection_set_adjustment(Collection *collection,
102 GtkAdjustment *vadj);
103 static void collection_set_arg( GtkObject *object,
104 GtkArg *arg,
105 guint arg_id);
106 static void collection_get_arg( GtkObject *object,
107 GtkArg *arg,
108 guint arg_id);
109 static void collection_adjustment(GtkAdjustment *adjustment,
110 Collection *collection);
111 #ifndef GTK2
112 static void collection_disconnect(GtkAdjustment *adjustment,
113 Collection *collection);
114 #endif
115 static void set_vadjustment(Collection *collection);
116 static gint collection_expose(GtkWidget *widget, GdkEventExpose *event);
117 static void scroll_by(Collection *collection, gint diff);
118 static gint collection_button_press(GtkWidget *widget,
119 GdkEventButton *event);
120 static void default_draw_item(GtkWidget *widget,
121 CollectionItem *data,
122 GdkRectangle *area,
123 gpointer user_data);
124 static gboolean default_test_point(Collection *collection,
125 int point_x, int point_y,
126 CollectionItem *data,
127 int width, int height,
128 gpointer user_data);
129 static gint collection_motion_notify(GtkWidget *widget,
130 GdkEventMotion *event);
131 static void add_lasso_box(Collection *collection);
132 static void abort_lasso(Collection *collection);
133 static void remove_lasso_box(Collection *collection);
134 static void draw_lasso_box(Collection *collection);
135 static void cancel_wink(Collection *collection);
136 static gint collection_key_press(GtkWidget *widget, GdkEventKey *event);
137 static void get_visible_limits(Collection *collection, int *first, int *last);
138 static void scroll_to_show(Collection *collection, int item);
139 #ifndef GTK2
140 static void draw_focus(GtkWidget *widget);
141 static void collection_draw(GtkWidget *widget, GdkRectangle *area);
142 static gint focus_in(GtkWidget *widget, GdkEventFocus *event);
143 static gint focus_out(GtkWidget *widget, GdkEventFocus *event);
144 #endif
146 static void draw_focus_at(Collection *collection, GdkRectangle *area)
148 GtkWidget *widget;
149 GdkGC *gc;
151 widget = GTK_WIDGET(collection);
153 if (GTK_WIDGET_FLAGS(widget) & GTK_HAS_FOCUS)
154 gc = widget->style->fg_gc[GTK_STATE_ACTIVE];
155 else
156 gc = widget->style->fg_gc[GTK_STATE_INSENSITIVE];
158 gdk_draw_rectangle(widget->window, gc, FALSE,
159 area->x + 1, area->y + 1,
160 collection->item_width - 3,
161 area->height - 3);
164 static void draw_one_item(Collection *collection, int item, GdkRectangle *area)
166 if (item < collection->number_of_items)
168 collection->draw_item((GtkWidget *) collection,
169 &collection->items[item],
170 area,
171 collection->cb_user_data);
174 if (item == collection->cursor_item)
175 draw_focus_at(collection, area);
178 #ifndef GTK2
179 static void draw_focus(GtkWidget *widget)
181 Collection *collection;
183 g_return_if_fail(widget != NULL);
184 g_return_if_fail(IS_COLLECTION(widget));
186 collection = COLLECTION(widget);
188 if (collection->cursor_item < 0 || !GTK_WIDGET_REALIZED(widget))
189 return;
191 collection_draw_item(collection, collection->cursor_item, FALSE);
193 #endif
195 GtkType collection_get_type(void)
197 static guint my_type = 0;
199 if (!my_type)
201 static const GtkTypeInfo my_info =
203 "Collection",
204 sizeof(Collection),
205 sizeof(CollectionClass),
206 (GtkClassInitFunc) collection_class_init,
207 (GtkObjectInitFunc) collection_init,
208 NULL, /* Reserved 1 */
209 NULL, /* Reserved 2 */
210 (GtkClassInitFunc) NULL /* base_class_init_func */
213 my_type = gtk_type_unique(gtk_widget_get_type(),
214 &my_info);
217 return my_type;
220 #ifdef GTK2
221 typedef void (*FinalizeFn)(GObject *object);
222 #endif
224 static void collection_class_init(CollectionClass *class)
226 GtkObjectClass *object_class;
227 GtkWidgetClass *widget_class;
228 GtkType type;
230 object_class = (GtkObjectClass*) class;
231 widget_class = (GtkWidgetClass*) class;
233 parent_class = gtk_type_class(gtk_widget_get_type());
235 gtk_object_add_arg_type("Collection::vadjustment",
236 GTK_TYPE_ADJUSTMENT,
237 GTK_ARG_READWRITE | GTK_ARG_CONSTRUCT,
238 ARG_VADJUSTMENT);
240 object_class->destroy = collection_destroy;
241 #ifdef GTK2
242 G_OBJECT_CLASS(object_class)->finalize =
243 (FinalizeFn) collection_finalize;
244 type = GTK_CLASS_TYPE(object_class);
245 #else
246 object_class->finalize = collection_finalize;
247 type = object_class->type;
248 #endif
250 widget_class->realize = collection_realize;
251 widget_class->expose_event = collection_expose;
252 widget_class->size_request = collection_size_request;
253 widget_class->size_allocate = collection_size_allocate;
254 widget_class->style_set = collection_set_style;
256 widget_class->key_press_event = collection_key_press;
257 widget_class->button_press_event = collection_button_press;
258 widget_class->motion_notify_event = collection_motion_notify;
259 #ifndef GTK2
260 widget_class->focus_in_event = focus_in;
261 widget_class->focus_out_event = focus_out;
262 widget_class->draw = collection_draw;
263 widget_class->draw_focus = draw_focus;
264 #endif
266 object_class->set_arg = collection_set_arg;
267 object_class->get_arg = collection_get_arg;
269 class->gain_selection = NULL;
270 class->lose_selection = NULL;
271 class->selection_changed = NULL;
273 collection_signals[GAIN_SELECTION] = gtk_signal_new("gain_selection",
274 GTK_RUN_LAST,
275 type,
276 GTK_SIGNAL_OFFSET(CollectionClass,
277 gain_selection),
278 gtk_marshal_NONE__INT,
279 GTK_TYPE_NONE, 1,
280 GTK_TYPE_INT);
281 collection_signals[LOSE_SELECTION] = gtk_signal_new("lose_selection",
282 GTK_RUN_LAST,
283 type,
284 GTK_SIGNAL_OFFSET(CollectionClass,
285 lose_selection),
286 gtk_marshal_NONE__INT,
287 GTK_TYPE_NONE, 1,
288 GTK_TYPE_INT);
289 collection_signals[SELECTION_CHANGED] = gtk_signal_new(
290 "selection_changed",
291 GTK_RUN_LAST,
292 type,
293 GTK_SIGNAL_OFFSET(CollectionClass,
294 selection_changed),
295 gtk_marshal_NONE__INT,
296 GTK_TYPE_NONE, 1,
297 GTK_TYPE_INT);
299 #ifndef GTK2
300 gtk_object_class_add_signals(object_class,
301 collection_signals, LAST_SIGNAL);
302 #endif
305 static void collection_init(Collection *object)
307 g_return_if_fail(object != NULL);
308 g_return_if_fail(IS_COLLECTION(object));
310 GTK_WIDGET_SET_FLAGS(GTK_WIDGET(object), GTK_CAN_FOCUS);
312 object->number_of_items = 0;
313 object->number_selected = 0;
314 object->block_selection_changed = 0;
315 object->columns = 1;
316 object->item_width = 64;
317 object->item_height = 64;
318 object->vadj = NULL;
319 object->paint_level = PAINT_OVERWRITE;
320 object->last_scroll = 0;
321 object->bg_gc = NULL;
323 object->items = g_malloc(sizeof(CollectionItem) * MINIMUM_ITEMS);
324 object->cursor_item = -1;
325 object->cursor_item_old = -1;
326 object->wink_item = -1;
327 object->array_size = MINIMUM_ITEMS;
328 object->draw_item = default_draw_item;
329 object->test_point = default_test_point;
331 object->auto_scroll = -1;
333 return;
336 GtkWidget* collection_new(GtkAdjustment *vadj)
338 if (vadj)
339 g_return_val_if_fail(GTK_IS_ADJUSTMENT(vadj), NULL);
341 return GTK_WIDGET(gtk_widget_new(collection_get_type(),
342 "vadjustment", vadj,
343 NULL));
346 /* Note: The draw_item call gives the maximum area that can be
347 * drawn to. For the column on the far right, this extends to the
348 * edge of the window. Normally, use collection->item_width instead
349 * of area->width to calculate the position.
351 * test_point does not use a larger value for the width, but the
352 * x point of the click may be larger than the width.
354 void collection_set_functions(Collection *collection,
355 CollectionDrawFunc draw_item,
356 CollectionTestFunc test_point,
357 gpointer user_data)
359 GtkWidget *widget;
361 g_return_if_fail(collection != NULL);
362 g_return_if_fail(IS_COLLECTION(collection));
364 widget = GTK_WIDGET(collection);
366 if (!draw_item)
367 draw_item = default_draw_item;
368 if (!test_point)
369 test_point = default_test_point;
371 collection->draw_item = draw_item;
372 collection->test_point = test_point;
373 collection->cb_user_data = user_data;
375 if (GTK_WIDGET_REALIZED(widget))
377 collection->paint_level = PAINT_CLEAR;
378 gtk_widget_queue_clear(widget);
382 /* After this we are unusable, but our data (if any) is still hanging around.
383 * It will be freed later with finalize.
385 static void collection_destroy(GtkObject *object)
387 Collection *collection;
389 g_return_if_fail(object != NULL);
390 g_return_if_fail(IS_COLLECTION(object));
392 collection = COLLECTION(object);
394 if (collection->wink_item != -1)
396 collection->wink_item = -1;
397 gtk_timeout_remove(collection->wink_timeout);
400 if (collection->auto_scroll != -1)
402 gtk_timeout_remove(collection->auto_scroll);
403 collection->auto_scroll = -1;
406 if (collection->bg_gc)
408 gdk_gc_destroy(collection->bg_gc);
409 collection->bg_gc = NULL;
412 if (collection->vadj)
414 gtk_signal_disconnect_by_data(GTK_OBJECT(collection->vadj),
415 collection);
416 gtk_object_unref(GTK_OBJECT(collection->vadj));
417 collection->vadj = NULL;
420 if (GTK_OBJECT_CLASS(parent_class)->destroy)
421 (*GTK_OBJECT_CLASS(parent_class)->destroy)(object);
424 /* This is the last thing that happens to us. Free all data. */
425 static void collection_finalize(GtkObject *object)
427 Collection *collection;
429 collection = COLLECTION(object);
431 g_free(collection->items);
434 static GdkGC *create_bg_gc(GtkWidget *widget)
436 GdkGCValues values;
438 values.tile = widget->style->bg_pixmap[GTK_STATE_NORMAL];
439 values.fill = GDK_TILED;
441 return gdk_gc_new_with_values(widget->window, &values,
442 GDK_GC_FILL | GDK_GC_TILE);
445 static void collection_realize(GtkWidget *widget)
447 Collection *collection;
448 GdkWindowAttr attributes;
449 gint attributes_mask;
450 GdkGCValues xor_values;
451 GdkColor *bg, *fg;
453 g_return_if_fail(widget != NULL);
454 g_return_if_fail(IS_COLLECTION(widget));
455 g_return_if_fail(widget->parent != NULL);
457 GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);
458 collection = COLLECTION(widget);
460 attributes.x = widget->allocation.x;
461 attributes.y = widget->allocation.y;
462 attributes.width = widget->allocation.width;
463 attributes.height = widget->allocation.height;
464 attributes.wclass = GDK_INPUT_OUTPUT;
465 attributes.window_type = GDK_WINDOW_CHILD;
466 attributes.event_mask = gtk_widget_get_events(widget) |
467 GDK_EXPOSURE_MASK |
468 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
469 GDK_BUTTON1_MOTION_MASK | GDK_BUTTON2_MOTION_MASK |
470 GDK_BUTTON3_MOTION_MASK;
471 attributes.visual = gtk_widget_get_visual(widget);
472 attributes.colormap = gtk_widget_get_colormap(widget);
474 attributes_mask = GDK_WA_X | GDK_WA_Y |
475 GDK_WA_VISUAL | GDK_WA_COLORMAP;
476 widget->window = gdk_window_new(widget->parent->window,
477 &attributes, attributes_mask);
479 widget->style = gtk_style_attach(widget->style, widget->window);
481 gdk_window_set_user_data(widget->window, widget);
483 gdk_window_set_background(widget->window,
484 &widget->style->bg[GTK_STATE_NORMAL]);
485 if (widget->style->bg_pixmap[GTK_STATE_NORMAL])
486 collection->bg_gc = create_bg_gc(widget);
488 /* Try to stop everything flickering horribly */
489 gdk_window_set_static_gravities(widget->window, TRUE);
491 set_vadjustment(collection);
493 bg = &widget->style->bg[GTK_STATE_NORMAL];
494 fg = &widget->style->fg[GTK_STATE_NORMAL];
495 xor_values.function = GDK_XOR;
496 xor_values.foreground.pixel = fg->pixel ^ bg->pixel;
497 collection->xor_gc = gdk_gc_new_with_values(widget->window,
498 &xor_values,
499 GDK_GC_FOREGROUND
500 | GDK_GC_FUNCTION);
503 static void collection_size_request(GtkWidget *widget,
504 GtkRequisition *requisition)
506 requisition->width = MIN_WIDTH;
507 requisition->height = MIN_HEIGHT;
510 static void collection_size_allocate(GtkWidget *widget,
511 GtkAllocation *allocation)
513 Collection *collection;
514 int old_columns;
516 g_return_if_fail(widget != NULL);
517 g_return_if_fail(IS_COLLECTION(widget));
518 g_return_if_fail(allocation != NULL);
520 collection = COLLECTION(widget);
522 old_columns = collection->columns;
523 if (widget->allocation.x != allocation->x
524 || widget->allocation.y != allocation->y)
525 collection->paint_level = PAINT_CLEAR;
527 widget->allocation = *allocation;
529 collection->columns = allocation->width / collection->item_width;
530 if (collection->columns < 1)
531 collection->columns = 1;
533 if (GTK_WIDGET_REALIZED(widget))
535 gdk_window_move_resize(widget->window,
536 allocation->x, allocation->y,
537 allocation->width, allocation->height);
539 /* Force a redraw if the number of columns has changed
540 * or we have a background pixmap (!).
542 if (old_columns != collection->columns || collection->bg_gc)
544 collection->paint_level = PAINT_CLEAR;
545 gtk_widget_queue_clear(widget);
548 set_vadjustment(collection);
550 if (collection->cursor_item != -1)
551 scroll_to_show(collection, collection->cursor_item);
555 static void collection_set_style(GtkWidget *widget,
556 GtkStyle *previous_style)
558 Collection *collection;
560 g_return_if_fail(IS_COLLECTION(widget));
562 collection = COLLECTION(widget);
564 collection->paint_level = PAINT_CLEAR;
566 if (GTK_WIDGET_REALIZED(widget))
568 gdk_window_set_background(widget->window,
569 &widget->style->bg[GTK_STATE_NORMAL]);
571 if (collection->bg_gc)
573 gdk_gc_destroy(collection->bg_gc);
574 collection->bg_gc = NULL;
577 if (widget->style->bg_pixmap[GTK_STATE_NORMAL])
578 collection->bg_gc = create_bg_gc(widget);
582 static void clear_area(Collection *collection, GdkRectangle *area)
584 GtkWidget *widget = GTK_WIDGET(collection);
585 int scroll = collection->vadj->value;
587 if (collection->bg_gc)
589 gdk_gc_set_ts_origin(collection->bg_gc, 0, -scroll);
591 gdk_draw_rectangle(widget->window,
592 collection->bg_gc,
593 TRUE,
594 area->x, area->y,
595 area->width, area->height);
597 else
598 gdk_window_clear_area(widget->window,
599 area->x, area->y, area->width, area->height);
602 /* Return the area occupied by the item at (row, col) by filling
603 * in 'area'.
605 static void collection_get_item_area(Collection *collection,
606 int row, int col,
607 GdkRectangle *area)
610 int scroll = collection->vadj->value; /* (round to int) */
612 area->x = col * collection->item_width;
613 area->y = row * collection->item_height - scroll;
615 area->width = collection->item_width;
616 area->height = collection->item_height;
617 if (col == collection->columns - 1)
618 area->width <<= 1;
621 static gint collection_paint(Collection *collection,
622 GdkRectangle *area)
624 GdkRectangle whole, item_area;
625 GtkWidget *widget;
626 int row, col;
627 int item;
628 int scroll;
629 int start_row, last_row;
630 int start_col, last_col;
631 int phys_last_col;
632 GdkRectangle clip;
633 gint width, height;
635 scroll = collection->vadj->value;
637 widget = GTK_WIDGET(collection);
639 gdk_window_get_size(widget->window, &width, &height);
640 whole.x = 0;
641 whole.y = 0;
642 whole.width = width;
643 whole.height = height;
645 if (collection->paint_level > PAINT_NORMAL || area == NULL)
647 area = &whole;
649 if (collection->paint_level == PAINT_CLEAR
650 && !collection->lasso_box)
651 clear_area(collection, area);
653 collection->paint_level = PAINT_NORMAL;
656 /* Calculate the ranges to plot */
657 start_row = (area->y + scroll) / collection->item_height;
658 last_row = (area->y + area->height - 1 + scroll)
659 / collection->item_height;
660 row = start_row;
662 start_col = area->x / collection->item_width;
663 phys_last_col = (area->x + area->width - 1) / collection->item_width;
665 if (collection->lasso_box)
667 /* You can't be too careful with lasso boxes...
669 * clip gives the total area drawn over (this may be larger
670 * than the requested area). It's used to redraw the lasso
671 * box.
673 collection_get_item_area(collection,
674 start_row, start_col, &clip);
675 clip.width *= phys_last_col - start_col + 1;
676 clip.height *= last_row - start_row + 1;
678 clear_area(collection, &clip);
681 /* The right-most column may be wider than the others.
682 * Therefore, to redraw the area after the last 'real' column
683 * we may have to draw the right-most column.
685 if (start_col >= collection->columns)
686 start_col = collection->columns - 1;
688 if (phys_last_col >= collection->columns)
689 last_col = collection->columns - 1;
690 else
691 last_col = phys_last_col;
693 col = start_col;
695 item = row * collection->columns + col;
697 while ((item == 0 || item < collection->number_of_items)
698 && row <= last_row)
700 collection_get_item_area(collection, row, col, &item_area);
702 draw_one_item(collection, item, &item_area);
703 col++;
705 if (col > last_col)
707 col = start_col;
708 row++;
709 item = row * collection->columns + col;
711 else
712 item++;
715 if (collection->lasso_box)
717 gdk_gc_set_clip_rectangle(collection->xor_gc, &clip);
718 draw_lasso_box(collection);
719 gdk_gc_set_clip_rectangle(collection->xor_gc, NULL);
722 return FALSE;
725 static void default_draw_item( GtkWidget *widget,
726 CollectionItem *item,
727 GdkRectangle *area,
728 gpointer user_data)
730 gdk_draw_arc(widget->window,
731 item->selected ? widget->style->white_gc
732 : widget->style->black_gc,
733 TRUE,
734 area->x, area->y,
735 COLLECTION(widget)->item_width, area->height,
736 0, 360 * 64);
740 static gboolean default_test_point(Collection *collection,
741 int point_x, int point_y,
742 CollectionItem *item,
743 int width, int height,
744 gpointer user_data)
746 float f_x, f_y;
748 /* Convert to point in unit circle */
749 f_x = ((float) point_x / width) - 0.5;
750 f_y = ((float) point_y / height) - 0.5;
752 return (f_x * f_x) + (f_y * f_y) <= .25;
755 static void collection_set_arg( GtkObject *object,
756 GtkArg *arg,
757 guint arg_id)
759 Collection *collection;
761 collection = COLLECTION(object);
763 switch (arg_id)
765 case ARG_VADJUSTMENT:
766 collection_set_adjustment(collection,
767 GTK_VALUE_POINTER(*arg));
768 break;
769 default:
770 break;
774 static void collection_set_adjustment( Collection *collection,
775 GtkAdjustment *vadj)
777 if (vadj)
778 g_return_if_fail (GTK_IS_ADJUSTMENT (vadj));
779 else
780 vadj = GTK_ADJUSTMENT(gtk_adjustment_new(0.0,
781 0.0, 0.0,
782 0.0, 0.0, 0.0));
783 if (collection->vadj && (collection->vadj != vadj))
785 gtk_signal_disconnect_by_data(GTK_OBJECT(collection->vadj),
786 collection);
787 gtk_object_unref(GTK_OBJECT(collection->vadj));
790 if (collection->vadj != vadj)
792 collection->vadj = vadj;
793 gtk_object_ref(GTK_OBJECT(collection->vadj));
794 gtk_object_sink(GTK_OBJECT(collection->vadj));
796 gtk_signal_connect(GTK_OBJECT(collection->vadj),
797 "changed",
798 (GtkSignalFunc) collection_adjustment,
799 collection);
800 gtk_signal_connect(GTK_OBJECT(collection->vadj),
801 "value_changed",
802 (GtkSignalFunc) collection_adjustment,
803 collection);
804 #ifndef GTK2
805 /* Is this used for anything? */
806 gtk_signal_connect(GTK_OBJECT(collection->vadj),
807 "disconnect",
808 (GtkSignalFunc) collection_disconnect,
809 collection);
810 #endif
811 collection_adjustment(vadj, collection);
815 static void collection_get_arg( GtkObject *object,
816 GtkArg *arg,
817 guint arg_id)
819 Collection *collection;
821 collection = COLLECTION(object);
823 switch (arg_id)
825 case ARG_VADJUSTMENT:
826 GTK_VALUE_POINTER(*arg) = collection->vadj;
827 break;
828 default:
829 arg->type = GTK_TYPE_INVALID;
830 break;
834 /* Something about the adjustment has changed */
835 static void collection_adjustment(GtkAdjustment *adjustment,
836 Collection *collection)
838 gint diff;
840 g_return_if_fail(adjustment != NULL);
841 g_return_if_fail(GTK_IS_ADJUSTMENT(adjustment));
842 g_return_if_fail(collection != NULL);
843 g_return_if_fail(IS_COLLECTION(collection));
845 diff = ((gint) adjustment->value) - collection->last_scroll;
847 if (diff)
849 collection->last_scroll = adjustment->value;
851 if (collection->lasso_box)
853 remove_lasso_box(collection);
854 collection->drag_box_y[0] -= diff;
855 scroll_by(collection, diff);
856 add_lasso_box(collection);
858 else
859 scroll_by(collection, diff);
863 #ifndef GTK2
864 static void collection_disconnect(GtkAdjustment *adjustment,
865 Collection *collection)
867 g_return_if_fail(adjustment != NULL);
868 g_return_if_fail(GTK_IS_ADJUSTMENT(adjustment));
869 g_return_if_fail(collection != NULL);
870 g_return_if_fail(IS_COLLECTION(collection));
872 collection_set_adjustment(collection, NULL);
874 #endif
876 static void set_vadjustment(Collection *collection)
878 GtkWidget *widget;
879 gint height;
880 int cols, rows;
882 widget = GTK_WIDGET(collection);
884 if (!GTK_WIDGET_REALIZED(widget))
885 return;
887 gdk_window_get_size(widget->window, NULL, &height);
888 cols = collection->columns;
889 rows = (collection->number_of_items + cols - 1) / cols;
891 collection->vadj->lower = 0.0;
892 collection->vadj->upper = collection->item_height * rows;
893 if (!collection->vadj->upper)
894 collection->vadj->upper = 1;
896 collection->vadj->step_increment =
897 MIN(collection->vadj->upper, collection->item_height / 4);
899 collection->vadj->page_increment =
900 MIN(collection->vadj->upper,
901 height - 5.0);
903 collection->vadj->page_size = MIN(collection->vadj->upper, height);
905 collection->vadj->value = MIN(collection->vadj->value,
906 collection->vadj->upper - collection->vadj->page_size);
908 collection->vadj->value = MAX(collection->vadj->value, 0.0);
910 gtk_signal_emit_by_name(GTK_OBJECT(collection->vadj), "changed");
913 /* Change the adjustment by this amount. Bounded. */
914 static void diff_vpos(Collection *collection, int diff)
916 int value = collection->vadj->value + diff;
918 value = CLAMP(value, 0,
919 collection->vadj->upper - collection->vadj->page_size);
920 gtk_adjustment_set_value(collection->vadj, value);
923 #ifndef GTK2
924 static void collection_draw(GtkWidget *widget, GdkRectangle *area)
926 Collection *collection;
928 g_return_if_fail(widget != NULL);
929 g_return_if_fail(IS_COLLECTION(widget));
930 g_return_if_fail(area != NULL); /* Not actually used */
932 collection = COLLECTION(widget);
934 /* This doesn't always work - I think Gtk+ may be doing some
935 * kind of expose-event compression...
936 * 29/9/2000: Turned expose_events on in copy_area... maybe
937 * we can use this again? Try after 1.0.0!
938 if (collection->paint_level > PAINT_NORMAL)
940 collection_paint(collection, area);
942 #endif
944 static gint collection_expose(GtkWidget *widget, GdkEventExpose *event)
946 Collection *collection;
948 g_return_val_if_fail(widget != NULL, FALSE);
949 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
950 g_return_val_if_fail(event != NULL, FALSE);
952 collection = COLLECTION(widget);
954 clear_area(collection, &event->area);
956 collection_paint(collection, &event->area);
958 return FALSE;
961 /* Positive makes the contents go move up the screen */
962 static void scroll_by(Collection *collection, gint diff)
964 GtkWidget *widget;
965 gint width, height;
966 guint from_y, to_y;
967 guint amount;
968 GdkRectangle new_area;
970 if (diff == 0)
971 return;
973 widget = GTK_WIDGET(collection);
975 if (collection->lasso_box)
976 abort_lasso(collection);
978 gdk_window_get_size(widget->window, &width, &height);
979 new_area.x = 0;
980 new_area.width = width;
982 if (diff > 0)
984 amount = diff;
985 from_y = amount;
986 to_y = 0;
987 new_area.y = height - amount;
989 else
991 amount = -diff;
992 from_y = 0;
993 to_y = amount;
994 new_area.y = 0;
997 new_area.height = amount;
999 if (amount < height)
1001 static GdkGC *expo_gc = NULL;
1003 if (!expo_gc)
1005 expo_gc = gdk_gc_new(widget->window);
1006 gdk_gc_copy(expo_gc, widget->style->white_gc);
1007 gdk_gc_set_exposures(expo_gc, TRUE);
1010 gdk_draw_pixmap(widget->window,
1011 expo_gc,
1012 widget->window,
1014 from_y,
1016 to_y,
1017 width,
1018 height - amount);
1019 /* We have to redraw everything because any pending
1020 * expose events now contain invalid areas.
1021 * Don't need to clear the area first though...
1023 if (collection->paint_level < PAINT_OVERWRITE)
1024 collection->paint_level = PAINT_OVERWRITE;
1026 else
1027 collection->paint_level = PAINT_CLEAR;
1029 clear_area(collection, &new_area);
1030 collection_paint(collection, NULL);
1033 static void resize_arrays(Collection *collection, guint new_size)
1035 g_return_if_fail(collection != NULL);
1036 g_return_if_fail(IS_COLLECTION(collection));
1037 g_return_if_fail(new_size >= collection->number_of_items);
1039 collection->items = g_realloc(collection->items,
1040 sizeof(CollectionItem) * new_size);
1041 collection->array_size = new_size;
1044 static gint collection_key_press(GtkWidget *widget, GdkEventKey *event)
1046 Collection *collection;
1047 int item;
1048 int key;
1050 g_return_val_if_fail(widget != NULL, FALSE);
1051 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1052 g_return_val_if_fail(event != NULL, FALSE);
1054 collection = (Collection *) widget;
1055 item = collection->cursor_item;
1057 key = event->keyval;
1058 if (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK))
1060 if (key == GDK_Left || key == GDK_Right || \
1061 key == GDK_Up || key == GDK_Down)
1062 return TRUE;
1063 return FALSE;
1066 switch (key)
1068 case GDK_Left:
1069 collection_move_cursor(collection, 0, -1);
1070 break;
1071 case GDK_Right:
1072 collection_move_cursor(collection, 0, 1);
1073 break;
1074 case GDK_Up:
1075 collection_move_cursor(collection, -1, 0);
1076 break;
1077 case GDK_Down:
1078 collection_move_cursor(collection, 1, 0);
1079 break;
1080 case GDK_Home:
1081 collection_set_cursor_item(collection, 0);
1082 break;
1083 case GDK_End:
1084 collection_set_cursor_item(collection,
1085 MAX((gint) collection->number_of_items - 1, 0));
1086 break;
1087 case GDK_Page_Up:
1089 int first, last;
1090 get_visible_limits(collection, &first, &last);
1091 collection_move_cursor(collection, first - last - 1, 0);
1092 break;
1094 case GDK_Page_Down:
1096 int first, last;
1097 get_visible_limits(collection, &first, &last);
1098 collection_move_cursor(collection, last - first + 1, 0);
1099 break;
1101 case GDK_Escape:
1102 collection_set_cursor_item(collection, -1);
1103 collection_clear_selection(collection);
1104 return FALSE; /* Pass it on */
1105 case ' ':
1106 if (item >=0 && item < collection->number_of_items)
1108 collection_toggle_item(collection, item);
1109 if (item < collection->number_of_items - 1)
1110 collection_set_cursor_item(collection,
1111 item + 1);
1113 break;
1114 default:
1115 return FALSE;
1118 return TRUE;
1121 static gint collection_button_press(GtkWidget *widget,
1122 GdkEventButton *event)
1124 Collection *collection;
1125 int diff;
1127 g_return_val_if_fail(widget != NULL, FALSE);
1128 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1129 g_return_val_if_fail(event != NULL, FALSE);
1131 collection = COLLECTION(widget);
1133 if (event->button <= 3 || event->type != GDK_BUTTON_PRESS)
1134 return FALSE; /* Only deal with wheel events here */
1136 /* Wheel mouse scrolling */
1137 diff = 32;
1139 if (event->button == 4)
1140 diff = -diff;
1141 else if (event->button != 5)
1142 return FALSE;
1144 if (diff)
1146 int old_value = collection->vadj->value;
1147 int new_value = 0;
1148 gboolean box = collection->lasso_box;
1150 new_value = CLAMP(old_value + diff, 0.0,
1151 collection->vadj->upper
1152 - collection->vadj->page_size);
1153 diff = new_value - old_value;
1154 if (diff)
1156 if (box)
1158 remove_lasso_box(collection);
1159 collection->drag_box_y[0] -= diff;
1161 collection->vadj->value = new_value;
1162 gtk_signal_emit_by_name(
1163 GTK_OBJECT(collection->vadj),
1164 "changed");
1165 if (box)
1166 add_lasso_box(collection);
1170 return TRUE;
1173 /* 'from' and 'to' are pixel positions. 'step' is the size of each item.
1174 * Returns the index of the first item covered, and the number of items.
1176 #ifdef GTK2
1177 static void get_range(int from, int to, int step, gint *pos, gint *len)
1178 #else
1179 static void get_range(int from, int to, int step, short *pos, short *len)
1180 #endif
1182 if (from > to)
1184 int tmp = to;
1185 to = from;
1186 from = tmp;
1189 from = (from + step / 4) / step; /* First item */
1190 to = (to + step - step / 4) / step; /* Last item (inclusive) */
1192 *pos = MAX(from, 0);
1193 *len = to - *pos;
1196 /* Fills in the area with a rectangle corresponding to the current
1197 * size of the lasso box (units of items, not pixels).
1199 * The box will only span valid columns, but the total number
1200 * of items is not taken into account (rows or cols).
1202 static void find_lasso_area(Collection *collection, GdkRectangle *area)
1204 int scroll;
1205 int cols = collection->columns;
1206 int dx = collection->drag_box_x[0] - collection->drag_box_x[1];
1207 int dy = collection->drag_box_y[0] - collection->drag_box_y[1];
1209 if (ABS(dx) < 8 && ABS(dy) < 8)
1211 /* Didn't move far enough - ignore */
1212 area->x = area->y = 0;
1213 area->width = 0;
1214 area->height = 0;
1215 return;
1218 scroll = collection->vadj->value;
1220 get_range(collection->drag_box_x[0],
1221 collection->drag_box_x[1],
1222 collection->item_width,
1223 &area->x,
1224 &area->width);
1226 if (area->x >= cols)
1227 area->width = 0;
1228 else if (area->x + area->width > cols)
1229 area->width = cols - area->x;
1231 get_range(collection->drag_box_y[0] + scroll,
1232 collection->drag_box_y[1] + scroll,
1233 collection->item_height,
1234 &area->y,
1235 &area->height);
1238 static void collection_process_area(Collection *collection,
1239 GdkRectangle *area,
1240 GdkFunction fn,
1241 guint32 time)
1243 int x, y;
1244 guint32 stacked_time;
1245 int item;
1246 gboolean changed = FALSE;
1248 g_return_if_fail(fn == GDK_SET || fn == GDK_INVERT);
1250 stacked_time = current_event_time;
1251 current_event_time = time;
1253 collection->block_selection_changed++;
1255 for (y = area->y; y < area->y + area->height; y++)
1257 item = y * collection->columns + area->x;
1259 for (x = area->x; x < area->x + area->width; x++)
1261 if (item >= collection->number_of_items)
1262 goto out;
1264 if (fn == GDK_INVERT)
1265 collection_toggle_item(collection, item);
1266 else
1267 collection_select_item(collection, item);
1269 changed = TRUE;
1270 item++;
1274 out:
1275 collection_unblock_selection_changed(collection,
1276 current_event_time, changed);
1277 current_event_time = stacked_time;
1280 static gint collection_motion_notify(GtkWidget *widget,
1281 GdkEventMotion *event)
1283 Collection *collection;
1284 gint x, y;
1286 g_return_val_if_fail(widget != NULL, FALSE);
1287 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1288 g_return_val_if_fail(event != NULL, FALSE);
1290 collection = COLLECTION(widget);
1292 if (!collection->lasso_box)
1293 return FALSE;
1295 if (event->window != widget->window)
1296 gdk_window_get_pointer(widget->window, &x, &y, NULL);
1297 else
1299 x = event->x;
1300 y = event->y;
1303 remove_lasso_box(collection);
1304 collection->drag_box_x[1] = x;
1305 collection->drag_box_y[1] = y;
1306 add_lasso_box(collection);
1307 return TRUE;
1310 static void add_lasso_box(Collection *collection)
1312 g_return_if_fail(collection != NULL);
1313 g_return_if_fail(IS_COLLECTION(collection));
1314 g_return_if_fail(collection->lasso_box == FALSE);
1316 collection->lasso_box = TRUE;
1317 draw_lasso_box(collection);
1320 static void draw_lasso_box(Collection *collection)
1322 GtkWidget *widget;
1323 int x, y, width, height;
1325 widget = GTK_WIDGET(collection);
1327 x = MIN(collection->drag_box_x[0], collection->drag_box_x[1]);
1328 y = MIN(collection->drag_box_y[0], collection->drag_box_y[1]);
1329 width = abs(collection->drag_box_x[1] - collection->drag_box_x[0]);
1330 height = abs(collection->drag_box_y[1] - collection->drag_box_y[0]);
1332 gdk_draw_rectangle(widget->window, collection->xor_gc, FALSE,
1333 x, y, width, height);
1336 static void abort_lasso(Collection *collection)
1338 if (collection->lasso_box)
1340 remove_lasso_box(collection);
1341 collection_set_autoscroll(collection, FALSE);
1345 static void remove_lasso_box(Collection *collection)
1347 g_return_if_fail(collection != NULL);
1348 g_return_if_fail(IS_COLLECTION(collection));
1349 g_return_if_fail(collection->lasso_box == TRUE);
1351 draw_lasso_box(collection);
1353 collection->lasso_box = FALSE;
1355 return;
1358 /* Make sure that 'item' is fully visible (vertically), scrolling if not. */
1359 static void scroll_to_show(Collection *collection, int item)
1361 int first, last, row;
1363 g_return_if_fail(collection != NULL);
1364 g_return_if_fail(IS_COLLECTION(collection));
1366 row = item / collection->columns;
1367 get_visible_limits(collection, &first, &last);
1369 if (row <= first)
1371 gtk_adjustment_set_value(collection->vadj,
1372 row * collection->item_height);
1374 else if (row >= last)
1376 GtkWidget *widget = (GtkWidget *) collection;
1377 gint height;
1379 if (GTK_WIDGET_REALIZED(widget))
1381 gdk_window_get_size(widget->window, NULL, &height);
1382 gtk_adjustment_set_value(collection->vadj,
1383 (row + 1) * collection->item_height - height);
1388 /* Return the first and last rows which are [partly] visible. Does not
1389 * ensure that the rows actually exist (contain items).
1391 static void get_visible_limits(Collection *collection, int *first, int *last)
1393 GtkWidget *widget = (GtkWidget *) collection;
1394 gint scroll, height;
1396 g_return_if_fail(collection != NULL);
1397 g_return_if_fail(IS_COLLECTION(collection));
1398 g_return_if_fail(first != NULL && last != NULL);
1400 if (!GTK_WIDGET_REALIZED(widget))
1402 *first = 0;
1403 *last = 0;
1405 else
1407 scroll = collection->vadj->value;
1408 gdk_window_get_size(widget->window, NULL, &height);
1410 *first = MAX(scroll / collection->item_height, 0);
1411 *last = (scroll + height - 1) /collection->item_height;
1413 if (*last < *first)
1414 *last = *first;
1418 /* Cancel the current wink effect. */
1419 static void cancel_wink(Collection *collection)
1421 gint item;
1423 g_return_if_fail(collection != NULL);
1424 g_return_if_fail(IS_COLLECTION(collection));
1425 g_return_if_fail(collection->wink_item != -1);
1427 item = collection->wink_item;
1429 collection->wink_item = -1;
1430 gtk_timeout_remove(collection->wink_timeout);
1432 collection_draw_item(collection, item, TRUE);
1435 /* Draw/undraw a box around collection->wink_item */
1436 static void invert_wink(Collection *collection)
1438 GdkRectangle area;
1439 gint row, col;
1441 g_return_if_fail(collection->wink_item >= 0);
1443 if (!GTK_WIDGET_REALIZED(GTK_WIDGET(collection)))
1444 return;
1446 col = collection->wink_item % collection->columns;
1447 row = collection->wink_item / collection->columns;
1448 collection_get_item_area(collection, row, col, &area);
1450 gdk_draw_rectangle(((GtkWidget *) collection)->window,
1451 collection->xor_gc, FALSE,
1452 area.x, area.y,
1453 collection->item_width - 1,
1454 area.height - 1);
1457 static gboolean wink_timeout(Collection *collection)
1459 gint item;
1461 g_return_val_if_fail(collection != NULL, FALSE);
1462 g_return_val_if_fail(IS_COLLECTION(collection), FALSE);
1463 g_return_val_if_fail(collection->wink_item != -1, FALSE);
1465 item = collection->wink_item;
1467 if (collection->winks_left-- > 0)
1469 invert_wink(collection);
1470 return TRUE;
1473 collection->wink_item = -1;
1475 collection_draw_item(collection, item, TRUE);
1477 return FALSE;
1480 #ifndef GTK2
1481 static gint focus_in(GtkWidget *widget, GdkEventFocus *event)
1483 g_return_val_if_fail(widget != NULL, FALSE);
1484 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1485 g_return_val_if_fail(event != NULL, FALSE);
1487 GTK_WIDGET_SET_FLAGS(widget, GTK_HAS_FOCUS);
1488 gtk_widget_draw_focus(widget);
1490 return FALSE;
1493 static gint focus_out(GtkWidget *widget, GdkEventFocus *event)
1495 g_return_val_if_fail(widget != NULL, FALSE);
1496 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1497 g_return_val_if_fail(event != NULL, FALSE);
1499 GTK_WIDGET_UNSET_FLAGS(widget, GTK_HAS_FOCUS);
1500 gtk_widget_draw_focus(widget);
1502 return FALSE;
1504 #endif
1506 /* This is called frequently while auto_scroll is on.
1507 * Checks the pointer position and scrolls the window if it's
1508 * near the top or bottom.
1510 static gboolean as_timeout(Collection *collection)
1512 GdkWindow *window = GTK_WIDGET(collection)->window;
1513 gint x, y, w, h;
1514 GdkModifierType mask;
1515 int diff = 0;
1517 gdk_window_get_pointer(window, &x, &y, &mask);
1518 gdk_window_get_size(window, &w, &h);
1520 if ((x < 0 || x > w || y < 0 || y > h) && !collection->lasso_box)
1522 collection->auto_scroll = -1;
1523 return FALSE; /* Out of window - stop */
1526 if (y < AUTOSCROLL_STEP)
1527 diff = y - AUTOSCROLL_STEP;
1528 else if (y > h - AUTOSCROLL_STEP)
1529 diff = AUTOSCROLL_STEP + y - h;
1531 if (diff)
1532 diff_vpos(collection, diff);
1534 return TRUE;
1537 /* Change the selected state of an item */
1538 static void collection_item_set_selected(Collection *collection,
1539 gint item,
1540 gboolean selected)
1542 g_return_if_fail(collection != NULL);
1543 g_return_if_fail(IS_COLLECTION(collection));
1544 g_return_if_fail(item >= 0 && item < collection->number_of_items);
1546 if (collection->items[item].selected == selected)
1547 return;
1549 collection->items[item].selected = selected;
1550 collection_draw_item(collection, item, TRUE);
1552 if (selected)
1554 collection->number_selected++;
1555 if (collection->number_selected == 1)
1556 gtk_signal_emit(GTK_OBJECT(collection),
1557 collection_signals[GAIN_SELECTION],
1558 current_event_time);
1560 else
1562 collection->number_selected--;
1563 if (collection->number_selected == 0)
1564 gtk_signal_emit(GTK_OBJECT(collection),
1565 collection_signals[LOSE_SELECTION],
1566 current_event_time);
1569 EMIT_SELECTION_CHANGED(collection, current_event_time);
1572 /* Functions for managing collections */
1574 /* Remove all objects from the collection */
1575 void collection_clear(Collection *collection)
1577 int prev_selected;
1579 g_return_if_fail(IS_COLLECTION(collection));
1581 if (collection->number_of_items == 0)
1582 return;
1584 if (collection->wink_item != -1)
1586 collection->wink_item = -1;
1587 gtk_timeout_remove(collection->wink_timeout);
1590 collection_set_cursor_item(collection,
1591 collection->cursor_item == -1 ? -1: 0);
1592 collection->cursor_item_old = -1;
1593 prev_selected = collection->number_selected;
1594 collection->number_of_items = collection->number_selected = 0;
1596 resize_arrays(collection, MINIMUM_ITEMS);
1598 collection->paint_level = PAINT_CLEAR;
1600 gtk_widget_queue_clear(GTK_WIDGET(collection));
1602 if (prev_selected && collection->number_selected == 0)
1603 gtk_signal_emit(GTK_OBJECT(collection),
1604 collection_signals[LOSE_SELECTION],
1605 current_event_time);
1608 /* Inserts a new item at the end. The new item is unselected, and its
1609 * number is returned.
1611 gint collection_insert(Collection *collection, gpointer data)
1613 int item;
1615 g_return_val_if_fail(IS_COLLECTION(collection), -1);
1617 item = collection->number_of_items;
1619 if (item >= collection->array_size)
1620 resize_arrays(collection, item + (item >> 1));
1622 collection->items[item].data = data;
1623 collection->items[item].selected = FALSE;
1625 collection->number_of_items++;
1627 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection)))
1629 set_vadjustment(collection);
1630 collection_draw_item(collection,
1631 collection->number_of_items - 1,
1632 FALSE);
1635 return item;
1638 void collection_unselect_item(Collection *collection, gint item)
1640 collection_item_set_selected(collection, item, FALSE);
1643 void collection_select_item(Collection *collection, gint item)
1645 collection_item_set_selected(collection, item, TRUE);
1648 void collection_toggle_item(Collection *collection, gint item)
1650 collection_item_set_selected(collection, item,
1651 !collection->items[item].selected);
1654 /* Select all items in the collection */
1655 void collection_select_all(Collection *collection)
1657 GtkWidget *widget;
1658 int item = 0;
1660 g_return_if_fail(collection != NULL);
1661 g_return_if_fail(IS_COLLECTION(collection));
1663 widget = GTK_WIDGET(collection);
1665 if (collection->number_selected == collection->number_of_items)
1666 return; /* Nothing to do */
1668 while (collection->number_selected < collection->number_of_items)
1670 while (collection->items[item].selected)
1671 item++;
1673 collection->items[item].selected = TRUE;
1674 collection_draw_item(collection, item, TRUE);
1675 item++;
1677 collection->number_selected++;
1680 gtk_signal_emit(GTK_OBJECT(collection),
1681 collection_signals[GAIN_SELECTION],
1682 current_event_time);
1683 EMIT_SELECTION_CHANGED(collection, current_event_time);
1686 /* Unselect all items except number item, which is selected (-1 to unselect
1687 * everything).
1689 void collection_clear_except(Collection *collection, gint item)
1691 GtkWidget *widget;
1692 int i = 0;
1693 int end; /* Selected items to end up with */
1695 g_return_if_fail(collection != NULL);
1696 g_return_if_fail(IS_COLLECTION(collection));
1697 g_return_if_fail(item >= -1 && item < collection->number_of_items);
1699 widget = GTK_WIDGET(collection);
1701 if (item == -1)
1702 end = 0;
1703 else
1705 collection_select_item(collection, item);
1706 end = 1;
1709 if (collection->number_selected == 0)
1710 return;
1712 while (collection->number_selected > end)
1714 while (i == item || !collection->items[i].selected)
1715 i++;
1717 collection->items[i].selected = FALSE;
1718 collection_draw_item(collection, i, TRUE);
1719 i++;
1721 collection->number_selected--;
1724 if (end == 0)
1725 gtk_signal_emit(GTK_OBJECT(collection),
1726 collection_signals[LOSE_SELECTION],
1727 current_event_time);
1728 EMIT_SELECTION_CHANGED(collection, current_event_time);
1731 /* Unselect all items in the collection */
1732 void collection_clear_selection(Collection *collection)
1734 g_return_if_fail(collection != NULL);
1735 g_return_if_fail(IS_COLLECTION(collection));
1737 collection_clear_except(collection, -1);
1740 /* Force a redraw of the specified item, if it is visible */
1741 void collection_draw_item(Collection *collection, gint item, gboolean blank)
1743 gint height;
1744 GdkRectangle area;
1745 GtkWidget *widget;
1746 int row, col;
1748 g_return_if_fail(collection != NULL);
1749 g_return_if_fail(IS_COLLECTION(collection));
1750 g_return_if_fail(item >= 0 &&
1751 (item == 0 || item < collection->number_of_items));
1753 widget = GTK_WIDGET(collection);
1754 if (!GTK_WIDGET_REALIZED(widget))
1755 return;
1757 col = item % collection->columns;
1758 row = item / collection->columns;
1760 collection_get_item_area(collection, row, col, &area);
1762 if (area.y + area.height < 0)
1763 return;
1765 gdk_window_get_size(widget->window, NULL, &height);
1767 if (area.y > height)
1768 return;
1770 if (blank || collection->lasso_box)
1771 clear_area(collection, &area);
1773 draw_one_item(collection, item, &area);
1775 if (collection->lasso_box)
1777 gdk_gc_set_clip_rectangle(collection->xor_gc, &area);
1778 draw_lasso_box(collection);
1779 gdk_gc_set_clip_rectangle(collection->xor_gc, NULL);
1783 void collection_set_item_size(Collection *collection, int width, int height)
1785 GtkWidget *widget;
1787 g_return_if_fail(collection != NULL);
1788 g_return_if_fail(IS_COLLECTION(collection));
1789 g_return_if_fail(width > 4 && height > 4);
1791 if (collection->item_width == width &&
1792 collection->item_height == height)
1793 return;
1795 widget = GTK_WIDGET(collection);
1797 collection->item_width = width;
1798 collection->item_height = height;
1800 if (GTK_WIDGET_REALIZED(widget))
1802 gint window_width;
1804 collection->paint_level = PAINT_CLEAR;
1805 gdk_window_get_size(widget->window, &window_width, NULL);
1806 collection->columns = MAX(window_width / collection->item_width,
1809 set_vadjustment(collection);
1810 if (collection->cursor_item != -1)
1811 scroll_to_show(collection, collection->cursor_item);
1812 gtk_widget_queue_draw(widget);
1816 /* Cursor is positioned on item with the same data as before the sort.
1817 * Same for the wink item.
1819 void collection_qsort(Collection *collection,
1820 int (*compar)(const void *, const void *))
1822 int cursor, wink, items;
1823 gpointer cursor_data = NULL;
1824 gpointer wink_data = NULL;
1826 g_return_if_fail(collection != NULL);
1827 g_return_if_fail(IS_COLLECTION(collection));
1828 g_return_if_fail(compar != NULL);
1830 items = collection->number_of_items;
1832 wink = collection->wink_item;
1833 if (wink >= 0 && wink < items)
1835 wink_data = collection->items[wink].data;
1836 collection->wink_item = -1;
1838 else
1839 wink = -1;
1841 cursor = collection->cursor_item;
1842 if (cursor >= 0 && cursor < items)
1843 cursor_data = collection->items[cursor].data;
1844 else
1845 cursor = -1;
1847 qsort(collection->items, items, sizeof(collection->items[0]), compar);
1849 if (cursor > -1 || wink > -1)
1851 int item;
1853 for (item = 0; item < items; item++)
1855 if (collection->items[item].data == cursor_data)
1856 collection_set_cursor_item(collection, item);
1857 if (collection->items[item].data == wink_data)
1859 collection->cursor_item_old = item;
1860 collection->wink_item = item;
1861 scroll_to_show(collection, item);
1866 collection->paint_level = PAINT_CLEAR;
1868 gtk_widget_queue_draw(GTK_WIDGET(collection));
1871 /* Find an item in an unsorted collection.
1872 * Returns the item number, or -1 if not found.
1874 int collection_find_item(Collection *collection, gpointer data,
1875 int (*compar)(const void *, const void *))
1877 int i;
1879 g_return_val_if_fail(collection != NULL, -1);
1880 g_return_val_if_fail(IS_COLLECTION(collection), -1);
1881 g_return_val_if_fail(compar != NULL, -1);
1883 for (i = 0; i < collection->number_of_items; i++)
1884 if (compar(&collection->items[i].data, &data) == 0)
1885 return i;
1887 return -1;
1890 /* Return the number of the item under the point (x,y), or -1 for none.
1891 * This may call your test_point callback. The point is relative to the
1892 * collection's origin.
1894 int collection_get_item(Collection *collection, int x, int y)
1896 int scroll;
1897 int row, col;
1898 int width;
1899 int item;
1901 g_return_val_if_fail(collection != NULL, -1);
1903 scroll = collection->vadj->value;
1904 col = x / collection->item_width;
1905 row = (y + scroll) / collection->item_height;
1907 if (col >= collection->columns)
1908 col = collection->columns - 1;
1910 if (col < 0 || row < 0)
1911 return -1;
1913 if (col == collection->columns - 1)
1914 width = collection->item_width << 1;
1915 else
1916 width = collection->item_width;
1918 item = col + row * collection->columns;
1919 if (item >= collection->number_of_items
1921 !collection->test_point(collection,
1922 x - col * collection->item_width,
1923 y - row * collection->item_height
1924 + scroll,
1925 &collection->items[item],
1926 width,
1927 collection->item_height,
1928 collection->cb_user_data))
1930 return -1;
1933 return item;
1936 /* Set the cursor/highlight over the given item. Passing -1
1937 * hides the cursor. As a special case, you may set the cursor item
1938 * to zero when there are no items.
1940 void collection_set_cursor_item(Collection *collection, gint item)
1942 int old_item;
1944 g_return_if_fail(collection != NULL);
1945 g_return_if_fail(IS_COLLECTION(collection));
1946 g_return_if_fail(item >= -1 &&
1947 (item < collection->number_of_items || item == 0));
1949 old_item = collection->cursor_item;
1951 if (old_item == item)
1952 return;
1954 collection->cursor_item = item;
1956 if (old_item != -1)
1957 collection_draw_item(collection, old_item, TRUE);
1959 if (item != -1)
1961 collection_draw_item(collection, item, TRUE);
1962 if (collection->auto_scroll == -1)
1963 scroll_to_show(collection, item);
1965 else if (old_item != -1)
1966 collection->cursor_item_old = old_item;
1969 /* Briefly highlight an item to draw the user's attention to it.
1970 * -1 cancels the effect, as does deleting items, sorting the collection
1971 * or starting a new wink effect.
1972 * Otherwise, the effect will cancel itself after a short pause.
1973 * */
1974 void collection_wink_item(Collection *collection, gint item)
1976 g_return_if_fail(collection != NULL);
1977 g_return_if_fail(IS_COLLECTION(collection));
1978 g_return_if_fail(item >= -1 && item < collection->number_of_items);
1980 if (collection->wink_item != -1)
1981 cancel_wink(collection);
1982 if (item == -1)
1983 return;
1985 collection->cursor_item_old = collection->wink_item = item;
1986 collection->winks_left = 3;
1987 collection->wink_timeout = gtk_timeout_add(70,
1988 (GtkFunction) wink_timeout,
1989 collection);
1990 scroll_to_show(collection, item);
1991 invert_wink(collection);
1993 gdk_flush();
1996 /* Call test(item, data) on each item in the collection.
1997 * Remove all items for which it returns TRUE. test() should
1998 * free the data before returning TRUE. The collection is in an
1999 * inconsistant state during this call (ie, when test() is called).
2001 void collection_delete_if(Collection *collection,
2002 gboolean (*test)(gpointer item, gpointer data),
2003 gpointer data)
2005 int in, out = 0;
2006 int selected = 0;
2007 int cursor;
2009 g_return_if_fail(collection != NULL);
2010 g_return_if_fail(IS_COLLECTION(collection));
2011 g_return_if_fail(test != NULL);
2013 cursor = collection->cursor_item;
2015 for (in = 0; in < collection->number_of_items; in++)
2017 if (!test(collection->items[in].data, data))
2019 if (collection->items[in].selected)
2021 collection->items[out].selected = TRUE;
2022 selected++;
2024 else
2025 collection->items[out].selected = FALSE;
2027 collection->items[out++].data =
2028 collection->items[in].data;
2030 else if (cursor >= in)
2031 cursor--;
2034 if (in != out)
2036 collection->cursor_item = cursor;
2038 if (collection->wink_item != -1)
2040 collection->wink_item = -1;
2041 gtk_timeout_remove(collection->wink_timeout);
2044 collection->number_of_items = out;
2045 if (collection->number_selected && !selected)
2047 /* We've lost all the selected items */
2048 gtk_signal_emit(GTK_OBJECT(collection),
2049 collection_signals[LOSE_SELECTION],
2050 current_event_time);
2053 collection->number_selected = selected;
2054 resize_arrays(collection,
2055 MAX(collection->number_of_items, MINIMUM_ITEMS));
2057 collection->paint_level = PAINT_CLEAR;
2059 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection)))
2061 set_vadjustment(collection);
2062 gtk_widget_queue_draw(GTK_WIDGET(collection));
2067 /* Move the cursor by the given row and column offsets.
2068 * Moving by (0,0) can be used to simply make the cursor appear.
2070 void collection_move_cursor(Collection *collection, int drow, int dcol)
2072 int row, col, item;
2073 int first, last, total_rows;
2075 g_return_if_fail(collection != NULL);
2076 g_return_if_fail(IS_COLLECTION(collection));
2078 get_visible_limits(collection, &first, &last);
2080 item = collection->cursor_item;
2081 if (item == -1)
2083 item = MIN(collection->cursor_item_old,
2084 collection->number_of_items - 1);
2087 if (item == -1)
2089 col = 0;
2090 row = first;
2092 else
2094 row = item / collection->columns;
2095 col = item % collection->columns + dcol;
2097 if (row < first)
2098 row = first;
2099 else if (row > last)
2100 row = last;
2101 else
2102 row = MAX(row + drow, 0);
2105 total_rows = (collection->number_of_items + collection->columns - 1)
2106 / collection->columns;
2108 if (row >= total_rows - 1 && drow > 0)
2110 row = total_rows - 1;
2111 item = col + row * collection->columns;
2112 if (item >= collection->number_of_items - 1)
2114 collection_set_cursor_item(collection,
2115 collection->number_of_items - 1);
2116 return;
2119 if (row < 0)
2120 row = 0;
2122 item = col + row * collection->columns;
2124 if (item >= 0 && item < collection->number_of_items)
2125 collection_set_cursor_item(collection, item);
2128 /* When autoscroll is on, a timer keeps track of the pointer position.
2129 * While it's near the top or bottom of the window, the window scrolls.
2131 * If the mouse buttons are released, or the pointer leaves the window,
2132 * auto_scroll is turned off.
2134 void collection_set_autoscroll(Collection *collection, gboolean auto_scroll)
2136 g_return_if_fail(collection != NULL);
2137 g_return_if_fail(IS_COLLECTION(collection));
2139 if (auto_scroll)
2141 if (collection->auto_scroll != -1)
2142 return; /* Already on! */
2144 collection->auto_scroll = gtk_timeout_add(50,
2145 (GtkFunction) as_timeout,
2146 collection);
2148 else
2150 if (collection->auto_scroll == -1)
2151 return; /* Already off! */
2153 gtk_timeout_remove(collection->auto_scroll);
2154 collection->auto_scroll = -1;
2158 /* Start a lasso box drag */
2159 void collection_lasso_box(Collection *collection, int x, int y)
2161 collection->drag_box_x[0] = x;
2162 collection->drag_box_y[0] = y;
2163 collection->drag_box_x[1] = x;
2164 collection->drag_box_y[1] = y;
2166 collection_set_autoscroll(collection, TRUE);
2167 add_lasso_box(collection);
2170 /* Remove the lasso box. Applies fn to each item inside the box.
2171 * fn may be GDK_INVERT, GDK_SET, GDK_NOOP or GDK_CLEAR.
2173 void collection_end_lasso(Collection *collection, GdkFunction fn)
2175 if (fn != GDK_CLEAR)
2177 GdkRectangle region;
2179 find_lasso_area(collection, &region);
2181 collection_process_area(collection, &region, fn,
2182 GDK_CURRENT_TIME);
2185 abort_lasso(collection);
2189 /* Unblock the selection_changed signal, emitting the signal if the
2190 * block counter reaches zero and emit is TRUE.
2192 void collection_unblock_selection_changed(Collection *collection,
2193 guint time,
2194 gboolean emit)
2196 g_return_if_fail(collection != NULL);
2197 g_return_if_fail(IS_COLLECTION(collection));
2198 g_return_if_fail(collection->block_selection_changed > 0);
2200 collection->block_selection_changed--;
2202 if (emit && !collection->block_selection_changed)
2203 gtk_signal_emit(GTK_OBJECT(collection),
2204 collection_signals[SELECTION_CHANGED], time);