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