+/** @brief Text should be editable */
+#define DETAIL_EDITABLE 2
+
+/** @brief Add a row to the user detail table */
+static void users_detail_generic(const char *title,
+ GtkWidget *selector) {
+ const int row = users_details_row++;
+ GtkWidget *const label = gtk_label_new(title);
+ gtk_misc_set_alignment(GTK_MISC(label), 1, 0);
+ gtk_table_attach(GTK_TABLE(users_details_table),
+ label,
+ 0, 1, /* left/right_attach */
+ row, row+1, /* top/bottom_attach */
+ GTK_FILL, /* xoptions */
+ 0, /* yoptions */
+ 1, 1); /* x/ypadding */
+ gtk_table_attach(GTK_TABLE(users_details_table),
+ selector,
+ 1, 2, /* left/right_attach */
+ row, row + 1, /* top/bottom_attach */
+ GTK_EXPAND|GTK_FILL, /* xoptions */
+ GTK_FILL, /* yoptions */
+ 1, 1); /* x/ypadding */
+}
+
+static void users_entry_changed(GtkEditable attribute((unused)) *editable,
+ gpointer attribute((unused)) user_data) {
+ users_details_sensitize_all();
+}
+
+/** @brief Add a row to the user details table
+ * @param entryp Where to put GtkEntry
+ * @param title Label for this row
+ * @param value Initial value or NULL
+ * @param flags Flags word
+ */
+static void users_add_detail(GtkWidget **entryp,
+ const char *title,
+ const char *value,
+ unsigned flags) {
+ GtkWidget *entry;
+
+ if(!(entry = *entryp)) {
+ *entryp = entry = gtk_entry_new();
+ g_signal_connect(entry, "changed",
+ G_CALLBACK(users_entry_changed), 0);
+ users_detail_generic(title, entry);
+ }
+ gtk_entry_set_visibility(GTK_ENTRY(entry),
+ !!(flags & DETAIL_VISIBLE));
+ gtk_editable_set_editable(GTK_EDITABLE(entry),
+ !!(flags & DETAIL_EDITABLE));
+ gtk_entry_set_text(GTK_ENTRY(entry), value ? value : "");
+}
+
+/** @brief Add a checkbox for a right
+ * @param title Label for this row
+ * @param value Current value
+ * @param right Right bit
+ */
+static void users_add_right(const char *title,
+ rights_type value,
+ rights_type right) {
+ GtkWidget *check;
+ GtkWidget **checkp = &users_details_rights[leftmost_bit(right)];
+
+ if(!(check = *checkp)) {
+ *checkp = check = gtk_check_button_new_with_label("");
+ users_detail_generic(title, check);
+ }
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), !!(value & right));
+}
+
+/** @brief Set sensitivity of particular mine/random rights bits */
+static void users_details_sensitize(rights_type r) {
+ const int bit = leftmost_bit(r);
+ const GtkWidget *all = users_details_rights[bit];
+ const int sensitive = (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(all))
+ && users_mode != MODE_NONE);
+
+ gtk_widget_set_sensitive(users_details_rights[bit + 1], sensitive);
+ gtk_widget_set_sensitive(users_details_rights[bit + 2], sensitive);
+}
+
+/** @brief Set sensitivity of everything in sight */
+static void users_details_sensitize_all(void) {
+ int n;
+ const char *report = 0;
+
+ for(n = 0; n < 32; ++n)
+ if(users_details_rights[n])
+ gtk_widget_set_sensitive(users_details_rights[n], users_mode != MODE_NONE);
+ gtk_widget_set_sensitive(users_details_name, users_mode != MODE_NONE);
+ gtk_widget_set_sensitive(users_details_email, users_mode != MODE_NONE);
+ gtk_widget_set_sensitive(users_details_password, users_mode != MODE_NONE);
+ gtk_widget_set_sensitive(users_details_password2, users_mode != MODE_NONE);
+ users_details_sensitize(RIGHT_MOVE_ANY);
+ users_details_sensitize(RIGHT_REMOVE_ANY);
+ users_details_sensitize(RIGHT_SCRATCH_ANY);
+ int apply_sensitive = 1;
+ if(users_mode == MODE_NONE)
+ apply_sensitive = 0;
+ else {
+ const char *name = gtk_entry_get_text(GTK_ENTRY(users_details_name));
+ const char *email = gtk_entry_get_text(GTK_ENTRY(users_details_email));
+ const char *pw = gtk_entry_get_text(GTK_ENTRY(users_details_password));
+ const char *pw2 = gtk_entry_get_text(GTK_ENTRY(users_details_password2));
+ /* Username must be filled in */
+ if(!*name) {
+ apply_sensitive = 0;
+ if(!report)
+ report = "Must fill in username";
+ }
+ /* Passwords must be nontrivial and match */
+ if(!*pw) {
+ apply_sensitive = 0;
+ if(!report)
+ report = "Must fill in password";
+ }
+ if(strcmp(pw, pw2)) {
+ apply_sensitive = 0;
+ if(!report)
+ report = "Passwords must match";
+ }
+ /* Email address must be somewhat valid */
+ if(*email) {
+ if(!email_valid(email)) {
+ apply_sensitive = 0;
+ report = "Invalid email address";
+ }