[nautilus/antonioffix-menus-and-popovers: 17/20] files-view: Pop up context menu near selection



commit 4ed726ff5c89bd573b03014b067946a8d240267f
Author: António Fernandes <antoniof gnome org>
Date:   Sun Jan 7 02:00:05 2018 +0000

    files-view: Pop up context menu near selection
    
    Context menus pop up where the pointer is, which is at the
    selection if the pointing device is used to open the menu.
    
    However, if the <Menu> key or <Shift>+<F10> keyboard shortcut
    are used, context menus still shows up wherever the pointer is,
    which may not be near the selected item at all.
    
    Instead, when the context menu is activated from the keyboard,
    show it next to the selected item. If multiple items are selected,
    show it next to the focused item. If the focus is not on a selected
    item, show it next to the selected item which is sorted the lowest.
    In all cases, scroll as necessary to make sure the whole item is
    visible.
    
    Fixes: https://bugzilla.gnome.org/show_bug.cgi?id=102666

 src/nautilus-canvas-container.c     | 41 ++++++++++++++++++++++
 src/nautilus-canvas-container.h     |  3 ++
 src/nautilus-canvas-view.c          | 49 ++++++++++++++++++++++++++
 src/nautilus-files-view.c           | 35 +++++++++++++++++--
 src/nautilus-files-view.h           |  2 ++
 src/nautilus-list-view.c            | 69 +++++++++++++++++++++++++++++++++++++
 src/nautilus-view-icon-controller.c | 51 +++++++++++++++++++++++++++
 7 files changed, 247 insertions(+), 3 deletions(-)
---
diff --git a/src/nautilus-canvas-container.c b/src/nautilus-canvas-container.c
index f42d56d60..5372df654 100644
--- a/src/nautilus-canvas-container.c
+++ b/src/nautilus-canvas-container.c
@@ -4594,6 +4594,21 @@ nautilus_canvas_container_get_first_visible_icon (NautilusCanvasContainer *conta
     return best_icon ? best_icon->data : NULL;
 }
 
+NautilusCanvasIconData *
+nautilus_canvas_container_get_focused_icon (NautilusCanvasContainer *container)
+{
+    NautilusCanvasIcon *icon;
+
+    icon = container->details->focus;
+
+    if (icon != NULL)
+    {
+        return icon->data;
+    }
+
+    return NULL;
+}
+
 /* puts the icon at the top of the screen */
 void
 nautilus_canvas_container_scroll_to_canvas (NautilusCanvasContainer *container,
@@ -5330,6 +5345,32 @@ nautilus_canvas_container_get_icon_locations (NautilusCanvasContainer *container
     return result;
 }
 
+/* Returns a GdkRectangle of the icon. The bounding box is adjusted with the
+ * pixels_per_unit already, so they are the final positions on the canvas */
+GdkRectangle *
+nautilus_canvas_container_get_icon_bounding_box (NautilusCanvasContainer *container,
+                                                 NautilusCanvasIconData  *data)
+{
+    NautilusCanvasIcon *icon;
+    int x1, x2, y1, y2;
+    GdkRectangle *bounding_box;
+
+    g_return_val_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container), NULL);
+    g_return_val_if_fail (data != NULL, NULL);
+
+    icon = g_hash_table_lookup (container->details->icon_set, data);
+    icon_get_bounding_box (icon,
+                           &x1, &y1, &x2, &y2,
+                           BOUNDS_USAGE_FOR_DISPLAY);
+    bounding_box = g_malloc0 (sizeof (GdkRectangle));
+    bounding_box->x = x1 * EEL_CANVAS (container)->pixels_per_unit;
+    bounding_box->width = (x2 - x1) * EEL_CANVAS (container)->pixels_per_unit;
+    bounding_box->y = y1 * EEL_CANVAS (container)->pixels_per_unit;
+    bounding_box->height = (y2 - y1) * EEL_CANVAS (container)->pixels_per_unit;
+
+    return bounding_box;
+}
+
 /* Returns an array of GdkRectangles of the icons. The bounding box is adjusted
  * with the pixels_per_unit already, so they are the final positions on the canvas */
 static GArray *
