[gnome-builder/editor-layout] command-bar: Implement tab completion



commit c5ee616c4081a0a1e7f8c47027af02cd7e86147c
Author: Alexander Larsson <alexl redhat com>
Date:   Mon Nov 24 15:42:14 2014 +0100

    command-bar: Implement tab completion
    
    When you press tab we expand to the longest common prefix from, and
    if no common prefix exists we show the available matches in a list
    (multiple tabs scroll in the list).
    
    https://bugzilla.gnome.org/show_bug.cgi?id=740629

 src/commands/gb-command-bar.c      |  156 ++++++++++++++++++++++++++++++++++++
 src/resources/ui/gb-command-bar.ui |   17 ++++
 2 files changed, 173 insertions(+), 0 deletions(-)
---
diff --git a/src/commands/gb-command-bar.c b/src/commands/gb-command-bar.c
index cdf7734..bfae313 100644
--- a/src/commands/gb-command-bar.c
+++ b/src/commands/gb-command-bar.c
@@ -31,6 +31,9 @@ struct _GbCommandBarPrivate
   GtkEntry          *entry;
   GtkListBox        *list_box;
   GtkScrolledWindow *scroller;
+  GtkScrolledWindow *completion_scroller;
+  GtkFlowBox        *flow_box;
+  gchar             *last_completion;
 };
 
 G_DEFINE_TYPE_WITH_PRIVATE (GbCommandBar, gb_command_bar, GTK_TYPE_REVEALER)
