Check-in [e0e28edba2]
Overview
| Comment: | Experiment with a few more target types. Works with a few text editors on STRING. Most just want a uri-list, but can't handle it. | 
|---|---|
| Downloads: | Tarball | ZIP archive | SQL archive | 
| Timelines: | family | ancestors | descendants | both | trunk | 
| Files: | files | file ages | folders | 
| SHA1: | e0e28edba26bf906967648092b554b80 | 
| User & Date: | mario on 2015-04-19 19:35:57 | 
| Other Links: | manifest | tags | 
Context
| 2015-04-19 | ||
| 22:17 | Implement in-application row copying per JSON (info=51, mime=json/vnd.streamtuner2.station). Fixed set_text() bug by using set("STRING",..) atom instead. check-in: 38812e4bbf user: mario tags: trunk | |
| 19:35 | Experiment with a few more target types. Works with a few text editors on STRING. Most just want a uri-list, but can't handle it. check-in: e0e28edba2 user: mario tags: trunk | |
| 19:35 | Add plugin defaults. check-in: 6a17061df0 user: mario tags: trunk | |
Changes
Modified action.py from [820487cd33] to [ceb9b4a4e2].
| ︙ | ︙ | |||
| 387 388 389 390 391 392 393 | 
                    row["url"] = url
                    new_rows.append(row)
                    # Or just allow one stream per station in a playlist entry
                    if not self.multiply:
                        break
            rows = new_rows
 | | | 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 | 
                    row["url"] = url
                    new_rows.append(row)
                    # Or just allow one stream per station in a playlist entry
                    if not self.multiply:
                        break
            rows = new_rows
        debug(dbg.DATA, "conversion to:", dest, "  with rows=", rows)
        # call conversion schemes
        converter = getattr(self, dest) or self.pls
        return converter(rows)
    # save directly
    def file(self, rows, dest, fn):
 | 
| ︙ | ︙ | |||
| 458 459 460 461 462 463 464 | 
    # SMIL
    def smil(self, rows):
        txt = """<smil>\n<head>\n\t<meta name="title" content="%s"/>\n</head>\n<body>\n\t<seq>\n""" % (rows[0]["title"])
        for row in rows:
            if row.get("url"):
 | | | 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 | 
    # SMIL
    def smil(self, rows):
        txt = """<smil>\n<head>\n\t<meta name="title" content="%s"/>\n</head>\n<body>\n\t<seq>\n""" % (rows[0]["title"])
        for row in rows:
            if row.get("url"):
                txt += """\t\t<{} src="{}"/>\n""".format(row.get("format", "audio").split("/")[0], row["url"])
        txt += """\t</seq>\n</body>\n</smil>\n"""
        return txt
# Generate filename for temporary .m3u, if possible with unique id
 | 