diff --git a/src/nautilus-canvas-container.h b/src/nautilus-canvas-container.h
index 63cec4b6b..9da9fa61f 100644
--- a/src/nautilus-canvas-container.h
+++ b/src/nautilus-canvas-container.h
@@ -226,6 +226,9 @@ void              nautilus_canvas_container_reveal                        (Nauti
                                                                           NautilusCanvasIconData       
*data);
 gboolean          nautilus_canvas_container_is_empty                      (NautilusCanvasContainer  
*container);
 NautilusCanvasIconData *nautilus_canvas_container_get_first_visible_icon        (NautilusCanvasContainer  
*container);
+NautilusCanvasIconData *nautilus_canvas_container_get_focused_icon              (NautilusCanvasContainer  
*container);
+GdkRectangle      *nautilus_canvas_container_get_icon_bounding_box          (NautilusCanvasContainer  
*container,
+                                                                            NautilusCanvasIconData       
*data);
 void              nautilus_canvas_container_scroll_to_canvas                (NautilusCanvasContainer  
*container,
                                                                             NautilusCanvasIconData       
*data);
 
diff --git a/src/nautilus-canvas-view.c b/src/nautilus-canvas-view.c
index f00fa5064..f5392e97d 100644
--- a/src/nautilus-canvas-view.c
+++ b/src/nautilus-canvas-view.c
@@ -959,6 +959,54 @@ nautilus_canvas_view_compute_rename_popover_pointing_to (NautilusFilesView *view
     return bounding_box;
 }
 
+static GdkRectangle *
+nautilus_canvas_view_reveal_rectangle_for_context_menu (NautilusFilesView *view)
+{
+    GList *selection;
+    NautilusCanvasContainer *container;
+    NautilusCanvasIconData *data;
+    GdkRectangle *rectangle;
+
+    g_return_val_if_fail (NAUTILUS_IS_CANVAS_VIEW (view), NULL);
+
+    selection = nautilus_view_get_selection (NAUTILUS_VIEW (view));
+    g_return_val_if_fail (selection != NULL, NULL);
+
+    container = get_canvas_container (NAUTILUS_CANVAS_VIEW (view));
+
+    /* Update the icon ordering to reveal the rigth selection */
+    nautilus_canvas_container_layout_now (container);
+
+    /* Get the data of the focused item, if selected. Otherwise, get the
+     * data of the last selected item.*/
+    data = nautilus_canvas_container_get_focused_icon (container);
+    if (!data || g_list_index (selection, NAUTILUS_FILE (data)) == -1)
+    {
+        selection = g_list_last (selection);
+        data = NAUTILUS_CANVAS_ICON_DATA (selection->data);
+    }
+    nautilus_file_list_free (selection);
+
+    nautilus_canvas_container_reveal (container, data);
+
+    rectangle = nautilus_canvas_container_get_icon_bounding_box (container, data);
+    if (rectangle != NULL)
+    {
+        GtkScrolledWindow *parent_container;
+        GtkAdjustment *vadjustment;
+        GtkAdjustment *hadjustment;
+
+        parent_container = GTK_SCROLLED_WINDOW (nautilus_files_view_get_content_widget (view));
+        vadjustment = gtk_scrolled_window_get_vadjustment (parent_container);
+        hadjustment = gtk_scrolled_window_get_hadjustment (parent_container);
+
+        rectangle->x -= gtk_adjustment_get_value (hadjustment);
+        rectangle->y -= gtk_adjustment_get_value (vadjustment);
+    }
+
+    return rectangle;
+}
+
 static void
 nautilus_canvas_view_set_selection (NautilusFilesView *view,
                                     GList             *selection)
@@ -1515,6 +1563,7 @@ nautilus_canvas_view_class_init (NautilusCanvasViewClass *klass)
     nautilus_files_view_class->get_view_id = nautilus_canvas_view_get_id;
     nautilus_files_view_class->get_first_visible_file = canvas_view_get_first_visible_file;
     nautilus_files_view_class->scroll_to_file = canvas_view_scroll_to_file;
+    nautilus_files_view_class->reveal_rectangle_for_context_menu = 
nautilus_canvas_view_reveal_rectangle_for_context_menu;
 }
 
 static void
diff --git a/src/nautilus-files-view.c b/src/nautilus-files-view.c
index 97ef75fed..1c73c0f44 100644
--- a/src/nautilus-files-view.c
+++ b/src/nautilus-files-view.c
@@ -8106,6 +8106,12 @@ nautilus_files_view_update_toolbar_menus (NautilusFilesView *view)
     nautilus_files_view_reset_view_menu (view);
 }
 
+static GdkRectangle *
+nautilus_files_view_reveal_rectangle_for_context_menu (NautilusFilesView *view)
+{
+    return NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->reveal_rectangle_for_context_menu (view);
+}
+
 /**
  * nautilus_files_view_pop_up_selection_context_menu
  *
@@ -8129,9 +8135,32 @@ nautilus_files_view_pop_up_selection_context_menu  (NautilusFilesView *view,
      */
     update_context_menus_if_pending (view);
 
