[gtk+/wip/gbsneto/other-locations] entrycompletion: enable changing popup position



commit 2212329279c9e0986a1ff96510867c338764e1c2
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date:   Fri Jul 3 14:20:30 2015 -0300

    entrycompletion: enable changing popup position
    
    GtkEntryCompletion only handles bottom popups, and it's
    enought for most cases. There is, however, a considerable
    number of situations where it is good to change the position
    of the popup window.
    
    The new GtkPlacesView widget, for example, requires that
    the popup window is placed above the entry, not below. Since
    the widget has a minimal space below, the popup window is
    narrowed down to a ridiculous height, only to fit the space
    below the entry.
    
    By adding a GtkEntryCompletion::popup-position property,
    GtkEntryCompletion is able to know where the user wants to
    place the popup window, and reposition it properly.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=751913

 gtk/gtkentrycompletion.c    |  146 +++++++++++++++++++++++++++++++++++++++----
 gtk/gtkentrycompletion.h    |    8 +++
 gtk/gtkentryprivate.h       |    2 +
 tests/testentrycompletion.c |  100 ++++++++++++++++++++---------
 4 files changed, 212 insertions(+), 44 deletions(-)
---
diff --git a/gtk/gtkentrycompletion.c b/gtk/gtkentrycompletion.c
index 6a8cbf4..798ba9c 100644
--- a/gtk/gtkentrycompletion.c
+++ b/gtk/gtkentrycompletion.c
@@ -112,6 +112,7 @@ enum
   PROP_TEXT_COLUMN,
   PROP_INLINE_COMPLETION,
   PROP_POPUP_COMPLETION,
+  PROP_POPUP_POSITION,
   PROP_POPUP_SET_WIDTH,
   PROP_POPUP_SINGLE_MATCH,
   PROP_INLINE_SELECTION,
@@ -409,6 +410,24 @@ gtk_entry_completion_class_init (GtkEntryCompletionClass *klass)
                                                          GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
 
   /**
+   * GtkEntryCompletion:popup-position:
+   *
+   * Determines where the popup window should be positioned
+   * regarding its parent entry.
+   *
+   * Since: 3.18
+   **/
+  g_object_class_install_property (object_class,
+                                   PROP_POPUP_POSITION,
+                                   g_param_spec_int ("popup-position",
+                                                     P_("Popup position"),
+                                                     P_("Where the completions window should be positioned"),
+                                                     GTK_POS_LEFT,
+                                                     GTK_POS_BOTTOM,
+                                                     GTK_POS_BOTTOM,
+                                                     GTK_PARAM_READWRITE));
+
+  /**
    * GtkEntryCompletion:popup-set-width:
    *
    * Determines whether the completions popup window will be
@@ -520,6 +539,7 @@ gtk_entry_completion_init (GtkEntryCompletion *completion)
   priv->popup_set_width = TRUE;
   priv->popup_single_match = TRUE;
   priv->inline_selection = FALSE;
+  priv->popup_position = GTK_POS_BOTTOM;
 
   priv->filter_model = NULL;
 }
@@ -681,6 +701,11 @@ gtk_entry_completion_set_property (GObject      *object,
                                                   g_value_get_boolean (value));
         break;
 
+      case PROP_POPUP_POSITION:
+       gtk_entry_completion_set_popup_position (completion,
+                                                g_value_get_int (value));
+        break;
+
       case PROP_POPUP_SET_WIDTH:
        gtk_entry_completion_set_popup_set_width (completion,
                                                  g_value_get_boolean (value));
@@ -749,6 +774,10 @@ gtk_entry_completion_get_property (GObject    *object,
         g_value_set_boolean (value, gtk_entry_completion_get_popup_completion (completion));
         break;
 
+      case PROP_POPUP_POSITION:
+        g_value_set_int (value, gtk_entry_completion_get_popup_position (completion));
+        break;
+
       case PROP_POPUP_SET_WIDTH:
         g_value_set_boolean (value, gtk_entry_completion_get_popup_set_width (completion));
         break;
@@ -1484,7 +1513,6 @@ gtk_entry_completion_list_motion_notify (GtkWidget      *widget,
   return FALSE;
 }
 
-
 /* some nasty size requisition */
 gboolean
 _gtk_entry_completion_resize_popup (GtkEntryCompletion *completion)
