X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/blobdiff_plain/295b03dc2c13a6029f4f3e2d889743045281f1c2..7736e1cc4f9caf63686a425cf716ab54c67647e4:/disobedience/queue.c diff --git a/disobedience/queue.c b/disobedience/queue.c index 4d756c5..6008e4e 100644 --- a/disobedience/queue.c +++ b/disobedience/queue.c @@ -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 @@ -52,9 +52,14 @@ * 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 ------------------------------------------------------ */