Playlist rearrangement now implemented.
There's still a bug in playlist d+d handling somewhere, e.g. make a
4-element playlist and rearrange the first three elements to after
themselves twice in a row. The first time it succeeds (but using an
odd after_me) the second time it blows up.
.ncolumns = sizeof added_columns / sizeof *added_columns,
.menuitems = added_menuitems,
.nmenuitems = sizeof added_menuitems / sizeof *added_menuitems,
+ .drag_source_targets = choose_targets,
+ .drag_source_actions = GDK_ACTION_COPY,
};
GtkWidget *added_widget(void) {
#include "disobedience.h"
#include "choose.h"
#include "multidrag.h"
+#include "queue-generic.h"
#include <gdk/gdkkeysyms.h>
/** @brief Drag types */
-static const GtkTargetEntry choose_targets[] = {
+const GtkTargetEntry choose_targets[] = {
{
- (char *)"text/x-disorder-playable-tracks", /* drag type */
+ PLAYABLE_TRACKS, /* drag type */
GTK_TARGET_SAME_APP|GTK_TARGET_OTHER_WIDGET, /* copying between widgets */
- 1 /* ID value */
+ PLAYABLE_TRACKS_ID /* ID value */
},
+ {
+ .target = NULL
+ }
};
/** @brief The current selection tree */
gtk_drag_source_set(choose_view,
GDK_BUTTON1_MASK,
choose_targets,
- sizeof choose_targets / sizeof *choose_targets,
+ 1,
GDK_ACTION_COPY);
g_signal_connect(choose_view, "drag-data-get",
G_CALLBACK(choose_drag_data_get), NULL);
void play_completed(void *v,
const char *err);
+extern const GtkTargetEntry choose_targets[];
+
/* Login details */
void login_box(void);
{ "Deselect all tracks", ql_selectnone_activate, ql_selectnone_sensitive, 0, 0 },
};
+static const GtkTargetEntry playlist_targets[] = {
+ {
+ PLAYLIST_TRACKS, /* drag type */
+ GTK_TARGET_SAME_WIDGET, /* rearrangement within a widget */
+ PLAYLIST_TRACKS_ID /* ID value */
+ },
+ {
+ PLAYABLE_TRACKS, /* drag type */
+ GTK_TARGET_SAME_APP|GTK_TARGET_OTHER_WIDGET, /* copying between widgets */
+ PLAYABLE_TRACKS_ID, /* ID value */
+ },
+ {
+ .target = NULL
+ }
+};
+
/** @brief Queuelike for editing a playlist */
static struct queuelike ql_playlist = {
.name = "playlist",
.ncolumns = sizeof playlist_columns / sizeof *playlist_columns,
.menuitems = playlist_menuitems,
.nmenuitems = sizeof playlist_menuitems / sizeof *playlist_menuitems,
- .drop = playlist_drop
+ .drop = playlist_drop,
+ .drag_source_targets = playlist_targets,
+ .drag_source_actions = GDK_ACTION_MOVE|GDK_ACTION_COPY,
+ .drag_dest_targets = playlist_targets,
+ .drag_dest_actions = GDK_ACTION_MOVE|GDK_ACTION_COPY,
};
/* Maintaining the list of playlists ---------------------------------------- */
mod->playlist, mod);
}
+/** @brief Return true if track @p i is in the moved set */
+static int playlist_drop_is_moved(struct playlist_modify_data *mod,
+ int i) {
+ struct queue_entry *q;
+
+ fprintf(stderr, "is %d moved?\n", i);
+ /* Find the q corresponding to i, so we can get the ID */
+ for(q = ql_playlist.q; i; q = q->next, --i)
+ ;
+ fprintf(stderr, "id is %s\n", q->id);
+ /* See if track i matches any of the moved set by ID */
+ for(int n = 0; n < mod->ntracks; ++n)
+ if(!strcmp(q->id, mod->ids[n])) {
+ fprintf(stderr, "YES, it was moved.\n");
+ return 1;
+ }
+ fprintf(stderr, "NO it was not.\n");
+ return 0;
+}
+
static void playlist_drop_modify(struct playlist_modify_data *mod,
int nvec, char **vec) {
char **newvec;
int nnewvec;
+ /* after_me is the queue_entry to insert after, or NULL to insert at the
+ * beginning (including the case when the playlist is empty) */
fprintf(stderr, "after_me = %s\n",
mod->after_me ? mod->after_me->track : "NULL");
+ struct queue_entry *q = ql_playlist.q;
+ int ins = 0;
+ if(mod->after_me) {
+ ++ins;
+ while(q && q != mod->after_me) {
+ q = q->next;
+ ++ins;
+ }
+ }
+ /* Now ins is the index to insert at; equivalently, the row to insert before,
+ * and so equal to nvec to append. */
+ fprintf(stderr, "ins = %d = %s\n",
+ ins, ins < nvec ? vec[ins] : "NULL");
+ fprintf(stderr, "nvec = %d\n", nvec);
if(mod->ids) {
/* This is a rearrangement */
- /* TODO what if it's a drag from the queue? */
- abort();
- } else {
- /* This is an insertion */
- struct queue_entry *q = ql_playlist.q;
- int ins = 0;
- if(mod->after_me) {
- ++ins;
- while(q && q != mod->after_me) {
- q = q->next;
- ++ins;
+ nnewvec = nvec;
+ newvec = xcalloc(nnewvec, sizeof (char *));
+ int i = 0;
+ /* For each destination slot decide what will go there */
+ for(int n = 0; n < nnewvec; ++n) {
+ fprintf(stderr, "n=%d i=%d\n", n, i);
+ if(n >= ins && n < ins + mod->ntracks) {
+ fprintf(stderr, "inside insertion range\n");
+ newvec[n] = mod->tracks[n - ins];
+ } else {
+ /* Pick the next track from vec[] that is not mentioned in mod->ids[] */
+ while(playlist_drop_is_moved(mod, i)) {
+ ++i;
+ --ins;
+ fprintf(stderr, "now: i=%d ins=%d\n", i, ins);
+ }
+ newvec[n] = vec[i++];
}
}
- fprintf(stderr, "ins = %d = %s\n",
- ins, ins < nvec ? vec[ins] : "NULL");
+ } else {
+ /* This is an insertion */
nnewvec = nvec + mod->ntracks;
newvec = xcalloc(nnewvec, sizeof (char *));
memcpy(newvec, vec,
#include "multidrag.h"
#include "autoscroll.h"
-static const GtkTargetEntry queuelike_targets[] = {
- {
- (char *)"text/x-disorder-queued-tracks", /* drag type */
- GTK_TARGET_SAME_WIDGET, /* rearrangement within a widget */
- 0 /* ID value */
- },
- {
- (char *)"text/x-disorder-playable-tracks", /* drag type */
- GTK_TARGET_SAME_APP|GTK_TARGET_OTHER_WIDGET, /* copying between widgets */
- 1 /* ID value */
- },
-};
-
/* Track detail lookup ----------------------------------------------------- */
static void queue_lookups_completed(const char attribute((unused)) *event,
return path;
}
+#if 0
+static const char *act(GdkDragAction action) {
+ struct dynstr d[1];
+
+ dynstr_init(d);
+ if(action & GDK_ACTION_DEFAULT)
+ dynstr_append_string(d, "|DEFAULT");
+ if(action & GDK_ACTION_COPY)
+ dynstr_append_string(d, "|COPY");
+ if(action & GDK_ACTION_MOVE)
+ dynstr_append_string(d, "|MOVE");
+ if(action & GDK_ACTION_LINK)
+ dynstr_append_string(d, "|LINK");
+ if(action & GDK_ACTION_PRIVATE)
+ dynstr_append_string(d, "|PRIVATE");
+ if(action & GDK_ACTION_ASK)
+ dynstr_append_string(d, "|ASK");
+ dynstr_terminate(d);
+ return d->nvec ? d->vec + 1 : "";
+}
+#endif
+
/** @brief Called when a drag moves within a candidate destination
* @param w Destination widget
* @param dc Drag context
action = GDK_ACTION_MOVE;
else if(dc->actions & GDK_ACTION_COPY)
action = GDK_ACTION_COPY;
- /*fprintf(stderr, "suggested %#x actions %#x result %#x\n",
- dc->suggested_action, dc->actions, action);*/
+ /* TODO this comes up with the wrong answer sometimes. If we are in the
+ * middle of a rearrange then the suggested action will be COPY, which we'll
+ * take, even though MOVE would actually be appropriate. The drag still
+ * seems to work, but it _is_ wrong. The answer is to take the target into
+ * account. */
+ /*fprintf(stderr, "suggested %s actions %s result %s\n",
+ act(dc->suggested_action), act(dc->actions), act(action));*/
if(action) {
// If the action is acceptable then we see if this widget is acceptable
if(gtk_drag_dest_find_target(w, dc, NULL) == GDK_NONE)
static void ql_drag_data_get(GtkWidget attribute((unused)) *w,
GdkDragContext attribute((unused)) *dc,
GtkSelectionData *data,
- guint attribute((unused)) info_,
+ guint info,
guint attribute((unused)) time_,
gpointer user_data) {
struct queuelike *const ql = user_data;
struct dynstr result[1];
+ fprintf(stderr, "ql_drag_data_get %s info=%d\n", ql->name, info);
dynstr_init(result);
gtk_tree_selection_selected_foreach(ql->selection,
ql_drag_data_get_collect,
gint x,
gint y,
GtkSelectionData *data,
- guint attribute((unused)) info_,
+ guint info_,
guint attribute((unused)) time_,
gpointer user_data) {
struct queuelike *const ql = user_data;
struct vector ids[1], tracks[1];
int parity = 0;
- //fprintf(stderr, "drag-data-received: %d,%d info_=%u\n", x, y, info_);
+ fprintf(stderr, "drag-data-received: %d,%d info_=%u\n", x, y, info_);
/* Get the selection string */
p = result = (char *)gtk_selection_data_get_text(data);
if(!result) {
/* Note that q->id can match one of ids[]. This doesn't matter for
* moveafter but TODO may matter for playlist support. */
switch(info_) {
- case 0:
- /* Rearrangement. Send ID and track data. */
+ case QUEUED_TRACKS_ID:
+ case PLAYLIST_TRACKS_ID:
+ /* Rearrangement within some widget. Send ID and track data. */
ql->drop(ql, tracks->nvec, tracks->vec, ids->vec, q);
break;
- case 1:
+ case PLAYABLE_TRACKS_ID:
/* Copying between widgets. IDs mean nothing so don't send them. */
ql->drop(ql, tracks->nvec, tracks->vec, NULL, q);
break;
gtk_tree_path_free(path);
}
+static int count_drag_targets(const GtkTargetEntry *targets) {
+ const GtkTargetEntry *t = targets;
+
+ while(t->target)
+ ++t;
+ return t - targets;
+}
+
/** @brief Initialize a @ref queuelike */
GtkWidget *init_queuelike(struct queuelike *ql) {
D(("init_queuelike"));
/* This view will act as a drag source */
gtk_drag_source_set(ql->view,
GDK_BUTTON1_MASK,
- queuelike_targets,
- sizeof queuelike_targets / sizeof *queuelike_targets,
- GDK_ACTION_MOVE);
+ ql->drag_source_targets,
+ count_drag_targets(ql->drag_source_targets),
+ ql->drag_dest_actions);
/* This view will act as a drag destination */
gtk_drag_dest_set(ql->view,
GTK_DEST_DEFAULT_HIGHLIGHT|GTK_DEST_DEFAULT_DROP,
- queuelike_targets,
- sizeof queuelike_targets / sizeof *queuelike_targets,
- GDK_ACTION_MOVE|GDK_ACTION_COPY);
+ ql->drag_dest_targets,
+ count_drag_targets(ql->drag_dest_targets),
+ ql->drag_dest_actions);
g_signal_connect(ql->view, "drag-motion",
G_CALLBACK(ql_drag_motion), ql);
g_signal_connect(ql->view, "drag-leave",
/* For queues that cannot accept a drop we still accept a copy out */
gtk_drag_source_set(ql->view,
GDK_BUTTON1_MASK,
- queuelike_targets,
- sizeof queuelike_targets / sizeof *queuelike_targets,
- GDK_ACTION_COPY);
+ ql->drag_source_targets,
+ count_drag_targets(ql->drag_source_targets),
+ ql->drag_source_actions);
g_signal_connect(ql->view, "drag-data-get",
G_CALLBACK(ql_drag_data_get), ql);
make_treeview_multidrag(ql->view, NULL);
void (*drop)(struct queuelike *ql, int ntracks, char **tracks, char **ids,
struct queue_entry *after_me);
+ /** @brief Source target list */
+ const GtkTargetEntry *drag_source_targets;
+
+ /** @brief Drag source actions */
+ GdkDragAction drag_source_actions;
+
+ /** @brief Destination target list */
+ const GtkTargetEntry *drag_dest_targets;
+
+ /** @brief Drag destination actions */
+ GdkDragAction drag_dest_actions;
+
};
+enum {
+ PLAYABLE_TRACKS_ID,
+ QUEUED_TRACKS_ID,
+ PLAYLIST_TRACKS_ID
+};
+
+#define PLAYABLE_TRACKS (char *)"text/x-disorder-playable-tracks"
+#define QUEUED_TRACKS (char *)"text/x-disorder-queued-tracks"
+#define PLAYLIST_TRACKS (char *)"text/x-disorder-playlist-tracks"
+
enum {
QUEUEPOINTER_COLUMN,
FOREGROUND_COLUMN,
{ "Adopt track", ql_adopt_activate, ql_adopt_sensitive, 0, 0 },
};
+static const GtkTargetEntry queue_targets[] = {
+ {
+ QUEUED_TRACKS, /* drag type */
+ GTK_TARGET_SAME_WIDGET, /* rearrangement within a widget */
+ QUEUED_TRACKS_ID /* ID value */
+ },
+ {
+ PLAYABLE_TRACKS, /* drag type */
+ GTK_TARGET_SAME_APP|GTK_TARGET_OTHER_WIDGET, /* copying between widgets */
+ PLAYABLE_TRACKS_ID, /* ID value */
+ },
+ {
+ .target = NULL
+ }
+};
+
struct queuelike ql_queue = {
.name = "queue",
.init = queue_init,
.ncolumns = sizeof queue_columns / sizeof *queue_columns,
.menuitems = queue_menuitems,
.nmenuitems = sizeof queue_menuitems / sizeof *queue_menuitems,
- .drop = queue_drop
+ .drop = queue_drop,
+ .drag_source_targets = queue_targets,
+ .drag_source_actions = GDK_ACTION_MOVE|GDK_ACTION_COPY,
+ .drag_dest_targets = queue_targets,
+ .drag_dest_actions = GDK_ACTION_MOVE|GDK_ACTION_COPY,
};
/** @brief Called when a key is pressed in the queue tree view */
.ncolumns = sizeof recent_columns / sizeof *recent_columns,
.menuitems = recent_menuitems,
.nmenuitems = sizeof recent_menuitems / sizeof *recent_menuitems,
+ .drag_source_targets = choose_targets,
+ .drag_source_actions = GDK_ACTION_COPY,
};
GtkWidget *recent_widget(void) {