[evince/891-provide-context-menu-for-annotations-in-sidebar] sidebar-annotations: Add context menu for sidebar annotations



commit a817c8c6ea28a9afe315bcc1d2ace1583134e222
Author: Nelson Benítez León <nbenitezl+gnome gmail com>
Date:   Fri Apr 27 23:27:13 2018 +0500

    sidebar-annotations: Add context menu for sidebar annotations
    
    Implement new contextual menu for annotations in sidebar, we
    reuse annotation actions used by EvWindow in its context
    menu, so same actions will be shown in both places.
    
    We also implement the GtkWidget "popup" signal so the context
    menu can also be triggered by keyboard (usually Shift-F10 or
    specific context menu key in some keyboards).
    
    https://bugzilla.gnome.org/show_bug.cgi?id=795631

 shell/ev-sidebar-annotations.c | 222 +++++++++++++++++++++++++++++++++++++----
 shell/ev-window.c              |   7 ++
 shell/ev-window.h              |   2 +
 shell/evince-menus.ui          |  25 +++++
 4 files changed, 236 insertions(+), 20 deletions(-)
---
diff --git a/shell/ev-sidebar-annotations.c b/shell/ev-sidebar-annotations.c
index 2815f827..0ba13385 100644
--- a/shell/ev-sidebar-annotations.c
+++ b/shell/ev-sidebar-annotations.c
@@ -28,6 +28,8 @@
 #include "ev-jobs.h"
 #include "ev-job-scheduler.h"
 #include "ev-stock-icons.h"
+#include "ev-window.h"
+#include "ev-utils.h"
 
 enum {
        PROP_0,
@@ -52,13 +54,21 @@ struct _EvSidebarAnnotationsPrivate {
         GtkWidget   *swindow;
        GtkWidget   *tree_view;
 
+       GMenuModel  *popup_model;
+       GtkWidget   *popup;
+
        EvJob       *job;
        guint        selection_changed_id;
 };
 
 static void ev_sidebar_annotations_page_iface_init (EvSidebarPageInterface *iface);
 static void ev_sidebar_annotations_load            (EvSidebarAnnotations   *sidebar_annots);
-
+static gboolean ev_sidebar_annotations_popup_menu (GtkWidget *widget);
+static gboolean ev_sidebar_annotations_popup_menu_show (EvSidebarAnnotations *sidebar_annots,
+                                                       GdkWindow            *rect_window,
+                                                       const GdkRectangle   *rect,
+                                                       EvMapping            *annot_mapping,
+                                                       const GdkEvent       *event);
 static guint signals[N_SIGNALS];
 
 G_DEFINE_TYPE_EXTENDED (EvSidebarAnnotations,
@@ -82,6 +92,7 @@ ev_sidebar_annotations_dispose (GObject *object)
                priv->document = NULL;
        }
 
+       g_clear_object (&priv->popup_model);
        G_OBJECT_CLASS (ev_sidebar_annotations_parent_class)->dispose (object);
 }
 
@@ -116,6 +127,7 @@ ev_sidebar_annotations_init (EvSidebarAnnotations *ev_annots)
        GtkCellRenderer   *renderer;
        GtkTreeViewColumn *column;
        GtkTreeSelection  *selection;
+       GtkBuilder        *builder;
 
         ev_annots->priv = EV_SIDEBAR_ANNOTATIONS_GET_PRIVATE (ev_annots);
 
@@ -150,6 +162,10 @@ ev_sidebar_annotations_init (EvSidebarAnnotations *ev_annots)
        gtk_container_add (GTK_CONTAINER (ev_annots->priv->swindow), ev_annots->priv->tree_view);
        gtk_widget_show (ev_annots->priv->tree_view);
 
+       /* Annotation pop-up */
+       builder = gtk_builder_new_from_resource ("/org/gnome/evince/gtk/menus.ui");
+       ev_annots->priv->popup_model = g_object_ref (G_MENU_MODEL (gtk_builder_get_object (builder, 
"annotation-popup")));
+       g_object_unref (builder);
         gtk_box_pack_start (GTK_BOX (ev_annots), ev_annots->priv->swindow, TRUE, TRUE, 0);
        gtk_widget_show (ev_annots->priv->swindow);
 }
