thunar-usagebars/thunar/thunar-standard-view.c
4aiman fa6a8694c3 feat: add disk usage bars to side panel and computer:// view
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()
2026-11-04 01:40:30 +03:00

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);
}