4 * Collection - a GTK+ widget
5 * Copyright (C) 2000, 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
);
143 static void draw_focus(GtkWidget
*widget
);
145 static void draw_focus_at(Collection
*collection
, GdkRectangle
*area
)
150 widget
= GTK_WIDGET(collection
);
152 if (collection
->target_cb
)
153 gc
= widget
->style
->white_gc
;
154 else if (GTK_WIDGET_FLAGS(widget
) & GTK_HAS_FOCUS
)
155 gc
= widget
->style
->black_gc
;
157 gc
= widget
->style
->fg_gc
[GTK_STATE_INSENSITIVE
];
159 gdk_draw_rectangle(widget
->window
, gc
, FALSE
,
160 area
->x
+ 1, area
->y
+ 1,
165 static void draw_one_item(Collection
*collection
, int item
, GdkRectangle
*area
)
167 if (item
< collection
->number_of_items
)
169 collection
->draw_item((GtkWidget
*) collection
,
170 &collection
->items
[item
],
172 if (item
== collection
->wink_item
)
173 gdk_draw_rectangle(((GtkWidget
*) collection
)->window
,
174 ((GtkWidget
*) collection
)->style
->black_gc
,
177 area
->width
- 1, area
->height
- 1);
180 if (item
== collection
->cursor_item
)
181 draw_focus_at(collection
, area
);
184 static void draw_focus(GtkWidget
*widget
)
186 Collection
*collection
;
188 g_return_if_fail(widget
!= NULL
);
189 g_return_if_fail(IS_COLLECTION(widget
));
191 collection
= COLLECTION(widget
);
193 if (collection
->cursor_item
< 0 || !GTK_WIDGET_REALIZED(widget
))
196 collection_draw_item(collection
, collection
->cursor_item
, FALSE
);
199 GtkType
collection_get_type(void)
201 static guint my_type
= 0;
205 static const GtkTypeInfo my_info
=
209 sizeof(CollectionClass
),
210 (GtkClassInitFunc
) collection_class_init
,
211 (GtkObjectInitFunc
) collection_init
,
212 NULL
, /* Reserved 1 */
213 NULL
, /* Reserved 2 */
214 (GtkClassInitFunc
) NULL
/* base_class_init_func */
217 my_type
= gtk_type_unique(gtk_widget_get_type(),
224 static void collection_class_init(CollectionClass
*class)
226 GtkObjectClass
*object_class
;
227 GtkWidgetClass
*widget_class
;
229 object_class
= (GtkObjectClass
*) class;
230 widget_class
= (GtkWidgetClass
*) class;
232 parent_class
= gtk_type_class(gtk_widget_get_type());
234 gtk_object_add_arg_type("Collection::vadjustment",
236 GTK_ARG_READWRITE
| GTK_ARG_CONSTRUCT
,
239 object_class
->destroy
= collection_destroy
;
240 object_class
->finalize
= collection_finalize
;
242 widget_class
->realize
= collection_realize
;
243 widget_class
->draw
= collection_draw
;
244 widget_class
->expose_event
= collection_expose
;
245 widget_class
->size_request
= collection_size_request
;
246 widget_class
->size_allocate
= collection_size_allocate
;
247 widget_class
->style_set
= collection_set_style
;
249 widget_class
->key_press_event
= collection_key_press
;
250 widget_class
->button_press_event
= collection_button_press
;
251 widget_class
->button_release_event
= collection_button_release
;
252 widget_class
->motion_notify_event
= collection_motion_notify
;
253 widget_class
->focus_in_event
= focus_in
;
254 widget_class
->focus_out_event
= focus_out
;
255 widget_class
->draw_focus
= draw_focus
;
257 object_class
->set_arg
= collection_set_arg
;
258 object_class
->get_arg
= collection_get_arg
;
260 class->open_item
= NULL
;
261 class->drag_selection
= NULL
;
262 class->show_menu
= NULL
;
263 class->gain_selection
= NULL
;
264 class->lose_selection
= NULL
;
266 collection_signals
[OPEN_ITEM
] = gtk_signal_new("open_item",
269 GTK_SIGNAL_OFFSET(CollectionClass
,
271 gtk_marshal_NONE__POINTER_UINT
,
275 collection_signals
[DRAG_SELECTION
] = gtk_signal_new("drag_selection",
278 GTK_SIGNAL_OFFSET(CollectionClass
,
280 gtk_marshal_NONE__POINTER_UINT
,
284 collection_signals
[SHOW_MENU
] = gtk_signal_new("show_menu",
287 GTK_SIGNAL_OFFSET(CollectionClass
,
289 gtk_marshal_NONE__POINTER_INT
,
293 collection_signals
[GAIN_SELECTION
] = gtk_signal_new("gain_selection",
296 GTK_SIGNAL_OFFSET(CollectionClass
,
298 gtk_marshal_NONE__UINT
,
301 collection_signals
[LOSE_SELECTION
] = gtk_signal_new("lose_selection",
304 GTK_SIGNAL_OFFSET(CollectionClass
,
306 gtk_marshal_NONE__UINT
,
310 gtk_object_class_add_signals(object_class
,
311 collection_signals
, LAST_SIGNAL
);
314 static void collection_init(Collection
*object
)
316 g_return_if_fail(object
!= NULL
);
317 g_return_if_fail(IS_COLLECTION(object
));
320 crosshair
= gdk_cursor_new(GDK_CROSSHAIR
);
322 GTK_WIDGET_SET_FLAGS(GTK_WIDGET(object
), GTK_CAN_FOCUS
);
324 object
->panel
= FALSE
;
325 object
->number_of_items
= 0;
326 object
->number_selected
= 0;
328 object
->item_width
= 64;
329 object
->item_height
= 64;
331 object
->paint_level
= PAINT_OVERWRITE
;
332 object
->last_scroll
= 0;
333 object
->bg_gc
= NULL
;
335 object
->items
= g_malloc(sizeof(CollectionItem
) * MINIMUM_ITEMS
);
336 object
->cursor_item
= -1;
337 object
->cursor_item_old
= -1;
338 object
->wink_item
= -1;
339 object
->array_size
= MINIMUM_ITEMS
;
340 object
->draw_item
= default_draw_item
;
341 object
->test_point
= default_test_point
;
343 object
->buttons_pressed
= 0;
344 object
->may_drag
= FALSE
;
349 GtkWidget
* collection_new(GtkAdjustment
*vadj
)
352 g_return_val_if_fail(GTK_IS_ADJUSTMENT(vadj
), NULL
);
354 return GTK_WIDGET(gtk_widget_new(collection_get_type(),
359 void collection_set_functions(Collection
*collection
,
360 CollectionDrawFunc draw_item
,
361 CollectionTestFunc test_point
)
365 g_return_if_fail(collection
!= NULL
);
366 g_return_if_fail(IS_COLLECTION(collection
));
368 widget
= GTK_WIDGET(collection
);
371 draw_item
= default_draw_item
;
373 test_point
= default_test_point
;
375 collection
->draw_item
= draw_item
;
376 collection
->test_point
= test_point
;
378 if (GTK_WIDGET_REALIZED(widget
))
380 collection
->paint_level
= PAINT_CLEAR
;
381 gtk_widget_queue_clear(widget
);
385 /* After this we are unusable, but our data (if any) is still hanging around.
386 * It will be freed later with finalize.
388 static void collection_destroy(GtkObject
*object
)
390 Collection
*collection
;
392 g_return_if_fail(object
!= NULL
);
393 g_return_if_fail(IS_COLLECTION(object
));
395 collection
= COLLECTION(object
);
397 if (collection
->wink_item
!= -1)
399 collection
->wink_item
= -1;
400 gtk_timeout_remove(collection
->wink_timeout
);
403 gtk_signal_disconnect_by_data(GTK_OBJECT(collection
->vadj
),
406 if (collection
->bg_gc
)
408 gdk_gc_destroy(collection
->bg_gc
);
409 collection
->bg_gc
= NULL
;
412 if (GTK_OBJECT_CLASS(parent_class
)->destroy
)
413 (*GTK_OBJECT_CLASS(parent_class
)->destroy
)(object
);
416 /* This is the last thing that happens to us. Free all data. */
417 static void collection_finalize(GtkObject
*object
)
419 Collection
*collection
;
421 collection
= COLLECTION(object
);
423 if (collection
->vadj
)
425 gtk_object_unref(GTK_OBJECT(collection
->vadj
));
428 g_free(collection
->items
);
431 static GdkGC
*create_bg_gc(GtkWidget
*widget
)
435 values
.tile
= widget
->style
->bg_pixmap
[GTK_STATE_INSENSITIVE
];
436 values
.fill
= GDK_TILED
;
438 return gdk_gc_new_with_values(widget
->window
, &values
,
439 GDK_GC_FILL
| GDK_GC_TILE
);
442 static void collection_realize(GtkWidget
*widget
)
444 Collection
*collection
;
445 GdkWindowAttr attributes
;
446 gint attributes_mask
;
447 GdkGCValues xor_values
;
449 g_return_if_fail(widget
!= NULL
);
450 g_return_if_fail(IS_COLLECTION(widget
));
451 g_return_if_fail(widget
->parent
!= NULL
);
453 GTK_WIDGET_SET_FLAGS(widget
, GTK_REALIZED
);
454 collection
= COLLECTION(widget
);
456 attributes
.x
= widget
->allocation
.x
;
457 attributes
.y
= widget
->allocation
.y
;
458 attributes
.width
= widget
->allocation
.width
;
459 attributes
.height
= widget
->allocation
.height
;
460 attributes
.wclass
= GDK_INPUT_OUTPUT
;
461 attributes
.window_type
= GDK_WINDOW_CHILD
;
462 attributes
.event_mask
= gtk_widget_get_events(widget
) |
464 GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK
|
465 GDK_BUTTON1_MOTION_MASK
| GDK_BUTTON2_MOTION_MASK
|
466 GDK_BUTTON3_MOTION_MASK
;
467 attributes
.visual
= gtk_widget_get_visual(widget
);
468 attributes
.colormap
= gtk_widget_get_colormap(widget
);
470 attributes_mask
= GDK_WA_X
| GDK_WA_Y
|
471 GDK_WA_VISUAL
| GDK_WA_COLORMAP
;
472 widget
->window
= gdk_window_new(widget
->parent
->window
,
473 &attributes
, attributes_mask
);
475 widget
->style
= gtk_style_attach(widget
->style
, widget
->window
);
477 gdk_window_set_user_data(widget
->window
, widget
);
479 gdk_window_set_background(widget
->window
,
480 &widget
->style
->base
[GTK_STATE_INSENSITIVE
]);
481 if (widget
->style
->bg_pixmap
[GTK_STATE_INSENSITIVE
])
482 collection
->bg_gc
= create_bg_gc(widget
);
484 /* Try to stop everything flickering horribly */
485 gdk_window_set_static_gravities(widget
->window
, TRUE
);
487 set_vadjustment(collection
);
489 xor_values
.function
= GDK_XOR
;
490 xor_values
.foreground
.red
= 0xffff;
491 xor_values
.foreground
.green
= 0xffff;
492 xor_values
.foreground
.blue
= 0xffff;
493 gdk_color_alloc(gtk_widget_get_colormap(widget
),
494 &xor_values
.foreground
);
495 collection
->xor_gc
= gdk_gc_new_with_values(widget
->window
,
501 static void collection_size_request(GtkWidget
*widget
,
502 GtkRequisition
*requisition
)
504 requisition
->width
= MIN_WIDTH
;
505 requisition
->height
= MIN_HEIGHT
;
508 static void collection_size_allocate(GtkWidget
*widget
,
509 GtkAllocation
*allocation
)
511 Collection
*collection
;
514 g_return_if_fail(widget
!= NULL
);
515 g_return_if_fail(IS_COLLECTION(widget
));
516 g_return_if_fail(allocation
!= NULL
);
518 collection
= COLLECTION(widget
);
520 old_columns
= collection
->columns
;
521 if (widget
->allocation
.x
!= allocation
->x
522 || widget
->allocation
.y
!= allocation
->y
)
523 collection
->paint_level
= PAINT_CLEAR
;
525 widget
->allocation
= *allocation
;
527 collection
->columns
= allocation
->width
/ collection
->item_width
;
528 if (collection
->columns
< 1)
529 collection
->columns
= 1;
531 if (GTK_WIDGET_REALIZED(widget
))
533 gdk_window_move_resize(widget
->window
,
534 allocation
->x
, allocation
->y
,
535 allocation
->width
, allocation
->height
);
537 if (old_columns
!= collection
->columns
)
539 collection
->paint_level
= PAINT_CLEAR
;
540 gtk_widget_queue_clear(widget
);
543 set_vadjustment(collection
);
545 if (collection
->cursor_item
!= -1)
546 scroll_to_show(collection
, collection
->cursor_item
);
550 static void collection_set_style(GtkWidget
*widget
,
551 GtkStyle
*previous_style
)
553 Collection
*collection
;
555 g_return_if_fail(IS_COLLECTION(widget
));
557 collection
= COLLECTION(widget
);
559 collection
->paint_level
= PAINT_CLEAR
;
561 if (GTK_WIDGET_REALIZED(widget
))
563 gdk_window_set_background(widget
->window
,
564 &widget
->style
->base
[GTK_STATE_INSENSITIVE
]);
566 if (collection
->bg_gc
)
568 gdk_gc_destroy(collection
->bg_gc
);
569 collection
->bg_gc
= NULL
;
572 if (widget
->style
->bg_pixmap
[GTK_STATE_INSENSITIVE
])
573 collection
->bg_gc
= create_bg_gc(widget
);
577 static void clear_area(Collection
*collection
, GdkRectangle
*area
)
579 GtkWidget
*widget
= GTK_WIDGET(collection
);
580 int scroll
= collection
->vadj
->value
;
582 if (collection
->bg_gc
)
584 gdk_gc_set_ts_origin(collection
->bg_gc
, 0, -scroll
);
586 gdk_draw_rectangle(widget
->window
,
590 area
->width
, area
->height
);
593 gdk_window_clear_area(widget
->window
,
594 area
->x
, area
->y
, area
->width
, area
->height
);
597 static gint
collection_paint(Collection
*collection
,
600 GdkRectangle whole
, item_area
;
605 int start_row
, last_row
;
606 int start_col
, last_col
;
610 scroll
= collection
->vadj
->value
;
612 widget
= GTK_WIDGET(collection
);
614 if (collection
->paint_level
> PAINT_NORMAL
|| area
== NULL
)
617 gdk_window_get_size(widget
->window
, &width
, &height
);
622 whole
.height
= height
;
626 if (collection
->paint_level
== PAINT_CLEAR
627 && !collection
->lasso_box
)
628 clear_area(collection
, area
);
630 collection
->paint_level
= PAINT_NORMAL
;
633 /* Calculate the ranges to plot */
634 start_row
= (area
->y
+ scroll
) / collection
->item_height
;
635 last_row
= (area
->y
+ area
->height
- 1 + scroll
)
636 / collection
->item_height
;
639 start_col
= area
->x
/ collection
->item_width
;
640 phys_last_col
= (area
->x
+ area
->width
- 1) / collection
->item_width
;
642 if (collection
->lasso_box
)
644 /* You can't be too careful with lasso boxes...
646 * clip gives the total area drawn over (this may be larger
647 * than the requested area). It's used to redraw the lasso
650 clip
.x
= start_col
* collection
->item_width
;
651 clip
.y
= start_row
* collection
->item_height
- scroll
;
652 clip
.width
= (phys_last_col
- start_col
+ 1)
653 * collection
->item_width
;
654 clip
.height
= (last_row
- start_row
+ 1)
655 * collection
->item_height
;
657 clear_area(collection
, &clip
);
660 if (start_col
< collection
->columns
)
662 if (phys_last_col
>= collection
->columns
)
663 last_col
= collection
->columns
- 1;
665 last_col
= phys_last_col
;
669 item
= row
* collection
->columns
+ col
;
670 item_area
.width
= collection
->item_width
;
671 item_area
.height
= collection
->item_height
;
673 while ((item
== 0 || item
< collection
->number_of_items
)
676 item_area
.x
= col
* collection
->item_width
;
677 item_area
.y
= row
* collection
->item_height
- scroll
;
679 draw_one_item(collection
, item
, &item_area
);
686 item
= row
* collection
->columns
+ col
;
693 if (collection
->lasso_box
)
695 gdk_gc_set_clip_rectangle(collection
->xor_gc
, &clip
);
696 draw_lasso_box(collection
);
697 gdk_gc_set_clip_rectangle(collection
->xor_gc
, NULL
);
703 static void default_draw_item( GtkWidget
*widget
,
704 CollectionItem
*item
,
707 gdk_draw_arc(widget
->window
,
708 item
->selected
? widget
->style
->white_gc
709 : widget
->style
->black_gc
,
712 area
->width
, area
->height
,
717 static gboolean
default_test_point(Collection
*collection
,
718 int point_x
, int point_y
,
719 CollectionItem
*item
,
720 int width
, int height
)
724 /* Convert to point in unit circle */
725 f_x
= ((float) point_x
/ width
) - 0.5;
726 f_y
= ((float) point_y
/ height
) - 0.5;
728 return (f_x
* f_x
) + (f_y
* f_y
) <= .25;
731 static void collection_set_arg( GtkObject
*object
,
735 Collection
*collection
;
737 collection
= COLLECTION(object
);
741 case ARG_VADJUSTMENT
:
742 collection_set_adjustment(collection
,
743 GTK_VALUE_POINTER(*arg
));
750 static void collection_set_adjustment( Collection
*collection
,
754 g_return_if_fail (GTK_IS_ADJUSTMENT (vadj
));
756 vadj
= GTK_ADJUSTMENT(gtk_adjustment_new(0.0,
759 if (collection
->vadj
&& (collection
->vadj
!= vadj
))
761 gtk_signal_disconnect_by_data(GTK_OBJECT(collection
->vadj
),
763 gtk_object_unref(GTK_OBJECT(collection
->vadj
));
766 if (collection
->vadj
!= vadj
)
768 collection
->vadj
= vadj
;
769 gtk_object_ref(GTK_OBJECT(collection
->vadj
));
770 gtk_object_sink(GTK_OBJECT(collection
->vadj
));
772 gtk_signal_connect(GTK_OBJECT(collection
->vadj
),
774 (GtkSignalFunc
) collection_adjustment
,
776 gtk_signal_connect(GTK_OBJECT(collection
->vadj
),
778 (GtkSignalFunc
) collection_adjustment
,
780 gtk_signal_connect(GTK_OBJECT(collection
->vadj
),
782 (GtkSignalFunc
) collection_disconnect
,
784 collection_adjustment(vadj
, collection
);
788 static void collection_get_arg( GtkObject
*object
,
792 Collection
*collection
;
794 collection
= COLLECTION(object
);
798 case ARG_VADJUSTMENT
:
799 GTK_VALUE_POINTER(*arg
) = collection
->vadj
;
802 arg
->type
= GTK_TYPE_INVALID
;
807 /* Something about the adjustment has changed */
808 static void collection_adjustment(GtkAdjustment
*adjustment
,
809 Collection
*collection
)
813 g_return_if_fail(adjustment
!= NULL
);
814 g_return_if_fail(GTK_IS_ADJUSTMENT(adjustment
));
815 g_return_if_fail(collection
!= NULL
);
816 g_return_if_fail(IS_COLLECTION(collection
));
818 diff
= ((gint
) adjustment
->value
) - collection
->last_scroll
;
822 collection
->last_scroll
= adjustment
->value
;
824 scroll_by(collection
, diff
);
828 static void collection_disconnect(GtkAdjustment
*adjustment
,
829 Collection
*collection
)
831 g_return_if_fail(adjustment
!= NULL
);
832 g_return_if_fail(GTK_IS_ADJUSTMENT(adjustment
));
833 g_return_if_fail(collection
!= NULL
);
834 g_return_if_fail(IS_COLLECTION(collection
));
836 collection_set_adjustment(collection
, NULL
);
839 static void set_vadjustment(Collection
*collection
)
845 widget
= GTK_WIDGET(collection
);
847 if (!GTK_WIDGET_REALIZED(widget
))
850 gdk_window_get_size(widget
->window
, NULL
, &height
);
851 cols
= collection
->columns
;
852 rows
= (collection
->number_of_items
+ cols
- 1) / cols
;
854 collection
->vadj
->lower
= 0.0;
855 collection
->vadj
->upper
= collection
->item_height
* rows
;
857 collection
->vadj
->step_increment
=
858 MIN(collection
->vadj
->upper
, collection
->item_height
/ 4);
860 collection
->vadj
->page_increment
=
861 MIN(collection
->vadj
->upper
,
864 collection
->vadj
->page_size
= MIN(collection
->vadj
->upper
, height
);
866 collection
->vadj
->value
= MIN(collection
->vadj
->value
,
867 collection
->vadj
->upper
- collection
->vadj
->page_size
);
869 collection
->vadj
->value
= MAX(collection
->vadj
->value
, 0.0);
871 gtk_signal_emit_by_name(GTK_OBJECT(collection
->vadj
), "changed");
874 static void collection_draw(GtkWidget
*widget
, GdkRectangle
*area
)
876 Collection
*collection
;
878 g_return_if_fail(widget
!= NULL
);
879 g_return_if_fail(IS_COLLECTION(widget
));
880 g_return_if_fail(area
!= NULL
); /* Not actually used */
882 collection
= COLLECTION(widget
);
884 /* This doesn't always work - I think Gtk+ may be doing some
885 * kind of expose-event compression...
886 if (collection->paint_level > PAINT_NORMAL)
888 collection_paint(collection
, area
);
891 static gint
collection_expose(GtkWidget
*widget
, GdkEventExpose
*event
)
893 Collection
*collection
;
895 g_return_val_if_fail(widget
!= NULL
, FALSE
);
896 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
897 g_return_val_if_fail(event
!= NULL
, FALSE
);
899 collection
= COLLECTION(widget
);
901 clear_area(collection
, &event
->area
);
903 collection_paint(collection
, &event
->area
);
908 /* Positive makes the contents go move up the screen */
909 static void scroll_by(Collection
*collection
, gint diff
)
915 GdkRectangle new_area
;
920 widget
= GTK_WIDGET(collection
);
922 if (collection
->lasso_box
)
923 remove_lasso_box(collection
);
925 gdk_window_get_size(widget
->window
, &width
, &height
);
927 new_area
.width
= width
;
934 new_area
.y
= height
- amount
;
944 new_area
.height
= amount
;
948 gdk_draw_pixmap(widget
->window
,
949 widget
->style
->white_gc
,
957 /* We have to redraw everything because any pending
958 * expose events now contain invalid areas.
959 * Don't need to clear the area first though...
961 if (collection
->paint_level
< PAINT_OVERWRITE
)
962 collection
->paint_level
= PAINT_OVERWRITE
;
965 collection
->paint_level
= PAINT_CLEAR
;
967 clear_area(collection
, &new_area
);
968 collection_paint(collection
, NULL
);
971 static void resize_arrays(Collection
*collection
, guint new_size
)
973 g_return_if_fail(collection
!= NULL
);
974 g_return_if_fail(IS_COLLECTION(collection
));
975 g_return_if_fail(new_size
>= collection
->number_of_items
);
977 collection
->items
= g_realloc(collection
->items
,
978 sizeof(CollectionItem
) * new_size
);
979 collection
->array_size
= new_size
;
982 static void return_pressed(Collection
*collection
)
984 int item
= collection
->cursor_item
;
985 CollectionTargetFunc cb
= collection
->target_cb
;
986 gpointer data
= collection
->target_data
;
988 collection_target(collection
, NULL
, NULL
);
989 if (item
< 0 || item
>= collection
->number_of_items
)
994 cb(collection
, item
, data
);
998 gtk_signal_emit(GTK_OBJECT(collection
),
999 collection_signals
[OPEN_ITEM
],
1000 collection
->items
[item
].data
,
1004 static gint
collection_key_press(GtkWidget
*widget
, GdkEventKey
*event
)
1006 Collection
*collection
;
1009 g_return_val_if_fail(widget
!= NULL
, FALSE
);
1010 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
1011 g_return_val_if_fail(event
!= NULL
, FALSE
);
1013 collection
= (Collection
*) widget
;
1014 item
= collection
->cursor_item
;
1016 switch (event
->keyval
)
1019 collection_move_cursor(collection
, 0, -1);
1022 collection_move_cursor(collection
, 0, 1);
1025 collection_move_cursor(collection
, -1, 0);
1028 collection_move_cursor(collection
, 1, 0);
1031 collection_set_cursor_item(collection
, 0);
1034 collection_set_cursor_item(collection
,
1035 MAX((gint
) collection
->number_of_items
- 1, 0));
1038 collection_move_cursor(collection
, -10, 0);
1041 collection_move_cursor(collection
, 10, 0);
1044 return_pressed(collection
);
1047 if (!collection
->target_cb
)
1049 collection_set_cursor_item(collection
, -1);
1050 collection_clear_selection(collection
);
1051 return FALSE
; /* Pass it on */
1053 collection_target(collection
, NULL
, NULL
);
1056 if (item
>=0 && item
< collection
->number_of_items
)
1057 collection_toggle_item(collection
, item
);
1066 static gint
collection_button_press(GtkWidget
*widget
,
1067 GdkEventButton
*event
)
1069 Collection
*collection
;
1076 g_return_val_if_fail(widget
!= NULL
, FALSE
);
1077 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
1078 g_return_val_if_fail(event
!= NULL
, FALSE
);
1080 collection
= COLLECTION(widget
);
1082 collection
->item_clicked
= -1;
1084 if (event
->button
> 3)
1088 /* Wheel mouse scrolling */
1089 if (event
->button
== 4)
1090 diff
= -((signed int) collection
->item_height
) / 4;
1091 else if (event
->button
== 5)
1092 diff
= collection
->item_height
/ 4;
1098 int old_value
= collection
->vadj
->value
;
1100 gboolean box
= collection
->lasso_box
;
1102 new_value
= CLAMP(old_value
+ diff
, 0.0,
1103 collection
->vadj
->upper
1104 - collection
->vadj
->page_size
);
1105 diff
= new_value
- old_value
;
1110 remove_lasso_box(collection
);
1111 collection
->drag_box_y
[0] -= diff
;
1113 collection
->vadj
->value
= new_value
;
1114 gtk_signal_emit_by_name(
1115 GTK_OBJECT(collection
->vadj
),
1118 add_lasso_box(collection
);
1124 if (collection
->cursor_item
!= -1)
1125 collection_set_cursor_item(collection
, -1);
1127 scroll
= collection
->vadj
->value
;
1129 if (event
->type
== GDK_BUTTON_PRESS
&&
1130 event
->button
!= collection_menu_button
)
1132 if (collection
->buttons_pressed
++ == 0)
1133 gtk_grab_add(widget
);
1135 return FALSE
; /* Ignore extra presses */
1138 if (event
->state
& GDK_CONTROL_MASK
)
1141 action
= event
->button
;
1143 /* Ignore all clicks while we are dragging a lasso box */
1144 if (collection
->lasso_box
)
1147 col
= event
->x
/ collection
->item_width
;
1148 row
= (event
->y
+ scroll
) / collection
->item_height
;
1150 if (col
< 0 || row
< 0 || col
>= collection
->columns
)
1154 item
= col
+ row
* collection
->columns
;
1155 if (item
>= collection
->number_of_items
1157 !collection
->test_point(collection
,
1158 event
->x
- col
* collection
->item_width
,
1159 event
->y
- row
* collection
->item_height
1161 &collection
->items
[item
],
1162 collection
->item_width
,
1163 collection
->item_height
))
1169 if (collection
->target_cb
)
1171 CollectionTargetFunc cb
= collection
->target_cb
;
1172 gpointer data
= collection
->target_data
;
1174 collection_target(collection
, NULL
, NULL
);
1175 if (collection
->buttons_pressed
)
1177 gtk_grab_remove(widget
);
1178 collection
->buttons_pressed
= 0;
1180 if (item
> -1 && event
->button
!= collection_menu_button
)
1181 cb(collection
, item
, data
);
1185 collection
->drag_box_x
[0] = event
->x
;
1186 collection
->drag_box_y
[0] = event
->y
;
1187 collection
->item_clicked
= item
;
1189 stacked_time
= current_event_time
;
1190 current_event_time
= event
->time
;
1192 if (event
->button
== collection_menu_button
)
1194 gtk_signal_emit(GTK_OBJECT(collection
),
1195 collection_signals
[SHOW_MENU
],
1199 else if (event
->type
== GDK_2BUTTON_PRESS
&& collection
->panel
)
1203 else if ((event
->type
== GDK_2BUTTON_PRESS
&& !collection_single_click
)
1204 || collection
->panel
)
1208 if (collection
->buttons_pressed
)
1210 gtk_grab_remove(widget
);
1211 collection
->buttons_pressed
= 0;
1213 collection_unselect_item(collection
, item
);
1214 gtk_signal_emit(GTK_OBJECT(collection
),
1215 collection_signals
[OPEN_ITEM
],
1216 collection
->items
[item
].data
,
1220 else if (event
->type
== GDK_BUTTON_PRESS
)
1222 collection
->may_drag
= event
->button
!= collection_menu_button
;
1228 if (!collection
->items
[item
].selected
)
1230 collection_select_item(collection
,
1232 collection_clear_except(collection
,
1237 collection_toggle_item(collection
, item
);
1239 else if (action
== 1)
1240 collection_clear_selection(collection
);
1243 current_event_time
= stacked_time
;
1247 static gint
collection_button_release(GtkWidget
*widget
,
1248 GdkEventButton
*event
)
1250 Collection
*collection
;
1254 int col
, start_col
, last_col
;
1260 g_return_val_if_fail(widget
!= NULL
, FALSE
);
1261 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
1262 g_return_val_if_fail(event
!= NULL
, FALSE
);
1264 collection
= COLLECTION(widget
);
1265 button
= event
->button
;
1267 scroll
= collection
->vadj
->value
;
1269 if (event
->button
> 3 || event
->button
== collection_menu_button
)
1271 if (collection
->buttons_pressed
== 0)
1273 if (--collection
->buttons_pressed
== 0)
1274 gtk_grab_remove(widget
);
1276 return FALSE
; /* Wait until ALL buttons are up */
1278 if (!collection
->lasso_box
)
1280 int item
= collection
->item_clicked
;
1282 if (collection_single_click
&& item
> -1
1283 && item
< collection
->number_of_items
1284 && (event
->state
& GDK_CONTROL_MASK
) == 0)
1286 int dx
= event
->x
- collection
->drag_box_x
[0];
1287 int dy
= event
->y
- collection
->drag_box_y
[0];
1289 if (ABS(dx
) + ABS(dy
) > 9)
1292 collection_unselect_item(collection
, item
);
1293 gtk_signal_emit(GTK_OBJECT(collection
),
1294 collection_signals
[OPEN_ITEM
],
1295 collection
->items
[item
].data
,
1302 remove_lasso_box(collection
);
1304 w
= collection
->item_width
;
1305 h
= collection
->item_height
;
1307 top
= collection
->drag_box_y
[0] + scroll
;
1308 bottom
= collection
->drag_box_y
[1] + scroll
;
1319 row
= MAX(top
/ h
, 0);
1320 last_row
= bottom
/ h
;
1322 top
= collection
->drag_box_x
[0]; /* (left) */
1323 bottom
= collection
->drag_box_x
[1];
1333 start_col
= MAX(top
/ w
, 0);
1334 last_col
= bottom
/ w
;
1335 if (last_col
>= collection
->columns
)
1336 last_col
= collection
->columns
- 1;
1338 stacked_time
= current_event_time
;
1339 current_event_time
= event
->time
;
1341 while (row
<= last_row
)
1344 item
= row
* collection
->columns
+ col
;
1345 while (col
<= last_col
)
1347 if (item
>= collection
->number_of_items
)
1349 current_event_time
= stacked_time
;
1354 collection_select_item(collection
, item
);
1356 collection_toggle_item(collection
, item
);
1363 current_event_time
= stacked_time
;
1368 static gint
collection_motion_notify(GtkWidget
*widget
,
1369 GdkEventMotion
*event
)
1371 Collection
*collection
;
1375 g_return_val_if_fail(widget
!= NULL
, FALSE
);
1376 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
1377 g_return_val_if_fail(event
!= NULL
, FALSE
);
1379 collection
= COLLECTION(widget
);
1381 if (collection
->buttons_pressed
== 0)
1384 stacked_time
= current_event_time
;
1385 current_event_time
= event
->time
;
1387 if (event
->window
!= widget
->window
)
1388 gdk_window_get_pointer(widget
->window
, &x
, &y
, NULL
);
1395 if (collection
->lasso_box
)
1397 int new_value
= 0, diff
;
1400 gdk_window_get_size(widget
->window
, NULL
, &height
);
1404 int old_value
= collection
->vadj
->value
;
1406 new_value
= MAX(old_value
+ y
/ 10, 0.0);
1407 diff
= new_value
- old_value
;
1409 else if (y
> height
)
1411 int old_value
= collection
->vadj
->value
;
1413 new_value
= MIN(old_value
+ (y
- height
) / 10,
1414 collection
->vadj
->upper
1415 - collection
->vadj
->page_size
);
1416 diff
= new_value
- old_value
;
1421 remove_lasso_box(collection
);
1422 collection
->drag_box_x
[1] = x
;
1423 collection
->drag_box_y
[1] = y
;
1427 collection
->drag_box_y
[0] -= diff
;
1428 collection
->vadj
->value
= new_value
;
1429 gtk_signal_emit_by_name(GTK_OBJECT(collection
->vadj
),
1432 add_lasso_box(collection
);
1434 else if (collection
->may_drag
)
1436 int dx
= x
- collection
->drag_box_x
[0];
1437 int dy
= y
- collection
->drag_box_y
[0];
1439 if (abs(dx
) > 9 || abs(dy
) > 9)
1442 int scroll
= collection
->vadj
->value
;
1444 collection
->may_drag
= FALSE
;
1446 col
= collection
->drag_box_x
[0]
1447 / collection
->item_width
;
1448 row
= (collection
->drag_box_y
[0] + scroll
)
1449 / collection
->item_height
;
1450 item
= item_at_row_col(collection
, row
, col
);
1452 if (item
!= -1 && collection
->test_point(collection
,
1453 collection
->drag_box_x
[0] -
1454 col
* collection
->item_width
,
1455 collection
->drag_box_y
[0]
1456 - row
* collection
->item_height
1458 &collection
->items
[item
],
1459 collection
->item_width
,
1460 collection
->item_height
))
1462 collection
->buttons_pressed
= 0;
1463 gtk_grab_remove(widget
);
1464 collection_select_item(collection
, item
);
1465 gtk_signal_emit(GTK_OBJECT(collection
),
1466 collection_signals
[DRAG_SELECTION
],
1468 collection
->number_selected
);
1472 collection
->drag_box_x
[1] = x
;
1473 collection
->drag_box_y
[1] = y
;
1474 add_lasso_box(collection
);
1479 current_event_time
= stacked_time
;
1483 static void add_lasso_box(Collection
*collection
)
1485 g_return_if_fail(collection
!= NULL
);
1486 g_return_if_fail(IS_COLLECTION(collection
));
1487 g_return_if_fail(collection
->lasso_box
== FALSE
);
1489 collection
->lasso_box
= TRUE
;
1490 draw_lasso_box(collection
);
1493 static void draw_lasso_box(Collection
*collection
)
1496 int x
, y
, width
, height
;
1498 widget
= GTK_WIDGET(collection
);
1500 x
= MIN(collection
->drag_box_x
[0], collection
->drag_box_x
[1]);
1501 y
= MIN(collection
->drag_box_y
[0], collection
->drag_box_y
[1]);
1502 width
= abs(collection
->drag_box_x
[1] - collection
->drag_box_x
[0]);
1503 height
= abs(collection
->drag_box_y
[1] - collection
->drag_box_y
[0]);
1505 gdk_draw_rectangle(widget
->window
, collection
->xor_gc
, FALSE
,
1506 x
, y
, width
, height
);
1509 static void remove_lasso_box(Collection
*collection
)
1511 g_return_if_fail(collection
!= NULL
);
1512 g_return_if_fail(IS_COLLECTION(collection
));
1513 g_return_if_fail(collection
->lasso_box
== TRUE
);
1515 draw_lasso_box(collection
);
1517 collection
->lasso_box
= FALSE
;
1522 /* Convert a row,col address to an item number, or -1 if none */
1523 static int item_at_row_col(Collection
*collection
, int row
, int col
)
1527 if (row
< 0 || col
< 0 || col
>= collection
->columns
)
1530 item
= col
+ row
* collection
->columns
;
1532 if (item
>= collection
->number_of_items
)
1537 /* Make sure that 'item' is fully visible (vertically), scrolling if not. */
1538 static void scroll_to_show(Collection
*collection
, int item
)
1540 int first
, last
, row
;
1542 g_return_if_fail(collection
!= NULL
);
1543 g_return_if_fail(IS_COLLECTION(collection
));
1545 row
= item
/ collection
->columns
;
1546 get_visible_limits(collection
, &first
, &last
);
1550 gtk_adjustment_set_value(collection
->vadj
,
1551 row
* collection
->item_height
);
1553 else if (row
>= last
)
1555 GtkWidget
*widget
= (GtkWidget
*) collection
;
1558 if (GTK_WIDGET_REALIZED(widget
))
1560 gdk_window_get_size(widget
->window
, NULL
, &height
);
1561 gtk_adjustment_set_value(collection
->vadj
,
1562 (row
+ 1) * collection
->item_height
- height
);
1567 /* Return the first and last rows which are [partly] visible. Does not
1568 * ensure that the rows actually exist (contain items).
1570 static void get_visible_limits(Collection
*collection
, int *first
, int *last
)
1572 GtkWidget
*widget
= (GtkWidget
*) collection
;
1575 g_return_if_fail(collection
!= NULL
);
1576 g_return_if_fail(IS_COLLECTION(collection
));
1577 g_return_if_fail(first
!= NULL
&& last
!= NULL
);
1579 if (!GTK_WIDGET_REALIZED(widget
))
1586 scroll
= collection
->vadj
->value
;
1587 gdk_window_get_size(widget
->window
, NULL
, &height
);
1589 *first
= MAX(scroll
/ collection
->item_height
, 0);
1590 *last
= (scroll
+ height
- 1) /collection
->item_height
;
1597 /* Unselect all items except number item (-1 to unselect everything) */
1598 static void collection_clear_except(Collection
*collection
, gint exception
)
1604 int end
; /* Selected items to end up with */
1606 widget
= GTK_WIDGET(collection
);
1607 scroll
= collection
->vadj
->value
;
1609 end
= exception
>= 0 && exception
< collection
->number_of_items
1610 ? collection
->items
[exception
].selected
!= 0 : 0;
1612 area
.width
= collection
->item_width
;
1613 area
.height
= collection
->item_height
;
1615 if (collection
->number_selected
== 0)
1618 while (collection
->number_selected
> end
)
1620 while (item
== exception
|| !collection
->items
[item
].selected
)
1623 area
.x
= (item
% collection
->columns
) * area
.width
;
1624 area
.y
= (item
/ collection
->columns
) * area
.height
1627 collection
->items
[item
++].selected
= FALSE
;
1628 clear_area(collection
, &area
);
1629 collection_paint(collection
, &area
);
1631 collection
->number_selected
--;
1635 gtk_signal_emit(GTK_OBJECT(collection
),
1636 collection_signals
[LOSE_SELECTION
],
1637 current_event_time
);
1640 /* Cancel the current wink effect. */
1641 static void cancel_wink(Collection
*collection
)
1645 g_return_if_fail(collection
!= NULL
);
1646 g_return_if_fail(IS_COLLECTION(collection
));
1647 g_return_if_fail(collection
->wink_item
!= -1);
1649 item
= collection
->wink_item
;
1651 collection
->wink_item
= -1;
1652 gtk_timeout_remove(collection
->wink_timeout
);
1654 collection_draw_item(collection
, item
, TRUE
);
1657 static gboolean
cancel_wink_timeout(Collection
*collection
)
1661 g_return_val_if_fail(collection
!= NULL
, FALSE
);
1662 g_return_val_if_fail(IS_COLLECTION(collection
), FALSE
);
1663 g_return_val_if_fail(collection
->wink_item
!= -1, FALSE
);
1665 item
= collection
->wink_item
;
1667 collection
->wink_item
= -1;
1669 collection_draw_item(collection
, item
, TRUE
);
1674 static gint
focus_in(GtkWidget
*widget
, GdkEventFocus
*event
)
1676 g_return_val_if_fail(widget
!= NULL
, FALSE
);
1677 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
1678 g_return_val_if_fail(event
!= NULL
, FALSE
);
1680 GTK_WIDGET_SET_FLAGS(widget
, GTK_HAS_FOCUS
);
1681 gtk_widget_draw_focus(widget
);
1686 static gint
focus_out(GtkWidget
*widget
, GdkEventFocus
*event
)
1688 g_return_val_if_fail(widget
!= NULL
, FALSE
);
1689 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
1690 g_return_val_if_fail(event
!= NULL
, FALSE
);
1692 GTK_WIDGET_UNSET_FLAGS(widget
, GTK_HAS_FOCUS
);
1693 gtk_widget_draw_focus(widget
);
1698 /* Functions for managing collections */
1700 /* Remove all objects from the collection */
1701 void collection_clear(Collection
*collection
)
1705 g_return_if_fail(IS_COLLECTION(collection
));
1707 if (collection
->number_of_items
== 0)
1710 if (collection
->wink_item
!= -1)
1712 collection
->wink_item
= -1;
1713 gtk_timeout_remove(collection
->wink_timeout
);
1716 collection_set_cursor_item(collection
,
1717 collection
->cursor_item
== -1 ? -1: 0);
1718 collection
->cursor_item_old
= -1;
1719 prev_selected
= collection
->number_selected
;
1720 collection
->number_of_items
= collection
->number_selected
= 0;
1722 resize_arrays(collection
, MINIMUM_ITEMS
);
1724 collection
->paint_level
= PAINT_CLEAR
;
1726 gtk_widget_queue_clear(GTK_WIDGET(collection
));
1728 if (prev_selected
&& collection
->number_selected
== 0)
1729 gtk_signal_emit(GTK_OBJECT(collection
),
1730 collection_signals
[LOSE_SELECTION
],
1731 current_event_time
);
1734 /* Inserts a new item at the end. The new item is unselected, and its
1735 * number is returned.
1737 gint
collection_insert(Collection
*collection
, gpointer data
)
1741 g_return_val_if_fail(IS_COLLECTION(collection
), -1);
1743 item
= collection
->number_of_items
;
1745 if (item
>= collection
->array_size
)
1746 resize_arrays(collection
, item
+ (item
>> 1));
1748 collection
->items
[item
].data
= data
;
1749 collection
->items
[item
].selected
= FALSE
;
1751 collection
->number_of_items
++;
1753 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection
)))
1755 set_vadjustment(collection
);
1756 collection_draw_item(collection
,
1757 collection
->number_of_items
- 1,
1764 /* Unselect an item by number */
1765 void collection_unselect_item(Collection
*collection
, gint item
)
1767 g_return_if_fail(collection
!= NULL
);
1768 g_return_if_fail(IS_COLLECTION(collection
));
1769 g_return_if_fail(item
>= 0 && item
< collection
->number_of_items
);
1771 if (collection
->items
[item
].selected
)
1773 collection
->items
[item
].selected
= FALSE
;
1774 collection_draw_item(collection
, item
, TRUE
);
1776 if (--collection
->number_selected
== 0)
1777 gtk_signal_emit(GTK_OBJECT(collection
),
1778 collection_signals
[LOSE_SELECTION
],
1779 current_event_time
);
1783 /* Select an item by number */
1784 void collection_select_item(Collection
*collection
, gint item
)
1786 g_return_if_fail(collection
!= NULL
);
1787 g_return_if_fail(IS_COLLECTION(collection
));
1788 g_return_if_fail(item
>= 0 && item
< collection
->number_of_items
);
1790 if (collection
->items
[item
].selected
)
1791 return; /* Already selected */
1793 collection
->items
[item
].selected
= TRUE
;
1794 collection_draw_item(collection
, item
, TRUE
);
1796 if (collection
->number_selected
++ == 0)
1797 gtk_signal_emit(GTK_OBJECT(collection
),
1798 collection_signals
[GAIN_SELECTION
],
1799 current_event_time
);
1802 /* Toggle the selected state of an item (by number) */
1803 void collection_toggle_item(Collection
*collection
, gint item
)
1805 g_return_if_fail(collection
!= NULL
);
1806 g_return_if_fail(IS_COLLECTION(collection
));
1807 g_return_if_fail(item
>= 0 && item
< collection
->number_of_items
);
1809 if (collection
->items
[item
].selected
)
1811 collection
->items
[item
].selected
= FALSE
;
1812 if (--collection
->number_selected
== 0)
1813 gtk_signal_emit(GTK_OBJECT(collection
),
1814 collection_signals
[LOSE_SELECTION
],
1815 current_event_time
);
1819 collection
->items
[item
].selected
= TRUE
;
1820 if (collection
->number_selected
++ == 0)
1821 gtk_signal_emit(GTK_OBJECT(collection
),
1822 collection_signals
[GAIN_SELECTION
],
1823 current_event_time
);
1825 collection_draw_item(collection
, item
, TRUE
);
1828 /* Select all items in the collection */
1829 void collection_select_all(Collection
*collection
)
1836 g_return_if_fail(collection
!= NULL
);
1837 g_return_if_fail(IS_COLLECTION(collection
));
1839 widget
= GTK_WIDGET(collection
);
1840 scroll
= collection
->vadj
->value
;
1842 area
.width
= collection
->item_width
;
1843 area
.height
= collection
->item_height
;
1845 if (collection
->number_selected
== collection
->number_of_items
)
1846 return; /* Nothing to do */
1848 while (collection
->number_selected
< collection
->number_of_items
)
1850 while (collection
->items
[item
].selected
)
1853 area
.x
= (item
% collection
->columns
) * area
.width
;
1854 area
.y
= (item
/ collection
->columns
) * area
.height
1857 collection
->items
[item
++].selected
= TRUE
;
1858 clear_area(collection
, &area
);
1859 collection_paint(collection
, &area
);
1861 collection
->number_selected
++;
1864 gtk_signal_emit(GTK_OBJECT(collection
),
1865 collection_signals
[GAIN_SELECTION
],
1866 current_event_time
);
1869 /* Unselect all items in the collection */
1870 void collection_clear_selection(Collection
*collection
)
1872 g_return_if_fail(collection
!= NULL
);
1873 g_return_if_fail(IS_COLLECTION(collection
));
1875 collection_clear_except(collection
, -1);
1878 /* Force a redraw of the specified item, if it is visible */
1879 void collection_draw_item(Collection
*collection
, gint item
, gboolean blank
)
1886 int area_y
, area_height
; /* NOT shorts! */
1888 g_return_if_fail(collection
!= NULL
);
1889 g_return_if_fail(IS_COLLECTION(collection
));
1890 g_return_if_fail(item
>= 0 &&
1891 (item
== 0 || item
< collection
->number_of_items
));
1893 widget
= GTK_WIDGET(collection
);
1894 if (!GTK_WIDGET_REALIZED(widget
))
1897 col
= item
% collection
->columns
;
1898 row
= item
/ collection
->columns
;
1899 scroll
= collection
->vadj
->value
; /* (round to int) */
1901 area
.x
= col
* collection
->item_width
;
1902 area_y
= row
* collection
->item_height
- scroll
;
1903 area
.width
= collection
->item_width
;
1904 area_height
= collection
->item_height
;
1906 if (area_y
+ area_height
< 0)
1909 gdk_window_get_size(widget
->window
, NULL
, &height
);
1911 if (area_y
> height
)
1915 area
.height
= area_height
;
1917 if (blank
|| collection
->lasso_box
)
1918 clear_area(collection
, &area
);
1920 draw_one_item(collection
, item
, &area
);
1922 if (collection
->lasso_box
)
1924 gdk_gc_set_clip_rectangle(collection
->xor_gc
, &area
);
1925 draw_lasso_box(collection
);
1926 gdk_gc_set_clip_rectangle(collection
->xor_gc
, NULL
);
1930 void collection_set_item_size(Collection
*collection
, int width
, int height
)
1934 g_return_if_fail(collection
!= NULL
);
1935 g_return_if_fail(IS_COLLECTION(collection
));
1936 g_return_if_fail(width
> 4 && height
> 4);
1938 if (collection
->item_width
== width
&&
1939 collection
->item_height
== height
)
1942 widget
= GTK_WIDGET(collection
);
1944 collection
->item_width
= width
;
1945 collection
->item_height
= height
;
1947 if (GTK_WIDGET_REALIZED(widget
))
1951 collection
->paint_level
= PAINT_CLEAR
;
1952 gdk_window_get_size(widget
->window
, &window_width
, NULL
);
1953 collection
->columns
= MAX(window_width
/ collection
->item_width
,
1956 set_vadjustment(collection
);
1957 if (collection
->cursor_item
!= -1)
1958 scroll_to_show(collection
, collection
->cursor_item
);
1959 gtk_widget_queue_draw(widget
);
1963 /* Cursor is positioned on item with the same data as before the sort.
1964 * Same for the wink item.
1966 void collection_qsort(Collection
*collection
,
1967 int (*compar
)(const void *, const void *))
1969 int cursor
, wink
, items
;
1970 gpointer cursor_data
= NULL
;
1971 gpointer wink_data
= NULL
;
1973 g_return_if_fail(collection
!= NULL
);
1974 g_return_if_fail(IS_COLLECTION(collection
));
1975 g_return_if_fail(compar
!= NULL
);
1977 items
= collection
->number_of_items
;
1979 wink
= collection
->wink_item
;
1980 if (wink
>= 0 && wink
< items
)
1981 wink_data
= collection
->items
[wink
].data
;
1985 cursor
= collection
->cursor_item
;
1986 if (cursor
>= 0 && cursor
< items
)
1987 cursor_data
= collection
->items
[cursor
].data
;
1991 if (collection
->wink_item
!= -1)
1993 collection
->wink_item
= -1;
1994 gtk_timeout_remove(collection
->wink_timeout
);
1997 qsort(collection
->items
, items
, sizeof(collection
->items
[0]), compar
);
1999 if (cursor
> -1 || wink
> -1)
2003 for (item
= 0; item
< items
; item
++)
2005 if (collection
->items
[item
].data
== cursor_data
)
2006 collection_set_cursor_item(collection
, item
);
2007 if (collection
->items
[item
].data
== wink_data
)
2008 collection_wink_item(collection
, item
);
2012 collection
->paint_level
= PAINT_CLEAR
;
2014 gtk_widget_queue_draw(GTK_WIDGET(collection
));
2017 /* Find an item in an unsorted collection.
2018 * Returns the item number, or -1 if not found.
2020 int collection_find_item(Collection
*collection
, gpointer data
,
2021 int (*compar
)(const void *, const void *))
2025 g_return_val_if_fail(collection
!= NULL
, -1);
2026 g_return_val_if_fail(IS_COLLECTION(collection
), -1);
2027 g_return_val_if_fail(compar
!= NULL
, -1);
2029 for (i
= 0; i
< collection
->number_of_items
; i
++)
2030 if (compar(&collection
->items
[i
].data
, &data
) == 0)
2036 /* The collection may be in either normal mode or panel mode.
2038 * - a single click calls open_item
2039 * - items are never selected
2040 * - lasso boxes are disabled
2042 void collection_set_panel(Collection
*collection
, gboolean panel
)
2044 g_return_if_fail(collection
!= NULL
);
2045 g_return_if_fail(IS_COLLECTION(collection
));
2047 collection
->panel
= panel
== TRUE
;
2049 if (collection
->panel
)
2051 collection_clear_selection(collection
);
2052 if (collection
->lasso_box
)
2053 remove_lasso_box(collection
);
2057 /* Return the number of the item under the point (x,y), or -1 for none.
2058 * This may call your test_point callback. The point is relative to the
2059 * collection's origin.
2061 int collection_get_item(Collection
*collection
, int x
, int y
)
2067 g_return_val_if_fail(collection
!= NULL
, -1);
2069 scroll
= collection
->vadj
->value
;
2070 col
= x
/ collection
->item_width
;
2071 row
= (y
+ scroll
) / collection
->item_height
;
2073 if (col
< 0 || row
< 0 || col
>= collection
->columns
)
2076 item
= col
+ row
* collection
->columns
;
2077 if (item
>= collection
->number_of_items
2079 !collection
->test_point(collection
,
2080 x
- col
* collection
->item_width
,
2081 y
- row
* collection
->item_height
2083 &collection
->items
[item
],
2084 collection
->item_width
,
2085 collection
->item_height
))
2093 /* Set the cursor/highlight over the given item. Passing -1
2094 * hides the cursor. As a special case, you may set the cursor item
2095 * to zero when there are no items.
2097 void collection_set_cursor_item(Collection
*collection
, gint item
)
2101 g_return_if_fail(collection
!= NULL
);
2102 g_return_if_fail(IS_COLLECTION(collection
));
2103 g_return_if_fail(item
>= -1 &&
2104 (item
< collection
->number_of_items
|| item
== 0));
2106 old_item
= collection
->cursor_item
;
2108 if (old_item
== item
)
2111 collection
->cursor_item
= item
;
2114 collection_draw_item(collection
, old_item
, TRUE
);
2118 collection_draw_item(collection
, item
, TRUE
);
2119 scroll_to_show(collection
, item
);
2121 else if (old_item
!= -1)
2122 collection
->cursor_item_old
= old_item
;
2125 /* Briefly highlight an item to draw the user's attention to it.
2126 * -1 cancels the effect, as does deleting items, sorting the collection
2127 * or starting a new wink effect.
2128 * Otherwise, the effect will cancel itself after a short pause.
2130 void collection_wink_item(Collection
*collection
, gint item
)
2132 g_return_if_fail(collection
!= NULL
);
2133 g_return_if_fail(IS_COLLECTION(collection
));
2134 g_return_if_fail(item
>= -1 && item
< collection
->number_of_items
);
2136 if (collection
->wink_item
!= -1)
2137 cancel_wink(collection
);
2141 collection
->cursor_item_old
= collection
->wink_item
= item
;
2142 collection
->wink_timeout
= gtk_timeout_add(300,
2143 (GtkFunction
) cancel_wink_timeout
,
2145 collection_draw_item(collection
, item
, TRUE
);
2146 scroll_to_show(collection
, item
);
2151 /* Call test(item, data) on each item in the collection.
2152 * Remove all items for which it returns TRUE. test() should
2153 * free the data before returning TRUE. The collection is in an
2154 * inconsistant state during this call (ie, when test() is called).
2156 void collection_delete_if(Collection
*collection
,
2157 gboolean (*test
)(gpointer item
, gpointer data
),
2164 g_return_if_fail(collection
!= NULL
);
2165 g_return_if_fail(IS_COLLECTION(collection
));
2166 g_return_if_fail(test
!= NULL
);
2168 cursor
= collection
->cursor_item
;
2170 for (in
= 0; in
< collection
->number_of_items
; in
++)
2172 if (!test(collection
->items
[in
].data
, data
))
2174 if (collection
->items
[in
].selected
)
2176 collection
->items
[out
].selected
= TRUE
;
2180 collection
->items
[out
].selected
= FALSE
;
2182 collection
->items
[out
++].data
=
2183 collection
->items
[in
].data
;
2185 else if (cursor
>= in
)
2191 collection
->cursor_item
= cursor
;
2193 if (collection
->wink_item
!= -1)
2195 collection
->wink_item
= -1;
2196 gtk_timeout_remove(collection
->wink_timeout
);
2199 collection
->number_of_items
= out
;
2200 collection
->number_selected
= selected
;
2201 resize_arrays(collection
,
2202 MAX(collection
->number_of_items
, MINIMUM_ITEMS
));
2204 collection
->paint_level
= PAINT_CLEAR
;
2206 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection
)))
2208 set_vadjustment(collection
);
2209 gtk_widget_queue_draw(GTK_WIDGET(collection
));
2214 /* Display a cross-hair pointer and the next time an item is clicked,
2215 * call the callback function. If the background is clicked or NULL
2216 * is passed as the callback then revert to normal operation.
2218 void collection_target(Collection
*collection
,
2219 CollectionTargetFunc callback
,
2222 g_return_if_fail(collection
!= NULL
);
2223 g_return_if_fail(IS_COLLECTION(collection
));
2225 if (callback
!= collection
->target_cb
)
2226 gdk_window_set_cursor(GTK_WIDGET(collection
)->window
,
2227 callback
? crosshair
: NULL
);
2229 collection
->target_cb
= callback
;
2230 collection
->target_data
= user_data
;
2232 if (collection
->cursor_item
!= -1)
2233 collection_draw_item(collection
, collection
->cursor_item
,
2237 /* Move the cursor by the given row and column offsets.
2238 * Moving by (0,0) can be used to simply make the cursor appear.
2240 void collection_move_cursor(Collection
*collection
, int drow
, int dcol
)
2243 int first
, last
, total_rows
;
2245 g_return_if_fail(collection
!= NULL
);
2246 g_return_if_fail(IS_COLLECTION(collection
));
2248 get_visible_limits(collection
, &first
, &last
);
2250 item
= collection
->cursor_item
;
2253 item
= MIN(collection
->cursor_item_old
,
2254 collection
->number_of_items
- 1);
2264 row
= item
/ collection
->columns
;
2265 col
= item
% collection
->columns
+ dcol
;
2269 else if (row
> last
)
2272 row
= MAX(row
+ drow
, 0);
2275 total_rows
= (collection
->number_of_items
+ collection
->columns
- 1)
2276 / collection
->columns
;
2278 if (row
>= total_rows
- 1 && drow
> 0)
2280 row
= total_rows
- 1;
2281 item
= col
+ row
* collection
->columns
;
2282 if (item
>= collection
->number_of_items
)
2285 scroll_to_show(collection
, item
);
2291 item
= col
+ row
* collection
->columns
;
2293 if (item
>= 0 && item
< collection
->number_of_items
)
2294 collection_set_cursor_item(collection
, item
);