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