[glade] * plugins/gtk+/Makefile.am, plugins/gtk+/glade-string-list.[ch]: Added Boxed type and GladeEdito



commit e086ae995cc0253e6cc4f0b28a1fa368d3cf0970
Author: Tristan Van Berkom <tristan van berkom gmail com>
Date:   Wed Jan 26 21:28:20 2011 +0900

    	* plugins/gtk+/Makefile.am, plugins/gtk+/glade-string-list.[ch]: Added Boxed type
    	  and GladeEditorProperty to manage/edit a list of strings that can be translatable.
    	  This property editor uses a treeview and supports reordering of rows with Drag'n'Drop
    	  as well as deletion of rows using the 'Delete' key. Every row can have individual
    	  i18n information set via the common i18n dialog.
    
    	* plugins/gtk+/glade-gtk.c, plugins/gtk+/gtk+.xml.in: Add GtkComboBoxText support
    	  to Glade and use the GladeStringList boxed type to edit the list of translatable
    	  items.

 ChangeLog                        |   58 +++--
 plugins/gtk+/Makefile.am         |    5 +-
 plugins/gtk+/glade-gtk.c         |  216 +++++++++++++
 plugins/gtk+/glade-string-list.c |  614 ++++++++++++++++++++++++++++++++++++++
 plugins/gtk+/glade-string-list.h |   43 +++
 plugins/gtk+/gtk+.xml.in         |   23 ++
 6 files changed, 934 insertions(+), 25 deletions(-)
---
diff --git a/ChangeLog b/ChangeLog
index 0dbf38b..1096a8d 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,28 +1,40 @@
+2011-01-26  Tristan Van Berkom <tristanvb openismus com>
+
+	* plugins/gtk+/Makefile.am, plugins/gtk+/glade-string-list.[ch]: Added Boxed type
+	  and GladeEditorProperty to manage/edit a list of strings that can be translatable.
+	  This property editor uses a treeview and supports reordering of rows with Drag'n'Drop
+	  as well as deletion of rows using the 'Delete' key. Every row can have individual
+	  i18n information set via the common i18n dialog.
+
+	* plugins/gtk+/glade-gtk.c, plugins/gtk+/gtk+.xml.in: Add GtkComboBoxText support
+	  to Glade and use the GladeStringList boxed type to edit the list of translatable
+	  items.
+
 2011-01-25  Juan Pablo Ugarte <juanpablougarte gmail com>
 
-  * gladeui/glade-design-layout.[ch]:
-    o Made GladeDesignLayout an offscreen container, all its children are redirected offscreen
-      and composited later on. This allow us drawing "pretty" selections over widgets.
-    o added new function derived from glade_design_layout_widget_event(),
-      glade_design_layout_do_event() to marshal events from the override class handler
-    o added glade_design_layout_selection_set()
-    o reworked cursors pointer in an array to ease setting them.
-    o replaced glade_design_layout_get_pointer_region () with gdl_get_activity_from_pointer()
-    o removed call to gtk_widget_size_allocate() in glade_design_layout_update_child()
-      fixes rendering glith while resizing
-
-  * gladeui/glade-design-view.c: set layout selection on project's selection-changed signal
-    
-  * gladeui/glade-app.[ch]:
-    o added glade_app_do_event()
-    o set custom gdk event handler to marshal event to GladeDesignLayouts using
-      glade_design_layout_do_event()
-    
-  * gladeui/glade-utils.[ch]: removed unused function glade_util_draw_selection_nodes()
-
-  * gladeui/glade-widget.[ch]:
-    o moved IS_GLADE_WIDGET_EVENT macro to header.
-    o replaced glade_widget_connect_signal_handlers() with glade_widget_add_events()
+	* gladeui/glade-design-layout.[ch]:
+	o Made GladeDesignLayout an offscreen container, all its children are redirected offscreen
+	  and composited later on. This allow us drawing "pretty" selections over widgets.
+	o added new function derived from glade_design_layout_widget_event(),
+	  glade_design_layout_do_event() to marshal events from the override class handler
+	o added glade_design_layout_selection_set()
+	o reworked cursors pointer in an array to ease setting them.
+	o replaced glade_design_layout_get_pointer_region () with gdl_get_activity_from_pointer()
+	o removed call to gtk_widget_size_allocate() in glade_design_layout_update_child()
+	  fixes rendering glith while resizing
+
+	* gladeui/glade-design-view.c: set layout selection on project's selection-changed signal
+
+	* gladeui/glade-app.[ch]:
+	o added glade_app_do_event()
+	o set custom gdk event handler to marshal event to GladeDesignLayouts using
+	  glade_design_layout_do_event()
+
+	* gladeui/glade-utils.[ch]: removed unused function glade_util_draw_selection_nodes()
+
+	* gladeui/glade-widget.[ch]:
+	o moved IS_GLADE_WIDGET_EVENT macro to header.
+	o replaced glade_widget_connect_signal_handlers() with glade_widget_add_events()
 
 2011-01-24  Tristan Van Berkom <tristanvb openismus com>
 
