[ekiga/ds-gtk-application] GmEntry: Added new GmEntry widget and use it in Forms.



commit 2c8b28a97acac6e6723a3b8e713df77f36ef0a56
Author: Damien Sandras <dsandras seconix com>
Date:   Sun Oct 26 12:24:35 2014 +0100

    GmEntry: Added new GmEntry widget and use it in Forms.
    
    That new GmEntry widget is a enhanced GtkEntry with more features:
     - It can be associated with a validation regex. The GmEntry content
       is considered valid if the regex matches, or if it is empty and the
       allow-empty property is set to TRUE.
     - A GmEntry emits a validity-changed signal when their validity
       changes. It allows user programs to make the OK button unsensitive in
       dialogs as long as at least one GmEntry is considered as invalid.
     - A GmEntry displays the edit-clear symbol for quick content deletion
       when they are not empty.

 lib/Makefile.am                             |    2 +
 lib/engine/gui/gtk-core/form-dialog-gtk.cpp |  113 +++-------
 lib/gui/gm-entry.c                          |  329 +++++++++++++++++++++++++++
 lib/gui/gm-entry.h                          |  131 +++++++++++
 4 files changed, 492 insertions(+), 83 deletions(-)
---
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 4956ad2..9878788 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -121,6 +121,8 @@ libekiga_la_SOURCES += \
        gui/dialpad.c \
        gui/gm-smileys.h \
        gui/gm-smileys.c \
+       gui/gm-entry.h \
+       gui/gm-entry.c \
        gui/gmwindow.c \
        gui/gmwindow.h \
        gui/gmentrydialog.c \
diff --git a/lib/engine/gui/gtk-core/form-dialog-gtk.cpp b/lib/engine/gui/gtk-core/form-dialog-gtk.cpp
index 8d6f091..85609d6 100644
--- a/lib/engine/gui/gtk-core/form-dialog-gtk.cpp
+++ b/lib/engine/gui/gtk-core/form-dialog-gtk.cpp
@@ -40,34 +40,31 @@
 
 #include "platform.h"
 #include "form-dialog-gtk.h"
+#include "gm-entry.h"
+
+#define URI_SCHEME "([A-Za-z]+:)?"
+#define BASIC_URI_PART "[A-Za-z0-9_\\-\\.]+"
+#define BASIC_URI_REGEX "^" URI_SCHEME BASIC_URI_PART "@" BASIC_URI_PART "$"
+#define PHONE_NUMBER_REGEX "\\+?[0-9]+"
 
 /*
  * Declarations : GTK+ Callbacks
  */
 
-/** Called when the GtkEntry delete icon has been clicked.
+/** Called when a GmEntry validity has changed.
  *
- * Delete the entry content.
- */
-static void
-text_entry_icon_release_cb (GtkEntry *entry,
-                            G_GNUC_UNUSED gpointer data);
-
-
-/** Called when a GtkEntry has new content.
+ * The GmEntry is considered valid iff:
+ *   - The GmEntry content is empty and it is allowed.
+ *   - The GmEntry regex matches
  *
- * If the Form text entry does not allow empty values,
- * the "OK" button will stay unsensitive as long as
- * the entry does not contain non-empty text.
- *
- * A delete all icon also appears when the entry is not
- * empty.
+ * The "OK" button will be sensitive iff all Form elements
+ * are considered valid.
  *
  * @param: data is a pointer to the FormDialog object.
  */
 static void
