Vesion 019
[rox-musicbox.git] / musicbox.py
blobe8647195662b39dad6dcef4c83308609a1dbb896
1 """
2 musicbox.py (play either ogg or mp3 files)
4 Copyright 2004 Kenneth Hayber <khayber@socal.rr.com>
5 All rights reserved.
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License.
11 This program is distributed in the hope that it will be useful
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 """
21 from __future__ import generators
23 import os, sys, re, string, threading, pango, gtk, gobject
24 from threading import *
26 import rox
27 from rox import Menu, app_options, loading, saving, InfoWin, OptionsBox
28 from rox.options import Option
30 try:
31 import player, playlist, playlistui, xsoap
32 except:
33 rox.report_exception()
36 #Who am I and how did I get here?
37 APP_NAME = "MusicBox"
38 APP_DIR = rox.app_dir
41 #View options
42 VIEW_DEFAULT_SIZE = (200, 100)
44 #Toolbar button indexes
45 BTN_PREV = 0
46 BTN_PLAY = 1
47 BTN_STOP = 2
48 BTN_NEXT = 3
49 BTN_REPEAT = 4
50 BTN_SHUFFLE = 5
51 BTN_PLAYLIST = 6
52 BTN_OPTIONS = 7
55 #Bitmaps (potentially) used in this application
56 factory = gtk.IconFactory()
57 for name in [
58 'media-eject', 'media-ffwd', 'media-next',
59 'media-pause', 'media-play', 'media-prev',
60 'media-record', 'media-repeat', 'media-rewind',
61 'media-shuffle', 'media-stop', 'media-track',
62 'volume-max', 'volume-medium', 'volume-min',
63 'volume-mute', 'volume-zero',
65 path = os.path.join(rox.app_dir, "images", name + ".png")
66 pixbuf = gtk.gdk.pixbuf_new_from_file(path)
67 if not pixbuf:
68 print >>sys.stderr, "Can't load stock icon '%s'" % name
69 gtk.stock_add([(name, name, 0, 0, "")])
70 factory.add(name, gtk.IconSet(pixbuf = pixbuf))
71 factory.add_default()
74 #Options.xml processing
75 rox.setup_app_options(APP_NAME)
77 #assume that everyone puts their music in ~/Music
78 LIBRARY = Option('library', os.path.expanduser('~')+'/Music')
80 #how to parse each library leaf to get artist, album, title...
81 LIBRARY_RE = Option('library_re', '^.*/(?P<artist>.*)/(?P<album>.*)/(?P<title>.*)')
83 #the ao driver type you want to use (esd, oss, alsa, alsa09, ...)
84 DRIVER_ID = Option('driver_id', 'esd')
86 SHUFFLE = Option('shuffle', 0)
87 REPEAT = Option('repeat', 0)
89 #Don't replay any of the last n songs in shuffle mode
90 SHUFFLE_CACHE_SIZE = Option('shuffle_cache', 10)
92 #Buffer size used by audio device read/write
93 AUDIO_BUFFER_SIZE = Option('audio_buffer', 4096)
95 #Eye candy
96 SONG_FONT = Option('song_font', None)
97 BASE_FONT = Option('base_font', None)
98 BG_COLOR = Option('bg_color', '#A6A699')
99 FG_COLOR = Option('fg_color', '#000000')
101 #Show/Hide settings
102 SH_TOOLBAR = Option('toolbar', True)
103 SH_VOLUME = Option('volume', True)
104 SH_SEEKBAR = Option('seekbar', True)
106 #Other GUI details
107 WORDWRAP = Option('word_wrap', False)
108 TIMEDISPLAY = Option('time_display', 0)
110 def build_tool_options(box, node, label, option):
111 """Custom Option widget to allow hide/display of each mixer control"""
113 type = gtk.TOOLBAR_CHILD_TOGGLEBUTTON
114 size = gtk.ICON_SIZE_SMALL_TOOLBAR
116 toolbar = gtk.Toolbar()
117 toolbar.set_style(gtk.TOOLBAR_ICONS)
119 buttons = [
120 (_("Prev"), gtk.image_new_from_stock('media-prev', size)),
121 (_("Play"), gtk.image_new_from_stock('media-play', size)),
122 (_("Stop"), gtk.image_new_from_stock('media-stop', size)),
123 (_("Next"), gtk.image_new_from_stock('media-next', size)),
124 (_("Repeat"), gtk.image_new_from_stock('media-repeat', size)),
125 (_("Shuffle"), gtk.image_new_from_stock('media-shuffle', size)),
126 (_("Playlist"), gtk.image_new_from_stock(gtk.STOCK_INDEX, size)),
127 (_("Options"), gtk.image_new_from_stock(gtk.STOCK_PREFERENCES, size)),
130 done = False #don't try to update things until we're done building the control
131 def item_changed(thing):
132 if done:
133 box.check_widget(option)
135 controls = []
136 for (text, image) in buttons:
137 controls.append(toolbar.insert_element(type, None, text, text, None, image,
138 item_changed, None, -1))
140 def get_values():
141 value = 0
142 for i in range(len(controls)):
143 if controls[i].get_active():
144 value |= (1 << i)
145 return value
146 def set_values(): pass
147 box.handlers[option] = (get_values, set_values)
149 #initialize the buttons states
150 for i in range(len(controls)):
151 if option.int_value & (1 << i):
152 controls[i].set_active(True)
153 done = True #OK, updates activated.
155 box.may_add_tip(toolbar, node)
156 return [toolbar]
158 OptionsBox.widget_registry['tool_options'] = build_tool_options
159 TOOLOPTIONS = Option('toolbar_disable', -1)
161 rox.app_options.notify()
163 DND_TYPES = ['audio/x-mp3' 'application/ogg' 'inode/directory']
165 class MusicBox(rox.Window, loading.XDSLoader):
166 """A Music Player for mp3 and ogg - main class"""
167 time_string = ''
168 _shuffle = False
169 _repeat = False
172 def __init__(self):
173 """Constructor for MusicBox"""
174 rox.Window.__init__(self)
175 loading.XDSLoader.__init__(self, DND_TYPES)
177 # Main window settings
178 self.set_title(APP_NAME)
179 self.set_role("MainWindow")
180 self.set_border_width(0)
181 self.set_default_size(VIEW_DEFAULT_SIZE[0], VIEW_DEFAULT_SIZE[1])
182 self.set_position(gtk.WIN_POS_MOUSE)
184 # Notifications
185 rox.app_options.add_notify(self.get_options)
186 self.connect('delete_event', self.delete_event)
187 self.connect('window-state-event', self.window_state_event)
188 self.connect('drag-motion', self.xds_drag_motion)
190 # Set some defaults
191 self.replace_library = False
192 self.library = LIBRARY.value.split(':')
193 self.playlist = None
194 self.playlistUI = None
195 self.current_song = None
197 self.shuffle = bool(SHUFFLE.int_value)
198 self.repeat = bool(REPEAT.int_value)
200 # Build and Init everything
201 self.build_menu()
202 self.build_toolbar()
203 self.build_labels()
204 self.build_misc()
205 self.set_fonts()
206 self.set_colors()
208 # Pack and show widgets
209 self.vbox = gtk.VBox()
210 self.hbox = gtk.HBox()
211 self.add(self.vbox)
212 self.vbox.add(self.hbox)
214 self.hbox.pack_start(self.display, True, True, 0)
215 self.hbox.pack_end(self.volume_control, False, True, 0)
216 self.vbox.pack_end(self.toolbar, False, True, 0)
217 self.vbox.pack_end(self.seek_bar_control, False, True, 0)
219 self.vbox.show_all()
220 self.show_hide_controls()
221 self.show_hide_buttons()
222 self.show()
224 #Start xmlrpc server
225 self.server()
227 self.playlist = playlist.Playlist(SHUFFLE_CACHE_SIZE.int_value, LIBRARY_RE.value)
228 self.player = player.Player(DRIVER_ID.value,
229 AUDIO_BUFFER_SIZE.int_value)
230 self.foo = Thread(name='player', target=self.player.run)
231 self.foo.setDaemon(True)
232 self.foo.start()
233 self.volume.set_value(self.player.get_volume())
235 if len(sys.argv) > 1:
236 self.load_args(sys.argv[1:], True)
237 else:
238 self.load_args([], False)
240 gobject.timeout_add(500, self.display_update)
243 def build_menu(self):
244 self.add_events(gtk.gdk.BUTTON_PRESS_MASK)
245 self.connect('button-press-event', self.button_press)
246 Menu.set_save_name(APP_NAME)
247 self.menu = Menu.Menu('main', [
248 Menu.Action(_("Play")+'\/'+_("Pause"), 'play_pause', '', 'media-play'),
249 Menu.Action(_("Stop"), 'stop', '', 'media-stop'),
250 Menu.Separator(),
252 Menu.Action(_("Back"), 'prev', '', 'media-prev'),
253 Menu.Action(_("Next"), 'next', '', 'media-next'),
254 Menu.Separator(),
256 Menu.ToggleItem(_("Shuffle"), 'shuffle'),
257 Menu.ToggleItem(_("Repeat"), 'repeat'),
258 Menu.Separator(),
260 Menu.Action(_("Save"), 'save', '', gtk.STOCK_SAVE),
261 Menu.Action(_("Playist"), 'show_playlist', '', gtk.STOCK_INDEX),
262 Menu.Action(_("Options"), 'show_options', '', gtk.STOCK_PREFERENCES),
263 Menu.Action(_('Info'), 'get_info', '', gtk.STOCK_DIALOG_INFO),
264 Menu.Separator(),
266 Menu.Action(_("Quit"), 'close', '', gtk.STOCK_CLOSE),
268 self.menu.attach(self,self)
271 def build_toolbar(self):
272 self.toolbar = gtk.Toolbar()
273 self.toolbar.set_style(gtk.TOOLBAR_ICONS)
275 size = gtk.ICON_SIZE_SMALL_TOOLBAR
276 type1 = gtk.TOOLBAR_CHILD_BUTTON
277 type2 = gtk.TOOLBAR_CHILD_TOGGLEBUTTON
279 items = [
280 #(type, image, text, callback)
281 (type1, gtk.image_new_from_stock('media-prev', size), _("Prev"), self.prev),
282 (type1, gtk.image_new_from_stock('media-play', size), _("Play"), self.play_pause),
283 (type1, gtk.image_new_from_stock('media-stop', size), _("Stop"), self.stop),
284 (type1, gtk.image_new_from_stock('media-next', size), _("Next"), self.next),
285 (type2, gtk.image_new_from_stock('media-repeat', size), _("Repeat"),
286 lambda button: self.set_repeat(button.get_active())),
287 (type2, gtk.image_new_from_stock('media-shuffle', size), _("Shuffle"),
288 lambda button: self.set_shuffle(button.get_active())),
289 (type1, gtk.image_new_from_stock(gtk.STOCK_INDEX, size), _("Playlist"), self.show_playlist),
290 (type1, gtk.image_new_from_stock(gtk.STOCK_PREFERENCES, size), _("Options"), self.show_options),
293 buttons = []
294 for (type, image, text, callback) in items:
295 buttons.append(self.toolbar.insert_element(type, None, text, text, None, image, callback, None, -1))
296 self.buttons = buttons
298 self.image_play = items[BTN_PLAY][1]
299 buttons[BTN_REPEAT].set_active(self.repeat)
300 buttons[BTN_SHUFFLE].set_active(self.shuffle)
301 buttons[BTN_PLAYLIST].set_sensitive(False)
304 def build_labels(self):
305 self.display = gtk.Layout()
306 self.display_size = (0, 0)
307 self.display.connect('size-allocate', self.resize)
308 self.vvbox = gtk.VBox()
309 self.display.put(self.vvbox, 6, 0)
311 self.display_song = gtk.Label()
312 self.display_song.set_line_wrap(bool(WORDWRAP.int_value))
313 self.display_song.set_alignment(0.0, 0.0)
315 self.display_album = gtk.Label()
316 self.display_album.set_line_wrap(bool(WORDWRAP.int_value))
317 self.display_album.set_alignment(0.0, 0.0)
319 self.display_artist = gtk.Label()
320 self.display_artist.set_line_wrap(bool(WORDWRAP.int_value))
321 self.display_artist.set_alignment(0.0, 0.0)
323 self.display_status = gtk.Label()
324 self.display_status.set_line_wrap(bool(WORDWRAP.int_value))
325 self.display_status.set_alignment(0.0, 0.0)
327 self.vvbox.pack_start(self.display_song, False, True, 0)
328 self.vvbox.pack_start(self.display_album, False, True, 0)
329 self.vvbox.pack_start(self.display_artist, False, True, 0)
330 self.vvbox.pack_start(gtk.Label(), False, True, 0)
331 self.vvbox.pack_start(self.display_status, False, True, 0)
334 def build_misc(self):
335 self.volume = gtk.Adjustment(50.0, 0.0, 100.0, 1.0, 10.0, 0.0)
336 self.volume.connect('value_changed', self.adjust_volume)
337 self.volume_control = gtk.VScale(self.volume)
338 self.volume_control.set_draw_value(False)
339 self.volume_control.set_inverted(True)
340 self.volume_control.set_size_request(17, 100)
342 self.seek_bar = gtk.Adjustment(0.0, 0.0, 1.0, 0.01, 0.1, 0.0)
343 self.seek_id = self.seek_bar.connect('value_changed', self.adjust_seek_bar)
344 self.seek_bar_control = gtk.HScale(self.seek_bar)
345 self.seek_bar_control.set_update_policy(gtk.UPDATE_DELAYED)
346 self.seek_bar_control.set_draw_value(False)
347 self.seek_bar_control.set_size_request(100, 17)
350 def set_fonts(self):
351 song_font = pango.FontDescription(SONG_FONT.value)
352 base_font = pango.FontDescription(BASE_FONT.value)
353 self.display_song.modify_font(song_font)
354 self.display_album.modify_font(base_font)
355 self.display_artist.modify_font(base_font)
356 self.display_status.modify_font(base_font)
359 def set_colors(self):
360 fg_color = gtk.gdk.color_parse(FG_COLOR.value)
361 bg_color = gtk.gdk.color_parse(BG_COLOR.value)
362 self.display.modify_bg(gtk.STATE_NORMAL, bg_color)
363 self.display_song.modify_fg(gtk.STATE_NORMAL, fg_color)
364 self.display_album.modify_fg(gtk.STATE_NORMAL, fg_color)
365 self.display_artist.modify_fg(gtk.STATE_NORMAL, fg_color)
366 self.display_status.modify_fg(gtk.STATE_NORMAL, fg_color)
369 def show_hide_controls(self):
370 for (option, control) in [
371 (SH_VOLUME, self.volume_control), (SH_TOOLBAR, self.toolbar),
372 (SH_SEEKBAR, self.seek_bar_control)]:
373 if bool(option.int_value):
374 control.show()
375 else:
376 control.hide()
379 def show_hide_buttons(self):
380 for i in range(len(self.buttons)):
381 if TOOLOPTIONS.int_value & (1 << i):
382 self.buttons[i].show()
383 else:
384 self.buttons[i].hide()
387 def set_shuffle(self, value):
388 self._shuffle = value
389 try: self.buttons[BTN_SHUFFLE].set_active(self._shuffle)
390 except: pass
391 shuffle = property(lambda self: self._shuffle, set_shuffle)
394 def set_repeat(self, value):
395 self._repeat = value
396 try: self.buttons[BTN_REPEAT].set_active(self._repeat)
397 except: pass
398 repeat = property(lambda self: self._repeat, set_repeat)
401 def resize(self, widget, rectangle):
402 """Called when the window resizes."""
403 #the -18 is for the volume control?
404 width = rectangle[2]-18
405 height = rectangle[3]
406 if self.display_size != (width, height):
407 self.display_size = (width, height)
408 self.vvbox.set_size_request(width, height)
409 self.display_song.set_size_request(width, -1)
410 self.display_album.set_size_request(width, -1)
411 self.display_artist.set_size_request(width, -1)
412 self.display_status.set_size_request(width, -1)
415 def set_sensitive(self, state):
416 self.buttons[BTN_PLAYLIST].set_sensitive(state)
417 self.buttons[BTN_PLAY].set_sensitive(state)
418 self.buttons[BTN_NEXT].set_sensitive(state)
419 self.buttons[BTN_STOP].set_sensitive(state)
422 def server(self):
423 """process external/remote commands"""
424 def callback(window, cmd, args):
425 if cmd == 'play':
426 self.play()
427 elif cmd == 'stop':
428 self.stop()
429 elif cmd == 'pause':
430 self.pause()
431 elif cmd == 'next':
432 self.next()
433 elif cmd == 'prev':
434 self.prev()
435 elif cmd == 'add_songs':
436 self.add_songs(args)
437 elif cmd == 'load_songs':
438 self.load_songs(args)
439 else:
440 rox.info("Bad rpc message")
442 xsoap.register_server("_ROX_MUSICBOX")
443 xsoap.register_callback(callback)
446 def update_thd(self, button=None):
447 """load songs from source dirs"""
448 self.load()
451 def refresh(self):
452 self.library = [LIBRARY.value]
453 self.update_thd()
456 def loading(self):
457 self.display_status.set_text(_("Loading")+': '+str(len(self.playlist)))
458 if len(self.playlist):
459 self.set_sensitive(True)
460 else:
461 self.set_sensitive(False)
464 def load(self):
465 """Load the playlist either from a saved xml file, or from source dirs"""
466 self.display_status.set_text(_("Loading songs, please wait..."))
467 if self.playlistUI:
468 self.playlistUI.view.set_model(None)
470 self.playlist.get_songs(self.library, self.loading, self.replace_library)
472 self.display_status.set_text(_("Ready")+': '+_("loaded ")+str(len(self.playlist))+_(" songs"))
474 if len(self.playlist):
475 self.set_sensitive(True)
476 else:
477 self.set_sensitive(False)
479 if self.playlistUI:
480 self.playlistUI.view.set_model(self.playlist.song_list)
482 if self.replace_library and len(self.playlist):
483 self.play()
486 def save(self):
487 """Save the current list"""
488 box = saving.SaveBox(self.playlist, rox.choices.save(APP_NAME, 'Library.xml'), 'text/xml')
489 box.show()
492 def load_args(self, args, replace=True):
493 """Accept files and folders from the command line (or dropped on our icon)"""
494 self.replace_library = replace
495 if len(args):
496 path = []
497 for s in args:
498 path.append(s)
499 self.library = path
500 self.update_thd()
503 def add_songs(self, args):
504 self.load_args(args, False)
507 def load_songs(self, args):
508 self.load_args(args, True)
511 def play(self):
512 """Play the current song"""
513 size = gtk.ICON_SIZE_SMALL_TOOLBAR
515 try:
516 self.player.stop()
517 self.current_song = self.playlist.get()
518 self.player.play(self.current_song.filename, self.current_song.type)
519 self.image_play.set_from_stock('media-pause', size)
520 self.buttons[BTN_PREV].set_sensitive(self.playlist.get_previous())
521 self.display_song.set_text(self.current_song.title)
522 self.display_artist.set_text(self.current_song.artist)
523 self.display_album.set_text(self.current_song.album)
524 except TypeError, detail:
525 rox.info(str(detail))
526 except:
527 rox.info(_("Failed to start playing %s") % self.current_song.filename)
529 if self.playlistUI:
530 self.playlistUI.sync()
533 def play_pause(self, button=None):
534 """Play button handler (toggle between play and pause)"""
535 if (self.player.state == 'play') or (self.player.state == 'pause'):
536 self.pause()
537 else:
538 self.play()
541 def prev(self, button=None):
542 """Skip to previous song and play it"""
543 self.current_song = self.playlist.prev()
544 self.play()
547 def next(self, button=None):
548 """Skip to next song and play it (with shuffle and repeat)"""
549 if self.shuffle:
550 self.playlist.shuffle()
551 self.current_song = self.playlist.get()
552 else:
553 try:
554 self.current_song = self.playlist.next()
555 except StopIteration:
556 if self.repeat:
557 self.current_song = self.playlist.first()
558 else:
559 self.stop()
560 return True
561 self.play()
564 def stop(self, button=None):
565 """Stop playing"""
566 size = gtk.ICON_SIZE_SMALL_TOOLBAR
567 self.player.stop()
568 self.image_play.set_from_stock('media-play', size)
569 self.seek_bar.set_value(0.0)
572 def pause(self, button=None):
573 """Pause playing (toggle)"""
574 size = gtk.ICON_SIZE_SMALL_TOOLBAR
575 self.player.pause()
576 if (self.player.state == 'play'):
577 self.image_play.set_from_stock('media-pause', size)
578 else:
579 self.image_play.set_from_stock('media-play', size)
582 def display_update(self):
583 duration = int(self.player.remain + self.player.elapse)
584 if duration:
585 progress = float(self.player.elapse)/duration
586 else:
587 progress = 0
589 min = string.zfill(str(int(duration)%3600/60),2)
590 sec = string.zfill(str(int(duration)%3600%60),2)
591 total = min+':'+sec
593 minremain = string.zfill(str(self.player.remain%3600/60),2)
594 secremain = string.zfill(str(self.player.remain%3600%60),2)
595 remain = minremain+':'+secremain
597 minelapse = string.zfill(str(self.player.elapse%3600/60),2)
598 secelapse = string.zfill(str(self.player.elapse%3600%60),2)
599 elapse = minelapse+':'+secelapse
601 show_remain = bool(TIMEDISPLAY.int_value)
602 if show_remain:
603 self.time_string = remain+' / '+total
604 else:
605 self.time_string = elapse+' / '+total
607 state_string = ''
608 if self.player.state == 'play':
609 self.display_status.set_text(_("Playing")+': '+self.time_string)
610 self.seek_bar.handler_block(self.seek_id)
611 self.seek_bar.set_value(progress)
612 self.seek_bar.handler_unblock(self.seek_id)
613 elif self.player.state == 'pause':
614 self.display_status.set_text(_("Paused")+': '+self.time_string)
615 elif self.player.state == 'stop':
616 self.display_status.set_text(_("Stopped"))
617 elif self.player.state == 'eof':
618 self.display_status.set_text("")
619 self.next()
621 if (self.window_state & gtk.gdk.WINDOW_STATE_ICONIFIED):
622 self.set_title(self.current_song.title+' - '+self.time_string)
624 #update the volume control if something other than us changed it
625 self.volume.set_value(self.player.get_volume())
627 return True #keep running
630 def delete_event(self, ev, e1):
631 """Same as close, but called from the window manager"""
632 self.close()
635 def window_state_event(self, window, event):
636 """Track changes in window state and such..."""
637 self.my_gdk_window = event.window
638 self.window_state = event.new_window_state
639 if not (self.window_state & gtk.gdk.WINDOW_STATE_ICONIFIED):
640 self.set_title(APP_NAME)
643 def close(self, button = None):
644 """Stop playing, kill the player and exit"""
645 self.stop()
646 if self.playlistUI:
647 self.playlistUI.close()
649 xsoap.unregister_server("_ROX_MUSICBOX")
650 self.destroy()
653 def get_options(self):
654 """Used as the notify callback when options change"""
655 if SHUFFLE.has_changed:
656 self.shuffle = SHUFFLE.int_value
658 if REPEAT.has_changed:
659 self.repeat = REPEAT.int_value
661 if SONG_FONT.has_changed or BASE_FONT.has_changed:
662 self.set_fonts()
664 if FG_COLOR.has_changed or BG_COLOR.has_changed:
665 self.set_colors()
667 if TOOLOPTIONS.has_changed:
668 self.show_hide_buttons()
670 if SH_TOOLBAR.has_changed or SH_VOLUME.has_changed or SH_SEEKBAR.has_changed:
671 self.show_hide_controls()
674 def show_options(self, button=None):
675 """Options edit dialog"""
676 rox.edit_options()
679 def show_playlist(self, button=None):
680 """Display the playlist window"""
681 if not self.playlistUI:
682 self.playlistUI = playlistui.PlaylistUI(self.playlist, self)
683 self.playlistUI.connect('destroy', self.playlist_close)
684 self.buttons[BTN_PLAYLIST].set_sensitive(False)
687 def playlist_close(self, item=None):
688 """Notice when the playlistUI goes away (so we don't crash)"""
689 self.playlistUI = None
690 self.buttons[BTN_PLAYLIST].set_sensitive(True)
693 def button_press(self, text, event):
694 """Popup menu handler"""
695 if event.button != 3:
696 return 0
697 self.menu.popup(self, event)
698 return 1
701 def get_info(self):
702 InfoWin.infowin(APP_NAME)
705 def adjust_seek_bar(self, pos):
706 """Set the playback position (seek)"""
707 self.player.seek(pos.get_value())
710 def adjust_volume(self, vol):
711 """Set the playback volume"""
712 self.player.set_volume(vol.get_value())
715 def xds_drag_motion(self, widget, context, x, y, timestamp):
716 pass
719 def xds_drag_drop(self, widget, context, data, info, time):
720 """Check if the Shift key is pressed or not when Dropping files"""
721 if context.actions & gtk.gdk.ACTION_MOVE:
722 self.replace_library = True
723 else:
724 self.replace_library = False
725 return loading.XDSLoader.xds_drag_drop(self, widget, context, data, info, time)
728 def xds_load_uris(self, uris):
729 """Accept files and folders dropped on us as new Library"""
730 path = []
731 #strip off the 'file://' part and concatenate them
732 for s in uris:
733 path.append(rox.get_local_path(s))
734 self.library = path
735 self.update_thd()