[libdazzle] suggestions: add positioning func to suggestion box



commit 51752dd892ebbfd6e5b17e34e994463416a38aa8
Author: Christian Hergert <chergert redhat com>
Date:   Mon Jul 10 20:13:47 2017 -0700

    suggestions: add positioning func to suggestion box
    
    This allows some flexibility in how results are displayed. You
    can position things relative to the entry, or globally. There
    are two helper functions for the common cases: directly under
    the entry, or full-width like some web-browsers today.

 src/suggestions/dzl-suggestion-entry.c    |  186 ++++++++++++++++++++++++++++-
 src/suggestions/dzl-suggestion-entry.h    |   48 ++++++--
 src/suggestions/dzl-suggestion-popover.c  |   65 +++++++----
 src/suggestions/dzl-suggestion-popover.ui |    5 -
 src/suggestions/dzl-suggestion-private.h  |   31 +++++
 tests/test-suggestion.c                   |    3 +
 6 files changed, 297 insertions(+), 41 deletions(-)
---
diff --git a/src/suggestions/dzl-suggestion-entry.c b/src/suggestions/dzl-suggestion-entry.c
index 96b1b90..295a5b3 100644
--- a/src/suggestions/dzl-suggestion-entry.c
+++ b/src/suggestions/dzl-suggestion-entry.c
@@ -24,6 +24,7 @@
 #include "suggestions/dzl-suggestion-entry.h"
 #include "suggestions/dzl-suggestion-entry-buffer.h"
 #include "suggestions/dzl-suggestion-popover.h"
+#include "suggestions/dzl-suggestion-private.h"
 #include "util/dzl-util-private.h"
 
 #if 0
