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
);
139 static gint
focus_in(GtkWidget
*widget
, GdkEventFocus
*event
);
140 static gint
focus_out(GtkWidget
*widget
, GdkEventFocus
*event
);
142 static void draw_one_item(Collection
*collection
, int item
, GdkRectangle
*area
)
144 if (item
< collection
->number_of_items
)
146 collection
->draw_item((GtkWidget
*) collection
,
147 &collection
->items
[item
],
149 if (item
== collection
->wink_item
)
150 gdk_draw_rectangle(((GtkWidget
*) collection
)->window
,
151 ((GtkWidget
*) collection
)->style
->black_gc
,
154 area
->width
- 1, area
->height
- 1);
156 if (item
== collection
->cursor_item
)
158 gdk_draw_rectangle(((GtkWidget
*) collection
)->window
,
159 collection
->target_cb
160 ? ((GtkWidget
*) collection
)->style
->white_gc
161 : ((GtkWidget
*) collection
)->style
->black_gc
,
163 area
->x
+ 1, area
->y
+ 1,
164 area
->width
- 3, area
->height
- 3);
168 GtkType
collection_get_type(void)
170 static guint my_type
= 0;
174 static const GtkTypeInfo my_info
=
178 sizeof(CollectionClass
),
179 (GtkClassInitFunc
) collection_class_init
,
180 (GtkObjectInitFunc
) collection_init
,
181 NULL
, /* Reserved 1 */
182 NULL
, /* Reserved 2 */
183 (GtkClassInitFunc
) NULL
/* base_class_init_func */
186 my_type
= gtk_type_unique(gtk_widget_get_type(),
193 static void collection_class_init(CollectionClass
*class)
195 GtkObjectClass
*object_class
;
196 GtkWidgetClass
*widget_class
;
198 object_class
= (GtkObjectClass
*) class;
199 widget_class
= (GtkWidgetClass
*) class;
201 parent_class
= gtk_type_class(gtk_widget_get_type());
203 gtk_object_add_arg_type("Collection::vadjustment",
205 GTK_ARG_READWRITE
| GTK_ARG_CONSTRUCT
,
208 object_class
->destroy
= collection_destroy
;
209 object_class
->finalize
= collection_finalize
;
211 widget_class
->realize
= collection_realize
;
212 widget_class
->draw
= collection_draw
;
213 widget_class
->expose_event
= collection_expose
;
214 widget_class
->size_request
= collection_size_request
;
215 widget_class
->size_allocate
= collection_size_allocate
;
217 widget_class
->key_press_event
= collection_key_press
;
218 widget_class
->button_press_event
= collection_button_press
;
219 widget_class
->button_release_event
= collection_button_release
;
220 widget_class
->motion_notify_event
= collection_motion_notify
;
221 widget_class
->focus_in_event
= focus_in
;
222 widget_class
->focus_out_event
= focus_out
;
224 object_class
->set_arg
= collection_set_arg
;
225 object_class
->get_arg
= collection_get_arg
;
227 class->open_item
= NULL
;
228 class->drag_selection
= NULL
;
229 class->show_menu
= NULL
;
230 class->gain_selection
= NULL
;
231 class->lose_selection
= NULL
;
233 collection_signals
[OPEN_ITEM
] = gtk_signal_new("open_item",
236 GTK_SIGNAL_OFFSET(CollectionClass
,
238 gtk_marshal_NONE__POINTER_UINT
,
242 collection_signals
[DRAG_SELECTION
] = gtk_signal_new("drag_selection",
245 GTK_SIGNAL_OFFSET(CollectionClass
,
247 gtk_marshal_NONE__POINTER_UINT
,
251 collection_signals
[SHOW_MENU
] = gtk_signal_new("show_menu",
254 GTK_SIGNAL_OFFSET(CollectionClass
,
256 gtk_marshal_NONE__POINTER_INT
,
260 collection_signals
[GAIN_SELECTION
] = gtk_signal_new("gain_selection",
263 GTK_SIGNAL_OFFSET(CollectionClass
,
265 gtk_marshal_NONE__UINT
,
268 collection_signals
[LOSE_SELECTION
] = gtk_signal_new("lose_selection",
271 GTK_SIGNAL_OFFSET(CollectionClass
,
273 gtk_marshal_NONE__UINT
,
277 gtk_object_class_add_signals(object_class
,
278 collection_signals
, LAST_SIGNAL
);
281 static void collection_init(Collection
*object
)
283 g_return_if_fail(object
!= NULL
);
284 g_return_if_fail(IS_COLLECTION(object
));
287 crosshair
= gdk_cursor_new(GDK_CROSSHAIR
);
289 GTK_WIDGET_SET_FLAGS(GTK_WIDGET(object
), GTK_CAN_FOCUS
);
291 object
->panel
= FALSE
;
292 object
->number_of_items
= 0;
293 object
->number_selected
= 0;
295 object
->item_width
= 64;
296 object
->item_height
= 64;
298 object
->paint_level
= PAINT_OVERWRITE
;
299 object
->last_scroll
= 0;
301 object
->items
= g_malloc(sizeof(CollectionItem
) * MINIMUM_ITEMS
);
302 object
->cursor_item
= -1;
303 object
->wink_item
= -1;
304 object
->array_size
= MINIMUM_ITEMS
;
305 object
->draw_item
= default_draw_item
;
306 object
->test_point
= default_test_point
;
308 object
->buttons_pressed
= 0;
309 object
->may_drag
= FALSE
;
314 GtkWidget
* collection_new(GtkAdjustment
*vadj
)
317 g_return_val_if_fail(GTK_IS_ADJUSTMENT(vadj
), NULL
);
319 return GTK_WIDGET(gtk_widget_new(collection_get_type(),
324 void collection_set_functions(Collection
*collection
,
325 CollectionDrawFunc draw_item
,
326 CollectionTestFunc test_point
)
330 g_return_if_fail(collection
!= NULL
);
331 g_return_if_fail(IS_COLLECTION(collection
));
333 widget
= GTK_WIDGET(collection
);
336 draw_item
= default_draw_item
;
338 test_point
= default_test_point
;
340 collection
->draw_item
= draw_item
;
341 collection
->test_point
= test_point
;
343 if (GTK_WIDGET_REALIZED(widget
))
345 collection
->paint_level
= PAINT_CLEAR
;
346 gtk_widget_queue_clear(widget
);
350 /* After this we are unusable, but our data (if any) is still hanging around.
351 * It will be freed later with finalize.
353 static void collection_destroy(GtkObject
*object
)
355 Collection
*collection
;
357 g_return_if_fail(object
!= NULL
);
358 g_return_if_fail(IS_COLLECTION(object
));
360 collection
= COLLECTION(object
);
362 if (collection
->wink_item
!= -1)
364 collection
->wink_item
= -1;
365 gtk_timeout_remove(collection
->wink_timeout
);
368 gtk_signal_disconnect_by_data(GTK_OBJECT(collection
->vadj
),
371 if (GTK_OBJECT_CLASS(parent_class
)->destroy
)
372 (*GTK_OBJECT_CLASS(parent_class
)->destroy
)(object
);
375 /* This is the last thing that happens to us. Free all data. */
376 static void collection_finalize(GtkObject
*object
)
378 Collection
*collection
;
380 collection
= COLLECTION(object
);
382 if (collection
->vadj
)
384 gtk_object_unref(GTK_OBJECT(collection
->vadj
));
387 g_free(collection
->items
);
390 static void collection_realize(GtkWidget
*widget
)
392 Collection
*collection
;
393 GdkWindowAttr attributes
;
394 gint attributes_mask
;
395 GdkGCValues xor_values
;
397 g_return_if_fail(widget
!= NULL
);
398 g_return_if_fail(IS_COLLECTION(widget
));
399 g_return_if_fail(widget
->parent
!= NULL
);
401 GTK_WIDGET_SET_FLAGS(widget
, GTK_REALIZED
);
402 collection
= COLLECTION(widget
);
404 attributes
.x
= widget
->allocation
.x
;
405 attributes
.y
= widget
->allocation
.y
;
406 attributes
.width
= widget
->allocation
.width
;
407 attributes
.height
= widget
->allocation
.height
;
408 attributes
.wclass
= GDK_INPUT_OUTPUT
;
409 attributes
.window_type
= GDK_WINDOW_CHILD
;
410 attributes
.event_mask
= gtk_widget_get_events(widget
) |
412 GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK
|
413 GDK_BUTTON1_MOTION_MASK
| GDK_BUTTON2_MOTION_MASK
|
414 GDK_BUTTON3_MOTION_MASK
;
415 attributes
.visual
= gtk_widget_get_visual(widget
);
416 attributes
.colormap
= gtk_widget_get_colormap(widget
);
418 attributes_mask
= GDK_WA_X
| GDK_WA_Y
|
419 GDK_WA_VISUAL
| GDK_WA_COLORMAP
;
420 widget
->window
= gdk_window_new(widget
->parent
->window
,
421 &attributes
, attributes_mask
);
423 widget
->style
= gtk_style_attach(widget
->style
, widget
->window
);
425 gdk_window_set_user_data(widget
->window
, widget
);
427 gdk_window_set_background(widget
->window
,
428 &widget
->style
->base
[GTK_STATE_INSENSITIVE
]);
430 /* Try to stop everything flickering horribly */
431 gdk_window_set_static_gravities(widget
->window
, TRUE
);
433 set_vadjustment(collection
);
435 xor_values
.function
= GDK_XOR
;
436 xor_values
.foreground
.red
= 0xffff;
437 xor_values
.foreground
.green
= 0xffff;
438 xor_values
.foreground
.blue
= 0xffff;
439 gdk_color_alloc(gtk_widget_get_colormap(widget
),
440 &xor_values
.foreground
);
441 collection
->xor_gc
= gdk_gc_new_with_values(widget
->window
,
447 static void collection_size_request(GtkWidget
*widget
,
448 GtkRequisition
*requisition
)
450 requisition
->width
= MIN_WIDTH
;
451 requisition
->height
= MIN_HEIGHT
;
454 static void collection_size_allocate(GtkWidget
*widget
,
455 GtkAllocation
*allocation
)
457 Collection
*collection
;
460 g_return_if_fail(widget
!= NULL
);
461 g_return_if_fail(IS_COLLECTION(widget
));
462 g_return_if_fail(allocation
!= NULL
);
464 collection
= COLLECTION(widget
);
466 old_columns
= collection
->columns
;
467 if (widget
->allocation
.x
!= allocation
->x
468 || widget
->allocation
.y
!= allocation
->y
)
469 collection
->paint_level
= PAINT_CLEAR
;
471 widget
->allocation
= *allocation
;
473 collection
->columns
= allocation
->width
/ collection
->item_width
;
474 if (collection
->columns
< 1)
475 collection
->columns
= 1;
477 if (GTK_WIDGET_REALIZED(widget
))
479 gdk_window_move_resize(widget
->window
,
480 allocation
->x
, allocation
->y
,
481 allocation
->width
, allocation
->height
);
483 if (old_columns
!= collection
->columns
)
485 collection
->paint_level
= PAINT_CLEAR
;
486 gtk_widget_queue_clear(widget
);
489 set_vadjustment(collection
);
491 if (collection
->cursor_item
!= -1)
492 scroll_to_show(collection
, collection
->cursor_item
);
496 static gint
collection_paint(Collection
*collection
,
499 GdkRectangle whole
, item_area
;
504 int start_row
, last_row
;
505 int start_col
, last_col
;
509 scroll
= collection
->vadj
->value
;
511 widget
= GTK_WIDGET(collection
);
513 if (collection
->paint_level
> PAINT_NORMAL
|| area
== NULL
)
516 gdk_window_get_size(widget
->window
, &width
, &height
);
521 whole
.height
= height
;
525 if (collection
->paint_level
== PAINT_CLEAR
526 && !collection
->lasso_box
)
527 gdk_window_clear(widget
->window
);
529 collection
->paint_level
= PAINT_NORMAL
;
532 /* Calculate the ranges to plot */
533 start_row
= (area
->y
+ scroll
) / collection
->item_height
;
534 last_row
= (area
->y
+ area
->height
- 1 + scroll
)
535 / collection
->item_height
;
538 start_col
= area
->x
/ collection
->item_width
;
539 phys_last_col
= (area
->x
+ area
->width
- 1) / collection
->item_width
;
541 if (collection
->lasso_box
)
543 /* You can't be too careful with lasso boxes...
545 * clip gives the total area drawn over (this may be larger
546 * than the requested area). It's used to redraw the lasso
549 clip
.x
= start_col
* collection
->item_width
;
550 clip
.y
= start_row
* collection
->item_height
- scroll
;
551 clip
.width
= (phys_last_col
- start_col
+ 1)
552 * collection
->item_width
;
553 clip
.height
= (last_row
- start_row
+ 1)
554 * collection
->item_height
;
556 gdk_window_clear_area(widget
->window
,
557 clip
.x
, clip
.y
, clip
.width
, clip
.height
);
560 if (start_col
< collection
->columns
)
562 if (phys_last_col
>= collection
->columns
)
563 last_col
= collection
->columns
- 1;
565 last_col
= phys_last_col
;
569 item
= row
* collection
->columns
+ col
;
570 item_area
.width
= collection
->item_width
;
571 item_area
.height
= collection
->item_height
;
573 while ((item
== 0 || item
< collection
->number_of_items
)
576 item_area
.x
= col
* collection
->item_width
;
577 item_area
.y
= row
* collection
->item_height
- scroll
;
579 draw_one_item(collection
, item
, &item_area
);
586 item
= row
* collection
->columns
+ col
;
593 if (collection
->lasso_box
)
595 gdk_gc_set_clip_rectangle(collection
->xor_gc
, &clip
);
596 draw_lasso_box(collection
);
597 gdk_gc_set_clip_rectangle(collection
->xor_gc
, NULL
);
603 static void default_draw_item( GtkWidget
*widget
,
604 CollectionItem
*item
,
607 gdk_draw_arc(widget
->window
,
608 item
->selected
? widget
->style
->white_gc
609 : widget
->style
->black_gc
,
612 area
->width
, area
->height
,
617 static gboolean
default_test_point(Collection
*collection
,
618 int point_x
, int point_y
,
619 CollectionItem
*item
,
620 int width
, int height
)
624 /* Convert to point in unit circle */
625 f_x
= ((float) point_x
/ width
) - 0.5;
626 f_y
= ((float) point_y
/ height
) - 0.5;
628 return (f_x
* f_x
) + (f_y
* f_y
) <= .25;
631 static void collection_set_arg( GtkObject
*object
,
635 Collection
*collection
;
637 collection
= COLLECTION(object
);
641 case ARG_VADJUSTMENT
:
642 collection_set_adjustment(collection
,
643 GTK_VALUE_POINTER(*arg
));
650 static void collection_set_adjustment( Collection
*collection
,
654 g_return_if_fail (GTK_IS_ADJUSTMENT (vadj
));
656 vadj
= GTK_ADJUSTMENT(gtk_adjustment_new(0.0,
659 if (collection
->vadj
&& (collection
->vadj
!= vadj
))
661 gtk_signal_disconnect_by_data(GTK_OBJECT(collection
->vadj
),
663 gtk_object_unref(GTK_OBJECT(collection
->vadj
));
666 if (collection
->vadj
!= vadj
)
668 collection
->vadj
= vadj
;
669 gtk_object_ref(GTK_OBJECT(collection
->vadj
));
670 gtk_object_sink(GTK_OBJECT(collection
->vadj
));
672 gtk_signal_connect(GTK_OBJECT(collection
->vadj
),
674 (GtkSignalFunc
) collection_adjustment
,
676 gtk_signal_connect(GTK_OBJECT(collection
->vadj
),
678 (GtkSignalFunc
) collection_adjustment
,
680 gtk_signal_connect(GTK_OBJECT(collection
->vadj
),
682 (GtkSignalFunc
) collection_disconnect
,
684 collection_adjustment(vadj
, collection
);
688 static void collection_get_arg( GtkObject
*object
,
692 Collection
*collection
;
694 collection
= COLLECTION(object
);
698 case ARG_VADJUSTMENT
:
699 GTK_VALUE_POINTER(*arg
) = collection
->vadj
;
702 arg
->type
= GTK_TYPE_INVALID
;
707 /* Something about the adjustment has changed */
708 static void collection_adjustment(GtkAdjustment
*adjustment
,
709 Collection
*collection
)
713 g_return_if_fail(adjustment
!= NULL
);
714 g_return_if_fail(GTK_IS_ADJUSTMENT(adjustment
));
715 g_return_if_fail(collection
!= NULL
);
716 g_return_if_fail(IS_COLLECTION(collection
));
718 diff
= ((gint
) adjustment
->value
) - collection
->last_scroll
;
722 collection
->last_scroll
= adjustment
->value
;
724 scroll_by(collection
, diff
);
728 static void collection_disconnect(GtkAdjustment
*adjustment
,
729 Collection
*collection
)
731 g_return_if_fail(adjustment
!= NULL
);
732 g_return_if_fail(GTK_IS_ADJUSTMENT(adjustment
));
733 g_return_if_fail(collection
!= NULL
);
734 g_return_if_fail(IS_COLLECTION(collection
));
736 collection_set_adjustment(collection
, NULL
);
739 static void set_vadjustment(Collection
*collection
)
745 widget
= GTK_WIDGET(collection
);
747 if (!GTK_WIDGET_REALIZED(widget
))
750 gdk_window_get_size(widget
->window
, NULL
, &height
);
751 cols
= collection
->columns
;
752 rows
= (collection
->number_of_items
+ cols
- 1) / cols
;
754 collection
->vadj
->lower
= 0.0;
755 collection
->vadj
->upper
= collection
->item_height
* rows
;
757 collection
->vadj
->step_increment
=
758 MIN(collection
->vadj
->upper
, collection
->item_height
/ 4);
760 collection
->vadj
->page_increment
=
761 MIN(collection
->vadj
->upper
,
764 collection
->vadj
->page_size
= MIN(collection
->vadj
->upper
, height
);
766 collection
->vadj
->value
= MIN(collection
->vadj
->value
,
767 collection
->vadj
->upper
- collection
->vadj
->page_size
);
769 collection
->vadj
->value
= MAX(collection
->vadj
->value
, 0.0);
771 gtk_signal_emit_by_name(GTK_OBJECT(collection
->vadj
), "changed");
774 static void collection_draw(GtkWidget
*widget
, GdkRectangle
*area
)
776 Collection
*collection
;
778 g_return_if_fail(widget
!= NULL
);
779 g_return_if_fail(IS_COLLECTION(widget
));
780 g_return_if_fail(area
!= NULL
); /* Not actually used */
782 collection
= COLLECTION(widget
);
784 if (collection
->paint_level
> PAINT_NORMAL
)
785 collection_paint(collection
, area
);
788 static gint
collection_expose(GtkWidget
*widget
, GdkEventExpose
*event
)
790 g_return_val_if_fail(widget
!= NULL
, FALSE
);
791 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
792 g_return_val_if_fail(event
!= NULL
, FALSE
);
794 collection_paint(COLLECTION(widget
), &event
->area
);
799 /* Positive makes the contents go move up the screen */
800 static void scroll_by(Collection
*collection
, gint diff
)
806 GdkRectangle new_area
;
811 widget
= GTK_WIDGET(collection
);
813 if (collection
->lasso_box
)
814 remove_lasso_box(collection
);
816 gdk_window_get_size(widget
->window
, &width
, &height
);
818 new_area
.width
= width
;
825 new_area
.y
= height
- amount
;
835 new_area
.height
= amount
;
839 gdk_draw_pixmap(widget
->window
,
840 widget
->style
->white_gc
,
848 /* We have to redraw everything because any pending
849 * expose events now contain invalid areas.
850 * Don't need to clear the area first though...
852 if (collection
->paint_level
< PAINT_OVERWRITE
)
853 collection
->paint_level
= PAINT_OVERWRITE
;
856 collection
->paint_level
= PAINT_CLEAR
;
858 gdk_window_clear_area(widget
->window
,
860 width
, new_area
.height
);
861 collection_paint(collection
, NULL
);
864 static void resize_arrays(Collection
*collection
, guint new_size
)
866 g_return_if_fail(collection
!= NULL
);
867 g_return_if_fail(IS_COLLECTION(collection
));
868 g_return_if_fail(new_size
>= collection
->number_of_items
);
870 collection
->items
= g_realloc(collection
->items
,
871 sizeof(CollectionItem
) * new_size
);
872 collection
->array_size
= new_size
;
875 static void return_pressed(Collection
*collection
)
877 int item
= collection
->cursor_item
;
878 CollectionTargetFunc cb
= collection
->target_cb
;
879 gpointer data
= collection
->target_data
;
881 collection_target(collection
, NULL
, NULL
);
882 if (item
< 0 || item
>= collection
->number_of_items
)
887 cb(collection
, item
, data
);
891 gtk_signal_emit(GTK_OBJECT(collection
),
892 collection_signals
[OPEN_ITEM
],
893 collection
->items
[item
].data
,
897 static gint
collection_key_press(GtkWidget
*widget
, GdkEventKey
*event
)
899 Collection
*collection
;
902 g_return_val_if_fail(widget
!= NULL
, FALSE
);
903 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
904 g_return_val_if_fail(event
!= NULL
, FALSE
);
906 collection
= (Collection
*) widget
;
907 item
= collection
->cursor_item
;
909 switch (event
->keyval
)
912 collection_move_cursor(collection
, 0, -1);
915 collection_move_cursor(collection
, 0, 1);
918 collection_move_cursor(collection
, -1, 0);
921 collection_move_cursor(collection
, 1, 0);
924 collection_set_cursor_item(collection
, 0);
927 collection_set_cursor_item(collection
,
928 MAX((gint
) collection
->number_of_items
- 1, 0));
931 collection_move_cursor(collection
, -10, 0);
934 collection_move_cursor(collection
, 10, 0);
937 return_pressed(collection
);
940 if (!collection
->target_cb
)
941 return FALSE
; /* Pass it on */
942 collection_target(collection
, NULL
, NULL
);
945 if (item
>=0 && item
< collection
->number_of_items
)
946 collection_toggle_item(collection
, item
);
955 static gint
collection_button_press(GtkWidget
*widget
,
956 GdkEventButton
*event
)
958 Collection
*collection
;
965 g_return_val_if_fail(widget
!= NULL
, FALSE
);
966 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
967 g_return_val_if_fail(event
!= NULL
, FALSE
);
969 collection
= COLLECTION(widget
);
971 collection
->item_clicked
= -1;
973 if (event
->button
> 3)
977 /* Wheel mouse scrolling */
978 if (event
->button
== 4)
979 diff
= -((signed int) collection
->item_height
) / 4;
980 else if (event
->button
== 5)
981 diff
= collection
->item_height
/ 4;
987 int old_value
= collection
->vadj
->value
;
989 gboolean box
= collection
->lasso_box
;
991 new_value
= CLAMP(old_value
+ diff
, 0.0,
992 collection
->vadj
->upper
993 - collection
->vadj
->page_size
);
994 diff
= new_value
- old_value
;
999 remove_lasso_box(collection
);
1000 collection
->drag_box_y
[0] -= diff
;
1002 collection
->vadj
->value
= new_value
;
1003 gtk_signal_emit_by_name(
1004 GTK_OBJECT(collection
->vadj
),
1007 add_lasso_box(collection
);
1013 if (collection
->cursor_item
!= -1)
1014 collection_set_cursor_item(collection
, -1);
1016 scroll
= collection
->vadj
->value
;
1018 if (event
->type
== GDK_BUTTON_PRESS
&&
1019 event
->button
!= collection_menu_button
)
1021 if (collection
->buttons_pressed
++ == 0)
1022 gtk_grab_add(widget
);
1024 return FALSE
; /* Ignore extra presses */
1027 if (event
->state
& GDK_CONTROL_MASK
&& !collection_single_click
)
1030 action
= event
->button
;
1032 /* Ignore all clicks while we are dragging a lasso box */
1033 if (collection
->lasso_box
)
1036 col
= event
->x
/ collection
->item_width
;
1037 row
= (event
->y
+ scroll
) / collection
->item_height
;
1039 if (col
< 0 || row
< 0 || col
>= collection
->columns
)
1043 item
= col
+ row
* collection
->columns
;
1044 if (item
>= collection
->number_of_items
1046 !collection
->test_point(collection
,
1047 event
->x
- col
* collection
->item_width
,
1048 event
->y
- row
* collection
->item_height
1050 &collection
->items
[item
],
1051 collection
->item_width
,
1052 collection
->item_height
))
1058 if (collection
->target_cb
)
1060 CollectionTargetFunc cb
= collection
->target_cb
;
1061 gpointer data
= collection
->target_data
;
1063 collection_target(collection
, NULL
, NULL
);
1064 if (collection
->buttons_pressed
)
1066 gtk_grab_remove(widget
);
1067 collection
->buttons_pressed
= 0;
1069 if (item
> -1 && event
->button
!= collection_menu_button
)
1070 cb(collection
, item
, data
);
1074 collection
->drag_box_x
[0] = event
->x
;
1075 collection
->drag_box_y
[0] = event
->y
;
1076 collection
->item_clicked
= item
;
1078 stacked_time
= current_event_time
;
1079 current_event_time
= event
->time
;
1081 if (event
->button
== collection_menu_button
)
1083 gtk_signal_emit(GTK_OBJECT(collection
),
1084 collection_signals
[SHOW_MENU
],
1088 else if (event
->type
== GDK_2BUTTON_PRESS
&& collection
->panel
)
1092 else if ((event
->type
== GDK_2BUTTON_PRESS
&& !collection_single_click
)
1093 || collection
->panel
)
1097 if (collection
->buttons_pressed
)
1099 gtk_grab_remove(widget
);
1100 collection
->buttons_pressed
= 0;
1102 collection_unselect_item(collection
, item
);
1103 gtk_signal_emit(GTK_OBJECT(collection
),
1104 collection_signals
[OPEN_ITEM
],
1105 collection
->items
[item
].data
,
1109 else if (event
->type
== GDK_BUTTON_PRESS
)
1111 collection
->may_drag
= event
->button
< collection_menu_button
;
1117 if (!collection
->items
[item
].selected
)
1119 collection_select_item(collection
,
1121 collection_clear_except(collection
,
1126 collection_toggle_item(collection
, item
);
1128 else if (action
== 1)
1129 collection_clear_selection(collection
);
1132 current_event_time
= stacked_time
;
1136 static gint
collection_button_release(GtkWidget
*widget
,
1137 GdkEventButton
*event
)
1139 Collection
*collection
;
1143 int col
, start_col
, last_col
;
1149 g_return_val_if_fail(widget
!= NULL
, FALSE
);
1150 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
1151 g_return_val_if_fail(event
!= NULL
, FALSE
);
1153 collection
= COLLECTION(widget
);
1154 button
= event
->button
;
1156 scroll
= collection
->vadj
->value
;
1158 if (event
->button
> 3 || event
->button
== collection_menu_button
)
1160 if (collection
->buttons_pressed
== 0)
1162 if (--collection
->buttons_pressed
== 0)
1163 gtk_grab_remove(widget
);
1165 return FALSE
; /* Wait until ALL buttons are up */
1167 if (!collection
->lasso_box
)
1169 int item
= collection
->item_clicked
;
1171 if (collection_single_click
&& item
> -1
1172 && item
< collection
->number_of_items
1173 && (event
->state
& GDK_CONTROL_MASK
) == 0)
1175 int dx
= event
->x
- collection
->drag_box_x
[0];
1176 int dy
= event
->y
- collection
->drag_box_y
[0];
1178 if (ABS(dx
) + ABS(dy
) > 9)
1181 collection_unselect_item(collection
, item
);
1182 gtk_signal_emit(GTK_OBJECT(collection
),
1183 collection_signals
[OPEN_ITEM
],
1184 collection
->items
[item
].data
,
1191 remove_lasso_box(collection
);
1193 w
= collection
->item_width
;
1194 h
= collection
->item_height
;
1196 top
= collection
->drag_box_y
[0] + scroll
;
1197 bottom
= collection
->drag_box_y
[1] + scroll
;
1208 row
= MAX(top
/ h
, 0);
1209 last_row
= bottom
/ h
;
1211 top
= collection
->drag_box_x
[0]; /* (left) */
1212 bottom
= collection
->drag_box_x
[1];
1222 start_col
= MAX(top
/ w
, 0);
1223 last_col
= bottom
/ w
;
1224 if (last_col
>= collection
->columns
)
1225 last_col
= collection
->columns
- 1;
1227 stacked_time
= current_event_time
;
1228 current_event_time
= event
->time
;
1230 while (row
<= last_row
)
1233 item
= row
* collection
->columns
+ col
;
1234 while (col
<= last_col
)
1236 if (item
>= collection
->number_of_items
)
1238 current_event_time
= stacked_time
;
1243 collection_select_item(collection
, item
);
1245 collection_toggle_item(collection
, item
);
1252 current_event_time
= stacked_time
;
1257 static gint
collection_motion_notify(GtkWidget
*widget
,
1258 GdkEventMotion
*event
)
1260 Collection
*collection
;
1264 g_return_val_if_fail(widget
!= NULL
, FALSE
);
1265 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
1266 g_return_val_if_fail(event
!= NULL
, FALSE
);
1268 collection
= COLLECTION(widget
);
1270 if (collection
->buttons_pressed
== 0)
1273 stacked_time
= current_event_time
;
1274 current_event_time
= event
->time
;
1276 if (event
->window
!= widget
->window
)
1277 gdk_window_get_pointer(widget
->window
, &x
, &y
, NULL
);
1284 if (collection
->lasso_box
)
1286 int new_value
= 0, diff
;
1289 gdk_window_get_size(widget
->window
, NULL
, &height
);
1293 int old_value
= collection
->vadj
->value
;
1295 new_value
= MAX(old_value
+ y
/ 10, 0.0);
1296 diff
= new_value
- old_value
;
1298 else if (y
> height
)
1300 int old_value
= collection
->vadj
->value
;
1302 new_value
= MIN(old_value
+ (y
- height
) / 10,
1303 collection
->vadj
->upper
1304 - collection
->vadj
->page_size
);
1305 diff
= new_value
- old_value
;
1310 remove_lasso_box(collection
);
1311 collection
->drag_box_x
[1] = x
;
1312 collection
->drag_box_y
[1] = y
;
1316 collection
->drag_box_y
[0] -= diff
;
1317 collection
->vadj
->value
= new_value
;
1318 gtk_signal_emit_by_name(GTK_OBJECT(collection
->vadj
),
1321 add_lasso_box(collection
);
1323 else if (collection
->may_drag
)
1325 int dx
= x
- collection
->drag_box_x
[0];
1326 int dy
= y
- collection
->drag_box_y
[0];
1328 if (abs(dx
) > 9 || abs(dy
) > 9)
1331 int scroll
= collection
->vadj
->value
;
1333 collection
->may_drag
= FALSE
;
1335 col
= collection
->drag_box_x
[0]
1336 / collection
->item_width
;
1337 row
= (collection
->drag_box_y
[0] + scroll
)
1338 / collection
->item_height
;
1339 item
= item_at_row_col(collection
, row
, col
);
1341 if (item
!= -1 && collection
->test_point(collection
,
1342 collection
->drag_box_x
[0] -
1343 col
* collection
->item_width
,
1344 collection
->drag_box_y
[0]
1345 - row
* collection
->item_height
1347 &collection
->items
[item
],
1348 collection
->item_width
,
1349 collection
->item_height
))
1351 collection
->buttons_pressed
= 0;
1352 gtk_grab_remove(widget
);
1353 collection_select_item(collection
, item
);
1354 gtk_signal_emit(GTK_OBJECT(collection
),
1355 collection_signals
[DRAG_SELECTION
],
1357 collection
->number_selected
);
1361 collection
->drag_box_x
[1] = x
;
1362 collection
->drag_box_y
[1] = y
;
1363 add_lasso_box(collection
);
1368 current_event_time
= stacked_time
;
1372 static void add_lasso_box(Collection
*collection
)
1374 g_return_if_fail(collection
!= NULL
);
1375 g_return_if_fail(IS_COLLECTION(collection
));
1376 g_return_if_fail(collection
->lasso_box
== FALSE
);
1378 collection
->lasso_box
= TRUE
;
1379 draw_lasso_box(collection
);
1382 static void draw_lasso_box(Collection
*collection
)
1385 int x
, y
, width
, height
;
1387 widget
= GTK_WIDGET(collection
);
1389 x
= MIN(collection
->drag_box_x
[0], collection
->drag_box_x
[1]);
1390 y
= MIN(collection
->drag_box_y
[0], collection
->drag_box_y
[1]);
1391 width
= abs(collection
->drag_box_x
[1] - collection
->drag_box_x
[0]);
1392 height
= abs(collection
->drag_box_y
[1] - collection
->drag_box_y
[0]);
1394 gdk_draw_rectangle(widget
->window
, collection
->xor_gc
, FALSE
,
1395 x
, y
, width
, height
);
1398 static void remove_lasso_box(Collection
*collection
)
1400 g_return_if_fail(collection
!= NULL
);
1401 g_return_if_fail(IS_COLLECTION(collection
));
1402 g_return_if_fail(collection
->lasso_box
== TRUE
);
1404 draw_lasso_box(collection
);
1406 collection
->lasso_box
= FALSE
;
1411 /* Convert a row,col address to an item number, or -1 if none */
1412 static int item_at_row_col(Collection
*collection
, int row
, int col
)
1416 if (row
< 0 || col
< 0 || col
>= collection
->columns
)
1419 item
= col
+ row
* collection
->columns
;
1421 if (item
>= collection
->number_of_items
)
1426 /* Make sure that 'item' is fully visible (vertically), scrolling if not. */
1427 static void scroll_to_show(Collection
*collection
, int item
)
1429 int first
, last
, row
;
1431 g_return_if_fail(collection
!= NULL
);
1432 g_return_if_fail(IS_COLLECTION(collection
));
1434 row
= item
/ collection
->columns
;
1435 get_visible_limits(collection
, &first
, &last
);
1439 gtk_adjustment_set_value(collection
->vadj
,
1440 row
* collection
->item_height
);
1442 else if (row
>= last
)
1444 GtkWidget
*widget
= (GtkWidget
*) collection
;
1447 if (GTK_WIDGET_REALIZED(widget
))
1449 gdk_window_get_size(widget
->window
, NULL
, &height
);
1450 gtk_adjustment_set_value(collection
->vadj
,
1451 (row
+ 1) * collection
->item_height
- height
);
1456 /* Return the first and last rows which are [partly] visible. Does not
1457 * ensure that the rows actually exist (contain items).
1459 static void get_visible_limits(Collection
*collection
, int *first
, int *last
)
1461 GtkWidget
*widget
= (GtkWidget
*) collection
;
1464 g_return_if_fail(collection
!= NULL
);
1465 g_return_if_fail(IS_COLLECTION(collection
));
1466 g_return_if_fail(first
!= NULL
&& last
!= NULL
);
1468 if (!GTK_WIDGET_REALIZED(widget
))
1475 scroll
= collection
->vadj
->value
;
1476 gdk_window_get_size(widget
->window
, NULL
, &height
);
1478 *first
= MAX(scroll
/ collection
->item_height
, 0);
1479 *last
= (scroll
+ height
- 1) /collection
->item_height
;
1486 /* Unselect all items except number item (-1 to unselect everything) */
1487 static void collection_clear_except(Collection
*collection
, gint exception
)
1493 int end
; /* Selected items to end up with */
1495 widget
= GTK_WIDGET(collection
);
1496 scroll
= collection
->vadj
->value
;
1498 end
= exception
>= 0 && exception
< collection
->number_of_items
1499 ? collection
->items
[exception
].selected
!= 0 : 0;
1501 area
.width
= collection
->item_width
;
1502 area
.height
= collection
->item_height
;
1504 if (collection
->number_selected
== 0)
1507 while (collection
->number_selected
> end
)
1509 while (item
== exception
|| !collection
->items
[item
].selected
)
1512 area
.x
= (item
% collection
->columns
) * area
.width
;
1513 area
.y
= (item
/ collection
->columns
) * area
.height
1516 collection
->items
[item
++].selected
= FALSE
;
1517 gdk_window_clear_area(widget
->window
,
1518 area
.x
, area
.y
, area
.width
, area
.height
);
1519 collection_paint(collection
, &area
);
1521 collection
->number_selected
--;
1525 gtk_signal_emit(GTK_OBJECT(collection
),
1526 collection_signals
[LOSE_SELECTION
],
1527 current_event_time
);
1530 /* Cancel the current wink effect. */
1531 static void cancel_wink(Collection
*collection
)
1535 g_return_if_fail(collection
!= NULL
);
1536 g_return_if_fail(IS_COLLECTION(collection
));
1537 g_return_if_fail(collection
->wink_item
!= -1);
1539 item
= collection
->wink_item
;
1541 collection
->wink_item
= -1;
1542 gtk_timeout_remove(collection
->wink_timeout
);
1544 collection_draw_item(collection
, item
, TRUE
);
1547 static gboolean
cancel_wink_timeout(Collection
*collection
)
1551 g_return_val_if_fail(collection
!= NULL
, FALSE
);
1552 g_return_val_if_fail(IS_COLLECTION(collection
), FALSE
);
1553 g_return_val_if_fail(collection
->wink_item
!= -1, FALSE
);
1555 item
= collection
->wink_item
;
1557 collection
->wink_item
= -1;
1559 collection_draw_item(collection
, item
, TRUE
);
1564 static gint
focus_in(GtkWidget
*widget
, GdkEventFocus
*event
)
1566 g_return_val_if_fail(widget
!= NULL
, FALSE
);
1567 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
1568 g_return_val_if_fail(event
!= NULL
, FALSE
);
1570 GTK_WIDGET_SET_FLAGS(widget
, GTK_HAS_FOCUS
);
1571 gtk_widget_draw_focus(widget
);
1576 static gint
focus_out(GtkWidget
*widget
, GdkEventFocus
*event
)
1578 g_return_val_if_fail(widget
!= NULL
, FALSE
);
1579 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
1580 g_return_val_if_fail(event
!= NULL
, FALSE
);
1582 GTK_WIDGET_UNSET_FLAGS(widget
, GTK_HAS_FOCUS
);
1583 gtk_widget_draw_focus(widget
);
1588 /* Functions for managing collections */
1590 /* Remove all objects from the collection */
1591 void collection_clear(Collection
*collection
)
1595 g_return_if_fail(IS_COLLECTION(collection
));
1597 if (collection
->number_of_items
== 0)
1600 if (collection
->wink_item
!= -1)
1602 collection
->wink_item
= -1;
1603 gtk_timeout_remove(collection
->wink_timeout
);
1606 collection_set_cursor_item(collection
,
1607 collection
->cursor_item
== -1 ? -1: 0);
1608 prev_selected
= collection
->number_selected
;
1609 collection
->number_of_items
= collection
->number_selected
= 0;
1611 resize_arrays(collection
, MINIMUM_ITEMS
);
1613 collection
->paint_level
= PAINT_CLEAR
;
1615 gtk_widget_queue_clear(GTK_WIDGET(collection
));
1617 if (prev_selected
&& collection
->number_selected
== 0)
1618 gtk_signal_emit(GTK_OBJECT(collection
),
1619 collection_signals
[LOSE_SELECTION
],
1620 current_event_time
);
1623 /* Inserts a new item at the end. The new item is unselected, and its
1624 * number is returned.
1626 gint
collection_insert(Collection
*collection
, gpointer data
)
1630 g_return_val_if_fail(IS_COLLECTION(collection
), -1);
1632 item
= collection
->number_of_items
;
1634 if (item
>= collection
->array_size
)
1635 resize_arrays(collection
, item
+ (item
>> 1));
1637 collection
->items
[item
].data
= data
;
1638 collection
->items
[item
].selected
= FALSE
;
1640 collection
->number_of_items
++;
1642 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection
)))
1644 set_vadjustment(collection
);
1645 collection_draw_item(collection
,
1646 collection
->number_of_items
- 1,
1653 /* Unselect an item by number */
1654 void collection_unselect_item(Collection
*collection
, gint item
)
1656 g_return_if_fail(collection
!= NULL
);
1657 g_return_if_fail(IS_COLLECTION(collection
));
1658 g_return_if_fail(item
>= 0 && item
< collection
->number_of_items
);
1660 if (collection
->items
[item
].selected
)
1662 collection
->items
[item
].selected
= FALSE
;
1663 collection_draw_item(collection
, item
, TRUE
);
1665 if (--collection
->number_selected
== 0)
1666 gtk_signal_emit(GTK_OBJECT(collection
),
1667 collection_signals
[LOSE_SELECTION
],
1668 current_event_time
);
1672 /* Select an item by number */
1673 void collection_select_item(Collection
*collection
, gint item
)
1675 g_return_if_fail(collection
!= NULL
);
1676 g_return_if_fail(IS_COLLECTION(collection
));
1677 g_return_if_fail(item
>= 0 && item
< collection
->number_of_items
);
1679 if (collection
->items
[item
].selected
)
1680 return; /* Already selected */
1682 collection
->items
[item
].selected
= TRUE
;
1683 collection_draw_item(collection
, item
, TRUE
);
1685 if (collection
->number_selected
++ == 0)
1686 gtk_signal_emit(GTK_OBJECT(collection
),
1687 collection_signals
[GAIN_SELECTION
],
1688 current_event_time
);
1691 /* Toggle the selected state of an item (by number) */
1692 void collection_toggle_item(Collection
*collection
, gint item
)
1694 g_return_if_fail(collection
!= NULL
);
1695 g_return_if_fail(IS_COLLECTION(collection
));
1696 g_return_if_fail(item
>= 0 && item
< collection
->number_of_items
);
1698 if (collection
->items
[item
].selected
)
1700 collection
->items
[item
].selected
= FALSE
;
1701 if (--collection
->number_selected
== 0)
1702 gtk_signal_emit(GTK_OBJECT(collection
),
1703 collection_signals
[LOSE_SELECTION
],
1704 current_event_time
);
1708 collection
->items
[item
].selected
= TRUE
;
1709 if (collection
->number_selected
++ == 0)
1710 gtk_signal_emit(GTK_OBJECT(collection
),
1711 collection_signals
[GAIN_SELECTION
],
1712 current_event_time
);
1714 collection_draw_item(collection
, item
, TRUE
);
1717 /* Select all items in the collection */
1718 void collection_select_all(Collection
*collection
)
1725 g_return_if_fail(collection
!= NULL
);
1726 g_return_if_fail(IS_COLLECTION(collection
));
1728 widget
= GTK_WIDGET(collection
);
1729 scroll
= collection
->vadj
->value
;
1731 area
.width
= collection
->item_width
;
1732 area
.height
= collection
->item_height
;
1734 if (collection
->number_selected
== collection
->number_of_items
)
1735 return; /* Nothing to do */
1737 while (collection
->number_selected
< collection
->number_of_items
)
1739 while (collection
->items
[item
].selected
)
1742 area
.x
= (item
% collection
->columns
) * area
.width
;
1743 area
.y
= (item
/ collection
->columns
) * area
.height
1746 collection
->items
[item
++].selected
= TRUE
;
1747 gdk_window_clear_area(widget
->window
,
1748 area
.x
, area
.y
, area
.width
, area
.height
);
1749 collection_paint(collection
, &area
);
1751 collection
->number_selected
++;
1754 gtk_signal_emit(GTK_OBJECT(collection
),
1755 collection_signals
[GAIN_SELECTION
],
1756 current_event_time
);
1759 /* Unselect all items in the collection */
1760 void collection_clear_selection(Collection
*collection
)
1762 g_return_if_fail(collection
!= NULL
);
1763 g_return_if_fail(IS_COLLECTION(collection
));
1765 collection_clear_except(collection
, -1);
1768 /* Force a redraw of the specified item, if it is visible */
1769 void collection_draw_item(Collection
*collection
, gint item
, gboolean blank
)
1776 int area_y
, area_height
; /* NOT shorts! */
1778 g_return_if_fail(collection
!= NULL
);
1779 g_return_if_fail(IS_COLLECTION(collection
));
1780 g_return_if_fail(item
>= 0 &&
1781 (item
== 0 || item
< collection
->number_of_items
));
1783 widget
= GTK_WIDGET(collection
);
1784 if (!GTK_WIDGET_REALIZED(widget
))
1787 col
= item
% collection
->columns
;
1788 row
= item
/ collection
->columns
;
1789 scroll
= collection
->vadj
->value
; /* (round to int) */
1791 area
.x
= col
* collection
->item_width
;
1792 area_y
= row
* collection
->item_height
- scroll
;
1793 area
.width
= collection
->item_width
;
1794 area_height
= collection
->item_height
;
1796 if (area_y
+ area_height
< 0)
1799 gdk_window_get_size(widget
->window
, NULL
, &height
);
1801 if (area_y
> height
)
1805 area
.height
= area_height
;
1807 if (blank
|| collection
->lasso_box
)
1808 gdk_window_clear_area(widget
->window
,
1809 area
.x
, area
.y
, area
.width
, area
.height
);
1811 draw_one_item(collection
, item
, &area
);
1813 if (collection
->lasso_box
)
1815 gdk_gc_set_clip_rectangle(collection
->xor_gc
, &area
);
1816 draw_lasso_box(collection
);
1817 gdk_gc_set_clip_rectangle(collection
->xor_gc
, NULL
);
1821 void collection_set_item_size(Collection
*collection
, int width
, int height
)
1825 g_return_if_fail(collection
!= NULL
);
1826 g_return_if_fail(IS_COLLECTION(collection
));
1827 g_return_if_fail(width
> 4 && height
> 4);
1829 widget
= GTK_WIDGET(collection
);
1831 collection
->item_width
= width
;
1832 collection
->item_height
= height
;
1834 if (GTK_WIDGET_REALIZED(widget
))
1838 collection
->paint_level
= PAINT_CLEAR
;
1839 gdk_window_get_size(widget
->window
, &window_width
, NULL
);
1840 collection
->columns
= MAX(window_width
/ collection
->item_width
,
1843 set_vadjustment(collection
);
1844 if (collection
->cursor_item
!= -1)
1845 scroll_to_show(collection
, collection
->cursor_item
);
1846 gtk_widget_queue_draw(widget
);
1850 /* Cursor is positioned on item with the same data as before the sort.
1851 * Same for the wink item.
1853 void collection_qsort(Collection
*collection
,
1854 int (*compar
)(const void *, const void *))
1856 int cursor
, wink
, items
;
1857 gpointer cursor_data
= NULL
;
1858 gpointer wink_data
= NULL
;
1860 g_return_if_fail(collection
!= NULL
);
1861 g_return_if_fail(IS_COLLECTION(collection
));
1862 g_return_if_fail(compar
!= NULL
);
1864 items
= collection
->number_of_items
;
1866 wink
= collection
->wink_item
;
1867 if (wink
>= 0 && wink
< items
)
1868 wink_data
= collection
->items
[wink
].data
;
1872 cursor
= collection
->cursor_item
;
1873 if (cursor
>= 0 && cursor
< items
)
1874 cursor_data
= collection
->items
[cursor
].data
;
1878 if (collection
->wink_item
!= -1)
1880 collection
->wink_item
= -1;
1881 gtk_timeout_remove(collection
->wink_timeout
);
1884 qsort(collection
->items
, items
, sizeof(collection
->items
[0]), compar
);
1886 if (cursor
> -1 || wink
> -1)
1890 for (item
= 0; item
< items
; item
++)
1892 if (collection
->items
[item
].data
== cursor_data
)
1893 collection_set_cursor_item(collection
, item
);
1894 if (collection
->items
[item
].data
== wink_data
)
1895 collection_wink_item(collection
, item
);
1899 collection
->paint_level
= PAINT_CLEAR
;
1901 gtk_widget_queue_draw(GTK_WIDGET(collection
));
1904 /* Find an item in an unsorted collection.
1905 * Returns the item number, or -1 if not found.
1907 int collection_find_item(Collection
*collection
, gpointer data
,
1908 int (*compar
)(const void *, const void *))
1912 g_return_val_if_fail(collection
!= NULL
, -1);
1913 g_return_val_if_fail(IS_COLLECTION(collection
), -1);
1914 g_return_val_if_fail(compar
!= NULL
, -1);
1916 for (i
= 0; i
< collection
->number_of_items
; i
++)
1917 if (compar(&collection
->items
[i
].data
, &data
) == 0)
1923 /* The collection may be in either normal mode or panel mode.
1925 * - a single click calls open_item
1926 * - items are never selected
1927 * - lasso boxes are disabled
1929 void collection_set_panel(Collection
*collection
, gboolean panel
)
1931 g_return_if_fail(collection
!= NULL
);
1932 g_return_if_fail(IS_COLLECTION(collection
));
1934 collection
->panel
= panel
== TRUE
;
1936 if (collection
->panel
)
1938 collection_clear_selection(collection
);
1939 if (collection
->lasso_box
)
1940 remove_lasso_box(collection
);
1944 /* Return the number of the item under the point (x,y), or -1 for none.
1945 * This may call your test_point callback. The point is relative to the
1946 * collection's origin.
1948 int collection_get_item(Collection
*collection
, int x
, int y
)
1954 g_return_val_if_fail(collection
!= NULL
, -1);
1956 scroll
= collection
->vadj
->value
;
1957 col
= x
/ collection
->item_width
;
1958 row
= (y
+ scroll
) / collection
->item_height
;
1960 if (col
< 0 || row
< 0 || col
>= collection
->columns
)
1963 item
= col
+ row
* collection
->columns
;
1964 if (item
>= collection
->number_of_items
1966 !collection
->test_point(collection
,
1967 x
- col
* collection
->item_width
,
1968 y
- row
* collection
->item_height
1970 &collection
->items
[item
],
1971 collection
->item_width
,
1972 collection
->item_height
))
1980 /* Set the cursor/highlight over the given item. Passing -1
1981 * hides the cursor. As a special case, you may set the cursor item
1982 * to zero when there are no items.
1984 void collection_set_cursor_item(Collection
*collection
, gint item
)
1988 g_return_if_fail(collection
!= NULL
);
1989 g_return_if_fail(IS_COLLECTION(collection
));
1990 g_return_if_fail(item
>= -1 &&
1991 (item
< collection
->number_of_items
|| item
== 0));
1993 old_item
= collection
->cursor_item
;
1995 if (old_item
== item
)
1998 collection
->cursor_item
= item
;
2001 collection_draw_item(collection
, old_item
, TRUE
);
2005 collection_draw_item(collection
, item
, TRUE
);
2006 scroll_to_show(collection
, item
);
2010 /* Briefly highlight an item to draw the user's attention to it.
2011 * -1 cancels the effect, as does deleting items, sorting the collection
2012 * or starting a new wink effect.
2013 * Otherwise, the effect will cancel itself after a short pause.
2015 void collection_wink_item(Collection
*collection
, gint item
)
2017 g_return_if_fail(collection
!= NULL
);
2018 g_return_if_fail(IS_COLLECTION(collection
));
2019 g_return_if_fail(item
>= -1 && item
< collection
->number_of_items
);
2021 if (collection
->wink_item
!= -1)
2022 cancel_wink(collection
);
2026 collection
->wink_item
= item
;
2027 collection
->wink_timeout
= gtk_timeout_add(300,
2028 (GtkFunction
) cancel_wink_timeout
,
2030 collection_draw_item(collection
, item
, TRUE
);
2031 scroll_to_show(collection
, item
);
2036 /* Call test(item, data) on each item in the collection.
2037 * Remove all items for which it returns TRUE. test() should
2038 * free the data before returning TRUE. The collection is in an
2039 * inconsistant state during this call (ie, when test() is called).
2041 void collection_delete_if(Collection
*collection
,
2042 gboolean (*test
)(gpointer item
, gpointer data
),
2049 g_return_if_fail(collection
!= NULL
);
2050 g_return_if_fail(IS_COLLECTION(collection
));
2051 g_return_if_fail(test
!= NULL
);
2053 cursor
= collection
->cursor_item
;
2055 for (in
= 0; in
< collection
->number_of_items
; in
++)
2057 if (!test(collection
->items
[in
].data
, data
))
2059 if (collection
->items
[in
].selected
)
2061 collection
->items
[out
].selected
= TRUE
;
2065 collection
->items
[out
].selected
= FALSE
;
2067 collection
->items
[out
++].data
=
2068 collection
->items
[in
].data
;
2070 else if (cursor
>= in
)
2076 collection
->cursor_item
= cursor
;
2078 if (collection
->wink_item
!= -1)
2080 collection
->wink_item
= -1;
2081 gtk_timeout_remove(collection
->wink_timeout
);
2084 collection
->number_of_items
= out
;
2085 collection
->number_selected
= selected
;
2086 resize_arrays(collection
,
2087 MAX(collection
->number_of_items
, MINIMUM_ITEMS
));
2089 collection
->paint_level
= PAINT_CLEAR
;
2091 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection
)))
2093 set_vadjustment(collection
);
2094 gtk_widget_queue_draw(GTK_WIDGET(collection
));
2099 /* Display a cross-hair pointer and the next time an item is clicked,
2100 * call the callback function. If the background is clicked or NULL
2101 * is passed as the callback then revert to normal operation.
2103 void collection_target(Collection
*collection
,
2104 CollectionTargetFunc callback
,
2107 g_return_if_fail(collection
!= NULL
);
2108 g_return_if_fail(IS_COLLECTION(collection
));
2110 if (callback
!= collection
->target_cb
)
2111 gdk_window_set_cursor(GTK_WIDGET(collection
)->window
,
2112 callback
? crosshair
: NULL
);
2114 collection
->target_cb
= callback
;
2115 collection
->target_data
= user_data
;
2117 if (collection
->cursor_item
!= -1)
2118 collection_draw_item(collection
, collection
->cursor_item
,
2122 /* Move the cursor by the given row and column offsets. */
2123 void collection_move_cursor(Collection
*collection
, int drow
, int dcol
)
2126 int first
, last
, total_rows
;
2128 g_return_if_fail(collection
!= NULL
);
2129 g_return_if_fail(IS_COLLECTION(collection
));
2131 get_visible_limits(collection
, &first
, &last
);
2133 item
= collection
->cursor_item
;
2141 row
= item
/ collection
->columns
;
2142 col
= item
% collection
->columns
+ dcol
;
2146 else if (row
> last
)
2149 row
= MAX(row
+ drow
, 0);
2152 total_rows
= (collection
->number_of_items
+ collection
->columns
- 1)
2153 / collection
->columns
;
2155 if (row
>= total_rows
- 1 && drow
> 0)
2157 row
= total_rows
- 1;
2158 item
= col
+ row
* collection
->columns
;
2159 if (item
>= collection
->number_of_items
)
2162 scroll_to_show(collection
, item
);
2168 item
= col
+ row
* collection
->columns
;
2170 if (item
>= 0 && item
< collection
->number_of_items
)
2171 collection_set_cursor_item(collection
, item
);