diff --git a/plugins/gtk+/Makefile.am b/plugins/gtk+/Makefile.am
index 833cec8..8d45ef5 100644
--- a/plugins/gtk+/Makefile.am
+++ b/plugins/gtk+/Makefile.am
@@ -25,7 +25,7 @@ libgladegtk_la_SOURCES     = glade-gtk.c glade-fixed.c glade-accels.c glade-attr
 	glade-icon-sources.c glade-button-editor.c glade-tool-button-editor.c glade-image-editor.c \
 	glade-image-item-editor.c glade-icon-factory-editor.c glade-store-editor.c glade-label-editor.c \
 	glade-cell-renderer-editor.c glade-treeview-editor.c glade-entry-editor.c glade-activatable-editor.c \
-	glade-tool-item-group-editor.c
+	glade-tool-item-group-editor.c glade-string-list.c
 
 libgladegtk_la_LDFLAGS     = -module -avoid-version $(AM_LDFLAGS)
 libgladegtk_la_LIBADD      = $(libgladeui) $(GTK_LIBS)
@@ -35,7 +35,8 @@ libgladegtkinclude_HEADERS = glade-gtk.h glade-accels.h glade-attributes.h glade
 	glade-text-button.h glade-icon-sources.h glade-button-editor.h \
 	glade-tool-button-editor.h glade-image-editor.h glade-image-item-editor.h glade-icon-factory-editor.h \
 	glade-store-editor.h glade-label-editor.h glade-cell-renderer-editor.h glade-treeview-editor.h \
-	glade-entry-editor.h glade-activatable-editor.h glade-fixed.h glade-tool-item-group-editor.h
+	glade-entry-editor.h glade-activatable-editor.h glade-fixed.h glade-tool-item-group-editor.h \
+	glade-string-list.h
 
 
 if PLATFORM_WIN32
diff --git a/plugins/gtk+/glade-gtk.c b/plugins/gtk+/glade-gtk.c
index 2a5a493..128ddb2 100644
--- a/plugins/gtk+/glade-gtk.c
+++ b/plugins/gtk+/glade-gtk.c
@@ -42,6 +42,7 @@
 #include "glade-entry-editor.h"
 #include "glade-activatable-editor.h"
 #include "glade-tool-item-group-editor.h"
+#include "glade-string-list.h"
 #include "glade-fixed.h"
 
 #include <gladeui/glade-editor-property.h>
@@ -8672,6 +8673,11 @@ glade_gtk_combo_box_post_create (GladeWidgetAdaptor * adaptor,
     glade_widget_adaptor_create_internal
         (widget, G_OBJECT (gtk_bin_get_child (GTK_BIN (object))),
          "entry", "comboboxentry", FALSE, reason);
+
+  /* For some reason combos need a kick in the but in order to show
+   * up properly after loading. */
+  gtk_widget_hide (GTK_WIDGET (object));
+  gtk_widget_show (GTK_WIDGET (object));
 }
 
 void
@@ -8733,6 +8739,216 @@ glade_gtk_combo_box_get_internal_child (GladeWidgetAdaptor * adaptor,
   return child;
 }
 
