r1066: Fixed some compiler warnings (Vincent Lef�vre).
[rox-filer.git] / ROX-Filer / src / collection.c
blobf999ac07bdd043b6d92a7ec415a2ca4ebbd023dd
1 /*
2 * $Id$
4 * Collection - a GTK+ widget
5 * Copyright (C) 2002, 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 "global.h"
35 #include "collection.h"
37 #define MIN_WIDTH 80
38 #define MIN_HEIGHT 60
39 #define MINIMUM_ITEMS 16
40 #define AUTOSCROLL_STEP 20
42 #define MAX_WINKS 3 /* Should be an odd number */
44 /* Macro to emit the "selection_changed" signal only if allowed */
45 #define EMIT_SELECTION_CHANGED(collection, time) \
46 if (!collection->block_selection_changed) \
47 gtk_signal_emit(GTK_OBJECT(collection), \
48 collection_signals[SELECTION_CHANGED], time)
50 enum
52 ARG_0,
53 ARG_VADJUSTMENT
56 /* Signals:
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 * void selection_changed(collection, user_data)
69 * The set of selected items has changed.
70 * Time is the time of the event that caused the change, or
71 * GDK_CURRENT_TIME if not known.
73 enum
75 GAIN_SELECTION,
76 LOSE_SELECTION,
77 SELECTION_CHANGED,
78 LAST_SIGNAL
81 static guint collection_signals[LAST_SIGNAL] = { 0 };
83 static guint32 current_event_time = GDK_CURRENT_TIME;
85 static GtkWidgetClass *parent_class = NULL;
87 /* Static prototypes */
88 static void clear_area(Collection *collection, GdkRectangle *area);
89 static void draw_one_item(Collection *collection,
90 int item,
91 GdkRectangle *area);
92 static void collection_class_init(CollectionClass *class);
93 static void collection_init(Collection *object);
94 static void collection_destroy(GtkObject *object);
95 static void collection_finalize(GtkObject *object);
96 static void collection_realize(GtkWidget *widget);
97 static void collection_map(GtkWidget *widget);
98 static gint collection_paint(Collection *collection,
99 GdkRectangle *area);
100 static void collection_size_request(GtkWidget *widget,
101 GtkRequisition *requisition);
102 static void collection_size_allocate(GtkWidget *widget,
103 GtkAllocation *allocation);
104 static void collection_set_adjustment(Collection *collection,
105 GtkAdjustment *vadj);
106 static void collection_set_arg( GtkObject *object,
107 GtkArg *arg,
108 guint arg_id);
109 static void collection_get_arg( GtkObject *object,
110 GtkArg *arg,
111 guint arg_id);
112 static gint collection_expose(GtkWidget *widget, GdkEventExpose *event);
113 static void default_draw_item(GtkWidget *widget,
114 CollectionItem *data,
115 GdkRectangle *area,
116 gpointer user_data);
117 static gboolean default_test_point(Collection *collection,
118 int point_x, int point_y,
119 CollectionItem *data,
120 int width, int height,
121 gpointer user_data);
122 static gint collection_motion_notify(GtkWidget *widget,
123 GdkEventMotion *event);
124 static void add_lasso_box(Collection *collection);
125 static void abort_lasso(Collection *collection);
126 static void remove_lasso_box(Collection *collection);
127 static void draw_lasso_box(Collection *collection);
128 static void cancel_wink(Collection *collection);
129 static gint collection_key_press(GtkWidget *widget, GdkEventKey *event);
130 static void get_visible_limits(Collection *collection, int *first, int *last);
131 static void scroll_to_show(Collection *collection, int item);
132 static void collection_item_set_selected(Collection *collection,
133 gint item,
134 gboolean selected,
135 gboolean signal);
136 #ifdef GTK2
137 static gint collection_scroll_event(GtkWidget *widget, GdkEventScroll *event);
138 #else
139 static gint collection_scroll_event(GtkWidget *widget, GdkEventButton *event);
140 #endif
142 #ifndef GTK2
143 static void collection_adjustment(GtkAdjustment *adjustment,
144 Collection *collection);
145 static void collection_set_style(GtkWidget *widget,
146 GtkStyle *previous_style);
147 static void collection_disconnect(GtkAdjustment *adjustment,
148 Collection *collection);
149 static void scroll_by(Collection *collection, gint diff);
150 static void set_vadjustment(Collection *collection);
151 static void draw_focus(GtkWidget *widget);
152 static void collection_draw(GtkWidget *widget, GdkRectangle *area);
153 static gint focus_in(GtkWidget *widget, GdkEventFocus *event);
154 static gint focus_out(GtkWidget *widget, GdkEventFocus *event);
155 #endif
157 static void draw_focus_at(Collection *collection, GdkRectangle *area)
159 GtkWidget *widget;
160 GdkGC *gc;
162 widget = GTK_WIDGET(collection);
164 if (GTK_WIDGET_FLAGS(widget) & GTK_HAS_FOCUS)
165 gc = widget->style->fg_gc[GTK_STATE_ACTIVE];
166 else
167 gc = widget->style->fg_gc[GTK_STATE_INSENSITIVE];
169 gdk_draw_rectangle(widget->window, gc, FALSE,
170 area->x + 1, area->y + 1,
171 collection->item_width - 3,
172 area->height - 3);
175 static void draw_one_item(Collection *collection, int item, GdkRectangle *area)
177 if (item < collection->number_of_items)
179 collection->draw_item((GtkWidget *) collection,
180 &collection->items[item],
181 area,
182 collection->cb_user_data);
185 if (item == collection->cursor_item)
186 draw_focus_at(collection, area);
189 #ifndef GTK2
190 static void draw_focus(GtkWidget *widget)
192 Collection *collection;
194 g_return_if_fail(widget != NULL);
195 g_return_if_fail(IS_COLLECTION(widget));
197 collection = COLLECTION(widget);
199 if (collection->cursor_item < 0 || !GTK_WIDGET_REALIZED(widget))
200 return;
202 collection_draw_item(collection, collection->cursor_item, FALSE);
204 #endif
206 GtkType collection_get_type(void)
208 static guint my_type = 0;
210 if (!my_type)
212 static const GtkTypeInfo my_info =
214 "Collection",
215 sizeof(Collection),
216 sizeof(CollectionClass),
217 (GtkClassInitFunc) collection_class_init,
218 (GtkObjectInitFunc) collection_init,
219 NULL, /* Reserved 1 */
220 NULL, /* Reserved 2 */
221 (GtkClassInitFunc) NULL /* base_class_init_func */
224 my_type = gtk_type_unique(gtk_widget_get_type(),
225 &my_info);
228 return my_type;
231 #ifdef GTK2
232 typedef void (*FinalizeFn)(GObject *object);
233 #endif
235 static void collection_class_init(CollectionClass *class)
237 GtkObjectClass *object_class;
238 GtkWidgetClass *widget_class;
239 GtkType type;
241 object_class = (GtkObjectClass*) class;
242 widget_class = (GtkWidgetClass*) class;
244 parent_class = gtk_type_class(gtk_widget_get_type());
246 gtk_object_add_arg_type("Collection::vadjustment",
247 GTK_TYPE_ADJUSTMENT,
248 GTK_ARG_READWRITE | GTK_ARG_CONSTRUCT,
249 ARG_VADJUSTMENT);
251 object_class->destroy = collection_destroy;
252 #ifdef GTK2
253 G_OBJECT_CLASS(object_class)->finalize =
254 (FinalizeFn) collection_finalize;
255 type = GTK_CLASS_TYPE(object_class);
256 #else
257 object_class->finalize = collection_finalize;
258 type = object_class->type;
259 #endif
261 widget_class->realize = collection_realize;
262 widget_class->expose_event = collection_expose;
263 widget_class->size_request = collection_size_request;
264 widget_class->size_allocate = collection_size_allocate;
266 widget_class->key_press_event = collection_key_press;
268 widget_class->motion_notify_event = collection_motion_notify;
269 widget_class->map = collection_map;
270 #ifdef GTK2
271 widget_class->scroll_event = collection_scroll_event;
272 #else
273 widget_class->button_press_event = collection_scroll_event;
274 #endif
276 #ifndef GTK2
277 widget_class->style_set = collection_set_style; /* XXX: Test for 2.0 */
278 widget_class->focus_in_event = focus_in;
279 widget_class->focus_out_event = focus_out;
280 widget_class->draw = collection_draw;
281 widget_class->draw_focus = draw_focus;
282 #endif
284 object_class->set_arg = collection_set_arg;
285 object_class->get_arg = collection_get_arg;
287 class->gain_selection = NULL;
288 class->lose_selection = NULL;
289 class->selection_changed = NULL;
291 collection_signals[GAIN_SELECTION] = gtk_signal_new("gain_selection",
292 GTK_RUN_LAST,
293 type,
294 GTK_SIGNAL_OFFSET(CollectionClass,
295 gain_selection),
296 gtk_marshal_NONE__INT,
297 GTK_TYPE_NONE, 1,
298 GTK_TYPE_INT);
299 collection_signals[LOSE_SELECTION] = gtk_signal_new("lose_selection",
300 GTK_RUN_LAST,
301 type,
302 GTK_SIGNAL_OFFSET(CollectionClass,
303 lose_selection),
304 gtk_marshal_NONE__INT,
305 GTK_TYPE_NONE, 1,
306 GTK_TYPE_INT);
307 collection_signals[SELECTION_CHANGED] = gtk_signal_new(
308 "selection_changed",
309 GTK_RUN_LAST,
310 type,
311 GTK_SIGNAL_OFFSET(CollectionClass,
312 selection_changed),
313 gtk_marshal_NONE__INT,
314 GTK_TYPE_NONE, 1,
315 GTK_TYPE_INT);
317 #ifndef GTK2
318 gtk_object_class_add_signals(object_class,
319 collection_signals, LAST_SIGNAL);
320 #endif
323 static void collection_init(Collection *object)
325 g_return_if_fail(object != NULL);
326 g_return_if_fail(IS_COLLECTION(object));
328 GTK_WIDGET_SET_FLAGS(GTK_WIDGET(object), GTK_CAN_FOCUS);
330 object->number_of_items = 0;
331 object->number_selected = 0;
332 object->block_selection_changed = 0;
333 object->columns = 1;
334 object->item_width = 64;
335 object->item_height = 64;
336 object->vadj = NULL;
337 #ifndef GTK2
338 object->paint_level = PAINT_OVERWRITE;
339 object->last_scroll = 0;
340 #endif
341 object->bg_gc = NULL;
343 object->items = g_new(CollectionItem, MINIMUM_ITEMS);
344 object->cursor_item = -1;
345 object->cursor_item_old = -1;
346 object->wink_item = -1;
347 object->wink_on_map = -1;
348 object->array_size = MINIMUM_ITEMS;
349 object->draw_item = default_draw_item;
350 object->test_point = default_test_point;
351 object->free_item = NULL;
353 object->auto_scroll = -1;
355 return;
358 GtkWidget* collection_new(void)
360 return GTK_WIDGET(gtk_widget_new(collection_get_type(), NULL));
363 /* Note: The draw_item call gives the maximum area that can be
364 * drawn to. For the column on the far right, this extends to the
365 * edge of the window. Normally, use collection->item_width instead
366 * of area->width to calculate the position.
368 * test_point does not use a larger value for the width, but the
369 * x point of the click may be larger than the width.
371 void collection_set_functions(Collection *collection,
372 CollectionDrawFunc draw_item,
373 CollectionTestFunc test_point,
374 gpointer user_data)
376 GtkWidget *widget;
378 g_return_if_fail(collection != NULL);
379 g_return_if_fail(IS_COLLECTION(collection));
381 widget = GTK_WIDGET(collection);
383 if (!draw_item)
384 draw_item = default_draw_item;
385 if (!test_point)
386 test_point = default_test_point;
388 collection->draw_item = draw_item;
389 collection->test_point = test_point;
390 collection->cb_user_data = user_data;
392 if (GTK_WIDGET_REALIZED(widget))
394 #ifndef GTK2
395 collection->paint_level = PAINT_CLEAR;
396 #endif
397 gtk_widget_queue_clear(widget);
401 /* After this we are unusable, but our data (if any) is still hanging around.
402 * It will be freed later with finalize.
404 static void collection_destroy(GtkObject *object)
406 Collection *collection;
408 g_return_if_fail(object != NULL);
409 g_return_if_fail(IS_COLLECTION(object));
411 collection = COLLECTION(object);
413 collection_clear(collection);
415 if (collection->auto_scroll != -1)
417 gtk_timeout_remove(collection->auto_scroll);
418 collection->auto_scroll = -1;
421 if (collection->bg_gc)
423 gdk_gc_destroy(collection->bg_gc);
424 collection->bg_gc = NULL;
427 if (collection->vadj)
429 #ifndef GTK2
430 gtk_signal_disconnect_by_data(GTK_OBJECT(collection->vadj),
431 collection);
432 #endif
433 gtk_object_unref(GTK_OBJECT(collection->vadj));
434 collection->vadj = NULL;
437 if (GTK_OBJECT_CLASS(parent_class)->destroy)
438 (*GTK_OBJECT_CLASS(parent_class)->destroy)(object);
441 /* This is the last thing that happens to us. Free all data. */
442 static void collection_finalize(GtkObject *object)
444 Collection *collection;
446 collection = COLLECTION(object);
448 g_return_if_fail(collection->number_of_items == 0);
450 g_free(collection->items);
453 static void collection_map(GtkWidget *widget)
455 Collection *collection = COLLECTION(widget);
457 if (GTK_WIDGET_CLASS(parent_class)->map)
458 (*GTK_WIDGET_CLASS(parent_class)->map)(widget);
460 if (collection->wink_on_map >= 0)
462 collection_wink_item(collection, collection->wink_on_map);
463 collection->wink_on_map = -1;
467 static GdkGC *create_bg_gc(GtkWidget *widget)
469 GdkGCValues values;
471 values.tile = widget->style->bg_pixmap[GTK_STATE_NORMAL];
472 values.fill = GDK_TILED;
474 return gdk_gc_new_with_values(widget->window, &values,
475 GDK_GC_FILL | GDK_GC_TILE);
478 static void collection_realize(GtkWidget *widget)
480 Collection *collection;
481 GdkWindowAttr attributes;
482 gint attributes_mask;
483 GdkGCValues xor_values;
484 GdkColor *bg, *fg;
486 g_return_if_fail(widget != NULL);
487 g_return_if_fail(IS_COLLECTION(widget));
488 g_return_if_fail(widget->parent != NULL);
490 GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);
491 collection = COLLECTION(widget);
493 attributes.x = widget->allocation.x;
494 attributes.y = widget->allocation.y;
495 attributes.width = widget->allocation.width;
496 attributes.height = widget->allocation.height;
497 attributes.wclass = GDK_INPUT_OUTPUT;
498 attributes.window_type = GDK_WINDOW_CHILD;
499 attributes.event_mask = gtk_widget_get_events(widget) |
500 GDK_EXPOSURE_MASK |
501 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
502 GDK_BUTTON1_MOTION_MASK | GDK_BUTTON2_MOTION_MASK |
503 GDK_BUTTON3_MOTION_MASK;
504 attributes.visual = gtk_widget_get_visual(widget);
505 attributes.colormap = gtk_widget_get_colormap(widget);
507 attributes_mask = GDK_WA_X | GDK_WA_Y |
508 GDK_WA_VISUAL | GDK_WA_COLORMAP;
509 widget->window = gdk_window_new(gtk_widget_get_parent_window(widget),
510 &attributes, attributes_mask);
512 widget->style = gtk_style_attach(widget->style, widget->window);
514 gdk_window_set_user_data(widget->window, widget);
516 gdk_window_set_background(widget->window,
517 &widget->style->bg[GTK_STATE_NORMAL]);
518 if (widget->style->bg_pixmap[GTK_STATE_NORMAL])
519 collection->bg_gc = create_bg_gc(widget);
521 #ifndef GTK2
522 /* Try to stop everything flickering horribly */
523 gdk_window_set_static_gravities(widget->window, TRUE);
525 set_vadjustment(collection);
526 #endif
528 bg = &widget->style->bg[GTK_STATE_NORMAL];
529 fg = &widget->style->fg[GTK_STATE_NORMAL];
530 xor_values.function = GDK_XOR;
531 xor_values.foreground.pixel = fg->pixel ^ bg->pixel;
532 collection->xor_gc = gdk_gc_new_with_values(widget->window,
533 &xor_values,
534 GDK_GC_FOREGROUND
535 | GDK_GC_FUNCTION);
538 static void collection_size_request(GtkWidget *widget,
539 GtkRequisition *requisition)
541 #ifdef GTK2
542 Collection *collection = COLLECTION(widget);
543 int rows, cols = collection->columns;
545 /* We ask for the total size we need; our containing viewport
546 * will deal with scrolling.
548 requisition->width = MIN_WIDTH;
549 rows = (collection->number_of_items + cols - 1) / cols;
550 requisition->height = rows * collection->item_height;
551 #else
552 requisition->width = MIN_WIDTH;
553 requisition->height = MIN_HEIGHT;
554 #endif
557 #ifdef GTK2
558 static gboolean scroll_after_alloc(Collection *collection)
560 if (collection->wink_item != -1)
561 scroll_to_show(collection, collection->wink_item);
562 else if (collection->cursor_item != -1)
563 scroll_to_show(collection, collection->cursor_item);
564 g_object_unref(G_OBJECT(collection));
566 return FALSE;
568 #endif
570 static void collection_size_allocate(GtkWidget *widget,
571 GtkAllocation *allocation)
573 Collection *collection;
574 int old_columns;
575 gboolean cursor_visible = FALSE;
577 g_return_if_fail(widget != NULL);
578 g_return_if_fail(IS_COLLECTION(widget));
579 g_return_if_fail(allocation != NULL);
581 collection = COLLECTION(widget);
583 if (collection->cursor_item != -1)
585 int first, last;
586 int crow = collection->cursor_item / collection->columns;
588 get_visible_limits(collection, &first, &last);
590 cursor_visible = crow >= first && crow <= last;
593 old_columns = collection->columns;
594 #ifndef GTK2
595 if (widget->allocation.x != allocation->x
596 || widget->allocation.y != allocation->y)
597 collection->paint_level = PAINT_CLEAR;
598 #endif
600 widget->allocation = *allocation;
602 collection->columns = allocation->width / collection->item_width;
603 if (collection->columns < 1)
604 collection->columns = 1;
606 if (GTK_WIDGET_REALIZED(widget))
608 gdk_window_move_resize(widget->window,
609 allocation->x, allocation->y,
610 allocation->width, allocation->height);
612 #ifndef GTK2
613 /* Force a redraw if the number of columns has changed
614 * or we have a background pixmap (!).
616 if (old_columns != collection->columns || collection->bg_gc)
618 collection->paint_level = PAINT_CLEAR;
619 gtk_widget_queue_clear(widget);
622 set_vadjustment(collection);
623 #endif
625 if (cursor_visible)
626 scroll_to_show(collection, collection->cursor_item);
629 #ifdef GTK2
630 if (old_columns != collection->columns)
632 /* Need to go around again... */
633 gtk_widget_queue_resize(widget);
635 else if (collection->wink_item != -1 || collection->cursor_item != -1)
637 /* Viewport resets the adjustments after the alloc */
638 g_object_ref(G_OBJECT(collection));
639 g_idle_add((GSourceFunc) scroll_after_alloc, collection);
641 #endif
644 #ifndef GTK2
645 static void collection_set_style(GtkWidget *widget,
646 GtkStyle *previous_style)
648 Collection *collection;
650 g_return_if_fail(IS_COLLECTION(widget));
652 collection = COLLECTION(widget);
654 collection->paint_level = PAINT_CLEAR;
656 if (GTK_WIDGET_REALIZED(widget))
658 gdk_window_set_background(widget->window,
659 &widget->style->bg[GTK_STATE_NORMAL]);
661 if (collection->bg_gc)
663 gdk_gc_destroy(collection->bg_gc);
664 collection->bg_gc = NULL;
667 if (widget->style->bg_pixmap[GTK_STATE_NORMAL])
668 collection->bg_gc = create_bg_gc(widget);
671 #endif
673 static void clear_area(Collection *collection, GdkRectangle *area)
675 GtkWidget *widget = GTK_WIDGET(collection);
676 #ifdef GTK2
677 int scroll = 0;
678 #else
679 int scroll = collection->vadj->value;
680 #endif
682 if (collection->bg_gc)
684 gdk_gc_set_ts_origin(collection->bg_gc, 0, -scroll);
686 gdk_draw_rectangle(widget->window,
687 collection->bg_gc,
688 TRUE,
689 area->x, area->y,
690 area->width, area->height);
692 else
694 gdk_window_set_background(widget->window,
695 &widget->style->bg[GTK_STATE_NORMAL]);
696 gdk_window_clear_area(widget->window,
697 area->x, area->y, area->width, area->height);
701 /* Return the area occupied by the item at (row, col) by filling
702 * in 'area'.
704 static void collection_get_item_area(Collection *collection,
705 int row, int col,
706 GdkRectangle *area)
709 #ifdef GTK2
710 int scroll = 0;
711 #else
712 int scroll = collection->vadj->value; /* (round to int) */
713 #endif
715 area->x = col * collection->item_width;
716 area->y = row * collection->item_height - scroll;
718 area->width = collection->item_width;
719 area->height = collection->item_height;
720 if (col == collection->columns - 1)
721 area->width <<= 1;
724 static gint collection_paint(Collection *collection,
725 GdkRectangle *area)
727 GdkRectangle item_area;
728 int row, col;
729 int item;
730 int scroll = 0;
731 int start_row, last_row;
732 int start_col, last_col;
733 int phys_last_col;
734 GdkRectangle clip;
735 #ifndef GTK2
736 GtkWidget *widget;
737 GdkRectangle whole;
738 gint width, height;
740 widget = GTK_WIDGET(collection);
742 scroll = collection->vadj->value;
743 gdk_window_get_size(widget->window, &width, &height);
744 whole.x = 0;
745 whole.y = 0;
746 whole.width = width;
747 whole.height = height;
749 if (collection->paint_level > PAINT_NORMAL || area == NULL)
751 area = &whole;
753 if (collection->paint_level == PAINT_CLEAR
754 && !collection->lasso_box)
755 clear_area(collection, area);
757 collection->paint_level = PAINT_NORMAL;
759 #endif
761 /* Calculate the ranges to plot */
762 start_row = (area->y + scroll) / collection->item_height;
763 last_row = (area->y + area->height - 1 + scroll)
764 / collection->item_height;
765 row = start_row;
767 start_col = area->x / collection->item_width;
768 phys_last_col = (area->x + area->width - 1) / collection->item_width;
770 if (collection->lasso_box)
772 /* You can't be too careful with lasso boxes...
774 * clip gives the total area drawn over (this may be larger
775 * than the requested area). It's used to redraw the lasso
776 * box.
778 collection_get_item_area(collection,
779 start_row, start_col, &clip);
780 clip.width *= phys_last_col - start_col + 1;
781 clip.height *= last_row - start_row + 1;
783 clear_area(collection, &clip);
786 /* The right-most column may be wider than the others.
787 * Therefore, to redraw the area after the last 'real' column
788 * we may have to draw the right-most column.
790 if (start_col >= collection->columns)
791 start_col = collection->columns - 1;
793 if (phys_last_col >= collection->columns)
794 last_col = collection->columns - 1;
795 else
796 last_col = phys_last_col;
798 col = start_col;
800 item = row * collection->columns + col;
802 /* g_print("[ paint %d..%d ]\n", row, last_row); */
803 while ((item == 0 || item < collection->number_of_items)
804 && row <= last_row)
806 collection_get_item_area(collection, row, col, &item_area);
808 draw_one_item(collection, item, &item_area);
809 col++;
811 if (col > last_col)
813 col = start_col;
814 row++;
815 item = row * collection->columns + col;
817 else
818 item++;
821 if (collection->lasso_box)
823 gdk_gc_set_clip_rectangle(collection->xor_gc, &clip);
824 draw_lasso_box(collection);
825 gdk_gc_set_clip_rectangle(collection->xor_gc, NULL);
828 return FALSE;
831 static void default_draw_item( GtkWidget *widget,
832 CollectionItem *item,
833 GdkRectangle *area,
834 gpointer user_data)
836 gdk_draw_arc(widget->window,
837 item->selected ? widget->style->white_gc
838 : widget->style->black_gc,
839 TRUE,
840 area->x, area->y,
841 COLLECTION(widget)->item_width, area->height,
842 0, 360 * 64);
846 static gboolean default_test_point(Collection *collection,
847 int point_x, int point_y,
848 CollectionItem *item,
849 int width, int height,
850 gpointer user_data)
852 float f_x, f_y;
854 /* Convert to point in unit circle */
855 f_x = ((float) point_x / width) - 0.5;
856 f_y = ((float) point_y / height) - 0.5;
858 return (f_x * f_x) + (f_y * f_y) <= .25;
861 static void collection_set_arg( GtkObject *object,
862 GtkArg *arg,
863 guint arg_id)
865 Collection *collection;
867 collection = COLLECTION(object);
869 switch (arg_id)
871 case ARG_VADJUSTMENT:
872 collection_set_adjustment(collection,
873 GTK_VALUE_POINTER(*arg));
874 break;
875 default:
876 break;
880 static void collection_set_adjustment(Collection *collection,
881 GtkAdjustment *vadj)
883 if (vadj)
884 g_return_if_fail (GTK_IS_ADJUSTMENT (vadj));
885 else
886 vadj = GTK_ADJUSTMENT(gtk_adjustment_new(0.0,
887 0.0, 0.0,
888 0.0, 0.0, 0.0));
889 if (collection->vadj && (collection->vadj != vadj))
891 #ifndef GTK2
892 gtk_signal_disconnect_by_data(GTK_OBJECT(collection->vadj),
893 collection);
894 #endif
895 gtk_object_unref(GTK_OBJECT(collection->vadj));
898 if (collection->vadj != vadj)
900 collection->vadj = vadj;
901 gtk_object_ref(GTK_OBJECT(collection->vadj));
902 gtk_object_sink(GTK_OBJECT(collection->vadj));
904 #ifndef GTK2
905 gtk_signal_connect(GTK_OBJECT(collection->vadj),
906 "changed",
907 (GtkSignalFunc) collection_adjustment,
908 collection);
909 gtk_signal_connect(GTK_OBJECT(collection->vadj),
910 "value_changed",
911 (GtkSignalFunc) collection_adjustment,
912 collection);
913 /* Is this used for anything? */
914 gtk_signal_connect(GTK_OBJECT(collection->vadj),
915 "disconnect",
916 (GtkSignalFunc) collection_disconnect,
917 collection);
918 collection_adjustment(vadj, collection);
919 #endif
923 static void collection_get_arg( GtkObject *object,
924 GtkArg *arg,
925 guint arg_id)
927 Collection *collection;
929 collection = COLLECTION(object);
931 switch (arg_id)
933 case ARG_VADJUSTMENT:
934 GTK_VALUE_POINTER(*arg) = collection->vadj;
935 break;
936 default:
937 arg->type = GTK_TYPE_INVALID;
938 break;
942 #ifndef GTK2
943 /* Something about the adjustment has changed */
944 static void collection_adjustment(GtkAdjustment *adjustment,
945 Collection *collection)
947 gint diff;
949 g_return_if_fail(adjustment != NULL);
950 g_return_if_fail(GTK_IS_ADJUSTMENT(adjustment));
951 g_return_if_fail(collection != NULL);
952 g_return_if_fail(IS_COLLECTION(collection));
954 diff = ((gint) adjustment->value) - collection->last_scroll;
956 if (diff)
958 collection->last_scroll = adjustment->value;
960 if (collection->lasso_box)
962 remove_lasso_box(collection);
963 collection->drag_box_y[0] -= diff;
964 scroll_by(collection, diff);
965 add_lasso_box(collection);
967 else
968 scroll_by(collection, diff);
972 static void collection_disconnect(GtkAdjustment *adjustment,
973 Collection *collection)
975 g_return_if_fail(adjustment != NULL);
976 g_return_if_fail(GTK_IS_ADJUSTMENT(adjustment));
977 g_return_if_fail(collection != NULL);
978 g_return_if_fail(IS_COLLECTION(collection));
980 collection_set_adjustment(collection, NULL);
983 static void set_vadjustment(Collection *collection)
985 GtkWidget *widget;
986 gint height;
987 int cols, rows;
989 widget = GTK_WIDGET(collection);
991 if (!GTK_WIDGET_REALIZED(widget))
992 return;
994 gdk_window_get_size(widget->window, NULL, &height);
995 cols = collection->columns;
996 rows = (collection->number_of_items + cols - 1) / cols;
998 collection->vadj->lower = 0.0;
999 collection->vadj->upper = collection->item_height * rows;
1000 if (!collection->vadj->upper)
1001 collection->vadj->upper = 1;
1003 collection->vadj->step_increment =
1004 MIN(collection->vadj->upper, collection->item_height / 4);
1006 collection->vadj->page_increment =
1007 MIN(collection->vadj->upper,
1008 height - 5.0);
1010 collection->vadj->page_size = MIN(collection->vadj->upper, height);
1012 collection->vadj->value = MIN(collection->vadj->value,
1013 collection->vadj->upper - collection->vadj->page_size);
1015 collection->vadj->value = MAX(collection->vadj->value, 0.0);
1017 gtk_signal_emit_by_name(GTK_OBJECT(collection->vadj), "changed");
1020 static void collection_draw(GtkWidget *widget, GdkRectangle *area)
1022 Collection *collection;
1024 g_return_if_fail(widget != NULL);
1025 g_return_if_fail(IS_COLLECTION(widget));
1026 g_return_if_fail(area != NULL); /* Not actually used */
1028 collection = COLLECTION(widget);
1030 collection_paint(collection, area);
1032 #endif
1034 /* Change the adjustment by this amount. Bounded. */
1035 static void diff_vpos(Collection *collection, int diff)
1037 int value = collection->vadj->value + diff;
1039 value = CLAMP(value, 0,
1040 collection->vadj->upper - collection->vadj->page_size);
1041 gtk_adjustment_set_value(collection->vadj, value);
1044 static gint collection_expose(GtkWidget *widget, GdkEventExpose *event)
1046 Collection *collection;
1048 g_return_val_if_fail(widget != NULL, FALSE);
1049 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1050 g_return_val_if_fail(event != NULL, FALSE);
1052 collection = COLLECTION(widget);
1054 #ifndef GTK2
1055 clear_area(collection, &event->area);
1056 #endif
1058 collection_paint(collection, &event->area);
1060 return FALSE;
1063 #ifndef GTK2
1064 /* Positive makes the contents go move up the screen */
1065 static void scroll_by(Collection *collection, gint diff)
1067 GtkWidget *widget;
1068 gint width, height;
1069 guint from_y, to_y;
1070 guint amount;
1071 GdkRectangle new_area;
1073 if (diff == 0)
1074 return;
1076 widget = GTK_WIDGET(collection);
1078 if (collection->lasso_box)
1079 abort_lasso(collection);
1081 gdk_window_get_size(widget->window, &width, &height);
1082 new_area.x = 0;
1083 new_area.width = width;
1085 if (diff > 0)
1087 amount = diff;
1088 from_y = amount;
1089 to_y = 0;
1090 new_area.y = height - amount;
1092 else
1094 amount = -diff;
1095 from_y = 0;
1096 to_y = amount;
1097 new_area.y = 0;
1100 new_area.height = amount;
1102 if (amount < height)
1104 static GdkGC *expo_gc = NULL;
1106 if (!expo_gc)
1108 expo_gc = gdk_gc_new(widget->window);
1109 gdk_gc_copy(expo_gc, widget->style->white_gc);
1110 gdk_gc_set_exposures(expo_gc, TRUE);
1113 gdk_draw_pixmap(widget->window,
1114 expo_gc,
1115 widget->window,
1117 from_y,
1119 to_y,
1120 width,
1121 height - amount);
1122 /* We have to redraw everything because any pending
1123 * expose events now contain invalid areas.
1124 * Don't need to clear the area first though...
1126 if (collection->paint_level < PAINT_OVERWRITE)
1127 collection->paint_level = PAINT_OVERWRITE;
1129 else
1130 collection->paint_level = PAINT_CLEAR;
1132 clear_area(collection, &new_area);
1133 collection_paint(collection, NULL);
1135 #endif
1137 static void resize_arrays(Collection *collection, guint new_size)
1139 g_return_if_fail(collection != NULL);
1140 g_return_if_fail(IS_COLLECTION(collection));
1141 g_return_if_fail(new_size >= collection->number_of_items);
1143 collection->items = g_realloc(collection->items,
1144 sizeof(CollectionItem) * new_size);
1145 collection->array_size = new_size;
1148 static gint collection_key_press(GtkWidget *widget, GdkEventKey *event)
1150 Collection *collection;
1151 int item;
1152 int key;
1154 g_return_val_if_fail(widget != NULL, FALSE);
1155 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1156 g_return_val_if_fail(event != NULL, FALSE);
1158 collection = (Collection *) widget;
1159 item = collection->cursor_item;
1161 key = event->keyval;
1162 if (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK))
1164 if (key == GDK_Left || key == GDK_Right || \
1165 key == GDK_Up || key == GDK_Down)
1166 return TRUE;
1167 return FALSE;
1170 switch (key)
1172 case GDK_Left:
1173 collection_move_cursor(collection, 0, -1);
1174 break;
1175 case GDK_Right:
1176 collection_move_cursor(collection, 0, 1);
1177 break;
1178 case GDK_Up:
1179 collection_move_cursor(collection, -1, 0);
1180 break;
1181 case GDK_Down:
1182 collection_move_cursor(collection, 1, 0);
1183 break;
1184 case GDK_Home:
1185 collection_set_cursor_item(collection, 0);
1186 break;
1187 case GDK_End:
1188 collection_set_cursor_item(collection,
1189 MAX((gint) collection->number_of_items - 1, 0));
1190 break;
1191 case GDK_Page_Up:
1193 int first, last;
1194 get_visible_limits(collection, &first, &last);
1195 collection_move_cursor(collection, first - last - 1, 0);
1196 break;
1198 case GDK_Page_Down:
1200 int first, last;
1201 get_visible_limits(collection, &first, &last);
1202 collection_move_cursor(collection, last - first + 1, 0);
1203 break;
1205 case GDK_Escape:
1206 collection_set_cursor_item(collection, -1);
1207 collection_clear_selection(collection);
1208 return FALSE; /* Pass it on */
1209 case ' ':
1210 if (item >=0 && item < collection->number_of_items)
1212 collection_toggle_item(collection, item);
1213 if (item < collection->number_of_items - 1)
1214 collection_set_cursor_item(collection,
1215 item + 1);
1217 break;
1218 default:
1219 return FALSE;
1222 return TRUE;
1225 /* Wheel mouse scrolling */
1226 #ifdef GTK2
1227 static gint collection_scroll_event(GtkWidget *widget, GdkEventScroll *event)
1228 #else
1229 static gint collection_scroll_event(GtkWidget *widget, GdkEventButton *event)
1230 #endif
1232 Collection *collection;
1233 int diff = 0;
1235 g_return_val_if_fail(widget != NULL, FALSE);
1236 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1237 g_return_val_if_fail(event != NULL, FALSE);
1239 collection = COLLECTION(widget);
1241 #ifdef GTK2
1242 if (event->direction == GDK_SCROLL_UP)
1243 diff = -1;
1244 else if (event->direction == GDK_SCROLL_DOWN)
1245 diff = 1;
1246 else
1247 return FALSE;
1248 #else
1249 if (event->button <= 3 || event->type != GDK_BUTTON_PRESS)
1250 return FALSE; /* Only deal with wheel events here */
1252 if (event->button == 4)
1253 diff = -1;
1254 else if (event->button == 5)
1255 diff = 1;
1256 else
1257 return FALSE;
1258 #endif
1260 if (diff)
1262 int old_value = collection->vadj->value;
1263 int new_value = 0;
1264 gboolean box = collection->lasso_box;
1265 int step = collection->vadj->page_increment / 2;
1267 new_value = CLAMP(old_value + diff * step, 0.0,
1268 collection->vadj->upper
1269 - collection->vadj->page_size);
1270 diff = new_value - old_value;
1271 if (diff)
1273 if (box)
1275 remove_lasso_box(collection);
1276 collection->drag_box_y[0] -= diff;
1278 gtk_adjustment_set_value(collection->vadj, new_value);
1279 if (box)
1280 add_lasso_box(collection);
1284 return TRUE;
1287 /* 'from' and 'to' are pixel positions. 'step' is the size of each item.
1288 * Returns the index of the first item covered, and the number of items.
1290 #ifdef GTK2
1291 static void get_range(int from, int to, int step, gint *pos, gint *len)
1292 #else
1293 static void get_range(int from, int to, int step, short *pos, short *len)
1294 #endif
1296 if (from > to)
1298 int tmp = to;
1299 to = from;
1300 from = tmp;
1303 from = (from + step / 4) / step; /* First item */
1304 to = (to + step - step / 4) / step; /* Last item (inclusive) */
1306 *pos = MAX(from, 0);
1307 *len = to - *pos;
1310 /* Fills in the area with a rectangle corresponding to the current
1311 * size of the lasso box (units of items, not pixels).
1313 * The box will only span valid columns, but the total number
1314 * of items is not taken into account (rows or cols).
1316 static void find_lasso_area(Collection *collection, GdkRectangle *area)
1318 int scroll = 0;
1319 int cols = collection->columns;
1320 int dx = collection->drag_box_x[0] - collection->drag_box_x[1];
1321 int dy = collection->drag_box_y[0] - collection->drag_box_y[1];
1323 if (ABS(dx) < 8 && ABS(dy) < 8)
1325 /* Didn't move far enough - ignore */
1326 area->x = area->y = 0;
1327 area->width = 0;
1328 area->height = 0;
1329 return;
1332 #ifndef GTK2
1333 scroll = collection->vadj->value;
1334 #endif
1336 get_range(collection->drag_box_x[0],
1337 collection->drag_box_x[1],
1338 collection->item_width,
1339 &area->x,
1340 &area->width);
1342 if (area->x >= cols)
1343 area->width = 0;
1344 else if (area->x + area->width > cols)
1345 area->width = cols - area->x;
1347 get_range(collection->drag_box_y[0] + scroll,
1348 collection->drag_box_y[1] + scroll,
1349 collection->item_height,
1350 &area->y,
1351 &area->height);
1354 static void collection_process_area(Collection *collection,
1355 GdkRectangle *area,
1356 GdkFunction fn,
1357 guint32 time)
1359 int x, y;
1360 guint32 stacked_time;
1361 int item;
1362 gboolean changed = FALSE;
1363 guint old_selected;
1365 g_return_if_fail(fn == GDK_SET || fn == GDK_INVERT);
1367 old_selected = collection->number_selected;
1369 stacked_time = current_event_time;
1370 current_event_time = time;
1372 collection->block_selection_changed++;
1374 for (y = area->y; y < area->y + area->height; y++)
1376 item = y * collection->columns + area->x;
1378 for (x = area->x; x < area->x + area->width; x++)
1380 if (item >= collection->number_of_items)
1381 goto out;
1383 if (fn == GDK_INVERT)
1384 collection_item_set_selected(collection, item,
1385 !collection->items[item].selected,
1386 FALSE);
1387 else
1388 collection_item_set_selected(collection, item,
1389 TRUE, FALSE);
1391 changed = TRUE;
1392 item++;
1396 out:
1397 if (collection->number_selected && !old_selected)
1398 gtk_signal_emit(GTK_OBJECT(collection),
1399 collection_signals[GAIN_SELECTION],
1400 current_event_time);
1401 else if (!collection->number_selected && old_selected)
1402 gtk_signal_emit(GTK_OBJECT(collection),
1403 collection_signals[LOSE_SELECTION],
1404 current_event_time);
1406 collection_unblock_selection_changed(collection,
1407 current_event_time, changed);
1408 current_event_time = stacked_time;
1411 static gint collection_motion_notify(GtkWidget *widget,
1412 GdkEventMotion *event)
1414 Collection *collection;
1415 gint x, y;
1417 g_return_val_if_fail(widget != NULL, FALSE);
1418 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1419 g_return_val_if_fail(event != NULL, FALSE);
1421 collection = COLLECTION(widget);
1423 if (!collection->lasso_box)
1424 return FALSE;
1426 if (event->window != widget->window)
1427 gdk_window_get_pointer(widget->window, &x, &y, NULL);
1428 else
1430 x = event->x;
1431 y = event->y;
1434 remove_lasso_box(collection);
1435 collection->drag_box_x[1] = x;
1436 collection->drag_box_y[1] = y;
1437 add_lasso_box(collection);
1438 return TRUE;
1441 static void add_lasso_box(Collection *collection)
1443 g_return_if_fail(collection != NULL);
1444 g_return_if_fail(IS_COLLECTION(collection));
1445 g_return_if_fail(collection->lasso_box == FALSE);
1447 collection->lasso_box = TRUE;
1448 draw_lasso_box(collection);
1451 static void draw_lasso_box(Collection *collection)
1453 GtkWidget *widget;
1454 int x, y, width, height;
1456 widget = GTK_WIDGET(collection);
1458 x = MIN(collection->drag_box_x[0], collection->drag_box_x[1]);
1459 y = MIN(collection->drag_box_y[0], collection->drag_box_y[1]);
1460 width = abs(collection->drag_box_x[1] - collection->drag_box_x[0]);
1461 height = abs(collection->drag_box_y[1] - collection->drag_box_y[0]);
1463 gdk_draw_rectangle(widget->window, collection->xor_gc, FALSE,
1464 x, y, width, height);
1467 static void abort_lasso(Collection *collection)
1469 if (collection->lasso_box)
1471 remove_lasso_box(collection);
1472 collection_set_autoscroll(collection, FALSE);
1476 static void remove_lasso_box(Collection *collection)
1478 g_return_if_fail(collection != NULL);
1479 g_return_if_fail(IS_COLLECTION(collection));
1480 g_return_if_fail(collection->lasso_box == TRUE);
1482 draw_lasso_box(collection);
1484 collection->lasso_box = FALSE;
1486 return;
1489 /* Make sure that 'item' is fully visible (vertically), scrolling if not. */
1490 static void scroll_to_show(Collection *collection, int item)
1492 int first, last, row;
1494 g_return_if_fail(collection != NULL);
1495 g_return_if_fail(IS_COLLECTION(collection));
1497 row = item / collection->columns;
1498 get_visible_limits(collection, &first, &last);
1500 if (row <= first)
1502 gtk_adjustment_set_value(collection->vadj,
1503 row * collection->item_height);
1505 else if (row >= last)
1507 GtkWidget *widget = (GtkWidget *) collection;
1508 gint height;
1510 if (GTK_WIDGET_REALIZED(widget))
1512 height = collection->vadj->page_size;
1513 gtk_adjustment_set_value(collection->vadj,
1514 (row + 1) * collection->item_height - height);
1519 /* Return the first and last rows which are [partly] visible. Does not
1520 * ensure that the rows actually exist (contain items).
1522 static void get_visible_limits(Collection *collection, int *first, int *last)
1524 GtkWidget *widget = (GtkWidget *) collection;
1525 gint scroll = 0, height;
1527 g_return_if_fail(collection != NULL);
1528 g_return_if_fail(IS_COLLECTION(collection));
1529 g_return_if_fail(first != NULL && last != NULL);
1531 if (!GTK_WIDGET_REALIZED(widget))
1533 *first = 0;
1534 *last = 0;
1536 else
1538 scroll = collection->vadj->value;
1539 height = collection->vadj->page_size;
1541 *first = MAX(scroll / collection->item_height, 0);
1542 *last = (scroll + height - 1) /collection->item_height;
1544 if (*last < *first)
1545 *last = *first;
1549 /* Cancel the current wink effect. */
1550 static void cancel_wink(Collection *collection)
1552 gint item;
1554 g_return_if_fail(collection != NULL);
1555 g_return_if_fail(IS_COLLECTION(collection));
1556 g_return_if_fail(collection->wink_item != -1);
1558 item = collection->wink_item;
1560 collection->wink_item = -1;
1561 gtk_timeout_remove(collection->wink_timeout);
1563 collection_draw_item(collection, item, TRUE);
1566 /* Draw/undraw a box around collection->wink_item */
1567 static void invert_wink(Collection *collection)
1569 GdkRectangle area;
1570 gint row, col;
1572 g_return_if_fail(collection->wink_item >= 0);
1574 if (!GTK_WIDGET_REALIZED(GTK_WIDGET(collection)))
1575 return;
1577 col = collection->wink_item % collection->columns;
1578 row = collection->wink_item / collection->columns;
1579 collection_get_item_area(collection, row, col, &area);
1581 gdk_draw_rectangle(((GtkWidget *) collection)->window,
1582 collection->xor_gc, FALSE,
1583 area.x, area.y,
1584 collection->item_width - 1,
1585 area.height - 1);
1588 static gboolean wink_timeout(Collection *collection)
1590 gint item;
1592 g_return_val_if_fail(collection != NULL, FALSE);
1593 g_return_val_if_fail(IS_COLLECTION(collection), FALSE);
1594 g_return_val_if_fail(collection->wink_item != -1, FALSE);
1596 item = collection->wink_item;
1598 if (collection->winks_left-- > 0)
1600 invert_wink(collection);
1601 return TRUE;
1604 collection->wink_item = -1;
1606 collection_draw_item(collection, item, TRUE);
1608 return FALSE;
1611 #ifndef GTK2
1612 static gint focus_in(GtkWidget *widget, GdkEventFocus *event)
1614 g_return_val_if_fail(widget != NULL, FALSE);
1615 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1616 g_return_val_if_fail(event != NULL, FALSE);
1618 GTK_WIDGET_SET_FLAGS(widget, GTK_HAS_FOCUS);
1619 gtk_widget_draw_focus(widget);
1621 return FALSE;
1624 static gint focus_out(GtkWidget *widget, GdkEventFocus *event)
1626 g_return_val_if_fail(widget != NULL, FALSE);
1627 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
1628 g_return_val_if_fail(event != NULL, FALSE);
1630 GTK_WIDGET_UNSET_FLAGS(widget, GTK_HAS_FOCUS);
1631 gtk_widget_draw_focus(widget);
1633 return FALSE;
1635 #endif
1637 /* This is called frequently while auto_scroll is on.
1638 * Checks the pointer position and scrolls the window if it's
1639 * near the top or bottom.
1641 static gboolean as_timeout(Collection *collection)
1643 GdkWindow *window = GTK_WIDGET(collection)->window;
1644 gint x, y, w, h;
1645 GdkModifierType mask;
1646 int diff = 0;
1648 gdk_window_get_pointer(window, &x, &y, &mask);
1649 gdk_window_get_size(window, &w, NULL);
1651 h = collection->vadj->page_size;
1652 #ifdef GTK2
1653 y -= collection->vadj->value;
1654 #endif
1656 if ((x < 0 || x > w || y < 0 || y > h) && !collection->lasso_box)
1658 collection->auto_scroll = -1;
1659 return FALSE; /* Out of window - stop */
1662 if (y < AUTOSCROLL_STEP)
1663 diff = y - AUTOSCROLL_STEP;
1664 else if (y > h - AUTOSCROLL_STEP)
1665 diff = AUTOSCROLL_STEP + y - h;
1667 if (diff)
1668 diff_vpos(collection, diff);
1670 return TRUE;
1673 /* Change the selected state of an item.
1674 * Send GAIN/LOSE signals if 'signal' is TRUE.
1675 * Send SELECTION_CHANGED unless blocked.
1676 * Updates number_selected and redraws the item.
1678 static void collection_item_set_selected(Collection *collection,
1679 gint item,
1680 gboolean selected,
1681 gboolean signal)
1683 g_return_if_fail(collection != NULL);
1684 g_return_if_fail(IS_COLLECTION(collection));
1685 g_return_if_fail(item >= 0 && item < collection->number_of_items);
1687 if (collection->items[item].selected == selected)
1688 return;
1690 collection->items[item].selected = selected;
1691 collection_draw_item(collection, item, TRUE);
1693 if (selected)
1695 collection->number_selected++;
1696 if (signal && collection->number_selected == 1)
1697 gtk_signal_emit(GTK_OBJECT(collection),
1698 collection_signals[GAIN_SELECTION],
1699 current_event_time);
1701 else
1703 collection->number_selected--;
1704 if (signal && collection->number_selected == 0)
1705 gtk_signal_emit(GTK_OBJECT(collection),
1706 collection_signals[LOSE_SELECTION],
1707 current_event_time);
1710 EMIT_SELECTION_CHANGED(collection, current_event_time);
1713 /* Functions for managing collections */
1715 /* Remove all objects from the collection */
1716 void collection_clear(Collection *collection)
1718 collection_delete_if(collection, NULL, NULL);
1721 /* Inserts a new item at the end. The new item is unselected, and its
1722 * number is returned.
1724 gint collection_insert(Collection *collection, gpointer data, gpointer view)
1726 int item;
1728 g_return_val_if_fail(IS_COLLECTION(collection), -1);
1730 item = collection->number_of_items;
1732 if (item >= collection->array_size)
1733 resize_arrays(collection, item + (item >> 1));
1735 collection->items[item].data = data;
1736 collection->items[item].view_data = view;
1737 collection->items[item].selected = FALSE;
1739 collection->number_of_items++;
1741 #ifdef GTK2
1742 gtk_widget_queue_resize(GTK_WIDGET(collection));
1743 #else
1744 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection)))
1745 set_vadjustment(collection);
1746 #endif
1748 collection_draw_item(collection, item, FALSE);
1750 return item;
1753 void collection_unselect_item(Collection *collection, gint item)
1755 collection_item_set_selected(collection, item, FALSE, TRUE);
1758 void collection_select_item(Collection *collection, gint item)
1760 collection_item_set_selected(collection, item, TRUE, TRUE);
1763 void collection_toggle_item(Collection *collection, gint item)
1765 collection_item_set_selected(collection, item,
1766 !collection->items[item].selected, TRUE);
1769 /* Select all items in the collection */
1770 void collection_select_all(Collection *collection)
1772 GtkWidget *widget;
1773 int item = 0;
1775 g_return_if_fail(collection != NULL);
1776 g_return_if_fail(IS_COLLECTION(collection));
1778 widget = GTK_WIDGET(collection);
1780 if (collection->number_selected == collection->number_of_items)
1781 return; /* Nothing to do */
1783 while (collection->number_selected < collection->number_of_items)
1785 while (collection->items[item].selected)
1786 item++;
1788 collection->items[item].selected = TRUE;
1789 collection_draw_item(collection, item, TRUE);
1790 item++;
1792 collection->number_selected++;
1795 gtk_signal_emit(GTK_OBJECT(collection),
1796 collection_signals[GAIN_SELECTION],
1797 current_event_time);
1798 EMIT_SELECTION_CHANGED(collection, current_event_time);
1801 /* Toggle all items in the collection */
1802 void collection_invert_selection(Collection *collection)
1804 int item;
1806 g_return_if_fail(collection != NULL);
1807 g_return_if_fail(IS_COLLECTION(collection));
1809 if (collection->number_selected == 0)
1811 collection_select_all(collection);
1812 return;
1814 else if (collection->number_of_items == collection->number_selected)
1816 collection_clear_selection(collection);
1817 return;
1820 for (item = 0; item < collection->number_of_items; item++)
1821 collection->items[item].selected =
1822 !collection->items[item].selected;
1824 collection->number_selected = collection->number_of_items -
1825 collection->number_selected;
1827 /* Have to redraw everything... */
1828 #ifndef GTK2
1829 collection->paint_level = PAINT_CLEAR;
1830 #endif
1831 gtk_widget_queue_clear(GTK_WIDGET(collection));
1833 EMIT_SELECTION_CHANGED(collection, current_event_time);
1836 /* Unselect all items except number item, which is selected (-1 to unselect
1837 * everything).
1839 void collection_clear_except(Collection *collection, gint item)
1841 GtkWidget *widget;
1842 int i = 0;
1843 int end; /* Selected items to end up with */
1845 g_return_if_fail(collection != NULL);
1846 g_return_if_fail(IS_COLLECTION(collection));
1847 g_return_if_fail(item >= -1 && item < collection->number_of_items);
1849 widget = GTK_WIDGET(collection);
1851 if (item == -1)
1852 end = 0;
1853 else
1855 collection_select_item(collection, item);
1856 end = 1;
1859 if (collection->number_selected == 0)
1860 return;
1862 while (collection->number_selected > end)
1864 while (i == item || !collection->items[i].selected)
1865 i++;
1867 collection->items[i].selected = FALSE;
1868 collection_draw_item(collection, i, TRUE);
1869 i++;
1871 collection->number_selected--;
1874 if (end == 0)
1875 gtk_signal_emit(GTK_OBJECT(collection),
1876 collection_signals[LOSE_SELECTION],
1877 current_event_time);
1878 EMIT_SELECTION_CHANGED(collection, current_event_time);
1881 /* Unselect all items in the collection */
1882 void collection_clear_selection(Collection *collection)
1884 g_return_if_fail(collection != NULL);
1885 g_return_if_fail(IS_COLLECTION(collection));
1887 collection_clear_except(collection, -1);
1890 /* Force a redraw of the specified item, if it is visible */
1891 void collection_draw_item(Collection *collection, gint item, gboolean blank)
1893 GdkRectangle area;
1894 GtkWidget *widget;
1895 int row, col;
1896 #ifndef GTK2
1897 gint height;
1898 #endif
1900 g_return_if_fail(collection != NULL);
1901 g_return_if_fail(IS_COLLECTION(collection));
1902 g_return_if_fail(item >= 0 &&
1903 (item == 0 || item < collection->number_of_items));
1905 widget = GTK_WIDGET(collection);
1906 if (!GTK_WIDGET_REALIZED(widget))
1907 return;
1909 col = item % collection->columns;
1910 row = item / collection->columns;
1912 collection_get_item_area(collection, row, col, &area);
1914 #ifdef GTK2
1915 gdk_window_invalidate_rect(widget->window, &area, FALSE);
1916 #else
1917 if (area.y + area.height < 0)
1918 return;
1920 gdk_window_get_size(widget->window, NULL, &height);
1922 if (area.y > height)
1923 return;
1925 if (blank || collection->lasso_box)
1926 clear_area(collection, &area);
1928 draw_one_item(collection, item, &area);
1930 if (collection->lasso_box)
1932 gdk_gc_set_clip_rectangle(collection->xor_gc, &area);
1933 draw_lasso_box(collection);
1934 gdk_gc_set_clip_rectangle(collection->xor_gc, NULL);
1936 #endif
1939 void collection_set_item_size(Collection *collection, int width, int height)
1941 GtkWidget *widget;
1943 g_return_if_fail(collection != NULL);
1944 g_return_if_fail(IS_COLLECTION(collection));
1945 g_return_if_fail(width > 4 && height > 4);
1947 if (collection->item_width == width &&
1948 collection->item_height == height)
1949 return;
1951 widget = GTK_WIDGET(collection);
1953 collection->item_width = width;
1954 collection->item_height = height;
1956 if (GTK_WIDGET_REALIZED(widget))
1958 gint window_width;
1960 gdk_window_get_size(widget->window, &window_width, NULL);
1961 collection->columns = MAX(window_width / collection->item_width,
1963 #ifndef GTK2
1964 collection->paint_level = PAINT_CLEAR;
1965 set_vadjustment(collection);
1966 #endif
1967 if (collection->cursor_item != -1)
1968 scroll_to_show(collection, collection->cursor_item);
1969 gtk_widget_queue_draw(widget);
1972 #ifdef GTK2
1973 gtk_widget_queue_resize(GTK_WIDGET(collection));
1974 #endif
1977 /* Cursor is positioned on item with the same data as before the sort.
1978 * Same for the wink item.
1980 void collection_qsort(Collection *collection,
1981 int (*compar)(const void *, const void *))
1983 int cursor, wink, items, wink_on_map;
1984 gpointer cursor_data = NULL;
1985 gpointer wink_data = NULL;
1986 gpointer wink_on_map_data = NULL;
1987 CollectionItem *array;
1988 int i;
1990 g_return_if_fail(collection != NULL);
1991 g_return_if_fail(IS_COLLECTION(collection));
1992 g_return_if_fail(compar != NULL);
1994 /* Check to see if it needs sorting (saves redrawing) */
1995 if (collection->number_of_items < 2)
1996 return;
1998 array = collection->items;
1999 for (i = 1; i < collection->number_of_items; i++)
2001 if (compar(&array[i - 1], &array[i]) > 0)
2002 break;
2004 if (i == collection->number_of_items)
2005 return; /* Already sorted */
2007 items = collection->number_of_items;
2009 wink_on_map = collection->wink_on_map;
2010 if (wink_on_map >= 0 && wink_on_map < items)
2012 wink_on_map_data = collection->items[wink_on_map].data;
2013 collection->wink_on_map = -1;
2015 else
2016 wink = -1;
2018 wink = collection->wink_item;
2019 if (wink >= 0 && wink < items)
2021 wink_data = collection->items[wink].data;
2022 collection->wink_item = -1;
2024 else
2025 wink = -1;
2027 cursor = collection->cursor_item;
2028 if (cursor >= 0 && cursor < items)
2029 cursor_data = collection->items[cursor].data;
2030 else
2031 cursor = -1;
2033 qsort(collection->items, items, sizeof(collection->items[0]), compar);
2035 if (cursor > -1 || wink > -1 || wink_on_map > -1)
2037 int item;
2039 for (item = 0; item < items; item++)
2041 if (collection->items[item].data == cursor_data)
2042 collection_set_cursor_item(collection, item);
2043 if (collection->items[item].data == wink_on_map_data)
2044 collection->wink_on_map = item;
2045 if (collection->items[item].data == wink_data)
2047 collection->cursor_item_old = item;
2048 collection->wink_item = item;
2049 scroll_to_show(collection, item);
2054 #ifndef GTK2
2055 collection->paint_level = PAINT_CLEAR;
2056 #endif
2058 gtk_widget_queue_draw(GTK_WIDGET(collection));
2061 /* Find an item in an unsorted collection.
2062 * Returns the item number, or -1 if not found.
2064 int collection_find_item(Collection *collection, gpointer data,
2065 int (*compar)(const void *, const void *))
2067 int i;
2069 g_return_val_if_fail(collection != NULL, -1);
2070 g_return_val_if_fail(IS_COLLECTION(collection), -1);
2071 g_return_val_if_fail(compar != NULL, -1);
2073 for (i = 0; i < collection->number_of_items; i++)
2074 if (compar(&collection->items[i].data, &data) == 0)
2075 return i;
2077 return -1;
2080 /* Return the number of the item under the point (x,y), or -1 for none.
2081 * This may call your test_point callback. The point is relative to the
2082 * collection's origin.
2084 int collection_get_item(Collection *collection, int x, int y)
2086 int scroll = 0;
2087 int row, col;
2088 int width;
2089 int item;
2091 g_return_val_if_fail(collection != NULL, -1);
2093 #ifndef GTK2
2094 scroll = collection->vadj->value;
2095 #endif
2096 col = x / collection->item_width;
2097 row = (y + scroll) / collection->item_height;
2099 if (col >= collection->columns)
2100 col = collection->columns - 1;
2102 if (col < 0 || row < 0)
2103 return -1;
2105 if (col == collection->columns - 1)
2106 width = collection->item_width << 1;
2107 else
2108 width = collection->item_width;
2110 item = col + row * collection->columns;
2111 if (item >= collection->number_of_items
2113 !collection->test_point(collection,
2114 x - col * collection->item_width,
2115 y - row * collection->item_height
2116 + scroll,
2117 &collection->items[item],
2118 width,
2119 collection->item_height,
2120 collection->cb_user_data))
2122 return -1;
2125 return item;
2128 /* Set the cursor/highlight over the given item. Passing -1
2129 * hides the cursor. As a special case, you may set the cursor item
2130 * to zero when there are no items.
2132 void collection_set_cursor_item(Collection *collection, gint item)
2134 int old_item;
2136 g_return_if_fail(collection != NULL);
2137 g_return_if_fail(IS_COLLECTION(collection));
2138 g_return_if_fail(item >= -1 &&
2139 (item < collection->number_of_items || item == 0));
2141 old_item = collection->cursor_item;
2143 if (old_item == item)
2144 return;
2146 collection->cursor_item = item;
2148 if (old_item != -1)
2149 collection_draw_item(collection, old_item, TRUE);
2151 if (item != -1)
2153 collection_draw_item(collection, item, TRUE);
2154 if (collection->auto_scroll == -1)
2155 scroll_to_show(collection, item);
2157 else if (old_item != -1)
2158 collection->cursor_item_old = old_item;
2161 /* Briefly highlight an item to draw the user's attention to it.
2162 * -1 cancels the effect, as does deleting items, sorting the collection
2163 * or starting a new wink effect.
2164 * Otherwise, the effect will cancel itself after a short pause.
2165 * */
2166 void collection_wink_item(Collection *collection, gint item)
2168 g_return_if_fail(collection != NULL);
2169 g_return_if_fail(IS_COLLECTION(collection));
2170 g_return_if_fail(item >= -1 && item < collection->number_of_items);
2172 if (collection->wink_item != -1)
2173 cancel_wink(collection);
2174 if (item == -1)
2175 return;
2177 if (!GTK_WIDGET_MAPPED(GTK_WIDGET(collection)))
2179 collection->wink_on_map = item;
2180 return;
2183 collection->cursor_item_old = collection->wink_item = item;
2184 collection->winks_left = MAX_WINKS;
2186 collection->wink_timeout = gtk_timeout_add(70,
2187 (GtkFunction) wink_timeout,
2188 collection);
2189 scroll_to_show(collection, item);
2190 invert_wink(collection);
2192 gdk_flush();
2195 /* Call test(item, data) on each item in the collection.
2196 * Remove all items for which it returns TRUE. test() should
2197 * free the data before returning TRUE. The collection is in an
2198 * inconsistant state during this call (ie, when test() is called).
2200 * If test is NULL, remove all items.
2202 void collection_delete_if(Collection *collection,
2203 gboolean (*test)(gpointer item, gpointer data),
2204 gpointer data)
2206 int in, out = 0;
2207 int selected = 0;
2208 int cursor;
2210 g_return_if_fail(collection != NULL);
2211 g_return_if_fail(IS_COLLECTION(collection));
2213 cursor = collection->cursor_item;
2215 for (in = 0; in < collection->number_of_items; in++)
2217 if (test && !test(collection->items[in].data, data))
2219 /* Keep item */
2220 if (collection->items[in].selected)
2222 collection->items[out].selected = TRUE;
2223 selected++;
2225 else
2226 collection->items[out].selected = FALSE;
2228 collection->items[out].data =
2229 collection->items[in].data;
2230 collection->items[out].view_data =
2231 collection->items[in].view_data;
2232 out++;
2234 else
2236 /* Remove item */
2237 if (collection->free_item)
2238 collection->free_item(collection,
2239 &collection->items[in]);
2241 if (cursor >= in)
2242 cursor--;
2246 if (in != out)
2248 collection->cursor_item = cursor;
2250 if (collection->wink_item != -1)
2252 collection->wink_item = -1;
2253 gtk_timeout_remove(collection->wink_timeout);
2256 collection->number_of_items = out;
2257 if (collection->number_selected && !selected)
2259 /* We've lost all the selected items */
2260 gtk_signal_emit(GTK_OBJECT(collection),
2261 collection_signals[LOSE_SELECTION],
2262 current_event_time);
2265 collection->number_selected = selected;
2266 resize_arrays(collection,
2267 MAX(collection->number_of_items, MINIMUM_ITEMS));
2269 #ifndef GTK2
2270 collection->paint_level = PAINT_CLEAR;
2271 #endif
2273 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection)))
2275 #ifndef GTK2
2276 set_vadjustment(collection);
2277 #endif
2278 gtk_widget_queue_draw(GTK_WIDGET(collection));
2280 #ifdef GTK2
2281 gtk_widget_queue_resize(GTK_WIDGET(collection));
2282 #endif
2286 /* Move the cursor by the given row and column offsets.
2287 * Moving by (0,0) can be used to simply make the cursor appear.
2289 void collection_move_cursor(Collection *collection, int drow, int dcol)
2291 int row, col, item;
2292 int first, last, total_rows;
2294 g_return_if_fail(collection != NULL);
2295 g_return_if_fail(IS_COLLECTION(collection));
2297 get_visible_limits(collection, &first, &last);
2299 item = collection->cursor_item;
2300 if (item == -1)
2302 item = MIN(collection->cursor_item_old,
2303 collection->number_of_items - 1);
2306 if (item == -1)
2308 col = 0;
2309 row = first;
2311 else
2313 row = item / collection->columns;
2314 col = item % collection->columns + dcol;
2316 if (row < first)
2317 row = first;
2318 else if (row > last)
2319 row = last;
2320 else
2321 row = MAX(row + drow, 0);
2324 total_rows = (collection->number_of_items + collection->columns - 1)
2325 / collection->columns;
2327 if (row >= total_rows - 1 && drow > 0)
2329 row = total_rows - 1;
2330 item = col + row * collection->columns;
2331 if (item >= collection->number_of_items - 1)
2333 collection_set_cursor_item(collection,
2334 collection->number_of_items - 1);
2335 return;
2338 if (row < 0)
2339 row = 0;
2341 item = col + row * collection->columns;
2343 if (item >= 0 && item < collection->number_of_items)
2344 collection_set_cursor_item(collection, item);
2347 /* When autoscroll is on, a timer keeps track of the pointer position.
2348 * While it's near the top or bottom of the window, the window scrolls.
2350 * If the mouse buttons are released, or the pointer leaves the window,
2351 * auto_scroll is turned off.
2353 void collection_set_autoscroll(Collection *collection, gboolean auto_scroll)
2355 g_return_if_fail(collection != NULL);
2356 g_return_if_fail(IS_COLLECTION(collection));
2358 if (auto_scroll)
2360 if (collection->auto_scroll != -1)
2361 return; /* Already on! */
2363 collection->auto_scroll = gtk_timeout_add(50,
2364 (GtkFunction) as_timeout,
2365 collection);
2367 else
2369 if (collection->auto_scroll == -1)
2370 return; /* Already off! */
2372 gtk_timeout_remove(collection->auto_scroll);
2373 collection->auto_scroll = -1;
2377 /* Start a lasso box drag */
2378 void collection_lasso_box(Collection *collection, int x, int y)
2380 collection->drag_box_x[0] = x;
2381 collection->drag_box_y[0] = y;
2382 collection->drag_box_x[1] = x;
2383 collection->drag_box_y[1] = y;
2385 collection_set_autoscroll(collection, TRUE);
2386 add_lasso_box(collection);
2389 /* Remove the lasso box. Applies fn to each item inside the box.
2390 * fn may be GDK_INVERT, GDK_SET, GDK_NOOP or GDK_CLEAR.
2392 void collection_end_lasso(Collection *collection, GdkFunction fn)
2394 if (fn != GDK_CLEAR)
2396 GdkRectangle region;
2398 find_lasso_area(collection, &region);
2400 collection_process_area(collection, &region, fn,
2401 GDK_CURRENT_TIME);
2404 abort_lasso(collection);
2407 /* Unblock the selection_changed signal, emitting the signal if the
2408 * block counter reaches zero and emit is TRUE.
2410 void collection_unblock_selection_changed(Collection *collection,
2411 guint time,
2412 gboolean emit)
2414 g_return_if_fail(collection != NULL);
2415 g_return_if_fail(IS_COLLECTION(collection));
2416 g_return_if_fail(collection->block_selection_changed > 0);
2418 collection->block_selection_changed--;
2420 if (emit && !collection->block_selection_changed)
2421 gtk_signal_emit(GTK_OBJECT(collection),
2422 collection_signals[SELECTION_CHANGED], time);