chiark / gitweb /
Make the Disobedience login window a bit saner. There is now just a
[disorder] / disobedience / queue.c
index 4d756c504a8a5a9d2dc6deed545c41e901e20dbb..6008e4ed32eb91d10d44cd8ef354165f776e8ef9 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * This file is part of DisOrder
- * Copyright (C) 2006,  2007 Richard Kettlewell
+ * Copyright (C) 2006-2008 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
  * These only exist while the drag proceeds, as otherwise they steal events
  * from more deserving widgets.  (It might work to hide them when not in use
  * too but this way around the d+d code is a bit more self-contained.)
+ *
+ * NB that while in the server the playing track is not in the queue, in
+ * Disobedience, the playing does live in @c ql_queue.q, despite its different
+ * status to everything else found in that list.
  */
 
 #include "disobedience.h"
+#include "charset.h"
 
 /** @brief Horizontal padding for queue cells */
 #define HCELLPADDING 4
@@ -193,6 +198,7 @@ struct queuelike {
   struct queue_menuitem *menuitems;     /**< @brief menu items */
   GtkWidget *dragmark;                  /**< @brief drag destination marker */
   GtkWidget **dropzones;                /**< @brief drag targets */
+  int ndropzones;                       /**< @brief number of drag targets */
 
   /* State */
   struct queue_entry *q;                /**< @brief head of queue */
@@ -430,7 +436,8 @@ static GtkWidget *column_namepart(const struct queuelike
                                   const char *data) {
   D(("column_namepart"));
   NW(label);
-  return gtk_label_new(namepart(q->track, "display", data));
+  return gtk_label_new(truncate_for_display(namepart(q->track, "display", data),
+                                            config->short_display));
 }
 
 /** @brief Compute the length field */
@@ -508,13 +515,12 @@ static void update_queue(struct queuelike *ql, struct queue_entry *newq) {
 
 /** @brief Wrap up a widget for putting into the queue or title
  * @param label Label to contain
- * @param color Pointer to color
+ * @param style Pointer to style to use
  * @param wp Updated with maximum width (or NULL)
  * @return New widget
  */
 static GtkWidget *wrap_queue_cell(GtkWidget *label,
-                                  const GdkColor *bgcolor,
-                                  const GdkColor *fgcolor,
+                                  GtkStyle *style,
                                   int *wp) {
   GtkRequisition req;
   GtkWidget *bg;
@@ -533,12 +539,8 @@ static GtkWidget *wrap_queue_cell(GtkWidget *label,
     if(req.width > *wp) *wp = req.width;
   }
   /* Set colors */
-  gtk_widget_modify_bg(bg, GTK_STATE_NORMAL, bgcolor);
-  gtk_widget_modify_bg(bg, GTK_STATE_SELECTED, &selected_bg);
-  gtk_widget_modify_bg(bg, GTK_STATE_PRELIGHT, &selected_bg);
-  gtk_widget_modify_fg(label, GTK_STATE_NORMAL, fgcolor);
-  gtk_widget_modify_fg(label, GTK_STATE_SELECTED, &selected_fg);
-  gtk_widget_modify_fg(label, GTK_STATE_PRELIGHT, &selected_fg);
+  gtk_widget_set_style(bg, style);
+  gtk_widget_set_style(label, style);
   return bg;
 }
 
@@ -547,22 +549,20 @@ static GtkWidget *get_queue_cell(struct queuelike *ql,
                                  const struct queue_entry *q,
                                  int row,
                                  int col,
-                                 const GdkColor *bgcolor,
-                                 const GdkColor *fgcolor,
+                                 GtkStyle *style,
                                  int *wp) {
   GtkWidget *label;
   D(("get_queue_cell %d %d", row, col));
   label = ql->columns[col].widget(ql, q, ql->columns[col].data);
   gtk_misc_set_alignment(GTK_MISC(label), ql->columns[col].xalign, 0);
-  return wrap_queue_cell(label, bgcolor, fgcolor, wp);
+  return wrap_queue_cell(label, style, wp);
 }
 
 /** @brief Add a padding cell to the end of a row */
-static GtkWidget *get_padding_cell(const GdkColor *bgcolor,
-                                   const GdkColor *fgcolor) {
+static GtkWidget *get_padding_cell(GtkStyle *style) {
   D(("get_padding_cell"));
   NW(label);
-  return wrap_queue_cell(gtk_label_new(""), bgcolor, fgcolor, 0);
+  return wrap_queue_cell(gtk_label_new(""), style, 0);
 }
 
 /* User button press and menu ---------------------------------------------- */
@@ -703,6 +703,16 @@ void queue_select_all(struct queuelike *ql) {
   set_widget_states(ql);
 }
 
+/** @brief Deselect all entries in a queue */
+void queue_select_none(struct queuelike *ql) {
+  struct queue_entry *qq;
+
+  for(qq = ql->q; qq; qq = qq->next)
+    selection_set(ql->selection, qq->id, 0);
+  ql->last_click = 0;
+  set_widget_states(ql);
+}
+
 /** @brief Pop up properties for selected tracks */
 void queue_properties(struct queuelike *ql) {
   struct vector v;
@@ -813,7 +823,7 @@ static gboolean queue_drag_motion(GtkWidget attribute((unused)) *widget,
       g_signal_connect(ql->dragmark, "destroy",
                        G_CALLBACK(gtk_widget_destroyed), &ql->dragmark);
       gtk_widget_set_size_request(ql->dragmark, 10240, row ? 4 : 2);
-      gtk_widget_modify_bg(ql->dragmark, GTK_STATE_NORMAL, &drag_target);
+      gtk_widget_set_style(ql->dragmark, drag_style);
       gtk_layout_put(GTK_LAYOUT(ql->mainlayout), ql->dragmark, 0, 
                      (row + 1) * ql->mainrowheight - !!row);
     } else
@@ -838,15 +848,23 @@ static void queue_drag_leave(GtkWidget attribute((unused)) *widget,
     gtk_widget_hide(ql->dragmark);
 }
 
-/** @brief Add a drag target at position @p y
+/** @brief Add a drag target
+ * @param ql The queue-like (in practice this is always @ref ql_queue)
+ * @param y The Y coordinate to place the drag target
+ * @param id Track to insert moved tracks after, or NULL
+ *
+ * Adds a drop zone at Y coordinate @p y, which is assumed to lie between two
+ * tracks (or before the start of the queue or after the end of the queue).  If
+ * tracks are dragged into this dropzone then they will be moved @em after
+ * track @p id, or to the start of the queue if @p id is NULL.
  *
- * @p id is the track to insert the moved tracks after, and might be 0 to
- * insert before the start. */
-static void add_drag_target(struct queuelike *ql, int y, int row,
+ * We remember all the dropzones in @c ql->dropzones so they can be destroyed
+ * later.
+ */
+static void add_drag_target(struct queuelike *ql, int y,
                             const char *id) {
   GtkWidget *eventbox;
 
-  assert(ql->dropzones[row] == 0);
   NW(event_box);
   eventbox = gtk_event_box_new();
   /* Make the target zone invisible */
@@ -871,40 +889,43 @@ static void add_drag_target(struct queuelike *ql, int y, int row,
   /* The widget needs to be shown to receive drags */
   gtk_widget_show(eventbox);
   /* Remember the drag targets */
-  ql->dropzones[row] = eventbox;
+  ql->dropzones[ql->ndropzones] = eventbox;
   g_signal_connect(eventbox, "destroy",
-                   G_CALLBACK(gtk_widget_destroyed), &ql->dropzones[row]);
+                   G_CALLBACK(gtk_widget_destroyed),
+                   &ql->dropzones[ql->ndropzones]);
+  ++ql->ndropzones;
 }
 
 /** @brief Create dropzones for dragging into */
 static void add_drag_targets(struct queuelike *ql) {
-  int row, y;
+  int y;
   struct queue_entry *q;
 
   /* Create an array to store the widgets */
   ql->dropzones = xcalloc(ql->nrows, sizeof (GtkWidget *));
+  ql->ndropzones = 0;
   y = 0;
   /* Add a drag target before the first row provided it's not the playing
    * track */
   if(!playing_track || ql->q != playing_track)
-    add_drag_target(ql, 0, 0, 0);
+    add_drag_target(ql, 0, 0);
   /* Put a drag target at the bottom of every row */
-  for(q = ql->q, row = 0; q; q = q->next, ++row) {
+  for(q = ql->q; q; q = q->next) {
     y += ql->mainrowheight;
-    add_drag_target(ql, y, row, q->id);
+    add_drag_target(ql, y, q->id);
   }
 }
 
 /** @brief Remove the dropzones */
 static void remove_drag_targets(struct queuelike *ql) {
-  int row;
+  int n;
 
-  for(row = 0; row < ql->nrows; ++row) {
-    if(ql->dropzones[row]) {
+  for(n = 0; n < ql->ndropzones; ++n) {
+    if(ql->dropzones[n]) {
       DW(event_box);
-      gtk_widget_destroy(ql->dropzones[row]);
+      gtk_widget_destroy(ql->dropzones[n]);
     }
-    assert(ql->dropzones[row] == 0);
+    assert(ql->dropzones[n] == 0);
   }
 }
 
@@ -915,7 +936,7 @@ static void redisplay_queue(struct queuelike *ql) {
   struct queue_entry *q;
   int row, col;
   GList *c, *children;
-  const GdkColor *bgcolor;
+  GtkStyle *style;
   GtkRequisition req;  
   GtkWidget *w;
   int maxwidths[MAXCOLUMNS], x, y, titlerowheight;
@@ -955,16 +976,15 @@ static void redisplay_queue(struct queuelike *ql) {
     /* Construct the widgets */
     for(q = ql->q, row = 0; q; q = q->next, ++row) {
       /* Figure out the widget name for this row */
-      if(q == playing_track) bgcolor = &active_bg;
-      else bgcolor = row % 2 ? &even_bg : &odd_bg;
+      if(q == playing_track) style = active_style;
+      else style = row % 2 ? even_style : odd_style;
       /* Make the widget for each column */
       for(col = 0; col <= ql->ncolumns; ++col) {
         /* Create and store the widget */
         if(col < ql->ncolumns)
-          w = get_queue_cell(ql, q, row, col, bgcolor, &item_fg,
-                             &maxwidths[col]);
+          w = get_queue_cell(ql, q, row, col, style, &maxwidths[col]);
         else
-          w = get_padding_cell(bgcolor, &item_fg);
+          w = get_padding_cell(style);
         ql->cells[row * (ql->ncolumns + 1) + col] = w;
         /* Maybe mark it draggable */
         if(draggable_row(q)) {
@@ -1086,9 +1106,10 @@ static GtkWidget *queuelike(struct queuelike *ql,
   /* Create the layouts */
   NW(layout);
   ql->mainlayout = gtk_layout_new(0, 0);
-  gtk_widget_modify_bg(ql->mainlayout, GTK_STATE_NORMAL, &layout_bg);
+  gtk_widget_set_style(ql->mainlayout, layout_style);
   NW(layout);
   ql->titlelayout = gtk_layout_new(0, 0);
+  gtk_widget_set_style(ql->titlelayout, title_style);
   /* Scroll the layouts */
   ql->mainscroll = mainscroll = scroll_widget(ql->mainlayout);
   titlescroll = scroll_widget(ql->titlelayout);
@@ -1103,10 +1124,10 @@ static GtkWidget *queuelike(struct queuelike *ql,
     NW(label);
     label = gtk_label_new(ql->columns[col].name);
     gtk_misc_set_alignment(GTK_MISC(label), ql->columns[col].xalign, 0);
-    ql->titlecells[col] = wrap_queue_cell(label, &title_bg, &title_fg, 0);
+    ql->titlecells[col] = wrap_queue_cell(label, title_style, 0);
     gtk_layout_put(GTK_LAYOUT(ql->titlelayout), ql->titlecells[col], 0, 0);
   }
-  ql->titlecells[col] = get_padding_cell(&title_bg, &title_fg);
+  ql->titlecells[col] = get_padding_cell(title_style);
   gtk_layout_put(GTK_LAYOUT(ql->titlelayout), ql->titlecells[col], 0, 0);
   /* Pack the lot together in a vbox */
   NW(vbox);
@@ -1241,6 +1262,21 @@ static void selectall_activate(GtkMenuItem attribute((unused)) *menuitem,
   queue_select_all(mii->ql);
 }
 
+/** @brief Determine whether the select none menu option should be sensitive */
+static int selectnone_sensitive(struct queuelike *ql,
+                                struct queue_menuitem attribute((unused)) *m,
+                                struct queue_entry attribute((unused)) *q) {
+  /* Sensitive if there is anything selected */
+  return hash_count(ql->selection) != 0;
+}
+
+/** @brief Select no tracks */
+static void selectnone_activate(GtkMenuItem attribute((unused)) *menuitem,
+                               gpointer user_data) {
+  const struct menuiteminfo *mii = user_data;
+  queue_select_none(mii->ql);
+}
+
 /** @brief Determine whether the play menu option should be sensitive */
 static int play_sensitive(struct queuelike *ql,
                           struct queue_menuitem attribute((unused)) *m,
@@ -1300,6 +1336,7 @@ static gboolean adjust_sofar(gpointer attribute((unused)) data) {
 static struct queue_menuitem queue_menu[] = {
   { "Track properties", properties_activate, properties_sensitive, 0, 0 },
   { "Select all tracks", selectall_activate, selectall_sensitive, 0, 0 },
+  { "Deselect all tracks", selectnone_activate, selectnone_sensitive, 0, 0 },
   { "Scratch track", scratch_activate, scratch_sensitive, 0, 0 },
   { "Remove track from queue", remove_activate, remove_sensitive, 0, 0 },
   { 0, 0, 0, 0, 0 }
@@ -1322,7 +1359,7 @@ GtkWidget *queue_widget(void) {
   /* Arrange periodic update of the so-far played field */
   g_timeout_add(1000/*ms*/, adjust_sofar, 0);
   /* Arrange a callback whenever the playing state changes */ 
-  register_monitor(playing_update, 0,  DISORDER_PLAYING|DISORDER_TRACK_PAUSED);
+  register_monitor(playing_update, 0, DISORDER_PLAYING|DISORDER_TRACK_PAUSED);
   register_reset(queue_update);
   /* We pass choose_update() as our notify function since the choose screen
    * marks tracks that are playing/in the queue. */
@@ -1372,6 +1409,7 @@ static struct queue_entry *fixup_recent(struct queue_entry *q) {
 static struct queue_menuitem recent_menu[] = {
   { "Track properties", properties_activate, properties_sensitive,0, 0 },
   { "Select all tracks", selectall_activate, selectall_sensitive, 0, 0 },
+  { "Deselect all tracks", selectnone_activate, selectnone_sensitive, 0, 0 },
   { 0, 0, 0, 0, 0 }
 };
 
@@ -1405,6 +1443,7 @@ static struct queue_menuitem added_menu[] = {
   { "Track properties", properties_activate, properties_sensitive, 0, 0 },
   { "Play track", play_activate, play_sensitive, 0, 0 },
   { "Select all tracks", selectall_activate, selectall_sensitive, 0, 0 },
+  { "Deselect all tracks", selectnone_activate, selectnone_sensitive, 0, 0 },
   { 0, 0, 0, 0, 0 }
 };
 
@@ -1463,6 +1502,12 @@ static int queue_selectall_sensitive(GtkWidget *w) {
   return !!queue_count_entries(g_object_get_data(G_OBJECT(w), "queue"));
 }
 
+static int queue_selectnone_sensitive(GtkWidget *w) {
+  struct queuelike *const ql = g_object_get_data(G_OBJECT(w), "queue");
+
+  return hash_count(ql->selection) != 0;
+}
+
 static void queue_properties_activate(GtkWidget *w) {
   queue_properties(g_object_get_data(G_OBJECT(w), "queue"));
 }
@@ -1471,11 +1516,17 @@ static void queue_selectall_activate(GtkWidget *w) {
   queue_select_all(g_object_get_data(G_OBJECT(w), "queue"));
 }
 
+static void queue_selectnone_activate(GtkWidget *w) {
+  queue_select_none(g_object_get_data(G_OBJECT(w), "queue"));
+}
+
 static const struct tabtype tabtype_queue = {
   queue_properties_sensitive,
   queue_selectall_sensitive,
+  queue_selectnone_sensitive,
   queue_properties_activate,
   queue_selectall_activate,
+  queue_selectnone_activate,
 };
 
 /* Other entry points ------------------------------------------------------ */