[ekiga/ds-gtk-application] GmEntry: Added new GmEntry widget and use it in Forms.
- From: Damien Sandras <dsandras src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [ekiga/ds-gtk-application] GmEntry: Added new GmEntry widget and use it in Forms.
- Date: Sun, 26 Oct 2014 13:10:27 +0000 (UTC)
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]