Third commit
[rox-ripper.git] / ripper.py
blob6d83fb82b3f6d29509f5e999bc8beb9e1fafc215
1 """
2 ripper.py
3 GUI front-end to cdda2wav and lame.
5 Copyright 2004 Kenneth Hayber <khayber@socal.rr.com>
6 All rights reserved.
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
21 """
23 import os, sys, signal, re, string, socket, time, popen2, threading, Queue
24 from random import Random
25 from threading import *
27 import rox
28 from rox import g, i18n, app_options
29 from rox.options import Option
31 import PyCDDB, cd_logic, CDROM, genres
33 try:
34 import xattr
35 HAVE_XATTR = True
36 except:
37 HAVE_XATTR = False
39 _ = rox.i18n.translation(os.path.join(rox.app_dir, 'Messages'))
41 #Who am I and how did I get here?
42 APP_NAME = 'Ripper' #I could call it Mr. Giles, but that would be gay.
43 APP_PATH = os.path.split(os.path.abspath(sys.argv[0]))[0]
46 #Options.xml processing
47 rox.setup_app_options(APP_NAME)
49 #assume that everyone puts their music in ~/Music
50 LIBRARY = Option('library', os.path.expanduser('~')+'/MyMusic')
52 #RIPPER options
53 RIPPER = Option('ripper', 'cdda2wav')
54 RIPPER_DEV = Option('ripper_dev', '/dev/cdrom')
55 RIPPER_LUN = Option('ripper_lun', 'ATAPI:0,1,0')
56 RIPPER_OPTS = Option('ripper_opts', '-x -H')
58 EJECT_AFTER_RIP = Option('eject_after_rip', '0')
60 #ENCODER options
61 ENCODER = Option('encoder', 'MP3')
63 MP3_ENCODER = Option('mp3_encoder', 'lame')
64 MP3_ENCODER_OPTS = Option('mp3_encoder_opts', '--vbr-new -b160 --nohist --add-id3v2')
66 OGG_ENCODER = Option('ogg_encoder', 'oggenc')
67 OGG_ENCODER_OPTS = Option('ogg_encoder_opts', '-q5')
69 #CDDB Server and Options
70 CDDB_SERVER = Option('cddb_server', 'http://freedb.freedb.org/~cddb/cddb.cgi')
72 rox.app_options.notify()
75 #Column indicies
76 COL_ENABLE = 0
77 COL_TRACK = 1
78 COL_TIME = 2
79 COL_STATUS = 3
82 # My gentoo python doesn't have universal line ending support compiled in
83 # and these guys (cdda2wav and lame) use CR line endings to pretty-up their output.
84 def myreadline(file):
85 '''Return a line of input using \r or \n as terminators'''
86 line = ''
87 while '\n' not in line and '\r' not in line:
88 char = file.read(1)
89 if char == '': return line
90 line += char
91 return line
94 def which(filename):
95 '''Return the full path of an executable if found on the path'''
96 if (filename == None) or (filename == ''):
97 return None
99 env_path = os.getenv('PATH').split(':')
100 for p in env_path:
101 if os.access(p+'/'+filename, os.X_OK):
102 return p+'/'+filename
103 return None
106 def strip_illegal(instr):
107 '''remove illegal (filename) characters from string'''
108 str = instr
109 str = str.strip()
110 str = string.translate(str, string.maketrans(r'/+{}*.?', r'--()___'))
111 return str
114 class Ripper(rox.Window):
115 '''Rip and Encode a CD'''
116 def __init__(self):
117 rox.Window.__init__(self)
119 self.set_title(APP_NAME)
120 self.set_default_size(450, 500)
121 self.set_position(g.WIN_POS_MOUSE)
123 #capture wm delete event
124 self.connect("delete_event", self.delete_event)
126 # Update things when options change
127 rox.app_options.add_notify(self.get_options)
130 #song list
131 #######################################
132 swin = g.ScrolledWindow()
133 self.scroll_window = swin
134 swin.set_policy(g.POLICY_AUTOMATIC, g.POLICY_AUTOMATIC)
135 swin.set_shadow_type(g.SHADOW_IN)
137 self.store = g.ListStore(int, str, str, str)
138 view = g.TreeView(self.store)
139 self.view = view
140 swin.add(view)
141 view.set_rules_hint(True)
143 # self.view.add_events(g.gdk.BUTTON_PRESS_MASK)
144 # self.view.connect('button-press-event', self.button_press)
146 cell = g.CellRendererToggle()
147 cell.connect('toggled', self.toggle_check)
148 column = g.TreeViewColumn('', cell, active=COL_ENABLE)
149 view.append_column(column)
150 column.set_resizable(False)
151 column.set_reorderable(False)
153 cell = g.CellRendererText()
154 column = g.TreeViewColumn(_('Track'), cell, text = COL_TRACK)
155 view.append_column(column)
156 column.set_resizable(True)
157 #column.set_sizing(g.TREE_VIEW_COLUMN_AUTOSIZE)
158 column.set_reorderable(False)
160 cell = g.CellRendererText()
161 column = g.TreeViewColumn(_('Time'), cell, text = COL_TIME)
162 view.append_column(column)
163 column.set_resizable(True)
164 column.set_reorderable(False)
166 cell = g.CellRendererText()
167 column = g.TreeViewColumn(_('Status'), cell, text = COL_STATUS)
168 view.append_column(column)
169 column.set_resizable(True)
170 column.set_reorderable(False)
172 view.connect('row-activated', self.activate)
173 self.selection = view.get_selection()
174 self.handler = self.selection.connect('changed', self.set_selection)
177 self.toolbar = g.Toolbar()
178 self.toolbar.set_style(g.TOOLBAR_ICONS)
179 self.toolbar.insert_stock(g.STOCK_PREFERENCES,
180 _('Settings'), None, self.show_options, None, 0)
181 self.stop_btn = self.toolbar.insert_stock(g.STOCK_STOP,
182 _('Stop'), None, self.stop, None, 0)
183 self.rip_btn = self.toolbar.insert_stock(g.STOCK_EXECUTE,
184 _('Rip & Encode'), None, self.rip_n_encode, None, 0)
185 self.refresh_btn = self.toolbar.insert_stock(g.STOCK_REFRESH,
186 _('Reload CD'), None, self.do_get_tracks, None, 0)
189 self.table = g.Table(5, 2, False)
190 x_pad = 2
191 y_pad = 1
193 self.artist_entry = g.Entry(max=255)
194 self.artist_entry.connect('changed', self.stuff_changed)
195 self.table.attach(g.Label(str=_('Artist')), 0, 1, 2, 3, 0, 0, 4, y_pad)
196 self.table.attach(self.artist_entry, 1, 2, 2, 3, g.EXPAND|g.FILL, 0, x_pad, y_pad)
198 self.album_entry = g.Entry(max=255)
199 self.album_entry.connect('changed', self.stuff_changed)
200 self.table.attach(g.Label(str=_('Album')), 0, 1, 3, 4, 0, 0, 4, y_pad)
201 self.table.attach(self.album_entry, 1, 2, 3, 4, g.EXPAND|g.FILL, 0, x_pad, y_pad)
203 genres.genre_list.sort()
204 self.genre_combo = g.Combo()
205 self.genre_combo.set_popdown_strings(genres.genre_list)
206 self.genre_combo.entry.connect('changed', self.stuff_changed)
207 self.table.attach(g.Label(str=_('Genre')), 0, 1, 4, 5, 0, 0, 4, y_pad)
208 self.table.attach(self.genre_combo, 1, 2, 4, 5, g.EXPAND|g.FILL, 0, x_pad, y_pad)
210 self.year_entry = g.Entry(max=4)
211 self.year_entry.connect('changed', self.stuff_changed)
212 self.table.attach(g.Label(str=_('Year')), 0, 1, 5, 6, 0, 0, 4, y_pad)
213 self.table.attach(self.year_entry, 1, 2, 5, 6, g.EXPAND|g.FILL, 0, x_pad, y_pad)
216 # Create layout, pack and show widgets
217 self.vbox = g.VBox()
218 self.add(self.vbox)
219 self.vbox.pack_start(self.toolbar, False, True, 0)
220 self.vbox.pack_start(self.table, False, True, 0)
221 self.vbox.pack_start(self.scroll_window, True, True, 0)
222 self.vbox.show_all()
224 self.cddb_thd = None
225 self.ripper_thd = None
226 self.encoder_thd = None
227 self.is_ripping = False
228 self.is_encoding = False
229 self.is_cddbing = False
230 self.stop_request = False
232 cd_logic.set_dev(RIPPER_DEV.value)
233 self.cd_status = cd_logic.check_dev()
234 self.disc_id = None
235 self.cd_status_changed = False
237 if self.cd_status in [CDROM.CDS_TRAY_OPEN, CDROM.CDS_NO_DISC]:
238 self.no_disc()
239 else:
240 self.do_get_tracks()
241 g.timeout_add(1000, self.update_gui)
244 def update_gui(self):
245 '''Update button status based on current state'''
246 cd_status = cd_logic.check_dev()
247 if self.cd_status != cd_status:
248 self.cd_status = cd_status
249 self.cd_status_changed = True
251 if self.is_ripping or self.is_encoding:
252 self.stop_btn.set_sensitive(True)
253 self.rip_btn.set_sensitive(False)
254 self.refresh_btn.set_sensitive(False)
256 if not self.is_ripping and not self.is_encoding and not self.is_cddbing:
257 self.stop_btn.set_sensitive(False)
258 self.rip_btn.set_sensitive(True)
259 self.refresh_btn.set_sensitive(True)
261 #get tracks if cd changed and not doing other things
262 if self.cd_status_changed:
263 if self.cd_status in [CDROM.CDS_TRAY_OPEN, CDROM.CDS_NO_DISC]:
264 self.no_disc()
265 self.disc_id = None
266 else:
267 disc_id = cd_logic.get_disc_id()
268 if self.disc_id <> disc_id:
269 self.disc_id = disc_id
270 self.do_get_tracks()
271 self.cd_status_changed = False
273 #need this to keep the timer running(?)
274 return True
277 def stuff_changed(self, button=None):
278 '''Get new text from edit boxes and save it'''
279 self.genre = self.genre_combo.entry.get_text()
280 self.artist = self.artist_entry.get_text()
281 self.album = self.album_entry.get_text()
282 self.year = self.year_entry.get_text()
285 def runit(self, it):
286 '''Run a function in a thread'''
287 thd_it = Thread(name='mythread', target=it)
288 thd_it.setDaemon(True)
289 thd_it.start()
290 return thd_it
293 def stop(self, it):
294 '''Stop current rip/encode process'''
295 self.stop_request = True
298 def do_get_tracks(self, button=None):
299 '''Get the track info (cddb and cd) in a thread'''
300 if self.is_ripping:
301 return
302 self.cddb_thd = self.runit(self.get_tracks)
305 def no_disc(self):
306 '''Clear all info and display <no disc>'''
307 #print "no disc in tray?"
308 g.threads_enter()
309 self.store.clear()
310 self.artist_entry.set_text(_('<no disc>'))
311 self.album_entry.set_text('')
312 self.genre_combo.entry.set_text('')
313 self.year_entry.set_text('')
314 self.view.columns_autosize()
315 g.threads_leave()
318 def get_tracks(self):
319 '''Get the track info (cddb and cd)'''
320 self.is_cddbing = True
321 stuff = self.get_cddb()
322 g.threads_enter()
323 (count, artist, album, genre, year, tracklist) = stuff
324 #print count, artist, album, genre, year, tracklist
326 self.artist = artist
327 self.count = count
328 self.album = album
329 self.genre = genre
330 self.year = year
331 self.tracklist = tracklist
333 if artist: self.artist_entry.set_text(artist)
334 if album: self.album_entry.set_text(album)
335 if genre: self.genre_combo.entry.set_text(genre)
336 if year: self.year_entry.set_text(year)
338 self.store.clear()
339 for track in tracklist:
340 #print song
341 iter = self.store.append(None)
342 self.store.set(iter, COL_TRACK, track[0])
343 self.store.set(iter, COL_TIME, track[1])
344 self.store.set(iter, COL_ENABLE, True)
346 self.view.columns_autosize()
347 g.threads_leave()
348 self.is_cddbing = False
351 def get_cddb(self):
352 '''Query cddb for track and cd info'''
353 g.threads_enter()
354 dlg = g.MessageDialog(buttons=g.BUTTONS_CANCEL, message_format="Getting Track Info.")
355 dlg.set_position(g.WIN_POS_NONE)
356 (a, b) = dlg.get_size()
357 (x, y) = self.get_position()
358 (dx, dy) = self.get_size()
359 dlg.move(x+dx/2-a/2, y+dy/2-b/2)
360 dlg.show()
361 g.threads_leave()
363 count = artist = genre = album = year = ''
364 tracklist = []
365 tracktime = []
367 #Note: all the nested try statements are to ensure that as much
368 #info is processed as possible. One exception should not stop
369 #the whole thing and return nothing.
371 try:
372 count = cd_logic.total_tracks()
373 cddb_id = cd_logic.get_cddb_id()
375 #PyCDDB wants a string delimited by spaces, go figure.
376 cddb_id_string = ''
377 for n in cddb_id:
378 cddb_id_string += str(n)+' '
379 #print disc_id
380 #print cddb_id, cddb_id_string
382 for i in range(count):
383 tracktime = cd_logic.get_track_time_total(i+1)
384 track_time = time.strftime('%M:%S', time.gmtime(tracktime))
385 tracklist.append((_('Track')+`i`,track_time))
387 try:
388 db = PyCDDB.PyCDDB(CDDB_SERVER.value)
389 query_info = db.query(cddb_id_string)
390 #print query_info
392 g.threads_enter()
393 dlg.set_title(_('Got Disc Info'))
394 g.threads_leave()
396 #make sure we didn't get an error, then query CDDB
397 if len(query_info) > 0:
398 rndm = Random()
399 index = rndm.randrange(0, len(query_info))
400 read_info = db.read(query_info[index])
401 #print read_info
402 g.threads_enter()
403 dlg.set_title(_('Got Track Info'))
404 g.threads_leave()
406 try:
407 (artist, album) = query_info[index]['title'].split('/')
408 artist = artist.strip()
409 album = album.strip()
410 genre = query_info[index]['category']
411 if genre in ['misc', 'data']:
412 genre = 'Other'
414 print query_info['year']
415 print read_info['EXTD']
416 print read_info['YEARD']
418 #x = re.match(r'.*YEAR: (.+).*',read_info['EXTD'])
419 #if x:
420 # print x.group(1)
421 # year = x.group(1)
422 except:
423 pass
425 if len(read_info['TTITLE']) > 0:
426 for i in range(count):
427 try:
428 track_name = read_info['TTITLE'][i]
429 track_time = tracklist[i][1]
430 #print i, track_name, track_time
431 tracklist[i] = (track_name, track_time)
432 except:
433 pass
434 except:
435 pass
437 except:
438 pass
440 g.threads_enter()
441 dlg.destroy()
442 g.threads_leave()
443 return count, artist, album, genre, year, tracklist
446 def get_cdda2wav(self, tracknum, track):
447 '''Run cdda2wav to rip a track from the CD'''
448 cdda2wav_cmd = RIPPER.value
449 cdda2wav_dev = RIPPER_DEV.value
450 cdda2wav_lun = RIPPER_LUN.value
451 cdda2wav_args = '-D%s -A%s -t %d "%s"' % (
452 cdda2wav_lun, cdda2wav_dev, tracknum+1, strip_illegal(track))
453 cdda2wav_opts = RIPPER_OPTS.value
454 #print cdda2wav_opts, cdda2wav_args
456 thing = popen2.Popen4(cdda2wav_cmd+' '+cdda2wav_opts+' '+cdda2wav_args )
457 outfile = thing.fromchild
459 while True:
460 line = myreadline(outfile)
461 if line:
462 x = re.match('([\s0-9]+)%', line)
463 if x:
464 percent = int(x.group(1))
465 self.status_update(tracknum, 'rip', percent)
466 else:
467 break
468 if self.stop_request:
469 break
471 if self.stop_request:
472 os.kill(thing.pid, signal.SIGKILL)
474 code = thing.wait()
475 self.status_update(tracknum, 'rip', 100)
476 #print code
477 return code
480 def get_lame(self, tracknum, track, artist, genre, album, year):
481 '''Run lame to encode a wav file to mp3'''
482 try:
483 int_year = int(year)
484 except:
485 int_year = 1
487 lame_cmd = MP3_ENCODER.value
488 lame_opts = MP3_ENCODER_OPTS.value
489 lame_tags = '--ta "%s" --tt "%s" --tl "%s" --tg "%s" --tn %d --ty %d' % (
490 artist, track, album, genre, tracknum+1, int_year)
491 lame_args = '"%s" "%s"' % (strip_illegal(track)+'.wav', strip_illegal(track)+'.mp3')
493 #print lame_opts, lame_tags, lame_args
495 thing = popen2.Popen4(lame_cmd+' '+lame_opts+' '+lame_tags+' '+lame_args )
496 outfile = thing.fromchild
498 while True:
499 line = myreadline(outfile)
500 if line:
501 #print line
502 #for some reason getting this right for lame was a royal pain.
503 x = re.match(r"^[\s]+([0-9]+)/([0-9]+)", line)
504 if x:
505 percent = int(100 * (float(x.group(1)) / float(x.group(2))))
506 self.status_update(tracknum, 'enc', percent)
507 else:
508 break
509 if self.stop_request:
510 break
512 if self.stop_request:
513 os.kill(thing.pid, signal.SIGKILL)
514 elif HAVE_XATTR:
515 try:
516 filename = strip_illegal(track)+'.mp3'
517 xattr.setxattr(filename, 'user.Title', track)
518 xattr.setxattr(filename, 'user.Artist', artist)
519 xattr.setxattr(filename, 'user.Album', album)
520 xattr.setxattr(filename, 'user.Genre', genre)
521 xattr.setxattr(filename, 'user.Track', '%d' % tracknum)
522 xattr.setxattr(filename, 'user.Year', year)
523 except:
524 pass
526 code = thing.wait()
527 self.status_update(tracknum, 'enc', 100)
528 #print code
529 return code
532 def get_ogg(self, tracknum, track, artist, genre, album, year):
533 '''Run oggenc to encode a wav file to ogg'''
534 try:
535 int_year = int(year)
536 except:
537 int_year = 1
539 ogg_cmd = OGG_ENCODER.value
540 ogg_opts = OGG_ENCODER_OPTS.value
541 ogg_tags = '-a "%s" -t "%s" -l "%s" -G "%s" -N %d -d %d' % (
542 artist, track, album, genre, tracknum+1, int_year)
543 ogg_args = '"%s"' % (strip_illegal(track)+'.wav')
545 #print ogg_opts, ogg_tags, ogg_args
547 thing = popen2.Popen4(ogg_cmd+' '+ogg_opts+' '+ogg_tags+' '+ogg_args )
548 outfile = thing.fromchild
550 while True:
551 line = myreadline(outfile)
552 if line:
553 #print line
554 #for some reason getting this right for ogg was a royal pain.
555 x = re.match('^.*\[[\s]*([.0-9]+)%\]', line)
556 if x:
557 percent = float(x.group(1))
558 self.status_update(tracknum, 'enc', percent)
559 else:
560 break
561 if self.stop_request:
562 break
564 if self.stop_request:
565 os.kill(thing.pid, signal.SIGKILL)
566 elif HAVE_XATTR:
567 try:
568 filename = strip_illegal(track)+'.ogg'
569 xattr.setxattr(filename, 'user.Title', track)
570 xattr.setxattr(filename, 'user.Artist', artist)
571 xattr.setxattr(filename, 'user.Album', album)
572 xattr.setxattr(filename, 'user.Genre', genre)
573 xattr.setxattr(filename, 'user.Track', '%d' % tracknum)
574 xattr.setxattr(filename, 'user.Year', year)
575 except:
576 pass
578 code = thing.wait()
579 self.status_update(tracknum, 'enc', 100)
580 #print code
581 return code
584 def rip_n_encode(self, button=None):
585 '''Process all selected tracks (rip and encode)'''
586 try: os.chdir(os.path.expanduser('~'))
587 except: pass
588 try: os.mkdir(LIBRARY.value)
589 except: pass
590 try: os.chdir(LIBRARY.value)
591 except: pass
593 if self.count and self.artist and self.album:
594 try: os.mkdir(self.artist)
595 except: pass
597 try: os.mkdir(self.artist+'/'+self.album)
598 except: pass
600 try: os.chdir(self.artist+'/'+self.album)
601 except: pass
603 self.stop_request = False
605 #the queue to feed tracks from ripper to encoder
606 self.wavqueue = Queue.Queue(1000)
608 self.ripper_thd = self.runit(self.ripit)
609 self.encoder_thd = self.runit(self.encodeit)
612 def ripit(self):
613 '''Thread to rip all selected tracks'''
614 self.is_ripping = True
615 for i in range(self.count):
616 if self.stop_request:
617 break;
619 if self.store[i][COL_ENABLE]:
620 track = self.store[i][COL_TRACK]
621 #print i, track
622 status = self.get_cdda2wav(i, track)
623 if status <> 0:
624 print 'cdda2wav died %d' % status
625 self.status_update(i, 'rip_error', 0)
626 else:
627 #push this track on the queue for the encoder
628 if self.wavqueue:
629 self.wavqueue.put((track, i))
631 #push None object to tell encoder we're done
632 if self.wavqueue:
633 self.wavqueue.put((None, None))
635 self.is_ripping = False
636 cd_logic.stop()
637 if EJECT_AFTER_RIP.int_value:
638 cd_logic.eject()
641 def encodeit(self):
642 '''Thread to encode all tracks from the wavqueue'''
643 self.is_encoding = True
644 while True:
645 if self.stop_request:
646 break
647 (track, tracknum) = self.wavqueue.get(True)
648 if track == None:
649 break
651 if ENCODER.value == 'MP3':
652 status = self.get_lame(tracknum, track, self.artist, self.genre, self.album, self.year)
653 else:
654 status = self.get_ogg(tracknum, track, self.artist, self.genre, self.album, self.year)
656 if status <> 0:
657 print 'encoder died %d' % status
658 self.status_update(tracknum, 'enc_error', 0)
659 try: os.unlink(strip_illegal(track)+".wav")
660 except: pass
661 try: os.unlink(strip_illegal(track)+".inf")
662 except: pass
664 self.is_encoding = False
665 del self.wavqueue
668 def status_update(self, row, state, percent):
669 '''Callback from rip/encode threads to update display'''
670 g.threads_enter()
672 iter = self.store.get_iter((row,))
673 if not iter: return
675 if state == 'rip':
676 if percent < 100:
677 self.store.set_value(iter, COL_STATUS, _('Ripping')+': %d%%' % percent)
678 else:
679 self.store.set_value(iter, COL_STATUS, _('Ripping')+': '+_('done'))
681 if state == 'enc':
682 if percent < 100:
683 self.store.set_value(iter, COL_STATUS, _('Encoding')+': %d%%' % percent)
684 else:
685 self.store.set_value(iter, COL_STATUS, _('Encoding')+': '+_('done'))
687 if state == 'rip_error':
688 self.store.set_value(iter, COL_STATUS, _('Ripping')+': '+_('error'))
690 if state == 'enc_error':
691 self.store.set_value(iter, COL_STATUS, _('Encoding')+': '+_('error'))
693 g.threads_leave()
696 def activate(self, view, path, column):
697 '''Edit a track name'''
698 model, iter = self.view.get_selection().get_selected()
699 if iter:
700 track = model.get_value(iter, COL_TRACK)
701 dlg = g.Dialog(APP_NAME)
702 dlg.set_position(g.WIN_POS_NONE)
703 dlg.set_default_size(350, 100)
704 (a, b) = dlg.get_size()
705 (x, y) = self.get_position()
706 (dx, dy) = self.get_size()
707 dlg.move(x+dx/2-a/2, y+dy/2-b/2)
708 dlg.show()
710 entry = g.Entry()
711 entry.set_text(track)
712 dlg.set_position(g.WIN_POS_MOUSE)
713 entry.show()
714 entry.set_activates_default(True)
715 dlg.vbox.pack_start(entry)
717 dlg.add_button(g.STOCK_OK, g.RESPONSE_OK)
718 dlg.add_button(g.STOCK_CANCEL, g.RESPONSE_CANCEL)
719 dlg.set_default_response(g.RESPONSE_OK)
720 response = dlg.run()
722 if response == g.RESPONSE_OK:
723 track = entry.get_text()
724 #print track
725 model.set_value(iter, COL_TRACK, track)
726 self.view.columns_autosize()
728 dlg.destroy()
731 def toggle_check(self, cell, rownum):
732 '''Toggle state for each song'''
733 row = self.store[rownum]
734 row[COL_ENABLE] = not row[COL_ENABLE]
735 self.store.row_changed(rownum, row.iter)
738 def set_selection(self, thing):
739 '''Get current selection'''
740 #model, iter = self.view.get_selection().get_selected()
741 #if iter:
742 # track = model.get_value(iter, COL_TRACK)
744 def show_options(self, button=None):
745 '''Show Options dialog'''
746 rox.edit_options()
748 def get_options(self):
749 '''Get changed Options'''
750 pass
752 def delete_event(self, ev, e1):
753 '''Bye-bye'''
754 self.close()
756 def close(self, button = None):
757 '''We're outta here!'''
758 self.destroy()