@@ -1505,8 +1533,10 @@ _gtk_entry_completion_resize_popup (GtkEntryCompletion *completion)
   gint width;
   GtkTreeViewColumn *action_column;
   gint action_height;
+  GtkPositionType pos;
 
   window = gtk_widget_get_window (completion->priv->entry);
+  pos = completion->priv->popup_position;
 
   if (!window)
     return FALSE;
@@ -1514,17 +1544,15 @@ _gtk_entry_completion_resize_popup (GtkEntryCompletion *completion)
   if (!completion->priv->filter_model)
     return FALSE;
 
+  matches = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (completion->priv->filter_model), NULL);
+  actions = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (completion->priv->actions), NULL);
+  action_column  = gtk_tree_view_get_column (GTK_TREE_VIEW (completion->priv->action_view), 0);
+
   gtk_widget_get_allocation (completion->priv->entry, &allocation);
   gtk_widget_get_preferred_size (completion->priv->entry,
                                  &entry_req, NULL);
 
   gdk_window_get_origin (window, &x, &y);
-  x += allocation.x;
-  y += allocation.y + (allocation.height - entry_req.height) / 2;
-
-  matches = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (completion->priv->filter_model), NULL);
-  actions = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (completion->priv->actions), NULL);
-  action_column  = gtk_tree_view_get_column (GTK_TREE_VIEW (completion->priv->action_view), 0);
 
   /* Call get preferred size on the on the tree view to force it to validate its
    * cells before calling into the cell size functions.
@@ -1578,21 +1606,65 @@ _gtk_entry_completion_resize_popup (GtkEntryCompletion *completion)
   gtk_widget_get_preferred_size (completion->priv->popup_window,
                                  &popup_req, NULL);
 
+  above = FALSE;
+
+  switch (pos)
+    {
+    case GTK_POS_TOP:
+      x += allocation.x;
+      y -= popup_req.height;
+      above = TRUE;
+      break;
+
+    case GTK_POS_RIGHT:
+      x += allocation.x + width;
+      break;
+
+    case GTK_POS_BOTTOM:
+      x += allocation.x;
+      y += allocation.y + (allocation.height - entry_req.height) / 2;
+      break;
+
+    case GTK_POS_LEFT:
+      x += allocation.x - width;
+      break;
+
+    default:
+      g_assert_not_reached ();
+    }
+
+
   if (x < monitor.x)
     x = monitor.x;
   else if (x + popup_req.width > monitor.x + monitor.width)
     x = monitor.x + monitor.width - popup_req.width;
 
-  if (y + entry_req.height + popup_req.height <= monitor.y + monitor.height ||
-      y - monitor.y < (monitor.y + monitor.height) - (y + entry_req.height))
+  if (pos == GTK_POS_TOP)
     {
-      y += entry_req.height;
-      above = FALSE;
+      if (y < monitor.y)
+        {
+          y += popup_req.height + allocation.y + (allocation.height - entry_req.height) / 2 + 
entry_req.height;
+          above = FALSE;
+        }
+      else
+        {
+          y += entry_req.height;
+          above = TRUE;
+        }
     }
   else
     {
-      y -= popup_req.height;
-      above = TRUE;
+      if (y + entry_req.height + popup_req.height <= monitor.y + monitor.height ||
+          y - monitor.y < (monitor.y + monitor.height) - (y + entry_req.height))
+        {
+          y += entry_req.height;
+          above = FALSE;
+        }
+      else
+        {
+          y -= popup_req.height;
+          above = TRUE;
+        }
     }
 
   if (matches > 0)
@@ -2149,6 +2221,54 @@ gtk_entry_completion_get_inline_selection (GtkEntryCompletion *completion)
   return completion->priv->inline_selection;
 }
 
+/**
+ * gtk_entry_completion_get_popup_position:
+ * @completion: a #GtkEntryCompletion
+ *
+ * Returns the position relative to the #GtkEntry where
+ * the popup window is placed.
+ *
+ * Returns: a #GtkPositionType.
+ *
+ * Since: 3.18
+ */
+GtkPositionType
+gtk_entry_completion_get_popup_position (GtkEntryCompletion *completion)
+{
+  g_return_val_if_fail (GTK_IS_ENTRY_COMPLETION (completion), 0);
+
+  return completion->priv->popup_position;
+}
+
+/**
+ * gtk_entry_completion_set_popup_position:
+ * @completion: a #GtkEntryCompletion
+ * @position: a #GtkPositionType
+ *
+ * Sets where the popup window will be placed relatively
+ * to the #GtkEntry of @completion.
+ *
+ * Since: 3.18
+ */
+void
+gtk_entry_completion_set_popup_position (GtkEntryCompletion *completion,
+                                         GtkPositionType     position)
+{
+  g_return_if_fail (GTK_IS_ENTRY_COMPLETION (completion));
+
+  if (completion->priv->popup_position != position)
+    {
+      completion->priv->popup_position = position;
+
+      if (gtk_widget_get_visible (completion->priv->popup_window))
+        {
+          _gtk_entry_completion_popdown (completion);
+          gtk_entry_completion_popup(completion);
+        }
+
+      g_object_notify (G_OBJECT (completion), "popup-position");
+    }
+}
 
 static gint
 gtk_entry_completion_timeout (gpointer data)
