1 /* gtkplacesview.c
2  *
3  * Copyright (C) 2015 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU Lesser General Public License as published by
7  * the Free Software Foundation, either version 2.1 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "config.h"
20 
21 #include <gio/gio.h>
22 #include <gio/gvfs.h>
23 #include <gtk/gtk.h>
24 
25 #include "gtkintl.h"
26 #include "gtkmarshalers.h"
27 #include "gtkplacesviewprivate.h"
28 #include "gtkplacesviewrowprivate.h"
29 #include "gtktypebuiltins.h"
30 
31 /**
32  * SECTION:gtkplacesview
33  * @Short_description: Widget that displays persistent drives and manages mounted networks
34  * @Title: GtkPlacesView
35  * @See_also: #GtkFileChooser
36  *
37  * #GtkPlacesView is a stock widget that displays a list of persistent drives
38  * such as harddisk partitions and networks.  #GtkPlacesView does not monitor
39  * removable devices.
40  *
41  * The places view displays drives and networks, and will automatically mount
42  * them when the user activates. Network addresses are stored even if they fail
43  * to connect. When the connection is successful, the connected network is
44  * shown at the network list.
45  *
46  * To make use of the places view, an application at least needs to connect
47  * to the #GtkPlacesView::open-location signal. This is emitted when the user
48  * selects a location to open in the view.
49  */
50 
51 struct _GtkPlacesViewPrivate
52 {
53   GVolumeMonitor                *volume_monitor;
54   GtkPlacesOpenFlags             open_flags;
55   GtkPlacesOpenFlags             current_open_flags;
56 
57   GFile                         *server_list_file;
58   GFileMonitor                  *server_list_monitor;
59   GFileMonitor                  *network_monitor;
60 
61   GCancellable                  *cancellable;
62 
63   gchar                         *search_query;
64 
65   GtkWidget                     *actionbar;
66   GtkWidget                     *address_entry;
67   GtkWidget                     *connect_button;
68   GtkWidget                     *listbox;
69   GtkWidget                     *popup_menu;
70   GtkWidget                     *recent_servers_listbox;
71   GtkWidget                     *recent_servers_popover;
72   GtkWidget                     *recent_servers_stack;
73   GtkWidget                     *stack;
74   GtkWidget                     *server_adresses_popover;
75   GtkWidget                     *available_protocols_grid;
76   GtkWidget                     *network_placeholder;
77   GtkWidget                     *network_placeholder_label;
78 
79   GtkSizeGroup                  *path_size_group;
80   GtkSizeGroup                  *space_size_group;
81 
82   GtkEntryCompletion            *address_entry_completion;
83   GtkListStore                  *completion_store;
84 
85   GCancellable                  *networks_fetching_cancellable;
86 
87   guint                          local_only : 1;
88   guint                          should_open_location : 1;
89   guint                          should_pulse_entry : 1;
90   guint                          entry_pulse_timeout_id;
91   guint                          connecting_to_server : 1;
92   guint                          mounting_volume : 1;
93   guint                          unmounting_mount : 1;
94   guint                          fetching_networks : 1;
95   guint                          loading : 1;
96   guint                          destroyed : 1;
97 };
98 
99 static void        mount_volume                                  (GtkPlacesView *view,
100                                                                   GVolume       *volume);
101 
102 static gboolean    on_button_press_event                         (GtkPlacesViewRow *row,
103                                                                   GdkEventButton   *event);
104 
105 static void        on_eject_button_clicked                       (GtkWidget        *widget,
106                                                                   GtkPlacesViewRow *row);
107 
108 static gboolean    on_row_popup_menu                             (GtkPlacesViewRow *row);
109 
110 static void        populate_servers                              (GtkPlacesView *view);
111 
112 static gboolean    gtk_places_view_get_fetching_networks         (GtkPlacesView *view);
113 
114 static void        gtk_places_view_set_fetching_networks         (GtkPlacesView *view,
115                                                                   gboolean       fetching_networks);
116 
117 static void        gtk_places_view_set_loading                   (GtkPlacesView *view,
118                                                                   gboolean       loading);
119 
120 static void        update_loading                                (GtkPlacesView *view);
121 
122 G_DEFINE_TYPE_WITH_PRIVATE (GtkPlacesView, gtk_places_view, GTK_TYPE_BOX)
123 
124 /* GtkPlacesView properties & signals */
125 enum {
126   PROP_0,
127   PROP_LOCAL_ONLY,
128   PROP_OPEN_FLAGS,
129   PROP_FETCHING_NETWORKS,
130   PROP_LOADING,
131   LAST_PROP
132 };
133 
134 enum {
135   OPEN_LOCATION,
136   SHOW_ERROR_MESSAGE,
137   LAST_SIGNAL
138 };
139 
140 const gchar *unsupported_protocols [] =
141 {
142   "file", "afc", "obex", "http",
143   "trash", "burn", "computer",
144   "archive", "recent", "localtest",
145   NULL
146 };
147 
148 static guint places_view_signals [LAST_SIGNAL] = { 0 };
149 static GParamSpec *properties [LAST_PROP];
150 
151 static void
emit_open_location(GtkPlacesView * view,GFile * location,GtkPlacesOpenFlags open_flags)152 emit_open_location (GtkPlacesView      *view,
153                     GFile              *location,
154                     GtkPlacesOpenFlags  open_flags)
155 {
156   GtkPlacesViewPrivate *priv;
157 
158   priv = gtk_places_view_get_instance_private (view);
159 
160   if ((open_flags & priv->open_flags) == 0)
161     open_flags = GTK_PLACES_OPEN_NORMAL;
162 
163   g_signal_emit (view, places_view_signals[OPEN_LOCATION], 0, location, open_flags);
164 }
165 
166 static void
emit_show_error_message(GtkPlacesView * view,gchar * primary_message,gchar * secondary_message)167 emit_show_error_message (GtkPlacesView *view,
168                          gchar         *primary_message,
169                          gchar         *secondary_message)
170 {
171   g_signal_emit (view, places_view_signals[SHOW_ERROR_MESSAGE],
172                          0, primary_message, secondary_message);
173 }
174 
175 static void
server_file_changed_cb(GtkPlacesView * view)176 server_file_changed_cb (GtkPlacesView *view)
177 {
178   populate_servers (view);
179 }
180 
181 static GBookmarkFile *
server_list_load(GtkPlacesView * view)182 server_list_load (GtkPlacesView *view)
183 {
184   GtkPlacesViewPrivate *priv;
185   GBookmarkFile *bookmarks;
186   GError *error = NULL;
187   gchar *datadir;
188   gchar *filename;
189 
190   priv = gtk_places_view_get_instance_private (view);
191   bookmarks = g_bookmark_file_new ();
192   datadir = g_build_filename (g_get_user_config_dir (), "gtk-3.0", NULL);
193   filename = g_build_filename (datadir, "servers", NULL);
194 
195   g_mkdir_with_parents (datadir, 0700);
196   g_bookmark_file_load_from_file (bookmarks, filename, &error);
197 
198   if (error)
199     {
200       if (!g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
201         {
202           /* only warn if the file exists */
203           g_warning ("Unable to open server bookmarks: %s", error->message);
204           g_clear_pointer (&bookmarks, g_bookmark_file_free);
205         }
206 
207       g_clear_error (&error);
208     }
209 
210   /* Monitor the file in case it's modified outside this code */
211   if (!priv->server_list_monitor)
212     {
213       priv->server_list_file = g_file_new_for_path (filename);
214 
215       if (priv->server_list_file)
216         {
217           priv->server_list_monitor = g_file_monitor_file (priv->server_list_file,
218                                                            G_FILE_MONITOR_NONE,
219                                                            NULL,
220                                                            &error);
221 
222           if (error)
223             {
224               g_warning ("Cannot monitor server file: %s", error->message);
225               g_clear_error (&error);
226             }
227           else
228             {
229               g_signal_connect_swapped (priv->server_list_monitor,
230                                         "changed",
231                                         G_CALLBACK (server_file_changed_cb),
232                                         view);
233             }
234         }
235 
236       g_clear_object (&priv->server_list_file);
237     }
238 
239   g_free (datadir);
240   g_free (filename);
241 
242   return bookmarks;
243 }
244 
245 static void
server_list_save(GBookmarkFile * bookmarks)246 server_list_save (GBookmarkFile *bookmarks)
247 {
248   gchar *filename;
249 
250   filename = g_build_filename (g_get_user_config_dir (), "gtk-3.0", "servers", NULL);
251   g_bookmark_file_to_file (bookmarks, filename, NULL);
252   g_free (filename);
253 }
254 
255 static void
server_list_add_server(GtkPlacesView * view,GFile * file)256 server_list_add_server (GtkPlacesView *view,
257                         GFile         *file)
258 {
259   GBookmarkFile *bookmarks;
260   GFileInfo *info;
261   GError *error;
262   gchar *title;
263   gchar *uri;
264 
265   error = NULL;
266   bookmarks = server_list_load (view);
267 
268   if (!bookmarks)
269     return;
270 
271   uri = g_file_get_uri (file);
272 
273   info = g_file_query_info (file,
274                             G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
275                             G_FILE_QUERY_INFO_NONE,
276                             NULL,
277                             &error);
278   title = g_file_info_get_attribute_as_string (info, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME);
279 
280   g_bookmark_file_set_title (bookmarks, uri, title);
281   g_bookmark_file_set_visited (bookmarks, uri, -1);
282   g_bookmark_file_add_application (bookmarks, uri, NULL, NULL);
283 
284   server_list_save (bookmarks);
285 
286   g_bookmark_file_free (bookmarks);
287   g_clear_object (&info);
288   g_free (title);
289   g_free (uri);
290 }
291 
292 static void
server_list_remove_server(GtkPlacesView * view,const gchar * uri)293 server_list_remove_server (GtkPlacesView *view,
294                            const gchar   *uri)
295 {
296   GBookmarkFile *bookmarks;
297 
298   bookmarks = server_list_load (view);
299 
300   if (!bookmarks)
301     return;
302 
303   g_bookmark_file_remove_item (bookmarks, uri, NULL);
304   server_list_save (bookmarks);
305 
306   g_bookmark_file_free (bookmarks);
307 }
308 
309 /* Returns a toplevel GtkWindow, or NULL if none */
310 static GtkWindow *
get_toplevel(GtkWidget * widget)311 get_toplevel (GtkWidget *widget)
312 {
313   GtkWidget *toplevel;
314 
315   toplevel = gtk_widget_get_toplevel (widget);
316   if (!gtk_widget_is_toplevel (toplevel))
317     return NULL;
318   else
319     return GTK_WINDOW (toplevel);
320 }
321 
322 static void
set_busy_cursor(GtkPlacesView * view,gboolean busy)323 set_busy_cursor (GtkPlacesView *view,
324                  gboolean       busy)
325 {
326   GtkWidget *widget;
327   GtkWindow *toplevel;
328   GdkDisplay *display;
329   GdkCursor *cursor;
330 
331   toplevel = get_toplevel (GTK_WIDGET (view));
332   widget = GTK_WIDGET (toplevel);
333   if (!toplevel || !gtk_widget_get_realized (widget))
334     return;
335 
336   display = gtk_widget_get_display (widget);
337 
338   if (busy)
339     cursor = gdk_cursor_new_from_name (display, "progress");
340   else
341     cursor = NULL;
342 
343   gdk_window_set_cursor (gtk_widget_get_window (widget), cursor);
344   gdk_display_flush (display);
345 
346   if (cursor)
347     g_object_unref (cursor);
348 }
349 
350 /* Activates the given row, with the given flags as parameter */
351 static void
activate_row(GtkPlacesView * view,GtkPlacesViewRow * row,GtkPlacesOpenFlags flags)352 activate_row (GtkPlacesView      *view,
353               GtkPlacesViewRow   *row,
354               GtkPlacesOpenFlags  flags)
355 {
356   GtkPlacesViewPrivate *priv;
357   GVolume *volume;
358   GMount *mount;
359   GFile *file;
360 
361   priv = gtk_places_view_get_instance_private (view);
362   mount = gtk_places_view_row_get_mount (row);
363   volume = gtk_places_view_row_get_volume (row);
364   file = gtk_places_view_row_get_file (row);
365 
366   if (file)
367     {
368       emit_open_location (view, file, flags);
369     }
370   else if (mount)
371     {
372       GFile *location = g_mount_get_default_location (mount);
373 
374       emit_open_location (view, location, flags);
375 
376       g_object_unref (location);
377     }
378   else if (volume && g_volume_can_mount (volume))
379     {
380       /*
381        * When the row is activated, the unmounted volume shall
382        * be mounted and opened right after.
383        */
384       priv->should_open_location = TRUE;
385 
386       gtk_places_view_row_set_busy (row, TRUE);
387       mount_volume (view, volume);
388     }
389 }
390 
391 static void update_places (GtkPlacesView *view);
392 
393 static void
gtk_places_view_destroy(GtkWidget * widget)394 gtk_places_view_destroy (GtkWidget *widget)
395 {
396   GtkPlacesView *self = GTK_PLACES_VIEW (widget);
397   GtkPlacesViewPrivate *priv = gtk_places_view_get_instance_private (self);
398 
399   priv->destroyed = 1;
400 
401   g_signal_handlers_disconnect_by_func (priv->volume_monitor, update_places, widget);
402 
403   if (priv->network_monitor)
404     g_signal_handlers_disconnect_by_func (priv->network_monitor, update_places, widget);
405 
406   if (priv->server_list_monitor)
407     g_signal_handlers_disconnect_by_func (priv->server_list_monitor, server_file_changed_cb, widget);
408 
409   g_cancellable_cancel (priv->cancellable);
410   g_cancellable_cancel (priv->networks_fetching_cancellable);
411 
412   GTK_WIDGET_CLASS (gtk_places_view_parent_class)->destroy (widget);
413 }
414 
415 static void
gtk_places_view_finalize(GObject * object)416 gtk_places_view_finalize (GObject *object)
417 {
418   GtkPlacesView *self = (GtkPlacesView *)object;
419   GtkPlacesViewPrivate *priv = gtk_places_view_get_instance_private (self);
420 
421   if (priv->entry_pulse_timeout_id > 0)
422     g_source_remove (priv->entry_pulse_timeout_id);
423 
424   g_clear_pointer (&priv->search_query, g_free);
425   g_clear_object (&priv->server_list_file);
426   g_clear_object (&priv->server_list_monitor);
427   g_clear_object (&priv->volume_monitor);
428   g_clear_object (&priv->network_monitor);
429   g_clear_object (&priv->cancellable);
430   g_clear_object (&priv->networks_fetching_cancellable);
431   g_clear_object (&priv->path_size_group);
432   g_clear_object (&priv->space_size_group);
433 
434   G_OBJECT_CLASS (gtk_places_view_parent_class)->finalize (object);
435 }
436 
437 static void
gtk_places_view_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)438 gtk_places_view_get_property (GObject    *object,
439                               guint       prop_id,
440                               GValue     *value,
441                               GParamSpec *pspec)
442 {
443   GtkPlacesView *self = GTK_PLACES_VIEW (object);
444 
445   switch (prop_id)
446     {
447     case PROP_LOCAL_ONLY:
448       g_value_set_boolean (value, gtk_places_view_get_local_only (self));
449       break;
450 
451     case PROP_LOADING:
452       g_value_set_boolean (value, gtk_places_view_get_loading (self));
453       break;
454 
455     case PROP_FETCHING_NETWORKS:
456       g_value_set_boolean (value, gtk_places_view_get_fetching_networks (self));
457       break;
458 
459     default:
460       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
461     }
462 }
463 
464 static void
gtk_places_view_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)465 gtk_places_view_set_property (GObject      *object,
466                               guint         prop_id,
467                               const GValue *value,
468                               GParamSpec   *pspec)
469 {
470   GtkPlacesView *self = GTK_PLACES_VIEW (object);
471 
472   switch (prop_id)
473     {
474     case PROP_LOCAL_ONLY:
475       gtk_places_view_set_local_only (self, g_value_get_boolean (value));
476       break;
477 
478     default:
479       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
480     }
481 }
482 
483 static gboolean
is_external_volume(GVolume * volume)484 is_external_volume (GVolume *volume)
485 {
486   gboolean is_external;
487   GDrive *drive;
488   gchar *id;
489 
490   drive = g_volume_get_drive (volume);
491   id = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_CLASS);
492 
493   is_external = g_volume_can_eject (volume);
494 
495   /* NULL volume identifier only happens on removable devices */
496   is_external |= !id;
497 
498   if (drive)
499     is_external |= g_drive_is_removable (drive);
500 
501   g_clear_object (&drive);
502   g_free (id);
503 
504   return is_external;
505 }
506 
507 typedef struct
508 {
509   gchar         *uri;
510   GtkPlacesView *view;
511 } RemoveServerData;
512 
513 static void
on_remove_server_button_clicked(RemoveServerData * data)514 on_remove_server_button_clicked (RemoveServerData *data)
515 {
516   server_list_remove_server (data->view, data->uri);
517 
518   populate_servers (data->view);
519 }
520 
521 static void
populate_servers(GtkPlacesView * view)522 populate_servers (GtkPlacesView *view)
523 {
524   GtkPlacesViewPrivate *priv;
525   GBookmarkFile *server_list;
526   GList *children;
527   gchar **uris;
528   gsize num_uris;
529   gint i;
530 
531   priv = gtk_places_view_get_instance_private (view);
532   server_list = server_list_load (view);
533 
534   if (!server_list)
535     return;
536 
537   uris = g_bookmark_file_get_uris (server_list, &num_uris);
538 
539   gtk_stack_set_visible_child_name (GTK_STACK (priv->recent_servers_stack),
540                                     num_uris > 0 ? "list" : "empty");
541 
542   if (!uris)
543     {
544       g_bookmark_file_free (server_list);
545       return;
546     }
547 
548   /* clear previous items */
549   children = gtk_container_get_children (GTK_CONTAINER (priv->recent_servers_listbox));
550   g_list_free_full (children, (GDestroyNotify) gtk_widget_destroy);
551 
552   gtk_list_store_clear (priv->completion_store);
553 
554   for (i = 0; i < num_uris; i++)
555     {
556       RemoveServerData *data;
557       GtkTreeIter iter;
558       GtkWidget *row;
559       GtkWidget *grid;
560       GtkWidget *button;
561       GtkWidget *label;
562       gchar *name;
563       gchar *dup_uri;
564 
565       name = g_bookmark_file_get_title (server_list, uris[i], NULL);
566       dup_uri = g_strdup (uris[i]);
567 
568       /* add to the completion list */
569       gtk_list_store_append (priv->completion_store, &iter);
570       gtk_list_store_set (priv->completion_store,
571                           &iter,
572                           0, name,
573                           1, uris[i],
574                           -1);
575 
576       /* add to the recent servers listbox */
577       row = gtk_list_box_row_new ();
578 
579       grid = g_object_new (GTK_TYPE_GRID,
580                            "orientation", GTK_ORIENTATION_VERTICAL,
581                            "border-width", 3,
582                            NULL);
583 
584       /* name of the connected uri, if any */
585       label = gtk_label_new (name);
586       gtk_widget_set_hexpand (label, TRUE);
587       gtk_label_set_xalign (GTK_LABEL (label), 0.0);
588       gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
589       gtk_container_add (GTK_CONTAINER (grid), label);
590 
591       /* the uri itself */
592       label = gtk_label_new (uris[i]);
593       gtk_widget_set_hexpand (label, TRUE);
594       gtk_label_set_xalign (GTK_LABEL (label), 0.0);
595       gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
596       gtk_style_context_add_class (gtk_widget_get_style_context (label), "dim-label");
597       gtk_container_add (GTK_CONTAINER (grid), label);
598 
599       /* remove button */
600       button = gtk_button_new_from_icon_name ("window-close-symbolic", GTK_ICON_SIZE_BUTTON);
601       gtk_widget_set_halign (button, GTK_ALIGN_END);
602       gtk_widget_set_valign (button, GTK_ALIGN_CENTER);
603       gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
604       gtk_style_context_add_class (gtk_widget_get_style_context (button), "sidebar-button");
605       gtk_grid_attach (GTK_GRID (grid), button, 1, 0, 1, 2);
606 
607       gtk_container_add (GTK_CONTAINER (row), grid);
608       gtk_container_add (GTK_CONTAINER (priv->recent_servers_listbox), row);
609 
610       /* custom data */
611       data = g_new0 (RemoveServerData, 1);
612       data->view = view;
613       data->uri = dup_uri;
614 
615       g_object_set_data_full (G_OBJECT (row), "uri", dup_uri, g_free);
616       g_object_set_data_full (G_OBJECT (row), "remove-server-data", data, g_free);
617 
618       g_signal_connect_swapped (button,
619                                 "clicked",
620                                 G_CALLBACK (on_remove_server_button_clicked),
621                                 data);
622 
623       gtk_widget_show_all (row);
624 
625       g_free (name);
626     }
627 
628   g_strfreev (uris);
629   g_bookmark_file_free (server_list);
630 }
631 
632 static void
update_view_mode(GtkPlacesView * view)633 update_view_mode (GtkPlacesView *view)
634 {
635   GtkPlacesViewPrivate *priv;
636   GList *children;
637   GList *l;
638   gboolean show_listbox;
639 
640   priv = gtk_places_view_get_instance_private (view);
641   show_listbox = FALSE;
642 
643   /* drives */
644   children = gtk_container_get_children (GTK_CONTAINER (priv->listbox));
645 
646   for (l = children; l; l = l->next)
647     {
648       /* GtkListBox filter rows by changing their GtkWidget::child-visible property */
649       if (gtk_widget_get_child_visible (l->data))
650         {
651           show_listbox = TRUE;
652           break;
653         }
654     }
655 
656   g_list_free (children);
657 
658   if (!show_listbox &&
659       priv->search_query &&
660       priv->search_query[0] != '\0')
661     {
662         gtk_stack_set_visible_child_name (GTK_STACK (priv->stack), "empty-search");
663     }
664   else
665     {
666       gtk_stack_set_visible_child_name (GTK_STACK (priv->stack), "browse");
667     }
668 }
669 
670 static void
insert_row(GtkPlacesView * view,GtkWidget * row,gboolean is_network)671 insert_row (GtkPlacesView *view,
672             GtkWidget     *row,
673             gboolean       is_network)
674 {
675   GtkPlacesViewPrivate *priv;
676 
677   priv = gtk_places_view_get_instance_private (view);
678 
679   g_object_set_data (G_OBJECT (row), "is-network", GINT_TO_POINTER (is_network));
680 
681   g_signal_connect_swapped (gtk_places_view_row_get_event_box (GTK_PLACES_VIEW_ROW (row)),
682                             "button-press-event",
683                             G_CALLBACK (on_button_press_event),
684                             row);
685 
686   g_signal_connect (row,
687                     "popup-menu",
688                     G_CALLBACK (on_row_popup_menu),
689                     row);
690 
691   g_signal_connect (gtk_places_view_row_get_eject_button (GTK_PLACES_VIEW_ROW (row)),
692                     "clicked",
693                     G_CALLBACK (on_eject_button_clicked),
694                     row);
695 
696   gtk_places_view_row_set_path_size_group (GTK_PLACES_VIEW_ROW (row), priv->path_size_group);
697   gtk_places_view_row_set_space_size_group (GTK_PLACES_VIEW_ROW (row), priv->space_size_group);
698 
699   gtk_container_add (GTK_CONTAINER (priv->listbox), row);
700 }
701 
702 static void
add_volume(GtkPlacesView * view,GVolume * volume)703 add_volume (GtkPlacesView *view,
704             GVolume       *volume)
705 {
706   gboolean is_network;
707   GMount *mount;
708   GFile *root;
709   GIcon *icon;
710   gchar *identifier;
711   gchar *name;
712   gchar *path;
713 
714   if (is_external_volume (volume))
715     return;
716 
717   identifier = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_CLASS);
718   is_network = g_strcmp0 (identifier, "network") == 0;
719 
720   mount = g_volume_get_mount (volume);
721   root = mount ? g_mount_get_default_location (mount) : NULL;
722   icon = g_volume_get_icon (volume);
723   name = g_volume_get_name (volume);
724   path = !is_network ? g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE) : NULL;
725 
726   if (!mount || !g_mount_is_shadowed (mount))
727     {
728       GtkWidget *row;
729 
730       row = g_object_new (GTK_TYPE_PLACES_VIEW_ROW,
731                           "icon", icon,
732                           "name", name,
733                           "path", path ? path : "",
734                           "volume", volume,
735                           "mount", mount,
736                           "file", NULL,
737                           "is-network", is_network,
738                           NULL);
739 
740       insert_row (view, row, is_network);
741     }
742 
743   g_clear_object (&root);
744   g_clear_object (&icon);
745   g_clear_object (&mount);
746   g_free (identifier);
747   g_free (name);
748   g_free (path);
749 }
750 
751 static void
add_mount(GtkPlacesView * view,GMount * mount)752 add_mount (GtkPlacesView *view,
753            GMount        *mount)
754 {
755   gboolean is_network;
756   GFile *root;
757   GIcon *icon;
758   gchar *name;
759   gchar *path;
760   gchar *uri;
761   gchar *schema;
762 
763   icon = g_mount_get_icon (mount);
764   name = g_mount_get_name (mount);
765   root = g_mount_get_default_location (mount);
766   path = root ? g_file_get_parse_name (root) : NULL;
767   uri = g_file_get_uri (root);
768   schema = g_uri_parse_scheme (uri);
769   is_network = g_strcmp0 (schema, "file") != 0;
770 
771   if (is_network)
772     g_clear_pointer (&path, g_free);
773 
774   if (!g_mount_is_shadowed (mount))
775     {
776       GtkWidget *row;
777 
778       row = g_object_new (GTK_TYPE_PLACES_VIEW_ROW,
779                           "icon", icon,
780                           "name", name,
781                           "path", path ? path : "",
782                           "volume", NULL,
783                           "mount", mount,
784                           "file", NULL,
785                           "is-network", is_network,
786                           NULL);
787 
788       insert_row (view, row, is_network);
789     }
790 
791   g_clear_object (&root);
792   g_clear_object (&icon);
793   g_free (name);
794   g_free (path);
795   g_free (uri);
796   g_free (schema);
797 }
798 
799 static void
add_drive(GtkPlacesView * view,GDrive * drive)800 add_drive (GtkPlacesView *view,
801            GDrive        *drive)
802 {
803   GList *volumes;
804   GList *l;
805 
806   volumes = g_drive_get_volumes (drive);
807 
808   for (l = volumes; l != NULL; l = l->next)
809     add_volume (view, l->data);
810 
811   g_list_free_full (volumes, g_object_unref);
812 }
813 
814 static void
add_file(GtkPlacesView * view,GFile * file,GIcon * icon,const gchar * display_name,const gchar * path,gboolean is_network)815 add_file (GtkPlacesView *view,
816           GFile         *file,
817           GIcon         *icon,
818           const gchar   *display_name,
819           const gchar   *path,
820           gboolean       is_network)
821 {
822   GtkWidget *row;
823   row = g_object_new (GTK_TYPE_PLACES_VIEW_ROW,
824                       "icon", icon,
825                       "name", display_name,
826                       "path", path,
827                       "volume", NULL,
828                       "mount", NULL,
829                       "file", file,
830                       "is_network", is_network,
831                       NULL);
832 
833   insert_row (view, row, is_network);
834 }
835 
836 static gboolean
has_networks(GtkPlacesView * view)837 has_networks (GtkPlacesView *view)
838 {
839   GList *l;
840   GtkPlacesViewPrivate *priv;
841   GList *children;
842   gboolean has_network = FALSE;
843 
844   priv = gtk_places_view_get_instance_private (view);
845 
846   children = gtk_container_get_children (GTK_CONTAINER (priv->listbox));
847   for (l = children; l != NULL; l = l->next)
848     {
849       if (GPOINTER_TO_INT (g_object_get_data (l->data, "is-network")) == TRUE &&
850           g_object_get_data (l->data, "is-placeholder") == NULL)
851       {
852         has_network = TRUE;
853         break;
854       }
855     }
856 
857   g_list_free (children);
858 
859   return has_network;
860 }
861 
862 static void
update_network_state(GtkPlacesView * view)863 update_network_state (GtkPlacesView *view)
864 {
865   GtkPlacesViewPrivate *priv;
866 
867   priv = gtk_places_view_get_instance_private (view);
868 
869   if (priv->network_placeholder == NULL)
870     {
871       priv->network_placeholder = gtk_list_box_row_new ();
872       priv->network_placeholder_label = gtk_label_new ("");
873       gtk_label_set_xalign (GTK_LABEL (priv->network_placeholder_label), 0.0);
874       gtk_widget_set_margin_start (priv->network_placeholder_label, 12);
875       gtk_widget_set_margin_end (priv->network_placeholder_label, 12);
876       gtk_widget_set_margin_top (priv->network_placeholder_label, 6);
877       gtk_widget_set_margin_bottom (priv->network_placeholder_label, 6);
878       gtk_widget_set_hexpand (priv->network_placeholder_label, TRUE);
879       gtk_widget_set_sensitive (priv->network_placeholder, FALSE);
880       gtk_container_add (GTK_CONTAINER (priv->network_placeholder),
881                          priv->network_placeholder_label);
882       g_object_set_data (G_OBJECT (priv->network_placeholder),
883                          "is-network", GINT_TO_POINTER (TRUE));
884       /* mark the row as placeholder, so it always goes first */
885       g_object_set_data (G_OBJECT (priv->network_placeholder),
886                          "is-placeholder", GINT_TO_POINTER (TRUE));
887       gtk_container_add (GTK_CONTAINER (priv->listbox), priv->network_placeholder);
888     }
889 
890   if (gtk_places_view_get_fetching_networks (view))
891     {
892       /* only show a placeholder with a message if the list is empty.
893        * otherwise just show the spinner in the header */
894       if (!has_networks (view))
895         {
896           gtk_widget_show_all (priv->network_placeholder);
897           gtk_label_set_text (GTK_LABEL (priv->network_placeholder_label),
898                               _("Searching for network locations"));
899         }
900     }
901   else if (!has_networks (view))
902     {
903       gtk_widget_show_all (priv->network_placeholder);
904       gtk_label_set_text (GTK_LABEL (priv->network_placeholder_label),
905                           _("No network locations found"));
906     }
907   else
908     {
909       gtk_widget_hide (priv->network_placeholder);
910     }
911 }
912 
913 static void
monitor_network(GtkPlacesView * self)914 monitor_network (GtkPlacesView *self)
915 {
916   GtkPlacesViewPrivate *priv;
917   GFile *network_file;
918   GError *error;
919 
920   priv = gtk_places_view_get_instance_private (self);
921 
922   if (priv->network_monitor)
923     return;
924 
925   error = NULL;
926   network_file = g_file_new_for_uri ("network:///");
927   priv->network_monitor = g_file_monitor (network_file,
928                                           G_FILE_MONITOR_NONE,
929                                           NULL,
930                                           &error);
931 
932   g_clear_object (&network_file);
933 
934   if (error)
935     {
936       g_warning ("Error monitoring network: %s", error->message);
937       g_clear_error (&error);
938       return;
939     }
940 
941   g_signal_connect_swapped (priv->network_monitor,
942                             "changed",
943                             G_CALLBACK (update_places),
944                             self);
945 }
946 
947 static void
populate_networks(GtkPlacesView * view,GFileEnumerator * enumerator,GList * detected_networks)948 populate_networks (GtkPlacesView   *view,
949                    GFileEnumerator *enumerator,
950                    GList           *detected_networks)
951 {
952   GList *l;
953   GFile *file;
954   GFile *activatable_file;
955   gchar *uri;
956   GFileType type;
957   GIcon *icon;
958   gchar *display_name;
959 
960   for (l = detected_networks; l != NULL; l = l->next)
961     {
962       file = g_file_enumerator_get_child (enumerator, l->data);
963       type = g_file_info_get_file_type (l->data);
964       if (type == G_FILE_TYPE_SHORTCUT || type == G_FILE_TYPE_MOUNTABLE)
965         uri = g_file_info_get_attribute_as_string (l->data, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI);
966       else
967         uri = g_file_get_uri (file);
968       activatable_file = g_file_new_for_uri (uri);
969       display_name = g_file_info_get_attribute_as_string (l->data, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME);
970       icon = g_file_info_get_icon (l->data);
971 
972       add_file (view, activatable_file, icon, display_name, NULL, TRUE);
973 
974       g_free (uri);
975       g_free (display_name);
976       g_clear_object (&file);
977       g_clear_object (&activatable_file);
978     }
979 }
980 
981 static void
network_enumeration_next_files_finished(GObject * source_object,GAsyncResult * res,gpointer user_data)982 network_enumeration_next_files_finished (GObject      *source_object,
983                                          GAsyncResult *res,
984                                          gpointer      user_data)
985 {
986   GtkPlacesViewPrivate *priv;
987   GtkPlacesView *view;
988   GList *detected_networks;
989   GError *error;
990 
991   view = GTK_PLACES_VIEW (user_data);
992   priv = gtk_places_view_get_instance_private (view);
993   error = NULL;
994 
995   detected_networks = g_file_enumerator_next_files_finish (G_FILE_ENUMERATOR (source_object),
996                                                            res, &error);
997 
998   if (error)
999     {
1000       if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
1001         g_warning ("Failed to fetch network locations: %s", error->message);
1002 
1003       g_clear_error (&error);
1004     }
1005   else
1006     {
1007       gtk_places_view_set_fetching_networks (view, FALSE);
1008       populate_networks (view, G_FILE_ENUMERATOR (source_object), detected_networks);
1009 
1010       g_list_free_full (detected_networks, g_object_unref);
1011     }
1012 
1013   g_object_unref (view);
1014 
1015   /* avoid to update widgets if we are already destroyed
1016      (and got cancelled s a result of that) */
1017   if (!priv->destroyed)
1018     {
1019       update_network_state (view);
1020       monitor_network (view);
1021       update_loading (view);
1022     }
1023 }
1024 
1025 static void
network_enumeration_finished(GObject * source_object,GAsyncResult * res,gpointer user_data)1026 network_enumeration_finished (GObject      *source_object,
1027                               GAsyncResult *res,
1028                               gpointer      user_data)
1029 {
1030   GtkPlacesViewPrivate *priv;
1031   GFileEnumerator *enumerator;
1032   GError *error;
1033 
1034   error = NULL;
1035   enumerator = g_file_enumerate_children_finish (G_FILE (source_object), res, &error);
1036 
1037   if (error)
1038     {
1039       if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) &&
1040           !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED))
1041         g_warning ("Failed to fetch network locations: %s", error->message);
1042 
1043       g_clear_error (&error);
1044       g_object_unref (GTK_PLACES_VIEW (user_data));
1045     }
1046   else
1047     {
1048       priv = gtk_places_view_get_instance_private (GTK_PLACES_VIEW (user_data));
1049       g_file_enumerator_next_files_async (enumerator,
1050                                           G_MAXINT32,
1051                                           G_PRIORITY_DEFAULT,
1052                                           priv->networks_fetching_cancellable,
1053                                           network_enumeration_next_files_finished,
1054                                           user_data);
1055       g_object_unref (enumerator);
1056     }
1057 }
1058 
1059 static void
fetch_networks(GtkPlacesView * view)1060 fetch_networks (GtkPlacesView *view)
1061 {
1062   GtkPlacesViewPrivate *priv;
1063   GFile *network_file;
1064   const gchar * const *supported_uris;
1065   gboolean found;
1066 
1067   priv = gtk_places_view_get_instance_private (view);
1068   supported_uris = g_vfs_get_supported_uri_schemes (g_vfs_get_default ());
1069 
1070   for (found = FALSE; !found && supported_uris && supported_uris[0]; supported_uris++)
1071     if (g_strcmp0 (supported_uris[0], "network") == 0)
1072       found = TRUE;
1073 
1074   if (!found)
1075     return;
1076 
1077   network_file = g_file_new_for_uri ("network:///");
1078 
1079   g_cancellable_cancel (priv->networks_fetching_cancellable);
1080   g_clear_object (&priv->networks_fetching_cancellable);
1081   priv->networks_fetching_cancellable = g_cancellable_new ();
1082   gtk_places_view_set_fetching_networks (view, TRUE);
1083   update_network_state (view);
1084 
1085   g_object_ref (view);
1086   g_file_enumerate_children_async (network_file,
1087                                    "standard::type,standard::target-uri,standard::name,standard::display-name,standard::icon",
1088                                    G_FILE_QUERY_INFO_NONE,
1089                                    G_PRIORITY_DEFAULT,
1090                                    priv->networks_fetching_cancellable,
1091                                    network_enumeration_finished,
1092                                    view);
1093 
1094   g_clear_object (&network_file);
1095 }
1096 
1097 static void
update_places(GtkPlacesView * view)1098 update_places (GtkPlacesView *view)
1099 {
1100   GtkPlacesViewPrivate *priv;
1101   GList *children;
1102   GList *mounts;
1103   GList *volumes;
1104   GList *drives;
1105   GList *l;
1106   GIcon *icon;
1107   GFile *file;
1108 
1109   priv = gtk_places_view_get_instance_private (view);
1110 
1111   /* Clear all previously added items */
1112   children = gtk_container_get_children (GTK_CONTAINER (priv->listbox));
1113   g_list_free_full (children, (GDestroyNotify) gtk_widget_destroy);
1114   priv->network_placeholder = NULL;
1115   /* Inform clients that we started loading */
1116   gtk_places_view_set_loading (view, TRUE);
1117 
1118   /* Add "Computer" row */
1119   file = g_file_new_for_path ("/");
1120   icon = g_themed_icon_new_with_default_fallbacks ("drive-harddisk");
1121 
1122   add_file (view, file, icon, _("Computer"), "/", FALSE);
1123 
1124   g_clear_object (&file);
1125   g_clear_object (&icon);
1126 
1127   /* Add currently connected drives */
1128   drives = g_volume_monitor_get_connected_drives (priv->volume_monitor);
1129 
1130   for (l = drives; l != NULL; l = l->next)
1131     add_drive (view, l->data);
1132 
1133   g_list_free_full (drives, g_object_unref);
1134 
1135   /*
1136    * Since all volumes with an associated GDrive were already added with
1137    * add_drive before, add all volumes that aren't associated with a
1138    * drive.
1139    */
1140   volumes = g_volume_monitor_get_volumes (priv->volume_monitor);
1141 
1142   for (l = volumes; l != NULL; l = l->next)
1143     {
1144       GVolume *volume;
1145       GDrive *drive;
1146 
1147       volume = l->data;
1148       drive = g_volume_get_drive (volume);
1149 
1150       if (drive)
1151         {
1152           g_object_unref (drive);
1153           continue;
1154         }
1155 
1156       add_volume (view, volume);
1157     }
1158 
1159   g_list_free_full (volumes, g_object_unref);
1160 
1161   /*
1162    * Now that all necessary drives and volumes were already added, add mounts
1163    * that have no volume, such as /etc/mtab mounts, ftp, sftp, etc.
1164    */
1165   mounts = g_volume_monitor_get_mounts (priv->volume_monitor);
1166 
1167   for (l = mounts; l != NULL; l = l->next)
1168     {
1169       GMount *mount;
1170       GVolume *volume;
1171 
1172       mount = l->data;
1173       volume = g_mount_get_volume (mount);
1174 
1175       if (volume)
1176         {
1177           g_object_unref (volume);
1178           continue;
1179         }
1180 
1181       add_mount (view, mount);
1182     }
1183 
1184   g_list_free_full (mounts, g_object_unref);
1185 
1186   /* load saved servers */
1187   populate_servers (view);
1188 
1189   /* fetch networks and add them asynchronously */
1190   fetch_networks (view);
1191 
1192   update_view_mode (view);
1193   /* Check whether we still are in a loading state */
1194   update_loading (view);
1195 }
1196 
1197 static void
server_mount_ready_cb(GObject * source_file,GAsyncResult * res,gpointer user_data)1198 server_mount_ready_cb (GObject      *source_file,
1199                        GAsyncResult *res,
1200                        gpointer      user_data)
1201 {
1202   GtkPlacesViewPrivate *priv;
1203   GtkPlacesView *view;
1204   gboolean should_show;
1205   GError *error;
1206   GFile *location;
1207 
1208   location = G_FILE (source_file);
1209   should_show = TRUE;
1210   error = NULL;
1211 
1212   view = GTK_PLACES_VIEW (user_data);
1213 
1214   g_file_mount_enclosing_volume_finish (location, res, &error);
1215   if (error)
1216     {
1217       should_show = FALSE;
1218 
1219       if (error->code == G_IO_ERROR_ALREADY_MOUNTED)
1220         {
1221           /*
1222            * Already mounted volume is not a critical error
1223            * and we can still continue with the operation.
1224            */
1225           should_show = TRUE;
1226         }
1227       else if (error->domain != G_IO_ERROR ||
1228                (error->code != G_IO_ERROR_CANCELLED &&
1229                 error->code != G_IO_ERROR_FAILED_HANDLED))
1230         {
1231           /* if it wasn't cancelled show a dialog */
1232           emit_show_error_message (view, _("Unable to access location"), error->message);
1233         }
1234 
1235       /* The operation got cancelled by the user and or the error
1236          has been handled already. */
1237       g_clear_error (&error);
1238     }
1239 
1240   priv = gtk_places_view_get_instance_private (view);
1241 
1242   if (priv->destroyed) {
1243     g_object_unref (view);
1244     return;
1245   }
1246 
1247   priv->should_pulse_entry = FALSE;
1248 
1249   /* Restore from Cancel to Connect */
1250   gtk_button_set_label (GTK_BUTTON (priv->connect_button), _("Con_nect"));
1251   gtk_widget_set_sensitive (priv->address_entry, TRUE);
1252   priv->connecting_to_server = FALSE;
1253 
1254   if (should_show)
1255     {
1256       server_list_add_server (view, location);
1257 
1258       /*
1259        * Only clear the entry if it successfully connects to the server.
1260        * Otherwise, the user would lost the typed address even if it fails
1261        * to connect.
1262        */
1263       gtk_entry_set_text (GTK_ENTRY (priv->address_entry), "");
1264 
1265       if (priv->should_open_location)
1266         {
1267           GMount *mount;
1268           GFile *root;
1269 
1270           /*
1271            * If the mount is not found at this point, it is probably user-
1272            * invisible, which happens e.g for smb-browse, but the location
1273            * should be opened anyway...
1274            */
1275           mount = g_file_find_enclosing_mount (location, priv->cancellable, NULL);
1276           if (mount)
1277             {
1278               root = g_mount_get_default_location (mount);
1279 
1280               emit_open_location (view, root, priv->open_flags);
1281 
1282               g_object_unref (root);
1283               g_object_unref (mount);
1284             }
1285           else
1286             {
1287               emit_open_location (view, location, priv->open_flags);
1288             }
1289         }
1290     }
1291 
1292   update_places (view);
1293   g_object_unref (view);
1294 }
1295 
1296 static void
volume_mount_ready_cb(GObject * source_volume,GAsyncResult * res,gpointer user_data)1297 volume_mount_ready_cb (GObject      *source_volume,
1298                        GAsyncResult *res,
1299                        gpointer      user_data)
1300 {
1301   GtkPlacesViewPrivate *priv;
1302   GtkPlacesView *view;
1303   gboolean should_show;
1304   GVolume *volume;
1305   GError *error;
1306 
1307   volume = G_VOLUME (source_volume);
1308   should_show = TRUE;
1309   error = NULL;
1310 
1311   g_volume_mount_finish (volume, res, &error);
1312 
1313   if (error)
1314     {
1315       should_show = FALSE;
1316 
1317       if (error->code == G_IO_ERROR_ALREADY_MOUNTED)
1318         {
1319           /*
1320            * If the volume was already mounted, it's not a hard error
1321            * and we can still continue with the operation.
1322            */
1323           should_show = TRUE;
1324         }
1325       else if (error->domain != G_IO_ERROR ||
1326                (error->code != G_IO_ERROR_CANCELLED &&
1327                 error->code != G_IO_ERROR_FAILED_HANDLED))
1328         {
1329           /* if it wasn't cancelled show a dialog */
1330           emit_show_error_message (GTK_PLACES_VIEW (user_data), _("Unable to access location"), error->message);
1331           should_show = FALSE;
1332         }
1333 
1334       /* The operation got cancelled by the user and or the error
1335          has been handled already. */
1336       g_clear_error (&error);
1337     }
1338 
1339   view = GTK_PLACES_VIEW (user_data);
1340   priv = gtk_places_view_get_instance_private (view);
1341 
1342   if (priv->destroyed)
1343     {
1344       g_object_unref(view);
1345       return;
1346     }
1347 
1348   priv->mounting_volume = FALSE;
1349   update_loading (view);
1350 
1351   if (should_show)
1352     {
1353       GMount *mount;
1354       GFile *root;
1355 
1356       mount = g_volume_get_mount (volume);
1357       root = g_mount_get_default_location (mount);
1358 
1359       if (priv->should_open_location)
1360         emit_open_location (GTK_PLACES_VIEW (user_data), root, priv->open_flags);
1361 
1362       g_object_unref (mount);
1363       g_object_unref (root);
1364     }
1365 
1366   update_places (view);
1367   g_object_unref (view);
1368 }
1369 
1370 static void
unmount_ready_cb(GObject * source_mount,GAsyncResult * res,gpointer user_data)1371 unmount_ready_cb (GObject      *source_mount,
1372                   GAsyncResult *res,
1373                   gpointer      user_data)
1374 {
1375   GtkPlacesView *view;
1376   GtkPlacesViewPrivate *priv;
1377   GMount *mount;
1378   GError *error;
1379 
1380   view = GTK_PLACES_VIEW (user_data);
1381   mount = G_MOUNT (source_mount);
1382   error = NULL;
1383 
1384   g_mount_unmount_with_operation_finish (mount, res, &error);
1385 
1386   if (error)
1387     {
1388       if (error->domain != G_IO_ERROR ||
1389           (error->code != G_IO_ERROR_CANCELLED &&
1390            error->code != G_IO_ERROR_FAILED_HANDLED))
1391         {
1392           /* if it wasn't cancelled show a dialog */
1393           emit_show_error_message (view, _("Unable to unmount volume"), error->message);
1394         }
1395 
1396       g_clear_error (&error);
1397     }
1398 
1399   priv = gtk_places_view_get_instance_private (view);
1400 
1401   if (priv->destroyed) {
1402     g_object_unref (view);
1403     return;
1404   }
1405 
1406   priv->unmounting_mount = FALSE;
1407   update_loading (view);
1408 
1409   g_object_unref (view);
1410 }
1411 
1412 static gboolean
pulse_entry_cb(gpointer user_data)1413 pulse_entry_cb (gpointer user_data)
1414 {
1415   GtkPlacesViewPrivate *priv;
1416 
1417   priv = gtk_places_view_get_instance_private (GTK_PLACES_VIEW (user_data));
1418 
1419   if (priv->destroyed)
1420     {
1421       priv->entry_pulse_timeout_id = 0;
1422 
1423       return G_SOURCE_REMOVE;
1424     }
1425   else if (priv->should_pulse_entry)
1426     {
1427       gtk_entry_progress_pulse (GTK_ENTRY (priv->address_entry));
1428 
1429       return G_SOURCE_CONTINUE;
1430     }
1431   else
1432     {
1433       gtk_entry_set_progress_pulse_step (GTK_ENTRY (priv->address_entry), 0.0);
1434       gtk_entry_set_progress_fraction (GTK_ENTRY (priv->address_entry), 0.0);
1435       priv->entry_pulse_timeout_id = 0;
1436 
1437       return G_SOURCE_REMOVE;
1438     }
1439 }
1440 
1441 static void
unmount_mount(GtkPlacesView * view,GMount * mount)1442 unmount_mount (GtkPlacesView *view,
1443                GMount        *mount)
1444 {
1445   GtkPlacesViewPrivate *priv;
1446   GMountOperation *operation;
1447   GtkWidget *toplevel;
1448 
1449   priv = gtk_places_view_get_instance_private (view);
1450   toplevel = gtk_widget_get_toplevel (GTK_WIDGET (view));
1451 
1452   g_cancellable_cancel (priv->cancellable);
1453   g_clear_object (&priv->cancellable);
1454   priv->cancellable = g_cancellable_new ();
1455 
1456   priv->unmounting_mount = TRUE;
1457   update_loading (view);
1458 
1459   g_object_ref (view);
1460 
1461   operation = gtk_mount_operation_new (GTK_WINDOW (toplevel));
1462   g_mount_unmount_with_operation (mount,
1463                                   0,
1464                                   operation,
1465                                   priv->cancellable,
1466                                   unmount_ready_cb,
1467                                   view);
1468   g_object_unref (operation);
1469 }
1470 
1471 static void
mount_server(GtkPlacesView * view,GFile * location)1472 mount_server (GtkPlacesView *view,
1473               GFile         *location)
1474 {
1475   GtkPlacesViewPrivate *priv;
1476   GMountOperation *operation;
1477   GtkWidget *toplevel;
1478 
1479   priv = gtk_places_view_get_instance_private (view);
1480 
1481   g_cancellable_cancel (priv->cancellable);
1482   g_clear_object (&priv->cancellable);
1483   /* User cliked when the operation was ongoing, so wanted to cancel it */
1484   if (priv->connecting_to_server)
1485     return;
1486 
1487   priv->cancellable = g_cancellable_new ();
1488   toplevel = gtk_widget_get_toplevel (GTK_WIDGET (view));
1489   operation = gtk_mount_operation_new (GTK_WINDOW (toplevel));
1490 
1491   priv->should_pulse_entry = TRUE;
1492   gtk_entry_set_progress_pulse_step (GTK_ENTRY (priv->address_entry), 0.1);
1493   /* Allow to cancel the operation */
1494   gtk_button_set_label (GTK_BUTTON (priv->connect_button), _("Cance_l"));
1495   gtk_widget_set_sensitive (priv->address_entry, FALSE);
1496   priv->connecting_to_server = TRUE;
1497   update_loading (view);
1498 
1499   if (priv->entry_pulse_timeout_id == 0)
1500     priv->entry_pulse_timeout_id = g_timeout_add (100, (GSourceFunc) pulse_entry_cb, view);
1501 
1502   g_mount_operation_set_password_save (operation, G_PASSWORD_SAVE_FOR_SESSION);
1503 
1504   /* make sure we keep the view around for as long as we are running */
1505   g_object_ref (view);
1506 
1507   g_file_mount_enclosing_volume (location,
1508                                  0,
1509                                  operation,
1510                                  priv->cancellable,
1511                                  server_mount_ready_cb,
1512                                  view);
1513 
1514   /* unref operation here - g_file_mount_enclosing_volume() does ref for itself */
1515   g_object_unref (operation);
1516 }
1517 
1518 static void
mount_volume(GtkPlacesView * view,GVolume * volume)1519 mount_volume (GtkPlacesView *view,
1520               GVolume       *volume)
1521 {
1522   GtkPlacesViewPrivate *priv;
1523   GMountOperation *operation;
1524   GtkWidget *toplevel;
1525 
1526   priv = gtk_places_view_get_instance_private (view);
1527   toplevel = gtk_widget_get_toplevel (GTK_WIDGET (view));
1528   operation = gtk_mount_operation_new (GTK_WINDOW (toplevel));
1529 
1530   g_cancellable_cancel (priv->cancellable);
1531   g_clear_object (&priv->cancellable);
1532   priv->cancellable = g_cancellable_new ();
1533 
1534   priv->mounting_volume = TRUE;
1535   update_loading (view);
1536 
1537   g_mount_operation_set_password_save (operation, G_PASSWORD_SAVE_FOR_SESSION);
1538 
1539   /* make sure we keep the view around for as long as we are running */
1540   g_object_ref (view);
1541 
1542   g_volume_mount (volume,
1543                   0,
1544                   operation,
1545                   priv->cancellable,
1546                   volume_mount_ready_cb,
1547                   view);
1548 
1549   /* unref operation here - g_file_mount_enclosing_volume() does ref for itself */
1550   g_object_unref (operation);
1551 }
1552 
1553 /* Callback used when the file list's popup menu is detached */
1554 static void
popup_menu_detach_cb(GtkWidget * attach_widget,GtkMenu * menu)1555 popup_menu_detach_cb (GtkWidget *attach_widget,
1556                       GtkMenu   *menu)
1557 {
1558   GtkPlacesViewPrivate *priv;
1559 
1560   priv = gtk_places_view_get_instance_private (GTK_PLACES_VIEW (attach_widget));
1561   priv->popup_menu = NULL;
1562 }
1563 
1564 static void
open_cb(GtkMenuItem * item,GtkPlacesViewRow * row)1565 open_cb (GtkMenuItem      *item,
1566          GtkPlacesViewRow *row)
1567 {
1568   GtkPlacesView *self;
1569 
1570   self = GTK_PLACES_VIEW (gtk_widget_get_ancestor (GTK_WIDGET (row), GTK_TYPE_PLACES_VIEW));
1571   activate_row (self, row, GTK_PLACES_OPEN_NORMAL);
1572 }
1573 
1574 static void
open_in_new_tab_cb(GtkMenuItem * item,GtkPlacesViewRow * row)1575 open_in_new_tab_cb (GtkMenuItem      *item,
1576                     GtkPlacesViewRow *row)
1577 {
1578   GtkPlacesView *self;
1579 
1580   self = GTK_PLACES_VIEW (gtk_widget_get_ancestor (GTK_WIDGET (row), GTK_TYPE_PLACES_VIEW));
1581   activate_row (self, row, GTK_PLACES_OPEN_NEW_TAB);
1582 }
1583 
1584 static void
open_in_new_window_cb(GtkMenuItem * item,GtkPlacesViewRow * row)1585 open_in_new_window_cb (GtkMenuItem      *item,
1586                        GtkPlacesViewRow *row)
1587 {
1588   GtkPlacesView *self;
1589 
1590   self = GTK_PLACES_VIEW (gtk_widget_get_ancestor (GTK_WIDGET (row), GTK_TYPE_PLACES_VIEW));
1591   activate_row (self, row, GTK_PLACES_OPEN_NEW_WINDOW);
1592 }
1593 
1594 static void
mount_cb(GtkMenuItem * item,GtkPlacesViewRow * row)1595 mount_cb (GtkMenuItem      *item,
1596           GtkPlacesViewRow *row)
1597 {
1598   GtkPlacesViewPrivate *priv;
1599   GtkWidget *view;
1600   GVolume *volume;
1601 
1602   view = gtk_widget_get_ancestor (GTK_WIDGET (row), GTK_TYPE_PLACES_VIEW);
1603   priv = gtk_places_view_get_instance_private (GTK_PLACES_VIEW (view));
1604   volume = gtk_places_view_row_get_volume (row);
1605 
1606   /*
1607    * When the mount item is activated, it's expected that
1608    * the volume only gets mounted, without opening it after
1609    * the operation is complete.
1610    */
1611   priv->should_open_location = FALSE;
1612 
1613   gtk_places_view_row_set_busy (row, TRUE);
1614   mount_volume (GTK_PLACES_VIEW (view), volume);
1615 }
1616 
1617 static void
unmount_cb(GtkMenuItem * item,GtkPlacesViewRow * row)1618 unmount_cb (GtkMenuItem      *item,
1619             GtkPlacesViewRow *row)
1620 {
1621   GtkWidget *view;
1622   GMount *mount;
1623 
1624   view = gtk_widget_get_ancestor (GTK_WIDGET (row), GTK_TYPE_PLACES_VIEW);
1625   mount = gtk_places_view_row_get_mount (row);
1626 
1627   gtk_places_view_row_set_busy (row, TRUE);
1628 
1629   unmount_mount (GTK_PLACES_VIEW (view), mount);
1630 }
1631 
1632 static void
attach_protocol_row_to_grid(GtkGrid * grid,const gchar * protocol_name,const gchar * protocol_prefix)1633 attach_protocol_row_to_grid (GtkGrid     *grid,
1634                              const gchar *protocol_name,
1635                              const gchar *protocol_prefix)
1636 {
1637   GtkWidget *name_label;
1638   GtkWidget *prefix_label;
1639 
1640   name_label = gtk_label_new (protocol_name);
1641   gtk_widget_set_halign (name_label, GTK_ALIGN_START);
1642   gtk_grid_attach_next_to (grid, name_label, NULL, GTK_POS_BOTTOM, 1, 1);
1643 
1644   prefix_label = gtk_label_new (protocol_prefix);
1645   gtk_widget_set_halign (prefix_label, GTK_ALIGN_START);
1646   gtk_grid_attach_next_to (grid, prefix_label, name_label, GTK_POS_RIGHT, 1, 1);
1647 }
1648 
1649 static void
populate_available_protocols_grid(GtkGrid * grid)1650 populate_available_protocols_grid (GtkGrid *grid)
1651 {
1652   const gchar* const *supported_protocols;
1653 
1654   supported_protocols = g_vfs_get_supported_uri_schemes (g_vfs_get_default ());
1655 
1656   if (g_strv_contains (supported_protocols, "afp"))
1657     attach_protocol_row_to_grid (grid, _("AppleTalk"), "afp://");
1658 
1659   if (g_strv_contains (supported_protocols, "ftp"))
1660     /* Translators: do not translate ftp:// and ftps:// */
1661     attach_protocol_row_to_grid (grid, _("File Transfer Protocol"), _("ftp:// or ftps://"));
1662 
1663   if (g_strv_contains (supported_protocols, "nfs"))
1664     attach_protocol_row_to_grid (grid, _("Network File System"), "nfs://");
1665 
1666   if (g_strv_contains (supported_protocols, "smb"))
1667     attach_protocol_row_to_grid (grid, _("Samba"), "smb://");
1668 
1669   if (g_strv_contains (supported_protocols, "ssh"))
1670     /* Translators: do not translate sftp:// and ssh:// */
1671     attach_protocol_row_to_grid (grid, _("SSH File Transfer Protocol"), _("sftp:// or ssh://"));
1672 
1673   if (g_strv_contains (supported_protocols, "dav"))
1674     /* Translators: do not translate dav:// and davs:// */
1675     attach_protocol_row_to_grid (grid, _("WebDAV"), _("dav:// or davs://"));
1676 
1677   gtk_widget_show_all (GTK_WIDGET (grid));
1678 }
1679 
1680 /* Constructs the popup menu if needed */
1681 static void
build_popup_menu(GtkPlacesView * view,GtkPlacesViewRow * row)1682 build_popup_menu (GtkPlacesView    *view,
1683                   GtkPlacesViewRow *row)
1684 {
1685   GtkPlacesViewPrivate *priv;
1686   GtkWidget *item;
1687   GMount *mount;
1688   GFile *file;
1689   gboolean is_network;
1690 
1691   priv = gtk_places_view_get_instance_private (view);
1692   mount = gtk_places_view_row_get_mount (row);
1693   file = gtk_places_view_row_get_file (row);
1694   is_network = gtk_places_view_row_get_is_network (row);
1695 
1696   priv->popup_menu = gtk_menu_new ();
1697   gtk_style_context_add_class (gtk_widget_get_style_context (priv->popup_menu),
1698                                GTK_STYLE_CLASS_CONTEXT_MENU);
1699 
1700   gtk_menu_attach_to_widget (GTK_MENU (priv->popup_menu),
1701                              GTK_WIDGET (view),
1702                              popup_menu_detach_cb);
1703 
1704   /* Open item is always present */
1705   item = gtk_menu_item_new_with_mnemonic (_("_Open"));
1706   g_signal_connect (item,
1707                     "activate",
1708                     G_CALLBACK (open_cb),
1709                     row);
1710   gtk_widget_show (item);
1711   gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
1712 
1713   if (priv->open_flags & GTK_PLACES_OPEN_NEW_TAB)
1714     {
1715       item = gtk_menu_item_new_with_mnemonic (_("Open in New _Tab"));
1716       g_signal_connect (item,
1717                         "activate",
1718                         G_CALLBACK (open_in_new_tab_cb),
1719                         row);
1720       gtk_widget_show (item);
1721       gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
1722     }
1723 
1724   if (priv->open_flags & GTK_PLACES_OPEN_NEW_WINDOW)
1725     {
1726       item = gtk_menu_item_new_with_mnemonic (_("Open in New _Window"));
1727       g_signal_connect (item,
1728                         "activate",
1729                         G_CALLBACK (open_in_new_window_cb),
1730                         row);
1731       gtk_widget_show (item);
1732       gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
1733     }
1734 
1735   /*
1736    * The only item that contains a file up to now is the Computer
1737    * item, which cannot be mounted or unmounted.
1738    */
1739   if (file)
1740     return;
1741 
1742   /* Separator */
1743   item = gtk_separator_menu_item_new ();
1744   gtk_widget_show (item);
1745   gtk_menu_shell_insert (GTK_MENU_SHELL (priv->popup_menu), item, -1);
1746 
1747   /* Mount/Unmount items */
1748   if (mount)
1749     {
1750       item = gtk_menu_item_new_with_mnemonic (is_network ? _("_Disconnect") : _("_Unmount"));
1751       g_signal_connect (item,
1752                         "activate",
1753                         G_CALLBACK (unmount_cb),
1754                         row);
1755       gtk_widget_show (item);
1756       gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
1757     }
1758   else
1759     {
1760       item = gtk_menu_item_new_with_mnemonic (is_network ? _("_Connect") : _("_Mount"));
1761       g_signal_connect (item,
1762                         "activate",
1763                         G_CALLBACK (mount_cb),
1764                         row);
1765       gtk_widget_show (item);
1766       gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
1767     }
1768 }
1769 
1770 static void
popup_menu(GtkPlacesViewRow * row,GdkEventButton * event)1771 popup_menu (GtkPlacesViewRow *row,
1772             GdkEventButton   *event)
1773 {
1774   GtkPlacesViewPrivate *priv;
1775   GtkWidget *view;
1776 
1777   view = gtk_widget_get_ancestor (GTK_WIDGET (row), GTK_TYPE_PLACES_VIEW);
1778   priv = gtk_places_view_get_instance_private (GTK_PLACES_VIEW (view));
1779 
1780   g_clear_pointer (&priv->popup_menu, gtk_widget_destroy);
1781 
1782   build_popup_menu (GTK_PLACES_VIEW (view), row);
1783 
1784   gtk_menu_popup_at_pointer (GTK_MENU (priv->popup_menu), (GdkEvent *) event);
1785 }
1786 
1787 static gboolean
on_row_popup_menu(GtkPlacesViewRow * row)1788 on_row_popup_menu (GtkPlacesViewRow *row)
1789 {
1790   popup_menu (row, NULL);
1791   return TRUE;
1792 }
1793 
1794 static gboolean
on_button_press_event(GtkPlacesViewRow * row,GdkEventButton * event)1795 on_button_press_event (GtkPlacesViewRow *row,
1796                        GdkEventButton   *event)
1797 {
1798   if (row &&
1799       gdk_event_triggers_context_menu ((GdkEvent*) event) &&
1800       event->type == GDK_BUTTON_PRESS)
1801     {
1802       popup_menu (row, event);
1803 
1804       return TRUE;
1805     }
1806 
1807   return FALSE;
1808 }
1809 
1810 static gboolean
on_key_press_event(GtkWidget * widget,GdkEventKey * event,GtkPlacesView * view)1811 on_key_press_event (GtkWidget     *widget,
1812                     GdkEventKey   *event,
1813                     GtkPlacesView *view)
1814 {
1815   GtkPlacesViewPrivate *priv;
1816 
1817   priv = gtk_places_view_get_instance_private (view);
1818 
1819   if (event)
1820     {
1821       guint modifiers;
1822 
1823       modifiers = gtk_accelerator_get_default_mod_mask ();
1824 
1825       if (event->keyval == GDK_KEY_Return ||
1826           event->keyval == GDK_KEY_KP_Enter ||
1827           event->keyval == GDK_KEY_ISO_Enter ||
1828           event->keyval == GDK_KEY_space)
1829         {
1830           GtkWidget *focus_widget;
1831           GtkWindow *toplevel;
1832 
1833           priv->current_open_flags = GTK_PLACES_OPEN_NORMAL;
1834           toplevel = get_toplevel (GTK_WIDGET (view));
1835 
1836           if (!toplevel)
1837             return FALSE;
1838 
1839           focus_widget = gtk_window_get_focus (toplevel);
1840 
1841           if (!GTK_IS_PLACES_VIEW_ROW (focus_widget))
1842             return FALSE;
1843 
1844           if ((event->state & modifiers) == GDK_SHIFT_MASK)
1845             priv->current_open_flags = GTK_PLACES_OPEN_NEW_TAB;
1846           else if ((event->state & modifiers) == GDK_CONTROL_MASK)
1847             priv->current_open_flags = GTK_PLACES_OPEN_NEW_WINDOW;
1848 
1849           activate_row (view, GTK_PLACES_VIEW_ROW (focus_widget), priv->current_open_flags);
1850 
1851           return TRUE;
1852         }
1853     }
1854 
1855   return FALSE;
1856 }
1857 
1858 static void
on_eject_button_clicked(GtkWidget * widget,GtkPlacesViewRow * row)1859 on_eject_button_clicked (GtkWidget        *widget,
1860                          GtkPlacesViewRow *row)
1861 {
1862   if (row)
1863     {
1864       GtkWidget *view = gtk_widget_get_ancestor (GTK_WIDGET (row), GTK_TYPE_PLACES_VIEW);
1865 
1866       unmount_mount (GTK_PLACES_VIEW (view), gtk_places_view_row_get_mount (row));
1867     }
1868 }
1869 
1870 static void
on_connect_button_clicked(GtkPlacesView * view)1871 on_connect_button_clicked (GtkPlacesView *view)
1872 {
1873   GtkPlacesViewPrivate *priv;
1874   const gchar *uri;
1875   GFile *file;
1876 
1877   priv = gtk_places_view_get_instance_private (view);
1878   file = NULL;
1879 
1880   /*
1881    * Since the 'Connect' button is updated whenever the typed
1882    * address changes, it is sufficient to check if it's sensitive
1883    * or not, in order to determine if the given address is valid.
1884    */
1885   if (!gtk_widget_get_sensitive (priv->connect_button))
1886     return;
1887 
1888   uri = gtk_entry_get_text (GTK_ENTRY (priv->address_entry));
1889 
1890   if (uri != NULL && uri[0] != '\0')
1891     file = g_file_new_for_commandline_arg (uri);
1892 
1893   if (file)
1894     {
1895       priv->should_open_location = TRUE;
1896 
1897       mount_server (view, file);
1898     }
1899   else
1900     {
1901       emit_show_error_message (view, _("Unable to get remote server location"), NULL);
1902     }
1903 }
1904 
1905 static void
on_address_entry_text_changed(GtkPlacesView * view)1906 on_address_entry_text_changed (GtkPlacesView *view)
1907 {
1908   GtkPlacesViewPrivate *priv;
1909   const gchar* const *supported_protocols;
1910   gchar *address, *scheme;
1911   gboolean supported;
1912 
1913   priv = gtk_places_view_get_instance_private (view);
1914   supported = FALSE;
1915   supported_protocols = g_vfs_get_supported_uri_schemes (g_vfs_get_default ());
1916   address = g_strdup (gtk_entry_get_text (GTK_ENTRY (priv->address_entry)));
1917   scheme = g_uri_parse_scheme (address);
1918 
1919   if (!supported_protocols)
1920     goto out;
1921 
1922   if (!scheme)
1923     goto out;
1924 
1925   supported = g_strv_contains (supported_protocols, scheme) &&
1926               !g_strv_contains (unsupported_protocols, scheme);
1927 
1928 out:
1929   gtk_widget_set_sensitive (priv->connect_button, supported);
1930   if (scheme && !supported)
1931     gtk_style_context_add_class (gtk_widget_get_style_context (priv->address_entry),
1932                                  GTK_STYLE_CLASS_ERROR);
1933   else
1934     gtk_style_context_remove_class (gtk_widget_get_style_context (priv->address_entry),
1935                                     GTK_STYLE_CLASS_ERROR);
1936 
1937   g_free (address);
1938   g_free (scheme);
1939 }
1940 
1941 static void
on_address_entry_show_help_pressed(GtkPlacesView * view,GtkEntryIconPosition icon_pos,GdkEvent * event,GtkEntry * entry)1942 on_address_entry_show_help_pressed (GtkPlacesView        *view,
1943                                     GtkEntryIconPosition  icon_pos,
1944                                     GdkEvent             *event,
1945                                     GtkEntry             *entry)
1946 {
1947   GtkPlacesViewPrivate *priv;
1948   GdkRectangle rect;
1949 
1950   priv = gtk_places_view_get_instance_private (view);
1951 
1952   /* Setup the auxiliary popover's rectangle */
1953   gtk_entry_get_icon_area (GTK_ENTRY (priv->address_entry),
1954                            GTK_ENTRY_ICON_SECONDARY,
1955                            &rect);
1956 
1957   gtk_popover_set_pointing_to (GTK_POPOVER (priv->server_adresses_popover), &rect);
1958   gtk_widget_set_visible (priv->server_adresses_popover, TRUE);
1959 }
1960 
1961 static void
on_recent_servers_listbox_row_activated(GtkPlacesView * view,GtkPlacesViewRow * row,GtkWidget * listbox)1962 on_recent_servers_listbox_row_activated (GtkPlacesView    *view,
1963                                          GtkPlacesViewRow *row,
1964                                          GtkWidget        *listbox)
1965 {
1966   GtkPlacesViewPrivate *priv;
1967   gchar *uri;
1968 
1969   priv = gtk_places_view_get_instance_private (view);
1970   uri = g_object_get_data (G_OBJECT (row), "uri");
1971 
1972   gtk_entry_set_text (GTK_ENTRY (priv->address_entry), uri);
1973 
1974   gtk_widget_hide (priv->recent_servers_popover);
1975 }
1976 
1977 static void
on_listbox_row_activated(GtkPlacesView * view,GtkPlacesViewRow * row,GtkWidget * listbox)1978 on_listbox_row_activated (GtkPlacesView    *view,
1979                           GtkPlacesViewRow *row,
1980                           GtkWidget        *listbox)
1981 {
1982   GtkPlacesViewPrivate *priv;
1983   GdkEvent *event;
1984   guint button;
1985   GtkPlacesOpenFlags open_flags;
1986 
1987   priv = gtk_places_view_get_instance_private (view);
1988 
1989   event = gtk_get_current_event ();
1990   gdk_event_get_button (event, &button);
1991 
1992   if (gdk_event_get_event_type (event) == GDK_BUTTON_RELEASE && button == GDK_BUTTON_MIDDLE)
1993     open_flags = GTK_PLACES_OPEN_NEW_TAB;
1994   else
1995     open_flags = priv->current_open_flags;
1996 
1997   activate_row (view, row, open_flags);
1998 }
1999 
2000 static gboolean
is_mount_locally_accessible(GMount * mount)2001 is_mount_locally_accessible (GMount *mount)
2002 {
2003   GFile *base_file;
2004   gchar *path;
2005 
2006   if (mount == NULL)
2007     return FALSE;
2008 
2009   base_file = g_mount_get_root (mount);
2010 
2011   if (base_file == NULL)
2012     return FALSE;
2013 
2014   path = g_file_get_path (base_file);
2015   g_object_unref (base_file);
2016 
2017   if (path == NULL)
2018     return FALSE;
2019 
2020   g_free (path);
2021   return TRUE;
2022 }
2023 
2024 static gboolean
listbox_filter_func(GtkListBoxRow * row,gpointer user_data)2025 listbox_filter_func (GtkListBoxRow *row,
2026                      gpointer       user_data)
2027 {
2028   GtkPlacesViewPrivate *priv;
2029   gboolean is_network;
2030   gboolean is_placeholder;
2031   gboolean is_local = FALSE;
2032   gboolean retval;
2033   gboolean searching;
2034   gchar *name;
2035   gchar *path;
2036 
2037   priv = gtk_places_view_get_instance_private (GTK_PLACES_VIEW (user_data));
2038   retval = FALSE;
2039   searching = priv->search_query && priv->search_query[0] != '\0';
2040 
2041   is_network = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "is-network"));
2042   is_placeholder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "is-placeholder"));
2043 
2044   if (GTK_IS_PLACES_VIEW_ROW (row))
2045     {
2046       GtkPlacesViewRow *placesviewrow;
2047       GMount *mount;
2048 
2049       placesviewrow = GTK_PLACES_VIEW_ROW (row);
2050       g_object_get(G_OBJECT (placesviewrow), "mount", &mount, NULL);
2051 
2052       is_local = is_mount_locally_accessible (mount);
2053 
2054       g_clear_object (&mount);
2055     }
2056 
2057   if (is_network && priv->local_only && !is_local)
2058     return FALSE;
2059 
2060   if (is_placeholder && searching)
2061     return FALSE;
2062 
2063   if (!searching)
2064     return TRUE;
2065 
2066   g_object_get (row,
2067                 "name", &name,
2068                 "path", &path,
2069                 NULL);
2070 
2071   if (name)
2072     retval |= strstr (name, priv->search_query) != NULL;
2073 
2074   if (path)
2075     retval |= strstr (path, priv->search_query) != NULL;
2076 
2077   g_free (name);
2078   g_free (path);
2079 
2080   return retval;
2081 }
2082 
2083 static void
listbox_header_func(GtkListBoxRow * row,GtkListBoxRow * before,gpointer user_data)2084 listbox_header_func (GtkListBoxRow *row,
2085                      GtkListBoxRow *before,
2086                      gpointer       user_data)
2087 {
2088   gboolean row_is_network;
2089   gchar *text;
2090 
2091   text = NULL;
2092   row_is_network = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "is-network"));
2093 
2094   if (!before)
2095     {
2096       text = g_strdup_printf ("<b>%s</b>", row_is_network ? _("Networks") : _("On This Computer"));
2097     }
2098   else
2099     {
2100       gboolean before_is_network;
2101 
2102       before_is_network = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (before), "is-network"));
2103 
2104       if (before_is_network != row_is_network)
2105         text = g_strdup_printf ("<b>%s</b>", row_is_network ? _("Networks") : _("On This Computer"));
2106     }
2107 
2108   if (text)
2109     {
2110       GtkWidget *header;
2111       GtkWidget *label;
2112       GtkWidget *separator;
2113 
2114       header = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
2115       gtk_widget_set_margin_top (header, 6);
2116 
2117       separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
2118 
2119       label = g_object_new (GTK_TYPE_LABEL,
2120                             "use_markup", TRUE,
2121                             "margin-start", 12,
2122                             "label", text,
2123                             "xalign", 0.0f,
2124                             NULL);
2125       if (row_is_network)
2126         {
2127           GtkWidget *header_name;
2128           GtkWidget *network_header_spinner;
2129 
2130           g_object_set (label,
2131                         "margin-end", 6,
2132                         NULL);
2133 
2134           header_name = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
2135           network_header_spinner = gtk_spinner_new ();
2136           g_object_set (network_header_spinner,
2137                         "margin-end", 12,
2138                         NULL);
2139           g_object_bind_property (GTK_PLACES_VIEW (user_data),
2140                                   "fetching-networks",
2141                                   network_header_spinner,
2142                                   "active",
2143                                   G_BINDING_SYNC_CREATE);
2144 
2145           gtk_container_add (GTK_CONTAINER (header_name), label);
2146           gtk_container_add (GTK_CONTAINER (header_name), network_header_spinner);
2147           gtk_container_add (GTK_CONTAINER (header), header_name);
2148         }
2149       else
2150         {
2151           g_object_set (label,
2152                         "hexpand", TRUE,
2153                         "margin-end", 12,
2154                         NULL);
2155           gtk_container_add (GTK_CONTAINER (header), label);
2156         }
2157 
2158       gtk_container_add (GTK_CONTAINER (header), separator);
2159       gtk_widget_show_all (header);
2160 
2161       gtk_list_box_row_set_header (row, header);
2162 
2163       g_free (text);
2164     }
2165   else
2166     {
2167       gtk_list_box_row_set_header (row, NULL);
2168     }
2169 }
2170 
2171 static gint
listbox_sort_func(GtkListBoxRow * row1,GtkListBoxRow * row2,gpointer user_data)2172 listbox_sort_func (GtkListBoxRow *row1,
2173                    GtkListBoxRow *row2,
2174                    gpointer       user_data)
2175 {
2176   gboolean row1_is_network;
2177   gboolean row2_is_network;
2178   gchar *path1;
2179   gchar *path2;
2180   gboolean *is_placeholder1;
2181   gboolean *is_placeholder2;
2182   gint retval;
2183 
2184   row1_is_network = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row1), "is-network"));
2185   row2_is_network = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row2), "is-network"));
2186 
2187   retval = row1_is_network - row2_is_network;
2188 
2189   if (retval != 0)
2190     return retval;
2191 
2192   is_placeholder1 = g_object_get_data (G_OBJECT (row1), "is-placeholder");
2193   is_placeholder2 = g_object_get_data (G_OBJECT (row2), "is-placeholder");
2194 
2195   /* we can't have two placeholders for the same section */
2196   g_assert (!(is_placeholder1 != NULL && is_placeholder2 != NULL));
2197 
2198   if (is_placeholder1)
2199     return -1;
2200   if (is_placeholder2)
2201     return 1;
2202 
2203   g_object_get (row1, "path", &path1, NULL);
2204   g_object_get (row2, "path", &path2, NULL);
2205 
2206   retval = g_utf8_collate (path1, path2);
2207 
2208   g_free (path1);
2209   g_free (path2);
2210 
2211   return retval;
2212 }
2213 
2214 static void
gtk_places_view_constructed(GObject * object)2215 gtk_places_view_constructed (GObject *object)
2216 {
2217   GtkPlacesViewPrivate *priv;
2218 
2219   priv = gtk_places_view_get_instance_private (GTK_PLACES_VIEW (object));
2220 
2221   G_OBJECT_CLASS (gtk_places_view_parent_class)->constructed (object);
2222 
2223   gtk_list_box_set_sort_func (GTK_LIST_BOX (priv->listbox),
2224                               listbox_sort_func,
2225                               object,
2226                               NULL);
2227   gtk_list_box_set_filter_func (GTK_LIST_BOX (priv->listbox),
2228                                 listbox_filter_func,
2229                                 object,
2230                                 NULL);
2231   gtk_list_box_set_header_func (GTK_LIST_BOX (priv->listbox),
2232                                 listbox_header_func,
2233                                 object,
2234                                 NULL);
2235 
2236   /* load drives */
2237   update_places (GTK_PLACES_VIEW (object));
2238 
2239   g_signal_connect_swapped (priv->volume_monitor,
2240                             "mount-added",
2241                             G_CALLBACK (update_places),
2242                             object);
2243   g_signal_connect_swapped (priv->volume_monitor,
2244                             "mount-changed",
2245                             G_CALLBACK (update_places),
2246                             object);
2247   g_signal_connect_swapped (priv->volume_monitor,
2248                             "mount-removed",
2249                             G_CALLBACK (update_places),
2250                             object);
2251   g_signal_connect_swapped (priv->volume_monitor,
2252                             "volume-added",
2253                             G_CALLBACK (update_places),
2254                             object);
2255   g_signal_connect_swapped (priv->volume_monitor,
2256                             "volume-changed",
2257                             G_CALLBACK (update_places),
2258                             object);
2259   g_signal_connect_swapped (priv->volume_monitor,
2260                             "volume-removed",
2261                             G_CALLBACK (update_places),
2262                             object);
2263 }
2264 
2265 static void
gtk_places_view_map(GtkWidget * widget)2266 gtk_places_view_map (GtkWidget *widget)
2267 {
2268   GtkPlacesViewPrivate *priv;
2269 
2270   priv = gtk_places_view_get_instance_private (GTK_PLACES_VIEW (widget));
2271 
2272   gtk_entry_set_text (GTK_ENTRY (priv->address_entry), "");
2273 
2274   GTK_WIDGET_CLASS (gtk_places_view_parent_class)->map (widget);
2275 }
2276 
2277 static void
gtk_places_view_class_init(GtkPlacesViewClass * klass)2278 gtk_places_view_class_init (GtkPlacesViewClass *klass)
2279 {
2280   GObjectClass *object_class = G_OBJECT_CLASS (klass);
2281   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2282 
2283   object_class->finalize = gtk_places_view_finalize;
2284   object_class->constructed = gtk_places_view_constructed;
2285   object_class->get_property = gtk_places_view_get_property;
2286   object_class->set_property = gtk_places_view_set_property;
2287 
2288   widget_class->destroy = gtk_places_view_destroy;
2289   widget_class->map = gtk_places_view_map;
2290 
2291   /**
2292    * GtkPlacesView::open-location:
2293    * @view: the object which received the signal.
2294    * @location: (type Gio.File): #GFile to which the caller should switch.
2295    * @open_flags: a single value from #GtkPlacesOpenFlags specifying how the @location
2296    * should be opened.
2297    *
2298    * The places view emits this signal when the user selects a location
2299    * in it. The calling application should display the contents of that
2300    * location; for example, a file manager should show a list of files in
2301    * the specified location.
2302    *
2303    * Since: 3.18
2304    */
2305   places_view_signals [OPEN_LOCATION] =
2306           g_signal_new (I_("open-location"),
2307                         G_OBJECT_CLASS_TYPE (object_class),
2308                         G_SIGNAL_RUN_FIRST,
2309                         G_STRUCT_OFFSET (GtkPlacesViewClass, open_location),
2310                         NULL, NULL,
2311                         _gtk_marshal_VOID__OBJECT_FLAGS,
2312                         G_TYPE_NONE, 2,
2313                         G_TYPE_OBJECT,
2314                         GTK_TYPE_PLACES_OPEN_FLAGS);
2315   g_signal_set_va_marshaller (places_view_signals [OPEN_LOCATION],
2316                               G_TYPE_FROM_CLASS (object_class),
2317                               _gtk_marshal_VOID__OBJECT_FLAGSv);
2318 
2319   /**
2320    * GtkPlacesView::show-error-message:
2321    * @view: the object which received the signal.
2322    * @primary: primary message with a summary of the error to show.
2323    * @secondary: secondary message with details of the error to show.
2324    *
2325    * The places view emits this signal when it needs the calling
2326    * application to present an error message.  Most of these messages
2327    * refer to mounting or unmounting media, for example, when a drive
2328    * cannot be started for some reason.
2329    *
2330    * Since: 3.18
2331    */
2332   places_view_signals [SHOW_ERROR_MESSAGE] =
2333           g_signal_new (I_("show-error-message"),
2334                         G_OBJECT_CLASS_TYPE (object_class),
2335                         G_SIGNAL_RUN_FIRST,
2336                         G_STRUCT_OFFSET (GtkPlacesViewClass, show_error_message),
2337                         NULL, NULL,
2338                         _gtk_marshal_VOID__STRING_STRING,
2339                         G_TYPE_NONE, 2,
2340                         G_TYPE_STRING,
2341                         G_TYPE_STRING);
2342 
2343   properties[PROP_LOCAL_ONLY] =
2344           g_param_spec_boolean ("local-only",
2345                                 P_("Local Only"),
2346                                 P_("Whether the sidebar only includes local files"),
2347                                 FALSE,
2348                                 G_PARAM_READWRITE);
2349 
2350   properties[PROP_LOADING] =
2351           g_param_spec_boolean ("loading",
2352                                 P_("Loading"),
2353                                 P_("Whether the view is loading locations"),
2354                                 FALSE,
2355                                 G_PARAM_READABLE);
2356 
2357   properties[PROP_FETCHING_NETWORKS] =
2358           g_param_spec_boolean ("fetching-networks",
2359                                 P_("Fetching networks"),
2360                                 P_("Whether the view is fetching networks"),
2361                                 FALSE,
2362                                 G_PARAM_READABLE);
2363 
2364   properties[PROP_OPEN_FLAGS] =
2365           g_param_spec_flags ("open-flags",
2366                               P_("Open Flags"),
2367                               P_("Modes in which the calling application can open locations selected in the sidebar"),
2368                               GTK_TYPE_PLACES_OPEN_FLAGS,
2369                               GTK_PLACES_OPEN_NORMAL,
2370                               G_PARAM_READWRITE);
2371 
2372   g_object_class_install_properties (object_class, LAST_PROP, properties);
2373 
2374   /* Bind class to template */
2375   gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/ui/gtkplacesview.ui");
2376 
2377   gtk_widget_class_bind_template_child_private (widget_class, GtkPlacesView, actionbar);
2378   gtk_widget_class_bind_template_child_private (widget_class, GtkPlacesView, address_entry);
2379   gtk_widget_class_bind_template_child_private (widget_class, GtkPlacesView, address_entry_completion);
2380   gtk_widget_class_bind_template_child_private (widget_class, GtkPlacesView, completion_store);
2381   gtk_widget_class_bind_template_child_private (widget_class, GtkPlacesView, connect_button);
2382   gtk_widget_class_bind_template_child_private (widget_class, GtkPlacesView, listbox);
2383   gtk_widget_class_bind_template_child_private (widget_class, GtkPlacesView, recent_servers_listbox);
2384   gtk_widget_class_bind_template_child_private (widget_class, GtkPlacesView, recent_servers_popover);
2385   gtk_widget_class_bind_template_child_private (widget_class, GtkPlacesView, recent_servers_stack);
2386   gtk_widget_class_bind_template_child_private (widget_class, GtkPlacesView, stack);
2387   gtk_widget_class_bind_template_child_private (widget_class, GtkPlacesView, server_adresses_popover);
2388   gtk_widget_class_bind_template_child_private (widget_class, GtkPlacesView, available_protocols_grid);
2389 
2390   gtk_widget_class_bind_template_callback (widget_class, on_address_entry_text_changed);
2391   gtk_widget_class_bind_template_callback (widget_class, on_address_entry_show_help_pressed);
2392   gtk_widget_class_bind_template_callback (widget_class, on_connect_button_clicked);
2393   gtk_widget_class_bind_template_callback (widget_class, on_key_press_event);
2394   gtk_widget_class_bind_template_callback (widget_class, on_listbox_row_activated);
2395   gtk_widget_class_bind_template_callback (widget_class, on_recent_servers_listbox_row_activated);
2396 
2397   gtk_widget_class_set_css_name (widget_class, "placesview");
2398 }
2399 
2400 static void
gtk_places_view_init(GtkPlacesView * self)2401 gtk_places_view_init (GtkPlacesView *self)
2402 {
2403   GtkPlacesViewPrivate *priv;
2404 
2405   priv = gtk_places_view_get_instance_private (self);
2406 
2407   priv->volume_monitor = g_volume_monitor_get ();
2408   priv->open_flags = GTK_PLACES_OPEN_NORMAL;
2409   priv->path_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
2410   priv->space_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
2411 
2412   gtk_widget_init_template (GTK_WIDGET (self));
2413 
2414   populate_available_protocols_grid (GTK_GRID (priv->available_protocols_grid));
2415 }
2416 
2417 /**
2418  * gtk_places_view_new:
2419  *
2420  * Creates a new #GtkPlacesView widget.
2421  *
2422  * The application should connect to at least the
2423  * #GtkPlacesView::open-location signal to be notified
2424  * when the user makes a selection in the view.
2425  *
2426  * Returns: a newly created #GtkPlacesView
2427  *
2428  * Since: 3.18
2429  */
2430 GtkWidget *
gtk_places_view_new(void)2431 gtk_places_view_new (void)
2432 {
2433   return g_object_new (GTK_TYPE_PLACES_VIEW, NULL);
2434 }
2435 
2436 /**
2437  * gtk_places_view_set_open_flags:
2438  * @view: a #GtkPlacesView
2439  * @flags: Bitmask of modes in which the calling application can open locations
2440  *
2441  * Sets the way in which the calling application can open new locations from
2442  * the places view.  For example, some applications only open locations
2443  * “directly” into their main view, while others may support opening locations
2444  * in a new notebook tab or a new window.
2445  *
2446  * This function is used to tell the places @view about the ways in which the
2447  * application can open new locations, so that the view can display (or not)
2448  * the “Open in new tab” and “Open in new window” menu items as appropriate.
2449  *
2450  * When the #GtkPlacesView::open-location signal is emitted, its flags
2451  * argument will be set to one of the @flags that was passed in
2452  * gtk_places_view_set_open_flags().
2453  *
2454  * Passing 0 for @flags will cause #GTK_PLACES_OPEN_NORMAL to always be sent
2455  * to callbacks for the “open-location” signal.
2456  *
2457  * Since: 3.18
2458  */
2459 void
gtk_places_view_set_open_flags(GtkPlacesView * view,GtkPlacesOpenFlags flags)2460 gtk_places_view_set_open_flags (GtkPlacesView      *view,
2461                                 GtkPlacesOpenFlags  flags)
2462 {
2463   GtkPlacesViewPrivate *priv;
2464 
2465   g_return_if_fail (GTK_IS_PLACES_VIEW (view));
2466 
2467   priv = gtk_places_view_get_instance_private (view);
2468 
2469   if (priv->open_flags != flags)
2470     {
2471       priv->open_flags = flags;
2472       g_object_notify_by_pspec (G_OBJECT (view), properties[PROP_OPEN_FLAGS]);
2473     }
2474 }
2475 
2476 /**
2477  * gtk_places_view_get_open_flags:
2478  * @view: a #GtkPlacesSidebar
2479  *
2480  * Gets the open flags.
2481  *
2482  * Returns: the #GtkPlacesOpenFlags of @view
2483  *
2484  * Since: 3.18
2485  */
2486 GtkPlacesOpenFlags
gtk_places_view_get_open_flags(GtkPlacesView * view)2487 gtk_places_view_get_open_flags (GtkPlacesView *view)
2488 {
2489   GtkPlacesViewPrivate *priv;
2490 
2491   g_return_val_if_fail (GTK_IS_PLACES_VIEW (view), 0);
2492 
2493   priv = gtk_places_view_get_instance_private (view);
2494 
2495   return priv->open_flags;
2496 }
2497 
2498 /**
2499  * gtk_places_view_get_search_query:
2500  * @view: a #GtkPlacesView
2501  *
2502  * Retrieves the current search query from @view.
2503  *
2504  * Returns: (transfer none): the current search query.
2505  */
2506 const gchar*
gtk_places_view_get_search_query(GtkPlacesView * view)2507 gtk_places_view_get_search_query (GtkPlacesView *view)
2508 {
2509   GtkPlacesViewPrivate *priv;
2510 
2511   g_return_val_if_fail (GTK_IS_PLACES_VIEW (view), NULL);
2512 
2513   priv = gtk_places_view_get_instance_private (view);
2514 
2515   return priv->search_query;
2516 }
2517 
2518 /**
2519  * gtk_places_view_set_search_query:
2520  * @view: a #GtkPlacesView
2521  * @query_text: the query, or NULL.
2522  *
2523  * Sets the search query of @view. The search is immediately performed
2524  * once the query is set.
2525  */
2526 void
gtk_places_view_set_search_query(GtkPlacesView * view,const gchar * query_text)2527 gtk_places_view_set_search_query (GtkPlacesView *view,
2528                                   const gchar   *query_text)
2529 {
2530   GtkPlacesViewPrivate *priv;
2531 
2532   g_return_if_fail (GTK_IS_PLACES_VIEW (view));
2533 
2534   priv = gtk_places_view_get_instance_private (view);
2535 
2536   if (g_strcmp0 (priv->search_query, query_text) != 0)
2537     {
2538       g_clear_pointer (&priv->search_query, g_free);
2539       priv->search_query = g_strdup (query_text);
2540 
2541       gtk_list_box_invalidate_filter (GTK_LIST_BOX (priv->listbox));
2542       gtk_list_box_invalidate_headers (GTK_LIST_BOX (priv->listbox));
2543 
2544       update_view_mode (view);
2545     }
2546 }
2547 
2548 /**
2549  * gtk_places_view_get_loading:
2550  * @view: a #GtkPlacesView
2551  *
2552  * Returns %TRUE if the view is loading locations.
2553  *
2554  * Since: 3.18
2555  */
2556 gboolean
gtk_places_view_get_loading(GtkPlacesView * view)2557 gtk_places_view_get_loading (GtkPlacesView *view)
2558 {
2559   GtkPlacesViewPrivate *priv;
2560 
2561   g_return_val_if_fail (GTK_IS_PLACES_VIEW (view), FALSE);
2562 
2563   priv = gtk_places_view_get_instance_private (view);
2564 
2565   return priv->loading;
2566 }
2567 
2568 static void
update_loading(GtkPlacesView * view)2569 update_loading (GtkPlacesView *view)
2570 {
2571   GtkPlacesViewPrivate *priv;
2572   gboolean loading;
2573 
2574   g_return_if_fail (GTK_IS_PLACES_VIEW (view));
2575 
2576   priv = gtk_places_view_get_instance_private (view);
2577   loading = priv->fetching_networks || priv->connecting_to_server ||
2578             priv->mounting_volume || priv->unmounting_mount;
2579 
2580   set_busy_cursor (view, loading);
2581   gtk_places_view_set_loading (view, loading);
2582 }
2583 
2584 static void
gtk_places_view_set_loading(GtkPlacesView * view,gboolean loading)2585 gtk_places_view_set_loading (GtkPlacesView *view,
2586                              gboolean       loading)
2587 {
2588   GtkPlacesViewPrivate *priv;
2589 
2590   g_return_if_fail (GTK_IS_PLACES_VIEW (view));
2591 
2592   priv = gtk_places_view_get_instance_private (view);
2593 
2594   if (priv->loading != loading)
2595     {
2596       priv->loading = loading;
2597       g_object_notify_by_pspec (G_OBJECT (view), properties [PROP_LOADING]);
2598     }
2599 }
2600 
2601 static gboolean
gtk_places_view_get_fetching_networks(GtkPlacesView * view)2602 gtk_places_view_get_fetching_networks (GtkPlacesView *view)
2603 {
2604   GtkPlacesViewPrivate *priv;
2605 
2606   g_return_val_if_fail (GTK_IS_PLACES_VIEW (view), FALSE);
2607 
2608   priv = gtk_places_view_get_instance_private (view);
2609 
2610   return priv->fetching_networks;
2611 }
2612 
2613 static void
gtk_places_view_set_fetching_networks(GtkPlacesView * view,gboolean fetching_networks)2614 gtk_places_view_set_fetching_networks (GtkPlacesView *view,
2615                                        gboolean       fetching_networks)
2616 {
2617   GtkPlacesViewPrivate *priv;
2618 
2619   g_return_if_fail (GTK_IS_PLACES_VIEW (view));
2620 
2621   priv = gtk_places_view_get_instance_private (view);
2622 
2623   if (priv->fetching_networks != fetching_networks)
2624     {
2625       priv->fetching_networks = fetching_networks;
2626       g_object_notify_by_pspec (G_OBJECT (view), properties [PROP_FETCHING_NETWORKS]);
2627     }
2628 }
2629 
2630 /**
2631  * gtk_places_view_get_local_only:
2632  * @view: a #GtkPlacesView
2633  *
2634  * Returns %TRUE if only local volumes are shown, i.e. no networks
2635  * are displayed.
2636  *
2637  * Returns: %TRUE if only local volumes are shown, %FALSE otherwise.
2638  *
2639  * Since: 3.18
2640  */
2641 gboolean
gtk_places_view_get_local_only(GtkPlacesView * view)2642 gtk_places_view_get_local_only (GtkPlacesView *view)
2643 {
2644   GtkPlacesViewPrivate *priv;
2645 
2646   g_return_val_if_fail (GTK_IS_PLACES_VIEW (view), FALSE);
2647 
2648   priv = gtk_places_view_get_instance_private (view);
2649 
2650   return priv->local_only;
2651 }
2652 
2653 /**
2654  * gtk_places_view_set_local_only:
2655  * @view: a #GtkPlacesView
2656  * @local_only: %TRUE to hide remote locations, %FALSE to show.
2657  *
2658  * Sets the #GtkPlacesView::local-only property to @local_only.
2659  *
2660  * Since: 3.18
2661  */
2662 void
gtk_places_view_set_local_only(GtkPlacesView * view,gboolean local_only)2663 gtk_places_view_set_local_only (GtkPlacesView *view,
2664                                 gboolean       local_only)
2665 {
2666   GtkPlacesViewPrivate *priv;
2667 
2668   g_return_if_fail (GTK_IS_PLACES_VIEW (view));
2669 
2670   priv = gtk_places_view_get_instance_private (view);
2671 
2672   if (priv->local_only != local_only)
2673     {
2674       priv->local_only = local_only;
2675 
2676       gtk_widget_set_visible (priv->actionbar, !local_only);
2677       update_places (view);
2678 
2679       update_view_mode (view);
2680 
2681       g_object_notify_by_pspec (G_OBJECT (view), properties [PROP_LOCAL_ONLY]);
2682     }
2683 }
2684