@@ -178,9 +194,11 @@ static void
 ev_sidebar_annotations_class_init (EvSidebarAnnotationsClass *klass)
 {
        GObjectClass *g_object_class = G_OBJECT_CLASS (klass);
+       GtkWidgetClass *gtk_widget_class = GTK_WIDGET_CLASS (klass);
 
        g_object_class->get_property = ev_sidebar_annotations_get_property;
        g_object_class->dispose = ev_sidebar_annotations_dispose;
+       gtk_widget_class->popup_menu = ev_sidebar_annotations_popup_menu;
 
        g_type_class_add_private (g_object_class, sizeof (EvSidebarAnnotationsPrivate));
 
@@ -218,17 +236,41 @@ ev_sidebar_annotations_annot_removed (EvSidebarAnnotations *sidebar_annots)
        ev_sidebar_annotations_load (sidebar_annots);
 }
 
+/**
+ * iter_has_mapping:
+ * @model: a #GtkTreeModel
+ * @iter: a #GtkTreeIter contained in @model
+ * @mapping_out: (out) (allow-none): if non-%NULL, will be
+ *               filled with the found mapping (#EvMapping)
+ *
+ * Checks whether @iter contains a #EvMapping, optionally
+ * placing it in @mapping_out.
+ */
+static gboolean
+iter_has_mapping (GtkTreeModel  *model,
+                 GtkTreeIter   *iter,
+                 EvMapping     **mapping_out)
+{
+       EvMapping *mapping = NULL;
+
+       gtk_tree_model_get (model, iter,
+                           COLUMN_ANNOT_MAPPING, &mapping,
+                           -1);
+
+       if (mapping_out && mapping)
+               *mapping_out = mapping;
+
+       return mapping != NULL;
+}
+
 static void
 ev_sidebar_annotations_activate_result_at_iter (EvSidebarAnnotations *sidebar_annots,
                                                 GtkTreeModel  *model,
                                                 GtkTreeIter   *iter)
 {
-               EvMapping *mapping = NULL;
+               EvMapping *mapping;
 
-               gtk_tree_model_get (model, iter,
-                                   COLUMN_ANNOT_MAPPING, &mapping,
-                                   -1);
-               if (mapping)
+               if (iter_has_mapping (model, iter, &mapping))
                        g_signal_emit (sidebar_annots, signals[ANNOT_ACTIVATED], 0, mapping);
 }
 
@@ -243,6 +285,43 @@ selection_changed_cb (GtkTreeSelection     *selection,
            ev_sidebar_annotations_activate_result_at_iter (sidebar_annots, model, &iter);
 }
 