diff --git a/gtk/gtkentrycompletion.h b/gtk/gtkentrycompletion.h
index d0f5d08..df22d99 100644
--- a/gtk/gtkentrycompletion.h
+++ b/gtk/gtkentrycompletion.h
@@ -23,6 +23,7 @@
 #endif
 
 #include <gdk/gdk.h>
+#include <gtk/gtkenums.h>
 #include <gtk/gtktreemodel.h>
 #include <gtk/gtkliststore.h>
 #include <gtk/gtkcellarea.h>
@@ -177,6 +178,13 @@ void                gtk_entry_completion_set_text_column        (GtkEntryComplet
 GDK_AVAILABLE_IN_ALL
 gint                gtk_entry_completion_get_text_column        (GtkEntryCompletion          *completion);
 
+GDK_AVAILABLE_IN_3_18
+GtkPositionType     gtk_entry_completion_get_popup_position     (GtkEntryCompletion          *completion);
+
+GDK_AVAILABLE_IN_3_18
+void                gtk_entry_completion_set_popup_position     (GtkEntryCompletion          *completion,
+                                                                 GtkPositionType              position);
+
 G_END_DECLS
 
 #endif /* __GTK_ENTRY_COMPLETION_H__ */
diff --git a/gtk/gtkentryprivate.h b/gtk/gtkentryprivate.h
index 7e84d06..f55ac45 100644
--- a/gtk/gtkentryprivate.h
+++ b/gtk/gtkentryprivate.h
@@ -51,6 +51,8 @@ struct _GtkEntryCompletionPrivate
   GtkWidget *scrolled_window;
   GtkWidget *action_view;
 
+  GtkPositionType popup_position;
+
   gulong completion_timeout;
   gulong changed_id;
   gulong insert_text_id;
diff --git a/tests/testentrycompletion.c b/tests/testentrycompletion.c
index 16f7f10..dfc358f 100644
--- a/tests/testentrycompletion.c
+++ b/tests/testentrycompletion.c
@@ -286,13 +286,26 @@ match_selected_cb (GtkEntryCompletion *completion,
   return TRUE;
 }
 
-int 
+void
+position_radio_activated_cb (GtkRadioButton     *button,
+                             GtkEntryCompletion *completion)
+{
+  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
+    {
+      GtkPositionType pos = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), "position"));
+      gtk_entry_completion_set_popup_position (completion, pos);
+    }
+}
+
+int
 main (int argc, char *argv[])
 {
   GtkWidget *vbox;
+  GtkWidget *hbox;
   GtkWidget *label;
   GtkWidget *entry;
-  GtkEntryCompletion *completion;
+  GtkWidget *button;
+  GtkEntryCompletion *completion[4];
   GtkTreeModel *completion_model;
   GtkCellRenderer *cell;
 
@@ -316,83 +329,83 @@ main (int argc, char *argv[])
   entry = gtk_entry_new ();
   
   /* Create the completion object */
-  completion = gtk_entry_completion_new ();
-  gtk_entry_completion_set_inline_completion (completion, TRUE);
+  completion[0] = gtk_entry_completion_new ();
+  gtk_entry_completion_set_inline_completion (completion[0], TRUE);
   
   /* Assign the completion to the entry */
-  gtk_entry_set_completion (GTK_ENTRY (entry), completion);
-  g_object_unref (completion);
+  gtk_entry_set_completion (GTK_ENTRY (entry), completion[0]);
+  g_object_unref (completion[0]);
   
   gtk_container_add (GTK_CONTAINER (vbox), entry);
 
   /* Create a tree model and use it as the completion model */
   completion_model = create_simple_completion_model ();
-  gtk_entry_completion_set_model (completion, completion_model);
+  gtk_entry_completion_set_model (completion[0], completion_model);
   g_object_unref (completion_model);
   
   /* Use model column 0 as the text column */
-  gtk_entry_completion_set_text_column (completion, 0);
+  gtk_entry_completion_set_text_column (completion[0], 0);
 
   /* Create our second entry */
   entry = gtk_entry_new ();
 
   /* Create the completion object */
-  completion = gtk_entry_completion_new ();
+  completion[1] = gtk_entry_completion_new ();
   
   /* Assign the completion to the entry */
-  gtk_entry_set_completion (GTK_ENTRY (entry), completion);
-  g_object_unref (completion);
+  gtk_entry_set_completion (GTK_ENTRY (entry), completion[1]);
+  g_object_unref (completion[1]);
   
   gtk_container_add (GTK_CONTAINER (vbox), entry);
 
   /* Create a tree model and use it as the completion model */
   completion_model = create_completion_model ();
-  gtk_entry_completion_set_model (completion, completion_model);
-  gtk_entry_completion_set_minimum_key_length (completion, 2);
+  gtk_entry_completion_set_model (completion[1], completion_model);
+  gtk_entry_completion_set_minimum_key_length (completion[1], 2);
   g_object_unref (completion_model);
   
   /* Use model column 1 as the text column */
   cell = gtk_cell_renderer_pixbuf_new ();
-  gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (completion), cell, FALSE);
-  gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (completion), cell, 
+  gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (completion[1]), cell, FALSE);
+  gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (completion[1]), cell,
                                  "pixbuf", 0, NULL); 
 
   cell = gtk_cell_renderer_text_new ();
