~ubuntu-branches/debian/wheezy/phatch/wheezy

« back to all changes in this revision

Viewing changes to phatch/pyWx/dialogs.py

  • Committer: Bazaar Package Importer
  • Author(s): Emilio Pozuelo Monfort
  • Date: 2008-02-13 23:48:47 UTC
  • Revision ID: james.westby@ubuntu.com-20080213234847-mp6vc4y88a9rz5qz
Tags: upstream-0.1
ImportĀ upstreamĀ versionĀ 0.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: UTF-8 -*-
 
2
 
 
3
# Phatch - Photo Batch Processor
 
4
# Copyright (C) 2007-2008 www.stani.be
 
5
#
 
6
# This program is free software: you can redistribute it and/or modify
 
7
# it under the terms of the GNU General Public License as published by
 
8
# the Free Software Foundation, either version 3 of the License, or
 
9
# (at your option) any later version.
 
10
#
 
11
# This program is distributed in the hope that it will be useful,
 
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
14
# GNU General Public License for more details.
 
15
 
16
# You should have received a copy of the GNU General Public License
 
17
# along with this program.  If not, see http://www.gnu.org/licenses/
 
18
#
 
19
# Phatch recommends SPE (http://pythonide.stani.be) for editing python files.
 
20
 
 
21
#---first test
 
22
import os, time, types
 
23
 
 
24
if __name__ == '__main__':
 
25
    import sys
 
26
    sys.path.extend(['lib','../core','../core/lib'])
 
27
    #test environment
 
28
    import gettext, sys
 
29
    gettext.install("test")
 
30
    sys.path.insert(0,os.path.dirname(os.getcwd()))
 
31
    
 
32
#---begin
 
33
import wx
 
34
from core import ct
 
35
 
 
36
#core.lib
 
37
import formField 
 
38
from core.message import send, ProgressReceiver
 
39
from core.pil import exif
 
40
 
 
41
#gui-dependent
 
42
import graphics, vlistTag, images, paint
 
43
from wildcard import wildcard_list, _wildcard_extension
 
44
from tag import Browser, ContentMixin
 
45
from pyWx.wxGlade   import dialogs
 
46
 
 
47
VLIST_ICON_SIZE = (48,48)
 
48
 
 
49
class BrowseMixin:
 
50
    def show_dir_dialog(self,defaultPath,message=_('Choose a folder'),
 
51
            style=wx.DEFAULT_DIALOG_STYLE): 
 
52
        dlg = wx.DirDialog(self,message,
 
53
            defaultPath = defaultPath,
 
54
            style       = wx.DEFAULT_DIALOG_STYLE)
 
55
        if dlg.ShowModal() == wx.ID_OK:
 
56
            path = dlg.GetPath()
 
57
        else:
 
58
            path = None
 
59
        dlg.Destroy()
 
60
        return path
 
61
 
 
62
class _IconDialog:
 
63
    if wx.Platform == '__WXGTK__':
 
64
        _icon_size  = (48,48)
 
65
    else:
 
66
        _icon_size  = (32,32)
 
67
    def _icon(self,name='information'):
 
68
        name    = 'ART_%s'%name.upper()
 
69
        #title icon
 
70
        bitmap  = graphics.bitmap(name,(16,16))
 
71
        _ic = wx.EmptyIcon()
 
72
        _ic.CopyFromBitmap(bitmap)
 
73
        self.SetIcon(_ic)
 
74
        #dialog icon
 
75
        bitmap  = graphics.bitmap(name,self._icon_size)
 
76
        self.icon.SetBitmap(bitmap)
 
77
        self.icon.Show(True)
 
78
 
 
79
class ErrorDialog(dialogs.ErrorDialog,_IconDialog):
 
80
    def __init__(self,parent,message,details,ignore=True,**keyw):
 
81
        super(ErrorDialog,self).__init__(parent,-1,**keyw)
 
82
        self._icon('error')
 
83
        self.message.SetLabel(message)
 
