[SCM] streamtuner2/upstream: Imported Upstream version 2.1.3
takaki at users.alioth.debian.org
takaki at users.alioth.debian.org
Wed Nov 25 07:06:53 UTC 2015
The following commit has been merged in the upstream branch:
commit a524fac4cfb8f7f5af1d410e16db8028f475e809
Author: TANIGUCHI Takaki <takaki at asis.media-as.org>
Date: Mon Aug 18 00:00:56 2014 +0900
Imported Upstream version 2.1.3
diff --git a/README b/README
index 1e155de..21edff8 100644
--- a/README
+++ b/README
@@ -51,6 +51,15 @@ Public Domain.
history
-------
+2.1.3 (2014-08-15)
+- New plugin for Dirble.com introduced.
+- Channel tabs can now be rearranged from notebook top to left side.
+- Live365 was fixed again.
+- Xiph cache service was fixed, and duplicates are now filtered out.
+- Category map storage is now handled by backend instead of channels.
+- Shorter Youtube homepage URLs are used, HTTP headers compacted.
+
+
2.1.2 (2014-07-31)
- Listing from the renewed Radionomy Shoutcast has been fixed.
- Live365 was disabled.
diff --git a/_package.epm b/_package.epm
index 8751128..4e740f2 100644
--- a/_package.epm
+++ b/_package.epm
@@ -1,5 +1,5 @@
%product streamtuner2 - internet radio browser
-%version 2.1.2
+%version 2.1.3
%vendor Mario Salzer
%license
%copyright Placed into the Public Domain, 2009-2014
@@ -47,6 +47,8 @@ f 644 root root /usr/share/streamtuner2/pq.py ./pq.py
d 755 root root /usr/share/streamtuner2/channels -
f 644 root root /usr/share/streamtuner2/channels/__init__.py ./channels/__init__.py
f 644 root root /usr/share/streamtuner2/channels/_generic.py ./channels/_generic.py
+f 644 root root /usr/share/streamtuner2/channels/dirble.py ./channels/dirble.py
+f 644 root root /usr/share/streamtuner2/channels/dirble.png ./channels/dirble.png
f 644 root root /usr/share/streamtuner2/channels/icast.py ./channels/icast.py
f 644 root root /usr/share/streamtuner2/channels/icast.png ./channels/icast.png
f 644 root root /usr/share/streamtuner2/channels/internet_radio.py ./channels/internet_radio.py
diff --git a/ahttp.py b/ahttp.py
index 6fd1516..bd9d662 100644
--- a/ahttp.py
+++ b/ahttp.py
@@ -56,7 +56,7 @@ session.headers.update({
#
# Well, it says "get", but it actually does POST and AJAXish GET requests too.
#
-def get(url, params={}, referer="", post=0, ajax=0, binary=0, feedback=None):
+def get(url, params={}, referer="", post=0, ajax=0, binary=0, feedback=None, content=True):
__print__( dbg.HTTP, "GET", url, params )
# statusbar info
@@ -78,14 +78,18 @@ def get(url, params={}, referer="", post=0, ajax=0, binary=0, feedback=None):
__print__( dbg.HTTP, r.request.headers );
__print__( dbg.HTTP, r.headers );
- # result
- #progress_feedback(0.9)
- content = (r.content if binary else r.text)
-
# finish, clean statusbar
+ #progress_feedback(0.9)
progress_feedback("")
- __print__( dbg.INFO, "Content-Length", len(content) )
- return content
+
+ # result
+ __print__( dbg.INFO, "Content-Length", len(r.content) )
+ if not content:
+ return r
+ elif binary:
+ return r.content
+ else:
+ return r.text
diff --git a/channels/_generic.py b/channels/_generic.py
index 06c0468..1335b34 100644
--- a/channels/_generic.py
+++ b/channels/_generic.py
@@ -58,6 +58,7 @@ class GenericChannel(object):
# categories
categories = ["empty", ]
+ catmap = {}
current = ""
default = "empty"
shown = None # last selected entry in stream list, also indicator if notebook tab has been selected once / stream list of current category been displayed yet
@@ -133,6 +134,10 @@ class GenericChannel(object):
cache = conf.load("cache/categories_" + self.module)
if (cache):
self.categories = cache
+ # catmap (optional)
+ cache = conf.load("cache/catmap_" + self.module)
+ if (cache):
+ self.catmap = cache
pass
@@ -376,7 +381,10 @@ class GenericChannel(object):
# get data and save
self.update_categories()
- conf.save("cache/categories_"+self.module, self.categories)
+ if self.categories:
+ conf.save("cache/categories_"+self.module, self.categories)
+ if self.catmap:
+ conf.save("cache/catmap_" + self.module, self.catmap);
# display outside of this non-main thread
mygtk.do(self.display_categories)
@@ -464,6 +472,11 @@ class GenericChannel(object):
# convert special characters to &xx; escapes
def xmlentities(self, s):
return xml.sax.saxutils.escape(s)
+
+ # Extracts integer from string
+ def to_int(self, s):
+ i = re.findall("\d+", s) or [0]
+ return int(i[0])
diff --git a/channels/dirble.png b/channels/dirble.png
new file mode 100644
index 0000000..ee6337e
Binary files /dev/null and b/channels/dirble.png differ
diff --git a/channels/dirble.py b/channels/dirble.py
new file mode 100644
index 0000000..9d01df5
--- /dev/null
+++ b/channels/dirble.py
@@ -0,0 +1,127 @@
+# encoding: UTF-8
+# api: streamtuner2
+# title: Dirble
+# description: Open radio station directory.
+# version: 0.2
+# type: channel
+# category: radio
+# priority: optional
+# documentation: http://dirble.com/developer/api
+#
+# Provides a nice JSON API, so is easy to support.
+#
+# However useful station information (homepage, etc.) only
+# with extraneous requests. So just for testing as of now.
+#
+# Uh, and API is appearently becoming for-pay (two days
+# after writing this plugin;). So ST2 users may have to
+# request their own Dirble.com key probably.
+#
+
+
+import re
+import json
+from config import conf, dbg, __print__
+from channels import *
+import ahttp as http
+
+
+# Surfmusik sharing site
+class dirble (ChannelPlugin):
+
+ # description
+ title = "Dirble"
+ module = "dirble"
+ homepage = "http://dirble.com/"
+ has_search = True
+ listformat = "audio/x-scpls"
+ titles = dict(listeners=False, playing="Location")
+
+ categories = []
+ config = [
+ {"name": "dirble_api_key",
+ "value": "",
+ "type": "text",
+ "description": "Custom API access key."
+ },
+ {"name": "dirble_fetch_homepage",
+ "value": 0,
+ "type": "boolean",
+ "description": "Also fetch homepages when updating stations. (This is slow, as it requires one extra request for each.)"
+ }
+ ]
+ catmap = {}
+
+ base = "http://api.dirble.com/v1/%s/apikey/%s/"
+ cid = "84be582567ff418c9ba94d90d075d7fee178ad60"
+
+
+ # Retrieve cat list and map
+ def update_categories(self):
+ self.categories = []
+ # Main categories
+ for row in self.api("primaryCategories"):
+ self.categories.append(row["name"])
+ self.catmap[row["name"]] = row["id"]
+ # Request subcats
+ sub = []
+ self.categories.append(sub)
+ for subrow in self.api("childCategories", "primaryid", row["id"]):
+ sub.append(subrow["name"])
+ self.catmap[subrow["name"]] = subrow["id"]
+
+
+ # Just copy over stream URLs and station titles
+ def update_streams(self, cat, search=None):
+
+ if cat:
+ id = self.catmap.get(cat, 0);
+ data = self.api("stations", "id", id)
+ elif search:
+ data = self.api("search", "search", search)
+ else:
+ pass
+
+ r = []
+ for e in data:
+ # skip musicgoal (resolve to just a blocking teaser)
+ if e["streamurl"].find("musicgoal") > 0:
+ continue
+ # append dict after renaming fields
+ r.append(dict(
+ id = e["id"],
+ genre = str(cat),
+ status = e["status"],
+ title = e["name"],
+ playing = e["country"],
+ bitrate = self.to_int(e["bitrate"]),
+ url = e["streamurl"],
+ homepage = e.get("homepage") or self.get_homepage(e["id"], e["name"]),
+ format = "audio/mpeg"
+ ))
+ return r
+
+
+ # Request homepage for stations, else try to deduce Dirble page
+ def get_homepage(self, id, name):
+ if conf.dirble_fetch_homepage:
+ try:
+ return self.api("station", "id", id)["website"]
+ except:
+ None
+ name = re.sub("[^\w\s]+", "", name)
+ name = re.sub("\s", "-", name)
+ return "http://dirble.com/station/" + name.lower();
+
+
+ # Patch API url together, send request, decode JSON and whathaveyou
+ def api(self, *params):
+ method = params[0]
+ try:
+ j = http.get((self.base % (method, conf.dirble_api_key or self.cid)) + "/".join([str(e) for e in params[1:]]))
+ r = json.loads(j);
+ except:
+ r = []
+ return r
+
+
diff --git a/channels/live365.py b/channels/live365.py
index 882441e..f2100c5 100644
--- a/channels/live365.py
+++ b/channels/live365.py
@@ -7,20 +7,18 @@
# version: 0.3
# priority: optional
#
-# 2.1.2 broken,
-# new URLs:
+#
#
-# GET /cgi-bin/mini.cgi?version=3&templateid=xml&from=web&site=web&caller=&tag=web&station_name=bofbm&_=1404610275892
-# <NANOCASTER_PARAMS> (session id)
+# We're currently extracting from the JavaScript;
+#
+# stn.set("param", "value");
+#
+# And using a HTML5 player direct URL now:
+#
+# /cgi-bin/play.pls?stationid=%s&direct=1&file=%s.pls
#
-# GET /play?now=59&membername=&session=1404610276-475426&tag=web&s=bofbm&d=LIVE365&r=0
-# &app_id=web%3ABROWSER&token=b99d7f579bacab06b9baa1502d53bedc-3101060080001248&AuthType=NORMAL
-# &lid=276006-deu&SaneID=178.24.130.71-1404610229579
#
#
-
-raise Exception
-
# streamtuner2 modules
@@ -42,6 +40,7 @@ from itertools import groupby
from time import time
from xml.dom.minidom import parseString
+
# channel live365
class live365(ChannelPlugin):
@@ -83,9 +82,12 @@ class live365(ChannelPlugin):
# extract stream infos
def update_streams(self, cat):
-
- url = "http://www.live365.com/genres/%s" % cat.lower()
- html = http.get(url, feedback=self.parent.status)
+
+ # Retrieve genere index pages
+ html = ""
+ for i in [1, 17, 33, 49]:
+ url = "http://www.live365.com/cgi-bin/directory.cgi?first=%i&site=web&mode=3&genre=%s&charset=UTF-8&target=content" % (i, cat.lower())
+ html += http.get(url, feedback=self.parent.status)
# Extract from JavaScript
rx = re.compile(r"""
@@ -94,65 +96,62 @@ class live365(ChannelPlugin):
# Group entries before adding them
ls = []
- for i,g in groupby(rx.findall(html), self.group_by_station):
- row = dict(g)
+ for i,row in groupby(rx.findall(html), self.group_by_station):
+ row = dict(row)
ls.append({
+ "status": (None if row["listenerAccess"] == "PUBLIC" else gtk.STOCK_STOP),
+ "deleted": row["status"] != "OK",
"name": row["stationName"],
"title": row["title"],
- "playing": "",
+ "playing": "n/a",
"id": row["id"],
"access": row["listenerAccess"],
"status": row["status"],
"mode": row["serverMode"],
"rating": int(row["rating"]),
- "rating": row["ratingCount"],
+ #"rating": row["ratingCount"],
"listeners": int(row["tlh"]),
"location": row["location"],
"favicon": row["imgUrl"],
"format": self.mediatype,
- "url": "urn:live365:%s:%s" % (row["id"], row["stationName"])
+ "url": "%scgi-bin/play.pls?stationid=%s&direct=1&file=%s.pls" % (self.base_url, row["id"], row["stationName"])
})
- print ls
return ls
+ # itertools.groupby filter
+ gi = 0
+ def group_by_station(self, kv):
+ if kv[0] == "stationName":
+ self.gi += 1
+ return self.gi
+
+
# inject session id etc. into direct audio url
- def play(self, row):
+ def UNUSED_play(self, row):
if row.get("url"):
# params
id = row["id"]
name = row["name"]
- # get session
- mini = "http://www.live365.com/cgi-bin/mini.cgi?version=3&templateid=xml&from=web&site=web" \
+ # get mini.cgi station resource
+ mini_url = "http://www.live365.com/cgi-bin/mini.cgi?version=3&templateid=xml&from=web&site=web" \
+ "&caller=&tag=web&station_name=%s&_=%i111" % (name, time())
- xml = parseString(http.get(mini)).getElementsByTagName("LIVE365_PLAYER_WINDOW")[0]
- x = lambda name: xml.getElementsByTagName(name)[0].childNodes[0].data
+ mini_r = http.get(mini_url, content=False)
+ mini_xml = parseString(mini_r.text).getElementsByTagName("LIVE365_PLAYER_WINDOW")[0]
+ mini = lambda name: mini_xml.getElementsByTagName(name)[0].childNodes[0].data
+
+ # authorize with play.cgi
+ play_url = ""
# mk audio url
- play = "http://www.live365.com/play?now=0&" \
- + x("NANOCASTER_PARAMS") \
- + "&token=" + x("TOKEN") \
+ play = "http://%s/play" % mini("STREAM_URL") \
+ + "?now=0&" \
+ + mini("NANOCASTER_PARAMS") \
+ + "&token=" + mini("TOKEN") \
+ "&AuthType=NORMAL&lid=276006-deu&SaneID=178.24.130.71-1406763621701"
- __print__(dbg.DATA, play)
# let's see what happens
action.action.play(play, self.mediatype, self.listformat)
-
-
-
- # itertools.groupby filter
- gi = 0
- def group_by_station(self, kv):
- if kv[0] == "stationName":
- self.gi += 1
- return self.gi
-
- # we can no longer cache all the things
- def cache(self):
- pass
- def save(self):
- pass
-
diff --git a/channels/shoutcast.py b/channels/shoutcast.py
index e8adb26..67e6c57 100644
--- a/channels/shoutcast.py
+++ b/channels/shoutcast.py
@@ -5,7 +5,7 @@
# type: channel
# category: radio
# priority: default
-# version: 1.4
+# version: 1.5
# depends: pq, re, http
# author: Mario
# original: Jean-Yves Lefort
@@ -23,7 +23,7 @@
# Therefore we'll be retrieving stuff from the homepage still. The new
# interface conveniently uses JSON already, so let's use that:
#
-# POST http://www.shoutcast.com/Home/BrowseByGenre {genreid: 9}
+# POST http://www.shoutcast.com/Home/BrowseByGenre {genrename: Pop}
#
# We do need a catmap now too, but that's easy to aquire and will be kept
# within the cache dirs.
@@ -74,27 +74,22 @@ class shoutcast(channels.ChannelPlugin):
html = http.get(self.base_url)
#__print__( dbg.DATA, html )
self.categories = []
-
- # Main genres from mobile dropdown
- """ <option value="1">Alternative</option> """
- rx = re.compile(r'<option value="(\d+)">(\w+\h*\w+)<')
- main = rx.findall(html)
+
# Genre list in sidebar
- """ <li><a href="#c-genre-2" onclick="loadStationsByGenre(2, true)">Adult Alternative</a></li> """
- rx = re.compile(r'c-genre-(\d+).+?>(\w[\w\h]*\w)<')
+ """ <li><a id="genre-90" href="/Genre?name=Adult" onclick="loadStationsByGenre('Adult', 90, 89); return false;">Adult</a></li> """
+ rx = re.compile(r"loadStationsByGenre\( '([^']+)' [,\s]* (\d+) [,\s]* (\d+) \)", re.X)
subs = rx.findall(html)
- print main
- print subs
# group
- for (id, title) in subs:
- if (id,title) in main:
+ current = []
+ for (title, id, main) in subs:
+ self.catmap[title] = int(id)
+ if not int(main):
self.categories.append(title)
current = []
self.categories.append(current)
else:
current.append(title)
- self.catmap[title] = int(id)
self.save()
@@ -108,7 +103,7 @@ class shoutcast(channels.ChannelPlugin):
# page
url = "http://www.shoutcast.com/Home/BrowseByGenre"
- params = { "genreid": int(id) }
+ params = { "genrename": cat }
referer = None
json = http.get(url, params=params, referer=referer, post=1, ajax=1)
self.parent.status(0.75)
diff --git a/channels/xiph.py b/channels/xiph.py
index e34cc67..9ef9784 100644
--- a/channels/xiph.py
+++ b/channels/xiph.py
@@ -107,17 +107,18 @@ class xiph (ChannelPlugin):
#__print__(dbg.DATA, e)
bitrate = int(e["bitrate"])
if conf.xiph_min_bitrate and bitrate and bitrate >= int(conf.xiph_min_bitrate):
- l.append({
- "title": e["stream_name"],
- "url": e["listen_url"],
- "format": e["type"],
- "bitrate": int(e["bitrate"]),
- "genre": e["genre"],
- "playing": e["current_song"],
- "listeners": 0,
- "max": 0,
- "homepage": (e["homepage"] if ("homepage" in e) else ""),
- })
+ if not len(l) or l[-1]["title"] != e["stream_name"]:
+ l.append({
+ "title": e["stream_name"],
+ "url": e["listen_url"],
+ "format": e["type"],
+ "bitrate": bitrate,
+ "genre": e["genre"],
+ "playing": e["current_song"],
+ "listeners": 0,
+ "max": 0,
+ "homepage": (e["homepage"] if ("homepage" in e) else ""),
+ })
# send back the list
return l
diff --git a/config.py b/config.py
index 6bdf9e7..b5875ee 100644
--- a/config.py
+++ b/config.py
@@ -214,15 +214,15 @@ def __print__(*args):
# error colorization
dbg = type('obj', (object,), {
- "ERR": "[31m[ERR][0m", # red ERROR
- "INIT": "[31m[INIT][0m", # red INIT ERROR
- "PROC": "[32m[PROC][0m", # green PROCESS
- "CONF": "[33m[CONF][0m", # brown CONFIG DATA
- "UI": "[34m[UI][0m", # blue USER INTERFACE BEHAVIOUR
- "HTTP": "[35m[HTTP][0m", # magenta HTTP REQUEST
- "DATA": "[36m[DATA][0m", # cyan DATA
- "INFO": "[37m[INFO][0m", # gray INFO
- "STAT": "[37m[STATE][0m", # gray CONFIG STATE
+ "ERR": r"[31m[ERR][0m", # red ERROR
+ "INIT": r"[31m[INIT][0m", # red INIT ERROR
+ "PROC": r"[32m[PROC][0m", # green PROCESS
+ "CONF": r"[33m[CONF][0m", # brown CONFIG DATA
+ "UI": r"[34m[UI][0m", # blue USER INTERFACE BEHAVIOUR
+ "HTTP": r"[35m[HTTP][0m", # magenta HTTP REQUEST
+ "DATA": r"[36m[DATA][0m", # cyan DATA
+ "INFO": r"[37m[INFO][0m", # gray INFO
+ "STAT": r"[37m[STATE][0m", # gray CONFIG STATE
})
diff --git a/g.py b/g.py
new file mode 100644
index 0000000..b679756
--- /dev/null
+++ b/g.py
@@ -0,0 +1,15 @@
+from itertools import groupby
+
+ls = [(0,7), (1,7), (0,5), (1,5), (2,5), (0,9), (9,9)]
+
+i = 0
+def grp(kv):
+ global i
+ if kv[0] == 0:
+ i += 1
+ return i
+
+
+for i,row in groupby(ls, grp):
+ print list(row)
+
diff --git a/gtk2.xml b/gtk2.xml
index 56c18e0..a2518f4 100644
--- a/gtk2.xml
+++ b/gtk2.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
- <requires lib="gtk+" version="2.20"/>
+ <!-- interface-requires gtk+ 3.0 -->
<!-- interface-naming-policy toplevel-contextual -->
<object class="GtkListStore" id="config_play">
<columns>
@@ -47,13 +47,10 @@
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="scrollable">True</property>
- <property name="homogeneous">True</property>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow2">
<property name="visible">True</property>
<property name="can_focus">True</property>
- <property name="hscrollbar_policy">automatic</property>
- <property name="vscrollbar_policy">automatic</property>
<child>
<object class="GtkViewport" id="viewport2">
<property name="visible">True</property>
@@ -82,8 +79,6 @@
<object class="GtkScrolledWindow" id="scrolledwindow4">
<property name="visible">True</property>
<property name="can_focus">True</property>
- <property name="hscrollbar_policy">automatic</property>
- <property name="vscrollbar_policy">automatic</property>
<child>
<object class="GtkTreeView" id="tv_config_player">
<property name="width_request">540</property>
@@ -264,8 +259,6 @@ and <b>%srv</b> to use direct streaming URLs.</property>
<object class="GtkScrolledWindow" id="scrolledwindow3">
<property name="visible">True</property>
<property name="can_focus">True</property>
- <property name="hscrollbar_policy">automatic</property>
- <property name="vscrollbar_policy">automatic</property>
<child>
<object class="GtkViewport" id="viewport3">
<property name="visible">True</property>
@@ -463,7 +456,6 @@ the website extraction method can fix some station list update errors.</small
<property name="width_chars">5</property>
<property name="text" translatable="yes">500</property>
<property name="shadow_type">out</property>
- <property name="invisible_char_set">True</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
@@ -616,7 +608,6 @@ the website extraction method can fix some station list update errors.</small
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">●</property>
- <property name="invisible_char_set">True</property>
<property name="primary_icon_stock">gtk-save-as</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
@@ -686,7 +677,6 @@ the website extraction method can fix some station list update errors.</small
<property name="editable">False</property>
<property name="invisible_char">●</property>
<property name="width_chars">20</property>
- <property name="invisible_char_set">True</property>
<property name="primary_icon_stock">gtk-home</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
@@ -804,7 +794,6 @@ or internal functions. Changes take effect after restarting streamtuner2.</prope
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">●</property>
- <property name="invisible_char_set">True</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
@@ -1093,230 +1082,15 @@ Plugins</property>
<signal name="delete-event" handler="search_cancel" swapped="no"/>
<child internal-child="vbox">
<object class="GtkVBox" id="dialog-vbox1">
- <property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="spacing">2</property>
<child internal-child="action_area">
<object class="GtkHButtonBox" id="dialog-action_area1">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="layout_style">end</property>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">False</property>
- <property name="pack_type">end</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkVBox" id="vbox1">
- <property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="spacing">20</property>
- <child>
- <object class="GtkLabel" id="label2">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="label" translatable="yes"><b><big>search</big></b></property>
- <property name="use_markup">True</property>
- </object>
- <packing>
- <property name="expand">True</property>
- <property name="fill">True</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <object class="GtkHBox" id="hbox1">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <child>
- <object class="GtkLabel" id="label1sd">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="ypad">10</property>
- <property name="label" translatable="yes">for</property>
- </object>
- <packing>
- <property name="expand">True</property>
- <property name="fill">True</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkEntry" id="search_full">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="has_focus">True</property>
- <property name="is_focus">True</property>
- <property name="tooltip_text" translatable="yes">A single word to search for in all stations.</property>
- <property name="invisible_char">●</property>
- <property name="activates_default">True</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
- <property name="primary_icon_sensitive">True</property>
- <property name="secondary_icon_sensitive">True</property>
- </object>
- <packing>
- <property name="expand">True</property>
- <property name="fill">True</property>
- <property name="position">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkLabel" id="label4sd">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- </object>
- <packing>
- <property name="expand">True</property>
- <property name="fill">True</property>
- <property name="position">2</property>
- </packing>
- </child>
- </object>
- <packing>
- <property name="expand">True</property>
- <property name="fill">True</property>
- <property name="position">2</property>
- </packing>
- </child>
- <child>
- <object class="GtkHBox" id="hbox2">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <child>
- <object class="GtkLabel" id="label3sd">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="xpad">10</property>
- <property name="ypad">10</property>
- <property name="label" translatable="yes">in</property>
- </object>
- <packing>
- <property name="expand">True</property>
- <property name="fill">True</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkRadioButton" id="search_dialog_all">
- <property name="label" translatable="yes">all channels</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">False</property>
- <property name="active">True</property>
- <property name="draw_indicator">True</property>
- <property name="group">search_dialog_current</property>
- </object>
- <packing>
- <property name="expand">True</property>
- <property name="fill">True</property>
- <property name="position">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkRadioButton" id="search_dialog_current">
- <property name="label" translatable="yes">just current</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">False</property>
- <property name="active">True</property>
- <property name="draw_indicator">True</property>
- <property name="group">search_dialog_all</property>
- </object>
- <packing>
- <property name="expand">True</property>
- <property name="fill">True</property>
- <property name="position">2</property>
- </packing>
- </child>
- </object>
- <packing>
- <property name="expand">True</property>
- <property name="fill">True</property>
- <property name="position">3</property>
- </packing>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <object class="GtkHBox" id="hbox3">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="spacing">20</property>
- <child>
- <object class="GtkButton" id="cache_search">
- <property name="label" translatable="yes">Cache _find</property>
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="receives_default">False</property>
- <property name="tooltip_text" translatable="yes">Start searching for above search term in the currently loaded station lists. Doesn't find *new* information, just looks through the known data.</property>
- <property name="image">image1</property>
- <property name="relief">half</property>
- <property name="use_underline">True</property>
- <accelerator key="Return" signal="activate"/>
- <signal name="clicked" handler="search_go" swapped="no"/>
- </object>
- <packing>
- <property name="expand">True</property>
- <property name="fill">True</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkButton" id="server_search">
- <property name="label" translatable="yes">Server _search</property>
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="has_focus">True</property>
- <property name="is_focus">True</property>
- <property name="can_default">True</property>
- <property name="has_default">True</property>
- <property name="receives_default">True</property>
- <property name="tooltip_text" translatable="yes">Instead of doing a cache search, go through the search functions on the directory service homepages. (UNIMPLEMENTED)</property>
- <property name="image">image2</property>
- <property name="use_underline">True</property>
- <signal name="clicked" handler="search_srv" swapped="no"/>
- </object>
- <packing>
- <property name="expand">True</property>
- <property name="fill">True</property>
- <property name="position">1</property>
- </packing>
- </child>
- </object>
- <packing>
- <property name="expand">True</property>
- <property name="fill">True</property>
- <property name="position">5</property>
- </packing>
- </child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
- <property name="position">1</property>
+ <property name="position">0</property>
</packing>
</child>
</object>
@@ -1408,639 +1182,40 @@ Plugins</property>
<signal name="delete-event" handler="timer_cancel" swapped="no"/>
<child internal-child="vbox">
<object class="GtkVBox" id="dialog-vbox2">
- <property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="spacing">2</property>
<child internal-child="action_area">
<object class="GtkHButtonBox" id="dialog-action_area2">
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <object class="GtkWindow" id="url_action_container">
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkVBox" id="url_action_list">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="url_action_1_online_forum">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="layout_style">end</property>
- <child>
- <object class="GtkButton" id="timer_cancel">
- <property name="label" translatable="yes">cancel</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <signal name="clicked" handler="timer_cancel" swapped="no"/>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">False</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkButton" id="timer_ok">
- <property name="label" translatable="yes">ok</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <signal name="clicked" handler="timer_ok" swapped="no"/>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">False</property>
- <property name="position">1</property>
- </packing>
- </child>
+ <property name="label" translatable="yes">http://sourceforge.net/projects/streamtuner2/forums/forum/1173108</property>
</object>
<packing>
- <property name="expand">False</property>
+ <property name="expand">True</property>
<property name="fill">True</property>
- <property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
<child>
- <object class="GtkTable" id="table2">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="n_rows">3</property>
- <property name="n_columns">3</property>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <object class="GtkEntry" id="timer_value">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="invisible_char">●</property>
- <property name="text" translatable="yes">Fri,Sat 20:00-21:00</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
- <property name="primary_icon_sensitive">True</property>
- <property name="secondary_icon_sensitive">True</property>
- </object>
- <packing>
- <property name="left_attach">1</property>
- <property name="right_attach">2</property>
- <property name="top_attach">1</property>
- <property name="bottom_attach">2</property>
- </packing>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- </object>
- <packing>
- <property name="expand">True</property>
- <property name="fill">True</property>
- <property name="position">1</property>
- </packing>
- </child>
- </object>
- </child>
- <action-widgets>
- <action-widget response="0">timer_cancel</action-widget>
- <action-widget response="0">timer_ok</action-widget>
- </action-widgets>
- </object>
- <object class="GtkWindow" id="url_action_container">
- <property name="can_focus">False</property>
- <child>
- <object class="GtkVBox" id="url_action_list">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <child>
- <object class="GtkLabel" id="url_action_1_online_forum">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="label" translatable="yes">http://sourceforge.net/projects/streamtuner2/forums/forum/1173108</property>
- </object>
- <packing>
- <property name="expand">True</property>
- <property name="fill">True</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkLabel" id="url_action_2_fossil_wiki">
+ <object class="GtkLabel" id="url_action_2_fossil_wiki">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">http://fossil.include-once.org/streamtuner2/</property>
@@ -2656,6 +1831,56 @@ Plugins</property>
</object>
</child>
<child>
+ <object class="GtkMenuItem" id="menu_entry_switch_channel_tabs">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Channel tab position</property>
+ <property name="use_underline">True</property>
+ <child type="submenu">
+ <object class="GtkMenu" id="menu4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkMenuItem" id="menu_entry_switch_channel_tabs1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Top</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="menu_notebook_pos_top" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menu_entry_switch_channel_tabs2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Left</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="menu_notebook_pos_left" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menu_entry_switch_channel_tabs3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Bottom</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="menu_notebook_pos_bottom" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menu_entry_switch_channel_tabs4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Right</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="menu_notebook_pos_right" swapped="no"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
<object class="GtkMenuItem" id="menuitem15savestates">
<property name="visible">True</property>
<property name="can_focus">False</property>
diff --git a/gtk3.xml b/gtk3.xml
index a55dca2..6c15bc2 100644
--- a/gtk3.xml
+++ b/gtk3.xml
@@ -1898,6 +1898,42 @@ Plugins</property>
<placeholder/>
</child>
<child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
<object class="GtkEntry" id="timer_value">
<property name="visible">True</property>
<property name="can_focus">True</property>
@@ -2015,6 +2051,12 @@ Plugins</property>
<child>
<placeholder/>
</child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
</object>
<packing>
<property name="expand">True</property>
@@ -2643,6 +2685,56 @@ Plugins</property>
</object>
</child>
<child>
+ <object class="GtkMenuItem" id="menu_entry_switch_channel_tabs">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Channel tab position</property>
+ <property name="use_underline">True</property>
+ <child type="submenu">
+ <object class="GtkMenu" id="menu4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkMenuItem" id="menu_entry_switch_channel_tabs1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Top</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="menu_notebook_pos_top" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menu_entry_switch_channel_tabs2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Left</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="menu_notebook_pos_left" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menu_entry_switch_channel_tabs3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Bottom</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="menu_notebook_pos_bottom" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menu_entry_switch_channel_tabs4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Right</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="menu_notebook_pos_right" swapped="no"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
<object class="GtkMenuItem" id="menuitem15savestates">
<property name="visible">True</property>
<property name="can_focus">False</property>
diff --git a/mygtk.py b/mygtk.py
index c3d5f69..d127abe 100644
--- a/mygtk.py
+++ b/mygtk.py
@@ -301,9 +301,12 @@ class mygtk:
# gtk.Notebook
if t == gtk.Notebook:
r[wn]["page"] = w.get_current_page()
+ r[wn]["tab_pos"] = int(w.get_tab_pos())
#print(r)
return r
+ gtk_position_type_enum = [gtk.POS_LEFT, gtk.POS_RIGHT, gtk.POS_TOP, gtk.POS_BOTTOM]
+
#-- restore window and widget properties
#
@@ -341,6 +344,8 @@ class mygtk:
# gtk.Notebook
if method == "page":
w.set_current_page(args)
+ if method == "tab_pos":
+ w.set_tab_pos(r[wn]["tab_pos"])
pass
diff --git a/st2.py b/st2.py
index 79d8cec..5ec8fb8 100755
--- a/st2.py
+++ b/st2.py
@@ -5,10 +5,10 @@
# title: streamtuner2
# description: Directory browser for internet radio / audio streams
# depends: pygtk | pygi, threading, pyquery, kronos, requests
-# version: 2.1.2
+# version: 2.1.3
# author: mario salzer
# license: public domain
-# url: http://freshmeat.net/projects/streamtuner2
+# url: http://freshcode.club/projects/streamtuner2
# config: <env name="http_proxy" value="" description="proxy for HTTP access" /> <env name="XDG_CONFIG_HOME" description="relocates user .config subdirectory" />
# category: multimedia
#
@@ -96,7 +96,7 @@ from channels import *
import favicon
-__version__ = "2.1.2"
+__version__ = "2.1.3"
# this represents the main window
@@ -193,6 +193,10 @@ class StreamTunerTwo(gtk.Builder):
"menu_toolbar_size_small": lambda w: (self.toolbar.set_icon_size(gtk.ICON_SIZE_SMALL_TOOLBAR)),
"menu_toolbar_size_medium": lambda w: (self.toolbar.set_icon_size(gtk.ICON_SIZE_DND)),
"menu_toolbar_size_large": lambda w: (self.toolbar.set_icon_size(gtk.ICON_SIZE_DIALOG)),
+ "menu_notebook_pos_top": lambda w: self.notebook_channels.set_tab_pos(2),
+ "menu_notebook_pos_left": lambda w: self.notebook_channels.set_tab_pos(0),
+ "menu_notebook_pos_right": lambda w: self.notebook_channels.set_tab_pos(1),
+ "menu_notebook_pos_bottom": lambda w: self.notebook_channels.set_tab_pos(3),
# win_config
"menu_properties": config_dialog.open,
"config_cancel": config_dialog.hide,
@@ -234,8 +238,6 @@ class StreamTunerTwo(gtk.Builder):
self.win_streamtuner2.show()
-
-
#-- Shortcut for glade.get_widget()
# Allows access to widgets as direct attributes instead of using .get_widget()
# Also looks in self.channels[] for the named channel plugins
@@ -245,29 +247,24 @@ class StreamTunerTwo(gtk.Builder):
else:
return self.get_object(name) # or gives an error if neither exists
-
- # custom-named widgets are available from .widgets{} not via .get_widget()
+ # Custom-named widgets are available from .widgets{} not via .get_widget()
def get_widget(self, name):
if name in self.widgets:
return self.widgets[name]
else:
return gtk.Builder.get_object(self, name)
-
-
-
- # returns the currently selected directory/channel object
+ # returns the currently selected directory/channel object (remembered position)
def channel(self):
return self.channels[self.current_channel]
-
+ # returns the currently selected directory/channel object (from gtk)
def current_channel_gtk(self):
i = self.notebook_channels.get_current_page()
try: return self.channel_names[i]
except: return "bookmarks"
-
- # notebook tab clicked
+ # Notebook tab clicked
def channel_switch(self, notebook, page, page_num=0, *args):
# can be called from channelmenu as well:
@@ -285,14 +282,12 @@ class StreamTunerTwo(gtk.Builder):
except:
__print__(dbg.INIT, "channel .first_show() initialization error")
-
- # convert ListStore iter to row number
+ # Convert ListStore iter to row number
def rowno(self):
(model, iter) = self.model_iter()
return model.get_path(iter)[0]
-
- # currently selected entry in stations list, return complete data dict
+ # Currently selected entry in stations list, return complete data dict
def row(self):
return self.channel().stations() [self.rowno()]
@@ -300,44 +295,37 @@ class StreamTunerTwo(gtk.Builder):
# return ListStore object and Iterator for currently selected row in gtk.TreeView station list
def model_iter(self):
return self.channel().gtk_list.get_selection().get_selected()
-
- # fetches a single varname from currently selected station entry
+ # Fetches a single varname from currently selected station entry
def selected(self, name="url"):
return self.row().get(name)
-
-
-
- # play button
+
+ # Play button
def on_play_clicked(self, widget, event=None, *args):
row = self.row()
if row:
self.channel().play(row)
[callback(row) for callback in self.hooks["play"]]
-
- # streamripper
+ # Recording: invoke streamripper for current stream URL
def on_record_clicked(self, widget):
row = self.row()
action.record(row.get("url"), row.get("format", "audio/mpeg"), "url/direct", row=row)
-
- # browse stream
+ # Open stream homepage in web browser
def on_homepage_stream_clicked(self, widget):
url = self.selected("homepage")
action.browser(url)
-
- # browse channel
+ # Browse to channel homepage (double click on notebook tab)
def on_homepage_channel_clicked(self, widget, event=2):
if event == 2 or event.type == gtk.gdk._2BUTTON_PRESS:
__print__(dbg.UI, "dblclick")
action.browser(self.channel().homepage)
-
- # reload stream list in current channel-category
+ # Reload stream list in current channel-category
def on_reload_clicked(self, widget=None, reload=1):
__print__(dbg.UI, "reload", reload, self.current_channel, self.channels[self.current_channel], self.channel().current)
category = self.channel().current
@@ -345,30 +333,26 @@ class StreamTunerTwo(gtk.Builder):
lambda: ( self.channel().load(category,reload), reload and self.bookmarks.heuristic_update(self.current_channel,category) )
)
-
- # thread a function, add to worker pool (for utilizing stop button)
+ # Thread a function, add to worker pool (for utilizing stop button)
def thread(self, target, *args):
thread = Thread(target=target, args=args)
thread.start()
self.working.append(thread)
-
- # stop reload/update threads
+ # Stop reload/update threads
def on_stop_clicked(self, widget):
while self.working:
thread = self.working.pop()
thread.stop()
-
- # click in category list
+ # Click in category list
def on_category_clicked(self, widget, event, *more):
category = self.channel().currentcat()
__print__(dbg.UI, "on_category_clicked", category, self.current_channel)
self.on_reload_clicked(None, reload=0)
pass
-
- # add current selection to bookmark store
+ # Add current selection to bookmark store
def bookmark(self, widget):
self.bookmarks.add(self.row())
# code to update current list (set icon just in on-screen liststore, it would be updated with next display() anyhow - and there's no need to invalidate the ls cache, because that's referenced by model anyhow)
@@ -380,19 +364,16 @@ class StreamTunerTwo(gtk.Builder):
# refresh bookmarks tab
self.bookmarks.load(self.bookmarks.default)
-
- # reload category tree
+ # Reload category tree
def update_categories(self, widget):
Thread(target=self.channel().reload_categories).start()
-
- # menu invocation: refresh favicons for all stations in current streams category
+ # Menu invocation: refresh favicons for all stations in current streams category
def update_favicons(self, widget):
entries = self.channel().stations()
favicon.download_all(entries)
-
- # save a file
+ # Save stream to file (.m3u)
def save_as(self, widget):
row = self.row()
default_fn = row["title"] + ".m3u"
@@ -401,23 +382,24 @@ class StreamTunerTwo(gtk.Builder):
action.save(row, fn)
pass
-
- # save current stream URL into clipboard
+ # Save current stream URL into clipboard
def menu_copy(self, w):
gtk.clipboard_get().set_text(self.selected("url"))
-
- # remove an entry
+ # Remove a stream entry
def delete_entry(self, w):
n = self.rowno()
del self.channel().stations()[ n ]
self.channel().switch()
self.channel().save()
-
- # stream right click
+ # Richt clicking a stream opens an action content menu
def station_context_menu(self, treeview, event):
return station_context_menu(treeview, event) # wrapper to the static function
+
+ # Alternative Notebook channel tabs between TOP and LEFT position
+ def switch_notebook_tabs_position(self, w, pos):
+ self.notebook_channels.set_tab_pos(pos);
@@ -1190,6 +1172,7 @@ if __name__ == "__main__":
# run
gui_startup(100/100.0)
gtk.main()
+ __print__(dbg.PROC, r"[31m gtk_main_quit [0m")
# invoke command-line interface
--
streamtuner2 packaging
More information about the pkg-multimedia-commits
mailing list