4 * Collection - a GTK+ widget
5 * Copyright (C) 2002, the ROX-Filer team.
7 * The collection widget provides an area for displaying a collection of
8 * objects (such as files). It allows the user to choose a selection of
9 * them and provides signals to allow popping up menus, detecting
12 * This program is free software; you can redistribute it and/or modify it
13 * under the terms of the GNU General Public License as published by the Free
14 * Software Foundation; either version 2 of the License, or (at your option)
17 * This program is distributed in the hope that it will be useful, but WITHOUT
18 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
22 * You should have received a copy of the GNU General Public License along with
23 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
24 * Place, Suite 330, Boston, MA 02111-1307 USA
32 #include <gdk/gdkkeysyms.h>
35 #include "collection.h"
39 #define MINIMUM_ITEMS 16
40 #define AUTOSCROLL_STEP 20
42 #define MAX_WINKS 3 /* Should be an odd number */
44 /* Macro to emit the "selection_changed" signal only if allowed */
45 #define EMIT_SELECTION_CHANGED(collection, time) \
46 if (!collection->block_selection_changed) \
47 gtk_signal_emit(GTK_OBJECT(collection), \
48 collection_signals[SELECTION_CHANGED], time)
58 * void gain_selection(collection, time, user_data)
59 * We've gone from no selected items to having a selection.
60 * Time is the time of the event that caused the change, or
61 * GDK_CURRENT_TIME if not known.
63 * void lose_selection(collection, time, user_data)
64 * We've dropped to having no selected items.
65 * Time is the time of the event that caused the change, or
66 * GDK_CURRENT_TIME if not known.
68 * void selection_changed(collection, user_data)
69 * The set of selected items has changed.
70 * Time is the time of the event that caused the change, or
71 * GDK_CURRENT_TIME if not known.
81 static guint collection_signals
[LAST_SIGNAL
] = { 0 };
83 static guint32 current_event_time
= GDK_CURRENT_TIME
;
85 static GtkWidgetClass
*parent_class
= NULL
;
87 /* Static prototypes */
88 static void clear_area(Collection
*collection
, GdkRectangle
*area
);
89 static void draw_one_item(Collection
*collection
,
92 static void collection_class_init(CollectionClass
*class);
93 static void collection_init(Collection
*object
);
94 static void collection_destroy(GtkObject
*object
);
95 static void collection_finalize(GtkObject
*object
);
96 static void collection_realize(GtkWidget
*widget
);
97 static void collection_map(GtkWidget
*widget
);
98 static gint
collection_paint(Collection
*collection
,
100 static void collection_size_request(GtkWidget
*widget
,
101 GtkRequisition
*requisition
);
102 static void collection_size_allocate(GtkWidget
*widget
,
103 GtkAllocation
*allocation
);
104 static void collection_set_adjustment(Collection
*collection
,
105 GtkAdjustment
*vadj
);
106 static void collection_set_arg( GtkObject
*object
,
109 static void collection_get_arg( GtkObject
*object
,
112 static gint
collection_expose(GtkWidget
*widget
, GdkEventExpose
*event
);
113 static void default_draw_item(GtkWidget
*widget
,
114 CollectionItem
*data
,
117 static gboolean
default_test_point(Collection
*collection
,
118 int point_x
, int point_y
,
119 CollectionItem
*data
,
120 int width
, int height
,
122 static gint
collection_motion_notify(GtkWidget
*widget
,
123 GdkEventMotion
*event
);
124 static void add_lasso_box(Collection
*collection
);
125 static void abort_lasso(Collection
*collection
);
126 static void remove_lasso_box(Collection
*collection
);
127 static void draw_lasso_box(Collection
*collection
);
128 static void cancel_wink(Collection
*collection
);
129 static gint
collection_key_press(GtkWidget
*widget
, GdkEventKey
*event
);
130 static void get_visible_limits(Collection
*collection
, int *first
, int *last
);
131 static void scroll_to_show(Collection
*collection
, int item
);
132 static void collection_item_set_selected(Collection
*collection
,
137 static gint
collection_scroll_event(GtkWidget
*widget
, GdkEventScroll
*event
);
139 static gint
collection_scroll_event(GtkWidget
*widget
, GdkEventButton
*event
);
143 static void collection_adjustment(GtkAdjustment
*adjustment
,
144 Collection
*collection
);
145 static void collection_set_style(GtkWidget
*widget
,
146 GtkStyle
*previous_style
);
147 static void collection_disconnect(GtkAdjustment
*adjustment
,
148 Collection
*collection
);
149 static void scroll_by(Collection
*collection
, gint diff
);
150 static void set_vadjustment(Collection
*collection
);
151 static void draw_focus(GtkWidget
*widget
);
152 static void collection_draw(GtkWidget
*widget
, GdkRectangle
*area
);
153 static gint
focus_in(GtkWidget
*widget
, GdkEventFocus
*event
);
154 static gint
focus_out(GtkWidget
*widget
, GdkEventFocus
*event
);
157 static void draw_focus_at(Collection
*collection
, GdkRectangle
*area
)
162 widget
= GTK_WIDGET(collection
);
164 if (GTK_WIDGET_FLAGS(widget
) & GTK_HAS_FOCUS
)
165 gc
= widget
->style
->fg_gc
[GTK_STATE_ACTIVE
];
167 gc
= widget
->style
->fg_gc
[GTK_STATE_INSENSITIVE
];
169 gdk_draw_rectangle(widget
->window
, gc
, FALSE
,
170 area
->x
+ 1, area
->y
+ 1,
171 collection
->item_width
- 3,
175 static void draw_one_item(Collection
*collection
, int item
, GdkRectangle
*area
)
177 if (item
< collection
->number_of_items
)
179 collection
->draw_item((GtkWidget
*) collection
,
180 &collection
->items
[item
],
182 collection
->cb_user_data
);
185 if (item
== collection
->cursor_item
)
186 draw_focus_at(collection
, area
);
190 static void draw_focus(GtkWidget
*widget
)
192 Collection
*collection
;
194 g_return_if_fail(widget
!= NULL
);
195 g_return_if_fail(IS_COLLECTION(widget
));
197 collection
= COLLECTION(widget
);
199 if (collection
->cursor_item
< 0 || !GTK_WIDGET_REALIZED(widget
))
202 collection_draw_item(collection
, collection
->cursor_item
, FALSE
);
206 GtkType
collection_get_type(void)
208 static guint my_type
= 0;
212 static const GtkTypeInfo my_info
=
216 sizeof(CollectionClass
),
217 (GtkClassInitFunc
) collection_class_init
,
218 (GtkObjectInitFunc
) collection_init
,
219 NULL
, /* Reserved 1 */
220 NULL
, /* Reserved 2 */
221 (GtkClassInitFunc
) NULL
/* base_class_init_func */
224 my_type
= gtk_type_unique(gtk_widget_get_type(),
232 typedef void (*FinalizeFn
)(GObject
*object
);
235 static void collection_class_init(CollectionClass
*class)
237 GtkObjectClass
*object_class
;
238 GtkWidgetClass
*widget_class
;
241 object_class
= (GtkObjectClass
*) class;
242 widget_class
= (GtkWidgetClass
*) class;
244 parent_class
= gtk_type_class(gtk_widget_get_type());
246 gtk_object_add_arg_type("Collection::vadjustment",
248 GTK_ARG_READWRITE
| GTK_ARG_CONSTRUCT
,
251 object_class
->destroy
= collection_destroy
;
253 G_OBJECT_CLASS(object_class
)->finalize
=
254 (FinalizeFn
) collection_finalize
;
255 type
= GTK_CLASS_TYPE(object_class
);
257 object_class
->finalize
= collection_finalize
;
258 type
= object_class
->type
;
261 widget_class
->realize
= collection_realize
;
262 widget_class
->expose_event
= collection_expose
;
263 widget_class
->size_request
= collection_size_request
;
264 widget_class
->size_allocate
= collection_size_allocate
;
266 widget_class
->key_press_event
= collection_key_press
;
268 widget_class
->motion_notify_event
= collection_motion_notify
;
269 widget_class
->map
= collection_map
;
271 widget_class
->scroll_event
= collection_scroll_event
;
273 widget_class
->button_press_event
= collection_scroll_event
;
277 widget_class
->style_set
= collection_set_style
; /* XXX: Test for 2.0 */
278 widget_class
->focus_in_event
= focus_in
;
279 widget_class
->focus_out_event
= focus_out
;
280 widget_class
->draw
= collection_draw
;
281 widget_class
->draw_focus
= draw_focus
;
284 object_class
->set_arg
= collection_set_arg
;
285 object_class
->get_arg
= collection_get_arg
;
287 class->gain_selection
= NULL
;
288 class->lose_selection
= NULL
;
289 class->selection_changed
= NULL
;
291 collection_signals
[GAIN_SELECTION
] = gtk_signal_new("gain_selection",
294 GTK_SIGNAL_OFFSET(CollectionClass
,
296 gtk_marshal_NONE__INT
,
299 collection_signals
[LOSE_SELECTION
] = gtk_signal_new("lose_selection",
302 GTK_SIGNAL_OFFSET(CollectionClass
,
304 gtk_marshal_NONE__INT
,
307 collection_signals
[SELECTION_CHANGED
] = gtk_signal_new(
311 GTK_SIGNAL_OFFSET(CollectionClass
,
313 gtk_marshal_NONE__INT
,
318 gtk_object_class_add_signals(object_class
,
319 collection_signals
, LAST_SIGNAL
);
323 static void collection_init(Collection
*object
)
325 g_return_if_fail(object
!= NULL
);
326 g_return_if_fail(IS_COLLECTION(object
));
328 GTK_WIDGET_SET_FLAGS(GTK_WIDGET(object
), GTK_CAN_FOCUS
);
330 object
->number_of_items
= 0;
331 object
->number_selected
= 0;
332 object
->block_selection_changed
= 0;
334 object
->item_width
= 64;
335 object
->item_height
= 64;
338 object
->paint_level
= PAINT_OVERWRITE
;
339 object
->last_scroll
= 0;
341 object
->bg_gc
= NULL
;
343 object
->items
= g_new(CollectionItem
, MINIMUM_ITEMS
);
344 object
->cursor_item
= -1;
345 object
->cursor_item_old
= -1;
346 object
->wink_item
= -1;
347 object
->wink_on_map
= -1;
348 object
->array_size
= MINIMUM_ITEMS
;
349 object
->draw_item
= default_draw_item
;
350 object
->test_point
= default_test_point
;
351 object
->free_item
= NULL
;
353 object
->auto_scroll
= -1;
358 GtkWidget
* collection_new(void)
360 return GTK_WIDGET(gtk_widget_new(collection_get_type(), NULL
));
363 /* Note: The draw_item call gives the maximum area that can be
364 * drawn to. For the column on the far right, this extends to the
365 * edge of the window. Normally, use collection->item_width instead
366 * of area->width to calculate the position.
368 * test_point does not use a larger value for the width, but the
369 * x point of the click may be larger than the width.
371 void collection_set_functions(Collection
*collection
,
372 CollectionDrawFunc draw_item
,
373 CollectionTestFunc test_point
,
378 g_return_if_fail(collection
!= NULL
);
379 g_return_if_fail(IS_COLLECTION(collection
));
381 widget
= GTK_WIDGET(collection
);
384 draw_item
= default_draw_item
;
386 test_point
= default_test_point
;
388 collection
->draw_item
= draw_item
;
389 collection
->test_point
= test_point
;
390 collection
->cb_user_data
= user_data
;
392 if (GTK_WIDGET_REALIZED(widget
))
395 collection
->paint_level
= PAINT_CLEAR
;
397 gtk_widget_queue_clear(widget
);
401 /* After this we are unusable, but our data (if any) is still hanging around.
402 * It will be freed later with finalize.
404 static void collection_destroy(GtkObject
*object
)
406 Collection
*collection
;
408 g_return_if_fail(object
!= NULL
);
409 g_return_if_fail(IS_COLLECTION(object
));
411 collection
= COLLECTION(object
);
413 collection_clear(collection
);
415 if (collection
->auto_scroll
!= -1)
417 gtk_timeout_remove(collection
->auto_scroll
);
418 collection
->auto_scroll
= -1;
421 if (collection
->bg_gc
)
423 gdk_gc_destroy(collection
->bg_gc
);
424 collection
->bg_gc
= NULL
;
427 if (collection
->vadj
)
430 gtk_signal_disconnect_by_data(GTK_OBJECT(collection
->vadj
),
433 gtk_object_unref(GTK_OBJECT(collection
->vadj
));
434 collection
->vadj
= NULL
;
437 if (GTK_OBJECT_CLASS(parent_class
)->destroy
)
438 (*GTK_OBJECT_CLASS(parent_class
)->destroy
)(object
);
441 /* This is the last thing that happens to us. Free all data. */
442 static void collection_finalize(GtkObject
*object
)
444 Collection
*collection
;
446 collection
= COLLECTION(object
);
448 g_return_if_fail(collection
->number_of_items
== 0);
450 g_free(collection
->items
);
453 static void collection_map(GtkWidget
*widget
)
455 Collection
*collection
= COLLECTION(widget
);
457 if (GTK_WIDGET_CLASS(parent_class
)->map
)
458 (*GTK_WIDGET_CLASS(parent_class
)->map
)(widget
);
460 if (collection
->wink_on_map
>= 0)
462 collection_wink_item(collection
, collection
->wink_on_map
);
463 collection
->wink_on_map
= -1;
467 static GdkGC
*create_bg_gc(GtkWidget
*widget
)
471 values
.tile
= widget
->style
->bg_pixmap
[GTK_STATE_NORMAL
];
472 values
.fill
= GDK_TILED
;
474 return gdk_gc_new_with_values(widget
->window
, &values
,
475 GDK_GC_FILL
| GDK_GC_TILE
);
478 static void collection_realize(GtkWidget
*widget
)
480 Collection
*collection
;
481 GdkWindowAttr attributes
;
482 gint attributes_mask
;
483 GdkGCValues xor_values
;
486 g_return_if_fail(widget
!= NULL
);
487 g_return_if_fail(IS_COLLECTION(widget
));
488 g_return_if_fail(widget
->parent
!= NULL
);
490 GTK_WIDGET_SET_FLAGS(widget
, GTK_REALIZED
);
491 collection
= COLLECTION(widget
);
493 attributes
.x
= widget
->allocation
.x
;
494 attributes
.y
= widget
->allocation
.y
;
495 attributes
.width
= widget
->allocation
.width
;
496 attributes
.height
= widget
->allocation
.height
;
497 attributes
.wclass
= GDK_INPUT_OUTPUT
;
498 attributes
.window_type
= GDK_WINDOW_CHILD
;
499 attributes
.event_mask
= gtk_widget_get_events(widget
) |
501 GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK
|
502 GDK_BUTTON1_MOTION_MASK
| GDK_BUTTON2_MOTION_MASK
|
503 GDK_BUTTON3_MOTION_MASK
;
504 attributes
.visual
= gtk_widget_get_visual(widget
);
505 attributes
.colormap
= gtk_widget_get_colormap(widget
);
507 attributes_mask
= GDK_WA_X
| GDK_WA_Y
|
508 GDK_WA_VISUAL
| GDK_WA_COLORMAP
;
509 widget
->window
= gdk_window_new(gtk_widget_get_parent_window(widget
),
510 &attributes
, attributes_mask
);
512 widget
->style
= gtk_style_attach(widget
->style
, widget
->window
);
514 gdk_window_set_user_data(widget
->window
, widget
);
516 gdk_window_set_background(widget
->window
,
517 &widget
->style
->bg
[GTK_STATE_NORMAL
]);
518 if (widget
->style
->bg_pixmap
[GTK_STATE_NORMAL
])
519 collection
->bg_gc
= create_bg_gc(widget
);
522 /* Try to stop everything flickering horribly */
523 gdk_window_set_static_gravities(widget
->window
, TRUE
);
525 set_vadjustment(collection
);
528 bg
= &widget
->style
->bg
[GTK_STATE_NORMAL
];
529 fg
= &widget
->style
->fg
[GTK_STATE_NORMAL
];
530 xor_values
.function
= GDK_XOR
;
531 xor_values
.foreground
.pixel
= fg
->pixel
^ bg
->pixel
;
532 collection
->xor_gc
= gdk_gc_new_with_values(widget
->window
,
538 static void collection_size_request(GtkWidget
*widget
,
539 GtkRequisition
*requisition
)
542 Collection
*collection
= COLLECTION(widget
);
543 int rows
, cols
= collection
->columns
;
545 /* We ask for the total size we need; our containing viewport
546 * will deal with scrolling.
548 requisition
->width
= MIN_WIDTH
;
549 rows
= (collection
->number_of_items
+ cols
- 1) / cols
;
550 requisition
->height
= rows
* collection
->item_height
;
552 requisition
->width
= MIN_WIDTH
;
553 requisition
->height
= MIN_HEIGHT
;
558 static gboolean
scroll_after_alloc(Collection
*collection
)
560 if (collection
->wink_item
!= -1)
561 scroll_to_show(collection
, collection
->wink_item
);
562 else if (collection
->cursor_item
!= -1)
563 scroll_to_show(collection
, collection
->cursor_item
);
564 g_object_unref(G_OBJECT(collection
));
570 static void collection_size_allocate(GtkWidget
*widget
,
571 GtkAllocation
*allocation
)
573 Collection
*collection
;
575 gboolean cursor_visible
= FALSE
;
577 g_return_if_fail(widget
!= NULL
);
578 g_return_if_fail(IS_COLLECTION(widget
));
579 g_return_if_fail(allocation
!= NULL
);
581 collection
= COLLECTION(widget
);
583 if (collection
->cursor_item
!= -1)
586 int crow
= collection
->cursor_item
/ collection
->columns
;
588 get_visible_limits(collection
, &first
, &last
);
590 cursor_visible
= crow
>= first
&& crow
<= last
;
593 old_columns
= collection
->columns
;
595 if (widget
->allocation
.x
!= allocation
->x
596 || widget
->allocation
.y
!= allocation
->y
)
597 collection
->paint_level
= PAINT_CLEAR
;
600 widget
->allocation
= *allocation
;
602 collection
->columns
= allocation
->width
/ collection
->item_width
;
603 if (collection
->columns
< 1)
604 collection
->columns
= 1;
606 if (GTK_WIDGET_REALIZED(widget
))
608 gdk_window_move_resize(widget
->window
,
609 allocation
->x
, allocation
->y
,
610 allocation
->width
, allocation
->height
);
613 /* Force a redraw if the number of columns has changed
614 * or we have a background pixmap (!).
616 if (old_columns
!= collection
->columns
|| collection
->bg_gc
)
618 collection
->paint_level
= PAINT_CLEAR
;
619 gtk_widget_queue_clear(widget
);
622 set_vadjustment(collection
);
626 scroll_to_show(collection
, collection
->cursor_item
);
630 if (old_columns
!= collection
->columns
)
632 /* Need to go around again... */
633 gtk_widget_queue_resize(widget
);
635 else if (collection
->wink_item
!= -1 || collection
->cursor_item
!= -1)
637 /* Viewport resets the adjustments after the alloc */
638 g_object_ref(G_OBJECT(collection
));
639 g_idle_add((GSourceFunc
) scroll_after_alloc
, collection
);
645 static void collection_set_style(GtkWidget
*widget
,
646 GtkStyle
*previous_style
)
648 Collection
*collection
;
650 g_return_if_fail(IS_COLLECTION(widget
));
652 collection
= COLLECTION(widget
);
654 collection
->paint_level
= PAINT_CLEAR
;
656 if (GTK_WIDGET_REALIZED(widget
))
658 gdk_window_set_background(widget
->window
,
659 &widget
->style
->bg
[GTK_STATE_NORMAL
]);
661 if (collection
->bg_gc
)
663 gdk_gc_destroy(collection
->bg_gc
);
664 collection
->bg_gc
= NULL
;
667 if (widget
->style
->bg_pixmap
[GTK_STATE_NORMAL
])
668 collection
->bg_gc
= create_bg_gc(widget
);
673 static void clear_area(Collection
*collection
, GdkRectangle
*area
)
675 GtkWidget
*widget
= GTK_WIDGET(collection
);
679 int scroll
= collection
->vadj
->value
;
682 if (collection
->bg_gc
)
684 gdk_gc_set_ts_origin(collection
->bg_gc
, 0, -scroll
);
686 gdk_draw_rectangle(widget
->window
,
690 area
->width
, area
->height
);
694 gdk_window_set_background(widget
->window
,
695 &widget
->style
->bg
[GTK_STATE_NORMAL
]);
696 gdk_window_clear_area(widget
->window
,
697 area
->x
, area
->y
, area
->width
, area
->height
);
701 /* Return the area occupied by the item at (row, col) by filling
704 static void collection_get_item_area(Collection
*collection
,
712 int scroll
= collection
->vadj
->value
; /* (round to int) */
715 area
->x
= col
* collection
->item_width
;
716 area
->y
= row
* collection
->item_height
- scroll
;
718 area
->width
= collection
->item_width
;
719 area
->height
= collection
->item_height
;
720 if (col
== collection
->columns
- 1)
724 static gint
collection_paint(Collection
*collection
,
727 GdkRectangle item_area
;
731 int start_row
, last_row
;
732 int start_col
, last_col
;
740 widget
= GTK_WIDGET(collection
);
742 scroll
= collection
->vadj
->value
;
743 gdk_window_get_size(widget
->window
, &width
, &height
);
747 whole
.height
= height
;
749 if (collection
->paint_level
> PAINT_NORMAL
|| area
== NULL
)
753 if (collection
->paint_level
== PAINT_CLEAR
754 && !collection
->lasso_box
)
755 clear_area(collection
, area
);
757 collection
->paint_level
= PAINT_NORMAL
;
761 /* Calculate the ranges to plot */
762 start_row
= (area
->y
+ scroll
) / collection
->item_height
;
763 last_row
= (area
->y
+ area
->height
- 1 + scroll
)
764 / collection
->item_height
;
767 start_col
= area
->x
/ collection
->item_width
;
768 phys_last_col
= (area
->x
+ area
->width
- 1) / collection
->item_width
;
770 if (collection
->lasso_box
)
772 /* You can't be too careful with lasso boxes...
774 * clip gives the total area drawn over (this may be larger
775 * than the requested area). It's used to redraw the lasso
778 collection_get_item_area(collection
,
779 start_row
, start_col
, &clip
);
780 clip
.width
*= phys_last_col
- start_col
+ 1;
781 clip
.height
*= last_row
- start_row
+ 1;
783 clear_area(collection
, &clip
);
786 /* The right-most column may be wider than the others.
787 * Therefore, to redraw the area after the last 'real' column
788 * we may have to draw the right-most column.
790 if (start_col
>= collection
->columns
)
791 start_col
= collection
->columns
- 1;
793 if (phys_last_col
>= collection
->columns
)
794 last_col
= collection
->columns
- 1;
796 last_col
= phys_last_col
;
800 item
= row
* collection
->columns
+ col
;
802 /* g_print("[ paint %d..%d ]\n", row, last_row); */
803 while ((item
== 0 || item
< collection
->number_of_items
)
806 collection_get_item_area(collection
, row
, col
, &item_area
);
808 draw_one_item(collection
, item
, &item_area
);
815 item
= row
* collection
->columns
+ col
;
821 if (collection
->lasso_box
)
823 gdk_gc_set_clip_rectangle(collection
->xor_gc
, &clip
);
824 draw_lasso_box(collection
);
825 gdk_gc_set_clip_rectangle(collection
->xor_gc
, NULL
);
831 static void default_draw_item( GtkWidget
*widget
,
832 CollectionItem
*item
,
836 gdk_draw_arc(widget
->window
,
837 item
->selected
? widget
->style
->white_gc
838 : widget
->style
->black_gc
,
841 COLLECTION(widget
)->item_width
, area
->height
,
846 static gboolean
default_test_point(Collection
*collection
,
847 int point_x
, int point_y
,
848 CollectionItem
*item
,
849 int width
, int height
,
854 /* Convert to point in unit circle */
855 f_x
= ((float) point_x
/ width
) - 0.5;
856 f_y
= ((float) point_y
/ height
) - 0.5;
858 return (f_x
* f_x
) + (f_y
* f_y
) <= .25;
861 static void collection_set_arg( GtkObject
*object
,
865 Collection
*collection
;
867 collection
= COLLECTION(object
);
871 case ARG_VADJUSTMENT
:
872 collection_set_adjustment(collection
,
873 GTK_VALUE_POINTER(*arg
));
880 static void collection_set_adjustment(Collection
*collection
,
884 g_return_if_fail (GTK_IS_ADJUSTMENT (vadj
));
886 vadj
= GTK_ADJUSTMENT(gtk_adjustment_new(0.0,
889 if (collection
->vadj
&& (collection
->vadj
!= vadj
))
892 gtk_signal_disconnect_by_data(GTK_OBJECT(collection
->vadj
),
895 gtk_object_unref(GTK_OBJECT(collection
->vadj
));
898 if (collection
->vadj
!= vadj
)
900 collection
->vadj
= vadj
;
901 gtk_object_ref(GTK_OBJECT(collection
->vadj
));
902 gtk_object_sink(GTK_OBJECT(collection
->vadj
));
905 gtk_signal_connect(GTK_OBJECT(collection
->vadj
),
907 (GtkSignalFunc
) collection_adjustment
,
909 gtk_signal_connect(GTK_OBJECT(collection
->vadj
),
911 (GtkSignalFunc
) collection_adjustment
,
913 /* Is this used for anything? */
914 gtk_signal_connect(GTK_OBJECT(collection
->vadj
),
916 (GtkSignalFunc
) collection_disconnect
,
918 collection_adjustment(vadj
, collection
);
923 static void collection_get_arg( GtkObject
*object
,
927 Collection
*collection
;
929 collection
= COLLECTION(object
);
933 case ARG_VADJUSTMENT
:
934 GTK_VALUE_POINTER(*arg
) = collection
->vadj
;
937 arg
->type
= GTK_TYPE_INVALID
;
943 /* Something about the adjustment has changed */
944 static void collection_adjustment(GtkAdjustment
*adjustment
,
945 Collection
*collection
)
949 g_return_if_fail(adjustment
!= NULL
);
950 g_return_if_fail(GTK_IS_ADJUSTMENT(adjustment
));
951 g_return_if_fail(collection
!= NULL
);
952 g_return_if_fail(IS_COLLECTION(collection
));
954 diff
= ((gint
) adjustment
->value
) - collection
->last_scroll
;
958 collection
->last_scroll
= adjustment
->value
;
960 if (collection
->lasso_box
)
962 remove_lasso_box(collection
);
963 collection
->drag_box_y
[0] -= diff
;
964 scroll_by(collection
, diff
);
965 add_lasso_box(collection
);
968 scroll_by(collection
, diff
);
972 static void collection_disconnect(GtkAdjustment
*adjustment
,
973 Collection
*collection
)
975 g_return_if_fail(adjustment
!= NULL
);
976 g_return_if_fail(GTK_IS_ADJUSTMENT(adjustment
));
977 g_return_if_fail(collection
!= NULL
);
978 g_return_if_fail(IS_COLLECTION(collection
));
980 collection_set_adjustment(collection
, NULL
);
983 static void set_vadjustment(Collection
*collection
)
989 widget
= GTK_WIDGET(collection
);
991 if (!GTK_WIDGET_REALIZED(widget
))
994 gdk_window_get_size(widget
->window
, NULL
, &height
);
995 cols
= collection
->columns
;
996 rows
= (collection
->number_of_items
+ cols
- 1) / cols
;
998 collection
->vadj
->lower
= 0.0;
999 collection
->vadj
->upper
= collection
->item_height
* rows
;
1000 if (!collection
->vadj
->upper
)
1001 collection
->vadj
->upper
= 1;
1003 collection
->vadj
->step_increment
=
1004 MIN(collection
->vadj
->upper
, collection
->item_height
/ 4);
1006 collection
->vadj
->page_increment
=
1007 MIN(collection
->vadj
->upper
,
1010 collection
->vadj
->page_size
= MIN(collection
->vadj
->upper
, height
);
1012 collection
->vadj
->value
= MIN(collection
->vadj
->value
,
1013 collection
->vadj
->upper
- collection
->vadj
->page_size
);
1015 collection
->vadj
->value
= MAX(collection
->vadj
->value
, 0.0);
1017 gtk_signal_emit_by_name(GTK_OBJECT(collection
->vadj
), "changed");
1020 static void collection_draw(GtkWidget
*widget
, GdkRectangle
*area
)
1022 Collection
*collection
;
1024 g_return_if_fail(widget
!= NULL
);
1025 g_return_if_fail(IS_COLLECTION(widget
));
1026 g_return_if_fail(area
!= NULL
); /* Not actually used */
1028 collection
= COLLECTION(widget
);
1030 collection_paint(collection
, area
);
1034 /* Change the adjustment by this amount. Bounded. */
1035 static void diff_vpos(Collection
*collection
, int diff
)
1037 int value
= collection
->vadj
->value
+ diff
;
1039 value
= CLAMP(value
, 0,
1040 collection
->vadj
->upper
- collection
->vadj
->page_size
);
1041 gtk_adjustment_set_value(collection
->vadj
, value
);
1044 static gint
collection_expose(GtkWidget
*widget
, GdkEventExpose
*event
)
1046 Collection
*collection
;
1048 g_return_val_if_fail(widget
!= NULL
, FALSE
);
1049 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
1050 g_return_val_if_fail(event
!= NULL
, FALSE
);
1052 collection
= COLLECTION(widget
);
1055 clear_area(collection
, &event
->area
);
1058 collection_paint(collection
, &event
->area
);
1064 /* Positive makes the contents go move up the screen */
1065 static void scroll_by(Collection
*collection
, gint diff
)
1071 GdkRectangle new_area
;
1076 widget
= GTK_WIDGET(collection
);
1078 if (collection
->lasso_box
)
1079 abort_lasso(collection
);
1081 gdk_window_get_size(widget
->window
, &width
, &height
);
1083 new_area
.width
= width
;
1090 new_area
.y
= height
- amount
;
1100 new_area
.height
= amount
;
1102 if (amount
< height
)
1104 static GdkGC
*expo_gc
= NULL
;
1108 expo_gc
= gdk_gc_new(widget
->window
);
1109 gdk_gc_copy(expo_gc
, widget
->style
->white_gc
);
1110 gdk_gc_set_exposures(expo_gc
, TRUE
);
1113 gdk_draw_pixmap(widget
->window
,
1122 /* We have to redraw everything because any pending
1123 * expose events now contain invalid areas.
1124 * Don't need to clear the area first though...
1126 if (collection
->paint_level
< PAINT_OVERWRITE
)
1127 collection
->paint_level
= PAINT_OVERWRITE
;
1130 collection
->paint_level
= PAINT_CLEAR
;
1132 clear_area(collection
, &new_area
);
1133 collection_paint(collection
, NULL
);
1137 static void resize_arrays(Collection
*collection
, guint new_size
)
1139 g_return_if_fail(collection
!= NULL
);
1140 g_return_if_fail(IS_COLLECTION(collection
));
1141 g_return_if_fail(new_size
>= collection
->number_of_items
);
1143 collection
->items
= g_realloc(collection
->items
,
1144 sizeof(CollectionItem
) * new_size
);
1145 collection
->array_size
= new_size
;
1148 static gint
collection_key_press(GtkWidget
*widget
, GdkEventKey
*event
)
1150 Collection
*collection
;
1154 g_return_val_if_fail(widget
!= NULL
, FALSE
);
1155 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
1156 g_return_val_if_fail(event
!= NULL
, FALSE
);
1158 collection
= (Collection
*) widget
;
1159 item
= collection
->cursor_item
;
1161 key
= event
->keyval
;
1162 if (event
->state
& (GDK_CONTROL_MASK
| GDK_SHIFT_MASK
))
1164 if (key
== GDK_Left
|| key
== GDK_Right
|| \
1165 key
== GDK_Up
|| key
== GDK_Down
)
1173 collection_move_cursor(collection
, 0, -1);
1176 collection_move_cursor(collection
, 0, 1);
1179 collection_move_cursor(collection
, -1, 0);
1182 collection_move_cursor(collection
, 1, 0);
1185 collection_set_cursor_item(collection
, 0);
1188 collection_set_cursor_item(collection
,
1189 MAX((gint
) collection
->number_of_items
- 1, 0));
1194 get_visible_limits(collection
, &first
, &last
);
1195 collection_move_cursor(collection
, first
- last
- 1, 0);
1201 get_visible_limits(collection
, &first
, &last
);
1202 collection_move_cursor(collection
, last
- first
+ 1, 0);
1206 collection_set_cursor_item(collection
, -1);
1207 collection_clear_selection(collection
);
1208 return FALSE
; /* Pass it on */
1210 if (item
>=0 && item
< collection
->number_of_items
)
1212 collection_toggle_item(collection
, item
);
1213 if (item
< collection
->number_of_items
- 1)
1214 collection_set_cursor_item(collection
,
1225 /* Wheel mouse scrolling */
1227 static gint
collection_scroll_event(GtkWidget
*widget
, GdkEventScroll
*event
)
1229 static gint
collection_scroll_event(GtkWidget
*widget
, GdkEventButton
*event
)
1232 Collection
*collection
;
1235 g_return_val_if_fail(widget
!= NULL
, FALSE
);
1236 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
1237 g_return_val_if_fail(event
!= NULL
, FALSE
);
1239 collection
= COLLECTION(widget
);
1242 if (event
->direction
== GDK_SCROLL_UP
)
1244 else if (event
->direction
== GDK_SCROLL_DOWN
)
1249 if (event
->button
<= 3 || event
->type
!= GDK_BUTTON_PRESS
)
1250 return FALSE
; /* Only deal with wheel events here */
1252 if (event
->button
== 4)
1254 else if (event
->button
== 5)
1262 int old_value
= collection
->vadj
->value
;
1264 gboolean box
= collection
->lasso_box
;
1265 int step
= collection
->vadj
->page_increment
/ 2;
1267 new_value
= CLAMP(old_value
+ diff
* step
, 0.0,
1268 collection
->vadj
->upper
1269 - collection
->vadj
->page_size
);
1270 diff
= new_value
- old_value
;
1275 remove_lasso_box(collection
);
1276 collection
->drag_box_y
[0] -= diff
;
1278 gtk_adjustment_set_value(collection
->vadj
, new_value
);
1280 add_lasso_box(collection
);
1287 /* 'from' and 'to' are pixel positions. 'step' is the size of each item.
1288 * Returns the index of the first item covered, and the number of items.
1291 static void get_range(int from
, int to
, int step
, gint
*pos
, gint
*len
)
1293 static void get_range(int from
, int to
, int step
, short *pos
, short *len
)
1303 from
= (from
+ step
/ 4) / step
; /* First item */
1304 to
= (to
+ step
- step
/ 4) / step
; /* Last item (inclusive) */
1306 *pos
= MAX(from
, 0);
1310 /* Fills in the area with a rectangle corresponding to the current
1311 * size of the lasso box (units of items, not pixels).
1313 * The box will only span valid columns, but the total number
1314 * of items is not taken into account (rows or cols).
1316 static void find_lasso_area(Collection
*collection
, GdkRectangle
*area
)
1319 int cols
= collection
->columns
;
1320 int dx
= collection
->drag_box_x
[0] - collection
->drag_box_x
[1];
1321 int dy
= collection
->drag_box_y
[0] - collection
->drag_box_y
[1];
1323 if (ABS(dx
) < 8 && ABS(dy
) < 8)
1325 /* Didn't move far enough - ignore */
1326 area
->x
= area
->y
= 0;
1333 scroll
= collection
->vadj
->value
;
1336 get_range(collection
->drag_box_x
[0],
1337 collection
->drag_box_x
[1],
1338 collection
->item_width
,
1342 if (area
->x
>= cols
)
1344 else if (area
->x
+ area
->width
> cols
)
1345 area
->width
= cols
- area
->x
;
1347 get_range(collection
->drag_box_y
[0] + scroll
,
1348 collection
->drag_box_y
[1] + scroll
,
1349 collection
->item_height
,
1354 static void collection_process_area(Collection
*collection
,
1360 guint32 stacked_time
;
1362 gboolean changed
= FALSE
;
1365 g_return_if_fail(fn
== GDK_SET
|| fn
== GDK_INVERT
);
1367 old_selected
= collection
->number_selected
;
1369 stacked_time
= current_event_time
;
1370 current_event_time
= time
;
1372 collection
->block_selection_changed
++;
1374 for (y
= area
->y
; y
< area
->y
+ area
->height
; y
++)
1376 item
= y
* collection
->columns
+ area
->x
;
1378 for (x
= area
->x
; x
< area
->x
+ area
->width
; x
++)
1380 if (item
>= collection
->number_of_items
)
1383 if (fn
== GDK_INVERT
)
1384 collection_item_set_selected(collection
, item
,
1385 !collection
->items
[item
].selected
,
1388 collection_item_set_selected(collection
, item
,
1397 if (collection
->number_selected
&& !old_selected
)
1398 gtk_signal_emit(GTK_OBJECT(collection
),
1399 collection_signals
[GAIN_SELECTION
],
1400 current_event_time
);
1401 else if (!collection
->number_selected
&& old_selected
)
1402 gtk_signal_emit(GTK_OBJECT(collection
),
1403 collection_signals
[LOSE_SELECTION
],
1404 current_event_time
);
1406 collection_unblock_selection_changed(collection
,
1407 current_event_time
, changed
);
1408 current_event_time
= stacked_time
;
1411 static gint
collection_motion_notify(GtkWidget
*widget
,
1412 GdkEventMotion
*event
)
1414 Collection
*collection
;
1417 g_return_val_if_fail(widget
!= NULL
, FALSE
);
1418 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
1419 g_return_val_if_fail(event
!= NULL
, FALSE
);
1421 collection
= COLLECTION(widget
);
1423 if (!collection
->lasso_box
)
1426 if (event
->window
!= widget
->window
)
1427 gdk_window_get_pointer(widget
->window
, &x
, &y
, NULL
);
1434 remove_lasso_box(collection
);
1435 collection
->drag_box_x
[1] = x
;
1436 collection
->drag_box_y
[1] = y
;
1437 add_lasso_box(collection
);
1441 static void add_lasso_box(Collection
*collection
)
1443 g_return_if_fail(collection
!= NULL
);
1444 g_return_if_fail(IS_COLLECTION(collection
));
1445 g_return_if_fail(collection
->lasso_box
== FALSE
);
1447 collection
->lasso_box
= TRUE
;
1448 draw_lasso_box(collection
);
1451 static void draw_lasso_box(Collection
*collection
)
1454 int x
, y
, width
, height
;
1456 widget
= GTK_WIDGET(collection
);
1458 x
= MIN(collection
->drag_box_x
[0], collection
->drag_box_x
[1]);
1459 y
= MIN(collection
->drag_box_y
[0], collection
->drag_box_y
[1]);
1460 width
= abs(collection
->drag_box_x
[1] - collection
->drag_box_x
[0]);
1461 height
= abs(collection
->drag_box_y
[1] - collection
->drag_box_y
[0]);
1463 gdk_draw_rectangle(widget
->window
, collection
->xor_gc
, FALSE
,
1464 x
, y
, width
, height
);
1467 static void abort_lasso(Collection
*collection
)
1469 if (collection
->lasso_box
)
1471 remove_lasso_box(collection
);
1472 collection_set_autoscroll(collection
, FALSE
);
1476 static void remove_lasso_box(Collection
*collection
)
1478 g_return_if_fail(collection
!= NULL
);
1479 g_return_if_fail(IS_COLLECTION(collection
));
1480 g_return_if_fail(collection
->lasso_box
== TRUE
);
1482 draw_lasso_box(collection
);
1484 collection
->lasso_box
= FALSE
;
1489 /* Make sure that 'item' is fully visible (vertically), scrolling if not. */
1490 static void scroll_to_show(Collection
*collection
, int item
)
1492 int first
, last
, row
;
1494 g_return_if_fail(collection
!= NULL
);
1495 g_return_if_fail(IS_COLLECTION(collection
));
1497 row
= item
/ collection
->columns
;
1498 get_visible_limits(collection
, &first
, &last
);
1502 gtk_adjustment_set_value(collection
->vadj
,
1503 row
* collection
->item_height
);
1505 else if (row
>= last
)
1507 GtkWidget
*widget
= (GtkWidget
*) collection
;
1510 if (GTK_WIDGET_REALIZED(widget
))
1512 height
= collection
->vadj
->page_size
;
1513 gtk_adjustment_set_value(collection
->vadj
,
1514 (row
+ 1) * collection
->item_height
- height
);
1519 /* Return the first and last rows which are [partly] visible. Does not
1520 * ensure that the rows actually exist (contain items).
1522 static void get_visible_limits(Collection
*collection
, int *first
, int *last
)
1524 GtkWidget
*widget
= (GtkWidget
*) collection
;
1525 gint scroll
= 0, height
;
1527 g_return_if_fail(collection
!= NULL
);
1528 g_return_if_fail(IS_COLLECTION(collection
));
1529 g_return_if_fail(first
!= NULL
&& last
!= NULL
);
1531 if (!GTK_WIDGET_REALIZED(widget
))
1538 scroll
= collection
->vadj
->value
;
1539 height
= collection
->vadj
->page_size
;
1541 *first
= MAX(scroll
/ collection
->item_height
, 0);
1542 *last
= (scroll
+ height
- 1) /collection
->item_height
;
1549 /* Cancel the current wink effect. */
1550 static void cancel_wink(Collection
*collection
)
1554 g_return_if_fail(collection
!= NULL
);
1555 g_return_if_fail(IS_COLLECTION(collection
));
1556 g_return_if_fail(collection
->wink_item
!= -1);
1558 item
= collection
->wink_item
;
1560 collection
->wink_item
= -1;
1561 gtk_timeout_remove(collection
->wink_timeout
);
1563 collection_draw_item(collection
, item
, TRUE
);
1566 /* Draw/undraw a box around collection->wink_item */
1567 static void invert_wink(Collection
*collection
)
1572 g_return_if_fail(collection
->wink_item
>= 0);
1574 if (!GTK_WIDGET_REALIZED(GTK_WIDGET(collection
)))
1577 col
= collection
->wink_item
% collection
->columns
;
1578 row
= collection
->wink_item
/ collection
->columns
;
1579 collection_get_item_area(collection
, row
, col
, &area
);
1581 gdk_draw_rectangle(((GtkWidget
*) collection
)->window
,
1582 collection
->xor_gc
, FALSE
,
1584 collection
->item_width
- 1,
1588 static gboolean
wink_timeout(Collection
*collection
)
1592 g_return_val_if_fail(collection
!= NULL
, FALSE
);
1593 g_return_val_if_fail(IS_COLLECTION(collection
), FALSE
);
1594 g_return_val_if_fail(collection
->wink_item
!= -1, FALSE
);
1596 item
= collection
->wink_item
;
1598 if (collection
->winks_left
-- > 0)
1600 invert_wink(collection
);
1604 collection
->wink_item
= -1;
1606 collection_draw_item(collection
, item
, TRUE
);
1612 static gint
focus_in(GtkWidget
*widget
, GdkEventFocus
*event
)
1614 g_return_val_if_fail(widget
!= NULL
, FALSE
);
1615 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
1616 g_return_val_if_fail(event
!= NULL
, FALSE
);
1618 GTK_WIDGET_SET_FLAGS(widget
, GTK_HAS_FOCUS
);
1619 gtk_widget_draw_focus(widget
);
1624 static gint
focus_out(GtkWidget
*widget
, GdkEventFocus
*event
)
1626 g_return_val_if_fail(widget
!= NULL
, FALSE
);
1627 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
1628 g_return_val_if_fail(event
!= NULL
, FALSE
);
1630 GTK_WIDGET_UNSET_FLAGS(widget
, GTK_HAS_FOCUS
);
1631 gtk_widget_draw_focus(widget
);
1637 /* This is called frequently while auto_scroll is on.
1638 * Checks the pointer position and scrolls the window if it's
1639 * near the top or bottom.
1641 static gboolean
as_timeout(Collection
*collection
)
1643 GdkWindow
*window
= GTK_WIDGET(collection
)->window
;
1645 GdkModifierType mask
;
1648 gdk_window_get_pointer(window
, &x
, &y
, &mask
);
1649 gdk_window_get_size(window
, &w
, NULL
);
1651 h
= collection
->vadj
->page_size
;
1653 y
-= collection
->vadj
->value
;
1656 if ((x
< 0 || x
> w
|| y
< 0 || y
> h
) && !collection
->lasso_box
)
1658 collection
->auto_scroll
= -1;
1659 return FALSE
; /* Out of window - stop */
1662 if (y
< AUTOSCROLL_STEP
)
1663 diff
= y
- AUTOSCROLL_STEP
;
1664 else if (y
> h
- AUTOSCROLL_STEP
)
1665 diff
= AUTOSCROLL_STEP
+ y
- h
;
1668 diff_vpos(collection
, diff
);
1673 /* Change the selected state of an item.
1674 * Send GAIN/LOSE signals if 'signal' is TRUE.
1675 * Send SELECTION_CHANGED unless blocked.
1676 * Updates number_selected and redraws the item.
1678 static void collection_item_set_selected(Collection
*collection
,
1683 g_return_if_fail(collection
!= NULL
);
1684 g_return_if_fail(IS_COLLECTION(collection
));
1685 g_return_if_fail(item
>= 0 && item
< collection
->number_of_items
);
1687 if (collection
->items
[item
].selected
== selected
)
1690 collection
->items
[item
].selected
= selected
;
1691 collection_draw_item(collection
, item
, TRUE
);
1695 collection
->number_selected
++;
1696 if (signal
&& collection
->number_selected
== 1)
1697 gtk_signal_emit(GTK_OBJECT(collection
),
1698 collection_signals
[GAIN_SELECTION
],
1699 current_event_time
);
1703 collection
->number_selected
--;
1704 if (signal
&& collection
->number_selected
== 0)
1705 gtk_signal_emit(GTK_OBJECT(collection
),
1706 collection_signals
[LOSE_SELECTION
],
1707 current_event_time
);
1710 EMIT_SELECTION_CHANGED(collection
, current_event_time
);
1713 /* Functions for managing collections */
1715 /* Remove all objects from the collection */
1716 void collection_clear(Collection
*collection
)
1718 collection_delete_if(collection
, NULL
, NULL
);
1721 /* Inserts a new item at the end. The new item is unselected, and its
1722 * number is returned.
1724 gint
collection_insert(Collection
*collection
, gpointer data
, gpointer view
)
1728 g_return_val_if_fail(IS_COLLECTION(collection
), -1);
1730 item
= collection
->number_of_items
;
1732 if (item
>= collection
->array_size
)
1733 resize_arrays(collection
, item
+ (item
>> 1));
1735 collection
->items
[item
].data
= data
;
1736 collection
->items
[item
].view_data
= view
;
1737 collection
->items
[item
].selected
= FALSE
;
1739 collection
->number_of_items
++;
1742 gtk_widget_queue_resize(GTK_WIDGET(collection
));
1744 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection
)))
1745 set_vadjustment(collection
);
1748 collection_draw_item(collection
, item
, FALSE
);
1753 void collection_unselect_item(Collection
*collection
, gint item
)
1755 collection_item_set_selected(collection
, item
, FALSE
, TRUE
);
1758 void collection_select_item(Collection
*collection
, gint item
)
1760 collection_item_set_selected(collection
, item
, TRUE
, TRUE
);
1763 void collection_toggle_item(Collection
*collection
, gint item
)
1765 collection_item_set_selected(collection
, item
,
1766 !collection
->items
[item
].selected
, TRUE
);
1769 /* Select all items in the collection */
1770 void collection_select_all(Collection
*collection
)
1775 g_return_if_fail(collection
!= NULL
);
1776 g_return_if_fail(IS_COLLECTION(collection
));
1778 widget
= GTK_WIDGET(collection
);
1780 if (collection
->number_selected
== collection
->number_of_items
)
1781 return; /* Nothing to do */
1783 while (collection
->number_selected
< collection
->number_of_items
)
1785 while (collection
->items
[item
].selected
)
1788 collection
->items
[item
].selected
= TRUE
;
1789 collection_draw_item(collection
, item
, TRUE
);
1792 collection
->number_selected
++;
1795 gtk_signal_emit(GTK_OBJECT(collection
),
1796 collection_signals
[GAIN_SELECTION
],
1797 current_event_time
);
1798 EMIT_SELECTION_CHANGED(collection
, current_event_time
);
1801 /* Toggle all items in the collection */
1802 void collection_invert_selection(Collection
*collection
)
1806 g_return_if_fail(collection
!= NULL
);
1807 g_return_if_fail(IS_COLLECTION(collection
));
1809 if (collection
->number_selected
== 0)
1811 collection_select_all(collection
);
1814 else if (collection
->number_of_items
== collection
->number_selected
)
1816 collection_clear_selection(collection
);
1820 for (item
= 0; item
< collection
->number_of_items
; item
++)
1821 collection
->items
[item
].selected
=
1822 !collection
->items
[item
].selected
;
1824 collection
->number_selected
= collection
->number_of_items
-
1825 collection
->number_selected
;
1827 /* Have to redraw everything... */
1829 collection
->paint_level
= PAINT_CLEAR
;
1831 gtk_widget_queue_clear(GTK_WIDGET(collection
));
1833 EMIT_SELECTION_CHANGED(collection
, current_event_time
);
1836 /* Unselect all items except number item, which is selected (-1 to unselect
1839 void collection_clear_except(Collection
*collection
, gint item
)
1843 int end
; /* Selected items to end up with */
1845 g_return_if_fail(collection
!= NULL
);
1846 g_return_if_fail(IS_COLLECTION(collection
));
1847 g_return_if_fail(item
>= -1 && item
< collection
->number_of_items
);
1849 widget
= GTK_WIDGET(collection
);
1855 collection_select_item(collection
, item
);
1859 if (collection
->number_selected
== 0)
1862 while (collection
->number_selected
> end
)
1864 while (i
== item
|| !collection
->items
[i
].selected
)
1867 collection
->items
[i
].selected
= FALSE
;
1868 collection_draw_item(collection
, i
, TRUE
);
1871 collection
->number_selected
--;
1875 gtk_signal_emit(GTK_OBJECT(collection
),
1876 collection_signals
[LOSE_SELECTION
],
1877 current_event_time
);
1878 EMIT_SELECTION_CHANGED(collection
, current_event_time
);
1881 /* Unselect all items in the collection */
1882 void collection_clear_selection(Collection
*collection
)
1884 g_return_if_fail(collection
!= NULL
);
1885 g_return_if_fail(IS_COLLECTION(collection
));
1887 collection_clear_except(collection
, -1);
1890 /* Force a redraw of the specified item, if it is visible */
1891 void collection_draw_item(Collection
*collection
, gint item
, gboolean blank
)
1900 g_return_if_fail(collection
!= NULL
);
1901 g_return_if_fail(IS_COLLECTION(collection
));
1902 g_return_if_fail(item
>= 0 &&
1903 (item
== 0 || item
< collection
->number_of_items
));
1905 widget
= GTK_WIDGET(collection
);
1906 if (!GTK_WIDGET_REALIZED(widget
))
1909 col
= item
% collection
->columns
;
1910 row
= item
/ collection
->columns
;
1912 collection_get_item_area(collection
, row
, col
, &area
);
1915 gdk_window_invalidate_rect(widget
->window
, &area
, FALSE
);
1917 if (area
.y
+ area
.height
< 0)
1920 gdk_window_get_size(widget
->window
, NULL
, &height
);
1922 if (area
.y
> height
)
1925 if (blank
|| collection
->lasso_box
)
1926 clear_area(collection
, &area
);
1928 draw_one_item(collection
, item
, &area
);
1930 if (collection
->lasso_box
)
1932 gdk_gc_set_clip_rectangle(collection
->xor_gc
, &area
);
1933 draw_lasso_box(collection
);
1934 gdk_gc_set_clip_rectangle(collection
->xor_gc
, NULL
);
1939 void collection_set_item_size(Collection
*collection
, int width
, int height
)
1943 g_return_if_fail(collection
!= NULL
);
1944 g_return_if_fail(IS_COLLECTION(collection
));
1945 g_return_if_fail(width
> 4 && height
> 4);
1947 if (collection
->item_width
== width
&&
1948 collection
->item_height
== height
)
1951 widget
= GTK_WIDGET(collection
);
1953 collection
->item_width
= width
;
1954 collection
->item_height
= height
;
1956 if (GTK_WIDGET_REALIZED(widget
))
1960 gdk_window_get_size(widget
->window
, &window_width
, NULL
);
1961 collection
->columns
= MAX(window_width
/ collection
->item_width
,
1964 collection
->paint_level
= PAINT_CLEAR
;
1965 set_vadjustment(collection
);
1967 if (collection
->cursor_item
!= -1)
1968 scroll_to_show(collection
, collection
->cursor_item
);
1969 gtk_widget_queue_draw(widget
);
1973 gtk_widget_queue_resize(GTK_WIDGET(collection
));
1977 /* Cursor is positioned on item with the same data as before the sort.
1978 * Same for the wink item.
1980 void collection_qsort(Collection
*collection
,
1981 int (*compar
)(const void *, const void *))
1983 int cursor
, wink
, items
, wink_on_map
;
1984 gpointer cursor_data
= NULL
;
1985 gpointer wink_data
= NULL
;
1986 gpointer wink_on_map_data
= NULL
;
1987 CollectionItem
*array
;
1990 g_return_if_fail(collection
!= NULL
);
1991 g_return_if_fail(IS_COLLECTION(collection
));
1992 g_return_if_fail(compar
!= NULL
);
1994 /* Check to see if it needs sorting (saves redrawing) */
1995 if (collection
->number_of_items
< 2)
1998 array
= collection
->items
;
1999 for (i
= 1; i
< collection
->number_of_items
; i
++)
2001 if (compar(&array
[i
- 1], &array
[i
]) > 0)
2004 if (i
== collection
->number_of_items
)
2005 return; /* Already sorted */
2007 items
= collection
->number_of_items
;
2009 wink_on_map
= collection
->wink_on_map
;
2010 if (wink_on_map
>= 0 && wink_on_map
< items
)
2012 wink_on_map_data
= collection
->items
[wink_on_map
].data
;
2013 collection
->wink_on_map
= -1;
2018 wink
= collection
->wink_item
;
2019 if (wink
>= 0 && wink
< items
)
2021 wink_data
= collection
->items
[wink
].data
;
2022 collection
->wink_item
= -1;
2027 cursor
= collection
->cursor_item
;
2028 if (cursor
>= 0 && cursor
< items
)
2029 cursor_data
= collection
->items
[cursor
].data
;
2033 qsort(collection
->items
, items
, sizeof(collection
->items
[0]), compar
);
2035 if (cursor
> -1 || wink
> -1 || wink_on_map
> -1)
2039 for (item
= 0; item
< items
; item
++)
2041 if (collection
->items
[item
].data
== cursor_data
)
2042 collection_set_cursor_item(collection
, item
);
2043 if (collection
->items
[item
].data
== wink_on_map_data
)
2044 collection
->wink_on_map
= item
;
2045 if (collection
->items
[item
].data
== wink_data
)
2047 collection
->cursor_item_old
= item
;
2048 collection
->wink_item
= item
;
2049 scroll_to_show(collection
, item
);
2055 collection
->paint_level
= PAINT_CLEAR
;
2058 gtk_widget_queue_draw(GTK_WIDGET(collection
));
2061 /* Find an item in an unsorted collection.
2062 * Returns the item number, or -1 if not found.
2064 int collection_find_item(Collection
*collection
, gpointer data
,
2065 int (*compar
)(const void *, const void *))
2069 g_return_val_if_fail(collection
!= NULL
, -1);
2070 g_return_val_if_fail(IS_COLLECTION(collection
), -1);
2071 g_return_val_if_fail(compar
!= NULL
, -1);
2073 for (i
= 0; i
< collection
->number_of_items
; i
++)
2074 if (compar(&collection
->items
[i
].data
, &data
) == 0)
2080 /* Return the number of the item under the point (x,y), or -1 for none.
2081 * This may call your test_point callback. The point is relative to the
2082 * collection's origin.
2084 int collection_get_item(Collection
*collection
, int x
, int y
)
2091 g_return_val_if_fail(collection
!= NULL
, -1);
2094 scroll
= collection
->vadj
->value
;
2096 col
= x
/ collection
->item_width
;
2097 row
= (y
+ scroll
) / collection
->item_height
;
2099 if (col
>= collection
->columns
)
2100 col
= collection
->columns
- 1;
2102 if (col
< 0 || row
< 0)
2105 if (col
== collection
->columns
- 1)
2106 width
= collection
->item_width
<< 1;
2108 width
= collection
->item_width
;
2110 item
= col
+ row
* collection
->columns
;
2111 if (item
>= collection
->number_of_items
2113 !collection
->test_point(collection
,
2114 x
- col
* collection
->item_width
,
2115 y
- row
* collection
->item_height
2117 &collection
->items
[item
],
2119 collection
->item_height
,
2120 collection
->cb_user_data
))
2128 /* Set the cursor/highlight over the given item. Passing -1
2129 * hides the cursor. As a special case, you may set the cursor item
2130 * to zero when there are no items.
2132 void collection_set_cursor_item(Collection
*collection
, gint item
)
2136 g_return_if_fail(collection
!= NULL
);
2137 g_return_if_fail(IS_COLLECTION(collection
));
2138 g_return_if_fail(item
>= -1 &&
2139 (item
< collection
->number_of_items
|| item
== 0));
2141 old_item
= collection
->cursor_item
;
2143 if (old_item
== item
)
2146 collection
->cursor_item
= item
;
2149 collection_draw_item(collection
, old_item
, TRUE
);
2153 collection_draw_item(collection
, item
, TRUE
);
2154 if (collection
->auto_scroll
== -1)
2155 scroll_to_show(collection
, item
);
2157 else if (old_item
!= -1)
2158 collection
->cursor_item_old
= old_item
;
2161 /* Briefly highlight an item to draw the user's attention to it.
2162 * -1 cancels the effect, as does deleting items, sorting the collection
2163 * or starting a new wink effect.
2164 * Otherwise, the effect will cancel itself after a short pause.
2166 void collection_wink_item(Collection
*collection
, gint item
)
2168 g_return_if_fail(collection
!= NULL
);
2169 g_return_if_fail(IS_COLLECTION(collection
));
2170 g_return_if_fail(item
>= -1 && item
< collection
->number_of_items
);
2172 if (collection
->wink_item
!= -1)
2173 cancel_wink(collection
);
2177 if (!GTK_WIDGET_MAPPED(GTK_WIDGET(collection
)))
2179 collection
->wink_on_map
= item
;
2183 collection
->cursor_item_old
= collection
->wink_item
= item
;
2184 collection
->winks_left
= MAX_WINKS
;
2186 collection
->wink_timeout
= gtk_timeout_add(70,
2187 (GtkFunction
) wink_timeout
,
2189 scroll_to_show(collection
, item
);
2190 invert_wink(collection
);
2195 /* Call test(item, data) on each item in the collection.
2196 * Remove all items for which it returns TRUE. test() should
2197 * free the data before returning TRUE. The collection is in an
2198 * inconsistant state during this call (ie, when test() is called).
2200 * If test is NULL, remove all items.
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
));
2213 cursor
= collection
->cursor_item
;
2215 for (in
= 0; in
< collection
->number_of_items
; in
++)
2217 if (test
&& !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
;
2230 collection
->items
[out
].view_data
=
2231 collection
->items
[in
].view_data
;
2237 if (collection
->free_item
)
2238 collection
->free_item(collection
,
2239 &collection
->items
[in
]);
2248 collection
->cursor_item
= cursor
;
2250 if (collection
->wink_item
!= -1)
2252 collection
->wink_item
= -1;
2253 gtk_timeout_remove(collection
->wink_timeout
);
2256 collection
->number_of_items
= out
;
2257 if (collection
->number_selected
&& !selected
)
2259 /* We've lost all the selected items */
2260 gtk_signal_emit(GTK_OBJECT(collection
),
2261 collection_signals
[LOSE_SELECTION
],
2262 current_event_time
);
2265 collection
->number_selected
= selected
;
2266 resize_arrays(collection
,
2267 MAX(collection
->number_of_items
, MINIMUM_ITEMS
));
2270 collection
->paint_level
= PAINT_CLEAR
;
2273 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection
)))
2276 set_vadjustment(collection
);
2278 gtk_widget_queue_draw(GTK_WIDGET(collection
));
2281 gtk_widget_queue_resize(GTK_WIDGET(collection
));
2286 /* Move the cursor by the given row and column offsets.
2287 * Moving by (0,0) can be used to simply make the cursor appear.
2289 void collection_move_cursor(Collection
*collection
, int drow
, int dcol
)
2292 int first
, last
, total_rows
;
2294 g_return_if_fail(collection
!= NULL
);
2295 g_return_if_fail(IS_COLLECTION(collection
));
2297 get_visible_limits(collection
, &first
, &last
);
2299 item
= collection
->cursor_item
;
2302 item
= MIN(collection
->cursor_item_old
,
2303 collection
->number_of_items
- 1);
2313 row
= item
/ collection
->columns
;
2314 col
= item
% collection
->columns
+ dcol
;
2318 else if (row
> last
)
2321 row
= MAX(row
+ drow
, 0);
2324 total_rows
= (collection
->number_of_items
+ collection
->columns
- 1)
2325 / collection
->columns
;
2327 if (row
>= total_rows
- 1 && drow
> 0)
2329 row
= total_rows
- 1;
2330 item
= col
+ row
* collection
->columns
;
2331 if (item
>= collection
->number_of_items
- 1)
2333 collection_set_cursor_item(collection
,
2334 collection
->number_of_items
- 1);
2341 item
= col
+ row
* collection
->columns
;
2343 if (item
>= 0 && item
< collection
->number_of_items
)
2344 collection_set_cursor_item(collection
, item
);
2347 /* When autoscroll is on, a timer keeps track of the pointer position.
2348 * While it's near the top or bottom of the window, the window scrolls.
2350 * If the mouse buttons are released, or the pointer leaves the window,
2351 * auto_scroll is turned off.
2353 void collection_set_autoscroll(Collection
*collection
, gboolean auto_scroll
)
2355 g_return_if_fail(collection
!= NULL
);
2356 g_return_if_fail(IS_COLLECTION(collection
));
2360 if (collection
->auto_scroll
!= -1)
2361 return; /* Already on! */
2363 collection
->auto_scroll
= gtk_timeout_add(50,
2364 (GtkFunction
) as_timeout
,
2369 if (collection
->auto_scroll
== -1)
2370 return; /* Already off! */
2372 gtk_timeout_remove(collection
->auto_scroll
);
2373 collection
->auto_scroll
= -1;
2377 /* Start a lasso box drag */
2378 void collection_lasso_box(Collection
*collection
, int x
, int y
)
2380 collection
->drag_box_x
[0] = x
;
2381 collection
->drag_box_y
[0] = y
;
2382 collection
->drag_box_x
[1] = x
;
2383 collection
->drag_box_y
[1] = y
;
2385 collection_set_autoscroll(collection
, TRUE
);
2386 add_lasso_box(collection
);
2389 /* Remove the lasso box. Applies fn to each item inside the box.
2390 * fn may be GDK_INVERT, GDK_SET, GDK_NOOP or GDK_CLEAR.
2392 void collection_end_lasso(Collection
*collection
, GdkFunction fn
)
2394 if (fn
!= GDK_CLEAR
)
2396 GdkRectangle region
;
2398 find_lasso_area(collection
, ®ion
);
2400 collection_process_area(collection
, ®ion
, fn
,
2404 abort_lasso(collection
);
2407 /* Unblock the selection_changed signal, emitting the signal if the
2408 * block counter reaches zero and emit is TRUE.
2410 void collection_unblock_selection_changed(Collection
*collection
,
2414 g_return_if_fail(collection
!= NULL
);
2415 g_return_if_fail(IS_COLLECTION(collection
));
2416 g_return_if_fail(collection
->block_selection_changed
> 0);
2418 collection
->block_selection_changed
--;
2420 if (emit
&& !collection
->block_selection_changed
)
2421 gtk_signal_emit(GTK_OBJECT(collection
),
2422 collection_signals
[SELECTION_CHANGED
], time
);