~vorlon/ubuntu/saucy/gourmet/trunk

« back to all changes in this revision

Viewing changes to src/lib/FileChooserSaveAs.py

  • Committer: Bazaar Package Importer
  • Author(s): Rolf Leggewie
  • Date: 2008-07-26 13:29:41 UTC
  • Revision ID: james.westby@ubuntu.com-20080726132941-6ldd73qmacrzz0bn
Tags: upstream-0.14.0
ImportĀ upstreamĀ versionĀ 0.14.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
import gtk, gobject, os.path, fnmatch
 
2
 
 
3
from gettext import gettext as _
 
4
 
 
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/
 
10
 
 
11
    filename is an initial filename
 
12
 
 
13
    set_filter is whether to initially set the filter when opening a file
 
14
 
 
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.
 
17
 
 
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).
 
26
 
 
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.
 
29
 
 
30
    parent is the parent for our dialog.
 
31
    """
 
32
    def __init__ (self,
 
33
                  title,
 
34
                  filename=None,
 
35
                  filters=[],
 
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,
 
39
                  set_filter=True,
 
40
                  buttons=None,
 
41
                  show_filetype=True,
 
42
                  parent=None
 
43
                  ):
 
44
        self.parent=parent
 
45
        self.buttons=buttons
 
46
        self.set_filter=set_filter
 
47
        self.action=action
 
48
        self.filename=filename
 
49
        self.title=title
 
50
        self.filters=filters
 
51
        self.show_filetype=show_filetype
 
52
        self.setup_dialog()
 
53
        self.post_dialog()
 
54
 
 
55
    def post_dialog (self):
 
56
        """Run after the dialog is set up (to allow subclasses to do further setup)"""
 
57
        pass
 
58
 
 
59
    def setup_dialog (self):
 
60
        """Create our dialog"""
 
61
        self.setup_buttons()
 
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)
 
64
        if self.filename:
 
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)
 
68
        self.setup_filters()
 
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()
 
73
 
 
74
    def setup_buttons (self):
 
75
        """Set our self.buttons attribute"""
 
76
        if not self.buttons:
 
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)
 
79
            else:
 
80
                self.buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OK,gtk.RESPONSE_OK)
 
81
    
 
82
    def setup_filters (self):
 
83
        """Create and set filters for the dialog."""
 
84
        self.extensions = []
 
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)
 
89
            if filter_mime_types:
 
90
                for f in filter_mime_types:
 
91
                    filter.add_mime_type(f)
 
92
            if filter_patterns:
 
93
                for f in filter_patterns:
 
94
                    filter.add_pattern(f)
 
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])
 
99
 
 
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()
 
104
        # set up a ComboBox 
 
105
        self.it = gtk.icon_theme_get_default()
 
106
        ls=gtk.ListStore(str,gtk.gdk.Pixbuf)
 
107
        n = 0
 
108
        longest_word = 0
 
109
        self.ext_to_n = {}
 
110
        self.n_to_ext = {}
 
111
        for name,mimetypes,regexps in self.filters:
 
112
            if len(name) > longest_word:
 
113
                longest_word = len(name)
 
114
            image = None
 
115
            baseimage = None
 
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
 
118
            # icon.
 
119
            mimetypes = mimetypes[0:]
 
120
            mimetypes.reverse()
 
121
            while mimetypes and not image:
 
122
                mt = mimetypes.pop()
 
123
                if mt.find("/") > -1:
 
124
                    base,det=mt.split("/")
 
125
                else:
 
126
                    base = mt
 
127
                    det = ""
 
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]
 
133
            for r in regexps:
 
134
                ext = os.path.splitext(r)[-1]
 
135
                # ext_to_n let's us select the correct iter from the extension typed in
 
136
                self.ext_to_n[ext]=n
 
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])
 
140
            n += 1
 
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()
 
151
        l=gtk.Label()
 
152
        l.set_use_markup(True)
 
153
        l.set_text_with_mnemonic(_('Select File_type'))
 
154
        l.set_mnemonic_widget(self.saveas)
 
155
        self.hbox.add(l)
 
156
        self.hbox.add(self.saveas)
 
157
        self.hbox.show_all()
 
158
        self.fsd.set_extra_widget(self.hbox)
 
159
        self.fn = None
 
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
 
165
        # an idle call.
 
166
        self.timeout = gobject.timeout_add(100, self.update_filetype_widget)
 
167
        
 
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()
 
172
        if self.fn != fn:
 
173
            self.fn = fn
 
174
            if not fn:
 
175
                return True
 
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])
 
179
        return True
 
180
 
 
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]
 
189
        else:
 
190
            base = fn
 
191
        ext = self.n_to_ext[self.saveas.get_active()]
 
192
        if self.show_filetype:
 
193
            self.fsd.set_current_name(base + ext)
 
194
        else:
 
195
            self.fsd.set_current_name(base)
 
196
            
 
197
    def find_mime_icon (self, base, ext=None, size=48):
 
198
        prefixes = ['gnome','gnome-mime']
 
199
        while prefixes:
 
200
            prefix = prefixes.pop()
 
201
            name = prefix + "-" + base
 
202
            if ext:
 
203
                name = name + "-" + ext
 
204
            if self.it.has_icon(name):
 
205
                return self.it.load_icon(name, size, gtk.ICON_LOOKUP_USE_BUILTIN)
 
206
 
 
207
    def is_extension_legal (self, fn):
 
208
        for e in self.extensions:
 
209
            if fnmatch.fnmatch(fn, e):
 
210
                return True
 
211
 
 
212
    def run (self):
 
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(
 
223
                    default=False,
 
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?"),
 
226
                    parent=self.fsd,
 
227
                    cancel=False, # cancel==No in this case
 
228
                    custom_yes='_Replace',
 
229
                    custom_no=gtk.STOCK_CANCEL,
 
230
                    ):
 
231
                    return self.run()
 
232
            self.quit()
 
233
            return fn
 
234
        else:
 
235
            self.quit()
 
236
            return None
 
237
 
 
238
    def quit (self, *args):
 
239
        print 'removing timeout'
 
240
        if hasattr(self,'timeout'):
 
241
            gobject.source_remove(self.timeout)
 
242
        self.fsd.destroy()
 
243
 
 
244
def saveas_file (title,
 
245
                 filename=None,
 
246
                 filters=[],
 
247
                 action=gtk.FILE_CHOOSER_ACTION_SAVE,
 
248
                 set_filter=True,
 
249
                 buttons=None,
 
250
                 parent=None,
 
251
                 show_filetype=True):
 
252
 
 
253
    """Select a file and return a tuple containing
 