@@ -66,6 +69,8 @@ gb_command_bar_show (GbCommandBar *bar)
 {
   g_return_if_fail (GB_IS_COMMAND_BAR (bar));
 
+  gtk_widget_hide (GTK_WIDGET (bar->priv->completion_scroller));
+
   gtk_revealer_set_reveal_child (GTK_REVEALER (bar), TRUE);
   gtk_entry_set_text (bar->priv->entry, "");
   gtk_widget_grab_focus (GTK_WIDGET (bar->priv->entry));
@@ -121,6 +126,8 @@ gb_command_bar_on_entry_activate (GbCommandBar *bar,
   if (!workbench)
     return;
 
+  gtk_widget_hide (GTK_WIDGET (bar->priv->completion_scroller));
+
   if (!gb_str_empty0 (text))
     {
       GbCommandManager *manager;
@@ -183,6 +190,44 @@ gb_command_bar_grab_focus (GtkWidget *widget)
   gtk_widget_grab_focus (GTK_WIDGET (bar->priv->entry));
 }
 
+static gchar *
+find_longest_common_prefix (gchar **strv)
+{
+  gchar *lcp = NULL;
+  gchar *lcp_end = NULL;
+  int i;
+
+  for (i = 0; strv[i] != NULL; i++)
+    {
+      gchar *str = strv[i];
+      if (lcp == NULL)
+        {
+          lcp = str;
+          lcp_end = str + strlen (str);
+        }
+      else
+        {
+          gchar *tmp = lcp;
+
+          while (tmp < lcp_end  && *str != 0 && *tmp == *str)
+            {
+              str++;
+              tmp++;
+            }
+
+          lcp_end = tmp;
+        }
+    }
+
+  if (lcp == NULL)
+    return g_strdup ("");
+
+  return g_strndup (lcp, lcp_end - lcp);
+}
+
+#define N_COMPLETION_COLUMS 3
+#define N_UNSCROLLED_COMPLETION_ROWS 4
+
 static gboolean
 gb_command_bar_on_entry_key_press_event (GbCommandBar *bar,
                                          GdkEventKey  *event,
@@ -198,6 +243,111 @@ gb_command_bar_on_entry_key_press_event (GbCommandBar *bar,
       return TRUE;
     }
 
+  if (event->keyval == GDK_KEY_Tab)
+    {
+      GbWorkbench *workbench = NULL;
+
+      workbench = GB_WORKBENCH (gtk_widget_get_toplevel (GTK_WIDGET (bar)));
+      if (workbench)
+        {
+          GtkEditable *editable = GTK_EDITABLE (bar->priv->entry);
+          GtkWidget *viewport = gtk_bin_get_child (GTK_BIN (bar->priv->completion_scroller));
+          GbCommandManager *manager;
+          gchar **completions;
+          int pos, i;
+          gchar *current_prefix, *expanded_prefix;
+
+          pos = gtk_editable_get_position (editable);
+          current_prefix = gtk_editable_get_chars (editable, 0, pos);
+
+          /* If we complete again with the same data we scroll the completion instead */
+          if (gtk_widget_is_visible (GTK_WIDGET (bar->priv->completion_scroller)) &&
+              bar->priv->last_completion != NULL &&
+              strcmp (bar->priv->last_completion, current_prefix) == 0)
+            {
+              GtkAdjustment *vadj = gtk_scrolled_window_get_vadjustment (bar->priv->completion_scroller);
+              int viewport_height = gtk_widget_get_allocated_height (viewport);
+              int y = gtk_adjustment_get_value (vadj);
+              int max = gtk_adjustment_get_upper (vadj);
+
+              y += viewport_height;
+              if (y >= max)
+                y = 0;
+
+              gtk_adjustment_set_value (vadj, y);
+            }
+          else
+            {
+              g_clear_pointer (&bar->priv->last_completion, g_free);
+
+              manager = gb_workbench_get_command_manager (workbench);
+              completions = gb_command_manager_complete (manager, current_prefix);
+
+              expanded_prefix = find_longest_common_prefix (completions);
+
+              if (strlen (expanded_prefix) > strlen (current_prefix))
+                {
+                  gtk_widget_hide (GTK_WIDGET (bar->priv->completion_scroller));
+                  gtk_editable_insert_text (editable, expanded_prefix + strlen (current_prefix), -1, &pos);
+                  gtk_editable_set_position (editable, pos);
+                }
+              else if (g_strv_length (completions) > 1)
+                {
+                  gint wrapped_height = 0;
+                  bar->priv->last_completion = g_strdup (current_prefix);
+
+                  gtk_widget_show (GTK_WIDGET (bar->priv->completion_scroller));
+                  gtk_container_foreach (GTK_CONTAINER (bar->priv->flow_box),
+                                         (GtkCallback)gtk_widget_destroy, NULL);
+
+
+                  gtk_flow_box_set_min_children_per_line (bar->priv->flow_box, N_COMPLETION_COLUMS);
+                  gtk_flow_box_set_max_children_per_line (bar->priv->flow_box, N_COMPLETION_COLUMS);
+
+                  for (i = 0; completions[i] != NULL; i++)
+                    {
+                      GtkWidget *label;
+                      char *s;
+
+                      label = gtk_label_new ("");
+                      s = g_strdup_printf ("<b>%s</b>%s", current_prefix, completions[i] + strlen 
(current_prefix));
+                      gtk_label_set_markup (GTK_LABEL (label), s);
+                      g_free (s);
+
+                      gtk_container_add (GTK_CONTAINER (bar->priv->flow_box), label);
+                      gtk_widget_show (label);
+
+                      if (i == N_COMPLETION_COLUMS * N_UNSCROLLED_COMPLETION_ROWS - 1)
+                        gtk_widget_get_preferred_height (GTK_WIDGET (bar->priv->flow_box), &wrapped_height, 
NULL);
+                    }
+
+                  if (i < N_COMPLETION_COLUMS * N_UNSCROLLED_COMPLETION_ROWS)
+                    {
+                      gtk_widget_set_size_request (GTK_WIDGET (bar->priv->completion_scroller), -1, -1);
+                      gtk_scrolled_window_set_policy (bar->priv->completion_scroller,
+                                                      GTK_POLICY_NEVER, GTK_POLICY_NEVER);
+                    }
+                  else
+                    {
+                      gtk_widget_set_size_request (GTK_WIDGET (bar->priv->completion_scroller), -1, 
wrapped_height);
+                      gtk_scrolled_window_set_policy (bar->priv->completion_scroller,
+                                                      GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+                    }
+                }
+              else
+                gtk_widget_hide (GTK_WIDGET (bar->priv->completion_scroller));
+
+              g_free (expanded_prefix);
+
+              g_strfreev (completions);
+            }
+
+          g_free (current_prefix);
+
+          return TRUE;
+        }
+    }
+
   return GDK_EVENT_PROPAGATE;
 }
 
@@ -259,6 +409,10 @@ gb_command_bar_constructed (GObject *object)
 static void
 gb_command_bar_finalize (GObject *object)
 {
+  GbCommandBar *bar = (GbCommandBar *)object;
+
+  g_clear_pointer (&bar->priv->last_completion, g_free);
+
   G_OBJECT_CLASS (gb_command_bar_parent_class)->finalize (object);
 }
 
@@ -280,6 +434,8 @@ gb_command_bar_class_init (GbCommandBarClass *klass)
   gtk_widget_class_bind_template_child_private (widget_class, GbCommandBar, list_box);
   gtk_widget_class_bind_template_child_private (widget_class, GbCommandBar, scroller);
   gtk_widget_class_bind_template_child_private (widget_class, GbCommandBar, result_size_group);
+  gtk_widget_class_bind_template_child_private (widget_class, GbCommandBar, completion_scroller);
+  gtk_widget_class_bind_template_child_private (widget_class, GbCommandBar, flow_box);
 }
 
 static void
diff --git a/src/resources/ui/gb-command-bar.ui b/src/resources/ui/gb-command-bar.ui
index 7c9f4c1..e52362b 100644
--- a/src/resources/ui/gb-command-bar.ui
+++ b/src/resources/ui/gb-command-bar.ui
@@ -30,7 +30,24 @@
         <child>
           <object class="GtkSeparator" id="hsep1">
             <property name="orientation">horizontal</property>
+            <property name="visible">True</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkScrolledWindow" id="completion_scroller">
+            <property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
             <property name="visible">False</property>
+            <property name="hexpand">True</property>
+            <property name="vexpand">False</property>
+            <child>
+              <object class="GtkFlowBox" id="flow_box">
+                <property name="visible">True</property>
+                <property name="column-spacing">8</property>
+                <property name="halign">start</property>
+                <property name="hexpand">False</property>
+                <property name="selection_mode">GTK_SELECTION_NONE</property>
+              </object>
+            </child>
           </object>
         </child>
         <child>


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