84
        #self._details(details)
 
85
        if not ignore: 
 
86
            self.ignore.Hide()
 
87
            self.skip.SetDefault()
 
88
        self.GetSizer().Fit(self)
 
89
        self.Layout()
 
90
        
 
91
    #---events
 
92
    def on_skip(self,event):
 
93
        self.EndModal(wx.ID_FORWARD)
 
94
    
 
95
    def on_abort(self,event):
 
96
        self.EndModal(wx.ID_ABORT)
 
97
    
 
98
    def on_ignore(self,event):
 
99
        self.EndModal(wx.ID_IGNORE)
 
100
        
 
101
    #---details
 
102
    def _details(self,details):
 
103
        #self.details_show(False)
 
104
        self.details.SetValue(details.strip())
 
105
        
 
106
    def details_show(self,show):
 
107
        sizer   = self.GetSizer()
 
108
        self.details.Show(show)
 
109
        self.SetMinSize(sizer.GetMinSize())
 
110
        self.Fit()
 
111
        
 
112
    def on_details(self,event):
 
113
        self.details_show(event.IsChecked())
 
114
    
 
115
class ExecuteDialog(BrowseMixin,dialogs.ExecuteDialog):
 
116
    def __init__(self,parent,drop=False,**options):
 
117
        super(ExecuteDialog,self).__init__(parent,-1,**options)
 
118
        self.set_drop(drop)
 
119
        if wx.Platform == '__WXGTK__':
 
120
            width = 600
 
121
        else:
 
122
            width = 450
 
123
        self.SetSize((width,self.GetSize()[1]))
 
124
        
 
125
    def __do_layout(self,*args,**keyw):
 
126
        super(ExecuteDialog,self).__do_layout(*args,**keyw)
 
127
        self.save_metadata = wx.CheckBox(self, -1, 
 
128
            _("Save metadata")+" (exif && itpc)")
 
129
        self.options_sizer.Add(self.save_metadata, 0, wx.ALIGN_CENTER_VERTICAL, 6)
 
130
        self.Layout()
 
131
        
 
132
    def browse_files(self):
 
133
        style   = wx.OPEN | wx.MULTIPLE | wx.CHANGE_DIR
 
134
        if hasattr(wx,'FD_PREVIEW'):
 
135
            style   |= wx.FD_PREVIEW
 
136
        dlg = wx.FileDialog(
 
137
            self, message   = _("Choose File(s)"),
 
138
            defaultDir      = self.get_default_path(),
 
139
            defaultFile     = "",
 
140
            wildcard        = self.wildcard(),
 
141
            style           = style,
 
142
            )
 
143
        if dlg.ShowModal() == wx.ID_OK:
 
144
            self.path.SetValue(ct.PATH_DELIMITER.join(dlg.GetPaths()))
 
145
        dlg.Destroy()
 
146
            
 
147
    def browse_folder(self): 
 
148
        path    = self.show_dir_dialog(
 
149
            defaultPath = self.get_default_path(),
 
150
            message     = _("Choose an image folder")) 
 
151
        if path != None:
 
152
            self.path.SetValue(path)
 
153
        
 
154
    def get_default_path(self):
 
155
        path    = self.path.GetValue().split(ct.PATH_DELIMITER)[0] 
 
156
        return os.path.dirname(path)
 
157
    
 
158
    def export_settings(self,settings):
 
159
        settings['paths']      = self.path.GetValue().split(ct.PATH_DELIMITER)
 
160
        settings['extensions'] = [self.extensions.GetString(i) \
 
161
            for i in range(self.extensions.GetCount()) \
 
162
            if self.extensions.IsChecked(i)]
 
163
        settings['recursive']  = self.recursive.GetValue()
 
164
        settings['save_metadata']  = self.save_metadata.GetValue()
 
165
        settings['stop_for_errors'] = self.stop_for_errors.GetValue()
 
166
        settings['overwrite_existing_images'] = \
 
