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

⌈⌋ branch:  streamtuner2


Check-in [50c68197c7]

Overview
Comment:Enable `list`/`array`/`table` config schemes in addition to `dict`. Currently unused, just added because it's little more code. No custom value="x,x,x|y,y,y" parser yet. Not for `dict` either, as it's only used by specbuttons with a predefined list.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 50c68197c7f3427db8ce244557332a314e1f3410
User & Date: mario on 2016-10-28 23:12:11
Other Links: manifest | tags
Context
2016-10-28
23:18
bump minor versions check-in: 1478740ada user: mario tags: trunk
23:12
Enable `list`/`array`/`table` config schemes in addition to `dict`. Currently unused, just added because it's little more code. No custom value="x,x,x|y,y,y" parser yet. Not for `dict` either, as it's only used by specbuttons with a predefined list. check-in: 50c68197c7 user: mario tags: trunk
23:08
Fix add_default=False logic. check-in: 87a6d5ac96 user: mario tags: trunk
Changes

Modified channels/configwin.py from [834ef930af] to [f3a2a25e57].

62
63
64
65
66
67
68
69
70

71
72
73






74
75
76
77
78
79
80
..
88
89
90
91
92
93
94





95
96
97
98
99
100
101
102
103
104
105
                # dropdown
                elif isinstance(w, ComboBoxText):
                    w.set_default(val)
                # number
                elif isinstance(w, gtk.SpinButton):
                    w.set_value(int(val))
                # list
                elif isinstance(w, gtk.ListStore) and isinstance(val, dict):
                    w.clear()

                    for k,v in val.items():
                        w.append([k, v, uikit.app_bin_check(v)])
                    w.append(["", "", gtk.STOCK_NEW])






            #log.CONF("config load", prefix+key, val, type(w))

    # Store gtk widget valus back into conf. dict
    def save_config(self, config, prefix="config_", save=0):
        for key,val in config.items():
            w = self.main.get_widget(prefix + key)
            if w:
................................................................................
                elif isinstance(w, gtk.CheckButton):
                    config[key] = w.get_active()
                # int
                elif isinstance(w, gtk.SpinButton):
                    config[key] = int(w.get_value(val))
                # dict
                elif isinstance(w, gtk.ListStore):





                    config[key] = {}
                    for row in w:
                        if row[0] and row[1]:
                            config[key][row[0]] = row[1]
            log.CONF("config save", prefix+key, val)
    

    # iterate over channel and feature plugins
    def add_plugins(self):
        ls = all_plugin_meta()
        for name,meta in sorted(ls.items(), key=lambda e: e[1]["type"]+e[1]["title"].lower(), reverse=False):







|

>
|
|
|
>
>
>
>
>
>







 







>
>
>
>
>
|
|
|
|







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
..
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
                # dropdown
                elif isinstance(w, ComboBoxText):
                    w.set_default(val)
                # number
                elif isinstance(w, gtk.SpinButton):
                    w.set_value(int(val))
                # list
                elif isinstance(w, gtk.ListStore):
                    w.clear()
                    if isinstance(val, dict):
                        for k,v in val.items():
                            w.append([k, v, uikit.app_bin_check(v)])
                        w.append(["", "", gtk.STOCK_NEW])
                    elif isinstance(val, list):
                        for row in val:
                            log.DATA(row)
                            w.append([str(e) for e in row])
                        if len(val):
                            w.append(["" for e in val[0]])
            #log.CONF("config load", prefix+key, val, type(w))

    # Store gtk widget valus back into conf. dict
    def save_config(self, config, prefix="config_", save=0):
        for key,val in config.items():
            w = self.main.get_widget(prefix + key)
            if w:
................................................................................
                elif isinstance(w, gtk.CheckButton):
                    config[key] = w.get_active()
                # int
                elif isinstance(w, gtk.SpinButton):
                    config[key] = int(w.get_value(val))
                # dict
                elif isinstance(w, gtk.ListStore):
                    if key in config and isinstance(config[key], list):
                        config[key] = []
                        for row in w:
                            config[key].append([str(e) for e in row])
                    else:
                        config[key] = {}
                        for row in w:
                            if row[0] and row[1]:
                                config[key][row[0]] = row[1]
            log.CONF("config save", prefix+key, val)
    

    # iterate over channel and feature plugins
    def add_plugins(self):
        ls = all_plugin_meta()
        for name,meta in sorted(ls.items(), key=lambda e: e[1]["type"]+e[1]["title"].lower(), reverse=False):

