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, &region);
1809 
1810 		collection_process_area(collection, &region, 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