[gnome-builder] prefs: reflow preference groups



commit b3e7d94d2afd6cbc64f845750100bd579243047f
Author: Christian Hergert <chergert redhat com>
Date:   Fri Dec 4 19:16:24 2015 -0800

    prefs: reflow preference groups
    
    This follows the updated design a bit closer, allowing for more efficient
    use of larger screens (the typical case).

 data/ui/ide-preferences-group.ui                   |    1 +
 data/ui/ide-preferences-page.ui                    |   14 +-
 libide/Makefile.am                                 |    2 +
 libide/preferences/ide-preferences-builtin.c       |   14 +-
 libide/preferences/ide-preferences-flow-box.c      |  331 ++++++++++++++++++++
 libide/preferences/ide-preferences-flow-box.h      |   32 ++
 libide/preferences/ide-preferences-group-private.h |   15 +
 libide/preferences/ide-preferences-group.c         |   27 +-
 libide/preferences/ide-preferences-group.h         |    5 +-
 libide/preferences/ide-preferences-page.c          |   14 +-
 10 files changed, 410 insertions(+), 45 deletions(-)
---
diff --git a/data/ui/ide-preferences-group.ui b/data/ui/ide-preferences-group.ui
index 3a78994..04aac85 100644
--- a/data/ui/ide-preferences-group.ui
+++ b/data/ui/ide-preferences-group.ui
@@ -3,6 +3,7 @@
   <!-- interface-requires gtk+ 3.18 -->
   <template class="IdePreferencesGroup" parent="GtkBin">
     <property name="vexpand">false</property>
+    <property name="halign">fill</property>
     <child>
       <object class="GtkBox">
         <property name="orientation">vertical</property>
diff --git a/data/ui/ide-preferences-page.ui b/data/ui/ide-preferences-page.ui
index 61f02bb..89a5e20 100644
--- a/data/ui/ide-preferences-page.ui
+++ b/data/ui/ide-preferences-page.ui
@@ -4,21 +4,11 @@
   <template class="IdePreferencesPage" parent="GtkBin">
     <child>
       <object class="GtkScrolledWindow">
+        <property name="hscrollbar-policy">never</property>
         <property name="visible">true</property>
         <child>
-          <object class="EggCenteringBin">
-            <property name="max-width-request">550</property>
+          <object class="IdePreferencesFlowBox" id="box">
             <property name="visible">true</property>
-            <child>
-              <object class="GtkBox" id="box">
-                <property name="halign">center</property>
-                <property name="margin">24</property>
-                <property name="orientation">vertical</property>
-                <property name="spacing">24</property>
-                <property name="visible">true</property>
-                <property name="width-request">550</property>
-              </object>
-            </child>
           </object>
         </child>
       </object>
diff --git a/libide/Makefile.am b/libide/Makefile.am
index 599bfef..bb19042 100644
--- a/libide/Makefile.am
+++ b/libide/Makefile.am
@@ -335,6 +335,8 @@ libide_1_0_la_SOURCES = \
        preferences/ide-preferences-bin-private.h \
        preferences/ide-preferences-entry.c \
        preferences/ide-preferences-entry.h \
+       preferences/ide-preferences-flow-box.c \
+       preferences/ide-preferences-flow-box.h \
        preferences/ide-preferences-font-button.c \
        preferences/ide-preferences-font-button.h \
        preferences/ide-preferences-group.c \
diff --git a/libide/preferences/ide-preferences-builtin.c b/libide/preferences/ide-preferences-builtin.c
index 1cb9348..4d5e5aa 100644
--- a/libide/preferences/ide-preferences-builtin.c
+++ b/libide/preferences/ide-preferences-builtin.c
@@ -69,13 +69,11 @@ ide_preferences_builtin_register_appearance (IdePreferences *preferences)
 
   ide_preferences_add_page (preferences, "appearance", _("Appearance"), 0);
 