+/* ----------------------------- GtkComboBoxText ------------------------------ */
+#define GLADE_TAG_ITEMS  "items"
+#define GLADE_TAG_ITEM   "item"
+
+void
+glade_gtk_combo_box_text_post_create (GladeWidgetAdaptor *adaptor,
+				      GObject            *object, 
+				      GladeCreateReason   reason)
+{
+  GladeWidget *gwidget;
+
+  /* Chain Up */
+  GWA_GET_CLASS (GTK_TYPE_COMBO_BOX)->post_create (adaptor, object, reason);
+
+  /* No editor, no model, no cells on a GtkComboBoxText, just the items. */
+  gwidget = glade_widget_get_from_gobject (object);
+  glade_widget_set_action_visible (gwidget, "launch_editor", FALSE);
+}
+
+GladeEditorProperty *
+glade_gtk_combo_box_text_create_eprop (GladeWidgetAdaptor * adaptor,
+				       GladePropertyClass * klass, 
+				       gboolean use_command)
+{
+  GladeEditorProperty *eprop;
+  GParamSpec          *pspec;
+
+  pspec = glade_property_class_get_pspec (klass);
+
+  if (pspec->value_type == GLADE_TYPE_STRING_LIST)
+    {
+      eprop = glade_eprop_string_list_new (klass, use_command, TRUE);
+    }
+  else
+    eprop = GWA_GET_CLASS
+        (GTK_TYPE_WIDGET)->create_eprop (adaptor, klass, use_command);
+
+  return eprop;
+}
+
+gchar *
+glade_gtk_combo_box_text_string_from_value (GladeWidgetAdaptor * adaptor,
+					    GladePropertyClass * klass,
+					    const GValue * value)
+{
+  GParamSpec          *pspec;
+
+  pspec = glade_property_class_get_pspec (klass);
+
+  if (pspec->value_type == GLADE_TYPE_STRING_LIST)
+    {
+      GList *list = g_value_get_boxed (value);
+
+      return glade_string_list_to_string (list);
+    }
+  else
+    return GWA_GET_CLASS
+        (GTK_TYPE_COMBO_BOX)->string_from_value (adaptor, klass, value);
+}
+
+void
+glade_gtk_combo_box_text_set_property (GladeWidgetAdaptor * adaptor,
+				       GObject * object,
+				       const gchar * id, const GValue * value)
+{
+  if (!strcmp (id, "glade-items"))
+    {
+      GList *string_list, *l;
+      GladeString *string;
+      gint active;
+
+      string_list = g_value_get_boxed (value);
+
+      active = gtk_combo_box_get_active (GTK_COMBO_BOX (object));
+
+      /* Update comboboxtext items */
+      gtk_combo_box_text_remove_all (GTK_COMBO_BOX_TEXT (object));
+
+      for (l = string_list; l; l = l->next)
+	{
+	  string = l->data;
+
+	  gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (object), string->string);
+	}
+
+      gtk_combo_box_set_active (GTK_COMBO_BOX (object),
+				CLAMP (active, 0, g_list_length (string_list) - 1));
+    }
+  else
+    GWA_GET_CLASS (GTK_TYPE_COMBO_BOX)->set_property (adaptor, object, id, value);
+}
+
+static void
+glade_gtk_combo_box_text_read_items (GladeWidget * widget, GladeXmlNode * node)
+{
+  GladeXmlNode *items_node;
+  GladeXmlNode *item_node;
+  GList        *string_list = NULL;
+
+  if ((items_node =
+       glade_xml_search_child (node, GLADE_TAG_ITEMS)) != NULL)
+    {
+
+      for (item_node = glade_xml_node_get_children (items_node);
+	   item_node; item_node = glade_xml_node_next (item_node))
+	{
+	  gchar *str, *comment, *context;
+	  gboolean translatable;
+
+	  if (!glade_xml_node_verify (item_node, GLADE_TAG_ITEM))
+	    continue;
+
+          if ((str = glade_xml_get_content (item_node)) == NULL)
+	    continue;
+
+	  context      = glade_xml_get_property_string (item_node, GLADE_TAG_CONTEXT);
+	  comment      = glade_xml_get_property_string (item_node, GLADE_TAG_COMMENT);
+          translatable = glade_xml_get_property_boolean (item_node, GLADE_TAG_TRANSLATABLE, FALSE);
+
+	  string_list = 
+	    glade_string_list_append (string_list,
+				      str, comment, context, translatable);
+
+	  g_free (str);
+	  g_free (context);
+	  g_free (comment);
+	}
+
+      glade_widget_property_set (widget, "glade-items", string_list);
+      glade_string_list_free (string_list);
+    }
+}
+
+void
+glade_gtk_combo_box_text_read_widget (GladeWidgetAdaptor * adaptor,
+				      GladeWidget * widget, GladeXmlNode * node)
+{
+  if (!glade_xml_node_verify (node, GLADE_XML_TAG_WIDGET))
+    return;
+
+  /* First chain up and read in all the normal properties.. */
+  GWA_GET_CLASS (GTK_TYPE_COMBO_BOX)->read_widget (adaptor, widget, node);
+
+  glade_gtk_combo_box_text_read_items (widget, node);
+}
+
+static void
+glade_gtk_combo_box_text_write_items (GladeWidget * widget,
+				      GladeXmlContext * context,
+				      GladeXmlNode * node)
+{
+  GladeXmlNode *item_node;
+  GList *string_list = NULL, *l;
+  GladeString *string;
+
+  if (!glade_widget_property_get (widget, "glade-items", &string_list) || !string_list)
+    return;
+
+  for (l = string_list; l; l = l->next)
+    {
+      string = l->data;
+
+      item_node = glade_xml_node_new (context, GLADE_TAG_ITEM);
+      glade_xml_node_append_child (node, item_node);
+
+      glade_xml_set_content (item_node, string->string);
+
+      if (string->translatable)
+        glade_xml_node_set_property_string (item_node,
+                                            GLADE_TAG_TRANSLATABLE,
+                                            GLADE_XML_TAG_I18N_TRUE);
+
+      if (string->comment)
+        glade_xml_node_set_property_string (item_node,
+                                            GLADE_TAG_COMMENT,
+                                            string->comment);
+
+      if (string->context)
+        glade_xml_node_set_property_string (item_node,
+                                            GLADE_TAG_CONTEXT,
+                                            string->context);
+    }
+}
+
+void
+glade_gtk_combo_box_text_write_widget (GladeWidgetAdaptor * adaptor,
+				       GladeWidget * widget,
+				       GladeXmlContext * context, GladeXmlNode * node)
+{
+  GladeXmlNode *attrs_node;
+
+  if (!glade_xml_node_verify (node, GLADE_XML_TAG_WIDGET))
+    return;
+
+  /* First chain up and read in all the normal properties.. */
+  GWA_GET_CLASS (GTK_TYPE_WIDGET)->write_widget (adaptor, widget, context,
+                                                 node);
+
+  attrs_node = glade_xml_node_new (context, GLADE_TAG_ITEMS);
+
+  glade_gtk_combo_box_text_write_items (widget, context, attrs_node);
+
+  if (!glade_xml_node_get_children (attrs_node))
+    glade_xml_node_delete (attrs_node);
+  else
+    glade_xml_node_append_child (node, attrs_node);
+
+}
+
+
 /* ----------------------------- GtkSpinButton ------------------------------ */
 static void
 glade_gtk_spin_button_set_adjustment (GObject * object, const GValue * value)
