+/** @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();
+ 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;
+
+ 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);
+ gtk_widget_set_sensitive(users_apply_button, users_mode != MODE_NONE);
+ gtk_widget_set_sensitive(users_delete_button, !!users_selected);
+}
+
+/** @brief Called when an _ALL widget is toggled
+ *
+ * Modifies sensitivity of the corresponding _MINE and _RANDOM widgets. We
+ * just do the lot rather than trying to figure out which one changed,
+ */
+static void users_any_toggled(GtkToggleButton attribute((unused)) *togglebutton,
+ gpointer attribute((unused)) user_data) {
+ users_details_sensitize_all();
+}
+
+/** @brief Add a checkbox for a three-right group
+ * @param title Label for this row
+ * @param bits Rights bits (not masked or normalized)
+ * @param mask Mask for this group (must be 7*2^n)
+ */
+static void users_add_right_group(const char *title,
+ rights_type bits,
+ rights_type mask) {
+ const uint32_t first = mask / 7;
+ const int bit = leftmost_bit(first);
+ GtkWidget **widgets = &users_details_rights[bit], *any, *mine, *rnd;
+
+ if(!*widgets) {
+ GtkWidget *hbox = gtk_hbox_new(FALSE, 2);
+
+ any = widgets[0] = gtk_check_button_new_with_label("Any");
+ mine = widgets[1] = gtk_check_button_new_with_label("Own");
+ rnd = widgets[2] = gtk_check_button_new_with_label("Random");
+ gtk_box_pack_start(GTK_BOX(hbox), any, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(hbox), mine, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(hbox), rnd, FALSE, FALSE, 0);
+ users_detail_generic(title, hbox);
+ g_signal_connect(any, "toggled", G_CALLBACK(users_any_toggled), NULL);
+ users_details_rights[bit] = any;
+ users_details_rights[bit + 1] = mine;
+ users_details_rights[bit + 2] = rnd;
+ } else {
+ any = widgets[0];
+ mine = widgets[1];
+ rnd = widgets[2];
+ }
+ /* Discard irrelevant bits */
+ bits &= mask;
+ /* Shift down to bits 0-2; the mask is always 3 contiguous bits */
+ bits >>= bit;
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(any), !!(bits & 1));
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(mine), !!(bits & 2));
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rnd), !!(bits & 4));
+}
+
+/** @brief Called when the details table is destroyed */
+static void users_details_destroyed(GtkWidget attribute((unused)) *widget,
+ GtkWidget attribute((unused)) **wp) {
+ users_details_table = 0;
+ g_object_unref(users_list);
+ users_list = 0;
+ users_details_name = 0;
+ users_details_email = 0;
+ users_details_password = 0;
+ users_details_password2 = 0;
+ memset(users_details_rights, 0, sizeof users_details_rights);
+ /* also users_selection? Not AFAICT; _get_selection does upref */
+}
+
+/** @brief Create or modify the user details table
+ * @param name User name (users_edit()) or NULL (users_add())
+ * @param email Email address
+ * @param rights User rights string
+ * @param password Password
+ */
+static void users_makedetails(const char *name,
+ const char *email,
+ const char *rights,
+ const char *password,
+ unsigned nameflags,
+ unsigned flags) {
+ rights_type r = 0;
+
+ /* Create the table if it doesn't already exist */
+ if(!users_details_table) {
+ users_details_table = gtk_table_new(4, 2, FALSE/*!homogeneous*/);
+ g_signal_connect(users_details_table, "destroy",
+ G_CALLBACK(users_details_destroyed), 0);
+ }
+
+ /* Create or update the widgets */
+ users_add_detail(&users_details_name, "Username", name,
+ (DETAIL_EDITABLE|DETAIL_VISIBLE) & nameflags);
+
+ users_add_detail(&users_details_email, "Email", email,
+ (DETAIL_EDITABLE|DETAIL_VISIBLE) & flags);
+
+ users_add_detail(&users_details_password, "Password", password,
+ DETAIL_EDITABLE & flags);
+ users_add_detail(&users_details_password2, "Password", password,
+ DETAIL_EDITABLE & flags);
+
+ parse_rights(rights, &r, 0);
+ users_add_right("Read operations", r, RIGHT_READ);
+ users_add_right("Play track", r, RIGHT_PLAY);
+ users_add_right_group("Move", r, RIGHT_MOVE__MASK);
+ users_add_right_group("Remove", r, RIGHT_REMOVE__MASK);
+ users_add_right_group("Scratch", r, RIGHT_SCRATCH__MASK);
+ users_add_right("Set volume", r, RIGHT_VOLUME);
+ users_add_right("Admin operations", r, RIGHT_ADMIN);
+ users_add_right("Rescan", r, RIGHT_RESCAN);
+ users_add_right("Register new users", r, RIGHT_REGISTER);
+ users_add_right("Modify own userinfo", r, RIGHT_USERINFO);
+ users_add_right("Modify track preferences", r, RIGHT_PREFS);
+ users_add_right("Modify global preferences", r, RIGHT_GLOBAL_PREFS);
+ users_add_right("Pause/resume tracks", r, RIGHT_PAUSE);
+ users_details_sensitize_all();
+}
+
+/** @brief Called when the 'add' button is pressed */