diff --git a/docs/reference/gtk/gtk-docs.sgml b/docs/reference/gtk/gtk-docs.sgml index df6af83c7b..671c3b67d6 100644 --- a/docs/reference/gtk/gtk-docs.sgml +++ b/docs/reference/gtk/gtk-docs.sgml @@ -220,6 +220,7 @@ + diff --git a/docs/reference/gtk/gtk3-sections.txt b/docs/reference/gtk/gtk3-sections.txt index deb83f9eb5..39721dd959 100644 --- a/docs/reference/gtk/gtk3-sections.txt +++ b/docs/reference/gtk/gtk3-sections.txt @@ -2483,6 +2483,32 @@ GTK_PANED_GET_CLASS gtk_paned_get_type +
+gtkplacessidebar +GtkPlacesSidebar</FILE> +GtkPlacesSidebar +GtkPlacesOpenFlags +gtk_places_sidebar_new +gtk_places_sidebar_set_open_flags +gtk_places_sidebar_set_location +gtk_places_sidebar_get_location +gtk_places_sidebar_set_show_desktop +gtk_places_sidebar_set_accept_uri_drops +gtk_places_sidebar_add_shortcut +gtk_places_sidebar_remove_shortcut +gtk_places_sidebar_list_shortcuts +gtk_places_sidebar_get_nth_bookmark +<SUBSECTION Standard> +GTK_PLACES_SIDEBAR +GTK_IS_PLACES_SIDEBAR +GTK_TYPE_PLACES_SIDEBAR +GTK_PLACES_SIDEBAR_CLASS +GTK_IS_PLACES_SIDEBAR_CLASS +GTK_PLACES_SIDEBAR_GET_CLASS +<SUBSECTION Private> +gtk_places_sidebar_get_type +</SECTION> + <SECTION> <FILE>gtkplug</FILE> <TITLE>GtkPlug diff --git a/gtk/Makefile.am b/gtk/Makefile.am index 4741e2a4c4..6d621d3d61 100644 --- a/gtk/Makefile.am +++ b/gtk/Makefile.am @@ -295,6 +295,7 @@ gtk_public_h_sources = \ gtkpagesetup.h \ gtkpaned.h \ gtkpapersize.h \ + gtkplacessidebar.h \ gtkplug.h \ gtkprintcontext.h \ gtkprintoperation.h \ @@ -421,6 +422,7 @@ gtk_private_h_sources = \ gtkbindingsprivate.h \ gtkbitmaskprivate.h \ gtkbitmaskprivateimpl.h \ + gtkbookmarksmanager.h \ gtkborderimageprivate.h \ gtkboxprivate.h \ gtkbubblewindowprivate.h \ @@ -547,6 +549,7 @@ gtk_private_h_sources = \ gtktextutil.h \ gtkthemingbackgroundprivate.h \ gtkthemingengineprivate.h \ + gtktrashmonitor.h \ gtktoolpaletteprivate.h \ gtktreedatalist.h \ gtktreeprivate.h \ @@ -621,6 +624,7 @@ gtk_base_c_sources = \ gtkbbox.c \ gtkbin.c \ gtkbindings.c \ + gtkbookmarksmanager.c \ gtkborder.c \ gtkborderimage.c \ gtkbox.c \ @@ -783,6 +787,7 @@ gtk_base_c_sources = \ gtkpango.c \ gtkpapersize.c \ gtkpathbar.c \ + gtkplacessidebar.c \ gtkpressandhold.c \ gtkprintcontext.c \ gtkprintoperation.c \ @@ -866,6 +871,7 @@ gtk_base_c_sources = \ gtktoolpalette.c \ gtktoolshell.c \ gtktooltip.c \ + gtktrashmonitor.c \ gtktreedatalist.c \ gtktreednd.c \ gtktreemenu.c \ diff --git a/gtk/gtk-default.css b/gtk/gtk-default.css index d0776802f5..b125cf68e2 100644 --- a/gtk/gtk-default.css +++ b/gtk/gtk-default.css @@ -41,6 +41,13 @@ GtkTreeView.view.expander:selected:hover { color: @text_color; } +GtkTreeView.dnd { + border-color: @internal_element_color; + border-radius: 0; + border-width: 1px; + border-style: solid; +} + *:insensitive { border-color: shade (@bg_color, 0.7); background-color: shade (@bg_color, 0.9); diff --git a/gtk/gtk.h b/gtk/gtk.h index 3ebfaeaa84..61d4107c0c 100644 --- a/gtk/gtk.h +++ b/gtk/gtk.h @@ -145,6 +145,7 @@ #include #include #include +#include #include #include #include diff --git a/gtk/gtkbookmarksmanager.c b/gtk/gtkbookmarksmanager.c new file mode 100644 index 0000000000..91ccd89c29 --- /dev/null +++ b/gtk/gtkbookmarksmanager.c @@ -0,0 +1,596 @@ +/* -*- Mode: C; c-file-style: "gnu"; tab-width: 8 -*- */ +/* GTK - The GIMP Toolkit + * gtkbookmarksmanager.c: Utilities to manage and monitor ~/.gtk-bookmarks + * Copyright (C) 2003, Red Hat, Inc. + * Copyright (C) 2007-2008 Carlos Garnacho + * Copyright (C) 2011 Suse + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + * Authors: Federico Mena Quintero + */ + +#include "config.h" + +#include + +#include + +#include "gtkbookmarksmanager.h" +#include "gtkfilechooser.h" /* for the GError types */ + +static void +_gtk_bookmark_free (GtkBookmark *bookmark) +{ + g_object_unref (bookmark->file); + g_free (bookmark->label); + g_slice_free (GtkBookmark, bookmark); +} + +static GFile * +get_legacy_bookmarks_file (void) +{ + GFile *file; + gchar *filename; + + filename = g_build_filename (g_get_home_dir (), ".gtk-bookmarks", NULL); + file = g_file_new_for_path (filename); + g_free (filename); + + return file; +} + +static GFile * +get_bookmarks_file (void) +{ + GFile *file; + gchar *filename; + + filename = g_build_filename (g_get_user_config_dir (), "gtk-3.0", "bookmarks", NULL); + file = g_file_new_for_path (filename); + g_free (filename); + + return file; +} + +static GSList * +read_bookmarks (GFile *file) +{ + gchar *contents; + gchar **lines, *space; + GSList *bookmarks = NULL; + gint i; + + if (!g_file_load_contents (file, NULL, &contents, + NULL, NULL, NULL)) + return NULL; + + lines = g_strsplit (contents, "\n", -1); + + for (i = 0; lines[i]; i++) + { + GtkBookmark *bookmark; + + if (!*lines[i]) + continue; + + if (!g_utf8_validate (lines[i], -1, NULL)) + continue; + + bookmark = g_slice_new0 (GtkBookmark); + + if ((space = strchr (lines[i], ' ')) != NULL) + { + space[0] = '\0'; + bookmark->label = g_strdup (space + 1); + } + + bookmark->file = g_file_new_for_uri (lines[i]); + bookmarks = g_slist_prepend (bookmarks, bookmark); + } + + bookmarks = g_slist_reverse (bookmarks); + g_strfreev (lines); + g_free (contents); + + return bookmarks; +} + +static void +save_bookmarks (GFile *bookmarks_file, + GSList *bookmarks) +{ + GError *error = NULL; + GString *contents; + GSList *l; + + contents = g_string_new (""); + + for (l = bookmarks; l; l = l->next) + { + GtkBookmark *bookmark = l->data; + gchar *uri; + + uri = g_file_get_uri (bookmark->file); + if (!uri) + continue; + + g_string_append (contents, uri); + + if (bookmark->label) + g_string_append_printf (contents, " %s", bookmark->label); + + g_string_append_c (contents, '\n'); + g_free (uri); + } + + if (!g_file_replace_contents (bookmarks_file, + contents->str, + strlen (contents->str), + NULL, FALSE, 0, NULL, + NULL, &error)) + { + g_critical ("%s", error->message); + g_error_free (error); + } + + g_string_free (contents, TRUE); +} + +static void +notify_changed (GtkBookmarksManager *manager) +{ + if (manager->changed_func) + manager->changed_func (manager->changed_func_data); +} + +static void +bookmarks_file_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event, + gpointer data) +{ + GtkBookmarksManager *manager = data; + + switch (event) + { + case G_FILE_MONITOR_EVENT_CHANGED: + case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: + case G_FILE_MONITOR_EVENT_CREATED: + case G_FILE_MONITOR_EVENT_DELETED: + g_slist_foreach (manager->bookmarks, (GFunc) _gtk_bookmark_free, NULL); + g_slist_free (manager->bookmarks); + + manager->bookmarks = read_bookmarks (file); + + gdk_threads_enter (); + notify_changed (manager); + gdk_threads_leave (); + break; + + default: + /* ignore at the moment */ + break; + } +} + +GtkBookmarksManager * +_gtk_bookmarks_manager_new (GtkBookmarksChangedFunc changed_func, gpointer changed_func_data) +{ + GtkBookmarksManager *manager; + GFile *bookmarks_file; + GError *error; + + manager = g_new0 (GtkBookmarksManager, 1); + + manager->changed_func = changed_func; + manager->changed_func_data = changed_func_data; + + bookmarks_file = get_bookmarks_file (); + manager->bookmarks = read_bookmarks (bookmarks_file); + if (!manager->bookmarks) + { + GFile *legacy_bookmarks_file; + + /* Read the legacy one and write it to the new one */ + legacy_bookmarks_file = get_legacy_bookmarks_file (); + manager->bookmarks = read_bookmarks (legacy_bookmarks_file); + save_bookmarks (bookmarks_file, manager->bookmarks); + + g_object_unref (legacy_bookmarks_file); + } + + error = NULL; + manager->bookmarks_monitor = g_file_monitor_file (bookmarks_file, + G_FILE_MONITOR_NONE, + NULL, &error); + if (error) + { + g_warning ("%s", error->message); + g_error_free (error); + } + else + manager->bookmarks_monitor_changed_id = g_signal_connect (manager->bookmarks_monitor, "changed", + G_CALLBACK (bookmarks_file_changed), manager); + + g_object_unref (bookmarks_file); + + return manager; +} + +void +_gtk_bookmarks_manager_free (GtkBookmarksManager *manager) +{ + g_return_if_fail (manager != NULL); + + if (manager->bookmarks_monitor) + { + g_file_monitor_cancel (manager->bookmarks_monitor); + g_signal_handler_disconnect (manager->bookmarks_monitor, manager->bookmarks_monitor_changed_id); + manager->bookmarks_monitor_changed_id = 0; + g_object_unref (manager->bookmarks_monitor); + } + + if (manager->bookmarks) + { + g_slist_foreach (manager->bookmarks, (GFunc) _gtk_bookmark_free, NULL); + g_slist_free (manager->bookmarks); + } + + g_free (manager); +} + +GSList * +_gtk_bookmarks_manager_list_bookmarks (GtkBookmarksManager *manager) +{ + GSList *bookmarks, *files = NULL; + + g_return_val_if_fail (manager != NULL, NULL); + + bookmarks = manager->bookmarks; + + while (bookmarks) + { + GtkBookmark *bookmark; + + bookmark = bookmarks->data; + bookmarks = bookmarks->next; + + files = g_slist_prepend (files, g_object_ref (bookmark->file)); + } + + return g_slist_reverse (files); +} + +static GSList * +find_bookmark_link_for_file (GSList *bookmarks, GFile *file, int *position_ret) +{ + int pos; + + pos = 0; + for (; bookmarks; bookmarks = bookmarks->next) + { + GtkBookmark *bookmark = bookmarks->data; + + if (g_file_equal (file, bookmark->file)) + { + if (position_ret) + *position_ret = pos; + + return bookmarks; + } + + pos++; + } + + if (position_ret) + *position_ret = -1; + + return NULL; +} + +gboolean +_gtk_bookmarks_manager_has_bookmark (GtkBookmarksManager *manager, + GFile *file) +{ + GSList *link; + + link = find_bookmark_link_for_file (manager->bookmarks, file, NULL); + return (link != NULL); +} + +gboolean +_gtk_bookmarks_manager_insert_bookmark (GtkBookmarksManager *manager, + GFile *file, + gint position, + GError **error) +{ + GSList *link; + GtkBookmark *bookmark; + GFile *bookmarks_file; + + g_return_val_if_fail (manager != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + link = find_bookmark_link_for_file (manager->bookmarks, file, NULL); + + if (link) + { + bookmark = link->data; + gchar *uri = g_file_get_uri (bookmark->file); + + g_set_error (error, + GTK_FILE_CHOOSER_ERROR, + GTK_FILE_CHOOSER_ERROR_ALREADY_EXISTS, + "%s already exists in the bookmarks list", + uri); + + g_free (uri); + + return FALSE; + } + + bookmark = g_slice_new0 (GtkBookmark); + bookmark->file = g_object_ref (file); + + manager->bookmarks = g_slist_insert (manager->bookmarks, bookmark, position); + + bookmarks_file = get_bookmarks_file (); + save_bookmarks (bookmarks_file, manager->bookmarks); + g_object_unref (bookmarks_file); + + notify_changed (manager); + + return TRUE; +} + +gboolean +_gtk_bookmarks_manager_remove_bookmark (GtkBookmarksManager *manager, + GFile *file, + GError **error) +{ + GSList *link; + GFile *bookmarks_file; + + g_return_val_if_fail (manager != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (!manager->bookmarks) + return FALSE; + + link = find_bookmark_link_for_file (manager->bookmarks, file, NULL); + if (link) + { + GtkBookmark *bookmark = link->data; + + manager->bookmarks = g_slist_remove_link (manager->bookmarks, link); + _gtk_bookmark_free (bookmark); + g_slist_free_1 (link); + } + else + { + gchar *uri = g_file_get_uri (file); + + g_set_error (error, + GTK_FILE_CHOOSER_ERROR, + GTK_FILE_CHOOSER_ERROR_NONEXISTENT, + "%s does not exist in the bookmarks list", + uri); + + g_free (uri); + + return FALSE; + } + + bookmarks_file = get_bookmarks_file (); + save_bookmarks (bookmarks_file, manager->bookmarks); + g_object_unref (bookmarks_file); + + notify_changed (manager); + + return TRUE; +} + +gboolean +_gtk_bookmarks_manager_reorder_bookmark (GtkBookmarksManager *manager, + GFile *file, + gint new_position, + GError **error) +{ + GSList *link; + GFile *bookmarks_file; + int old_position; + + g_return_val_if_fail (manager != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + g_return_val_if_fail (new_position >= 0, FALSE); + + if (!manager->bookmarks) + return FALSE; + + link = find_bookmark_link_for_file (manager->bookmarks, file, &old_position); + if (new_position == old_position) + return TRUE; + + if (link) + { + GtkBookmark *bookmark = link->data; + + manager->bookmarks = g_slist_remove_link (manager->bookmarks, link); + g_slist_free_1 (link); + + if (new_position > old_position) + new_position--; + + manager->bookmarks = g_slist_insert (manager->bookmarks, bookmark, new_position); + } + else + { + gchar *uri = g_file_get_uri (file); + + g_set_error (error, + GTK_FILE_CHOOSER_ERROR, + GTK_FILE_CHOOSER_ERROR_NONEXISTENT, + "%s does not exist in the bookmarks list", + uri); + + g_free (uri); + + return FALSE; + } + + bookmarks_file = get_bookmarks_file (); + save_bookmarks (bookmarks_file, manager->bookmarks); + g_object_unref (bookmarks_file); + + notify_changed (manager); + + return TRUE; +} + +gchar * +_gtk_bookmarks_manager_get_bookmark_label (GtkBookmarksManager *manager, + GFile *file) +{ + GSList *bookmarks; + gchar *label = NULL; + + g_return_val_if_fail (manager != NULL, NULL); + g_return_val_if_fail (file != NULL, NULL); + + bookmarks = manager->bookmarks; + + while (bookmarks) + { + GtkBookmark *bookmark; + + bookmark = bookmarks->data; + bookmarks = bookmarks->next; + + if (g_file_equal (file, bookmark->file)) + { + label = g_strdup (bookmark->label); + break; + } + } + + return label; +} + +gboolean +_gtk_bookmarks_manager_set_bookmark_label (GtkBookmarksManager *manager, + GFile *file, + const gchar *label, + GError **error) +{ + GFile *bookmarks_file; + GSList *link; + + g_return_val_if_fail (manager != NULL, FALSE); + g_return_val_if_fail (file != NULL, FALSE); + + link = find_bookmark_link_for_file (manager->bookmarks, file, NULL); + if (link) + { + GtkBookmark *bookmark = link->data; + + g_free (bookmark->label); + bookmark->label = g_strdup (label); + } + else + { + gchar *uri = g_file_get_uri (file); + + g_set_error (error, + GTK_FILE_CHOOSER_ERROR, + GTK_FILE_CHOOSER_ERROR_NONEXISTENT, + "%s does not exist in the bookmarks list", + uri); + + g_free (uri); + + return FALSE; + } + + bookmarks_file = get_bookmarks_file (); + save_bookmarks (bookmarks_file, manager->bookmarks); + g_object_unref (bookmarks_file); + + notify_changed (manager); + + return TRUE; +} + +gboolean +_gtk_bookmarks_manager_get_xdg_type (GtkBookmarksManager *manager, + GFile *file, + GUserDirectory *directory) +{ + GSList *link; + gboolean match; + GFile *location; + const gchar *path; + GUserDirectory dir; + GtkBookmark *bookmark; + + link = find_bookmark_link_for_file (manager->bookmarks, file, NULL); + if (!link) + return FALSE; + + match = FALSE; + bookmark = link->data; + + for (dir = 0; dir < G_USER_N_DIRECTORIES; dir++) + { + path = g_get_user_special_dir (dir); + if (!path) + continue; + + location = g_file_new_for_path (path); + match = g_file_equal (location, bookmark->file); + g_object_unref (location); + + if (match) + break; + } + + if (match && directory != NULL) + *directory = dir; + + return match; +} + +gboolean +_gtk_bookmarks_manager_get_is_builtin (GtkBookmarksManager *manager, + GFile *file) +{ + GUserDirectory xdg_type; + + /* if this is not an XDG dir, it's never builtin */ + if (!_gtk_bookmarks_manager_get_xdg_type (manager, file, &xdg_type)) + return FALSE; + + /* exclude XDG locations we don't display by default */ + return _gtk_bookmarks_manager_get_is_xdg_dir_builtin (xdg_type); +} + +gboolean +_gtk_bookmarks_manager_get_is_xdg_dir_builtin (GUserDirectory xdg_type) +{ + return (xdg_type != G_USER_DIRECTORY_DESKTOP) && + (xdg_type != G_USER_DIRECTORY_TEMPLATES) && + (xdg_type != G_USER_DIRECTORY_PUBLIC_SHARE); +} diff --git a/gtk/gtkbookmarksmanager.h b/gtk/gtkbookmarksmanager.h new file mode 100644 index 0000000000..f2efd6577b --- /dev/null +++ b/gtk/gtkbookmarksmanager.h @@ -0,0 +1,91 @@ +/* -*- Mode: C; c-file-style: "gnu"; tab-width: 8 -*- */ +/* GTK - The GIMP Toolkit + * gtkbookmarksmanager.h: Utilities to manage and monitor ~/.gtk-bookmarks + * Copyright (C) 2003, Red Hat, Inc. + * Copyright (C) 2007-2008 Carlos Garnacho + * Copyright (C) 2011 Suse + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + * Authors: Federico Mena Quintero + */ + +#ifndef __GTK_BOOKMARKS_MANAGER_H__ +#define __GTK_BOOKMARKS_MANAGER_H__ + +#include + +typedef void (* GtkBookmarksChangedFunc) (gpointer data); + +typedef struct +{ + /* This list contains GtkBookmark structs */ + GSList *bookmarks; + + GFileMonitor *bookmarks_monitor; + gulong bookmarks_monitor_changed_id; + + gpointer changed_func_data; + GtkBookmarksChangedFunc changed_func; +} GtkBookmarksManager; + +typedef struct +{ + GFile *file; + gchar *label; +} GtkBookmark; + +GtkBookmarksManager *_gtk_bookmarks_manager_new (GtkBookmarksChangedFunc changed_func, + gpointer changed_func_data); + + +void _gtk_bookmarks_manager_free (GtkBookmarksManager *manager); + +GSList *_gtk_bookmarks_manager_list_bookmarks (GtkBookmarksManager *manager); + +gboolean _gtk_bookmarks_manager_insert_bookmark (GtkBookmarksManager *manager, + GFile *file, + gint position, + GError **error); + +gboolean _gtk_bookmarks_manager_remove_bookmark (GtkBookmarksManager *manager, + GFile *file, + GError **error); + +gboolean _gtk_bookmarks_manager_reorder_bookmark (GtkBookmarksManager *manager, + GFile *file, + gint new_position, + GError **error); + +gboolean _gtk_bookmarks_manager_has_bookmark (GtkBookmarksManager *manager, + GFile *file); + +gchar * _gtk_bookmarks_manager_get_bookmark_label (GtkBookmarksManager *manager, + GFile *file); + +gboolean _gtk_bookmarks_manager_set_bookmark_label (GtkBookmarksManager *manager, + GFile *file, + const gchar *label, + GError **error); + +gboolean _gtk_bookmarks_manager_get_xdg_type (GtkBookmarksManager *manager, + GFile *file, + GUserDirectory *directory); +gboolean _gtk_bookmarks_manager_get_is_builtin (GtkBookmarksManager *manager, + GFile *file); + +gboolean _gtk_bookmarks_manager_get_is_xdg_dir_builtin (GUserDirectory xdg_type); + +#endif /* __GTK_BOOKMARKS_MANAGER_H__ */ diff --git a/gtk/gtkfilechooser.c b/gtk/gtkfilechooser.c index 141e8bf76e..2600a136a9 100644 --- a/gtk/gtkfilechooser.c +++ b/gtk/gtkfilechooser.c @@ -55,9 +55,8 @@ * * Shortcuts * - * can be provided by the application or by the underlying filesystem - * abstraction (e.g. both the gnome-vfs and the Windows filesystems - * provide "Desktop" shortcuts). Shortcuts cannot be modified by the + * can be provided by the application. For example, a Paint program may + * want to add a shortcut for a Clipart folder. Shortcuts cannot be modified by the * user. * * diff --git a/gtk/gtkfilechooserbutton.c b/gtk/gtkfilechooserbutton.c index 86403d650c..8c744ce8fd 100644 --- a/gtk/gtkfilechooserbutton.c +++ b/gtk/gtkfilechooserbutton.c @@ -186,12 +186,13 @@ struct _GtkFileChooserButtonPrivate GFile *current_folder_while_inactive; gulong fs_volumes_changed_id; - gulong fs_bookmarks_changed_id; GCancellable *dnd_select_folder_cancellable; GCancellable *update_button_cancellable; GSList *change_icon_theme_cancellables; + GtkBookmarksManager *bookmarks_manager; + gint icon_size; guint8 n_special; @@ -323,8 +324,7 @@ static void update_label_and_image (GtkFileChooserButton *button) /* Child Object Callbacks */ static void fs_volumes_changed_cb (GtkFileSystem *fs, gpointer user_data); -static void fs_bookmarks_changed_cb (GtkFileSystem *fs, - gpointer user_data); +static void bookmarks_changed_cb (gpointer user_data); static void combo_box_changed_cb (GtkComboBox *combo_box, gpointer user_data); @@ -502,6 +502,8 @@ gtk_file_chooser_button_init (GtkFileChooserButton *button) gtk_widget_init_template (GTK_WIDGET (button)); + /* Bookmarks manager */ + priv->bookmarks_manager = _gtk_bookmarks_manager_new (bookmarks_changed_cb, button); gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (priv->combo_box), priv->name_cell, name_cell_data_func, NULL, NULL); @@ -849,7 +851,7 @@ gtk_file_chooser_button_constructor (GType type, model_add_volumes (button, list); g_slist_free (list); - list = _gtk_file_system_list_bookmarks (priv->fs); + list = _gtk_bookmarks_manager_list_bookmarks (priv->bookmarks_manager); model_add_bookmarks (button, list); g_slist_foreach (list, (GFunc) g_object_unref, NULL); g_slist_free (list); @@ -878,9 +880,6 @@ gtk_file_chooser_button_constructor (GType type, priv->fs_volumes_changed_id = g_signal_connect (priv->fs, "volumes-changed", G_CALLBACK (fs_volumes_changed_cb), object); - priv->fs_bookmarks_changed_id = - g_signal_connect (priv->fs, "bookmarks-changed", - G_CALLBACK (fs_bookmarks_changed_cb), object); update_label_and_image (button); update_combo_box (button); @@ -964,7 +963,7 @@ gtk_file_chooser_button_set_property (GObject *object, case GTK_FILE_CHOOSER_PROP_LOCAL_ONLY: g_object_set_property (G_OBJECT (priv->dialog), pspec->name, value); fs_volumes_changed_cb (priv->fs, button); - fs_bookmarks_changed_cb (priv->fs, button); + bookmarks_changed_cb (button); break; case GTK_FILE_CHOOSER_PROP_SELECT_MULTIPLE: @@ -1089,11 +1088,16 @@ gtk_file_chooser_button_destroy (GtkWidget *widget) if (priv->fs) { g_signal_handler_disconnect (priv->fs, priv->fs_volumes_changed_id); - g_signal_handler_disconnect (priv->fs, priv->fs_bookmarks_changed_id); g_object_unref (priv->fs); priv->fs = NULL; } + if (priv->bookmarks_manager) + { + _gtk_bookmarks_manager_free (priv->bookmarks_manager); + priv->bookmarks_manager = NULL; + } + GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->destroy (widget); } @@ -1604,7 +1608,7 @@ set_info_for_file_at_iter (GtkFileChooserButton *button, data = g_new0 (struct SetDisplayNameData, 1); data->button = g_object_ref (button); - data->label = _gtk_file_system_get_bookmark_label (button->priv->fs, file); + data->label = _gtk_bookmarks_manager_get_bookmark_label (button->priv->bookmarks_manager, file); tree_path = gtk_tree_model_get_path (button->priv->model, iter); data->row_ref = gtk_tree_row_reference_new (button->priv->model, tree_path); @@ -1988,7 +1992,7 @@ model_add_bookmarks (GtkFileChooserButton *button, * If we switch to a better bookmarks file format (XBEL), we * should use mime info to get a better icon. */ - label = _gtk_file_system_get_bookmark_label (button->priv->fs, file); + label = _gtk_bookmarks_manager_get_bookmark_label (button->priv->bookmarks_manager, file); if (!label) label = _gtk_file_chooser_label_for_file (file); @@ -2091,7 +2095,7 @@ model_update_current_folder (GtkFileChooserButton *button, * If we switch to a better bookmarks file format (XBEL), we * should use mime info to get a better icon. */ - label = _gtk_file_system_get_bookmark_label (button->priv->fs, file); + label = _gtk_bookmarks_manager_get_bookmark_label (button->priv->bookmarks_manager, file); if (!label) label = _gtk_file_chooser_label_for_file (file); @@ -2548,7 +2552,7 @@ update_label_and_image (GtkFileChooserButton *button) { GdkPixbuf *pixbuf; - label_text = _gtk_file_system_get_bookmark_label (button->priv->fs, file); + label_text = _gtk_bookmarks_manager_get_bookmark_label (button->priv->bookmarks_manager, file); pixbuf = gtk_icon_theme_load_icon (get_icon_theme (GTK_WIDGET (priv->image)), "text-x-generic", priv->icon_size, 0, NULL); @@ -2616,14 +2620,13 @@ fs_volumes_changed_cb (GtkFileSystem *fs, } static void -fs_bookmarks_changed_cb (GtkFileSystem *fs, - gpointer user_data) +bookmarks_changed_cb (gpointer user_data) { GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (user_data); GtkFileChooserButtonPrivate *priv = button->priv; GSList *bookmarks; - bookmarks = _gtk_file_system_list_bookmarks (fs); + bookmarks = _gtk_bookmarks_manager_list_bookmarks (priv->bookmarks_manager); model_remove_rows (user_data, model_get_type_position (user_data, ROW_TYPE_BOOKMARK_SEPARATOR), diff --git a/gtk/gtkfilechooserdefault.c b/gtk/gtkfilechooserdefault.c index 99c4200abd..26ced927ba 100644 --- a/gtk/gtkfilechooserdefault.c +++ b/gtk/gtkfilechooserdefault.c @@ -17,6 +17,11 @@ * License along with this library. If not, see . */ +/* TODO: + * + * * Fix FIXME-places-sidebar + */ + #include "config.h" #include "gtkfilechooserdefault.h" @@ -51,6 +56,7 @@ #include "gtkmountoperation.h" #include "gtkpaned.h" #include "gtkpathbar.h" +#include "gtkplacessidebar.h" #include "gtkprivate.h" #include "gtkradiobutton.h" #include "gtkrecentfilter.h" @@ -175,26 +181,6 @@ enum { static guint signals[LAST_SIGNAL] = { 0 }; -/* Column numbers for the shortcuts tree. Keep these in sync with shortcuts_model_create() */ -enum { - SHORTCUTS_COL_PIXBUF, - SHORTCUTS_COL_NAME, - SHORTCUTS_COL_DATA, - SHORTCUTS_COL_TYPE, - SHORTCUTS_COL_REMOVABLE, - SHORTCUTS_COL_PIXBUF_VISIBLE, - SHORTCUTS_COL_CANCELLABLE, - SHORTCUTS_COL_NUM_COLUMNS -}; - -typedef enum { - SHORTCUT_TYPE_FILE, - SHORTCUT_TYPE_VOLUME, - SHORTCUT_TYPE_SEPARATOR, - SHORTCUT_TYPE_SEARCH, - SHORTCUT_TYPE_RECENT -} ShortcutType; - #define MODEL_ATTRIBUTES "standard::name,standard::type,standard::display-name," \ "standard::is-hidden,standard::is-backup,standard::size," \ "standard::content-type,time::modified" @@ -234,21 +220,6 @@ enum { GTK_TREE_MODEL_ROW, }; -/* Interesting places in the shortcuts bar */ -typedef enum { - SHORTCUTS_SEARCH, - SHORTCUTS_RECENT, - SHORTCUTS_RECENT_SEPARATOR, - SHORTCUTS_HOME, - SHORTCUTS_DESKTOP, - SHORTCUTS_VOLUMES, - SHORTCUTS_SHORTCUTS, - SHORTCUTS_BOOKMARKS_SEPARATOR, - SHORTCUTS_BOOKMARKS, - SHORTCUTS_CURRENT_FOLDER_SEPARATOR, - SHORTCUTS_CURRENT_FOLDER -} ShortcutsIndex; - /* Icon size for if we can't get it from the theme */ #define FALLBACK_ICON_SIZE 16 @@ -339,6 +310,8 @@ static void search_shortcut_handler (GtkFileChooserDefault *impl); static void recent_shortcut_handler (GtkFileChooserDefault *impl); static void update_appearance (GtkFileChooserDefault *impl); +static void operation_mode_set (GtkFileChooserDefault *impl, OperationMode mode); + static void set_current_filter (GtkFileChooserDefault *impl, GtkFileFilter *filter); static void check_preview_change (GtkFileChooserDefault *impl); @@ -346,26 +319,6 @@ static void check_preview_change (GtkFileChooserDefault *impl); static void filter_combo_changed (GtkComboBox *combo_box, GtkFileChooserDefault *impl); -static gboolean shortcuts_key_press_event_cb (GtkWidget *widget, - GdkEventKey *event, - GtkFileChooserDefault *impl); - -static gboolean shortcuts_select_func (GtkTreeSelection *selection, - GtkTreeModel *model, - GtkTreePath *path, - gboolean path_currently_selected, - gpointer data); -static gboolean shortcuts_get_selected (GtkFileChooserDefault *impl, - GtkTreeIter *iter); -static void shortcuts_activate_iter (GtkFileChooserDefault *impl, - GtkTreeIter *iter); -static int shortcuts_get_index (GtkFileChooserDefault *impl, - ShortcutsIndex where); -static int shortcut_find_position (GtkFileChooserDefault *impl, - GFile *file); - -static void bookmarks_check_add_sensitivity (GtkFileChooserDefault *impl); - static gboolean list_select_func (GtkTreeSelection *selection, GtkTreeModel *model, GtkTreePath *path, @@ -385,11 +338,6 @@ static void path_bar_clicked (GtkPathBar *path_bar, gboolean child_is_hidden, GtkFileChooserDefault *impl); -static void add_bookmark_button_clicked_cb (GtkButton *button, - GtkFileChooserDefault *impl); -static void remove_bookmark_button_clicked_cb (GtkButton *button, - GtkFileChooserDefault *impl); - static void update_cell_renderer_attributes (GtkFileChooserDefault *impl); static void load_remove_timer (GtkFileChooserDefault *impl, LoadState new_load_state); @@ -423,38 +371,6 @@ static void set_file_system_backend (GtkFileChooserDefault *impl); static void unset_file_system_backend (GtkFileChooserDefault *impl); - - - -/* Drag and drop interface declarations */ - -typedef struct { - GtkTreeModelFilter parent; - - GtkFileChooserDefault *impl; -} ShortcutsPaneModelFilter; - -typedef struct { - GtkTreeModelFilterClass parent_class; -} ShortcutsPaneModelFilterClass; - -#define SHORTCUTS_PANE_MODEL_FILTER_TYPE (_shortcuts_pane_model_filter_get_type ()) -#define SHORTCUTS_PANE_MODEL_FILTER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SHORTCUTS_PANE_MODEL_FILTER_TYPE, ShortcutsPaneModelFilter)) - -static void shortcuts_pane_model_filter_drag_source_iface_init (GtkTreeDragSourceIface *iface); - -GType _shortcuts_pane_model_filter_get_type (void); - -G_DEFINE_TYPE_WITH_CODE (ShortcutsPaneModelFilter, - _shortcuts_pane_model_filter, - GTK_TYPE_TREE_MODEL_FILTER, - G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_DRAG_SOURCE, - shortcuts_pane_model_filter_drag_source_iface_init)) - -static GtkTreeModel *shortcuts_pane_model_filter_new (GtkFileChooserDefault *impl, - GtkTreeModel *child_model, - GtkTreePath *root); - G_DEFINE_TYPE_WITH_CODE (GtkFileChooserDefault, _gtk_file_chooser_default, GTK_TYPE_BOX, @@ -739,71 +655,11 @@ _gtk_file_chooser_default_init (GtkFileChooserDefault *impl) set_file_system_backend (impl); + impl->bookmarks_manager = _gtk_bookmarks_manager_new (NULL, NULL); + profile_end ("end", NULL); } -/* Frees the data columns for the specified iter in the shortcuts model*/ -static void -shortcuts_free_row_data (GtkFileChooserDefault *impl, - GtkTreeIter *iter) -{ - gpointer col_data; - ShortcutType shortcut_type; - GCancellable *cancellable; - - gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), iter, - SHORTCUTS_COL_DATA, &col_data, - SHORTCUTS_COL_TYPE, &shortcut_type, - SHORTCUTS_COL_CANCELLABLE, &cancellable, - -1); - - if (cancellable) - { - g_cancellable_cancel (cancellable); - g_object_unref (cancellable); - } - - if (!(shortcut_type == SHORTCUT_TYPE_FILE || - shortcut_type == SHORTCUT_TYPE_VOLUME) || - !col_data) - return; - - if (shortcut_type == SHORTCUT_TYPE_VOLUME) - { - GtkFileSystemVolume *volume; - - volume = col_data; - _gtk_file_system_volume_unref (volume); - } - if (shortcut_type == SHORTCUT_TYPE_FILE) - { - GFile *file; - - file = col_data; - g_object_unref (file); - } -} - -/* Frees all the data columns in the shortcuts model */ -static void -shortcuts_free (GtkFileChooserDefault *impl) -{ - GtkTreeIter iter; - - if (!impl->shortcuts_model) - return; - - if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (impl->shortcuts_model), &iter)) - do - { - shortcuts_free_row_data (impl, &iter); - } - while (gtk_tree_model_iter_next (GTK_TREE_MODEL (impl->shortcuts_model), &iter)); - - g_object_unref (impl->shortcuts_model); - impl->shortcuts_model = NULL; -} - static void pending_select_files_free (GtkFileChooserDefault *impl) { @@ -827,11 +683,6 @@ gtk_file_chooser_default_finalize (GObject *object) unset_file_system_backend (impl); - if (impl->shortcuts_pane_filter_model) - g_object_unref (impl->shortcuts_pane_filter_model); - - shortcuts_free (impl); - g_free (impl->browse_files_last_selected_name); for (l = impl->filters; l; l = l->next) @@ -942,41 +793,6 @@ error_dialog (GtkFileChooserDefault *impl, } } -/* Displays an error message about not being able to get information for a file. - * Frees the GError as well. - */ -static void -error_getting_info_dialog (GtkFileChooserDefault *impl, - GFile *file, - GError *error) -{ - error_dialog (impl, - _("Could not retrieve information about the file"), - file, error); -} - -/* Shows an error dialog about not being able to add a bookmark */ -static void -error_adding_bookmark_dialog (GtkFileChooserDefault *impl, - GFile *file, - GError *error) -{ - error_dialog (impl, - _("Could not add a bookmark"), - file, error); -} - -/* Shows an error dialog about not being able to remove a bookmark */ -static void -error_removing_bookmark_dialog (GtkFileChooserDefault *impl, - GFile *file, - GError *error) -{ - error_dialog (impl, - _("Could not remove bookmark"), - file, error); -} - /* Shows an error dialog about not being able to create a folder */ static void error_creating_folder_dialog (GtkFileChooserDefault *impl, @@ -1143,393 +959,6 @@ set_preview_widget (GtkFileChooserDefault *impl, update_preview_widget_visibility (impl); } -/* Renders a "Search" icon at an appropriate size for a tree view */ -static GdkPixbuf * -render_search_icon (GtkFileChooserDefault *impl) -{ - return gtk_widget_render_icon_pixbuf (GTK_WIDGET (impl), GTK_STOCK_FIND, GTK_ICON_SIZE_MENU); -} - -static GdkPixbuf * -render_recent_icon (GtkFileChooserDefault *impl) -{ - GtkIconTheme *theme; - GdkPixbuf *retval; - - if (gtk_widget_has_screen (GTK_WIDGET (impl))) - theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (impl))); - else - theme = gtk_icon_theme_get_default (); - - retval = gtk_icon_theme_load_icon (theme, "document-open-recent", - impl->icon_size, 0, - NULL); - - /* fallback */ - if (!retval) - retval = gtk_widget_render_icon_pixbuf (GTK_WIDGET (impl), GTK_STOCK_FILE, GTK_ICON_SIZE_MENU); - - return retval; -} - - -/* Re-reads all the icons for the shortcuts, used when the theme changes */ -struct ReloadIconsData -{ - GtkFileChooserDefault *impl; - GtkTreeRowReference *row_ref; -}; - -static void -shortcuts_reload_icons_get_info_cb (GCancellable *cancellable, - GFileInfo *info, - const GError *error, - gpointer user_data) -{ - GdkPixbuf *pixbuf; - GtkTreeIter iter; - GtkTreePath *path; - gboolean cancelled = g_cancellable_is_cancelled (cancellable); - struct ReloadIconsData *data = user_data; - - if (!g_slist_find (data->impl->reload_icon_cancellables, cancellable)) - goto out; - - data->impl->reload_icon_cancellables = g_slist_remove (data->impl->reload_icon_cancellables, cancellable); - - if (cancelled || error) - goto out; - - pixbuf = _gtk_file_info_render_icon (info, GTK_WIDGET (data->impl), data->impl->icon_size); - - path = gtk_tree_row_reference_get_path (data->row_ref); - if (path) - { - gtk_tree_model_get_iter (GTK_TREE_MODEL (data->impl->shortcuts_model), &iter, path); - gtk_list_store_set (data->impl->shortcuts_model, &iter, - SHORTCUTS_COL_PIXBUF, pixbuf, - -1); - gtk_tree_path_free (path); - } - - if (pixbuf) - g_object_unref (pixbuf); - -out: - gtk_tree_row_reference_free (data->row_ref); - g_object_unref (data->impl); - g_free (data); - - g_object_unref (cancellable); -} - -static void -shortcuts_reload_icons (GtkFileChooserDefault *impl) -{ - GSList *l; - GtkTreeIter iter; - - profile_start ("start", NULL); - - if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (impl->shortcuts_model), &iter)) - goto out; - - for (l = impl->reload_icon_cancellables; l; l = l->next) - { - GCancellable *cancellable = G_CANCELLABLE (l->data); - g_cancellable_cancel (cancellable); - } - g_slist_free (impl->reload_icon_cancellables); - impl->reload_icon_cancellables = NULL; - - do - { - gpointer data; - ShortcutType shortcut_type; - gboolean pixbuf_visible; - GdkPixbuf *pixbuf; - - gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), &iter, - SHORTCUTS_COL_DATA, &data, - SHORTCUTS_COL_TYPE, &shortcut_type, - SHORTCUTS_COL_PIXBUF_VISIBLE, &pixbuf_visible, - -1); - - pixbuf = NULL; - if (pixbuf_visible) - { - if (shortcut_type == SHORTCUT_TYPE_VOLUME) - { - GtkFileSystemVolume *volume; - - volume = data; - pixbuf = _gtk_file_system_volume_render_icon (volume, GTK_WIDGET (impl), - impl->icon_size, NULL); - } - else if (shortcut_type == SHORTCUT_TYPE_FILE) - { - if (g_file_is_native (G_FILE (data))) - { - GFile *file; - struct ReloadIconsData *info; - GtkTreePath *tree_path; - GCancellable *cancellable; - - file = data; - - info = g_new0 (struct ReloadIconsData, 1); - info->impl = g_object_ref (impl); - tree_path = gtk_tree_model_get_path (GTK_TREE_MODEL (impl->shortcuts_model), &iter); - info->row_ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (impl->shortcuts_model), tree_path); - gtk_tree_path_free (tree_path); - - cancellable = _gtk_file_system_get_info (impl->file_system, file, - "standard::icon", - shortcuts_reload_icons_get_info_cb, - info); - impl->reload_icon_cancellables = g_slist_append (impl->reload_icon_cancellables, cancellable); - } - else - { - GtkIconTheme *icon_theme; - - /* Don't call get_info for remote paths to avoid latency and - * auth dialogs. - * If we switch to a better bookmarks file format (XBEL), we - * should use mime info to get a better icon. - */ - icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (impl))); - pixbuf = gtk_icon_theme_load_icon (icon_theme, "folder-remote", - impl->icon_size, 0, NULL); - } - } - else if (shortcut_type == SHORTCUT_TYPE_SEARCH) - { - pixbuf = render_search_icon (impl); - } - else if (shortcut_type == SHORTCUT_TYPE_RECENT) - { - pixbuf = render_recent_icon (impl); - } - - gtk_list_store_set (impl->shortcuts_model, &iter, - SHORTCUTS_COL_PIXBUF, pixbuf, - -1); - - if (pixbuf) - g_object_unref (pixbuf); - - } - } - while (gtk_tree_model_iter_next (GTK_TREE_MODEL (impl->shortcuts_model),&iter)); - - out: - - profile_end ("end", NULL); -} - -static void -shortcuts_find_folder (GtkFileChooserDefault *impl, - GFile *folder) -{ - GtkTreeSelection *selection; - int pos; - GtkTreePath *path; - - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_shortcuts_tree_view)); - - g_assert (folder != NULL); - pos = shortcut_find_position (impl, folder); - if (pos == -1) - { - gtk_tree_selection_unselect_all (selection); - return; - } - - path = gtk_tree_path_new_from_indices (pos, -1); - gtk_tree_selection_select_path (selection, path); - gtk_tree_path_free (path); -} - -/* If a shortcut corresponds to the current folder, selects it */ -static void -shortcuts_find_current_folder (GtkFileChooserDefault *impl) -{ - shortcuts_find_folder (impl, impl->current_folder); -} - -/* Removes the specified number of rows from the shortcuts list */ -static void -shortcuts_remove_rows (GtkFileChooserDefault *impl, - int start_row, - int n_rows) -{ - GtkTreePath *path; - - path = gtk_tree_path_new_from_indices (start_row, -1); - - for (; n_rows; n_rows--) - { - GtkTreeIter iter; - - if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (impl->shortcuts_model), &iter, path)) - g_assert_not_reached (); - - shortcuts_free_row_data (impl, &iter); - gtk_list_store_remove (impl->shortcuts_model, &iter); - } - - gtk_tree_path_free (path); -} - -static void -shortcuts_update_count (GtkFileChooserDefault *impl, - ShortcutsIndex type, - gint value) -{ - switch (type) - { - case SHORTCUTS_HOME: - if (value < 0) - impl->has_home = FALSE; - else - impl->has_home = TRUE; - break; - - case SHORTCUTS_DESKTOP: - if (value < 0) - impl->has_desktop = FALSE; - else - impl->has_desktop = TRUE; - break; - - case SHORTCUTS_VOLUMES: - impl->num_volumes += value; - break; - - case SHORTCUTS_SHORTCUTS: - impl->num_shortcuts += value; - break; - - case SHORTCUTS_BOOKMARKS: - impl->num_bookmarks += value; - break; - - case SHORTCUTS_CURRENT_FOLDER: - if (value < 0) - impl->shortcuts_current_folder_active = FALSE; - else - impl->shortcuts_current_folder_active = TRUE; - break; - - default: - /* nothing */ - break; - } -} - -struct ShortcutsInsertRequest -{ - GtkFileChooserDefault *impl; - GFile *file; - int pos; - char *label_copy; - GtkTreeRowReference *row_ref; - ShortcutsIndex type; - gboolean name_only; - gboolean removable; -}; - -static void -get_file_info_finished (GCancellable *cancellable, - GFileInfo *info, - const GError *error, - gpointer data) -{ - gboolean cancelled = g_cancellable_is_cancelled (cancellable); - GdkPixbuf *pixbuf; - GtkTreePath *path; - GtkTreeIter iter; - GCancellable *model_cancellable = NULL; - struct ShortcutsInsertRequest *request = data; - - path = gtk_tree_row_reference_get_path (request->row_ref); - if (!path) - /* Handle doesn't exist anymore in the model */ - goto out; - - gtk_tree_model_get_iter (GTK_TREE_MODEL (request->impl->shortcuts_model), - &iter, path); - gtk_tree_path_free (path); - - /* validate cancellable, else goto out */ - gtk_tree_model_get (GTK_TREE_MODEL (request->impl->shortcuts_model), &iter, - SHORTCUTS_COL_CANCELLABLE, &model_cancellable, - -1); - if (cancellable != model_cancellable) - goto out; - - /* set the cancellable to NULL in the model (we unref later on) */ - gtk_list_store_set (request->impl->shortcuts_model, &iter, - SHORTCUTS_COL_CANCELLABLE, NULL, - -1); - - if (cancelled) - goto out; - - if (!info) - { - gtk_list_store_remove (request->impl->shortcuts_model, &iter); - shortcuts_update_count (request->impl, request->type, -1); - - if (request->type == SHORTCUTS_HOME) - { - GFile *home; - - home = g_file_new_for_path (g_get_home_dir ()); - error_getting_info_dialog (request->impl, home, g_error_copy (error)); - g_object_unref (home); - } - else if (request->type == SHORTCUTS_CURRENT_FOLDER) - { - /* Remove the current folder separator */ - gint separator_pos = shortcuts_get_index (request->impl, SHORTCUTS_CURRENT_FOLDER_SEPARATOR); - shortcuts_remove_rows (request->impl, separator_pos, 1); - } - - goto out; - } - - if (!request->label_copy) - request->label_copy = g_strdup (g_file_info_get_display_name (info)); - pixbuf = _gtk_file_info_render_icon (info, GTK_WIDGET (request->impl), - request->impl->icon_size); - - gtk_list_store_set (request->impl->shortcuts_model, &iter, - SHORTCUTS_COL_PIXBUF, pixbuf, - SHORTCUTS_COL_PIXBUF_VISIBLE, TRUE, - SHORTCUTS_COL_NAME, request->label_copy, - SHORTCUTS_COL_TYPE, SHORTCUT_TYPE_FILE, - SHORTCUTS_COL_REMOVABLE, request->removable, - -1); - - if (request->impl->shortcuts_pane_filter_model) - gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (request->impl->shortcuts_pane_filter_model)); - - if (pixbuf) - g_object_unref (pixbuf); - -out: - g_object_unref (request->impl); - g_object_unref (request->file); - gtk_tree_row_reference_free (request->row_ref); - g_free (request->label_copy); - g_free (request); - - if (model_cancellable) - g_object_unref (model_cancellable); -} - /* FIXME: GtkFileSystem needs a function to split a remote path * into hostname and path components, or maybe just have a * gtk_file_system_path_get_display_name(). @@ -1586,628 +1015,6 @@ _gtk_file_chooser_label_for_file (GFile *file) return label; } -/* Inserts a path in the shortcuts tree, making a copy of it; alternatively, - * inserts a volume. A position of -1 indicates the end of the tree. - */ -static void -shortcuts_insert_file (GtkFileChooserDefault *impl, - int pos, - ShortcutType shortcut_type, - GtkFileSystemVolume *volume, - GFile *file, - const char *label, - gboolean removable, - ShortcutsIndex type) -{ - char *label_copy; - GdkPixbuf *pixbuf = NULL; - gpointer data = NULL; - GtkTreeIter iter; - GtkIconTheme *icon_theme; - - profile_start ("start shortcut", NULL); - - if (shortcut_type == SHORTCUT_TYPE_VOLUME) - { - data = volume; - label_copy = _gtk_file_system_volume_get_display_name (volume); - pixbuf = _gtk_file_system_volume_render_icon (volume, GTK_WIDGET (impl), - impl->icon_size, NULL); - } - else if (shortcut_type == SHORTCUT_TYPE_FILE) - { - if (g_file_is_native (file)) - { - struct ShortcutsInsertRequest *request; - GCancellable *cancellable; - GtkTreePath *p; - - request = g_new0 (struct ShortcutsInsertRequest, 1); - request->impl = g_object_ref (impl); - request->file = g_object_ref (file); - request->name_only = TRUE; - request->removable = removable; - request->pos = pos; - request->type = type; - if (label) - request->label_copy = g_strdup (label); - - if (pos == -1) - gtk_list_store_append (impl->shortcuts_model, &iter); - else - gtk_list_store_insert (impl->shortcuts_model, &iter, pos); - - p = gtk_tree_model_get_path (GTK_TREE_MODEL (impl->shortcuts_model), &iter); - request->row_ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (impl->shortcuts_model), p); - gtk_tree_path_free (p); - - cancellable = _gtk_file_system_get_info (request->impl->file_system, request->file, - "standard::is-hidden,standard::is-backup,standard::display-name,standard::icon", - get_file_info_finished, request); - - gtk_list_store_set (impl->shortcuts_model, &iter, - SHORTCUTS_COL_DATA, g_object_ref (file), - SHORTCUTS_COL_TYPE, SHORTCUT_TYPE_FILE, - SHORTCUTS_COL_CANCELLABLE, cancellable, - -1); - - shortcuts_update_count (impl, type, 1); - - return; - } - else - { - /* Don't call get_info for remote paths to avoid latency and - * auth dialogs. - */ - data = g_object_ref (file); - if (label) - label_copy = g_strdup (label); - else - label_copy = _gtk_file_chooser_label_for_file (file); - - /* If we switch to a better bookmarks file format (XBEL), we - * should use mime info to get a better icon. - */ - icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (impl))); - pixbuf = gtk_icon_theme_load_icon (icon_theme, "folder-remote", - impl->icon_size, 0, NULL); - } - } - else - { - g_assert_not_reached (); - - return; - } - - if (pos == -1) - gtk_list_store_append (impl->shortcuts_model, &iter); - else - gtk_list_store_insert (impl->shortcuts_model, &iter, pos); - - shortcuts_update_count (impl, type, 1); - - gtk_list_store_set (impl->shortcuts_model, &iter, - SHORTCUTS_COL_PIXBUF, pixbuf, - SHORTCUTS_COL_PIXBUF_VISIBLE, TRUE, - SHORTCUTS_COL_NAME, label_copy, - SHORTCUTS_COL_DATA, data, - SHORTCUTS_COL_TYPE, shortcut_type, - SHORTCUTS_COL_REMOVABLE, removable, - SHORTCUTS_COL_CANCELLABLE, NULL, - -1); - - if (impl->shortcuts_pane_filter_model) - gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (impl->shortcuts_pane_filter_model)); - - g_free (label_copy); - - if (pixbuf) - g_object_unref (pixbuf); - - profile_end ("end", NULL); -} - -static void -shortcuts_append_search (GtkFileChooserDefault *impl) -{ - GdkPixbuf *pixbuf; - GtkTreeIter iter; - - pixbuf = render_search_icon (impl); - - gtk_list_store_append (impl->shortcuts_model, &iter); - gtk_list_store_set (impl->shortcuts_model, &iter, - SHORTCUTS_COL_PIXBUF, pixbuf, - SHORTCUTS_COL_PIXBUF_VISIBLE, TRUE, - SHORTCUTS_COL_NAME, _("Search"), - SHORTCUTS_COL_DATA, NULL, - SHORTCUTS_COL_TYPE, SHORTCUT_TYPE_SEARCH, - SHORTCUTS_COL_REMOVABLE, FALSE, - -1); - - if (pixbuf) - g_object_unref (pixbuf); - - impl->has_search = TRUE; -} - -static gboolean -shortcuts_get_recent_enabled (GtkWidget *widget) -{ - GtkSettings *settings; - gboolean enabled; - - if (gtk_widget_has_screen (widget)) - settings = gtk_settings_get_for_screen (gtk_widget_get_screen (widget)); - else - settings = gtk_settings_get_default (); - - g_object_get (settings, "gtk-recent-files-enabled", &enabled, NULL); - return enabled; -} - -static void -shortcuts_append_recent (GtkFileChooserDefault *impl) -{ - GdkPixbuf *pixbuf; - GtkTreeIter iter; - gboolean enabled; - - enabled = shortcuts_get_recent_enabled (GTK_WIDGET (impl)); - if (!enabled) - return; - - pixbuf = render_recent_icon (impl); - - gtk_list_store_append (impl->shortcuts_model, &iter); - gtk_list_store_set (impl->shortcuts_model, &iter, - SHORTCUTS_COL_PIXBUF, pixbuf, - SHORTCUTS_COL_PIXBUF_VISIBLE, TRUE, - SHORTCUTS_COL_NAME, _("Recently Used"), - SHORTCUTS_COL_DATA, NULL, - SHORTCUTS_COL_TYPE, SHORTCUT_TYPE_RECENT, - SHORTCUTS_COL_REMOVABLE, FALSE, - -1); - - if (pixbuf) - g_object_unref (pixbuf); - - impl->has_recent = TRUE; -} - -/* Appends an item for the user's home directory to the shortcuts model */ -static void -shortcuts_append_home (GtkFileChooserDefault *impl) -{ - const char *home_path; - GFile *home; - - profile_start ("start", NULL); - - home_path = g_get_home_dir (); - if (home_path == NULL) - { - profile_end ("end - no home directory!?", NULL); - return; - } - - home = g_file_new_for_path (home_path); - shortcuts_insert_file (impl, -1, SHORTCUT_TYPE_FILE, NULL, home, NULL, FALSE, SHORTCUTS_HOME); - impl->has_home = TRUE; - - g_object_unref (home); - - profile_end ("end", NULL); -} - -/* Appends the ~/Desktop directory to the shortcuts model */ -static void -shortcuts_append_desktop (GtkFileChooserDefault *impl) -{ - const char *name; - GFile *file; - - profile_start ("start", NULL); - - 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 (!g_strcmp0 (name, g_get_home_dir ())) - { - profile_end ("end", NULL); - return; - } - - file = g_file_new_for_path (name); - shortcuts_insert_file (impl, -1, SHORTCUT_TYPE_FILE, NULL, file, _("Desktop"), FALSE, SHORTCUTS_DESKTOP); - impl->has_desktop = TRUE; - - /* We do not actually pop up an error dialog if there is no desktop directory - * because some people may really not want to have one. - */ - - g_object_unref (file); - - profile_end ("end", NULL); -} - -/* Appends a list of GFile to the shortcuts model; returns how many were inserted */ -static int -shortcuts_append_bookmarks (GtkFileChooserDefault *impl, - GSList *bookmarks) -{ - int start_row; - int num_inserted; - gchar *label; - - profile_start ("start", NULL); - - start_row = shortcuts_get_index (impl, SHORTCUTS_BOOKMARKS_SEPARATOR) + 1; - num_inserted = 0; - - for (; bookmarks; bookmarks = bookmarks->next) - { - GFile *file; - - file = bookmarks->data; - - if (impl->local_only && !_gtk_file_has_native_path (file)) - continue; - - if (shortcut_find_position (impl, file) != -1) - continue; - - label = _gtk_file_system_get_bookmark_label (impl->file_system, file); - - shortcuts_insert_file (impl, start_row + num_inserted, SHORTCUT_TYPE_FILE, NULL, file, label, TRUE, SHORTCUTS_BOOKMARKS); - g_free (label); - - num_inserted++; - } - - profile_end ("end", NULL); - - return num_inserted; -} - -/* Returns the index for the corresponding item in the shortcuts bar */ -static int -shortcuts_get_index (GtkFileChooserDefault *impl, - ShortcutsIndex where) -{ - int n; - - n = 0; - - if (where == SHORTCUTS_SEARCH) - goto out; - - n += impl->has_search ? 1 : 0; - - if (where == SHORTCUTS_RECENT) - goto out; - - n += impl->has_recent ? 1 : 0; - - if (where == SHORTCUTS_RECENT_SEPARATOR) - goto out; - - n += 1; /* we always have the separator after the recently-used item */ - - if (where == SHORTCUTS_HOME) - goto out; - - n += impl->has_home ? 1 : 0; - - if (where == SHORTCUTS_DESKTOP) - goto out; - - n += impl->has_desktop ? 1 : 0; - - if (where == SHORTCUTS_VOLUMES) - goto out; - - n += impl->num_volumes; - - if (where == SHORTCUTS_SHORTCUTS) - goto out; - - n += impl->num_shortcuts; - - if (where == SHORTCUTS_BOOKMARKS_SEPARATOR) - goto out; - - /* If there are no bookmarks there won't be a separator */ - n += (impl->num_bookmarks > 0) ? 1 : 0; - - if (where == SHORTCUTS_BOOKMARKS) - goto out; - - n += impl->num_bookmarks; - - if (where == SHORTCUTS_CURRENT_FOLDER_SEPARATOR) - goto out; - - n += 1; - - if (where == SHORTCUTS_CURRENT_FOLDER) - goto out; - - g_assert_not_reached (); - - out: - - return n; -} - -/* Adds all the file system volumes to the shortcuts model */ -static void -shortcuts_add_volumes (GtkFileChooserDefault *impl) -{ - int start_row; - GSList *list, *l; - int n; - gboolean old_changing_folders; - - profile_start ("start", NULL); - - old_changing_folders = impl->changing_folder; - impl->changing_folder = TRUE; - - start_row = shortcuts_get_index (impl, SHORTCUTS_VOLUMES); - shortcuts_remove_rows (impl, start_row, impl->num_volumes); - impl->num_volumes = 0; - - list = _gtk_file_system_list_volumes (impl->file_system); - - n = 0; - - for (l = list; l; l = l->next) - { - GtkFileSystemVolume *volume; - - volume = l->data; - - if (impl->local_only) - { - if (_gtk_file_system_volume_is_mounted (volume)) - { - GFile *base_file; - gboolean base_has_native_path = FALSE; - - base_file = _gtk_file_system_volume_get_root (volume); - if (base_file != NULL) - { - base_has_native_path = _gtk_file_has_native_path (base_file); - g_object_unref (base_file); - } - - if (!base_has_native_path) - continue; - } - } - - shortcuts_insert_file (impl, - start_row + n, - SHORTCUT_TYPE_VOLUME, - _gtk_file_system_volume_ref (volume), - NULL, - NULL, - FALSE, - SHORTCUTS_VOLUMES); - n++; - } - - impl->num_volumes = n; - g_slist_free (list); - - if (impl->shortcuts_pane_filter_model) - gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (impl->shortcuts_pane_filter_model)); - - impl->changing_folder = old_changing_folders; - - profile_end ("end", NULL); -} - -/* Inserts a separator node in the shortcuts list */ -static void -shortcuts_insert_separator (GtkFileChooserDefault *impl, - ShortcutsIndex where) -{ - GtkTreeIter iter; - - g_assert (where == SHORTCUTS_RECENT_SEPARATOR || - where == SHORTCUTS_BOOKMARKS_SEPARATOR || - where == SHORTCUTS_CURRENT_FOLDER_SEPARATOR); - - gtk_list_store_insert (impl->shortcuts_model, &iter, - shortcuts_get_index (impl, where)); - gtk_list_store_set (impl->shortcuts_model, &iter, - SHORTCUTS_COL_PIXBUF, NULL, - SHORTCUTS_COL_PIXBUF_VISIBLE, FALSE, - SHORTCUTS_COL_NAME, NULL, - SHORTCUTS_COL_DATA, NULL, - SHORTCUTS_COL_TYPE, SHORTCUT_TYPE_SEPARATOR, - -1); -} - -/* Updates the list of bookmarks */ -static void -shortcuts_add_bookmarks (GtkFileChooserDefault *impl) -{ - GSList *bookmarks; - gboolean old_changing_folders; - GtkTreeIter iter; - GFile *list_selected = NULL; - ShortcutType shortcut_type; - gpointer col_data; - - profile_start ("start", NULL); - - old_changing_folders = impl->changing_folder; - impl->changing_folder = TRUE; - - if (shortcuts_get_selected (impl, &iter)) - { - gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), - &iter, - SHORTCUTS_COL_DATA, &col_data, - SHORTCUTS_COL_TYPE, &shortcut_type, - -1); - - if (col_data && shortcut_type == SHORTCUT_TYPE_FILE) - list_selected = g_object_ref (col_data); - } - - if (impl->num_bookmarks > 0) - shortcuts_remove_rows (impl, - shortcuts_get_index (impl, SHORTCUTS_BOOKMARKS_SEPARATOR), - impl->num_bookmarks + 1); - - impl->num_bookmarks = 0; - shortcuts_insert_separator (impl, SHORTCUTS_BOOKMARKS_SEPARATOR); - - bookmarks = _gtk_file_system_list_bookmarks (impl->file_system); - shortcuts_append_bookmarks (impl, bookmarks); - g_slist_foreach (bookmarks, (GFunc) g_object_unref, NULL); - g_slist_free (bookmarks); - - if (impl->num_bookmarks == 0) - shortcuts_remove_rows (impl, shortcuts_get_index (impl, SHORTCUTS_BOOKMARKS_SEPARATOR), 1); - - if (impl->shortcuts_pane_filter_model) - gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (impl->shortcuts_pane_filter_model)); - - if (list_selected) - { - shortcuts_find_folder (impl, list_selected); - g_object_unref (list_selected); - } - - impl->changing_folder = old_changing_folders; - - profile_end ("end", NULL); -} - -/* Appends a separator and a row to the shortcuts list for the current folder */ -static void -shortcuts_add_current_folder (GtkFileChooserDefault *impl) -{ - int pos; - - g_assert (!impl->shortcuts_current_folder_active); - - g_assert (impl->current_folder != NULL); - - pos = shortcut_find_position (impl, impl->current_folder); - if (pos == -1) - { - GtkFileSystemVolume *volume; - GFile *base_file; - - /* Separator */ - shortcuts_insert_separator (impl, SHORTCUTS_CURRENT_FOLDER_SEPARATOR); - - /* Item */ - pos = shortcuts_get_index (impl, SHORTCUTS_CURRENT_FOLDER); - - volume = _gtk_file_system_get_volume_for_file (impl->file_system, impl->current_folder); - if (volume) - base_file = _gtk_file_system_volume_get_root (volume); - else - base_file = NULL; - - if (base_file && g_file_equal (base_file, impl->current_folder)) - shortcuts_insert_file (impl, pos, SHORTCUT_TYPE_VOLUME, volume, NULL, NULL, FALSE, SHORTCUTS_CURRENT_FOLDER); - else - shortcuts_insert_file (impl, pos, SHORTCUT_TYPE_FILE, NULL, impl->current_folder, NULL, FALSE, SHORTCUTS_CURRENT_FOLDER); - - if (base_file) - g_object_unref (base_file); - } -} - -/* Updates the current folder row in the shortcuts model */ -static void -shortcuts_update_current_folder (GtkFileChooserDefault *impl) -{ - int pos; - - pos = shortcuts_get_index (impl, SHORTCUTS_CURRENT_FOLDER_SEPARATOR); - - if (impl->shortcuts_current_folder_active) - { - shortcuts_remove_rows (impl, pos, 2); - impl->shortcuts_current_folder_active = FALSE; - } - - shortcuts_add_current_folder (impl); -} - -/* Filter function used for the shortcuts filter model */ -static gboolean -shortcuts_pane_filter_cb (GtkTreeModel *model, - GtkTreeIter *iter, - gpointer data) -{ - GtkFileChooserDefault *impl; - GtkTreePath *path; - int pos; - - impl = GTK_FILE_CHOOSER_DEFAULT (data); - - path = gtk_tree_model_get_path (model, iter); - if (!path) - return FALSE; - - pos = *gtk_tree_path_get_indices (path); - gtk_tree_path_free (path); - - return (pos < shortcuts_get_index (impl, SHORTCUTS_CURRENT_FOLDER_SEPARATOR)); -} - -/* Creates the list model for shortcuts */ -static void -shortcuts_model_create (GtkFileChooserDefault *impl) -{ - /* Keep this order in sync with the SHORCUTS_COL_* enum values */ - impl->shortcuts_model = gtk_list_store_new (SHORTCUTS_COL_NUM_COLUMNS, - GDK_TYPE_PIXBUF, /* pixbuf */ - G_TYPE_STRING, /* name */ - G_TYPE_POINTER, /* path or volume */ - G_TYPE_INT, /* ShortcutType */ - G_TYPE_BOOLEAN, /* removable */ - G_TYPE_BOOLEAN, /* pixbuf cell visibility */ - G_TYPE_POINTER); /* GCancellable */ - - shortcuts_append_search (impl); - - if (impl->recent_manager) - { - shortcuts_append_recent (impl); - shortcuts_insert_separator (impl, SHORTCUTS_RECENT_SEPARATOR); - } - - if (impl->file_system) - { - shortcuts_append_home (impl); - shortcuts_append_desktop (impl); - shortcuts_add_volumes (impl); - } - - impl->shortcuts_pane_filter_model = shortcuts_pane_model_filter_new (impl, - GTK_TREE_MODEL (impl->shortcuts_model), - NULL); - - gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (impl->shortcuts_pane_filter_model), - shortcuts_pane_filter_cb, - impl, - NULL); -} - /* Callback used when the "New Folder" button is clicked */ static void new_folder_button_clicked (GtkButton *button, @@ -2371,229 +1178,6 @@ filter_create (GtkFileChooserDefault *impl) return impl->filter_combo; } -static GtkWidget * -toolbutton_new (GtkFileChooserDefault *impl, - GIcon *icon, - gboolean sensitive, - gboolean show, - GCallback callback) -{ - GtkToolItem *item; - GtkWidget *image; - - item = gtk_tool_button_new (NULL, NULL); - image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_SMALL_TOOLBAR); - gtk_widget_show (image); - gtk_tool_button_set_icon_widget (GTK_TOOL_BUTTON (item), image); - - gtk_widget_set_sensitive (GTK_WIDGET (item), sensitive); - g_signal_connect (item, "clicked", callback, impl); - - if (show) - gtk_widget_show (GTK_WIDGET (item)); - - return GTK_WIDGET (item); -} - -/* Looks for a path among the shortcuts; returns its index or -1 if it doesn't exist */ -static int -shortcut_find_position (GtkFileChooserDefault *impl, - GFile *file) -{ - GtkTreeIter iter; - int i; - int current_folder_separator_idx; - - if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (impl->shortcuts_model), &iter)) - return -1; - - current_folder_separator_idx = shortcuts_get_index (impl, SHORTCUTS_CURRENT_FOLDER_SEPARATOR); - -#if 0 - /* FIXME: is this still needed? */ - if (current_folder_separator_idx >= impl->shortcuts_model->length) - return -1; -#endif - - for (i = 0; i < current_folder_separator_idx; i++) - { - gpointer col_data; - ShortcutType shortcut_type; - - gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), &iter, - SHORTCUTS_COL_DATA, &col_data, - SHORTCUTS_COL_TYPE, &shortcut_type, - -1); - - if (col_data) - { - if (shortcut_type == SHORTCUT_TYPE_VOLUME) - { - GtkFileSystemVolume *volume; - GFile *base_file; - gboolean exists; - - volume = col_data; - base_file = _gtk_file_system_volume_get_root (volume); - - exists = base_file && g_file_equal (file, base_file); - - if (base_file) - g_object_unref (base_file); - - if (exists) - return i; - } - else if (shortcut_type == SHORTCUT_TYPE_FILE) - { - GFile *model_file; - - model_file = col_data; - - if (model_file && g_file_equal (model_file, file)) - return i; - } - } - - if (i < current_folder_separator_idx - 1) - { - if (!gtk_tree_model_iter_next (GTK_TREE_MODEL (impl->shortcuts_model), &iter)) - g_assert_not_reached (); - } - } - - return -1; -} - -/* Tries to add a bookmark from a path name */ -static gboolean -shortcuts_add_bookmark_from_file (GtkFileChooserDefault *impl, - GFile *file, - int pos) -{ - GError *error; - - g_return_val_if_fail (G_IS_FILE (file), FALSE); - - if (shortcut_find_position (impl, file) != -1) - return FALSE; - - error = NULL; - if (!_gtk_file_system_insert_bookmark (impl->file_system, file, pos, &error)) - { - error_adding_bookmark_dialog (impl, file, error); - return FALSE; - } - - return TRUE; -} - -static void -add_bookmark_foreach_cb (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gpointer data) -{ - GtkFileChooserDefault *impl; - GFile *file; - - impl = (GtkFileChooserDefault *) data; - - gtk_tree_model_get (model, iter, - MODEL_COL_FILE, &file, - -1); - - shortcuts_add_bookmark_from_file (impl, file, -1); - - g_object_unref (file); -} - -/* Adds a bookmark from the currently selected item in the file list */ -static void -bookmarks_add_selected_folder (GtkFileChooserDefault *impl) -{ - GtkTreeSelection *selection; - - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_files_tree_view)); - - if (gtk_tree_selection_count_selected_rows (selection) == 0) - shortcuts_add_bookmark_from_file (impl, impl->current_folder, -1); - else - gtk_tree_selection_selected_foreach (selection, - add_bookmark_foreach_cb, - impl); -} - -/* Callback used when the "Add bookmark" button is clicked */ -static void -add_bookmark_button_clicked_cb (GtkButton *button, - GtkFileChooserDefault *impl) -{ - bookmarks_add_selected_folder (impl); -} - -/* Returns TRUE plus an iter in the shortcuts_model if a row is selected; - * returns FALSE if no shortcut is selected. - */ -static gboolean -shortcuts_get_selected (GtkFileChooserDefault *impl, - GtkTreeIter *iter) -{ - GtkTreeSelection *selection; - GtkTreeIter parent_iter; - - if (!impl->browse_shortcuts_tree_view) - return FALSE; - - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_shortcuts_tree_view)); - - if (!gtk_tree_selection_get_selected (selection, NULL, &parent_iter)) - return FALSE; - - gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (impl->shortcuts_pane_filter_model), - iter, - &parent_iter); - return TRUE; -} - -/* Removes the selected bookmarks */ -static void -remove_selected_bookmarks (GtkFileChooserDefault *impl) -{ - GtkTreeIter iter; - gpointer col_data; - GFile *file; - gboolean removable; - GError *error; - - if (!shortcuts_get_selected (impl, &iter)) - return; - - gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), &iter, - SHORTCUTS_COL_DATA, &col_data, - SHORTCUTS_COL_REMOVABLE, &removable, - -1); - - if (!removable) - return; - - g_assert (col_data != NULL); - - file = col_data; - - error = NULL; - if (!_gtk_file_system_remove_bookmark (impl->file_system, file, &error)) - error_removing_bookmark_dialog (impl, file, error); -} - -/* Callback used when the "Remove bookmark" button is clicked */ -static void -remove_bookmark_button_clicked_cb (GtkButton *button, - GtkFileChooserDefault *impl) -{ - remove_selected_bookmarks (impl); -} - struct selection_check_closure { GtkFileChooserDefault *impl; int num_selected; @@ -2661,987 +1245,54 @@ selection_check (GtkFileChooserDefault *impl, *all_folders = closure.all_folders; } -struct get_selected_file_closure { - GtkFileChooserDefault *impl; - GFile *file; -}; - -static void -get_selected_file_foreach_cb (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gpointer data) -{ - struct get_selected_file_closure *closure = data; - - if (closure->file) - { - /* Just in case this function gets run more than once with a multiple selection; we only care about one file */ - g_object_unref (closure->file); - closure->file = NULL; - } - - gtk_tree_model_get (model, iter, - MODEL_COL_FILE, &closure->file, /* this will give us a reffed file */ - -1); -} - -/* Returns a selected path from the file list */ -static GFile * -get_selected_file (GtkFileChooserDefault *impl) -{ - struct get_selected_file_closure closure; - GtkTreeSelection *selection; - - closure.impl = impl; - closure.file = NULL; - - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_files_tree_view)); - gtk_tree_selection_selected_foreach (selection, - get_selected_file_foreach_cb, - &closure); - - return closure.file; -} - -typedef struct { - GtkFileChooserDefault *impl; - gchar *tip; -} UpdateTooltipData; - -static void -update_tooltip (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gpointer data) -{ - UpdateTooltipData *udata = data; - - if (udata->tip == NULL) - { - gchar *display_name; - - gtk_tree_model_get (model, iter, - MODEL_COL_NAME, &display_name, - -1); - - udata->tip = g_strdup_printf (_("Add the folder '%s' to the bookmarks"), - display_name); - g_free (display_name); - } -} - - -/* Sensitize the "add bookmark" button if all the selected items are folders, or - * if there are no selected items *and* the current folder is not in the - * bookmarks list. De-sensitize the button otherwise. - */ -static void -bookmarks_check_add_sensitivity (GtkFileChooserDefault *impl) -{ - gint num_selected; - gboolean all_folders; - gboolean active; - gchar *tip; - - selection_check (impl, &num_selected, NULL, &all_folders); - - if (num_selected == 0) - active = (impl->current_folder != NULL) && (shortcut_find_position (impl, impl->current_folder) == -1); - else if (num_selected == 1) - { - GFile *file; - - file = get_selected_file (impl); - active = file && all_folders && (shortcut_find_position (impl, file) == -1); - if (file) - g_object_unref (file); - } - else - active = all_folders; - - gtk_widget_set_sensitive (impl->browse_shortcuts_add_button, active); - - if (impl->browse_files_popup_menu_add_shortcut_item) - gtk_widget_set_sensitive (impl->browse_files_popup_menu_add_shortcut_item, - (num_selected == 0) ? FALSE : active); - - if (active) - { - if (num_selected == 0) - tip = g_strdup_printf (_("Add the current folder to the bookmarks")); - else if (num_selected > 1) - tip = g_strdup_printf (_("Add the selected folders to the bookmarks")); - else - { - GtkTreeSelection *selection; - UpdateTooltipData data; - - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_files_tree_view)); - data.impl = impl; - data.tip = NULL; - gtk_tree_selection_selected_foreach (selection, update_tooltip, &data); - tip = data.tip; - } - - gtk_widget_set_tooltip_text (impl->browse_shortcuts_add_button, tip); - g_free (tip); - } -} - -/* Sets the sensitivity of the "remove bookmark" button depending on whether a - * bookmark row is selected in the shortcuts tree. - */ -static void -bookmarks_check_remove_sensitivity (GtkFileChooserDefault *impl) -{ - GtkTreeIter iter; - gboolean removable = FALSE; - gchar *name = NULL; - gchar *tip; - - if (shortcuts_get_selected (impl, &iter)) - { - gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), &iter, - SHORTCUTS_COL_REMOVABLE, &removable, - SHORTCUTS_COL_NAME, &name, - -1); - gtk_widget_set_sensitive (impl->browse_shortcuts_remove_button, removable); - - if (removable) - tip = g_strdup_printf (_("Remove the bookmark '%s'"), name); - else - tip = g_strdup_printf (_("Bookmark '%s' cannot be removed"), name); - - gtk_widget_set_tooltip_text (impl->browse_shortcuts_remove_button, tip); - g_free (tip); - } - else - gtk_widget_set_tooltip_text (impl->browse_shortcuts_remove_button, - _("Remove the selected bookmark")); - g_free (name); -} - -static void -shortcuts_check_popup_sensitivity (GtkFileChooserDefault *impl) -{ - GtkTreeIter iter; - gboolean removable = FALSE; - - if (impl->browse_shortcuts_popup_menu == NULL) - return; - - if (shortcuts_get_selected (impl, &iter)) - gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), &iter, - SHORTCUTS_COL_REMOVABLE, &removable, - -1); - - gtk_widget_set_sensitive (impl->browse_shortcuts_popup_menu_remove_item, removable); - gtk_widget_set_sensitive (impl->browse_shortcuts_popup_menu_rename_item, removable); -} - -/* GtkWidget::drag-begin handler for the shortcuts list. */ -static void -shortcuts_drag_begin_cb (GtkWidget *widget, - GdkDragContext *context, - GtkFileChooserDefault *impl) -{ -#if 0 - impl->shortcuts_drag_context = g_object_ref (context); -#endif -} - -#if 0 -/* Removes the idle handler for outside drags */ -static void -shortcuts_cancel_drag_outside_idle (GtkFileChooserDefault *impl) -{ - if (!impl->shortcuts_drag_outside_idle) - return; - - g_source_destroy (impl->shortcuts_drag_outside_idle); - impl->shortcuts_drag_outside_idle = NULL; -} -#endif - -/* GtkWidget::drag-end handler for the shortcuts list. */ -static void -shortcuts_drag_end_cb (GtkWidget *widget, - GdkDragContext *context, - GtkFileChooserDefault *impl) -{ -#if 0 - g_object_unref (impl->shortcuts_drag_context); - - shortcuts_cancel_drag_outside_idle (impl); - - if (!impl->shortcuts_drag_outside) - return; - - gtk_button_clicked (GTK_BUTTON (impl->browse_shortcuts_remove_button)); - - impl->shortcuts_drag_outside = FALSE; -#endif -} - -/* GtkWidget::drag-data-delete handler for the shortcuts list. */ -static void -shortcuts_drag_data_delete_cb (GtkWidget *widget, - GdkDragContext *context, - GtkFileChooserDefault *impl) -{ - g_signal_stop_emission_by_name (widget, "drag-data-delete"); -} - -/* GtkWidget::drag-leave handler for the shortcuts list. We unhighlight the - * drop position. - */ -static void -shortcuts_drag_leave_cb (GtkWidget *widget, - GdkDragContext *context, - guint time_, - GtkFileChooserDefault *impl) -{ -#if 0 - if (gtk_drag_get_source_widget (context) == widget && !impl->shortcuts_drag_outside_idle) - { - impl->shortcuts_drag_outside_idle = add_idle_while_impl_is_alive (impl, G_CALLBACK (shortcuts_drag_outside_idle_cb)); - } -#endif - - gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (impl->browse_shortcuts_tree_view), - NULL, - GTK_TREE_VIEW_DROP_BEFORE); - - g_signal_stop_emission_by_name (widget, "drag-leave"); -} - -/* Computes the appropriate row and position for dropping */ -static void -shortcuts_compute_drop_position (GtkFileChooserDefault *impl, - int x, - int y, - GtkTreePath **path, - GtkTreeViewDropPosition *pos) -{ - GtkTreeView *tree_view; - GtkTreeViewColumn *column; - int cell_y; - GdkRectangle cell; - int row; - int bookmarks_index; - int header_height = 0; - - tree_view = GTK_TREE_VIEW (impl->browse_shortcuts_tree_view); - - if (gtk_tree_view_get_headers_visible (tree_view)) - header_height = _gtk_tree_view_get_header_height (tree_view); - - bookmarks_index = shortcuts_get_index (impl, SHORTCUTS_BOOKMARKS); - - if (!gtk_tree_view_get_path_at_pos (tree_view, - x, - y - header_height, - path, - &column, - NULL, - &cell_y)) - { - row = bookmarks_index + impl->num_bookmarks - 1; - *path = gtk_tree_path_new_from_indices (row, -1); - *pos = GTK_TREE_VIEW_DROP_AFTER; - return; - } - - row = *gtk_tree_path_get_indices (*path); - gtk_tree_view_get_background_area (tree_view, *path, column, &cell); - gtk_tree_path_free (*path); - - if (row < bookmarks_index) - { - row = bookmarks_index; - *pos = GTK_TREE_VIEW_DROP_BEFORE; - } - else if (row > bookmarks_index + impl->num_bookmarks - 1) - { - row = bookmarks_index + impl->num_bookmarks - 1; - *pos = GTK_TREE_VIEW_DROP_AFTER; - } - else - { - if (cell_y < cell.height / 2) - *pos = GTK_TREE_VIEW_DROP_BEFORE; - else - *pos = GTK_TREE_VIEW_DROP_AFTER; - } - - *path = gtk_tree_path_new_from_indices (row, -1); -} - -/* GtkWidget::drag-motion handler for the shortcuts list. We basically - * implement the destination side of DnD by hand, due to limitations in - * GtkTreeView's DnD API. - */ static gboolean -shortcuts_drag_motion_cb (GtkWidget *widget, - GdkDragContext *context, - gint x, - gint y, - guint time_, - GtkFileChooserDefault *impl) +file_is_recent_uri (GFile *file) { - GtkTreePath *path; - GtkTreeViewDropPosition pos; - GdkDragAction action; + GFile *recent; + gboolean same; -#if 0 - if (gtk_drag_get_source_widget (context) == widget) - { - shortcuts_cancel_drag_outside_idle (impl); + recent = g_file_new_for_uri ("recent:///"); + same = g_file_equal (file, recent); + g_object_unref (recent); - if (impl->shortcuts_drag_outside) - { - shortcuts_drag_set_delete_cursor (impl, FALSE); - impl->shortcuts_drag_outside = FALSE; - } - } -#endif - - if (gdk_drag_context_get_suggested_action (context) == GDK_ACTION_COPY || - (gdk_drag_context_get_actions (context) & GDK_ACTION_COPY) != 0) - action = GDK_ACTION_COPY; - else if (gdk_drag_context_get_suggested_action (context) == GDK_ACTION_MOVE || - (gdk_drag_context_get_actions (context) & GDK_ACTION_MOVE) != 0) - action = GDK_ACTION_MOVE; - else - { - action = 0; - goto out; - } - - shortcuts_compute_drop_position (impl, x, y, &path, &pos); - gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (impl->browse_shortcuts_tree_view), path, pos); - gtk_tree_path_free (path); - - out: - - g_signal_stop_emission_by_name (widget, "drag-motion"); - - if (action != 0) - { - gdk_drag_status (context, action, time_); - return TRUE; - } - else - return FALSE; -} - -/* GtkWidget::drag-drop handler for the shortcuts list. */ -static gboolean -shortcuts_drag_drop_cb (GtkWidget *widget, - GdkDragContext *context, - gint x, - gint y, - guint time_, - GtkFileChooserDefault *impl) -{ -#if 0 - shortcuts_cancel_drag_outside_idle (impl); -#endif - - g_signal_stop_emission_by_name (widget, "drag-drop"); - return TRUE; -} - -/* Parses a "text/uri-list" string and inserts its URIs as bookmarks */ -static void -shortcuts_drop_uris (GtkFileChooserDefault *impl, - GtkSelectionData *selection_data, - int position) -{ - gchar **uris; - gint i; - - uris = gtk_selection_data_get_uris (selection_data); - if (!uris) - return; - - for (i = 0; uris[i]; i++) - { - char *uri; - GFile *file; - - uri = uris[i]; - file = g_file_new_for_uri (uri); - - if (shortcuts_add_bookmark_from_file (impl, file, position)) - position++; - - g_object_unref (file); - } - - g_strfreev (uris); -} - -/* Reorders the selected bookmark to the specified position */ -static void -shortcuts_reorder (GtkFileChooserDefault *impl, - int new_position) -{ - GtkTreeIter iter; - gpointer col_data; - ShortcutType shortcut_type; - GtkTreePath *path; - int old_position; - int bookmarks_index; - GFile *file; - GError *error; - gchar *name = NULL; - - /* Get the selected path */ - - if (!shortcuts_get_selected (impl, &iter)) - g_assert_not_reached (); - - path = gtk_tree_model_get_path (GTK_TREE_MODEL (impl->shortcuts_model), &iter); - old_position = *gtk_tree_path_get_indices (path); - gtk_tree_path_free (path); - - bookmarks_index = shortcuts_get_index (impl, SHORTCUTS_BOOKMARKS); - old_position -= bookmarks_index; - g_assert (old_position >= 0 && old_position < impl->num_bookmarks); - - gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), &iter, - SHORTCUTS_COL_NAME, &name, - SHORTCUTS_COL_DATA, &col_data, - SHORTCUTS_COL_TYPE, &shortcut_type, - -1); - g_assert (col_data != NULL); - g_assert (shortcut_type == SHORTCUT_TYPE_FILE); - - file = col_data; - g_object_ref (file); /* removal below will free file, so we need a new ref */ - - /* Remove the path from the old position and insert it in the new one */ - - if (new_position > old_position) - new_position--; - - if (old_position == new_position) - goto out; - - error = NULL; - if (_gtk_file_system_remove_bookmark (impl->file_system, file, &error)) - { - shortcuts_add_bookmark_from_file (impl, file, new_position); - _gtk_file_system_set_bookmark_label (impl->file_system, file, name); - } - else - error_adding_bookmark_dialog (impl, file, error); - - out: - - g_object_unref (file); - g_free (name); -} - -/* Callback used when we get the drag data for the bookmarks list. We add the - * received URIs as bookmarks if they are folders. - */ -static void -shortcuts_drag_data_received_cb (GtkWidget *widget, - GdkDragContext *context, - gint x, - gint y, - GtkSelectionData *selection_data, - guint info, - guint time_, - gpointer data) -{ - GtkFileChooserDefault *impl; - GtkTreePath *tree_path; - GtkTreeViewDropPosition tree_pos; - GdkAtom target; - int position; - int bookmarks_index; - - impl = GTK_FILE_CHOOSER_DEFAULT (data); - - /* Compute position */ - - bookmarks_index = shortcuts_get_index (impl, SHORTCUTS_BOOKMARKS); - - shortcuts_compute_drop_position (impl, x, y, &tree_path, &tree_pos); - position = *gtk_tree_path_get_indices (tree_path); - gtk_tree_path_free (tree_path); - - if (tree_pos == GTK_TREE_VIEW_DROP_AFTER) - position++; - - g_assert (position >= bookmarks_index); - position -= bookmarks_index; - - target = gtk_selection_data_get_target (selection_data); - - if (gtk_targets_include_uri (&target, 1)) - shortcuts_drop_uris (impl, selection_data, position); - else if (target == gdk_atom_intern_static_string ("GTK_TREE_MODEL_ROW")) - shortcuts_reorder (impl, position); - - g_signal_stop_emission_by_name (widget, "drag-data-received"); -} - -/* Callback used to display a tooltip in the shortcuts tree */ -static gboolean -shortcuts_query_tooltip_cb (GtkWidget *widget, - gint x, - gint y, - gboolean keyboard_mode, - GtkTooltip *tooltip, - GtkFileChooserDefault *impl) -{ - GtkTreeModel *model; - GtkTreeIter iter; - - if (gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (widget), - &x, &y, - keyboard_mode, - &model, - NULL, - &iter)) - { - gpointer col_data; - ShortcutType shortcut_type; - - gtk_tree_model_get (model, &iter, - SHORTCUTS_COL_DATA, &col_data, - SHORTCUTS_COL_TYPE, &shortcut_type, - -1); - - if (shortcut_type == SHORTCUT_TYPE_SEPARATOR) - return FALSE; - else if (shortcut_type == SHORTCUT_TYPE_VOLUME) - return FALSE; - else if (shortcut_type == SHORTCUT_TYPE_FILE) - { - GFile *file; - char *parse_name; - - file = G_FILE (col_data); - parse_name = g_file_get_parse_name (file); - - gtk_tooltip_set_text (tooltip, parse_name); - - g_free (parse_name); - - return TRUE; - } - else if (shortcut_type == SHORTCUT_TYPE_SEARCH) - { - return FALSE; - } - else if (shortcut_type == SHORTCUT_TYPE_RECENT) - { - return FALSE; - } - } - - return FALSE; -} - - -/* Callback used when the selection in the shortcuts tree changes */ -static void -shortcuts_selection_changed_cb (GtkTreeSelection *selection, - GtkFileChooserDefault *impl) -{ - GtkTreeIter iter; - GtkTreeIter child_iter; - - bookmarks_check_remove_sensitivity (impl); - shortcuts_check_popup_sensitivity (impl); - - if (impl->changing_folder) - return; - - if (gtk_tree_selection_get_selected(selection, NULL, &iter)) - { - gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (impl->shortcuts_pane_filter_model), - &child_iter, - &iter); - shortcuts_activate_iter (impl, &child_iter); - } -} - -static gboolean -shortcuts_row_separator_func (GtkTreeModel *model, - GtkTreeIter *iter, - gpointer data) -{ - ShortcutType shortcut_type; - - gtk_tree_model_get (model, iter, SHORTCUTS_COL_TYPE, &shortcut_type, -1); - - return shortcut_type == SHORTCUT_TYPE_SEPARATOR; -} - -static gboolean -shortcuts_key_press_event_after_cb (GtkWidget *tree_view, - GdkEventKey *event, - GtkFileChooserDefault *impl) -{ - GtkWidget *entry; - - /* don't screw up focus switching with Tab */ - if (event->keyval == GDK_KEY_Tab - || event->keyval == GDK_KEY_KP_Tab - || event->keyval == GDK_KEY_ISO_Left_Tab - || event->length < 1) - return FALSE; - - if (impl->location_entry) - entry = impl->location_entry; - else if (impl->search_entry) - entry = impl->search_entry; - else - entry = NULL; - - if (entry) - { - gtk_widget_grab_focus (entry); - return gtk_widget_event (entry, (GdkEvent *) event); - } - else - return FALSE; -} - -/* Callback used when the file list's popup menu is detached */ -static void -shortcuts_popup_menu_detach_cb (GtkWidget *attach_widget, - GtkMenu *menu) -{ - GtkFileChooserDefault *impl; - - impl = g_object_get_data (G_OBJECT (attach_widget), "GtkFileChooserDefault"); - g_assert (GTK_IS_FILE_CHOOSER_DEFAULT (impl)); - - impl->browse_shortcuts_popup_menu = NULL; - impl->browse_shortcuts_popup_menu_remove_item = NULL; - impl->browse_shortcuts_popup_menu_rename_item = NULL; + return same; } static void -remove_shortcut_cb (GtkMenuItem *item, - GtkFileChooserDefault *impl) +places_sidebar_open_location_cb (GtkPlacesSidebar *sidebar, GFile *location, GtkPlacesOpenFlags open_flags, GtkFileChooserDefault *impl) { - remove_selected_bookmarks (impl); -} + gboolean clear_entry; -/* Rename the selected bookmark */ -static void -rename_selected_bookmark (GtkFileChooserDefault *impl) -{ - GtkTreeIter iter; - GtkTreePath *path; - GtkTreeViewColumn *column; - GtkCellRenderer *cell; - GList *renderers; - - if (shortcuts_get_selected (impl, &iter)) - { - path = gtk_tree_model_get_path (GTK_TREE_MODEL (impl->shortcuts_model), &iter); - column = gtk_tree_view_get_column (GTK_TREE_VIEW (impl->browse_shortcuts_tree_view), 0); - renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column)); - cell = g_list_nth_data (renderers, 1); - g_list_free (renderers); - g_object_set (cell, "editable", TRUE, NULL); - gtk_tree_view_set_cursor_on_cell (GTK_TREE_VIEW (impl->browse_shortcuts_tree_view), - path, column, cell, TRUE); - gtk_tree_path_free (path); - } -} - -static void -rename_shortcut_cb (GtkMenuItem *item, - GtkFileChooserDefault *impl) -{ - rename_selected_bookmark (impl); -} - -/* Constructs the popup menu for the file list if needed */ -static void -shortcuts_build_popup_menu (GtkFileChooserDefault *impl) -{ - GtkWidget *item; - - if (impl->browse_shortcuts_popup_menu) - return; - - impl->browse_shortcuts_popup_menu = gtk_menu_new (); - gtk_menu_attach_to_widget (GTK_MENU (impl->browse_shortcuts_popup_menu), - impl->browse_shortcuts_tree_view, - shortcuts_popup_menu_detach_cb); - - item = gtk_image_menu_item_new_with_label (_("Remove")); - impl->browse_shortcuts_popup_menu_remove_item = item; - gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), - gtk_image_new_from_stock (GTK_STOCK_REMOVE, GTK_ICON_SIZE_MENU)); - g_signal_connect (item, "activate", - G_CALLBACK (remove_shortcut_cb), impl); - gtk_widget_show (item); - gtk_menu_shell_append (GTK_MENU_SHELL (impl->browse_shortcuts_popup_menu), item); - - item = gtk_menu_item_new_with_label (_("Renameā€¦")); - impl->browse_shortcuts_popup_menu_rename_item = item; - g_signal_connect (item, "activate", - G_CALLBACK (rename_shortcut_cb), impl); - gtk_widget_show (item); - gtk_menu_shell_append (GTK_MENU_SHELL (impl->browse_shortcuts_popup_menu), item); -} - -static void -shortcuts_update_popup_menu (GtkFileChooserDefault *impl) -{ - shortcuts_build_popup_menu (impl); - shortcuts_check_popup_sensitivity (impl); -} - -static void -popup_position_func (GtkMenu *menu, - gint *x, - gint *y, - gboolean *push_in, - gpointer user_data); - -static void -shortcuts_popup_menu (GtkFileChooserDefault *impl, - GdkEventButton *event) -{ - shortcuts_update_popup_menu (impl); - if (event) - gtk_menu_popup (GTK_MENU (impl->browse_shortcuts_popup_menu), - NULL, NULL, NULL, NULL, - event->button, event->time); - else - { - gtk_menu_popup (GTK_MENU (impl->browse_shortcuts_popup_menu), - NULL, NULL, - popup_position_func, impl->browse_shortcuts_tree_view, - 0, GDK_CURRENT_TIME); - gtk_menu_shell_select_first (GTK_MENU_SHELL (impl->browse_shortcuts_popup_menu), - FALSE); - } -} - -/* Callback used for the GtkWidget::popup-menu signal of the shortcuts list */ -static gboolean -shortcuts_popup_menu_cb (GtkWidget *widget, - GtkFileChooserDefault *impl) -{ - shortcuts_popup_menu (impl, NULL); - return TRUE; -} - -/* Callback used when a button is pressed on the shortcuts list. - * We trap button 3 to bring up a popup menu. - */ -static gboolean -shortcuts_button_press_event_cb (GtkWidget *widget, - GdkEventButton *event, - GtkFileChooserDefault *impl) -{ - static gboolean in_press = FALSE; - gboolean handled; - - if (in_press) - return FALSE; - - if (!gdk_event_triggers_context_menu ((GdkEvent *) event)) - return FALSE; - - in_press = TRUE; - handled = gtk_widget_event (impl->browse_shortcuts_tree_view, (GdkEvent *) event); - in_press = FALSE; - - if (!handled) - return FALSE; - - shortcuts_popup_menu (impl, event); - return TRUE; -} - -static void -shortcuts_edited (GtkCellRenderer *cell, - gchar *path_string, - gchar *new_text, - GtkFileChooserDefault *impl) -{ - GtkTreePath *path; - GtkTreeIter iter; - GFile *shortcut; - - g_object_set (cell, "editable", FALSE, NULL); - - path = gtk_tree_path_new_from_string (path_string); - if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (impl->shortcuts_model), &iter, path)) - g_assert_not_reached (); - - gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), &iter, - SHORTCUTS_COL_DATA, &shortcut, - -1); - gtk_tree_path_free (path); - - _gtk_file_system_set_bookmark_label (impl->file_system, shortcut, new_text); -} - -static void -shortcuts_editing_canceled (GtkCellRenderer *cell, - GtkFileChooserDefault *impl) -{ - g_object_set (cell, "editable", FALSE, NULL); -} - -/* Creates the widgets for the shortcuts and bookmarks tree */ -static GtkWidget * -shortcuts_list_create (GtkFileChooserDefault *impl) -{ - GtkWidget *swin; - GtkTreeSelection *selection; - GtkTreeViewColumn *column; - GtkCellRenderer *renderer; - - /* Target types for dragging a row to/from the shortcuts list */ - const GtkTargetEntry tree_model_row_targets[] = { - { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, GTK_TREE_MODEL_ROW } - }; - - /* Scrolled window */ - - swin = gtk_scrolled_window_new (NULL, NULL); - gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (swin), - GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); - gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (swin), - GTK_SHADOW_IN); - gtk_widget_show (swin); - - /* Tree */ - impl->browse_shortcuts_tree_view = gtk_tree_view_new (); - gtk_style_context_add_class (gtk_widget_get_style_context (impl->browse_shortcuts_tree_view), - GTK_STYLE_CLASS_SIDEBAR); - gtk_tree_view_set_enable_search (GTK_TREE_VIEW (impl->browse_shortcuts_tree_view), FALSE); -#ifdef PROFILE_FILE_CHOOSER - g_object_set_data (G_OBJECT (impl->browse_shortcuts_tree_view), "fmq-name", "shortcuts"); -#endif - - /* Connect "after" to key-press-event on the shortcuts pane. We want this action to be possible: - * - * 1. user brings up a SAVE dialog - * 2. user clicks on a shortcut in the shortcuts pane - * 3. user starts typing a filename - * - * Normally, the user's typing would be ignored, as the shortcuts treeview doesn't - * support interactive search. However, we'd rather focus the location entry - * so that the user can type *there*. - * - * To preserve keyboard navigation in the shortcuts pane, we don't focus the - * filename entry if one clicks on a shortcut; rather, we focus the entry only - * if the user starts typing while the focus is in the shortcuts pane. + /* In the Save modes, we want to preserve what the uesr typed in the filename + * entry, so that he may choose another folder without erasing his typed name. */ - g_signal_connect_after (impl->browse_shortcuts_tree_view, "key-press-event", - G_CALLBACK (shortcuts_key_press_event_after_cb), impl); + if (impl->location_entry + && !(impl->action == GTK_FILE_CHOOSER_ACTION_SAVE + || impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER)) + clear_entry = TRUE; + else + clear_entry = FALSE; - g_signal_connect (impl->browse_shortcuts_tree_view, "popup-menu", - G_CALLBACK (shortcuts_popup_menu_cb), impl); - g_signal_connect (impl->browse_shortcuts_tree_view, "button-press-event", - G_CALLBACK (shortcuts_button_press_event_cb), impl); - /* Accessible object name for the file chooser's shortcuts pane */ - atk_object_set_name (gtk_widget_get_accessible (impl->browse_shortcuts_tree_view), _("Places")); + /* FIXME-places-sidebar: + * + * GtkPlacesSidebar doesn't have a Search item anymore. We should put that function in a toolbar-like button, like + * in Nautilus, and do operation_mode_set (impl, OPERATION_MODE_SEARCH); + */ - gtk_tree_view_set_model (GTK_TREE_VIEW (impl->browse_shortcuts_tree_view), impl->shortcuts_pane_filter_model); + if (file_is_recent_uri (location)) + operation_mode_set (impl, OPERATION_MODE_RECENT); + else + change_folder_and_display_error (impl, location, clear_entry); +} - gtk_tree_view_enable_model_drag_source (GTK_TREE_VIEW (impl->browse_shortcuts_tree_view), - GDK_BUTTON1_MASK, - tree_model_row_targets, - G_N_ELEMENTS (tree_model_row_targets), - GDK_ACTION_MOVE); - - gtk_drag_dest_set (impl->browse_shortcuts_tree_view, - GTK_DEST_DEFAULT_ALL, - tree_model_row_targets, - G_N_ELEMENTS (tree_model_row_targets), - GDK_ACTION_COPY | GDK_ACTION_MOVE); - gtk_drag_dest_add_uri_targets (impl->browse_shortcuts_tree_view); - - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_shortcuts_tree_view)); - gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE); - gtk_tree_selection_set_select_function (selection, - shortcuts_select_func, - impl, NULL); - - g_signal_connect (selection, "changed", - G_CALLBACK (shortcuts_selection_changed_cb), impl); - - g_signal_connect (impl->browse_shortcuts_tree_view, "key-press-event", - G_CALLBACK (shortcuts_key_press_event_cb), impl); - - g_signal_connect (impl->browse_shortcuts_tree_view, "drag-begin", - G_CALLBACK (shortcuts_drag_begin_cb), impl); - g_signal_connect (impl->browse_shortcuts_tree_view, "drag-end", - G_CALLBACK (shortcuts_drag_end_cb), impl); - g_signal_connect (impl->browse_shortcuts_tree_view, "drag-data-delete", - G_CALLBACK (shortcuts_drag_data_delete_cb), impl); - - g_signal_connect (impl->browse_shortcuts_tree_view, "drag-leave", - G_CALLBACK (shortcuts_drag_leave_cb), impl); - g_signal_connect (impl->browse_shortcuts_tree_view, "drag-motion", - G_CALLBACK (shortcuts_drag_motion_cb), impl); - g_signal_connect (impl->browse_shortcuts_tree_view, "drag-drop", - G_CALLBACK (shortcuts_drag_drop_cb), impl); - g_signal_connect (impl->browse_shortcuts_tree_view, "drag-data-received", - G_CALLBACK (shortcuts_drag_data_received_cb), impl); - - /* Support tooltips */ - gtk_widget_set_has_tooltip (impl->browse_shortcuts_tree_view, TRUE); - g_signal_connect (impl->browse_shortcuts_tree_view, "query-tooltip", - G_CALLBACK (shortcuts_query_tooltip_cb), impl); - - gtk_container_add (GTK_CONTAINER (swin), impl->browse_shortcuts_tree_view); - gtk_widget_show (impl->browse_shortcuts_tree_view); - - /* Column */ - - column = gtk_tree_view_column_new (); - /* Column header for the file chooser's shortcuts pane */ - gtk_tree_view_column_set_title (column, _("_Places")); - - renderer = gtk_cell_renderer_pixbuf_new (); - gtk_tree_view_column_pack_start (column, renderer, FALSE); - gtk_tree_view_column_set_attributes (column, renderer, - "pixbuf", SHORTCUTS_COL_PIXBUF, - "visible", SHORTCUTS_COL_PIXBUF_VISIBLE, - NULL); - - renderer = gtk_cell_renderer_text_new (); - g_object_set (renderer, - "width-chars", 12, - "ellipsize", PANGO_ELLIPSIZE_END, - NULL); - g_signal_connect (renderer, "edited", - G_CALLBACK (shortcuts_edited), impl); - g_signal_connect (renderer, "editing-canceled", - G_CALLBACK (shortcuts_editing_canceled), impl); - gtk_tree_view_column_pack_start (column, renderer, TRUE); - gtk_tree_view_column_set_attributes (column, renderer, - "text", SHORTCUTS_COL_NAME, - NULL); - - gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (impl->browse_shortcuts_tree_view), - shortcuts_row_separator_func, - NULL, NULL); - - gtk_tree_view_append_column (GTK_TREE_VIEW (impl->browse_shortcuts_tree_view), column); - - return swin; +/* Callback used when the places sidebar needs us to display an error message */ +static void +places_sidebar_show_error_message_cb (GtkPlacesSidebar *sidebar, + const char *primary, + const char *secondary, + GtkFileChooserDefault *impl) +{ + error_message (impl, primary, secondary); } /* Creates the widgets for the shortcuts/bookmarks pane */ @@ -3649,64 +1300,16 @@ static GtkWidget * shortcuts_pane_create (GtkFileChooserDefault *impl, GtkSizeGroup *size_group) { - GtkWidget *vbox; - GtkWidget *toolbar; - GtkWidget *widget; - GtkStyleContext *context; - GIcon *icon; + impl->places_sidebar = gtk_places_sidebar_new (); - vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); - gtk_widget_show (vbox); + g_signal_connect (impl->places_sidebar, "open-location", + G_CALLBACK (places_sidebar_open_location_cb), + impl); + g_signal_connect (impl->places_sidebar, "show-error-message", + G_CALLBACK (places_sidebar_show_error_message_cb), + impl); - /* Shortcuts tree */ - - widget = shortcuts_list_create (impl); - - gtk_size_group_add_widget (size_group, widget); - context = gtk_widget_get_style_context (widget); - gtk_style_context_set_junction_sides (context, GTK_JUNCTION_BOTTOM); - - gtk_box_pack_start (GTK_BOX (vbox), widget, TRUE, TRUE, 0); - - /* Box for buttons */ - - toolbar = gtk_toolbar_new (); - gtk_toolbar_set_style (GTK_TOOLBAR (toolbar), GTK_TOOLBAR_ICONS); - gtk_toolbar_set_icon_size (GTK_TOOLBAR (toolbar), GTK_ICON_SIZE_MENU); - - context = gtk_widget_get_style_context (toolbar); - gtk_style_context_set_junction_sides (context, GTK_JUNCTION_TOP); - gtk_style_context_add_class (context, GTK_STYLE_CLASS_INLINE_TOOLBAR); - - gtk_box_pack_start (GTK_BOX (vbox), toolbar, FALSE, FALSE, 0); - gtk_widget_show (toolbar); - - /* Add bookmark button */ - icon = g_themed_icon_new_with_default_fallbacks ("list-add-symbolic"); - impl->browse_shortcuts_add_button = toolbutton_new (impl, - icon, - FALSE, - TRUE, - G_CALLBACK (add_bookmark_button_clicked_cb)); - g_object_unref (icon); - - gtk_toolbar_insert (GTK_TOOLBAR (toolbar), GTK_TOOL_ITEM (impl->browse_shortcuts_add_button), 0); - gtk_widget_set_tooltip_text (impl->browse_shortcuts_add_button, - _("Add the selected folder to the Bookmarks")); - - /* Remove bookmark button */ - icon = g_themed_icon_new_with_default_fallbacks ("list-remove-symbolic"); - impl->browse_shortcuts_remove_button = toolbutton_new (impl, - icon, - FALSE, - TRUE, - G_CALLBACK (remove_bookmark_button_clicked_cb)); - g_object_unref (icon); - gtk_toolbar_insert (GTK_TOOLBAR (toolbar), GTK_TOOL_ITEM (impl->browse_shortcuts_remove_button), 1); - gtk_widget_set_tooltip_text (impl->browse_shortcuts_remove_button, - _("Remove the selected bookmark")); - - return vbox; + return impl->places_sidebar; } static gboolean @@ -3753,7 +1356,7 @@ browse_files_key_press_event_cb (GtkWidget *widget, if (key_is_left_or_right (event)) { - gtk_widget_grab_focus (impl->browse_shortcuts_tree_view); + gtk_widget_grab_focus (impl->places_sidebar); return TRUE; } @@ -3805,12 +1408,41 @@ popup_menu_detach_cb (GtkWidget *attach_widget, impl->browse_files_popup_menu_copy_file_location_item = NULL; } +/* Callback used from gtk_tree_selection_selected_foreach(); adds a bookmark for + * each selected item in the file list. + */ +static void +add_bookmark_foreach_cb (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + GtkFileChooserDefault *impl; + GFile *file; + + impl = (GtkFileChooserDefault *) data; + + gtk_tree_model_get (model, iter, + MODEL_COL_FILE, &file, + -1); + + _gtk_bookmarks_manager_insert_bookmark (impl->bookmarks_manager, file, 0, NULL); /* NULL-GError */ + + g_object_unref (file); +} + /* Callback used when the "Add to Bookmarks" menu item is activated */ static void add_to_shortcuts_cb (GtkMenuItem *item, GtkFileChooserDefault *impl) { - bookmarks_add_selected_folder (impl); + GtkTreeSelection *selection; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_files_tree_view)); + + gtk_tree_selection_selected_foreach (selection, + add_bookmark_foreach_cb, + impl); } /* callback used to set data to clipboard */ @@ -4197,7 +1829,6 @@ file_list_build_popup_menu (GtkFileChooserDefault *impl) impl->browse_files_popup_menu_size_column_item = file_list_add_check_menu_item (impl, _("Show _Size Column"), G_CALLBACK (show_size_column_toggled_cb)); - bookmarks_check_add_sensitivity (impl); check_copy_file_location_sensitivity (impl); } @@ -5093,9 +2724,6 @@ gtk_file_chooser_default_constructor (GType type, gtk_widget_push_composite_child (); - /* Shortcuts model */ - shortcuts_model_create (impl); - /* The browse widgets */ browse_widgets_create (impl); @@ -5141,6 +2769,22 @@ set_extra_widget (GtkFileChooserDefault *impl, gtk_widget_hide (impl->extra_align); } +static void +switch_to_home_dir (GtkFileChooserDefault *impl) +{ + const gchar *home = g_get_home_dir (); + GFile *home_file; + + if (home == NULL) + return; + + home_file = g_file_new_for_path (home); + + gtk_file_chooser_set_current_folder_file (GTK_FILE_CHOOSER (impl), home_file, NULL); /* NULL-GError */ + + g_object_unref (home_file); +} + static void set_local_only (GtkFileChooserDefault *impl, gboolean local_only) @@ -5152,12 +2796,6 @@ set_local_only (GtkFileChooserDefault *impl, if (impl->location_entry) _gtk_file_chooser_entry_set_local_only (GTK_FILE_CHOOSER_ENTRY (impl->location_entry), local_only); - if (impl->shortcuts_model && impl->file_system) - { - shortcuts_add_volumes (impl); - shortcuts_add_bookmarks (impl); - } - if (local_only && impl->current_folder && !_gtk_file_has_native_path (impl->current_folder)) { @@ -5165,33 +2803,11 @@ set_local_only (GtkFileChooserDefault *impl, * back to a local folder, but it's really up to the app to not cause * such a situation, so we ignore errors. */ - const gchar *home = g_get_home_dir (); - GFile *home_file; - - if (home == NULL) - return; - - home_file = g_file_new_for_path (home); - - gtk_file_chooser_set_current_folder_file (GTK_FILE_CHOOSER (impl), home_file, NULL); - - g_object_unref (home_file); + switch_to_home_dir (impl); } } } -static void -volumes_bookmarks_changed_cb (GtkFileSystem *file_system, - GtkFileChooserDefault *impl) -{ - shortcuts_add_volumes (impl); - shortcuts_add_bookmarks (impl); - - bookmarks_check_add_sensitivity (impl); - bookmarks_check_remove_sensitivity (impl); - shortcuts_check_popup_sensitivity (impl); -} - /* Sets the file chooser to multiple selection mode */ static void set_select_multiple (GtkFileChooserDefault *impl, @@ -5224,20 +2840,12 @@ set_file_system_backend (GtkFileChooserDefault *impl) impl->file_system = _gtk_file_system_new (); - g_signal_connect (impl->file_system, "volumes-changed", - G_CALLBACK (volumes_bookmarks_changed_cb), impl); - g_signal_connect (impl->file_system, "bookmarks-changed", - G_CALLBACK (volumes_bookmarks_changed_cb), impl); - profile_end ("end", NULL); } static void unset_file_system_backend (GtkFileChooserDefault *impl) { - g_signal_handlers_disconnect_by_func (impl->file_system, - G_CALLBACK (volumes_bookmarks_changed_cb), impl); - g_object_unref (impl->file_system); impl->file_system = NULL; @@ -5432,28 +3040,10 @@ operation_mode_set_recent (GtkFileChooserDefault *impl) recent_start_loading (impl); } -/* Sometimes we need to frob the selection in the shortcuts list manually */ -static void -shortcuts_select_item_without_activating (GtkFileChooserDefault *impl, int pos) -{ - GtkTreeSelection *selection; - GtkTreePath *path; - - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_shortcuts_tree_view)); - - g_signal_handlers_block_by_func (selection, G_CALLBACK (shortcuts_selection_changed_cb), impl); - - path = gtk_tree_path_new_from_indices (pos, -1); - gtk_tree_selection_select_path (selection, path); - gtk_tree_path_free (path); - - g_signal_handlers_unblock_by_func (selection, G_CALLBACK (shortcuts_selection_changed_cb), impl); -} - static void operation_mode_set (GtkFileChooserDefault *impl, OperationMode mode) { - ShortcutsIndex shortcut_to_select; + GFile *file; operation_mode_stop (impl, impl->operation_mode); @@ -5463,26 +3053,23 @@ operation_mode_set (GtkFileChooserDefault *impl, OperationMode mode) { case OPERATION_MODE_BROWSE: operation_mode_set_browse (impl); - shortcut_to_select = SHORTCUTS_CURRENT_FOLDER; break; case OPERATION_MODE_SEARCH: operation_mode_set_search (impl); - shortcut_to_select = SHORTCUTS_SEARCH; break; case OPERATION_MODE_RECENT: operation_mode_set_recent (impl); - shortcut_to_select = SHORTCUTS_RECENT; + file = g_file_new_for_uri ("recent:///"); + gtk_places_sidebar_set_location (GTK_PLACES_SIDEBAR (impl->places_sidebar), file); + g_object_unref (file); break; default: g_assert_not_reached (); return; } - - if (shortcut_to_select != SHORTCUTS_CURRENT_FOLDER) - shortcuts_select_item_without_activating (impl, shortcuts_get_index (impl, shortcut_to_select)); } /* This function is basically a do_all function. @@ -5813,6 +3400,12 @@ gtk_file_chooser_default_dispose (GObject *object) remove_settings_signal (impl, gtk_widget_get_screen (GTK_WIDGET (impl))); + if (impl->bookmarks_manager) + { + _gtk_bookmarks_manager_free (impl->bookmarks_manager); + impl->bookmarks_manager = NULL; + } + G_OBJECT_CLASS (_gtk_file_chooser_default_parent_class)->dispose (object); } @@ -5891,7 +3484,6 @@ change_icon_theme (GtkFileChooserDefault *impl) else impl->icon_size = FALLBACK_ICON_SIZE; - shortcuts_reload_icons (impl); /* the first cell in the first column is the icon column, and we have a fixed size there */ cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT ( gtk_tree_view_get_column (GTK_TREE_VIEW (impl->browse_files_tree_view), 0))); @@ -6018,6 +3610,7 @@ settings_load (GtkFileChooserDefault *impl) gboolean show_size_column; gint sort_column; GtkSortType sort_order; + StartupMode startup_mode; gint sidebar_width; GSettings *settings; @@ -6029,6 +3622,7 @@ settings_load (GtkFileChooserDefault *impl) sort_column = g_settings_get_enum (settings, SETTINGS_KEY_SORT_COLUMN); sort_order = g_settings_get_enum (settings, SETTINGS_KEY_SORT_ORDER); sidebar_width = g_settings_get_int (settings, SETTINGS_KEY_SIDEBAR_WIDTH); + startup_mode = g_settings_get_enum (settings, SETTINGS_KEY_STARTUP_MODE); location_mode_set (impl, location_mode, TRUE); @@ -6039,6 +3633,8 @@ settings_load (GtkFileChooserDefault *impl) impl->sort_column = sort_column; impl->sort_order = sort_order; + impl->startup_mode = startup_mode; + /* We don't call set_sort_column() here as the models may not have been * created yet. The individual functions that create and set the models will * call set_sort_column() themselves. @@ -6082,6 +3678,95 @@ gtk_file_chooser_default_realize (GtkWidget *widget) emit_default_size_changed (impl); } +/* Changes the current folder to $CWD */ +static void +switch_to_cwd (GtkFileChooserDefault *impl) +{ + char *current_working_dir; + + current_working_dir = g_get_current_dir (); + gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (impl), current_working_dir); + g_free (current_working_dir); +} + +/* Sets the file chooser to showing Recent Files or $CWD, depending on the + * user's settings. + */ +static void +set_startup_mode (GtkFileChooserDefault *impl) +{ + switch (impl->startup_mode) + { + case STARTUP_MODE_RECENT: + operation_mode_set (impl, OPERATION_MODE_RECENT); + break; + + case STARTUP_MODE_CWD: + switch_to_cwd (impl); + break; + + default: + g_assert_not_reached (); + } +} + +static gboolean +shortcut_exists (GtkFileChooserDefault *impl, GFile *needle) +{ + GSList *haystack; + GSList *l; + gboolean exists; + + exists = FALSE; + + haystack = gtk_places_sidebar_list_shortcuts (GTK_PLACES_SIDEBAR (impl->places_sidebar)); + for (l = haystack; l; l = l->next) + { + GFile *hay; + + hay = G_FILE (l->data); + if (g_file_equal (hay, needle)) + { + exists = TRUE; + break; + } + } + g_slist_free_full (haystack, g_object_unref); + + return exists; +} + +static void +add_cwd_to_sidebar_if_needed (GtkFileChooserDefault *impl) +{ + char *cwd; + GFile *cwd_file; + GFile *home_file; + + cwd = g_get_current_dir (); + cwd_file = g_file_new_for_path (cwd); + g_free (cwd); + + if (shortcut_exists (impl, cwd_file)) + goto out; + + home_file = g_file_new_for_path (g_get_home_dir ()); + + /* We only add an item for $CWD if it is different from $HOME. This way, + * applications which get launched from a shell in a terminal (by someone who + * knows what they are doing) will get an item for $CWD in the places sidebar, + * and "normal" applications launched from the desktop shell (whose $CWD is + * $HOME) won't get any extra clutter in the sidebar. + */ + if (!g_file_equal (home_file, cwd_file)) + gtk_places_sidebar_add_shortcut (GTK_PLACES_SIDEBAR (impl->places_sidebar), cwd_file); + + g_object_unref (home_file); + + out: + g_object_unref (cwd_file); +} + /* GtkWidget::map method */ static void gtk_file_chooser_default_map (GtkWidget *widget) @@ -6094,12 +3779,16 @@ gtk_file_chooser_default_map (GtkWidget *widget) GTK_WIDGET_CLASS (_gtk_file_chooser_default_parent_class)->map (widget); + settings_load (impl); + + add_cwd_to_sidebar_if_needed (impl); + if (impl->operation_mode == OPERATION_MODE_BROWSE) { switch (impl->reload_state) { case RELOAD_EMPTY: - recent_shortcut_handler (impl); + set_startup_mode (impl); break; case RELOAD_HAS_FOLDER: @@ -6113,10 +3802,6 @@ gtk_file_chooser_default_map (GtkWidget *widget) } } - volumes_bookmarks_changed_cb (impl->file_system, impl); - - settings_load (impl); - profile_end ("end", NULL); } @@ -7276,17 +4961,6 @@ update_current_folder_get_info_cb (GCancellable *cancellable, impl->reload_state = RELOAD_HAS_FOLDER; - /* Update the widgets that may trigger a folder change themselves. */ - - if (!impl->changing_folder) - { - impl->changing_folder = TRUE; - - shortcuts_update_current_folder (impl); - - impl->changing_folder = FALSE; - } - /* Set the folder on the save entry */ if (impl->location_entry) @@ -7306,12 +4980,11 @@ update_current_folder_get_info_cb (GCancellable *cancellable, /* Refresh controls */ - shortcuts_find_current_folder (impl); + gtk_places_sidebar_set_location (GTK_PLACES_SIDEBAR (impl->places_sidebar), impl->current_folder); g_signal_emit_by_name (impl, "current-folder-changed", 0); check_preview_change (impl); - bookmarks_check_add_sensitivity (impl); g_signal_emit_by_name (impl, "selection-changed", 0); @@ -7871,118 +5544,14 @@ gtk_file_chooser_default_list_filters (GtkFileChooser *chooser) return g_slist_copy (impl->filters); } -/* Returns the position in the shortcuts tree where the nth specified shortcut would appear */ -static int -shortcuts_get_pos_for_shortcut_folder (GtkFileChooserDefault *impl, - int pos) -{ - return pos + shortcuts_get_index (impl, SHORTCUTS_SHORTCUTS); -} - -struct AddShortcutData -{ - GtkFileChooserDefault *impl; - GFile *file; -}; - -static void -add_shortcut_get_info_cb (GCancellable *cancellable, - GFileInfo *info, - const GError *error, - gpointer user_data) -{ - int pos; - gboolean cancelled = g_cancellable_is_cancelled (cancellable); - struct AddShortcutData *data = user_data; - - if (!g_slist_find (data->impl->loading_shortcuts, cancellable)) - goto out; - - data->impl->loading_shortcuts = g_slist_remove (data->impl->loading_shortcuts, cancellable); - - if (cancelled || error || (! _gtk_file_info_consider_as_directory (info))) - goto out; - - pos = shortcuts_get_pos_for_shortcut_folder (data->impl, data->impl->num_shortcuts); - - shortcuts_insert_file (data->impl, pos, SHORTCUT_TYPE_FILE, NULL, data->file, NULL, FALSE, SHORTCUTS_SHORTCUTS); - - /* need to call shortcuts_add_bookmarks to flush out any duplicates bug #577806 */ - shortcuts_add_bookmarks (data->impl); - -out: - g_object_unref (data->impl); - g_object_unref (data->file); - g_free (data); - - g_object_unref (cancellable); -} - static gboolean gtk_file_chooser_default_add_shortcut_folder (GtkFileChooser *chooser, GFile *file, GError **error) { - GCancellable *cancellable; GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (chooser); - struct AddShortcutData *data; - GSList *l; - int pos; - - /* Avoid adding duplicates */ - pos = shortcut_find_position (impl, file); - if (pos >= 0 && pos < shortcuts_get_index (impl, SHORTCUTS_BOOKMARKS_SEPARATOR)) - { - gchar *uri; - - uri = g_file_get_uri (file); - /* translators, "Shortcut" means "Bookmark" here */ - g_set_error (error, - GTK_FILE_CHOOSER_ERROR, - GTK_FILE_CHOOSER_ERROR_ALREADY_EXISTS, - _("Shortcut %s already exists"), - uri); - g_free (uri); - - return FALSE; - } - - for (l = impl->loading_shortcuts; l; l = l->next) - { - GCancellable *c = l->data; - GFile *f; - - f = g_object_get_data (G_OBJECT (c), "add-shortcut-path-key"); - if (f && g_file_equal (file, f)) - { - gchar *uri; - - uri = g_file_get_uri (file); - g_set_error (error, - GTK_FILE_CHOOSER_ERROR, - GTK_FILE_CHOOSER_ERROR_ALREADY_EXISTS, - _("Shortcut %s already exists"), - uri); - g_free (uri); - - return FALSE; - } - } - - data = g_new0 (struct AddShortcutData, 1); - data->impl = g_object_ref (impl); - data->file = g_object_ref (file); - - cancellable = _gtk_file_system_get_info (impl->file_system, file, - "standard::type", - add_shortcut_get_info_cb, data); - - if (!cancellable) - return FALSE; - - impl->loading_shortcuts = g_slist_append (impl->loading_shortcuts, cancellable); - g_object_set_data (G_OBJECT (cancellable), "add-shortcut-path-key", data->file); + gtk_places_sidebar_add_shortcut (GTK_PLACES_SIDEBAR (impl->places_sidebar), file); return TRUE; } @@ -7992,114 +5561,17 @@ gtk_file_chooser_default_remove_shortcut_folder (GtkFileChooser *chooser, GError **error) { GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (chooser); - int pos; - GtkTreeIter iter; - GSList *l; - char *uri; - int i; - for (l = impl->loading_shortcuts; l; l = l->next) - { - GCancellable *c = l->data; - GFile *f; - - f = g_object_get_data (G_OBJECT (c), "add-shortcut-path-key"); - if (f && g_file_equal (file, f)) - { - impl->loading_shortcuts = g_slist_remove (impl->loading_shortcuts, c); - g_cancellable_cancel (c); - return TRUE; - } - } - - if (impl->num_shortcuts == 0) - goto out; - - pos = shortcuts_get_pos_for_shortcut_folder (impl, 0); - if (!gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (impl->shortcuts_model), &iter, NULL, pos)) - g_assert_not_reached (); - - for (i = 0; i < impl->num_shortcuts; i++) - { - gpointer col_data; - ShortcutType shortcut_type; - GFile *shortcut; - - gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), &iter, - SHORTCUTS_COL_DATA, &col_data, - SHORTCUTS_COL_TYPE, &shortcut_type, - -1); - g_assert (col_data != NULL); - g_assert (shortcut_type == SHORTCUT_TYPE_FILE); - - shortcut = col_data; - if (g_file_equal (shortcut, file)) - { - shortcuts_remove_rows (impl, pos + i, 1); - impl->num_shortcuts--; - return TRUE; - } - - if (!gtk_tree_model_iter_next (GTK_TREE_MODEL (impl->shortcuts_model), &iter)) - g_assert_not_reached (); - } - - out: - - uri = g_file_get_uri (file); - /* translators, "Shortcut" means "Bookmark" here */ - g_set_error (error, - GTK_FILE_CHOOSER_ERROR, - GTK_FILE_CHOOSER_ERROR_NONEXISTENT, - _("Shortcut %s does not exist"), - uri); - g_free (uri); - - return FALSE; + gtk_places_sidebar_remove_shortcut (GTK_PLACES_SIDEBAR (impl->places_sidebar), file); + return TRUE; } static GSList * gtk_file_chooser_default_list_shortcut_folders (GtkFileChooser *chooser) { GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (chooser); - int pos; - GtkTreeIter iter; - int i; - GSList *list; - if (impl->num_shortcuts == 0) - return NULL; - - pos = shortcuts_get_pos_for_shortcut_folder (impl, 0); - if (!gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (impl->shortcuts_model), &iter, NULL, pos)) - g_assert_not_reached (); - - list = NULL; - - for (i = 0; i < impl->num_shortcuts; i++) - { - gpointer col_data; - ShortcutType shortcut_type; - GFile *shortcut; - - gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), &iter, - SHORTCUTS_COL_DATA, &col_data, - SHORTCUTS_COL_TYPE, &shortcut_type, - -1); - g_assert (col_data != NULL); - g_assert (shortcut_type == SHORTCUT_TYPE_FILE); - - shortcut = col_data; - list = g_slist_prepend (list, g_object_ref (shortcut)); - - if (i != impl->num_shortcuts - 1) - { - if (!gtk_tree_model_iter_next (GTK_TREE_MODEL (impl->shortcuts_model), &iter)) - g_assert_not_reached (); - } - } - - return g_slist_reverse (list); + return gtk_places_sidebar_list_shortcuts (GTK_PLACES_SIDEBAR (impl->places_sidebar)); } /* Guesses a size based upon font sizes */ @@ -9711,304 +7183,6 @@ check_preview_change (GtkFileChooserDefault *impl) } } -static void -shortcuts_activate_volume_mount_cb (GCancellable *cancellable, - GtkFileSystemVolume *volume, - const GError *error, - gpointer data) -{ - GFile *file; - gboolean cancelled = g_cancellable_is_cancelled (cancellable); - GtkFileChooserDefault *impl = data; - - if (cancellable != impl->shortcuts_activate_iter_cancellable) - goto out; - - impl->shortcuts_activate_iter_cancellable = NULL; - - set_busy_cursor (impl, FALSE); - - if (cancelled) - goto out; - - if (error) - { - if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_FAILED_HANDLED)) - { - char *msg, *name; - - name = _gtk_file_system_volume_get_display_name (volume); - msg = g_strdup_printf (_("Could not mount %s"), name); - - error_message (impl, msg, error->message); - - g_free (msg); - g_free (name); - } - - goto out; - } - - file = _gtk_file_system_volume_get_root (volume); - if (file != NULL) - { - change_folder_and_display_error (impl, file, FALSE); - g_object_unref (file); - } - -out: - g_object_unref (impl); - g_object_unref (cancellable); -} - - -/* Activates a volume by mounting it if necessary and then switching to its - * base path. - */ -static void -shortcuts_activate_volume (GtkFileChooserDefault *impl, - GtkFileSystemVolume *volume) -{ - GFile *file; - - operation_mode_set (impl, OPERATION_MODE_BROWSE); - - /* We ref the file chooser since volume_mount() may run a main loop, and the - * user could close the file chooser window in the meantime. - */ - g_object_ref (impl); - - if (!_gtk_file_system_volume_is_mounted (volume)) - { - GMountOperation *mount_op; - - set_busy_cursor (impl, TRUE); - - mount_op = gtk_mount_operation_new (get_toplevel (GTK_WIDGET (impl))); - impl->shortcuts_activate_iter_cancellable = - _gtk_file_system_mount_volume (impl->file_system, volume, mount_op, - shortcuts_activate_volume_mount_cb, - g_object_ref (impl)); - g_object_unref (mount_op); - } - else - { - file = _gtk_file_system_volume_get_root (volume); - if (file != NULL) - { - change_folder_and_display_error (impl, file, FALSE); - g_object_unref (file); - } - } - - g_object_unref (impl); -} - -/* Opens the folder or volume at the specified iter in the shortcuts model */ -struct ShortcutsActivateData -{ - GtkFileChooserDefault *impl; - GFile *file; -}; - -static void -shortcuts_activate_get_info_cb (GCancellable *cancellable, - GFileInfo *info, - const GError *error, - gpointer user_data) -{ - gboolean cancelled = g_cancellable_is_cancelled (cancellable); - struct ShortcutsActivateData *data = user_data; - - if (cancellable != data->impl->shortcuts_activate_iter_cancellable) - goto out; - - data->impl->shortcuts_activate_iter_cancellable = NULL; - - if (cancelled) - goto out; - - if (!error && _gtk_file_info_consider_as_directory (info)) - change_folder_and_display_error (data->impl, data->file, FALSE); - else - gtk_file_chooser_default_select_file (GTK_FILE_CHOOSER (data->impl), - data->file, - NULL); - -out: - g_object_unref (data->impl); - g_object_unref (data->file); - g_free (data); - - g_object_unref (cancellable); -} - -static void -shortcuts_activate_mount_enclosing_volume (GCancellable *cancellable, - GtkFileSystemVolume *volume, - const GError *error, - gpointer user_data) -{ - struct ShortcutsActivateData *data = user_data; - - if (error) - { - error_changing_folder_dialog (data->impl, data->file, g_error_copy (error)); - - g_object_unref (data->impl); - g_object_unref (data->file); - g_free (data); - - return; - } - - data->impl->shortcuts_activate_iter_cancellable = - _gtk_file_system_get_info (data->impl->file_system, data->file, - "standard::type", - shortcuts_activate_get_info_cb, data); - - if (volume) - _gtk_file_system_volume_unref (volume); -} - -static void -shortcuts_activate_iter (GtkFileChooserDefault *impl, - GtkTreeIter *iter) -{ - gpointer col_data; - ShortcutType shortcut_type; - - /* In the Save modes, we want to preserve what the uesr typed in the filename - * entry, so that he may choose another folder without erasing his typed name. - */ - if (impl->location_entry - && !(impl->action == GTK_FILE_CHOOSER_ACTION_SAVE - || impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER)) - gtk_entry_set_text (GTK_ENTRY (impl->location_entry), ""); - - gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), iter, - SHORTCUTS_COL_DATA, &col_data, - SHORTCUTS_COL_TYPE, &shortcut_type, - -1); - - if (impl->shortcuts_activate_iter_cancellable) - { - g_cancellable_cancel (impl->shortcuts_activate_iter_cancellable); - impl->shortcuts_activate_iter_cancellable = NULL; - } - - if (shortcut_type == SHORTCUT_TYPE_SEPARATOR) - return; - else if (shortcut_type == SHORTCUT_TYPE_VOLUME) - { - GtkFileSystemVolume *volume; - - volume = col_data; - - operation_mode_set (impl, OPERATION_MODE_BROWSE); - - shortcuts_activate_volume (impl, volume); - } - else if (shortcut_type == SHORTCUT_TYPE_FILE) - { - struct ShortcutsActivateData *data; - GtkFileSystemVolume *volume; - - operation_mode_set (impl, OPERATION_MODE_BROWSE); - - volume = _gtk_file_system_get_volume_for_file (impl->file_system, col_data); - - data = g_new0 (struct ShortcutsActivateData, 1); - data->impl = g_object_ref (impl); - data->file = g_object_ref (col_data); - - if (!volume || !_gtk_file_system_volume_is_mounted (volume)) - { - GMountOperation *mount_operation; - GtkWidget *toplevel; - - toplevel = gtk_widget_get_toplevel (GTK_WIDGET (impl)); - - mount_operation = gtk_mount_operation_new (GTK_WINDOW (toplevel)); - - impl->shortcuts_activate_iter_cancellable = - _gtk_file_system_mount_enclosing_volume (impl->file_system, col_data, - mount_operation, - shortcuts_activate_mount_enclosing_volume, - data); - } - else - { - impl->shortcuts_activate_iter_cancellable = - _gtk_file_system_get_info (impl->file_system, data->file, - "standard::type", - shortcuts_activate_get_info_cb, data); - } - } - else if (shortcut_type == SHORTCUT_TYPE_SEARCH) - { - operation_mode_set (impl, OPERATION_MODE_SEARCH); - } - else if (shortcut_type == SHORTCUT_TYPE_RECENT) - { - operation_mode_set (impl, OPERATION_MODE_RECENT); - } -} - -/* Handler for GtkWidget::key-press-event on the shortcuts list */ -static gboolean -shortcuts_key_press_event_cb (GtkWidget *widget, - GdkEventKey *event, - GtkFileChooserDefault *impl) -{ - guint modifiers; - - modifiers = gtk_accelerator_get_default_mod_mask (); - - if (key_is_left_or_right (event)) - { - gtk_widget_grab_focus (impl->browse_files_tree_view); - return TRUE; - } - - if ((event->keyval == GDK_KEY_BackSpace - || event->keyval == GDK_KEY_Delete - || event->keyval == GDK_KEY_KP_Delete) - && (event->state & modifiers) == 0) - { - remove_selected_bookmarks (impl); - return TRUE; - } - - if ((event->keyval == GDK_KEY_F2) - && (event->state & modifiers) == 0) - { - rename_selected_bookmark (impl); - return TRUE; - } - - return FALSE; -} - -static gboolean -shortcuts_select_func (GtkTreeSelection *selection, - GtkTreeModel *model, - GtkTreePath *path, - gboolean path_currently_selected, - gpointer data) -{ - GtkFileChooserDefault *impl = data; - GtkTreeIter filter_iter; - ShortcutType shortcut_type; - - if (!gtk_tree_model_get_iter (impl->shortcuts_pane_filter_model, &filter_iter, path)) - g_assert_not_reached (); - - gtk_tree_model_get (impl->shortcuts_pane_filter_model, &filter_iter, SHORTCUTS_COL_TYPE, &shortcut_type, -1); - - return shortcut_type != SHORTCUT_TYPE_SEPARATOR; -} - static gboolean list_select_func (GtkTreeSelection *selection, GtkTreeModel *model, @@ -10065,7 +7239,6 @@ list_selection_changed (GtkTreeSelection *selection, path_bar_update (impl); check_preview_change (impl); - bookmarks_check_add_sensitivity (impl); check_copy_file_location_sensitivity (impl); g_signal_emit_by_name (impl, "selection-changed", 0); @@ -10257,77 +7430,63 @@ down_folder_handler (GtkFileChooserDefault *impl) _gtk_path_bar_down (GTK_PATH_BAR (impl->browse_path_bar)); } -/* Switches to the shortcut in the specified index */ -static void -switch_to_shortcut (GtkFileChooserDefault *impl, - int pos) -{ - GtkTreeIter iter; - - if (!gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (impl->shortcuts_model), &iter, NULL, pos)) - g_assert_not_reached (); - - shortcuts_activate_iter (impl, &iter); -} - /* Handler for the "home-folder" keybinding signal */ static void home_folder_handler (GtkFileChooserDefault *impl) { - if (impl->has_home) - switch_to_shortcut (impl, shortcuts_get_index (impl, SHORTCUTS_HOME)); + switch_to_home_dir (impl); } /* Handler for the "desktop-folder" keybinding signal */ static void desktop_folder_handler (GtkFileChooserDefault *impl) { - if (impl->has_desktop) - switch_to_shortcut (impl, shortcuts_get_index (impl, SHORTCUTS_DESKTOP)); + const char *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 (!g_strcmp0 (name, g_get_home_dir ())) { + return; + } + + gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (impl), name); } /* Handler for the "search-shortcut" keybinding signal */ static void search_shortcut_handler (GtkFileChooserDefault *impl) { - if (impl->has_search) - { - switch_to_shortcut (impl, shortcuts_get_index (impl, SHORTCUTS_SEARCH)); + operation_mode_set (impl, OPERATION_MODE_SEARCH); - /* we want the entry widget to grab the focus the first - * time, not the browse_files_tree_view widget. - */ - if (impl->search_entry) - gtk_widget_grab_focus (impl->search_entry); - } + /* we want the entry widget to grab the focus the first + * time, not the browse_files_tree_view widget. + */ + if (impl->search_entry) + gtk_widget_grab_focus (impl->search_entry); } /* Handler for the "recent-shortcut" keybinding signal */ static void recent_shortcut_handler (GtkFileChooserDefault *impl) { - switch_to_shortcut (impl, shortcuts_get_index (impl, SHORTCUTS_RECENT)); + operation_mode_set (impl, OPERATION_MODE_RECENT); } static void quick_bookmark_handler (GtkFileChooserDefault *impl, gint bookmark_index) { - int bookmark_pos; - GtkTreePath *path; + GFile *file; - if (bookmark_index < 0 || bookmark_index >= impl->num_bookmarks) - return; + file = gtk_places_sidebar_get_nth_bookmark (GTK_PLACES_SIDEBAR (impl->places_sidebar), bookmark_index); - bookmark_pos = shortcuts_get_index (impl, SHORTCUTS_BOOKMARKS) + bookmark_index; - - path = gtk_tree_path_new_from_indices (bookmark_pos, -1); - gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (impl->browse_shortcuts_tree_view), - path, NULL, - FALSE, 0.0, 0.0); - gtk_tree_path_free (path); - - switch_to_shortcut (impl, bookmark_pos); + if (file) + { + change_folder_and_display_error (impl, file, FALSE); + g_object_unref (file); + } } static void @@ -10337,83 +7496,3 @@ show_hidden_handler (GtkFileChooserDefault *impl) "show-hidden", !impl->show_hidden, NULL); } - - -/* Drag and drop interfaces */ - -static void -_shortcuts_pane_model_filter_class_init (ShortcutsPaneModelFilterClass *class) -{ -} - -static void -_shortcuts_pane_model_filter_init (ShortcutsPaneModelFilter *model) -{ - model->impl = NULL; -} - -/* GtkTreeDragSource::row_draggable implementation for the shortcuts filter model */ -static gboolean -shortcuts_pane_model_filter_row_draggable (GtkTreeDragSource *drag_source, - GtkTreePath *path) -{ - ShortcutsPaneModelFilter *model; - int pos; - int bookmarks_pos; - - model = SHORTCUTS_PANE_MODEL_FILTER (drag_source); - - pos = *gtk_tree_path_get_indices (path); - bookmarks_pos = shortcuts_get_index (model->impl, SHORTCUTS_BOOKMARKS); - - return (pos >= bookmarks_pos && pos < bookmarks_pos + model->impl->num_bookmarks); -} - -/* GtkTreeDragSource::drag_data_get implementation for the shortcuts - * filter model - */ -static gboolean -shortcuts_pane_model_filter_drag_data_get (GtkTreeDragSource *drag_source, - GtkTreePath *path, - GtkSelectionData *selection_data) -{ - /* FIXME */ - - return FALSE; -} - -/* Fill the GtkTreeDragSourceIface vtable */ -static void -shortcuts_pane_model_filter_drag_source_iface_init (GtkTreeDragSourceIface *iface) -{ - iface->row_draggable = shortcuts_pane_model_filter_row_draggable; - iface->drag_data_get = shortcuts_pane_model_filter_drag_data_get; -} - -#if 0 -/* Fill the GtkTreeDragDestIface vtable */ -static void -shortcuts_pane_model_filter_drag_dest_iface_init (GtkTreeDragDestIface *iface) -{ - iface->drag_data_received = shortcuts_pane_model_filter_drag_data_received; - iface->row_drop_possible = shortcuts_pane_model_filter_row_drop_possible; -} -#endif - -static GtkTreeModel * -shortcuts_pane_model_filter_new (GtkFileChooserDefault *impl, - GtkTreeModel *child_model, - GtkTreePath *root) -{ - ShortcutsPaneModelFilter *model; - - model = g_object_new (SHORTCUTS_PANE_MODEL_FILTER_TYPE, - "child-model", child_model, - "virtual-root", root, - NULL); - - model->impl = impl; - - return GTK_TREE_MODEL (model); -} - diff --git a/gtk/gtkfilechooserprivate.h b/gtk/gtkfilechooserprivate.h index efcb0ea0a0..fba29c609a 100644 --- a/gtk/gtkfilechooserprivate.h +++ b/gtk/gtkfilechooserprivate.h @@ -19,6 +19,7 @@ #ifndef __GTK_FILE_CHOOSER_PRIVATE_H__ #define __GTK_FILE_CHOOSER_PRIVATE_H__ +#include "gtkbookmarksmanager.h" #include "gtkfilechooser.h" #include "gtkfilesystem.h" #include "gtkfilesystemmodel.h" @@ -42,6 +43,7 @@ G_BEGIN_DECLS #define SETTINGS_KEY_WINDOW_POSITION "window-position" #define SETTINGS_KEY_WINDOW_SIZE "window-size" #define SETTINGS_KEY_SIDEBAR_WIDTH "sidebar-width" +#define SETTINGS_KEY_STARTUP_MODE "startup-mode" #define GTK_FILE_CHOOSER_GET_IFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), GTK_TYPE_FILE_CHOOSER, GtkFileChooserIface)) @@ -146,6 +148,13 @@ typedef enum { OPERATION_MODE_RECENT } OperationMode; +typedef enum { + STARTUP_MODE_RECENT, + STARTUP_MODE_CWD +} StartupMode; + +#define REMOVE_FOR_PLACES_SIDEBAR 0 + struct _GtkFileChooserDefault { GtkBox parent_instance; @@ -164,12 +173,6 @@ struct _GtkFileChooserDefault GtkWidget *browse_widgets_box; GtkWidget *browse_widgets_hpaned; GtkWidget *browse_header_box; - GtkWidget *browse_shortcuts_tree_view; - GtkWidget *browse_shortcuts_add_button; - GtkWidget *browse_shortcuts_remove_button; - GtkWidget *browse_shortcuts_popup_menu; - GtkWidget *browse_shortcuts_popup_menu_remove_item; - GtkWidget *browse_shortcuts_popup_menu_rename_item; GtkWidget *browse_files_tree_view; GtkWidget *browse_files_popup_menu; GtkWidget *browse_files_popup_menu_add_shortcut_item; @@ -190,6 +193,9 @@ struct _GtkFileChooserDefault GtkFileSystemModel *browse_files_model; char *browse_files_last_selected_name; + GtkWidget *places_sidebar; + StartupMode startup_mode; + /* OPERATION_MODE_SEARCH */ GtkWidget *search_hbox; GtkWidget *search_entry; @@ -216,13 +222,6 @@ struct _GtkFileChooserDefault GtkWidget *location_entry; LocationMode location_mode; - GtkListStore *shortcuts_model; - - /* Filter for the shortcuts pane. We filter out the "current folder" row and - * the separator that we use for the "Save in folder" combo. - */ - GtkTreeModel *shortcuts_pane_filter_model; - /* Handles */ GSList *loading_shortcuts; GSList *reload_icon_cancellables; @@ -244,6 +243,8 @@ struct _GtkFileChooserDefault GtkFileFilter *current_filter; GSList *filters; + GtkBookmarksManager *bookmarks_manager; + int num_volumes; int num_shortcuts; int num_bookmarks; diff --git a/gtk/gtkfilesystem.c b/gtk/gtkfilesystem.c index 2fdfa3df9d..186992ca0e 100644 --- a/gtk/gtkfilesystem.c +++ b/gtk/gtkfilesystem.c @@ -55,7 +55,6 @@ enum { }; enum { - BOOKMARKS_CHANGED, VOLUMES_CHANGED, FS_LAST_SIGNAL }; @@ -81,11 +80,6 @@ struct GtkFileSystemPrivate * of type GDrive, GVolume and GMount */ GSList *volumes; - - /* This list contains GtkFileSystemBookmark structs */ - GSList *bookmarks; - - GFileMonitor *bookmarks_monitor; }; struct AsyncFuncData @@ -99,24 +93,9 @@ struct AsyncFuncData gpointer data; }; -struct GtkFileSystemBookmark -{ - GFile *file; - gchar *label; -}; - G_DEFINE_TYPE (GtkFileSystem, _gtk_file_system, G_TYPE_OBJECT) -/* GtkFileSystemBookmark methods */ -void -_gtk_file_system_bookmark_free (GtkFileSystemBookmark *bookmark) -{ - g_object_unref (bookmark->file); - g_free (bookmark->label); - g_slice_free (GtkFileSystemBookmark, bookmark); -} - /* GtkFileSystem methods */ static void volumes_changed (GVolumeMonitor *volume_monitor, @@ -157,42 +136,12 @@ gtk_file_system_dispose (GObject *object) G_OBJECT_CLASS (_gtk_file_system_parent_class)->dispose (object); } -static void -gtk_file_system_finalize (GObject *object) -{ - GtkFileSystem *file_system = GTK_FILE_SYSTEM (object); - GtkFileSystemPrivate *priv = file_system->priv; - - DEBUG ("finalize"); - - if (priv->bookmarks_monitor) - g_object_unref (priv->bookmarks_monitor); - - if (priv->bookmarks) - { - g_slist_foreach (priv->bookmarks, (GFunc) _gtk_file_system_bookmark_free, NULL); - g_slist_free (priv->bookmarks); - } - - G_OBJECT_CLASS (_gtk_file_system_parent_class)->finalize (object); -} - static void _gtk_file_system_class_init (GtkFileSystemClass *class) { GObjectClass *object_class = G_OBJECT_CLASS (class); object_class->dispose = gtk_file_system_dispose; - object_class->finalize = gtk_file_system_finalize; - - fs_signals[BOOKMARKS_CHANGED] = - g_signal_new ("bookmarks-changed", - G_TYPE_FROM_CLASS (object_class), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (GtkFileSystemClass, bookmarks_changed), - NULL, NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, 0); fs_signals[VOLUMES_CHANGED] = g_signal_new ("volumes-changed", @@ -206,155 +155,6 @@ _gtk_file_system_class_init (GtkFileSystemClass *class) g_type_class_add_private (object_class, sizeof (GtkFileSystemPrivate)); } -static GFile * -get_legacy_bookmarks_file (void) -{ - GFile *file; - gchar *filename; - - filename = g_build_filename (g_get_home_dir (), ".gtk-bookmarks", NULL); - file = g_file_new_for_path (filename); - g_free (filename); - - return file; -} - -static GFile * -get_bookmarks_file (void) -{ - GFile *file; - gchar *filename; - - filename = g_build_filename (g_get_user_config_dir (), "gtk-3.0", "bookmarks", NULL); - file = g_file_new_for_path (filename); - g_free (filename); - - return file; -} - -static GSList * -read_bookmarks (GFile *file) -{ - gchar *contents; - gchar **lines, *space; - GSList *bookmarks = NULL; - gint i; - - if (!g_file_load_contents (file, NULL, &contents, - NULL, NULL, NULL)) - return NULL; - - lines = g_strsplit (contents, "\n", -1); - - for (i = 0; lines[i]; i++) - { - GtkFileSystemBookmark *bookmark; - - if (!*lines[i]) - continue; - - if (!g_utf8_validate (lines[i], -1, NULL)) - continue; - - bookmark = g_slice_new0 (GtkFileSystemBookmark); - - if ((space = strchr (lines[i], ' ')) != NULL) - { - space[0] = '\0'; - bookmark->label = g_strdup (space + 1); - } - - bookmark->file = g_file_new_for_uri (lines[i]); - bookmarks = g_slist_prepend (bookmarks, bookmark); - } - - bookmarks = g_slist_reverse (bookmarks); - g_strfreev (lines); - g_free (contents); - - return bookmarks; -} - -static void -save_bookmarks (GFile *bookmarks_file, - GSList *bookmarks) -{ - GError *error = NULL; - GString *contents; - GSList *l; - GFile *parent_file; - gchar *path; - - contents = g_string_new (""); - - for (l = bookmarks; l; l = l->next) - { - GtkFileSystemBookmark *bookmark = l->data; - gchar *uri; - - uri = g_file_get_uri (bookmark->file); - if (!uri) - continue; - - g_string_append (contents, uri); - - if (bookmark->label) - g_string_append_printf (contents, " %s", bookmark->label); - - g_string_append_c (contents, '\n'); - g_free (uri); - } - - parent_file = g_file_get_parent (bookmarks_file); - path = g_file_get_path (parent_file); - if (g_mkdir_with_parents (path, 0700) == 0) - { - if (!g_file_replace_contents (bookmarks_file, - contents->str, - strlen (contents->str), - NULL, FALSE, 0, NULL, - NULL, &error)) - { - g_critical ("%s", error->message); - g_error_free (error); - } - } - g_free (path); - g_object_unref (parent_file); - g_string_free (contents, TRUE); -} - -static void -bookmarks_file_changed (GFileMonitor *monitor, - GFile *file, - GFile *other_file, - GFileMonitorEvent event, - gpointer data) -{ - GtkFileSystem *file_system = GTK_FILE_SYSTEM (data); - GtkFileSystemPrivate *priv = file_system->priv; - - switch (event) - { - case G_FILE_MONITOR_EVENT_CHANGED: - case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: - case G_FILE_MONITOR_EVENT_CREATED: - case G_FILE_MONITOR_EVENT_DELETED: - g_slist_foreach (priv->bookmarks, (GFunc) _gtk_file_system_bookmark_free, NULL); - g_slist_free (priv->bookmarks); - - priv->bookmarks = read_bookmarks (file); - - gdk_threads_enter (); - g_signal_emit (data, fs_signals[BOOKMARKS_CHANGED], 0); - gdk_threads_leave (); - break; - default: - /* ignore at the moment */ - break; - } -} - static gboolean mount_referenced_by_volume_activation_root (GList *volumes, GMount *mount) { @@ -533,8 +333,6 @@ static void _gtk_file_system_init (GtkFileSystem *file_system) { GtkFileSystemPrivate *priv; - GFile *bookmarks_file; - GError *error = NULL; DEBUG ("init"); @@ -564,35 +362,6 @@ _gtk_file_system_init (GtkFileSystem *file_system) G_CALLBACK (volumes_changed), file_system); g_signal_connect (priv->volume_monitor, "drive-changed", G_CALLBACK (volumes_changed), file_system); - - /* Bookmarks */ - bookmarks_file = get_bookmarks_file (); - priv->bookmarks = read_bookmarks (bookmarks_file); - if (!priv->bookmarks) - { - GFile *legacy_bookmarks_file; - - /* Read the legacy one and write it to the new one */ - legacy_bookmarks_file = get_legacy_bookmarks_file (); - priv->bookmarks = read_bookmarks (legacy_bookmarks_file); - save_bookmarks (bookmarks_file, priv->bookmarks); - - g_object_unref (legacy_bookmarks_file); - } - - priv->bookmarks_monitor = g_file_monitor_file (bookmarks_file, - G_FILE_MONITOR_NONE, - NULL, &error); - if (error) - { - g_warning ("%s", error->message); - g_error_free (error); - } - else - g_signal_connect (priv->bookmarks_monitor, "changed", - G_CALLBACK (bookmarks_file_changed), file_system); - - g_object_unref (bookmarks_file); } /* GtkFileSystem public methods */ @@ -622,29 +391,6 @@ _gtk_file_system_list_volumes (GtkFileSystem *file_system) return list; } -GSList * -_gtk_file_system_list_bookmarks (GtkFileSystem *file_system) -{ - GtkFileSystemPrivate *priv = file_system->priv; - GSList *bookmarks, *files = NULL; - - DEBUG ("list_bookmarks"); - - bookmarks = priv->bookmarks; - - while (bookmarks) - { - GtkFileSystemBookmark *bookmark; - - bookmark = bookmarks->data; - bookmarks = bookmarks->next; - - files = g_slist_prepend (files, g_object_ref (bookmark->file)); - } - - return g_slist_reverse (files); -} - static void free_async_data (AsyncFuncData *async_data) { @@ -871,185 +617,6 @@ _gtk_file_system_mount_enclosing_volume (GtkFileSystem *file return cancellable; } -gboolean -_gtk_file_system_insert_bookmark (GtkFileSystem *file_system, - GFile *file, - gint position, - GError **error) -{ - GtkFileSystemPrivate *priv = file_system->priv; - GSList *bookmarks; - GtkFileSystemBookmark *bookmark; - gboolean result = TRUE; - GFile *bookmarks_file; - - bookmarks = priv->bookmarks; - - while (bookmarks) - { - bookmark = bookmarks->data; - bookmarks = bookmarks->next; - - if (g_file_equal (bookmark->file, file)) - { - /* File is already in bookmarks */ - result = FALSE; - break; - } - } - - if (!result) - { - gchar *uri = g_file_get_uri (file); - - g_set_error (error, - GTK_FILE_CHOOSER_ERROR, - GTK_FILE_CHOOSER_ERROR_ALREADY_EXISTS, - "%s already exists in the bookmarks list", - uri); - - g_free (uri); - - return FALSE; - } - - bookmark = g_slice_new0 (GtkFileSystemBookmark); - bookmark->file = g_object_ref (file); - - priv->bookmarks = g_slist_insert (priv->bookmarks, bookmark, position); - - bookmarks_file = get_bookmarks_file (); - save_bookmarks (bookmarks_file, priv->bookmarks); - g_object_unref (bookmarks_file); - - g_signal_emit (file_system, fs_signals[BOOKMARKS_CHANGED], 0); - - return TRUE; -} - -gboolean -_gtk_file_system_remove_bookmark (GtkFileSystem *file_system, - GFile *file, - GError **error) -{ - GtkFileSystemPrivate *priv = file_system->priv; - GtkFileSystemBookmark *bookmark; - GSList *bookmarks; - gboolean result = FALSE; - GFile *bookmarks_file; - - if (!priv->bookmarks) - return FALSE; - - bookmarks = priv->bookmarks; - - while (bookmarks) - { - bookmark = bookmarks->data; - - if (g_file_equal (bookmark->file, file)) - { - result = TRUE; - priv->bookmarks = g_slist_remove_link (priv->bookmarks, bookmarks); - _gtk_file_system_bookmark_free (bookmark); - g_slist_free_1 (bookmarks); - break; - } - - bookmarks = bookmarks->next; - } - - if (!result) - { - gchar *uri = g_file_get_uri (file); - - g_set_error (error, - GTK_FILE_CHOOSER_ERROR, - GTK_FILE_CHOOSER_ERROR_NONEXISTENT, - "%s does not exist in the bookmarks list", - uri); - - g_free (uri); - - return FALSE; - } - - bookmarks_file = get_bookmarks_file (); - save_bookmarks (bookmarks_file, priv->bookmarks); - g_object_unref (bookmarks_file); - - g_signal_emit (file_system, fs_signals[BOOKMARKS_CHANGED], 0); - - return TRUE; -} - -gchar * -_gtk_file_system_get_bookmark_label (GtkFileSystem *file_system, - GFile *file) -{ - GtkFileSystemPrivate *priv = file_system->priv; - GSList *bookmarks; - gchar *label = NULL; - - DEBUG ("get_bookmark_label"); - - bookmarks = priv->bookmarks; - - while (bookmarks) - { - GtkFileSystemBookmark *bookmark; - - bookmark = bookmarks->data; - bookmarks = bookmarks->next; - - if (g_file_equal (file, bookmark->file)) - { - label = g_strdup (bookmark->label); - break; - } - } - - return label; -} - -void -_gtk_file_system_set_bookmark_label (GtkFileSystem *file_system, - GFile *file, - const gchar *label) -{ - GtkFileSystemPrivate *priv = file_system->priv; - gboolean changed = FALSE; - GFile *bookmarks_file; - GSList *bookmarks; - - DEBUG ("set_bookmark_label"); - - bookmarks = priv->bookmarks; - - while (bookmarks) - { - GtkFileSystemBookmark *bookmark; - - bookmark = bookmarks->data; - bookmarks = bookmarks->next; - - if (g_file_equal (file, bookmark->file)) - { - g_free (bookmark->label); - bookmark->label = g_strdup (label); - changed = TRUE; - break; - } - } - - bookmarks_file = get_bookmarks_file (); - save_bookmarks (bookmarks_file, priv->bookmarks); - g_object_unref (bookmarks_file); - - if (changed) - g_signal_emit_by_name (file_system, "bookmarks-changed", 0); -} - GtkFileSystemVolume * _gtk_file_system_get_volume_for_file (GtkFileSystem *file_system, GFile *file) diff --git a/gtk/gtkfilesystem.h b/gtk/gtkfilesystem.h index 03782d6f7f..c24ae29623 100644 --- a/gtk/gtkfilesystem.h +++ b/gtk/gtkfilesystem.h @@ -37,7 +37,6 @@ typedef struct GtkFileSystemClass GtkFileSystemClass; typedef struct GtkFileSystemVolume GtkFileSystemVolume; /* opaque struct */ -typedef struct GtkFileSystemBookmark GtkFileSystemBookmark; /* opaque struct */ struct GtkFileSystem @@ -51,7 +50,6 @@ struct GtkFileSystemClass { GObjectClass parent_class; - void (*bookmarks_changed) (GtkFileSystem *file_system); void (*volumes_changed) (GtkFileSystem *file_system); }; @@ -71,7 +69,6 @@ GType _gtk_file_system_get_type (void) G_GNUC_CONST; GtkFileSystem * _gtk_file_system_new (void); GSList * _gtk_file_system_list_volumes (GtkFileSystem *file_system); -GSList * _gtk_file_system_list_bookmarks (GtkFileSystem *file_system); GCancellable * _gtk_file_system_get_info (GtkFileSystem *file_system, GFile *file, @@ -89,20 +86,6 @@ GCancellable * _gtk_file_system_mount_enclosing_volume (GtkFileSystem GtkFileSystemVolumeMountCallback callback, gpointer data); -gboolean _gtk_file_system_insert_bookmark (GtkFileSystem *file_system, - GFile *file, - gint position, - GError **error); -gboolean _gtk_file_system_remove_bookmark (GtkFileSystem *file_system, - GFile *file, - GError **error); - -gchar * _gtk_file_system_get_bookmark_label (GtkFileSystem *file_system, - GFile *file); -void _gtk_file_system_set_bookmark_label (GtkFileSystem *file_system, - GFile *file, - const gchar *label); - GtkFileSystemVolume * _gtk_file_system_get_volume_for_file (GtkFileSystem *file_system, GFile *file); @@ -118,9 +101,6 @@ GdkPixbuf * _gtk_file_system_volume_render_icon (GtkFileSystemVol GtkFileSystemVolume *_gtk_file_system_volume_ref (GtkFileSystemVolume *volume); void _gtk_file_system_volume_unref (GtkFileSystemVolume *volume); -/* GtkFileSystemBookmark methods */ -void _gtk_file_system_bookmark_free (GtkFileSystemBookmark *bookmark); - /* GFileInfo helper functions */ GdkPixbuf * _gtk_file_info_render_icon (GFileInfo *info, GtkWidget *widget, diff --git a/gtk/gtkgladecatalog.c b/gtk/gtkgladecatalog.c index 310aa2b84c..30e0e6cfef 100644 --- a/gtk/gtkgladecatalog.c +++ b/gtk/gtkgladecatalog.c @@ -30,7 +30,6 @@ /* Some forward declarations of internal types */ GType _gtk_scale_button_scale_get_type (void); -GType _shortcuts_pane_model_filter_get_type (void); /* This function is referred to in gtk/glade/gtk-private-widgets.xml * and is used to ensure the private types for use in Glade while @@ -44,7 +43,6 @@ gtk_glade_catalog_init (const gchar *catalog_name) g_type_ensure (GTK_TYPE_COLOR_PLANE); g_type_ensure (GTK_TYPE_COLOR_SCALE); g_type_ensure (_gtk_scale_button_scale_get_type ()); - g_type_ensure (_shortcuts_pane_model_filter_get_type ()); #ifdef G_OS_UNIX g_type_ensure (GTK_TYPE_PRINTER_OPTION_WIDGET); diff --git a/gtk/gtkmarshalers.list b/gtk/gtkmarshalers.list index 14d0519023..31977fe845 100644 --- a/gtk/gtkmarshalers.list +++ b/gtk/gtkmarshalers.list @@ -83,12 +83,15 @@ VOID:OBJECT,BOOLEAN VOID:OBJECT,BOXED,BOXED VOID:OBJECT,BOXED,UINT,UINT VOID:OBJECT,BOXED,BOOLEAN,BOOLEAN +VOID:OBJECT,ENUM +VOID:OBJECT,FLAGS VOID:OBJECT,INT VOID:OBJECT,INT,OBJECT VOID:OBJECT,INT,INT VOID:OBJECT,INT,INT,BOXED,UINT,UINT VOID:OBJECT,OBJECT VOID:OBJECT,POINTER +VOID:OBJECT,POINTER,INT VOID:OBJECT,STRING VOID:OBJECT,STRING,STRING VOID:OBJECT,UINT @@ -97,6 +100,7 @@ VOID:OBJECT,STRING VOID:OBJECT,OBJECT,STRING VOID:OBJECT,OBJECT,OBJECT VOID:OBJECT,OBJECT,BOXED,STRING +VOID:OBJECT,OBJECT,POINTER,POINTER VOID:POINTER VOID:POINTER,INT VOID:POINTER,BOOLEAN @@ -120,3 +124,7 @@ VOID:UINT,UINT VOID:VOID OBJECT:OBJECT,INT,INT VOID:POINTER,POINTER,POINTER,POINTER,STRING +VOID:OBJECT,STRING,POINTER,POINTER +INT:INT +VOID:POINTER,STRING,INT +INT:OBJECT,OBJECT,POINTER diff --git a/gtk/gtkplacessidebar.c b/gtk/gtkplacessidebar.c new file mode 100644 index 0000000000..cf780503b3 --- /dev/null +++ b/gtk/gtkplacessidebar.c @@ -0,0 +1,4336 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* + * GtkPlacesSidebar - sidebar widget for places in the filesystem + * + * This code comes from Nautilus, GNOME's 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 + * Federico Mena Quintero + * + */ + +/* TODO: + * + * * Fix instances of "#if DO_NOT_COMPILE" + * + * * 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 + +#include "gdk/gdkkeysyms.h" +#include "gtkbookmarksmanager.h" +#include "gtkcelllayout.h" +#include "gtkcellrenderertext.h" +#include "gtkcellrendererpixbuf.h" +#include "gtkfilechooserprivate.h" +#include "gtkicontheme.h" +#include "gtkimagemenuitem.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 "gtkstock.h" +#include "gtktrashmonitor.h" +#include "gtktreeselection.h" +#include "gtktreednd.h" +#include "gtktypebuiltins.h" +#include "gtkwindow.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 user's home directory, the user's 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 6 +#define ICON_CELL_XPAD 6 + +#define DO_NOT_COMPILE 0 + +struct _GtkPlacesSidebar { + GtkScrolledWindow parent; + + GtkTreeView *tree_view; + GtkCellRenderer *eject_icon_cell_renderer; + GtkListStore *store; + GtkBookmarksManager *bookmarks_manager; + GVolumeMonitor *volume_monitor; + GtkTrashMonitor *trash_monitor; + + gulong trash_monitor_changed_id; + + gboolean devices_header_added; + gboolean bookmarks_header_added; + + /* DnD */ + GList *drag_list; /* list of GFile */ + gboolean drag_data_received; + int drag_data_info; + gboolean drop_occured; + + GtkWidget *popup_menu; + + /* volume mounting - delayed open process */ + gboolean mounting; + GtkPlacesOpenFlags go_to_after_mount_open_flags; + + GSList *shortcuts; + + GDBusProxy *hostnamed_proxy; + GCancellable *hostnamed_cancellable; + char *hostname; + + GtkPlacesOpenFlags open_flags; + + guint show_desktop : 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); + void (* show_error_message) (GtkPlacesSidebar *sidebar, + const char *primary, + const char *secondary); + 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); +}; + +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, +} PlaceType; + +typedef enum { + SECTION_DEVICES, + SECTION_BOOKMARKS, + SECTION_COMPUTER, + SECTION_NETWORK, +} SectionType; + +enum { + OPEN_LOCATION, + POPULATE_POPUP, + SHOW_ERROR_MESSAGE, + DRAG_ACTION_REQUESTED, + DRAG_ACTION_ASK, + DRAG_PERFORM_DROP, + LAST_SIGNAL, +}; + +enum { + PROP_LOCATION = 1, + PROP_OPEN_FLAGS, + PROP_SHOW_DESKTOP, + NUM_PROPERTIES, +}; + +/* Names for themed icons */ +#define ICON_NAME_HOME "user-home-symbolic" +#define ICON_NAME_DESKTOP "user-desktop" +#define ICON_NAME_FILESYSTEM "drive-harddisk-symbolic" +#define ICON_NAME_EJECT "media-eject-symbolic" +#define ICON_NAME_NETWORK "network-workgroup-symbolic" + +#define ICON_NAME_FOLDER_DESKTOP "user-desktop" +#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" + +/* Settings keys */ +#define SETTINGS_KEY_STARTUP_MODE "startup-mode" + +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 }, + { "text/uri-list", 0, TEXT_URI_LIST } +}; + +/* 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) +{ + g_signal_emit (sidebar, places_sidebar_signals[POPULATE_POPUP], 0, + menu, selected_item); +} + +static void +emit_show_error_message (GtkPlacesSidebar *sidebar, const char *primary, const char *secondary) +{ + g_signal_emit (sidebar, places_sidebar_signals[SHOW_ERROR_MESSAGE], 0, + primary, secondary); +} + +static GdkDragAction +emit_drag_action_requested (GtkPlacesSidebar *sidebar, + GdkDragContext *context, + GFile *dest_file, + GList *source_file_list) +{ + GdkDragAction ret_action; + + 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; + + 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) +{ + GtkSettings *settings; + gint width, height; + + settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (sidebar))); + + if (gtk_icon_size_lookup_for_settings (settings, 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 char *name, + GIcon *icon, + const char *uri, + GDrive *drive, + GVolume *volume, + GMount *mount, + const int index, + const char *tooltip) +{ + GtkTreeIter iter; + gboolean show_eject, show_unmount; + gboolean show_eject_button; + + 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); + } + + 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, + PLACES_SIDEBAR_COLUMN_SECTION_TYPE, section_type, + -1); +} + +static GIcon * +special_directory_get_gicon (GUserDirectory directory) +{ +#define ICON_CASE(x) \ + case G_USER_DIRECTORY_ ## x: \ + return g_themed_icon_new (ICON_NAME_FOLDER_ ## x); + + switch (directory) { + + 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 ("folder-symbolic"); + } + +#undef ICON_CASE +} + +static gboolean +recent_files_setting_is_enabled (GtkPlacesSidebar *sidebar) +{ + GtkSettings *settings; + gboolean enabled; + + if (gtk_widget_has_screen (GTK_WIDGET (sidebar))) + settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (sidebar))); + else + settings = gtk_settings_get_default (); + + g_object_get (settings, "gtk-recent-files-enabled", &enabled, NULL); + return enabled; +} + +static gboolean +recent_scheme_is_supported (void) +{ + const char * const *supported; + int i; + + supported = g_vfs_get_supported_uri_schemes (g_vfs_get_default ()); + if (!supported) { + return FALSE; + } + + for (i = 0; supported[i] != NULL; i++) { + if (strcmp ("recent", supported[i]) == 0) { + return TRUE; + } + } + return FALSE; +} + +static gboolean +should_show_recent (GtkPlacesSidebar *sidebar) +{ + return recent_files_setting_is_enabled (sidebar) && recent_scheme_is_supported (); +} + +static void +add_special_dirs (GtkPlacesSidebar *sidebar) +{ + GList *dirs; + int index; + + dirs = NULL; + for (index = 0; index < G_USER_N_DIRECTORIES; index++) { + const char *path; + GFile *root; + GIcon *icon; + char *name; + char *mount_uri; + char *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 + || g_strcmp0 (path, g_get_home_dir ()) == 0 + || 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, (char *)path); + } + + g_list_free (dirs); +} + +static char * +get_home_directory_uri (void) +{ + const char *home; + + home = g_get_home_dir (); + if (!home) + return NULL; + + return g_strconcat ("file://", home, NULL); +} + +static char * +get_desktop_directory_uri (void) +{ + const char *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 (!g_strcmp0 (name, g_get_home_dir ())) { + return NULL; + } else { + return g_strconcat ("file://", name, NULL); + } +} + +static void +add_application_shortcuts (GtkPlacesSidebar *sidebar) +{ + GSList *l; + + for (l = sidebar->shortcuts; l; l = l->next) { + GFile *file; + GFileInfo *info; + + file = G_FILE (l->data); + + /* FIXME: we are getting file info synchronously. We may want to do it async at some point. */ + info = g_file_query_info (file, + "standard::display-name,standard::symbolic-icon", + G_FILE_QUERY_INFO_NONE, + NULL, + NULL); /* NULL-GError */ + + if (info) { + char *uri; + char *tooltip; + const char *name; + GIcon *icon; + + 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); + + add_place (sidebar, PLACES_BUILT_IN, + SECTION_COMPUTER, + name, icon, uri, + NULL, NULL, NULL, 0, + tooltip); + + g_free (uri); + g_free (tooltip); + + g_object_unref (info); + } + } +} + +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); +} + +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; + int index; + char *original_uri, *mount_uri, *name, *identifier; + char *home_uri; + char *bookmark_name; + GIcon *icon; + GFile *root; + char *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; + + 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 */ + + add_heading (sidebar, SECTION_COMPUTER, + _("Places")); + + if (should_show_recent (sidebar)) { + mount_uri = "recent:///"; /* No need to strdup */ + icon = g_themed_icon_new ("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 (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); + + if (sidebar->show_desktop) { + /* desktop */ + mount_uri = get_desktop_directory_uri (); + icon = g_themed_icon_new (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); + + /* Trash */ + 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); + g_object_unref (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 (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 (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++) { + GFileInfo *info; + + root = sl->data; + +#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; + } + + /* FIXME: we are getting file info synchronously. We may want to do it async at some point. */ + info = g_file_query_info (root, + "standard::display-name,standard::symbolic-icon", + G_FILE_QUERY_INFO_NONE, + NULL, + NULL); /* NULL-GError */ + + if (info) { + bookmark_name = _gtk_bookmarks_manager_get_bookmark_label (sidebar->bookmarks_manager, root); + + if (bookmark_name == NULL) + bookmark_name = g_strdup (g_file_info_get_display_name (info)); + + icon = g_file_info_get_symbolic_icon (info); + + 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, index, + tooltip); + + g_free (mount_uri); + g_free (tooltip); + g_free (bookmark_name); + + g_object_unref (info); + } + } + + g_slist_foreach (bookmarks, (GFunc) g_object_unref, NULL); + g_slist_free (bookmarks); + + /* network */ + add_heading (sidebar, SECTION_NETWORK, + _("Network")); + + mount_uri = "network:///"; /* No need to strdup */ + icon = g_themed_icon_new (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); + + 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); + } + } + + g_list_free_full (network_volumes, g_object_unref); + + 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 (mount); + g_object_unref (icon); + g_free (name); + g_free (mount_uri); + g_free (tooltip); + } + + 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 void +mount_added_callback (GVolumeMonitor *volume_monitor, + GMount *mount, + GtkPlacesSidebar *sidebar) +{ + update_places (sidebar); +} + +static void +mount_removed_callback (GVolumeMonitor *volume_monitor, + GMount *mount, + GtkPlacesSidebar *sidebar) +{ + update_places (sidebar); +} + +static void +mount_changed_callback (GVolumeMonitor *volume_monitor, + GMount *mount, + GtkPlacesSidebar *sidebar) +{ + update_places (sidebar); +} + +static void +volume_added_callback (GVolumeMonitor *volume_monitor, + GVolume *volume, + GtkPlacesSidebar *sidebar) +{ + update_places (sidebar); +} + +static void +volume_removed_callback (GVolumeMonitor *volume_monitor, + GVolume *volume, + GtkPlacesSidebar *sidebar) +{ + update_places (sidebar); +} + +static void +volume_changed_callback (GVolumeMonitor *volume_monitor, + GVolume *volume, + GtkPlacesSidebar *sidebar) +{ + update_places (sidebar); +} + +static void +drive_disconnected_callback (GVolumeMonitor *volume_monitor, + GDrive *drive, + GtkPlacesSidebar *sidebar) +{ + update_places (sidebar); +} + +static void +drive_connected_callback (GVolumeMonitor *volume_monitor, + GDrive *drive, + GtkPlacesSidebar *sidebar) +{ + update_places (sidebar); +} + +static void +drive_changed_callback (GVolumeMonitor *volume_monitor, + GDrive *drive, + GtkPlacesSidebar *sidebar) +{ + update_places (sidebar); +} + +static gboolean +over_eject_button (GtkPlacesSidebar *sidebar, + gint x, + gint y, + GtkTreePath **path) +{ + GtkTreeViewColumn *column; + int width, x_offset, hseparator; + int 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 gtk+ 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: + if (*path != NULL) { + gtk_tree_path_free (*path); + *path = NULL; + } + + return FALSE; +} + +static gboolean +clicked_eject_button (GtkPlacesSidebar *sidebar, + GtkTreePath **path) +{ + GdkEvent *event = gtk_get_current_event (); + GdkEventButton *button_event = (GdkEventButton *) event; + + if ((event->type == GDK_BUTTON_PRESS || event->type == GDK_BUTTON_RELEASE) && + over_eject_button (sidebar, button_event->x, button_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, + int x, + int 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, §ion_type, + -1); + + drop_possible = TRUE; + + /* 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) { + char *uri; + + gtk_tree_model_get (model, &iter, + PLACES_SIDEBAR_COLUMN_URI, &uri, + -1); + + if (strcmp (uri, "recent:///") == 0) + drop_possible = FALSE; + + g_free (uri); + } + + if (!drop_possible) { + gtk_tree_path_free (*path); + *path = NULL; + + return FALSE; + } + + return TRUE; +} + +static gboolean +get_drag_data (GtkTreeView *tree_view, + GdkDragContext *context, + unsigned int 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 +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; + } +} + +static gboolean +drag_motion_callback (GtkTreeView *tree_view, + GdkDragContext *context, + int x, + int y, + unsigned int time, + GtkPlacesSidebar *sidebar) +{ + GtkTreePath *path; + GtkTreeViewDropPosition pos; + int action; + GtkTreeIter iter; + gboolean res; + + action = 0; + + if (!sidebar->drag_data_received) { + if (!get_drag_data (tree_view, context, time)) { + goto out; + } + } + + path = NULL; + 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; + gboolean drop_as_bookmarks; + + 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, §ion_type, + PLACES_SIDEBAR_COLUMN_ROW_TYPE, &place_type, + -1); + + drop_as_bookmarks = FALSE; + + if (section_type == SECTION_BOOKMARKS) { + if (pos == GTK_TREE_VIEW_DROP_BEFORE || pos == GTK_TREE_VIEW_DROP_AFTER) { + action = GDK_ACTION_COPY; + drop_as_bookmarks = TRUE; + } + } + + if (!drop_as_bookmarks) { + char *uri; + + gtk_tree_model_get (GTK_TREE_MODEL (sidebar->store), + &iter, + PLACES_SIDEBAR_COLUMN_URI, &uri, + -1); + + if (uri != NULL) { + GFile *dest_file = g_file_new_for_uri (uri); + + action = emit_drag_action_requested (sidebar, context, dest_file, sidebar->drag_list); + + g_object_unref (dest_file); + g_free (uri); + } /* uri may be NULL for unmounted volumes, for example, so we don't allow drops there */ + } + } + } + + out: + if (action != 0) + gtk_tree_view_set_drag_dest_row (tree_view, path, pos); + else + gtk_tree_view_set_drag_dest_row (tree_view, NULL, pos); + + 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 void +drag_leave_callback (GtkTreeView *tree_view, + GdkDragContext *context, + unsigned int time, + GtkPlacesSidebar *sidebar) +{ + free_drag_data (sidebar); + gtk_tree_view_set_drag_dest_row (tree_view, NULL, 0); + 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 char **uris) +{ + GList *result; + int 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, + int new_position) +{ + GtkTreeIter iter; + char *uri; + GFile *file; + + /* Get the selected path */ + 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, + int position) +{ + GList *l; + + for (l = files; l; l = l->next) { + GFile *f = G_FILE (l->data); + + _gtk_bookmarks_manager_insert_bookmark (sidebar->bookmarks_manager, f, position++, NULL); /* NULL-GError */ + } +} + +static void +drag_data_received_callback (GtkWidget *widget, + GdkDragContext *context, + int x, + int y, + GtkSelectionData *selection_data, + unsigned int info, + unsigned int time, + GtkPlacesSidebar *sidebar) +{ + GtkTreeView *tree_view; + GtkTreePath *tree_path; + GtkTreeViewDropPosition tree_pos; + GtkTreeIter iter; + int 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) { + char **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, §ion_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; + char **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) { + char *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, §ion_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 char **) 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) { + 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); + gtk_drag_finish (context, success, FALSE, time); + + gtk_tree_path_free (tree_path); +} + +static gboolean +drag_drop_callback (GtkTreeView *tree_view, + GdkDragContext *context, + int x, + int y, + unsigned int 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) +{ + GtkPlacesSidebar *sidebar; + + sidebar = GTK_PLACES_SIDEBAR (attach_widget); + + sidebar->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; + char *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; + char *primary; + char *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); + } +} + +/* Callback from g_volume_mount() */ +static void +volume_mount_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) +{ + GtkPlacesSidebar *sidebar = GTK_PLACES_SIDEBAR (user_data); + GVolume *volume; + GError *error; + char *primary; + char *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); +} + +/* This was nautilus_file_operations_mount_volume_full() */ +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_bookmark (GtkPlacesSidebar *sidebar, + GtkTreeModel *model, + GtkTreeIter *iter, + GtkPlacesOpenFlags open_flags) +{ + GFile *location; + char *uri; + + if (!iter) { + return; + } + + gtk_tree_model_get (model, iter, PLACES_SIDEBAR_COLUMN_URI, &uri, -1); + + if (uri != NULL) { + location = g_file_new_for_uri (uri); + emit_open_location (sidebar, location, open_flags); + + g_object_unref (location); + g_free (uri); + + } else { + 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, sidebar); + g_object_unref (mount_op); + } + + g_clear_object (&drive); + g_clear_object (&volume); + } +} + +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; + char *uri; + char *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); + } +} + +/* Rename the selected bookmark */ +static void +rename_selected_bookmark (GtkPlacesSidebar *sidebar) +{ + GtkTreeIter iter; + GtkTreePath *path; + GtkTreeViewColumn *column; + GtkCellRenderer *cell; + GList *renderers; + 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; + } + + 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); + renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column)); + cell = g_list_nth_data (renderers, 6); + g_list_free (renderers); + g_object_set (cell, "editable", TRUE, NULL); + gtk_tree_view_set_cursor_on_cell (GTK_TREE_VIEW (sidebar->tree_view), + path, column, cell, TRUE); + gtk_tree_path_free (path); + } +} + +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; + char *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) { + char *name; + char *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); + } + + /* FIXME: we need to switch to a path that is available now - $HOME? */ + + g_object_unref (sidebar); +} + +static void +show_unmount_progress_cb (GMountOperation *op, + const gchar *message, + gint64 time_left, + gint64 bytes_left, + gpointer user_data) +{ + /* FIXME: These are just libnotify notifications, but GTK+ doesn't do notifications right now. + * Should we just call D-Bus directly? + */ +#if DO_NOT_COMPILE + NautilusApplication *app = NAUTILUS_APPLICATION (g_application_get_default ()); + + if (bytes_left == 0) { + nautilus_application_notify_unmount_done (app, message); + } else { + nautilus_application_notify_unmount_show (app, message); + } +#endif +} + +static void +show_unmount_progress_aborted_cb (GMountOperation *op, + gpointer user_data) +{ + /* FIXME: These are just libnotify notifications, but GTK+ doesn't do notifications right now. + * Should we just call D-Bus directly? + */ +#if DO_NOT_COMPILE + NautilusApplication *app = NAUTILUS_APPLICATION (g_application_get_default ()); + nautilus_application_notify_unmount_done (app, NULL); +#endif +} + +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; +} + +static void +do_unmount (GMount *mount, + GtkPlacesSidebar *sidebar) +{ + if (mount != NULL) { + GMountOperation *mount_op; + + 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_eject_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GtkPlacesSidebar *sidebar; + GError *error; + char *primary; + char *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; + char *primary; + char *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; + char *primary; + char *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 (mount != NULL) { + g_mount_eject_with_operation (mount, 0, mount_op, NULL, mount_eject_cb, + g_object_ref (sidebar)); + } else if (volume != NULL) { + g_volume_eject_with_operation (volume, 0, mount_op, NULL, volume_eject_cb, + g_object_ref (sidebar)); + } else if (drive != NULL) { + 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; + char *primary; + char *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); + } + + /* FIXME: drive_stop_cb() gets a reffed sidebar, and unrefs it. Do we need to do the same here? */ +} + +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, sidebar); + g_object_unref (drive); + } +} + +static void +drive_start_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GtkPlacesSidebar *sidebar; + GError *error; + char *primary; + char *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); + } + + /* FIXME: drive_stop_cb() gets a reffed sidebar, and unrefs it. Do we need to do the same here? */ +} + +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, sidebar); + + g_object_unref (mount_op); + g_object_unref (drive); + } +} + +static void +drive_stop_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GtkPlacesSidebar *sidebar; + GError *error; + char *primary; + char *name; + + 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 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 +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; + int 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; + + res = get_selected_iter (sidebar, &iter); + + if (!res) { + 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_menu_attach_to_widget (GTK_MENU (sidebar->popup_menu), + GTK_WIDGET (sidebar), + bookmarks_popup_menu_detach_cb); + + item = gtk_image_menu_item_new_with_mnemonic (_("_Open")); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), + gtk_image_new_from_stock (GTK_STOCK_OPEN, GTK_ICON_SIZE_MENU)); + 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_image_menu_item_new_with_label (_("Remove")); + menu_data.remove_item = item; + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), + gtk_image_new_from_stock (GTK_STOCK_REMOVE, GTK_ICON_SIZE_MENU)); + 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); + + g_object_unref (file); + + free_selection_info (&sel_info); +} + +static void +bookmarks_popup_menu (GtkPlacesSidebar *sidebar, + GdkEventButton *event) +{ + int 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), /* menu */ + NULL, /* parent_menu_shell */ + NULL, /* parent_menu_item */ + NULL, /* popup_position_func */ + NULL, /* popup_position_user_data */ + button, /* button */ + event ? event->time : gtk_get_current_event_time ()); /* activate_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 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 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 == 1) { + open_selected_bookmark (sidebar, model, &iter, 0); + } else 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) { + bookmarks_popup_menu (sidebar, event); + } + } + + gtk_tree_path_free (path); + + return ret; +} + +static void +bookmarks_edited (GtkCellRenderer *cell, + gchar *path_string, + gchar *new_text, + GtkPlacesSidebar *sidebar) +{ + GtkTreePath *path; + GtkTreeIter iter; + char *uri; + GFile *file; + + g_object_set (cell, "editable", FALSE, NULL); + + path = gtk_tree_path_new_from_string (path_string); + 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_URI, &uri, + -1); + gtk_tree_path_free (path); + + file = g_file_new_for_uri (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); /* NULL-GError */ + + g_object_unref (file); + g_free (uri); +} + +static void +bookmarks_editing_canceled (GtkCellRenderer *cell, + GtkPlacesSidebar *sidebar) +{ + g_object_set (cell, "editable", FALSE, NULL); +} + +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); + + if (type == PLACES_HEADING) { + g_object_set (cell, + "visible", FALSE, + NULL); + } else { + g_object_set (cell, + "visible", TRUE, + NULL); + } +} + +static void +padding_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); + + if (type == PLACES_HEADING) { + g_object_set (cell, + "visible", FALSE, + "xpad", 0, + "ypad", 0, + NULL); + } else { + g_object_set (cell, + "visible", TRUE, + "xpad", 3, + "ypad", 3, + NULL); + } +} + +static void +heading_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); + + if (type == PLACES_HEADING) { + g_object_set (cell, + "visible", TRUE, + NULL); + } else { + g_object_set (cell, + "visible", FALSE, + 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, §ion_type_a, + PLACES_SIDEBAR_COLUMN_ROW_TYPE, &place_type_a, + -1); + gtk_tree_model_get (model, iter_b, + PLACES_SIDEBAR_COLUMN_SECTION_TYPE, §ion_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); + } + + 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; + + sidebar->hostnamed_proxy = g_dbus_proxy_new_for_bus_finish (res, &error); + 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 (volume_added_callback), sidebar, 0); + g_signal_connect_object (sidebar->volume_monitor, "volume_removed", + G_CALLBACK (volume_removed_callback), sidebar, 0); + g_signal_connect_object (sidebar->volume_monitor, "volume_changed", + G_CALLBACK (volume_changed_callback), sidebar, 0); + g_signal_connect_object (sidebar->volume_monitor, "mount_added", + G_CALLBACK (mount_added_callback), sidebar, 0); + g_signal_connect_object (sidebar->volume_monitor, "mount_removed", + G_CALLBACK (mount_removed_callback), sidebar, 0); + g_signal_connect_object (sidebar->volume_monitor, "mount_changed", + G_CALLBACK (mount_changed_callback), sidebar, 0); + g_signal_connect_object (sidebar->volume_monitor, "drive_disconnected", + G_CALLBACK (drive_disconnected_callback), sidebar, 0); + g_signal_connect_object (sidebar->volume_monitor, "drive_connected", + G_CALLBACK (drive_connected_callback), sidebar, 0); + g_signal_connect_object (sidebar->volume_monitor, "drive_changed", + G_CALLBACK (drive_changed_callback), sidebar, 0); +} + +static void +bookmarks_changed_cb (gpointer data) +{ + GtkPlacesSidebar *sidebar = GTK_PLACES_SIDEBAR (data); + + update_places (sidebar); +} + +static gboolean +tree_view_button_press_callback (GtkWidget *tree_view, + GdkEventButton *event, + gpointer data) +{ + GtkTreePath *path; + GtkTreeViewColumn *column; + + if (event->button == 1 && event->type == GDK_BUTTON_PRESS) { + if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (tree_view), + event->x, event->y, + &path, + &column, + NULL, + NULL)) { + gtk_tree_view_row_activated (GTK_TREE_VIEW (tree_view), path, column); + } + } + + return FALSE; +} + +static void +tree_view_set_activate_on_single_click (GtkTreeView *tree_view) +{ + g_signal_connect (tree_view, "button_press_event", + G_CALLBACK (tree_view_button_press_callback), + NULL); +} + +static void +trash_monitor_trash_state_changed_cb (GtkTrashMonitor *monitor, + GtkPlacesSidebar *sidebar) +{ + update_places (sidebar); +} + + +static void +gtk_places_sidebar_init (GtkPlacesSidebar *sidebar) +{ + GtkTreeView *tree_view; + GtkTreeViewColumn *col; + GtkCellRenderer *cell; + GtkTreeSelection *selection; + GIcon *eject; + + gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (sidebar)), GTK_STYLE_CLASS_SIDEBAR); + + create_volume_monitor (sidebar); + + sidebar->open_flags = GTK_PLACES_OPEN_NORMAL; + + sidebar->bookmarks_manager = _gtk_bookmarks_manager_new (bookmarks_changed_cb, sidebar); + + sidebar->trash_monitor = _gtk_trash_monitor_get (); + sidebar->trash_monitor_changed_id = g_signal_connect (sidebar->trash_monitor, "trash-state-changed", + G_CALLBACK (trash_monitor_trash_state_changed_cb), sidebar); + + sidebar->shortcuts = NULL; + + 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); + + col = GTK_TREE_VIEW_COLUMN (gtk_tree_view_column_new ()); + + /* initial padding */ + cell = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (col, cell, FALSE); + g_object_set (cell, + "xpad", 6, + NULL); + + /* headings */ + cell = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (col, cell, FALSE); + gtk_tree_view_column_set_attributes (col, cell, + "text", PLACES_SIDEBAR_COLUMN_HEADING_TEXT, + NULL); + g_object_set (cell, + "weight", PANGO_WEIGHT_BOLD, + "weight-set", TRUE, + "ypad", 6, + "xpad", 0, + NULL); + gtk_tree_view_column_set_cell_data_func (col, cell, + heading_cell_renderer_func, + sidebar, NULL); + + /* icon padding */ + cell = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (col, cell, FALSE); + gtk_tree_view_column_set_cell_data_func (col, cell, + padding_cell_renderer_func, + sidebar, NULL); + + /* icon renderer */ + cell = gtk_cell_renderer_pixbuf_new (); + g_object_set (cell, "follow-state", TRUE, 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, + "follow-state", TRUE, + "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 (); + 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); + + g_signal_connect (cell, "edited", + G_CALLBACK (bookmarks_edited), sidebar); + g_signal_connect (cell, "editing-canceled", + G_CALLBACK (bookmarks_editing_canceled), sidebar); + + /* 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, + dnd_drop_targets, G_N_ELEMENTS (dnd_drop_targets), + GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK); + + 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); + + tree_view_set_activate_on_single_click (sidebar->tree_view); + + 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); +} + +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_DESKTOP: + gtk_places_sidebar_set_show_desktop (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_DESKTOP: + g_value_set_boolean (value, gtk_places_sidebar_get_show_desktop (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); + + sidebar->tree_view = NULL; + + 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, + volume_added_callback, sidebar); + g_signal_handlers_disconnect_by_func (sidebar->volume_monitor, + volume_removed_callback, sidebar); + g_signal_handlers_disconnect_by_func (sidebar->volume_monitor, + volume_changed_callback, sidebar); + g_signal_handlers_disconnect_by_func (sidebar->volume_monitor, + mount_added_callback, sidebar); + g_signal_handlers_disconnect_by_func (sidebar->volume_monitor, + mount_removed_callback, sidebar); + g_signal_handlers_disconnect_by_func (sidebar->volume_monitor, + mount_changed_callback, sidebar); + g_signal_handlers_disconnect_by_func (sidebar->volume_monitor, + drive_disconnected_callback, sidebar); + g_signal_handlers_disconnect_by_func (sidebar->volume_monitor, + drive_connected_callback, sidebar); + g_signal_handlers_disconnect_by_func (sidebar->volume_monitor, + drive_changed_callback, 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; + + 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: #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.8 + */ + 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: a #GtkMenu. + * @selected_item: #GFile with the item to which the menu should refer. + * + * 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 @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.8 + */ + 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, + G_TYPE_NONE, 2, + 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.8 + */ + 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::drag-action-requested: + * @sidebar: the object which received the signal. + * @context: #GdkDragContext with information about the drag operation + * @dest_file: #GFile with the tentative location that is being hovered for a drop + * @source_file_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. + * + * Return value: 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.8 + */ + 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. + * + * Return value: the final drag action that the sidebar should pass to the drag side + * of the drag-and-drop operation. + * + * Since: 3.8 + */ + 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: Destination #GFile. + * @source_file_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.8 + */ + 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_("The open modes supported for this widget"), + P_("The set of open modes supported for this widget"), + GTK_TYPE_PLACES_OPEN_FLAGS, + GTK_PLACES_OPEN_NORMAL, + G_PARAM_READWRITE); + properties[PROP_SHOW_DESKTOP] = + g_param_spec_boolean ("show-desktop", + P_("Whether to show desktop"), + P_("Whether the sidebar includes a builtin shortcut to the desktop folder"), + 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. + */ +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, §ion_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.8 + */ +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]); + } +} + +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.8 + */ +void +gtk_places_sidebar_set_location (GtkPlacesSidebar *sidebar, GFile *location) +{ + GtkTreeSelection *selection; + GtkTreeIter iter; + gboolean valid; + char *iter_uri; + char *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 (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 sidebar's list of places to + * show. + * + * You can use this function to get the selection in the @sidebar. Also, if you + * connect to the #GtkPlacesSidebar::popup-menu 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.8 + */ +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)) { + char *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_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; this is off by default. + * An application may want to turn this on if the desktop environment actually supports the + * notion of a desktop. + * + * Since: 3.8 + */ +void +gtk_places_sidebar_set_show_desktop (GtkPlacesSidebar *sidebar, gboolean show_desktop) +{ + g_return_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar)); + + 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() + * + * Return value: %TRUE if the sidebar will display a builtin shortcut to the desktop folder. + * + * Since: 3.8 + */ +gboolean +gtk_places_sidebar_get_show_desktop (GtkPlacesSidebar *sidebar) +{ + g_return_val_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar), FALSE); + + return sidebar->show_desktop; +} + +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 sidebar's list in the same order as the function is called. + * + * Since: 3.8 + */ +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.8 + */ +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 + * + * Return value: (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 + * |[ + * g_slist_free_full (list, (GDestroyNotify) g_object_unref); + * ]| + * + * Since: 3.8 + */ +GSList * +gtk_places_sidebar_list_shortcuts (GtkPlacesSidebar *sidebar) +{ + g_return_val_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar), FALSE); + + 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. + * + * Return value: (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.8 + */ +GFile * +gtk_places_sidebar_get_nth_bookmark (GtkPlacesSidebar *sidebar, int 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; + char *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; +} diff --git a/gtk/gtkplacessidebar.h b/gtk/gtkplacessidebar.h new file mode 100644 index 0000000000..40e33a36ab --- /dev/null +++ b/gtk/gtkplacessidebar.h @@ -0,0 +1,100 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* + * GtkPlacesSidebar - sidebar widget for places in the filesystem + * + * This code comes from Nautilus, GNOME's 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) + * Federico Mena Quintero + * + */ +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __GTK_PLACES_SIDEBAR_H__ +#define __GTK_PLACES_SIDEBAR_H__ + +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_PLACES_SIDEBAR (gtk_places_sidebar_get_type ()) +#define GTK_PLACES_SIDEBAR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_PLACES_SIDEBAR, GtkPlacesSidebar)) +#define GTK_PLACES_SIDEBAR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_PLACES_SIDEBAR, GtkPlacesSidebarClass)) +#define GTK_IS_PLACES_SIDEBAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_PLACES_SIDEBAR)) +#define GTK_IS_PLACES_SIDEBAR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_PLACES_SIDEBAR)) +#define GTK_PLACES_SIDEBAR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_PLACES_SIDEBAR, GtkPlacesSidebarClass)) + +typedef struct _GtkPlacesSidebar GtkPlacesSidebar; +typedef struct _GtkPlacesSidebarClass GtkPlacesSidebarClass; + +/** + * GtkPlacesOpenFlags: + * @GTK_PLACES_OPEN_NORMAL: This is the default mode that #GtkPlacesSidebar uses if no other flags + * are specified. It indicates that the calling application should open the selected location + * in the normal way, for example, in the folder view beside the sidebar. + * @GTK_PLACES_OPEN_NEW_TAB: When passed to gtk_places_sidebar_set_open_flags(), this indicates + * that the application can open folders selected from the sidebar in new tabs. This value + * will be passed to the #GtkPlacesSidebar::open-location signal when the user selects + * that a location be opened in a new tab instead of in the standard fashion. + * @GTK_PLACES_OPEN_NEW_WINDOW: Similar to @GTK_PLACES_OPEN_NEW_TAB, but indicates that the application + * can open folders in new windows. + * + * These flags serve two purposes. First, the application can call gtk_places_sidebar_set_open_flags() + * using these flags as a bitmask. This tells the sidebar that the application is able to open + * folders selected from the sidebar in various ways, for example, in new tabs or in new windows in + * addition to the normal mode. + * + * Second, when one of these values gets passed back to the application in the + * #GtkPlacesSidebar::open-location signal, it means that the application should + * open the selected location in the normal way, in a new tab, or in a new + * window. The sidebar takes care of determining the desired way to open the location, + * based on the modifier keys that the user is pressing at the time the selection is made. + * + * If the application never calls gtk_places_sidebar_set_open_flags(), then the sidebar will only + * use #GTK_PLACES_OPEN_NORMAL in the #GtkPlacesSidebar::open-location signal. This is the + * default mode of operation. + */ +typedef enum { + GTK_PLACES_OPEN_NORMAL = 1 << 0, + GTK_PLACES_OPEN_NEW_TAB = 1 << 1, + GTK_PLACES_OPEN_NEW_WINDOW = 1 << 2 +} GtkPlacesOpenFlags; + +GType gtk_places_sidebar_get_type (void); +GtkWidget *gtk_places_sidebar_new (void); + +GtkPlacesOpenFlags gtk_places_sidebar_get_open_flags (GtkPlacesSidebar *sidebar); +void gtk_places_sidebar_set_open_flags (GtkPlacesSidebar *sidebar, GtkPlacesOpenFlags flags); + +GFile *gtk_places_sidebar_get_location (GtkPlacesSidebar *sidebar); +void gtk_places_sidebar_set_location (GtkPlacesSidebar *sidebar, GFile *location); + +gboolean gtk_places_sidebar_get_show_desktop (GtkPlacesSidebar *sidebar); +void gtk_places_sidebar_set_show_desktop (GtkPlacesSidebar *sidebar, gboolean show_desktop); + +void gtk_places_sidebar_add_shortcut (GtkPlacesSidebar *sidebar, GFile *location); +void gtk_places_sidebar_remove_shortcut (GtkPlacesSidebar *sidebar, GFile *location); +GSList *gtk_places_sidebar_list_shortcuts (GtkPlacesSidebar *sidebar); + +GFile *gtk_places_sidebar_get_nth_bookmark (GtkPlacesSidebar *sidebar, int n); + +G_END_DECLS + +#endif /* __GTK_PLACES_SIDEBAR_H__ */ diff --git a/gtk/gtktrashmonitor.c b/gtk/gtktrashmonitor.c new file mode 100644 index 0000000000..c61e9db1b1 --- /dev/null +++ b/gtk/gtktrashmonitor.c @@ -0,0 +1,267 @@ +/* GTK - The GIMP Toolkit + * gtktrashmonitor.h: Monitor the trash:/// folder to see if there is trash or not + * Copyright (C) 2011 Suse + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + * Authors: Federico Mena Quintero + */ + +#include "config.h" + +#include "gtkintl.h" +#include "gtkmarshalers.h" +#include "gtktrashmonitor.h" + +struct _GtkTrashMonitor +{ + GObject parent; + + GFileMonitor *file_monitor; + gulong file_monitor_changed_id; + + guint has_trash : 1; +}; + +struct _GtkTrashMonitorClass +{ + GObjectClass parent_class; + + void (* trash_state_changed) (GtkTrashMonitor *monitor); +}; + +enum { + TRASH_STATE_CHANGED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + +G_DEFINE_TYPE (GtkTrashMonitor, _gtk_trash_monitor, G_TYPE_OBJECT) + +static GtkTrashMonitor *the_trash_monitor; + +#define ICON_NAME_TRASH_EMPTY "user-trash-symbolic" +#define ICON_NAME_TRASH_FULL "user-trash-full-symbolic" + +static void +gtk_trash_monitor_dispose (GObject *object) +{ + GtkTrashMonitor *monitor; + + monitor = GTK_TRASH_MONITOR (object); + + if (monitor->file_monitor) + { + g_signal_handler_disconnect (monitor->file_monitor, monitor->file_monitor_changed_id); + monitor->file_monitor_changed_id = 0; + + g_clear_object (&monitor->file_monitor); + } + + G_OBJECT_CLASS (_gtk_trash_monitor_parent_class)->dispose (object); +} + +static void +_gtk_trash_monitor_class_init (GtkTrashMonitorClass *class) +{ + GObjectClass *gobject_class; + + gobject_class = (GObjectClass *) class; + + gobject_class->dispose = gtk_trash_monitor_dispose; + + signals[TRASH_STATE_CHANGED] = + g_signal_new (I_("trash-state-changed"), + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GtkTrashMonitorClass, trash_state_changed), + NULL, NULL, + _gtk_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +/* Updates the internal has_trash flag and emits the "trash-state-changed" signal */ +static void +update_has_trash_and_notify (GtkTrashMonitor *monitor, + gboolean has_trash) +{ + monitor->has_trash = !!has_trash; + + g_signal_emit (monitor, signals[TRASH_STATE_CHANGED], 0); +} + +static void +trash_enumerate_next_files_cb (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GtkTrashMonitor *monitor = GTK_TRASH_MONITOR (user_data); + GFileEnumerator *enumerator; + GList *infos; + + enumerator = G_FILE_ENUMERATOR (source); + + infos = g_file_enumerator_next_files_finish (enumerator, result, NULL); + if (infos) + { + update_has_trash_and_notify (monitor, TRUE); + g_list_free_full (infos, g_object_unref); + } + else + { + update_has_trash_and_notify (monitor, FALSE); + } + + g_object_unref (monitor); /* was reffed in recompute_trash_state() */ +} + +/* Callback used from g_file_enumerate_children_async() - this is what enumerates "trash:///" */ +static void +trash_enumerate_children_cb (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GtkTrashMonitor *monitor = GTK_TRASH_MONITOR (user_data); + GFileEnumerator *enumerator; + + enumerator = g_file_enumerate_children_finish (G_FILE (source), result, NULL); + if (enumerator) + { + g_file_enumerator_next_files_async (enumerator, + 1, + G_PRIORITY_DEFAULT, + NULL, + trash_enumerate_next_files_cb, + monitor); + g_object_unref (enumerator); + } + else + { + update_has_trash_and_notify (monitor, FALSE); + g_object_unref (monitor); /* was reffed in recompute_trash_state() */ + } +} + +/* Asynchronously recomputes whether there is trash or not */ +static void +recompute_trash_state (GtkTrashMonitor *monitor) +{ + GFile *file; + + g_object_ref (monitor); + + file = g_file_new_for_uri ("trash:///"); + g_file_enumerate_children_async (file, + G_FILE_ATTRIBUTE_STANDARD_TYPE, + G_FILE_QUERY_INFO_NONE, + G_PRIORITY_DEFAULT, + NULL, + trash_enumerate_children_cb, + monitor); + + g_object_unref (file); +} + +/* Callback used when the "trash:///" file monitor changes; we just recompute the trash state + * whenever something happens. + */ +static void +file_monitor_changed_cb (GFileMonitor *file_monitor, + GFile *child, + GFile *other_file, + GFileMonitorEvent event_type, + GtkTrashMonitor *monitor) +{ + recompute_trash_state (monitor); +} + +static void +_gtk_trash_monitor_init (GtkTrashMonitor *monitor) +{ + GFile *file; + + file = g_file_new_for_uri ("trash:///"); + + monitor->file_monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, NULL); + + g_object_unref (file); + + if (monitor->file_monitor) + monitor->file_monitor_changed_id = g_signal_connect (monitor->file_monitor, "changed", + G_CALLBACK (file_monitor_changed_cb), monitor); + + recompute_trash_state (monitor); +} + +/** + * _gtk_trash_monitor_get: + * + * Return value: (transfer full): a new reference to the singleton + * #GtkTrashMonitor object. Be sure to call g_object_unref() on it when you are + * done with the trash monitor. + */ +GtkTrashMonitor * +_gtk_trash_monitor_get (void) +{ + if (the_trash_monitor != NULL) + { + g_object_ref (the_trash_monitor); + } + else + { + the_trash_monitor = g_object_new (GTK_TYPE_TRASH_MONITOR, NULL); + g_object_add_weak_pointer (G_OBJECT (the_trash_monitor), (gpointer *) &the_trash_monitor); + } + + return the_trash_monitor; +} + +/** + * _gtk_trash_monitor_get_icon: + * @monitor: a #GtkTrashMonitor + * + * Return value: (transfer full): the #GIcon that should be used to represent + * the state of the trash folder on screen, based on whether there is trash or + * not. + */ +GIcon * +_gtk_trash_monitor_get_icon (GtkTrashMonitor *monitor) +{ + const char *icon_name; + + g_return_val_if_fail (GTK_IS_TRASH_MONITOR (monitor), NULL); + + if (monitor->has_trash) + icon_name = ICON_NAME_TRASH_FULL; + else + icon_name = ICON_NAME_TRASH_EMPTY; + + return g_themed_icon_new (icon_name); +} + +/** + * _gtk_trash_monitor_get_has_trash: + * @monitor: a #GtkTrashMonitor + * + * Return value: #TRUE if there is trash in the trash:/// folder, or #FALSE otherwise. + */ +gboolean +_gtk_trash_monitor_get_has_trash (GtkTrashMonitor *monitor) +{ + g_return_val_if_fail (GTK_IS_TRASH_MONITOR (monitor), FALSE); + + return monitor->has_trash; +} diff --git a/gtk/gtktrashmonitor.h b/gtk/gtktrashmonitor.h new file mode 100644 index 0000000000..f458b2bab3 --- /dev/null +++ b/gtk/gtktrashmonitor.h @@ -0,0 +1,48 @@ +/* GTK - The GIMP Toolkit + * gtktrashmonitor.h: Monitor the trash:/// folder to see if there is trash or not + * Copyright (C) 2011 Suse + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + * Authors: Federico Mena Quintero + */ + +#ifndef __GTK_TRASH_MONITOR_H__ +#define __GTK_TRASH_MONITOR_H__ + +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_TRASH_MONITOR (_gtk_trash_monitor_get_type ()) +#define GTK_TRASH_MONITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_TRASH_MONITOR, GtkTrashMonitor)) +#define GTK_TRASH_MONITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_TRASH_MONITOR, GtkTrashMonitorClass)) +#define GTK_IS_TRASH_MONITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_TRASH_MONITOR)) +#define GTK_IS_TRASH_MONITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_TRASH_MONITOR)) +#define GTK_TRASH_MONITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_TRASH_MONITOR, GtkTrashMonitorClass)) + +typedef struct _GtkTrashMonitor GtkTrashMonitor; +typedef struct _GtkTrashMonitorClass GtkTrashMonitorClass; + +GType _gtk_trash_monitor_get_type (void); +GtkTrashMonitor *_gtk_trash_monitor_get (void); + +GIcon *_gtk_trash_monitor_get_icon (GtkTrashMonitor *monitor); + +gboolean _gtk_trash_monitor_get_has_trash (GtkTrashMonitor *monitor); + +G_END_DECLS + +#endif /* __GTK_TRASH_MONITOR_H__ */ diff --git a/gtk/org.gtk.Settings.FileChooser.gschema.xml b/gtk/org.gtk.Settings.FileChooser.gschema.xml index d7d87058af..f2424e5655 100644 --- a/gtk/org.gtk.Settings.FileChooser.gschema.xml +++ b/gtk/org.gtk.Settings.FileChooser.gschema.xml @@ -33,6 +33,11 @@ + + + + + "" @@ -63,6 +68,9 @@ (-1, -1) + + 'recent' + 148 diff --git a/po/POTFILES.in b/po/POTFILES.in index 763e5c58f2..d97e1bad19 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -57,6 +57,7 @@ gtk/gtkassistant.c gtk/gtkbbox.c gtk/gtkbin.c gtk/gtkbindings.c +gtk/gtkbookmarksmanager.c gtk/gtkbox.c gtk/gtkbubblewindow.c gtk/gtkbuildable.c