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 "collection.h"
34 #define MINIMUM_ITEMS 16
44 * void open_item(collection, item, item_number, user_data)
45 * User has double clicked on this item.
47 * void drag_selection(collection, motion_event, number_selected, user_data)
48 * User has tried to drag the selection.
50 * void show_menu(collection, button_event, item, user_data)
51 * User has right-clicked on the collection. 'item' is the number
52 * of the item clicked, or -1 if the click was over the background.
54 * void gain_selection(collection, time, user_data)
55 * We've gone from no selected items to having a selection.
56 * Time is the time of the event that caused the change, or
57 * GDK_CURRENT_TIME if not known.
59 * void lose_selection(collection, time, user_data)
60 * We've dropped to having no selected items.
61 * Time is the time of the event that caused the change, or
62 * GDK_CURRENT_TIME if not known.
74 static guint collection_signals
[LAST_SIGNAL
] = { 0 };
76 static guint32 current_event_time
= GDK_CURRENT_TIME
;
78 static GtkWidgetClass
*parent_class
= NULL
;
80 /* Static prototypes */
81 static void draw_one_item(Collection
*collection
,
84 static void collection_class_init(CollectionClass
*class);
85 static void collection_init(Collection
*object
);
86 static void collection_destroy(GtkObject
*object
);
87 static void collection_finalize(GtkObject
*object
);
88 static void collection_realize(GtkWidget
*widget
);
89 static gint
collection_paint(Collection
*collection
,
91 static void collection_size_request(GtkWidget
*widget
,
92 GtkRequisition
*requisition
);
93 static void collection_size_allocate(GtkWidget
*widget
,
94 GtkAllocation
*allocation
);
95 static void collection_set_adjustment(Collection
*collection
,
97 static void collection_set_arg( GtkObject
*object
,
100 static void collection_get_arg( GtkObject
*object
,
103 static void collection_adjustment(GtkAdjustment
*adjustment
,
104 Collection
*collection
);
105 static void collection_disconnect(GtkAdjustment
*adjustment
,
106 Collection
*collection
);
107 static void set_vadjustment(Collection
*collection
);
108 static void collection_draw(GtkWidget
*widget
, GdkRectangle
*area
);
109 static gint
collection_expose(GtkWidget
*widget
, GdkEventExpose
*event
);
110 static void scroll_by(Collection
*collection
, gint diff
);
111 static gint
collection_button_press(GtkWidget
*widget
,
112 GdkEventButton
*event
);
113 static gint
collection_button_release(GtkWidget
*widget
,
114 GdkEventButton
*event
);
115 static void default_draw_item(GtkWidget
*widget
,
116 CollectionItem
*data
,
118 static gboolean
default_test_point(Collection
*collection
,
119 int point_x
, int point_y
,
120 CollectionItem
*data
,
121 int width
, int height
);
122 static gint
collection_motion_notify(GtkWidget
*widget
,
123 GdkEventMotion
*event
);
124 static void add_lasso_box(Collection
*collection
);
125 static void remove_lasso_box(Collection
*collection
);
126 static void draw_lasso_box(Collection
*collection
);
127 static int item_at_row_col(Collection
*collection
, int row
, int col
);
128 static void collection_clear_except(Collection
*collection
, gint exception
);
129 static void cancel_wink(Collection
*collection
);
131 static void draw_one_item(Collection
*collection
, int item
, GdkRectangle
*area
)
133 collection
->draw_item((GtkWidget
*) collection
,
134 &collection
->items
[item
],
136 if (item
== collection
->wink_item
)
137 gdk_draw_rectangle(((GtkWidget
*) collection
)->window
,
138 ((GtkWidget
*) collection
)->style
->black_gc
,
140 area
->x
+ 1, area
->y
+ 1,
141 area
->width
- 3, area
->height
- 3);
142 if (item
== collection
->cursor_item
)
143 gdk_draw_rectangle(((GtkWidget
*) collection
)->window
,
144 ((GtkWidget
*) collection
)->style
->black_gc
,
147 area
->width
- 1, area
->height
- 1);
151 GtkType
collection_get_type(void)
153 static guint my_type
= 0;
157 static const GtkTypeInfo my_info
=
161 sizeof(CollectionClass
),
162 (GtkClassInitFunc
) collection_class_init
,
163 (GtkObjectInitFunc
) collection_init
,
164 NULL
, /* Reserved 1 */
165 NULL
, /* Reserved 2 */
166 (GtkClassInitFunc
) NULL
/* base_class_init_func */
169 my_type
= gtk_type_unique(gtk_widget_get_type(),
176 static void collection_class_init(CollectionClass
*class)
178 GtkObjectClass
*object_class
;
179 GtkWidgetClass
*widget_class
;
181 object_class
= (GtkObjectClass
*) class;
182 widget_class
= (GtkWidgetClass
*) class;
184 parent_class
= gtk_type_class(gtk_widget_get_type());
186 gtk_object_add_arg_type("Collection::vadjustment",
188 GTK_ARG_READWRITE
| GTK_ARG_CONSTRUCT
,
191 object_class
->destroy
= collection_destroy
;
192 object_class
->finalize
= collection_finalize
;
194 widget_class
->realize
= collection_realize
;
195 widget_class
->draw
= collection_draw
;
196 widget_class
->expose_event
= collection_expose
;
197 widget_class
->size_request
= collection_size_request
;
198 widget_class
->size_allocate
= collection_size_allocate
;
200 widget_class
->button_press_event
= collection_button_press
;
201 widget_class
->button_release_event
= collection_button_release
;
202 widget_class
->motion_notify_event
= collection_motion_notify
;
203 object_class
->set_arg
= collection_set_arg
;
204 object_class
->get_arg
= collection_get_arg
;
206 class->open_item
= NULL
;
207 class->drag_selection
= NULL
;
208 class->show_menu
= NULL
;
209 class->gain_selection
= NULL
;
210 class->lose_selection
= NULL
;
212 collection_signals
[OPEN_ITEM
] = gtk_signal_new("open_item",
215 GTK_SIGNAL_OFFSET(CollectionClass
,
217 gtk_marshal_NONE__POINTER_UINT
,
221 collection_signals
[DRAG_SELECTION
] = gtk_signal_new("drag_selection",
224 GTK_SIGNAL_OFFSET(CollectionClass
,
226 gtk_marshal_NONE__POINTER_UINT
,
230 collection_signals
[SHOW_MENU
] = gtk_signal_new("show_menu",
233 GTK_SIGNAL_OFFSET(CollectionClass
,
235 gtk_marshal_NONE__POINTER_INT
,
239 collection_signals
[GAIN_SELECTION
] = gtk_signal_new("gain_selection",
242 GTK_SIGNAL_OFFSET(CollectionClass
,
244 gtk_marshal_NONE__UINT
,
247 collection_signals
[LOSE_SELECTION
] = gtk_signal_new("lose_selection",
250 GTK_SIGNAL_OFFSET(CollectionClass
,
252 gtk_marshal_NONE__UINT
,
256 gtk_object_class_add_signals(object_class
,
257 collection_signals
, LAST_SIGNAL
);
260 static void collection_init(Collection
*object
)
262 object
->panel
= FALSE
;
263 object
->number_of_items
= 0;
264 object
->number_selected
= 0;
266 object
->item_width
= 64;
267 object
->item_height
= 64;
269 object
->paint_level
= PAINT_OVERWRITE
;
270 object
->last_scroll
= 0;
272 object
->items
= g_malloc(sizeof(CollectionItem
) * MINIMUM_ITEMS
);
273 object
->cursor_item
= -1;
274 object
->wink_item
= -1;
275 object
->array_size
= MINIMUM_ITEMS
;
276 object
->draw_item
= default_draw_item
;
277 object
->test_point
= default_test_point
;
279 object
->buttons_pressed
= 0;
280 object
->may_drag
= FALSE
;
285 GtkWidget
* collection_new(GtkAdjustment
*vadj
)
288 g_return_val_if_fail(GTK_IS_ADJUSTMENT(vadj
), NULL
);
290 return GTK_WIDGET(gtk_widget_new(collection_get_type(),
295 void collection_set_functions(Collection
*collection
,
296 CollectionDrawFunc draw_item
,
297 CollectionTestFunc test_point
)
301 g_return_if_fail(collection
!= NULL
);
302 g_return_if_fail(IS_COLLECTION(collection
));
304 widget
= GTK_WIDGET(collection
);
307 draw_item
= default_draw_item
;
309 test_point
= default_test_point
;
311 collection
->draw_item
= draw_item
;
312 collection
->test_point
= test_point
;
314 if (GTK_WIDGET_REALIZED(widget
))
316 collection
->paint_level
= PAINT_CLEAR
;
317 gtk_widget_queue_clear(widget
);
321 /* After this we are unusable, but our data (if any) is still hanging around.
322 * It will be freed later with finalize.
324 static void collection_destroy(GtkObject
*object
)
326 Collection
*collection
;
328 g_return_if_fail(object
!= NULL
);
329 g_return_if_fail(IS_COLLECTION(object
));
331 collection
= COLLECTION(object
);
333 if (collection
->wink_item
!= -1)
335 collection
->wink_item
= -1;
336 gtk_timeout_remove(collection
->wink_timeout
);
339 gtk_signal_disconnect_by_data(GTK_OBJECT(collection
->vadj
),
342 if (GTK_OBJECT_CLASS(parent_class
)->destroy
)
343 (*GTK_OBJECT_CLASS(parent_class
)->destroy
)(object
);
346 /* This is the last thing that happens to us. Free all data. */
347 static void collection_finalize(GtkObject
*object
)
349 Collection
*collection
;
351 collection
= COLLECTION(object
);
353 if (collection
->vadj
)
355 gtk_object_unref(GTK_OBJECT(collection
->vadj
));
358 g_free(collection
->items
);
361 static void collection_realize(GtkWidget
*widget
)
363 Collection
*collection
;
364 GdkWindowAttr attributes
;
365 gint attributes_mask
;
366 GdkGCValues xor_values
;
368 g_return_if_fail(widget
!= NULL
);
369 g_return_if_fail(IS_COLLECTION(widget
));
370 g_return_if_fail(widget
->parent
!= NULL
);
372 GTK_WIDGET_SET_FLAGS(widget
, GTK_REALIZED
);
373 collection
= COLLECTION(widget
);
375 attributes
.x
= widget
->allocation
.x
;
376 attributes
.y
= widget
->allocation
.y
;
377 attributes
.width
= widget
->allocation
.width
;
378 attributes
.height
= widget
->allocation
.height
;
379 attributes
.wclass
= GDK_INPUT_OUTPUT
;
380 attributes
.window_type
= GDK_WINDOW_CHILD
;
381 attributes
.event_mask
= gtk_widget_get_events(widget
) |
383 GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK
|
384 GDK_BUTTON1_MOTION_MASK
| GDK_BUTTON2_MOTION_MASK
;
385 attributes
.visual
= gtk_widget_get_visual(widget
);
386 attributes
.colormap
= gtk_widget_get_colormap(widget
);
388 attributes_mask
= GDK_WA_X
| GDK_WA_Y
|
389 GDK_WA_VISUAL
| GDK_WA_COLORMAP
;
390 widget
->window
= gdk_window_new(widget
->parent
->window
,
391 &attributes
, attributes_mask
);
393 widget
->style
= gtk_style_attach(widget
->style
, widget
->window
);
395 gdk_window_set_user_data(widget
->window
, widget
);
397 gdk_window_set_background(widget
->window
,
398 &widget
->style
->base
[GTK_STATE_INSENSITIVE
]);
400 /* Try to stop everything flickering horribly */
401 gdk_window_set_static_gravities(widget
->window
, TRUE
);
403 set_vadjustment(collection
);
405 xor_values
.function
= GDK_XOR
;
406 xor_values
.foreground
.red
= 0xffff;
407 xor_values
.foreground
.green
= 0xffff;
408 xor_values
.foreground
.blue
= 0xffff;
409 gdk_color_alloc(gtk_widget_get_colormap(widget
),
410 &xor_values
.foreground
);
411 collection
->xor_gc
= gdk_gc_new_with_values(widget
->window
,
417 static void collection_size_request(GtkWidget
*widget
,
418 GtkRequisition
*requisition
)
420 requisition
->width
= MIN_WIDTH
;
421 requisition
->height
= MIN_HEIGHT
;
424 static void collection_size_allocate(GtkWidget
*widget
,
425 GtkAllocation
*allocation
)
427 Collection
*collection
;
430 g_return_if_fail(widget
!= NULL
);
431 g_return_if_fail(IS_COLLECTION(widget
));
432 g_return_if_fail(allocation
!= NULL
);
434 collection
= COLLECTION(widget
);
436 old_columns
= collection
->columns
;
437 widget
->allocation
= *allocation
;
439 collection
->columns
= allocation
->width
/ collection
->item_width
;
440 if (collection
->columns
< 1)
441 collection
->columns
= 1;
443 if (GTK_WIDGET_REALIZED(widget
))
445 gdk_window_move_resize(widget
->window
,
446 allocation
->x
, allocation
->y
,
447 allocation
->width
, allocation
->height
);
449 if (old_columns
!= collection
->columns
)
451 collection
->paint_level
= PAINT_CLEAR
;
452 gtk_widget_queue_clear(widget
);
455 set_vadjustment(collection
);
459 static gint
collection_paint(Collection
*collection
,
462 GdkRectangle whole
, item_area
;
467 int start_row
, last_row
;
468 int start_col
, last_col
;
472 scroll
= collection
->vadj
->value
;
474 widget
= GTK_WIDGET(collection
);
476 if (collection
->paint_level
> PAINT_NORMAL
|| area
== NULL
)
479 gdk_window_get_size(widget
->window
, &width
, &height
);
484 whole
.height
= height
;
488 if (collection
->paint_level
== PAINT_CLEAR
489 && !collection
->lasso_box
)
490 gdk_window_clear(widget
->window
);
492 collection
->paint_level
= PAINT_NORMAL
;
495 /* Calculate the ranges to plot */
496 start_row
= (area
->y
+ scroll
) / collection
->item_height
;
497 last_row
= (area
->y
+ area
->height
- 1 + scroll
)
498 / collection
->item_height
;
501 start_col
= area
->x
/ collection
->item_width
;
502 phys_last_col
= (area
->x
+ area
->width
- 1) / collection
->item_width
;
504 if (collection
->lasso_box
)
506 /* You can't be too careful with lasso boxes...
508 * clip gives the total area drawn over (this may be larger
509 * than the requested area). It's used to redraw the lasso
512 clip
.x
= start_col
* collection
->item_width
;
513 clip
.y
= start_row
* collection
->item_height
- scroll
;
514 clip
.width
= (phys_last_col
- start_col
+ 1)
515 * collection
->item_width
;
516 clip
.height
= (last_row
- start_row
+ 1)
517 * collection
->item_height
;
519 gdk_window_clear_area(widget
->window
,
520 clip
.x
, clip
.y
, clip
.width
, clip
.height
);
523 if (start_col
< collection
->columns
)
525 if (phys_last_col
>= collection
->columns
)
526 last_col
= collection
->columns
- 1;
528 last_col
= phys_last_col
;
532 item
= row
* collection
->columns
+ col
;
533 item_area
.width
= collection
->item_width
;
534 item_area
.height
= collection
->item_height
;
536 while (item
< collection
->number_of_items
&& row
<= last_row
)
538 item_area
.x
= col
* collection
->item_width
;
539 item_area
.y
= row
* collection
->item_height
- scroll
;
541 draw_one_item(collection
, item
, &item_area
);
548 item
= row
* collection
->columns
+ col
;
555 if (collection
->lasso_box
)
557 gdk_gc_set_clip_rectangle(collection
->xor_gc
, &clip
);
558 draw_lasso_box(collection
);
559 gdk_gc_set_clip_rectangle(collection
->xor_gc
, NULL
);
565 static void default_draw_item( GtkWidget
*widget
,
566 CollectionItem
*item
,
569 gdk_draw_arc(widget
->window
,
570 item
->selected
? widget
->style
->white_gc
571 : widget
->style
->black_gc
,
574 area
->width
, area
->height
,
579 static gboolean
default_test_point(Collection
*collection
,
580 int point_x
, int point_y
,
581 CollectionItem
*item
,
582 int width
, int height
)
586 /* Convert to point in unit circle */
587 f_x
= ((float) point_x
/ width
) - 0.5;
588 f_y
= ((float) point_y
/ height
) - 0.5;
590 return (f_x
* f_x
) + (f_y
* f_y
) <= .25;
593 static void collection_set_arg( GtkObject
*object
,
597 Collection
*collection
;
599 collection
= COLLECTION(object
);
603 case ARG_VADJUSTMENT
:
604 collection_set_adjustment(collection
,
605 GTK_VALUE_POINTER(*arg
));
612 static void collection_set_adjustment( Collection
*collection
,
616 g_return_if_fail (GTK_IS_ADJUSTMENT (vadj
));
618 vadj
= GTK_ADJUSTMENT(gtk_adjustment_new(0.0,
621 if (collection
->vadj
&& (collection
->vadj
!= vadj
))
623 gtk_signal_disconnect_by_data(GTK_OBJECT(collection
->vadj
),
625 gtk_object_unref(GTK_OBJECT(collection
->vadj
));
628 if (collection
->vadj
!= vadj
)
630 collection
->vadj
= vadj
;
631 gtk_object_ref(GTK_OBJECT(collection
->vadj
));
632 gtk_object_sink(GTK_OBJECT(collection
->vadj
));
634 gtk_signal_connect(GTK_OBJECT(collection
->vadj
),
636 (GtkSignalFunc
) collection_adjustment
,
638 gtk_signal_connect(GTK_OBJECT(collection
->vadj
),
640 (GtkSignalFunc
) collection_adjustment
,
642 gtk_signal_connect(GTK_OBJECT(collection
->vadj
),
644 (GtkSignalFunc
) collection_disconnect
,
646 collection_adjustment(vadj
, collection
);
650 static void collection_get_arg( GtkObject
*object
,
654 Collection
*collection
;
656 collection
= COLLECTION(object
);
660 case ARG_VADJUSTMENT
:
661 GTK_VALUE_POINTER(*arg
) = collection
->vadj
;
664 arg
->type
= GTK_TYPE_INVALID
;
669 /* Something about the adjustment has changed */
670 static void collection_adjustment(GtkAdjustment
*adjustment
,
671 Collection
*collection
)
675 g_return_if_fail(adjustment
!= NULL
);
676 g_return_if_fail(GTK_IS_ADJUSTMENT(adjustment
));
677 g_return_if_fail(collection
!= NULL
);
678 g_return_if_fail(IS_COLLECTION(collection
));
680 diff
= ((gint
) adjustment
->value
) - collection
->last_scroll
;
684 collection
->last_scroll
= adjustment
->value
;
686 scroll_by(collection
, diff
);
690 static void collection_disconnect(GtkAdjustment
*adjustment
,
691 Collection
*collection
)
693 g_return_if_fail(adjustment
!= NULL
);
694 g_return_if_fail(GTK_IS_ADJUSTMENT(adjustment
));
695 g_return_if_fail(collection
!= NULL
);
696 g_return_if_fail(IS_COLLECTION(collection
));
698 collection_set_adjustment(collection
, NULL
);
701 static void set_vadjustment(Collection
*collection
)
707 widget
= GTK_WIDGET(collection
);
709 if (!GTK_WIDGET_REALIZED(widget
))
712 gdk_window_get_size(widget
->window
, NULL
, &height
);
713 cols
= collection
->columns
;
714 rows
= (collection
->number_of_items
+ cols
- 1) / cols
;
716 collection
->vadj
->lower
= 0.0;
717 collection
->vadj
->upper
= collection
->item_height
* rows
;
719 collection
->vadj
->step_increment
=
720 MIN(collection
->vadj
->upper
, collection
->item_height
/ 4);
722 collection
->vadj
->page_increment
=
723 MIN(collection
->vadj
->upper
,
726 collection
->vadj
->page_size
= MIN(collection
->vadj
->upper
, height
);
728 collection
->vadj
->value
= MIN(collection
->vadj
->value
,
729 collection
->vadj
->upper
- collection
->vadj
->page_size
);
731 collection
->vadj
->value
= MAX(collection
->vadj
->value
, 0.0);
733 gtk_signal_emit_by_name(GTK_OBJECT(collection
->vadj
), "changed");
736 static void collection_draw(GtkWidget
*widget
, GdkRectangle
*area
)
738 Collection
*collection
;
740 g_return_if_fail(widget
!= NULL
);
741 g_return_if_fail(IS_COLLECTION(widget
));
742 g_return_if_fail(area
!= NULL
); /* Not actually used */
744 collection
= COLLECTION(widget
);
746 if (collection
->paint_level
> PAINT_NORMAL
)
747 collection_paint(collection
, area
);
750 static gint
collection_expose(GtkWidget
*widget
, GdkEventExpose
*event
)
752 g_return_val_if_fail(widget
!= NULL
, FALSE
);
753 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
754 g_return_val_if_fail(event
!= NULL
, FALSE
);
756 collection_paint(COLLECTION(widget
), &event
->area
);
761 /* Positive makes the contents go move up the screen */
762 static void scroll_by(Collection
*collection
, gint diff
)
768 GdkRectangle new_area
;
770 widget
= GTK_WIDGET(collection
);
772 if (collection
->lasso_box
)
773 remove_lasso_box(collection
);
775 gdk_window_get_size(widget
->window
, &width
, &height
);
777 new_area
.width
= width
;
784 new_area
.y
= height
- amount
;
794 new_area
.height
= amount
;
798 gdk_draw_pixmap(widget
->window
,
799 widget
->style
->white_gc
,
807 /* We have to redraw everything because any pending
808 * expose events now contain invalid areas.
809 * Don't need to clear the area first though...
811 if (collection
->paint_level
< PAINT_OVERWRITE
)
812 collection
->paint_level
= PAINT_OVERWRITE
;
815 collection
->paint_level
= PAINT_CLEAR
;
817 gdk_window_clear_area(widget
->window
,
819 width
, new_area
.height
);
820 collection_paint(collection
, NULL
);
823 static void resize_arrays(Collection
*collection
, guint new_size
)
825 g_return_if_fail(collection
!= NULL
);
826 g_return_if_fail(IS_COLLECTION(collection
));
827 g_return_if_fail(new_size
>= collection
->number_of_items
);
829 collection
->items
= g_realloc(collection
->items
,
830 sizeof(CollectionItem
) * new_size
);
831 collection
->array_size
= new_size
;
834 static gint
collection_button_press(GtkWidget
*widget
,
835 GdkEventButton
*event
)
837 Collection
*collection
;
844 g_return_val_if_fail(widget
!= NULL
, FALSE
);
845 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
846 g_return_val_if_fail(event
!= NULL
, FALSE
);
848 collection
= COLLECTION(widget
);
850 if (event
->button
> 3)
854 /* Wheel mouse scrolling */
855 if (event
->button
== 4)
856 diff
= -((signed int) collection
->item_height
) / 4;
857 else if (event
->button
== 5)
858 diff
= collection
->item_height
/ 4;
864 int old_value
= collection
->vadj
->value
;
866 gboolean box
= collection
->lasso_box
;
868 new_value
= CLAMP(old_value
+ diff
, 0.0,
869 collection
->vadj
->upper
870 - collection
->vadj
->page_size
);
871 diff
= new_value
- old_value
;
876 remove_lasso_box(collection
);
877 collection
->drag_box_y
[0] -= diff
;
879 collection
->vadj
->value
= new_value
;
880 gtk_signal_emit_by_name(
881 GTK_OBJECT(collection
->vadj
),
884 add_lasso_box(collection
);
890 scroll
= collection
->vadj
->value
;
892 if (event
->type
== GDK_BUTTON_PRESS
&& event
->button
< 3)
894 if (collection
->buttons_pressed
++ == 0)
895 gtk_grab_add(widget
);
897 return FALSE
; /* Ignore extra presses */
900 if (event
->state
& GDK_CONTROL_MASK
)
903 action
= event
->button
;
905 /* Ignore all clicks while we are dragging a lasso box */
906 if (collection
->lasso_box
)
909 col
= event
->x
/ collection
->item_width
;
910 row
= (event
->y
+ scroll
) / collection
->item_height
;
912 if (col
< 0 || row
< 0 || col
>= collection
->columns
)
916 item
= col
+ row
* collection
->columns
;
917 if (item
>= collection
->number_of_items
919 !collection
->test_point(collection
,
920 event
->x
- col
* collection
->item_width
,
921 event
->y
- row
* collection
->item_height
923 &collection
->items
[item
],
924 collection
->item_width
,
925 collection
->item_height
))
931 collection
->drag_box_x
[0] = event
->x
;
932 collection
->drag_box_y
[0] = event
->y
;
934 stacked_time
= current_event_time
;
935 current_event_time
= event
->time
;
937 if (event
->button
== 3)
939 gtk_signal_emit(GTK_OBJECT(collection
),
940 collection_signals
[SHOW_MENU
],
944 else if (event
->type
== GDK_2BUTTON_PRESS
&& collection
->panel
)
948 else if (event
->type
== GDK_2BUTTON_PRESS
|| collection
->panel
)
952 if (collection
->buttons_pressed
)
954 gtk_grab_remove(widget
);
955 collection
->buttons_pressed
= 0;
957 collection_unselect_item(collection
, item
);
958 gtk_signal_emit(GTK_OBJECT(collection
),
959 collection_signals
[OPEN_ITEM
],
960 collection
->items
[item
].data
,
964 else if (event
->type
== GDK_BUTTON_PRESS
)
966 collection
->may_drag
= event
->button
< 3;
972 if (!collection
->items
[item
].selected
)
974 collection_select_item(collection
,
976 collection_clear_except(collection
,
981 collection_toggle_item(collection
, item
);
983 else if (action
== 1)
984 collection_clear_selection(collection
);
987 current_event_time
= stacked_time
;
991 static gint
collection_button_release(GtkWidget
*widget
,
992 GdkEventButton
*event
)
994 Collection
*collection
;
998 int col
, start_col
, last_col
;
1003 g_return_val_if_fail(widget
!= NULL
, FALSE
);
1004 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
1005 g_return_val_if_fail(event
!= NULL
, FALSE
);
1007 collection
= COLLECTION(widget
);
1009 scroll
= collection
->vadj
->value
;
1011 if (event
->button
> 2)
1013 if (collection
->buttons_pressed
== 0)
1015 if (--collection
->buttons_pressed
== 0)
1016 gtk_grab_remove(widget
);
1018 return FALSE
; /* Wait until ALL buttons are up */
1020 if (!collection
->lasso_box
)
1023 remove_lasso_box(collection
);
1025 w
= collection
->item_width
;
1026 h
= collection
->item_height
;
1028 top
= collection
->drag_box_y
[0] + scroll
;
1029 bottom
= collection
->drag_box_y
[1] + scroll
;
1040 row
= MAX(top
/ h
, 0);
1041 last_row
= bottom
/ h
;
1043 top
= collection
->drag_box_x
[0]; /* (left) */
1044 bottom
= collection
->drag_box_x
[1];
1054 start_col
= MAX(top
/ w
, 0);
1055 last_col
= bottom
/ w
;
1056 if (last_col
>= collection
->columns
)
1057 last_col
= collection
->columns
- 1;
1059 stacked_time
= current_event_time
;
1060 current_event_time
= event
->time
;
1062 while (row
<= last_row
)
1065 item
= row
* collection
->columns
+ col
;
1066 while (col
<= last_col
)
1068 if (item
>= collection
->number_of_items
)
1070 current_event_time
= stacked_time
;
1073 collection_select_item(collection
, item
);
1080 current_event_time
= stacked_time
;
1085 static gint
collection_motion_notify(GtkWidget
*widget
,
1086 GdkEventMotion
*event
)
1088 Collection
*collection
;
1092 g_return_val_if_fail(widget
!= NULL
, FALSE
);
1093 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
1094 g_return_val_if_fail(event
!= NULL
, FALSE
);
1096 collection
= COLLECTION(widget
);
1098 if (collection
->buttons_pressed
== 0)
1101 stacked_time
= current_event_time
;
1102 current_event_time
= event
->time
;
1104 if (event
->window
!= widget
->window
)
1105 gdk_window_get_pointer(widget
->window
, &x
, &y
, NULL
);
1112 if (collection
->lasso_box
)
1114 int new_value
= 0, diff
;
1117 gdk_window_get_size(widget
->window
, NULL
, &height
);
1121 int old_value
= collection
->vadj
->value
;
1123 new_value
= MAX(old_value
+ y
/ 10, 0.0);
1124 diff
= new_value
- old_value
;
1126 else if (y
> height
)
1128 int old_value
= collection
->vadj
->value
;
1130 new_value
= MIN(old_value
+ (y
- height
) / 10,
1131 collection
->vadj
->upper
1132 - collection
->vadj
->page_size
);
1133 diff
= new_value
- old_value
;
1138 remove_lasso_box(collection
);
1139 collection
->drag_box_x
[1] = x
;
1140 collection
->drag_box_y
[1] = y
;
1144 collection
->drag_box_y
[0] -= diff
;
1145 collection
->vadj
->value
= new_value
;
1146 gtk_signal_emit_by_name(GTK_OBJECT(collection
->vadj
),
1149 add_lasso_box(collection
);
1151 else if (collection
->may_drag
)
1153 int dx
= x
- collection
->drag_box_x
[0];
1154 int dy
= y
- collection
->drag_box_y
[0];
1156 if (abs(dx
) > 9 || abs(dy
) > 9)
1159 int scroll
= collection
->vadj
->value
;
1161 collection
->may_drag
= FALSE
;
1163 col
= collection
->drag_box_x
[0]
1164 / collection
->item_width
;
1165 row
= (collection
->drag_box_y
[0] + scroll
)
1166 / collection
->item_height
;
1167 item
= item_at_row_col(collection
, row
, col
);
1169 if (item
!= -1 && collection
->test_point(collection
,
1170 collection
->drag_box_x
[0] -
1171 col
* collection
->item_width
,
1172 collection
->drag_box_y
[0]
1173 - row
* collection
->item_height
1175 &collection
->items
[item
],
1176 collection
->item_width
,
1177 collection
->item_height
))
1179 collection
->buttons_pressed
= 0;
1180 gtk_grab_remove(widget
);
1181 collection_select_item(collection
, item
);
1182 gtk_signal_emit(GTK_OBJECT(collection
),
1183 collection_signals
[DRAG_SELECTION
],
1185 collection
->number_selected
);
1189 collection
->drag_box_x
[1] = x
;
1190 collection
->drag_box_y
[1] = y
;
1191 add_lasso_box(collection
);
1196 current_event_time
= stacked_time
;
1200 static void add_lasso_box(Collection
*collection
)
1202 g_return_if_fail(collection
!= NULL
);
1203 g_return_if_fail(IS_COLLECTION(collection
));
1204 g_return_if_fail(collection
->lasso_box
== FALSE
);
1206 collection
->lasso_box
= TRUE
;
1207 draw_lasso_box(collection
);
1210 static void draw_lasso_box(Collection
*collection
)
1213 int x
, y
, width
, height
;
1215 widget
= GTK_WIDGET(collection
);
1217 x
= MIN(collection
->drag_box_x
[0], collection
->drag_box_x
[1]);
1218 y
= MIN(collection
->drag_box_y
[0], collection
->drag_box_y
[1]);
1219 width
= abs(collection
->drag_box_x
[1] - collection
->drag_box_x
[0]);
1220 height
= abs(collection
->drag_box_y
[1] - collection
->drag_box_y
[0]);
1222 gdk_draw_rectangle(widget
->window
, collection
->xor_gc
, FALSE
,
1223 x
, y
, width
, height
);
1226 static void remove_lasso_box(Collection
*collection
)
1228 g_return_if_fail(collection
!= NULL
);
1229 g_return_if_fail(IS_COLLECTION(collection
));
1230 g_return_if_fail(collection
->lasso_box
== TRUE
);
1232 draw_lasso_box(collection
);
1234 collection
->lasso_box
= FALSE
;
1239 /* Convert a row,col address to an item number, or -1 if none */
1240 static int item_at_row_col(Collection
*collection
, int row
, int col
)
1244 if (row
< 0 || col
< 0 || col
>= collection
->columns
)
1247 item
= col
+ row
* collection
->columns
;
1249 if (item
>= collection
->number_of_items
)
1254 /* Unselect all items except number item (-1 to unselect everything) */
1255 static void collection_clear_except(Collection
*collection
, gint exception
)
1261 int end
; /* Selected items to end up with */
1263 widget
= GTK_WIDGET(collection
);
1264 scroll
= collection
->vadj
->value
;
1266 end
= exception
>= 0 && exception
< collection
->number_of_items
1267 ? collection
->items
[exception
].selected
!= 0 : 0;
1269 area
.width
= collection
->item_width
;
1270 area
.height
= collection
->item_height
;
1272 if (collection
->number_selected
== 0)
1275 while (collection
->number_selected
> end
)
1277 while (item
== exception
|| !collection
->items
[item
].selected
)
1280 area
.x
= (item
% collection
->columns
) * area
.width
;
1281 area
.y
= (item
/ collection
->columns
) * area
.height
1284 collection
->items
[item
++].selected
= FALSE
;
1285 gdk_window_clear_area(widget
->window
,
1286 area
.x
, area
.y
, area
.width
, area
.height
);
1287 collection_paint(collection
, &area
);
1289 collection
->number_selected
--;
1293 gtk_signal_emit(GTK_OBJECT(collection
),
1294 collection_signals
[LOSE_SELECTION
],
1295 current_event_time
);
1298 /* Cancel the current wink effect. */
1299 void cancel_wink(Collection
*collection
)
1303 g_return_if_fail(collection
!= NULL
);
1304 g_return_if_fail(IS_COLLECTION(collection
));
1305 g_return_if_fail(collection
->wink_item
!= -1);
1307 item
= collection
->wink_item
;
1309 collection
->wink_item
= -1;
1310 gtk_timeout_remove(collection
->wink_timeout
);
1312 collection_draw_item(collection
, item
, TRUE
);
1315 gboolean
cancel_wink_timeout(Collection
*collection
)
1319 g_return_val_if_fail(collection
!= NULL
, FALSE
);
1320 g_return_val_if_fail(IS_COLLECTION(collection
), FALSE
);
1321 g_return_val_if_fail(collection
->wink_item
!= -1, FALSE
);
1323 item
= collection
->wink_item
;
1325 collection
->wink_item
= -1;
1327 collection_draw_item(collection
, item
, TRUE
);
1332 /* Functions for managing collections */
1334 /* Remove all objects from the collection */
1335 void collection_clear(Collection
*collection
)
1339 g_return_if_fail(IS_COLLECTION(collection
));
1341 if (collection
->number_of_items
== 0)
1344 if (collection
->wink_item
!= -1)
1346 collection
->wink_item
= -1;
1347 gtk_timeout_remove(collection
->wink_timeout
);
1350 collection_set_cursor_item(collection
, -1);
1351 prev_selected
= collection
->number_selected
;
1352 collection
->number_of_items
= collection
->number_selected
= 0;
1354 resize_arrays(collection
, MINIMUM_ITEMS
);
1356 collection
->paint_level
= PAINT_CLEAR
;
1358 gtk_widget_queue_clear(GTK_WIDGET(collection
));
1360 if (prev_selected
&& collection
->number_selected
== 0)
1361 gtk_signal_emit(GTK_OBJECT(collection
),
1362 collection_signals
[LOSE_SELECTION
],
1363 current_event_time
);
1366 /* Inserts a new item at the end. The new item is unselected, and its
1367 * number is returned.
1369 gint
collection_insert(Collection
*collection
, gpointer data
)
1373 g_return_val_if_fail(IS_COLLECTION(collection
), -1);
1375 item
= collection
->number_of_items
;
1377 if (item
>= collection
->array_size
)
1378 resize_arrays(collection
, item
+ (item
>> 1));
1380 collection
->items
[item
].data
= data
;
1381 collection
->items
[item
].selected
= FALSE
;
1383 collection
->number_of_items
++;
1385 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection
)))
1387 set_vadjustment(collection
);
1388 collection_draw_item(collection
,
1389 collection
->number_of_items
- 1,
1396 /* Unselect an item by number */
1397 void collection_unselect_item(Collection
*collection
, gint item
)
1399 g_return_if_fail(collection
!= NULL
);
1400 g_return_if_fail(IS_COLLECTION(collection
));
1401 g_return_if_fail(item
>= 0 && item
< collection
->number_of_items
);
1403 if (collection
->items
[item
].selected
)
1405 collection
->items
[item
].selected
= FALSE
;
1406 collection_draw_item(collection
, item
, TRUE
);
1408 if (--collection
->number_selected
== 0)
1409 gtk_signal_emit(GTK_OBJECT(collection
),
1410 collection_signals
[LOSE_SELECTION
],
1411 current_event_time
);
1415 /* Select an item by number */
1416 void collection_select_item(Collection
*collection
, gint item
)
1418 g_return_if_fail(collection
!= NULL
);
1419 g_return_if_fail(IS_COLLECTION(collection
));
1420 g_return_if_fail(item
>= 0 && item
< collection
->number_of_items
);
1422 if (collection
->items
[item
].selected
)
1423 return; /* Already selected */
1425 collection
->items
[item
].selected
= TRUE
;
1426 collection_draw_item(collection
, item
, TRUE
);
1428 if (collection
->number_selected
++ == 0)
1429 gtk_signal_emit(GTK_OBJECT(collection
),
1430 collection_signals
[GAIN_SELECTION
],
1431 current_event_time
);
1434 /* Toggle the selected state of an item (by number) */
1435 void collection_toggle_item(Collection
*collection
, gint item
)
1437 g_return_if_fail(collection
!= NULL
);
1438 g_return_if_fail(IS_COLLECTION(collection
));
1439 g_return_if_fail(item
>= 0 && item
< collection
->number_of_items
);
1441 if (collection
->items
[item
].selected
)
1443 collection
->items
[item
].selected
= FALSE
;
1444 if (--collection
->number_selected
== 0)
1445 gtk_signal_emit(GTK_OBJECT(collection
),
1446 collection_signals
[LOSE_SELECTION
],
1447 current_event_time
);
1451 collection
->items
[item
].selected
= TRUE
;
1452 if (collection
->number_selected
++ == 0)
1453 gtk_signal_emit(GTK_OBJECT(collection
),
1454 collection_signals
[GAIN_SELECTION
],
1455 current_event_time
);
1457 collection_draw_item(collection
, item
, TRUE
);
1460 /* Select all items in the collection */
1461 void collection_select_all(Collection
*collection
)
1468 g_return_if_fail(collection
!= NULL
);
1469 g_return_if_fail(IS_COLLECTION(collection
));
1471 widget
= GTK_WIDGET(collection
);
1472 scroll
= collection
->vadj
->value
;
1474 area
.width
= collection
->item_width
;
1475 area
.height
= collection
->item_height
;
1477 if (collection
->number_selected
== collection
->number_of_items
)
1478 return; /* Nothing to do */
1480 while (collection
->number_selected
< collection
->number_of_items
)
1482 while (collection
->items
[item
].selected
)
1485 area
.x
= (item
% collection
->columns
) * area
.width
;
1486 area
.y
= (item
/ collection
->columns
) * area
.height
1489 collection
->items
[item
++].selected
= TRUE
;
1490 gdk_window_clear_area(widget
->window
,
1491 area
.x
, area
.y
, area
.width
, area
.height
);
1492 collection_paint(collection
, &area
);
1494 collection
->number_selected
++;
1497 gtk_signal_emit(GTK_OBJECT(collection
),
1498 collection_signals
[GAIN_SELECTION
],
1499 current_event_time
);
1502 /* Unselect all items in the collection */
1503 void collection_clear_selection(Collection
*collection
)
1505 g_return_if_fail(collection
!= NULL
);
1506 g_return_if_fail(IS_COLLECTION(collection
));
1508 collection_clear_except(collection
, -1);
1511 /* Force a redraw of the specified item, if it is visible */
1512 void collection_draw_item(Collection
*collection
, gint item
, gboolean blank
)
1519 int area_y
, area_height
; /* NOT shorts! */
1521 g_return_if_fail(collection
!= NULL
);
1522 g_return_if_fail(IS_COLLECTION(collection
));
1523 g_return_if_fail(item
>= 0 && item
< collection
->number_of_items
);
1525 widget
= GTK_WIDGET(collection
);
1527 col
= item
% collection
->columns
;
1528 row
= item
/ collection
->columns
;
1529 scroll
= collection
->vadj
->value
; /* (round to int) */
1531 area
.x
= col
* collection
->item_width
;
1532 area_y
= row
* collection
->item_height
- scroll
;
1533 area
.width
= collection
->item_width
;
1534 area_height
= collection
->item_height
;
1536 if (area_y
+ area_height
< 0)
1539 gdk_window_get_size(widget
->window
, NULL
, &height
);
1541 if (area_y
> height
)
1545 area
.height
= area_height
;
1548 gdk_window_clear_area(widget
->window
,
1549 area
.x
, area
.y
, area
.width
, area
.height
);
1551 draw_one_item(collection
, item
, &area
);
1554 void collection_set_item_size(Collection
*collection
, int width
, int height
)
1558 g_return_if_fail(collection
!= NULL
);
1559 g_return_if_fail(IS_COLLECTION(collection
));
1560 g_return_if_fail(width
> 4 && height
> 4);
1562 widget
= GTK_WIDGET(collection
);
1564 collection
->item_width
= width
;
1565 collection
->item_height
= height
;
1567 if (GTK_WIDGET_REALIZED(widget
))
1571 collection
->paint_level
= PAINT_CLEAR
;
1572 gdk_window_get_size(widget
->window
, &window_width
, NULL
);
1573 collection
->columns
= MAX(window_width
/ collection
->item_width
,
1576 set_vadjustment(collection
);
1577 gtk_widget_queue_draw(widget
);
1581 void collection_qsort(Collection
*collection
,
1582 int (*compar
)(const void *, const void *))
1584 g_return_if_fail(collection
!= NULL
);
1585 g_return_if_fail(IS_COLLECTION(collection
));
1586 g_return_if_fail(compar
!= NULL
);
1588 if (collection
->wink_item
!= -1)
1590 collection
->wink_item
= -1;
1591 gtk_timeout_remove(collection
->wink_timeout
);
1594 qsort(collection
->items
, collection
->number_of_items
,
1595 sizeof(collection
->items
[0]), compar
);
1597 collection
->paint_level
= PAINT_CLEAR
;
1599 gtk_widget_queue_draw(GTK_WIDGET(collection
));
1602 /* Find an item in an unsorted collection.
1603 * Returns the item number, or -1 if not found.
1605 int collection_find_item(Collection
*collection
, gpointer data
,
1606 int (*compar
)(const void *, const void *))
1610 g_return_val_if_fail(collection
!= NULL
, -1);
1611 g_return_val_if_fail(IS_COLLECTION(collection
), -1);
1612 g_return_val_if_fail(compar
!= NULL
, -1);
1614 for (i
= 0; i
< collection
->number_of_items
; i
++)
1615 if (compar(&collection
->items
[i
].data
, &data
) == 0)
1621 /* The collection may be in either normal mode or panel mode.
1623 * - a single click calls open_item
1624 * - items are never selected
1625 * - lasso boxes are disabled
1627 void collection_set_panel(Collection
*collection
, gboolean panel
)
1629 g_return_if_fail(collection
!= NULL
);
1630 g_return_if_fail(IS_COLLECTION(collection
));
1632 collection
->panel
= panel
== TRUE
;
1634 if (collection
->panel
)
1636 collection_clear_selection(collection
);
1637 if (collection
->lasso_box
)
1638 remove_lasso_box(collection
);
1642 /* Return the number of the item under the point (x,y), or -1 for none.
1643 * This may call your test_point callback. The point is relative to the
1644 * collection's origin.
1646 int collection_get_item(Collection
*collection
, int x
, int y
)
1652 g_return_val_if_fail(collection
!= NULL
, -1);
1654 scroll
= collection
->vadj
->value
;
1655 col
= x
/ collection
->item_width
;
1656 row
= (y
+ scroll
) / collection
->item_height
;
1658 if (col
< 0 || row
< 0 || col
>= collection
->columns
)
1661 item
= col
+ row
* collection
->columns
;
1662 if (item
>= collection
->number_of_items
1664 !collection
->test_point(collection
,
1665 x
- col
* collection
->item_width
,
1666 y
- row
* collection
->item_height
1668 &collection
->items
[item
],
1669 collection
->item_width
,
1670 collection
->item_height
))
1678 /* Set the cursor/highlight over the given item. Passing -1
1681 void collection_set_cursor_item(Collection
*collection
, gint item
)
1685 g_return_if_fail(collection
!= NULL
);
1686 g_return_if_fail(IS_COLLECTION(collection
));
1687 g_return_if_fail(item
>= -1 &&
1688 item
< (int) collection
->number_of_items
);
1690 old_item
= collection
->cursor_item
;
1692 if (old_item
== item
)
1695 collection
->cursor_item
= item
;
1698 collection_draw_item(collection
, old_item
, TRUE
);
1700 collection_draw_item(collection
, item
, TRUE
);
1703 /* Briefly highlight an item to draw the user's attention to it.
1704 * -1 cancels the effect, as does deleting items, sorting the collection
1705 * or starting a new wink effect.
1706 * Otherwise, the effect will cancel itself after a short pause.
1708 void collection_wink_item(Collection
*collection
, gint item
)
1710 g_return_if_fail(collection
!= NULL
);
1711 g_return_if_fail(IS_COLLECTION(collection
));
1712 g_return_if_fail(item
>= -1 && item
< collection
->number_of_items
);
1714 if (collection
->wink_item
!= -1)
1715 cancel_wink(collection
);
1719 collection
->wink_item
= item
;
1720 collection
->wink_timeout
= gtk_timeout_add(200,
1721 (GtkFunction
) cancel_wink_timeout
,
1723 collection_draw_item(collection
, item
, TRUE
);
1728 /* Call test(item, data) on each item in the collection.
1729 * Remove all items for which it returns TRUE. test() should
1730 * free the data before returning TRUE. The collection is in an
1731 * inconsistant state during this call (ie, when test() is called).
1733 void collection_delete_if(Collection
*collection
,
1734 gboolean (*test
)(gpointer item
, gpointer data
),
1740 g_return_if_fail(collection
!= NULL
);
1741 g_return_if_fail(IS_COLLECTION(collection
));
1742 g_return_if_fail(test
!= NULL
);
1744 for (in
= 0; in
< collection
->number_of_items
; in
++)
1746 if (!test(collection
->items
[in
].data
, data
))
1748 if (collection
->items
[in
].selected
)
1750 collection
->items
[out
].selected
= TRUE
;
1754 collection
->items
[out
].selected
= FALSE
;
1756 collection
->items
[out
++].data
=
1757 collection
->items
[in
].data
;
1763 if (collection
->wink_item
!= -1)
1765 collection
->wink_item
= -1;
1766 gtk_timeout_remove(collection
->wink_timeout
);
1769 collection
->number_of_items
= out
;
1770 collection
->number_selected
= selected
;
1771 resize_arrays(collection
,
1772 MAX(collection
->number_of_items
, MINIMUM_ITEMS
));
1774 collection
->paint_level
= PAINT_CLEAR
;
1776 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection
)))
1778 set_vadjustment(collection
);
1779 gtk_widget_queue_draw(GTK_WIDGET(collection
));