1 /*
2  * xed-file-browser-view.c - Xed plugin providing easy file access
3  * from the sidepanel
4  *
5  * Copyright (C) 2006 - Jesse van den Kieboom <jesse@icecrew.nl>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2, or (at your option)
10  * any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
20  */
21 
22 #include <string.h>
23 #include <glib-object.h>
24 #include <gio/gio.h>
25 #include <gtk/gtk.h>
26 #include <gdk/gdkkeysyms.h>
27 
28 #include "xed-file-browser-store.h"
29 #include "xed-file-bookmarks-store.h"
30 #include "xed-file-browser-view.h"
31 #include "xed-file-browser-marshal.h"
32 #include "xed-file-browser-enum-types.h"
33 
34 struct _XedFileBrowserViewPrivate
35 {
36     GtkTreeViewColumn *column;
37     GtkCellRenderer *pixbuf_renderer;
38     GtkCellRenderer *text_renderer;
39 
40     GtkTreeModel *model;
41     GtkTreeRowReference *editable;
42 
43     GtkTreePath *double_click_path[2]; /* Both clicks in a double click need to be on the same row */
44     GtkTreePath *hover_path;
45     GdkCursor *hand_cursor;
46     gboolean ignore_release;
47     gboolean selected_on_button_down;
48     gint drag_button;
49     gboolean drag_started;
50 
51     gboolean restore_expand_state;
52     gboolean is_refresh;
53     GHashTable * expand_state;
54 };
55 
56 /* Properties */
57 enum
58 {
59     PROP_0,
60 
61     PROP_RESTORE_EXPAND_STATE
62 };
63 
64 /* Signals */
65 enum
66 {
67     ERROR,
68     FILE_ACTIVATED,
69     DIRECTORY_ACTIVATED,
70     BOOKMARK_ACTIVATED,
71     NUM_SIGNALS
72 };
73 
74 static guint signals[NUM_SIGNALS] = { 0 };
75 
76 static const GtkTargetEntry drag_source_targets[] = {
77     { "text/uri-list", 0, 0 }
78 };
79 
80 G_DEFINE_DYNAMIC_TYPE_EXTENDED (XedFileBrowserView,
81                                 xed_file_browser_view,
82                                 GTK_TYPE_TREE_VIEW,
83                                 0,
84                                 G_ADD_PRIVATE_DYNAMIC (XedFileBrowserView))
85 
86 static void on_cell_edited (GtkCellRendererText *cell,
87                             gchar               *path,
88                             gchar               *new_text,
89                             XedFileBrowserView  *tree_view);
90 
91 static void on_begin_refresh (XedFileBrowserStore *model,
92                               XedFileBrowserView  *view);
93 static void on_end_refresh (XedFileBrowserStore *model,
94                             XedFileBrowserView  *view);
95 
96 static void on_unload (XedFileBrowserStore *model,
97                        GFile               *location,
98                        XedFileBrowserView  *view);
99 
100 static void on_row_inserted (XedFileBrowserStore *model,
101                              GtkTreePath         *path,
102                              GtkTreeIter         *iter,
103                              XedFileBrowserView  *view);
104 
105 static void
xed_file_browser_view_finalize(GObject * object)106 xed_file_browser_view_finalize (GObject *object)
107 {
108     XedFileBrowserView *obj = XED_FILE_BROWSER_VIEW(object);
109 
110     if (obj->priv->hand_cursor)
111     {
112         g_object_unref (obj->priv->hand_cursor);
113     }
114 
115     if (obj->priv->hover_path)
116     {
117         gtk_tree_path_free (obj->priv->hover_path);
118     }
119 
120     if (obj->priv->expand_state)
121     {
122         g_hash_table_destroy (obj->priv->expand_state);
123         obj->priv->expand_state = NULL;
124     }
125 
126     G_OBJECT_CLASS (xed_file_browser_view_parent_class)->finalize (object);
127 }
128 
129 static void
add_expand_state(XedFileBrowserView * view,GFile * location)130 add_expand_state (XedFileBrowserView *view,
131                   GFile              *location)
132 {
133     if (!location)
134     {
135         return;
136     }
137 
138     if (view->priv->expand_state)
139     {
140         g_hash_table_insert (view->priv->expand_state, location, g_object_ref (location));
141     }
142 }
143 
144 static void
remove_expand_state(XedFileBrowserView * view,GFile * location)145 remove_expand_state (XedFileBrowserView *view,
146                      GFile              *location)
147 {
148     if (!location)
149     {
150         return;
151     }
152 
153     if (view->priv->expand_state)
154     {
155         g_hash_table_remove (view->priv->expand_state, location);
156     }
157 }
158 
159 static void
row_expanded(GtkTreeView * tree_view,GtkTreeIter * iter,GtkTreePath * path)160 row_expanded (GtkTreeView *tree_view,
161               GtkTreeIter *iter,
162               GtkTreePath *path)
163 {
164     XedFileBrowserView *view = XED_FILE_BROWSER_VIEW (tree_view);
165 
166     if (GTK_TREE_VIEW_CLASS (xed_file_browser_view_parent_class)->row_expanded)
167     {
168         GTK_TREE_VIEW_CLASS (xed_file_browser_view_parent_class)->row_expanded (tree_view, iter, path);
169     }
170 
171     if (!XED_IS_FILE_BROWSER_STORE (view->priv->model))
172     {
173         return;
174     }
175 
176     if (view->priv->restore_expand_state)
177     {
178         GFile *location;
179 
180         gtk_tree_model_get (view->priv->model, iter, XED_FILE_BROWSER_STORE_COLUMN_LOCATION, &location, -1);
181 
182         add_expand_state (view, location);
183 
184         if (location)
185         {
186             g_object_unref (location);
187         }
188     }
189 
190     _xed_file_browser_store_iter_expanded (XED_FILE_BROWSER_STORE (view->priv->model), iter);
191 }
192 
193 static void
row_collapsed(GtkTreeView * tree_view,GtkTreeIter * iter,GtkTreePath * path)194 row_collapsed (GtkTreeView *tree_view,
195                GtkTreeIter *iter,
196                GtkTreePath *path)
197 {
198     XedFileBrowserView *view = XED_FILE_BROWSER_VIEW (tree_view);
199 
200     if (GTK_TREE_VIEW_CLASS (xed_file_browser_view_parent_class)->row_collapsed)
201     {
202         GTK_TREE_VIEW_CLASS (xed_file_browser_view_parent_class)->row_collapsed (tree_view, iter, path);
203     }
204 
205     if (!XED_IS_FILE_BROWSER_STORE (view->priv->model))
206     {
207         return;
208     }
209 
210     if (view->priv->restore_expand_state)
211     {
212         GFile *location;
213 
214         gtk_tree_model_get (view->priv->model, iter, XED_FILE_BROWSER_STORE_COLUMN_LOCATION, &location, -1);
215 
216         remove_expand_state (view, location);
217 
218         if (location)
219         {
220             g_object_unref (location);
221         }
222     }
223 
224     _xed_file_browser_store_iter_collapsed (XED_FILE_BROWSER_STORE (view->priv->model), iter);
225 }
226 
227 static void
directory_activated(XedFileBrowserView * view,GtkTreeIter * iter)228 directory_activated (XedFileBrowserView *view,
229                      GtkTreeIter        *iter)
230 {
231     xed_file_browser_store_set_virtual_root (XED_FILE_BROWSER_STORE (view->priv->model), iter);
232 }
233 
234 static void
activate_selected_files(XedFileBrowserView * view)235 activate_selected_files (XedFileBrowserView *view)
236 {
237     GtkTreeView *tree_view = GTK_TREE_VIEW (view);
238     GtkTreeSelection *selection = gtk_tree_view_get_selection (tree_view);
239     GList *rows, *row;
240     GtkTreePath *directory = NULL;
241     GtkTreePath *path;
242     GtkTreeIter iter;
243     XedFileBrowserStoreFlag flags;
244 
245     rows = gtk_tree_selection_get_selected_rows (selection, &view->priv->model);
246 
247     for (row = rows; row; row = row->next)
248     {
249         path = (GtkTreePath *)(row->data);
250 
251         /* Get iter from path */
252         if (!gtk_tree_model_get_iter (view->priv->model, &iter, path))
253         {
254             continue;
255         }
256 
257         gtk_tree_model_get (view->priv->model, &iter, XED_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, -1);
258 
259         if (FILE_IS_DIR (flags))
260         {
261             if (directory == NULL)
262             {
263                 directory = path;
264             }
265 
266         }
267         else if (!FILE_IS_DUMMY (flags))
268         {
269             g_signal_emit (view, signals[FILE_ACTIVATED], 0, &iter);
270         }
271     }
272 
273     if (directory != NULL)
274     {
275         if (gtk_tree_model_get_iter (view->priv->model, &iter, directory))
276         {
277             g_signal_emit (view, signals[DIRECTORY_ACTIVATED], 0, &iter);
278         }
279     }
280 
281     g_list_foreach (rows, (GFunc)gtk_tree_path_free, NULL);
282     g_list_free (rows);
283 }
284 
285 static void
activate_selected_bookmark(XedFileBrowserView * view)286 activate_selected_bookmark (XedFileBrowserView *view)
287 {
288     GtkTreeView *tree_view = GTK_TREE_VIEW (view);
289     GtkTreeSelection *selection = gtk_tree_view_get_selection (tree_view);
290     GtkTreeIter iter;
291 
292     if (gtk_tree_selection_get_selected (selection, &view->priv->model, &iter))
293     {
294         g_signal_emit (view, signals[BOOKMARK_ACTIVATED], 0, &iter);
295     }
296 }
297 
298 static void
activate_selected_items(XedFileBrowserView * view)299 activate_selected_items (XedFileBrowserView *view)
300 {
301     if (XED_IS_FILE_BROWSER_STORE (view->priv->model))
302     {
303         activate_selected_files (view);
304     }
305     else if (XED_IS_FILE_BOOKMARKS_STORE (view->priv->model))
306     {
307         activate_selected_bookmark (view);
308     }
309 }
310 
311 static void
row_activated(GtkTreeView * tree_view,GtkTreePath * path,GtkTreeViewColumn * column)312 row_activated (GtkTreeView       *tree_view,
313                GtkTreePath       *path,
314                GtkTreeViewColumn *column)
315 {
316     GtkTreeSelection *selection = gtk_tree_view_get_selection (tree_view);
317 
318     /* Make sure the activated row is the only one selected */
319     gtk_tree_selection_unselect_all (selection);
320     gtk_tree_selection_select_path (selection, path);
321 
322     activate_selected_items (XED_FILE_BROWSER_VIEW (tree_view));
323 }
324 
325 static void
toggle_hidden_filter(XedFileBrowserView * view)326 toggle_hidden_filter (XedFileBrowserView *view)
327 {
328     XedFileBrowserStoreFilterMode mode;
329 
330     if (XED_IS_FILE_BROWSER_STORE (view->priv->model))
331     {
332         mode = xed_file_browser_store_get_filter_mode (XED_FILE_BROWSER_STORE (view->priv->model));
333         mode ^= XED_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN;
334         xed_file_browser_store_set_filter_mode (XED_FILE_BROWSER_STORE (view->priv->model), mode);
335     }
336 }
337 
338 static gboolean
button_event_modifies_selection(GdkEventButton * event)339 button_event_modifies_selection (GdkEventButton *event)
340 {
341     return (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) != 0;
342 }
343 
344 static void
drag_begin(GtkWidget * widget,GdkDragContext * context)345 drag_begin (GtkWidget      *widget,
346             GdkDragContext *context)
347 {
348     XedFileBrowserView *view = XED_FILE_BROWSER_VIEW (widget);
349 
350     view->priv->drag_button = 0;
351     view->priv->drag_started = TRUE;
352 
353     /* Chain up */
354     GTK_WIDGET_CLASS (xed_file_browser_view_parent_class)->drag_begin (widget, context);
355 }
356 
357 static void
did_not_drag(XedFileBrowserView * view,GdkEventButton * event)358 did_not_drag (XedFileBrowserView *view,
359               GdkEventButton     *event)
360 {
361     GtkTreeView *tree_view;
362     GtkTreeSelection *selection;
363     GtkTreePath *path;
364 
365     tree_view = GTK_TREE_VIEW (view);
366     selection = gtk_tree_view_get_selection (tree_view);
367 
368     if (gtk_tree_view_get_path_at_pos (tree_view, event->x, event->y, &path, NULL, NULL, NULL))
369     {
370         if ((event->button == 1 || event->button == 2)
371                  && ((event->state & GDK_CONTROL_MASK) != 0 ||
372                  (event->state & GDK_SHIFT_MASK) == 0)
373                  && view->priv->selected_on_button_down)
374         {
375             if (!button_event_modifies_selection (event))
376             {
377                 gtk_tree_selection_unselect_all (selection);
378                 gtk_tree_selection_select_path (selection, path);
379             }
380             else
381             {
382                 gtk_tree_selection_unselect_path (selection, path);
383             }
384         }
385 
386         gtk_tree_path_free (path);
387     }
388 }
389 
390 static gboolean
button_release_event(GtkWidget * widget,GdkEventButton * event)391 button_release_event (GtkWidget      *widget,
392                       GdkEventButton *event)
393 {
394     XedFileBrowserView *view = XED_FILE_BROWSER_VIEW (widget);
395 
396     if (event->button == view->priv->drag_button)
397     {
398         view->priv->drag_button = 0;
399 
400         if (!view->priv->drag_started && !view->priv->ignore_release)
401         {
402             did_not_drag (view, event);
403         }
404     }
405 
406     /* Chain up */
407     return GTK_WIDGET_CLASS (xed_file_browser_view_parent_class)->button_release_event (widget, event);
408 }
409 
410 static gboolean
button_press_event(GtkWidget * widget,GdkEventButton * event)411 button_press_event (GtkWidget      *widget,
412                     GdkEventButton *event)
413 {
414     int double_click_time;
415     static int click_count = 0;
416     static guint32 last_click_time = 0;
417     XedFileBrowserView *view;
418     GtkTreeView *tree_view;
419     GtkTreeSelection *selection;
420     GtkTreePath *path;
421     int expander_size;
422     int horizontal_separator;
423     gboolean on_expander;
424     gboolean call_parent;
425     gboolean selected;
426     GtkWidgetClass *widget_parent = GTK_WIDGET_CLASS(xed_file_browser_view_parent_class);
427 
428     tree_view = GTK_TREE_VIEW (widget);
429     view = XED_FILE_BROWSER_VIEW (widget);
430     selection = gtk_tree_view_get_selection (tree_view);
431 
432     /* Get double click time */
433     g_object_get (G_OBJECT (gtk_widget_get_settings (widget)),
434                   "gtk-double-click-time", &double_click_time,
435                   NULL);
436 
437     /* Determine click count */
438     if (event->time - last_click_time < double_click_time)
439     {
440         click_count++;
441     }
442     else
443     {
444         click_count = 0;
445     }
446 
447     last_click_time = event->time;
448 
449     view->priv->ignore_release = FALSE;
450     call_parent = TRUE;
451 
452     if (gtk_tree_view_get_path_at_pos (tree_view, event->x, event->y, &path, NULL, NULL, NULL))
453     {
454         /* Keep track of path of last click so double clicks only happen
455          * on the same item */
456         if ((event->button == 1 || event->button == 2) && event->type == GDK_BUTTON_PRESS)
457         {
458             if (view->priv->double_click_path[1])
459             {
460                 gtk_tree_path_free (view->priv->double_click_path[1]);
461             }
462 
463             view->priv->double_click_path[1] = view->priv->double_click_path[0];
464             view->priv->double_click_path[0] = gtk_tree_path_copy (path);
465         }
466 
467         if (event->type == GDK_2BUTTON_PRESS)
468         {
469             /* Chain up, must be before activating the selected
470                items because this will cause the view to grab focus */
471             widget_parent->button_press_event (widget, event);
472 
473             if (view->priv->double_click_path[1] &&
474                 gtk_tree_path_compare (view->priv->double_click_path[0], view->priv->double_click_path[1]) == 0)
475             {
476                 activate_selected_items (view);
477             }
478         }
479         else
480         {
481             /* We're going to filter out some situations where
482              * we can't let the default code run because all
483              * but one row would be would be deselected. We don't
484              * want that; we want the right click menu or single
485              * click to apply to everything that's currently selected. */
486             selected = gtk_tree_selection_path_is_selected (selection, path);
487 
488             if (event->button == 3 && selected)
489             {
490                 call_parent = FALSE;
491             }
492 
493             if ((event->button == 1 || event->button == 2) &&
494                 ((event->state & GDK_CONTROL_MASK) != 0 ||
495                  (event->state & GDK_SHIFT_MASK) == 0))
496             {
497                 gtk_widget_style_get (widget,
498                                       "expander-size", &expander_size,
499                                       "horizontal-separator", &horizontal_separator,
500                                       NULL);
501                 on_expander = (event->x <= horizontal_separator / 2 + gtk_tree_path_get_depth (path) * expander_size);
502 
503                 view->priv->selected_on_button_down = selected;
504 
505                 if (selected)
506                 {
507                     call_parent = on_expander || gtk_tree_selection_count_selected_rows (selection) == 1;
508                     view->priv->ignore_release = call_parent;
509                 }
510                 else if  ((event->state & GDK_CONTROL_MASK) != 0)
511                 {
512                     call_parent = FALSE;
513                     gtk_tree_selection_select_path (selection, path);
514                 }
515                 else
516                 {
517                     view->priv->ignore_release = on_expander;
518                 }
519             }
520 
521             if (call_parent)
522             {
523                 /* Chain up */
524                 widget_parent->button_press_event (widget, event);
525             }
526             else if (selected)
527             {
528                 gtk_widget_grab_focus (widget);
529             }
530 
531             if ((event->button == 1 || event->button == 2) &&
532                 event->type == GDK_BUTTON_PRESS)
533             {
534                 view->priv->drag_started = FALSE;
535                 view->priv->drag_button = event->button;
536             }
537         }
538 
539         gtk_tree_path_free (path);
540     }
541     else
542     {
543         if ((event->button == 1 || event->button == 2) && event->type == GDK_BUTTON_PRESS)
544         {
545             if (view->priv->double_click_path[1])
546             {
547                 gtk_tree_path_free (view->priv->double_click_path[1]);
548             }
549 
550             view->priv->double_click_path[1] = view->priv->double_click_path[0];
551             view->priv->double_click_path[0] = NULL;
552         }
553 
554         gtk_tree_selection_unselect_all (selection);
555         /* Chain up */
556         widget_parent->button_press_event (widget, event);
557     }
558 
559     /* We already chained up if nescessary, so just return TRUE */
560     return TRUE;
561 }
562 
563 static gboolean
key_press_event(GtkWidget * widget,GdkEventKey * event)564 key_press_event (GtkWidget   *widget,
565                  GdkEventKey *event)
566 {
567     XedFileBrowserView *view;
568     guint modifiers;
569     gboolean handled;
570 
571     view = XED_FILE_BROWSER_VIEW (widget);
572     handled = FALSE;
573 
574     modifiers = gtk_accelerator_get_default_mod_mask ();
575 
576     switch (event->keyval)
577     {
578         case GDK_KEY_space:
579             if (event->state & GDK_CONTROL_MASK)
580             {
581                 handled = FALSE;
582                 break;
583             }
584             if (!gtk_widget_has_focus (widget))
585             {
586                 handled = FALSE;
587                 break;
588             }
589 
590             activate_selected_items (view);
591             handled = TRUE;
592             break;
593 
594         case GDK_KEY_Return:
595         case GDK_KEY_KP_Enter:
596             activate_selected_items (view);
597             handled = TRUE;
598             break;
599 
600         case GDK_KEY_h:
601             if ((event->state & modifiers) == GDK_CONTROL_MASK)
602             {
603                 toggle_hidden_filter (view);
604                 handled = TRUE;
605                 break;
606             }
607 
608         default:
609             handled = FALSE;
610     }
611 
612     /* Chain up */
613     if (!handled)
614     {
615         return GTK_WIDGET_CLASS (xed_file_browser_view_parent_class)->key_press_event (widget, event);
616     }
617 
618     return TRUE;
619 }
620 
621 static void
fill_expand_state(XedFileBrowserView * view,GtkTreeIter * iter)622 fill_expand_state (XedFileBrowserView *view,
623                    GtkTreeIter        *iter)
624 {
625     GtkTreePath * path;
626     GtkTreeIter child;
627 
628     if (!gtk_tree_model_iter_has_child (view->priv->model, iter))
629     {
630         return;
631     }
632 
633     path = gtk_tree_model_get_path (view->priv->model, iter);
634 
635     if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (view), path))
636     {
637         GFile *location;
638 
639         gtk_tree_model_get (view->priv->model,
640                             iter,
641                             XED_FILE_BROWSER_STORE_COLUMN_LOCATION,
642                             &location,
643                             -1);
644 
645         add_expand_state (view, location);
646 
647         if (location)
648         {
649             g_object_unref (location);
650         }
651     }
652 
653     if (gtk_tree_model_iter_children (view->priv->model, &child, iter))
654     {
655         do
656         {
657             fill_expand_state (view, &child);
658         } while (gtk_tree_model_iter_next (view->priv->model, &child));
659     }
660 
661     gtk_tree_path_free (path);
662 }
663 
664 static void
uninstall_restore_signals(XedFileBrowserView * tree_view,GtkTreeModel * model)665 uninstall_restore_signals (XedFileBrowserView *tree_view,
666                            GtkTreeModel       *model)
667 {
668     g_signal_handlers_disconnect_by_func (model, on_begin_refresh, tree_view);
669     g_signal_handlers_disconnect_by_func (model, on_end_refresh, tree_view);
670     g_signal_handlers_disconnect_by_func (model, on_unload, tree_view);
671     g_signal_handlers_disconnect_by_func (model, on_row_inserted, tree_view);
672 }
673 
674 static void
install_restore_signals(XedFileBrowserView * tree_view,GtkTreeModel * model)675 install_restore_signals (XedFileBrowserView *tree_view,
676                          GtkTreeModel       *model)
677 {
678     g_signal_connect (model, "begin-refresh",
679                       G_CALLBACK (on_begin_refresh), tree_view);
680     g_signal_connect (model, "end-refresh",
681                       G_CALLBACK (on_end_refresh), tree_view);
682     g_signal_connect (model, "unload",
683                       G_CALLBACK (on_unload), tree_view);
684     g_signal_connect_after (model, "row-inserted",
685                             G_CALLBACK (on_row_inserted), tree_view);
686 }
687 
688 static void
set_restore_expand_state(XedFileBrowserView * view,gboolean state)689 set_restore_expand_state (XedFileBrowserView *view,
690                           gboolean            state)
691 {
692     if (state == view->priv->restore_expand_state)
693     {
694         return;
695     }
696 
697     if (view->priv->expand_state)
698     {
699         g_hash_table_destroy (view->priv->expand_state);
700         view->priv->expand_state = NULL;
701     }
702 
703     if (state)
704     {
705         view->priv->expand_state = g_hash_table_new_full (g_file_hash,
706                                                           (GEqualFunc)g_file_equal,
707                                                           g_object_unref,
708                                                           NULL);
709 
710         if (view->priv->model && XED_IS_FILE_BROWSER_STORE (view->priv->model))
711         {
712             fill_expand_state (view, NULL);
713 
714             install_restore_signals (view, view->priv->model);
715         }
716     }
717     else if (view->priv->model && XED_IS_FILE_BROWSER_STORE (view->priv->model))
718     {
719         uninstall_restore_signals (view, view->priv->model);
720     }
721 
722     view->priv->restore_expand_state = state;
723 }
724 
725 static void
get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)726 get_property (GObject    *object,
727               guint       prop_id,
728               GValue     *value,
729               GParamSpec *pspec)
730 {
731     XedFileBrowserView *obj = XED_FILE_BROWSER_VIEW (object);
732 
733     switch (prop_id)
734     {
735         case PROP_RESTORE_EXPAND_STATE:
736             g_value_set_boolean (value, obj->priv->restore_expand_state);
737             break;
738         default:
739             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
740             break;
741     }
742 }
743 
744 static void
set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)745 set_property (GObject      *object,
746               guint         prop_id,
747               const GValue *value,
748               GParamSpec   *pspec)
749 {
750     XedFileBrowserView *obj = XED_FILE_BROWSER_VIEW (object);
751 
752     switch (prop_id)
753     {
754         case PROP_RESTORE_EXPAND_STATE:
755             set_restore_expand_state (obj, g_value_get_boolean (value));
756             break;
757         default:
758             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
759             break;
760     }
761 }
762 
763 static void
xed_file_browser_view_class_init(XedFileBrowserViewClass * klass)764 xed_file_browser_view_class_init (XedFileBrowserViewClass *klass)
765 {
766     GObjectClass *object_class = G_OBJECT_CLASS (klass);
767     GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
768     GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
769 
770     object_class->finalize = xed_file_browser_view_finalize;
771     object_class->get_property = get_property;
772     object_class->set_property = set_property;
773 
774     /* Event handlers */
775     widget_class->button_press_event = button_press_event;
776     widget_class->button_release_event = button_release_event;
777     widget_class->drag_begin = drag_begin;
778     widget_class->key_press_event = key_press_event;
779 
780     /* Tree view handlers */
781     tree_view_class->row_activated = row_activated;
782     tree_view_class->row_expanded = row_expanded;
783     tree_view_class->row_collapsed = row_collapsed;
784 
785     /* Default handlers */
786     klass->directory_activated = directory_activated;
787 
788     g_object_class_install_property (object_class, PROP_RESTORE_EXPAND_STATE,
789                                      g_param_spec_boolean ("restore-expand-state",
790                                                            "Restore Expand State",
791                                                            "Restore expanded state of loaded directories",
792                                                            FALSE,
793                                                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
794 
795     signals[ERROR] =
796         g_signal_new ("error",
797                       G_OBJECT_CLASS_TYPE (object_class),
798                       G_SIGNAL_RUN_LAST,
799                       G_STRUCT_OFFSET (XedFileBrowserViewClass,
800                                error), NULL, NULL,
801                       xed_file_browser_marshal_VOID__UINT_STRING,
802                       G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_STRING);
803     signals[FILE_ACTIVATED] =
804         g_signal_new ("file-activated",
805                       G_OBJECT_CLASS_TYPE (object_class),
806                       G_SIGNAL_RUN_LAST,
807                       G_STRUCT_OFFSET (XedFileBrowserViewClass,
808                                file_activated), NULL, NULL,
809                       g_cclosure_marshal_VOID__BOXED,
810                       G_TYPE_NONE, 1, GTK_TYPE_TREE_ITER);
811     signals[DIRECTORY_ACTIVATED] =
812         g_signal_new ("directory-activated",
813                       G_OBJECT_CLASS_TYPE (object_class),
814                       G_SIGNAL_RUN_LAST,
815                       G_STRUCT_OFFSET (XedFileBrowserViewClass,
816                                directory_activated), NULL, NULL,
817                       g_cclosure_marshal_VOID__BOXED,
818                       G_TYPE_NONE, 1, GTK_TYPE_TREE_ITER);
819     signals[BOOKMARK_ACTIVATED] =
820         g_signal_new ("bookmark-activated",
821                       G_OBJECT_CLASS_TYPE (object_class),
822                       G_SIGNAL_RUN_LAST,
823                       G_STRUCT_OFFSET (XedFileBrowserViewClass,
824                                bookmark_activated), NULL, NULL,
825                       g_cclosure_marshal_VOID__BOXED,
826                       G_TYPE_NONE, 1, GTK_TYPE_TREE_ITER);
827 }
828 
829 static void
xed_file_browser_view_class_finalize(XedFileBrowserViewClass * klass)830 xed_file_browser_view_class_finalize (XedFileBrowserViewClass *klass)
831 {
832     /* dummy function - used by G_DEFINE_DYNAMIC_TYPE */
833 }
834 
835 static void
cell_data_cb(GtkTreeViewColumn * tree_column,GtkCellRenderer * cell,GtkTreeModel * tree_model,GtkTreeIter * iter,XedFileBrowserView * obj)836 cell_data_cb (GtkTreeViewColumn  *tree_column,
837               GtkCellRenderer    *cell,
838               GtkTreeModel       *tree_model,
839               GtkTreeIter        *iter,
840               XedFileBrowserView *obj)
841 {
842     GtkTreePath *path;
843     PangoUnderline underline = PANGO_UNDERLINE_NONE;
844     gboolean editable = FALSE;
845 
846     path = gtk_tree_model_get_path (tree_model, iter);
847 
848     if (XED_IS_FILE_BROWSER_STORE (tree_model))
849     {
850         if (obj->priv->editable != NULL && gtk_tree_row_reference_valid (obj->priv->editable))
851         {
852             GtkTreePath *edpath = gtk_tree_row_reference_get_path (obj->priv->editable);
853 
854             editable = edpath && gtk_tree_path_compare (path, edpath) == 0;
855         }
856     }
857 
858     gtk_tree_path_free (path);
859     g_object_set (cell, "editable", editable, "underline", underline, NULL);
860 }
861 
862 static void
xed_file_browser_view_init(XedFileBrowserView * obj)863 xed_file_browser_view_init (XedFileBrowserView *obj)
864 {
865     obj->priv = xed_file_browser_view_get_instance_private (obj);
866 
867     obj->priv->column = gtk_tree_view_column_new ();
868 
869     obj->priv->pixbuf_renderer = gtk_cell_renderer_pixbuf_new ();
870     gtk_tree_view_column_pack_start (obj->priv->column, obj->priv->pixbuf_renderer, FALSE);
871     gtk_tree_view_column_add_attribute (obj->priv->column,
872                                         obj->priv->pixbuf_renderer,
873                                         "pixbuf",
874                                         XED_FILE_BROWSER_STORE_COLUMN_ICON);
875 
876     obj->priv->text_renderer = gtk_cell_renderer_text_new ();
877     gtk_tree_view_column_pack_start (obj->priv->column, obj->priv->text_renderer, TRUE);
878     gtk_tree_view_column_add_attribute (obj->priv->column,
879                                         obj->priv->text_renderer,
880                                         "text",
881                                         XED_FILE_BROWSER_STORE_COLUMN_NAME);
882 
883     g_signal_connect (obj->priv->text_renderer, "edited",
884                       G_CALLBACK (on_cell_edited), obj);
885 
886     gtk_tree_view_append_column (GTK_TREE_VIEW (obj), obj->priv->column);
887     gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (obj), FALSE);
888 
889     gtk_tree_view_enable_model_drag_source (GTK_TREE_VIEW (obj),
890                                             GDK_BUTTON1_MASK,
891                                             drag_source_targets,
892                                             G_N_ELEMENTS (drag_source_targets),
893                                             GDK_ACTION_COPY);
894 }
895 
896 static gboolean
bookmarks_separator_func(GtkTreeModel * model,GtkTreeIter * iter,gpointer user_data)897 bookmarks_separator_func (GtkTreeModel *model,
898                           GtkTreeIter  *iter,
899                           gpointer      user_data)
900 {
901     guint flags;
902 
903     gtk_tree_model_get (model, iter, XED_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, &flags, -1);
904 
905     return (flags & XED_FILE_BOOKMARKS_STORE_IS_SEPARATOR);
906 }
907 
908 /* Public */
909 GtkWidget *
xed_file_browser_view_new(void)910 xed_file_browser_view_new (void)
911 {
912     XedFileBrowserView *obj = XED_FILE_BROWSER_VIEW (g_object_new (XED_TYPE_FILE_BROWSER_VIEW, NULL));
913 
914     return GTK_WIDGET (obj);
915 }
916 
917 void
xed_file_browser_view_set_model(XedFileBrowserView * tree_view,GtkTreeModel * model)918 xed_file_browser_view_set_model (XedFileBrowserView *tree_view,
919                                  GtkTreeModel       *model)
920 {
921     GtkTreeSelection *selection;
922 
923     if (tree_view->priv->model == model)
924     {
925         return;
926     }
927 
928     selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view));
929 
930     if (XED_IS_FILE_BOOKMARKS_STORE (model))
931     {
932         gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
933         gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW
934                                               (tree_view),
935                                               bookmarks_separator_func,
936                                               NULL, NULL);
937         gtk_tree_view_column_set_cell_data_func (tree_view->priv->
938                                                  column,
939                                                  tree_view->priv->
940                                                  text_renderer,
941                                                  (GtkTreeCellDataFunc)
942                                                  cell_data_cb,
943                                                  tree_view, NULL);
944     }
945     else
946     {
947         gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
948         gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW
949                                               (tree_view), NULL,
950                                               NULL, NULL);
951         gtk_tree_view_column_set_cell_data_func (tree_view->priv->
952                                                  column,
953                                                  tree_view->priv->
954                                                  text_renderer,
955                                                  (GtkTreeCellDataFunc)
956                                                  cell_data_cb,
957                                                  tree_view, NULL);
958 
959         if (tree_view->priv->restore_expand_state)
960         {
961             install_restore_signals (tree_view, model);
962         }
963 
964     }
965 
966     if (tree_view->priv->hover_path != NULL)
967     {
968         gtk_tree_path_free (tree_view->priv->hover_path);
969         tree_view->priv->hover_path = NULL;
970     }
971 
972     if (XED_IS_FILE_BROWSER_STORE (tree_view->priv->model))
973     {
974         if (tree_view->priv->restore_expand_state)
975         {
976             uninstall_restore_signals (tree_view, tree_view->priv->model);
977         }
978     }
979 
980     tree_view->priv->model = model;
981     gtk_tree_view_set_model (GTK_TREE_VIEW (tree_view), model);
982 }
983 
984 void
xed_file_browser_view_start_rename(XedFileBrowserView * tree_view,GtkTreeIter * iter)985 xed_file_browser_view_start_rename (XedFileBrowserView *tree_view,
986                                     GtkTreeIter        *iter)
987 {
988     guint flags;
989     GtkTreeRowReference *rowref;
990     GtkTreePath *path;
991 
992     g_return_if_fail (XED_IS_FILE_BROWSER_VIEW (tree_view));
993     g_return_if_fail (XED_IS_FILE_BROWSER_STORE (tree_view->priv->model));
994     g_return_if_fail (iter != NULL);
995 
996     gtk_tree_model_get (tree_view->priv->model, iter, XED_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, -1);
997 
998     if (!(FILE_IS_DIR (flags) || !FILE_IS_DUMMY (flags)))
999     {
1000         return;
1001     }
1002 
1003     path = gtk_tree_model_get_path (tree_view->priv->model, iter);
1004     rowref = gtk_tree_row_reference_new (tree_view->priv->model, path);
1005 
1006     /* Start editing */
1007     gtk_widget_grab_focus (GTK_WIDGET (tree_view));
1008 
1009     if (gtk_tree_path_up (path))
1010     {
1011         gtk_tree_view_expand_to_path (GTK_TREE_VIEW (tree_view), path);
1012     }
1013 
1014     gtk_tree_path_free (path);
1015     tree_view->priv->editable = rowref;
1016 
1017     gtk_tree_view_set_cursor (GTK_TREE_VIEW (tree_view),
1018                               gtk_tree_row_reference_get_path (tree_view->priv->editable),
1019                               tree_view->priv->column, TRUE);
1020 
1021     gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (tree_view),
1022                                   gtk_tree_row_reference_get_path (tree_view->priv->editable),
1023                                   tree_view->priv->column,
1024                                   FALSE, 0.0, 0.0);
1025 }
1026 
1027 void
xed_file_browser_view_set_restore_expand_state(XedFileBrowserView * tree_view,gboolean restore_expand_state)1028 xed_file_browser_view_set_restore_expand_state (XedFileBrowserView *tree_view,
1029                                                 gboolean            restore_expand_state)
1030 {
1031     g_return_if_fail (XED_IS_FILE_BROWSER_VIEW (tree_view));
1032 
1033     set_restore_expand_state (tree_view, restore_expand_state);
1034     g_object_notify (G_OBJECT (tree_view), "restore-expand-state");
1035 }
1036 
1037 /* Signal handlers */
1038 static void
on_cell_edited(GtkCellRendererText * cell,gchar * path,gchar * new_text,XedFileBrowserView * tree_view)1039 on_cell_edited (GtkCellRendererText *cell,
1040                 gchar               *path,
1041                 gchar               *new_text,
1042                 XedFileBrowserView  *tree_view)
1043 {
1044     GtkTreePath * treepath;
1045     GtkTreeIter iter;
1046     gboolean ret;
1047     GError * error = NULL;
1048 
1049     gtk_tree_row_reference_free (tree_view->priv->editable);
1050     tree_view->priv->editable = NULL;
1051 
1052     if (new_text == NULL || *new_text == '\0')
1053     {
1054         return;
1055     }
1056 
1057     treepath = gtk_tree_path_new_from_string (path);
1058     ret = gtk_tree_model_get_iter (GTK_TREE_MODEL (tree_view->priv->model), &iter, treepath);
1059     gtk_tree_path_free (treepath);
1060 
1061     if (ret)
1062     {
1063         if (xed_file_browser_store_rename (XED_FILE_BROWSER_STORE (tree_view->priv->model), &iter, new_text, &error))
1064         {
1065             treepath = gtk_tree_model_get_path (GTK_TREE_MODEL (tree_view->priv->model), &iter);
1066             gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (tree_view), treepath, NULL, FALSE, 0.0, 0.0);
1067             gtk_tree_path_free (treepath);
1068         }
1069         else
1070         {
1071             if (error)
1072             {
1073                 g_signal_emit (tree_view, signals[ERROR], 0, error->code, error->message);
1074                 g_error_free (error);
1075             }
1076         }
1077     }
1078 }
1079 
1080 static void
on_begin_refresh(XedFileBrowserStore * model,XedFileBrowserView * view)1081 on_begin_refresh (XedFileBrowserStore *model,
1082                   XedFileBrowserView  *view)
1083 {
1084     /* Store the refresh state, so we can handle unloading of nodes while
1085        refreshing properly */
1086     view->priv->is_refresh = TRUE;
1087 }
1088 
1089 static void
on_end_refresh(XedFileBrowserStore * model,XedFileBrowserView * view)1090 on_end_refresh (XedFileBrowserStore *model,
1091                 XedFileBrowserView  *view)
1092 {
1093     /* Store the refresh state, so we can handle unloading of nodes while
1094        refreshing properly */
1095     view->priv->is_refresh = FALSE;
1096 }
1097 
1098 static void
on_unload(XedFileBrowserStore * model,GFile * location,XedFileBrowserView * view)1099 on_unload (XedFileBrowserStore *model,
1100            GFile               *location,
1101            XedFileBrowserView  *view)
1102 {
1103     /* Don't remove the expand state if we are refreshing */
1104     if (!view->priv->restore_expand_state || view->priv->is_refresh)
1105     {
1106         return;
1107     }
1108 
1109     remove_expand_state (view, location);
1110 }
1111 
1112 static void
restore_expand_state(XedFileBrowserView * view,XedFileBrowserStore * model,GtkTreeIter * iter)1113 restore_expand_state (XedFileBrowserView  *view,
1114                       XedFileBrowserStore *model,
1115                       GtkTreeIter         *iter)
1116 {
1117     GFile *location;
1118 
1119     gtk_tree_model_get (GTK_TREE_MODEL (model),
1120                         iter,
1121                         XED_FILE_BROWSER_STORE_COLUMN_LOCATION,
1122                         &location,
1123                         -1);
1124 
1125     if (location)
1126     {
1127         GtkTreePath *path;
1128 
1129         path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), iter);
1130 
1131         if (g_hash_table_lookup (view->priv->expand_state, location))
1132         {
1133             gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, FALSE);
1134         }
1135 
1136         gtk_tree_path_free (path);
1137         g_object_unref (location);
1138     }
1139 }
1140 
1141 static void
on_row_inserted(XedFileBrowserStore * model,GtkTreePath * path,GtkTreeIter * iter,XedFileBrowserView * view)1142 on_row_inserted (XedFileBrowserStore *model,
1143                  GtkTreePath         *path,
1144                  GtkTreeIter         *iter,
1145                  XedFileBrowserView  *view)
1146 {
1147     GtkTreeIter parent;
1148     GtkTreePath * copy;
1149 
1150     if (gtk_tree_model_iter_has_child (GTK_TREE_MODEL (model), iter))
1151     {
1152         restore_expand_state (view, model, iter);
1153     }
1154 
1155     copy = gtk_tree_path_copy (path);
1156 
1157     if (gtk_tree_path_up (copy) &&
1158         (gtk_tree_path_get_depth (copy) != 0) &&
1159         gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &parent, copy))
1160     {
1161         restore_expand_state (view, model, &parent);
1162     }
1163 
1164     gtk_tree_path_free (copy);
1165 }
1166 
1167 void
_xed_file_browser_view_register_type(GTypeModule * type_module)1168 _xed_file_browser_view_register_type (GTypeModule *type_module)
1169 {
1170     xed_file_browser_view_register_type (type_module);
1171 }
1172 
1173 // ex:ts=8:noet:
1174