Side panel devices show a thin progress bar below each drive name, proportional to disk usage. Bar turns red when usage exceeds 90%. The computer:// icon view also shows usage bars under volume names. New files: - thunar-shortcuts-progress-renderer.c/.h: custom cell renderer (extends GtkCellRendererText) that draws text + usage bar Modified: - thunar-shortcuts-model: added USAGE_PERCENT column - thunar-shortcuts-view: uses progress renderer instead of plain text - thunar-text-renderer: added usage-percent property with bar drawing - thunar-standard-view: sets usage-percent via cell_data_func for computer:// entries using thunar_g_file_get_free_space()
4935 lines
188 KiB
C
4935 lines
188 KiB
C
/* vi:set et ai sw=2 sts=2 ts=2: */
|
|
/*-
|
|
* Copyright (c) 2005-2006 Benedikt Meurer <benny@xfce.org>
|
|
* Copyright (c) 2009-2011 Jannis Pohlmann <jannis@xfce.org>
|
|
*
|
|
* 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 "config.h"
|
|
#endif
|
|
|
|
#ifdef HAVE_MEMORY_H
|
|
#include <memory.h>
|
|
#endif
|
|
#ifdef HAVE_STRING_H
|
|
#include <string.h>
|
|
#endif
|
|
|
|
#include "thunar/thunar-action-manager.h"
|
|
#include "thunar/thunar-application.h"
|
|
#include "thunar/thunar-details-view.h"
|
|
#include "thunar/thunar-dialogs.h"
|
|
#include "thunar/thunar-dnd.h"
|
|
#include "thunar/thunar-enum-types.h"
|
|
#include "thunar/thunar-gio-extensions.h"
|
|
#include "thunar/thunar-gobject-extensions.h"
|
|
#include "thunar/thunar-gtk-extensions.h"
|
|
#include "thunar/thunar-history.h"
|
|
#include "thunar/thunar-icon-renderer.h"
|
|
#include "thunar/thunar-io-jobs.h"
|
|
#include "thunar/thunar-marshal.h"
|
|
#include "thunar/thunar-menu.h"
|
|
#include "thunar/thunar-pango-extensions.h"
|
|
#include "thunar/thunar-private.h"
|
|
#include "thunar/thunar-properties-dialog.h"
|
|
#include "thunar/thunar-renamer-dialog.h"
|
|
#include "thunar/thunar-simple-job.h"
|
|
#include "thunar/thunar-standard-view.h"
|
|
#include "thunar/thunar-text-renderer.h"
|
|
#include "thunar/thunar-util.h"
|
|
|
|
#include <gdk/gdkkeysyms.h>
|
|
#include <libxfce4util/libxfce4util.h>
|
|
|
|
#if defined(GDK_WINDOWING_X11)
|
|
#include <gdk/gdkx.h>
|
|
#endif
|
|
|
|
#define THUNAR_STANDARD_VIEW_SELECTION_CHANGED_DELAY_MS 10
|
|
|
|
|
|
|
|
/* Property identifiers */
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_CURRENT_DIRECTORY,
|
|
PROP_LOADING,
|
|
PROP_SEARCHING,
|
|
PROP_SEARCH_MODE_ACTIVE,
|
|
PROP_DISPLAY_NAME,
|
|
PROP_FULL_PARSED_PATH,
|
|
PROP_SELECTED_FILES,
|
|
PROP_SHOW_HIDDEN,
|
|
PROP_STATUSBAR_TEXT,
|
|
PROP_ZOOM_LEVEL,
|
|
PROP_DIRECTORY_SPECIFIC_SETTINGS,
|
|
PROP_THUMBNAIL_DRAW_FRAMES,
|
|
PROP_SORT_COLUMN,
|
|
PROP_SORT_COLUMN_DEFAULT,
|
|
PROP_SORT_ORDER,
|
|
PROP_SORT_ORDER_DEFAULT,
|
|
PROP_ACCEL_GROUP,
|
|
PROP_MODEL_TYPE,
|
|
N_PROPERTIES
|
|
};
|
|
|
|
/* Signal identifiers */
|
|
enum
|
|
{
|
|
START_OPEN_LOCATION,
|
|
LAST_SIGNAL,
|
|
};
|
|
|
|
/* Identifiers for DnD target types */
|
|
enum
|
|
{
|
|
TARGET_TEXT_URI_LIST,
|
|
TARGET_XDND_DIRECT_SAVE0,
|
|
TARGET_NETSCAPE_URL,
|
|
TARGET_APPLICATION_OCTET_STREAM,
|
|
};
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_component_init (ThunarComponentIface *iface);
|
|
static void
|
|
thunar_standard_view_navigator_init (ThunarNavigatorIface *iface);
|
|
static void
|
|
thunar_standard_view_view_init (ThunarViewIface *iface);
|
|
static GObject *
|
|
thunar_standard_view_constructor (GType type,
|
|
guint n_construct_properties,
|
|
GObjectConstructParam *construct_properties);
|
|
static void
|
|
thunar_standard_view_dispose (GObject *object);
|
|
static void
|
|
thunar_standard_view_finalize (GObject *object);
|
|
static void
|
|
thunar_standard_view_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec);
|
|
static void
|
|
thunar_standard_view_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec);
|
|
static void
|
|
thunar_standard_view_scale_changed (GObject *object,
|
|
GParamSpec *pspec,
|
|
gpointer user_data);
|
|
static void
|
|
thunar_standard_view_realize (GtkWidget *widget);
|
|
static void
|
|
thunar_standard_view_unrealize (GtkWidget *widget);
|
|
static void
|
|
thunar_standard_view_grab_focus (GtkWidget *widget);
|
|
static gboolean
|
|
thunar_standard_view_draw (GtkWidget *widget,
|
|
cairo_t *cr);
|
|
static GList *
|
|
thunar_standard_view_get_selected_files_component (ThunarComponent *component);
|
|
static void
|
|
thunar_standard_view_set_selected_files_component (ThunarComponent *component,
|
|
GList *selected_files);
|
|
static ThunarFile *
|
|
thunar_standard_view_get_current_directory (ThunarNavigator *navigator);
|
|
static void
|
|
thunar_standard_view_set_current_directory (ThunarNavigator *navigator,
|
|
ThunarFile *current_directory);
|
|
static gboolean
|
|
thunar_standard_view_get_loading (ThunarView *view);
|
|
static void
|
|
thunar_standard_view_set_loading (ThunarStandardView *standard_view,
|
|
gboolean loading);
|
|
static gboolean
|
|
thunar_standard_view_get_searching (ThunarView *view);
|
|
static gchar *
|
|
thunar_standard_view_get_statusbar_text (ThunarView *view);
|
|
static gboolean
|
|
thunar_standard_view_get_show_hidden (ThunarView *view);
|
|
static void
|
|
thunar_standard_view_set_show_hidden (ThunarView *view,
|
|
gboolean show_hidden);
|
|
static ThunarZoomLevel
|
|
thunar_standard_view_get_zoom_level (ThunarView *view);
|
|
static void
|
|
thunar_standard_view_set_zoom_level (ThunarView *view,
|
|
ThunarZoomLevel zoom_level);
|
|
static void
|
|
thunar_standard_view_reset_zoom_level (ThunarView *view);
|
|
static void
|
|
thunar_standard_view_apply_directory_specific_settings (ThunarStandardView *standard_view,
|
|
ThunarFile *directory);
|
|
static void
|
|
thunar_standard_view_set_directory_specific_settings (ThunarStandardView *standard_view,
|
|
gboolean directory_specific_settings);
|
|
static void
|
|
thunar_standard_view_reload (ThunarView *view,
|
|
gboolean reload_info);
|
|
static gboolean
|
|
thunar_standard_view_get_visible_range (ThunarView *view,
|
|
ThunarFile **start_file,
|
|
ThunarFile **end_file);
|
|
static void
|
|
thunar_standard_view_scroll_to_file (ThunarView *view,
|
|
ThunarFile *file,
|
|
gboolean select_file,
|
|
gboolean use_align,
|
|
gfloat row_align,
|
|
gfloat col_align);
|
|
static GdkDragAction
|
|
thunar_standard_view_get_dest_actions (ThunarStandardView *standard_view,
|
|
GdkDragContext *context,
|
|
gint x,
|
|
gint y,
|
|
guint timestamp,
|
|
ThunarFile **file_return);
|
|
static ThunarFile *
|
|
thunar_standard_view_get_drop_file (ThunarStandardView *standard_view,
|
|
gint x,
|
|
gint y,
|
|
GtkTreePath **path_return);
|
|
static void
|
|
thunar_standard_view_current_directory_destroy (ThunarFile *current_directory,
|
|
ThunarStandardView *standard_view);
|
|
static void
|
|
thunar_standard_view_current_directory_changed (ThunarFile *current_directory,
|
|
ThunarStandardView *standard_view);
|
|
static GList *
|
|
thunar_standard_view_get_selected_files_view (ThunarView *view);
|
|
static void
|
|
thunar_standard_view_set_selected_files_view (ThunarView *view,
|
|
GList *selected_files);
|
|
static gboolean
|
|
thunar_standard_view_select_all_files (ThunarView *view);
|
|
static gboolean
|
|
thunar_standard_view_select_by_pattern (ThunarView *view);
|
|
static gboolean
|
|
thunar_standard_view_selection_invert (ThunarView *view);
|
|
static gboolean
|
|
thunar_standard_view_unselect_all_files (ThunarView *view);
|
|
static GClosure *
|
|
thunar_standard_view_new_files_closure (ThunarStandardView *standard_view,
|
|
GtkWidget *source_view);
|
|
static void
|
|
thunar_standard_view_new_files (ThunarStandardView *standard_view,
|
|
GList *path_list);
|
|
static gboolean
|
|
thunar_standard_view_button_release_event (GtkWidget *view,
|
|
GdkEventButton *event,
|
|
ThunarStandardView *standard_view);
|
|
static gboolean
|
|
thunar_standard_view_motion_notify_event (GtkWidget *view,
|
|
GdkEventMotion *event,
|
|
ThunarStandardView *standard_view);
|
|
static gboolean
|
|
thunar_standard_view_key_press_event (GtkWidget *view,
|
|
GdkEventKey *event,
|
|
ThunarStandardView *standard_view);
|
|
static gboolean
|
|
thunar_standard_view_scroll_event (GtkWidget *view,
|
|
GdkEventScroll *event,
|
|
ThunarStandardView *standard_view);
|
|
static gboolean
|
|
thunar_standard_view_drag_drop (GtkWidget *view,
|
|
GdkDragContext *context,
|
|
gint x,
|
|
gint y,
|
|
guint timestamp,
|
|
ThunarStandardView *standard_view);
|
|
static void
|
|
thunar_standard_view_drag_data_received (GtkWidget *view,
|
|
GdkDragContext *context,
|
|
gint x,
|
|
gint y,
|
|
GtkSelectionData *selection_data,
|
|
guint info,
|
|
guint timestamp,
|
|
ThunarStandardView *standard_view);
|
|
static void
|
|
thunar_standard_view_drag_leave (GtkWidget *view,
|
|
GdkDragContext *context,
|
|
guint timestamp,
|
|
ThunarStandardView *standard_view);
|
|
static gboolean
|
|
thunar_standard_view_drag_motion (GtkWidget *view,
|
|
GdkDragContext *context,
|
|
gint x,
|
|
gint y,
|
|
guint timestamp,
|
|
ThunarStandardView *standard_view);
|
|
static void
|
|
thunar_standard_view_drag_begin (GtkWidget *view,
|
|
GdkDragContext *context,
|
|
ThunarStandardView *standard_view);
|
|
static void
|
|
thunar_standard_view_drag_data_get (GtkWidget *view,
|
|
GdkDragContext *context,
|
|
GtkSelectionData *selection_data,
|
|
guint info,
|
|
guint timestamp,
|
|
ThunarStandardView *standard_view);
|
|
static void
|
|
thunar_standard_view_drag_data_delete (GtkWidget *view,
|
|
GdkDragContext *context,
|
|
ThunarStandardView *standard_view);
|
|
static void
|
|
thunar_standard_view_drag_end (GtkWidget *view,
|
|
GdkDragContext *context,
|
|
ThunarStandardView *standard_view);
|
|
static void
|
|
thunar_standard_view_select_after_row_deleted (ThunarStandardViewModel *model,
|
|
GtkTreePath *path,
|
|
ThunarStandardView *standard_view);
|
|
static void
|
|
thunar_standard_view_rows_reordered (ThunarStandardViewModel *model,
|
|
GtkTreePath *path,
|
|
GtkTreeIter *iter,
|
|
gpointer new_order,
|
|
ThunarStandardView *standard_view);
|
|
static void
|
|
thunar_standard_view_error (ThunarStandardViewModel *model,
|
|
const GError *error,
|
|
ThunarStandardView *standard_view);
|
|
static void
|
|
thunar_standard_view_search_done (ThunarStandardViewModel *model,
|
|
ThunarStandardView *standard_view);
|
|
static void
|
|
thunar_standard_view_sort_column_changed (GtkTreeSortable *tree_sortable,
|
|
ThunarStandardView *standard_view);
|
|
static void
|
|
thunar_standard_view_loading_unbound (gpointer user_data);
|
|
static gboolean
|
|
thunar_standard_view_drag_scroll_timer (gpointer user_data);
|
|
static void
|
|
thunar_standard_view_drag_scroll_timer_destroy (gpointer user_data);
|
|
static gboolean
|
|
thunar_standard_view_drag_enter_timer (gpointer user_data);
|
|
static void
|
|
thunar_standard_view_drag_enter_timer_destroy (gpointer user_data);
|
|
static gboolean
|
|
thunar_standard_view_drag_timer (gpointer user_data);
|
|
static void
|
|
thunar_standard_view_drag_timer_destroy (gpointer user_data);
|
|
static void
|
|
thunar_standard_view_connect_accelerators (ThunarStandardView *standard_view);
|
|
static void
|
|
thunar_standard_view_disconnect_accelerators (ThunarStandardView *standard_view);
|
|
|
|
static gboolean
|
|
thunar_standard_view_action_sort_by_name (ThunarStandardView *standard_view);
|
|
static gboolean
|
|
thunar_standard_view_action_sort_by_type (ThunarStandardView *standard_view);
|
|
static gboolean
|
|
thunar_standard_view_action_sort_by_date (ThunarStandardView *standard_view);
|
|
static gboolean
|
|
thunar_standard_view_action_sort_by_date_deleted (ThunarStandardView *standard_view);
|
|
static gboolean
|
|
thunar_standard_view_action_sort_by_size (ThunarStandardView *standard_view);
|
|
static gboolean
|
|
thunar_standard_view_action_sort_ascending (ThunarStandardView *standard_view);
|
|
static gboolean
|
|
thunar_standard_view_action_sort_descending (ThunarStandardView *standard_view);
|
|
static void
|
|
thunar_standard_view_set_sort_column (ThunarStandardView *standard_view,
|
|
ThunarColumn column);
|
|
static void
|
|
thunar_standard_view_set_sort_order (ThunarStandardView *standard_view,
|
|
GtkSortType order);
|
|
static gboolean
|
|
thunar_standard_view_toggle_sort_order (ThunarStandardView *standard_view);
|
|
static void
|
|
thunar_standard_view_store_sort_column (ThunarStandardView *standard_view);
|
|
static void
|
|
thunar_standard_view_highlight_option_changed (ThunarStandardView *standard_view);
|
|
static void
|
|
thunar_standard_view_cell_layout_data_func (GtkCellLayout *layout,
|
|
GtkCellRenderer *cell,
|
|
GtkTreeModel *model,
|
|
GtkTreeIter *iter,
|
|
gpointer data);
|
|
static void
|
|
thunar_standard_view_set_model (ThunarStandardView *standard_view);
|
|
static void
|
|
thunar_standard_view_update_selected_files (ThunarStandardView *standard_view,
|
|
GList *files_to_select);
|
|
|
|
struct _ThunarStandardViewPrivate
|
|
{
|
|
/* current directory of the view */
|
|
ThunarFile *current_directory;
|
|
|
|
/* history of the current view */
|
|
ThunarHistory *history;
|
|
|
|
/* zoom-level support */
|
|
ThunarZoomLevel zoom_level;
|
|
|
|
/* directory specific settings */
|
|
gboolean directory_specific_settings;
|
|
|
|
/* scroll_to_file support */
|
|
GHashTable *scroll_to_files;
|
|
|
|
/* statusbar text is mutex protected, since filled by a dedicated ThunarJob */
|
|
gchar *statusbar_text;
|
|
GMutex statusbar_text_mutex;
|
|
guint statusbar_text_idle_id;
|
|
ThunarJob *statusbar_job;
|
|
|
|
/* drag path list */
|
|
GList *drag_g_file_list;
|
|
|
|
/* autoscroll during drag timer source */
|
|
guint drag_scroll_timer_id;
|
|
|
|
/* enter drag target folder timer source */
|
|
guint drag_enter_timer_id;
|
|
ThunarFile *drag_enter_target;
|
|
|
|
/* right-click drag/popup support */
|
|
guint drag_timer_id;
|
|
GdkEvent *drag_timer_event;
|
|
gint drag_x;
|
|
gint drag_y;
|
|
|
|
/* drop site support */
|
|
guint drop_data_ready : 1; /* whether the drop data was received already */
|
|
guint drop_highlight : 1;
|
|
guint drop_occurred : 1; /* whether the data was dropped */
|
|
GList *drop_file_list; /* the list of URIs that are contained in the drop data */
|
|
|
|
/* the "new-files" closure, which is used to select files whenever
|
|
* new files are created by a ThunarJob associated with this view
|
|
*/
|
|
GClosure *new_files_closure;
|
|
|
|
/* the "new-files" path list that was remembered in the closure callback
|
|
* if the view is currently being loaded and as such the folder may
|
|
* not have all "new-files" at hand. This list is used when the
|
|
* folder tells that it's ready loading and the view will try again
|
|
* to select exactly these files.
|
|
*/
|
|
GList *new_files_path_list;
|
|
|
|
/* scroll-to-file support */
|
|
ThunarFile *scroll_to_file;
|
|
guint scroll_to_select : 1;
|
|
guint scroll_to_use_align : 1;
|
|
gfloat scroll_to_row_align;
|
|
gfloat scroll_to_col_align;
|
|
|
|
/* #GList of currently selected #ThunarFile<!---->s */
|
|
GList *selected_files;
|
|
guint restore_selection_idle_id;
|
|
|
|
/* #GList of #ThunarFile<!---->s which are to select when loading the folder finished */
|
|
GList *files_to_select;
|
|
|
|
/* row insert and delete signal IDs, for blocking/unblocking */
|
|
gulong row_deleted_id;
|
|
|
|
/* current sort column ID and it's fallback
|
|
* the default is only relevant for directory specific settings */
|
|
ThunarColumn sort_column;
|
|
ThunarColumn sort_column_default;
|
|
|
|
/* current sort_order (GTK_SORT_ASCENDING || GTK_SORT_DESCENDING)
|
|
* the default is only relevant for directory specific settings */
|
|
GtkSortType sort_order;
|
|
GtkSortType sort_order_default;
|
|
|
|
/* current search query, used to allow switching between views with different (or NULL) search queries */
|
|
gchar *search_query;
|
|
|
|
/* if you are wondering why we don't check if search_query is NULL instead of adding a new variable,
|
|
* we don't want to show the spinner when the search query is empty (i.e. "") */
|
|
gboolean active_search;
|
|
|
|
/* used to restore the view type after a search is completed */
|
|
GType type;
|
|
|
|
/* The css_provider is added to or removed from the style_context of the view
|
|
* using @gtk_style_context_add/remove_provider.
|
|
* Thus we need to maintain the reference. */
|
|
GtkCssProvider *css_provider;
|
|
|
|
GType model_type;
|
|
|
|
/* Used in order to throttle selection changes to prevent lag */
|
|
gboolean selection_changed_requested;
|
|
guint selection_changed_timeout_source;
|
|
};
|
|
|
|
/* clang-format off */
|
|
static XfceGtkActionEntry thunar_standard_view_action_entries[] =
|
|
{
|
|
{ THUNAR_STANDARD_VIEW_ACTION_SELECT_ALL_FILES, "<Actions>/ThunarStandardView/select-all-files", "<Primary>a", XFCE_GTK_MENU_ITEM, N_ ("Select _all Files"), N_ ("Select all files in this window"), NULL, G_CALLBACK (thunar_standard_view_select_all_files), },
|
|
{ THUNAR_STANDARD_VIEW_ACTION_SELECT_BY_PATTERN, "<Actions>/ThunarStandardView/select-by-pattern", "<Primary>s", XFCE_GTK_MENU_ITEM, N_ ("Select _by Pattern..."), N_ ("Select all files that match a certain pattern"), NULL, G_CALLBACK (thunar_standard_view_select_by_pattern), },
|
|
{ THUNAR_STANDARD_VIEW_ACTION_INVERT_SELECTION, "<Actions>/ThunarStandardView/invert-selection", "<Primary><shift>I", XFCE_GTK_MENU_ITEM, N_ ("_Invert Selection"), N_ ("Select all files but not those currently selected"), NULL, G_CALLBACK (thunar_standard_view_selection_invert), },
|
|
{ THUNAR_STANDARD_VIEW_ACTION_UNSELECT_ALL_FILES, "<Actions>/ThunarStandardView/unselect-all-files", "Escape", XFCE_GTK_MENU_ITEM, N_ ("U_nselect all Files"), N_ ("Unselect all files in this window"), NULL, G_CALLBACK (thunar_standard_view_unselect_all_files), },
|
|
{ THUNAR_STANDARD_VIEW_ACTION_ARRANGE_ITEMS_MENU, "<Actions>/ThunarStandardView/arrange-items-menu", "", XFCE_GTK_MENU_ITEM, N_ ("Arran_ge Items"), NULL, NULL, G_CALLBACK (NULL), },
|
|
{ THUNAR_STANDARD_VIEW_ACTION_SORT_ORDER_TOGGLE, "<Actions>/ThunarStandardView/toggle-sort-order", "", XFCE_GTK_CHECK_MENU_ITEM, N_ ("_Reversed Order"), N_ ("Reverse the sort order"), NULL, G_CALLBACK (thunar_standard_view_toggle_sort_order), },
|
|
{ THUNAR_STANDARD_VIEW_ACTION_SORT_BY_NAME, "<Actions>/ThunarStandardView/sort-by-name", "", XFCE_GTK_RADIO_MENU_ITEM, N_ ("By _Name"), N_ ("Keep items sorted by their name"), NULL, G_CALLBACK (thunar_standard_view_action_sort_by_name), },
|
|
{ THUNAR_STANDARD_VIEW_ACTION_SORT_BY_SIZE, "<Actions>/ThunarStandardView/sort-by-size", "", XFCE_GTK_RADIO_MENU_ITEM, N_ ("By _Size"), N_ ("Keep items sorted by their size"), NULL, G_CALLBACK (thunar_standard_view_action_sort_by_size), },
|
|
{ THUNAR_STANDARD_VIEW_ACTION_SORT_BY_TYPE, "<Actions>/ThunarStandardView/sort-by-type", "", XFCE_GTK_RADIO_MENU_ITEM, N_ ("By _Type"), N_ ("Keep items sorted by their type"), NULL, G_CALLBACK (thunar_standard_view_action_sort_by_type), },
|
|
{ THUNAR_STANDARD_VIEW_ACTION_SORT_BY_MTIME, "<Actions>/ThunarStandardView/sort-by-mtime", "", XFCE_GTK_RADIO_MENU_ITEM, N_ ("By _Modification Date"), N_ ("Keep items sorted by their modification date"), NULL, G_CALLBACK (thunar_standard_view_action_sort_by_date), },
|
|
{ THUNAR_STANDARD_VIEW_ACTION_SORT_BY_DTIME, "<Actions>/ThunarStandardView/sort-by-dtime", "", XFCE_GTK_RADIO_MENU_ITEM, N_ ("By D_eletion Date"), N_ ("Keep items sorted by their deletion date"), NULL, G_CALLBACK (thunar_standard_view_action_sort_by_date_deleted), },
|
|
{ THUNAR_STANDARD_VIEW_ACTION_SORT_ASCENDING, "<Actions>/ThunarStandardView/sort-ascending", "", XFCE_GTK_RADIO_MENU_ITEM, N_ ("_Ascending"), N_ ("Sort items in ascending order"), NULL, G_CALLBACK (thunar_standard_view_action_sort_ascending), },
|
|
{ THUNAR_STANDARD_VIEW_ACTION_SORT_DESCENDING, "<Actions>/ThunarStandardView/sort-descending", "", XFCE_GTK_RADIO_MENU_ITEM, N_ ("_Descending"), N_ ("Sort items in descending order"), NULL, G_CALLBACK (thunar_standard_view_action_sort_descending), },
|
|
};
|
|
|
|
#define get_action_entry(id) xfce_gtk_get_action_entry_by_id(thunar_standard_view_action_entries,G_N_ELEMENTS(thunar_standard_view_action_entries),id)
|
|
|
|
/* Target types for dragging from the view */
|
|
static const GtkTargetEntry drag_targets[] =
|
|
{
|
|
{ "text/uri-list", 0, TARGET_TEXT_URI_LIST, },
|
|
};
|
|
|
|
/* Target types for dropping to the view */
|
|
static const GtkTargetEntry drop_targets[] =
|
|
{
|
|
{ "text/uri-list", 0, TARGET_TEXT_URI_LIST, },
|
|
{ "application/octet-stream", 0, TARGET_APPLICATION_OCTET_STREAM, },
|
|
{ "XdndDirectSave0", 0, TARGET_XDND_DIRECT_SAVE0, },
|
|
{ "_NETSCAPE_URL", 0, TARGET_NETSCAPE_URL, },
|
|
};
|
|
/* clang-format on */
|
|
|
|
|
|
|
|
static guint standard_view_signals[LAST_SIGNAL];
|
|
static GParamSpec *standard_view_props[N_PROPERTIES] = {
|
|
NULL,
|
|
};
|
|
|
|
|
|
|
|
G_DEFINE_ABSTRACT_TYPE_WITH_CODE (ThunarStandardView, thunar_standard_view, GTK_TYPE_SCROLLED_WINDOW,
|
|
G_IMPLEMENT_INTERFACE (THUNAR_TYPE_NAVIGATOR, thunar_standard_view_navigator_init)
|
|
G_IMPLEMENT_INTERFACE (THUNAR_TYPE_COMPONENT, thunar_standard_view_component_init)
|
|
G_IMPLEMENT_INTERFACE (THUNAR_TYPE_VIEW, thunar_standard_view_view_init)
|
|
G_ADD_PRIVATE (ThunarStandardView))
|
|
|
|
|
|
static gboolean
|
|
thunar_standard_view_action_sort_ascending (ThunarStandardView *standard_view)
|
|
{
|
|
if (standard_view->priv->sort_order == GTK_SORT_DESCENDING)
|
|
thunar_standard_view_set_sort_column (standard_view, standard_view->priv->sort_column);
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_standard_view_action_sort_descending (ThunarStandardView *standard_view)
|
|
{
|
|
if (standard_view->priv->sort_order == GTK_SORT_ASCENDING)
|
|
thunar_standard_view_set_sort_column (standard_view, standard_view->priv->sort_column);
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_set_sort_column (ThunarStandardView *standard_view,
|
|
ThunarColumn column)
|
|
{
|
|
GtkSortType order = standard_view->priv->sort_order;
|
|
|
|
if (standard_view->priv->sort_column == column)
|
|
order = (order == GTK_SORT_ASCENDING ? GTK_SORT_DESCENDING : GTK_SORT_ASCENDING);
|
|
|
|
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (standard_view->model), column, order);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_set_sort_order (ThunarStandardView *standard_view,
|
|
GtkSortType order)
|
|
{
|
|
if (standard_view->priv->sort_order == order)
|
|
return;
|
|
|
|
standard_view->priv->sort_order = order;
|
|
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (standard_view->model), standard_view->priv->sort_column, order);
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_standard_view_action_sort_by_name (ThunarStandardView *standard_view)
|
|
{
|
|
thunar_standard_view_set_sort_column (standard_view, THUNAR_COLUMN_NAME);
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_standard_view_action_sort_by_size (ThunarStandardView *standard_view)
|
|
{
|
|
thunar_standard_view_set_sort_column (standard_view, THUNAR_COLUMN_SIZE);
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_standard_view_action_sort_by_type (ThunarStandardView *standard_view)
|
|
{
|
|
thunar_standard_view_set_sort_column (standard_view, THUNAR_COLUMN_TYPE);
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_standard_view_action_sort_by_date (ThunarStandardView *standard_view)
|
|
{
|
|
thunar_standard_view_set_sort_column (standard_view, THUNAR_COLUMN_DATE_MODIFIED);
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_standard_view_action_sort_by_date_deleted (ThunarStandardView *standard_view)
|
|
{
|
|
thunar_standard_view_set_sort_column (standard_view, THUNAR_COLUMN_DATE_DELETED);
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_standard_view_toggle_sort_order (ThunarStandardView *standard_view)
|
|
{
|
|
thunar_standard_view_set_sort_column (standard_view, standard_view->priv->sort_column);
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_class_init (ThunarStandardViewClass *klass)
|
|
{
|
|
GtkWidgetClass *gtkwidget_class;
|
|
GObjectClass *gobject_class;
|
|
gpointer g_iface;
|
|
|
|
gobject_class = G_OBJECT_CLASS (klass);
|
|
gobject_class->constructor = thunar_standard_view_constructor;
|
|
gobject_class->dispose = thunar_standard_view_dispose;
|
|
gobject_class->finalize = thunar_standard_view_finalize;
|
|
gobject_class->get_property = thunar_standard_view_get_property;
|
|
gobject_class->set_property = thunar_standard_view_set_property;
|
|
|
|
gtkwidget_class = GTK_WIDGET_CLASS (klass);
|
|
gtkwidget_class->realize = thunar_standard_view_realize;
|
|
gtkwidget_class->unrealize = thunar_standard_view_unrealize;
|
|
gtkwidget_class->grab_focus = thunar_standard_view_grab_focus;
|
|
gtkwidget_class->draw = thunar_standard_view_draw;
|
|
|
|
klass->set_model = thunar_standard_view_set_model;
|
|
klass->cell_layout_data_func = thunar_standard_view_cell_layout_data_func;
|
|
|
|
xfce_gtk_translate_action_entries (thunar_standard_view_action_entries, G_N_ELEMENTS (thunar_standard_view_action_entries));
|
|
|
|
/**
|
|
* ThunarStandardView:loading:
|
|
*
|
|
* Whether the folder associated with this view is
|
|
* currently being loaded from the underlying media.
|
|
*
|
|
* Override property to set the property as writable
|
|
* for the binding.
|
|
**/
|
|
standard_view_props[PROP_LOADING] =
|
|
g_param_spec_override ("loading",
|
|
g_param_spec_boolean ("loading",
|
|
"loading",
|
|
"loading",
|
|
FALSE,
|
|
EXO_PARAM_READWRITE));
|
|
|
|
/**
|
|
* ThunarStandardView:searching:
|
|
*
|
|
* Whether the folder associated with this view is
|
|
* currently being searched.
|
|
**/
|
|
standard_view_props[PROP_SEARCHING] =
|
|
g_param_spec_override ("searching",
|
|
g_param_spec_boolean ("searching",
|
|
"searching",
|
|
"searching",
|
|
FALSE,
|
|
EXO_PARAM_READABLE));
|
|
|
|
/**
|
|
* ThunarStandardView:search-mode-active:
|
|
*
|
|
* Whether the view currently is in search mode
|
|
**/
|
|
standard_view_props[PROP_SEARCH_MODE_ACTIVE] =
|
|
g_param_spec_override ("search-mode-active",
|
|
g_param_spec_boolean ("search-mode-active",
|
|
"search-mode-active",
|
|
"search-mode-active",
|
|
FALSE,
|
|
EXO_PARAM_READABLE));
|
|
|
|
/**
|
|
* ThunarStandardView:display-name:
|
|
*
|
|
* Display name of the current directory, for label text
|
|
**/
|
|
standard_view_props[PROP_DISPLAY_NAME] =
|
|
g_param_spec_string ("display-name",
|
|
"display-name",
|
|
"display-name",
|
|
NULL,
|
|
EXO_PARAM_READABLE);
|
|
|
|
/**
|
|
* ThunarStandardView:full-parsed-path
|
|
*
|
|
* Full parsed path of the current directory, for label tooltip
|
|
**/
|
|
standard_view_props[PROP_FULL_PARSED_PATH] =
|
|
g_param_spec_string ("full-parsed-path",
|
|
"full-parsed-path",
|
|
"full-parsed-path",
|
|
NULL,
|
|
EXO_PARAM_READABLE);
|
|
|
|
/**
|
|
* ThunarStandardView:directory-specific-settings:
|
|
*
|
|
* Whether to use directory specific settings.
|
|
**/
|
|
standard_view_props[PROP_DIRECTORY_SPECIFIC_SETTINGS] =
|
|
g_param_spec_boolean ("directory-specific-settings",
|
|
"directory-specific-settings",
|
|
"directory-specific-settings",
|
|
FALSE,
|
|
EXO_PARAM_READWRITE);
|
|
|
|
/**
|
|
* ThunarStandardView:thumbnail-draw-frames:
|
|
*
|
|
* Whether to draw black frames around thumbnails.
|
|
* This looks neat, but will delay the first draw a bit.
|
|
* May have an impact on older systems, on folders with many pictures.
|
|
**/
|
|
standard_view_props[PROP_THUMBNAIL_DRAW_FRAMES] =
|
|
g_param_spec_boolean ("thumbnail-draw-frames",
|
|
"thumbnail-draw-frames",
|
|
"thumbnail-draw-frames",
|
|
FALSE,
|
|
EXO_PARAM_READWRITE);
|
|
|
|
/**
|
|
* ThunarStandardView:sort-column:
|
|
*
|
|
* The sort column currently used for this view.
|
|
**/
|
|
standard_view_props[PROP_SORT_COLUMN] =
|
|
g_param_spec_enum ("sort-column",
|
|
"SortColumn",
|
|
NULL,
|
|
THUNAR_TYPE_COLUMN,
|
|
THUNAR_COLUMN_NAME,
|
|
EXO_PARAM_READWRITE);
|
|
|
|
/**
|
|
* ThunarStandardView:sort-column-default:
|
|
*
|
|
* Only relevant for directory specific settings
|
|
* The sort column to use if no directory specific settings are found for a directory
|
|
**/
|
|
standard_view_props[PROP_SORT_COLUMN_DEFAULT] =
|
|
g_param_spec_enum ("sort-column-default",
|
|
"SortColumnDefault",
|
|
NULL,
|
|
THUNAR_TYPE_COLUMN,
|
|
THUNAR_COLUMN_NAME,
|
|
EXO_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY);
|
|
|
|
/**
|
|
* ThunarStandardView:sort-order:
|
|
*
|
|
* The sort order currently used for this view.
|
|
**/
|
|
standard_view_props[PROP_SORT_ORDER] =
|
|
g_param_spec_enum ("sort-order",
|
|
"SortOrder",
|
|
NULL,
|
|
GTK_TYPE_SORT_TYPE,
|
|
GTK_SORT_ASCENDING,
|
|
EXO_PARAM_READWRITE);
|
|
|
|
/**
|
|
* ThunarStandardView:sort-order-default:
|
|
*
|
|
* Only relevant for directory specific settings
|
|
* The sort order to use if no directory specific settings are found for a directory
|
|
**/
|
|
standard_view_props[PROP_SORT_ORDER_DEFAULT] =
|
|
g_param_spec_enum ("sort-order-default",
|
|
"SortOrderDefault",
|
|
NULL,
|
|
GTK_TYPE_SORT_TYPE,
|
|
GTK_SORT_ASCENDING,
|
|
EXO_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY);
|
|
|
|
/**
|
|
* ThunarStandardView:model-type:
|
|
*
|
|
* Defines the GType of the model to be used.
|
|
* To be set by the different views for different models.
|
|
**/
|
|
standard_view_props[PROP_MODEL_TYPE] =
|
|
g_param_spec_gtype ("model-type",
|
|
"ModelType",
|
|
NULL,
|
|
G_TYPE_NONE,
|
|
EXO_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
|
|
|
|
/* override ThunarComponent's properties */
|
|
g_iface = g_type_default_interface_peek (THUNAR_TYPE_COMPONENT);
|
|
standard_view_props[PROP_SELECTED_FILES] =
|
|
g_param_spec_override ("selected-files",
|
|
g_object_interface_find_property (g_iface, "selected-files"));
|
|
|
|
/* override ThunarNavigator's properties */
|
|
g_iface = g_type_default_interface_peek (THUNAR_TYPE_NAVIGATOR);
|
|
standard_view_props[PROP_CURRENT_DIRECTORY] =
|
|
g_param_spec_override ("current-directory",
|
|
g_object_interface_find_property (g_iface, "current-directory"));
|
|
|
|
/* override ThunarView's properties */
|
|
g_iface = g_type_default_interface_peek (THUNAR_TYPE_VIEW);
|
|
|
|
standard_view_props[PROP_SHOW_HIDDEN] =
|
|
g_param_spec_override ("show-hidden",
|
|
g_object_interface_find_property (g_iface, "show-hidden"));
|
|
|
|
standard_view_props[PROP_ZOOM_LEVEL] =
|
|
g_param_spec_override ("zoom-level",
|
|
g_object_interface_find_property (g_iface, "zoom-level"));
|
|
|
|
standard_view_props[PROP_ACCEL_GROUP] =
|
|
g_param_spec_object ("accel-group",
|
|
"accel-group",
|
|
"accel-group",
|
|
GTK_TYPE_ACCEL_GROUP,
|
|
G_PARAM_WRITABLE);
|
|
|
|
/**
|
|
* ThunarStandardView:statusbar-text:
|
|
*
|
|
* The text to be displayed in the status bar.
|
|
**/
|
|
standard_view_props[PROP_STATUSBAR_TEXT] =
|
|
g_param_spec_string ("statusbar-text",
|
|
"statusbar-text",
|
|
"statusbar-text",
|
|
NULL,
|
|
EXO_PARAM_READABLE);
|
|
|
|
|
|
/* install all properties */
|
|
g_object_class_install_properties (gobject_class, N_PROPERTIES, standard_view_props);
|
|
|
|
/**
|
|
* ThunarStandardView::start-opn-location:
|
|
* @standard_view : a #ThunarStandardView.
|
|
* @initial_text : the inital location text.
|
|
*
|
|
* Emitted by @standard_view, whenever the user requests to
|
|
* select a custom location (using either the "Open Location"
|
|
* dialog or the location entry widget) by specifying an
|
|
* @initial_text (i.e. if the user types "/" into the
|
|
* view).
|
|
**/
|
|
standard_view_signals[START_OPEN_LOCATION] =
|
|
g_signal_new (I_ ("start-open-location"),
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (ThunarStandardViewClass, start_open_location),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__STRING,
|
|
G_TYPE_NONE, 1, G_TYPE_STRING);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_component_init (ThunarComponentIface *iface)
|
|
{
|
|
iface->get_selected_files = thunar_standard_view_get_selected_files_component;
|
|
iface->set_selected_files = thunar_standard_view_set_selected_files_component;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_navigator_init (ThunarNavigatorIface *iface)
|
|
{
|
|
iface->get_current_directory = thunar_standard_view_get_current_directory;
|
|
iface->set_current_directory = thunar_standard_view_set_current_directory;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_view_init (ThunarViewIface *iface)
|
|
{
|
|
iface->get_loading = thunar_standard_view_get_loading;
|
|
iface->get_show_hidden = thunar_standard_view_get_show_hidden;
|
|
iface->set_show_hidden = thunar_standard_view_set_show_hidden;
|
|
iface->get_zoom_level = thunar_standard_view_get_zoom_level;
|
|
iface->set_zoom_level = thunar_standard_view_set_zoom_level;
|
|
iface->reset_zoom_level = thunar_standard_view_reset_zoom_level;
|
|
iface->reload = thunar_standard_view_reload;
|
|
iface->get_visible_range = thunar_standard_view_get_visible_range;
|
|
iface->scroll_to_file = thunar_standard_view_scroll_to_file;
|
|
iface->get_selected_files = thunar_standard_view_get_selected_files_view;
|
|
iface->set_selected_files = thunar_standard_view_set_selected_files_view;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_init (ThunarStandardView *standard_view)
|
|
{
|
|
standard_view->priv = thunar_standard_view_get_instance_private (standard_view);
|
|
|
|
standard_view->priv->selection_changed_timeout_source = 0;
|
|
standard_view->priv->selection_changed_requested = FALSE;
|
|
|
|
/* allocate the scroll_to_files mapping (directory GFile -> first visible child GFile) */
|
|
standard_view->priv->scroll_to_files = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, g_object_unref);
|
|
|
|
/* grab a reference on the preferences */
|
|
standard_view->preferences = thunar_preferences_get ();
|
|
|
|
/* initialize the scrolled window */
|
|
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (standard_view),
|
|
GTK_POLICY_AUTOMATIC,
|
|
GTK_POLICY_AUTOMATIC);
|
|
gtk_scrolled_window_set_hadjustment (GTK_SCROLLED_WINDOW (standard_view), NULL);
|
|
gtk_scrolled_window_set_vadjustment (GTK_SCROLLED_WINDOW (standard_view), NULL);
|
|
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (standard_view), GTK_SHADOW_IN);
|
|
|
|
/* setup the history support */
|
|
standard_view->priv->history = g_object_new (THUNAR_TYPE_HISTORY, NULL);
|
|
g_signal_connect_swapped (G_OBJECT (standard_view->priv->history), "change-directory", G_CALLBACK (thunar_navigator_change_directory), standard_view);
|
|
|
|
/* The model will be set by the derived views.
|
|
* i.e Abstract icon view or Details view */
|
|
standard_view->model = NULL;
|
|
standard_view->priv->files_to_select = NULL;
|
|
|
|
/* setup the icon renderer */
|
|
standard_view->icon_renderer = thunar_icon_renderer_new ();
|
|
g_object_ref_sink (G_OBJECT (standard_view->icon_renderer));
|
|
g_object_bind_property (G_OBJECT (standard_view), "zoom-level", G_OBJECT (standard_view->icon_renderer), "size", G_BINDING_SYNC_CREATE);
|
|
g_object_bind_property (G_OBJECT (standard_view->preferences), "misc-highlighting-enabled", G_OBJECT (standard_view->icon_renderer), "highlighting-enabled", G_BINDING_SYNC_CREATE);
|
|
g_object_bind_property (G_OBJECT (standard_view->preferences), "last-image-preview-visible", G_OBJECT (standard_view->icon_renderer), "image-preview-enabled", G_BINDING_SYNC_CREATE);
|
|
g_signal_connect (G_OBJECT (standard_view), "notify::scale-factor", G_CALLBACK (thunar_standard_view_scale_changed), NULL);
|
|
|
|
/* setup the name renderer */
|
|
standard_view->name_renderer = thunar_text_renderer_new ();
|
|
g_object_set (standard_view->name_renderer,
|
|
#if PANGO_VERSION_CHECK(1, 44, 0)
|
|
"attributes", thunar_pango_attr_disable_hyphens (),
|
|
#endif
|
|
"alignment", PANGO_ALIGN_CENTER,
|
|
"xalign", 0.5,
|
|
NULL);
|
|
g_object_ref_sink (G_OBJECT (standard_view->name_renderer));
|
|
g_object_bind_property (G_OBJECT (standard_view->preferences), "misc-highlighting-enabled", G_OBJECT (standard_view->name_renderer), "highlighting-enabled", G_BINDING_SYNC_CREATE);
|
|
|
|
/* this is required in order to disable foreground & background colors on the text renderers when the feature is disabled */
|
|
g_object_bind_property (G_OBJECT (standard_view->preferences), "misc-highlighting-enabled", G_OBJECT (standard_view->name_renderer), "foreground-set", G_BINDING_SYNC_CREATE);
|
|
|
|
/* TODO: prelit underline
|
|
g_object_bind_property (G_OBJECT (standard_view->preferences), "misc-single-click", G_OBJECT (standard_view->name_renderer), "follow-prelit", G_BINDING_SYNC_CREATE);*/
|
|
|
|
/* add widget to css class */
|
|
gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (standard_view)), "standard-view");
|
|
|
|
standard_view->accel_group = NULL;
|
|
|
|
standard_view->priv->search_query = NULL;
|
|
standard_view->priv->active_search = FALSE;
|
|
standard_view->priv->type = 0;
|
|
|
|
standard_view->priv->css_provider = NULL;
|
|
|
|
g_mutex_init (&standard_view->priv->statusbar_text_mutex);
|
|
}
|
|
|
|
static void
|
|
thunar_standard_view_store_sort_column (ThunarStandardView *standard_view)
|
|
{
|
|
GtkSortType sort_order;
|
|
gint sort_column;
|
|
|
|
if (gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (standard_view->model), &sort_column, &sort_order))
|
|
{
|
|
standard_view->priv->sort_column = sort_column;
|
|
standard_view->priv->sort_order = sort_order;
|
|
}
|
|
}
|
|
|
|
|
|
static GObject *
|
|
thunar_standard_view_constructor (GType type,
|
|
guint n_construct_properties,
|
|
GObjectConstructParam *construct_properties)
|
|
{
|
|
ThunarStandardView *standard_view;
|
|
ThunarZoomLevel zoom_level;
|
|
GtkWidget *view;
|
|
GObject *object;
|
|
|
|
/* let the GObject constructor create the instance */
|
|
object = G_OBJECT_CLASS (thunar_standard_view_parent_class)->constructor (type, n_construct_properties, construct_properties);
|
|
|
|
/* cast to standard_view for convenience */
|
|
standard_view = THUNAR_STANDARD_VIEW (object);
|
|
|
|
/* setup the default zoom-level, determined from the "last-<view>-zoom-level" preference */
|
|
g_object_get (G_OBJECT (standard_view->preferences), THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->zoom_level_property_name, &zoom_level, NULL);
|
|
thunar_view_set_zoom_level (THUNAR_VIEW (standard_view), zoom_level);
|
|
|
|
/* save the "zoom-level" as "last-<view>-zoom-level" whenever the user changes the zoom level */
|
|
g_object_bind_property (object, "zoom-level", G_OBJECT (standard_view->preferences), THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->zoom_level_property_name, G_BINDING_DEFAULT);
|
|
|
|
/* determine the real view widget (treeview or iconview) */
|
|
view = gtk_bin_get_child (GTK_BIN (object));
|
|
|
|
/* apply our list model to the real view (the child of the scrolled window),
|
|
* we therefore assume that all real views have the "model" property.
|
|
*/
|
|
g_object_set (G_OBJECT (view), "model", standard_view->model, NULL);
|
|
|
|
/* apply the single-click settings to the view */
|
|
g_object_bind_property (G_OBJECT (standard_view->preferences), "misc-single-click", G_OBJECT (view), "single-click", G_BINDING_SYNC_CREATE);
|
|
g_object_bind_property (G_OBJECT (standard_view->preferences), "misc-single-click-timeout", G_OBJECT (view), "single-click-timeout", G_BINDING_SYNC_CREATE);
|
|
|
|
/* apply the default sort column and sort order */
|
|
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (standard_view->model), standard_view->priv->sort_column_default, standard_view->priv->sort_order_default);
|
|
|
|
/* stay informed about changes to the sort column/order */
|
|
g_signal_connect (G_OBJECT (standard_view->model), "sort-column-changed", G_CALLBACK (thunar_standard_view_sort_column_changed), standard_view);
|
|
|
|
/* setup support to navigate using a horizontal mouse wheel and the back and forward buttons */
|
|
g_signal_connect (G_OBJECT (view), "scroll-event", G_CALLBACK (thunar_standard_view_scroll_event), object);
|
|
|
|
/* need to catch certain keys for the internal view widget */
|
|
g_signal_connect (G_OBJECT (view), "key-press-event", G_CALLBACK (thunar_standard_view_key_press_event), object);
|
|
|
|
/* setup the real view as drop site */
|
|
gtk_drag_dest_set (view, 0, drop_targets, G_N_ELEMENTS (drop_targets), GDK_ACTION_ASK | GDK_ACTION_COPY | GDK_ACTION_LINK | GDK_ACTION_MOVE);
|
|
g_signal_connect (G_OBJECT (view), "drag-drop", G_CALLBACK (thunar_standard_view_drag_drop), object);
|
|
g_signal_connect (G_OBJECT (view), "drag-data-received", G_CALLBACK (thunar_standard_view_drag_data_received), object);
|
|
g_signal_connect (G_OBJECT (view), "drag-leave", G_CALLBACK (thunar_standard_view_drag_leave), object);
|
|
g_signal_connect (G_OBJECT (view), "drag-motion", G_CALLBACK (thunar_standard_view_drag_motion), object);
|
|
|
|
/* setup the real view as drag source */
|
|
gtk_drag_source_set (view, GDK_BUTTON1_MASK, drag_targets, G_N_ELEMENTS (drag_targets), GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
|
|
g_signal_connect (G_OBJECT (view), "drag-begin", G_CALLBACK (thunar_standard_view_drag_begin), object);
|
|
g_signal_connect (G_OBJECT (view), "drag-data-get", G_CALLBACK (thunar_standard_view_drag_data_get), object);
|
|
g_signal_connect (G_OBJECT (view), "drag-data-delete", G_CALLBACK (thunar_standard_view_drag_data_delete), object);
|
|
g_signal_connect (G_OBJECT (view), "drag-end", G_CALLBACK (thunar_standard_view_drag_end), object);
|
|
|
|
/* synchronise the "directory-specific-settings" property with the global "misc-directory-specific-settings" property */
|
|
g_object_bind_property (standard_view->preferences, "misc-directory-specific-settings",
|
|
standard_view, "directory-specific-settings",
|
|
G_BINDING_SYNC_CREATE);
|
|
|
|
/* done, we have a working object */
|
|
return object;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_dispose (GObject *object)
|
|
{
|
|
ThunarStandardView *standard_view = THUNAR_STANDARD_VIEW (object);
|
|
|
|
/* unregister the "loading" binding */
|
|
if (G_UNLIKELY (standard_view->loading_binding != NULL))
|
|
{
|
|
g_object_unref (standard_view->loading_binding);
|
|
standard_view->loading_binding = NULL;
|
|
}
|
|
|
|
/* be sure to cancel any pending drag autoscroll timer */
|
|
if (G_UNLIKELY (standard_view->priv->drag_scroll_timer_id != 0))
|
|
g_source_remove (standard_view->priv->drag_scroll_timer_id);
|
|
|
|
/* be sure to cancel any pending drag enter timer */
|
|
if (G_UNLIKELY (standard_view->priv->drag_enter_timer_id != 0))
|
|
g_source_remove (standard_view->priv->drag_enter_timer_id);
|
|
|
|
/* be sure to cancel any pending drag timer */
|
|
if (G_UNLIKELY (standard_view->priv->drag_timer_id != 0))
|
|
g_source_remove (standard_view->priv->drag_timer_id);
|
|
|
|
/* disconnect from file */
|
|
if (standard_view->priv->current_directory != NULL)
|
|
{
|
|
ThunarFolder *folder = thunar_folder_get_for_file (standard_view->priv->current_directory);
|
|
g_signal_handlers_disconnect_by_data (folder, standard_view);
|
|
g_object_unref (folder);
|
|
|
|
g_signal_handlers_disconnect_by_data (standard_view->priv->current_directory, standard_view);
|
|
g_object_unref (standard_view->priv->current_directory);
|
|
standard_view->priv->current_directory = NULL;
|
|
}
|
|
|
|
(*G_OBJECT_CLASS (thunar_standard_view_parent_class)->dispose) (object);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_finalize (GObject *object)
|
|
{
|
|
ThunarStandardView *standard_view = THUNAR_STANDARD_VIEW (object);
|
|
|
|
/* some safety checks */
|
|
_thunar_assert (standard_view->loading_binding == NULL);
|
|
_thunar_assert (standard_view->icon_factory == NULL);
|
|
|
|
/* disconnect accelerators */
|
|
thunar_standard_view_disconnect_accelerators (standard_view);
|
|
|
|
if (standard_view->priv->selection_changed_timeout_source != 0)
|
|
g_source_remove (standard_view->priv->selection_changed_timeout_source);
|
|
|
|
/* release the scroll_to_file reference (if any) */
|
|
if (G_UNLIKELY (standard_view->priv->scroll_to_file != NULL))
|
|
g_object_unref (G_OBJECT (standard_view->priv->scroll_to_file));
|
|
|
|
/* release css_provider */
|
|
if (G_UNLIKELY (standard_view->priv->css_provider != NULL))
|
|
g_object_unref (G_OBJECT (standard_view->priv->css_provider));
|
|
|
|
/* release the selected_files list and the files to select (if any) */
|
|
thunar_g_list_free_full (standard_view->priv->selected_files);
|
|
thunar_g_list_free_full (standard_view->priv->files_to_select);
|
|
|
|
/* release the drag path list (just in case the drag-end wasn't fired before) */
|
|
thunar_g_list_free_full (standard_view->priv->drag_g_file_list);
|
|
|
|
/* release the drop path list (just in case the drag-leave wasn't fired before) */
|
|
thunar_g_list_free_full (standard_view->priv->drop_file_list);
|
|
|
|
/* release the history */
|
|
g_object_unref (standard_view->priv->history);
|
|
|
|
/* release the reference on the name renderer */
|
|
g_object_unref (G_OBJECT (standard_view->name_renderer));
|
|
|
|
/* release the reference on the icon renderer */
|
|
g_object_unref (G_OBJECT (standard_view->icon_renderer));
|
|
|
|
/* drop any existing "new-files" closure */
|
|
if (G_UNLIKELY (standard_view->priv->new_files_closure != NULL))
|
|
{
|
|
g_closure_invalidate (standard_view->priv->new_files_closure);
|
|
g_closure_unref (standard_view->priv->new_files_closure);
|
|
standard_view->priv->new_files_closure = NULL;
|
|
}
|
|
|
|
/* drop any remaining "new-files" paths */
|
|
thunar_g_list_free_full (standard_view->priv->new_files_path_list);
|
|
|
|
/* release our reference on the preferences */
|
|
g_object_unref (G_OBJECT (standard_view->preferences));
|
|
|
|
/* disconnect from the list model */
|
|
g_signal_handlers_disconnect_by_data (G_OBJECT (standard_view->model), standard_view);
|
|
g_object_unref (G_OBJECT (standard_view->model));
|
|
|
|
/* remove selection restore timeout */
|
|
if (standard_view->priv->restore_selection_idle_id != 0)
|
|
g_source_remove (standard_view->priv->restore_selection_idle_id);
|
|
|
|
/* free the statusbar text (if any) */
|
|
if (standard_view->priv->statusbar_text_idle_id != 0)
|
|
g_source_remove (standard_view->priv->statusbar_text_idle_id);
|
|
g_free (standard_view->priv->statusbar_text);
|
|
|
|
if (standard_view->priv->statusbar_job != NULL)
|
|
{
|
|
g_signal_handlers_disconnect_by_data (standard_view->priv->statusbar_job, standard_view);
|
|
exo_job_cancel (EXO_JOB (standard_view->priv->statusbar_job));
|
|
g_object_unref (standard_view->priv->statusbar_job);
|
|
standard_view->priv->statusbar_job = NULL;
|
|
}
|
|
|
|
g_free (standard_view->priv->search_query);
|
|
|
|
g_mutex_clear (&standard_view->priv->statusbar_text_mutex);
|
|
|
|
/* release the scroll_to_files hash table */
|
|
g_hash_table_destroy (standard_view->priv->scroll_to_files);
|
|
|
|
(*G_OBJECT_CLASS (thunar_standard_view_parent_class)->finalize) (object);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
ThunarFile *current_directory;
|
|
gboolean thumbnail_draw_frames;
|
|
gchar *statusbar_text;
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_CURRENT_DIRECTORY:
|
|
g_value_set_object (value, thunar_navigator_get_current_directory (THUNAR_NAVIGATOR (object)));
|
|
break;
|
|
|
|
case PROP_LOADING:
|
|
g_value_set_boolean (value, thunar_view_get_loading (THUNAR_VIEW (object)));
|
|
break;
|
|
|
|
case PROP_SEARCHING:
|
|
g_value_set_boolean (value, thunar_standard_view_get_searching (THUNAR_VIEW (object)));
|
|
break;
|
|
|
|
case PROP_SEARCH_MODE_ACTIVE:
|
|
g_value_set_boolean (value, THUNAR_STANDARD_VIEW (object)->priv->search_query != NULL);
|
|
break;
|
|
|
|
case PROP_DISPLAY_NAME:
|
|
current_directory = thunar_navigator_get_current_directory (THUNAR_NAVIGATOR (object));
|
|
if (THUNAR_STANDARD_VIEW (object)->priv->search_query != NULL)
|
|
{
|
|
gchar *label = g_strjoin (NULL, "Search for '", THUNAR_STANDARD_VIEW (object)->priv->search_query, "' in ", thunar_file_get_display_name (current_directory), NULL);
|
|
g_value_take_string (value, label);
|
|
}
|
|
else
|
|
{
|
|
if (current_directory != NULL)
|
|
g_value_set_static_string (value, thunar_file_get_display_name (current_directory));
|
|
}
|
|
break;
|
|
|
|
case PROP_FULL_PARSED_PATH:
|
|
current_directory = thunar_navigator_get_current_directory (THUNAR_NAVIGATOR (object));
|
|
if (current_directory != NULL)
|
|
g_value_take_string (value, g_file_get_parse_name (thunar_file_get_file (current_directory)));
|
|
break;
|
|
|
|
case PROP_SELECTED_FILES:
|
|
g_value_set_boxed (value, thunar_component_get_selected_files (THUNAR_COMPONENT (object)));
|
|
break;
|
|
|
|
case PROP_SHOW_HIDDEN:
|
|
g_value_set_boolean (value, thunar_view_get_show_hidden (THUNAR_VIEW (object)));
|
|
break;
|
|
|
|
case PROP_STATUSBAR_TEXT:
|
|
statusbar_text = thunar_standard_view_get_statusbar_text (THUNAR_VIEW (object));
|
|
g_value_set_string (value, statusbar_text);
|
|
g_free (statusbar_text);
|
|
break;
|
|
|
|
case PROP_ZOOM_LEVEL:
|
|
g_value_set_enum (value, thunar_view_get_zoom_level (THUNAR_VIEW (object)));
|
|
break;
|
|
|
|
case PROP_THUMBNAIL_DRAW_FRAMES:
|
|
g_object_get (G_OBJECT (THUNAR_STANDARD_VIEW (object)->icon_factory), "thumbnail-draw-frames", &thumbnail_draw_frames, NULL);
|
|
g_value_set_boolean (value, thumbnail_draw_frames);
|
|
break;
|
|
|
|
case PROP_SORT_COLUMN:
|
|
g_value_set_enum (value, THUNAR_STANDARD_VIEW (object)->priv->sort_column);
|
|
break;
|
|
|
|
case PROP_SORT_ORDER:
|
|
g_value_set_enum (value, THUNAR_STANDARD_VIEW (object)->priv->sort_order);
|
|
break;
|
|
|
|
case PROP_MODEL_TYPE:
|
|
g_value_set_gtype (value, THUNAR_STANDARD_VIEW (object)->priv->model_type);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
ThunarStandardView *standard_view = THUNAR_STANDARD_VIEW (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_CURRENT_DIRECTORY:
|
|
thunar_navigator_set_current_directory (THUNAR_NAVIGATOR (object), g_value_get_object (value));
|
|
break;
|
|
|
|
case PROP_LOADING:
|
|
thunar_standard_view_set_loading (standard_view, g_value_get_boolean (value));
|
|
break;
|
|
|
|
case PROP_SELECTED_FILES:
|
|
thunar_component_set_selected_files (THUNAR_COMPONENT (object), g_value_get_boxed (value));
|
|
break;
|
|
|
|
case PROP_SHOW_HIDDEN:
|
|
thunar_view_set_show_hidden (THUNAR_VIEW (object), g_value_get_boolean (value));
|
|
break;
|
|
|
|
case PROP_ZOOM_LEVEL:
|
|
thunar_view_set_zoom_level (THUNAR_VIEW (object), g_value_get_enum (value));
|
|
break;
|
|
|
|
case PROP_DIRECTORY_SPECIFIC_SETTINGS:
|
|
thunar_standard_view_set_directory_specific_settings (standard_view, g_value_get_boolean (value));
|
|
break;
|
|
|
|
case PROP_THUMBNAIL_DRAW_FRAMES:
|
|
g_object_set (G_OBJECT (standard_view->icon_factory), "thumbnail-draw-frames", g_value_get_boolean (value), NULL);
|
|
break;
|
|
|
|
case PROP_SORT_COLUMN:
|
|
thunar_standard_view_set_sort_column (standard_view, g_value_get_enum (value));
|
|
break;
|
|
|
|
case PROP_SORT_COLUMN_DEFAULT:
|
|
standard_view->priv->sort_column_default = g_value_get_enum (value);
|
|
break;
|
|
|
|
case PROP_SORT_ORDER:
|
|
thunar_standard_view_set_sort_order (standard_view, g_value_get_enum (value));
|
|
break;
|
|
|
|
case PROP_SORT_ORDER_DEFAULT:
|
|
standard_view->priv->sort_order_default = g_value_get_enum (value);
|
|
break;
|
|
|
|
case PROP_ACCEL_GROUP:
|
|
thunar_standard_view_disconnect_accelerators (standard_view);
|
|
standard_view->accel_group = g_value_dup_object (value);
|
|
thunar_standard_view_connect_accelerators (standard_view);
|
|
break;
|
|
|
|
case PROP_MODEL_TYPE:
|
|
standard_view->priv->model_type = g_value_get_gtype (value);
|
|
(*THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->set_model) (standard_view);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_scale_changed (GObject *object,
|
|
GParamSpec *pspec,
|
|
gpointer user_data)
|
|
{
|
|
gtk_widget_queue_draw (GTK_WIDGET (object));
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_realize (GtkWidget *widget)
|
|
{
|
|
ThunarStandardView *standard_view = THUNAR_STANDARD_VIEW (widget);
|
|
GtkIconTheme *icon_theme;
|
|
|
|
/* let the GtkWidget do its work */
|
|
GTK_WIDGET_CLASS (thunar_standard_view_parent_class)->realize (widget);
|
|
|
|
/* determine the icon factory for the screen on which we are realized */
|
|
icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (widget));
|
|
standard_view->icon_factory = thunar_icon_factory_get_for_icon_theme (icon_theme);
|
|
g_object_bind_property (G_OBJECT (standard_view->icon_renderer), "size", G_OBJECT (standard_view->icon_factory), "thumbnail-size", G_BINDING_SYNC_CREATE);
|
|
|
|
/* apply the thumbnail frame preferences after icon_factory got initialized */
|
|
g_object_bind_property (G_OBJECT (standard_view->preferences), "misc-thumbnail-draw-frames", G_OBJECT (standard_view), "thumbnail-draw-frames", G_BINDING_SYNC_CREATE);
|
|
|
|
/* apply/unapply the highlights to name & icon renderers whenever the property changes */
|
|
g_signal_connect_swapped (standard_view->preferences, "notify::misc-highlighting-enabled",
|
|
G_CALLBACK (thunar_standard_view_highlight_option_changed), standard_view);
|
|
thunar_standard_view_highlight_option_changed (standard_view);
|
|
|
|
/* store sort information to keep indicators in menu in sync */
|
|
thunar_standard_view_store_sort_column (standard_view);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_unrealize (GtkWidget *widget)
|
|
{
|
|
ThunarStandardView *standard_view = THUNAR_STANDARD_VIEW (widget);
|
|
|
|
/* Disconnect from methods which make use of the icon factory */
|
|
g_signal_handlers_disconnect_by_func (G_OBJECT (standard_view->preferences), thunar_standard_view_highlight_option_changed, standard_view);
|
|
g_signal_handlers_disconnect_by_data (G_OBJECT (standard_view->icon_factory), standard_view);
|
|
|
|
/* drop the reference on the icon factory */
|
|
g_object_unref (G_OBJECT (standard_view->icon_factory));
|
|
standard_view->icon_factory = NULL;
|
|
|
|
/* let the GtkWidget do its work */
|
|
GTK_WIDGET_CLASS (thunar_standard_view_parent_class)->unrealize (widget);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_grab_focus (GtkWidget *widget)
|
|
{
|
|
/* forward the focus grab to the real view */
|
|
gtk_widget_grab_focus (gtk_bin_get_child (GTK_BIN (widget)));
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_standard_view_draw (GtkWidget *widget,
|
|
cairo_t *cr)
|
|
{
|
|
gboolean result = FALSE;
|
|
GtkAllocation a;
|
|
GtkStyleContext *context;
|
|
|
|
/* let the scrolled window do it's work */
|
|
cairo_save (cr);
|
|
result = (*GTK_WIDGET_CLASS (thunar_standard_view_parent_class)->draw) (widget, cr);
|
|
cairo_restore (cr);
|
|
|
|
/* render the folder drop shadow */
|
|
if (G_UNLIKELY (THUNAR_STANDARD_VIEW (widget)->priv->drop_highlight))
|
|
{
|
|
gtk_widget_get_allocation (widget, &a);
|
|
|
|
context = gtk_widget_get_style_context (widget);
|
|
|
|
gtk_style_context_save (context);
|
|
gtk_style_context_set_state (context, GTK_STATE_FLAG_DROP_ACTIVE);
|
|
gtk_render_frame (context, cr, 0, 0, a.width, a.height);
|
|
gtk_style_context_restore (context);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
static GList *
|
|
thunar_standard_view_get_selected_files_component (ThunarComponent *component)
|
|
{
|
|
return THUNAR_STANDARD_VIEW (component)->priv->selected_files;
|
|
}
|
|
|
|
|
|
|
|
static GList *
|
|
thunar_standard_view_get_selected_files_view (ThunarView *view)
|
|
{
|
|
return THUNAR_STANDARD_VIEW (view)->priv->selected_files;
|
|
}
|
|
|
|
|
|
void
|
|
thunar_standard_view_update_selected_files (ThunarStandardView *standard_view,
|
|
GList *files_to_select)
|
|
{
|
|
GtkTreePath *first_path = NULL;
|
|
GList *paths;
|
|
GList *lp;
|
|
|
|
/* verify that we have a valid model */
|
|
if (G_UNLIKELY (standard_view->model == NULL))
|
|
return;
|
|
|
|
/* determine the tree paths for the given files */
|
|
paths = thunar_standard_view_model_get_paths_for_files (standard_view->model, files_to_select);
|
|
|
|
/* unselect all previously selected files - this may free the "files_to_select" list */
|
|
(*THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->unselect_all) (standard_view);
|
|
|
|
if (G_LIKELY (paths != NULL))
|
|
{
|
|
/* determine the first path */
|
|
for (first_path = paths->data, lp = paths; lp != NULL; lp = lp->next)
|
|
{
|
|
/* check if this path is located before the current first_path */
|
|
if (gtk_tree_path_compare (lp->data, first_path) < 0)
|
|
first_path = lp->data;
|
|
}
|
|
|
|
/* place the cursor on the first selected path (must be first for GtkTreeView) */
|
|
(*THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->set_cursor) (standard_view, first_path, FALSE);
|
|
|
|
/* if we don't block the selection changed then for each new selection,
|
|
* selection_changed handler will be called. selection_changed handler
|
|
* runs in O(n) time where it iterates over all the n selected paths.
|
|
* Since we select each file one by one, we will have a worst case
|
|
* time complexity ~ O(n^2); but instead if we call the handler after
|
|
* all the necessary files have been selected then time comp = O(n) */
|
|
(*THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->block_selection) (standard_view);
|
|
|
|
/* select the given tree paths paths */
|
|
for (first_path = paths->data, lp = paths; lp != NULL; lp = lp->next)
|
|
{
|
|
/* select the path */
|
|
(*THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->select_path) (standard_view, lp->data);
|
|
}
|
|
|
|
(*THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->unblock_selection) (standard_view);
|
|
|
|
/* call the selection_changed call since we had previously blocked selection */
|
|
thunar_standard_view_selection_changed (standard_view);
|
|
|
|
/* scroll to the first path (previously determined) */
|
|
(*THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->scroll_to_path) (standard_view, first_path, FALSE, 0.0f, 0.0f);
|
|
|
|
/* release the tree paths */
|
|
g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_set_selected_files_component (ThunarComponent *component,
|
|
GList *selected_files)
|
|
{
|
|
ThunarStandardView *standard_view = THUNAR_STANDARD_VIEW (component);
|
|
|
|
/* unselect only done via unselect all */
|
|
if (selected_files == NULL)
|
|
return;
|
|
|
|
/* clear the current selection */
|
|
if (standard_view->priv->selected_files != NULL)
|
|
{
|
|
thunar_g_list_free_full (standard_view->priv->selected_files);
|
|
standard_view->priv->selected_files = NULL;
|
|
}
|
|
|
|
/* The selection will either be updated directly, or after loading the folder got finished */
|
|
if (thunar_view_get_loading (THUNAR_VIEW (standard_view)))
|
|
{
|
|
/* update the files which are to select */
|
|
thunar_g_list_free_full (standard_view->priv->files_to_select);
|
|
standard_view->priv->files_to_select = thunar_g_list_copy_deep (selected_files);
|
|
}
|
|
else
|
|
{
|
|
thunar_standard_view_update_selected_files (standard_view, selected_files);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_set_selected_files_view (ThunarView *view,
|
|
GList *selected_files)
|
|
{
|
|
thunar_standard_view_set_selected_files_component (THUNAR_COMPONENT (view), selected_files);
|
|
}
|
|
|
|
|
|
|
|
static ThunarFile *
|
|
thunar_standard_view_get_current_directory (ThunarNavigator *navigator)
|
|
{
|
|
ThunarStandardView *standard_view = THUNAR_STANDARD_VIEW (navigator);
|
|
_thunar_return_val_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view), NULL);
|
|
return standard_view->priv->current_directory;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_scroll_position_save (ThunarStandardView *standard_view)
|
|
{
|
|
ThunarFile *first_file;
|
|
GFile *gfile;
|
|
|
|
_thunar_return_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view));
|
|
|
|
/* store the previous directory in the scroll hash table */
|
|
if (standard_view->priv->current_directory != NULL)
|
|
{
|
|
gfile = thunar_file_get_file (standard_view->priv->current_directory);
|
|
|
|
if (thunar_view_get_visible_range (THUNAR_VIEW (standard_view), &first_file, NULL))
|
|
{
|
|
/* add the file to our internal mapping of directories to scroll files */
|
|
g_hash_table_replace (standard_view->priv->scroll_to_files,
|
|
g_object_ref (gfile),
|
|
g_object_ref (thunar_file_get_file (first_file)));
|
|
g_object_unref (first_file);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_restore_selection_from_history (ThunarStandardView *standard_view)
|
|
{
|
|
GList selected_files;
|
|
ThunarFile *selected_file;
|
|
|
|
_thunar_return_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view));
|
|
_thunar_return_if_fail (THUNAR_IS_FILE (standard_view->priv->current_directory));
|
|
|
|
/* reset the selected files list */
|
|
selected_files.data = NULL;
|
|
selected_files.prev = NULL;
|
|
selected_files.next = NULL;
|
|
|
|
/* determine the next file in the history */
|
|
selected_file = thunar_history_peek_forward (standard_view->priv->history);
|
|
if (selected_file != NULL)
|
|
{
|
|
/* mark the file from history for selection if it is inside the new
|
|
* directory */
|
|
if (thunar_file_is_parent (standard_view->priv->current_directory, selected_file))
|
|
selected_files.data = selected_file;
|
|
else
|
|
g_object_unref (selected_file);
|
|
}
|
|
|
|
/* do the same with the previous file in the history */
|
|
if (selected_files.data == NULL)
|
|
{
|
|
selected_file = thunar_history_peek_back (standard_view->priv->history);
|
|
if (selected_file != NULL)
|
|
{
|
|
/* mark the file from history for selection if it is inside the
|
|
* new directory */
|
|
if (thunar_file_is_parent (standard_view->priv->current_directory, selected_file))
|
|
selected_files.data = selected_file;
|
|
else
|
|
g_object_unref (selected_file);
|
|
}
|
|
}
|
|
|
|
/* select the previous or next file from the history if it is inside the
|
|
* new current directory */
|
|
if (selected_files.data != NULL)
|
|
{
|
|
thunar_component_set_selected_files (THUNAR_COMPONENT (standard_view), &selected_files);
|
|
g_object_unref (G_OBJECT (selected_files.data));
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_set_current_directory (ThunarNavigator *navigator,
|
|
ThunarFile *current_directory)
|
|
{
|
|
ThunarStandardView *standard_view = THUNAR_STANDARD_VIEW (navigator);
|
|
ThunarFolder *folder;
|
|
|
|
_thunar_return_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view));
|
|
_thunar_return_if_fail (current_directory == NULL || THUNAR_IS_FILE (current_directory));
|
|
|
|
/* get the current directory */
|
|
if (standard_view->priv->current_directory == current_directory)
|
|
return;
|
|
|
|
/* store the current scroll position */
|
|
if (current_directory != NULL)
|
|
thunar_standard_view_scroll_position_save (standard_view);
|
|
|
|
/* release previous directory */
|
|
if (standard_view->priv->current_directory != NULL)
|
|
{
|
|
folder = thunar_folder_get_for_file (standard_view->priv->current_directory);
|
|
g_signal_handlers_disconnect_by_data (standard_view->priv->current_directory, standard_view);
|
|
g_signal_handlers_disconnect_by_data (folder, standard_view);
|
|
g_object_unref (folder);
|
|
g_object_unref (standard_view->priv->current_directory);
|
|
}
|
|
|
|
/* check if we want to reset the directory */
|
|
if (G_UNLIKELY (current_directory == NULL))
|
|
{
|
|
/* unset */
|
|
standard_view->priv->current_directory = NULL;
|
|
|
|
/* resetting the folder for the model can take some time if the view has
|
|
* to update the selection every time (i.e. closing a window with a lot of
|
|
* selected files), so we temporarily disconnect the model from the view.
|
|
*/
|
|
g_object_set (G_OBJECT (gtk_bin_get_child (GTK_BIN (standard_view))), "model", NULL, NULL);
|
|
|
|
/* reset the folder for the model */
|
|
thunar_standard_view_model_set_folder (standard_view->model, NULL, NULL);
|
|
|
|
/* reconnect the model to the view */
|
|
g_object_set (G_OBJECT (gtk_bin_get_child (GTK_BIN (standard_view))), "model", standard_view->model, NULL);
|
|
|
|
/* and we're done */
|
|
return;
|
|
}
|
|
|
|
/* take ref on new directory */
|
|
standard_view->priv->current_directory = g_object_ref (current_directory);
|
|
g_signal_connect (G_OBJECT (current_directory), "destroy", G_CALLBACK (thunar_standard_view_current_directory_destroy), standard_view);
|
|
g_signal_connect (G_OBJECT (current_directory), "changed", G_CALLBACK (thunar_standard_view_current_directory_changed), standard_view);
|
|
|
|
/* scroll to top-left when changing folder */
|
|
gtk_adjustment_set_value (gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (standard_view)), 0.0);
|
|
gtk_adjustment_set_value (gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (standard_view)), 0.0);
|
|
|
|
/* store the directory in the history */
|
|
thunar_navigator_set_current_directory (THUNAR_NAVIGATOR (standard_view->priv->history), current_directory);
|
|
|
|
/* if directory specific settings are enabled, apply them */
|
|
if (standard_view->priv->directory_specific_settings)
|
|
thunar_standard_view_apply_directory_specific_settings (standard_view, current_directory);
|
|
|
|
/* We drop the model from the view as a simple optimization to speed up
|
|
* the process of disconnecting the model data from the view.
|
|
*/
|
|
g_object_set (G_OBJECT (gtk_bin_get_child (GTK_BIN (standard_view))), "model", NULL, NULL);
|
|
|
|
/* open the new directory as folder */
|
|
folder = thunar_folder_get_for_file (current_directory);
|
|
g_signal_connect_swapped (folder, "thumbnails-updated", G_CALLBACK (thunar_standard_view_queue_redraw), standard_view);
|
|
|
|
/* connect to the loading property of the new directory */
|
|
standard_view->loading_binding =
|
|
g_object_bind_property_full (folder, "loading",
|
|
standard_view, "loading",
|
|
G_BINDING_SYNC_CREATE,
|
|
NULL, NULL,
|
|
standard_view,
|
|
thunar_standard_view_loading_unbound);
|
|
|
|
/* apply the new folder, ignore removal of any old files */
|
|
g_signal_handler_block (standard_view->model, standard_view->priv->row_deleted_id);
|
|
thunar_standard_view_model_set_folder (standard_view->model, folder, NULL);
|
|
g_signal_handler_unblock (standard_view->model, standard_view->priv->row_deleted_id);
|
|
g_object_unref (G_OBJECT (folder));
|
|
|
|
/* reconnect our model to the view */
|
|
g_object_set (G_OBJECT (gtk_bin_get_child (GTK_BIN (standard_view))), "model", standard_view->model, NULL);
|
|
|
|
/* notify all listeners about the new/old current directory */
|
|
g_object_notify_by_pspec (G_OBJECT (standard_view), standard_view_props[PROP_CURRENT_DIRECTORY]);
|
|
|
|
/* update tab label and tooltip */
|
|
g_object_notify_by_pspec (G_OBJECT (standard_view), standard_view_props[PROP_DISPLAY_NAME]);
|
|
g_object_notify_by_pspec (G_OBJECT (standard_view), standard_view_props[PROP_FULL_PARSED_PATH]);
|
|
|
|
/* restore the selection from the history */
|
|
thunar_standard_view_restore_selection_from_history (standard_view);
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_standard_view_get_loading (ThunarView *view)
|
|
{
|
|
return THUNAR_STANDARD_VIEW (view)->loading;
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_standard_view_get_searching (ThunarView *view)
|
|
{
|
|
return THUNAR_STANDARD_VIEW (view)->priv->active_search;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_set_loading (ThunarStandardView *standard_view,
|
|
gboolean loading)
|
|
{
|
|
ThunarFile *file;
|
|
GList *new_files_path_list;
|
|
GFile *first_file;
|
|
ThunarFile *current_directory;
|
|
|
|
loading = !!loading;
|
|
|
|
/* check if we're already in that state */
|
|
if (G_UNLIKELY (standard_view->loading == loading))
|
|
return;
|
|
|
|
/* apply the new state */
|
|
standard_view->loading = loading;
|
|
|
|
/* check if we have a path list from new_files pending */
|
|
if (G_UNLIKELY (!loading && standard_view->priv->new_files_path_list != NULL))
|
|
{
|
|
/* remember and reset the new_files_path_list */
|
|
new_files_path_list = standard_view->priv->new_files_path_list;
|
|
standard_view->priv->new_files_path_list = NULL;
|
|
|
|
/* and try again */
|
|
thunar_standard_view_new_files (standard_view, new_files_path_list);
|
|
|
|
/* cleanup */
|
|
thunar_g_list_free_full (new_files_path_list);
|
|
}
|
|
|
|
/* when loading is finished, update the selection if required */
|
|
if (!loading && standard_view->priv->files_to_select != NULL)
|
|
{
|
|
thunar_standard_view_update_selected_files (standard_view, standard_view->priv->files_to_select);
|
|
thunar_g_list_free_full (standard_view->priv->files_to_select);
|
|
standard_view->priv->files_to_select = NULL;
|
|
}
|
|
|
|
/* check if we're done loading and have a scheduled scroll_to_file
|
|
* scrolling after loading circumvents the scroll caused by gtk_tree_view_set_cell */
|
|
if (THUNAR_IS_DETAILS_VIEW (standard_view))
|
|
{
|
|
gchar *statusbar_text;
|
|
|
|
statusbar_text = g_strdup (_("Loading folder contents..."));
|
|
thunar_standard_view_set_statusbar_text (standard_view, statusbar_text);
|
|
g_free (statusbar_text);
|
|
g_object_notify_by_pspec (G_OBJECT (standard_view), standard_view_props[PROP_STATUSBAR_TEXT]);
|
|
|
|
if (G_UNLIKELY (!loading))
|
|
{
|
|
/* update the statusbar text after loading is finished */
|
|
thunar_standard_view_update_statusbar_text (standard_view);
|
|
|
|
if (standard_view->priv->scroll_to_file != NULL)
|
|
{
|
|
/* remember and reset the scroll_to_file reference */
|
|
file = standard_view->priv->scroll_to_file;
|
|
standard_view->priv->scroll_to_file = NULL;
|
|
|
|
/* and try again */
|
|
thunar_view_scroll_to_file (THUNAR_VIEW (standard_view), file,
|
|
standard_view->priv->scroll_to_select,
|
|
standard_view->priv->scroll_to_use_align,
|
|
standard_view->priv->scroll_to_row_align,
|
|
standard_view->priv->scroll_to_col_align);
|
|
|
|
/* cleanup */
|
|
g_object_unref (G_OBJECT (file));
|
|
}
|
|
else
|
|
{
|
|
/* look for a first visible file in the hash table */
|
|
current_directory = thunar_navigator_get_current_directory (THUNAR_NAVIGATOR (standard_view));
|
|
if (G_LIKELY (current_directory != NULL))
|
|
{
|
|
first_file = g_hash_table_lookup (standard_view->priv->scroll_to_files, thunar_file_get_file (current_directory));
|
|
if (G_LIKELY (first_file != NULL))
|
|
{
|
|
file = thunar_file_cache_lookup (first_file);
|
|
if (G_LIKELY (file != NULL))
|
|
{
|
|
thunar_view_scroll_to_file (THUNAR_VIEW (standard_view), file, FALSE, TRUE, 0.0f, 0.0f);
|
|
g_object_unref (file);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* notify listeners */
|
|
g_object_freeze_notify (G_OBJECT (standard_view));
|
|
g_object_notify_by_pspec (G_OBJECT (standard_view), standard_view_props[PROP_LOADING]);
|
|
g_object_thaw_notify (G_OBJECT (standard_view));
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_load_statusbar_text_finished (ThunarJob *job,
|
|
ThunarStandardView *standard_view)
|
|
{
|
|
_thunar_return_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view));
|
|
|
|
/* tell everybody that the statusbar text may have changed */
|
|
g_object_notify_by_pspec (G_OBJECT (standard_view), standard_view_props[PROP_STATUSBAR_TEXT]);
|
|
|
|
if (standard_view->priv->statusbar_job != NULL)
|
|
{
|
|
g_object_unref (standard_view->priv->statusbar_job);
|
|
standard_view->priv->statusbar_job = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
thunar_standard_view_set_statusbar_text (ThunarStandardView *standard_view,
|
|
const gchar *text)
|
|
{
|
|
_thunar_return_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view));
|
|
|
|
g_mutex_lock (&standard_view->priv->statusbar_text_mutex);
|
|
g_free (standard_view->priv->statusbar_text);
|
|
standard_view->priv->statusbar_text = g_strdup (text);
|
|
g_mutex_unlock (&standard_view->priv->statusbar_text_mutex);
|
|
}
|
|
|
|
|
|
static gchar *
|
|
thunar_standard_view_get_statusbar_text (ThunarView *view)
|
|
{
|
|
ThunarStandardView *standard_view = THUNAR_STANDARD_VIEW (view);
|
|
gchar *text;
|
|
|
|
_thunar_return_val_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view), NULL);
|
|
|
|
g_mutex_lock (&standard_view->priv->statusbar_text_mutex);
|
|
if (standard_view->priv->statusbar_text == 0)
|
|
text = g_strdup (" ");
|
|
else
|
|
text = g_strdup (standard_view->priv->statusbar_text);
|
|
g_mutex_unlock (&standard_view->priv->statusbar_text_mutex);
|
|
|
|
return text;
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_standard_view_get_show_hidden (ThunarView *view)
|
|
{
|
|
return thunar_standard_view_model_get_show_hidden (THUNAR_STANDARD_VIEW (view)->model);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_set_show_hidden (ThunarView *view,
|
|
gboolean show_hidden)
|
|
{
|
|
ThunarStandardView *standard_view = THUNAR_STANDARD_VIEW (view);
|
|
|
|
/* set new value without triggering thunar_standard_view_select_after_row_deleted
|
|
* to prevent selection changes */
|
|
g_signal_handler_block (standard_view->model, standard_view->priv->row_deleted_id);
|
|
thunar_standard_view_model_set_show_hidden (standard_view->model, show_hidden);
|
|
g_signal_handler_unblock (standard_view->model, standard_view->priv->row_deleted_id);
|
|
}
|
|
|
|
|
|
|
|
static ThunarZoomLevel
|
|
thunar_standard_view_get_zoom_level (ThunarView *view)
|
|
{
|
|
return THUNAR_STANDARD_VIEW (view)->priv->zoom_level;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_set_zoom_level (ThunarView *view,
|
|
ThunarZoomLevel zoom_level)
|
|
{
|
|
ThunarStandardView *standard_view = THUNAR_STANDARD_VIEW (view);
|
|
gchar *zoom_level_attribute_name;
|
|
|
|
/* check if we have a new zoom-level here */
|
|
if (G_LIKELY (standard_view->priv->zoom_level != zoom_level))
|
|
{
|
|
if (standard_view->priv->directory_specific_settings)
|
|
{
|
|
const gchar *zoom_level_name;
|
|
|
|
/* save the zoom level name */
|
|
zoom_level_name = thunar_zoom_level_string_from_value (zoom_level);
|
|
if (zoom_level_name != NULL)
|
|
{
|
|
/* do not set it asynchronously to ensure the correct operation of thumbnails (check the commit message for more) */
|
|
zoom_level_attribute_name = g_strdup_printf ("thunar-zoom-level-%s", G_OBJECT_TYPE_NAME (standard_view));
|
|
thunar_file_set_metadata_setting (standard_view->priv->current_directory, zoom_level_attribute_name, zoom_level_name, FALSE);
|
|
g_free (zoom_level_attribute_name);
|
|
}
|
|
}
|
|
|
|
standard_view->priv->zoom_level = zoom_level;
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (standard_view), standard_view_props[PROP_ZOOM_LEVEL]);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_reset_zoom_level (ThunarView *view)
|
|
{
|
|
ThunarStandardView *standard_view = THUNAR_STANDARD_VIEW (view);
|
|
const gchar *property_name;
|
|
GParamSpec *pspec;
|
|
GValue value = G_VALUE_INIT;
|
|
|
|
/* determine the default zoom level from the preferences */
|
|
property_name = THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->zoom_level_property_name;
|
|
pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (standard_view->preferences), property_name);
|
|
g_value_init (&value, THUNAR_TYPE_ZOOM_LEVEL);
|
|
g_param_value_set_default (pspec, &value);
|
|
g_object_set_property (G_OBJECT (view), "zoom-level", &value);
|
|
g_value_unset (&value);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_apply_directory_specific_settings (ThunarStandardView *standard_view,
|
|
ThunarFile *directory)
|
|
{
|
|
gchar *sort_column_name;
|
|
gchar *sort_order_name;
|
|
gchar *zoom_level_name;
|
|
ThunarColumn sort_column;
|
|
GtkSortType sort_order;
|
|
gint zoom_level;
|
|
gchar *zoom_level_attribute_name;
|
|
|
|
/* get the default sort column and sort order */
|
|
g_object_get (G_OBJECT (standard_view->preferences), "last-sort-column", &sort_column, "last-sort-order", &sort_order, NULL);
|
|
|
|
/* get the stored directory specific settings (if any) */
|
|
sort_column_name = thunar_file_get_metadata_setting (directory, "thunar-sort-column");
|
|
sort_order_name = thunar_file_get_metadata_setting (directory, "thunar-sort-order");
|
|
|
|
zoom_level_attribute_name = g_strdup_printf ("thunar-zoom-level-%s", G_OBJECT_TYPE_NAME (standard_view));
|
|
zoom_level_name = thunar_file_get_metadata_setting (directory, zoom_level_attribute_name);
|
|
g_free (zoom_level_attribute_name);
|
|
|
|
/* View specific zoom level was added later on .. fall back to shared zoom-level if not found */
|
|
if (zoom_level_name == NULL)
|
|
zoom_level_name = thunar_file_get_metadata_setting (directory, "thunar-zoom-level");
|
|
|
|
/* convert the sort column name to a value */
|
|
if (sort_column_name != NULL)
|
|
thunar_column_value_from_string (sort_column_name, &sort_column);
|
|
g_free (sort_column_name);
|
|
|
|
/* Out of range ? Use the default value */
|
|
if (sort_column >= THUNAR_N_VISIBLE_COLUMNS)
|
|
sort_column = standard_view->priv->sort_column_default;
|
|
|
|
/* convert the sort order name to a value */
|
|
if (sort_order_name != NULL)
|
|
{
|
|
if (g_strcmp0 (sort_order_name, "GTK_SORT_DESCENDING") == 0)
|
|
sort_order = GTK_SORT_DESCENDING;
|
|
else if (g_strcmp0 (sort_order_name, "GTK_SORT_ASCENDING") == 0)
|
|
sort_order = GTK_SORT_ASCENDING;
|
|
else
|
|
sort_order = standard_view->priv->sort_order_default;
|
|
g_free (sort_order_name);
|
|
}
|
|
|
|
/* convert the zoom level name to a value */
|
|
if (zoom_level_name != NULL)
|
|
{
|
|
if (thunar_zoom_level_value_from_string (zoom_level_name, &zoom_level) == TRUE)
|
|
thunar_standard_view_set_zoom_level (THUNAR_VIEW (standard_view), zoom_level);
|
|
g_free (zoom_level_name);
|
|
}
|
|
|
|
/* thunar_standard_view_sort_column_changed saves the directory specific settings to the directory, but we do not
|
|
* want that behaviour here so we disconnect the signal before calling gtk_tree_sortable_set_sort_column_id */
|
|
g_signal_handlers_disconnect_by_func (G_OBJECT (standard_view->model),
|
|
G_CALLBACK (thunar_standard_view_sort_column_changed),
|
|
standard_view);
|
|
|
|
/* apply the sort column and sort order */
|
|
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (standard_view->model), sort_column, sort_order);
|
|
standard_view->priv->sort_column = sort_column;
|
|
standard_view->priv->sort_order = sort_order;
|
|
|
|
/* request a selection update */
|
|
thunar_standard_view_selection_changed (standard_view);
|
|
|
|
/* reconnect the signal */
|
|
g_signal_connect (G_OBJECT (standard_view->model),
|
|
"sort-column-changed",
|
|
G_CALLBACK (thunar_standard_view_sort_column_changed),
|
|
standard_view);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_set_directory_specific_settings (ThunarStandardView *standard_view,
|
|
gboolean directory_specific_settings)
|
|
{
|
|
/* save the setting */
|
|
standard_view->priv->directory_specific_settings = directory_specific_settings;
|
|
|
|
/* if there is no current directory then return */
|
|
if (standard_view->priv->current_directory == NULL)
|
|
return;
|
|
|
|
/* apply the appropriate settings */
|
|
if (directory_specific_settings)
|
|
{
|
|
/* apply the directory specific settings (if any) */
|
|
thunar_standard_view_apply_directory_specific_settings (standard_view, standard_view->priv->current_directory);
|
|
}
|
|
else /* apply the shared settings to the current view */
|
|
{
|
|
ThunarColumn sort_column;
|
|
GtkSortType sort_order;
|
|
|
|
/* apply the last sort column and sort order */
|
|
g_object_get (G_OBJECT (standard_view->preferences), "last-sort-column", &sort_column, "last-sort-order", &sort_order, NULL);
|
|
if (sort_column >= THUNAR_N_VISIBLE_COLUMNS)
|
|
sort_column = THUNAR_COLUMN_NAME;
|
|
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (standard_view->model), sort_column, sort_order);
|
|
standard_view->priv->sort_column = sort_column;
|
|
standard_view->priv->sort_order = sort_order;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_reload (ThunarView *view,
|
|
gboolean reload_info)
|
|
{
|
|
ThunarStandardView *standard_view = THUNAR_STANDARD_VIEW (view);
|
|
ThunarFolder *folder;
|
|
ThunarFile *file;
|
|
|
|
/* determine the folder for the view model */
|
|
folder = thunar_standard_view_model_get_folder (standard_view->model);
|
|
if (G_LIKELY (folder != NULL))
|
|
{
|
|
file = thunar_folder_get_corresponding_file (folder);
|
|
|
|
if (thunar_file_exists (file))
|
|
thunar_folder_reload (folder, reload_info);
|
|
else
|
|
thunar_standard_view_current_directory_destroy (file, standard_view);
|
|
}
|
|
|
|
/* if directory specific settings are enabled, apply them. the reload might have been triggered */
|
|
/* specifically to ensure that any change in these settings is applied */
|
|
if (standard_view->priv->directory_specific_settings)
|
|
thunar_standard_view_apply_directory_specific_settings (standard_view, standard_view->priv->current_directory);
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_standard_view_get_visible_range (ThunarView *view,
|
|
ThunarFile **start_file,
|
|
ThunarFile **end_file)
|
|
{
|
|
ThunarStandardView *standard_view = THUNAR_STANDARD_VIEW (view);
|
|
GtkTreePath *start_path;
|
|
GtkTreePath *end_path;
|
|
GtkTreeIter iter;
|
|
|
|
/* determine the visible range as tree paths */
|
|
if ((*THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->get_visible_range) (standard_view, &start_path, &end_path))
|
|
{
|
|
/* determine the file for the start path */
|
|
if (G_LIKELY (start_file != NULL))
|
|
{
|
|
gtk_tree_model_get_iter (GTK_TREE_MODEL (standard_view->model), &iter, start_path);
|
|
*start_file = thunar_standard_view_model_get_file (standard_view->model, &iter);
|
|
if (*start_file == NULL)
|
|
return FALSE;
|
|
}
|
|
|
|
/* determine the file for the end path */
|
|
if (G_LIKELY (end_file != NULL))
|
|
{
|
|
gtk_tree_model_get_iter (GTK_TREE_MODEL (standard_view->model), &iter, end_path);
|
|
*end_file = thunar_standard_view_model_get_file (standard_view->model, &iter);
|
|
if (*end_file == NULL)
|
|
return FALSE;
|
|
}
|
|
|
|
/* release the tree paths */
|
|
gtk_tree_path_free (start_path);
|
|
gtk_tree_path_free (end_path);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_scroll_to_file (ThunarView *view,
|
|
ThunarFile *file,
|
|
gboolean select_file,
|
|
gboolean use_align,
|
|
gfloat row_align,
|
|
gfloat col_align)
|
|
{
|
|
ThunarStandardView *standard_view = THUNAR_STANDARD_VIEW (view);
|
|
GList files;
|
|
GList *paths;
|
|
|
|
/* release the previous scroll_to_file reference (if any) */
|
|
if (G_UNLIKELY (standard_view->priv->scroll_to_file != NULL))
|
|
{
|
|
g_object_unref (G_OBJECT (standard_view->priv->scroll_to_file));
|
|
standard_view->priv->scroll_to_file = NULL;
|
|
}
|
|
|
|
/* check if we're still loading */
|
|
if (thunar_view_get_loading (view))
|
|
{
|
|
/* remember a reference for the new file and settings */
|
|
standard_view->priv->scroll_to_file = THUNAR_FILE (g_object_ref (G_OBJECT (file)));
|
|
standard_view->priv->scroll_to_select = select_file;
|
|
standard_view->priv->scroll_to_use_align = use_align;
|
|
standard_view->priv->scroll_to_row_align = row_align;
|
|
standard_view->priv->scroll_to_col_align = col_align;
|
|
}
|
|
else
|
|
{
|
|
/* fake a file list */
|
|
files.data = file;
|
|
files.next = NULL;
|
|
files.prev = NULL;
|
|
|
|
/* determine the path for the file */
|
|
paths = thunar_standard_view_model_get_paths_for_files (standard_view->model, &files);
|
|
if (G_LIKELY (paths != NULL))
|
|
{
|
|
/* scroll to the path */
|
|
(*THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->scroll_to_path) (standard_view, paths->data, use_align, row_align, col_align);
|
|
|
|
/* check if we should also alter the selection */
|
|
if (G_UNLIKELY (select_file))
|
|
{
|
|
/* select only the file in question */
|
|
(*THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->unselect_all) (standard_view);
|
|
(*THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->select_path) (standard_view, paths->data);
|
|
}
|
|
|
|
/* cleanup */
|
|
g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static GdkDragAction
|
|
thunar_standard_view_get_dest_actions (ThunarStandardView *standard_view,
|
|
GdkDragContext *context,
|
|
gint x,
|
|
gint y,
|
|
guint timestamp,
|
|
ThunarFile **file_return)
|
|
{
|
|
GdkDragAction actions = 0;
|
|
GdkDragAction action = 0;
|
|
GtkTreePath *path;
|
|
ThunarFile *file;
|
|
gboolean skip_drop_highlight = FALSE;
|
|
|
|
/* determine the file and path for the given coordinates */
|
|
file = thunar_standard_view_get_drop_file (standard_view, x, y, &path);
|
|
|
|
/* check if we can drop there */
|
|
if (G_LIKELY (file != NULL) && G_LIKELY (standard_view->priv->search_query == NULL))
|
|
{
|
|
/* determine the possible drop actions for the file (and the suggested action if any) */
|
|
actions = thunar_file_accepts_drop (file, standard_view->priv->drop_file_list, context, &action);
|
|
if (G_LIKELY (actions != 0))
|
|
{
|
|
/* tell the caller about the file (if it's interested) */
|
|
if (G_UNLIKELY (file_return != NULL))
|
|
*file_return = THUNAR_FILE (g_object_ref (G_OBJECT (file)));
|
|
}
|
|
}
|
|
|
|
/* reset path if we cannot drop */
|
|
if (G_UNLIKELY (action == 0 && path != NULL))
|
|
{
|
|
gtk_tree_path_free (path);
|
|
path = NULL;
|
|
}
|
|
|
|
/* setup the drop-file for the icon renderer, so the user
|
|
* gets good visual feedback for the drop target */
|
|
g_object_set (G_OBJECT (standard_view->icon_renderer), "drop-file", (action != 0) ? file : NULL, NULL);
|
|
|
|
/* check if we are dragging files within the same folder and
|
|
* only highlight the view for possible actions (copy or link) */
|
|
if (action == GDK_ACTION_MOVE && standard_view->priv->drop_file_list != NULL)
|
|
{
|
|
GFile *drop_file = standard_view->priv->drop_file_list->data;
|
|
GFile *parent_file = g_file_get_parent (drop_file);
|
|
|
|
if (parent_file != NULL)
|
|
{
|
|
skip_drop_highlight = g_file_equal (parent_file, thunar_file_get_file (standard_view->priv->current_directory));
|
|
g_object_unref (parent_file);
|
|
}
|
|
}
|
|
|
|
/* do the view highlighting */
|
|
if (standard_view->priv->drop_highlight != (path == NULL && action != 0 && !skip_drop_highlight))
|
|
{
|
|
standard_view->priv->drop_highlight = (path == NULL && action != 0 && !skip_drop_highlight);
|
|
gtk_widget_queue_draw (GTK_WIDGET (standard_view));
|
|
}
|
|
|
|
/* do the item highlighting */
|
|
(*THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->highlight_path) (standard_view, path);
|
|
|
|
/* stop any running drag enter timer */
|
|
if (G_UNLIKELY (standard_view->priv->drag_enter_timer_id != 0))
|
|
g_source_remove (standard_view->priv->drag_enter_timer_id);
|
|
|
|
/* skip if the drag target is the current directory */
|
|
if (action != 0 && file != standard_view->priv->current_directory)
|
|
{
|
|
gint delay = 1000;
|
|
|
|
/* remember the drag target */
|
|
standard_view->priv->drag_enter_target = g_object_ref (file);
|
|
|
|
/* schedule the drag enter timer */
|
|
standard_view->priv->drag_enter_timer_id = g_timeout_add_full (G_PRIORITY_LOW, delay,
|
|
thunar_standard_view_drag_enter_timer, standard_view,
|
|
thunar_standard_view_drag_enter_timer_destroy);
|
|
}
|
|
|
|
/* tell Gdk whether we can drop here */
|
|
gdk_drag_status (context, action, timestamp);
|
|
|
|
/* clean up */
|
|
if (G_LIKELY (file != NULL))
|
|
g_object_unref (G_OBJECT (file));
|
|
if (G_LIKELY (path != NULL))
|
|
gtk_tree_path_free (path);
|
|
|
|
return actions;
|
|
}
|
|
|
|
|
|
|
|
static ThunarFile *
|
|
thunar_standard_view_get_drop_file (ThunarStandardView *standard_view,
|
|
gint x,
|
|
gint y,
|
|
GtkTreePath **path_return)
|
|
{
|
|
GtkTreePath *path = NULL;
|
|
GtkTreeIter iter;
|
|
ThunarFile *file = NULL, *parent = NULL;
|
|
GList list;
|
|
GList *path_list;
|
|
|
|
/* determine the path for the given coordinates */
|
|
path = (*THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->get_path_at_pos) (standard_view, x, y);
|
|
if (G_LIKELY (path != NULL))
|
|
{
|
|
/* determine the file for the path */
|
|
gtk_tree_model_get_iter (GTK_TREE_MODEL (standard_view->model), &iter, path);
|
|
file = thunar_standard_view_model_get_file (standard_view->model, &iter);
|
|
|
|
/* we can only drop to directories and executable files */
|
|
if (file != NULL && !thunar_file_is_directory (file) && !thunar_file_can_execute (file, NULL))
|
|
{
|
|
/* since this is a file and not a folder we should
|
|
* instead drop the source file into the direct parent
|
|
* of this file i.e make the source a sibling of this file */
|
|
parent = thunar_file_get_parent (file, NULL);
|
|
g_object_unref (G_OBJECT (file));
|
|
file = parent;
|
|
|
|
/* free the previous path */
|
|
gtk_tree_path_free (path);
|
|
path = NULL;
|
|
|
|
if (file != NULL)
|
|
{
|
|
/* get the path of the parent */
|
|
list.data = file;
|
|
list.next = NULL;
|
|
list.prev = NULL;
|
|
path_list = thunar_standard_view_model_get_paths_for_files (standard_view->model, &list);
|
|
if (path_list != NULL)
|
|
{
|
|
path = path_list->data;
|
|
/* only free the container */
|
|
g_list_free (path_list);
|
|
path_list = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* if we don't have a path yet, we'll drop to the folder instead */
|
|
if (G_UNLIKELY (path == NULL))
|
|
{
|
|
/* determine the current directory */
|
|
file = thunar_navigator_get_current_directory (THUNAR_NAVIGATOR (standard_view));
|
|
if (G_LIKELY (file != NULL))
|
|
g_object_ref (G_OBJECT (file));
|
|
}
|
|
|
|
/* return the path (if any) */
|
|
if (G_LIKELY (path_return != NULL))
|
|
*path_return = path;
|
|
else if (G_LIKELY (path != NULL))
|
|
gtk_tree_path_free (path);
|
|
|
|
return file;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_update_statusbar_text_error (ThunarJob *job,
|
|
ThunarStandardView *standard_view)
|
|
{
|
|
g_warning ("Error while updating statusbar");
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_standard_view_update_statusbar_text_idle (gpointer data)
|
|
{
|
|
ThunarStandardView *standard_view = THUNAR_STANDARD_VIEW (data);
|
|
GList *selected_items_tree_path_list;
|
|
GtkTreeIter iter;
|
|
ThunarFile *file;
|
|
gchar *statusbar_text;
|
|
|
|
_thunar_return_val_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view), FALSE);
|
|
|
|
standard_view->priv->statusbar_text_idle_id = 0;
|
|
|
|
/* cancel pending statusbar job, if any */
|
|
if (standard_view->priv->statusbar_job != NULL)
|
|
{
|
|
g_signal_handlers_disconnect_by_data (standard_view->priv->statusbar_job, standard_view);
|
|
exo_job_cancel (EXO_JOB (standard_view->priv->statusbar_job));
|
|
g_object_unref (standard_view->priv->statusbar_job);
|
|
standard_view->priv->statusbar_job = NULL;
|
|
}
|
|
|
|
/* query the selected items */
|
|
selected_items_tree_path_list = THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->get_selected_items (standard_view);
|
|
|
|
if (selected_items_tree_path_list == NULL) /* nothing selected */
|
|
{
|
|
if (thunar_standard_view_model_get_folder (standard_view->model) == NULL)
|
|
return FALSE;
|
|
|
|
/* search mode active */
|
|
if (standard_view->priv->search_query != NULL)
|
|
{
|
|
gint num_files;
|
|
|
|
g_object_get (G_OBJECT (standard_view->model), "num-files", &num_files, NULL);
|
|
|
|
statusbar_text = g_strdup_printf (ngettext ("%d file found", "%d files found", num_files), num_files);
|
|
thunar_standard_view_set_statusbar_text (standard_view, statusbar_text);
|
|
g_free (statusbar_text);
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (standard_view), standard_view_props[PROP_STATUSBAR_TEXT]);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
standard_view->priv->statusbar_job = thunar_io_jobs_load_statusbar_text_for_folder (standard_view, thunar_standard_view_model_get_folder (standard_view->model));
|
|
|
|
g_signal_connect (standard_view->priv->statusbar_job, "error", G_CALLBACK (thunar_standard_view_update_statusbar_text_error), standard_view);
|
|
g_signal_connect (standard_view->priv->statusbar_job, "finished", G_CALLBACK (thunar_standard_view_load_statusbar_text_finished), standard_view);
|
|
|
|
exo_job_launch (EXO_JOB (standard_view->priv->statusbar_job));
|
|
}
|
|
else if (selected_items_tree_path_list->next == NULL) /* only one item selected */
|
|
{
|
|
/* resolve the iter for the single path */
|
|
gtk_tree_model_get_iter (GTK_TREE_MODEL (standard_view->model), &iter, selected_items_tree_path_list->data);
|
|
|
|
/* get the file for the given iter */
|
|
file = thunar_standard_view_model_get_file (standard_view->model, &iter);
|
|
|
|
if (file == NULL)
|
|
return FALSE;
|
|
|
|
/* For s single file we load the text without using a separate job */
|
|
statusbar_text = thunar_util_get_statusbar_text_for_single_file (file);
|
|
if (statusbar_text != NULL)
|
|
thunar_standard_view_set_statusbar_text (standard_view, statusbar_text);
|
|
g_free (statusbar_text);
|
|
|
|
g_object_unref (file);
|
|
g_object_notify_by_pspec (G_OBJECT (standard_view), standard_view_props[PROP_STATUSBAR_TEXT]);
|
|
}
|
|
else /* more than one item selected */
|
|
{
|
|
GHashTable *selected_g_files = g_hash_table_new (g_direct_hash, NULL);
|
|
GList *lp;
|
|
|
|
/* build GList of files from selection */
|
|
for (lp = selected_items_tree_path_list; lp != NULL; lp = lp->next)
|
|
{
|
|
gtk_tree_model_get_iter (GTK_TREE_MODEL (standard_view->model), &iter, lp->data);
|
|
file = thunar_standard_view_model_get_file (standard_view->model, &iter);
|
|
if (file != NULL)
|
|
{
|
|
g_hash_table_add (selected_g_files, thunar_file_get_file (file));
|
|
g_object_unref (file);
|
|
}
|
|
}
|
|
|
|
standard_view->priv->statusbar_job = thunar_io_jobs_load_statusbar_text_for_selection (standard_view, selected_g_files);
|
|
g_signal_connect (standard_view->priv->statusbar_job, "error", G_CALLBACK (thunar_standard_view_update_statusbar_text_error), standard_view);
|
|
g_signal_connect (standard_view->priv->statusbar_job, "finished", G_CALLBACK (thunar_standard_view_load_statusbar_text_finished), standard_view);
|
|
g_hash_table_destroy (selected_g_files);
|
|
|
|
exo_job_launch (EXO_JOB (standard_view->priv->statusbar_job));
|
|
}
|
|
|
|
g_list_free_full (selected_items_tree_path_list, (GDestroyNotify) gtk_tree_path_free);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
void
|
|
thunar_standard_view_update_statusbar_text (ThunarStandardView *standard_view)
|
|
{
|
|
/* stop pending timeout */
|
|
if (standard_view->priv->statusbar_text_idle_id != 0)
|
|
g_source_remove (standard_view->priv->statusbar_text_idle_id);
|
|
|
|
/* restart a new one, this way we avoid multiple update when
|
|
* the user is pressing a key to scroll */
|
|
standard_view->priv->statusbar_text_idle_id =
|
|
g_timeout_add_full (G_PRIORITY_DEFAULT, 50, thunar_standard_view_update_statusbar_text_idle,
|
|
standard_view, NULL);
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Find a fallback directory we can navigate to if the directory gets
|
|
* deleted. It first tries the parent folders, and finally if none can
|
|
* be found, the home folder. If the home folder cannot be accessed,
|
|
* the error will be stored for use by the caller.
|
|
*/
|
|
static ThunarFile *
|
|
thunar_standard_view_get_fallback_directory (ThunarFile *directory,
|
|
GError *error)
|
|
{
|
|
ThunarFile *new_directory = NULL;
|
|
GFile *path;
|
|
GFile *tmp;
|
|
|
|
_thunar_return_val_if_fail (THUNAR_IS_FILE (directory), NULL);
|
|
|
|
/* determine the path of the directory */
|
|
path = g_object_ref (thunar_file_get_file (directory));
|
|
|
|
/* try to find a parent directory that still exists */
|
|
while (new_directory == NULL)
|
|
{
|
|
/* check whether the directory exists */
|
|
if (g_file_query_exists (path, NULL))
|
|
{
|
|
/* it does, try to load the file */
|
|
new_directory = thunar_file_get (path, NULL);
|
|
|
|
/* fall back to $HOME if loading the file failed */
|
|
if (new_directory == NULL)
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
/* determine the parent of the directory */
|
|
tmp = g_file_get_parent (path);
|
|
|
|
/* if there's no parent this means that we've found no parent
|
|
* that still exists at all. Fall back to $HOME then */
|
|
if (tmp == NULL)
|
|
break;
|
|
|
|
/* free the old directory */
|
|
g_object_unref (path);
|
|
|
|
/* check the parent next */
|
|
path = tmp;
|
|
}
|
|
}
|
|
|
|
/* release last ref */
|
|
if (path != NULL)
|
|
g_object_unref (path);
|
|
|
|
if (new_directory == NULL)
|
|
{
|
|
/* fall-back to the home directory */
|
|
path = thunar_g_file_new_for_home ();
|
|
new_directory = thunar_file_get (path, &error);
|
|
g_object_unref (path);
|
|
}
|
|
|
|
return new_directory;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_current_directory_destroy (ThunarFile *current_directory,
|
|
ThunarStandardView *standard_view)
|
|
{
|
|
GtkWidget *window;
|
|
ThunarFile *new_directory = NULL;
|
|
GError *error = NULL;
|
|
|
|
_thunar_return_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view));
|
|
_thunar_return_if_fail (THUNAR_IS_FILE (current_directory));
|
|
_thunar_return_if_fail (standard_view->priv->current_directory == current_directory);
|
|
|
|
/* get a fallback directory (parents or home) we can navigate to */
|
|
new_directory = thunar_standard_view_get_fallback_directory (current_directory, error);
|
|
if (G_UNLIKELY (new_directory == NULL))
|
|
{
|
|
/* display an error to the user */
|
|
thunar_dialogs_show_error (GTK_WIDGET (standard_view), error, _("Failed to open the home folder"));
|
|
g_error_free (error);
|
|
return;
|
|
}
|
|
|
|
/* let the parent window update all active and inactive views (tabs) */
|
|
window = gtk_widget_get_toplevel (GTK_WIDGET (standard_view));
|
|
thunar_window_update_directories (THUNAR_WINDOW (window),
|
|
current_directory,
|
|
new_directory);
|
|
|
|
/* release the reference to the new directory */
|
|
g_object_unref (new_directory);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_current_directory_changed (ThunarFile *current_directory,
|
|
ThunarStandardView *standard_view)
|
|
{
|
|
_thunar_return_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view));
|
|
_thunar_return_if_fail (THUNAR_IS_FILE (current_directory));
|
|
_thunar_return_if_fail (standard_view->priv->current_directory == current_directory);
|
|
|
|
/* update tab label and tooltip */
|
|
g_object_notify_by_pspec (G_OBJECT (standard_view), standard_view_props[PROP_DISPLAY_NAME]);
|
|
g_object_notify_by_pspec (G_OBJECT (standard_view), standard_view_props[PROP_FULL_PARSED_PATH]);
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_standard_view_select_all_files (ThunarView *view)
|
|
{
|
|
ThunarStandardView *standard_view = THUNAR_STANDARD_VIEW (view);
|
|
|
|
_thunar_return_val_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view), FALSE);
|
|
|
|
/* grab the focus to the view */
|
|
gtk_widget_grab_focus (GTK_WIDGET (standard_view));
|
|
|
|
/* select all files in the real view */
|
|
(*THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->select_all) (standard_view);
|
|
|
|
/* required in case of shortcut activation, in order to signal that the accel key got handled */
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_standard_view_select_by_pattern (ThunarView *view)
|
|
{
|
|
ThunarStandardView *standard_view = THUNAR_STANDARD_VIEW (view);
|
|
GtkWidget *window;
|
|
GtkWidget *dialog;
|
|
GtkBox *content_area;
|
|
GtkGrid *grid;
|
|
GtkWidget *label;
|
|
GtkWidget *entry;
|
|
GtkWidget *info_image;
|
|
GtkWidget *case_sensitive_button;
|
|
GtkWidget *match_diacritics_button;
|
|
GList *paths;
|
|
GList *lp;
|
|
gint response;
|
|
const gchar *pattern;
|
|
gchar *pattern_extended = NULL;
|
|
gboolean case_sensitive;
|
|
gboolean match_diacritics;
|
|
gint row = 0;
|
|
|
|
_thunar_return_val_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view), FALSE);
|
|
|
|
window = gtk_widget_get_toplevel (GTK_WIDGET (standard_view));
|
|
/* TRANSLATORS: Dialog allowing selection by wildcard ("*.c", etc.) */
|
|
dialog = gtk_dialog_new_with_buttons (C_ ("Select by Pattern dialog: title",
|
|
"Select by Pattern"),
|
|
GTK_WINDOW (window),
|
|
GTK_DIALOG_MODAL
|
|
| GTK_DIALOG_DESTROY_WITH_PARENT,
|
|
C_ ("Select by Pattern dialog: buttons",
|
|
"_Cancel"),
|
|
GTK_RESPONSE_CANCEL,
|
|
C_ ("Select by Pattern dialog: buttons",
|
|
"_Select"),
|
|
GTK_RESPONSE_OK,
|
|
NULL);
|
|
gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
|
|
gtk_window_set_default_size (GTK_WINDOW (dialog), 290, -1);
|
|
|
|
content_area = GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog)));
|
|
gtk_container_set_border_width (GTK_CONTAINER (content_area), 6);
|
|
|
|
grid = GTK_GRID (gtk_grid_new ());
|
|
g_object_set (G_OBJECT (grid), "column-spacing", 10, "row-spacing", 10, NULL);
|
|
gtk_box_pack_start (content_area, GTK_WIDGET (grid), TRUE, TRUE, 10);
|
|
gtk_widget_show (GTK_WIDGET (grid));
|
|
|
|
label = gtk_label_new_with_mnemonic (C_ ("Select by Pattern dialog: labels: pattern entry textbox", "_Pattern:"));
|
|
gtk_widget_set_tooltip_text (GTK_WIDGET (label), C_ ("Select by Pattern dialog: tooltips on label for pattern entry textbox", "Files whose name matches the wildcard pattern you enter will be selected in the main window."));
|
|
gtk_grid_attach (grid, label, 0, row, 1, 1);
|
|
gtk_widget_show (label);
|
|
|
|
entry = gtk_entry_new ();
|
|
gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE);
|
|
gtk_entry_set_width_chars (GTK_ENTRY (entry), 25);
|
|
gtk_grid_attach_next_to (grid, entry, label, GTK_POS_RIGHT, 1, 1);
|
|
gtk_widget_set_hexpand (entry, TRUE);
|
|
gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry);
|
|
gtk_widget_show (entry);
|
|
|
|
info_image = gtk_image_new_from_icon_name ("dialog-information", GTK_ICON_SIZE_MENU);
|
|
/* TRANSLATORS: the * and ? characters are the ASCII wildcard special symbols, and they must not be localized. */
|
|
gtk_widget_set_tooltip_text (GTK_WIDGET (info_image), C_ ("Select by Pattern dialog: tooltips: pattern entry text box", "? matches exactly one character,\n* matches any number of characters, including zero.\n\nFor example: *.txt, file??.png, pict\n\nWithout any * or ? wildcards, the pattern will match anywhere in a name. With wildcards, the pattern must match at both the start and the end of a name."));
|
|
gtk_grid_attach_next_to (grid, info_image, entry, GTK_POS_RIGHT, 1, 1);
|
|
gtk_widget_show (info_image);
|
|
|
|
case_sensitive_button = gtk_check_button_new_with_mnemonic (C_ ("Select by Pattern dialog: labels: case sensitive checkbox", "C_ase sensitive"));
|
|
gtk_widget_set_tooltip_text (GTK_WIDGET (case_sensitive_button), C_ ("Select by Pattern dialog: tooltips: case sensitive checkbox", "If enabled, letter case must match the pattern.\nExamp* would match Example.txt and not example.txt"));
|
|
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (case_sensitive_button), FALSE);
|
|
gtk_grid_attach_next_to (grid, case_sensitive_button, entry, GTK_POS_BOTTOM, 1, 1);
|
|
gtk_widget_show (case_sensitive_button);
|
|
|
|
match_diacritics_button = gtk_check_button_new_with_mnemonic (C_ ("Select by Pattern dialog: labels: match diacritics checkbox", "_Match diacritics"));
|
|
gtk_widget_set_tooltip_text (GTK_WIDGET (match_diacritics_button), C_ ("Select by Pattern dialog: tooltips: match diacritics checkbox", "If enabled, require accents to match the pattern.\nRés* would match Résumé.txt and not Resume.txt"));
|
|
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (match_diacritics_button), FALSE);
|
|
gtk_grid_attach_next_to (grid, match_diacritics_button, case_sensitive_button, GTK_POS_BOTTOM, 1, 1);
|
|
gtk_widget_show (match_diacritics_button);
|
|
|
|
response = gtk_dialog_run (GTK_DIALOG (dialog));
|
|
if (response == GTK_RESPONSE_OK)
|
|
{
|
|
/* get entered pattern */
|
|
pattern = gtk_entry_get_text (GTK_ENTRY (entry));
|
|
if (pattern != NULL
|
|
&& strchr (pattern, '*') == NULL
|
|
&& strchr (pattern, '?') == NULL)
|
|
{
|
|
/* make file matching pattern */
|
|
pattern_extended = g_strdup_printf ("*%s*", pattern);
|
|
pattern = pattern_extended;
|
|
}
|
|
|
|
/* get case sensitivity option */
|
|
case_sensitive = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (case_sensitive_button));
|
|
|
|
/* get ignore diacritics option */
|
|
match_diacritics = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (match_diacritics_button));
|
|
|
|
/* select all files that match pattern */
|
|
paths = thunar_standard_view_model_get_paths_for_pattern (standard_view->model, pattern, case_sensitive, match_diacritics);
|
|
THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->unselect_all (standard_view);
|
|
|
|
/* set the cursor and scroll to the first selected item */
|
|
if (paths != NULL)
|
|
THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->set_cursor (standard_view, g_list_last (paths)->data, FALSE);
|
|
|
|
for (lp = paths; lp != NULL; lp = lp->next)
|
|
{
|
|
THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->select_path (standard_view, lp->data);
|
|
gtk_tree_path_free (lp->data);
|
|
}
|
|
g_list_free (paths);
|
|
g_free (pattern_extended);
|
|
}
|
|
|
|
gtk_widget_destroy (dialog);
|
|
|
|
/* required in case of shortcut activation, in order to signal that the accel key got handled */
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_standard_view_selection_invert (ThunarView *view)
|
|
{
|
|
ThunarStandardView *standard_view = THUNAR_STANDARD_VIEW (view);
|
|
|
|
_thunar_return_val_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view), FALSE);
|
|
|
|
/* grab the focus to the view */
|
|
gtk_widget_grab_focus (GTK_WIDGET (standard_view));
|
|
|
|
/* invert all files in the real view */
|
|
(*THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->selection_invert) (standard_view);
|
|
|
|
/* required in case of shortcut activation, in order to signal that the accel key got handled */
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_standard_view_unselect_all_files (ThunarView *view)
|
|
{
|
|
ThunarStandardView *standard_view = THUNAR_STANDARD_VIEW (view);
|
|
|
|
_thunar_return_val_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view), FALSE);
|
|
|
|
/* grab the focus to the view */
|
|
gtk_widget_grab_focus (GTK_WIDGET (standard_view));
|
|
|
|
/* unselect all files in the real view */
|
|
(*THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->unselect_all) (standard_view);
|
|
|
|
/* required in case of shortcut activation, in order to signal that the accel key got handled */
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
static GClosure *
|
|
thunar_standard_view_new_files_closure (ThunarStandardView *standard_view,
|
|
GtkWidget *source_view)
|
|
{
|
|
_thunar_return_val_if_fail (source_view == NULL || THUNAR_IS_VIEW (source_view), NULL);
|
|
|
|
/* drop any previous "new-files" closure */
|
|
if (G_UNLIKELY (standard_view->priv->new_files_closure != NULL))
|
|
{
|
|
g_closure_invalidate (standard_view->priv->new_files_closure);
|
|
g_closure_unref (standard_view->priv->new_files_closure);
|
|
}
|
|
|
|
/* set the remove view data we possibly need to reload */
|
|
g_object_set_data (G_OBJECT (standard_view), I_ ("source-view"), source_view);
|
|
|
|
/* allocate a new "new-files" closure */
|
|
standard_view->priv->new_files_closure = g_cclosure_new_swap (G_CALLBACK (thunar_standard_view_new_files), standard_view, NULL);
|
|
g_closure_ref (standard_view->priv->new_files_closure);
|
|
g_closure_sink (standard_view->priv->new_files_closure);
|
|
|
|
/* and return our new closure */
|
|
return standard_view->priv->new_files_closure;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_new_files (ThunarStandardView *standard_view,
|
|
GList *path_list)
|
|
{
|
|
ThunarFile *file;
|
|
GList *file_list = NULL;
|
|
GList *lp;
|
|
GtkWidget *source_view;
|
|
GFile *parent_file;
|
|
gboolean belongs_here;
|
|
|
|
_thunar_return_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view));
|
|
|
|
/* release the previous "new-files" paths (if any) */
|
|
if (G_UNLIKELY (standard_view->priv->new_files_path_list != NULL))
|
|
{
|
|
thunar_g_list_free_full (standard_view->priv->new_files_path_list);
|
|
standard_view->priv->new_files_path_list = NULL;
|
|
}
|
|
|
|
/* check if the folder is currently being loaded */
|
|
if (G_UNLIKELY (standard_view->loading))
|
|
{
|
|
/* schedule the "new-files" paths for later processing */
|
|
standard_view->priv->new_files_path_list = thunar_g_list_copy_deep (path_list);
|
|
}
|
|
else if (G_LIKELY (path_list != NULL))
|
|
{
|
|
/* to check if we should reload */
|
|
parent_file = thunar_file_get_file (standard_view->priv->current_directory);
|
|
belongs_here = FALSE;
|
|
|
|
/* determine the files for the paths */
|
|
for (lp = path_list; lp != NULL; lp = lp->next)
|
|
{
|
|
file = thunar_file_cache_lookup (lp->data);
|
|
if (G_LIKELY (file != NULL))
|
|
file_list = g_list_prepend (file_list, file);
|
|
else if (!belongs_here && g_file_has_parent (lp->data, parent_file))
|
|
belongs_here = TRUE;
|
|
}
|
|
|
|
/* check if we have any new files here */
|
|
if (G_LIKELY (file_list != NULL))
|
|
{
|
|
/* select the files */
|
|
thunar_component_set_selected_files (THUNAR_COMPONENT (standard_view), file_list);
|
|
|
|
/* release the file list */
|
|
g_list_free_full (file_list, g_object_unref);
|
|
|
|
/* grab the focus to the view widget */
|
|
gtk_widget_grab_focus (gtk_bin_get_child (GTK_BIN (standard_view)));
|
|
}
|
|
else if (belongs_here)
|
|
{
|
|
/* thunar files are not created yet, try again later because we know
|
|
* some of them belong in this directory, so eventually they
|
|
* will get a ThunarFile */
|
|
standard_view->priv->new_files_path_list = thunar_g_list_copy_deep (path_list);
|
|
}
|
|
}
|
|
|
|
/* when performing dnd between 2 views, we force a reload on the source as well */
|
|
source_view = g_object_get_data (G_OBJECT (standard_view), I_ ("source-view"));
|
|
if (THUNAR_IS_VIEW (source_view))
|
|
thunar_view_reload (THUNAR_VIEW (source_view), FALSE);
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_standard_view_button_release_event (GtkWidget *view,
|
|
GdkEventButton *event,
|
|
ThunarStandardView *standard_view)
|
|
{
|
|
_thunar_return_val_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view), FALSE);
|
|
_thunar_return_val_if_fail (standard_view->priv->drag_timer_id != 0, FALSE);
|
|
|
|
/* cancel the pending drag timer */
|
|
g_source_remove (standard_view->priv->drag_timer_id);
|
|
|
|
/* fire up the context menu */
|
|
thunar_standard_view_context_menu (standard_view);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_standard_view_motion_notify_event (GtkWidget *view,
|
|
GdkEventMotion *event,
|
|
ThunarStandardView *standard_view)
|
|
{
|
|
GtkTargetList *target_list;
|
|
|
|
_thunar_return_val_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view), FALSE);
|
|
_thunar_return_val_if_fail (standard_view->priv->drag_timer_id != 0, FALSE);
|
|
|
|
/* check if we passed the DnD threshold */
|
|
if (gtk_drag_check_threshold (view, standard_view->priv->drag_x, standard_view->priv->drag_y, event->x, event->y))
|
|
{
|
|
/* cancel the drag timer, as we won't popup the menu anymore */
|
|
g_source_remove (standard_view->priv->drag_timer_id);
|
|
|
|
/* allocate the drag context */
|
|
target_list = gtk_target_list_new (drag_targets, G_N_ELEMENTS (drag_targets));
|
|
gtk_drag_begin_with_coordinates (view, target_list,
|
|
GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK | GDK_ACTION_ASK,
|
|
3, (GdkEvent *) event, -1, -1);
|
|
gtk_target_list_unref (target_list);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_standard_view_scroll_event (GtkWidget *view,
|
|
GdkEventScroll *event,
|
|
ThunarStandardView *standard_view)
|
|
{
|
|
GdkScrollDirection scrolling_direction;
|
|
gboolean misc_horizontal_wheel_navigates;
|
|
gboolean misc_ctrl_scroll_wheel_to_zoom;
|
|
|
|
_thunar_return_val_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view), FALSE);
|
|
|
|
if (event->direction != GDK_SCROLL_SMOOTH)
|
|
scrolling_direction = event->direction;
|
|
else if (event->delta_y < 0)
|
|
scrolling_direction = GDK_SCROLL_UP;
|
|
else if (event->delta_y > 0)
|
|
scrolling_direction = GDK_SCROLL_DOWN;
|
|
else if (event->delta_x < 0)
|
|
scrolling_direction = GDK_SCROLL_LEFT;
|
|
else if (event->delta_x > 0)
|
|
scrolling_direction = GDK_SCROLL_RIGHT;
|
|
else
|
|
{
|
|
g_debug ("GDK_SCROLL_SMOOTH scrolling event with no delta happened");
|
|
return FALSE;
|
|
}
|
|
|
|
if (G_UNLIKELY (scrolling_direction == GDK_SCROLL_LEFT || scrolling_direction == GDK_SCROLL_RIGHT))
|
|
{
|
|
/* check if we should use the horizontal mouse wheel for navigation */
|
|
g_object_get (G_OBJECT (standard_view->preferences), "misc-horizontal-wheel-navigates", &misc_horizontal_wheel_navigates, NULL);
|
|
if (G_UNLIKELY (misc_horizontal_wheel_navigates))
|
|
{
|
|
if (scrolling_direction == GDK_SCROLL_LEFT)
|
|
thunar_history_action_back (standard_view->priv->history);
|
|
else
|
|
thunar_history_action_forward (standard_view->priv->history);
|
|
}
|
|
}
|
|
|
|
/* zoom-in/zoom-out on control+mouse wheel */
|
|
if ((event->state & GDK_CONTROL_MASK) != 0 && (scrolling_direction == GDK_SCROLL_UP || scrolling_direction == GDK_SCROLL_DOWN))
|
|
{
|
|
/* check if we should use ctrl + scroll to zoom */
|
|
g_object_get (G_OBJECT (standard_view->preferences), "misc-ctrl-scroll-wheel-to-zoom", &misc_ctrl_scroll_wheel_to_zoom, NULL);
|
|
if (misc_ctrl_scroll_wheel_to_zoom)
|
|
{
|
|
thunar_view_set_zoom_level (THUNAR_VIEW (standard_view),
|
|
(scrolling_direction == GDK_SCROLL_UP)
|
|
? MIN (standard_view->priv->zoom_level + 1, THUNAR_ZOOM_N_LEVELS - 1)
|
|
: MAX (standard_view->priv->zoom_level, 1) - 1);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
/* next please... */
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_standard_view_key_press_event (GtkWidget *view,
|
|
GdkEventKey *event,
|
|
ThunarStandardView *standard_view)
|
|
{
|
|
_thunar_return_val_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view), FALSE);
|
|
|
|
/* need to catch "/" and "~" first, as the views would otherwise start interactive search */
|
|
if ((event->keyval == GDK_KEY_slash || event->keyval == GDK_KEY_asciitilde || event->keyval == GDK_KEY_dead_tilde) && !(event->state & (~GDK_SHIFT_MASK & gtk_accelerator_get_default_mod_mask ())))
|
|
{
|
|
/* popup the location selector (in whatever way) */
|
|
if (event->keyval == GDK_KEY_dead_tilde)
|
|
g_signal_emit (G_OBJECT (standard_view), standard_view_signals[START_OPEN_LOCATION], 0, "~");
|
|
else
|
|
g_signal_emit (G_OBJECT (standard_view), standard_view_signals[START_OPEN_LOCATION], 0, event->string);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
if (standard_view->accel_group != NULL && xfce_gtk_handle_tab_accels (event, standard_view->accel_group, standard_view, thunar_standard_view_action_entries, THUNAR_STANDARD_VIEW_N_ACTIONS) == TRUE)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_standard_view_drag_drop (GtkWidget *view,
|
|
GdkDragContext *context,
|
|
gint x,
|
|
gint y,
|
|
guint timestamp,
|
|
ThunarStandardView *standard_view)
|
|
{
|
|
ThunarFile *file = NULL;
|
|
GdkAtom target;
|
|
guchar *prop_text;
|
|
GFile *path;
|
|
gchar *uri = NULL;
|
|
gint prop_len;
|
|
|
|
target = gtk_drag_dest_find_target (view, context, NULL);
|
|
if (G_UNLIKELY (target == GDK_NONE))
|
|
{
|
|
/* we cannot handle the drag data */
|
|
return FALSE;
|
|
}
|
|
else if (G_UNLIKELY (target == gdk_atom_intern_static_string ("XdndDirectSave0")))
|
|
{
|
|
/* determine the file for the drop position */
|
|
file = thunar_standard_view_get_drop_file (standard_view, x, y, NULL);
|
|
if (G_LIKELY (file != NULL))
|
|
{
|
|
/* determine the file name from the DnD source window */
|
|
if (gdk_property_get (gdk_drag_context_get_source_window (context),
|
|
gdk_atom_intern_static_string ("XdndDirectSave0"),
|
|
gdk_atom_intern_static_string ("text/plain"), 0, 1024, FALSE, NULL, NULL,
|
|
&prop_len, &prop_text)
|
|
&& prop_text != NULL)
|
|
{
|
|
/* zero-terminate the string */
|
|
prop_text = g_realloc (prop_text, prop_len + 1);
|
|
prop_text[prop_len] = '\0';
|
|
|
|
/* verify that the file name provided by the source is valid */
|
|
if (G_LIKELY (*prop_text != '\0' && strchr ((const gchar *) prop_text, G_DIR_SEPARATOR) == NULL))
|
|
{
|
|
/* allocate the relative path for the target */
|
|
path = g_file_resolve_relative_path (thunar_file_get_file (file),
|
|
(const gchar *) prop_text);
|
|
|
|
/* determine the new URI */
|
|
uri = g_file_get_uri (path);
|
|
|
|
/* setup the property */
|
|
gdk_property_change (gdk_drag_context_get_source_window (context),
|
|
gdk_atom_intern_static_string ("XdndDirectSave0"),
|
|
gdk_atom_intern_static_string ("text/plain"), 8,
|
|
GDK_PROP_MODE_REPLACE, (const guchar *) uri,
|
|
strlen (uri));
|
|
|
|
/* cleanup */
|
|
g_object_unref (path);
|
|
g_free (uri);
|
|
}
|
|
else
|
|
{
|
|
/* tell the user that the file name provided by the X Direct Save source is invalid */
|
|
thunar_dialogs_show_error (GTK_WIDGET (standard_view), NULL, _("Invalid filename provided by XDS drag site"));
|
|
}
|
|
|
|
/* cleanup */
|
|
g_free (prop_text);
|
|
}
|
|
|
|
/* release the file reference */
|
|
g_object_unref (G_OBJECT (file));
|
|
}
|
|
|
|
/* if uri == NULL, we didn't set the property */
|
|
if (G_UNLIKELY (uri == NULL))
|
|
return FALSE;
|
|
}
|
|
|
|
/* set state so the drag-data-received knows that
|
|
* this is really a drop this time.
|
|
*/
|
|
standard_view->priv->drop_occurred = TRUE;
|
|
|
|
/* request the drag data from the source (initiates
|
|
* saving in case of XdndDirectSave).
|
|
*/
|
|
gtk_drag_get_data (view, context, target, timestamp);
|
|
|
|
/* we'll call gtk_drag_finish() later */
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
tsv_reload_directory (GPid pid,
|
|
gint status,
|
|
gpointer user_data)
|
|
{
|
|
GFileMonitor *monitor;
|
|
GFile *file;
|
|
|
|
/* determine the path for the directory */
|
|
file = g_file_new_for_uri (user_data);
|
|
|
|
/* schedule a changed event for the directory */
|
|
monitor = g_file_monitor (file, G_FILE_MONITOR_NONE, NULL, NULL);
|
|
if (monitor != NULL)
|
|
{
|
|
g_file_monitor_emit_event (monitor, file, NULL, G_FILE_MONITOR_EVENT_CHANGED);
|
|
g_object_unref (monitor);
|
|
}
|
|
|
|
g_object_unref (file);
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_standard_view_receive_xdnd_direct_save (GdkDragContext *context,
|
|
gint x,
|
|
gint y,
|
|
GtkSelectionData *selection_data,
|
|
ThunarStandardView *standard_view)
|
|
{
|
|
gint format;
|
|
gint length;
|
|
ThunarFolder *folder;
|
|
ThunarFile *file = NULL;
|
|
|
|
format = gtk_selection_data_get_format (selection_data);
|
|
length = gtk_selection_data_get_length (selection_data);
|
|
|
|
/* we don't handle XdndDirectSave stage (3), result "F" yet */
|
|
if (G_UNLIKELY (format == 8 && length == 1 && gtk_selection_data_get_data (selection_data)[0] == 'F'))
|
|
{
|
|
/* indicate that we don't provide "F" fallback */
|
|
gdk_property_change (gdk_drag_context_get_source_window (context),
|
|
gdk_atom_intern_static_string ("XdndDirectSave0"),
|
|
gdk_atom_intern_static_string ("text/plain"), 8,
|
|
GDK_PROP_MODE_REPLACE, (const guchar *) "", 0);
|
|
}
|
|
else if (G_LIKELY (format == 8 && length == 1 && gtk_selection_data_get_data (selection_data)[0] == 'S'))
|
|
{
|
|
/* XDS was successfull, so determine the file for the drop position */
|
|
file = thunar_standard_view_get_drop_file (standard_view, x, y, NULL);
|
|
if (G_LIKELY (file != NULL))
|
|
{
|
|
/* verify that we have a directory here */
|
|
if (thunar_file_is_directory (file))
|
|
{
|
|
/* reload the folder corresponding to the file */
|
|
folder = thunar_folder_get_for_file (file);
|
|
thunar_folder_reload (folder, FALSE);
|
|
g_object_unref (G_OBJECT (folder));
|
|
}
|
|
|
|
/* cleanup */
|
|
g_object_unref (G_OBJECT (file));
|
|
}
|
|
}
|
|
|
|
/* in either case, we succeed! */
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_standard_view_receive_netscape_url (GtkWidget *view,
|
|
GdkDragContext *context,
|
|
gint x,
|
|
gint y,
|
|
GtkSelectionData *selection_data,
|
|
ThunarStandardView *standard_view)
|
|
{
|
|
gint format;
|
|
gint length;
|
|
gchar **bits;
|
|
ThunarFile *file = NULL;
|
|
gchar *working_directory;
|
|
gchar *argv[11];
|
|
gint pid;
|
|
gint n = 0;
|
|
GError *error = NULL;
|
|
GtkWidget *toplevel;
|
|
GdkScreen *screen;
|
|
char *display = NULL;
|
|
gboolean succeed = FALSE;
|
|
|
|
format = gtk_selection_data_get_format (selection_data);
|
|
length = gtk_selection_data_get_length (selection_data);
|
|
|
|
/* check if the format is valid and we have any data */
|
|
if (G_UNLIKELY (format != 8 || length <= 0))
|
|
return succeed;
|
|
|
|
/* _NETSCAPE_URL looks like this: "$URL\n$TITLE" */
|
|
bits = g_strsplit ((const gchar *) gtk_selection_data_get_data (selection_data), "\n", -1);
|
|
if (G_UNLIKELY (g_strv_length (bits) != 2))
|
|
{
|
|
g_strfreev (bits);
|
|
return succeed;
|
|
}
|
|
|
|
/* determine the file for the drop position */
|
|
file = thunar_standard_view_get_drop_file (standard_view, x, y, NULL);
|
|
if (G_LIKELY (file != NULL))
|
|
{
|
|
/* determine the absolute path to the target directory */
|
|
working_directory = g_file_get_path (thunar_file_get_file (file));
|
|
if (G_LIKELY (working_directory != NULL))
|
|
{
|
|
/* prepare the basic part of the command */
|
|
argv[n++] = "exo-desktop-item-edit";
|
|
argv[n++] = "--type=Link";
|
|
argv[n++] = "--url";
|
|
argv[n++] = bits[0];
|
|
argv[n++] = "--name";
|
|
argv[n++] = bits[1];
|
|
|
|
/* determine the toplevel window */
|
|
toplevel = gtk_widget_get_toplevel (view);
|
|
if (toplevel != NULL && gtk_widget_is_toplevel (toplevel))
|
|
{
|
|
#if defined(GDK_WINDOWING_X11)
|
|
/* on X11, we can supply the parent window id here */
|
|
argv[n++] = "--xid";
|
|
argv[n++] = g_newa (gchar, 32);
|
|
g_snprintf (argv[n - 1], 32, "%ld", (glong) GDK_WINDOW_XID (gtk_widget_get_window (GTK_WIDGET (toplevel))));
|
|
#endif
|
|
}
|
|
|
|
/* terminate the parameter list */
|
|
argv[n++] = "--create-new";
|
|
argv[n++] = working_directory;
|
|
argv[n++] = NULL;
|
|
|
|
screen = gtk_widget_get_screen (GTK_WIDGET (view));
|
|
|
|
if (screen != NULL)
|
|
display = g_strdup (gdk_display_get_name (gdk_screen_get_display (screen)));
|
|
|
|
/* try to run exo-desktop-item-edit */
|
|
succeed = g_spawn_async (working_directory, argv, NULL,
|
|
G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH,
|
|
thunar_setup_display_cb, display, &pid, &error);
|
|
|
|
if (G_LIKELY (succeed))
|
|
{
|
|
/* reload the directory when the command terminates */
|
|
g_child_watch_add_full (G_PRIORITY_LOW, pid, tsv_reload_directory, working_directory, g_free);
|
|
}
|
|
else
|
|
{
|
|
/* display an error dialog to the user */
|
|
thunar_dialogs_show_error (standard_view, error, _("Failed to create a link for the URL \"%s\""), bits[0]);
|
|
g_free (working_directory);
|
|
g_error_free (error);
|
|
}
|
|
|
|
g_free (display);
|
|
}
|
|
g_object_unref (G_OBJECT (file));
|
|
}
|
|
|
|
g_strfreev (bits);
|
|
|
|
return succeed;
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_standard_view_receive_application_octet_stream (GdkDragContext *context,
|
|
gint x,
|
|
gint y,
|
|
GtkSelectionData *selection_data,
|
|
ThunarStandardView *standard_view)
|
|
{
|
|
ThunarFile *file = NULL;
|
|
gchar *filename;
|
|
gchar *filepath = NULL;
|
|
const gchar *content;
|
|
gint length;
|
|
GFile *dest;
|
|
GFileOutputStream *out;
|
|
|
|
if (gtk_selection_data_get_length (selection_data) <= 0)
|
|
return FALSE;
|
|
|
|
/* determine filename */
|
|
if (gdk_property_get (gdk_drag_context_get_source_window (context),
|
|
gdk_atom_intern ("XdndDirectSave0", FALSE),
|
|
gdk_atom_intern ("text/plain", FALSE), 0, 1024,
|
|
FALSE, NULL, NULL, &length,
|
|
(guchar **) &filename)
|
|
&& length > 0)
|
|
{
|
|
filename = g_realloc (filename, length + 1);
|
|
filename[length] = '\0';
|
|
}
|
|
else
|
|
filename = g_strdup (_("Untitled document"));
|
|
|
|
/* determine filepath */
|
|
file = thunar_standard_view_get_drop_file (standard_view, x, y, NULL);
|
|
if (G_LIKELY (file != NULL))
|
|
{
|
|
if (thunar_file_is_directory (file))
|
|
{
|
|
gchar *filename_temp;
|
|
gchar *folder_path;
|
|
|
|
folder_path = g_file_get_path (thunar_file_get_file (file));
|
|
filename_temp = thunar_util_next_new_file_name (file, filename, THUNAR_NEXT_FILE_NAME_MODE_COPY, FALSE);
|
|
g_free (filename);
|
|
filename = filename_temp;
|
|
filepath = g_build_filename (folder_path, filename, NULL);
|
|
|
|
g_free (folder_path);
|
|
}
|
|
|
|
g_object_unref (G_OBJECT (file));
|
|
}
|
|
|
|
if (G_LIKELY (filepath != NULL))
|
|
{
|
|
dest = g_file_new_for_path (filepath);
|
|
out = g_file_create (dest, G_FILE_CREATE_NONE, NULL, NULL);
|
|
|
|
if (out)
|
|
{
|
|
content = (const gchar *) gtk_selection_data_get_data (selection_data);
|
|
length = gtk_selection_data_get_length (selection_data);
|
|
|
|
if (g_output_stream_write_all (G_OUTPUT_STREAM (out), content, length, NULL, NULL, NULL))
|
|
g_output_stream_close (G_OUTPUT_STREAM (out), NULL, NULL);
|
|
|
|
g_object_unref (out);
|
|
}
|
|
|
|
g_object_unref (dest);
|
|
}
|
|
|
|
g_free (filename);
|
|
g_free (filepath);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_standard_view_receive_text_uri_list (GdkDragContext *context,
|
|
gint x,
|
|
gint y,
|
|
GtkSelectionData *selection_data,
|
|
guint timestamp,
|
|
ThunarStandardView *standard_view)
|
|
{
|
|
GdkDragAction actions;
|
|
GdkDragAction action;
|
|
ThunarFile *file = NULL;
|
|
GtkWidget *window;
|
|
GtkWidget *source_widget;
|
|
GtkWidget *source_view = NULL;
|
|
gboolean succeed = FALSE;
|
|
|
|
/* determine the drop position */
|
|
actions = thunar_standard_view_get_dest_actions (standard_view, context, x, y, timestamp, &file);
|
|
if (G_LIKELY ((actions & (GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK)) != 0))
|
|
{
|
|
/* stop any running drag enter timer */
|
|
if (G_UNLIKELY (standard_view->priv->drag_enter_timer_id != 0))
|
|
g_source_remove (standard_view->priv->drag_enter_timer_id);
|
|
|
|
/* ask the user what to do with the drop data */
|
|
action = (gdk_drag_context_get_selected_action (context) == GDK_ACTION_ASK)
|
|
? thunar_dnd_ask (GTK_WIDGET (standard_view), file, standard_view->priv->drop_file_list, actions)
|
|
: gdk_drag_context_get_selected_action (context);
|
|
|
|
/* perform the requested action */
|
|
if (G_LIKELY (action != 0))
|
|
{
|
|
/* move the focus to the target widget */
|
|
window = gtk_widget_get_toplevel (GTK_WIDGET (standard_view));
|
|
if (GTK_IS_WINDOW (window))
|
|
{
|
|
/* if split view is active, give focus to the pane containing the view */
|
|
thunar_window_focus_view (THUNAR_WINDOW (window), GTK_WIDGET (standard_view));
|
|
}
|
|
|
|
/* look if we can find the drag source widget */
|
|
source_widget = gtk_drag_get_source_widget (context);
|
|
if (source_widget != NULL)
|
|
{
|
|
/* if this is a source view, attach it to the view receiving
|
|
* the data, see thunar_standard_view_new_files */
|
|
source_view = gtk_widget_get_parent (source_widget);
|
|
if (!THUNAR_IS_VIEW (source_view))
|
|
source_view = NULL;
|
|
}
|
|
|
|
succeed = thunar_dnd_perform (GTK_WIDGET (standard_view), file, standard_view->priv->drop_file_list,
|
|
action, thunar_standard_view_new_files_closure (standard_view, source_view));
|
|
}
|
|
}
|
|
|
|
/* release the file reference */
|
|
if (G_LIKELY (file != NULL))
|
|
g_object_unref (G_OBJECT (file));
|
|
|
|
return succeed;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_drag_data_received (GtkWidget *view,
|
|
GdkDragContext *context,
|
|
gint x,
|
|
gint y,
|
|
GtkSelectionData *selection_data,
|
|
guint info,
|
|
guint timestamp,
|
|
ThunarStandardView *standard_view)
|
|
{
|
|
gboolean succeed = FALSE;
|
|
|
|
/* check if we don't already know the drop data */
|
|
if (G_LIKELY (!standard_view->priv->drop_data_ready))
|
|
{
|
|
/* extract the URI list from the selection data (if valid) */
|
|
if (info == TARGET_TEXT_URI_LIST && gtk_selection_data_get_format (selection_data) == 8 && gtk_selection_data_get_length (selection_data) > 0)
|
|
standard_view->priv->drop_file_list = thunar_g_file_list_new_from_string ((gchar *) gtk_selection_data_get_data (selection_data));
|
|
|
|
/* reset the state */
|
|
standard_view->priv->drop_data_ready = TRUE;
|
|
}
|
|
|
|
/* check if the data was dropped */
|
|
if (G_UNLIKELY (standard_view->priv->drop_occurred))
|
|
{
|
|
/* reset the state */
|
|
standard_view->priv->drop_occurred = FALSE;
|
|
|
|
/* check if we're doing XdndDirectSave */
|
|
if (G_UNLIKELY (info == TARGET_XDND_DIRECT_SAVE0))
|
|
succeed = thunar_standard_view_receive_xdnd_direct_save (context, x, y, selection_data, standard_view);
|
|
else if (G_UNLIKELY (info == TARGET_NETSCAPE_URL))
|
|
succeed = thunar_standard_view_receive_netscape_url (view, context, x, y, selection_data, standard_view);
|
|
else if (G_UNLIKELY (info == TARGET_APPLICATION_OCTET_STREAM))
|
|
succeed = thunar_standard_view_receive_application_octet_stream (context, x, y, selection_data, standard_view);
|
|
else if (G_LIKELY (info == TARGET_TEXT_URI_LIST))
|
|
succeed = thunar_standard_view_receive_text_uri_list (context, x, y, selection_data, timestamp, standard_view);
|
|
|
|
/* tell the peer that we handled the drop */
|
|
gtk_drag_finish (context, succeed, FALSE, timestamp);
|
|
|
|
/* disable the highlighting and release the drag data */
|
|
thunar_standard_view_drag_leave (view, context, timestamp, standard_view);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_drag_leave (GtkWidget *widget,
|
|
GdkDragContext *context,
|
|
guint timestamp,
|
|
ThunarStandardView *standard_view)
|
|
{
|
|
/* reset the drop-file for the icon renderer */
|
|
g_object_set (G_OBJECT (standard_view->icon_renderer), "drop-file", NULL, NULL);
|
|
|
|
/* stop any running drag autoscroll timer */
|
|
if (G_UNLIKELY (standard_view->priv->drag_scroll_timer_id != 0))
|
|
g_source_remove (standard_view->priv->drag_scroll_timer_id);
|
|
|
|
/* stop any running drag enter timer */
|
|
if (G_UNLIKELY (standard_view->priv->drag_enter_timer_id != 0))
|
|
g_source_remove (standard_view->priv->drag_enter_timer_id);
|
|
|
|
/* disable the drop highlighting around the view */
|
|
if (G_LIKELY (standard_view->priv->drop_highlight))
|
|
{
|
|
standard_view->priv->drop_highlight = FALSE;
|
|
gtk_widget_queue_draw (GTK_WIDGET (standard_view));
|
|
}
|
|
|
|
/* reset the "drop data ready" status and free the URI list */
|
|
if (G_LIKELY (standard_view->priv->drop_data_ready))
|
|
{
|
|
thunar_g_list_free_full (standard_view->priv->drop_file_list);
|
|
standard_view->priv->drop_file_list = NULL;
|
|
standard_view->priv->drop_data_ready = FALSE;
|
|
}
|
|
|
|
/* disable the highlighting of the items in the view */
|
|
(*THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->highlight_path) (standard_view, NULL);
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_standard_view_drag_motion (GtkWidget *view,
|
|
GdkDragContext *context,
|
|
gint x,
|
|
gint y,
|
|
guint timestamp,
|
|
ThunarStandardView *standard_view)
|
|
{
|
|
GdkDragAction action = 0;
|
|
GtkTreePath *path;
|
|
ThunarFile *file = NULL;
|
|
GdkAtom target;
|
|
|
|
/* request the drop data on-demand (if we don't have it already) */
|
|
if (G_UNLIKELY (!standard_view->priv->drop_data_ready))
|
|
{
|
|
/* check if we can handle that drag data (yet?) */
|
|
target = gtk_drag_dest_find_target (view, context, NULL);
|
|
|
|
if (target == gdk_atom_intern_static_string ("XdndDirectSave0") || target == gdk_atom_intern_static_string ("_NETSCAPE_URL") || target == gdk_atom_intern_static_string ("application/octet-stream"))
|
|
{
|
|
/* determine the file for the given coordinates */
|
|
file = thunar_standard_view_get_drop_file (standard_view, x, y, &path);
|
|
|
|
/* check if we can save here */
|
|
if (G_LIKELY (file != NULL
|
|
&& thunar_file_is_local (file)
|
|
&& thunar_file_is_directory (file)
|
|
&& thunar_file_is_writable (file)))
|
|
{
|
|
action = gdk_drag_context_get_suggested_action (context);
|
|
}
|
|
|
|
/* reset path if we cannot drop */
|
|
if (G_UNLIKELY (action == 0 && path != NULL))
|
|
{
|
|
gtk_tree_path_free (path);
|
|
path = NULL;
|
|
}
|
|
|
|
/* do the view highlighting */
|
|
if (standard_view->priv->drop_highlight != (path == NULL && action != 0))
|
|
{
|
|
standard_view->priv->drop_highlight = (path == NULL && action != 0);
|
|
gtk_widget_queue_draw (GTK_WIDGET (standard_view));
|
|
}
|
|
|
|
/* setup drop-file for the icon renderer to highlight the target */
|
|
g_object_set (G_OBJECT (standard_view->icon_renderer), "drop-file", (action != 0) ? file : NULL, NULL);
|
|
|
|
/* do the item highlighting */
|
|
(*THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->highlight_path) (standard_view, path);
|
|
|
|
/* cleanup */
|
|
if (G_LIKELY (file != NULL))
|
|
g_object_unref (G_OBJECT (file));
|
|
if (G_LIKELY (path != NULL))
|
|
gtk_tree_path_free (path);
|
|
}
|
|
else
|
|
{
|
|
/* request the drag data from the source */
|
|
if (target != GDK_NONE)
|
|
gtk_drag_get_data (view, context, target, timestamp);
|
|
}
|
|
|
|
/* tell Gdk whether we can drop here */
|
|
gdk_drag_status (context, action, timestamp);
|
|
}
|
|
else
|
|
{
|
|
/* check whether we can drop at (x,y) */
|
|
thunar_standard_view_get_dest_actions (standard_view, context, x, y, timestamp, NULL);
|
|
}
|
|
|
|
/* start the drag autoscroll timer if not already running */
|
|
if (G_UNLIKELY (standard_view->priv->drag_scroll_timer_id == 0))
|
|
{
|
|
/* schedule the drag autoscroll timer */
|
|
standard_view->priv->drag_scroll_timer_id = g_timeout_add_full (G_PRIORITY_LOW, 50, thunar_standard_view_drag_scroll_timer,
|
|
standard_view, thunar_standard_view_drag_scroll_timer_destroy);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_drag_begin (GtkWidget *view,
|
|
GdkDragContext *context,
|
|
ThunarStandardView *standard_view)
|
|
{
|
|
ThunarFile *file;
|
|
GdkPixbuf *icon;
|
|
cairo_surface_t *surface;
|
|
gint size;
|
|
gint scale_factor;
|
|
|
|
/* release the drag path list (just in case the drag-end wasn't fired before) */
|
|
thunar_g_list_free_full (standard_view->priv->drag_g_file_list);
|
|
|
|
/* query the list of selected URIs */
|
|
standard_view->priv->drag_g_file_list = thunar_file_list_to_thunar_g_file_list (standard_view->priv->selected_files);
|
|
if (G_LIKELY (standard_view->priv->drag_g_file_list != NULL))
|
|
{
|
|
/* determine the first selected file */
|
|
file = thunar_file_get (standard_view->priv->drag_g_file_list->data, NULL);
|
|
if (G_LIKELY (file != NULL))
|
|
{
|
|
/* generate an icon based on that file */
|
|
g_object_get (G_OBJECT (standard_view->icon_renderer), "size", &size, NULL);
|
|
scale_factor = gtk_widget_get_scale_factor (view);
|
|
icon = thunar_icon_factory_load_file_icon (standard_view->icon_factory, file,
|
|
THUNAR_FILE_ICON_STATE_DEFAULT, size,
|
|
scale_factor, FALSE, NULL);
|
|
if (G_LIKELY (icon != NULL))
|
|
{
|
|
surface = gdk_cairo_surface_create_from_pixbuf (icon, scale_factor, gtk_widget_get_window (view));
|
|
g_object_unref (G_OBJECT (icon));
|
|
gtk_drag_set_icon_surface (context, surface);
|
|
cairo_surface_destroy (surface);
|
|
}
|
|
|
|
/* release the file */
|
|
g_object_unref (G_OBJECT (file));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_drag_data_get (GtkWidget *view,
|
|
GdkDragContext *context,
|
|
GtkSelectionData *selection_data,
|
|
guint info,
|
|
guint timestamp,
|
|
ThunarStandardView *standard_view)
|
|
{
|
|
gchar **uris;
|
|
|
|
/* set the URI list for the drag selection */
|
|
if (standard_view->priv->drag_g_file_list != NULL)
|
|
{
|
|
uris = thunar_g_file_list_to_stringv (standard_view->priv->drag_g_file_list);
|
|
gtk_selection_data_set_uris (selection_data, uris);
|
|
g_strfreev (uris);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_drag_data_delete (GtkWidget *view,
|
|
GdkDragContext *context,
|
|
ThunarStandardView *standard_view)
|
|
{
|
|
/* make sure the default handler of ExoIconView/GtkTreeView is never run */
|
|
g_signal_stop_emission_by_name (G_OBJECT (view), "drag-data-delete");
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_drag_end (GtkWidget *view,
|
|
GdkDragContext *context,
|
|
ThunarStandardView *standard_view)
|
|
{
|
|
/* stop any running drag autoscroll timer */
|
|
if (G_UNLIKELY (standard_view->priv->drag_scroll_timer_id != 0))
|
|
g_source_remove (standard_view->priv->drag_scroll_timer_id);
|
|
|
|
/* release the list of dragged URIs */
|
|
thunar_g_list_free_full (standard_view->priv->drag_g_file_list);
|
|
standard_view->priv->drag_g_file_list = NULL;
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_standard_view_restore_selection_idle (gpointer user_data)
|
|
{
|
|
ThunarStandardView *standard_view = user_data;
|
|
GtkAdjustment *hadjustment;
|
|
GtkAdjustment *vadjustment;
|
|
gdouble h, v, hl, hu, vl, vu;
|
|
|
|
_thunar_return_val_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view), FALSE);
|
|
|
|
/* save the current scroll position and limits */
|
|
hadjustment = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (standard_view));
|
|
vadjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (standard_view));
|
|
g_object_get (G_OBJECT (hadjustment), "value", &h, "lower", &hl, "upper", &hu, NULL);
|
|
g_object_get (G_OBJECT (vadjustment), "value", &v, "lower", &vl, "upper", &vu, NULL);
|
|
|
|
/* keep the current scroll position by setting the limits to the current value */
|
|
g_object_set (G_OBJECT (hadjustment), "lower", h, "upper", h, NULL);
|
|
g_object_set (G_OBJECT (vadjustment), "lower", v, "upper", v, NULL);
|
|
|
|
/* restore the selected files */
|
|
thunar_standard_view_update_selected_files (standard_view, standard_view->priv->selected_files);
|
|
|
|
standard_view->priv->restore_selection_idle_id = 0;
|
|
|
|
/* unfreeze the scroll position */
|
|
g_object_set (G_OBJECT (hadjustment), "value", h, "lower", hl, "upper", hu, NULL);
|
|
g_object_set (G_OBJECT (vadjustment), "value", v, "lower", vl, "upper", vu, NULL);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_rows_reordered (ThunarStandardViewModel *model,
|
|
GtkTreePath *path,
|
|
GtkTreeIter *iter,
|
|
gpointer new_order,
|
|
ThunarStandardView *standard_view)
|
|
{
|
|
_thunar_return_if_fail (THUNAR_IS_STANDARD_VIEW_MODEL (model));
|
|
_thunar_return_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view));
|
|
_thunar_return_if_fail (standard_view->model == model);
|
|
|
|
/* ignore while searching */
|
|
if (standard_view->priv->active_search == TRUE)
|
|
return;
|
|
|
|
/* the order of the paths might have changed, but the selection
|
|
* stayed the same, so restore the selection of the proper files
|
|
* after letting row changes accumulate a bit */
|
|
if (standard_view->priv->restore_selection_idle_id == 0)
|
|
standard_view->priv->restore_selection_idle_id =
|
|
g_timeout_add (50, thunar_standard_view_restore_selection_idle, standard_view);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_select_after_row_deleted (ThunarStandardViewModel *model,
|
|
GtkTreePath *path,
|
|
ThunarStandardView *standard_view)
|
|
{
|
|
_thunar_return_if_fail (path != NULL);
|
|
_thunar_return_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view));
|
|
|
|
/* ignore while searching */
|
|
if (standard_view->priv->active_search == TRUE)
|
|
return;
|
|
|
|
/* ignore if we have selected files */
|
|
if (standard_view->priv->selected_files != NULL)
|
|
return;
|
|
|
|
/* select the path */
|
|
(*THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->select_path) (standard_view, path);
|
|
|
|
/* place the cursor on the first selected path (must be first for GtkTreeView) */
|
|
(*THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->set_cursor) (standard_view, path, FALSE);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_error (ThunarStandardViewModel *model,
|
|
const GError *error,
|
|
ThunarStandardView *standard_view)
|
|
{
|
|
ThunarFile *file;
|
|
|
|
_thunar_return_if_fail (THUNAR_IS_STANDARD_VIEW_MODEL (model));
|
|
_thunar_return_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view));
|
|
_thunar_return_if_fail (standard_view->model == model);
|
|
|
|
/* determine the ThunarFile for the current directory */
|
|
file = thunar_navigator_get_current_directory (THUNAR_NAVIGATOR (standard_view));
|
|
if (G_UNLIKELY (file == NULL))
|
|
return;
|
|
|
|
/* inform the user about the problem */
|
|
thunar_dialogs_show_error (GTK_WIDGET (standard_view), error,
|
|
_("Failed to open directory \"%s\""),
|
|
thunar_file_get_display_name (file));
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_search_done (ThunarStandardViewModel *model,
|
|
ThunarStandardView *standard_view)
|
|
{
|
|
_thunar_return_if_fail (THUNAR_IS_STANDARD_VIEW_MODEL (model));
|
|
_thunar_return_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view));
|
|
_thunar_return_if_fail (standard_view->model == model);
|
|
|
|
standard_view->priv->active_search = FALSE;
|
|
|
|
/* notify listeners */
|
|
g_object_notify_by_pspec (G_OBJECT (standard_view), standard_view_props[PROP_SEARCHING]);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_sort_column_changed (GtkTreeSortable *tree_sortable,
|
|
ThunarStandardView *standard_view)
|
|
{
|
|
GtkSortType sort_order;
|
|
gint sort_column;
|
|
|
|
_thunar_return_if_fail (GTK_IS_TREE_SORTABLE (tree_sortable));
|
|
_thunar_return_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view));
|
|
|
|
/* determine the new sort column and sort order, and save them */
|
|
if (gtk_tree_sortable_get_sort_column_id (tree_sortable, &sort_column, &sort_order))
|
|
{
|
|
thunar_standard_view_store_sort_column (standard_view);
|
|
|
|
if (standard_view->priv->directory_specific_settings)
|
|
{
|
|
const gchar *sort_column_name;
|
|
const gchar *sort_order_name;
|
|
|
|
/* save the sort column name */
|
|
sort_column_name = thunar_column_string_from_value (sort_column);
|
|
if (sort_column_name != NULL)
|
|
thunar_file_set_metadata_setting (standard_view->priv->current_directory, "thunar-sort-column", sort_column_name, TRUE);
|
|
|
|
/* convert the sort order to a string */
|
|
if (sort_order == GTK_SORT_ASCENDING)
|
|
sort_order_name = "GTK_SORT_ASCENDING";
|
|
if (sort_order == GTK_SORT_DESCENDING)
|
|
sort_order_name = "GTK_SORT_DESCENDING";
|
|
|
|
/* save the sort order */
|
|
thunar_file_set_metadata_setting (standard_view->priv->current_directory, "thunar-sort-order", sort_order_name, TRUE);
|
|
}
|
|
else
|
|
{
|
|
/* remember the new values as default */
|
|
g_object_set (G_OBJECT (standard_view->preferences),
|
|
"last-sort-column", sort_column,
|
|
"last-sort-order", sort_order,
|
|
NULL);
|
|
}
|
|
}
|
|
|
|
/* request a selection update */
|
|
thunar_standard_view_selection_changed (standard_view);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_loading_unbound (gpointer user_data)
|
|
{
|
|
ThunarStandardView *standard_view = THUNAR_STANDARD_VIEW (user_data);
|
|
|
|
/* we don't have any binding now */
|
|
standard_view->loading_binding = NULL;
|
|
|
|
/* reset the "loading" property */
|
|
if (G_UNLIKELY (standard_view->loading))
|
|
{
|
|
standard_view->loading = FALSE;
|
|
g_object_freeze_notify (G_OBJECT (standard_view));
|
|
g_object_notify_by_pspec (G_OBJECT (standard_view), standard_view_props[PROP_LOADING]);
|
|
thunar_standard_view_update_statusbar_text (standard_view);
|
|
g_object_thaw_notify (G_OBJECT (standard_view));
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_standard_view_drag_scroll_timer (gpointer user_data)
|
|
{
|
|
ThunarStandardView *standard_view = THUNAR_STANDARD_VIEW (user_data);
|
|
GtkAdjustment *adjustment;
|
|
GdkWindow *window;
|
|
GdkSeat *seat;
|
|
GdkDevice *pointer;
|
|
gfloat value;
|
|
gint offset;
|
|
gint y, x;
|
|
gint w, h;
|
|
|
|
/* verify that we are realized */
|
|
if (G_LIKELY (gtk_widget_get_realized (GTK_WIDGET (standard_view))))
|
|
{
|
|
/* determine pointer location and window geometry */
|
|
window = gtk_widget_get_window (gtk_bin_get_child (GTK_BIN (standard_view)));
|
|
seat = gdk_display_get_default_seat (gdk_display_get_default ());
|
|
pointer = gdk_seat_get_pointer (seat);
|
|
|
|
gdk_window_get_device_position (window, pointer, &x, &y, NULL);
|
|
gdk_window_get_geometry (window, NULL, NULL, &w, &h);
|
|
|
|
/* check if we are near the edge (vertical) */
|
|
offset = y - (2 * 20);
|
|
if (G_UNLIKELY (offset > 0))
|
|
offset = MAX (y - (h - 2 * 20), 0);
|
|
|
|
/* change the vertical adjustment appropriately */
|
|
if (G_UNLIKELY (offset != 0))
|
|
{
|
|
/* determine the vertical adjustment */
|
|
adjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (standard_view));
|
|
|
|
/* determine the new value */
|
|
value = CLAMP (gtk_adjustment_get_value (adjustment) + 2 * offset, gtk_adjustment_get_lower (adjustment), gtk_adjustment_get_upper (adjustment) - gtk_adjustment_get_page_size (adjustment));
|
|
|
|
/* apply the new value */
|
|
gtk_adjustment_set_value (adjustment, value);
|
|
}
|
|
|
|
/* check if we are near the edge (horizontal) */
|
|
offset = x - (2 * 20);
|
|
if (G_UNLIKELY (offset > 0))
|
|
offset = MAX (x - (w - 2 * 20), 0);
|
|
|
|
/* change the horizontal adjustment appropriately */
|
|
if (G_UNLIKELY (offset != 0))
|
|
{
|
|
/* determine the vertical adjustment */
|
|
adjustment = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (standard_view));
|
|
|
|
/* determine the new value */
|
|
value = CLAMP (gtk_adjustment_get_value (adjustment) + 2 * offset, gtk_adjustment_get_lower (adjustment), gtk_adjustment_get_upper (adjustment) - gtk_adjustment_get_page_size (adjustment));
|
|
|
|
/* apply the new value */
|
|
gtk_adjustment_set_value (adjustment, value);
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_drag_scroll_timer_destroy (gpointer user_data)
|
|
{
|
|
THUNAR_STANDARD_VIEW (user_data)->priv->drag_scroll_timer_id = 0;
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_standard_view_drag_enter_timer (gpointer user_data)
|
|
{
|
|
ThunarStandardView *standard_view = THUNAR_STANDARD_VIEW (user_data);
|
|
GtkWidget *window;
|
|
|
|
/* cancel any pending drag autoscroll timer */
|
|
if (G_UNLIKELY (standard_view->priv->drag_scroll_timer_id != 0))
|
|
g_source_remove (standard_view->priv->drag_scroll_timer_id);
|
|
|
|
if (standard_view->priv->drag_enter_target != NULL)
|
|
{
|
|
/* if split view is active, give focus to the pane containing the view */
|
|
window = gtk_widget_get_toplevel (GTK_WIDGET (standard_view));
|
|
thunar_window_focus_view (THUNAR_WINDOW (window), GTK_WIDGET (standard_view));
|
|
|
|
/* open the drag target folder */
|
|
thunar_navigator_change_directory (THUNAR_NAVIGATOR (standard_view), standard_view->priv->drag_enter_target);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_drag_enter_timer_destroy (gpointer user_data)
|
|
{
|
|
ThunarStandardView *standard_view = THUNAR_STANDARD_VIEW (user_data);
|
|
|
|
if (standard_view->priv->drag_enter_target != NULL)
|
|
{
|
|
g_object_unref (standard_view->priv->drag_enter_target);
|
|
standard_view->priv->drag_enter_target = NULL;
|
|
}
|
|
|
|
standard_view->priv->drag_enter_timer_id = 0;
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_standard_view_drag_timer (gpointer user_data)
|
|
{
|
|
ThunarStandardView *standard_view = THUNAR_STANDARD_VIEW (user_data);
|
|
|
|
/* fire up the context menu */
|
|
thunar_standard_view_context_menu (standard_view);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_drag_timer_destroy (gpointer user_data)
|
|
{
|
|
ThunarStandardView *standard_view = THUNAR_STANDARD_VIEW (user_data);
|
|
|
|
/* unregister the motion notify and button release event handlers (thread-safe) */
|
|
g_signal_handlers_disconnect_by_func (gtk_bin_get_child (GTK_BIN (user_data)), thunar_standard_view_button_release_event, user_data);
|
|
g_signal_handlers_disconnect_by_func (gtk_bin_get_child (GTK_BIN (user_data)), thunar_standard_view_motion_notify_event, user_data);
|
|
|
|
/* reset drag data */
|
|
standard_view->priv->drag_timer_id = 0;
|
|
if (standard_view->priv->drag_timer_event != NULL)
|
|
{
|
|
gdk_event_free (standard_view->priv->drag_timer_event);
|
|
standard_view->priv->drag_timer_event = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* thunar_standard_view_context_menu:
|
|
* @standard_view : a #ThunarStandardView instance.
|
|
*
|
|
* Invoked by derived classes (and only by derived classes!) whenever the user
|
|
* requests to open a context menu, e.g. by right-clicking on a file/folder or by
|
|
* using one of the context menu shortcuts.
|
|
**/
|
|
void
|
|
thunar_standard_view_context_menu (ThunarStandardView *standard_view)
|
|
{
|
|
GtkWidget *window;
|
|
ThunarMenu *context_menu;
|
|
GList *selected_items;
|
|
|
|
_thunar_return_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view));
|
|
|
|
/* grab an additional reference on the view */
|
|
g_object_ref (G_OBJECT (standard_view));
|
|
|
|
selected_items = (*THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->get_selected_items) (standard_view);
|
|
|
|
window = gtk_widget_get_toplevel (GTK_WIDGET (standard_view));
|
|
|
|
context_menu = g_object_new (THUNAR_TYPE_MENU, "menu-type", THUNAR_MENU_TYPE_CONTEXT_STANDARD_VIEW,
|
|
"action_mgr", thunar_window_get_action_manager (THUNAR_WINDOW (window)), NULL);
|
|
if (selected_items != NULL)
|
|
{
|
|
thunar_menu_add_sections (context_menu, THUNAR_MENU_SECTION_OPEN
|
|
| THUNAR_MENU_SECTION_SENDTO
|
|
| THUNAR_MENU_SECTION_CUT
|
|
| THUNAR_MENU_SECTION_COPY_PASTE
|
|
| THUNAR_MENU_SECTION_TRASH_DELETE
|
|
| THUNAR_MENU_SECTION_EMPTY_TRASH
|
|
| THUNAR_MENU_SECTION_RENAME
|
|
| THUNAR_MENU_SECTION_RESTORE
|
|
| THUNAR_MENU_SECTION_REMOVE_FROM_RECENT
|
|
| THUNAR_MENU_SECTION_CUSTOM_ACTIONS
|
|
| THUNAR_MENU_SECTION_PROPERTIES);
|
|
}
|
|
else /* right click on some empty space */
|
|
{
|
|
thunar_menu_add_sections (context_menu, THUNAR_MENU_SECTION_CREATE_NEW_FILES
|
|
| THUNAR_MENU_SECTION_COPY_PASTE
|
|
| THUNAR_MENU_SECTION_EMPTY_TRASH
|
|
| THUNAR_MENU_SECTION_CUSTOM_ACTIONS);
|
|
thunar_standard_view_append_menu_items (standard_view, GTK_MENU (context_menu), NULL);
|
|
xfce_gtk_menu_append_separator (GTK_MENU_SHELL (context_menu));
|
|
thunar_menu_add_sections (context_menu, THUNAR_MENU_SECTION_ZOOM
|
|
| THUNAR_MENU_SECTION_PROPERTIES);
|
|
}
|
|
thunar_gtk_menu_hide_accel_labels (GTK_MENU (context_menu));
|
|
gtk_widget_show_all (GTK_WIDGET (context_menu));
|
|
thunar_window_redirect_menu_tooltips_to_statusbar (THUNAR_WINDOW (window), GTK_MENU (context_menu));
|
|
|
|
/* if there is a drag_timer_event (long press), we use it */
|
|
if (standard_view->priv->drag_timer_event != NULL)
|
|
{
|
|
thunar_gtk_menu_run_at_event (GTK_MENU (context_menu), standard_view->priv->drag_timer_event);
|
|
gdk_event_free (standard_view->priv->drag_timer_event);
|
|
standard_view->priv->drag_timer_event = NULL;
|
|
}
|
|
else
|
|
thunar_gtk_menu_run (GTK_MENU (context_menu));
|
|
|
|
g_list_free_full (selected_items, (GDestroyNotify) gtk_tree_path_free);
|
|
|
|
/* release the additional reference on the view */
|
|
g_object_unref (G_OBJECT (standard_view));
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* thunar_standard_view_queue_popup:
|
|
* @standard_view : a #ThunarStandardView.
|
|
* @event : the right click event.
|
|
*
|
|
* Schedules a context menu popup in response to
|
|
* a right-click button event. Right-click events
|
|
* need to be handled in a special way, as the
|
|
* user may also start a drag using the right
|
|
* mouse button and therefore this function
|
|
* schedules a timer, which - once expired -
|
|
* opens the context menu. If the user moves
|
|
* the mouse prior to expiration, a right-click
|
|
* drag (with #GDK_ACTION_ASK) will be started
|
|
* instead.
|
|
**/
|
|
void
|
|
thunar_standard_view_queue_popup (ThunarStandardView *standard_view,
|
|
GdkEventButton *event)
|
|
{
|
|
GtkSettings *settings;
|
|
GtkWidget *view;
|
|
gint delay;
|
|
|
|
_thunar_return_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view));
|
|
_thunar_return_if_fail (event != NULL);
|
|
|
|
/* check if we have already scheduled a drag timer */
|
|
if (G_LIKELY (standard_view->priv->drag_timer_id == 0))
|
|
{
|
|
/* remember the new coordinates */
|
|
standard_view->priv->drag_x = event->x;
|
|
standard_view->priv->drag_y = event->y;
|
|
|
|
/* figure out the real view */
|
|
view = gtk_bin_get_child (GTK_BIN (standard_view));
|
|
|
|
/* we use the menu popup delay here, note that we only use this to
|
|
* allow higher values! see bug #3549 */
|
|
settings = gtk_settings_get_for_screen (gtk_widget_get_screen (view));
|
|
g_object_get (G_OBJECT (settings), "gtk-menu-popup-delay", &delay, NULL);
|
|
|
|
/* schedule the timer */
|
|
standard_view->priv->drag_timer_id = g_timeout_add_full (G_PRIORITY_LOW, MAX (225, delay), thunar_standard_view_drag_timer,
|
|
standard_view, thunar_standard_view_drag_timer_destroy);
|
|
/* store current event data */
|
|
standard_view->priv->drag_timer_event = gtk_get_current_event ();
|
|
|
|
/* register the motion notify and the button release events on the real view */
|
|
g_signal_connect (G_OBJECT (view), "button-release-event", G_CALLBACK (thunar_standard_view_button_release_event), standard_view);
|
|
g_signal_connect (G_OBJECT (view), "motion-notify-event", G_CALLBACK (thunar_standard_view_motion_notify_event), standard_view);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
_thunar_standard_view_selection_changed (ThunarStandardView *standard_view)
|
|
{
|
|
GtkTreeIter iter;
|
|
GList *lp, *selected_thunar_files;
|
|
ThunarFile *file;
|
|
|
|
_thunar_return_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view));
|
|
|
|
/* drop any existing "new-files" closure */
|
|
if (G_UNLIKELY (standard_view->priv->new_files_closure != NULL))
|
|
{
|
|
g_closure_invalidate (standard_view->priv->new_files_closure);
|
|
g_closure_unref (standard_view->priv->new_files_closure);
|
|
standard_view->priv->new_files_closure = NULL;
|
|
}
|
|
|
|
/* release the previously selected files */
|
|
thunar_g_list_free_full (standard_view->priv->selected_files);
|
|
|
|
/* determine the new list of selected files (replacing GtkTreePath's with ThunarFile's) */
|
|
selected_thunar_files = (*THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->get_selected_items) (standard_view);
|
|
for (lp = selected_thunar_files; lp != NULL; lp = lp->next)
|
|
{
|
|
/* determine the iterator for the path */
|
|
if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (standard_view->model), &iter, lp->data))
|
|
{
|
|
g_assert (FALSE);
|
|
}
|
|
|
|
gtk_tree_path_free (lp->data);
|
|
|
|
file = thunar_standard_view_model_get_file (standard_view->model, &iter);
|
|
|
|
if (file != NULL)
|
|
{
|
|
/* ...and replace it with the file */
|
|
lp->data = file;
|
|
}
|
|
}
|
|
|
|
/* and setup the new selected files list */
|
|
standard_view->priv->selected_files = selected_thunar_files;
|
|
|
|
/* update the statusbar text */
|
|
thunar_standard_view_update_statusbar_text (standard_view);
|
|
|
|
/* emit notification for "selected-files" */
|
|
g_object_notify_by_pspec (G_OBJECT (standard_view), standard_view_props[PROP_SELECTED_FILES]);
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_standard_view_selection_changed_timeout (ThunarStandardView *standard_view)
|
|
{
|
|
_thunar_return_val_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view), FALSE);
|
|
|
|
if (standard_view->priv->selection_changed_requested)
|
|
_thunar_standard_view_selection_changed (standard_view);
|
|
|
|
standard_view->priv->selection_changed_timeout_source = 0;
|
|
standard_view->priv->selection_changed_requested = FALSE;
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* thunar_standard_view_selection_changed:
|
|
* @standard_view : a #ThunarStandardView instance.
|
|
*
|
|
* Called by derived classes (and only by derived classes!) whenever the file
|
|
* selection changes.
|
|
*
|
|
* Note, that this is also called internally whenever the number of
|
|
* files in the @standard_view<!---->s model changes.
|
|
**/
|
|
void
|
|
thunar_standard_view_selection_changed (ThunarStandardView *standard_view)
|
|
{
|
|
/* in case a timeout is running, the selection-change will be delayed (performance) */
|
|
if (standard_view->priv->selection_changed_timeout_source != 0)
|
|
{
|
|
standard_view->priv->selection_changed_requested = TRUE;
|
|
return;
|
|
}
|
|
|
|
/* handle the first request directly */
|
|
_thunar_standard_view_selection_changed (standard_view);
|
|
|
|
/* throttle consecutive requests if multiple files are selected */
|
|
if (standard_view->priv->selected_files != NULL && g_list_length (standard_view->priv->selected_files) > 1)
|
|
{
|
|
standard_view->priv->selection_changed_timeout_source =
|
|
g_timeout_add (THUNAR_STANDARD_VIEW_SELECTION_CHANGED_DELAY_MS, (GSourceFunc) thunar_standard_view_selection_changed_timeout, standard_view);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* thunar_standard_view_set_history:
|
|
* @standard_view : a #ThunarStandardView instance.
|
|
* @history : the #ThunarHistory to set.
|
|
*
|
|
* replaces the history of this #ThunarStandardView with the passed history
|
|
**/
|
|
void
|
|
thunar_standard_view_set_history (ThunarStandardView *standard_view,
|
|
ThunarHistory *history)
|
|
{
|
|
_thunar_return_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view));
|
|
_thunar_return_if_fail (history == NULL || THUNAR_IS_HISTORY (history));
|
|
|
|
/* set the new history */
|
|
g_object_unref (standard_view->priv->history);
|
|
standard_view->priv->history = history;
|
|
|
|
/* connect callback */
|
|
g_signal_connect_swapped (G_OBJECT (history), "change-directory", G_CALLBACK (thunar_navigator_change_directory), standard_view);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* thunar_standard_view_get_history:
|
|
* @standard_view : a #ThunarStandardView instance.
|
|
*
|
|
* returns the #ThunarHistory of this #ThunarStandardView
|
|
*
|
|
* Return value: (transfer none): The #ThunarHistory of this #ThunarStandardView
|
|
**/
|
|
ThunarHistory *
|
|
thunar_standard_view_get_history (ThunarStandardView *standard_view)
|
|
{
|
|
return standard_view->priv->history;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* thunar_standard_view_copy_history:
|
|
* @standard_view : a #ThunarStandardView instance.
|
|
*
|
|
* returns a copy of the #ThunarHistory of this #ThunarStandardView
|
|
* The caller has to release the passed history with g_object_unref() after use.
|
|
*
|
|
* Return value: (transfer full): A copy of the #ThunarHistory of this #ThunarStandardView
|
|
**/
|
|
ThunarHistory *
|
|
thunar_standard_view_copy_history (ThunarStandardView *standard_view)
|
|
{
|
|
_thunar_return_val_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view), NULL);
|
|
|
|
return thunar_history_copy (standard_view->priv->history);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* thunar_standard_view_append_menu_items:
|
|
* @standard_view : a #ThunarStandardView.
|
|
* @menu : the #GtkMenu to add the menu items.
|
|
* @accel_group : a #GtkAccelGroup to be used used for new menu items
|
|
*
|
|
* Appends widget-specific menu items to a #GtkMenu and connects them to the passed #GtkAccelGroup
|
|
* The concrete implementation depends on the concrete widget which is implementing this view
|
|
**/
|
|
void
|
|
thunar_standard_view_append_menu_items (ThunarStandardView *standard_view,
|
|
GtkMenu *menu,
|
|
GtkAccelGroup *accel_group)
|
|
{
|
|
GtkWidget *item;
|
|
GtkWidget *submenu;
|
|
|
|
_thunar_return_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view));
|
|
|
|
item = xfce_gtk_menu_item_new_from_action_entry (get_action_entry (THUNAR_STANDARD_VIEW_ACTION_ARRANGE_ITEMS_MENU), NULL, GTK_MENU_SHELL (menu));
|
|
submenu = gtk_menu_new ();
|
|
xfce_gtk_toggle_menu_item_new_from_action_entry (get_action_entry (THUNAR_STANDARD_VIEW_ACTION_SORT_BY_NAME), G_OBJECT (standard_view),
|
|
standard_view->priv->sort_column == THUNAR_COLUMN_NAME, GTK_MENU_SHELL (submenu));
|
|
xfce_gtk_toggle_menu_item_new_from_action_entry (get_action_entry (THUNAR_STANDARD_VIEW_ACTION_SORT_BY_SIZE), G_OBJECT (standard_view),
|
|
standard_view->priv->sort_column == THUNAR_COLUMN_SIZE, GTK_MENU_SHELL (submenu));
|
|
xfce_gtk_toggle_menu_item_new_from_action_entry (get_action_entry (THUNAR_STANDARD_VIEW_ACTION_SORT_BY_TYPE), G_OBJECT (standard_view),
|
|
standard_view->priv->sort_column == THUNAR_COLUMN_TYPE, GTK_MENU_SHELL (submenu));
|
|
xfce_gtk_toggle_menu_item_new_from_action_entry (get_action_entry (THUNAR_STANDARD_VIEW_ACTION_SORT_BY_MTIME), G_OBJECT (standard_view),
|
|
standard_view->priv->sort_column == THUNAR_COLUMN_DATE_MODIFIED, GTK_MENU_SHELL (submenu));
|
|
if (thunar_file_is_trash (standard_view->priv->current_directory))
|
|
xfce_gtk_toggle_menu_item_new_from_action_entry (get_action_entry (THUNAR_STANDARD_VIEW_ACTION_SORT_BY_DTIME), G_OBJECT (standard_view),
|
|
standard_view->priv->sort_column == THUNAR_COLUMN_DATE_DELETED, GTK_MENU_SHELL (submenu));
|
|
xfce_gtk_menu_append_separator (GTK_MENU_SHELL (submenu));
|
|
xfce_gtk_toggle_menu_item_new_from_action_entry (get_action_entry (THUNAR_STANDARD_VIEW_ACTION_SORT_ASCENDING), G_OBJECT (standard_view),
|
|
standard_view->priv->sort_order == GTK_SORT_ASCENDING, GTK_MENU_SHELL (submenu));
|
|
xfce_gtk_toggle_menu_item_new_from_action_entry (get_action_entry (THUNAR_STANDARD_VIEW_ACTION_SORT_DESCENDING), G_OBJECT (standard_view),
|
|
standard_view->priv->sort_order == GTK_SORT_DESCENDING, GTK_MENU_SHELL (submenu));
|
|
xfce_gtk_menu_append_separator (GTK_MENU_SHELL (submenu));
|
|
xfce_gtk_toggle_menu_item_new_from_action_entry (get_action_entry (THUNAR_STANDARD_VIEW_ACTION_SORT_ORDER_TOGGLE), G_OBJECT (standard_view),
|
|
standard_view->priv->sort_order == GTK_SORT_DESCENDING, GTK_MENU_SHELL (submenu));
|
|
gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), GTK_WIDGET (submenu));
|
|
gtk_widget_show (item);
|
|
|
|
if (THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->append_menu_items != NULL)
|
|
(*THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->append_menu_items) (standard_view, menu, accel_group);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* thunar_standard_view_append_menu_item:
|
|
* @standard_view : Instance of a #ThunarStandardView
|
|
* @menu : #GtkMenuShell to which the item should be added
|
|
* @action : #ThunarStandardViewAction to select which item should be added
|
|
*
|
|
* Adds the selected, widget specific #GtkMenuItem to the passed #GtkMenuShell
|
|
*
|
|
* Return value: (transfer none): The added #GtkMenuItem
|
|
**/
|
|
GtkWidget *
|
|
thunar_standard_view_append_menu_item (ThunarStandardView *standard_view,
|
|
GtkMenu *menu,
|
|
ThunarStandardViewAction action)
|
|
{
|
|
GtkWidget *item;
|
|
|
|
_thunar_return_val_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view), NULL);
|
|
|
|
item = xfce_gtk_menu_item_new_from_action_entry (get_action_entry (action), G_OBJECT (standard_view), GTK_MENU_SHELL (menu));
|
|
|
|
if (action == THUNAR_STANDARD_VIEW_ACTION_UNSELECT_ALL_FILES)
|
|
gtk_widget_set_sensitive (item, standard_view->priv->selected_files != NULL);
|
|
|
|
return item;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* thunar_standard_view_connect_accelerators:
|
|
* @standard_view : a #ThunarStandardView.
|
|
*
|
|
* Connects all accelerators and corresponding default keys of this widget to the global accelerator list
|
|
* The concrete implementation depends on the concrete widget which is implementing this view
|
|
**/
|
|
static void
|
|
thunar_standard_view_connect_accelerators (ThunarStandardView *standard_view)
|
|
{
|
|
_thunar_return_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view));
|
|
|
|
if (standard_view->accel_group == NULL)
|
|
return;
|
|
|
|
/* can cause emissions of "accel-map::changed" which in turns calls thunar_window_reconnect_accelerators */
|
|
xfce_gtk_accel_map_add_entries (thunar_standard_view_action_entries, G_N_ELEMENTS (thunar_standard_view_action_entries));
|
|
xfce_gtk_accel_group_connect_action_entries (standard_view->accel_group,
|
|
thunar_standard_view_action_entries,
|
|
G_N_ELEMENTS (thunar_standard_view_action_entries),
|
|
standard_view);
|
|
|
|
/* as well append accelerators of derived widgets */
|
|
if (THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->connect_accelerators != NULL)
|
|
(*THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->connect_accelerators) (standard_view, standard_view->accel_group);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* thunar_standard_view_disconnect_accelerators:
|
|
* @standard_view : a #ThunarStandardView.
|
|
*
|
|
* Disconnects all accelerators of this widget from the global accelerator list
|
|
* The concrete implementation depends on the concrete widget which is implementing this view
|
|
**/
|
|
static void
|
|
thunar_standard_view_disconnect_accelerators (ThunarStandardView *standard_view)
|
|
{
|
|
_thunar_return_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view));
|
|
|
|
if (standard_view->accel_group == NULL)
|
|
return;
|
|
|
|
/* Dont listen to the accel keys defined by the action entries any more */
|
|
xfce_gtk_accel_group_disconnect_action_entries (standard_view->accel_group,
|
|
thunar_standard_view_action_entries,
|
|
G_N_ELEMENTS (thunar_standard_view_action_entries));
|
|
|
|
/* as well disconnect accelerators of derived widgets */
|
|
if (THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->disconnect_accelerators != NULL)
|
|
(*THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->disconnect_accelerators) (standard_view, standard_view->accel_group);
|
|
|
|
/* and release the accel group */
|
|
g_object_unref (standard_view->accel_group);
|
|
standard_view->accel_group = NULL;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* _thunar_standard_view_open_on_middle_click:
|
|
* @standard_view : a #ThunarStandardView.
|
|
* @tree_path : the #GtkTreePath to open.
|
|
* @event_state : The event_state of the pressed #GdkEventButton
|
|
*
|
|
* Method only should be used by child widgets.
|
|
* The method will attempt to find a thunar file for the given #GtkTreePath and open it as window/tab, if it is a directory
|
|
* Note that this method only should be used after pressing the middle-mouse button
|
|
**/
|
|
void
|
|
_thunar_standard_view_open_on_middle_click (ThunarStandardView *standard_view,
|
|
GtkTreePath *tree_path,
|
|
guint event_state)
|
|
{
|
|
GtkTreeIter iter;
|
|
GList selected_files;
|
|
gboolean in_tab;
|
|
GtkWidget *window;
|
|
ThunarActionManager *action_mgr;
|
|
|
|
_thunar_return_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view));
|
|
|
|
/* init the selected files list */
|
|
selected_files.data = NULL;
|
|
selected_files.prev = NULL;
|
|
selected_files.next = NULL;
|
|
|
|
/* determine the file for the path */
|
|
gtk_tree_model_get_iter (GTK_TREE_MODEL (standard_view->model), &iter, tree_path);
|
|
selected_files.data = thunar_standard_view_model_get_file (standard_view->model, &iter);
|
|
if (G_LIKELY (selected_files.data != NULL))
|
|
{
|
|
if (thunar_file_is_directory (selected_files.data))
|
|
{
|
|
/* lookup setting if we should open in a tab or a window */
|
|
g_object_get (G_OBJECT (standard_view->preferences), "misc-middle-click-in-tab", &in_tab, NULL);
|
|
|
|
/* holding ctrl inverts the action */
|
|
if ((event_state & GDK_CONTROL_MASK) != 0)
|
|
in_tab = !in_tab;
|
|
|
|
window = gtk_widget_get_toplevel (GTK_WIDGET (standard_view));
|
|
action_mgr = thunar_window_get_action_manager (THUNAR_WINDOW (window));
|
|
thunar_action_manager_set_selection (action_mgr, &selected_files, NULL, NULL);
|
|
thunar_action_manager_open_selected_folders (action_mgr, in_tab);
|
|
}
|
|
|
|
/* release the file reference */
|
|
g_object_unref (G_OBJECT (selected_files.data));
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* thunar_standard_view_set_searching:
|
|
* @standard_view : a #ThunarStandardView.
|
|
* @search_query : the search string.
|
|
*
|
|
* If @search_query is not NULL a search is initialized for this view. If it is NULL and the view is displaying
|
|
* the results of a previous search it reverts to its normal contents.
|
|
**/
|
|
void
|
|
thunar_standard_view_set_searching (ThunarStandardView *standard_view,
|
|
gchar *search_query)
|
|
{
|
|
GtkTreeView *tree_view = NULL;
|
|
|
|
/* can be called from a change in the path entry when the tab switches and a new directory is set
|
|
* which in turn sets a new location (see thunar_window_notebook_switch_page) */
|
|
if (standard_view == NULL)
|
|
return;
|
|
|
|
if (standard_view->priv->search_query != NULL && search_query == NULL)
|
|
thunar_standard_view_search_done (standard_view->model, standard_view);
|
|
|
|
/* save the new query (used for switching between views) */
|
|
g_free (standard_view->priv->search_query);
|
|
standard_view->priv->search_query = g_strdup (search_query);
|
|
|
|
/* initiate the search */
|
|
/* set_folder() can emit a large number of row-deleted signals for large folders,
|
|
* to the extent it degrades performance: https://gitlab.xfce.org/xfce/thunar/-/issues/914 */
|
|
|
|
/* We drop the model from the view as a simple optimization to speed up
|
|
* the process of disconnecting the model data from the view.
|
|
*/
|
|
g_object_set (G_OBJECT (gtk_bin_get_child (GTK_BIN (standard_view))), "model", NULL, NULL);
|
|
|
|
g_object_ref (G_OBJECT (thunar_standard_view_model_get_folder (standard_view->model))); /* temporarily hold a reference so the folder doesn't get deleted */
|
|
thunar_standard_view_model_set_folder (standard_view->model, thunar_standard_view_model_get_folder (standard_view->model), search_query);
|
|
g_object_unref (G_OBJECT (thunar_standard_view_model_get_folder (standard_view->model))); /* reference no longer needed */
|
|
|
|
/* reconnect our model to the view */
|
|
g_object_set (G_OBJECT (gtk_bin_get_child (GTK_BIN (standard_view))), "model", standard_view->model, NULL);
|
|
|
|
|
|
/* change the display name in the tab */
|
|
g_object_notify_by_pspec (G_OBJECT (standard_view), standard_view_props[PROP_DISPLAY_NAME]);
|
|
|
|
if (THUNAR_IS_DETAILS_VIEW (standard_view))
|
|
tree_view = GTK_TREE_VIEW (gtk_bin_get_child (GTK_BIN (standard_view)));
|
|
|
|
if (search_query != NULL && g_strcmp0 (search_query, "") != 0)
|
|
{
|
|
standard_view->priv->active_search = TRUE;
|
|
/* disable expandable folders when searching */
|
|
if (tree_view != NULL)
|
|
{
|
|
gtk_tree_view_set_show_expanders (tree_view, FALSE);
|
|
gtk_tree_view_set_enable_tree_lines (tree_view, FALSE);
|
|
gtk_tree_view_collapse_all (tree_view);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
standard_view->priv->active_search = FALSE;
|
|
if (tree_view != NULL)
|
|
g_object_notify (G_OBJECT (standard_view->preferences), "misc-expandable-folders");
|
|
}
|
|
|
|
/* notify listeners */
|
|
g_object_notify_by_pspec (G_OBJECT (standard_view), standard_view_props[PROP_SEARCHING]);
|
|
g_object_notify_by_pspec (G_OBJECT (standard_view), standard_view_props[PROP_SEARCH_MODE_ACTIVE]);
|
|
}
|
|
|
|
|
|
|
|
gchar *
|
|
thunar_standard_view_get_search_query (ThunarStandardView *standard_view)
|
|
{
|
|
return standard_view->priv->search_query;
|
|
}
|
|
|
|
|
|
|
|
void
|
|
thunar_standard_view_save_view_type (ThunarStandardView *standard_view,
|
|
GType type)
|
|
{
|
|
standard_view->priv->type = type;
|
|
}
|
|
|
|
|
|
|
|
GType
|
|
thunar_standard_view_get_saved_view_type (ThunarStandardView *standard_view)
|
|
{
|
|
return standard_view->priv->type;
|
|
}
|
|
|
|
|
|
|
|
XfceGtkActionEntry *
|
|
thunar_standard_view_get_action_entries (void)
|
|
{
|
|
return thunar_standard_view_action_entries;
|
|
}
|
|
|
|
|
|
|
|
void
|
|
thunar_standard_view_queue_redraw (ThunarStandardView *standard_view)
|
|
{
|
|
_thunar_return_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view));
|
|
(*THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->queue_redraw) (standard_view);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_highlight_option_changed (ThunarStandardView *standard_view)
|
|
{
|
|
GtkWidget *view = gtk_bin_get_child (GTK_BIN (standard_view));
|
|
GtkCellLayout *layout = NULL;
|
|
GtkCellLayoutDataFunc function = NULL;
|
|
gboolean show_highlight;
|
|
GtkStyleContext *context = gtk_widget_get_style_context (view);
|
|
|
|
if (GTK_IS_TREE_VIEW (view))
|
|
layout = GTK_CELL_LAYOUT (gtk_tree_view_get_column (GTK_TREE_VIEW (view), THUNAR_COLUMN_NAME));
|
|
else
|
|
layout = GTK_CELL_LAYOUT (view);
|
|
|
|
g_object_get (G_OBJECT (THUNAR_STANDARD_VIEW (standard_view)->preferences), "misc-highlighting-enabled", &show_highlight, NULL);
|
|
|
|
if (show_highlight)
|
|
function = (GtkCellLayoutDataFunc) THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->cell_layout_data_func;
|
|
|
|
if (standard_view->priv->css_provider == NULL)
|
|
{
|
|
standard_view->priv->css_provider = gtk_css_provider_new ();
|
|
gtk_css_provider_load_from_data (
|
|
standard_view->priv->css_provider,
|
|
".standard-view .view:selected { background-color: rgba(0,0,0,0); outline: rgba(0,0,0,0); }",
|
|
-1, NULL);
|
|
}
|
|
|
|
/* if highlighting is enabled; add our custom css. */
|
|
if (show_highlight)
|
|
gtk_style_context_add_provider (
|
|
context,
|
|
GTK_STYLE_PROVIDER (standard_view->priv->css_provider),
|
|
GTK_STYLE_PROVIDER_PRIORITY_USER);
|
|
/* if highlighting is disabled; drop our custom css */
|
|
else
|
|
gtk_style_context_remove_provider (context, GTK_STYLE_PROVIDER (standard_view->priv->css_provider));
|
|
|
|
gtk_cell_layout_set_cell_data_func (layout,
|
|
standard_view->icon_renderer,
|
|
function, standard_view, NULL);
|
|
gtk_cell_layout_set_cell_data_func (layout,
|
|
standard_view->name_renderer,
|
|
function, standard_view, NULL);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_standard_view_cell_layout_data_func (GtkCellLayout *layout,
|
|
GtkCellRenderer *cell,
|
|
GtkTreeModel *model,
|
|
GtkTreeIter *iter,
|
|
gpointer data)
|
|
{
|
|
ThunarFile *file = THUNAR_FILE (thunar_standard_view_model_get_file (THUNAR_STANDARD_VIEW_MODEL (model), iter));
|
|
gchar *background = NULL;
|
|
gchar *foreground = NULL;
|
|
GdkRGBA foreground_rgba;
|
|
GtkWidget *view = GTK_WIDGET (data);
|
|
GtkWidget *toplevel;
|
|
|
|
if (file == NULL)
|
|
return;
|
|
|
|
background = thunar_file_get_metadata_setting (file, "thunar-highlight-color-background");
|
|
foreground = thunar_file_get_metadata_setting (file, "thunar-highlight-color-foreground");
|
|
|
|
if (foreground != NULL)
|
|
gdk_rgba_parse (&foreground_rgba, foreground);
|
|
foreground_rgba.alpha = ALPHA_FOCUSED;
|
|
|
|
/* check the state of our window (active/backdrop) */
|
|
if (GTK_IS_WIDGET (view))
|
|
{
|
|
toplevel = gtk_widget_get_toplevel (view);
|
|
if (GTK_IS_WINDOW (toplevel) && !gtk_window_is_active (GTK_WINDOW (toplevel)))
|
|
foreground_rgba.alpha = ALPHA_BACKDROP;
|
|
}
|
|
|
|
/* since this function is being used for both icon & name renderers;
|
|
* we need to make sure the right properties are applied to the right renderers */
|
|
if (THUNAR_IS_TEXT_RENDERER (cell))
|
|
{
|
|
/* set disk usage bar for computer:// items */
|
|
gint usage = -1;
|
|
GFile *gfile = thunar_file_get_file (file);
|
|
if (gfile != NULL && g_file_has_uri_scheme (gfile, "computer"))
|
|
{
|
|
GFile *root = thunar_file_get_target_location (file);
|
|
if (root == NULL)
|
|
root = gfile;
|
|
guint64 fs_free = 0, fs_total = 0;
|
|
if (thunar_g_file_get_free_space (root, &fs_free, &fs_total) && fs_total > 0)
|
|
usage = (gint) ((fs_total - fs_free) * 100 / fs_total);
|
|
if (root != gfile)
|
|
g_object_unref (root);
|
|
}
|
|
g_object_set (G_OBJECT (cell),
|
|
"foreground-rgba", foreground != NULL ? &foreground_rgba : NULL,
|
|
"highlight-color", background,
|
|
"usage-percent", usage,
|
|
NULL);
|
|
}
|
|
|
|
else if (THUNAR_IS_ICON_RENDERER (cell))
|
|
g_object_set (G_OBJECT (cell),
|
|
"highlight-color", background,
|
|
NULL);
|
|
|
|
else if (GTK_IS_CELL_RENDERER_TEXT (cell))
|
|
g_object_set (G_OBJECT (cell),
|
|
"foreground-rgba", foreground != NULL ? &foreground_rgba : NULL,
|
|
"background", background,
|
|
NULL);
|
|
|
|
else if (GTK_IS_CELL_RENDERER (cell))
|
|
g_object_set (G_OBJECT (cell),
|
|
"cell-background", background,
|
|
NULL);
|
|
|
|
else
|
|
g_warn_if_reached ();
|
|
|
|
g_free (foreground);
|
|
g_free (background);
|
|
g_object_unref (file);
|
|
}
|
|
|
|
|
|
|
|
void
|
|
thunar_standard_view_set_model (ThunarStandardView *standard_view)
|
|
{
|
|
if (standard_view->priv->model_type == G_TYPE_NONE)
|
|
return;
|
|
|
|
if (standard_view->model != NULL)
|
|
{
|
|
g_signal_handlers_disconnect_by_data (G_OBJECT (standard_view->model), standard_view);
|
|
g_object_unref (G_OBJECT (standard_view->model));
|
|
standard_view->model = NULL;
|
|
if (G_LIKELY (standard_view->loading_binding != NULL))
|
|
{
|
|
/* this will free it as well */
|
|
g_binding_unbind (standard_view->loading_binding);
|
|
standard_view->loading_binding = NULL;
|
|
}
|
|
}
|
|
|
|
standard_view->model = g_object_new (standard_view->priv->model_type, NULL);
|
|
standard_view->priv->row_deleted_id = g_signal_connect_after (G_OBJECT (standard_view->model), "row-deleted", G_CALLBACK (thunar_standard_view_select_after_row_deleted), standard_view);
|
|
g_signal_connect (G_OBJECT (standard_view->model), "rows-reordered", G_CALLBACK (thunar_standard_view_rows_reordered), standard_view);
|
|
g_signal_connect (G_OBJECT (standard_view->model), "error", G_CALLBACK (thunar_standard_view_error), standard_view);
|
|
g_signal_connect (G_OBJECT (standard_view->model), "search-done", G_CALLBACK (thunar_standard_view_search_done), standard_view);
|
|
g_object_bind_property (G_OBJECT (standard_view->preferences), "misc-case-sensitive", G_OBJECT (standard_view->model), "case-sensitive", G_BINDING_SYNC_CREATE);
|
|
g_object_bind_property (G_OBJECT (standard_view->preferences), "misc-date-style", G_OBJECT (standard_view->model), "date-style", G_BINDING_SYNC_CREATE);
|
|
g_object_bind_property (G_OBJECT (standard_view->preferences), "misc-date-custom-style", G_OBJECT (standard_view->model), "date-custom-style", G_BINDING_SYNC_CREATE);
|
|
g_object_bind_property (G_OBJECT (standard_view->preferences), "misc-folders-first", G_OBJECT (standard_view->model), "folders-first", G_BINDING_SYNC_CREATE);
|
|
g_object_bind_property (G_OBJECT (standard_view->preferences), "misc-file-size-binary", G_OBJECT (standard_view->model), "file-size-binary", G_BINDING_SYNC_CREATE);
|
|
g_object_bind_property (G_OBJECT (standard_view->preferences), "misc-folder-item-count", G_OBJECT (standard_view->model), "folder-item-count", G_BINDING_SYNC_CREATE);
|
|
|
|
/* be sure to update the selection whenever the folder changes */
|
|
g_signal_connect_swapped (G_OBJECT (standard_view->model), "notify::folder", G_CALLBACK (thunar_standard_view_selection_changed), standard_view);
|
|
|
|
/* be sure to update the statusbar text whenever the number of files in our model changes. */
|
|
g_signal_connect_swapped (G_OBJECT (standard_view->model), "notify::num-files", G_CALLBACK (thunar_standard_view_update_statusbar_text), standard_view);
|
|
|
|
/* be sure to update the statusbar text whenever the file-size-binary property changes */
|
|
g_signal_connect_swapped (G_OBJECT (standard_view->model), "notify::file-size-binary", G_CALLBACK (thunar_standard_view_update_statusbar_text), standard_view);
|
|
|
|
/* pass down the "loading" property into the new model */
|
|
g_object_bind_property (standard_view->model, "loading",
|
|
standard_view, "loading",
|
|
G_BINDING_SYNC_CREATE);
|
|
}
|
|
|
|
|
|
|
|
void
|
|
thunar_standard_view_transfer_selection (ThunarStandardView *standard_view,
|
|
ThunarStandardView *old_view)
|
|
{
|
|
GList *files;
|
|
|
|
_thunar_return_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view));
|
|
_thunar_return_if_fail (THUNAR_IS_STANDARD_VIEW (old_view));
|
|
|
|
if (standard_view->priv->files_to_select != NULL)
|
|
g_list_free_full (standard_view->priv->files_to_select, g_object_unref);
|
|
|
|
if (old_view->priv->files_to_select != NULL)
|
|
standard_view->priv->files_to_select = thunar_g_list_copy_deep (old_view->priv->files_to_select);
|
|
|
|
files = thunar_component_get_selected_files (THUNAR_COMPONENT (old_view));
|
|
if (files != NULL)
|
|
thunar_component_set_selected_files (THUNAR_COMPONENT (standard_view), files);
|
|
}
|