/*
* 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
const struct queue_entry *q,
const char *data);
static int draggable_row(const struct queue_entry *q);
+static void recent_changed(const char *event,
+ void *eventdata,
+ void *callbackdata);
+static void added_changed(const char *event,
+ void *eventdata,
+ void *callbackdata);
+static void queue_changed(const char *event,
+ void *eventdata,
+ void *callbackdata);
static const struct tabtype tabtype_queue; /* forward */
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;
+/** @brief Called when a namepart lookup has completed */
+static void namepart_completed(void *v, const char *error, const char *value) {
+ if(error) {
+ gtk_label_set_text(GTK_LABEL(report_label), error);
+ } else {
+ const char *key = v;
- D(("namepart_completed"));
- cache_put(&cachetype_string, cbd->u.key, value);
- ++namepart_completions_deferred;
+ cache_put(&cachetype_string, key, value);
+ ++namepart_completions_deferred;
+ }
namepart_completed_or_failed();
}
/** @brief Called when a length lookup has completed */
-static void length_completed(void *v, long l) {
- struct callbackdata *cbd = v;
- long *value;
-
- D(("namepart_completed"));
- value = xmalloc(sizeof *value);
- *value = l;
- cache_put(&cachetype_integer, cbd->u.key, value);
- ++namepart_completions_deferred;
- namepart_completed_or_failed();
-}
-
-/** @brief Called when a length or namepart lookup has failed */
-static void namepart_protocol_error(
- struct callbackdata attribute((unused)) *cbd,
- int attribute((unused)) code,
- const char *msg) {
- D(("namepart_protocol_error"));
- gtk_label_set_text(GTK_LABEL(report_label), msg);
+static void length_completed(void *v, const char *error, long l) {
+ if(error)
+ gtk_label_set_text(GTK_LABEL(report_label), error);
+ else {
+ const char *key = v;
+ long *value;
+
+ D(("namepart_completed"));
+ value = xmalloc(sizeof *value);
+ *value = l;
+ cache_put(&cachetype_integer, key, 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
static long getlength(const char *track) {
char *key;
const long *value;
- struct callbackdata *cbd;
static const long bogus = -1;
D(("getlength %s", track));
D(("deferring..."));;
cache_put(&cachetype_integer, key, value = &bogus);
++namepart_lookups_outstanding;
- cbd = xmalloc(sizeof *cbd);
- cbd->onerror = namepart_protocol_error;
- cbd->u.key = key;
- disorder_eclient_length(client, length_completed, track, cbd);
+ disorder_eclient_length(client, length_completed, track, key);
}
return *value;
}
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
*/
static GtkWidget *wrap_queue_cell(GtkWidget *label,
- const GdkColor *bgcolor,
- const GdkColor *fgcolor,
+ GtkStyle *style,
int *wp) {
GtkRequisition req;
GtkWidget *bg;
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;
}
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 ---------------------------------------------- */
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);
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
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 */
/* 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);
}
}
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;
/* 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)) {
}
/** @brief Called with new queue/recent contents */
-static void queuelike_completed(void *v, struct queue_entry *q) {
- struct callbackdata *cbd = v;
- struct queuelike *ql = cbd->u.ql;
-
- D(("queuelike_complete"));
- /* Install the new queue */
- update_queue(ql, ql->fixup ? ql->fixup(q) : q);
- /* Update the display */
- redisplay_queue(ql);
- if(ql->notify)
- ql->notify();
- /* Update sensitivity of main menu items */
- menu_update(-1);
+static void queuelike_completed(void *v,
+ const char *error,
+ struct queue_entry *q) {
+ if(error)
+ popup_protocol_error(0, error);
+ else {
+ struct queuelike *const ql = v;
+
+ D(("queuelike_complete"));
+ /* Install the new queue */
+ update_queue(ql, ql->fixup ? ql->fixup(q) : q);
+ /* Update the display */
+ redisplay_queue(ql);
+ if(ql->notify)
+ ql->notify();
+ /* Update sensitivity of main menu items */
+ menu_update(-1);
+ }
}
/** @brief Called with a new currently playing track */
static void playing_completed(void attribute((unused)) *v,
+ const char *error,
struct queue_entry *q) {
- struct callbackdata cbd;
- D(("playing_completed"));
- playing_track = q;
- /* Record when we got the playing track data so we know how old the 'sofar'
- * field is */
- time(&last_playing);
- cbd.u.ql = &ql_queue;
- queuelike_completed(&cbd, actual_queue);
+ if(error)
+ popup_protocol_error(0, error);
+ else {
+ D(("playing_completed"));
+ playing_track = q;
+ /* Record when we got the playing track data so we know how old the 'sofar'
+ * field is */
+ time(&last_playing);
+ queuelike_completed(&ql_queue, 0, actual_queue);
+ }
}
/** @brief Called when the queue is scrolled */
/* 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);
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);
&& 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 }
* We monitor pause/resume as well as whether the track is playing in order to
* keep the time played so far up to date correctly. See playing_completed().
*/
-static void playing_update(void attribute((unused)) *v) {
- D(("playing_update"));
+static void playing_changed(const char attribute((unused)) *event,
+ void attribute((unused)) *evendata,
+ void attribute((unused)) *callbackdata) {
+ D(("playing_changed"));
gtk_label_set_text(GTK_LABEL(report_label), "updating playing track");
disorder_eclient_playing(client, playing_completed, 0);
}
/* 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_reset(queue_update);
+ event_register("playing-changed", playing_changed, 0);
+ event_register("pause-changed", playing_changed, 0);
+ event_register("queue-changed", queue_changed, 0);
/* We pass choose_update() as our notify function since the choose screen
* marks tracks that are playing/in the queue. */
return queuelike(&ql_queue, fixup_queue, choose_update, queue_menu,
* Called when a track is added to the queue, removed from the queue (by user
* cmmand or because it is to be played) or moved within the queue
*/
-void queue_update(void) {
- struct callbackdata *cbd;
-
- D(("queue_update"));
- cbd = xmalloc(sizeof *cbd);
- cbd->onerror = 0;
- cbd->u.ql = &ql_queue;
+void queue_changed(const char attribute((unused)) *event,
+ void attribute((unused)) *eventdata,
+ void attribute((unused)) *callbackdata) {
+ D(("queue_changed"));
gtk_label_set_text(GTK_LABEL(report_label), "updating queue");
- disorder_eclient_queue(client, queuelike_completed, cbd);
+ disorder_eclient_queue(client, queuelike_completed, &ql_queue);
}
/* Recently played tracks -------------------------------------------------- */
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 }
};
/** @brief Create the recently-played list */
GtkWidget *recent_widget(void) {
D(("recent_widget"));
- register_reset(recent_update);
+ event_register("recent-changed",
+ recent_changed,
+ 0);
return queuelike(&ql_recent, fixup_recent, 0, recent_menu,
maincolumns, NMAINCOLUMNS);
}
*
* Called whenever a track is added to it or removed from it.
*/
-void recent_update(void) {
- struct callbackdata *cbd;
-
- D(("recent_update"));
- cbd = xmalloc(sizeof *cbd);
- cbd->onerror = 0;
- cbd->u.ql = &ql_recent;
+static void recent_changed(const char attribute((unused)) *event,
+ void attribute((unused)) *eventdata,
+ void attribute((unused)) *callbackdata) {
+ D(("recent_changed"));
gtk_label_set_text(GTK_LABEL(report_label), "updating recently played list");
- disorder_eclient_recent(client, queuelike_completed, cbd);
+ disorder_eclient_recent(client, queuelike_completed, &ql_recent);
}
/* Newly added tracks ------------------------------------------------------ */
{ "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 }
};
/** @brief Create the newly-added list */
GtkWidget *added_widget(void) {
D(("added_widget"));
- register_reset(added_update);
+ event_register("added-changed", added_changed, 0);
return queuelike(&ql_added, 0/*fixup*/, 0/*notify*/, added_menu,
addedcolumns, NADDEDCOLUMNS);
}
* disobedience/queue.c requires @ref queue_entry structures with a valid and
* unique @c id field. This function fakes it.
*/
-static void new_completed(void *v, int nvec, char **vec) {
- struct queue_entry *q, *qh, *qlast = 0, **qq = &qh;
- int n;
-
- for(n = 0; n < nvec; ++n) {
- q = xmalloc(sizeof *q);
- q->prev = qlast;
- q->track = vec[n];
- q->id = vec[n];
- *qq = q;
- qq = &q->next;
- qlast = q;
+static void new_completed(void *v,
+ const char *error,
+ int nvec, char **vec) {
+ if(error)
+ popup_protocol_error(0, error);
+ else {
+ struct queuelist *ql = v;
+ /* Convert the vector result to a queue linked list */
+ struct queue_entry *q, *qh, *qlast = 0, **qq = &qh;
+ int n;
+
+ for(n = 0; n < nvec; ++n) {
+ q = xmalloc(sizeof *q);
+ q->prev = qlast;
+ q->track = vec[n];
+ q->id = vec[n];
+ *qq = q;
+ qq = &q->next;
+ qlast = q;
+ }
+ *qq = 0;
+ queuelike_completed(ql, 0, qh);
}
- *qq = 0;
- queuelike_completed(v, qh);
}
/** @brief Update the newly-added list */
-void added_update(void) {
- struct callbackdata *cbd;
- D(("added_updae"));
+static void added_changed(const char attribute((unused)) *event,
+ void attribute((unused)) *eventdata,
+ void attribute((unused)) *callbackdata) {
+ D(("added_changed"));
- cbd = xmalloc(sizeof *cbd);
- cbd->onerror = 0;
- cbd->u.ql = &ql_added;
gtk_label_set_text(GTK_LABEL(report_label),
"updating newly added track list");
- disorder_eclient_new_tracks(client, new_completed, 0/*all*/, cbd);
+ disorder_eclient_new_tracks(client, new_completed, 0/*all*/, &ql_added);
}
/* Main menu plumbing ------------------------------------------------------ */
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 ------------------------------------------------------ */