chiark / gitweb /
(Slightly scrappy) new playlist box
authorRichard Kettlewell <rjk@greenend.org.uk>
Sun, 22 Nov 2009 17:53:31 +0000 (17:53 +0000)
committerRichard Kettlewell <rjk@greenend.org.uk>
Sun, 22 Nov 2009 17:53:31 +0000 (17:53 +0000)
disobedience/playlists.c
lib/Makefile.am
lib/trackdb-int.h
lib/trackdb-playlists.c
lib/trackdb.c
lib/trackdb.h
lib/validity.c [new file with mode: 0644]
lib/validity.h [new file with mode: 0644]

index bf6d18584f087dfaf9897a6a28bd666f80fe6298..1a3e74faf46b9c984f2bb426361527283b61eb4f 100644 (file)
@@ -30,6 +30,7 @@
 #include "disobedience.h"
 #include "queue-generic.h"
 #include "popup.h"
+#include "validity.h"
 
 #if PLAYLISTS
 
@@ -194,6 +195,167 @@ static void menu_playlists_changed(const char attribute((unused)) *event,
                            nplaylists >= 0);
 }
 
+/* Popup to create a new playlist ------------------------------------------- */
+
+/** @brief New-playlist popup */
+static GtkWidget *playlist_new_window;
+
+/** @brief Text entry in new-playlist popup */
+static GtkWidget *playlist_new_entry;
+
+static GtkWidget *playlist_new_info;
+
+static GtkWidget *playlist_new_shared;
+static GtkWidget *playlist_new_public;
+static GtkWidget *playlist_new_private;
+
+/** @brief Called when 'ok' is clicked in new-playlist popup */
+static void playlist_new_ok(GtkButton attribute((unused)) *button,
+                            gpointer attribute((unused)) userdata) {
+}
+
+/** @brief Called when 'cancel' is clicked in new-playlist popup */
+static void playlist_new_cancel(GtkButton attribute((unused)) *button,
+                                gpointer attribute((unused)) userdata) {
+  gtk_widget_destroy(playlist_new_window);
+}
+
+/** @brief Buttons for new-playlist popup */
+static struct button playlist_new_buttons[] = {
+  {
+    .stock = GTK_STOCK_OK,
+    .clicked = playlist_new_ok,
+    .tip = "Create new playlist"
+  },
+  {
+    .stock = GTK_STOCK_CANCEL,
+    .clicked = playlist_new_cancel,
+    .tip = "Do not create new plalist"
+  }
+};
+#define NPLAYLIST_NEW_BUTTONS (sizeof playlist_new_buttons / sizeof *playlist_new_buttons)
+
+/** @brief Test whether the new-playlist window settings are valid */
+static const char *playlist_new_valid(void) {
+  gboolean shared, public, private;
+  g_object_get(playlist_new_shared, "active", &shared, (char *)NULL);
+  g_object_get(playlist_new_public, "active", &public, (char *)NULL);
+  g_object_get(playlist_new_private, "active", &private, (char *)NULL);
+  if(!(shared || public || private))
+    return "No type set.";
+  char *name = gtk_editable_get_chars(GTK_EDITABLE(playlist_new_entry),
+                                            0, -1);
+  if(!*name)
+    return "";
+  if(!valid_username(name))
+    return "Not a valid playlist name.";
+  /* Construct the full form of the name */
+  char *fullname;
+  if(shared)
+    fullname = name;
+  else
+    byte_xasprintf(&fullname, "%s.%s", config->username, name);
+  /* See if the result is valid */
+  if(playlist_parse_name(fullname, NULL, NULL))
+    return "Not a valid playlist name.";
+  /* See if the result clashes with an existing name */
+  for(int n = 0; n < nplaylists; ++n)
+    if(!strcmp(playlists[n], fullname)) {
+      if(shared)
+        return "A shared playlist with that name already exists.";
+      else
+        return "You already have a playlist with that name.";
+    }
+  /* As far as we can tell creation would work */
+  return NULL;
+}
+
+/** @brief Called when anything in the new-playlist popup changes
+ *
+ * We use this to set the activation state of the OK button.
+ */
+static void playlist_new_changed(void) {
+  const char *reason = playlist_new_valid();
+  gtk_widget_set_sensitive(playlist_new_buttons[0].widget,
+                           !reason);
+  gtk_label_set_text(GTK_LABEL(playlist_new_info), reason);
+}
+
+/** @brief Called when some radio button in the new-playlist popup changes */
+static void playlist_new_button_toggled(GtkToggleButton attribute((unused)) tb,
+                                        gpointer attribute((unused)) userdata) {
+  playlist_new_changed();
+}
+
+/** @brief Called when the text entry field in the new-playlist popup changes */
+static void playlist_new_entry_edited(GtkEditable attribute((unused)) *editable,
+                                      gpointer attribute((unused)) user_data) {
+  playlist_new_changed();
+}
+
+/** @brief Pop up a new window to enter the playlist name and details */
+static void playlist_new(void) {
+  assert(playlist_new_window == NULL);
+  playlist_new_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+  g_signal_connect(playlist_new_window, "destroy",
+                  G_CALLBACK(gtk_widget_destroyed), &playlist_new_window);
+  gtk_window_set_title(GTK_WINDOW(playlist_new_window), "Create new playlist");
+  /* Window will be modal, suppressing access to other windows */
+  gtk_window_set_modal(GTK_WINDOW(playlist_new_window), TRUE);
+
+  /* Window contents will use a table (grid) layout */
+  GtkWidget *table = gtk_table_new(3, 3, FALSE/*!homogeneous*/);
+
+  /* First row: playlist name */
+  gtk_table_attach_defaults(GTK_TABLE(table),
+                            gtk_label_new("Playlist name"),
+                            0, 1, 0, 1);
+  playlist_new_entry = gtk_entry_new();
+  g_signal_connect(playlist_new_entry, "changed",
+                   G_CALLBACK(playlist_new_entry_edited), NULL);
+  gtk_table_attach_defaults(GTK_TABLE(table),
+                            playlist_new_entry,
+                            1, 3, 0, 1);
+
+  /* Second row: radio buttons to choose type */
+  playlist_new_shared = gtk_radio_button_new_with_label(NULL, "shared");
+  playlist_new_public
+    = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(playlist_new_shared),
+                                                  "public");
+  playlist_new_private
+    = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(playlist_new_shared),
+                                                  "private");
+  g_signal_connect(playlist_new_shared, "toggled",
+                   G_CALLBACK(playlist_new_button_toggled), NULL);
+  g_signal_connect(playlist_new_public, "toggled",
+                   G_CALLBACK(playlist_new_button_toggled), NULL);
+  g_signal_connect(playlist_new_private, "toggled",
+                   G_CALLBACK(playlist_new_button_toggled), NULL);
+  gtk_table_attach_defaults(GTK_TABLE(table), playlist_new_shared, 0, 1, 1, 2);
+  gtk_table_attach_defaults(GTK_TABLE(table), playlist_new_public, 1, 2, 1, 2);
+  gtk_table_attach_defaults(GTK_TABLE(table), playlist_new_private, 2, 3, 1, 2);
+
+  /* Third row: info bar saying why not */
+  playlist_new_info = gtk_label_new("");
+  gtk_table_attach_defaults(GTK_TABLE(table), playlist_new_info,
+                            0, 3, 2, 3);
+
+  /* Fourth row: ok/cancel buttons */
+  GtkWidget *hbox = create_buttons_box(playlist_new_buttons,
+                                       NPLAYLIST_NEW_BUTTONS,
+                                       gtk_hbox_new(FALSE, 0));
+  gtk_table_attach_defaults(GTK_TABLE(table), hbox, 0, 3, 3, 4);
+
+  gtk_container_add(GTK_CONTAINER(playlist_new_window),
+                    frame_widget(table, NULL));
+
+  /* Set initial state of OK button */
+  playlist_new_changed();
+
+  /* Display the window */
+  gtk_widget_show_all(playlist_new_window);
+}
+
 /* Playlists window (list of playlists) ------------------------------------- */
 
 /** @brief (Re-)populate the playlist tree model */