-  ide_preferences_add_list_group (preferences, "appearance", "basic", NULL, 0);
+  ide_preferences_add_list_group (preferences, "appearance", "basic", _("Themes"), 0);
   ide_preferences_add_switch (preferences, "appearance", "basic", "org.gnome.builder", "night-mode", NULL, 
NULL, _("Dark Theme"), _("Whether Builder should use a dark theme"), _("dark theme"), 0);
+  ide_preferences_add_switch (preferences, "appearance", "basic", "org.gnome.builder.editor", 
"show-grid-lines", NULL, NULL, _("Grid Pattern"), _("Display a grid pattern underneath source code"), NULL, 
0);
 
-  ide_preferences_add_list_group (preferences, "appearance", "font", _("Font"), 100);
-  ide_preferences_add_font_button (preferences, "appearance", "font", "org.gnome.builder.editor", 
"font-name", _("Editor"), _("editor font monospace"), 0);
-
-  ide_preferences_add_list_group (preferences, "appearance", "schemes", _("Themes"), 200);
+  ide_preferences_add_list_group (preferences, "appearance", "schemes", NULL, 100);
 
   manager = gtk_source_style_scheme_manager_get_default ();
   scheme_ids = gtk_source_style_scheme_manager_get_scheme_ids (manager);
@@ -93,8 +91,8 @@ ide_preferences_builtin_register_appearance (IdePreferences *preferences)
       ide_preferences_add_radio (preferences, "appearance", "schemes", "org.gnome.builder.editor", 
"style-scheme-name", NULL, variant_str, title, NULL, title, i);
     }
 
-  ide_preferences_add_list_group (preferences, "appearance", "background", NULL, 300);
-  ide_preferences_add_switch (preferences, "appearance", "background", "org.gnome.builder.editor", 
"show-grid-lines", NULL, NULL, _("Grid Pattern"), _("Display a grid pattern underneath source code"), NULL, 
0);
+  ide_preferences_add_list_group (preferences, "appearance", "font", _("Font"), 200);
+  ide_preferences_add_font_button (preferences, "appearance", "font", "org.gnome.builder.editor", 
"font-name", _("Editor"), _("editor font monospace"), 0);
 }
 
 static void
