/* * folder-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-config.h" #include "fm-folder-view.h" #include "fm-folder.h" #include "fm-folder-model.h" #include "fm-gtk-marshal.h" #include "fm-cell-renderer-text.h" #include "fm-cell-renderer-pixbuf.h" #include "fm-gtk-utils.h" #include "exo/exo-icon-view.h" #include "exo/exo-tree-view.h" enum{ CHDIR, LOADED, STATUS, CLICKED, SEL_CHANGED, SORT_CHANGED, N_SIGNALS }; #define SINGLE_CLICK_TIMEOUT 600 static guint signals[N_SIGNALS]; static void fm_folder_view_finalize (GObject *object); G_DEFINE_TYPE(FmFolderView, fm_folder_view, GTK_TYPE_SCROLLED_WINDOW); static GList* fm_folder_view_get_selected_tree_paths(FmFolderView* fv); static gboolean on_folder_view_focus_in(GtkWidget* widget, GdkEventFocus* evt); static void on_chdir(FmFolderView* fv, FmPath* dir_path); static void on_loaded(FmFolderView* fv, FmPath* dir_path); static void on_status(FmFolderView* fv, const char* msg); static void on_model_loaded(FmFolderModel* model, FmFolderView* fv); static FmJobErrorAction on_folder_err(FmFolder* folder, GError* err, FmJobErrorSeverity severity, FmFolderView* fv); static gboolean on_btn_pressed(GtkWidget* view, GdkEventButton* evt, FmFolderView* fv); static void on_sel_changed(GObject* obj, FmFolderView* fv); static void on_sort_col_changed(GtkTreeSortable* sortable, FmFolderView* fv); static void on_dnd_src_data_get(FmDndSrc* ds, FmFolderView* fv); static gboolean on_dnd_dest_query_info(FmDndDest* dd, int x, int y, GdkDragAction* action, FmFolderView* fv); static void on_single_click_changed(FmConfig* cfg, FmFolderView* fv); static void on_big_icon_size_changed(FmConfig* cfg, FmFolderView* fv); static void on_small_icon_size_changed(FmConfig* cfg, FmFolderView* fv); static void on_thumbnail_size_changed(FmConfig* cfg, FmFolderView* fv); static void cancel_pending_row_activated(FmFolderView* fv); static void fm_folder_view_class_init(FmFolderViewClass *klass) { GObjectClass *g_object_class; GtkWidgetClass *widget_class; FmFolderViewClass *fv_class; g_object_class = G_OBJECT_CLASS(klass); g_object_class->finalize = fm_folder_view_finalize; widget_class = GTK_WIDGET_CLASS(klass); widget_class->focus_in_event = on_folder_view_focus_in; fv_class = FM_FOLDER_VIEW_CLASS(klass); fv_class->chdir = on_chdir; fv_class->loaded = on_loaded; fm_folder_view_parent_class = (GtkScrolledWindowClass*)g_type_class_peek(GTK_TYPE_SCROLLED_WINDOW); signals[CHDIR]= g_signal_new("chdir", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(FmFolderViewClass, chdir), NULL, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); signals[LOADED]= g_signal_new("loaded", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(FmFolderViewClass, loaded), NULL, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); signals[STATUS]= g_signal_new("status", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(FmFolderViewClass, status), NULL, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); signals[CLICKED]= g_signal_new("clicked", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(FmFolderViewClass, clicked), NULL, NULL, g_cclosure_marshal_VOID__UINT_POINTER, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_POINTER); /* Emitted when selection of the view got changed. * Currently selected files are passed as the parameter. * If there is no file selected, NULL is passed instead. */ signals[SEL_CHANGED]= g_signal_new("sel-changed", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(FmFolderViewClass, sel_changed), NULL, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); /* Emitted when sorting of the view got changed. */ signals[SORT_CHANGED]= g_signal_new("sort-changed", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(FmFolderViewClass, sort_changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); } gboolean on_folder_view_focus_in(GtkWidget* widget, GdkEventFocus* evt) { FmFolderView* fv = (FmFolderView*)widget; if( fv->view ) { gtk_widget_grab_focus(fv->view); return TRUE; } return FALSE; } void on_chdir(FmFolderView* fv, FmPath* dir_path) { GtkWidget* toplevel = gtk_widget_get_toplevel((GtkWidget*)fv); if(GTK_WIDGET_REALIZED(toplevel)) { GdkCursor* cursor = gdk_cursor_new(GDK_WATCH); gdk_window_set_cursor(toplevel->window, cursor); } } void on_loaded(FmFolderView* fv, FmPath* dir_path) { GtkWidget* toplevel = gtk_widget_get_toplevel((GtkWidget*)fv); if(GTK_WIDGET_REALIZED(toplevel)) gdk_window_set_cursor(toplevel->window, NULL); } void on_status(FmFolderView* fv, const char* msg) { } void on_model_loaded(FmFolderModel* model, FmFolderView* fv) { FmFolder* folder = model->dir; char* msg; /* FIXME: prevent direct access to data members */ g_signal_emit(fv, signals[LOADED], 0, folder->dir_path); /* FIXME: show number of hidden files and available disk spaces. */ msg = g_strdup_printf("%d files are listed.", fm_list_get_length(folder->files) ); g_signal_emit(fv, signals[STATUS], 0, msg); g_free(msg); } FmJobErrorAction on_folder_err(FmFolder* folder, GError* err, FmJobErrorSeverity severity, FmFolderView* fv) { GtkWindow* parent = (GtkWindow*)gtk_widget_get_toplevel((GtkWidget*)fv); if( err->domain == G_IO_ERROR ) { if( err->code == G_IO_ERROR_NOT_MOUNTED && severity < FM_JOB_ERROR_CRITICAL ) if(fm_mount_path(parent, folder->dir_path, TRUE)) return FM_JOB_RETRY; else if(err->code == G_IO_ERROR_FAILED_HANDLED) return FM_JOB_CONTINUE; } fm_show_error(parent, err->message); return FM_JOB_CONTINUE; } void on_single_click_changed(FmConfig* cfg, FmFolderView* fv) { switch(fv->mode) { case FM_FV_LIST_VIEW: exo_tree_view_set_single_click((ExoTreeView*)fv->view, cfg->single_click); break; case FM_FV_ICON_VIEW: case FM_FV_COMPACT_VIEW: case FM_FV_THUMBNAIL_VIEW: exo_icon_view_set_single_click((ExoIconView*)fv->view, cfg->single_click); break; } } static void item_clicked( FmFolderView* fv, GtkTreePath* path, FmFolderViewClickType type ) { GtkTreeIter it; if(path) { if(gtk_tree_model_get_iter(fv->model, &it, path)) { FmFileInfo* fi; gtk_tree_model_get(fv->model, &it, COL_FILE_INFO, &fi, -1); g_signal_emit(fv, signals[CLICKED], 0, type, fi); } } else g_signal_emit(fv, signals[CLICKED], 0, type, NULL); } static void on_icon_view_item_activated(ExoIconView* iv, GtkTreePath* path, FmFolderView* fv) { item_clicked(fv, path, FM_FV_ACTIVATED); } static gboolean on_idle_tree_view_row_activated(FmFolderView* fv) { GtkTreePath* path; if(gtk_tree_row_reference_valid(fv->activated_row_ref)) { path = gtk_tree_row_reference_get_path(fv->activated_row_ref); item_clicked(fv, path, FM_FV_ACTIVATED); } gtk_tree_row_reference_free(fv->activated_row_ref); fv->activated_row_ref = NULL; fv->row_activated_idle = 0; return FALSE; } static void on_tree_view_row_activated(GtkTreeView* tv, GtkTreePath* path, GtkTreeViewColumn* col, FmFolderView* fv) { /* Due to GTK+ and libexo bugs, here a workaround is needed. * https://bugzilla.gnome.org/show_bug.cgi?id=612802 * http://bugzilla.xfce.org/show_bug.cgi?id=6230 * Gtk+ 2.20+ changed its behavior, which is really bad. * row-activated signal is now issued in the second button-press events * rather than double click events. The content of the view and model * gets changed in row-activated signal handler before button-press-event * handling is finished, and this breaks button-press handler of ExoTreeView * and causing some selection-related bugs since select function cannot be reset.*/ cancel_pending_row_activated(fv); if(fv->model) { fv->activated_row_ref = gtk_tree_row_reference_new(GTK_TREE_MODEL(fv->model), path); g_idle_add((GSourceFunc)on_idle_tree_view_row_activated, fv); } } static void fm_folder_view_init(FmFolderView *self) { gtk_scrolled_window_set_hadjustment((GtkScrolledWindow*)self, NULL); gtk_scrolled_window_set_vadjustment((GtkScrolledWindow*)self, NULL); gtk_scrolled_window_set_policy((GtkScrolledWindow*)self, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); /* config change notifications */ g_signal_connect(fm_config, "changed::single_click", G_CALLBACK(on_single_click_changed), self); /* dnd support */ self->dnd_src = fm_dnd_src_new(NULL); g_signal_connect(self->dnd_src, "data-get", G_CALLBACK(on_dnd_src_data_get), self); self->dnd_dest = fm_dnd_dest_new(NULL); g_signal_connect(self->dnd_dest, "query-info", G_CALLBACK(on_dnd_dest_query_info), self); self->mode = -1; self->sort_type = GTK_SORT_ASCENDING; self->sort_by = COL_FILE_NAME; } GtkWidget* fm_folder_view_new(FmFolderViewMode mode) { FmFolderView* fv = (FmFolderView*)g_object_new(FM_FOLDER_VIEW_TYPE, NULL); fm_folder_view_set_mode(fv, mode); return (GtkWidget*)fv; } static void fm_folder_view_finalize(GObject *object) { FmFolderView *self; g_return_if_fail(object != NULL); g_return_if_fail(IS_FM_FOLDER_VIEW(object)); self = FM_FOLDER_VIEW(object); if(self->folder) { g_object_unref(self->folder); if( self->model ) g_object_unref(self->model); } g_object_unref(self->dnd_src); g_object_unref(self->dnd_dest); if(self->cwd) fm_path_unref(self->cwd); g_signal_handlers_disconnect_by_func(fm_config, on_single_click_changed, object); cancel_pending_row_activated(self); if(self->icon_size_changed_handler) g_signal_handler_disconnect(fm_config, self->icon_size_changed_handler); if (G_OBJECT_CLASS(fm_folder_view_parent_class)->finalize) (* G_OBJECT_CLASS(fm_folder_view_parent_class)->finalize)(object); } static void set_icon_size(FmFolderView* fv, guint icon_size) { FmCellRendererPixbuf* render = (FmCellRendererPixbuf*)fv->renderer_pixbuf; fm_cell_renderer_pixbuf_set_fixed_size(render, icon_size, icon_size); if(!fv->model) return; fm_folder_model_set_icon_size(FM_FOLDER_MODEL(fv->model), icon_size); if( fv->mode != FM_FV_LIST_VIEW ) /* this is an ExoIconView */ { /* FIXME: reset ExoIconView item sizes */ } } static void on_big_icon_size_changed(FmConfig* cfg, FmFolderView* fv) { set_icon_size(fv, cfg->big_icon_size); } static void on_small_icon_size_changed(FmConfig* cfg, FmFolderView* fv) { set_icon_size(fv, cfg->small_icon_size); } static void on_thumbnail_size_changed(FmConfig* cfg, FmFolderView* fv) { /* FIXME: thumbnail and icons should have different sizes */ /* maybe a separate API: fm_folder_model_set_thumbnail_size() */ set_icon_size(fv, cfg->thumbnail_size); } void fm_folder_view_set_mode(FmFolderView* fv, FmFolderViewMode mode) { if( mode != fv->mode ) { GtkTreeViewColumn* col; GtkTreeSelection* ts; GList *sels, *l, *cells; GtkCellRenderer* render; FmFolderModel* model = (FmFolderModel*)fv->model; int icon_size = 0; guint handler; gboolean has_focus; if( G_LIKELY(fv->view) ) { has_focus = GTK_WIDGET_HAS_FOCUS(fv->view); /* preserve old selections */ sels = fm_folder_view_get_selected_tree_paths(fv); gtk_widget_destroy(fv->view ); /* FIXME: compact view and icon view actually use the same * type of widget, ExoIconView. So it may be better to * reuse the widget when available. */ } else { sels = NULL; has_focus = FALSE; } if(fv->icon_size_changed_handler) { g_signal_handler_disconnect(fm_config, fv->icon_size_changed_handler); fv->icon_size_changed_handler = 0; } fv->mode = mode; switch(fv->mode) { case FM_FV_COMPACT_VIEW: case FM_FV_ICON_VIEW: case FM_FV_THUMBNAIL_VIEW: fv->view = exo_icon_view_new(); render = fm_cell_renderer_pixbuf_new(); fv->renderer_pixbuf = render; g_object_set((GObject*)render, "follow-state", TRUE, NULL ); gtk_cell_layout_pack_start((GtkCellLayout*)fv->view, render, TRUE); gtk_cell_layout_add_attribute((GtkCellLayout*)fv->view, render, "pixbuf", COL_FILE_ICON ); gtk_cell_layout_add_attribute((GtkCellLayout*)fv->view, render, "info", COL_FILE_INFO ); if(fv->mode == FM_FV_COMPACT_VIEW) /* compact view */ { handler = g_signal_connect(fm_config, "changed::small_icon_size", G_CALLBACK(on_small_icon_size_changed), fv); fv->icon_size_changed_handler = handler; icon_size = fm_config->small_icon_size; fm_cell_renderer_pixbuf_set_fixed_size(FM_CELL_RENDERER_PIXBUF(fv->renderer_pixbuf), icon_size, icon_size); if(model) fm_folder_model_set_icon_size(model, icon_size); render = fm_cell_renderer_text_new(); g_object_set((GObject*)render, "xalign", 1.0, /* FIXME: why this needs to be 1.0? */ "yalign", 0.5, NULL ); exo_icon_view_set_layout_mode( (ExoIconView*)fv->view, EXO_ICON_VIEW_LAYOUT_COLS ); exo_icon_view_set_orientation( (ExoIconView*)fv->view, GTK_ORIENTATION_HORIZONTAL ); } else /* big icon view or thumbnail view */ { if(fv->mode == FM_FV_ICON_VIEW) { handler = g_signal_connect(fm_config, "changed::big_icon_size", G_CALLBACK(on_big_icon_size_changed), fv); fv->icon_size_changed_handler = handler; icon_size = fm_config->big_icon_size; fm_cell_renderer_pixbuf_set_fixed_size(FM_CELL_RENDERER_PIXBUF(fv->renderer_pixbuf), icon_size, icon_size); if(model) fm_folder_model_set_icon_size(model, icon_size); render = fm_cell_renderer_text_new(); /* FIXME: set the sizes of cells according to iconsize */ g_object_set((GObject*)render, "wrap-mode", PANGO_WRAP_WORD_CHAR, "wrap-width", 90, "alignment", PANGO_ALIGN_CENTER, "xalign", 0.5, "yalign", 0.0, NULL ); exo_icon_view_set_column_spacing( (ExoIconView*)fv->view, 4 ); exo_icon_view_set_item_width ( (ExoIconView*)fv->view, 110 ); } else { handler = g_signal_connect(fm_config, "changed::thumbnail_size", G_CALLBACK(on_thumbnail_size_changed), fv); fv->icon_size_changed_handler = handler; icon_size = fm_config->thumbnail_size; fm_cell_renderer_pixbuf_set_fixed_size(FM_CELL_RENDERER_PIXBUF(fv->renderer_pixbuf), icon_size, icon_size); if(model) fm_folder_model_set_icon_size(model, icon_size); render = fm_cell_renderer_text_new(); /* FIXME: set the sizes of cells according to iconsize */ g_object_set((GObject*)render, "wrap-mode", PANGO_WRAP_WORD_CHAR, "wrap-width", 180, "alignment", PANGO_ALIGN_CENTER, "xalign", 0.5, "yalign", 0.0, NULL ); exo_icon_view_set_column_spacing( (ExoIconView*)fv->view, 8 ); exo_icon_view_set_item_width ( (ExoIconView*)fv->view, 200 ); } } gtk_cell_layout_pack_start((GtkCellLayout*)fv->view, render, TRUE); gtk_cell_layout_add_attribute((GtkCellLayout*)fv->view, render, "text", COL_FILE_NAME ); exo_icon_view_set_item_width((ExoIconView*)fv->view, 96); exo_icon_view_set_search_column((ExoIconView*)fv->view, COL_FILE_NAME); g_signal_connect(fv->view, "item-activated", G_CALLBACK(on_icon_view_item_activated), fv); g_signal_connect(fv->view, "selection-changed", G_CALLBACK(on_sel_changed), fv); exo_icon_view_set_model((ExoIconView*)fv->view, fv->model); exo_icon_view_set_selection_mode((ExoIconView*)fv->view, fv->sel_mode); exo_icon_view_set_single_click((ExoIconView*)fv->view, fm_config->single_click); exo_icon_view_set_single_click_timeout((ExoIconView*)fv->view, SINGLE_CLICK_TIMEOUT); for(l = sels;l;l=l->next) exo_icon_view_select_path((ExoIconView*)fv->view, (GtkTreePath*)l->data); break; case FM_FV_LIST_VIEW: /* detailed list view */ fv->view = exo_tree_view_new(); render = fm_cell_renderer_pixbuf_new(); fv->renderer_pixbuf = render; handler = g_signal_connect(fm_config, "changed::small_icon_size", G_CALLBACK(on_small_icon_size_changed), fv); fv->icon_size_changed_handler = handler; icon_size = fm_config->small_icon_size; fm_cell_renderer_pixbuf_set_fixed_size(FM_CELL_RENDERER_PIXBUF(fv->renderer_pixbuf), icon_size, icon_size); if(model) fm_folder_model_set_icon_size(model, icon_size); gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(fv->view), TRUE); col = gtk_tree_view_column_new(); gtk_tree_view_column_set_title(col, _("Name")); gtk_tree_view_column_pack_start(col, render, FALSE); gtk_tree_view_column_set_attributes(col, render, "pixbuf", COL_FILE_ICON, "info", COL_FILE_INFO, NULL); render = gtk_cell_renderer_text_new(); g_object_set(render, "ellipsize", PANGO_ELLIPSIZE_END, NULL); gtk_tree_view_column_pack_start(col, render, TRUE); gtk_tree_view_column_set_attributes(col, render, "text", COL_FILE_NAME, NULL); gtk_tree_view_column_set_sort_column_id(col, COL_FILE_NAME); gtk_tree_view_column_set_expand(col, TRUE); gtk_tree_view_column_set_resizable(col, TRUE); gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED); gtk_tree_view_column_set_fixed_width(col, 200); gtk_tree_view_append_column((GtkTreeView*)fv->view, col); /* only this column is activable */ exo_tree_view_set_activable_column((ExoTreeView*)fv->view, col); render = gtk_cell_renderer_text_new(); col = gtk_tree_view_column_new_with_attributes(_("Description"), render, "text", COL_FILE_DESC, NULL); gtk_tree_view_column_set_resizable(col, TRUE); gtk_tree_view_column_set_sort_column_id(col, COL_FILE_DESC); gtk_tree_view_append_column((GtkTreeView*)fv->view, col); render = gtk_cell_renderer_text_new(); g_object_set(render, "xalign", 1.0, NULL); col = gtk_tree_view_column_new_with_attributes(_("Size"), render, "text", COL_FILE_SIZE, NULL); gtk_tree_view_column_set_sort_column_id(col, COL_FILE_SIZE); gtk_tree_view_column_set_resizable(col, TRUE); gtk_tree_view_append_column((GtkTreeView*)fv->view, col); render = gtk_cell_renderer_text_new(); col = gtk_tree_view_column_new_with_attributes(_("Modified"), render, "text", COL_FILE_MTIME, NULL); gtk_tree_view_column_set_resizable(col, TRUE); gtk_tree_view_column_set_sort_column_id(col, COL_FILE_MTIME); gtk_tree_view_append_column((GtkTreeView*)fv->view, col); gtk_tree_view_set_search_column((GtkTreeView*)fv->view, COL_FILE_NAME); gtk_tree_view_set_rubber_banding((GtkTreeView*)fv->view, TRUE); exo_tree_view_set_single_click((ExoTreeView*)fv->view, fm_config->single_click); exo_tree_view_set_single_click_timeout((ExoTreeView*)fv->view, SINGLE_CLICK_TIMEOUT); ts = gtk_tree_view_get_selection((GtkTreeView*)fv->view); g_signal_connect(fv->view, "row-activated", G_CALLBACK(on_tree_view_row_activated), fv); g_signal_connect(ts, "changed", G_CALLBACK(on_sel_changed), fv); /*cancel_pending_row_activated(fv);*/ /* FIXME: is this needed? */ gtk_tree_view_set_model((GtkTreeView*)fv->view, fv->model); gtk_tree_selection_set_mode(ts, fv->sel_mode); for(l = sels;l;l=l->next) gtk_tree_selection_select_path(ts, (GtkTreePath*)l->data); break; } g_list_foreach(sels, (GFunc)gtk_tree_path_free, NULL); g_list_free(sels); /* FIXME: maybe calling set_icon_size here is a good idea */ 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(fv->view, 0, fm_default_dnd_dest_targets, N_FM_DND_DEST_DEFAULT_TARGETS, GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_LINK|GDK_ACTION_ASK); fm_dnd_dest_set_widget(fv->dnd_dest, fv->view); g_signal_connect(fv->view, "button-press-event", G_CALLBACK(on_btn_pressed), fv); gtk_widget_show(fv->view); gtk_container_add((GtkContainer*)fv, fv->view); if(has_focus) /* restore the focus if needed. */ gtk_widget_grab_focus(fv->view); } else { /* g_debug("same mode"); */ } } FmFolderViewMode fm_folder_view_get_mode(FmFolderView* fv) { return fv->mode; } void fm_folder_view_set_selection_mode(FmFolderView* fv, GtkSelectionMode mode) { if(fv->sel_mode != mode) { fv->sel_mode = mode; switch(fv->mode) { case FM_FV_LIST_VIEW: { GtkTreeSelection* sel = gtk_tree_view_get_selection((GtkTreeView*)fv->view); gtk_tree_selection_set_mode(sel, mode); break; } case FM_FV_ICON_VIEW: case FM_FV_COMPACT_VIEW: case FM_FV_THUMBNAIL_VIEW: exo_icon_view_set_selection_mode((ExoIconView*)fv->view, mode); break; } } } GtkSelectionMode fm_folder_view_get_selection_mode(FmFolderView* fv) { return fv->sel_mode; } void fm_folder_view_sort(FmFolderView* fv, GtkSortType type, int by) { /* (int) is needed here since enum seems to be treated as unsigned int so -1 becomes > 0 */ if((int)type >=0) fv->sort_type = type; if(by >=0) fv->sort_by = by; if(fv->model) gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(fv->model), fv->sort_by, fv->sort_type); } GtkSortType fm_folder_view_get_sort_type(FmFolderView* fv) { return fv->sort_type; } int fm_folder_view_get_sort_by(FmFolderView* fv) { return fv->sort_by; } void fm_folder_view_set_show_hidden(FmFolderView* fv, gboolean show) { if(show != fv->show_hidden ) { fv->show_hidden = show; if(G_LIKELY(fv->model)) fm_folder_model_set_show_hidden(FM_FOLDER_MODEL(fv->model), show); } } gboolean fm_folder_view_get_show_hidden(FmFolderView* fv) { return fv->show_hidden; } gboolean fm_folder_view_chdir_by_name(FmFolderView* fv, const char* path_str) { gboolean ret; FmPath* path; if( G_UNLIKELY( !path_str ) ) return FALSE; path = fm_path_new(path_str); if(!path) /* might be a malformed path */ return FALSE; ret = fm_folder_view_chdir(fv, path); fm_path_unref(path); return ret; } static void on_folder_unmounted(FmFolder* folder, FmFolderView* fv) { switch(fv->mode) { case FM_FV_LIST_VIEW: cancel_pending_row_activated(fv); gtk_tree_view_set_model(GTK_TREE_VIEW(fv->view), NULL); break; case FM_FV_ICON_VIEW: case FM_FV_COMPACT_VIEW: case FM_FV_THUMBNAIL_VIEW: exo_icon_view_set_model(EXO_ICON_VIEW(fv->view), NULL); break; } if(fv->model) { g_signal_handlers_disconnect_by_func(fv->model, on_sort_col_changed, fv); g_object_unref(fv->model); fv->model = NULL; } } static void on_folder_loaded(FmFolder* folder, FmFolderView* fv) { FmFolderModel* model; guint icon_size = 0; model = fm_folder_model_new(folder, fv->show_hidden); gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model), fv->sort_by, fv->sort_type); g_signal_connect(model, "sort-column-changed", G_CALLBACK(on_sort_col_changed), fv); switch(fv->mode) { case FM_FV_LIST_VIEW: cancel_pending_row_activated(fv); gtk_tree_view_set_model(GTK_TREE_VIEW(fv->view), model); icon_size = fm_config->small_icon_size; fm_folder_model_set_icon_size(model, icon_size); break; case FM_FV_ICON_VIEW: icon_size = fm_config->big_icon_size; fm_folder_model_set_icon_size(model, icon_size); exo_icon_view_set_model(EXO_ICON_VIEW(fv->view), model); break; case FM_FV_COMPACT_VIEW: icon_size = fm_config->small_icon_size; fm_folder_model_set_icon_size(model, icon_size); exo_icon_view_set_model(EXO_ICON_VIEW(fv->view), model); break; case FM_FV_THUMBNAIL_VIEW: icon_size = fm_config->thumbnail_size; fm_folder_model_set_icon_size(model, icon_size); exo_icon_view_set_model(EXO_ICON_VIEW(fv->view), model); break; } fv->model = model; on_model_loaded(model, fv); } gboolean fm_folder_view_chdir(FmFolderView* fv, FmPath* path) { FmFolderModel* model; FmFolder* folder; if(fv->folder) { g_signal_handlers_disconnect_by_func(fv->folder, on_folder_loaded, fv); g_signal_handlers_disconnect_by_func(fv->folder, on_folder_unmounted, fv); g_signal_handlers_disconnect_by_func(fv->folder, on_folder_err, fv); g_object_unref(fv->folder); fv->folder = NULL; if(fv->model) { model = FM_FOLDER_MODEL(fv->model); g_signal_handlers_disconnect_by_func(model, on_model_loaded, fv); g_signal_handlers_disconnect_by_func(model, on_sort_col_changed, fv); if(model->dir) g_signal_handlers_disconnect_by_func(model->dir, on_folder_err, fv); g_object_unref(model); fv->model = NULL; } } /* FIXME: the signal handler should be able to cancel the loading. */ g_signal_emit(fv, signals[CHDIR], 0, path); if(fv->cwd) fm_path_unref(fv->cwd); fv->cwd = fm_path_ref(path); fv->folder = folder = fm_folder_get_for_path(path); if(folder) { /* connect error handler */ g_signal_connect(folder, "loaded", on_folder_loaded, fv); g_signal_connect(folder, "unmount", on_folder_unmounted, fv); g_signal_connect(folder, "error", on_folder_err, fv); if(fm_folder_get_is_loading(folder)) { switch(fv->mode) { case FM_FV_LIST_VIEW: cancel_pending_row_activated(fv); gtk_tree_view_set_model(GTK_TREE_VIEW(fv->view), NULL); break; case FM_FV_ICON_VIEW: case FM_FV_COMPACT_VIEW: case FM_FV_THUMBNAIL_VIEW: exo_icon_view_set_model(EXO_ICON_VIEW(fv->view), NULL); break; } fv->model = NULL; } else on_folder_loaded(folder, fv); } return TRUE; } FmPath* fm_folder_view_get_cwd(FmFolderView* fv) { return fv->cwd; } GList* fm_folder_view_get_selected_tree_paths(FmFolderView* fv) { GList *sels = NULL; switch(fv->mode) { case FM_FV_LIST_VIEW: { GtkTreeSelection* sel; sel = gtk_tree_view_get_selection((GtkTreeView*)fv->view); sels = gtk_tree_selection_get_selected_rows(sel, NULL); break; } case FM_FV_ICON_VIEW: case FM_FV_COMPACT_VIEW: case FM_FV_THUMBNAIL_VIEW: sels = exo_icon_view_get_selected_items((ExoIconView*)fv->view); break; } return sels; } FmFileInfoList* fm_folder_view_get_selected_files(FmFolderView* fv) { FmFileInfoList* fis; GList *sels = fm_folder_view_get_selected_tree_paths(fv); GList *l, *next; if(!sels) return NULL; fis = fm_file_info_list_new(); for(l = sels;l;l=next) { FmFileInfo* fi; GtkTreeIter it; GtkTreePath* tp = (GtkTreePath*)l->data; gtk_tree_model_get_iter(fv->model, &it, l->data); gtk_tree_model_get(fv->model, &it, COL_FILE_INFO, &fi, -1); gtk_tree_path_free(tp); next = l->next; l->data = fm_file_info_ref( fi ); l->prev = l->next = NULL; fm_list_push_tail_link(fis, l); } return fis; } FmPathList* fm_folder_view_get_selected_file_paths(FmFolderView* fv) { FmFileInfoList* files = fm_folder_view_get_selected_files(fv); FmPathList* list; if(files) { list = fm_path_list_new_from_file_info_list(files); fm_list_unref(files); } else list = NULL; return list; } void on_sel_changed(GObject* obj, FmFolderView* fv) { /* FIXME: this is inefficient, but currently there is no better way */ FmFileInfo* files = fm_folder_view_get_selected_files(fv); g_signal_emit(fv, signals[SEL_CHANGED], 0, files); if(files) fm_list_unref(files); } void on_sort_col_changed(GtkTreeSortable* sortable, FmFolderView* fv) { int col; GtkSortType order; if(gtk_tree_sortable_get_sort_column_id(sortable, &col, &order)) { fv->sort_by = col; fv->sort_type = order; g_signal_emit(fv, signals[SORT_CHANGED], 0); } } gboolean on_btn_pressed(GtkWidget* view, GdkEventButton* evt, FmFolderView* fv) { GList* sels; FmFolderViewClickType type = 0; GtkTreePath* tp; if(!fv->model) return FALSE; /* FIXME: handle single click activation */ if( evt->type == GDK_BUTTON_PRESS ) { /* special handling for ExoIconView */ if(evt->button != 1) { if(fv->mode==FM_FV_ICON_VIEW || fv->mode==FM_FV_COMPACT_VIEW || fv->mode==FM_FV_THUMBNAIL_VIEW) { /* select the item on right click for ExoIconView */ if(exo_icon_view_get_item_at_pos(EXO_ICON_VIEW(view), evt->x, evt->y, &tp, NULL)) { /* if the hit item is not currently selected */ if(!exo_icon_view_path_is_selected(EXO_ICON_VIEW(view), tp)) { sels = exo_icon_view_get_selected_items((const struct ExoIconView *)view); if( sels ) /* if there are selected items */ { exo_icon_view_unselect_all(EXO_ICON_VIEW(view)); /* unselect all items */ g_list_foreach(sels, (GFunc)gtk_tree_path_free, NULL); g_list_free(sels); } exo_icon_view_select_path(EXO_ICON_VIEW(view), tp); exo_icon_view_set_cursor(EXO_ICON_VIEW(view), tp, NULL, FALSE); } gtk_tree_path_free(tp); } } else if( fv->mode == FM_FV_LIST_VIEW && evt->window == gtk_tree_view_get_bin_window(GTK_TREE_VIEW(view))) { /* special handling for ExoTreeView */ /* Fix #2986834: MAJOR PROBLEM: Deletes Wrong File Frequently. */ GtkTreeViewColumn* col; if(gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(view), evt->x, evt->y, &tp, &col, NULL, NULL)) { GtkTreeSelection* tree_sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view)); if(!gtk_tree_selection_path_is_selected(tree_sel, tp)) { gtk_tree_selection_unselect_all(tree_sel); if(col == exo_tree_view_get_activable_column(EXO_TREE_VIEW(view))) { gtk_tree_selection_select_path(tree_sel, tp); gtk_tree_view_set_cursor(GTK_TREE_VIEW(view), tp, NULL, FALSE); } } gtk_tree_path_free(tp); } } } if(evt->button == 2) /* middle click */ type = FM_FV_MIDDLE_CLICK; else if(evt->button == 3) /* right click */ type = FM_FV_CONTEXT_MENU; } if( type != FM_FV_CLICK_NONE ) { sels = fm_folder_view_get_selected_tree_paths(fv); if( sels || type == FM_FV_CONTEXT_MENU ) { item_clicked(fv, sels ? sels->data : NULL, type); if(sels) { g_list_foreach(sels, (GFunc)gtk_tree_path_free, NULL); g_list_free(sels); } } } return FALSE; } void fm_folder_view_select_all(FmFolderView* fv) { GtkTreeSelection * tree_sel; switch(fv->mode) { case FM_FV_LIST_VIEW: tree_sel = gtk_tree_view_get_selection((GtkTreeView*)fv->view); gtk_tree_selection_select_all(tree_sel); break; case FM_FV_ICON_VIEW: case FM_FV_COMPACT_VIEW: case FM_FV_THUMBNAIL_VIEW: exo_icon_view_select_all((ExoIconView*)fv->view); break; } } void on_dnd_src_data_get(FmDndSrc* ds, FmFolderView* fv) { FmFileInfoList* files = fm_folder_view_get_selected_files(fv); if(files) { fm_dnd_src_set_files(ds, files); fm_list_unref(files); } } gboolean on_dnd_dest_query_info(FmDndDest* dd, int x, int y, GdkDragAction* action, FmFolderView* fv) { GtkTreePath* tp = NULL; gboolean droppable = TRUE; switch(fv->mode) { case FM_FV_LIST_VIEW: { GtkTreeViewDropPosition pos; GtkTreeViewColumn* col; if(gtk_tree_view_get_dest_row_at_pos((GtkTreeView*)fv->view, x, y, &tp, NULL)) { /* if(gtk_tree_view_column_get_sort_column_id(col)!=COL_FILE_NAME) { gtk_tree_path_free(tp); tp = NULL; } */ } gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW(fv->view), tp, GTK_TREE_VIEW_DROP_INTO_OR_AFTER); break; } case FM_FV_ICON_VIEW: case FM_FV_COMPACT_VIEW: case FM_FV_THUMBNAIL_VIEW: { tp = exo_icon_view_get_path_at_pos((const struct ExoIconView *)fv->view, x, y); exo_icon_view_set_drag_dest_item(EXO_ICON_VIEW(fv->view), tp, EXO_ICON_VIEW_DROP_INTO); break; } } if(tp) { GtkTreeIter it; if(gtk_tree_model_get_iter(fv->model, &it, tp)) { FmFileInfo* fi; gtk_tree_model_get(fv->model, &it, COL_FILE_INFO, &fi, -1); fm_dnd_dest_set_dest_file(dd, fi); } gtk_tree_path_free(tp); } else { /* FIXME: prevent direct access to data members. */ FmFolderModel* model = (FmFolderModel*)fv->model; FmPath* dir_path = model->dir->dir_path; fm_dnd_dest_set_dest_file(dd, model->dir->dir_fi); } return FALSE; } void fm_folder_view_select_invert(FmFolderView* fv) { switch(fv->mode) { case FM_FV_LIST_VIEW: { GtkTreeSelection *tree_sel; GtkTreeIter it; if(!gtk_tree_model_get_iter_first(fv->model, &it)) return; tree_sel = gtk_tree_view_get_selection((GtkTreeView*)fv->view); do { if(gtk_tree_selection_iter_is_selected(tree_sel, &it)) gtk_tree_selection_unselect_iter(tree_sel, &it); else gtk_tree_selection_select_iter(tree_sel, &it); }while( gtk_tree_model_iter_next(fv->model, &it )); break; } case FM_FV_ICON_VIEW: case FM_FV_COMPACT_VIEW: case FM_FV_THUMBNAIL_VIEW: { GtkTreePath* path; int i, n; n = gtk_tree_model_iter_n_children(fv->model, NULL); if(n == 0) return; path = gtk_tree_path_new_first(); for( i=0; iview, path)) exo_icon_view_unselect_path((ExoIconView*)fv->view, path); else exo_icon_view_select_path((ExoIconView*)fv->view, path); } break; } } } void fm_folder_view_select_file_path(FmFolderView* fv, FmPath* path) { if(fm_path_equal(path->parent, fv->cwd)) { FmFolderModel* model = (FmFolderModel*)fv->model; GtkTreeIter it; if(fm_folder_model_find_iter_by_filename(model, &it, path->name)) { switch(fv->mode) { case FM_FV_LIST_VIEW: { GtkTreeSelection* sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(fv->view)); gtk_tree_selection_select_iter(sel, &it); } break; case FM_FV_ICON_VIEW: case FM_FV_COMPACT_VIEW: case FM_FV_THUMBNAIL_VIEW: { GtkTreePath* tp = gtk_tree_model_get_path(GTK_TREE_MODEL(model), &it); if(tp) { exo_icon_view_select_path(EXO_ICON_VIEW(fv->view), tp); gtk_tree_path_free(tp); } } break; } } } } void fm_folder_view_select_file_paths(FmFolderView* fv, FmPathList* paths) { GList* l; for(l = fm_list_peek_head_link(paths);l; l=l->next) { FmPath* path = FM_PATH(l->data); fm_folder_view_select_file_path(fv, path); } } /* FIXME: select files by custom func, not yet implemented */ void fm_folder_view_custom_select(FmFolderView* fv, GFunc filter, gpointer user_data) { } FmFileInfo* fm_folder_view_get_cwd_info(FmFolderView* fv) { return FM_FOLDER_MODEL(fv->model)->dir->dir_fi; } static void cancel_pending_row_activated(FmFolderView* fv) { if(fv->row_activated_idle) { g_source_remove(fv->row_activated_idle); fv->row_activated_idle = 0; gtk_tree_row_reference_free(fv->activated_row_ref); fv->activated_row_ref = NULL; } }