Check-in [b973f0e385]
Overview
| Comment: | Slim down initialization (wrapper script for /usr/bin and pyzip will be used). Move module coupling into ST2 window constructor. |
|---|---|
| Downloads: | Tarball | ZIP archive | SQL archive |
| Timelines: | family | ancestors | descendants | both | trunk |
| Files: | files | file ages | folders |
| SHA1: |
b973f0e3851cf87583589c00d650e5ed |
| User & Date: | mario on 2015-04-01 11:16:39 |
| Other Links: | manifest | tags |
Context
|
2015-04-01
| ||
| 11:17 | Tried SVG for logo, but Gtk refuses to play along; given up. Set progressbar to no-show-all. check-in: ef90440dbf user: mario tags: trunk | |
| 11:16 | Slim down initialization (wrapper script for /usr/bin and pyzip will be used). Move module coupling into ST2 window constructor. check-in: b973f0e385 user: mario tags: trunk | |
| 11:15 | Remove gtk/visibility setting in favour of show_all(). Fix pixbuf creation, b64decode ignoring non-base64 data. check-in: 1786e24701 user: mario tags: trunk | |
Changes
Modified st2.py from [df1a1ff19b] to [15304419e8].
| ︙ | ︙ | |||
12 13 14 15 16 17 18 |
# url: http://freshcode.club/projects/streamtuner2
# config:
# { type: env, name: http_proxy, description: proxy for HTTP access }
# { type: env, name: XDG_CONFIG_HOME, description: relocates user .config subdirectory }
# category: sound
# depends: pygtk | gi, threading, requests, pyquery, lxml
# id: streamtuner2
| | < < | > | | > > < | | < > | 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 |
# url: http://freshcode.club/projects/streamtuner2
# config:
# { type: env, name: http_proxy, description: proxy for HTTP access }
# { type: env, name: XDG_CONFIG_HOME, description: relocates user .config subdirectory }
# category: sound
# depends: pygtk | gi, threading, requests, pyquery, lxml
# id: streamtuner2
# pack: *.py, gtk*.xml, bin=/usr/bin/streamtuner2, channels/__init__.py, bundle/*.py,
# streamtuner2.desktop=/usr/share/applications/, README=/usr/share/doc/streamtuner2/,
# NEWS.gz=/usr/share/doc/streamtuner2/changelog.gz, help/streamtuner2.1=/usr/share/man/man1/,
# help/*page=/usr/share/doc/streamtuner2/help/, help/img/*=/usr/share/doc/streamtuner2/help/img/,
# logo.png=/usr/share/pixmaps/streamtuner2.png,
# architecture: all
#
# Streamtuner2 is a GUI for browsing internet radio directories, music
# collections, and video services - grouped by genres or categories.
# It runs your preferred audio player, and streamripper for recording.
#
# It's an independent rewrite of streamtuner1. Being written in Python,
# can be more easily extended and fixed. The mix of JSON APIs, regex
# or PyQuery extraction makes list generation simpler and more robust.
#
# Primarily radio stations are displayed, some channels however are music
# collections. Commercial and sign-up services are not an objective.
# standard modules
import sys
import os
import re
from copy import copy
import inspect
import traceback
from threading import Thread
# add library path (either global setup, or pyzip basename)
if not os.path.dirname(__file__) in sys.path:
sys.path.insert(0, os.path.dirname(__file__))
# initializes itself, so all conf.vars are available right away
from config import *
# gtk modules
from uikit import pygtk, gtk, gobject, uikit, ui_xml, gui_startup, AboutStreamtuner2
# custom modules
import ahttp
import action
import logo
import favicon
import channels
import channels.bookmarks
import channels.configwin
import channels.streamedit
import channels.search
# This represents the main window, dispatches Gtk events,
# and shares most application behaviour with the channel modules.
class StreamTunerTwo(gtk.Builder):
# object containers
widgets = {} # non-glade widgets (the manually instantiated ones)
channels = {} # channel modules
features = {} # non-channel plugins
working = [] # threads
add_signals = {} # channel gtk-handler signals
hooks = {
"play": [favicon.download_playing], # observers queue here
"init": [],
"config_load": [],
"config_save": [],
}
meta = plugin_meta()
# status variables
channel_names = ["bookmarks"] # order of channel notebook tabs
current_channel = "bookmarks" # currently selected channel name (as index in self.channels{})
# constructor
|
| ︙ | ︙ | |||
108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
# dialogs that are connected to main
self.features = {
"search": channels.search.search(self),
"configwin": channels.configwin.configwin(self),
"streamedit": channels.streamedit.streamedit(self),
}
gui_startup(4/20.0)
# append other channel modules and plugins
self.load_plugin_channels()
# load application state (widget sizes, selections, etc.)
try:
winlayout = conf.load("window")
| > > > > > > | 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
# dialogs that are connected to main
self.features = {
"search": channels.search.search(self),
"configwin": channels.configwin.configwin(self),
"streamedit": channels.streamedit.streamedit(self),
}
gui_startup(4/20.0)
# early module coupling
action.main = self # action (play/record) module needs a reference to main window for gtk interaction and some URL/URI callbacks
self.action = action.action # shorter name (could also become a features. entry...)
ahttp.feedback = self.status # http module gives status feedbacks too
# append other channel modules and plugins
self.load_plugin_channels()
# load application state (widget sizes, selections, etc.)
try:
winlayout = conf.load("window")
|
| ︙ | ︙ | |||
180 181 182 183 184 185 186 |
# else
"update_categories": self.update_categories,
"update_favicons": self.update_favicons,
"app_state": self.app_state,
"bookmark": self.bookmark,
"save_as": self.save_as,
"menu_about": lambda w: AboutStreamtuner2(self),
| | | | | | | 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 |
# else
"update_categories": self.update_categories,
"update_favicons": self.update_favicons,
"app_state": self.app_state,
"bookmark": self.bookmark,
"save_as": self.save_as,
"menu_about": lambda w: AboutStreamtuner2(self),
"menu_help": self.action.help,
"menu_onlineforum": lambda w: self.action.browser("http://sourceforge.net/projects/streamtuner2/forums/forum/1173108"),
"menu_fossilwiki": lambda w: self.action.browser("http://fossil.include-once.org/streamtuner2/"),
"menu_projhomepage": lambda w: self.action.browser("http://milki.include-once.org/streamtuner2/"),
# "menu_bugreport": lambda w: BugReport(),
"menu_copy": self.menu_copy,
"delete_entry": self.delete_entry,
# search dialog
"quicksearch_set": self.search.quicksearch_set,
"search_open": self.search.menu_search,
"search_go": self.search.cache_search,
"search_srv": self.search.server_search,
"search_cancel": self.search.cancel,
"true": lambda w,*args: True,
# win_streamedit
"streamedit_open": self.streamedit.open,
"streamedit_save": self.streamedit.save,
"streamedit_new": self.streamedit.new,
"streamedit_cancel": self.streamedit.cancel,
}, **self.add_signals))
# actually display main window
self.win_streamtuner2.show_all()
gui_startup(1.0)
#-- 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
def __getattr__(self, name):
|
| ︙ | ︙ | |||
282 283 284 285 286 287 288 |
if row:
self.channel().play(row)
[callback(row) for callback in self.hooks["play"]]
# Recording: invoke streamripper for current stream URL
def on_record_clicked(self, widget):
row = self.row()
| | | | | 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 |
if row:
self.channel().play(row)
[callback(row) for callback in self.hooks["play"]]
# Recording: invoke streamripper for current stream URL
def on_record_clicked(self, widget):
row = self.row()
self.action.record(row.get("url"), row.get("format", "audio/mpeg"), "url/direct", row=row)
# Open stream homepage in web browser
def on_homepage_stream_clicked(self, widget):
url = self.selected("homepage")
self.action.browser(url)
# 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")
self.action.browser(self.channel().homepage)
# 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
self.thread(
lambda: ( self.channel().load(category,reload), reload and self.bookmarks.heuristic_update(self.current_channel,category) )
|
| ︙ | ︙ | |||
350 351 352 353 354 355 356 |
# Save stream to file (.m3u)
def save_as(self, widget):
row = self.row()
default_fn = row["title"] + ".m3u"
fn = uikit.save_file("Save Stream", None, default_fn, [(".m3u","*m3u"),(".pls","*pls"),(".xspf","*xspf"),(".smil","*smil"),(".asx","*asx"),("all files","*")])
if fn:
| | | 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 |
# Save stream to file (.m3u)
def save_as(self, widget):
row = self.row()
default_fn = row["title"] + ".m3u"
fn = uikit.save_file("Save Stream", None, default_fn, [(".m3u","*m3u"),(".pls","*pls"),(".xspf","*xspf"),(".smil","*smil"),(".asx","*asx"),("all files","*")])
if fn:
self.action.save(row, fn)
pass
# Save current stream URL into clipboard
def menu_copy(self, w):
gtk.clipboard_get().set_text(self.selected("url"))
# Remove a stream entry
|
| ︙ | ︙ | |||
386 387 388 389 390 391 392 |
sbar_msg.pop()
uikit.do(lambda:self.statusbar.pop(sbar_cid))
# progressbar
if (type(text)==float):
if (text >= 999.0/1000): # completed
uikit.do(lambda:self.progress.hide())
else: # show percentage
| | | 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 |
sbar_msg.pop()
uikit.do(lambda:self.statusbar.pop(sbar_cid))
# progressbar
if (type(text)==float):
if (text >= 999.0/1000): # completed
uikit.do(lambda:self.progress.hide())
else: # show percentage
uikit.do(lambda:self.progress.show_all() or self.progress.set_fraction(text))
if (text <= 0): # unknown state
uikit.do(lambda:self.progress.pulse())
# add text
elif (type(text)==str):
sbar_msg.append(1)
uikit.do(lambda:self.statusbar.push(sbar_cid, text))
pass
|
| ︙ | ︙ | |||
470 471 472 473 474 475 476 |
# Right clicking a stream/station in the treeview to make context menu pop out.
def station_context_menu(self, treeview, event):
if event.button >= 3:
path = treeview.get_path_at_pos(int(event.x), int(event.y))[0]
treeview.grab_focus()
treeview.set_cursor(path, None, False)
| | | | < < < < < < > > > | 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 |
# Right clicking a stream/station in the treeview to make context menu pop out.
def station_context_menu(self, treeview, event):
if event.button >= 3:
path = treeview.get_path_at_pos(int(event.x), int(event.y))[0]
treeview.grab_focus()
treeview.set_cursor(path, None, False)
self.streamactions.popup(
parent_menu_shell=None, parent_menu_item=None, func=None,
button=event.button, activate_time=event.time,
data=None
)
return None
# else pass on to normal left-button signal handler
else:
return False
# startup procedure
def main():
# graphical
if len(sys.argv) < 2 or "--gtk3" in sys.argv:
# prepare for threading in Gtk+ callbacks
gobject.threads_init()
# prepare main window
main = StreamTunerTwo()
# first invocation
if (conf.get("firstrun")):
main.configwin.open(None)
del conf.firstrun
# run
gtk.main()
__print__(dbg.PROC, r"[31m gtk_main_quit [0m")
# invoke command-line interface
else:
import cli
cli.StreamTunerCLI()
# run
if __name__ == "__main__":
main()
|