167
            self.overwrite_existing_images.GetValue() 
 
168
        settings['check_images_first'] = self.check_images_first.GetValue() 
 
169
        wx.GetApp()._saveSettings()
 
170
        
 
171
    def get_selected_extensions(self):
 
172
        result  = []
 
173
        exts    = formField.IMAGE_READ_EXTENSIONS
 
174
        for index, extension in enumerate(exts):
 
175
            if self.extensions.IsChecked(index):
 
176
                result.append(extension)
 
177
        return result
 
178
                
 
179
    def set_drop(self,drop):
 
180
        if drop:
 
181
            #change title
 
182
            self.SetTitle(_('Drag & Drop'))
 
183
            #radio box
 
184
            self.source.Hide()
 
185
            #hide browse & path
 
186
            self.browse.Hide()
 
187
            self.path.Hide()
 
188
            #layout (only allow vertical fit)
 
189
            grid_sizer  = self.GetSizer()
 
190
            size        = (self.GetSize()[0], grid_sizer.GetMinSize()[1])
 
191
            self.SetMinSize(size)
 
192
            self.Fit()
 
193
        
 
194
    def import_settings(self,settings):
 
195
        #path
 
196
        self.path.SetValue(ct.PATH_DELIMITER.join(settings['paths']))
 
197
        #extensions
 
198
        exts    = formField.IMAGE_READ_EXTENSIONS
 
199
        self.extensions.Set(exts)
 
200
        for index, extension in enumerate(exts):
 
201
            if extension in settings['extensions']:
 
202
                self.extensions.Check(index)
 
203
        #overwrite existing images
 
204
        self.overwrite_existing_images.SetValue(
 
205
            settings['overwrite_existing_images'] or\
 
206
            settings['overwrite_existing_images_forced'])
 
207
        #overwrite existing files
 
208
        self.check_images_first.SetValue(
 
209
            settings['check_images_first'])
 
210
        #recursive
 
211
        self.recursive.SetValue(settings['recursive'])
 
212
        #save_metadata
 
213
        self.save_metadata.SetValue(settings['save_metadata'])
 
214
        #errors
 
215
        self.stop_for_errors.SetValue(settings['stop_for_errors'])
 
216
 
 
217
    #---wildcard
 
218
    def wildcard(self):
 
219
        extensions  = self.get_selected_extensions()
 
220
        selected    = wildcard_list(_('All selected image types'),
 
221
                        extensions)
 
222
        default     = wildcard_list(_('All default image types'),
 
223
                        formField.IMAGE_EXTENSIONS)
 
224
        result      = [selected,default]
 
225
        result.extend([('%s '+_('images')+'|%s')\
 
226
                    %(ext,_wildcard_extension(ext))
 
227
                    for ext in extensions])
 
228
        return '|'.join(result)
 
229
        
 
230
    #---events
 
231
    def on_browse(self,event):
 
232
        if self.source.GetSelection() == 0:
 
233
            self.browse_folder()
 
234
        else:
 
235
            self.browse_files()
 
236
        
 
237
    def on_default(self,event):
 
238
        exts    = formField.IMAGE_READ_EXTENSIONS
 
239
        for index, extension in enumerate(exts):
 
240
            if extension in formField.IMAGE_EXTENSIONS:
 
241
                self.extensions.Check(index,True)
 
242
            else:
 
243
                self.extensions.Check(index,False)
 
244
                
 
245
    def on_source(self,event):
 
246
        self.browse.SetLabel(_('Browse')+' '+event.GetString())
 
247
        
 
248
class FilesDialog(dialogs.FilesDialog,_IconDialog):
 
249
    def __init__(self,parent,message,title,files,icon='warning',**keyw):
 
250
        super(FilesDialog,self).__init__(parent,-1,
 
251
            style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.MAXIMIZE_BOX,
 
252
            **keyw)
 
253
        self.SetTitle(title)
 
254
        self.message.SetLabel(message)
 
255
        self.list.InsertColumn(0, _("File"))
 
