-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,
- gint attribute((unused)) x,
- gint attribute((unused)) y,
- guint when,
- gpointer user_data) {
- struct queuelike *ql = &ql_queue;
- const char *id = user_data;
- struct vector vec;
- struct queue_entry *q;
-
- if(!id || (playing_track && !strcmp(id, playing_track->id)))
- id = "";
- vector_init(&vec);
- for(q = ql->q; q; q = q->next)
- 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,
- move_completed, 0/*v*/);
- gtk_drag_finish(drag_context, TRUE, TRUE, when);
- /* Destroy dropzones */
- remove_drag_targets(ql);
- return TRUE;
-}
-
-/** @brief Called when we enter, or move within, a drop zone */
-static gboolean queue_drag_motion(GtkWidget attribute((unused)) *widget,
- GdkDragContext *drag_context,
- gint attribute((unused)) x,
- gint attribute((unused)) y,
- guint when,
- gpointer user_data) {
- struct queuelike *ql = &ql_queue;
- const char *id = user_data;
- int row;
- struct queue_entry *q = findentry(ql, id, &row);
-
- if(!id || q) {
- if(!ql->dragmark) {
- NW(event_box);
- ql->dragmark = gtk_event_box_new();
- 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_set_style(ql->dragmark, drag_style);
- gtk_layout_put(GTK_LAYOUT(ql->mainlayout), ql->dragmark, 0,
- (row + 1) * ql->mainrowheight - !!row);
- } else
- gtk_layout_move(GTK_LAYOUT(ql->mainlayout), ql->dragmark, 0,
- (row + 1) * ql->mainrowheight - !!row);
- gtk_widget_show(ql->dragmark);
- gdk_drag_status(drag_context, GDK_ACTION_MOVE, when);
- return TRUE;
- } else
- /* ID has gone AWOL */
- return FALSE;
-}
-
-/** @brief Called when we leave a drop zone */
-static void queue_drag_leave(GtkWidget attribute((unused)) *widget,
- GdkDragContext attribute((unused)) *drag_context,
- guint attribute((unused)) when,
- gpointer attribute((unused)) user_data) {
- struct queuelike *ql = &ql_queue;
-
- if(ql->dragmark)
- gtk_widget_hide(ql->dragmark);
-}
-
-/** @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.
- *
- * 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;
-
- NW(event_box);
- eventbox = gtk_event_box_new();
- /* Make the target zone invisible */
- gtk_event_box_set_visible_window(GTK_EVENT_BOX(eventbox), FALSE);
- /* Make it large enough */
- gtk_widget_set_size_request(eventbox, 10240,
- y ? ql->mainrowheight : ql->mainrowheight / 2);
- /* Position it */
- gtk_layout_put(GTK_LAYOUT(ql->mainlayout), eventbox, 0,
- y ? y - ql->mainrowheight / 2 : 0);
- /* Mark it as capable of receiving drops */
- gtk_drag_dest_set(eventbox,
- 0,
- dragtargets, NDRAGTARGETS, GDK_ACTION_MOVE);
- g_signal_connect(eventbox, "drag-drop",
- G_CALLBACK(queue_drag_drop), (char *)id);
- /* Monitor drag motion */
- g_signal_connect(eventbox, "drag-motion",
- G_CALLBACK(queue_drag_motion), (char *)id);
- g_signal_connect(eventbox, "drag-leave",
- G_CALLBACK(queue_drag_leave), (char *)id);
- /* The widget needs to be shown to receive drags */
- gtk_widget_show(eventbox);
- /* Remember the drag targets */
- ql->dropzones[ql->ndropzones] = eventbox;
- g_signal_connect(eventbox, "destroy",
- 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 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);
- /* Put a drag target at the bottom of every row */
- for(q = ql->q; q; q = q->next) {
- y += ql->mainrowheight;
- add_drag_target(ql, y, q->id);
- }
-}
-
-/** @brief Remove the dropzones */
-static void remove_drag_targets(struct queuelike *ql) {
- int n;
-
- for(n = 0; n < ql->ndropzones; ++n) {
- if(ql->dropzones[n]) {
- DW(event_box);
- gtk_widget_destroy(ql->dropzones[n]);
- }
- assert(ql->dropzones[n] == 0);
- }
-}
-
-/* Layout ------------------------------------------------------------------ */
-
-/** @brief Redisplay a queue */
-static void redisplay_queue(struct queuelike *ql) {
- struct queue_entry *q;
- int row, col;
- GList *c, *children;
- GtkStyle *style;
- GtkRequisition req;
- GtkWidget *w;
- int maxwidths[MAXCOLUMNS], x, y, titlerowheight;
- int totalwidth = 10240; /* TODO: can we be less blunt */
-
- D(("redisplay_queue"));
- /* Eliminate all the existing widgets and start from scratch */
- for(c = children = gtk_container_get_children(GTK_CONTAINER(ql->mainlayout));
- c;
- c = c->next) {
- /* Destroy both the label and the eventbox */
- if(GTK_BIN(c->data)->child) {
- DW(label);
- gtk_widget_destroy(GTK_BIN(c->data)->child);
- }
- DW(event_box);
- gtk_widget_destroy(GTK_WIDGET(c->data));
- }
- g_list_free(children);
- /* Adjust the row count */
- for(q = ql->q, ql->nrows = 0; q; q = q->next)
- ++ql->nrows;
- /* We need to create all the widgets before we can position them */
- ql->cells = xcalloc(ql->nrows * (ql->ncolumns + 1), sizeof *ql->cells);
- /* Minimum width is given by the column headings */
- for(col = 0; col < ql->ncolumns; ++col) {
- /* Reset size so we don't inherit last iteration's maximum size */
- gtk_widget_set_size_request(GTK_BIN(ql->titlecells[col])->child, -1, -1);
- gtk_widget_size_request(GTK_BIN(ql->titlecells[col])->child, &req);
- maxwidths[col] = req.width;
- }
- /* Find the vertical size of the title bar */
- gtk_widget_size_request(GTK_BIN(ql->titlecells[0])->child, &req);
- titlerowheight = req.height;
- y = 0;
- if(ql->nrows) {
- /* 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) 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, style, &maxwidths[col]);
- else
- w = get_padding_cell(style);
- ql->cells[row * (ql->ncolumns + 1) + col] = w;
- /* Maybe mark it draggable */
- if(draggable_row(q)) {
- gtk_drag_source_set(w, GDK_BUTTON1_MASK,
- dragtargets, NDRAGTARGETS, GDK_ACTION_MOVE);
- g_signal_connect(w, "drag-begin", G_CALLBACK(queue_drag_begin), q);
- }
- /* Catch button presses */
- g_signal_connect(w, "button-release-event",
- G_CALLBACK(queuelike_button_released), q);
- g_signal_connect(w, "button-press-event",
- G_CALLBACK(queuelike_button_released), q);
- }
- }
- /* ...and of each row in the main layout */
- gtk_widget_size_request(GTK_BIN(ql->cells[0])->child, &req);
- ql->mainrowheight = req.height;
- /* Now we know the maximum width of each column we can set the size of
- * everything and position it */
- for(row = 0, q = ql->q; row < ql->nrows; ++row, q = q->next) {
- x = 0;
- for(col = 0; col < ql->ncolumns; ++col) {
- w = ql->cells[row * (ql->ncolumns + 1) + col];
- gtk_widget_set_size_request(GTK_BIN(w)->child,
- maxwidths[col], -1);
- gtk_layout_put(GTK_LAYOUT(ql->mainlayout), w, x, y);
- x += maxwidths[col];
- }
- w = ql->cells[row * (ql->ncolumns + 1) + col];
- gtk_widget_set_size_request(GTK_BIN(w)->child,
- totalwidth - x, -1);
- gtk_layout_put(GTK_LAYOUT(ql->mainlayout), w, x, y);
- y += ql->mainrowheight;
- }
- }
- /* Titles */
- x = 0;
- for(col = 0; col < ql->ncolumns; ++col) {
- gtk_widget_set_size_request(GTK_BIN(ql->titlecells[col])->child,
- maxwidths[col], -1);
- gtk_layout_move(GTK_LAYOUT(ql->titlelayout), ql->titlecells[col], x, 0);
- x += maxwidths[col];
- }
- gtk_widget_set_size_request(GTK_BIN(ql->titlecells[col])->child,
- totalwidth - x, -1);
- gtk_layout_move(GTK_LAYOUT(ql->titlelayout), ql->titlecells[col], x, 0);
- /* Set the states */
- set_widget_states(ql);
- /* Make sure it's all visible */
- gtk_widget_show_all(ql->mainlayout);
- gtk_widget_show_all(ql->titlelayout);
- /* Layouts might shrink to arrange for the area they shrink out of to be
- * redrawn */
- gtk_widget_queue_draw(ql->mainlayout);
- gtk_widget_queue_draw(ql->titlelayout);
- /* Adjust the size of the layout */
- gtk_layout_set_size(GTK_LAYOUT(ql->mainlayout), x, y);
- gtk_layout_set_size(GTK_LAYOUT(ql->titlelayout), x, titlerowheight);
- gtk_widget_set_size_request(ql->titlelayout, -1, titlerowheight);
-}
-
-/** @brief Called with new queue/recent contents */
-static void queuelike_completed(void *v,
- const char *error,
- struct queue_entry *q) {
- if(error)