-  gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (completion), cell, FALSE);
-  gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (completion), cell, 
+  gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (completion[1]), cell, FALSE);
+  gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (completion[1]), cell,
                                  "text", 1, NULL); 
   
-  gtk_entry_completion_set_match_func (completion, match_func, NULL, NULL);
-  g_signal_connect (completion, "match-selected", 
+  gtk_entry_completion_set_match_func (completion[1], match_func, NULL, NULL);
+  g_signal_connect (completion[1], "match-selected",
                    G_CALLBACK (match_selected_cb), NULL);
 
-  gtk_entry_completion_insert_action_text (completion, 100, "action!");
-  gtk_entry_completion_insert_action_text (completion, 101, "'nother action!");
-  g_signal_connect (completion, "action_activated", G_CALLBACK (activated_cb), NULL);
+  gtk_entry_completion_insert_action_text (completion[1], 100, "action!");
+  gtk_entry_completion_insert_action_text (completion[1], 101, "'nother action!");
+  g_signal_connect (completion[1], "action_activated", G_CALLBACK (activated_cb), NULL);
 
   /* Create our third entry */
   entry = gtk_entry_new ();
 
   /* Create the completion object */
-  completion = gtk_entry_completion_new ();
+  completion[2] = gtk_entry_completion_new ();
   
   /* Assign the completion to the entry */
-  gtk_entry_set_completion (GTK_ENTRY (entry), completion);
-  g_object_unref (completion);
+  gtk_entry_set_completion (GTK_ENTRY (entry), completion[2]);
+  g_object_unref (completion[2]);
   
   gtk_container_add (GTK_CONTAINER (vbox), entry);
 
   /* Create a tree model and use it as the completion model */
   completion_model = GTK_TREE_MODEL (gtk_list_store_new (1, G_TYPE_STRING));
 
-  gtk_entry_completion_set_model (completion, completion_model);
+  gtk_entry_completion_set_model (completion[2], completion_model);
   g_object_unref (completion_model);
 
   /* Use model column 0 as the text column */
-  gtk_entry_completion_set_text_column (completion, 0);
+  gtk_entry_completion_set_text_column (completion[2], 0);
 
   /* Fill the completion dynamically */
-  gdk_threads_add_timeout (1000, (GSourceFunc) animation_timer, completion);
+  gdk_threads_add_timeout (1000, (GSourceFunc) animation_timer, completion[2]);
 
   /* Fourth entry */
   gtk_box_pack_start (GTK_BOX (vbox), gtk_label_new ("Model-less entry completion"), FALSE, FALSE, 0);
@@ -400,14 +413,39 @@ main (int argc, char *argv[])
   entry = gtk_entry_new ();
 
   /* Create the completion object */
-  completion = gtk_entry_completion_new ();
+  completion[3] = gtk_entry_completion_new ();
   
   /* Assign the completion to the entry */
-  gtk_entry_set_completion (GTK_ENTRY (entry), completion);
-  g_object_unref (completion);
+  gtk_entry_set_completion (GTK_ENTRY (entry), completion[3]);
+  g_object_unref (completion[3]);
   
   gtk_container_add (GTK_CONTAINER (vbox), entry);
 
+  /* Position selector */
+  hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+  button = gtk_radio_button_new_with_label (NULL, "Bottom");
+  g_object_set_data (G_OBJECT (button), "position", GINT_TO_POINTER (GTK_POS_BOTTOM));
+  g_signal_connect (button, "toggled", G_CALLBACK (position_radio_activated_cb), completion[0]);
+  gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
+
+  button = gtk_radio_button_new_with_label_from_widget (GTK_RADIO_BUTTON (button), "Top");
+  g_object_set_data (G_OBJECT (button), "position", GINT_TO_POINTER (GTK_POS_TOP));
+  g_signal_connect (button, "toggled", G_CALLBACK (position_radio_activated_cb), completion[0]);
+  gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
+
+  button = gtk_radio_button_new_with_label_from_widget (GTK_RADIO_BUTTON (button), "Left");
+  g_object_set_data (G_OBJECT (button), "position", GINT_TO_POINTER (GTK_POS_LEFT));
+  g_signal_connect (button, "toggled", G_CALLBACK (position_radio_activated_cb), completion[0]);
+  gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
+
+  button = gtk_radio_button_new_with_label_from_widget (GTK_RADIO_BUTTON (button), "Right");
+  g_object_set_data (G_OBJECT (button), "position", GINT_TO_POINTER (GTK_POS_RIGHT));
+  g_signal_connect (button, "toggled", G_CALLBACK (position_radio_activated_cb), completion[0]);
+  gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
+
+  gtk_container_add (GTK_CONTAINER (vbox), gtk_label_new ("Position of the completion popup"));
+  gtk_container_add (GTK_CONTAINER (vbox), hbox);
+
   gtk_widget_show_all (window);
 
   gtk_main ();


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