diff --git a/plugins/gtk+/glade-string-list.c b/plugins/gtk+/glade-string-list.c
new file mode 100644
index 0000000..4b6f016
--- /dev/null
+++ b/plugins/gtk+/glade-string-list.c
@@ -0,0 +1,614 @@
+/*
+ * glade-string-list.c - Editing support for lists of translatable strings
+ *
+ * Copyright (C) 2010 Openismus GmbH
+ *
+ * Author(s):
+ *      Tristan Van Berkom <tvb gnome org>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <config.h>
+
+#include <string.h>
+#include <stdlib.h>
+#include <gtk/gtk.h>
+#include <gladeui/glade.h>
+#include <glib/gi18n-lib.h>
+
+#include "glade-string-list.h"
+
+
+/**************************************************************
+ *            GladeStringList boxed type stuff here           *
+ **************************************************************/
+static GladeString *
+glade_string_new (const gchar *string,
+		  const gchar *comment,
+		  const gchar *context,
+		  gboolean     translatable)
+{
+  GladeString *gstring = g_slice_new0 (GladeString);
+
+  gstring->string       = g_strdup (string);
+  gstring->comment      = g_strdup (comment);
+  gstring->context      = g_strdup (context);
+  gstring->translatable = translatable;
+
+  return gstring;
+}
+
+static GladeString *
+glade_string_copy (GladeString *string)
+{
+  return glade_string_new (string->string, 
+			   string->comment, 
+			   string->context, 
+			   string->translatable);
+}
+
+static void
+glade_string_free (GladeString *string)
+{
+  g_free (string->string);
+  g_free (string->comment);
+  g_free (string->context);
+  g_slice_free (GladeString, string);
+}
+
+GList *
+glade_string_list_append (GList   *list,
+			  gchar   *string,
+			  gchar   *comment,
+			  gchar   *context,
+			  gboolean translatable)
+{
+  GladeString *gstring;
+
+  gstring = glade_string_new (string, comment, context, translatable);
+
+  return g_list_append (list, gstring);
+}
+
+GList *
+glade_string_list_copy (GList *string_list)
+{
+  GList *ret = NULL, *list;
+  GladeString *string, *copy;
+
+  for (list = string_list; list; list = list->next)
+    {
+      string = list->data;
+
+      copy = glade_string_copy (string);
+
+      ret = g_list_prepend (ret, copy);
+    }
+
+  return g_list_reverse (ret);
+}
+
+void
+glade_string_list_free (GList * string_list)
+{
+  g_list_foreach (string_list, (GFunc)glade_string_free, NULL);
+  g_list_free (string_list);
+}
+
+GType
+glade_string_list_get_type (void)
+{
+  static GType type_id = 0;
+
+  if (!type_id)
+    type_id = g_boxed_type_register_static
+        ("GladeStringList",
+         (GBoxedCopyFunc) glade_string_list_copy,
+         (GBoxedFreeFunc) glade_string_list_free);
+  return type_id;
+}
+
+
+gchar *
+glade_string_list_to_string (GList   *list)
+{
+  GString *string = g_string_new ("");
+  GList *l;
+
+  for (l = list; l; l = l->next)
+    {
+      GladeString *str = l->data;
+
+      if (l != list)
+	g_string_append_c (string, ',');
+
+      g_string_append_printf (string, "%s:%s:%s:%d", 
+			      str->string,
+			      str->comment ? str->comment : "",
+			      str->context ? str->context : "",
+			      str->translatable);
+    }
+
+  return g_string_free (string, FALSE);
+}
+
+/**************************************************************
+ *              GladeEditorProperty stuff here
+ **************************************************************/
+typedef struct
+{
+  GladeEditorProperty parent_instance;
+
+  GtkTreeModel *model;
+  GtkWidget    *view;
+
+  guint  translatable : 1;
+  guint  want_focus : 1;
+
+  guint  editing_index;
+
+  guint  changed_id;
+  guint  update_id;
+  GList *pending_string_list;
+} GladeEPropStringList;
+
+enum
+{
+  COLUMN_STRING,
+  COLUMN_INDEX,
+  COLUMN_DUMMY,
+  NUM_COLUMNS
+};
+
+GLADE_MAKE_EPROP (GladeEPropStringList, glade_eprop_string_list)
+#define GLADE_EPROP_STRING_LIST(obj)					\
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GLADE_TYPE_EPROP_STRING_LIST, GladeEPropStringList))
+
+static void
+glade_eprop_string_list_finalize (GObject * object)
+{
+  GladeEPropStringList *eprop_string_list = GLADE_EPROP_STRING_LIST (object);
+  GObjectClass *parent_class =
+      g_type_class_peek_parent (G_OBJECT_GET_CLASS (object));
+
+  if (eprop_string_list->update_id)
+    {
+      g_source_remove (eprop_string_list->update_id);
+      eprop_string_list->update_id = 0;
+    }
+
+  if (eprop_string_list->changed_id)
+    {
+      g_source_remove (eprop_string_list->changed_id);
+      eprop_string_list->changed_id = 0;
+    }
+
+  if (eprop_string_list->pending_string_list)
+    {
+      glade_string_list_free (eprop_string_list->pending_string_list);
+      eprop_string_list->pending_string_list = NULL;
+    }
+
+  /* Chain up */
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gboolean
+update_string_list_idle (GladeEditorProperty * eprop)
+{
+  GladeEPropStringList *eprop_string_list = GLADE_EPROP_STRING_LIST (eprop);
+  GValue value = { 0, };
+
+  eprop_string_list->want_focus = TRUE;
+
+  g_value_init (&value, GLADE_TYPE_STRING_LIST);
+  g_value_take_boxed (&value, eprop_string_list->pending_string_list);
+  glade_editor_property_commit (eprop, &value);
+  g_value_unset (&value);
+
+  eprop_string_list->want_focus = FALSE;
+
+  eprop_string_list->pending_string_list = NULL;
+  eprop_string_list->update_id = 0;
+    
+  return FALSE;
+}
+
+static gboolean
+data_changed_idle (GladeEditorProperty *eprop)
+{
+  GladeEPropStringList *eprop_string_list = GLADE_EPROP_STRING_LIST (eprop);
+  GladeProperty        *property = glade_editor_property_get_property (eprop);
+  GladeString          *string, *copy;
+  GList                *string_list = NULL;
+  GList                *new_list = NULL;
+  GtkTreeIter           iter;
+  guint                 index;
+
+  /* Create a new list based on the order and contents
+   * of the model state */
+  glade_property_get (property, &string_list);
+
+  if (gtk_tree_model_get_iter_first (eprop_string_list->model, &iter))
+    {
+      do
+        {
+          gtk_tree_model_get (eprop_string_list->model, &iter,
+                              COLUMN_INDEX, &index, -1);
+
+          if ((string = g_list_nth_data (string_list, index)) != NULL)
+            {
+	      copy = glade_string_copy (string);
+	      new_list = g_list_prepend (new_list, copy);
+            }
+        }
+      while (gtk_tree_model_iter_next (eprop_string_list->model, &iter));
+    }
+
+  new_list = g_list_reverse (new_list);
+
+  if (eprop_string_list->pending_string_list)
+    glade_string_list_free (eprop_string_list->pending_string_list);
+  eprop_string_list->pending_string_list = new_list;
+
+  /* We're already in an idle, just call it directly here */
+  update_string_list_idle (eprop);
+
+  eprop_string_list->changed_id = 0;
+  return FALSE;
+}
+
+static void
+row_deleted (GtkTreeModel * tree_model,
+	     GtkTreePath * path, GladeEditorProperty * eprop)
+{
+  GladeEPropStringList *eprop_string_list = GLADE_EPROP_STRING_LIST (eprop);
+
+  if (glade_editor_property_loading (eprop))
+    return;
+
+  eprop_string_list->editing_index = 0;
+
+  if (eprop_string_list->changed_id == 0)
+    eprop_string_list->changed_id = 
+      g_idle_add ((GSourceFunc) data_changed_idle, eprop);
+}
+
+static void
+glade_eprop_string_list_load (GladeEditorProperty * eprop, GladeProperty * property)
+{
+  GladeEPropStringList *eprop_string_list = GLADE_EPROP_STRING_LIST (eprop);
+  GladeEditorPropertyClass *parent_class =
+      g_type_class_peek_parent (G_OBJECT_GET_CLASS (eprop));
+  GList *string_list, *list;
+  GtkTreeIter iter;
+  guint i;
+
+  g_signal_handlers_block_by_func (eprop_string_list->model, row_deleted, eprop);
+  gtk_list_store_clear (GTK_LIST_STORE (eprop_string_list->model));
+  g_signal_handlers_unblock_by_func (eprop_string_list->model, row_deleted, eprop);
+
+  parent_class->load (eprop, property);
+
+  if (!property)
+    return;
+
+  glade_property_get (property, &string_list);
+
+  for (list = string_list, i = 0; list; list = list->next, i++)
+    {
+      GladeString *string = list->data;
+
+      gtk_list_store_append (GTK_LIST_STORE (eprop_string_list->model), &iter);
+      gtk_list_store_set (GTK_LIST_STORE (eprop_string_list->model), &iter,
+			  COLUMN_STRING, string->string,
+			  COLUMN_INDEX, i,
+			  COLUMN_DUMMY, FALSE,
+			  -1);
+    }
+
+  gtk_list_store_append (GTK_LIST_STORE (eprop_string_list->model), &iter);
+  gtk_list_store_set (GTK_LIST_STORE (eprop_string_list->model), &iter,
+		      COLUMN_STRING, _("<Type Here>"),
+		      COLUMN_INDEX, i,
+		      COLUMN_DUMMY, TRUE,
+		      -1);
+
+  if (eprop_string_list->want_focus)
+    {
+      GtkTreePath *path = gtk_tree_path_new_from_indices (eprop_string_list->editing_index, -1);
+      GtkTreeViewColumn *column = gtk_tree_view_get_column (GTK_TREE_VIEW (eprop_string_list->view), 0);
+
+      gtk_widget_grab_focus (eprop_string_list->view);
+      gtk_tree_view_set_cursor (GTK_TREE_VIEW (eprop_string_list->view), path, column, FALSE);
+
+      gtk_tree_path_free (path);
+    }
+}
+
+static void
+string_edited (GtkCellRendererText *renderer,
+	       gchar               *path,
+	       gchar               *new_text,
+	       GladeEditorProperty *eprop)
+{
+  GladeEPropStringList *eprop_string_list = GLADE_EPROP_STRING_LIST (eprop);
+  GtkTreePath          *tree_path = gtk_tree_path_new_from_string (path);
+  GtkTreeIter           iter;
+  gboolean              dummy;
+  guint                 index;
+  GladeProperty        *property = glade_editor_property_get_property (eprop);
+  GList                *string_list = NULL;
+
+  gtk_tree_model_get_iter (eprop_string_list->model, &iter, tree_path);
+  gtk_tree_model_get (eprop_string_list->model, &iter,
+		      COLUMN_INDEX, &index,
+		      COLUMN_DUMMY, &dummy,
+		      -1);
+
+  glade_property_get (property, &string_list);
+
+  if (string_list)
+    string_list = glade_string_list_copy (string_list);
+
+  if (dummy)
+    {
+      if (new_text && new_text[0] && strcmp (new_text, _("<Type Here>")) != 0)
+	string_list = 
+	  glade_string_list_append (string_list,
+				    new_text, NULL, NULL, 
+				    eprop_string_list->translatable);
+    }
+  else if (new_text && new_text[0])
+    {
+      GladeString *string = 
+	g_list_nth_data (string_list, index);
+
+      g_free (string->string);
+      string->string = g_strdup (new_text);
+    }
+  else
+    {
+      GList *node = g_list_nth (string_list, index);
+      glade_string_free (node->data);
+      string_list = 
+	g_list_delete_link (string_list, node);
+    }
+
+  eprop_string_list->editing_index = index;
+
+  if (eprop_string_list->pending_string_list)
+    glade_string_list_free (eprop_string_list->pending_string_list);
+  eprop_string_list->pending_string_list = string_list;
+
+  if (eprop_string_list->update_id == 0)
+    eprop_string_list->update_id = 
+      g_idle_add ((GSourceFunc) update_string_list_idle, eprop);
+
+  gtk_tree_path_free (tree_path);
+}
+
+static void
+i18n_icon_activate (GtkCellRenderer     *renderer,
+		    const gchar         *path,
+		    GladeEditorProperty *eprop)
+{
+  GladeEPropStringList *eprop_string_list = GLADE_EPROP_STRING_LIST (eprop);
+  GtkTreePath          *tree_path = gtk_tree_path_new_from_string (path);
+  GtkTreeIter           iter;
+  guint                 index;
+  GladeProperty        *property = glade_editor_property_get_property (eprop);
+  GList                *string_list = NULL;
+  GladeString          *string;
+
+  gtk_tree_model_get_iter (eprop_string_list->model, &iter, tree_path);
+  gtk_tree_model_get (eprop_string_list->model, &iter,
+		      COLUMN_INDEX, &index,
+		      -1);
+
+  glade_property_get (property, &string_list);
+  string_list = glade_string_list_copy (string_list);
+
+  string = g_list_nth_data (string_list, index);
+
+  if (glade_editor_property_show_i18n_dialog (NULL,
+                                              &string->string,
+                                              &string->context,
+                                              &string->comment,
+                                              &string->translatable))
+    {
+      eprop_string_list->editing_index = index;
+
+      if (eprop_string_list->pending_string_list)
+	glade_string_list_free (eprop_string_list->pending_string_list);
+      eprop_string_list->pending_string_list = string_list;
+
+      if (eprop_string_list->update_id == 0)
+	eprop_string_list->update_id = 
+	  g_idle_add ((GSourceFunc) update_string_list_idle, eprop);
+    }
+  else
+    glade_string_list_free (string_list);
+
+  gtk_tree_path_free (tree_path);
+}
+
+static void
+cell_data_func (GtkTreeViewColumn   *column,
+		GtkCellRenderer     *renderer,
+		GtkTreeModel        *model,
+		GtkTreeIter         *iter,
+		GladeEditorProperty *eprop)
+{
+  GladeEPropStringList *eprop_string_list = GLADE_EPROP_STRING_LIST (eprop);
+  gboolean dummy;
+  GdkRGBA  color;
+
+  gtk_tree_model_get (model, iter, COLUMN_DUMMY, &dummy, -1);
+
+  if (GTK_IS_CELL_RENDERER_TEXT (renderer))
+    {
+      GtkStyleContext* context = gtk_widget_get_style_context (eprop_string_list->view);
+
+      if (dummy)
+	{
+	  gtk_style_context_get_color (context, GTK_STATE_FLAG_INSENSITIVE, &color);
+	  g_object_set (renderer, 
+			"style", PANGO_STYLE_ITALIC,
+			"foreground-rgba", &color,
+			NULL);
+	}
+      else
+	{
+	  gtk_style_context_get_color (context, GTK_STATE_FLAG_NORMAL, &color);
+	  g_object_set (renderer,
+			"style", PANGO_STYLE_NORMAL,
+			"foreground-rgba", &color,
+			NULL);
+	}
+    }
+  else if (GLADE_IS_CELL_RENDERER_ICON (renderer))
+    g_object_set (renderer, "visible", !dummy && eprop_string_list->translatable, NULL);
+}
+
+static gboolean
+treeview_key_press (GtkWidget           *treeview,
+		    GdkEventKey         *event, 
+		    GladeEditorProperty *eprop)
+{
+
+  /* Delete rows from the store, this will trigger "row-deleted" which will
+   * handle the property update in an idle handler */
+  if (event->keyval == GDK_KEY_Delete)
+    {
+      GladeEPropStringList *eprop_string_list = GLADE_EPROP_STRING_LIST (eprop);
+      GtkTreeSelection *selection;
+      GtkTreeIter iter;
+      GList *selected_rows, *l;
+
+      selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
+
+      if ((selected_rows = 
+	   gtk_tree_selection_get_selected_rows (selection, NULL)) != NULL)
+	{
+	  for (l = selected_rows; l; l = l->next)
+	    {
+	      GtkTreePath *path = l->data;
+
+	      if (gtk_tree_model_get_iter (eprop_string_list->model, &iter, path))
+		gtk_list_store_remove (GTK_LIST_STORE (eprop_string_list->model), &iter);
+	    }
+
+	  g_list_foreach (selected_rows, (GFunc)gtk_tree_path_free, NULL);
+	  g_list_free (selected_rows);
+	}
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static GtkWidget *
+glade_eprop_string_list_create_input (GladeEditorProperty * eprop)
+{
+  GladeEPropStringList *eprop_string_list = GLADE_EPROP_STRING_LIST (eprop);
+  GtkTreeViewColumn    *column;
+  GtkCellRenderer      *renderer;
+  GtkWidget            *swindow;
+
+  eprop_string_list->view = gtk_tree_view_new ();
+  column                  = gtk_tree_view_column_new ();
+
+  /* Text renderer */
+  renderer = gtk_cell_renderer_text_new ();
+  g_object_set (G_OBJECT (renderer), 
+		"editable", TRUE,
+		"ellipsize", PANGO_ELLIPSIZE_END,
+		NULL);
+  g_signal_connect (G_OBJECT (renderer), "edited",
+		    G_CALLBACK (string_edited), eprop);
+
+  gtk_tree_view_column_pack_start (column, renderer, TRUE);
+  gtk_tree_view_column_set_attributes (column, renderer,
+				       "text", COLUMN_STRING,
+				       NULL);
+  gtk_tree_view_column_set_cell_data_func (column, renderer,
+					   (GtkTreeCellDataFunc)cell_data_func,
+					   eprop, NULL);
+
+  /* i18n icon renderer */
+  renderer = glade_cell_renderer_icon_new ();
+  g_object_set (G_OBJECT (renderer), "icon-name", GTK_STOCK_EDIT, NULL);
+  g_signal_connect (G_OBJECT (renderer), "activate",
+		    G_CALLBACK (i18n_icon_activate), eprop);
+
+  gtk_tree_view_column_pack_start (column, renderer, FALSE);
+  gtk_tree_view_column_set_cell_data_func (column, renderer,
+					   (GtkTreeCellDataFunc)cell_data_func,
+					   eprop, NULL);
+
+  eprop_string_list->model = (GtkTreeModel *)gtk_list_store_new (NUM_COLUMNS,
+								 G_TYPE_STRING,
+								 G_TYPE_UINT,
+								 G_TYPE_BOOLEAN);
+
+
+  g_signal_connect (G_OBJECT (eprop_string_list->model), "row-deleted",
+		    G_CALLBACK (row_deleted), eprop);
+
+  gtk_tree_view_append_column (GTK_TREE_VIEW (eprop_string_list->view), column);
+
+  gtk_tree_view_set_model (GTK_TREE_VIEW (eprop_string_list->view),
+			   eprop_string_list->model);
+
+  gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (eprop_string_list->view), FALSE);
+  gtk_tree_view_set_reorderable (GTK_TREE_VIEW (eprop_string_list->view), TRUE);
+
+  g_signal_connect (eprop_string_list->view, "key-press-event",
+                    G_CALLBACK (treeview_key_press), eprop);
+
+  swindow = gtk_scrolled_window_new (NULL, NULL);
+
+  gtk_scrolled_window_set_min_content_height (GTK_SCROLLED_WINDOW (swindow), 150);
+  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (swindow),
+				  GTK_POLICY_AUTOMATIC,
+				  GTK_POLICY_AUTOMATIC);
+  gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (swindow), GTK_SHADOW_IN);
+  gtk_container_add (GTK_CONTAINER (swindow), eprop_string_list->view);
+
+  gtk_widget_show (eprop_string_list->view);
+  gtk_widget_show (swindow);
+
+  return swindow;
+}
+
+GladeEditorProperty *
+glade_eprop_string_list_new (GladePropertyClass *pclass,
+			     gboolean            use_command,
+			     gboolean            translatable)
+{
+  GladeEditorProperty *eprop = 
+    g_object_new (GLADE_TYPE_EPROP_STRING_LIST, 
+		  "property-class", pclass,
+		  "use-command", use_command,
+		  NULL);
+
+  GladeEPropStringList *eprop_string_list = GLADE_EPROP_STRING_LIST (eprop);
+
+  eprop_string_list->translatable = translatable;
+
+  return eprop;
+}
diff --git a/plugins/gtk+/glade-string-list.h b/plugins/gtk+/glade-string-list.h
new file mode 100644
index 0000000..06a0090
--- /dev/null
+++ b/plugins/gtk+/glade-string-list.h
@@ -0,0 +1,43 @@
+#ifndef __GLADE_STRING_LIST_H__
+#define __GLADE_STRING_LIST_H__
+
+#include <glib-object.h>
+#include <gladeui/glade.h>
+
+G_BEGIN_DECLS
+
+
+#define GLADE_TYPE_EPROP_STRING_LIST       (glade_eprop_string_list_get_type())
+#define	GLADE_TYPE_STRING_LIST             (glade_string_list_get_type())
+
+/* The GladeStringList boxed type is a GList * of GladeString structs */
+typedef struct _GladeString             GladeString;
+
+struct _GladeString {
+  gchar    *string;
+  gchar    *comment;
+  gchar    *context;
+  gboolean  translatable;
+};
+
+GType        glade_eprop_string_list_get_type    (void) G_GNUC_CONST;
+GType        glade_string_list_get_type          (void) G_GNUC_CONST;
+
+void         glade_string_list_free              (GList   *list);
+GList       *glade_string_list_copy              (GList   *list);
+
+GList       *glade_string_list_append            (GList   *list,
+						  gchar   *string,
+						  gchar   *comment,
+						  gchar   *context,
+						  gboolean translatable);
+
+gchar       *glade_string_list_to_string         (GList   *list);
+
+GladeEditorProperty *glade_eprop_string_list_new (GladePropertyClass *pclass,
+						  gboolean            use_command,
+						  gboolean            translatable);
+
+G_END_DECLS
+
+#endif   /* __GLADE_STRING_LIST_H__ */
diff --git a/plugins/gtk+/gtk+.xml.in b/plugins/gtk+/gtk+.xml.in
index 309b74b..f30ebb4 100644
--- a/plugins/gtk+/gtk+.xml.in
+++ b/plugins/gtk+/gtk+.xml.in
@@ -1205,6 +1205,28 @@ embedded in another object</_tooltip>
       </properties>
     </glade-widget-class>
 
