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 | |
52 | struct _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 *, |
61 | const char *primary, |
62 | const char *secondary); |
63 | }; |
64 | |
65 | struct _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 *; |
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 | |
116 | static void mount_volume (GtkPlacesView *view, |
117 | GVolume *volume); |
118 | |
119 | static void on_eject_button_clicked (GtkWidget *widget, |
120 | GtkPlacesViewRow *row); |
121 | |
122 | static gboolean on_row_popup_menu (GtkWidget *widget, |
123 | GVariant *args, |
124 | gpointer user_data); |
125 | |
126 | static void click_cb (GtkGesture *gesture, |
127 | int n_press, |
128 | double x, |
129 | double y, |
130 | gpointer user_data); |
131 | |
132 | static void populate_servers (GtkPlacesView *view); |
133 | |
134 | static gboolean gtk_places_view_get_fetching_networks (GtkPlacesView *view); |
135 | |
136 | static void gtk_places_view_set_fetching_networks (GtkPlacesView *view, |
137 | gboolean fetching_networks); |
138 | |
139 | static void gtk_places_view_set_loading (GtkPlacesView *view, |
140 | gboolean loading); |
141 | |
142 | static void update_loading (GtkPlacesView *view); |
143 | |
144 | G_DEFINE_TYPE (GtkPlacesView, gtk_places_view, GTK_TYPE_BOX) |
145 | |
146 | /* GtkPlacesView properties & signals */ |
147 | enum { |
148 | PROP_0, |
149 | PROP_OPEN_FLAGS, |
150 | PROP_FETCHING_NETWORKS, |
151 | PROP_LOADING, |
152 | LAST_PROP |
153 | }; |
154 | |
155 | enum { |
156 | OPEN_LOCATION, |
157 | SHOW_ERROR_MESSAGE, |
158 | LAST_SIGNAL |
159 | }; |
160 | |
161 | const char *unsupported_protocols [] = |
162 | { |
163 | "file" , "afc" , "obex" , "http" , |
164 | "trash" , "burn" , "computer" , |
165 | "archive" , "recent" , "localtest" , |
166 | NULL |
167 | }; |
168 | |
169 | static guint places_view_signals [LAST_SIGNAL] = { 0 }; |
170 | static GParamSpec *properties [LAST_PROP]; |
171 | |
172 | static void |
173 | emit_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 | |
183 | static void |
184 | emit_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 | |
192 | static void |
193 | server_file_changed_cb (GtkPlacesView *view) |
194 | { |
195 | populate_servers (view); |
196 | } |
197 | |
198 | static GBookmarkFile * |
199 | server_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 | |
260 | static void |
261 | server_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 | |
270 | static void |
271 | server_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 | |
310 | static void |
311 | server_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 */ |
328 | static GtkWindow * |
329 | get_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 | |
340 | static void |
341 | set_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 */ |
359 | static void |
360 | activate_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 | |
397 | static void update_places (GtkPlacesView *view); |
398 | |
399 | static void |
400 | gtk_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 | |
420 | static void |
421 | gtk_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 | |
442 | static void |
443 | gtk_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 | |
469 | static void |
470 | gtk_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 | |
488 | static gboolean |
489 | is_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 | |
512 | typedef struct |
513 | { |
514 | char *uri; |
515 | GtkPlacesView *view; |
516 | } RemoveServerData; |
517 | |
518 | static void |
519 | on_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 | |
526 | static void |
527 | populate_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 | |
632 | static void |
633 | update_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 | |
665 | static void |
666 | insert_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 | |
702 | static void |
703 | add_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 | |
751 | static void |
752 | add_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 | |
799 | static void |
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, volume: l->data); |
810 | |
811 | g_list_free_full (list: volumes, free_func: g_object_unref); |
812 | } |
813 | |
814 | static void |
815 | add_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 | |
836 | static gboolean |
837 | has_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 | |
857 | static void |
858 | update_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 | |
904 | static void |
905 | monitor_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 | |
935 | static void |
936 | populate_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 | |
969 | static void |
970 | network_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 | |
1011 | static void |
1012 | network_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 | |
1044 | static void |
1045 | fetch_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 | |
1080 | static void |
1081 | update_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 | |
1178 | static void |
1179 | server_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 | |
1274 | static void |
1275 | volume_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 | |
1344 | static void |
1345 | unmount_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 | |
1383 | static gboolean |
1384 | pulse_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 | |
1409 | static void |
1410 | unmount_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 | |
1437 | static void |
1438 | mount_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 | |
1482 | static void |
1483 | mount_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 | |
1515 | static void |
1516 | open_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 | |
1536 | static void |
1537 | mount_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 | |
1560 | static void |
1561 | unmount_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 | |
1578 | static void |
1579 | attach_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 | |
1595 | static void |
1596 | populate_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 | |
1649 | static GMenuModel * |
1650 | (void) |
1651 | { |
1652 | GMenu *; |
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 | |
1703 | static gboolean |
1704 | (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 | |
1759 | static void |
1760 | click_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 | |
1769 | static gboolean |
1770 | on_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 | |
1812 | static void |
1813 | on_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 | |
1830 | static void |
1831 | on_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 | |
1842 | static void |
1843 | on_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 | |
1875 | static void |
1876 | on_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 | |
1896 | out: |
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 | |
1907 | static void |
1908 | on_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 | |
1928 | static void |
1929 | on_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 | |
1942 | static void |
1943 | on_listbox_row_activated (GtkPlacesView *view, |
1944 | GtkPlacesViewRow *row, |
1945 | GtkWidget *listbox) |
1946 | { |
1947 | activate_row (view, row, flags: view->current_open_flags); |
1948 | } |
1949 | |
1950 | static gboolean |
1951 | listbox_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 | |
2001 | static void |
2002 | (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 *; |
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 *; |
2046 | GtkWidget *; |
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 | |
2082 | static int |
2083 | listbox_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 | |
2125 | static void |
2126 | gtk_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 | |
2174 | static void |
2175 | gtk_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 | |
2184 | static void |
2185 | gtk_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 | |
2344 | static void |
2345 | gtk_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 | */ |
2388 | GtkWidget * |
2389 | gtk_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 | */ |
2415 | void |
2416 | gtk_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 | */ |
2442 | GtkPlacesOpenFlags |
2443 | gtk_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 | */ |
2458 | const char * |
2459 | gtk_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 | */ |
2474 | void |
2475 | gtk_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 | */ |
2498 | gboolean |
2499 | gtk_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 | |
2506 | static void |
2507 | update_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 | |
2520 | static void |
2521 | gtk_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 | |
2533 | static gboolean |
2534 | gtk_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 | |
2541 | static void |
2542 | gtk_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 | |