[gtk+/wip/gbsneto/other-locations] placesview: add context menu



commit 7bf55caef66db582369acba7e04e2d5271f7ed80
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date:   Fri Jul 3 00:17:37 2015 -0300

    placesview: add context menu
    
    Add a context menu, allowing the user to:
    
    - open a location normally
    - open in new tab (if possible)
    - open in new window (if possible)
    - mount a volume if unmounted
    - unmount a volume if mounted

 gtk/gtkplacesview.c        |  311 ++++++++++++++++++++++++++++++++++++++++++++
 gtk/gtkplacesviewrow.c     |   12 ++
 gtk/gtkplacesviewrow.h     |    2 +
 gtk/ui/gtkplacesview.ui    |    1 +
 gtk/ui/gtkplacesviewrow.ui |   96 +++++++-------
 5 files changed, 376 insertions(+), 46 deletions(-)
---
diff --git a/gtk/gtkplacesview.c b/gtk/gtkplacesview.c
index 9f54e18..2ab47c2 100644
--- a/gtk/gtkplacesview.c
+++ b/gtk/gtkplacesview.c
@@ -59,6 +59,7 @@ struct _GtkPlacesViewPrivate
   GtkWidget                     *drives_listbox;
   GtkWidget                     *network_grid;
   GtkWidget                     *network_listbox;
+  GtkWidget                     *popup_menu;
   GtkWidget                     *recent_servers_listbox;
 
   guint local_only             : 1;
@@ -67,6 +68,10 @@ struct _GtkPlacesViewPrivate
 static void        mount_volume                                  (GtkPlacesView *view,
                                                                   GVolume       *volume);
 
+static gboolean    on_button_release_event                       (GtkWidget        *widget,
+                                                                  GdkEventButton   *event,
+                                                                  GtkPlacesViewRow *sidebar);
+
 G_DEFINE_TYPE_WITH_PRIVATE (GtkPlacesView, gtk_places_view, GTK_TYPE_BOX)
 
 /* GtkPlacesView properties & signals */
@@ -515,6 +520,11 @@ add_volume (GtkPlacesView *view,
                       "mount", mount,
                       NULL);
 
+      g_signal_connect (gtk_places_view_row_get_event_box (GTK_PLACES_VIEW_ROW (row)),
+                        "button-release-event",
+                        G_CALLBACK (on_button_release_event),
+                        row);
+
       if (is_network)
         gtk_container_add (GTK_CONTAINER (priv->network_listbox), row);
       else
@@ -589,6 +599,11 @@ add_mount (GtkPlacesView *view,
                       "mount", mount,
                       NULL);
 
+      g_signal_connect (gtk_places_view_row_get_event_box (GTK_PLACES_VIEW_ROW (row)),
+                        "button-release-event",
+                        G_CALLBACK (on_button_release_event),
+                        row);
+
       if (is_network)
         gtk_container_add (GTK_CONTAINER (priv->network_listbox), row);
       else
@@ -803,6 +818,33 @@ volume_mount_ready_callback (GObject      *source_volume,
 }
 
 static void
