Index: channels/dnd.py ================================================================== --- channels/dnd.py +++ channels/dnd.py @@ -12,78 +12,137 @@ # PLS, XSPF collections. # # Also used by the bookmarks tab to move favourites around. +import copy from config import * from uikit import * +import action # Drag and Drop support class dnd(object): module = "dnd" meta = plugin_meta() # Keeps selected row on starting DND event - current_row = None + row = None + # Buffer converted types + buf = {} # Supported type map drag_types = [ + ("json/vnd.streamtuner2.station", 0, 51), ("audio/x-mpegurl", 0, 20), ("application/x-scpls", 0, 21), ("application/xspf+xml", 0, 22), + ("FILE_NAME", 0, 3), ("text/uri-list", 0, 4), -# ("TEXT", 0, 5), -# ("STRING", 0, 5), -# ("text/plain", 0, 5), -# ("UTF8_STRING", 0, 5), + ("STRING", 0, 5), + ("text/plain", 0, 5), ] + cnv_types = { + 20: "m3u", + 21: "pls", + 22: "xspf", + 4: "temp", + 5: "srv", + 51: "json", + } # Hook to main, and extend channel tabs def __init__(self, parent): self.parent = parent parent.hooks["init"].append(self.add_dnd) - # Attach drag and drop functionality to each TreeView + + # Attach drag and drop handlers to each channels´ station TreeView def add_dnd(self, parent): # visit each module for cn,module in parent.channels.items(): w = module.gtk_list - # as source + # bind SOURCE events w.enable_model_drag_source(gtk.gdk.BUTTON1_MASK, self.drag_types, gtk.gdk.ACTION_DEFAULT|gtk.gdk.ACTION_COPY|gtk.gdk.ACTION_MOVE) w.connect('drag-begin', self.begin) w.connect('drag-data-get', self.data_get) - # as target + # bind DESTINATION events w.enable_model_drag_dest(self.drag_types, gtk.gdk.ACTION_DEFAULT|gtk.gdk.ACTION_COPY) - w.connect('drag-drop', self.drop) + w.connect('drag-drop', self.drop)#self.drag_types w.connect('drag-data-received', self.data_received) - # --SOURCE-- - def begin(self, tv, context): - print "begin-dragging" - context.set_icon_stock("gtk-add", 2, 2) - self.current_row = self.treelist_row(tv) - return True - # src testing - def data_get(self, tv, context, selection, info, *time): - print "data-get→send", context.targets, selection, info, time - selection.set_uris([self.current_row["url"]]) - return True - # Get selected row - def treelist_row(self, tv): - return self.parent.channel().row() - - - # --DESTINATION-- - def drop(self, tv, context, x, y, time, *e): - print "drop→probing", context.targets, x, y, time, context.drag_get_selection() - tv.drag_get_data(context, context.targets[-1], time) - return True - # dest testing - def data_received(self, tv, context, x, y, selection, info, time, *e): - print "data-received", x,y,selection, info, time, selection.get_uris() - context.finish(True, True, time) - return True + + # -- SOURCE, drag'n'drop from ST2 to elsewhere -- + + # Starting to drag a row + def begin(self, widget, context): + __print__(dbg.UI, "dnd←source: begin-drag, store current row") + self.row = self.treelist_row() + self.buf = {} + #context.set_icon_stock("gtk-add", 2, 2) + return "url" in self.row + + + # Keep currently selected row when source dragging starts + def treelist_row(self): + cn = self.parent.channel() + row = copy.copy(cn.row()) + row.setdefault("format", cn.audioformat) + row.setdefault("listformat", cn.listformat) + return row + + + # Target window/app requests data for offered drop + def data_get(self, widget, context, selection, info, time): + __print__(dbg.UI, "dnd←source: data-get, send and convert to requested target type", info) + + # Start new converter if not buffered (because `data_get` gets called mercilessly along the dragging path) + if not info in self.buf: + r = self.row + cnv = action.save_playlist(source=r["listformat"], multiply=False) + + # Pass M3U/PLS/XSPF as direct content, or internal JSON even + if info >= 20: + buf = 'set_text', cnv.export(urls=[r["url"]], row=r, dest=self.cnv_types[info]) + # Create temporary PLS file, because "text/uri-list" is widely misunderstood and just implemented for file:// IRLs + elif info <= 4: + fn = "{}/{}.pls".format(conf.tmp, re.sub("[^\w-]+", " ", r["title"])) + cnv.file(rows=[r], dest="pls", fn=fn) + if info == 4: + fn = ["file://localhost{}".format(fn)] + buf = 'set_uris', fn + # Text sources are assumed to understand the literal URL, or expect a description + else: + buf = 'set_text', "{url}\n# Title: {title}\n# Homepage: {homepage}".format(**r) + + # Buffer + self.buf[info] = buf + + # Return prepared data + func, data = self.buf[info] + if func in ('set_text'): + selection.set_text(data) + else: + selection.set_uris(data) + return True + + + # -- DESTINATION, when playlist/url gets dragged in from other app -- + + # Just a notification for incoming drop + def drop(self, widget, context, x, y, time): + __print__(dbg.UI, "dnd→dest: drop-probing", context.targets, x, y, time, context.drag_get_selection()) + widget.drag_get_data(context, context.targets[0], time) + return True + + # Actual data is being passed, + # now has to be converted and patched into stream rows and channel liststore + def data_received(self, widget, context, x, y, selection, info, time): + __print__(dbg.UI, "dnd→dest: data-receival", x,y,selection, info, time, selection.get_uris(), selection.get_text()) + context.finish(True, False, time) + return True + +