@@ -117,7 +115,7 @@ ide_preferences_builtin_register_editor (IdePreferences *preferences)
 {
   ide_preferences_add_page (preferences, "editor", _("Editor"), 100);
 
-  ide_preferences_add_list_group (preferences, "editor", "position", NULL, 0);
+  ide_preferences_add_list_group (preferences, "editor", "position", _("Cursor"), 0);
   ide_preferences_add_switch (preferences, "editor", "position", "org.gnome.builder.editor", 
"restore-insert-mark", NULL, NULL, _("Restore cursor position"), _("Restore cursor position when a file is 
reopened"), NULL, 0);
   ide_preferences_add_spin_button (preferences, "editor", "position", "org.gnome.builder.editor", 
"scroll-offset", NULL, _("Scroll Offset"), _("Minimum number of lines to keep above and below the cursor"), 
NULL, 10);
 
diff --git a/libide/preferences/ide-preferences-flow-box.c b/libide/preferences/ide-preferences-flow-box.c
new file mode 100644
index 0000000..bd2ebc8
--- /dev/null
+++ b/libide/preferences/ide-preferences-flow-box.c
@@ -0,0 +1,331 @@
+/* ide-preferences-flow-box.c
+ *
+ * Copyright (C) 2015 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/>.
+ */
+
+#include "ide-preferences-flow-box.h"
+#include "ide-preferences-group.h"
+#include "ide-preferences-group-private.h"
+
+/**
+ * SECTION:ide-preferences-flow-box
+ *
+ * This is a custom container similar to flow box, but not quiet. It is
+ * meant to have multiple columns with preference items in it. We will
+ * try to reflow the groups based on a couple hueristics to make things
+ * more pleasant to look at.
+ */
+
+#define BORDER_WIDTH   32
+#define COLUMN_WIDTH   500
+#define COLUMN_SPACING 32
+#define ROW_SPACING    24
+
+struct _IdePreferencesFlowBox
+{
+  GtkBox     parent_instance;
+
+  guint      needs_reflow : 1;
+  guint      max_columns : 3;
+
+  GPtrArray *columns;
+  GList     *groups;
+};
+
+G_DEFINE_TYPE (IdePreferencesFlowBox, ide_preferences_flow_box, GTK_TYPE_BOX)
+
+static gint
+compare_group (gconstpointer a,
+               gconstpointer b)
+{
+  const IdePreferencesGroup *group_a = a;
+  const IdePreferencesGroup *group_b = b;
+
+  return group_a->priority - group_b->priority;
+}
+
+static gint
+find_next_column (IdePreferencesFlowBox *self,
+                  IdePreferencesGroup   *group,
+                  gint                   last_column)
+{
+  gint i;
+  struct {
+    gint column;
+    gint height;
+  } shortest = { -1, 0 };
+
+  g_assert (IDE_IS_PREFERENCES_FLOW_BOX (self));
+  g_assert (IDE_IS_PREFERENCES_GROUP (group));
+  g_assert (last_column >= -1);
+  g_assert (self->columns->len > 0);
+
+  if (ide_preferences_group_get_title (group) == NULL)
+    return last_column == -1 ? 0 : last_column;
+
+  for (i = 0; i < self->columns->len; i++)
+    {
+      GtkBox *column = g_ptr_array_index (self->columns, i);
+      gint height;
+
+      gtk_widget_get_preferred_height (GTK_WIDGET (column), NULL, &height);
+
+      if (shortest.column == -1 || height < shortest.height)
+        {
+          shortest.height = height;
+          shortest.column = i;
+        }
+    }
+
+  return shortest.column;
+}
+
+static void
+ide_preferences_flow_box_reflow (IdePreferencesFlowBox *self)
+{
+  GtkAllocation alloc;
+  const GList *iter;
+  gint n_columns;
+  gint width;
+  gint spacing;
+  gint border_width;
+  gint last_column = -1;
+
+  g_assert (IDE_IS_PREFERENCES_FLOW_BOX (self));
+
+  self->needs_reflow = FALSE;
+
+  gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
+
+  /*
+   * Remove all groups from their containers, and add an extra reference
+   * until we have added the items back to a new box.
+   */
+  for (iter = self->groups; iter; iter = iter->next)
+    {
+      GtkWidget *group = iter->data;
+      GtkWidget *parent = gtk_widget_get_parent (group);
+
+      g_object_ref (group);
+
+      if (parent != NULL)
+        gtk_container_remove (GTK_CONTAINER (parent), group);
+    }
+
+  /*
+   * Now remove the containers.
+   */
+  while (self->columns->len > 0)
+    {
+      GtkWidget *box = g_ptr_array_index (self->columns, self->columns->len - 1);
+
+      gtk_container_remove (GTK_CONTAINER (self), box);
+      g_ptr_array_remove_index (self->columns, self->columns->len - 1);
+    }
+
+  g_assert (self->columns->len == 0);
+
+  /*
+   * Determine the number of containers we need based on column width and
+   * allocation width, taking border_width and spacing into account.
+   */
+  n_columns = 1;
+  spacing = gtk_box_get_spacing (GTK_BOX (self));
+  border_width = gtk_container_get_border_width (GTK_CONTAINER (self));
+  width = (border_width * 2) + COLUMN_WIDTH;
+
+  while (TRUE)
+    {
+      width += spacing;
+      width += COLUMN_WIDTH;
+
+      if (width <= alloc.width)
+        {
+          n_columns++;
+          continue;
+        }
+
+      break;
+    }
+
+  /*
+   * Limit ourselves to our max columns.
+   */
+  n_columns = MIN (n_columns, self->max_columns);
+
+  /*
+   * Add those columns, we'll add items to them after they are configured.
+   */
+  while (self->columns->len < n_columns)
+    {
+      GtkWidget *column;
+
+      column = g_object_new (GTK_TYPE_BOX,
+                             "hexpand", FALSE,
+                             "orientation", GTK_ORIENTATION_VERTICAL,
+                             "spacing", ROW_SPACING,
+                             "visible", TRUE,
+                             "width-request", COLUMN_WIDTH,
+                             NULL);
+      GTK_CONTAINER_CLASS (ide_preferences_flow_box_parent_class)->add (GTK_CONTAINER (self), column);
+      g_ptr_array_add (self->columns, column);
+    }
+
+  /*
+   * Now go through adding groups to columns based on the column with the
+   * shortest height. If the group does not have a title, it should be
+   * placed in the same column as the previous group.
+   */
+  for (iter = self->groups; iter; iter = iter->next)
+    {
+      IdePreferencesGroup *group = iter->data;
+      GtkBox *box;
+      gint column;
+
+      column = find_next_column (self, group, last_column);
+      g_assert (column >= 0);
+      g_assert (column < self->columns->len);
+
+      box = g_ptr_array_index (self->columns, column);
+      g_assert (GTK_IS_BOX (box));
+
+      gtk_container_add (GTK_CONTAINER (box), GTK_WIDGET (group));
+
+      last_column = column;
+    }
+
+  /*
+   * Now we can drop our extra reference to the groups.
+   */
+  g_list_foreach (self->groups, (GFunc)g_object_unref, NULL);
+}
+
+static void
+ide_preferences_flow_box_add_group (IdePreferencesFlowBox *self,
+                                    IdePreferencesGroup   *group)
+{
+  g_assert (IDE_IS_PREFERENCES_FLOW_BOX (self));
+  g_assert (IDE_IS_PREFERENCES_GROUP (group));
+
+  self->groups = g_list_insert_sorted (self->groups, group, compare_group);
+  self->needs_reflow = TRUE;
+
+  gtk_widget_queue_allocate (GTK_WIDGET (self));
+}
+
+static void
+ide_preferences_flow_box_add (GtkContainer *container,
+                              GtkWidget    *child)
+{
+  IdePreferencesFlowBox *self = (IdePreferencesFlowBox *)container;
+
+  g_assert (IDE_IS_PREFERENCES_FLOW_BOX (self));
+  g_assert (IDE_IS_PREFERENCES_GROUP (child));
+
+  ide_preferences_flow_box_add_group (self, IDE_PREFERENCES_GROUP (child));
+}
+
+static void
+ide_preferences_flow_box_size_allocate (GtkWidget     *widget,
+                                        GtkAllocation *allocation)
+{
+  IdePreferencesFlowBox *self = (IdePreferencesFlowBox *)widget;
+  gint border_width;
+  gint min_width;
+  gint spacing;
+
+  g_assert (IDE_IS_PREFERENCES_FLOW_BOX (self));
+  g_assert (allocation != NULL);
+
+  border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
+  spacing = gtk_box_get_spacing (GTK_BOX (self));
+
+  min_width = (border_width * 2)
+            + (spacing * (self->columns->len - 1))
+            + (COLUMN_WIDTH * self->columns->len);
+
+  /*
+   * If we need to shrink our number of columns, or add another column,
+   * lets go through the reflow state.
+   */
+  if ((allocation->width < min_width) ||
+      ((allocation->width >= (min_width + spacing + COLUMN_WIDTH)) &&
+       (self->columns->len < self->max_columns)))
+    self->needs_reflow = TRUE;
+
+  GTK_WIDGET_CLASS (ide_preferences_flow_box_parent_class)->size_allocate (widget, allocation);
+
+  if (self->needs_reflow)
+    ide_preferences_flow_box_reflow (self);
+}
+
+static void
+ide_preferences_flow_box_get_preferred_width (GtkWidget *widget,
+                                              gint      *min_width,
+                                              gint      *nat_width)
+{
+  gint border_width;
+
+  g_assert (GTK_IS_WIDGET (widget));
+  g_assert (min_width != NULL);
+  g_assert (nat_width != NULL);
+
+  GTK_WIDGET_CLASS (ide_preferences_flow_box_parent_class)->get_preferred_width (widget, min_width, 
nat_width);
+
+  border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
+
+  *min_width = (border_width * 2) + COLUMN_WIDTH;
+
+  if (*nat_width < *min_width)
+    *nat_width = *min_width;
+}
+
+static void
+ide_preferences_flow_box_finalize (GObject *object)
+{
+  IdePreferencesFlowBox *self = (IdePreferencesFlowBox *)object;
+
+  g_clear_pointer (&self->columns, g_ptr_array_unref);
+  g_clear_pointer (&self->groups, g_list_free);
+
+  G_OBJECT_CLASS (ide_preferences_flow_box_parent_class)->finalize (object);
+}
+
+static void
+ide_preferences_flow_box_class_init (IdePreferencesFlowBoxClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+
+  object_class->finalize = ide_preferences_flow_box_finalize;
+
+  widget_class->size_allocate = ide_preferences_flow_box_size_allocate;
+  widget_class->get_preferred_width = ide_preferences_flow_box_get_preferred_width;
+
+  container_class->add = ide_preferences_flow_box_add;
+}
+
+static void
+ide_preferences_flow_box_init (IdePreferencesFlowBox *self)
+{
+  self->columns = g_ptr_array_new ();
+  self->max_columns = 2;
+
+  gtk_widget_set_hexpand (GTK_WIDGET (self), TRUE);
+  gtk_container_set_border_width (GTK_CONTAINER (self), BORDER_WIDTH);
+  gtk_box_set_spacing (GTK_BOX (self), COLUMN_SPACING);
+}
diff --git a/libide/preferences/ide-preferences-flow-box.h b/libide/preferences/ide-preferences-flow-box.h
new file mode 100644
index 0000000..49e763e
--- /dev/null
+++ b/libide/preferences/ide-preferences-flow-box.h
@@ -0,0 +1,32 @@
+/* ide-preferences-flow-box.h
+ *
+ * Copyright (C) 2015 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 IDE_PREFERENCES_FLOW_BOX_H
+#define IDE_PREFERENCES_FLOW_BOX_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_PREFERENCES_FLOW_BOX (ide_preferences_flow_box_get_type())
+
+G_DECLARE_FINAL_TYPE (IdePreferencesFlowBox, ide_preferences_flow_box, IDE, PREFERENCES_FLOW_BOX, GtkBox)
+
+G_END_DECLS
+
+#endif /* IDE_PREFERENCES_FLOW_BOX_H */
diff --git a/libide/preferences/ide-preferences-group-private.h 
b/libide/preferences/ide-preferences-group-private.h
index 50d2c4d..2a52529 100644
--- a/libide/preferences/ide-preferences-group-private.h
+++ b/libide/preferences/ide-preferences-group-private.h
@@ -24,6 +24,21 @@
 
 G_BEGIN_DECLS
 
+struct _IdePreferencesGroup
+{
+  GtkBin      parent_instance;
+
+  gint        priority;
+  guint       is_list : 1;
+
+  GtkLabel   *title;
+  GtkBox     *box;
+  GtkListBox *list_box;
+  GtkFrame   *list_box_frame;
+
+  GPtrArray  *widgets;
+};
+
 void  _ide_preferences_group_set_map  (IdePreferencesGroup *self,
                                        GHashTable          *map);
 guint _ide_preferences_group_refilter (IdePreferencesGroup *self,
diff --git a/libide/preferences/ide-preferences-group.c b/libide/preferences/ide-preferences-group.c
index d826d17..46a37d7 100644
--- a/libide/preferences/ide-preferences-group.c
+++ b/libide/preferences/ide-preferences-group.c
@@ -21,21 +21,6 @@
 #include "ide-preferences-group.h"
 #include "ide-preferences-group-private.h"
 
-struct _IdePreferencesGroup
-{
-  GtkBin      parent_instance;
-
-  gint        priority;
-  guint       is_list : 1;
-
-  GtkLabel   *title;
-  GtkBox     *box;
-  GtkListBox *list_box;
-  GtkFrame   *list_box_frame;
-
-  GPtrArray  *widgets;
-};
-
 G_DEFINE_TYPE (IdePreferencesGroup, ide_preferences_group, GTK_TYPE_BIN)
 
 enum {
@@ -74,6 +59,18 @@ ide_preferences_group_row_activated (IdePreferencesGroup *self,
     gtk_widget_activate (child);
 }
 
+const gchar *
+ide_preferences_group_get_title (IdePreferencesGroup *self)
+{
+  const gchar *title;
+
+  g_return_val_if_fail (IDE_IS_PREFERENCES_GROUP (self), NULL);
+
+  title = gtk_label_get_label (self->title);
+
+  return (!title || !*title) ? NULL : title;
+}
+
 static void
 ide_preferences_group_finalize (GObject *object)
 {
diff --git a/libide/preferences/ide-preferences-group.h b/libide/preferences/ide-preferences-group.h
index c4b0be9..e4fe5f8 100644
--- a/libide/preferences/ide-preferences-group.h
+++ b/libide/preferences/ide-preferences-group.h
@@ -27,8 +27,9 @@ G_BEGIN_DECLS
 
 G_DECLARE_FINAL_TYPE (IdePreferencesGroup, ide_preferences_group, IDE, PREFERENCES_GROUP, GtkBin)
 
-void ide_preferences_group_add (IdePreferencesGroup *self,
-                                GtkWidget           *widget);
+void         ide_preferences_group_add       (IdePreferencesGroup *self,
+                                              GtkWidget           *widget);
+const gchar *ide_preferences_group_get_title (IdePreferencesGroup *self);
 
 G_END_DECLS
 
diff --git a/libide/preferences/ide-preferences-page.c b/libide/preferences/ide-preferences-page.c
index c2cf64e..5bb499b 100644
--- a/libide/preferences/ide-preferences-page.c
+++ b/libide/preferences/ide-preferences-page.c
@@ -18,6 +18,7 @@
 
 #include <glib/gi18n.h>
 
+#include "ide-preferences-flow-box.h"
 #include "ide-preferences-group.h"
 #include "ide-preferences-group-private.h"
 #include "ide-preferences-page.h"
@@ -25,12 +26,12 @@
 
 struct _IdePreferencesPage
 {
-  GtkBin      parent_instance;
+  GtkBin                 parent_instance;
 
-  gint        priority;
+  gint                   priority;
 
-  GtkBox     *box;
-  GHashTable *groups_by_name;
+  IdePreferencesFlowBox *box;
+  GHashTable            *groups_by_name;
 };
 
 enum {
@@ -129,7 +130,6 @@ ide_preferences_page_add_group (IdePreferencesPage  *self,
                                 IdePreferencesGroup *group)
 {
   gchar *name = NULL;
-  gint position = -1;
 
   g_return_if_fail (IDE_IS_PREFERENCES_PAGE (self));
   g_return_if_fail (IDE_IS_PREFERENCES_GROUP (group));
@@ -144,9 +144,7 @@ ide_preferences_page_add_group (IdePreferencesPage  *self,
 
   g_hash_table_insert (self->groups_by_name, name, group);
 
-  gtk_container_add_with_properties (GTK_CONTAINER (self->box), GTK_WIDGET (group),
-                                     "position", position,
-                                     NULL);
+  gtk_container_add (GTK_CONTAINER (self->box), GTK_WIDGET (group));
 }
 
 IdePreferencesGroup *


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