+unmount_ready_callback (GObject      *source_mount,
+                        GAsyncResult *res,
+                        gpointer      user_data)
+{
+  GtkPlacesViewPrivate *priv;
+  GMount *mount;
+  GError *error;
+
+  g_return_if_fail (GTK_IS_PLACES_VIEW (user_data));
+  g_return_if_fail (G_IS_MOUNT (source_mount));
+
+  priv = GTK_PLACES_VIEW (user_data)->priv;
+  mount = G_MOUNT (source_mount);
+  error = NULL;
+
+  g_clear_object (&priv->connection_cancellable);
+
+  g_mount_unmount_with_operation_finish (mount, res, &error);
+
+  if (error)
+    {
+      g_warning ("Unable to unmount mountpoint: %s", error->message);
+      g_clear_error (&error);
+         }
+}
+
+static void
 mount_location (GtkPlacesView *view,
                 GFile         *location)
 {
@@ -860,6 +902,275 @@ mount_volume (GtkPlacesView *view,
   g_object_unref (operation);
 }
 
+/* Callback used when the file list's popup menu is detached */
+static void
+popup_menu_detach_cb (GtkWidget *attach_widget,
+                      GtkMenu   *menu)
+{
+  GtkPlacesViewPrivate *priv;
+
+  g_assert (GTK_IS_PLACES_VIEW (attach_widget));
+
+  priv = GTK_PLACES_VIEW (attach_widget)->priv;
+
+  priv->popup_menu = NULL;
+}
+
+static void
+get_view_and_file (GtkPlacesViewRow  *row,
+                   GtkWidget        **view,
+                   GFile            **file)
+{
+  g_assert (GTK_IS_PLACES_VIEW_ROW (row));
+
+  if (view)
+    *view = gtk_widget_get_ancestor (GTK_WIDGET (row), GTK_TYPE_PLACES_VIEW);
+
+  if (file)
+    {
+      GVolume *volume;
+      GMount *mount;
+
+      volume = gtk_places_view_row_get_volume (row);
+      mount = gtk_places_view_row_get_mount (row);
+
+      if (mount)
+        *file = g_mount_get_root (mount);
+      else if (volume)
+        *file = g_volume_get_activation_root (volume);
+      else
+        *file = NULL;
+    }
+}
+
+static void
+open_cb (GtkMenuItem      *item,
+         GtkPlacesViewRow *row)
+{
+  GtkWidget *view;
+  GFile *file;
+
+  get_view_and_file (row, &view, &file);
+
+  if (!view)
+    return;
+
+  emit_open_location (GTK_PLACES_VIEW (view), file, GTK_PLACES_OPEN_NORMAL);
+
+  g_clear_object (&file);
+}
+
+static void
+open_in_new_tab_cb (GtkMenuItem      *item,
+                    GtkPlacesViewRow *row)
+{
+  GtkWidget *view;
+  GFile *file;
+
+  get_view_and_file (row, &view, &file);
+
+  if (!view)
+    return;
+
+  emit_open_location (GTK_PLACES_VIEW (view), file, GTK_PLACES_OPEN_NEW_TAB);
+
+  g_clear_object (&file);
+}
+
+static void
+open_in_new_window_cb (GtkMenuItem      *item,
+                       GtkPlacesViewRow *row)
+{
+  GtkWidget *view;
+  GFile *file;
+
+  get_view_and_file (row, &view, &file);
+
+  if (!view)
+    return;
+
+  emit_open_location (GTK_PLACES_VIEW (view), file, GTK_PLACES_OPEN_NEW_WINDOW);
+
+  g_clear_object (&file);
+}
+
+static void
+mount_cb (GtkMenuItem      *item,
+          GtkPlacesViewRow *row)
+{
+  GtkWidget *view;
+  GVolume *volume;
+
+  view = gtk_widget_get_ancestor (GTK_WIDGET (row), GTK_TYPE_PLACES_VIEW);
+  volume = gtk_places_view_row_get_volume (row);
+
+  if (!view || !volume)
+    return;
+
+  mount_volume (GTK_PLACES_VIEW (view), volume);
+}
+
+static void
+unmount_cb (GtkMenuItem      *item,
+            GtkPlacesViewRow *row)
+{
+  GMountOperation *mount_op;
+  GtkWidget *view;
+  GMount *mount;
+
+  view = gtk_widget_get_ancestor (GTK_WIDGET (row), GTK_TYPE_PLACES_VIEW);
+  mount = gtk_places_view_row_get_mount (row);
+
+  if (!view || !mount)
+    return;
+
+  mount_op = gtk_mount_operation_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (row))));
+  g_mount_unmount_with_operation (mount,
+                                  0,
+                                  mount_op,
+                                  NULL,
+                                  unmount_ready_callback,
+                                  view);
+  g_object_unref (mount_op);
+}
+
+/* Constructs the popup menu if needed */
+static void
+build_popup_menu (GtkPlacesView    *view,
+                  GtkPlacesViewRow *row)
+{
+  GtkPlacesViewPrivate *priv;
+  GtkWidget *item;
+  GMount *mount;
+
+  g_assert (GTK_IS_PLACES_VIEW (view));
+  g_assert (GTK_IS_PLACES_VIEW_ROW (row));
+
+  priv = view->priv;
+  mount = gtk_places_view_row_get_mount (row);
+
+  priv->popup_menu = gtk_menu_new ();
+  gtk_style_context_add_class (gtk_widget_get_style_context (priv->popup_menu),
+                               GTK_STYLE_CLASS_CONTEXT_MENU);
+
+  gtk_menu_attach_to_widget (GTK_MENU (priv->popup_menu),
+                             GTK_WIDGET (view),
+                             popup_menu_detach_cb);
+
+  item = gtk_menu_item_new_with_mnemonic (_("_Open"));
+  g_signal_connect (item,
+                    "activate",
+                    G_CALLBACK (open_cb),
+                    row);
+  gtk_widget_show (item);
+  gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
+
+  if (priv->open_flags & GTK_PLACES_OPEN_NEW_TAB)
+    {
+      item = gtk_menu_item_new_with_mnemonic (_("Open in New _Tab"));
+      g_signal_connect (item,
+                        "activate",
+                        G_CALLBACK (open_in_new_tab_cb),
+                        row);
+      gtk_widget_show (item);
+      gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
+    }
+
+  if (priv->open_flags & GTK_PLACES_OPEN_NEW_WINDOW)
+    {
+      item = gtk_menu_item_new_with_mnemonic (_("Open in New _Window"));
+      g_signal_connect (item,
+                        "activate",
+                        G_CALLBACK (open_in_new_window_cb),
+                        row);
+      gtk_widget_show (item);
+      gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
+    }
+
+  /* Separator */
+  item = gtk_separator_menu_item_new ();
+  gtk_widget_show (item);
+  gtk_menu_shell_insert (GTK_MENU_SHELL (priv->popup_menu), item, -1);
+
+  /* Mount/Unmount items */
+  if (mount)
+    {
+      item = gtk_menu_item_new_with_mnemonic (_("_Unmount"));
+      g_signal_connect (item,
+                        "activate",
+                        G_CALLBACK (unmount_cb),
+                        row);
+      gtk_widget_show (item);
+      gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
+    }
+  else
+    {
+      item = gtk_menu_item_new_with_mnemonic (_("_Mount"));
+      g_signal_connect (item,
+                        "activate",
+                        G_CALLBACK (mount_cb),
+                        row);
+      gtk_widget_show (item);
+      gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
+    }
+}
+
+static void
+popup_menu (GtkPlacesViewRow *row,
+            GdkEventButton   *event)
+{
+  GtkPlacesViewPrivate *priv;
+  GtkWidget *view;
+  gint button;
+
+  view = gtk_widget_get_ancestor (GTK_WIDGET (row), GTK_TYPE_PLACES_VIEW);
+  priv = GTK_PLACES_VIEW (view)->priv;
+
+  g_clear_pointer (&priv->popup_menu, gtk_widget_destroy);
+
+  build_popup_menu (GTK_PLACES_VIEW (view), row);
+
+  /* The event button needs to be 0 if we're popping up this menu from
+   * a button release, else a 2nd click outside the menu with any button
+   * other than the one that invoked the menu will be ignored (instead
+   * of dismissing the menu). This is a subtle fragility of the GTK menu code.
+   */
+  if (event)
+    {
+      if (event->type == GDK_BUTTON_RELEASE)
+        button = 0;
+      else
+        button = event->button;
+    }
+  else
+    {
+      button = 0;
+    }
+
+  gtk_menu_popup (GTK_MENU (priv->popup_menu),
+                  NULL,
+                  NULL,
+                  NULL,
+                  NULL,
+                  button,
+                  event ? event->time : gtk_get_current_event_time ());
+}
+
+static gboolean
+on_button_release_event (GtkWidget        *widget,
+                         GdkEventButton   *event,
+                         GtkPlacesViewRow *row)
+{
+  if (row &&
+      event &&
+      event->button == 3)
+    {
+      popup_menu (row, event);
+    }
+
+  return TRUE;
+}
+
 static void
 on_connect_button_clicked (GtkPlacesView *view)
 {
diff --git a/gtk/gtkplacesviewrow.c b/gtk/gtkplacesviewrow.c
index 30edbbf..6152f11 100644
--- a/gtk/gtkplacesviewrow.c
+++ b/gtk/gtkplacesviewrow.c
@@ -29,12 +29,15 @@ struct _GtkPlacesViewRow
 {
   GtkListBoxRow  parent_instance;
 
+  GtkEventBox   *event_box;
   GtkImage      *icon_image;
   GtkLabel      *name_label;
   GtkLabel      *path_label;
 
   GVolume       *volume;
   GMount        *mount;
+
+  GdkWindow      *event_window;
 };
 
 G_DEFINE_TYPE (GtkPlacesViewRow, gtk_places_view_row, GTK_TYPE_LIST_BOX_ROW)
@@ -176,6 +179,7 @@ gtk_places_view_row_class_init (GtkPlacesViewRowClass *klass)
 
   gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/ui/gtkplacesviewrow.ui");
 
+  gtk_widget_class_bind_template_child (widget_class, GtkPlacesViewRow, event_box);
   gtk_widget_class_bind_template_child (widget_class, GtkPlacesViewRow, icon_image);
   gtk_widget_class_bind_template_child (widget_class, GtkPlacesViewRow, name_label);
   gtk_widget_class_bind_template_child (widget_class, GtkPlacesViewRow, path_label);
@@ -212,3 +216,11 @@ gtk_places_view_row_get_volume (GtkPlacesViewRow *row)
 
   return row->volume;
 }