254
    the filename and the export type (the string the user selected)"""
 
255
 
 
256
    sfd=FileSelectorDialog(title,filename=filename,filters=filters,
 
257
                           action=action,set_filter=set_filter,buttons=buttons,
 
258
                           show_filetype=show_filetype,parent=parent)
 
259
    retval = sfd.run()
 
260
    if not retval:
 
261
        return None,None    
 
262
    exp_type = None
 
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()
 
267
        for r in rgxps:
 
268
            if os.path.splitext(r)[1] == ext:
 
269
                exp_type = name
 
270
    return retval,exp_type
 
271
 
 
272
if __name__ == '__main__':
 
273
    w=gtk.Window()
 
274
    w.connect('delete_event',gtk.main_quit)
 
275
    vb = gtk.VBox()
 
276
    w.add(vb)
 
277
    def msg (args):
 
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']],
 
284
             ]
 
285
    for s,f in [
 
286
        ['Save as (show extensions)', 
 
287
         lambda *args: msg(saveas_file('Save As...',filename='~/duck.pdf',
 
288
                                       filters=filters))
 
289
         ],
 
290
        ['Save as (hide extensions)',
 
291
         lambda *args: msg(saveas_file('Save As...',filename='~/duck.pdf',
 
292
                                       filters=filters, show_filetype=False))
 
293
         ]
 
294
        ]:
 
295
        b = gtk.Button(s)
 
296
        b.connect('clicked',f)
 
297
        vb.add(b)
 
298
    vb.show_all()
 
299
    w.show_all()
 
300
    gtk.main()