fourth commit
[rox-ripper.git] / ripper.py
blob6c4bfd423c210c156d1c1514ffed6dc0fa9af3d8
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, Menu
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 cell = g.CellRendererToggle()
144 cell.connect('toggled', self.toggle_check)
145 column = g.TreeViewColumn('', cell, active=COL_ENABLE)
146 view.append_column(column)
147 column.set_resizable(False)
148 column.set_reorderable(False)
150 cell = g.CellRendererText()
151 column = g.TreeViewColumn(_('Track'), cell, text = COL_TRACK)
152 view.append_column(column)
153 column.set_resizable(True)
154 #column.set_sizing(g.TREE_VIEW_COLUMN_AUTOSIZE)
155 column.set_reorderable(False)
157 cell = g.CellRendererText()
158 column = g.TreeViewColumn(_('Time'), cell, text = COL_TIME)
159 view.append_column(column)
160 column.set_resizable(True)
161 column.set_reorderable(False)
163 cell = g.CellRendererText()
164 column = g.TreeViewColumn(_('Status'), cell, text = COL_STATUS)
165 view.append_column(column)
166 column.set_resizable(True)
167 column.set_reorderable(False)
169 view.connect('row-activated', self.activate)
170 self.selection = view.get_selection()
171 self.handler = self.selection.connect('changed', self.set_selection)
174 self.toolbar = g.Toolbar()
175 self.toolbar.set_style(g.TOOLBAR_ICONS)
176 self.toolbar.insert_stock(g.STOCK_PREFERENCES,
177 _('Settings'), None, self.show_options, None, 0)
178 self.stop_btn = self.toolbar.insert_stock(g.STOCK_STOP,
179 _('Stop'), None, self.stop, None, 0)
180 self.rip_btn = self.toolbar.insert_stock(g.STOCK_EXECUTE,
181 _('Rip & Encode'), None, self.rip_n_encode, None, 0)
182 self.refresh_btn = self.toolbar.insert_stock(g.STOCK_REFRESH,
183 _('Reload CD'), None, self.do_get_tracks, None, 0)
186 self.table = g.Table(5, 2, False)
187 x_pad = 2
188 y_pad = 1
190 self.artist_entry = g.Entry(max=255)
191 self.artist_entry.connect('changed', self.stuff_changed)
192 self.table.attach(g.Label(str=_('Artist')), 0, 1, 2, 3, 0, 0, 4, y_pad)
193 self.table.attach(self.artist_entry, 1, 2, 2, 3, g.EXPAND|g.FILL, 0, x_pad, y_pad)
195 self.album_entry = g.Entry(max=255)
196 self.album_entry.connect('changed', self.stuff_changed)
197 self.table.attach(g.Label(str=_('Album')), 0, 1, 3, 4, 0, 0, 4, y_pad)
198 self.table.attach(self.album_entry, 1, 2, 3, 4, g.EXPAND|g.FILL, 0, x_pad, y_pad)
200 genres.genre_list.sort()
201 self.genre_combo = g.Combo()
202 self.genre_combo.set_popdown_strings(genres.genre_list)
203 self.genre_combo.entry.connect('changed', self.stuff_changed)
204 self.table.attach(g.Label(str=_('Genre')), 0, 1, 4, 5, 0, 0, 4, y_pad)
205 self.table.attach(self.genre_combo, 1, 2, 4, 5, g.EXPAND|g.FILL, 0, x_pad, y_pad)
207 self.year_entry = g.Entry(max=4)
208 self.year_entry.connect('changed', self.stuff_changed)
209 self.table.attach(g.Label(str=_('Year')), 0, 1, 5, 6, 0, 0, 4, y_pad)
210 self.table.attach(self.year_entry, 1, 2, 5, 6, g.EXPAND|g.FILL, 0, x_pad, y_pad)
213 # Create layout, pack and show widgets
214 self.vbox = g.VBox()
215 self.add(self.vbox)
216 self.vbox.pack_start(self.toolbar, False, True, 0)
217 self.vbox.pack_start(self.table, False, True, 0)
218 self.vbox.pack_start(self.scroll_window, True, True, 0)
219 self.vbox.show_all()
221 # Menu
222 self.add_events(g.gdk.BUTTON_PRESS_MASK)
223 self.connect('button-press-event', self.button_press)
224 view.add_events(g.gdk.BUTTON_PRESS_MASK)
225 view.connect('button-press-event', self.button_press)
227 Menu.set_save_name(APP_NAME)
228 self.menu = Menu.Menu('main', [
229 Menu.Action(_('Rip & Encode'), 'rip_n_encode', '', g.STOCK_EXECUTE),
230 Menu.Action(_('Reload CD'), 'do_get_tracks', '', g.STOCK_REFRESH),
231 Menu.Action(_('Stop'), 'stop', '', g.STOCK_STOP),
232 Menu.Separator(),
233 Menu.Action(_('Settings'), 'show_options', '', g.STOCK_PREFERENCES),
234 Menu.Action(_("Quit"), 'close', '', g.STOCK_CLOSE),
236 self.menu.attach(self,self)
238 # Defaults and Misc
239 self.cddb_thd = None
240 self.ripper_thd = None
241 self.encoder_thd = None
242 self.is_ripping = False
243 self.is_encoding = False
244 self.is_cddbing = False
245 self.stop_request = False
247 cd_logic.set_dev(RIPPER_DEV.value)
248 self.cd_status = cd_logic.check_dev()
249 self.disc_id = None
250 self.cd_status_changed = False
252 if self.cd_status in [CDROM.CDS_TRAY_OPEN, CDROM.CDS_NO_DISC]:
253 self.no_disc()
254 else:
255 self.do_get_tracks()
256 g.timeout_add(1000, self.update_gui)
259 def update_gui(self):
260 '''Update button status based on current state'''
261 cd_status = cd_logic.check_dev()
262 if self.cd_status != cd_status:
263 self.cd_status = cd_status
264 self.cd_status_changed = True
266 if self.is_ripping or self.is_encoding:
267 self.stop_btn.set_sensitive(True)
268 self.rip_btn.set_sensitive(False)
269 self.refresh_btn.set_sensitive(False)
271 if not self.is_ripping and not self.is_encoding and not self.is_cddbing:
272 self.stop_btn.set_sensitive(False)
273 self.rip_btn.set_sensitive(True)
274 self.refresh_btn.set_sensitive(True)
276 #get tracks if cd changed and not doing other things
277 if self.cd_status_changed:
278 if self.cd_status in [CDROM.CDS_TRAY_OPEN, CDROM.CDS_NO_DISC]:
279 self.no_disc()
280 self.disc_id = None
281 else:
282 disc_id = cd_logic.get_disc_id()
283 if self.disc_id <> disc_id:
284 self.disc_id = disc_id
285 self.do_get_tracks()
286 self.cd_status_changed = False
288 #need this to keep the timer running(?)
289 return True
292 def stuff_changed(self, button=None):
293 '''Get new text from edit boxes and save it'''
294 self.genre = self.genre_combo.entry.get_text()
295 self.artist = self.artist_entry.get_text()
296 self.album = self.album_entry.get_text()
297 self.year = self.year_entry.get_text()
300 def runit(self, it):
301 '''Run a function in a thread'''
302 thd_it = Thread(name='mythread', target=it)
303 thd_it.setDaemon(True)
304 thd_it.start()
305 return thd_it
308 def stop(self, it):
309 '''Stop current rip/encode process'''
310 self.stop_request = True
313 def do_get_tracks(self, button=None):
314 '''Get the track info (cddb and cd) in a thread'''
315 if self.is_ripping:
316 return
317 self.cddb_thd = self.runit(self.get_tracks)
320 def no_disc(self):
321 '''Clear all info and display <no disc>'''
322 #print "no disc in tray?"
323 g.threads_enter()
324 self.store.clear()
325 self.artist_entry.set_text(_('<no disc>'))
326 self.album_entry.set_text('')
327 self.genre_combo.entry.set_text('')
328 self.year_entry.set_text('')
329 self.view.columns_autosize()
330 g.threads_leave()
333 def get_tracks(self):
334 '''Get the track info (cddb and cd)'''
335 self.is_cddbing = True
336 stuff = self.get_cddb()
337 g.threads_enter()
338 (count, artist, album, genre, year, tracklist) = stuff
339 #print count, artist, album, genre, year, tracklist
341 self.artist = artist
342 self.count = count
343 self.album = album
344 self.genre = genre
345 self.year = year
346 self.tracklist = tracklist
348 if artist: self.artist_entry.set_text(artist)
349 if album: self.album_entry.set_text(album)
350 if genre: self.genre_combo.entry.set_text(genre)
351 if year: self.year_entry.set_text(year)
353 self.store.clear()
354 for track in tracklist:
355 #print song
356 iter = self.store.append(None)
357 self.store.set(iter, COL_TRACK, track[0])
358 self.store.set(iter, COL_TIME, track[1])
359 self.store.set(iter, COL_ENABLE, True)
361 self.view.columns_autosize()
362 g.threads_leave()
363 self.is_cddbing = False
366 def get_cddb(self):
367 '''Query cddb for track and cd info'''
368 g.threads_enter()
369 dlg = g.MessageDialog(buttons=g.BUTTONS_CANCEL, message_format="Getting Track Info.")
370 dlg.set_position(g.WIN_POS_NONE)
371 (a, b) = dlg.get_size()
372 (x, y) = self.get_position()
373 (dx, dy) = self.get_size()
374 dlg.move(x+dx/2-a/2, y+dy/2-b/2)
375 dlg.show()
376 g.threads_leave()
378 count = artist = genre = album = year = ''
379 tracklist = []
380 tracktime = []
382 #Note: all the nested try statements are to ensure that as much
383 #info is processed as possible. One exception should not stop
384 #the whole thing and return nothing.
386 try:
387 count = cd_logic.total_tracks()
388 cddb_id = cd_logic.get_cddb_id()
390 #PyCDDB wants a string delimited by spaces, go figure.
391 cddb_id_string = ''
392 for n in cddb_id:
393 cddb_id_string += str(n)+' '
394 #print disc_id
395 #print cddb_id, cddb_id_string
397 for i in range(count):
398 tracktime = cd_logic.get_track_time_total(i+1)
399 track_time = time.strftime('%M:%S', time.gmtime(tracktime))
400 tracklist.append((_('Track')+`i`,track_time))
402 try:
403 db = PyCDDB.PyCDDB(CDDB_SERVER.value)
404 query_info = db.query(cddb_id_string)
405 #print query_info
407 g.threads_enter()
408 dlg.set_title(_('Got Disc Info'))
409 g.threads_leave()
411 #make sure we didn't get an error, then query CDDB
412 if len(query_info) > 0:
413 rndm = Random()
414 index = rndm.randrange(0, len(query_info))
415 read_info = db.read(query_info[index])
416 #print read_info
417 g.threads_enter()
418 dlg.set_title(_('Got Track Info'))
419 g.threads_leave()
421 try:
422 (artist, album) = query_info[index]['title'].split('/')
423 artist = artist.strip()
424 album = album.strip()
425 genre = query_info[index]['category']
426 if genre in ['misc', 'data']:
427 genre = 'Other'
429 print query_info['year']
430 print read_info['EXTD']
431 print read_info['YEARD']
433 #x = re.match(r'.*YEAR: (.+).*',read_info['EXTD'])
434 #if x:
435 # print x.group(1)
436 # year = x.group(1)
437 except:
438 pass
440 if len(read_info['TTITLE']) > 0:
441 for i in range(count):
442 try:
443 track_name = read_info['TTITLE'][i]
444 track_time = tracklist[i][1]
445 #print i, track_name, track_time
446 tracklist[i] = (track_name, track_time)
447 except:
448 pass
449 except:
450 pass
452 except:
453 pass
455 g.threads_enter()
456 dlg.destroy()
457 g.threads_leave()
458 return count, artist, album, genre, year, tracklist
461 def get_cdda2wav(self, tracknum, track):
462 '''Run cdda2wav to rip a track from the CD'''
463 cdda2wav_cmd = RIPPER.value
464 cdda2wav_dev = RIPPER_DEV.value
465 cdda2wav_lun = RIPPER_LUN.value
466 cdda2wav_args = '-D%s -A%s -t %d "%s"' % (
467 cdda2wav_lun, cdda2wav_dev, tracknum+1, strip_illegal(track))
468 cdda2wav_opts = RIPPER_OPTS.value
469 #print cdda2wav_opts, cdda2wav_args
471 thing = popen2.Popen4(cdda2wav_cmd+' '+cdda2wav_opts+' '+cdda2wav_args )
472 outfile = thing.fromchild
474 while True:
475 line = myreadline(outfile)
476 if line:
477 x = re.match('([\s0-9]+)%', line)
478 if x:
479 percent = int(x.group(1))
480 self.status_update(tracknum, 'rip', percent)
481 else:
482 break
483 if self.stop_request:
484 break
486 if self.stop_request:
487 os.kill(thing.pid, signal.SIGKILL)
489 code = thing.wait()
490 self.status_update(tracknum, 'rip', 100)
491 #print code
492 return code
495 def get_lame(self, tracknum, track, artist, genre, album, year):
496 '''Run lame to encode a wav file to mp3'''
497 try:
498 int_year = int(year)
499 except:
500 int_year = 1
502 lame_cmd = MP3_ENCODER.value
503 lame_opts = MP3_ENCODER_OPTS.value
504 lame_tags = '--ta "%s" --tt "%s" --tl "%s" --tg "%s" --tn %d --ty %d' % (
505 artist, track, album, genre, tracknum+1, int_year)
506 lame_args = '"%s" "%s"' % (strip_illegal(track)+'.wav', strip_illegal(track)+'.mp3')
508 #print lame_opts, lame_tags, lame_args
510 thing = popen2.Popen4(lame_cmd+' '+lame_opts+' '+lame_tags+' '+lame_args )
511 outfile = thing.fromchild
513 while True:
514 line = myreadline(outfile)
515 if line:
516 #print line
517 #for some reason getting this right for lame was a royal pain.
518 x = re.match(r"^[\s]+([0-9]+)/([0-9]+)", line)
519 if x:
520 percent = int(100 * (float(x.group(1)) / float(x.group(2))))
521 self.status_update(tracknum, 'enc', percent)
522 else:
523 break
524 if self.stop_request:
525 break
527 if self.stop_request:
528 os.kill(thing.pid, signal.SIGKILL)
529 elif HAVE_XATTR:
530 try:
531 filename = strip_illegal(track)+'.mp3'
532 xattr.setxattr(filename, 'user.Title', track)
533 xattr.setxattr(filename, 'user.Artist', artist)
534 xattr.setxattr(filename, 'user.Album', album)
535 xattr.setxattr(filename, 'user.Genre', genre)
536 xattr.setxattr(filename, 'user.Track', '%d' % tracknum)
537 xattr.setxattr(filename, 'user.Year', year)
538 except:
539 pass
541 code = thing.wait()
542 self.status_update(tracknum, 'enc', 100)
543 #print code
544 return code
547 def get_ogg(self, tracknum, track, artist, genre, album, year):
548 '''Run oggenc to encode a wav file to ogg'''
549 try:
550 int_year = int(year)
551 except:
552 int_year = 1
554 ogg_cmd = OGG_ENCODER.value
555 ogg_opts = OGG_ENCODER_OPTS.value
556 ogg_tags = '-a "%s" -t "%s" -l "%s" -G "%s" -N %d -d %d' % (
557 artist, track, album, genre, tracknum+1, int_year)
558 ogg_args = '"%s"' % (strip_illegal(track)+'.wav')
560 #print ogg_opts, ogg_tags, ogg_args
562 thing = popen2.Popen4(ogg_cmd+' '+ogg_opts+' '+ogg_tags+' '+ogg_args )
563 outfile = thing.fromchild
565 while True:
566 line = myreadline(outfile)
567 if line:
568 #print line
569 #for some reason getting this right for ogg was a royal pain.
570 x = re.match('^.*\[[\s]*([.0-9]+)%\]', line)
571 if x:
572 percent = float(x.group(1))
573 self.status_update(tracknum, 'enc', percent)
574 else:
575 break
576 if self.stop_request:
577 break
579 if self.stop_request:
580 os.kill(thing.pid, signal.SIGKILL)
581 elif HAVE_XATTR:
582 try:
583 filename = strip_illegal(track)+'.ogg'
584 xattr.setxattr(filename, 'user.Title', track)
585 xattr.setxattr(filename, 'user.Artist', artist)
586 xattr.setxattr(filename, 'user.Album', album)
587 xattr.setxattr(filename, 'user.Genre', genre)
588 xattr.setxattr(filename, 'user.Track', '%d' % tracknum)
589 xattr.setxattr(filename, 'user.Year', year)
590 except:
591 pass
593 code = thing.wait()
594 self.status_update(tracknum, 'enc', 100)
595 #print code
596 return code
599 def rip_n_encode(self, button=None):
600 '''Process all selected tracks (rip and encode)'''
601 try: os.chdir(os.path.expanduser('~'))
602 except: pass
603 try: os.mkdir(LIBRARY.value)
604 except: pass
605 try: os.chdir(LIBRARY.value)
606 except: pass
608 if self.count and self.artist and self.album:
609 try: os.mkdir(self.artist)
610 except: pass
612 try: os.mkdir(self.artist+'/'+self.album)
613 except: pass
615 try: os.chdir(self.artist+'/'+self.album)
616 except: pass
618 self.stop_request = False
620 #the queue to feed tracks from ripper to encoder
621 self.wavqueue = Queue.Queue(1000)
623 self.ripper_thd = self.runit(self.ripit)
624 self.encoder_thd = self.runit(self.encodeit)
627 def ripit(self):
628 '''Thread to rip all selected tracks'''
629 self.is_ripping = True
630 for i in range(self.count):
631 if self.stop_request:
632 break;
634 if self.store[i][COL_ENABLE]:
635 track = self.store[i][COL_TRACK]
636 #print i, track
637 status = self.get_cdda2wav(i, track)
638 if status <> 0:
639 print 'cdda2wav died %d' % status
640 self.status_update(i, 'rip_error', 0)
641 else:
642 #push this track on the queue for the encoder
643 if self.wavqueue:
644 self.wavqueue.put((track, i))
646 #push None object to tell encoder we're done
647 if self.wavqueue:
648 self.wavqueue.put((None, None))
650 self.is_ripping = False
651 cd_logic.stop()
652 if EJECT_AFTER_RIP.int_value:
653 cd_logic.eject()
656 def encodeit(self):
657 '''Thread to encode all tracks from the wavqueue'''
658 self.is_encoding = True
659 while True:
660 if self.stop_request:
661 break
662 (track, tracknum) = self.wavqueue.get(True)
663 if track == None:
664 break
666 if ENCODER.value == 'MP3':
667 status = self.get_lame(tracknum, track, self.artist, self.genre, self.album, self.year)
668 else:
669 status = self.get_ogg(tracknum, track, self.artist, self.genre, self.album, self.year)
671 if status <> 0:
672 print 'encoder died %d' % status
673 self.status_update(tracknum, 'enc_error', 0)
674 try: os.unlink(strip_illegal(track)+".wav")
675 except: pass
676 try: os.unlink(strip_illegal(track)+".inf")
677 except: pass
679 self.is_encoding = False
680 del self.wavqueue
683 def status_update(self, row, state, percent):
684 '''Callback from rip/encode threads to update display'''
685 g.threads_enter()
687 iter = self.store.get_iter((row,))
688 if not iter: return
690 if state == 'rip':
691 if percent < 100:
692 self.store.set_value(iter, COL_STATUS, _('Ripping')+': %d%%' % percent)
693 else:
694 self.store.set_value(iter, COL_STATUS, _('Ripping')+': '+_('done'))
696 if state == 'enc':
697 if percent < 100:
698 self.store.set_value(iter, COL_STATUS, _('Encoding')+': %d%%' % percent)
699 else:
700 self.store.set_value(iter, COL_STATUS, _('Encoding')+': '+_('done'))
702 if state == 'rip_error':
703 self.store.set_value(iter, COL_STATUS, _('Ripping')+': '+_('error'))
705 if state == 'enc_error':
706 self.store.set_value(iter, COL_STATUS, _('Encoding')+': '+_('error'))
708 g.threads_leave()
711 def activate(self, view, path, column):
712 '''Edit a track name'''
713 model, iter = self.view.get_selection().get_selected()
714 if iter:
715 track = model.get_value(iter, COL_TRACK)
716 dlg = g.Dialog(APP_NAME)
717 dlg.set_position(g.WIN_POS_NONE)
718 dlg.set_default_size(350, 100)
719 (a, b) = dlg.get_size()
720 (x, y) = self.get_position()
721 (dx, dy) = self.get_size()
722 dlg.move(x+dx/2-a/2, y+dy/2-b/2)
723 dlg.show()
725 entry = g.Entry()
726 entry.set_text(track)
727 dlg.set_position(g.WIN_POS_MOUSE)
728 entry.show()
729 entry.set_activates_default(True)
730 dlg.vbox.pack_start(entry)
732 dlg.add_button(g.STOCK_OK, g.RESPONSE_OK)
733 dlg.add_button(g.STOCK_CANCEL, g.RESPONSE_CANCEL)
734 dlg.set_default_response(g.RESPONSE_OK)
735 response = dlg.run()
737 if response == g.RESPONSE_OK:
738 track = entry.get_text()
739 #print track
740 model.set_value(iter, COL_TRACK, track)
741 self.view.columns_autosize()
743 dlg.destroy()
746 def toggle_check(self, cell, rownum):
747 '''Toggle state for each song'''
748 row = self.store[rownum]
749 row[COL_ENABLE] = not row[COL_ENABLE]
750 self.store.row_changed(rownum, row.iter)
753 def set_selection(self, thing):
754 '''Get current selection'''
755 #model, iter = self.view.get_selection().get_selected()
756 #if iter:
757 # track = model.get_value(iter, COL_TRACK)
759 def button_press(self, text, event):
760 '''Popup menu handler'''
761 if event.button != 3:
762 return 0
763 self.menu.popup(self, event)
764 return 1
766 def show_options(self, button=None):
767 '''Show Options dialog'''
768 rox.edit_options()
770 def get_options(self):
771 '''Get changed Options'''
772 pass
774 def delete_event(self, ev, e1):
775 '''Bye-bye'''
776 self.close()
778 def close(self, button = None):
779 '''We're outta here!'''
780 self.destroy()