2 musicbox.py (play either ogg or mp3 files)
4 Copyright 2004 Kenneth Hayber <khayber@socal.rr.com>
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
21 from __future__
import generators
23 import os
, sys
, re
, string
, threading
, pango
, gtk
, gobject
24 from threading
import *
27 from rox
import Menu
, app_options
, loading
, saving
, InfoWin
, OptionsBox
28 from rox
.options
import Option
31 import player
, playlist
, playlistui
, xsoap
33 rox
.report_exception()
36 #Who am I and how did I get here?
42 VIEW_DEFAULT_SIZE
= (200, 100)
44 #Toolbar button indexes
55 #Bitmaps (potentially) used in this application
56 factory
= gtk
.IconFactory()
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
)
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
))
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)
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')
102 SH_TOOLBAR
= Option('toolbar', True)
103 SH_VOLUME
= Option('volume', True)
104 SH_SEEKBAR
= Option('seekbar', True)
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
)
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
):
133 box
.check_widget(option
)
136 for (text
, image
) in buttons
:
137 controls
.append(toolbar
.insert_element(type, None, text
, text
, None, image
,
138 item_changed
, None, -1))
142 for i
in range(len(controls
)):
143 if controls
[i
].get_active():
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
)
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"""
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
)
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
)
191 self
.replace_library
= False
192 self
.library
= LIBRARY
.value
.split(':')
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
208 # Pack and show widgets
209 self
.vbox
= gtk
.VBox()
210 self
.hbox
= gtk
.HBox()
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)
220 self
.show_hide_controls()
221 self
.show_hide_buttons()
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)
233 self
.volume
.set_value(self
.player
.get_volume())
235 if len(sys
.argv
) > 1:
236 self
.load_args(sys
.argv
[1:], True)
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'),
252 Menu
.Action(_("Back"), 'prev', '', 'media-prev'),
253 Menu
.Action(_("Next"), 'next', '', 'media-next'),
256 Menu
.ToggleItem(_("Shuffle"), 'shuffle'),
257 Menu
.ToggleItem(_("Repeat"), 'repeat'),
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
),
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
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
),
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)
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
):
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()
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
)
391 shuffle
= property(lambda self
: self
._shuffle
, set_shuffle
)
394 def set_repeat(self
, value
):
396 try: self
.buttons
[BTN_REPEAT
].set_active(self
._repeat
)
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
)
423 """process external/remote commands"""
424 def callback(window
, cmd
, args
):
435 elif cmd
== 'add_songs':
437 elif cmd
== 'load_songs':
438 self
.load_songs(args
)
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"""
452 self
.library
= [LIBRARY
.value
]
457 self
.display_status
.set_text(_("Loading")+': '+str(len(self
.playlist
)))
458 if len(self
.playlist
):
459 self
.set_sensitive(True)
461 self
.set_sensitive(False)
465 """Load the playlist either from a saved xml file, or from source dirs"""
466 self
.display_status
.set_text(_("Loading songs, please wait..."))
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)
477 self
.set_sensitive(False)
480 self
.playlistUI
.view
.set_model(self
.playlist
.song_list
)
482 if self
.replace_library
and len(self
.playlist
):
487 """Save the current list"""
488 box
= saving
.SaveBox(self
.playlist
, rox
.choices
.save(APP_NAME
, 'Library.xml'), 'text/xml')
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
503 def add_songs(self
, args
):
504 self
.load_args(args
, False)
507 def load_songs(self
, args
):
508 self
.load_args(args
, True)
512 """Play the current song"""
513 size
= gtk
.ICON_SIZE_SMALL_TOOLBAR
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
))
527 rox
.info(_("Failed to start playing %s") % self
.current_song
.filename
)
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'):
541 def prev(self
, button
=None):
542 """Skip to previous song and play it"""
543 self
.current_song
= self
.playlist
.prev()
547 def next(self
, button
=None):
548 """Skip to next song and play it (with shuffle and repeat)"""
550 self
.playlist
.shuffle()
551 self
.current_song
= self
.playlist
.get()
554 self
.current_song
= self
.playlist
.next()
555 except StopIteration:
557 self
.current_song
= self
.playlist
.first()
564 def stop(self
, button
=None):
566 size
= gtk
.ICON_SIZE_SMALL_TOOLBAR
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
576 if (self
.player
.state
== 'play'):
577 self
.image_play
.set_from_stock('media-pause', size
)
579 self
.image_play
.set_from_stock('media-play', size
)
582 def display_update(self
):
583 duration
= int(self
.player
.remain
+ self
.player
.elapse
)
585 progress
= float(self
.player
.elapse
)/duration
589 min = string
.zfill(str(int(duration
)%3600/60),2)
590 sec
= string
.zfill(str(int(duration
)%3600%60),2)
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
)
603 self
.time_string
= remain
+' / '+total
605 self
.time_string
= elapse
+' / '+total
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("")
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"""
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"""
647 self
.playlistUI
.close()
649 xsoap
.unregister_server("_ROX_MUSICBOX")
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
:
664 if FG_COLOR
.has_changed
or BG_COLOR
.has_changed
:
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"""
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:
697 self
.menu
.popup(self
, event
)
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
):
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
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"""
731 #strip off the 'file://' part and concatenate them
733 path
.append(rox
.get_local_path(s
))