1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 
3 /*
4  *  GThumb
5  *
6  *  Copyright (C) 2001-2011 The Free Software Foundation, Inc.
7  *
8  *  This program is free software; you can redistribute it and/or modify
9  *  it under the terms of the GNU General Public License as published by
10  *  the Free Software Foundation; either version 2 of the License, or
11  *  (at your option) any later version.
12  *
13  *  This program is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *  GNU General Public License for more details.
17  *
18  *  You should have received a copy of the GNU General Public License
19  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
20  */
21 
22 #include <config.h>
23 #include <libintl.h>
24 #include <string.h>
25 #include <math.h>
26 #include <glib.h>
27 #include <gdk/gdkkeysyms.h>
28 #include <gdk-pixbuf/gdk-pixbuf.h>
29 #include <gtk/gtk.h>
30 #include "cairo-utils.h"
31 #include "glib-utils.h"
32 #include "gth-file-data.h"
33 #include "gth-file-selection.h"
34 #include "gth-file-store.h"
35 #include "gth-file-view.h"
36 #include "gth-icon-cache.h"
37 #include "gth-grid-view.h"
38 #include "gth-marshal.h"
39 #include "gth-enum-types.h"
40 #include "gtk-utils.h"
41 
42 
43 #define GTH_GRID_VIEW_ITEM(x)      ((GthGridViewItem *)(x))
44 #define GTH_GRID_VIEW_LINE(x)      ((GthGridViewLine *)(x))
45 #define CAPTION_LINE_SPACING       4
46 #define DEFAULT_CAPTION_SPACING    4
47 #define DEFAULT_CAPTION_PADDING    2
48 #define DEFAULT_CELL_SPACING       16
49 #define DEFAULT_CELL_PADDING       5
50 #define DEFAULT_THUMBNAIL_BORDER   3
51 #define SCROLL_DELAY               30
52 #define LAYOUT_DELAY               20
53 #define MAX_DELTA_FOR_SCROLLING    1024.0
54 #define RUBBERBAND_BORDER          2
55 #define STEP_INCREMENT             0.10
56 #define PAGE_INCREMENT             0.33
57 
58 
59 static void gth_grid_view_gth_file_selection_interface_init (GthFileSelectionInterface *iface);
60 static void gth_grid_view_gth_file_view_interface_init (GthFileViewInterface *iface);
61 static void gth_grid_view_gtk_scrollable_interface_init (GtkScrollableInterface *iface);
62 
63 
64 enum {
65 	SELECT_ALL,
66 	UNSELECT_ALL,
67 	MOVE_CURSOR,
68 	SELECT_CURSOR_ITEM,
69 	TOGGLE_CURSOR_ITEM,
70 	ACTIVATE_CURSOR_ITEM,
71 	LAST_SIGNAL
72 };
73 
74 
75 enum {
76 	PROP_0,
77 	PROP_CAPTION,
78 	PROP_CELL_SPACING,
79 	PROP_HADJUSTMENT,
80 	PROP_HSCROLL_POLICY,
81 	PROP_MODEL,
82 	PROP_THUMBNAIL_SIZE,
83 	PROP_ACTIVATE_ON_SINGLE_CLICK,
84 	PROP_VADJUSTMENT,
85 	PROP_VSCROLL_POLICY
86 };
87 
88 
89 typedef enum {
90 	SYNC_INSERT,
91 	SYNC_REMOVE
92 } SyncType;
93 
94 
95 static guint grid_view_signals[LAST_SIGNAL] = { 0 };
96 
97 
98 typedef struct {
99 	/* data */
100 
101 	guint                  ref;
102 	GthFileData           *file_data;
103 	cairo_surface_t       *thumbnail;
104 	gboolean               is_icon : 1;
105 	char                  *caption;
106 	gboolean               is_image : 1;
107 	gboolean               is_video : 1;
108 	gboolean               has_alpha : 1;
109 	ItemStyle	       style;
110 
111 	/* item state */
112 
113 	GtkStateFlags          state;
114 	GtkStateFlags          tmp_state;
115 	gboolean               update_caption_height : 1;
116 
117 	/* geometry info */
118 
119 	cairo_rectangle_int_t  area;          /* union of thumbnail_area and caption_area */
120 	cairo_rectangle_int_t  thumbnail_area;
121 	cairo_rectangle_int_t  pixbuf_area;
122 	cairo_rectangle_int_t  caption_area;
123 } GthGridViewItem;
124 
125 
126 typedef struct {
127 	int    y;
128 	int    height;
129 	GList *items;
130 } GthGridViewLine;
131 
132 
133 struct _GthGridViewPrivate {
134 	GtkTreeModel          *model;
135 	GList                 *items;
136 	int                    n_items;
137 	GList                 *lines;
138 	GList                 *selection;
139 	int                    focused_item;
140 	int                    first_focused_item;  /* Used to do multiple selection with the keyboard. */
141 	guint                  make_focused_visible : 1;
142 	double                 initial_vscroll;
143 	guint                  needs_relayout : 1;
144 	guint                  needs_relayout_after_size_allocate : 1;
145 
146 	gboolean               activate_on_single_click;
147 	guint                  activate_pending : 1; /* postpone activation on button release */
148 
149 	guint                  layout_timeout;
150 	int                    relayout_from_line;
151 	guint                  update_caption_height : 1;
152 
153 	int                    width;               /* size of the view */
154 	int                    height;
155 	int                    thumbnail_size;
156 	int                    thumbnail_border;
157 	int                    cell_size;           /* max size of any cell area */
158 	int                    cell_spacing;        /* vertical space and mininum horizontal space between adjacent cell areas */
159 	double                 cell_x_spacing;      /* horizontal space between adjacent cell areas (calculated automatically to fill the horizontal space uniformly). */
160 	int                    cell_padding;        /* space between the cell area border and its content */
161 	int                    caption_spacing;     /* space between the thumbnail area and the caption area */
162 	int                    caption_padding;     /* space between the caption area border and its content */
163 
164 	guint                  scroll_timeout;      /* timeout ID for autoscrolling */
165 	double                 autoscroll_y_delta;  /* change the adjustment value by this amount when autoscrolling */
166 
167 	double                 event_last_x;        /* mouse position for autoscrolling */
168 	double                 event_last_y;
169 
170 	/* selection */
171 
172 	guint                  selecting : 1;       /* whether the user is performing a rubberband selection. */
173 	guint                  select_pending : 1;  /* whether selection is pending after a button press. */
174 	int                    select_pending_pos;
175 	GthGridViewItem       *select_pending_item;
176 	GtkSelectionMode       selection_mode;
177 	cairo_rectangle_int_t  selection_area;
178 	int                    last_selected_pos;
179 	guint                  multi_selecting_with_keyboard : 1; /* Whether a multi selection with the keyboard has started. */
180 	guint                  selection_changed : 1;
181 	int                    sel_start_x;         /* The point where the mouse selection started. */
182 	int                    sel_start_y;
183 	guint                  sel_state;           /* Modifier state when the selection began. */
184 
185 	/* drag and drop */
186 
187 	guint                  dragging : 1;        /* Whether the user is dragging items. */
188 	guint                  drag_started : 1;    /* Whether the drag has started. */
189 	gboolean               drag_source_enabled;
190 	GdkModifierType        drag_start_button_mask;
191 	int                    drag_button;
192 	GtkTargetList         *drag_target_list;
193 	GdkDragAction          drag_actions;
194 	int                    drag_start_x;        /* The point where the drag started. */
195 	int                    drag_start_y;
196 	int                    drop_item;
197 	GthDropPosition        drop_pos;
198 
199 	/*  */
200 
201 	GtkAdjustment         *hadjustment;
202 	GtkAdjustment         *vadjustment;
203 	GdkWindow             *bin_window;
204 
205 	char                  *caption_attributes;
206 	char                 **caption_attributes_v;
207 	PangoLayout           *caption_layout;
208 	gboolean               no_caption;
209 
210 	GthIconCache          *icon_cache;
211 };
212 
213 
G_DEFINE_TYPE_WITH_CODE(GthGridView,gth_grid_view,GTK_TYPE_WIDGET,G_ADD_PRIVATE (GthGridView)G_IMPLEMENT_INTERFACE (GTH_TYPE_FILE_SELECTION,gth_grid_view_gth_file_selection_interface_init)G_IMPLEMENT_INTERFACE (GTH_TYPE_FILE_VIEW,gth_grid_view_gth_file_view_interface_init)G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE,gth_grid_view_gtk_scrollable_interface_init))214 G_DEFINE_TYPE_WITH_CODE (GthGridView,
215 			 gth_grid_view,
216 			 GTK_TYPE_WIDGET,
217 			 G_ADD_PRIVATE (GthGridView)
218 			 G_IMPLEMENT_INTERFACE (GTH_TYPE_FILE_SELECTION,
219 						gth_grid_view_gth_file_selection_interface_init)
220 			 G_IMPLEMENT_INTERFACE (GTH_TYPE_FILE_VIEW,
221 						gth_grid_view_gth_file_view_interface_init)
222 			 G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE,
223 					 	gth_grid_view_gtk_scrollable_interface_init))
224 
225 
226 /* -- gth_grid_view_item -- */
227 
228 
229 static void
230 gth_grid_view_item_set_file_data (GthGridViewItem *item,
231 	          	  	  GthFileData     *file_data)
232 {
233 	_g_object_unref (item->file_data);
234 	item->file_data = _g_object_ref (file_data);
235 
236 	item->is_video = (item->file_data != NULL) ? _g_mime_type_is_video (gth_file_data_get_mime_type (item->file_data)) : FALSE;
237 	item->is_image = (item->file_data != NULL) ? _g_mime_type_is_image (gth_file_data_get_mime_type (item->file_data)) : FALSE;
238 }
239 
240 
241 static void
gth_grid_view_item_set_thumbnail(GthGridViewItem * item,cairo_surface_t * thumbnail)242 gth_grid_view_item_set_thumbnail (GthGridViewItem *item,
243 				  cairo_surface_t *thumbnail)
244 {
245 	cairo_surface_destroy (item->thumbnail);
246 	item->thumbnail = cairo_surface_reference (thumbnail);
247 
248 	if (item->thumbnail != NULL) {
249 		item->pixbuf_area.width = cairo_image_surface_get_width (item->thumbnail);
250 		item->pixbuf_area.height = cairo_image_surface_get_height (item->thumbnail);
251 		item->has_alpha = _cairo_image_surface_get_has_alpha (item->thumbnail);
252 	}
253 	else {
254 		item->pixbuf_area.width = 0;
255 		item->pixbuf_area.height = 0;
256 		item->has_alpha = FALSE;
257 	}
258 
259 	item->pixbuf_area.x = item->thumbnail_area.x  + ((item->thumbnail_area.width - item->pixbuf_area.width) / 2);
260 	item->pixbuf_area.y = item->thumbnail_area.y  + ((item->thumbnail_area.height - item->pixbuf_area.height) / 2);
261 }
262 
263 
264 #define MAX_TEXT_LENGTH     70
265 #define ODD_ROW_ATTR_STYLE  " size='small'"
266 #define EVEN_ROW_ATTR_STYLE " size='small' style='italic'"
267 
268 
269 static void
gth_grid_view_item_update_caption(GthGridViewItem * item,char ** attributes_v)270 gth_grid_view_item_update_caption (GthGridViewItem  *item,
271 				   char            **attributes_v)
272 {
273 	GString  *metadata;
274 	gboolean  odd;
275 	int       i;
276 
277 	item->update_caption_height = TRUE;
278 
279 	g_free (item->caption);
280 	item->caption = NULL;
281 
282 	if ((item->file_data == NULL)
283 	    || (attributes_v == NULL)
284 	    || g_str_equal (attributes_v[0], "none"))
285 	{
286 		return;
287 	}
288 
289 	metadata = g_string_new (NULL);
290 
291 	odd = TRUE;
292 	for (i = 0; attributes_v[i] != NULL; i++) {
293 		char *value;
294 
295 		value = gth_file_data_get_attribute_as_string (item->file_data, attributes_v[i]);
296 		if ((value != NULL) && ! g_str_equal (value, "")) {
297 			char *escaped;
298 			char *style;
299 
300 			if (metadata->len > 0)
301 				g_string_append (metadata, "\n");
302 			if (g_utf8_strlen (value, -1) > MAX_TEXT_LENGTH) {
303 				char *tmp;
304 
305 				tmp = g_strdup (value);
306 				g_utf8_strncpy (tmp, value, MAX_TEXT_LENGTH);
307 				g_free (value);
308 				value = g_strdup_printf ("%s…", tmp);
309 
310 				g_free (tmp);
311 			}
312 
313 			escaped = g_markup_escape_text (value, -1);
314 			if (strcmp (attributes_v[i], "general::rating") == 0)
315 				style = "";
316 			else
317 				style = (odd ? ODD_ROW_ATTR_STYLE : EVEN_ROW_ATTR_STYLE);
318 			g_string_append_printf (metadata, "<span%s>%s</span>", style, escaped);
319 
320 			g_free (escaped);
321 		}
322 		odd = ! odd;
323 
324 		g_free (value);
325 	}
326 
327 	item->caption = g_string_free (metadata, FALSE);
328 }
329 
330 
331 static GthGridViewItem *
gth_grid_view_item_new(GthGridView * grid_view,GthFileData * file_data,cairo_surface_t * thumbnail,gboolean is_icon,char ** attributes_v)332 gth_grid_view_item_new (GthGridView      *grid_view,
333 			GthFileData      *file_data,
334 			cairo_surface_t  *thumbnail,
335 			gboolean          is_icon,
336 			char            **attributes_v)
337 {
338 	GthGridViewItem *item;
339 
340 	item = g_new0 (GthGridViewItem, 1);
341 	item->ref = 1;
342 	gth_grid_view_item_set_file_data (item, file_data);
343 	gth_grid_view_item_set_thumbnail (item, thumbnail);
344 	item->is_icon = is_icon;
345 	gth_grid_view_item_update_caption (item, attributes_v);
346 
347 	return item;
348 }
349 
350 
351 static GthGridViewItem *
gth_grid_view_item_ref(GthGridViewItem * item)352 gth_grid_view_item_ref (GthGridViewItem *item)
353 {
354 	if (item != NULL)
355 		item->ref++;
356 	return item;
357 }
358 
359 
360 static void
gth_grid_view_item_unref(GthGridViewItem * item)361 gth_grid_view_item_unref (GthGridViewItem *item)
362 {
363 	if ((item == NULL) || (--item->ref > 0))
364 		return;
365 
366 	g_free (item->caption);
367 	cairo_surface_destroy (item->thumbnail);
368 	_g_object_unref (item->file_data);
369 	g_free (item);
370 }
371 
372 
373 /* -- gth_grid_view_line -- */
374 
375 
376 static void
gth_grid_view_line_free(GthGridViewLine * line)377 gth_grid_view_line_free (GthGridViewLine *line)
378 {
379 	g_list_foreach (line->items, (GFunc) gth_grid_view_item_unref, NULL);
380 	g_list_free (line->items);
381 	g_free (line);
382 }
383 
384 
385 /**/
386 
387 
388 static void
_gth_grid_view_free_lines(GthGridView * self)389 _gth_grid_view_free_lines (GthGridView *self)
390 {
391 	g_list_foreach (self->priv->lines, (GFunc) gth_grid_view_line_free, NULL);
392 	g_list_free (self->priv->lines);
393 	self->priv->lines = NULL;
394 	self->priv->height = 0;
395 }
396 
397 
398 static void
_gth_grid_view_free_items(GthGridView * self)399 _gth_grid_view_free_items (GthGridView *self)
400 {
401 	g_list_foreach (self->priv->items, (GFunc) gth_grid_view_item_unref, NULL);
402 	g_list_free (self->priv->items);
403 	self->priv->items = NULL;
404 
405 	g_list_free (self->priv->selection);
406 	self->priv->selection = NULL;
407 }
408 
409 
410 static void
gth_grid_view_finalize(GObject * object)411 gth_grid_view_finalize (GObject *object)
412 {
413 	GthGridView *self;
414 
415 	self = GTH_GRID_VIEW (object);
416 
417 	if (self->priv->layout_timeout != 0) {
418 		g_source_remove (self->priv->layout_timeout);
419 		self->priv->layout_timeout = 0;
420 	}
421 
422 	if (self->priv->scroll_timeout != 0) {
423 		g_source_remove (self->priv->scroll_timeout);
424 		self->priv->scroll_timeout = 0;
425 	}
426 
427 	_gth_grid_view_free_items (self);
428 	_gth_grid_view_free_lines (self);
429 	g_list_free (self->priv->selection);
430 
431 	if (self->priv->hadjustment != NULL) {
432 		_g_signal_handlers_disconnect_by_data (self->priv->hadjustment, self);
433 		g_object_unref (self->priv->hadjustment);
434 		self->priv->hadjustment = NULL;
435 	}
436 
437 	if (self->priv->vadjustment != NULL) {
438 		_g_signal_handlers_disconnect_by_data (self->priv->vadjustment, self);
439 		g_object_unref (self->priv->vadjustment);
440 		self->priv->vadjustment = NULL;
441 	}
442 
443 	if (self->priv->drag_target_list != NULL) {
444 		gtk_target_list_unref (self->priv->drag_target_list);
445 		self->priv->drag_target_list = NULL;
446 	}
447 	g_free (self->priv->caption_attributes);
448 	g_strfreev (self->priv->caption_attributes_v);
449 	_g_object_unref (self->priv->model);
450 
451 	G_OBJECT_CLASS (gth_grid_view_parent_class)->finalize (object);
452 }
453 
454 
455 static void
adjustment_value_changed(GtkAdjustment * adj,GthGridView * self)456 adjustment_value_changed (GtkAdjustment *adj,
457 			  GthGridView   *self)
458 {
459 	if (gtk_widget_get_realized (GTK_WIDGET (self)))
460 		gdk_window_move (self->priv->bin_window,
461 				 (int) - gtk_adjustment_get_value (self->priv->hadjustment),
462 				 (int) - gtk_adjustment_get_value (self->priv->vadjustment));
463 }
464 
465 
466 static void
gth_grid_view_map(GtkWidget * widget)467 gth_grid_view_map (GtkWidget *widget)
468 {
469 	GthGridView *self = GTH_GRID_VIEW (widget);
470 
471 	gtk_widget_set_mapped (widget, TRUE);
472 	gdk_window_show (self->priv->bin_window);
473 	gdk_window_show (gtk_widget_get_window (widget));
474 }
475 
476 
477 static void
_gth_grid_view_stop_dragging(GthGridView * self)478 _gth_grid_view_stop_dragging (GthGridView *self)
479 {
480 	if (! self->priv->dragging)
481 		return;
482 
483 	self->priv->dragging = FALSE;
484 	self->priv->drag_started = FALSE;
485 }
486 
487 
488 static void
gth_grid_view_unmap(GtkWidget * widget)489 gth_grid_view_unmap (GtkWidget *widget)
490 {
491 	GthGridView *self = GTH_GRID_VIEW (widget);
492 
493 	_gth_grid_view_stop_dragging (self);
494 	GTK_WIDGET_CLASS (gth_grid_view_parent_class)->unmap (widget);
495 }
496 
497 
498 /* -- _gth_grid_view_make_item_fully_visible -- */
499 
500 
501 static void
gth_grid_view_scroll_to(GthFileView * file_view,int pos,double yalign)502 gth_grid_view_scroll_to (GthFileView *file_view,
503 			 int          pos,
504 			 double       yalign)
505 {
506 	GthGridView *self = GTH_GRID_VIEW (file_view);
507 	int          n_line;
508 	int          y;
509 	int          i;
510 	GList       *line;
511 
512 	g_return_if_fail ((pos >= 0) && (pos < self->priv->n_items));
513 	g_return_if_fail ((yalign >= 0.0) && (yalign <= 1.0));
514 
515 	if (self->priv->lines == NULL)
516 		return;
517 
518 	n_line = pos / gth_grid_view_get_items_per_line (self);
519 	y = self->priv->cell_spacing;
520 	for (i = 0, line = self->priv->lines;
521 	     (i < n_line) && (line != NULL);
522 	     i++, line = line->next)
523 	{
524 		y += GTH_GRID_VIEW_LINE (line->data)->height + self->priv->cell_spacing;
525 	}
526 
527 	if (line != NULL) {
528 		int    h;
529 		double value;
530 
531 		h = gtk_widget_get_allocated_height (GTK_WIDGET (self)) - GTH_GRID_VIEW_LINE (line->data)->height - self->priv->cell_spacing;
532 		value = CLAMP ((y - (h * yalign) - ((1.0 - yalign) * self->priv->cell_spacing)),
533 			       0.0,
534 			       self->priv->height - gtk_widget_get_allocated_height (GTK_WIDGET (self)));
535 		gtk_adjustment_set_value (self->priv->vadjustment, value);
536 	}
537 }
538 
539 
540 
541 static void
gth_grid_view_set_vscroll(GthFileView * file_view,double vscroll)542 gth_grid_view_set_vscroll (GthFileView *file_view,
543 			   double       vscroll)
544 {
545 	GthGridView *self = GTH_GRID_VIEW (file_view);
546 
547 	self->priv->initial_vscroll = vscroll;
548 	gtk_adjustment_set_value (self->priv->vadjustment, vscroll);
549 }
550 
551 
552 static GthVisibility
gth_grid_view_get_visibility(GthFileView * file_view,int pos)553 gth_grid_view_get_visibility (GthFileView *file_view,
554 			      int          pos)
555 {
556 	GthGridView *self = GTH_GRID_VIEW (file_view);
557 	int          cell_top;
558 	int          line_n;
559 	int          i;
560 	GList       *line;
561 	int          cell_bottom;
562 	int          window_top;
563 	int          window_bottom;
564 
565 	g_return_val_if_fail ((pos >= 0) && (pos < self->priv->n_items), GTH_VISIBILITY_NONE);
566 
567 	if (self->priv->lines == NULL)
568 		return GTH_VISIBILITY_NONE;
569 
570 	cell_top = self->priv->cell_spacing;
571 	line_n = pos / gth_grid_view_get_items_per_line (self);
572 	for (i = 0, line = self->priv->lines;
573 	     (i < line_n) && (line != NULL);
574 	     i++, line = line->next)
575 	{
576 		cell_top += GTH_GRID_VIEW_LINE (line->data)->height + self->priv->cell_spacing;
577 	}
578 
579 	if (line == NULL)
580 		return GTH_VISIBILITY_NONE;
581 
582 	cell_bottom = cell_top + GTH_GRID_VIEW_LINE (line->data)->height + self->priv->cell_spacing;
583 	window_top = gtk_adjustment_get_value (self->priv->vadjustment);
584 	window_bottom = window_top + gtk_widget_get_allocated_height (GTK_WIDGET (self));
585 
586 	if (cell_bottom < window_top)
587 		return GTH_VISIBILITY_NONE;
588 
589 	if (cell_top > window_bottom)
590 		return GTH_VISIBILITY_NONE;
591 
592 	if ((cell_top >= window_top) && (cell_bottom <= window_bottom))
593 		return GTH_VISIBILITY_FULL;
594 
595 	if ((cell_top < window_top) && (cell_bottom >= window_top))
596 		return GTH_VISIBILITY_PARTIAL_TOP;
597 
598 	if ((cell_top <= window_bottom) && (cell_bottom > window_bottom))
599 		return GTH_VISIBILITY_PARTIAL_BOTTOM;
600 
601 	return GTH_VISIBILITY_PARTIAL;
602 }
603 
604 
605 static void
_gth_grid_view_make_item_fully_visible(GthGridView * self,int pos)606 _gth_grid_view_make_item_fully_visible (GthGridView *self,
607 					int          pos)
608 {
609 	GthVisibility visibility;
610 
611 	if (pos < 0)
612 		return;
613 
614 	visibility = gth_grid_view_get_visibility (GTH_FILE_VIEW (self), pos);
615 	if (visibility != GTH_VISIBILITY_FULL) {
616 		double y_alignment = -1.0;
617 
618 		switch (visibility) {
619 		case GTH_VISIBILITY_NONE:
620 			y_alignment = 0.5;
621 			break;
622 
623 		case GTH_VISIBILITY_PARTIAL_TOP:
624 			y_alignment = 0.0;
625 			break;
626 
627 		case GTH_VISIBILITY_PARTIAL_BOTTOM:
628 			y_alignment = 1.0;
629 			break;
630 
631 		case GTH_VISIBILITY_PARTIAL:
632 		case GTH_VISIBILITY_FULL:
633 			y_alignment = -1.0;
634 			break;
635 		}
636 
637 		if (y_alignment >= 0.0)
638 			gth_grid_view_scroll_to (GTH_FILE_VIEW (self),
639 						 pos,
640 						 y_alignment);
641 	}
642 }
643 
644 
645 /* -- grid layout -- */
646 
647 
648 static void
_gth_grid_view_update_item_size(GthGridView * self,GthGridViewItem * item)649 _gth_grid_view_update_item_size (GthGridView     *self,
650 				 GthGridViewItem *item)
651 {
652 	int thumbnail_size;
653 
654 	thumbnail_size = self->priv->cell_size - (self->priv->cell_padding * 2);
655 
656 	if (item->is_icon
657 	    || item->has_alpha
658 	    || ((item->pixbuf_area.width < self->priv->thumbnail_size) && (item->pixbuf_area.height < self->priv->thumbnail_size))
659             || (item->file_data == NULL))
660 	{
661 		item->style = ITEM_STYLE_ICON;
662 	}
663 	else if (item->is_video)
664 		item->style = ITEM_STYLE_VIDEO;
665 	else
666 		item->style = ITEM_STYLE_IMAGE;
667 
668 	switch (item->style) {
669 	case ITEM_STYLE_VIDEO:
670 		item->thumbnail_area.width = item->pixbuf_area.width;
671 		item->thumbnail_area.height = thumbnail_size - (self->priv->thumbnail_border * 2);
672 		break;
673 	case ITEM_STYLE_IMAGE:
674 		item->thumbnail_area.width = item->pixbuf_area.width + (self->priv->thumbnail_border * 2);
675 		item->thumbnail_area.height = item->pixbuf_area.height + (self->priv->thumbnail_border * 2);
676 		break;
677 	case ITEM_STYLE_ICON:
678 		item->thumbnail_area.width = thumbnail_size;
679 		item->thumbnail_area.height = thumbnail_size;
680 		break;
681 	}
682 
683 	item->caption_area.width = thumbnail_size;
684 
685 	item->area.width = self->priv->cell_size;
686 	item->area.height = self->priv->cell_padding + thumbnail_size;
687 
688 	if ((self->priv->caption_layout != NULL) && (self->priv->update_caption_height || item->update_caption_height)) {
689 		if ((item->caption != NULL) && (g_strcmp0 (item->caption, "") != 0)) {
690 			pango_layout_set_markup (self->priv->caption_layout, item->caption, -1);
691 			pango_layout_get_pixel_size (self->priv->caption_layout, NULL, &item->caption_area.height);
692 			item->caption_area.height += self->priv->caption_padding * 2;
693 		}
694 		else
695 			item->caption_area.height = 0;
696 
697 		item->update_caption_height = FALSE;
698 	}
699 
700 	if (item->caption_area.height > 0)
701 		item->area.height += self->priv->caption_spacing + item->caption_area.height;
702 
703 	item->area.height += self->priv->cell_padding;
704 }
705 
706 
707 static void
_gth_grid_view_place_item_at(GthGridView * self,GthGridViewItem * item,int x,int y)708 _gth_grid_view_place_item_at (GthGridView     *self,
709 			      GthGridViewItem *item,
710 			      int              x,
711 			      int              y)
712 {
713 	item->area.x = x;
714 	item->area.y = y;
715 
716 	switch (item->style) {
717 	case ITEM_STYLE_VIDEO:
718 	case ITEM_STYLE_IMAGE:
719 		item->thumbnail_area.x = item->area.x + ((self->priv->cell_size - item->thumbnail_area.width) / 2);
720 		if (self->priv->no_caption)
721 			item->thumbnail_area.y = item->area.y + ((self->priv->cell_size - item->thumbnail_area.height) / 2);
722 		else
723 			item->thumbnail_area.y = item->area.y + self->priv->cell_size - self->priv->cell_padding - item->thumbnail_area.height;
724 		break;
725 	case ITEM_STYLE_ICON:
726 		item->thumbnail_area.x = item->area.x + self->priv->cell_padding;
727 		item->thumbnail_area.y = item->area.y + self->priv->cell_padding;
728 		break;
729 	}
730 
731 	item->pixbuf_area.x = item->thumbnail_area.x + ((item->thumbnail_area.width - item->pixbuf_area.width) / 2);
732 	item->pixbuf_area.y = item->thumbnail_area.y + ((item->thumbnail_area.height - item->pixbuf_area.height) / 2);
733 
734 	item->caption_area.x = item->area.x + self->priv->cell_padding;
735 	item->caption_area.y = item->area.y + self->priv->cell_size - self->priv->cell_padding + self->priv->caption_spacing;
736 }
737 
738 
739 static void
_gth_grid_view_layout_line(GthGridView * self,GthGridViewLine * line)740 _gth_grid_view_layout_line (GthGridView     *self,
741 			    GthGridViewLine *line)
742 {
743 	double x;
744 	int    direction;
745 	GList *scan;
746 
747 	if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL) {
748 		x = self->priv->width - self->priv->cell_size;
749 		direction = -1;
750 	}
751 	else {
752 		x = 0;
753 		direction = 1;
754 	}
755 
756 	for (scan = line->items; scan; scan = scan->next) {
757 		GthGridViewItem *item = scan->data;
758 
759 		x += direction * self->priv->cell_x_spacing;
760 		_gth_grid_view_place_item_at (self, item, round (x), line->y);
761 		x += direction * self->priv->cell_size;
762 		x += direction * self->priv->cell_x_spacing;
763 	}
764 }
765 
766 
767 static GthGridViewLine *
_gth_grid_view_line_new(GthGridView * self,GList * items,int y,int height)768 _gth_grid_view_line_new (GthGridView *self,
769 		     	 GList       *items,
770 		     	 int          y,
771 		     	 int          height)
772 {
773 	GthGridViewLine *line;
774 
775 	line = g_new0 (GthGridViewLine, 1);
776 	line->items = g_list_reverse (items);
777 	line->y = y;
778 	line->height = height;
779 	_gth_grid_view_layout_line (self, line);
780 
781 	return line;
782 }
783 
784 
785 static void
_gth_grid_view_configure_hadjustment(GthGridView * self)786 _gth_grid_view_configure_hadjustment (GthGridView *self)
787 {
788 	double page_size;
789 	double value;
790 
791 	if (self->priv->hadjustment == NULL)
792 		return;
793 
794 	page_size = gtk_widget_get_allocated_width (GTK_WIDGET (self));
795 	value = gtk_adjustment_get_value (self->priv->hadjustment);
796 	if (value + page_size > self->priv->width)
797 		value = MAX (self->priv->width - page_size, 0);
798 
799 	gtk_adjustment_configure (self->priv->hadjustment,
800 				  value,
801 				  0.0,
802 				  MAX (page_size, self->priv->width),
803 				  STEP_INCREMENT * page_size,
804 				  PAGE_INCREMENT * page_size,
805 				  page_size);
806 }
807 
808 
809 static void
_gth_grid_view_configure_vadjustment(GthGridView * self)810 _gth_grid_view_configure_vadjustment (GthGridView *self)
811 {
812 	double page_size;
813 	double value;
814 
815 	if (self->priv->vadjustment == NULL)
816 		return;
817 
818 	page_size = gtk_widget_get_allocated_height (GTK_WIDGET (self));
819 	value = gtk_adjustment_get_value (self->priv->vadjustment);
820 	if (value + page_size > self->priv->height)
821 		value = MAX (self->priv->height - page_size, 0);
822 
823 	gtk_adjustment_configure (self->priv->vadjustment,
824 				  value,
825 				  0.0,
826 				  MAX (page_size, self->priv->height),
827 				  STEP_INCREMENT * page_size,
828 				  PAGE_INCREMENT * page_size,
829 				  page_size);
830 }
831 
832 
833 static void
_gth_grid_view_relayout_at(GthGridView * self,int pos,int y)834 _gth_grid_view_relayout_at (GthGridView *self,
835 		    	    int          pos,
836 		    	    int          y)
837 {
838 	GList *new_lines;
839 	int    items_per_line;
840 	GList *items;
841 	int    max_height;
842 	GList *scan;
843 	int    n;
844 
845 	new_lines = NULL;
846 	items_per_line = gth_grid_view_get_items_per_line (self);
847 	items = NULL;
848 	max_height = 0;
849 	for (scan = g_list_nth (self->priv->items, pos), n = pos;
850 	     scan;
851 	     scan = scan->next, n++)
852 	{
853 		GthGridViewItem *item = scan->data;
854 
855 		if ((n % items_per_line) == 0) {
856 			if (items != NULL) {
857 				new_lines = g_list_prepend (new_lines, _gth_grid_view_line_new (self, items, y, max_height));
858 				items = NULL;
859 				y += max_height + self->priv->cell_spacing;
860 			}
861 			max_height = 0;
862 		}
863 
864 		_gth_grid_view_update_item_size (self, item);
865 		max_height = MAX (item->area.height, max_height);
866 
867 		items = g_list_prepend (items, gth_grid_view_item_ref (item));
868 	}
869 
870 	if (items != NULL) {
871 		new_lines = g_list_prepend (new_lines, _gth_grid_view_line_new (self, items, y, max_height));
872 		y += max_height + self->priv->cell_spacing;
873 	}
874 
875 	self->priv->lines = g_list_concat (self->priv->lines, g_list_reverse (new_lines));
876 
877 	if (y != self->priv->height) {
878 		GtkAllocation allocation;
879 
880 		self->priv->height = y;
881 
882 		gtk_widget_get_allocation (GTK_WIDGET (self), &allocation);
883 		gdk_window_resize (self->priv->bin_window,
884 				   MAX (self->priv->width, allocation.width),
885 				   MAX (self->priv->height, allocation.height));
886 	}
887 
888 	_gth_grid_view_configure_hadjustment (self);
889 	_gth_grid_view_configure_vadjustment (self);
890 }
891 
892 
893 static void
_gth_grid_view_free_lines_from(GthGridView * self,int first_line)894 _gth_grid_view_free_lines_from (GthGridView *self,
895 				int          first_line)
896 {
897 	GList *lines;
898 	GList *scan;
899 
900 	lines = g_list_nth (self->priv->lines, first_line);
901 	if (lines == NULL)
902 		return;
903 
904 	/* truncate self->priv->lines before the first line to free */
905 	if (lines->prev != NULL)
906 		lines->prev->next = NULL;
907 	else
908 		self->priv->lines = NULL;
909 
910 	for (scan = lines; scan; scan = scan->next)
911 		gth_grid_view_line_free (GTH_GRID_VIEW_LINE (scan->data));
912 	g_list_free (lines);
913 }
914 
915 
916 static void
_gth_grid_view_relayout_from_line(GthGridView * self,int line)917 _gth_grid_view_relayout_from_line (GthGridView *self,
918 				   int          line)
919 {
920 	int    y;
921 	GList *scan;
922 
923 	if (! gtk_widget_get_realized (GTK_WIDGET (self))) {
924 		self->priv->needs_relayout = TRUE;
925 		return;
926 	}
927 
928 	if (self->priv->update_caption_height)
929 		pango_layout_set_width (self->priv->caption_layout,
930 				        (self->priv->cell_size - (self->priv->cell_padding * 2)) * PANGO_SCALE);
931 
932 	_gth_grid_view_free_lines_from (self, line);
933 	y = self->priv->cell_spacing;
934 	for (scan = self->priv->lines; scan; scan = scan->next)
935 		y += GTH_GRID_VIEW_LINE (scan->data)->height + self->priv->cell_spacing;
936 
937 	_gth_grid_view_relayout_at (self,
938 				    line * gth_grid_view_get_items_per_line (self),
939 				    y);
940 
941 	self->priv->update_caption_height = FALSE;
942 	self->priv->relayout_from_line = -1;
943 	self->priv->needs_relayout = FALSE;
944 
945 	gtk_widget_queue_draw (GTK_WIDGET (self));
946 }
947 
948 
949 static gboolean
_gth_grid_view_relayout_cb(gpointer data)950 _gth_grid_view_relayout_cb (gpointer data)
951 {
952 	GthGridView *self = data;
953 
954 	if (self->priv->layout_timeout != 0) {
955 		g_source_remove (self->priv->layout_timeout);
956 		self->priv->layout_timeout = 0;
957 	}
958 
959 	_gth_grid_view_relayout_from_line (self, self->priv->relayout_from_line);
960 
961 	if (self->priv->make_focused_visible) {
962 		self->priv->make_focused_visible = FALSE;
963 		_gth_grid_view_make_item_fully_visible (self, self->priv->focused_item);
964 	}
965 
966 	if (self->priv->initial_vscroll > 0) {
967 		gtk_adjustment_set_value (self->priv->vadjustment, self->priv->initial_vscroll);
968 		self->priv->initial_vscroll = 0;
969 	}
970 
971 	return FALSE;
972 }
973 
974 
975 static void
_gth_grid_view_queue_relayout_from_line(GthGridView * self,int line)976 _gth_grid_view_queue_relayout_from_line (GthGridView *self,
977 					 int          line)
978 {
979 	if (self->priv->relayout_from_line != -1)
980 		self->priv->relayout_from_line = MIN (line, self->priv->relayout_from_line);
981 	else
982 		self->priv->relayout_from_line = line;
983 
984 	if (! gtk_widget_get_realized (GTK_WIDGET (self))) {
985 		self->priv->needs_relayout = TRUE;
986 		return;
987 	}
988 
989 	if (self->priv->layout_timeout == 0)
990 		self->priv->layout_timeout = g_timeout_add (LAYOUT_DELAY,
991 							    _gth_grid_view_relayout_cb,
992 							    self);
993 }
994 
995 
996 static void
_gth_grid_view_queue_relayout(GthGridView * self)997 _gth_grid_view_queue_relayout (GthGridView *self)
998 {
999 	_gth_grid_view_queue_relayout_from_line (self, 0);
1000 }
1001 
1002 
1003 static void
_gth_grid_view_queue_relayout_from_position(GthGridView * self,int pos)1004 _gth_grid_view_queue_relayout_from_position (GthGridView *self,
1005 					     int          pos)
1006 {
1007 	_gth_grid_view_queue_relayout_from_line (self, pos / gth_grid_view_get_items_per_line (self));
1008 }
1009 
1010 
1011 static void
gth_grid_view_realize(GtkWidget * widget)1012 gth_grid_view_realize (GtkWidget *widget)
1013 {
1014 	GthGridView     *self;
1015 	GtkAllocation    allocation;
1016 	GdkWindowAttr    attributes;
1017 	int              attributes_mask;
1018 	GdkWindow       *window;
1019 
1020 	self = GTH_GRID_VIEW (widget);
1021 
1022 	gtk_widget_set_realized (widget, TRUE);
1023 	gtk_widget_get_allocation (widget, &allocation);
1024 
1025 	/* view window */
1026 
1027 	attributes.window_type = GDK_WINDOW_CHILD;
1028 	attributes.x           = allocation.x;
1029 	attributes.y           = allocation.y;
1030 	attributes.width       = allocation.width;
1031 	attributes.height      = allocation.height;
1032 	attributes.wclass      = GDK_INPUT_OUTPUT;
1033 	attributes.visual      = gtk_widget_get_visual (widget);
1034 	attributes.event_mask  = GDK_VISIBILITY_NOTIFY_MASK;
1035 	attributes_mask        = (GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL);
1036 	window = gdk_window_new (gtk_widget_get_parent_window (widget),
1037 				 &attributes,
1038 				 attributes_mask);
1039 	gtk_widget_register_window (widget, window);
1040 	gtk_widget_set_window (widget, window);
1041 
1042 	/* bin window */
1043 
1044 	attributes.x = 0;
1045 	attributes.y = 0;
1046 	attributes.width = MAX (self->priv->width, allocation.width);
1047 	attributes.height = MAX (self->priv->height, allocation.height);
1048 	attributes.event_mask = (GDK_EXPOSURE_MASK
1049 				 | GDK_SCROLL_MASK
1050 				 | GDK_SMOOTH_SCROLL_MASK
1051 				 | GDK_POINTER_MOTION_MASK
1052 				 | GDK_ENTER_NOTIFY_MASK
1053 				 | GDK_LEAVE_NOTIFY_MASK
1054 				 | GDK_BUTTON_PRESS_MASK
1055 				 | GDK_BUTTON_RELEASE_MASK
1056 				 | gtk_widget_get_events (widget));
1057 
1058 	self->priv->bin_window = gdk_window_new (gtk_widget_get_window (widget),
1059 						 &attributes,
1060 						 attributes_mask);
1061 	gtk_widget_register_window (widget, self->priv->bin_window);
1062 
1063 	/* style */
1064 
1065 	self->priv->caption_layout = gtk_widget_create_pango_layout (widget, NULL);
1066 	pango_layout_set_wrap (self->priv->caption_layout, PANGO_WRAP_WORD_CHAR);
1067 	pango_layout_set_alignment (self->priv->caption_layout, PANGO_ALIGN_LEFT);
1068 	pango_layout_set_spacing (self->priv->caption_layout, CAPTION_LINE_SPACING);
1069 
1070 	/**/
1071 
1072 	gdk_window_show (self->priv->bin_window);
1073 	self->priv->needs_relayout = TRUE;
1074 
1075 	/* this is used to make make_focused_visible work correctly */
1076 	if (self->priv->needs_relayout_after_size_allocate) {
1077 		self->priv->needs_relayout_after_size_allocate = FALSE;
1078 		_gth_grid_view_queue_relayout (self);
1079 	}
1080 }
1081 
1082 
1083 static void
gth_grid_view_unrealize(GtkWidget * widget)1084 gth_grid_view_unrealize (GtkWidget *widget)
1085 {
1086 	GthGridView *self;
1087 
1088 	self = GTH_GRID_VIEW (widget);
1089 
1090 	gtk_widget_unregister_window (widget, self->priv->bin_window);
1091 	gdk_window_destroy (self->priv->bin_window);
1092 	self->priv->bin_window = NULL;
1093 
1094 	g_object_unref (self->priv->caption_layout);
1095 	self->priv->caption_layout = NULL;
1096 
1097 	gth_icon_cache_free (self->priv->icon_cache);
1098 	self->priv->icon_cache = NULL;
1099 
1100 	GTK_WIDGET_CLASS (gth_grid_view_parent_class)->unrealize (widget);
1101 }
1102 
1103 
1104 static void
gth_grid_view_state_flags_changed(GtkWidget * widget,GtkStateFlags previous_state)1105 gth_grid_view_state_flags_changed (GtkWidget     *widget,
1106                                    GtkStateFlags  previous_state)
1107 {
1108 	gtk_widget_queue_draw (widget);
1109 }
1110 
1111 
1112 static void
gth_grid_view_style_updated(GtkWidget * widget)1113 gth_grid_view_style_updated (GtkWidget *widget)
1114 {
1115 	GTK_WIDGET_CLASS (gth_grid_view_parent_class)->style_updated (widget);
1116 
1117 	gtk_widget_queue_resize (widget);
1118 }
1119 
1120 
1121 static void
gth_grid_view_get_preferred_width(GtkWidget * widget,int * minimum,int * natural)1122 gth_grid_view_get_preferred_width (GtkWidget *widget,
1123                                    int       *minimum,
1124                                    int       *natural)
1125 {
1126 	GthGridView *self = GTH_GRID_VIEW (widget);
1127 
1128 	if (minimum != NULL)
1129 		*minimum = self->priv->cell_size;
1130 	if (natural != NULL)
1131 		*natural = *minimum;
1132 }
1133 
1134 
1135 static void
gth_grid_view_get_preferred_height(GtkWidget * widget,int * minimum,int * natural)1136 gth_grid_view_get_preferred_height (GtkWidget *widget,
1137                                     int       *minimum,
1138                                     int       *natural)
1139 {
1140 	GthGridView *self = GTH_GRID_VIEW (widget);
1141 
1142 	if (minimum != NULL)
1143 		*minimum = self->priv->cell_size;
1144 	if (natural != NULL)
1145 		*natural = *minimum;
1146 }
1147 
1148 
1149 static void
_gth_grid_view_relayout_lines(GthGridView * self)1150 _gth_grid_view_relayout_lines (GthGridView *self)
1151 {
1152 	GList *scan;
1153 
1154 	for (scan = self->priv->lines; scan; scan = scan->next) {
1155 		GthGridViewLine *line = scan->data;
1156 		_gth_grid_view_layout_line (self, line);
1157 	}
1158 
1159 	gtk_widget_queue_draw (GTK_WIDGET (self));
1160 }
1161 
1162 
1163 static double
round_to_0_2(double n)1164 round_to_0_2 (double n)
1165 {
1166 	double i = floor (n);
1167 	double f = n - i;
1168 	double r = 0;
1169 
1170 	if (f < 0.1)
1171 		r = 0;
1172 	else if (f < 0.3)
1173 		r = 0.2;
1174 	else if (f < 0.5)
1175 		r = 0.4;
1176 	else if (f < 0.7)
1177 		r = 0.6;
1178 	else if (f < 0.9)
1179 		r = 0.8;
1180 	else
1181 		r = 1.0;
1182 	return i + r;
1183 }
1184 
1185 
1186 static void
gth_grid_view_size_allocate(GtkWidget * widget,GtkAllocation * allocation)1187 gth_grid_view_size_allocate (GtkWidget     *widget,
1188 			     GtkAllocation *allocation)
1189 {
1190 	GthGridView *self;
1191 	int          old_cells_per_line;
1192 	int          new_cells_per_line;
1193 	double       new_cell_x_spacing;
1194 	gboolean     cell_x_spacing_changed;
1195 
1196 	self = GTH_GRID_VIEW (widget);
1197 
1198 	old_cells_per_line = gth_grid_view_get_items_per_line (self);
1199 	self->priv->width = allocation->width;
1200 
1201 	new_cells_per_line = gth_grid_view_get_items_per_line (self);
1202 	new_cell_x_spacing = round_to_0_2 ((((double) self->priv->width / new_cells_per_line) - self->priv->cell_size) / 2.0);
1203 	cell_x_spacing_changed = (new_cell_x_spacing != self->priv->cell_x_spacing);
1204 	self->priv->cell_x_spacing = new_cell_x_spacing;
1205 
1206 	gtk_widget_set_allocation (widget, allocation);
1207 
1208 	if (gtk_widget_get_realized (widget)) {
1209 		gdk_window_move_resize (gtk_widget_get_window (widget),
1210 					allocation->x,
1211 					allocation->y,
1212 					allocation->width,
1213 					allocation->height);
1214 		gdk_window_resize (self->priv->bin_window,
1215 				   MAX (self->priv->width, allocation->width),
1216 				   MAX (self->priv->height, allocation->height));
1217 
1218 		if (self->priv->needs_relayout || (old_cells_per_line != new_cells_per_line)) {
1219 			self->priv->make_focused_visible = TRUE;
1220 			_gth_grid_view_queue_relayout (self);
1221 		}
1222 		else if (cell_x_spacing_changed)
1223 			_gth_grid_view_relayout_lines (self);
1224 	}
1225 	else
1226 		self->priv->needs_relayout_after_size_allocate = TRUE;
1227 
1228 	_gth_grid_view_configure_hadjustment (self);
1229 	_gth_grid_view_configure_vadjustment (self);
1230 }
1231 
1232 
1233 static int
get_first_visible_at_offset(GthGridView * self,double ofs)1234 get_first_visible_at_offset (GthGridView *self,
1235 			     double       ofs)
1236 {
1237 	int    n_line;
1238 	GList *scan;
1239 	int    pos;
1240 
1241 	if ((self->priv->n_items == 0) || (self->priv->lines == NULL))
1242 		return -1;
1243 
1244 	n_line = 0;
1245 	for (scan = self->priv->lines; scan && (ofs > 0.0); scan = scan->next) {
1246 		ofs -= GTH_GRID_VIEW_LINE (scan->data)->height + self->priv->cell_spacing;
1247 		n_line++;
1248 	}
1249 	pos = gth_grid_view_get_items_per_line (self) * (n_line - 1);
1250 
1251 	return CLAMP (pos, 0, self->priv->n_items - 1);
1252 }
1253 
1254 
1255 static int
gth_grid_view_get_first_visible(GthFileView * file_view)1256 gth_grid_view_get_first_visible (GthFileView *file_view)
1257 {
1258 	GthGridView *self = GTH_GRID_VIEW (file_view);
1259 
1260 	return get_first_visible_at_offset (self, gtk_adjustment_get_value (self->priv->vadjustment));
1261 }
1262 
1263 
1264 static int
get_last_visible_at_offset(GthGridView * self,double ofs)1265 get_last_visible_at_offset (GthGridView *self,
1266 			    double       ofs)
1267 {
1268 	int    n_line;
1269 	GList *scan;
1270 	int    pos;
1271 
1272 	if ((self->priv->n_items == 0) || (self->priv->lines == NULL))
1273 		return -1;
1274 
1275 	n_line = 0;
1276 	for (scan = self->priv->lines; scan && (ofs > 0.0); scan = scan->next) {
1277 		ofs -= GTH_GRID_VIEW_LINE (scan->data)->height + self->priv->cell_spacing;
1278 		n_line++;
1279 	}
1280 	pos = gth_grid_view_get_items_per_line (self) * n_line - 1;
1281 
1282 	return CLAMP (pos, 0, self->priv->n_items - 1);
1283 }
1284 
1285 
1286 static int
gth_grid_view_get_last_visible(GthFileView * file_view)1287 gth_grid_view_get_last_visible (GthFileView *file_view)
1288 {
1289 	GthGridView *self = GTH_GRID_VIEW (file_view);
1290 
1291 	return get_last_visible_at_offset (self,
1292 					   (gtk_adjustment_get_value (self->priv->vadjustment)
1293 					    + gtk_adjustment_get_page_size (self->priv->vadjustment)));
1294 }
1295 
1296 
1297 /* -- gth_grid_view_draw -- */
1298 
1299 
1300 static void
_gth_grid_view_item_draw_thumbnail(GthGridViewItem * item,cairo_t * cr,GtkWidget * widget,GtkStateFlags item_state,GthGridView * grid_view)1301 _gth_grid_view_item_draw_thumbnail (GthGridViewItem *item,
1302 				    cairo_t         *cr,
1303 				    GtkWidget       *widget,
1304 				    GtkStateFlags    item_state,
1305 				    GthGridView     *grid_view)
1306 {
1307 	cairo_surface_t       *image;
1308 	GtkStyleContext       *style_context;
1309 	cairo_rectangle_int_t  frame_rect;
1310 
1311 	image = item->thumbnail;
1312 	if (image == NULL)
1313 		return;
1314 
1315 	cairo_surface_reference (image);
1316 
1317 	cairo_save (cr);
1318 	style_context = gtk_widget_get_style_context (widget);
1319 	gtk_style_context_save (style_context);
1320 
1321 #if GTK_CHECK_VERSION(3, 20, 0)
1322 	gtk_style_context_set_state (style_context, item_state);
1323 #else
1324 	gtk_style_context_remove_class (style_context, GTK_STYLE_CLASS_VIEW);
1325 	gtk_style_context_add_class (style_context, GTK_STYLE_CLASS_CELL);
1326 #endif
1327 
1328 	frame_rect = item->pixbuf_area;
1329 
1330 	if ((item->style == ITEM_STYLE_ICON)
1331             || ! (item->is_image || (item_state & GTK_STATE_FLAG_SELECTED) || (item_state == GTK_STATE_FLAG_NORMAL)))
1332 	{
1333 		/* use a gray rounded box for icons or when the original size
1334 		 * is smaller than the thumbnail size... */
1335 
1336 #if GTK_CHECK_VERSION(3, 20, 0)
1337 		gtk_style_context_save (style_context);
1338 		gtk_style_context_add_class (style_context, "icon");
1339 		gtk_render_background (style_context,
1340 				       cr,
1341 				       item->thumbnail_area.x,
1342 				       item->thumbnail_area.y,
1343 				       item->thumbnail_area.width,
1344 				       item->thumbnail_area.height);
1345 		gtk_style_context_restore (style_context);
1346 #else
1347 		GdkRGBA background_color;
1348 
1349 		gtk_style_context_get_background_color (style_context, item_state, &background_color);
1350 		gdk_cairo_set_source_rgba (cr, &background_color);
1351 
1352 		_cairo_draw_rounded_box (cr,
1353 					 item->thumbnail_area.x,
1354 					 item->thumbnail_area.y,
1355 					 item->thumbnail_area.width,
1356 					 item->thumbnail_area.height,
1357 					 4);
1358 		cairo_fill (cr);
1359 #endif
1360 	}
1361 
1362 	if (item->style == ITEM_STYLE_IMAGE) {
1363 
1364 		/* ...draw a frame with a drop-shadow effect */
1365 
1366 		_cairo_draw_thumbnail_frame (cr,
1367 					     item->thumbnail_area.x,
1368 					     item->thumbnail_area.y,
1369 					     item->thumbnail_area.width,
1370 					     item->thumbnail_area.height);
1371 	}
1372 
1373 	if (item->style == ITEM_STYLE_VIDEO) {
1374 		frame_rect = item->thumbnail_area;
1375 
1376 		_cairo_draw_film_background (cr,
1377 					     item->thumbnail_area.x,
1378 					     item->thumbnail_area.y,
1379 					     item->thumbnail_area.width,
1380 					     item->thumbnail_area.height);
1381 	}
1382 
1383 	/* thumbnail */
1384 
1385 	cairo_set_source_surface (cr, image, item->pixbuf_area.x, item->pixbuf_area.y);
1386 	cairo_rectangle (cr, item->pixbuf_area.x, item->pixbuf_area.y, item->pixbuf_area.width, item->pixbuf_area.height);
1387 	cairo_fill (cr);
1388 
1389 	if (item->style == ITEM_STYLE_VIDEO) {
1390 		_cairo_draw_film_foreground (cr,
1391 					     item->thumbnail_area.x,
1392 					     item->thumbnail_area.y,
1393 					     item->thumbnail_area.width,
1394 					     item->thumbnail_area.height,
1395 					     grid_view->priv->thumbnail_size);
1396 	}
1397 
1398 	if ((item_state & GTK_STATE_FLAG_SELECTED) || (item_state & GTK_STATE_FLAG_FOCUSED)) {
1399 #if GTK_CHECK_VERSION(3, 20, 0)
1400 		gtk_style_context_save (style_context);
1401 		gtk_style_context_add_class (style_context, "icon-effect");
1402 		gtk_render_background (style_context,
1403 				       cr,
1404 				       frame_rect.x,
1405 				       frame_rect.y,
1406 				       frame_rect.width,
1407 				       frame_rect.height);
1408 		gtk_style_context_restore (style_context);
1409 #else
1410 		GdkRGBA color;
1411 		gtk_style_context_get_background_color (style_context, item_state, &color);
1412 		cairo_set_source_rgba (cr, color.red, color.green, color.blue, 0.5);
1413 		cairo_rectangle (cr,
1414 				 frame_rect.x,
1415 				 frame_rect.y,
1416 				 frame_rect.width,
1417 				 frame_rect.height);
1418 		cairo_fill_preserve (cr);
1419 
1420 		cairo_set_line_width (cr, 2);
1421 		cairo_set_source_rgb (cr, color.red, color.green, color.blue);
1422 		cairo_stroke (cr);
1423 #endif
1424 	}
1425 
1426 	gtk_style_context_restore (style_context);
1427 	cairo_restore (cr);
1428 
1429 	cairo_surface_destroy (image);
1430 }
1431 
1432 
1433 static void
_gth_grid_view_item_draw_caption(GthGridViewItem * item,cairo_t * cr,GtkWidget * widget,GtkStateFlags item_state,PangoLayout * pango_layout,GthGridView * grid_view)1434 _gth_grid_view_item_draw_caption (GthGridViewItem *item,
1435 				  cairo_t         *cr,
1436 				  GtkWidget       *widget,
1437 				  GtkStateFlags    item_state,
1438 				  PangoLayout     *pango_layout,
1439 				  GthGridView     *grid_view)
1440 {
1441 	GtkStyleContext *style_context;
1442 	GdkRGBA          color;
1443 
1444 	if (item->caption_area.height == 0)
1445 		return;
1446 
1447 	cairo_save (cr);
1448 	style_context = gtk_widget_get_style_context (widget);
1449 	gtk_style_context_save (style_context);
1450 
1451 	gtk_style_context_get_color (style_context, item_state, &color);
1452 	gdk_cairo_set_source_rgba (cr, &color);
1453 	cairo_move_to (cr, item->caption_area.x, item->caption_area.y + grid_view->priv->caption_padding);
1454 	pango_layout_set_markup (pango_layout, item->caption, -1);
1455 	pango_cairo_show_layout (cr, pango_layout);
1456 
1457 	if (item_state & GTK_STATE_FLAG_FOCUSED)
1458 		gtk_render_focus (style_context,
1459 				  cr,
1460 				  item->caption_area.x,
1461 				  item->caption_area.y,
1462 				  item->caption_area.width,
1463 				  item->caption_area.height);
1464 
1465 	gtk_style_context_restore (style_context);
1466 	cairo_restore (cr);
1467 }
1468 
1469 
1470 #define EMBLEM_SIZE 16
1471 
1472 
1473 static void
_gth_grid_view_item_draw_emblems(GthGridViewItem * item,cairo_t * cr,GtkWidget * widget,GtkStateFlags item_state,GthGridView * grid_view)1474 _gth_grid_view_item_draw_emblems (GthGridViewItem *item,
1475 				  cairo_t         *cr,
1476 				  GtkWidget       *widget,
1477 				  GtkStateFlags    item_state,
1478 				  GthGridView     *grid_view)
1479 {
1480 	GthStringList *emblems;
1481 	GList         *scan;
1482 	int            emblem_offset;
1483 
1484 	cairo_save (cr);
1485 
1486 	emblem_offset = 0;
1487 	emblems = (GthStringList *) g_file_info_get_attribute_object (item->file_data->info, GTH_FILE_ATTRIBUTE_EMBLEMS);
1488 	for (scan = gth_string_list_get_list (emblems); scan; scan = scan->next) {
1489 		char            *emblem = scan->data;
1490 		GIcon           *icon;
1491 		cairo_surface_t *image;
1492 
1493 		if (grid_view->priv->icon_cache == NULL)
1494 			grid_view->priv->icon_cache = gth_icon_cache_new (gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (grid_view))), EMBLEM_SIZE);
1495 
1496 		icon = g_themed_icon_new (emblem);
1497 		image = gth_icon_cache_get_surface (grid_view->priv->icon_cache, icon);
1498 		if (image != NULL) {
1499 			cairo_rectangle (cr,
1500 					 item->thumbnail_area.x + emblem_offset + 1,
1501 					 item->thumbnail_area.y + 1,
1502 					 cairo_image_surface_get_width (image),
1503 					 cairo_image_surface_get_height (image));
1504 			cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
1505 			cairo_fill_preserve (cr);
1506 			cairo_set_source_surface (cr, image, item->thumbnail_area.x + emblem_offset + 1, item->thumbnail_area.y + 1);
1507 			cairo_fill (cr);
1508 
1509 			cairo_surface_destroy (image);
1510 
1511 			emblem_offset += EMBLEM_SIZE;
1512 		}
1513 
1514 		g_object_unref (icon);
1515 	}
1516 
1517 	cairo_restore (cr);
1518 }
1519 
1520 
1521 static void
_gth_grid_view_draw_item(GthGridView * self,GthGridViewItem * item,cairo_t * cr)1522 _gth_grid_view_draw_item (GthGridView     *self,
1523 			  GthGridViewItem *item,
1524 			  cairo_t         *cr)
1525 {
1526 	GtkStateFlags item_state;
1527 
1528 	item_state = item->state;
1529 	if (! gtk_widget_has_focus (GTK_WIDGET (self)) && (item_state & GTK_STATE_FLAG_FOCUSED))
1530 		item_state ^= GTK_STATE_FLAG_FOCUSED;
1531 	if (! gtk_widget_has_focus (GTK_WIDGET (self)) && (item_state & GTK_STATE_FLAG_ACTIVE))
1532 		item_state ^= GTK_STATE_FLAG_ACTIVE;
1533 
1534 	if (item_state ^ GTK_STATE_FLAG_NORMAL) {
1535 		GtkStyleContext *style_context;
1536 
1537 		cairo_save (cr);
1538 		style_context = gtk_widget_get_style_context (GTK_WIDGET (self));
1539 		gtk_style_context_save (style_context);
1540 
1541 #if GTK_CHECK_VERSION(3, 20, 0)
1542 		gtk_style_context_set_state (style_context, item_state);
1543 
1544 		if (item->style != ITEM_STYLE_ICON) {
1545 			cairo_region_t		 *area;
1546 			cairo_rectangle_int_t	  extents;
1547 
1548 			area = cairo_region_create_rectangle (&item->thumbnail_area);
1549 			cairo_region_union_rectangle (area, &item->caption_area);
1550 			cairo_region_get_extents (area, &extents);
1551 
1552 			gtk_render_background (style_context,
1553 					       cr,
1554 					       extents.x - self->priv->cell_padding,
1555 					       extents.y - self->priv->cell_padding,
1556 					       extents.width + (self->priv->cell_padding * 2),
1557 					       extents.height + (self->priv->cell_padding * 2));
1558 
1559 			cairo_region_destroy (area);
1560 		}
1561 		else
1562 			gtk_render_background (style_context,
1563 					       cr,
1564 					       item->area.x,
1565 					       item->area.y,
1566 					       item->area.width,
1567 					       item->area.height);
1568 #else
1569 		GdkRGBA color;
1570 
1571 		gtk_style_context_remove_class (style_context, GTK_STYLE_CLASS_VIEW);
1572 		gtk_style_context_add_class (style_context, GTK_STYLE_CLASS_CELL);
1573 		gtk_style_context_set_state (style_context, item_state);
1574 		gtk_style_context_get_background_color (style_context, item_state, &color);
1575 		_gdk_rgba_lighter (&color, &color);
1576 		cairo_set_source_rgba (cr, color.red, color.green, color.blue, color.alpha);
1577 
1578 		if (item->style != ITEM_STYLE_ICON) {
1579 			cairo_region_t		 *area;
1580 			cairo_rectangle_int_t	  extents;
1581 
1582 			area = cairo_region_create_rectangle (&item->thumbnail_area);
1583 			cairo_region_union_rectangle (area, &item->caption_area);
1584 			cairo_region_get_extents (area, &extents);
1585 
1586 			_cairo_draw_rounded_box (cr,
1587 						 extents.x - self->priv->cell_padding,
1588 						 extents.y - self->priv->cell_padding,
1589 						 extents.width + (self->priv->cell_padding * 2),
1590 						 extents.height + (self->priv->cell_padding * 2),
1591 						 4);
1592 
1593 			cairo_region_destroy (area);
1594 		}
1595 		else
1596 			_cairo_draw_rounded_box (cr,
1597 						 item->area.x,
1598 						 item->area.y,
1599 						 item->area.width,
1600 						 item->area.height,
1601 						 4);
1602 
1603 		cairo_fill (cr);
1604 #endif
1605 
1606 		gtk_style_context_restore (style_context);
1607 		cairo_restore (cr);
1608 	}
1609 
1610 	_gth_grid_view_item_draw_thumbnail (item, cr, GTK_WIDGET (self), item_state, self);
1611 	_gth_grid_view_item_draw_caption (item, cr, GTK_WIDGET (self), item_state, self->priv->caption_layout, self);
1612 	_gth_grid_view_item_draw_emblems (item, cr, GTK_WIDGET (self), item_state, self);
1613 }
1614 
1615 
1616 static void
_gth_grid_view_draw_rubberband(GthGridView * self,cairo_t * cr)1617 _gth_grid_view_draw_rubberband (GthGridView *self,
1618 		  	  	cairo_t     *cr)
1619 {
1620 	GtkStyleContext *style_context;
1621 
1622 	if ((self->priv->selection_area.width <= 1.0) || (self->priv->selection_area.height <= 1.0))
1623 		return;
1624 
1625 	cairo_save (cr);
1626 
1627 	style_context = gtk_widget_get_style_context (GTK_WIDGET (self));
1628 	gtk_style_context_save (style_context);
1629 	gtk_style_context_add_class (style_context, GTK_STYLE_CLASS_RUBBERBAND);
1630 
1631 	gdk_cairo_rectangle (cr, &self->priv->selection_area);
1632 	cairo_clip (cr);
1633 	gtk_render_background (style_context,
1634 			       cr,
1635 			       self->priv->selection_area.x,
1636 			       self->priv->selection_area.y,
1637 			       self->priv->selection_area.width,
1638 			       self->priv->selection_area.height);
1639 	gtk_render_frame (style_context,
1640 			  cr,
1641 			  self->priv->selection_area.x,
1642 			  self->priv->selection_area.y,
1643 			  self->priv->selection_area.width,
1644 			  self->priv->selection_area.height);
1645 
1646 	gtk_style_context_restore (style_context);
1647 	cairo_restore (cr);
1648 }
1649 
1650 
1651 static void
_gth_grid_view_draw_drop_target(GthGridView * self,cairo_t * cr)1652 _gth_grid_view_draw_drop_target (GthGridView *self,
1653 				 cairo_t     *cr)
1654 {
1655 	GtkStyleContext *style_context;
1656 	GthGridViewItem *item;
1657 	int              x;
1658 
1659 	if ((self->priv->drop_item < 0) || (self->priv->drop_item >= self->priv->n_items))
1660 		return;
1661 
1662 	style_context = gtk_widget_get_style_context (GTK_WIDGET (self));
1663 
1664 	item = g_list_nth (self->priv->items, self->priv->drop_item)->data;
1665 
1666 	x = 0;
1667 	if (self->priv->drop_pos == GTH_DROP_POSITION_LEFT)
1668 		x = item->area.x - (self->priv->cell_x_spacing / 2);
1669 	else if (self->priv->drop_pos == GTH_DROP_POSITION_RIGHT)
1670 		x = item->area.x + self->priv->cell_size + (self->priv->cell_x_spacing / 2);
1671 
1672 	gtk_render_focus (style_context,
1673 			   cr,
1674 			   x - 1,
1675 			   item->area.y + self->priv->cell_padding,
1676 			   2,
1677 			   item->area.height - (self->priv->cell_padding * 2));
1678 }
1679 
1680 
1681 static void
_gth_grid_draw_background(GthGridView * self,cairo_t * cr)1682 _gth_grid_draw_background (GthGridView *self,
1683 		 	   cairo_t     *cr)
1684 {
1685 	GtkAllocation allocation;
1686 
1687 	gtk_widget_get_allocation (GTK_WIDGET (self), &allocation);
1688 	gtk_render_background (gtk_widget_get_style_context (GTK_WIDGET (self)),
1689 			       cr,
1690 			       0,
1691 			       gtk_adjustment_get_value (self->priv->vadjustment),
1692 			       allocation.width,
1693 			       allocation.height);
1694 }
1695 
1696 
1697 static gboolean
gth_grid_view_draw(GtkWidget * widget,cairo_t * cr)1698 gth_grid_view_draw (GtkWidget *widget,
1699 		    cairo_t   *cr)
1700 {
1701 	GthGridView *self = (GthGridView*) widget;
1702 	int          first_visible;
1703 
1704 	if (! gtk_cairo_should_draw_window (cr, self->priv->bin_window))
1705 		return FALSE;
1706 
1707 	cairo_save (cr);
1708 	gtk_cairo_transform_to_window (cr, widget, self->priv->bin_window);
1709 	cairo_set_line_width (cr, 1.0);
1710 
1711 	_gth_grid_draw_background (self, cr);
1712 
1713 	first_visible = gth_grid_view_get_first_visible (GTH_FILE_VIEW (self));
1714 	if (first_visible >= 0) {
1715 		int    last_visible;
1716 		int    i;
1717 		GList *scan;
1718 
1719 		last_visible = gth_grid_view_get_last_visible (GTH_FILE_VIEW (self));
1720 
1721 		for (i = first_visible, scan = g_list_nth (self->priv->items, first_visible);
1722 		     (i <= last_visible) && scan;
1723 		     i++, scan = scan->next)
1724 		{
1725 			_gth_grid_view_draw_item (self, GTH_GRID_VIEW_ITEM (scan->data), cr);
1726 		}
1727 
1728 		if (self->priv->selecting || self->priv->multi_selecting_with_keyboard)
1729 			_gth_grid_view_draw_rubberband (self, cr);
1730 
1731 		if (self->priv->drop_pos != GTH_DROP_POSITION_NONE)
1732 			_gth_grid_view_draw_drop_target (self, cr);
1733 	}
1734 
1735 	cairo_restore (cr);
1736 
1737 	return TRUE;
1738 }
1739 
1740 
1741 static gboolean
gth_grid_view_key_press(GtkWidget * widget,GdkEventKey * event)1742 gth_grid_view_key_press (GtkWidget   *widget,
1743 			  GdkEventKey *event)
1744 {
1745 	GthGridView *self = GTH_GRID_VIEW (widget);
1746 	gboolean     handled;
1747 
1748 	if (! self->priv->multi_selecting_with_keyboard
1749 	    && (event->state & GDK_SHIFT_MASK)
1750 	    && ((event->keyval == GDK_KEY_Left)
1751 		|| (event->keyval == GDK_KEY_Right)
1752 		|| (event->keyval == GDK_KEY_Up)
1753 		|| (event->keyval == GDK_KEY_Down)
1754 		|| (event->keyval == GDK_KEY_Page_Up)
1755 		|| (event->keyval == GDK_KEY_Page_Down)
1756 		|| (event->keyval == GDK_KEY_Home)
1757 		|| (event->keyval == GDK_KEY_End)))
1758 	{
1759 		self->priv->multi_selecting_with_keyboard = TRUE;
1760 		self->priv->first_focused_item = self->priv->focused_item;
1761 
1762 		self->priv->selection_area.x = 0;
1763 		self->priv->selection_area.y = 0;
1764 		self->priv->selection_area.width = 0;
1765 		self->priv->selection_area.height = 0;
1766 	}
1767 
1768 	handled = gtk_bindings_activate (G_OBJECT (widget),
1769 					 event->keyval,
1770 					 event->state);
1771 
1772 	if (handled)
1773 		return TRUE;
1774 
1775 	if ((GTK_WIDGET_CLASS (gth_grid_view_parent_class)->key_press_event != NULL)
1776 	    && GTK_WIDGET_CLASS (gth_grid_view_parent_class)->key_press_event (widget, event))
1777 	{
1778 		return TRUE;
1779 	}
1780 
1781 	return FALSE;
1782 }
1783 
1784 
1785 static gboolean
gth_grid_view_key_release(GtkWidget * widget,GdkEventKey * event)1786 gth_grid_view_key_release (GtkWidget   *widget,
1787 			   GdkEventKey *event)
1788 {
1789 	GthGridView *self = GTH_GRID_VIEW (widget);
1790 
1791 	if (self->priv->multi_selecting_with_keyboard
1792 	    && (event->state & GDK_SHIFT_MASK)
1793 	    && ((event->keyval == GDK_KEY_Shift_L)
1794 		|| (event->keyval == GDK_KEY_Shift_R)))
1795 	{
1796 		self->priv->multi_selecting_with_keyboard = FALSE;
1797 	}
1798 
1799 	gtk_widget_queue_draw (widget);
1800 
1801 	if ((GTK_WIDGET_CLASS (gth_grid_view_parent_class)->key_press_event != NULL)
1802 	    && GTK_WIDGET_CLASS (gth_grid_view_parent_class)->key_press_event (widget, event))
1803 	{
1804 		return TRUE;
1805 	}
1806 
1807 	return FALSE;
1808 }
1809 
1810 
1811 /* -- GthFileSelection interface -- */
1812 
1813 
1814 static void
gth_grid_view_set_selection_mode(GthFileSelection * selection,GtkSelectionMode mode)1815 gth_grid_view_set_selection_mode (GthFileSelection *selection,
1816 				  GtkSelectionMode  mode)
1817 {
1818 	GthGridView *self = GTH_GRID_VIEW (selection);
1819 
1820 	self->priv->selection_mode = mode;
1821 
1822 	/* the cell padding is used to show the selection, set it to 0 if
1823 	 * selection is not allowed. */
1824 	self->priv->cell_padding = (mode == GTK_SELECTION_NONE) ? 0 : DEFAULT_CELL_PADDING;
1825 }
1826 
1827 
1828 static GList *
gth_grid_view_get_selected(GthFileSelection * selection)1829 gth_grid_view_get_selected (GthFileSelection *selection)
1830 {
1831 	GthGridView *self = GTH_GRID_VIEW (selection);
1832 	GList       *selected;
1833 	GList       *scan;
1834 
1835 	selected = NULL;
1836 	for (scan = self->priv->selection; scan; scan = scan->next) {
1837 		int pos;
1838 
1839 		pos = GPOINTER_TO_INT (scan->data);
1840 		selected = g_list_prepend (selected, gtk_tree_path_new_from_indices (pos, -1));
1841 	}
1842 
1843 	return g_list_reverse (selected);
1844 }
1845 
1846 
1847 static void
_gth_grid_view_queue_draw_item(GthGridView * self,GthGridViewItem * item)1848 _gth_grid_view_queue_draw_item (GthGridView     *self,
1849 				GthGridViewItem *item)
1850 {
1851 	if (gtk_widget_get_realized (GTK_WIDGET (self)))
1852 		gdk_window_invalidate_rect (self->priv->bin_window, &item->area, FALSE);
1853 }
1854 
1855 
1856 static void
_gth_grid_view_select_item(GthGridView * self,int pos)1857 _gth_grid_view_select_item (GthGridView *self,
1858 			    int          pos)
1859 {
1860 	GList           *link;
1861 	GthGridViewItem *item;
1862 
1863 	g_return_if_fail ((pos >= 0) && (pos < self->priv->n_items));
1864 
1865 	if (self->priv->selection_mode == GTK_SELECTION_NONE)
1866 		return;
1867 
1868 	link = g_list_nth (self->priv->items, pos);
1869 	g_return_if_fail (link != NULL);
1870 
1871 	item = link->data;
1872 	if (item->state & GTK_STATE_FLAG_SELECTED)
1873 		return;
1874 
1875 	item->state |= GTK_STATE_FLAG_SELECTED;
1876 	self->priv->selection = g_list_prepend (self->priv->selection, GINT_TO_POINTER (pos));
1877 	self->priv->selection_changed = TRUE;
1878 
1879 	_gth_grid_view_queue_draw_item (self, item);
1880 }
1881 
1882 
1883 static void
_gth_grid_view_unselect_item(GthGridView * self,int pos)1884 _gth_grid_view_unselect_item (GthGridView *self,
1885 		     	      int          pos)
1886 {
1887 	GList           *link;
1888 	GthGridViewItem *item;
1889 
1890 	g_return_if_fail ((pos >= 0) && (pos < self->priv->n_items));
1891 
1892 	if (self->priv->selection_mode == GTK_SELECTION_NONE)
1893 		return;
1894 
1895 	link = g_list_nth (self->priv->items, pos);
1896 	g_return_if_fail (link != NULL);
1897 
1898 	item = link->data;
1899 
1900 	if (! (item->state & GTK_STATE_FLAG_SELECTED))
1901 		return;
1902 
1903 	item->state ^= GTK_STATE_FLAG_SELECTED;
1904 	self->priv->selection = g_list_remove (self->priv->selection, GINT_TO_POINTER (pos));
1905 	self->priv->selection_changed = TRUE;
1906 
1907 	_gth_grid_view_queue_draw_item (self, item);
1908 }
1909 
1910 
1911 static void
_gth_grid_view_set_item_selected(GthGridView * self,gboolean selected,int pos)1912 _gth_grid_view_set_item_selected (GthGridView *self,
1913 				  gboolean     selected,
1914 				  int          pos)
1915 {
1916 	if (selected)
1917 		_gth_grid_view_select_item (self, pos);
1918 	else
1919 		_gth_grid_view_unselect_item (self, pos);
1920 }
1921 
1922 
1923 static void
_gth_grid_view_emit_selection_changed(GthGridView * self)1924 _gth_grid_view_emit_selection_changed (GthGridView *self)
1925 {
1926 	if (self->priv->selection_changed && (self->priv->selection_mode != GTK_SELECTION_NONE)) {
1927 		gth_file_selection_changed (GTH_FILE_SELECTION (self));
1928 		self->priv->selection_changed = FALSE;
1929 	}
1930 }
1931 
1932 
1933 static void
_gth_grid_view_set_item_selected_and_emit_signal(GthGridView * self,gboolean select,int pos)1934 _gth_grid_view_set_item_selected_and_emit_signal (GthGridView *self,
1935 						  gboolean     select,
1936 						  int          pos)
1937 {
1938 	_gth_grid_view_set_item_selected (self, select, pos);
1939 	_gth_grid_view_emit_selection_changed (self);
1940 }
1941 
1942 
1943 static int
_gth_grid_view_unselect_all(GthGridView * self,gpointer keep_selected)1944 _gth_grid_view_unselect_all (GthGridView *self,
1945 			     gpointer     keep_selected)
1946 {
1947 	GthGridViewPrivate *priv = self->priv;
1948 	int                 idx;
1949 	GList              *scan;
1950 	int                 i;
1951 
1952 	idx = 0;
1953 	for (scan = priv->items, i = 0;
1954 	     scan != NULL;
1955 	     scan = scan->next, i++)
1956 	{
1957 		GthGridViewItem *item = scan->data;
1958 
1959 		if (item == keep_selected)
1960 			idx = i;
1961 		else if (item->state & GTK_STATE_FLAG_SELECTED)
1962 			_gth_grid_view_set_item_selected (self, FALSE, i);
1963 	}
1964 
1965 	return idx;
1966 }
1967 
1968 
1969 static void
gth_grid_view_select(GthFileSelection * selection,int pos)1970 gth_grid_view_select (GthFileSelection *selection,
1971 		      int               pos)
1972 {
1973 	GthGridView *self = GTH_GRID_VIEW (selection);
1974 	GList       *list;
1975 	int          i;
1976 
1977 	switch (self->priv->selection_mode) {
1978 	case GTK_SELECTION_SINGLE:
1979 		for (list = self->priv->items, i = 0;
1980 		     list != NULL;
1981 		     list = list->next, i++)
1982 		{
1983 			GthGridViewItem *item = list->data;
1984 
1985 			if ((i != pos) && (item->state & GTK_STATE_FLAG_SELECTED))
1986 				_gth_grid_view_set_item_selected (self, FALSE, i);
1987 		}
1988 		_gth_grid_view_set_item_selected (self, TRUE, pos);
1989 		_gth_grid_view_emit_selection_changed (self);
1990 		break;
1991 
1992 	case GTK_SELECTION_MULTIPLE:
1993 		self->priv->select_pending = FALSE;
1994 
1995 		_gth_grid_view_set_item_selected_and_emit_signal (self, TRUE, pos);
1996 		self->priv->last_selected_pos = pos;
1997 		break;
1998 
1999 	default:
2000 		break;
2001 	}
2002 }
2003 
2004 
2005 static void
gth_grid_view_unselect(GthFileSelection * selection,int pos)2006 gth_grid_view_unselect (GthFileSelection *selection,
2007 		        int               pos)
2008 {
2009 	_gth_grid_view_set_item_selected_and_emit_signal (GTH_GRID_VIEW (selection), FALSE, pos);
2010 }
2011 
2012 
2013 static void
_gth_grid_view_select_all(GthGridView * self)2014 _gth_grid_view_select_all (GthGridView *self)
2015 {
2016 	GList *scan;
2017 	int    i;
2018 
2019 	for (scan = self->priv->items, i = 0;
2020 	     scan != NULL;
2021 	     scan = scan->next, i++)
2022 	{
2023 		GthGridViewItem *item = scan->data;
2024 
2025 		if (! (item->state & GTK_STATE_FLAG_SELECTED))
2026 			_gth_grid_view_set_item_selected (self, TRUE, i);
2027 	}
2028 }
2029 
2030 
2031 static void
gth_grid_view_select_all(GthFileSelection * selection)2032 gth_grid_view_select_all (GthFileSelection *selection)
2033 {
2034 	GthGridView *self = GTH_GRID_VIEW (selection);
2035 
2036 	_gth_grid_view_select_all (self);
2037 	_gth_grid_view_emit_selection_changed (self);
2038 }
2039 
2040 
2041 static void
gth_grid_view_unselect_all(GthFileSelection * selection)2042 gth_grid_view_unselect_all (GthFileSelection *selection)
2043 {
2044 	GthGridView *self = GTH_GRID_VIEW (selection);
2045 
2046 	_gth_grid_view_unselect_all (self, NULL);
2047 	_gth_grid_view_emit_selection_changed (self);
2048 }
2049 
2050 
2051 static gboolean
gth_grid_view_is_selected(GthFileSelection * selection,int pos)2052 gth_grid_view_is_selected (GthFileSelection *selection,
2053 			   int               pos)
2054 {
2055 	GthGridView *self = GTH_GRID_VIEW (selection);
2056 	GList       *scan;
2057 
2058 	for (scan = self->priv->selection; scan; scan = scan->next)
2059 		if (GPOINTER_TO_INT (scan->data) == pos)
2060 			return TRUE;
2061 
2062 	return FALSE;
2063 }
2064 
2065 
2066 static GtkTreePath *
gth_grid_view_get_first_selected(GthFileSelection * selection)2067 gth_grid_view_get_first_selected (GthFileSelection *selection)
2068 {
2069 	GthGridView *self = GTH_GRID_VIEW (selection);
2070 	GList       *scan;
2071 	int          pos;
2072 
2073 	scan = self->priv->selection;
2074 	if (scan == NULL)
2075 		return NULL;
2076 
2077 	pos = GPOINTER_TO_INT (scan->data);
2078 	for (scan = scan->next; scan; scan = scan->next)
2079 		pos = MIN (pos, GPOINTER_TO_INT (scan->data));
2080 
2081 	return gtk_tree_path_new_from_indices (pos, -1);
2082 }
2083 
2084 
2085 static GtkTreePath *
gth_grid_view_get_last_selected(GthFileSelection * selection)2086 gth_grid_view_get_last_selected (GthFileSelection *selection)
2087 {
2088 	GthGridView *self = GTH_GRID_VIEW (selection);
2089 	GList       *scan;
2090 	int          pos;
2091 
2092 	scan = self->priv->selection;
2093 	if (scan == NULL)
2094 		return NULL;
2095 
2096 	pos = GPOINTER_TO_INT (scan->data);
2097 	for (scan = scan->next; scan; scan = scan->next)
2098 		pos = MAX (pos, GPOINTER_TO_INT (scan->data));
2099 
2100 	return gtk_tree_path_new_from_indices (pos, -1);
2101 }
2102 
2103 
2104 static guint
gth_grid_view_get_n_selected(GthFileSelection * selection)2105 gth_grid_view_get_n_selected (GthFileSelection *selection)
2106 {
2107 	return g_list_length (GTH_GRID_VIEW (selection)->priv->selection);
2108 }
2109 
2110 
2111 /* -- GthFileView interface -- */
2112 
2113 
2114 static void
model_row_changed_cb(GtkTreeModel * tree_model,GtkTreePath * path,GtkTreeIter * iter,gpointer user_data)2115 model_row_changed_cb (GtkTreeModel *tree_model,
2116 		      GtkTreePath  *path,
2117 		      GtkTreeIter  *iter,
2118 		      gpointer      user_data)
2119 {
2120 	GthGridView     *self = user_data;
2121 	int              pos;
2122 	GList           *link;
2123 	GthFileData     *file_data;
2124 	cairo_surface_t *thumbnail;
2125 	gboolean         is_icon;
2126 	GthGridViewItem *item;
2127 
2128 	gtk_tree_model_get (tree_model,
2129 			    iter,
2130 			    GTH_FILE_STORE_FILE_DATA_COLUMN, &file_data,
2131 			    GTH_FILE_STORE_THUMBNAIL_COLUMN, &thumbnail,
2132 			    GTH_FILE_STORE_IS_ICON_COLUMN, &is_icon,
2133 			    -1);
2134 
2135 	pos = gtk_tree_path_get_indices (path)[0];
2136 	link = g_list_nth (self->priv->items, pos);
2137 	g_return_if_fail (link != NULL);
2138 
2139 	item = GTH_GRID_VIEW_ITEM (link->data);
2140 	gth_grid_view_item_set_file_data (item, file_data);
2141 	gth_grid_view_item_set_thumbnail (item, thumbnail);
2142 	item->is_icon = is_icon;
2143 	gth_grid_view_item_update_caption (item, self->priv->caption_attributes_v);
2144 
2145 	_gth_grid_view_queue_relayout_from_position (self, pos);
2146 
2147 	g_object_unref (file_data);
2148 	cairo_surface_destroy (thumbnail);
2149 }
2150 
2151 
2152 static void
model_row_deleted_cb(GtkTreeModel * tree_model,GtkTreePath * path,gpointer user_data)2153 model_row_deleted_cb (GtkTreeModel *tree_model,
2154 		      GtkTreePath  *path,
2155 		      gpointer      user_data)
2156 {
2157 	GthGridView *self = user_data;
2158 	int          pos;
2159 	GList       *link;
2160 	GList       *scan;
2161 	GList       *selected_link;
2162 
2163 	pos = gtk_tree_path_get_indices (path)[0];
2164 	link = g_list_nth (self->priv->items, pos);
2165 	self->priv->items = g_list_remove_link (self->priv->items, link);
2166 	self->priv->n_items--;
2167 
2168 	/* update the selection */
2169 
2170 	selected_link = NULL;
2171 	for (scan = self->priv->selection; scan; scan = scan->next) {
2172 		int selected_pos = GPOINTER_TO_INT (scan->data);
2173 		if (selected_pos > pos)
2174 			scan->data = GINT_TO_POINTER (selected_pos - 1);
2175 		else if (selected_pos == pos)
2176 			selected_link = scan;
2177 	}
2178 	if (selected_link != NULL) {
2179 		self->priv->selection = g_list_remove_link (self->priv->selection, selected_link);
2180 		g_list_free (selected_link);
2181 	}
2182 
2183 	/* update the cursor position */
2184 
2185 	if (pos < self->priv->focused_item)
2186 		self->priv->focused_item--;
2187 	else if (pos == self->priv->focused_item)
2188 		self->priv->focused_item = -1;
2189 
2190 	/* update the last selected position */
2191 
2192 	if (pos < self->priv->last_selected_pos)
2193 		self->priv->last_selected_pos--;
2194 	else if (pos == self->priv->last_selected_pos)
2195 		self->priv->last_selected_pos = -1;
2196 
2197 	/* relayout from the minimum changed position */
2198 
2199 	_gth_grid_view_queue_relayout_from_position (self, pos);
2200 
2201 	gth_grid_view_item_unref (GTH_GRID_VIEW_ITEM (link->data));
2202 	g_list_free (link);
2203 }
2204 
2205 
2206 static void
model_row_inserted_cb(GtkTreeModel * tree_model,GtkTreePath * path,GtkTreeIter * iter,gpointer user_data)2207 model_row_inserted_cb (GtkTreeModel *tree_model,
2208 		       GtkTreePath  *path,
2209 		       GtkTreeIter  *iter,
2210 		       gpointer      user_data)
2211 {
2212 	GthGridView     *self = user_data;
2213 	GthFileData     *file_data;
2214 	cairo_surface_t *thumbnail;
2215 	gboolean         is_icon;
2216 	GthGridViewItem *item;
2217 	int              pos;
2218 	GList           *scan;
2219 
2220 	gtk_tree_model_get (tree_model,
2221 			    iter,
2222 			    GTH_FILE_STORE_FILE_DATA_COLUMN, &file_data,
2223 			    GTH_FILE_STORE_THUMBNAIL_COLUMN, &thumbnail,
2224 			    GTH_FILE_STORE_IS_ICON_COLUMN, &is_icon,
2225 			    -1);
2226 	item = gth_grid_view_item_new (self,
2227 				       file_data,
2228 				       thumbnail,
2229 				       is_icon,
2230 				       self->priv->caption_attributes_v);
2231 	pos = gtk_tree_path_get_indices (path)[0];
2232 	self->priv->items = g_list_insert (self->priv->items, item, pos);
2233 	self->priv->n_items++;
2234 
2235 	/* update the selection */
2236 
2237 	for (scan = self->priv->selection; scan; scan = scan->next) {
2238 		int selected_pos = GPOINTER_TO_INT (scan->data);
2239 		if (selected_pos >= pos)
2240 			scan->data = GINT_TO_POINTER (selected_pos + 1);
2241 	}
2242 
2243 	/* update the cursor position */
2244 
2245 	if (pos <= self->priv->focused_item)
2246 		self->priv->focused_item++;
2247 
2248 	/* update the last selected position */
2249 
2250 	if (pos <= self->priv->last_selected_pos)
2251 		self->priv->last_selected_pos++;
2252 
2253 	/* relayout from the minimum changed position */
2254 
2255 	_gth_grid_view_queue_relayout_from_position (self, pos);
2256 
2257 	g_object_unref (file_data);
2258 	cairo_surface_destroy (thumbnail);
2259 }
2260 
2261 
2262 static void
model_rows_reordered_cb(GtkTreeModel * tree_model,GtkTreePath * path,GtkTreeIter * iter,gpointer new_order,gpointer user_data)2263 model_rows_reordered_cb (GtkTreeModel *tree_model,
2264 			 GtkTreePath  *path,
2265 			 GtkTreeIter  *iter,
2266 			 gpointer      new_order,
2267 			 gpointer      user_data)
2268 {
2269 	GthGridView *self = user_data;
2270 	GList       *items;
2271 	int          i;
2272 	int          min_changed_pos;
2273 	gboolean     focused_updated = FALSE;
2274 	GList       *scan;
2275 
2276 	/* change the order of the items list */
2277 
2278 	min_changed_pos = -1;
2279 	items = NULL;
2280 	for (i = 0; i < self->priv->n_items; i++) {
2281 		GList *link;
2282 		int    old_pos;
2283 
2284 		old_pos = ((int *) new_order)[i];
2285 		if ((min_changed_pos == -1) && (old_pos != i))
2286 			min_changed_pos = i;
2287 
2288 		/* update the cursor position */
2289 
2290 		if (! focused_updated && (old_pos == self->priv->focused_item)) {
2291 			self->priv->focused_item = i;
2292 			focused_updated = TRUE;
2293 		}
2294 
2295 		link = g_list_nth (self->priv->items, old_pos);
2296 		g_return_if_fail (link != NULL);
2297 		items = g_list_prepend (items, link->data);
2298 	}
2299 	items = g_list_reverse (items);
2300 
2301 	g_list_free (self->priv->items);
2302 	self->priv->items = items;
2303 
2304 	/* update the selection */
2305 
2306 	for (scan = self->priv->selection; scan; scan = scan->next) {
2307 		int selected_pos = GPOINTER_TO_INT (scan->data);
2308 
2309 		for (i = 0; i < self->priv->n_items; i++) {
2310 			int old_pos = ((int *) new_order)[i];
2311 
2312 			if (selected_pos == old_pos) {
2313 				scan->data = GINT_TO_POINTER (i);
2314 				break;
2315 			}
2316 		}
2317 	}
2318 
2319 	/* relayout from the minimum changed position */
2320 
2321 	if (min_changed_pos >= 0)
2322 		_gth_grid_view_queue_relayout_from_position (self, min_changed_pos);
2323 }
2324 
2325 
2326 static void
model_thumbnail_changed_cb(GtkTreeModel * tree_model,GtkTreePath * path,GtkTreeIter * iter,gpointer user_data)2327 model_thumbnail_changed_cb (GtkTreeModel *tree_model,
2328 		      	    GtkTreePath  *path,
2329 		      	    GtkTreeIter  *iter,
2330 		      	    gpointer      user_data)
2331 {
2332 	GthGridView     *self = user_data;
2333 	int              pos;
2334 	GList           *link;
2335 	cairo_surface_t *thumbnail;
2336 	gboolean         is_icon;
2337 	GthGridViewItem *item;
2338 
2339 	gtk_tree_model_get (tree_model,
2340 			    iter,
2341 			    GTH_FILE_STORE_THUMBNAIL_COLUMN, &thumbnail,
2342 			    GTH_FILE_STORE_IS_ICON_COLUMN, &is_icon,
2343 			    -1);
2344 
2345 	pos = gtk_tree_path_get_indices (path)[0];
2346 	link = g_list_nth (self->priv->items, pos);
2347 	g_return_if_fail (link != NULL);
2348 
2349 	item = GTH_GRID_VIEW_ITEM (link->data);
2350 	gth_grid_view_item_set_thumbnail (item, thumbnail);
2351 	item->is_icon = is_icon;
2352 
2353 	_gth_grid_view_update_item_size (self, item);
2354 	_gth_grid_view_place_item_at (self, item, item->area.x, item->area.y);
2355 	_gth_grid_view_queue_draw_item (self, item);
2356 
2357 	cairo_surface_destroy (thumbnail);
2358 }
2359 
2360 
2361 static void
gth_grid_view_set_model(GthFileView * file_view,GtkTreeModel * model)2362 gth_grid_view_set_model (GthFileView  *file_view,
2363 			 GtkTreeModel *model)
2364 {
2365 	GthGridView *self = GTH_GRID_VIEW (file_view);
2366 
2367 	if (model != NULL)
2368 		g_object_ref (model);
2369 	if (self->priv->model != NULL) {
2370 		_g_signal_handlers_disconnect_by_data (self->priv->model, self);
2371 		g_object_unref (self->priv->model);
2372 	}
2373 	self->priv->model = model;
2374 	g_object_notify (G_OBJECT (self), "model");
2375 
2376 	if (self->priv->model == NULL)
2377 		return;
2378 
2379 	g_signal_connect (self->priv->model,
2380 			  "row-changed",
2381 			  G_CALLBACK (model_row_changed_cb),
2382 			  self);
2383 	g_signal_connect (self->priv->model,
2384 			  "row-deleted",
2385 			  G_CALLBACK (model_row_deleted_cb),
2386 			  self);
2387 	g_signal_connect (self->priv->model,
2388 			  "row-inserted",
2389 			  G_CALLBACK (model_row_inserted_cb),
2390 			  self);
2391 	g_signal_connect (self->priv->model,
2392 			  "rows-reordered",
2393 			  G_CALLBACK (model_rows_reordered_cb),
2394 			  self);
2395 	g_signal_connect (self->priv->model,
2396 			  "thumbnail-changed",
2397 			  G_CALLBACK (model_thumbnail_changed_cb),
2398 			  self);
2399 }
2400 
2401 
2402 static int
_gth_grid_view_get_at_position(GthGridView * self,int x,int y,GthGridViewItem ** item_ref)2403 _gth_grid_view_get_at_position (GthGridView      *self,
2404 			        int               x,
2405 			        int               y,
2406 			        GthGridViewItem **item_ref)
2407 {
2408 	GList *scan;
2409 	int    n;
2410 
2411 	for (scan = self->priv->items, n = 0;
2412 	     scan != NULL;
2413 	     scan = scan->next, n++)
2414 	{
2415 		GthGridViewItem *item = scan->data;
2416 
2417 		if (_cairo_rectangle_contains_point (&item->thumbnail_area, x, y)
2418 		    || _cairo_rectangle_contains_point (&item->caption_area, x, y))
2419 		{
2420 			if (item_ref != NULL)
2421 				*item_ref = item;
2422 			return n;
2423 		}
2424 	}
2425 
2426 	if (item_ref != NULL)
2427 		*item_ref = NULL;
2428 
2429 	return -1;
2430 }
2431 
2432 
2433 static int
gth_grid_view_get_at_position(GthFileView * file_view,int x,int y)2434 gth_grid_view_get_at_position (GthFileView     *file_view,
2435 			       int               x,
2436 			       int               y)
2437 {
2438 	return _gth_grid_view_get_at_position (GTH_GRID_VIEW (file_view), x, y, NULL);
2439 }
2440 
2441 
2442 static void
gth_grid_view_cursor_changed(GthFileView * file_view,int pos)2443 gth_grid_view_cursor_changed (GthFileView *file_view,
2444 			      int          pos)
2445 {
2446 	GthGridView     *self = GTH_GRID_VIEW (file_view);
2447 	GList           *link;
2448 	GthGridViewItem *new_item;
2449 
2450 	if (pos != self->priv->focused_item) {
2451 		GthGridViewItem *old_item = NULL;
2452 
2453 		if (self->priv->focused_item >= 0) {
2454 			link = g_list_nth (self->priv->items, self->priv->focused_item);
2455 			if (link != NULL)
2456 				old_item = link->data;
2457 		}
2458 		if (old_item != NULL) {
2459 			old_item->state ^= GTK_STATE_FLAG_FOCUSED | GTK_STATE_FLAG_ACTIVE;
2460 			_gth_grid_view_queue_draw_item (self, old_item);
2461 		}
2462 	}
2463 
2464 	link = g_list_nth (self->priv->items, pos);
2465 	g_return_if_fail (link != NULL);
2466 
2467 	self->priv->focused_item = pos;
2468 
2469 	new_item = link->data;
2470 	new_item->state |= GTK_STATE_FLAG_FOCUSED | GTK_STATE_FLAG_ACTIVE;
2471 	_gth_grid_view_queue_draw_item (self, new_item);
2472 
2473 	self->priv->make_focused_visible = TRUE;
2474 	_gth_grid_view_make_item_fully_visible (self, self->priv->focused_item);
2475 }
2476 
2477 
2478 static int
gth_grid_view_get_cursor(GthFileView * file_view)2479 gth_grid_view_get_cursor (GthFileView *file_view)
2480 {
2481 	GthGridView *self = GTH_GRID_VIEW (file_view);
2482 
2483 	if (! gtk_widget_has_focus (GTK_WIDGET (self)))
2484 		return -1;
2485 	else
2486 		return self->priv->focused_item;
2487 }
2488 
2489 
2490 static void
gth_grid_view_enable_drag_source(GthFileView * file_view,GdkModifierType start_button_mask,const GtkTargetEntry * targets,int n_targets,GdkDragAction actions)2491 gth_grid_view_enable_drag_source (GthFileView          *file_view,
2492 				  GdkModifierType       start_button_mask,
2493 				  const GtkTargetEntry *targets,
2494 				  int                   n_targets,
2495 				  GdkDragAction         actions)
2496 {
2497 	GthGridView *self = GTH_GRID_VIEW (file_view);
2498 
2499 	if (self->priv->drag_target_list != NULL)
2500 		gtk_target_list_unref (self->priv->drag_target_list);
2501 
2502 	self->priv->drag_source_enabled = TRUE;
2503 	self->priv->drag_start_button_mask = start_button_mask;
2504 	self->priv->drag_target_list = gtk_target_list_new (targets, n_targets);
2505 	self->priv->drag_actions = actions;
2506 }
2507 
2508 
2509 static void
gth_grid_view_unset_drag_source(GthFileView * file_view)2510 gth_grid_view_unset_drag_source (GthFileView *file_view)
2511 {
2512 	GTH_GRID_VIEW (file_view)->priv->drag_source_enabled = FALSE;
2513 }
2514 
2515 
2516 static void
gth_grid_view_enable_drag_dest(GthFileView * file_view,const GtkTargetEntry * targets,int n_targets,GdkDragAction actions)2517 gth_grid_view_enable_drag_dest (GthFileView          *file_view,
2518 				const GtkTargetEntry *targets,
2519 				int                   n_targets,
2520 				GdkDragAction         actions)
2521 {
2522 	GthGridView *self = GTH_GRID_VIEW (file_view);
2523 
2524 	gtk_drag_dest_set (GTK_WIDGET (self),
2525 			   0,
2526 			   targets,
2527 			   n_targets,
2528 			   actions);
2529 }
2530 
2531 
2532 static void
gth_grid_view_unset_drag_dest(GthFileView * file_view)2533 gth_grid_view_unset_drag_dest (GthFileView *file_view)
2534 {
2535 	GthGridView *self = GTH_GRID_VIEW (file_view);
2536 
2537 	gtk_drag_dest_unset (GTK_WIDGET (self));
2538 }
2539 
2540 
2541 static int
_gth_grid_view_get_drop_target_at(GthGridView * self,int x,int y)2542 _gth_grid_view_get_drop_target_at (GthGridView *self,
2543 				   int          x,
2544 				   int          y)
2545 {
2546 	int    row;
2547 	int    height;
2548 	GList *scan;
2549 	int    items_per_line;
2550 	int    col;
2551 
2552 	x += gtk_adjustment_get_value (self->priv->hadjustment);
2553 	y += gtk_adjustment_get_value (self->priv->vadjustment);
2554 
2555 	row = -1;
2556 	height = self->priv->cell_spacing;
2557 	for (scan = self->priv->lines; scan && (height < y); scan = scan->next) {
2558 		height += GTH_GRID_VIEW_LINE (scan->data)->height + self->priv->cell_spacing;
2559 		row++;
2560 	}
2561 	if (height < y)
2562 		row++;
2563 	row = MAX (row, 0);
2564 
2565 	items_per_line = gth_grid_view_get_items_per_line (self);
2566 	col = (x - (self->priv->cell_x_spacing / 2)) / (self->priv->cell_size + self->priv->cell_x_spacing) + 1;
2567 	col = MIN (col, items_per_line);
2568 
2569 	return (items_per_line * row) + col - 1;
2570 }
2571 
2572 
2573 static void
gth_grid_view_set_drag_dest_pos(GthFileView * file_view,GdkDragContext * context,int x,int y,guint time,int * pos)2574 gth_grid_view_set_drag_dest_pos (GthFileView    *file_view,
2575 				 GdkDragContext *context,
2576 				 int             x,
2577 				 int             y,
2578 				 guint           time,
2579 				 int            *pos)
2580 {
2581 	GthGridView     *self = GTH_GRID_VIEW (file_view);
2582 	GthDropPosition  drop_pos;
2583 	int              drop_image;
2584 
2585 	g_return_if_fail (GTH_IS_GRID_VIEW (self));
2586 
2587 	drop_pos = self->priv->drop_pos;
2588 	drop_image = self->priv->drop_item;
2589 
2590 	if ((x < 0) && (y < 0) && (drop_pos != GTH_DROP_POSITION_NONE)) {
2591 		if (drop_pos == GTH_DROP_POSITION_RIGHT)
2592 			drop_image++;
2593 		drop_pos = GTH_DROP_POSITION_NONE;
2594 	}
2595 	else {
2596 		drop_image = _gth_grid_view_get_drop_target_at (self, x, y);
2597 
2598 		if (drop_image < 0) {
2599 			drop_image = 0;
2600 			drop_pos = GTH_DROP_POSITION_LEFT;
2601 		}
2602 		else if (drop_image >= self->priv->n_items) {
2603 			drop_image = self->priv->n_items - 1;
2604 			drop_pos = GTH_DROP_POSITION_RIGHT;
2605 		}
2606 		else {
2607 			GthGridViewItem *item = g_list_nth (self->priv->items, drop_image)->data;
2608 			if (x - item->area.x > self->priv->cell_size / 2)
2609 				drop_pos = GTH_DROP_POSITION_RIGHT;
2610 			else
2611 				drop_pos = GTH_DROP_POSITION_LEFT;
2612 		}
2613 	}
2614 
2615 	if (pos != NULL) {
2616 		*pos = drop_image;
2617 		if (gtk_widget_get_direction(GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL) {
2618 			if (drop_pos == GTH_DROP_POSITION_LEFT)
2619 				*pos = *pos + 1;
2620 		}
2621 		else {
2622 			if (drop_pos == GTH_DROP_POSITION_RIGHT)
2623 				*pos = *pos + 1;
2624 		}
2625 	}
2626 
2627 	if ((drop_pos != self->priv->drop_pos) || (drop_image != self->priv->drop_item)) {
2628 		self->priv->drop_pos = drop_pos;
2629 		self->priv->drop_item = drop_image;
2630 		gtk_widget_queue_draw (GTK_WIDGET (self));
2631 	}
2632 }
2633 
2634 
2635 static void
gth_grid_view_get_drag_dest_pos(GthFileView * file_view,int * pos)2636 gth_grid_view_get_drag_dest_pos (GthFileView *file_view,
2637 				 int         *pos)
2638 {
2639 	GthGridView *self = GTH_GRID_VIEW (file_view);
2640 
2641 	g_return_if_fail (pos != NULL);
2642 	*pos = self->priv->drop_item;
2643 }
2644 
2645 
2646 /* -- GtkScrollable interface -- */
2647 
2648 
2649 static gboolean
gth_grid_view_get_border(GtkScrollable * scrollable,GtkBorder * border)2650 gth_grid_view_get_border (GtkScrollable *scrollable,
2651 			  GtkBorder     *border)
2652 {
2653 	return FALSE;
2654 }
2655 
2656 
2657 /* GtkWidget methods */
2658 
2659 
2660 static void
_gth_grid_view_select_range(GthGridView * self,GthGridViewItem * item,int pos)2661 _gth_grid_view_select_range (GthGridView     *self,
2662 			     GthGridViewItem *item,
2663 			     int              pos)
2664 {
2665 	int    a, b;
2666 	GList *scan;
2667 
2668 	if (self->priv->last_selected_pos == -1)
2669 		self->priv->last_selected_pos = pos;
2670 
2671 	if (pos < self->priv->last_selected_pos) {
2672 		a = pos;
2673 		b = self->priv->last_selected_pos;
2674 	}
2675 	else {
2676 		a = self->priv->last_selected_pos;
2677 		b = pos;
2678 	}
2679 
2680 	for (scan = g_list_nth (self->priv->items, a);
2681 	     (a <= b) && (scan != NULL);
2682 	     a++, scan = scan->next)
2683 	{
2684 		GthGridViewItem *item = scan->data;
2685 
2686 		if (! (item->state & GTK_STATE_FLAG_SELECTED))
2687 			_gth_grid_view_set_item_selected (self, TRUE, a);
2688 	}
2689 
2690 	_gth_grid_view_set_item_selected (self, TRUE, pos);
2691 	_gth_grid_view_emit_selection_changed (self);
2692 
2693 	gth_file_view_set_cursor (GTH_FILE_VIEW (self), pos);
2694 }
2695 
2696 
2697 static void
_gth_grid_view_select_single(GthGridView * self,GthGridViewItem * item,int pos,GdkEventButton * event)2698 _gth_grid_view_select_single (GthGridView     *self,
2699 	        	      GthGridViewItem *item,
2700 	        	      int              pos,
2701 	        	      GdkEventButton  *event)
2702 {
2703 	if (item->state & GTK_STATE_FLAG_SELECTED) {
2704 		/* postpone selection to handle dragging. */
2705 		self->priv->select_pending = TRUE;
2706 		self->priv->select_pending_pos = pos;
2707 		self->priv->select_pending_item = item;
2708 	}
2709 	else {
2710 		_gth_grid_view_unselect_all (self, NULL);
2711 		_gth_grid_view_set_item_selected_and_emit_signal (self, TRUE, pos);
2712 		self->priv->last_selected_pos = pos;
2713 
2714 		if (self->priv->activate_on_single_click && ((event->state & GDK_MOD1_MASK) == 0))
2715 			self->priv->activate_pending = TRUE;
2716 	}
2717 
2718 	gth_file_view_set_cursor (GTH_FILE_VIEW (self), pos);
2719 }
2720 
2721 
2722 static void
_gth_grid_view_select_multiple(GthGridView * self,GthGridViewItem * item,int pos,GdkEventButton * event)2723 _gth_grid_view_select_multiple (GthGridView     *self,
2724 			        GthGridViewItem *item,
2725 			        int              pos,
2726 			        GdkEventButton  *event)
2727 {
2728 	gboolean range;
2729 	gboolean additive;
2730 
2731 	range    = (event->state & GDK_SHIFT_MASK) != 0;
2732 	additive = (event->state & GDK_CONTROL_MASK) != 0;
2733 
2734 	if (! additive && ! range) {
2735 		_gth_grid_view_select_single (self, item, pos, event);
2736 		return;
2737 	}
2738 
2739 	if (range) {
2740 		_gth_grid_view_unselect_all (self, item);
2741 		_gth_grid_view_select_range (self, item, pos);
2742 	}
2743 	else if (additive) {
2744 		_gth_grid_view_set_item_selected_and_emit_signal (self, ! (item->state & GTK_STATE_FLAG_SELECTED), pos);
2745 		self->priv->last_selected_pos = pos;
2746 	}
2747 
2748 	gth_file_view_set_cursor (GTH_FILE_VIEW (self), pos);
2749 }
2750 
2751 
2752 static void
gth_grid_view_store_items_state(GthGridView * self)2753 gth_grid_view_store_items_state (GthGridView *self)
2754 {
2755 	GList *scan;
2756 
2757 	for (scan = self->priv->items; scan; scan = scan->next) {
2758 		GthGridViewItem *item = scan->data;
2759 		item->tmp_state = item->state;
2760 	}
2761 }
2762 
2763 
2764 static int
gth_grid_view_button_press(GtkWidget * widget,GdkEventButton * event)2765 gth_grid_view_button_press (GtkWidget      *widget,
2766 			    GdkEventButton *event)
2767 {
2768 	GthGridView *    self = GTH_GRID_VIEW (widget);
2769 	int              retval = FALSE;
2770 	int              pos;
2771 	GthGridViewItem *item;
2772 
2773 	if (event->window != self->priv->bin_window)
2774 		return FALSE;
2775 
2776 	if (! gtk_widget_has_focus (widget))
2777 		gtk_widget_grab_focus (widget);
2778 
2779 	pos = _gth_grid_view_get_at_position (self, event->x, event->y, &item);
2780 
2781 	if ((pos != -1) && (event->button == 1) && (event->type == GDK_2BUTTON_PRESS)) {
2782 		/* Double click activates the item */
2783 
2784 		if (! (event->state & GDK_CONTROL_MASK)
2785 		    && ! (event->state & GDK_SHIFT_MASK)
2786 		    && ! (event->state & GDK_MOD1_MASK))
2787 		{
2788 			gth_file_view_activated (GTH_FILE_VIEW (self), pos);
2789 		}
2790 		retval = TRUE;
2791 	}
2792 	else if ((pos != -1) && (event->button == 2) && (event->type == GDK_BUTTON_PRESS)) {
2793 		/* This can be the start of a dragging action. */
2794 
2795 		if (! (event->state & GDK_CONTROL_MASK)
2796 		    && ! (event->state & GDK_SHIFT_MASK)
2797 		    && ! (event->state & GDK_MOD1_MASK)
2798 		    && self->priv->drag_source_enabled)
2799 		{
2800 			self->priv->dragging = TRUE;
2801 			self->priv->drag_button = 2;
2802 			self->priv->drag_start_x = event->x;
2803 			self->priv->drag_start_y = event->y;
2804 		}
2805 		retval = TRUE;
2806 	}
2807 	else if ((pos != -1) && (event->button == 1) && (event->type == GDK_BUTTON_PRESS)) {
2808 		/* This can be the start of a dragging action. */
2809 
2810 		if (! (event->state & GDK_CONTROL_MASK)
2811 		    && ! (event->state & GDK_SHIFT_MASK)
2812 		    && ! (event->state & GDK_MOD1_MASK)
2813 		    && self->priv->drag_source_enabled)
2814 		{
2815 			self->priv->dragging = TRUE;
2816 			self->priv->drag_button = 1;
2817 			self->priv->drag_start_x = event->x;
2818 			self->priv->drag_start_y = event->y;
2819 		}
2820 
2821 		if (self->priv->selection_mode != GTK_SELECTION_NONE) {
2822 			if (self->priv->selection_mode == GTK_SELECTION_MULTIPLE)
2823 				_gth_grid_view_select_multiple (self, item, pos, event);
2824 			else
2825 				_gth_grid_view_select_single (self, item, pos, event);
2826 		}
2827 		else
2828 			gth_file_view_set_cursor (GTH_FILE_VIEW (self), pos);
2829 
2830 		retval = TRUE;
2831 	}
2832 	else if ((pos == -1) && (event->button == 1) && (event->type == GDK_BUTTON_PRESS) && (self->priv->selection_mode == GTK_SELECTION_MULTIPLE)) {
2833 		/* This can be the start of a selection */
2834 
2835 		if ((event->state & GDK_CONTROL_MASK) == 0)
2836 			_gth_grid_view_unselect_all (self, NULL);
2837 
2838 		if (! self->priv->selecting) {
2839 			gth_grid_view_store_items_state (self);
2840 
2841 			self->priv->sel_start_x = event->x;
2842 			self->priv->sel_start_y = event->y;
2843 			self->priv->selection_area.x = event->x;
2844 			self->priv->selection_area.y = event->y;
2845 			self->priv->selection_area.width = 0;
2846 			self->priv->selection_area.height = 0;
2847 			self->priv->sel_state = event->state;
2848 			self->priv->selecting = TRUE;
2849 		}
2850 		retval = TRUE;
2851 	}
2852 
2853 	return retval;
2854 }
2855 
2856 
2857 static gboolean
gth_grid_view_item_is_inside_area(GthGridViewItem * item,int x1,int y1,int x2,int y2)2858 gth_grid_view_item_is_inside_area (GthGridViewItem *item,
2859 				   int              x1,
2860 				   int              y1,
2861 				   int              x2,
2862 				   int              y2)
2863 {
2864 	GdkRectangle area;
2865 	GdkRectangle item_area;
2866 	double       x_ofs;
2867 	double       y_ofs;
2868 
2869 	if ((x1 == x2) && (y1 == y2))
2870 		return FALSE;
2871 
2872 	area.x = x1;
2873 	area.y = y1;
2874 	area.width = x2 - x1;
2875 	area.height = y2 - y1;
2876 
2877 	item_area = item->thumbnail_area;
2878 	x_ofs = item_area.width / 6;
2879 	y_ofs = item_area.height / 6;
2880 	item_area.x      += x_ofs;
2881 	item_area.y      += y_ofs;
2882 	item_area.width  -= x_ofs * 2;
2883 	item_area.height -= y_ofs * 2;
2884 
2885 	return gdk_rectangle_intersect (&item_area, &area, NULL);
2886 }
2887 
2888 
2889 static void
_gth_grid_view_update_mouse_selection(GthGridView * self,int x,int y)2890 _gth_grid_view_update_mouse_selection (GthGridView *self,
2891 				       int           x,
2892 				       int           y)
2893 {
2894 	cairo_rectangle_int_t  old_selection_area;
2895 	cairo_region_t        *invalid_region;
2896 	int                    x1, y1, x2, y2;
2897 	GtkAllocation          allocation;
2898 	cairo_region_t        *common_region;
2899 	gboolean               additive;
2900 	gboolean               invert;
2901 	int                    min_y, max_y;
2902 	GList                 *l, *begin, *end;
2903 	int                    i, begin_idx, end_idx;
2904 
2905 	old_selection_area = self->priv->selection_area;
2906 
2907 	/* calculate the new selection area */
2908 
2909 	if (self->priv->sel_start_x < x) {
2910 		x1 = self->priv->sel_start_x;
2911 		x2 = x;
2912 	}
2913 	else {
2914 		x1 = x;
2915 		x2 = self->priv->sel_start_x;
2916 	}
2917 
2918 	if (self->priv->sel_start_y < y) {
2919 		y1 = self->priv->sel_start_y;
2920 		y2 = y;
2921 	}
2922 	else {
2923 		y1 = y;
2924 		y2 = self->priv->sel_start_y;
2925 	}
2926 
2927 	gtk_widget_get_allocation (GTK_WIDGET (self), &allocation);
2928 	allocation.width = MAX (allocation.width, self->priv->width);
2929 	allocation.height = MAX (allocation.height, self->priv->height);
2930 	x1 = CLAMP (x1, 0, allocation.width);
2931 	y1 = CLAMP (y1, 0, allocation.height);
2932 	x2 = CLAMP (x2, 0, allocation.width);
2933 	y2 = CLAMP (y2, 0, allocation.height);
2934 
2935 	self->priv->selection_area.x = x1;
2936 	self->priv->selection_area.y = y1;
2937 	self->priv->selection_area.width = x2 - x1;
2938 	self->priv->selection_area.height = y2 - y1;
2939 
2940 	/* repaint the changed area */
2941 
2942 	invalid_region = cairo_region_create_rectangle (&old_selection_area);
2943 	cairo_region_union_rectangle (invalid_region, &self->priv->selection_area);
2944 
2945 	common_region = cairo_region_create_rectangle (&old_selection_area);
2946 	cairo_region_intersect_rectangle (common_region, &self->priv->selection_area);
2947 
2948 	if (! cairo_region_is_empty (common_region)) {
2949 		cairo_rectangle_int_t common_region_extents;
2950 
2951 		/* invalidate the border as well */
2952 		cairo_region_get_extents (common_region, &common_region_extents);
2953 		common_region_extents.x += RUBBERBAND_BORDER;
2954 		common_region_extents.y += RUBBERBAND_BORDER;
2955 		common_region_extents.width -= RUBBERBAND_BORDER * 2;
2956 		common_region_extents.height -= RUBBERBAND_BORDER * 2;
2957 
2958 		cairo_region_subtract_rectangle (invalid_region, &common_region_extents);
2959 	}
2960 
2961 	gdk_window_invalidate_region (self->priv->bin_window, invalid_region, FALSE);
2962 
2963 	cairo_region_destroy (common_region);
2964 	cairo_region_destroy (invalid_region);
2965 
2966 	/* select or unselect images as appropriate */
2967 
2968 	additive = self->priv->sel_state & GDK_SHIFT_MASK;
2969 	invert   = self->priv->sel_state & GDK_CONTROL_MASK;
2970 
2971 	/* Consider only images in the min_y --> max_y offset. */
2972 
2973 	min_y = self->priv->selection_area.y;
2974 	max_y = self->priv->selection_area.y + self->priv->selection_area.height;
2975 
2976 	begin_idx = get_first_visible_at_offset (self, min_y);
2977 	begin = g_list_nth (self->priv->items, begin_idx);
2978 
2979 	end_idx = get_last_visible_at_offset (self, max_y);
2980 	end = g_list_nth (self->priv->items, end_idx);
2981 
2982 	if (end != NULL)
2983 		end = end->next;
2984 
2985 	gdk_window_freeze_updates (self->priv->bin_window);
2986 
2987 	x1 = self->priv->selection_area.x;
2988 	y1 = self->priv->selection_area.y;
2989 	x2 = x1 + self->priv->selection_area.width;
2990 	y2 = y1 + self->priv->selection_area.height;
2991 
2992 	for (l = begin, i = begin_idx; l != end; l = l->next, i++) {
2993 		GthGridViewItem *item = l->data;
2994 		gboolean         selection_changed;
2995 
2996 		selection_changed = (item->state & GTK_STATE_FLAG_SELECTED) != (item->tmp_state & GTK_STATE_FLAG_SELECTED);
2997 		if (gth_grid_view_item_is_inside_area (item, x1, y1, x2, y2)) {
2998 			if (invert) {
2999 				if (! selection_changed)
3000 					_gth_grid_view_set_item_selected (self, ! (item->state & GTK_STATE_FLAG_SELECTED), i);
3001 			}
3002 			else if (additive) {
3003 				if (! (item->state & GTK_STATE_FLAG_SELECTED))
3004 					_gth_grid_view_set_item_selected (self, TRUE, i);
3005 			}
3006 			else {
3007 				if (! (item->state & GTK_STATE_FLAG_SELECTED))
3008 					_gth_grid_view_set_item_selected (self, TRUE, i);
3009 			}
3010 		}
3011 		else if (selection_changed)
3012 			_gth_grid_view_set_item_selected (self, (item->tmp_state & GTK_STATE_FLAG_SELECTED), i);
3013 	}
3014 
3015 	gdk_window_thaw_updates (self->priv->bin_window);
3016 
3017 	_gth_grid_view_emit_selection_changed (self);
3018 }
3019 
3020 
3021 static void
_gth_grid_view_stop_selecting(GthGridView * self)3022 _gth_grid_view_stop_selecting (GthGridView *self)
3023 {
3024 	if (! self->priv->selecting)
3025 		return;
3026 
3027 	self->priv->selecting = FALSE;
3028 	self->priv->sel_start_x = 0;
3029 	self->priv->sel_start_y = 0;
3030 
3031 	if (self->priv->scroll_timeout != 0) {
3032 		g_source_remove (self->priv->scroll_timeout);
3033 		self->priv->scroll_timeout = 0;
3034 	}
3035 
3036 	gdk_window_invalidate_rect (self->priv->bin_window,
3037 				    &self->priv->selection_area,
3038 				    FALSE);
3039 }
3040 
3041 
3042 static gboolean
gth_grid_view_button_release(GtkWidget * widget,GdkEventButton * event)3043 gth_grid_view_button_release (GtkWidget      *widget,
3044 			      GdkEventButton *event)
3045 {
3046 	GthGridView *self = GTH_GRID_VIEW (widget);
3047 
3048 	if (self->priv->dragging) {
3049 		self->priv->select_pending = self->priv->select_pending && ! self->priv->drag_started;
3050 		self->priv->activate_pending = self->priv->activate_pending && ! self->priv->drag_started;
3051 		_gth_grid_view_stop_dragging (self);
3052 	}
3053 
3054 	if (self->priv->selecting) {
3055 		_gth_grid_view_update_mouse_selection (self, event->x, event->y);
3056 		_gth_grid_view_stop_selecting (self);
3057 	}
3058 
3059 	if (self->priv->select_pending) {
3060 		self->priv->select_pending = FALSE;
3061 		_gth_grid_view_unselect_all (self, NULL);
3062 		_gth_grid_view_set_item_selected_and_emit_signal (self, TRUE, self->priv->select_pending_pos);
3063 		self->priv->last_selected_pos = self->priv->select_pending_pos;
3064 		if (self->priv->activate_on_single_click && ((event->state & GDK_MOD1_MASK) == 0))
3065 			self->priv->activate_pending = TRUE;
3066 	}
3067 
3068 	if (self->priv->activate_pending) {
3069 		self->priv->activate_pending = FALSE;
3070 		gth_file_view_activated (GTH_FILE_VIEW (self), self->priv->last_selected_pos);
3071 	}
3072 
3073 	return FALSE;
3074 }
3075 
3076 
3077 static gboolean
autoscroll_cb(gpointer user_data)3078 autoscroll_cb (gpointer user_data)
3079 {
3080 	GthGridView *self = user_data;
3081 	double       max_value;
3082 	double       value;
3083 
3084 	max_value = gtk_adjustment_get_upper (self->priv->vadjustment) - gtk_adjustment_get_page_size (self->priv->vadjustment);
3085 	value = gtk_adjustment_get_value (self->priv->vadjustment) + self->priv->autoscroll_y_delta;
3086 	if (value > max_value)
3087 		value = max_value;
3088 
3089 	gtk_adjustment_set_value (self->priv->vadjustment, value);
3090 	self->priv->event_last_y = self->priv->event_last_y + self->priv->autoscroll_y_delta;
3091 	_gth_grid_view_update_mouse_selection (self, self->priv->event_last_x, self->priv->event_last_y);
3092 
3093 	return TRUE;
3094 }
3095 
3096 
3097 static gboolean
gth_grid_view_motion_notify(GtkWidget * widget,GdkEventMotion * event)3098 gth_grid_view_motion_notify (GtkWidget      *widget,
3099 			     GdkEventMotion *event)
3100 {
3101 	GthGridView *self = GTH_GRID_VIEW (widget);
3102 
3103 	if (self->priv->dragging) {
3104 		if (! self->priv->drag_started
3105 		    && (self->priv->selection != NULL)
3106 		    && gtk_drag_check_threshold (widget,
3107 						 self->priv->drag_start_x,
3108 						 self->priv->drag_start_y,
3109 						 event->x,
3110 						 event->y))
3111 		{
3112 			GthGridViewItem *item;
3113 			int              pos;
3114 			GdkDragContext  *context;
3115 			gboolean         multi_dnd;
3116 
3117 			/**/
3118 
3119 			pos = _gth_grid_view_get_at_position (self,
3120 							      self->priv->drag_start_x,
3121 							      self->priv->drag_start_y,
3122 							      &item);
3123 			if (pos != -1)
3124 				gth_file_view_set_cursor (GTH_FILE_VIEW (self), pos);
3125 
3126 			/**/
3127 
3128 			self->priv->drag_started = TRUE;
3129 			self->priv->select_pending = FALSE;
3130 			self->priv->activate_pending = FALSE;
3131 			self->priv->selecting = FALSE;
3132 
3133 			context = gtk_drag_begin_with_coordinates (widget,
3134 								   self->priv->drag_target_list,
3135 								   self->priv->drag_actions,
3136 								   self->priv->drag_button,
3137 								   (GdkEvent *) event,
3138 								   -1,
3139 								   -1);
3140 
3141 			multi_dnd = self->priv->selection->next != NULL;
3142 
3143 			if ((pos != -1) && (item != NULL) && (item->thumbnail != NULL)) {
3144 				cairo_surface_t *icon = _cairo_create_dnd_icon (item->thumbnail,
3145 										self->priv->thumbnail_size,
3146 										item->style,
3147 										multi_dnd);
3148 				cairo_surface_set_device_offset (icon,
3149 								 item->thumbnail_area.x - event->x,
3150 								 item->thumbnail_area.y - event->y);
3151 				gtk_drag_set_icon_surface (context, icon);
3152 				cairo_surface_destroy (icon);
3153 			}
3154 			else
3155 				gtk_drag_set_icon_name (context,
3156 							multi_dnd ? "emblem-documents-symbolic" : "folder-documents-symbolic",
3157 							-4,
3158 							-4);
3159 		}
3160 
3161 		return TRUE;
3162 	}
3163 	else if (self->priv->selecting) {
3164 		double y_delta;
3165 
3166 		y_delta = event->y - gtk_adjustment_get_value (self->priv->vadjustment);
3167 		if (fabs (y_delta) > MAX_DELTA_FOR_SCROLLING)
3168 			event->y = gtk_adjustment_get_upper (self->priv->vadjustment);
3169 
3170 		_gth_grid_view_update_mouse_selection (self, event->x, event->y);
3171 
3172 		/* If we are out of bounds, schedule a timeout that will do
3173 		 * the scrolling */
3174 
3175 		y_delta = event->y - gtk_adjustment_get_value (self->priv->vadjustment);
3176 		if ((y_delta < 0) || (y_delta > gtk_widget_get_allocated_height (widget))) {
3177 			self->priv->event_last_x = event->x;
3178 			self->priv->event_last_y = event->y;
3179 
3180 			/* Make the stepping relative to the mouse
3181 			 * distance from the canvas.
3182 			 * Also notice the timeout below is small to give a
3183 			 * more smooth movement.
3184 			 */
3185 			if (y_delta < 0)
3186 				self->priv->autoscroll_y_delta = y_delta;
3187 			else
3188 				self->priv->autoscroll_y_delta = y_delta - gtk_widget_get_allocated_height (widget);
3189 			self->priv->autoscroll_y_delta /= 2;
3190 
3191 			if (self->priv->scroll_timeout == 0)
3192 				self->priv->scroll_timeout = gdk_threads_add_timeout (SCROLL_DELAY, autoscroll_cb, self);
3193 		}
3194 		else if (self->priv->scroll_timeout != 0) {
3195 			g_source_remove (self->priv->scroll_timeout);
3196 			self->priv->scroll_timeout = 0;
3197 		}
3198 
3199 		return TRUE;
3200 	}
3201 
3202 	return FALSE;
3203 }
3204 
3205 
3206 static void
gth_grid_view_drag_end(GtkWidget * widget,GdkDragContext * context)3207 gth_grid_view_drag_end (GtkWidget      *widget,
3208 	                GdkDragContext *context)
3209 {
3210 	GthGridView *self = GTH_GRID_VIEW (widget);
3211 	_gth_grid_view_stop_dragging (self);
3212 }
3213 
3214 
3215 static void
gth_grid_view_drag_leave(GtkWidget * widget,GdkDragContext * context,guint time)3216 gth_grid_view_drag_leave (GtkWidget      *widget,
3217 	                  GdkDragContext *context,
3218 			  guint           time)
3219 {
3220 	GthGridView *self = GTH_GRID_VIEW (widget);
3221 
3222 	if ((self->priv->drop_pos != GTH_DROP_POSITION_NONE) || (self->priv->drop_item != -1)) {
3223 		self->priv->drop_item = -1;
3224 		self->priv->drop_pos = GTH_DROP_POSITION_NONE;
3225 		gtk_widget_queue_draw (GTK_WIDGET (self));
3226 	}
3227 }
3228 
3229 
3230 static void
select_range_with_keyboard(GthGridView * self,int next_focused_item)3231 select_range_with_keyboard (GthGridView *self,
3232 			    int          next_focused_item)
3233 {
3234 	int    begin_idx;
3235 	int    end_idx;
3236 	GList *begin;
3237 	GList *end;
3238 	int    i;
3239 	GList *link;
3240 
3241 	if (self->priv->focused_item == -1)
3242 		return;
3243 
3244 	begin_idx = MIN (MIN (self->priv->first_focused_item, self->priv->focused_item), next_focused_item);
3245 	end_idx = MAX (MAX (self->priv->first_focused_item, self->priv->focused_item), next_focused_item);
3246 	begin = g_list_nth (self->priv->items, begin_idx);
3247 	end = g_list_nth (self->priv->items, end_idx);
3248 	if (end != NULL)
3249 		end = end->next;
3250 
3251 	gdk_window_freeze_updates (self->priv->bin_window);
3252 
3253 	for (link = begin, i = begin_idx; link != end; link = link->next, i++) {
3254 		if (next_focused_item > self->priv->first_focused_item) {
3255 			if ((i >= self->priv->first_focused_item) && (i <= next_focused_item))
3256 				_gth_grid_view_select_item (self, i);
3257 			else
3258 				_gth_grid_view_unselect_item (self, i);
3259 		}
3260 		else {
3261 			if ((i >= next_focused_item) && (i <= self->priv->first_focused_item))
3262 				_gth_grid_view_select_item (self, i);
3263 			else
3264 				_gth_grid_view_unselect_item (self, i);
3265 		}
3266 	}
3267 
3268 	gdk_window_thaw_updates (self->priv->bin_window);
3269 
3270 	_gth_grid_view_emit_selection_changed (self);
3271 }
3272 
3273 
3274 static GList *
_gth_grid_view_get_line_at_position(GthGridView * self,int pos)3275 _gth_grid_view_get_line_at_position (GthGridView *self,
3276 				     int          pos)
3277 {
3278 	GList           *link;
3279 	GthGridViewItem *item;
3280 	GList           *scan;
3281 
3282 	link = g_list_nth (self->priv->items, pos);
3283 	g_return_val_if_fail (link != NULL, NULL);
3284 
3285 	item = link->data;
3286 	for (scan = self->priv->lines; scan; scan = scan->next) {
3287 		GthGridViewLine *line = scan->data;
3288 
3289 		if (g_list_find (line->items, item) != NULL)
3290 			return scan;
3291 	}
3292 
3293 	return NULL;
3294 }
3295 
3296 
3297 static int
_gth_grid_view_get_item_at_page_distance(GthGridView * self,int focused_item,gboolean downward)3298 _gth_grid_view_get_item_at_page_distance (GthGridView *self,
3299 					  int          focused_item,
3300 					  gboolean     downward)
3301 {
3302 	int    old_focused_item;
3303 	int    direction;
3304 	int    h;
3305 	GList *line;
3306 	int    items_per_line;
3307 
3308 	old_focused_item = focused_item;
3309 	direction = downward ? 1 : -1;
3310 	h = gtk_widget_get_allocated_height (GTK_WIDGET (self));
3311 	line = _gth_grid_view_get_line_at_position (self, focused_item);
3312 	items_per_line = gth_grid_view_get_items_per_line (self);
3313 
3314 	while ((h > 0) && (line != NULL)) {
3315 		h -= GTH_GRID_VIEW_LINE (line->data)->height + self->priv->cell_spacing;
3316 		if (h > 0) {
3317 			focused_item = focused_item + direction * items_per_line;
3318 			if ((focused_item >= self->priv->n_items - 1) || (focused_item <= 0))
3319 				return focused_item;
3320 		}
3321 
3322 		if (downward)
3323 			line = line->next;
3324 		else
3325 			line = line->prev;
3326 	}
3327 
3328 	if (old_focused_item == focused_item)
3329 		focused_item = focused_item + (direction * items_per_line);
3330 
3331 	return focused_item;
3332 }
3333 
3334 
3335 static gboolean
gth_grid_view_move_cursor(GthGridView * self,GthCursorMovement dir,GthSelectionChange sel_change)3336 gth_grid_view_move_cursor (GthGridView        *self,
3337 			   GthCursorMovement   dir,
3338 			   GthSelectionChange  sel_change)
3339 {
3340 	int items_per_line;
3341 	int next_focused_item;
3342 
3343 	if (self->priv->n_items == 0)
3344 		return FALSE;
3345 
3346 	if (! gtk_widget_has_focus (GTK_WIDGET (self)))
3347 		return FALSE;
3348 
3349 	items_per_line = gth_grid_view_get_items_per_line (self);
3350 	next_focused_item = self->priv->focused_item;
3351 
3352 	if (self->priv->focused_item == -1) {
3353 		self->priv->first_focused_item = 0;
3354 		next_focused_item = 0;
3355 	}
3356 	else {
3357 		switch (dir) {
3358 		case GTH_CURSOR_MOVE_RIGHT:
3359 			next_focused_item++;
3360 			break;
3361 
3362 		case GTH_CURSOR_MOVE_LEFT:
3363 			next_focused_item--;
3364 			break;
3365 
3366 		case GTH_CURSOR_MOVE_DOWN:
3367 			next_focused_item += items_per_line;
3368 			break;
3369 
3370 		case GTH_CURSOR_MOVE_UP:
3371 			next_focused_item -= items_per_line;
3372 			break;
3373 
3374 		case GTH_CURSOR_MOVE_PAGE_UP:
3375 			next_focused_item = _gth_grid_view_get_item_at_page_distance (self,
3376 								     	     	      next_focused_item,
3377 								     	     	      FALSE);
3378 			break;
3379 
3380 		case GTH_CURSOR_MOVE_PAGE_DOWN:
3381 			next_focused_item = _gth_grid_view_get_item_at_page_distance (self,
3382 								     	     	      next_focused_item,
3383 								     	     	      TRUE);
3384 			break;
3385 
3386 		case GTH_CURSOR_MOVE_BEGIN:
3387 			next_focused_item = 0;
3388 			break;
3389 
3390 		case GTH_CURSOR_MOVE_END:
3391 			next_focused_item = self->priv->n_items - 1;
3392 			break;
3393 
3394 		default:
3395 			break;
3396 		}
3397 
3398 		if (((next_focused_item < 0) && (self->priv->focused_item == 0))
3399 		    || ((next_focused_item > self->priv->n_items - 1) && (self->priv->focused_item == self->priv->n_items - 1)))
3400 		{
3401 			gdk_window_beep (gtk_widget_get_window (GTK_WIDGET (self)));
3402 		}
3403 		next_focused_item = CLAMP (next_focused_item, 0, self->priv->n_items - 1);
3404 	}
3405 
3406 	if (sel_change == GTH_SELECTION_SET_CURSOR) {
3407 		_gth_grid_view_unselect_all (self, NULL);
3408 		_gth_grid_view_set_item_selected (self, TRUE, next_focused_item);
3409 		_gth_grid_view_emit_selection_changed (self);
3410 	}
3411 	else if (sel_change == GTH_SELECTION_SET_RANGE)
3412 		select_range_with_keyboard (self, next_focused_item);
3413 
3414 	gth_file_view_set_cursor (GTH_FILE_VIEW (self), next_focused_item);
3415 
3416 	return TRUE;
3417 }
3418 
3419 
3420 static gboolean
gth_grid_view_select_cursor_item(GthGridView * self)3421 gth_grid_view_select_cursor_item (GthGridView *self)
3422 {
3423 	GthGridViewItem *item;
3424 
3425 	if (self->priv->focused_item == -1)
3426 		return FALSE;
3427 
3428 	item = g_list_nth (self->priv->items, self->priv->focused_item)->data;
3429 	g_return_val_if_fail (item != NULL, FALSE);
3430 
3431 	_gth_grid_view_unselect_all (self, item);
3432 	_gth_grid_view_select_item (self, self->priv->focused_item);
3433 	self->priv->last_selected_pos = self->priv->select_pending_pos;
3434 	_gth_grid_view_emit_selection_changed (self);
3435 
3436 	return TRUE;
3437 }
3438 
3439 
3440 static gboolean
gth_grid_view_toggle_cursor_item(GthGridView * self)3441 gth_grid_view_toggle_cursor_item (GthGridView *self)
3442 {
3443 	GList           *link;
3444 	GthGridViewItem *item;
3445 
3446 	if (self->priv->focused_item == -1)
3447 		return FALSE;
3448 
3449 	link = g_list_nth (self->priv->items, self->priv->focused_item);
3450 	g_return_val_if_fail (link != NULL, FALSE);
3451 
3452 	item = link->data;
3453 	if (item->state & GTK_STATE_FLAG_SELECTED)
3454 		_gth_grid_view_unselect_item (self, self->priv->focused_item);
3455 	else
3456 		_gth_grid_view_select_item (self, self->priv->focused_item);
3457 	_gth_grid_view_emit_selection_changed (self);
3458 
3459 	return TRUE;
3460 }
3461 
3462 
3463 static gboolean
gth_grid_view_activate_cursor_item(GthGridView * self)3464 gth_grid_view_activate_cursor_item (GthGridView *self)
3465 {
3466 	if (self->priv->focused_item >= 0) {
3467 		gth_file_view_activated (GTH_FILE_VIEW (self), self->priv->focused_item);
3468 		return TRUE;
3469 	}
3470 
3471 	return FALSE;
3472 }
3473 
3474 
3475 static void
_gtk_binding_entry_add_move_cursor_signals(GtkBindingSet * binding_set,guint keyval,GthCursorMovement dir)3476 _gtk_binding_entry_add_move_cursor_signals (GtkBindingSet     *binding_set,
3477 					    guint              keyval,
3478 					    GthCursorMovement  dir)
3479 {
3480 	gtk_binding_entry_add_signal (binding_set, keyval, 0,
3481 				      "move-cursor", 2,
3482 				      G_TYPE_ENUM, dir,
3483 				      G_TYPE_ENUM, GTH_SELECTION_SET_CURSOR);
3484 
3485 	gtk_binding_entry_add_signal (binding_set, keyval, GDK_CONTROL_MASK,
3486 				      "move-cursor", 2,
3487 				      G_TYPE_ENUM, dir,
3488 				      G_TYPE_ENUM, GTH_SELECTION_KEEP);
3489 
3490 	gtk_binding_entry_add_signal (binding_set, keyval, GDK_SHIFT_MASK,
3491 				      "move-cursor", 2,
3492 				      G_TYPE_ENUM, dir,
3493 				      G_TYPE_ENUM, GTH_SELECTION_SET_RANGE);
3494 }
3495 
3496 
3497 static void
_gth_grid_view_set_hadjustment(GthGridView * self,GtkAdjustment * adjustment)3498 _gth_grid_view_set_hadjustment (GthGridView   *self,
3499 				GtkAdjustment *adjustment)
3500 {
3501 	if (adjustment != NULL) {
3502 		g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
3503 		g_object_ref_sink (adjustment);
3504 	}
3505 	else
3506 		adjustment = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
3507 
3508 	if (self->priv->hadjustment != NULL) {
3509 		_g_signal_handlers_disconnect_by_data (self->priv->hadjustment, self);
3510 		g_object_unref (self->priv->hadjustment);
3511 	}
3512 
3513 	self->priv->hadjustment = adjustment;
3514 	_gth_grid_view_configure_hadjustment (self);
3515 
3516 	g_signal_connect (self->priv->hadjustment,
3517 			  "value-changed",
3518 			  G_CALLBACK (adjustment_value_changed),
3519 			  self);
3520 }
3521 
3522 
3523 static void
_gth_grid_view_set_vadjustment(GthGridView * self,GtkAdjustment * adjustment)3524 _gth_grid_view_set_vadjustment (GthGridView   *self,
3525 				GtkAdjustment *adjustment)
3526 {
3527 	if (adjustment != NULL) {
3528 		g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
3529 		g_object_ref_sink (adjustment);
3530 	}
3531 	else
3532 		adjustment = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
3533 
3534 	if (self->priv->vadjustment != NULL) {
3535 		_g_signal_handlers_disconnect_by_data (self->priv->vadjustment, self);
3536 		g_object_unref (self->priv->vadjustment);
3537 	}
3538 
3539 	self->priv->vadjustment = adjustment;
3540 	_gth_grid_view_configure_vadjustment (self);
3541 
3542 	g_signal_connect (self->priv->vadjustment,
3543 			  "value-changed",
3544 			  G_CALLBACK (adjustment_value_changed),
3545 			  self);
3546 }
3547 
3548 
3549 static void
_gth_grid_view_set_thumbnail_size(GthGridView * self,int size)3550 _gth_grid_view_set_thumbnail_size (GthGridView *self,
3551 				   int          size)
3552 {
3553 	self->priv->thumbnail_size = size;
3554 	self->priv->cell_size = self->priv->thumbnail_size + (self->priv->thumbnail_border * 2) + (self->priv->cell_padding * 2);
3555 	self->priv->update_caption_height = TRUE;
3556 	g_object_notify (G_OBJECT (self), "thumbnail-size");
3557 
3558 	gtk_widget_queue_resize (GTK_WIDGET (self));
3559 }
3560 
3561 
3562 static void
_gth_grid_view_set_caption(GthGridView * self,const char * attributes)3563 _gth_grid_view_set_caption (GthGridView *self,
3564 			    const char  *attributes)
3565 {
3566 	GList *scan;
3567 
3568 	g_free (self->priv->caption_attributes);
3569 	self->priv->caption_attributes = g_strdup (attributes);
3570 
3571 	if (self->priv->caption_attributes_v != NULL) {
3572 		g_strfreev (self->priv->caption_attributes_v);
3573 		self->priv->caption_attributes_v = NULL;
3574 	}
3575 	if (self->priv->caption_attributes != NULL)
3576 		self->priv->caption_attributes_v = g_strsplit (self->priv->caption_attributes, ",", -1);
3577 
3578 	self->priv->no_caption = (self->priv->caption_attributes_v == NULL) || (self->priv->caption_attributes_v[0] == NULL) || (g_strcmp0 (self->priv->caption_attributes_v[0], "none") == 0);
3579 
3580 	for (scan = self->priv->items; scan; scan = scan->next)
3581 		gth_grid_view_item_update_caption (GTH_GRID_VIEW_ITEM (scan->data), self->priv->caption_attributes_v);
3582 	self->priv->update_caption_height = TRUE;
3583 
3584 	g_object_notify (G_OBJECT (self), "caption");
3585 
3586 	_gth_grid_view_queue_relayout (self);
3587 }
3588 
3589 
3590 static void
_gth_grid_view_set_activate_on_single_click(GthGridView * self,gboolean value)3591 _gth_grid_view_set_activate_on_single_click (GthGridView *self,
3592 					     gboolean     value)
3593 {
3594 	self->priv->activate_on_single_click = value;
3595 	g_object_notify (G_OBJECT (self), "activate-on-single-click");
3596 }
3597 
3598 
3599 static void
gth_grid_view_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)3600 gth_grid_view_set_property (GObject      *object,
3601 			    guint         prop_id,
3602 			    const GValue *value,
3603 			    GParamSpec   *pspec)
3604 {
3605 	GthGridView *self;
3606 
3607 	self = GTH_GRID_VIEW (object);
3608 
3609 	switch (prop_id) {
3610 	case PROP_CAPTION:
3611 		_gth_grid_view_set_caption (self, g_value_get_string (value));
3612 		break;
3613 	case PROP_HADJUSTMENT:
3614 		_gth_grid_view_set_hadjustment (self, g_value_get_object (value));
3615 		break;
3616 	case PROP_HSCROLL_POLICY:
3617 		/* FIXME */
3618 		break;
3619 	case PROP_MODEL:
3620 		gth_grid_view_set_model (GTH_FILE_VIEW (self), g_value_get_object (value));
3621 		break;
3622 	case PROP_THUMBNAIL_SIZE:
3623 		_gth_grid_view_set_thumbnail_size (self, g_value_get_int (value));
3624 		break;
3625 	case PROP_ACTIVATE_ON_SINGLE_CLICK:
3626 		_gth_grid_view_set_activate_on_single_click (self, g_value_get_boolean (value));
3627 		break;
3628 	case PROP_VADJUSTMENT:
3629 		_gth_grid_view_set_vadjustment (self, g_value_get_object (value));
3630 		break;
3631 	case PROP_VSCROLL_POLICY:
3632 		/* FIXME */
3633 		break;
3634 	default:
3635 		break;
3636 	}
3637 }
3638 
3639 
3640 static void
gth_grid_view_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)3641 gth_grid_view_get_property (GObject    *object,
3642 			    guint       prop_id,
3643 			    GValue     *value,
3644 			    GParamSpec *pspec)
3645 {
3646 	GthGridView *self;
3647 
3648 	self = GTH_GRID_VIEW (object);
3649 
3650 	switch (prop_id) {
3651 	case PROP_CAPTION:
3652 		g_value_set_string (value, self->priv->caption_attributes);
3653 		break;
3654 	case PROP_CELL_SPACING:
3655 		g_value_set_int (value, self->priv->cell_spacing);
3656 		break;
3657 	case PROP_HADJUSTMENT:
3658 		g_value_set_object (value, self->priv->hadjustment);
3659 		break;
3660 	case PROP_HSCROLL_POLICY:
3661 		g_value_set_enum (value, GTK_SCROLL_NATURAL);
3662 		break;
3663 	case PROP_MODEL:
3664 		g_value_set_object (value, self->priv->model);
3665 		break;
3666 	case PROP_THUMBNAIL_SIZE:
3667 		g_value_set_int (value, self->priv->thumbnail_size);
3668 		break;
3669 	case PROP_ACTIVATE_ON_SINGLE_CLICK:
3670 		g_value_set_boolean (value, self->priv->activate_on_single_click);
3671 		break;
3672 	case PROP_VADJUSTMENT:
3673 		g_value_set_object (value, self->priv->vadjustment);
3674 		break;
3675 	case PROP_VSCROLL_POLICY:
3676 		g_value_set_enum (value, GTK_SCROLL_NATURAL);
3677 		break;
3678 	default:
3679 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
3680 		break;
3681 	}
3682 }
3683 
3684 
3685 static void
gth_grid_view_class_init(GthGridViewClass * grid_view_class)3686 gth_grid_view_class_init (GthGridViewClass *grid_view_class)
3687 {
3688 	GObjectClass   *gobject_class;
3689 	GtkWidgetClass *widget_class;
3690 	GtkBindingSet  *binding_set;
3691 
3692 	/* Methods */
3693 
3694 	gobject_class = (GObjectClass*) grid_view_class;
3695 	gobject_class->finalize = gth_grid_view_finalize;
3696 	gobject_class->set_property = gth_grid_view_set_property;
3697 	gobject_class->get_property = gth_grid_view_get_property;
3698 
3699 	widget_class = (GtkWidgetClass*) grid_view_class;
3700 	widget_class->map = gth_grid_view_map;
3701 	widget_class->unmap = gth_grid_view_unmap;
3702 	widget_class->realize = gth_grid_view_realize;
3703 	widget_class->unrealize = gth_grid_view_unrealize;
3704 	widget_class->state_flags_changed = gth_grid_view_state_flags_changed;
3705 	widget_class->style_updated = gth_grid_view_style_updated;
3706 	widget_class->get_preferred_width = gth_grid_view_get_preferred_width;
3707 	widget_class->get_preferred_height = gth_grid_view_get_preferred_height;
3708 	widget_class->size_allocate = gth_grid_view_size_allocate;
3709 	widget_class->draw = gth_grid_view_draw;
3710 	widget_class->key_press_event = gth_grid_view_key_press;
3711 	widget_class->key_release_event = gth_grid_view_key_release;
3712 	widget_class->button_press_event = gth_grid_view_button_press;
3713 	widget_class->button_release_event = gth_grid_view_button_release;
3714 	widget_class->motion_notify_event = gth_grid_view_motion_notify;
3715 	widget_class->drag_end = gth_grid_view_drag_end;
3716 	widget_class->drag_leave = gth_grid_view_drag_leave;
3717 
3718 	grid_view_class->select_all = gth_grid_view_select_all;
3719 	grid_view_class->unselect_all = gth_grid_view_unselect_all;
3720 	grid_view_class->toggle_cursor_item = gth_grid_view_toggle_cursor_item;
3721 	grid_view_class->select_cursor_item = gth_grid_view_select_cursor_item;
3722 	grid_view_class->move_cursor = gth_grid_view_move_cursor;
3723 	grid_view_class->activate_cursor_item = gth_grid_view_activate_cursor_item;
3724 
3725 	/* Signals */
3726 
3727 	grid_view_signals[SELECT_ALL] =
3728 		g_signal_new ("select-all",
3729 			      G_TYPE_FROM_CLASS (gobject_class),
3730 			      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
3731 			      G_STRUCT_OFFSET (GthGridViewClass, select_all),
3732 			      NULL, NULL,
3733 			      g_cclosure_marshal_VOID__VOID,
3734 			      G_TYPE_NONE, 0);
3735 	grid_view_signals[UNSELECT_ALL] =
3736 		g_signal_new ("unselect-all",
3737 			      G_TYPE_FROM_CLASS (gobject_class),
3738 			      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
3739 			      G_STRUCT_OFFSET (GthGridViewClass, unselect_all),
3740 			      NULL, NULL,
3741 			      g_cclosure_marshal_VOID__VOID,
3742 			      G_TYPE_NONE, 0);
3743 	grid_view_signals[ACTIVATE_CURSOR_ITEM] =
3744 		g_signal_new ("activate-cursor-item",
3745 			      G_TYPE_FROM_CLASS (gobject_class),
3746 			      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
3747 			      G_STRUCT_OFFSET (GthGridViewClass, activate_cursor_item),
3748 			      NULL, NULL,
3749 			      gth_marshal_BOOLEAN__VOID,
3750 			      G_TYPE_BOOLEAN, 0);
3751 	grid_view_signals[MOVE_CURSOR] =
3752 		g_signal_new ("move-cursor",
3753 			      G_TYPE_FROM_CLASS (gobject_class),
3754 			      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
3755 			      G_STRUCT_OFFSET (GthGridViewClass, move_cursor),
3756 			      NULL, NULL,
3757 			      gth_marshal_BOOLEAN__ENUM_ENUM,
3758 			      G_TYPE_BOOLEAN, 2,
3759 			      GTH_TYPE_CURSOR_MOVEMENT,
3760 			      GTH_TYPE_SELECTION_CHANGE);
3761 	grid_view_signals[SELECT_CURSOR_ITEM] =
3762 		g_signal_new ("select-cursor-item",
3763 			      G_TYPE_FROM_CLASS (gobject_class),
3764 			      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
3765 			      G_STRUCT_OFFSET (GthGridViewClass, select_cursor_item),
3766 			      NULL, NULL,
3767 			      gth_marshal_BOOLEAN__VOID,
3768 			      G_TYPE_BOOLEAN, 0);
3769 	grid_view_signals[TOGGLE_CURSOR_ITEM] =
3770 		g_signal_new ("toggle-cursor-item",
3771 			      G_TYPE_FROM_CLASS (gobject_class),
3772 			      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
3773 			      G_STRUCT_OFFSET (GthGridViewClass, toggle_cursor_item),
3774 			      NULL, NULL,
3775 			      gth_marshal_BOOLEAN__VOID,
3776 			      G_TYPE_BOOLEAN, 0);
3777 
3778 	/* Properties */
3779 
3780 	g_object_class_install_property (gobject_class,
3781 					 PROP_CELL_SPACING,
3782 					 g_param_spec_int ("cell-spacing",
3783 							   "Cell Spacing",
3784 							   "Spacing between cells both horizontally and vertically",
3785 							   0,
3786 							   G_MAXINT32,
3787 							   DEFAULT_CELL_SPACING,
3788 							   G_PARAM_READWRITE));
3789 
3790 	/* GtkScrollable properties */
3791 
3792 	g_object_class_override_property (gobject_class, PROP_HADJUSTMENT, "hadjustment");
3793 	g_object_class_override_property (gobject_class, PROP_VADJUSTMENT, "vadjustment");
3794 	g_object_class_override_property (gobject_class, PROP_HSCROLL_POLICY, "hscroll-policy");
3795 	g_object_class_override_property (gobject_class, PROP_VSCROLL_POLICY, "vscroll-policy");
3796 
3797 	/* GthFileView properties */
3798 
3799 	g_object_class_override_property (gobject_class, PROP_CAPTION, "caption");
3800 	g_object_class_override_property (gobject_class, PROP_MODEL, "model");
3801 	g_object_class_override_property (gobject_class, PROP_THUMBNAIL_SIZE, "thumbnail-size");
3802 	g_object_class_override_property (gobject_class, PROP_ACTIVATE_ON_SINGLE_CLICK, "activate-on-single-click");
3803 
3804 	/* Key bindings */
3805 
3806 	binding_set = gtk_binding_set_by_class (grid_view_class);
3807 
3808 	_gtk_binding_entry_add_move_cursor_signals (binding_set, GDK_KEY_Right, GTH_CURSOR_MOVE_RIGHT);
3809 	_gtk_binding_entry_add_move_cursor_signals (binding_set, GDK_KEY_Left, GTH_CURSOR_MOVE_LEFT);
3810 	_gtk_binding_entry_add_move_cursor_signals (binding_set, GDK_KEY_Down, GTH_CURSOR_MOVE_DOWN);
3811 	_gtk_binding_entry_add_move_cursor_signals (binding_set, GDK_KEY_Up, GTH_CURSOR_MOVE_UP);
3812 	_gtk_binding_entry_add_move_cursor_signals (binding_set, GDK_KEY_Page_Up, GTH_CURSOR_MOVE_PAGE_UP);
3813 	_gtk_binding_entry_add_move_cursor_signals (binding_set, GDK_KEY_Page_Down, GTH_CURSOR_MOVE_PAGE_DOWN);
3814 	_gtk_binding_entry_add_move_cursor_signals (binding_set, GDK_KEY_Home, GTH_CURSOR_MOVE_BEGIN);
3815 	_gtk_binding_entry_add_move_cursor_signals (binding_set, GDK_KEY_End, GTH_CURSOR_MOVE_END);
3816 
3817 	_gtk_binding_entry_add_move_cursor_signals (binding_set, GDK_KEY_KP_Right, GTH_CURSOR_MOVE_RIGHT);
3818 	_gtk_binding_entry_add_move_cursor_signals (binding_set, GDK_KEY_KP_Left, GTH_CURSOR_MOVE_LEFT);
3819 	_gtk_binding_entry_add_move_cursor_signals (binding_set, GDK_KEY_KP_Down, GTH_CURSOR_MOVE_DOWN);
3820 	_gtk_binding_entry_add_move_cursor_signals (binding_set, GDK_KEY_KP_Up, GTH_CURSOR_MOVE_UP);
3821 	_gtk_binding_entry_add_move_cursor_signals (binding_set, GDK_KEY_KP_Page_Up, GTH_CURSOR_MOVE_PAGE_UP);
3822 	_gtk_binding_entry_add_move_cursor_signals (binding_set, GDK_KEY_KP_Page_Down, GTH_CURSOR_MOVE_PAGE_DOWN);
3823 	_gtk_binding_entry_add_move_cursor_signals (binding_set, GDK_KEY_KP_Home, GTH_CURSOR_MOVE_BEGIN);
3824 	_gtk_binding_entry_add_move_cursor_signals (binding_set, GDK_KEY_KP_End, GTH_CURSOR_MOVE_END);
3825 
3826 	gtk_binding_entry_add_signal (binding_set, GDK_KEY_space, 0,
3827 				      "select-cursor-item", 0);
3828 	gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Space, 0,
3829 				      "select-cursor-item", 0);
3830 
3831 	gtk_binding_entry_add_signal (binding_set, GDK_KEY_space, GDK_CONTROL_MASK,
3832 				      "toggle-cursor-item", 0);
3833 	gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Space, GDK_CONTROL_MASK,
3834 				      "toggle-cursor-item", 0);
3835 
3836 	gtk_binding_entry_add_signal (binding_set, GDK_KEY_Return, 0,
3837 				      "activate-cursor-item", 0);
3838 	gtk_binding_entry_add_signal (binding_set, GDK_KEY_ISO_Enter, 0,
3839 				      "activate-cursor-item", 0);
3840 	gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Enter, 0,
3841 				      "activate-cursor-item", 0);
3842 
3843 #if GTK_CHECK_VERSION(3,20,0)
3844 	gtk_widget_class_set_css_name (widget_class, "iconview");
3845 #endif
3846 }
3847 
3848 
3849 static void
gth_grid_view_gth_file_selection_interface_init(GthFileSelectionInterface * iface)3850 gth_grid_view_gth_file_selection_interface_init (GthFileSelectionInterface *iface)
3851 {
3852 	iface->set_selection_mode = gth_grid_view_set_selection_mode;
3853 	iface->get_selected = gth_grid_view_get_selected;
3854 	iface->select = gth_grid_view_select;
3855 	iface->unselect = gth_grid_view_unselect;
3856 	iface->select_all = gth_grid_view_select_all;
3857 	iface->unselect_all = gth_grid_view_unselect_all;
3858 	iface->is_selected = gth_grid_view_is_selected;
3859 	iface->get_first_selected = gth_grid_view_get_first_selected;
3860 	iface->get_last_selected = gth_grid_view_get_last_selected;
3861 	iface->get_n_selected = gth_grid_view_get_n_selected;
3862 }
3863 
3864 
3865 static void
gth_grid_view_gth_file_view_interface_init(GthFileViewInterface * iface)3866 gth_grid_view_gth_file_view_interface_init (GthFileViewInterface *iface)
3867 {
3868 	iface->scroll_to = gth_grid_view_scroll_to;
3869 	iface->set_vscroll = gth_grid_view_set_vscroll;
3870 	iface->get_visibility = gth_grid_view_get_visibility;
3871 	iface->get_at_position = gth_grid_view_get_at_position;
3872 	iface->get_first_visible = gth_grid_view_get_first_visible;
3873 	iface->get_last_visible = gth_grid_view_get_last_visible;
3874 	iface->cursor_changed = gth_grid_view_cursor_changed;
3875 	iface->get_cursor = gth_grid_view_get_cursor;
3876 	iface->enable_drag_source = gth_grid_view_enable_drag_source;
3877 	iface->unset_drag_source = gth_grid_view_unset_drag_source;
3878 	iface->enable_drag_dest = gth_grid_view_enable_drag_dest;
3879 	iface->unset_drag_dest = gth_grid_view_unset_drag_dest;
3880 	iface->set_drag_dest_pos = gth_grid_view_set_drag_dest_pos;
3881 	iface->get_drag_dest_pos = gth_grid_view_get_drag_dest_pos;
3882 }
3883 
3884 
3885 static void
gth_grid_view_gtk_scrollable_interface_init(GtkScrollableInterface * iface)3886 gth_grid_view_gtk_scrollable_interface_init (GtkScrollableInterface *iface)
3887 {
3888 	iface->get_border = gth_grid_view_get_border;
3889 }
3890 
3891 
3892 static void
gth_grid_view_init(GthGridView * self)3893 gth_grid_view_init (GthGridView *self)
3894 {
3895 	GtkStyleContext *style_context;
3896 
3897 	style_context = gtk_widget_get_style_context (GTK_WIDGET (self));
3898 	gtk_style_context_add_class (style_context, GTK_STYLE_CLASS_VIEW);
3899 	gtk_style_context_add_class (style_context, GTK_STYLE_CLASS_FRAME);
3900 
3901 	gtk_widget_set_can_focus (GTK_WIDGET (self), TRUE);
3902 
3903 	self->priv = gth_grid_view_get_instance_private (self);
3904 
3905 	/* self->priv->model = NULL; */
3906 	self->priv->items = NULL;
3907 	self->priv->n_items = 0;
3908 	self->priv->lines = NULL;
3909 	self->priv->selection = NULL;
3910 	self->priv->focused_item = -1;
3911 	self->priv->first_focused_item = -1;
3912 	self->priv->make_focused_visible = FALSE;
3913 	self->priv->initial_vscroll = 0.0;
3914 	self->priv->needs_relayout = FALSE;
3915 	self->priv->needs_relayout_after_size_allocate = FALSE;
3916 	self->priv->layout_timeout = 0;
3917 	self->priv->relayout_from_line = -1;
3918 	self->priv->update_caption_height = TRUE;
3919 	self->priv->width = 0;
3920 	self->priv->height = 0;
3921 	/* self->priv->thumbnail_size = 0; */
3922 	self->priv->thumbnail_border = DEFAULT_THUMBNAIL_BORDER;
3923 
3924 	/* self->priv->cell_size = 0; */
3925 	self->priv->cell_spacing = DEFAULT_CELL_SPACING;
3926 	self->priv->cell_x_spacing = -1;
3927 	/* self->priv->cell_padding = DEFAULT_CELL_PADDING; */
3928 	self->priv->caption_spacing = DEFAULT_CAPTION_SPACING;
3929 	self->priv->caption_padding = DEFAULT_CAPTION_PADDING;
3930 
3931 	self->priv->scroll_timeout = 0;
3932 	self->priv->autoscroll_y_delta = 0;
3933 	self->priv->event_last_x = 0;
3934 	self->priv->event_last_y = 0;
3935 
3936 	self->priv->selecting = FALSE;
3937 	self->priv->select_pending = FALSE;
3938 	self->priv->activate_pending = FALSE;
3939 	self->priv->activate_on_single_click = TRUE;
3940 	self->priv->select_pending_pos = -1;
3941 	self->priv->select_pending_item = NULL;
3942 	gth_grid_view_set_selection_mode (GTH_FILE_SELECTION (self), GTK_SELECTION_MULTIPLE);
3943 	/* self->priv->selection_area = 0; */
3944 	self->priv->last_selected_pos = -1;
3945 	self->priv->multi_selecting_with_keyboard = FALSE;
3946 	self->priv->selection_changed = FALSE;
3947 	self->priv->sel_start_x = 0;
3948 	self->priv->sel_start_y = 0;
3949 	self->priv->sel_state = 0;
3950 
3951 	self->priv->dragging = FALSE;
3952 	self->priv->drag_started = FALSE;
3953 	self->priv->drag_source_enabled = FALSE;
3954 	self->priv->drag_start_button_mask = 0;
3955 	self->priv->drag_button = 0;
3956 	self->priv->drag_target_list = NULL;
3957 	self->priv->drag_actions = 0;
3958 	self->priv->drag_start_x = 0;
3959 	self->priv->drag_start_y = 0;
3960 	self->priv->drop_item = -1;
3961 	self->priv->drop_pos = GTH_DROP_POSITION_NONE;
3962 
3963 	self->priv->bin_window = NULL;
3964 
3965 	self->priv->caption_attributes = NULL;
3966 	self->priv->caption_attributes_v = NULL;
3967 	self->priv->no_caption = TRUE;
3968 	self->priv->caption_layout = NULL;
3969 	self->priv->icon_cache = NULL;
3970 
3971 	_gth_grid_view_set_hadjustment (self, gtk_adjustment_new (0.0, 1.0, 0.0, 0.1, 1.0, 1.0));
3972 	_gth_grid_view_set_vadjustment (self, gtk_adjustment_new (0.0, 1.0, 0.0, 0.1, 1.0, 1.0));
3973 }
3974 
3975 
3976 GtkWidget *
gth_grid_view_new(void)3977 gth_grid_view_new (void)
3978 {
3979 	return g_object_new (GTH_TYPE_GRID_VIEW, NULL);
3980 }
3981 
3982 
3983 void
gth_grid_view_set_cell_spacing(GthGridView * self,int cell_spacing)3984 gth_grid_view_set_cell_spacing (GthGridView *self,
3985 				int          cell_spacing)
3986 {
3987 	g_return_if_fail (GTH_IS_GRID_VIEW (self));
3988 
3989 	self->priv->cell_spacing = cell_spacing;
3990 	g_object_notify (G_OBJECT (self), "cell-spacing");
3991 
3992 	_gth_grid_view_queue_relayout (self);
3993 }
3994 
3995 
3996 int
gth_grid_view_get_items_per_line(GthGridView * self)3997 gth_grid_view_get_items_per_line (GthGridView *self)
3998 {
3999 	g_return_val_if_fail (GTH_IS_GRID_VIEW (self), 0);
4000 
4001 	return MAX (self->priv->width / (self->priv->cell_size + self->priv->cell_spacing), 1);
4002 }
4003