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

source code of gtk/gtk/gtkplacesview.c