/*
* 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
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 */
}
/** @brief Called when A namepart lookup has completed */
-static void namepart_completed(void *v, const char *value) {
- struct callbackdata *cbd = v;
-
- D(("namepart_completed"));
- cache_put(&cachetype_string, cbd->u.key, value);
- ++namepart_completions_deferred;
+static void namepart_completed(void *v, const char *error, const char *value) {
+ if(error) {
+ gtk_label_set_text(GTK_LABEL(report_label), error);
+ } else {
+ cache_put(&cachetype_string, v, value);
+ ++namepart_completions_deferred;
+ }
namepart_completed_or_failed();
}
const char *context,
const char *part,
const char *key) {
- struct callbackdata *cbd;
-
++namepart_lookups_outstanding;
- cbd = xmalloc(sizeof *cbd);
- cbd->onerror = namepart_protocol_error;
- cbd->u.key = key;
disorder_eclient_namepart(client, namepart_completed,
- track, context, part, cbd);
+ track, context, part, (void *)key);
}
/** @brief Look up a namepart
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 */
/** @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
*/
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;
return q;
}
+static void move_completed(void attribute((unused)) *v,
+ const char *error) {
+ if(error)
+ popup_protocol_error(0, error);
+}
+
/** @brief Called when data is dropped */
static gboolean queue_drag_drop(GtkWidget attribute((unused)) *widget,
GdkDragContext *drag_context,
if(q != playing_track && selection_selected(ql->selection, q->id))
vector_append(&vec, (char *)q->id);
disorder_eclient_moveafter(client, id, vec.nvec, (const char **)vec.vec,
- 0/*completed*/, 0/*v*/);
+ move_completed, 0/*v*/);
gtk_drag_finish(drag_context, TRUE, TRUE, when);
/* Destroy dropzones */
remove_drag_targets(ql);
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
*
- * @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,
+ * 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.
+ *
+ * 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 */
/* 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);
}
}
&& selection_selected(ql->selection, playing_track->id));
}
+/** @brief Called when disorder_eclient_scratch completes */
+static void scratch_completed(void attribute((unused)) *v,
+ const char *error) {
+ if(error)
+ popup_protocol_error(0, error);
+}
+
/** @brief Scratch the playing track */
static void scratch_activate(GtkMenuItem attribute((unused)) *menuitem,
gpointer attribute((unused)) user_data) {
if(playing_track)
- disorder_eclient_scratch(client, playing_track->id, 0, 0);
+ disorder_eclient_scratch(client, playing_track->id, scratch_completed, 0);
}
/** @brief Determine whether the remove option should be sensitive */
|| count_selected_nonplaying(ql)));
}
+static void remove_completed(void attribute((unused)) *v,
+ const char *error) {
+ if(error)
+ popup_protocol_error(0, error);
+}
+
/** @brief Remove selected track(s) */
static void remove_activate(GtkMenuItem attribute((unused)) *menuitem,
gpointer user_data) {
/* Remove selected tracks */
for(q = ql->q; q; q = q->next)
if(selection_selected(ql->selection, q->id) && q != playing_track)
- disorder_eclient_remove(client, q->id, 0, 0);
+ disorder_eclient_remove(client, q->id, move_completed, 0);
} else if(q)
/* Remove just the hovered track */
- disorder_eclient_remove(client, q->id, 0, 0);
+ disorder_eclient_remove(client, q->id, remove_completed, 0);
}
/** @brief Determine whether the properties menu option should be sensitive */
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,
/* Play selected tracks */
for(q = ql->q; q; q = q->next)
if(selection_selected(ql->selection, q->id))
- disorder_eclient_play(client, q->track, 0, 0);
+ disorder_eclient_play(client, q->track, play_completed, 0);
} else if(q)
/* Nothing is selected, so play the hovered track */
- disorder_eclient_play(client, q->track, 0, 0);
+ disorder_eclient_play(client, q->track, play_completed, 0);
}
/* The queue --------------------------------------------------------------- */
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 }
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 }
};
{ "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 }
};
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"));
}
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 ------------------------------------------------------ */