diff options
Diffstat (limited to '')
-rw-r--r-- | src/gtk/nautilusgtkplacesview.c | 2635 |
1 files changed, 2635 insertions, 0 deletions
diff --git a/src/gtk/nautilusgtkplacesview.c b/src/gtk/nautilusgtkplacesview.c new file mode 100644 index 0000000..0d062c9 --- /dev/null +++ b/src/gtk/nautilusgtkplacesview.c @@ -0,0 +1,2635 @@ +/* nautilusgtkplacesview.c + * + * Copyright (C) 2015 Georges Basile Stavracas Neto <georges.stavracas@gmail.com> + * + * 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.1 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "config.h" +#include <glib/gi18n.h> +#include <gtk/gtk.h> +#include "nautilus-enum-types.h" + +#include <gio/gio.h> +#include <gio/gvfs.h> +#include <gtk/gtk.h> + +#include "nautilusgtkplacesviewprivate.h" +#include "nautilusgtkplacesviewrowprivate.h" +#include "nautilus-file.h" +#include "nautilus-file-utilities.h" +#include "nautilus-properties-window.h" + +/*< private > + * NautilusGtkPlacesView: + * + * NautilusGtkPlacesView is a widget that displays a list of persistent drives + * such as harddisk partitions and networks. NautilusGtkPlacesView does not monitor + * removable devices. + * + * The places view displays drives and networks, and will automatically mount + * them when the user activates. Network addresses are stored even if they fail + * to connect. When the connection is successful, the connected network is + * shown at the network list. + * + * To make use of the places view, an application at least needs to connect + * to the NautilusGtkPlacesView::open-location signal. This is emitted when the user + * selects a location to open in the view. + */ + +struct _NautilusGtkPlacesViewClass +{ + GtkBoxClass parent_class; + + void (* open_location) (NautilusGtkPlacesView *view, + GFile *location, + NautilusGtkPlacesOpenFlags open_flags); + + void (* show_error_message) (NautilusGtkPlacesSidebar *sidebar, + const char *primary, + const char *secondary); +}; + +struct _NautilusGtkPlacesView +{ + GtkBox parent_instance; + + GVolumeMonitor *volume_monitor; + NautilusGtkPlacesOpenFlags open_flags; + NautilusGtkPlacesOpenFlags current_open_flags; + + GFile *server_list_file; + GFileMonitor *server_list_monitor; + GFileMonitor *network_monitor; + + GCancellable *cancellable; + + char *search_query; + + GtkWidget *actionbar; + GtkWidget *address_entry; + GtkWidget *connect_button; + GtkWidget *listbox; + GtkWidget *popup_menu; + GtkWidget *recent_servers_listbox; + GtkWidget *recent_servers_popover; + GtkWidget *recent_servers_stack; + GtkWidget *stack; + GtkWidget *server_adresses_popover; + GtkWidget *available_protocols_grid; + GtkWidget *network_placeholder; + GtkWidget *network_placeholder_label; + + GtkSizeGroup *path_size_group; + GtkSizeGroup *space_size_group; + + GtkEntryCompletion *address_entry_completion; + GtkListStore *completion_store; + + GCancellable *networks_fetching_cancellable; + + NautilusGtkPlacesViewRow *row_for_action; + + guint should_open_location : 1; + guint should_pulse_entry : 1; + guint entry_pulse_timeout_id; + guint connecting_to_server : 1; + guint mounting_volume : 1; + guint unmounting_mount : 1; + guint fetching_networks : 1; + guint loading : 1; + guint destroyed : 1; +}; + +static void mount_volume (NautilusGtkPlacesView *view, + GVolume *volume); + +static void on_eject_button_clicked (GtkWidget *widget, + NautilusGtkPlacesViewRow *row); + +static gboolean on_row_popup_menu (GtkWidget *widget, + GVariant *args, + gpointer user_data); + +static void click_cb (GtkGesture *gesture, + int n_press, + double x, + double y, + gpointer user_data); + +static void populate_servers (NautilusGtkPlacesView *view); + +static gboolean nautilus_gtk_places_view_get_fetching_networks (NautilusGtkPlacesView *view); + +static void nautilus_gtk_places_view_set_fetching_networks (NautilusGtkPlacesView *view, + gboolean fetching_networks); + +static void nautilus_gtk_places_view_set_loading (NautilusGtkPlacesView *view, + gboolean loading); + +static void update_loading (NautilusGtkPlacesView *view); + +G_DEFINE_TYPE (NautilusGtkPlacesView, nautilus_gtk_places_view, GTK_TYPE_BOX) + +/* NautilusGtkPlacesView properties & signals */ +enum { + PROP_0, + PROP_OPEN_FLAGS, + PROP_FETCHING_NETWORKS, + PROP_LOADING, + LAST_PROP +}; + +enum { + OPEN_LOCATION, + SHOW_ERROR_MESSAGE, + LAST_SIGNAL +}; + +const char *unsupported_protocols [] = +{ + "file", "afc", "obex", "http", + "trash", "burn", "computer", + "archive", "recent", "localtest", + NULL +}; + +static guint places_view_signals [LAST_SIGNAL] = { 0 }; +static GParamSpec *properties [LAST_PROP]; + +static void +emit_open_location (NautilusGtkPlacesView *view, + GFile *location, + NautilusGtkPlacesOpenFlags open_flags) +{ + if ((open_flags & view->open_flags) == 0) + open_flags = NAUTILUS_GTK_PLACES_OPEN_NORMAL; + + g_signal_emit (view, places_view_signals[OPEN_LOCATION], 0, location, open_flags); +} + +static void +emit_show_error_message (NautilusGtkPlacesView *view, + char *primary_message, + char *secondary_message) +{ + g_signal_emit (view, places_view_signals[SHOW_ERROR_MESSAGE], + 0, primary_message, secondary_message); +} + +static void +server_file_changed_cb (NautilusGtkPlacesView *view) +{ + populate_servers (view); +} + +static GBookmarkFile * +server_list_load (NautilusGtkPlacesView *view) +{ + GBookmarkFile *bookmarks; + GError *error = NULL; + char *datadir; + char *filename; + + bookmarks = g_bookmark_file_new (); + datadir = g_build_filename (g_get_user_config_dir (), "gtk-4.0", NULL); + filename = g_build_filename (datadir, "servers", NULL); + + g_mkdir_with_parents (datadir, 0700); + g_bookmark_file_load_from_file (bookmarks, filename, &error); + + if (error) + { + if (!g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) + { + /* only warn if the file exists */ + g_warning ("Unable to open server bookmarks: %s", error->message); + g_clear_pointer (&bookmarks, g_bookmark_file_free); + } + + g_clear_error (&error); + } + + /* Monitor the file in case it's modified outside this code */ + if (!view->server_list_monitor) + { + view->server_list_file = g_file_new_for_path (filename); + + if (view->server_list_file) + { + view->server_list_monitor = g_file_monitor_file (view->server_list_file, + G_FILE_MONITOR_NONE, + NULL, + &error); + + if (error) + { + g_warning ("Cannot monitor server file: %s", error->message); + g_clear_error (&error); + } + else + { + g_signal_connect_swapped (view->server_list_monitor, + "changed", + G_CALLBACK (server_file_changed_cb), + view); + } + } + + g_clear_object (&view->server_list_file); + } + + g_free (datadir); + g_free (filename); + + return bookmarks; +} + +static void +server_list_save (GBookmarkFile *bookmarks) +{ + char *filename; + + filename = g_build_filename (g_get_user_config_dir (), "gtk-4.0", "servers", NULL); + g_bookmark_file_to_file (bookmarks, filename, NULL); + g_free (filename); +} + +static void +server_list_add_server (NautilusGtkPlacesView *view, + GFile *file) +{ + GBookmarkFile *bookmarks; + GFileInfo *info; + GError *error; + char *title; + char *uri; + GDateTime *now; + + error = NULL; + bookmarks = server_list_load (view); + + if (!bookmarks) + return; + + uri = g_file_get_uri (file); + + info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME, + G_FILE_QUERY_INFO_NONE, + NULL, + &error); + title = g_file_info_get_attribute_as_string (info, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME); + + g_bookmark_file_set_title (bookmarks, uri, title); + now = g_date_time_new_now_utc (); + g_bookmark_file_set_visited_date_time (bookmarks, uri, now); + g_date_time_unref (now); + g_bookmark_file_add_application (bookmarks, uri, NULL, NULL); + + server_list_save (bookmarks); + + g_bookmark_file_free (bookmarks); + g_clear_object (&info); + g_free (title); + g_free (uri); +} + +static void +server_list_remove_server (NautilusGtkPlacesView *view, + const char *uri) +{ + GBookmarkFile *bookmarks; + + bookmarks = server_list_load (view); + + if (!bookmarks) + return; + + g_bookmark_file_remove_item (bookmarks, uri, NULL); + server_list_save (bookmarks); + + g_bookmark_file_free (bookmarks); +} + +/* Returns a toplevel GtkWindow, or NULL if none */ +static GtkWindow * +get_toplevel (GtkWidget *widget) +{ + GtkWidget *toplevel; + + toplevel = GTK_WIDGET (gtk_widget_get_root (widget)); + if (GTK_IS_WINDOW (toplevel)) + return GTK_WINDOW (toplevel); + else + return NULL; +} + +static void +set_busy_cursor (NautilusGtkPlacesView *view, + gboolean busy) +{ + GtkWidget *widget; + GtkWindow *toplevel; + + toplevel = get_toplevel (GTK_WIDGET (view)); + widget = GTK_WIDGET (toplevel); + if (!toplevel || !gtk_widget_get_realized (widget)) + return; + + if (busy) + gtk_widget_set_cursor_from_name (widget, "progress"); + else + gtk_widget_set_cursor (widget, NULL); +} + +/* Activates the given row, with the given flags as parameter */ +static void +activate_row (NautilusGtkPlacesView *view, + NautilusGtkPlacesViewRow *row, + NautilusGtkPlacesOpenFlags flags) +{ + GVolume *volume; + GMount *mount; + GFile *file; + + mount = nautilus_gtk_places_view_row_get_mount (row); + volume = nautilus_gtk_places_view_row_get_volume (row); + file = nautilus_gtk_places_view_row_get_file (row); + + if (file) + { + emit_open_location (view, file, flags); + } + else if (mount) + { + GFile *location = g_mount_get_default_location (mount); + + emit_open_location (view, location, flags); + + g_object_unref (location); + } + else if (volume && g_volume_can_mount (volume)) + { + /* + * When the row is activated, the unmounted volume shall + * be mounted and opened right after. + */ + view->should_open_location = TRUE; + + nautilus_gtk_places_view_row_set_busy (row, TRUE); + mount_volume (view, volume); + } +} + +static void update_places (NautilusGtkPlacesView *view); + +static void +nautilus_gtk_places_view_finalize (GObject *object) +{ + NautilusGtkPlacesView *view = (NautilusGtkPlacesView *)object; + + if (view->entry_pulse_timeout_id > 0) + g_source_remove (view->entry_pulse_timeout_id); + + g_clear_pointer (&view->search_query, g_free); + g_clear_object (&view->server_list_file); + g_clear_object (&view->server_list_monitor); + g_clear_object (&view->volume_monitor); + g_clear_object (&view->network_monitor); + g_clear_object (&view->cancellable); + g_clear_object (&view->networks_fetching_cancellable); + g_clear_object (&view->path_size_group); + g_clear_object (&view->space_size_group); + + G_OBJECT_CLASS (nautilus_gtk_places_view_parent_class)->finalize (object); +} + +static void +nautilus_gtk_places_view_dispose (GObject *object) +{ + NautilusGtkPlacesView *view = (NautilusGtkPlacesView *)object; + + view->destroyed = 1; + + g_signal_handlers_disconnect_by_func (view->volume_monitor, update_places, object); + + if (view->network_monitor) + g_signal_handlers_disconnect_by_func (view->network_monitor, update_places, object); + + if (view->server_list_monitor) + g_signal_handlers_disconnect_by_func (view->server_list_monitor, server_file_changed_cb, object); + + g_cancellable_cancel (view->cancellable); + g_cancellable_cancel (view->networks_fetching_cancellable); + g_clear_pointer (&view->popup_menu, gtk_widget_unparent); + + G_OBJECT_CLASS (nautilus_gtk_places_view_parent_class)->dispose (object); +} + +static void +nautilus_gtk_places_view_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + NautilusGtkPlacesView *self = NAUTILUS_GTK_PLACES_VIEW (object); + + switch (prop_id) + { + case PROP_LOADING: + g_value_set_boolean (value, nautilus_gtk_places_view_get_loading (self)); + break; + + case PROP_OPEN_FLAGS: + g_value_set_flags (value, nautilus_gtk_places_view_get_open_flags (self)); + break; + + case PROP_FETCHING_NETWORKS: + g_value_set_boolean (value, nautilus_gtk_places_view_get_fetching_networks (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +nautilus_gtk_places_view_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + NautilusGtkPlacesView *self = NAUTILUS_GTK_PLACES_VIEW (object); + + switch (prop_id) + { + case PROP_OPEN_FLAGS: + nautilus_gtk_places_view_set_open_flags (self, g_value_get_flags (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static gboolean +is_external_volume (GVolume *volume) +{ + gboolean is_external; + GDrive *drive; + char *id; + + drive = g_volume_get_drive (volume); + id = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_CLASS); + + is_external = g_volume_can_eject (volume); + + /* NULL volume identifier only happens on removable devices */ + is_external |= !id; + + if (drive) + is_external |= g_drive_is_removable (drive); + + g_clear_object (&drive); + g_free (id); + + return is_external; +} + +typedef struct +{ + char *uri; + NautilusGtkPlacesView *view; +} RemoveServerData; + +static void +on_remove_server_button_clicked (RemoveServerData *data) +{ + server_list_remove_server (data->view, data->uri); + + populate_servers (data->view); +} + +static void +populate_servers (NautilusGtkPlacesView *view) +{ + GBookmarkFile *server_list; + GtkWidget *child; + char **uris; + gsize num_uris; + int i; + + server_list = server_list_load (view); + + if (!server_list) + return; + + uris = g_bookmark_file_get_uris (server_list, &num_uris); + + gtk_stack_set_visible_child_name (GTK_STACK (view->recent_servers_stack), + num_uris > 0 ? "list" : "empty"); + + if (!uris) + { + g_bookmark_file_free (server_list); + return; + } + + /* clear previous items */ + while ((child = gtk_widget_get_first_child (GTK_WIDGET (view->recent_servers_listbox)))) + gtk_list_box_remove (GTK_LIST_BOX (view->recent_servers_listbox), child); + + gtk_list_store_clear (view->completion_store); + + for (i = 0; i < num_uris; i++) + { + RemoveServerData *data; + GtkTreeIter iter; + GtkWidget *row; + GtkWidget *grid; + GtkWidget *button; + GtkWidget *label; + char *name; + char *dup_uri; + + name = g_bookmark_file_get_title (server_list, uris[i], NULL); + dup_uri = g_strdup (uris[i]); + + /* add to the completion list */ + gtk_list_store_append (view->completion_store, &iter); + gtk_list_store_set (view->completion_store, + &iter, + 0, name, + 1, uris[i], + -1); + + /* add to the recent servers listbox */ + row = gtk_list_box_row_new (); + + grid = g_object_new (GTK_TYPE_GRID, + "orientation", GTK_ORIENTATION_VERTICAL, + NULL); + + /* name of the connected uri, if any */ + label = gtk_label_new (name); + gtk_widget_set_hexpand (label, TRUE); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END); + gtk_grid_attach (GTK_GRID (grid), label, 0, 0, 1, 1); + + /* the uri itself */ + label = gtk_label_new (uris[i]); + gtk_widget_set_hexpand (label, TRUE); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END); + gtk_widget_add_css_class (label, "dim-label"); + gtk_grid_attach (GTK_GRID (grid), label, 0, 1, 1, 1); + + /* remove button */ + button = gtk_button_new_from_icon_name ("window-close-symbolic"); + gtk_widget_set_halign (button, GTK_ALIGN_END); + gtk_widget_set_valign (button, GTK_ALIGN_CENTER); + gtk_button_set_has_frame (GTK_BUTTON (button), FALSE); + gtk_widget_add_css_class (button, "sidebar-button"); + gtk_grid_attach (GTK_GRID (grid), button, 1, 0, 1, 2); + + gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), grid); + gtk_list_box_insert (GTK_LIST_BOX (view->recent_servers_listbox), row, -1); + + /* custom data */ + data = g_new0 (RemoveServerData, 1); + data->view = view; + data->uri = dup_uri; + + g_object_set_data_full (G_OBJECT (row), "uri", dup_uri, g_free); + g_object_set_data_full (G_OBJECT (row), "remove-server-data", data, g_free); + + g_signal_connect_swapped (button, + "clicked", + G_CALLBACK (on_remove_server_button_clicked), + data); + + g_free (name); + } + + g_strfreev (uris); + g_bookmark_file_free (server_list); +} + +static void +update_view_mode (NautilusGtkPlacesView *view) +{ + GtkWidget *child; + gboolean show_listbox; + + show_listbox = FALSE; + + /* drives */ + for (child = gtk_widget_get_first_child (GTK_WIDGET (view->listbox)); + child != NULL; + child = gtk_widget_get_next_sibling (child)) + { + /* GtkListBox filter rows by changing their GtkWidget::child-visible property */ + if (gtk_widget_get_child_visible (child)) + { + show_listbox = TRUE; + break; + } + } + + if (!show_listbox && + view->search_query && + view->search_query[0] != '\0') + { + gtk_stack_set_visible_child_name (GTK_STACK (view->stack), "empty-search"); + } + else + { + gtk_stack_set_visible_child_name (GTK_STACK (view->stack), "browse"); + } +} + +static void +insert_row (NautilusGtkPlacesView *view, + GtkWidget *row, + gboolean is_network) +{ + GtkEventController *controller; + GtkShortcutTrigger *trigger; + GtkShortcutAction *action; + GtkShortcut *shortcut; + GtkGesture *gesture; + + g_object_set_data (G_OBJECT (row), "is-network", GINT_TO_POINTER (is_network)); + + controller = gtk_shortcut_controller_new (); + trigger = gtk_alternative_trigger_new (gtk_keyval_trigger_new (GDK_KEY_F10, GDK_SHIFT_MASK), + gtk_keyval_trigger_new (GDK_KEY_Menu, 0)); + action = gtk_callback_action_new (on_row_popup_menu, row, NULL); + shortcut = gtk_shortcut_new (trigger, action); + gtk_shortcut_controller_add_shortcut (GTK_SHORTCUT_CONTROLLER (controller), shortcut); + gtk_widget_add_controller (GTK_WIDGET (row), controller); + + gesture = gtk_gesture_click_new (); + gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), GDK_BUTTON_SECONDARY); + g_signal_connect (gesture, "pressed", G_CALLBACK (click_cb), row); + gtk_widget_add_controller (row, GTK_EVENT_CONTROLLER (gesture)); + + g_signal_connect (nautilus_gtk_places_view_row_get_eject_button (NAUTILUS_GTK_PLACES_VIEW_ROW (row)), + "clicked", + G_CALLBACK (on_eject_button_clicked), + row); + + nautilus_gtk_places_view_row_set_path_size_group (NAUTILUS_GTK_PLACES_VIEW_ROW (row), view->path_size_group); + nautilus_gtk_places_view_row_set_space_size_group (NAUTILUS_GTK_PLACES_VIEW_ROW (row), view->space_size_group); + + gtk_list_box_insert (GTK_LIST_BOX (view->listbox), row, -1); +} + +static void +add_volume (NautilusGtkPlacesView *view, + GVolume *volume) +{ + gboolean is_network; + GMount *mount; + GFile *root; + GIcon *icon; + char *identifier; + char *name; + char *path; + + if (is_external_volume (volume)) + return; + + identifier = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_CLASS); + is_network = g_strcmp0 (identifier, "network") == 0; + + mount = g_volume_get_mount (volume); + root = mount ? g_mount_get_default_location (mount) : NULL; + icon = g_volume_get_icon (volume); + name = g_volume_get_name (volume); + path = !is_network ? g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE) : NULL; + + if (!mount || !g_mount_is_shadowed (mount)) + { + GtkWidget *row; + + row = g_object_new (NAUTILUS_TYPE_GTK_PLACES_VIEW_ROW, + "icon", icon, + "name", name, + "path", path ? path : "", + "volume", volume, + "mount", mount, + "file", NULL, + "is-network", is_network, + NULL); + + insert_row (view, row, is_network); + } + + g_clear_object (&root); + g_clear_object (&icon); + g_clear_object (&mount); + g_free (identifier); + g_free (name); + g_free (path); +} + +static void +add_mount (NautilusGtkPlacesView *view, + GMount *mount) +{ + gboolean is_network; + GFile *root; + GIcon *icon; + char *name; + char *path; + char *uri; + char *schema; + + icon = g_mount_get_icon (mount); + name = g_mount_get_name (mount); + root = g_mount_get_default_location (mount); + path = root ? g_file_get_parse_name (root) : NULL; + uri = g_file_get_uri (root); + schema = g_uri_parse_scheme (uri); + is_network = g_strcmp0 (schema, "file") != 0; + + if (is_network) + g_clear_pointer (&path, g_free); + + if (!g_mount_is_shadowed (mount)) + { + GtkWidget *row; + + row = g_object_new (NAUTILUS_TYPE_GTK_PLACES_VIEW_ROW, + "icon", icon, + "name", name, + "path", path ? path : "", + "volume", NULL, + "mount", mount, + "file", NULL, + "is-network", is_network, + NULL); + + insert_row (view, row, is_network); + } + + g_clear_object (&root); + g_clear_object (&icon); + g_free (name); + g_free (path); + g_free (uri); + g_free (schema); +} + +static void +add_drive (NautilusGtkPlacesView *view, + GDrive *drive) +{ + GList *volumes; + GList *l; + + volumes = g_drive_get_volumes (drive); + + for (l = volumes; l != NULL; l = l->next) + add_volume (view, l->data); + + g_list_free_full (volumes, g_object_unref); +} + +static void +add_file (NautilusGtkPlacesView *view, + GFile *file, + GIcon *icon, + const char *display_name, + const char *path, + gboolean is_network) +{ + GtkWidget *row; + row = g_object_new (NAUTILUS_TYPE_GTK_PLACES_VIEW_ROW, + "icon", icon, + "name", display_name, + "path", path, + "volume", NULL, + "mount", NULL, + "file", file, + "is_network", is_network, + NULL); + + insert_row (view, row, is_network); +} + +static gboolean +has_networks (NautilusGtkPlacesView *view) +{ + GtkWidget *child; + gboolean has_network = FALSE; + + for (child = gtk_widget_get_first_child (GTK_WIDGET (view->listbox)); + child != NULL; + child = gtk_widget_get_next_sibling (child)) + { + if (GPOINTER_TO_INT (g_object_get_data (G_OBJECT (child), "is-network")) && + g_object_get_data (G_OBJECT (child), "is-placeholder") == NULL) + { + has_network = TRUE; + break; + } + } + + return has_network; +} + +static void +update_network_state (NautilusGtkPlacesView *view) +{ + if (view->network_placeholder == NULL) + { + view->network_placeholder = gtk_list_box_row_new (); + view->network_placeholder_label = gtk_label_new (""); + gtk_label_set_xalign (GTK_LABEL (view->network_placeholder_label), 0.0); + gtk_widget_set_margin_start (view->network_placeholder_label, 12); + gtk_widget_set_margin_end (view->network_placeholder_label, 12); + gtk_widget_set_margin_top (view->network_placeholder_label, 6); + gtk_widget_set_margin_bottom (view->network_placeholder_label, 6); + gtk_widget_set_hexpand (view->network_placeholder_label, TRUE); + gtk_widget_set_sensitive (view->network_placeholder, FALSE); + gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (view->network_placeholder), + view->network_placeholder_label); + g_object_set_data (G_OBJECT (view->network_placeholder), + "is-network", GINT_TO_POINTER (TRUE)); + /* mark the row as placeholder, so it always goes first */ + g_object_set_data (G_OBJECT (view->network_placeholder), + "is-placeholder", GINT_TO_POINTER (TRUE)); + gtk_list_box_insert (GTK_LIST_BOX (view->listbox), view->network_placeholder, -1); + } + + if (nautilus_gtk_places_view_get_fetching_networks (view)) + { + /* only show a placeholder with a message if the list is empty. + * otherwise just show the spinner in the header */ + if (!has_networks (view)) + { + gtk_widget_show (view->network_placeholder); + gtk_label_set_text (GTK_LABEL (view->network_placeholder_label), + _("Searching for network locations")); + } + } + else if (!has_networks (view)) + { + gtk_widget_show (view->network_placeholder); + gtk_label_set_text (GTK_LABEL (view->network_placeholder_label), + _("No network locations found")); + } + else + { + gtk_widget_hide (view->network_placeholder); + } +} + +static void +monitor_network (NautilusGtkPlacesView *view) +{ + GFile *network_file; + GError *error; + + if (view->network_monitor) + return; + + error = NULL; + network_file = g_file_new_for_uri ("network:///"); + view->network_monitor = g_file_monitor (network_file, + G_FILE_MONITOR_NONE, + NULL, + &error); + + g_clear_object (&network_file); + + if (error) + { + g_warning ("Error monitoring network: %s", error->message); + g_clear_error (&error); + return; + } + + g_signal_connect_swapped (view->network_monitor, + "changed", + G_CALLBACK (update_places), + view); +} + +static void +populate_networks (NautilusGtkPlacesView *view, + GFileEnumerator *enumerator, + GList *detected_networks) +{ + GList *l; + GFile *file; + GFile *activatable_file; + char *uri; + GFileType type; + GIcon *icon; + char *display_name; + + for (l = detected_networks; l != NULL; l = l->next) + { + file = g_file_enumerator_get_child (enumerator, l->data); + type = g_file_info_get_file_type (l->data); + if (type == G_FILE_TYPE_SHORTCUT || type == G_FILE_TYPE_MOUNTABLE) + uri = g_file_info_get_attribute_as_string (l->data, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI); + else + uri = g_file_get_uri (file); + activatable_file = g_file_new_for_uri (uri); + display_name = g_file_info_get_attribute_as_string (l->data, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME); + icon = g_file_info_get_icon (l->data); + + add_file (view, activatable_file, icon, display_name, NULL, TRUE); + + g_free (uri); + g_free (display_name); + g_clear_object (&file); + g_clear_object (&activatable_file); + } +} + +static void +network_enumeration_next_files_finished (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + NautilusGtkPlacesView *view; + GList *detected_networks; + GError *error; + + view = NAUTILUS_GTK_PLACES_VIEW (user_data); + error = NULL; + + detected_networks = g_file_enumerator_next_files_finish (G_FILE_ENUMERATOR (source_object), + res, &error); + + if (error) + { + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + g_clear_error (&error); + g_object_unref (view); + return; + } + + g_warning ("Failed to fetch network locations: %s", error->message); + g_clear_error (&error); + } + else + { + nautilus_gtk_places_view_set_fetching_networks (view, FALSE); + populate_networks (view, G_FILE_ENUMERATOR (source_object), detected_networks); + + g_list_free_full (detected_networks, g_object_unref); + } + + update_network_state (view); + monitor_network (view); + update_loading (view); + + g_object_unref (view); +} + +static void +network_enumeration_finished (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (user_data); + GFileEnumerator *enumerator; + GError *error; + + error = NULL; + enumerator = g_file_enumerate_children_finish (G_FILE (source_object), res, &error); + + if (error) + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) && + !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) + g_warning ("Failed to fetch network locations: %s", error->message); + + g_clear_error (&error); + g_object_unref (view); + } + else + { + g_file_enumerator_next_files_async (enumerator, + G_MAXINT32, + G_PRIORITY_DEFAULT, + view->networks_fetching_cancellable, + network_enumeration_next_files_finished, + user_data); + g_object_unref (enumerator); + } +} + +static void +fetch_networks (NautilusGtkPlacesView *view) +{ + GFile *network_file; + const char * const *supported_uris; + gboolean found; + + supported_uris = g_vfs_get_supported_uri_schemes (g_vfs_get_default ()); + + for (found = FALSE; !found && supported_uris && supported_uris[0]; supported_uris++) + if (g_strcmp0 (supported_uris[0], "network") == 0) + found = TRUE; + + if (!found) + return; + + network_file = g_file_new_for_uri ("network:///"); + + g_cancellable_cancel (view->networks_fetching_cancellable); + g_clear_object (&view->networks_fetching_cancellable); + view->networks_fetching_cancellable = g_cancellable_new (); + nautilus_gtk_places_view_set_fetching_networks (view, TRUE); + update_network_state (view); + + g_object_ref (view); + g_file_enumerate_children_async (network_file, + "standard::type,standard::target-uri,standard::name,standard::display-name,standard::icon", + G_FILE_QUERY_INFO_NONE, + G_PRIORITY_DEFAULT, + view->networks_fetching_cancellable, + network_enumeration_finished, + view); + + g_clear_object (&network_file); +} + +static void +update_places (NautilusGtkPlacesView *view) +{ + GList *mounts; + GList *volumes; + GList *drives; + GList *l; + GIcon *icon; + GFile *file; + GtkWidget *child; + gchar *osname; + + /* Clear all previously added items */ + while ((child = gtk_widget_get_first_child (GTK_WIDGET (view->listbox)))) + gtk_list_box_remove (GTK_LIST_BOX (view->listbox), child); + + view->network_placeholder = NULL; + /* Inform clients that we started loading */ + nautilus_gtk_places_view_set_loading (view, TRUE); + + /* Add "Computer" row */ + file = g_file_new_for_path ("/"); + icon = g_themed_icon_new_with_default_fallbacks ("drive-harddisk"); + osname = g_get_os_info (G_OS_INFO_KEY_NAME); + + add_file (view, file, icon, ((osname != NULL) ? osname : _("Operating System")), "/", FALSE); + + g_clear_object (&file); + g_clear_object (&icon); + g_free (osname); + + /* Add currently connected drives */ + drives = g_volume_monitor_get_connected_drives (view->volume_monitor); + + for (l = drives; l != NULL; l = l->next) + add_drive (view, l->data); + + g_list_free_full (drives, g_object_unref); + + /* + * Since all volumes with an associated GDrive were already added with + * add_drive before, add all volumes that aren't associated with a + * drive. + */ + volumes = g_volume_monitor_get_volumes (view->volume_monitor); + + for (l = volumes; l != NULL; l = l->next) + { + GVolume *volume; + GDrive *drive; + + volume = l->data; + drive = g_volume_get_drive (volume); + + if (drive) + { + g_object_unref (drive); + continue; + } + + add_volume (view, volume); + } + + g_list_free_full (volumes, g_object_unref); + + /* + * Now that all necessary drives and volumes were already added, add mounts + * that have no volume, such as /etc/mtab mounts, ftp, sftp, etc. + */ + mounts = g_volume_monitor_get_mounts (view->volume_monitor); + + for (l = mounts; l != NULL; l = l->next) + { + GMount *mount; + GVolume *volume; + + mount = l->data; + volume = g_mount_get_volume (mount); + + if (volume) + { + g_object_unref (volume); + continue; + } + + add_mount (view, mount); + } + + g_list_free_full (mounts, g_object_unref); + + /* load saved servers */ + populate_servers (view); + + /* fetch networks and add them asynchronously */ + fetch_networks (view); + + update_view_mode (view); + /* Check whether we still are in a loading state */ + update_loading (view); +} + +static void +server_mount_ready_cb (GObject *source_file, + GAsyncResult *res, + gpointer user_data) +{ + NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (user_data); + gboolean should_show; + GError *error; + GFile *location; + + location = G_FILE (source_file); + should_show = TRUE; + error = NULL; + + g_file_mount_enclosing_volume_finish (location, res, &error); + if (error) + { + should_show = FALSE; + + if (error->code == G_IO_ERROR_ALREADY_MOUNTED) + { + /* + * Already mounted volume is not a critical error + * and we can still continue with the operation. + */ + should_show = TRUE; + } + else if (error->domain != G_IO_ERROR || + (error->code != G_IO_ERROR_CANCELLED && + error->code != G_IO_ERROR_FAILED_HANDLED)) + { + /* if it wasn't cancelled show a dialog */ + emit_show_error_message (view, _("Unable to access location"), error->message); + } + + /* The operation got cancelled by the user and or the error + has been handled already. */ + g_clear_error (&error); + } + + if (view->destroyed) + { + g_object_unref (view); + return; + } + + view->should_pulse_entry = FALSE; + gtk_entry_set_progress_fraction (GTK_ENTRY (view->address_entry), 0); + + /* Restore from Cancel to Connect */ + gtk_button_set_label (GTK_BUTTON (view->connect_button), _("Con_nect")); + gtk_widget_set_sensitive (view->address_entry, TRUE); + view->connecting_to_server = FALSE; + + if (should_show) + { + server_list_add_server (view, location); + + /* + * Only clear the entry if it successfully connects to the server. + * Otherwise, the user would lost the typed address even if it fails + * to connect. + */ + gtk_editable_set_text (GTK_EDITABLE (view->address_entry), ""); + + if (view->should_open_location) + { + GMount *mount; + GFile *root; + + /* + * If the mount is not found at this point, it is probably user- + * invisible, which happens e.g for smb-browse, but the location + * should be opened anyway... + */ + mount = g_file_find_enclosing_mount (location, view->cancellable, NULL); + if (mount) + { + root = g_mount_get_default_location (mount); + + emit_open_location (view, root, view->open_flags); + + g_object_unref (root); + g_object_unref (mount); + } + else + { + emit_open_location (view, location, view->open_flags); + } + } + } + + update_places (view); + g_object_unref (view); +} + +static void +volume_mount_ready_cb (GObject *source_volume, + GAsyncResult *res, + gpointer user_data) +{ + NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (user_data); + gboolean should_show; + GVolume *volume; + GError *error; + + volume = G_VOLUME (source_volume); + should_show = TRUE; + error = NULL; + + g_volume_mount_finish (volume, res, &error); + + if (error) + { + should_show = FALSE; + + if (error->code == G_IO_ERROR_ALREADY_MOUNTED) + { + /* + * If the volume was already mounted, it's not a hard error + * and we can still continue with the operation. + */ + should_show = TRUE; + } + else if (error->domain != G_IO_ERROR || + (error->code != G_IO_ERROR_CANCELLED && + error->code != G_IO_ERROR_FAILED_HANDLED)) + { + /* if it wasn't cancelled show a dialog */ + emit_show_error_message (NAUTILUS_GTK_PLACES_VIEW (user_data), _("Unable to access location"), error->message); + should_show = FALSE; + } + + /* The operation got cancelled by the user and or the error + has been handled already. */ + g_clear_error (&error); + } + + if (view->destroyed) + { + g_object_unref(view); + return; + } + + view->mounting_volume = FALSE; + update_loading (view); + + if (should_show) + { + GMount *mount; + GFile *root; + + mount = g_volume_get_mount (volume); + root = g_mount_get_default_location (mount); + + if (view->should_open_location) + emit_open_location (NAUTILUS_GTK_PLACES_VIEW (user_data), root, view->open_flags); + + g_object_unref (mount); + g_object_unref (root); + } + + update_places (view); + g_object_unref (view); +} + +static void +unmount_ready_cb (GObject *source_mount, + GAsyncResult *res, + gpointer user_data) +{ + NautilusGtkPlacesView *view; + GMount *mount; + GError *error; + + view = NAUTILUS_GTK_PLACES_VIEW (user_data); + mount = G_MOUNT (source_mount); + error = NULL; + + g_mount_unmount_with_operation_finish (mount, res, &error); + + if (error) + { + if (error->domain != G_IO_ERROR || + (error->code != G_IO_ERROR_CANCELLED && + error->code != G_IO_ERROR_FAILED_HANDLED)) + { + /* if it wasn't cancelled show a dialog */ + emit_show_error_message (view, _("Unable to unmount volume"), error->message); + } + + g_clear_error (&error); + } + + if (view->destroyed) { + g_object_unref (view); + return; + } + + view->row_for_action = NULL; + view->unmounting_mount = FALSE; + update_loading (view); + + g_object_unref (view); +} + +static gboolean +pulse_entry_cb (gpointer user_data) +{ + NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (user_data); + + if (view->destroyed) + { + view->entry_pulse_timeout_id = 0; + + return G_SOURCE_REMOVE; + } + else if (view->should_pulse_entry) + { + gtk_entry_progress_pulse (GTK_ENTRY (view->address_entry)); + + return G_SOURCE_CONTINUE; + } + else + { + gtk_entry_set_progress_fraction (GTK_ENTRY (view->address_entry), 0); + view->entry_pulse_timeout_id = 0; + + return G_SOURCE_REMOVE; + } +} + +static void +unmount_mount (NautilusGtkPlacesView *view, + GMount *mount) +{ + GMountOperation *operation; + GtkWidget *toplevel; + + toplevel = GTK_WIDGET (gtk_widget_get_root (GTK_WIDGET (view))); + + g_cancellable_cancel (view->cancellable); + g_clear_object (&view->cancellable); + view->cancellable = g_cancellable_new (); + + view->unmounting_mount = TRUE; + update_loading (view); + + g_object_ref (view); + + operation = gtk_mount_operation_new (GTK_WINDOW (toplevel)); + g_mount_unmount_with_operation (mount, + 0, + operation, + view->cancellable, + unmount_ready_cb, + view); + g_object_unref (operation); +} + +static void +mount_server (NautilusGtkPlacesView *view, + GFile *location) +{ + GMountOperation *operation; + GtkWidget *toplevel; + + g_cancellable_cancel (view->cancellable); + g_clear_object (&view->cancellable); + /* User cliked when the operation was ongoing, so wanted to cancel it */ + if (view->connecting_to_server) + return; + + view->cancellable = g_cancellable_new (); + toplevel = GTK_WIDGET (gtk_widget_get_root (GTK_WIDGET (view))); + operation = gtk_mount_operation_new (GTK_WINDOW (toplevel)); + + view->should_pulse_entry = TRUE; + gtk_entry_set_progress_pulse_step (GTK_ENTRY (view->address_entry), 0.1); + gtk_entry_set_progress_fraction (GTK_ENTRY (view->address_entry), 0.1); + /* Allow to cancel the operation */ + gtk_button_set_label (GTK_BUTTON (view->connect_button), _("Cance_l")); + gtk_widget_set_sensitive (view->address_entry, FALSE); + view->connecting_to_server = TRUE; + update_loading (view); + + if (view->entry_pulse_timeout_id == 0) + view->entry_pulse_timeout_id = g_timeout_add (100, (GSourceFunc) pulse_entry_cb, view); + + g_mount_operation_set_password_save (operation, G_PASSWORD_SAVE_FOR_SESSION); + + /* make sure we keep the view around for as long as we are running */ + g_object_ref (view); + + g_file_mount_enclosing_volume (location, + 0, + operation, + view->cancellable, + server_mount_ready_cb, + view); + + /* unref operation here - g_file_mount_enclosing_volume() does ref for itself */ + g_object_unref (operation); +} + +static void +mount_volume (NautilusGtkPlacesView *view, + GVolume *volume) +{ + GMountOperation *operation; + GtkWidget *toplevel; + + toplevel = GTK_WIDGET (gtk_widget_get_root (GTK_WIDGET (view))); + operation = gtk_mount_operation_new (GTK_WINDOW (toplevel)); + + g_cancellable_cancel (view->cancellable); + g_clear_object (&view->cancellable); + view->cancellable = g_cancellable_new (); + + view->mounting_volume = TRUE; + update_loading (view); + + g_mount_operation_set_password_save (operation, G_PASSWORD_SAVE_FOR_SESSION); + + /* make sure we keep the view around for as long as we are running */ + g_object_ref (view); + + g_volume_mount (volume, + 0, + operation, + view->cancellable, + volume_mount_ready_cb, + view); + + /* unref operation here - g_file_mount_enclosing_volume() does ref for itself */ + g_object_unref (operation); +} + +static void +open_cb (GtkWidget *widget, + const char *action_name, + GVariant *parameter) +{ + NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (widget); + NautilusGtkPlacesOpenFlags flags = NAUTILUS_GTK_PLACES_OPEN_NORMAL; + + if (view->row_for_action == NULL) + return; + + if (strcmp (action_name, "location.open") == 0) + flags = NAUTILUS_GTK_PLACES_OPEN_NORMAL; + else if (strcmp (action_name, "location.open-tab") == 0) + flags = NAUTILUS_GTK_PLACES_OPEN_NEW_TAB; + else if (strcmp (action_name, "location.open-window") == 0) + flags = NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW; + + activate_row (view, view->row_for_action, flags); +} + +static void +properties_cb (GtkWidget *widget, + const char *action_name, + GVariant *parameter) +{ + NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (widget); + GList *list; + GMount *mount; + g_autoptr (GFile) location = NULL; + NautilusFile *file; + + if (view->row_for_action == NULL) + return; + + file = NULL; + mount = nautilus_gtk_places_view_row_get_mount (view->row_for_action); + + if (mount) + { + location = g_mount_get_root (mount); + file = nautilus_file_get (location); + } + else + file = nautilus_file_get (nautilus_gtk_places_view_row_get_file (view->row_for_action)); + + if (file) + { + list = g_list_append (NULL, file); + nautilus_properties_window_present (list, widget, NULL, NULL, NULL); + + nautilus_file_list_unref (list); + } +} + +static void +mount_cb (GtkWidget *widget, + const char *action_name, + GVariant *parameter) +{ + NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (widget); + GVolume *volume; + + if (view->row_for_action == NULL) + return; + + volume = nautilus_gtk_places_view_row_get_volume (view->row_for_action); + + /* + * When the mount item is activated, it's expected that + * the volume only gets mounted, without opening it after + * the operation is complete. + */ + view->should_open_location = FALSE; + + nautilus_gtk_places_view_row_set_busy (view->row_for_action, TRUE); + mount_volume (view, volume); +} + +static void +unmount_cb (GtkWidget *widget, + const char *action_name, + GVariant *parameter) +{ + NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (widget); + GMount *mount; + + if (view->row_for_action == NULL) + return; + + mount = nautilus_gtk_places_view_row_get_mount (view->row_for_action); + + nautilus_gtk_places_view_row_set_busy (view->row_for_action, TRUE); + + unmount_mount (view, mount); +} + +static void +attach_protocol_row_to_grid (GtkGrid *grid, + const char *protocol_name, + const char *protocol_prefix) +{ + GtkWidget *name_label; + GtkWidget *prefix_label; + + name_label = gtk_label_new (protocol_name); + gtk_widget_set_halign (name_label, GTK_ALIGN_START); + gtk_grid_attach_next_to (grid, name_label, NULL, GTK_POS_BOTTOM, 1, 1); + + prefix_label = gtk_label_new (protocol_prefix); + gtk_widget_set_halign (prefix_label, GTK_ALIGN_START); + gtk_grid_attach_next_to (grid, prefix_label, name_label, GTK_POS_RIGHT, 1, 1); +} + +static void +populate_available_protocols_grid (GtkGrid *grid) +{ + const char * const *supported_protocols; + gboolean has_any = FALSE; + + supported_protocols = g_vfs_get_supported_uri_schemes (g_vfs_get_default ()); + + if (g_strv_contains (supported_protocols, "afp")) + { + attach_protocol_row_to_grid (grid, _("AppleTalk"), "afp://"); + has_any = TRUE; + } + + if (g_strv_contains (supported_protocols, "ftp")) + { + attach_protocol_row_to_grid (grid, _("File Transfer Protocol"), + /* Translators: do not translate ftp:// and ftps:// */ + _("ftp:// or ftps://")); + has_any = TRUE; + } + + if (g_strv_contains (supported_protocols, "nfs")) + { + attach_protocol_row_to_grid (grid, _("Network File System"), "nfs://"); + has_any = TRUE; + } + + if (g_strv_contains (supported_protocols, "smb")) + { + attach_protocol_row_to_grid (grid, _("Samba"), "smb://"); + has_any = TRUE; + } + + if (g_strv_contains (supported_protocols, "ssh")) + { + attach_protocol_row_to_grid (grid, _("SSH File Transfer Protocol"), + /* Translators: do not translate sftp:// and ssh:// */ + _("sftp:// or ssh://")); + has_any = TRUE; + } + + if (g_strv_contains (supported_protocols, "dav")) + { + attach_protocol_row_to_grid (grid, _("WebDAV"), + /* Translators: do not translate dav:// and davs:// */ + _("dav:// or davs://")); + has_any = TRUE; + } + + if (!has_any) + gtk_widget_hide (GTK_WIDGET (grid)); +} + +static GMenuModel * +get_menu_model (void) +{ + GMenu *menu; + GMenu *section; + GMenuItem *item; + + menu = g_menu_new (); + section = g_menu_new (); + item = g_menu_item_new (_("_Open"), "location.open"); + g_menu_append_item (section, item); + g_object_unref (item); + + item = g_menu_item_new (_("Open in New _Tab"), "location.open-tab"); + g_menu_item_set_attribute (item, "hidden-when", "s", "action-disabled"); + g_menu_append_item (section, item); + g_object_unref (item); + + item = g_menu_item_new (_("Open in New _Window"), "location.open-window"); + g_menu_item_set_attribute (item, "hidden-when", "s", "action-disabled"); + g_menu_append_item (section, item); + g_object_unref (item); + + g_menu_append_section (menu, NULL, G_MENU_MODEL (section)); + g_object_unref (section); + + section = g_menu_new (); + item = g_menu_item_new (_("_Disconnect"), "location.disconnect"); + g_menu_item_set_attribute (item, "hidden-when", "s", "action-disabled"); + g_menu_append_item (section, item); + g_object_unref (item); + + item = g_menu_item_new (_("_Unmount"), "location.unmount"); + g_menu_item_set_attribute (item, "hidden-when", "s", "action-disabled"); + g_menu_append_item (section, item); + g_object_unref (item); + + + item = g_menu_item_new (_("_Connect"), "location.connect"); + g_menu_item_set_attribute (item, "hidden-when", "s", "action-disabled"); + g_menu_append_item (section, item); + g_object_unref (item); + + item = g_menu_item_new (_("_Mount"), "location.mount"); + g_menu_item_set_attribute (item, "hidden-when", "s", "action-disabled"); + g_menu_append_item (section, item); + g_object_unref (item); + + item = g_menu_item_new (_("_Properties"), "location.properties"); + g_menu_item_set_attribute (item, "hidden-when", "s", "action-disabled"); + g_menu_append_item (section, item); + g_object_unref (item); + + g_menu_append_section (menu, NULL, G_MENU_MODEL (section)); + g_object_unref (section); + + return G_MENU_MODEL (menu); +} + +static void +_popover_set_pointing_to_widget (GtkPopover *popover, + GtkWidget *target) +{ + GtkWidget *parent; + double x, y, w, h; + + parent = gtk_widget_get_parent (GTK_WIDGET (popover)); + + if (!gtk_widget_translate_coordinates (target, parent, 0, 0, &x, &y)) + return; + + w = gtk_widget_get_allocated_width (GTK_WIDGET (target)); + h = gtk_widget_get_allocated_height (GTK_WIDGET (target)); + + gtk_popover_set_pointing_to (popover, &(GdkRectangle){x, y, w, h}); +} + +static gboolean +real_popup_menu (GtkWidget *widget, + double x, + double y) +{ + NautilusGtkPlacesViewRow *row = NAUTILUS_GTK_PLACES_VIEW_ROW (widget); + NautilusGtkPlacesView *view; + GMount *mount; + GFile *file; + gboolean is_root, is_network; + double x_in_view, y_in_view; + + view = NAUTILUS_GTK_PLACES_VIEW (gtk_widget_get_ancestor (GTK_WIDGET (row), NAUTILUS_TYPE_GTK_PLACES_VIEW)); + + mount = nautilus_gtk_places_view_row_get_mount (row); + file = nautilus_gtk_places_view_row_get_file (row); + is_root = file && nautilus_is_root_directory (file); + is_network = nautilus_gtk_places_view_row_get_is_network (row); + + gtk_widget_action_set_enabled (GTK_WIDGET (view), "location.disconnect", + !file && mount && is_network); + gtk_widget_action_set_enabled (GTK_WIDGET (view), "location.unmount", + !file && mount && !is_network); + gtk_widget_action_set_enabled (GTK_WIDGET (view), "location.connect", + !file && !mount && is_network); + gtk_widget_action_set_enabled (GTK_WIDGET (view), "location.mount", + !file && !mount && !is_network); + gtk_widget_action_set_enabled (GTK_WIDGET (view), "location.properties", + (is_root || + (mount && !(file && is_network)))); + + if (!view->popup_menu) + { + GMenuModel *model = get_menu_model (); + + view->popup_menu = gtk_popover_menu_new_from_model (model); + + gtk_popover_set_has_arrow (GTK_POPOVER (view->popup_menu), FALSE); + gtk_widget_set_parent (view->popup_menu, GTK_WIDGET (view)); + gtk_widget_set_halign (view->popup_menu, GTK_ALIGN_START); + + g_object_unref (model); + } + + if (view->row_for_action) + g_object_set_data (G_OBJECT (view->row_for_action), "menu", NULL); + + if (x == -1 && y == -1) + _popover_set_pointing_to_widget (GTK_POPOVER (view->popup_menu), widget); + else + { + gtk_widget_translate_coordinates (widget, GTK_WIDGET (view), + x, y, &x_in_view, &y_in_view); + gtk_popover_set_pointing_to (GTK_POPOVER (view->popup_menu), + &(GdkRectangle){x_in_view, y_in_view, 0, 0}); + } + + view->row_for_action = row; + if (view->row_for_action) + g_object_set_data (G_OBJECT (view->row_for_action), "menu", view->popup_menu); + + gtk_popover_popup (GTK_POPOVER (view->popup_menu)); + + return TRUE; +} + +static gboolean +on_row_popup_menu (GtkWidget *widget, + GVariant *args, + gpointer user_data) +{ + return real_popup_menu (widget, -1, -1); +} + +static void +click_cb (GtkGesture *gesture, + int n_press, + double x, + double y, + gpointer user_data) +{ + real_popup_menu (GTK_WIDGET (user_data), x, y); +} + +static gboolean +on_key_press_event (GtkEventController *controller, + guint keyval, + guint keycode, + GdkModifierType state, + NautilusGtkPlacesView *view) +{ + GdkModifierType modifiers; + + modifiers = gtk_accelerator_get_default_mod_mask (); + + if (keyval == GDK_KEY_Return || + keyval == GDK_KEY_KP_Enter || + keyval == GDK_KEY_ISO_Enter || + keyval == GDK_KEY_space) + { + GtkWidget *focus_widget; + GtkWindow *toplevel; + + view->current_open_flags = NAUTILUS_GTK_PLACES_OPEN_NORMAL; + toplevel = get_toplevel (GTK_WIDGET (view)); + + if (!toplevel) + return FALSE; + + focus_widget = gtk_root_get_focus (GTK_ROOT (toplevel)); + + if (!NAUTILUS_IS_GTK_PLACES_VIEW_ROW (focus_widget)) + return FALSE; + + if ((state & modifiers) == GDK_SHIFT_MASK) + view->current_open_flags = NAUTILUS_GTK_PLACES_OPEN_NEW_TAB; + else if ((state & modifiers) == GDK_CONTROL_MASK) + view->current_open_flags = NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW; + + activate_row (view, NAUTILUS_GTK_PLACES_VIEW_ROW (focus_widget), view->current_open_flags); + + return TRUE; + } + + return FALSE; +} + +static void +on_middle_click_row_event (GtkGestureClick *gesture, + guint n_press, + double x, + double y, + NautilusGtkPlacesView *view) +{ + GtkListBoxRow *row; + + if (n_press != 1) + return; + + row = gtk_list_box_get_row_at_y (GTK_LIST_BOX (view->listbox), y); + if (row != NULL && gtk_widget_is_sensitive (GTK_WIDGET (row))) + activate_row (view, NAUTILUS_GTK_PLACES_VIEW_ROW (row), NAUTILUS_GTK_PLACES_OPEN_NEW_TAB); +} + + +static void +on_eject_button_clicked (GtkWidget *widget, + NautilusGtkPlacesViewRow *row) +{ + if (row) + { + GtkWidget *view = gtk_widget_get_ancestor (GTK_WIDGET (row), NAUTILUS_TYPE_GTK_PLACES_VIEW); + + unmount_mount (NAUTILUS_GTK_PLACES_VIEW (view), nautilus_gtk_places_view_row_get_mount (row)); + } +} + +static void +on_connect_button_clicked (NautilusGtkPlacesView *view) +{ + const char *uri; + GFile *file; + + file = NULL; + + /* + * Since the 'Connect' button is updated whenever the typed + * address changes, it is sufficient to check if it's sensitive + * or not, in order to determine if the given address is valid. + */ + if (!gtk_widget_get_sensitive (view->connect_button)) + return; + + uri = gtk_editable_get_text (GTK_EDITABLE (view->address_entry)); + + if (uri != NULL && uri[0] != '\0') + file = g_file_new_for_commandline_arg (uri); + + if (file) + { + view->should_open_location = TRUE; + + mount_server (view, file); + } + else + { + emit_show_error_message (view, _("Unable to get remote server location"), NULL); + } +} + +static void +on_address_entry_text_changed (NautilusGtkPlacesView *view) +{ + const char * const *supported_protocols; + char *address, *scheme; + gboolean supported; + + supported = FALSE; + supported_protocols = g_vfs_get_supported_uri_schemes (g_vfs_get_default ()); + address = g_strdup (gtk_editable_get_text (GTK_EDITABLE (view->address_entry))); + scheme = g_uri_parse_scheme (address); + + if (!supported_protocols) + goto out; + + if (!scheme) + goto out; + + supported = g_strv_contains (supported_protocols, scheme) && + !g_strv_contains (unsupported_protocols, scheme); + +out: + gtk_widget_set_sensitive (view->connect_button, supported); + if (scheme && !supported) + gtk_widget_add_css_class (view->address_entry, "error"); + else + gtk_widget_remove_css_class (view->address_entry, "error"); + + g_free (address); + g_free (scheme); +} + +static void +on_address_entry_show_help_pressed (NautilusGtkPlacesView *view, + GtkEntryIconPosition icon_pos, + GtkEntry *entry) +{ + GdkRectangle rect; + double x, y; + + /* Setup the auxiliary popover's rectangle */ + gtk_entry_get_icon_area (GTK_ENTRY (view->address_entry), + GTK_ENTRY_ICON_SECONDARY, + &rect); + gtk_widget_translate_coordinates (view->address_entry, GTK_WIDGET (view), + rect.x, rect.y, &x, &y); + + rect.x = x; + rect.y = y; + gtk_popover_set_pointing_to (GTK_POPOVER (view->server_adresses_popover), &rect); + gtk_widget_set_visible (view->server_adresses_popover, TRUE); +} + +static void +on_recent_servers_listbox_row_activated (NautilusGtkPlacesView *view, + NautilusGtkPlacesViewRow *row, + GtkWidget *listbox) +{ + char *uri; + + uri = g_object_get_data (G_OBJECT (row), "uri"); + + gtk_editable_set_text (GTK_EDITABLE (view->address_entry), uri); + + gtk_widget_hide (view->recent_servers_popover); +} + +static void +on_listbox_row_activated (NautilusGtkPlacesView *view, + NautilusGtkPlacesViewRow *row, + GtkWidget *listbox) +{ + activate_row (view, row, view->current_open_flags); +} + +static gboolean +listbox_filter_func (GtkListBoxRow *row, + gpointer user_data) +{ + NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (user_data); + gboolean is_placeholder; + gboolean retval; + gboolean searching; + char *name; + char *path; + + retval = FALSE; + searching = view->search_query && view->search_query[0] != '\0'; + + is_placeholder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "is-placeholder")); + + if (is_placeholder && searching) + return FALSE; + + if (!searching) + return TRUE; + + g_object_get (row, + "name", &name, + "path", &path, + NULL); + + if (name) + { + char *lowercase_name = g_utf8_strdown (name, -1); + + retval |= strstr (lowercase_name, view->search_query) != NULL; + + g_free (lowercase_name); + } + + if (path) + { + char *lowercase_path = g_utf8_strdown (path, -1); + + retval |= strstr (lowercase_path, view->search_query) != NULL; + + g_free (lowercase_path); + } + + g_free (name); + g_free (path); + + return retval; +} + +static void +listbox_header_func (GtkListBoxRow *row, + GtkListBoxRow *before, + gpointer user_data) +{ + gboolean row_is_network; + char *text; + + text = NULL; + row_is_network = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "is-network")); + + if (!before) + { + text = g_strdup_printf ("<b>%s</b>", row_is_network ? _("Networks") : _("On This Computer")); + } + else + { + gboolean before_is_network; + + before_is_network = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (before), "is-network")); + + if (before_is_network != row_is_network) + text = g_strdup_printf ("<b>%s</b>", row_is_network ? _("Networks") : _("On This Computer")); + } + + if (text) + { + GtkWidget *header; + GtkWidget *label; + GtkWidget *separator; + + header = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_widget_set_margin_top (header, 6); + + separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL); + + label = g_object_new (GTK_TYPE_LABEL, + "use_markup", TRUE, + "margin-start", 12, + "label", text, + "xalign", 0.0f, + NULL); + if (row_is_network) + { + GtkWidget *header_name; + GtkWidget *network_header_spinner; + + gtk_widget_set_margin_end (label, 6); + + header_name = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + network_header_spinner = gtk_spinner_new (); + gtk_widget_set_margin_end (network_header_spinner, 12); + g_object_bind_property (NAUTILUS_GTK_PLACES_VIEW (user_data), + "fetching-networks", + network_header_spinner, + "spinning", + G_BINDING_SYNC_CREATE); + + gtk_box_append (GTK_BOX (header_name), label); + gtk_box_append (GTK_BOX (header_name), network_header_spinner); + gtk_box_append (GTK_BOX (header), header_name); + } + else + { + gtk_widget_set_hexpand (label, TRUE); + gtk_widget_set_margin_end (label, 12); + gtk_box_append (GTK_BOX (header), label); + } + + gtk_box_append (GTK_BOX (header), separator); + + gtk_list_box_row_set_header (row, header); + + g_free (text); + } + else + { + gtk_list_box_row_set_header (row, NULL); + } +} + +static int +listbox_sort_func (GtkListBoxRow *row1, + GtkListBoxRow *row2, + gpointer user_data) +{ + gboolean row1_is_network; + gboolean row2_is_network; + char *path1; + char *path2; + gboolean *is_placeholder1; + gboolean *is_placeholder2; + int retval; + + row1_is_network = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row1), "is-network")); + row2_is_network = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row2), "is-network")); + + retval = row1_is_network - row2_is_network; + + if (retval != 0) + return retval; + + is_placeholder1 = g_object_get_data (G_OBJECT (row1), "is-placeholder"); + is_placeholder2 = g_object_get_data (G_OBJECT (row2), "is-placeholder"); + + /* we can't have two placeholders for the same section */ + g_assert (!(is_placeholder1 != NULL && is_placeholder2 != NULL)); + + if (is_placeholder1) + return -1; + if (is_placeholder2) + return 1; + + g_object_get (row1, "path", &path1, NULL); + g_object_get (row2, "path", &path2, NULL); + + retval = g_utf8_collate (path1, path2); + + g_free (path1); + g_free (path2); + + return retval; +} + +static void +nautilus_gtk_places_view_constructed (GObject *object) +{ + NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (object); + + G_OBJECT_CLASS (nautilus_gtk_places_view_parent_class)->constructed (object); + + gtk_list_box_set_sort_func (GTK_LIST_BOX (view->listbox), + listbox_sort_func, + object, + NULL); + gtk_list_box_set_filter_func (GTK_LIST_BOX (view->listbox), + listbox_filter_func, + object, + NULL); + gtk_list_box_set_header_func (GTK_LIST_BOX (view->listbox), + listbox_header_func, + object, + NULL); + + /* load drives */ + update_places (view); + + g_signal_connect_swapped (view->volume_monitor, + "mount-added", + G_CALLBACK (update_places), + object); + g_signal_connect_swapped (view->volume_monitor, + "mount-changed", + G_CALLBACK (update_places), + object); + g_signal_connect_swapped (view->volume_monitor, + "mount-removed", + G_CALLBACK (update_places), + object); + g_signal_connect_swapped (view->volume_monitor, + "volume-added", + G_CALLBACK (update_places), + object); + g_signal_connect_swapped (view->volume_monitor, + "volume-changed", + G_CALLBACK (update_places), + object); + g_signal_connect_swapped (view->volume_monitor, + "volume-removed", + G_CALLBACK (update_places), + object); +} + +static void +nautilus_gtk_places_view_map (GtkWidget *widget) +{ + NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (widget); + + gtk_editable_set_text (GTK_EDITABLE (view->address_entry), ""); + + GTK_WIDGET_CLASS (nautilus_gtk_places_view_parent_class)->map (widget); +} + +static void +nautilus_gtk_places_view_class_init (NautilusGtkPlacesViewClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = nautilus_gtk_places_view_finalize; + object_class->dispose = nautilus_gtk_places_view_dispose; + object_class->constructed = nautilus_gtk_places_view_constructed; + object_class->get_property = nautilus_gtk_places_view_get_property; + object_class->set_property = nautilus_gtk_places_view_set_property; + + widget_class->map = nautilus_gtk_places_view_map; + + /* + * NautilusGtkPlacesView::open-location: + * @view: the object which received the signal. + * @location: (type Gio.File): GFile to which the caller should switch. + * @open_flags: a single value from NautilusGtkPlacesOpenFlags specifying how the @location + * should be opened. + * + * The places view 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. + */ + places_view_signals [OPEN_LOCATION] = + g_signal_new ("open-location", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (NautilusGtkPlacesViewClass, open_location), + NULL, NULL, + NULL, + G_TYPE_NONE, 2, + G_TYPE_OBJECT, + NAUTILUS_TYPE_OPEN_FLAGS); + + /* + * NautilusGtkPlacesView::show-error-message: + * @view: 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 view 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. + */ + places_view_signals [SHOW_ERROR_MESSAGE] = + g_signal_new ("show-error-message", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (NautilusGtkPlacesViewClass, show_error_message), + NULL, NULL, + NULL, + G_TYPE_NONE, 2, + G_TYPE_STRING, + G_TYPE_STRING); + + properties[PROP_LOADING] = + g_param_spec_boolean ("loading", + "Loading", + "Whether the view is loading locations", + FALSE, + G_PARAM_READABLE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB); + + properties[PROP_FETCHING_NETWORKS] = + g_param_spec_boolean ("fetching-networks", + "Fetching networks", + "Whether the view is fetching networks", + FALSE, + G_PARAM_READABLE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB); + + properties[PROP_OPEN_FLAGS] = + g_param_spec_flags ("open-flags", + "Open Flags", + "Modes in which the calling application can open locations selected in the sidebar", + NAUTILUS_TYPE_OPEN_FLAGS, + NAUTILUS_GTK_PLACES_OPEN_NORMAL, + G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB); + + g_object_class_install_properties (object_class, LAST_PROP, properties); + + /* Bind class to template */ + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/nautilus/gtk/ui/nautilusgtkplacesview.ui"); + + gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, actionbar); + gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, address_entry); + gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, address_entry_completion); + gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, completion_store); + gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, connect_button); + gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, listbox); + gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, recent_servers_listbox); + gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, recent_servers_popover); + gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, recent_servers_stack); + gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, stack); + gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, server_adresses_popover); + gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, available_protocols_grid); + + gtk_widget_class_bind_template_callback (widget_class, on_address_entry_text_changed); + gtk_widget_class_bind_template_callback (widget_class, on_address_entry_show_help_pressed); + gtk_widget_class_bind_template_callback (widget_class, on_connect_button_clicked); + gtk_widget_class_bind_template_callback (widget_class, on_listbox_row_activated); + gtk_widget_class_bind_template_callback (widget_class, on_recent_servers_listbox_row_activated); + + /** + * NautilusGtkPlacesView|location.open: + * + * Opens the location in the current window. + */ + gtk_widget_class_install_action (widget_class, "location.open", NULL, open_cb); + + /** + * NautilusGtkPlacesView|location.open-tab: + * + * Opens the location in a new tab. + */ + gtk_widget_class_install_action (widget_class, "location.open-tab", NULL, open_cb); + + /** + * NautilusGtkPlacesView|location.open-window: + * + * Opens the location in a new window. + */ + gtk_widget_class_install_action (widget_class, "location.open-window", NULL, open_cb); + + /** + * NautilusGtkPlacesView|location.mount: + * + * Mount the location. + */ + gtk_widget_class_install_action (widget_class, "location.mount", NULL, mount_cb); + + /** + * NautilusGtkPlacesView|location.connect: + * + * Connect the location. + */ + gtk_widget_class_install_action (widget_class, "location.connect", NULL, mount_cb); + + /** + * NautilusGtkPlacesView|location.unmount: + * + * Unmount the location. + */ + gtk_widget_class_install_action (widget_class, "location.unmount", NULL, unmount_cb); + + /** + * NautilusGtkPlacesView|location.disconnect: + * + * Disconnect the location. + */ + gtk_widget_class_install_action (widget_class, "location.disconnect", NULL, unmount_cb); + + /** + * NautilusGtkPlacesView|location.properties: + * + * Show location properties. + */ + gtk_widget_class_install_action (widget_class, "location.properties", NULL, properties_cb); + + gtk_widget_class_set_css_name (widget_class, "placesview"); + gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_LIST); +} + +static void +nautilus_gtk_places_view_init (NautilusGtkPlacesView *self) +{ + GtkEventController *controller; + + self->volume_monitor = g_volume_monitor_get (); + self->open_flags = NAUTILUS_GTK_PLACES_OPEN_NORMAL; + self->path_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + self->space_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + + gtk_widget_action_set_enabled (GTK_WIDGET (self), "location.open-tab", FALSE); + gtk_widget_action_set_enabled (GTK_WIDGET (self), "location.open-window", FALSE); + + gtk_widget_init_template (GTK_WIDGET (self)); + + gtk_widget_set_parent (self->server_adresses_popover, GTK_WIDGET (self)); + controller = gtk_event_controller_key_new (); + g_signal_connect (controller, "key-pressed", G_CALLBACK (on_key_press_event), self); + gtk_widget_add_controller (GTK_WIDGET (self), controller); + + /* We need an additional controller because GtkListBox only + * activates rows for GDK_BUTTON_PRIMARY clicks + */ + controller = (GtkEventController *) gtk_gesture_click_new (); + gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_BUBBLE); + gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (controller), GDK_BUTTON_MIDDLE); + g_signal_connect (controller, "released", + G_CALLBACK (on_middle_click_row_event), self); + gtk_widget_add_controller (self->listbox, controller); + + populate_available_protocols_grid (GTK_GRID (self->available_protocols_grid)); +} + +/* + * nautilus_gtk_places_view_new: + * + * Creates a new NautilusGtkPlacesView widget. + * + * The application should connect to at least the + * NautilusGtkPlacesView::open-location signal to be notified + * when the user makes a selection in the view. + * + * Returns: a newly created NautilusGtkPlacesView + */ +GtkWidget * +nautilus_gtk_places_view_new (void) +{ + return g_object_new (NAUTILUS_TYPE_GTK_PLACES_VIEW, NULL); +} + +/* + * nautilus_gtk_places_view_set_open_flags: + * @view: a NautilusGtkPlacesView + * @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 view. 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 @view about the ways in which the + * application can open new locations, so that the view can display (or not) + * the “Open in new tab” and “Open in new window” menu items as appropriate. + * + * When the NautilusGtkPlacesView::open-location signal is emitted, its flags + * argument will be set to one of the @flags that was passed in + * nautilus_gtk_places_view_set_open_flags(). + * + * Passing 0 for @flags will cause NAUTILUS_GTK_PLACES_OPEN_NORMAL to always be sent + * to callbacks for the “open-location” signal. + */ +void +nautilus_gtk_places_view_set_open_flags (NautilusGtkPlacesView *view, + NautilusGtkPlacesOpenFlags flags) +{ + g_return_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view)); + + if (view->open_flags == flags) + return; + + view->open_flags = flags; + + gtk_widget_action_set_enabled (GTK_WIDGET (view), "location.open-tab", + (flags & NAUTILUS_GTK_PLACES_OPEN_NEW_TAB) != 0); + gtk_widget_action_set_enabled (GTK_WIDGET (view), "location.open-window", + (flags & NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW) != 0); + + g_object_notify_by_pspec (G_OBJECT (view), properties[PROP_OPEN_FLAGS]); +} + +/* + * nautilus_gtk_places_view_get_open_flags: + * @view: a NautilusGtkPlacesSidebar + * + * Gets the open flags. + * + * Returns: the NautilusGtkPlacesOpenFlags of @view + */ +NautilusGtkPlacesOpenFlags +nautilus_gtk_places_view_get_open_flags (NautilusGtkPlacesView *view) +{ + g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view), 0); + + return view->open_flags; +} + +/* + * nautilus_gtk_places_view_get_search_query: + * @view: a NautilusGtkPlacesView + * + * Retrieves the current search query from @view. + * + * Returns: (transfer none): the current search query. + */ +const char * +nautilus_gtk_places_view_get_search_query (NautilusGtkPlacesView *view) +{ + g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view), NULL); + + return view->search_query; +} + +/* + * nautilus_gtk_places_view_set_search_query: + * @view: a NautilusGtkPlacesView + * @query_text: the query, or NULL. + * + * Sets the search query of @view. The search is immediately performed + * once the query is set. + */ +void +nautilus_gtk_places_view_set_search_query (NautilusGtkPlacesView *view, + const char *query_text) +{ + g_return_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view)); + + if (g_strcmp0 (view->search_query, query_text) != 0) + { + g_clear_pointer (&view->search_query, g_free); + view->search_query = g_utf8_strdown (query_text, -1); + + gtk_list_box_invalidate_filter (GTK_LIST_BOX (view->listbox)); + gtk_list_box_invalidate_headers (GTK_LIST_BOX (view->listbox)); + + update_view_mode (view); + } +} + +/* + * nautilus_gtk_places_view_get_loading: + * @view: a NautilusGtkPlacesView + * + * Returns %TRUE if the view is loading locations. + */ +gboolean +nautilus_gtk_places_view_get_loading (NautilusGtkPlacesView *view) +{ + g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view), FALSE); + + return view->loading; +} + +static void +update_loading (NautilusGtkPlacesView *view) +{ + gboolean loading; + + g_return_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view)); + + loading = view->fetching_networks || view->connecting_to_server || + view->mounting_volume || view->unmounting_mount; + + set_busy_cursor (view, loading); + nautilus_gtk_places_view_set_loading (view, loading); +} + +static void +nautilus_gtk_places_view_set_loading (NautilusGtkPlacesView *view, + gboolean loading) +{ + g_return_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view)); + + if (view->loading != loading) + { + view->loading = loading; + g_object_notify_by_pspec (G_OBJECT (view), properties [PROP_LOADING]); + } +} + +static gboolean +nautilus_gtk_places_view_get_fetching_networks (NautilusGtkPlacesView *view) +{ + g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view), FALSE); + + return view->fetching_networks; +} + +static void +nautilus_gtk_places_view_set_fetching_networks (NautilusGtkPlacesView *view, + gboolean fetching_networks) +{ + g_return_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view)); + + if (view->fetching_networks != fetching_networks) + { + view->fetching_networks = fetching_networks; + g_object_notify_by_pspec (G_OBJECT (view), properties [PROP_FETCHING_NETWORKS]); + } +} |