1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
#!/usr/bin/env python
# encoding: UTF-8
# api: python
# type: application
# title: streamtuner2
# description: directory browser for internet radio / audio streams
# depends: gtk, pygtk, xml.dom.minidom, threading, lxml, pyquery, kronos
# version: 2.0.9
# author: mario salzer
# license: public domain
# url: http://freshmeat.net/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
#
#
|
|
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
#!/usr/bin/env python
# encoding: UTF-8
# api: python
# type: application
# title: streamtuner2
# description: directory browser for internet radio / audio streams
# depends: gtk, pygtk, xml.dom.minidom, threading, lxml, pyquery, kronos
# version: 2.1.0
# author: mario salzer
# license: public domain
# url: http://freshmeat.net/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
#
#
|
︙ | | | ︙ | |
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
|
try:
from processing import Process as Thread
except:
from threading import Thread
Thread.stop = lambda self: None
# gtk modules
import pygtk
import gtk
import gtk.glade
import gobject
# custom modules
sys.path.insert(0, "/usr/share/streamtuner2") # pre-defined directory for modules
sys.path.insert(0, ".") # pre-defined directory for modules
from config import conf # initializes itself, so all conf.vars are available right away
from mygtk import mygtk # gtk treeview
import http
import action # needs workaround... (action.main=main)
from channels import *
from channels import __print__
import favicon
#from pq import pq
|
|
<
<
<
<
|
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
|
try:
from processing import Process as Thread
except:
from threading import Thread
Thread.stop = lambda self: None
# gtk modules
from mygtk import pygtk, gtk, gobject, ui_file, mygtk
# custom modules
sys.path.insert(0, "/usr/share/streamtuner2") # pre-defined directory for modules
sys.path.insert(0, ".") # pre-defined directory for modules
from config import conf # initializes itself, so all conf.vars are available right away
import http
import action # needs workaround... (action.main=main)
from channels import *
from channels import __print__
import favicon
#from pq import pq
|
︙ | | | ︙ | |
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
|
def __init__(self):
# gtkrc stylesheet
self.load_theme(), gui_startup(0.05)
# instantiate gtk/glade widgets in current object
gtk.Builder.__init__(self)
ui_file = [i for i in sum([[i, conf.share+"/"+i] for i in ["ui.xml", "st2.gtk"]], []) if os.path.exists(i)][0];
gtk.Builder.add_from_file(self, ui_file), gui_startup(0.10)
# manual gtk operations
self.extensionsCTM.set_submenu(self.extensions) # duplicates Station>Extension menu into stream context menu
# initialize channels
self.channels = {
"bookmarks": bookmarks(parent=self), # this the remaining built-in channel
"shoutcast": None,#shoutcast(parent=self),
|
<
|
|
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
|
def __init__(self):
# gtkrc stylesheet
self.load_theme(), gui_startup(0.05)
# instantiate gtk/glade widgets in current object
gtk.Builder.__init__(self)
gtk.Builder.add_from_file(self, conf.find_in_dirs([".", conf.share], ui_file)), gui_startup(0.10)
# manual gtk operations
self.extensionsCTM.set_submenu(self.extensions) # duplicates Station>Extension menu into stream context menu
# initialize channels
self.channels = {
"bookmarks": bookmarks(parent=self), # this the remaining built-in channel
"shoutcast": None,#shoutcast(parent=self),
|
︙ | | | ︙ | |
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
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
|
# WHY DON'T YOU WANT TO WORK?!
#self.shoutcast.gtk_list.set_enable_search(True)
#self.shoutcast.gtk_list.set_search_column(4)
#-- Shortcut fo 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):
if (self.channels.has_key(name)):
return self.channels[name] # like self.shoutcast
else:
return self.get_object(name) # or gives an error if neither exists
# custom-named widgets are available from .widgets{} not via .get_widget()
def get_widget(self, name):
if self.widgets.has_key(name):
return self.widgets[name]
else:
return gtk.Builder.get_object(self, name)
# returns the currently selected directory/channel object
def channel(self):
#try:
return self.channels[self.current_channel]
#except Exception,e:
# print(e)
# self.notebook_channels.set_current_page(0)
# self.current_channel = "bookmarks"
# return self.channels["bookmarks"]
def current_channel_gtk(self):
i = self.notebook_channels.get_current_page()
try: return self.channel_names[i]
except: return "bookmarks"
# notebook tab clicked
def channel_switch(self, notebook, page, page_num=0, *args):
# can be called from channelmenu as well:
if type(page) == str:
self.current_channel = page
|
|
|
|
>
>
>
|
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
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
|
# WHY DON'T YOU WANT TO WORK?!
#self.shoutcast.gtk_list.set_enable_search(True)
#self.shoutcast.gtk_list.set_search_column(4)
#-- 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):
if (self.channels.has_key(name)):
return self.channels[name] # like self.shoutcast
else:
return self.get_object(name) # or gives an error if neither exists
# custom-named widgets are available from .widgets{} not via .get_widget()
def get_widget(self, name):
if self.widgets.has_key(name):
return self.widgets[name]
else:
return gtk.Builder.get_object(self, name)
# returns the currently selected directory/channel object
def channel(self):
#try:
return self.channels[self.current_channel]
#except Exception,e:
# print(e)
# self.notebook_channels.set_current_page(0)
# self.current_channel = "bookmarks"
# return self.channels["bookmarks"]
def current_channel_gtk(self):
i = self.notebook_channels.get_current_page()
try: return self.channel_names[i]
except: return "bookmarks"
# notebook tab clicked
def channel_switch(self, notebook, page, page_num=0, *args):
# can be called from channelmenu as well:
if type(page) == str:
self.current_channel = page
|
︙ | | | ︙ | |
287
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
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
|
print("try: .first_show", self.channel().module);
print(self.channel().first_show)
print(self.channel().first_show())
except:
print("channel .first_show() initialization error")
# 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
def row(self):
return self.channel().stations() [self.rowno()]
# 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
def selected(self, name="url"):
return self.row().get(name)
# play button
def on_play_clicked(self, widget, event=None, *args):
row = self.row()
if row:
self.channel().play(row)
favicon.download_playing(row)
# streamripper
def on_record_clicked(self, widget):
row = self.row()
action.record(row.get("url"), "audio/mp3", "url/direct", row=row)
# browse stream
def on_homepage_stream_clicked(self, widget):
url = self.selected("homepage")
action.browser(url)
# browse channel
def on_homepage_channel_clicked(self, widget, event=2):
if event == 2 or event.type == gtk.gdk._2BUTTON_PRESS:
__print__("dblclick")
action.browser(self.channel().homepage)
# reload stream list in current channel-category
def on_reload_clicked(self, widget=None, reload=1):
__print__("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) )
)
# 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
def on_stop_clicked(self, widget):
while self.working:
thread = self.working.pop()
thread.stop()
# click in category list
def on_category_clicked(self, widget, event, *more):
category = self.channel().currentcat()
__print__("on_category_clicked", category, self.current_channel)
self.on_reload_clicked(None, reload=0)
pass
# 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)
try:
(model,iter) = self.model_iter()
model.set_value(iter, 0, gtk.STOCK_ABOUT)
except:
pass
# refresh bookmarks tab
self.bookmarks.load(self.bookmarks.default)
# 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
def update_favicons(self, widget):
entries = self.channel().stations()
favicon.download_all(entries)
# save a file
def save_as(self, widget):
row = self.row()
default_fn = row["title"] + ".m3u"
fn = mygtk.save_file("Save Stream", None, default_fn, [(".m3u","*m3u"),(".pls","*pls"),(".xspf","*xspf"),(".smil","*smil"),(".asx","*asx"),("all files","*")])
if fn:
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 an entry
def delete_entry(self, w):
n = self.rowno()
del self.channel().stations()[ n ]
self.channel().switch()
self.channel().save()
# stream right click
def station_context_menu(self, treeview, event):
return station_context_menu(treeview, event) # wrapper to the static function
|
<
<
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
285
286
287
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
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
|
print("try: .first_show", self.channel().module);
print(self.channel().first_show)
print(self.channel().first_show())
except:
print("channel .first_show() initialization error")
# 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
def row(self):
return self.channel().stations() [self.rowno()]
# 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
def selected(self, name="url"):
return self.row().get(name)
# play button
def on_play_clicked(self, widget, event=None, *args):
row = self.row()
if row:
self.channel().play(row)
favicon.download_playing(row)
# streamripper
def on_record_clicked(self, widget):
row = self.row()
action.record(row.get("url"), "audio/mp3", "url/direct", row=row)
# browse stream
def on_homepage_stream_clicked(self, widget):
url = self.selected("homepage")
action.browser(url)
# browse channel
def on_homepage_channel_clicked(self, widget, event=2):
if event == 2 or event.type == gtk.gdk._2BUTTON_PRESS:
__print__("dblclick")
action.browser(self.channel().homepage)
# reload stream list in current channel-category
def on_reload_clicked(self, widget=None, reload=1):
__print__("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) )
)
# 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
def on_stop_clicked(self, widget):
while self.working:
thread = self.working.pop()
thread.stop()
# click in category list
def on_category_clicked(self, widget, event, *more):
category = self.channel().currentcat()
__print__("on_category_clicked", category, self.current_channel)
self.on_reload_clicked(None, reload=0)
pass
# 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)
try:
(model,iter) = self.model_iter()
model.set_value(iter, 0, gtk.STOCK_ABOUT)
except:
pass
# refresh bookmarks tab
self.bookmarks.load(self.bookmarks.default)
# 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
def update_favicons(self, widget):
entries = self.channel().stations()
favicon.download_all(entries)
# save a file
def save_as(self, widget):
row = self.row()
default_fn = row["title"] + ".m3u"
fn = mygtk.save_file("Save Stream", None, default_fn, [(".m3u","*m3u"),(".pls","*pls"),(".xspf","*xspf"),(".smil","*smil"),(".asx","*asx"),("all files","*")])
if fn:
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 an entry
def delete_entry(self, w):
n = self.rowno()
del self.channel().stations()[ n ]
self.channel().switch()
self.channel().save()
# stream right click
def station_context_menu(self, treeview, event):
return station_context_menu(treeview, event) # wrapper to the static function
|
︙ | | | ︙ | |
430
431
432
433
434
435
436
437
438
439
440
441
442
443
|
if (text <= 0.0): # unknown state
mygtk.do(lambda:self.progress.pulse())
# add text
elif (type(text)==str):
sbar_msg.append(1)
mygtk.do(lambda:self.statusbar.push(sbar_cid, text))
pass
# load plugins from /usr/share/streamtuner2/channels/
def load_plugin_channels(self):
# find plugin files
ls = os.listdir(conf.share + "/channels/")
ls = [fn[:-3] for fn in ls if re.match("^[a-z][\w\d_]+\.py$", fn)]
|
>
|
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
|
if (text <= 0.0): # unknown state
mygtk.do(lambda:self.progress.pulse())
# add text
elif (type(text)==str):
sbar_msg.append(1)
mygtk.do(lambda:self.statusbar.push(sbar_cid, text))
pass
# load plugins from /usr/share/streamtuner2/channels/
def load_plugin_channels(self):
# find plugin files
ls = os.listdir(conf.share + "/channels/")
ls = [fn[:-3] for fn in ls if re.match("^[a-z][\w\d_]+\.py$", fn)]
|
︙ | | | ︙ | |
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
|
print("error initializing:", module, ", exception:")
import traceback
traceback.print_exc()
# default plugins
conf.add_plugin_defaults(self.channels["bookmarks"].config, "bookmarks")
#conf.add_plugin_defaults(self.channels["shoutcast"].config, "shoutcast")
# store window/widget states (sizes, selections, etc.)
def app_state(self, widget):
# gtk widget states
widgetnames = ["win_streamtuner2", "toolbar", "notebook_channels", ] \
+ [id+"_list" for id in self.channel_names] + [id+"_cat" for id in self.channel_names]
conf.save("window", mygtk.app_state(wTree=self, widgetnames=widgetnames), nice=1)
# object vars
channelopts = {} #dict([(id, {"current":self.channels[id].current}) for id in self.channel_names])
for id in self.channels.keys():
if (self.channels[id]):
channelopts[id] = {"current":self.channels[id].current}
conf.save("state", channelopts, nice=1)
# apply gtkrc stylesheet
def load_theme(self):
if conf.get("theme"):
for dir in (conf.dir, conf.share, "/usr/share"):
f = dir + "/themes/" + conf.theme + "/gtk-2.0/gtkrc"
if os.path.exists(f):
gtk.rc_parse(f)
pass
# end application and gtk+ main loop
def gtk_main_quit(self, widget, *x):
if conf.auto_save_appstate:
self.app_state(widget)
gtk.main_quit()
|
>
>
>
|
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
527
528
529
|
print("error initializing:", module, ", exception:")
import traceback
traceback.print_exc()
# default plugins
conf.add_plugin_defaults(self.channels["bookmarks"].config, "bookmarks")
#conf.add_plugin_defaults(self.channels["shoutcast"].config, "shoutcast")
# store window/widget states (sizes, selections, etc.)
def app_state(self, widget):
# gtk widget states
widgetnames = ["win_streamtuner2", "toolbar", "notebook_channels", ] \
+ [id+"_list" for id in self.channel_names] + [id+"_cat" for id in self.channel_names]
conf.save("window", mygtk.app_state(wTree=self, widgetnames=widgetnames), nice=1)
# object vars
channelopts = {} #dict([(id, {"current":self.channels[id].current}) for id in self.channel_names])
for id in self.channels.keys():
if (self.channels[id]):
channelopts[id] = {"current":self.channels[id].current}
conf.save("state", channelopts, nice=1)
# apply gtkrc stylesheet
def load_theme(self):
if conf.get("theme"):
for dir in (conf.dir, conf.share, "/usr/share"):
f = dir + "/themes/" + conf.theme + "/gtk-2.0/gtkrc"
if os.path.exists(f):
gtk.rc_parse(f)
pass
# end application and gtk+ main loop
def gtk_main_quit(self, widget, *x):
if conf.auto_save_appstate:
self.app_state(widget)
gtk.main_quit()
|
︙ | | | ︙ | |
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
|
""" allows to use self. and main. almost interchangably """
# aux win: search dialog (keeps search text in self.q)
# and also: quick search textbox (uses main.q instead)
class search (auxiliary_window):
# show search dialog
def menu_search(self, w):
self.search_dialog.show();
# hide dialog box again
def cancel(self, *args):
self.search_dialog.hide()
return True # stop any other gtk handlers
#self.search_dialog.hide() #if conf.hide_searchdialog
|
>
>
|
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
|
""" allows to use self. and main. almost interchangably """
# aux win: search dialog (keeps search text in self.q)
# and also: quick search textbox (uses main.q instead)
class search (auxiliary_window):
# show search dialog
def menu_search(self, w):
self.search_dialog.show();
# hide dialog box again
def cancel(self, *args):
self.search_dialog.hide()
return True # stop any other gtk handlers
#self.search_dialog.hide() #if conf.hide_searchdialog
|
︙ | | | ︙ | |
624
625
626
627
628
629
630
631
632
633
634
635
636
637
|
main.channels["bookmarks"].streams["search"] = entries # we have to set it here, else .currentcat() might reset it
main.bookmarks.load("search")
# live search on directory server homepages
def server_query(self, w):
"unimplemented"
# don't search at all, open a web browser
def google(self, w):
self.cancel()
action.browser("http://www.google.com/search?q=" + self.search_full.get_text())
|
>
|
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
|
main.channels["bookmarks"].streams["search"] = entries # we have to set it here, else .currentcat() might reset it
main.bookmarks.load("search")
# live search on directory server homepages
def server_query(self, w):
"unimplemented"
# don't search at all, open a web browser
def google(self, w):
self.cancel()
action.browser("http://www.google.com/search?q=" + self.search_full.get_text())
|
︙ | | | ︙ | |
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
|
# instantiates itself
# aux win: stream data editing dialog
class streamedit (auxiliary_window):
# show stream data editing dialog
def open(self, mw):
row = main.row()
for name in ("title", "playing", "genre", "homepage", "url", "favicon", "format", "extra"):
w = main.get_widget("streamedit_" + name)
if w:
w.set_text((str(row.get(name)) if row.get(name) else ""))
self.win_streamedit.show()
# copy widget contents to stream
def save(self, w):
row = main.row()
for name in ("title", "playing", "genre", "homepage", "url", "favicon", "format", "extra"):
w = main.get_widget("streamedit_" + name)
if w:
row[name] = w.get_text()
main.channel().save()
self.cancel(w)
# add a new list entry, update window
def new(self, w):
s = main.channel().stations()
s.append({"title":"new", "url":"", "format":"audio/mp3", "genre":"", "listeners":1});
main.channel().switch() # update display
main.channel().gtk_list.get_selection().select_path(str(len(s)-1)); # set cursor to last row
self.open(w)
# hide window
def cancel(self, *w):
self.win_streamedit.hide()
return True
streamedit = streamedit()
# instantiates itself
# aux win: settings UI
class config_dialog (auxiliary_window):
# display win_config, pre-fill text fields from global conf. object
def open(self, widget):
self.add_plugins()
self.apply(conf.__dict__, "config_", 0)
#self.win_config.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse('#443399'))
self.combobox_theme()
self.win_config.show()
def hide(self, *args):
self.win_config.hide()
return True
# set/load values between gtk window and conf. dict
def apply(self, config, prefix="config_", save=0):
for key,val in config.iteritems():
# map non-alphanumeric chars from config{} to underscores in according gtk widget names
id = re.sub("[^\w]", "_", key)
w = main.get_widget(prefix + id)
|
>
>
>
>
>
>
>
|
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
|
# instantiates itself
# aux win: stream data editing dialog
class streamedit (auxiliary_window):
# show stream data editing dialog
def open(self, mw):
row = main.row()
for name in ("title", "playing", "genre", "homepage", "url", "favicon", "format", "extra"):
w = main.get_widget("streamedit_" + name)
if w:
w.set_text((str(row.get(name)) if row.get(name) else ""))
self.win_streamedit.show()
# copy widget contents to stream
def save(self, w):
row = main.row()
for name in ("title", "playing", "genre", "homepage", "url", "favicon", "format", "extra"):
w = main.get_widget("streamedit_" + name)
if w:
row[name] = w.get_text()
main.channel().save()
self.cancel(w)
# add a new list entry, update window
def new(self, w):
s = main.channel().stations()
s.append({"title":"new", "url":"", "format":"audio/mp3", "genre":"", "listeners":1});
main.channel().switch() # update display
main.channel().gtk_list.get_selection().select_path(str(len(s)-1)); # set cursor to last row
self.open(w)
# hide window
def cancel(self, *w):
self.win_streamedit.hide()
return True
streamedit = streamedit()
# instantiates itself
# aux win: settings UI
class config_dialog (auxiliary_window):
# display win_config, pre-fill text fields from global conf. object
def open(self, widget):
self.add_plugins()
self.apply(conf.__dict__, "config_", 0)
#self.win_config.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse('#443399'))
self.combobox_theme()
self.win_config.show()
def hide(self, *args):
self.win_config.hide()
return True
# set/load values between gtk window and conf. dict
def apply(self, config, prefix="config_", save=0):
for key,val in config.iteritems():
# map non-alphanumeric chars from config{} to underscores in according gtk widget names
id = re.sub("[^\w]", "_", key)
w = main.get_widget(prefix + id)
|
︙ | | | ︙ | |
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
|
elif (w and type(w)==gtk.Entry):
w.set_text(str(val))
elif (w and save):
config[key] = w.get_active()
elif (w):
w.set_active(bool(val))
pass
# fill combobox
def combobox_theme(self):
# self.theme.combo_box_new_text()
# find themes
themedirs = (conf.share+"/themes", conf.dir+"/themes", "/usr/share/themes")
themes = ["no theme"]
[[themes.append(e) for e in os.listdir(dir)] for dir in themedirs if os.path.exists(dir)]
# add to combobox
for num,themename in enumerate(themes):
self.theme.append_text(themename)
if conf.theme == themename:
self.theme.set_active(num)
# erase this function, so it only ever gets called once
self.combobox_theme = lambda: None
# retrieve currently selected value
def apply_theme(self):
if self.theme.get_active() >= 0:
conf.theme = self.theme.get_model()[ self.theme.get_active()][0]
main.load_theme()
# add configuration setting definitions from plugins
once = 0
def add_plugins(self):
if self.once:
return
|
>
>
>
|
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
|
elif (w and type(w)==gtk.Entry):
w.set_text(str(val))
elif (w and save):
config[key] = w.get_active()
elif (w):
w.set_active(bool(val))
pass
# fill combobox
def combobox_theme(self):
# self.theme.combo_box_new_text()
# find themes
themedirs = (conf.share+"/themes", conf.dir+"/themes", "/usr/share/themes")
themes = ["no theme"]
[[themes.append(e) for e in os.listdir(dir)] for dir in themedirs if os.path.exists(dir)]
# add to combobox
for num,themename in enumerate(themes):
self.theme.append_text(themename)
if conf.theme == themename:
self.theme.set_active(num)
# erase this function, so it only ever gets called once
self.combobox_theme = lambda: None
# retrieve currently selected value
def apply_theme(self):
if self.theme.get_active() >= 0:
conf.theme = self.theme.get_model()[ self.theme.get_active()][0]
main.load_theme()
# add configuration setting definitions from plugins
once = 0
def add_plugins(self):
if self.once:
return
|
︙ | | | ︙ | |
811
812
813
814
815
816
817
818
819
820
821
822
823
824
|
self.add_( "config_"+opt["name"], cb )
else:
self.add_( "config_"+opt["name"], gtk.Entry(), opt["description"] )
# spacer
self.add_( "filler_pl_"+name, gtk.HSeparator() )
self.once = 1
# put gtk widgets into config dialog notebook
def add_(self, id, w, label=None, color=""):
w.set_property("visible", True)
main.widgets[id] = w
if label:
w.set_width_chars(10)
|
>
|
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
|
self.add_( "config_"+opt["name"], cb )
else:
self.add_( "config_"+opt["name"], gtk.Entry(), opt["description"] )
# spacer
self.add_( "filler_pl_"+name, gtk.HSeparator() )
self.once = 1
# put gtk widgets into config dialog notebook
def add_(self, id, w, label=None, color=""):
w.set_property("visible", True)
main.widgets[id] = w
if label:
w.set_width_chars(10)
|
︙ | | | ︙ | |
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
|
# This module lists static content from ~/.config/streamtuner2/bookmarks.json;
# its data list is queried by other plugins to add 'star' icons.
#
# Some feature extensions inject custom categories[] into streams{}
# e.g. "search" adds its own category once activated, as does the "timer" plugin.
#
class bookmarks(GenericChannel):
# desc
api = "streamtuner2"
module = "bookmarks"
title = "bookmarks"
version = 0.4
base_url = "file:.config/streamtuner2/bookmarks.json"
listformat = "*/*"
# i like this
config = [
{"name":"like_my_bookmarks", "type":"boolean", "value":0, "description":"I like my bookmarks"},
]
# content
categories = ["favourite", ]
current = "favourite"
default = "favourite"
streams = {"favourite":[], "search":[], "scripts":[], "timer":[], }
# cache list, to determine if a PLS url is bookmarked
urls = []
# this channel does not actually retrieve/parse data from anywhere
def update_categories(self):
pass
def update_streams(self, cat):
return self.streams.get(cat, [])
# initial display
def first_show(self):
if not self.streams["favourite"]:
self.cache()
# all entries just come from "bookmarks.json"
def cache(self):
# stream list
cache = conf.load(self.module)
if (cache):
self.streams = cache
# save to cache file
def save(self):
conf.save(self.module, self.streams, nice=1)
# checks for existence of an URL in bookmarks store,
# this method is called by other channel modules' display() method
|
>
>
>
|
>
>
>
>
>
>
|
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
|
# This module lists static content from ~/.config/streamtuner2/bookmarks.json;
# its data list is queried by other plugins to add 'star' icons.
#
# Some feature extensions inject custom categories[] into streams{}
# e.g. "search" adds its own category once activated, as does the "timer" plugin.
#
class bookmarks(GenericChannel):
# desc
api = "streamtuner2"
module = "bookmarks"
title = "bookmarks"
version = 0.4
base_url = "file:.config/streamtuner2/bookmarks.json"
listformat = "*/*"
# i like this
config = [
{"name":"like_my_bookmarks", "type":"boolean", "value":0, "description":"I like my bookmarks"},
]
# content
categories = ["favourite", ] # timer, links, search, and links show up as needed
current = "favourite"
default = "favourite"
streams = {"favourite":[], "search":[], "scripts":[], "timer":[], }
# cache list, to determine if a PLS url is bookmarked
urls = []
# this channel does not actually retrieve/parse data from anywhere
def update_categories(self):
pass
def update_streams(self, cat):
return self.streams.get(cat, [])
# initial display
def first_show(self):
if not self.streams["favourite"]:
self.cache()
# all entries just come from "bookmarks.json"
def cache(self):
# stream list
cache = conf.load(self.module)
if (cache):
self.streams = cache
# save to cache file
def save(self):
conf.save(self.module, self.streams, nice=1)
# checks for existence of an URL in bookmarks store,
# this method is called by other channel modules' display() method
|
︙ | | | ︙ | |
971
972
973
974
975
976
977
978
979
980
981
982
983
984
|
# select a category in treeview
def add_category(self, cat):
if cat not in self.categories: # add category if missing
self.categories.append(cat)
self.display_categories()
# change cursor
def set_category(self, cat):
self.add_category(cat)
self.gtk_cat.get_selection().select_path(str(self.categories.index(cat)))
return self.currentcat()
|
>
>
|
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
|
# select a category in treeview
def add_category(self, cat):
if cat not in self.categories: # add category if missing
self.categories.append(cat)
self.display_categories()
# change cursor
def set_category(self, cat):
self.add_category(cat)
self.gtk_cat.get_selection().select_path(str(self.categories.index(cat)))
return self.currentcat()
|
︙ | | | ︙ | |
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
|
#-- startup progress bar
progresswin, progressbar = 0, 0
def gui_startup(p=0.0, msg="streamtuner2 is starting"):
global progresswin,progressbar
if not progresswin:
# GtkWindow "progresswin"
progresswin = gtk.Window()
progresswin.set_property("title", "streamtuner2")
progresswin.set_property("default_width", 300)
progresswin.set_property("width_request", 300)
progresswin.set_property("default_height", 30)
progresswin.set_property("height_request", 30)
progresswin.set_property("window_position", "center")
progresswin.set_property("decorated", False)
progresswin.set_property("visible", True)
# GtkProgressBar "progressbar"
progressbar = gtk.ProgressBar()
progressbar.set_property("visible", True)
progressbar.set_property("show_text", True)
progressbar.set_property("text", msg)
progresswin.add(progressbar)
progresswin.show_all()
try:
if p<1:
progressbar.set_fraction(p)
progressbar.set_property("text", msg)
while gtk.events_pending(): gtk.main_iteration(False)
else:
progresswin.destroy()
except: return
#-- run main ---------------------------------------------
if __name__ == "__main__":
|
>
>
>
>
|
|
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
|
#-- startup progress bar
progresswin, progressbar = 0, 0
def gui_startup(p=0.0, msg="streamtuner2 is starting"):
global progresswin,progressbar
if not progresswin:
# GtkWindow "progresswin"
progresswin = gtk.Window()
progresswin.set_property("title", "streamtuner2")
progresswin.set_property("default_width", 300)
progresswin.set_property("width_request", 300)
progresswin.set_property("default_height", 30)
progresswin.set_property("height_request", 30)
progresswin.set_property("window_position", "center")
progresswin.set_property("decorated", False)
progresswin.set_property("visible", True)
# GtkProgressBar "progressbar"
progressbar = gtk.ProgressBar()
progressbar.set_property("visible", True)
progressbar.set_property("show_text", True)
progressbar.set_property("text", msg)
progresswin.add(progressbar)
progresswin.show_all()
try:
if p<1:
progressbar.set_fraction(p)
progressbar.set_property("text", msg)
while gtk.events_pending(): gtk.main_iteration(False)
else:
progresswin.hide()
except: return
#-- run main ---------------------------------------------
if __name__ == "__main__":
|
︙ | | | ︙ | |