[accounts-dialog] Improved account creation dialog
- From: Matthias Clasen <matthiasc src gnome org>
- To: svn-commits-list gnome org
- Cc:
- Subject: [accounts-dialog] Improved account creation dialog
- Date: Sat, 23 Jan 2010 02:53:41 +0000 (UTC)
commit 29f3799600ad444b023400424212c8b383988670
Author: Matthias Clasen <mclasen redhat com>
Date: Fri Jan 22 21:49:53 2010 -0500
Improved account creation dialog
It is now possible to set the account type when creating a new user,
and we generate several proposals for shortnames. The improved
shortname generation is based on code written by Milan Bouchet-Valat
for gnome-system-tools.
data/account-dialog.ui | 77 +++++++++--
src/um-account-dialog.c | 354 ++++++++++++++++++++++++++++++++++++++---------
src/um-user-manager.c | 16 ++-
src/um-user-manager.h | 3 +-
4 files changed, 368 insertions(+), 82 deletions(-)
---
diff --git a/data/account-dialog.ui b/data/account-dialog.ui
index e2cb9c1..2322683 100644
--- a/data/account-dialog.ui
+++ b/data/account-dialog.ui
@@ -1,13 +1,31 @@
<?xml version="1.0"?>
<interface>
+ <!-- interface-requires gtk+ 2.12 -->
+ <!-- interface-naming-policy toplevel-contextual -->
+ <object class="GtkListStore" id="shortname-model">
+ <columns>
+ <column type="gchararray"/>
+ </columns>
+ </object>
+ <object class="GtkListStore" id="account-type-model">
+ <columns>
+ <column type="gchararray"/>
+ <column type="gint"/>
+ </columns>
+ <data>
+ <row><col id="0" translatable="True" context="Account type">Standard</col><col id="1">0</col></row>
+ <row><col id="0" translatable="True" context="Account type">Administrator</col><col id="1">1</col></row>
+ <row><col id="0" translatable="True" context="Account type">Supervised</col><col id="1">2</col></row>
+ </data>
+ </object>
<object class="GtkDialog" id="dialog">
<property name="border_width">5</property>
+ <property name="title"> </property>
<property name="resizable">False</property>
<property name="modal">True</property>
<property name="window_position">center-on-parent</property>
<property name="type_hint">dialog</property>
<property name="has_separator">False</property>
- <property name="title"> </property>
<child internal-child="vbox">
<object class="GtkVBox" id="content-area">
<property name="visible">True</property>
@@ -21,21 +39,22 @@
<child>
<object class="GtkTable" id="table1">
<property name="visible">True</property>
- <property name="n_rows">4</property>
+ <property name="n_rows">5</property>
<property name="n_columns">2</property>
<property name="column_spacing">6</property>
<property name="row_spacing">6</property>
<child>
- <object class="GtkEntry" id="shortname-entry">
+ <object class="GtkComboBoxEntry" id="shortname-combo">
<property name="visible">True</property>
<property name="can_focus">True</property>
- <property name="activates_default">True</property>
+ <property name="model">shortname-model</property>
+ <property name="text_column">0</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
- <property name="top_attach">3</property>
- <property name="bottom_attach">4</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
</packing>
</child>
<child>
@@ -48,8 +67,8 @@
</attributes>
</object>
<packing>
- <property name="top_attach">3</property>
- <property name="bottom_attach">4</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
<property name="x_options">GTK_FILL</property>
</packing>
</child>
@@ -112,8 +131,8 @@
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
- <property name="top_attach">2</property>
- <property name="bottom_attach">3</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
</packing>
</child>
<child>
@@ -126,9 +145,41 @@
</attributes>
</object>
<packing>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label7">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Account Type:</property>
+ <attributes>
+ <attribute name="foreground" value="#555555555555"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="account-type-combo">
+ <property name="visible">True</property>
+ <property name="model">account-type-model</property>
+ <child>
+ <object class="GtkCellRendererText" id="account-type-cell"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
- <property name="x_options">GTK_FILL</property>
</packing>
</child>
</object>
@@ -185,5 +236,9 @@
</child>
</object>
</child>
+ <action-widgets>
+ <action-widget response="0">cancel-button</action-widget>
+ <action-widget response="0">ok-button</action-widget>
+ </action-widgets>
</object>
</interface>
diff --git a/src/um-account-dialog.c b/src/um-account-dialog.c
index f0a3baf..8edc623 100644
--- a/src/um-account-dialog.c
+++ b/src/um-account-dialog.c
@@ -33,9 +33,13 @@
struct _UmAccountDialog {
GtkWidget *dialog;
- GtkWidget *shortname_entry;
+ GtkWidget *shortname_combo;
GtkWidget *name_entry;
+ GtkWidget *account_type_combo;
GtkWidget *ok_button;
+
+ gboolean valid_name;
+ gboolean valid_shortname;
};
static void
@@ -46,96 +50,314 @@ cancel_account_dialog (GtkButton *button,
}
static void
-user_created (GError *error, gpointer data)
-{
- if (error) {
- g_warning ("Creating user failed: %s", error->message);
- }
-}
-
-static void
accept_account_dialog (GtkButton *button,
UmAccountDialog *um)
{
UmUserManager *manager;
const gchar *shortname;
const gchar *name;
+ gint account_type;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
-
- shortname = gtk_entry_get_text (GTK_ENTRY (um->shortname_entry));
- name = gtk_entry_get_text (GTK_ENTRY (um->shortname_entry));
+ name = gtk_entry_get_text (GTK_ENTRY (um->name_entry));
+ shortname = gtk_combo_box_get_active_text (GTK_COMBO_BOX (um->shortname_combo));
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (um->account_type_combo));
+ gtk_combo_box_get_active_iter (GTK_COMBO_BOX (um->account_type_combo), &iter);
+ gtk_tree_model_get (model, &iter, 1, &account_type, -1);
manager = um_user_manager_ref_default ();
- um_user_manager_create_user (manager, shortname, name);
+ um_user_manager_create_user (manager, shortname, name, account_type);
g_object_unref (manager);
gtk_widget_hide (um->dialog);
}
-static void
-update_sensitivity (UmAccountDialog *um)
+static gboolean
+is_shortname_used (const gchar *shortname)
{
- const gchar *name, *shortname;
- gboolean can_create;
UmUserManager *manager;
UmUser *user;
- gchar *tip;
- name = gtk_entry_get_text (GTK_ENTRY (um->name_entry));
- shortname = gtk_entry_get_text (GTK_ENTRY (um->shortname_entry));
-
- can_create = name[0] != 0 && shortname[0] != 0;
- if (can_create) {
- manager = um_user_manager_ref_default ();
- user = um_user_manager_get_user (manager, shortname);
- g_object_unref (manager);
- if (user != NULL) {
- gtk_widget_error_bell (um->shortname_entry);
- gtk_entry_set_icon_from_stock (GTK_ENTRY (um->shortname_entry),
- GTK_ENTRY_ICON_SECONDARY,
- GTK_STOCK_DIALOG_ERROR);
- tip = g_strdup_printf (_("A user with the short name '%s' already exists."),
- shortname);
- gtk_entry_set_icon_tooltip_text (GTK_ENTRY (um->shortname_entry),
- GTK_ENTRY_ICON_SECONDARY,
- tip);
- g_free (tip);
-
- can_create = FALSE;
- }
- else {
- gtk_entry_set_icon_from_pixbuf (GTK_ENTRY (um->shortname_entry),
- GTK_ENTRY_ICON_SECONDARY,
- NULL);
- }
- }
+ manager = um_user_manager_ref_default ();
+ user = um_user_manager_get_user (manager, shortname);
+ g_object_unref (manager);
- gtk_widget_set_sensitive (um->ok_button, can_create);
+ return user != NULL;
}
static void
-shortname_changed (GtkWidget *entry,
- GParamSpec *pspec,
+shortname_changed (GtkComboBox *combo,
UmAccountDialog *um)
{
- update_sensitivity (um);
+ gboolean in_use;
+ gboolean empty;
+ gboolean valid;
+ const gchar *shortname;
+ const gchar *c;
+ gchar *tip;
+ GtkWidget *entry;
+
+ shortname = gtk_combo_box_get_active_text (combo);
+
+ in_use = is_shortname_used (shortname);
+ empty = strlen (shortname) == 0;
+ valid = TRUE;
+
+ if (!in_use && !empty) {
+ /* First char must be a letter, and it must only composed
+ * of ASCII letters, digits, and a '.', '-', '_'
+ */
+ for (c = shortname; *c; c++) {
+ if ((c == shortname && !g_ascii_islower (*c)) ||
+ !(g_ascii_isdigit (*c) || g_ascii_islower (*c) ||
+ *c == '.' || *c == '-' || *c == '_' )) {
+ valid = FALSE;
+ }
+ }
+ }
+
+ um->valid_shortname = !empty && !in_use && valid;
+ gtk_widget_set_sensitive (um->ok_button, um->valid_name && um->valid_shortname);
+
+ entry = gtk_bin_get_child (GTK_BIN (combo));
+
+ if (!empty && (in_use || !valid)) {
+ gtk_entry_set_icon_from_stock (GTK_ENTRY (entry),
+ GTK_ENTRY_ICON_SECONDARY,
+ GTK_STOCK_DIALOG_ERROR);
+ if (in_use) {
+ tip = g_strdup_printf (_("A user with the short name '%s' already exists."),
+ shortname);
+ }
+ else if (!g_ascii_islower (shortname[0])) {
+ tip = g_strdup (_("The short name must start with a lowercase letter."));
+ }
+ else {
+ tip = g_strdup (_("The short name must consist of:\n"
+ " \xe2\x9e\xa3 lower case letters from the English alphabet\n"
+ " \xe2\x9e\xa3 digits\n"
+ " \xe2\x9e\xa3 any of the characters '.', '-' and '_'"));
+ }
+
+ gtk_entry_set_icon_tooltip_text (GTK_ENTRY (entry),
+ GTK_ENTRY_ICON_SECONDARY,
+ tip);
+ g_free (tip);
+ }
+ else {
+ gtk_entry_set_icon_from_pixbuf (GTK_ENTRY (entry),
+ GTK_ENTRY_ICON_SECONDARY,
+ NULL);
+ }
}
static void
-name_changed (GtkWidget *entry,
+name_changed (GtkEntry *name_entry,
GParamSpec *pspec,
UmAccountDialog *um)
{
- const gchar *name;
- gchar *shortname;
+ GtkWidget *entry;
+ GtkTreeModel *model;
+ gboolean in_use;
+ const char *name;
+ char *lc_name, *ascii_name, *stripped_name;
+ char **words1;
+ char **words2 = NULL;
+ char **w1, **w2;
+ char *c;
+ char *unicode_fallback = "?";
+ GString *first_word, *last_word;
+ GString *item1, *item2, *item3, *item4;
+ int len;
+ int nwords1, nwords2, i;
+ GHashTable *items;
+
+ entry = gtk_bin_get_child (GTK_BIN (um->shortname_combo));
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (um->shortname_combo));
+ gtk_list_store_clear (GTK_LIST_STORE (model));
+
+ name = gtk_entry_get_text (GTK_ENTRY (name_entry));
+
+ um->valid_name = (strlen (name) > 0);
+ gtk_widget_set_sensitive (um->ok_button, um->valid_name && um->valid_shortname);
+
+ if (!um->valid_name) {
+ gtk_entry_set_text (GTK_ENTRY (entry), "");
+ return;
+ }
- name = gtk_entry_get_text (GTK_ENTRY (um->name_entry));
+ ascii_name = g_convert_with_fallback (name, -1, "ASCII//TRANSLIT", "UTF-8",
+ unicode_fallback, NULL, NULL, NULL);
+
+ lc_name = g_ascii_strdown (ascii_name, -1);
+
+ /* remove all non ASCII alphanumeric chars from the name,
+ * apart from the few allowed symbols
+ */
+ stripped_name = g_strnfill (strlen (lc_name) + 1, '\0');
+ i = 0;
+ for (c = lc_name; *c; c++) {
+ if (!(g_ascii_isdigit (*c) || g_ascii_islower (*c) ||
+ *c == ' ' || *c == '-' || *c == '.' || *c == '_' ||
+ /* used to track invalid words, removed below */
+ *c == '?') )
+ continue;
+
+ stripped_name[i] = *c;
+ i++;
+ }
- shortname = um_compute_short_name (name);
- gtk_entry_set_text (GTK_ENTRY (um->shortname_entry), shortname);
- g_free (shortname);
+ if (strlen (stripped_name) == 0) {
+ g_free (ascii_name);
+ g_free (lc_name);
+ g_free (stripped_name);
+ return;
+ }
+
+ /* we split name on spaces, and then on dashes, so that we can treat
+ * words linked with dashes the same way, i.e. both fully shown, or
+ * both abbreviated
+ */
+ words1 = g_strsplit_set (stripped_name, " ", -1);
+ len = g_strv_length (words1);
+
+ g_free (ascii_name);
+ g_free (lc_name);
+ g_free (stripped_name);
+
+ /* Concatenate the whole first word with the first letter of each
+ * word (item1), and the last word with the first letter of each
+ * word (item2). item3 and item4 are symmetrical respectively to
+ * item1 and item2.
+ *
+ * Constant 5 is the max reasonable number of words we may get when
+ * splitting on dashes, since we can't guess it at this point,
+ * and reallocating would be too bad.
+ */
+ item1 = g_string_sized_new (strlen (words1[0]) + len - 1 + 5);
+ item3 = g_string_sized_new (strlen (words1[0]) + len - 1 + 5);
+
+ item2 = g_string_sized_new (strlen (words1[len - 1]) + len - 1 + 5);
+ item4 = g_string_sized_new (strlen (words1[len - 1]) + len - 1 + 5);
+
+ /* again, guess at the max size of names */
+ first_word = g_string_sized_new (20);
+ last_word = g_string_sized_new (20);
+
+ nwords1 = 0;
+ nwords2 = 0;
+ for (w1 = words1; *w1; w1++) {
+ if (strlen (*w1) == 0)
+ continue;
+
+ /* skip words with string '?', most likely resulting
+ * from failed transliteration to ASCII
+ */
+ if (strstr (*w1, unicode_fallback) != NULL)
+ continue;
+
+ nwords1++; /* count real words, excluding empty string */
+
+ words2 = g_strsplit_set (*w1, "-", -1);
+ /* reset last word if a new non-empty word has been found */
+ if (strlen (*words2) > 0)
+ last_word = g_string_set_size (last_word, 0);
+
+ for (w2 = words2; *w2; w2++) {
+ if (strlen (*w2) == 0)
+ continue;
+
+ nwords2++;
+
+ /* part of the first "toplevel" real word */
+ if (nwords1 == 1) {
+ item1 = g_string_append (item1, *w2);
+ first_word = g_string_append (first_word, *w2);
+ }
+ else {
+ item1 = g_string_append_unichar (item1,
+ g_utf8_get_char (*w2));
+ item3 = g_string_append_unichar (item3,
+ g_utf8_get_char (*w2));
+ }
+
+ /* not part of the last "toplevel" word */
+ if (w1 != words1 + len - 1) {
+ item2 = g_string_append_unichar (item2,
+ g_utf8_get_char (*w2));
+ item4 = g_string_append_unichar (item4,
+ g_utf8_get_char (*w2));
+ }
+
+ /* always save current word so that we have it if last one reveals empty */
+ last_word = g_string_append (last_word, *w2);
+ }
+
+ g_strfreev (words2);
+ }
+ item2 = g_string_append (item2, last_word->str);
+ item3 = g_string_append (item3, first_word->str);
+ item4 = g_string_prepend (item4, last_word->str);
+
+ items = g_hash_table_new (g_str_hash, g_str_equal);
+
+ in_use = is_shortname_used (item1->str);
+ if (nwords2 > 0 && !in_use && !g_ascii_isdigit (item1->str[0])) {
+ gtk_combo_box_append_text (GTK_COMBO_BOX (um->shortname_combo), item1->str);
+ g_hash_table_insert (items, item1->str, item1->str);
+ }
- update_sensitivity (um);
+ /* if there's only one word, would be the same as item1 */
+ if (nwords2 > 1) {
+ /* add other items */
+ in_use = is_shortname_used (item2->str);
+ if (!in_use && !g_ascii_isdigit (item2->str[0]) &&
+ !g_hash_table_lookup (items, item2->str)) {
+ gtk_combo_box_append_text (GTK_COMBO_BOX (um->shortname_combo), item2->str);
+ g_hash_table_insert (items, item2->str, item2->str);
+ }
+
+ in_use = is_shortname_used (item3->str);
+ if (!in_use && !g_ascii_isdigit (item3->str[0]) &&
+ !g_hash_table_lookup (items, item3->str)) {
+ gtk_combo_box_append_text (GTK_COMBO_BOX (um->shortname_combo), item3->str);
+ g_hash_table_insert (items, item3->str, item3->str);
+ }
+
+ in_use = is_shortname_used (item4->str);
+ if (!in_use && !g_ascii_isdigit (item4->str[0]) &&
+ !g_hash_table_lookup (items, item4->str)) {
+ gtk_combo_box_append_text (GTK_COMBO_BOX (um->shortname_combo), item4->str);
+ g_hash_table_insert (items, item4->str, item4->str);
+ }
+
+ /* add the last word */
+ in_use = is_shortname_used (last_word->str);
+ if (!in_use && !g_ascii_isdigit (last_word->str[0]) &&
+ !g_hash_table_lookup (items, last_word->str)) {
+ gtk_combo_box_append_text (GTK_COMBO_BOX (um->shortname_combo), last_word->str);
+ g_hash_table_insert (items, last_word->str, last_word->str);
+ }
+
+ /* ...and the first one */
+ in_use = is_shortname_used (first_word->str);
+ if (!in_use && !g_ascii_isdigit (first_word->str[0]) &&
+ !g_hash_table_lookup (items, first_word->str)) {
+ gtk_combo_box_append_text (GTK_COMBO_BOX (um->shortname_combo), first_word->str);
+ g_hash_table_insert (items, first_word->str, first_word->str);
+ }
+ }
+
+ gtk_combo_box_set_active (GTK_COMBO_BOX (um->shortname_combo), 0);
+ g_hash_table_destroy (items);
+ g_strfreev (words1);
+ g_string_free (first_word, TRUE);
+ g_string_free (last_word, TRUE);
+ g_string_free (item1, TRUE);
+ g_string_free (item2, TRUE);
+ g_string_free (item3, TRUE);
+ g_string_free (item4, TRUE);
}
UmAccountDialog *
@@ -150,7 +372,7 @@ um_account_dialog_new (void)
builder = gtk_builder_new ();
filename = UIDIR "/account-dialog.ui";
- if (!g_file_test (filename, G_FILE_TEST_EXISTS))
+ if (g_file_test (filename, G_FILE_TEST_EXISTS))
filename = "../data/account-dialog.ui";
if (!gtk_builder_add_from_file (builder, filename, &error)) {
g_error ("%s", error->message);
@@ -174,10 +396,10 @@ um_account_dialog_new (void)
G_CALLBACK (accept_account_dialog), um);
gtk_widget_grab_default (widget);
- widget = (GtkWidget *) gtk_builder_get_object (builder, "shortname-entry");
- g_signal_connect (widget, "notify::text",
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "shortname-combo");
+ g_signal_connect (widget, "changed",
G_CALLBACK (shortname_changed), um);
- um->shortname_entry = widget;
+ um->shortname_combo = widget;
widget = (GtkWidget *) gtk_builder_get_object (builder, "name-entry");
g_signal_connect (widget, "notify::text",
@@ -186,6 +408,9 @@ um_account_dialog_new (void)
um->ok_button = (GtkWidget *) gtk_builder_get_object (builder, "ok-button");
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "account-type-combo");
+ um->account_type_combo = widget;
+
return um;
}
@@ -201,11 +426,14 @@ um_account_dialog_show (UmAccountDialog *um,
GtkWindow *parent)
{
gtk_entry_set_text (GTK_ENTRY (um->name_entry), "");
- gtk_entry_set_text (GTK_ENTRY (um->shortname_entry), "");
+ gtk_entry_set_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (um->shortname_combo))), "");
+ gtk_combo_box_set_active (GTK_COMBO_BOX (um->account_type_combo), 0);
gtk_window_set_transient_for (GTK_WINDOW (um->dialog), parent);
gtk_window_present (GTK_WINDOW (um->dialog));
gtk_widget_grab_focus (um->name_entry);
+
+ um->valid_name = um->valid_shortname = TRUE;
}
diff --git a/src/um-user-manager.c b/src/um-user-manager.c
index 4644d6d..695037c 100644
--- a/src/um-user-manager.c
+++ b/src/um-user-manager.c
@@ -266,12 +266,14 @@ um_user_manager_ref_default (void)
void
um_user_manager_create_user (UmUserManager *manager,
const char *user_name,
- const char *real_name)
+ const char *real_name,
+ gint account_type)
{
dbus_g_proxy_call_no_reply (manager->proxy,
"CreateUser",
G_TYPE_STRING, user_name,
G_TYPE_STRING, real_name,
+ G_TYPE_INT, account_type,
G_TYPE_INVALID);
}
@@ -313,14 +315,14 @@ UmUser *
um_user_manager_get_user_by_id (UmUserManager *manager,
uid_t uid)
{
- struct passwd *pwent;
+ struct passwd *pwent;
- pwent = getpwuid (uid);
- if (!pwent) {
- return NULL;
- }
+ pwent = getpwuid (uid);
+ if (!pwent) {
+ return NULL;
+ }
- return um_user_manager_get_user (manager, pwent->pw_name);
+ return um_user_manager_get_user (manager, pwent->pw_name);
}
gboolean
diff --git a/src/um-user-manager.h b/src/um-user-manager.h
index 3c117af..fe8b193 100644
--- a/src/um-user-manager.h
+++ b/src/um-user-manager.h
@@ -75,7 +75,8 @@ UmUser * um_user_manager_get_user_by_id (UmUserManager *manager
uid_t uid);
void um_user_manager_create_user (UmUserManager *manager,
const char *user_name,
- const char *real_name);
+ const char *real_name,
+ gint account_type);
void um_user_manager_delete_user (UmUserManager *manager,
UmUser *user,
gboolean remove_files);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]