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
+
+