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