| ︙ | ︙ | 
Modified channels/dnd.py from [2bdcd36106] to [3ecc3ca6e1].
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | # encoding: UTF-8 # api: streamtuner2 # title: Drag and Drop # description: Move streams/stations from and to other applications. # depends: uikit # version: 0.1 # type: interface # category: ui # # Implements Gtk/X11 drag and drop support for station lists. # Should allow to export either just stream URLs, or complete # PLS, XSPF collections. # # Also used by the bookmarks tab to move favourites around. import copy | > > > > > > > > > > | > > > | > > > > > | > > > > > > > > > > | | < > < | > > > > | | | < > | < < < | | > | > > > > > | | | | | < > > | > | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 | 
# encoding: UTF-8
# api: streamtuner2
# title: Drag and Drop
# description: Move streams/stations from and to other applications.
# depends: uikit
# version: 0.1
# type: interface
# config:
#   { name: dnd_format, type: select, value: pls, select: "pls|m3u|xspf|jspf|asx|smil", description: "Default temporary file format for copying a station entry." }
# category: ui
# priority: experimental
#
# Implements Gtk/X11 drag and drop support for station lists.
# Should allow to export either just stream URLs, or complete
# PLS, XSPF collections.
#
# Also used by the bookmarks tab to move favourites around.
# mousepad == ['GTK_TEXT_BUFFER_CONTENTS', 'application/x-gtk-text-buffer-rich-text',
#   'UTF8_STRING', 'COMPOUND_TEXT', 'TEXT', 'STRING',
#   'text/plain;charset=utf-8', 'text/plain']
# libreoffice ==# ['text/plain;charset=utf-8', 'UTF8_STRING', 'application/x-openoffice-embed-source-xml;windows_formatname="Star Embed# Source (XML)"', 'text/richtext', 'text/html',
#    'application/x-openoffice-objectdescriptor-xml;windows_formatname="Star Object Descriptor (XML)";classname="8BC6B165-B1B2-4EDD-aa47-dae2ee689dd6";typename="LibreOffice 4.4 Textdokument";viewaspect="1";width="16999";height="2995";posx="5347";posy="5347"']
import copy
from config import conf, __print__, dbg, json
from uikit import *
import action
# Drag and Drop support
class dnd(object):
    module = "dnd"
    meta = plugin_meta()
    # Keeps selected row on starting DND event
    row = None
    # Buffer converted types
    buf = {}
    # Supported type map
    drag_types = [
      # internal
      ("json/vnd.streamtuner2.station", 0, 51),
      # literal exports
      ("audio/x-mpegurl", 0, 20),
      ("application/x-scpls", 0, 21),
      ("application/xspf+xml", 0, 22),
      ("application/smil", 0, 23),
      ("text/html", 0, 23),
      ("text/richtext", 0, 23),
      ("application/jspf+json", 0, 25),
      # direct srv urls
      ("text/url", 0, 15),  #@TODO: support in action.save_/convert_
      ("message/external-body", 0, 15),
      ("url/direct", 0, 15),
      # url+comments
      ("TEXT", 0, 5),
      ("STRING", 0, 5),
      ("UTF8_STRING", 0, 5),
      ("text/plain", 0, 5),
      # filename, file:// IRL
      ("FILE_NAME", 0, 3),
      ("text/uri-list", 0, 4),
    ]
    cnv_types = {
       20: "m3u",
       21: "pls",
       22: "xspf",
       23: "smil",
       25: "jspf",
       15: "srv",
        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)
        conf.add_plugin_defaults(self.meta, self.module)
    # 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
            # 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)
            # 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-data-received', self.data_received)
    # -- 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 = {}
        uikit.do(context.set_icon_stock, gtk.STOCK_ADD, 16, 16)
        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)
        row.setdefault("url", row.get("homepage"))
        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, selection.get_target())
        # 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)
            # internal JSON row
            info = 5
            if info >= 51:
                buf = 'text', json.dumps(r)
            # Pass M3U/PLS/XSPF as literal payload
            elif info >= 20:
                buf = 'text', cnv.export(urls=[r["url"]], row=r, dest=self.cnv_types[info])
            # Direct server URL
            elif info >= 10:
                urls = action.convert_playlist(r["url"], r["listformat"], "srv", False, r)
                #buf = 'uris', urls
                buf = 'text', urls[0]
            # Text sources are assumed to understand the literal URL or expect a description block
            elif info >= 5:
                buf = 'text', "{url}\n# Title: {title}\n# Homepage: {homepage}\n\n".format(**r)
            # Create temporary PLS file, because "text/uri-list" is widely misunderstood and just implemented for file:// IRLs
            else:
                tmpfn = "{}/{}.{}".format(conf.tmp, re.sub("[^\w-]+", " ", r["title"]), conf.dnd_format)
                cnv.file(rows=[r], dest=conf.dnd_format, fn=tmpfn)
                buf = 'uris', ["file://{}".format(tmpfn)] if (info==4) else tmpfn
            # Keep in type request buffer
            self.buf[info] = buf
        
        # Return prepared data
        func, data = self.buf[info]
        if func.find("text") >= 0:
            selection.set_text(data, len(data))
        if func.find("uris") >= 0:
            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, "STRING", time)#context.targets[0], time)
        context.drop_reply(True, 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", info, selection.get_target(), selection.get_uris(), selection.get_text())
        context.drop_finish(True, time)
        context.finish(True, False, time)
        return True
 |