@@ -247,12 +409,7 @@ static void playlists_add(GtkButton attribute((unused)) *button,
   /* Unselect whatever is selected */
   gtk_tree_selection_unselect_all(playlists_selection);
   fprintf(stderr, "playlists_add\n");/* TODO */
-  /* We need to pop up a window asking for:
-   * - the name for the playlist
-   * - whether it is to be a public, private or shared playlist
-   * Moreover we should keep track of the known playlists and grey out OK
-   * if the name is a clash (as well as if it's actually invalid).
-   */
+  playlist_new();
 }
 
 /** @brief Called when the 'Delete' button is pressed */
@@ -360,7 +517,6 @@ static gboolean playlists_keypress(GtkWidget attribute((unused)) *widget,
 /** @brief Called when the playlist window is destroyed */
 static void playlists_window_destroyed(GtkWidget attribute((unused)) *widget,
                                        GtkWidget **widget_pointer) {
-  fprintf(stderr, "playlists_window_destroy\n");
   destroy_queuelike(&ql_playlist);
   *widget_pointer = NULL;
 }
@@ -389,7 +545,7 @@ void edit_playlists(gpointer attribute((unused)) callback_data,
   g_signal_connect(playlists_window, "key-press-event",
                    G_CALLBACK(playlists_keypress), 0);
   /* default size is too small */
-  gtk_window_set_default_size(GTK_WINDOW(playlists_window), 240, 240);
+  gtk_window_set_default_size(GTK_WINDOW(playlists_window), 512, 240);
 
   GtkWidget *hbox = gtk_hbox_new(FALSE, 0);
   gtk_box_pack_start(GTK_BOX(hbox), playlists_window_list(),
index 3c1e2f107af9ccd903b74224ed9229e5776b6c5e..3505eb99ce6f2a2a5a9d9ce3a322ede675e1aba0 100644 (file)
@@ -91,6 +91,7 @@ libdisorder_a_SOURCES=charset.c charset.h             \
        unicode.h unicode.c                             \
        unidata.h unidata.c                             \
        vacopy.h                                        \
+       validity.c validity.h                           \
        vector.c vector.h                               \
        version.c version.h                             \
        wav.h wav.c                                     \
index b21b5d9f83349a8f95d3ee3d1c90cee391dbe704..3be725e32d4d9106a4faa5a6bf59e4d56ddc0191 100644 (file)
@@ -153,7 +153,6 @@ int trackdb_get_global_tid(const char *name,
 
 char **parsetags(const char *s);
 int tag_intersection(char **a, char **b);
-int valid_username(const char *user);
 
 #endif /* TRACKDB_INT_H */
 
index fd333938968eff847f2d9e7f9a45d06fc0b27897..27d33193feb7f6db8e7898785467178ed084a199 100644 (file)
@@ -33,6 +33,7 @@
 #include "configuration.h"
 #include "vector.h"
 #include "eventlog.h"
+#include "validity.h"
 
 static int trackdb_playlist_get_tid(const char *name,
                                     const char *who,
@@ -54,43 +55,6 @@ static int trackdb_playlist_delete_tid(const char *name,
                                        const char *who,
                                        DB_TXN *tid);
 
-/** @brief Parse a playlist name
- * @param name Playlist name
- * @param ownerp Where to put owner, or NULL
- * @param sharep Where to put default sharing, or NULL
- * @return 0 on success, -1 on error
- *
- * Playlists take the form USER.PLAYLIST or just PLAYLIST.  The PLAYLIST part
- * is alphanumeric and nonempty.  USER is a username (see valid_username()).
- */
-int playlist_parse_name(const char *name,
-                        char **ownerp,
-                        char **sharep) {
-  const char *dot = strchr(name, '.'), *share;
-  char *owner;
-
-  if(dot) {
-    /* Owned playlist */
-    owner = xstrndup(name, dot - name);
-    if(!valid_username(owner))
-      return -1;
-    if(!valid_username(dot + 1))
-      return -1;
-    share = "private";
-  } else {
-    /* Shared playlist */
-    if(!valid_username(name))
-      return -1;
-    owner = 0;
-    share = "shared";
-  }
-  if(ownerp)
-    *ownerp = owner;
-  if(sharep)
-    *sharep = xstrdup(share);
-  return 0;
-}
-
 /** @brief Check read access rights
  * @param name Playlist name
  * @param who Who wants to read
index d2ebe070687dc187d657e8f59af42687634fe294..628498a0cbcc89e7bfaaea6d6412246e8bdc5dff 100644 (file)
@@ -59,6 +59,7 @@
 #include "unidata.h"
 #include "base64.h"
 #include "sendmail.h"
+#include "validity.h"
 
 #define RESCAN "disorder-rescan"
 #define DEADLOCK "disorder-deadlock"
@@ -3013,32 +3014,6 @@ static int trusted(const char *user) {
   return n < config->trust.n;
 }
 
-/** @brief Return non-zero for a valid username
- * @param user Candidate username
- * @return Nonzero if it's valid
- *
- * Currently we only allow the letters and digits in ASCII.  We could be more
- * liberal than this but it is a nice simple test.  It is critical that
- * semicolons are never allowed.
- *
- * NB also used by playlist_parse_name() to validate playlist names!
- */
-int valid_username(const char *user) {
-  if(!*user)
-    return 0;
-  while(*user) {
-    const uint8_t c = *user++;
-    /* For now we are very strict */
-    if((c >= 'a' && c <= 'z')
-       || (c >= 'A' && c <= 'Z')
-       || (c >= '0' && c <= '9'))
-      /* ok */;
-    else
-      return 0;
-  }
-  return 1;
-}
-
 /** @brief Add a user
  * @param user Username
  * @param password Initial password or NULL
index 901a74a2b66aacf8c6cec1ba7c20b68d93f70efb..1d7c8e97cf02d94e25498a32527eee21622cef79 100644 (file)
@@ -184,9 +184,6 @@ void trackdb_add_rescanned(void (*rescanned)(void *ru),
                            void *ru);
 int trackdb_rescan_underway(void);
 
-int playlist_parse_name(const char *name,
-                        char **ownerp,
-                        char **sharep);
 int trackdb_playlist_get(const char *name,
                          const char *who,
                          char ***tracksp,
diff --git a/lib/validity.c b/lib/validity.c
new file mode 100644 (file)
index 0000000..408c4d5
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * This file is part of DisOrder
+ * Copyright (C) 2009 Richard Kettlewell
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+/** @file lib/validity.c
+ * @brief Various validity checks
+ */
+#include "common.h"
+#include "validity.h"
+
+#include "mem.h"
+
+/** @brief Parse a playlist name
+ * @param name Playlist name
+ * @param ownerp Where to put owner, or NULL
+ * @param sharep Where to put default sharing, or NULL
+ * @return 0 on success, -1 on error
+ *
+ * Playlists take the form USER.PLAYLIST or just PLAYLIST.  The PLAYLIST part
+ * is alphanumeric and nonempty.  USER is a username (see valid_username()).
+ */
+int playlist_parse_name(const char *name,
+                        char **ownerp,
+                        char **sharep) {
+  const char *dot = strchr(name, '.'), *share;
+  char *owner;
+
+  if(dot) {
+    /* Owned playlist */
+    owner = xstrndup(name, dot - name);
+    if(!valid_username(owner))
+      return -1;
+    if(!valid_username(dot + 1))
+      return -1;
+    share = "private";
+  } else {
+    /* Shared playlist */
+    if(!valid_username(name))
+      return -1;
+    owner = 0;
+    share = "shared";
+  }
+  if(ownerp)
+    *ownerp = owner;
+  if(sharep)
+    *sharep = xstrdup(share);
+  return 0;
+}
+
+/** @brief Return non-zero for a valid username
+ * @param user Candidate username
+ * @return Nonzero if it's valid
+ *
+ * Currently we only allow the letters and digits in ASCII.  We could be more
+ * liberal than this but it is a nice simple test.  It is critical that
+ * semicolons are never allowed.
+ *
+ * NB also used by playlist_parse_name() to validate playlist names!
+ */
+int valid_username(const char *user) {
+  if(!*user)
+    return 0;
+  while(*user) {
+    const uint8_t c = *user++;
+    /* For now we are very strict */
+    if((c >= 'a' && c <= 'z')
+       || (c >= 'A' && c <= 'Z')
+       || (c >= '0' && c <= '9'))
+      /* ok */;
+    else
+      return 0;
+  }
+  return 1;
+}
+
+/*
+Local Variables:
+c-basic-offset:2
+comment-column:40
+fill-column:79
+indent-tabs-mode:nil
+End:
+*/
diff --git a/lib/validity.h b/lib/validity.h
new file mode 100644 (file)
index 0000000..8ce2505
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * This file is part of DisOrder
+ * Copyright (C) 2009 Richard Kettlewell
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+/** @file lib/validity.c
+ * @brief Various validity checks
+ */
+#ifndef VALIDITY_H
+#define VALIDITY_H
+
+#include "common.h"
+#include "validity.h"
+
+int playlist_parse_name(const char *name,
+                        char **ownerp,
+                        char **sharep);
+int valid_username(const char *user);
+
+#endif /* VALIDITY_H */
+
+/*
+Local Variables:
+c-basic-offset:2
+comment-column:40
+fill-column:79
+indent-tabs-mode:nil
+End:
+*/