4 * Collection - a GTK+ widget
5 * Copyright (C) 2001, 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>
33 #include "collection.h"
37 #define MINIMUM_ITEMS 16
38 #define AUTOSCROLL_STEP 20
40 /* Macro to emit the "selection_changed" signal only if allowed */
41 #define EMIT_SELECTION_CHANGED(collection, time) \
42 if (!collection->block_selection_changed) \
43 gtk_signal_emit(GTK_OBJECT(collection), \
44 collection_signals[SELECTION_CHANGED], time)
54 * void gain_selection(collection, time, user_data)
55 * We've gone from no selected items to having a selection.
56 * Time is the time of the event that caused the change, or
57 * GDK_CURRENT_TIME if not known.
59 * void lose_selection(collection, time, user_data)
60 * We've dropped to having no selected items.
61 * Time is the time of the event that caused the change, or
62 * GDK_CURRENT_TIME if not known.
64 * void selection_changed(collection, user_data)
65 * The set of selected items has changed.
66 * Time is the time of the event that caused the change, or
67 * GDK_CURRENT_TIME if not known.
77 static guint collection_signals
[LAST_SIGNAL
] = { 0 };
79 static guint32 current_event_time
= GDK_CURRENT_TIME
;
81 static GtkWidgetClass
*parent_class
= NULL
;
83 /* Static prototypes */
84 static void clear_area(Collection
*collection
, GdkRectangle
*area
);
85 static void draw_one_item(Collection
*collection
,
88 static void collection_class_init(CollectionClass
*class);
89 static void collection_init(Collection
*object
);
90 static void collection_destroy(GtkObject
*object
);
91 static void collection_finalize(GtkObject
*object
);
92 static void collection_realize(GtkWidget
*widget
);
93 static gint
collection_paint(Collection
*collection
,
95 static void collection_size_request(GtkWidget
*widget
,
96 GtkRequisition
*requisition
);
97 static void collection_size_allocate(GtkWidget
*widget
,
98 GtkAllocation
*allocation
);
99 static void collection_set_style(GtkWidget
*widget
,
100 GtkStyle
*previous_style
);
101 static void collection_set_adjustment(Collection
*collection
,
102 GtkAdjustment
*vadj
);
103 static void collection_set_arg( GtkObject
*object
,
106 static void collection_get_arg( GtkObject
*object
,
109 static void collection_adjustment(GtkAdjustment
*adjustment
,
110 Collection
*collection
);
112 static void collection_disconnect(GtkAdjustment
*adjustment
,
113 Collection
*collection
);
115 static void set_vadjustment(Collection
*collection
);
116 static gint
collection_expose(GtkWidget
*widget
, GdkEventExpose
*event
);
117 static void scroll_by(Collection
*collection
, gint diff
);
118 static gint
collection_button_press(GtkWidget
*widget
,
119 GdkEventButton
*event
);
120 static void default_draw_item(GtkWidget
*widget
,
121 CollectionItem
*data
,
124 static gboolean
default_test_point(Collection
*collection
,
125 int point_x
, int point_y
,
126 CollectionItem
*data
,
127 int width
, int height
,
129 static gint
collection_motion_notify(GtkWidget
*widget
,
130 GdkEventMotion
*event
);
131 static void add_lasso_box(Collection
*collection
);
132 static void abort_lasso(Collection
*collection
);
133 static void remove_lasso_box(Collection
*collection
);
134 static void draw_lasso_box(Collection
*collection
);
135 static void cancel_wink(Collection
*collection
);
136 static gint
collection_key_press(GtkWidget
*widget
, GdkEventKey
*event
);
137 static void get_visible_limits(Collection
*collection
, int *first
, int *last
);
138 static void scroll_to_show(Collection
*collection
, int item
);
140 static void draw_focus(GtkWidget
*widget
);
141 static void collection_draw(GtkWidget
*widget
, GdkRectangle
*area
);
142 static gint
focus_in(GtkWidget
*widget
, GdkEventFocus
*event
);
143 static gint
focus_out(GtkWidget
*widget
, GdkEventFocus
*event
);
146 static void draw_focus_at(Collection
*collection
, GdkRectangle
*area
)
151 widget
= GTK_WIDGET(collection
);
153 if (GTK_WIDGET_FLAGS(widget
) & GTK_HAS_FOCUS
)
154 gc
= widget
->style
->fg_gc
[GTK_STATE_ACTIVE
];
156 gc
= widget
->style
->fg_gc
[GTK_STATE_INSENSITIVE
];
158 gdk_draw_rectangle(widget
->window
, gc
, FALSE
,
159 area
->x
+ 1, area
->y
+ 1,
160 collection
->item_width
- 3,
164 static void draw_one_item(Collection
*collection
, int item
, GdkRectangle
*area
)
166 if (item
< collection
->number_of_items
)
168 collection
->draw_item((GtkWidget
*) collection
,
169 &collection
->items
[item
],
171 collection
->cb_user_data
);
174 if (item
== collection
->cursor_item
)
175 draw_focus_at(collection
, area
);
179 static void draw_focus(GtkWidget
*widget
)
181 Collection
*collection
;
183 g_return_if_fail(widget
!= NULL
);
184 g_return_if_fail(IS_COLLECTION(widget
));
186 collection
= COLLECTION(widget
);
188 if (collection
->cursor_item
< 0 || !GTK_WIDGET_REALIZED(widget
))
191 collection_draw_item(collection
, collection
->cursor_item
, FALSE
);
195 GtkType
collection_get_type(void)
197 static guint my_type
= 0;
201 static const GtkTypeInfo my_info
=
205 sizeof(CollectionClass
),
206 (GtkClassInitFunc
) collection_class_init
,
207 (GtkObjectInitFunc
) collection_init
,
208 NULL
, /* Reserved 1 */
209 NULL
, /* Reserved 2 */
210 (GtkClassInitFunc
) NULL
/* base_class_init_func */
213 my_type
= gtk_type_unique(gtk_widget_get_type(),
221 typedef void (*FinalizeFn
)(GObject
*object
);
224 static void collection_class_init(CollectionClass
*class)
226 GtkObjectClass
*object_class
;
227 GtkWidgetClass
*widget_class
;
230 object_class
= (GtkObjectClass
*) class;
231 widget_class
= (GtkWidgetClass
*) class;
233 parent_class
= gtk_type_class(gtk_widget_get_type());
235 gtk_object_add_arg_type("Collection::vadjustment",
237 GTK_ARG_READWRITE
| GTK_ARG_CONSTRUCT
,
240 object_class
->destroy
= collection_destroy
;
242 G_OBJECT_CLASS(object_class
)->finalize
=
243 (FinalizeFn
) collection_finalize
;
244 type
= GTK_CLASS_TYPE(object_class
);
246 object_class
->finalize
= collection_finalize
;
247 type
= object_class
->type
;
250 widget_class
->realize
= collection_realize
;
251 widget_class
->expose_event
= collection_expose
;
252 widget_class
->size_request
= collection_size_request
;
253 widget_class
->size_allocate
= collection_size_allocate
;
254 widget_class
->style_set
= collection_set_style
;
256 widget_class
->key_press_event
= collection_key_press
;
257 widget_class
->button_press_event
= collection_button_press
;
258 widget_class
->motion_notify_event
= collection_motion_notify
;
260 widget_class
->focus_in_event
= focus_in
;
261 widget_class
->focus_out_event
= focus_out
;
262 widget_class
->draw
= collection_draw
;
263 widget_class
->draw_focus
= draw_focus
;
266 object_class
->set_arg
= collection_set_arg
;
267 object_class
->get_arg
= collection_get_arg
;
269 class->gain_selection
= NULL
;
270 class->lose_selection
= NULL
;
271 class->selection_changed
= NULL
;
273 collection_signals
[GAIN_SELECTION
] = gtk_signal_new("gain_selection",
276 GTK_SIGNAL_OFFSET(CollectionClass
,
278 gtk_marshal_NONE__INT
,
281 collection_signals
[LOSE_SELECTION
] = gtk_signal_new("lose_selection",
284 GTK_SIGNAL_OFFSET(CollectionClass
,
286 gtk_marshal_NONE__INT
,
289 collection_signals
[SELECTION_CHANGED
] = gtk_signal_new(
293 GTK_SIGNAL_OFFSET(CollectionClass
,
295 gtk_marshal_NONE__INT
,
300 gtk_object_class_add_signals(object_class
,
301 collection_signals
, LAST_SIGNAL
);
305 static void collection_init(Collection
*object
)
307 g_return_if_fail(object
!= NULL
);
308 g_return_if_fail(IS_COLLECTION(object
));
310 GTK_WIDGET_SET_FLAGS(GTK_WIDGET(object
), GTK_CAN_FOCUS
);
312 object
->number_of_items
= 0;
313 object
->number_selected
= 0;
314 object
->block_selection_changed
= 0;
316 object
->item_width
= 64;
317 object
->item_height
= 64;
319 object
->paint_level
= PAINT_OVERWRITE
;
320 object
->last_scroll
= 0;
321 object
->bg_gc
= NULL
;
323 object
->items
= g_malloc(sizeof(CollectionItem
) * MINIMUM_ITEMS
);
324 object
->cursor_item
= -1;
325 object
->cursor_item_old
= -1;
326 object
->wink_item
= -1;
327 object
->array_size
= MINIMUM_ITEMS
;
328 object
->draw_item
= default_draw_item
;
329 object
->test_point
= default_test_point
;
331 object
->auto_scroll
= -1;
336 GtkWidget
* collection_new(GtkAdjustment
*vadj
)
339 g_return_val_if_fail(GTK_IS_ADJUSTMENT(vadj
), NULL
);
341 return GTK_WIDGET(gtk_widget_new(collection_get_type(),
346 /* Note: The draw_item call gives the maximum area that can be
347 * drawn to. For the column on the far right, this extends to the
348 * edge of the window. Normally, use collection->item_width instead
349 * of area->width to calculate the position.
351 * test_point does not use a larger value for the width, but the
352 * x point of the click may be larger than the width.
354 void collection_set_functions(Collection
*collection
,
355 CollectionDrawFunc draw_item
,
356 CollectionTestFunc test_point
,
361 g_return_if_fail(collection
!= NULL
);
362 g_return_if_fail(IS_COLLECTION(collection
));
364 widget
= GTK_WIDGET(collection
);
367 draw_item
= default_draw_item
;
369 test_point
= default_test_point
;
371 collection
->draw_item
= draw_item
;
372 collection
->test_point
= test_point
;
373 collection
->cb_user_data
= user_data
;
375 if (GTK_WIDGET_REALIZED(widget
))
377 collection
->paint_level
= PAINT_CLEAR
;
378 gtk_widget_queue_clear(widget
);
382 /* After this we are unusable, but our data (if any) is still hanging around.
383 * It will be freed later with finalize.
385 static void collection_destroy(GtkObject
*object
)
387 Collection
*collection
;
389 g_return_if_fail(object
!= NULL
);
390 g_return_if_fail(IS_COLLECTION(object
));
392 collection
= COLLECTION(object
);
394 if (collection
->wink_item
!= -1)
396 collection
->wink_item
= -1;
397 gtk_timeout_remove(collection
->wink_timeout
);
400 if (collection
->auto_scroll
!= -1)
402 gtk_timeout_remove(collection
->auto_scroll
);
403 collection
->auto_scroll
= -1;
406 if (collection
->bg_gc
)
408 gdk_gc_destroy(collection
->bg_gc
);
409 collection
->bg_gc
= NULL
;
412 if (collection
->vadj
)
414 gtk_signal_disconnect_by_data(GTK_OBJECT(collection
->vadj
),
416 gtk_object_unref(GTK_OBJECT(collection
->vadj
));
417 collection
->vadj
= NULL
;
420 if (GTK_OBJECT_CLASS(parent_class
)->destroy
)
421 (*GTK_OBJECT_CLASS(parent_class
)->destroy
)(object
);
424 /* This is the last thing that happens to us. Free all data. */
425 static void collection_finalize(GtkObject
*object
)
427 Collection
*collection
;
429 collection
= COLLECTION(object
);
431 g_free(collection
->items
);
434 static GdkGC
*create_bg_gc(GtkWidget
*widget
)
438 values
.tile
= widget
->style
->bg_pixmap
[GTK_STATE_NORMAL
];
439 values
.fill
= GDK_TILED
;
441 return gdk_gc_new_with_values(widget
->window
, &values
,
442 GDK_GC_FILL
| GDK_GC_TILE
);
445 static void collection_realize(GtkWidget
*widget
)
447 Collection
*collection
;
448 GdkWindowAttr attributes
;
449 gint attributes_mask
;
450 GdkGCValues xor_values
;
453 g_return_if_fail(widget
!= NULL
);
454 g_return_if_fail(IS_COLLECTION(widget
));
455 g_return_if_fail(widget
->parent
!= NULL
);
457 GTK_WIDGET_SET_FLAGS(widget
, GTK_REALIZED
);
458 collection
= COLLECTION(widget
);
460 attributes
.x
= widget
->allocation
.x
;
461 attributes
.y
= widget
->allocation
.y
;
462 attributes
.width
= widget
->allocation
.width
;
463 attributes
.height
= widget
->allocation
.height
;
464 attributes
.wclass
= GDK_INPUT_OUTPUT
;
465 attributes
.window_type
= GDK_WINDOW_CHILD
;
466 attributes
.event_mask
= gtk_widget_get_events(widget
) |
468 GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK
|
469 GDK_BUTTON1_MOTION_MASK
| GDK_BUTTON2_MOTION_MASK
|
470 GDK_BUTTON3_MOTION_MASK
;
471 attributes
.visual
= gtk_widget_get_visual(widget
);
472 attributes
.colormap
= gtk_widget_get_colormap(widget
);
474 attributes_mask
= GDK_WA_X
| GDK_WA_Y
|
475 GDK_WA_VISUAL
| GDK_WA_COLORMAP
;
476 widget
->window
= gdk_window_new(widget
->parent
->window
,
477 &attributes
, attributes_mask
);
479 widget
->style
= gtk_style_attach(widget
->style
, widget
->window
);
481 gdk_window_set_user_data(widget
->window
, widget
);
483 gdk_window_set_background(widget
->window
,
484 &widget
->style
->bg
[GTK_STATE_NORMAL
]);
485 if (widget
->style
->bg_pixmap
[GTK_STATE_NORMAL
])
486 collection
->bg_gc
= create_bg_gc(widget
);
488 /* Try to stop everything flickering horribly */
489 gdk_window_set_static_gravities(widget
->window
, TRUE
);
491 set_vadjustment(collection
);
493 bg
= &widget
->style
->bg
[GTK_STATE_NORMAL
];
494 fg
= &widget
->style
->fg
[GTK_STATE_NORMAL
];
495 xor_values
.function
= GDK_XOR
;
496 xor_values
.foreground
.pixel
= fg
->pixel
^ bg
->pixel
;
497 collection
->xor_gc
= gdk_gc_new_with_values(widget
->window
,
503 static void collection_size_request(GtkWidget
*widget
,
504 GtkRequisition
*requisition
)
506 requisition
->width
= MIN_WIDTH
;
507 requisition
->height
= MIN_HEIGHT
;
510 static void collection_size_allocate(GtkWidget
*widget
,
511 GtkAllocation
*allocation
)
513 Collection
*collection
;
516 g_return_if_fail(widget
!= NULL
);
517 g_return_if_fail(IS_COLLECTION(widget
));
518 g_return_if_fail(allocation
!= NULL
);
520 collection
= COLLECTION(widget
);
522 old_columns
= collection
->columns
;
523 if (widget
->allocation
.x
!= allocation
->x
524 || widget
->allocation
.y
!= allocation
->y
)
525 collection
->paint_level
= PAINT_CLEAR
;
527 widget
->allocation
= *allocation
;
529 collection
->columns
= allocation
->width
/ collection
->item_width
;
530 if (collection
->columns
< 1)
531 collection
->columns
= 1;
533 if (GTK_WIDGET_REALIZED(widget
))
535 gdk_window_move_resize(widget
->window
,
536 allocation
->x
, allocation
->y
,
537 allocation
->width
, allocation
->height
);
539 /* Force a redraw if the number of columns has changed
540 * or we have a background pixmap (!).
542 if (old_columns
!= collection
->columns
|| collection
->bg_gc
)
544 collection
->paint_level
= PAINT_CLEAR
;
545 gtk_widget_queue_clear(widget
);
548 set_vadjustment(collection
);
550 if (collection
->cursor_item
!= -1)
551 scroll_to_show(collection
, collection
->cursor_item
);
555 static void collection_set_style(GtkWidget
*widget
,
556 GtkStyle
*previous_style
)
558 Collection
*collection
;
560 g_return_if_fail(IS_COLLECTION(widget
));
562 collection
= COLLECTION(widget
);
564 collection
->paint_level
= PAINT_CLEAR
;
566 if (GTK_WIDGET_REALIZED(widget
))
568 gdk_window_set_background(widget
->window
,
569 &widget
->style
->bg
[GTK_STATE_NORMAL
]);
571 if (collection
->bg_gc
)
573 gdk_gc_destroy(collection
->bg_gc
);
574 collection
->bg_gc
= NULL
;
577 if (widget
->style
->bg_pixmap
[GTK_STATE_NORMAL
])
578 collection
->bg_gc
= create_bg_gc(widget
);
582 static void clear_area(Collection
*collection
, GdkRectangle
*area
)
584 GtkWidget
*widget
= GTK_WIDGET(collection
);
585 int scroll
= collection
->vadj
->value
;
587 if (collection
->bg_gc
)
589 gdk_gc_set_ts_origin(collection
->bg_gc
, 0, -scroll
);
591 gdk_draw_rectangle(widget
->window
,
595 area
->width
, area
->height
);
598 gdk_window_clear_area(widget
->window
,
599 area
->x
, area
->y
, area
->width
, area
->height
);
602 /* Return the area occupied by the item at (row, col) by filling
605 static void collection_get_item_area(Collection
*collection
,
610 int scroll
= collection
->vadj
->value
; /* (round to int) */
612 area
->x
= col
* collection
->item_width
;
613 area
->y
= row
* collection
->item_height
- scroll
;
615 area
->width
= collection
->item_width
;
616 area
->height
= collection
->item_height
;
617 if (col
== collection
->columns
- 1)
621 static gint
collection_paint(Collection
*collection
,
624 GdkRectangle whole
, item_area
;
629 int start_row
, last_row
;
630 int start_col
, last_col
;
635 scroll
= collection
->vadj
->value
;
637 widget
= GTK_WIDGET(collection
);
639 gdk_window_get_size(widget
->window
, &width
, &height
);
643 whole
.height
= height
;
645 if (collection
->paint_level
> PAINT_NORMAL
|| area
== NULL
)
649 if (collection
->paint_level
== PAINT_CLEAR
650 && !collection
->lasso_box
)
651 clear_area(collection
, area
);
653 collection
->paint_level
= PAINT_NORMAL
;
656 /* Calculate the ranges to plot */
657 start_row
= (area
->y
+ scroll
) / collection
->item_height
;
658 last_row
= (area
->y
+ area
->height
- 1 + scroll
)
659 / collection
->item_height
;
662 start_col
= area
->x
/ collection
->item_width
;
663 phys_last_col
= (area
->x
+ area
->width
- 1) / collection
->item_width
;
665 if (collection
->lasso_box
)
667 /* You can't be too careful with lasso boxes...
669 * clip gives the total area drawn over (this may be larger
670 * than the requested area). It's used to redraw the lasso
673 collection_get_item_area(collection
,
674 start_row
, start_col
, &clip
);
675 clip
.width
*= phys_last_col
- start_col
+ 1;
676 clip
.height
*= last_row
- start_row
+ 1;
678 clear_area(collection
, &clip
);
681 /* The right-most column may be wider than the others.
682 * Therefore, to redraw the area after the last 'real' column
683 * we may have to draw the right-most column.
685 if (start_col
>= collection
->columns
)
686 start_col
= collection
->columns
- 1;
688 if (phys_last_col
>= collection
->columns
)
689 last_col
= collection
->columns
- 1;
691 last_col
= phys_last_col
;
695 item
= row
* collection
->columns
+ col
;
697 while ((item
== 0 || item
< collection
->number_of_items
)
700 collection_get_item_area(collection
, row
, col
, &item_area
);
702 draw_one_item(collection
, item
, &item_area
);
709 item
= row
* collection
->columns
+ col
;
715 if (collection
->lasso_box
)
717 gdk_gc_set_clip_rectangle(collection
->xor_gc
, &clip
);
718 draw_lasso_box(collection
);
719 gdk_gc_set_clip_rectangle(collection
->xor_gc
, NULL
);
725 static void default_draw_item( GtkWidget
*widget
,
726 CollectionItem
*item
,
730 gdk_draw_arc(widget
->window
,
731 item
->selected
? widget
->style
->white_gc
732 : widget
->style
->black_gc
,
735 COLLECTION(widget
)->item_width
, area
->height
,
740 static gboolean
default_test_point(Collection
*collection
,
741 int point_x
, int point_y
,
742 CollectionItem
*item
,
743 int width
, int height
,
748 /* Convert to point in unit circle */
749 f_x
= ((float) point_x
/ width
) - 0.5;
750 f_y
= ((float) point_y
/ height
) - 0.5;
752 return (f_x
* f_x
) + (f_y
* f_y
) <= .25;
755 static void collection_set_arg( GtkObject
*object
,
759 Collection
*collection
;
761 collection
= COLLECTION(object
);
765 case ARG_VADJUSTMENT
:
766 collection_set_adjustment(collection
,
767 GTK_VALUE_POINTER(*arg
));
774 static void collection_set_adjustment( Collection
*collection
,
778 g_return_if_fail (GTK_IS_ADJUSTMENT (vadj
));
780 vadj
= GTK_ADJUSTMENT(gtk_adjustment_new(0.0,
783 if (collection
->vadj
&& (collection
->vadj
!= vadj
))
785 gtk_signal_disconnect_by_data(GTK_OBJECT(collection
->vadj
),
787 gtk_object_unref(GTK_OBJECT(collection
->vadj
));
790 if (collection
->vadj
!= vadj
)
792 collection
->vadj
= vadj
;
793 gtk_object_ref(GTK_OBJECT(collection
->vadj
));
794 gtk_object_sink(GTK_OBJECT(collection
->vadj
));
796 gtk_signal_connect(GTK_OBJECT(collection
->vadj
),
798 (GtkSignalFunc
) collection_adjustment
,
800 gtk_signal_connect(GTK_OBJECT(collection
->vadj
),
802 (GtkSignalFunc
) collection_adjustment
,
805 /* Is this used for anything? */
806 gtk_signal_connect(GTK_OBJECT(collection
->vadj
),
808 (GtkSignalFunc
) collection_disconnect
,
811 collection_adjustment(vadj
, collection
);
815 static void collection_get_arg( GtkObject
*object
,
819 Collection
*collection
;
821 collection
= COLLECTION(object
);
825 case ARG_VADJUSTMENT
:
826 GTK_VALUE_POINTER(*arg
) = collection
->vadj
;
829 arg
->type
= GTK_TYPE_INVALID
;
834 /* Something about the adjustment has changed */
835 static void collection_adjustment(GtkAdjustment
*adjustment
,
836 Collection
*collection
)
840 g_return_if_fail(adjustment
!= NULL
);
841 g_return_if_fail(GTK_IS_ADJUSTMENT(adjustment
));
842 g_return_if_fail(collection
!= NULL
);
843 g_return_if_fail(IS_COLLECTION(collection
));
845 diff
= ((gint
) adjustment
->value
) - collection
->last_scroll
;
849 collection
->last_scroll
= adjustment
->value
;
851 if (collection
->lasso_box
)
853 remove_lasso_box(collection
);
854 collection
->drag_box_y
[0] -= diff
;
855 scroll_by(collection
, diff
);
856 add_lasso_box(collection
);
859 scroll_by(collection
, diff
);
864 static void collection_disconnect(GtkAdjustment
*adjustment
,
865 Collection
*collection
)
867 g_return_if_fail(adjustment
!= NULL
);
868 g_return_if_fail(GTK_IS_ADJUSTMENT(adjustment
));
869 g_return_if_fail(collection
!= NULL
);
870 g_return_if_fail(IS_COLLECTION(collection
));
872 collection_set_adjustment(collection
, NULL
);
876 static void set_vadjustment(Collection
*collection
)
882 widget
= GTK_WIDGET(collection
);
884 if (!GTK_WIDGET_REALIZED(widget
))
887 gdk_window_get_size(widget
->window
, NULL
, &height
);
888 cols
= collection
->columns
;
889 rows
= (collection
->number_of_items
+ cols
- 1) / cols
;
891 collection
->vadj
->lower
= 0.0;
892 collection
->vadj
->upper
= collection
->item_height
* rows
;
893 if (!collection
->vadj
->upper
)
894 collection
->vadj
->upper
= 1;
896 collection
->vadj
->step_increment
=
897 MIN(collection
->vadj
->upper
, collection
->item_height
/ 4);
899 collection
->vadj
->page_increment
=
900 MIN(collection
->vadj
->upper
,
903 collection
->vadj
->page_size
= MIN(collection
->vadj
->upper
, height
);
905 collection
->vadj
->value
= MIN(collection
->vadj
->value
,
906 collection
->vadj
->upper
- collection
->vadj
->page_size
);
908 collection
->vadj
->value
= MAX(collection
->vadj
->value
, 0.0);
910 gtk_signal_emit_by_name(GTK_OBJECT(collection
->vadj
), "changed");
913 /* Change the adjustment by this amount. Bounded. */
914 static void diff_vpos(Collection
*collection
, int diff
)
916 int value
= collection
->vadj
->value
+ diff
;
918 value
= CLAMP(value
, 0,
919 collection
->vadj
->upper
- collection
->vadj
->page_size
);
920 gtk_adjustment_set_value(collection
->vadj
, value
);
924 static void collection_draw(GtkWidget
*widget
, GdkRectangle
*area
)
926 Collection
*collection
;
928 g_return_if_fail(widget
!= NULL
);
929 g_return_if_fail(IS_COLLECTION(widget
));
930 g_return_if_fail(area
!= NULL
); /* Not actually used */
932 collection
= COLLECTION(widget
);
934 /* This doesn't always work - I think Gtk+ may be doing some
935 * kind of expose-event compression...
936 * 29/9/2000: Turned expose_events on in copy_area... maybe
937 * we can use this again? Try after 1.0.0!
938 if (collection->paint_level > PAINT_NORMAL)
940 collection_paint(collection
, area
);
944 static gint
collection_expose(GtkWidget
*widget
, GdkEventExpose
*event
)
946 Collection
*collection
;
948 g_return_val_if_fail(widget
!= NULL
, FALSE
);
949 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
950 g_return_val_if_fail(event
!= NULL
, FALSE
);
952 collection
= COLLECTION(widget
);
954 clear_area(collection
, &event
->area
);
956 collection_paint(collection
, &event
->area
);
961 /* Positive makes the contents go move up the screen */
962 static void scroll_by(Collection
*collection
, gint diff
)
968 GdkRectangle new_area
;
973 widget
= GTK_WIDGET(collection
);
975 if (collection
->lasso_box
)
976 abort_lasso(collection
);
978 gdk_window_get_size(widget
->window
, &width
, &height
);
980 new_area
.width
= width
;
987 new_area
.y
= height
- amount
;
997 new_area
.height
= amount
;
1001 static GdkGC
*expo_gc
= NULL
;
1005 expo_gc
= gdk_gc_new(widget
->window
);
1006 gdk_gc_copy(expo_gc
, widget
->style
->white_gc
);
1007 gdk_gc_set_exposures(expo_gc
, TRUE
);
1010 gdk_draw_pixmap(widget
->window
,
1019 /* We have to redraw everything because any pending
1020 * expose events now contain invalid areas.
1021 * Don't need to clear the area first though...
1023 if (collection
->paint_level
< PAINT_OVERWRITE
)
1024 collection
->paint_level
= PAINT_OVERWRITE
;
1027 collection
->paint_level
= PAINT_CLEAR
;
1029 clear_area(collection
, &new_area
);
1030 collection_paint(collection
, NULL
);
1033 static void resize_arrays(Collection
*collection
, guint new_size
)
1035 g_return_if_fail(collection
!= NULL
);
1036 g_return_if_fail(IS_COLLECTION(collection
));
1037 g_return_if_fail(new_size
>= collection
->number_of_items
);
1039 collection
->items
= g_realloc(collection
->items
,
1040 sizeof(CollectionItem
) * new_size
);
1041 collection
->array_size
= new_size
;
1044 static gint
collection_key_press(GtkWidget
*widget
, GdkEventKey
*event
)
1046 Collection
*collection
;
1050 g_return_val_if_fail(widget
!= NULL
, FALSE
);
1051 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
1052 g_return_val_if_fail(event
!= NULL
, FALSE
);
1054 collection
= (Collection
*) widget
;
1055 item
= collection
->cursor_item
;
1057 key
= event
->keyval
;
1058 if (event
->state
& (GDK_CONTROL_MASK
| GDK_SHIFT_MASK
))
1060 if (key
== GDK_Left
|| key
== GDK_Right
|| \
1061 key
== GDK_Up
|| key
== GDK_Down
)
1069 collection_move_cursor(collection
, 0, -1);
1072 collection_move_cursor(collection
, 0, 1);
1075 collection_move_cursor(collection
, -1, 0);
1078 collection_move_cursor(collection
, 1, 0);
1081 collection_set_cursor_item(collection
, 0);
1084 collection_set_cursor_item(collection
,
1085 MAX((gint
) collection
->number_of_items
- 1, 0));
1090 get_visible_limits(collection
, &first
, &last
);
1091 collection_move_cursor(collection
, first
- last
- 1, 0);
1097 get_visible_limits(collection
, &first
, &last
);
1098 collection_move_cursor(collection
, last
- first
+ 1, 0);
1102 collection_set_cursor_item(collection
, -1);
1103 collection_clear_selection(collection
);
1104 return FALSE
; /* Pass it on */
1106 if (item
>=0 && item
< collection
->number_of_items
)
1108 collection_toggle_item(collection
, item
);
1109 if (item
< collection
->number_of_items
- 1)
1110 collection_set_cursor_item(collection
,
1121 static gint
collection_button_press(GtkWidget
*widget
,
1122 GdkEventButton
*event
)
1124 Collection
*collection
;
1127 g_return_val_if_fail(widget
!= NULL
, FALSE
);
1128 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
1129 g_return_val_if_fail(event
!= NULL
, FALSE
);
1131 collection
= COLLECTION(widget
);
1133 if (event
->button
<= 3 || event
->type
!= GDK_BUTTON_PRESS
)
1134 return FALSE
; /* Only deal with wheel events here */
1136 /* Wheel mouse scrolling */
1139 if (event
->button
== 4)
1141 else if (event
->button
!= 5)
1146 int old_value
= collection
->vadj
->value
;
1148 gboolean box
= collection
->lasso_box
;
1150 new_value
= CLAMP(old_value
+ diff
, 0.0,
1151 collection
->vadj
->upper
1152 - collection
->vadj
->page_size
);
1153 diff
= new_value
- old_value
;
1158 remove_lasso_box(collection
);
1159 collection
->drag_box_y
[0] -= diff
;
1161 collection
->vadj
->value
= new_value
;
1162 gtk_signal_emit_by_name(
1163 GTK_OBJECT(collection
->vadj
),
1166 add_lasso_box(collection
);
1173 /* 'from' and 'to' are pixel positions. 'step' is the size of each item.
1174 * Returns the index of the first item covered, and the number of items.
1177 static void get_range(int from
, int to
, int step
, gint
*pos
, gint
*len
)
1179 static void get_range(int from
, int to
, int step
, short *pos
, short *len
)
1189 from
= (from
+ step
/ 4) / step
; /* First item */
1190 to
= (to
+ step
- step
/ 4) / step
; /* Last item (inclusive) */
1192 *pos
= MAX(from
, 0);
1196 /* Fills in the area with a rectangle corresponding to the current
1197 * size of the lasso box (units of items, not pixels).
1199 * The box will only span valid columns, but the total number
1200 * of items is not taken into account (rows or cols).
1202 static void find_lasso_area(Collection
*collection
, GdkRectangle
*area
)
1205 int cols
= collection
->columns
;
1206 int dx
= collection
->drag_box_x
[0] - collection
->drag_box_x
[1];
1207 int dy
= collection
->drag_box_y
[0] - collection
->drag_box_y
[1];
1209 if (ABS(dx
) < 8 && ABS(dy
) < 8)
1211 /* Didn't move far enough - ignore */
1212 area
->x
= area
->y
= 0;
1218 scroll
= collection
->vadj
->value
;
1220 get_range(collection
->drag_box_x
[0],
1221 collection
->drag_box_x
[1],
1222 collection
->item_width
,
1226 if (area
->x
>= cols
)
1228 else if (area
->x
+ area
->width
> cols
)
1229 area
->width
= cols
- area
->x
;
1231 get_range(collection
->drag_box_y
[0] + scroll
,
1232 collection
->drag_box_y
[1] + scroll
,
1233 collection
->item_height
,
1238 static void collection_process_area(Collection
*collection
,
1244 guint32 stacked_time
;
1246 gboolean changed
= FALSE
;
1248 g_return_if_fail(fn
== GDK_SET
|| fn
== GDK_INVERT
);
1250 stacked_time
= current_event_time
;
1251 current_event_time
= time
;
1253 collection
->block_selection_changed
++;
1255 for (y
= area
->y
; y
< area
->y
+ area
->height
; y
++)
1257 item
= y
* collection
->columns
+ area
->x
;
1259 for (x
= area
->x
; x
< area
->x
+ area
->width
; x
++)
1261 if (item
>= collection
->number_of_items
)
1264 if (fn
== GDK_INVERT
)
1265 collection_toggle_item(collection
, item
);
1267 collection_select_item(collection
, item
);
1275 collection_unblock_selection_changed(collection
,
1276 current_event_time
, changed
);
1277 current_event_time
= stacked_time
;
1280 static gint
collection_motion_notify(GtkWidget
*widget
,
1281 GdkEventMotion
*event
)
1283 Collection
*collection
;
1286 g_return_val_if_fail(widget
!= NULL
, FALSE
);
1287 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
1288 g_return_val_if_fail(event
!= NULL
, FALSE
);
1290 collection
= COLLECTION(widget
);
1292 if (!collection
->lasso_box
)
1295 if (event
->window
!= widget
->window
)
1296 gdk_window_get_pointer(widget
->window
, &x
, &y
, NULL
);
1303 remove_lasso_box(collection
);
1304 collection
->drag_box_x
[1] = x
;
1305 collection
->drag_box_y
[1] = y
;
1306 add_lasso_box(collection
);
1310 static void add_lasso_box(Collection
*collection
)
1312 g_return_if_fail(collection
!= NULL
);
1313 g_return_if_fail(IS_COLLECTION(collection
));
1314 g_return_if_fail(collection
->lasso_box
== FALSE
);
1316 collection
->lasso_box
= TRUE
;
1317 draw_lasso_box(collection
);
1320 static void draw_lasso_box(Collection
*collection
)
1323 int x
, y
, width
, height
;
1325 widget
= GTK_WIDGET(collection
);
1327 x
= MIN(collection
->drag_box_x
[0], collection
->drag_box_x
[1]);
1328 y
= MIN(collection
->drag_box_y
[0], collection
->drag_box_y
[1]);
1329 width
= abs(collection
->drag_box_x
[1] - collection
->drag_box_x
[0]);
1330 height
= abs(collection
->drag_box_y
[1] - collection
->drag_box_y
[0]);
1332 gdk_draw_rectangle(widget
->window
, collection
->xor_gc
, FALSE
,
1333 x
, y
, width
, height
);
1336 static void abort_lasso(Collection
*collection
)
1338 if (collection
->lasso_box
)
1340 remove_lasso_box(collection
);
1341 collection_set_autoscroll(collection
, FALSE
);
1345 static void remove_lasso_box(Collection
*collection
)
1347 g_return_if_fail(collection
!= NULL
);
1348 g_return_if_fail(IS_COLLECTION(collection
));
1349 g_return_if_fail(collection
->lasso_box
== TRUE
);
1351 draw_lasso_box(collection
);
1353 collection
->lasso_box
= FALSE
;
1358 /* Make sure that 'item' is fully visible (vertically), scrolling if not. */
1359 static void scroll_to_show(Collection
*collection
, int item
)
1361 int first
, last
, row
;
1363 g_return_if_fail(collection
!= NULL
);
1364 g_return_if_fail(IS_COLLECTION(collection
));
1366 row
= item
/ collection
->columns
;
1367 get_visible_limits(collection
, &first
, &last
);
1371 gtk_adjustment_set_value(collection
->vadj
,
1372 row
* collection
->item_height
);
1374 else if (row
>= last
)
1376 GtkWidget
*widget
= (GtkWidget
*) collection
;
1379 if (GTK_WIDGET_REALIZED(widget
))
1381 gdk_window_get_size(widget
->window
, NULL
, &height
);
1382 gtk_adjustment_set_value(collection
->vadj
,
1383 (row
+ 1) * collection
->item_height
- height
);
1388 /* Return the first and last rows which are [partly] visible. Does not
1389 * ensure that the rows actually exist (contain items).
1391 static void get_visible_limits(Collection
*collection
, int *first
, int *last
)
1393 GtkWidget
*widget
= (GtkWidget
*) collection
;
1394 gint scroll
, height
;
1396 g_return_if_fail(collection
!= NULL
);
1397 g_return_if_fail(IS_COLLECTION(collection
));
1398 g_return_if_fail(first
!= NULL
&& last
!= NULL
);
1400 if (!GTK_WIDGET_REALIZED(widget
))
1407 scroll
= collection
->vadj
->value
;
1408 gdk_window_get_size(widget
->window
, NULL
, &height
);
1410 *first
= MAX(scroll
/ collection
->item_height
, 0);
1411 *last
= (scroll
+ height
- 1) /collection
->item_height
;
1418 /* Cancel the current wink effect. */
1419 static void cancel_wink(Collection
*collection
)
1423 g_return_if_fail(collection
!= NULL
);
1424 g_return_if_fail(IS_COLLECTION(collection
));
1425 g_return_if_fail(collection
->wink_item
!= -1);
1427 item
= collection
->wink_item
;
1429 collection
->wink_item
= -1;
1430 gtk_timeout_remove(collection
->wink_timeout
);
1432 collection_draw_item(collection
, item
, TRUE
);
1435 /* Draw/undraw a box around collection->wink_item */
1436 static void invert_wink(Collection
*collection
)
1441 g_return_if_fail(collection
->wink_item
>= 0);
1443 if (!GTK_WIDGET_REALIZED(GTK_WIDGET(collection
)))
1446 col
= collection
->wink_item
% collection
->columns
;
1447 row
= collection
->wink_item
/ collection
->columns
;
1448 collection_get_item_area(collection
, row
, col
, &area
);
1450 gdk_draw_rectangle(((GtkWidget
*) collection
)->window
,
1451 collection
->xor_gc
, FALSE
,
1453 collection
->item_width
- 1,
1457 static gboolean
wink_timeout(Collection
*collection
)
1461 g_return_val_if_fail(collection
!= NULL
, FALSE
);
1462 g_return_val_if_fail(IS_COLLECTION(collection
), FALSE
);
1463 g_return_val_if_fail(collection
->wink_item
!= -1, FALSE
);
1465 item
= collection
->wink_item
;
1467 if (collection
->winks_left
-- > 0)
1469 invert_wink(collection
);
1473 collection
->wink_item
= -1;
1475 collection_draw_item(collection
, item
, TRUE
);
1481 static gint
focus_in(GtkWidget
*widget
, GdkEventFocus
*event
)
1483 g_return_val_if_fail(widget
!= NULL
, FALSE
);
1484 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
1485 g_return_val_if_fail(event
!= NULL
, FALSE
);
1487 GTK_WIDGET_SET_FLAGS(widget
, GTK_HAS_FOCUS
);
1488 gtk_widget_draw_focus(widget
);
1493 static gint
focus_out(GtkWidget
*widget
, GdkEventFocus
*event
)
1495 g_return_val_if_fail(widget
!= NULL
, FALSE
);
1496 g_return_val_if_fail(IS_COLLECTION(widget
), FALSE
);
1497 g_return_val_if_fail(event
!= NULL
, FALSE
);
1499 GTK_WIDGET_UNSET_FLAGS(widget
, GTK_HAS_FOCUS
);
1500 gtk_widget_draw_focus(widget
);
1506 /* This is called frequently while auto_scroll is on.
1507 * Checks the pointer position and scrolls the window if it's
1508 * near the top or bottom.
1510 static gboolean
as_timeout(Collection
*collection
)
1512 GdkWindow
*window
= GTK_WIDGET(collection
)->window
;
1514 GdkModifierType mask
;
1517 gdk_window_get_pointer(window
, &x
, &y
, &mask
);
1518 gdk_window_get_size(window
, &w
, &h
);
1520 if ((x
< 0 || x
> w
|| y
< 0 || y
> h
) && !collection
->lasso_box
)
1522 collection
->auto_scroll
= -1;
1523 return FALSE
; /* Out of window - stop */
1526 if (y
< AUTOSCROLL_STEP
)
1527 diff
= y
- AUTOSCROLL_STEP
;
1528 else if (y
> h
- AUTOSCROLL_STEP
)
1529 diff
= AUTOSCROLL_STEP
+ y
- h
;
1532 diff_vpos(collection
, diff
);
1537 /* Change the selected state of an item */
1538 static void collection_item_set_selected(Collection
*collection
,
1542 g_return_if_fail(collection
!= NULL
);
1543 g_return_if_fail(IS_COLLECTION(collection
));
1544 g_return_if_fail(item
>= 0 && item
< collection
->number_of_items
);
1546 if (collection
->items
[item
].selected
== selected
)
1549 collection
->items
[item
].selected
= selected
;
1550 collection_draw_item(collection
, item
, TRUE
);
1554 collection
->number_selected
++;
1555 if (collection
->number_selected
== 1)
1556 gtk_signal_emit(GTK_OBJECT(collection
),
1557 collection_signals
[GAIN_SELECTION
],
1558 current_event_time
);
1562 collection
->number_selected
--;
1563 if (collection
->number_selected
== 0)
1564 gtk_signal_emit(GTK_OBJECT(collection
),
1565 collection_signals
[LOSE_SELECTION
],
1566 current_event_time
);
1569 EMIT_SELECTION_CHANGED(collection
, current_event_time
);
1572 /* Functions for managing collections */
1574 /* Remove all objects from the collection */
1575 void collection_clear(Collection
*collection
)
1579 g_return_if_fail(IS_COLLECTION(collection
));
1581 if (collection
->number_of_items
== 0)
1584 if (collection
->wink_item
!= -1)
1586 collection
->wink_item
= -1;
1587 gtk_timeout_remove(collection
->wink_timeout
);
1590 collection_set_cursor_item(collection
,
1591 collection
->cursor_item
== -1 ? -1: 0);
1592 collection
->cursor_item_old
= -1;
1593 prev_selected
= collection
->number_selected
;
1594 collection
->number_of_items
= collection
->number_selected
= 0;
1596 resize_arrays(collection
, MINIMUM_ITEMS
);
1598 collection
->paint_level
= PAINT_CLEAR
;
1600 gtk_widget_queue_clear(GTK_WIDGET(collection
));
1602 if (prev_selected
&& collection
->number_selected
== 0)
1603 gtk_signal_emit(GTK_OBJECT(collection
),
1604 collection_signals
[LOSE_SELECTION
],
1605 current_event_time
);
1608 /* Inserts a new item at the end. The new item is unselected, and its
1609 * number is returned.
1611 gint
collection_insert(Collection
*collection
, gpointer data
)
1615 g_return_val_if_fail(IS_COLLECTION(collection
), -1);
1617 item
= collection
->number_of_items
;
1619 if (item
>= collection
->array_size
)
1620 resize_arrays(collection
, item
+ (item
>> 1));
1622 collection
->items
[item
].data
= data
;
1623 collection
->items
[item
].selected
= FALSE
;
1625 collection
->number_of_items
++;
1627 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection
)))
1629 set_vadjustment(collection
);
1630 collection_draw_item(collection
,
1631 collection
->number_of_items
- 1,
1638 void collection_unselect_item(Collection
*collection
, gint item
)
1640 collection_item_set_selected(collection
, item
, FALSE
);
1643 void collection_select_item(Collection
*collection
, gint item
)
1645 collection_item_set_selected(collection
, item
, TRUE
);
1648 void collection_toggle_item(Collection
*collection
, gint item
)
1650 collection_item_set_selected(collection
, item
,
1651 !collection
->items
[item
].selected
);
1654 /* Select all items in the collection */
1655 void collection_select_all(Collection
*collection
)
1660 g_return_if_fail(collection
!= NULL
);
1661 g_return_if_fail(IS_COLLECTION(collection
));
1663 widget
= GTK_WIDGET(collection
);
1665 if (collection
->number_selected
== collection
->number_of_items
)
1666 return; /* Nothing to do */
1668 while (collection
->number_selected
< collection
->number_of_items
)
1670 while (collection
->items
[item
].selected
)
1673 collection
->items
[item
].selected
= TRUE
;
1674 collection_draw_item(collection
, item
, TRUE
);
1677 collection
->number_selected
++;
1680 gtk_signal_emit(GTK_OBJECT(collection
),
1681 collection_signals
[GAIN_SELECTION
],
1682 current_event_time
);
1683 EMIT_SELECTION_CHANGED(collection
, current_event_time
);
1686 /* Unselect all items except number item, which is selected (-1 to unselect
1689 void collection_clear_except(Collection
*collection
, gint item
)
1693 int end
; /* Selected items to end up with */
1695 g_return_if_fail(collection
!= NULL
);
1696 g_return_if_fail(IS_COLLECTION(collection
));
1697 g_return_if_fail(item
>= -1 && item
< collection
->number_of_items
);
1699 widget
= GTK_WIDGET(collection
);
1705 collection_select_item(collection
, item
);
1709 if (collection
->number_selected
== 0)
1712 while (collection
->number_selected
> end
)
1714 while (i
== item
|| !collection
->items
[i
].selected
)
1717 collection
->items
[i
].selected
= FALSE
;
1718 collection_draw_item(collection
, i
, TRUE
);
1721 collection
->number_selected
--;
1725 gtk_signal_emit(GTK_OBJECT(collection
),
1726 collection_signals
[LOSE_SELECTION
],
1727 current_event_time
);
1728 EMIT_SELECTION_CHANGED(collection
, current_event_time
);
1731 /* Unselect all items in the collection */
1732 void collection_clear_selection(Collection
*collection
)
1734 g_return_if_fail(collection
!= NULL
);
1735 g_return_if_fail(IS_COLLECTION(collection
));
1737 collection_clear_except(collection
, -1);
1740 /* Force a redraw of the specified item, if it is visible */
1741 void collection_draw_item(Collection
*collection
, gint item
, gboolean blank
)
1748 g_return_if_fail(collection
!= NULL
);
1749 g_return_if_fail(IS_COLLECTION(collection
));
1750 g_return_if_fail(item
>= 0 &&
1751 (item
== 0 || item
< collection
->number_of_items
));
1753 widget
= GTK_WIDGET(collection
);
1754 if (!GTK_WIDGET_REALIZED(widget
))
1757 col
= item
% collection
->columns
;
1758 row
= item
/ collection
->columns
;
1760 collection_get_item_area(collection
, row
, col
, &area
);
1762 if (area
.y
+ area
.height
< 0)
1765 gdk_window_get_size(widget
->window
, NULL
, &height
);
1767 if (area
.y
> height
)
1770 if (blank
|| collection
->lasso_box
)
1771 clear_area(collection
, &area
);
1773 draw_one_item(collection
, item
, &area
);
1775 if (collection
->lasso_box
)
1777 gdk_gc_set_clip_rectangle(collection
->xor_gc
, &area
);
1778 draw_lasso_box(collection
);
1779 gdk_gc_set_clip_rectangle(collection
->xor_gc
, NULL
);
1783 void collection_set_item_size(Collection
*collection
, int width
, int height
)
1787 g_return_if_fail(collection
!= NULL
);
1788 g_return_if_fail(IS_COLLECTION(collection
));
1789 g_return_if_fail(width
> 4 && height
> 4);
1791 if (collection
->item_width
== width
&&
1792 collection
->item_height
== height
)
1795 widget
= GTK_WIDGET(collection
);
1797 collection
->item_width
= width
;
1798 collection
->item_height
= height
;
1800 if (GTK_WIDGET_REALIZED(widget
))
1804 collection
->paint_level
= PAINT_CLEAR
;
1805 gdk_window_get_size(widget
->window
, &window_width
, NULL
);
1806 collection
->columns
= MAX(window_width
/ collection
->item_width
,
1809 set_vadjustment(collection
);
1810 if (collection
->cursor_item
!= -1)
1811 scroll_to_show(collection
, collection
->cursor_item
);
1812 gtk_widget_queue_draw(widget
);
1816 /* Cursor is positioned on item with the same data as before the sort.
1817 * Same for the wink item.
1819 void collection_qsort(Collection
*collection
,
1820 int (*compar
)(const void *, const void *))
1822 int cursor
, wink
, items
;
1823 gpointer cursor_data
= NULL
;
1824 gpointer wink_data
= NULL
;
1826 g_return_if_fail(collection
!= NULL
);
1827 g_return_if_fail(IS_COLLECTION(collection
));
1828 g_return_if_fail(compar
!= NULL
);
1830 items
= collection
->number_of_items
;
1832 wink
= collection
->wink_item
;
1833 if (wink
>= 0 && wink
< items
)
1835 wink_data
= collection
->items
[wink
].data
;
1836 collection
->wink_item
= -1;
1841 cursor
= collection
->cursor_item
;
1842 if (cursor
>= 0 && cursor
< items
)
1843 cursor_data
= collection
->items
[cursor
].data
;
1847 qsort(collection
->items
, items
, sizeof(collection
->items
[0]), compar
);
1849 if (cursor
> -1 || wink
> -1)
1853 for (item
= 0; item
< items
; item
++)
1855 if (collection
->items
[item
].data
== cursor_data
)
1856 collection_set_cursor_item(collection
, item
);
1857 if (collection
->items
[item
].data
== wink_data
)
1859 collection
->cursor_item_old
= item
;
1860 collection
->wink_item
= item
;
1861 scroll_to_show(collection
, item
);
1866 collection
->paint_level
= PAINT_CLEAR
;
1868 gtk_widget_queue_draw(GTK_WIDGET(collection
));
1871 /* Find an item in an unsorted collection.
1872 * Returns the item number, or -1 if not found.
1874 int collection_find_item(Collection
*collection
, gpointer data
,
1875 int (*compar
)(const void *, const void *))
1879 g_return_val_if_fail(collection
!= NULL
, -1);
1880 g_return_val_if_fail(IS_COLLECTION(collection
), -1);
1881 g_return_val_if_fail(compar
!= NULL
, -1);
1883 for (i
= 0; i
< collection
->number_of_items
; i
++)
1884 if (compar(&collection
->items
[i
].data
, &data
) == 0)
1890 /* Return the number of the item under the point (x,y), or -1 for none.
1891 * This may call your test_point callback. The point is relative to the
1892 * collection's origin.
1894 int collection_get_item(Collection
*collection
, int x
, int y
)
1901 g_return_val_if_fail(collection
!= NULL
, -1);
1903 scroll
= collection
->vadj
->value
;
1904 col
= x
/ collection
->item_width
;
1905 row
= (y
+ scroll
) / collection
->item_height
;
1907 if (col
>= collection
->columns
)
1908 col
= collection
->columns
- 1;
1910 if (col
< 0 || row
< 0)
1913 if (col
== collection
->columns
- 1)
1914 width
= collection
->item_width
<< 1;
1916 width
= collection
->item_width
;
1918 item
= col
+ row
* collection
->columns
;
1919 if (item
>= collection
->number_of_items
1921 !collection
->test_point(collection
,
1922 x
- col
* collection
->item_width
,
1923 y
- row
* collection
->item_height
1925 &collection
->items
[item
],
1927 collection
->item_height
,
1928 collection
->cb_user_data
))
1936 /* Set the cursor/highlight over the given item. Passing -1
1937 * hides the cursor. As a special case, you may set the cursor item
1938 * to zero when there are no items.
1940 void collection_set_cursor_item(Collection
*collection
, gint item
)
1944 g_return_if_fail(collection
!= NULL
);
1945 g_return_if_fail(IS_COLLECTION(collection
));
1946 g_return_if_fail(item
>= -1 &&
1947 (item
< collection
->number_of_items
|| item
== 0));
1949 old_item
= collection
->cursor_item
;
1951 if (old_item
== item
)
1954 collection
->cursor_item
= item
;
1957 collection_draw_item(collection
, old_item
, TRUE
);
1961 collection_draw_item(collection
, item
, TRUE
);
1962 if (collection
->auto_scroll
== -1)
1963 scroll_to_show(collection
, item
);
1965 else if (old_item
!= -1)
1966 collection
->cursor_item_old
= old_item
;
1969 /* Briefly highlight an item to draw the user's attention to it.
1970 * -1 cancels the effect, as does deleting items, sorting the collection
1971 * or starting a new wink effect.
1972 * Otherwise, the effect will cancel itself after a short pause.
1974 void collection_wink_item(Collection
*collection
, gint item
)
1976 g_return_if_fail(collection
!= NULL
);
1977 g_return_if_fail(IS_COLLECTION(collection
));
1978 g_return_if_fail(item
>= -1 && item
< collection
->number_of_items
);
1980 if (collection
->wink_item
!= -1)
1981 cancel_wink(collection
);
1985 collection
->cursor_item_old
= collection
->wink_item
= item
;
1986 collection
->winks_left
= 3;
1987 collection
->wink_timeout
= gtk_timeout_add(70,
1988 (GtkFunction
) wink_timeout
,
1990 scroll_to_show(collection
, item
);
1991 invert_wink(collection
);
1996 /* Call test(item, data) on each item in the collection.
1997 * Remove all items for which it returns TRUE. test() should
1998 * free the data before returning TRUE. The collection is in an
1999 * inconsistant state during this call (ie, when test() is called).
2001 void collection_delete_if(Collection
*collection
,
2002 gboolean (*test
)(gpointer item
, gpointer data
),
2009 g_return_if_fail(collection
!= NULL
);
2010 g_return_if_fail(IS_COLLECTION(collection
));
2011 g_return_if_fail(test
!= NULL
);
2013 cursor
= collection
->cursor_item
;
2015 for (in
= 0; in
< collection
->number_of_items
; in
++)
2017 if (!test(collection
->items
[in
].data
, data
))
2019 if (collection
->items
[in
].selected
)
2021 collection
->items
[out
].selected
= TRUE
;
2025 collection
->items
[out
].selected
= FALSE
;
2027 collection
->items
[out
++].data
=
2028 collection
->items
[in
].data
;
2030 else if (cursor
>= in
)
2036 collection
->cursor_item
= cursor
;
2038 if (collection
->wink_item
!= -1)
2040 collection
->wink_item
= -1;
2041 gtk_timeout_remove(collection
->wink_timeout
);
2044 collection
->number_of_items
= out
;
2045 if (collection
->number_selected
&& !selected
)
2047 /* We've lost all the selected items */
2048 gtk_signal_emit(GTK_OBJECT(collection
),
2049 collection_signals
[LOSE_SELECTION
],
2050 current_event_time
);
2053 collection
->number_selected
= selected
;
2054 resize_arrays(collection
,
2055 MAX(collection
->number_of_items
, MINIMUM_ITEMS
));
2057 collection
->paint_level
= PAINT_CLEAR
;
2059 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection
)))
2061 set_vadjustment(collection
);
2062 gtk_widget_queue_draw(GTK_WIDGET(collection
));
2067 /* Move the cursor by the given row and column offsets.
2068 * Moving by (0,0) can be used to simply make the cursor appear.
2070 void collection_move_cursor(Collection
*collection
, int drow
, int dcol
)
2073 int first
, last
, total_rows
;
2075 g_return_if_fail(collection
!= NULL
);
2076 g_return_if_fail(IS_COLLECTION(collection
));
2078 get_visible_limits(collection
, &first
, &last
);
2080 item
= collection
->cursor_item
;
2083 item
= MIN(collection
->cursor_item_old
,
2084 collection
->number_of_items
- 1);
2094 row
= item
/ collection
->columns
;
2095 col
= item
% collection
->columns
+ dcol
;
2099 else if (row
> last
)
2102 row
= MAX(row
+ drow
, 0);
2105 total_rows
= (collection
->number_of_items
+ collection
->columns
- 1)
2106 / collection
->columns
;
2108 if (row
>= total_rows
- 1 && drow
> 0)
2110 row
= total_rows
- 1;
2111 item
= col
+ row
* collection
->columns
;
2112 if (item
>= collection
->number_of_items
- 1)
2114 collection_set_cursor_item(collection
,
2115 collection
->number_of_items
- 1);
2122 item
= col
+ row
* collection
->columns
;
2124 if (item
>= 0 && item
< collection
->number_of_items
)
2125 collection_set_cursor_item(collection
, item
);
2128 /* When autoscroll is on, a timer keeps track of the pointer position.
2129 * While it's near the top or bottom of the window, the window scrolls.
2131 * If the mouse buttons are released, or the pointer leaves the window,
2132 * auto_scroll is turned off.
2134 void collection_set_autoscroll(Collection
*collection
, gboolean auto_scroll
)
2136 g_return_if_fail(collection
!= NULL
);
2137 g_return_if_fail(IS_COLLECTION(collection
));
2141 if (collection
->auto_scroll
!= -1)
2142 return; /* Already on! */
2144 collection
->auto_scroll
= gtk_timeout_add(50,
2145 (GtkFunction
) as_timeout
,
2150 if (collection
->auto_scroll
== -1)
2151 return; /* Already off! */
2153 gtk_timeout_remove(collection
->auto_scroll
);
2154 collection
->auto_scroll
= -1;
2158 /* Start a lasso box drag */
2159 void collection_lasso_box(Collection
*collection
, int x
, int y
)
2161 collection
->drag_box_x
[0] = x
;
2162 collection
->drag_box_y
[0] = y
;
2163 collection
->drag_box_x
[1] = x
;
2164 collection
->drag_box_y
[1] = y
;
2166 collection_set_autoscroll(collection
, TRUE
);
2167 add_lasso_box(collection
);
2170 /* Remove the lasso box. Applies fn to each item inside the box.
2171 * fn may be GDK_INVERT, GDK_SET, GDK_NOOP or GDK_CLEAR.
2173 void collection_end_lasso(Collection
*collection
, GdkFunction fn
)
2175 if (fn
!= GDK_CLEAR
)
2177 GdkRectangle region
;
2179 find_lasso_area(collection
, ®ion
);
2181 collection_process_area(collection
, ®ion
, fn
,
2185 abort_lasso(collection
);
2189 /* Unblock the selection_changed signal, emitting the signal if the
2190 * block counter reaches zero and emit is TRUE.
2192 void collection_unblock_selection_changed(Collection
*collection
,
2196 g_return_if_fail(collection
!= NULL
);
2197 g_return_if_fail(IS_COLLECTION(collection
));
2198 g_return_if_fail(collection
->block_selection_changed
> 0);
2200 collection
->block_selection_changed
--;
2202 if (emit
&& !collection
->block_selection_changed
)
2203 gtk_signal_emit(GTK_OBJECT(collection
),
2204 collection_signals
[SELECTION_CHANGED
], time
);