1
# -*- coding: UTF-8 -*-
3
# Phatch - Photo Batch Processor
4
# Copyright (C) 2007-2008 www.stani.be
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.
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.
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/
19
# Phatch recommends SPE (http://pythonide.stani.be) for editing python files.
22
import os, time, types
24
if __name__ == '__main__':
26
sys.path.extend(['lib','../core','../core/lib'])
29
gettext.install("test")
30
sys.path.insert(0,os.path.dirname(os.getcwd()))
38
from core.message import send, ProgressReceiver
39
from core.pil import exif
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
47
VLIST_ICON_SIZE = (48,48)
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:
63
if wx.Platform == '__WXGTK__':
67
def _icon(self,name='information'):
68
name = 'ART_%s'%name.upper()
70
bitmap = graphics.bitmap(name,(16,16))
72
_ic.CopyFromBitmap(bitmap)
75
bitmap = graphics.bitmap(name,self._icon_size)
76
self.icon.SetBitmap(bitmap)
79
class ErrorDialog(dialogs.ErrorDialog,_IconDialog):
80
def __init__(self,parent,message,details,ignore=True,**keyw):
81
super(ErrorDialog,self).__init__(parent,-1,**keyw)
83
self.message.SetLabel(message)
84
#self._details(details)
87
self.skip.SetDefault()
88
self.GetSizer().Fit(self)
92
def on_skip(self,event):
93
self.EndModal(wx.ID_FORWARD)
95
def on_abort(self,event):
96
self.EndModal(wx.ID_ABORT)
98
def on_ignore(self,event):
99
self.EndModal(wx.ID_IGNORE)
102
def _details(self,details):
103
#self.details_show(False)
104
self.details.SetValue(details.strip())
106
def details_show(self,show):
107
sizer = self.GetSizer()
108
self.details.Show(show)
109
self.SetMinSize(sizer.GetMinSize())
112
def on_details(self,event):
113
self.details_show(event.IsChecked())
115
class ExecuteDialog(BrowseMixin,dialogs.ExecuteDialog):
116
def __init__(self,parent,drop=False,**options):
117
super(ExecuteDialog,self).__init__(parent,-1,**options)
119
if wx.Platform == '__WXGTK__':
123
self.SetSize((width,self.GetSize()[1]))
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)
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
137
self, message = _("Choose File(s)"),
138
defaultDir = self.get_default_path(),
140
wildcard = self.wildcard(),
143
if dlg.ShowModal() == wx.ID_OK:
144
self.path.SetValue(ct.PATH_DELIMITER.join(dlg.GetPaths()))
147
def browse_folder(self):
148
path = self.show_dir_dialog(
149
defaultPath = self.get_default_path(),
150
message = _("Choose an image folder"))
152
self.path.SetValue(path)
154
def get_default_path(self):
155
path = self.path.GetValue().split(ct.PATH_DELIMITER)[0]
156
return os.path.dirname(path)
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()
171
def get_selected_extensions(self):
173
exts = formField.IMAGE_READ_EXTENSIONS
174
for index, extension in enumerate(exts):
175
if self.extensions.IsChecked(index):
176
result.append(extension)
179
def set_drop(self,drop):
182
self.SetTitle(_('Drag & Drop'))
188
#layout (only allow vertical fit)
189
grid_sizer = self.GetSizer()
190
size = (self.GetSize()[0], grid_sizer.GetMinSize()[1])
191
self.SetMinSize(size)
194
def import_settings(self,settings):
196
self.path.SetValue(ct.PATH_DELIMITER.join(settings['paths']))
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'])
211
self.recursive.SetValue(settings['recursive'])
213
self.save_metadata.SetValue(settings['save_metadata'])
215
self.stop_for_errors.SetValue(settings['stop_for_errors'])
219
extensions = self.get_selected_extensions()
220
selected = wildcard_list(_('All selected image types'),
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)
231
def on_browse(self,event):
232
if self.source.GetSelection() == 0:
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)
243
self.extensions.Check(index,False)
245
def on_source(self,event):
246
self.browse.SetLabel(_('Browse')+' '+event.GetString())
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,
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)
263
if self.list.GetColumnWidth(0) < min:
264
self.list.SetColumnWidth(0, min)
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,
276
style = wx.PD_CAN_ABORT
278
| wx.PD_REMAINING_TIME
281
self.Bind(wx.EVT_CLOSE,self.close,self)
283
#---pubsub event methods
284
def close(self,event=None):
285
self.unsubscribe_all()
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
294
result['keepgoing'], result['skip'] = status
295
if result['keepgoing']:
303
class ActionListBox(ContentMixin,vlistTag.Box):
304
#---vlist.Box obligatory overwritten
305
def SetTag(self,tag=_('all')):
306
super(ActionListBox,self).SetTag(tag)
309
if tag == _('Select'):
313
self.tag_actions = self.all_actions
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())
320
def SetFilter(self,filter):
322
self.actions = [a for a in self.tag_actions
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()
330
wx.GetTopLevelParent(self).ok.Enable(bool(self.actions))
333
self.Bind(wx.EVT_CONTEXT_MENU, self.OnContextMenu)
336
def SetActions(self,actions):
337
self.all_actions = actions.values()
338
for action in self.all_actions:
339
self.MetaAction(action)
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)
349
def OnContextMenu(self,event):
350
# todo: does contextmenu always have to be recreated?
352
self.id_view_source = wx.NewId()
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)
364
def OnViewSource(self,event):
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),
374
def RefreshList(self):
375
self.actions.sort(cmp=lambda x,y: cmp(_(x.label),_(y.label)))
377
self.SetItemCount(len(self.actions))
380
def GetItem(self, n):
381
action = self.actions[n]
382
return (_(action.label),_(action.__doc__),
383
graphics.bitmap(action.icon,self.GetIconSize()))
385
def GetStringSelection(self):
386
return self.actions[self.GetSelection()].label
389
return not (hasattr(self,'actions') and self.actions)
391
class ActionBrowser(Browser):
392
ContentCtrl = ActionListBox
394
paint_message = _("broaden your search")
395
paint_colour = images.LOGO_COLOUR
396
#paint_logo = images.LOGO
398
class ActionDialog(paint.Mixin,vlistTag.Dialog):
399
ContentBrowser = ActionBrowser
401
def __init__(self,parent,actions,*args,**keyw):
403
tags = self.ExtractTags(actions.values())
405
super(ActionDialog,self).__init__(parent,tags,*args,**keyw)
407
list_box = self.GetListBox()
408
list_box.SetActions(actions)
409
list_box.SetIconSize(VLIST_ICON_SIZE)
410
list_box.SetTag(_('default'))
412
def ExtractTags(self,actions):
413
"""Called by SetActions."""
414
tags = vlistTag.extract_tags(actions)
415
tags.remove(_('default'))
417
tags = [_('Select'),_('all')]+tags
420
def GetListBox(self):
421
return self.browser.content
423
def GetStringSelection(self):
424
return self.GetListBox().GetStringSelection()
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))
434
self.template_show(False)
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, ""))
442
def on_help(self,event):
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: ... "
450
def on_template(self,event):
451
self.template_show(event.IsChecked())
453
def template_show(self,show):
454
sizer = self.GetSizer()
457
self.SetMinSize(sizer.GetMinSize())
459
self.code.ShowPosition(0)
464
wx.InitAllImageHandlers()
465
frame = wx.Frame(None, -1, "")
466
self.SetTopWindow(frame)
468
wx.CallAfter(self.show_dialogs)
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()
479
def show_error_dialog(self):
480
dlg = ErrorDialog(self.GetTopWindow(),'message','details')
484
def show_execute_dialog(self,drop=False):
485
dlg = ExecuteDialog(self.GetTopWindow(),drop)
489
def show_files_dialog(self):
490
dlg = FilesDialog(self.GetTopWindow(),'message','title',
491
['path/file'],'warning')
495
def show_progress_dialog(self):
496
from core.lib.events import send
499
dlg = ProgressDialog(self.GetTopWindow(),'title','messages',n)
501
for value in range(n):
502
send.progress_update(result,value)
503
if not result['keepgoing']:
508
def show_action_dialog(self):
511
dlg = ActionDialog(self.GetTopWindow(),api.ACTIONS,
520
if __name__ == '__main__':
b'\\ No newline at end of file'