X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/blobdiff_plain/fbcfb25715ddbb522c8efac3faec1eca4811683d..6c7a654c60f91c36474fd66e93cd8ac928b51d86:/disobedience/queue.c diff --git a/disobedience/queue.c b/disobedience/queue.c index 7565935..8ff6b6f 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,6 +52,10 @@ * 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" @@ -90,6 +94,15 @@ static GtkWidget *column_length(const struct queuelike *ql, 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 */ @@ -194,6 +207,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 */ @@ -267,36 +281,33 @@ static void namepart_completed_or_failed(void) { } } -/** @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(); } @@ -305,14 +316,9 @@ static void namepart_fill(const char *track, 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 @@ -359,7 +365,6 @@ void namepart_update(const char *track, static long getlength(const char *track) { char *key; const long *value; - struct callbackdata *cbd; static const long bogus = -1; D(("getlength %s", track)); @@ -369,10 +374,7 @@ static long getlength(const char *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; } @@ -698,6 +700,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; @@ -763,6 +775,12 @@ static struct queue_entry *findentry(struct queuelike *ql, 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, @@ -782,7 +800,7 @@ static gboolean queue_drag_drop(GtkWidget attribute((unused)) *widget, 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); @@ -833,15 +851,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 */ @@ -866,40 +892,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); } } @@ -1021,32 +1050,40 @@ static void redisplay_queue(struct queuelike *ql) { } /** @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 */ @@ -1168,11 +1205,18 @@ static int scratch_sensitive(struct queuelike attribute((unused)) *ql, && 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 */ @@ -1187,6 +1231,12 @@ static int remove_sensitive(struct queuelike *ql, || 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) { @@ -1198,10 +1248,10 @@ static void remove_activate(GtkMenuItem attribute((unused)) *menuitem, /* 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 */ @@ -1236,6 +1286,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, @@ -1256,10 +1321,10 @@ static void play_activate(GtkMenuItem attribute((unused)) *menuitem, /* 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 --------------------------------------------------------------- */ @@ -1295,6 +1360,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 } @@ -1305,8 +1371,10 @@ static struct queue_menuitem queue_menu[] = { * 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); } @@ -1317,8 +1385,9 @@ 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_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, @@ -1330,15 +1399,12 @@ GtkWidget *queue_widget(void) { * 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 -------------------------------------------------- */ @@ -1367,13 +1433,16 @@ 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 } }; /** @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); } @@ -1382,15 +1451,12 @@ GtkWidget *recent_widget(void) { * * 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 ------------------------------------------------------ */ @@ -1400,13 +1466,14 @@ 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 } }; /** @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); } @@ -1417,34 +1484,40 @@ GtkWidget *added_widget(void) { * 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 ------------------------------------------------------ */ @@ -1458,6 +1531,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")); } @@ -1466,11 +1545,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 ------------------------------------------------------ */