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 g_signal_emit(collection, \
48 collection_signals[SELECTION_CHANGED], 0, 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 draw_one_item(Collection
*collection
,
91 static void collection_class_init(GObjectClass
*gclass
, gpointer data
);
92 static void collection_init(GTypeInstance
*object
, gpointer g_class
);
93 static void collection_destroy(GtkObject
*object
);
94 static void collection_finalize(GObject
*object
);
95 static void collection_realize(GtkWidget
*widget
);
96 static void collection_map(GtkWidget
*widget
);
97 static void collection_size_request(GtkWidget
*widget
,
98 GtkRequisition
*requisition
);
99 static void collection_size_allocate(GtkWidget
*widget
,
100 GtkAllocation
*allocation
);
101 static void collection_set_adjustment(Collection
*collection
,
102 GtkAdjustment
*vadj
);
103 static void collection_get_property(GObject
*object
,
107 static void collection_set_property(GObject
*object
,
111 static gint
collection_expose(GtkWidget
*widget
, GdkEventExpose
*event
);
112 static void default_draw_item(GtkWidget
*widget
,
113 CollectionItem
*data
,
116 static gboolean
default_test_point(Collection
*collection
,
117 int point_x
, int point_y
,
118 CollectionItem
*data
,
119 int width
, int height
,
121 static gint
collection_motion_notify(GtkWidget
*widget
,
122 GdkEventMotion
*event
);
123 static void add_lasso_box(Collection
*collection
);
124 static void abort_lasso(Collection
*collection
);
125 static void remove_lasso_box(Collection
*collection
);
126 static void draw_lasso_box(Collection
*collection
);
127 static void cancel_wink(Collection
*collection
);
128 static gint
collection_key_press(GtkWidget
*widget
, GdkEventKey
*event
);
129 static void get_visible_limits(Collection
*collection
, int *first
, int *last
);
130 static void scroll_to_show(Collection
*collection
, int item
);
131 static void collection_item_set_selected(Collection
*collection
,
135 static void collection_drag_leave(GtkWidget
*widget
, GdkDragContext
*context
,
137 static void collection_drag_end(GtkWidget
*widget
, GdkDragContext
*context
);
138 static gboolean
collection_drag_motion(GtkWidget
*widget
, GdkDragContext
139 *context
, gint x
, gint y
, guint time
);
140 static gint
collection_scroll_event(GtkWidget
*widget
, GdkEventScroll
*event
);
142 static void draw_focus_at(Collection
*collection
, GdkRectangle
*area
)
147 widget
= GTK_WIDGET(collection
);
149 if (GTK_WIDGET_FLAGS(widget
) & GTK_HAS_FOCUS
)
150 gc
= widget
->style
->fg_gc
[GTK_STATE_ACTIVE
];
152 gc
= widget
->style
->fg_gc
[GTK_STATE_INSENSITIVE
];
154 gdk_draw_rectangle(widget
->window
, gc
, FALSE
,
156 collection
->item_width
- 1,
160 static void draw_one_item(Collection
*collection
, int item
, GdkRectangle
*area
)
162 if (item
< collection
->number_of_items
)
164 collection
->draw_item((GtkWidget
*) collection
,
165 &collection
->items
[item
],
166 area
, collection
->cb_user_data
);
169 if (item
== collection
->cursor_item
)
170 draw_focus_at(collection
, area
);
173 GType
collection_get_type(void)
175 static GType my_type
= 0;
179 static const GTypeInfo info
=
181 sizeof(CollectionClass
),
182 NULL
, /* base_init */
183 NULL
, /* base_finalise */
184 (GClassInitFunc
) collection_class_init
,
185 NULL
, /* class_finalise */
186 NULL
, /* class_data */
192 my_type
= g_type_register_static(gtk_widget_get_type(),
193 "Collection", &info
, 0);
199 typedef void (*FinalizeFn
)(GObject
*object
);
201 static void collection_class_init(GObjectClass
*gclass
, gpointer data
)
203 CollectionClass
*collection_class
= (CollectionClass
*) gclass
;
204 GtkObjectClass
*object_class
= (GtkObjectClass
*) gclass
;
205 GtkWidgetClass
*widget_class
= (GtkWidgetClass
*) gclass
;
207 parent_class
= gtk_type_class(gtk_widget_get_type());
209 object_class
->destroy
= collection_destroy
;
210 G_OBJECT_CLASS(object_class
)->finalize
=
211 (FinalizeFn
) collection_finalize
;
213 widget_class
->realize
= collection_realize
;
214 widget_class
->expose_event
= collection_expose
;
215 widget_class
->size_request
= collection_size_request
;
216 widget_class
->size_allocate
= collection_size_allocate
;
218 widget_class
->key_press_event
= collection_key_press
;
220 widget_class
->motion_notify_event
= collection_motion_notify
;
221 widget_class
->map
= collection_map
;
222 widget_class
->scroll_event
= collection_scroll_event
;
223 widget_class
->drag_motion
= collection_drag_motion
;
224 widget_class
->drag_leave
= collection_drag_leave
;
225 widget_class
->drag_end
= collection_drag_end
;
227 gclass
->set_property
= collection_set_property
;
228 gclass
->get_property
= collection_get_property
;
230 collection_class
->gain_selection
= NULL
;
231 collection_class
->lose_selection
= NULL
;
232 collection_class
->selection_changed
= NULL
;
234 collection_signals
[GAIN_SELECTION
] = g_signal_new("gain_selection",
235 G_TYPE_FROM_CLASS(gclass
),
237 G_STRUCT_OFFSET(CollectionClass
,
240 g_cclosure_marshal_VOID__INT
,
244 collection_signals
[LOSE_SELECTION
] = g_signal_new("lose_selection",
245 G_TYPE_FROM_CLASS(gclass
),
247 G_STRUCT_OFFSET(CollectionClass
,
250 g_cclosure_marshal_VOID__INT
,
254 collection_signals
[SELECTION_CHANGED
] = g_signal_new(
256 G_TYPE_FROM_CLASS(gclass
),
258 G_STRUCT_OFFSET(CollectionClass
,
261 g_cclosure_marshal_VOID__INT
,
265 g_object_class_install_property(gclass
,
267 g_param_spec_object("vadjustment",
268 _("Vertical Adjustment"),
269 _("The GtkAdjustment for the vertical position."),
271 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT
));
274 static void collection_init(GTypeInstance
*instance
, gpointer g_class
)
276 Collection
*object
= (Collection
*) instance
;
278 g_return_if_fail(object
!= NULL
);
279 g_return_if_fail(IS_COLLECTION(object
));
281 GTK_WIDGET_SET_FLAGS(GTK_WIDGET(object
), GTK_CAN_FOCUS
);
283 object
->number_of_items
= 0;
284 object
->number_selected
= 0;
285 object
->block_selection_changed
= 0;
287 object
->item_width
= 64;
288 object
->item_height
= 64;
291 object
->items
= g_new(CollectionItem
, MINIMUM_ITEMS
);
292 object
->cursor_item
= -1;
293 object
->cursor_item_old
= -1;
294 object
->wink_item
= -1;
295 object
->wink_on_map
= -1;
296 object
->array_size
= MINIMUM_ITEMS
;
297 object
->draw_item
= default_draw_item
;
298 object
->test_point
= default_test_point
;
299 object
->free_item
= NULL
;
301 object
->auto_scroll
= -1;
304 GtkWidget
* collection_new(void)
306 return GTK_WIDGET(gtk_widget_new(collection_get_type(), NULL
));
309 /* After this we are unusable, but our data (if any) is still hanging around.
310 * It will be freed later with finalize.
312 static void collection_destroy(GtkObject
*object
)
314 Collection
*collection
;
316 g_return_if_fail(object
!= NULL
);
317 g_return_if_fail(IS_COLLECTION(object
));
319 collection
= COLLECTION(object
);
321 collection_clear(collection
);
323 if (collection
->auto_scroll
!= -1)
325 gtk_timeout_remove(collection
->auto_scroll
);
326 collection
->auto_scroll
= -1;
329 if (collection
->vadj
)
331 g_object_unref(G_OBJECT(collection
->vadj
));
332 collection
->vadj
= NULL
;
335 if (GTK_OBJECT_CLASS(parent_class
)->destroy
)
336 (*GTK_OBJECT_CLASS(parent_class
)->destroy
)(object
);
339 /* This is the last thing that happens to us. Free all data. */
340 static void collection_finalize(GObject
*object
)
342 Collection
*collection
;
344 collection
= COLLECTION(object
);
346 g_return_if_fail(collection
->number_of_items
== 0);
348 g_free(collection
->items
);
350 if (G_OBJECT_CLASS(parent_class
)->finalize
)
351 G_OBJECT_CLASS(parent_class
)->finalize(object
);
354 static void collection_drag_leave(GtkWidget
*widget
, GdkDragContext
*context
,
357 Collection
*collection
= COLLECTION(widget
);
359 /* Note that this isn't always called when the pointer leaves the
360 * widget; only if we highlighted an item at some point.
363 /* collection_set_autoscroll(collection, FALSE); - not needed? */
364 collection_set_cursor_item(collection
, -1);
366 if (GTK_WIDGET_CLASS(parent_class
)->drag_leave
)
367 (*GTK_WIDGET_CLASS(parent_class
)->drag_leave
)(widget
, context
,
371 /* Turn on auto scrolling */
372 static gboolean
collection_drag_motion(GtkWidget
*widget
, GdkDragContext
373 *context
, gint x
, gint y
, guint time
)
375 Collection
*collection
= COLLECTION(widget
);
377 collection_set_autoscroll(collection
, TRUE
);
379 if (GTK_WIDGET_CLASS(parent_class
)->drag_motion
)
380 return (*GTK_WIDGET_CLASS(parent_class
)->drag_motion
)(widget
,
381 context
, x
, y
, time
);
385 /* Turn off auto scrolling.
386 * (do we need this AND drag_leave?)
388 static void collection_drag_end(GtkWidget
*widget
, GdkDragContext
*context
)
390 Collection
*collection
= COLLECTION(widget
);
392 collection_set_autoscroll(collection
, FALSE
);
394 if (GTK_WIDGET_CLASS(parent_class
)->drag_end
)
395 (*GTK_WIDGET_CLASS(parent_class
)->drag_end
)(widget
, context
);
398 static void collection_map(GtkWidget
*widget
)
400 Collection
*collection
= COLLECTION(widget
);
402 if (GTK_WIDGET_CLASS(parent_class
)->map
)
403 (*GTK_WIDGET_CLASS(parent_class
)->map
)(widget
);
405 if (collection
->wink_on_map
>= 0)
407 collection_wink_item(collection
, collection
->wink_on_map
);
408 collection
->wink_on_map
= -1;
412 static void collection_realize(GtkWidget
*widget
)
414 Collection
*collection
;
415 GdkWindowAttr attributes
;
416 gint attributes_mask
;
417 GdkGCValues xor_values
;
420 g_return_if_fail(widget
!= NULL
);
421 g_return_if_fail(IS_COLLECTION(widget
));
422 g_return_if_fail(widget
->parent
!= NULL
);
424 GTK_WIDGET_SET_FLAGS(widget
, GTK_REALIZED
);
425 collection
= COLLECTION(widget
);
427 attributes
.x
= widget
->allocation
.x
;
428 attributes
.y
= widget
->allocation
.y
;
429 attributes
.width
= widget
->allocation
.width
;
430 attributes
.height
= widget
->allocation
.height
;
431 attributes
.wclass
= GDK_INPUT_OUTPUT
;
432 attributes
.window_type
= GDK_WINDOW_CHILD
;
433 attributes
.event_mask
= gtk_widget_get_events(widget
) |
435 GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK
|
436 GDK_BUTTON1_MOTION_MASK
| GDK_BUTTON2_MOTION_MASK
|
437 GDK_BUTTON3_MOTION_MASK
;
438 attributes
.visual
= gtk_widget_get_visual(widget
);
439 attributes
.colormap
= gtk_widget_get_colormap(widget
);
441 attributes_mask
= GDK_WA_X
| GDK_WA_Y
|
442 GDK_WA_VISUAL
| GDK_WA_COLORMAP
;
443 widget
->window
= gdk_window_new(gtk_widget_get_parent_window(widget
),
444 &attributes
, attributes_mask
);
446 widget
->style
= gtk_style_attach(widget
->style
, widget
->window
);
448 gdk_window_set_user_data(widget
->window
, widget
);
449 gdk_window_set_background(widget
->window
,
450 &widget
->style
->bg
[GTK_STATE_NORMAL
]);
452 bg
= &widget
->style
->bg
[GTK_STATE_NORMAL
];
453 fg
= &widget
->style
->fg
[GTK_STATE_NORMAL
];
454 xor_values
.function
= GDK_XOR
;
455 xor_values
.foreground
.pixel
= fg
->pixel
^ bg
->pixel
;
456 collection
->xor_gc
= gdk_gc_new_with_values(widget
->window
,
462 static void collection_size_request(GtkWidget
*widget
,
463 GtkRequisition
*requisition
)
465 Collection
*collection
= COLLECTION(widget
);
466 int rows
, cols
= collection
->columns
;
468 /* We ask for the total size we need; our containing viewport
469 * will deal with scrolling.
471 requisition
->width
= MIN_WIDTH
;
472 rows
= (collection
->number_of_items
+ cols
- 1) / cols
;
473 requisition
->height
= rows
* collection
->item_height
;
476 static gboolean
scroll_after_alloc(Collection
*collection
)
478 if (collection
->wink_item
!= -1)
479 scroll_to_show(collection
, collection
->wink_item
);
480 else if (collection
->cursor_item
!= -1)
481 scroll_to_show(collection
, collection
->cursor_item
);
482 g_object_unref(G_OBJECT(collection
));
487 static void collection_size_allocate(GtkWidget
*widget
,
488 GtkAllocation
*allocation
)
490 Collection
*collection
;
492 gboolean cursor_visible
= FALSE
;
494 g_return_if_fail(widget
!= NULL
);
495 g_return_if_fail(IS_COLLECTION(widget
));
496 g_return_if_fail(allocation
!= NULL
);
498 collection
= COLLECTION(widget
);
500 if (collection
->cursor_item
!= -1)
503 int crow
= collection
->cursor_item
/ collection
->columns
;
505 get_visible_limits(collection
, &first
, &last
);
507 cursor_visible
= crow
>= first
&& crow
<= last
;
510 old_columns
= collection
->columns
;
512 widget
->allocation
= *allocation
;
514 collection
->columns
= allocation
->width
/ collection
->item_width
;
515 if (collection
->columns
< 1)
516 collection
->columns
= 1;
518 if (GTK_WIDGET_REALIZED(widget
))
520 gdk_window_move_resize(widget
->window
,
521 allocation
->x
, allocation
->y
,
522 allocation
->width
, allocation
->height
);
525 scroll_to_show(collection
, collection
->cursor_item
);
528 if (old_columns
!= collection
->columns
)
530 /* Need to go around again... */
531 gtk_widget_queue_resize(widget
);
533 else if (collection
->wink_item
!= -1 || collection
->cursor_item
!= -1)
535 /* Viewport resets the adjustments after the alloc */
536 g_object_ref(G_OBJECT(collection
));
537 g_idle_add((GSourceFunc
) scroll_after_alloc
, collection
);
541 /* Return the area occupied by the item at (row, col) by filling
544 static void collection_get_item_area(Collection
*collection
,
549 area
->x
= col
* collection
->item_width
;
550 area
->y
= row
* collection
->item_height
;
552 area
->width
= collection
->item_width
;
553 area
->height
= collection
->item_height
;
554 if (col
== collection
->columns
- 1)
558 static gint
collection_expose(GtkWidget
*widget
, GdkEventExpose
*event
)
560 Collection
*collection
;
561 GdkRectangle item_area
;
564 int start_row
, last_row
;
565 int start_col
, last_col
;
568 g_return_val_if_fail(widget
!= NULL
, FALSE
);
569 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
570 g_return_val_if_fail(event
!= NULL
, FALSE
);
572 gtk_paint_flat_box(widget
->style
, widget
->window
, GTK_STATE_NORMAL
,
573 GTK_SHADOW_NONE
, &event
->area
,
574 widget
, "base", 0, 0, -1, -1);
576 collection
= COLLECTION(widget
);
578 /* Calculate the ranges to plot */
579 start_row
= event
->area
.y
/ collection
->item_height
;
580 last_row
= (event
->area
.y
+ event
->area
.height
- 1)
581 / collection
->item_height
;
584 start_col
= event
->area
.x
/ collection
->item_width
;
585 phys_last_col
= (event
->area
.x
+ event
->area
.width
- 1)
586 / collection
->item_width
;
588 /* The right-most column may be wider than the others.
589 * Therefore, to redraw the area after the last 'real' column
590 * we may have to draw the right-most column.
592 if (start_col
>= collection
->columns
)
593 start_col
= collection
->columns
- 1;
595 if (phys_last_col
>= collection
->columns
)
596 last_col
= collection
->columns
- 1;
598 last_col
= phys_last_col
;
602 item
= row
* collection
->columns
+ col
;
604 while ((item
== 0 || item
< collection
->number_of_items
)
607 collection_get_item_area(collection
, row
, col
, &item_area
);
609 draw_one_item(collection
, item
, &item_area
);
616 item
= row
* collection
->columns
+ col
;
622 if (collection
->lasso_box
)
623 draw_lasso_box(collection
);
628 static void default_draw_item(GtkWidget
*widget
,
629 CollectionItem
*item
,
633 gdk_draw_arc(widget
->window
,
634 item
->selected
? widget
->style
->white_gc
635 : widget
->style
->black_gc
,
638 COLLECTION(widget
)->item_width
, area
->height
,
643 static gboolean
default_test_point(Collection
*collection
,
644 int point_x
, int point_y
,
645 CollectionItem
*item
,
646 int width
, int height
,
651 /* Convert to point in unit circle */
652 f_x
= ((float) point_x
/ width
) - 0.5;
653 f_y
= ((float) point_y
/ height
) - 0.5;
655 return (f_x
* f_x
) + (f_y
* f_y
) <= .25;
658 static void collection_set_property(GObject
*object
,
663 Collection
*collection
;
665 collection
= COLLECTION(object
);
669 case PROP_VADJUSTMENT
:
670 collection_set_adjustment(collection
,
671 g_value_get_object(value
));
674 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
,
680 static void collection_set_adjustment(Collection
*collection
,
684 g_return_if_fail(GTK_IS_ADJUSTMENT(vadj
));
686 vadj
= GTK_ADJUSTMENT(gtk_adjustment_new(0.0,
690 if (collection
->vadj
== vadj
)
693 if (collection
->vadj
)
694 g_object_unref(G_OBJECT(collection
->vadj
));
696 collection
->vadj
= vadj
;
697 g_object_ref(G_OBJECT(collection
->vadj
));
698 gtk_object_sink(GTK_OBJECT(collection
->vadj
));
701 static void collection_get_property(GObject
*object
,
706 Collection
*collection
;
708 collection
= COLLECTION(object
);
712 case PROP_VADJUSTMENT
:
713 g_value_set_object(value
, G_OBJECT(collection
->vadj
));
716 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
,
722 /* Change the adjustment by this amount. Bounded. */
723 static void diff_vpos(Collection
*collection
, int diff
)
725 int value
= collection
->vadj
->value
+ diff
;
727 value
= CLAMP(value
, 0,
728 collection
->vadj
->upper
- collection
->vadj
->page_size
);
729 gtk_adjustment_set_value(collection
->vadj
, value
);
732 static void resize_arrays(Collection
*collection
, guint new_size
)
734 g_return_if_fail(collection
!= NULL
);
735 g_return_if_fail(IS_COLLECTION(collection
));
736 g_return_if_fail(new_size
>= collection
->number_of_items
);
738 collection
->items
= g_realloc(collection
->items
,
739 sizeof(CollectionItem
) * new_size
);
740 collection
->array_size
= new_size
;
743 static gint
collection_key_press(GtkWidget
*widget
, GdkEventKey
*event
)
745 Collection
*collection
;
749 g_return_val_if_fail(widget
!= NULL
, FALSE
);
750 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
751 g_return_val_if_fail(event
!= NULL
, FALSE
);
753 collection
= (Collection
*) widget
;
754 item
= collection
->cursor_item
;
757 if (event
->state
& (GDK_CONTROL_MASK
| GDK_SHIFT_MASK
))
759 if (key
== GDK_Left
|| key
== GDK_Right
|| \
760 key
== GDK_Up
|| key
== GDK_Down
)
768 collection_move_cursor(collection
, 0, -1);
771 collection_move_cursor(collection
, 0, 1);
774 collection_move_cursor(collection
, -1, 0);
777 collection_move_cursor(collection
, 1, 0);
780 collection_set_cursor_item(collection
, 0);
783 collection_set_cursor_item(collection
,
784 MAX((gint
) collection
->number_of_items
- 1, 0));
789 get_visible_limits(collection
, &first
, &last
);
790 collection_move_cursor(collection
, first
- last
- 1, 0);
796 get_visible_limits(collection
, &first
, &last
);
797 collection_move_cursor(collection
, last
- first
+ 1, 0);
801 collection_set_cursor_item(collection
, -1);
802 collection_clear_selection(collection
);
803 return FALSE
; /* Pass it on */
805 if (item
>=0 && item
< collection
->number_of_items
)
807 collection_toggle_item(collection
, item
);
808 if (item
< collection
->number_of_items
- 1)
809 collection_set_cursor_item(collection
,
820 /* Wheel mouse scrolling */
821 static gint
collection_scroll_event(GtkWidget
*widget
, GdkEventScroll
*event
)
823 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
);
832 if (event
->direction
== GDK_SCROLL_UP
)
834 else if (event
->direction
== GDK_SCROLL_DOWN
)
841 int old_value
= collection
->vadj
->value
;
843 gboolean box
= collection
->lasso_box
;
844 int step
= collection
->vadj
->page_increment
/ 2;
846 new_value
= CLAMP(old_value
+ diff
* step
, 0.0,
847 collection
->vadj
->upper
848 - collection
->vadj
->page_size
);
849 diff
= new_value
- old_value
;
854 remove_lasso_box(collection
);
855 collection
->drag_box_y
[0] -= diff
;
857 gtk_adjustment_set_value(collection
->vadj
, new_value
);
859 add_lasso_box(collection
);
866 /* 'from' and 'to' are pixel positions. 'step' is the size of each item.
867 * Returns the index of the first item covered, and the number of items.
869 static void get_range(int from
, int to
, int step
, gint
*pos
, gint
*len
)
871 int margin
= MIN(step
/ 4, 40);
880 from
= (from
+ margin
) / step
; /* First item */
881 to
= (to
+ step
- margin
) / step
; /* Last item (inclusive) */
887 /* Fills in the area with a rectangle corresponding to the current
888 * size of the lasso box (units of items, not pixels).
890 * The box will only span valid columns, but the total number
891 * of items is not taken into account (rows or cols).
893 static void find_lasso_area(Collection
*collection
, GdkRectangle
*area
)
895 int cols
= collection
->columns
;
896 int dx
= collection
->drag_box_x
[0] - collection
->drag_box_x
[1];
897 int dy
= collection
->drag_box_y
[0] - collection
->drag_box_y
[1];
899 if (ABS(dx
) < 8 && ABS(dy
) < 8)
901 /* Didn't move far enough - ignore */
902 area
->x
= area
->y
= 0;
908 get_range(collection
->drag_box_x
[0], collection
->drag_box_x
[1],
909 collection
->item_width
, &area
->x
, &area
->width
);
913 else if (area
->x
+ area
->width
> cols
)
914 area
->width
= cols
- area
->x
;
916 get_range(collection
->drag_box_y
[0], collection
->drag_box_y
[1],
917 collection
->item_height
, &area
->y
, &area
->height
);
920 static void collection_process_area(Collection
*collection
,
926 guint32 stacked_time
;
928 gboolean changed
= FALSE
;
931 g_return_if_fail(fn
== GDK_SET
|| fn
== GDK_INVERT
);
933 old_selected
= collection
->number_selected
;
935 stacked_time
= current_event_time
;
936 current_event_time
= time
;
938 collection
->block_selection_changed
++;
940 for (y
= area
->y
; y
< area
->y
+ area
->height
; y
++)
942 item
= y
* collection
->columns
+ area
->x
;
944 for (x
= area
->x
; x
< area
->x
+ area
->width
; x
++)
946 if (item
>= collection
->number_of_items
)
949 if (fn
== GDK_INVERT
)
950 collection_item_set_selected(collection
, item
,
951 !collection
->items
[item
].selected
,
954 collection_item_set_selected(collection
, item
,
963 if (collection
->number_selected
&& !old_selected
)
964 g_signal_emit(collection
,
965 collection_signals
[GAIN_SELECTION
], 0,
967 else if (!collection
->number_selected
&& old_selected
)
968 g_signal_emit(collection
,
969 collection_signals
[LOSE_SELECTION
], 0,
972 collection_unblock_selection_changed(collection
,
973 current_event_time
, changed
);
974 current_event_time
= stacked_time
;
977 static gint
collection_motion_notify(GtkWidget
*widget
,
978 GdkEventMotion
*event
)
980 Collection
*collection
;
983 g_return_val_if_fail(widget
!= NULL
, FALSE
);
984 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
985 g_return_val_if_fail(event
!= NULL
, FALSE
);
987 collection
= COLLECTION(widget
);
989 if (!collection
->lasso_box
)
992 if (event
->window
!= widget
->window
)
993 gdk_window_get_pointer(widget
->window
, &x
, &y
, NULL
);
1000 remove_lasso_box(collection
);
1001 collection
->drag_box_x
[1] = x
;
1002 collection
->drag_box_y
[1] = y
;
1003 add_lasso_box(collection
);
1007 static void add_lasso_box(Collection
*collection
)
1009 g_return_if_fail(collection
!= NULL
);
1010 g_return_if_fail(IS_COLLECTION(collection
));
1011 g_return_if_fail(collection
->lasso_box
== FALSE
);
1013 collection
->lasso_box
= TRUE
;
1014 draw_lasso_box(collection
);
1017 static void draw_lasso_box(Collection
*collection
)
1020 int x
, y
, width
, height
;
1022 widget
= GTK_WIDGET(collection
);
1024 x
= MIN(collection
->drag_box_x
[0], collection
->drag_box_x
[1]);
1025 y
= MIN(collection
->drag_box_y
[0], collection
->drag_box_y
[1]);
1026 width
= abs(collection
->drag_box_x
[1] - collection
->drag_box_x
[0]);
1027 height
= abs(collection
->drag_box_y
[1] - collection
->drag_box_y
[0]);
1029 /* XXX: A redraw bug sometimes leaves a one-pixel dot on the screen.
1030 * As a quick hack, don't draw boxes that small for now...
1032 if (width
|| height
)
1033 gdk_draw_rectangle(widget
->window
, collection
->xor_gc
, FALSE
,
1034 x
, y
, width
, height
);
1037 static void abort_lasso(Collection
*collection
)
1039 if (collection
->lasso_box
)
1041 remove_lasso_box(collection
);
1042 collection_set_autoscroll(collection
, FALSE
);
1046 static void remove_lasso_box(Collection
*collection
)
1048 g_return_if_fail(collection
!= NULL
);
1049 g_return_if_fail(IS_COLLECTION(collection
));
1050 g_return_if_fail(collection
->lasso_box
== TRUE
);
1052 draw_lasso_box(collection
);
1054 collection
->lasso_box
= FALSE
;
1059 /* Make sure that 'item' is fully visible (vertically), scrolling if not. */
1060 static void scroll_to_show(Collection
*collection
, int item
)
1062 int first
, last
, row
;
1064 g_return_if_fail(collection
!= NULL
);
1065 g_return_if_fail(IS_COLLECTION(collection
));
1067 row
= item
/ collection
->columns
;
1068 get_visible_limits(collection
, &first
, &last
);
1072 gtk_adjustment_set_value(collection
->vadj
,
1073 row
* collection
->item_height
);
1075 else if (row
>= last
)
1077 GtkWidget
*widget
= (GtkWidget
*) collection
;
1080 if (GTK_WIDGET_REALIZED(widget
))
1082 height
= collection
->vadj
->page_size
;
1083 gtk_adjustment_set_value(collection
->vadj
,
1084 (row
+ 1) * collection
->item_height
- height
);
1089 /* Return the first and last rows which are [partly] visible. Does not
1090 * ensure that the rows actually exist (contain items).
1092 static void get_visible_limits(Collection
*collection
, int *first
, int *last
)
1094 GtkWidget
*widget
= (GtkWidget
*) collection
;
1095 gint scroll
= 0, height
;
1097 g_return_if_fail(collection
!= NULL
);
1098 g_return_if_fail(IS_COLLECTION(collection
));
1099 g_return_if_fail(first
!= NULL
&& last
!= NULL
);
1101 if (!GTK_WIDGET_REALIZED(widget
))
1108 scroll
= collection
->vadj
->value
;
1109 height
= collection
->vadj
->page_size
;
1111 *first
= MAX(scroll
/ collection
->item_height
, 0);
1112 *last
= (scroll
+ height
- 1) /collection
->item_height
;
1119 /* Cancel the current wink effect. */
1120 static void cancel_wink(Collection
*collection
)
1124 g_return_if_fail(collection
!= NULL
);
1125 g_return_if_fail(IS_COLLECTION(collection
));
1126 g_return_if_fail(collection
->wink_item
!= -1);
1128 item
= collection
->wink_item
;
1130 collection
->wink_item
= -1;
1131 gtk_timeout_remove(collection
->wink_timeout
);
1133 collection_draw_item(collection
, item
, TRUE
);
1136 /* Draw/undraw a box around collection->wink_item */
1137 static void invert_wink(Collection
*collection
)
1142 g_return_if_fail(collection
->wink_item
>= 0);
1144 if (!GTK_WIDGET_REALIZED(GTK_WIDGET(collection
)))
1147 col
= collection
->wink_item
% collection
->columns
;
1148 row
= collection
->wink_item
/ collection
->columns
;
1149 collection_get_item_area(collection
, row
, col
, &area
);
1151 gdk_draw_rectangle(((GtkWidget
*) collection
)->window
,
1152 collection
->xor_gc
, FALSE
,
1154 collection
->item_width
- 1,
1158 static gboolean
wink_timeout(Collection
*collection
)
1162 g_return_val_if_fail(collection
!= NULL
, FALSE
);
1163 g_return_val_if_fail(IS_COLLECTION(collection
), FALSE
);
1164 g_return_val_if_fail(collection
->wink_item
!= -1, FALSE
);
1166 item
= collection
->wink_item
;
1168 if (collection
->winks_left
-- > 0)
1170 invert_wink(collection
);
1174 collection
->wink_item
= -1;
1176 collection_draw_item(collection
, item
, TRUE
);
1181 /* This is called frequently while auto_scroll is on.
1182 * Checks the pointer position and scrolls the window if it's
1183 * near the top or bottom.
1185 static gboolean
as_timeout(Collection
*collection
)
1187 GdkWindow
*window
= GTK_WIDGET(collection
)->window
;
1189 GdkModifierType mask
;
1192 gdk_window_get_pointer(window
, &x
, &y
, &mask
);
1193 gdk_drawable_get_size(window
, &w
, NULL
);
1195 h
= collection
->vadj
->page_size
;
1196 y
-= collection
->vadj
->value
;
1198 if ((x
< 0 || x
> w
|| y
< 0 || y
> h
) && !collection
->lasso_box
)
1200 collection
->auto_scroll
= -1;
1201 return FALSE
; /* Out of window - stop */
1204 if (y
< AUTOSCROLL_STEP
)
1205 diff
= y
- AUTOSCROLL_STEP
;
1206 else if (y
> h
- AUTOSCROLL_STEP
)
1207 diff
= AUTOSCROLL_STEP
+ y
- h
;
1210 diff_vpos(collection
, diff
);
1215 /* Change the selected state of an item.
1216 * Send GAIN/LOSE signals if 'signal' is TRUE.
1217 * Send SELECTION_CHANGED unless blocked.
1218 * Updates number_selected and redraws the item.
1220 static void collection_item_set_selected(Collection
*collection
,
1225 g_return_if_fail(collection
!= NULL
);
1226 g_return_if_fail(IS_COLLECTION(collection
));
1227 g_return_if_fail(item
>= 0 && item
< collection
->number_of_items
);
1229 if (collection
->items
[item
].selected
== selected
)
1232 collection
->items
[item
].selected
= selected
;
1233 collection_draw_item(collection
, item
, TRUE
);
1237 collection
->number_selected
++;
1238 if (signal
&& collection
->number_selected
== 1)
1239 g_signal_emit(collection
,
1240 collection_signals
[GAIN_SELECTION
], 0,
1241 current_event_time
);
1245 collection
->number_selected
--;
1246 if (signal
&& collection
->number_selected
== 0)
1247 g_signal_emit(collection
,
1248 collection_signals
[LOSE_SELECTION
], 0,
1249 current_event_time
);
1252 EMIT_SELECTION_CHANGED(collection
, current_event_time
);
1255 /* Functions for managing collections */
1257 /* Remove all objects from the collection */
1258 void collection_clear(Collection
*collection
)
1260 collection_delete_if(collection
, NULL
, NULL
);
1263 /* Inserts a new item at the end. The new item is unselected, and its
1264 * number is returned.
1266 gint
collection_insert(Collection
*collection
, gpointer data
, gpointer view
)
1270 /* g_return_val_if_fail(IS_COLLECTION(collection), -1); (slow) */
1272 item
= collection
->number_of_items
;
1274 if (item
>= collection
->array_size
)
1275 resize_arrays(collection
, item
+ (item
>> 1));
1277 collection
->items
[item
].data
= data
;
1278 collection
->items
[item
].view_data
= view
;
1279 collection
->items
[item
].selected
= FALSE
;
1281 collection
->number_of_items
++;
1283 gtk_widget_queue_resize(GTK_WIDGET(collection
));
1285 collection_draw_item(collection
, item
, FALSE
);
1290 void collection_unselect_item(Collection
*collection
, gint item
)
1292 collection_item_set_selected(collection
, item
, FALSE
, TRUE
);
1295 void collection_select_item(Collection
*collection
, gint item
)
1297 collection_item_set_selected(collection
, item
, TRUE
, TRUE
);
1300 void collection_toggle_item(Collection
*collection
, gint item
)
1302 collection_item_set_selected(collection
, item
,
1303 !collection
->items
[item
].selected
, TRUE
);
1306 /* Select all items in the collection */
1307 void collection_select_all(Collection
*collection
)
1312 g_return_if_fail(collection
!= NULL
);
1313 g_return_if_fail(IS_COLLECTION(collection
));
1315 widget
= GTK_WIDGET(collection
);
1317 if (collection
->number_selected
== collection
->number_of_items
)
1318 return; /* Nothing to do */
1320 while (collection
->number_selected
< collection
->number_of_items
)
1322 while (collection
->items
[item
].selected
)
1325 collection
->items
[item
].selected
= TRUE
;
1326 collection_draw_item(collection
, item
, TRUE
);
1329 collection
->number_selected
++;
1332 g_signal_emit(collection
, collection_signals
[GAIN_SELECTION
], 0,
1333 current_event_time
);
1334 EMIT_SELECTION_CHANGED(collection
, current_event_time
);
1337 /* Toggle all items in the collection */
1338 void collection_invert_selection(Collection
*collection
)
1342 g_return_if_fail(collection
!= NULL
);
1343 g_return_if_fail(IS_COLLECTION(collection
));
1345 if (collection
->number_selected
== 0)
1347 collection_select_all(collection
);
1350 else if (collection
->number_of_items
== collection
->number_selected
)
1352 collection_clear_selection(collection
);
1356 for (item
= 0; item
< collection
->number_of_items
; item
++)
1357 collection
->items
[item
].selected
=
1358 !collection
->items
[item
].selected
;
1360 collection
->number_selected
= collection
->number_of_items
-
1361 collection
->number_selected
;
1363 /* Have to redraw everything... */
1364 gtk_widget_queue_draw(GTK_WIDGET(collection
));
1366 EMIT_SELECTION_CHANGED(collection
, current_event_time
);
1369 /* Unselect all items except number item, which is selected (-1 to unselect
1372 void collection_clear_except(Collection
*collection
, gint item
)
1376 int end
; /* Selected items to end up with */
1378 g_return_if_fail(collection
!= NULL
);
1379 g_return_if_fail(IS_COLLECTION(collection
));
1380 g_return_if_fail(item
>= -1 && item
< collection
->number_of_items
);
1382 widget
= GTK_WIDGET(collection
);
1388 collection_select_item(collection
, item
);
1392 if (collection
->number_selected
== 0)
1395 while (collection
->number_selected
> end
)
1397 while (i
== item
|| !collection
->items
[i
].selected
)
1400 collection
->items
[i
].selected
= FALSE
;
1401 collection_draw_item(collection
, i
, TRUE
);
1404 collection
->number_selected
--;
1408 g_signal_emit(collection
, collection_signals
[LOSE_SELECTION
], 0,
1409 current_event_time
);
1410 EMIT_SELECTION_CHANGED(collection
, current_event_time
);
1413 /* Unselect all items in the collection */
1414 void collection_clear_selection(Collection
*collection
)
1416 g_return_if_fail(collection
!= NULL
);
1417 g_return_if_fail(IS_COLLECTION(collection
));
1419 collection_clear_except(collection
, -1);
1422 /* Force a redraw of the specified item, if it is visible */
1423 void collection_draw_item(Collection
*collection
, gint item
, gboolean blank
)
1429 g_return_if_fail(collection
!= NULL
);
1430 /* g_return_if_fail(IS_COLLECTION(collection)); (slow) */
1431 g_return_if_fail(item
>= 0 &&
1432 (item
== 0 || item
< collection
->number_of_items
));
1434 widget
= GTK_WIDGET(collection
);
1435 if (!GTK_WIDGET_REALIZED(widget
))
1438 col
= item
% collection
->columns
;
1439 row
= item
/ collection
->columns
;
1441 collection_get_item_area(collection
, row
, col
, &area
);
1443 gdk_window_invalidate_rect(widget
->window
, &area
, FALSE
);
1446 void collection_set_item_size(Collection
*collection
, int width
, int height
)
1450 g_return_if_fail(collection
!= NULL
);
1451 g_return_if_fail(IS_COLLECTION(collection
));
1452 g_return_if_fail(width
> 4 && height
> 4);
1454 if (collection
->item_width
== width
&&
1455 collection
->item_height
== height
)
1458 widget
= GTK_WIDGET(collection
);
1460 collection
->item_width
= width
;
1461 collection
->item_height
= height
;
1463 if (GTK_WIDGET_REALIZED(widget
))
1467 gdk_drawable_get_size(widget
->window
, &window_width
, NULL
);
1468 collection
->columns
= MAX(window_width
/ collection
->item_width
,
1470 if (collection
->cursor_item
!= -1)
1471 scroll_to_show(collection
, collection
->cursor_item
);
1472 gtk_widget_queue_draw(widget
);
1475 gtk_widget_queue_resize(GTK_WIDGET(collection
));
1478 static int (*cmp_callback
)(const void *a
, const void *b
) = NULL
;
1479 static int collection_cmp(const void *a
, const void *b
)
1481 return cmp_callback(((CollectionItem
*) a
)->data
,
1482 ((CollectionItem
*) b
)->data
);
1485 /* Cursor is positioned on item with the same data as before the sort.
1486 * Same for the wink item.
1488 void collection_qsort(Collection
*collection
,
1489 int (*compar
)(const void *, const void *))
1491 int cursor
, wink
, items
, wink_on_map
;
1492 gpointer cursor_data
= NULL
;
1493 gpointer wink_data
= NULL
;
1494 gpointer wink_on_map_data
= NULL
;
1495 CollectionItem
*array
;
1498 g_return_if_fail(collection
!= NULL
);
1499 g_return_if_fail(IS_COLLECTION(collection
));
1500 g_return_if_fail(compar
!= NULL
);
1501 g_return_if_fail(cmp_callback
== NULL
);
1503 /* Check to see if it needs sorting (saves redrawing) */
1504 if (collection
->number_of_items
< 2)
1507 array
= collection
->items
;
1508 for (i
= 1; i
< collection
->number_of_items
; i
++)
1510 if (compar(array
[i
- 1].data
, array
[i
].data
) > 0)
1513 if (i
== collection
->number_of_items
)
1514 return; /* Already sorted */
1516 items
= collection
->number_of_items
;
1518 wink_on_map
= collection
->wink_on_map
;
1519 if (wink_on_map
>= 0 && wink_on_map
< items
)
1521 wink_on_map_data
= collection
->items
[wink_on_map
].data
;
1522 collection
->wink_on_map
= -1;
1527 wink
= collection
->wink_item
;
1528 if (wink
>= 0 && wink
< items
)
1530 wink_data
= collection
->items
[wink
].data
;
1531 collection
->wink_item
= -1;
1536 cursor
= collection
->cursor_item
;
1537 if (cursor
>= 0 && cursor
< items
)
1538 cursor_data
= collection
->items
[cursor
].data
;
1542 cmp_callback
= compar
;
1543 qsort(collection
->items
, items
, sizeof(collection
->items
[0]),
1545 cmp_callback
= NULL
;
1547 if (cursor
> -1 || wink
> -1 || wink_on_map
> -1)
1551 for (item
= 0; item
< items
; item
++)
1553 if (collection
->items
[item
].data
== cursor_data
)
1554 collection_set_cursor_item(collection
, item
);
1555 if (collection
->items
[item
].data
== wink_on_map_data
)
1556 collection
->wink_on_map
= item
;
1557 if (collection
->items
[item
].data
== wink_data
)
1559 collection
->cursor_item_old
= item
;
1560 collection
->wink_item
= item
;
1561 scroll_to_show(collection
, item
);
1566 gtk_widget_queue_draw(GTK_WIDGET(collection
));
1569 /* Find an item in a sorted collection.
1570 * Returns the item number, or -1 if not found.
1572 int collection_find_item(Collection
*collection
, gpointer data
,
1573 int (*compar
)(const void *, const void *))
1577 g_return_val_if_fail(collection
!= NULL
, -1);
1578 g_return_val_if_fail(IS_COLLECTION(collection
), -1);
1579 g_return_val_if_fail(compar
!= NULL
, -1);
1581 /* If item is here, then: lower <= i < upper */
1583 upper
= collection
->number_of_items
;
1585 while (lower
< upper
)
1589 i
= (lower
+ upper
) >> 1;
1591 cmp
= compar(collection
->items
[i
].data
, data
);
1604 /* Return the number of the item under the point (x,y), or -1 for none.
1605 * This may call your test_point callback. The point is relative to the
1606 * collection's origin.
1608 int collection_get_item(Collection
*collection
, int x
, int y
)
1614 g_return_val_if_fail(collection
!= NULL
, -1);
1616 col
= x
/ collection
->item_width
;
1617 row
= y
/ collection
->item_height
;
1619 if (col
>= collection
->columns
)
1620 col
= collection
->columns
- 1;
1622 if (col
< 0 || row
< 0)
1625 if (col
== collection
->columns
- 1)
1626 width
= collection
->item_width
<< 1;
1628 width
= collection
->item_width
;
1630 item
= col
+ row
* collection
->columns
;
1631 if (item
>= collection
->number_of_items
)
1634 x
-= col
* collection
->item_width
;
1635 y
-= row
* collection
->item_height
;
1637 if (collection
->test_point(collection
, x
, y
,
1638 &collection
->items
[item
], width
, collection
->item_height
,
1639 collection
->cb_user_data
))
1645 int collection_selected_item_number(Collection
*collection
)
1649 g_return_val_if_fail(collection
!= NULL
, -1);
1650 g_return_val_if_fail(IS_COLLECTION(collection
), -1);
1651 g_return_val_if_fail(collection
->number_selected
== 1, -1);
1653 for (i
= 0; i
< collection
->number_of_items
; i
++)
1654 if (collection
->items
[i
].selected
)
1657 g_warning("collection_selected_item_number: number_selected is wrong");
1662 /* Set the cursor/highlight over the given item. Passing -1
1663 * hides the cursor. As a special case, you may set the cursor item
1664 * to zero when there are no items.
1666 void collection_set_cursor_item(Collection
*collection
, gint item
)
1670 g_return_if_fail(collection
!= NULL
);
1671 g_return_if_fail(IS_COLLECTION(collection
));
1672 g_return_if_fail(item
>= -1 &&
1673 (item
< collection
->number_of_items
|| item
== 0));
1675 old_item
= collection
->cursor_item
;
1677 if (old_item
== item
)
1680 collection
->cursor_item
= item
;
1683 collection_draw_item(collection
, old_item
, TRUE
);
1687 collection_draw_item(collection
, item
, TRUE
);
1688 if (collection
->auto_scroll
== -1)
1689 scroll_to_show(collection
, item
);
1691 else if (old_item
!= -1)
1692 collection
->cursor_item_old
= old_item
;
1695 /* Briefly highlight an item to draw the user's attention to it.
1696 * -1 cancels the effect, as does deleting items, sorting the collection
1697 * or starting a new wink effect.
1698 * Otherwise, the effect will cancel itself after a short pause.
1700 void collection_wink_item(Collection
*collection
, gint item
)
1702 g_return_if_fail(collection
!= NULL
);
1703 g_return_if_fail(IS_COLLECTION(collection
));
1704 g_return_if_fail(item
>= -1 && item
< collection
->number_of_items
);
1706 if (collection
->wink_item
!= -1)
1707 cancel_wink(collection
);
1711 if (!GTK_WIDGET_MAPPED(GTK_WIDGET(collection
)))
1713 collection
->wink_on_map
= item
;
1717 collection
->cursor_item_old
= collection
->wink_item
= item
;
1718 collection
->winks_left
= MAX_WINKS
;
1720 collection
->wink_timeout
= gtk_timeout_add(70,
1721 (GtkFunction
) wink_timeout
,
1723 scroll_to_show(collection
, item
);
1724 invert_wink(collection
);
1729 /* Call test(item, data) on each item in the collection.
1730 * Remove all items for which it returns TRUE. test() should
1731 * free the data before returning TRUE. The collection is in an
1732 * inconsistant state during this call (ie, when test() is called).
1734 * If test is NULL, remove all items.
1736 void collection_delete_if(Collection
*collection
,
1737 gboolean (*test
)(gpointer item
, gpointer data
),
1744 g_return_if_fail(collection
!= NULL
);
1745 g_return_if_fail(IS_COLLECTION(collection
));
1747 cursor
= collection
->cursor_item
;
1749 for (in
= 0; in
< collection
->number_of_items
; in
++)
1751 if (test
&& !test(collection
->items
[in
].data
, data
))
1754 if (collection
->items
[in
].selected
)
1756 collection
->items
[out
].selected
= TRUE
;
1760 collection
->items
[out
].selected
= FALSE
;
1762 collection
->items
[out
].data
=
1763 collection
->items
[in
].data
;
1764 collection
->items
[out
].view_data
=
1765 collection
->items
[in
].view_data
;
1771 if (collection
->free_item
)
1772 collection
->free_item(collection
,
1773 &collection
->items
[in
]);
1782 collection
->cursor_item
= cursor
;
1784 if (collection
->wink_item
!= -1)
1786 collection
->wink_item
= -1;
1787 gtk_timeout_remove(collection
->wink_timeout
);
1790 collection
->number_of_items
= out
;
1791 if (collection
->number_selected
&& !selected
)
1793 /* We've lost all the selected items */
1794 g_signal_emit(collection
,
1795 collection_signals
[LOSE_SELECTION
], 0,
1796 current_event_time
);
1799 collection
->number_selected
= selected
;
1800 resize_arrays(collection
,
1801 MAX(collection
->number_of_items
, MINIMUM_ITEMS
));
1803 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection
)))
1804 gtk_widget_queue_draw(GTK_WIDGET(collection
));
1806 gtk_widget_queue_resize(GTK_WIDGET(collection
));
1810 /* Move the cursor by the given row and column offsets.
1811 * Moving by (0,0) can be used to simply make the cursor appear.
1813 void collection_move_cursor(Collection
*collection
, int drow
, int dcol
)
1816 int first
, last
, total_rows
;
1818 g_return_if_fail(collection
!= NULL
);
1819 g_return_if_fail(IS_COLLECTION(collection
));
1821 get_visible_limits(collection
, &first
, &last
);
1823 item
= collection
->cursor_item
;
1826 item
= MIN(collection
->cursor_item_old
,
1827 collection
->number_of_items
- 1);
1837 row
= item
/ collection
->columns
;
1838 col
= item
% collection
->columns
+ dcol
;
1842 else if (row
> last
)
1845 row
= MAX(row
+ drow
, 0);
1848 total_rows
= (collection
->number_of_items
+ collection
->columns
- 1)
1849 / collection
->columns
;
1851 if (row
>= total_rows
- 1 && drow
> 0)
1853 row
= total_rows
- 1;
1854 item
= col
+ row
* collection
->columns
;
1855 if (item
>= collection
->number_of_items
- 1)
1857 collection_set_cursor_item(collection
,
1858 collection
->number_of_items
- 1);
1865 item
= col
+ row
* collection
->columns
;
1867 if (item
>= 0 && item
< collection
->number_of_items
)
1868 collection_set_cursor_item(collection
, item
);
1871 /* When autoscroll is on, a timer keeps track of the pointer position.
1872 * While it's near the top or bottom of the window, the window scrolls.
1874 * If the mouse buttons are released, the pointer leaves the window, or
1875 * a drag-and-drop operation finishes, auto_scroll is turned off.
1877 void collection_set_autoscroll(Collection
*collection
, gboolean auto_scroll
)
1879 g_return_if_fail(collection
!= NULL
);
1880 g_return_if_fail(IS_COLLECTION(collection
));
1884 if (collection
->auto_scroll
!= -1)
1885 return; /* Already on! */
1887 collection
->auto_scroll
= gtk_timeout_add(50,
1888 (GtkFunction
) as_timeout
,
1893 if (collection
->auto_scroll
== -1)
1894 return; /* Already off! */
1896 gtk_timeout_remove(collection
->auto_scroll
);
1897 collection
->auto_scroll
= -1;
1901 /* Start a lasso box drag */
1902 void collection_lasso_box(Collection
*collection
, int x
, int y
)
1904 collection
->drag_box_x
[0] = x
;
1905 collection
->drag_box_y
[0] = y
;
1906 collection
->drag_box_x
[1] = x
;
1907 collection
->drag_box_y
[1] = y
;
1909 collection_set_autoscroll(collection
, TRUE
);
1910 add_lasso_box(collection
);
1913 /* Remove the lasso box. Applies fn to each item inside the box.
1914 * fn may be GDK_INVERT, GDK_SET, GDK_NOOP or GDK_CLEAR.
1916 void collection_end_lasso(Collection
*collection
, GdkFunction fn
)
1918 if (fn
!= GDK_CLEAR
)
1920 GdkRectangle region
;
1922 find_lasso_area(collection
, ®ion
);
1924 collection_process_area(collection
, ®ion
, fn
,
1928 abort_lasso(collection
);
1931 /* Unblock the selection_changed signal, emitting the signal if the
1932 * block counter reaches zero and emit is TRUE.
1934 void collection_unblock_selection_changed(Collection
*collection
,
1938 g_return_if_fail(collection
!= NULL
);
1939 g_return_if_fail(IS_COLLECTION(collection
));
1940 g_return_if_fail(collection
->block_selection_changed
> 0);
1942 collection
->block_selection_changed
--;
1944 if (emit
&& !collection
->block_selection_changed
)
1945 g_signal_emit(collection
,
1946 collection_signals
[SELECTION_CHANGED
], 0, time
);