3 GUI front-end to cdda2wav and lame.
5 Copyright 2004 Kenneth Hayber <khayber@socal.rr.com>
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
, threading
, Queue
24 from random
import Random
25 from threading
import *
28 from rox
import i18n
, app_options
, Menu
, filer
, InfoWin
29 from rox
.options
import Option
31 import PyCDDB
, cd_logic
, CDROM
, genres
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
= rox
.app_dir
46 #Options.xml processing
47 from rox
import choices
48 choices
.migrate(APP_NAME
, 'hayber.us')
49 rox
.setup_app_options(APP_NAME
, site
='hayber.us')
50 Menu
.set_save_name(APP_NAME
, site
='hayber.us')
52 #assume that everyone puts their music in ~/Music
53 LIBRARY
= Option('library', '~/Music')
56 RIPPER
= Option('ripper', 'cdda2wav')
57 RIPPER_DEV
= Option('ripper_dev', '/dev/cdrom')
58 RIPPER_LUN
= Option('ripper_lun', 'ATAPI:0,1,0')
59 RIPPER_OPTS
= Option('ripper_opts', '-x -H')
61 EJECT_AFTER_RIP
= Option('eject_after_rip', '0')
64 ENCODER
= Option('encoder', 'MP3')
66 MP3_ENCODER
= Option('mp3_encoder', 'lame')
67 MP3_ENCODER_OPTS
= Option('mp3_encoder_opts', '--vbr-new -b160 --nohist --add-id3v2')
69 OGG_ENCODER
= Option('ogg_encoder', 'oggenc')
70 OGG_ENCODER_OPTS
= Option('ogg_encoder_opts', '-q5')
72 #CDDB Server and Options
73 CDDB_SERVER
= Option('cddb_server', 'http://freedb.freedb.org/~cddb/cddb.cgi')
75 rox
.app_options
.notify()
85 # My gentoo python doesn't have universal line ending support compiled in
86 # and these guys (cdda2wav and lame) use CR line endings to pretty-up their output.
88 '''Return a line of input using \r or \n as terminators'''
90 while '\n' not in line
and '\r' not in line
:
92 if char
== '': return line
98 '''Return the full path of an executable if found on the path'''
99 if (filename
== None) or (filename
== ''):
102 env_path
= os
.getenv('PATH').split(':')
104 if os
.access(p
+'/'+filename
, os
.X_OK
):
105 return p
+'/'+filename
109 def strip_illegal(instr
):
110 '''remove illegal (filename) characters from string'''
113 str = string
.translate(str, string
.maketrans(r
'/+{}*.?', r
'--()___'))
117 class Ripper(rox
.Window
):
118 '''Rip and Encode a CD'''
120 rox
.Window
.__init
__(self
)
122 self
.set_title(APP_NAME
)
123 self
.set_default_size(450, 500)
124 self
.set_position(gtk
.WIN_POS_MOUSE
)
126 #capture wm delete event
127 self
.connect("delete_event", self
.delete_event
)
129 # Update things when options change
130 rox
.app_options
.add_notify(self
.get_options
)
134 #######################################
135 swin
= gtk
.ScrolledWindow()
136 self
.scroll_window
= swin
137 swin
.set_policy(gtk
.POLICY_AUTOMATIC
, gtk
.POLICY_AUTOMATIC
)
138 swin
.set_shadow_type(gtk
.SHADOW_IN
)
140 self
.store
= gtk
.ListStore(int, str, str, str)
141 view
= gtk
.TreeView(self
.store
)
144 view
.set_rules_hint(True)
146 cell
= gtk
.CellRendererToggle()
147 cell
.connect('toggled', self
.toggle_check
)
148 column
= gtk
.TreeViewColumn('', cell
, active
=COL_ENABLE
)
149 view
.append_column(column
)
150 column
.set_resizable(False)
151 column
.set_reorderable(False)
153 cell
= gtk
.CellRendererText()
154 column
= gtk
.TreeViewColumn(_('Track'), cell
, text
= COL_TRACK
)
155 view
.append_column(column
)
156 column
.set_resizable(True)
157 #column.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
158 column
.set_reorderable(False)
160 cell
= gtk
.CellRendererText()
161 column
= gtk
.TreeViewColumn(_('Time'), cell
, text
= COL_TIME
)
162 view
.append_column(column
)
163 column
.set_resizable(True)
164 column
.set_reorderable(False)
166 cell
= gtk
.CellRendererText()
167 column
= gtk
.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
= gtk
.Toolbar()
178 self
.toolbar
.set_style(gtk
.TOOLBAR_ICONS
)
179 self
.toolbar
.insert_stock(gtk
.STOCK_PREFERENCES
,
180 _('Settings'), None, self
.show_options
, None, 0)
181 self
.stop_btn
= self
.toolbar
.insert_stock(gtk
.STOCK_STOP
,
182 _('Stop'), None, self
.stop
, None, 0)
183 self
.rip_btn
= self
.toolbar
.insert_stock(gtk
.STOCK_EXECUTE
,
184 _('Rip & Encode'), None, self
.rip_n_encode
, None, 0)
185 self
.refresh_btn
= self
.toolbar
.insert_stock(gtk
.STOCK_REFRESH
,
186 _('Reload CD'), None, self
.do_get_tracks
, None, 0)
188 self
.toolbar
.insert_stock(gtk
.STOCK_GO_UP
,
189 _('Show destination dir'), None, self
.show_dir
, None, 0)
191 self
.toolbar
.insert_stock(gtk
.STOCK_CLOSE
,
192 _('Close'), None, self
.close
, None, 0)
195 self
.table
= gtk
.Table(5, 2, False)
199 self
.artist_entry
= gtk
.Entry(max=255)
200 self
.artist_entry
.connect('changed', self
.stuff_changed
)
201 self
.table
.attach(gtk
.Label(str=_('Artist')), 0, 1, 2, 3, 0, 0, 4, y_pad
)
202 self
.table
.attach(self
.artist_entry
, 1, 2, 2, 3, gtk
.EXPAND|gtk
.FILL
, 0, x_pad
, y_pad
)
204 self
.album_entry
= gtk
.Entry(max=255)
205 self
.album_entry
.connect('changed', self
.stuff_changed
)
206 self
.table
.attach(gtk
.Label(str=_('Album')), 0, 1, 3, 4, 0, 0, 4, y_pad
)
207 self
.table
.attach(self
.album_entry
, 1, 2, 3, 4, gtk
.EXPAND|gtk
.FILL
, 0, x_pad
, y_pad
)
209 genres
.genre_list
.sort()
210 self
.genre_combo
= gtk
.Combo()
211 self
.genre_combo
.set_popdown_strings(genres
.genre_list
)
212 self
.genre_combo
.entry
.connect('changed', self
.stuff_changed
)
213 self
.table
.attach(gtk
.Label(str=_('Genre')), 0, 1, 4, 5, 0, 0, 4, y_pad
)
214 self
.table
.attach(self
.genre_combo
, 1, 2, 4, 5, gtk
.EXPAND|gtk
.FILL
, 0, x_pad
, y_pad
)
216 self
.year_entry
= gtk
.Entry(max=4)
217 self
.year_entry
.connect('changed', self
.stuff_changed
)
218 self
.table
.attach(gtk
.Label(str=_('Year')), 0, 1, 5, 6, 0, 0, 4, y_pad
)
219 self
.table
.attach(self
.year_entry
, 1, 2, 5, 6, gtk
.EXPAND|gtk
.FILL
, 0, x_pad
, y_pad
)
222 # Create layout, pack and show widgets
223 self
.vbox
= gtk
.VBox()
225 self
.vbox
.pack_start(self
.toolbar
, False, True, 0)
226 self
.vbox
.pack_start(self
.table
, False, True, 0)
227 self
.vbox
.pack_start(self
.scroll_window
, True, True, 0)
231 self
.add_events(gtk
.gdk
.BUTTON_PRESS_MASK
)
232 self
.connect('button-press-event', self
.button_press
)
233 view
.add_events(gtk
.gdk
.BUTTON_PRESS_MASK
)
234 view
.connect('button-press-event', self
.button_press
)
236 self
.menu
= Menu
.Menu('main', [
237 Menu
.Action(_('Rip & Encode'), 'rip_n_encode', '', gtk
.STOCK_EXECUTE
),
238 Menu
.Action(_('Reload CD'), 'do_get_tracks', '', gtk
.STOCK_REFRESH
),
239 Menu
.Action(_('Stop'), 'stop', '', gtk
.STOCK_STOP
),
241 Menu
.Action(_('Options'), 'show_options', '', gtk
.STOCK_PREFERENCES
),
242 Menu
.Action(_('Info'), 'get_info', '', gtk
.STOCK_DIALOG_INFO
),
243 Menu
.Action(_("Quit"), 'close', '', gtk
.STOCK_CLOSE
),
245 self
.menu
.attach(self
,self
)
249 self
.ripper_thd
= None
250 self
.encoder_thd
= None
251 self
.is_ripping
= False
252 self
.is_encoding
= False
253 self
.is_cddbing
= False
254 self
.stop_request
= False
256 cd_logic
.set_dev(RIPPER_DEV
.value
)
257 self
.cd_status
= cd_logic
.check_dev()
259 self
.cd_status_changed
= False
261 if self
.cd_status
in [CDROM
.CDS_TRAY_OPEN
, CDROM
.CDS_NO_DISC
]:
265 gtk
.timeout_add(1000, self
.update_gui
)
268 def update_gui(self
):
269 '''Update button status based on current state'''
270 cd_status
= cd_logic
.check_dev()
271 if self
.cd_status
!= cd_status
:
272 self
.cd_status
= cd_status
273 self
.cd_status_changed
= True
275 if self
.is_ripping
or self
.is_encoding
:
276 self
.stop_btn
.set_sensitive(True)
277 self
.rip_btn
.set_sensitive(False)
278 self
.refresh_btn
.set_sensitive(False)
280 if not self
.is_ripping
and not self
.is_encoding
and not self
.is_cddbing
:
281 self
.stop_btn
.set_sensitive(False)
282 self
.rip_btn
.set_sensitive(True)
283 self
.refresh_btn
.set_sensitive(True)
285 #get tracks if cd changed and not doing other things
286 if self
.cd_status_changed
:
287 if self
.cd_status
in [CDROM
.CDS_TRAY_OPEN
, CDROM
.CDS_NO_DISC
]:
291 disc_id
= cd_logic
.get_disc_id()
292 if self
.disc_id
<> disc_id
:
293 self
.disc_id
= disc_id
295 self
.cd_status_changed
= False
297 #need this to keep the timer running(?)
301 def stuff_changed(self
, button
=None):
302 '''Get new text from edit boxes and save it'''
303 self
.genre
= self
.genre_combo
.entry
.get_text()
304 self
.artist
= self
.artist_entry
.get_text()
305 self
.album
= self
.album_entry
.get_text()
306 self
.year
= self
.year_entry
.get_text()
310 '''Run a function in a thread'''
311 thd_it
= Thread(name
='mythread', target
=it
)
312 thd_it
.setDaemon(True)
318 '''Stop current rip/encode process'''
319 self
.stop_request
= True
321 def show_dir(self
, *dummy
):
322 ''' Pops up a filer window. '''
323 temp
= os
.path
.join(os
.path
.expanduser(LIBRARY
.value
), self
.artist
, self
.album
)
324 filer
.show_file(temp
)
326 def do_get_tracks(self
, button
=None):
327 '''Get the track info (cddb and cd) in a thread'''
330 self
.cddb_thd
= self
.runit(self
.get_tracks
)
334 '''Clear all info and display <no disc>'''
335 #print "no disc in tray?"
338 self
.artist_entry
.set_text(_('<no disc>'))
339 self
.album_entry
.set_text('')
340 self
.genre_combo
.entry
.set_text('')
341 self
.year_entry
.set_text('')
342 self
.view
.columns_autosize()
346 def get_tracks(self
):
347 '''Get the track info (cddb and cd)'''
348 self
.is_cddbing
= True
349 stuff
= self
.get_cddb()
351 (count
, artist
, album
, genre
, year
, tracklist
) = stuff
352 #print count, artist, album, genre, year, tracklist
359 self
.tracklist
= tracklist
361 if artist
: self
.artist_entry
.set_text(artist
)
362 if album
: self
.album_entry
.set_text(album
)
363 if genre
: self
.genre_combo
.entry
.set_text(genre
)
364 if year
: self
.year_entry
.set_text(year
)
367 for track
in tracklist
:
369 iter = self
.store
.append(None)
370 self
.store
.set(iter, COL_TRACK
, track
[0])
371 self
.store
.set(iter, COL_TIME
, track
[1])
372 self
.store
.set(iter, COL_ENABLE
, True)
374 self
.view
.columns_autosize()
376 self
.is_cddbing
= False
380 '''Query cddb for track and cd info'''
382 dlg
= gtk
.MessageDialog(buttons
=gtk
.BUTTONS_CANCEL
, message_format
="Getting Track Info.")
383 # dlg.set_position(gtk.WIN_POS_NONE)
384 # (a, b) = dlg.get_size()
385 # (x, y) = self.get_position()
386 # (dx, dy) = self.get_size()
387 # dlg.move(x+dx/2-a/2, y+dy/2-b/2)
391 count
= artist
= genre
= album
= year
= ''
395 #Note: all the nested try|except|pass statements are to ensure that as much
396 #info is processed as possible. One exception should not stop
397 #the whole thing and return nothing.
400 count
= cd_logic
.total_tracks()
401 cddb_id
= cd_logic
.get_cddb_id()
403 #PyCDDB wants a string delimited by spaces, go figure.
406 cddb_id_string
+= str(n
)+' '
408 #print cddb_id, cddb_id_string
410 for i
in range(count
):
411 tracktime
= cd_logic
.get_track_time_total(i
+1)
412 track_time
= time
.strftime('%M:%S', time
.gmtime(tracktime
))
413 tracklist
.append((_('Track')+`i`
,track_time
))
416 db
= PyCDDB
.PyCDDB(CDDB_SERVER
.value
)
417 query_info
= db
.query(cddb_id_string
)
421 dlg
.set_title(_('Got Disc Info'))
424 #make sure we didn't get an error, then query CDDB
425 if len(query_info
) > 0:
427 index
= rndm
.randrange(0, len(query_info
))
428 read_info
= db
.read(query_info
[index
])
431 dlg
.set_title(_('Got Track Info'))
435 (artist
, album
) = query_info
[index
]['title'].split('/')
436 artist
= artist
.strip()
437 album
= album
.strip()
438 genre
= query_info
[index
]['category']
439 if genre
in ['misc', 'data']:
442 print query_info
['year']
443 print read_info
['EXTD']
444 print read_info
['YEARD']
446 #x = re.match(r'.*YEAR: (.+).*',read_info['EXTD'])
453 if len(read_info
['TTITLE']) > 0:
454 for i
in range(count
):
456 track_name
= read_info
['TTITLE'][i
]
457 track_time
= tracklist
[i
][1]
458 #print i, track_name, track_time
459 tracklist
[i
] = (track_name
, track_time
)
471 return count
, artist
, album
, genre
, year
, tracklist
474 def get_cdda2wav(self
, tracknum
, track
):
475 '''Run cdda2wav to rip a track from the CD'''
476 cdda2wav_cmd
= RIPPER
.value
477 cdda2wav_dev
= RIPPER_DEV
.value
478 cdda2wav_lun
= RIPPER_LUN
.value
479 cdda2wav_args
= '-g -D%s -A%s -t %d "%s"' % (
480 cdda2wav_lun
, cdda2wav_dev
, tracknum
+1, strip_illegal(track
))
481 cdda2wav_opts
= RIPPER_OPTS
.value
482 #print cdda2wav_opts, cdda2wav_args
484 thing
= popen2
.Popen4(cdda2wav_cmd
+' '+cdda2wav_opts
+' '+cdda2wav_args
)
485 outfile
= thing
.fromchild
488 line
= myreadline(outfile
)
490 x
= re
.match("(.+[\s]+)([0-9]+)%", line
)
492 percent
= int(x
.group(2))
493 self
.status_update(tracknum
, 'rip', percent
)
496 if self
.stop_request
:
499 if self
.stop_request
:
500 os
.kill(thing
.pid
, signal
.SIGKILL
)
503 self
.status_update(tracknum
, 'rip', 100)
508 def get_lame(self
, tracknum
, track
, artist
, genre
, album
, year
):
509 '''Run lame to encode a wav file to mp3'''
515 lame_cmd
= MP3_ENCODER
.value
516 lame_opts
= MP3_ENCODER_OPTS
.value
517 lame_tags
= '--ta "%s" --tt "%s" --tl "%s" --tg "%s" --tn %d --ty %d' % (
518 artist
, track
, album
, genre
, tracknum
+1, int_year
)
519 lame_args
= '"%s" "%s"' % (strip_illegal(track
)+'.wav', strip_illegal(track
)+'.mp3')
521 #print lame_opts, lame_tags, lame_args
523 thing
= popen2
.Popen4(lame_cmd
+' '+lame_opts
+' '+lame_tags
+' '+lame_args
)
524 outfile
= thing
.fromchild
527 line
= myreadline(outfile
)
530 #for some reason getting this right for lame was a royal pain.
531 x
= re
.match(r
"^[\s]+([0-9]+)/([0-9]+)", line
)
533 percent
= int(100 * (float(x
.group(1)) / float(x
.group(2))))
534 self
.status_update(tracknum
, 'enc', percent
)
537 if self
.stop_request
:
540 if self
.stop_request
:
541 os
.kill(thing
.pid
, signal
.SIGKILL
)
544 filename
= strip_illegal(track
)+'.mp3'
545 xattr
.setxattr(filename
, 'user.Title', track
)
546 xattr
.setxattr(filename
, 'user.Artist', artist
)
547 xattr
.setxattr(filename
, 'user.Album', album
)
548 xattr
.setxattr(filename
, 'user.Genre', genre
)
549 xattr
.setxattr(filename
, 'user.Track', '%d' % tracknum
)
550 xattr
.setxattr(filename
, 'user.Year', year
)
555 self
.status_update(tracknum
, 'enc', 100)
560 def get_ogg(self
, tracknum
, track
, artist
, genre
, album
, year
):
561 '''Run oggenc to encode a wav file to ogg'''
567 ogg_cmd
= OGG_ENCODER
.value
568 ogg_opts
= OGG_ENCODER_OPTS
.value
569 ogg_tags
= '-a "%s" -t "%s" -l "%s" -G "%s" -N %d -d %d' % (
570 artist
, track
, album
, genre
, tracknum
+1, int_year
)
571 ogg_args
= '"%s"' % (strip_illegal(track
)+'.wav')
573 #print ogg_opts, ogg_tags, ogg_args
575 thing
= popen2
.Popen4(ogg_cmd
+' '+ogg_opts
+' '+ogg_tags
+' '+ogg_args
)
576 outfile
= thing
.fromchild
579 line
= myreadline(outfile
)
582 #for some reason getting this right for ogg was a royal pain.
583 x
= re
.match('^.*\[[\s]*([.0-9]+)%\]', line
)
585 percent
= float(x
.group(1))
586 self
.status_update(tracknum
, 'enc', percent
)
589 if self
.stop_request
:
592 if self
.stop_request
:
593 os
.kill(thing
.pid
, signal
.SIGKILL
)
596 filename
= strip_illegal(track
)+'.ogg'
597 xattr
.setxattr(filename
, 'user.Title', track
)
598 xattr
.setxattr(filename
, 'user.Artist', artist
)
599 xattr
.setxattr(filename
, 'user.Album', album
)
600 xattr
.setxattr(filename
, 'user.Genre', genre
)
601 xattr
.setxattr(filename
, 'user.Track', '%d' % tracknum
)
602 xattr
.setxattr(filename
, 'user.Year', year
)
607 self
.status_update(tracknum
, 'enc', 100)
612 def rip_n_encode(self
, button
=None):
613 '''Process all selected tracks (rip and encode)'''
615 os
.chdir(os
.path
.expanduser(LIBRARY
.value
))
618 os
.mkdir(os
.path
.expanduser(LIBRARY
.value
))
619 os
.chdir(os
.path
.expanduser(LIBRARY
.value
))
621 rox
.alert("Failed to find or create Library dir")
623 if self
.count
and self
.artist
and self
.album
:
624 try: os
.mkdir(self
.artist
)
627 try: os
.mkdir(self
.artist
+'/'+self
.album
)
630 try: os
.chdir(self
.artist
+'/'+self
.album
)
633 self
.stop_request
= False
635 #the queue to feed tracks from ripper to encoder
636 self
.wavqueue
= Queue
.Queue(1000)
638 self
.ripper_thd
= self
.runit(self
.ripit
)
639 self
.encoder_thd
= self
.runit(self
.encodeit
)
643 '''Thread to rip all selected tracks'''
644 self
.is_ripping
= True
645 for i
in range(self
.count
):
646 if self
.stop_request
:
649 if self
.store
[i
][COL_ENABLE
]:
650 track
= self
.store
[i
][COL_TRACK
]
652 status
= self
.get_cdda2wav(i
, track
)
654 print 'cdda2wav died %d' % status
655 self
.status_update(i
, 'rip_error', 0)
657 #push this track on the queue for the encoder
659 self
.wavqueue
.put((track
, i
))
661 #push None object to tell encoder we're done
663 self
.wavqueue
.put((None, None))
665 self
.is_ripping
= False
667 if EJECT_AFTER_RIP
.int_value
:
672 '''Thread to encode all tracks from the wavqueue'''
673 self
.is_encoding
= True
675 if self
.stop_request
:
677 (track
, tracknum
) = self
.wavqueue
.get(True)
681 if ENCODER
.value
== 'MP3':
682 status
= self
.get_lame(tracknum
, track
, self
.artist
, self
.genre
, self
.album
, self
.year
)
684 status
= self
.get_ogg(tracknum
, track
, self
.artist
, self
.genre
, self
.album
, self
.year
)
687 print 'encoder died %d' % status
688 self
.status_update(tracknum
, 'enc_error', 0)
689 try: os
.unlink(strip_illegal(track
)+".wav")
691 try: os
.unlink(strip_illegal(track
)+".inf")
694 self
.is_encoding
= False
698 def status_update(self
, row
, state
, percent
):
699 '''Callback from rip/encode threads to update display'''
702 iter = self
.store
.get_iter((row
,))
707 self
.store
.set_value(iter, COL_STATUS
, _('Ripping')+': %d%%' % percent
)
709 self
.store
.set_value(iter, COL_STATUS
, _('Ripping')+': '+_('done'))
713 self
.store
.set_value(iter, COL_STATUS
, _('Encoding')+': %d%%' % percent
)
715 self
.store
.set_value(iter, COL_STATUS
, _('Encoding')+': '+_('done'))
717 if state
== 'rip_error':
718 self
.store
.set_value(iter, COL_STATUS
, _('Ripping')+': '+_('error'))
720 if state
== 'enc_error':
721 self
.store
.set_value(iter, COL_STATUS
, _('Encoding')+': '+_('error'))
726 def activate(self
, view
, path
, column
):
727 '''Edit a track name'''
728 model
, iter = self
.view
.get_selection().get_selected()
730 track
= model
.get_value(iter, COL_TRACK
)
731 dlg
= gtk
.Dialog(APP_NAME
)
732 # dlg.set_position(gtk.WIN_POS_NONE)
733 dlg
.set_default_size(350, 100)
734 # (a, b) = dlg.get_size()
735 # (x, y) = self.get_position()
736 # (dx, dy) = self.get_size()
737 # dlg.move(x+dx/2-a/2, y+dy/2-b/2)
741 entry
.set_text(track
)
742 # dlg.set_position(gtk.WIN_POS_MOUSE)
744 entry
.set_activates_default(True)
745 dlg
.vbox
.pack_start(entry
)
747 dlg
.add_button(gtk
.STOCK_OK
, gtk
.RESPONSE_OK
)
748 dlg
.add_button(gtk
.STOCK_CANCEL
, gtk
.RESPONSE_CANCEL
)
749 dlg
.set_default_response(gtk
.RESPONSE_OK
)
752 if response
== gtk
.RESPONSE_OK
:
753 track
= entry
.get_text()
755 model
.set_value(iter, COL_TRACK
, track
)
756 self
.view
.columns_autosize()
761 def toggle_check(self
, cell
, rownum
):
762 '''Toggle state for each song'''
763 row
= self
.store
[rownum
]
764 row
[COL_ENABLE
] = not row
[COL_ENABLE
]
765 self
.store
.row_changed(rownum
, row
.iter)
768 def set_selection(self
, thing
):
769 '''Get current selection'''
770 #model, iter = self.view.get_selection().get_selected()
772 # track = model.get_value(iter, COL_TRACK)
774 def button_press(self
, text
, event
):
775 '''Popup menu handler'''
776 if event
.button
!= 3:
778 self
.menu
.popup(self
, event
)
781 def show_options(self
, button
=None):
782 '''Show Options dialog'''
785 def get_options(self
):
786 '''Get changed Options'''
790 InfoWin
.infowin(APP_NAME
)
792 def delete_event(self
, ev
, e1
):
796 def close(self
, button
= None):
797 '''We're outta here!'''