X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/blobdiff_plain/ad47bd4ceb35ce30440fb7ccc5a7d871375ea362..b88fd7912fc173b50b4638fb1e25155999568990:/disobedience/queue-generic.c diff --git a/disobedience/queue-generic.c b/disobedience/queue-generic.c index cc6bf53..60ff794 100644 --- a/disobedience/queue-generic.c +++ b/disobedience/queue-generic.c @@ -2,23 +2,21 @@ * This file is part of DisOrder * Copyright (C) 2006-2008 Richard Kettlewell * - * This program is free software; you can redistribute it and/or modify + * 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 - * the Free Software Foundation; either version 2 of the License, or + * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - * USA + * along with this program. If not, see . */ /** @file disobedience/queue-generic.c - * @brief Queue widgets + * @brief Disobedience queue widgets * * This file provides contains code shared between all the queue-like * widgets - the queue, the recent list and the added tracks list. @@ -129,6 +127,8 @@ const char *column_length(const struct queue_entry *q, if(last_state & DISORDER_TRACK_PAUSED) l = playing_track->sofar; else { + if(!last_playing) + return NULL; time(&now); l = playing_track->sofar + (now - last_playing); } @@ -143,14 +143,14 @@ const char *column_length(const struct queue_entry *q, /** @brief Return the @ref queue_entry corresponding to @p iter * @param model Model that owns @p iter * @param iter Tree iterator - * @return ID string + * @return Pointer to queue entry */ struct queue_entry *ql_iter_to_q(GtkTreeModel *model, GtkTreeIter *iter) { struct queuelike *ql = g_object_get_data(G_OBJECT(model), "ql"); GValue v[1]; memset(v, 0, sizeof v); - gtk_tree_model_get_value(model, iter, ql->ncolumns, v); + gtk_tree_model_get_value(model, iter, ql->ncolumns + QUEUEPOINTER_COLUMN, v); assert(G_VALUE_TYPE(v) == G_TYPE_POINTER); struct queue_entry *const q = g_value_get_pointer(v); g_value_unset(v); @@ -178,13 +178,27 @@ void ql_update_row(struct queue_entry *q, iter = my_iter; } /* Update all the columns */ - for(int col = 0; col < ql->ncolumns; ++col) + for(int col = 0; col < ql->ncolumns; ++col) { + const char *const v = ql->columns[col].value(q, + ql->columns[col].data); + if(v) + gtk_list_store_set(ql->store, iter, + col, v, + -1); + } + gtk_list_store_set(ql->store, iter, + ql->ncolumns + QUEUEPOINTER_COLUMN, q, + -1); + if(q == playing_track) + gtk_list_store_set(ql->store, iter, + ql->ncolumns + BACKGROUND_COLUMN, BG_PLAYING, + ql->ncolumns + FOREGROUND_COLUMN, FG_PLAYING, + -1); + else gtk_list_store_set(ql->store, iter, - col, ql->columns[col].value(q, - ql->columns[col].data), + ql->ncolumns + BACKGROUND_COLUMN, (char *)0, + ql->ncolumns + FOREGROUND_COLUMN, (char *)0, -1); - /* The hidden extra column is the queue entry */ - gtk_list_store_set(ql->store, iter, ql->ncolumns, q, -1); } /** @brief Update the list store @@ -287,7 +301,9 @@ void ql_new_queue(struct queuelike *ql, const struct newqueue_data *nqd = hash_find(h, q->id); if(nqd->new) { /* Tell this row that it belongs to the new version of the queue */ - gtk_list_store_set(ql->store, iter, ql->ncolumns, nqd->new, -1); + gtk_list_store_set(ql->store, iter, + ql->ncolumns + QUEUEPOINTER_COLUMN, nqd->new, + -1); it = gtk_tree_model_iter_next(GTK_TREE_MODEL(ql->store), iter); ++kept; } else { @@ -308,7 +324,6 @@ void ql_new_queue(struct queuelike *ql, for(struct queue_entry *q = newq; q; q = q->next) { const struct newqueue_data *nqd = hash_find(h, q->id); if(!nqd->old) { - GtkTreeIter iter[1]; if(after) { /* Try to insert at the right sort of place */ GtkTreeIter where[1]; @@ -322,7 +337,9 @@ void ql_new_queue(struct queuelike *ql, gtk_list_store_append(ql->store, iter); } else gtk_list_store_prepend(ql->store, iter); - gtk_list_store_set(ql->store, iter, ql->ncolumns, q, -1); + gtk_list_store_set(ql->store, iter, + ql->ncolumns + QUEUEPOINTER_COLUMN, q, + -1); //fprintf(stderr, " add %s", q->id); ++inserted; } @@ -385,15 +402,130 @@ void ql_new_queue(struct queuelike *ql, --suppress_actions; } +/* Drag and drop has to be figured out experimentally, because it is not well + * documented. + * + * First you get a row-inserted. The path argument points to the destination + * row but this will not yet have had its values set. The source row is still + * present. AFAICT the iter argument points to the same place. + * + * Then you get a row-deleted. The path argument identifies the row that was + * deleted. By this stage the row inserted above has acquired its values. + * + * A complication is that the deletion will move the inserted row. For + * instance, if you do a drag that moves row 1 down to after the track that was + * formerly on row 9, in the row-inserted call it will show up as row 10, but + * in the row-deleted call, row 1 will have been deleted thus making the + * inserted row be row 9. + * + * So when we see the row-inserted we have no idea what track to move. + * Therefore we stash it until we see a row-deleted. + */ + +/** @brief row-inserted callback */ +static void ql_row_inserted(GtkTreeModel attribute((unused)) *treemodel, + GtkTreePath *path, + GtkTreeIter attribute((unused)) *iter, + gpointer user_data) { + struct queuelike *const ql = user_data; + if(!suppress_actions) { +#if 0 + char *ps = gtk_tree_path_to_string(path); + GtkTreeIter piter[1]; + gboolean pi = gtk_tree_model_get_iter(treemodel, piter, path); + struct queue_entry *pq = pi ? ql_iter_to_q(treemodel, piter) : 0; + struct queue_entry *iq = ql_iter_to_q(treemodel, iter); + + fprintf(stderr, "row-inserted %s path=%s pi=%d pq=%p path=%s iq=%p iter=%s\n", + ql->name, + ps, + pi, + pq, + (pi + ? (pq ? pq->track : "(pq=0)") + : "(pi=FALSE)"), + iq, + iq ? iq->track : "(iq=0)"); + + GtkTreeIter j[1]; + gboolean jt = gtk_tree_model_get_iter_first(treemodel, j); + int row = 0; + while(jt) { + struct queue_entry *q = ql_iter_to_q(treemodel, j); + fprintf(stderr, " %2d %s\n", row++, q ? q->track : "(no q)"); + jt = gtk_tree_model_iter_next(GTK_TREE_MODEL(ql->store), j); + } + g_free(ps); +#endif + /* Remember an iterator pointing at the insertion target */ + if(ql->drag_target) + gtk_tree_path_free(ql->drag_target); + ql->drag_target = gtk_tree_path_copy(path); + } +} + +/** @brief row-deleted callback */ +static void ql_row_deleted(GtkTreeModel attribute((unused)) *treemodel, + GtkTreePath *path, + gpointer user_data) { + struct queuelike *const ql = user_data; + + if(!suppress_actions) { +#if 0 + char *ps = gtk_tree_path_to_string(path); + fprintf(stderr, "row-deleted %s path=%s ql->drag_target=%s\n", + ql->name, ps, gtk_tree_path_to_string(ql->drag_target)); + GtkTreeIter j[1]; + gboolean jt = gtk_tree_model_get_iter_first(treemodel, j); + int row = 0; + while(jt) { + struct queue_entry *q = ql_iter_to_q(treemodel, j); + fprintf(stderr, " %2d %s\n", row++, q ? q->track : "(no q)"); + jt = gtk_tree_model_iter_next(GTK_TREE_MODEL(ql->store), j); + } + g_free(ps); +#endif + if(!ql->drag_target) { + error(0, "%s: unsuppressed row-deleted with no row-inserted", + ql->name); + return; + } + + /* Get the source and destination row numbers. */ + int srcrow = gtk_tree_path_get_indices(path)[0]; + int dstrow = gtk_tree_path_get_indices(ql->drag_target)[0]; + //fprintf(stderr, "srcrow=%d dstrow=%d\n", srcrow, dstrow); + + /* Note that the source row is computed AFTER the destination has been + * inserted, since GTK+ does the insert before the delete. Therefore if + * the source row is south (higher row number) of the destination, it will + * be one higher than expected. + * + * For instance if we drag row 1 to before row 0 we will see row-inserted + * for row 0 but then a row-deleted for row 2. + */ + if(srcrow > dstrow) + --srcrow; + + /* Tell the queue implementation */ + ql->drop(srcrow, dstrow); + + /* Dispose of stashed data */ + gtk_tree_path_free(ql->drag_target); + ql->drag_target = 0; + } +} + /** @brief Initialize a @ref queuelike */ GtkWidget *init_queuelike(struct queuelike *ql) { D(("init_queuelike")); - /* Create the list store. We add an extra column to hold the ID. */ - GType *types = xcalloc(ql->ncolumns + 1, sizeof (GType)); - for(int n = 0; n < ql->ncolumns; ++n) + /* Create the list store. We add an extra column to hold a pointer to the + * queue_entry. */ + GType *types = xcalloc(ql->ncolumns + EXTRA_COLUMNS, sizeof (GType)); + for(int n = 0; n < ql->ncolumns + EXTRA_COLUMNS; ++n) types[n] = G_TYPE_STRING; - types[ql->ncolumns] = G_TYPE_POINTER; - ql->store = gtk_list_store_newv(ql->ncolumns + 1, types); + types[ql->ncolumns + QUEUEPOINTER_COLUMN] = G_TYPE_POINTER; + ql->store = gtk_list_store_newv(ql->ncolumns + EXTRA_COLUMNS, types); g_object_set_data(G_OBJECT(ql->store), "ql", (void *)ql); /* Create the view */ @@ -411,6 +543,8 @@ GtkWidget *init_queuelike(struct queuelike *ql) { (ql->columns[n].name, r, "text", n, + "background", ql->ncolumns + BACKGROUND_COLUMN, + "foreground", ql->ncolumns + FOREGROUND_COLUMN, (char *)0); gtk_tree_view_column_set_resizable(c, TRUE); gtk_tree_view_column_set_reorderable(c, TRUE); @@ -427,6 +561,17 @@ GtkWidget *init_queuelike(struct queuelike *ql) { g_signal_connect(ql->view, "button-press-event", G_CALLBACK(ql_button_release), ql); + /* Drag+drop*/ + if(ql->drop) { + gtk_tree_view_set_reorderable(GTK_TREE_VIEW(ql->view), TRUE); + g_signal_connect(ql->store, + "row-inserted", + G_CALLBACK(ql_row_inserted), ql); + g_signal_connect(ql->store, + "row-deleted", + G_CALLBACK(ql_row_deleted), ql); + } + /* TODO style? */ ql->init();