Modified channels/specbuttons.py from [19182e61c1] to [0939200c7c].

3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# description: Adds configurable mini toolbar buttons
# version: 0.7
# depends: streamtuner2 >= 2.2.0
# type: feature
# category: ui
# config:
#    { name: specbutton_rows, value: 2, max: 4, type: int, description: "Number of rows to arrange buttons in." }
#    { name: specbuttons, type: dict, columns: "Icon,Command", description: "Icons can either be `<a href='http://www.pygtk.org/pygtk2reference/gtk-stock-items.html'>gtk-xyz</a>` internal names, or `/usr/share/icon/*.png` path names ← which icon basenames would be expanded into. Commands can be any external program." }
# doc:
#    http://fossil.include-once.org/streamtuner2/info/43b36ed35b1488d5
#
# Adds the mini/extra buttons in the toolbar, which allow to control your
# audio player or run other system commands. The configuration list is in
# the Settings → Options tab.
#







|







3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# description: Adds configurable mini toolbar buttons
# version: 0.7
# depends: streamtuner2 >= 2.2.0
# type: feature
# category: ui
# config:
#    { name: specbutton_rows, value: 2, max: 4, type: int, description: "Number of rows to arrange buttons in." }
#    { name: specbuttons, type: dict, columns: "Icon,Command", description: "Icons can be `<a href='http://www.pygtk.org/pygtk2reference/gtk-stock-items.html'>gtk-xyz</a>` internal names. Else use `/usr/share/icon/*.png` file names. Icon file basenames will be expanded into full paths. "}
# doc:
#    http://fossil.include-once.org/streamtuner2/info/43b36ed35b1488d5
#
# Adds the mini/extra buttons in the toolbar, which allow to control your
# audio player or run other system commands. The configuration list is in
# the Settings → Options tab.
#

Modified help/specbuttons.page from [3b72d31b24] to [76c3d65779].

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
..
54
55
56
57
58
59
60





61
62


63
64
65
<info>
	<link type="guide" xref="index#extra"/>
	<link type="guide" xref="configuration#apps"/>
	<link type="guide" xref="configuration#gui"/>
	<desc>Toolbar application short cuts.</desc>
</info>

	<title>Extra/mini buttons</title>

	<p>Using the specbuttons plugin, you can define additional
	shortcuts. This is meant as convenience - allows to start
	other audio apps or system ontrols from within streamtuner2.</p>
	
	<p>The settings list can be found in the Settings → Options tab. It
	follows the layout of the player/recording configuration. Except
	that it lists icons and their according action commands:</p>


	<table shade="rows" rules="rows cols">
		<tr><td><p><em>Icon</em></p></td>  	<td><p><em>Cmd</em></p></td> </tr>
		<tr><td><p><app>gtk-media-forward</app></p></td>     <td><p><cmd>vlc next</cmd></p></td> </tr>
		<tr><td><p><app>terminal</app></p></td>  	<td><p><cmd>xterm</cmd></p></td> </tr>
		<tr><td><p><app>volume</app></p></td>		<td><p><cmd>pavucontrol</cmd></p></td> </tr>
		<tr><td><p><app>database</app></p></td>		<td><p><cmd>./myscript "%url" "%title"</cmd></p></td> </tr>
		<tr><td><p><app>./import.png</app></p></td>	<td><p><cmd>audacity %pls</cmd></p></td> </tr>
	</table>

        <section id="gtk-icons">
        <title>gtk-icons</title>
	<p>Note that the icon name can either be Gtk-builtin icon. Their
        names always start with "<app>gtk-</app>", for example "<app>gtk-cancel</app>". You can
        find a list of all available builtins under
        <link href="http://www.pygtk.org/pygtk2reference/gtk-stock-items.html">http://www.pygtk.org/pygtk2reference/gtk-stock-items.html</link>.</p>
	</section>
	
        <section id="png-icons">
        <title>PNG icons</title>
