4 * Collection - a GTK+ widget
5 * Copyright (C) 2003, the ROX-Filer team.
7 * The collection widget provides an area for displaying a collection of
8 * objects (such as files). It allows the user to choose a selection of
9 * them and provides signals to allow popping up menus, detecting
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
32 #include <gdk/gdkkeysyms.h>
35 #include "collection.h"
39 #define MINIMUM_ITEMS 16
41 #define MAX_WINKS 3 /* Should be an odd number */
43 /* Macro to emit the "selection_changed" signal only if allowed */
44 #define EMIT_SELECTION_CHANGED(collection, time) \
45 if (!collection->block_selection_changed) \
46 g_signal_emit(collection, \
47 collection_signals[SELECTION_CHANGED], 0, time)
57 * void gain_selection(collection, time, user_data)
58 * We've gone from no selected items to having a selection.
59 * Time is the time of the event that caused the change, or
60 * GDK_CURRENT_TIME if not known.
62 * void lose_selection(collection, time, user_data)
63 * We've dropped to having no selected items.
64 * Time is the time of the event that caused the change, or
65 * GDK_CURRENT_TIME if not known.
67 * void selection_changed(collection, user_data)
68 * The set of selected items has changed.
69 * Time is the time of the event that caused the change, or
70 * GDK_CURRENT_TIME if not known.
80 static guint collection_signals
[LAST_SIGNAL
] = { 0 };
82 static guint32 current_event_time
= GDK_CURRENT_TIME
;
84 static GtkWidgetClass
*parent_class
= NULL
;
86 /* Static prototypes */
87 static void draw_one_item(Collection
*collection
,
90 static void collection_class_init(GObjectClass
*gclass
, gpointer data
);
91 static void collection_init(GTypeInstance
*object
, gpointer g_class
);
92 static void collection_destroy(GtkObject
*object
);
93 static void collection_finalize(GObject
*object
);
94 static void collection_realize(GtkWidget
*widget
);
95 static void collection_map(GtkWidget
*widget
);
96 static void collection_size_request(GtkWidget
*widget
,
97 GtkRequisition
*requisition
);
98 static void collection_size_allocate(GtkWidget
*widget
,
99 GtkAllocation
*allocation
);
100 static void collection_set_adjustment(Collection
*collection
,
101 GtkAdjustment
*vadj
);
102 static void collection_get_property(GObject
*object
,
106 static void collection_set_property(GObject
*object
,
110 static gint
collection_expose(GtkWidget
*widget
, GdkEventExpose
*event
);
111 static void default_draw_item(GtkWidget
*widget
,
112 CollectionItem
*data
,
115 static gboolean
default_test_point(Collection
*collection
,
116 int point_x
, int point_y
,
117 CollectionItem
*data
,
118 int width
, int height
,
120 static gint
collection_motion_notify(GtkWidget
*widget
,
121 GdkEventMotion
*event
);
122 static void add_lasso_box(Collection
*collection
);
123 static void abort_lasso(Collection
*collection
);
124 static void remove_lasso_box(Collection
*collection
);
125 static void draw_lasso_box(Collection
*collection
);
126 static void cancel_wink(Collection
*collection
);
127 static gint
collection_key_press(GtkWidget
*widget
, GdkEventKey
*event
);
128 static void get_visible_limits(Collection
*collection
, int *first
, int *last
);
129 static void scroll_to_show(Collection
*collection
, int item
);
130 static void collection_item_set_selected(Collection
*collection
,
134 static gint
collection_scroll_event(GtkWidget
*widget
, GdkEventScroll
*event
);
136 static void draw_focus_at(Collection
*collection
, GdkRectangle
*area
)
141 widget
= GTK_WIDGET(collection
);
143 if (GTK_WIDGET_FLAGS(widget
) & GTK_HAS_FOCUS
)
144 state
= GTK_STATE_ACTIVE
;
146 state
= GTK_STATE_INSENSITIVE
;
148 gtk_paint_focus(widget
->style
,
155 collection
->item_width
,
159 static void draw_one_item(Collection
*collection
, int item
, GdkRectangle
*area
)
161 if (item
< collection
->number_of_items
)
163 collection
->draw_item((GtkWidget
*) collection
,
164 &collection
->items
[item
],
165 area
, collection
->cb_user_data
);
168 if (item
== collection
->cursor_item
)
169 draw_focus_at(collection
, area
);
172 GType
collection_get_type(void)
174 static GType my_type
= 0;
178 static const GTypeInfo info
=
180 sizeof(CollectionClass
),
181 NULL
, /* base_init */
182 NULL
, /* base_finalise */
183 (GClassInitFunc
) collection_class_init
,
184 NULL
, /* class_finalise */
185 NULL
, /* class_data */
191 my_type
= g_type_register_static(gtk_widget_get_type(),
192 "Collection", &info
, 0);
198 typedef void (*FinalizeFn
)(GObject
*object
);
200 static void collection_class_init(GObjectClass
*gclass
, gpointer data
)
202 CollectionClass
*collection_class
= (CollectionClass
*) gclass
;
203 GtkObjectClass
*object_class
= (GtkObjectClass
*) gclass
;
204 GtkWidgetClass
*widget_class
= (GtkWidgetClass
*) gclass
;
206 parent_class
= gtk_type_class(gtk_widget_get_type());
208 object_class
->destroy
= collection_destroy
;
209 G_OBJECT_CLASS(object_class
)->finalize
=
210 (FinalizeFn
) collection_finalize
;
212 widget_class
->realize
= collection_realize
;
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
;
219 widget_class
->motion_notify_event
= collection_motion_notify
;
220 widget_class
->map
= collection_map
;
221 widget_class
->scroll_event
= collection_scroll_event
;
223 gclass
->set_property
= collection_set_property
;
224 gclass
->get_property
= collection_get_property
;
226 collection_class
->gain_selection
= NULL
;
227 collection_class
->lose_selection
= NULL
;
228 collection_class
->selection_changed
= NULL
;
230 collection_signals
[GAIN_SELECTION
] = g_signal_new("gain_selection",
231 G_TYPE_FROM_CLASS(gclass
),
233 G_STRUCT_OFFSET(CollectionClass
,
236 g_cclosure_marshal_VOID__INT
,
240 collection_signals
[LOSE_SELECTION
] = g_signal_new("lose_selection",
241 G_TYPE_FROM_CLASS(gclass
),
243 G_STRUCT_OFFSET(CollectionClass
,
246 g_cclosure_marshal_VOID__INT
,
250 collection_signals
[SELECTION_CHANGED
] = g_signal_new(
252 G_TYPE_FROM_CLASS(gclass
),
254 G_STRUCT_OFFSET(CollectionClass
,
257 g_cclosure_marshal_VOID__INT
,
261 g_object_class_install_property(gclass
,
263 g_param_spec_object("vadjustment",
264 "Vertical Adjustment",
265 "The GtkAdjustment for the vertical position.",
267 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT
));
270 static void collection_init(GTypeInstance
*instance
, gpointer g_class
)
272 Collection
*object
= (Collection
*) instance
;
274 g_return_if_fail(object
!= NULL
);
275 g_return_if_fail(IS_COLLECTION(object
));
277 GTK_WIDGET_SET_FLAGS(GTK_WIDGET(object
), GTK_CAN_FOCUS
);
279 object
->number_of_items
= 0;
280 object
->number_selected
= 0;
281 object
->block_selection_changed
= 0;
283 object
->item_width
= 64;
284 object
->item_height
= 64;
287 object
->items
= g_new(CollectionItem
, MINIMUM_ITEMS
);
288 object
->cursor_item
= -1;
289 object
->cursor_item_old
= -1;
290 object
->wink_item
= -1;
291 object
->wink_on_map
= -1;
292 object
->array_size
= MINIMUM_ITEMS
;
293 object
->draw_item
= default_draw_item
;
294 object
->test_point
= default_test_point
;
295 object
->free_item
= NULL
;
298 GtkWidget
* collection_new(void)
300 return GTK_WIDGET(gtk_widget_new(collection_get_type(), NULL
));
303 /* After this we are unusable, but our data (if any) is still hanging around.
304 * It will be freed later with finalize.
306 static void collection_destroy(GtkObject
*object
)
308 Collection
*collection
;
310 g_return_if_fail(object
!= NULL
);
311 g_return_if_fail(IS_COLLECTION(object
));
313 collection
= COLLECTION(object
);
315 collection_clear(collection
);
317 if (collection
->vadj
)
319 g_object_unref(G_OBJECT(collection
->vadj
));
320 collection
->vadj
= NULL
;
323 if (GTK_OBJECT_CLASS(parent_class
)->destroy
)
324 (*GTK_OBJECT_CLASS(parent_class
)->destroy
)(object
);
327 /* This is the last thing that happens to us. Free all data. */
328 static void collection_finalize(GObject
*object
)
330 Collection
*collection
;
332 collection
= COLLECTION(object
);
334 g_return_if_fail(collection
->number_of_items
== 0);
336 g_free(collection
->items
);
338 if (G_OBJECT_CLASS(parent_class
)->finalize
)
339 G_OBJECT_CLASS(parent_class
)->finalize(object
);
343 static void collection_drag_leave(GtkWidget
*widget
, GdkDragContext
*context
,
346 Collection
*collection
= COLLECTION(widget
);
348 /* Note that this isn't always called when the pointer leaves the
349 * widget; only if we highlighted an item at some point.
352 /* collection_set_autoscroll(collection, FALSE); - not needed? */
353 collection_set_cursor_item(collection
, -1);
355 if (GTK_WIDGET_CLASS(parent_class
)->drag_leave
)
356 (*GTK_WIDGET_CLASS(parent_class
)->drag_leave
)(widget
, context
,
361 static void collection_map(GtkWidget
*widget
)
363 Collection
*collection
= COLLECTION(widget
);
365 if (GTK_WIDGET_CLASS(parent_class
)->map
)
366 (*GTK_WIDGET_CLASS(parent_class
)->map
)(widget
);
368 if (collection
->wink_on_map
>= 0)
370 collection_wink_item(collection
, collection
->wink_on_map
);
371 collection
->wink_on_map
= -1;
375 static void collection_realize(GtkWidget
*widget
)
377 Collection
*collection
;
378 GdkWindowAttr attributes
;
379 gint attributes_mask
;
380 GdkGCValues xor_values
;
383 g_return_if_fail(widget
!= NULL
);
384 g_return_if_fail(IS_COLLECTION(widget
));
385 g_return_if_fail(widget
->parent
!= NULL
);
387 GTK_WIDGET_SET_FLAGS(widget
, GTK_REALIZED
);
388 collection
= COLLECTION(widget
);
390 attributes
.x
= widget
->allocation
.x
;
391 attributes
.y
= widget
->allocation
.y
;
392 attributes
.width
= widget
->allocation
.width
;
393 attributes
.height
= widget
->allocation
.height
;
394 attributes
.wclass
= GDK_INPUT_OUTPUT
;
395 attributes
.window_type
= GDK_WINDOW_CHILD
;
396 attributes
.event_mask
= gtk_widget_get_events(widget
) |
398 GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK
|
399 GDK_BUTTON1_MOTION_MASK
| GDK_BUTTON2_MOTION_MASK
|
400 GDK_BUTTON3_MOTION_MASK
;
401 attributes
.visual
= gtk_widget_get_visual(widget
);
402 attributes
.colormap
= gtk_widget_get_colormap(widget
);
404 attributes_mask
= GDK_WA_X
| GDK_WA_Y
|
405 GDK_WA_VISUAL
| GDK_WA_COLORMAP
;
406 widget
->window
= gdk_window_new(gtk_widget_get_parent_window(widget
),
407 &attributes
, attributes_mask
);
409 widget
->style
= gtk_style_attach(widget
->style
, widget
->window
);
411 gdk_window_set_user_data(widget
->window
, widget
);
412 gdk_window_set_background(widget
->window
,
413 &widget
->style
->bg
[GTK_STATE_NORMAL
]);
415 bg
= &widget
->style
->bg
[GTK_STATE_NORMAL
];
416 fg
= &widget
->style
->fg
[GTK_STATE_NORMAL
];
417 xor_values
.function
= GDK_XOR
;
418 xor_values
.foreground
.pixel
= fg
->pixel
^ bg
->pixel
;
419 collection
->xor_gc
= gdk_gc_new_with_values(widget
->window
,
425 static void collection_size_request(GtkWidget
*widget
,
426 GtkRequisition
*requisition
)
428 Collection
*collection
= COLLECTION(widget
);
429 int rows
, cols
= collection
->columns
;
431 /* We ask for the total size we need; our containing viewport
432 * will deal with scrolling.
434 requisition
->width
= MIN_WIDTH
;
435 rows
= (collection
->number_of_items
+ cols
- 1) / cols
;
436 requisition
->height
= rows
* collection
->item_height
;
439 static gboolean
scroll_after_alloc(Collection
*collection
)
441 if (collection
->wink_item
!= -1)
442 scroll_to_show(collection
, collection
->wink_item
);
443 else if (collection
->cursor_item
!= -1)
444 scroll_to_show(collection
, collection
->cursor_item
);
445 g_object_unref(G_OBJECT(collection
));
450 static void collection_size_allocate(GtkWidget
*widget
,
451 GtkAllocation
*allocation
)
453 Collection
*collection
;
455 gboolean cursor_visible
= FALSE
;
457 g_return_if_fail(widget
!= NULL
);
458 g_return_if_fail(IS_COLLECTION(widget
));
459 g_return_if_fail(allocation
!= NULL
);
461 collection
= COLLECTION(widget
);
463 if (collection
->cursor_item
!= -1)
466 int crow
= collection
->cursor_item
/ collection
->columns
;
468 get_visible_limits(collection
, &first
, &last
);
470 cursor_visible
= crow
>= first
&& crow
<= last
;
473 old_columns
= collection
->columns
;
475 widget
->allocation
= *allocation
;
477 collection
->columns
= allocation
->width
/ collection
->item_width
;
478 if (collection
->columns
< 1)
479 collection
->columns
= 1;
481 if (GTK_WIDGET_REALIZED(widget
))
483 gdk_window_move_resize(widget
->window
,
484 allocation
->x
, allocation
->y
,
485 allocation
->width
, allocation
->height
);
488 scroll_to_show(collection
, collection
->cursor_item
);
491 if (old_columns
!= collection
->columns
)
493 /* Need to go around again... */
494 gtk_widget_queue_resize(widget
);
496 else if (collection
->wink_item
!= -1 || collection
->cursor_item
!= -1)
498 /* Viewport resets the adjustments after the alloc */
499 g_object_ref(G_OBJECT(collection
));
500 g_idle_add((GSourceFunc
) scroll_after_alloc
, collection
);
504 /* Return the area occupied by the item at (row, col) by filling
507 static void collection_get_item_area(Collection
*collection
,
512 area
->x
= col
* collection
->item_width
;
513 area
->y
= row
* collection
->item_height
;
515 area
->width
= collection
->item_width
;
516 area
->height
= collection
->item_height
;
517 if (col
== collection
->columns
- 1)
521 static gint
collection_expose(GtkWidget
*widget
, GdkEventExpose
*event
)
523 Collection
*collection
;
524 GdkRectangle item_area
;
527 int start_row
, last_row
;
528 int start_col
, last_col
;
531 g_return_val_if_fail(widget
!= NULL
, FALSE
);
532 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
533 g_return_val_if_fail(event
!= NULL
, FALSE
);
535 gtk_paint_flat_box(widget
->style
, widget
->window
, GTK_STATE_NORMAL
,
536 GTK_SHADOW_NONE
, &event
->area
,
537 widget
, "base", 0, 0, -1, -1);
539 collection
= COLLECTION(widget
);
541 /* Calculate the ranges to plot */
542 start_row
= event
->area
.y
/ collection
->item_height
;
543 last_row
= (event
->area
.y
+ event
->area
.height
- 1)
544 / collection
->item_height
;
547 start_col
= event
->area
.x
/ collection
->item_width
;
548 phys_last_col
= (event
->area
.x
+ event
->area
.width
- 1)
549 / collection
->item_width
;
551 /* The right-most column may be wider than the others.
552 * Therefore, to redraw the area after the last 'real' column
553 * we may have to draw the right-most column.
555 if (start_col
>= collection
->columns
)
556 start_col
= collection
->columns
- 1;
558 if (phys_last_col
>= collection
->columns
)
559 last_col
= collection
->columns
- 1;
561 last_col
= phys_last_col
;
565 item
= row
* collection
->columns
+ col
;
567 while ((item
== 0 || item
< collection
->number_of_items
)
570 collection_get_item_area(collection
, row
, col
, &item_area
);
572 draw_one_item(collection
, item
, &item_area
);
579 item
= row
* collection
->columns
+ col
;
585 if (collection
->lasso_box
)
586 draw_lasso_box(collection
);
591 static void default_draw_item(GtkWidget
*widget
,
592 CollectionItem
*item
,
596 gdk_draw_arc(widget
->window
,
597 item
->selected
? widget
->style
->white_gc
598 : widget
->style
->black_gc
,
601 COLLECTION(widget
)->item_width
, area
->height
,
606 static gboolean
default_test_point(Collection
*collection
,
607 int point_x
, int point_y
,
608 CollectionItem
*item
,
609 int width
, int height
,
614 /* Convert to point in unit circle */
615 f_x
= ((float) point_x
/ width
) - 0.5;
616 f_y
= ((float) point_y
/ height
) - 0.5;
618 return (f_x
* f_x
) + (f_y
* f_y
) <= .25;
621 static void collection_set_property(GObject
*object
,
626 Collection
*collection
;
628 collection
= COLLECTION(object
);
632 case PROP_VADJUSTMENT
:
633 collection_set_adjustment(collection
,
634 g_value_get_object(value
));
637 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
,
643 static void collection_set_adjustment(Collection
*collection
,
647 g_return_if_fail(GTK_IS_ADJUSTMENT(vadj
));
649 vadj
= GTK_ADJUSTMENT(gtk_adjustment_new(0.0,
653 if (collection
->vadj
== vadj
)
656 if (collection
->vadj
)
657 g_object_unref(G_OBJECT(collection
->vadj
));
659 collection
->vadj
= vadj
;
660 g_object_ref(G_OBJECT(collection
->vadj
));
661 gtk_object_sink(GTK_OBJECT(collection
->vadj
));
664 static void collection_get_property(GObject
*object
,
669 Collection
*collection
;
671 collection
= COLLECTION(object
);
675 case PROP_VADJUSTMENT
:
676 g_value_set_object(value
, G_OBJECT(collection
->vadj
));
679 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
,
685 static void resize_arrays(Collection
*collection
, guint new_size
)
687 g_return_if_fail(collection
!= NULL
);
688 g_return_if_fail(IS_COLLECTION(collection
));
689 g_return_if_fail(new_size
>= collection
->number_of_items
);
691 collection
->items
= g_realloc(collection
->items
,
692 sizeof(CollectionItem
) * new_size
);
693 collection
->array_size
= new_size
;
696 static gint
collection_key_press(GtkWidget
*widget
, GdkEventKey
*event
)
698 Collection
*collection
;
702 g_return_val_if_fail(widget
!= NULL
, FALSE
);
703 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
704 g_return_val_if_fail(event
!= NULL
, FALSE
);
706 collection
= (Collection
*) widget
;
707 item
= collection
->cursor_item
;
710 if (event
->state
& (GDK_CONTROL_MASK
| GDK_SHIFT_MASK
))
712 if (key
== GDK_Left
|| key
== GDK_Right
|| \
713 key
== GDK_Up
|| key
== GDK_Down
)
721 collection_move_cursor(collection
, 0, -1);
724 collection_move_cursor(collection
, 0, 1);
727 collection_move_cursor(collection
, -1, 0);
730 collection_move_cursor(collection
, 1, 0);
733 collection_set_cursor_item(collection
, 0, TRUE
);
736 collection_set_cursor_item(collection
,
737 MAX((gint
) collection
->number_of_items
- 1, 0),
743 get_visible_limits(collection
, &first
, &last
);
744 collection_move_cursor(collection
, first
- last
- 1, 0);
750 get_visible_limits(collection
, &first
, &last
);
751 collection_move_cursor(collection
, last
- first
+ 1, 0);
761 /* Wheel mouse scrolling */
762 static gint
collection_scroll_event(GtkWidget
*widget
, GdkEventScroll
*event
)
764 Collection
*collection
;
767 g_return_val_if_fail(widget
!= NULL
, FALSE
);
768 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
769 g_return_val_if_fail(event
!= NULL
, FALSE
);
771 collection
= COLLECTION(widget
);
773 if (event
->direction
== GDK_SCROLL_UP
)
775 else if (event
->direction
== GDK_SCROLL_DOWN
)
782 int old_value
= collection
->vadj
->value
;
784 gboolean box
= collection
->lasso_box
;
785 int step
= collection
->vadj
->page_increment
/ 2;
787 new_value
= CLAMP(old_value
+ diff
* step
, 0.0,
788 collection
->vadj
->upper
789 - collection
->vadj
->page_size
);
790 diff
= new_value
- old_value
;
795 remove_lasso_box(collection
);
796 collection
->drag_box_y
[0] -= diff
;
798 gtk_adjustment_set_value(collection
->vadj
, new_value
);
800 add_lasso_box(collection
);
807 /* 'from' and 'to' are pixel positions. 'step' is the size of each item.
808 * Returns the index of the first item covered, and the number of items.
810 static void get_range(int from
, int to
, int step
, gint
*pos
, gint
*len
)
812 int margin
= MIN(step
/ 4, 40);
821 from
= (from
+ margin
) / step
; /* First item */
822 to
= (to
+ step
- margin
) / step
; /* Last item (inclusive) */
828 /* Fills in the area with a rectangle corresponding to the current
829 * size of the lasso box (units of items, not pixels).
831 * The box will only span valid columns, but the total number
832 * of items is not taken into account (rows or cols).
834 static void find_lasso_area(Collection
*collection
, GdkRectangle
*area
)
836 int cols
= collection
->columns
;
837 int dx
= collection
->drag_box_x
[0] - collection
->drag_box_x
[1];
838 int dy
= collection
->drag_box_y
[0] - collection
->drag_box_y
[1];
840 if (ABS(dx
) < 8 && ABS(dy
) < 8)
842 /* Didn't move far enough - ignore */
843 area
->x
= area
->y
= 0;
849 get_range(collection
->drag_box_x
[0], collection
->drag_box_x
[1],
850 collection
->item_width
, &area
->x
, &area
->width
);
854 else if (area
->x
+ area
->width
> cols
)
855 area
->width
= cols
- area
->x
;
857 get_range(collection
->drag_box_y
[0], collection
->drag_box_y
[1],
858 collection
->item_height
, &area
->y
, &area
->height
);
861 static void collection_process_area(Collection
*collection
,
867 guint32 stacked_time
;
869 gboolean changed
= FALSE
;
872 g_return_if_fail(fn
== GDK_SET
|| fn
== GDK_INVERT
);
874 old_selected
= collection
->number_selected
;
876 stacked_time
= current_event_time
;
877 current_event_time
= time
;
879 collection
->block_selection_changed
++;
881 for (y
= area
->y
; y
< area
->y
+ area
->height
; y
++)
883 item
= y
* collection
->columns
+ area
->x
;
885 for (x
= area
->x
; x
< area
->x
+ area
->width
; x
++)
887 if (item
>= collection
->number_of_items
)
890 if (fn
== GDK_INVERT
)
891 collection_item_set_selected(collection
, item
,
892 !collection
->items
[item
].selected
,
895 collection_item_set_selected(collection
, item
,
904 if (collection
->number_selected
&& !old_selected
)
905 g_signal_emit(collection
,
906 collection_signals
[GAIN_SELECTION
], 0,
908 else if (!collection
->number_selected
&& old_selected
)
909 g_signal_emit(collection
,
910 collection_signals
[LOSE_SELECTION
], 0,
913 collection_unblock_selection_changed(collection
,
914 current_event_time
, changed
);
915 current_event_time
= stacked_time
;
918 static gint
collection_motion_notify(GtkWidget
*widget
,
919 GdkEventMotion
*event
)
921 Collection
*collection
;
924 g_return_val_if_fail(widget
!= NULL
, FALSE
);
925 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
926 g_return_val_if_fail(event
!= NULL
, FALSE
);
928 collection
= COLLECTION(widget
);
930 if (!collection
->lasso_box
)
933 if (event
->window
!= widget
->window
)
934 gdk_window_get_pointer(widget
->window
, &x
, &y
, NULL
);
941 remove_lasso_box(collection
);
942 collection
->drag_box_x
[1] = x
;
943 collection
->drag_box_y
[1] = y
;
944 add_lasso_box(collection
);
948 static void add_lasso_box(Collection
*collection
)
950 g_return_if_fail(collection
!= NULL
);
951 g_return_if_fail(IS_COLLECTION(collection
));
952 g_return_if_fail(collection
->lasso_box
== FALSE
);
954 collection
->lasso_box
= TRUE
;
955 draw_lasso_box(collection
);
958 static void draw_lasso_box(Collection
*collection
)
961 int x
, y
, width
, height
;
963 widget
= GTK_WIDGET(collection
);
965 x
= MIN(collection
->drag_box_x
[0], collection
->drag_box_x
[1]);
966 y
= MIN(collection
->drag_box_y
[0], collection
->drag_box_y
[1]);
967 width
= abs(collection
->drag_box_x
[1] - collection
->drag_box_x
[0]);
968 height
= abs(collection
->drag_box_y
[1] - collection
->drag_box_y
[0]);
970 /* XXX: A redraw bug sometimes leaves a one-pixel dot on the screen.
971 * As a quick hack, don't draw boxes that small for now...
974 gdk_draw_rectangle(widget
->window
, collection
->xor_gc
, FALSE
,
975 x
, y
, width
, height
);
978 static void abort_lasso(Collection
*collection
)
980 if (collection
->lasso_box
)
981 remove_lasso_box(collection
);
984 static void remove_lasso_box(Collection
*collection
)
986 g_return_if_fail(collection
!= NULL
);
987 g_return_if_fail(IS_COLLECTION(collection
));
988 g_return_if_fail(collection
->lasso_box
== TRUE
);
990 draw_lasso_box(collection
);
992 collection
->lasso_box
= FALSE
;
997 /* Make sure that 'item' is fully visible (vertically), scrolling if not. */
998 static void scroll_to_show(Collection
*collection
, int item
)
1000 int first
, last
, row
;
1002 g_return_if_fail(collection
!= NULL
);
1003 g_return_if_fail(IS_COLLECTION(collection
));
1005 row
= item
/ collection
->columns
;
1006 get_visible_limits(collection
, &first
, &last
);
1010 gtk_adjustment_set_value(collection
->vadj
,
1011 row
* collection
->item_height
);
1013 else if (row
>= last
)
1015 GtkWidget
*widget
= (GtkWidget
*) collection
;
1018 if (GTK_WIDGET_REALIZED(widget
))
1020 height
= collection
->vadj
->page_size
;
1021 gtk_adjustment_set_value(collection
->vadj
,
1022 (row
+ 1) * collection
->item_height
- height
);
1027 /* Return the first and last rows which are [partly] visible. Does not
1028 * ensure that the rows actually exist (contain items).
1030 static void get_visible_limits(Collection
*collection
, int *first
, int *last
)
1032 GtkWidget
*widget
= (GtkWidget
*) collection
;
1033 gint scroll
= 0, height
;
1035 g_return_if_fail(collection
!= NULL
);
1036 g_return_if_fail(IS_COLLECTION(collection
));
1037 g_return_if_fail(first
!= NULL
&& last
!= NULL
);
1039 if (!GTK_WIDGET_REALIZED(widget
))
1046 scroll
= collection
->vadj
->value
;
1047 height
= collection
->vadj
->page_size
;
1049 *first
= MAX(scroll
/ collection
->item_height
, 0);
1050 *last
= (scroll
+ height
- 1) /collection
->item_height
;
1057 /* Cancel the current wink effect. */
1058 static void cancel_wink(Collection
*collection
)
1062 g_return_if_fail(collection
!= NULL
);
1063 g_return_if_fail(IS_COLLECTION(collection
));
1064 g_return_if_fail(collection
->wink_item
!= -1);
1066 item
= collection
->wink_item
;
1068 collection
->wink_item
= -1;
1069 gtk_timeout_remove(collection
->wink_timeout
);
1071 collection_draw_item(collection
, item
, TRUE
);
1074 /* Draw/undraw a box around collection->wink_item */
1075 static void invert_wink(Collection
*collection
)
1080 g_return_if_fail(collection
->wink_item
>= 0);
1082 if (!GTK_WIDGET_REALIZED(GTK_WIDGET(collection
)))
1085 col
= collection
->wink_item
% collection
->columns
;
1086 row
= collection
->wink_item
/ collection
->columns
;
1087 collection_get_item_area(collection
, row
, col
, &area
);
1089 gdk_draw_rectangle(((GtkWidget
*) collection
)->window
,
1090 collection
->xor_gc
, FALSE
,
1092 collection
->item_width
- 1,
1096 static gboolean
wink_timeout(Collection
*collection
)
1100 g_return_val_if_fail(collection
!= NULL
, FALSE
);
1101 g_return_val_if_fail(IS_COLLECTION(collection
), FALSE
);
1102 g_return_val_if_fail(collection
->wink_item
!= -1, FALSE
);
1104 item
= collection
->wink_item
;
1106 if (collection
->winks_left
-- > 0)
1108 invert_wink(collection
);
1112 collection
->wink_item
= -1;
1114 collection_draw_item(collection
, item
, TRUE
);
1119 /* Change the selected state of an item.
1120 * Send GAIN/LOSE signals if 'signal' is TRUE.
1121 * Send SELECTION_CHANGED unless blocked.
1122 * Updates number_selected and redraws the item.
1124 static void collection_item_set_selected(Collection
*collection
,
1129 g_return_if_fail(collection
!= NULL
);
1130 g_return_if_fail(IS_COLLECTION(collection
));
1131 g_return_if_fail(item
>= 0 && item
< collection
->number_of_items
);
1133 if (collection
->items
[item
].selected
== selected
)
1136 collection
->items
[item
].selected
= selected
;
1137 collection_draw_item(collection
, item
, TRUE
);
1141 collection
->number_selected
++;
1142 if (signal
&& collection
->number_selected
== 1)
1143 g_signal_emit(collection
,
1144 collection_signals
[GAIN_SELECTION
], 0,
1145 current_event_time
);
1149 collection
->number_selected
--;
1150 if (signal
&& collection
->number_selected
== 0)
1151 g_signal_emit(collection
,
1152 collection_signals
[LOSE_SELECTION
], 0,
1153 current_event_time
);
1156 EMIT_SELECTION_CHANGED(collection
, current_event_time
);
1159 /* Functions for managing collections */
1161 /* Remove all objects from the collection */
1162 void collection_clear(Collection
*collection
)
1164 collection_delete_if(collection
, NULL
, NULL
);
1167 /* Inserts a new item at the end. The new item is unselected, and its
1168 * number is returned.
1170 gint
collection_insert(Collection
*collection
, gpointer data
, gpointer view
)
1174 /* g_return_val_if_fail(IS_COLLECTION(collection), -1); (slow) */
1176 item
= collection
->number_of_items
;
1178 if (item
>= collection
->array_size
)
1179 resize_arrays(collection
, item
+ (item
>> 1));
1181 collection
->items
[item
].data
= data
;
1182 collection
->items
[item
].view_data
= view
;
1183 collection
->items
[item
].selected
= FALSE
;
1185 collection
->number_of_items
++;
1187 gtk_widget_queue_resize(GTK_WIDGET(collection
));
1189 collection_draw_item(collection
, item
, FALSE
);
1194 void collection_unselect_item(Collection
*collection
, gint item
)
1196 collection_item_set_selected(collection
, item
, FALSE
, TRUE
);
1199 void collection_select_item(Collection
*collection
, gint item
)
1201 collection_item_set_selected(collection
, item
, TRUE
, TRUE
);
1204 void collection_toggle_item(Collection
*collection
, gint item
)
1206 collection_item_set_selected(collection
, item
,
1207 !collection
->items
[item
].selected
, TRUE
);
1210 /* Select all items in the collection */
1211 void collection_select_all(Collection
*collection
)
1216 g_return_if_fail(collection
!= NULL
);
1217 g_return_if_fail(IS_COLLECTION(collection
));
1219 widget
= GTK_WIDGET(collection
);
1221 if (collection
->number_selected
== collection
->number_of_items
)
1222 return; /* Nothing to do */
1224 while (collection
->number_selected
< collection
->number_of_items
)
1226 while (collection
->items
[item
].selected
)
1229 collection
->items
[item
].selected
= TRUE
;
1230 collection_draw_item(collection
, item
, TRUE
);
1233 collection
->number_selected
++;
1236 g_signal_emit(collection
, collection_signals
[GAIN_SELECTION
], 0,
1237 current_event_time
);
1238 EMIT_SELECTION_CHANGED(collection
, current_event_time
);
1241 /* Toggle all items in the collection */
1242 void collection_invert_selection(Collection
*collection
)
1246 g_return_if_fail(collection
!= NULL
);
1247 g_return_if_fail(IS_COLLECTION(collection
));
1249 if (collection
->number_selected
== 0)
1251 collection_select_all(collection
);
1254 else if (collection
->number_of_items
== collection
->number_selected
)
1256 collection_clear_selection(collection
);
1260 for (item
= 0; item
< collection
->number_of_items
; item
++)
1261 collection
->items
[item
].selected
=
1262 !collection
->items
[item
].selected
;
1264 collection
->number_selected
= collection
->number_of_items
-
1265 collection
->number_selected
;
1267 /* Have to redraw everything... */
1268 gtk_widget_queue_draw(GTK_WIDGET(collection
));
1270 EMIT_SELECTION_CHANGED(collection
, current_event_time
);
1273 /* Unselect all items except number item, which is selected (-1 to unselect
1276 void collection_clear_except(Collection
*collection
, gint item
)
1280 int end
; /* Selected items to end up with */
1282 g_return_if_fail(collection
!= NULL
);
1283 g_return_if_fail(IS_COLLECTION(collection
));
1284 g_return_if_fail(item
>= -1 && item
< collection
->number_of_items
);
1286 widget
= GTK_WIDGET(collection
);
1292 collection_select_item(collection
, item
);
1296 if (collection
->number_selected
== 0)
1299 while (collection
->number_selected
> end
)
1301 while (i
== item
|| !collection
->items
[i
].selected
)
1304 collection
->items
[i
].selected
= FALSE
;
1305 collection_draw_item(collection
, i
, TRUE
);
1308 collection
->number_selected
--;
1312 g_signal_emit(collection
, collection_signals
[LOSE_SELECTION
], 0,
1313 current_event_time
);
1314 EMIT_SELECTION_CHANGED(collection
, current_event_time
);
1317 /* Unselect all items in the collection */
1318 void collection_clear_selection(Collection
*collection
)
1320 g_return_if_fail(collection
!= NULL
);
1321 g_return_if_fail(IS_COLLECTION(collection
));
1323 collection_clear_except(collection
, -1);
1326 /* Force a redraw of the specified item, if it is visible */
1327 void collection_draw_item(Collection
*collection
, gint item
, gboolean blank
)
1333 g_return_if_fail(collection
!= NULL
);
1334 /* g_return_if_fail(IS_COLLECTION(collection)); (slow) */
1335 g_return_if_fail(item
>= 0 &&
1336 (item
== 0 || item
< collection
->number_of_items
));
1338 widget
= GTK_WIDGET(collection
);
1339 if (!GTK_WIDGET_REALIZED(widget
))
1342 col
= item
% collection
->columns
;
1343 row
= item
/ collection
->columns
;
1345 collection_get_item_area(collection
, row
, col
, &area
);
1347 gdk_window_invalidate_rect(widget
->window
, &area
, FALSE
);
1350 void collection_set_item_size(Collection
*collection
, int width
, int height
)
1354 g_return_if_fail(collection
!= NULL
);
1355 g_return_if_fail(IS_COLLECTION(collection
));
1356 g_return_if_fail(width
> 4 && height
> 4);
1358 if (collection
->item_width
== width
&&
1359 collection
->item_height
== height
)
1362 widget
= GTK_WIDGET(collection
);
1364 collection
->item_width
= width
;
1365 collection
->item_height
= height
;
1367 if (GTK_WIDGET_REALIZED(widget
))
1371 gdk_drawable_get_size(widget
->window
, &window_width
, NULL
);
1372 collection
->columns
= MAX(window_width
/ collection
->item_width
,
1374 if (collection
->cursor_item
!= -1)
1375 scroll_to_show(collection
, collection
->cursor_item
);
1376 gtk_widget_queue_draw(widget
);
1379 gtk_widget_queue_resize(GTK_WIDGET(collection
));
1382 static int (*cmp_callback
)(const void *a
, const void *b
) = NULL
;
1383 static int collection_cmp(const void *a
, const void *b
)
1385 return cmp_callback(((CollectionItem
*) a
)->data
,
1386 ((CollectionItem
*) b
)->data
);
1388 static int collection_rcmp(const void *a
, const void *b
)
1390 return -cmp_callback(((CollectionItem
*) a
)->data
,
1391 ((CollectionItem
*) b
)->data
);
1394 /* Cursor is positioned on item with the same data as before the sort.
1395 * Same for the wink item.
1397 void collection_qsort(Collection
*collection
,
1398 int (*compar
)(const void *, const void *),
1401 int cursor
, wink
, items
, wink_on_map
;
1402 gpointer cursor_data
= NULL
;
1403 gpointer wink_data
= NULL
;
1404 gpointer wink_on_map_data
= NULL
;
1405 CollectionItem
*array
;
1407 int mul
= order
== GTK_SORT_ASCENDING
? 1 : -1;
1409 g_return_if_fail(collection
!= NULL
);
1410 g_return_if_fail(IS_COLLECTION(collection
));
1411 g_return_if_fail(compar
!= NULL
);
1412 g_return_if_fail(cmp_callback
== NULL
);
1414 /* Check to see if it needs sorting (saves redrawing) */
1415 if (collection
->number_of_items
< 2)
1418 array
= collection
->items
;
1419 for (i
= 1; i
< collection
->number_of_items
; i
++)
1421 if (mul
* compar(array
[i
- 1].data
, array
[i
].data
) > 0)
1424 if (i
== collection
->number_of_items
)
1425 return; /* Already sorted */
1427 items
= collection
->number_of_items
;
1429 wink_on_map
= collection
->wink_on_map
;
1430 if (wink_on_map
>= 0 && wink_on_map
< items
)
1432 wink_on_map_data
= collection
->items
[wink_on_map
].data
;
1433 collection
->wink_on_map
= -1;
1438 wink
= collection
->wink_item
;
1439 if (wink
>= 0 && wink
< items
)
1441 wink_data
= collection
->items
[wink
].data
;
1442 collection
->wink_item
= -1;
1447 cursor
= collection
->cursor_item
;
1448 if (cursor
>= 0 && cursor
< items
)
1449 cursor_data
= collection
->items
[cursor
].data
;
1453 cmp_callback
= compar
;
1454 qsort(collection
->items
, items
, sizeof(collection
->items
[0]),
1455 order
== GTK_SORT_ASCENDING
? collection_cmp
1457 cmp_callback
= NULL
;
1459 if (cursor
> -1 || wink
> -1 || wink_on_map
> -1)
1463 for (item
= 0; item
< items
; item
++)
1465 if (collection
->items
[item
].data
== cursor_data
)
1466 collection_set_cursor_item(collection
, item
,
1468 if (collection
->items
[item
].data
== wink_on_map_data
)
1469 collection
->wink_on_map
= item
;
1470 if (collection
->items
[item
].data
== wink_data
)
1472 collection
->cursor_item_old
= item
;
1473 collection
->wink_item
= item
;
1474 scroll_to_show(collection
, item
);
1479 gtk_widget_queue_draw(GTK_WIDGET(collection
));
1482 /* Find an item in a sorted collection.
1483 * Returns the item number, or -1 if not found.
1485 int collection_find_item(Collection
*collection
, gpointer data
,
1486 int (*compar
)(const void *, const void *),
1490 int mul
= order
== GTK_SORT_ASCENDING
? 1 : -1;
1492 g_return_val_if_fail(collection
!= NULL
, -1);
1493 g_return_val_if_fail(IS_COLLECTION(collection
), -1);
1494 g_return_val_if_fail(compar
!= NULL
, -1);
1496 /* If item is here, then: lower <= i < upper */
1498 upper
= collection
->number_of_items
;
1500 while (lower
< upper
)
1504 i
= (lower
+ upper
) >> 1;
1506 cmp
= mul
* compar(collection
->items
[i
].data
, data
);
1519 /* Return the number of the item under the point (x,y), or -1 for none.
1520 * This may call your test_point callback. The point is relative to the
1521 * collection's origin.
1523 int collection_get_item(Collection
*collection
, int x
, int y
)
1529 g_return_val_if_fail(collection
!= NULL
, -1);
1531 col
= x
/ collection
->item_width
;
1532 row
= y
/ collection
->item_height
;
1534 if (col
>= collection
->columns
)
1535 col
= collection
->columns
- 1;
1537 if (col
< 0 || row
< 0)
1540 if (col
== collection
->columns
- 1)
1541 width
= collection
->item_width
<< 1;
1543 width
= collection
->item_width
;
1545 item
= col
+ row
* collection
->columns
;
1546 if (item
>= collection
->number_of_items
)
1549 x
-= col
* collection
->item_width
;
1550 y
-= row
* collection
->item_height
;
1552 if (collection
->test_point(collection
, x
, y
,
1553 &collection
->items
[item
], width
, collection
->item_height
,
1554 collection
->cb_user_data
))
1560 /* Set the cursor/highlight over the given item. Passing -1
1561 * hides the cursor. As a special case, you may set the cursor item
1562 * to zero when there are no items.
1564 void collection_set_cursor_item(Collection
*collection
, gint item
,
1565 gboolean may_scroll
)
1569 g_return_if_fail(collection
!= NULL
);
1570 g_return_if_fail(IS_COLLECTION(collection
));
1571 g_return_if_fail(item
>= -1 &&
1572 (item
< collection
->number_of_items
|| item
== 0));
1574 old_item
= collection
->cursor_item
;
1576 if (old_item
== item
)
1579 collection
->cursor_item
= item
;
1582 collection_draw_item(collection
, old_item
, TRUE
);
1586 collection_draw_item(collection
, item
, TRUE
);
1588 scroll_to_show(collection
, item
);
1590 else if (old_item
!= -1)
1591 collection
->cursor_item_old
= old_item
;
1594 /* Briefly highlight an item to draw the user's attention to it.
1595 * -1 cancels the effect, as does deleting items, sorting the collection
1596 * or starting a new wink effect.
1597 * Otherwise, the effect will cancel itself after a short pause.
1599 void collection_wink_item(Collection
*collection
, gint item
)
1601 g_return_if_fail(collection
!= NULL
);
1602 g_return_if_fail(IS_COLLECTION(collection
));
1603 g_return_if_fail(item
>= -1 && item
< collection
->number_of_items
);
1605 if (collection
->wink_item
!= -1)
1606 cancel_wink(collection
);
1610 if (!GTK_WIDGET_MAPPED(GTK_WIDGET(collection
)))
1612 collection
->wink_on_map
= item
;
1616 collection
->cursor_item_old
= collection
->wink_item
= item
;
1617 collection
->winks_left
= MAX_WINKS
;
1619 collection
->wink_timeout
= gtk_timeout_add(70,
1620 (GtkFunction
) wink_timeout
,
1622 scroll_to_show(collection
, item
);
1623 invert_wink(collection
);
1628 /* Call test(item, data) on each item in the collection.
1629 * Remove all items for which it returns TRUE. test() should
1630 * free the data before returning TRUE. The collection is in an
1631 * inconsistant state during this call (ie, when test() is called).
1633 * If test is NULL, remove all items.
1635 void collection_delete_if(Collection
*collection
,
1636 gboolean (*test
)(gpointer item
, gpointer data
),
1643 g_return_if_fail(collection
!= NULL
);
1644 g_return_if_fail(IS_COLLECTION(collection
));
1646 cursor
= collection
->cursor_item
;
1648 for (in
= 0; in
< collection
->number_of_items
; in
++)
1650 if (test
&& !test(collection
->items
[in
].data
, data
))
1653 if (collection
->items
[in
].selected
)
1655 collection
->items
[out
].selected
= TRUE
;
1659 collection
->items
[out
].selected
= FALSE
;
1661 collection
->items
[out
].data
=
1662 collection
->items
[in
].data
;
1663 collection
->items
[out
].view_data
=
1664 collection
->items
[in
].view_data
;
1670 if (collection
->free_item
)
1671 collection
->free_item(collection
,
1672 &collection
->items
[in
]);
1674 if (collection
->cursor_item
>= in
)
1681 collection
->cursor_item
= cursor
;
1683 if (collection
->wink_item
!= -1)
1685 collection
->wink_item
= -1;
1686 gtk_timeout_remove(collection
->wink_timeout
);
1689 collection
->number_of_items
= out
;
1690 if (collection
->number_selected
&& !selected
)
1692 /* We've lost all the selected items */
1693 g_signal_emit(collection
,
1694 collection_signals
[LOSE_SELECTION
], 0,
1695 current_event_time
);
1698 collection
->number_selected
= selected
;
1699 resize_arrays(collection
,
1700 MAX(collection
->number_of_items
, MINIMUM_ITEMS
));
1702 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection
)))
1703 gtk_widget_queue_draw(GTK_WIDGET(collection
));
1705 gtk_widget_queue_resize(GTK_WIDGET(collection
));
1709 /* Move the cursor by the given row and column offsets.
1710 * Moving by (0,0) can be used to simply make the cursor appear.
1712 void collection_move_cursor(Collection
*collection
, int drow
, int dcol
)
1715 int first
, last
, total_rows
;
1717 g_return_if_fail(collection
!= NULL
);
1718 g_return_if_fail(IS_COLLECTION(collection
));
1720 if (!collection
->number_of_items
)
1722 /* Show the cursor, even though there are no items */
1723 collection_set_cursor_item(collection
, 0, TRUE
);
1727 get_visible_limits(collection
, &first
, &last
);
1729 item
= collection
->cursor_item
;
1732 item
= MIN(collection
->cursor_item_old
,
1733 collection
->number_of_items
- 1);
1743 row
= item
/ collection
->columns
;
1744 col
= item
% collection
->columns
+ dcol
;
1748 else if (row
> last
)
1751 row
= MAX(row
+ drow
, 0);
1754 total_rows
= (collection
->number_of_items
+ collection
->columns
- 1)
1755 / collection
->columns
;
1757 if (row
>= total_rows
- 1 && drow
> 0)
1759 row
= total_rows
- 1;
1760 item
= col
+ row
* collection
->columns
;
1761 if (item
>= collection
->number_of_items
- 1)
1763 collection_set_cursor_item(collection
,
1764 collection
->number_of_items
- 1,
1772 item
= col
+ row
* collection
->columns
;
1774 if (item
>= 0 && item
< collection
->number_of_items
)
1775 collection_set_cursor_item(collection
, item
, TRUE
);
1778 /* Start a lasso box drag */
1779 void collection_lasso_box(Collection
*collection
, int x
, int y
)
1781 collection
->drag_box_x
[0] = x
;
1782 collection
->drag_box_y
[0] = y
;
1783 collection
->drag_box_x
[1] = x
;
1784 collection
->drag_box_y
[1] = y
;
1786 add_lasso_box(collection
);
1789 /* Remove the lasso box. Applies fn to each item inside the box.
1790 * fn may be GDK_INVERT, GDK_SET, GDK_NOOP or GDK_CLEAR.
1792 void collection_end_lasso(Collection
*collection
, GdkFunction fn
)
1794 if (fn
!= GDK_CLEAR
)
1796 GdkRectangle region
;
1798 find_lasso_area(collection
, ®ion
);
1800 collection_process_area(collection
, ®ion
, fn
,
1804 abort_lasso(collection
);
1807 /* Unblock the selection_changed signal, emitting the signal if the
1808 * block counter reaches zero and emit is TRUE.
1810 void collection_unblock_selection_changed(Collection
*collection
,
1814 g_return_if_fail(collection
!= NULL
);
1815 g_return_if_fail(IS_COLLECTION(collection
));
1816 g_return_if_fail(collection
->block_selection_changed
> 0);
1818 collection
->block_selection_changed
--;
1820 if (emit
&& !collection
->block_selection_changed
)
1821 g_signal_emit(collection
,
1822 collection_signals
[SELECTION_CHANGED
], 0, time
);