Internet radio browser GUI for music/video streams from various directory services.

⌈⌋ branch:  streamtuner2


Check-in [9f0bce8535]

Overview
Comment:Fallback for Gtk3 using set_text() now. Fix "href" vs "raw" format probing.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 9f0bce853564a44b0778318bafd6c0ccb5a54113
User & Date: mario on 2015-04-24 21:53:39
Other Links: manifest | tags
Context
2015-04-24
21:54
Add spacing for config dialog options (indented per plugin). Narrower labels, icons now show up. Undo newline-removal for Gtk3 tooltips (work with preformatted text instead). check-in: c02e9a3ec0 user: mario tags: trunk
21:53
Fallback for Gtk3 using set_text() now. Fix "href" vs "raw" format probing. check-in: 9f0bce8535 user: mario tags: trunk
19:22
Add .url exporting (shallow Windows variant of .desktop files). check-in: fd963a3d9b user: mario tags: trunk
Changes

Modified channels/dnd.py from [aebf24e232] to [4107767093].

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
import action
import compat2and3


# Welcome to my new blog.
#
# Now it's perhaps not even Gtks fault, but all the gory implementation
# details of XDND are pretty gory. Neither match up to reality anymore.

#
# Pretty much only the ridiculous `TEXT/URI-LIST` is used in practice.
# Without host names, of course, despite the spec saying otherwise. (It
# perhaps leaked into the Gnome UI, and they decreed it banished). And
# needless to say, there's no actual IRI/URI support in any file manager

# or pairing apps beyond local paths.
#
# Supporting PLS, XSPF, M3U as direct payload was a pointless exercise.
# It's not gonna get requested by anyone. Instead there's another config
# option now, which predefines the exchange format for temporary file:///
# dumps. Because, you know, there was never any point in type negotiation
# due to all the API overhead.

#
# What works, and what's widely used in practice instead, is declaring
# yet another custom type per application. Our row format is transferred
# unfiltered over the selection buffer as JSON. However, it's decidedly
# never exposed to other apps as x-special/x-custom whatever. (It's also
# not using the MIME 1.0 application/* trash bin for that very reason.)








|
>

|
|
|
|
>
|





|
>







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
import action
import compat2and3


# Welcome to my new blog.
#
# Now it's perhaps not even Gtks fault, but all the gory implementation
# details of XDND are pretty gory. And not much documentatiion lives up
# to reality anymore.
#
# Almost only the ridiculous `TEXT/URI-LIST` is used in practice. Without
# host names, of course, despite the spec saying otherwise. (It perhaps
# leaked into the Gnome UI, and they decreed it banished). And needless
# to say, there's no actual IRI (or "URI" if you've been living under a
# rock for two decades) support in any file manager or pairing clients
# beyond local paths.
#
# Supporting PLS, XSPF, M3U as direct payload was a pointless exercise.
# It's not gonna get requested by anyone. Instead there's another config
# option now, which predefines the exchange format for temporary file:///
# dumps. Because, you know, there was never any point in type negotiation
# due to all the API overhead. There's no way to indicate any actually
# supported content types per text/uri-list.
#
# What works, and what's widely used in practice instead, is declaring
# yet another custom type per application. Our row format is transferred
# unfiltered over the selection buffer as JSON. However, it's decidedly
# never exposed to other apps as x-special/x-custom whatever. (It's also
# not using the MIME 1.0 application/* trash bin for that very reason.)

77
78
79
80
81
82
83

84
85
86
87
88
89
90
      ("application/x-desktop", 0, 26),
      # direct srv urls
      ("text/url", 0, 15),  #@TODO: support in action.save_/convert_
      ("message/external-body", 0, 15),
      ("url/direct", 0, 15),
      # filename, file:// IRL
      ("FILE_NAME", 0, 3),

      ("text/uri-list", 0, 4),
      # url+comments
      ("TEXT", 0, 5),
      ("STRING", 0, 5),
      ("UTF8_STRING", 0, 5),
      ("text/plain", 0, 5),
    ]







>







80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
      ("application/x-desktop", 0, 26),
      # direct srv urls
      ("text/url", 0, 15),  #@TODO: support in action.save_/convert_
      ("message/external-body", 0, 15),
      ("url/direct", 0, 15),
      # filename, file:// IRL
      ("FILE_NAME", 0, 3),
#     ("text/uri-list;x-format=xspf,pls,m3u,jspf,smil,http", 0, 4),
      ("text/uri-list", 0, 4),
      # url+comments
      ("TEXT", 0, 5),
      ("STRING", 0, 5),
      ("UTF8_STRING", 0, 5),
      ("text/plain", 0, 5),
    ]
158
159
160
161
162
163
164

165
166
167


168

169
170
171
172
173
174
175
    # Target window/app requests data for offered drop
    def data_get(self, widget, context, selection, info, time):
        log.DND("source→out: data-get, send and convert to requested target type:", info, selection.get_target())
        # Return prepared data
        func, data = self.export_row(info, self.row)
        log.DND("data==", func, data)
        if func.find("text") >= 0:

            # Yay for trial and error. Nay for docs. PyGtks selection.set_text() doesn't
            # actually work unless the requested target type is an Atom. Therefore "STRING".
            selection.set("STRING", 8, data)


            # Neither gtk.gdk.TARGET_STRING nor selection.get_target() satisfy Gtk3 however

        if func.find("uris") >= 0:
            selection.set_uris(data)
        return True

    # Handles the conversion from the stored .row to the desired selection data
    def export_row(self, info, r):








