1 /*
2 * ROX-Filer, filer for the ROX desktop project
3 * Copyright (C) 2006, Thomas Leonard and others (see changelog for details).
4 *
5 * The collection widget provides an area for displaying a collection of
6 * objects (such as files). It allows the user to choose a selection of
7 * them and provides signals to allow popping up menus, detecting
8 * double-clicks etc.
9 *
10 * This program is free software; you can redistribute it and/or modify it
11 * under the terms of the GNU General Public License as published by the Free
12 * Software Foundation; either version 2 of the License, or (at your option)
13 * any later version.
14 *
15 * This program is distributed in the hope that it will be useful, but WITHOUT
16 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
17 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
18 * more details.
19 *
20 * You should have received a copy of the GNU General Public License along with
21 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
22 * Place, Suite 330, Boston, MA 02111-1307 USA
23 */
24
25 #include "config.h"
26
27 #include <stdlib.h>
28
29 #include <gtk/gtk.h>
30 #include <gdk/gdkkeysyms.h>
31 #include "global.h"
32
33 #include "collection.h"
34
35 #define MIN_WIDTH 80
36 #define MIN_HEIGHT 60
37 #define MINIMUM_ITEMS 16
38
39 #define MAX_WINKS 5 /* Should be an odd number */
40
41 /* Macro to emit the "selection_changed" signal only if allowed */
42 #define EMIT_SELECTION_CHANGED(collection, time) \
43 if (!collection->block_selection_changed) \
44 g_signal_emit(collection, \
45 collection_signals[SELECTION_CHANGED], 0, time)
46
47 enum
48 {
49 PROP_0,
50 PROP_VADJUSTMENT
51 };
52
53 /* Signals:
54 *
55 * void gain_selection(collection, time, user_data)
56 * We've gone from no selected items to having a selection.
57 * Time is the time of the event that caused the change, or
58 * GDK_CURRENT_TIME if not known.
59 *
60 * void lose_selection(collection, time, user_data)
61 * We've dropped to having no selected items.
62 * Time is the time of the event that caused the change, or
63 * GDK_CURRENT_TIME if not known.
64 *
65 * void selection_changed(collection, user_data)
66 * The set of selected items has changed.
67 * Time is the time of the event that caused the change, or
68 * GDK_CURRENT_TIME if not known.
69 */
70 enum
71 {
72 GAIN_SELECTION,
73 LOSE_SELECTION,
74 SELECTION_CHANGED,
75 LAST_SIGNAL
76 };
77
78 static guint collection_signals[LAST_SIGNAL] = { 0 };
79
80 static guint32 current_event_time = GDK_CURRENT_TIME;
81
82 static GtkWidgetClass *parent_class = NULL;
83
84 /* Static prototypes */
85 static void draw_one_item(Collection *collection,
86 int item,
87 GdkRectangle *area);
88 static void collection_class_init(GObjectClass *gclass, gpointer data);
89 static void collection_init(GTypeInstance *object, gpointer g_class);
90 static void collection_destroy(GtkObject *object);
91 static void collection_finalize(GObject *object);
92 static void collection_realize(GtkWidget *widget);
93 static void collection_map(GtkWidget *widget);
94 static void collection_size_request(GtkWidget *widget,
95 GtkRequisition *requisition);
96 static void collection_size_allocate(GtkWidget *widget,
97 GtkAllocation *allocation);
98 static void collection_set_adjustment(Collection *collection,
99 GtkAdjustment *vadj);
100 static void collection_get_property(GObject *object,
101 guint prop_id,
102 GValue *value,
103 GParamSpec *pspec);
104 static void collection_set_property(GObject *object,
105 guint prop_id,
106 const GValue *value,
107 GParamSpec *pspec);
108 static gint collection_expose(GtkWidget *widget, GdkEventExpose *event);
109 static void default_draw_item(GtkWidget *widget,
110 CollectionItem *data,
111 GdkRectangle *area,
112 gpointer user_data);
113 static gboolean default_test_point(Collection *collection,
114 int point_x, int point_y,
115 CollectionItem *data,
116 int width, int height,
117 gpointer user_data);
118 static gint collection_motion_notify(GtkWidget *widget,
119 GdkEventMotion *event);
120 static void add_lasso_box(Collection *collection);
121 static void abort_lasso(Collection *collection);
122 static void remove_lasso_box(Collection *collection);
123 static void draw_lasso_box(Collection *collection);
124 static void cancel_wink(Collection *collection);
125 static gint collection_key_press(GtkWidget *widget, GdkEventKey *event);
126 static void get_visible_limits(Collection *collection, int *first, int *last);
127 static void scroll_to_show(Collection *collection, int item);
128 static void collection_item_set_selected(Collection *collection,
129 gint item,
130 gboolean selected,
131 gboolean signal);
132 static gint collection_scroll_event(GtkWidget *widget, GdkEventScroll *event);
133 static int collection_get_rows(const Collection *collection);
134 static int collection_get_cols(const Collection *collection);
135
136
137 /* The number of rows, at least 1. */
collection_get_rows(const Collection * collection)138 static inline int collection_get_rows(const Collection *collection)
139 {
140 int rows = (collection->number_of_items + collection->columns - 1) /
141 collection->columns;
142 return MAX(rows, 1);
143 }
144
145 /* The number of columns _actually_ displayed, at least 1. This
146 * function is required in vertical_order layout-based manipulation
147 * such as moving the cursor to detect the last column. */
collection_get_cols(const Collection * collection)148 static inline int collection_get_cols(const Collection *collection)
149 {
150 if (collection->vertical_order)
151 {
152 int rows = collection_get_rows(collection);
153 int cols = (collection->number_of_items + rows - 1) / rows;
154 return MAX(1, cols);
155 }
156 else
157 return collection->columns;
158 }
159
draw_focus_at(Collection * collection,GdkRectangle * area)160 static void draw_focus_at(Collection *collection, GdkRectangle *area)
161 {
162 GtkWidget *widget;
163 GtkStateType state;
164
165 widget = GTK_WIDGET(collection);
166
167 if (GTK_WIDGET_FLAGS(widget) & GTK_HAS_FOCUS)
168 state = GTK_STATE_ACTIVE;
169 else
170 state = GTK_STATE_INSENSITIVE;
171
172 gtk_paint_focus(widget->style,
173 widget->window,
174 state,
175 NULL,
176 widget,
177 "collection",
178 area->x, area->y,
179 collection->item_width,
180 area->height);
181 }
182
draw_one_item(Collection * collection,int item,GdkRectangle * area)183 static void draw_one_item(Collection *collection, int item, GdkRectangle *area)
184 {
185 if (item < collection->number_of_items)
186 {
187 collection->draw_item((GtkWidget *) collection,
188 &collection->items[item],
189 area, collection->cb_user_data);
190 }
191
192 if (item == collection->cursor_item)
193 draw_focus_at(collection, area);
194 }
195
collection_get_type(void)196 GType collection_get_type(void)
197 {
198 static GType my_type = 0;
199
200 if (!my_type)
201 {
202 static const GTypeInfo info =
203 {
204 sizeof(CollectionClass),
205 NULL, /* base_init */
206 NULL, /* base_finalise */
207 (GClassInitFunc) collection_class_init,
208 NULL, /* class_finalise */
209 NULL, /* class_data */
210 sizeof(Collection),
211 0, /* n_preallocs */
212 collection_init
213 };
214
215 my_type = g_type_register_static(gtk_widget_get_type(),
216 "Collection", &info, 0);
217 }
218
219 return my_type;
220 }
221
222 typedef void (*FinalizeFn)(GObject *object);
223
collection_class_init(GObjectClass * gclass,gpointer data)224 static void collection_class_init(GObjectClass *gclass, gpointer data)
225 {
226 CollectionClass *collection_class = (CollectionClass *) gclass;
227 GtkObjectClass *object_class = (GtkObjectClass *) gclass;
228 GtkWidgetClass *widget_class = (GtkWidgetClass *) gclass;
229
230 parent_class = gtk_type_class(gtk_widget_get_type());
231
232 object_class->destroy = collection_destroy;
233 G_OBJECT_CLASS(object_class)->finalize =
234 (FinalizeFn) collection_finalize;
235
236 widget_class->realize = collection_realize;
237 widget_class->expose_event = collection_expose;
238 widget_class->size_request = collection_size_request;
239 widget_class->size_allocate = collection_size_allocate;
240
241 widget_class->key_press_event = collection_key_press;
242
243 widget_class->motion_notify_event = collection_motion_notify;
244 widget_class->map = collection_map;
245 widget_class->scroll_event = collection_scroll_event;
246
247 gclass->set_property = collection_set_property;
248 gclass->get_property = collection_get_property;
249
250 collection_class->gain_selection = NULL;
251 collection_class->lose_selection = NULL;
252 collection_class->selection_changed = NULL;
253
254 collection_signals[GAIN_SELECTION] = g_signal_new("gain_selection",
255 G_TYPE_FROM_CLASS(gclass),
256 G_SIGNAL_RUN_LAST,
257 G_STRUCT_OFFSET(CollectionClass,
258 gain_selection),
259 NULL, NULL,
260 g_cclosure_marshal_VOID__INT,
261 G_TYPE_NONE, 1,
262 G_TYPE_INT);
263
264 collection_signals[LOSE_SELECTION] = g_signal_new("lose_selection",
265 G_TYPE_FROM_CLASS(gclass),
266 G_SIGNAL_RUN_LAST,
267 G_STRUCT_OFFSET(CollectionClass,
268 lose_selection),
269 NULL, NULL,
270 g_cclosure_marshal_VOID__INT,
271 G_TYPE_NONE, 1,
272 G_TYPE_INT);
273
274 collection_signals[SELECTION_CHANGED] = g_signal_new(
275 "selection_changed",
276 G_TYPE_FROM_CLASS(gclass),
277 G_SIGNAL_RUN_LAST,
278 G_STRUCT_OFFSET(CollectionClass,
279 selection_changed),
280 NULL, NULL,
281 g_cclosure_marshal_VOID__INT,
282 G_TYPE_NONE, 1,
283 G_TYPE_INT);
284
285 g_object_class_install_property(gclass,
286 PROP_VADJUSTMENT,
287 g_param_spec_object("vadjustment",
288 "Vertical Adjustment",
289 "The GtkAdjustment for the vertical position.",
290 GTK_TYPE_ADJUSTMENT,
291 G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
292 }
293
collection_init(GTypeInstance * instance,gpointer g_class)294 static void collection_init(GTypeInstance *instance, gpointer g_class)
295 {
296 Collection *object = (Collection *) instance;
297
298 g_return_if_fail(object != NULL);
299 g_return_if_fail(IS_COLLECTION(object));
300
301 GTK_WIDGET_SET_FLAGS(GTK_WIDGET(object), GTK_CAN_FOCUS);
302
303 object->number_of_items = 0;
304 object->number_selected = 0;
305 object->block_selection_changed = 0;
306 object->columns = 1;
307 object->vertical_order = FALSE;
308 object->item_width = 64;
309 object->item_height = 64;
310 object->vadj = NULL;
311
312 object->items = g_new(CollectionItem, MINIMUM_ITEMS);
313 object->cursor_item = -1;
314 object->cursor_item_old = -1;
315 object->wink_item = -1;
316 object->wink_on_map = -1;
317 object->array_size = MINIMUM_ITEMS;
318 object->draw_item = default_draw_item;
319 object->test_point = default_test_point;
320 object->free_item = NULL;
321 }
322
collection_new(void)323 GtkWidget* collection_new(void)
324 {
325 return GTK_WIDGET(gtk_widget_new(collection_get_type(), NULL));
326 }
327
328 /* After this we are unusable, but our data (if any) is still hanging around.
329 * It will be freed later with finalize.
330 */
collection_destroy(GtkObject * object)331 static void collection_destroy(GtkObject *object)
332 {
333 Collection *collection;
334
335 g_return_if_fail(object != NULL);
336 g_return_if_fail(IS_COLLECTION(object));
337
338 collection = COLLECTION(object);
339
340 collection_clear(collection);
341
342 if (collection->vadj)
343 {
344 g_object_unref(G_OBJECT(collection->vadj));
345 collection->vadj = NULL;
346 }
347
348 if (GTK_OBJECT_CLASS(parent_class)->destroy)
349 (*GTK_OBJECT_CLASS(parent_class)->destroy)(object);
350 }
351
352 /* This is the last thing that happens to us. Free all data. */
collection_finalize(GObject * object)353 static void collection_finalize(GObject *object)
354 {
355 Collection *collection;
356
357 collection = COLLECTION(object);
358
359 g_return_if_fail(collection->number_of_items == 0);
360
361 g_free(collection->items);
362
363 if (G_OBJECT_CLASS(parent_class)->finalize)
364 G_OBJECT_CLASS(parent_class)->finalize(object);
365 }
366
collection_map(GtkWidget * widget)367 static void collection_map(GtkWidget *widget)
368 {
369 Collection *collection = COLLECTION(widget);
370
371 if (GTK_WIDGET_CLASS(parent_class)->map)
372 (*GTK_WIDGET_CLASS(parent_class)->map)(widget);
373
374 if (collection->wink_on_map >= 0)
375 {
376 collection_wink_item(collection, collection->wink_on_map);
377 collection->wink_on_map = -1;
378 }
379 }
380
collection_realize(GtkWidget * widget)381 static void collection_realize(GtkWidget *widget)
382 {
383 Collection *collection;
384 GdkWindowAttr attributes;
385 gint attributes_mask;
386 GdkGCValues xor_values;
387 GdkColor *bg, *fg;
388
389 g_return_if_fail(widget != NULL);
390 g_return_if_fail(IS_COLLECTION(widget));
391 g_return_if_fail(widget->parent != NULL);
392
393 GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);
394 collection = COLLECTION(widget);
395
396 attributes.x = widget->allocation.x;
397 attributes.y = widget->allocation.y;
398 attributes.width = widget->allocation.width;
399 attributes.height = widget->allocation.height;
400 attributes.wclass = GDK_INPUT_OUTPUT;
401 attributes.window_type = GDK_WINDOW_CHILD;
402 attributes.event_mask = gtk_widget_get_events(widget) |
403 GDK_EXPOSURE_MASK |
404 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
405 GDK_BUTTON1_MOTION_MASK | GDK_BUTTON2_MOTION_MASK |
406 GDK_BUTTON3_MOTION_MASK;
407 attributes.visual = gtk_widget_get_visual(widget);
408 attributes.colormap = gtk_widget_get_colormap(widget);
409
410 attributes_mask = GDK_WA_X | GDK_WA_Y |
411 GDK_WA_VISUAL | GDK_WA_COLORMAP;
412 widget->window = gdk_window_new(gtk_widget_get_parent_window(widget),
413 &attributes, attributes_mask);
414
415 widget->style = gtk_style_attach(widget->style, widget->window);
416
417 gdk_window_set_user_data(widget->window, widget);
418 gdk_window_set_background(widget->window,
419 &widget->style->base[GTK_STATE_NORMAL]);
420
421 bg = &widget->style->base[GTK_STATE_NORMAL];
422 fg = &widget->style->text[GTK_STATE_NORMAL];
423 xor_values.function = GDK_XOR;
424 xor_values.foreground.pixel = fg->pixel ^ bg->pixel;
425 collection->xor_gc = gdk_gc_new_with_values(widget->window,
426 &xor_values,
427 GDK_GC_FOREGROUND
428 | GDK_GC_FUNCTION);
429 }
430
collection_size_request(GtkWidget * widget,GtkRequisition * requisition)431 static void collection_size_request(GtkWidget *widget,
432 GtkRequisition *requisition)
433 {
434 Collection *collection = COLLECTION(widget);
435 int rows;
436
437 /* We ask for the total size we need; our containing viewport
438 * will deal with scrolling.
439 */
440 requisition->width = MIN_WIDTH;
441 rows = collection_get_rows(collection);
442 requisition->height = rows * collection->item_height;
443 }
444
scroll_after_alloc(Collection * collection)445 static gboolean scroll_after_alloc(Collection *collection)
446 {
447 if (collection->wink_item != -1)
448 scroll_to_show(collection, collection->wink_item);
449 else if (collection->cursor_item != -1)
450 scroll_to_show(collection, collection->cursor_item);
451 g_object_unref(G_OBJECT(collection));
452
453 return FALSE;
454 }
455
collection_size_allocate(GtkWidget * widget,GtkAllocation * allocation)456 static void collection_size_allocate(GtkWidget *widget,
457 GtkAllocation *allocation)
458 {
459 Collection *collection;
460 int old_columns;
461 gboolean cursor_visible = FALSE;
462
463 g_return_if_fail(widget != NULL);
464 g_return_if_fail(IS_COLLECTION(widget));
465 g_return_if_fail(allocation != NULL);
466
467 collection = COLLECTION(widget);
468
469 if (collection->cursor_item != -1)
470 {
471 int first, last;
472 int crow, ccol;
473
474 collection_item_to_rowcol(collection, collection->cursor_item,
475 &crow, &ccol);
476
477 get_visible_limits(collection, &first, &last);
478
479 cursor_visible = crow >= first && crow <= last;
480 }
481
482 old_columns = collection->columns;
483
484 widget->allocation = *allocation;
485
486 collection->columns = allocation->width / collection->item_width;
487 if (collection->columns < 1)
488 collection->columns = 1;
489
490 if (GTK_WIDGET_REALIZED(widget))
491 {
492 gdk_window_move_resize(widget->window,
493 allocation->x, allocation->y,
494 allocation->width, allocation->height);
495
496 if (cursor_visible)
497 scroll_to_show(collection, collection->cursor_item);
498 }
499
500 if (old_columns != collection->columns)
501 {
502 /* Need to go around again... */
503 gtk_widget_queue_resize(widget);
504 }
505 else if (collection->wink_item != -1 || collection->cursor_item != -1)
506 {
507 /* Viewport resets the adjustments after the alloc */
508 g_object_ref(G_OBJECT(collection));
509 g_idle_add((GSourceFunc) scroll_after_alloc, collection);
510 }
511 }
512
513 /* Return the area occupied by the item at (row, col) by filling
514 * in 'area'.
515 */
collection_get_item_area(Collection * collection,int row,int col,GdkRectangle * area)516 static void collection_get_item_area(Collection *collection,
517 int row, int col,
518 GdkRectangle *area)
519
520 {
521 area->x = col * collection->item_width;
522 area->y = row * collection->item_height;
523
524 area->width = collection->item_width;
525 area->height = collection->item_height;
526 if (col == collection->columns - 1)
527 area->width <<= 1;
528 }
529
collection_expose(GtkWidget * widget,GdkEventExpose * event)530 static gint collection_expose(GtkWidget *widget, GdkEventExpose *event)
531 {
532 Collection *collection;
533 GdkRectangle item_area;
534 int row, col;
535 int item;
536 int start_row, last_row;
537 int start_col, last_col;
538 int phys_last_col;
539
540 g_return_val_if_fail(widget != NULL, FALSE);
541 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
542 g_return_val_if_fail(event != NULL, FALSE);
543
544 /* Note about 'detail' argument:
545 * - If set to "base", lighthouse theme will crash
546 * - If set to NULL, cleanice theme will crash
547 *
548 * Clear the background only if we have a background pixmap.
549 */
550 if (widget->style->bg_pixmap[GTK_STATE_NORMAL])
551 gtk_paint_flat_box(widget->style, widget->window, GTK_STATE_NORMAL,
552 GTK_SHADOW_NONE, &event->area,
553 widget, "collection", 0, 0, -1, -1);
554
555 collection = COLLECTION(widget);
556
557 /* Calculate the ranges to plot */
558 start_row = event->area.y / collection->item_height;
559 last_row = (event->area.y + event->area.height - 1)
560 / collection->item_height;
561
562 if (last_row >= collection_get_rows(collection))
563 last_row = collection_get_rows(collection) - 1;
564
565 start_col = event->area.x / collection->item_width;
566 phys_last_col = (event->area.x + event->area.width - 1)
567 / collection->item_width;
568
569 /* The right-most column may be wider than the others.
570 * Therefore, to redraw the area after the last 'real' column
571 * we may have to draw the right-most column.
572 */
573 if (start_col >= collection->columns)
574 start_col = collection->columns - 1;
575
576 if (phys_last_col >= collection->columns)
577 last_col = collection->columns - 1;
578 else
579 last_col = phys_last_col;
580
581
582 for(row = start_row; row <= last_row; row++)
583 for(col = start_col; col <= last_col; col++)
584 {
585 item = collection_rowcol_to_item(collection, row, col);
586 if (item == 0 || item < collection->number_of_items) {
587 collection_get_item_area(collection,
588 row, col, &item_area);
589 draw_one_item(collection, item, &item_area);
590 }
591 }
592
593 if (collection->lasso_box)
594 draw_lasso_box(collection);
595
596 return FALSE;
597 }
598
default_draw_item(GtkWidget * widget,CollectionItem * item,GdkRectangle * area,gpointer user_data)599 static void default_draw_item(GtkWidget *widget,
600 CollectionItem *item,
601 GdkRectangle *area,
602 gpointer user_data)
603 {
604 gdk_draw_arc(widget->window,
605 item->selected ? widget->style->white_gc
606 : widget->style->black_gc,
607 TRUE,
608 area->x, area->y,
609 COLLECTION(widget)->item_width, area->height,
610 0, 360 * 64);
611 }
612
613
default_test_point(Collection * collection,int point_x,int point_y,CollectionItem * item,int width,int height,gpointer user_data)614 static gboolean default_test_point(Collection *collection,
615 int point_x, int point_y,
616 CollectionItem *item,
617 int width, int height,
618 gpointer user_data)
619 {
620 float f_x, f_y;
621
622 /* Convert to point in unit circle */
623 f_x = ((float) point_x / width) - 0.5;
624 f_y = ((float) point_y / height) - 0.5;
625
626 return (f_x * f_x) + (f_y * f_y) <= .25;
627 }
628
collection_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)629 static void collection_set_property(GObject *object,
630 guint prop_id,
631 const GValue *value,
632 GParamSpec *pspec)
633 {
634 Collection *collection;
635
636 collection = COLLECTION(object);
637
638 switch (prop_id)
639 {
640 case PROP_VADJUSTMENT:
641 collection_set_adjustment(collection,
642 g_value_get_object(value));
643 break;
644 default:
645 G_OBJECT_WARN_INVALID_PROPERTY_ID(object,
646 prop_id, pspec);
647 break;
648 }
649 }
650
collection_set_adjustment(Collection * collection,GtkAdjustment * vadj)651 static void collection_set_adjustment(Collection *collection,
652 GtkAdjustment *vadj)
653 {
654 if (vadj)
655 g_return_if_fail(GTK_IS_ADJUSTMENT(vadj));
656 else
657 vadj = GTK_ADJUSTMENT(gtk_adjustment_new(0.0,
658 0.0, 0.0,
659 0.0, 0.0, 0.0));
660
661 if (collection->vadj == vadj)
662 return;
663
664 if (collection->vadj)
665 g_object_unref(G_OBJECT(collection->vadj));
666
667 collection->vadj = vadj;
668 g_object_ref(G_OBJECT(collection->vadj));
669 gtk_object_sink(GTK_OBJECT(collection->vadj));
670 }
671
collection_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)672 static void collection_get_property(GObject *object,
673 guint prop_id,
674 GValue *value,
675 GParamSpec *pspec)
676 {
677 Collection *collection;
678
679 collection = COLLECTION(object);
680
681 switch (prop_id)
682 {
683 case PROP_VADJUSTMENT:
684 g_value_set_object(value, G_OBJECT(collection->vadj));
685 break;
686 default:
687 G_OBJECT_WARN_INVALID_PROPERTY_ID(object,
688 prop_id, pspec);
689 break;
690 }
691 }
692
resize_arrays(Collection * collection,guint new_size)693 static void resize_arrays(Collection *collection, guint new_size)
694 {
695 g_return_if_fail(collection != NULL);
696 g_return_if_fail(IS_COLLECTION(collection));
697 g_return_if_fail(new_size >= collection->number_of_items);
698
699 collection->items = g_realloc(collection->items,
700 sizeof(CollectionItem) * new_size);
701 collection->array_size = new_size;
702 }
703
collection_key_press(GtkWidget * widget,GdkEventKey * event)704 static gint collection_key_press(GtkWidget *widget, GdkEventKey *event)
705 {
706 Collection *collection;
707 int item;
708 int key;
709
710 g_return_val_if_fail(widget != NULL, FALSE);
711 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
712 g_return_val_if_fail(event != NULL, FALSE);
713
714 collection = (Collection *) widget;
715 item = collection->cursor_item;
716
717 key = event->keyval;
718 if (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK))
719 {
720 if (key == GDK_Left || key == GDK_Right || \
721 key == GDK_Up || key == GDK_Down)
722 return TRUE;
723 return FALSE;
724 }
725
726 switch (key)
727 {
728 case GDK_Left:
729 collection_move_cursor(collection, 0, -1);
730 break;
731 case GDK_Right:
732 collection_move_cursor(collection, 0, 1);
733 break;
734 case GDK_Up:
735 collection_move_cursor(collection, -1, 0);
736 break;
737 case GDK_Down:
738 collection_move_cursor(collection, 1, 0);
739 break;
740 case GDK_Home:
741 collection_set_cursor_item(collection, 0, TRUE);
742 break;
743 case GDK_End:
744 collection_set_cursor_item(collection,
745 MAX((gint) collection->number_of_items - 1, 0),
746 TRUE);
747 break;
748 case GDK_Page_Up:
749 {
750 int first, last;
751 get_visible_limits(collection, &first, &last);
752 collection_move_cursor(collection, first - last - 1, 0);
753 break;
754 }
755 case GDK_Page_Down:
756 {
757 int first, last;
758 get_visible_limits(collection, &first, &last);
759 collection_move_cursor(collection, last - first + 1, 0);
760 break;
761 }
762 default:
763 return FALSE;
764 }
765
766 return TRUE;
767 }
768
769 /* Wheel mouse scrolling */
collection_scroll_event(GtkWidget * widget,GdkEventScroll * event)770 static gint collection_scroll_event(GtkWidget *widget, GdkEventScroll *event)
771 {
772 Collection *collection;
773 int diff = 0;
774
775 g_return_val_if_fail(widget != NULL, FALSE);
776 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
777 g_return_val_if_fail(event != NULL, FALSE);
778
779 collection = COLLECTION(widget);
780
781 if (event->direction == GDK_SCROLL_UP)
782 diff = -1;
783 else if (event->direction == GDK_SCROLL_DOWN)
784 diff = 1;
785 else
786 return FALSE;
787
788 if (diff)
789 {
790 int old_value = collection->vadj->value;
791 int new_value = 0;
792 gboolean box = collection->lasso_box;
793 int step = collection->vadj->page_increment / 2;
794
795 new_value = CLAMP(old_value + diff * step, 0.0,
796 collection->vadj->upper
797 - collection->vadj->page_size);
798 diff = new_value - old_value;
799 if (diff)
800 {
801 if (box)
802 {
803 remove_lasso_box(collection);
804 collection->drag_box_y[0] -= diff;
805 }
806 gtk_adjustment_set_value(collection->vadj, new_value);
807 if (box)
808 add_lasso_box(collection);
809 }
810 }
811
812 return TRUE;
813 }
814
815 /* 'from' and 'to' are pixel positions. 'step' is the size of each item.
816 * Returns the index of the first item covered, and the number of items.
817 */
get_range(int from,int to,int step,gint * pos,gint * len)818 static void get_range(int from, int to, int step, gint *pos, gint *len)
819 {
820 int margin = MIN(step / 4, 40);
821
822 if (from > to)
823 {
824 int tmp = to;
825 to = from;
826 from = tmp;
827 }
828
829 from = (from + margin) / step; /* First item */
830 to = (to + step - margin) / step; /* Last item (inclusive) */
831
832 *pos = MAX(from, 0);
833 *len = to - *pos;
834 }
835
836 /* Fills in the area with a rectangle corresponding to the current
837 * size of the lasso box (units of items, not pixels).
838 *
839 * The box will only span valid columns, but the total number
840 * of items is not taken into account (rows or cols).
841 */
find_lasso_area(Collection * collection,GdkRectangle * area)842 static void find_lasso_area(Collection *collection, GdkRectangle *area)
843 {
844 int cols = collection->columns;
845 int dx = collection->drag_box_x[0] - collection->drag_box_x[1];
846 int dy = collection->drag_box_y[0] - collection->drag_box_y[1];
847
848 if (ABS(dx) < 8 && ABS(dy) < 8)
849 {
850 /* Didn't move far enough - ignore */
851 area->x = area->y = 0;
852 area->width = 0;
853 area->height = 0;
854 return;
855 }
856
857 get_range(collection->drag_box_x[0], collection->drag_box_x[1],
858 collection->item_width, &area->x, &area->width);
859
860 if (area->x >= cols)
861 area->width = 0;
862 else if (area->x + area->width > cols)
863 area->width = cols - area->x;
864
865 get_range(collection->drag_box_y[0], collection->drag_box_y[1],
866 collection->item_height, &area->y, &area->height);
867 }
868
collection_process_area(Collection * collection,GdkRectangle * area,GdkFunction fn,guint32 time)869 static void collection_process_area(Collection *collection,
870 GdkRectangle *area,
871 GdkFunction fn,
872 guint32 time)
873 {
874 int x, y;
875 int rows = collection_get_rows(collection);
876 int cols = collection->columns;
877 guint32 stacked_time;
878 int item;
879 gboolean changed = FALSE;
880 guint old_selected;
881
882 g_return_if_fail(fn == GDK_SET || fn == GDK_INVERT);
883
884 old_selected = collection->number_selected;
885
886 stacked_time = current_event_time;
887 current_event_time = time;
888
889 collection->block_selection_changed++;
890
891 for (y = area->y; y < area->y + area->height && y < rows; y++)
892 for (x = area->x; x < area->x + area->width && x < cols; x++)
893 {
894 item = collection_rowcol_to_item(collection, y, x);
895 if (item < collection->number_of_items) {
896 if (fn == GDK_INVERT)
897 collection_item_set_selected(
898 collection, item,
899 !collection-> items[item].selected,
900 FALSE);
901 else
902 collection_item_set_selected(
903 collection, item, TRUE, FALSE);
904
905 changed = TRUE;
906 }
907 }
908
909 if (collection->number_selected && !old_selected)
910 g_signal_emit(collection,
911 collection_signals[GAIN_SELECTION], 0,
912 current_event_time);
913 else if (!collection->number_selected && old_selected)
914 g_signal_emit(collection,
915 collection_signals[LOSE_SELECTION], 0,
916 current_event_time);
917
918 collection_unblock_selection_changed(collection,
919 current_event_time, changed);
920 current_event_time = stacked_time;
921 }
922
collection_motion_notify(GtkWidget * widget,GdkEventMotion * event)923 static gint collection_motion_notify(GtkWidget *widget,
924 GdkEventMotion *event)
925 {
926 Collection *collection;
927 gint x, y;
928
929 g_return_val_if_fail(widget != NULL, FALSE);
930 g_return_val_if_fail(IS_COLLECTION(widget), FALSE);
931 g_return_val_if_fail(event != NULL, FALSE);
932
933 collection = COLLECTION(widget);
934
935 if (!collection->lasso_box)
936 return FALSE;
937
938 if (event->window != widget->window)
939 gdk_window_get_pointer(widget->window, &x, &y, NULL);
940 else
941 {
942 x = event->x;
943 y = event->y;
944 }
945
946 remove_lasso_box(collection);
947 collection->drag_box_x[1] = x;
948 collection->drag_box_y[1] = y;
949 add_lasso_box(collection);
950 return TRUE;
951 }
952
add_lasso_box(Collection * collection)953 static void add_lasso_box(Collection *collection)
954 {
955 g_return_if_fail(collection != NULL);
956 g_return_if_fail(IS_COLLECTION(collection));
957 g_return_if_fail(collection->lasso_box == FALSE);
958
959 collection->lasso_box = TRUE;
960 draw_lasso_box(collection);
961 }
962
draw_lasso_box(Collection * collection)963 static void draw_lasso_box(Collection *collection)
964 {
965 GtkWidget *widget;
966 int x, y, width, height;
967
968 widget = GTK_WIDGET(collection);
969
970 x = MIN(collection->drag_box_x[0], collection->drag_box_x[1]);
971 y = MIN(collection->drag_box_y[0], collection->drag_box_y[1]);
972 width = abs(collection->drag_box_x[1] - collection->drag_box_x[0]);
973 height = abs(collection->drag_box_y[1] - collection->drag_box_y[0]);
974
975 /* XXX: A redraw bug sometimes leaves a one-pixel dot on the screen.
976 * As a quick hack, don't draw boxes that small for now...
977 */
978 if (width || height)
979 gdk_draw_rectangle(widget->window, collection->xor_gc, FALSE,
980 x, y, width, height);
981 }
982
abort_lasso(Collection * collection)983 static void abort_lasso(Collection *collection)
984 {
985 if (collection->lasso_box)
986 remove_lasso_box(collection);
987 }
988
remove_lasso_box(Collection * collection)989 static void remove_lasso_box(Collection *collection)
990 {
991 g_return_if_fail(collection != NULL);
992 g_return_if_fail(IS_COLLECTION(collection));
993 g_return_if_fail(collection->lasso_box == TRUE);
994
995 draw_lasso_box(collection);
996
997 collection->lasso_box = FALSE;
998
999 return;
1000 }
1001
1002 /* Make sure that 'item' is fully visible (vertically), scrolling if not. */
scroll_to_show(Collection * collection,int item)1003 static void scroll_to_show(Collection *collection, int item)
1004 {
1005 int first, last, row, col;
1006
1007 g_return_if_fail(collection != NULL);
1008 g_return_if_fail(IS_COLLECTION(collection));
1009
1010 collection_item_to_rowcol(collection, item, &row, &col);
1011 get_visible_limits(collection, &first, &last);
1012
1013 if (row <= first)
1014 {
1015 gtk_adjustment_set_value(collection->vadj,
1016 row * collection->item_height);
1017 }
1018 else if (row >= last)
1019 {
1020 GtkWidget *widget = (GtkWidget *) collection;
1021 gint height;
1022
1023 if (GTK_WIDGET_REALIZED(widget))
1024 {
1025 height = collection->vadj->page_size;
1026 gtk_adjustment_set_value(collection->vadj,
1027 (row + 1) * collection->item_height - height);
1028 }
1029 }
1030 }
1031
1032 /* Return the first and last rows which are [partly] visible. Does not
1033 * ensure that the rows actually exist (contain items).
1034 */
get_visible_limits(Collection * collection,int * first,int * last)1035 static void get_visible_limits(Collection *collection, int *first, int *last)
1036 {
1037 GtkWidget *widget = (GtkWidget *) collection;
1038 gint scroll = 0, height;
1039
1040 g_return_if_fail(collection != NULL);
1041 g_return_if_fail(IS_COLLECTION(collection));
1042 g_return_if_fail(first != NULL && last != NULL);
1043
1044 if (!GTK_WIDGET_REALIZED(widget))
1045 {
1046 *first = 0;
1047 *last = 0;
1048 }
1049 else
1050 {
1051 scroll = collection->vadj->value;
1052 height = collection->vadj->page_size;
1053
1054 *first = MAX(scroll / collection->item_height, 0);
1055 *last = (scroll + height - 1) /collection->item_height;
1056
1057 if (*last < *first)
1058 *last = *first;
1059 }
1060 }
1061
1062 /* Cancel the current wink effect. */
cancel_wink(Collection * collection)1063 static void cancel_wink(Collection *collection)
1064 {
1065 gint item;
1066
1067 g_return_if_fail(collection != NULL);
1068 g_return_if_fail(IS_COLLECTION(collection));
1069 g_return_if_fail(collection->wink_item != -1);
1070
1071 item = collection->wink_item;
1072
1073 collection->wink_item = -1;
1074 g_source_remove(collection->wink_timeout);
1075
1076 collection_draw_item(collection, item, TRUE);
1077 }
1078
1079 /* Draw/undraw a box around collection->wink_item */
invert_wink(Collection * collection)1080 static void invert_wink(Collection *collection)
1081 {
1082 GdkRectangle area;
1083 gint row, col;
1084
1085 g_return_if_fail(collection->wink_item >= 0);
1086
1087 if (!GTK_WIDGET_REALIZED(GTK_WIDGET(collection)))
1088 return;
1089
1090 collection_item_to_rowcol(collection, collection->wink_item,
1091 &row, &col);
1092 collection_get_item_area(collection, row, col, &area);
1093
1094 gdk_draw_rectangle(((GtkWidget *) collection)->window,
1095 collection->xor_gc, FALSE,
1096 area.x, area.y,
1097 collection->item_width - 1,
1098 area.height - 1);
1099 }
1100
wink_timeout(Collection * collection)1101 static gboolean wink_timeout(Collection *collection)
1102 {
1103 gint item;
1104
1105 g_return_val_if_fail(collection != NULL, FALSE);
1106 g_return_val_if_fail(IS_COLLECTION(collection), FALSE);
1107 g_return_val_if_fail(collection->wink_item != -1, FALSE);
1108
1109 item = collection->wink_item;
1110
1111 if (collection->winks_left-- > 0)
1112 {
1113 invert_wink(collection);
1114 return TRUE;
1115 }
1116
1117 collection->wink_item = -1;
1118
1119 collection_draw_item(collection, item, TRUE);
1120
1121 return FALSE;
1122 }
1123
1124 /* Change the selected state of an item.
1125 * Send GAIN/LOSE signals if 'signal' is TRUE.
1126 * Send SELECTION_CHANGED unless blocked.
1127 * Updates number_selected and redraws the item.
1128 */
collection_item_set_selected(Collection * collection,gint item,gboolean selected,gboolean signal)1129 static void collection_item_set_selected(Collection *collection,
1130 gint item,
1131 gboolean selected,
1132 gboolean signal)
1133 {
1134 g_return_if_fail(collection != NULL);
1135 g_return_if_fail(IS_COLLECTION(collection));
1136 g_return_if_fail(item >= 0 && item < collection->number_of_items);
1137
1138 if (collection->items[item].selected == selected)
1139 return;
1140
1141 collection->items[item].selected = selected;
1142 collection_draw_item(collection, item, TRUE);
1143
1144 if (selected)
1145 {
1146 collection->number_selected++;
1147 if (signal && collection->number_selected == 1)
1148 g_signal_emit(collection,
1149 collection_signals[GAIN_SELECTION], 0,
1150 current_event_time);
1151 }
1152 else
1153 {
1154 collection->number_selected--;
1155 if (signal && collection->number_selected == 0)
1156 g_signal_emit(collection,
1157 collection_signals[LOSE_SELECTION], 0,
1158 current_event_time);
1159 }
1160
1161 EMIT_SELECTION_CHANGED(collection, current_event_time);
1162 }
1163
1164 /* Functions for managing collections */
1165
1166 /* Remove all objects from the collection */
collection_clear(Collection * collection)1167 void collection_clear(Collection *collection)
1168 {
1169 collection_delete_if(collection, NULL, NULL);
1170 }
1171
1172 /* Inserts a new item at the end. The new item is unselected, and its
1173 * number is returned.
1174 */
collection_insert(Collection * collection,gpointer data,gpointer view)1175 gint collection_insert(Collection *collection, gpointer data, gpointer view)
1176 {
1177 int item;
1178
1179 /* g_return_val_if_fail(IS_COLLECTION(collection), -1); (slow) */
1180
1181 item = collection->number_of_items;
1182
1183 if (item >= collection->array_size)
1184 resize_arrays(collection, item + (item >> 1));
1185
1186 collection->items[item].data = data;
1187 collection->items[item].view_data = view;
1188 collection->items[item].selected = FALSE;
1189
1190 collection->number_of_items++;
1191
1192 gtk_widget_queue_resize(GTK_WIDGET(collection));
1193
1194 collection_draw_item(collection, item, FALSE);
1195
1196 return item;
1197 }
1198
collection_unselect_item(Collection * collection,gint item)1199 void collection_unselect_item(Collection *collection, gint item)
1200 {
1201 collection_item_set_selected(collection, item, FALSE, TRUE);
1202 }
1203
collection_select_item(Collection * collection,gint item)1204 void collection_select_item(Collection *collection, gint item)
1205 {
1206 collection_item_set_selected(collection, item, TRUE, TRUE);
1207 }
1208
collection_toggle_item(Collection * collection,gint item)1209 void collection_toggle_item(Collection *collection, gint item)
1210 {
1211 collection_item_set_selected(collection, item,
1212 !collection->items[item].selected, TRUE);
1213 }
1214
1215 /* Select all items in the collection */
collection_select_all(Collection * collection)1216 void collection_select_all(Collection *collection)
1217 {
1218 GtkWidget *widget;
1219 int item = 0;
1220
1221 g_return_if_fail(collection != NULL);
1222 g_return_if_fail(IS_COLLECTION(collection));
1223
1224 widget = GTK_WIDGET(collection);
1225
1226 if (collection->number_selected == collection->number_of_items)
1227 return; /* Nothing to do */
1228
1229 while (collection->number_selected < collection->number_of_items)
1230 {
1231 while (collection->items[item].selected)
1232 item++;
1233
1234 collection->items[item].selected = TRUE;
1235 collection_draw_item(collection, item, TRUE);
1236 item++;
1237
1238 collection->number_selected++;
1239 }
1240
1241 g_signal_emit(collection, collection_signals[GAIN_SELECTION], 0,
1242 current_event_time);
1243 EMIT_SELECTION_CHANGED(collection, current_event_time);
1244 }
1245
1246 /* Toggle all items in the collection */
collection_invert_selection(Collection * collection)1247 void collection_invert_selection(Collection *collection)
1248 {
1249 int item;
1250
1251 g_return_if_fail(collection != NULL);
1252 g_return_if_fail(IS_COLLECTION(collection));
1253
1254 if (collection->number_selected == 0)
1255 {
1256 collection_select_all(collection);
1257 return;
1258 }
1259 else if (collection->number_of_items == collection->number_selected)
1260 {
1261 collection_clear_selection(collection);
1262 return;
1263 }
1264
1265 for (item = 0; item < collection->number_of_items; item++)
1266 collection->items[item].selected =
1267 !collection->items[item].selected;
1268
1269 collection->number_selected = collection->number_of_items -
1270 collection->number_selected;
1271
1272 /* Have to redraw everything... */
1273 gtk_widget_queue_draw(GTK_WIDGET(collection));
1274
1275 EMIT_SELECTION_CHANGED(collection, current_event_time);
1276 }
1277
1278 /* Unselect all items except number item, which is selected (-1 to unselect
1279 * everything).
1280 */
collection_clear_except(Collection * collection,gint item)1281 void collection_clear_except(Collection *collection, gint item)
1282 {
1283 GtkWidget *widget;
1284 int i = 0;
1285 int end; /* Selected items to end up with */
1286
1287 g_return_if_fail(collection != NULL);
1288 g_return_if_fail(IS_COLLECTION(collection));
1289 g_return_if_fail(item >= -1 && item < collection->number_of_items);
1290
1291 widget = GTK_WIDGET(collection);
1292
1293 if (item == -1)
1294 end = 0;
1295 else
1296 {
1297 collection_select_item(collection, item);
1298 end = 1;
1299 }
1300
1301 if (collection->number_selected == 0)
1302 return;
1303
1304 while (collection->number_selected > end)
1305 {
1306 while (i == item || !collection->items[i].selected)
1307 i++;
1308
1309 collection->items[i].selected = FALSE;
1310 collection_draw_item(collection, i, TRUE);
1311 i++;
1312
1313 collection->number_selected--;
1314 }
1315
1316 if (end == 0)
1317 g_signal_emit(collection, collection_signals[LOSE_SELECTION], 0,
1318 current_event_time);
1319 EMIT_SELECTION_CHANGED(collection, current_event_time);
1320 }
1321
1322 /* Unselect all items in the collection */
collection_clear_selection(Collection * collection)1323 void collection_clear_selection(Collection *collection)
1324 {
1325 g_return_if_fail(collection != NULL);
1326 g_return_if_fail(IS_COLLECTION(collection));
1327
1328 collection_clear_except(collection, -1);
1329 }
1330
1331 /* Force a redraw of the specified item, if it is visible */
collection_draw_item(Collection * collection,gint item,gboolean blank)1332 void collection_draw_item(Collection *collection, gint item, gboolean blank)
1333 {
1334 GdkRectangle area;
1335 GtkWidget *widget;
1336 int row, col;
1337
1338 g_return_if_fail(collection != NULL);
1339 /* g_return_if_fail(IS_COLLECTION(collection)); (slow) */
1340 g_return_if_fail(item >= 0 &&
1341 (item == 0 || item < collection->number_of_items));
1342
1343 widget = GTK_WIDGET(collection);
1344 if (!GTK_WIDGET_REALIZED(widget))
1345 return;
1346
1347 collection_item_to_rowcol(collection, item, &row, &col);
1348
1349 collection_get_item_area(collection, row, col, &area);
1350
1351 gdk_window_invalidate_rect(widget->window, &area, FALSE);
1352 }
1353
collection_set_item_size(Collection * collection,int width,int height)1354 void collection_set_item_size(Collection *collection, int width, int height)
1355 {
1356 GtkWidget *widget;
1357
1358 g_return_if_fail(collection != NULL);
1359 g_return_if_fail(IS_COLLECTION(collection));
1360 g_return_if_fail(width > 4 && height > 4);
1361
1362 if (collection->item_width == width &&
1363 collection->item_height == height)
1364 return;
1365
1366 widget = GTK_WIDGET(collection);
1367
1368 collection->item_width = width;
1369 collection->item_height = height;
1370
1371 if (GTK_WIDGET_REALIZED(widget))
1372 {
1373 gint window_width;
1374
1375 gdk_drawable_get_size(widget->window, &window_width, NULL);
1376 collection->columns = MAX(window_width / collection->item_width,
1377 1);
1378 if (collection->cursor_item != -1)
1379 scroll_to_show(collection, collection->cursor_item);
1380 gtk_widget_queue_draw(widget);
1381 }
1382
1383 gtk_widget_queue_resize(GTK_WIDGET(collection));
1384 }
1385
1386 static int (*cmp_callback)(const void *a, const void *b) = NULL;
collection_cmp(const void * a,const void * b)1387 static int collection_cmp(const void *a, const void *b)
1388 {
1389 return cmp_callback(((CollectionItem *) a)->data,
1390 ((CollectionItem *) b)->data);
1391 }
collection_rcmp(const void * a,const void * b)1392 static int collection_rcmp(const void *a, const void *b)
1393 {
1394 return -cmp_callback(((CollectionItem *) a)->data,
1395 ((CollectionItem *) b)->data);
1396 }
1397
1398 /* Cursor is positioned on item with the same data as before the sort.
1399 * Same for the wink item.
1400 */
collection_qsort(Collection * collection,int (* compar)(const void *,const void *),GtkSortType order)1401 void collection_qsort(Collection *collection,
1402 int (*compar)(const void *, const void *),
1403 GtkSortType order)
1404 {
1405 int cursor, wink, items, wink_on_map;
1406 gpointer cursor_data = NULL;
1407 gpointer wink_data = NULL;
1408 gpointer wink_on_map_data = NULL;
1409 CollectionItem *array;
1410 int i;
1411 int mul = order == GTK_SORT_ASCENDING ? 1 : -1;
1412
1413 g_return_if_fail(collection != NULL);
1414 g_return_if_fail(IS_COLLECTION(collection));
1415 g_return_if_fail(compar != NULL);
1416 g_return_if_fail(cmp_callback == NULL);
1417
1418 /* Check to see if it needs sorting (saves redrawing) */
1419 if (collection->number_of_items < 2)
1420 return;
1421
1422 array = collection->items;
1423 for (i = 1; i < collection->number_of_items; i++)
1424 {
1425 if (mul * compar(array[i - 1].data, array[i].data) > 0)
1426 break;
1427 }
1428 if (i == collection->number_of_items)
1429 return; /* Already sorted */
1430
1431 items = collection->number_of_items;
1432
1433 wink_on_map = collection->wink_on_map;
1434 if (wink_on_map >= 0 && wink_on_map < items)
1435 {
1436 wink_on_map_data = collection->items[wink_on_map].data;
1437 collection->wink_on_map = -1;
1438 }
1439 else
1440 wink = -1;
1441
1442 wink = collection->wink_item;
1443 if (wink >= 0 && wink < items)
1444 {
1445 wink_data = collection->items[wink].data;
1446 collection->wink_item = -1;
1447 }
1448 else
1449 wink = -1;
1450
1451 cursor = collection->cursor_item;
1452 if (cursor >= 0 && cursor < items)
1453 cursor_data = collection->items[cursor].data;
1454 else
1455 cursor = -1;
1456
1457 cmp_callback = compar;
1458 qsort(collection->items, items, sizeof(collection->items[0]),
1459 order == GTK_SORT_ASCENDING ? collection_cmp
1460 : collection_rcmp);
1461 cmp_callback = NULL;
1462
1463 if (cursor > -1 || wink > -1 || wink_on_map > -1)
1464 {
1465 int item;
1466
1467 for (item = 0; item < items; item++)
1468 {
1469 if (collection->items[item].data == cursor_data)
1470 collection_set_cursor_item(collection, item,
1471 TRUE);
1472 if (collection->items[item].data == wink_on_map_data)
1473 collection->wink_on_map = item;
1474 if (collection->items[item].data == wink_data)
1475 {
1476 collection->cursor_item_old = item;
1477 collection->wink_item = item;
1478 scroll_to_show(collection, item);
1479 }
1480 }
1481 }
1482
1483 gtk_widget_queue_draw(GTK_WIDGET(collection));
1484 }
1485
1486 /* Find an item in a sorted collection.
1487 * Returns the item number, or -1 if not found.
1488 */
collection_find_item(Collection * collection,gpointer data,int (* compar)(const void *,const void *),GtkSortType order)1489 int collection_find_item(Collection *collection, gpointer data,
1490 int (*compar)(const void *, const void *),
1491 GtkSortType order)
1492 {
1493 int lower, upper;
1494 int mul = order == GTK_SORT_ASCENDING ? 1 : -1;
1495
1496 g_return_val_if_fail(collection != NULL, -1);
1497 g_return_val_if_fail(IS_COLLECTION(collection), -1);
1498 g_return_val_if_fail(compar != NULL, -1);
1499
1500 /* If item is here, then: lower <= i < upper */
1501 lower = 0;
1502 upper = collection->number_of_items;
1503
1504 while (lower < upper)
1505 {
1506 int i, cmp;
1507
1508 i = (lower + upper) >> 1;
1509
1510 cmp = mul * compar(collection->items[i].data, data);
1511 if (cmp == 0)
1512 return i;
1513
1514 if (cmp > 0)
1515 upper = i;
1516 else
1517 lower = i + 1;
1518 }
1519
1520 return -1;
1521 }
1522
1523 /* Return the number of the item under the point (x,y), or -1 for none.
1524 * This may call your test_point callback. The point is relative to the
1525 * collection's origin.
1526 */
collection_get_item(Collection * collection,int x,int y)1527 int collection_get_item(Collection *collection, int x, int y)
1528 {
1529 int row, col;
1530 int width;
1531 int item;
1532
1533 g_return_val_if_fail(collection != NULL, -1);
1534
1535 col = x / collection->item_width;
1536 row = y / collection->item_height;
1537
1538 if (col >= collection->columns)
1539 col = collection->columns - 1;
1540
1541 if (col < 0 || row < 0)
1542 return -1;
1543
1544 if (col == collection->columns - 1)
1545 width = collection->item_width << 1;
1546 else
1547 width = collection->item_width;
1548
1549 item = collection_rowcol_to_item(collection, row, col);
1550 if (item >= collection->number_of_items)
1551 return -1;
1552
1553 x -= col * collection->item_width;
1554 y -= row * collection->item_height;
1555
1556 if (collection->test_point(collection, x, y,
1557 &collection->items[item], width, collection->item_height,
1558 collection->cb_user_data))
1559 return item;
1560
1561 return -1;
1562 }
1563
1564 /* Set the cursor/highlight over the given item. Passing -1
1565 * hides the cursor. As a special case, you may set the cursor item
1566 * to zero when there are no items.
1567 */
collection_set_cursor_item(Collection * collection,gint item,gboolean may_scroll)1568 void collection_set_cursor_item(Collection *collection, gint item,
1569 gboolean may_scroll)
1570 {
1571 int old_item;
1572
1573 g_return_if_fail(collection != NULL);
1574 g_return_if_fail(IS_COLLECTION(collection));
1575 g_return_if_fail(item >= -1 &&
1576 (item < collection->number_of_items || item == 0));
1577
1578 old_item = collection->cursor_item;
1579
1580 if (old_item == item)
1581 return;
1582
1583 collection->cursor_item = item;
1584
1585 if (old_item != -1)
1586 collection_draw_item(collection, old_item, TRUE);
1587
1588 if (item != -1)
1589 {
1590 collection_draw_item(collection, item, TRUE);
1591 if (may_scroll)
1592 scroll_to_show(collection, item);
1593 }
1594 else if (old_item != -1)
1595 collection->cursor_item_old = old_item;
1596 }
1597
1598 /* Briefly highlight an item to draw the user's attention to it.
1599 * -1 cancels the effect, as does deleting items, sorting the collection
1600 * or starting a new wink effect.
1601 * Otherwise, the effect will cancel itself after a short pause.
1602 * */
collection_wink_item(Collection * collection,gint item)1603 void collection_wink_item(Collection *collection, gint item)
1604 {
1605 g_return_if_fail(collection != NULL);
1606 g_return_if_fail(IS_COLLECTION(collection));
1607 g_return_if_fail(item >= -1 && item < collection->number_of_items);
1608
1609 if (collection->wink_item != -1)
1610 cancel_wink(collection);
1611 if (item == -1)
1612 return;
1613
1614 if (!GTK_WIDGET_MAPPED(GTK_WIDGET(collection)))
1615 {
1616 collection->wink_on_map = item;
1617 return;
1618 }
1619
1620 collection->cursor_item_old = collection->wink_item = item;
1621 collection->winks_left = MAX_WINKS;
1622
1623 collection->wink_timeout = g_timeout_add(70,
1624 (GSourceFunc) wink_timeout,
1625 collection);
1626 scroll_to_show(collection, item);
1627 invert_wink(collection);
1628
1629 gdk_flush();
1630 }
1631
1632 /* Call test(item, data) on each item in the collection.
1633 * Remove all items for which it returns TRUE. test() should
1634 * free the data before returning TRUE. The collection is in an
1635 * inconsistant state during this call (ie, when test() is called).
1636 *
1637 * If test is NULL, remove all items.
1638 */
collection_delete_if(Collection * collection,gboolean (* test)(gpointer item,gpointer data),gpointer data)1639 void collection_delete_if(Collection *collection,
1640 gboolean (*test)(gpointer item, gpointer data),
1641 gpointer data)
1642 {
1643 int in, out = 0;
1644 int selected = 0;
1645 int cursor;
1646
1647 g_return_if_fail(collection != NULL);
1648 g_return_if_fail(IS_COLLECTION(collection));
1649
1650 cursor = collection->cursor_item;
1651
1652 for (in = 0; in < collection->number_of_items; in++)
1653 {
1654 if (test && !test(collection->items[in].data, data))
1655 {
1656 /* Keep item */
1657 if (collection->items[in].selected)
1658 {
1659 collection->items[out].selected = TRUE;
1660 selected++;
1661 }
1662 else
1663 collection->items[out].selected = FALSE;
1664
1665 collection->items[out].data =
1666 collection->items[in].data;
1667 collection->items[out].view_data =
1668 collection->items[in].view_data;
1669 out++;
1670 }
1671 else
1672 {
1673 /* Remove item */
1674 if (collection->free_item)
1675 collection->free_item(collection,
1676 &collection->items[in]);
1677
1678 if (collection->cursor_item >= in)
1679 cursor--;
1680 }
1681 }
1682
1683 if (in != out)
1684 {
1685 collection->cursor_item = cursor;
1686
1687 if (collection->wink_item != -1)
1688 {
1689 collection->wink_item = -1;
1690 g_source_remove(collection->wink_timeout);
1691 }
1692
1693 collection->number_of_items = out;
1694 if (collection->number_selected && !selected)
1695 {
1696 /* We've lost all the selected items */
1697 g_signal_emit(collection,
1698 collection_signals[LOSE_SELECTION], 0,
1699 current_event_time);
1700 }
1701
1702 collection->number_selected = selected;
1703 resize_arrays(collection,
1704 MAX(collection->number_of_items, MINIMUM_ITEMS));
1705
1706 if (GTK_WIDGET_REALIZED(GTK_WIDGET(collection)))
1707 gtk_widget_queue_draw(GTK_WIDGET(collection));
1708
1709 gtk_widget_queue_resize(GTK_WIDGET(collection));
1710 }
1711 }
1712
1713 /* Move the cursor by the given row and column offsets.
1714 * Moving by (0,0) can be used to simply make the cursor appear.
1715 */
collection_move_cursor(Collection * collection,int drow,int dcol)1716 void collection_move_cursor(Collection *collection, int drow, int dcol)
1717 {
1718 int row, col, item;
1719 int first, last, total_rows, total_cols;
1720
1721 g_return_if_fail(collection != NULL);
1722 g_return_if_fail(IS_COLLECTION(collection));
1723
1724 if (!collection->number_of_items)
1725 {
1726 /* Show the cursor, even though there are no items */
1727 collection_set_cursor_item(collection, 0, TRUE);
1728 return;
1729 }
1730
1731 get_visible_limits(collection, &first, &last);
1732 total_rows = collection_get_rows(collection);
1733 total_cols = collection_get_cols(collection);
1734
1735 item = collection->cursor_item;
1736 if (item == -1)
1737 {
1738 item = MIN(collection->cursor_item_old,
1739 collection->number_of_items - 1);
1740 }
1741
1742 if (item == -1)
1743 {
1744 col = 0;
1745 row = first;
1746 }
1747 else
1748 {
1749 collection_item_to_rowcol(collection, item, &row, &col);
1750
1751 col += dcol;
1752 if (collection->vertical_order)
1753 {
1754 col = MAX(0,col);
1755 col = MIN(col, total_cols - 1);
1756 }
1757
1758 if (row < first)
1759 row = first;
1760 else if (row > last)
1761 row = last;
1762 else {
1763 row += drow;
1764 if (collection->vertical_order)
1765 {
1766 if (row >= total_rows)
1767 {
1768 row = 0;
1769 col += 1;
1770 }
1771 }
1772 else
1773 {
1774 row = MAX(row, 0);
1775 row = MIN(row, total_rows - 1);
1776 }
1777 }
1778 }
1779
1780 item = collection_rowcol_to_item(collection, row, col);
1781
1782 item = MAX(item, 0);
1783 item = MIN(item, collection->number_of_items-1);
1784
1785 collection_set_cursor_item(collection, item, TRUE);
1786 }
1787
1788 /* Start a lasso box drag */
collection_lasso_box(Collection * collection,int x,int y)1789 void collection_lasso_box(Collection *collection, int x, int y)
1790 {
1791 collection->drag_box_x[0] = x;
1792 collection->drag_box_y[0] = y;
1793 collection->drag_box_x[1] = x;
1794 collection->drag_box_y[1] = y;
1795
1796 add_lasso_box(collection);
1797 }
1798
1799 /* Remove the lasso box. Applies fn to each item inside the box.
1800 * fn may be GDK_INVERT, GDK_SET, GDK_NOOP or GDK_CLEAR.
1801 */
collection_end_lasso(Collection * collection,GdkFunction fn)1802 void collection_end_lasso(Collection *collection, GdkFunction fn)
1803 {
1804 if (fn != GDK_CLEAR)
1805 {
1806 GdkRectangle region;
1807
1808 find_lasso_area(collection, ®ion);
1809
1810 collection_process_area(collection, ®ion, fn,
1811 GDK_CURRENT_TIME);
1812 }
1813
1814 abort_lasso(collection);
1815 }
1816
1817 /* Unblock the selection_changed signal, emitting the signal if the
1818 * block counter reaches zero and emit is TRUE.
1819 */
collection_unblock_selection_changed(Collection * collection,guint time,gboolean emit)1820 void collection_unblock_selection_changed(Collection *collection,
1821 guint time,
1822 gboolean emit)
1823 {
1824 g_return_if_fail(collection != NULL);
1825 g_return_if_fail(IS_COLLECTION(collection));
1826 g_return_if_fail(collection->block_selection_changed > 0);
1827
1828 collection->block_selection_changed--;
1829
1830 if (emit && !collection->block_selection_changed)
1831 g_signal_emit(collection,
1832 collection_signals[SELECTION_CHANGED], 0, time);
1833 }
1834
1835 /* Translate the item number to the (row, column) form */
collection_item_to_rowcol(const Collection * collection,int item,int * row,int * col)1836 void collection_item_to_rowcol (const Collection *collection,
1837 int item, int *row, int *col)
1838 {
1839 if (!collection->vertical_order)
1840 {
1841 *row = item / collection->columns;
1842 *col = item % collection->columns;
1843 }
1844 else
1845 {
1846 int rows = collection_get_rows(collection);
1847 *row = item % rows;
1848 *col = item / rows;
1849 }
1850 }
1851
1852
1853
1854 /* Translate the (row, column) form to the item number.
1855 * May return a number >= collection->number_of_items.
1856 */
collection_rowcol_to_item(const Collection * collection,int row,int col)1857 int collection_rowcol_to_item(const Collection *collection, int row, int col)
1858 {
1859 if (!collection->vertical_order)
1860 return row * collection->columns + col;
1861 else
1862 {
1863 int rows = collection_get_rows(collection);
1864 if (row >= rows)
1865 return collection->number_of_items;
1866 return row + col * rows;
1867 }
1868 }
1869