+static gboolean
+sidebar_tree_button_release_cb (GtkTreeView    *view,
+                               GdkEventButton *event,
+                               EvSidebarAnnotations  *sidebar_annots)
+{
+       GtkTreeModel         *model;
+       GtkTreePath          *path;
+       GtkTreeIter           iter;
+       GtkTreeSelection     *selection;
+       GdkRectangle          rect;
+       EvMapping            *annot_mapping;
+
+       gtk_tree_view_get_path_at_pos (view, event->x, event->y, &path,
+                                       NULL, NULL, NULL);
+        if (!path)
+                return GDK_EVENT_PROPAGATE;
+
+       model = gtk_tree_view_get_model (view);
+       gtk_tree_model_get_iter (model, &iter, path);
+       gtk_tree_path_free (path);
+       selection = gtk_tree_view_get_selection (view);
+
+       if (event->button == GDK_BUTTON_SECONDARY && event->type == GDK_BUTTON_RELEASE &&
+           iter_has_mapping (model, &iter, &annot_mapping) &&
+           gtk_tree_selection_iter_is_selected (selection, &iter)) {
+               rect.x = event->x;
+               rect.y = event->y;
+               rect.width = rect.height = 20;
+               ev_sidebar_annotations_popup_menu_show (sidebar_annots,
+                                                       gtk_tree_view_get_bin_window (view),
+                                                       &rect, annot_mapping, (GdkEvent *) event);
+               return GDK_EVENT_STOP;
+       }
+
+       return GDK_EVENT_PROPAGATE;
+}
+
 static gboolean
 sidebar_tree_button_press_cb (GtkTreeView    *view,
                               GdkEventButton *event,
@@ -252,28 +331,41 @@ sidebar_tree_button_press_cb (GtkTreeView    *view,
         GtkTreePath          *path;
         GtkTreeIter           iter;
         GtkTreeSelection     *selection;
+        EvMapping            *annot_mapping;
+        GdkRectangle          rect;
 
         gtk_tree_view_get_path_at_pos (view, event->x, event->y, &path,
                                        NULL, NULL, NULL);
         if (!path)
-                return FALSE;
-        
+                return GDK_EVENT_PROPAGATE;
+
+       model = gtk_tree_view_get_model (view);
+       gtk_tree_model_get_iter (model, &iter, path);
+       gtk_tree_path_free (path);
         selection = gtk_tree_view_get_selection (view);
-        if (!gtk_tree_selection_path_is_selected (selection, path)) {
-                gtk_tree_path_free (path);
-                return FALSE;
-        }
 
-        model = gtk_tree_view_get_model (view);
-        gtk_tree_model_get_iter (model, &iter, path);
-        gtk_tree_path_free (path);
+       if (gdk_event_triggers_context_menu ((const GdkEvent *) event) &&
+           iter_has_mapping (model, &iter, &annot_mapping)) {
 
-        ev_sidebar_annotations_activate_result_at_iter (sidebar_annots, model, &iter);
+               if (!EV_IS_ANNOTATION (annot_mapping->data))
+                       return GDK_EVENT_PROPAGATE;
+
+               rect.x = event->x;
+               rect.y = event->y;
+               rect.width = rect.height = 20;
+               gtk_tree_selection_select_iter (selection, &iter);
+
+               return GDK_EVENT_STOP;
+       } else {
+               if (!gtk_tree_selection_iter_is_selected (selection, &iter))
+                       gtk_tree_selection_select_iter (selection, &iter);
+               else
+                       /* This will reveal annotation again in case was scrolled out of EvView */
+                       ev_sidebar_annotations_activate_result_at_iter (sidebar_annots, model, &iter);
+       }
 
-        /* Always return FALSE so the tree view gets the event and can update
-         * the selection etc.
-         */
-        return FALSE;
+        /* Propagate so the tree view gets the event and can update the selection etc. */
+        return GDK_EVENT_PROPAGATE;
 }
 
 static void
@@ -317,6 +409,9 @@ job_finished_callback (EvJobAnnots          *job,
     g_signal_connect (priv->tree_view, "button-press-event",
                       G_CALLBACK (sidebar_tree_button_press_cb),
                       sidebar_annots);
+    g_signal_connect (priv->tree_view, "button-release-event",
+                      G_CALLBACK (sidebar_tree_button_release_cb),
+                      sidebar_annots);
 
 
        model = gtk_tree_store_new (N_COLUMNS,
@@ -488,6 +583,93 @@ ev_sidebar_annotations_document_changed_cb (EvDocumentModel      *model,
        ev_sidebar_annotations_load (sidebar_annots);
 }
 
+static void
+ev_sidebar_annotations_popup_detach_cb (GtkWidget *widget,
+                                       GtkMenu   *menu)
+{
+       EvSidebarAnnotations *sidebar_annots = EV_SIDEBAR_ANNOTATIONS (widget);
+       sidebar_annots->priv->popup = NULL;
+}
+
+/* event parameter can be NULL, that means popup was triggered from keyboard */
+static gboolean
+ev_sidebar_annotations_popup_menu_show (EvSidebarAnnotations *sidebar_annots,
+                                       GdkWindow            *rect_window,
+                                       const GdkRectangle   *rect,
+                                       EvMapping            *annot_mapping,
+                                       const GdkEvent       *event)
+{
+       GtkWidget *window;
+
+       EvSidebarAnnotationsPrivate *priv = sidebar_annots->priv;
+
+       if (!EV_IS_ANNOTATION (annot_mapping->data))
+               return FALSE;
+
+       if (!priv->popup) {
+               priv->popup = gtk_menu_new_from_model (priv->popup_model);
+               gtk_menu_attach_to_widget (GTK_MENU (priv->popup), GTK_WIDGET (sidebar_annots),
+                                          ev_sidebar_annotations_popup_detach_cb);
+       }
+
+       window = gtk_widget_get_toplevel (GTK_WIDGET (sidebar_annots));
+
+       ev_window_handle_annot_popup (EV_WINDOW (window), EV_ANNOTATION (annot_mapping->data));
+
+#if GTK_CHECK_VERSION (3, 22, 0)
+       gtk_menu_popup_at_rect (GTK_MENU (priv->popup), rect_window, rect,
+                               GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_NORTH_WEST, event);
+#else
+       if (event != NULL)
+               gtk_menu_popup_for_device (GTK_MENU (priv->popup),
+                                          gdk_event_get_device (event),
+                                          NULL, NULL, NULL, NULL, NULL, 0,
+                                          gdk_event_get_time (event));
+       else
+               /* popup was triggered from keyboard */
+               gtk_menu_popup (GTK_MENU (priv->popup), NULL, NULL,
+                               ev_gui_menu_position_tree_selection,
+                               priv->tree_view, 0,
+                               gtk_get_current_event_time ());
+#endif
+
+       return TRUE;
+}
+
+static gboolean
+ev_sidebar_annotations_popup_menu (GtkWidget *widget)
+{
+       GtkTreeView          *tree_view;
+       GtkTreeModel         *model;
+       GtkTreePath          *path;
+       GtkTreeSelection     *selection;
+       EvMapping            *annot_mapping;
+       GtkTreeIter           iter;
+       GdkRectangle          rect;
+
+       EvSidebarAnnotations *sidebar_annots = EV_SIDEBAR_ANNOTATIONS (widget);
+       EvSidebarAnnotationsPrivate *priv = sidebar_annots->priv;
+
+       tree_view = GTK_TREE_VIEW (priv->tree_view);
+       selection = gtk_tree_view_get_selection (tree_view);
+       if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+               return FALSE;
+
+       if (!iter_has_mapping (model, &iter, &annot_mapping))
+               return FALSE;
+
+       path = gtk_tree_model_get_path (model, &iter);
+
+       gtk_tree_view_get_cell_area (tree_view, path,
+                                    gtk_tree_view_get_column (tree_view, 0),
+                                    &rect);
+       gtk_tree_path_free (path);
+
+       return ev_sidebar_annotations_popup_menu_show (sidebar_annots,
+                                                      gtk_tree_view_get_bin_window (tree_view),
+                                                      &rect, annot_mapping, NULL);
+}
+
 /* EvSidebarPageIface */
 static void
 ev_sidebar_annotations_set_model (EvSidebarPage   *sidebar_page,
diff --git a/shell/ev-window.c b/shell/ev-window.c
index 36a6d031..4de2a0fa 100644
--- a/shell/ev-window.c
+++ b/shell/ev-window.c
@@ -5391,6 +5391,13 @@ search_bar_search_mode_enabled_changed (GtkSearchBar *search_bar,
        }
 }
 
+void
+ev_window_handle_annot_popup (EvWindow     *ev_window,
+                             EvAnnotation *annot)
+{
+       view_menu_annot_popup (ev_window, annot);
+}
+
 static void
 ev_window_show_find_bar (EvWindow *ev_window,
                         gboolean  restart)
diff --git a/shell/ev-window.h b/shell/ev-window.h
index 28d031a8..dd5afff6 100644
--- a/shell/ev-window.h
+++ b/shell/ev-window.h
@@ -95,6 +95,8 @@ EvDocumentModel *ev_window_get_document_model            (EvWindow       *ev_win
 void            ev_window_focus_view                     (EvWindow       *ev_window);
 GtkWidget      *ev_window_get_toolbar                   (EvWindow       *ev_window);
 void            ev_window_file_open_dialog               (EvWindow       *ev_window);
+void            ev_window_handle_annot_popup             (EvWindow       *ev_window,
+                                                          EvAnnotation   *annot);
 
 G_END_DECLS
 
diff --git a/shell/evince-menus.ui b/shell/evince-menus.ui
index 73a8df18..2d74b1a4 100644
--- a/shell/evince-menus.ui
+++ b/shell/evince-menus.ui
@@ -326,4 +326,29 @@
       </item>
     </section>
   </menu>
+
+  <menu id="annotation-popup">
+    <section>
+      <item>
+        <attribute name="label" translatable="yes">Annotation Properties…</attribute>
+        <attribute name="action">win.annot-properties</attribute>
+        <attribute name='hidden-when'>action-disabled</attribute>
+      </item>
+      <item>
+        <attribute name="label" translatable="yes">Remove Annotation</attribute>
+        <attribute name="action">win.remove-annot</attribute>
+        <attribute name='hidden-when'>action-disabled</attribute>
+      </item>
+      <item>
+        <attribute name="label" translatable="yes">_Open Attachment</attribute>
+        <attribute name="action">win.open-attachment</attribute>
+        <attribute name='hidden-when'>action-disabled</attribute>
+      </item>
+      <item>
+        <attribute name="label" translatable="yes">_Save Attachment As…</attribute>
+        <attribute name="action">win.save-attachment</attribute>
+        <attribute name='hidden-when'>action-disabled</attribute>
+      </item>
+    </section>
+  </menu>
 </interface>


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]