3 GUI front-end to cdda2wav and lame.
5 Copyright 2004-2006 Kenneth Hayber <ken@hayber.us>
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License.
12 This program is distributed in the hope that it will be useful
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 import gtk
, os
, sys
, signal
, re
, string
, socket
, time
, popen2
, Queue
24 from random
import Random
27 from rox
import i18n
, app_options
, Menu
, filer
, InfoWin
, tasks
, loading
, fileutils
28 from rox
.options
import Option
30 import PyCDDB
, cd_logic
, CDROM
, genres
, support
38 _
= rox
.i18n
.translation(os
.path
.join(rox
.app_dir
, 'Messages'))
40 #Who am I and how did I get here?
41 APP_NAME
= 'Ripper' #I could call it Mr. Giles, but that would be gay.
42 APP_PATH
= rox
.app_dir
45 #Options.xml processing
46 from rox
import choices
47 choices
.migrate(APP_NAME
, 'hayber.us')
48 rox
.setup_app_options(APP_NAME
, site
='hayber.us')
49 Menu
.set_save_name(APP_NAME
, site
='hayber.us')
51 #assume that everyone puts their music in ~/Music
52 LIBRARY
= Option('library', '~/Music/$A/$L/$S')
55 DEVICE
= Option('device', '/dev/cdrom')
56 RIPPER
= Option('ripper', 'nice cdda2wav -g -x -H -D$D -A$D -t $T "$F"')
57 ENCODER
= Option('encoder', 'nice lame --ta "$A" --tt "$S" --tl "$L" --tg "$G" --tn $T --ty $Y "$F.wav" "$F.mp3"')
59 EJECT_AFTER_RIP
= Option('eject_after_rip', '0')
60 RIP_ONLY
= Option('rip_only', '0')
61 KEEP_WAV
= Option('keep_wav', '0')
63 CDDB_SERVER
= Option('cddb_server', 'http://freedb.freedb.org/~cddb/cddb.cgi')
65 rox
.app_options
.notify()
80 print >>sys
.stderr
, args
83 # My gentoo python doesn't have universal line ending support compiled in
84 # and these guys (cdda2wav and lame) use CR line endings to pretty-up their output.
86 '''Return a line of input using \r or \n as terminators'''
88 while '\n' not in line
and '\r' not in line
:
90 if char
== '': return line
96 '''Return the full path of an executable if found on the path'''
97 if (filename
== None) or (filename
== ''):
100 env_path
= os
.getenv('PATH').split(':')
102 if os
.access(p
+'/'+filename
, os
.X_OK
):
103 return p
+'/'+filename
107 def strip_illegal(instr
):
108 '''remove illegal (filename) characters from string'''
111 str = string
.translate(str, string
.maketrans(r
'/+{}*.?', r
'--()___'))
115 class Ripper(rox
.Window
, rox
.loading
.XDSLoader
):
116 '''Rip and Encode a CD'''
118 rox
.Window
.__init
__(self
)
120 # Support dropping Album Cover Art on our Window.
121 # Get types supported by gdk-pixbuf
123 for fmt
in gtk
.gdk
.pixbuf_get_formats():
124 mtypes
+= fmt
['mime_types']
125 rox
.loading
.XDSLoader
.__init
__(self
, mtypes
)
127 self
.set_title(APP_NAME
)
128 self
.set_default_size(450, 500)
129 self
.set_position(gtk
.WIN_POS_MOUSE
)
131 # Capture wm delete event
132 self
.connect("delete_event", self
.delete_event
)
134 # Update things when options change
135 rox
.app_options
.add_notify(self
.get_options
)
141 # Create layout, pack and show widgets
142 self
.vbox
= gtk
.VBox()
144 self
.vbox
.pack_start(self
.toolbar
, False, True, 0)
145 self
.vbox
.pack_start(self
.table
, False, True, 0)
146 self
.vbox
.pack_start(self
.scroll_window
, True, True, 0)
150 self
.is_ripping
= False
151 self
.is_encoding
= False
152 self
.is_cddbing
= False
153 self
.stop_request
= False
155 self
.artwork_saved
= False
157 cd_logic
.set_dev(DEVICE
.value
)
158 self
.cd_status
= None
160 self
.cd_status_changed
= False
162 tasks
.Task(self
.update_gui())
165 def build_menu(self
):
166 self
.add_events(gtk
.gdk
.BUTTON_PRESS_MASK
)
167 self
.connect('button-press-event', self
.button_press
)
168 self
.view
.add_events(gtk
.gdk
.BUTTON_PRESS_MASK
)
169 self
.view
.connect('button-press-event', self
.button_press
)
171 self
.menu
= Menu
.Menu('main', [
172 Menu
.Action(_('Rip & Encode'), 'rip_n_encode', '', gtk
.STOCK_EXECUTE
),
173 Menu
.Action(_('Reload CD'), 'do_get_tracks', '', gtk
.STOCK_REFRESH
),
174 Menu
.Action(_('Stop'), 'stop', '', gtk
.STOCK_STOP
),
176 Menu
.Action(_('Options'), 'show_options', '', gtk
.STOCK_PREFERENCES
),
177 Menu
.Action(_('Help'), 'show_help', '', gtk
.STOCK_HELP
),
178 Menu
.Action(_("Quit"), 'close', '', gtk
.STOCK_CLOSE
),
180 self
.menu
.attach(self
,self
)
183 def build_toolbar(self
):
184 self
.toolbar
= gtk
.Toolbar()
185 self
.toolbar
.set_style(gtk
.TOOLBAR_ICONS
)
186 self
.toolbar
.insert_stock(gtk
.STOCK_HELP
, _('Help'), None, self
.show_help
, None, 0)
187 self
.toolbar
.insert_stock(gtk
.STOCK_PREFERENCES
, _('Settings'), None, self
.show_options
, None, 0)
188 self
.stop_btn
= self
.toolbar
.insert_stock(gtk
.STOCK_STOP
, _('Stop'), None, self
.stop
, None, 0)
189 self
.rip_btn
= self
.toolbar
.insert_stock(gtk
.STOCK_EXECUTE
, _('Rip & Encode'), None, self
.rip_n_encode
, None, 0)
190 self
.refresh_btn
= self
.toolbar
.insert_stock(gtk
.STOCK_REFRESH
, _('Reload CD'), None, self
.do_get_tracks
, None, 0)
191 self
.toolbar
.insert_stock(gtk
.STOCK_GO_UP
, _('Show destination dir'), None, self
.show_dir
, None, 0)
192 self
.toolbar
.insert_stock(gtk
.STOCK_CLOSE
, _('Close'), None, self
.close
, None, 0)
196 swin
= gtk
.ScrolledWindow()
197 self
.scroll_window
= swin
198 swin
.set_policy(gtk
.POLICY_AUTOMATIC
, gtk
.POLICY_AUTOMATIC
)
199 swin
.set_shadow_type(gtk
.SHADOW_IN
)
201 self
.store
= gtk
.ListStore(int, str, str, str, int)
202 view
= gtk
.TreeView(self
.store
)
205 view
.set_rules_hint(True)
207 cell
= gtk
.CellRendererToggle()
208 cell
.connect('toggled', self
.toggle_check
)
209 column
= gtk
.TreeViewColumn('', cell
, active
=COL_ENABLE
)
210 view
.append_column(column
)
211 column
.set_resizable(False)
212 column
.set_reorderable(False)
214 cell
= gtk
.CellRendererText()
215 column
= gtk
.TreeViewColumn(_('Track'), cell
, text
=COL_TRACK
)
216 view
.append_column(column
)
217 column
.set_resizable(True)
218 column
.set_reorderable(False)
220 cell
= gtk
.CellRendererText()
221 column
= gtk
.TreeViewColumn(_('Time'), cell
, text
=COL_TIME
)
222 view
.append_column(column
)
223 column
.set_resizable(True)
224 column
.set_reorderable(False)
226 cell
= gtk
.CellRendererProgress()
227 column
= gtk
.TreeViewColumn(_('Status'), cell
, text
=COL_STATUS
, value
=COL_PERCENT
)
228 view
.append_column(column
)
229 column
.set_resizable(True)
230 column
.set_reorderable(False)
232 view
.connect('row-activated', self
.activate
)
233 self
.selection
= view
.get_selection()
234 self
.handler
= self
.selection
.connect('changed', self
.set_selection
)
236 self
.table
= gtk
.Table(5, 3, False)
240 self
.artwork
= gtk
.Image()
241 self
.table
.attach(self
.artwork
, 2, 3, 0, 5, 0, 0, x_pad
, y_pad
)
243 self
.artist_entry
= gtk
.Entry(max=255)
244 self
.artist_entry
.connect('changed', self
.stuff_changed
)
245 self
.table
.attach(gtk
.Label(str=_('Artist')), 0, 1, 2, 3, 0, 0, 4, y_pad
)
246 self
.table
.attach(self
.artist_entry
, 1, 2, 2, 3, gtk
.EXPAND|gtk
.FILL
, 0, x_pad
, y_pad
)
248 self
.album_entry
= gtk
.Entry(max=255)
249 self
.album_entry
.connect('changed', self
.stuff_changed
)
250 self
.table
.attach(gtk
.Label(str=_('Album')), 0, 1, 3, 4, 0, 0, 4, y_pad
)
251 self
.table
.attach(self
.album_entry
, 1, 2, 3, 4, gtk
.EXPAND|gtk
.FILL
, 0, x_pad
, y_pad
)
253 self
.genre_entry
= gtk
.Entry(max=255)
254 self
.genre_entry
.connect('changed', self
.stuff_changed
)
255 self
.table
.attach(gtk
.Label(str=_('Genre')), 0, 1, 4, 5, 0, 0, 4, y_pad
)
256 self
.table
.attach(self
.genre_entry
, 1, 2, 4, 5, gtk
.EXPAND|gtk
.FILL
, 0, x_pad
, y_pad
)
258 self
.year_entry
= gtk
.Entry(max=4)
259 self
.year_entry
.connect('changed', self
.stuff_changed
)
260 self
.table
.attach(gtk
.Label(str=_('Year')), 0, 1, 5, 6, 0, 0, 4, y_pad
)
261 self
.table
.attach(self
.year_entry
, 1, 2, 5, 6, gtk
.EXPAND|gtk
.FILL
, 0, x_pad
, y_pad
)
265 ck
= gtk
.CheckButton(label
=_('Eject after Rip'))
266 ck
.set_active(bool(EJECT_AFTER_RIP
.int_value
))
267 ck
.connect('toggled', self
.toggled
, EJECT_AFTER_RIP
)
268 hbox1
.pack_start(ck
, False, False, 5)
270 ck
= gtk
.CheckButton(label
=_('Keep WAV files'))
271 ck
.set_active(bool(KEEP_WAV
.int_value
))
272 ck
.connect('toggled', self
.toggled
, KEEP_WAV
)
273 hbox1
.pack_start(ck
, False, False, 5)
275 ck
= gtk
.CheckButton(label
=_('Rip Only'))
276 ck
.set_active(bool(RIP_ONLY
.int_value
))
277 ck
.connect('toggled', self
.toggled
, RIP_ONLY
)
278 hbox1
.pack_start(ck
, False, False, 5)
280 self
.table
.attach(hbox1
, 0, 3, 6, 7, True, True, x_pad
, y_pad
)
283 def toggled(self
, button
, param
):
284 param
.int_value
= button
.get_active()
287 def update_gui(self
):
288 '''Update UI based on current state'''
290 cd_status
= cd_logic
.check_dev()
291 if self
.cd_status
!= cd_status
:
292 self
.cd_status
= cd_status
293 self
.cd_status_changed
= True
295 if self
.is_ripping
or self
.is_encoding
:
296 self
.stop_btn
.set_sensitive(True)
297 self
.rip_btn
.set_sensitive(False)
298 self
.refresh_btn
.set_sensitive(False)
300 if not self
.is_ripping
and not self
.is_encoding
and not self
.is_cddbing
:
301 self
.stop_btn
.set_sensitive(False)
302 self
.rip_btn
.set_sensitive(bool(self
.disc_id
is not None))
303 self
.refresh_btn
.set_sensitive(True)
305 #get tracks if cd changed and not doing other things
306 if self
.cd_status_changed
:
307 if self
.cd_status
in [CDROM
.CDS_TRAY_OPEN
, CDROM
.CDS_NO_DISC
]:
311 disc_id
= cd_logic
.get_disc_id()
312 if self
.disc_id
<> disc_id
:
313 self
.disc_id
= disc_id
315 self
.cd_status_changed
= False
320 yield tasks
.TimeoutBlocker(1)
323 def scale_cover(self
, pixbuf
):
324 w
= pixbuf
.get_width()
325 h
= pixbuf
.get_height()
326 if w
< COVER_SIZE
and h
< COVER_SIZE
:
329 if w
< COVER_SIZE
*2 and h
< COVER_SIZE
*2:
333 scale
= float(COVER_SIZE
)/float(w
)
335 scale
= float(COVER_SIZE
)/float(h
)
337 return pixbuf
.scale_simple(int(scale
*w
), int(scale
*h
), gtk
.gdk
.INTERP_BILINEAR
)
340 def xds_load_from_stream(self
, name
, type, stream
):
341 loader
= gtk
.gdk
.PixbufLoader()
344 pbuf
= loader
.get_pixbuf()
345 self
.artwork_saved
= False
346 self
.set_artwork(pbuf
)
349 def set_artwork(self
, pbuf
):
351 self
.artwork
.set_from_pixbuf(self
.scale_cover(pbuf
))
352 self
.set_size_request(COVER_SIZE
, COVER_SIZE
)
356 def save_artwork(self
, force
=False):
357 pbuf
= self
.artwork
.get_pixbuf()
360 # We're not sure about the target dir yet, so skip it.
361 if not force
and not self
.is_ripping
and not self
.is_encoding
:
364 # OK, we're have the dir, check for existing file and ask to overwrite
365 filename
= os
.path
.join(self
.library
, '.DirIcon')
366 if os
.path
.exists(filename
):
368 if self
.artwork_saved
:
370 if not rox
.confirm(_("Overwrite current Artwork?"), gtk
.STOCK_SAVE
):
373 # Save the damn thing already, will ya?
374 pbuf
.save(filename
, 'png')
375 self
.artwork_saved
= True
378 def stuff_changed(self
, button
=None):
379 '''Get new text from edit boxes and save it'''
380 self
.genre
= self
.genre_entry
.get_text()
381 self
.artist
= self
.artist_entry
.get_text()
382 self
.album
= self
.album_entry
.get_text()
383 self
.year
= self
.year_entry
.get_text()
388 '''Stop current rip/encode process'''
389 self
.stop_request
= True
392 def parse_library(self
):
393 library
= os
.path
.expanduser(LIBRARY
.value
)
396 (library
, filespec
) = os
.path
.split(library
)
397 library
= string
.replace(library
, '$A', strip_illegal(self
.artist
))
398 library
= string
.replace(library
, '$L', strip_illegal(self
.album
))
399 self
.library
= library
400 self
.filespec
= filespec
#still may contain $ parameters, we parse these later
403 def build_filename(self
, track
, tracknum
):
405 fn
= string
.replace(fn
, '$A', strip_illegal(self
.artist
))
406 fn
= string
.replace(fn
, '$L', strip_illegal(self
.album
))
407 fn
= string
.replace(fn
, '$S', strip_illegal(track
))
408 fn
= string
.replace(fn
, '$T', "%02d" % (tracknum
+1))
412 def show_dir(self
, *dummy
):
413 ''' Pops up a filer window. '''
414 filer
.show_file(self
.library
)
417 def do_get_tracks(self
, button
=None):
418 '''Get the track info (cddb and cd) in a thread'''
421 tasks
.Task(self
.get_tracks())
425 '''Clear all info and display <no disc>'''
426 #dbg("no disc in tray?")
428 self
.artist_entry
.set_text(_('<no disc>'))
429 self
.album_entry
.set_text('')
430 self
.genre_entry
.set_text('')
431 self
.year_entry
.set_text('')
432 self
.view
.columns_autosize()
436 def get_tracks(self
):
437 '''Get the track info (cddb and cd)'''
438 self
.is_cddbing
= True
439 stuff
= self
.do_cddb()
440 (count
, artist
, album
, genre
, year
, tracklist
) = stuff
445 self
.tracklist
= tracklist
447 if artist
: self
.artist_entry
.set_text(artist
)
448 if album
: self
.album_entry
.set_text(album
)
449 if genre
: self
.genre_entry
.set_text(genre
)
450 if year
: self
.year_entry
.set_text(year
)
454 for track
in tracklist
:
456 iter = self
.store
.append(None)
457 self
.store
.set(iter, COL_TRACK
, track
[0])
458 self
.store
.set(iter, COL_TIME
, track
[1])
459 self
.store
.set(iter, COL_ENABLE
, True)
460 self
.store
.set(iter, COL_STATUS
, None)
461 self
.store
.set(iter, COL_STATUS
, "")
464 self
.view
.columns_autosize()
465 self
.is_cddbing
= False
469 '''Query cddb for track and cd info'''
470 dlg
= gtk
.MessageDialog(buttons
=gtk
.BUTTONS_CANCEL
, message_format
="Getting Track Info.")
472 dlg
.set_transient_for(self
)
474 while gtk
.events_pending():
477 count
= artist
= genre
= album
= year
= ''
481 #Note: all the nested try|except|pass statements are to ensure that as much
482 #info is processed as possible. One exception should not stop
483 #the whole thing and return nothing.
486 count
= cd_logic
.total_tracks()
487 cddb_id
= cd_logic
.get_cddb_id()
489 #PyCDDB wants a string delimited by spaces, go figure.
492 cddb_id_string
+= str(n
)+' '
493 #dbg(disc_id, cddb_id, cddb_id_string)
495 for i
in range(count
):
496 tracktime
= cd_logic
.get_track_time_total(i
+1)
497 track_time
= time
.strftime('%M:%S', time
.gmtime(tracktime
))
498 tracklist
.append((_('Track')+`i`
,track_time
))
501 db
= PyCDDB
.PyCDDB(CDDB_SERVER
.value
)
502 query_info
= db
.query(cddb_id_string
)
505 dlg
.set_title(_('Got Disc Info'))
506 while gtk
.events_pending():
509 #make sure we didn't get an error, then query CDDB
510 if len(query_info
) > 0:
512 index
= rndm
.randrange(0, len(query_info
))
513 read_info
= db
.read(query_info
[index
])
515 dlg
.set_title(_('Got Track Info'))
516 while gtk
.events_pending():
520 (artist
, album
) = query_info
[index
]['title'].split('/')
521 artist
= artist
.strip().decode('latin-1', 'replace')
522 album
= album
.strip().decode('latin-1', 'replace')
523 genre
= query_info
[index
]['category']
524 if genre
in ['misc', 'data']:
527 dbg(query_info
['year'])
528 dbg(read_info
['EXTD'])
529 dbg(read_info
['YEARD'])
531 #x = re.match(r'.*YEAR: (.+).*',read_info['EXTD'])
538 if len(read_info
['TTITLE']) > 0:
539 for i
in range(count
):
541 track_name
= read_info
['TTITLE'][i
].decode('latin-1', 'replace')
542 track_time
= tracklist
[i
][1]
543 #dbg(i, track_name, track_time)
544 tracklist
[i
] = (track_name
, track_time
)
554 return count
, artist
, album
, genre
, year
, tracklist
557 def run_ripper(self
, tracknum
, track
, filename
):
558 '''Rip selected tracks from the CD'''
560 cmd
= string
.replace(cmd
, '$D', DEVICE
.value
)
561 cmd
= string
.replace(cmd
, '$T', str(tracknum
+1))
562 cmd
= string
.replace(cmd
, '$F', filename
)
563 thing
= popen2
.Popen4(cmd
)
567 def run_encoder(self
, tracknum
, track
, filename
, artist
, genre
, album
, year
):
568 '''Encode each WAV file'''
569 if year
is None or not len(year
):
573 cmd
= string
.replace(cmd
, '$A', artist
)
574 cmd
= string
.replace(cmd
, '$L', album
)
575 cmd
= string
.replace(cmd
, '$S', track
)
576 cmd
= string
.replace(cmd
, '$G', genre
)
577 cmd
= string
.replace(cmd
, '$T', str(tracknum
+1))
578 cmd
= string
.replace(cmd
, '$Y', year
)
579 cmd
= string
.replace(cmd
, '$F', filename
)
580 thing
= popen2
.Popen4(cmd
)
584 def rip_n_encode(self
, button
=None):
585 '''Process all selected tracks (rip and encode)'''
587 rox
.fileutils
.makedirs(self
.library
)
588 os
.chdir(self
.library
)
590 rox
.alert(_("Failed to find or create Library dir. Cannot save files."))
593 self
.save_artwork(True) #force
595 self
.stop_request
= False
596 self
.wavqueue
= Queue
.Queue(1000)
597 tasks
.Task(self
.ripit())
598 tasks
.Task(self
.encodeit())
602 '''Thread to rip all selected tracks'''
603 self
.is_ripping
= True
605 for tracknum
in range(self
.count
):
606 if self
.stop_request
:
609 if self
.store
[tracknum
][COL_ENABLE
]:
610 track
= self
.store
[tracknum
][COL_TRACK
]
611 filename
= self
.build_filename(track
, tracknum
)
612 thing
= self
.run_ripper(tracknum
, track
, filename
)
613 outfile
= thing
.fromchild
616 handler
= support
.get_handler(RIPPER
.value
)
618 blocker
= tasks
.InputBlocker(outfile
)
621 if self
.stop_request
:
622 os
.kill(thing
.pid
, signal
.SIGKILL
)
625 line
= myreadline(outfile
)
627 percent
= handler
.get_percent(line
)
628 if percent
<> last_percent
:
629 self
.status_update(tracknum
, 'rip', percent
)
630 last_percent
= percent
633 status
= thing
.wait()
634 self
.status_update(tracknum
, 'rip', 100)
637 #dbg('ripper died %d' % status)
638 self
.status_update(tracknum
, 'rip_error', 0)
640 #push this track on the queue for the encoder
641 if self
.wavqueue
and not RIP_ONLY
.int_value
:
642 self
.wavqueue
.put((track
, tracknum
, filename
))
644 #push None object to tell encoder we're done
646 self
.wavqueue
.put((None, None, None))
648 self
.is_ripping
= False
650 if EJECT_AFTER_RIP
.int_value
:
655 '''Thread to encode all tracks from the wavqueue'''
656 self
.is_encoding
= True
659 if self
.stop_request
:
663 (track
, tracknum
, filename
) = self
.wavqueue
.get(False)
665 yield tasks
.TimeoutBlocker(1)
671 thing
= self
.run_encoder(tracknum
, track
, filename
, self
.artist
, self
.genre
, self
.album
, self
.year
)
672 outfile
= thing
.fromchild
674 handler
= support
.get_handler(ENCODER
.value
)
676 blocker
= tasks
.InputBlocker(outfile
)
679 if self
.stop_request
:
680 os
.kill(thing
.pid
, signal
.SIGKILL
)
683 line
= myreadline(outfile
)
686 percent
= handler
.get_percent(line
)
687 self
.status_update(tracknum
, 'enc', percent
)
691 status
= thing
.wait()
692 self
.status_update(tracknum
, 'enc', 100)
695 dbg('encoder died %d' % status
)
696 self
.status_update(tracknum
, 'enc_error', 0)
698 if not KEEP_WAV
.int_value
:
699 try: os
.unlink(filename
+".wav")
702 self
.is_encoding
= False
705 def status_update(self
, row
, state
, percent
):
706 '''Callback from rip/encode tasks to update display'''
713 msg2
= ': %s' % _('error')
714 # elif percent == -1:
715 # pulse #how to handle unknown processes?
717 msg2
= ': %d%%' % percent
719 msg2
= ': %s' % _('done')
721 iter = self
.store
.get_iter((row
,))
723 self
.store
.set_value(iter, COL_STATUS
, msg1
+msg2
)
724 self
.store
.set_value(iter, COL_PERCENT
, percent
)
727 def activate(self
, view
, path
, column
):
728 '''Edit a track name'''
729 model
, iter = self
.view
.get_selection().get_selected()
731 track
= model
.get_value(iter, COL_TRACK
)
732 dlg
= gtk
.Dialog(APP_NAME
)
733 dlg
.set_default_size(350, 100)
734 dlg
.set_transient_for(self
)
739 entry
.set_text(track
)
741 entry
.set_activates_default(True)
742 dlg
.vbox
.pack_start(entry
)
744 dlg
.add_button(gtk
.STOCK_OK
, gtk
.RESPONSE_OK
)
745 dlg
.add_button(gtk
.STOCK_CANCEL
, gtk
.RESPONSE_CANCEL
)
746 dlg
.set_default_response(gtk
.RESPONSE_OK
)
749 if response
== gtk
.RESPONSE_OK
:
750 track
= entry
.get_text()
752 model
.set_value(iter, COL_TRACK
, track
)
753 self
.view
.columns_autosize()
758 def toggle_check(self
, cell
, rownum
):
759 '''Toggle state for each song'''
760 row
= self
.store
[rownum
]
761 row
[COL_ENABLE
] = not row
[COL_ENABLE
]
762 self
.store
.row_changed(rownum
, row
.iter)
765 def set_selection(self
, thing
):
766 '''Get current selection'''
767 # Not needed, just here for future reference.
768 #model, iter = self.view.get_selection().get_selected()
770 # track = model.get_value(iter, COL_TRACK)
772 def button_press(self
, text
, event
):
773 '''Popup menu handler'''
774 if event
.button
!= 3:
776 self
.menu
.popup(self
, event
)
779 def show_options(self
, button
=None):
780 '''Show Options dialog'''
783 def get_options(self
):
784 '''Get changed Options'''
785 #TODO Update toggle buttons
788 def show_help(self
, button
=None):
789 rox
.filer
.open_dir(os
.path
.join(rox
.app_dir
, 'Help'))
791 def delete_event(self
, ev
, e1
):
795 def close(self
, button
= None):
796 '''We are outta here!'''
799 if self
.is_ripping
or self
.is_encoding
:
800 dlg
= gtk
.MessageDialog(self
, gtk
.DIALOG_MODAL|gtk
.DIALOG_DESTROY_WITH_PARENT
)
801 dlg
.set_title(_("Please Wait"))
802 dlg
.set_markup(_("Stopping current operation..."))
804 while self
.is_ripping
or self
.is_encoding
:
805 while gtk
.events_pending():