256
        self.list.InsertColumn(1, _("Folder"))
 
257
        for index, f in enumerate(files):
 
258
            index = self.list.InsertStringItem(index, os.path.basename(f))
 
259
            self.list.SetStringItem(index, 1, os.path.dirname(f))
 
260
        self.list.SetColumnWidth(0, wx.LIST_AUTOSIZE)
 
261
        self.list.SetColumnWidth(1, wx.LIST_AUTOSIZE)
 
262
        min = 100
 
263
        if self.list.GetColumnWidth(0) < min:
 
264
            self.list.SetColumnWidth(0, min)
 
265
        self._icon(icon)
 
266
        
 
267
class ProgressDialog(wx.ProgressDialog,ProgressReceiver):
 
268
    """+1 is added because eg opening a file is also an action"""
 
269
    def __init__(self,parent,title,parent_max=1,child_max=1,message=''):
 
270
        ProgressReceiver.__init__(self,parent_max,child_max)
 
271
        wx.ProgressDialog.__init__(self,
 
272
                title   = title,
 
273
                message = message,
 
274
                maximum = self.max,
 
275
                parent  = parent,
 
276
                style   = wx.PD_CAN_ABORT
 
277
                            | wx.PD_APP_MODAL
 
278
                            | wx.PD_REMAINING_TIME
 
279
                            | wx.PD_SMOOTH
 
280
            )
 
281
        self.Bind(wx.EVT_CLOSE,self.close,self)
 
282
        
 
283
    #---pubsub event methods
 
284
    def close(self,event=None):
 
285
        self.unsubscribe_all()
 
286
        self.Destroy()
 
287
        
 
288
    def update(self,result,value,**message):
 
289
        """Fix for wxPython2.6"""
 
290
        status = self.Update(value,**message)
 
291
        if type(status) == bool:
 
292
            result['keepgoing'] = result['skip'] = status
 
293
        else:
 
294
            result['keepgoing'], result['skip'] = status
 
295
        if result['keepgoing']:
 
296
            self.Refresh()
 
297
        else:
 
298
            self.close()
 
299
 
 
300
    def sleep(self):
 
301
        time.sleep(0.001)
 
302
        
 
303
class ActionListBox(ContentMixin,vlistTag.Box):
 
304
    #---vlist.Box obligatory overwritten
 
305
    def SetTag(self,tag=_('all')):
 
306
        super(ActionListBox,self).SetTag(tag)
 
307
        #process tag
 
308
        self.tag                = tag
 
309
        if tag == _('Select'):
 
310
            tag = _('default')
 
311
        #choose tag actions
 
312
        if tag == _('all'):
 
313
            self.tag_actions    = self.all_actions
 
314
        else:
 
315
            self.tag_actions    = [a for a in self.all_actions 
 
316
                                    if tag in a.tags or _(a.label) == _('Save')]
 
317
        #take filter in account
 
318
        self.SetFilter(self.GetFilter().GetValue())
 
319
            
 
320
    def SetFilter(self,filter):
 
321
        if filter.strip():
 
322
            self.actions    = [a for a in self.tag_actions 
 
323
                                if filter in a.meta]
 
324
        else:
 
325
            self.actions    = self.tag_actions
 
326
        self.actions.sort(cmp=lambda x,y: cmp(_(x.label),_(y.label)))
 
327
        self.SetItemCount(len(self.actions))
 
328
        self.GetParent().CheckEmpty()
 
329
        self.RefreshAll()
 
330
        wx.GetTopLevelParent(self).ok.Enable(bool(self.actions))
 
331
        
 
332
    def _events(self):
 
333
        self.Bind(wx.EVT_CONTEXT_MENU, self.OnContextMenu)
 
334
 
 
335
    #---actions
 
336
    def SetActions(self,actions):
 
337
        self.all_actions    = actions.values()
 
338
        for action in self.all_actions:
 
339
            self.MetaAction(action)
 