-    nautilus_pop_up_context_menu_at_pointer (GTK_WIDGET (view),
-                                             priv->selection_menu,
-                                             event);
+    if (event != NULL)
+    {
+        nautilus_pop_up_context_menu_at_pointer (GTK_WIDGET (view),
+                                                 priv->selection_menu,
+                                                 event);
+    }
+    else
+    {
+        /* If triggered from the keyboard, popup at selection, not pointer */
+        g_autofree GdkRectangle *rectangle = NULL;
+        g_autoptr (GtkWidget) gtk_menu = NULL;
+
+        rectangle = nautilus_files_view_reveal_rectangle_for_context_menu (view);
+        g_return_if_fail (rectangle != NULL);
+
+        gtk_menu = gtk_menu_new_from_model (G_MENU_MODEL (priv->selection_menu));
+        gtk_menu_attach_to_widget (GTK_MENU (gtk_menu), GTK_WIDGET (view), NULL);
+
+        gtk_menu_popup_at_rect (GTK_MENU (gtk_menu),
+                                gtk_widget_get_window (GTK_WIDGET (view)),
+                                rectangle,
+                                GDK_GRAVITY_SOUTH_WEST,
+                                GDK_GRAVITY_NORTH_WEST,
+                                NULL);
+        g_object_ref_sink (gtk_menu);
+    }
 }
 
 /**
diff --git a/src/nautilus-files-view.h b/src/nautilus-files-view.h
index 80384f27d..7767e2a69 100644
--- a/src/nautilus-files-view.h
+++ b/src/nautilus-files-view.h
@@ -231,6 +231,8 @@ struct _NautilusFilesViewClass {
 
         GdkRectangle * (* compute_rename_popover_pointing_to) (NautilusFilesView *view);
 
+        GdkRectangle * (* reveal_rectangle_for_context_menu) (NautilusFilesView *view);
+
         GIcon *        (* get_icon)          (NautilusFilesView *view);
 
         /* Use this to show an optional visual feedback when the directory is empty.
diff --git a/src/nautilus-list-view.c b/src/nautilus-list-view.c
index 60740e2ce..a4bf8bfc2 100644
--- a/src/nautilus-list-view.c
+++ b/src/nautilus-list-view.c
@@ -3696,6 +3696,74 @@ nautilus_list_view_compute_rename_popover_pointing_to (NautilusFilesView *view)
     return rect;
 }
 
+static GdkRectangle *
+nautilus_list_view_reveal_rectangle_for_context_menu (NautilusFilesView *view)
+{
+    NautilusListView *list_view;
+    GtkTreeView *tree_view;
+    GtkTreeSelection *tree_selection;
+    GtkTreePath *path;
+    GdkRectangle *rect;
+
+    g_return_val_if_fail (NAUTILUS_IS_LIST_VIEW (view), NULL);
+
+    list_view = NAUTILUS_LIST_VIEW (view);
+    tree_view = list_view->details->tree_view;
+    tree_selection = gtk_tree_view_get_selection (tree_view);
+    g_return_val_if_fail (tree_selection != NULL, NULL);
+
+    /* Get the path to the last focused item, if selected. Otherwise, get
+     * the path to the selected item which is sorted the lowest.
+     */
+    gtk_tree_view_get_cursor (tree_view, &path, NULL);
+    if (!path || !gtk_tree_selection_path_is_selected (tree_selection, path))
+    {
+        GList *list;
+
+        list = gtk_tree_selection_get_selected_rows (tree_selection, NULL);
+        list = g_list_last (list);
+        path = g_steal_pointer(&list->data);
+
+        g_list_free_full (list, (GDestroyNotify) gtk_tree_path_free);
+    }
+
+    gtk_tree_view_scroll_to_cell (tree_view, path, NULL, FALSE, 0.0, 0.0);
+
+    rect = g_malloc0 (sizeof (GdkRectangle));
+    gtk_tree_view_get_cell_area (tree_view,
+                                 path,
+                                 list_view->details->file_name_column,
+                                 rect);
+    gtk_tree_view_convert_bin_window_to_widget_coords (tree_view,
+                                                       rect->x, rect->y,
+                                                       &rect->x, &rect->y);
+
+    /* FIXME Due to smooth scrolling, we may get the cell area while the view is
+     * still scrolling (and still outside the view), not at the final position
+     * of the cell after scrolling.
+     * https://bugzilla.gnome.org/show_bug.cgi?id=746773
+     * The following workaround guesses the final "y" coordinate by clamping it
+     * to the widget edge. Note that the top edge has got columns header, which
+     * is private, so first guess the header height from the difference between
+     * widget coordinates and bin cooridinates.
+     */
+    {
+        int header_height;
+
+        gtk_tree_view_convert_bin_window_to_widget_coords (tree_view,
+                                                           0, 0,
+                                                           NULL, &header_height);
+
+        rect->y = CLAMP (rect->y,
+                         header_height,
+                         gtk_widget_get_allocated_height (GTK_WIDGET (list_view)) - rect->height);
+    }
+
+    gtk_tree_path_free (path);
+
+    return rect;
+}
+
 static void
 nautilus_list_view_class_init (NautilusListViewClass *class)
 {
@@ -3735,6 +3803,7 @@ nautilus_list_view_class_init (NautilusListViewClass *class)
     nautilus_files_view_class->get_first_visible_file = nautilus_list_view_get_first_visible_file;
     nautilus_files_view_class->scroll_to_file = list_view_scroll_to_file;
     nautilus_files_view_class->compute_rename_popover_pointing_to = 
nautilus_list_view_compute_rename_popover_pointing_to;
+    nautilus_files_view_class->reveal_rectangle_for_context_menu = 
nautilus_list_view_reveal_rectangle_for_context_menu;
 }
 
 static void
