5 require 'xmmsclient_glib'
10 class Grux::ClientWin < Gtk::VBox
13 def initialize( address )
14 @server_address = address
15 grux_debug "Initializing window..."
19 grux_debug "Creating connection handle..."
20 @server = Xmms::Client.new( 'Grux' )
23 grux_debug "Connecting to server..."
25 @status[:connected] = self.connect( @server_address )
28 grux_debug "Creating and placing child widgets..."
29 @statusBox = Gtk::HBox.new
30 @mainBox = Gtk::VPaned.new
31 @statusBar = Gtk::Statusbar.new
33 self.pack_start @statusBox, false
34 self.pack_start @mainBox
35 self.pack_end @statusBar, false
39 @currentartistFrame = Gtk::Frame.new( "Artist" ).add( @currentartistLabel = Gtk::Label.new ),
40 @currenttitleFrame = Gtk::Frame.new( "Title" ).add( @currenttitleLabel = Gtk::Label.new ),
41 @currentalbumFrame = Gtk::Frame.new( "Album" ).add( @currentalbumLabel = Gtk::Label.new )
43 @statusWdgts.each { |frame| @statusBox.pack_start frame }
47 @cntrlBox = Gtk::HBox.new,
48 @medialistsWin = Gtk::Notebook.new
50 @mainBox.pack1 @cntrlBox, true, false
51 @mainBox.pack2 @medialistsWin, true, false
55 @cntrlBtnBox = Gtk::VButtonBox.new.set_layout_style( Gtk::ButtonBox::CENTER ),
56 @toolChsr = Gtk::Notebook.new
58 @cntrlBox.pack_start @cntrlBtnBox, false
59 @cntrlBox.pack_start @toolChsr
63 @playBtn = Gtk::ToggleButton.new('_Play', true),
64 @pauseBtn = Gtk::ToggleButton.new('Pa_use', true),
65 @stopBtn = Gtk::ToggleButton.new('_Stop', true),
66 @nextBtn = Gtk::Button.new('_Next'),
67 @prevBtn = Gtk::Button.new('P_revious'),
68 @rndmBtn = Gtk::Button.new('Randomi_ze'),
69 @rptBtn = Gtk::ToggleButton.new('Repeat _All', true),
70 @rpt1Btn = Gtk::ToggleButton.new('Repeat _One', true)
72 @cntrlbtns.each do |button|
73 @cntrlBtnBox.pack_start button
78 [ @extinfoTab = Gtk::VBox.new,
79 @extinfoTabLbl = Gtk::Label.new( "Status" ) ],
80 [ @tageditorTab = Gtk::VBox.new,
81 @tageditorTabLbl = Gtk::Label.new( "Tag Editor" ) ],
82 [ @searchTab = Gtk::VBox.new,
83 @searchTabLbl = Gtk::Label.new( "Search" ) ]
85 @toolTabs.each do |content, label|
86 @toolChsr.append_page( Gtk::ScrolledWindow.new.add_with_viewport( content ), label )
90 @searchTermEntry = Gtk::Entry.new,
91 @startsearchBtn = Gtk::Button.new( "Search" )
93 @searchTabItems.each do |wdgt|
94 @searchTab.pack_start( wdgt )
98 @medialistTextRenderer = Gtk::CellRendererText.new
99 @medialistColumns = [ 'id', 'artist', 'title' ]
102 [ @playlistViewLbl = Gtk::Label.new( "Current Playlist" ),
103 @playlistViewWin = Gtk::ScrolledWindow.new,
104 @playlistView = Gtk::TreeView.new,
105 @playlistStore = Gtk::ListStore.new( String, String, String )
107 [ @medialibViewLbl = Gtk::Label.new( "Medialib" ),
108 @medialibViewWin = Gtk::ScrolledWindow.new,
109 @medialibView = Gtk::TreeView.new,
110 @medialibStore = Gtk::ListStore.new( String, String, String )
112 [ @searchResultViewLbl = Gtk::Label.new( "Search Results" ),
113 @searchResultViewWin = Gtk::ScrolledWindow.new,
114 @searchResultView = Gtk::TreeView.new,
115 @searchResultStore = Gtk::ListStore.new( String, String, String )
119 @medialists.each do |label, window, view, store|
120 @medialistColumns.each_with_index do |tag, index|
121 view.append_column Gtk::TreeViewColumn.new( tag, @medialistTextRenderer, :text => index )
123 view.search_column = 2
124 @medialistsWin.append_page( window.add( view ), label )
132 @playBtnClkdHndlr = @playBtn.signal_connect :clicked do
135 @pauseBtnClkdHndlr = @pauseBtn.signal_connect :clicked do
138 @stopBtnClkdHndlr = @stopBtn.signal_connect :clicked do
141 @nextBtn.signal_connect :clicked do
142 self.step_in_playlist( 1 )
144 @prevBtn.signal_connect :clicked do
145 self.step_in_playlist( -1 )
147 @rndmBtn.signal_connect :clicked do
148 self.randomize_playlist
150 @rptBtnClkdHndlr = @rptBtn.signal_connect :clicked do
151 self.toggle_repeat_all
153 @rpt1BtnClkdHndlr = @rpt1Btn.signal_connect :clicked do
154 self.toggle_repeat_one
157 @startsearchBtn.signal_connect :clicked do
158 searchregex = Regexp.new( /#{@searchTermEntry.text}/ix )
160 @status[:medialib].each_with_index do |songinfo, id|
161 songinfo && songinfo.each do |key, info|
162 info.each do |src, value|
163 if searchregex.match( value.to_s )
170 searchResultIds, searchResult = self.xmmspls_to_lststor( results.uniq!, @searchResultStore )
171 @searchResultView.model = searchResult
172 @medialistsWin.page = 2
176 @playlistView.signal_connect :row_activated do |view, path, column|
177 @server.playlist_set_next( path.to_str.to_i ).notifier do
178 @server.playback_tickle.notifier do
187 @medialibView.signal_connect :row_activated do |view, path, column|
188 songid = @medialibStore.get_iter( path )[0].to_i
189 @server.playlist.add_entry( songid ).notifier do
190 current_row = @playlistStore.append
191 @server.medialib_get_info( songid ).notifier do |songinfo|
192 current_row[0] = songid.to_s
193 current_row[1] = songinfo[:artist] ? songinfo[:artist].first[1] : ""
194 current_row[2] = songinfo[:title] ? songinfo[:title].first[1] : ""
197 @server.playlist_set_next( current_row.to_s.to_i ).notifier do
198 @server.playback_tickle.notifier do
208 if @status[:connected]
209 grux_debug "Connected to server."
210 self.resync_completely
215 def grux_debug( message )
216 exit 1 unless @server_address
217 STDERR.puts "#{@server_address}: #{message}"
220 def connect( address )
222 @server.connect( address )
223 @server.add_to_glib_mainloop
224 rescue Xmms::Client::ClientError => ex
225 grux_debug "Connection failed. Error Code:"
231 @server.broadcast_coll_changed.notifier do |*foo|
232 self.handle_coll_changed foo
235 @server.broadcast_config_value_changed.notifier do |*foo|
236 self.handle_config_value_changed foo
239 @server.broadcast_mediainfo_reader_status.notifier do |*foo|
240 self.handle_mediainfo_reader_status foo
243 @server.broadcast_medialib_entry_added.notifier do |*foo|
244 self.handle_medialib_entry_added foo
247 @server.broadcast_medialib_entry_changed.notifier do |*foo|
248 self.handle_medialib_entry_changed foo
251 @server.broadcast_playback_current_id.notifier do |id|
252 self.handle_playback_current_id id
255 @server.broadcast_playback_status.notifier do |status|
256 self.handle_playback_status status
259 @server.broadcast_playback_volume_changed.notifier do |*foo|
260 self.handle_playback_volume_changed foo
263 @server.broadcast_playlist_changed.notifier do |*foo|
264 self.handle_playlist_changed foo
267 @server.broadcast_playlist_current_pos.notifier do |pos|
268 self.handle_playlist_current_pos pos
271 @server.broadcast_playlist_loaded.notifier do |*foo|
272 self.handle_playlist_loaded foo
275 @server.broadcast_quit.notifier do |*foo|
283 def handle_coll_changed( *foo )
284 grux_debug "Collection changed: #{foo}"
287 def handle_config_value_changed( *foo )
288 grux_debug "Config changed: #{foo}"
291 def handle_mediainfo_reader_status( *foo )
292 grux_debug "Mediainfo reader status changed: #{foo}"
295 def handle_medialib_entry_added( *foo )
296 grux_debug "Medialib entry added: #{foo}"
299 def handle_medialib_entry_changed( *foo )
300 grux_debug "Medialib entry changed: #{foo}"
305 def handle_playback_current_id( id )
306 grux_debug "Current playback ID changed: #{id}"
307 songpath = Gtk::TreePath.new( @status[:playlistids].index id )
309 @playlistView.scroll_to_cell( songpath, nil, true, 0.5, 0.5 )
310 @playlistView.set_cursor( songpath, nil, false )
312 @server.medialib_get_info( id ).notifier do |songinfo|
313 @currentartistLabel.text = ""
314 @currenttitleLabel.text = ""
315 @currentalbumLabel.text = ""
317 @extinfoTab.each do |child| @extinfoTab.remove child end
319 songinfo[:artist] && songinfo[:artist].each_pair do |src, value|
320 @currentartistLabel.text += @currentartistLabel.text.empty? ? value : "\n#{value}"
322 songinfo[:title] && songinfo[:title].each_pair do |src, value|
323 @currenttitleLabel.text += @currenttitleLabel.text.empty? ? value : "\n#{value}"
325 songinfo[:album] && songinfo[:album].each_pair do |src, value|
326 @currentalbumLabel.text += @currentalbumLabel.text.empty? ? value : "\n#{value}"
328 songinfo.each_key do |key|
330 songinfo[key].each_pair do |src,value|
331 infos.empty? || infos += "\n"
332 infos += "#{value} (#{src})"
334 @extinfoTab.pack_end( Gtk::Frame.new( key.to_s ).add( Gtk::Label.new( infos ) ) )
343 def handle_playback_status( status )
344 grux_debug "Current playback status changed: #{status}"
345 @playbackState = status
347 @playBtn.signal_handler_block( @playBtnClkdHndlr ) do
348 @playBtn.active = false
350 @pauseBtn.signal_handler_block( @pauseBtnClkdHndlr ) do
351 @pauseBtn.active = true
353 @stopBtn.signal_handler_block( @stopBtnClkdHndlr ) do
354 @stopBtn.active = false
356 @statusBar.push( @statusBar.get_context_id( "playback" ), "Playback paused" )
358 @playBtn.signal_handler_block( @playBtnClkdHndlr ) do
359 @playBtn.active = true
361 @pauseBtn.signal_handler_block( @pauseBtnClkdHndlr ) do
362 @pauseBtn.active = false
364 @stopBtn.signal_handler_block( @stopBtnClkdHndlr ) do
365 @stopBtn.active = false
367 @statusBar.push( @statusBar.get_context_id( "playback" ), "Playback started" )
369 @playBtn.signal_handler_block( @playBtnClkdHndlr ) do
370 @playBtn.active = false
372 @pauseBtn.signal_handler_block( @pauseBtnClkdHndlr ) do
373 @pauseBtn.active = false
375 @stopBtn.signal_handler_block( @stopBtnClkdHndlr ) do
376 @stopBtn.active = true
378 @statusBar.push( @statusBar.get_context_id( "playback" ), "Playback stopped" )
380 grux_debug "Unknown status #{status}"
386 def handle_playback_volume_changed( *foo )
387 grux_debug "Playback volume changed: #{foo}"
390 def handle_playlist_changed( *foo )
391 grux_debug "Playlist changed: #{foo}"
397 def handle_playlist_current_pos( pos )
398 grux_debug "Current pos changed: #{pos}"
399 #@server.playlist.entries.notifier do |playlist|
400 # @server.medialib_get_info( playlist[pos[:position]] ).notifier do |songinfo|
401 # @nextSongLbl.text = ""
402 # songinfo.each_pair do |key, info|
403 # info.each_pair do |src, value|
404 # @nextSongLbl.text += "[#{src}] #{key} = #{value}\n"
415 def handle_playlist_loaded( *foo )
416 grux_debug "Playlist loaded: #{foo}"
419 def handle_quit( *foo )
420 grux_debug "Server quit: #{foo}"
425 def xmmspls_to_lststor( songids, store )
426 grux_debug "Processing songids: #{songids.join(' ')}."
430 current_row = store.append
431 @server.medialib_get_info( id ).notifier do |songinfo|
432 current_row[0] = id.to_s
434 songinfos[id] = songinfo
435 current_row[1] = songinfo[:artist] ? songinfo[:artist].first[1] : ""
436 current_row[2] = songinfo[:title] ? songinfo[:title].first[1] : ""
441 return [songinfos, store]
446 @server.playback_start.notifier do |res|
447 @playBtn.signal_handler_block( @playBtnClkdHndlr ) do
448 @playBtn.active = true
450 @pauseBtn.signal_handler_block( @pauseBtnClkdHndlr ) do
451 @pauseBtn.active = false
453 @stopBtn.signal_handler_block( @stopBtnClkdHndlr ) do
454 @stopBtn.active = false
456 @statusBar.push( @statusBar.get_context_id( "playback" ), "Started playback" )
463 @server.playback_pause.notifier do |res|
464 @playBtn.signal_handler_block( @playBtnClkdHndlr ) do
465 @playBtn.active = false
467 @pauseBtn.signal_handler_block( @pauseBtnClkdHndlr ) do
468 @pauseBtn.active = true
470 @stopBtn.signal_handler_block( @stopBtnClkdHndlr ) do
471 @stopBtn.active = false
473 @statusBar.push( @statusBar.get_context_id( "playback" ), "Paused playback" )
480 @server.playback_stop.notifier do |res|
481 @playBtn.signal_handler_block( @playBtnClkdHndlr ) do
482 @playBtn.active = false
484 @pauseBtn.signal_handler_block( @pauseBtnClkdHndlr ) do
485 @pauseBtn.active = false
487 @stopBtn.signal_handler_block( @stopBtnClkdHndlr ) do
488 @stopBtn.active = true
490 @statusBar.push( @statusBar.get_context_id( "playback" ), "Stopped playback" )
497 if @playbackState == 1
498 @server.playback_pause.notifier do |res|
499 @statusBar.push( @statusBar.get_context_id( "playback" ), "Paused playback" )
502 elsif @playbackState == ( 0 || 2 )
503 @server.playback_start.notifier do |res|
504 @statusBar.push( @statusBar.get_context_id( "playback" ), "Started playback" )
508 grux_debug "Playbackstate unknown"
509 @server.playback_start.notifier do |res|
510 @statusBar.push( @statusBar.get_context_id( "playback" ), "Started playback" )
517 def step_in_playlist( offset = 1 )
518 @server.playlist_set_next_rel( offset ).notifier do
519 @server.playback_tickle.notifier do |res|
520 @statusBar.push( @statusBar.get_context_id( "playback" ), "Skipped playback by #{offset} song#{offset.abs >= 1 ? "s" : ''}" )
528 def handle_repeat_all_state( state )
529 @currentconfig[:"playlist.repeat_all"] = ( state ? "1" : "0" )
530 @rptBtn.signal_handler_block( @rptBtnClkdHndlr ) do
531 @rptBtn.active = state
536 def handle_repeat_one_state( state )
537 @currentconfig[:"playlist.repeat_one"] = ( state ? "1" : "0" )
538 @rpt1Btn.signal_handler_block( @rpt1BtnClkdHndlr ) do
539 @rpt1Btn.active = state
544 def toggle_repeat_all
545 if @currentconfig[:"playlist.repeat_all"] == "1"
546 @server.config_set_value( "playlist.repeat_all", "0" ).notifier do |res|
547 @server.config_get_value( "playlist.repeat_all" ).notifier do |value|
548 self.handle_repeat_all_state( value == "1" ? true : false )
553 elsif @currentconfig[:"playlist.repeat_all"] == "0"
554 @server.config_set_value( "playlist.repeat_all", "1" ).notifier do |res|
555 @server.config_get_value( "playlist.repeat_all" ).notifier do |value|
556 self.handle_repeat_all_state( value == "1" ? true : false )
562 grux_debug "Unknown state: #{@currentconfig[:"playlist.repeat_all"]}"
568 def toggle_repeat_one
569 if @currentconfig[:"playlist.repeat_one"] == "1"
570 @server.config_set_value( "playlist.repeat_one", "0" ).notifier do |res|
571 @server.config_get_value( "playlist.repeat_one" ).notifier do |value|
572 self.handle_repeat_one_state( value == "1" ? true : false )
577 elsif @currentconfig[:"playlist.repeat_one"] == "0"
578 @server.config_set_value( "playlist.repeat_one", "1" ).notifier do |res|
579 @server.config_get_value( "playlist.repeat_one" ).notifier do |value|
580 self.handle_repeat_one_state( value == "1" ? true : false )
586 grux_debug "Unknown state: #{@currentconfig[:"playlist.repeat_one"]}"
593 grux_debug "Retrieving playlist..."
594 @server.playlist.entries.notifier do |playlistids|
595 grux_debug "Playlist-IDs: #{playlistids.join(' ')}"
596 @status[:playlistids] = playlistids
597 @status[:playlist], @playlistView.model = self.xmmspls_to_lststor( playlistids, @playlistStore )
604 grux_debug "Retrieving medialib..."
605 @server.coll_query_ids( Xmms::Collection.universe ).notifier do |medialibids|
606 grux_debug "Medialib-IDs: #{medialibids.join(' ')}"
607 @status[:medialibids] = medialibids
608 @status[:medialib], @medialibView.model = self.xmmspls_to_lststor( medialibids, @medialibStore )
615 grux_debug "Retrieving currently playing song..."
616 @server.playback_current_id.notifier do |id|
618 grux_debug "XXX-UNCLEAR: Songid is 0, playlist may be empty?"
620 self.handle_playback_current_id id
628 grux_debug "Retrieving configuration..."
629 @server.config_list_values.notifier do |res|
631 res.each_pair do |key, value|
632 @currentconfig[key] = value
635 @rpt1Btn.signal_handler_block( @rpt1BtnClkdHndlr ) do
636 @rpt1Btn.active = if( @currentconfig[:"playlist.repeat_one"] == "1" )
642 @rptBtn.signal_handler_block( @rptBtnClkdHndlr ) do
643 @rptBtn.active = if @currentconfig[:"playlist.repeat_all"] == "1"
655 grux_debug "Retrieving collections..."
656 @server.coll_list.notifier do |colls|
657 colls.each do |collname|
658 @server.coll_get( collname ).notifier do |coll|
659 @server.coll_query_ids( coll ).notifier do |ids|
661 grux_debug ids.join(' ')
672 def resync_completely
673 grux_debug "Doing a complete resync:"
676 self.get_current_song
679 grux_debug "Complete resync done."
684 class Grux::MainWin < Gtk::Window
686 STDERR.puts "Main: Initializing window..."
688 STDERR.puts "Main: Done"
690 STDERR.puts "Main: Populating window..."
691 @mainCont = Gtk::VBox.new
692 @btnCont = Gtk::HBox.new
693 @clntCont = Gtk::Notebook.new
695 @cnnctInput = Gtk::ComboBoxEntry.new
696 @cnnctBtn = Gtk::Button.new "_Connect"
697 @quitBtn = Gtk::Button.new "_Quit"
700 STDERR.puts "Main: Done"
702 STDERR.puts "Main: XXX-FEATURE: load (and save) previously used entries for: "
703 STDERR.puts "Main: Populating server list..."
704 @cnnctInput.append_text 'tcp://127.0.0.1:9667'
705 STDERR.puts "Main: Done"
707 STDERR.puts "Main: Trying to connect..."
708 @cnnctBtnHndlr = @cnnctBtn.signal_connect :clicked do
709 if not @cnnctInput.active_text.empty?
710 server_address = @cnnctInput.active_text
712 server_address = ENV['XMMS_PATH']
714 @clntCont.append_page( Grux::ClientWin.new( server_address ), Gtk::Label.new( server_address ) )
716 STDERR.puts "Main: Done"
718 @winDstryHndlr = self.signal_connect :destroy do
721 @quitBtnHndlr = @quitBtn.signal_connect :clicked do
725 STDERR.puts "Main: Packing and showing window content..."
726 @btnCont.pack_start @cnnctInput
727 @btnCont.pack_start @cnnctBtn
728 @btnCont.pack_end @quitBtn
730 @mainCont.pack_start @btnCont, false
731 @mainCont.pack_end @clntCont
735 STDERR.puts "Main: Done"
740 STDERR.puts "Creating main client window:"
741 gruxWin = Grux::MainWin.new
742 STDERR.puts "Starting GTK main loop."