@@ -44,10 +45,15 @@
 
 typedef struct
 {
-  DzlSuggestionPopover     *popover;
-  DzlSuggestionEntryBuffer *buffer;
-  GListModel               *model;
-  gulong                    changed_handler;
+  DzlSuggestionPopover      *popover;
+  DzlSuggestionEntryBuffer  *buffer;
+  GListModel                *model;
+
+  gulong                     changed_handler;
+
+  DzlSuggestionPositionFunc  func;
+  gpointer                   func_data;
+  GDestroyNotify             func_data_destroy;
 } DzlSuggestionEntryPrivate;
 
 enum {
@@ -88,6 +94,7 @@ dzl_suggestion_entry_show_suggestions (DzlSuggestionEntry *self)
 
   g_assert (DZL_IS_SUGGESTION_ENTRY (self));
 
+  _dzl_suggestion_entry_reposition (self, priv->popover);
   dzl_suggestion_popover_popup (priv->popover);
 
   DZL_EXIT;
@@ -320,6 +327,14 @@ dzl_suggestion_entry_destroy (GtkWidget *widget)
   DzlSuggestionEntry *self = (DzlSuggestionEntry *)widget;
   DzlSuggestionEntryPrivate *priv = dzl_suggestion_entry_get_instance_private (self);
 
+  if (priv->func_data_destroy != NULL)
+    {
+      GDestroyNotify notify = g_steal_pointer (&priv->func_data_destroy);
+      gpointer notify_data = g_steal_pointer (&priv->func_data);
+
+      notify (notify_data);
+    }
+
   if (priv->popover != NULL)
     gtk_widget_destroy (GTK_WIDGET (priv->popover));
 
@@ -472,6 +487,8 @@ dzl_suggestion_entry_init (DzlSuggestionEntry *self)
 {
   DzlSuggestionEntryPrivate *priv = dzl_suggestion_entry_get_instance_private (self);
 
+  priv->func = dzl_suggestion_entry_default_position_func;
+
   priv->changed_handler =
     g_signal_connect_after (self,
                             "changed",
@@ -642,3 +659,164 @@ dzl_suggestion_entry_get_typed_text (DzlSuggestionEntry *self)
 
   return dzl_suggestion_entry_buffer_get_typed_text (priv->buffer);
 }
+
+void
+dzl_suggestion_entry_default_position_func (DzlSuggestionEntry *self,
+                                            GdkRectangle       *area,
+                                            gboolean           *is_absolute,
+                                            gpointer            user_data)
+{
+  GtkAllocation alloc;
+
+  g_return_if_fail (DZL_IS_SUGGESTION_ENTRY (self));
+  g_return_if_fail (area != NULL);
+  g_return_if_fail (is_absolute != NULL);
+
+  *is_absolute = FALSE;
+
+  gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
+
+  area->y += alloc.height;
+  area->height = 300;
+}
+
+/**
+ * dzl_suggestion_entry_window_position_func:
+ *
+ * This is a #DzlSuggestionPositionFunc that can be used to make the suggestion
+ * popover the full width of the window. It is similar to what you might find
+ * in a web browser.
+ */
+void
+dzl_suggestion_entry_window_position_func (DzlSuggestionEntry *self,
+                                           GdkRectangle       *area,
+                                           gboolean           *is_absolute,
+                                           gpointer            user_data)
+{
+  GtkWidget *toplevel;
+
+  g_return_if_fail (DZL_IS_SUGGESTION_ENTRY (self));
+  g_return_if_fail (area != NULL);
+  g_return_if_fail (is_absolute != NULL);
+
+  toplevel = gtk_widget_get_ancestor (GTK_WIDGET (self), GTK_TYPE_WINDOW);
+
+  if (toplevel != NULL)
+    {
+      GtkWidget *child = gtk_bin_get_child (GTK_BIN (toplevel));
+      GtkAllocation alloc;
+      gint x, y;
+      gint height = 300;
+
+      gtk_widget_translate_coordinates (child, toplevel, 0, 0, &x, &y);
+      gtk_widget_get_allocation (child, &alloc);
+      gtk_window_get_size (GTK_WINDOW (toplevel), NULL, &height);
+
+      area->x = x;
+      area->y = y;
+      area->width = alloc.width;
+      area->height = MAX (300, height / 2);
+
+      /* If our widget would get obscurred, adjust it */
+      gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
+      gtk_widget_translate_coordinates (GTK_WIDGET (self), toplevel,
+                                        0, alloc.height, NULL, &y);
+      if (y > area->y)
+        area->y = y;
+
+      *is_absolute = TRUE;
+
+      return;
+    }
+
+  dzl_suggestion_entry_default_position_func (self, area, is_absolute, NULL);
+}
+
+/**
+ * dzl_suggestion_entry_set_position_func:
+ * @self: a #DzlSuggestionEntry
+ * @func: (scope async) (closure func_data) (destroy func_data_destroy) (nullable):
+ *   A function to call to position the popover, or %NULL to set the default.
+ * @func_data: (nullable): closure data for @func
+ * @func_data_destroy: (nullable): a destroy notify for @func_data
+ *
+ * Sets a position func to position the popover.
+ *
+ * In @func, you should set the height of the rectangle to the maximum height
+ * that the popover should be allowed to grow.
+ *
+ * Since: 3.26
+ */
+void
+dzl_suggestion_entry_set_position_func (DzlSuggestionEntry        *self,
+                                        DzlSuggestionPositionFunc  func,
+                                        gpointer                   func_data,
+                                        GDestroyNotify             func_data_destroy)
+{
+  DzlSuggestionEntryPrivate *priv = dzl_suggestion_entry_get_instance_private (self);
+  GDestroyNotify notify = NULL;
+  gpointer notify_data = NULL;
+
+  g_return_if_fail (DZL_IS_SUGGESTION_ENTRY (self));
+
+  if (func == NULL)
+    {
+      func = dzl_suggestion_entry_default_position_func;
+      func_data = NULL;
+      func_data_destroy = NULL;
+    }
+
+  if (priv->func_data_destroy != NULL)
+    {
+      notify = priv->func_data_destroy;
+      notify_data = priv->func_data;
+    }
+
+  priv->func = func;
+  priv->func_data = func_data;
+  priv->func_data_destroy = func_data_destroy;
+
+  if (notify)
+    notify (notify_data);
+}
+
+void
+_dzl_suggestion_entry_reposition (DzlSuggestionEntry   *self,
+                                  DzlSuggestionPopover *popover)
+{
+  DzlSuggestionEntryPrivate *priv = dzl_suggestion_entry_get_instance_private (self);
+  GtkWidget *toplevel;
+  GtkAllocation alloc;
+  gboolean is_absolute = FALSE;
+
+  g_return_if_fail (DZL_IS_SUGGESTION_ENTRY (self));
+  g_return_if_fail (DZL_IS_SUGGESTION_POPOVER (popover));
+
+  if (!gtk_widget_get_realized (GTK_WIDGET (self)) ||
+      !gtk_widget_get_realized (GTK_WIDGET (popover)))
+    return;
+
+  toplevel = gtk_widget_get_toplevel (GTK_WIDGET (self));
+
+  gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
+
+  alloc.x = 0;
+  alloc.y = 0;
+
+  priv->func (self, &alloc, &is_absolute, priv->func_data);
+
+  _dzl_suggestion_popover_set_max_height (priv->popover, alloc.height);
+
+  if (!is_absolute)
+    {
+      gint x;
+      gint y;
+
+      gtk_widget_translate_coordinates (GTK_WIDGET (self), toplevel, 0, 0, &x, &y);
+      alloc.x += x;
+      alloc.y += y;
+    }
+
+  gtk_widget_set_size_request (GTK_WIDGET (popover), alloc.width, -1);
+  gtk_window_move (GTK_WINDOW (popover), alloc.x, alloc.y);
+}
diff --git a/src/suggestions/dzl-suggestion-entry.h b/src/suggestions/dzl-suggestion-entry.h
index b21bc06..21ac9c3 100644
--- a/src/suggestions/dzl-suggestion-entry.h
+++ b/src/suggestions/dzl-suggestion-entry.h
@@ -29,6 +29,25 @@ G_BEGIN_DECLS
 
 G_DECLARE_DERIVABLE_TYPE (DzlSuggestionEntry, dzl_suggestion_entry, DZL, SUGGESTION_ENTRY, GtkEntry)
 
+/**
+ * DzlSuggestionPositionFunc:
+ * @self: a #DzlSuggestionEntry
+ * @area: (inout): location to place the popover
+ * @is_absolute: (inout): If the area is in absolute coordinates
+ * @user_data: closure data
+ *
+ * Positions the popover in the coordinates defined by @area.
+ *
+ * If @is_absolute is set to %TRUE, then absolute coordinates are used.
+ * Otherwise, the position is expected to be relative to @entry.
+ *
+ * Since: 3.26
+ */
+typedef void (*DzlSuggestionPositionFunc) (DzlSuggestionEntry *entry,
+                                           GdkRectangle       *area,
+                                           gboolean           *is_absolute,
+                                           gpointer            user_data);
+
 struct _DzlSuggestionEntryClass
 {
   GtkEntryClass parent_class;
@@ -50,14 +69,27 @@ struct _DzlSuggestionEntryClass
   gpointer _reserved8;
 };
 
-GtkWidget     *dzl_suggestion_entry_new            (void);
-void           dzl_suggestion_entry_set_model      (DzlSuggestionEntry *self,
-                                                    GListModel         *model);
-GListModel    *dzl_suggestion_entry_get_model      (DzlSuggestionEntry *self);
-const gchar   *dzl_suggestion_entry_get_typed_text (DzlSuggestionEntry *self);
-DzlSuggestion *dzl_suggestion_entry_get_suggestion (DzlSuggestionEntry *self);
-void           dzl_suggestion_entry_set_suggestion (DzlSuggestionEntry *self,
-                                                    DzlSuggestion      *suggestion);
+GtkWidget     *dzl_suggestion_entry_new               (void);
+void           dzl_suggestion_entry_set_model         (DzlSuggestionEntry        *self,
+                                                       GListModel                *model);
+GListModel    *dzl_suggestion_entry_get_model         (DzlSuggestionEntry        *self);
+const gchar   *dzl_suggestion_entry_get_typed_text    (DzlSuggestionEntry        *self);
+DzlSuggestion *dzl_suggestion_entry_get_suggestion    (DzlSuggestionEntry        *self);
+void           dzl_suggestion_entry_set_suggestion    (DzlSuggestionEntry        *self,
+                                                       DzlSuggestion             *suggestion);
+void           dzl_suggestion_entry_set_position_func (DzlSuggestionEntry        *self,
+                                                       DzlSuggestionPositionFunc  func,
+                                                       gpointer                   func_data,
+                                                       GDestroyNotify             func_data_destroy);
+
+void dzl_suggestion_entry_default_position_func (DzlSuggestionEntry *self,
+                                                 GdkRectangle       *area,
+                                                 gboolean           *is_absolute,
+                                                 gpointer            user_data);
+void dzl_suggestion_entry_window_position_func  (DzlSuggestionEntry *self,
+                                                 GdkRectangle       *area,
+                                                 gboolean           *is_absolute,
+                                                 gpointer            user_data);
 
 G_END_DECLS
 
diff --git a/src/suggestions/dzl-suggestion-popover.c b/src/suggestions/dzl-suggestion-popover.c
index bd00464..50a8c4c 100644
--- a/src/suggestions/dzl-suggestion-popover.c
+++ b/src/suggestions/dzl-suggestion-popover.c
@@ -22,7 +22,9 @@
 
 #include "animation/dzl-animation.h"
 #include "suggestions/dzl-suggestion.h"
+#include "suggestions/dzl-suggestion-entry.h"
 #include "suggestions/dzl-suggestion-popover.h"
+#include "suggestions/dzl-suggestion-private.h"
 #include "suggestions/dzl-suggestion-row.h"
 #include "util/dzl-util-private.h"
 #include "widgets/dzl-elastic-bin.h"
@@ -159,32 +161,10 @@ dzl_suggestion_popover_select_row (DzlSuggestionPopover *self,
 static void
 dzl_suggestion_popover_reposition (DzlSuggestionPopover *self)
 {
-  gint width;
-  gint x;
-  gint y;
-
   g_assert (DZL_IS_SUGGESTION_POPOVER (self));
 
-  if (self->relative_to == NULL ||
-      self->transient_for == NULL ||
-      !gtk_widget_get_mapped (self->relative_to) ||
-      !gtk_widget_get_mapped (GTK_WIDGET (self->transient_for)))
-    return;
-
-  gtk_window_get_size (self->transient_for, &width, NULL);
-  gtk_widget_set_size_request (GTK_WIDGET (self), width, -1);
-  gtk_window_get_position (self->transient_for, &x, &y);
-
-  /*
-   * XXX: This is just a hack for testing so we get the placement right.
-   *
-   *      What we should really do is allow hte DzlSuggestionEntry to set our
-   *      relative-to property by wrapping it. That would all the caller to
-   *      place the popover relative to the main content area of the window
-   *      as might be desired for a URL entry or global application search.
-   */
-
-  gtk_window_move (GTK_WINDOW (self), x, y + 47);
+  if (DZL_IS_SUGGESTION_ENTRY (self->relative_to))
+    _dzl_suggestion_entry_reposition (DZL_SUGGESTION_ENTRY (self->relative_to), self);
 }
 
 /**
@@ -358,9 +338,12 @@ dzl_suggestion_popover_screen_changed (GtkWidget *widget,
 static void
 dzl_suggestion_popover_realize (GtkWidget *widget)
 {
+  DzlSuggestionPopover *self = (DzlSuggestionPopover *)widget;
   GdkScreen *screen;
   GdkVisual *visual;
 
+  g_assert (DZL_IS_SUGGESTION_POPOVER (self));
+
   screen = gtk_widget_get_screen (widget);
   visual = gdk_screen_get_rgba_visual (screen);
 
@@ -368,6 +351,8 @@ dzl_suggestion_popover_realize (GtkWidget *widget)
     gtk_widget_set_visual (widget, visual);
 
   GTK_WIDGET_CLASS (dzl_suggestion_popover_parent_class)->realize (widget);
+
+  dzl_suggestion_popover_reposition (self);
 }
 
 static void
@@ -915,3 +900,35 @@ dzl_suggestion_popover_activate_selected (DzlSuggestionPopover *self)
   if (NULL != (suggestion = dzl_suggestion_popover_get_selected (self)))
     g_signal_emit (self, signals [SUGGESTION_ACTIVATED], 0, suggestion);
 }
+
+void
+_dzl_suggestion_popover_set_max_height (DzlSuggestionPopover *self,
+                                        gint                  max_height)
+{
+  GdkWindow *window;
+  gint clip_height = 3000; /* something near 4k'ish */
+
+  g_return_if_fail (DZL_IS_SUGGESTION_POPOVER (self));
+
+  window = gtk_widget_get_window (GTK_WIDGET (self));
+
+  if (window != NULL)
+    {
+      GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (self));
+      GdkMonitor *monitor = gdk_display_get_monitor_at_window (display, window);
+
+      if (monitor != NULL)
+        {
+          GdkRectangle geom;
+
+          gdk_monitor_get_geometry (monitor, &geom);
+          clip_height = geom.height;
+        }
+    }
+
+  max_height = CLAMP (max_height, -1, clip_height);
+
+  g_object_set (self->scrolled_window,
+                "max-content-height", max_height,
+                NULL);
+}
diff --git a/src/suggestions/dzl-suggestion-popover.ui b/src/suggestions/dzl-suggestion-popover.ui
index 55ef0d1..c16a0d5 100644
--- a/src/suggestions/dzl-suggestion-popover.ui
+++ b/src/suggestions/dzl-suggestion-popover.ui
@@ -16,11 +16,6 @@
                 <property name="visible">true</property>
                 <child>
                   <object class="GtkScrolledWindow" id="scrolled_window">
-                    <!-- XXX
-                         We should generate max-content-height based on window position
-                         or some other hueristic. (transient-for maybe?)
-                    -->
-                    <property name="max-content-height">500</property>
                     <property name="hscrollbar-policy">never</property>
                     <property name="propagate-natural-height">true</property>
                     <property name="visible">true</property>
diff --git a/src/suggestions/dzl-suggestion-private.h b/src/suggestions/dzl-suggestion-private.h
new file mode 100644
index 0000000..fd922b6
--- /dev/null
+++ b/src/suggestions/dzl-suggestion-private.h
@@ -0,0 +1,31 @@
+/* dzl-suggestion-private.h
+ *
+ * Copyright (C) 2017 Christian Hergert <chergert redhat com>
+ *
+ * This program 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef DZL_SUGGESTION_PRIVATE_H
+#define DZL_SUGGESTION_PRIVATE_H
+
+#include "suggestions/dzl-suggestion-entry.h"
+#include "suggestions/dzl-suggestion-popover.h"
+
+void _dzl_suggestion_entry_reposition       (DzlSuggestionEntry   *entry,
+                                             DzlSuggestionPopover *popover);
+void _dzl_suggestion_popover_set_max_height (DzlSuggestionPopover *popover,
+                                             gint                  max_height);
+
+#endif /* DZL_SUGGESTION_PRIVATE_H */
diff --git a/tests/test-suggestion.c b/tests/test-suggestion.c
index 46091e4..2da667b 100644
--- a/tests/test-suggestion.c
+++ b/tests/test-suggestion.c
@@ -289,6 +289,9 @@ main (gint   argc,
                         "visible", TRUE,
                         "width-chars", 30,
                         NULL);
+  dzl_suggestion_entry_set_position_func (DZL_SUGGESTION_ENTRY (entry),
+                                          dzl_suggestion_entry_window_position_func,
+                                          NULL, NULL);
   gtk_box_set_center_widget (GTK_BOX (box), entry);
   g_signal_connect (entry, "changed", G_CALLBACK (search_changed), NULL);
   g_signal_connect (entry, "suggestion-activated", G_CALLBACK (suggestion_activated), NULL);


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