................................................................................
        
	<p>Placeholders like %title and %url or %pls and %m3u are supported
	for all commands as well. So you can have additional play/record
	shortcuts.</p>
	</section>

        <section>





        <note style="bug"><p>The icon list isn't ordered. So you cannot
        define which button appears first.</p></note>


        </section>

</page>







|





|
|
|
>












|







 







>
>
>
>
>

|
>
>



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
..
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
<info>
	<link type="guide" xref="index#extra"/>
	<link type="guide" xref="configuration#apps"/>
	<link type="guide" xref="configuration#gui"/>
	<desc>Toolbar application short cuts.</desc>
</info>

	<title>Special extra/mini buttons</title>

	<p>Using the specbuttons plugin, you can define additional
	shortcuts. This is meant as convenience - allows to start
	other audio apps or system ontrols from within streamtuner2.</p>
	
	<p>The settings list can be found with th plugin options in the
	<key>🔌 Features</key> tab.  It follows the layout of the player/recording
	configuration.  Except that it lists icons and their according
	action commands:</p>

	<table shade="rows" rules="rows cols">
		<tr><td><p><em>Icon</em></p></td>  	<td><p><em>Cmd</em></p></td> </tr>
		<tr><td><p><app>gtk-media-forward</app></p></td>     <td><p><cmd>vlc next</cmd></p></td> </tr>
		<tr><td><p><app>terminal</app></p></td>  	<td><p><cmd>xterm</cmd></p></td> </tr>
		<tr><td><p><app>volume</app></p></td>		<td><p><cmd>pavucontrol</cmd></p></td> </tr>
		<tr><td><p><app>database</app></p></td>		<td><p><cmd>./myscript "%url" "%title"</cmd></p></td> </tr>
		<tr><td><p><app>./import.png</app></p></td>	<td><p><cmd>audacity %pls</cmd></p></td> </tr>
	</table>

        <section id="gtk-icons">
        <title>gtk-icons</title>
	<p>Note that the icon name can also be that of a Gtk-builtin icon. Their
        names always start with "<app>gtk-</app>", for example "<app>gtk-cancel</app>". You can
        find a list of all available builtins under
        <link href="http://www.pygtk.org/pygtk2reference/gtk-stock-items.html">http://www.pygtk.org/pygtk2reference/gtk-stock-items.html</link>.</p>
	</section>
	
        <section id="png-icons">
        <title>PNG icons</title>
................................................................................
        
	<p>Placeholders like %title and %url or %pls and %m3u are supported
	for all commands as well. So you can have additional play/record
	shortcuts.</p>
	</section>

        <section>
        <title>icon rows</title>
	<p>With the "number of rows" setting, more buttons can be packed together.
	It looks okay with up to 3 rows - when using the large sized main toolbar.
	The defined icon shortcuts will always be packed column-wise.</p>

        <note style="bug"><p>The icon list isn't ordered. So you cannot
        define which button appears first. (Albeit the plugin config
        definition can be changed from 'dict' to 'table' now, and the plugin
        required just a little editing then.)</p></note>
        </section>

</page>

Modified pluginconf.py from [3b562696a0] to [25ef6eb532].

479
480
481
482
483
484
485




486
487
488
489
490
491
492
493
494
495
496
        if "name" in opt and "value" in opt:
            if opt["name"] not in conf_options:
                # typemap "bool" and "int" here
                if opt["type"] in ("bool", "boolean"):
                    val = opt["value"].lower() in ("1", "true", "yes", "on")
                elif opt["type"] in ("int", "integer", "numeric"):
                    val = int(opt["value"])




                else:
                    val = str(opt["value"])
                conf_options[opt["name"]] = val

    # Initial plugin activation status
    if module and module not in conf_plugins:
        conf_plugins[module] = meta.get("priority") in (
            "core", "builtin", "always", "default", "standard"
        )









>
>
>
>











479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
        if "name" in opt and "value" in opt:
            if opt["name"] not in conf_options:
                # typemap "bool" and "int" here
                if opt["type"] in ("bool", "boolean"):
                    val = opt["value"].lower() in ("1", "true", "yes", "on")
                elif opt["type"] in ("int", "integer", "numeric"):
                    val = int(opt["value"])
                elif opt["type"] in ("array", "table", "list"):
                    val = [ opt["value"].split(",") ]
                elif opt["type"] in ("dict"):
                    val = dict(opt["value"].split(","))
                else:
                    val = str(opt["value"])
                conf_options[opt["name"]] = val

    # Initial plugin activation status
    if module and module not in conf_plugins:
        conf_plugins[module] = meta.get("priority") in (
            "core", "builtin", "always", "default", "standard"
        )


