/*
* 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
/** @brief Called when an update completes */
void (*notify)(void);
- /** @brief Called to fix up the queue after update
+ /** @brief Called to fix up the queue after update
* @param q The list passed back from the server
* @return Assigned to @c ql->q
*/
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 */
static void namepart_completed_or_failed(void) {
D(("namepart_completed_or_failed"));
--namepart_lookups_outstanding;
- if(!namepart_lookups_outstanding || namepart_completions_deferred > 24) {
+ if(!namepart_lookups_outstanding) {
redisplay_queue(&ql_queue);
redisplay_queue(&ql_recent);
redisplay_queue(&ql_added);
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;
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)) {
/* 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);
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,
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 }
/* 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. */
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 ------------------------------------------------------ */