4 * Collection - a GTK+ widget
5 * Copyright (C) 2000, Thomas Leonard, <tal197@users.sourceforge.net>.
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
= TRUE
;
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
,
127 static gboolean
default_test_point(Collection
*collection
,
128 int point_x
, int point_y
,
129 CollectionItem
*data
,
130 int width
, int height
,
132 static gint
collection_motion_notify(GtkWidget
*widget
,
133 GdkEventMotion
*event
);
134 static void add_lasso_box(Collection
*collection
);
135 static void abort_lasso(Collection
*collection
);
136 static void remove_lasso_box(Collection
*collection
);
137 static void draw_lasso_box(Collection
*collection
);
138 static int item_at_row_col(Collection
*collection
, int row
, int col
);
139 static void collection_clear_except(Collection
*collection
, gint exception
);
140 static void cancel_wink(Collection
*collection
);
141 static gint
collection_key_press(GtkWidget
*widget
, GdkEventKey
*event
);
142 static void get_visible_limits(Collection
*collection
, int *first
, int *last
);
143 static void scroll_to_show(Collection
*collection
, int item
);
144 static gint
focus_in(GtkWidget
*widget
, GdkEventFocus
*event
);
145 static gint
focus_out(GtkWidget
*widget
, GdkEventFocus
*event
);
146 static void draw_focus(GtkWidget
*widget
);
148 static void draw_focus_at(Collection
*collection
, GdkRectangle
*area
)
153 widget
= GTK_WIDGET(collection
);
155 if (collection
->target_cb
)
156 gc
= widget
->style
->white_gc
;
157 else if (GTK_WIDGET_FLAGS(widget
) & GTK_HAS_FOCUS
)
158 gc
= widget
->style
->black_gc
;
160 gc
= widget
->style
->fg_gc
[GTK_STATE_INSENSITIVE
];
162 gdk_draw_rectangle(widget
->window
, gc
, FALSE
,
163 area
->x
+ 1, area
->y
+ 1,
168 static void draw_one_item(Collection
*collection
, int item
, GdkRectangle
*area
)
170 if (item
< collection
->number_of_items
)
172 collection
->draw_item((GtkWidget
*) collection
,
173 &collection
->items
[item
],
175 collection
->cb_user_data
);
176 if (item
== collection
->wink_item
)
177 gdk_draw_rectangle(((GtkWidget
*) collection
)->window
,
178 ((GtkWidget
*) collection
)->style
->black_gc
,
181 area
->width
- 1, area
->height
- 1);
184 if (item
== collection
->cursor_item
)
185 draw_focus_at(collection
, area
);
188 static void draw_focus(GtkWidget
*widget
)
190 Collection
*collection
;
192 g_return_if_fail(widget
!= NULL
);
193 g_return_if_fail(IS_COLLECTION(widget
));
195 collection
= COLLECTION(widget
);
197 if (collection
->cursor_item
< 0 || !GTK_WIDGET_REALIZED(widget
))
200 collection_draw_item(collection
, collection
->cursor_item
, FALSE
);
203 GtkType
collection_get_type(void)
205 static guint my_type
= 0;
209 static const GtkTypeInfo my_info
=
213 sizeof(CollectionClass
),
214 (GtkClassInitFunc
) collection_class_init
,
215 (GtkObjectInitFunc
) collection_init
,
216 NULL
, /* Reserved 1 */
217 NULL
, /* Reserved 2 */
218 (GtkClassInitFunc
) NULL
/* base_class_init_func */
221 my_type
= gtk_type_unique(gtk_widget_get_type(),
228 static void collection_class_init(CollectionClass
*class)
230 GtkObjectClass
*object_class
;
231 GtkWidgetClass
*widget_class
;
233 object_class
= (GtkObjectClass
*) class;
234 widget_class
= (GtkWidgetClass
*) class;
236 parent_class
= gtk_type_class(gtk_widget_get_type());
238 gtk_object_add_arg_type("Collection::vadjustment",
240 GTK_ARG_READWRITE
| GTK_ARG_CONSTRUCT
,
243 object_class
->destroy
= collection_destroy
;
244 object_class
->finalize
= collection_finalize
;
246 widget_class
->realize
= collection_realize
;
247 widget_class
->draw
= collection_draw
;
248 widget_class
->expose_event
= collection_expose
;
249 widget_class
->size_request
= collection_size_request
;
250 widget_class
->size_allocate
= collection_size_allocate
;
251 widget_class
->style_set
= collection_set_style
;
253 widget_class
->key_press_event
= collection_key_press
;
254 widget_class
->button_press_event
= collection_button_press
;
255 widget_class
->button_release_event
= collection_button_release
;
256 widget_class
->motion_notify_event
= collection_motion_notify
;
257 widget_class
->focus_in_event
= focus_in
;
258 widget_class
->focus_out_event
= focus_out
;
259 widget_class
->draw_focus
= draw_focus
;
261 object_class
->set_arg
= collection_set_arg
;
262 object_class
->get_arg
= collection_get_arg
;
264 class->open_item
= NULL
;
265 class->drag_selection
= NULL
;
266 class->show_menu
= NULL
;
267 class->gain_selection
= NULL
;
268 class->lose_selection
= NULL
;
270 collection_signals
[OPEN_ITEM
] = gtk_signal_new("open_item",
273 GTK_SIGNAL_OFFSET(CollectionClass
,
275 gtk_marshal_NONE__POINTER_UINT
,
279 collection_signals
[DRAG_SELECTION
] = gtk_signal_new("drag_selection",
282 GTK_SIGNAL_OFFSET(CollectionClass
,
284 gtk_marshal_NONE__POINTER_UINT
,
288 collection_signals
[SHOW_MENU
] = gtk_signal_new("show_menu",
291 GTK_SIGNAL_OFFSET(CollectionClass
,
293 gtk_marshal_NONE__POINTER_INT
,
297 collection_signals
[GAIN_SELECTION
] = gtk_signal_new("gain_selection",
300 GTK_SIGNAL_OFFSET(CollectionClass
,
302 gtk_marshal_NONE__UINT
,
305 collection_signals
[LOSE_SELECTION
] = gtk_signal_new("lose_selection",
308 GTK_SIGNAL_OFFSET(CollectionClass
,
310 gtk_marshal_NONE__UINT
,
314 gtk_object_class_add_signals(object_class
,
315 collection_signals
, LAST_SIGNAL
);
318 static void collection_init(Collection
*object
)
320 g_return_if_fail(object
!= NULL
);
321 g_return_if_fail(IS_COLLECTION(object
));
324 crosshair
= gdk_cursor_new(GDK_CROSSHAIR
);
326 GTK_WIDGET_SET_FLAGS(GTK_WIDGET(object
), GTK_CAN_FOCUS
);
328 object
->panel
= FALSE
;
329 object
->number_of_items
= 0;
330 object
->number_selected
= 0;
332 object
->item_width
= 64;
333 object
->item_height
= 64;
335 object
->paint_level
= PAINT_OVERWRITE
;
336 object
->last_scroll
= 0;
337 object
->bg_gc
= NULL
;
339 object
->items
= g_malloc(sizeof(CollectionItem
) * MINIMUM_ITEMS
);
340 object
->cursor_item
= -1;
341 object
->cursor_item_old
= -1;
342 object
->wink_item
= -1;
343 object
->array_size
= MINIMUM_ITEMS
;
344 object
->draw_item
= default_draw_item
;
345 object
->test_point
= default_test_point
;
347 object
->buttons_pressed
= 0;
348 object
->may_drag
= FALSE
;
350 object
->auto_scroll
= -1;
355 GtkWidget
* collection_new(GtkAdjustment
*vadj
)
358 g_return_val_if_fail(GTK_IS_ADJUSTMENT(vadj
), NULL
);
360 return GTK_WIDGET(gtk_widget_new(collection_get_type(),
365 void collection_set_functions(Collection
*collection
,
366 CollectionDrawFunc draw_item
,
367 CollectionTestFunc test_point
,
372 g_return_if_fail(collection
!= NULL
);
373 g_return_if_fail(IS_COLLECTION(collection
));
375 widget
= GTK_WIDGET(collection
);
378 draw_item
= default_draw_item
;
380 test_point
= default_test_point
;
382 collection
->draw_item
= draw_item
;
383 collection
->test_point
= test_point
;
384 collection
->cb_user_data
= user_data
;
386 if (GTK_WIDGET_REALIZED(widget
))
388 collection
->paint_level
= PAINT_CLEAR
;
389 gtk_widget_queue_clear(widget
);
393 /* After this we are unusable, but our data (if any) is still hanging around.
394 * It will be freed later with finalize.
396 static void collection_destroy(GtkObject
*object
)
398 Collection
*collection
;
400 g_return_if_fail(object
!= NULL
);
401 g_return_if_fail(IS_COLLECTION(object
));
403 collection
= COLLECTION(object
);
405 if (collection
->wink_item
!= -1)
407 collection
->wink_item
= -1;
408 gtk_timeout_remove(collection
->wink_timeout
);
411 if (collection
->auto_scroll
!= -1)
413 gtk_timeout_remove(collection
->auto_scroll
);
414 collection
->auto_scroll
= -1;
417 gtk_signal_disconnect_by_data(GTK_OBJECT(collection
->vadj
),
420 if (collection
->bg_gc
)
422 gdk_gc_destroy(collection
->bg_gc
);
423 collection
->bg_gc
= NULL
;
426 if (GTK_OBJECT_CLASS(parent_class
)->destroy
)
427 (*GTK_OBJECT_CLASS(parent_class
)->destroy
)(object
);
430 /* This is the last thing that happens to us. Free all data. */
431 static void collection_finalize(GtkObject
*object
)
433 Collection
*collection
;
435 collection
= COLLECTION(object
);
437 if (collection
->vadj
)
439 gtk_object_unref(GTK_OBJECT(collection
->vadj
));
442 g_free(collection
->items
);
445 static GdkGC
*create_bg_gc(GtkWidget
*widget
)
449 values
.tile
= widget
->style
->bg_pixmap
[GTK_STATE_INSENSITIVE
];
450 values
.fill
= GDK_TILED
;
452 return gdk_gc_new_with_values(widget
->window
, &values
,
453 GDK_GC_FILL
| GDK_GC_TILE
);
456 static void collection_realize(GtkWidget
*widget
)
458 Collection
*collection
;
459 GdkWindowAttr attributes
;
460 gint attributes_mask
;
461 GdkGCValues xor_values
;
463 g_return_if_fail(widget
!= NULL
);
464 g_return_if_fail(IS_COLLECTION(widget
));
465 g_return_if_fail(widget
->parent
!= NULL
);
467 GTK_WIDGET_SET_FLAGS(widget
, GTK_REALIZED
);
468 collection
= COLLECTION(widget
);
470 attributes
.x
= widget
->allocation
.x
;
471 attributes
.y
= widget
->allocation
.y
;
472 attributes
.width
= widget
->allocation
.width
;
473 attributes
.height
= widget
->allocation
.height
;
474 attributes
.wclass
= GDK_INPUT_OUTPUT
;
475 attributes
.window_type
= GDK_WINDOW_CHILD
;
476 attributes
.event_mask
= gtk_widget_get_events(widget
) |
478 GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK
|
479 GDK_BUTTON1_MOTION_MASK
| GDK_BUTTON2_MOTION_MASK
|
480 GDK_BUTTON3_MOTION_MASK
;
481 attributes
.visual
= gtk_widget_get_visual(widget
);
482 attributes
.colormap
= gtk_widget_get_colormap(widget
);
484 attributes_mask
= GDK_WA_X
| GDK_WA_Y
|
485 GDK_WA_VISUAL
| GDK_WA_COLORMAP
;
486 widget
->window
= gdk_window_new(widget
->parent
->window
,
487 &attributes
, attributes_mask
);
489 widget
->style
= gtk_style_attach(widget
->style
, widget
->window
);
491 gdk_window_set_user_data(widget
->window
, widget
);
493 gdk_window_set_background(widget
->window
,
494 &widget
->style
->base
[GTK_STATE_INSENSITIVE
]);
495 if (widget
->style
->bg_pixmap
[GTK_STATE_INSENSITIVE
])
496 collection
->bg_gc
= create_bg_gc(widget
);
498 /* Try to stop everything flickering horribly */
499 gdk_window_set_static_gravities(widget
->window
, TRUE
);
501 set_vadjustment(collection
);
503 xor_values
.function
= GDK_XOR
;
504 xor_values
.foreground
.red
= 0xffff;
505 xor_values
.foreground
.green
= 0xffff;
506 xor_values
.foreground
.blue
= 0xffff;
507 gdk_color_alloc(gtk_widget_get_colormap(widget
),
508 &xor_values
.foreground
);
509 collection
->xor_gc
= gdk_gc_new_with_values(widget
->window
,
515 static void collection_size_request(GtkWidget
*widget
,
516 GtkRequisition
*requisition
)
518 requisition
->width
= MIN_WIDTH
;
519 requisition
->height
= MIN_HEIGHT
;
522 static void collection_size_allocate(GtkWidget
*widget
,
523 GtkAllocation
*allocation
)
525 Collection
*collection
;
528 g_return_if_fail(widget
!= NULL
);
529 g_return_if_fail(IS_COLLECTION(widget
));
530 g_return_if_fail(allocation
!= NULL
);
532 collection
= COLLECTION(widget
);
534 old_columns
= collection
->columns
;
535 if (widget
->allocation
.x
!= allocation
->x
536 || widget
->allocation
.y
!= allocation
->y
)
537 collection
->paint_level
= PAINT_CLEAR
;
539 widget
->allocation
= *allocation
;
541 collection
->columns
= allocation
->width
/ collection
->item_width
;
542 if (collection
->columns
< 1)
543 collection
->columns
= 1;
545 if (GTK_WIDGET_REALIZED(widget
))
547 gdk_window_move_resize(widget
->window
,
548 allocation
->x
, allocation
->y
,
549 allocation
->width
, allocation
->height
);
551 if (old_columns
!= collection
->columns
)
553 collection
->paint_level
= PAINT_CLEAR
;
554 gtk_widget_queue_clear(widget
);
557 set_vadjustment(collection
);
559 if (collection
->cursor_item
!= -1)
560 scroll_to_show(collection
, collection
->cursor_item
);
564 static void collection_set_style(GtkWidget
*widget
,
565 GtkStyle
*previous_style
)
567 Collection
*collection
;
569 g_return_if_fail(IS_COLLECTION(widget
));
571 collection
= COLLECTION(widget
);
573 collection
->paint_level
= PAINT_CLEAR
;
575 if (GTK_WIDGET_REALIZED(widget
))
577 gdk_window_set_background(widget
->window
,
578 &widget
->style
->base
[GTK_STATE_INSENSITIVE
]);
580 if (collection
->bg_gc
)
582 gdk_gc_destroy(collection
->bg_gc
);
583 collection
->bg_gc
= NULL
;
586 if (widget
->style
->bg_pixmap
[GTK_STATE_INSENSITIVE
])
587 collection
->bg_gc
= create_bg_gc(widget
);
591 static void clear_area(Collection
*collection
, GdkRectangle
*area
)
593 GtkWidget
*widget
= GTK_WIDGET(collection
);
594 int scroll
= collection
->vadj
->value
;
596 if (collection
->bg_gc
)
598 gdk_gc_set_ts_origin(collection
->bg_gc
, 0, -scroll
);
600 gdk_draw_rectangle(widget
->window
,
604 area
->width
, area
->height
);
607 gdk_window_clear_area(widget
->window
,
608 area
->x
, area
->y
, area
->width
, area
->height
);
611 static gint
collection_paint(Collection
*collection
,
614 GdkRectangle whole
, item_area
;
619 int start_row
, last_row
;
620 int start_col
, last_col
;
624 scroll
= collection
->vadj
->value
;
626 widget
= GTK_WIDGET(collection
);
628 if (collection
->paint_level
> PAINT_NORMAL
|| area
== NULL
)
631 gdk_window_get_size(widget
->window
, &width
, &height
);
636 whole
.height
= height
;
640 if (collection
->paint_level
== PAINT_CLEAR
641 && !collection
->lasso_box
)
642 clear_area(collection
, area
);
644 collection
->paint_level
= PAINT_NORMAL
;
647 /* Calculate the ranges to plot */
648 start_row
= (area
->y
+ scroll
) / collection
->item_height
;
649 last_row
= (area
->y
+ area
->height
- 1 + scroll
)
650 / collection
->item_height
;
653 start_col
= area
->x
/ collection
->item_width
;
654 phys_last_col
= (area
->x
+ area
->width
- 1) / collection
->item_width
;
656 if (collection
->lasso_box
)
658 /* You can't be too careful with lasso boxes...
660 * clip gives the total area drawn over (this may be larger
661 * than the requested area). It's used to redraw the lasso
664 clip
.x
= start_col
* collection
->item_width
;
665 clip
.y
= start_row
* collection
->item_height
- scroll
;
666 clip
.width
= (phys_last_col
- start_col
+ 1)
667 * collection
->item_width
;
668 clip
.height
= (last_row
- start_row
+ 1)
669 * collection
->item_height
;
671 clear_area(collection
, &clip
);
674 if (start_col
< collection
->columns
)
676 if (phys_last_col
>= collection
->columns
)
677 last_col
= collection
->columns
- 1;
679 last_col
= phys_last_col
;
683 item
= row
* collection
->columns
+ col
;
684 item_area
.width
= collection
->item_width
;
685 item_area
.height
= collection
->item_height
;
687 while ((item
== 0 || item
< collection
->number_of_items
)
690 item_area
.x
= col
* collection
->item_width
;
691 item_area
.y
= row
* collection
->item_height
- scroll
;
693 draw_one_item(collection
, item
, &item_area
);
700 item
= row
* collection
->columns
+ col
;
707 if (collection
->lasso_box
)
709 gdk_gc_set_clip_rectangle(collection
->xor_gc
, &clip
);
710 draw_lasso_box(collection
);
711 gdk_gc_set_clip_rectangle(collection
->xor_gc
, NULL
);
717 static void default_draw_item( GtkWidget
*widget
,
718 CollectionItem
*item
,
722 gdk_draw_arc(widget
->window
,
723 item
->selected
? widget
->style
->white_gc
724 : widget
->style
->black_gc
,
727 area
->width
, area
->height
,
732 static gboolean
default_test_point(Collection
*collection
,
733 int point_x
, int point_y
,
734 CollectionItem
*item
,
735 int width
, int height
,
740 /* Convert to point in unit circle */
741 f_x
= ((float) point_x
/ width
) - 0.5;
742 f_y
= ((float) point_y
/ height
) - 0.5;
744 return (f_x
* f_x
) + (f_y
* f_y
) <= .25;
747 static void collection_set_arg( GtkObject
*object
,
751 Collection
*collection
;
753 collection
= COLLECTION(object
);
757 case ARG_VADJUSTMENT
:
758 collection_set_adjustment(collection
,
759 GTK_VALUE_POINTER(*arg
));
766 static void collection_set_adjustment( Collection
*collection
,
770 g_return_if_fail (GTK_IS_ADJUSTMENT (vadj
));
772 vadj
= GTK_ADJUSTMENT(gtk_adjustment_new(0.0,
775 if (collection
->vadj
&& (collection
->vadj
!= vadj
))
777 gtk_signal_disconnect_by_data(GTK_OBJECT(collection
->vadj
),
779 gtk_object_unref(GTK_OBJECT(collection
->vadj
));
782 if (collection
->vadj
!= vadj
)
784 collection
->vadj
= vadj
;
785 gtk_object_ref(GTK_OBJECT(collection
->vadj
));
786 gtk_object_sink(GTK_OBJECT(collection
->vadj
));
788 gtk_signal_connect(GTK_OBJECT(collection
->vadj
),
790 (GtkSignalFunc
) collection_adjustment
,
792 gtk_signal_connect(GTK_OBJECT(collection
->vadj
),
794 (GtkSignalFunc
) collection_adjustment
,
796 gtk_signal_connect(GTK_OBJECT(collection
->vadj
),
798 (GtkSignalFunc
) collection_disconnect
,
800 collection_adjustment(vadj
, collection
);
804 static void collection_get_arg( GtkObject
*object
,
808 Collection
*collection
;
810 collection
= COLLECTION(object
);
814 case ARG_VADJUSTMENT
:
815 GTK_VALUE_POINTER(*arg
) = collection
->vadj
;
818 arg
->type
= GTK_TYPE_INVALID
;
823 /* Something about the adjustment has changed */
824 static void collection_adjustment(GtkAdjustment
*adjustment
,
825 Collection
*collection
)
829 g_return_if_fail(adjustment
!= NULL
);
830 g_return_if_fail(GTK_IS_ADJUSTMENT(adjustment
));
831 g_return_if_fail(collection
!= NULL
);
832 g_return_if_fail(IS_COLLECTION(collection
));
834 diff
= ((gint
) adjustment
->value
) - collection
->last_scroll
;
838 collection
->last_scroll
= adjustment
->value
;
840 if (collection
->lasso_box
)
842 remove_lasso_box(collection
);
843 collection
->drag_box_y
[0] -= diff
;
844 scroll_by(collection
, diff
);
845 add_lasso_box(collection
);
848 scroll_by(collection
, diff
);
852 static void collection_disconnect(GtkAdjustment
*adjustment
,
853 Collection
*collection
)
855 g_return_if_fail(adjustment
!= NULL
);
856 g_return_if_fail(GTK_IS_ADJUSTMENT(adjustment
));
857 g_return_if_fail(collection
!= NULL
);
858 g_return_if_fail(IS_COLLECTION(collection
));
860 collection_set_adjustment(collection
, NULL
);
863 static void set_vadjustment(Collection
*collection
)
869 widget
= GTK_WIDGET(collection
);
871 if (!GTK_WIDGET_REALIZED(widget
))
874 gdk_window_get_size(widget
->window
, NULL
, &height
);
875 cols
= collection
->columns
;
876 rows
= (collection
->number_of_items
+ cols
- 1) / cols
;
878 collection
->vadj
->lower
= 0.0;
879 collection
->vadj
->upper
= collection
->item_height
* rows
;
881 collection
->vadj
->step_increment
=
882 MIN(collection
->vadj
->upper
, collection
->item_height
/ 4);
884 collection
->vadj
->page_increment
=
885 MIN(collection
->vadj
->upper
,
888 collection
->vadj
->page_size
= MIN(collection
->vadj
->upper
, height
);
890 collection
->vadj
->value
= MIN(collection
->vadj
->value
,
891 collection
->vadj
->upper
- collection
->vadj
->page_size
);
893 collection
->vadj
->value
= MAX(collection
->vadj
->value
, 0.0);
895 gtk_signal_emit_by_name(GTK_OBJECT(collection
->vadj
), "changed");
898 /* Change the adjustment by this amount. Bounded. */
899 static void diff_vpos(Collection
*collection
, int diff
)
901 int value
= collection
->vadj
->value
+ diff
;
903 value
= CLAMP(value
, 0,
904 collection
->vadj
->upper
- collection
->vadj
->page_size
);
905 gtk_adjustment_set_value(collection
->vadj
, value
);
908 static void collection_draw(GtkWidget
*widget
, GdkRectangle
*area
)
910 Collection
*collection
;
912 g_return_if_fail(widget
!= NULL
);
913 g_return_if_fail(IS_COLLECTION(widget
));
914 g_return_if_fail(area
!= NULL
); /* Not actually used */
916 collection
= COLLECTION(widget
);
918 /* This doesn't always work - I think Gtk+ may be doing some
919 * kind of expose-event compression...
920 if (collection->paint_level > PAINT_NORMAL)
922 collection_paint(collection
, area
);
925 static gint
collection_expose(GtkWidget
*widget
, GdkEventExpose
*event
)
927 Collection
*collection
;
929 g_return_val_if_fail(widget
!= NULL
, FALSE
);
930 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
931 g_return_val_if_fail(event
!= NULL
, FALSE
);
933 collection
= COLLECTION(widget
);
935 clear_area(collection
, &event
->area
);
937 collection_paint(collection
, &event
->area
);
942 /* Positive makes the contents go move up the screen */
943 static void scroll_by(Collection
*collection
, gint diff
)
949 GdkRectangle new_area
;
954 widget
= GTK_WIDGET(collection
);
956 if (collection
->lasso_box
)
957 abort_lasso(collection
);
959 gdk_window_get_size(widget
->window
, &width
, &height
);
961 new_area
.width
= width
;
968 new_area
.y
= height
- amount
;
978 new_area
.height
= amount
;
982 gdk_draw_pixmap(widget
->window
,
983 widget
->style
->white_gc
,
991 /* We have to redraw everything because any pending
992 * expose events now contain invalid areas.
993 * Don't need to clear the area first though...
995 if (collection
->paint_level
< PAINT_OVERWRITE
)
996 collection
->paint_level
= PAINT_OVERWRITE
;
999 collection
->paint_level
= PAINT_CLEAR
;
1001 clear_area(collection
, &new_area
);
1002 collection_paint(collection
, NULL
);
1005 static void resize_arrays(Collection
*collection
, guint new_size
)
1007 g_return_if_fail(collection
!= NULL
);
1008 g_return_if_fail(IS_COLLECTION(collection
));
1009 g_return_if_fail(new_size
>= collection
->number_of_items
);
1011 collection
->items
= g_realloc(collection
->items
,
1012 sizeof(CollectionItem
) * new_size
);
1013 collection
->array_size
= new_size
;
1016 static void return_pressed(Collection
*collection
)
1018 int item
= collection
->cursor_item
;
1019 CollectionTargetFunc cb
= collection
->target_cb
;
1020 gpointer data
= collection
->target_data
;
1022 collection_target(collection
, NULL
, NULL
);
1023 if (item
< 0 || item
>= collection
->number_of_items
)
1028 cb(collection
, item
, data
);
1032 gtk_signal_emit(GTK_OBJECT(collection
),
1033 collection_signals
[OPEN_ITEM
],
1034 collection
->items
[item
].data
,
1038 static gint
collection_key_press(GtkWidget
*widget
, GdkEventKey
*event
)
1040 Collection
*collection
;
1043 g_return_val_if_fail(widget
!= NULL
, FALSE
);
1044 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
1045 g_return_val_if_fail(event
!= NULL
, FALSE
);
1047 collection
= (Collection
*) widget
;
1048 item
= collection
->cursor_item
;
1050 switch (event
->keyval
)
1053 collection_move_cursor(collection
, 0, -1);
1056 collection_move_cursor(collection
, 0, 1);
1059 collection_move_cursor(collection
, -1, 0);
1062 collection_move_cursor(collection
, 1, 0);
1065 collection_set_cursor_item(collection
, 0);
1068 collection_set_cursor_item(collection
,
1069 MAX((gint
) collection
->number_of_items
- 1, 0));
1072 collection_move_cursor(collection
, -10, 0);
1075 collection_move_cursor(collection
, 10, 0);
1078 return_pressed(collection
);
1081 if (!collection
->target_cb
)
1083 collection_set_cursor_item(collection
, -1);
1084 collection_clear_selection(collection
);
1085 return FALSE
; /* Pass it on */
1087 collection_target(collection
, NULL
, NULL
);
1090 if (item
>=0 && item
< collection
->number_of_items
)
1091 collection_toggle_item(collection
, item
);
1100 static gint
collection_button_press(GtkWidget
*widget
,
1101 GdkEventButton
*event
)
1103 Collection
*collection
;
1110 g_return_val_if_fail(widget
!= NULL
, FALSE
);
1111 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
1112 g_return_val_if_fail(event
!= NULL
, FALSE
);
1114 collection
= COLLECTION(widget
);
1116 collection
->item_clicked
= -1;
1118 if (event
->button
> 3)
1122 /* Wheel mouse scrolling */
1123 if (event
->button
== 4)
1124 diff
= -((signed int) collection
->item_height
) / 4;
1125 else if (event
->button
== 5)
1126 diff
= collection
->item_height
/ 4;
1132 int old_value
= collection
->vadj
->value
;
1134 gboolean box
= collection
->lasso_box
;
1136 new_value
= CLAMP(old_value
+ diff
, 0.0,
1137 collection
->vadj
->upper
1138 - collection
->vadj
->page_size
);
1139 diff
= new_value
- old_value
;
1144 remove_lasso_box(collection
);
1145 collection
->drag_box_y
[0] -= diff
;
1147 collection
->vadj
->value
= new_value
;
1148 gtk_signal_emit_by_name(
1149 GTK_OBJECT(collection
->vadj
),
1152 add_lasso_box(collection
);
1158 if (collection
->cursor_item
!= -1)
1159 collection_set_cursor_item(collection
, -1);
1161 scroll
= collection
->vadj
->value
;
1163 if (event
->type
== GDK_BUTTON_PRESS
&&
1164 event
->button
!= collection_menu_button
)
1166 if (collection
->buttons_pressed
++ == 0)
1167 gtk_grab_add(widget
);
1169 return FALSE
; /* Ignore extra presses */
1172 if (event
->state
& GDK_CONTROL_MASK
)
1175 action
= event
->button
;
1177 /* Ignore all clicks while we are dragging a lasso box */
1178 if (collection
->lasso_box
)
1181 col
= event
->x
/ collection
->item_width
;
1182 row
= (event
->y
+ scroll
) / collection
->item_height
;
1184 if (col
< 0 || row
< 0 || col
>= collection
->columns
)
1188 item
= col
+ row
* collection
->columns
;
1189 if (item
>= collection
->number_of_items
1191 !collection
->test_point(collection
,
1192 event
->x
- col
* collection
->item_width
,
1193 event
->y
- row
* collection
->item_height
1195 &collection
->items
[item
],
1196 collection
->item_width
,
1197 collection
->item_height
,
1198 collection
->cb_user_data
))
1204 if (collection
->target_cb
)
1206 CollectionTargetFunc cb
= collection
->target_cb
;
1207 gpointer data
= collection
->target_data
;
1209 collection_target(collection
, NULL
, NULL
);
1210 if (collection
->buttons_pressed
)
1212 gtk_grab_remove(widget
);
1213 collection
->buttons_pressed
= 0;
1215 if (item
> -1 && event
->button
!= collection_menu_button
)
1216 cb(collection
, item
, data
);
1220 collection
->drag_box_x
[0] = event
->x
;
1221 collection
->drag_box_y
[0] = event
->y
;
1222 collection
->item_clicked
= item
;
1224 stacked_time
= current_event_time
;
1225 current_event_time
= event
->time
;
1227 if (event
->button
== collection_menu_button
)
1229 gtk_signal_emit(GTK_OBJECT(collection
),
1230 collection_signals
[SHOW_MENU
],
1234 else if (event
->type
== GDK_2BUTTON_PRESS
&& collection
->panel
)
1238 else if ((event
->type
== GDK_2BUTTON_PRESS
&& !collection_single_click
)
1239 || collection
->panel
)
1243 if (collection
->buttons_pressed
)
1245 gtk_grab_remove(widget
);
1246 collection
->buttons_pressed
= 0;
1248 collection_unselect_item(collection
, item
);
1249 gtk_signal_emit(GTK_OBJECT(collection
),
1250 collection_signals
[OPEN_ITEM
],
1251 collection
->items
[item
].data
,
1255 else if (event
->type
== GDK_BUTTON_PRESS
)
1257 collection
->may_drag
= event
->button
!= collection_menu_button
;
1263 if (!collection
->items
[item
].selected
)
1265 collection_select_item(collection
,
1267 collection_clear_except(collection
,
1272 collection_toggle_item(collection
, item
);
1274 else if (action
== 1)
1275 collection_clear_selection(collection
);
1278 current_event_time
= stacked_time
;
1282 static gint
collection_button_release(GtkWidget
*widget
,
1283 GdkEventButton
*event
)
1285 Collection
*collection
;
1289 int col
, start_col
, last_col
;
1295 g_return_val_if_fail(widget
!= NULL
, FALSE
);
1296 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
1297 g_return_val_if_fail(event
!= NULL
, FALSE
);
1299 collection
= COLLECTION(widget
);
1300 button
= event
->button
;
1302 scroll
= collection
->vadj
->value
;
1304 if (event
->button
> 3 || event
->button
== collection_menu_button
)
1306 if (collection
->buttons_pressed
== 0)
1308 if (--collection
->buttons_pressed
== 0)
1309 gtk_grab_remove(widget
);
1311 return FALSE
; /* Wait until ALL buttons are up */
1313 if (!collection
->lasso_box
)
1315 int item
= collection
->item_clicked
;
1317 if (collection_single_click
&& item
> -1
1318 && item
< collection
->number_of_items
1319 && (event
->state
& GDK_CONTROL_MASK
) == 0)
1321 int dx
= event
->x
- collection
->drag_box_x
[0];
1322 int dy
= event
->y
- collection
->drag_box_y
[0];
1324 if (ABS(dx
) + ABS(dy
) > 9)
1327 collection_unselect_item(collection
, item
);
1328 gtk_signal_emit(GTK_OBJECT(collection
),
1329 collection_signals
[OPEN_ITEM
],
1330 collection
->items
[item
].data
,
1337 abort_lasso(collection
);
1339 w
= collection
->item_width
;
1340 h
= collection
->item_height
;
1342 top
= collection
->drag_box_y
[0] + scroll
;
1343 bottom
= collection
->drag_box_y
[1] + scroll
;
1354 row
= MAX(top
/ h
, 0);
1355 last_row
= bottom
/ h
;
1357 top
= collection
->drag_box_x
[0]; /* (left) */
1358 bottom
= collection
->drag_box_x
[1];
1368 start_col
= MAX(top
/ w
, 0);
1369 last_col
= bottom
/ w
;
1370 if (last_col
>= collection
->columns
)
1371 last_col
= collection
->columns
- 1;
1373 stacked_time
= current_event_time
;
1374 current_event_time
= event
->time
;
1376 while (row
<= last_row
)
1379 item
= row
* collection
->columns
+ col
;
1380 while (col
<= last_col
)
1382 if (item
>= collection
->number_of_items
)
1384 current_event_time
= stacked_time
;
1389 collection_select_item(collection
, item
);
1391 collection_toggle_item(collection
, item
);
1398 current_event_time
= stacked_time
;
1403 static gint
collection_motion_notify(GtkWidget
*widget
,
1404 GdkEventMotion
*event
)
1406 Collection
*collection
;
1410 g_return_val_if_fail(widget
!= NULL
, FALSE
);
1411 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
1412 g_return_val_if_fail(event
!= NULL
, FALSE
);
1414 collection
= COLLECTION(widget
);
1416 if (collection
->buttons_pressed
== 0)
1419 stacked_time
= current_event_time
;
1420 current_event_time
= event
->time
;
1422 if (event
->window
!= widget
->window
)
1423 gdk_window_get_pointer(widget
->window
, &x
, &y
, NULL
);
1430 if (collection
->lasso_box
)
1432 remove_lasso_box(collection
);
1433 collection
->drag_box_x
[1] = x
;
1434 collection
->drag_box_y
[1] = y
;
1435 add_lasso_box(collection
);
1437 else if (collection
->may_drag
)
1439 int dx
= x
- collection
->drag_box_x
[0];
1440 int dy
= y
- collection
->drag_box_y
[0];
1442 if (abs(dx
) > 9 || abs(dy
) > 9)
1445 int scroll
= collection
->vadj
->value
;
1447 collection
->may_drag
= FALSE
;
1449 col
= collection
->drag_box_x
[0]
1450 / collection
->item_width
;
1451 row
= (collection
->drag_box_y
[0] + scroll
)
1452 / collection
->item_height
;
1453 item
= item_at_row_col(collection
, row
, col
);
1455 if (item
!= -1 && collection
->test_point(collection
,
1456 collection
->drag_box_x
[0] -
1457 col
* collection
->item_width
,
1458 collection
->drag_box_y
[0]
1459 - row
* collection
->item_height
1461 &collection
->items
[item
],
1462 collection
->item_width
,
1463 collection
->item_height
,
1464 collection
->cb_user_data
))
1466 collection
->buttons_pressed
= 0;
1467 gtk_grab_remove(widget
);
1468 collection_select_item(collection
, item
);
1469 gtk_signal_emit(GTK_OBJECT(collection
),
1470 collection_signals
[DRAG_SELECTION
],
1472 collection
->number_selected
);
1476 collection
->drag_box_x
[1] = x
;
1477 collection
->drag_box_y
[1] = y
;
1478 collection_set_autoscroll(collection
, TRUE
);
1479 add_lasso_box(collection
);
1484 current_event_time
= stacked_time
;
1488 static void add_lasso_box(Collection
*collection
)
1490 g_return_if_fail(collection
!= NULL
);
1491 g_return_if_fail(IS_COLLECTION(collection
));
1492 g_return_if_fail(collection
->lasso_box
== FALSE
);
1494 collection
->lasso_box
= TRUE
;
1495 draw_lasso_box(collection
);
1498 static void draw_lasso_box(Collection
*collection
)
1501 int x
, y
, width
, height
;
1503 widget
= GTK_WIDGET(collection
);
1505 x
= MIN(collection
->drag_box_x
[0], collection
->drag_box_x
[1]);
1506 y
= MIN(collection
->drag_box_y
[0], collection
->drag_box_y
[1]);
1507 width
= abs(collection
->drag_box_x
[1] - collection
->drag_box_x
[0]);
1508 height
= abs(collection
->drag_box_y
[1] - collection
->drag_box_y
[0]);
1510 gdk_draw_rectangle(widget
->window
, collection
->xor_gc
, FALSE
,
1511 x
, y
, width
, height
);
1514 static void abort_lasso(Collection
*collection
)
1516 if (collection
->lasso_box
)
1518 remove_lasso_box(collection
);
1519 collection_set_autoscroll(collection
, FALSE
);
1523 static void remove_lasso_box(Collection
*collection
)
1525 g_return_if_fail(collection
!= NULL
);
1526 g_return_if_fail(IS_COLLECTION(collection
));
1527 g_return_if_fail(collection
->lasso_box
== TRUE
);
1529 draw_lasso_box(collection
);
1531 collection
->lasso_box
= FALSE
;
1536 /* Convert a row,col address to an item number, or -1 if none */
1537 static int item_at_row_col(Collection
*collection
, int row
, int col
)
1541 if (row
< 0 || col
< 0 || col
>= collection
->columns
)
1544 item
= col
+ row
* collection
->columns
;
1546 if (item
>= collection
->number_of_items
)
1551 /* Make sure that 'item' is fully visible (vertically), scrolling if not. */
1552 static void scroll_to_show(Collection
*collection
, int item
)
1554 int first
, last
, row
;
1556 g_return_if_fail(collection
!= NULL
);
1557 g_return_if_fail(IS_COLLECTION(collection
));
1559 row
= item
/ collection
->columns
;
1560 get_visible_limits(collection
, &first
, &last
);
1564 gtk_adjustment_set_value(collection
->vadj
,
1565 row
* collection
->item_height
);
1567 else if (row
>= last
)
1569 GtkWidget
*widget
= (GtkWidget
*) collection
;
1572 if (GTK_WIDGET_REALIZED(widget
))
1574 gdk_window_get_size(widget
->window
, NULL
, &height
);
1575 gtk_adjustment_set_value(collection
->vadj
,
1576 (row
+ 1) * collection
->item_height
- height
);
1581 /* Return the first and last rows which are [partly] visible. Does not
1582 * ensure that the rows actually exist (contain items).
1584 static void get_visible_limits(Collection
*collection
, int *first
, int *last
)
1586 GtkWidget
*widget
= (GtkWidget
*) collection
;
1589 g_return_if_fail(collection
!= NULL
);
1590 g_return_if_fail(IS_COLLECTION(collection
));
1591 g_return_if_fail(first
!= NULL
&& last
!= NULL
);
1593 if (!GTK_WIDGET_REALIZED(widget
))
1600 scroll
= collection
->vadj
->value
;
1601 gdk_window_get_size(widget
->window
, NULL
, &height
);
1603 *first
= MAX(scroll
/ collection
->item_height
, 0);
1604 *last
= (scroll
+ height
- 1) /collection
->item_height
;
1611 /* Unselect all items except number item (-1 to unselect everything) */
1612 static void collection_clear_except(Collection
*collection
, gint exception
)
1618 int end
; /* Selected items to end up with */
1620 widget
= GTK_WIDGET(collection
);
1621 scroll
= collection
->vadj
->value
;
1623 end
= exception
>= 0 && exception
< collection
->number_of_items
1624 ? collection
->items
[exception
].selected
!= 0 : 0;
1626 area
.width
= collection
->item_width
;
1627 area
.height
= collection
->item_height
;
1629 if (collection
->number_selected
== 0)
1632 while (collection
->number_selected
> end
)
1634 while (item
== exception
|| !collection
->items
[item
].selected
)
1637 area
.x
= (item
% collection
->columns
) * area
.width
;
1638 area
.y
= (item
/ collection
->columns
) * area
.height
1641 collection
->items
[item
++].selected
= FALSE
;
1642 clear_area(collection
, &area
);
1643 collection_paint(collection
, &area
);
1645 collection
->number_selected
--;
1649 gtk_signal_emit(GTK_OBJECT(collection
),
1650 collection_signals
[LOSE_SELECTION
],
1651 current_event_time
);
1654 /* Cancel the current wink effect. */
1655 static void cancel_wink(Collection
*collection
)
1659 g_return_if_fail(collection
!= NULL
);
1660 g_return_if_fail(IS_COLLECTION(collection
));
1661 g_return_if_fail(collection
->wink_item
!= -1);
1663 item
= collection
->wink_item
;
1665 collection
->wink_item
= -1;
1666 gtk_timeout_remove(collection
->wink_timeout
);
1668 collection_draw_item(collection
, item
, TRUE
);
1671 static gboolean
cancel_wink_timeout(Collection
*collection
)
1675 g_return_val_if_fail(collection
!= NULL
, FALSE
);
1676 g_return_val_if_fail(IS_COLLECTION(collection
), FALSE
);
1677 g_return_val_if_fail(collection
->wink_item
!= -1, FALSE
);
1679 item
= collection
->wink_item
;
1681 collection
->wink_item
= -1;
1683 collection_draw_item(collection
, item
, TRUE
);
1688 static gint
focus_in(GtkWidget
*widget
, GdkEventFocus
*event
)
1690 g_return_val_if_fail(widget
!= NULL
, FALSE
);
1691 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
1692 g_return_val_if_fail(event
!= NULL
, FALSE
);
1694 GTK_WIDGET_SET_FLAGS(widget
, GTK_HAS_FOCUS
);
1695 gtk_widget_draw_focus(widget
);
1700 static gint
focus_out(GtkWidget
*widget
, GdkEventFocus
*event
)
1702 g_return_val_if_fail(widget
!= NULL
, FALSE
);
1703 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
1704 g_return_val_if_fail(event
!= NULL
, FALSE
);
1706 GTK_WIDGET_UNSET_FLAGS(widget
, GTK_HAS_FOCUS
);
1707 gtk_widget_draw_focus(widget
);
1712 /* This is called frequently while auto_scroll is on.
1713 * Checks the pointer position and scrolls the window if it's
1714 * near the top or bottom.
1716 static gboolean
as_timeout(Collection
*collection
)
1718 GdkWindow
*window
= GTK_WIDGET(collection
)->window
;
1720 GdkModifierType mask
;
1723 gdk_window_get_pointer(window
, &x
, &y
, &mask
);
1724 gdk_window_get_size(window
, &w
, &h
);
1726 if ((x
< 0 || x
> w
|| y
< 0 || y
> h
) && !collection
->lasso_box
)
1728 collection
->auto_scroll
= -1;
1729 return FALSE
; /* Out of window - stop */
1734 else if (y
> h
- 20)
1738 diff_vpos(collection
, diff
);
1743 /* Functions for managing collections */
1745 /* Remove all objects from the collection */
1746 void collection_clear(Collection
*collection
)
1750 g_return_if_fail(IS_COLLECTION(collection
));
1752 if (collection
->number_of_items
== 0)
1755 if (collection
->wink_item
!= -1)
1757 collection
->wink_item
= -1;
1758 gtk_timeout_remove(collection
->wink_timeout
);
1761 collection_set_cursor_item(collection
,
1762 collection
->cursor_item
== -1 ? -1: 0);
1763 collection
->cursor_item_old
= -1;
1764 prev_selected
= collection
->number_selected
;
1765 collection
->number_of_items
= collection
->number_selected
= 0;
1767 resize_arrays(collection
, MINIMUM_ITEMS
);
1769 collection
->paint_level
= PAINT_CLEAR
;
1771 gtk_widget_queue_clear(GTK_WIDGET(collection
));
1773 if (prev_selected
&& collection
->number_selected
== 0)
1774 gtk_signal_emit(GTK_OBJECT(collection
),
1775 collection_signals
[LOSE_SELECTION
],
1776 current_event_time
);
1779 /* Inserts a new item at the end. The new item is unselected, and its
1780 * number is returned.
1782 gint
collection_insert(Collection
*collection
, gpointer data
)
1786 g_return_val_if_fail(IS_COLLECTION(collection
), -1);
1788 item
= collection
->number_of_items
;
1790 if (item
>= collection
->array_size
)
1791 resize_arrays(collection
, item
+ (item
>> 1));
1793 collection
->items
[item
].data
= data
;
1794 collection
->items
[item
].selected
= FALSE
;
1796 collection
->number_of_items
++;
1798 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection
)))
1800 set_vadjustment(collection
);
1801 collection_draw_item(collection
,
1802 collection
->number_of_items
- 1,
1809 /* Unselect an item by number */
1810 void collection_unselect_item(Collection
*collection
, gint item
)
1812 g_return_if_fail(collection
!= NULL
);
1813 g_return_if_fail(IS_COLLECTION(collection
));
1814 g_return_if_fail(item
>= 0 && item
< collection
->number_of_items
);
1816 if (collection
->items
[item
].selected
)
1818 collection
->items
[item
].selected
= FALSE
;
1819 collection_draw_item(collection
, item
, TRUE
);
1821 if (--collection
->number_selected
== 0)
1822 gtk_signal_emit(GTK_OBJECT(collection
),
1823 collection_signals
[LOSE_SELECTION
],
1824 current_event_time
);
1828 /* Select an item by number */
1829 void collection_select_item(Collection
*collection
, gint item
)
1831 g_return_if_fail(collection
!= NULL
);
1832 g_return_if_fail(IS_COLLECTION(collection
));
1833 g_return_if_fail(item
>= 0 && item
< collection
->number_of_items
);
1835 if (collection
->items
[item
].selected
)
1836 return; /* Already selected */
1838 collection
->items
[item
].selected
= TRUE
;
1839 collection_draw_item(collection
, item
, TRUE
);
1841 if (collection
->number_selected
++ == 0)
1842 gtk_signal_emit(GTK_OBJECT(collection
),
1843 collection_signals
[GAIN_SELECTION
],
1844 current_event_time
);
1847 /* Toggle the selected state of an item (by number) */
1848 void collection_toggle_item(Collection
*collection
, gint item
)
1850 g_return_if_fail(collection
!= NULL
);
1851 g_return_if_fail(IS_COLLECTION(collection
));
1852 g_return_if_fail(item
>= 0 && item
< collection
->number_of_items
);
1854 if (collection
->items
[item
].selected
)
1856 collection
->items
[item
].selected
= FALSE
;
1857 if (--collection
->number_selected
== 0)
1858 gtk_signal_emit(GTK_OBJECT(collection
),
1859 collection_signals
[LOSE_SELECTION
],
1860 current_event_time
);
1864 collection
->items
[item
].selected
= TRUE
;
1865 if (collection
->number_selected
++ == 0)
1866 gtk_signal_emit(GTK_OBJECT(collection
),
1867 collection_signals
[GAIN_SELECTION
],
1868 current_event_time
);
1870 collection_draw_item(collection
, item
, TRUE
);
1873 /* Select all items in the collection */
1874 void collection_select_all(Collection
*collection
)
1881 g_return_if_fail(collection
!= NULL
);
1882 g_return_if_fail(IS_COLLECTION(collection
));
1884 widget
= GTK_WIDGET(collection
);
1885 scroll
= collection
->vadj
->value
;
1887 area
.width
= collection
->item_width
;
1888 area
.height
= collection
->item_height
;
1890 if (collection
->number_selected
== collection
->number_of_items
)
1891 return; /* Nothing to do */
1893 while (collection
->number_selected
< collection
->number_of_items
)
1895 while (collection
->items
[item
].selected
)
1898 area
.x
= (item
% collection
->columns
) * area
.width
;
1899 area
.y
= (item
/ collection
->columns
) * area
.height
1902 collection
->items
[item
++].selected
= TRUE
;
1903 clear_area(collection
, &area
);
1904 collection_paint(collection
, &area
);
1906 collection
->number_selected
++;
1909 gtk_signal_emit(GTK_OBJECT(collection
),
1910 collection_signals
[GAIN_SELECTION
],
1911 current_event_time
);
1914 /* Unselect all items in the collection */
1915 void collection_clear_selection(Collection
*collection
)
1917 g_return_if_fail(collection
!= NULL
);
1918 g_return_if_fail(IS_COLLECTION(collection
));
1920 collection_clear_except(collection
, -1);
1923 /* Force a redraw of the specified item, if it is visible */
1924 void collection_draw_item(Collection
*collection
, gint item
, gboolean blank
)
1931 int area_y
, area_height
; /* NOT shorts! */
1933 g_return_if_fail(collection
!= NULL
);
1934 g_return_if_fail(IS_COLLECTION(collection
));
1935 g_return_if_fail(item
>= 0 &&
1936 (item
== 0 || item
< collection
->number_of_items
));
1938 widget
= GTK_WIDGET(collection
);
1939 if (!GTK_WIDGET_REALIZED(widget
))
1942 col
= item
% collection
->columns
;
1943 row
= item
/ collection
->columns
;
1944 scroll
= collection
->vadj
->value
; /* (round to int) */
1946 area
.x
= col
* collection
->item_width
;
1947 area_y
= row
* collection
->item_height
- scroll
;
1948 area
.width
= collection
->item_width
;
1949 area_height
= collection
->item_height
;
1951 if (area_y
+ area_height
< 0)
1954 gdk_window_get_size(widget
->window
, NULL
, &height
);
1956 if (area_y
> height
)
1960 area
.height
= area_height
;
1962 if (blank
|| collection
->lasso_box
)
1963 clear_area(collection
, &area
);
1965 draw_one_item(collection
, item
, &area
);
1967 if (collection
->lasso_box
)
1969 gdk_gc_set_clip_rectangle(collection
->xor_gc
, &area
);
1970 draw_lasso_box(collection
);
1971 gdk_gc_set_clip_rectangle(collection
->xor_gc
, NULL
);
1975 void collection_set_item_size(Collection
*collection
, int width
, int height
)
1979 g_return_if_fail(collection
!= NULL
);
1980 g_return_if_fail(IS_COLLECTION(collection
));
1981 g_return_if_fail(width
> 4 && height
> 4);
1983 if (collection
->item_width
== width
&&
1984 collection
->item_height
== height
)
1987 widget
= GTK_WIDGET(collection
);
1989 collection
->item_width
= width
;
1990 collection
->item_height
= height
;
1992 if (GTK_WIDGET_REALIZED(widget
))
1996 collection
->paint_level
= PAINT_CLEAR
;
1997 gdk_window_get_size(widget
->window
, &window_width
, NULL
);
1998 collection
->columns
= MAX(window_width
/ collection
->item_width
,
2001 set_vadjustment(collection
);
2002 if (collection
->cursor_item
!= -1)
2003 scroll_to_show(collection
, collection
->cursor_item
);
2004 gtk_widget_queue_draw(widget
);
2008 /* Cursor is positioned on item with the same data as before the sort.
2009 * Same for the wink item.
2011 void collection_qsort(Collection
*collection
,
2012 int (*compar
)(const void *, const void *))
2014 int cursor
, wink
, items
;
2015 gpointer cursor_data
= NULL
;
2016 gpointer wink_data
= NULL
;
2018 g_return_if_fail(collection
!= NULL
);
2019 g_return_if_fail(IS_COLLECTION(collection
));
2020 g_return_if_fail(compar
!= NULL
);
2022 items
= collection
->number_of_items
;
2024 wink
= collection
->wink_item
;
2025 if (wink
>= 0 && wink
< items
)
2026 wink_data
= collection
->items
[wink
].data
;
2030 cursor
= collection
->cursor_item
;
2031 if (cursor
>= 0 && cursor
< items
)
2032 cursor_data
= collection
->items
[cursor
].data
;
2036 if (collection
->wink_item
!= -1)
2038 collection
->wink_item
= -1;
2039 gtk_timeout_remove(collection
->wink_timeout
);
2042 qsort(collection
->items
, items
, sizeof(collection
->items
[0]), compar
);
2044 if (cursor
> -1 || wink
> -1)
2048 for (item
= 0; item
< items
; item
++)
2050 if (collection
->items
[item
].data
== cursor_data
)
2051 collection_set_cursor_item(collection
, item
);
2052 if (collection
->items
[item
].data
== wink_data
)
2053 collection_wink_item(collection
, item
);
2057 collection
->paint_level
= PAINT_CLEAR
;
2059 gtk_widget_queue_draw(GTK_WIDGET(collection
));
2062 /* Find an item in an unsorted collection.
2063 * Returns the item number, or -1 if not found.
2065 int collection_find_item(Collection
*collection
, gpointer data
,
2066 int (*compar
)(const void *, const void *))
2070 g_return_val_if_fail(collection
!= NULL
, -1);
2071 g_return_val_if_fail(IS_COLLECTION(collection
), -1);
2072 g_return_val_if_fail(compar
!= NULL
, -1);
2074 for (i
= 0; i
< collection
->number_of_items
; i
++)
2075 if (compar(&collection
->items
[i
].data
, &data
) == 0)
2081 /* The collection may be in either normal mode or panel mode.
2083 * - a single click calls open_item
2084 * - items are never selected
2085 * - lasso boxes are disabled
2087 void collection_set_panel(Collection
*collection
, gboolean panel
)
2089 g_return_if_fail(collection
!= NULL
);
2090 g_return_if_fail(IS_COLLECTION(collection
));
2092 collection
->panel
= panel
== TRUE
;
2094 if (collection
->panel
)
2096 collection_clear_selection(collection
);
2097 abort_lasso(collection
);
2101 /* Return the number of the item under the point (x,y), or -1 for none.
2102 * This may call your test_point callback. The point is relative to the
2103 * collection's origin.
2105 int collection_get_item(Collection
*collection
, int x
, int y
)
2111 g_return_val_if_fail(collection
!= NULL
, -1);
2113 scroll
= collection
->vadj
->value
;
2114 col
= x
/ collection
->item_width
;
2115 row
= (y
+ scroll
) / collection
->item_height
;
2117 if (col
< 0 || row
< 0 || col
>= collection
->columns
)
2120 item
= col
+ row
* collection
->columns
;
2121 if (item
>= collection
->number_of_items
2123 !collection
->test_point(collection
,
2124 x
- col
* collection
->item_width
,
2125 y
- row
* collection
->item_height
2127 &collection
->items
[item
],
2128 collection
->item_width
,
2129 collection
->item_height
,
2130 collection
->cb_user_data
))
2138 /* Set the cursor/highlight over the given item. Passing -1
2139 * hides the cursor. As a special case, you may set the cursor item
2140 * to zero when there are no items.
2142 void collection_set_cursor_item(Collection
*collection
, gint item
)
2146 g_return_if_fail(collection
!= NULL
);
2147 g_return_if_fail(IS_COLLECTION(collection
));
2148 g_return_if_fail(item
>= -1 &&
2149 (item
< collection
->number_of_items
|| item
== 0));
2151 old_item
= collection
->cursor_item
;
2153 if (old_item
== item
)
2156 collection
->cursor_item
= item
;
2159 collection_draw_item(collection
, old_item
, TRUE
);
2163 collection_draw_item(collection
, item
, TRUE
);
2164 if (collection
->auto_scroll
== -1)
2165 scroll_to_show(collection
, item
);
2167 else if (old_item
!= -1)
2168 collection
->cursor_item_old
= old_item
;
2171 /* Briefly highlight an item to draw the user's attention to it.
2172 * -1 cancels the effect, as does deleting items, sorting the collection
2173 * or starting a new wink effect.
2174 * Otherwise, the effect will cancel itself after a short pause.
2176 void collection_wink_item(Collection
*collection
, gint item
)
2178 g_return_if_fail(collection
!= NULL
);
2179 g_return_if_fail(IS_COLLECTION(collection
));
2180 g_return_if_fail(item
>= -1 && item
< collection
->number_of_items
);
2182 if (collection
->wink_item
!= -1)
2183 cancel_wink(collection
);
2187 collection
->cursor_item_old
= collection
->wink_item
= item
;
2188 collection
->wink_timeout
= gtk_timeout_add(300,
2189 (GtkFunction
) cancel_wink_timeout
,
2191 collection_draw_item(collection
, item
, TRUE
);
2192 scroll_to_show(collection
, item
);
2197 /* Call test(item, data) on each item in the collection.
2198 * Remove all items for which it returns TRUE. test() should
2199 * free the data before returning TRUE. The collection is in an
2200 * inconsistant state during this call (ie, when test() is called).
2202 void collection_delete_if(Collection
*collection
,
2203 gboolean (*test
)(gpointer item
, gpointer data
),
2210 g_return_if_fail(collection
!= NULL
);
2211 g_return_if_fail(IS_COLLECTION(collection
));
2212 g_return_if_fail(test
!= NULL
);
2214 cursor
= collection
->cursor_item
;
2216 for (in
= 0; in
< collection
->number_of_items
; in
++)
2218 if (!test(collection
->items
[in
].data
, data
))
2220 if (collection
->items
[in
].selected
)
2222 collection
->items
[out
].selected
= TRUE
;
2226 collection
->items
[out
].selected
= FALSE
;
2228 collection
->items
[out
++].data
=
2229 collection
->items
[in
].data
;
2231 else if (cursor
>= in
)
2237 collection
->cursor_item
= cursor
;
2239 if (collection
->wink_item
!= -1)
2241 collection
->wink_item
= -1;
2242 gtk_timeout_remove(collection
->wink_timeout
);
2245 collection
->number_of_items
= out
;
2246 collection
->number_selected
= selected
;
2247 resize_arrays(collection
,
2248 MAX(collection
->number_of_items
, MINIMUM_ITEMS
));
2250 collection
->paint_level
= PAINT_CLEAR
;
2252 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection
)))
2254 set_vadjustment(collection
);
2255 gtk_widget_queue_draw(GTK_WIDGET(collection
));
2260 /* Display a cross-hair pointer and the next time an item is clicked,
2261 * call the callback function. If the background is clicked or NULL
2262 * is passed as the callback then revert to normal operation.
2264 void collection_target(Collection
*collection
,
2265 CollectionTargetFunc callback
,
2268 g_return_if_fail(collection
!= NULL
);
2269 g_return_if_fail(IS_COLLECTION(collection
));
2271 if (callback
!= collection
->target_cb
)
2272 gdk_window_set_cursor(GTK_WIDGET(collection
)->window
,
2273 callback
? crosshair
: NULL
);
2275 collection
->target_cb
= callback
;
2276 collection
->target_data
= user_data
;
2278 if (collection
->cursor_item
!= -1)
2279 collection_draw_item(collection
, collection
->cursor_item
,
2283 /* Move the cursor by the given row and column offsets.
2284 * Moving by (0,0) can be used to simply make the cursor appear.
2286 void collection_move_cursor(Collection
*collection
, int drow
, int dcol
)
2289 int first
, last
, total_rows
;
2291 g_return_if_fail(collection
!= NULL
);
2292 g_return_if_fail(IS_COLLECTION(collection
));
2294 get_visible_limits(collection
, &first
, &last
);
2296 item
= collection
->cursor_item
;
2299 item
= MIN(collection
->cursor_item_old
,
2300 collection
->number_of_items
- 1);
2310 row
= item
/ collection
->columns
;
2311 col
= item
% collection
->columns
+ dcol
;
2315 else if (row
> last
)
2318 row
= MAX(row
+ drow
, 0);
2321 total_rows
= (collection
->number_of_items
+ collection
->columns
- 1)
2322 / collection
->columns
;
2324 if (row
>= total_rows
- 1 && drow
> 0)
2326 row
= total_rows
- 1;
2327 item
= col
+ row
* collection
->columns
;
2328 if (item
>= collection
->number_of_items
)
2331 scroll_to_show(collection
, item
);
2337 item
= col
+ row
* collection
->columns
;
2339 if (item
>= 0 && item
< collection
->number_of_items
)
2340 collection_set_cursor_item(collection
, item
);
2343 /* When autoscroll is on, a timer keeps track of the pointer position.
2344 * While it's near the top or bottom of the window, the window scrolls.
2346 * If the mouse buttons are released, or the pointer leaves the window,
2347 * auto_scroll is turned off.
2349 void collection_set_autoscroll(Collection
*collection
, gboolean auto_scroll
)
2351 g_return_if_fail(collection
!= NULL
);
2352 g_return_if_fail(IS_COLLECTION(collection
));
2356 if (collection
->auto_scroll
!= -1)
2357 return; /* Already on! */
2359 collection
->auto_scroll
= gtk_timeout_add(50,
2360 (GtkFunction
) as_timeout
,
2365 if (collection
->auto_scroll
== -1)
2366 return; /* Already off! */
2368 gtk_timeout_remove(collection
->auto_scroll
);
2369 collection
->auto_scroll
= -1;