+
+    <glade-widget-class name="GtkComboBoxText" generic-name="comboboxtext" _title="Combo Box Text">
+      <post-create-function>glade_gtk_combo_box_text_post_create</post-create-function>
+      <create-editor-property-function>glade_gtk_combo_box_text_create_eprop</create-editor-property-function>
+      <string-from-value-function>glade_gtk_combo_box_text_string_from_value</string-from-value-function>
+      <set-property-function>glade_gtk_combo_box_text_set_property</set-property-function>
+      <read-widget-function>glade_gtk_combo_box_text_read_widget</read-widget-function>
+      <write-widget-function>glade_gtk_combo_box_text_write_widget</write-widget-function>
+
+      <properties>
+        <property id="model" disabled="True"/>
+	<property id="glade-items" _name="Items" save="False" since="2.24">
+	  <parameter-spec>
+	    <type>GParamBoxed</type>
+	    <value-type>GladeStringList</value-type>
+	  </parameter-spec>
+	  <_tooltip>The items to show in the combo box</_tooltip>
+	</property>
+      </properties>
+    </glade-widget-class>
+
+
     <glade-widget-class name="GtkProgressBar" generic-name="progressbar" _title="Progress Bar">
       <properties>
         <property id="text" translatable="True"/>
@@ -3422,6 +3444,7 @@ embedded in another object</_tooltip>
     <glade-widget-class-ref name="GtkVScrollbar"/>
 
     <glade-widget-class-ref name="GtkComboBox"/>
+    <glade-widget-class-ref name="GtkComboBoxText"/>
     <glade-widget-class-ref name="GtkProgressBar"/>
     <glade-widget-class-ref name="GtkSpinner"/>
 



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