def __init__(self, host='localhost', port=1234, op='request'): gtk.VPaned.__init__(self) self.show() # Connect to liquidsoap self.op = op self.tel = LiqClient(host, port) # Fast metadata view: short metadata, right click to see more (TODO) self.list = View( [ ['rid', 40], ['2nd_queue_pos', 80], ['skip', False], ['artist', '120'], ['title', '120'], # Right-align URI because the end is more informative # than the beginning ['initial_uri', '250', { 'xalign': 1.0 }] ], self.queue()) self.list.drag_dest_set(gtk.DEST_DEFAULT_ALL, [('text/uri-list', 0, 0)], gtk.gdk.ACTION_COPY) self.list.connect("drag_data_received", self.drag) self.list.connect("row_activated", self.row_activated) scroll = gtk.ScrolledWindow() scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scroll.add(self.list) # The list is ready, pack it to the top region # together with a help label box = gtk.VBox() lbl = gtk.Label() lbl.set_markup("\ <b>Enqueue</b> files by drag-n-dropping from your file manager or \ using the file chooser below.\n\ <b>Remove/restore</b> scheduled files by double-clicking.") box.pack_start(scroll, fill=True, expand=True) box.pack_start(lbl, fill=True, expand=False) self.pack1(box, resize=True) lbl.show() self.list.show() scroll.show() box.show() fsel = gtk.FileChooserWidget() fsel.connect("file_activated", lambda s: self.selected(s)) exp = gtk.Expander("File chooser") exp.add(fsel) self.pack2(exp, resize=False) fsel.show() exp.show() gobject.timeout_add(1000, self.update)
def __init__(self, host='localhost', port=1234, op='root'): gtk.VBox.__init__(self) # Connect to liquidsoap self.op = op self.tel = LiqClient(host, port) # Fast metadata view: short metadata, right click to see more (TODO) a = self.tel.metadata(self.op + ".metadata") self.list = View( [ ['rid', 40], ['artist', '120'], ['title', '120'], # Right-align URI because the end is more informative # than the beginning ['initial_uri', '300', { 'xalign': 1.0 }], ['on_air', '50'] ], a) scroll = gtk.ScrolledWindow() scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scroll.add(self.list) scroll.show() self.pack_start(scroll, padding=10) # an hbox containing : remaining / start-stop / skip self.hbox = gtk.HBox() self.pack_start(self.hbox, False, False) self.remaining = gtk.Label("...") self.hbox.pack_start(self.remaining) gobject.timeout_add(1000, self.update) self.skip = gtk.Button("skip") self.skip.connect("clicked", self.do_skip, None) self.hbox.pack_start(self.skip, False, False) self.onoff = gtk.ToggleButton("on/off") self.onoff.set_active(self.tel.command(self.op + ".status") == "on") self.onoff.connect("clicked", self.do_onoff, None) self.hbox.pack_start(self.onoff, False, False) # Show everything... self.list.show() self.remaining.show() self.onoff.show() self.skip.show() self.show() self.hbox.show()
def __init__(self,host='localhost',port=1234,op='root'): gtk.ScrolledWindow.__init__(self) self.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC) # Connect to liquidsoap self.op = op self.tel = LiqClient(host,port) self.view = View([['status','50'],['uri','300']],[]) self.update() self.add(self.view) self.view.show() self.show() gobject.timeout_add(1000,self.update)
class LiqPlaylist(gtk.ScrolledWindow): def __init__(self,host='localhost',port=1234,op='root'): gtk.ScrolledWindow.__init__(self) self.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC) # Connect to liquidsoap self.op = op self.tel = LiqClient(host,port) self.view = View([['status','50'],['uri','300']],[]) self.update() self.add(self.view) self.view.show() self.show() gobject.timeout_add(1000,self.update) def next(self): a = filter(lambda x: x!='', re.compile('\n').split(self.tel.command(self.op+".next"))) def f(e): m = re.search('^\[(.*?)\] (.*)',e) if m: return { 'uri': m.group(2) , 'status': m.group(1) } else: return { 'uri': e } return [ f(e) for e in a ] def update(self): # TODO restore scroll position self.view.setModel(self.next()) return True
def __init__(self, host='localhost', port=1234): gtk.Notebook.__init__(self) self.set_show_tabs(False) self.host = host self.port = port self.tel = LiqClient(host, port) self.list = View([['type', '80'], ['name', '100']], []) self.list.connect('row_activated', self.row_activated) box = gtk.HBox() box.pack_start(self.list, False, False) lbl = gtk.Label() lbl.set_markup(""" <b>The Savonet Team is happy to welcome you in liguidsoap.</b> On the left is a list of controllable nodes. Double-click a row to open the controller. The <b>output</b> nodes are those which do something with your stream. Some output directly to your speakers, some save your stream to a file, and finally, some output to an icecast server for webradio. A <b>mixer</b> is a mixing table. It has some input sources, and allows you to select which one you want to play, tune the volume, etc. <b>editable</b> and <b>queue</b> nodes are interactive playlists, in which you can add files using drag-n-drop or builtin file browser. The editable is more powerful, allows you to delete, insert at any place, not only at the bottom. Finally, a <b>playlist</b> is a non-interactive playlist, which list and behaviour is hard-coded in the liquidsoap script. You can just see what will be played next. (Actually, more could be coming soon, like changing random mode and playlist file...) Liquidsoap and liguidsoap are copyright (c) 2003-2017 Savonet Team. This is free software, released under the terms of GPL version 2 or higher. """) box.pack_start(lbl) self.append_page(box, gtk.Label('list')) box.show() lbl.show() self.list.show() self.show() gobject.timeout_add(1000, self.update)
def __init__(self,host='localhost',port=1234,op='request'): gtk.VPaned.__init__(self) self.show() # Connect to liquidsoap self.op = op self.tel = LiqClient(host,port) # Fast metadata view: short metadata, right click to see more (TODO) self.list = View([['rid' , 40], ['2nd_queue_pos', 80], ['skip' , False], ['artist' ,'120'], ['title' ,'120'], # Right-align URI because the end is more informative # than the beginning ['initial_uri','250', {'xalign':1.0}]], self.queue()) self.list.drag_dest_set(gtk.DEST_DEFAULT_ALL, [('text/uri-list',0,0)], gtk.gdk.ACTION_COPY) self.list.connect("drag_data_received",self.drag) self.list.connect("row_activated",self.row_activated) scroll = gtk.ScrolledWindow() scroll.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC) scroll.add(self.list) # The list is ready, pack it to the top region # together with a help label box = gtk.VBox() lbl = gtk.Label() lbl.set_markup("\ <b>Enqueue</b> files by drag-n-dropping from your file manager or \ using the file chooser below.\n\ <b>Remove/restore</b> scheduled files by double-clicking.") box.pack_start(scroll,fill=True,expand=True) box.pack_start(lbl,fill=True,expand=False) self.pack1(box,resize=True) lbl.show() self.list.show() scroll.show() box.show() fsel = gtk.FileChooserWidget() fsel.connect("file_activated", lambda s: self.selected(s)) exp = gtk.Expander("File chooser") exp.add(fsel) self.pack2(exp,resize=False) fsel.show() exp.show() gobject.timeout_add(1000,self.update)
def __init__(self,host='localhost',port=1234,op='root'): gtk.VBox.__init__(self) # Connect to liquidsoap self.op = op self.tel = LiqClient(host,port) # Fast metadata view: short metadata, right click to see more (TODO) a=self.tel.metadata(self.op+".metadata") self.list = View([['rid' , 40], ['artist' ,'120'], ['title' ,'120'], # Right-align URI because the end is more informative # than the beginning ['initial_uri','300', {'xalign':1.0}], ['on_air','50']], a) scroll = gtk.ScrolledWindow() scroll.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC) scroll.add(self.list) scroll.show() self.pack_start(scroll,padding=10) # an hbox containing : remaining / start-stop / skip self.hbox = gtk.HBox() self.pack_start(self.hbox,False,False) self.remaining = gtk.Label("...") self.hbox.pack_start(self.remaining) gobject.timeout_add(1000,self.update) self.skip = gtk.Button("skip") self.skip.connect("clicked", self.do_skip, None) self.hbox.pack_start(self.skip,False,False) self.onoff = gtk.ToggleButton("on/off") self.onoff.set_active(self.tel.command(self.op+".status")=="on") self.onoff.connect("clicked", self.do_onoff, None) self.hbox.pack_start(self.onoff,False,False) # Show everything... self.list.show() self.remaining.show() self.onoff.show() self.skip.show() self.show() self.hbox.show()
def __init__(self,host='localhost',port=1234): gtk.Notebook.__init__(self) self.set_show_tabs(False) self.host=host self.port=port self.tel = LiqClient(host,port) self.list = View([['type','80'],['name','100']],[]) self.list.connect('row_activated',self.row_activated) box = gtk.HBox() box.pack_start(self.list,False,False) lbl = gtk.Label() lbl.set_markup(""" <b>The Savonet Team is happy to welcome you in liguidsoap.</b> On the left is a list of controllable nodes. Double-click a row to open the controller. The <b>output</b> nodes are those which do something with your stream. Some output directly to your speakers, some save your stream to a file, and finally, some output to an icecast server for webradio. A <b>mixer</b> is a mixing table. It has some input sources, and allows you to select which one you want to play, tune the volume, etc. <b>editable</b> and <b>queue</b> nodes are interactive playlists, in which you can add files using drag-n-drop or builtin file browser. The editable is more powerful, allows you to delete, insert at any place, not only at the bottom. Finally, a <b>playlist</b> is a non-interactive playlist, which list and behaviour is hard-coded in the liquidsoap script. You can just see what will be played next. (Actually, more could be coming soon, like changing random mode and playlist file...) Liquidsoap and liguidsoap are copyright (c) 2003-2014 Savonet Team. This is free software, released under the terms of GPL version 2 or higher. """) box.pack_start(lbl) self.append_page(box,gtk.Label('list')) box.show() ; lbl.show() self.list.show() self.show() gobject.timeout_add(1000,self.update)
class LiqGui(gtk.Notebook): def __init__(self,host='localhost',port=1234): gtk.Notebook.__init__(self) self.set_show_tabs(False) self.host=host self.port=port self.tel = LiqClient(host,port) self.list = View([['type','80'],['name','100']],[]) self.list.connect('row_activated',self.row_activated) box = gtk.HBox() box.pack_start(self.list,False,False) lbl = gtk.Label() lbl.set_markup(""" <b>The Savonet Team is happy to welcome you in liguidsoap.</b> On the left is a list of controllable nodes. Double-click a row to open the controller. The <b>output</b> nodes are those which do something with your stream. Some output directly to your speakers, some save your stream to a file, and finally, some output to an icecast server for webradio. A <b>mixer</b> is a mixing table. It has some input sources, and allows you to select which one you want to play, tune the volume, etc. <b>editable</b> and <b>queue</b> nodes are interactive playlists, in which you can add files using drag-n-drop or builtin file browser. The editable is more powerful, allows you to delete, insert at any place, not only at the bottom. Finally, a <b>playlist</b> is a non-interactive playlist, which list and behaviour is hard-coded in the liquidsoap script. You can just see what will be played next. (Actually, more could be coming soon, like changing random mode and playlist file...) Liquidsoap and liguidsoap are copyright (c) 2003-2014 Savonet Team. This is free software, released under the terms of GPL version 2 or higher. """) box.pack_start(lbl) self.append_page(box,gtk.Label('list')) box.show() ; lbl.show() self.list.show() self.show() gobject.timeout_add(1000,self.update) def update(self): def hashof(s): m = re.search('(\S+)\s:\s(\S+)',s) return { 'name' : m.group(1), 'type' : m.group(2) } list = [ hashof(s) for s in filter(lambda x: x!='', re.compile('\n').split(self.tel.command('list'))) ] self.list.setModel(list) def row_activated(self,view,path,column): type = view.get_model().get_value(view.get_model().get_iter(path),0) name = view.get_model().get_value(view.get_model().get_iter(path),1) b = gtk.HBox() l = gtk.Label(name+' ') c = gtk.Button('x') # TODO stock close c.n = self.get_n_pages() b.pack_start(l) b.pack_start(c) c.show() ; l.show() def clicked(w): self.remove_page(w.n) # TODO cleanup contents (especially telnet) c.connect('clicked',clicked) self.set_show_tabs(True) # This is a dirty workaround, we should not show this item # in the first place! if type=='interactive': print("Unsupported item...") else: self.append_page( {'queue' : lambda x: LiqQueue(self.host,self.port,x), 'editable': lambda x: LiqEditable(self.host,self.port,x), 'playlist': lambda x: LiqPlaylist(self.host,self.port,x), 'mixer' : lambda x: LiqMix(self.host,self.port,x), 'output.icecast': lambda x: LiqOutput(self.host,self.port,x), 'output.file' : lambda x: LiqOutput(self.host,self.port,x), 'output.oss' : lambda x: LiqOutput(self.host,self.port,x), 'output.ao' : lambda x: LiqOutput(self.host,self.port,x), 'output.alsa' : lambda x: LiqOutput(self.host,self.port,x), 'output.portaudio': lambda x: LiqOutput(self.host,self.port,x), 'output.pulseaudio': lambda x: LiqOutput(self.host,self.port,x), 'output.dummy' : lambda x: LiqOutput(self.host,self.port,x), }[type](name),b)
class LiqOutput(gtk.VBox): def delete_event(self, widget, event, data=None): return False def destroy(self, widget, data=None): gtk.main_quit() def __init__(self, host='localhost', port=1234, op='root'): gtk.VBox.__init__(self) # Connect to liquidsoap self.op = op self.tel = LiqClient(host, port) # Fast metadata view: short metadata, right click to see more (TODO) a = self.tel.metadata(self.op + ".metadata") self.list = View( [ ['rid', 40], ['artist', '120'], ['title', '120'], # Right-align URI because the end is more informative # than the beginning ['initial_uri', '300', { 'xalign': 1.0 }], ['on_air', '50'] ], a) scroll = gtk.ScrolledWindow() scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scroll.add(self.list) scroll.show() self.pack_start(scroll, padding=10) # an hbox containing : remaining / start-stop / skip self.hbox = gtk.HBox() self.pack_start(self.hbox, False, False) self.remaining = gtk.Label("...") self.hbox.pack_start(self.remaining) gobject.timeout_add(1000, self.update) self.skip = gtk.Button("skip") self.skip.connect("clicked", self.do_skip, None) self.hbox.pack_start(self.skip, False, False) self.onoff = gtk.ToggleButton("on/off") self.onoff.set_active(self.tel.command(self.op + ".status") == "on") self.onoff.connect("clicked", self.do_onoff, None) self.hbox.pack_start(self.onoff, False, False) # Show everything... self.list.show() self.remaining.show() self.onoff.show() self.skip.show() self.show() self.hbox.show() def update(self): remaining = self.tel.command(self.op + ".remaining") if remaining != "(undef)": remaining = float(remaining) self.remaining.set_label("Remaining: %02d:%02d" % (remaining / 60, remaining % 60)) else: self.remaining.set_label("Remaining: (undef)") self.list.setModel(self.tel.metadata(self.op + ".metadata")) if self.tel.command(self.op + ".status") == "on": if not self.onoff.get_active(): self.onoff.set_active(True) else: if self.onoff.get_active(): self.onoff.set_active(False) return 1 def do_skip(self, widget, data=None): self.tel.command(self.op + ".skip") def do_onoff(self, widget, data=None): # The state we get is the one *after* the click if self.onoff.get_active(): self.tel.command(self.op + ".start") else: self.tel.command(self.op + ".stop") def main(self): gtk.main()
class LiqGui(gtk.Notebook): def __init__(self,host='localhost',port=1234): gtk.Notebook.__init__(self) self.set_show_tabs(False) self.host=host self.port=port self.tel = LiqClient(host,port) self.list = View([['type','80'],['name','100']],[]) self.list.connect('row_activated',self.row_activated) box = gtk.HBox() box.pack_start(self.list,False,False) lbl = gtk.Label() lbl.set_markup(""" <b>The Savonet Team is happy to welcome you in liguidsoap.</b> On the left is a list of controllable nodes. Double-click a row to open the controller. The <b>output</b> nodes are those which do something with your stream. Some output directly to your speakers, some save your stream to a file, and finally, some output to an icecast server for webradio. A <b>mixer</b> is a mixing table. It has some input sources, and allows you to select which one you want to play, tune the volume, etc. <b>editable</b> and <b>queue</b> nodes are interactive playlists, in which you can add files using drag-n-drop or builtin file browser. The editable is more powerful, allows you to delete, insert at any place, not only at the bottom. Finally, a <b>playlist</b> is a non-interactive playlist, which list and behaviour is hard-coded in the liquidsoap script. You can just see what will be played next. (Actually, more could be coming soon, like changing random mode and playlist file...) Liquidsoap and liguidsoap are copyright (c) 2003-2021 Savonet Team. This is free software, released under the terms of GPL version 2 or higher. """) box.pack_start(lbl) self.append_page(box,gtk.Label('list')) box.show() ; lbl.show() self.list.show() self.show() gobject.timeout_add(1000,self.update) def update(self): def hashof(s): m = re.search('(\S+)\s:\s(\S+)',s) return { 'name' : m.group(1), 'type' : m.group(2) } list = [ hashof(s) for s in filter(lambda x: x!='', re.compile('\n').split(self.tel.command('list'))) ] self.list.setModel(list) def row_activated(self,view,path,column): type = view.get_model().get_value(view.get_model().get_iter(path),0) name = view.get_model().get_value(view.get_model().get_iter(path),1) b = gtk.HBox() l = gtk.Label(name+' ') c = gtk.Button('x') # TODO stock close c.n = self.get_n_pages() b.pack_start(l) b.pack_start(c) c.show() ; l.show() def clicked(w): self.remove_page(w.n) # TODO cleanup contents (especially telnet) c.connect('clicked',clicked) self.set_show_tabs(True) # This is a dirty workaround, we should not show this item # in the first place! if type=='interactive': print("Unsupported item...") else: self.append_page( {'queue' : lambda x: LiqQueue(self.host,self.port,x), 'editable': lambda x: LiqEditable(self.host,self.port,x), 'playlist': lambda x: LiqPlaylist(self.host,self.port,x), 'mixer' : lambda x: LiqMix(self.host,self.port,x), 'output.icecast': lambda x: LiqOutput(self.host,self.port,x), 'output.file' : lambda x: LiqOutput(self.host,self.port,x), 'output.oss' : lambda x: LiqOutput(self.host,self.port,x), 'output.ao' : lambda x: LiqOutput(self.host,self.port,x), 'output.alsa' : lambda x: LiqOutput(self.host,self.port,x), 'output.portaudio': lambda x: LiqOutput(self.host,self.port,x), 'output.pulseaudio': lambda x: LiqOutput(self.host,self.port,x), 'output.dummy' : lambda x: LiqOutput(self.host,self.port,x), }[type](name),b)
class LiqOutput(gtk.VBox): def delete_event(self, widget, event, data=None): return False def destroy(self, widget, data=None): gtk.main_quit() def __init__(self,host='localhost',port=1234,op='root'): gtk.VBox.__init__(self) # Connect to liquidsoap self.op = op self.tel = LiqClient(host,port) # Fast metadata view: short metadata, right click to see more (TODO) a=self.tel.metadata(self.op+".metadata") self.list = View([['rid' , 40], ['artist' ,'120'], ['title' ,'120'], # Right-align URI because the end is more informative # than the beginning ['initial_uri','300', {'xalign':1.0}], ['on_air','50']], a) scroll = gtk.ScrolledWindow() scroll.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC) scroll.add(self.list) scroll.show() self.pack_start(scroll,padding=10) # an hbox containing : remaining / start-stop / skip self.hbox = gtk.HBox() self.pack_start(self.hbox,False,False) self.remaining = gtk.Label("...") self.hbox.pack_start(self.remaining) gobject.timeout_add(1000,self.update) self.skip = gtk.Button("skip") self.skip.connect("clicked", self.do_skip, None) self.hbox.pack_start(self.skip,False,False) self.onoff = gtk.ToggleButton("on/off") self.onoff.set_active(self.tel.command(self.op+".status")=="on") self.onoff.connect("clicked", self.do_onoff, None) self.hbox.pack_start(self.onoff,False,False) # Show everything... self.list.show() self.remaining.show() self.onoff.show() self.skip.show() self.show() self.hbox.show() def update(self): remaining = self.tel.command(self.op+".remaining") if remaining!="(undef)": remaining=float(remaining) self.remaining.set_label("Remaining: %02d:%02d" % (remaining/60,remaining%60)) else: self.remaining.set_label("Remaining: (undef)") self.list.setModel(self.tel.metadata(self.op+".metadata")) if self.tel.command(self.op+".status")=="on": if not self.onoff.get_active(): self.onoff.set_active(True) else: if self.onoff.get_active(): self.onoff.set_active(False) return 1 def do_skip(self,widget,data=None): self.tel.command(self.op+".skip") def do_onoff(self,widget,data=None): # The state we get is the one *after* the click if self.onoff.get_active(): self.tel.command(self.op+".start") else: self.tel.command(self.op+".stop") def main(self): gtk.main()
class LiqQueue(gtk.VPaned): def __init__(self,host='localhost',port=1234,op='request'): gtk.VPaned.__init__(self) self.show() # Connect to liquidsoap self.op = op self.tel = LiqClient(host,port) # Fast metadata view: short metadata, right click to see more (TODO) self.list = View([['rid' , 40], ['2nd_queue_pos', 80], ['skip' , False], ['artist' ,'120'], ['title' ,'120'], # Right-align URI because the end is more informative # than the beginning ['initial_uri','250', {'xalign':1.0}]], self.queue()) self.list.drag_dest_set(gtk.DEST_DEFAULT_ALL, [('text/uri-list',0,0)], gtk.gdk.ACTION_COPY) self.list.connect("drag_data_received",self.drag) self.list.connect("row_activated",self.row_activated) scroll = gtk.ScrolledWindow() scroll.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC) scroll.add(self.list) # The list is ready, pack it to the top region # together with a help label box = gtk.VBox() lbl = gtk.Label() lbl.set_markup("\ <b>Enqueue</b> files by drag-n-dropping from your file manager or \ using the file chooser below.\n\ <b>Remove/restore</b> scheduled files by double-clicking.") box.pack_start(scroll,fill=True,expand=True) box.pack_start(lbl,fill=True,expand=False) self.pack1(box,resize=True) lbl.show() self.list.show() scroll.show() box.show() fsel = gtk.FileChooserWidget() fsel.connect("file_activated", lambda s: self.selected(s)) exp = gtk.Expander("File chooser") exp.add(fsel) self.pack2(exp,resize=False) fsel.show() exp.show() gobject.timeout_add(1000,self.update) def selected(self,file): self.tel.command(self.op+".push "+file.get_filename()) def drag(self,w,context,x,y,data,info,time): if data and data.format == 8: for e in data.data.split('\r\n')[:-1]: self.tel.command(self.op+".push "+urllib.unquote(e)) context.finish(gtk.TRUE, gtk.FALSE, time) def row_activated(self,view,path,column): rid = view.get_model().get_value(view.get_model().get_iter(path),0) skip = view.get_model().get_value(view.get_model().get_iter(path),2) if skip: self.tel.command(self.op+".consider "+str(rid)) else: self.tel.command(self.op+".ignore "+str(rid)) def queue(self): a = filter(lambda x: x!='', re.compile('(\d+)\s*').split(self.tel.command(self.op+".queue"))) a = [ self.tel.metadata('request.metadata '+e)[0] for e in a ] return a def update(self): self.list.setModel(self.queue()) return 1
def __init__(self, host='localhost', port=1234, op='request'): gtk.VPaned.__init__(self) self.show() # Connect to liquidsoap self.op = op self.tel = LiqClient(host, port) self.list = View( [ ['rid', 40], ['status', '70'], ['artist', '120'], ['title', '120'], # Right-align URI because the end is more informative # than the beginning ['initial_uri', '300', { 'xalign': 1.0 }] ], []) self.update() # Popup menu for requests menu = gtk.Menu() item = gtk.ImageMenuItem(gtk.STOCK_REMOVE) def remove_request(item): model, path = self.list.get_selection().get_selected() self.tel.command(self.op + '.remove ' + str(model.get_value(path, 0))) item.connect('activate', remove_request) item.show() menu.append(item) def popup(w, event): if event.button == 3: menu.popup(None, None, None, event.button, event.time) self.list.connect('button_press_event', popup) # Receive drag-n-drops self.list.enable_model_drag_dest([('text/uri-list', 0, 0), ridformat], gtk.gdk.ACTION_DEFAULT) def dnd_receive(w, context, x, y, data, info, time): if data and (data.format != 8 and data.format != ridformat[2]): print "DnD received: Unknown data format! (%d)" % data.format return row = w.get_dest_row_at_pos(x, y) if row: # This is an insertion path, pos = row # Remove the number of resolv(ed|ing) requests to get the pos # in the pending queue pos = path[0] - (len(w.get_model()) - self.plen) if pos < 0: print "Cannot move outside pending queue!" return if pos >= self.plen - 1: pos = -1 if data and data.format == ridformat[2]: rid = int(data.data) self.tel.command('%s.move %d %d' % (self.op, rid, pos)) if data and data.format == 8: for e in data.data.split('\r\n')[:-1]: self.tel.command(self.op + '.insert ' + str(pos) + ' ' + e) else: # This is a push if data and data.format == ridformat[2]: rid = int(data.data) self.tel.command('%s.move %d %d' % (self.op, rid, -1)) if data and data.format == 8: for e in data.data.split('\r\n')[:-1]: self.tel.command(self.op + ".push " + urllib.unquote(e)) context.finish(True, False, time) self.list.connect("drag_data_received", dnd_receive) # Emit drag-n-drops self.list.enable_model_drag_source(gtk.gdk.BUTTON1_MASK, [ridformat], gtk.DEST_DEFAULT_ALL) def dnd_emit(w, context, sel, info, time): # Check that format is RID if info == ridformat[2]: model, iter = w.get_selection().get_selected() if iter: sel.set(sel.target, info, str(model.get_value(iter, 0))) self.list.connect("drag_data_get", dnd_emit) # Put the list in a scroll scroll = gtk.ScrolledWindow() scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scroll.add(self.list) scroll.show() self.list.show() # Pack this to self, with a doc label box = gtk.VBox() lbl = gtk.Label() lbl.set_markup("\ <b>Enqueue</b> files by drag-n-dropping from your file manager or \ using the file chooser below.\n\ <b>Re-order</b> the queue by drag-n-dropping.\n\ <b>Remove</b> scheduled requests by right-clicking.") box.pack_start(scroll, fill=True, expand=True) box.pack_start(lbl, fill=True, expand=False) self.pack1(box, resize=True) lbl.show() box.show() # A file selector in the other side of the pane fsel = gtk.FileChooserWidget() fsel.connect("file_activated", lambda s: self.selected(s)) exp = gtk.Expander("File chooser") exp.add(fsel) self.pack2(exp, resize=False) fsel.show() exp.show() # And the update callback gobject.timeout_add(1000, self.update)
def __init__(self,host='localhost',port=1234,op='mix'): gtk.HBox.__init__(self) scroll = gtk.ScrolledWindow() scroll.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_NEVER) self.add(scroll) scroll.show() self.box = gtk.HBox() scroll.add_with_viewport(self.box) self.box.show() fadebttn = gtk.Button("Fade") self.box.pack_start(fadebttn, False, False) fadebttn.show() # Connect to liquidsoap self.tel = LiqClient(host,port) # Get info about the mixing table self.inputs = filter(lambda x: x!='', re.compile('\s*(\S+)\s*').split(self.tel.command(op+".inputs"))) self.n = len(self.inputs) self.status = [ {} for e in range(self.n) ] # Create widgets remaining,skip,ready,volume,single,fadein,fadeout,selected=[ [] for i in range(8) ] def play_stop(i): self.tel.command(op+".select "+str(i)+" "+ (strbool(not self.status[i]['selected']))) for i in range(self.n): # Add a controller scroll = gtk.ScrolledWindow() scroll.set_policy(gtk.POLICY_NEVER,gtk.POLICY_AUTOMATIC) self.box.pack_start(scroll) vbox = gtk.VBox() scroll.add_with_viewport(vbox) scroll.show() vbox.show() # Status line ready.append(gtk.Label()) vbox.pack_start(ready[i],False,False,10) ready[i].set_markup('<b>'+self.inputs[i]+'</b>') ready[i].set_justify(gtk.JUSTIFY_CENTER) ready[i].show() # Two next buttons will be packed horizontally hbox = gtk.HBox() vbox.pack_start(hbox,False,False) hbox.show() # Play/stop button selected.append(gtk.Button('play/pause')) hbox.pack_start(selected[i]) selected[i].show() selected[i].connect("clicked",lambda b,i:play_stop(i),i) # Skip skip.append(gtk.Button("skip")) skip[i].connect("clicked", lambda button,i: self.tel.command(op+".skip "+str(i)), i) hbox.pack_start(skip[i]) skip[i].show() # Single mode single.append(gtk.CheckButton("Stop at end of track")) vbox.pack_start(single[i],False,False) single[i].show() single[i].connect("clicked", lambda button,i: self.tel.command(op+".single "+str(i)+" "+ (strbool(single[i].get_active()))),i) # Fade in check box fade = gtk.HBox() vbox.pack_start(fade,False,False) lbl = gtk.Label("Fade: ") fade.pack_start(lbl) fadein.append(gtk.CheckButton("in")) fadeout.append(gtk.CheckButton("out")) fade.pack_start(fadein[i]) fade.pack_start(fadeout[i]) lbl.show() fadein[i].show() fadeout[i].show() fade.show() # Volume volume.append(gtk.Adjustment()) vol=gtk.VScale(volume[i]) vbox.pack_start(vol,True,True) vol.set_inverted(True) vol.set_increments(1,10) vol.set_range(0,100) vol.connect("value-changed", lambda l,i: self.tel.command(op+".volume "+str(i)+" "+ str(volume[i].get_value())),i) vol.set_update_policy(gtk.UPDATE_CONTINUOUS) vol.show() # Remaining time remaining.append(gtk.Label("...")) vbox.pack_start(remaining[i],False,False) remaining[i].show() # Fade out all the fadeout_Checkbutton and fade in all the fadein_CB # during duration def fade_in(i): volume[i].set_value(volume[i].get_value() + 1) if volume[i].get_value() == 100: return False #No need to redo else: return True #Need to redo def fade_out(i): volume[i].set_value(volume[i].get_value() - 1) if volume[i].get_value() == 0: return False else: return True def fade_in_out(): duration = 10000 # in ms for i in range(self.n): if fadein[i].get_active() and not fadeout[i].get_active(): sleep_in = duration/(101 - volume[i].get_value()) # print sleep_in gobject.timeout_add(int(sleep_in), lambda i:fade_in(i),i) elif fadeout[i].get_active() and not fadein[i].get_active(): sleep_out = duration/(volume[i].get_value()+1) # print sleep_out gobject.timeout_add(int(sleep_out), lambda i:fade_out(i),i) elif fadeout[i].get_active() and fadein[i].get_active(): pass # TODO fadebttn.connect("clicked", lambda y:fade_in_out()) # TODO charmap dependent keys = list('aqAQw'+'zsZSx'+'edEDc'+'rfRFv'+'tgTGb'+'yhYHn') def keypress(vol,ev): try: # print ev.string, ev.keyval, ev.hardware_keycode i = keys.index(ev.string) if i%5==4: play_stop(i/5) elif i%5==2: fadein[i/5].set_active(not fadein[i/5].get_active()) elif i%5==3: fadeout[i/5].set_active(not fadeout[i/5].get_active()) else: volume[i/5].set_value(volume[i/5].get_value()+((i%5==0 and 1) or -1)) except: pass self.set_events(gtk.gdk.KEY_PRESS_MASK) self.connect("key_press_event",keypress) def update(): for i in range(self.n): a=filter(lambda x: x!='', re.compile('(\S+)=(\S+)\s*').split( self.tel.command(op+".status "+str(i)))) self.status[i]={} for j in range(len(a)/2): self.status[i][a[2*j]]=a[2*j+1] self.status[i]['selected'] = (self.status[i]['selected']=='true') self.status[i]['ready'] = (self.status[i]['ready']=='true') selected[i].set_label( (self.status[i]['selected'] and "pause") or 'start') remaining[i].set_label('End of track: '+self.status[i]['remaining']) volume[i].set_value(int(re.sub('%','',self.status[i]['volume']))) if self.status[i]['selected']: ready[i].set_markup('<b>'+self.inputs[i]+'</b>\n'+ ((self.status[i]['ready'] and 'Currently playing') or 'Waiting for source')) else: ready[i].set_label('<b>'+self.inputs[i]+'</b>\nStopped, '+ ((self.status[i]['ready'] and 'ready') or 'not ready')) return True gobject.timeout_add(1000,update) self.show()
def __init__(self,host='localhost',port=1234,op='request'): gtk.VPaned.__init__(self) self.show() # Connect to liquidsoap self.op = op self.tel = LiqClient(host,port) self.list = View([['rid' , 40], ['status' ,'70'], ['artist' ,'120'], ['title' ,'120'], # Right-align URI because the end is more informative # than the beginning ['initial_uri','300', {'xalign':1.0}]], []) self.update() # Popup menu for requests menu = gtk.Menu() item = gtk.ImageMenuItem(gtk.STOCK_REMOVE) def remove_request(item): model,path = self.list.get_selection().get_selected() self.tel.command(self.op+'.remove '+str(model.get_value(path,0))) item.connect('activate',remove_request) item.show() menu.append(item) def popup(w,event): if event.button==3: menu.popup(None,None,None,event.button,event.time) self.list.connect('button_press_event',popup) # Receive drag-n-drops self.list.enable_model_drag_dest( [('text/uri-list',0,0),ridformat], gtk.gdk.ACTION_DEFAULT) def dnd_receive(w,context,x,y,data,info,time): if data and (data.format != 8 and data.format != ridformat[2]): print "DnD received: Unknown data format! (%d)" % data.format return row = w.get_dest_row_at_pos(x,y) if row: # This is an insertion path, pos = row # Remove the number of resolv(ed|ing) requests to get the pos # in the pending queue pos = path[0]-(len(w.get_model())-self.plen) if pos<0: print "Cannot move outside pending queue!" return if pos >= self.plen-1: pos = -1 if data and data.format == ridformat[2]: rid = int(data.data) self.tel.command('%s.move %d %d' % (self.op,rid,pos)) if data and data.format == 8: for e in data.data.split('\r\n')[:-1]: self.tel.command(self.op+'.insert '+str(pos)+' '+e) else: # This is a push if data and data.format == ridformat[2]: rid = int(data.data) self.tel.command('%s.move %d %d' % (self.op,rid,-1)) if data and data.format == 8: for e in data.data.split('\r\n')[:-1]: self.tel.command(self.op+".push "+urllib.unquote(e)) context.finish(True, False, time) self.list.connect("drag_data_received",dnd_receive) # Emit drag-n-drops self.list.enable_model_drag_source(gtk.gdk.BUTTON1_MASK,[ridformat], gtk.DEST_DEFAULT_ALL) def dnd_emit(w,context,sel,info,time): # Check that format is RID if info==ridformat[2]: model,iter = w.get_selection().get_selected() if iter: sel.set(sel.target,info,str(model.get_value(iter,0))) self.list.connect("drag_data_get",dnd_emit) # Put the list in a scroll scroll = gtk.ScrolledWindow() scroll.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC) scroll.add(self.list) scroll.show() self.list.show() # Pack this to self, with a doc label box = gtk.VBox() lbl = gtk.Label() lbl.set_markup("\ <b>Enqueue</b> files by drag-n-dropping from your file manager or \ using the file chooser below.\n\ <b>Re-order</b> the queue by drag-n-dropping.\n\ <b>Remove</b> scheduled requests by right-clicking.") box.pack_start(scroll,fill=True,expand=True) box.pack_start(lbl,fill=True,expand=False) self.pack1(box,resize=True) lbl.show() box.show() # A file selector in the other side of the pane fsel = gtk.FileChooserWidget() fsel.connect("file_activated", lambda s: self.selected(s)) exp = gtk.Expander("File chooser") exp.add(fsel) self.pack2(exp,resize=False) fsel.show() exp.show() # And the update callback gobject.timeout_add(1000,self.update)
class LiqQueue(gtk.VPaned): def __init__(self, host='localhost', port=1234, op='request'): gtk.VPaned.__init__(self) self.show() # Connect to liquidsoap self.op = op self.tel = LiqClient(host, port) # Fast metadata view: short metadata, right click to see more (TODO) self.list = View( [ ['rid', 40], ['2nd_queue_pos', 80], ['skip', False], ['artist', '120'], ['title', '120'], # Right-align URI because the end is more informative # than the beginning ['initial_uri', '250', { 'xalign': 1.0 }] ], self.queue()) self.list.drag_dest_set(gtk.DEST_DEFAULT_ALL, [('text/uri-list', 0, 0)], gtk.gdk.ACTION_COPY) self.list.connect("drag_data_received", self.drag) self.list.connect("row_activated", self.row_activated) scroll = gtk.ScrolledWindow() scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scroll.add(self.list) # The list is ready, pack it to the top region # together with a help label box = gtk.VBox() lbl = gtk.Label() lbl.set_markup("\ <b>Enqueue</b> files by drag-n-dropping from your file manager or \ using the file chooser below.\n\ <b>Remove/restore</b> scheduled files by double-clicking.") box.pack_start(scroll, fill=True, expand=True) box.pack_start(lbl, fill=True, expand=False) self.pack1(box, resize=True) lbl.show() self.list.show() scroll.show() box.show() fsel = gtk.FileChooserWidget() fsel.connect("file_activated", lambda s: self.selected(s)) exp = gtk.Expander("File chooser") exp.add(fsel) self.pack2(exp, resize=False) fsel.show() exp.show() gobject.timeout_add(1000, self.update) def selected(self, file): self.tel.command(self.op + ".push " + file.get_filename()) def drag(self, w, context, x, y, data, info, time): if data and data.format == 8: for e in data.data.split('\r\n')[:-1]: self.tel.command(self.op + ".push " + urllib.unquote(e)) context.finish(gtk.TRUE, gtk.FALSE, time) def row_activated(self, view, path, column): rid = view.get_model().get_value(view.get_model().get_iter(path), 0) skip = view.get_model().get_value(view.get_model().get_iter(path), 2) if skip: self.tel.command(self.op + ".consider " + str(rid)) else: self.tel.command(self.op + ".ignore " + str(rid)) def queue(self): a = filter( lambda x: x != '', re.compile('(\d+)\s*').split(self.tel.command(self.op + ".queue"))) a = [self.tel.metadata('request.metadata ' + e)[0] for e in a] return a def update(self): self.list.setModel(self.queue()) return 1