/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ /* * Caja: Bookmarks sidebar * * Copyright (C) 2020 Gordon N. Squash. * Copyright (C) 2012-2021 The MATE developers * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * Authors: Gordon N. Squash * * Based largely on the Caja Places sidebar and the Caja History sidebar. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "caja-bookmark-list.h" #include "caja-window.h" #include "caja-bookmarks-sidebar.h" typedef struct { GtkScrolledWindowClass parent; } CajaBookmarksSidebarClass; typedef struct { GObject parent; } CajaBookmarksSidebarProvider; typedef struct { GObjectClass parent; } CajaBookmarksSidebarProviderClass; enum { BOOKMARKS_SIDEBAR_COLUMN_ICON, BOOKMARKS_SIDEBAR_COLUMN_NAME, BOOKMARKS_SIDEBAR_COLUMN_STYLE, BOOKMARKS_SIDEBAR_COLUMN_ICON_VISIBLE, BOOKMARKS_SIDEBAR_COLUMN_BOOKMARK, BOOKMARKS_SIDEBAR_COLUMN_COUNT }; static void caja_bookmarks_sidebar_iface_init (CajaSidebarIface *iface); static void sidebar_provider_iface_init (CajaSidebarProviderIface *iface); static GType caja_bookmarks_sidebar_provider_get_type (void); static void caja_bookmarks_sidebar_style_updated (GtkWidget *widget); G_DEFINE_TYPE_WITH_CODE (CajaBookmarksSidebar, caja_bookmarks_sidebar, GTK_TYPE_SCROLLED_WINDOW, G_IMPLEMENT_INTERFACE (CAJA_TYPE_SIDEBAR, caja_bookmarks_sidebar_iface_init)); G_DEFINE_TYPE_WITH_CODE (CajaBookmarksSidebarProvider, caja_bookmarks_sidebar_provider, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (CAJA_TYPE_SIDEBAR_PROVIDER, sidebar_provider_iface_init)); static gboolean is_built_in_bookmark (CajaFile *file) { gboolean built_in; gint idx; built_in = FALSE; for (idx = 0; idx < G_USER_N_DIRECTORIES; idx++) { /* PUBLIC_SHARE and TEMPLATES are not in our built-in list */ if (caja_file_is_user_special_directory (file, idx)) { if (idx != G_USER_DIRECTORY_PUBLIC_SHARE && idx != G_USER_DIRECTORY_TEMPLATES) { built_in = TRUE; } break; } } return built_in; } static void update_bookmarks (CajaBookmarksSidebar *sidebar) { GtkListStore *store; GtkTreeSelection *selection; GtkTreeIter iter; CajaBookmark *bookmark = NULL; int bookmark_count = 0; int index = 0; GFile *root; CajaFile *file; char *bookmark_uri; cairo_surface_t *surface = NULL; char *name; gboolean bookmarks_list_empty = TRUE; store = GTK_LIST_STORE (gtk_tree_view_get_model (sidebar->tree_view)); gtk_list_store_clear (store); selection = GTK_TREE_SELECTION (gtk_tree_view_get_selection (sidebar->tree_view)); bookmark_count = caja_bookmark_list_length (sidebar->bookmarks); for (index = 0; index < bookmark_count; index++) { bookmark = caja_bookmark_list_item_at (sidebar->bookmarks, index); if (caja_bookmark_uri_known_not_to_exist (bookmark)) { continue; } bookmark_uri = caja_bookmark_get_uri (bookmark); root = caja_bookmark_get_location (bookmark); file = caja_file_get (root); if (is_built_in_bookmark (file)) { g_object_unref (root); caja_file_unref (file); continue; } bookmarks_list_empty = FALSE; surface = caja_bookmark_get_surface (bookmark, GTK_ICON_SIZE_MENU); name = caja_bookmark_get_name (bookmark); gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, BOOKMARKS_SIDEBAR_COLUMN_ICON, surface, BOOKMARKS_SIDEBAR_COLUMN_NAME, name, BOOKMARKS_SIDEBAR_COLUMN_STYLE, PANGO_STYLE_NORMAL, BOOKMARKS_SIDEBAR_COLUMN_ICON_VISIBLE, TRUE, BOOKMARKS_SIDEBAR_COLUMN_BOOKMARK, bookmark, -1); /* Select the bookmark if we're in the directory the bookmark points to. */ if (g_strcmp0 (bookmark_uri, sidebar->current_uri) == 0) gtk_tree_selection_select_iter (selection, &iter); g_object_unref (root); caja_file_unref (file); g_free (bookmark_uri); if (surface != NULL) { cairo_surface_destroy (surface); } g_free (name); } /* If the user has no user-defined bookmarks, then add one row to the list * which says that no bookmarks have been defined, rather than leaving the * user scratching their head wondering why the list is blank. */ if (bookmarks_list_empty) { gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, BOOKMARKS_SIDEBAR_COLUMN_ICON, NULL, BOOKMARKS_SIDEBAR_COLUMN_NAME, _("No bookmarks defined"), BOOKMARKS_SIDEBAR_COLUMN_STYLE, PANGO_STYLE_ITALIC, BOOKMARKS_SIDEBAR_COLUMN_ICON_VISIBLE, FALSE, BOOKMARKS_SIDEBAR_COLUMN_BOOKMARK, NULL, -1); gtk_tree_selection_select_iter (selection, &iter); } } static void open_selected_item (CajaBookmarksSidebar *sidebar, GtkTreePath *path, CajaWindowOpenFlags flags) { CajaWindowSlotInfo *slot; GtkTreeModel *model; GtkTreeIter iter; CajaBookmark *bookmark; GFile *location; model = gtk_tree_view_get_model (sidebar->tree_view); if (!gtk_tree_model_get_iter (model, &iter, path)) return; gtk_tree_model_get (model, &iter, BOOKMARKS_SIDEBAR_COLUMN_BOOKMARK, &bookmark, -1); if (bookmark == NULL) return; /* Navigate to the clicked location. */ location = caja_bookmark_get_location (CAJA_BOOKMARK (bookmark)); slot = caja_window_info_get_active_slot (sidebar->window); caja_window_slot_info_open_location (slot, location, CAJA_WINDOW_OPEN_ACCORDING_TO_MODE, flags, NULL); g_object_unref (location); } static void row_activated_callback (GtkTreeView *tree_view, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data) { CajaBookmarksSidebar *sidebar; sidebar = CAJA_BOOKMARKS_SIDEBAR (user_data); g_assert (sidebar->tree_view == tree_view); open_selected_item (sidebar, path, 0); } static gboolean button_press_event_callback (GtkWidget *widget, GdkEventButton *event, gpointer user_data) { CajaBookmarksSidebar *sidebar; GtkTreePath *path; int open_flags = 0; if (event->type == GDK_BUTTON_PRESS) { /** * If the middle button was pressed, open the bookmark in a new tab. */ if (event->button == GDK_BUTTON_MIDDLE) open_flags = CAJA_WINDOW_OPEN_FLAG_NEW_TAB; /** * If the middle button was not pressed, don't do * anything, since the action will be handled in * row_activated_callback() (above). */ else return FALSE; } sidebar = CAJA_BOOKMARKS_SIDEBAR (user_data); g_assert (sidebar->tree_view == GTK_TREE_VIEW (widget)); if (gtk_tree_view_get_path_at_pos (sidebar->tree_view, event->x, event->y, &path, NULL, NULL, NULL)) { open_selected_item (sidebar, path, open_flags); gtk_tree_path_free (path); } /* * If the middle button was pressed, don't let the event bubble up to the * row_activated_callback() (above), because we do not want the row to * be selected: The location of the bookmark was opened in a new tab, not * the current tab. */ return TRUE; } static void loading_uri_callback (CajaWindowInfo *window, char *location, CajaBookmarksSidebar *sidebar) { GtkTreeModel *model; GtkTreeIter iter; gboolean valid; CajaBookmark *bookmark; char *uri; if (strcmp (sidebar->current_uri, location) != 0) { GtkTreeSelection *selection; g_free (sidebar->current_uri); sidebar->current_uri = g_strdup (location); /* set selection if any place matches location */ selection = gtk_tree_view_get_selection (sidebar->tree_view); gtk_tree_selection_unselect_all (selection); model = gtk_tree_view_get_model (sidebar->tree_view); valid = gtk_tree_model_get_iter_first (model, &iter); while (valid) { gtk_tree_model_get (model, &iter, BOOKMARKS_SIDEBAR_COLUMN_BOOKMARK, &bookmark, -1); uri = caja_bookmark_get_uri (bookmark); if (uri != NULL) { if (strcmp (uri, location) == 0) { g_free (uri); gtk_tree_selection_select_iter (selection, &iter); break; } g_free (uri); } valid = gtk_tree_model_iter_next (model, &iter); } } } /* * If no bookmarks have been defined by the user, then we create one row * in the bookmark list sidebar which says "No bookmarks defined". We * do not want the user to be able to select the row, however, so if the * bookmark is NULL, then refuse selection; if the bookmark is not NULL, * allow selection. */ static gboolean is_row_selectable (GtkTreeSelection *selection, GtkTreeModel *model, GtkTreePath *path, gboolean path_currently_selected, gpointer data) { GtkTreeIter iter; CajaBookmark *bookmark; if (!gtk_tree_model_get_iter (model, &iter, path)) return FALSE; gtk_tree_model_get (model, &iter, BOOKMARKS_SIDEBAR_COLUMN_BOOKMARK, &bookmark, -1); if (bookmark == NULL) return FALSE; else return TRUE; } static void caja_bookmarks_sidebar_init (CajaBookmarksSidebar *sidebar) { GtkTreeView *tree_view; GtkTreeViewColumn *col; GtkCellRenderer *cell; GtkListStore *store; GtkTreeSelection *selection; tree_view = GTK_TREE_VIEW (gtk_tree_view_new ()); gtk_tree_view_set_headers_visible (tree_view, FALSE); gtk_widget_show (GTK_WIDGET (tree_view)); col = GTK_TREE_VIEW_COLUMN (gtk_tree_view_column_new ()); cell = gtk_cell_renderer_pixbuf_new (); gtk_tree_view_column_pack_start (col, cell, FALSE); gtk_tree_view_column_set_attributes (col, cell, "surface", BOOKMARKS_SIDEBAR_COLUMN_ICON, "visible", BOOKMARKS_SIDEBAR_COLUMN_ICON_VISIBLE, NULL); cell = gtk_cell_renderer_text_new (); gtk_tree_view_column_pack_start (col, cell, TRUE); gtk_tree_view_column_set_attributes (col, cell, "text", BOOKMARKS_SIDEBAR_COLUMN_NAME, "style", BOOKMARKS_SIDEBAR_COLUMN_STYLE, NULL); gtk_tree_view_column_set_fixed_width (col, CAJA_ICON_SIZE_SMALLER); gtk_tree_view_append_column (tree_view, col); store = gtk_list_store_new (BOOKMARKS_SIDEBAR_COLUMN_COUNT, CAIRO_GOBJECT_TYPE_SURFACE, G_TYPE_STRING, PANGO_TYPE_STYLE, G_TYPE_BOOLEAN, CAJA_TYPE_BOOKMARK); gtk_tree_view_set_model (tree_view, GTK_TREE_MODEL (store)); g_object_unref (store); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sidebar), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_scrolled_window_set_hadjustment (GTK_SCROLLED_WINDOW (sidebar), NULL); gtk_scrolled_window_set_vadjustment (GTK_SCROLLED_WINDOW (sidebar), NULL); gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sidebar), GTK_SHADOW_IN); gtk_scrolled_window_set_overlay_scrolling (GTK_SCROLLED_WINDOW (sidebar), FALSE); gtk_container_add (GTK_CONTAINER (sidebar), GTK_WIDGET (tree_view)); gtk_widget_show (GTK_WIDGET (sidebar)); sidebar->tree_view = tree_view; selection = gtk_tree_view_get_selection (tree_view); gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE); /* Please see the comment associated with the function is_row_selectable() * (above). */ gtk_tree_selection_set_select_function (selection, is_row_selectable, NULL, NULL); g_signal_connect_object (tree_view, "row_activated", G_CALLBACK (row_activated_callback), sidebar, 0); g_signal_connect (tree_view, "button-press-event", G_CALLBACK (button_press_event_callback), sidebar); eel_gtk_tree_view_set_activate_on_single_click (sidebar->tree_view, TRUE); } static void caja_bookmarks_sidebar_finalize (GObject *object) { CajaBookmarksSidebar *sidebar; sidebar = CAJA_BOOKMARKS_SIDEBAR (object); g_free (sidebar->current_uri); G_OBJECT_CLASS (caja_bookmarks_sidebar_parent_class)->finalize (object); } static void caja_bookmarks_sidebar_class_init (CajaBookmarksSidebarClass *class) { G_OBJECT_CLASS (class)->finalize = caja_bookmarks_sidebar_finalize; GTK_WIDGET_CLASS (class)->style_updated = caja_bookmarks_sidebar_style_updated; } static const char * caja_bookmarks_sidebar_get_sidebar_id (CajaSidebar *sidebar) { return CAJA_BOOKMARKS_SIDEBAR_ID; } static char * caja_bookmarks_sidebar_get_tab_label (CajaSidebar *sidebar) { return g_strdup (_("Bookmarks")); } static char * caja_bookmarks_sidebar_get_tab_tooltip (CajaSidebar *sidebar) { return g_strdup (_("Show a list of your personal bookmarks")); } static GdkPixbuf * caja_bookmarks_sidebar_get_tab_icon (CajaSidebar *sidebar) { return NULL; } static void caja_bookmarks_sidebar_is_visible_changed (CajaSidebar *sidebar, gboolean is_visible) { /* Do nothing */ } static void caja_bookmarks_sidebar_iface_init (CajaSidebarIface *iface) { iface->get_sidebar_id = caja_bookmarks_sidebar_get_sidebar_id; iface->get_tab_label = caja_bookmarks_sidebar_get_tab_label; iface->get_tab_tooltip = caja_bookmarks_sidebar_get_tab_tooltip; iface->get_tab_icon = caja_bookmarks_sidebar_get_tab_icon; iface->is_visible_changed = caja_bookmarks_sidebar_is_visible_changed; } static void caja_bookmarks_sidebar_set_parent_window (CajaBookmarksSidebar *sidebar, CajaWindowInfo *window) { CajaWindowSlotInfo *slot; sidebar->window = window; slot = caja_window_info_get_active_slot (window); sidebar->bookmarks = caja_bookmark_list_new (); sidebar->current_uri = caja_window_slot_info_get_current_location (slot); g_signal_connect_object (sidebar->bookmarks, "contents_changed", G_CALLBACK (update_bookmarks), sidebar, G_CONNECT_SWAPPED); g_signal_connect_object (window, "loading_uri", G_CALLBACK (loading_uri_callback), sidebar, 0); update_bookmarks (sidebar); } static void caja_bookmarks_sidebar_style_updated (GtkWidget *widget) { CajaBookmarksSidebar *sidebar; sidebar = CAJA_BOOKMARKS_SIDEBAR (widget); update_bookmarks (sidebar); } static CajaSidebar * caja_bookmarks_sidebar_create (CajaSidebarProvider *provider, CajaWindowInfo *window) { CajaBookmarksSidebar *sidebar; sidebar = g_object_new (caja_bookmarks_sidebar_get_type (), NULL); caja_bookmarks_sidebar_set_parent_window (sidebar, window); g_object_ref_sink (sidebar); return CAJA_SIDEBAR (sidebar); } static void sidebar_provider_iface_init (CajaSidebarProviderIface *iface) { iface->create = caja_bookmarks_sidebar_create; } static void caja_bookmarks_sidebar_provider_init (CajaBookmarksSidebarProvider *sidebar) { } static void caja_bookmarks_sidebar_provider_class_init (CajaBookmarksSidebarProviderClass *class) { } void caja_bookmarks_sidebar_register (void) { caja_module_add_type (caja_bookmarks_sidebar_provider_get_type ()); }