340
        
 
341
    def MetaAction(self,action):
 
342
        action.tags = [_(tag) for tag in action.tags]
 
343
        action.meta = ' '.join([
 
344
            _(action.label).lower(),
 
345
            _(action.__doc__).lower(),
 
346
            ' '.join(action.tags)
 
347
        ])
 
348
        
 
349
    def OnContextMenu(self,event):
 
350
        # todo: does contextmenu always have to be recreated?
 
351
        #create id
 
352
        self.id_view_source = wx.NewId()
 
353
        #create menu
 
354
        menu = wx.Menu()
 
355
        item = wx.MenuItem(menu, self.id_view_source,_("View Source"))
 
356
        item.SetBitmap(graphics.bitmap('ART_FIND',(16,16)))
 
357
        self.Bind(wx.EVT_MENU, self.OnViewSource, id=self.id_view_source)
 
358
        menu.AppendItem(item)
 
359
        #show menu
 
360
        self.PopupMenu(menu)
 
361
        #destroy menu
 
362
        menu.Destroy()
 
363
        
 
364
    def OnViewSource(self,event):
 
365
        import actions
 
366
        action      = self.actions[self.list.GetSelection()]
 
367
        module      = action.__module__.split('.')[-1]
 
368
        filename    = os.path.join(ct.ACTIONS_PATH,'%s.py'%module)
 
369
        message     = open(filename).read()
 
370
        dir, base   = os.path.split(filename)
 
371
        send.frame_show_scrolled_message(message,'%s - %s'%(base,dir),
 
372
            size=(600,300))
 
373
            
 
374
    def RefreshList(self):
 
375
        self.actions.sort(cmp=lambda x,y: cmp(_(x.label),_(y.label)))
 
376
        self.Clear()
 
377
        self.SetItemCount(len(self.actions))
 
378
        self.RefreshAll()
 
379
        
 
380
    def GetItem(self, n):
 
381
        action  = self.actions[n]
 
382
        return (_(action.label),_(action.__doc__), 
 
383
            graphics.bitmap(action.icon,self.GetIconSize()))
 
384
    
 
385
    def GetStringSelection(self):
 
386
        return self.actions[self.GetSelection()].label
 
387
    
 
388
    def IsEmpty(self):
 
389
        return not (hasattr(self,'actions') and self.actions)
 
390
    
 
391
class ActionBrowser(Browser):
 
392
    ContentCtrl     = ActionListBox
 
393
    
 
394
    paint_message   = _("broaden your search")
 
395
    paint_colour    = images.LOGO_COLOUR
 
396
    #paint_logo      = images.LOGO
 
397
        
 
398
class ActionDialog(paint.Mixin,vlistTag.Dialog):
 
399
    ContentBrowser  = ActionBrowser
 
400
    
 
401
    def __init__(self,parent,actions,*args,**keyw):
 
402
        #extract tags
 
403
        tags    = self.ExtractTags(actions.values())
 
404
        #init dialog
 
405
        super(ActionDialog,self).__init__(parent,tags,*args,**keyw)
 
406
        #configure listbox
 
407
        list_box    = self.GetListBox()
 
408
        list_box.SetActions(actions)
 
409
        list_box.SetIconSize(VLIST_ICON_SIZE)
 
410
        list_box.SetTag(_('default'))
 
411
            
 
412
    def ExtractTags(self,actions):
 
413
        """Called by SetActions."""
 
414
        tags    = vlistTag.extract_tags(actions)
 
415
        tags.remove(_('default'))
 
416
        tags.sort()
 
417
        tags    = [_('Select'),_('all')]+tags
 
418
        return tags
 
419
        
 
420
    def GetListBox(self):
 
421
        return self.browser.content
 
422
        
 
423
    def GetStringSelection(self):
 
424
        return self.GetListBox().GetStringSelection()
 
425
    
 
426
class WritePluginDialog(dialogs.WritePluginDialog,_IconDialog):
 
