Copyright (C) 2001 Joe Drew
Copyright (C) 2000-2001 Robert Leslie
Portions Copyright (C) 1997-2006 Free Software Foundation, Inc.
+Portions Copyright (C) 2000 Red Hat, Inc., Jonathan Blandford <jrb@redhat.com>
Binaries may derive extra copyright owners through linkage (binary distributors
are expected to do their own legwork)
Portions extracted from MPG321, http://mpg321.sourceforge.net/
Copyright (C) 2001 Joe Drew
Copyright (C) (C) 2000-2001 Robert Leslie
+Portions Copyright (C) 1997-2006 Free Software Foundation, Inc.
+Portions Copyright (C) 2000 Red Hat, Inc., Jonathan Blandford <jrb@redhat.com>
Binaries may derive extra copyright owners through linkage
(binary distributors are expected to do their own legwork)
choose.c choose-menu.c choose-search.c popup.c misc.c \
control.c properties.c menu.c log.c progress.c login.c rtp.c \
help.c ../lib/memgc.c settings.c users.c lookup.c choose.h \
- popup.h playlists.c multidrag.c
+ popup.h playlists.c multidrag.c multidrag.h autoscroll.c
+ autoscroll.h
disobedience_LDADD=../lib/libdisorder.a $(LIBPCRE) $(LIBGC) $(LIBGCRYPT) \
$(LIBASOUND) $(COREAUDIO) $(LIBDB) $(LIBICONV)
disobedience_LDFLAGS=$(GTK_LIBS)
--- /dev/null
+/* Derived from gtktreeview.c
+ * Copyright (C) 2000 Red Hat, Inc., Jonathan Blandford <jrb@redhat.com>
+ * Portions copyright (C) 2009 Richard Kettlewell
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+/** @file disobedience/autoscroll.c
+ * @brief Automatic scrolling of a GtkTreeView
+ *
+ * GTK+ doesn't expose the automatic scrolling support if you don't use its
+ * high-level treeview drag+drop features.
+ *
+ * Adapted from GTK+ upstream as of commit
+ * 7fda8e6378d90bc8cf670ffe9dea682911e5e241.
+ */
+
+#include <config.h>
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include "autoscroll.h"
+
+/** @brief Object data key used to track the autoscroll timeout */
+#define AUTOSCROLL_KEY "autoscroll.greenend.org.uk"
+
+/** @brief Controls size of edge region that provokes scrolling
+ *
+ * Actually this is half the size of the scroll region. In isolation this may
+ * seem bizarre, but GTK+ uses the value internally for other purposes.
+ */
+#define SCROLL_EDGE_SIZE 15
+
+/** @brief Called from time to time to check whether auto-scrolling is needed
+ * @param data The GtkTreeView
+ * @return TRUE, to keep on truckin'
+ */
+static gboolean autoscroll_timeout(gpointer data) {
+ GtkTreeView *tree_view = data;
+ GdkRectangle visible_rect;
+ gint wx, wy, tx, ty;
+ gint offset;
+ gfloat value;
+
+ /* First we must find the pointer Y position in tree coordinates. GTK+
+ * natively knows what the bin window is and can get the pointer in bin
+ * coords and convert to tree coords. But there is no published way for us
+ * to find the bin window, so we must start in widget coords. */
+ gtk_widget_get_pointer(GTK_WIDGET(tree_view), &wx, &wy);
+ //fprintf(stderr, "widget coords: %d, %d\n", wx, wy);
+ gtk_tree_view_convert_widget_to_tree_coords(tree_view, wx, wy, &tx, &ty);
+ //fprintf(stderr, "tree coords: %d, %d\n", tx, ty);
+
+ gtk_tree_view_get_visible_rect (tree_view, &visible_rect);
+
+ /* see if we are near the edge. */
+ offset = ty - (visible_rect.y + 2 * SCROLL_EDGE_SIZE);
+ if (offset > 0)
+ {
+ offset = ty - (visible_rect.y + visible_rect.height - 2 * SCROLL_EDGE_SIZE);
+ if (offset < 0)
+ return TRUE;
+ }
+
+ GtkAdjustment *vadjustment = gtk_tree_view_get_vadjustment(tree_view);
+
+ value = CLAMP (vadjustment->value + offset, 0.0,
+ vadjustment->upper - vadjustment->page_size);
+ gtk_adjustment_set_value (vadjustment, value);
+
+ return TRUE;
+}
+
+/** @brief Enable autoscrolling
+ * @param tree_view Tree view to enable autoscrolling
+ *
+ * It's harmless to call this if autoscrolling is already enabled.
+ *
+ * It's up to you to cancel the callback when no longer required (including
+ * object destruction).
+ */
+void autoscroll_add(GtkTreeView *tree_view) {
+ guint *scrolldata = g_object_get_data(G_OBJECT(tree_view), AUTOSCROLL_KEY);
+ if(!scrolldata) {
+ /* Set up the callback */
+ scrolldata = g_new(guint, 1);
+ *scrolldata = gdk_threads_add_timeout(150, autoscroll_timeout, tree_view);
+ g_object_set_data(G_OBJECT(tree_view), AUTOSCROLL_KEY, scrolldata);
+ //fprintf(stderr, "autoscroll enabled\n");
+ }
+}
+
+/** @brief Disable autoscrolling
+ * @param tree_view Tree view to enable autoscrolling
+ *
+ * It's harmless to call this if autoscrolling is not enabled.
+ */
+void autoscroll_remove(GtkTreeView *tree_view) {
+ guint *scrolldata = g_object_get_data(G_OBJECT(tree_view), AUTOSCROLL_KEY);
+ if(scrolldata) {
+ g_object_set_data(G_OBJECT(tree_view), AUTOSCROLL_KEY, NULL);
+ g_source_remove(*scrolldata);
+ g_free(scrolldata);
+ //fprintf(stderr, "autoscroll disabled\n");
+ }
+}
+
+/*
+Local Variables:
+c-basic-offset:2
+comment-column:40
+fill-column:79
+indent-tabs-mode:nil
+End:
+*/
--- /dev/null
+/* Derived from gtktreeview.c
+ * Copyright (C) 2000 Red Hat, Inc., Jonathan Blandford <jrb@redhat.com>
+ * Portions copyright (C) 2009 Richard Kettlewell
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+/** @file disobedience/autoscroll.h
+ * @brief Automatic scrolling of a GtkTreeView
+ */
+#ifndef AUTOSCROLL_H
+#define AUTOSCROLL_H
+
+void autoscroll_add(GtkTreeView *tree_view);
+void autoscroll_remove(GtkTreeView *tree_view);
+
+#endif /* AUTOSCROLL_H */
+
+/*
+Local Variables:
+c-basic-offset:2
+comment-column:40
+fill-column:79
+indent-tabs-mode:nil
+End:
+*/
#include "disobedience.h"
#include "choose.h"
+#include "multidrag.h"
#include <gdk/gdkkeysyms.h>
+/** @brief Drag types */
+static const GtkTargetEntry choose_targets[] = {
+ {
+ (char *)"text/x-disorder-playable-tracks", /* drag type */
+ GTK_TARGET_SAME_APP|GTK_TARGET_OTHER_WIDGET, /* copying between widgets */
+ 1 /* ID value */
+ },
+};
+
/** @brief The current selection tree */
GtkTreeStore *choose_store;
return TRUE; /* Handled it */
}
+static gboolean choose_multidrag_predicate(GtkTreePath attribute((unused)) *path,
+ GtkTreeIter *iter) {
+ return choose_is_file(iter);
+}
+
+
+/** @brief Callback to add selected tracks to the selection data
+ *
+ * Called from choose_drag_data_get().
+ */
+static void choose_drag_data_get_collect(GtkTreeModel attribute((unused)) *model,
+ GtkTreePath attribute((unused)) *path,
+ GtkTreeIter *iter,
+ gpointer data) {
+ struct dynstr *const result = data;
+
+ if(choose_is_file(iter)) { /* no diretories */
+ dynstr_append_string(result, ""); /* no ID */
+ dynstr_append(result, '\n');
+ dynstr_append_string(result, choose_get_track(iter));
+ dynstr_append(result, '\n');
+ }
+}
+
+/** @brief Called to extract the dragged data from the choose view
+ * @param w Source widget (the tree view)
+ * @param dc Drag context
+ * @param data Where to put the answer
+ * @param info_ Target @c info parameter
+ * @param time_ Time data requested (for some reason not a @c time_t)
+ * @param user_data The queuelike
+ *
+ * Closely analogous to ql_drag_data_get(), and uses the same data format.
+ * IDs are sent as empty strings.
+ */
+static void choose_drag_data_get(GtkWidget *w,
+ GdkDragContext attribute((unused)) *dc,
+ GtkSelectionData *data,
+ guint attribute((unused)) info_,
+ guint attribute((unused)) time_,
+ gpointer attribute((unused)) user_data) {
+ struct dynstr result[1];
+ GtkTreeSelection *sel;
+
+ sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(w));
+ dynstr_init(result);
+ gtk_tree_selection_selected_foreach(sel,
+ choose_drag_data_get_collect,
+ result);
+ gtk_selection_data_set(data,
+ GDK_TARGET_STRING,
+ 8, (guchar *)result->vec, result->nvec);
+}
+
/** @brief Create the choose tab */
GtkWidget *choose_widget(void) {
/* Create the tree store. */
g_signal_connect(choose_view, "key-release-event",
G_CALLBACK(choose_key_event), choose_search_entry);
+ /* Enable dragging of tracks out */
+ gtk_drag_source_set(choose_view,
+ GDK_BUTTON1_MASK,
+ choose_targets,
+ sizeof choose_targets / sizeof *choose_targets,
+ GDK_ACTION_COPY);
+ g_signal_connect(choose_view, "drag-data-get",
+ G_CALLBACK(choose_drag_data_get), NULL);
+ make_treeview_multidrag(choose_view,
+ choose_multidrag_predicate);
+
return vbox;
}
extern GtkWidget *editplaylists_widget;
#endif
-void make_treeview_multidrag(GtkWidget *w);
-
#endif /* DISOBEDIENCE_H */
/*
/*
- * This file is part of DisOrder
* Copyright (C) 2009 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
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
+ * Note that this license ONLY applies to multidrag.c and multidrag.h, not to
+ * the rest of DisOrder.
*
- * 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, see <http://www.gnu.org/licenses/>.
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE
+ * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/** @file disobedience/multidrag.c
* @brief Drag multiple rows of a GtkTreeView
* you dragged from (because it can't cope with dragging more than one row at a
* time).
*
- * Disobedience needs more. To implement this it intercepts button-press-event
- * and button-release event and for clicks that might be the start of drags,
- * suppresses changes to the selection. A consequence of this is that it needs
- * to intercept button-release-event too, to restore the effect of the click,
- * if it turns out not to be drag after all.
+ * Disobedience needs more.
+ *
+ * Firstly it intercepts button-press-event and button-release event and for
+ * clicks that might be the start of drags, suppresses changes to the
+ * selection. A consequence of this is that it needs to intercept
+ * button-release-event too, to restore the effect of the click, if it turns
+ * out not to be drag after all.
*
* The location of the initial click is stored in object data called @c
* multidrag-where.
*
+ * Secondly it intercepts drag-begin and constructs an icon from the rows to be
+ * dragged.
+ *
* Inspired by similar code in <a
* href="http://code.google.com/p/quodlibet/">Quodlibet</a> (another software
* jukebox, albeit as far as I can see a single-user one).
*/
-#include "disobedience.h"
+#include <config.h>
+
+#include <string.h>
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include "multidrag.h"
static gboolean multidrag_selection_block(GtkTreeSelection attribute((unused)) *selection,
GtkTreeModel attribute((unused)) *model,
return FALSE; /* propagate */
}
+/** @brief State for multidrag_begin() and its callbacks */
+struct multidrag_begin_state {
+ GtkTreeView *view;
+ multidrag_row_predicate *predicate;
+ int rows;
+ int index;
+ GdkPixmap **pixmaps;
+};
+
+/** @brief Callback to construct a row pixmap */
+static void multidrag_make_row_pixmaps(GtkTreeModel attribute((unused)) *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer data) {
+ struct multidrag_begin_state *qdbs = data;
+
+ if(qdbs->predicate(path, iter)) {
+ qdbs->pixmaps[qdbs->index++]
+ = gtk_tree_view_create_row_drag_icon(qdbs->view, path);
+ }
+}
+
+/** @brief Called when a drag operation starts
+ * @param w Source widget (the tree view)
+ * @param dc Drag context
+ * @param user_data Row predicate
+ */
+static void multidrag_drag_begin(GtkWidget *w,
+ GdkDragContext attribute((unused)) *dc,
+ gpointer user_data) {
+ struct multidrag_begin_state qdbs[1];
+ GdkPixmap *icon;
+ GtkTreeSelection *sel;
+
+ //fprintf(stderr, "drag-begin\n");
+ memset(qdbs, 0, sizeof *qdbs);
+ qdbs->view = GTK_TREE_VIEW(w);
+ qdbs->predicate = (multidrag_row_predicate *)user_data;
+ sel = gtk_tree_view_get_selection(qdbs->view);
+ /* Find out how many rows there are */
+ if(!(qdbs->rows = gtk_tree_selection_count_selected_rows(sel)))
+ return; /* doesn't make sense */
+ /* Generate a pixmap for each row */
+ qdbs->pixmaps = g_new(GdkPixmap *, qdbs->rows);
+ gtk_tree_selection_selected_foreach(sel,
+ multidrag_make_row_pixmaps,
+ qdbs);
+ /* Might not have used all rows */
+ qdbs->rows = qdbs->index;
+ /* Determine the size of the final icon */
+ int height = 0, width = 0;
+ for(int n = 0; n < qdbs->rows; ++n) {
+ int pxw, pxh;
+ gdk_drawable_get_size(qdbs->pixmaps[n], &pxw, &pxh);
+ if(pxw > width)
+ width = pxw;
+ height += pxh;
+ }
+ if(!width || !height)
+ return; /* doesn't make sense */
+ /* Construct the icon */
+ icon = gdk_pixmap_new(qdbs->pixmaps[0], width, height, -1);
+ GdkGC *gc = gdk_gc_new(icon);
+ gdk_gc_set_colormap(gc, gtk_widget_get_colormap(w));
+ int y = 0;
+ for(int n = 0; n < qdbs->rows; ++n) {
+ int pxw, pxh;
+ gdk_drawable_get_size(qdbs->pixmaps[n], &pxw, &pxh);
+ gdk_draw_drawable(icon,
+ gc,
+ qdbs->pixmaps[n],
+ 0, 0, /* source coords */
+ 0, y, /* dest coords */
+ pxw, pxh); /* size */
+ y += pxh;
+ gdk_drawable_unref(qdbs->pixmaps[n]);
+ qdbs->pixmaps[n] = NULL;
+ }
+ g_free(qdbs->pixmaps);
+ qdbs->pixmaps = NULL;
+ // TODO scale down a bit, the resulting icons are currently a bit on the
+ // large side.
+ gtk_drag_source_set_icon(w,
+ gtk_widget_get_colormap(w),
+ icon,
+ NULL);
+}
+
+static gboolean multidrag_default_predicate(GtkTreePath attribute((unused)) *path,
+ GtkTreeIter attribute((unused)) *iter) {
+ return TRUE;
+}
+
/** @brief Allow multi-row drag for @p w
* @param w A GtkTreeView widget
+ * @param predicate Function called to test rows for draggability, or NULL
+ *
+ * Suppresses the restriction of selections when a drag is started, and
+ * intercepts drag-begin to construct an icon.
*
- * Suppresses the restriction of selections when a drag is started.
+ * @p predicate should return TRUE for draggable rows and FALSE otherwise, to
+ * control what goes in the icon. If NULL, equivalent to a function that
+ * always returns TRUE.
*/
-void make_treeview_multidrag(GtkWidget *w) {
+void make_treeview_multidrag(GtkWidget *w,
+ multidrag_row_predicate *predicate) {
+ if(!predicate)
+ predicate = multidrag_default_predicate;
g_signal_connect(w, "button-press-event",
G_CALLBACK(multidrag_button_press_event), NULL);
g_signal_connect(w, "button-release-event",
G_CALLBACK(multidrag_button_release_event), NULL);
+ g_signal_connect(w, "drag-begin",
+ G_CALLBACK(multidrag_drag_begin), predicate);
}
/*
--- /dev/null
+/*
+ * Copyright (C) 2009 Richard Kettlewell
+ *
+ * Note that this license ONLY applies to multidrag.c and multidrag.h, not to
+ * the rest of DisOrder.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE
+ * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+/** @file disobedience/multidrag.h
+ * @brief Drag multiple rows of a GtkTreeView
+ */
+#ifndef MULTIDRAG_H
+#define MULTIDRAG_H
+
+/** @brief Predicate type for rows to drag
+ * @param path Path to row
+ * @param iter Iterator pointing at row
+ * @return TRUE if row is draggable else FALSE
+ */
+typedef gboolean multidrag_row_predicate(GtkTreePath *path,
+ GtkTreeIter *iter);
+
+void make_treeview_multidrag(GtkWidget *w,
+ multidrag_row_predicate *predicate);
+
+#endif /* MULTIDRAG_H */
+
+/*
+Local Variables:
+c-basic-offset:2
+comment-column:40
+fill-column:79
+indent-tabs-mode:nil
+End:
+*/
#include "disobedience.h"
#include "popup.h"
#include "queue-generic.h"
-
+#include "multidrag.h"
+#include "autoscroll.h"
static const GtkTargetEntry queuelike_targets[] = {
{
- (char *)"text/x-disorder-track-data", /* drag type */
- GTK_TARGET_SAME_WIDGET, /* rearrangement only for now */
+ (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 ----------------------------------------------------- */
--suppress_actions;
}
-/** @brief State for ql_drag_begin() and its callbacks */
-struct ql_drag_begin_state {
- struct queuelike *ql;
- int rows;
- int index;
- GdkPixmap **pixmaps;
-};
-
-/** @brief Callback to construct a row pixmap */
-static void ql_drag_make_row_pixmaps(GtkTreeModel attribute((unused)) *model,
- GtkTreePath *path,
- GtkTreeIter attribute((unused)) *iter,
- gpointer data) {
- struct ql_drag_begin_state *qdbs = data;
-
- qdbs->pixmaps[qdbs->index++]
- = gtk_tree_view_create_row_drag_icon(GTK_TREE_VIEW(qdbs->ql->view),
- path);
-}
+/* Drag and drop ------------------------------------------------------------ */
-/** @brief Called when a drag operation from this queuelike begins
- * @param w Source widget (the tree view)
- * @param dc Drag context
- * @param user_data The queuelike
+/** @brief Identify the drop path
+ * @param w Destination tree view widget
+ * @param model Underlying tree model
+ * @param wx X coordinate
+ * @param wy Y coordinate
+ * @param posp Where to store relative position
+ * @return Target path or NULL
+ *
+ * This is used by ql_drag_motion() and ql_drag_data_received() to identify a
+ * drop would or does land. It's important that they use the same code since
+ * otherwise the visual feedback can be inconsistent with the actual effect!
*/
-static void ql_drag_begin(GtkWidget attribute((unused)) *w,
- GdkDragContext attribute((unused)) *dc,
- gpointer user_data) {
- struct queuelike *const ql = user_data;
- struct ql_drag_begin_state qdbs[1];
- GdkPixmap *icon;
-
- //fprintf(stderr, "drag-begin\n");
- memset(qdbs, 0, sizeof *qdbs);
- qdbs->ql = ql;
- /* Find out how many rows there are */
- if(!(qdbs->rows = gtk_tree_selection_count_selected_rows(ql->selection)))
- return; /* doesn't make sense */
- /* Generate a pixmap for each row */
- qdbs->pixmaps = xcalloc(qdbs->rows, sizeof *qdbs->pixmaps);
- gtk_tree_selection_selected_foreach(ql->selection,
- ql_drag_make_row_pixmaps,
- qdbs);
- /* Determine the size of the final icon */
- int height = 0, width = 0;
- for(int n = 0; n < qdbs->rows; ++n) {
- int pxw, pxh;
- gdk_drawable_get_size(qdbs->pixmaps[n], &pxw, &pxh);
- if(pxw > width)
- width = pxw;
- height += pxh;
- }
- if(!width || !height)
- return; /* doesn't make sense */
- /* Construct the icon */
- icon = gdk_pixmap_new(qdbs->pixmaps[0], width, height, -1);
- GdkGC *gc = gdk_gc_new(icon);
- gdk_gc_set_colormap(gc, gtk_widget_get_colormap(ql->view));
- int y = 0;
- for(int n = 0; n < qdbs->rows; ++n) {
- int pxw, pxh;
- gdk_drawable_get_size(qdbs->pixmaps[n], &pxw, &pxh);
- gdk_draw_drawable(icon,
- gc,
- qdbs->pixmaps[n],
- 0, 0, /* source coords */
- 0, y, /* dest coords */
- pxw, pxh); /* size */
- y += pxh;
+static GtkTreePath *ql_drop_path(GtkWidget *w,
+ GtkTreeModel *model,
+ int wx, int wy,
+ GtkTreeViewDropPosition *posp) {
+ GtkTreePath *path = NULL;
+ GtkTreeViewDropPosition pos;
+ GtkTreeIter iter[1], last[1];
+ int tx, ty;
+
+ gtk_tree_view_convert_widget_to_tree_coords(GTK_TREE_VIEW(w),
+ wx, wy, &tx, &ty);
+ if(gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(w),
+ wx, wy,
+ &path,
+ &pos)) {
+ //fprintf(stderr, "gtk_tree_view_get_dest_row_at_pos() -> TRUE\n");
+ // Normalize drop position
+ switch(pos) {
+ case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
+ pos = GTK_TREE_VIEW_DROP_BEFORE;
+ break;
+ case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
+ pos = GTK_TREE_VIEW_DROP_AFTER;
+ break;
+ default: break;
+ }
+ } else if(gtk_tree_model_get_iter_first(model, iter)) {
+ /* If the pointer isn't over any particular row then either it's below
+ * the last row, in which case we want the dropzone to be below that row;
+ * or it's above the first row (in the column headings) in which case we
+ * want the dropzone to be above that row. */
+ if(ty >= 0) {
+ /* Find the last row */
+ do {
+ *last = *iter;
+ } while(gtk_tree_model_iter_next(model, iter));
+ /* The drop target is just after it */
+ pos = GTK_TREE_VIEW_DROP_AFTER;
+ *iter = *last;
+ } else {
+ /* The drop target will be just before the first row */
+ pos = GTK_TREE_VIEW_DROP_BEFORE;
+ }
+ path = gtk_tree_model_get_path(model, iter);
}
- // TODO scale down a bit, the resulting icons are currently a bit on the
- // large side.
- gtk_drag_source_set_icon(ql->view,
- gtk_widget_get_colormap(ql->view),
- icon,
- NULL);
+ *posp = pos;
+ return path;
}
/** @brief Called when a drag moves within a candidate destination
* @param time_ Current time
* @param user_data Pointer to queuelike
* @return TRUE in a dropzone, otherwise FALSE
+ *
+ * This is the handler for the "drag-motion" signal.
*/
static gboolean ql_drag_motion(GtkWidget *w,
GdkDragContext *dc,
gint x,
gint y,
guint time_,
- gpointer attribute((unused)) user_data) {
- //struct queuelike *const ql = user_data;
+ gpointer user_data) {
+ struct queuelike *const ql = user_data;
GdkDragAction action = 0;
-
+
// GTK_DEST_DEFAULT_MOTION vets actions as follows:
// 1) if dc->suggested_action is in the gtk_drag_dest_set actions
// then dc->suggested_action is taken as the action.
// then the lowest-numbered member of the intersection is chosen.
// 3) otherwise no member is chosen and gdk_drag_status() is called
// with action=0 to refuse the drop.
- // Currently we can only accept _MOVE. But in the future we will
- // need to accept _COPY in some cases.
if(dc->suggested_action) {
- if(dc->suggested_action == GDK_ACTION_MOVE)
+ if(dc->suggested_action & (GDK_ACTION_MOVE|GDK_ACTION_COPY))
action = dc->suggested_action;
} else if(dc->actions & GDK_ACTION_MOVE)
- action = dc->actions;
+ 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);*/
if(action) {
// If the action is acceptable then we see if this widget is acceptable
- if(!gtk_drag_dest_find_target(w, dc, NULL))
+ if(gtk_drag_dest_find_target(w, dc, NULL) == GDK_NONE)
action = 0;
}
// Report the status
+ //fprintf(stderr, "drag action: %u\n", action);
gdk_drag_status(dc, action, time_);
if(action) {
- // Highlight the drop area
- GtkTreePath *path;
GtkTreeViewDropPosition pos;
- if(gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(w),
- x, y,
- &path,
- &pos)) {
- //fprintf(stderr, "gtk_tree_view_get_dest_row_at_pos() -> TRUE\n");
- // Normalize drop position
- switch(pos) {
- case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
- pos = GTK_TREE_VIEW_DROP_BEFORE;
- break;
- case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
- pos = GTK_TREE_VIEW_DROP_AFTER;
- break;
- default: break;
- }
- // Highlight drop target
- gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW(w), path, pos);
- } else {
- //fprintf(stderr, "gtk_tree_view_get_dest_row_at_pos() -> FALSE\n");
- gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW(w), NULL, 0);
- }
+ // Find the drop target
+ GtkTreePath *path = ql_drop_path(w, GTK_TREE_MODEL(ql->store), x, y, &pos);
+ // Highlight drop target
+ gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW(w), path, pos);
+ if(path)
+ gtk_tree_path_free(path);
}
- return TRUE;
+ autoscroll_add(GTK_TREE_VIEW(w));
+ return TRUE; /* We are (always) in a drop zone */
}
/** @brief Called when a drag moves leaves a candidate destination
* @param dc Drag context
* @param time_ Current time
* @param user_data Pointer to queuelike
+ *
+ * This is the handler for the "drag-leave" signal.
+ *
+ * It turns out that we get a drag-leave event when the data is dropped, too
+ * (See _gtk_drag_dest_handle_event). This seems logically consistent and is
+ * convenient too - for instance it's why autoscroll_remove() gets called at
+ * the end of a drag+drop sequence.
*/
static void ql_drag_leave(GtkWidget *w,
GdkDragContext attribute((unused)) *dc,
//struct queuelike *const ql = user_data;
gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW(w), NULL, 0);
+ autoscroll_remove(GTK_TREE_VIEW(w));
}
/** @brief Callback to add selected tracks to the selection data
* @param info_ Target @c info parameter
* @param time_ Time data requested (for some reason not a @c time_t)
* @param user_data The queuelike
+ *
+ * The list of tracks is converted into a single string, consisting of IDs
+ * and track names. Each is terminated by a newline. Including both ID and
+ * track name means that the receiver can use whichever happens to be more
+ * convenient.
+ *
+ * If there are no IDs for rows in this widget then the ID half is undefined.
+ *
+ * This is the handler for the "drag-data-get" signal.
*/
static void ql_drag_data_get(GtkWidget attribute((unused)) *w,
GdkDragContext attribute((unused)) *dc,
struct queuelike *const ql = user_data;
struct dynstr result[1];
- /* The list of tracks is converted into a single string, consisting of IDs
- * and track names. Each is terminated by a newline. Including both ID and
- * track name means that the receiver can use whichever happens to be more
- * convenient. */
dynstr_init(result);
gtk_tree_selection_selected_foreach(ql->selection,
ql_drag_data_get_collect,
result);
+ // TODO must not be able to drag playing track!
//fprintf(stderr, "drag-data-get: %.*s\n",
// result->nvec, result->vec);
/* gtk_selection_data_set_text() insists that data->target is one of a
* @param info_ The target type that was chosen
* @param time_ Time data received (for some reason not a @c time_t)
* @param user_data The queuelike
+ *
+ * This is the handler for the "drag-data-received" signal.
*/
static void ql_drag_data_received(GtkWidget attribute((unused)) *w,
GdkDragContext attribute((unused)) *dc,
struct vector ids[1], tracks[1];
int parity = 0;
- //fprintf(stderr, "drag-data-received: %d,%d\n", x, y);
+ //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) {
vector_terminate(ids);
vector_terminate(tracks);
/* Figure out which row the drop precedes (if any) */
- GtkTreePath *path;
GtkTreeViewDropPosition pos;
struct queue_entry *q;
- if(!gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(ql->view), x, y,
- &path, &pos)) {
- //fprintf(stderr, "gtk_tree_view_get_dest_row_at_pos returned FALSE\n");
+ GtkTreePath *path = ql_drop_path(w, GTK_TREE_MODEL(ql->store), x, y, &pos);
+ if(path) {
+ q = ql_path_to_q(GTK_TREE_MODEL(ql->store), path);
+ } else {
/* This generally means a drop past the end of the queue. We find the last
* element in the queue and ask to move after that. */
for(q = ql->q; q && q->next; q = q->next)
;
- } else {
- /* Convert the path to a queue entry pointer. */
- q = ql_path_to_q(GTK_TREE_MODEL(ql->store), path);
- //fprintf(stderr, " tree view likes to drop near %s\n",
- // q->id ? q->id : "NULL");
- /* TODO interpretation of q=NULL */
- /* Q should point to the entry just before the insertion point, so that
- * moveafter works, or NULL to insert right at the start. We don't support
- * dropping into a row, since that doesn't make sense for us. */
- switch(pos) {
- case GTK_TREE_VIEW_DROP_BEFORE:
- case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
- if(q) {
- q = q->prev;
- //fprintf(stderr, " ...but we like to drop near %s\n",
- // q ? q->id : "NULL");
- }
- break;
- default:
- break;
+ }
+ switch(pos) {
+ case GTK_TREE_VIEW_DROP_BEFORE:
+ case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
+ if(q) {
+ q = q->prev;
+ //fprintf(stderr, " ...but we like to drop near %s\n",
+ // q ? q->id : "NULL");
}
+ break;
+ default:
+ break;
}
+ /* Guarantee we never drop an empty list */
+ if(!tracks->nvec)
+ return;
/* Note that q->id can match one of ids[]. This doesn't matter for
* moveafter but TODO may matter for playlist support. */
- ql->drop(ql, tracks->nvec, tracks->vec, ids->vec, q);
+ switch(info_) {
+ case 0:
+ /* Rearrangement. Send ID and track data. */
+ ql->drop(ql, tracks->nvec, tracks->vec, ids->vec, q);
+ break;
+ case 1:
+ /* Copying between widgets. IDs mean nothing so don't send them. */
+ ql->drop(ql, tracks->nvec, tracks->vec, NULL, q);
+ break;
+ }
}
/** @brief Initialize a @ref queuelike */
GTK_DEST_DEFAULT_HIGHLIGHT|GTK_DEST_DEFAULT_DROP,
queuelike_targets,
sizeof queuelike_targets / sizeof *queuelike_targets,
- GDK_ACTION_MOVE);
- g_signal_connect(ql->view, "drag-begin",
- G_CALLBACK(ql_drag_begin), ql);
+ GDK_ACTION_MOVE|GDK_ACTION_COPY);
g_signal_connect(ql->view, "drag-motion",
G_CALLBACK(ql_drag_motion), ql);
g_signal_connect(ql->view, "drag-leave",
G_CALLBACK(ql_drag_data_get), ql);
g_signal_connect(ql->view, "drag-data-received",
G_CALLBACK(ql_drag_data_received), ql);
- make_treeview_multidrag(ql->view);
+ make_treeview_multidrag(ql->view, NULL);
+ // TODO playing track should be refused by predicate arg
} else {
- /* TODO: support copy-dragging out of non-rearrangeable queues. Will need
- * to support copy dropping into the rearrangeable ones. */
+ /* 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);
+ g_signal_connect(ql->view, "drag-data-get",
+ G_CALLBACK(ql_drag_data_get), ql);
+ make_treeview_multidrag(ql->view, NULL);
}
/* TODO style? */
g_timeout_add(1000/*ms*/, playing_periodic, 0);
}
-static void queue_move_completed(void attribute((unused)) *v,
+static void queue_drop_completed(void attribute((unused)) *v,
const char *err) {
if(err) {
popup_protocol_error(0, err);
/** @brief Called when drag+drop completes */
static void queue_drop(struct queuelike attribute((unused)) *ql,
int ntracks,
- char attribute((unused)) **tracks, char **ids,
+ char **tracks, char **ids,
struct queue_entry *after_me) {
int n;
-
- if(playing_track) {
- /* If there's a playing track then you can't drag it anywhere */
- for(n = 0; n < ntracks; ++n) {
- if(!strcmp(playing_track->id, ids[n])) {
- fprintf(stderr, "cannot drag playing track\n");
- return;
+
+ if(ids) {
+ /* Rearrangement */
+ if(playing_track) {
+ /* If there's a playing track then you can't drag it anywhere */
+ for(n = 0; n < ntracks; ++n) {
+ if(!strcmp(playing_track->id, ids[n])) {
+ fprintf(stderr, "cannot drag playing track\n");
+ return;
+ }
}
+ /* You can't tell the server to drag after the playing track by ID, you
+ * have to send "". */
+ if(after_me == playing_track)
+ after_me = NULL;
+ /* If you try to drag before the playing track (i.e. after_me=NULL on
+ * input) then the effect is just to drag after it, although there's no
+ * longer code to explicitly implement this. */
}
- /* You can't tell the server to drag after the playing track by ID, you
+ /* Tell the server to move them. The log will tell us about the change (if
+ * indeed it succeeds!), so no need to rearrange the model now. */
+ disorder_eclient_moveafter(client,
+ after_me ? after_me->id : "",
+ ntracks, (const char **)ids,
+ queue_drop_completed, NULL);
+ } else {
+ /* You can't tell the server to insert after the playing track by ID, you
* have to send "". */
if(after_me == playing_track)
after_me = NULL;
- /* If you try to drag before the playing track (i.e. after_me=NULL on
- * input) then the effect is just to drag after it, although there's no
- * longer code to explicitly implement this. */
+ /* Play the tracks */
+ disorder_eclient_playafter(client,
+ after_me ? after_me->id : "",
+ ntracks, (const char **)tracks,
+ queue_drop_completed, NULL);
}
- /* Tell the server to move them. The log will tell us about the change (if
- * indeed it succeeds!), so no need to rearrange the model now. */
- disorder_eclient_moveafter(client,
- after_me ? after_me->id : "",
- ntracks, (const char **)ids,
- queue_move_completed, NULL);
}
/** @brief Columns for the queue */
Left clicking on a file will select it.
As with the queue tab you can use SHIFT and CTRL to select multiple files.
.PP
+Files may be played by dragging them to the queue tab and thence to a
+destination position in the queue.
+.PP
The text box at the bottom is a search form.
If you enter search terms here then tracks containing all those words will be
highlighted.
Drew, Copyright © 2000-2001 Robert Leslie<br>
Portions copyright © 1997-2006 <a
- href="http://www.fsf.org/">Free Software Foundation, Inc</a>.</p>
+ href="http://www.fsf.org/">Free Software Foundation, Inc</a><br>
+
+ Portions Copyright © 2000 <a href="http://www.redhat.com">Red Hat,
+ Inc.</a>, Jonathan Blandford <jrb@redhat.com></p>
<p>This program is free software: you can redistribute it and/or modify it
under the terms of the <a href="http://www.gnu.org/licenses/gpl-3.0.html">GNU