+
+GtkWidget*
+gtk_places_view_row_get_event_box (GtkPlacesViewRow *row)
+{
+  g_return_val_if_fail (GTK_IS_PLACES_VIEW_ROW (row), NULL);
+
+  return GTK_WIDGET (row->event_box);
+}
diff --git a/gtk/gtkplacesviewrow.h b/gtk/gtkplacesviewrow.h
index 2746a35..6de480d 100644
--- a/gtk/gtkplacesviewrow.h
+++ b/gtk/gtkplacesviewrow.h
@@ -34,6 +34,8 @@ G_DECLARE_FINAL_TYPE (GtkPlacesViewRow, gtk_places_view_row, GTK, PLACES_VIEW_RO
 GtkWidget*         gtk_places_view_row_new                       (GVolume            *volume,
                                                                   GMount             *mount);
 
+GtkWidget*         gtk_places_view_row_get_event_box             (GtkPlacesViewRow   *row);
+
 GMount*            gtk_places_view_row_get_mount                 (GtkPlacesViewRow   *row);
 
 GVolume*           gtk_places_view_row_get_volume                (GtkPlacesViewRow   *row);
diff --git a/gtk/ui/gtkplacesview.ui b/gtk/ui/gtkplacesview.ui
index 68ef7de..ce4ef83 100644
--- a/gtk/ui/gtkplacesview.ui
+++ b/gtk/ui/gtkplacesview.ui
@@ -40,6 +40,7 @@
                   <object class="GtkListBox" id="recent_servers_listbox">
                     <property name="visible">True</property>
                     <property name="can_focus">True</property>
+                    <property name="selection_mode">none</property>
                   </object>
                 </child>
               </object>
diff --git a/gtk/ui/gtkplacesviewrow.ui b/gtk/ui/gtkplacesviewrow.ui
index c17a1ed..1ccee77 100644
--- a/gtk/ui/gtkplacesviewrow.ui
+++ b/gtk/ui/gtkplacesviewrow.ui
@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.18.1 -->
 <interface>
   <requires lib="gtk+" version="3.16"/>
   <template class="GtkPlacesViewRow" parent="GtkListBoxRow">
@@ -10,56 +9,61 @@
       <class name="volume-row" />
     </style>
     <child>
-      <object class="GtkBox" id="box">
+      <object class="GtkEventBox" id="event_box">
         <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="margin_start">12</property>
-        <property name="margin_end">12</property>
-        <property name="margin_top">6</property>
-        <property name="margin_bottom">6</property>
-        <property name="border_width">0</property>
-        <property name="spacing">18</property>
         <child>
-          <object class="GtkImage" id="icon_image">
+          <object class="GtkBox" id="box">
             <property name="visible">True</property>
             <property name="can_focus">False</property>
-            <property name="pixel_size">32</property>
+            <property name="margin_start">12</property>
+            <property name="margin_end">12</property>
+            <property name="margin_top">6</property>
+            <property name="margin_bottom">6</property>
+            <property name="border_width">0</property>
+            <property name="spacing">18</property>
+            <child>
+              <object class="GtkImage" id="icon_image">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="pixel_size">32</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="name_label">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="hexpand">True</property>
+                <property name="xalign">0</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="path_label">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="justify">right</property>
+                <property name="ellipsize">middle</property>
+                <property name="xalign">1</property>
+                <style>
+                  <class name="dim-label"/>
+                </style>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
           </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">True</property>
-            <property name="position">0</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkLabel" id="name_label">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="hexpand">True</property>
-            <property name="xalign">0</property>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">True</property>
-            <property name="position">1</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkLabel" id="path_label">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="justify">right</property>
-            <property name="ellipsize">middle</property>
-            <property name="xalign">1</property>
-            <style>
-              <class name="dim-label"/>
-            </style>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">True</property>
-            <property name="position">2</property>
-          </packing>
         </child>
       </object>
     </child>


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