1 /*
2  *      fm-places-view.c
3  *
4  *      Copyright 2009 - 2012 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
5  *      Copyright 2012-2015 Andriy Grytsenko (LStranger) <andrej@rep.kiev.ua>
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 of the License, or
10  *      (at your option) 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 Street, Fifth Floor, Boston,
20  *      MA 02110-1301, USA.
21  */
22 
23 /**
24  * SECTION:fm-places-view
25  * @short_description: A widget for side panel with places list.
26  * @title: FmPlacesView
27  *
28  * @include: libfm/fm-gtk.h
29  *
30  * The #FmPlacesView displays list of pseudo-folders which contains
31  * such items as Home directory, Trash bin, mounted removable drives,
32  * bookmarks, etc.
33  */
34 
35 #ifdef HAVE_CONFIG_H
36 #include <config.h>
37 #endif
38 
39 #define FM_DISABLE_SEAL
40 
41 #include <glib/gi18n-lib.h>
42 #include "fm-places-view.h"
43 #include "fm-config.h"
44 #include "fm-utils.h"
45 #include "fm-gtk-utils.h"
46 #include "fm-bookmarks.h"
47 #include "fm-file-menu.h"
48 #include "fm-cell-renderer-pixbuf.h"
49 #include "fm-dnd-auto-scroll.h"
50 #include "fm-places-model.h"
51 #include "fm-gtk-file-launcher.h"
52 #include "fm-gtk-marshal.h"
53 
54 #include <gdk/gdkkeysyms.h>
55 #include "gtk-compat.h"
56 
57 enum
58 {
59     PROP_0,
60     PROP_HOME_DIR
61 };
62 
63 enum
64 {
65     CHDIR,
66     ITEM_POPUP,
67     N_SIGNALS
68 };
69 
70 static void activate_row(FmPlacesView* view, guint button, GtkTreePath* tree_path);
71 static void on_row_activated( GtkTreeView* view, GtkTreePath* tree_path, GtkTreeViewColumn *col);
72 static gboolean on_button_press(GtkWidget* view, GdkEventButton* evt);
73 static gboolean on_button_release(GtkWidget* view, GdkEventButton* evt);
74 
75 static void on_mount(GtkAction* act, gpointer user_data);
76 static void on_umount(GtkAction* act, gpointer user_data);
77 static void on_eject(GtkAction* act, gpointer user_data);
78 static void on_format(GtkAction* act, gpointer user_data);
79 
80 static void on_remove_bm(GtkAction* act, gpointer user_data);
81 static void on_rename_bm(GtkAction* act, gpointer user_data);
82 static void on_move_bm_up(GtkAction* act, gpointer user_data);
83 static void on_move_bm_down(GtkAction* act, gpointer user_data);
84 static void on_empty_trash(GtkAction* act, gpointer user_data);
85 
86 static gboolean on_dnd_dest_files_dropped(FmDndDest* dd, int x, int y, GdkDragAction action,
87                                           FmDndDestTargetType info_type,
88                                           FmPathList* files, FmPlacesView* view);
89 
90 //static void on_trash_changed(GFileMonitor *monitor, GFile *gf, GFile *other, GFileMonitorEvent evt, gpointer user_data);
91 //static void on_use_trash_changed(FmConfig* cfg, gpointer unused);
92 //static void on_pane_icon_size_changed(FmConfig* cfg, gpointer unused);
93 
94 G_DEFINE_TYPE(FmPlacesView, fm_places_view, GTK_TYPE_TREE_VIEW);
95 
96 /** One common FmPlacesModel for all FmPlacesView instances */
97 static FmPlacesModel* model = NULL;
98 
99 static guint signals[N_SIGNALS];
100 
101 #define PLACES_MENU_XML \
102 "<popup>" \
103   "<placeholder name='ph1'/>" \
104   "<separator/>" \
105   "<placeholder name='ph2'/>" \
106   "<separator/>" \
107   "<placeholder name='ph3'/>" \
108 "</popup>"
109 
110 static const char vol_menu_xml[]=
111 PLACES_MENU_XML
112 "<popup>"
113   "<placeholder name='ph3'>"
114   "<menuitem action='Mount'/>"
115   "<menuitem action='Unmount'/>"
116   "<menuitem action='Eject'/>"
117   "<menuitem action='Format'/>"
118   "</placeholder>"
119 "</popup>";
120 
121 static const char mount_menu_xml[]=
122 PLACES_MENU_XML
123 "<popup>"
124   "<placeholder name='ph3'>"
125   "<menuitem action='Unmount'/>"
126   "</placeholder>"
127 "</popup>";
128 
129 static GtkActionEntry vol_menu_actions[]=
130 {
131     {"Mount", NULL, N_("_Mount Volume"), NULL, NULL, G_CALLBACK(on_mount)},
132     {"Unmount", NULL, N_("_Unmount Volume"), NULL, NULL, G_CALLBACK(on_umount)},
133     {"Eject", NULL, N_("_Eject Removable Media"), NULL, NULL, G_CALLBACK(on_eject)},
134     {"Format", NULL, N_("_Format Volume"), NULL, NULL, G_CALLBACK(on_format)}
135 };
136 
137 static const char bookmark_menu_xml[]=
138 PLACES_MENU_XML
139 "<popup>"
140   "<placeholder name='ph3'>"
141   "<menuitem action='RenameBm'/>"
142   "<menuitem action='RemoveBm'/>"
143   "<menuitem action='MoveBmUp'/>"
144   "<menuitem action='MoveBmDown'/>"
145   "</placeholder>"
146 "</popup>";
147 
148 static GtkActionEntry bm_menu_actions[]=
149 {
150     {"RenameBm", GTK_STOCK_EDIT, N_("_Rename Bookmark Item"), NULL, NULL, G_CALLBACK(on_rename_bm)},
151     {"RemoveBm", GTK_STOCK_REMOVE, N_("Re_move from Bookmarks"), NULL, NULL, G_CALLBACK(on_remove_bm)},
152     {"MoveBmUp", GTK_STOCK_GO_UP, N_("Move Bookmark _Up"), NULL, NULL, G_CALLBACK(on_move_bm_up)},
153     {"MoveBmDown", GTK_STOCK_GO_DOWN, N_("Move Bookmark _Down"), NULL, NULL, G_CALLBACK(on_move_bm_down)}
154 };
155 
156 static const char trash_menu_xml[]=
157 PLACES_MENU_XML
158 "<popup>"
159   "<placeholder name='ph3'>"
160   "<menuitem action='EmptyTrash'/>"
161   "</placeholder>"
162 "</popup>";
163 
164 static GtkActionEntry trash_menu_actions[]=
165 {
166     {"EmptyTrash", NULL, N_("_Empty Trash Can"), NULL, NULL, G_CALLBACK(on_empty_trash)}
167 };
168 
169 /* targets are added to FmDndDest, also only targets for GtkTreeDragSource */
170 enum {
171     FM_DND_TARGET_BOOOKMARK = N_FM_DND_DEST_DEFAULT_TARGETS
172 };
173 
174 /* Target types for dragging items of list */
175 static const GtkTargetEntry dnd_targets[] = {
176     { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, FM_DND_TARGET_BOOOKMARK }
177 };
178 
179 static GdkAtom tree_model_row_atom;
180 
sep_func(GtkTreeModel * model,GtkTreeIter * it,gpointer data)181 static gboolean sep_func( GtkTreeModel* model, GtkTreeIter* it, gpointer data )
182 {
183     return fm_places_model_iter_is_separator(FM_PLACES_MODEL(model), it);
184 }
185 
on_renderer_icon_size_changed(FmConfig * cfg,gpointer user_data)186 static void on_renderer_icon_size_changed(FmConfig* cfg, gpointer user_data)
187 {
188     FmCellRendererPixbuf* render = FM_CELL_RENDERER_PIXBUF(user_data);
189     fm_cell_renderer_pixbuf_set_fixed_size(render, fm_config->pane_icon_size, fm_config->pane_icon_size);
190 }
191 
on_cell_renderer_pixbuf_destroy(gpointer user_data,GObject * render)192 static void on_cell_renderer_pixbuf_destroy(gpointer user_data, GObject* render)
193 {
194     g_signal_handler_disconnect(fm_config, GPOINTER_TO_UINT(user_data));
195 }
196 
197 /*----------------------------------------------------------------------
198    Drag source is handled by model which implements GtkTreeDragSource */
199 
200 /*----------------------------------------------------------------------
201    Drop destination is handled by FmDndDest. We add own target there. */
202 
203 /* Given a drop path retrieved by gtk_tree_view_get_dest_row_at_pos, this function
204  * determines whether dropping a bookmark item at the specified path is allow.
205  * If dropping is not allowed, this function tries to choose an alternative position
206  * for the bookmark item and modified the tree path @tp passed into this function. */
get_bookmark_drag_dest(FmPlacesView * view,GtkTreePath ** tp,GtkTreeViewDropPosition * pos)207 static gboolean get_bookmark_drag_dest(FmPlacesView* view, GtkTreePath** tp, GtkTreeViewDropPosition* pos)
208 {
209     gboolean ret = TRUE;
210     if(*tp)
211     {
212         /* if the drop site is below the separator (in the bookmark area) */
213         if(fm_places_model_path_is_bookmark(model, *tp))
214         {
215             /* we cannot drop into a item */
216             if(*pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE ||
217                *pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER)
218                 ret = FALSE;
219             else
220                 ret = TRUE;
221         }
222         else /* the drop site is above the separator (in the places area containing volumes) */
223         {
224             GtkTreePath* sep = fm_places_model_get_separator_path(model);
225             /* set drop site at the first bookmark item */
226             gtk_tree_path_get_indices(*tp)[0] = gtk_tree_path_get_indices(sep)[0] + 1;
227             gtk_tree_path_free(sep);
228             *pos = GTK_TREE_VIEW_DROP_BEFORE;
229             ret = TRUE;
230         }
231     }
232     else
233     {
234         /* drop at end of the bookmarks list instead */
235         *tp = gtk_tree_path_new_from_indices(gtk_tree_model_iter_n_children(GTK_TREE_MODEL(model), NULL) - 1, -1);
236         *pos = GTK_TREE_VIEW_DROP_AFTER;
237         ret = TRUE;
238     }
239     /* g_debug("path: %s, pos: %d, ret: %d", gtk_tree_path_to_string(*tp), *pos, ret); */
240     return ret;
241 }
242 
on_drag_motion(GtkWidget * dest_widget,GdkDragContext * drag_context,gint x,gint y,guint time)243 static gboolean on_drag_motion (GtkWidget *dest_widget,
244                     GdkDragContext *drag_context, gint x, gint y, guint time)
245 {
246     FmPlacesView* view = FM_PLACES_VIEW(dest_widget);
247     /* fm_drag_context_has_target_name(drag_context, "GTK_TREE_MODEL_ROW"); */
248     GdkAtom target;
249     GtkTreeViewDropPosition pos;
250     GtkTreePath* tp = NULL;
251     gboolean ret = FALSE;
252     GdkDragAction action = 0;
253 
254     target = gtk_drag_dest_find_target(dest_widget, drag_context, NULL);
255     if(target == GDK_NONE)
256         return FALSE;
257 
258     gtk_tree_view_get_dest_row_at_pos(&view->parent, x, y, &tp, &pos);
259 
260     /* handle reordering bookmark items first */
261     if(target == tree_model_row_atom)
262     {
263         /* bookmark item is being dragged */
264         ret = get_bookmark_drag_dest(view, &tp, &pos);
265         action = ret ? GDK_ACTION_MOVE : 0; /* bookmark items can only be moved */
266     }
267     /* try FmDndDest */
268     else if(fm_dnd_dest_is_target_supported(view->dnd_dest, target))
269     {
270         /* the user is dragging files. get FmFileInfo of drop site. */
271         if(pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE || pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER) /* drag into items */
272         {
273             FmPlacesItem* item = NULL;
274             GtkTreeIter it;
275             FmFileInfo* fi;
276             if(tp && gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &it, tp))
277                 gtk_tree_model_get(GTK_TREE_MODEL(model), &it, FM_PLACES_MODEL_COL_INFO, &item, -1);
278 
279             fi = item ? fm_places_item_get_info(item) : NULL;
280             fm_dnd_dest_set_dest_file(view->dnd_dest, fi);
281             /* query default action (this may trigger drag-data-received signal)
282              * FIXME: this is a dirty and bad API design definitely requires refactor. */
283             action = fm_dnd_dest_get_default_action(view->dnd_dest, drag_context, target);
284         }
285         else /* drop between items, create bookmark items for dragged files */
286         {
287             fm_dnd_dest_set_dest_file(view->dnd_dest, NULL);
288             /* FmDndDest requires this call */
289             fm_dnd_dest_get_default_action(view->dnd_dest, drag_context, target);
290             if( (!tp || fm_places_model_path_is_bookmark(model, tp))
291                && get_bookmark_drag_dest(view, &tp, &pos)) /* tp is after separator */
292                 action = GDK_ACTION_LINK;
293             else
294                 action = 0;
295         }
296         ret = (action != 0);
297     }
298     gdk_drag_status(drag_context, action, time);
299 
300     if(ret)
301         gtk_tree_view_set_drag_dest_row(&view->parent, tp, pos);
302     else
303         gtk_tree_view_set_drag_dest_row(&view->parent, NULL, 0);
304 
305     if(tp)
306         gtk_tree_path_free(tp);
307 
308     return ret;
309 }
310 
on_drag_leave(GtkWidget * dest_widget,GdkDragContext * drag_context,guint time)311 static void on_drag_leave ( GtkWidget *dest_widget,
312                     GdkDragContext *drag_context, guint time)
313 {
314     gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW(dest_widget), NULL, 0);
315     /* g_debug("drag_leave"); */
316 }
317 
on_drag_drop(GtkWidget * dest_widget,GdkDragContext * drag_context,gint x,gint y,guint time)318 static gboolean on_drag_drop ( GtkWidget *dest_widget,
319                     GdkDragContext *drag_context, gint x, gint y, guint time)
320 {
321     /* this is to reorder bookmark */
322     if(gtk_drag_dest_find_target(dest_widget, drag_context, NULL)
323        == tree_model_row_atom)
324     {
325         gtk_drag_get_data(dest_widget, drag_context, tree_model_row_atom, time);
326         return TRUE;
327     }
328     return FALSE;
329 }
330 
on_drag_data_received(GtkWidget * dest_widget,GdkDragContext * drag_context,gint x,gint y,GtkSelectionData * sel_data,guint info,guint time)331 static void on_drag_data_received ( GtkWidget *dest_widget,
332                 GdkDragContext *drag_context, gint x, gint y,
333                 GtkSelectionData *sel_data, guint info, guint time)
334 {
335     FmPlacesView* view = FM_PLACES_VIEW(dest_widget);
336     GtkTreePath* dest_tp = NULL;
337     GtkTreeViewDropPosition pos;
338     gboolean ret = FALSE;
339 
340     switch(info)
341     {
342     case FM_DND_TARGET_BOOOKMARK:
343         gtk_tree_view_get_dest_row_at_pos(&view->parent, x, y, &dest_tp, &pos);
344         if(get_bookmark_drag_dest(view, &dest_tp, &pos)) /* got the drop position */
345         {
346             GtkTreePath* src_tp;
347             /* get the source row; the GtkTreeDragSource ensured it's bookmark */
348             ret = gtk_tree_get_row_drag_data(sel_data, NULL, &src_tp);
349             if(ret)
350             {
351                 /* don't do anything if source and dest are the same row */
352                 if(G_UNLIKELY(gtk_tree_path_compare(src_tp, dest_tp) == 0))
353                     ret = FALSE;
354                 else
355                 {
356                     GtkTreeIter src_it, dest_it;
357                     FmPlacesItem* item = NULL;
358                     ret = FALSE;
359                     /* get the source bookmark item */
360                     if(gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &src_it, src_tp))
361                         gtk_tree_model_get(GTK_TREE_MODEL(model), &src_it, FM_PLACES_MODEL_COL_INFO, &item, -1);
362                     if(item)
363                     {
364                         /* move it to destination position */
365                         if(gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &dest_it, dest_tp))
366                         {
367                             int new_pos, sep_pos;
368                             /* get index of the separator */
369                             GtkTreePath* sep_tp = fm_places_model_get_separator_path(model);
370                             sep_pos = gtk_tree_path_get_indices(sep_tp)[0];
371 
372                             if(pos == GTK_TREE_VIEW_DROP_BEFORE)
373                                 gtk_list_store_move_before(GTK_LIST_STORE(model), &src_it, &dest_it);
374                             else
375                                 gtk_list_store_move_after(GTK_LIST_STORE(model), &src_it, &dest_it);
376                             new_pos = gtk_tree_path_get_indices(dest_tp)[0] - sep_pos - 1;
377                             /* reorder the bookmark item */
378                             fm_bookmarks_reorder(fm_places_model_get_bookmarks(model), fm_places_item_get_bookmark_item(item), new_pos);
379                             gtk_tree_path_free(sep_tp);
380                             ret = TRUE;
381                         }
382                     }
383                     /* else it might be additional separator */
384                 }
385                 gtk_tree_path_free(src_tp);
386             }
387         }
388         gtk_drag_finish(drag_context, ret, FALSE, time);
389         break;
390     }
391     if(dest_tp)
392         gtk_tree_path_free(dest_tp);
393 }
394 
on_dnd_dest_files_dropped(FmDndDest * dd,int x,int y,GdkDragAction action,FmDndDestTargetType info_type,FmPathList * files,FmPlacesView * view)395 static gboolean on_dnd_dest_files_dropped(FmDndDest* dd, int x, int y,
396                                           GdkDragAction action,
397                                           FmDndDestTargetType info_type,
398                                           FmPathList* files, FmPlacesView* view)
399 {
400     FmPath* dest;
401     GList* l;
402     gboolean ret = FALSE;
403 
404     dest = fm_dnd_dest_get_dest_path(dd);
405     /* g_debug("action= %d, %d files-dropped!, dest=%p info_type: %d", action, fm_path_list_get_length(files), dest, info_type); */
406 
407     if(!dest && action == GDK_ACTION_LINK) /* add bookmarks */
408     {
409         GtkTreePath* tp;
410         GtkTreeViewDropPosition pos;
411         gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(view), x, y, &tp, &pos);
412 
413         if(get_bookmark_drag_dest(view, &tp, &pos))
414         {
415             GtkTreePath* sep = fm_places_model_get_separator_path(model);
416             int idx = gtk_tree_path_get_indices(tp)[0] - gtk_tree_path_get_indices(sep)[0];
417             if(pos == GTK_TREE_VIEW_DROP_BEFORE)
418                 --idx;
419             for( l=fm_path_list_peek_head_link(files); l; l=l->next, ++idx )
420             {
421                 FmPath* path = FM_PATH(l->data);
422                 GFile* gf = fm_path_to_gfile(path);
423                 if(g_file_query_file_type(gf, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
424                                           NULL) == G_FILE_TYPE_DIRECTORY)
425                 {
426                     char* disp_name = fm_path_display_basename(path);
427                     fm_bookmarks_insert(fm_places_model_get_bookmarks(model), path, disp_name, idx);
428                     g_free(disp_name);
429                 }
430                 g_object_unref(gf);
431                 /* we don't need to add item to places view. Later the bookmarks will be reloaded. */
432             }
433             gtk_tree_path_free(sep);
434         }
435         if(tp)
436             gtk_tree_path_free(tp);
437         ret = TRUE;
438     }
439 
440     return ret;
441 }
442 
443 /*----------------------------------------------------------------------
444    Widget initialization and finalization */
445 
fm_places_view_dispose(GObject * object)446 static void fm_places_view_dispose(GObject *object)
447 {
448     FmPlacesView* self;
449 
450     g_return_if_fail(object != NULL);
451     g_return_if_fail(FM_IS_PLACES_VIEW(object));
452     self = (FmPlacesView*)object;
453 
454     if(self->dnd_dest)
455     {
456         g_signal_handlers_disconnect_by_func(self->dnd_dest, on_dnd_dest_files_dropped, self);
457         g_object_unref(self->dnd_dest);
458         self->dnd_dest = NULL;
459     }
460 
461     G_OBJECT_CLASS(fm_places_view_parent_class)->dispose(object);
462 }
463 
fm_places_view_finalize(GObject * object)464 static void fm_places_view_finalize(GObject *object)
465 {
466     FmPlacesView* self;
467 
468     g_return_if_fail(object != NULL);
469     g_return_if_fail(FM_IS_PLACES_VIEW(object));
470     self = (FmPlacesView*)object;
471 
472     if(self->clicked_row)
473         gtk_tree_path_free(self->clicked_row);
474 
475     G_OBJECT_CLASS(fm_places_view_parent_class)->finalize(object);
476 }
477 
fm_places_view_init(FmPlacesView * self)478 static void fm_places_view_init(FmPlacesView *self)
479 {
480     GtkTreeViewColumn* col;
481     GtkCellRenderer* renderer;
482     AtkObject *obj;
483     guint handler;
484 
485     if(G_UNLIKELY(!model))
486     {
487         model = fm_places_model_new();
488         g_object_add_weak_pointer(G_OBJECT(model), (gpointer*)&model);
489     }
490     else
491         g_object_ref(model);
492 
493     gtk_tree_view_set_model(GTK_TREE_VIEW(self), GTK_TREE_MODEL(model));
494     g_object_unref(model);
495 
496     gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(self), FALSE);
497     gtk_tree_view_set_row_separator_func(GTK_TREE_VIEW(self), sep_func, NULL, NULL);
498 
499     col = gtk_tree_view_column_new();
500     renderer = (GtkCellRenderer*)fm_cell_renderer_pixbuf_new();
501     handler = g_signal_connect(fm_config, "changed::pane_icon_size", G_CALLBACK(on_renderer_icon_size_changed), renderer);
502     g_object_weak_ref(G_OBJECT(renderer), on_cell_renderer_pixbuf_destroy, GUINT_TO_POINTER(handler));
503     fm_cell_renderer_pixbuf_set_fixed_size((FmCellRendererPixbuf*)renderer, fm_config->pane_icon_size, fm_config->pane_icon_size);
504 
505     gtk_tree_view_column_pack_start( col, renderer, FALSE );
506     gtk_tree_view_column_set_attributes( col, renderer,
507                                          "pixbuf", FM_PLACES_MODEL_COL_ICON, NULL );
508 
509     renderer = gtk_cell_renderer_text_new();
510     gtk_tree_view_column_pack_start( col, renderer, TRUE );
511     g_object_set(renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
512     gtk_tree_view_column_set_attributes( col, renderer,
513                                          "text", FM_PLACES_MODEL_COL_LABEL, NULL );
514 
515     renderer = gtk_cell_renderer_pixbuf_new();
516     self->mount_indicator_renderer = GTK_CELL_RENDERER_PIXBUF(renderer);
517     gtk_tree_view_column_pack_start( col, renderer, FALSE );
518     gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(col), renderer,
519                                        fm_places_model_mount_indicator_cell_data_func,
520                                        NULL, NULL);
521 
522     gtk_tree_view_append_column ( GTK_TREE_VIEW(self), col );
523 
524     gtk_tree_view_enable_model_drag_source(GTK_TREE_VIEW(self), GDK_BUTTON1_MASK,
525                       dnd_targets, G_N_ELEMENTS(dnd_targets), GDK_ACTION_MOVE);
526 
527     self->dnd_dest = fm_dnd_dest_new_with_handlers(GTK_WIDGET(self));
528     /* add our own targets */
529     fm_dnd_dest_add_targets(GTK_WIDGET(self), dnd_targets, G_N_ELEMENTS(dnd_targets));
530 
531     g_signal_connect(self->dnd_dest, "files-dropped", G_CALLBACK(on_dnd_dest_files_dropped), self);
532     obj = gtk_widget_get_accessible(GTK_WIDGET(self));
533     atk_object_set_description(obj, _("Shows list of common places, devices, and bookmarks in sidebar"));
534 }
535 
536 /*----------------------------------------------------------------------
537    Widget interface */
538 
539 /**
540  * fm_places_view_new
541  *
542  * Creates new #FmPlacesView widget.
543  *
544  * Returns: (transfer full): a new #FmPlacesView object.
545  *
546  * Since: 0.1.0
547  */
fm_places_view_new(void)548 FmPlacesView *fm_places_view_new(void)
549 {
550     return g_object_new(FM_PLACES_VIEW_TYPE, NULL);
551 }
552 
activate_row(FmPlacesView * view,guint button,GtkTreePath * tree_path)553 static void activate_row(FmPlacesView* view, guint button, GtkTreePath* tree_path)
554 {
555     GtkTreeIter it;
556     if(gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &it, tree_path))
557     {
558         FmPlacesItem* item;
559         FmPath* path;
560         gtk_tree_model_get(GTK_TREE_MODEL(model), &it, FM_PLACES_MODEL_COL_INFO, &item, -1);
561         if(!item)
562             return;
563         switch(fm_places_item_get_type(item))
564         {
565         case FM_PLACES_ITEM_PATH:
566         case FM_PLACES_ITEM_MOUNT:
567             path = fm_places_item_get_path(item);
568             if (path == fm_path_get_home() && view->home_dir)
569                 path = fm_path_new_for_str(view->home_dir);
570             else
571                 fm_path_ref(path);
572             break;
573         case FM_PLACES_ITEM_VOLUME:
574         {
575             GFile* gf;
576             GMount* mnt = g_volume_get_mount(fm_places_item_get_volume(item));
577             if(!mnt)
578             {
579                 GtkWindow* parent = GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(view)));
580                 if(!fm_mount_volume(parent, fm_places_item_get_volume(item), TRUE))
581                     return;
582                 mnt = g_volume_get_mount(fm_places_item_get_volume(item));
583                 if(!mnt)
584                 {
585                     g_debug("GMount is invalid after successful g_volume_mount().\nThis is quite possibly a gvfs bug.\nSee https://bugzilla.gnome.org/show_bug.cgi?id=552168");
586                     return;
587                 }
588             }
589             gf = g_mount_get_root(mnt);
590             g_object_unref(mnt);
591             if(gf)
592             {
593                 path = fm_path_new_for_gfile(gf);
594                 g_object_unref(gf);
595             }
596             else
597                 path = NULL;
598             break;
599         }
600         default:
601             return;
602         }
603 
604         if(path)
605         {
606             g_signal_emit(view, signals[CHDIR], 0, button, path);
607             fm_path_unref(path);
608         }
609     }
610 }
611 
on_row_activated(GtkTreeView * view,GtkTreePath * tree_path,GtkTreeViewColumn * col)612 static void on_row_activated(GtkTreeView* view, GtkTreePath* tree_path, GtkTreeViewColumn *col)
613 {
614     activate_row(FM_PLACES_VIEW(view), 1, tree_path);
615 }
616 
617 /**
618  * fm_places_view_chdir
619  * @pv: a widget to apply
620  * @path: the new path
621  *
622  * Changes active path and eventually sends the #FmPlacesView::chdir signal.
623  *
624  * Before 1.0.0 this call had name fm_places_chdir.
625  * Before 0.1.12 this call had name fm_places_select.
626  *
627  * Since: 0.1.0
628  */
fm_places_view_chdir(FmPlacesView * pv,FmPath * path)629 void fm_places_view_chdir(FmPlacesView* pv, FmPath* path)
630 {
631     GtkTreeIter it;
632     GtkTreeModel* model = gtk_tree_view_get_model(GTK_TREE_VIEW(pv));
633     GtkTreeSelection* ts = gtk_tree_view_get_selection(GTK_TREE_VIEW(pv));
634     if(fm_places_model_get_iter_by_fm_path(FM_PLACES_MODEL(model), &it, path))
635     {
636         gtk_tree_selection_select_iter(ts, &it);
637     }
638     else
639         gtk_tree_selection_unselect_all(ts);
640 }
641 
on_button_release(GtkWidget * widget,GdkEventButton * evt)642 static gboolean on_button_release(GtkWidget* widget, GdkEventButton* evt)
643 {
644     FmPlacesView* view = FM_PLACES_VIEW(widget);
645     gboolean ret = GTK_WIDGET_CLASS(fm_places_view_parent_class)->button_release_event(widget, evt);
646 
647     /* we should finish the event before we do our work, otherwise gtk may
648        activate already removed row in default handler */
649     if(view->clicked_row)
650     {
651         if(evt->button == 1)
652         {
653             GtkTreePath* tp;
654             GtkTreeViewColumn* col;
655             int cell_x;
656             if(gtk_tree_view_get_path_at_pos(&view->parent, evt->x, evt->y, &tp, &col, &cell_x, NULL))
657             {
658                 /* check if we release the button on the row we previously clicked. */
659                 if(gtk_tree_path_compare(tp, view->clicked_row) == 0)
660                 {
661                     /* check if we click on the "eject" icon. */
662                     int start, cell_w;
663                     gtk_tree_view_column_cell_get_position(col, GTK_CELL_RENDERER(view->mount_indicator_renderer),
664                                                            &start, &cell_w);
665                     if(cell_x > start && cell_x < (start + cell_w)) /* click on eject icon */
666                     {
667                         GtkTreeIter it;
668                         /* do eject if needed */
669                         if(gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &it, tp))
670                         {
671                             FmPlacesItem* item;
672                             gtk_tree_model_get(GTK_TREE_MODEL(model), &it, FM_PLACES_MODEL_COL_INFO, &item, -1);
673                             if(item && fm_places_item_is_mounted(item))
674                             {
675                                 GtkWindow* toplevel = GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(view)));
676                                 GMount* mount;
677                                 GVolume* volume;
678 
679                                 gtk_tree_path_free(view->clicked_row);
680                                 view->clicked_row = NULL;
681                                 gtk_tree_path_free(tp);
682                                 switch(fm_places_item_get_type(item))
683                                 {
684                                 case FM_PLACES_ITEM_VOLUME:
685                                     volume = fm_places_item_get_volume(item);
686                                     /* eject the volume */
687                                     if(g_volume_can_eject(volume))
688                                         fm_eject_volume(toplevel, volume, TRUE);
689                                     else /* not ejectable, do unmount */
690                                     {
691                                         mount = g_volume_get_mount(volume);
692                                         if(mount)
693                                         {
694                                             fm_unmount_mount(toplevel, mount, TRUE);
695                                             g_object_unref(mount);
696                                         }
697                                     }
698                                     break;
699                                 case FM_PLACES_ITEM_MOUNT:
700                                     mount = fm_places_item_get_mount(item);
701                                     if(g_mount_can_unmount(mount))
702                                         fm_unmount_mount(toplevel, mount, TRUE);
703                                     break;
704                                 default:
705                                     break;
706                                 }
707                                 /* bug #3614500: if we unmount volume, the main
708                                    window handlers will destroy the FmPlacesView
709                                    therefore we cannot touch it at this point */
710                                 goto _out;
711                             }
712                         }
713                     }
714                     /* activate the clicked row. */
715                     gtk_tree_view_row_activated(&view->parent, view->clicked_row, col);
716                 }
717                 gtk_tree_path_free(tp);
718             }
719         }
720 
721         gtk_tree_path_free(view->clicked_row);
722         view->clicked_row = NULL;
723     }
724 _out:
725     return ret;
726 }
727 
on_selection_done(GtkMenu * menu,gpointer unused)728 static void on_selection_done(GtkMenu *menu, gpointer unused)
729 {
730     GtkWidget *widget = gtk_menu_get_attach_widget(menu);
731 
732     /* g_debug("FmPlacesView:on_selection_done(): attached widget %p", widget); */
733     if (widget) /* it may be destroyed and detached already */
734         g_object_weak_unref(G_OBJECT(widget), (GWeakNotify)gtk_menu_detach, menu);
735     gtk_widget_destroy(GTK_WIDGET(menu));
736 }
737 
place_item_menu_unref(gpointer ui,GObject * menu)738 static void place_item_menu_unref(gpointer ui, GObject *menu)
739 {
740     g_object_unref(ui);
741 }
742 
place_item_get_menu(FmPlacesItem * item,GtkWidget * widget)743 static GtkWidget* place_item_get_menu(FmPlacesItem* item, GtkWidget *widget)
744 {
745     GtkWidget* menu = NULL;
746     GtkUIManager* ui = gtk_ui_manager_new();
747     GtkActionGroup* act_grp = gtk_action_group_new("Popup");
748     int sep_pos;
749     GtkTreeIter src_it;
750     GtkTreePath *tp, *sep_tp;
751     GtkAction* act;
752 
753     gtk_action_group_set_translation_domain(act_grp, GETTEXT_PACKAGE);
754 
755     /* FIXME: merge with FmFileMenu when possible */
756     if(fm_places_item_get_type(item) == FM_PLACES_ITEM_PATH)
757     {
758         if(fm_places_item_get_bookmark_item(item))
759         {
760             gtk_action_group_add_actions(act_grp, bm_menu_actions, G_N_ELEMENTS(bm_menu_actions), item);
761             gtk_ui_manager_add_ui_from_string(ui, bookmark_menu_xml, -1, NULL);
762             /* check and disable MoveBmUp and MoveBmDown */
763             if(fm_places_model_get_iter_by_fm_path(model, &src_it,
764                                                    fm_places_item_get_path(item)))
765             {
766                 /* get index of the separator */
767                 sep_tp = fm_places_model_get_separator_path(model);
768                 sep_pos = gtk_tree_path_get_indices(sep_tp)[0];
769                 tp = gtk_tree_model_get_path(GTK_TREE_MODEL(model), &src_it);
770                 if(!gtk_tree_path_prev(tp) ||
771                    gtk_tree_path_get_indices(tp)[0] - sep_pos - 1 < 0)
772                 {
773                     act = gtk_action_group_get_action(act_grp, "MoveBmUp");
774                     gtk_action_set_sensitive(act, FALSE);
775                 }
776                 if(!gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &src_it))
777                 {
778                     act = gtk_action_group_get_action(act_grp, "MoveBmDown");
779                     gtk_action_set_sensitive(act, FALSE);
780                 }
781                 gtk_tree_path_free(sep_tp);
782                 gtk_tree_path_free(tp);
783             }
784         }
785         else if(fm_path_is_trash_root(fm_places_item_get_path(item)))
786         {
787             gtk_action_group_add_actions(act_grp, trash_menu_actions, G_N_ELEMENTS(trash_menu_actions), item);
788             gtk_ui_manager_add_ui_from_string(ui, trash_menu_xml, -1, NULL);
789         }
790     }
791     else if(fm_places_item_get_type(item) == FM_PLACES_ITEM_VOLUME)
792     {
793         GVolume *vol = fm_places_item_get_volume(item);
794         GMount* mnt;
795         char *unix_path = NULL;
796         gtk_action_group_add_actions(act_grp, vol_menu_actions, G_N_ELEMENTS(vol_menu_actions), item);
797         gtk_ui_manager_add_ui_from_string(ui, vol_menu_xml, -1, NULL);
798 
799         mnt = g_volume_get_mount(vol);
800         if(mnt) /* mounted */
801         {
802             g_object_unref(mnt);
803             act = gtk_action_group_get_action(act_grp, "Mount");
804             gtk_action_set_visible(act, FALSE);
805             act = gtk_action_group_get_action(act_grp, "Unmount");
806             gtk_action_set_sensitive(act, g_mount_can_unmount(mnt));
807         }
808         else /* not mounted */
809         {
810             if (fm_config->format_cmd && fm_config->format_cmd[0])
811                 unix_path = g_volume_get_identifier(vol,
812                                                     G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
813             if (unix_path && unix_path[0] != '/') /* we can format only local */
814             {
815                 g_free(unix_path);
816                 unix_path = NULL;
817             }
818             g_free(unix_path); /* use it to mark only */
819             act = gtk_action_group_get_action(act_grp, "Unmount");
820             gtk_action_set_visible(act, FALSE);
821             act = gtk_action_group_get_action(act_grp, "Mount");
822             gtk_action_set_sensitive(act, g_volume_can_mount(vol));
823         }
824         if (unix_path == NULL)
825         {
826             act = gtk_action_group_get_action(act_grp, "Format");
827             if (act)
828                  gtk_action_set_visible(act, FALSE);
829         }
830 
831         if(!g_volume_can_eject(fm_places_item_get_volume(item)))
832         {
833             act = gtk_action_group_get_action(act_grp, "Eject");
834             gtk_action_set_visible(act, FALSE);
835         }
836     }
837     else if(fm_places_item_get_type(item) == FM_PLACES_ITEM_MOUNT)
838     {
839         GtkAction* act;
840         GMount* mnt;
841         gtk_action_group_add_actions(act_grp, vol_menu_actions, G_N_ELEMENTS(vol_menu_actions), item);
842         gtk_ui_manager_add_ui_from_string(ui, mount_menu_xml, -1, NULL);
843 
844         mnt = fm_places_item_get_mount(item);
845         if(mnt) /* mounted */
846         {
847             act = gtk_action_group_get_action(act_grp, "Mount");
848             gtk_action_set_sensitive(act, FALSE);
849             act = gtk_action_group_get_action(act_grp, "Unmount");
850             gtk_action_set_sensitive(act, g_mount_can_unmount(mnt));
851         }
852         else /* not mounted */
853         {
854             act = gtk_action_group_get_action(act_grp, "Unmount");
855             gtk_action_set_sensitive(act, FALSE);
856         }
857         act = gtk_action_group_get_action(act_grp, "Format");
858         if (act)
859              gtk_action_set_visible(act, FALSE);
860         act = gtk_action_group_get_action(act_grp, "Eject");
861         gtk_action_set_visible(act, FALSE);
862     }
863     else
864         goto _out;
865     gtk_ui_manager_insert_action_group(ui, act_grp, 0);
866 
867     /* send the signal so popup can be altered by application */
868     g_signal_emit(widget, signals[ITEM_POPUP], 0, ui, act_grp, fm_places_item_get_info(item));
869 
870     menu = gtk_ui_manager_get_widget(ui, "/popup");
871     if(menu)
872     {
873         g_signal_connect(menu, "selection-done", G_CALLBACK(on_selection_done), NULL);
874         g_object_weak_ref(G_OBJECT(menu), place_item_menu_unref, g_object_ref(ui));
875         gtk_menu_attach_to_widget(GTK_MENU(menu), widget, NULL);
876         /* bug #3614500: widget may be destroyed in selections such as
877            Unmount therefore we should detach menu to avoid crash
878            note that we should remove this ref when we destroy menu */
879         g_object_weak_ref(G_OBJECT(widget), (GWeakNotify)gtk_menu_detach, menu);
880         gtk_ui_manager_ensure_update(ui);
881     }
882 
883 _out:
884     g_object_unref(act_grp);
885     g_object_unref(ui);
886     return menu;
887 }
888 
popup_position_func(GtkMenu * menu,gint * x,gint * y,gboolean * push_in,gpointer user_data)889 static void popup_position_func(GtkMenu *menu, gint *x, gint *y,
890                                 gboolean *push_in, gpointer user_data)
891 {
892     GtkWidget *widget = gtk_menu_get_attach_widget(menu);
893     GtkTreeView *view = GTK_TREE_VIEW(widget);
894     GtkTreePath *path;
895     GdkScreen *screen;
896     gint index = GPOINTER_TO_INT(user_data);
897     GdkRectangle cell;
898     GtkAllocation a, ma;
899     gint x2, y2, mon;
900     gboolean rtl = (gtk_widget_get_direction(widget) == GTK_TEXT_DIR_RTL);
901 
902     /* realize menu so we get actual size of it */
903     gtk_widget_realize(GTK_WIDGET(menu));
904     /* get all the relative coordinates */
905     gtk_widget_get_allocation(widget, &a);
906     screen = gtk_widget_get_screen(widget);
907     gdk_window_get_device_position(gtk_widget_get_window(widget),
908                                    gdk_device_manager_get_client_pointer(
909                                         gdk_display_get_device_manager(
910                                             gdk_screen_get_display(screen))),
911                                    &x2, &y2, NULL);
912     gtk_widget_get_allocation(GTK_WIDGET(menu), &ma);
913     path = gtk_tree_path_new_from_indices(index, -1);
914     gtk_tree_view_get_cell_area(view, path, gtk_tree_view_get_column(view, 0), &cell);
915     gtk_tree_path_free(path);
916     /* position menu inside the cell if pointer isn't already in it */
917     if(x2 < cell.x || x2 > cell.x + cell.width)
918         x2 = cell.x + cell.width/2;
919     if(y2 < cell.y || y2 > cell.y + cell.height)
920         y2 = cell.y + cell.height - cell.height/8;
921     /* get absolute coordinate of parent window - we got coords relative to it */
922     gdk_window_get_origin(gtk_widget_get_parent_window(widget), x, y);
923     /* calculate desired position for menu */
924     *x += a.x + x2;
925     *y += a.y + y2;
926     /* limit coordinates so menu will be not positioned outside of screen */
927     mon = gdk_screen_get_monitor_at_point(screen, *x, *y);
928     /* get monitor geometry into the rectangle */
929     gdk_screen_get_monitor_geometry(screen, mon, &cell);
930     if(rtl) /* RTL */
931     {
932         x2 = cell.x + cell.width;
933         if (*x < cell.x + ma.width) /* out of monitor */
934             *x = MIN(*x + ma.width, x2); /* place menu right to cursor */
935         else
936             *x = MIN(*x, x2);
937     }
938     else /* LTR */
939     {
940         if (*x + ma.width > cell.x + cell.width) /* out of monitor */
941             *x = MAX(cell.x, *x - ma.width); /* place menu left to cursor */
942         else
943             *x = MAX(cell.x, *x); /* place menu right to cursor */
944     }
945     if (*y + ma.height > cell.y + cell.height) /* out of monitor */
946         *y = MAX(cell.y, *y - ma.height); /* place menu above cursor */
947     else
948         *y = MAX(cell.y, *y); /* place menu below cursor */
949 }
950 
fm_places_item_popup(GtkWidget * widget,GtkTreeIter * it,guint32 time)951 static void fm_places_item_popup(GtkWidget *widget, GtkTreeIter *it, guint32 time)
952 {
953     if(!fm_places_model_iter_is_separator(model, it))
954     {
955         FmPlacesItem* item;
956         GtkMenu* menu;
957         GtkTreePath* path;
958         gint* indices;
959         gtk_tree_model_get(GTK_TREE_MODEL(model), it, FM_PLACES_MODEL_COL_INFO, &item, -1);
960         menu = GTK_MENU(place_item_get_menu(item, widget));
961         if(menu)
962         {
963             path = gtk_tree_model_get_path(GTK_TREE_MODEL(model), it);
964             indices = gtk_tree_path_get_indices(path);
965             gtk_menu_popup(menu, NULL, NULL, popup_position_func,
966                            GINT_TO_POINTER(indices[0]), 3, time);
967             gtk_tree_path_free(path);
968         }
969     }
970 }
971 
on_button_press(GtkWidget * widget,GdkEventButton * evt)972 static gboolean on_button_press(GtkWidget* widget, GdkEventButton* evt)
973 {
974     FmPlacesView* view = FM_PLACES_VIEW(widget);
975     GtkTreePath* tp;
976     GtkTreeViewColumn* col;
977     GtkTreeIter it;
978     gboolean ret = GTK_WIDGET_CLASS(fm_places_view_parent_class)->button_press_event(widget, evt);
979 
980     gtk_tree_view_get_path_at_pos(&view->parent, evt->x, evt->y, &tp, &col, NULL, NULL);
981     if(view->clicked_row) /* what? more than one botton clicked? */
982         gtk_tree_path_free(view->clicked_row);
983     view->clicked_row = tp;
984     if(tp)
985     {
986         switch(evt->button) /* middle click */
987         {
988         case 1: /* left click */
989             break;
990         case 2: /* middle click */
991             activate_row(view, 2, tp);
992             break;
993         case 3: /* right click */
994             if(gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &it, tp))
995                 fm_places_item_popup(widget, &it, evt->time);
996             break;
997         }
998     }
999     return ret;
1000 }
1001 
1002 /* handle 'Menu' and 'Shift+F10' here */
on_key_press(GtkWidget * widget,GdkEventKey * evt)1003 static gboolean on_key_press(GtkWidget *widget, GdkEventKey *evt)
1004 {
1005     GtkTreeModel *model;
1006     GtkTreeSelection *sel;
1007     GtkTreeIter it;
1008     int modifier = (evt->state & gtk_accelerator_get_default_mod_mask());
1009 
1010     if((evt->keyval == GDK_KEY_Menu && !modifier) ||
1011        (evt->keyval == GDK_KEY_F10 && modifier == GDK_SHIFT_MASK))
1012     {
1013         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
1014         if(gtk_tree_selection_get_selected(sel, &model, &it))
1015         {
1016             fm_places_item_popup(widget, &it, evt->time);
1017             return TRUE;
1018         }
1019     }
1020     /* let others do the job */
1021     return GTK_WIDGET_CLASS(fm_places_view_parent_class)->key_press_event(widget, evt);
1022 }
1023 
on_mount(GtkAction * act,gpointer user_data)1024 static void on_mount(GtkAction* act, gpointer user_data)
1025 {
1026     FmPlacesItem* item = (FmPlacesItem*)user_data;
1027     if(fm_places_item_get_type(item) == FM_PLACES_ITEM_VOLUME)
1028     {
1029         GMount* mnt = g_volume_get_mount(fm_places_item_get_volume(item));
1030         if(!mnt)
1031         {
1032             if(!fm_mount_volume(NULL, fm_places_item_get_volume(item), TRUE))
1033                 return;
1034         }
1035         else
1036             g_object_unref(mnt);
1037     }
1038 }
1039 
on_umount(GtkAction * act,gpointer user_data)1040 static void on_umount(GtkAction* act, gpointer user_data)
1041 {
1042     FmPlacesItem* item = (FmPlacesItem*)user_data;
1043     GMount* mnt;
1044     switch(fm_places_item_get_type(item))
1045     {
1046     case FM_PLACES_ITEM_VOLUME:
1047         mnt = g_volume_get_mount(fm_places_item_get_volume(item));
1048         break;
1049     case FM_PLACES_ITEM_MOUNT:
1050         /* FIXME: this seems to be broken. */
1051         mnt = g_object_ref(fm_places_item_get_mount(item));
1052         break;
1053     default:
1054         mnt = NULL;
1055     }
1056 
1057     if(mnt)
1058     {
1059         fm_unmount_mount(NULL, mnt, TRUE);
1060         /* NOTE: the most probably FmPlacesView is destroyed at this point */
1061         g_object_unref(mnt);
1062     }
1063 }
1064 
_get_gtk_window_from_action(GtkAction * act)1065 static GtkWindow *_get_gtk_window_from_action(GtkAction* act)
1066 {
1067     /* FIXME: This is very dirty, but it's inevitable. :-( */
1068     GSList* proxies = gtk_action_get_proxies(act);
1069     GtkWidget *menu, *view;
1070     menu = gtk_widget_get_parent(proxies->data);
1071     if (menu == NULL || !GTK_IS_MENU(menu))
1072         return NULL;
1073     view = gtk_menu_get_attach_widget((GtkMenu*)menu);
1074     return view ? GTK_WINDOW(gtk_widget_get_toplevel(view)) : NULL;
1075 }
1076 
on_eject(GtkAction * act,gpointer user_data)1077 static void on_eject(GtkAction* act, gpointer user_data)
1078 {
1079     FmPlacesItem* item = (FmPlacesItem*)user_data;
1080     if(fm_places_item_get_type(item) == FM_PLACES_ITEM_VOLUME)
1081     {
1082         fm_eject_volume(_get_gtk_window_from_action(act),
1083                         fm_places_item_get_volume(item), TRUE);
1084         /* NOTE: the most probably FmPlacesView is destroyed at this point */
1085     }
1086 }
1087 
on_format(GtkAction * act,gpointer user_data)1088 static void on_format(GtkAction* act, gpointer user_data)
1089 {
1090     FmPlacesItem* item = (FmPlacesItem*)user_data;
1091     char *unix_path;
1092 
1093     if (fm_config->format_cmd == NULL || fm_config->format_cmd[0] == 0)
1094         return;
1095     unix_path = g_volume_get_identifier(fm_places_item_get_volume(item),
1096                                         G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
1097     if (unix_path)
1098     {
1099         /* call fm_config->format_cmd for device */
1100         g_debug("formatting %s ...", unix_path);
1101         FmPath *path = fm_path_new_for_path(unix_path);
1102         g_free(unix_path);
1103         FmPathList *paths = fm_path_list_new();
1104         fm_path_list_push_tail(paths, path);
1105         fm_path_unref(path);
1106         fm_launch_command_simple(_get_gtk_window_from_action(act), NULL, 0,
1107                                  fm_config->format_cmd, paths);
1108         fm_path_list_unref(paths);
1109     }
1110 }
1111 
on_remove_bm(GtkAction * act,gpointer user_data)1112 static void on_remove_bm(GtkAction* act, gpointer user_data)
1113 {
1114     FmPlacesItem* item = (FmPlacesItem*)user_data;
1115     fm_bookmarks_remove(fm_places_model_get_bookmarks(model), fm_places_item_get_bookmark_item(item));
1116     /* FIXME: remove item from FmPlacesModel or invalidate it right now so
1117        make duplicate deletions impossible */
1118 }
1119 
on_rename_bm(GtkAction * act,gpointer user_data)1120 static void on_rename_bm(GtkAction* act, gpointer user_data)
1121 {
1122     FmPlacesItem* item = (FmPlacesItem*)user_data;
1123     char* new_name = fm_get_user_input(_get_gtk_window_from_action(act),
1124                                        _("Rename Bookmark Item"),
1125                                        _("Enter a new name:"),
1126                                        fm_places_item_get_bookmark_item(item)->name);
1127     if(new_name)
1128     {
1129         if(strcmp(new_name, fm_places_item_get_bookmark_item(item)->name))
1130         {
1131             fm_bookmarks_rename(fm_places_model_get_bookmarks(model), fm_places_item_get_bookmark_item(item), new_name);
1132         }
1133         g_free(new_name);
1134     }
1135 }
1136 
on_move_bm_up(GtkAction * act,gpointer user_data)1137 static void on_move_bm_up(GtkAction* act, gpointer user_data)
1138 {
1139     FmPlacesItem* item = (FmPlacesItem*)user_data;
1140     int new_pos, sep_pos;
1141     GtkTreeIter src_it, dest_it;
1142     GtkTreePath *tp, *sep_tp;
1143 
1144     if(!fm_places_model_get_iter_by_fm_path(model, &src_it,
1145                                             fm_places_item_get_path(item)))
1146         return; /* FIXME: print error message */
1147     /* get index of the separator */
1148     sep_tp = fm_places_model_get_separator_path(model);
1149     sep_pos = gtk_tree_path_get_indices(sep_tp)[0];
1150     tp = gtk_tree_model_get_path(GTK_TREE_MODEL(model), &src_it);
1151     if(!gtk_tree_path_prev(tp) ||
1152        (new_pos = gtk_tree_path_get_indices(tp)[0] - sep_pos - 1) < 0 ||
1153        !gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &dest_it, tp))
1154         goto _end; /* cannot move it up */
1155     gtk_list_store_move_before(GTK_LIST_STORE(model), &src_it, &dest_it);
1156     /* reorder the bookmark item */
1157     fm_bookmarks_reorder(fm_places_model_get_bookmarks(model), fm_places_item_get_bookmark_item(item), new_pos);
1158 _end:
1159     gtk_tree_path_free(sep_tp);
1160     gtk_tree_path_free(tp);
1161 }
1162 
on_move_bm_down(GtkAction * act,gpointer user_data)1163 static void on_move_bm_down(GtkAction* act, gpointer user_data)
1164 {
1165     FmPlacesItem* item = (FmPlacesItem*)user_data;
1166     int new_pos, sep_pos;
1167     GtkTreeIter src_it, dest_it;
1168     GtkTreePath *tp, *sep_tp;
1169 
1170     if(!fm_places_model_get_iter_by_fm_path(model, &src_it,
1171                                             fm_places_item_get_path(item)))
1172         return; /* FIXME: print error message */
1173     /* get index of the separator */
1174     sep_tp = fm_places_model_get_separator_path(model);
1175     sep_pos = gtk_tree_path_get_indices(sep_tp)[0];
1176     dest_it = src_it;
1177     if(!gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &dest_it))
1178         goto _end; /* cannot move it down */
1179     gtk_list_store_move_after(GTK_LIST_STORE(model), &src_it, &dest_it);
1180     /* reorder the bookmark item */
1181     tp = gtk_tree_model_get_path(GTK_TREE_MODEL(model), &src_it);
1182     new_pos = gtk_tree_path_get_indices(tp)[0] - sep_pos - 1;
1183     fm_bookmarks_reorder(fm_places_model_get_bookmarks(model), fm_places_item_get_bookmark_item(item), new_pos);
1184     gtk_tree_path_free(tp);
1185 _end:
1186     gtk_tree_path_free(sep_tp);
1187 }
1188 
on_empty_trash(GtkAction * act,gpointer user_data)1189 static void on_empty_trash(GtkAction* act, gpointer user_data)
1190 {
1191     fm_empty_trash(_get_gtk_window_from_action(act));
1192 }
1193 
1194 #if !GTK_CHECK_VERSION(3, 0, 0)
on_set_scroll_adjustments(GtkTreeView * view,GtkAdjustment * hadj,GtkAdjustment * vadj)1195 static void on_set_scroll_adjustments(GtkTreeView* view, GtkAdjustment* hadj, GtkAdjustment* vadj)
1196 {
1197     /* we don't want scroll horizontally, so we pass NULL instead of hadj. */
1198     fm_dnd_set_dest_auto_scroll(GTK_WIDGET(view), NULL, vadj);
1199     GTK_TREE_VIEW_CLASS(fm_places_view_parent_class)->set_scroll_adjustments(view, hadj, vadj);
1200 }
1201 #endif
1202 
fm_places_view_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)1203 static void fm_places_view_set_property(GObject *object,
1204                                         guint prop_id,
1205                                         const GValue *value,
1206                                         GParamSpec *pspec)
1207 {
1208     FmPlacesView *view = FM_PLACES_VIEW(object);
1209     const char *home_dir;
1210 
1211     switch( prop_id )
1212     {
1213     case PROP_HOME_DIR:
1214         home_dir = g_value_get_string(value);
1215         if (home_dir && (!*home_dir || strcmp(home_dir, fm_get_home_dir()) == 0))
1216             home_dir = NULL;
1217         g_free(view->home_dir);
1218         view->home_dir = g_strdup(home_dir);
1219         break;
1220     default:
1221         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
1222         break;
1223     }
1224 }
1225 
fm_places_view_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)1226 static void fm_places_view_get_property(GObject *object,
1227                                         guint prop_id,
1228                                         GValue *value,
1229                                         GParamSpec *pspec)
1230 {
1231     FmPlacesView *view = FM_PLACES_VIEW(object);
1232 
1233     switch( prop_id ) {
1234     case PROP_HOME_DIR:
1235         if (view->home_dir)
1236             g_value_set_string(value, view->home_dir);
1237         else
1238             g_value_set_string(value, fm_get_home_dir());
1239         break;
1240     default:
1241         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
1242         break;
1243     }
1244 }
1245 
fm_places_view_class_init(FmPlacesViewClass * klass)1246 static void fm_places_view_class_init(FmPlacesViewClass *klass)
1247 {
1248     GObjectClass *g_object_class;
1249     GtkWidgetClass* widget_class;
1250     GtkTreeViewClass* tv_class;
1251     g_object_class = G_OBJECT_CLASS(klass);
1252     g_object_class->dispose = fm_places_view_dispose;
1253     g_object_class->finalize = fm_places_view_finalize;
1254 
1255     widget_class = GTK_WIDGET_CLASS(klass);
1256     widget_class->key_press_event = on_key_press;
1257     widget_class->button_press_event = on_button_press;
1258     widget_class->button_release_event = on_button_release;
1259     widget_class->drag_motion = on_drag_motion;
1260     widget_class->drag_leave = on_drag_leave;
1261     widget_class->drag_drop = on_drag_drop;
1262     widget_class->drag_data_received = on_drag_data_received;
1263 
1264     tv_class = GTK_TREE_VIEW_CLASS(klass);
1265     tv_class->row_activated = on_row_activated;
1266 #if !GTK_CHECK_VERSION(3, 0, 0)
1267     tv_class->set_scroll_adjustments = on_set_scroll_adjustments;
1268 #endif
1269 
1270     g_object_class->get_property = fm_places_view_get_property;
1271     g_object_class->set_property = fm_places_view_set_property;
1272 
1273     /**
1274      * FmPlacesView:home-dir-path:
1275      *
1276      * The #FmPlacesView:home-dir-path property defines which path will
1277      * be used on Home item activation. Value of %NULL resets it to the
1278      * default.
1279      *
1280      * Since: 1.2.0
1281      */
1282     g_object_class_install_property(g_object_class,
1283                                     PROP_HOME_DIR,
1284                                     g_param_spec_string("home-dir-path",
1285                                                         "Home item directory",
1286                                                         "What directory path will be used for Home item",
1287                                                         NULL,
1288                                                         G_PARAM_READWRITE));
1289 
1290     /**
1291      * FmPlacesView::chdir:
1292      * @view: a view instance that emitted the signal
1293      * @button: the button row was activated with
1294      * @path: (#FmPath *) new directory path
1295      *
1296      * The #FmPlacesView::chdir signal is emitted when current selected
1297      * directory in view is changed.
1298      *
1299      * Since: 0.1.0
1300      */
1301     signals[CHDIR] =
1302         g_signal_new("chdir",
1303                      G_TYPE_FROM_CLASS(klass),
1304                      G_SIGNAL_RUN_LAST,
1305                      G_STRUCT_OFFSET(FmPlacesViewClass, chdir),
1306                      NULL, NULL,
1307                      g_cclosure_marshal_VOID__UINT_POINTER,
1308                      G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_POINTER);
1309 
1310     /**
1311      * FmPlacesView::item-popup:
1312      * @view: a view instance that emitted the signal
1313      * @ui: the #GtkUIManager using to create the menu
1314      * @act_grp: (#GtkActionGroup *) the menu actions group
1315      * @fi: (#FmFileInfo *) the item where menu popup is activated
1316      *
1317      * The #FmPlacesView::item-popup signal is emitted when context menu
1318      * is created for any directory in the view. Handler can modify the
1319      * menu by adding or removing elements.
1320      *
1321      * Since: 1.2.0
1322      */
1323     signals[ITEM_POPUP] =
1324         g_signal_new("item-popup",
1325                      G_TYPE_FROM_CLASS(klass),
1326                      G_SIGNAL_RUN_LAST,
1327                      G_STRUCT_OFFSET(FmPlacesViewClass, item_popup),
1328                      NULL, NULL,
1329                      fm_marshal_VOID__OBJECT_OBJECT_POINTER,
1330                      G_TYPE_NONE, 3, G_TYPE_OBJECT, G_TYPE_OBJECT, G_TYPE_POINTER);
1331 
1332     tree_model_row_atom = gdk_atom_intern_static_string("GTK_TREE_MODEL_ROW");
1333 }
1334