1
import gtk, gobject, os.path, fnmatch
3
from gettext import gettext as _
5
class FileSelectorDialog:
6
"""We set up a file dialog. If we are SAVEing and we have filters,
7
we set up a nice ComboBox to let the user choose the filetype of the file,
8
trying to immitate the functionality described here:
9
http://www.gnome.org/~seth/designs/filechooser-spec/
11
filename is an initial filename
13
set_filter is whether to initially set the filter when opening a file
15
buttons are a list of buttons and responses to add to the dialog. If these
16
aren't provided, we'll provide sensible buttons depending on the action.
18
filters are lists of a name for the filter that the user will see, a list of
19
mime types the filter should include, and a list of patterns. In the case of
20
a save-as dialog, we will use the filters to set-up our choose-your-type dialog,
21
giving the name as the string, choosing an icon based on the
22
mime-type (if we can find one), and using the first pattern to
23
name our output file. NOTE: For this to work properly, the
24
patterns must simply be *.extension (since we use the extension
25
from the pattern directly).
27
if show_filetype is True, we show the filename exactly as it is
28
saved. If it is false, we hide the extension from the user.
30
parent is the parent for our dialog.
36
# filters are lists of a name, a list of mime types and a list of
37
# patterns ['Plain Text', ['text/plain'], ['*.txt']
38
action=gtk.FILE_CHOOSER_ACTION_SAVE,
46
self.set_filter=set_filter
48
self.filename=filename
51
self.show_filetype=show_filetype
55
def post_dialog (self):
56
"""Run after the dialog is set up (to allow subclasses to do further setup)"""
59
def setup_dialog (self):
60
"""Create our dialog"""
62
self.fsd = gtk.FileChooserDialog(self.title,action=self.action,parent=self.parent,buttons=self.buttons)
63
self.fsd.set_default_response(gtk.RESPONSE_OK)
65
path,name=os.path.split(os.path.expanduser(self.filename))
66
if path: self.fsd.set_current_folder(path)
67
if name: self.fsd.set_current_name(name)
69
if self.action==gtk.FILE_CHOOSER_ACTION_SAVE:
70
# a stupid hack until GNOME finally realizes the proper filechooser widget
71
# described here: http://www.gnome.org/~seth/designs/filechooser-spec/
72
self.setup_saveas_widget()
74
def setup_buttons (self):
75
"""Set our self.buttons attribute"""
77
if self.action==gtk.FILE_CHOOSER_ACTION_OPEN or self.action==gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER:
78
self.buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK)
80
self.buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OK,gtk.RESPONSE_OK)
82
def setup_filters (self):
83
"""Create and set filters for the dialog."""
85
for fil in self.filters:
86
filter = gtk.FileFilter()
87
filter_name, filter_mime_types, filter_patterns = fil
88
filter.set_name(filter_name)
90
for f in filter_mime_types:
91
filter.add_mime_type(f)
93
for f in filter_patterns:
95
self.extensions.append(f)
96
self.fsd.add_filter(filter)
97
if self.set_filter and self.filters:
98
self.fsd.set_filter(self.fsd.list_filters()[0])
100
def setup_saveas_widget (self):
101
"""We imitate the functionality we want GNOME to have not long
102
from now and we provide a saveas widget."""
103
self.saveas = gtk.ComboBox()
105
self.it = gtk.icon_theme_get_default()
106
ls=gtk.ListStore(str,gtk.gdk.Pixbuf)
111
for name,mimetypes,regexps in self.filters:
112
if len(name) > longest_word:
113
longest_word = len(name)
116
# we're going to turn this list around and pop off one
117
# item at a time in order and see if we have a corresponding
119
mimetypes = mimetypes[0:]
121
while mimetypes and not image:
123
if mt.find("/") > -1:
124
base,det=mt.split("/")
128
image = self.find_mime_icon(base,det)
129
if not image and not baseimage:
130
baseimage = self.find_mime_icon(base)
131
# n_to_ext let's us grab the correct extension from our active iter
132
self.n_to_ext[n]=os.path.splitext(regexps[0])[-1]
134
ext = os.path.splitext(r)[-1]
135
# ext_to_n let's us select the correct iter from the extension typed in
137
# for example, if there's no gnome-mime-text-plain, we'll settle for gnome-mime-text
138
if not image: image=baseimage
139
ls.append([name,image])
141
self.saveas.set_model(ls)
142
crp = gtk.CellRendererPixbuf()
143
crp.set_property('xalign',0) #line up our pixbufs nicely
144
self.saveas.pack_start(crp, expand=False)
145
self.saveas.add_attribute(crp, 'pixbuf', 1)
146
self.saveas.connect('changed', self.change_file_extension)
147
crt = gtk.CellRendererText()
148
self.saveas.pack_start(crt, expand=True)
149
self.saveas.add_attribute(crt, 'text', 0)
150
self.hbox = gtk.HBox()
152
l.set_use_markup(True)
153
l.set_text_with_mnemonic(_('Select File_type'))
154
l.set_mnemonic_widget(self.saveas)
156
self.hbox.add(self.saveas)
158
self.fsd.set_extra_widget(self.hbox)
160
self.fsd.connect('selection-changed',self.update_filetype_widget)
161
self.internal_extension_change=False
162
self.update_filetype_widget()
163
# and now for a hack -- since we can't connect to the Entry widget,
164
# we're going to simply check to see if the filename has changed with
166
self.timeout = gobject.timeout_add(100, self.update_filetype_widget)
168
def update_filetype_widget (self, *args):
169
"""Update the filetype widget based on the contents
170
of the entry widget."""
171
fn=self.fsd.get_filename()
176
ext=os.path.splitext(fn)[1]
177
if self.ext_to_n.has_key(ext):
178
self.saveas.set_active(self.ext_to_n[ext])
181
def change_file_extension (self, *args):
182
print 'changing extension'
183
#if self.internal_extension_change: return
184
fn = os.path.split(self.fsd.get_filename())[1]
185
# strip off the old extension if it was one of our
186
# filetypes and now we're changing
187
if self.is_extension_legal(fn):
188
base = os.path.splitext(fn)[0]
191
ext = self.n_to_ext[self.saveas.get_active()]
192
if self.show_filetype:
193
self.fsd.set_current_name(base + ext)
195
self.fsd.set_current_name(base)
197
def find_mime_icon (self, base, ext=None, size=48):
198
prefixes = ['gnome','gnome-mime']
200
prefix = prefixes.pop()
201
name = prefix + "-" + base
203
name = name + "-" + ext
204
if self.it.has_icon(name):
205
return self.it.load_icon(name, size, gtk.ICON_LOOKUP_USE_BUILTIN)
207
def is_extension_legal (self, fn):
208
for e in self.extensions:
209
if fnmatch.fnmatch(fn, e):
213
"""Run our dialog and return the filename or None"""
214
response = self.fsd.run()
215
if response == gtk.RESPONSE_OK:
216
fn = self.fsd.get_filename()
217
if self.action==gtk.FILE_CHOOSER_ACTION_SAVE:
218
# add the extension if need be...
219
if not self.is_extension_legal(fn):
220
fn += self.n_to_ext[self.saveas.get_active()]
221
# if we're saving, we warn the user before letting them save over something.
222
if os.path.exists(fn) and not getBoolean(
224
label=_("A file named %s already exists.")%os.path.split(fn)[1],
225
sublabel=_("Do you want to replace it with the one you are saving?"),
227
cancel=False, # cancel==No in this case
228
custom_yes='_Replace',
229
custom_no=gtk.STOCK_CANCEL,
238
def quit (self, *args):
239
print 'removing timeout'
240
if hasattr(self,'timeout'):
241
gobject.source_remove(self.timeout)
244
def saveas_file (title,
247
action=gtk.FILE_CHOOSER_ACTION_SAVE,
253
"""Select a file and return a tuple containing
254
the filename and the export type (the string the user selected)"""
256
sfd=FileSelectorDialog(title,filename=filename,filters=filters,
257
action=action,set_filter=set_filter,buttons=buttons,
258
show_filetype=show_filetype,parent=parent)
263
base,ext = os.path.splitext(retval)
264
filters = filters[0:] #we're about to destroy this list!
265
while filters and not exp_type:
266
name,mime,rgxps = filters.pop()
268
if os.path.splitext(r)[1] == ext:
270
return retval,exp_type
272
if __name__ == '__main__':
274
w.connect('delete_event',gtk.main_quit)
278
for a in args: print a
279
filters=[['Plain Text',['text/plain'],['*.txt']],
280
['PDF',['application/pdf'],['*.pdf']],
281
['Postscript',['application/postscript'],['*.ps']],
282
['Web Page (HTML)',['text/html'],['*.htm','*.html']],
283
['Jpeg Image',['image/jpeg'],['*.jpg','*.jpeg']],
286
['Save as (show extensions)',
287
lambda *args: msg(saveas_file('Save As...',filename='~/duck.pdf',
290
['Save as (hide extensions)',
291
lambda *args: msg(saveas_file('Save As...',filename='~/duck.pdf',
292
filters=filters, show_filetype=False))
296
b.connect('clicked',f)