Modified uikit.py from [1647517a84] to [26d04e8c56].

508
509
510
511
512
513
514


515


516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
...
538
539
540
541
542
543
544

545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
        w.show_all()
        return w
    
    
    # Config win table (editable dictionary, two columns w/ executable indicator pixbuf)
    @staticmethod
    def config_treeview(opt, columns=["Icon", "Command"]):


        liststore = gtk.ListStore(str, str, str)


        w = gtk.TreeView(liststore)
        lno = len(columns)
        # two text columns and renderers
        for i in range(0, lno):
            c = gtk.TreeViewColumn(columns[i])
            c.set_resizable(True)
            c.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
            c.set_fixed_width(150 + 75*i)
            r = gtk.CellRendererText()
            c.pack_end(r, expand=True)
            r.set_property("editable", True)
            r.connect("edited", uikit.liststore_edit, (liststore, i))
            c.add_attribute(r, "text", i)
            #c.add_attribute(r, "editable", 2)
            w.append_column(c)
................................................................................
        return w, liststore
        
    # Generic Gtk callback to update ListStore when entries get edited.
    # where user_data = (liststore, column #id)
    @staticmethod
    def liststore_edit(cell, row, text, user_data):
        #log.EDIT(cell, row, text, user_data)

        liststore, column = user_data
        liststore[row][column] = text
        # update executable-indicator pixbuf
        if column == 1 and len(liststore) == 3 and liststore[row][2].startswith("gtk."):
            liststore[row][2] = uikit.app_bin_check(text)
        # add new row if editing last
        if row == len(liststore) -1:
            liststore.append(*["" for c in liststore[column]])

    # return OK or CANCEL depending on availability of app
    @staticmethod
    def app_bin_check(v):
        bin = re.findall(r"(?<![$(`%-;/$])\b(\w+(?:-\w+)*)", v)
        if bin:
            bin = [find_executable(bin) for bin in bin]







>
>
|
>
>

<





|







 







>



|

|
|
|







508
509
510
511
512
513
514
515
516
517
518
519
520

521
522
523
524
525
526
527
528
529
530
531
532
533
...
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
        w.show_all()
        return w
    
    
    # Config win table (editable dictionary, two columns w/ executable indicator pixbuf)
    @staticmethod
    def config_treeview(opt, columns=["Icon", "Command"]):
        lno = len(columns)
        if lno == 2:
            liststore = gtk.ListStore(str, str, str)
        else:
            liststore = gtk.ListStore(*[str for i in range(0, lno)])
        w = gtk.TreeView(liststore)

        # two text columns and renderers
        for i in range(0, lno):
            c = gtk.TreeViewColumn(columns[i])
            c.set_resizable(True)
            c.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
            c.set_fixed_width(int(430/lno))
            r = gtk.CellRendererText()
            c.pack_end(r, expand=True)
            r.set_property("editable", True)
            r.connect("edited", uikit.liststore_edit, (liststore, i))
            c.add_attribute(r, "text", i)
            #c.add_attribute(r, "editable", 2)
            w.append_column(c)
................................................................................
        return w, liststore
        
    # Generic Gtk callback to update ListStore when entries get edited.
    # where user_data = (liststore, column #id)
    @staticmethod
    def liststore_edit(cell, row, text, user_data):
        #log.EDIT(cell, row, text, user_data)
        row = int(row)
        liststore, column = user_data
        liststore[row][column] = text
        # update executable-indicator pixbuf
        if column == 1 and len(liststore[0]) == 3 and liststore[row][2].startswith("gtk-"):
            liststore[row][2] = uikit.app_bin_check(text)
        # add new row when editing last one
        if len(text) and (row + 1) == len(liststore):
            liststore.append(["", "", "gtk-new"])

    # return OK or CANCEL depending on availability of app
    @staticmethod
    def app_bin_check(v):
        bin = re.findall(r"(?<![$(`%-;/$])\b(\w+(?:-\w+)*)", v)
        if bin:
            bin = [find_executable(bin) for bin in bin]