diff --git a/src/nautilus-view-icon-controller.c b/src/nautilus-view-icon-controller.c
index 93a921cff..e5d24adf8 100644
--- a/src/nautilus-view-icon-controller.c
+++ b/src/nautilus-view-icon-controller.c
@@ -639,6 +639,56 @@ real_compute_rename_popover_pointing_to (NautilusFilesView *files_view)
     return allocation;
 }
 
+static GdkRectangle *
+real_reveal_rectangle_for_context_menu (NautilusFilesView *files_view)
+{
+    g_autoptr (GList) selection = NULL;
+    NautilusViewIconController *self = NAUTILUS_VIEW_ICON_CONTROLLER (files_view);
+    GtkWidget *item_ui;
+    GdkRectangle *rectangle;
+    GtkWidget *content_widget;
+    GtkAdjustment *vadjustment;
+    int view_height;
+
+    selection = nautilus_view_get_selection (NAUTILUS_VIEW (files_view));
+    g_return_val_if_fail (selection != NULL, NULL);
+    nautilus_file_list_unref (selection);
+
+    /* Get the focused item_ui, if selected.
+     * Otherwise, get the selected item_ui which is sorted the lowest.*/
+    item_ui = gtk_container_get_focus_child (GTK_CONTAINER (self->view_ui));
+    if (!item_ui || !gtk_flow_box_child_is_selected (GTK_FLOW_BOX_CHILD (item_ui)))
+    {
+        g_autoptr (GList) list = gtk_flow_box_get_selected_children (GTK_FLOW_BOX (self->view_ui));
+
+        list = g_list_last (list);
+        item_ui = GTK_WIDGET (list->data);
+    }
+
+    rectangle = g_malloc0 (sizeof (GdkRectangle));
+    gtk_widget_get_allocation (item_ui, (GtkAllocation *) rectangle);
+    content_widget = nautilus_files_view_get_content_widget (files_view);
+    view_height = gtk_widget_get_allocated_height (content_widget);
+    vadjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (content_widget));
+
+    /* Scroll only as necessary. TODO: Would be nice to have this as part of
+     * GtkFlowBox. GtkTreeView has something similar. */
+    if (rectangle->y < gtk_adjustment_get_value (vadjustment))
+    {
+        gtk_adjustment_set_value (vadjustment, rectangle->y);
+    }
+    else if (rectangle->y + rectangle->height >
+                gtk_adjustment_get_value (vadjustment) + view_height)
+    {
+        gtk_adjustment_set_value (vadjustment,
+                                  rectangle->y + rectangle->height - view_height);
+    }
+
+    rectangle->y -= gtk_adjustment_get_value (vadjustment);
+
+    return rectangle;
+}
+
 static void
 real_click_policy_changed (NautilusFilesView *files_view)
 {
@@ -930,6 +980,7 @@ nautilus_view_icon_controller_class_init (NautilusViewIconControllerClass *klass
     files_view_class->get_zoom_level_percentage = real_get_zoom_level_percentage;
     files_view_class->is_zoom_level_default = real_is_zoom_level_default;
     files_view_class->compute_rename_popover_pointing_to = real_compute_rename_popover_pointing_to;
+    files_view_class->reveal_rectangle_for_context_menu = real_reveal_rectangle_for_context_menu;
 }
 
 static void


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