gtk2/gtk/gtkplacessidebar.c
Matthias Clasen a8191e7113 places sidebar: Make 'recent' work on other platforms
Since nautilus merge, we were not showing 'Recent' in the sidebar
if GIO did not support the recent: scheme. But the file chooser
can show recent files independent of gvfs - it loads the recent
files manually. This is relevant on Windows and OS X, where gvfs
is typically not used.

This commit adds a show-recent property which can be used to override
the recent: scheme check. We use it in the file chooser.

https://bugzilla.gnome.org/show_bug.cgi?id=750068
2015-06-06 17:13:22 -04:00

5393 lines
164 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* GtkPlacesSidebar - sidebar widget for places in the filesystem
*
* This code comes from Nautilus, GNOMEs file manager.
*
* This library 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 library 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 library; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* Authors : Mr Jamie McCracken (jamiemcc at blueyonder dot co dot uk)
* Cosimo Cecchi <cosimoc@gnome.org>
* Federico Mena Quintero <federico@gnome.org>
*
*/
/* TODO:
*
* * Fix instances of "#if 0"
*
* * Fix FIXMEs
*
* * Grep for "NULL-GError" and see if they should be taken care of
*
* * Although we do g_mount_unmount_with_operation(), Nautilus used to do
* nautilus_file_operations_unmount_mount_full() to unmount a volume. With
* that, Nautilus does the "volume has trash, empty it first?" dance. Cosimo
* suggests that this logic should be part of GtkMountOperation, which can
* have Unix-specific code for emptying trash.
*
* * Sync nautilus commit 17a85b78acc78b573c2e1776b348ed348e19adb7
*
*/
#include "config.h"
#include <gio/gio.h>
#include "gdk/gdkkeysyms.h"
#include "gtkbookmarksmanager.h"
#include "gtkcelllayout.h"
#include "gtkcellrenderertext.h"
#include "gtkcellrendererpixbuf.h"
#include "gtkfilesystem.h"
#include "gtkicontheme.h"
#include "gtkintl.h"
#include "gtkmain.h"
#include "gtkmarshalers.h"
#include "gtkmenuitem.h"
#include "gtkmountoperation.h"
#include "gtkplacessidebar.h"
#include "gtkscrolledwindow.h"
#include "gtkseparatormenuitem.h"
#include "gtksettings.h"
#include "gtktrashmonitor.h"
#include "gtktreeselection.h"
#include "gtktreednd.h"
#include "gtktypebuiltins.h"
#include "gtkwindow.h"
#include "gtkpopover.h"
#include "gtkgrid.h"
#include "gtklabel.h"
#include "gtkbutton.h"
/**
* SECTION:gtkplacessidebar
* @Short_description: Sidebar that displays frequently-used places in the file system
* @Title: GtkPlacesSidebar
* @See_also: #GtkFileChooser
*
* #GtkPlacesSidebar is a widget that displays a list of frequently-used places in the
* file system: the users home directory, the users bookmarks, and volumes and drives.
* This widget is used as a sidebar in #GtkFileChooser and may be used by file managers
* and similar programs.
*
* The places sidebar displays drives and volumes, and will automatically mount
* or unmount them when the user selects them.
*
* Applications can hook to various signals in the places sidebar to customize
* its behavior. For example, they can add extra commands to the context menu
* of the sidebar.
*
* While bookmarks are completely in control of the user, the places sidebar also
* allows individual applications to provide extra shortcut folders that are unique
* to each application. For example, a Paint program may want to add a shortcut
* for a Clipart folder. You can do this with gtk_places_sidebar_add_shortcut().
*
* To make use of the places sidebar, an application at least needs to connect
* to the #GtkPlacesSidebar::open-location signal. This is emitted when the
* user selects in the sidebar a location to open. The application should also
* call gtk_places_sidebar_set_location() when it changes the currently-viewed
* location.
*/
#define EJECT_BUTTON_XPAD 8
#define ICON_CELL_XPAD 8
#define TIMEOUT_EXPAND 500
/* These are used when a destination-side DND operation is taking place.
* Normally, when a file is being hovered directly over a bookmark,
* well be in DROP_STATE_NORMAL.
*
* But when a file is being hovered between bookmarks, this means the user
* may want to create a new bookmark for that file between those bookmarks.
* In that case, the drop state will be *not* DROP_STATE_NORMAL.
*
* When the drop state is FADING_OUT, it means that the user is hovering
* directly over an existing bookmark and an immediate drop will cause the
* file being dragged to be dropped on the bookmark, instead of causing
* a new bookmark to be created.
*/
typedef enum {
DROP_STATE_NORMAL,
DROP_STATE_NEW_BOOKMARK_FADING_IN,
DROP_STATE_NEW_BOOKMARK_ARMED,
DROP_STATE_NEW_BOOKMARK_FADING_OUT
} DropState;
struct _GtkPlacesSidebar {
GtkScrolledWindow parent;
GtkTreeView *tree_view;
GtkCellRenderer *eject_icon_cell_renderer;
GtkCellRenderer *text_cell_renderer;
GtkListStore *store;
GtkBookmarksManager *bookmarks_manager;
GVolumeMonitor *volume_monitor;
GtkTrashMonitor *trash_monitor;
GtkSettings *gtk_settings;
GFile *current_location;
GtkWidget *rename_popover;
GtkWidget *rename_entry;
GtkWidget *rename_button;
GtkWidget *rename_error;
gchar *rename_uri;
gulong trash_monitor_changed_id;
gboolean devices_header_added;
gboolean bookmarks_header_added;
/* DND */
GList *drag_list; /* list of GFile */
gint drag_data_info;
/* volume mounting - delayed open process */
GtkPlacesOpenFlags go_to_after_mount_open_flags;
GCancellable *cancellable;
GtkWidget *popup_menu;
GSList *shortcuts;
GDBusProxy *hostnamed_proxy;
GCancellable *hostnamed_cancellable;
gchar *hostname;
GtkPlacesOpenFlags open_flags;
DropState drop_state;
gint new_bookmark_index;
guint drag_leave_timeout_id;
gchar *drop_target_uri;
guint switch_location_timer;
guint mounting : 1;
guint drag_data_received : 1;
guint drop_occured : 1;
guint show_recent_set : 1;
guint show_recent : 1;
guint show_desktop_set : 1;
guint show_desktop : 1;
guint show_connect_to_server : 1;
guint show_enter_location : 1;
guint local_only : 1;
};
struct _GtkPlacesSidebarClass {
GtkScrolledWindowClass parent;
void (* open_location) (GtkPlacesSidebar *sidebar,
GFile *location,
GtkPlacesOpenFlags open_flags);
void (* populate_popup) (GtkPlacesSidebar *sidebar,
GtkMenu *menu,
GFile *selected_item,
GVolume *selected_volume);
void (* show_error_message) (GtkPlacesSidebar *sidebar,
const gchar *primary,
const gchar *secondary);
void (* show_connect_to_server) (GtkPlacesSidebar *sidebar);
GdkDragAction (* drag_action_requested) (GtkPlacesSidebar *sidebar,
GdkDragContext *context,
GFile *dest_file,
GList *source_file_list);
GdkDragAction (* drag_action_ask) (GtkPlacesSidebar *sidebar,
GdkDragAction actions);
void (* drag_perform_drop) (GtkPlacesSidebar *sidebar,
GFile *dest_file,
GList *source_file_list,
GdkDragAction action);
void (* show_enter_location) (GtkPlacesSidebar *sidebar);
};
enum {
PLACES_SIDEBAR_COLUMN_ROW_TYPE,
PLACES_SIDEBAR_COLUMN_URI,
PLACES_SIDEBAR_COLUMN_DRIVE,
PLACES_SIDEBAR_COLUMN_VOLUME,
PLACES_SIDEBAR_COLUMN_MOUNT,
PLACES_SIDEBAR_COLUMN_NAME,
PLACES_SIDEBAR_COLUMN_GICON,
PLACES_SIDEBAR_COLUMN_INDEX,
PLACES_SIDEBAR_COLUMN_EJECT,
PLACES_SIDEBAR_COLUMN_NO_EJECT,
PLACES_SIDEBAR_COLUMN_BOOKMARK,
PLACES_SIDEBAR_COLUMN_TOOLTIP,
PLACES_SIDEBAR_COLUMN_SECTION_TYPE,
PLACES_SIDEBAR_COLUMN_HEADING_TEXT,
PLACES_SIDEBAR_COLUMN_COUNT
};
typedef enum {
PLACES_BUILT_IN,
PLACES_XDG_DIR,
PLACES_MOUNTED_VOLUME,
PLACES_BOOKMARK,
PLACES_HEADING,
PLACES_CONNECT_TO_SERVER,
PLACES_ENTER_LOCATION,
PLACES_DROP_FEEDBACK
} PlaceType;
typedef enum {
SECTION_DEVICES,
SECTION_BOOKMARKS,
SECTION_COMPUTER,
SECTION_NETWORK
} SectionType;
enum {
OPEN_LOCATION,
POPULATE_POPUP,
SHOW_ERROR_MESSAGE,
SHOW_CONNECT_TO_SERVER,
SHOW_ENTER_LOCATION,
DRAG_ACTION_REQUESTED,
DRAG_ACTION_ASK,
DRAG_PERFORM_DROP,
LAST_SIGNAL
};
enum {
PROP_LOCATION = 1,
PROP_OPEN_FLAGS,
PROP_SHOW_RECENT,
PROP_SHOW_DESKTOP,
PROP_SHOW_CONNECT_TO_SERVER,
PROP_SHOW_ENTER_LOCATION,
PROP_LOCAL_ONLY,
NUM_PROPERTIES
};
/* Names for themed icons */
#define ICON_NAME_HOME "user-home-symbolic"
#define ICON_NAME_DESKTOP "user-desktop-symbolic"
#define ICON_NAME_FILESYSTEM "drive-harddisk-symbolic"
#define ICON_NAME_EJECT "media-eject-symbolic"
#define ICON_NAME_NETWORK "network-workgroup-symbolic"
#define ICON_NAME_NETWORK_SERVER "network-server-symbolic"
#define ICON_NAME_FOLDER_NETWORK "folder-remote-symbolic"
#define ICON_NAME_FOLDER "folder-symbolic"
#define ICON_NAME_FOLDER_DESKTOP "user-desktop-symbolic"
#define ICON_NAME_FOLDER_DOCUMENTS "folder-documents-symbolic"
#define ICON_NAME_FOLDER_DOWNLOAD "folder-download-symbolic"
#define ICON_NAME_FOLDER_MUSIC "folder-music-symbolic"
#define ICON_NAME_FOLDER_PICTURES "folder-pictures-symbolic"
#define ICON_NAME_FOLDER_PUBLIC_SHARE "folder-publicshare-symbolic"
#define ICON_NAME_FOLDER_TEMPLATES "folder-templates-symbolic"
#define ICON_NAME_FOLDER_VIDEOS "folder-videos-symbolic"
#define ICON_NAME_FOLDER_SAVED_SEARCH "folder-saved-search-symbolic"
static guint places_sidebar_signals [LAST_SIGNAL] = { 0 };
static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
static void open_selected_bookmark (GtkPlacesSidebar *sidebar,
GtkTreeModel *model,
GtkTreeIter *iter,
GtkPlacesOpenFlags open_flags);
static gboolean eject_or_unmount_bookmark (GtkPlacesSidebar *sidebar,
GtkTreePath *path);
static gboolean eject_or_unmount_selection (GtkPlacesSidebar *sidebar);
static void check_unmount_and_eject (GMount *mount,
GVolume *volume,
GDrive *drive,
gboolean *show_unmount,
gboolean *show_eject);
/* Identifiers for target types */
enum {
GTK_TREE_MODEL_ROW,
TEXT_URI_LIST
};
/* Target types for dragging from the shortcuts list */
static const GtkTargetEntry dnd_source_targets[] = {
{ "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, GTK_TREE_MODEL_ROW }
};
/* Target types for dropping into the shortcuts list */
static const GtkTargetEntry dnd_drop_targets [] = {
{ "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, GTK_TREE_MODEL_ROW }
};
/* Drag and drop interface declarations */
typedef struct {
GtkListStore parent;
GtkPlacesSidebar *sidebar;
} ShortcutsModel;
typedef struct {
GtkListStoreClass parent_class;
} ShortcutsModelClass;
#define SHORTCUTS_MODEL_TYPE (shortcuts_model_get_type ())
#define SHORTCUTS_MODEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SHORTCUTS_MODEL_TYPE, ShortcutsModel))
static GType shortcuts_model_get_type (void);
static void shortcuts_model_drag_source_iface_init (GtkTreeDragSourceIface *iface);
G_DEFINE_TYPE_WITH_CODE (ShortcutsModel, shortcuts_model, GTK_TYPE_LIST_STORE,
G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_DRAG_SOURCE,
shortcuts_model_drag_source_iface_init));
static GtkListStore *shortcuts_model_new (GtkPlacesSidebar *sidebar);
G_DEFINE_TYPE (GtkPlacesSidebar, gtk_places_sidebar, GTK_TYPE_SCROLLED_WINDOW);
static void
emit_open_location (GtkPlacesSidebar *sidebar,
GFile *location,
GtkPlacesOpenFlags open_flags)
{
if ((open_flags & sidebar->open_flags) == 0)
open_flags = GTK_PLACES_OPEN_NORMAL;
g_signal_emit (sidebar, places_sidebar_signals[OPEN_LOCATION], 0,
location, open_flags);
}
static void
emit_populate_popup (GtkPlacesSidebar *sidebar,
GtkMenu *menu,
GFile *selected_item,
GVolume *selected_volume)
{
g_signal_emit (sidebar, places_sidebar_signals[POPULATE_POPUP], 0,
menu, selected_item, selected_volume);
}
static void
emit_show_error_message (GtkPlacesSidebar *sidebar,
const gchar *primary,
const gchar *secondary)
{
g_signal_emit (sidebar, places_sidebar_signals[SHOW_ERROR_MESSAGE], 0,
primary, secondary);
}
static void
emit_show_connect_to_server (GtkPlacesSidebar *sidebar)
{
g_signal_emit (sidebar, places_sidebar_signals[SHOW_CONNECT_TO_SERVER], 0);
}
static void
emit_show_enter_location (GtkPlacesSidebar *sidebar)
{
g_signal_emit (sidebar, places_sidebar_signals[SHOW_ENTER_LOCATION], 0);
}
static GdkDragAction
emit_drag_action_requested (GtkPlacesSidebar *sidebar,
GdkDragContext *context,
GFile *dest_file,
GList *source_file_list)
{
GdkDragAction ret_action = 0;
g_signal_emit (sidebar, places_sidebar_signals[DRAG_ACTION_REQUESTED], 0,
context, dest_file, source_file_list, &ret_action);
return ret_action;
}
static GdkDragAction
emit_drag_action_ask (GtkPlacesSidebar *sidebar,
GdkDragAction actions)
{
GdkDragAction ret_action = 0;
g_signal_emit (sidebar, places_sidebar_signals[DRAG_ACTION_ASK], 0,
actions, &ret_action);
return ret_action;
}
static void
emit_drag_perform_drop (GtkPlacesSidebar *sidebar,
GFile *dest_file,
GList *source_file_list,
GdkDragAction action)
{
g_signal_emit (sidebar, places_sidebar_signals[DRAG_PERFORM_DROP], 0,
dest_file, source_file_list, action);
}
static gint
get_icon_size (GtkPlacesSidebar *sidebar)
{
gint width, height;
if (gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &width, &height))
return MAX (width, height);
else
return 16;
}
static GtkTreeIter
add_heading (GtkPlacesSidebar *sidebar,
SectionType section_type,
const gchar *title)
{
GtkTreeIter iter;
gtk_list_store_append (sidebar->store, &iter);
gtk_list_store_set (sidebar->store, &iter,
PLACES_SIDEBAR_COLUMN_ROW_TYPE, PLACES_HEADING,
PLACES_SIDEBAR_COLUMN_SECTION_TYPE, section_type,
PLACES_SIDEBAR_COLUMN_HEADING_TEXT, title,
PLACES_SIDEBAR_COLUMN_EJECT, FALSE,
PLACES_SIDEBAR_COLUMN_NO_EJECT, TRUE,
-1);
return iter;
}
static void
check_heading_for_section (GtkPlacesSidebar *sidebar,
SectionType section_type)
{
switch (section_type)
{
case SECTION_DEVICES:
if (!sidebar->devices_header_added)
{
add_heading (sidebar, SECTION_DEVICES, _("Devices"));
sidebar->devices_header_added = TRUE;
}
break;
case SECTION_BOOKMARKS:
if (!sidebar->bookmarks_header_added)
{
add_heading (sidebar, SECTION_BOOKMARKS, _("Bookmarks"));
sidebar->bookmarks_header_added = TRUE;
}
break;
default:
break;
}
}
static void
add_place (GtkPlacesSidebar *sidebar,
PlaceType place_type,
SectionType section_type,
const gchar *name,
GIcon *icon,
const gchar *uri,
GDrive *drive,
GVolume *volume,
GMount *mount,
const gint index,
const gchar *tooltip)
{
GtkTreeIter iter;
gboolean show_eject, show_unmount;
gboolean show_eject_button;
gchar *tooltip_escaped;
check_heading_for_section (sidebar, section_type);
check_unmount_and_eject (mount, volume, drive,
&show_unmount, &show_eject);
if (show_unmount || show_eject)
g_assert (place_type != PLACES_BOOKMARK);
if (mount == NULL)
show_eject_button = FALSE;
else
show_eject_button = (show_unmount || show_eject);
tooltip_escaped = g_markup_escape_text (tooltip, -1);
gtk_list_store_append (sidebar->store, &iter);
gtk_list_store_set (sidebar->store, &iter,
PLACES_SIDEBAR_COLUMN_GICON, icon,
PLACES_SIDEBAR_COLUMN_NAME, name,
PLACES_SIDEBAR_COLUMN_URI, uri,
PLACES_SIDEBAR_COLUMN_DRIVE, drive,
PLACES_SIDEBAR_COLUMN_VOLUME, volume,
PLACES_SIDEBAR_COLUMN_MOUNT, mount,
PLACES_SIDEBAR_COLUMN_ROW_TYPE, place_type,
PLACES_SIDEBAR_COLUMN_INDEX, index,
PLACES_SIDEBAR_COLUMN_EJECT, show_eject_button,
PLACES_SIDEBAR_COLUMN_NO_EJECT, !show_eject_button,
PLACES_SIDEBAR_COLUMN_BOOKMARK, place_type != PLACES_BOOKMARK,
PLACES_SIDEBAR_COLUMN_TOOLTIP, tooltip_escaped,
PLACES_SIDEBAR_COLUMN_SECTION_TYPE, section_type,
-1);
g_free (tooltip_escaped);
}
static GIcon *
special_directory_get_gicon (GUserDirectory directory)
{
#define ICON_CASE(x) \
case G_USER_DIRECTORY_ ## x: \
return g_themed_icon_new_with_default_fallbacks (ICON_NAME_FOLDER_ ## x);
switch (directory)
{
ICON_CASE (DESKTOP);
ICON_CASE (DOCUMENTS);
ICON_CASE (DOWNLOAD);
ICON_CASE (MUSIC);
ICON_CASE (PICTURES);
ICON_CASE (PUBLIC_SHARE);
ICON_CASE (TEMPLATES);
ICON_CASE (VIDEOS);
default:
return g_themed_icon_new_with_default_fallbacks (ICON_NAME_FOLDER);
}
#undef ICON_CASE
}
static gboolean
recent_files_setting_is_enabled (GtkPlacesSidebar *sidebar)
{
GtkSettings *settings;
gboolean enabled;
settings = gtk_widget_get_settings (GTK_WIDGET (sidebar));
g_object_get (settings, "gtk-recent-files-enabled", &enabled, NULL);
return enabled;
}
static gboolean
recent_scheme_is_supported (void)
{
const gchar * const *supported;
supported = g_vfs_get_supported_uri_schemes (g_vfs_get_default ());
if (supported != NULL)
return g_strv_contains (supported, "recent");
return FALSE;
}
static gboolean
should_show_recent (GtkPlacesSidebar *sidebar)
{
return recent_files_setting_is_enabled (sidebar) &&
((sidebar->show_recent_set && sidebar->show_recent) ||
(!sidebar->show_recent_set && recent_scheme_is_supported ()));
}
static gboolean
path_is_home_dir (const gchar *path)
{
GFile *home_dir;
GFile *location;
const gchar *home_path;
gboolean res;
home_path = g_get_home_dir ();
if (!home_path)
return FALSE;
home_dir = g_file_new_for_path (home_path);
location = g_file_new_for_path (path);
res = g_file_equal (home_dir, location);
g_object_unref (home_dir);
g_object_unref (location);
return res;
}
static void
open_home (GtkPlacesSidebar *sidebar)
{
const gchar *home_path;
GFile *home_dir;
home_path = g_get_home_dir ();
if (!home_path)
return;
home_dir = g_file_new_for_path (home_path);
emit_open_location (sidebar, home_dir, 0);
g_object_unref (home_dir);
}
static void
add_special_dirs (GtkPlacesSidebar *sidebar)
{
GList *dirs;
gint index;
dirs = NULL;
for (index = 0; index < G_USER_N_DIRECTORIES; index++)
{
const gchar *path;
GFile *root;
GIcon *icon;
gchar *name;
gchar *mount_uri;
gchar *tooltip;
if (!_gtk_bookmarks_manager_get_is_xdg_dir_builtin (index))
continue;
path = g_get_user_special_dir (index);
/* XDG resets special dirs to the home directory in case
* it's not finiding what it expects. We don't want the home
* to be added multiple times in that weird configuration.
*/
if (path == NULL ||
path_is_home_dir (path) ||
g_list_find_custom (dirs, path, (GCompareFunc) g_strcmp0) != NULL)
continue;
root = g_file_new_for_path (path);
name = _gtk_bookmarks_manager_get_bookmark_label (sidebar->bookmarks_manager, root);
if (!name)
name = g_file_get_basename (root);
icon = special_directory_get_gicon (index);
mount_uri = g_file_get_uri (root);
tooltip = g_file_get_parse_name (root);
add_place (sidebar, PLACES_XDG_DIR,
SECTION_COMPUTER,
name, icon, mount_uri,
NULL, NULL, NULL, 0,
tooltip);
g_free (name);
g_object_unref (root);
g_object_unref (icon);
g_free (mount_uri);
g_free (tooltip);
dirs = g_list_prepend (dirs, (gchar *)path);
}
g_list_free (dirs);
}
static gchar *
get_home_directory_uri (void)
{
const gchar *home;
home = g_get_home_dir ();
if (!home)
return NULL;
return g_filename_to_uri (home, NULL, NULL);
}
static gchar *
get_desktop_directory_uri (void)
{
const gchar *name;
name = g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP);
/* "To disable a directory, point it to the homedir."
* See http://freedesktop.org/wiki/Software/xdg-user-dirs
*/
if (path_is_home_dir (name))
return NULL;
return g_filename_to_uri (name, NULL, NULL);
}
static gboolean
should_show_file (GtkPlacesSidebar *sidebar,
GFile *file)
{
gchar *path;
if (!sidebar->local_only)
return TRUE;
path = g_file_get_path (file);
if (path)
{
g_free (path);
return TRUE;
}
return FALSE;
}
static gboolean
file_is_shown (GtkPlacesSidebar *sidebar,
GFile *file)
{
GtkTreeIter iter;
gchar *uri;
if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (sidebar->store), &iter))
return FALSE;
do
{
gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store), &iter,
PLACES_SIDEBAR_COLUMN_URI, &uri,
-1);
if (uri)
{
GFile *other;
gboolean found;
other = g_file_new_for_uri (uri);
g_free (uri);
found = g_file_equal (file, other);
g_object_unref (other);
if (found)
return TRUE;
}
}
while (gtk_tree_model_iter_next (GTK_TREE_MODEL (sidebar->store), &iter));
return FALSE;
}
static void
on_app_shortcuts_query_complete (GObject *source,
GAsyncResult *result,
gpointer data)
{
GtkPlacesSidebar *sidebar = data;
GFile *file = G_FILE (source);
GFileInfo *info;
info = g_file_query_info_finish (file, result, NULL);
if (info)
{
gchar *uri;
gchar *tooltip;
const gchar *name;
GIcon *icon;
int pos = 0;
name = g_file_info_get_display_name (info);
icon = g_file_info_get_symbolic_icon (info);
uri = g_file_get_uri (file);
tooltip = g_file_get_parse_name (file);
/* XXX: we could avoid this by using an ancillary closure
* with the index coming from add_application_shortcuts(),
* but in terms of algorithmic overhead, the application
* shortcuts is not going to be really big
*/
pos = g_slist_index (sidebar->shortcuts, file);
add_place (sidebar, PLACES_BUILT_IN,
SECTION_COMPUTER,
name, icon, uri,
NULL, NULL, NULL,
pos,
tooltip);
g_free (uri);
g_free (tooltip);
g_object_unref (info);
}
}
static void
add_application_shortcuts (GtkPlacesSidebar *sidebar)
{
GSList *l;
for (l = sidebar->shortcuts; l; l = l->next)
{
GFile *file = l->data;
if (!should_show_file (sidebar, file))
continue;
if (file_is_shown (sidebar, file))
continue;
g_file_query_info_async (file,
"standard::display-name,standard::symbolic-icon",
G_FILE_QUERY_INFO_NONE,
G_PRIORITY_DEFAULT,
sidebar->cancellable,
on_app_shortcuts_query_complete,
sidebar);
}
}
static gboolean
get_selected_iter (GtkPlacesSidebar *sidebar,
GtkTreeIter *iter)
{
GtkTreeSelection *selection;
selection = gtk_tree_view_get_selection (sidebar->tree_view);
return gtk_tree_selection_get_selected (selection, NULL, iter);
}
typedef struct {
GtkPlacesSidebar *sidebar;
int index;
gboolean is_native;
} BookmarkQueryClosure;
static void
on_bookmark_query_info_complete (GObject *source,
GAsyncResult *result,
gpointer data)
{
BookmarkQueryClosure *clos = data;
GtkPlacesSidebar *sidebar = clos->sidebar;
GFile *root = G_FILE (source);
GError *error = NULL;
GFileInfo *info;
gchar *bookmark_name;
gchar *mount_uri;
gchar *tooltip;
GIcon *icon;
info = g_file_query_info_finish (root, result, &error);
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
goto out;
bookmark_name = _gtk_bookmarks_manager_get_bookmark_label (sidebar->bookmarks_manager, root);
if (bookmark_name == NULL && info != NULL)
bookmark_name = g_strdup (g_file_info_get_display_name (info));
else if (bookmark_name == NULL)
{
/* Don't add non-UTF-8 bookmarks */
bookmark_name = g_file_get_basename (root);
if (!g_utf8_validate (bookmark_name, -1, NULL))
{
g_free (bookmark_name);
goto out;
}
}
if (info)
icon = g_object_ref (g_file_info_get_symbolic_icon (info));
else
icon = g_themed_icon_new_with_default_fallbacks (clos->is_native ? ICON_NAME_FOLDER : ICON_NAME_FOLDER_NETWORK);
mount_uri = g_file_get_uri (root);
tooltip = g_file_get_parse_name (root);
add_place (sidebar, PLACES_BOOKMARK,
SECTION_BOOKMARKS,
bookmark_name, icon, mount_uri,
NULL, NULL, NULL, clos->index,
tooltip);
g_free (mount_uri);
g_free (tooltip);
g_free (bookmark_name);
g_object_unref (icon);
out:
g_clear_object (&info);
g_clear_error (&error);
g_slice_free (BookmarkQueryClosure, clos);
}
static void
update_places (GtkPlacesSidebar *sidebar)
{
GtkTreeIter iter;
GVolumeMonitor *volume_monitor;
GList *mounts, *l, *ll;
GMount *mount;
GList *drives;
GDrive *drive;
GList *volumes;
GVolume *volume;
GSList *bookmarks, *sl;
gint index;
gchar *original_uri, *mount_uri, *name, *identifier;
gchar *home_uri;
GIcon *icon;
GFile *root;
gchar *tooltip;
GList *network_mounts, *network_volumes;
/* save original selection */
if (get_selected_iter (sidebar, &iter))
gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store),
&iter,
PLACES_SIDEBAR_COLUMN_URI, &original_uri, -1);
else
original_uri = NULL;
g_cancellable_cancel (sidebar->cancellable);
g_object_unref (sidebar->cancellable);
sidebar->cancellable = g_cancellable_new ();
gtk_list_store_clear (sidebar->store);
sidebar->devices_header_added = FALSE;
sidebar->bookmarks_header_added = FALSE;
network_mounts = network_volumes = NULL;
volume_monitor = sidebar->volume_monitor;
/* add built-in bookmarks */
if (should_show_recent (sidebar))
{
mount_uri = "recent:///";
icon = g_themed_icon_new_with_default_fallbacks ("document-open-recent-symbolic");
add_place (sidebar, PLACES_BUILT_IN,
SECTION_COMPUTER,
_("Recent"), icon, mount_uri,
NULL, NULL, NULL, 0,
_("Recent files"));
g_object_unref (icon);
}
/* home folder */
home_uri = get_home_directory_uri ();
icon = g_themed_icon_new_with_default_fallbacks (ICON_NAME_HOME);
add_place (sidebar, PLACES_BUILT_IN,
SECTION_COMPUTER,
_("Home"), icon, home_uri,
NULL, NULL, NULL, 0,
_("Open your personal folder"));
g_object_unref (icon);
g_free (home_uri);
/* desktop */
if (sidebar->show_desktop)
{
mount_uri = get_desktop_directory_uri ();
if (mount_uri)
{
icon = g_themed_icon_new_with_default_fallbacks (ICON_NAME_DESKTOP);
add_place (sidebar, PLACES_BUILT_IN,
SECTION_COMPUTER,
_("Desktop"), icon, mount_uri,
NULL, NULL, NULL, 0,
_("Open the contents of your desktop in a folder"));
g_object_unref (icon);
g_free (mount_uri);
}
}
/* XDG directories */
add_special_dirs (sidebar);
if (sidebar->show_enter_location)
{
icon = g_themed_icon_new_with_default_fallbacks (ICON_NAME_NETWORK_SERVER);
add_place (sidebar, PLACES_ENTER_LOCATION,
SECTION_COMPUTER,
_("Enter Location"), icon, NULL,
NULL, NULL, NULL, 0,
_("Manually enter a location"));
g_object_unref (icon);
}
/* Trash */
if (!sidebar->local_only)
{
mount_uri = "trash:///"; /* No need to strdup */
icon = _gtk_trash_monitor_get_icon (sidebar->trash_monitor);
add_place (sidebar, PLACES_BUILT_IN,
SECTION_COMPUTER,
_("Trash"), icon, mount_uri,
NULL, NULL, NULL, 0,
_("Open the trash"));
g_object_unref (icon);
}
/* Application-side shortcuts */
add_application_shortcuts (sidebar);
/* go through all connected drives */
drives = g_volume_monitor_get_connected_drives (volume_monitor);
for (l = drives; l != NULL; l = l->next)
{
drive = l->data;
volumes = g_drive_get_volumes (drive);
if (volumes != NULL)
{
for (ll = volumes; ll != NULL; ll = ll->next)
{
volume = ll->data;
identifier = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_CLASS);
if (g_strcmp0 (identifier, "network") == 0)
{
g_free (identifier);
network_volumes = g_list_prepend (network_volumes, volume);
continue;
}
g_free (identifier);
mount = g_volume_get_mount (volume);
if (mount != NULL)
{
/* Show mounted volume in the sidebar */
icon = g_mount_get_symbolic_icon (mount);
root = g_mount_get_default_location (mount);
mount_uri = g_file_get_uri (root);
name = g_mount_get_name (mount);
tooltip = g_file_get_parse_name (root);
add_place (sidebar, PLACES_MOUNTED_VOLUME,
SECTION_DEVICES,
name, icon, mount_uri,
drive, volume, mount, 0, tooltip);
g_object_unref (root);
g_object_unref (mount);
g_object_unref (icon);
g_free (tooltip);
g_free (name);
g_free (mount_uri);
}
else
{
/* Do show the unmounted volumes in the sidebar;
* this is so the user can mount it (in case automounting
* is off).
*
* Also, even if automounting is enabled, this gives a visual
* cue that the user should remember to yank out the media if
* he just unmounted it.
*/
icon = g_volume_get_symbolic_icon (volume);
name = g_volume_get_name (volume);
tooltip = g_strdup_printf (_("Mount and open “%s”"), name);
add_place (sidebar, PLACES_MOUNTED_VOLUME,
SECTION_DEVICES,
name, icon, NULL,
drive, volume, NULL, 0, tooltip);
g_object_unref (icon);
g_free (name);
g_free (tooltip);
}
g_object_unref (volume);
}
g_list_free (volumes);
}
else
{
if (g_drive_is_media_removable (drive) && !g_drive_is_media_check_automatic (drive))
{
/* If the drive has no mountable volumes and we cannot detect media change.. we
* display the drive in the sidebar so the user can manually poll the drive by
* right clicking and selecting "Rescan..."
*
* This is mainly for drives like floppies where media detection doesn't
* work.. but it's also for human beings who like to turn off media detection
* in the OS to save battery juice.
*/
icon = g_drive_get_symbolic_icon (drive);
name = g_drive_get_name (drive);
tooltip = g_strdup_printf (_("Mount and open “%s”"), name);
add_place (sidebar, PLACES_BUILT_IN,
SECTION_DEVICES,
name, icon, NULL,
drive, NULL, NULL, 0, tooltip);
g_object_unref (icon);
g_free (tooltip);
g_free (name);
}
}
g_object_unref (drive);
}
g_list_free (drives);
/* add all volumes that is not associated with a drive */
volumes = g_volume_monitor_get_volumes (volume_monitor);
for (l = volumes; l != NULL; l = l->next)
{
volume = l->data;
drive = g_volume_get_drive (volume);
if (drive != NULL)
{
g_object_unref (volume);
g_object_unref (drive);
continue;
}
identifier = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_CLASS);
if (g_strcmp0 (identifier, "network") == 0)
{
g_free (identifier);
network_volumes = g_list_prepend (network_volumes, volume);
continue;
}
g_free (identifier);
mount = g_volume_get_mount (volume);
if (mount != NULL)
{
icon = g_mount_get_symbolic_icon (mount);
root = g_mount_get_default_location (mount);
mount_uri = g_file_get_uri (root);
tooltip = g_file_get_parse_name (root);
name = g_mount_get_name (mount);
add_place (sidebar, PLACES_MOUNTED_VOLUME,
SECTION_DEVICES,
name, icon, mount_uri,
NULL, volume, mount, 0, tooltip);
g_object_unref (mount);
g_object_unref (root);
g_object_unref (icon);
g_free (name);
g_free (tooltip);
g_free (mount_uri);
}
else
{
/* see comment above in why we add an icon for an unmounted mountable volume */
icon = g_volume_get_symbolic_icon (volume);
name = g_volume_get_name (volume);
add_place (sidebar, PLACES_MOUNTED_VOLUME,
SECTION_DEVICES,
name, icon, NULL,
NULL, volume, NULL, 0, name);
g_object_unref (icon);
g_free (name);
}
g_object_unref (volume);
}
g_list_free (volumes);
/* file system root */
mount_uri = "file:///"; /* No need to strdup */
icon = g_themed_icon_new_with_default_fallbacks (ICON_NAME_FILESYSTEM);
add_place (sidebar, PLACES_BUILT_IN,
SECTION_DEVICES,
sidebar->hostname, icon, mount_uri,
NULL, NULL, NULL, 0,
_("Open the contents of the file system"));
g_object_unref (icon);
/* add mounts that has no volume (/etc/mtab mounts, ftp, sftp,...) */
mounts = g_volume_monitor_get_mounts (volume_monitor);
for (l = mounts; l != NULL; l = l->next)
{
mount = l->data;
if (g_mount_is_shadowed (mount))
{
g_object_unref (mount);
continue;
}
volume = g_mount_get_volume (mount);
if (volume != NULL)
{
g_object_unref (volume);
g_object_unref (mount);
continue;
}
root = g_mount_get_default_location (mount);
if (!g_file_is_native (root))
{
network_mounts = g_list_prepend (network_mounts, mount);
g_object_unref (root);
continue;
}
icon = g_mount_get_symbolic_icon (mount);
mount_uri = g_file_get_uri (root);
name = g_mount_get_name (mount);
tooltip = g_file_get_parse_name (root);
add_place (sidebar, PLACES_MOUNTED_VOLUME,
SECTION_COMPUTER,
name, icon, mount_uri,
NULL, NULL, mount, 0, tooltip);
g_object_unref (root);
g_object_unref (mount);
g_object_unref (icon);
g_free (name);
g_free (mount_uri);
g_free (tooltip);
}
g_list_free (mounts);
/* add bookmarks */
bookmarks = _gtk_bookmarks_manager_list_bookmarks (sidebar->bookmarks_manager);
for (sl = bookmarks, index = 0; sl; sl = sl->next, index++)
{
gboolean is_native;
BookmarkQueryClosure *clos;
root = sl->data;
is_native = g_file_is_native (root);
#if 0
/* FIXME: remove this? If we *do* show bookmarks for nonexistent files, the user will eventually clean them up */
if (!nautilus_bookmark_get_exists (bookmark))
continue;
#endif
if (_gtk_bookmarks_manager_get_is_builtin (sidebar->bookmarks_manager, root))
continue;
if (sidebar->local_only && !is_native)
continue;
clos = g_slice_new (BookmarkQueryClosure);
clos->sidebar = sidebar;
clos->index = index;
clos->is_native = is_native;
g_file_query_info_async (root,
"standard::display-name,standard::symbolic-icon",
G_FILE_QUERY_INFO_NONE,
G_PRIORITY_DEFAULT,
sidebar->cancellable,
on_bookmark_query_info_complete,
clos);
}
g_slist_foreach (bookmarks, (GFunc) g_object_unref, NULL);
g_slist_free (bookmarks);
/* network */
if (!sidebar->local_only)
{
add_heading (sidebar, SECTION_NETWORK, _("Network"));
mount_uri = "network:///";
icon = g_themed_icon_new_with_default_fallbacks (ICON_NAME_NETWORK);
add_place (sidebar, PLACES_BUILT_IN,
SECTION_NETWORK,
_("Browse Network"), icon, mount_uri,
NULL, NULL, NULL, 0,
_("Browse the contents of the network"));
g_object_unref (icon);
if (sidebar->show_connect_to_server)
{
icon = g_themed_icon_new_with_default_fallbacks (ICON_NAME_NETWORK_SERVER);
add_place (sidebar, PLACES_CONNECT_TO_SERVER,
SECTION_NETWORK,
_("Connect to Server"), icon, NULL,
NULL, NULL, NULL, 0,
_("Connect to a network server address"));
g_object_unref (icon);
}
network_volumes = g_list_reverse (network_volumes);
for (l = network_volumes; l != NULL; l = l->next)
{
volume = l->data;
mount = g_volume_get_mount (volume);
if (mount != NULL)
{
network_mounts = g_list_prepend (network_mounts, mount);
continue;
}
else
{
icon = g_volume_get_symbolic_icon (volume);
name = g_volume_get_name (volume);
tooltip = g_strdup_printf (_("Mount and open “%s”"), name);
add_place (sidebar, PLACES_MOUNTED_VOLUME,
SECTION_NETWORK,
name, icon, NULL,
NULL, volume, NULL, 0, tooltip);
g_object_unref (icon);
g_free (name);
g_free (tooltip);
}
}
network_mounts = g_list_reverse (network_mounts);
for (l = network_mounts; l != NULL; l = l->next)
{
mount = l->data;
root = g_mount_get_default_location (mount);
icon = g_mount_get_symbolic_icon (mount);
mount_uri = g_file_get_uri (root);
name = g_mount_get_name (mount);
tooltip = g_file_get_parse_name (root);
add_place (sidebar, PLACES_MOUNTED_VOLUME,
SECTION_NETWORK,
name, icon, mount_uri,
NULL, NULL, mount, 0, tooltip);
g_object_unref (root);
g_object_unref (icon);
g_free (name);
g_free (mount_uri);
g_free (tooltip);
}
}
g_list_free_full (network_volumes, g_object_unref);
g_list_free_full (network_mounts, g_object_unref);
/* restore original selection */
if (original_uri)
{
GFile *restore;
restore = g_file_new_for_uri (original_uri);
gtk_places_sidebar_set_location (sidebar, restore);
g_object_unref (restore);
g_free (original_uri);
}
}
static gboolean
over_eject_button (GtkPlacesSidebar *sidebar,
gint x,
gint y,
GtkTreePath **path)
{
GtkTreeViewColumn *column;
gint width, x_offset, hseparator;
gint eject_button_size;
gboolean show_eject;
GtkTreeIter iter;
GtkTreeModel *model;
*path = NULL;
model = gtk_tree_view_get_model (sidebar->tree_view);
if (gtk_tree_view_get_path_at_pos (sidebar->tree_view,
x, y, path, &column, NULL, NULL))
{
gtk_tree_model_get_iter (model, &iter, *path);
gtk_tree_model_get (model, &iter,
PLACES_SIDEBAR_COLUMN_EJECT, &show_eject,
-1);
if (!show_eject)
goto out;
gtk_widget_style_get (GTK_WIDGET (sidebar->tree_view),
"horizontal-separator", &hseparator,
NULL);
/* Reload cell attributes for this particular row */
gtk_tree_view_column_cell_set_cell_data (column,
model, &iter, FALSE, FALSE);
gtk_tree_view_column_cell_get_position (column,
sidebar->eject_icon_cell_renderer,
&x_offset, &width);
eject_button_size = get_icon_size (sidebar);
/* This is kinda weird, but we have to do it to workaround expanding
* the eject cell renderer (even thought we told it not to) and we
* then had to set it right-aligned
*/
x_offset += width - hseparator - EJECT_BUTTON_XPAD - eject_button_size;
if (x - x_offset >= 0 && x - x_offset <= eject_button_size)
return TRUE;
}
out:
g_clear_pointer (path, gtk_tree_path_free);
return FALSE;
}
static gboolean
clicked_eject_button (GtkPlacesSidebar *sidebar,
GtkTreePath **path)
{
GdkEvent *event;
event = gtk_get_current_event ();
if (event &&
(event->type == GDK_BUTTON_PRESS || event->type == GDK_BUTTON_RELEASE) &&
over_eject_button (sidebar, ((GdkEventButton *)event)->x, ((GdkEventButton *)event)->y, path))
return TRUE;
return FALSE;
}
static gboolean
pos_is_into_or_before (GtkTreeViewDropPosition pos)
{
return (pos == GTK_TREE_VIEW_DROP_BEFORE || pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
}
/* Computes the appropriate row and position for dropping */
static gboolean
compute_drop_position (GtkTreeView *tree_view,
gint x,
gint y,
GtkTreePath **path,
GtkTreeViewDropPosition *pos,
GtkPlacesSidebar *sidebar)
{
GtkTreeModel *model;
GtkTreeIter iter;
PlaceType place_type;
SectionType section_type;
gboolean drop_possible;
if (!gtk_tree_view_get_dest_row_at_pos (tree_view, x, y, path, pos))
return FALSE;
model = gtk_tree_view_get_model (tree_view);
gtk_tree_model_get_iter (model, &iter, *path);
gtk_tree_model_get (model, &iter,
PLACES_SIDEBAR_COLUMN_ROW_TYPE, &place_type,
PLACES_SIDEBAR_COLUMN_SECTION_TYPE, &section_type,
-1);
drop_possible = TRUE;
/* Normalize drops on the feedback row */
if (place_type == PLACES_DROP_FEEDBACK)
{
*pos = GTK_TREE_VIEW_DROP_INTO_OR_BEFORE;
goto out;
}
/* Never drop on headings, but special case the bookmarks heading,
* so we can drop bookmarks in between it and the first bookmark.
*/
if (place_type == PLACES_HEADING && section_type != SECTION_BOOKMARKS)
drop_possible = FALSE;
/* Dragging a bookmark? */
if (sidebar->drag_data_received &&
sidebar->drag_data_info == GTK_TREE_MODEL_ROW)
{
/* Don't allow reordering bookmarks into non-bookmark areas */
if (section_type != SECTION_BOOKMARKS)
drop_possible = FALSE;
/* Bookmarks can only be reordered. Disallow dropping directly
* into them; only allow dropping between them.
*/
if (place_type == PLACES_HEADING)
{
if (pos_is_into_or_before (*pos))
drop_possible = FALSE;
else
*pos = GTK_TREE_VIEW_DROP_AFTER;
}
else
{
if (pos_is_into_or_before (*pos))
*pos = GTK_TREE_VIEW_DROP_BEFORE;
else
*pos = GTK_TREE_VIEW_DROP_AFTER;
}
}
else
{
/* Dragging a file */
/* Outside the bookmarks section, URIs can only be dropped
* directly into places items. Inside the bookmarks section,
* they can be dropped between items (to create new bookmarks)
* or in items themselves (to request a move/copy file
* operation).
*/
if (section_type != SECTION_BOOKMARKS)
*pos = GTK_TREE_VIEW_DROP_INTO_OR_BEFORE;
else
{
if (place_type == PLACES_HEADING)
{
if (pos_is_into_or_before (*pos))
drop_possible = FALSE;
else
*pos = GTK_TREE_VIEW_DROP_AFTER;
}
}
}
/* Disallow drops on recent:/// */
if (place_type == PLACES_BUILT_IN)
{
gchar *uri;
gtk_tree_model_get (model, &iter,
PLACES_SIDEBAR_COLUMN_URI, &uri,
-1);
if (g_strcmp0 (uri, "recent:///") == 0)
drop_possible = FALSE;
g_free (uri);
}
out:
if (!drop_possible)
{
gtk_tree_path_free (*path);
*path = NULL;
return FALSE;
}
return TRUE;
}
static gboolean
get_drag_data (GtkTreeView *tree_view,
GdkDragContext *context,
guint time)
{
GdkAtom target;
target = gtk_drag_dest_find_target (GTK_WIDGET (tree_view),
context,
NULL);
if (target == GDK_NONE)
return FALSE;
gtk_drag_get_data (GTK_WIDGET (tree_view),
context, target, time);
return TRUE;
}
static void
remove_switch_location_timer (GtkPlacesSidebar *sidebar)
{
if (sidebar->switch_location_timer != 0)
{
g_source_remove (sidebar->switch_location_timer);
sidebar->switch_location_timer = 0;
}
}
static void
free_drag_data (GtkPlacesSidebar *sidebar)
{
sidebar->drag_data_received = FALSE;
if (sidebar->drag_list)
{
g_list_free_full (sidebar->drag_list, g_object_unref);
sidebar->drag_list = NULL;
}
remove_switch_location_timer (sidebar);
g_free (sidebar->drop_target_uri);
sidebar->drop_target_uri = NULL;
}
static gboolean
switch_location_timer (gpointer user_data)
{
GtkPlacesSidebar *sidebar = GTK_PLACES_SIDEBAR (user_data);
GFile *location;
sidebar->switch_location_timer = 0;
location = g_file_new_for_uri (sidebar->drop_target_uri);
emit_open_location (sidebar, location, 0);
g_object_unref (location);
return FALSE;
}
static void
check_switch_location_timer (GtkPlacesSidebar *sidebar,
const gchar *uri)
{
if (g_strcmp0 (uri, sidebar->drop_target_uri) == 0)
return;
remove_switch_location_timer (sidebar);
g_free (sidebar->drop_target_uri);
sidebar->drop_target_uri = NULL;
if (uri != NULL)
{
sidebar->drop_target_uri = g_strdup (uri);
sidebar->switch_location_timer = gdk_threads_add_timeout (TIMEOUT_EXPAND, switch_location_timer, sidebar);
g_source_set_name_by_id (sidebar->switch_location_timer, "[gtk+] switch_location_timer");
}
}
static void
remove_drop_bookmark_feedback_row (GtkPlacesSidebar *sidebar)
{
if (sidebar->drop_state != DROP_STATE_NORMAL)
{
gboolean success;
GtkTreeIter iter;
success = gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (sidebar->store), &iter, NULL, sidebar->new_bookmark_index);
g_assert (success);
gtk_list_store_remove (sidebar->store, &iter);
sidebar->drop_state = DROP_STATE_NORMAL;
}
}
static void
start_drop_feedback (GtkPlacesSidebar *sidebar,
GtkTreePath *path,
GtkTreeViewDropPosition pos,
gboolean drop_as_bookmarks)
{
if (drop_as_bookmarks)
{
gint new_bookmark_index;
GtkTreePath *new_path;
gboolean need_feedback_row;
new_bookmark_index = gtk_tree_path_get_indices (path)[0];
if (pos == GTK_TREE_VIEW_DROP_AFTER)
new_bookmark_index++;
if (sidebar->drop_state == DROP_STATE_NORMAL)
need_feedback_row = TRUE;
else
{
/* Feedback row already exists; remove it if its position needs to change */
if (sidebar->new_bookmark_index == new_bookmark_index)
need_feedback_row = FALSE;
else
{
if (sidebar->new_bookmark_index < new_bookmark_index)
new_bookmark_index--; /* since the removal of the old feedback row pushed items one position up */
remove_drop_bookmark_feedback_row (sidebar);
need_feedback_row = TRUE;
}
}
if (need_feedback_row)
{
GtkTreeIter iter;
GtkTreeIter iter_prev;
GtkTreePath *path_prev;
gint new_bookmark_col_index;
SectionType section_type;
/* Use column index of previous bookmark to calculate index for "new bookmark" */
path_prev = gtk_tree_path_new_from_indices (sidebar->new_bookmark_index - 1, -1);
if (gtk_tree_model_get_iter (GTK_TREE_MODEL (sidebar->store), &iter_prev, path_prev))
gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store), &iter_prev,
PLACES_SIDEBAR_COLUMN_SECTION_TYPE, &section_type,
PLACES_SIDEBAR_COLUMN_INDEX, &new_bookmark_col_index, -1);
if (section_type != SECTION_BOOKMARKS)
new_bookmark_col_index = 0;
else
new_bookmark_col_index++;
sidebar->new_bookmark_index = new_bookmark_index;
gtk_list_store_insert_with_values (sidebar->store, &iter, sidebar->new_bookmark_index,
PLACES_SIDEBAR_COLUMN_ROW_TYPE, PLACES_DROP_FEEDBACK,
PLACES_SIDEBAR_COLUMN_SECTION_TYPE, SECTION_BOOKMARKS,
PLACES_SIDEBAR_COLUMN_NAME, _("New bookmark"),
PLACES_SIDEBAR_COLUMN_INDEX, new_bookmark_col_index,
PLACES_SIDEBAR_COLUMN_NO_EJECT, TRUE,
-1);
gtk_tree_path_free (path_prev);
}
new_path = gtk_tree_path_new_from_indices (sidebar->new_bookmark_index, -1);
gtk_tree_view_set_drag_dest_row (sidebar->tree_view, new_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
gtk_tree_path_free (new_path);
sidebar->drop_state = DROP_STATE_NEW_BOOKMARK_ARMED;
}
else
gtk_tree_view_set_drag_dest_row (sidebar->tree_view, path, pos);
}
static void
stop_drop_feedback (GtkPlacesSidebar *sidebar)
{
gtk_tree_view_set_drag_dest_row (sidebar->tree_view, NULL, 0);
}
static gboolean
drag_motion_callback (GtkTreeView *tree_view,
GdkDragContext *context,
gint x,
gint y,
guint time,
GtkPlacesSidebar *sidebar)
{
GtkTreePath *path;
GtkTreeViewDropPosition pos;
gint action;
GtkTreeIter iter;
gboolean res;
gboolean drop_as_bookmarks;
gchar *drop_target_uri = NULL;
action = 0;
drop_as_bookmarks = FALSE;
path = NULL;
if (!sidebar->drag_data_received)
{
if (!get_drag_data (tree_view, context, time))
goto out;
}
res = compute_drop_position (tree_view, x, y, &path, &pos, sidebar);
if (!res)
goto out;
if (sidebar->drag_data_received &&
sidebar->drag_data_info == GTK_TREE_MODEL_ROW)
{
/* Dragging bookmarks always moves them to another position in the bookmarks list */
action = GDK_ACTION_MOVE;
}
else
{
/* URIs are being dragged. See if the caller wants to handle a
* file move/copy operation itself, or if we should only try to
* create bookmarks out of the dragged URIs.
*/
if (sidebar->drag_list != NULL)
{
SectionType section_type;
PlaceType place_type;
gtk_tree_model_get_iter (GTK_TREE_MODEL (sidebar->store), &iter, path);
gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store),
&iter,
PLACES_SIDEBAR_COLUMN_SECTION_TYPE, &section_type,
PLACES_SIDEBAR_COLUMN_ROW_TYPE, &place_type,
-1);
if (place_type == PLACES_DROP_FEEDBACK ||
(section_type == SECTION_BOOKMARKS &&
(pos == GTK_TREE_VIEW_DROP_BEFORE || pos == GTK_TREE_VIEW_DROP_AFTER)))
{
action = GDK_ACTION_COPY;
drop_as_bookmarks = TRUE;
}
if (!drop_as_bookmarks)
{
gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store),
&iter,
PLACES_SIDEBAR_COLUMN_URI, &drop_target_uri,
-1);
if (drop_target_uri != NULL)
{
GFile *dest_file = g_file_new_for_uri (drop_target_uri);
action = emit_drag_action_requested (sidebar, context, dest_file, sidebar->drag_list);
g_object_unref (dest_file);
} /* uri may be NULL for unmounted volumes, for example, so we don't allow drops there */
}
}
}
out:
if (action != 0)
{
check_switch_location_timer (sidebar, drop_target_uri);
start_drop_feedback (sidebar, path, pos, drop_as_bookmarks);
}
else
{
remove_switch_location_timer (sidebar);
stop_drop_feedback (sidebar);
}
g_free (drop_target_uri);
if (path != NULL)
gtk_tree_path_free (path);
g_signal_stop_emission_by_name (tree_view, "drag-motion");
gdk_drag_status (context, action, time);
return TRUE;
}
static gboolean
drag_leave_timeout_cb (gpointer data)
{
GtkPlacesSidebar *sidebar = GTK_PLACES_SIDEBAR (data);
free_drag_data (sidebar);
stop_drop_feedback (sidebar);
remove_drop_bookmark_feedback_row (sidebar);
sidebar->drag_leave_timeout_id = 0;
return FALSE;
}
static void
drag_leave_callback (GtkTreeView *tree_view,
GdkDragContext *context,
guint time,
GtkPlacesSidebar *sidebar)
{
if (sidebar->drag_leave_timeout_id)
g_source_remove (sidebar->drag_leave_timeout_id);
sidebar->drag_leave_timeout_id = gdk_threads_add_timeout (500, drag_leave_timeout_cb, sidebar);
g_source_set_name_by_id (sidebar->drag_leave_timeout_id, "[gtk+] drag_leave_timeout_cb");
remove_switch_location_timer (sidebar);
g_signal_stop_emission_by_name (tree_view, "drag-leave");
}
/* Takes an array of URIs and turns it into a list of GFile */
static GList *
build_file_list_from_uris (const gchar **uris)
{
GList *result;
gint i;
result = NULL;
for (i = 0; uris[i]; i++)
{
GFile *file;
file = g_file_new_for_uri (uris[i]);
result = g_list_prepend (result, file);
}
return g_list_reverse (result);
}
/* Reorders the selected bookmark to the specified position */
static void
reorder_bookmarks (GtkPlacesSidebar *sidebar,
gint new_position)
{
GtkTreeIter iter;
gchar *uri;
GFile *file;
if (!get_selected_iter (sidebar, &iter))
return;
gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store), &iter,
PLACES_SIDEBAR_COLUMN_URI, &uri,
-1);
file = g_file_new_for_uri (uri);
_gtk_bookmarks_manager_reorder_bookmark (sidebar->bookmarks_manager, file, new_position, NULL); /* NULL-GError */
g_object_unref (file);
g_free (uri);
}
/* Creates bookmarks for the specified files at the given position in the bookmarks list */
static void
drop_files_as_bookmarks (GtkPlacesSidebar *sidebar,
GList *files,
gint position)
{
GList *l;
for (l = files; l; l = l->next)
{
GFile *f = G_FILE (l->data);
GFileInfo *info = g_file_query_info (f,
G_FILE_ATTRIBUTE_STANDARD_TYPE,
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
NULL,
NULL);
if (info)
{
if (_gtk_file_info_consider_as_directory (info))
_gtk_bookmarks_manager_insert_bookmark (sidebar->bookmarks_manager, f, position++, NULL); /* NULL-GError */
g_object_unref (info);
}
}
}
static void
drag_data_received_callback (GtkWidget *widget,
GdkDragContext *context,
int x,
int y,
GtkSelectionData *selection_data,
guint info,
guint time,
GtkPlacesSidebar *sidebar)
{
GtkTreeView *tree_view;
GtkTreePath *tree_path;
GtkTreeViewDropPosition tree_pos;
GtkTreeIter iter;
gint position;
GtkTreeModel *model;
PlaceType place_type;
SectionType section_type;
gboolean success;
tree_view = GTK_TREE_VIEW (widget);
if (!sidebar->drag_data_received)
{
if (gtk_selection_data_get_target (selection_data) != GDK_NONE &&
info == TEXT_URI_LIST)
{
gchar **uris;
uris = gtk_selection_data_get_uris (selection_data);
sidebar->drag_list = build_file_list_from_uris ((const char **) uris);
g_strfreev (uris);
}
else
{
sidebar->drag_list = NULL;
}
sidebar->drag_data_received = TRUE;
sidebar->drag_data_info = info;
}
g_signal_stop_emission_by_name (widget, "drag-data-received");
if (!sidebar->drop_occured)
return;
/* Compute position */
success = compute_drop_position (tree_view, x, y, &tree_path, &tree_pos, sidebar);
if (!success)
goto out;
success = FALSE;
if (sidebar->drag_data_info == GTK_TREE_MODEL_ROW)
{
/* A bookmark got reordered */
model = gtk_tree_view_get_model (tree_view);
if (!gtk_tree_model_get_iter (model, &iter, tree_path))
goto out;
gtk_tree_model_get (model, &iter,
PLACES_SIDEBAR_COLUMN_SECTION_TYPE, &section_type,
PLACES_SIDEBAR_COLUMN_ROW_TYPE, &place_type,
PLACES_SIDEBAR_COLUMN_INDEX, &position,
-1);
if (section_type != SECTION_BOOKMARKS)
goto out;
if (place_type == PLACES_HEADING)
position = 0;
else if (tree_pos == GTK_TREE_VIEW_DROP_AFTER)
position++;
reorder_bookmarks (sidebar, position);
success = TRUE;
}
else
{
/* Dropping URIs! */
GdkDragAction real_action;
gchar **uris;
GList *source_file_list;
/* file transfer requested */
real_action = gdk_drag_context_get_selected_action (context);
if (real_action == GDK_ACTION_ASK)
real_action = emit_drag_action_ask (sidebar, gdk_drag_context_get_actions (context));
if (real_action > 0)
{
gchar *uri;
GFile *dest_file;
gboolean drop_as_bookmarks;
model = gtk_tree_view_get_model (tree_view);
gtk_tree_model_get_iter (model, &iter, tree_path);
gtk_tree_model_get (model, &iter,
PLACES_SIDEBAR_COLUMN_SECTION_TYPE, &section_type,
PLACES_SIDEBAR_COLUMN_ROW_TYPE, &place_type,
PLACES_SIDEBAR_COLUMN_INDEX, &position,
-1);
drop_as_bookmarks = FALSE;
uris = gtk_selection_data_get_uris (selection_data);
source_file_list = build_file_list_from_uris ((const gchar **) uris);
if (section_type == SECTION_BOOKMARKS)
{
if (place_type == PLACES_HEADING)
{
position = 0;
tree_pos = GTK_TREE_VIEW_DROP_BEFORE;
}
if (tree_pos == GTK_TREE_VIEW_DROP_AFTER)
position++;
if (tree_pos == GTK_TREE_VIEW_DROP_BEFORE ||
tree_pos == GTK_TREE_VIEW_DROP_AFTER ||
place_type == PLACES_DROP_FEEDBACK)
{
remove_drop_bookmark_feedback_row (sidebar);
drop_files_as_bookmarks (sidebar, source_file_list, position);
success = TRUE;
drop_as_bookmarks = TRUE;
}
}
if (!drop_as_bookmarks)
{
gtk_tree_model_get_iter (model, &iter, tree_path);
gtk_tree_model_get (model, &iter,
PLACES_SIDEBAR_COLUMN_URI, &uri,
-1);
dest_file = g_file_new_for_uri (uri);
emit_drag_perform_drop (sidebar, dest_file, source_file_list, real_action);
success = TRUE;
g_object_unref (dest_file);
g_free (uri);
}
g_list_free_full (source_file_list, g_object_unref);
g_strfreev (uris);
}
}
out:
sidebar->drop_occured = FALSE;
free_drag_data (sidebar);
remove_drop_bookmark_feedback_row (sidebar);
gtk_drag_finish (context, success, FALSE, time);
gtk_tree_path_free (tree_path);
}
static gboolean
drag_drop_callback (GtkTreeView *tree_view,
GdkDragContext *context,
gint x,
gint y,
guint time,
GtkPlacesSidebar *sidebar)
{
gboolean retval = FALSE;
sidebar->drop_occured = TRUE;
retval = get_drag_data (tree_view, context, time);
g_signal_stop_emission_by_name (tree_view, "drag-drop");
return retval;
}
/* Callback used when the file list's popup menu is detached */
static void
bookmarks_popup_menu_detach_cb (GtkWidget *attach_widget,
GtkMenu *menu)
{
GTK_PLACES_SIDEBAR (attach_widget)->popup_menu = NULL;
}
static void
check_unmount_and_eject (GMount *mount,
GVolume *volume,
GDrive *drive,
gboolean *show_unmount,
gboolean *show_eject)
{
*show_unmount = FALSE;
*show_eject = FALSE;
if (drive != NULL)
*show_eject = g_drive_can_eject (drive);
if (volume != NULL)
*show_eject |= g_volume_can_eject (volume);
if (mount != NULL)
{
*show_eject |= g_mount_can_eject (mount);
*show_unmount = g_mount_can_unmount (mount) && !*show_eject;
}
}
static void
check_visibility (GMount *mount,
GVolume *volume,
GDrive *drive,
gboolean *show_mount,
gboolean *show_unmount,
gboolean *show_eject,
gboolean *show_rescan,
gboolean *show_start,
gboolean *show_stop)
{
*show_mount = FALSE;
*show_rescan = FALSE;
*show_start = FALSE;
*show_stop = FALSE;
check_unmount_and_eject (mount, volume, drive, show_unmount, show_eject);
if (drive != NULL)
{
if (g_drive_is_media_removable (drive) &&
!g_drive_is_media_check_automatic (drive) &&
g_drive_can_poll_for_media (drive))
*show_rescan = TRUE;
*show_start = g_drive_can_start (drive) || g_drive_can_start_degraded (drive);
*show_stop = g_drive_can_stop (drive);
if (*show_stop)
*show_unmount = FALSE;
}
if (volume != NULL)
{
if (mount == NULL)
*show_mount = g_volume_can_mount (volume);
}
}
typedef struct {
PlaceType type;
GDrive *drive;
GVolume *volume;
GMount *mount;
gchar *uri;
} SelectionInfo;
static void
get_selection_info (GtkPlacesSidebar *sidebar,
SelectionInfo *info)
{
GtkTreeIter iter;
info->type = PLACES_BUILT_IN;
info->drive = NULL;
info->volume = NULL;
info->mount = NULL;
info->uri = NULL;
if (get_selected_iter (sidebar, &iter))
gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store), &iter,
PLACES_SIDEBAR_COLUMN_ROW_TYPE, &info->type,
PLACES_SIDEBAR_COLUMN_DRIVE, &info->drive,
PLACES_SIDEBAR_COLUMN_VOLUME, &info->volume,
PLACES_SIDEBAR_COLUMN_MOUNT, &info->mount,
PLACES_SIDEBAR_COLUMN_URI, &info->uri,
-1);
}
static void
free_selection_info (SelectionInfo *info)
{
g_clear_object (&info->drive);
g_clear_object (&info->volume);
g_clear_object (&info->mount);
g_clear_pointer (&info->uri, g_free);
}
typedef struct {
GtkWidget *add_shortcut_item;
GtkWidget *remove_item;
GtkWidget *rename_item;
GtkWidget *separator_item;
GtkWidget *mount_item;
GtkWidget *unmount_item;
GtkWidget *eject_item;
GtkWidget *rescan_item;
GtkWidget *start_item;
GtkWidget *stop_item;
} PopupMenuData;
static void
check_popup_sensitivity (GtkPlacesSidebar *sidebar,
PopupMenuData *data,
SelectionInfo *info)
{
gboolean show_mount;
gboolean show_unmount;
gboolean show_eject;
gboolean show_rescan;
gboolean show_start;
gboolean show_stop;
gtk_widget_set_visible (data->add_shortcut_item, (info->type == PLACES_MOUNTED_VOLUME));
gtk_widget_set_sensitive (data->remove_item, (info->type == PLACES_BOOKMARK));
gtk_widget_set_sensitive (data->rename_item, (info->type == PLACES_BOOKMARK || info->type == PLACES_XDG_DIR));
check_visibility (info->mount, info->volume, info->drive,
&show_mount, &show_unmount, &show_eject, &show_rescan, &show_start, &show_stop);
gtk_widget_set_visible (data->separator_item, show_mount || show_unmount || show_eject);
gtk_widget_set_visible (data->mount_item, show_mount);
gtk_widget_set_visible (data->unmount_item, show_unmount);
gtk_widget_set_visible (data->eject_item, show_eject);
gtk_widget_set_visible (data->rescan_item, show_rescan);
gtk_widget_set_visible (data->start_item, show_start);
gtk_widget_set_visible (data->stop_item, show_stop);
/* Adjust start/stop items to reflect the type of the drive */
gtk_menu_item_set_label (GTK_MENU_ITEM (data->start_item), _("_Start"));
gtk_menu_item_set_label (GTK_MENU_ITEM (data->stop_item), _("_Stop"));
if ((show_start || show_stop) && info->drive != NULL)
{
switch (g_drive_get_start_stop_type (info->drive))
{
case G_DRIVE_START_STOP_TYPE_SHUTDOWN:
/* start() for type G_DRIVE_START_STOP_TYPE_SHUTDOWN is normally not used */
gtk_menu_item_set_label (GTK_MENU_ITEM (data->start_item), _("_Power On"));
gtk_menu_item_set_label (GTK_MENU_ITEM (data->stop_item), _("_Safely Remove Drive"));
break;
case G_DRIVE_START_STOP_TYPE_NETWORK:
gtk_menu_item_set_label (GTK_MENU_ITEM (data->start_item), _("_Connect Drive"));
gtk_menu_item_set_label (GTK_MENU_ITEM (data->stop_item), _("_Disconnect Drive"));
break;
case G_DRIVE_START_STOP_TYPE_MULTIDISK:
gtk_menu_item_set_label (GTK_MENU_ITEM (data->start_item), _("_Start Multi-disk Device"));
gtk_menu_item_set_label (GTK_MENU_ITEM (data->stop_item), _("_Stop Multi-disk Device"));
break;
case G_DRIVE_START_STOP_TYPE_PASSWORD:
/* stop() for type G_DRIVE_START_STOP_TYPE_PASSWORD is normally not used */
gtk_menu_item_set_label (GTK_MENU_ITEM (data->start_item), _("_Unlock Drive"));
gtk_menu_item_set_label (GTK_MENU_ITEM (data->stop_item), _("_Lock Drive"));
break;
default:
case G_DRIVE_START_STOP_TYPE_UNKNOWN:
/* uses defaults set above */
break;
}
}
}
static void
drive_start_from_bookmark_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
GtkPlacesSidebar *sidebar;
GError *error;
gchar *primary;
gchar *name;
sidebar = GTK_PLACES_SIDEBAR (user_data);
error = NULL;
if (!g_drive_poll_for_media_finish (G_DRIVE (source_object), res, &error))
{
if (error->code != G_IO_ERROR_FAILED_HANDLED)
{
name = g_drive_get_name (G_DRIVE (source_object));
primary = g_strdup_printf (_("Unable to start “%s”"), name);
g_free (name);
emit_show_error_message (sidebar, primary, error->message);
g_free (primary);
}
g_error_free (error);
}
}
static void
volume_mount_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
GtkPlacesSidebar *sidebar = GTK_PLACES_SIDEBAR (user_data);
GVolume *volume;
GError *error;
gchar *primary;
gchar *name;
GMount *mount;
volume = G_VOLUME (source_object);
error = NULL;
if (!g_volume_mount_finish (volume, result, &error))
{
if (error->code != G_IO_ERROR_FAILED_HANDLED &&
error->code != G_IO_ERROR_ALREADY_MOUNTED)
{
name = g_volume_get_name (G_VOLUME (source_object));
primary = g_strdup_printf (_("Unable to access “%s”"), name);
g_free (name);
emit_show_error_message (sidebar, primary, error->message);
g_free (primary);
}
g_error_free (error);
}
sidebar->mounting = FALSE;
mount = g_volume_get_mount (volume);
if (mount != NULL)
{
GFile *location;
location = g_mount_get_default_location (mount);
emit_open_location (sidebar, location, sidebar->go_to_after_mount_open_flags);
g_object_unref (G_OBJECT (location));
g_object_unref (G_OBJECT (mount));
}
g_object_unref (sidebar);
}
static void
mount_volume (GtkPlacesSidebar *sidebar,
GVolume *volume)
{
GMountOperation *mount_op;
mount_op = gtk_mount_operation_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (sidebar))));
g_mount_operation_set_password_save (mount_op, G_PASSWORD_SAVE_FOR_SESSION);
g_object_ref (sidebar);
g_volume_mount (volume, 0, mount_op, NULL, volume_mount_cb, sidebar);
}
static void
open_selected_volume (GtkPlacesSidebar *sidebar,
GtkTreeModel *model,
GtkTreeIter *iter,
GtkPlacesOpenFlags open_flags)
{
GDrive *drive;
GVolume *volume;
gtk_tree_model_get (model, iter,
PLACES_SIDEBAR_COLUMN_DRIVE, &drive,
PLACES_SIDEBAR_COLUMN_VOLUME, &volume,
-1);
if (volume != NULL && !sidebar->mounting)
{
sidebar->mounting = TRUE;
sidebar->go_to_after_mount_open_flags = open_flags;
mount_volume (sidebar, volume);
}
else if (volume == NULL && drive != NULL &&
(g_drive_can_start (drive) || g_drive_can_start_degraded (drive)))
{
GMountOperation *mount_op;
mount_op = gtk_mount_operation_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (sidebar))));
g_drive_start (drive, G_DRIVE_START_NONE, mount_op, NULL, drive_start_from_bookmark_cb, NULL);
g_object_unref (mount_op);
}
if (drive != NULL)
g_object_unref (drive);
if (volume != NULL)
g_object_unref (volume);
}
static void
open_selected_uri (GtkPlacesSidebar *sidebar,
const gchar *uri,
GtkPlacesOpenFlags open_flags)
{
GFile *location;
location = g_file_new_for_uri (uri);
emit_open_location (sidebar, location, open_flags);
g_object_unref (location);
}
static void
open_selected_bookmark (GtkPlacesSidebar *sidebar,
GtkTreeModel *model,
GtkTreeIter *iter,
GtkPlacesOpenFlags open_flags)
{
gchar *uri;
PlaceType place_type;
if (!iter)
return;
gtk_tree_model_get (model, iter,
PLACES_SIDEBAR_COLUMN_URI, &uri,
PLACES_SIDEBAR_COLUMN_ROW_TYPE, &place_type,
-1);
if (uri != NULL)
{
open_selected_uri (sidebar, uri, open_flags);
g_free (uri);
}
else if (place_type == PLACES_CONNECT_TO_SERVER)
{
emit_show_connect_to_server (sidebar);
}
else if (place_type == PLACES_ENTER_LOCATION)
{
emit_show_enter_location (sidebar);
}
else
{
open_selected_volume (sidebar, model, iter, open_flags);
}
}
static void
open_shortcut_from_menu (GtkPlacesSidebar *sidebar,
GtkPlacesOpenFlags open_flags)
{
GtkTreeModel *model;
GtkTreeIter iter;
GtkTreePath *path = NULL;
model = gtk_tree_view_get_model (sidebar->tree_view);
gtk_tree_view_get_cursor (sidebar->tree_view, &path, NULL);
if (path != NULL && gtk_tree_model_get_iter (model, &iter, path))
open_selected_bookmark (sidebar, model, &iter, open_flags);
gtk_tree_path_free (path);
}
/* Callback used for the "Open" menu item in the context menu */
static void
open_shortcut_cb (GtkMenuItem *item,
GtkPlacesSidebar *sidebar)
{
open_shortcut_from_menu (sidebar, GTK_PLACES_OPEN_NORMAL);
}
/* Callback used for the "Open in new tab" menu item in the context menu */
static void
open_shortcut_in_new_tab_cb (GtkMenuItem *item,
GtkPlacesSidebar *sidebar)
{
open_shortcut_from_menu (sidebar, GTK_PLACES_OPEN_NEW_TAB);
}
/* Callback used for the "Open in new window" menu item in the context menu */
static void
open_shortcut_in_new_window_cb (GtkMenuItem *item,
GtkPlacesSidebar *sidebar)
{
open_shortcut_from_menu (sidebar, GTK_PLACES_OPEN_NEW_WINDOW);
}
/* Add bookmark for the selected item - just used from mount points */
static void
add_shortcut_cb (GtkMenuItem *item,
GtkPlacesSidebar *sidebar)
{
GtkTreeModel *model;
GtkTreeIter iter;
gchar *uri;
gchar *name;
GFile *location;
model = gtk_tree_view_get_model (sidebar->tree_view);
if (get_selected_iter (sidebar, &iter))
{
gtk_tree_model_get (model, &iter,
PLACES_SIDEBAR_COLUMN_URI, &uri,
PLACES_SIDEBAR_COLUMN_NAME, &name,
-1);
if (uri == NULL)
return;
location = g_file_new_for_uri (uri);
if (_gtk_bookmarks_manager_insert_bookmark (sidebar->bookmarks_manager, location, -1, NULL))
_gtk_bookmarks_manager_set_bookmark_label (sidebar->bookmarks_manager, location, name, NULL);
g_object_unref (location);
g_free (uri);
g_free (name);
}
}
static void
rename_entry_changed (GtkEntry *entry,
GtkPlacesSidebar *sidebar)
{
GtkTreeIter iter;
PlaceType type;
gchar *name;
gchar *uri;
const gchar *new_name;
gboolean found = FALSE;
new_name = gtk_entry_get_text (GTK_ENTRY (sidebar->rename_entry));
if (strcmp (new_name, "") == 0)
{
gtk_widget_set_sensitive (sidebar->rename_button, FALSE);
gtk_label_set_label (GTK_LABEL (sidebar->rename_error), "");
return;
}
gtk_tree_model_get_iter_first (GTK_TREE_MODEL (sidebar->store), &iter);
do {
gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store), &iter,
PLACES_SIDEBAR_COLUMN_ROW_TYPE, &type,
PLACES_SIDEBAR_COLUMN_URI, &uri,
PLACES_SIDEBAR_COLUMN_NAME, &name,
-1);
if ((type == PLACES_XDG_DIR || type == PLACES_BOOKMARK) &&
strcmp (uri, sidebar->rename_uri) != 0 &&
strcmp (new_name, name) == 0)
found = TRUE;
g_free (name);
g_free (uri);
} while (gtk_tree_model_iter_next (GTK_TREE_MODEL (sidebar->store), &iter) && !found);
gtk_widget_set_sensitive (sidebar->rename_button, !found);
gtk_label_set_label (GTK_LABEL (sidebar->rename_error),
found ? _("This name is already taken") : "");
}
static void
do_rename (GtkButton *button,
GtkPlacesSidebar *sidebar)
{
const gchar *new_text;
GFile *file;
new_text = gtk_entry_get_text (GTK_ENTRY (sidebar->rename_entry));
file = g_file_new_for_uri (sidebar->rename_uri);
if (!_gtk_bookmarks_manager_has_bookmark (sidebar->bookmarks_manager, file))
_gtk_bookmarks_manager_insert_bookmark (sidebar->bookmarks_manager, file, -1, NULL);
_gtk_bookmarks_manager_set_bookmark_label (sidebar->bookmarks_manager, file, new_text, NULL);
g_object_unref (file);
g_clear_pointer (&sidebar->rename_uri, g_free);
gtk_widget_hide (sidebar->rename_popover);
}
static void
create_rename_popover (GtkPlacesSidebar *sidebar)
{
GtkWidget *popover;
GtkWidget *grid;
GtkWidget *label;
GtkWidget *entry;
GtkWidget *button;
GtkWidget *error;
gchar *str;
if (sidebar->rename_popover)
return;
popover = gtk_popover_new (GTK_WIDGET (sidebar));
gtk_popover_set_position (GTK_POPOVER (popover), GTK_POS_RIGHT);
grid = gtk_grid_new ();
gtk_container_add (GTK_CONTAINER (popover), grid);
g_object_set (grid,
"margin", 10,
"row-spacing", 6,
"column-spacing", 6,
NULL);
entry = gtk_entry_new ();
gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE);
g_signal_connect (entry, "changed", G_CALLBACK (rename_entry_changed), sidebar);
str = g_strdup_printf ("<b>%s</b>", _("Name"));
label = gtk_label_new (str);
gtk_widget_set_halign (label, GTK_ALIGN_START);
gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry);
g_free (str);
button = gtk_button_new_with_mnemonic (_("_Rename"));
gtk_widget_set_can_default (button, TRUE);
gtk_style_context_add_class (gtk_widget_get_style_context (button), "suggested-action");
g_signal_connect (button, "clicked", G_CALLBACK (do_rename), sidebar);
error = gtk_label_new ("");
gtk_widget_set_halign (error, GTK_ALIGN_START);
gtk_grid_attach (GTK_GRID (grid), label, 0, 0, 2, 1);
gtk_grid_attach (GTK_GRID (grid), entry, 0, 1, 1, 1);
gtk_grid_attach (GTK_GRID (grid), button,1, 1, 1, 1);
gtk_grid_attach (GTK_GRID (grid), error, 0, 2, 2, 1);
gtk_widget_show_all (grid);
gtk_popover_set_default_widget (GTK_POPOVER (popover), button);
sidebar->rename_popover = popover;
sidebar->rename_entry = entry;
sidebar->rename_button = button;
sidebar->rename_error = error;
}
static void
show_rename_popover (GtkPlacesSidebar *sidebar,
GtkTreeIter *iter)
{
GtkTreeViewColumn *column;
GdkRectangle rect;
gchar *name;
gchar *uri;
GtkTreePath *path;
create_rename_popover (sidebar);
gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store), iter,
PLACES_SIDEBAR_COLUMN_NAME, &name,
PLACES_SIDEBAR_COLUMN_URI, &uri,
-1);
if (sidebar->rename_uri)
g_free (sidebar->rename_uri);
sidebar->rename_uri = uri;
path = gtk_tree_model_get_path (GTK_TREE_MODEL (sidebar->store), iter);
column = gtk_tree_view_get_column (GTK_TREE_VIEW (sidebar->tree_view), 0);
gtk_tree_view_get_background_area (GTK_TREE_VIEW (sidebar->tree_view), path, column, &rect);
gtk_tree_path_free (path);
gtk_entry_set_text (GTK_ENTRY (sidebar->rename_entry), name);
gtk_popover_set_pointing_to (GTK_POPOVER (sidebar->rename_popover), &rect);
gtk_widget_show (sidebar->rename_popover);
gtk_widget_grab_focus (sidebar->rename_entry);
}
/* Rename the selected bookmark */
static void
rename_selected_bookmark (GtkPlacesSidebar *sidebar)
{
GtkTreeIter iter;
PlaceType type;
if (get_selected_iter (sidebar, &iter))
{
gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store), &iter,
PLACES_SIDEBAR_COLUMN_ROW_TYPE, &type,
-1);
if (type != PLACES_BOOKMARK && type != PLACES_XDG_DIR)
return;
show_rename_popover (sidebar, &iter);
}
}
static void
rename_shortcut_cb (GtkMenuItem *item,
GtkPlacesSidebar *sidebar)
{
rename_selected_bookmark (sidebar);
}
/* Removes the selected bookmarks */
static void
remove_selected_bookmarks (GtkPlacesSidebar *sidebar)
{
GtkTreeIter iter;
PlaceType type;
gchar *uri;
GFile *file;
if (!get_selected_iter (sidebar, &iter))
return;
gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store), &iter,
PLACES_SIDEBAR_COLUMN_ROW_TYPE, &type,
-1);
if (type != PLACES_BOOKMARK)
return;
gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store), &iter,
PLACES_SIDEBAR_COLUMN_URI, &uri,
-1);
file = g_file_new_for_uri (uri);
_gtk_bookmarks_manager_remove_bookmark (sidebar->bookmarks_manager, file, NULL);
g_object_unref (file);
g_free (uri);
}
static void
remove_shortcut_cb (GtkMenuItem *item,
GtkPlacesSidebar *sidebar)
{
remove_selected_bookmarks (sidebar);
}
static void
mount_shortcut_cb (GtkMenuItem *item,
GtkPlacesSidebar *sidebar)
{
GtkTreeIter iter;
GVolume *volume;
if (!get_selected_iter (sidebar, &iter))
return;
gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store), &iter,
PLACES_SIDEBAR_COLUMN_VOLUME, &volume,
-1);
if (volume != NULL)
{
mount_volume (sidebar, volume);
g_object_unref (volume);
}
}
/* Callback used from g_mount_unmount_with_operation() */
static void
unmount_mount_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
GtkPlacesSidebar *sidebar = GTK_PLACES_SIDEBAR (user_data);
GMount *mount;
GError *error;
mount = G_MOUNT (source_object);
error = NULL;
if (!g_mount_unmount_with_operation_finish (mount, result, &error))
{
if (error->code != G_IO_ERROR_FAILED_HANDLED)
{
gchar *name;
gchar *primary;
name = g_mount_get_name (mount);
primary = g_strdup_printf (_("Unable to unmount “%s”"), name);
g_free (name);
emit_show_error_message (sidebar, primary, error->message);
g_free (primary);
}
g_error_free (error);
}
g_object_unref (sidebar);
}
static void
notify_unmount_done (GMountOperation *op,
const gchar *message)
{
GApplication *application;
gchar *notification_id;
/* We only can support this when a default GApplication is set */
application = g_application_get_default ();
if (application == NULL)
return;
notification_id = g_strdup_printf ("gtk-mount-operation-%p", op);
g_application_withdraw_notification (application, notification_id);
if (message != NULL) {
GNotification *unplug;
GIcon *icon;
gchar **strings;
strings = g_strsplit (message, "\n", 0);
icon = g_themed_icon_new ("media-removable");
unplug = g_notification_new (strings[0]);
g_notification_set_body (unplug, strings[1]);
g_notification_set_icon (unplug, icon);
g_application_send_notification (application, notification_id, unplug);
g_object_unref (unplug);
g_object_unref (icon);
g_strfreev (strings);
}
g_free (notification_id);
}
static void
notify_unmount_show (GMountOperation *op,
const gchar *message)
{
GApplication *application;
GNotification *unmount;
gchar *notification_id;
GIcon *icon;
gchar **strings;
/* We only can support this when a default GApplication is set */
application = g_application_get_default ();
if (application == NULL)
return;
strings = g_strsplit (message, "\n", 0);
icon = g_themed_icon_new ("media-removable");
unmount = g_notification_new (strings[0]);
g_notification_set_body (unmount, strings[1]);
g_notification_set_icon (unmount, icon);
g_notification_set_priority (unmount, G_NOTIFICATION_PRIORITY_URGENT);
notification_id = g_strdup_printf ("gtk-mount-operation-%p", op);
g_application_send_notification (application, notification_id, unmount);
g_object_unref (unmount);
g_object_unref (icon);
g_strfreev (strings);
g_free (notification_id);
}
static void
show_unmount_progress_cb (GMountOperation *op,
const gchar *message,
gint64 time_left,
gint64 bytes_left,
gpointer user_data)
{
if (bytes_left == 0)
notify_unmount_done (op, message);
else
notify_unmount_show (op, message);
}
static void
show_unmount_progress_aborted_cb (GMountOperation *op,
gpointer user_data)
{
notify_unmount_done (op, NULL);
}
static GMountOperation *
get_unmount_operation (GtkPlacesSidebar *sidebar)
{
GMountOperation *mount_op;
mount_op = gtk_mount_operation_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (sidebar))));
g_signal_connect (mount_op, "show-unmount-progress",
G_CALLBACK (show_unmount_progress_cb), sidebar);
g_signal_connect (mount_op, "aborted",
G_CALLBACK (show_unmount_progress_aborted_cb), sidebar);
return mount_op;
}
/* Returns TRUE if file1 is prefix of file2 or if both files have the
* same path
*/
static gboolean
file_prefix_or_same (GFile *file1,
GFile *file2)
{
return g_file_has_prefix (file1, file2) ||
g_file_equal (file1, file2);
}
static gboolean
is_current_location_on_volume (GtkPlacesSidebar *sidebar,
GMount *mount,
GVolume *volume,
GDrive *drive)
{
gboolean current_location_on_volume;
GFile *mount_default_location;
GMount *mount_for_volume;
GList *volumes_for_drive;
GList *volume_for_drive;
current_location_on_volume = FALSE;
if (sidebar->current_location != NULL)
{
if (mount != NULL)
{
mount_default_location = g_mount_get_default_location (mount);
current_location_on_volume = file_prefix_or_same (sidebar->current_location,
mount_default_location);
g_object_unref (mount_default_location);
}
/* This code path is probably never reached since mount always exists,
* and if it doesn't exists we don't offer a way to eject a volume or
* drive in the UI. Do it for defensive programming
*/
else if (volume != NULL)
{
mount_for_volume = g_volume_get_mount (volume);
if (mount_for_volume != NULL)
{
mount_default_location = g_mount_get_default_location (mount_for_volume);
current_location_on_volume = file_prefix_or_same (sidebar->current_location,
mount_default_location);
g_object_unref (mount_default_location);
g_object_unref (mount_for_volume);
}
}
/* This code path is probably never reached since mount always exists,
* and if it doesn't exists we don't offer a way to eject a volume or
* drive in the UI. Do it for defensive programming
*/
else if (drive != NULL)
{
volumes_for_drive = g_drive_get_volumes (drive);
for (volume_for_drive = volumes_for_drive; volume_for_drive != NULL; volume_for_drive = volume_for_drive->next)
{
mount_for_volume = g_volume_get_mount (volume_for_drive->data);
if (mount_for_volume != NULL)
{
mount_default_location = g_mount_get_default_location (mount_for_volume);
current_location_on_volume = file_prefix_or_same (sidebar->current_location,
mount_default_location);
g_object_unref (mount_default_location);
g_object_unref (mount_for_volume);
if (current_location_on_volume)
break;
}
}
g_list_free_full (volumes_for_drive, g_object_unref);
}
}
return current_location_on_volume;
}
static void
do_unmount (GMount *mount,
GtkPlacesSidebar *sidebar)
{
if (mount != NULL)
{
GMountOperation *mount_op;
if (is_current_location_on_volume (sidebar, mount, NULL, NULL))
open_home (sidebar);
mount_op = get_unmount_operation (sidebar);
g_mount_unmount_with_operation (mount,
0,
mount_op,
NULL,
unmount_mount_cb,
g_object_ref (sidebar));
g_object_unref (mount_op);
}
}
static void
do_unmount_selection (GtkPlacesSidebar *sidebar)
{
GtkTreeIter iter;
GMount *mount;
if (!get_selected_iter (sidebar, &iter))
return;
gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store), &iter,
PLACES_SIDEBAR_COLUMN_MOUNT, &mount,
-1);
if (mount != NULL)
{
do_unmount (mount, sidebar);
g_object_unref (mount);
}
}
static void
unmount_shortcut_cb (GtkMenuItem *item,
GtkPlacesSidebar *sidebar)
{
do_unmount_selection (sidebar);
}
static void
drive_stop_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
GtkPlacesSidebar *sidebar;
GError *error;
gchar *primary;
gchar *name;
sidebar = user_data;
error = NULL;
if (!g_drive_stop_finish (G_DRIVE (source_object), res, &error))
{
if (error->code != G_IO_ERROR_FAILED_HANDLED)
{
name = g_drive_get_name (G_DRIVE (source_object));
primary = g_strdup_printf (_("Unable to stop “%s”"), name);
g_free (name);
emit_show_error_message (sidebar, primary, error->message);
g_free (primary);
}
g_error_free (error);
}
g_object_unref (sidebar);
}
static void
drive_eject_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
GtkPlacesSidebar *sidebar;
GError *error;
gchar *primary;
gchar *name;
sidebar = user_data;
error = NULL;
if (!g_drive_eject_with_operation_finish (G_DRIVE (source_object), res, &error))
{
if (error->code != G_IO_ERROR_FAILED_HANDLED)
{
name = g_drive_get_name (G_DRIVE (source_object));
primary = g_strdup_printf (_("Unable to eject “%s”"), name);
g_free (name);
emit_show_error_message (sidebar, primary, error->message);
g_free (primary);
}
g_error_free (error);
}
g_object_unref (sidebar);
}
static void
volume_eject_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
GtkPlacesSidebar *sidebar;
GError *error;
gchar *primary;
gchar *name;
sidebar = user_data;
error = NULL;
if (!g_volume_eject_with_operation_finish (G_VOLUME (source_object), res, &error))
{
if (error->code != G_IO_ERROR_FAILED_HANDLED)
{
name = g_volume_get_name (G_VOLUME (source_object));
primary = g_strdup_printf (_("Unable to eject %s"), name);
g_free (name);
emit_show_error_message (sidebar, primary, error->message);
g_free (primary);
}
g_error_free (error);
}
g_object_unref (sidebar);
}
static void
mount_eject_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
GtkPlacesSidebar *sidebar;
GError *error;
gchar *primary;
gchar *name;
sidebar = user_data;
error = NULL;
if (!g_mount_eject_with_operation_finish (G_MOUNT (source_object), res, &error))
{
if (error->code != G_IO_ERROR_FAILED_HANDLED)
{
name = g_mount_get_name (G_MOUNT (source_object));
primary = g_strdup_printf (_("Unable to eject %s"), name);
g_free (name);
emit_show_error_message (sidebar, primary, error->message);
g_free (primary);
}
g_error_free (error);
}
g_object_unref (sidebar);
}
static void
do_eject (GMount *mount,
GVolume *volume,
GDrive *drive,
GtkPlacesSidebar *sidebar)
{
GMountOperation *mount_op;
mount_op = get_unmount_operation (sidebar);
if (is_current_location_on_volume (sidebar, mount, volume, drive))
open_home (sidebar);
if (mount != NULL)
g_mount_eject_with_operation (mount, 0, mount_op, NULL, mount_eject_cb,
g_object_ref (sidebar));
/* This code path is probably never reached since mount always exists,
* and if it doesn't exists we don't offer a way to eject a volume or
* drive in the UI. Do it for defensive programming
*/
else if (volume != NULL)
g_volume_eject_with_operation (volume, 0, mount_op, NULL, volume_eject_cb,
g_object_ref (sidebar));
/* This code path is probably never reached since mount always exists,
* and if it doesn't exists we don't offer a way to eject a volume or
* drive in the UI. Do it for defensive programming
*/
else if (drive != NULL)
{
if (g_drive_can_stop (drive))
g_drive_stop (drive, 0, mount_op, NULL, drive_stop_cb,
g_object_ref (sidebar));
else
g_drive_eject_with_operation (drive, 0, mount_op, NULL, drive_eject_cb,
g_object_ref (sidebar));
}
g_object_unref (mount_op);
}
static void
eject_shortcut_cb (GtkMenuItem *item,
GtkPlacesSidebar *sidebar)
{
GtkTreeIter iter;
GMount *mount;
GVolume *volume;
GDrive *drive;
if (!get_selected_iter (sidebar, &iter))
return;
gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store), &iter,
PLACES_SIDEBAR_COLUMN_MOUNT, &mount,
PLACES_SIDEBAR_COLUMN_VOLUME, &volume,
PLACES_SIDEBAR_COLUMN_DRIVE, &drive,
-1);
do_eject (mount, volume, drive, sidebar);
}
static gboolean
eject_or_unmount_bookmark (GtkPlacesSidebar *sidebar,
GtkTreePath *path)
{
GtkTreeModel *model;
GtkTreeIter iter;
gboolean can_unmount, can_eject;
GMount *mount;
GVolume *volume;
GDrive *drive;
gboolean ret;
model = GTK_TREE_MODEL (sidebar->store);
if (!path)
return FALSE;
if (!gtk_tree_model_get_iter (model, &iter, path))
return FALSE;
gtk_tree_model_get (model, &iter,
PLACES_SIDEBAR_COLUMN_MOUNT, &mount,
PLACES_SIDEBAR_COLUMN_VOLUME, &volume,
PLACES_SIDEBAR_COLUMN_DRIVE, &drive,
-1);
ret = FALSE;
check_unmount_and_eject (mount, volume, drive, &can_unmount, &can_eject);
/* if we can eject, it has priority over unmount */
if (can_eject)
{
do_eject (mount, volume, drive, sidebar);
ret = TRUE;
}
else if (can_unmount)
{
do_unmount (mount, sidebar);
ret = TRUE;
}
g_clear_object (&mount);
g_clear_object (&volume);
g_clear_object (&drive);
return ret;
}
static gboolean
eject_or_unmount_selection (GtkPlacesSidebar *sidebar)
{
GtkTreeIter iter;
GtkTreePath *path;
gboolean ret;
if (!get_selected_iter (sidebar, &iter))
return FALSE;
path = gtk_tree_model_get_path (GTK_TREE_MODEL (sidebar->store), &iter);
if (path == NULL)
return FALSE;
ret = eject_or_unmount_bookmark (sidebar, path);
gtk_tree_path_free (path);
return ret;
}
static void
drive_poll_for_media_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
GtkPlacesSidebar *sidebar;
GError *error;
gchar *primary;
gchar *name;
sidebar = GTK_PLACES_SIDEBAR (user_data);
error = NULL;
if (!g_drive_poll_for_media_finish (G_DRIVE (source_object), res, &error))
{
if (error->code != G_IO_ERROR_FAILED_HANDLED)
{
name = g_drive_get_name (G_DRIVE (source_object));
primary = g_strdup_printf (_("Unable to poll “%s” for media changes"), name);
g_free (name);
emit_show_error_message (sidebar, primary, error->message);
g_free (primary);
}
g_error_free (error);
}
g_object_unref (sidebar);
}
static void
rescan_shortcut_cb (GtkMenuItem *item,
GtkPlacesSidebar *sidebar)
{
GtkTreeIter iter;
GDrive *drive;
if (!get_selected_iter (sidebar, &iter))
return;
gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store), &iter,
PLACES_SIDEBAR_COLUMN_DRIVE, &drive,
-1);
if (drive != NULL)
{
g_drive_poll_for_media (drive, NULL, drive_poll_for_media_cb, g_object_ref (sidebar));
g_object_unref (drive);
}
}
static void
drive_start_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
GtkPlacesSidebar *sidebar;
GError *error;
gchar *primary;
gchar *name;
sidebar = GTK_PLACES_SIDEBAR (user_data);
error = NULL;
if (!g_drive_start_finish (G_DRIVE (source_object), res, &error))
{
if (error->code != G_IO_ERROR_FAILED_HANDLED)
{
name = g_drive_get_name (G_DRIVE (source_object));
primary = g_strdup_printf (_("Unable to start “%s”"), name);
g_free (name);
emit_show_error_message (sidebar, primary, error->message);
g_free (primary);
}
g_error_free (error);
}
g_object_unref (sidebar);
}
static void
start_shortcut_cb (GtkMenuItem *item,
GtkPlacesSidebar *sidebar)
{
GtkTreeIter iter;
GDrive *drive;
if (!get_selected_iter (sidebar, &iter))
return;
gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store), &iter,
PLACES_SIDEBAR_COLUMN_DRIVE, &drive,
-1);
if (drive != NULL)
{
GMountOperation *mount_op;
mount_op = gtk_mount_operation_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (sidebar))));
g_drive_start (drive, G_DRIVE_START_NONE, mount_op, NULL, drive_start_cb, g_object_ref (sidebar));
g_object_unref (mount_op);
g_object_unref (drive);
}
}
static void
stop_shortcut_cb (GtkMenuItem *item,
GtkPlacesSidebar *sidebar)
{
GtkTreeIter iter;
GDrive *drive;
if (!get_selected_iter (sidebar, &iter))
return;
gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store), &iter,
PLACES_SIDEBAR_COLUMN_DRIVE, &drive,
-1);
if (drive != NULL)
{
GMountOperation *mount_op;
mount_op = get_unmount_operation (sidebar);
g_drive_stop (drive, G_MOUNT_UNMOUNT_NONE, mount_op, NULL, drive_stop_cb,
g_object_ref (sidebar));
g_object_unref (mount_op);
g_object_unref (drive);
}
}
static gboolean
find_prev_or_next_row (GtkPlacesSidebar *sidebar,
GtkTreeIter *iter,
gboolean go_up)
{
GtkTreeModel *model = GTK_TREE_MODEL (sidebar->store);
gboolean res;
gint place_type;
if (go_up)
res = gtk_tree_model_iter_previous (model, iter);
else
res = gtk_tree_model_iter_next (model, iter);
if (res)
{
gtk_tree_model_get (model, iter,
PLACES_SIDEBAR_COLUMN_ROW_TYPE, &place_type,
-1);
if (place_type == PLACES_HEADING)
{
if (go_up)
res = gtk_tree_model_iter_previous (model, iter);
else
res = gtk_tree_model_iter_next (model, iter);
}
}
return res;
}
static gboolean
find_prev_row (GtkPlacesSidebar *sidebar,
GtkTreeIter *iter)
{
return find_prev_or_next_row (sidebar, iter, TRUE);
}
static gboolean
find_next_row (GtkPlacesSidebar *sidebar,
GtkTreeIter *iter)
{
return find_prev_or_next_row (sidebar, iter, FALSE);
}
static gboolean
gtk_places_sidebar_focus (GtkWidget *widget,
GtkDirectionType direction)
{
GtkPlacesSidebar *sidebar = GTK_PLACES_SIDEBAR (widget);
GtkTreePath *path;
GtkTreeIter iter;
gboolean res;
if (!get_selected_iter (sidebar, &iter))
{
gtk_tree_model_get_iter_first (GTK_TREE_MODEL (sidebar->store), &iter);
res = find_next_row (sidebar, &iter);
if (res)
{
path = gtk_tree_model_get_path (GTK_TREE_MODEL (sidebar->store), &iter);
gtk_tree_view_set_cursor (sidebar->tree_view, path, NULL, FALSE);
gtk_tree_path_free (path);
}
}
return GTK_WIDGET_CLASS (gtk_places_sidebar_parent_class)->focus (widget, direction);
}
/* Handler for GtkWidget::key-press-event on the shortcuts list */
static gboolean
bookmarks_key_press_event_cb (GtkWidget *widget,
GdkEventKey *event,
GtkPlacesSidebar *sidebar)
{
guint modifiers;
GtkTreeIter selected_iter;
GtkTreePath *path;
if (!get_selected_iter (sidebar, &selected_iter))
return FALSE;
modifiers = gtk_accelerator_get_default_mod_mask ();
if (event->keyval == GDK_KEY_Return ||
event->keyval == GDK_KEY_KP_Enter ||
event->keyval == GDK_KEY_ISO_Enter ||
event->keyval == GDK_KEY_space)
{
GtkPlacesOpenFlags open_flags = GTK_PLACES_OPEN_NORMAL;
if ((event->state & modifiers) == GDK_SHIFT_MASK)
open_flags = GTK_PLACES_OPEN_NEW_TAB;
else if ((event->state & modifiers) == GDK_CONTROL_MASK)
open_flags = GTK_PLACES_OPEN_NEW_WINDOW;
open_selected_bookmark (sidebar, GTK_TREE_MODEL (sidebar->store),
&selected_iter, open_flags);
return TRUE;
}
if (event->keyval == GDK_KEY_Down &&
(event->state & modifiers) == GDK_MOD1_MASK)
return eject_or_unmount_selection (sidebar);
if (event->keyval == GDK_KEY_Up)
{
if (find_prev_row (sidebar, &selected_iter))
{
path = gtk_tree_model_get_path (GTK_TREE_MODEL (sidebar->store), &selected_iter);
gtk_tree_view_set_cursor (sidebar->tree_view, path, NULL, FALSE);
gtk_tree_path_free (path);
}
return TRUE;
}
if (event->keyval == GDK_KEY_Down)
{
if (find_next_row (sidebar, &selected_iter))
{
path = gtk_tree_model_get_path (GTK_TREE_MODEL (sidebar->store), &selected_iter);
gtk_tree_view_set_cursor (sidebar->tree_view, path, NULL, FALSE);
gtk_tree_path_free (path);
}
return TRUE;
}
if ((event->keyval == GDK_KEY_Delete ||
event->keyval == GDK_KEY_KP_Delete) &&
(event->state & modifiers) == 0)
{
remove_selected_bookmarks (sidebar);
return TRUE;
}
if ((event->keyval == GDK_KEY_F2) &&
(event->state & modifiers) == 0)
{
rename_selected_bookmark (sidebar);
return TRUE;
}
return FALSE;
}
static GtkMenuItem *
append_menu_separator (GtkMenu *menu)
{
GtkWidget *menu_item;
menu_item = gtk_separator_menu_item_new ();
gtk_widget_show (menu_item);
gtk_menu_shell_insert (GTK_MENU_SHELL (menu), menu_item, -1);
return GTK_MENU_ITEM (menu_item);
}
/* Constructs the popup menu for the file list if needed */
static void
bookmarks_build_popup_menu (GtkPlacesSidebar *sidebar)
{
PopupMenuData menu_data;
GtkWidget *item;
SelectionInfo sel_info;
GFile *file;
sidebar->popup_menu = gtk_menu_new ();
gtk_style_context_add_class (gtk_widget_get_style_context (sidebar->popup_menu),
GTK_STYLE_CLASS_CONTEXT_MENU);
gtk_menu_attach_to_widget (GTK_MENU (sidebar->popup_menu),
GTK_WIDGET (sidebar),
bookmarks_popup_menu_detach_cb);
item = gtk_menu_item_new_with_mnemonic (_("_Open"));
g_signal_connect (item, "activate",
G_CALLBACK (open_shortcut_cb), sidebar);
gtk_widget_show (item);
gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->popup_menu), item);
if (sidebar->open_flags & GTK_PLACES_OPEN_NEW_TAB)
{
item = gtk_menu_item_new_with_mnemonic (_("Open in New _Tab"));
g_signal_connect (item, "activate",
G_CALLBACK (open_shortcut_in_new_tab_cb), sidebar);
gtk_widget_show (item);
gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->popup_menu), item);
}
if (sidebar->open_flags & GTK_PLACES_OPEN_NEW_WINDOW)
{
item = gtk_menu_item_new_with_mnemonic (_("Open in New _Window"));
g_signal_connect (item, "activate",
G_CALLBACK (open_shortcut_in_new_window_cb), sidebar);
gtk_widget_show (item);
gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->popup_menu), item);
}
append_menu_separator (GTK_MENU (sidebar->popup_menu));
item = gtk_menu_item_new_with_mnemonic (_("_Add Bookmark"));
menu_data.add_shortcut_item = item;
g_signal_connect (item, "activate",
G_CALLBACK (add_shortcut_cb), sidebar);
gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->popup_menu), item);
item = gtk_menu_item_new_with_label (_("Remove"));
menu_data.remove_item = item;
g_signal_connect (item, "activate",
G_CALLBACK (remove_shortcut_cb), sidebar);
gtk_widget_show (item);
gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->popup_menu), item);
item = gtk_menu_item_new_with_label (_("Rename…"));
menu_data.rename_item = item;
g_signal_connect (item, "activate",
G_CALLBACK (rename_shortcut_cb), sidebar);
gtk_widget_show (item);
gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->popup_menu), item);
/* Mount/Unmount/Eject menu items */
menu_data.separator_item = GTK_WIDGET (append_menu_separator (GTK_MENU (sidebar->popup_menu)));
item = gtk_menu_item_new_with_mnemonic (_("_Mount"));
menu_data.mount_item = item;
g_signal_connect (item, "activate",
G_CALLBACK (mount_shortcut_cb), sidebar);
gtk_widget_show (item);
gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->popup_menu), item);
item = gtk_menu_item_new_with_mnemonic (_("_Unmount"));
menu_data.unmount_item = item;
g_signal_connect (item, "activate",
G_CALLBACK (unmount_shortcut_cb), sidebar);
gtk_widget_show (item);
gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->popup_menu), item);
item = gtk_menu_item_new_with_mnemonic (_("_Eject"));
menu_data.eject_item = item;
g_signal_connect (item, "activate",
G_CALLBACK (eject_shortcut_cb), sidebar);
gtk_widget_show (item);
gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->popup_menu), item);
item = gtk_menu_item_new_with_mnemonic (_("_Detect Media"));
menu_data.rescan_item = item;
g_signal_connect (item, "activate",
G_CALLBACK (rescan_shortcut_cb), sidebar);
gtk_widget_show (item);
gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->popup_menu), item);
item = gtk_menu_item_new_with_mnemonic (_("_Start"));
menu_data.start_item = item;
g_signal_connect (item, "activate",
G_CALLBACK (start_shortcut_cb), sidebar);
gtk_widget_show (item);
gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->popup_menu), item);
item = gtk_menu_item_new_with_mnemonic (_("_Stop"));
menu_data.stop_item = item;
g_signal_connect (item, "activate",
G_CALLBACK (stop_shortcut_cb), sidebar);
gtk_widget_show (item);
gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->popup_menu), item);
/* Update everything! */
get_selection_info (sidebar, &sel_info);
check_popup_sensitivity (sidebar, &menu_data, &sel_info);
/* And let the caller spice things up */
if (sel_info.uri)
file = g_file_new_for_uri (sel_info.uri);
else
file = NULL;
emit_populate_popup (sidebar, GTK_MENU (sidebar->popup_menu), file, sel_info.volume);
if (file)
g_object_unref (file);
free_selection_info (&sel_info);
}
static void
bookmarks_popup_menu (GtkPlacesSidebar *sidebar,
GdkEventButton *event)
{
gint button;
if (sidebar->popup_menu)
gtk_widget_destroy (sidebar->popup_menu);
bookmarks_build_popup_menu (sidebar);
/* The event button needs to be 0 if we're popping up this menu from
* a button release, else a 2nd click outside the menu with any button
* other than the one that invoked the menu will be ignored (instead
* of dismissing the menu). This is a subtle fragility of the GTK menu code.
*/
if (event)
{
if (event->type == GDK_BUTTON_RELEASE)
button = 0;
else
button = event->button;
}
else
{
button = 0;
}
gtk_menu_popup (GTK_MENU (sidebar->popup_menu),
NULL,
NULL,
NULL,
NULL,
button,
event ? event->time : gtk_get_current_event_time ());
}
/* Callback used for the GtkWidget::popup-menu signal of the shortcuts list */
static gboolean
bookmarks_popup_menu_cb (GtkWidget *widget,
GtkPlacesSidebar *sidebar)
{
bookmarks_popup_menu (sidebar, NULL);
return TRUE;
}
static void
bookmarks_row_activated_cb (GtkWidget *widget,
GtkTreePath *path,
GtkTreeViewColumn *column,
GtkPlacesSidebar *sidebar)
{
GtkTreeIter iter;
GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
GtkTreePath *dummy;
if (!gtk_tree_model_get_iter (model, &iter, path))
return;
dummy = NULL;
if (!clicked_eject_button (sidebar, &dummy))
{
open_selected_bookmark (sidebar, model, &iter, 0);
gtk_tree_path_free (dummy);
}
}
static gboolean
bookmarks_button_release_event_cb (GtkWidget *widget,
GdkEventButton *event,
GtkPlacesSidebar *sidebar)
{
GtkTreePath *path;
GtkTreeIter iter;
GtkTreeModel *model;
GtkTreeView *tree_view;
gboolean ret = FALSE;
gboolean res;
path = NULL;
if (event->type != GDK_BUTTON_RELEASE)
return TRUE;
if (clicked_eject_button (sidebar, &path))
{
eject_or_unmount_bookmark (sidebar, path);
gtk_tree_path_free (path);
return TRUE;
}
if (event->button == 1)
return FALSE;
tree_view = GTK_TREE_VIEW (widget);
model = gtk_tree_view_get_model (tree_view);
if (event->window != gtk_tree_view_get_bin_window (tree_view))
return FALSE;
res = gtk_tree_view_get_path_at_pos (tree_view, (int) event->x, (int) event->y,
&path, NULL, NULL, NULL);
if (!res || path == NULL)
return FALSE;
res = gtk_tree_model_get_iter (model, &iter, path);
if (!res)
{
gtk_tree_path_free (path);
return FALSE;
}
if (event->button == 2)
{
GtkPlacesOpenFlags open_flags = GTK_PLACES_OPEN_NORMAL;
open_flags = (event->state & GDK_CONTROL_MASK) ?
GTK_PLACES_OPEN_NEW_WINDOW :
GTK_PLACES_OPEN_NEW_TAB;
open_selected_bookmark (sidebar, model, &iter, open_flags);
ret = TRUE;
}
else if (event->button == 3)
{
PlaceType row_type;
gtk_tree_model_get (model, &iter,
PLACES_SIDEBAR_COLUMN_ROW_TYPE, &row_type,
-1);
if (row_type != PLACES_HEADING && row_type != PLACES_CONNECT_TO_SERVER)
bookmarks_popup_menu (sidebar, event);
}
gtk_tree_path_free (path);
return ret;
}
static gboolean
tree_selection_func (GtkTreeSelection *selection,
GtkTreeModel *model,
GtkTreePath *path,
gboolean path_currently_selected,
gpointer user_data)
{
GtkTreeIter iter;
PlaceType row_type;
gtk_tree_model_get_iter (model, &iter, path);
gtk_tree_model_get (model, &iter,
PLACES_SIDEBAR_COLUMN_ROW_TYPE, &row_type,
-1);
if (row_type == PLACES_HEADING)
return FALSE;
return TRUE;
}
static void
icon_cell_renderer_func (GtkTreeViewColumn *column,
GtkCellRenderer *cell,
GtkTreeModel *model,
GtkTreeIter *iter,
gpointer user_data)
{
PlaceType type;
gtk_tree_model_get (model, iter,
PLACES_SIDEBAR_COLUMN_ROW_TYPE, &type,
-1);
g_object_set (cell, "visible", type != PLACES_HEADING, NULL);
}
static gint
places_sidebar_sort_func (GtkTreeModel *model,
GtkTreeIter *iter_a,
GtkTreeIter *iter_b,
gpointer user_data)
{
SectionType section_type_a, section_type_b;
PlaceType place_type_a, place_type_b;
gint retval = 0;
gtk_tree_model_get (model, iter_a,
PLACES_SIDEBAR_COLUMN_SECTION_TYPE, &section_type_a,
PLACES_SIDEBAR_COLUMN_ROW_TYPE, &place_type_a,
-1);
gtk_tree_model_get (model, iter_b,
PLACES_SIDEBAR_COLUMN_SECTION_TYPE, &section_type_b,
PLACES_SIDEBAR_COLUMN_ROW_TYPE, &place_type_b,
-1);
/* fall back to the default order if we're not in the
* XDG part of the computer section.
*/
if ((section_type_a == section_type_b) &&
(section_type_a == SECTION_COMPUTER) &&
(place_type_a == place_type_b) &&
(place_type_a == PLACES_XDG_DIR))
{
gchar *name_a, *name_b;
gtk_tree_model_get (model, iter_a,
PLACES_SIDEBAR_COLUMN_NAME, &name_a,
-1);
gtk_tree_model_get (model, iter_b,
PLACES_SIDEBAR_COLUMN_NAME, &name_b,
-1);
retval = g_utf8_collate (name_a, name_b);
g_free (name_a);
g_free (name_b);
}
else if ((place_type_a == place_type_b) &&
(place_type_a == PLACES_BOOKMARK))
{
gint pos_a, pos_b;
gtk_tree_model_get (model, iter_a,
PLACES_SIDEBAR_COLUMN_INDEX, &pos_a,
-1);
gtk_tree_model_get (model, iter_b,
PLACES_SIDEBAR_COLUMN_INDEX, &pos_b,
-1);
retval = pos_a - pos_b;
}
else if (place_type_a == PLACES_CONNECT_TO_SERVER)
{
retval = 1;
}
else if (place_type_b == PLACES_CONNECT_TO_SERVER)
{
retval = -1;
}
return retval;
}
static void
update_hostname (GtkPlacesSidebar *sidebar)
{
GVariant *variant;
gsize len;
const gchar *hostname;
if (sidebar->hostnamed_proxy == NULL)
return;
variant = g_dbus_proxy_get_cached_property (sidebar->hostnamed_proxy,
"PrettyHostname");
if (variant == NULL)
return;
hostname = g_variant_get_string (variant, &len);
if (len > 0 &&
g_strcmp0 (sidebar->hostname, hostname) != 0)
{
g_free (sidebar->hostname);
sidebar->hostname = g_strdup (hostname);
update_places (sidebar);
}
g_variant_unref (variant);
}
static void
hostname_proxy_new_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
GtkPlacesSidebar *sidebar = user_data;
GError *error = NULL;
GDBusProxy *proxy;
proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
{
g_error_free (error);
return;
}
sidebar->hostnamed_proxy = proxy;
g_clear_object (&sidebar->hostnamed_cancellable);
if (error != NULL)
{
g_debug ("Failed to create D-Bus proxy: %s", error->message);
g_error_free (error);
return;
}
g_signal_connect_swapped (sidebar->hostnamed_proxy,
"g-properties-changed",
G_CALLBACK (update_hostname),
sidebar);
update_hostname (sidebar);
}
static void
create_volume_monitor (GtkPlacesSidebar *sidebar)
{
g_assert (sidebar->volume_monitor == NULL);
sidebar->volume_monitor = g_volume_monitor_get ();
g_signal_connect_object (sidebar->volume_monitor, "volume_added",
G_CALLBACK (update_places), sidebar, G_CONNECT_SWAPPED);
g_signal_connect_object (sidebar->volume_monitor, "volume_removed",
G_CALLBACK (update_places), sidebar, G_CONNECT_SWAPPED);
g_signal_connect_object (sidebar->volume_monitor, "volume_changed",
G_CALLBACK (update_places), sidebar, G_CONNECT_SWAPPED);
g_signal_connect_object (sidebar->volume_monitor, "mount_added",
G_CALLBACK (update_places), sidebar, G_CONNECT_SWAPPED);
g_signal_connect_object (sidebar->volume_monitor, "mount_removed",
G_CALLBACK (update_places), sidebar, G_CONNECT_SWAPPED);
g_signal_connect_object (sidebar->volume_monitor, "mount_changed",
G_CALLBACK (update_places), sidebar, G_CONNECT_SWAPPED);
g_signal_connect_object (sidebar->volume_monitor, "drive_disconnected",
G_CALLBACK (update_places), sidebar, G_CONNECT_SWAPPED);
g_signal_connect_object (sidebar->volume_monitor, "drive_connected",
G_CALLBACK (update_places), sidebar, G_CONNECT_SWAPPED);
g_signal_connect_object (sidebar->volume_monitor, "drive_changed",
G_CALLBACK (update_places), sidebar, G_CONNECT_SWAPPED);
}
static void
shell_shows_desktop_changed (GtkSettings *settings,
GParamSpec *pspec,
gpointer user_data)
{
GtkPlacesSidebar *sidebar = user_data;
gboolean b;
g_assert (settings == sidebar->gtk_settings);
/* Check if the user explicitly set this and, if so, don't change it. */
if (sidebar->show_desktop_set)
return;
g_object_get (settings, "gtk-shell-shows-desktop", &b, NULL);
if (b != sidebar->show_desktop)
{
sidebar->show_desktop = b;
update_places (sidebar);
g_object_notify_by_pspec (G_OBJECT (sidebar), properties[PROP_SHOW_DESKTOP]);
}
}
static gboolean
row_separator_func (GtkTreeModel *model,
GtkTreeIter *iter,
gpointer data)
{
PlaceType type;
gtk_tree_model_get (model, iter,
PLACES_SIDEBAR_COLUMN_ROW_TYPE, &type,
-1);
return type == PLACES_HEADING;
}
static void
gtk_places_sidebar_init (GtkPlacesSidebar *sidebar)
{
GtkTreeView *tree_view;
GtkTreeViewColumn *col;
GtkCellRenderer *cell;
GtkTreeSelection *selection;
GIcon *eject;
GtkTargetList *target_list;
gboolean b;
gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (sidebar)), GTK_STYLE_CLASS_SIDEBAR);
sidebar->cancellable = g_cancellable_new ();
create_volume_monitor (sidebar);
sidebar->open_flags = GTK_PLACES_OPEN_NORMAL;
sidebar->bookmarks_manager = _gtk_bookmarks_manager_new ((GtkBookmarksChangedFunc)update_places, sidebar);
sidebar->trash_monitor = _gtk_trash_monitor_get ();
sidebar->trash_monitor_changed_id = g_signal_connect_swapped (sidebar->trash_monitor, "trash-state-changed",
G_CALLBACK (update_places), sidebar);
sidebar->shortcuts = NULL;
gtk_widget_set_size_request (GTK_WIDGET (sidebar), 140, 280);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sidebar),
GTK_POLICY_NEVER,
GTK_POLICY_AUTOMATIC);
gtk_scrolled_window_set_hadjustment (GTK_SCROLLED_WINDOW (sidebar), NULL);
gtk_scrolled_window_set_vadjustment (GTK_SCROLLED_WINDOW (sidebar), NULL);
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sidebar), GTK_SHADOW_IN);
gtk_style_context_set_junction_sides (gtk_widget_get_style_context (GTK_WIDGET (sidebar)),
GTK_JUNCTION_RIGHT | GTK_JUNCTION_LEFT);
/* tree view */
tree_view = GTK_TREE_VIEW (gtk_tree_view_new ());
gtk_tree_view_set_headers_visible (tree_view, FALSE);
gtk_tree_view_set_row_separator_func (tree_view,
row_separator_func,
sidebar,
NULL);
col = gtk_tree_view_column_new ();
/* icon renderer */
cell = gtk_cell_renderer_pixbuf_new ();
g_object_set (cell,
"xpad", 10,
"ypad", 8,
NULL);
gtk_tree_view_column_pack_start (col, cell, FALSE);
gtk_tree_view_column_set_attributes (col, cell,
"gicon", PLACES_SIDEBAR_COLUMN_GICON,
NULL);
gtk_tree_view_column_set_cell_data_func (col, cell,
icon_cell_renderer_func,
sidebar, NULL);
/* eject text renderer */
cell = gtk_cell_renderer_text_new ();
gtk_tree_view_column_pack_start (col, cell, TRUE);
gtk_tree_view_column_set_attributes (col, cell,
"text", PLACES_SIDEBAR_COLUMN_NAME,
"visible", PLACES_SIDEBAR_COLUMN_EJECT,
NULL);
g_object_set (cell,
"ellipsize", PANGO_ELLIPSIZE_END,
"ellipsize-set", TRUE,
NULL);
/* eject icon renderer */
cell = gtk_cell_renderer_pixbuf_new ();
sidebar->eject_icon_cell_renderer = cell;
eject = g_themed_icon_new_with_default_fallbacks ("media-eject-symbolic");
g_object_set (cell,
"mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE,
"stock-size", GTK_ICON_SIZE_MENU,
"xpad", EJECT_BUTTON_XPAD,
/* align right, because for some reason gtk+ expands
this even though we tell it not to. */
"xalign", 1.0,
"gicon", eject,
NULL);
gtk_tree_view_column_pack_start (col, cell, FALSE);
gtk_tree_view_column_set_attributes (col, cell,
"visible", PLACES_SIDEBAR_COLUMN_EJECT,
NULL);
g_object_unref (eject);
/* normal text renderer */
cell = gtk_cell_renderer_text_new ();
sidebar->text_cell_renderer = cell;
gtk_tree_view_column_pack_start (col, cell, TRUE);
g_object_set (G_OBJECT (cell), "editable", FALSE, NULL);
gtk_tree_view_column_set_attributes (col, cell,
"text", PLACES_SIDEBAR_COLUMN_NAME,
"visible", PLACES_SIDEBAR_COLUMN_NO_EJECT,
"editable-set", PLACES_SIDEBAR_COLUMN_BOOKMARK,
NULL);
g_object_set (cell,
"ellipsize", PANGO_ELLIPSIZE_END,
"ellipsize-set", TRUE,
NULL);
/* this is required to align the eject buttons to the right */
gtk_tree_view_column_set_max_width (GTK_TREE_VIEW_COLUMN (col), 24);
gtk_tree_view_append_column (tree_view, col);
sidebar->store = shortcuts_model_new (sidebar);
gtk_tree_view_set_tooltip_column (tree_view, PLACES_SIDEBAR_COLUMN_TOOLTIP);
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sidebar->store),
PLACES_SIDEBAR_COLUMN_NAME,
GTK_SORT_ASCENDING);
gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sidebar->store),
PLACES_SIDEBAR_COLUMN_NAME,
places_sidebar_sort_func,
sidebar, NULL);
gtk_tree_view_set_model (tree_view, GTK_TREE_MODEL (sidebar->store));
gtk_container_add (GTK_CONTAINER (sidebar), GTK_WIDGET (tree_view));
gtk_widget_show (GTK_WIDGET (tree_view));
gtk_tree_view_set_enable_search (tree_view, FALSE);
gtk_widget_show (GTK_WIDGET (sidebar));
sidebar->tree_view = tree_view;
gtk_tree_view_set_search_column (tree_view, PLACES_SIDEBAR_COLUMN_NAME);
selection = gtk_tree_view_get_selection (tree_view);
gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
gtk_tree_selection_set_select_function (selection,
tree_selection_func,
sidebar,
NULL);
gtk_tree_view_enable_model_drag_source (GTK_TREE_VIEW (tree_view),
GDK_BUTTON1_MASK,
dnd_source_targets, G_N_ELEMENTS (dnd_source_targets),
GDK_ACTION_MOVE);
gtk_drag_dest_set (GTK_WIDGET (tree_view),
0,
NULL, 0,
GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK);
target_list = gtk_target_list_new (dnd_drop_targets, G_N_ELEMENTS (dnd_drop_targets));
gtk_target_list_add_uri_targets (target_list, TEXT_URI_LIST);
gtk_drag_dest_set_target_list (GTK_WIDGET (tree_view), target_list);
gtk_target_list_unref (target_list);
g_signal_connect (tree_view, "key-press-event",
G_CALLBACK (bookmarks_key_press_event_cb), sidebar);
g_signal_connect (tree_view, "drag-motion",
G_CALLBACK (drag_motion_callback), sidebar);
g_signal_connect (tree_view, "drag-leave",
G_CALLBACK (drag_leave_callback), sidebar);
g_signal_connect (tree_view, "drag-data-received",
G_CALLBACK (drag_data_received_callback), sidebar);
g_signal_connect (tree_view, "drag-drop",
G_CALLBACK (drag_drop_callback), sidebar);
g_signal_connect (tree_view, "popup-menu",
G_CALLBACK (bookmarks_popup_menu_cb), sidebar);
g_signal_connect (tree_view, "button-release-event",
G_CALLBACK (bookmarks_button_release_event_cb), sidebar);
g_signal_connect (tree_view, "row-activated",
G_CALLBACK (bookmarks_row_activated_cb), sidebar);
gtk_tree_view_set_activate_on_single_click (sidebar->tree_view, TRUE);
sidebar->hostname = g_strdup (_("Computer"));
sidebar->hostnamed_cancellable = g_cancellable_new ();
g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
G_DBUS_PROXY_FLAGS_GET_INVALIDATED_PROPERTIES,
NULL,
"org.freedesktop.hostname1",
"/org/freedesktop/hostname1",
"org.freedesktop.hostname1",
sidebar->hostnamed_cancellable,
hostname_proxy_new_cb,
sidebar);
sidebar->drop_state = DROP_STATE_NORMAL;
sidebar->new_bookmark_index = -1;
/* Don't bother trying to trace this across hierarchy changes... */
sidebar->gtk_settings = gtk_settings_get_default ();
g_signal_connect (sidebar->gtk_settings, "notify::gtk-shell-shows-desktop",
G_CALLBACK (shell_shows_desktop_changed), sidebar);
g_object_get (sidebar->gtk_settings, "gtk-shell-shows-desktop", &b, NULL);
sidebar->show_desktop = b;
/* populate the sidebar */
update_places (sidebar);
}
static void
gtk_places_sidebar_set_property (GObject *obj,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GtkPlacesSidebar *sidebar = GTK_PLACES_SIDEBAR (obj);
switch (property_id)
{
case PROP_LOCATION:
gtk_places_sidebar_set_location (sidebar, g_value_get_object (value));
break;
case PROP_OPEN_FLAGS:
gtk_places_sidebar_set_open_flags (sidebar, g_value_get_flags (value));
break;
case PROP_SHOW_RECENT:
gtk_places_sidebar_set_show_recent (sidebar, g_value_get_boolean (value));
break;
case PROP_SHOW_DESKTOP:
gtk_places_sidebar_set_show_desktop (sidebar, g_value_get_boolean (value));
break;
case PROP_SHOW_CONNECT_TO_SERVER:
gtk_places_sidebar_set_show_connect_to_server (sidebar, g_value_get_boolean (value));
break;
case PROP_SHOW_ENTER_LOCATION:
gtk_places_sidebar_set_show_enter_location (sidebar, g_value_get_boolean (value));
break;
case PROP_LOCAL_ONLY:
gtk_places_sidebar_set_local_only (sidebar, g_value_get_boolean (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec);
break;
}
}
static void
gtk_places_sidebar_get_property (GObject *obj,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
GtkPlacesSidebar *sidebar = GTK_PLACES_SIDEBAR (obj);
switch (property_id)
{
case PROP_LOCATION:
g_value_take_object (value, gtk_places_sidebar_get_location (sidebar));
break;
case PROP_OPEN_FLAGS:
g_value_set_flags (value, gtk_places_sidebar_get_open_flags (sidebar));
break;
case PROP_SHOW_RECENT:
g_value_set_boolean (value, gtk_places_sidebar_get_show_recent (sidebar));
break;
case PROP_SHOW_DESKTOP:
g_value_set_boolean (value, gtk_places_sidebar_get_show_desktop (sidebar));
break;
case PROP_SHOW_CONNECT_TO_SERVER:
g_value_set_boolean (value, gtk_places_sidebar_get_show_connect_to_server (sidebar));
break;
case PROP_SHOW_ENTER_LOCATION:
g_value_set_boolean (value, gtk_places_sidebar_get_show_enter_location (sidebar));
break;
case PROP_LOCAL_ONLY:
g_value_set_boolean (value, gtk_places_sidebar_get_local_only (sidebar));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec);
break;
}
}
static void
gtk_places_sidebar_dispose (GObject *object)
{
GtkPlacesSidebar *sidebar;
sidebar = GTK_PLACES_SIDEBAR (object);
if (sidebar->cancellable)
{
g_cancellable_cancel (sidebar->cancellable);
g_object_unref (sidebar->cancellable);
sidebar->cancellable = NULL;
}
sidebar->tree_view = NULL;
if (sidebar->drag_leave_timeout_id)
{
g_source_remove (sidebar->drag_leave_timeout_id);
sidebar->drag_leave_timeout_id = 0;
}
free_drag_data (sidebar);
if (sidebar->bookmarks_manager != NULL)
{
_gtk_bookmarks_manager_free (sidebar->bookmarks_manager);
sidebar->bookmarks_manager = NULL;
}
if (sidebar->popup_menu)
{
gtk_widget_destroy (sidebar->popup_menu);
sidebar->popup_menu = NULL;
}
if (sidebar->trash_monitor)
{
g_signal_handler_disconnect (sidebar->trash_monitor, sidebar->trash_monitor_changed_id);
sidebar->trash_monitor_changed_id = 0;
g_clear_object (&sidebar->trash_monitor);
}
g_clear_object (&sidebar->store);
g_slist_free_full (sidebar->shortcuts, g_object_unref);
sidebar->shortcuts = NULL;
if (sidebar->volume_monitor != NULL)
{
g_signal_handlers_disconnect_by_func (sidebar->volume_monitor,
update_places, sidebar);
g_clear_object (&sidebar->volume_monitor);
}
if (sidebar->hostnamed_cancellable != NULL)
{
g_cancellable_cancel (sidebar->hostnamed_cancellable);
g_clear_object (&sidebar->hostnamed_cancellable);
}
g_clear_object (&sidebar->hostnamed_proxy);
g_free (sidebar->hostname);
sidebar->hostname = NULL;
if (sidebar->gtk_settings)
{
g_signal_handlers_disconnect_by_func (sidebar->gtk_settings, shell_shows_desktop_changed, sidebar);
sidebar->gtk_settings = NULL;
}
g_clear_object (&sidebar->current_location);
g_clear_pointer (&sidebar->rename_uri, g_free);
G_OBJECT_CLASS (gtk_places_sidebar_parent_class)->dispose (object);
}
static void
gtk_places_sidebar_class_init (GtkPlacesSidebarClass *class)
{
GObjectClass *gobject_class;
gobject_class = (GObjectClass *) class;
gobject_class->dispose = gtk_places_sidebar_dispose;
gobject_class->set_property = gtk_places_sidebar_set_property;
gobject_class->get_property = gtk_places_sidebar_get_property;
GTK_WIDGET_CLASS (class)->focus = gtk_places_sidebar_focus;
/**
* GtkPlacesSidebar::open-location:
* @sidebar: the object which received the signal.
* @location: (type Gio.File): #GFile to which the caller should switch.
* @open_flags: a single value from #GtkPlacesOpenFlags specifying how the @location should be opened.
*
* The places sidebar emits this signal when the user selects a location
* in it. The calling application should display the contents of that
* location; for example, a file manager should show a list of files in
* the specified location.
*
* Since: 3.10
*/
places_sidebar_signals [OPEN_LOCATION] =
g_signal_new (I_("open-location"),
G_OBJECT_CLASS_TYPE (gobject_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (GtkPlacesSidebarClass, open_location),
NULL, NULL,
_gtk_marshal_VOID__OBJECT_FLAGS,
G_TYPE_NONE, 2,
G_TYPE_OBJECT,
GTK_TYPE_PLACES_OPEN_FLAGS);
/**
* GtkPlacesSidebar::populate-popup:
* @sidebar: the object which received the signal.
* @menu: (type Gtk.Menu): a #GtkMenu.
* @selected_item: (type Gio.File) (nullable): #GFile with the item to which the menu should refer, or #NULL in the case of a @selected_volume.
* @selected_volume: (type Gio.Volume) (nullable): #GVolume if the selected item is a volume, or #NULL if it is a file.
*
* The places sidebar emits this signal when the user invokes a contextual
* menu on one of its items. In the signal handler, the application may
* add extra items to the menu as appropriate. For example, a file manager
* may want to add a "Properties" command to the menu.
*
* It is not necessary to store the @selected_item for each menu item;
* during their GtkMenuItem::activate callbacks, the application can use
* gtk_places_sidebar_get_location() to get the file to which the item
* refers.
*
* The @selected_item argument may be #NULL in case the selection refers to
* a volume. In this case, @selected_volume will be non-NULL. In this case,
* the calling application will have to g_object_ref() the @selected_volume and
* keep it around for the purposes of its menu item's "activate" callback.
*
* The @menu and all its menu items are destroyed after the user
* dismisses the menu. The menu is re-created (and thus, this signal is
* emitted) every time the user activates the contextual menu.
*
* Since: 3.10
*/
places_sidebar_signals [POPULATE_POPUP] =
g_signal_new (I_("populate-popup"),
G_OBJECT_CLASS_TYPE (gobject_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (GtkPlacesSidebarClass, populate_popup),
NULL, NULL,
_gtk_marshal_VOID__OBJECT_OBJECT_OBJECT,
G_TYPE_NONE, 3,
G_TYPE_OBJECT,
G_TYPE_OBJECT,
G_TYPE_OBJECT);
/**
* GtkPlacesSidebar::show-error-message:
* @sidebar: the object which received the signal.
* @primary: primary message with a summary of the error to show.
* @secondary: secondary message with details of the error to show.
*
* The places sidebar emits this signal when it needs the calling
* application to present an error message. Most of these messages
* refer to mounting or unmounting media, for example, when a drive
* cannot be started for some reason.
*
* Since: 3.10
*/
places_sidebar_signals [SHOW_ERROR_MESSAGE] =
g_signal_new (I_("show-error-message"),
G_OBJECT_CLASS_TYPE (gobject_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (GtkPlacesSidebarClass, show_error_message),
NULL, NULL,
_gtk_marshal_VOID__STRING_STRING,
G_TYPE_NONE, 2,
G_TYPE_STRING,
G_TYPE_STRING);
/**
* GtkPlacesSidebar::show-connect-to-server:
* @sidebar: the object which received the signal.
*
* The places sidebar emits this signal when it needs the calling
* application to present an way to connect directly to a network server.
* For example, the application may bring up a dialog box asking for
* a URL like "sftp://ftp.example.com". It is up to the application to create
* the corresponding mount by using, for example, g_file_mount_enclosing_volume().
*
* Since: 3.10
*/
places_sidebar_signals [SHOW_CONNECT_TO_SERVER] =
g_signal_new (I_("show-connect-to-server"),
G_OBJECT_CLASS_TYPE (gobject_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (GtkPlacesSidebarClass, show_connect_to_server),
NULL, NULL,
_gtk_marshal_VOID__VOID,
G_TYPE_NONE, 0);
/**
* GtkPlacesSidebar::show-enter-location:
* @sidebar: the object which received the signal.
*
* The places sidebar emits this signal when it needs the calling
* application to present an way to directly enter a location.
* For example, the application may bring up a dialog box asking for
* a URL like "http://http.example.com".
*
* Since: 3.14
*/
places_sidebar_signals [SHOW_ENTER_LOCATION] =
g_signal_new (I_("show-enter-location"),
G_OBJECT_CLASS_TYPE (gobject_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (GtkPlacesSidebarClass, show_enter_location),
NULL, NULL,
_gtk_marshal_VOID__VOID,
G_TYPE_NONE, 0);
/**
* GtkPlacesSidebar::drag-action-requested:
* @sidebar: the object which received the signal.
* @context: (type Gdk.DragContext): #GdkDragContext with information about the drag operation
* @dest_file: (type Gio.File): #GFile with the tentative location that is being hovered for a drop
* @source_file_list: (type GLib.List) (element-type GFile) (transfer none):
* List of #GFile that are being dragged
*
* When the user starts a drag-and-drop operation and the sidebar needs
* to ask the application for which drag action to perform, then the
* sidebar will emit this signal.
*
* The application can evaluate the @context for customary actions, or
* it can check the type of the files indicated by @source_file_list against the
* possible actions for the destination @dest_file.
*
* The drag action to use must be the return value of the signal handler.
*
* Returns: The drag action to use, for example, #GDK_ACTION_COPY
* or #GDK_ACTION_MOVE, or 0 if no action is allowed here (i.e. drops
* are not allowed in the specified @dest_file).
*
* Since: 3.10
*/
places_sidebar_signals [DRAG_ACTION_REQUESTED] =
g_signal_new (I_("drag-action-requested"),
G_OBJECT_CLASS_TYPE (gobject_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GtkPlacesSidebarClass, drag_action_requested),
NULL, NULL,
_gtk_marshal_INT__OBJECT_OBJECT_POINTER,
G_TYPE_INT, 3,
GDK_TYPE_DRAG_CONTEXT,
G_TYPE_OBJECT,
G_TYPE_POINTER /* GList of GFile */ );
/**
* GtkPlacesSidebar::drag-action-ask:
* @sidebar: the object which received the signal.
* @actions: Possible drag actions that need to be asked for.
*
* The places sidebar emits this signal when it needs to ask the application
* to pop up a menu to ask the user for which drag action to perform.
*
* Returns: the final drag action that the sidebar should pass to the drag side
* of the drag-and-drop operation.
*
* Since: 3.10
*/
places_sidebar_signals [DRAG_ACTION_ASK] =
g_signal_new (I_("drag-action-ask"),
G_OBJECT_CLASS_TYPE (gobject_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GtkPlacesSidebarClass, drag_action_ask),
NULL, NULL,
_gtk_marshal_INT__INT,
G_TYPE_INT, 1,
G_TYPE_INT);
/**
* GtkPlacesSidebar::drag-perform-drop:
* @sidebar: the object which received the signal.
* @dest_file: (type Gio.File): Destination #GFile.
* @source_file_list: (type GLib.List) (element-type GFile) (transfer none):
* #GList of #GFile that got dropped.
* @action: Drop action to perform.
*
* The places sidebar emits this signal when the user completes a
* drag-and-drop operation and one of the sidebar's items is the
* destination. This item is in the @dest_file, and the
* @source_file_list has the list of files that are dropped into it and
* which should be copied/moved/etc. based on the specified @action.
*
* Since: 3.10
*/
places_sidebar_signals [DRAG_PERFORM_DROP] =
g_signal_new (I_("drag-perform-drop"),
G_OBJECT_CLASS_TYPE (gobject_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (GtkPlacesSidebarClass, drag_perform_drop),
NULL, NULL,
_gtk_marshal_VOID__OBJECT_POINTER_INT,
G_TYPE_NONE, 3,
G_TYPE_OBJECT,
G_TYPE_POINTER, /* GList of GFile */
G_TYPE_INT);
properties[PROP_LOCATION] =
g_param_spec_object ("location",
P_("Location to Select"),
P_("The location to highlight in the sidebar"),
G_TYPE_FILE,
G_PARAM_READWRITE);
properties[PROP_OPEN_FLAGS] =
g_param_spec_flags ("open-flags",
P_("Open Flags"),
P_("Modes in which the calling application can open locations selected in the sidebar"),
GTK_TYPE_PLACES_OPEN_FLAGS,
GTK_PLACES_OPEN_NORMAL,
G_PARAM_READWRITE);
properties[PROP_SHOW_RECENT] =
g_param_spec_boolean ("show-recent",
P_("Show recent files"),
P_("Whether the sidebar includes a builtin shortcut for recent files"),
TRUE,
G_PARAM_READWRITE);
properties[PROP_SHOW_DESKTOP] =
g_param_spec_boolean ("show-desktop",
P_("Show 'Desktop'"),
P_("Whether the sidebar includes a builtin shortcut to the Desktop folder"),
TRUE,
G_PARAM_READWRITE);
properties[PROP_SHOW_CONNECT_TO_SERVER] =
g_param_spec_boolean ("show-connect-to-server",
P_("Show 'Connect to Server'"),
P_("Whether the sidebar includes a builtin shortcut to a 'Connect to server' dialog"),
FALSE,
G_PARAM_READWRITE);
properties[PROP_SHOW_ENTER_LOCATION] =
g_param_spec_boolean ("show-enter-location",
P_("Show 'Enter Location'"),
P_("Whether the sidebar includes a builtin shortcut to manually enter a location"),
FALSE,
G_PARAM_READWRITE);
properties[PROP_LOCAL_ONLY] =
g_param_spec_boolean ("local-only",
P_("Local Only"),
P_("Whether the sidebar only includes local files"),
FALSE,
G_PARAM_READWRITE);
g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties);
}
/**
* gtk_places_sidebar_new:
*
* Creates a new #GtkPlacesSidebar widget.
*
* The application should connect to at least the
* #GtkPlacesSidebar::open-location signal to be notified
* when the user makes a selection in the sidebar.
*
* Returns: a newly created #GtkPlacesSidebar
*
* Since: 3.10
*/
GtkWidget *
gtk_places_sidebar_new (void)
{
return GTK_WIDGET (g_object_new (gtk_places_sidebar_get_type (), NULL));
}
/* Drag and drop interfaces */
/* GtkTreeDragSource::row_draggable implementation for the shortcuts model */
static gboolean
shortcuts_model_row_draggable (GtkTreeDragSource *drag_source,
GtkTreePath *path)
{
GtkTreeModel *model;
GtkTreeIter iter;
PlaceType place_type;
SectionType section_type;
model = GTK_TREE_MODEL (drag_source);
gtk_tree_model_get_iter (model, &iter, path);
gtk_tree_model_get (model, &iter,
PLACES_SIDEBAR_COLUMN_ROW_TYPE, &place_type,
PLACES_SIDEBAR_COLUMN_SECTION_TYPE, &section_type,
-1);
if (place_type != PLACES_HEADING && section_type == SECTION_BOOKMARKS)
return TRUE;
return FALSE;
}
/* Fill the GtkTreeDragSourceIface vtable */
static void
shortcuts_model_class_init (ShortcutsModelClass *klass)
{
}
static void
shortcuts_model_init (ShortcutsModel *model)
{
model->sidebar = NULL;
}
static void
shortcuts_model_drag_source_iface_init (GtkTreeDragSourceIface *iface)
{
iface->row_draggable = shortcuts_model_row_draggable;
}
static GtkListStore *
shortcuts_model_new (GtkPlacesSidebar *sidebar)
{
ShortcutsModel *model;
GType model_types[PLACES_SIDEBAR_COLUMN_COUNT] = {
G_TYPE_INT,
G_TYPE_STRING,
G_TYPE_DRIVE,
G_TYPE_VOLUME,
G_TYPE_MOUNT,
G_TYPE_STRING,
G_TYPE_ICON,
G_TYPE_INT,
G_TYPE_BOOLEAN,
G_TYPE_BOOLEAN,
G_TYPE_BOOLEAN,
G_TYPE_STRING,
G_TYPE_INT,
G_TYPE_STRING
};
model = g_object_new (shortcuts_model_get_type (), NULL);
model->sidebar = sidebar;
gtk_list_store_set_column_types (GTK_LIST_STORE (model),
PLACES_SIDEBAR_COLUMN_COUNT,
model_types);
return GTK_LIST_STORE (model);
}
/* Public methods for GtkPlacesSidebar */
/**
* gtk_places_sidebar_set_open_flags:
* @sidebar: a places sidebar
* @flags: Bitmask of modes in which the calling application can open locations
*
* Sets the way in which the calling application can open new locations from
* the places sidebar. For example, some applications only open locations
* “directly” into their main view, while others may support opening locations
* in a new notebook tab or a new window.
*
* This function is used to tell the places @sidebar about the ways in which the
* application can open new locations, so that the sidebar can display (or not)
* the “Open in new tab” and “Open in new window” menu items as appropriate.
*
* When the #GtkPlacesSidebar::open-location signal is emitted, its flags
* argument will be set to one of the @flags that was passed in
* gtk_places_sidebar_set_open_flags().
*
* Passing 0 for @flags will cause #GTK_PLACES_OPEN_NORMAL to always be sent
* to callbacks for the “open-location” signal.
*
* Since: 3.10
*/
void
gtk_places_sidebar_set_open_flags (GtkPlacesSidebar *sidebar,
GtkPlacesOpenFlags flags)
{
g_return_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar));
if (sidebar->open_flags != flags)
{
sidebar->open_flags = flags;
g_object_notify_by_pspec (G_OBJECT (sidebar), properties[PROP_OPEN_FLAGS]);
}
}
/**
* gtk_places_sidebar_get_open_flags:
* @sidebar: a #GtkPlacesSidebar
*
* Gets the open flags.
*
* Returns: the #GtkPlacesOpenFlags of @sidebar
*
* Since: 3.10
*/
GtkPlacesOpenFlags
gtk_places_sidebar_get_open_flags (GtkPlacesSidebar *sidebar)
{
g_return_val_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar), 0);
return sidebar->open_flags;
}
/**
* gtk_places_sidebar_set_location:
* @sidebar: a places sidebar
* @location: (allow-none): location to select, or #NULL for no current path
*
* Sets the location that is being shown in the widgets surrounding the
* @sidebar, for example, in a folder view in a file manager. In turn, the
* @sidebar will highlight that location if it is being shown in the list of
* places, or it will unhighlight everything if the @location is not among the
* places in the list.
*
* Since: 3.10
*/
void
gtk_places_sidebar_set_location (GtkPlacesSidebar *sidebar,
GFile *location)
{
GtkTreeSelection *selection;
GtkTreeIter iter;
gboolean valid;
gchar *iter_uri;
gchar *uri;
g_return_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar));
selection = gtk_tree_view_get_selection (sidebar->tree_view);
gtk_tree_selection_unselect_all (selection);
if (sidebar->current_location != NULL)
g_object_unref (sidebar->current_location);
sidebar->current_location = location;
if (sidebar->current_location != NULL)
g_object_ref (sidebar->current_location);
if (location == NULL)
goto out;
uri = g_file_get_uri (location);
valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (sidebar->store), &iter);
while (valid)
{
gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store), &iter,
PLACES_SIDEBAR_COLUMN_URI, &iter_uri,
-1);
if (iter_uri != NULL)
{
if (strcmp (iter_uri, uri) == 0)
{
g_free (iter_uri);
gtk_tree_selection_select_iter (selection, &iter);
break;
}
g_free (iter_uri);
}
valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (sidebar->store), &iter);
}
g_free (uri);
out:
g_object_notify_by_pspec (G_OBJECT (sidebar), properties[PROP_LOCATION]);
}
/**
* gtk_places_sidebar_get_location:
* @sidebar: a places sidebar
*
* Gets the currently-selected location in the @sidebar. This can be #NULL when
* nothing is selected, for example, when gtk_places_sidebar_set_location() has
* been called with a location that is not among the sidebars list of places to
* show.
*
* You can use this function to get the selection in the @sidebar. Also, if you
* connect to the #GtkPlacesSidebar::populate-popup signal, you can use this
* function to get the location that is being referred to during the callbacks
* for your menu items.
*
* Returns: (transfer full): a GFile with the selected location, or #NULL if nothing is visually
* selected.
*
* Since: 3.10
*/
GFile *
gtk_places_sidebar_get_location (GtkPlacesSidebar *sidebar)
{
GtkTreeIter iter;
GFile *file;
g_return_val_if_fail (sidebar != NULL, NULL);
file = NULL;
if (get_selected_iter (sidebar, &iter))
{
gchar *uri;
gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store), &iter,
PLACES_SIDEBAR_COLUMN_URI, &uri,
-1);
file = g_file_new_for_uri (uri);
g_free (uri);
}
return file;
}
/**
* gtk_places_sidebar_set_show_recent:
* @sidebar: a places sidebar
* @show_recent: whether to show an item for recent files
*
* Sets whether the @sidebar should show an item for recent files.
* The default value for this option is determined by the desktop
* environment, but this function can be used to override it on a
* per-application basis.
*
* Since: 3.18
*/
void
gtk_places_sidebar_set_show_recent (GtkPlacesSidebar *sidebar,
gboolean show_recent)
{
g_return_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar));
sidebar->show_recent_set = TRUE;
show_recent = !!show_recent;
if (sidebar->show_recent != show_recent)
{
sidebar->show_recent = show_recent;
update_places (sidebar);
g_object_notify_by_pspec (G_OBJECT (sidebar), properties[PROP_SHOW_RECENT]);
}
}
/**
* gtk_places_sidebar_get_show_recent:
* @sidebar: a places sidebar
*
* Returns the value previously set with gtk_places_sidebar_set_show_recent()
*
* Returns: %TRUE if the sidebar will display a builtin shortcut for recent files
*
* Since: 3.18
*/
gboolean
gtk_places_sidebar_get_show_recent (GtkPlacesSidebar *sidebar)
{
g_return_val_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar), FALSE);
return sidebar->show_recent;
}
/**
* gtk_places_sidebar_set_show_desktop:
* @sidebar: a places sidebar
* @show_desktop: whether to show an item for the Desktop folder
*
* Sets whether the @sidebar should show an item for the Desktop folder.
* The default value for this option is determined by the desktop
* environment and the users configuration, but this function can be
* used to override it on a per-application basis.
*
* Since: 3.10
*/
void
gtk_places_sidebar_set_show_desktop (GtkPlacesSidebar *sidebar,
gboolean show_desktop)
{
g_return_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar));
/* Don't bother disconnecting from the GtkSettings -- it will just
* complicate things. Besides, it's highly unlikely that this will
* change while we're running, but we can ignore it if it does.
*/
sidebar->show_desktop_set = TRUE;
show_desktop = !!show_desktop;
if (sidebar->show_desktop != show_desktop)
{
sidebar->show_desktop = show_desktop;
update_places (sidebar);
g_object_notify_by_pspec (G_OBJECT (sidebar), properties[PROP_SHOW_DESKTOP]);
}
}
/**
* gtk_places_sidebar_get_show_desktop:
* @sidebar: a places sidebar
*
* Returns the value previously set with gtk_places_sidebar_set_show_desktop()
*
* Returns: %TRUE if the sidebar will display a builtin shortcut to the desktop folder.
*
* Since: 3.10
*/
gboolean
gtk_places_sidebar_get_show_desktop (GtkPlacesSidebar *sidebar)
{
g_return_val_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar), FALSE);
return sidebar->show_desktop;
}
/**
* gtk_places_sidebar_set_show_connect_to_server:
* @sidebar: a places sidebar
* @show_connect_to_server: whether to show an item for the Connect to Server command
*
* Sets whether the @sidebar should show an item for connecting to a network server; this is off by default.
* An application may want to turn this on if it implements a way for the user to connect
* to network servers directly.
*
* Since: 3.10
*/
void
gtk_places_sidebar_set_show_connect_to_server (GtkPlacesSidebar *sidebar,
gboolean show_connect_to_server)
{
g_return_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar));
show_connect_to_server = !!show_connect_to_server;
if (sidebar->show_connect_to_server != show_connect_to_server)
{
sidebar->show_connect_to_server = show_connect_to_server;
update_places (sidebar);
g_object_notify_by_pspec (G_OBJECT (sidebar), properties[PROP_SHOW_CONNECT_TO_SERVER]);
}
}
/**
* gtk_places_sidebar_get_show_connect_to_server:
* @sidebar: a places sidebar
*
* Returns the value previously set with gtk_places_sidebar_set_show_connect_to_server()
*
* Returns: %TRUE if the sidebar will display a “Connect to Server” item.
*
* Since: 3.10
*/
gboolean
gtk_places_sidebar_get_show_connect_to_server (GtkPlacesSidebar *sidebar)
{
g_return_val_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar), FALSE);
return sidebar->show_connect_to_server;
}
/**
* gtk_places_sidebar_set_show_enter_location:
* @sidebar: a places sidebar
* @show_enter_location: whether to show an item for the Connect to Server command
*
* Sets whether the @sidebar should show an item for connecting to a network server; this is off by default.
* An application may want to turn this on if it implements a way for the user to connect
* to network servers directly.
*
* Since: 3.14
*/
void
gtk_places_sidebar_set_show_enter_location (GtkPlacesSidebar *sidebar,
gboolean show_enter_location)
{
g_return_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar));
show_enter_location = !!show_enter_location;
if (sidebar->show_enter_location != show_enter_location)
{
sidebar->show_enter_location = show_enter_location;
update_places (sidebar);
g_object_notify_by_pspec (G_OBJECT (sidebar), properties[PROP_SHOW_ENTER_LOCATION]);
}
}
/**
* gtk_places_sidebar_get_show_enter_location:
* @sidebar: a places sidebar
*
* Returns the value previously set with gtk_places_sidebar_set_show_enter_location()
*
* Returns: %TRUE if the sidebar will display an “Enter Location” item.
*
* Since: 3.14
*/
gboolean
gtk_places_sidebar_get_show_enter_location (GtkPlacesSidebar *sidebar)
{
g_return_val_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar), FALSE);
return sidebar->show_enter_location;
}
/**
* gtk_places_sidebar_set_local_only:
* @sidebar: a places sidebar
* @local_only: whether to show only local files
*
* Sets whether the @sidebar should only show local files.
*
* Since: 3.12
*/
void
gtk_places_sidebar_set_local_only (GtkPlacesSidebar *sidebar,
gboolean local_only)
{
g_return_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar));
local_only = !!local_only;
if (sidebar->local_only != local_only)
{
sidebar->local_only = local_only;
update_places (sidebar);
g_object_notify_by_pspec (G_OBJECT (sidebar), properties[PROP_LOCAL_ONLY]);
}
}
/**
* gtk_places_sidebar_get_local_only:
* @sidebar: a places sidebar
*
* Returns the value previously set with gtk_places_sidebar_set_local_only().
*
* Returns: %TRUE if the sidebar will only show local files.
*
* Since: 3.12
*/
gboolean
gtk_places_sidebar_get_local_only (GtkPlacesSidebar *sidebar)
{
g_return_val_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar), FALSE);
return sidebar->local_only;
}
static GSList *
find_shortcut_link (GtkPlacesSidebar *sidebar,
GFile *location)
{
GSList *l;
for (l = sidebar->shortcuts; l; l = l->next)
{
GFile *shortcut;
shortcut = G_FILE (l->data);
if (g_file_equal (shortcut, location))
return l;
}
return NULL;
}
/**
* gtk_places_sidebar_add_shortcut:
* @sidebar: a places sidebar
* @location: location to add as an application-specific shortcut
*
* Applications may want to present some folders in the places sidebar if
* they could be immediately useful to users. For example, a drawing
* program could add a “/usr/share/clipart” location when the sidebar is
* being used in an “Insert Clipart” dialog box.
*
* This function adds the specified @location to a special place for immutable
* shortcuts. The shortcuts are application-specific; they are not shared
* across applications, and they are not persistent. If this function
* is called multiple times with different locations, then they are added
* to the sidebars list in the same order as the function is called.
*
* Since: 3.10
*/
void
gtk_places_sidebar_add_shortcut (GtkPlacesSidebar *sidebar,
GFile *location)
{
g_return_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar));
g_return_if_fail (G_IS_FILE (location));
g_object_ref (location);
sidebar->shortcuts = g_slist_append (sidebar->shortcuts, location);
update_places (sidebar);
}
/**
* gtk_places_sidebar_remove_shortcut:
* @sidebar: a places sidebar
* @location: location to remove
*
* Removes an application-specific shortcut that has been previously been
* inserted with gtk_places_sidebar_add_shortcut(). If the @location is not a
* shortcut in the sidebar, then nothing is done.
*
* Since: 3.10
*/
void
gtk_places_sidebar_remove_shortcut (GtkPlacesSidebar *sidebar,
GFile *location)
{
GSList *link;
GFile *shortcut;
g_return_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar));
g_return_if_fail (G_IS_FILE (location));
link = find_shortcut_link (sidebar, location);
if (!link)
return;
shortcut = G_FILE (link->data);
g_object_unref (shortcut);
sidebar->shortcuts = g_slist_delete_link (sidebar->shortcuts, link);
update_places (sidebar);
}
/**
* gtk_places_sidebar_list_shortcuts:
* @sidebar: a places sidebar
*
* Gets the list of shortcuts.
*
* Returns: (element-type GFile) (transfer full):
* A #GSList of #GFile of the locations that have been added as
* application-specific shortcuts with gtk_places_sidebar_add_shortcut().
* To free this list, you can use
* |[<!-- language="C" -->
* g_slist_free_full (list, (GDestroyNotify) g_object_unref);
* ]|
*
* Since: 3.10
*/
GSList *
gtk_places_sidebar_list_shortcuts (GtkPlacesSidebar *sidebar)
{
g_return_val_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar), NULL);
return g_slist_copy_deep (sidebar->shortcuts, (GCopyFunc) g_object_ref, NULL);
}
/**
* gtk_places_sidebar_get_nth_bookmark:
* @sidebar: a places sidebar
* @n: index of the bookmark to query
*
* This function queries the bookmarks added by the user to the places sidebar,
* and returns one of them. This function is used by #GtkFileChooser to implement
* the “Alt-1”, “Alt-2”, etc. shortcuts, which activate the cooresponding bookmark.
*
* Returns: (transfer full): The bookmark specified by the index @n, or
* #NULL if no such index exist. Note that the indices start at 0, even though
* the file chooser starts them with the keyboard shortcut “Alt-1”.
*
* Since: 3.10
*/
GFile *
gtk_places_sidebar_get_nth_bookmark (GtkPlacesSidebar *sidebar,
gint n)
{
GtkTreeIter iter;
int k;
GFile *file;
g_return_val_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar), NULL);
file = NULL;
if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (sidebar->store), &iter))
{
k = 0;
do
{
PlaceType place_type;
gchar *uri;
gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store), &iter,
PLACES_SIDEBAR_COLUMN_ROW_TYPE, &place_type,
PLACES_SIDEBAR_COLUMN_URI, &uri,
-1);
if (place_type == PLACES_BOOKMARK)
{
if (k == n)
{
file = g_file_new_for_uri (uri);
g_free (uri);
break;
}
g_free (uri);
k++;
}
}
while (gtk_tree_model_iter_next (GTK_TREE_MODEL (sidebar->store), &iter));
}
return file;
}