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 python editing.
30
from lib.pyWx.wxcheck import ensure
32
wx = ensure('2.8', '2.8')
37
#force wxpython2.6 for testing
39
#wxversion.select('2.6')
42
#check with other encoding
43
##wx.SetDefaultPyEncoding('ISO8859-15')
57
from core import config
59
from core.message import FrameReceiver
60
from lib import formField
61
from lib import notify
63
from lib import system
64
from lib import listData
65
from lib.unicoding import exception_to_unicode
66
notify.init(ct.INFO['name'])
69
from lib.pyWx import droplet
70
from lib.pyWx import graphics
71
from lib.pyWx import imageFileBrowser
72
from lib.pyWx import imageInspector
73
from lib.pyWx import paint
74
from lib.pyWx.clipboard import copy_text
79
from wxGlade import frame
81
WX_ENCODING = wx.GetDefaultPyEncoding()
83
_('You can paste it as text into the properties of a new launcher.')
84
ERROR_INSTALL_ACTION = \
85
_('Sorry, you need to install the %s action for this action list.')
86
WARNING_LOST_VALUES = \
87
_('Sorry, the values of these options will be lost in %(name)s %(version)s:')
88
CLIPBOARD_ACTIONLIST = \
89
_('The droplet command for this action list was copied to the clipboard.')
91
_('The droplet command for recent action lists was copied to the clipboard.')
92
CLIPBOARD_IMAGE_INSPECTOR = \
93
_('The droplet command for the image inspector was copied to the clipboard.')
95
_('In Phatch you need to open or create an action list first.') + ' ' + \
96
_('As an example try out the polaroid action list from the library.') + \
98
_('Afterwards you can drag&drop images on the Phatch window to batch them.') +\
100
_('For more information see the tutorials (Help>Documentation)')
105
if wx.Platform != '__WXGTK__':
109
def set_theme(name='default'):
110
if name == 'nuovext':
111
from nuovext import Provider
112
wx.ArtProvider.Push(Provider())
117
def findWindowById(id):
118
return wx.GetApp().GetTopWindow().FindWindowById(id)
122
_icon_filename = None
125
def show_error(self, message):
126
return self.show_message(message, style=wx.OK | wx.ICON_ERROR)
128
def show_execute_dialog(self, result, settings, files=None):
129
dlg = dialogs.ExecuteDialog(self, drop=files)
130
if settings['overwrite_existing_images_forced']:
131
dlg.overwrite_existing_images.Disable()
133
#store in settings, not result as it will be saved
134
settings['paths'] = files
135
dlg.import_settings(settings)
136
result['cancel'] = dlg.ShowModal() == wx.ID_CANCEL
140
#Retrieve settings from dialog
141
dlg.export_settings(settings)
144
def show_files_message(self, result, message, title, files):
145
dlg = dialogs.FilesDialog(self, message, title, files)
146
x0, y0 = self.GetSize()
147
x1, y1 = dlg.GetSize()
151
result['cancel'] = dlg.ShowModal() == wx.ID_CANCEL
153
def show_message(self, message, title='',
154
style=wx.OK | wx.ICON_EXCLAMATION):
159
dlg = wx.MessageDialog(parent,
161
'%(name)s ' % ct.INFO + title,
164
answer = dlg.ShowModal()
168
def show_status(self, message, log=True):
169
dlg = dialogs.StatusDialog(self)
171
dlg.SetMessage(message)
175
def show_question(self, message, style=wx.YES_NO | wx.ICON_QUESTION):
176
return self.show_message(message, style=style)
178
def show_image_tree(self, result, image_infos, widths, headers,
179
ok_label='&OK', buttons=False, modal=False):
180
data = listData.files_data_dict(image_infos)
181
dlg = dialogs.ImageTreeDialog(data, listData.DataDict, headers,
182
self, size=(600, dialogs.get_max_height(300)))
183
dlg.SetColumnWidths(*widths)
184
dlg.SetOkLabel(ok_label)
185
dlg.ShowButtons(buttons)
187
answer = dlg.ShowModal()
188
result['answer'] = answer == wx.ID_OK
192
def show_report(self):
193
report = wx.GetApp().report
195
self.show_image_tree({}, report,
196
widths=(200, 60, 60, 60, 500),
197
headers=['filename', 'width', 'height', 'mode',
200
modal=not isinstance(self, Frame))
202
self.show_message(_('No images have been processed to report.'))
205
if os.path.exists(ct.USER_LOG_PATH):
206
log_file = open(ct.USER_LOG_PATH)
207
msg = log_file.read().strip()
210
msg = _('Hooray, no issues!')
212
msg = _('Nothing has been logged yet.')
213
self.show_scrolled_message(msg, '%s - %s' \
214
% (_('Log'), ct.USER_LOG_PATH))
216
def show_info(self, message, title=''):
217
return self.show_message(message, title,
218
style=wx.OK | wx.ICON_INFORMATION)
220
def show_progress(self, title, parent_max, child_max=1, message=''):
221
dlg = dialogs.ProgressDialog(self, title, parent_max, child_max,
224
def show_progress_error(self, result, message, ignore=True):
225
message += '\n\n' + api.SEE_LOG
226
errorDlg = dialogs.ErrorDialog(self, message, ignore)
227
answer = errorDlg.ShowModal()
228
result['stop_for_errors'] = not errorDlg.future_errors.GetValue()
230
if answer == wx.ID_ABORT:
231
result['answer'] = _('abort')
233
elif answer == wx.ID_FORWARD:
234
result['answer'] = _('skip')
236
result['answer'] = _('ignore')
238
def show_scrolled_message(self, message, title, **keyw):
239
import wx.lib.dialogs
240
dlg = wx.lib.dialogs.ScrolledMessageDialog(self, message, title,
241
style=wx.DEFAULT_DIALOG_STYLE | wx.MAXIMIZE_BOX | wx.RESIZE_BORDER,
245
def show_notification(self, message, force=False, report=None):
246
self.set_report(report)
247
active = wx.GetApp().IsActive() or self.IsActive()
248
if force or not active:
250
title=system.filename_to_title(self.filename),
252
icon=self.get_icon_filename(),
253
wxicon=graphics.bitmap(images.ICON_PHATCH_64))
255
self.RequestUserAttention()
258
def get_setting(self, name):
259
return wx.GetApp().settings[name]
261
def set_setting(self, name, value):
262
wx.GetApp().settings[name] = value
265
def load_actionlist_data(self, filename):
266
if not os.path.exists(filename):
269
data, warnings = api.open_actionlist(filename)
270
except KeyError, details:
271
self.show_error(ERROR_INSTALL_ACTION\
272
% exception_to_unicode(details, WX_ENCODING))
275
self.show_error(api.ERROR_INCOMPATIBLE_ACTIONLIST % ct.INFO)
277
if data['invalid labels']:
278
self.show_message('\n'.join([
279
_('This action list was made by a different %(name)s version.')\
280
% ct.INFO + '\n\n' + \
281
WARNING_LOST_VALUES % ct.INFO + '\n',
282
'\n'.join(data['invalid labels'])]))
284
if formField.get_safe():
285
self.show_error('%s\n\n%s\n%s' % (
286
api.ERROR_UNSAFE_ACTIONLIST_INTRO, warnings,
287
api.ERROR_UNSAFE_ACTIONLIST_DISABLE_SAFE))
290
wx.CallAfter(self.show_error, '%s\n\n%s\n%s' % (
291
api.ERROR_UNSAFE_ACTIONLIST_INTRO, warnings,
292
api.ERROR_UNSAFE_ACTIONLIST_ACCEPT))
296
def _execute(self, actionlist, **keyw):
299
api.apply_actions_to_photos(actionlist, app.settings,
300
update=self._send_update_event, **keyw)
302
def _send_update_event(self):
303
update_event = imageInspector.UpdateEvent()
304
for frame in wx.GetTopLevelWindows():
306
wx.PostEvent(frame, update_event)
308
def set_report(self, report):
309
wx.GetApp().report = report
311
def get_icon_filename(self):
312
if self._icon_filename == None:
313
self._icon_filename = os.path.join(
314
self.get_setting("PHATCH_IMAGE_PATH"), 'icons',
315
'48x48', 'phatch.png')
316
return self._icon_filename
319
class Frame(DialogsMixin, dialogs.BrowseMixin, droplet.Mixin, paint.Mixin,
320
frame.Frame, FrameReceiver):
321
DEFAULT_PAINT_MESSAGE = _("click '+' to add actions")
322
paint_message = DEFAULT_PAINT_MESSAGE
323
paint_color = images.LOGO_COLOUR
324
paint_logo = images.LOGO
326
def __init__(self, actionlist, *args, **keyw):
327
frame.Frame.__init__(self, *args, **keyw)
329
self.dlg_library = None
331
self.EnableBackgroundPainting(self.empty)
335
self.on_menu_file_new()
336
images.set_icon(self, 'phatch')
343
if actionlist.endswith(ct.EXTENSION):
344
self._open(actionlist)
347
#make it eee pc friendly
349
self._max_height = dialogs.get_max_height()
350
self.SetSize((self._width, min(600, self._max_height)))
351
super(Frame, self).__set_properties()
354
plugin.install_frame(self)
356
def _description(self):
357
self.show_description(False)
360
self.SetAsFileDropTarget(self.tree, self.on_drop)
364
self.menu_file_export = \
365
self.menu_file_export_actionlist_to_clipboard.GetMenu()
367
self.menu_file_recent = wx.Menu()
368
self.filehistory = wx.FileHistory()
369
self.filehistory.UseMenu(self.menu_file_recent)
370
self._set_file_history(self.get_setting('file_history'))
371
self.Bind(wx.EVT_MENU_RANGE, self.on_menu_file_history,
372
id=wx.ID_FILE1, id2=wx.ID_FILE9)
373
self.menu_file.InsertMenu(2, wx.ID_REFRESH,
375
self.menu_file_recent, "")
378
#actionlists = [(system.filename_to_title(a), a)
379
# for a in wx.GetApp().get_action_list_files()]
381
#library = self.menu_file_library = wx.Menu()
382
#prefix = len(actionlists) < 10
383
#self.library_files = {}
384
#for index, actionlist in enumerate(actionlists):
386
# label = actionlist[0]
388
# label = '&%d %s' % (index + 1, label)
389
# item = library.Append(id, label)
390
# self.library_files[id] = actionlist[1]
391
# self.Bind(wx.EVT_MENU, self.on_menu_file_library, item)
394
#self.menu_file.InsertMenu(3, wx_ID_EDIT, _("Open &Library"),
396
self.library_files_dictionary = formField.files_dictionary(
397
paths=[config.PATHS["PHATCH_ACTIONLISTS_PATH"],
398
ct.USER_ACTIONLISTS_PATH],
403
#menu_item (for enabling/disabling)
404
edit = [getattr(self, attr) for attr in dir(self)
405
if attr[:10] == 'menu_edit_']
406
edit.remove(self.menu_edit_add)
407
view = [getattr(self, attr) for attr in dir(self)
408
if attr[:10] == 'menu_view_']
411
self.menu_file_new.GetId(),
412
self.menu_file_save.GetId(),
413
self.menu_file_save_as.GetId()]),
414
(self.menu_edit, [item.GetId() for item in edit]),
415
(self.menu_view, [item.GetId() for item in view]),
417
self.menu_tools_execute.GetId(),
418
self.menu_tools_show_report.GetId(),
419
self.menu_tools_show_log.GetId()]),
420
(self.menu_file_export, [
421
self.menu_file_export_actionlist_to_clipboard.GetId()])]
422
self.on_show_droplet(False)
424
if wx.Platform == "__WXMAC__":
425
#todo: about doesn't seem to work: why?!
427
#app.SetMacHelpMenuTitleName(_('&Help'))
428
app.SetMacAboutMenuItemId(wx.ID_ABOUT)
429
app.SetMacExitMenuItemId(wx.ID_EXIT)
430
#app.SetMacPreferencesMenuItemId(wx.ID_PREFERENCES)
432
self.menu_file.InsertSeparator(8)
435
path, filename = os.path.split(self.filename)
436
self.SetTitle(ct.FRAME_TITLE\
437
% (self.dirty, os.path.splitext(filename)[0]))
438
self.frame_statusbar.SetStatusText(path)
439
self.SetStatusText(ct.COPYRIGHT)
441
def is_protected_actionlist(self, filename):
442
return config.PATHS["PHATCH_ACTIONLISTS_PATH"] == \
443
os.path.dirname(filename)
446
def add_tool(self, bitmap, label, tooltip, method, item=wx.ITEM_NORMAL):
448
bitmap = graphics.bitmap(bitmap, self.tool_bitmap_size,
449
client=wx.ART_TOOLBAR)
450
args = (id, label, bitmap, wx.NullBitmap, item,
452
tool = self.frame_toolbar.AddLabelTool(*args)
453
self.Bind(wx.EVT_TOOL, method, id=id)
457
self.tool_bitmap_size = (32, 32)
458
self.frame_toolbar = wx.ToolBar(self, -1, style=wx.TB_FLAT)
459
self.SetToolBar(self.frame_toolbar)
460
self.frame_toolbar.SetToolBitmapSize(self.tool_bitmap_size)
462
self.add_tool('ART_FILE_OPEN', _("Open"),
463
_("Open an action list"), self.on_menu_file_open)]
465
self.add_tool('ART_EXECUTABLE_FILE', _("Execute"),
466
_("Execute the action"), self.on_menu_tools_execute)]
467
self.frame_toolbar.AddSeparator()
468
tools_item_other.extend([
469
self.add_tool('ART_ADD_BOOKMARK', _("Add"),
470
_("Add an action"), self.on_menu_edit_add)])
472
self.add_tool('ART_DEL_BOOKMARK', _("Remove"),
473
_("Remove the selected action"), self.on_menu_edit_remove),
474
self.add_tool('ART_GO_UP', _("Up"),
475
_("Move the selected action up"), self.on_menu_edit_up),
476
self.add_tool('ART_GO_DOWN', _("Down"),
477
_("Move the selected action down"), self.on_menu_edit_down)])
478
self.frame_toolbar.AddSeparator()
479
tools_item_other.extend([
480
self.add_tool('ART_FIND', _("Image Inspector"),
481
_("Look up exif and iptc tags"),
482
self.on_menu_tools_image_inspector)])
483
self.frame_toolbar.AddSeparator()
484
self.toolbar_description = self.add_tool('ART_TIP', _("Description"),
485
_("Show description of the action list"),
486
self.on_menu_view_description, item=wx.ITEM_CHECK)
487
tools_item.extend([self.toolbar_description])
488
self.tools_item = [tool.GetId() for tool in tools_item]
489
self.tools_all = tools_item + \
490
[tool.GetId() for tool in tools_item_other]
491
self._menu_toolbar_state = True
492
self.frame_toolbar.Realize()
494
def enable_actions(self, state=True):
495
if state != self._menu_toolbar_state:
496
for tool in self.tools_item:
497
self.frame_toolbar.EnableTool(tool, state)
498
for menu, items in self.menu_item:
500
menu.Enable(item, state)
501
self._menu_toolbar_state = state
502
self.tree.Show(state)
503
self.empty.Show(not state)
504
self.show_description(state and self.get_setting('description'))
506
def enable_menu(self, state=True):
507
self._menu_toolbar_state = None
508
menu = self.frame_menubar
509
for index in range(menu.GetMenuCount()):
510
menu.EnableTop(index, state)
512
def enable_toolbar(self, state=True):
513
self._menu_toolbar_state = None
514
for tool in self.tools_all:
515
self.frame_toolbar.EnableTool(tool, state)
517
def show_paint_message(self, message=None):
519
self.paint_message = self.DEFAULT_PAINT_MESSAGE
521
self.paint_message = message
525
def on_menu_file_new(self, event=None):
526
if self.is_save_not_ok():
528
self._set_filename(ct.UNKNOWN)
529
self.description.SetValue(ct.ACTION_LIST_DESCRIPTION)
530
self.saved_description = ct.ACTION_LIST_DESCRIPTION
532
self.enable_actions(False)
534
def on_menu_file_open_library(self, event):
535
if self.is_save_not_ok():
537
if not self.dlg_library:
538
self.dlg_library = imageFileBrowser.Dialog(
540
files=self.library_files_dictionary,
541
title=_('Library Action Lists'),
542
size=(self.GetSize()[0], 370),
543
icon_size=(128, 128))
544
self.dlg_library.image_list.Select(0)
545
if self.dlg_library.ShowModal() == wx.ID_OK:
546
filename = self.dlg_library.image_path.GetValue()
547
filename = self.library_files_dictionary.get(filename, filename)\
548
.replace('.png', '.phatch')
549
#21/9/2009 may be removed
550
#save_filename = os.path.join(ct.USER_ACTIONLISTS_PATH,
551
# os.path.basename(filename))
553
self.dlg_library.Hide()
556
def on_menu_file_open(self, event):
557
if self.is_save_not_ok():
559
dlg = wx.FileDialog(self,
560
message=_('Choose an Action List File...'),
561
defaultDir=os.path.dirname(self.filename),
562
wildcard=ct.WILDCARD,
565
if dlg.ShowModal() == wx.ID_OK:
566
filename = dlg.GetPath()
570
#def on_menu_file_library(self, event):
571
# if self.is_save_not_ok():
573
# filename = self.library_files[event.GetId()]
574
# save_filename = os.path.join(ct.USER_ACTIONLISTS_PATH,
575
# os.path.basename(filename))
576
# self._open(filename, save_filename)
577
def on_menu_file_save(self, event):
578
if self.filename == ct.UNKNOWN \
579
or self.is_protected_actionlist(self.filename):
580
return self.on_menu_file_save_as()
585
def on_menu_file_save_as(self, event=None):
586
if self.is_protected_actionlist(self.filename) \
587
or not os.path.isfile(self.filename):
588
default_dir = ct.USER_ACTIONLISTS_PATH
590
default_dir = os.path.dirname(self.filename)
591
dlg = wx.FileDialog(self,
592
message=_('Save Action List As...'),
593
defaultDir=default_dir,
594
wildcard=ct.WILDCARD,
595
style=wx.SAVE | wx.OVERWRITE_PROMPT,
597
if dlg.ShowModal() == wx.ID_OK:
600
if dlg.GetFilterIndex() == 0 \
601
and not os.path.splitext(path)[1]:
603
if os.path.exists(path) and self.show_question('%s %s'\
604
% (_('This file exists already.'),
605
_('Do you want to overwrite it?'))) == wx.ID_NO:
614
def on_menu_file_export_actionlist_to_clipboard(self, event):
615
if self.is_save_not_ok():
617
copy_text(ct.COMMAND['DROP'] % self.filename)
618
self.show_info(' '.join([CLIPBOARD_ACTIONLIST, COMMAND_PASTE]))
620
def on_menu_file_export_recent_to_clipboard(self, event):
621
if self.is_save_not_ok():
623
copy_text(ct.COMMAND['RECENT'])
624
self.show_info(' '.join([CLIPBOARD_RECENT, COMMAND_PASTE]))
626
def on_menu_file_export_inspector_to_clipboard(self, event):
627
if self.is_save_not_ok():
629
copy_text(ct.COMMAND['INSPECTOR'])
630
self.show_info(' '.join([CLIPBOARD_IMAGE_INSPECTOR,
633
def on_menu_file_quit(self, event):
636
def on_menu_file_history(self, event):
637
if self.is_save_not_ok():
639
# get the file based on the menu ID
640
filenum = event.GetId() - wx.ID_FILE1
641
filename = self.filehistory.GetHistoryFile(filenum)
644
def on_menu_edit_add(self, event):
645
settings = wx.GetApp().settings
646
if not hasattr(self, 'dialog_actions'):
647
self.dialog_actions = dialogs.ActionDialog(self,
648
api.ACTIONS, settings['tag_actions'],
649
size=(self._width, min(400, self._max_height)),
650
title=_("%(name)s actions") % ct.INFO)
651
if self.dialog_actions.ShowModal() == wx.ID_OK:
653
label = self.dialog_actions.GetStringSelection()
654
self.tree.append_form_by_label_to_selected(label)
655
self.enable_actions(True)
656
self.dialog_actions.Hide()
658
def on_menu_edit_remove(self, event):
659
if self.tree.remove_selected_form():
661
self.enable_actions(False)
662
self.set_dirty(False)
666
def on_menu_edit_up(self, event):
668
self.tree.move_form_selected_up()
670
def on_menu_edit_down(self, event):
672
self.tree.move_form_selected_down()
674
def on_menu_edit_enable(self, event):
675
self.tree.enable_selected_form(True)
677
def on_menu_edit_disable(self, event):
678
self.tree.enable_selected_form(False)
680
def on_menu_view_droplet(self, event):
681
self.show_droplet(event.IsChecked())
683
def droplet_label_format(self, x):
684
#return '123456789012345678901234567890'
687
def on_menu_view_description(self, event):
688
self.show_description(event.IsChecked())
690
def on_menu_view_expand_all(self, event):
691
self.tree.expand_forms()
693
def on_menu_view_collapse_all(self, event):
694
self.tree.collapse_forms()
696
def on_menu_view_collapse_automatic(self, event):
697
self.enable_collapse_automatic(event.IsChecked())
699
def on_menu_tools_execute(self, event):
700
actionlist = self.tree.export_forms()
701
self._execute(actionlist)
703
def on_menu_tools_safe(self, event):
704
self.set_safe_mode(event.IsChecked())
706
def on_menu_tools_image_inspector(self, event):
707
frame = dialogs.ImageInspectorFrame(self,
708
size=(470, dialogs.get_max_height(510)),
709
icon=images.get_icon('inspector'))
712
def on_menu_tools_browse_library_user(self, event):
713
system.start(wx.GetApp().settings['USER_DATA_PATH'])
715
def on_menu_tools_browse_library_phatch(self, event):
716
system.start(wx.GetApp().settings['PHATCH_DATA_PATH'])
718
def on_menu_tools_show_report(self, event):
721
def on_menu_tools_show_log(self, event):
724
def on_menu_tools_update_fonts(self, event):
725
config.check_fonts(True)
727
def on_menu_tools_python_shell(self, event):
728
from lib.pyWx import shell
729
self.tree.close_popup()
730
if self.shell is None:
731
title = ct.TITLE.lower()
732
self.shell = shell.Frame(self,
733
title=_('%(name)s Shell') % ct.INFO,
734
intro='%(name)s ' % ct.INFO,
736
'%s_%s' % (title, _('application')): wx.GetApp(),
737
'%s_%s' % (title, _('frame')): self,
738
'%s_%s' % (title, _('actions')): self.tree.export_forms,
740
icon=graphics.bitmap(images.ICON_PHATCH_64),
742
self.shell.Show(event.IsChecked())
744
def on_menu_help_website(self, event):
745
webbrowser.open('http://photobatch.stani.be')
747
def on_menu_help_documentation(self, event):
748
webbrowser.open('http://photobatch.stani.be/documentation')
750
def on_menu_help_forum(self, event):
751
webbrowser.open('http://photobatch.stani.be/forum')
753
def on_menu_help_translate(self, event):
755
'https://translations.launchpad.net/phatch/trunk/+pots/phatch')
757
def on_menu_help_bug(self, event):
758
webbrowser.open('https://bugs.launchpad.net/phatch')
760
def on_menu_help_plugin(self, event):
761
help_path = self.get_setting("PHATCH_DOCS_PATH")
762
help_file = os.path.join(help_path, 'index.html')
763
if not os.path.exists(help_file):
765
help_file = os.path.join(help_path, 'build', 'html',
767
if not os.path.exists(help_file):
769
help_file = os.path.join(sys.prefix, 'share', 'phatch', 'docs',
771
webbrowser.open(help_file)
772
dlg = dialogs.WritePluginDialog(self, '\n'.join([
773
_('A html tutorial will open in your internet browser.'),
775
_('You only need to know PIL to write a plugin for Phatch.'),
776
_('Phatch will generate the user interface automatically.'),
777
_('Study the action plugins in:') + ' ' + ct.PHATCH_ACTIONS_PATH,
779
_('If you want to contribute a plugin for Phatch,'),
780
_('please email: ') + ct.CONTACT]))
782
#settings in mixin because now app bas, also config_path
784
def on_menu_help_about(self, event):
785
from lib.pyWx import about
786
from data.info import all_credits
787
dlg = about.Dialog(self,
788
title='%(version)s' % ct.INFO,
789
logo=graphics.bitmap(images.LOGO),
790
description=_('PHoto bATCH Processor'),
791
website=ct.INFO['url'],
792
credits=all_credits(),
800
self.set_dirty(False)
801
self.tree.delete_all_forms()
803
def _open(self, filename):
805
if not os.path.exists(filename):
806
self.show_error(_('Sorry, "%s" is not a valid path.' % filename))
808
if system.file_extension(filename) in pil.IMAGE_READ_EXTENSIONS:
809
self.show_error(NO_PHOTOS)
810
filename = self.library_files_dictionary['Polaroid']\
811
.replace('.png', '.phatch')
812
data = self.load_actionlist_data(filename)
814
description = data.get('description', '')
815
self.show_description(bool(description))
817
wx.FutureCall(5000, self.show_description, False)
819
description = ct.ACTION_LIST_DESCRIPTION
820
self.description.SetValue(description)
821
self.saved_description = description
822
if self.tree.append_forms(data['actions']):
823
self.enable_actions(True)
824
self._set_filename(filename)
825
# add it to the history
826
if not self.is_protected_actionlist(filename):
827
self.filehistory.AddFileToHistory(filename)
829
self.enable_actions(False)
830
self.set_dirty(False)
832
def _save(self, filename=None):
834
self._set_filename(filename)
835
description = self.description.GetValue()
836
self.saved_description = description
837
if description == ct.ACTION_LIST_DESCRIPTION:
840
'description': description,
841
'actions': self.tree.export_forms(),
843
api.save_actionlist(self.filename, data)
844
self.set_dirty(False)
846
# add it to the history
847
self.filehistory.AddFileToHistory(filename)
849
def _set_filename(self, filename):
850
self.filename = filename
851
self.set_dirty(False)
854
def enable_collapse_automatic(self, checked):
855
self.set_setting('collapse_automatic', checked)
856
self.menu_view.Check(self.menu_view_collapse_automatic.GetId(),
858
self.tree.enable_collapse_automatic(checked)
860
def show_description(self, checked):
861
self.set_setting('description', checked)
862
self.description.Show(checked)
863
self.menu_view.Check(self.menu_view_description.GetId(), checked)
864
self.frame_toolbar.ToggleTool(self.toolbar_description.GetId(),
868
def show_droplet(self, checked):
870
if api.check_actionlist(self.tree.export_forms(),
871
wx.GetApp().settings):
872
self.droplet = droplet.Frame(self,
873
title=_("Drag & Drop") + ' - ' + ct.TITLE,
874
bitmap=graphics.bitmap(images.DROPLET),
876
label=self.droplet_label_format(
877
system.filename_to_title(self.filename)),
878
label_color=wx.Colour(217, 255, 186),
881
pos=self.GetPosition(),
883
OnShow=self.on_show_droplet,
885
"Drop any files and/or folders on this Phatch droplet\n"
886
"to batch process them.\n"
887
"Right-click or double-click to switch to normal view."))
889
wx.CallAfter(self.on_show_droplet, False)
892
self.droplet.show(False)
894
def on_show_droplet(self, bool):
895
self.menu_view.Check(self.menu_view_droplet.GetId(), bool)
897
self.droplet = None # don't keep a reference
900
def append_save_action(self, actions):
901
only_metadata = self.only_actions_with_tag(actions, 'metadata')
902
message = ct.SAVE_ACTION_NEEDED + " " + \
903
_("Phatch will add one for you, please check its settings.")
905
message += ' \n\n%s%s' % (\
906
_('The action list only processes metadata.'),
907
_('Phatch chooses the lossless "Save Tags" action.'))
908
self.show_message(message)
910
self.tree.append_form_by_label_to_last('Save Tags')
912
self.tree.append_form_by_label_to_last('Save')
914
def only_actions_with_tag(self, actions, tag):
915
for action in actions:
916
if tag not in action.tags:
923
self.Bind(wx.EVT_CLOSE, self.on_close, self)
924
self.Bind(wx.EVT_SIZE, self.on_size, self)
925
self.Bind(wx.EVT_MENU_HIGHLIGHT_ALL, self.on_menu_tool_enter)
926
self.Bind(wx.EVT_TOOL_ENTER, self.on_menu_tool_enter)
927
self.Bind(wx.EVT_TEXT, self.on_description_text, self.description)
928
self.tree.Bind(wx.EVT_TREE_END_DRAG, self.on_tree_end_drag, self.tree)
929
self.tree.Bind(wx.EVT_CONTEXT_MENU, self.on_context_menu, self.tree)
931
def on_description_text(self, event):
932
if not self.dirty and event.GetString() != self.saved_description:
935
def on_drop(self, filenames, x, y):
936
api.apply_actions_to_photos(self.tree.export_forms(),
937
wx.GetApp().settings, paths=filenames, drop=True)
939
def on_menu_tool_enter(self, event):
940
self.tree.close_popup()
943
def on_tree_end_drag(self, event):
945
wx.CallAfter(self.set_dirty, True)
947
def on_context_menu(self, event):
948
if self.tree.is_form_selected():
949
self.tree.PopupMenu(self.menu_edit)
951
def is_save_not_ok(self):
953
answer = self.show_message(_('Save last changes to') + '\n"%s"?'\
955
style=wx.YES_NO | wx.CANCEL | wx.ICON_EXCLAMATION)
956
if answer == wx.ID_CANCEL:
958
if answer == wx.ID_YES:
959
return not self.on_menu_file_save(None)
962
def on_close(self, event=None):
963
if self.is_save_not_ok():
966
wx.GetApp()._saveSettings()
970
def on_size(self, event):
975
self.tree.resize_popup()
978
return not self.tree.has_forms()
981
def _get_file_history(self):
983
for index in range(self.filehistory.GetCount()):
984
filename = self.filehistory.GetHistoryFile(index)
986
result.append(filename)
989
def _set_file_history(self, files):
991
for filename in files:
992
if os.path.exists(filename):
993
self.filehistory.AddFileToHistory(filename)
996
def set_dirty(self, value):
997
self.dirty = ('', '*')[value]
1000
def set_safe_mode(self, state):
1002
answer = self.show_question(
1003
_('Safe mode protects you from the execution of possibly '\
1004
'harmful scripts.\nAre you sure you want to disable it?'),
1005
style=wx.YES_NO | wx.ICON_QUESTION | wx.NO_DEFAULT)
1006
if answer != wx.ID_YES:
1007
self.menu_tools.Check(self.menu_tools_safe.GetId(), True)
1009
formField.set_safe(state)
1012
def get_droplet_folder(self):
1013
folder = self.show_dir_dialog(
1014
defaultPath=self.get_setting('droplet_path'),
1015
message=_('Choose the folder for the droplet'))
1017
self.set_setting('droplet_path', folder)
1020
def menu_file_export_droplet(self, method, *args, **keyw):
1021
folder = self.get_droplet_folder()
1025
method(folder=folder, *args)
1026
self.show_info(_('Phatch successfully created the droplet.'))
1027
except Exception, details:
1028
reason = exception_to_unicode(details, WX_ENCODING)
1029
self.show_error(_('Phatch could not create the droplet: ')\
1032
def install_menu_item(self, menu, name, label, method, tooltip="",
1033
style=wx.ITEM_NORMAL):
1035
item = wx.MenuItem(menu, -1, label, tooltip, style)
1036
setattr(self, name, item)
1037
menu.InsertItem(0, item)
1039
method_name = 'on_' + name
1040
method = new.instancemethod(method, self, self.__class__)
1041
setattr(self, method_name, method)
1043
self.Bind(wx.EVT_MENU, method, item)
1050
class ImageInspectorApp(wx.App):
1052
def __init__(self, paths, *args, **keyw):
1054
super(ImageInspectorApp, self).__init__(*args, **keyw)
1057
wx.InitAllImageHandlers()
1059
frame = dialogs.ImageInspectorFrame(None,
1060
size=dialogs.imageInspector.SIZE)
1061
images.set_icon(frame, 'inspector')
1062
frame.OpenImages(self.paths)
1064
self.SetTopWindow(frame)
1069
app = ImageInspectorApp(paths, 0)
1075
class DropletFrame(DialogsMixin, wx.Frame, FrameReceiver):
1077
def __init__(self, actionlist, paths, *args, **keyw):
1078
wx.Frame.__init__(self, *args, **keyw)
1079
self.filename = actionlist
1081
data = self.load_actionlist_data(actionlist)
1083
wx.CallAfter(self.execute, data['actions'], paths=paths)
1085
sys.exit(_('Impossible to load data from action list.'))
1087
def execute(self, actionlist, paths):
1088
self._execute(actionlist, paths=paths, drop=True)
1095
wx.InitAllImageHandlers()
1096
#do all application initialisation
1100
#check for action list
1101
if self.actionlist == 'recent':
1102
self.actionlist = self.get_action_list(
1103
self.get_action_list_files() + \
1104
self.settings['file_history'])
1105
if self.actionlist is None:
1108
frame = DropletFrame(self.actionlist, self.paths, None, -1, ct.TITLE)
1110
self.SetTopWindow(frame)
1113
def get_action_list_files(self):
1114
return glob.glob(os.path.join(ct.USER_ACTIONLISTS_PATH,
1115
'*' + ct.EXTENSION)) +\
1116
glob.glob(os.path.join(config.PHATCH_ACTIONLISTS_PATH,
1117
'*' + ct.EXTENSION))
1119
def get_action_list(self, file_list):
1121
file_list = self.get_action_list_files()
1124
d[system.filename_to_title(f)] = f
1125
actionlists = d.keys()
1127
dlg = wx.SingleChoiceDialog(None, _('Select action list'), ct.TITLE,
1128
actionlists, wx.CHOICEDLG_STYLE)
1129
if dlg.ShowModal() == wx.ID_OK:
1130
actionlist = d[dlg.GetStringSelection()]
1139
def _loadSettings(self, settings):
1140
self.settings = settings
1141
if os.path.exists(ct.USER_SETTINGS_PATH):
1142
f = open(ct.USER_SETTINGS_PATH, 'rb')
1143
#exclude paths as they should not be overwritten
1145
items = safe.eval_restricted(f.read(),
1146
allowed=['True', 'False']).items()
1148
sys.stdout.write(' '.join([
1149
_('Sorry, your settings seem corrupt.'),
1150
_('Please delete "%s".' % ct.USER_SETTINGS_PATH),
1151
_('Also check if your hard disk not full.\n')]))
1154
for key, value in items:
1155
#FIXME: paths should not be in settings
1156
if not 'PATH' in key:
1157
self.settings[key] = value
1159
def _saveSettings(self):
1160
f = open(ct.USER_SETTINGS_PATH, 'wb')
1161
settings = self.settings.copy()
1162
# non permanent settings
1163
for key in ('desktop', 'safe', 'no_save'):
1166
f.write(pprint.pformat(settings))
1170
class DropletApp(DropletMixin, wx.App):
1172
def __init__(self, actionlist, paths, settings, *args, **keyw):
1173
self.actionlist = actionlist
1175
self._loadSettings(settings)
1176
super(DropletApp, self).__init__(*args, **keyw)
1179
def drop(actionlist, paths, settings):
1180
app = DropletApp(actionlist, paths, settings, 0)
1186
class App(DropletMixin, wx.App):
1188
def __init__(self, settings, actionlist, *args, **keyw):
1189
self._loadSettings(settings)
1190
self.filename = actionlist
1191
super(App, self).__init__(*args, **keyw)
1194
wx.InitAllImageHandlers()
1196
self.splash = self._splash()
1197
self.splash.CentreOnScreen()
1198
self.SetTopWindow(self.splash)
1201
wx.CallAfter(self.show_frame)
1204
def MacReopenApp(self):
1205
"""Called when the doc icon is clicked, and ???"""
1206
#TODO: test if this is working
1207
self.GetTopWindow().Raise()
1210
return droplet.Frame(None,
1212
bitmap=graphics.bitmap(images.SPLASH),
1216
def show_frame(self):
1217
#do all application initialisation
1221
frame = Frame(self.filename, None, -1, ct.TITLE)
1222
frame.menu_tools.Check(frame.menu_tools_safe.GetId(),
1223
formField.get_safe())
1224
frame.CentreOnScreen()
1226
self.SetTopWindow(frame)
1227
frame.enable_collapse_automatic(self.settings['collapse_automatic'])
1230
self.splash.Destroy()
1234
super(App, self).init()
1236
def _saveSettings(self):
1237
frame = self.GetTopWindow()
1238
self.settings['file_history'] = frame._get_file_history()
1239
if hasattr(frame, 'dialog_actions'):
1240
if frame.dialog_actions:
1241
self.settings['tag_actions'] = \
1242
frame.dialog_actions.GetTagSelection()
1243
super(App, self)._saveSettings()
1246
def main(settings, actionlist):
1247
app = App(settings, actionlist, 0)
1251
if __name__ == "__main__":