chiark / gitweb /
Drag targets are now specified separately for each queuelike.
authorRichard Kettlewell <rjk@greenend.org.uk>
Tue, 24 Nov 2009 18:03:13 +0000 (18:03 +0000)
committerRichard Kettlewell <rjk@greenend.org.uk>
Tue, 24 Nov 2009 18:03:13 +0000 (18:03 +0000)
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.

disobedience/added.c
disobedience/choose.c
disobedience/disobedience.h
disobedience/playlists.c
disobedience/queue-generic.c
disobedience/queue-generic.h
disobedience/queue.c
disobedience/recent.c

index 7f654ee48ac18400fd1744aaaf9fe73a32b24b3f..aa15f48902d83079396d6ec566e1b44f253c286c 100644 (file)
@@ -93,6 +93,8 @@ struct queuelike ql_added = {
   .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) {
index 9829f199fc2ebebc1d9b178dedc1d6f23ce481eb..a1d50c1a62c18f584af0f74435f9bd533f435be8 100644 (file)
 #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 */
@@ -711,7 +715,7 @@ GtkWidget *choose_widget(void) {
   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);
index e8b2d020f488b4b6a9bce3ff7dc0ab477ff94553..29a278ebbe2dc92cb2baf26c359eba38b49b5d12 100644 (file)
@@ -214,6 +214,8 @@ void choose_update(void);
 void play_completed(void *v,
                     const char *err);
 
+extern const GtkTargetEntry choose_targets[];
+
 /* Login details */
 
 void login_box(void);
index 92772d7043471e4d6961e2e9b6fadb3ade054403..f46195d89fcbc00552cddaa3d584931bc149d42a 100644 (file)
@@ -130,6 +130,22 @@ static struct menuitem playlist_menuitems[] = {
   { "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",
@@ -137,7 +153,11 @@ static struct queuelike ql_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 ---------------------------------------- */
@@ -884,30 +904,72 @@ static void playlist_drop(struct queuelike attribute((unused)) *ql,
                                  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,
index e5f76a9825768201bca9f2275a761b942e0535ee..e2fd48a7ff8966d3d683c7ee42f081880a5d32a0 100644 (file)
 #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,
@@ -518,6 +505,28 @@ static GtkTreePath *ql_drop_path(GtkWidget *w,
   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
@@ -552,8 +561,13 @@ static gboolean ql_drag_motion(GtkWidget *w,
     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)
@@ -636,12 +650,13 @@ static void ql_drag_data_get_collect(GtkTreeModel *model,
 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,
@@ -674,7 +689,7 @@ static void ql_drag_data_received(GtkWidget attribute((unused)) *w,
                                   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;
@@ -682,7 +697,7 @@ static void ql_drag_data_received(GtkWidget attribute((unused)) *w,
   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) {
@@ -739,11 +754,12 @@ static void ql_drag_data_received(GtkWidget attribute((unused)) *w,
   /* 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;
@@ -752,6 +768,14 @@ static void ql_drag_data_received(GtkWidget attribute((unused)) *w,
     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"));
@@ -820,15 +844,15 @@ GtkWidget *init_queuelike(struct queuelike *ql) {
     /* 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",
@@ -843,9 +867,9 @@ GtkWidget *init_queuelike(struct queuelike *ql) {
     /* 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);
index 2b8b8ef1758fb2fb33b4daa76415fdf564bd2081..efd6400e19441e992827484c291d15dda6043ca0 100644 (file)
@@ -104,8 +104,30 @@ struct queuelike {
   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,
index 80b163a8e16072da63288cbcc849a350a34ca3f2..0db90d7ded457abfabf0ad3524be6bbb6f38a3dd 100644 (file)
@@ -225,6 +225,22 @@ static struct menuitem queue_menuitems[] = {
   { "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,
@@ -232,7 +248,11 @@ struct queuelike ql_queue = {
   .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 */
index f53e6313f654a7535e52104c3b9ac9da4e77cdb8..0e74feb07e7adc8b671057f49ef3c282561b856c 100644 (file)
@@ -91,6 +91,8 @@ struct queuelike ql_recent = {
   .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) {