-text_entry_changed_cb (GtkEntry *entry,
-                       gpointer data);
+text_entry_validity_changed_cb (GtkEntry *entry,
+                                gpointer data);
 
 
 /** Called when a choice has been toggled in the
@@ -300,40 +297,9 @@ public:
   ~TextSubmitter ()
   { }
 
-  void updated ()
-  {
-    const std::string value = gtk_entry_get_text (GTK_ENTRY (widget));
-    bool empty = value.empty ();
-    bool empty_ok = (allow_empty || (!empty && value.find_first_not_of (' ') != std::string::npos));
-    bool type_ok = false;
-
-    switch (type) {
-
-    case Ekiga::FormVisitor::URI:
-      type_ok =
-        PString (value).MatchesRegEx (PRegularExpression ("[A-Za-z0-9:_\\-\\ ]+ [A-Za-z0-9\\-\\ ]+",
-                                                          PRegularExpression::Extended));
-      break;
-
-    case Ekiga::FormVisitor::PHONE_NUMBER:
-      type_ok =
-        PString (value).MatchesRegEx (PRegularExpression ("\\+?[0-9]+",
-                                                          PRegularExpression::Extended));
-      break;
-
-    case Ekiga::FormVisitor::STANDARD:
-    case Ekiga::FormVisitor::PASSWORD:
-    default:
-      type_ok = true;
-      break;
-    }
-
-    submit_ok = (type_ok && empty_ok);
-  }
-
   bool can_submit ()
   {
-    return submit_ok;
+    return gm_entry_text_is_valid (GM_ENTRY (widget));
   }
 
   void submit (Ekiga::FormBuilder &builder)
@@ -707,34 +673,14 @@ editable_list_choice_toggled_cb (G_GNUC_UNUSED GtkCellRendererToggle *cell,
 
 
 static void
-text_entry_icon_release_cb (GtkEntry *entry,
-                            G_GNUC_UNUSED gpointer data)
-{
-  gtk_entry_set_text (entry, "");
-  gtk_widget_grab_focus (GTK_WIDGET(entry));
-}
-
-
-static void
-text_entry_changed_cb (GtkEntry *entry,
-                       gpointer data)
+text_entry_validity_changed_cb (G_GNUC_UNUSED GtkEntry *entry,
+                                gpointer data)
 {
   g_return_if_fail (data);
 
-  TextSubmitter *submitter = (TextSubmitter *) g_object_get_data (G_OBJECT (entry), "submitter");
   FormDialog *form_dialog = (FormDialog *) data;
   GtkWidget *dialog = form_dialog->get_dialog ();
   GtkWidget *ok_button = gtk_dialog_get_widget_for_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
-  bool empty = (gtk_entry_get_text_length (entry) == 0);
-  gboolean rtl = (gtk_widget_get_direction (GTK_WIDGET(entry)) == GTK_TEXT_DIR_RTL);
-
-  g_object_set (entry,
-                "secondary-icon-name", !empty ? (rtl ? "edit-clear-rtl-symbolic" : "edit-clear-symbolic") : 
NULL,
-                "secondary-icon-activatable", !empty,
-                "secondary-icon-sensitive", !empty,
-                NULL);
-
-  submitter->updated ();
 
   gtk_widget_set_sensitive (ok_button, form_dialog->can_submit ());
 }
@@ -1039,38 +985,39 @@ FormDialog::text (const std::string name,
   label = gtk_label_new (description.c_str ());
   gtk_widget_set_halign (GTK_WIDGET (label), GTK_ALIGN_END);
 
-  entry = gtk_entry_new ();
-  gtk_entry_set_placeholder_text (GTK_ENTRY (entry), placeholder_text.c_str ());
-  gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry);
-  gtk_entry_set_activates_default (GTK_ENTRY (entry), true);
-  gtk_entry_set_text (GTK_ENTRY (entry), value.c_str ());
-  g_object_set (G_OBJECT (entry), "expand", TRUE, NULL);
-
   switch (type) {
     case PASSWORD:
+      entry = gm_entry_new (NULL);
       gtk_entry_set_visibility (GTK_ENTRY (entry), FALSE);
       gtk_entry_set_input_purpose (GTK_ENTRY (entry), GTK_INPUT_PURPOSE_PASSWORD);
       break;
     case PHONE_NUMBER:
+      entry = gm_entry_new (PHONE_NUMBER_REGEX);
       gtk_entry_set_input_purpose (GTK_ENTRY (entry), GTK_INPUT_PURPOSE_PHONE);
       break;
     case URI:
+      entry = gm_entry_new (BASIC_URI_REGEX);
       gtk_entry_set_input_purpose (GTK_ENTRY (entry), GTK_INPUT_PURPOSE_URL);
       break;
     case STANDARD:
     default:
+      entry = gm_entry_new (NULL);
       break;
   };
 
+  gtk_entry_set_placeholder_text (GTK_ENTRY (entry), placeholder_text.c_str ());
+  gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry);
+  gtk_entry_set_activates_default (GTK_ENTRY (entry), true);
+  gtk_entry_set_text (GTK_ENTRY (entry), value.c_str ());
+  g_object_set (G_OBJECT (entry), "expand", TRUE, "allow-empty", allow_empty, NULL);
+
   submitter = new TextSubmitter (name, description, placeholder_text, type, advanced, allow_empty, entry);
   submitters.push_back (submitter);
 
   g_object_set_data (G_OBJECT (entry), "submitter", submitter);
-  text_entry_changed_cb (GTK_ENTRY (entry), (gpointer) this);
-  g_signal_connect (entry, "changed",
-                    G_CALLBACK (text_entry_changed_cb), this);
-  g_signal_connect(entry, "icon-release",
-                   G_CALLBACK (text_entry_icon_release_cb), entry);
+  text_entry_validity_changed_cb (GTK_ENTRY (entry), (gpointer) this);
+  g_signal_connect (entry, "validity-changed",
+                    G_CALLBACK (text_entry_validity_changed_cb), this);
 
   if (advanced) {
 
diff --git a/lib/gui/gm-entry.c b/lib/gui/gm-entry.c
new file mode 100644
index 0000000..887258c
--- /dev/null
+++ b/lib/gui/gm-entry.c
@@ -0,0 +1,329 @@
+
+/* Ekiga -- A VoIP and Video-Conferencing application
+ * Copyright (C) 2000-2014 Damien Sandras <dsandras seconix 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 2 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, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ *
+ * Ekiga is licensed under the GPL license and as a special exception,
+ * you have permission to link or otherwise combine this program with the
+ * programs OPAL, OpenH323 and PWLIB, and distribute the combination,
+ * without applying the requirements of the GNU GPL to the OPAL, OpenH323
+ * and PWLIB programs, as long as you do follow the requirements of the
+ * GNU GPL for all the rest of the software thus combined.
+ */
+
+
+/*
+ *                         gm-entry.c  -  description
+ *                         --------------------------
+ *   begin                : Sat Oct 25 2014
+ *   copyright            : (C) 2014 by Damien Sandras
+ *   description          : Contains a GmEntry implementation handling
+ *                          quick delete and basic validation depending
+ *                          on the entry type.
+ *
+ */
+
+
+#include "gm-entry.h"
+
+#include <regex.h>
+
+struct _GmEntryPrivate {
+
+  GRegex *regex;
+  gchar *regex_string;
+  gboolean is_valid;
+  gboolean allow_empty;
+};
+
+enum {
+  GM_ENTRY_REGEX = 1,
+  GM_ENTRY_ALLOW_EMPTY = 2,
+};
+
+enum {
+  VALIDITY_CHANGED_SIGNAL,
+  LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (GmEntry, gm_entry, GTK_TYPE_ENTRY);
+
+
+/* Callbacks */
+static void gm_entry_changed_cb (GmEntry *self,
+                                 G_GNUC_UNUSED gpointer data);
+
+static void gm_entry_icon_release_cb (GtkEntry *self,
+                                      G_GNUC_UNUSED gpointer data);
+
+
+/* Static GObject functions and declarations */
+static void gm_entry_class_init (GmEntryClass *);
+
+static void gm_entry_init (GmEntry *);
+
+static void gm_entry_dispose (GObject *);
+
+static void gm_entry_get_property (GObject *obj,
+                                   guint prop_id,
+                                   GValue *value,
+                                   GParamSpec *spec);
+
+static void gm_entry_set_property (GObject *obj,
+                                   guint prop_id,
+                                   const GValue *value,
+                                   GParamSpec *spec);
+
+
+/* Callbacks */
+static void
+gm_entry_changed_cb (GmEntry *self,
+                     G_GNUC_UNUSED gpointer data)
+{
+  gboolean empty = (gtk_entry_get_text_length (GTK_ENTRY (self)) == 0);
+  gboolean rtl = (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL);
+  gboolean is_valid = gm_entry_text_is_valid (self);
+
+  g_object_set (self,
+                "secondary-icon-name", !empty ? (rtl ? "edit-clear-rtl-symbolic" : "edit-clear-symbolic") : 
NULL,
+                "secondary-icon-activatable", !empty,
+                "secondary-icon-sensitive", !empty,
+                NULL);
+
+  if (is_valid != self->priv->is_valid) {
+    self->priv->is_valid = is_valid;
+    g_signal_emit (self, signals[VALIDITY_CHANGED_SIGNAL], 0);
+  }
+}
+
+
+static void
+gm_entry_icon_release_cb (GtkEntry *self,
+                          G_GNUC_UNUSED gpointer data)
+{
+  gtk_entry_set_text (self, "");
+  gtk_widget_grab_focus (GTK_WIDGET (self));
+}
+
+
+/* Implementation of GObject stuff */
+static void
+gm_entry_dispose (GObject* obj)
+{
+  GmEntryPrivate *priv = GM_ENTRY (obj)->priv;
+
+  if (priv->regex) {
+    g_regex_unref (priv->regex);
+    priv->regex = NULL;
+  }
+  if (priv->regex_string) {
+    g_free (priv->regex_string);
+    priv->regex_string = NULL;
+  }
+
+  G_OBJECT_CLASS (gm_entry_parent_class)->dispose (obj);
+}
+
+
+static void
+gm_entry_get_property (GObject *obj,
+                       guint prop_id,
+                       GValue *value,
+                       GParamSpec *spec)
+{
+  GmEntry *self = NULL;
+
+  self = GM_ENTRY (obj);
+  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GM_TYPE_ENTRY, GmEntryPrivate);
+
+  switch (prop_id) {
+
+  case GM_ENTRY_REGEX:
+    g_value_set_string (value, self->priv->regex_string);
+    break;
+
+  case GM_ENTRY_ALLOW_EMPTY:
+    g_value_set_boolean (value, self->priv->allow_empty);
+    break;
+
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, spec);
+    break;
+  }
+}
+
+
+static void
+gm_entry_set_property (GObject *obj,
+                       guint prop_id,
+                       const GValue *value,
+                       GParamSpec *spec)
+{
+  GmEntry *self = NULL;
+  GError *error = NULL;
+  const gchar *str = NULL;
+
+  self = GM_ENTRY (obj);
+  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GM_TYPE_ENTRY, GmEntryPrivate);
+
+  switch (prop_id) {
+
+  case GM_ENTRY_REGEX:
+    if (self->priv->regex_string)
+      g_free (self->priv->regex_string);
+    if (self->priv->regex)
+      g_regex_unref (self->priv->regex);
+    self->priv->regex_string = NULL;
+    self->priv->regex = NULL;
+
+    str = g_value_get_string (value);
+    if (g_strcmp0 (str, "")) {
+      self->priv->regex = g_regex_new (str, 0, 0, &error);
+      if (!self->priv->regex) {
+        g_warning ("Failed to create regex: %s", error->message);
+        g_error_free (error);
+        break;
+      }
+      self->priv->regex_string = g_strdup (str);
+    }
+    break;
+
+  case GM_ENTRY_ALLOW_EMPTY:
+    self->priv->allow_empty = g_value_get_boolean (value);
+    break;
+
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, spec);
+    break;
+  }
+}
+
+
+static void
+gm_entry_class_init (GmEntryClass *klass)
+{
+  GParamSpec *spec = NULL;
+
+  g_type_class_add_private (klass, sizeof (GmEntryPrivate));
+
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->dispose = gm_entry_dispose;
+  gobject_class->get_property = gm_entry_get_property;
+  gobject_class->set_property = gm_entry_set_property;
+
+  spec = g_param_spec_string ("regex", "Regex", "Validation Regex",
+                              NULL, (GParamFlags) G_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, GM_ENTRY_REGEX, spec);
+
+
+  spec = g_param_spec_boolean ("allow-empty", "Allow Empty", "Allow empty GmEntry",
+                               TRUE, (GParamFlags) G_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, GM_ENTRY_ALLOW_EMPTY, spec);
+
+
+  signals[VALIDITY_CHANGED_SIGNAL] =
+    g_signal_new ("validity-changed",
+                 G_OBJECT_CLASS_TYPE (gobject_class),
+                 G_SIGNAL_RUN_LAST,
+                 0, NULL, NULL,
+                 g_cclosure_marshal_generic,
+                 G_TYPE_NONE, 0);
+}
+
+
+static void
+gm_entry_init (GmEntry* self)
+{
+  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+                                            GM_TYPE_ENTRY,
+                                           GmEntryPrivate);
+
+  self->priv->regex = NULL;
+  self->priv->regex_string = NULL;
+  self->priv->is_valid = gm_entry_text_is_valid (self);
+
+  g_signal_connect (self, "changed",
+                    G_CALLBACK (gm_entry_changed_cb), NULL);
+  g_signal_connect (self, "icon-release",
+                    G_CALLBACK (gm_entry_icon_release_cb), NULL);
+}
+
+
+/* public api */
+GtkWidget *
+gm_entry_new (const gchar *regex)
+{
+  if (regex)
+    return GTK_WIDGET (g_object_new (GM_TYPE_ENTRY, "regex", regex, NULL));
+
+  return GTK_WIDGET (g_object_new (GM_TYPE_ENTRY, NULL));
+}
+
+
+gboolean
+gm_entry_text_is_valid (GmEntry *self)
+{
+  GMatchInfo *match_info = NULL;
+  gboolean success = FALSE;
+  gchar *value = NULL;
+
+  g_return_val_if_fail (GM_IS_ENTRY (self), success);
+  if (!self->priv->regex)
+    return TRUE;
+
+  const char *content = gtk_entry_get_text (GTK_ENTRY (self));
+  if (self->priv->allow_empty) {
+    value = g_strdup (content);
+    value = g_strstrip (value);
+    if (!g_strcmp0 (value, "")) {
+      g_free (value);
+      return TRUE;
+    }
+    g_free (value);
+  }
+
+  g_regex_match (self->priv->regex, content, 0, &match_info);
+
+  if (g_match_info_matches (match_info))
+    success = TRUE;
+
+  g_match_info_free (match_info);
+
+  return success;
+}
+
+
+void
+gm_entry_set_allow_empty (GmEntry *self,
+                          gboolean allow_empty)
+{
+  g_return_if_fail (GM_IS_ENTRY (self));
+
+  g_object_set (self, "allow-empty", allow_empty, NULL);
+}
+
+
+gboolean
+gm_entry_get_allow_empty (GmEntry *self)
+{
+  g_return_val_if_fail (GM_IS_ENTRY (self), TRUE);
+
+  return self->priv->allow_empty;
+}
diff --git a/lib/gui/gm-entry.h b/lib/gui/gm-entry.h
new file mode 100644
index 0000000..762261e
--- /dev/null
+++ b/lib/gui/gm-entry.h
@@ -0,0 +1,131 @@
+
+/* Ekiga -- A VoIP and Video-Conferencing application
+ * Copyright (C) 2000-2014 Damien Sandras <dsandras seconix 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 2 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, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ *
+ * Ekiga is licensed under the GPL license and as a special exception,
+ * you have permission to link or otherwise combine this program with the
+ * programs OPAL, OpenH323 and PWLIB, and distribute the combination,
+ * without applying the requirements of the GNU GPL to the OPAL, OpenH323
+ * and PWLIB programs, as long as you do follow the requirements of the
+ * GNU GPL for all the rest of the software thus combined.
+ */
+
+
+/*
+ *                         gm-entry.h  -  description
+ *                         --------------------------
+ *   begin                : Sat Oct 25 2014
+ *   copyright            : (C) 2014 by Damien Sandras
+ *   description          : Contains a GmEntry implementation handling
+ *                          quick delete and basic validation depending
+ *                          on the entry type.
+ *
+ */
+
+
+#ifndef __GM_ENTRY_H__
+#define __GM_ENTRY_H__
+
+#include <gtk/gtk.h>
+
+
+G_BEGIN_DECLS
+
+typedef struct _GmEntry GmEntry;
+typedef struct _GmEntryPrivate GmEntryPrivate;
+typedef struct _GmEntryClass GmEntryClass;
+
+/*
+ * Public API
+ *
+ */
+
+/** Create a new GmEntry.
+ * @param The regex that will determine if the content is valid.
+ *        Can be NULL.
+ * @return A GmEntry.
+ */
+GtkWidget *gm_entry_new (const gchar *regex);
+
+
+/** Check if the entry content is valid.
+ * @return TRUE if the GmEntry content matches the Regex, FALSE otherwise.
+ */
+gboolean gm_entry_text_is_valid (GmEntry *entry);
+
+
+/** Set if the entry content can be empty.
+ * @param The GmEntry
+ * @param The allow-empty property.
+ */
+void gm_entry_set_allow_empty (GmEntry *self,
+                               gboolean allow_empty);
+
+
+/** Check if the GmEntry content may be emty.
+ * @param The GmEntry
+ * @return TRUE if the GmEntry content may be empty, FALSE otherwise.
+ */
+gboolean gm_entry_get_allow_empty (GmEntry *self);
+
+
+/* Signals emitted by that widget :
+ *
+ * - "validity-changed"
+ *
+ */
+
+/* Properties of that widget :
+ *
+ * - "allow-empty": Defaults to TRUE.
+ *
+ */
+
+
+/* GObject thingies */
+struct _GmEntry
+{
+  GtkEntry parent;
+
+  GmEntryPrivate *priv;
+};
+
+struct _GmEntryClass
+{
+  GtkEntryClass parent;
+
+  void (*valid) (GmEntry* self);
+};
+
+#define GM_TYPE_ENTRY (gm_entry_get_type ())
+
+#define GM_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GM_TYPE_ENTRY, GmEntry))
+
+#define GM_IS_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GM_TYPE_ENTRY))
+
+#define GM_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GM_TYPE_ENTRY, GmEntryClass))
+
+#define GM_IS_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GM_TYPE_ENTRY))
+
+#define GM_ENTRY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GM_TYPE_ENTRY, GmEntryClass))
+
+GType gm_entry_get_type ();
+
+G_END_DECLS
+
+#endif


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