427
    def __init__(self,parent,message,**keyw):
 
428
        super(WritePluginDialog,self).__init__(parent,-1,**keyw)
 
429
        path    = os.path.join(ct.PATH,'templates', 'action.py')
 
430
        self._icon('information')
 
431
        self.message.SetLabel(message)
 
432
        self.path.SetLabel('%s: %s'%(_('Path'),path))
 
433
        self._code(path)
 
434
        self.template_show(False)
 
435
        
 
436
    def _code(self,path):
 
437
        self.code.SetValue(open(path).read())
 
438
        self.code.SetMinSize((660,300))
 
439
        self.code.SetFont(wx.Font(10, wx.TELETYPE, wx.NORMAL, wx.NORMAL, 0, ""))
 
440
 
 
441
    #---events
 
442
    def on_help(self,event):
 
443
        import webbrowser
 
444
        url = ct.SEND_MAIL%('Writing an action plugin for Phatch',
 
445
                "As I know PIL, I'd like to write a plugin which does ... "
 
446
                "but I still have this question: ... "
 
447
            )
 
448
        webbrowser.open(url)
 
449
        
 
450
    def on_template(self,event):
 
451
        self.template_show(event.IsChecked())
 
452
 
 
453
    def template_show(self,show):
 
454
        sizer   = self.GetSizer()
 
455
        self.path.Show(show)
 
456
        self.code.Show(show)
 
457
        self.SetMinSize(sizer.GetMinSize())
 
458
        self.Fit()
 
459
        self.code.ShowPosition(0)
 
460
 
 
461
def test():
 
462
    class App(wx.App):
 
463
        def OnInit(self):
 
464
            wx.InitAllImageHandlers()
 
465
            frame = wx.Frame(None, -1, "")
 
466
            self.SetTopWindow(frame)
 
467
            frame.Show(False)
 
468
            wx.CallAfter(self.show_dialogs)
 
469
            return 1
 
470
        
 
471
        def show_dialogs(self):
 
472
##            self.show_error_dialog()
 
473
##            self.show_execute_dialog()
 
474
##            self.show_files_dialog()
 
475
##            self.show_progress_dialog()
 
476
            self.show_action_dialog()
 
477
            self.GetTopWindow().Destroy()
 
478
            
 
479
        def show_error_dialog(self):
 
480
            dlg = ErrorDialog(self.GetTopWindow(),'message','details')
 
481
            dlg.ShowModal()
 
482
            dlg.Destroy()
 
483
            
 
484
        def show_execute_dialog(self,drop=False):
 
485
            dlg = ExecuteDialog(self.GetTopWindow(),drop)
 
486
            dlg.ShowModal()
 
487
            dlg.Destroy()
 
488
            
 
489
        def show_files_dialog(self):
 
490
            dlg = FilesDialog(self.GetTopWindow(),'message','title',
 
491
                ['path/file'],'warning')
 
492
            dlg.ShowModal()
 
493
            dlg.Destroy()
 
494
            
 
495
        def show_progress_dialog(self):
 
496
            from core.lib.events import send
 
497
            import time
 
498
            n       = 5
 
499
            dlg     = ProgressDialog(self.GetTopWindow(),'title','messages',n)
 
500
            result  = {}
 
501
            for value in range(n):
 
502
                send.progress_update(result,value)
 
503
                if not result['keepgoing']: 
 
504
                    break
 
505
                time.sleep(1)
 
506
            dlg.Destroy()
 
507
            
 
508
        def show_action_dialog(self):
 
509
            from core import api
 
510
            api.init()
 
511
            dlg     = ActionDialog(self.GetTopWindow(),api.ACTIONS,
 
512
                size=(400,500))
 
513
            dlg.ShowModal()
 
514
            dlg.Destroy()
 
515
            
 
516
            
 
517
    app = App(0)
 
518
    app.MainLoop()
 
519
    
 
520
if __name__ == '__main__':
 
521
    test()
 
 
b'\\ No newline at end of file'