4 * Collection - a GTK+ widget
5 * Copyright (C) 1999, Thomas Leonard, <tal197@ecs.soton.ac.uk>.
7 * The collection widget provides an area for displaying a collection of
8 * objects (such as files). It allows the user to choose a selection of
9 * them and provides signals to allow popping up menus, detecting
12 * This program is free software; you can redistribute it and/or modify it
13 * under the terms of the GNU General Public License as published by the Free
14 * Software Foundation; either version 2 of the License, or (at your option)
17 * This program is distributed in the hope that it will be useful, but WITHOUT
18 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
22 * You should have received a copy of the GNU General Public License along with
23 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
24 * Place, Suite 330, Boston, MA 02111-1307 USA
30 #include <gdk/gdkkeysyms.h>
31 #include "collection.h"
35 #define MINIMUM_ITEMS 16
37 int collection_menu_button
= 3;
38 gboolean collection_single_click
= FALSE
;
48 * void open_item(collection, item, item_number, user_data)
49 * User has double clicked on this item.
51 * void drag_selection(collection, motion_event, number_selected, user_data)
52 * User has tried to drag the selection.
54 * void show_menu(collection, button_event, item, user_data)
55 * User has menu-clicked on the collection. 'item' is the number
56 * of the item clicked, or -1 if the click was over the background.
58 * void gain_selection(collection, time, user_data)
59 * We've gone from no selected items to having a selection.
60 * Time is the time of the event that caused the change, or
61 * GDK_CURRENT_TIME if not known.
63 * void lose_selection(collection, time, user_data)
64 * We've dropped to having no selected items.
65 * Time is the time of the event that caused the change, or
66 * GDK_CURRENT_TIME if not known.
78 static guint collection_signals
[LAST_SIGNAL
] = { 0 };
80 static guint32 current_event_time
= GDK_CURRENT_TIME
;
82 static GtkWidgetClass
*parent_class
= NULL
;
84 static GdkCursor
*crosshair
= NULL
;
86 /* Static prototypes */
87 static void draw_one_item(Collection
*collection
,
90 static void collection_class_init(CollectionClass
*class);
91 static void collection_init(Collection
*object
);
92 static void collection_destroy(GtkObject
*object
);
93 static void collection_finalize(GtkObject
*object
);
94 static void collection_realize(GtkWidget
*widget
);
95 static gint
collection_paint(Collection
*collection
,
97 static void collection_size_request(GtkWidget
*widget
,
98 GtkRequisition
*requisition
);
99 static void collection_size_allocate(GtkWidget
*widget
,
100 GtkAllocation
*allocation
);
101 static void collection_set_style(GtkWidget
*widget
,
102 GtkStyle
*previous_style
);
103 static void collection_set_adjustment(Collection
*collection
,
104 GtkAdjustment
*vadj
);
105 static void collection_set_arg( GtkObject
*object
,
108 static void collection_get_arg( GtkObject
*object
,
111 static void collection_adjustment(GtkAdjustment
*adjustment
,
112 Collection
*collection
);
113 static void collection_disconnect(GtkAdjustment
*adjustment
,
114 Collection
*collection
);
115 static void set_vadjustment(Collection
*collection
);
116 static void collection_draw(GtkWidget
*widget
, GdkRectangle
*area
);
117 static gint
collection_expose(GtkWidget
*widget
, GdkEventExpose
*event
);
118 static void scroll_by(Collection
*collection
, gint diff
);
119 static gint
collection_button_press(GtkWidget
*widget
,
120 GdkEventButton
*event
);
121 static gint
collection_button_release(GtkWidget
*widget
,
122 GdkEventButton
*event
);
123 static void default_draw_item(GtkWidget
*widget
,
124 CollectionItem
*data
,
126 static gboolean
default_test_point(Collection
*collection
,
127 int point_x
, int point_y
,
128 CollectionItem
*data
,
129 int width
, int height
);
130 static gint
collection_motion_notify(GtkWidget
*widget
,
131 GdkEventMotion
*event
);
132 static void add_lasso_box(Collection
*collection
);
133 static void remove_lasso_box(Collection
*collection
);
134 static void draw_lasso_box(Collection
*collection
);
135 static int item_at_row_col(Collection
*collection
, int row
, int col
);
136 static void collection_clear_except(Collection
*collection
, gint exception
);
137 static void cancel_wink(Collection
*collection
);
138 static gint
collection_key_press(GtkWidget
*widget
, GdkEventKey
*event
);
139 static void get_visible_limits(Collection
*collection
, int *first
, int *last
);
140 static void scroll_to_show(Collection
*collection
, int item
);
141 static gint
focus_in(GtkWidget
*widget
, GdkEventFocus
*event
);
142 static gint
focus_out(GtkWidget
*widget
, GdkEventFocus
*event
);
144 static void draw_one_item(Collection
*collection
, int item
, GdkRectangle
*area
)
146 if (item
< collection
->number_of_items
)
148 collection
->draw_item((GtkWidget
*) collection
,
149 &collection
->items
[item
],
151 if (item
== collection
->wink_item
)
152 gdk_draw_rectangle(((GtkWidget
*) collection
)->window
,
153 ((GtkWidget
*) collection
)->style
->black_gc
,
156 area
->width
- 1, area
->height
- 1);
158 if (item
== collection
->cursor_item
)
160 gdk_draw_rectangle(((GtkWidget
*) collection
)->window
,
161 collection
->target_cb
162 ? ((GtkWidget
*) collection
)->style
->white_gc
163 : ((GtkWidget
*) collection
)->style
->black_gc
,
165 area
->x
+ 1, area
->y
+ 1,
166 area
->width
- 3, area
->height
- 3);
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 static void collection_class_init(CollectionClass
*class)
197 GtkObjectClass
*object_class
;
198 GtkWidgetClass
*widget_class
;
200 object_class
= (GtkObjectClass
*) class;
201 widget_class
= (GtkWidgetClass
*) class;
203 parent_class
= gtk_type_class(gtk_widget_get_type());
205 gtk_object_add_arg_type("Collection::vadjustment",
207 GTK_ARG_READWRITE
| GTK_ARG_CONSTRUCT
,
210 object_class
->destroy
= collection_destroy
;
211 object_class
->finalize
= collection_finalize
;
213 widget_class
->realize
= collection_realize
;
214 widget_class
->draw
= collection_draw
;
215 widget_class
->expose_event
= collection_expose
;
216 widget_class
->size_request
= collection_size_request
;
217 widget_class
->size_allocate
= collection_size_allocate
;
218 widget_class
->style_set
= collection_set_style
;
220 widget_class
->key_press_event
= collection_key_press
;
221 widget_class
->button_press_event
= collection_button_press
;
222 widget_class
->button_release_event
= collection_button_release
;
223 widget_class
->motion_notify_event
= collection_motion_notify
;
224 widget_class
->focus_in_event
= focus_in
;
225 widget_class
->focus_out_event
= focus_out
;
227 object_class
->set_arg
= collection_set_arg
;
228 object_class
->get_arg
= collection_get_arg
;
230 class->open_item
= NULL
;
231 class->drag_selection
= NULL
;
232 class->show_menu
= NULL
;
233 class->gain_selection
= NULL
;
234 class->lose_selection
= NULL
;
236 collection_signals
[OPEN_ITEM
] = gtk_signal_new("open_item",
239 GTK_SIGNAL_OFFSET(CollectionClass
,
241 gtk_marshal_NONE__POINTER_UINT
,
245 collection_signals
[DRAG_SELECTION
] = gtk_signal_new("drag_selection",
248 GTK_SIGNAL_OFFSET(CollectionClass
,
250 gtk_marshal_NONE__POINTER_UINT
,
254 collection_signals
[SHOW_MENU
] = gtk_signal_new("show_menu",
257 GTK_SIGNAL_OFFSET(CollectionClass
,
259 gtk_marshal_NONE__POINTER_INT
,
263 collection_signals
[GAIN_SELECTION
] = gtk_signal_new("gain_selection",
266 GTK_SIGNAL_OFFSET(CollectionClass
,
268 gtk_marshal_NONE__UINT
,
271 collection_signals
[LOSE_SELECTION
] = gtk_signal_new("lose_selection",
274 GTK_SIGNAL_OFFSET(CollectionClass
,
276 gtk_marshal_NONE__UINT
,
280 gtk_object_class_add_signals(object_class
,
281 collection_signals
, LAST_SIGNAL
);
284 static void collection_init(Collection
*object
)
286 g_return_if_fail(object
!= NULL
);
287 g_return_if_fail(IS_COLLECTION(object
));
290 crosshair
= gdk_cursor_new(GDK_CROSSHAIR
);
292 GTK_WIDGET_SET_FLAGS(GTK_WIDGET(object
), GTK_CAN_FOCUS
);
294 object
->panel
= FALSE
;
295 object
->number_of_items
= 0;
296 object
->number_selected
= 0;
298 object
->item_width
= 64;
299 object
->item_height
= 64;
301 object
->paint_level
= PAINT_OVERWRITE
;
302 object
->last_scroll
= 0;
304 object
->items
= g_malloc(sizeof(CollectionItem
) * MINIMUM_ITEMS
);
305 object
->cursor_item
= -1;
306 object
->wink_item
= -1;
307 object
->array_size
= MINIMUM_ITEMS
;
308 object
->draw_item
= default_draw_item
;
309 object
->test_point
= default_test_point
;
311 object
->buttons_pressed
= 0;
312 object
->may_drag
= FALSE
;
317 GtkWidget
* collection_new(GtkAdjustment
*vadj
)
320 g_return_val_if_fail(GTK_IS_ADJUSTMENT(vadj
), NULL
);
322 return GTK_WIDGET(gtk_widget_new(collection_get_type(),
327 void collection_set_functions(Collection
*collection
,
328 CollectionDrawFunc draw_item
,
329 CollectionTestFunc test_point
)
333 g_return_if_fail(collection
!= NULL
);
334 g_return_if_fail(IS_COLLECTION(collection
));
336 widget
= GTK_WIDGET(collection
);
339 draw_item
= default_draw_item
;
341 test_point
= default_test_point
;
343 collection
->draw_item
= draw_item
;
344 collection
->test_point
= test_point
;
346 if (GTK_WIDGET_REALIZED(widget
))
348 collection
->paint_level
= PAINT_CLEAR
;
349 gtk_widget_queue_clear(widget
);
353 /* After this we are unusable, but our data (if any) is still hanging around.
354 * It will be freed later with finalize.
356 static void collection_destroy(GtkObject
*object
)
358 Collection
*collection
;
360 g_return_if_fail(object
!= NULL
);
361 g_return_if_fail(IS_COLLECTION(object
));
363 collection
= COLLECTION(object
);
365 if (collection
->wink_item
!= -1)
367 collection
->wink_item
= -1;
368 gtk_timeout_remove(collection
->wink_timeout
);
371 gtk_signal_disconnect_by_data(GTK_OBJECT(collection
->vadj
),
374 if (GTK_OBJECT_CLASS(parent_class
)->destroy
)
375 (*GTK_OBJECT_CLASS(parent_class
)->destroy
)(object
);
378 /* This is the last thing that happens to us. Free all data. */
379 static void collection_finalize(GtkObject
*object
)
381 Collection
*collection
;
383 collection
= COLLECTION(object
);
385 if (collection
->vadj
)
387 gtk_object_unref(GTK_OBJECT(collection
->vadj
));
390 g_free(collection
->items
);
393 static void collection_realize(GtkWidget
*widget
)
395 Collection
*collection
;
396 GdkWindowAttr attributes
;
397 gint attributes_mask
;
398 GdkGCValues xor_values
;
400 g_return_if_fail(widget
!= NULL
);
401 g_return_if_fail(IS_COLLECTION(widget
));
402 g_return_if_fail(widget
->parent
!= NULL
);
404 GTK_WIDGET_SET_FLAGS(widget
, GTK_REALIZED
);
405 collection
= COLLECTION(widget
);
407 attributes
.x
= widget
->allocation
.x
;
408 attributes
.y
= widget
->allocation
.y
;
409 attributes
.width
= widget
->allocation
.width
;
410 attributes
.height
= widget
->allocation
.height
;
411 attributes
.wclass
= GDK_INPUT_OUTPUT
;
412 attributes
.window_type
= GDK_WINDOW_CHILD
;
413 attributes
.event_mask
= gtk_widget_get_events(widget
) |
415 GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK
|
416 GDK_BUTTON1_MOTION_MASK
| GDK_BUTTON2_MOTION_MASK
|
417 GDK_BUTTON3_MOTION_MASK
;
418 attributes
.visual
= gtk_widget_get_visual(widget
);
419 attributes
.colormap
= gtk_widget_get_colormap(widget
);
421 attributes_mask
= GDK_WA_X
| GDK_WA_Y
|
422 GDK_WA_VISUAL
| GDK_WA_COLORMAP
;
423 widget
->window
= gdk_window_new(widget
->parent
->window
,
424 &attributes
, attributes_mask
);
426 widget
->style
= gtk_style_attach(widget
->style
, widget
->window
);
428 gdk_window_set_user_data(widget
->window
, widget
);
430 gdk_window_set_background(widget
->window
,
431 &widget
->style
->base
[GTK_STATE_INSENSITIVE
]);
433 /* Try to stop everything flickering horribly */
434 gdk_window_set_static_gravities(widget
->window
, TRUE
);
436 set_vadjustment(collection
);
438 xor_values
.function
= GDK_XOR
;
439 xor_values
.foreground
.red
= 0xffff;
440 xor_values
.foreground
.green
= 0xffff;
441 xor_values
.foreground
.blue
= 0xffff;
442 gdk_color_alloc(gtk_widget_get_colormap(widget
),
443 &xor_values
.foreground
);
444 collection
->xor_gc
= gdk_gc_new_with_values(widget
->window
,
450 static void collection_size_request(GtkWidget
*widget
,
451 GtkRequisition
*requisition
)
453 requisition
->width
= MIN_WIDTH
;
454 requisition
->height
= MIN_HEIGHT
;
457 static void collection_size_allocate(GtkWidget
*widget
,
458 GtkAllocation
*allocation
)
460 Collection
*collection
;
463 g_return_if_fail(widget
!= NULL
);
464 g_return_if_fail(IS_COLLECTION(widget
));
465 g_return_if_fail(allocation
!= NULL
);
467 collection
= COLLECTION(widget
);
469 old_columns
= collection
->columns
;
470 if (widget
->allocation
.x
!= allocation
->x
471 || widget
->allocation
.y
!= allocation
->y
)
472 collection
->paint_level
= PAINT_CLEAR
;
474 widget
->allocation
= *allocation
;
476 collection
->columns
= allocation
->width
/ collection
->item_width
;
477 if (collection
->columns
< 1)
478 collection
->columns
= 1;
480 if (GTK_WIDGET_REALIZED(widget
))
482 gdk_window_move_resize(widget
->window
,
483 allocation
->x
, allocation
->y
,
484 allocation
->width
, allocation
->height
);
486 if (old_columns
!= collection
->columns
)
488 collection
->paint_level
= PAINT_CLEAR
;
489 gtk_widget_queue_clear(widget
);
492 set_vadjustment(collection
);
494 if (collection
->cursor_item
!= -1)
495 scroll_to_show(collection
, collection
->cursor_item
);
499 static void collection_set_style(GtkWidget
*widget
,
500 GtkStyle
*previous_style
)
502 Collection
*collection
;
504 g_return_if_fail(IS_COLLECTION(widget
));
506 collection
= COLLECTION(widget
);
508 collection
->paint_level
= PAINT_CLEAR
;
510 if (parent_class
->style_set
)
511 (*parent_class
->style_set
)(widget
, previous_style
);
514 static gint
collection_paint(Collection
*collection
,
517 GdkRectangle whole
, item_area
;
522 int start_row
, last_row
;
523 int start_col
, last_col
;
527 scroll
= collection
->vadj
->value
;
529 widget
= GTK_WIDGET(collection
);
531 if (collection
->paint_level
> PAINT_NORMAL
|| area
== NULL
)
534 gdk_window_get_size(widget
->window
, &width
, &height
);
539 whole
.height
= height
;
543 if (collection
->paint_level
== PAINT_CLEAR
544 && !collection
->lasso_box
)
545 gdk_window_clear(widget
->window
);
547 collection
->paint_level
= PAINT_NORMAL
;
550 /* Calculate the ranges to plot */
551 start_row
= (area
->y
+ scroll
) / collection
->item_height
;
552 last_row
= (area
->y
+ area
->height
- 1 + scroll
)
553 / collection
->item_height
;
556 start_col
= area
->x
/ collection
->item_width
;
557 phys_last_col
= (area
->x
+ area
->width
- 1) / collection
->item_width
;
559 if (collection
->lasso_box
)
561 /* You can't be too careful with lasso boxes...
563 * clip gives the total area drawn over (this may be larger
564 * than the requested area). It's used to redraw the lasso
567 clip
.x
= start_col
* collection
->item_width
;
568 clip
.y
= start_row
* collection
->item_height
- scroll
;
569 clip
.width
= (phys_last_col
- start_col
+ 1)
570 * collection
->item_width
;
571 clip
.height
= (last_row
- start_row
+ 1)
572 * collection
->item_height
;
574 gdk_window_clear_area(widget
->window
,
575 clip
.x
, clip
.y
, clip
.width
, clip
.height
);
578 if (start_col
< collection
->columns
)
580 if (phys_last_col
>= collection
->columns
)
581 last_col
= collection
->columns
- 1;
583 last_col
= phys_last_col
;
587 item
= row
* collection
->columns
+ col
;
588 item_area
.width
= collection
->item_width
;
589 item_area
.height
= collection
->item_height
;
591 while ((item
== 0 || item
< collection
->number_of_items
)
594 item_area
.x
= col
* collection
->item_width
;
595 item_area
.y
= row
* collection
->item_height
- scroll
;
597 draw_one_item(collection
, item
, &item_area
);
604 item
= row
* collection
->columns
+ col
;
611 if (collection
->lasso_box
)
613 gdk_gc_set_clip_rectangle(collection
->xor_gc
, &clip
);
614 draw_lasso_box(collection
);
615 gdk_gc_set_clip_rectangle(collection
->xor_gc
, NULL
);
621 static void default_draw_item( GtkWidget
*widget
,
622 CollectionItem
*item
,
625 gdk_draw_arc(widget
->window
,
626 item
->selected
? widget
->style
->white_gc
627 : widget
->style
->black_gc
,
630 area
->width
, area
->height
,
635 static gboolean
default_test_point(Collection
*collection
,
636 int point_x
, int point_y
,
637 CollectionItem
*item
,
638 int width
, int height
)
642 /* Convert to point in unit circle */
643 f_x
= ((float) point_x
/ width
) - 0.5;
644 f_y
= ((float) point_y
/ height
) - 0.5;
646 return (f_x
* f_x
) + (f_y
* f_y
) <= .25;
649 static void collection_set_arg( GtkObject
*object
,
653 Collection
*collection
;
655 collection
= COLLECTION(object
);
659 case ARG_VADJUSTMENT
:
660 collection_set_adjustment(collection
,
661 GTK_VALUE_POINTER(*arg
));
668 static void collection_set_adjustment( Collection
*collection
,
672 g_return_if_fail (GTK_IS_ADJUSTMENT (vadj
));
674 vadj
= GTK_ADJUSTMENT(gtk_adjustment_new(0.0,
677 if (collection
->vadj
&& (collection
->vadj
!= vadj
))
679 gtk_signal_disconnect_by_data(GTK_OBJECT(collection
->vadj
),
681 gtk_object_unref(GTK_OBJECT(collection
->vadj
));
684 if (collection
->vadj
!= vadj
)
686 collection
->vadj
= vadj
;
687 gtk_object_ref(GTK_OBJECT(collection
->vadj
));
688 gtk_object_sink(GTK_OBJECT(collection
->vadj
));
690 gtk_signal_connect(GTK_OBJECT(collection
->vadj
),
692 (GtkSignalFunc
) collection_adjustment
,
694 gtk_signal_connect(GTK_OBJECT(collection
->vadj
),
696 (GtkSignalFunc
) collection_adjustment
,
698 gtk_signal_connect(GTK_OBJECT(collection
->vadj
),
700 (GtkSignalFunc
) collection_disconnect
,
702 collection_adjustment(vadj
, collection
);
706 static void collection_get_arg( GtkObject
*object
,
710 Collection
*collection
;
712 collection
= COLLECTION(object
);
716 case ARG_VADJUSTMENT
:
717 GTK_VALUE_POINTER(*arg
) = collection
->vadj
;
720 arg
->type
= GTK_TYPE_INVALID
;
725 /* Something about the adjustment has changed */
726 static void collection_adjustment(GtkAdjustment
*adjustment
,
727 Collection
*collection
)
731 g_return_if_fail(adjustment
!= NULL
);
732 g_return_if_fail(GTK_IS_ADJUSTMENT(adjustment
));
733 g_return_if_fail(collection
!= NULL
);
734 g_return_if_fail(IS_COLLECTION(collection
));
736 diff
= ((gint
) adjustment
->value
) - collection
->last_scroll
;
740 collection
->last_scroll
= adjustment
->value
;
742 scroll_by(collection
, diff
);
746 static void collection_disconnect(GtkAdjustment
*adjustment
,
747 Collection
*collection
)
749 g_return_if_fail(adjustment
!= NULL
);
750 g_return_if_fail(GTK_IS_ADJUSTMENT(adjustment
));
751 g_return_if_fail(collection
!= NULL
);
752 g_return_if_fail(IS_COLLECTION(collection
));
754 collection_set_adjustment(collection
, NULL
);
757 static void set_vadjustment(Collection
*collection
)
763 widget
= GTK_WIDGET(collection
);
765 if (!GTK_WIDGET_REALIZED(widget
))
768 gdk_window_get_size(widget
->window
, NULL
, &height
);
769 cols
= collection
->columns
;
770 rows
= (collection
->number_of_items
+ cols
- 1) / cols
;
772 collection
->vadj
->lower
= 0.0;
773 collection
->vadj
->upper
= collection
->item_height
* rows
;
775 collection
->vadj
->step_increment
=
776 MIN(collection
->vadj
->upper
, collection
->item_height
/ 4);
778 collection
->vadj
->page_increment
=
779 MIN(collection
->vadj
->upper
,
782 collection
->vadj
->page_size
= MIN(collection
->vadj
->upper
, height
);
784 collection
->vadj
->value
= MIN(collection
->vadj
->value
,
785 collection
->vadj
->upper
- collection
->vadj
->page_size
);
787 collection
->vadj
->value
= MAX(collection
->vadj
->value
, 0.0);
789 gtk_signal_emit_by_name(GTK_OBJECT(collection
->vadj
), "changed");
792 static void collection_draw(GtkWidget
*widget
, GdkRectangle
*area
)
794 Collection
*collection
;
796 g_return_if_fail(widget
!= NULL
);
797 g_return_if_fail(IS_COLLECTION(widget
));
798 g_return_if_fail(area
!= NULL
); /* Not actually used */
800 collection
= COLLECTION(widget
);
802 if (collection
->paint_level
> PAINT_NORMAL
)
803 collection_paint(collection
, area
);
806 static gint
collection_expose(GtkWidget
*widget
, GdkEventExpose
*event
)
808 g_return_val_if_fail(widget
!= NULL
, FALSE
);
809 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
810 g_return_val_if_fail(event
!= NULL
, FALSE
);
812 collection_paint(COLLECTION(widget
), &event
->area
);
817 /* Positive makes the contents go move up the screen */
818 static void scroll_by(Collection
*collection
, gint diff
)
824 GdkRectangle new_area
;
829 widget
= GTK_WIDGET(collection
);
831 if (collection
->lasso_box
)
832 remove_lasso_box(collection
);
834 gdk_window_get_size(widget
->window
, &width
, &height
);
836 new_area
.width
= width
;
843 new_area
.y
= height
- amount
;
853 new_area
.height
= amount
;
857 gdk_draw_pixmap(widget
->window
,
858 widget
->style
->white_gc
,
866 /* We have to redraw everything because any pending
867 * expose events now contain invalid areas.
868 * Don't need to clear the area first though...
870 if (collection
->paint_level
< PAINT_OVERWRITE
)
871 collection
->paint_level
= PAINT_OVERWRITE
;
874 collection
->paint_level
= PAINT_CLEAR
;
876 gdk_window_clear_area(widget
->window
,
878 width
, new_area
.height
);
879 collection_paint(collection
, NULL
);
882 static void resize_arrays(Collection
*collection
, guint new_size
)
884 g_return_if_fail(collection
!= NULL
);
885 g_return_if_fail(IS_COLLECTION(collection
));
886 g_return_if_fail(new_size
>= collection
->number_of_items
);
888 collection
->items
= g_realloc(collection
->items
,
889 sizeof(CollectionItem
) * new_size
);
890 collection
->array_size
= new_size
;
893 static void return_pressed(Collection
*collection
)
895 int item
= collection
->cursor_item
;
896 CollectionTargetFunc cb
= collection
->target_cb
;
897 gpointer data
= collection
->target_data
;
899 collection_target(collection
, NULL
, NULL
);
900 if (item
< 0 || item
>= collection
->number_of_items
)
905 cb(collection
, item
, data
);
909 gtk_signal_emit(GTK_OBJECT(collection
),
910 collection_signals
[OPEN_ITEM
],
911 collection
->items
[item
].data
,
915 static gint
collection_key_press(GtkWidget
*widget
, GdkEventKey
*event
)
917 Collection
*collection
;
920 g_return_val_if_fail(widget
!= NULL
, FALSE
);
921 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
922 g_return_val_if_fail(event
!= NULL
, FALSE
);
924 collection
= (Collection
*) widget
;
925 item
= collection
->cursor_item
;
927 switch (event
->keyval
)
930 collection_move_cursor(collection
, 0, -1);
933 collection_move_cursor(collection
, 0, 1);
936 collection_move_cursor(collection
, -1, 0);
939 collection_move_cursor(collection
, 1, 0);
942 collection_set_cursor_item(collection
, 0);
945 collection_set_cursor_item(collection
,
946 MAX((gint
) collection
->number_of_items
- 1, 0));
949 collection_move_cursor(collection
, -10, 0);
952 collection_move_cursor(collection
, 10, 0);
955 return_pressed(collection
);
958 if (!collection
->target_cb
)
959 return FALSE
; /* Pass it on */
960 collection_target(collection
, NULL
, NULL
);
963 if (item
>=0 && item
< collection
->number_of_items
)
964 collection_toggle_item(collection
, item
);
973 static gint
collection_button_press(GtkWidget
*widget
,
974 GdkEventButton
*event
)
976 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 collection
->item_clicked
= -1;
991 if (event
->button
> 3)
995 /* Wheel mouse scrolling */
996 if (event
->button
== 4)
997 diff
= -((signed int) collection
->item_height
) / 4;
998 else if (event
->button
== 5)
999 diff
= collection
->item_height
/ 4;
1005 int old_value
= collection
->vadj
->value
;
1007 gboolean box
= collection
->lasso_box
;
1009 new_value
= CLAMP(old_value
+ diff
, 0.0,
1010 collection
->vadj
->upper
1011 - collection
->vadj
->page_size
);
1012 diff
= new_value
- old_value
;
1017 remove_lasso_box(collection
);
1018 collection
->drag_box_y
[0] -= diff
;
1020 collection
->vadj
->value
= new_value
;
1021 gtk_signal_emit_by_name(
1022 GTK_OBJECT(collection
->vadj
),
1025 add_lasso_box(collection
);
1031 if (collection
->cursor_item
!= -1)
1032 collection_set_cursor_item(collection
, -1);
1034 scroll
= collection
->vadj
->value
;
1036 if (event
->type
== GDK_BUTTON_PRESS
&&
1037 event
->button
!= collection_menu_button
)
1039 if (collection
->buttons_pressed
++ == 0)
1040 gtk_grab_add(widget
);
1042 return FALSE
; /* Ignore extra presses */
1045 if (event
->state
& GDK_CONTROL_MASK
&& !collection_single_click
)
1048 action
= event
->button
;
1050 /* Ignore all clicks while we are dragging a lasso box */
1051 if (collection
->lasso_box
)
1054 col
= event
->x
/ collection
->item_width
;
1055 row
= (event
->y
+ scroll
) / collection
->item_height
;
1057 if (col
< 0 || row
< 0 || col
>= collection
->columns
)
1061 item
= col
+ row
* collection
->columns
;
1062 if (item
>= collection
->number_of_items
1064 !collection
->test_point(collection
,
1065 event
->x
- col
* collection
->item_width
,
1066 event
->y
- row
* collection
->item_height
1068 &collection
->items
[item
],
1069 collection
->item_width
,
1070 collection
->item_height
))
1076 if (collection
->target_cb
)
1078 CollectionTargetFunc cb
= collection
->target_cb
;
1079 gpointer data
= collection
->target_data
;
1081 collection_target(collection
, NULL
, NULL
);
1082 if (collection
->buttons_pressed
)
1084 gtk_grab_remove(widget
);
1085 collection
->buttons_pressed
= 0;
1087 if (item
> -1 && event
->button
!= collection_menu_button
)
1088 cb(collection
, item
, data
);
1092 collection
->drag_box_x
[0] = event
->x
;
1093 collection
->drag_box_y
[0] = event
->y
;
1094 collection
->item_clicked
= item
;
1096 stacked_time
= current_event_time
;
1097 current_event_time
= event
->time
;
1099 if (event
->button
== collection_menu_button
)
1101 gtk_signal_emit(GTK_OBJECT(collection
),
1102 collection_signals
[SHOW_MENU
],
1106 else if (event
->type
== GDK_2BUTTON_PRESS
&& collection
->panel
)
1110 else if ((event
->type
== GDK_2BUTTON_PRESS
&& !collection_single_click
)
1111 || collection
->panel
)
1115 if (collection
->buttons_pressed
)
1117 gtk_grab_remove(widget
);
1118 collection
->buttons_pressed
= 0;
1120 collection_unselect_item(collection
, item
);
1121 gtk_signal_emit(GTK_OBJECT(collection
),
1122 collection_signals
[OPEN_ITEM
],
1123 collection
->items
[item
].data
,
1127 else if (event
->type
== GDK_BUTTON_PRESS
)
1129 collection
->may_drag
= event
->button
< collection_menu_button
;
1135 if (!collection
->items
[item
].selected
)
1137 collection_select_item(collection
,
1139 collection_clear_except(collection
,
1144 collection_toggle_item(collection
, item
);
1146 else if (action
== 1)
1147 collection_clear_selection(collection
);
1150 current_event_time
= stacked_time
;
1154 static gint
collection_button_release(GtkWidget
*widget
,
1155 GdkEventButton
*event
)
1157 Collection
*collection
;
1161 int col
, start_col
, last_col
;
1167 g_return_val_if_fail(widget
!= NULL
, FALSE
);
1168 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
1169 g_return_val_if_fail(event
!= NULL
, FALSE
);
1171 collection
= COLLECTION(widget
);
1172 button
= event
->button
;
1174 scroll
= collection
->vadj
->value
;
1176 if (event
->button
> 3 || event
->button
== collection_menu_button
)
1178 if (collection
->buttons_pressed
== 0)
1180 if (--collection
->buttons_pressed
== 0)
1181 gtk_grab_remove(widget
);
1183 return FALSE
; /* Wait until ALL buttons are up */
1185 if (!collection
->lasso_box
)
1187 int item
= collection
->item_clicked
;
1189 if (collection_single_click
&& item
> -1
1190 && item
< collection
->number_of_items
1191 && (event
->state
& GDK_CONTROL_MASK
) == 0)
1193 int dx
= event
->x
- collection
->drag_box_x
[0];
1194 int dy
= event
->y
- collection
->drag_box_y
[0];
1196 if (ABS(dx
) + ABS(dy
) > 9)
1199 collection_unselect_item(collection
, item
);
1200 gtk_signal_emit(GTK_OBJECT(collection
),
1201 collection_signals
[OPEN_ITEM
],
1202 collection
->items
[item
].data
,
1209 remove_lasso_box(collection
);
1211 w
= collection
->item_width
;
1212 h
= collection
->item_height
;
1214 top
= collection
->drag_box_y
[0] + scroll
;
1215 bottom
= collection
->drag_box_y
[1] + scroll
;
1226 row
= MAX(top
/ h
, 0);
1227 last_row
= bottom
/ h
;
1229 top
= collection
->drag_box_x
[0]; /* (left) */
1230 bottom
= collection
->drag_box_x
[1];
1240 start_col
= MAX(top
/ w
, 0);
1241 last_col
= bottom
/ w
;
1242 if (last_col
>= collection
->columns
)
1243 last_col
= collection
->columns
- 1;
1245 stacked_time
= current_event_time
;
1246 current_event_time
= event
->time
;
1248 while (row
<= last_row
)
1251 item
= row
* collection
->columns
+ col
;
1252 while (col
<= last_col
)
1254 if (item
>= collection
->number_of_items
)
1256 current_event_time
= stacked_time
;
1261 collection_select_item(collection
, item
);
1263 collection_toggle_item(collection
, item
);
1270 current_event_time
= stacked_time
;
1275 static gint
collection_motion_notify(GtkWidget
*widget
,
1276 GdkEventMotion
*event
)
1278 Collection
*collection
;
1282 g_return_val_if_fail(widget
!= NULL
, FALSE
);
1283 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
1284 g_return_val_if_fail(event
!= NULL
, FALSE
);
1286 collection
= COLLECTION(widget
);
1288 if (collection
->buttons_pressed
== 0)
1291 stacked_time
= current_event_time
;
1292 current_event_time
= event
->time
;
1294 if (event
->window
!= widget
->window
)
1295 gdk_window_get_pointer(widget
->window
, &x
, &y
, NULL
);
1302 if (collection
->lasso_box
)
1304 int new_value
= 0, diff
;
1307 gdk_window_get_size(widget
->window
, NULL
, &height
);
1311 int old_value
= collection
->vadj
->value
;
1313 new_value
= MAX(old_value
+ y
/ 10, 0.0);
1314 diff
= new_value
- old_value
;
1316 else if (y
> height
)
1318 int old_value
= collection
->vadj
->value
;
1320 new_value
= MIN(old_value
+ (y
- height
) / 10,
1321 collection
->vadj
->upper
1322 - collection
->vadj
->page_size
);
1323 diff
= new_value
- old_value
;
1328 remove_lasso_box(collection
);
1329 collection
->drag_box_x
[1] = x
;
1330 collection
->drag_box_y
[1] = y
;
1334 collection
->drag_box_y
[0] -= diff
;
1335 collection
->vadj
->value
= new_value
;
1336 gtk_signal_emit_by_name(GTK_OBJECT(collection
->vadj
),
1339 add_lasso_box(collection
);
1341 else if (collection
->may_drag
)
1343 int dx
= x
- collection
->drag_box_x
[0];
1344 int dy
= y
- collection
->drag_box_y
[0];
1346 if (abs(dx
) > 9 || abs(dy
) > 9)
1349 int scroll
= collection
->vadj
->value
;
1351 collection
->may_drag
= FALSE
;
1353 col
= collection
->drag_box_x
[0]
1354 / collection
->item_width
;
1355 row
= (collection
->drag_box_y
[0] + scroll
)
1356 / collection
->item_height
;
1357 item
= item_at_row_col(collection
, row
, col
);
1359 if (item
!= -1 && collection
->test_point(collection
,
1360 collection
->drag_box_x
[0] -
1361 col
* collection
->item_width
,
1362 collection
->drag_box_y
[0]
1363 - row
* collection
->item_height
1365 &collection
->items
[item
],
1366 collection
->item_width
,
1367 collection
->item_height
))
1369 collection
->buttons_pressed
= 0;
1370 gtk_grab_remove(widget
);
1371 collection_select_item(collection
, item
);
1372 gtk_signal_emit(GTK_OBJECT(collection
),
1373 collection_signals
[DRAG_SELECTION
],
1375 collection
->number_selected
);
1379 collection
->drag_box_x
[1] = x
;
1380 collection
->drag_box_y
[1] = y
;
1381 add_lasso_box(collection
);
1386 current_event_time
= stacked_time
;
1390 static void add_lasso_box(Collection
*collection
)
1392 g_return_if_fail(collection
!= NULL
);
1393 g_return_if_fail(IS_COLLECTION(collection
));
1394 g_return_if_fail(collection
->lasso_box
== FALSE
);
1396 collection
->lasso_box
= TRUE
;
1397 draw_lasso_box(collection
);
1400 static void draw_lasso_box(Collection
*collection
)
1403 int x
, y
, width
, height
;
1405 widget
= GTK_WIDGET(collection
);
1407 x
= MIN(collection
->drag_box_x
[0], collection
->drag_box_x
[1]);
1408 y
= MIN(collection
->drag_box_y
[0], collection
->drag_box_y
[1]);
1409 width
= abs(collection
->drag_box_x
[1] - collection
->drag_box_x
[0]);
1410 height
= abs(collection
->drag_box_y
[1] - collection
->drag_box_y
[0]);
1412 gdk_draw_rectangle(widget
->window
, collection
->xor_gc
, FALSE
,
1413 x
, y
, width
, height
);
1416 static void remove_lasso_box(Collection
*collection
)
1418 g_return_if_fail(collection
!= NULL
);
1419 g_return_if_fail(IS_COLLECTION(collection
));
1420 g_return_if_fail(collection
->lasso_box
== TRUE
);
1422 draw_lasso_box(collection
);
1424 collection
->lasso_box
= FALSE
;
1429 /* Convert a row,col address to an item number, or -1 if none */
1430 static int item_at_row_col(Collection
*collection
, int row
, int col
)
1434 if (row
< 0 || col
< 0 || col
>= collection
->columns
)
1437 item
= col
+ row
* collection
->columns
;
1439 if (item
>= collection
->number_of_items
)
1444 /* Make sure that 'item' is fully visible (vertically), scrolling if not. */
1445 static void scroll_to_show(Collection
*collection
, int item
)
1447 int first
, last
, row
;
1449 g_return_if_fail(collection
!= NULL
);
1450 g_return_if_fail(IS_COLLECTION(collection
));
1452 row
= item
/ collection
->columns
;
1453 get_visible_limits(collection
, &first
, &last
);
1457 gtk_adjustment_set_value(collection
->vadj
,
1458 row
* collection
->item_height
);
1460 else if (row
>= last
)
1462 GtkWidget
*widget
= (GtkWidget
*) collection
;
1465 if (GTK_WIDGET_REALIZED(widget
))
1467 gdk_window_get_size(widget
->window
, NULL
, &height
);
1468 gtk_adjustment_set_value(collection
->vadj
,
1469 (row
+ 1) * collection
->item_height
- height
);
1474 /* Return the first and last rows which are [partly] visible. Does not
1475 * ensure that the rows actually exist (contain items).
1477 static void get_visible_limits(Collection
*collection
, int *first
, int *last
)
1479 GtkWidget
*widget
= (GtkWidget
*) collection
;
1482 g_return_if_fail(collection
!= NULL
);
1483 g_return_if_fail(IS_COLLECTION(collection
));
1484 g_return_if_fail(first
!= NULL
&& last
!= NULL
);
1486 if (!GTK_WIDGET_REALIZED(widget
))
1493 scroll
= collection
->vadj
->value
;
1494 gdk_window_get_size(widget
->window
, NULL
, &height
);
1496 *first
= MAX(scroll
/ collection
->item_height
, 0);
1497 *last
= (scroll
+ height
- 1) /collection
->item_height
;
1504 /* Unselect all items except number item (-1 to unselect everything) */
1505 static void collection_clear_except(Collection
*collection
, gint exception
)
1511 int end
; /* Selected items to end up with */
1513 widget
= GTK_WIDGET(collection
);
1514 scroll
= collection
->vadj
->value
;
1516 end
= exception
>= 0 && exception
< collection
->number_of_items
1517 ? collection
->items
[exception
].selected
!= 0 : 0;
1519 area
.width
= collection
->item_width
;
1520 area
.height
= collection
->item_height
;
1522 if (collection
->number_selected
== 0)
1525 while (collection
->number_selected
> end
)
1527 while (item
== exception
|| !collection
->items
[item
].selected
)
1530 area
.x
= (item
% collection
->columns
) * area
.width
;
1531 area
.y
= (item
/ collection
->columns
) * area
.height
1534 collection
->items
[item
++].selected
= FALSE
;
1535 gdk_window_clear_area(widget
->window
,
1536 area
.x
, area
.y
, area
.width
, area
.height
);
1537 collection_paint(collection
, &area
);
1539 collection
->number_selected
--;
1543 gtk_signal_emit(GTK_OBJECT(collection
),
1544 collection_signals
[LOSE_SELECTION
],
1545 current_event_time
);
1548 /* Cancel the current wink effect. */
1549 static void cancel_wink(Collection
*collection
)
1553 g_return_if_fail(collection
!= NULL
);
1554 g_return_if_fail(IS_COLLECTION(collection
));
1555 g_return_if_fail(collection
->wink_item
!= -1);
1557 item
= collection
->wink_item
;
1559 collection
->wink_item
= -1;
1560 gtk_timeout_remove(collection
->wink_timeout
);
1562 collection_draw_item(collection
, item
, TRUE
);
1565 static gboolean
cancel_wink_timeout(Collection
*collection
)
1569 g_return_val_if_fail(collection
!= NULL
, FALSE
);
1570 g_return_val_if_fail(IS_COLLECTION(collection
), FALSE
);
1571 g_return_val_if_fail(collection
->wink_item
!= -1, FALSE
);
1573 item
= collection
->wink_item
;
1575 collection
->wink_item
= -1;
1577 collection_draw_item(collection
, item
, TRUE
);
1582 static gint
focus_in(GtkWidget
*widget
, GdkEventFocus
*event
)
1584 g_return_val_if_fail(widget
!= NULL
, FALSE
);
1585 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
1586 g_return_val_if_fail(event
!= NULL
, FALSE
);
1588 GTK_WIDGET_SET_FLAGS(widget
, GTK_HAS_FOCUS
);
1589 gtk_widget_draw_focus(widget
);
1594 static gint
focus_out(GtkWidget
*widget
, GdkEventFocus
*event
)
1596 g_return_val_if_fail(widget
!= NULL
, FALSE
);
1597 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
1598 g_return_val_if_fail(event
!= NULL
, FALSE
);
1600 GTK_WIDGET_UNSET_FLAGS(widget
, GTK_HAS_FOCUS
);
1601 gtk_widget_draw_focus(widget
);
1606 /* Functions for managing collections */
1608 /* Remove all objects from the collection */
1609 void collection_clear(Collection
*collection
)
1613 g_return_if_fail(IS_COLLECTION(collection
));
1615 if (collection
->number_of_items
== 0)
1618 if (collection
->wink_item
!= -1)
1620 collection
->wink_item
= -1;
1621 gtk_timeout_remove(collection
->wink_timeout
);
1624 collection_set_cursor_item(collection
,
1625 collection
->cursor_item
== -1 ? -1: 0);
1626 prev_selected
= collection
->number_selected
;
1627 collection
->number_of_items
= collection
->number_selected
= 0;
1629 resize_arrays(collection
, MINIMUM_ITEMS
);
1631 collection
->paint_level
= PAINT_CLEAR
;
1633 gtk_widget_queue_clear(GTK_WIDGET(collection
));
1635 if (prev_selected
&& collection
->number_selected
== 0)
1636 gtk_signal_emit(GTK_OBJECT(collection
),
1637 collection_signals
[LOSE_SELECTION
],
1638 current_event_time
);
1641 /* Inserts a new item at the end. The new item is unselected, and its
1642 * number is returned.
1644 gint
collection_insert(Collection
*collection
, gpointer data
)
1648 g_return_val_if_fail(IS_COLLECTION(collection
), -1);
1650 item
= collection
->number_of_items
;
1652 if (item
>= collection
->array_size
)
1653 resize_arrays(collection
, item
+ (item
>> 1));
1655 collection
->items
[item
].data
= data
;
1656 collection
->items
[item
].selected
= FALSE
;
1658 collection
->number_of_items
++;
1660 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection
)))
1662 set_vadjustment(collection
);
1663 collection_draw_item(collection
,
1664 collection
->number_of_items
- 1,
1671 /* Unselect an item by number */
1672 void collection_unselect_item(Collection
*collection
, gint item
)
1674 g_return_if_fail(collection
!= NULL
);
1675 g_return_if_fail(IS_COLLECTION(collection
));
1676 g_return_if_fail(item
>= 0 && item
< collection
->number_of_items
);
1678 if (collection
->items
[item
].selected
)
1680 collection
->items
[item
].selected
= FALSE
;
1681 collection_draw_item(collection
, item
, TRUE
);
1683 if (--collection
->number_selected
== 0)
1684 gtk_signal_emit(GTK_OBJECT(collection
),
1685 collection_signals
[LOSE_SELECTION
],
1686 current_event_time
);
1690 /* Select an item by number */
1691 void collection_select_item(Collection
*collection
, gint item
)
1693 g_return_if_fail(collection
!= NULL
);
1694 g_return_if_fail(IS_COLLECTION(collection
));
1695 g_return_if_fail(item
>= 0 && item
< collection
->number_of_items
);
1697 if (collection
->items
[item
].selected
)
1698 return; /* Already selected */
1700 collection
->items
[item
].selected
= TRUE
;
1701 collection_draw_item(collection
, item
, TRUE
);
1703 if (collection
->number_selected
++ == 0)
1704 gtk_signal_emit(GTK_OBJECT(collection
),
1705 collection_signals
[GAIN_SELECTION
],
1706 current_event_time
);
1709 /* Toggle the selected state of an item (by number) */
1710 void collection_toggle_item(Collection
*collection
, gint item
)
1712 g_return_if_fail(collection
!= NULL
);
1713 g_return_if_fail(IS_COLLECTION(collection
));
1714 g_return_if_fail(item
>= 0 && item
< collection
->number_of_items
);
1716 if (collection
->items
[item
].selected
)
1718 collection
->items
[item
].selected
= FALSE
;
1719 if (--collection
->number_selected
== 0)
1720 gtk_signal_emit(GTK_OBJECT(collection
),
1721 collection_signals
[LOSE_SELECTION
],
1722 current_event_time
);
1726 collection
->items
[item
].selected
= TRUE
;
1727 if (collection
->number_selected
++ == 0)
1728 gtk_signal_emit(GTK_OBJECT(collection
),
1729 collection_signals
[GAIN_SELECTION
],
1730 current_event_time
);
1732 collection_draw_item(collection
, item
, TRUE
);
1735 /* Select all items in the collection */
1736 void collection_select_all(Collection
*collection
)
1743 g_return_if_fail(collection
!= NULL
);
1744 g_return_if_fail(IS_COLLECTION(collection
));
1746 widget
= GTK_WIDGET(collection
);
1747 scroll
= collection
->vadj
->value
;
1749 area
.width
= collection
->item_width
;
1750 area
.height
= collection
->item_height
;
1752 if (collection
->number_selected
== collection
->number_of_items
)
1753 return; /* Nothing to do */
1755 while (collection
->number_selected
< collection
->number_of_items
)
1757 while (collection
->items
[item
].selected
)
1760 area
.x
= (item
% collection
->columns
) * area
.width
;
1761 area
.y
= (item
/ collection
->columns
) * area
.height
1764 collection
->items
[item
++].selected
= TRUE
;
1765 gdk_window_clear_area(widget
->window
,
1766 area
.x
, area
.y
, area
.width
, area
.height
);
1767 collection_paint(collection
, &area
);
1769 collection
->number_selected
++;
1772 gtk_signal_emit(GTK_OBJECT(collection
),
1773 collection_signals
[GAIN_SELECTION
],
1774 current_event_time
);
1777 /* Unselect all items in the collection */
1778 void collection_clear_selection(Collection
*collection
)
1780 g_return_if_fail(collection
!= NULL
);
1781 g_return_if_fail(IS_COLLECTION(collection
));
1783 collection_clear_except(collection
, -1);
1786 /* Force a redraw of the specified item, if it is visible */
1787 void collection_draw_item(Collection
*collection
, gint item
, gboolean blank
)
1794 int area_y
, area_height
; /* NOT shorts! */
1796 g_return_if_fail(collection
!= NULL
);
1797 g_return_if_fail(IS_COLLECTION(collection
));
1798 g_return_if_fail(item
>= 0 &&
1799 (item
== 0 || item
< collection
->number_of_items
));
1801 widget
= GTK_WIDGET(collection
);
1802 if (!GTK_WIDGET_REALIZED(widget
))
1805 col
= item
% collection
->columns
;
1806 row
= item
/ collection
->columns
;
1807 scroll
= collection
->vadj
->value
; /* (round to int) */
1809 area
.x
= col
* collection
->item_width
;
1810 area_y
= row
* collection
->item_height
- scroll
;
1811 area
.width
= collection
->item_width
;
1812 area_height
= collection
->item_height
;
1814 if (area_y
+ area_height
< 0)
1817 gdk_window_get_size(widget
->window
, NULL
, &height
);
1819 if (area_y
> height
)
1823 area
.height
= area_height
;
1825 if (blank
|| collection
->lasso_box
)
1826 gdk_window_clear_area(widget
->window
,
1827 area
.x
, area
.y
, area
.width
, area
.height
);
1829 draw_one_item(collection
, item
, &area
);
1831 if (collection
->lasso_box
)
1833 gdk_gc_set_clip_rectangle(collection
->xor_gc
, &area
);
1834 draw_lasso_box(collection
);
1835 gdk_gc_set_clip_rectangle(collection
->xor_gc
, NULL
);
1839 void collection_set_item_size(Collection
*collection
, int width
, int height
)
1843 g_return_if_fail(collection
!= NULL
);
1844 g_return_if_fail(IS_COLLECTION(collection
));
1845 g_return_if_fail(width
> 4 && height
> 4);
1847 widget
= GTK_WIDGET(collection
);
1849 collection
->item_width
= width
;
1850 collection
->item_height
= height
;
1852 if (GTK_WIDGET_REALIZED(widget
))
1856 collection
->paint_level
= PAINT_CLEAR
;
1857 gdk_window_get_size(widget
->window
, &window_width
, NULL
);
1858 collection
->columns
= MAX(window_width
/ collection
->item_width
,
1861 set_vadjustment(collection
);
1862 if (collection
->cursor_item
!= -1)
1863 scroll_to_show(collection
, collection
->cursor_item
);
1864 gtk_widget_queue_draw(widget
);
1868 /* Cursor is positioned on item with the same data as before the sort.
1869 * Same for the wink item.
1871 void collection_qsort(Collection
*collection
,
1872 int (*compar
)(const void *, const void *))
1874 int cursor
, wink
, items
;
1875 gpointer cursor_data
= NULL
;
1876 gpointer wink_data
= NULL
;
1878 g_return_if_fail(collection
!= NULL
);
1879 g_return_if_fail(IS_COLLECTION(collection
));
1880 g_return_if_fail(compar
!= NULL
);
1882 items
= collection
->number_of_items
;
1884 wink
= collection
->wink_item
;
1885 if (wink
>= 0 && wink
< items
)
1886 wink_data
= collection
->items
[wink
].data
;
1890 cursor
= collection
->cursor_item
;
1891 if (cursor
>= 0 && cursor
< items
)
1892 cursor_data
= collection
->items
[cursor
].data
;
1896 if (collection
->wink_item
!= -1)
1898 collection
->wink_item
= -1;
1899 gtk_timeout_remove(collection
->wink_timeout
);
1902 qsort(collection
->items
, items
, sizeof(collection
->items
[0]), compar
);
1904 if (cursor
> -1 || wink
> -1)
1908 for (item
= 0; item
< items
; item
++)
1910 if (collection
->items
[item
].data
== cursor_data
)
1911 collection_set_cursor_item(collection
, item
);
1912 if (collection
->items
[item
].data
== wink_data
)
1913 collection_wink_item(collection
, item
);
1917 collection
->paint_level
= PAINT_CLEAR
;
1919 gtk_widget_queue_draw(GTK_WIDGET(collection
));
1922 /* Find an item in an unsorted collection.
1923 * Returns the item number, or -1 if not found.
1925 int collection_find_item(Collection
*collection
, gpointer data
,
1926 int (*compar
)(const void *, const void *))
1930 g_return_val_if_fail(collection
!= NULL
, -1);
1931 g_return_val_if_fail(IS_COLLECTION(collection
), -1);
1932 g_return_val_if_fail(compar
!= NULL
, -1);
1934 for (i
= 0; i
< collection
->number_of_items
; i
++)
1935 if (compar(&collection
->items
[i
].data
, &data
) == 0)
1941 /* The collection may be in either normal mode or panel mode.
1943 * - a single click calls open_item
1944 * - items are never selected
1945 * - lasso boxes are disabled
1947 void collection_set_panel(Collection
*collection
, gboolean panel
)
1949 g_return_if_fail(collection
!= NULL
);
1950 g_return_if_fail(IS_COLLECTION(collection
));
1952 collection
->panel
= panel
== TRUE
;
1954 if (collection
->panel
)
1956 collection_clear_selection(collection
);
1957 if (collection
->lasso_box
)
1958 remove_lasso_box(collection
);
1962 /* Return the number of the item under the point (x,y), or -1 for none.
1963 * This may call your test_point callback. The point is relative to the
1964 * collection's origin.
1966 int collection_get_item(Collection
*collection
, int x
, int y
)
1972 g_return_val_if_fail(collection
!= NULL
, -1);
1974 scroll
= collection
->vadj
->value
;
1975 col
= x
/ collection
->item_width
;
1976 row
= (y
+ scroll
) / collection
->item_height
;
1978 if (col
< 0 || row
< 0 || col
>= collection
->columns
)
1981 item
= col
+ row
* collection
->columns
;
1982 if (item
>= collection
->number_of_items
1984 !collection
->test_point(collection
,
1985 x
- col
* collection
->item_width
,
1986 y
- row
* collection
->item_height
1988 &collection
->items
[item
],
1989 collection
->item_width
,
1990 collection
->item_height
))
1998 /* Set the cursor/highlight over the given item. Passing -1
1999 * hides the cursor. As a special case, you may set the cursor item
2000 * to zero when there are no items.
2002 void collection_set_cursor_item(Collection
*collection
, gint item
)
2006 g_return_if_fail(collection
!= NULL
);
2007 g_return_if_fail(IS_COLLECTION(collection
));
2008 g_return_if_fail(item
>= -1 &&
2009 (item
< collection
->number_of_items
|| item
== 0));
2011 old_item
= collection
->cursor_item
;
2013 if (old_item
== item
)
2016 collection
->cursor_item
= item
;
2019 collection_draw_item(collection
, old_item
, TRUE
);
2023 collection_draw_item(collection
, item
, TRUE
);
2024 scroll_to_show(collection
, item
);
2028 /* Briefly highlight an item to draw the user's attention to it.
2029 * -1 cancels the effect, as does deleting items, sorting the collection
2030 * or starting a new wink effect.
2031 * Otherwise, the effect will cancel itself after a short pause.
2033 void collection_wink_item(Collection
*collection
, gint item
)
2035 g_return_if_fail(collection
!= NULL
);
2036 g_return_if_fail(IS_COLLECTION(collection
));
2037 g_return_if_fail(item
>= -1 && item
< collection
->number_of_items
);
2039 if (collection
->wink_item
!= -1)
2040 cancel_wink(collection
);
2044 collection
->wink_item
= item
;
2045 collection
->wink_timeout
= gtk_timeout_add(300,
2046 (GtkFunction
) cancel_wink_timeout
,
2048 collection_draw_item(collection
, item
, TRUE
);
2049 scroll_to_show(collection
, item
);
2054 /* Call test(item, data) on each item in the collection.
2055 * Remove all items for which it returns TRUE. test() should
2056 * free the data before returning TRUE. The collection is in an
2057 * inconsistant state during this call (ie, when test() is called).
2059 void collection_delete_if(Collection
*collection
,
2060 gboolean (*test
)(gpointer item
, gpointer data
),
2067 g_return_if_fail(collection
!= NULL
);
2068 g_return_if_fail(IS_COLLECTION(collection
));
2069 g_return_if_fail(test
!= NULL
);
2071 cursor
= collection
->cursor_item
;
2073 for (in
= 0; in
< collection
->number_of_items
; in
++)
2075 if (!test(collection
->items
[in
].data
, data
))
2077 if (collection
->items
[in
].selected
)
2079 collection
->items
[out
].selected
= TRUE
;
2083 collection
->items
[out
].selected
= FALSE
;
2085 collection
->items
[out
++].data
=
2086 collection
->items
[in
].data
;
2088 else if (cursor
>= in
)
2094 collection
->cursor_item
= cursor
;
2096 if (collection
->wink_item
!= -1)
2098 collection
->wink_item
= -1;
2099 gtk_timeout_remove(collection
->wink_timeout
);
2102 collection
->number_of_items
= out
;
2103 collection
->number_selected
= selected
;
2104 resize_arrays(collection
,
2105 MAX(collection
->number_of_items
, MINIMUM_ITEMS
));
2107 collection
->paint_level
= PAINT_CLEAR
;
2109 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection
)))
2111 set_vadjustment(collection
);
2112 gtk_widget_queue_draw(GTK_WIDGET(collection
));
2117 /* Display a cross-hair pointer and the next time an item is clicked,
2118 * call the callback function. If the background is clicked or NULL
2119 * is passed as the callback then revert to normal operation.
2121 void collection_target(Collection
*collection
,
2122 CollectionTargetFunc callback
,
2125 g_return_if_fail(collection
!= NULL
);
2126 g_return_if_fail(IS_COLLECTION(collection
));
2128 if (callback
!= collection
->target_cb
)
2129 gdk_window_set_cursor(GTK_WIDGET(collection
)->window
,
2130 callback
? crosshair
: NULL
);
2132 collection
->target_cb
= callback
;
2133 collection
->target_data
= user_data
;
2135 if (collection
->cursor_item
!= -1)
2136 collection_draw_item(collection
, collection
->cursor_item
,
2140 /* Move the cursor by the given row and column offsets. */
2141 void collection_move_cursor(Collection
*collection
, int drow
, int dcol
)
2144 int first
, last
, total_rows
;
2146 g_return_if_fail(collection
!= NULL
);
2147 g_return_if_fail(IS_COLLECTION(collection
));
2149 get_visible_limits(collection
, &first
, &last
);
2151 item
= collection
->cursor_item
;
2159 row
= item
/ collection
->columns
;
2160 col
= item
% collection
->columns
+ dcol
;
2164 else if (row
> last
)
2167 row
= MAX(row
+ drow
, 0);
2170 total_rows
= (collection
->number_of_items
+ collection
->columns
- 1)
2171 / collection
->columns
;
2173 if (row
>= total_rows
- 1 && drow
> 0)
2175 row
= total_rows
- 1;
2176 item
= col
+ row
* collection
->columns
;
2177 if (item
>= collection
->number_of_items
)
2180 scroll_to_show(collection
, item
);
2186 item
= col
+ row
* collection
->columns
;
2188 if (item
>= 0 && item
< collection
->number_of_items
)
2189 collection_set_cursor_item(collection
, item
);