1/* GtkPlacesSidebar - sidebar widget for places in the filesystem
2 *
3 * This library is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU Lesser General Public
5 * License as published by the Free Software Foundation; either
6 * version 2 of the License, or (at your option) any later version.
7 *
8 * This library is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public
14 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * This code is originally from Nautilus.
17 *
18 * Authors : Mr Jamie McCracken (jamiemcc at blueyonder dot co dot uk)
19 * Cosimo Cecchi <cosimoc@gnome.org>
20 * Federico Mena Quintero <federico@gnome.org>
21 * Carlos Soriano <csoriano@gnome.org>
22 */
23
24#include "config.h"
25
26#include <gio/gio.h>
27#ifdef HAVE_CLOUDPROVIDERS
28#include <cloudproviders.h>
29#endif
30
31#include "gtkplacessidebarprivate.h"
32#include "gtksidebarrowprivate.h"
33#include "gdk/gdkkeysyms.h"
34#include "gtkbookmarksmanagerprivate.h"
35#include "gtkcelllayout.h"
36#include "gtkfilechooserutils.h"
37#include "gtkicontheme.h"
38#include "gtkintl.h"
39#include "gtkmain.h"
40#include "gtkmarshalers.h"
41#include "gtkmountoperation.h"
42#include "gtkscrolledwindow.h"
43#include "gtksettings.h"
44#include "gtktrashmonitor.h"
45#include "gtktypebuiltins.h"
46#include "gtkprivatetypebuiltins.h"
47#include "gtkpopovermenu.h"
48#include "gtkgrid.h"
49#include "gtklabel.h"
50#include "gtkbutton.h"
51#include "gtklistbox.h"
52#include "gtkdroptarget.h"
53#include "gtkseparator.h"
54#include "gtkentry.h"
55#include "gtkgesturelongpress.h"
56#include "gtkbox.h"
57#include "gtkmodelbuttonprivate.h"
58#include "gtkprivate.h"
59#include "gtkeventcontrollerkey.h"
60#include "gtkgestureclick.h"
61#include "gtkgesturedrag.h"
62#include "gtknative.h"
63#include "gtkdragsourceprivate.h"
64#include "gtkdragicon.h"
65
66/*< private >
67 * GtkPlacesSidebar:
68 *
69 * GtkPlacesSidebar is a widget that displays a list of frequently-used places in the
70 * file system: the user’s home directory, the user’s bookmarks, and volumes and drives.
71 * This widget is used as a sidebar in GtkFileChooser and may be used by file managers
72 * and similar programs.
73 *
74 * The places sidebar displays drives and volumes, and will automatically mount
75 * or unmount them when the user selects them.
76 *
77 * Applications can hook to various signals in the places sidebar to customize
78 * its behavior. For example, they can add extra commands to the context menu
79 * of the sidebar.
80 *
81 * While bookmarks are completely in control of the user, the places sidebar also
82 * allows individual applications to provide extra shortcut folders that are unique
83 * to each application. For example, a Paint program may want to add a shortcut
84 * for a Clipart folder. You can do this with gtk_places_sidebar_add_shortcut().
85 *
86 * To make use of the places sidebar, an application at least needs to connect
87 * to the GtkPlacesSidebar::open-location signal. This is emitted when the
88 * user selects in the sidebar a location to open. The application should also
89 * call gtk_places_sidebar_set_location() when it changes the currently-viewed
90 * location.
91 *
92 * # CSS nodes
93 *
94 * GtkPlacesSidebar uses a single CSS node with name placessidebar and style
95 * class .sidebar.
96 *
97 * Among the children of the places sidebar, the following style classes can
98 * be used:
99 * - .sidebar-new-bookmark-row for the 'Add new bookmark' row
100 * - .sidebar-placeholder-row for a row that is a placeholder
101 * - .has-open-popup when a popup is open for a row
102 */
103
104/* These are used when a destination-side DND operation is taking place.
105 * Normally, when a common drag action is taking place, the state will be
106 * DROP_STATE_NEW_BOOKMARK_ARMED, however, if the client of GtkPlacesSidebar
107 * wants to show hints about the valid targets, we sill set it as
108 * DROP_STATE_NEW_BOOKMARK_ARMED_PERMANENT, so the sidebar will show drop hints
109 * until the client says otherwise
110 */
111typedef enum {
112 DROP_STATE_NORMAL,
113 DROP_STATE_NEW_BOOKMARK_ARMED,
114 DROP_STATE_NEW_BOOKMARK_ARMED_PERMANENT,
115} DropState;
116
117struct _GtkPlacesSidebar {
118 GtkWidget parent;
119
120 GtkWidget *swin;
121 GtkWidget *list_box;
122 GtkWidget *new_bookmark_row;
123
124 GtkBookmarksManager *bookmarks_manager;
125
126 GActionGroup *row_actions;
127
128#ifdef HAVE_CLOUDPROVIDERS
129 CloudProvidersCollector *cloud_manager;
130 GList *unready_accounts;
131#endif
132
133 GVolumeMonitor *volume_monitor;
134 GtkTrashMonitor *trash_monitor;
135 GtkSettings *gtk_settings;
136 GFile *current_location;
137
138 GtkWidget *rename_popover;
139 GtkWidget *rename_entry;
140 GtkWidget *rename_button;
141 GtkWidget *rename_error;
142 char *rename_uri;
143
144 gulong trash_monitor_changed_id;
145 GtkWidget *trash_row;
146
147 /* DND */
148 gboolean dragging_over;
149 GtkWidget *drag_row;
150 int drag_row_height;
151 int drag_row_x;
152 int drag_row_y;
153 GtkWidget *row_placeholder;
154 DropState drop_state;
155
156 /* volume mounting - delayed open process */
157 GtkPlacesOpenFlags go_to_after_mount_open_flags;
158 GCancellable *cancellable;
159
160 GtkWidget *popover;
161 GtkSidebarRow *context_row;
162 GListStore *shortcuts;
163
164 GDBusProxy *hostnamed_proxy;
165 GCancellable *hostnamed_cancellable;
166 char *hostname;
167
168 GtkPlacesOpenFlags open_flags;
169
170 guint mounting : 1;
171 guint show_recent_set : 1;
172 guint show_recent : 1;
173 guint show_desktop_set : 1;
174 guint show_desktop : 1;
175 guint show_enter_location : 1;
176 guint show_other_locations : 1;
177 guint show_trash : 1;
178 guint show_starred_location : 1;
179};
180
181struct _GtkPlacesSidebarClass {
182 GtkWidgetClass parent_class;
183
184 void (* open_location) (GtkPlacesSidebar *sidebar,
185 GFile *location,
186 GtkPlacesOpenFlags open_flags);
187 void (* show_error_message) (GtkPlacesSidebar *sidebar,
188 const char *primary,
189 const char *secondary);
190 GdkDragAction (* drag_action_requested) (GtkPlacesSidebar *sidebar,
191 GFile *dest_file,
192 GSList *source_file_list);
193 GdkDragAction (* drag_action_ask) (GtkPlacesSidebar *sidebar,
194 GdkDragAction actions);
195 void (* drag_perform_drop) (GtkPlacesSidebar *sidebar,
196 GFile *dest_file,
197 GList *source_file_list,
198 GdkDragAction action);
199 void (* show_enter_location) (GtkPlacesSidebar *sidebar);
200
201 void (* show_other_locations_with_flags) (GtkPlacesSidebar *sidebar,
202 GtkPlacesOpenFlags open_flags);
203
204 void (* show_starred_location) (GtkPlacesSidebar *sidebar);
205
206 void (* mount) (GtkPlacesSidebar *sidebar,
207 GMountOperation *mount_operation);
208 void (* unmount) (GtkPlacesSidebar *sidebar,
209 GMountOperation *unmount_operation);
210};
211
212enum {
213 OPEN_LOCATION,
214 SHOW_ERROR_MESSAGE,
215 SHOW_ENTER_LOCATION,
216 DRAG_ACTION_REQUESTED,
217 DRAG_ACTION_ASK,
218 DRAG_PERFORM_DROP,
219 SHOW_OTHER_LOCATIONS_WITH_FLAGS,
220 SHOW_STARRED_LOCATION,
221 MOUNT,
222 UNMOUNT,
223 LAST_SIGNAL
224};
225
226enum {
227 PROP_LOCATION = 1,
228 PROP_OPEN_FLAGS,
229 PROP_SHOW_RECENT,
230 PROP_SHOW_DESKTOP,
231 PROP_SHOW_ENTER_LOCATION,
232 PROP_SHOW_TRASH,
233 PROP_SHOW_STARRED_LOCATION,
234 PROP_SHOW_OTHER_LOCATIONS,
235 NUM_PROPERTIES
236};
237
238/* Names for themed icons */
239#define ICON_NAME_HOME "user-home-symbolic"
240#define ICON_NAME_DESKTOP "user-desktop-symbolic"
241#define ICON_NAME_FILESYSTEM "drive-harddisk-symbolic"
242#define ICON_NAME_EJECT "media-eject-symbolic"
243#define ICON_NAME_NETWORK "network-workgroup-symbolic"
244#define ICON_NAME_NETWORK_SERVER "network-server-symbolic"
245#define ICON_NAME_FOLDER_NETWORK "folder-remote-symbolic"
246#define ICON_NAME_OTHER_LOCATIONS "list-add-symbolic"
247
248#define ICON_NAME_FOLDER "folder-symbolic"
249#define ICON_NAME_FOLDER_DESKTOP "user-desktop-symbolic"
250#define ICON_NAME_FOLDER_DOCUMENTS "folder-documents-symbolic"
251#define ICON_NAME_FOLDER_DOWNLOAD "folder-download-symbolic"
252#define ICON_NAME_FOLDER_MUSIC "folder-music-symbolic"
253#define ICON_NAME_FOLDER_PICTURES "folder-pictures-symbolic"
254#define ICON_NAME_FOLDER_PUBLIC_SHARE "folder-publicshare-symbolic"
255#define ICON_NAME_FOLDER_TEMPLATES "folder-templates-symbolic"
256#define ICON_NAME_FOLDER_VIDEOS "folder-videos-symbolic"
257#define ICON_NAME_FOLDER_SAVED_SEARCH "folder-saved-search-symbolic"
258
259static guint places_sidebar_signals [LAST_SIGNAL] = { 0 };
260static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
261
262static gboolean eject_or_unmount_bookmark (GtkSidebarRow *row);
263static gboolean eject_or_unmount_selection (GtkPlacesSidebar *sidebar);
264static void check_unmount_and_eject (GMount *mount,
265 GVolume *volume,
266 GDrive *drive,
267 gboolean *show_unmount,
268 gboolean *show_eject);
269static void on_row_pressed (GtkGestureClick *gesture,
270 int n_press,
271 double x,
272 double y,
273 GtkSidebarRow *row);
274static void on_row_released (GtkGestureClick *gesture,
275 int n_press,
276 double x,
277 double y,
278 GtkSidebarRow *row);
279static void on_row_dragged (GtkGestureDrag *gesture,
280 double x,
281 double y,
282 GtkSidebarRow *row);
283
284static void popup_menu_cb (GtkSidebarRow *row);
285static void long_press_cb (GtkGesture *gesture,
286 double x,
287 double y,
288 GtkPlacesSidebar *sidebar);
289static void stop_drop_feedback (GtkPlacesSidebar *sidebar);
290static GMountOperation * get_mount_operation (GtkPlacesSidebar *sidebar);
291static GMountOperation * get_unmount_operation (GtkPlacesSidebar *sidebar);
292
293
294G_DEFINE_TYPE (GtkPlacesSidebar, gtk_places_sidebar, GTK_TYPE_WIDGET);
295
296static void
297emit_open_location (GtkPlacesSidebar *sidebar,
298 GFile *location,
299 GtkPlacesOpenFlags open_flags)
300{
301 if ((open_flags & sidebar->open_flags) == 0)
302 open_flags = GTK_PLACES_OPEN_NORMAL;
303
304 g_signal_emit (instance: sidebar, signal_id: places_sidebar_signals[OPEN_LOCATION], detail: 0,
305 location, open_flags);
306}
307
308static void
309emit_show_error_message (GtkPlacesSidebar *sidebar,
310 const char *primary,
311 const char *secondary)
312{
313 g_signal_emit (instance: sidebar, signal_id: places_sidebar_signals[SHOW_ERROR_MESSAGE], detail: 0,
314 primary, secondary);
315}
316
317static void
318emit_show_enter_location (GtkPlacesSidebar *sidebar)
319{
320 g_signal_emit (instance: sidebar, signal_id: places_sidebar_signals[SHOW_ENTER_LOCATION], detail: 0);
321}
322
323static void
324emit_show_other_locations_with_flags (GtkPlacesSidebar *sidebar,
325 GtkPlacesOpenFlags open_flags)
326{
327 g_signal_emit (instance: sidebar, signal_id: places_sidebar_signals[SHOW_OTHER_LOCATIONS_WITH_FLAGS],
328 detail: 0, open_flags);
329}
330
331static void
332emit_show_starred_location (GtkPlacesSidebar *sidebar,
333 GtkPlacesOpenFlags open_flags)
334{
335 g_signal_emit (instance: sidebar, signal_id: places_sidebar_signals[SHOW_STARRED_LOCATION], detail: 0,
336 open_flags);
337}
338
339
340static void
341emit_mount_operation (GtkPlacesSidebar *sidebar,
342 GMountOperation *mount_op)
343{
344 g_signal_emit (instance: sidebar, signal_id: places_sidebar_signals[MOUNT], detail: 0, mount_op);
345}
346
347static void
348emit_unmount_operation (GtkPlacesSidebar *sidebar,
349 GMountOperation *mount_op)
350{
351 g_signal_emit (instance: sidebar, signal_id: places_sidebar_signals[UNMOUNT], detail: 0, mount_op);
352}
353
354static GdkDragAction
355emit_drag_action_requested (GtkPlacesSidebar *sidebar,
356 GFile *dest_file,
357 GSList *source_file_list)
358{
359 GdkDragAction ret_action = 0;
360
361 g_signal_emit (instance: sidebar, signal_id: places_sidebar_signals[DRAG_ACTION_REQUESTED], detail: 0,
362 dest_file, source_file_list, &ret_action);
363
364 return ret_action;
365}
366
367static void
368emit_drag_perform_drop (GtkPlacesSidebar *sidebar,
369 GFile *dest_file,
370 GSList *source_file_list,
371 GdkDragAction action)
372{
373 g_signal_emit (instance: sidebar, signal_id: places_sidebar_signals[DRAG_PERFORM_DROP], detail: 0,
374 dest_file, source_file_list, action);
375}
376static void
377list_box_header_func (GtkListBoxRow *row,
378 GtkListBoxRow *before,
379 gpointer user_data)
380{
381 GtkPlacesSectionType row_section_type;
382 GtkPlacesSectionType before_section_type;
383 GtkWidget *separator;
384
385 gtk_list_box_row_set_header (row, NULL);
386
387 g_object_get (object: row, first_property_name: "section-type", &row_section_type, NULL);
388 if (before)
389 {
390 g_object_get (object: before, first_property_name: "section-type", &before_section_type, NULL);
391 }
392 else
393 {
394 before_section_type = GTK_PLACES_SECTION_INVALID;
395 }
396
397 if (before && before_section_type != row_section_type)
398 {
399 separator = gtk_separator_new (orientation: GTK_ORIENTATION_HORIZONTAL);
400 gtk_list_box_row_set_header (row, header: separator);
401 }
402}
403
404static GtkWidget*
405add_place (GtkPlacesSidebar *sidebar,
406 GtkPlacesPlaceType place_type,
407 GtkPlacesSectionType section_type,
408 const char *name,
409 GIcon *start_icon,
410 GIcon *end_icon,
411 const char *uri,
412 GDrive *drive,
413 GVolume *volume,
414 GMount *mount,
415#ifdef HAVE_CLOUDPROVIDERS
416 CloudProvidersAccount *cloud_provider_account,
417#else
418 gpointer *cloud_provider_account,
419#endif
420 const int index,
421 const char *tooltip)
422{
423 gboolean show_eject, show_unmount;
424 gboolean show_eject_button;
425 GtkWidget *row;
426 GtkWidget *eject_button;
427 GtkGesture *gesture;
428
429 check_unmount_and_eject (mount, volume, drive,
430 show_unmount: &show_unmount, show_eject: &show_eject);
431
432 if (show_unmount || show_eject)
433 g_assert (place_type != GTK_PLACES_BOOKMARK);
434
435 show_eject_button = (show_unmount || show_eject);
436
437 row = g_object_new (GTK_TYPE_SIDEBAR_ROW,
438 first_property_name: "sidebar", sidebar,
439 "start-icon", start_icon,
440 "end-icon", end_icon,
441 "label", name,
442 "tooltip", tooltip,
443 "ejectable", show_eject_button,
444 "order-index", index,
445 "section-type", section_type,
446 "place-type", place_type,
447 "uri", uri,
448 "drive", drive,
449 "volume", volume,
450 "mount", mount,
451#ifdef HAVE_CLOUDPROVIDERS
452 "cloud-provider-account", cloud_provider_account,
453#endif
454 NULL);
455
456 eject_button = gtk_sidebar_row_get_eject_button (GTK_SIDEBAR_ROW (row));
457
458 g_signal_connect_swapped (eject_button, "clicked",
459 G_CALLBACK (eject_or_unmount_bookmark), row);
460
461 gesture = gtk_gesture_click_new ();
462 gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), button: 0);
463 g_signal_connect (gesture, "pressed",
464 G_CALLBACK (on_row_pressed), row);
465 g_signal_connect (gesture, "released",
466 G_CALLBACK (on_row_released), row);
467 gtk_widget_add_controller (widget: row, GTK_EVENT_CONTROLLER (gesture));
468
469 gesture = gtk_gesture_drag_new ();
470 g_signal_connect (gesture, "drag-update",
471 G_CALLBACK (on_row_dragged), row);
472 gtk_widget_add_controller (widget: row, GTK_EVENT_CONTROLLER (gesture));
473
474 gtk_list_box_insert (GTK_LIST_BOX (sidebar->list_box), GTK_WIDGET (row), position: -1);
475
476 return row;
477}
478
479static GIcon *
480special_directory_get_gicon (GUserDirectory directory)
481{
482#define ICON_CASE(x) \
483 case G_USER_DIRECTORY_ ## x: \
484 return g_themed_icon_new_with_default_fallbacks (ICON_NAME_FOLDER_ ## x);
485
486 switch (directory)
487 {
488
489 ICON_CASE (DESKTOP);
490 ICON_CASE (DOCUMENTS);
491 ICON_CASE (DOWNLOAD);
492 ICON_CASE (MUSIC);
493 ICON_CASE (PICTURES);
494 ICON_CASE (PUBLIC_SHARE);
495 ICON_CASE (TEMPLATES);
496 ICON_CASE (VIDEOS);
497
498 case G_USER_N_DIRECTORIES:
499 default:
500 return g_themed_icon_new_with_default_fallbacks (ICON_NAME_FOLDER);
501 }
502
503#undef ICON_CASE
504}
505
506static gboolean
507recent_files_setting_is_enabled (GtkPlacesSidebar *sidebar)
508{
509 GtkSettings *settings;
510 gboolean enabled;
511
512 settings = gtk_widget_get_settings (GTK_WIDGET (sidebar));
513 g_object_get (object: settings, first_property_name: "gtk-recent-files-enabled", &enabled, NULL);
514
515 return enabled;
516}
517
518static gboolean
519recent_scheme_is_supported (void)
520{
521 const char * const *supported;
522
523 supported = g_vfs_get_supported_uri_schemes (vfs: g_vfs_get_default ());
524 if (supported != NULL)
525 return g_strv_contains (strv: supported, str: "recent");
526
527 return FALSE;
528}
529
530static gboolean
531should_show_recent (GtkPlacesSidebar *sidebar)
532{
533 return recent_files_setting_is_enabled (sidebar) &&
534 ((sidebar->show_recent_set && sidebar->show_recent) ||
535 (!sidebar->show_recent_set && recent_scheme_is_supported ()));
536}
537
538static gboolean
539path_is_home_dir (const char *path)
540{
541 GFile *home_dir;
542 GFile *location;
543 const char *home_path;
544 gboolean res;
545
546 home_path = g_get_home_dir ();
547 if (!home_path)
548 return FALSE;
549
550 home_dir = g_file_new_for_path (path: home_path);
551 location = g_file_new_for_path (path);
552 res = g_file_equal (file1: home_dir, file2: location);
553
554 g_object_unref (object: home_dir);
555 g_object_unref (object: location);
556
557 return res;
558}
559
560static void
561open_home (GtkPlacesSidebar *sidebar)
562{
563 const char *home_path;
564 GFile *home_dir;
565
566 home_path = g_get_home_dir ();
567 if (!home_path)
568 return;
569
570 home_dir = g_file_new_for_path (path: home_path);
571 emit_open_location (sidebar, location: home_dir, open_flags: 0);
572
573 g_object_unref (object: home_dir);
574}
575
576static void
577add_special_dirs (GtkPlacesSidebar *sidebar)
578{
579 GList *dirs;
580 int index;
581
582 dirs = NULL;
583 for (index = 0; index < G_USER_N_DIRECTORIES; index++)
584 {
585 const char *path;
586 GFile *root;
587 GIcon *start_icon;
588 char *name;
589 char *mount_uri;
590 char *tooltip;
591
592 if (!_gtk_bookmarks_manager_get_is_xdg_dir_builtin (xdg_type: index))
593 continue;
594
595 path = g_get_user_special_dir (directory: index);
596
597 /* XDG resets special dirs to the home directory in case
598 * it's not finiding what it expects. We don't want the home
599 * to be added multiple times in that weird configuration.
600 */
601 if (path == NULL ||
602 path_is_home_dir (path) ||
603 g_list_find_custom (list: dirs, data: path, func: (GCompareFunc) g_strcmp0) != NULL)
604 continue;
605
606 root = g_file_new_for_path (path);
607
608 name = _gtk_bookmarks_manager_get_bookmark_label (manager: sidebar->bookmarks_manager, file: root);
609 if (!name)
610 name = g_file_get_basename (file: root);
611
612 start_icon = special_directory_get_gicon (directory: index);
613 mount_uri = g_file_get_uri (file: root);
614 tooltip = g_file_get_parse_name (file: root);
615
616 add_place (sidebar, place_type: GTK_PLACES_XDG_DIR,
617 section_type: GTK_PLACES_SECTION_COMPUTER,
618 name, start_icon, NULL, uri: mount_uri,
619 NULL, NULL, NULL, NULL, index: 0,
620 tooltip);
621 g_free (mem: name);
622 g_object_unref (object: root);
623 g_object_unref (object: start_icon);
624 g_free (mem: mount_uri);
625 g_free (mem: tooltip);
626
627 dirs = g_list_prepend (list: dirs, data: (char *)path);
628 }
629
630 g_list_free (list: dirs);
631}
632
633static char *
634get_home_directory_uri (void)
635{
636 const char *home;
637
638 home = g_get_home_dir ();
639 if (!home)
640 return NULL;
641
642 return g_filename_to_uri (filename: home, NULL, NULL);
643}
644
645static char *
646get_desktop_directory_uri (void)
647{
648 const char *name;
649
650 name = g_get_user_special_dir (directory: G_USER_DIRECTORY_DESKTOP);
651
652 /* "To disable a directory, point it to the homedir."
653 * See http://freedesktop.org/wiki/Software/xdg-user-dirs
654 */
655 if (path_is_home_dir (path: name))
656 return NULL;
657
658 return g_filename_to_uri (filename: name, NULL, NULL);
659}
660
661static gboolean
662file_is_shown (GtkPlacesSidebar *sidebar,
663 GFile *file)
664{
665 char *uri;
666 GtkWidget *row;
667 gboolean found = FALSE;
668
669 for (row = gtk_widget_get_first_child (GTK_WIDGET (sidebar->list_box));
670 row != NULL && !found;
671 row = gtk_widget_get_next_sibling (widget: row))
672 {
673 if (!GTK_IS_LIST_BOX_ROW (row))
674 continue;
675
676 g_object_get (object: row, first_property_name: "uri", &uri, NULL);
677 if (uri)
678 {
679 GFile *other;
680 other = g_file_new_for_uri (uri);
681 found = g_file_equal (file1: file, file2: other);
682 g_object_unref (object: other);
683 g_free (mem: uri);
684 }
685 }
686
687 return found;
688}
689
690typedef struct
691{
692 GtkPlacesSidebar *sidebar;
693 guint position;
694} ShortcutData;
695
696static void
697on_app_shortcuts_query_complete (GObject *source,
698 GAsyncResult *result,
699 gpointer data)
700{
701 ShortcutData *sdata = data;
702 GtkPlacesSidebar *sidebar = sdata->sidebar;
703 guint pos = sdata->position;
704 GFile *file = G_FILE (source);
705 GFileInfo *info;
706
707 g_free (mem: sdata);
708
709 info = g_file_query_info_finish (file, res: result, NULL);
710
711 if (info)
712 {
713 char *uri;
714 char *tooltip;
715 const char *name;
716 GIcon *start_icon;
717
718 name = g_file_info_get_display_name (info);
719 start_icon = g_file_info_get_symbolic_icon (info);
720 uri = g_file_get_uri (file);
721 tooltip = g_file_get_parse_name (file);
722
723 add_place (sidebar, place_type: GTK_PLACES_BUILT_IN,
724 section_type: GTK_PLACES_SECTION_COMPUTER,
725 name, start_icon, NULL, uri,
726 NULL, NULL, NULL, NULL,
727 index: pos,
728 tooltip);
729
730 g_free (mem: uri);
731 g_free (mem: tooltip);
732
733 g_object_unref (object: info);
734 }
735}
736
737static void
738add_application_shortcuts (GtkPlacesSidebar *sidebar)
739{
740 guint i, n;
741
742 n = g_list_model_get_n_items (list: G_LIST_MODEL (ptr: sidebar->shortcuts));
743 for (i = 0; i < n; i++)
744 {
745 GFile *file = g_list_model_get_item (list: G_LIST_MODEL (ptr: sidebar->shortcuts), position: i);
746 ShortcutData *data;
747
748 g_object_unref (object: file);
749
750 if (file_is_shown (sidebar, file))
751 continue;
752
753 data = g_new (ShortcutData, 1);
754 data->sidebar = sidebar;
755 data->position = i;
756 g_file_query_info_async (file,
757 attributes: "standard::display-name,standard::symbolic-icon",
758 flags: G_FILE_QUERY_INFO_NONE,
759 G_PRIORITY_DEFAULT,
760 cancellable: sidebar->cancellable,
761 callback: on_app_shortcuts_query_complete,
762 user_data: data);
763 }
764}
765
766typedef struct {
767 GtkPlacesSidebar *sidebar;
768 int index;
769 gboolean is_native;
770} BookmarkQueryClosure;
771
772static void
773on_bookmark_query_info_complete (GObject *source,
774 GAsyncResult *result,
775 gpointer data)
776{
777 BookmarkQueryClosure *clos = data;
778 GtkPlacesSidebar *sidebar = clos->sidebar;
779 GFile *root = G_FILE (source);
780 GError *error = NULL;
781 GFileInfo *info;
782 char *bookmark_name;
783 char *mount_uri;
784 char *tooltip;
785 GIcon *start_icon;
786
787 info = g_file_query_info_finish (file: root, res: result, error: &error);
788 if (g_error_matches (error, G_IO_ERROR, code: G_IO_ERROR_CANCELLED))
789 goto out;
790
791 bookmark_name = _gtk_bookmarks_manager_get_bookmark_label (manager: sidebar->bookmarks_manager, file: root);
792 if (bookmark_name == NULL && info != NULL)
793 bookmark_name = g_strdup (str: g_file_info_get_display_name (info));
794 else if (bookmark_name == NULL)
795 {
796 /* Don't add non-UTF-8 bookmarks */
797 bookmark_name = g_file_get_basename (file: root);
798 if (bookmark_name == NULL)
799 goto out;
800
801 if (!g_utf8_validate (str: bookmark_name, max_len: -1, NULL))
802 {
803 g_free (mem: bookmark_name);
804 goto out;
805 }
806 }
807
808 if (info)
809 start_icon = g_object_ref (g_file_info_get_symbolic_icon (info));
810 else
811 start_icon = g_themed_icon_new_with_default_fallbacks (iconname: clos->is_native ? ICON_NAME_FOLDER : ICON_NAME_FOLDER_NETWORK);
812
813 mount_uri = g_file_get_uri (file: root);
814 tooltip = g_file_get_parse_name (file: root);
815
816 add_place (sidebar, place_type: GTK_PLACES_BOOKMARK,
817 section_type: GTK_PLACES_SECTION_BOOKMARKS,
818 name: bookmark_name, start_icon, NULL, uri: mount_uri,
819 NULL, NULL, NULL, NULL, index: clos->index,
820 tooltip);
821
822 g_free (mem: mount_uri);
823 g_free (mem: tooltip);
824 g_free (mem: bookmark_name);
825 g_object_unref (object: start_icon);
826
827out:
828 g_clear_object (&info);
829 g_clear_error (err: &error);
830 g_slice_free (BookmarkQueryClosure, clos);
831}
832
833static gboolean
834is_external_volume (GVolume *volume)
835{
836 gboolean is_external;
837 GDrive *drive;
838 char *id;
839
840 drive = g_volume_get_drive (volume);
841 id = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_CLASS);
842
843 is_external = g_volume_can_eject (volume);
844
845 /* NULL volume identifier only happens on removable devices */
846 is_external |= !id;
847
848 if (drive)
849 is_external |= g_drive_is_removable (drive);
850
851 g_clear_object (&drive);
852 g_free (mem: id);
853
854 return is_external;
855}
856
857static void
858update_trash_icon (GtkPlacesSidebar *sidebar)
859{
860 if (sidebar->trash_row)
861 {
862 GIcon *icon;
863
864 icon = _gtk_trash_monitor_get_icon (monitor: sidebar->trash_monitor);
865 gtk_sidebar_row_set_start_icon (GTK_SIDEBAR_ROW (sidebar->trash_row), icon);
866 g_object_unref (object: icon);
867 }
868}
869
870#ifdef HAVE_CLOUDPROVIDERS
871
872static gboolean
873create_cloud_provider_account_row (GtkPlacesSidebar *sidebar,
874 CloudProvidersAccount *account)
875{
876 GIcon *end_icon;
877 GIcon *start_icon;
878 const char *mount_path;
879 const char *name;
880 char *mount_uri;
881 char *tooltip;
882 guint provider_account_status;
883
884 start_icon = cloud_providers_account_get_icon (account);
885 name = cloud_providers_account_get_name (account);
886 provider_account_status = cloud_providers_account_get_status (account);
887 mount_path = cloud_providers_account_get_path (account);
888 if (start_icon != NULL
889 && name != NULL
890 && provider_account_status != CLOUD_PROVIDERS_ACCOUNT_STATUS_INVALID
891 && mount_path != NULL)
892 {
893 switch (provider_account_status)
894 {
895 case CLOUD_PROVIDERS_ACCOUNT_STATUS_IDLE:
896 end_icon = NULL;
897 break;
898
899 case CLOUD_PROVIDERS_ACCOUNT_STATUS_SYNCING:
900 end_icon = g_themed_icon_new ("emblem-synchronizing-symbolic");
901 break;
902
903 case CLOUD_PROVIDERS_ACCOUNT_STATUS_ERROR:
904 end_icon = g_themed_icon_new ("dialog-warning-symbolic");
905 break;
906
907 default:
908 return FALSE;
909 }
910
911 mount_uri = g_strconcat ("file://", mount_path, NULL);
912
913 /* translators: %s is the name of a cloud provider for files */
914 tooltip = g_strdup_printf (_("Open %s"), name);
915
916 add_place (sidebar, GTK_PLACES_BUILT_IN,
917 GTK_PLACES_SECTION_CLOUD,
918 name, start_icon, end_icon, mount_uri,
919 NULL, NULL, NULL, account, 0,
920 tooltip);
921
922 g_free (tooltip);
923 g_free (mount_uri);
924 g_clear_object (&end_icon);
925
926 return TRUE;
927 }
928 else
929 {
930 return FALSE;
931 }
932}
933
934static void
935on_account_updated (GObject *object,
936 GParamSpec *pspec,
937 gpointer user_data)
938{
939 CloudProvidersAccount *account = CLOUD_PROVIDERS_ACCOUNT (object);
940 GtkPlacesSidebar *sidebar = GTK_PLACES_SIDEBAR (user_data);
941
942 if (create_cloud_provider_account_row (sidebar, account))
943 {
944 g_signal_handlers_disconnect_by_data (account, sidebar);
945 sidebar->unready_accounts = g_list_remove (sidebar->unready_accounts, account);
946 g_object_unref (account);
947 }
948}
949
950#endif
951
952static void
953update_places (GtkPlacesSidebar *sidebar)
954{
955 GList *mounts, *l, *ll;
956 GMount *mount;
957 GList *drives;
958 GDrive *drive;
959 GList *volumes;
960 GVolume *volume;
961 GSList *bookmarks, *sl;
962 int index;
963 char *original_uri, *name, *identifier;
964 GtkListBoxRow *selected;
965 char *home_uri;
966 GIcon *start_icon;
967 GFile *root;
968 char *tooltip;
969 GList *network_mounts, *network_volumes;
970 GIcon *new_bookmark_icon;
971 GtkWidget *child;
972#ifdef HAVE_CLOUDPROVIDERS
973 GList *cloud_providers;
974 GList *cloud_providers_accounts;
975 CloudProvidersAccount *cloud_provider_account;
976 CloudProvidersProvider *cloud_provider;
977#endif
978
979 /* save original selection */
980 selected = gtk_list_box_get_selected_row (GTK_LIST_BOX (sidebar->list_box));
981 if (selected)
982 g_object_get (object: selected, first_property_name: "uri", &original_uri, NULL);
983 else
984 original_uri = NULL;
985
986 g_cancellable_cancel (cancellable: sidebar->cancellable);
987
988 g_object_unref (object: sidebar->cancellable);
989 sidebar->cancellable = g_cancellable_new ();
990
991 /* Reset drag state, just in case we update the places while dragging or
992 * ending a drag */
993 stop_drop_feedback (sidebar);
994 while ((child = gtk_widget_get_first_child (GTK_WIDGET (sidebar->list_box))))
995 gtk_list_box_remove (GTK_LIST_BOX (sidebar->list_box), child);
996
997 network_mounts = network_volumes = NULL;
998
999 /* add built-in places */
1000 if (should_show_recent (sidebar))
1001 {
1002 start_icon = g_themed_icon_new_with_default_fallbacks (iconname: "document-open-recent-symbolic");
1003 add_place (sidebar, place_type: GTK_PLACES_BUILT_IN,
1004 section_type: GTK_PLACES_SECTION_COMPUTER,
1005 _("Recent"), start_icon, NULL, uri: "recent:///",
1006 NULL, NULL, NULL, NULL, index: 0,
1007 _("Recent files"));
1008 g_object_unref (object: start_icon);
1009 }
1010
1011 if (sidebar->show_starred_location)
1012 {
1013 start_icon = g_themed_icon_new_with_default_fallbacks (iconname: "starred-symbolic");
1014 add_place (sidebar, place_type: GTK_PLACES_STARRED_LOCATION,
1015 section_type: GTK_PLACES_SECTION_COMPUTER,
1016 _("Starred"), start_icon, NULL, uri: "starred:///",
1017 NULL, NULL, NULL, NULL, index: 0,
1018 _("Starred files"));
1019 g_object_unref (object: start_icon);
1020 }
1021
1022 /* home folder */
1023 home_uri = get_home_directory_uri ();
1024 start_icon = g_themed_icon_new_with_default_fallbacks (ICON_NAME_HOME);
1025 add_place (sidebar, place_type: GTK_PLACES_BUILT_IN,
1026 section_type: GTK_PLACES_SECTION_COMPUTER,
1027 _("Home"), start_icon, NULL, uri: home_uri,
1028 NULL, NULL, NULL, NULL, index: 0,
1029 _("Open your personal folder"));
1030 g_object_unref (object: start_icon);
1031 g_free (mem: home_uri);
1032
1033 /* desktop */
1034 if (sidebar->show_desktop)
1035 {
1036 char *mount_uri = get_desktop_directory_uri ();
1037 if (mount_uri)
1038 {
1039 start_icon = g_themed_icon_new_with_default_fallbacks (ICON_NAME_DESKTOP);
1040 add_place (sidebar, place_type: GTK_PLACES_BUILT_IN,
1041 section_type: GTK_PLACES_SECTION_COMPUTER,
1042 _("Desktop"), start_icon, NULL, uri: mount_uri,
1043 NULL, NULL, NULL, NULL, index: 0,
1044 _("Open the contents of your desktop in a folder"));
1045 g_object_unref (object: start_icon);
1046 g_free (mem: mount_uri);
1047 }
1048 }
1049
1050 /* XDG directories */
1051 add_special_dirs (sidebar);
1052
1053 if (sidebar->show_enter_location)
1054 {
1055 start_icon = g_themed_icon_new_with_default_fallbacks (ICON_NAME_NETWORK_SERVER);
1056 add_place (sidebar, place_type: GTK_PLACES_ENTER_LOCATION,
1057 section_type: GTK_PLACES_SECTION_COMPUTER,
1058 _("Enter Location"), start_icon, NULL, NULL,
1059 NULL, NULL, NULL, NULL, index: 0,
1060 _("Manually enter a location"));
1061 g_object_unref (object: start_icon);
1062 }
1063
1064 /* Trash */
1065 if (sidebar->show_trash)
1066 {
1067 start_icon = _gtk_trash_monitor_get_icon (monitor: sidebar->trash_monitor);
1068 sidebar->trash_row = add_place (sidebar, place_type: GTK_PLACES_BUILT_IN,
1069 section_type: GTK_PLACES_SECTION_COMPUTER,
1070 _("Trash"), start_icon, NULL, uri: "trash:///",
1071 NULL, NULL, NULL, NULL, index: 0,
1072 _("Open the trash"));
1073 g_object_add_weak_pointer (G_OBJECT (sidebar->trash_row),
1074 weak_pointer_location: (gpointer *) &sidebar->trash_row);
1075 g_object_unref (object: start_icon);
1076 }
1077
1078 /* Application-side shortcuts */
1079 add_application_shortcuts (sidebar);
1080
1081 /* Cloud providers */
1082#ifdef HAVE_CLOUDPROVIDERS
1083 cloud_providers = cloud_providers_collector_get_providers (sidebar->cloud_manager);
1084 for (l = sidebar->unready_accounts; l != NULL; l = l->next)
1085 {
1086 g_signal_handlers_disconnect_by_data (l->data, sidebar);
1087 }
1088 g_list_free_full (sidebar->unready_accounts, g_object_unref);
1089 sidebar->unready_accounts = NULL;
1090 for (l = cloud_providers; l != NULL; l = l->next)
1091 {
1092 cloud_provider = CLOUD_PROVIDERS_PROVIDER (l->data);
1093 g_signal_connect_swapped (cloud_provider, "accounts-changed",
1094 G_CALLBACK (update_places), sidebar);
1095 cloud_providers_accounts = cloud_providers_provider_get_accounts (cloud_provider);
1096 for (ll = cloud_providers_accounts; ll != NULL; ll = ll->next)
1097 {
1098 cloud_provider_account = CLOUD_PROVIDERS_ACCOUNT (ll->data);
1099 if (!create_cloud_provider_account_row (sidebar, cloud_provider_account))
1100 {
1101
1102 g_signal_connect (cloud_provider_account, "notify::name",
1103 G_CALLBACK (on_account_updated), sidebar);
1104 g_signal_connect (cloud_provider_account, "notify::status",
1105 G_CALLBACK (on_account_updated), sidebar);
1106 g_signal_connect (cloud_provider_account, "notify::status-details",
1107 G_CALLBACK (on_account_updated), sidebar);
1108 g_signal_connect (cloud_provider_account, "notify::path",
1109 G_CALLBACK (on_account_updated), sidebar);
1110 sidebar->unready_accounts = g_list_append (sidebar->unready_accounts,
1111 g_object_ref (cloud_provider_account));
1112 continue;
1113 }
1114
1115 }
1116 }
1117#endif
1118
1119 /* go through all connected drives */
1120 drives = g_volume_monitor_get_connected_drives (volume_monitor: sidebar->volume_monitor);
1121
1122 for (l = drives; l != NULL; l = l->next)
1123 {
1124 drive = l->data;
1125
1126 volumes = g_drive_get_volumes (drive);
1127 if (volumes != NULL)
1128 {
1129 for (ll = volumes; ll != NULL; ll = ll->next)
1130 {
1131 volume = ll->data;
1132 identifier = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_CLASS);
1133
1134 if (g_strcmp0 (str1: identifier, str2: "network") == 0)
1135 {
1136 g_free (mem: identifier);
1137 network_volumes = g_list_prepend (list: network_volumes, data: volume);
1138 continue;
1139 }
1140 g_free (mem: identifier);
1141
1142 if (sidebar->show_other_locations && !is_external_volume (volume))
1143 {
1144 g_object_unref (object: volume);
1145 continue;
1146 }
1147
1148 mount = g_volume_get_mount (volume);
1149 if (mount != NULL)
1150 {
1151 char *mount_uri;
1152
1153 /* Show mounted volume in the sidebar */
1154 start_icon = g_mount_get_symbolic_icon (mount);
1155 root = g_mount_get_default_location (mount);
1156 mount_uri = g_file_get_uri (file: root);
1157 name = g_mount_get_name (mount);
1158 tooltip = g_file_get_parse_name (file: root);
1159
1160 add_place (sidebar, place_type: GTK_PLACES_MOUNTED_VOLUME,
1161 section_type: GTK_PLACES_SECTION_MOUNTS,
1162 name, start_icon, NULL, uri: mount_uri,
1163 drive, volume, mount, NULL, index: 0, tooltip);
1164 g_object_unref (object: root);
1165 g_object_unref (object: mount);
1166 g_object_unref (object: start_icon);
1167 g_free (mem: tooltip);
1168 g_free (mem: name);
1169 g_free (mem: mount_uri);
1170 }
1171 else
1172 {
1173 /* Do show the unmounted volumes in the sidebar;
1174 * this is so the user can mount it (in case automounting
1175 * is off).
1176 *
1177 * Also, even if automounting is enabled, this gives a visual
1178 * cue that the user should remember to yank out the media if
1179 * he just unmounted it.
1180 */
1181 start_icon = g_volume_get_symbolic_icon (volume);
1182 name = g_volume_get_name (volume);
1183 tooltip = g_strdup_printf (_("Mount and open “%s”"), name);
1184
1185 add_place (sidebar, place_type: GTK_PLACES_MOUNTED_VOLUME,
1186 section_type: GTK_PLACES_SECTION_MOUNTS,
1187 name, start_icon, NULL, NULL,
1188 drive, volume, NULL, NULL, index: 0, tooltip);
1189 g_object_unref (object: start_icon);
1190 g_free (mem: name);
1191 g_free (mem: tooltip);
1192 }
1193 g_object_unref (object: volume);
1194 }
1195 g_list_free (list: volumes);
1196 }
1197 else
1198 {
1199 if (g_drive_is_media_removable (drive) && !g_drive_is_media_check_automatic (drive))
1200 {
1201 /* If the drive has no mountable volumes and we cannot detect media change.. we
1202 * display the drive in the sidebar so the user can manually poll the drive by
1203 * right clicking and selecting "Rescan..."
1204 *
1205 * This is mainly for drives like floppies where media detection doesn't
1206 * work.. but it's also for human beings who like to turn off media detection
1207 * in the OS to save battery juice.
1208 */
1209 start_icon = g_drive_get_symbolic_icon (drive);
1210 name = g_drive_get_name (drive);
1211 tooltip = g_strdup_printf (_("Mount and open “%s”"), name);
1212
1213 add_place (sidebar, place_type: GTK_PLACES_BUILT_IN,
1214 section_type: GTK_PLACES_SECTION_MOUNTS,
1215 name, start_icon, NULL, NULL,
1216 drive, NULL, NULL, NULL, index: 0, tooltip);
1217 g_object_unref (object: start_icon);
1218 g_free (mem: tooltip);
1219 g_free (mem: name);
1220 }
1221 }
1222 }
1223 g_list_free_full (list: drives, free_func: g_object_unref);
1224
1225 /* add all network volumes that are not associated with a drive, and
1226 * loop devices
1227 */
1228 volumes = g_volume_monitor_get_volumes (volume_monitor: sidebar->volume_monitor);
1229 for (l = volumes; l != NULL; l = l->next)
1230 {
1231 gboolean is_loop = FALSE;
1232 volume = l->data;
1233 drive = g_volume_get_drive (volume);
1234 if (drive != NULL)
1235 {
1236 g_object_unref (object: volume);
1237 g_object_unref (object: drive);
1238 continue;
1239 }
1240
1241 identifier = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_CLASS);
1242
1243 if (g_strcmp0 (str1: identifier, str2: "network") == 0)
1244 {
1245 g_free (mem: identifier);
1246 network_volumes = g_list_prepend (list: network_volumes, data: volume);
1247 continue;
1248 }
1249 else if (g_strcmp0 (str1: identifier, str2: "loop") == 0)
1250 is_loop = TRUE;
1251 g_free (mem: identifier);
1252
1253 if (sidebar->show_other_locations &&
1254 !is_external_volume (volume) &&
1255 !is_loop)
1256 {
1257 g_object_unref (object: volume);
1258 continue;
1259 }
1260
1261 mount = g_volume_get_mount (volume);
1262 if (mount != NULL)
1263 {
1264 char *mount_uri;
1265
1266 start_icon = g_mount_get_symbolic_icon (mount);
1267 root = g_mount_get_default_location (mount);
1268 mount_uri = g_file_get_uri (file: root);
1269 tooltip = g_file_get_parse_name (file: root);
1270 name = g_mount_get_name (mount);
1271 add_place (sidebar, place_type: GTK_PLACES_MOUNTED_VOLUME,
1272 section_type: GTK_PLACES_SECTION_MOUNTS,
1273 name, start_icon, NULL, uri: mount_uri,
1274 NULL, volume, mount, NULL, index: 0, tooltip);
1275 g_object_unref (object: mount);
1276 g_object_unref (object: root);
1277 g_object_unref (object: start_icon);
1278 g_free (mem: name);
1279 g_free (mem: tooltip);
1280 g_free (mem: mount_uri);
1281 }
1282 else
1283 {
1284 /* see comment above in why we add an icon for an unmounted mountable volume */
1285 start_icon = g_volume_get_symbolic_icon (volume);
1286 name = g_volume_get_name (volume);
1287 add_place (sidebar, place_type: GTK_PLACES_MOUNTED_VOLUME,
1288 section_type: GTK_PLACES_SECTION_MOUNTS,
1289 name, start_icon, NULL, NULL,
1290 NULL, volume, NULL, NULL, index: 0, tooltip: name);
1291 g_object_unref (object: start_icon);
1292 g_free (mem: name);
1293 }
1294 g_object_unref (object: volume);
1295 }
1296 g_list_free (list: volumes);
1297
1298 /* file system root */
1299 if (!sidebar->show_other_locations)
1300 {
1301 start_icon = g_themed_icon_new_with_default_fallbacks (ICON_NAME_FILESYSTEM);
1302 add_place (sidebar, place_type: GTK_PLACES_BUILT_IN,
1303 section_type: GTK_PLACES_SECTION_MOUNTS,
1304 name: sidebar->hostname, start_icon, NULL, uri: "file:///",
1305 NULL, NULL, NULL, NULL, index: 0,
1306 _("Open the contents of the file system"));
1307 g_object_unref (object: start_icon);
1308 }
1309
1310 /* add mounts that has no volume (/etc/mtab mounts, ftp, sftp,...) */
1311 mounts = g_volume_monitor_get_mounts (volume_monitor: sidebar->volume_monitor);
1312
1313 for (l = mounts; l != NULL; l = l->next)
1314 {
1315 char *mount_uri;
1316
1317 mount = l->data;
1318 if (g_mount_is_shadowed (mount))
1319 {
1320 g_object_unref (object: mount);
1321 continue;
1322 }
1323 volume = g_mount_get_volume (mount);
1324 if (volume != NULL)
1325 {
1326 g_object_unref (object: volume);
1327 g_object_unref (object: mount);
1328 continue;
1329 }
1330 root = g_mount_get_default_location (mount);
1331
1332 if (!g_file_is_native (file: root))
1333 {
1334 network_mounts = g_list_prepend (list: network_mounts, data: mount);
1335 g_object_unref (object: root);
1336 continue;
1337 }
1338
1339 start_icon = g_mount_get_symbolic_icon (mount);
1340 mount_uri = g_file_get_uri (file: root);
1341 name = g_mount_get_name (mount);
1342 tooltip = g_file_get_parse_name (file: root);
1343 add_place (sidebar, place_type: GTK_PLACES_MOUNTED_VOLUME,
1344 section_type: GTK_PLACES_SECTION_COMPUTER,
1345 name, start_icon, NULL, uri: mount_uri,
1346 NULL, NULL, mount, NULL, index: 0, tooltip);
1347 g_object_unref (object: root);
1348 g_object_unref (object: mount);
1349 g_object_unref (object: start_icon);
1350 g_free (mem: name);
1351 g_free (mem: mount_uri);
1352 g_free (mem: tooltip);
1353 }
1354 g_list_free (list: mounts);
1355
1356 /* add bookmarks */
1357 bookmarks = _gtk_bookmarks_manager_list_bookmarks (manager: sidebar->bookmarks_manager);
1358
1359 for (sl = bookmarks, index = 0; sl; sl = sl->next, index++)
1360 {
1361 gboolean is_native;
1362 BookmarkQueryClosure *clos;
1363
1364 root = sl->data;
1365 is_native = g_file_is_native (file: root);
1366
1367 if (_gtk_bookmarks_manager_get_is_builtin (manager: sidebar->bookmarks_manager, file: root))
1368 continue;
1369
1370 clos = g_slice_new (BookmarkQueryClosure);
1371 clos->sidebar = sidebar;
1372 clos->index = index;
1373 clos->is_native = is_native;
1374 g_file_query_info_async (file: root,
1375 attributes: "standard::display-name,standard::symbolic-icon",
1376 flags: G_FILE_QUERY_INFO_NONE,
1377 G_PRIORITY_DEFAULT,
1378 cancellable: sidebar->cancellable,
1379 callback: on_bookmark_query_info_complete,
1380 user_data: clos);
1381 }
1382
1383 g_slist_free_full (list: bookmarks, free_func: g_object_unref);
1384
1385 /* Add new bookmark row */
1386 new_bookmark_icon = g_themed_icon_new (iconname: "bookmark-new-symbolic");
1387 sidebar->new_bookmark_row = add_place (sidebar, place_type: GTK_PLACES_DROP_FEEDBACK,
1388 section_type: GTK_PLACES_SECTION_BOOKMARKS,
1389 _("New bookmark"), start_icon: new_bookmark_icon, NULL, NULL,
1390 NULL, NULL, NULL, NULL, index: 0,
1391 _("Add a new bookmark"));
1392 gtk_widget_add_css_class (widget: sidebar->new_bookmark_row, css_class: "sidebar-new-bookmark-row");
1393 g_object_unref (object: new_bookmark_icon);
1394
1395 /* network */
1396 network_volumes = g_list_reverse (list: network_volumes);
1397 for (l = network_volumes; l != NULL; l = l->next)
1398 {
1399 volume = l->data;
1400 mount = g_volume_get_mount (volume);
1401
1402 if (mount != NULL)
1403 {
1404 network_mounts = g_list_prepend (list: network_mounts, data: mount);
1405 continue;
1406 }
1407 else
1408 {
1409 start_icon = g_volume_get_symbolic_icon (volume);
1410 name = g_volume_get_name (volume);
1411 tooltip = g_strdup_printf (_("Mount and open “%s”"), name);
1412
1413 add_place (sidebar, place_type: GTK_PLACES_MOUNTED_VOLUME,
1414 section_type: GTK_PLACES_SECTION_MOUNTS,
1415 name, start_icon, NULL, NULL,
1416 NULL, volume, NULL, NULL, index: 0, tooltip);
1417 g_object_unref (object: start_icon);
1418 g_free (mem: name);
1419 g_free (mem: tooltip);
1420 }
1421 }
1422
1423 network_mounts = g_list_reverse (list: network_mounts);
1424 for (l = network_mounts; l != NULL; l = l->next)
1425 {
1426 char *mount_uri;
1427
1428 mount = l->data;
1429 root = g_mount_get_default_location (mount);
1430 start_icon = g_mount_get_symbolic_icon (mount);
1431 mount_uri = g_file_get_uri (file: root);
1432 name = g_mount_get_name (mount);
1433 tooltip = g_file_get_parse_name (file: root);
1434 add_place (sidebar, place_type: GTK_PLACES_MOUNTED_VOLUME,
1435 section_type: GTK_PLACES_SECTION_MOUNTS,
1436 name, start_icon, NULL, uri: mount_uri,
1437 NULL, NULL, mount, NULL, index: 0, tooltip);
1438 g_object_unref (object: root);
1439 g_object_unref (object: start_icon);
1440 g_free (mem: name);
1441 g_free (mem: mount_uri);
1442 g_free (mem: tooltip);
1443 }
1444
1445
1446 g_list_free_full (list: network_volumes, free_func: g_object_unref);
1447 g_list_free_full (list: network_mounts, free_func: g_object_unref);
1448
1449 /* Other locations */
1450 if (sidebar->show_other_locations)
1451 {
1452 start_icon = g_themed_icon_new_with_default_fallbacks (ICON_NAME_OTHER_LOCATIONS);
1453
1454 add_place (sidebar, place_type: GTK_PLACES_OTHER_LOCATIONS,
1455 section_type: GTK_PLACES_SECTION_OTHER_LOCATIONS,
1456 _("Other Locations"), start_icon, NULL, uri: "other-locations:///",
1457 NULL, NULL, NULL, NULL, index: 0, _("Show other locations"));
1458
1459 g_object_unref (object: start_icon);
1460 }
1461
1462 gtk_widget_show (GTK_WIDGET (sidebar));
1463 /* We want this hidden by default, but need to do it after the show_all call */
1464 gtk_sidebar_row_hide (GTK_SIDEBAR_ROW (sidebar->new_bookmark_row), TRUE);
1465
1466 /* restore original selection */
1467 if (original_uri)
1468 {
1469 GFile *restore;
1470
1471 restore = g_file_new_for_uri (uri: original_uri);
1472 gtk_places_sidebar_set_location (sidebar, location: restore);
1473 g_object_unref (object: restore);
1474 g_free (mem: original_uri);
1475 }
1476}
1477
1478static gboolean
1479check_valid_drop_target (GtkPlacesSidebar *sidebar,
1480 GtkSidebarRow *row,
1481 const GValue *value)
1482{
1483 GtkPlacesPlaceType place_type;
1484 GtkPlacesSectionType section_type;
1485 gboolean valid = FALSE;
1486 char *uri;
1487 GFile *dest_file;
1488 int drag_action;
1489
1490 g_return_val_if_fail (value != NULL, TRUE);
1491
1492 if (row == NULL)
1493 return FALSE;
1494
1495 g_object_get (object: row,
1496 first_property_name: "place-type", &place_type,
1497 "section_type", &section_type,
1498 "uri", &uri,
1499 NULL);
1500
1501 if (place_type == GTK_PLACES_STARRED_LOCATION)
1502 {
1503 g_free (mem: uri);
1504 return FALSE;
1505 }
1506
1507 if (place_type == GTK_PLACES_CONNECT_TO_SERVER)
1508 {
1509 g_free (mem: uri);
1510 return FALSE;
1511 }
1512
1513 if (place_type == GTK_PLACES_DROP_FEEDBACK)
1514 {
1515 g_free (mem: uri);
1516 return TRUE;
1517 }
1518
1519 /* Disallow drops on recent:/// */
1520 if (place_type == GTK_PLACES_BUILT_IN)
1521 {
1522 if (g_strcmp0 (str1: uri, str2: "recent:///") == 0)
1523 {
1524 g_free (mem: uri);
1525 return FALSE;
1526 }
1527 }
1528
1529 /* Dragging a bookmark? */
1530 if (G_VALUE_HOLDS (value, GTK_TYPE_SIDEBAR_ROW))
1531 {
1532 /* Don't allow reordering bookmarks into non-bookmark areas */
1533 valid = section_type == GTK_PLACES_SECTION_BOOKMARKS;
1534 }
1535 else if (G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST))
1536 {
1537 /* Dragging a file */
1538 if (uri != NULL)
1539 {
1540 dest_file = g_file_new_for_uri (uri);
1541 drag_action = emit_drag_action_requested (sidebar, dest_file, source_file_list: g_value_get_boxed (value));
1542 valid = drag_action > 0;
1543
1544 g_object_unref (object: dest_file);
1545 }
1546 else
1547 {
1548 valid = FALSE;
1549 }
1550 }
1551 else
1552 {
1553 g_assert_not_reached ();
1554 valid = TRUE;
1555 }
1556
1557 g_free (mem: uri);
1558 return valid;
1559}
1560
1561static void
1562update_possible_drop_targets (GtkPlacesSidebar *sidebar,
1563 const GValue *value)
1564{
1565 GtkWidget *row;
1566
1567 for (row = gtk_widget_get_first_child (GTK_WIDGET (sidebar->list_box));
1568 row != NULL;
1569 row = gtk_widget_get_next_sibling (widget: row))
1570 {
1571 gboolean sensitive;
1572
1573 if (!GTK_IS_LIST_BOX_ROW (row))
1574 continue;
1575
1576 sensitive = value == NULL ||
1577 check_valid_drop_target (sidebar, GTK_SIDEBAR_ROW (row), value);
1578 gtk_widget_set_sensitive (widget: row, sensitive);
1579 }
1580}
1581
1582static void
1583start_drop_feedback (GtkPlacesSidebar *sidebar,
1584 const GValue *value)
1585{
1586 if (value && !G_VALUE_HOLDS (value, GTK_TYPE_SIDEBAR_ROW))
1587 {
1588 gtk_sidebar_row_reveal (GTK_SIDEBAR_ROW (sidebar->new_bookmark_row));
1589 /* If the state is permanent, don't change it. The application controls it. */
1590 if (sidebar->drop_state != DROP_STATE_NEW_BOOKMARK_ARMED_PERMANENT)
1591 sidebar->drop_state = DROP_STATE_NEW_BOOKMARK_ARMED;
1592 }
1593
1594 update_possible_drop_targets (sidebar, value);
1595}
1596
1597static void
1598stop_drop_feedback (GtkPlacesSidebar *sidebar)
1599{
1600 update_possible_drop_targets (sidebar, NULL);
1601
1602 if (sidebar->drop_state != DROP_STATE_NEW_BOOKMARK_ARMED_PERMANENT &&
1603 sidebar->new_bookmark_row != NULL)
1604 {
1605 gtk_sidebar_row_hide (GTK_SIDEBAR_ROW (sidebar->new_bookmark_row), FALSE);
1606 sidebar->drop_state = DROP_STATE_NORMAL;
1607 }
1608
1609 if (sidebar->drag_row != NULL)
1610 {
1611 gtk_widget_show (widget: sidebar->drag_row);
1612 sidebar->drag_row = NULL;
1613 }
1614
1615 if (sidebar->row_placeholder != NULL)
1616 {
1617 if (gtk_widget_get_parent (widget: sidebar->row_placeholder) != NULL)
1618 gtk_list_box_remove (GTK_LIST_BOX (sidebar->list_box), child: sidebar->row_placeholder);
1619 sidebar->row_placeholder = NULL;
1620 }
1621
1622 sidebar->dragging_over = FALSE;
1623}
1624
1625static GtkWidget *
1626create_placeholder_row (GtkPlacesSidebar *sidebar)
1627{
1628 return g_object_new (GTK_TYPE_SIDEBAR_ROW, first_property_name: "placeholder", TRUE, NULL);
1629}
1630
1631static GdkDragAction
1632drag_motion_callback (GtkDropTarget *target,
1633 double x,
1634 double y,
1635 GtkPlacesSidebar *sidebar)
1636{
1637 GdkDragAction action;
1638 GtkListBoxRow *row;
1639 GtkPlacesPlaceType place_type;
1640 char *drop_target_uri = NULL;
1641 int row_index;
1642 int row_placeholder_index;
1643 const GValue *value;
1644
1645 sidebar->dragging_over = TRUE;
1646 action = 0;
1647 row = gtk_list_box_get_row_at_y (GTK_LIST_BOX (sidebar->list_box), y);
1648
1649 gtk_list_box_drag_unhighlight_row (GTK_LIST_BOX (sidebar->list_box));
1650
1651 /* Nothing to do if no value yet */
1652 value = gtk_drop_target_get_value (self: target);
1653 if (value == NULL)
1654 goto out;
1655
1656 /* Nothing to do if the target is not valid drop destination */
1657 if (!check_valid_drop_target (sidebar, GTK_SIDEBAR_ROW (row), value))
1658 goto out;
1659
1660 if (G_VALUE_HOLDS (value, GTK_TYPE_SIDEBAR_ROW))
1661 {
1662 /* Dragging bookmarks always moves them to another position in the bookmarks list */
1663 action = GDK_ACTION_MOVE;
1664 if (sidebar->row_placeholder == NULL)
1665 {
1666 sidebar->row_placeholder = create_placeholder_row (sidebar);
1667 g_object_ref_sink (sidebar->row_placeholder);
1668 }
1669 else if (GTK_WIDGET (row) == sidebar->row_placeholder)
1670 {
1671 goto out;
1672 }
1673
1674 if (gtk_widget_get_parent (widget: sidebar->row_placeholder) != NULL)
1675 gtk_list_box_remove (GTK_LIST_BOX (sidebar->list_box), child: sidebar->row_placeholder);
1676
1677 if (row != NULL)
1678 {
1679 g_object_get (object: row, first_property_name: "order-index", &row_index, NULL);
1680 g_object_get (object: sidebar->row_placeholder, first_property_name: "order-index", &row_placeholder_index, NULL);
1681 /* We order the bookmarks sections based on the bookmark index that we
1682 * set on the row as order-index property, but we have to deal with
1683 * the placeholder row wanting to be between two consecutive bookmarks,
1684 * with two consecutive order-index values which is the usual case.
1685 * For that, in the list box sort func we give priority to the placeholder row,
1686 * that means that if the index-order is the same as another bookmark
1687 * the placeholder row goes before. However if we want to show it after
1688 * the current row, for instance when the cursor is in the lower half
1689 * of the row, we need to increase the order-index.
1690 */
1691 row_placeholder_index = row_index;
1692 gtk_widget_translate_coordinates (GTK_WIDGET (sidebar), GTK_WIDGET (row),
1693 src_x: x, src_y: y,
1694 dest_x: &x, dest_y: &y);
1695
1696 if (y > sidebar->drag_row_height / 2 && row_index > 0)
1697 row_placeholder_index++;
1698 }
1699 else
1700 {
1701 /* If the user is dragging over an area that has no row, place the row
1702 * placeholder in the last position
1703 */
1704 row_placeholder_index = G_MAXINT32;
1705 }
1706
1707 g_object_set (object: sidebar->row_placeholder, first_property_name: "order-index", row_placeholder_index, NULL);
1708
1709 gtk_list_box_prepend (GTK_LIST_BOX (sidebar->list_box),
1710 child: sidebar->row_placeholder);
1711 }
1712 else if (G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST))
1713 {
1714 gtk_list_box_drag_highlight_row (GTK_LIST_BOX (sidebar->list_box), row);
1715
1716 g_object_get (object: row,
1717 first_property_name: "place-type", &place_type,
1718 "uri", &drop_target_uri,
1719 NULL);
1720 /* URIs are being dragged. See if the caller wants to handle a
1721 * file move/copy operation itself, or if we should only try to
1722 * create bookmarks out of the dragged URIs.
1723 */
1724 if (place_type == GTK_PLACES_DROP_FEEDBACK)
1725 {
1726 action = GDK_ACTION_COPY;
1727 }
1728 else
1729 {
1730 /* uri may be NULL for unmounted volumes, for example, so we don't allow drops there */
1731 if (drop_target_uri != NULL)
1732 {
1733 GFile *dest_file = g_file_new_for_uri (uri: drop_target_uri);
1734
1735 action = emit_drag_action_requested (sidebar, dest_file, source_file_list: g_value_get_boxed (value));
1736
1737 g_object_unref (object: dest_file);
1738 }
1739 }
1740
1741 g_free (mem: drop_target_uri);
1742 }
1743 else
1744 {
1745 g_assert_not_reached ();
1746 }
1747
1748 out:
1749 start_drop_feedback (sidebar, value);
1750 return action;
1751}
1752
1753/* Reorders the bookmark to the specified position */
1754static void
1755reorder_bookmarks (GtkPlacesSidebar *sidebar,
1756 GtkSidebarRow *row,
1757 int new_position)
1758{
1759 char *uri;
1760 GFile *file;
1761
1762 g_object_get (object: row, first_property_name: "uri", &uri, NULL);
1763 file = g_file_new_for_uri (uri);
1764 _gtk_bookmarks_manager_reorder_bookmark (manager: sidebar->bookmarks_manager, file, new_position, NULL);
1765
1766 g_object_unref (object: file);
1767 g_free (mem: uri);
1768}
1769
1770/* Creates bookmarks for the specified files at the given position in the bookmarks list */
1771static void
1772drop_files_as_bookmarks (GtkPlacesSidebar *sidebar,
1773 GSList *files,
1774 int position)
1775{
1776 GSList *l;
1777
1778 for (l = files; l; l = l->next)
1779 {
1780 GFile *f = G_FILE (l->data);
1781 GFileInfo *info = g_file_query_info (file: f,
1782 G_FILE_ATTRIBUTE_STANDARD_TYPE,
1783 flags: G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
1784 NULL,
1785 NULL);
1786
1787 if (info)
1788 {
1789 if (_gtk_file_info_consider_as_directory (info))
1790 _gtk_bookmarks_manager_insert_bookmark (manager: sidebar->bookmarks_manager, file: f, position: position++, NULL);
1791
1792 g_object_unref (object: info);
1793 }
1794 }
1795}
1796
1797static gboolean
1798drag_drop_callback (GtkDropTarget *target,
1799 const GValue *value,
1800 double x,
1801 double y,
1802 GtkPlacesSidebar *sidebar)
1803{
1804 int target_order_index;
1805 GtkPlacesPlaceType target_place_type;
1806 GtkPlacesSectionType target_section_type;
1807 char *target_uri;
1808 GtkListBoxRow *target_row;
1809 gboolean result;
1810
1811 target_row = gtk_list_box_get_row_at_y (GTK_LIST_BOX (sidebar->list_box), y);
1812 if (target_row == NULL)
1813 return FALSE;
1814
1815 if (!check_valid_drop_target (sidebar, GTK_SIDEBAR_ROW (target_row), value))
1816 return FALSE;
1817
1818 g_object_get (object: target_row,
1819 first_property_name: "place-type", &target_place_type,
1820 "section-type", &target_section_type,
1821 "order-index", &target_order_index,
1822 "uri", &target_uri,
1823 NULL);
1824 result = FALSE;
1825
1826 if (G_VALUE_HOLDS (value, GTK_TYPE_SIDEBAR_ROW))
1827 {
1828 GtkWidget *source_row;
1829 /* A bookmark got reordered */
1830 if (target_section_type != GTK_PLACES_SECTION_BOOKMARKS)
1831 goto out;
1832
1833 source_row = g_value_get_object (value);
1834
1835 if (sidebar->row_placeholder != NULL)
1836 g_object_get (object: sidebar->row_placeholder, first_property_name: "order-index", &target_order_index, NULL);
1837
1838 reorder_bookmarks (sidebar, GTK_SIDEBAR_ROW (source_row), new_position: target_order_index);
1839 result = TRUE;
1840 }
1841 else if (G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST))
1842 {
1843 /* Dropping URIs! */
1844 if (target_place_type == GTK_PLACES_DROP_FEEDBACK)
1845 {
1846 drop_files_as_bookmarks (sidebar, files: g_value_get_boxed (value), position: target_order_index);
1847 }
1848 else
1849 {
1850 GFile *dest_file = g_file_new_for_uri (uri: target_uri);
1851
1852 emit_drag_perform_drop (sidebar,
1853 dest_file,
1854 source_file_list: g_value_get_boxed (value),
1855 action: gdk_drop_get_actions (self: gtk_drop_target_get_current_drop (self: target)));
1856
1857 g_object_unref (object: dest_file);
1858 }
1859 result = TRUE;
1860 }
1861 else
1862 {
1863 g_assert_not_reached ();
1864 }
1865
1866out:
1867 stop_drop_feedback (sidebar);
1868 g_free (mem: target_uri);
1869 return result;
1870}
1871
1872static void
1873dnd_finished_cb (GdkDrag *drag,
1874 GtkPlacesSidebar *sidebar)
1875{
1876 stop_drop_feedback (sidebar);
1877}
1878
1879static void
1880dnd_cancel_cb (GdkDrag *drag,
1881 GdkDragCancelReason reason,
1882 GtkPlacesSidebar *sidebar)
1883{
1884 stop_drop_feedback (sidebar);
1885}
1886
1887/* This functions is called every time the drag source leaves
1888 * the sidebar widget.
1889 * The problem is that, we start showing hints for drop when the source
1890 * start being above the sidebar or when the application request so show
1891 * drop hints, but at some moment we need to restore to normal
1892 * state.
1893 * One could think that here we could simply call stop_drop_feedback,
1894 * but that's not true, because this function is called also before drag_drop,
1895 * which needs the data from the drag so we cannot free the drag data here.
1896 * So now one could think we could just do nothing here, and wait for
1897 * drag-end or drag-cancel signals and just stop_drop_feedback there. But that
1898 * is also not true, since when the drag comes from a different widget than the
1899 * sidebar, when the drag stops the last drag signal we receive is drag-leave.
1900 * So here what we will do is restore the state of the sidebar as if no drag
1901 * is being done (and if the application didn't request for permanent hints with
1902 * gtk_places_sidebar_show_drop_hints) and we will free the drag data next time
1903 * we build new drag data in drag_data_received.
1904 */
1905static void
1906drag_leave_callback (GtkDropTarget *dest,
1907 gpointer user_data)
1908{
1909 GtkPlacesSidebar *sidebar = GTK_PLACES_SIDEBAR (user_data);
1910
1911 if (sidebar->drop_state != DROP_STATE_NEW_BOOKMARK_ARMED_PERMANENT)
1912 {
1913 update_possible_drop_targets (sidebar, FALSE);
1914 gtk_sidebar_row_hide (GTK_SIDEBAR_ROW (sidebar->new_bookmark_row), FALSE);
1915 sidebar->drop_state = DROP_STATE_NORMAL;
1916 }
1917
1918 sidebar->dragging_over = FALSE;
1919}
1920
1921static void
1922check_unmount_and_eject (GMount *mount,
1923 GVolume *volume,
1924 GDrive *drive,
1925 gboolean *show_unmount,
1926 gboolean *show_eject)
1927{
1928 *show_unmount = FALSE;
1929 *show_eject = FALSE;
1930
1931 if (drive != NULL)
1932 *show_eject = g_drive_can_eject (drive);
1933
1934 if (volume != NULL)
1935 *show_eject |= g_volume_can_eject (volume);
1936
1937 if (mount != NULL)
1938 {
1939 *show_eject |= g_mount_can_eject (mount);
1940 *show_unmount = g_mount_can_unmount (mount) && !*show_eject;
1941 }
1942}
1943
1944static void
1945drive_start_from_bookmark_cb (GObject *source_object,
1946 GAsyncResult *res,
1947 gpointer user_data)
1948{
1949 GtkPlacesSidebar *sidebar;
1950 GError *error;
1951 char *primary;
1952 char *name;
1953
1954 sidebar = GTK_PLACES_SIDEBAR (user_data);
1955
1956 error = NULL;
1957 if (!g_drive_poll_for_media_finish (G_DRIVE (source_object), result: res, error: &error))
1958 {
1959 if (error->code != G_IO_ERROR_FAILED_HANDLED)
1960 {
1961 name = g_drive_get_name (G_DRIVE (source_object));
1962 primary = g_strdup_printf (_("Unable to start “%s”"), name);
1963 g_free (mem: name);
1964 emit_show_error_message (sidebar, primary, secondary: error->message);
1965 g_free (mem: primary);
1966 }
1967 g_error_free (error);
1968 }
1969}
1970
1971static void
1972volume_mount_cb (GObject *source_object,
1973 GAsyncResult *result,
1974 gpointer user_data)
1975{
1976 GtkSidebarRow *row = GTK_SIDEBAR_ROW (user_data);
1977 GtkPlacesSidebar *sidebar;
1978 GVolume *volume;
1979 GError *error;
1980 char *primary;
1981 char *name;
1982 GMount *mount;
1983
1984 volume = G_VOLUME (source_object);
1985 g_object_get (object: row, first_property_name: "sidebar", &sidebar, NULL);
1986
1987 error = NULL;
1988 if (!g_volume_mount_finish (volume, result, error: &error))
1989 {
1990 if (error->code != G_IO_ERROR_FAILED_HANDLED &&
1991 error->code != G_IO_ERROR_ALREADY_MOUNTED)
1992 {
1993 name = g_volume_get_name (G_VOLUME (source_object));
1994 if (g_str_has_prefix (str: error->message, prefix: "Error unlocking"))
1995 /* Translators: This means that unlocking an encrypted storage
1996 * device failed. %s is the name of the device.
1997 */
1998 primary = g_strdup_printf (_("Error unlocking “%s”"), name);
1999 else
2000 primary = g_strdup_printf (_("Unable to access “%s”"), name);
2001 g_free (mem: name);
2002 emit_show_error_message (sidebar, primary, secondary: error->message);
2003 g_free (mem: primary);
2004 }
2005 g_error_free (error);
2006 }
2007
2008 sidebar->mounting = FALSE;
2009 gtk_sidebar_row_set_busy (row, FALSE);
2010
2011 mount = g_volume_get_mount (volume);
2012 if (mount != NULL)
2013 {
2014 GFile *location;
2015
2016 location = g_mount_get_default_location (mount);
2017 emit_open_location (sidebar, location, open_flags: sidebar->go_to_after_mount_open_flags);
2018
2019 g_object_unref (G_OBJECT (location));
2020 g_object_unref (G_OBJECT (mount));
2021 }
2022
2023 g_object_unref (object: row);
2024 g_object_unref (object: sidebar);
2025}
2026
2027static void
2028mount_volume (GtkSidebarRow *row,
2029 GVolume *volume)
2030{
2031 GtkPlacesSidebar *sidebar;
2032 GMountOperation *mount_op;
2033
2034 g_object_get (object: row, first_property_name: "sidebar", &sidebar, NULL);
2035
2036 mount_op = get_mount_operation (sidebar);
2037 g_mount_operation_set_password_save (op: mount_op, save: G_PASSWORD_SAVE_FOR_SESSION);
2038
2039 g_object_ref (row);
2040 g_object_ref (sidebar);
2041 g_volume_mount (volume, flags: 0, mount_operation: mount_op, NULL, callback: volume_mount_cb, user_data: row);
2042}
2043
2044static void
2045open_drive (GtkSidebarRow *row,
2046 GDrive *drive,
2047 GtkPlacesOpenFlags open_flags)
2048{
2049 GtkPlacesSidebar *sidebar;
2050
2051 g_object_get (object: row, first_property_name: "sidebar", &sidebar, NULL);
2052
2053 if (drive != NULL &&
2054 (g_drive_can_start (drive) || g_drive_can_start_degraded (drive)))
2055 {
2056 GMountOperation *mount_op;
2057
2058 gtk_sidebar_row_set_busy (row, TRUE);
2059 mount_op = get_mount_operation (sidebar);
2060 g_drive_start (drive, flags: G_DRIVE_START_NONE, mount_operation: mount_op, NULL, callback: drive_start_from_bookmark_cb, NULL);
2061 g_object_unref (object: mount_op);
2062 }
2063}
2064
2065static void
2066open_volume (GtkSidebarRow *row,
2067 GVolume *volume,
2068 GtkPlacesOpenFlags open_flags)
2069{
2070 GtkPlacesSidebar *sidebar;
2071
2072 g_object_get (object: row, first_property_name: "sidebar", &sidebar, NULL);
2073
2074 if (volume != NULL && !sidebar->mounting)
2075 {
2076 sidebar->mounting = TRUE;
2077 sidebar->go_to_after_mount_open_flags = open_flags;
2078 gtk_sidebar_row_set_busy (row, TRUE);
2079 mount_volume (row, volume);
2080 }
2081}
2082
2083static void
2084open_uri (GtkPlacesSidebar *sidebar,
2085 const char *uri,
2086 GtkPlacesOpenFlags open_flags)
2087{
2088 GFile *location;
2089
2090 location = g_file_new_for_uri (uri);
2091 emit_open_location (sidebar, location, open_flags);
2092 g_object_unref (object: location);
2093}
2094
2095static void
2096open_row (GtkSidebarRow *row,
2097 GtkPlacesOpenFlags open_flags)
2098{
2099 char *uri;
2100 GDrive *drive;
2101 GVolume *volume;
2102 GtkPlacesPlaceType place_type;
2103 GtkPlacesSidebar *sidebar;
2104
2105 g_object_get (object: row,
2106 first_property_name: "sidebar", &sidebar,
2107 "uri", &uri,
2108 "place-type", &place_type,
2109 "drive", &drive,
2110 "volume", &volume,
2111 NULL);
2112
2113 if (place_type == GTK_PLACES_OTHER_LOCATIONS)
2114 {
2115 emit_show_other_locations_with_flags (sidebar, open_flags);
2116 }
2117 else if (place_type == GTK_PLACES_STARRED_LOCATION)
2118 {
2119 emit_show_starred_location (sidebar, open_flags);
2120 }
2121 else if (uri != NULL)
2122 {
2123 open_uri (sidebar, uri, open_flags);
2124 }
2125 else if (place_type == GTK_PLACES_ENTER_LOCATION)
2126 {
2127 emit_show_enter_location (sidebar);
2128 }
2129 else if (volume != NULL)
2130 {
2131 open_volume (row, volume, open_flags);
2132 }
2133 else if (drive != NULL)
2134 {
2135 open_drive (row, drive, open_flags);
2136 }
2137
2138 g_object_unref (object: sidebar);
2139 if (drive)
2140 g_object_unref (object: drive);
2141 if (volume)
2142 g_object_unref (object: volume);
2143 g_free (mem: uri);
2144}
2145
2146/* Callback used for the "Open" menu items in the context menu */
2147static void
2148open_shortcut_cb (GSimpleAction *action,
2149 GVariant *parameter,
2150 gpointer data)
2151{
2152 GtkPlacesSidebar *sidebar = data;
2153 GtkPlacesOpenFlags flags;
2154
2155 flags = (GtkPlacesOpenFlags)g_variant_get_int32 (value: parameter);
2156 open_row (row: sidebar->context_row, open_flags: flags);
2157}
2158
2159/* Add bookmark for the selected item - just used from mount points */
2160static void
2161add_shortcut_cb (GSimpleAction *action,
2162 GVariant *parameter,
2163 gpointer data)
2164{
2165 GtkPlacesSidebar *sidebar = data;
2166 char *uri;
2167 char *name;
2168 GFile *location;
2169
2170 g_object_get (object: sidebar->context_row,
2171 first_property_name: "uri", &uri,
2172 "label", &name,
2173 NULL);
2174
2175 if (uri != NULL)
2176 {
2177 location = g_file_new_for_uri (uri);
2178 if (_gtk_bookmarks_manager_insert_bookmark (manager: sidebar->bookmarks_manager, file: location, position: -1, NULL))
2179 _gtk_bookmarks_manager_set_bookmark_label (manager: sidebar->bookmarks_manager, file: location, label: name, NULL);
2180 g_object_unref (object: location);
2181 }
2182
2183 g_free (mem: uri);
2184 g_free (mem: name);
2185}
2186
2187static void
2188rename_entry_changed (GtkEntry *entry,
2189 GtkPlacesSidebar *sidebar)
2190{
2191 GtkPlacesPlaceType type;
2192 char *name;
2193 char *uri;
2194 const char *new_name;
2195 gboolean found = FALSE;
2196 GtkWidget *row;
2197
2198 new_name = gtk_editable_get_text (GTK_EDITABLE (sidebar->rename_entry));
2199
2200 if (strcmp (s1: new_name, s2: "") == 0)
2201 {
2202 gtk_widget_set_sensitive (widget: sidebar->rename_button, FALSE);
2203 gtk_label_set_label (GTK_LABEL (sidebar->rename_error), str: "");
2204 return;
2205 }
2206
2207 for (row = gtk_widget_get_first_child (GTK_WIDGET (sidebar->list_box));
2208 row != NULL && !found;
2209 row = gtk_widget_get_next_sibling (widget: row))
2210 {
2211 if (!GTK_IS_LIST_BOX_ROW (row))
2212 continue;
2213
2214 g_object_get (object: row,
2215 first_property_name: "place-type", &type,
2216 "uri", &uri,
2217 "label", &name,
2218 NULL);
2219
2220 if ((type == GTK_PLACES_XDG_DIR || type == GTK_PLACES_BOOKMARK) &&
2221 strcmp (s1: uri, s2: sidebar->rename_uri) != 0 &&
2222 strcmp (s1: new_name, s2: name) == 0)
2223 found = TRUE;
2224
2225 g_free (mem: uri);
2226 g_free (mem: name);
2227 }
2228
2229 gtk_widget_set_sensitive (widget: sidebar->rename_button, sensitive: !found);
2230 gtk_label_set_label (GTK_LABEL (sidebar->rename_error),
2231 str: found ? _("This name is already taken") : "");
2232}
2233
2234static void
2235do_rename (GtkButton *button,
2236 GtkPlacesSidebar *sidebar)
2237{
2238 char *new_text;
2239 GFile *file;
2240
2241 new_text = g_strdup (str: gtk_editable_get_text (GTK_EDITABLE (sidebar->rename_entry)));
2242
2243 file = g_file_new_for_uri (uri: sidebar->rename_uri);
2244 if (!_gtk_bookmarks_manager_has_bookmark (manager: sidebar->bookmarks_manager, file))
2245 _gtk_bookmarks_manager_insert_bookmark (manager: sidebar->bookmarks_manager, file, position: -1, NULL);
2246
2247 _gtk_bookmarks_manager_set_bookmark_label (manager: sidebar->bookmarks_manager, file, label: new_text, NULL);
2248
2249 g_object_unref (object: file);
2250 g_free (mem: new_text);
2251
2252 g_clear_pointer (&sidebar->rename_uri, g_free);
2253
2254 if (sidebar->rename_popover)
2255 gtk_popover_popdown (GTK_POPOVER (sidebar->rename_popover));
2256}
2257
2258static void
2259on_rename_popover_destroy (GtkWidget *rename_popover,
2260 GtkPlacesSidebar *sidebar)
2261{
2262 if (sidebar)
2263 {
2264 sidebar->rename_popover = NULL;
2265 sidebar->rename_entry = NULL;
2266 sidebar->rename_button = NULL;
2267 sidebar->rename_error = NULL;
2268 }
2269}
2270
2271static void
2272create_rename_popover (GtkPlacesSidebar *sidebar)
2273{
2274 GtkWidget *popover;
2275 GtkWidget *grid;
2276 GtkWidget *label;
2277 GtkWidget *entry;
2278 GtkWidget *button;
2279 GtkWidget *error;
2280 char *str;
2281
2282 if (sidebar->rename_popover)
2283 return;
2284
2285 popover = gtk_popover_new ();
2286 gtk_widget_set_parent (widget: popover, GTK_WIDGET (sidebar));
2287 /* Clean sidebar pointer when its destroyed, most of the times due to its
2288 * relative_to associated row being destroyed */
2289 g_signal_connect (popover, "destroy", G_CALLBACK (on_rename_popover_destroy), sidebar);
2290 gtk_popover_set_position (GTK_POPOVER (popover), position: GTK_POS_RIGHT);
2291 grid = gtk_grid_new ();
2292 gtk_popover_set_child (GTK_POPOVER (popover), child: grid);
2293 g_object_set (object: grid,
2294 first_property_name: "margin-start", 10,
2295 "margin-end", 10,
2296 "margin-top", 10,
2297 "margin-bottom", 10,
2298 "row-spacing", 6,
2299 "column-spacing", 6,
2300 NULL);
2301 entry = gtk_entry_new ();
2302 gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE);
2303 g_signal_connect (entry, "changed", G_CALLBACK (rename_entry_changed), sidebar);
2304 str = g_strdup_printf (format: "<b>%s</b>", _("Name"));
2305 label = gtk_label_new (str);
2306 gtk_widget_set_halign (widget: label, align: GTK_ALIGN_START);
2307 gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
2308 gtk_label_set_mnemonic_widget (GTK_LABEL (label), widget: entry);
2309 g_free (mem: str);
2310 button = gtk_button_new_with_mnemonic (_("_Rename"));
2311 gtk_widget_add_css_class (widget: button, css_class: "suggested-action");
2312 g_signal_connect (button, "clicked", G_CALLBACK (do_rename), sidebar);
2313 error = gtk_label_new (str: "");
2314 gtk_widget_set_halign (widget: error, align: GTK_ALIGN_START);
2315 gtk_grid_attach (GTK_GRID (grid), child: label, column: 0, row: 0, width: 2, height: 1);
2316 gtk_grid_attach (GTK_GRID (grid), child: entry, column: 0, row: 1, width: 1, height: 1);
2317 gtk_grid_attach (GTK_GRID (grid), child: button,column: 1, row: 1, width: 1, height: 1);
2318 gtk_grid_attach (GTK_GRID (grid), child: error, column: 0, row: 2, width: 2, height: 1);
2319 gtk_popover_set_default_widget (GTK_POPOVER (popover), widget: button);
2320
2321 sidebar->rename_popover = popover;
2322 sidebar->rename_entry = entry;
2323 sidebar->rename_button = button;
2324 sidebar->rename_error = error;
2325}
2326
2327/* Style the row differently while we show a popover for it.
2328 * Otherwise, the popover is 'pointing to nothing'. Since the
2329 * main popover and the rename popover interleave their hiding
2330 * and showing, we have to count to ensure that we don't loose
2331 * the state before the last popover is gone.
2332 *
2333 * This would be nicer as a state, but reusing hover for this
2334 * interferes with the normal handling of this state, so just
2335 * use a style class.
2336 */
2337static void
2338update_popover_shadowing (GtkWidget *row,
2339 gboolean shown)
2340{
2341 int count;
2342
2343 count = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "popover-count"));
2344 count = shown ? count + 1 : count - 1;
2345 g_object_set_data (G_OBJECT (row), key: "popover-count", GINT_TO_POINTER (count));
2346
2347 if (count > 0)
2348 gtk_widget_add_css_class (widget: row, css_class: "has-open-popup");
2349 else
2350 gtk_widget_remove_css_class (widget: row, css_class: "has-open-popup");
2351}
2352
2353static void
2354set_prelight (GtkPopover *popover)
2355{
2356 update_popover_shadowing (row: gtk_widget_get_parent (GTK_WIDGET (popover)), TRUE);
2357}
2358
2359static void
2360unset_prelight (GtkPopover *popover)
2361{
2362 update_popover_shadowing (row: gtk_widget_get_parent (GTK_WIDGET (popover)), FALSE);
2363}
2364
2365static void
2366setup_popover_shadowing (GtkWidget *popover)
2367{
2368 g_signal_connect (popover, "map", G_CALLBACK (set_prelight), NULL);
2369 g_signal_connect (popover, "unmap", G_CALLBACK (unset_prelight), NULL);
2370}
2371
2372static void
2373show_rename_popover (GtkSidebarRow *row)
2374{
2375 char *name;
2376 char *uri;
2377 GtkPlacesSidebar *sidebar;
2378
2379 g_object_get (object: row,
2380 first_property_name: "sidebar", &sidebar,
2381 "label", &name,
2382 "uri", &uri,
2383 NULL);
2384
2385 create_rename_popover (sidebar);
2386
2387 if (sidebar->rename_uri)
2388 g_free (mem: sidebar->rename_uri);
2389 sidebar->rename_uri = g_strdup (str: uri);
2390
2391 gtk_editable_set_text (GTK_EDITABLE (sidebar->rename_entry), text: name);
2392 g_object_ref (sidebar->rename_popover);
2393 gtk_widget_unparent (widget: sidebar->rename_popover);
2394 gtk_widget_set_parent (widget: sidebar->rename_popover, GTK_WIDGET (row));
2395 g_object_unref (object: sidebar->rename_popover);
2396
2397 setup_popover_shadowing (sidebar->rename_popover);
2398
2399 gtk_popover_popup (GTK_POPOVER (sidebar->rename_popover));
2400 gtk_widget_grab_focus (widget: sidebar->rename_entry);
2401
2402 g_free (mem: name);
2403 g_free (mem: uri);
2404 g_object_unref (object: sidebar);
2405}
2406
2407static void
2408rename_bookmark (GtkSidebarRow *row)
2409{
2410 GtkPlacesPlaceType type;
2411
2412 g_object_get (object: row, first_property_name: "place-type", &type, NULL);
2413
2414 if (type != GTK_PLACES_BOOKMARK && type != GTK_PLACES_XDG_DIR)
2415 return;
2416
2417 show_rename_popover (row);
2418}
2419
2420static void
2421rename_shortcut_cb (GSimpleAction *action,
2422 GVariant *parameter,
2423 gpointer data)
2424{
2425 GtkPlacesSidebar *sidebar = data;
2426
2427 rename_bookmark (row: sidebar->context_row);
2428}
2429
2430static void
2431remove_bookmark (GtkSidebarRow *row)
2432{
2433 GtkPlacesPlaceType type;
2434 char *uri;
2435 GFile *file;
2436 GtkPlacesSidebar *sidebar;
2437
2438 g_object_get (object: row,
2439 first_property_name: "sidebar", &sidebar,
2440 "place-type", &type,
2441 "uri", &uri,
2442 NULL);
2443
2444 if (type == GTK_PLACES_BOOKMARK)
2445 {
2446 file = g_file_new_for_uri (uri);
2447 _gtk_bookmarks_manager_remove_bookmark (manager: sidebar->bookmarks_manager, file, NULL);
2448 g_object_unref (object: file);
2449 }
2450
2451 g_free (mem: uri);
2452 g_object_unref (object: sidebar);
2453}
2454
2455static void
2456remove_shortcut_cb (GSimpleAction *action,
2457 GVariant *parameter,
2458 gpointer data)
2459{
2460 GtkPlacesSidebar *sidebar = data;
2461
2462 remove_bookmark (row: sidebar->context_row);
2463}
2464
2465static void
2466mount_shortcut_cb (GSimpleAction *action,
2467 GVariant *parameter,
2468 gpointer data)
2469{
2470 GtkPlacesSidebar *sidebar = data;
2471 GVolume *volume;
2472
2473 g_object_get (object: sidebar->context_row,
2474 first_property_name: "volume", &volume,
2475 NULL);
2476
2477 if (volume != NULL)
2478 mount_volume (row: sidebar->context_row, volume);
2479
2480 g_object_unref (object: volume);
2481}
2482
2483/* Callback used from g_mount_unmount_with_operation() */
2484static void
2485unmount_mount_cb (GObject *source_object,
2486 GAsyncResult *result,
2487 gpointer user_data)
2488{
2489 GtkPlacesSidebar *sidebar = GTK_PLACES_SIDEBAR (user_data);
2490 GMount *mount;
2491 GError *error;
2492
2493 mount = G_MOUNT (source_object);
2494
2495 error = NULL;
2496 if (!g_mount_unmount_with_operation_finish (mount, result, error: &error))
2497 {
2498 if (error->code != G_IO_ERROR_FAILED_HANDLED)
2499 {
2500 char *name;
2501 char *primary;
2502
2503 name = g_mount_get_name (mount);
2504 primary = g_strdup_printf (_("Unable to unmount “%s”"), name);
2505 g_free (mem: name);
2506 emit_show_error_message (sidebar, primary, secondary: error->message);
2507 g_free (mem: primary);
2508 }
2509
2510 g_error_free (error);
2511 }
2512
2513 g_object_unref (object: sidebar);
2514}
2515
2516static GMountOperation *
2517get_mount_operation (GtkPlacesSidebar *sidebar)
2518{
2519 GMountOperation *mount_op;
2520
2521 mount_op = gtk_mount_operation_new (GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (sidebar))));
2522
2523 emit_mount_operation (sidebar, mount_op);
2524
2525 return mount_op;
2526}
2527
2528static GMountOperation *
2529get_unmount_operation (GtkPlacesSidebar *sidebar)
2530{
2531 GMountOperation *mount_op;
2532
2533 mount_op = gtk_mount_operation_new (GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (sidebar))));
2534
2535 emit_unmount_operation (sidebar, mount_op);
2536
2537 return mount_op;
2538}
2539
2540/* Returns TRUE if file1 is prefix of file2 or if both files have the
2541 * same path
2542 */
2543static gboolean
2544file_prefix_or_same (GFile *file1,
2545 GFile *file2)
2546{
2547 return g_file_has_prefix (file: file1, prefix: file2) ||
2548 g_file_equal (file1, file2);
2549}
2550
2551static gboolean
2552is_current_location_on_volume (GtkPlacesSidebar *sidebar,
2553 GMount *mount,
2554 GVolume *volume,
2555 GDrive *drive)
2556{
2557 gboolean current_location_on_volume;
2558 GFile *mount_default_location;
2559 GMount *mount_for_volume;
2560 GList *volumes_for_drive;
2561 GList *volume_for_drive;
2562
2563 current_location_on_volume = FALSE;
2564
2565 if (sidebar->current_location != NULL)
2566 {
2567 if (mount != NULL)
2568 {
2569 mount_default_location = g_mount_get_default_location (mount);
2570 current_location_on_volume = file_prefix_or_same (file1: sidebar->current_location,
2571 file2: mount_default_location);
2572
2573 g_object_unref (object: mount_default_location);
2574 }
2575 /* This code path is probably never reached since mount always exists,
2576 * and if it doesn't exists we don't offer a way to eject a volume or
2577 * drive in the UI. Do it for defensive programming
2578 */
2579 else if (volume != NULL)
2580 {
2581 mount_for_volume = g_volume_get_mount (volume);
2582 if (mount_for_volume != NULL)
2583 {
2584 mount_default_location = g_mount_get_default_location (mount: mount_for_volume);
2585 current_location_on_volume = file_prefix_or_same (file1: sidebar->current_location,
2586 file2: mount_default_location);
2587
2588 g_object_unref (object: mount_default_location);
2589 g_object_unref (object: mount_for_volume);
2590 }
2591 }
2592 /* This code path is probably never reached since mount always exists,
2593 * and if it doesn't exists we don't offer a way to eject a volume or
2594 * drive in the UI. Do it for defensive programming
2595 */
2596 else if (drive != NULL)
2597 {
2598 volumes_for_drive = g_drive_get_volumes (drive);
2599 for (volume_for_drive = volumes_for_drive; volume_for_drive != NULL; volume_for_drive = volume_for_drive->next)
2600 {
2601 mount_for_volume = g_volume_get_mount (volume: volume_for_drive->data);
2602 if (mount_for_volume != NULL)
2603 {
2604 mount_default_location = g_mount_get_default_location (mount: mount_for_volume);
2605 current_location_on_volume = file_prefix_or_same (file1: sidebar->current_location,
2606 file2: mount_default_location);
2607
2608 g_object_unref (object: mount_default_location);
2609 g_object_unref (object: mount_for_volume);
2610
2611 if (current_location_on_volume)
2612 break;
2613 }
2614 }
2615 g_list_free_full (list: volumes_for_drive, free_func: g_object_unref);
2616 }
2617 }
2618
2619 return current_location_on_volume;
2620}
2621
2622static void
2623do_unmount (GMount *mount,
2624 GtkPlacesSidebar *sidebar)
2625{
2626 if (mount != NULL)
2627 {
2628 GMountOperation *mount_op;
2629
2630 if (is_current_location_on_volume (sidebar, mount, NULL, NULL))
2631 open_home (sidebar);
2632
2633 mount_op = get_unmount_operation (sidebar);
2634 g_mount_unmount_with_operation (mount,
2635 flags: 0,
2636 mount_operation: mount_op,
2637 NULL,
2638 callback: unmount_mount_cb,
2639 g_object_ref (sidebar));
2640 g_object_unref (object: mount_op);
2641 }
2642}
2643
2644static void
2645unmount_shortcut_cb (GSimpleAction *action,
2646 GVariant *parameter,
2647 gpointer data)
2648{
2649 GtkPlacesSidebar *sidebar = data;
2650 GMount *mount;
2651
2652 g_object_get (object: sidebar->context_row,
2653 first_property_name: "mount", &mount,
2654 NULL);
2655
2656 do_unmount (mount, sidebar);
2657
2658 if (mount)
2659 g_object_unref (object: mount);
2660}
2661
2662static void
2663drive_stop_cb (GObject *source_object,
2664 GAsyncResult *res,
2665 gpointer user_data)
2666{
2667 GtkPlacesSidebar *sidebar;
2668 GError *error;
2669 char *primary;
2670 char *name;
2671
2672 sidebar = user_data;
2673
2674 error = NULL;
2675 if (!g_drive_stop_finish (G_DRIVE (source_object), result: res, error: &error))
2676 {
2677 if (error->code != G_IO_ERROR_FAILED_HANDLED)
2678 {
2679 name = g_drive_get_name (G_DRIVE (source_object));
2680 primary = g_strdup_printf (_("Unable to stop “%s”"), name);
2681 g_free (mem: name);
2682 emit_show_error_message (sidebar, primary, secondary: error->message);
2683 g_free (mem: primary);
2684 }
2685 g_error_free (error);
2686 }
2687
2688 g_object_unref (object: sidebar);
2689}
2690
2691static void
2692drive_eject_cb (GObject *source_object,
2693 GAsyncResult *res,
2694 gpointer user_data)
2695{
2696 GtkPlacesSidebar *sidebar;
2697 GError *error;
2698 char *primary;
2699 char *name;
2700
2701 sidebar = user_data;
2702
2703 error = NULL;
2704 if (!g_drive_eject_with_operation_finish (G_DRIVE (source_object), result: res, error: &error))
2705 {
2706 if (error->code != G_IO_ERROR_FAILED_HANDLED)
2707 {
2708 name = g_drive_get_name (G_DRIVE (source_object));
2709 primary = g_strdup_printf (_("Unable to eject “%s”"), name);
2710 g_free (mem: name);
2711 emit_show_error_message (sidebar, primary, secondary: error->message);
2712 g_free (mem: primary);
2713 }
2714 g_error_free (error);
2715 }
2716
2717 g_object_unref (object: sidebar);
2718}
2719
2720static void
2721volume_eject_cb (GObject *source_object,
2722 GAsyncResult *res,
2723 gpointer user_data)
2724{
2725 GtkPlacesSidebar *sidebar;
2726 GError *error;
2727 char *primary;
2728 char *name;
2729
2730 sidebar = user_data;
2731
2732 error = NULL;
2733 if (!g_volume_eject_with_operation_finish (G_VOLUME (source_object), result: res, error: &error))
2734 {
2735 if (error->code != G_IO_ERROR_FAILED_HANDLED)
2736 {
2737 name = g_volume_get_name (G_VOLUME (source_object));
2738 primary = g_strdup_printf (_("Unable to eject %s"), name);
2739 g_free (mem: name);
2740 emit_show_error_message (sidebar, primary, secondary: error->message);
2741 g_free (mem: primary);
2742 }
2743 g_error_free (error);
2744 }
2745
2746 g_object_unref (object: sidebar);
2747}
2748
2749static void
2750mount_eject_cb (GObject *source_object,
2751 GAsyncResult *res,
2752 gpointer user_data)
2753{
2754 GtkPlacesSidebar *sidebar;
2755 GError *error;
2756 char *primary;
2757 char *name;
2758
2759 sidebar = user_data;
2760
2761 error = NULL;
2762 if (!g_mount_eject_with_operation_finish (G_MOUNT (source_object), result: res, error: &error))
2763 {
2764 if (error->code != G_IO_ERROR_FAILED_HANDLED)
2765 {
2766 name = g_mount_get_name (G_MOUNT (source_object));
2767 primary = g_strdup_printf (_("Unable to eject %s"), name);
2768 g_free (mem: name);
2769 emit_show_error_message (sidebar, primary, secondary: error->message);
2770 g_free (mem: primary);
2771 }
2772 g_error_free (error);
2773 }
2774
2775 g_object_unref (object: sidebar);
2776}
2777
2778static void
2779do_eject (GMount *mount,
2780 GVolume *volume,
2781 GDrive *drive,
2782 GtkPlacesSidebar *sidebar)
2783{
2784 GMountOperation *mount_op;
2785
2786 mount_op = get_unmount_operation (sidebar);
2787
2788 if (is_current_location_on_volume (sidebar, mount, volume, drive))
2789 open_home (sidebar);
2790
2791 if (mount != NULL)
2792 g_mount_eject_with_operation (mount, flags: 0, mount_operation: mount_op, NULL, callback: mount_eject_cb,
2793 g_object_ref (sidebar));
2794 /* This code path is probably never reached since mount always exists,
2795 * and if it doesn't exists we don't offer a way to eject a volume or
2796 * drive in the UI. Do it for defensive programming
2797 */
2798 else if (volume != NULL)
2799 g_volume_eject_with_operation (volume, flags: 0, mount_operation: mount_op, NULL, callback: volume_eject_cb,
2800 g_object_ref (sidebar));
2801 /* This code path is probably never reached since mount always exists,
2802 * and if it doesn't exists we don't offer a way to eject a volume or
2803 * drive in the UI. Do it for defensive programming
2804 */
2805 else if (drive != NULL)
2806 {
2807 if (g_drive_can_stop (drive))
2808 g_drive_stop (drive, flags: 0, mount_operation: mount_op, NULL, callback: drive_stop_cb,
2809 g_object_ref (sidebar));
2810 else
2811 g_drive_eject_with_operation (drive, flags: 0, mount_operation: mount_op, NULL, callback: drive_eject_cb,
2812 g_object_ref (sidebar));
2813 }
2814 g_object_unref (object: mount_op);
2815}
2816
2817static void
2818eject_shortcut_cb (GSimpleAction *action,
2819 GVariant *parameter,
2820 gpointer data)
2821{
2822 GtkPlacesSidebar *sidebar = data;
2823 GMount *mount;
2824 GVolume *volume;
2825 GDrive *drive;
2826
2827 g_object_get (object: sidebar->context_row,
2828 first_property_name: "mount", &mount,
2829 "volume", &volume,
2830 "drive", &drive,
2831 NULL);
2832
2833 do_eject (mount, volume, drive, sidebar);
2834
2835 if (mount)
2836 g_object_unref (object: mount);
2837 if (volume)
2838 g_object_unref (object: volume);
2839 if (drive)
2840 g_object_unref (object: drive);
2841}
2842
2843static gboolean
2844eject_or_unmount_bookmark (GtkSidebarRow *row)
2845{
2846 gboolean can_unmount, can_eject;
2847 GMount *mount;
2848 GVolume *volume;
2849 GDrive *drive;
2850 gboolean ret;
2851 GtkPlacesSidebar *sidebar;
2852
2853 g_object_get (object: row,
2854 first_property_name: "sidebar", &sidebar,
2855 "mount", &mount,
2856 "volume", &volume,
2857 "drive", &drive,
2858 NULL);
2859 ret = FALSE;
2860
2861 check_unmount_and_eject (mount, volume, drive, show_unmount: &can_unmount, show_eject: &can_eject);
2862 /* if we can eject, it has priority over unmount */
2863 if (can_eject)
2864 {
2865 do_eject (mount, volume, drive, sidebar);
2866 ret = TRUE;
2867 }
2868 else if (can_unmount)
2869 {
2870 do_unmount (mount, sidebar);
2871 ret = TRUE;
2872 }
2873
2874 g_object_unref (object: sidebar);
2875 if (mount)
2876 g_object_unref (object: mount);
2877 if (volume)
2878 g_object_unref (object: volume);
2879 if (drive)
2880 g_object_unref (object: drive);
2881
2882 return ret;
2883}
2884
2885static gboolean
2886eject_or_unmount_selection (GtkPlacesSidebar *sidebar)
2887{
2888 gboolean ret;
2889 GtkListBoxRow *row;
2890
2891 row = gtk_list_box_get_selected_row (GTK_LIST_BOX (sidebar->list_box));
2892 ret = eject_or_unmount_bookmark (GTK_SIDEBAR_ROW (row));
2893
2894 return ret;
2895}
2896
2897static void
2898drive_poll_for_media_cb (GObject *source_object,
2899 GAsyncResult *res,
2900 gpointer user_data)
2901{
2902 GtkPlacesSidebar *sidebar;
2903 GError *error;
2904 char *primary;
2905 char *name;
2906
2907 sidebar = GTK_PLACES_SIDEBAR (user_data);
2908
2909 error = NULL;
2910 if (!g_drive_poll_for_media_finish (G_DRIVE (source_object), result: res, error: &error))
2911 {
2912 if (error->code != G_IO_ERROR_FAILED_HANDLED)
2913 {
2914 name = g_drive_get_name (G_DRIVE (source_object));
2915 primary = g_strdup_printf (_("Unable to poll “%s” for media changes"), name);
2916 g_free (mem: name);
2917 emit_show_error_message (sidebar, primary, secondary: error->message);
2918 g_free (mem: primary);
2919 }
2920 g_error_free (error);
2921 }
2922
2923 g_object_unref (object: sidebar);
2924}
2925
2926static void
2927rescan_shortcut_cb (GSimpleAction *action,
2928 GVariant *parameter,
2929 gpointer data)
2930{
2931 GtkPlacesSidebar *sidebar = data;
2932 GDrive *drive;
2933
2934 g_object_get (object: sidebar->context_row,
2935 first_property_name: "drive", &drive,
2936 NULL);
2937
2938 if (drive != NULL)
2939 {
2940 g_drive_poll_for_media (drive, NULL, callback: drive_poll_for_media_cb, g_object_ref (sidebar));
2941 g_object_unref (object: drive);
2942 }
2943}
2944
2945static void
2946drive_start_cb (GObject *source_object,
2947 GAsyncResult *res,
2948 gpointer user_data)
2949{
2950 GtkPlacesSidebar *sidebar;
2951 GError *error;
2952 char *primary;
2953 char *name;
2954
2955 sidebar = GTK_PLACES_SIDEBAR (user_data);
2956
2957 error = NULL;
2958 if (!g_drive_start_finish (G_DRIVE (source_object), result: res, error: &error))
2959 {
2960 if (error->code != G_IO_ERROR_FAILED_HANDLED)
2961 {
2962 name = g_drive_get_name (G_DRIVE (source_object));
2963 primary = g_strdup_printf (_("Unable to start “%s”"), name);
2964 g_free (mem: name);
2965 emit_show_error_message (sidebar, primary, secondary: error->message);
2966 g_free (mem: primary);
2967 }
2968 g_error_free (error);
2969 }
2970
2971 g_object_unref (object: sidebar);
2972}
2973
2974static void
2975start_shortcut_cb (GSimpleAction *action,
2976 GVariant *parameter,
2977 gpointer data)
2978{
2979 GtkPlacesSidebar *sidebar = data;
2980 GDrive *drive;
2981
2982 g_object_get (object: sidebar->context_row,
2983 first_property_name: "drive", &drive,
2984 NULL);
2985
2986 if (drive != NULL)
2987 {
2988 GMountOperation *mount_op;
2989
2990 mount_op = get_mount_operation (sidebar);
2991
2992 g_drive_start (drive, flags: G_DRIVE_START_NONE, mount_operation: mount_op, NULL, callback: drive_start_cb, g_object_ref (sidebar));
2993
2994 g_object_unref (object: mount_op);
2995 g_object_unref (object: drive);
2996 }
2997}
2998
2999static void
3000stop_shortcut_cb (GSimpleAction *action,
3001 GVariant *parameter,
3002 gpointer data)
3003{
3004 GtkPlacesSidebar *sidebar = data;
3005 GDrive *drive;
3006
3007 g_object_get (object: sidebar->context_row,
3008 first_property_name: "drive", &drive,
3009 NULL);
3010
3011 if (drive != NULL)
3012 {
3013 GMountOperation *mount_op;
3014
3015 mount_op = get_unmount_operation (sidebar);
3016 g_drive_stop (drive, flags: G_MOUNT_UNMOUNT_NONE, mount_operation: mount_op, NULL, callback: drive_stop_cb,
3017 g_object_ref (sidebar));
3018
3019 g_object_unref (object: mount_op);
3020 g_object_unref (object: drive);
3021 }
3022}
3023
3024static gboolean
3025on_key_pressed (GtkEventControllerKey *controller,
3026 guint keyval,
3027 guint keycode,
3028 GdkModifierType state,
3029 GtkPlacesSidebar *sidebar)
3030{
3031 guint modifiers;
3032 GtkListBoxRow *row;
3033
3034 row = gtk_list_box_get_selected_row (GTK_LIST_BOX (sidebar->list_box));
3035 if (row)
3036 {
3037 modifiers = gtk_accelerator_get_default_mod_mask ();
3038
3039 if (keyval == GDK_KEY_Return ||
3040 keyval == GDK_KEY_KP_Enter ||
3041 keyval == GDK_KEY_ISO_Enter ||
3042 keyval == GDK_KEY_space)
3043 {
3044 GtkPlacesOpenFlags open_flags = GTK_PLACES_OPEN_NORMAL;
3045
3046 if ((state & modifiers) == GDK_SHIFT_MASK)
3047 open_flags = GTK_PLACES_OPEN_NEW_TAB;
3048 else if ((state & modifiers) == GDK_CONTROL_MASK)
3049 open_flags = GTK_PLACES_OPEN_NEW_WINDOW;
3050
3051 open_row (GTK_SIDEBAR_ROW (row), open_flags);
3052
3053 return TRUE;
3054 }
3055
3056 if (keyval == GDK_KEY_Down &&
3057 (state & modifiers) == GDK_ALT_MASK)
3058 return eject_or_unmount_selection (sidebar);
3059
3060 if ((keyval == GDK_KEY_Delete ||
3061 keyval == GDK_KEY_KP_Delete) &&
3062 (state & modifiers) == 0)
3063 {
3064 remove_bookmark (GTK_SIDEBAR_ROW (row));
3065 return TRUE;
3066 }
3067
3068 if ((keyval == GDK_KEY_F2) &&
3069 (state & modifiers) == 0)
3070 {
3071 rename_bookmark (GTK_SIDEBAR_ROW (row));
3072 return TRUE;
3073 }
3074
3075 if ((keyval == GDK_KEY_Menu) ||
3076 ((keyval == GDK_KEY_F10) &&
3077 (state & modifiers) == GDK_SHIFT_MASK))
3078 {
3079 popup_menu_cb (GTK_SIDEBAR_ROW (row));
3080 return TRUE;
3081 }
3082 }
3083
3084 return FALSE;
3085}
3086
3087static GActionEntry entries[] = {
3088 { "open", open_shortcut_cb, "i", NULL, NULL },
3089 { "open-other", open_shortcut_cb, "i", NULL, NULL },
3090 { "bookmark", add_shortcut_cb, NULL, NULL, NULL },
3091 { "remove", remove_shortcut_cb, NULL, NULL, NULL },
3092 { "rename", rename_shortcut_cb, NULL, NULL, NULL },
3093 { "mount", mount_shortcut_cb, NULL, NULL, NULL },
3094 { "unmount", unmount_shortcut_cb, NULL, NULL, NULL },
3095 { "eject", eject_shortcut_cb, NULL, NULL, NULL },
3096 { "rescan", rescan_shortcut_cb, NULL, NULL, NULL },
3097 { "start", start_shortcut_cb, NULL, NULL, NULL },
3098 { "stop", stop_shortcut_cb, NULL, NULL, NULL },
3099};
3100
3101static void
3102on_row_popover_destroy (GtkWidget *row_popover,
3103 GtkPlacesSidebar *sidebar)
3104{
3105 if (sidebar)
3106 sidebar->popover = NULL;
3107}
3108
3109#ifdef HAVE_CLOUDPROVIDERS
3110static void
3111build_popup_menu_using_gmenu (GtkSidebarRow *row)
3112{
3113 CloudProvidersAccount *cloud_provider_account;
3114 GtkPlacesSidebar *sidebar;
3115 GMenuModel *cloud_provider_menu;
3116 GActionGroup *cloud_provider_action_group;
3117
3118 g_object_get (row,
3119 "sidebar", &sidebar,
3120 "cloud-provider-account", &cloud_provider_account,
3121 NULL);
3122
3123 /* Cloud provider account */
3124 if (cloud_provider_account)
3125 {
3126 GMenu *menu = g_menu_new ();
3127 GMenuItem *item;
3128 item = g_menu_item_new (_("_Open"), "row.open");
3129 g_menu_item_set_action_and_target_value (item, "row.open",
3130 g_variant_new_int32 (GTK_PLACES_OPEN_NORMAL));
3131 g_menu_append_item (menu, item);
3132 if (sidebar->open_flags & GTK_PLACES_OPEN_NEW_TAB)
3133 {
3134 item = g_menu_item_new (_("Open in New _Tab"), "row.open-other");
3135 g_menu_item_set_action_and_target_value (item, "row.open-other", g_variant_new_int32(GTK_PLACES_OPEN_NEW_TAB));
3136 g_menu_append_item (menu, item);
3137 }
3138 if (sidebar->open_flags & GTK_PLACES_OPEN_NEW_WINDOW)
3139 {
3140 item = g_menu_item_new (_("Open in New _Window"), "row.open-other");
3141 g_menu_item_set_action_and_target_value (item, "row.open-other", g_variant_new_int32(GTK_PLACES_OPEN_NEW_WINDOW));
3142 g_menu_append_item (menu, item);
3143 }
3144 cloud_provider_menu = cloud_providers_account_get_menu_model (cloud_provider_account);
3145 cloud_provider_action_group = cloud_providers_account_get_action_group (cloud_provider_account);
3146 if (cloud_provider_menu != NULL && cloud_provider_action_group != NULL)
3147 {
3148 g_menu_append_section (menu, NULL, cloud_provider_menu);
3149 gtk_widget_insert_action_group (GTK_WIDGET (sidebar),
3150 "cloudprovider",
3151 G_ACTION_GROUP (cloud_provider_action_group));
3152 }
3153 if (sidebar->popover)
3154 gtk_widget_unparent (sidebar->popover);
3155
3156 sidebar->popover = gtk_popover_menu_new_from_model (G_MENU_MODEL (menu));
3157 gtk_widget_set_parent (sidebar->popover, GTK_WIDGET (sidebar));
3158 g_signal_connect (sidebar->popover, "destroy",
3159 G_CALLBACK (on_row_popover_destroy), sidebar);
3160 g_object_unref (sidebar);
3161 g_object_unref (cloud_provider_account);
3162 }
3163}
3164#endif
3165
3166/* Constructs the popover for the sidebar row if needed */
3167static void
3168create_row_popover (GtkPlacesSidebar *sidebar,
3169 GtkSidebarRow *row)
3170{
3171 GtkPlacesPlaceType type;
3172 GMenu *menu, *section;
3173 GMenuItem *item;
3174 GMount *mount;
3175 GVolume *volume;
3176 GDrive *drive;
3177 GAction *action;
3178 gboolean show_unmount, show_eject;
3179 gboolean show_stop;
3180
3181 g_object_get (object: row,
3182 first_property_name: "place-type", &type,
3183 "drive", &drive,
3184 "volume", &volume,
3185 "mount", &mount,
3186 NULL);
3187
3188 check_unmount_and_eject (mount, volume, drive, show_unmount: &show_unmount, show_eject: &show_eject);
3189
3190#ifdef HAVE_CLOUDPROVIDERS
3191 CloudProvidersAccount *cloud_provider_account;
3192
3193 g_object_get (row, "cloud-provider-account", &cloud_provider_account, NULL);
3194
3195 if (cloud_provider_account)
3196 {
3197 build_popup_menu_using_gmenu (row);
3198 return;
3199 }
3200#endif
3201
3202 action = g_action_map_lookup_action (G_ACTION_MAP (sidebar->row_actions), action_name: "remove");
3203 g_simple_action_set_enabled (G_SIMPLE_ACTION (action), enabled: (type == GTK_PLACES_BOOKMARK));
3204 action = g_action_map_lookup_action (G_ACTION_MAP (sidebar->row_actions), action_name: "rename");
3205 g_simple_action_set_enabled (G_SIMPLE_ACTION (action), enabled: (type == GTK_PLACES_BOOKMARK ||
3206 type == GTK_PLACES_XDG_DIR));
3207 action = g_action_map_lookup_action (G_ACTION_MAP (sidebar->row_actions), action_name: "open");
3208 g_simple_action_set_enabled (G_SIMPLE_ACTION (action), enabled: !gtk_list_box_row_is_selected (GTK_LIST_BOX_ROW (row)));
3209
3210 menu = g_menu_new ();
3211 section = g_menu_new ();
3212
3213 item = g_menu_item_new (_("_Open"), detailed_action: "row.open");
3214 g_menu_item_set_action_and_target_value (menu_item: item, action: "row.open",
3215 target_value: g_variant_new_int32 (value: GTK_PLACES_OPEN_NORMAL));
3216 g_menu_append_item (menu: section, item);
3217 g_object_unref (object: item);
3218
3219 if (sidebar->open_flags & GTK_PLACES_OPEN_NEW_TAB)
3220 {
3221 item = g_menu_item_new (_("Open in New _Tab"), detailed_action: "row.open");
3222 g_menu_item_set_action_and_target_value (menu_item: item, action: "row.open",
3223 target_value: g_variant_new_int32 (value: GTK_PLACES_OPEN_NEW_TAB));
3224 g_menu_append_item (menu: section, item);
3225 g_object_unref (object: item);
3226 }
3227
3228 if (sidebar->open_flags & GTK_PLACES_OPEN_NEW_WINDOW)
3229 {
3230 item = g_menu_item_new (_("Open in New _Window"), detailed_action: "row.open");
3231 g_menu_item_set_action_and_target_value (menu_item: item, action: "row.open",
3232 target_value: g_variant_new_int32 (value: GTK_PLACES_OPEN_NEW_WINDOW));
3233 g_menu_append_item (menu: section, item);
3234 g_object_unref (object: item);
3235 }
3236
3237 g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
3238 g_object_unref (object: section);
3239
3240 section = g_menu_new ();
3241 item = g_menu_item_new (_("_Add Bookmark"), detailed_action: "row.add-bookmark");
3242 g_menu_append_item (menu: section, item);
3243 g_object_unref (object: item);
3244
3245 item = g_menu_item_new (_("_Remove"), detailed_action: "row.remove");
3246 g_menu_append_item (menu: section, item);
3247 g_object_unref (object: item);
3248
3249 item = g_menu_item_new (_("_Rename"), detailed_action: "row.rename");
3250 g_menu_append_item (menu: section, item);
3251 g_object_unref (object: item);
3252
3253 g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
3254 g_object_unref (object: section);
3255
3256 section = g_menu_new ();
3257
3258 if (volume != NULL && mount == NULL &&
3259 g_volume_can_mount (volume))
3260 {
3261 item = g_menu_item_new (_("_Mount"), detailed_action: "row.mount");
3262 g_menu_append_item (menu: section, item);
3263 g_object_unref (object: item);
3264 }
3265
3266 show_stop = (drive != NULL && g_drive_can_stop (drive));
3267
3268 if (show_unmount && !show_stop)
3269 {
3270 item = g_menu_item_new (_("_Unmount"), detailed_action: "row.unmount");
3271 g_menu_append_item (menu: section, item);
3272 g_object_unref (object: item);
3273 }
3274
3275 if (show_eject)
3276 {
3277 item = g_menu_item_new (_("_Eject"), detailed_action: "row.eject");
3278 g_menu_append_item (menu: section, item);
3279 g_object_unref (object: item);
3280 }
3281
3282 if (drive != NULL &&
3283 g_drive_is_media_removable (drive) &&
3284 !g_drive_is_media_check_automatic (drive) &&
3285 g_drive_can_poll_for_media (drive))
3286 {
3287 item = g_menu_item_new (_("_Detect Media"), detailed_action: "row.rescan");
3288 g_menu_append_item (menu: section, item);
3289 g_object_unref (object: item);
3290 }
3291
3292 if (drive != NULL &&
3293 (g_drive_can_start (drive) || g_drive_can_start_degraded (drive)))
3294 {
3295 const guint ss_type = g_drive_get_start_stop_type (drive);
3296 const char *start_label = _("_Start");
3297
3298 if (ss_type == G_DRIVE_START_STOP_TYPE_SHUTDOWN) start_label = _("_Power On");
3299 else if (ss_type == G_DRIVE_START_STOP_TYPE_NETWORK) start_label = _("_Connect Drive");
3300 else if (ss_type == G_DRIVE_START_STOP_TYPE_MULTIDISK) start_label = _("_Start Multi-disk Device");
3301 else if (ss_type == G_DRIVE_START_STOP_TYPE_PASSWORD) start_label = _("_Unlock Device");
3302
3303 item = g_menu_item_new (label: start_label, detailed_action: "row.start");
3304 g_menu_append_item (menu: section, item);
3305 g_object_unref (object: item);
3306 }
3307
3308 if (show_stop && !show_unmount)
3309 {
3310 const guint ss_type = g_drive_get_start_stop_type (drive);
3311 const char *stop_label = _("_Stop");
3312
3313 if (ss_type == G_DRIVE_START_STOP_TYPE_SHUTDOWN) stop_label = _("_Safely Remove Drive");
3314 else if (ss_type == G_DRIVE_START_STOP_TYPE_NETWORK) stop_label = _("_Disconnect Drive");
3315 else if (ss_type == G_DRIVE_START_STOP_TYPE_MULTIDISK) stop_label = _("_Stop Multi-disk Device");
3316 else if (ss_type == G_DRIVE_START_STOP_TYPE_PASSWORD) stop_label = _("_Lock Device");
3317
3318 item = g_menu_item_new (label: stop_label, detailed_action: "row.stop");
3319 g_menu_append_item (menu: section, item);
3320 g_object_unref (object: item);
3321 }
3322
3323 g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
3324 g_object_unref (object: section);
3325
3326 sidebar->popover = gtk_popover_menu_new_from_model (G_MENU_MODEL (menu));
3327 g_object_unref (object: menu);
3328 g_signal_connect (sidebar->popover, "destroy", G_CALLBACK (on_row_popover_destroy), sidebar);
3329
3330 setup_popover_shadowing (sidebar->popover);
3331}
3332
3333static void
3334show_row_popover (GtkSidebarRow *row)
3335{
3336 GtkPlacesSidebar *sidebar;
3337
3338 g_object_get (object: row, first_property_name: "sidebar", &sidebar, NULL);
3339
3340 g_clear_pointer (&sidebar->popover, gtk_widget_unparent);
3341
3342 create_row_popover (sidebar, row);
3343
3344 gtk_widget_set_parent (widget: sidebar->popover, GTK_WIDGET (row));
3345
3346 sidebar->context_row = row;
3347 gtk_popover_popup (GTK_POPOVER (sidebar->popover));
3348
3349 g_object_unref (object: sidebar);
3350}
3351
3352static void
3353on_row_activated (GtkListBox *list_box,
3354 GtkListBoxRow *row,
3355 gpointer user_data)
3356{
3357 GtkSidebarRow *selected_row;
3358
3359 /* Avoid to open a location if the user is dragging. Changing the location
3360 * while dragging usually makes clients changing the view of the files, which
3361 * is confusing while the user has the attention on the drag
3362 */
3363 if (GTK_PLACES_SIDEBAR (user_data)->dragging_over)
3364 return;
3365
3366 selected_row = GTK_SIDEBAR_ROW (gtk_list_box_get_selected_row (list_box));
3367 open_row (row: selected_row, open_flags: 0);
3368}
3369
3370static void
3371on_row_pressed (GtkGestureClick *gesture,
3372 int n_press,
3373 double x,
3374 double y,
3375 GtkSidebarRow *row)
3376{
3377 GtkPlacesSidebar *sidebar;
3378 GtkPlacesSectionType section_type;
3379 GtkPlacesPlaceType row_type;
3380
3381 g_object_get (object: row,
3382 first_property_name: "sidebar", &sidebar,
3383 "section_type", &section_type,
3384 "place-type", &row_type,
3385 NULL);
3386
3387 if (section_type == GTK_PLACES_SECTION_BOOKMARKS)
3388 {
3389 sidebar->drag_row = GTK_WIDGET (row);
3390 sidebar->drag_row_x = (int)x;
3391 sidebar->drag_row_y = (int)y;
3392 }
3393
3394 g_object_unref (object: sidebar);
3395}
3396
3397static void
3398on_row_released (GtkGestureClick *gesture,
3399 int n_press,
3400 double x,
3401 double y,
3402 GtkSidebarRow *row)
3403{
3404 GtkPlacesSidebar *sidebar;
3405 GtkPlacesSectionType section_type;
3406 GtkPlacesPlaceType row_type;
3407 guint button, state;
3408
3409 g_object_get (object: row,
3410 first_property_name: "sidebar", &sidebar,
3411 "section_type", &section_type,
3412 "place-type", &row_type,
3413 NULL);
3414
3415 button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
3416 state = gtk_event_controller_get_current_event_state (GTK_EVENT_CONTROLLER (gesture));
3417
3418 if (row)
3419 {
3420 if (button == 2)
3421 {
3422 GtkPlacesOpenFlags open_flags = GTK_PLACES_OPEN_NORMAL;
3423
3424 open_flags = (state & GDK_CONTROL_MASK) ?
3425 GTK_PLACES_OPEN_NEW_WINDOW :
3426 GTK_PLACES_OPEN_NEW_TAB;
3427
3428 open_row (GTK_SIDEBAR_ROW (row), open_flags);
3429 gtk_gesture_set_state (GTK_GESTURE (gesture),
3430 state: GTK_EVENT_SEQUENCE_CLAIMED);
3431 }
3432 else if (button == 3)
3433 {
3434 if (row_type != GTK_PLACES_CONNECT_TO_SERVER)
3435 show_row_popover (GTK_SIDEBAR_ROW (row));
3436 }
3437 }
3438}
3439
3440static void
3441on_row_dragged (GtkGestureDrag *gesture,
3442 double x,
3443 double y,
3444 GtkSidebarRow *row)
3445{
3446 GtkPlacesSidebar *sidebar;
3447
3448 g_object_get (object: row, first_property_name: "sidebar", &sidebar, NULL);
3449
3450 if (sidebar->drag_row == NULL || sidebar->dragging_over)
3451 {
3452 g_object_unref (object: sidebar);
3453 return;
3454 }
3455
3456 if (gtk_drag_check_threshold_double (GTK_WIDGET (row), start_x: 0, start_y: 0, current_x: x, current_y: y))
3457 {
3458 double start_x, start_y;
3459 double drag_x, drag_y;
3460 GdkContentProvider *content;
3461 GdkSurface *surface;
3462 GdkDevice *device;
3463 GtkAllocation allocation;
3464 GtkWidget *drag_widget;
3465 GdkDrag *drag;
3466
3467 gtk_gesture_drag_get_start_point (gesture, x: &start_x, y: &start_y);
3468 gtk_widget_translate_coordinates (GTK_WIDGET (row),
3469 GTK_WIDGET (sidebar),
3470 src_x: start_x, src_y: start_y,
3471 dest_x: &drag_x, dest_y: &drag_y);
3472
3473 sidebar->dragging_over = TRUE;
3474
3475 content = gdk_content_provider_new_typed (GTK_TYPE_SIDEBAR_ROW, sidebar->drag_row);
3476
3477 surface = gtk_native_get_surface (self: gtk_widget_get_native (GTK_WIDGET (sidebar)));
3478 device = gtk_gesture_get_device (GTK_GESTURE (gesture));
3479
3480 drag = gdk_drag_begin (surface, device, content, actions: GDK_ACTION_MOVE, dx: drag_x, dy: drag_y);
3481
3482 g_object_unref (object: content);
3483
3484 g_signal_connect (drag, "dnd-finished", G_CALLBACK (dnd_finished_cb), sidebar);
3485 g_signal_connect (drag, "cancel", G_CALLBACK (dnd_cancel_cb), sidebar);
3486
3487 gtk_widget_get_allocation (widget: sidebar->drag_row, allocation: &allocation);
3488 gtk_widget_hide (widget: sidebar->drag_row);
3489
3490 drag_widget = GTK_WIDGET (gtk_sidebar_row_clone (GTK_SIDEBAR_ROW (sidebar->drag_row)));
3491 sidebar->drag_row_height = allocation.height;
3492 gtk_widget_set_size_request (widget: drag_widget, width: allocation.width, height: allocation.height);
3493 gtk_widget_set_opacity (widget: drag_widget, opacity: 0.8);
3494
3495 gtk_drag_icon_set_child (self: GTK_DRAG_ICON (ptr: gtk_drag_icon_get_for_drag (drag)), child: drag_widget);
3496
3497 g_object_unref (object: drag);
3498 }
3499
3500 g_object_unref (object: sidebar);
3501}
3502
3503static void
3504popup_menu_cb (GtkSidebarRow *row)
3505{
3506 GtkPlacesPlaceType row_type;
3507
3508 g_object_get (object: row, first_property_name: "place-type", &row_type, NULL);
3509
3510 if (row_type != GTK_PLACES_CONNECT_TO_SERVER)
3511 show_row_popover (row);
3512}
3513
3514static void
3515long_press_cb (GtkGesture *gesture,
3516 double x,
3517 double y,
3518 GtkPlacesSidebar *sidebar)
3519{
3520 GtkWidget *row;
3521
3522 row = GTK_WIDGET (gtk_list_box_get_row_at_y (GTK_LIST_BOX (sidebar->list_box), y));
3523 if (GTK_IS_SIDEBAR_ROW (row))
3524 popup_menu_cb (GTK_SIDEBAR_ROW (row));
3525}
3526
3527static int
3528list_box_sort_func (GtkListBoxRow *row1,
3529 GtkListBoxRow *row2,
3530 gpointer user_data)
3531{
3532 GtkPlacesSectionType section_type_1, section_type_2;
3533 GtkPlacesPlaceType place_type_1, place_type_2;
3534 char *label_1, *label_2;
3535 int index_1, index_2;
3536 int retval = 0;
3537
3538 g_object_get (object: row1,
3539 first_property_name: "label", &label_1,
3540 "place-type", &place_type_1,
3541 "section-type", &section_type_1,
3542 "order-index", &index_1,
3543 NULL);
3544 g_object_get (object: row2,
3545 first_property_name: "label", &label_2,
3546 "place-type", &place_type_2,
3547 "section-type", &section_type_2,
3548 "order-index", &index_2,
3549 NULL);
3550
3551 /* Always last position for "connect to server" */
3552 if (place_type_1 == GTK_PLACES_CONNECT_TO_SERVER)
3553 {
3554 retval = 1;
3555 }
3556 else if (place_type_2 == GTK_PLACES_CONNECT_TO_SERVER)
3557 {
3558 retval = -1;
3559 }
3560 else
3561 {
3562 if (section_type_1 == section_type_2)
3563 {
3564 if ((section_type_1 == GTK_PLACES_SECTION_COMPUTER &&
3565 place_type_1 == place_type_2 &&
3566 place_type_1 == GTK_PLACES_XDG_DIR) ||
3567 section_type_1 == GTK_PLACES_SECTION_MOUNTS)
3568 {
3569 retval = g_utf8_collate (str1: label_1, str2: label_2);
3570 }
3571 else if ((place_type_1 == GTK_PLACES_BOOKMARK || place_type_2 == GTK_PLACES_DROP_FEEDBACK) &&
3572 (place_type_1 == GTK_PLACES_DROP_FEEDBACK || place_type_2 == GTK_PLACES_BOOKMARK))
3573 {
3574 retval = index_1 - index_2;
3575 }
3576 /* We order the bookmarks sections based on the bookmark index that we
3577 * set on the row as an order-index property, but we have to deal with
3578 * the placeholder row wanted to be between two consecutive bookmarks,
3579 * with two consecutive order-index values which is the usual case.
3580 * For that, in the list box sort func we give priority to the placeholder row,
3581 * that means that if the index-order is the same as another bookmark
3582 * the placeholder row goes before. However if we want to show it after
3583 * the current row, for instance when the cursor is in the lower half
3584 * of the row, we need to increase the order-index.
3585 */
3586 else if (place_type_1 == GTK_PLACES_BOOKMARK_PLACEHOLDER && place_type_2 == GTK_PLACES_BOOKMARK)
3587 {
3588 if (index_1 == index_2)
3589 retval = index_1 - index_2 - 1;
3590 else
3591 retval = index_1 - index_2;
3592 }
3593 else if (place_type_1 == GTK_PLACES_BOOKMARK && place_type_2 == GTK_PLACES_BOOKMARK_PLACEHOLDER)
3594 {
3595 if (index_1 == index_2)
3596 retval = index_1 - index_2 + 1;
3597 else
3598 retval = index_1 - index_2;
3599 }
3600 }
3601 else
3602 {
3603 /* Order by section. That means the order in the enum of section types
3604 * define the actual order of them in the list */
3605 retval = section_type_1 - section_type_2;
3606 }
3607 }
3608
3609 g_free (mem: label_1);
3610 g_free (mem: label_2);
3611
3612 return retval;
3613}
3614
3615static void
3616update_hostname (GtkPlacesSidebar *sidebar)
3617{
3618 GVariant *variant;
3619 gsize len;
3620 const char *hostname;
3621
3622 if (sidebar->hostnamed_proxy == NULL)
3623 return;
3624
3625 variant = g_dbus_proxy_get_cached_property (proxy: sidebar->hostnamed_proxy,
3626 property_name: "PrettyHostname");
3627 if (variant == NULL)
3628 return;
3629
3630 hostname = g_variant_get_string (value: variant, length: &len);
3631 if (len > 0 &&
3632 g_strcmp0 (str1: sidebar->hostname, str2: hostname) != 0)
3633 {
3634 g_free (mem: sidebar->hostname);
3635 sidebar->hostname = g_strdup (str: hostname);
3636 update_places (sidebar);
3637 }
3638
3639 g_variant_unref (value: variant);
3640}
3641
3642static void
3643hostname_proxy_new_cb (GObject *source_object,
3644 GAsyncResult *res,
3645 gpointer user_data)
3646{
3647 GtkPlacesSidebar *sidebar = user_data;
3648 GError *error = NULL;
3649 GDBusProxy *proxy;
3650
3651 proxy = g_dbus_proxy_new_for_bus_finish (res, error: &error);
3652 if (g_error_matches (error, G_IO_ERROR, code: G_IO_ERROR_CANCELLED))
3653 {
3654 g_error_free (error);
3655 return;
3656 }
3657
3658 sidebar->hostnamed_proxy = proxy;
3659 g_clear_object (&sidebar->hostnamed_cancellable);
3660
3661 if (error != NULL)
3662 {
3663 g_debug ("Failed to create D-Bus proxy: %s", error->message);
3664 g_error_free (error);
3665 return;
3666 }
3667
3668 g_signal_connect_swapped (sidebar->hostnamed_proxy,
3669 "g-properties-changed",
3670 G_CALLBACK (update_hostname),
3671 sidebar);
3672 update_hostname (sidebar);
3673}
3674
3675static void
3676create_volume_monitor (GtkPlacesSidebar *sidebar)
3677{
3678 g_assert (sidebar->volume_monitor == NULL);
3679
3680 sidebar->volume_monitor = g_volume_monitor_get ();
3681
3682 g_signal_connect_object (instance: sidebar->volume_monitor, detailed_signal: "volume_added",
3683 G_CALLBACK (update_places), gobject: sidebar, connect_flags: G_CONNECT_SWAPPED);
3684 g_signal_connect_object (instance: sidebar->volume_monitor, detailed_signal: "volume_removed",
3685 G_CALLBACK (update_places), gobject: sidebar, connect_flags: G_CONNECT_SWAPPED);
3686 g_signal_connect_object (instance: sidebar->volume_monitor, detailed_signal: "volume_changed",
3687 G_CALLBACK (update_places), gobject: sidebar, connect_flags: G_CONNECT_SWAPPED);
3688 g_signal_connect_object (instance: sidebar->volume_monitor, detailed_signal: "mount_added",
3689 G_CALLBACK (update_places), gobject: sidebar, connect_flags: G_CONNECT_SWAPPED);
3690 g_signal_connect_object (instance: sidebar->volume_monitor, detailed_signal: "mount_removed",
3691 G_CALLBACK (update_places), gobject: sidebar, connect_flags: G_CONNECT_SWAPPED);
3692 g_signal_connect_object (instance: sidebar->volume_monitor, detailed_signal: "mount_changed",
3693 G_CALLBACK (update_places), gobject: sidebar, connect_flags: G_CONNECT_SWAPPED);
3694 g_signal_connect_object (instance: sidebar->volume_monitor, detailed_signal: "drive_disconnected",
3695 G_CALLBACK (update_places), gobject: sidebar, connect_flags: G_CONNECT_SWAPPED);
3696 g_signal_connect_object (instance: sidebar->volume_monitor, detailed_signal: "drive_connected",
3697 G_CALLBACK (update_places), gobject: sidebar, connect_flags: G_CONNECT_SWAPPED);
3698 g_signal_connect_object (instance: sidebar->volume_monitor, detailed_signal: "drive_changed",
3699 G_CALLBACK (update_places), gobject: sidebar, connect_flags: G_CONNECT_SWAPPED);
3700}
3701
3702static void
3703shell_shows_desktop_changed (GtkSettings *settings,
3704 GParamSpec *pspec,
3705 gpointer user_data)
3706{
3707 GtkPlacesSidebar *sidebar = user_data;
3708 gboolean show_desktop;
3709
3710 g_assert (settings == sidebar->gtk_settings);
3711
3712 /* Check if the user explicitly set this and, if so, don't change it. */
3713 if (sidebar->show_desktop_set)
3714 return;
3715
3716 g_object_get (object: settings, first_property_name: "gtk-shell-shows-desktop", &show_desktop, NULL);
3717
3718 if (show_desktop != sidebar->show_desktop)
3719 {
3720 sidebar->show_desktop = show_desktop;
3721 update_places (sidebar);
3722 g_object_notify_by_pspec (G_OBJECT (sidebar), pspec: properties[PROP_SHOW_DESKTOP]);
3723 }
3724}
3725
3726static void
3727gtk_places_sidebar_init (GtkPlacesSidebar *sidebar)
3728{
3729 GtkDropTarget *target;
3730 gboolean show_desktop;
3731 GtkEventController *controller;
3732 GtkGesture *gesture;
3733
3734 sidebar->cancellable = g_cancellable_new ();
3735
3736 sidebar->show_trash = TRUE;
3737 sidebar->show_other_locations = TRUE;
3738 sidebar->show_recent = TRUE;
3739 sidebar->show_desktop = TRUE;
3740
3741 sidebar->shortcuts = g_list_store_new (G_TYPE_FILE);
3742
3743 create_volume_monitor (sidebar);
3744
3745 sidebar->open_flags = GTK_PLACES_OPEN_NORMAL;
3746
3747 sidebar->bookmarks_manager = _gtk_bookmarks_manager_new (changed_func: (GtkBookmarksChangedFunc)update_places, changed_func_data: sidebar);
3748
3749 sidebar->trash_monitor = _gtk_trash_monitor_get ();
3750 sidebar->trash_monitor_changed_id = g_signal_connect_swapped (sidebar->trash_monitor, "trash-state-changed",
3751 G_CALLBACK (update_trash_icon), sidebar);
3752
3753 sidebar->swin = gtk_scrolled_window_new ();
3754 gtk_widget_set_parent (widget: sidebar->swin, GTK_WIDGET (sidebar));
3755 gtk_widget_set_size_request (widget: sidebar->swin, width: 140, height: 280);
3756
3757 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sidebar->swin),
3758 hscrollbar_policy: GTK_POLICY_NEVER,
3759 vscrollbar_policy: GTK_POLICY_AUTOMATIC);
3760
3761 gtk_widget_add_css_class (GTK_WIDGET (sidebar), css_class: "sidebar");
3762
3763 /* list box */
3764 sidebar->list_box = gtk_list_box_new ();
3765 gtk_widget_add_css_class (widget: sidebar->list_box, css_class: "navigation-sidebar");
3766
3767 gtk_list_box_set_header_func (GTK_LIST_BOX (sidebar->list_box),
3768 update_header: list_box_header_func, user_data: sidebar, NULL);
3769 gtk_list_box_set_sort_func (GTK_LIST_BOX (sidebar->list_box),
3770 sort_func: list_box_sort_func, NULL, NULL);
3771 gtk_list_box_set_selection_mode (GTK_LIST_BOX (sidebar->list_box), mode: GTK_SELECTION_SINGLE);
3772 gtk_list_box_set_activate_on_single_click (GTK_LIST_BOX (sidebar->list_box), TRUE);
3773
3774 g_signal_connect (sidebar->list_box, "row-activated",
3775 G_CALLBACK (on_row_activated), sidebar);
3776
3777 controller = gtk_event_controller_key_new ();
3778 g_signal_connect (controller, "key-pressed",
3779 G_CALLBACK (on_key_pressed), sidebar);
3780 gtk_widget_add_controller (widget: sidebar->list_box, controller);
3781
3782 gesture = gtk_gesture_long_press_new ();
3783 gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture), TRUE);
3784 g_signal_connect (gesture, "pressed",
3785 G_CALLBACK (long_press_cb), sidebar);
3786 gtk_widget_add_controller (GTK_WIDGET (sidebar), GTK_EVENT_CONTROLLER (gesture));
3787
3788 /* DND support */
3789 target = gtk_drop_target_new (G_TYPE_INVALID, actions: GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK);
3790 gtk_drop_target_set_preload (self: target, TRUE);
3791 gtk_drop_target_set_gtypes (self: target, types: (GType[2]) { GTK_TYPE_SIDEBAR_ROW, GDK_TYPE_FILE_LIST }, n_types: 2);
3792 g_signal_connect (target, "enter", G_CALLBACK (drag_motion_callback), sidebar);
3793 g_signal_connect (target, "motion", G_CALLBACK (drag_motion_callback), sidebar);
3794 g_signal_connect (target, "drop", G_CALLBACK (drag_drop_callback), sidebar);
3795 g_signal_connect (target, "leave", G_CALLBACK (drag_leave_callback), sidebar);
3796 gtk_widget_add_controller (widget: sidebar->list_box, GTK_EVENT_CONTROLLER (target));
3797
3798 sidebar->drag_row = NULL;
3799 sidebar->row_placeholder = NULL;
3800 sidebar->dragging_over = FALSE;
3801
3802 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sidebar->swin), child: sidebar->list_box);
3803
3804 sidebar->hostname = g_strdup (_("Computer"));
3805 sidebar->hostnamed_cancellable = g_cancellable_new ();
3806 g_dbus_proxy_new_for_bus (bus_type: G_BUS_TYPE_SYSTEM,
3807 flags: G_DBUS_PROXY_FLAGS_GET_INVALIDATED_PROPERTIES,
3808 NULL,
3809 name: "org.freedesktop.hostname1",
3810 object_path: "/org/freedesktop/hostname1",
3811 interface_name: "org.freedesktop.hostname1",
3812 cancellable: sidebar->hostnamed_cancellable,
3813 callback: hostname_proxy_new_cb,
3814 user_data: sidebar);
3815
3816 sidebar->drop_state = DROP_STATE_NORMAL;
3817
3818 /* Don't bother trying to trace this across hierarchy changes... */
3819 sidebar->gtk_settings = gtk_settings_get_default ();
3820 g_signal_connect (sidebar->gtk_settings, "notify::gtk-shell-shows-desktop",
3821 G_CALLBACK (shell_shows_desktop_changed), sidebar);
3822 g_object_get (object: sidebar->gtk_settings, first_property_name: "gtk-shell-shows-desktop", &show_desktop, NULL);
3823 sidebar->show_desktop = show_desktop;
3824
3825 /* Cloud providers */
3826#ifdef HAVE_CLOUDPROVIDERS
3827 sidebar->cloud_manager = cloud_providers_collector_dup_singleton ();
3828 g_signal_connect_swapped (sidebar->cloud_manager,
3829 "providers-changed",
3830 G_CALLBACK (update_places),
3831 sidebar);
3832#endif
3833
3834 /* populate the sidebar */
3835 update_places (sidebar);
3836
3837 sidebar->row_actions = G_ACTION_GROUP (g_simple_action_group_new ());
3838 g_action_map_add_action_entries (G_ACTION_MAP (sidebar->row_actions),
3839 entries, G_N_ELEMENTS (entries),
3840 user_data: sidebar);
3841 gtk_widget_insert_action_group (GTK_WIDGET (sidebar), name: "row", group: sidebar->row_actions);
3842}
3843
3844static void
3845gtk_places_sidebar_set_property (GObject *obj,
3846 guint property_id,
3847 const GValue *value,
3848 GParamSpec *pspec)
3849{
3850 GtkPlacesSidebar *sidebar = GTK_PLACES_SIDEBAR (obj);
3851
3852 switch (property_id)
3853 {
3854 case PROP_LOCATION:
3855 gtk_places_sidebar_set_location (sidebar, location: g_value_get_object (value));
3856 break;
3857
3858 case PROP_OPEN_FLAGS:
3859 gtk_places_sidebar_set_open_flags (sidebar, flags: g_value_get_flags (value));
3860 break;
3861
3862 case PROP_SHOW_RECENT:
3863 gtk_places_sidebar_set_show_recent (sidebar, show_recent: g_value_get_boolean (value));
3864 break;
3865
3866 case PROP_SHOW_DESKTOP:
3867 gtk_places_sidebar_set_show_desktop (sidebar, show_desktop: g_value_get_boolean (value));
3868 break;
3869
3870 case PROP_SHOW_ENTER_LOCATION:
3871 gtk_places_sidebar_set_show_enter_location (sidebar, show_enter_location: g_value_get_boolean (value));
3872 break;
3873
3874 case PROP_SHOW_OTHER_LOCATIONS:
3875 gtk_places_sidebar_set_show_other_locations (sidebar, show_other_locations: g_value_get_boolean (value));
3876 break;
3877
3878 case PROP_SHOW_TRASH:
3879 gtk_places_sidebar_set_show_trash (sidebar, show_trash: g_value_get_boolean (value));
3880 break;
3881
3882 case PROP_SHOW_STARRED_LOCATION:
3883 gtk_places_sidebar_set_show_starred_location (sidebar, show_starred_location: g_value_get_boolean (value));
3884 break;
3885
3886 default:
3887 G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec);
3888 break;
3889 }
3890}
3891
3892static void
3893gtk_places_sidebar_get_property (GObject *obj,
3894 guint property_id,
3895 GValue *value,
3896 GParamSpec *pspec)
3897{
3898 GtkPlacesSidebar *sidebar = GTK_PLACES_SIDEBAR (obj);
3899
3900 switch (property_id)
3901 {
3902 case PROP_LOCATION:
3903 g_value_take_object (value, v_object: gtk_places_sidebar_get_location (sidebar));
3904 break;
3905
3906 case PROP_OPEN_FLAGS:
3907 g_value_set_flags (value, v_flags: gtk_places_sidebar_get_open_flags (sidebar));
3908 break;
3909
3910 case PROP_SHOW_RECENT:
3911 g_value_set_boolean (value, v_boolean: gtk_places_sidebar_get_show_recent (sidebar));
3912 break;
3913
3914 case PROP_SHOW_DESKTOP:
3915 g_value_set_boolean (value, v_boolean: gtk_places_sidebar_get_show_desktop (sidebar));
3916 break;
3917
3918 case PROP_SHOW_ENTER_LOCATION:
3919 g_value_set_boolean (value, v_boolean: gtk_places_sidebar_get_show_enter_location (sidebar));
3920 break;
3921
3922 case PROP_SHOW_OTHER_LOCATIONS:
3923 g_value_set_boolean (value, v_boolean: gtk_places_sidebar_get_show_other_locations (sidebar));
3924 break;
3925
3926 case PROP_SHOW_TRASH:
3927 g_value_set_boolean (value, v_boolean: gtk_places_sidebar_get_show_trash (sidebar));
3928 break;
3929
3930 case PROP_SHOW_STARRED_LOCATION:
3931 g_value_set_boolean (value, v_boolean: gtk_places_sidebar_get_show_starred_location (sidebar));
3932 break;
3933
3934 default:
3935 G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec);
3936 break;
3937 }
3938}
3939
3940static void
3941gtk_places_sidebar_dispose (GObject *object)
3942{
3943 GtkPlacesSidebar *sidebar;
3944#ifdef HAVE_CLOUDPROVIDERS
3945 GList *l;
3946#endif
3947
3948 sidebar = GTK_PLACES_SIDEBAR (object);
3949
3950 if (sidebar->cancellable)
3951 {
3952 g_cancellable_cancel (cancellable: sidebar->cancellable);
3953 g_object_unref (object: sidebar->cancellable);
3954 sidebar->cancellable = NULL;
3955 }
3956
3957 if (sidebar->bookmarks_manager != NULL)
3958 {
3959 _gtk_bookmarks_manager_free (manager: sidebar->bookmarks_manager);
3960 sidebar->bookmarks_manager = NULL;
3961 }
3962
3963 g_clear_pointer (&sidebar->popover, gtk_widget_unparent);
3964
3965 if (sidebar->rename_popover)
3966 {
3967 gtk_widget_unparent (widget: sidebar->rename_popover);
3968 sidebar->rename_popover = NULL;
3969 sidebar->rename_entry = NULL;
3970 sidebar->rename_button = NULL;
3971 sidebar->rename_error = NULL;
3972 }
3973
3974 if (sidebar->trash_monitor)
3975 {
3976 g_signal_handler_disconnect (instance: sidebar->trash_monitor, handler_id: sidebar->trash_monitor_changed_id);
3977 sidebar->trash_monitor_changed_id = 0;
3978 g_clear_object (&sidebar->trash_monitor);
3979 }
3980
3981 if (sidebar->trash_row)
3982 {
3983 g_object_remove_weak_pointer (G_OBJECT (sidebar->trash_row),
3984 weak_pointer_location: (gpointer *) &sidebar->trash_row);
3985 sidebar->trash_row = NULL;
3986 }
3987
3988 if (sidebar->volume_monitor != NULL)
3989 {
3990 g_signal_handlers_disconnect_by_func (sidebar->volume_monitor,
3991 update_places, sidebar);
3992 g_clear_object (&sidebar->volume_monitor);
3993 }
3994
3995 if (sidebar->hostnamed_cancellable != NULL)
3996 {
3997 g_cancellable_cancel (cancellable: sidebar->hostnamed_cancellable);
3998 g_clear_object (&sidebar->hostnamed_cancellable);
3999 }
4000
4001 g_clear_object (&sidebar->hostnamed_proxy);
4002 g_free (mem: sidebar->hostname);
4003 sidebar->hostname = NULL;
4004
4005 if (sidebar->gtk_settings)
4006 {
4007 g_signal_handlers_disconnect_by_func (sidebar->gtk_settings, shell_shows_desktop_changed, sidebar);
4008 sidebar->gtk_settings = NULL;
4009 }
4010
4011 g_clear_object (&sidebar->current_location);
4012 g_clear_pointer (&sidebar->rename_uri, g_free);
4013 g_clear_object (&sidebar->shortcuts);
4014
4015#ifdef HAVE_CLOUDPROVIDERS
4016 for (l = sidebar->unready_accounts; l != NULL; l = l->next)
4017 {
4018 g_signal_handlers_disconnect_by_data (l->data, sidebar);
4019 }
4020 g_list_free_full (sidebar->unready_accounts, g_object_unref);
4021 sidebar->unready_accounts = NULL;
4022 if (sidebar->cloud_manager)
4023 {
4024 g_signal_handlers_disconnect_by_data (sidebar->cloud_manager, sidebar);
4025 for (l = cloud_providers_collector_get_providers (sidebar->cloud_manager);
4026 l != NULL; l = l->next)
4027 {
4028 g_signal_handlers_disconnect_by_data (l->data, sidebar);
4029 }
4030 g_object_unref (sidebar->cloud_manager);
4031 sidebar->cloud_manager = NULL;
4032 }
4033#endif
4034
4035 G_OBJECT_CLASS (gtk_places_sidebar_parent_class)->dispose (object);
4036}
4037
4038static void
4039gtk_places_sidebar_finalize (GObject *object)
4040{
4041 GtkPlacesSidebar *sidebar = GTK_PLACES_SIDEBAR (object);
4042
4043 g_clear_object (&sidebar->row_actions);
4044
4045 g_clear_pointer (&sidebar->swin, gtk_widget_unparent);
4046
4047 G_OBJECT_CLASS (gtk_places_sidebar_parent_class)->finalize (object);
4048}
4049
4050static void
4051gtk_places_sidebar_measure (GtkWidget *widget,
4052 GtkOrientation orientation,
4053 int for_size,
4054 int *minimum,
4055 int *natural,
4056 int *minimum_baseline,
4057 int *natural_baseline)
4058{
4059 GtkPlacesSidebar *sidebar = GTK_PLACES_SIDEBAR (widget);
4060
4061 gtk_widget_measure (widget: sidebar->swin,
4062 orientation,
4063 for_size,
4064 minimum, natural,
4065 minimum_baseline, natural_baseline);
4066}
4067
4068static void
4069gtk_places_sidebar_size_allocate (GtkWidget *widget,
4070 int width,
4071 int height,
4072 int baseline)
4073{
4074 GtkPlacesSidebar *sidebar = GTK_PLACES_SIDEBAR (widget);
4075
4076 gtk_widget_size_allocate (widget: sidebar->swin,
4077 allocation: &(GtkAllocation) { 0, 0, width, height },
4078 baseline);
4079
4080 if (sidebar->popover)
4081 gtk_popover_present (GTK_POPOVER (sidebar->popover));
4082
4083 if (sidebar->rename_popover)
4084 gtk_popover_present (GTK_POPOVER (sidebar->rename_popover));
4085}
4086
4087static void
4088gtk_places_sidebar_class_init (GtkPlacesSidebarClass *class)
4089{
4090 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
4091 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
4092
4093
4094 gobject_class->dispose = gtk_places_sidebar_dispose;
4095 gobject_class->finalize = gtk_places_sidebar_finalize;
4096 gobject_class->set_property = gtk_places_sidebar_set_property;
4097 gobject_class->get_property = gtk_places_sidebar_get_property;
4098
4099 widget_class->measure = gtk_places_sidebar_measure;
4100 widget_class->size_allocate = gtk_places_sidebar_size_allocate;
4101
4102 /*
4103 * GtkPlacesSidebar::open-location:
4104 * @sidebar: the object which received the signal.
4105 * @location: (type Gio.File): GFile to which the caller should switch.
4106 * @open_flags: a single value from GtkPlacesOpenFlags specifying how the @location should be opened.
4107 *
4108 * The places sidebar emits this signal when the user selects a location
4109 * in it. The calling application should display the contents of that
4110 * location; for example, a file manager should show a list of files in
4111 * the specified location.
4112 */
4113 places_sidebar_signals [OPEN_LOCATION] =
4114 g_signal_new (I_("open-location"),
4115 G_OBJECT_CLASS_TYPE (gobject_class),
4116 signal_flags: G_SIGNAL_RUN_FIRST,
4117 G_STRUCT_OFFSET (GtkPlacesSidebarClass, open_location),
4118 NULL, NULL,
4119 c_marshaller: _gtk_marshal_VOID__OBJECT_FLAGS,
4120 G_TYPE_NONE, n_params: 2,
4121 G_TYPE_OBJECT,
4122 GTK_TYPE_PLACES_OPEN_FLAGS);
4123
4124 /*
4125 * GtkPlacesSidebar::show-error-message:
4126 * @sidebar: the object which received the signal.
4127 * @primary: primary message with a summary of the error to show.
4128 * @secondary: secondary message with details of the error to show.
4129 *
4130 * The places sidebar emits this signal when it needs the calling
4131 * application to present an error message. Most of these messages
4132 * refer to mounting or unmounting media, for example, when a drive
4133 * cannot be started for some reason.
4134 */
4135 places_sidebar_signals [SHOW_ERROR_MESSAGE] =
4136 g_signal_new (I_("show-error-message"),
4137 G_OBJECT_CLASS_TYPE (gobject_class),
4138 signal_flags: G_SIGNAL_RUN_FIRST,
4139 G_STRUCT_OFFSET (GtkPlacesSidebarClass, show_error_message),
4140 NULL, NULL,
4141 c_marshaller: _gtk_marshal_VOID__STRING_STRING,
4142 G_TYPE_NONE, n_params: 2,
4143 G_TYPE_STRING,
4144 G_TYPE_STRING);
4145
4146 /*
4147 * GtkPlacesSidebar::show-enter-location:
4148 * @sidebar: the object which received the signal.
4149 *
4150 * The places sidebar emits this signal when it needs the calling
4151 * application to present a way to directly enter a location.
4152 * For example, the application may bring up a dialog box asking for
4153 * a URL like "http://http.example.com".
4154 */
4155 places_sidebar_signals [SHOW_ENTER_LOCATION] =
4156 g_signal_new (I_("show-enter-location"),
4157 G_OBJECT_CLASS_TYPE (gobject_class),
4158 signal_flags: G_SIGNAL_RUN_FIRST,
4159 G_STRUCT_OFFSET (GtkPlacesSidebarClass, show_enter_location),
4160 NULL, NULL,
4161 NULL,
4162 G_TYPE_NONE, n_params: 0);
4163
4164 /*
4165 * GtkPlacesSidebar::drag-action-requested:
4166 * @sidebar: the object which received the signal.
4167 * @drop: (type Gdk.Drop): GdkDrop with information about the drag operation
4168 * @dest_file: (type Gio.File): GFile with the tentative location that is being hovered for a drop
4169 * @source_file_list: (type GLib.SList) (element-type GFile) (transfer none):
4170 * List of GFile that are being dragged
4171 *
4172 * When the user starts a drag-and-drop operation and the sidebar needs
4173 * to ask the application for which drag action to perform, then the
4174 * sidebar will emit this signal.
4175 *
4176 * The application can evaluate the @context for customary actions, or
4177 * it can check the type of the files indicated by @source_file_list against the
4178 * possible actions for the destination @dest_file.
4179 *
4180 * The drag action to use must be the return value of the signal handler.
4181 *
4182 * Returns: The drag action to use, for example, GDK_ACTION_COPY
4183 * or GDK_ACTION_MOVE, or 0 if no action is allowed here (i.e. drops
4184 * are not allowed in the specified @dest_file).
4185 */
4186 places_sidebar_signals [DRAG_ACTION_REQUESTED] =
4187 g_signal_new (I_("drag-action-requested"),
4188 G_OBJECT_CLASS_TYPE (gobject_class),
4189 signal_flags: G_SIGNAL_RUN_LAST,
4190 G_STRUCT_OFFSET (GtkPlacesSidebarClass, drag_action_requested),
4191 NULL, NULL,
4192 c_marshaller: _gtk_marshal_INT__OBJECT_OBJECT_POINTER,
4193 return_type: GDK_TYPE_DRAG_ACTION, n_params: 2,
4194 G_TYPE_OBJECT,
4195 GDK_TYPE_FILE_LIST);
4196
4197 /*
4198 * GtkPlacesSidebar::drag-action-ask:
4199 * @sidebar: the object which received the signal.
4200 * @actions: Possible drag actions that need to be asked for.
4201 *
4202 * The places sidebar emits this signal when it needs to ask the application
4203 * to pop up a menu to ask the user for which drag action to perform.
4204 *
4205 * Returns: the final drag action that the sidebar should pass to the drag side
4206 * of the drag-and-drop operation.
4207 */
4208 places_sidebar_signals [DRAG_ACTION_ASK] =
4209 g_signal_new (I_("drag-action-ask"),
4210 G_OBJECT_CLASS_TYPE (gobject_class),
4211 signal_flags: G_SIGNAL_RUN_LAST,
4212 G_STRUCT_OFFSET (GtkPlacesSidebarClass, drag_action_ask),
4213 NULL, NULL,
4214 c_marshaller: _gtk_marshal_INT__INT,
4215 return_type: GDK_TYPE_DRAG_ACTION, n_params: 1,
4216 GDK_TYPE_DRAG_ACTION);
4217
4218 /*
4219 * GtkPlacesSidebar::drag-perform-drop:
4220 * @sidebar: the object which received the signal.
4221 * @dest_file: (type Gio.File): Destination GFile.
4222 * @source_file_list: (type GLib.SList) (element-type GFile) (transfer none):
4223 * GSList of GFile that got dropped.
4224 * @action: Drop action to perform.
4225 *
4226 * The places sidebar emits this signal when the user completes a
4227 * drag-and-drop operation and one of the sidebar's items is the
4228 * destination. This item is in the @dest_file, and the
4229 * @source_file_list has the list of files that are dropped into it and
4230 * which should be copied/moved/etc. based on the specified @action.
4231 */
4232 places_sidebar_signals [DRAG_PERFORM_DROP] =
4233 g_signal_new (I_("drag-perform-drop"),
4234 G_OBJECT_CLASS_TYPE (gobject_class),
4235 signal_flags: G_SIGNAL_RUN_FIRST,
4236 G_STRUCT_OFFSET (GtkPlacesSidebarClass, drag_perform_drop),
4237 NULL, NULL,
4238 c_marshaller: _gtk_marshal_VOID__OBJECT_POINTER_INT,
4239 G_TYPE_NONE, n_params: 3,
4240 G_TYPE_OBJECT,
4241 GDK_TYPE_FILE_LIST,
4242 GDK_TYPE_DRAG_ACTION);
4243
4244 /*
4245 * GtkPlacesSidebar::show-other-locations-with-flags:
4246 * @sidebar: the object which received the signal.
4247 * @open_flags: a single value from GtkPlacesOpenFlags specifying how it should be opened.
4248 *
4249 * The places sidebar emits this signal when it needs the calling
4250 * application to present a way to show other locations e.g. drives
4251 * and network access points.
4252 * For example, the application may bring up a page showing persistent
4253 * volumes and discovered network addresses.
4254 */
4255 places_sidebar_signals [SHOW_OTHER_LOCATIONS_WITH_FLAGS] =
4256 g_signal_new (I_("show-other-locations-with-flags"),
4257 G_OBJECT_CLASS_TYPE (gobject_class),
4258 signal_flags: G_SIGNAL_RUN_FIRST,
4259 G_STRUCT_OFFSET (GtkPlacesSidebarClass, show_other_locations_with_flags),
4260 NULL, NULL,
4261 NULL,
4262 G_TYPE_NONE, n_params: 1,
4263 GTK_TYPE_PLACES_OPEN_FLAGS);
4264
4265 /*
4266 * GtkPlacesSidebar::mount:
4267 * @sidebar: the object which received the signal.
4268 * @mount_operation: the GMountOperation that is going to start.
4269 *
4270 * The places sidebar emits this signal when it starts a new operation
4271 * because the user clicked on some location that needs mounting.
4272 * In this way the application using the GtkPlacesSidebar can track the
4273 * progress of the operation and, for example, show a notification.
4274 */
4275 places_sidebar_signals [MOUNT] =
4276 g_signal_new (I_("mount"),
4277 G_OBJECT_CLASS_TYPE (gobject_class),
4278 signal_flags: G_SIGNAL_RUN_FIRST,
4279 G_STRUCT_OFFSET (GtkPlacesSidebarClass, mount),
4280 NULL, NULL,
4281 NULL,
4282 G_TYPE_NONE,
4283 n_params: 1,
4284 G_TYPE_MOUNT_OPERATION);
4285 /*
4286 * GtkPlacesSidebar::unmount:
4287 * @sidebar: the object which received the signal.
4288 * @mount_operation: the GMountOperation that is going to start.
4289 *
4290 * The places sidebar emits this signal when it starts a new operation
4291 * because the user for example ejected some drive or unmounted a mount.
4292 * In this way the application using the GtkPlacesSidebar can track the
4293 * progress of the operation and, for example, show a notification.
4294 */
4295 places_sidebar_signals [UNMOUNT] =
4296 g_signal_new (I_("unmount"),
4297 G_OBJECT_CLASS_TYPE (gobject_class),
4298 signal_flags: G_SIGNAL_RUN_FIRST,
4299 G_STRUCT_OFFSET (GtkPlacesSidebarClass, unmount),
4300 NULL, NULL,
4301 NULL,
4302 G_TYPE_NONE,
4303 n_params: 1,
4304 G_TYPE_MOUNT_OPERATION);
4305
4306 /*
4307 * GtkPlacesSidebar::show-starred-location:
4308 * @sidebar: the object which received the signal
4309 * @flags: the flags for the operation
4310 *
4311 * The places sidebar emits this signal when it needs the calling
4312 * application to present a way to show the starred files. In GNOME,
4313 * starred files are implemented by setting the nao:predefined-tag-favorite
4314 * tag in the tracker database.
4315 */
4316 places_sidebar_signals [SHOW_STARRED_LOCATION] =
4317 g_signal_new (I_("show-starred-location"),
4318 G_OBJECT_CLASS_TYPE (gobject_class),
4319 signal_flags: G_SIGNAL_RUN_FIRST,
4320 G_STRUCT_OFFSET (GtkPlacesSidebarClass, show_starred_location),
4321 NULL, NULL,
4322 NULL,
4323 G_TYPE_NONE, n_params: 1,
4324 GTK_TYPE_PLACES_OPEN_FLAGS);
4325
4326 properties[PROP_LOCATION] =
4327 g_param_spec_object (name: "location",
4328 P_("Location to Select"),
4329 P_("The location to highlight in the sidebar"),
4330 G_TYPE_FILE,
4331 GTK_PARAM_READWRITE);
4332 properties[PROP_OPEN_FLAGS] =
4333 g_param_spec_flags (name: "open-flags",
4334 P_("Open Flags"),
4335 P_("Modes in which the calling application can open locations selected in the sidebar"),
4336 flags_type: GTK_TYPE_PLACES_OPEN_FLAGS,
4337 default_value: GTK_PLACES_OPEN_NORMAL,
4338 GTK_PARAM_READWRITE);
4339 properties[PROP_SHOW_RECENT] =
4340 g_param_spec_boolean (name: "show-recent",
4341 P_("Show recent files"),
4342 P_("Whether the sidebar includes a builtin shortcut for recent files"),
4343 TRUE,
4344 GTK_PARAM_READWRITE);
4345 properties[PROP_SHOW_DESKTOP] =
4346 g_param_spec_boolean (name: "show-desktop",
4347 P_("Show “Desktop”"),
4348 P_("Whether the sidebar includes a builtin shortcut to the Desktop folder"),
4349 TRUE,
4350 GTK_PARAM_READWRITE);
4351 properties[PROP_SHOW_ENTER_LOCATION] =
4352 g_param_spec_boolean (name: "show-enter-location",
4353 P_("Show “Enter Location”"),
4354 P_("Whether the sidebar includes a builtin shortcut to manually enter a location"),
4355 FALSE,
4356 GTK_PARAM_READWRITE);
4357 properties[PROP_SHOW_TRASH] =
4358 g_param_spec_boolean (name: "show-trash",
4359 P_("Show “Trash”"),
4360 P_("Whether the sidebar includes a builtin shortcut to the Trash location"),
4361 TRUE,
4362 GTK_PARAM_READWRITE);
4363 properties[PROP_SHOW_OTHER_LOCATIONS] =
4364 g_param_spec_boolean (name: "show-other-locations",
4365 P_("Show “Other locations”"),
4366 P_("Whether the sidebar includes an item to show external locations"),
4367 TRUE,
4368 GTK_PARAM_READWRITE);
4369 properties[PROP_SHOW_STARRED_LOCATION] =
4370 g_param_spec_boolean (name: "show-starred-location",
4371 P_("Show “Starred Location”"),
4372 P_("Whether the sidebar includes an item to show starred files"),
4373 FALSE,
4374 GTK_PARAM_READWRITE);
4375
4376 g_object_class_install_properties (oclass: gobject_class, n_pspecs: NUM_PROPERTIES, pspecs: properties);
4377
4378 gtk_widget_class_set_css_name (widget_class, I_("placessidebar"));
4379}
4380
4381/*
4382 * gtk_places_sidebar_new:
4383 *
4384 * Creates a new GtkPlacesSidebar widget.
4385 *
4386 * The application should connect to at least the
4387 * GtkPlacesSidebar::open-location signal to be notified
4388 * when the user makes a selection in the sidebar.
4389 *
4390 * Returns: a newly created GtkPlacesSidebar
4391 */
4392GtkWidget *
4393gtk_places_sidebar_new (void)
4394{
4395 return GTK_WIDGET (g_object_new (gtk_places_sidebar_get_type (), NULL));
4396}
4397
4398/*
4399 * gtk_places_sidebar_set_open_flags:
4400 * @sidebar: a places sidebar
4401 * @flags: Bitmask of modes in which the calling application can open locations
4402 *
4403 * Sets the way in which the calling application can open new locations from
4404 * the places sidebar. For example, some applications only open locations
4405 * “directly” into their main view, while others may support opening locations
4406 * in a new notebook tab or a new window.
4407 *
4408 * This function is used to tell the places @sidebar about the ways in which the
4409 * application can open new locations, so that the sidebar can display (or not)
4410 * the “Open in new tab” and “Open in new window” menu items as appropriate.
4411 *
4412 * When the GtkPlacesSidebar::open-location signal is emitted, its flags
4413 * argument will be set to one of the @flags that was passed in
4414 * gtk_places_sidebar_set_open_flags().
4415 *
4416 * Passing 0 for @flags will cause GTK_PLACES_OPEN_NORMAL to always be sent
4417 * to callbacks for the “open-location” signal.
4418 */
4419void
4420gtk_places_sidebar_set_open_flags (GtkPlacesSidebar *sidebar,
4421 GtkPlacesOpenFlags flags)
4422{
4423 g_return_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar));
4424
4425 if (sidebar->open_flags != flags)
4426 {
4427 sidebar->open_flags = flags;
4428 g_object_notify_by_pspec (G_OBJECT (sidebar), pspec: properties[PROP_OPEN_FLAGS]);
4429 }
4430}
4431
4432/*
4433 * gtk_places_sidebar_get_open_flags:
4434 * @sidebar: a GtkPlacesSidebar
4435 *
4436 * Gets the open flags.
4437 *
4438 * Returns: the GtkPlacesOpenFlags of @sidebar
4439 */
4440GtkPlacesOpenFlags
4441gtk_places_sidebar_get_open_flags (GtkPlacesSidebar *sidebar)
4442{
4443 g_return_val_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar), 0);
4444
4445 return sidebar->open_flags;
4446}
4447
4448/*
4449 * gtk_places_sidebar_set_location:
4450 * @sidebar: a places sidebar
4451 * @location: (nullable): location to select, or %NULL for no current path
4452 *
4453 * Sets the location that is being shown in the widgets surrounding the
4454 * @sidebar, for example, in a folder view in a file manager. In turn, the
4455 * @sidebar will highlight that location if it is being shown in the list of
4456 * places, or it will unhighlight everything if the @location is not among the
4457 * places in the list.
4458 */
4459void
4460gtk_places_sidebar_set_location (GtkPlacesSidebar *sidebar,
4461 GFile *location)
4462{
4463 GtkWidget *row;
4464 char *row_uri;
4465 char *uri;
4466 gboolean found = FALSE;
4467
4468 g_return_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar));
4469
4470 gtk_list_box_unselect_all (GTK_LIST_BOX (sidebar->list_box));
4471
4472 if (sidebar->current_location != NULL)
4473 g_object_unref (object: sidebar->current_location);
4474 sidebar->current_location = location;
4475 if (sidebar->current_location != NULL)
4476 g_object_ref (sidebar->current_location);
4477
4478 if (location == NULL)
4479 goto out;
4480
4481 uri = g_file_get_uri (file: location);
4482
4483 for (row = gtk_widget_get_first_child (GTK_WIDGET (sidebar->list_box));
4484 row != NULL && !found;
4485 row = gtk_widget_get_next_sibling (widget: row))
4486 {
4487 if (!GTK_IS_LIST_BOX_ROW (row))
4488 continue;
4489
4490 g_object_get (object: row, first_property_name: "uri", &row_uri, NULL);
4491 if (row_uri != NULL && g_strcmp0 (str1: row_uri, str2: uri) == 0)
4492 {
4493 gtk_list_box_select_row (GTK_LIST_BOX (sidebar->list_box),
4494 GTK_LIST_BOX_ROW (row));
4495 found = TRUE;
4496 }
4497
4498 g_free (mem: row_uri);
4499 }
4500
4501 g_free (mem: uri);
4502
4503 out:
4504 g_object_notify_by_pspec (G_OBJECT (sidebar), pspec: properties[PROP_LOCATION]);
4505}
4506
4507/*
4508 * gtk_places_sidebar_get_location:
4509 * @sidebar: a places sidebar
4510 *
4511 * Gets the currently selected location in the @sidebar. This can be %NULL when
4512 * nothing is selected, for example, when gtk_places_sidebar_set_location() has
4513 * been called with a location that is not among the sidebar’s list of places to
4514 * show.
4515 *
4516 * You can use this function to get the selection in the @sidebar.
4517 *
4518 * Returns: (nullable) (transfer full): a GFile with the selected location, or
4519 * %NULL if nothing is visually selected.
4520 */
4521GFile *
4522gtk_places_sidebar_get_location (GtkPlacesSidebar *sidebar)
4523{
4524 GtkListBoxRow *selected;
4525 GFile *file;
4526
4527 g_return_val_if_fail (sidebar != NULL, NULL);
4528
4529 file = NULL;
4530 selected = gtk_list_box_get_selected_row (GTK_LIST_BOX (sidebar->list_box));
4531
4532 if (selected)
4533 {
4534 char *uri;
4535
4536 g_object_get (object: selected, first_property_name: "uri", &uri, NULL);
4537 file = g_file_new_for_uri (uri);
4538 g_free (mem: uri);
4539 }
4540
4541 return file;
4542}
4543
4544char *
4545gtk_places_sidebar_get_location_title (GtkPlacesSidebar *sidebar)
4546{
4547 GtkListBoxRow *selected;
4548 char *title;
4549
4550 g_return_val_if_fail (sidebar != NULL, NULL);
4551
4552 title = NULL;
4553 selected = gtk_list_box_get_selected_row (GTK_LIST_BOX (sidebar->list_box));
4554
4555 if (selected)
4556 g_object_get (object: selected, first_property_name: "label", &title, NULL);
4557
4558 return title;
4559}
4560
4561/*
4562 * gtk_places_sidebar_set_show_recent:
4563 * @sidebar: a places sidebar
4564 * @show_recent: whether to show an item for recent files
4565 *
4566 * Sets whether the @sidebar should show an item for recent files.
4567 * The default value for this option is determined by the desktop
4568 * environment, but this function can be used to override it on a
4569 * per-application basis.
4570 */
4571void
4572gtk_places_sidebar_set_show_recent (GtkPlacesSidebar *sidebar,
4573 gboolean show_recent)
4574{
4575 g_return_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar));
4576
4577 sidebar->show_recent_set = TRUE;
4578
4579 show_recent = !!show_recent;
4580 if (sidebar->show_recent != show_recent)
4581 {
4582 sidebar->show_recent = show_recent;
4583 update_places (sidebar);
4584 g_object_notify_by_pspec (G_OBJECT (sidebar), pspec: properties[PROP_SHOW_RECENT]);
4585 }
4586}
4587
4588/*
4589 * gtk_places_sidebar_get_show_recent:
4590 * @sidebar: a places sidebar
4591 *
4592 * Returns the value previously set with gtk_places_sidebar_set_show_recent()
4593 *
4594 * Returns: %TRUE if the sidebar will display a builtin shortcut for recent files
4595 */
4596gboolean
4597gtk_places_sidebar_get_show_recent (GtkPlacesSidebar *sidebar)
4598{
4599 g_return_val_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar), FALSE);
4600
4601 return sidebar->show_recent;
4602}
4603
4604/*
4605 * gtk_places_sidebar_set_show_desktop:
4606 * @sidebar: a places sidebar
4607 * @show_desktop: whether to show an item for the Desktop folder
4608 *
4609 * Sets whether the @sidebar should show an item for the Desktop folder.
4610 * The default value for this option is determined by the desktop
4611 * environment and the user’s configuration, but this function can be
4612 * used to override it on a per-application basis.
4613 */
4614void
4615gtk_places_sidebar_set_show_desktop (GtkPlacesSidebar *sidebar,
4616 gboolean show_desktop)
4617{
4618 g_return_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar));
4619
4620 /* Don't bother disconnecting from the GtkSettings -- it will just
4621 * complicate things. Besides, it's highly unlikely that this will
4622 * change while we're running, but we can ignore it if it does.
4623 */
4624 sidebar->show_desktop_set = TRUE;
4625
4626 show_desktop = !!show_desktop;
4627 if (sidebar->show_desktop != show_desktop)
4628 {
4629 sidebar->show_desktop = show_desktop;
4630 update_places (sidebar);
4631 g_object_notify_by_pspec (G_OBJECT (sidebar), pspec: properties[PROP_SHOW_DESKTOP]);
4632 }
4633}
4634
4635/*
4636 * gtk_places_sidebar_get_show_desktop:
4637 * @sidebar: a places sidebar
4638 *
4639 * Returns the value previously set with gtk_places_sidebar_set_show_desktop()
4640 *
4641 * Returns: %TRUE if the sidebar will display a builtin shortcut to the desktop folder.
4642 */
4643gboolean
4644gtk_places_sidebar_get_show_desktop (GtkPlacesSidebar *sidebar)
4645{
4646 g_return_val_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar), FALSE);
4647
4648 return sidebar->show_desktop;
4649}
4650
4651/*
4652 * gtk_places_sidebar_set_show_enter_location:
4653 * @sidebar: a places sidebar
4654 * @show_enter_location: whether to show an item to enter a location
4655 *
4656 * Sets whether the @sidebar should show an item for entering a location;
4657 * this is off by default. An application may want to turn this on if manually
4658 * entering URLs is an expected user action.
4659 *
4660 * If you enable this, you should connect to the
4661 * GtkPlacesSidebar::show-enter-location signal.
4662 */
4663void
4664gtk_places_sidebar_set_show_enter_location (GtkPlacesSidebar *sidebar,
4665 gboolean show_enter_location)
4666{
4667 g_return_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar));
4668
4669 show_enter_location = !!show_enter_location;
4670 if (sidebar->show_enter_location != show_enter_location)
4671 {
4672 sidebar->show_enter_location = show_enter_location;
4673 update_places (sidebar);
4674 g_object_notify_by_pspec (G_OBJECT (sidebar), pspec: properties[PROP_SHOW_ENTER_LOCATION]);
4675 }
4676}
4677
4678/*
4679 * gtk_places_sidebar_get_show_enter_location:
4680 * @sidebar: a places sidebar
4681 *
4682 * Returns the value previously set with gtk_places_sidebar_set_show_enter_location()
4683 *
4684 * Returns: %TRUE if the sidebar will display an “Enter Location” item.
4685 */
4686gboolean
4687gtk_places_sidebar_get_show_enter_location (GtkPlacesSidebar *sidebar)
4688{
4689 g_return_val_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar), FALSE);
4690
4691 return sidebar->show_enter_location;
4692}
4693
4694/*
4695 * gtk_places_sidebar_set_show_other_locations:
4696 * @sidebar: a places sidebar
4697 * @show_other_locations: whether to show an item for the Other Locations view
4698 *
4699 * Sets whether the @sidebar should show an item for the application to show
4700 * an Other Locations view; this is off by default. When set to %TRUE, persistent
4701 * devices such as hard drives are hidden, otherwise they are shown in the sidebar.
4702 * An application may want to turn this on if it implements a way for the user to
4703 * see and interact with drives and network servers directly.
4704 *
4705 * If you enable this, you should connect to the
4706 * GtkPlacesSidebar::show-other-locations-with-flags signal.
4707 */
4708void
4709gtk_places_sidebar_set_show_other_locations (GtkPlacesSidebar *sidebar,
4710 gboolean show_other_locations)
4711{
4712 g_return_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar));
4713
4714 show_other_locations = !!show_other_locations;
4715 if (sidebar->show_other_locations != show_other_locations)
4716 {
4717 sidebar->show_other_locations = show_other_locations;
4718 update_places (sidebar);
4719 g_object_notify_by_pspec (G_OBJECT (sidebar), pspec: properties[PROP_SHOW_OTHER_LOCATIONS]);
4720 }
4721 }
4722
4723/*
4724 * gtk_places_sidebar_get_show_other_locations:
4725 * @sidebar: a places sidebar
4726 *
4727 * Returns the value previously set with gtk_places_sidebar_set_show_other_locations()
4728 *
4729 * Returns: %TRUE if the sidebar will display an “Other Locations” item.
4730 */
4731gboolean
4732gtk_places_sidebar_get_show_other_locations (GtkPlacesSidebar *sidebar)
4733{
4734 g_return_val_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar), FALSE);
4735
4736 return sidebar->show_other_locations;
4737}
4738
4739/*
4740 * gtk_places_sidebar_set_show_trash:
4741 * @sidebar: a places sidebar
4742 * @show_trash: whether to show an item for the Trash location
4743 *
4744 * Sets whether the @sidebar should show an item for the Trash location.
4745 */
4746void
4747gtk_places_sidebar_set_show_trash (GtkPlacesSidebar *sidebar,
4748 gboolean show_trash)
4749{
4750 g_return_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar));
4751
4752 show_trash = !!show_trash;
4753 if (sidebar->show_trash != show_trash)
4754 {
4755 sidebar->show_trash = show_trash;
4756 update_places (sidebar);
4757 g_object_notify_by_pspec (G_OBJECT (sidebar), pspec: properties[PROP_SHOW_TRASH]);
4758 }
4759}
4760
4761/*
4762 * gtk_places_sidebar_get_show_trash:
4763 * @sidebar: a places sidebar
4764 *
4765 * Returns the value previously set with gtk_places_sidebar_set_show_trash()
4766 *
4767 * Returns: %TRUE if the sidebar will display a “Trash” item.
4768 */
4769gboolean
4770gtk_places_sidebar_get_show_trash (GtkPlacesSidebar *sidebar)
4771{
4772 g_return_val_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar), TRUE);
4773
4774 return sidebar->show_trash;
4775}
4776
4777/*
4778 * gtk_places_sidebar_add_shortcut:
4779 * @sidebar: a places sidebar
4780 * @location: location to add as an application-specific shortcut
4781 *
4782 * Applications may want to present some folders in the places sidebar if
4783 * they could be immediately useful to users. For example, a drawing
4784 * program could add a “/usr/share/clipart” location when the sidebar is
4785 * being used in an “Insert Clipart” dialog box.
4786 *
4787 * This function adds the specified @location to a special place for immutable
4788 * shortcuts. The shortcuts are application-specific; they are not shared
4789 * across applications, and they are not persistent. If this function
4790 * is called multiple times with different locations, then they are added
4791 * to the sidebar’s list in the same order as the function is called.
4792 */
4793void
4794gtk_places_sidebar_add_shortcut (GtkPlacesSidebar *sidebar,
4795 GFile *location)
4796{
4797 g_return_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar));
4798 g_return_if_fail (G_IS_FILE (location));
4799
4800 g_list_store_append (store: sidebar->shortcuts, item: location);
4801
4802 update_places (sidebar);
4803}
4804
4805/*
4806 * gtk_places_sidebar_remove_shortcut:
4807 * @sidebar: a places sidebar
4808 * @location: location to remove
4809 *
4810 * Removes an application-specific shortcut that has been previously been
4811 * inserted with gtk_places_sidebar_add_shortcut(). If the @location is not a
4812 * shortcut in the sidebar, then nothing is done.
4813 */
4814void
4815gtk_places_sidebar_remove_shortcut (GtkPlacesSidebar *sidebar,
4816 GFile *location)
4817{
4818 guint i, n;
4819
4820 g_return_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar));
4821 g_return_if_fail (G_IS_FILE (location));
4822
4823 n = g_list_model_get_n_items (list: G_LIST_MODEL (ptr: sidebar->shortcuts));
4824 for (i = 0; i < n; i++)
4825 {
4826 GFile *shortcut = g_list_model_get_item (list: G_LIST_MODEL (ptr: sidebar->shortcuts), position: i);
4827
4828 if (shortcut == location)
4829 {
4830 g_list_store_remove (store: sidebar->shortcuts, position: i);
4831 g_object_unref (object: shortcut);
4832 update_places (sidebar);
4833 return;
4834 }
4835
4836 g_object_unref (object: shortcut);
4837 }
4838}
4839
4840/*
4841 * gtk_places_sidebar_list_shortcuts:
4842 * @sidebar: a places sidebar
4843 *
4844 * Gets the list of shortcuts, as a list model containing GFile objects.
4845 *
4846 * You should not modify the returned list model. Future changes to
4847 * @sidebar may or may not affect the returned model.
4848 *
4849 * Returns: (transfer full): a list model of GFiles that have been added as
4850 * application-specific shortcuts with gtk_places_sidebar_add_shortcut()
4851 */
4852GListModel *
4853gtk_places_sidebar_get_shortcuts (GtkPlacesSidebar *sidebar)
4854{
4855 g_return_val_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar), NULL);
4856
4857 return G_LIST_MODEL (g_object_ref (sidebar->shortcuts));
4858}
4859
4860/*
4861 * gtk_places_sidebar_get_nth_bookmark:
4862 * @sidebar: a places sidebar
4863 * @n: index of the bookmark to query
4864 *
4865 * This function queries the bookmarks added by the user to the places sidebar,
4866 * and returns one of them. This function is used by GtkFileChooser to implement
4867 * the “Alt-1”, “Alt-2”, etc. shortcuts, which activate the corresponding bookmark.
4868 *
4869 * Returns: (nullable) (transfer full): The bookmark specified by the index @n, or
4870 * %NULL if no such index exist. Note that the indices start at 0, even though
4871 * the file chooser starts them with the keyboard shortcut "Alt-1".
4872 */
4873GFile *
4874gtk_places_sidebar_get_nth_bookmark (GtkPlacesSidebar *sidebar,
4875 int n)
4876{
4877 GtkWidget *row;
4878 int k;
4879 GFile *file;
4880
4881 g_return_val_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar), NULL);
4882
4883 file = NULL;
4884 k = 0;
4885 for (row = gtk_widget_get_first_child (GTK_WIDGET (sidebar->list_box));
4886 row != NULL;
4887 row = gtk_widget_get_next_sibling (widget: row))
4888 {
4889 GtkPlacesPlaceType place_type;
4890 char *uri;
4891
4892 if (!GTK_IS_LIST_BOX_ROW (row))
4893 continue;
4894
4895 g_object_get (object: row,
4896 first_property_name: "place-type", &place_type,
4897 "uri", &uri,
4898 NULL);
4899 if (place_type == GTK_PLACES_BOOKMARK)
4900 {
4901 if (k == n)
4902 {
4903 file = g_file_new_for_uri (uri);
4904 g_free (mem: uri);
4905 break;
4906 }
4907 k++;
4908 }
4909 g_free (mem: uri);
4910 }
4911
4912 return file;
4913}
4914
4915/*
4916 * gtk_places_sidebar_set_drop_targets_visible:
4917 * @sidebar: a places sidebar.
4918 * @visible: whether to show the valid targets or not.
4919 *
4920 * Make the GtkPlacesSidebar show drop targets, so it can show the available
4921 * drop targets and a "new bookmark" row. This improves the Drag-and-Drop
4922 * experience of the user and allows applications to show all available
4923 * drop targets at once.
4924 *
4925 * This needs to be called when the application is aware of an ongoing drag
4926 * that might target the sidebar. The drop-targets-visible state will be unset
4927 * automatically if the drag finishes in the GtkPlacesSidebar. You only need
4928 * to unset the state when the drag ends on some other widget on your application.
4929 */
4930void
4931gtk_places_sidebar_set_drop_targets_visible (GtkPlacesSidebar *sidebar,
4932 gboolean visible)
4933{
4934 g_return_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar));
4935
4936 if (visible)
4937 {
4938 sidebar->drop_state = DROP_STATE_NEW_BOOKMARK_ARMED_PERMANENT;
4939 start_drop_feedback (sidebar, NULL);
4940 }
4941 else
4942 {
4943 if (sidebar->drop_state == DROP_STATE_NEW_BOOKMARK_ARMED_PERMANENT ||
4944 sidebar->drop_state == DROP_STATE_NEW_BOOKMARK_ARMED)
4945 {
4946 if (!sidebar->dragging_over)
4947 {
4948 sidebar->drop_state = DROP_STATE_NORMAL;
4949 stop_drop_feedback (sidebar);
4950 }
4951 else
4952 {
4953 /* In case this is called while we are dragging we need to mark the
4954 * drop state as no permanent so the leave timeout can do its job.
4955 * This will only happen in applications that call this in a wrong
4956 * time */
4957 sidebar->drop_state = DROP_STATE_NEW_BOOKMARK_ARMED;
4958 }
4959 }
4960 }
4961}
4962
4963/*
4964 * gtk_places_sidebar_set_show_starred_location:
4965 * @sidebar: a places sidebar
4966 * @show_starred_location: whether to show an item for Starred files
4967 *
4968 * If you enable this, you should connect to the
4969 * GtkPlacesSidebar::show-starred-location signal.
4970 */
4971void
4972gtk_places_sidebar_set_show_starred_location (GtkPlacesSidebar *sidebar,
4973 gboolean show_starred_location)
4974{
4975 g_return_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar));
4976
4977 show_starred_location = !!show_starred_location;
4978 if (sidebar->show_starred_location != show_starred_location)
4979 {
4980 sidebar->show_starred_location = show_starred_location;
4981 update_places (sidebar);
4982 g_object_notify_by_pspec (G_OBJECT (sidebar), pspec: properties[PROP_SHOW_STARRED_LOCATION]);
4983 }
4984}
4985
4986/*
4987 * gtk_places_sidebar_get_show_starred_location:
4988 * @sidebar: a places sidebar
4989 *
4990 * Returns the value previously set with gtk_places_sidebar_set_show_starred_location()
4991 *
4992 * Returns: %TRUE if the sidebar will display a Starred item.
4993 */
4994gboolean
4995gtk_places_sidebar_get_show_starred_location (GtkPlacesSidebar *sidebar)
4996{
4997 g_return_val_if_fail (GTK_IS_PLACES_SIDEBAR (sidebar), FALSE);
4998
4999 return sidebar->show_starred_location;
5000}
5001

source code of gtk/gtk/gtkplacessidebar.c