/* * fm-places-view.c * * Copyright 2009 PCMan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include #endif #include #include "fm-places-view.h" #include "fm-config.h" #include "fm-gtk-utils.h" #include "fm-bookmarks.h" #include "fm-file-menu.h" #include "fm-monitor.h" #include "fm-icon-pixbuf.h" #include "fm-cell-renderer-pixbuf.h" #include "fm-file-info-job.h" enum { CHDIR, N_SIGNALS }; enum { COL_ICON, COL_LABEL, COL_INFO, N_COLS }; typedef enum { PLACE_NONE, PLACE_PATH, PLACE_VOL, }PlaceType; typedef struct _PlaceItem { PlaceType type; FmFileInfo* fi; union { GVolume* vol; FmBookmarkItem* bm_item; }; }PlaceItem; static void fm_places_view_finalize (GObject *object); static void activate_row(FmPlacesView* view, guint button, GtkTreePath* tree_path); static void on_row_activated( GtkTreeView* view, GtkTreePath* tree_path, GtkTreeViewColumn *col); static gboolean on_button_press(GtkWidget* view, GdkEventButton* evt); static gboolean on_button_release(GtkWidget* view, GdkEventButton* evt); static void on_mount(GtkAction* act, gpointer user_data); static void on_umount(GtkAction* act, gpointer user_data); static void on_eject(GtkAction* act, gpointer user_data); static void on_remove_bm(GtkAction* act, gpointer user_data); static void on_rename_bm(GtkAction* act, gpointer user_data); static void on_empty_trash(GtkAction* act, gpointer user_data); static gboolean update_trash(gpointer user_data); static gboolean on_dnd_dest_query_info(FmDndDest* dd, int x, int y, GdkDragAction* action, FmPlacesView* view); static gboolean on_dnd_dest_files_dropped(FmDndDest* dd, GdkDragAction action, int info_type, FmFileInfoList* files, FmPlacesView* view); static void on_trash_changed(GFileMonitor *monitor, GFile *gf, GFile *other, GFileMonitorEvent evt, gpointer user_data); static void on_use_trash_changed(FmConfig* cfg, gpointer unused); static void on_pane_icon_size_changed(FmConfig* cfg, gpointer unused); static void create_trash(); static void update_icons(); static void on_vol_added(GVolumeMonitor* vm, GVolume* vol, gpointer user_data); static void on_vol_removed(GVolumeMonitor* vm, GVolume* vol, gpointer user_data); static void on_vol_changed(GVolumeMonitor* vm, GVolume* vol, gpointer user_data); static void on_mount_added(GVolumeMonitor* vm, GMount* mount, gpointer user_data); static void on_file_info_job_finished(FmFileInfoJob* job, gpointer user_data); G_DEFINE_TYPE(FmPlacesView, fm_places_view, GTK_TYPE_TREE_VIEW); static GtkListStore* model = NULL; static GVolumeMonitor* vol_mon = NULL; static FmBookmarks* bookmarks = NULL; static GtkTreeIter sep_it = {0}; static GtkTreeIter trash_it = {0}; static GFileMonitor* trash_monitor = NULL; static guint trash_idle = 0; static guint theme_change_handler = 0; static guint use_trash_change_handler = 0; static guint pane_icon_size_change_handler = 0; static GSList* jobs = NULL; static guint signals[N_SIGNALS]; static const char vol_menu_xml[]= "" "" "" "" "" "" ""; static GtkActionEntry vol_menu_actions[]= { {"Mount", NULL, N_("Mount Volume"), NULL, NULL, on_mount}, {"Unmount", NULL, N_("Unmount Volume"), NULL, NULL, on_umount}, {"Eject", NULL, N_("Eject Removable Media"), NULL, NULL, on_eject} }; static const char bookmark_menu_xml[]= "" "" "" "" "" ""; static GtkActionEntry bm_menu_actions[]= { {"RenameBm", GTK_STOCK_EDIT, N_("Rename Bookmark Item"), NULL, NULL, G_CALLBACK(on_rename_bm)}, {"RemoveBm", GTK_STOCK_REMOVE, N_("Remove from Bookmark"), NULL, NULL, G_CALLBACK(on_remove_bm)} }; static const char trash_menu_xml[]= "" "" "" "" ""; static GtkActionEntry trash_menu_actions[]= { {"EmptyTrash", NULL, N_("Empty Trash"), NULL, NULL, G_CALLBACK(on_empty_trash)} }; enum { FM_DND_DEST_TARGET_BOOOKMARK = N_FM_DND_DEST_DEFAULT_TARGETS + 1 }; GtkTargetEntry dnd_dest_targets[] = { {"application/x-bookmark", GTK_TARGET_SAME_WIDGET, FM_DND_DEST_TARGET_BOOOKMARK} }; static void fm_places_view_class_init(FmPlacesViewClass *klass) { GObjectClass *g_object_class; GtkWidgetClass* widget_class; GtkTreeViewClass* tv_class; g_object_class = G_OBJECT_CLASS(klass); g_object_class->finalize = fm_places_view_finalize; widget_class = GTK_WIDGET_CLASS(klass); widget_class->button_press_event = on_button_press; widget_class->button_release_event = on_button_release; tv_class = GTK_TREE_VIEW_CLASS(klass); tv_class->row_activated = on_row_activated; signals[CHDIR] = g_signal_new("chdir", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(FmPlacesViewClass, chdir), NULL, NULL, g_cclosure_marshal_VOID__UINT_POINTER, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_POINTER); } static void place_item_free(PlaceItem* item) { switch(item->type) { case PLACE_VOL: g_object_unref(item->vol); break; } fm_file_info_unref(item->fi); g_slice_free(PlaceItem, item); } static void fm_places_view_finalize(GObject *object) { FmPlacesView *self; g_return_if_fail(object != NULL); g_return_if_fail(IS_FM_PLACES_VIEW(object)); self = FM_PLACES_VIEW(object); if(self->dest_row) gtk_tree_path_free(self->dest_row); G_OBJECT_CLASS(fm_places_view_parent_class)->finalize(object); } static void on_model_destroy(gpointer unused, GObject* _model) { GtkTreeIter it; if(jobs) { GSList* l; for(l = jobs; l; l=l->next) { fm_job_cancel(FM_JOB(l->data)); g_object_unref(l->data); } g_slist_free(jobs); jobs = NULL; } if(gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &it)) { do { PlaceItem* item; gtk_tree_model_get(GTK_TREE_MODEL(model), &it, COL_INFO, &item, -1); if(G_LIKELY(item)) place_item_free(item); }while(gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &it)); } model = NULL; g_signal_handler_disconnect(gtk_icon_theme_get_default(), theme_change_handler); theme_change_handler = 0; g_signal_handler_disconnect(fm_config, use_trash_change_handler); use_trash_change_handler = 0; g_signal_handler_disconnect(fm_config, pane_icon_size_change_handler); pane_icon_size_change_handler = 0; g_signal_handlers_disconnect_by_func(vol_mon, on_vol_added, NULL); g_signal_handlers_disconnect_by_func(vol_mon, on_vol_removed, NULL); g_signal_handlers_disconnect_by_func(vol_mon, on_vol_changed, NULL); g_signal_handlers_disconnect_by_func(vol_mon, on_mount_added, NULL); g_object_unref(vol_mon); vol_mon = NULL; if(trash_monitor) { g_signal_handlers_disconnect_by_func(trash_monitor, on_trash_changed, NULL); g_object_unref(trash_monitor); trash_monitor = NULL; } if(trash_idle) g_source_remove(trash_idle); trash_idle = 0; memset(&trash_it, 0, sizeof(GtkTreeIter)); memset(&sep_it, 0, sizeof(GtkTreeIter)); } static void update_vol(PlaceItem* item, GtkTreeIter* it) { FmIcon* icon; GIcon* gicon; char* name; GdkPixbuf* pix; GMount* mount; name = g_volume_get_name(item->vol); if(item->fi->icon) fm_icon_unref(item->fi->icon); gicon = g_volume_get_icon(item->vol); icon = fm_icon_from_gicon(gicon); item->fi->icon = icon; g_object_unref(gicon); mount = g_volume_get_mount(item->vol); if(mount) { if(!item->fi->path) { GFile* gf = g_mount_get_root(mount); FmPath* path = fm_path_new_for_gfile(gf); g_object_unref(gf); item->fi->path = path; } g_object_unref(mount); } else { if(item->fi->path) { fm_path_unref(item->fi->path); item->fi->path = NULL; } } /* get mount path here if(job && item->fi->path) fm_file_info_job_add(job, item->fi->path); */ pix = fm_icon_get_pixbuf(item->fi->icon, fm_config->pane_icon_size); gtk_list_store_set(model, it, COL_ICON, pix, COL_LABEL, name, -1); g_object_unref(pix); g_free(name); } static void add_vol(GVolume* vol, FmFileInfoJob* job) { GtkTreeIter it; PlaceItem* item; item = g_slice_new0(PlaceItem); item->fi = fm_file_info_new(); item->type = PLACE_VOL; item->vol = (GVolume*)g_object_ref(vol); gtk_list_store_insert_before(model, &it, &sep_it); gtk_list_store_set(model, &it, COL_INFO, item, -1); update_vol(item, &it); } static PlaceItem* find_vol(GVolume* vol, GtkTreeIter* _it) { GtkTreeIter it; PlaceItem* item; /* FIXME: don't need to find from the first iter */ if(gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &it)) { do { PlaceItem* item; gtk_tree_model_get(GTK_TREE_MODEL(model), &it, COL_INFO, &item, -1); if(item && item->type == PLACE_VOL && item->vol == vol) { *_it = it; return item; } }while(it.user_data != sep_it.user_data && gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &it)); } return NULL; } void on_vol_added(GVolumeMonitor* vm, GVolume* vol, gpointer user_data) { g_debug("add vol: %p, uuid: %s, udi: %s", vol, g_volume_get_identifier(vol, "uuid"), g_volume_get_identifier(vol, "hal-udi")); add_vol(vol, NULL); } void on_vol_removed(GVolumeMonitor* vm, GVolume* vol, gpointer user_data) { PlaceItem* item; GtkTreeIter it; item = find_vol(vol, &it); /* g_debug("remove vol: %p, uuid: %s, udi: %s", vol, g_volume_get_identifier(vol, "uuid"), g_volume_get_identifier(vol, "hal-udi")); */ if(item) { gtk_list_store_remove(model, &it); place_item_free(item); } } void on_vol_changed(GVolumeMonitor* vm, GVolume* vol, gpointer user_data) { PlaceItem* item; GtkTreeIter it; item = find_vol(vol, &it); if(item) update_vol(item, &it); } void on_mount_added(GVolumeMonitor* vm, GMount* mount, gpointer user_data) { GVolume* vol = g_mount_get_volume(mount); if(vol) { PlaceItem *item; GtkTreeIter it; item = find_vol(vol, &it); if(item && item->type == PLACE_VOL && !item->fi->path) { GFile* gf = g_mount_get_root(mount); FmPath* path = fm_path_new_for_gfile(gf); g_debug("mount path: %s", path->name); g_object_unref(gf); item->fi->path = path; } g_object_unref(vol); } } static void add_bookmarks(FmFileInfoJob* job) { PlaceItem* item; GList *bms, *l; FmIcon* icon = fm_icon_from_name("folder"); FmIcon* remote_icon = NULL; GdkPixbuf* folder_pix = fm_icon_get_pixbuf(icon, fm_config->pane_icon_size); GdkPixbuf* remote_pix = NULL; bms = fm_bookmarks_list_all(bookmarks); for(l=bms;l;l=l->next) { FmBookmarkItem* bm = (FmBookmarkItem*)l->data; GtkTreeIter it; GdkPixbuf* pix; item = g_slice_new0(PlaceItem); item->type = PLACE_PATH; item->fi = fm_file_info_new(); item->fi->path = fm_path_ref(bm->path); fm_file_info_job_add(job, item->fi->path); if(fm_path_is_native(item->fi->path)) { item->fi->icon = fm_icon_ref(icon); pix = folder_pix; } else { if(G_UNLIKELY(!remote_icon)) { remote_icon = fm_icon_from_name("folder-remote"); remote_pix = fm_icon_get_pixbuf(remote_icon, fm_config->pane_icon_size); } item->fi->icon = fm_icon_ref(remote_icon); pix = remote_pix; } item->bm_item = bm; gtk_list_store_append(model, &it); gtk_list_store_set(model, &it, COL_ICON, pix, COL_LABEL, bm->name, COL_INFO, item, -1); } g_object_unref(folder_pix); fm_icon_unref(icon); if(remote_icon) { fm_icon_unref(remote_icon); if(remote_pix) g_object_unref(remote_pix); } } static void on_bookmarks_changed(FmBookmarks* bm, gpointer user_data) { FmFileInfoJob* job = fm_file_info_job_new(NULL, FM_FILE_INFO_JOB_FOLLOW_SYMLINK); GtkTreeIter it = sep_it; /* remove all old bookmarks */ if(gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &it)) { while(gtk_list_store_remove(model, &it)) continue; } add_bookmarks(job); g_signal_connect(job, "finished", G_CALLBACK(on_file_info_job_finished), NULL); jobs = g_slist_prepend(jobs, job); fm_job_run_async(job); } void create_trash() { GtkTreeIter it; PlaceItem* item; GdkPixbuf* pix; GFile* gf; gf = g_file_new_for_uri("trash:///"); trash_monitor = fm_monitor_directory(gf, NULL); g_signal_connect(trash_monitor, "changed", G_CALLBACK(on_trash_changed), NULL); g_object_unref(gf); item = g_slice_new0(PlaceItem); item->type = PLACE_PATH; item->fi = fm_file_info_new(); item->fi->path = fm_path_ref(fm_path_get_trash()); item->fi->icon = fm_icon_from_name("user-trash"); gtk_list_store_insert(model, &it, 2); pix = fm_icon_get_pixbuf(item->fi->icon, fm_config->pane_icon_size); gtk_list_store_set(model, &it, COL_ICON, pix, COL_LABEL, _("Trash"), COL_INFO, item, -1); g_object_unref(pix); trash_it = it; if(0 == trash_idle) trash_idle = g_idle_add((GSourceFunc)update_trash, NULL); } static void init_model() { if(G_UNLIKELY(!model)) { GtkTreeIter it; PlaceItem* item; GList *vols, *l; GIcon* gicon; FmIcon* icon; GFile* gf; GdkPixbuf* pix; FmFileInfoJob* job = fm_file_info_job_new(NULL, FM_FILE_INFO_JOB_FOLLOW_SYMLINK); theme_change_handler = g_signal_connect(gtk_icon_theme_get_default(), "changed", G_CALLBACK(update_icons), NULL); use_trash_change_handler = g_signal_connect(fm_config, "changed::use_trash", G_CALLBACK(on_use_trash_changed), NULL); pane_icon_size_change_handler = g_signal_connect(fm_config, "changed::pane_icon_size", G_CALLBACK(on_pane_icon_size_changed), NULL); model = gtk_list_store_new(N_COLS, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_POINTER); g_object_weak_ref(G_OBJECT(model), on_model_destroy, NULL); item = g_slice_new0(PlaceItem); item->type = PLACE_PATH; item->fi = fm_file_info_new(); item->fi->path = fm_path_ref(fm_path_get_home()); item->fi->icon = fm_icon_from_name("user-home"); gtk_list_store_append(model, &it); pix = fm_icon_get_pixbuf(item->fi->icon, fm_config->pane_icon_size); gtk_list_store_set(model, &it, COL_ICON, pix, COL_LABEL, item->fi->path->name, COL_INFO, item, -1); g_object_unref(pix); fm_file_info_job_add(job, item->fi->path); /* Only show desktop in side pane when the user has a desktop dir. */ if(g_file_test(g_get_user_special_dir(G_USER_DIRECTORY_DESKTOP), G_FILE_TEST_IS_DIR)) { item = g_slice_new0(PlaceItem); item->type = PLACE_PATH; item->fi = fm_file_info_new(); item->fi->path = fm_path_ref(fm_path_get_desktop()); item->fi->icon = fm_icon_from_name("user-desktop"); gtk_list_store_append(model, &it); pix = fm_icon_get_pixbuf(item->fi->icon, fm_config->pane_icon_size); gtk_list_store_set(model, &it, COL_ICON, pix, COL_LABEL, _("Desktop"), COL_INFO, item, -1); g_object_unref(pix); fm_file_info_job_add(job, item->fi->path); } if(fm_config->use_trash) create_trash(); /* FIXME: how to handle trash can? */ item = g_slice_new0(PlaceItem); item->type = PLACE_PATH; item->fi = fm_file_info_new(); item->fi->path = fm_path_ref(fm_path_get_apps_menu()); item->fi->icon = fm_icon_from_name("system-software-install"); gtk_list_store_append(model, &it); pix = fm_icon_get_pixbuf(item->fi->icon, fm_config->pane_icon_size); gtk_list_store_set(model, &it, COL_ICON, pix, COL_LABEL, _("Applications"), COL_INFO, item, -1); g_object_unref(pix); /* fm_file_info_job_add(job, item->fi->path); */ /* volumes */ vol_mon = g_volume_monitor_get(); g_signal_connect(vol_mon, "volume-added", G_CALLBACK(on_vol_added), NULL); g_signal_connect(vol_mon, "volume-removed", G_CALLBACK(on_vol_removed), NULL); g_signal_connect(vol_mon, "volume-changed", G_CALLBACK(on_vol_changed), NULL); g_signal_connect(vol_mon, "mount-added", G_CALLBACK(on_mount_added), NULL); /* separator */ gtk_list_store_append(model, &sep_it); /* add volumes to side-pane */ vols = g_volume_monitor_get_volumes(vol_mon); for(l=vols;l;l=l->next) { GVolume* vol = G_VOLUME(l->data); add_vol(vol, job); g_object_unref(vol); } g_list_free(vols); bookmarks = fm_bookmarks_get(); /* bookmarks */ g_signal_connect(bookmarks, "changed", G_CALLBACK(on_bookmarks_changed), NULL); /* add bookmarks to side pane */ add_bookmarks(job); g_signal_connect(job, "finished", G_CALLBACK(on_file_info_job_finished), NULL); jobs = g_slist_prepend(jobs, job); fm_job_run_async(job); } else g_object_ref(model); } static gboolean sep_func( GtkTreeModel* model, GtkTreeIter* it, gpointer data ) { return it->user_data == sep_it.user_data; } static void on_renderer_icon_size_changed(FmConfig* cfg, gpointer user_data) { FmCellRendererPixbuf* render = (FmCellRendererPixbuf*)user_data; fm_cell_renderer_pixbuf_set_fixed_size(render, fm_config->pane_icon_size, fm_config->pane_icon_size); } static void on_cell_renderer_pixbuf_destroy(gpointer user_data, GObject* render) { g_signal_handler_disconnect(fm_config, GPOINTER_TO_UINT(user_data)); } static void fm_places_view_init(FmPlacesView *self) { GtkTreeViewColumn* col; GtkCellRenderer* renderer; GtkTargetList* targets; GdkPixbuf* pix; guint handler; init_model(); gtk_tree_view_set_model(GTK_TREE_VIEW(self), model); g_object_unref(model); gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(self), FALSE); gtk_tree_view_set_row_separator_func(GTK_TREE_VIEW(self), (GtkTreeViewRowSeparatorFunc)sep_func, NULL, NULL ); col = gtk_tree_view_column_new(); renderer = fm_cell_renderer_pixbuf_new(); handler = g_signal_connect(fm_config, "changed::pane_icon_size", G_CALLBACK(on_renderer_icon_size_changed), renderer); g_object_weak_ref(G_OBJECT(renderer), (GDestroyNotify)on_cell_renderer_pixbuf_destroy, GUINT_TO_POINTER(handler)); fm_cell_renderer_pixbuf_set_fixed_size(FM_CELL_RENDERER_PIXBUF(renderer), fm_config->pane_icon_size, fm_config->pane_icon_size); gtk_tree_view_column_pack_start( col, renderer, FALSE ); gtk_tree_view_column_set_attributes( col, renderer, "pixbuf", COL_ICON, NULL ); renderer = gtk_cell_renderer_text_new(); // g_signal_connect( renderer, "edited", G_CALLBACK(on_bookmark_edited), view ); gtk_tree_view_column_pack_start( col, renderer, TRUE ); gtk_tree_view_column_set_attributes( col, renderer, "text", COL_LABEL, NULL ); gtk_tree_view_append_column ( GTK_TREE_VIEW(self), col ); /* gtk_drag_source_set(fv->view, GDK_BUTTON1_MASK, fm_default_dnd_src_targets, N_FM_DND_SRC_DEFAULT_TARGETS, GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_LINK|GDK_ACTION_ASK); fm_dnd_src_set_widget(fv->dnd_src, fv->view); */ gtk_drag_dest_set(self, 0, fm_default_dnd_dest_targets, N_FM_DND_DEST_DEFAULT_TARGETS, GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_LINK|GDK_ACTION_ASK); targets = gtk_drag_dest_get_target_list((GtkWidget*)self); /* add our own targets */ gtk_target_list_add_table(targets, dnd_dest_targets, G_N_ELEMENTS(dnd_dest_targets)); self->dnd_dest = fm_dnd_dest_new((GtkWidget*)self); g_signal_connect(self->dnd_dest, "query-info", G_CALLBACK(on_dnd_dest_query_info), self); g_signal_connect(self->dnd_dest, "files_dropped", G_CALLBACK(on_dnd_dest_files_dropped), self); } GtkWidget *fm_places_view_new(void) { return g_object_new(FM_PLACES_VIEW_TYPE, NULL); } void activate_row(FmPlacesView* view, guint button, GtkTreePath* tree_path) { GtkTreeIter it; if(gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &it, tree_path)) { PlaceItem* item; FmPath* path; gtk_tree_model_get(GTK_TREE_MODEL(model), &it, COL_INFO, &item, -1); if(!item) return; switch(item->type) { case PLACE_PATH: path = fm_path_ref(item->fi->path); break; case PLACE_VOL: { GFile* gf; GMount* mnt = g_volume_get_mount(item->vol); if(!mnt) { GtkWindow* parent = GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(view))); if(!fm_mount_volume(parent, item->vol, TRUE)) return; mnt = g_volume_get_mount(item->vol); if(!mnt) { 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"); return; } } gf = g_mount_get_root(mnt); g_object_unref(mnt); if(gf) { path = fm_path_new_for_gfile(gf); g_object_unref(gf); } else path = NULL; break; } default: return; } if(path) { g_signal_emit(view, signals[CHDIR], 0, button, path); fm_path_unref(path); } } } void on_row_activated(GtkTreeView* view, GtkTreePath* tree_path, GtkTreeViewColumn *col) { activate_row(view, 1, tree_path); } void fm_places_select(FmPlacesView* pv, FmPath* path) { } gboolean on_button_release(GtkWidget* view, GdkEventButton* evt) { GtkTreePath* tp; GtkTreeViewColumn* col; if(evt->button == 1 && gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(view), evt->x, evt->y, &tp, &col, NULL, NULL)) { gtk_tree_view_row_activated(GTK_TREE_VIEW(view), tp, col); gtk_tree_path_free(tp); } return GTK_WIDGET_CLASS(fm_places_view_parent_class)->button_release_event(view, evt); } GtkWidget* place_item_get_menu(PlaceItem* item) { GtkWidget* menu = NULL; FmFileMenu* file_menu; GtkUIManager* ui = gtk_ui_manager_new(); GtkActionGroup* act_grp = act_grp = gtk_action_group_new("Popup"); gtk_action_group_set_translation_domain(act_grp, GETTEXT_PACKAGE); /* FIXME: merge with FmFileMenu when possible */ if(item->type == PLACE_PATH) { if(item->bm_item) { gtk_action_group_add_actions(act_grp, bm_menu_actions, G_N_ELEMENTS(bm_menu_actions), item); gtk_ui_manager_add_ui_from_string(ui, bookmark_menu_xml, -1, NULL); } else if(fm_path_is_trash_root(item->fi->path)) { gtk_action_group_add_actions(act_grp, trash_menu_actions, G_N_ELEMENTS(trash_menu_actions), item); gtk_ui_manager_add_ui_from_string(ui, trash_menu_xml, -1, NULL); } } else if(item->type == PLACE_VOL) { GtkAction* act; GMount* mnt; gtk_action_group_add_actions(act_grp, vol_menu_actions, G_N_ELEMENTS(vol_menu_actions), item); gtk_ui_manager_add_ui_from_string(ui, vol_menu_xml, -1, NULL); mnt = g_volume_get_mount(item->vol); if(mnt) /* mounted */ { g_object_unref(mnt); act = gtk_action_group_get_action(act_grp, "Mount"); gtk_action_set_sensitive(act, FALSE); } else /* not mounted */ { act = gtk_action_group_get_action(act_grp, "Unmount"); gtk_action_set_sensitive(act, FALSE); } if(g_volume_can_eject(item->vol)) act = gtk_action_group_get_action(act_grp, "Unmount"); else act = gtk_action_group_get_action(act_grp, "Eject"); gtk_action_set_visible(act, FALSE); } else goto _out; gtk_ui_manager_insert_action_group(ui, act_grp, 0); menu = gtk_ui_manager_get_widget(ui, "/popup"); if(menu) { g_signal_connect(menu, "selection-done", G_CALLBACK(gtk_widget_destroy), NULL); g_object_weak_ref(G_OBJECT(menu), g_object_unref, g_object_ref(ui)); } _out: g_object_unref(act_grp); g_object_unref(ui); return menu; } gboolean on_button_press(GtkWidget* view, GdkEventButton* evt) { GtkTreePath* tp; GtkTreeViewColumn* col; gboolean ret = GTK_WIDGET_CLASS(fm_places_view_parent_class)->button_press_event(view, evt); if(evt->button == 2) /* middle click */ { if(gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(view), evt->x, evt->y, &tp, &col, NULL, NULL)) { activate_row(view, 2, tp); gtk_tree_path_free(tp); } } else if(evt->button == 3) /* right click */ { if(gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(view), evt->x, evt->y, &tp, &col, NULL, NULL)) { GtkTreeIter it; if(gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &it, tp) && it.user_data != sep_it.user_data ) { PlaceItem* item; GtkWidget* menu; gtk_tree_model_get(GTK_TREE_MODEL(model), &it, COL_INFO, &item, -1); menu = place_item_get_menu(item); if(menu) gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 3, evt->time); } gtk_tree_path_free(tp); } } return ret; } void on_mount(GtkAction* act, gpointer user_data) { PlaceItem* item = (PlaceItem*)user_data; GMount* mnt = g_volume_get_mount(item->vol); if(!mnt) { if(!fm_mount_volume(NULL, item->vol, TRUE)) return; } else g_object_unref(mnt); } void on_umount(GtkAction* act, gpointer user_data) { PlaceItem* item = (PlaceItem*)user_data; GMount* mnt = g_volume_get_mount(item->vol); if(mnt) { fm_unmount_mount(NULL, mnt, TRUE); g_object_unref(mnt); } } void on_eject(GtkAction* act, gpointer user_data) { PlaceItem* item = (PlaceItem*)user_data; fm_eject_volume(NULL, item->vol, TRUE); } void on_remove_bm(GtkAction* act, gpointer user_data) { PlaceItem* item = (PlaceItem*)user_data; fm_bookmarks_remove(bookmarks, item->bm_item); } void on_rename_bm(GtkAction* act, gpointer user_data) { PlaceItem* item = (PlaceItem*)user_data; char* new_name = fm_get_user_input(NULL, _("Rename Bookmark Item"), _("Enter a new name:"), item->bm_item->name); if(new_name) { if(strcmp(new_name, item->bm_item->name)) { fm_bookmarks_rename(bookmarks, item->bm_item, new_name); } g_free(new_name); } } void on_empty_trash(GtkAction* act, gpointer user_data) { fm_empty_trash(); } gboolean on_dnd_dest_query_info(FmDndDest* dd, int x, int y, GdkDragAction* action, FmPlacesView* view) { gboolean ret = TRUE; GtkTreeViewDropPosition pos; GtkTreePath* tp = NULL; GtkTreeViewColumn* col; FmFileInfo* dest = NULL; if(gtk_tree_view_get_dest_row_at_pos((GtkTreeView*)view, x, y, &tp, &pos)) { /* FIXME: this is inefficient. we should record the index of separator instead. */ if(pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE || pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER) { GtkTreeIter it; if(gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &it, tp)) { PlaceItem* item; gtk_tree_model_get(model, &it, COL_INFO, &item, -1); if(item && item->fi->path) { if(!fm_path_is_virtual(item->fi->path) || fm_path_is_trash_root(item->fi->path)) { dest = fm_file_info_ref(item->fi); } } else *action = 0; } else *action = 0; } else { GtkTreePath* sep = gtk_tree_model_get_path(GTK_TREE_MODEL(model), &sep_it); if(gtk_tree_path_compare(sep, tp) < 0) /* tp is after sep */ { *action = GDK_ACTION_LINK; } else { *action = 0; gtk_tree_path_free(tp); tp = NULL; } gtk_tree_path_free(sep); } } else { tp = gtk_tree_path_new_from_indices(gtk_tree_model_iter_n_children(GTK_TREE_MODEL(model), NULL)-1, -1); pos = GTK_TREE_VIEW_DROP_AFTER; *action = GDK_ACTION_LINK; } fm_dnd_dest_set_dest_file(view->dnd_dest, dest); if(dest) { ret = FALSE; fm_file_info_unref(dest); } gtk_tree_view_set_drag_dest_row((GtkTreeView*)view, tp, pos); if(view->dest_row) gtk_tree_path_free(view->dest_row); view->dest_row = tp; view->dest_pos = pos; return ret; } gboolean on_dnd_dest_files_dropped(FmDndDest* dd, GdkDragAction action, int info_type, FmFileInfoList* files, FmPlacesView* view) { FmPath* dest; GList* l; gboolean ret = FALSE; dest = fm_dnd_dest_get_dest_path(dd); /* g_debug("action= %d, %d files-dropped!, info_type: %d", action, fm_list_get_length(files), info_type); */ if(!dest && action == GDK_ACTION_LINK) /* add bookmarks */ { GtkTreePath* tp = view->dest_row; if(tp) { GtkTreePath* sep = gtk_tree_model_get_path(GTK_TREE_MODEL(model), &sep_it); int idx = gtk_tree_path_get_indices(tp)[0] - gtk_tree_path_get_indices(sep)[0]; gtk_tree_path_free(sep); if(view->dest_pos == GTK_TREE_VIEW_DROP_BEFORE) --idx; for( l=fm_list_peek_head_link(files); l; l=l->next, ++idx ) { FmBookmarkItem* item; FmFileInfo* fi = FM_FILE_INFO(l->data); if(fm_file_info_is_dir(fi)) item = fm_bookmarks_insert( bookmarks, fi->path, fi->disp_name, idx); /* we don't need to add item to places view. Later the bookmarks will be reloaded. */ } } ret = TRUE; } if(view->dest_row) { gtk_tree_path_free(view->dest_row); view->dest_row = NULL; } return ret; } gboolean update_trash(gpointer user_data) { if(fm_config->use_trash) { GFile* gf = g_file_new_for_uri("trash:///"); GFileInfo* inf = g_file_query_info(gf, G_FILE_ATTRIBUTE_TRASH_ITEM_COUNT, 0, NULL, NULL); g_object_unref(gf); if(inf) { FmIcon* icon; const char* icon_name; PlaceItem* item; GdkPixbuf* pix; guint32 n = g_file_info_get_attribute_uint32(inf, G_FILE_ATTRIBUTE_TRASH_ITEM_COUNT); g_object_unref(inf); icon_name = n > 0 ? "user-trash-full" : "user-trash"; icon = fm_icon_from_name(icon_name); gtk_tree_model_get(GTK_TREE_MODEL(model), &trash_it, COL_INFO, &item, -1); if(item->fi->icon) fm_icon_unref(item->fi->icon); item->fi->icon = icon; /* update the icon */ pix = fm_icon_get_pixbuf(item->fi->icon, fm_config->pane_icon_size); gtk_list_store_set(model, &trash_it, COL_ICON, pix, -1); g_object_unref(pix); } } return FALSE; } void on_trash_changed(GFileMonitor *monitor, GFile *gf, GFile *other, GFileMonitorEvent evt, gpointer user_data) { if(trash_idle) g_source_remove(trash_idle); trash_idle = g_idle_add((GSourceFunc)update_trash, NULL); } void update_icons() { GtkTreeIter it; gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &it); do{ if(it.user_data != sep_it.user_data) { PlaceItem* item; gtk_tree_model_get(GTK_TREE_MODEL(model), &it, COL_INFO, &item, -1); /* FIXME: get icon size from FmConfig */ GdkPixbuf* pix = fm_icon_get_pixbuf(item->fi->icon, fm_config->pane_icon_size); gtk_list_store_set(model, &it, COL_ICON, pix, -1); g_object_unref(pix); } }while( gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &it) ); } void on_use_trash_changed(FmConfig* cfg, gpointer unused) { if(cfg->use_trash && trash_it.user_data == NULL) create_trash(); else { PlaceItem *item; gtk_tree_model_get(GTK_TREE_MODEL(model), &trash_it, COL_INFO, &item, -1); gtk_list_store_remove(GTK_LIST_STORE(model), &trash_it); trash_it.user_data = NULL; place_item_free(item); if(trash_monitor) { g_signal_handlers_disconnect_by_func(trash_monitor, on_trash_changed, NULL); g_object_unref(trash_monitor); trash_monitor = NULL; } if(trash_idle) { g_source_remove(trash_idle); trash_idle = 0; } } } void on_pane_icon_size_changed(FmConfig* cfg, gpointer unused) { update_icons(); } void on_file_info_job_finished(FmFileInfoJob* job, gpointer user_data) { GList* l; GtkTreeIter it; PlaceItem* item; FmFileInfo* fi; /* g_debug("file info job finished"); */ jobs = g_slist_remove(jobs, job); if(!gtk_tree_model_get_iter_first(model, &it)) return; if(fm_list_is_empty(job->file_infos)) return; /* optimize for one file case */ if(fm_list_get_length(job->file_infos) == 1) { fi = FM_FILE_INFO(fm_list_peek_head(job->file_infos)); do { item = NULL; gtk_tree_model_get(model, &it, COL_INFO, &item, -1); if( item && item->fi && item->fi->path && fm_path_equal(item->fi->path, fi->path) ) { fm_file_info_unref(item->fi); item->fi = fm_file_info_ref(fi); break; } }while(gtk_tree_model_iter_next(model, &it)); } else { do { item = NULL; gtk_tree_model_get(model, &it, COL_INFO, &item, -1); if( item && item->fi && item->fi->path ) { for(l = fm_list_peek_head_link(job->file_infos); l; l = l->next ) { fi = FM_FILE_INFO(l->data); if(fm_path_equal(item->fi->path, fi->path)) { fm_file_info_unref(item->fi); item->fi = fm_file_info_ref(fi); /* remove the file from list to speed up further loading. * This won't cause problem since nobody else if using the list. */ fm_list_delete_link(job->file_infos, l); break; } } } }while(gtk_tree_model_iter_next(model, &it)); } }