4 * Collection - a GTK+ widget
5 * Copyright (C) 1999, Thomas Leonard, <tal197@ecs.soton.ac.uk>.
7 * The collection widget provides an area for displaying a collection of
8 * objects (such as files). It allows the user to choose a selection of
9 * them and provides signals to allow popping up menus, detecting
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)
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
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
30 #include <gdk/gdkkeysyms.h>
31 #include "collection.h"
35 #define MINIMUM_ITEMS 16
37 int collection_menu_button
= 3;
38 gboolean collection_single_click
= FALSE
;
48 * void open_item(collection, item, item_number, user_data)
49 * User has double clicked on this item.
51 * void drag_selection(collection, motion_event, number_selected, user_data)
52 * User has tried to drag the selection.
54 * void show_menu(collection, button_event, item, user_data)
55 * User has menu-clicked on the collection. 'item' is the number
56 * of the item clicked, or -1 if the click was over the background.
58 * void gain_selection(collection, time, user_data)
59 * We've gone from no selected items to having a selection.
60 * Time is the time of the event that caused the change, or
61 * GDK_CURRENT_TIME if not known.
63 * void lose_selection(collection, time, user_data)
64 * We've dropped to having no selected items.
65 * Time is the time of the event that caused the change, or
66 * GDK_CURRENT_TIME if not known.
78 static guint collection_signals
[LAST_SIGNAL
] = { 0 };
80 static guint32 current_event_time
= GDK_CURRENT_TIME
;
82 static GtkWidgetClass
*parent_class
= NULL
;
84 static GdkCursor
*crosshair
= NULL
;
86 /* Static prototypes */
87 static void draw_one_item(Collection
*collection
,
90 static void collection_class_init(CollectionClass
*class);
91 static void collection_init(Collection
*object
);
92 static void collection_destroy(GtkObject
*object
);
93 static void collection_finalize(GtkObject
*object
);
94 static void collection_realize(GtkWidget
*widget
);
95 static gint
collection_paint(Collection
*collection
,
97 static void collection_size_request(GtkWidget
*widget
,
98 GtkRequisition
*requisition
);
99 static void collection_size_allocate(GtkWidget
*widget
,
100 GtkAllocation
*allocation
);
101 static void collection_set_adjustment(Collection
*collection
,
102 GtkAdjustment
*vadj
);
103 static void collection_set_arg( GtkObject
*object
,
106 static void collection_get_arg( GtkObject
*object
,
109 static void collection_adjustment(GtkAdjustment
*adjustment
,
110 Collection
*collection
);
111 static void collection_disconnect(GtkAdjustment
*adjustment
,
112 Collection
*collection
);
113 static void set_vadjustment(Collection
*collection
);
114 static void collection_draw(GtkWidget
*widget
, GdkRectangle
*area
);
115 static gint
collection_expose(GtkWidget
*widget
, GdkEventExpose
*event
);
116 static void scroll_by(Collection
*collection
, gint diff
);
117 static gint
collection_button_press(GtkWidget
*widget
,
118 GdkEventButton
*event
);
119 static gint
collection_button_release(GtkWidget
*widget
,
120 GdkEventButton
*event
);
121 static void default_draw_item(GtkWidget
*widget
,
122 CollectionItem
*data
,
124 static gboolean
default_test_point(Collection
*collection
,
125 int point_x
, int point_y
,
126 CollectionItem
*data
,
127 int width
, int height
);
128 static gint
collection_motion_notify(GtkWidget
*widget
,
129 GdkEventMotion
*event
);
130 static void add_lasso_box(Collection
*collection
);
131 static void remove_lasso_box(Collection
*collection
);
132 static void draw_lasso_box(Collection
*collection
);
133 static int item_at_row_col(Collection
*collection
, int row
, int col
);
134 static void collection_clear_except(Collection
*collection
, gint exception
);
135 static void cancel_wink(Collection
*collection
);
136 static gint
collection_key_press(GtkWidget
*widget
, GdkEventKey
*event
);
137 static void get_visible_limits(Collection
*collection
, int *first
, int *last
);
138 static void scroll_to_show(Collection
*collection
, int item
);
140 static void draw_one_item(Collection
*collection
, int item
, GdkRectangle
*area
)
142 if (item
< collection
->number_of_items
)
144 collection
->draw_item((GtkWidget
*) collection
,
145 &collection
->items
[item
],
147 if (item
== collection
->wink_item
)
148 gdk_draw_rectangle(((GtkWidget
*) collection
)->window
,
149 ((GtkWidget
*) collection
)->style
->black_gc
,
152 area
->width
- 1, area
->height
- 1);
154 if (item
== collection
->cursor_item
)
156 gdk_draw_rectangle(((GtkWidget
*) collection
)->window
,
157 collection
->target_cb
158 ? ((GtkWidget
*) collection
)->style
->white_gc
159 : ((GtkWidget
*) collection
)->style
->black_gc
,
161 area
->x
+ 1, area
->y
+ 1,
162 area
->width
- 3, area
->height
- 3);
166 GtkType
collection_get_type(void)
168 static guint my_type
= 0;
172 static const GtkTypeInfo my_info
=
176 sizeof(CollectionClass
),
177 (GtkClassInitFunc
) collection_class_init
,
178 (GtkObjectInitFunc
) collection_init
,
179 NULL
, /* Reserved 1 */
180 NULL
, /* Reserved 2 */
181 (GtkClassInitFunc
) NULL
/* base_class_init_func */
184 my_type
= gtk_type_unique(gtk_widget_get_type(),
191 static void collection_class_init(CollectionClass
*class)
193 GtkObjectClass
*object_class
;
194 GtkWidgetClass
*widget_class
;
196 object_class
= (GtkObjectClass
*) class;
197 widget_class
= (GtkWidgetClass
*) class;
199 parent_class
= gtk_type_class(gtk_widget_get_type());
201 gtk_object_add_arg_type("Collection::vadjustment",
203 GTK_ARG_READWRITE
| GTK_ARG_CONSTRUCT
,
206 object_class
->destroy
= collection_destroy
;
207 object_class
->finalize
= collection_finalize
;
209 widget_class
->realize
= collection_realize
;
210 widget_class
->draw
= collection_draw
;
211 widget_class
->expose_event
= collection_expose
;
212 widget_class
->size_request
= collection_size_request
;
213 widget_class
->size_allocate
= collection_size_allocate
;
215 widget_class
->key_press_event
= collection_key_press
;
216 widget_class
->button_press_event
= collection_button_press
;
217 widget_class
->button_release_event
= collection_button_release
;
218 widget_class
->motion_notify_event
= collection_motion_notify
;
219 object_class
->set_arg
= collection_set_arg
;
220 object_class
->get_arg
= collection_get_arg
;
222 class->open_item
= NULL
;
223 class->drag_selection
= NULL
;
224 class->show_menu
= NULL
;
225 class->gain_selection
= NULL
;
226 class->lose_selection
= NULL
;
228 collection_signals
[OPEN_ITEM
] = gtk_signal_new("open_item",
231 GTK_SIGNAL_OFFSET(CollectionClass
,
233 gtk_marshal_NONE__POINTER_UINT
,
237 collection_signals
[DRAG_SELECTION
] = gtk_signal_new("drag_selection",
240 GTK_SIGNAL_OFFSET(CollectionClass
,
242 gtk_marshal_NONE__POINTER_UINT
,
246 collection_signals
[SHOW_MENU
] = gtk_signal_new("show_menu",
249 GTK_SIGNAL_OFFSET(CollectionClass
,
251 gtk_marshal_NONE__POINTER_INT
,
255 collection_signals
[GAIN_SELECTION
] = gtk_signal_new("gain_selection",
258 GTK_SIGNAL_OFFSET(CollectionClass
,
260 gtk_marshal_NONE__UINT
,
263 collection_signals
[LOSE_SELECTION
] = gtk_signal_new("lose_selection",
266 GTK_SIGNAL_OFFSET(CollectionClass
,
268 gtk_marshal_NONE__UINT
,
272 gtk_object_class_add_signals(object_class
,
273 collection_signals
, LAST_SIGNAL
);
276 static void collection_init(Collection
*object
)
278 g_return_if_fail(object
!= NULL
);
279 g_return_if_fail(IS_COLLECTION(object
));
282 crosshair
= gdk_cursor_new(GDK_CROSSHAIR
);
284 GTK_WIDGET_SET_FLAGS(GTK_WIDGET(object
), GTK_CAN_FOCUS
);
286 object
->panel
= FALSE
;
287 object
->number_of_items
= 0;
288 object
->number_selected
= 0;
290 object
->item_width
= 64;
291 object
->item_height
= 64;
293 object
->paint_level
= PAINT_OVERWRITE
;
294 object
->last_scroll
= 0;
296 object
->items
= g_malloc(sizeof(CollectionItem
) * MINIMUM_ITEMS
);
297 object
->cursor_item
= -1;
298 object
->wink_item
= -1;
299 object
->array_size
= MINIMUM_ITEMS
;
300 object
->draw_item
= default_draw_item
;
301 object
->test_point
= default_test_point
;
303 object
->buttons_pressed
= 0;
304 object
->may_drag
= FALSE
;
309 GtkWidget
* collection_new(GtkAdjustment
*vadj
)
312 g_return_val_if_fail(GTK_IS_ADJUSTMENT(vadj
), NULL
);
314 return GTK_WIDGET(gtk_widget_new(collection_get_type(),
319 void collection_set_functions(Collection
*collection
,
320 CollectionDrawFunc draw_item
,
321 CollectionTestFunc test_point
)
325 g_return_if_fail(collection
!= NULL
);
326 g_return_if_fail(IS_COLLECTION(collection
));
328 widget
= GTK_WIDGET(collection
);
331 draw_item
= default_draw_item
;
333 test_point
= default_test_point
;
335 collection
->draw_item
= draw_item
;
336 collection
->test_point
= test_point
;
338 if (GTK_WIDGET_REALIZED(widget
))
340 collection
->paint_level
= PAINT_CLEAR
;
341 gtk_widget_queue_clear(widget
);
345 /* After this we are unusable, but our data (if any) is still hanging around.
346 * It will be freed later with finalize.
348 static void collection_destroy(GtkObject
*object
)
350 Collection
*collection
;
352 g_return_if_fail(object
!= NULL
);
353 g_return_if_fail(IS_COLLECTION(object
));
355 collection
= COLLECTION(object
);
357 if (collection
->wink_item
!= -1)
359 collection
->wink_item
= -1;
360 gtk_timeout_remove(collection
->wink_timeout
);
363 gtk_signal_disconnect_by_data(GTK_OBJECT(collection
->vadj
),
366 if (GTK_OBJECT_CLASS(parent_class
)->destroy
)
367 (*GTK_OBJECT_CLASS(parent_class
)->destroy
)(object
);
370 /* This is the last thing that happens to us. Free all data. */
371 static void collection_finalize(GtkObject
*object
)
373 Collection
*collection
;
375 collection
= COLLECTION(object
);
377 if (collection
->vadj
)
379 gtk_object_unref(GTK_OBJECT(collection
->vadj
));
382 g_free(collection
->items
);
385 static void collection_realize(GtkWidget
*widget
)
387 Collection
*collection
;
388 GdkWindowAttr attributes
;
389 gint attributes_mask
;
390 GdkGCValues xor_values
;
392 g_return_if_fail(widget
!= NULL
);
393 g_return_if_fail(IS_COLLECTION(widget
));
394 g_return_if_fail(widget
->parent
!= NULL
);
396 GTK_WIDGET_SET_FLAGS(widget
, GTK_REALIZED
);
397 collection
= COLLECTION(widget
);
399 attributes
.x
= widget
->allocation
.x
;
400 attributes
.y
= widget
->allocation
.y
;
401 attributes
.width
= widget
->allocation
.width
;
402 attributes
.height
= widget
->allocation
.height
;
403 attributes
.wclass
= GDK_INPUT_OUTPUT
;
404 attributes
.window_type
= GDK_WINDOW_CHILD
;
405 attributes
.event_mask
= gtk_widget_get_events(widget
) |
407 GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK
|
408 GDK_BUTTON1_MOTION_MASK
| GDK_BUTTON2_MOTION_MASK
|
409 GDK_BUTTON3_MOTION_MASK
;
410 attributes
.visual
= gtk_widget_get_visual(widget
);
411 attributes
.colormap
= gtk_widget_get_colormap(widget
);
413 attributes_mask
= GDK_WA_X
| GDK_WA_Y
|
414 GDK_WA_VISUAL
| GDK_WA_COLORMAP
;
415 widget
->window
= gdk_window_new(widget
->parent
->window
,
416 &attributes
, attributes_mask
);
418 widget
->style
= gtk_style_attach(widget
->style
, widget
->window
);
420 gdk_window_set_user_data(widget
->window
, widget
);
422 gdk_window_set_background(widget
->window
,
423 &widget
->style
->base
[GTK_STATE_INSENSITIVE
]);
425 /* Try to stop everything flickering horribly */
426 gdk_window_set_static_gravities(widget
->window
, TRUE
);
428 set_vadjustment(collection
);
430 xor_values
.function
= GDK_XOR
;
431 xor_values
.foreground
.red
= 0xffff;
432 xor_values
.foreground
.green
= 0xffff;
433 xor_values
.foreground
.blue
= 0xffff;
434 gdk_color_alloc(gtk_widget_get_colormap(widget
),
435 &xor_values
.foreground
);
436 collection
->xor_gc
= gdk_gc_new_with_values(widget
->window
,
442 static void collection_size_request(GtkWidget
*widget
,
443 GtkRequisition
*requisition
)
445 requisition
->width
= MIN_WIDTH
;
446 requisition
->height
= MIN_HEIGHT
;
449 static void collection_size_allocate(GtkWidget
*widget
,
450 GtkAllocation
*allocation
)
452 Collection
*collection
;
455 g_return_if_fail(widget
!= NULL
);
456 g_return_if_fail(IS_COLLECTION(widget
));
457 g_return_if_fail(allocation
!= NULL
);
459 collection
= COLLECTION(widget
);
461 old_columns
= collection
->columns
;
462 widget
->allocation
= *allocation
;
464 collection
->columns
= allocation
->width
/ collection
->item_width
;
465 if (collection
->columns
< 1)
466 collection
->columns
= 1;
468 if (GTK_WIDGET_REALIZED(widget
))
470 gdk_window_move_resize(widget
->window
,
471 allocation
->x
, allocation
->y
,
472 allocation
->width
, allocation
->height
);
474 if (old_columns
!= collection
->columns
)
476 collection
->paint_level
= PAINT_CLEAR
;
477 gtk_widget_queue_clear(widget
);
480 set_vadjustment(collection
);
482 if (collection
->cursor_item
!= -1)
483 scroll_to_show(collection
, collection
->cursor_item
);
487 static gint
collection_paint(Collection
*collection
,
490 GdkRectangle whole
, item_area
;
495 int start_row
, last_row
;
496 int start_col
, last_col
;
500 scroll
= collection
->vadj
->value
;
502 widget
= GTK_WIDGET(collection
);
504 if (collection
->paint_level
> PAINT_NORMAL
|| area
== NULL
)
507 gdk_window_get_size(widget
->window
, &width
, &height
);
512 whole
.height
= height
;
516 if (collection
->paint_level
== PAINT_CLEAR
517 && !collection
->lasso_box
)
518 gdk_window_clear(widget
->window
);
520 collection
->paint_level
= PAINT_NORMAL
;
523 /* Calculate the ranges to plot */
524 start_row
= (area
->y
+ scroll
) / collection
->item_height
;
525 last_row
= (area
->y
+ area
->height
- 1 + scroll
)
526 / collection
->item_height
;
529 start_col
= area
->x
/ collection
->item_width
;
530 phys_last_col
= (area
->x
+ area
->width
- 1) / collection
->item_width
;
532 if (collection
->lasso_box
)
534 /* You can't be too careful with lasso boxes...
536 * clip gives the total area drawn over (this may be larger
537 * than the requested area). It's used to redraw the lasso
540 clip
.x
= start_col
* collection
->item_width
;
541 clip
.y
= start_row
* collection
->item_height
- scroll
;
542 clip
.width
= (phys_last_col
- start_col
+ 1)
543 * collection
->item_width
;
544 clip
.height
= (last_row
- start_row
+ 1)
545 * collection
->item_height
;
547 gdk_window_clear_area(widget
->window
,
548 clip
.x
, clip
.y
, clip
.width
, clip
.height
);
551 if (start_col
< collection
->columns
)
553 if (phys_last_col
>= collection
->columns
)
554 last_col
= collection
->columns
- 1;
556 last_col
= phys_last_col
;
560 item
= row
* collection
->columns
+ col
;
561 item_area
.width
= collection
->item_width
;
562 item_area
.height
= collection
->item_height
;
564 while ((item
== 0 || item
< collection
->number_of_items
)
567 item_area
.x
= col
* collection
->item_width
;
568 item_area
.y
= row
* collection
->item_height
- scroll
;
570 draw_one_item(collection
, item
, &item_area
);
577 item
= row
* collection
->columns
+ col
;
584 if (collection
->lasso_box
)
586 gdk_gc_set_clip_rectangle(collection
->xor_gc
, &clip
);
587 draw_lasso_box(collection
);
588 gdk_gc_set_clip_rectangle(collection
->xor_gc
, NULL
);
594 static void default_draw_item( GtkWidget
*widget
,
595 CollectionItem
*item
,
598 gdk_draw_arc(widget
->window
,
599 item
->selected
? widget
->style
->white_gc
600 : widget
->style
->black_gc
,
603 area
->width
, area
->height
,
608 static gboolean
default_test_point(Collection
*collection
,
609 int point_x
, int point_y
,
610 CollectionItem
*item
,
611 int width
, int height
)
615 /* Convert to point in unit circle */
616 f_x
= ((float) point_x
/ width
) - 0.5;
617 f_y
= ((float) point_y
/ height
) - 0.5;
619 return (f_x
* f_x
) + (f_y
* f_y
) <= .25;
622 static void collection_set_arg( GtkObject
*object
,
626 Collection
*collection
;
628 collection
= COLLECTION(object
);
632 case ARG_VADJUSTMENT
:
633 collection_set_adjustment(collection
,
634 GTK_VALUE_POINTER(*arg
));
641 static void collection_set_adjustment( Collection
*collection
,
645 g_return_if_fail (GTK_IS_ADJUSTMENT (vadj
));
647 vadj
= GTK_ADJUSTMENT(gtk_adjustment_new(0.0,
650 if (collection
->vadj
&& (collection
->vadj
!= vadj
))
652 gtk_signal_disconnect_by_data(GTK_OBJECT(collection
->vadj
),
654 gtk_object_unref(GTK_OBJECT(collection
->vadj
));
657 if (collection
->vadj
!= vadj
)
659 collection
->vadj
= vadj
;
660 gtk_object_ref(GTK_OBJECT(collection
->vadj
));
661 gtk_object_sink(GTK_OBJECT(collection
->vadj
));
663 gtk_signal_connect(GTK_OBJECT(collection
->vadj
),
665 (GtkSignalFunc
) collection_adjustment
,
667 gtk_signal_connect(GTK_OBJECT(collection
->vadj
),
669 (GtkSignalFunc
) collection_adjustment
,
671 gtk_signal_connect(GTK_OBJECT(collection
->vadj
),
673 (GtkSignalFunc
) collection_disconnect
,
675 collection_adjustment(vadj
, collection
);
679 static void collection_get_arg( GtkObject
*object
,
683 Collection
*collection
;
685 collection
= COLLECTION(object
);
689 case ARG_VADJUSTMENT
:
690 GTK_VALUE_POINTER(*arg
) = collection
->vadj
;
693 arg
->type
= GTK_TYPE_INVALID
;
698 /* Something about the adjustment has changed */
699 static void collection_adjustment(GtkAdjustment
*adjustment
,
700 Collection
*collection
)
704 g_return_if_fail(adjustment
!= NULL
);
705 g_return_if_fail(GTK_IS_ADJUSTMENT(adjustment
));
706 g_return_if_fail(collection
!= NULL
);
707 g_return_if_fail(IS_COLLECTION(collection
));
709 diff
= ((gint
) adjustment
->value
) - collection
->last_scroll
;
713 collection
->last_scroll
= adjustment
->value
;
715 scroll_by(collection
, diff
);
719 static void collection_disconnect(GtkAdjustment
*adjustment
,
720 Collection
*collection
)
722 g_return_if_fail(adjustment
!= NULL
);
723 g_return_if_fail(GTK_IS_ADJUSTMENT(adjustment
));
724 g_return_if_fail(collection
!= NULL
);
725 g_return_if_fail(IS_COLLECTION(collection
));
727 collection_set_adjustment(collection
, NULL
);
730 static void set_vadjustment(Collection
*collection
)
736 widget
= GTK_WIDGET(collection
);
738 if (!GTK_WIDGET_REALIZED(widget
))
741 gdk_window_get_size(widget
->window
, NULL
, &height
);
742 cols
= collection
->columns
;
743 rows
= (collection
->number_of_items
+ cols
- 1) / cols
;
745 collection
->vadj
->lower
= 0.0;
746 collection
->vadj
->upper
= collection
->item_height
* rows
;
748 collection
->vadj
->step_increment
=
749 MIN(collection
->vadj
->upper
, collection
->item_height
/ 4);
751 collection
->vadj
->page_increment
=
752 MIN(collection
->vadj
->upper
,
755 collection
->vadj
->page_size
= MIN(collection
->vadj
->upper
, height
);
757 collection
->vadj
->value
= MIN(collection
->vadj
->value
,
758 collection
->vadj
->upper
- collection
->vadj
->page_size
);
760 collection
->vadj
->value
= MAX(collection
->vadj
->value
, 0.0);
762 gtk_signal_emit_by_name(GTK_OBJECT(collection
->vadj
), "changed");
765 static void collection_draw(GtkWidget
*widget
, GdkRectangle
*area
)
767 Collection
*collection
;
769 g_return_if_fail(widget
!= NULL
);
770 g_return_if_fail(IS_COLLECTION(widget
));
771 g_return_if_fail(area
!= NULL
); /* Not actually used */
773 collection
= COLLECTION(widget
);
775 if (collection
->paint_level
> PAINT_NORMAL
)
776 collection_paint(collection
, area
);
779 static gint
collection_expose(GtkWidget
*widget
, GdkEventExpose
*event
)
781 g_return_val_if_fail(widget
!= NULL
, FALSE
);
782 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
783 g_return_val_if_fail(event
!= NULL
, FALSE
);
785 collection_paint(COLLECTION(widget
), &event
->area
);
790 /* Positive makes the contents go move up the screen */
791 static void scroll_by(Collection
*collection
, gint diff
)
797 GdkRectangle new_area
;
802 widget
= GTK_WIDGET(collection
);
804 if (collection
->lasso_box
)
805 remove_lasso_box(collection
);
807 gdk_window_get_size(widget
->window
, &width
, &height
);
809 new_area
.width
= width
;
816 new_area
.y
= height
- amount
;
826 new_area
.height
= amount
;
830 gdk_draw_pixmap(widget
->window
,
831 widget
->style
->white_gc
,
839 /* We have to redraw everything because any pending
840 * expose events now contain invalid areas.
841 * Don't need to clear the area first though...
843 if (collection
->paint_level
< PAINT_OVERWRITE
)
844 collection
->paint_level
= PAINT_OVERWRITE
;
847 collection
->paint_level
= PAINT_CLEAR
;
849 gdk_window_clear_area(widget
->window
,
851 width
, new_area
.height
);
852 collection_paint(collection
, NULL
);
855 static void resize_arrays(Collection
*collection
, guint new_size
)
857 g_return_if_fail(collection
!= NULL
);
858 g_return_if_fail(IS_COLLECTION(collection
));
859 g_return_if_fail(new_size
>= collection
->number_of_items
);
861 collection
->items
= g_realloc(collection
->items
,
862 sizeof(CollectionItem
) * new_size
);
863 collection
->array_size
= new_size
;
866 static void return_pressed(Collection
*collection
)
868 int item
= collection
->cursor_item
;
869 CollectionTargetFunc cb
= collection
->target_cb
;
870 gpointer data
= collection
->target_data
;
872 collection_target(collection
, NULL
, NULL
);
873 if (item
< 0 || item
>= collection
->number_of_items
)
878 cb(collection
, item
, data
);
882 gtk_signal_emit(GTK_OBJECT(collection
),
883 collection_signals
[OPEN_ITEM
],
884 collection
->items
[item
].data
,
888 static gint
collection_key_press(GtkWidget
*widget
, GdkEventKey
*event
)
890 Collection
*collection
;
893 g_return_val_if_fail(widget
!= NULL
, FALSE
);
894 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
895 g_return_val_if_fail(event
!= NULL
, FALSE
);
897 collection
= (Collection
*) widget
;
898 item
= collection
->cursor_item
;
900 switch (event
->keyval
)
903 collection_move_cursor(collection
, 0, -1);
906 collection_move_cursor(collection
, 0, 1);
909 collection_move_cursor(collection
, -1, 0);
912 collection_move_cursor(collection
, 1, 0);
915 collection_set_cursor_item(collection
, 0);
918 collection_set_cursor_item(collection
,
919 MAX((gint
) collection
->number_of_items
- 1, 0));
922 collection_move_cursor(collection
, -10, 0);
925 collection_move_cursor(collection
, 10, 0);
928 return_pressed(collection
);
931 collection_target(collection
, NULL
, NULL
);
934 if (item
>=0 && item
< collection
->number_of_items
)
935 collection_toggle_item(collection
, item
);
944 static gint
collection_button_press(GtkWidget
*widget
,
945 GdkEventButton
*event
)
947 Collection
*collection
;
954 g_return_val_if_fail(widget
!= NULL
, FALSE
);
955 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
956 g_return_val_if_fail(event
!= NULL
, FALSE
);
958 collection
= COLLECTION(widget
);
960 collection
->item_clicked
= -1;
962 if (event
->button
> 3)
966 /* Wheel mouse scrolling */
967 if (event
->button
== 4)
968 diff
= -((signed int) collection
->item_height
) / 4;
969 else if (event
->button
== 5)
970 diff
= collection
->item_height
/ 4;
976 int old_value
= collection
->vadj
->value
;
978 gboolean box
= collection
->lasso_box
;
980 new_value
= CLAMP(old_value
+ diff
, 0.0,
981 collection
->vadj
->upper
982 - collection
->vadj
->page_size
);
983 diff
= new_value
- old_value
;
988 remove_lasso_box(collection
);
989 collection
->drag_box_y
[0] -= diff
;
991 collection
->vadj
->value
= new_value
;
992 gtk_signal_emit_by_name(
993 GTK_OBJECT(collection
->vadj
),
996 add_lasso_box(collection
);
1002 if (collection
->cursor_item
!= -1)
1003 collection_set_cursor_item(collection
, -1);
1005 scroll
= collection
->vadj
->value
;
1007 if (event
->type
== GDK_BUTTON_PRESS
&&
1008 event
->button
!= collection_menu_button
)
1010 if (collection
->buttons_pressed
++ == 0)
1011 gtk_grab_add(widget
);
1013 return FALSE
; /* Ignore extra presses */
1016 if (event
->state
& GDK_CONTROL_MASK
&& !collection_single_click
)
1019 action
= event
->button
;
1021 /* Ignore all clicks while we are dragging a lasso box */
1022 if (collection
->lasso_box
)
1025 col
= event
->x
/ collection
->item_width
;
1026 row
= (event
->y
+ scroll
) / collection
->item_height
;
1028 if (col
< 0 || row
< 0 || col
>= collection
->columns
)
1032 item
= col
+ row
* collection
->columns
;
1033 if (item
>= collection
->number_of_items
1035 !collection
->test_point(collection
,
1036 event
->x
- col
* collection
->item_width
,
1037 event
->y
- row
* collection
->item_height
1039 &collection
->items
[item
],
1040 collection
->item_width
,
1041 collection
->item_height
))
1047 if (collection
->target_cb
)
1049 CollectionTargetFunc cb
= collection
->target_cb
;
1050 gpointer data
= collection
->target_data
;
1052 collection_target(collection
, NULL
, NULL
);
1053 if (collection
->buttons_pressed
)
1055 gtk_grab_remove(widget
);
1056 collection
->buttons_pressed
= 0;
1058 if (item
> -1 && event
->button
!= collection_menu_button
)
1059 cb(collection
, item
, data
);
1063 collection
->drag_box_x
[0] = event
->x
;
1064 collection
->drag_box_y
[0] = event
->y
;
1065 collection
->item_clicked
= item
;
1067 stacked_time
= current_event_time
;
1068 current_event_time
= event
->time
;
1070 if (event
->button
== collection_menu_button
)
1072 gtk_signal_emit(GTK_OBJECT(collection
),
1073 collection_signals
[SHOW_MENU
],
1077 else if (event
->type
== GDK_2BUTTON_PRESS
&& collection
->panel
)
1081 else if ((event
->type
== GDK_2BUTTON_PRESS
&& !collection_single_click
)
1082 || collection
->panel
)
1086 if (collection
->buttons_pressed
)
1088 gtk_grab_remove(widget
);
1089 collection
->buttons_pressed
= 0;
1091 collection_unselect_item(collection
, item
);
1092 gtk_signal_emit(GTK_OBJECT(collection
),
1093 collection_signals
[OPEN_ITEM
],
1094 collection
->items
[item
].data
,
1098 else if (event
->type
== GDK_BUTTON_PRESS
)
1100 collection
->may_drag
= event
->button
< collection_menu_button
;
1106 if (!collection
->items
[item
].selected
)
1108 collection_select_item(collection
,
1110 collection_clear_except(collection
,
1115 collection_toggle_item(collection
, item
);
1117 else if (action
== 1)
1118 collection_clear_selection(collection
);
1121 current_event_time
= stacked_time
;
1125 static gint
collection_button_release(GtkWidget
*widget
,
1126 GdkEventButton
*event
)
1128 Collection
*collection
;
1132 int col
, start_col
, last_col
;
1138 g_return_val_if_fail(widget
!= NULL
, FALSE
);
1139 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
1140 g_return_val_if_fail(event
!= NULL
, FALSE
);
1142 collection
= COLLECTION(widget
);
1143 button
= event
->button
;
1145 scroll
= collection
->vadj
->value
;
1147 if (event
->button
> 3 || event
->button
== collection_menu_button
)
1149 if (collection
->buttons_pressed
== 0)
1151 if (--collection
->buttons_pressed
== 0)
1152 gtk_grab_remove(widget
);
1154 return FALSE
; /* Wait until ALL buttons are up */
1156 if (!collection
->lasso_box
)
1158 int item
= collection
->item_clicked
;
1160 if (collection_single_click
&& item
> -1
1161 && item
< collection
->number_of_items
1162 && (event
->state
& GDK_CONTROL_MASK
) == 0)
1164 int dx
= event
->x
- collection
->drag_box_x
[0];
1165 int dy
= event
->y
- collection
->drag_box_y
[0];
1167 if (ABS(dx
) + ABS(dy
) > 9)
1170 collection_unselect_item(collection
, item
);
1171 gtk_signal_emit(GTK_OBJECT(collection
),
1172 collection_signals
[OPEN_ITEM
],
1173 collection
->items
[item
].data
,
1180 remove_lasso_box(collection
);
1182 w
= collection
->item_width
;
1183 h
= collection
->item_height
;
1185 top
= collection
->drag_box_y
[0] + scroll
;
1186 bottom
= collection
->drag_box_y
[1] + scroll
;
1197 row
= MAX(top
/ h
, 0);
1198 last_row
= bottom
/ h
;
1200 top
= collection
->drag_box_x
[0]; /* (left) */
1201 bottom
= collection
->drag_box_x
[1];
1211 start_col
= MAX(top
/ w
, 0);
1212 last_col
= bottom
/ w
;
1213 if (last_col
>= collection
->columns
)
1214 last_col
= collection
->columns
- 1;
1216 stacked_time
= current_event_time
;
1217 current_event_time
= event
->time
;
1219 while (row
<= last_row
)
1222 item
= row
* collection
->columns
+ col
;
1223 while (col
<= last_col
)
1225 if (item
>= collection
->number_of_items
)
1227 current_event_time
= stacked_time
;
1232 collection_select_item(collection
, item
);
1234 collection_toggle_item(collection
, item
);
1241 current_event_time
= stacked_time
;
1246 static gint
collection_motion_notify(GtkWidget
*widget
,
1247 GdkEventMotion
*event
)
1249 Collection
*collection
;
1253 g_return_val_if_fail(widget
!= NULL
, FALSE
);
1254 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
1255 g_return_val_if_fail(event
!= NULL
, FALSE
);
1257 collection
= COLLECTION(widget
);
1259 if (collection
->buttons_pressed
== 0)
1262 stacked_time
= current_event_time
;
1263 current_event_time
= event
->time
;
1265 if (event
->window
!= widget
->window
)
1266 gdk_window_get_pointer(widget
->window
, &x
, &y
, NULL
);
1273 if (collection
->lasso_box
)
1275 int new_value
= 0, diff
;
1278 gdk_window_get_size(widget
->window
, NULL
, &height
);
1282 int old_value
= collection
->vadj
->value
;
1284 new_value
= MAX(old_value
+ y
/ 10, 0.0);
1285 diff
= new_value
- old_value
;
1287 else if (y
> height
)
1289 int old_value
= collection
->vadj
->value
;
1291 new_value
= MIN(old_value
+ (y
- height
) / 10,
1292 collection
->vadj
->upper
1293 - collection
->vadj
->page_size
);
1294 diff
= new_value
- old_value
;
1299 remove_lasso_box(collection
);
1300 collection
->drag_box_x
[1] = x
;
1301 collection
->drag_box_y
[1] = y
;
1305 collection
->drag_box_y
[0] -= diff
;
1306 collection
->vadj
->value
= new_value
;
1307 gtk_signal_emit_by_name(GTK_OBJECT(collection
->vadj
),
1310 add_lasso_box(collection
);
1312 else if (collection
->may_drag
)
1314 int dx
= x
- collection
->drag_box_x
[0];
1315 int dy
= y
- collection
->drag_box_y
[0];
1317 if (abs(dx
) > 9 || abs(dy
) > 9)
1320 int scroll
= collection
->vadj
->value
;
1322 collection
->may_drag
= FALSE
;
1324 col
= collection
->drag_box_x
[0]
1325 / collection
->item_width
;
1326 row
= (collection
->drag_box_y
[0] + scroll
)
1327 / collection
->item_height
;
1328 item
= item_at_row_col(collection
, row
, col
);
1330 if (item
!= -1 && collection
->test_point(collection
,
1331 collection
->drag_box_x
[0] -
1332 col
* collection
->item_width
,
1333 collection
->drag_box_y
[0]
1334 - row
* collection
->item_height
1336 &collection
->items
[item
],
1337 collection
->item_width
,
1338 collection
->item_height
))
1340 collection
->buttons_pressed
= 0;
1341 gtk_grab_remove(widget
);
1342 collection_select_item(collection
, item
);
1343 gtk_signal_emit(GTK_OBJECT(collection
),
1344 collection_signals
[DRAG_SELECTION
],
1346 collection
->number_selected
);
1350 collection
->drag_box_x
[1] = x
;
1351 collection
->drag_box_y
[1] = y
;
1352 add_lasso_box(collection
);
1357 current_event_time
= stacked_time
;
1361 static void add_lasso_box(Collection
*collection
)
1363 g_return_if_fail(collection
!= NULL
);
1364 g_return_if_fail(IS_COLLECTION(collection
));
1365 g_return_if_fail(collection
->lasso_box
== FALSE
);
1367 collection
->lasso_box
= TRUE
;
1368 draw_lasso_box(collection
);
1371 static void draw_lasso_box(Collection
*collection
)
1374 int x
, y
, width
, height
;
1376 widget
= GTK_WIDGET(collection
);
1378 x
= MIN(collection
->drag_box_x
[0], collection
->drag_box_x
[1]);
1379 y
= MIN(collection
->drag_box_y
[0], collection
->drag_box_y
[1]);
1380 width
= abs(collection
->drag_box_x
[1] - collection
->drag_box_x
[0]);
1381 height
= abs(collection
->drag_box_y
[1] - collection
->drag_box_y
[0]);
1383 gdk_draw_rectangle(widget
->window
, collection
->xor_gc
, FALSE
,
1384 x
, y
, width
, height
);
1387 static void remove_lasso_box(Collection
*collection
)
1389 g_return_if_fail(collection
!= NULL
);
1390 g_return_if_fail(IS_COLLECTION(collection
));
1391 g_return_if_fail(collection
->lasso_box
== TRUE
);
1393 draw_lasso_box(collection
);
1395 collection
->lasso_box
= FALSE
;
1400 /* Convert a row,col address to an item number, or -1 if none */
1401 static int item_at_row_col(Collection
*collection
, int row
, int col
)
1405 if (row
< 0 || col
< 0 || col
>= collection
->columns
)
1408 item
= col
+ row
* collection
->columns
;
1410 if (item
>= collection
->number_of_items
)
1415 /* Make sure that 'item' is fully visible (vertically), scrolling if not. */
1416 static void scroll_to_show(Collection
*collection
, int item
)
1418 int first
, last
, row
;
1420 g_return_if_fail(collection
!= NULL
);
1421 g_return_if_fail(IS_COLLECTION(collection
));
1423 row
= item
/ collection
->columns
;
1424 get_visible_limits(collection
, &first
, &last
);
1428 gtk_adjustment_set_value(collection
->vadj
,
1429 row
* collection
->item_height
);
1431 else if (row
>= last
)
1433 GtkWidget
*widget
= (GtkWidget
*) collection
;
1436 if (GTK_WIDGET_REALIZED(widget
))
1438 gdk_window_get_size(widget
->window
, NULL
, &height
);
1439 gtk_adjustment_set_value(collection
->vadj
,
1440 (row
+ 1) * collection
->item_height
- height
);
1445 /* Return the first and last rows which are [partly] visible. Does not
1446 * ensure that the rows actually exist (contain items).
1448 static void get_visible_limits(Collection
*collection
, int *first
, int *last
)
1450 GtkWidget
*widget
= (GtkWidget
*) collection
;
1453 g_return_if_fail(collection
!= NULL
);
1454 g_return_if_fail(IS_COLLECTION(collection
));
1455 g_return_if_fail(first
!= NULL
&& last
!= NULL
);
1457 if (!GTK_WIDGET_REALIZED(widget
))
1464 scroll
= collection
->vadj
->value
;
1465 gdk_window_get_size(widget
->window
, NULL
, &height
);
1467 *first
= MAX(scroll
/ collection
->item_height
, 0);
1468 *last
= (scroll
+ height
- 1) /collection
->item_height
;
1475 /* Unselect all items except number item (-1 to unselect everything) */
1476 static void collection_clear_except(Collection
*collection
, gint exception
)
1482 int end
; /* Selected items to end up with */
1484 widget
= GTK_WIDGET(collection
);
1485 scroll
= collection
->vadj
->value
;
1487 end
= exception
>= 0 && exception
< collection
->number_of_items
1488 ? collection
->items
[exception
].selected
!= 0 : 0;
1490 area
.width
= collection
->item_width
;
1491 area
.height
= collection
->item_height
;
1493 if (collection
->number_selected
== 0)
1496 while (collection
->number_selected
> end
)
1498 while (item
== exception
|| !collection
->items
[item
].selected
)
1501 area
.x
= (item
% collection
->columns
) * area
.width
;
1502 area
.y
= (item
/ collection
->columns
) * area
.height
1505 collection
->items
[item
++].selected
= FALSE
;
1506 gdk_window_clear_area(widget
->window
,
1507 area
.x
, area
.y
, area
.width
, area
.height
);
1508 collection_paint(collection
, &area
);
1510 collection
->number_selected
--;
1514 gtk_signal_emit(GTK_OBJECT(collection
),
1515 collection_signals
[LOSE_SELECTION
],
1516 current_event_time
);
1519 /* Cancel the current wink effect. */
1520 static void cancel_wink(Collection
*collection
)
1524 g_return_if_fail(collection
!= NULL
);
1525 g_return_if_fail(IS_COLLECTION(collection
));
1526 g_return_if_fail(collection
->wink_item
!= -1);
1528 item
= collection
->wink_item
;
1530 collection
->wink_item
= -1;
1531 gtk_timeout_remove(collection
->wink_timeout
);
1533 collection_draw_item(collection
, item
, TRUE
);
1536 static gboolean
cancel_wink_timeout(Collection
*collection
)
1540 g_return_val_if_fail(collection
!= NULL
, FALSE
);
1541 g_return_val_if_fail(IS_COLLECTION(collection
), FALSE
);
1542 g_return_val_if_fail(collection
->wink_item
!= -1, FALSE
);
1544 item
= collection
->wink_item
;
1546 collection
->wink_item
= -1;
1548 collection_draw_item(collection
, item
, TRUE
);
1553 /* Functions for managing collections */
1555 /* Remove all objects from the collection */
1556 void collection_clear(Collection
*collection
)
1560 g_return_if_fail(IS_COLLECTION(collection
));
1562 if (collection
->number_of_items
== 0)
1565 if (collection
->wink_item
!= -1)
1567 collection
->wink_item
= -1;
1568 gtk_timeout_remove(collection
->wink_timeout
);
1571 collection_set_cursor_item(collection
,
1572 collection
->cursor_item
== -1 ? -1: 0);
1573 prev_selected
= collection
->number_selected
;
1574 collection
->number_of_items
= collection
->number_selected
= 0;
1576 resize_arrays(collection
, MINIMUM_ITEMS
);
1578 collection
->paint_level
= PAINT_CLEAR
;
1580 gtk_widget_queue_clear(GTK_WIDGET(collection
));
1582 if (prev_selected
&& collection
->number_selected
== 0)
1583 gtk_signal_emit(GTK_OBJECT(collection
),
1584 collection_signals
[LOSE_SELECTION
],
1585 current_event_time
);
1588 /* Inserts a new item at the end. The new item is unselected, and its
1589 * number is returned.
1591 gint
collection_insert(Collection
*collection
, gpointer data
)
1595 g_return_val_if_fail(IS_COLLECTION(collection
), -1);
1597 item
= collection
->number_of_items
;
1599 if (item
>= collection
->array_size
)
1600 resize_arrays(collection
, item
+ (item
>> 1));
1602 collection
->items
[item
].data
= data
;
1603 collection
->items
[item
].selected
= FALSE
;
1605 collection
->number_of_items
++;
1607 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection
)))
1609 set_vadjustment(collection
);
1610 collection_draw_item(collection
,
1611 collection
->number_of_items
- 1,
1618 /* Unselect an item by number */
1619 void collection_unselect_item(Collection
*collection
, gint item
)
1621 g_return_if_fail(collection
!= NULL
);
1622 g_return_if_fail(IS_COLLECTION(collection
));
1623 g_return_if_fail(item
>= 0 && item
< collection
->number_of_items
);
1625 if (collection
->items
[item
].selected
)
1627 collection
->items
[item
].selected
= FALSE
;
1628 collection_draw_item(collection
, item
, TRUE
);
1630 if (--collection
->number_selected
== 0)
1631 gtk_signal_emit(GTK_OBJECT(collection
),
1632 collection_signals
[LOSE_SELECTION
],
1633 current_event_time
);
1637 /* Select an item by number */
1638 void collection_select_item(Collection
*collection
, gint item
)
1640 g_return_if_fail(collection
!= NULL
);
1641 g_return_if_fail(IS_COLLECTION(collection
));
1642 g_return_if_fail(item
>= 0 && item
< collection
->number_of_items
);
1644 if (collection
->items
[item
].selected
)
1645 return; /* Already selected */
1647 collection
->items
[item
].selected
= TRUE
;
1648 collection_draw_item(collection
, item
, TRUE
);
1650 if (collection
->number_selected
++ == 0)
1651 gtk_signal_emit(GTK_OBJECT(collection
),
1652 collection_signals
[GAIN_SELECTION
],
1653 current_event_time
);
1656 /* Toggle the selected state of an item (by number) */
1657 void collection_toggle_item(Collection
*collection
, gint item
)
1659 g_return_if_fail(collection
!= NULL
);
1660 g_return_if_fail(IS_COLLECTION(collection
));
1661 g_return_if_fail(item
>= 0 && item
< collection
->number_of_items
);
1663 if (collection
->items
[item
].selected
)
1665 collection
->items
[item
].selected
= FALSE
;
1666 if (--collection
->number_selected
== 0)
1667 gtk_signal_emit(GTK_OBJECT(collection
),
1668 collection_signals
[LOSE_SELECTION
],
1669 current_event_time
);
1673 collection
->items
[item
].selected
= TRUE
;
1674 if (collection
->number_selected
++ == 0)
1675 gtk_signal_emit(GTK_OBJECT(collection
),
1676 collection_signals
[GAIN_SELECTION
],
1677 current_event_time
);
1679 collection_draw_item(collection
, item
, TRUE
);
1682 /* Select all items in the collection */
1683 void collection_select_all(Collection
*collection
)
1690 g_return_if_fail(collection
!= NULL
);
1691 g_return_if_fail(IS_COLLECTION(collection
));
1693 widget
= GTK_WIDGET(collection
);
1694 scroll
= collection
->vadj
->value
;
1696 area
.width
= collection
->item_width
;
1697 area
.height
= collection
->item_height
;
1699 if (collection
->number_selected
== collection
->number_of_items
)
1700 return; /* Nothing to do */
1702 while (collection
->number_selected
< collection
->number_of_items
)
1704 while (collection
->items
[item
].selected
)
1707 area
.x
= (item
% collection
->columns
) * area
.width
;
1708 area
.y
= (item
/ collection
->columns
) * area
.height
1711 collection
->items
[item
++].selected
= TRUE
;
1712 gdk_window_clear_area(widget
->window
,
1713 area
.x
, area
.y
, area
.width
, area
.height
);
1714 collection_paint(collection
, &area
);
1716 collection
->number_selected
++;
1719 gtk_signal_emit(GTK_OBJECT(collection
),
1720 collection_signals
[GAIN_SELECTION
],
1721 current_event_time
);
1724 /* Unselect all items in the collection */
1725 void collection_clear_selection(Collection
*collection
)
1727 g_return_if_fail(collection
!= NULL
);
1728 g_return_if_fail(IS_COLLECTION(collection
));
1730 collection_clear_except(collection
, -1);
1733 /* Force a redraw of the specified item, if it is visible */
1734 void collection_draw_item(Collection
*collection
, gint item
, gboolean blank
)
1741 int area_y
, area_height
; /* NOT shorts! */
1743 g_return_if_fail(collection
!= NULL
);
1744 g_return_if_fail(IS_COLLECTION(collection
));
1745 g_return_if_fail(item
>= 0 &&
1746 (item
== 0 || item
< collection
->number_of_items
));
1748 widget
= GTK_WIDGET(collection
);
1749 if (!GTK_WIDGET_REALIZED(widget
))
1752 col
= item
% collection
->columns
;
1753 row
= item
/ collection
->columns
;
1754 scroll
= collection
->vadj
->value
; /* (round to int) */
1756 area
.x
= col
* collection
->item_width
;
1757 area_y
= row
* collection
->item_height
- scroll
;
1758 area
.width
= collection
->item_width
;
1759 area_height
= collection
->item_height
;
1761 if (area_y
+ area_height
< 0)
1764 gdk_window_get_size(widget
->window
, NULL
, &height
);
1766 if (area_y
> height
)
1770 area
.height
= area_height
;
1772 if (blank
|| collection
->lasso_box
)
1773 gdk_window_clear_area(widget
->window
,
1774 area
.x
, area
.y
, area
.width
, area
.height
);
1776 draw_one_item(collection
, item
, &area
);
1778 if (collection
->lasso_box
)
1780 gdk_gc_set_clip_rectangle(collection
->xor_gc
, &area
);
1781 draw_lasso_box(collection
);
1782 gdk_gc_set_clip_rectangle(collection
->xor_gc
, NULL
);
1786 void collection_set_item_size(Collection
*collection
, int width
, int height
)
1790 g_return_if_fail(collection
!= NULL
);
1791 g_return_if_fail(IS_COLLECTION(collection
));
1792 g_return_if_fail(width
> 4 && height
> 4);
1794 widget
= GTK_WIDGET(collection
);
1796 collection
->item_width
= width
;
1797 collection
->item_height
= height
;
1799 if (GTK_WIDGET_REALIZED(widget
))
1803 collection
->paint_level
= PAINT_CLEAR
;
1804 gdk_window_get_size(widget
->window
, &window_width
, NULL
);
1805 collection
->columns
= MAX(window_width
/ collection
->item_width
,
1808 set_vadjustment(collection
);
1809 if (collection
->cursor_item
!= -1)
1810 scroll_to_show(collection
, collection
->cursor_item
);
1811 gtk_widget_queue_draw(widget
);
1815 /* Cursor is positioned on item with the same data as before the sort.
1816 * Same for the wink item.
1818 void collection_qsort(Collection
*collection
,
1819 int (*compar
)(const void *, const void *))
1821 int cursor
, wink
, items
;
1822 gpointer cursor_data
= NULL
;
1823 gpointer wink_data
= NULL
;
1825 g_return_if_fail(collection
!= NULL
);
1826 g_return_if_fail(IS_COLLECTION(collection
));
1827 g_return_if_fail(compar
!= NULL
);
1829 items
= collection
->number_of_items
;
1831 wink
= collection
->wink_item
;
1832 if (wink
>= 0 && wink
< items
)
1833 wink_data
= collection
->items
[wink
].data
;
1837 cursor
= collection
->cursor_item
;
1838 if (cursor
>= 0 && cursor
< items
)
1839 cursor_data
= collection
->items
[cursor
].data
;
1843 if (collection
->wink_item
!= -1)
1845 collection
->wink_item
= -1;
1846 gtk_timeout_remove(collection
->wink_timeout
);
1849 qsort(collection
->items
, items
, sizeof(collection
->items
[0]), compar
);
1851 if (cursor
> -1 || wink
> -1)
1855 for (item
= 0; item
< items
; item
++)
1857 if (collection
->items
[item
].data
== cursor_data
)
1858 collection_set_cursor_item(collection
, item
);
1859 if (collection
->items
[item
].data
== wink_data
)
1860 collection_wink_item(collection
, item
);
1864 collection
->paint_level
= PAINT_CLEAR
;
1866 gtk_widget_queue_draw(GTK_WIDGET(collection
));
1869 /* Find an item in an unsorted collection.
1870 * Returns the item number, or -1 if not found.
1872 int collection_find_item(Collection
*collection
, gpointer data
,
1873 int (*compar
)(const void *, const void *))
1877 g_return_val_if_fail(collection
!= NULL
, -1);
1878 g_return_val_if_fail(IS_COLLECTION(collection
), -1);
1879 g_return_val_if_fail(compar
!= NULL
, -1);
1881 for (i
= 0; i
< collection
->number_of_items
; i
++)
1882 if (compar(&collection
->items
[i
].data
, &data
) == 0)
1888 /* The collection may be in either normal mode or panel mode.
1890 * - a single click calls open_item
1891 * - items are never selected
1892 * - lasso boxes are disabled
1894 void collection_set_panel(Collection
*collection
, gboolean panel
)
1896 g_return_if_fail(collection
!= NULL
);
1897 g_return_if_fail(IS_COLLECTION(collection
));
1899 collection
->panel
= panel
== TRUE
;
1901 if (collection
->panel
)
1903 collection_clear_selection(collection
);
1904 if (collection
->lasso_box
)
1905 remove_lasso_box(collection
);
1909 /* Return the number of the item under the point (x,y), or -1 for none.
1910 * This may call your test_point callback. The point is relative to the
1911 * collection's origin.
1913 int collection_get_item(Collection
*collection
, int x
, int y
)
1919 g_return_val_if_fail(collection
!= NULL
, -1);
1921 scroll
= collection
->vadj
->value
;
1922 col
= x
/ collection
->item_width
;
1923 row
= (y
+ scroll
) / collection
->item_height
;
1925 if (col
< 0 || row
< 0 || col
>= collection
->columns
)
1928 item
= col
+ row
* collection
->columns
;
1929 if (item
>= collection
->number_of_items
1931 !collection
->test_point(collection
,
1932 x
- col
* collection
->item_width
,
1933 y
- row
* collection
->item_height
1935 &collection
->items
[item
],
1936 collection
->item_width
,
1937 collection
->item_height
))
1945 /* Set the cursor/highlight over the given item. Passing -1
1946 * hides the cursor. As a special case, you may set the cursor item
1947 * to zero when there are no items.
1949 void collection_set_cursor_item(Collection
*collection
, gint item
)
1953 g_return_if_fail(collection
!= NULL
);
1954 g_return_if_fail(IS_COLLECTION(collection
));
1955 g_return_if_fail(item
>= -1 &&
1956 (item
< (int) collection
->number_of_items
|| item
== 0));
1958 old_item
= collection
->cursor_item
;
1960 if (old_item
== item
)
1963 collection
->cursor_item
= item
;
1966 collection_draw_item(collection
, old_item
, TRUE
);
1970 collection_draw_item(collection
, item
, TRUE
);
1971 scroll_to_show(collection
, item
);
1975 /* Briefly highlight an item to draw the user's attention to it.
1976 * -1 cancels the effect, as does deleting items, sorting the collection
1977 * or starting a new wink effect.
1978 * Otherwise, the effect will cancel itself after a short pause.
1980 void collection_wink_item(Collection
*collection
, gint item
)
1982 g_return_if_fail(collection
!= NULL
);
1983 g_return_if_fail(IS_COLLECTION(collection
));
1984 g_return_if_fail(item
>= -1 && item
< collection
->number_of_items
);
1986 if (collection
->wink_item
!= -1)
1987 cancel_wink(collection
);
1991 collection
->wink_item
= item
;
1992 collection
->wink_timeout
= gtk_timeout_add(300,
1993 (GtkFunction
) cancel_wink_timeout
,
1995 collection_draw_item(collection
, item
, TRUE
);
1996 scroll_to_show(collection
, item
);
2001 /* Call test(item, data) on each item in the collection.
2002 * Remove all items for which it returns TRUE. test() should
2003 * free the data before returning TRUE. The collection is in an
2004 * inconsistant state during this call (ie, when test() is called).
2006 void collection_delete_if(Collection
*collection
,
2007 gboolean (*test
)(gpointer item
, gpointer data
),
2014 g_return_if_fail(collection
!= NULL
);
2015 g_return_if_fail(IS_COLLECTION(collection
));
2016 g_return_if_fail(test
!= NULL
);
2018 cursor
= collection
->cursor_item
;
2020 for (in
= 0; in
< collection
->number_of_items
; in
++)
2022 if (!test(collection
->items
[in
].data
, data
))
2024 if (collection
->items
[in
].selected
)
2026 collection
->items
[out
].selected
= TRUE
;
2030 collection
->items
[out
].selected
= FALSE
;
2032 collection
->items
[out
++].data
=
2033 collection
->items
[in
].data
;
2035 else if (cursor
>= in
)
2041 collection
->cursor_item
= cursor
;
2043 if (collection
->wink_item
!= -1)
2045 collection
->wink_item
= -1;
2046 gtk_timeout_remove(collection
->wink_timeout
);
2049 collection
->number_of_items
= out
;
2050 collection
->number_selected
= selected
;
2051 resize_arrays(collection
,
2052 MAX(collection
->number_of_items
, MINIMUM_ITEMS
));
2054 collection
->paint_level
= PAINT_CLEAR
;
2056 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection
)))
2058 set_vadjustment(collection
);
2059 gtk_widget_queue_draw(GTK_WIDGET(collection
));
2064 /* Display a cross-hair pointer and the next time an item is clicked,
2065 * call the callback function. If the background is clicked or NULL
2066 * is passed as the callback then revert to normal operation.
2068 void collection_target(Collection
*collection
,
2069 CollectionTargetFunc callback
,
2072 g_return_if_fail(collection
!= NULL
);
2073 g_return_if_fail(IS_COLLECTION(collection
));
2075 if (callback
!= collection
->target_cb
)
2076 gdk_window_set_cursor(GTK_WIDGET(collection
)->window
,
2077 callback
? crosshair
: NULL
);
2079 collection
->target_cb
= callback
;
2080 collection
->target_data
= user_data
;
2082 if (collection
->cursor_item
!= -1)
2083 collection_draw_item(collection
, collection
->cursor_item
,
2087 /* Move the cursor by the given row and column offsets. */
2088 void collection_move_cursor(Collection
*collection
, int drow
, int dcol
)
2091 int first
, last
, total_rows
;
2093 g_return_if_fail(collection
!= NULL
);
2094 g_return_if_fail(IS_COLLECTION(collection
));
2096 get_visible_limits(collection
, &first
, &last
);
2098 item
= collection
->cursor_item
;
2106 row
= item
/ collection
->columns
;
2107 col
= item
% collection
->columns
;
2111 else if (row
> last
)
2114 row
= MAX(row
+ drow
, 0);
2116 col
= MAX(col
+ dcol
, 0);
2119 if (col
>= collection
->columns
)
2120 col
= collection
->columns
- 1;
2122 total_rows
= (collection
->number_of_items
+ collection
->columns
- 1)
2123 / collection
->columns
;
2125 if (row
>= total_rows
- 1)
2127 row
= total_rows
- 1;
2128 item
= col
+ row
* collection
->columns
;
2129 if (item
>= collection
->number_of_items
)
2135 item
= col
+ row
* collection
->columns
;
2137 if (item
>= 0 && item
< collection
->number_of_items
)
2138 collection_set_cursor_item(collection
, item
);