4 * Collection - a GTK+ widget
5 * Copyright (C) 2002, the ROX-Filer team.
7 * The collection widget provides an area for displaying a collection of
8 * objects (such as files). It allows the user to choose a selection of
9 * them and provides signals to allow popping up menus, detecting
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
40 #define AUTOSCROLL_STEP 20
42 #define MAX_WINKS 3 /* Should be an odd number */
44 /* Macro to emit the "selection_changed" signal only if allowed */
45 #define EMIT_SELECTION_CHANGED(collection, time) \
46 if (!collection->block_selection_changed) \
47 gtk_signal_emit(GTK_OBJECT(collection), \
48 collection_signals[SELECTION_CHANGED], time)
58 * void gain_selection(collection, time, user_data)
59 * We've gone from no selected items to having a selection.
60 * Time is the time of the event that caused the change, or
61 * GDK_CURRENT_TIME if not known.
63 * void lose_selection(collection, time, user_data)
64 * We've dropped to having no selected items.
65 * Time is the time of the event that caused the change, or
66 * GDK_CURRENT_TIME if not known.
68 * void selection_changed(collection, user_data)
69 * The set of selected items has changed.
70 * Time is the time of the event that caused the change, or
71 * GDK_CURRENT_TIME if not known.
81 static guint collection_signals
[LAST_SIGNAL
] = { 0 };
83 static guint32 current_event_time
= GDK_CURRENT_TIME
;
85 static GtkWidgetClass
*parent_class
= NULL
;
87 /* Static prototypes */
88 static void clear_area(Collection
*collection
, GdkRectangle
*area
);
89 static void draw_one_item(Collection
*collection
,
92 static void collection_class_init(CollectionClass
*class);
93 static void collection_init(Collection
*object
);
94 static void collection_destroy(GtkObject
*object
);
95 static void collection_finalize(GtkObject
*object
);
96 static void collection_realize(GtkWidget
*widget
);
97 static void collection_map(GtkWidget
*widget
);
98 static gint
collection_paint(Collection
*collection
,
100 static void collection_size_request(GtkWidget
*widget
,
101 GtkRequisition
*requisition
);
102 static void collection_size_allocate(GtkWidget
*widget
,
103 GtkAllocation
*allocation
);
104 static void collection_set_adjustment(Collection
*collection
,
105 GtkAdjustment
*vadj
);
106 static void collection_set_arg( GtkObject
*object
,
109 static void collection_get_arg( GtkObject
*object
,
112 static gint
collection_expose(GtkWidget
*widget
, GdkEventExpose
*event
);
113 static void default_draw_item(GtkWidget
*widget
,
114 CollectionItem
*data
,
117 static gboolean
default_test_point(Collection
*collection
,
118 int point_x
, int point_y
,
119 CollectionItem
*data
,
120 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 abort_lasso(Collection
*collection
);
126 static void remove_lasso_box(Collection
*collection
);
127 static void draw_lasso_box(Collection
*collection
);
128 static void cancel_wink(Collection
*collection
);
129 static gint
collection_key_press(GtkWidget
*widget
, GdkEventKey
*event
);
130 static void get_visible_limits(Collection
*collection
, int *first
, int *last
);
131 static void scroll_to_show(Collection
*collection
, int item
);
132 static void collection_item_set_selected(Collection
*collection
,
136 static gint
collection_scroll_event(GtkWidget
*widget
, GdkEventScroll
*event
);
138 static void draw_focus_at(Collection
*collection
, GdkRectangle
*area
)
143 widget
= GTK_WIDGET(collection
);
145 if (GTK_WIDGET_FLAGS(widget
) & GTK_HAS_FOCUS
)
146 gc
= widget
->style
->fg_gc
[GTK_STATE_ACTIVE
];
148 gc
= widget
->style
->fg_gc
[GTK_STATE_INSENSITIVE
];
150 gdk_draw_rectangle(widget
->window
, gc
, FALSE
,
152 collection
->item_width
- 1,
156 static void draw_one_item(Collection
*collection
, int item
, GdkRectangle
*area
)
158 if (item
< collection
->number_of_items
)
160 collection
->draw_item((GtkWidget
*) collection
,
161 &collection
->items
[item
],
163 collection
->cb_user_data
);
166 if (item
== collection
->cursor_item
)
167 draw_focus_at(collection
, area
);
170 GtkType
collection_get_type(void)
172 static guint my_type
= 0;
176 static const GtkTypeInfo my_info
=
180 sizeof(CollectionClass
),
181 (GtkClassInitFunc
) collection_class_init
,
182 (GtkObjectInitFunc
) collection_init
,
183 NULL
, /* Reserved 1 */
184 NULL
, /* Reserved 2 */
185 (GtkClassInitFunc
) NULL
/* base_class_init_func */
188 my_type
= gtk_type_unique(gtk_widget_get_type(),
195 typedef void (*FinalizeFn
)(GObject
*object
);
197 static void collection_class_init(CollectionClass
*class)
199 GtkObjectClass
*object_class
;
200 GtkWidgetClass
*widget_class
;
203 object_class
= (GtkObjectClass
*) class;
204 widget_class
= (GtkWidgetClass
*) class;
206 parent_class
= gtk_type_class(gtk_widget_get_type());
208 gtk_object_add_arg_type("Collection::vadjustment",
210 GTK_ARG_READWRITE
| GTK_ARG_CONSTRUCT
,
213 object_class
->destroy
= collection_destroy
;
214 G_OBJECT_CLASS(object_class
)->finalize
=
215 (FinalizeFn
) collection_finalize
;
216 type
= GTK_CLASS_TYPE(object_class
);
218 widget_class
->realize
= collection_realize
;
219 widget_class
->expose_event
= collection_expose
;
220 widget_class
->size_request
= collection_size_request
;
221 widget_class
->size_allocate
= collection_size_allocate
;
223 widget_class
->key_press_event
= collection_key_press
;
225 widget_class
->motion_notify_event
= collection_motion_notify
;
226 widget_class
->map
= collection_map
;
227 widget_class
->scroll_event
= collection_scroll_event
;
230 widget_class
->style_set
= collection_set_style
; /* XXX: Test for 2.0 */
233 object_class
->set_arg
= collection_set_arg
;
234 object_class
->get_arg
= collection_get_arg
;
236 class->gain_selection
= NULL
;
237 class->lose_selection
= NULL
;
238 class->selection_changed
= NULL
;
240 /* XXX - do these do anything? */
241 collection_signals
[GAIN_SELECTION
] = gtk_signal_new("gain_selection",
244 GTK_SIGNAL_OFFSET(CollectionClass
,
246 gtk_marshal_NONE__INT
,
249 collection_signals
[LOSE_SELECTION
] = gtk_signal_new("lose_selection",
252 GTK_SIGNAL_OFFSET(CollectionClass
,
254 gtk_marshal_NONE__INT
,
257 collection_signals
[SELECTION_CHANGED
] = gtk_signal_new(
261 GTK_SIGNAL_OFFSET(CollectionClass
,
263 gtk_marshal_NONE__INT
,
268 static void collection_init(Collection
*object
)
270 g_return_if_fail(object
!= NULL
);
271 g_return_if_fail(IS_COLLECTION(object
));
273 GTK_WIDGET_SET_FLAGS(GTK_WIDGET(object
), GTK_CAN_FOCUS
);
275 object
->number_of_items
= 0;
276 object
->number_selected
= 0;
277 object
->block_selection_changed
= 0;
279 object
->item_width
= 64;
280 object
->item_height
= 64;
282 object
->bg_gc
= NULL
;
284 object
->items
= g_new(CollectionItem
, MINIMUM_ITEMS
);
285 object
->cursor_item
= -1;
286 object
->cursor_item_old
= -1;
287 object
->wink_item
= -1;
288 object
->wink_on_map
= -1;
289 object
->array_size
= MINIMUM_ITEMS
;
290 object
->draw_item
= default_draw_item
;
291 object
->test_point
= default_test_point
;
292 object
->free_item
= NULL
;
294 object
->auto_scroll
= -1;
299 GtkWidget
* collection_new(void)
301 return GTK_WIDGET(gtk_widget_new(collection_get_type(), NULL
));
304 /* Note: The draw_item call gives the maximum area that can be
305 * drawn to. For the column on the far right, this extends to the
306 * edge of the window. Normally, use collection->item_width instead
307 * of area->width to calculate the position.
309 * test_point does not use a larger value for the width, but the
310 * x point of the click may be larger than the width.
312 void collection_set_functions(Collection
*collection
,
313 CollectionDrawFunc draw_item
,
314 CollectionTestFunc test_point
,
319 g_return_if_fail(collection
!= NULL
);
320 g_return_if_fail(IS_COLLECTION(collection
));
322 widget
= GTK_WIDGET(collection
);
325 draw_item
= default_draw_item
;
327 test_point
= default_test_point
;
329 collection
->draw_item
= draw_item
;
330 collection
->test_point
= test_point
;
331 collection
->cb_user_data
= user_data
;
333 if (GTK_WIDGET_REALIZED(widget
))
334 gtk_widget_queue_clear(widget
);
337 /* After this we are unusable, but our data (if any) is still hanging around.
338 * It will be freed later with finalize.
340 static void collection_destroy(GtkObject
*object
)
342 Collection
*collection
;
344 g_return_if_fail(object
!= NULL
);
345 g_return_if_fail(IS_COLLECTION(object
));
347 collection
= COLLECTION(object
);
349 collection_clear(collection
);
351 if (collection
->auto_scroll
!= -1)
353 gtk_timeout_remove(collection
->auto_scroll
);
354 collection
->auto_scroll
= -1;
357 if (collection
->bg_gc
)
359 gdk_gc_destroy(collection
->bg_gc
);
360 collection
->bg_gc
= NULL
;
363 if (collection
->vadj
)
365 gtk_object_unref(GTK_OBJECT(collection
->vadj
));
366 collection
->vadj
= NULL
;
369 if (GTK_OBJECT_CLASS(parent_class
)->destroy
)
370 (*GTK_OBJECT_CLASS(parent_class
)->destroy
)(object
);
373 /* This is the last thing that happens to us. Free all data. */
374 static void collection_finalize(GtkObject
*object
)
376 Collection
*collection
;
378 collection
= COLLECTION(object
);
380 g_return_if_fail(collection
->number_of_items
== 0);
382 g_free(collection
->items
);
385 static void collection_map(GtkWidget
*widget
)
387 Collection
*collection
= COLLECTION(widget
);
389 if (GTK_WIDGET_CLASS(parent_class
)->map
)
390 (*GTK_WIDGET_CLASS(parent_class
)->map
)(widget
);
392 if (collection
->wink_on_map
>= 0)
394 collection_wink_item(collection
, collection
->wink_on_map
);
395 collection
->wink_on_map
= -1;
399 static GdkGC
*create_bg_gc(GtkWidget
*widget
)
403 values
.tile
= widget
->style
->bg_pixmap
[GTK_STATE_NORMAL
];
404 values
.fill
= GDK_TILED
;
406 return gdk_gc_new_with_values(widget
->window
, &values
,
407 GDK_GC_FILL
| GDK_GC_TILE
);
410 static void collection_realize(GtkWidget
*widget
)
412 Collection
*collection
;
413 GdkWindowAttr attributes
;
414 gint attributes_mask
;
415 GdkGCValues xor_values
;
418 g_return_if_fail(widget
!= NULL
);
419 g_return_if_fail(IS_COLLECTION(widget
));
420 g_return_if_fail(widget
->parent
!= NULL
);
422 GTK_WIDGET_SET_FLAGS(widget
, GTK_REALIZED
);
423 collection
= COLLECTION(widget
);
425 attributes
.x
= widget
->allocation
.x
;
426 attributes
.y
= widget
->allocation
.y
;
427 attributes
.width
= widget
->allocation
.width
;
428 attributes
.height
= widget
->allocation
.height
;
429 attributes
.wclass
= GDK_INPUT_OUTPUT
;
430 attributes
.window_type
= GDK_WINDOW_CHILD
;
431 attributes
.event_mask
= gtk_widget_get_events(widget
) |
433 GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK
|
434 GDK_BUTTON1_MOTION_MASK
| GDK_BUTTON2_MOTION_MASK
|
435 GDK_BUTTON3_MOTION_MASK
;
436 attributes
.visual
= gtk_widget_get_visual(widget
);
437 attributes
.colormap
= gtk_widget_get_colormap(widget
);
439 attributes_mask
= GDK_WA_X
| GDK_WA_Y
|
440 GDK_WA_VISUAL
| GDK_WA_COLORMAP
;
441 widget
->window
= gdk_window_new(gtk_widget_get_parent_window(widget
),
442 &attributes
, attributes_mask
);
444 widget
->style
= gtk_style_attach(widget
->style
, widget
->window
);
446 gdk_window_set_user_data(widget
->window
, widget
);
448 gdk_window_set_background(widget
->window
,
449 &widget
->style
->bg
[GTK_STATE_NORMAL
]);
450 if (widget
->style
->bg_pixmap
[GTK_STATE_NORMAL
])
451 collection
->bg_gc
= create_bg_gc(widget
);
453 bg
= &widget
->style
->bg
[GTK_STATE_NORMAL
];
454 fg
= &widget
->style
->fg
[GTK_STATE_NORMAL
];
455 xor_values
.function
= GDK_XOR
;
456 xor_values
.foreground
.pixel
= fg
->pixel
^ bg
->pixel
;
457 collection
->xor_gc
= gdk_gc_new_with_values(widget
->window
,
463 static void collection_size_request(GtkWidget
*widget
,
464 GtkRequisition
*requisition
)
466 Collection
*collection
= COLLECTION(widget
);
467 int rows
, cols
= collection
->columns
;
469 /* We ask for the total size we need; our containing viewport
470 * will deal with scrolling.
472 requisition
->width
= MIN_WIDTH
;
473 rows
= (collection
->number_of_items
+ cols
- 1) / cols
;
474 requisition
->height
= rows
* collection
->item_height
;
477 static gboolean
scroll_after_alloc(Collection
*collection
)
479 if (collection
->wink_item
!= -1)
480 scroll_to_show(collection
, collection
->wink_item
);
481 else if (collection
->cursor_item
!= -1)
482 scroll_to_show(collection
, collection
->cursor_item
);
483 g_object_unref(G_OBJECT(collection
));
488 static void collection_size_allocate(GtkWidget
*widget
,
489 GtkAllocation
*allocation
)
491 Collection
*collection
;
493 gboolean cursor_visible
= FALSE
;
495 g_return_if_fail(widget
!= NULL
);
496 g_return_if_fail(IS_COLLECTION(widget
));
497 g_return_if_fail(allocation
!= NULL
);
499 collection
= COLLECTION(widget
);
501 if (collection
->cursor_item
!= -1)
504 int crow
= collection
->cursor_item
/ collection
->columns
;
506 get_visible_limits(collection
, &first
, &last
);
508 cursor_visible
= crow
>= first
&& crow
<= last
;
511 old_columns
= collection
->columns
;
513 widget
->allocation
= *allocation
;
515 collection
->columns
= allocation
->width
/ collection
->item_width
;
516 if (collection
->columns
< 1)
517 collection
->columns
= 1;
519 if (GTK_WIDGET_REALIZED(widget
))
521 gdk_window_move_resize(widget
->window
,
522 allocation
->x
, allocation
->y
,
523 allocation
->width
, allocation
->height
);
526 scroll_to_show(collection
, collection
->cursor_item
);
529 if (old_columns
!= collection
->columns
)
531 /* Need to go around again... */
532 gtk_widget_queue_resize(widget
);
534 else if (collection
->wink_item
!= -1 || collection
->cursor_item
!= -1)
536 /* Viewport resets the adjustments after the alloc */
537 g_object_ref(G_OBJECT(collection
));
538 g_idle_add((GSourceFunc
) scroll_after_alloc
, collection
);
543 static void collection_set_style(GtkWidget
*widget
,
544 GtkStyle
*previous_style
)
546 Collection
*collection
;
548 g_return_if_fail(IS_COLLECTION(widget
));
550 collection
= COLLECTION(widget
);
552 collection
->paint_level
= PAINT_CLEAR
;
554 if (GTK_WIDGET_REALIZED(widget
))
556 gdk_window_set_background(widget
->window
,
557 &widget
->style
->bg
[GTK_STATE_NORMAL
]);
559 if (collection
->bg_gc
)
561 gdk_gc_destroy(collection
->bg_gc
);
562 collection
->bg_gc
= NULL
;
565 if (widget
->style
->bg_pixmap
[GTK_STATE_NORMAL
])
566 collection
->bg_gc
= create_bg_gc(widget
);
571 static void clear_area(Collection
*collection
, GdkRectangle
*area
)
573 GtkWidget
*widget
= GTK_WIDGET(collection
);
575 if (collection
->bg_gc
)
577 gdk_gc_set_ts_origin(collection
->bg_gc
, 0, 0);
579 gdk_draw_rectangle(widget
->window
,
583 area
->width
, area
->height
);
587 gdk_window_set_background(widget
->window
,
588 &widget
->style
->bg
[GTK_STATE_NORMAL
]);
589 gdk_window_clear_area(widget
->window
,
590 area
->x
, area
->y
, area
->width
, area
->height
);
594 /* Return the area occupied by the item at (row, col) by filling
597 static void collection_get_item_area(Collection
*collection
,
602 area
->x
= col
* collection
->item_width
;
603 area
->y
= row
* collection
->item_height
;
605 area
->width
= collection
->item_width
;
606 area
->height
= collection
->item_height
;
607 if (col
== collection
->columns
- 1)
611 static gint
collection_paint(Collection
*collection
,
614 GdkRectangle item_area
;
617 int start_row
, last_row
;
618 int start_col
, last_col
;
622 /* Calculate the ranges to plot */
623 start_row
= area
->y
/ collection
->item_height
;
624 last_row
= (area
->y
+ area
->height
- 1) / collection
->item_height
;
627 start_col
= area
->x
/ collection
->item_width
;
628 phys_last_col
= (area
->x
+ area
->width
- 1) / collection
->item_width
;
630 if (collection
->lasso_box
)
632 /* You can't be too careful with lasso boxes...
634 * clip gives the total area drawn over (this may be larger
635 * than the requested area). It's used to redraw the lasso
638 collection_get_item_area(collection
,
639 start_row
, start_col
, &clip
);
640 clip
.width
*= phys_last_col
- start_col
+ 1;
641 clip
.height
*= last_row
- start_row
+ 1;
643 clear_area(collection
, &clip
);
646 /* The right-most column may be wider than the others.
647 * Therefore, to redraw the area after the last 'real' column
648 * we may have to draw the right-most column.
650 if (start_col
>= collection
->columns
)
651 start_col
= collection
->columns
- 1;
653 if (phys_last_col
>= collection
->columns
)
654 last_col
= collection
->columns
- 1;
656 last_col
= phys_last_col
;
660 item
= row
* collection
->columns
+ col
;
662 /* g_print("[ paint %d..%d ]\n", row, last_row); */
663 while ((item
== 0 || item
< collection
->number_of_items
)
666 collection_get_item_area(collection
, row
, col
, &item_area
);
668 draw_one_item(collection
, item
, &item_area
);
675 item
= row
* collection
->columns
+ col
;
681 if (collection
->lasso_box
)
683 gdk_gc_set_clip_rectangle(collection
->xor_gc
, &clip
);
684 draw_lasso_box(collection
);
685 gdk_gc_set_clip_rectangle(collection
->xor_gc
, NULL
);
691 static void default_draw_item( GtkWidget
*widget
,
692 CollectionItem
*item
,
696 gdk_draw_arc(widget
->window
,
697 item
->selected
? widget
->style
->white_gc
698 : widget
->style
->black_gc
,
701 COLLECTION(widget
)->item_width
, area
->height
,
706 static gboolean
default_test_point(Collection
*collection
,
707 int point_x
, int point_y
,
708 CollectionItem
*item
,
709 int width
, int height
,
714 /* Convert to point in unit circle */
715 f_x
= ((float) point_x
/ width
) - 0.5;
716 f_y
= ((float) point_y
/ height
) - 0.5;
718 return (f_x
* f_x
) + (f_y
* f_y
) <= .25;
721 static void collection_set_arg( GtkObject
*object
,
725 Collection
*collection
;
727 collection
= COLLECTION(object
);
731 case ARG_VADJUSTMENT
:
732 collection_set_adjustment(collection
,
733 GTK_VALUE_POINTER(*arg
));
740 static void collection_set_adjustment(Collection
*collection
,
744 g_return_if_fail (GTK_IS_ADJUSTMENT (vadj
));
746 vadj
= GTK_ADJUSTMENT(gtk_adjustment_new(0.0,
749 if (collection
->vadj
&& (collection
->vadj
!= vadj
))
750 gtk_object_unref(GTK_OBJECT(collection
->vadj
));
752 if (collection
->vadj
!= vadj
)
754 collection
->vadj
= vadj
;
755 gtk_object_ref(GTK_OBJECT(collection
->vadj
));
756 gtk_object_sink(GTK_OBJECT(collection
->vadj
));
760 static void collection_get_arg( GtkObject
*object
,
764 Collection
*collection
;
766 collection
= COLLECTION(object
);
770 case ARG_VADJUSTMENT
:
771 GTK_VALUE_POINTER(*arg
) = collection
->vadj
;
774 arg
->type
= GTK_TYPE_INVALID
;
779 /* Change the adjustment by this amount. Bounded. */
780 static void diff_vpos(Collection
*collection
, int diff
)
782 int value
= collection
->vadj
->value
+ diff
;
784 value
= CLAMP(value
, 0,
785 collection
->vadj
->upper
- collection
->vadj
->page_size
);
786 gtk_adjustment_set_value(collection
->vadj
, value
);
789 static gint
collection_expose(GtkWidget
*widget
, GdkEventExpose
*event
)
791 Collection
*collection
;
793 g_return_val_if_fail(widget
!= NULL
, FALSE
);
794 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
795 g_return_val_if_fail(event
!= NULL
, FALSE
);
797 collection
= COLLECTION(widget
);
799 gtk_paint_flat_box(widget
->style
, widget
->window
,
800 widget
->state
, GTK_SHADOW_NONE
,
801 &event
->area
, widget
, "collection",
804 collection_paint(collection
, &event
->area
);
809 static void resize_arrays(Collection
*collection
, guint new_size
)
811 g_return_if_fail(collection
!= NULL
);
812 g_return_if_fail(IS_COLLECTION(collection
));
813 g_return_if_fail(new_size
>= collection
->number_of_items
);
815 collection
->items
= g_realloc(collection
->items
,
816 sizeof(CollectionItem
) * new_size
);
817 collection
->array_size
= new_size
;
820 static gint
collection_key_press(GtkWidget
*widget
, GdkEventKey
*event
)
822 Collection
*collection
;
826 g_return_val_if_fail(widget
!= NULL
, FALSE
);
827 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
828 g_return_val_if_fail(event
!= NULL
, FALSE
);
830 collection
= (Collection
*) widget
;
831 item
= collection
->cursor_item
;
834 if (event
->state
& (GDK_CONTROL_MASK
| GDK_SHIFT_MASK
))
836 if (key
== GDK_Left
|| key
== GDK_Right
|| \
837 key
== GDK_Up
|| key
== GDK_Down
)
845 collection_move_cursor(collection
, 0, -1);
848 collection_move_cursor(collection
, 0, 1);
851 collection_move_cursor(collection
, -1, 0);
854 collection_move_cursor(collection
, 1, 0);
857 collection_set_cursor_item(collection
, 0);
860 collection_set_cursor_item(collection
,
861 MAX((gint
) collection
->number_of_items
- 1, 0));
866 get_visible_limits(collection
, &first
, &last
);
867 collection_move_cursor(collection
, first
- last
- 1, 0);
873 get_visible_limits(collection
, &first
, &last
);
874 collection_move_cursor(collection
, last
- first
+ 1, 0);
878 collection_set_cursor_item(collection
, -1);
879 collection_clear_selection(collection
);
880 return FALSE
; /* Pass it on */
882 if (item
>=0 && item
< collection
->number_of_items
)
884 collection_toggle_item(collection
, item
);
885 if (item
< collection
->number_of_items
- 1)
886 collection_set_cursor_item(collection
,
897 /* Wheel mouse scrolling */
898 static gint
collection_scroll_event(GtkWidget
*widget
, GdkEventScroll
*event
)
900 Collection
*collection
;
903 g_return_val_if_fail(widget
!= NULL
, FALSE
);
904 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
905 g_return_val_if_fail(event
!= NULL
, FALSE
);
907 collection
= COLLECTION(widget
);
909 if (event
->direction
== GDK_SCROLL_UP
)
911 else if (event
->direction
== GDK_SCROLL_DOWN
)
918 int old_value
= collection
->vadj
->value
;
920 gboolean box
= collection
->lasso_box
;
921 int step
= collection
->vadj
->page_increment
/ 2;
923 new_value
= CLAMP(old_value
+ diff
* step
, 0.0,
924 collection
->vadj
->upper
925 - collection
->vadj
->page_size
);
926 diff
= new_value
- old_value
;
931 remove_lasso_box(collection
);
932 collection
->drag_box_y
[0] -= diff
;
934 gtk_adjustment_set_value(collection
->vadj
, new_value
);
936 add_lasso_box(collection
);
943 /* 'from' and 'to' are pixel positions. 'step' is the size of each item.
944 * Returns the index of the first item covered, and the number of items.
946 static void get_range(int from
, int to
, int step
, gint
*pos
, gint
*len
)
948 int margin
= MIN(step
/ 4, 40);
957 from
= (from
+ margin
) / step
; /* First item */
958 to
= (to
+ step
- margin
) / step
; /* Last item (inclusive) */
964 /* Fills in the area with a rectangle corresponding to the current
965 * size of the lasso box (units of items, not pixels).
967 * The box will only span valid columns, but the total number
968 * of items is not taken into account (rows or cols).
970 static void find_lasso_area(Collection
*collection
, GdkRectangle
*area
)
972 int cols
= collection
->columns
;
973 int dx
= collection
->drag_box_x
[0] - collection
->drag_box_x
[1];
974 int dy
= collection
->drag_box_y
[0] - collection
->drag_box_y
[1];
976 if (ABS(dx
) < 8 && ABS(dy
) < 8)
978 /* Didn't move far enough - ignore */
979 area
->x
= area
->y
= 0;
985 get_range(collection
->drag_box_x
[0], collection
->drag_box_x
[1],
986 collection
->item_width
, &area
->x
, &area
->width
);
990 else if (area
->x
+ area
->width
> cols
)
991 area
->width
= cols
- area
->x
;
993 get_range(collection
->drag_box_y
[0], collection
->drag_box_y
[1],
994 collection
->item_height
, &area
->y
, &area
->height
);
997 static void collection_process_area(Collection
*collection
,
1003 guint32 stacked_time
;
1005 gboolean changed
= FALSE
;
1008 g_return_if_fail(fn
== GDK_SET
|| fn
== GDK_INVERT
);
1010 old_selected
= collection
->number_selected
;
1012 stacked_time
= current_event_time
;
1013 current_event_time
= time
;
1015 collection
->block_selection_changed
++;
1017 for (y
= area
->y
; y
< area
->y
+ area
->height
; y
++)
1019 item
= y
* collection
->columns
+ area
->x
;
1021 for (x
= area
->x
; x
< area
->x
+ area
->width
; x
++)
1023 if (item
>= collection
->number_of_items
)
1026 if (fn
== GDK_INVERT
)
1027 collection_item_set_selected(collection
, item
,
1028 !collection
->items
[item
].selected
,
1031 collection_item_set_selected(collection
, item
,
1040 if (collection
->number_selected
&& !old_selected
)
1041 gtk_signal_emit(GTK_OBJECT(collection
),
1042 collection_signals
[GAIN_SELECTION
],
1043 current_event_time
);
1044 else if (!collection
->number_selected
&& old_selected
)
1045 gtk_signal_emit(GTK_OBJECT(collection
),
1046 collection_signals
[LOSE_SELECTION
],
1047 current_event_time
);
1049 collection_unblock_selection_changed(collection
,
1050 current_event_time
, changed
);
1051 current_event_time
= stacked_time
;
1054 static gint
collection_motion_notify(GtkWidget
*widget
,
1055 GdkEventMotion
*event
)
1057 Collection
*collection
;
1060 g_return_val_if_fail(widget
!= NULL
, FALSE
);
1061 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
1062 g_return_val_if_fail(event
!= NULL
, FALSE
);
1064 collection
= COLLECTION(widget
);
1066 if (!collection
->lasso_box
)
1069 if (event
->window
!= widget
->window
)
1070 gdk_window_get_pointer(widget
->window
, &x
, &y
, NULL
);
1077 remove_lasso_box(collection
);
1078 collection
->drag_box_x
[1] = x
;
1079 collection
->drag_box_y
[1] = y
;
1080 add_lasso_box(collection
);
1084 static void add_lasso_box(Collection
*collection
)
1086 g_return_if_fail(collection
!= NULL
);
1087 g_return_if_fail(IS_COLLECTION(collection
));
1088 g_return_if_fail(collection
->lasso_box
== FALSE
);
1090 collection
->lasso_box
= TRUE
;
1091 draw_lasso_box(collection
);
1094 static void draw_lasso_box(Collection
*collection
)
1097 int x
, y
, width
, height
;
1099 widget
= GTK_WIDGET(collection
);
1101 x
= MIN(collection
->drag_box_x
[0], collection
->drag_box_x
[1]);
1102 y
= MIN(collection
->drag_box_y
[0], collection
->drag_box_y
[1]);
1103 width
= abs(collection
->drag_box_x
[1] - collection
->drag_box_x
[0]);
1104 height
= abs(collection
->drag_box_y
[1] - collection
->drag_box_y
[0]);
1106 gdk_draw_rectangle(widget
->window
, collection
->xor_gc
, FALSE
,
1107 x
, y
, width
, height
);
1110 static void abort_lasso(Collection
*collection
)
1112 if (collection
->lasso_box
)
1114 remove_lasso_box(collection
);
1115 collection_set_autoscroll(collection
, FALSE
);
1119 static void remove_lasso_box(Collection
*collection
)
1121 g_return_if_fail(collection
!= NULL
);
1122 g_return_if_fail(IS_COLLECTION(collection
));
1123 g_return_if_fail(collection
->lasso_box
== TRUE
);
1125 draw_lasso_box(collection
);
1127 collection
->lasso_box
= FALSE
;
1132 /* Make sure that 'item' is fully visible (vertically), scrolling if not. */
1133 static void scroll_to_show(Collection
*collection
, int item
)
1135 int first
, last
, row
;
1137 g_return_if_fail(collection
!= NULL
);
1138 g_return_if_fail(IS_COLLECTION(collection
));
1140 row
= item
/ collection
->columns
;
1141 get_visible_limits(collection
, &first
, &last
);
1145 gtk_adjustment_set_value(collection
->vadj
,
1146 row
* collection
->item_height
);
1148 else if (row
>= last
)
1150 GtkWidget
*widget
= (GtkWidget
*) collection
;
1153 if (GTK_WIDGET_REALIZED(widget
))
1155 height
= collection
->vadj
->page_size
;
1156 gtk_adjustment_set_value(collection
->vadj
,
1157 (row
+ 1) * collection
->item_height
- height
);
1162 /* Return the first and last rows which are [partly] visible. Does not
1163 * ensure that the rows actually exist (contain items).
1165 static void get_visible_limits(Collection
*collection
, int *first
, int *last
)
1167 GtkWidget
*widget
= (GtkWidget
*) collection
;
1168 gint scroll
= 0, height
;
1170 g_return_if_fail(collection
!= NULL
);
1171 g_return_if_fail(IS_COLLECTION(collection
));
1172 g_return_if_fail(first
!= NULL
&& last
!= NULL
);
1174 if (!GTK_WIDGET_REALIZED(widget
))
1181 scroll
= collection
->vadj
->value
;
1182 height
= collection
->vadj
->page_size
;
1184 *first
= MAX(scroll
/ collection
->item_height
, 0);
1185 *last
= (scroll
+ height
- 1) /collection
->item_height
;
1192 /* Cancel the current wink effect. */
1193 static void cancel_wink(Collection
*collection
)
1197 g_return_if_fail(collection
!= NULL
);
1198 g_return_if_fail(IS_COLLECTION(collection
));
1199 g_return_if_fail(collection
->wink_item
!= -1);
1201 item
= collection
->wink_item
;
1203 collection
->wink_item
= -1;
1204 gtk_timeout_remove(collection
->wink_timeout
);
1206 collection_draw_item(collection
, item
, TRUE
);
1209 /* Draw/undraw a box around collection->wink_item */
1210 static void invert_wink(Collection
*collection
)
1215 g_return_if_fail(collection
->wink_item
>= 0);
1217 if (!GTK_WIDGET_REALIZED(GTK_WIDGET(collection
)))
1220 col
= collection
->wink_item
% collection
->columns
;
1221 row
= collection
->wink_item
/ collection
->columns
;
1222 collection_get_item_area(collection
, row
, col
, &area
);
1224 gdk_draw_rectangle(((GtkWidget
*) collection
)->window
,
1225 collection
->xor_gc
, FALSE
,
1227 collection
->item_width
- 1,
1231 static gboolean
wink_timeout(Collection
*collection
)
1235 g_return_val_if_fail(collection
!= NULL
, FALSE
);
1236 g_return_val_if_fail(IS_COLLECTION(collection
), FALSE
);
1237 g_return_val_if_fail(collection
->wink_item
!= -1, FALSE
);
1239 item
= collection
->wink_item
;
1241 if (collection
->winks_left
-- > 0)
1243 invert_wink(collection
);
1247 collection
->wink_item
= -1;
1249 collection_draw_item(collection
, item
, TRUE
);
1254 /* This is called frequently while auto_scroll is on.
1255 * Checks the pointer position and scrolls the window if it's
1256 * near the top or bottom.
1258 static gboolean
as_timeout(Collection
*collection
)
1260 GdkWindow
*window
= GTK_WIDGET(collection
)->window
;
1262 GdkModifierType mask
;
1265 gdk_window_get_pointer(window
, &x
, &y
, &mask
);
1266 gdk_window_get_size(window
, &w
, NULL
);
1268 h
= collection
->vadj
->page_size
;
1269 y
-= collection
->vadj
->value
;
1271 if ((x
< 0 || x
> w
|| y
< 0 || y
> h
) && !collection
->lasso_box
)
1273 collection
->auto_scroll
= -1;
1274 return FALSE
; /* Out of window - stop */
1277 if (y
< AUTOSCROLL_STEP
)
1278 diff
= y
- AUTOSCROLL_STEP
;
1279 else if (y
> h
- AUTOSCROLL_STEP
)
1280 diff
= AUTOSCROLL_STEP
+ y
- h
;
1283 diff_vpos(collection
, diff
);
1288 /* Change the selected state of an item.
1289 * Send GAIN/LOSE signals if 'signal' is TRUE.
1290 * Send SELECTION_CHANGED unless blocked.
1291 * Updates number_selected and redraws the item.
1293 static void collection_item_set_selected(Collection
*collection
,
1298 g_return_if_fail(collection
!= NULL
);
1299 g_return_if_fail(IS_COLLECTION(collection
));
1300 g_return_if_fail(item
>= 0 && item
< collection
->number_of_items
);
1302 if (collection
->items
[item
].selected
== selected
)
1305 collection
->items
[item
].selected
= selected
;
1306 collection_draw_item(collection
, item
, TRUE
);
1310 collection
->number_selected
++;
1311 if (signal
&& collection
->number_selected
== 1)
1312 gtk_signal_emit(GTK_OBJECT(collection
),
1313 collection_signals
[GAIN_SELECTION
],
1314 current_event_time
);
1318 collection
->number_selected
--;
1319 if (signal
&& collection
->number_selected
== 0)
1320 gtk_signal_emit(GTK_OBJECT(collection
),
1321 collection_signals
[LOSE_SELECTION
],
1322 current_event_time
);
1325 EMIT_SELECTION_CHANGED(collection
, current_event_time
);
1328 /* Functions for managing collections */
1330 /* Remove all objects from the collection */
1331 void collection_clear(Collection
*collection
)
1333 collection_delete_if(collection
, NULL
, NULL
);
1336 /* Inserts a new item at the end. The new item is unselected, and its
1337 * number is returned.
1339 gint
collection_insert(Collection
*collection
, gpointer data
, gpointer view
)
1343 g_return_val_if_fail(IS_COLLECTION(collection
), -1);
1345 item
= collection
->number_of_items
;
1347 if (item
>= collection
->array_size
)
1348 resize_arrays(collection
, item
+ (item
>> 1));
1350 collection
->items
[item
].data
= data
;
1351 collection
->items
[item
].view_data
= view
;
1352 collection
->items
[item
].selected
= FALSE
;
1354 collection
->number_of_items
++;
1356 gtk_widget_queue_resize(GTK_WIDGET(collection
));
1358 collection_draw_item(collection
, item
, FALSE
);
1363 void collection_unselect_item(Collection
*collection
, gint item
)
1365 collection_item_set_selected(collection
, item
, FALSE
, TRUE
);
1368 void collection_select_item(Collection
*collection
, gint item
)
1370 collection_item_set_selected(collection
, item
, TRUE
, TRUE
);
1373 void collection_toggle_item(Collection
*collection
, gint item
)
1375 collection_item_set_selected(collection
, item
,
1376 !collection
->items
[item
].selected
, TRUE
);
1379 /* Select all items in the collection */
1380 void collection_select_all(Collection
*collection
)
1385 g_return_if_fail(collection
!= NULL
);
1386 g_return_if_fail(IS_COLLECTION(collection
));
1388 widget
= GTK_WIDGET(collection
);
1390 if (collection
->number_selected
== collection
->number_of_items
)
1391 return; /* Nothing to do */
1393 while (collection
->number_selected
< collection
->number_of_items
)
1395 while (collection
->items
[item
].selected
)
1398 collection
->items
[item
].selected
= TRUE
;
1399 collection_draw_item(collection
, item
, TRUE
);
1402 collection
->number_selected
++;
1405 gtk_signal_emit(GTK_OBJECT(collection
),
1406 collection_signals
[GAIN_SELECTION
],
1407 current_event_time
);
1408 EMIT_SELECTION_CHANGED(collection
, current_event_time
);
1411 /* Toggle all items in the collection */
1412 void collection_invert_selection(Collection
*collection
)
1416 g_return_if_fail(collection
!= NULL
);
1417 g_return_if_fail(IS_COLLECTION(collection
));
1419 if (collection
->number_selected
== 0)
1421 collection_select_all(collection
);
1424 else if (collection
->number_of_items
== collection
->number_selected
)
1426 collection_clear_selection(collection
);
1430 for (item
= 0; item
< collection
->number_of_items
; item
++)
1431 collection
->items
[item
].selected
=
1432 !collection
->items
[item
].selected
;
1434 collection
->number_selected
= collection
->number_of_items
-
1435 collection
->number_selected
;
1437 /* Have to redraw everything... */
1438 gtk_widget_queue_clear(GTK_WIDGET(collection
));
1440 EMIT_SELECTION_CHANGED(collection
, current_event_time
);
1443 /* Unselect all items except number item, which is selected (-1 to unselect
1446 void collection_clear_except(Collection
*collection
, gint item
)
1450 int end
; /* Selected items to end up with */
1452 g_return_if_fail(collection
!= NULL
);
1453 g_return_if_fail(IS_COLLECTION(collection
));
1454 g_return_if_fail(item
>= -1 && item
< collection
->number_of_items
);
1456 widget
= GTK_WIDGET(collection
);
1462 collection_select_item(collection
, item
);
1466 if (collection
->number_selected
== 0)
1469 while (collection
->number_selected
> end
)
1471 while (i
== item
|| !collection
->items
[i
].selected
)
1474 collection
->items
[i
].selected
= FALSE
;
1475 collection_draw_item(collection
, i
, TRUE
);
1478 collection
->number_selected
--;
1482 gtk_signal_emit(GTK_OBJECT(collection
),
1483 collection_signals
[LOSE_SELECTION
],
1484 current_event_time
);
1485 EMIT_SELECTION_CHANGED(collection
, current_event_time
);
1488 /* Unselect all items in the collection */
1489 void collection_clear_selection(Collection
*collection
)
1491 g_return_if_fail(collection
!= NULL
);
1492 g_return_if_fail(IS_COLLECTION(collection
));
1494 collection_clear_except(collection
, -1);
1497 /* Force a redraw of the specified item, if it is visible */
1498 void collection_draw_item(Collection
*collection
, gint item
, gboolean blank
)
1504 g_return_if_fail(collection
!= NULL
);
1505 g_return_if_fail(IS_COLLECTION(collection
));
1506 g_return_if_fail(item
>= 0 &&
1507 (item
== 0 || item
< collection
->number_of_items
));
1509 widget
= GTK_WIDGET(collection
);
1510 if (!GTK_WIDGET_REALIZED(widget
))
1513 col
= item
% collection
->columns
;
1514 row
= item
/ collection
->columns
;
1516 collection_get_item_area(collection
, row
, col
, &area
);
1518 gdk_window_invalidate_rect(widget
->window
, &area
, FALSE
);
1521 void collection_set_item_size(Collection
*collection
, int width
, int height
)
1525 g_return_if_fail(collection
!= NULL
);
1526 g_return_if_fail(IS_COLLECTION(collection
));
1527 g_return_if_fail(width
> 4 && height
> 4);
1529 if (collection
->item_width
== width
&&
1530 collection
->item_height
== height
)
1533 widget
= GTK_WIDGET(collection
);
1535 collection
->item_width
= width
;
1536 collection
->item_height
= height
;
1538 if (GTK_WIDGET_REALIZED(widget
))
1542 gdk_window_get_size(widget
->window
, &window_width
, NULL
);
1543 collection
->columns
= MAX(window_width
/ collection
->item_width
,
1545 if (collection
->cursor_item
!= -1)
1546 scroll_to_show(collection
, collection
->cursor_item
);
1547 gtk_widget_queue_draw(widget
);
1550 gtk_widget_queue_resize(GTK_WIDGET(collection
));
1553 /* Cursor is positioned on item with the same data as before the sort.
1554 * Same for the wink item.
1556 void collection_qsort(Collection
*collection
,
1557 int (*compar
)(const void *, const void *))
1559 int cursor
, wink
, items
, wink_on_map
;
1560 gpointer cursor_data
= NULL
;
1561 gpointer wink_data
= NULL
;
1562 gpointer wink_on_map_data
= NULL
;
1563 CollectionItem
*array
;
1566 g_return_if_fail(collection
!= NULL
);
1567 g_return_if_fail(IS_COLLECTION(collection
));
1568 g_return_if_fail(compar
!= NULL
);
1570 /* Check to see if it needs sorting (saves redrawing) */
1571 if (collection
->number_of_items
< 2)
1574 array
= collection
->items
;
1575 for (i
= 1; i
< collection
->number_of_items
; i
++)
1577 if (compar(&array
[i
- 1], &array
[i
]) > 0)
1580 if (i
== collection
->number_of_items
)
1581 return; /* Already sorted */
1583 items
= collection
->number_of_items
;
1585 wink_on_map
= collection
->wink_on_map
;
1586 if (wink_on_map
>= 0 && wink_on_map
< items
)
1588 wink_on_map_data
= collection
->items
[wink_on_map
].data
;
1589 collection
->wink_on_map
= -1;
1594 wink
= collection
->wink_item
;
1595 if (wink
>= 0 && wink
< items
)
1597 wink_data
= collection
->items
[wink
].data
;
1598 collection
->wink_item
= -1;
1603 cursor
= collection
->cursor_item
;
1604 if (cursor
>= 0 && cursor
< items
)
1605 cursor_data
= collection
->items
[cursor
].data
;
1609 qsort(collection
->items
, items
, sizeof(collection
->items
[0]), compar
);
1611 if (cursor
> -1 || wink
> -1 || wink_on_map
> -1)
1615 for (item
= 0; item
< items
; item
++)
1617 if (collection
->items
[item
].data
== cursor_data
)
1618 collection_set_cursor_item(collection
, item
);
1619 if (collection
->items
[item
].data
== wink_on_map_data
)
1620 collection
->wink_on_map
= item
;
1621 if (collection
->items
[item
].data
== wink_data
)
1623 collection
->cursor_item_old
= item
;
1624 collection
->wink_item
= item
;
1625 scroll_to_show(collection
, item
);
1630 gtk_widget_queue_draw(GTK_WIDGET(collection
));
1633 /* Find an item in an unsorted collection.
1634 * Returns the item number, or -1 if not found.
1636 int collection_find_item(Collection
*collection
, gpointer data
,
1637 int (*compar
)(const void *, const void *))
1641 g_return_val_if_fail(collection
!= NULL
, -1);
1642 g_return_val_if_fail(IS_COLLECTION(collection
), -1);
1643 g_return_val_if_fail(compar
!= NULL
, -1);
1645 for (i
= 0; i
< collection
->number_of_items
; i
++)
1646 if (compar(&collection
->items
[i
].data
, &data
) == 0)
1652 /* Return the number of the item under the point (x,y), or -1 for none.
1653 * This may call your test_point callback. The point is relative to the
1654 * collection's origin.
1656 int collection_get_item(Collection
*collection
, int x
, int y
)
1662 g_return_val_if_fail(collection
!= NULL
, -1);
1664 col
= x
/ collection
->item_width
;
1665 row
= y
/ collection
->item_height
;
1667 if (col
>= collection
->columns
)
1668 col
= collection
->columns
- 1;
1670 if (col
< 0 || row
< 0)
1673 if (col
== collection
->columns
- 1)
1674 width
= collection
->item_width
<< 1;
1676 width
= collection
->item_width
;
1678 item
= col
+ row
* collection
->columns
;
1679 if (item
>= collection
->number_of_items
1681 !collection
->test_point(collection
,
1682 x
- col
* collection
->item_width
,
1683 y
- row
* collection
->item_height
,
1684 &collection
->items
[item
],
1686 collection
->item_height
,
1687 collection
->cb_user_data
))
1695 /* Set the cursor/highlight over the given item. Passing -1
1696 * hides the cursor. As a special case, you may set the cursor item
1697 * to zero when there are no items.
1699 void collection_set_cursor_item(Collection
*collection
, gint item
)
1703 g_return_if_fail(collection
!= NULL
);
1704 g_return_if_fail(IS_COLLECTION(collection
));
1705 g_return_if_fail(item
>= -1 &&
1706 (item
< collection
->number_of_items
|| item
== 0));
1708 old_item
= collection
->cursor_item
;
1710 if (old_item
== item
)
1713 collection
->cursor_item
= item
;
1716 collection_draw_item(collection
, old_item
, TRUE
);
1720 collection_draw_item(collection
, item
, TRUE
);
1721 if (collection
->auto_scroll
== -1)
1722 scroll_to_show(collection
, item
);
1724 else if (old_item
!= -1)
1725 collection
->cursor_item_old
= old_item
;
1728 /* Briefly highlight an item to draw the user's attention to it.
1729 * -1 cancels the effect, as does deleting items, sorting the collection
1730 * or starting a new wink effect.
1731 * Otherwise, the effect will cancel itself after a short pause.
1733 void collection_wink_item(Collection
*collection
, gint item
)
1735 g_return_if_fail(collection
!= NULL
);
1736 g_return_if_fail(IS_COLLECTION(collection
));
1737 g_return_if_fail(item
>= -1 && item
< collection
->number_of_items
);
1739 if (collection
->wink_item
!= -1)
1740 cancel_wink(collection
);
1744 if (!GTK_WIDGET_MAPPED(GTK_WIDGET(collection
)))
1746 collection
->wink_on_map
= item
;
1750 collection
->cursor_item_old
= collection
->wink_item
= item
;
1751 collection
->winks_left
= MAX_WINKS
;
1753 collection
->wink_timeout
= gtk_timeout_add(70,
1754 (GtkFunction
) wink_timeout
,
1756 scroll_to_show(collection
, item
);
1757 invert_wink(collection
);
1762 /* Call test(item, data) on each item in the collection.
1763 * Remove all items for which it returns TRUE. test() should
1764 * free the data before returning TRUE. The collection is in an
1765 * inconsistant state during this call (ie, when test() is called).
1767 * If test is NULL, remove all items.
1769 void collection_delete_if(Collection
*collection
,
1770 gboolean (*test
)(gpointer item
, gpointer data
),
1777 g_return_if_fail(collection
!= NULL
);
1778 g_return_if_fail(IS_COLLECTION(collection
));
1780 cursor
= collection
->cursor_item
;
1782 for (in
= 0; in
< collection
->number_of_items
; in
++)
1784 if (test
&& !test(collection
->items
[in
].data
, data
))
1787 if (collection
->items
[in
].selected
)
1789 collection
->items
[out
].selected
= TRUE
;
1793 collection
->items
[out
].selected
= FALSE
;
1795 collection
->items
[out
].data
=
1796 collection
->items
[in
].data
;
1797 collection
->items
[out
].view_data
=
1798 collection
->items
[in
].view_data
;
1804 if (collection
->free_item
)
1805 collection
->free_item(collection
,
1806 &collection
->items
[in
]);
1815 collection
->cursor_item
= cursor
;
1817 if (collection
->wink_item
!= -1)
1819 collection
->wink_item
= -1;
1820 gtk_timeout_remove(collection
->wink_timeout
);
1823 collection
->number_of_items
= out
;
1824 if (collection
->number_selected
&& !selected
)
1826 /* We've lost all the selected items */
1827 gtk_signal_emit(GTK_OBJECT(collection
),
1828 collection_signals
[LOSE_SELECTION
],
1829 current_event_time
);
1832 collection
->number_selected
= selected
;
1833 resize_arrays(collection
,
1834 MAX(collection
->number_of_items
, MINIMUM_ITEMS
));
1836 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection
)))
1837 gtk_widget_queue_draw(GTK_WIDGET(collection
));
1839 gtk_widget_queue_resize(GTK_WIDGET(collection
));
1843 /* Move the cursor by the given row and column offsets.
1844 * Moving by (0,0) can be used to simply make the cursor appear.
1846 void collection_move_cursor(Collection
*collection
, int drow
, int dcol
)
1849 int first
, last
, total_rows
;
1851 g_return_if_fail(collection
!= NULL
);
1852 g_return_if_fail(IS_COLLECTION(collection
));
1854 get_visible_limits(collection
, &first
, &last
);
1856 item
= collection
->cursor_item
;
1859 item
= MIN(collection
->cursor_item_old
,
1860 collection
->number_of_items
- 1);
1870 row
= item
/ collection
->columns
;
1871 col
= item
% collection
->columns
+ dcol
;
1875 else if (row
> last
)
1878 row
= MAX(row
+ drow
, 0);
1881 total_rows
= (collection
->number_of_items
+ collection
->columns
- 1)
1882 / collection
->columns
;
1884 if (row
>= total_rows
- 1 && drow
> 0)
1886 row
= total_rows
- 1;
1887 item
= col
+ row
* collection
->columns
;
1888 if (item
>= collection
->number_of_items
- 1)
1890 collection_set_cursor_item(collection
,
1891 collection
->number_of_items
- 1);
1898 item
= col
+ row
* collection
->columns
;
1900 if (item
>= 0 && item
< collection
->number_of_items
)
1901 collection_set_cursor_item(collection
, item
);
1904 /* When autoscroll is on, a timer keeps track of the pointer position.
1905 * While it's near the top or bottom of the window, the window scrolls.
1907 * If the mouse buttons are released, or the pointer leaves the window,
1908 * auto_scroll is turned off.
1910 void collection_set_autoscroll(Collection
*collection
, gboolean auto_scroll
)
1912 g_return_if_fail(collection
!= NULL
);
1913 g_return_if_fail(IS_COLLECTION(collection
));
1917 if (collection
->auto_scroll
!= -1)
1918 return; /* Already on! */
1920 collection
->auto_scroll
= gtk_timeout_add(50,
1921 (GtkFunction
) as_timeout
,
1926 if (collection
->auto_scroll
== -1)
1927 return; /* Already off! */
1929 gtk_timeout_remove(collection
->auto_scroll
);
1930 collection
->auto_scroll
= -1;
1934 /* Start a lasso box drag */
1935 void collection_lasso_box(Collection
*collection
, int x
, int y
)
1937 collection
->drag_box_x
[0] = x
;
1938 collection
->drag_box_y
[0] = y
;
1939 collection
->drag_box_x
[1] = x
;
1940 collection
->drag_box_y
[1] = y
;
1942 collection_set_autoscroll(collection
, TRUE
);
1943 add_lasso_box(collection
);
1946 /* Remove the lasso box. Applies fn to each item inside the box.
1947 * fn may be GDK_INVERT, GDK_SET, GDK_NOOP or GDK_CLEAR.
1949 void collection_end_lasso(Collection
*collection
, GdkFunction fn
)
1951 if (fn
!= GDK_CLEAR
)
1953 GdkRectangle region
;
1955 find_lasso_area(collection
, ®ion
);
1957 collection_process_area(collection
, ®ion
, fn
,
1961 abort_lasso(collection
);
1964 /* Unblock the selection_changed signal, emitting the signal if the
1965 * block counter reaches zero and emit is TRUE.
1967 void collection_unblock_selection_changed(Collection
*collection
,
1971 g_return_if_fail(collection
!= NULL
);
1972 g_return_if_fail(IS_COLLECTION(collection
));
1973 g_return_if_fail(collection
->block_selection_changed
> 0);
1975 collection
->block_selection_changed
--;
1977 if (emit
&& !collection
->block_selection_changed
)
1978 gtk_signal_emit(GTK_OBJECT(collection
),
1979 collection_signals
[SELECTION_CHANGED
], time
);