>
|
|
|
>
>
|
>







162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
    # Target window/app requests data for offered drop
    def data_get(self, widget, context, selection, info, time):
        log.DND("source→out: data-get, send and convert to requested target type:", info, selection.get_target())
        # Return prepared data
        func, data = self.export_row(info, self.row)
        log.DND("data==", func, data)
        if func.find("text") >= 0:
            try:
                # Yay for trial and error. Nay for docs. PyGtks selection.set_text() doesn't
                # actually work unless the requested target type is an Atom. Therefore "STRING".
                selection.set("STRING", 8, data)
            except:
                # Except of course Gtk3, where an Atom is required, but neither
                # gtk.gdk.TARGET_STRING nor selection.get_target() actually do
                selection.set_text(data, len(data))
        if func.find("uris") >= 0:
            selection.set_uris(data)
        return True

    # Handles the conversion from the stored .row to the desired selection data
    def export_row(self, info, r):

213
214
215
216
217
218
219

220
221
222
223
224
225
226
227

    # -- DESTINATION, when playlist/file gets dragged into ST2 from other app --

    # Just a notification for incoming drop
    def drop(self, widget, context, x, y, time):
        log.DND("dest←in: drop-probing, possible targets:", context.targets)
        # find a matching target

        accept = [type[0] for type in self.drag_types if type[0] in context.targets]
        context.drop_reply(len(accept) > 0, time)
        if accept:
                widget.drag_get_data(context, accept[0], time) or True
        return True

    # Actual data is being passed,
    def data_received(self, widget, context, x, y, selection, info, time):







>
|







221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236

    # -- DESTINATION, when playlist/file gets dragged into ST2 from other app --

    # Just a notification for incoming drop
    def drop(self, widget, context, x, y, time):
        log.DND("dest←in: drop-probing, possible targets:", context.targets)
        # find a matching target
        targets = [t.split(";")[0] for t in context.targets]
        accept = [type[0] for type in self.drag_types if type[0] in targets]
        context.drop_reply(len(accept) > 0, time)
        if accept:
                widget.drag_get_data(context, accept[0], time) or True
        return True

    # Actual data is being passed,
    def data_received(self, widget, context, x, y, selection, info, time):
252
253
254
255
256
257
258





259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
            log.DND("Received row in internal format, append+reload")
            rows += [ json.loads(data) ]

        # Convertible formats as direct payload
        elif data and info >= 5:
            log.DND("Converting direct payload playlist")
            cnv = action.extract_playlist(data)





            add = cnv.rows(self.cnv_types[info] if info>=20 else cnv.probe_fmt() or "raw")
            rows += [ cnv.mkrow(row) for row in add ]

        # Extract from playlist files, either passed as text/uri-list or single FILE_NAME
        elif urls:
            log.DND("Importing from playlist file")
            for fn in urls or [data]:
                if not re.match("^(scp|file)://(localhost)?/|/", fn):
                    continue
                fn = compat2and3.urldecode(re.sub("^\w+://[^/]*", "", fn))
                cnv = action.extract_playlist(fn=fn)
                if cnv.src:
                    rows += [ cnv.mkrow(row) for row in cnv.rows() ]
        
        # Insert and update view
        if rows:
            cn.insert_rows(rows, y)
            # if cn.module == "bookmarks":
            cn.save()
            # Show streamedit window if title is empty
            if not len(rows[0].get("title", "")):
                self.parent.configwin.load_config(rows[0], "streamedit_")
                self.parent.win_streamedit.show()
        else:
            self.parent.status("Unsupported station format. Not imported.")


        







>
>
>
>
>
|
















<










261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289

290
291
292
293
294
295
296
297
298
299
            log.DND("Received row in internal format, append+reload")
            rows += [ json.loads(data) ]

        # Convertible formats as direct payload
        elif data and info >= 5:
            log.DND("Converting direct payload playlist")
            cnv = action.extract_playlist(data)
            if info >= 20:
                fmt = self.cnv_types[info]
            else:
                fmt = cnv.probe_fmt()
                if fmt == "href": fmt = "raw"
            add = cnv.rows(fmt)
            rows += [ cnv.mkrow(row) for row in add ]

        # Extract from playlist files, either passed as text/uri-list or single FILE_NAME
        elif urls:
            log.DND("Importing from playlist file")
            for fn in urls or [data]:
                if not re.match("^(scp|file)://(localhost)?/|/", fn):
                    continue
                fn = compat2and3.urldecode(re.sub("^\w+://[^/]*", "", fn))
                cnv = action.extract_playlist(fn=fn)
                if cnv.src:
                    rows += [ cnv.mkrow(row) for row in cnv.rows() ]
        
        # Insert and update view
        if rows:
            cn.insert_rows(rows, y)

            cn.save()
            # Show streamedit window if title is empty
            if not len(rows[0].get("title", "")):
                self.parent.configwin.load_config(rows[0], "streamedit_")
                self.parent.win_streamedit.show()
        else:
            self.parent.status("Unsupported station format. Not imported.")