3 #$: << File.expand_path(File.dirname(FILE) + “/../lib”))
5 #require 'libgruxclient'
8 require 'xmmsclient_glib'
13 class Grux::ClientWin < Gtk::VBox
16 def initialize( server_address )
20 @server = Xmms::Client.new( 'Grux' )
23 @status[:connected] = self.connect( server_address )
25 @statusBox = Gtk::HBox.new
26 @mainBox = Gtk::VPaned.new
27 @statusBar = Gtk::Statusbar.new
29 self.pack_start @statusBox, false
30 self.pack_start @mainBox
31 self.pack_end @statusBar, false
35 @currentartistFrame = Gtk::Frame.new( "Artist" ).add( @currentartistLabel = Gtk::Label.new ),
36 @currenttitleFrame = Gtk::Frame.new( "Title" ).add( @currenttitleLabel = Gtk::Label.new ),
37 @currentalbumFrame = Gtk::Frame.new( "Album" ).add( @currentalbumLabel = Gtk::Label.new )
39 @statusWdgts.each { |frame| @statusBox.pack_start frame }
43 @cntrlBox = Gtk::HBox.new,
44 @medialistsWin = Gtk::Notebook.new
46 @mainBox.pack1 @cntrlBox, true, false
47 @mainBox.pack2 @medialistsWin, true, false
51 @cntrlBtnBox = Gtk::VButtonBox.new.set_layout_style( Gtk::ButtonBox::CENTER ),
52 @toolChsr = Gtk::Notebook.new
54 @cntrlBox.pack_start @cntrlBtnBox, false
55 @cntrlBox.pack_start @toolChsr
59 @playBtn = Gtk::ToggleButton.new('_Play', true),
60 @pauseBtn = Gtk::ToggleButton.new('Pa_use', true),
61 @stopBtn = Gtk::ToggleButton.new('_Stop', true),
62 @nextBtn = Gtk::Button.new('_Next'),
63 @prevBtn = Gtk::Button.new('P_revious'),
64 @rndmBtn = Gtk::Button.new('Randomi_ze'),
65 @rptBtn = Gtk::ToggleButton.new('Repeat _All', true),
66 @rpt1Btn = Gtk::ToggleButton.new('Repeat _One', true)
68 @cntrlbtns.each do |button|
69 @cntrlBtnBox.pack_start button
74 [ @extinfoTab = Gtk::VBox.new,
75 @extinfoTabLbl = Gtk::Label.new( "Status" ) ],
76 [ @tageditorTab = Gtk::VBox.new,
77 @tageditorTabLbl = Gtk::Label.new( "Tag Editor" ) ],
78 [ @searchTab = Gtk::VBox.new,
79 @searchTabLbl = Gtk::Label.new( "Search" ) ]
81 @toolTabs.each do |content, label|
82 @toolChsr.append_page( Gtk::ScrolledWindow.new.add_with_viewport( content ), label )
86 @searchTermEntry = Gtk::Entry.new,
87 @startsearchBtn = Gtk::Button.new( "Search" )
89 @searchTabItems.each do |wdgt|
90 @searchTab.pack_start( wdgt )
94 @medialistTextRenderer = Gtk::CellRendererText.new
95 @medialistColumns = [ 'id', 'artist', 'title' ]
98 [ @playlistViewLbl = Gtk::Label.new( "Current Playlist" ),
99 @playlistViewWin = Gtk::ScrolledWindow.new,
100 @playlistView = Gtk::TreeView.new,
101 @playlistStore = Gtk::ListStore.new( String, String, String )
103 [ @medialibViewLbl = Gtk::Label.new( "Medialib" ),
104 @medialibViewWin = Gtk::ScrolledWindow.new,
105 @medialibView = Gtk::TreeView.new,
106 @medialibStore = Gtk::ListStore.new( String, String, String )
108 [ @searchResultViewLbl = Gtk::Label.new( "Search Results" ),
109 @searchResultViewWin = Gtk::ScrolledWindow.new,
110 @searchResultView = Gtk::TreeView.new,
111 @searchResultStore = Gtk::ListStore.new( String, String, String )
115 @medialists.each do |label, window, view, store|
116 @medialistColumns.each_with_index do |tag, index|
117 view.append_column Gtk::TreeViewColumn.new( tag, @medialistTextRenderer, :text => index )
119 view.search_column = 2
120 @medialistsWin.append_page( window.add( view ), label )
127 @playBtnClkdHndlr = @playBtn.signal_connect :clicked do
130 @pauseBtnClkdHndlr = @pauseBtn.signal_connect :clicked do
133 @stopBtnClkdHndlr = @stopBtn.signal_connect :clicked do
136 @nextBtn.signal_connect :clicked do
137 self.step_in_playlist( 1 )
139 @prevBtn.signal_connect :clicked do
140 self.step_in_playlist( -1 )
142 @rndmBtn.signal_connect :clicked do
143 self.randomize_playlist
145 @rptBtnClkdHndlr = @rptBtn.signal_connect :clicked do
146 self.toggle_repeat_all
148 @rpt1BtnClkdHndlr = @rpt1Btn.signal_connect :clicked do
149 self.toggle_repeat_one
152 @startsearchBtn.signal_connect :clicked do
153 searchregex = Regexp.new( /#{@searchTermEntry.text}/ix )
155 @status[:medialib].each_with_index do |songinfo, id|
156 songinfo && songinfo.each do |key, info|
157 info.each do |src, value|
158 if searchregex.match( value.to_s )
165 searchResultIds, searchResult = self.xmmspls_to_lststor( results.uniq!, @searchResultStore )
166 @searchResultView.model = searchResult
170 @playlistView.signal_connect :row_activated do |view, path, column|
171 @server.playlist_set_next( path.to_str.to_i ).notifier do
172 @server.playback_tickle.notifier do
181 @medialibView.signal_connect :row_activated do |view, path, column|
182 songid = @medialibStore.get_iter( path )[0].to_i
183 @server.playlist.add_entry( songid ).notifier do
184 current_row = @playlistStore.append
185 @server.medialib_get_info( songid ).notifier do |songinfo|
186 current_row[0] = songid.to_s
187 current_row[1] = songinfo[:artist] ? songinfo[:artist].first[1] : ""
188 current_row[2] = songinfo[:title] ? songinfo[:title].first[1] : ""
191 @server.playlist_set_next( current_row.to_s.to_i ).notifier do
192 @server.playback_tickle.notifier do
202 if @status[:connected]
209 def connect( address )
211 @server.connect( address )
212 @server.add_to_glib_mainloop
213 rescue Xmms::Client::ClientError => ex
214 warn "Connection failed. Error Code:"
220 @server.broadcast_coll_changed.notifier do |*foo|
221 self.handle_coll_changed foo
224 @server.broadcast_config_value_changed.notifier do |*foo|
225 self.handle_config_value_changed foo
228 @server.broadcast_mediainfo_reader_status.notifier do |*foo|
229 self.handle_mediainfo_reader_status foo
232 @server.broadcast_medialib_entry_added.notifier do |*foo|
233 self.handle_medialib_entry_added foo
236 @server.broadcast_medialib_entry_changed.notifier do |*foo|
237 self.handle_medialib_entry_changed foo
240 @server.broadcast_playback_current_id.notifier do |id|
241 self.handle_playback_current_id id
244 @server.broadcast_playback_status.notifier do |status|
245 self.handle_playback_status status
248 @server.broadcast_playback_volume_changed.notifier do |*foo|
249 self.handle_playback_volume_changed foo
252 @server.broadcast_playlist_changed.notifier do |*foo|
253 self.handle_playlist_changed foo
256 @server.broadcast_playlist_current_pos.notifier do |pos|
257 self.handle_playlist_current_pos pos
260 @server.broadcast_playlist_loaded.notifier do |*foo|
261 self.handle_playlist_loaded foo
264 @server.broadcast_quit.notifier do |*foo|
272 def handle_coll_changed( *foo )
273 p "Collection changed: #{foo}"
276 def handle_config_value_changed( *foo )
277 p "Config changed: #{foo}"
280 def handle_mediainfo_reader_status( *foo )
281 p "Mediainfo reader status changed: #{foo}"
284 def handle_medialib_entry_added( *foo )
285 p "Medialib entry added: #{foo}"
288 def handle_medialib_entry_changed( *foo )
289 p "Medialib entry changed: #{foo}"
294 def handle_playback_current_id( id )
295 p "Current playback ID changed: #{id}"
296 songpath = Gtk::TreePath.new( @status[:playlistids].index id )
298 @playlistView.scroll_to_cell( songpath, nil, true, 0.5, 0.5 )
299 @playlistView.set_cursor( songpath, nil, false )
301 @server.medialib_get_info( id ).notifier do |songinfo|
302 @currentartistLabel.text = ""
303 @currenttitleLabel.text = ""
304 @currentalbumLabel.text = ""
306 @extinfoTab.each do |child| @extinfoTab.remove child end
308 songinfo[:artist] && songinfo[:artist].each_pair do |src, value|
309 @currentartistLabel.text += @currentartistLabel.text.empty? ? value : "\n#{value}"
311 songinfo[:title] && songinfo[:title].each_pair do |src, value|
312 @currenttitleLabel.text += @currenttitleLabel.text.empty? ? value : "\n#{value}"
314 songinfo[:album] && songinfo[:album].each_pair do |src, value|
315 @currentalbumLabel.text += @currentalbumLabel.text.empty? ? value : "\n#{value}"
317 songinfo.each_key do |key|
319 songinfo[key].each_pair do |src,value|
320 infos.empty? || infos += "\n"
321 infos += "#{value} (#{src})"
323 @extinfoTab.pack_end( Gtk::Frame.new( key.to_s ).add( Gtk::Label.new( infos ) ) )
332 def handle_playback_status( status )
333 p "Current playback status changed: #{status}"
334 @playbackState = status
339 def handle_playback_volume_changed( *foo )
340 p "Playback volume changed: #{foo}"
343 def handle_playlist_changed( *foo )
344 p "Playlist changed: #{foo}"
349 def handle_playlist_current_pos( pos )
350 p "Current pos changed: #{pos}"
351 #@server.playlist.entries.notifier do |playlist|
352 # @server.medialib_get_info( playlist[pos[:position]] ).notifier do |songinfo|
353 # @nextSongLbl.text = ""
354 # songinfo.each_pair do |key, info|
355 # info.each_pair do |src, value|
356 # @nextSongLbl.text += "[#{src}] #{key} = #{value}\n"
367 def handle_playlist_loaded( *foo )
368 p "Playlist loaded: #{foo}"
371 def handle_quit( *foo )
372 p "Server quit: #{foo}"
377 def xmmspls_to_lststor( songids, store )
381 current_row = store.append
382 @server.medialib_get_info( id ).notifier do |songinfo|
383 songinfos[id] = songinfo
384 current_row[0] = id.to_s
385 current_row[1] = songinfo[:artist] ? songinfo[:artist].first[1] : ""
386 current_row[2] = songinfo[:title] ? songinfo[:title].first[1] : ""
390 return [songinfos, store]
396 @server.playback_start.notifier do |res|
397 @playBtn.signal_handler_block( @playBtnClkdHndlr ) do
398 @playBtn.active = true
400 @pauseBtn.signal_handler_block( @pauseBtnClkdHndlr ) do
401 @pauseBtn.active = false
403 @stopBtn.signal_handler_block( @stopBtnClkdHndlr ) do
404 @stopBtn.active = false
406 @statusBar.push( @statusBar.get_context_id( "playback" ), "Started playback" )
413 @server.playback_pause.notifier do |res|
414 @playBtn.signal_handler_block( @playBtnClkdHndlr ) do
415 @playBtn.active = false
417 @pauseBtn.signal_handler_block( @pauseBtnClkdHndlr ) do
418 @pauseBtn.active = true
420 @stopBtn.signal_handler_block( @stopBtnClkdHndlr ) do
421 @stopBtn.active = false
423 @statusBar.push( @statusBar.get_context_id( "playback" ), "Paused playback" )
430 @server.playback_stop.notifier do |res|
431 @playBtn.signal_handler_block( @playBtnClkdHndlr ) do
432 @playBtn.active = false
434 @pauseBtn.signal_handler_block( @pauseBtnClkdHndlr ) do
435 @pauseBtn.active = false
437 @stopBtn.signal_handler_block( @stopBtnClkdHndlr ) do
438 @stopBtn.active = true
440 @statusBar.push( @statusBar.get_context_id( "playback" ), "Stopped playback" )
447 if @playbackState == 1
448 @server.playback_pause.notifier do |res|
449 @statusBar.push( @statusBar.get_context_id( "playback" ), "Paused playback" )
452 elsif @playbackState == ( 0 || 2 )
453 @server.playback_start.notifier do |res|
454 @statusBar.push( @statusBar.get_context_id( "playback" ), "Started playback" )
458 warn "Playbackstate unknown"
459 @server.playback_start.notifier do |res|
460 @statusBar.push( @statusBar.get_context_id( "playback" ), "Started playback" )
467 def step_in_playlist( offset = 1 )
468 @server.playlist_set_next_rel( offset ).notifier do
469 @server.playback_tickle.notifier do |res|
470 @statusBar.push( @statusBar.get_context_id( "playback" ), "Skipped playback by #{offset} song#{offset.abs >= 1 ? "s" : ''}" )
478 def handle_repeat_all_state( state )
479 @currentconfig[:"playlist.repeat_all"] = ( state ? "1" : "0" )
480 @rptBtn.signal_handler_block( @rptBtnClkdHndlr ) do
481 @rptBtn.active = state
486 def handle_repeat_one_state( state )
487 @currentconfig[:"playlist.repeat_one"] = ( state ? "1" : "0" )
488 @rpt1Btn.signal_handler_block( @rpt1BtnClkdHndlr ) do
489 @rpt1Btn.active = state
494 def toggle_repeat_all
495 if @currentconfig[:"playlist.repeat_all"] == "1"
496 @server.config_set_value( "playlist.repeat_all", "0" ).notifier do |res|
497 @server.config_get_value( "playlist.repeat_all" ).notifier do |value|
498 self.handle_repeat_all_state( value == "1" ? true : false )
503 elsif @currentconfig[:"playlist.repeat_all"] == "0"
504 @server.config_set_value( "playlist.repeat_all", "1" ).notifier do |res|
505 @server.config_get_value( "playlist.repeat_all" ).notifier do |value|
506 self.handle_repeat_all_state( value == "1" ? true : false )
512 warn "Unknown state: #{@currentconfig[:"playlist.repeat_all"]}"
518 def toggle_repeat_one
519 if @currentconfig[:"playlist.repeat_one"] == "1"
520 @server.config_set_value( "playlist.repeat_one", "0" ).notifier do |res|
521 @server.config_get_value( "playlist.repeat_one" ).notifier do |value|
522 self.handle_repeat_one_state( value == "1" ? true : false )
527 elsif @currentconfig[:"playlist.repeat_one"] == "0"
528 @server.config_set_value( "playlist.repeat_one", "1" ).notifier do |res|
529 @server.config_get_value( "playlist.repeat_one" ).notifier do |value|
530 self.handle_repeat_one_state( value == "1" ? true : false )
536 warn "Unknown state: #{@currentconfig[:"playlist.repeat_one"]}"
543 @server.playlist.entries.notifier do |playlistids|
544 @status[:playlistids] = playlistids
545 @status[:playlist], @playlistView.model = self.xmmspls_to_lststor( playlistids, @playlistStore )
549 @server.coll_query_ids( Xmms::Collection.universe ).notifier do |medialibids|
550 @status[:medialibids] = medialibids
551 @status[:medialib], @medialibView.model = self.xmmspls_to_lststor( medialibids, @medialibStore )
555 @server.playback_current_id.notifier do |id|
556 self.handle_playback_current_id id
560 @server.config_list_values.notifier do |res|
562 res.each_pair do |key, value|
563 @currentconfig[key] = value
566 @rpt1Btn.signal_handler_block( @rpt1BtnClkdHndlr ) do
567 @rpt1Btn.active = if( @currentconfig[:"playlist.repeat_one"] == "1" )
573 @rptBtn.signal_handler_block( @rptBtnClkdHndlr ) do
574 @rptBtn.active = if @currentconfig[:"playlist.repeat_all"] == "1"
583 @server.coll_list.notifier do |colls|
584 colls.each do |collname|
585 @server.coll_get( collname ).notifier do |coll|
586 @server.coll_query_ids( coll ).notifier do |ids|
602 class Grux::MainWin < Gtk::Window
606 @mainCont = Gtk::VBox.new
607 @btnCont = Gtk::HBox.new
608 @clntCont = Gtk::Notebook.new
610 @cnnctInput = Gtk::ComboBoxEntry.new
611 @cnnctBtn = Gtk::Button.new "_Connect"
612 @quitBtn = Gtk::Button.new "_Quit"
616 @winDstryHndlr = self.signal_connect :destroy do
620 @cnnctInput.append_text 'tcp://127.0.0.1:9667'
622 @cnnctBtnHndlr = @cnnctBtn.signal_connect :clicked do
623 if not @cnnctInput.active_text.empty?
624 server_address = @cnnctInput.active_text
626 server_address = ENV['XMMS_PATH']
629 @clntCont.append_page( Grux::ClientWin.new( server_address ), Gtk::Label.new( server_address ) )
631 @quitBtnHndlr = @quitBtn.signal_connect :clicked do
635 @btnCont.pack_start @cnnctInput
636 @btnCont.pack_start @cnnctBtn
637 @btnCont.pack_end @quitBtn
639 @mainCont.pack_start @btnCont, false
640 @mainCont.pack_end @clntCont
648 gruxWin = Grux::MainWin.new