1 # main.py: main module of photoreider
2 # arch-tag: main module of photoreider
9 import resources
, common
, picture_list
, viewer
14 class PhotoReiderBrowser(wx
.Frame
):
15 def __init__(self
, img_viewer
, **kwds
):
16 wx
.Frame
.__init
__(self
, None, -1, "")
17 self
.window_1
= wx
.SplitterWindow(self
, -1, style
=0)
18 self
.window_1_pane_1
= wx
.Panel(self
.window_1
, -1, style
=0)
19 self
.window_2
= wx
.SplitterWindow(self
.window_1_pane_1
, -1, style
=0)
20 self
.window_2_pane_1
= wx
.Panel(self
.window_2
, -1, style
=0)
21 self
.preview_panel
= wx
.Panel(self
.window_2
, 0)
22 self
.window_1_pane_2
= wx
.Panel(self
.window_1
, -1, style
=0)
23 self
.statusbar
= self
.CreateStatusBar(4)
25 self
.notebook
= wx
.Notebook(self
.window_2_pane_1
, -1)
26 if wx
.Platform
== '__WXMSW__':
27 self
.dir_ctrl
= wx
.GenericDirCtrl(self
.notebook
, -1,
28 filter="XML files (*.xml)|*.xml",
29 style
=wx
.SUNKEN_BORDER
)
31 self
.dir_ctrl
= dirctrl
.DirCtrl(self
.notebook
, -1,
32 filter="XML files (*.xml)|*.xml",
33 style
=wx
.SUNKEN_BORDER
)
35 self
.viewer
= img_viewer
36 self
.viewer
.photoreider_browser
= self
39 self
.picture_list
= picture_list
.PictureList(self
.window_1_pane_2
, -1,
42 res
= wx
.xrc
.XmlResource_Get()
43 res
.Load('resources.xrc')
44 self
.SetMenuBar(res
.LoadMenuBar('menubar'))
45 self
.bind_menubar_events()
47 res
.Load('toolbars.xrc')
48 self
.SetToolBar(res
.LoadToolBar(self
, 'browser_toolbar'))
49 index
= common
.config
.getint('photoreider', 'default_view')
51 self
.GetToolBar().ToggleTool(wx
.xrc
.XRCID('report_view'), True)
53 self
.GetToolBar().ToggleTool(wx
.xrc
.XRCID('thumbs_view'), True)
56 self
.__set
_properties
()
58 if common
.config
.getboolean('photoreider', 'show_hidden'):
59 self
.GetMenuBar().Check(wx
.xrc
.XRCID('show_hidden'), True)
60 self
.dir_ctrl
.ShowHidden(True)
62 self
.dir_ctrl
.SetPath(globaldefs
.album_instance
.location
)
63 #--- hack to fix bug of dir_ctrl ---
64 tree
= self
.dir_ctrl
.GetTreeCtrl()
65 tree
.EnsureVisible(tree
.GetSelection())
66 #-----------------------------------
67 if common
.config
.getint('photoreider', 'default_view') == 1:
68 # do this later, otherwise if started in thumbs view, the layout
71 wx
.CallAfter(self
.picture_list
.set_path
, globaldefs
.album_instance
.location
)
73 self
.picture_list
.set_path(globaldefs
.album_instance
.location
)
75 # dir selection... and thumbs/report view
77 self
.which_case
= 0 # 0 = dir_selection, 1 = details, 2 = thumbnails
78 self
.set_path_timer
= wx
.Timer(self
, TIMER_ID
)
79 wx
.EVT_TIMER(self
, TIMER_ID
, self
.on_timer
)
80 wx
.EVT_TREE_SEL_CHANGED(self
.dir_ctrl
, -1, #self.dir_ctrl.GetTreeCtrl().GetId(),
84 self
.picture_list
.update_statusbar_info()
85 self
.viewer
.update_statusbar_info()
86 wx
.EVT_IDLE(self
, on_idle
)
88 def bind_menubar_events(self
):
89 wx
.EVT_MENU(self
, wx
.xrc
.XRCID('view'),
90 lambda e
: self
.picture_list
.on_item_activated())
91 wx
.EVT_MENU(self
, wx
.xrc
.XRCID('exit'), lambda e
: self
.Close())
92 wx
.EVT_MENU(self
, wx
.xrc
.XRCID('refresh'),
93 lambda e
:self
.picture_list
.set_path(globaldefs
.album_instance
.location
,
95 about_msg
= _("PhotoReider v%s: a Python + wxPython + PIL image viewer\n"
96 "Authors: Reid Levesque & Saul Bancroft\n"
97 "License: GPL (see license.txt)\n"
98 "THIS PROGRAM COMES WITH NO WARRANTY")
99 wx
.EVT_MENU(self
, wx
.xrc
.XRCID('about'),
100 lambda e
: wx
.MessageBox(about_msg
% common
.__version
__,
101 _("About PhotoReider"),
103 wx
.ICON_INFORMATION
))
104 def show_hidden(event
):
105 if event
.IsChecked():
106 self
.dir_ctrl
.ShowHidden(True)
108 self
.dir_ctrl
.ShowHidden(False)
109 wx
.EVT_MENU(self
, wx
.xrc
.XRCID('show_hidden'), show_hidden
)
110 wx
.EVT_MENU(self
, wx
.xrc
.XRCID('preferences'),
111 lambda e
: resources
.PreferencesEditor(common
.config
,
113 def show_keybindigs(event
):
115 SPACE or PG_DOWN: Show next image
116 BACKSPACE or PG_UP: Show previous image
117 HOME: Show first image
119 LEFT_ARROW: Scroll image left
120 RIGHT_ARROW: Scroll image right
121 UP_ARROW: Scroll image up
122 DOWN_ARROW: Scroll image down
126 1: Restore original image size
127 K: Remember zoom and rotation settings
129 R: Rotate image 90 degrees clockwise
130 L: Rotate image 90 degrees counterclockwise
131 S: Start/Stop slideshow
132 F11: Toggle fullscreen mode
133 ESC: Close viewer frame
135 resources
.ScrolledMessageDialog(None, msg
,
136 _("PhotoReider Viewer Key Bindings"),
138 wx
.EVT_MENU(self
, wx
.xrc
.XRCID('viewer_keybindings'), show_keybindigs
)
139 def report_view(event
):
143 self
.set_path_timer
.Start(150, True)
144 def thumbs_view(event
):
148 self
.set_path_timer
.Start(150, True)
149 wx
.EVT_MENU(self
, wx
.xrc
.XRCID('report_view'), report_view
)
150 wx
.EVT_MENU(self
, wx
.xrc
.XRCID('thumbs_view'), thumbs_view
)
152 def add_file_album(event
):
153 path
, filename
= os
.path
.split(globaldefs
.last_album_file
)
156 file_dialog
= wx
.FileDialog(self
, "Choose an Album", path
, filename
, "XML files (*.xml)|*.xml", wx
.SAVE
)
157 if file_dialog
.ShowModal() == wx
.ID_OK
:
158 file = file_dialog
.GetPath()
159 temp
= album
.Album(file)
160 for a
in self
.picture_list
._lists
[0].get_selected_filenames():
161 file_to_add
= os
.path
.join(globaldefs
.album_instance
.location
, a
)
162 temp
.add_file(file_to_add
)
164 file_dialog
.Destroy()
165 wx
.EVT_MENU(self
, wx
.xrc
.XRCID('add_file_album'), add_file_album
)
167 def add_dir_album(event
):
168 path
, filename
= os
.path
.split(album
.last_album_file
)
169 if (album
.isdir(self
.dir_ctrl
.GetPath())):
170 file_dialog
= wx
.FileDialog(self
, "Choose an Album", path
, filename
, "XML files (*.xml)|*.xml", wx
.SAVE
)
171 if file_dialog
.ShowModal() == wx
.ID_OK
:
172 file = file_dialog
.GetPath()
173 temp
= album
.Album(file)
174 temp
.add_dir(self
.dir_ctrl
.GetPath())
176 file_dialog
.Destroy()
177 wx
.EVT_MENU(self
, wx
.xrc
.XRCID('add_dir_album'), add_dir_album
)
179 wx
.EVT_MENU(self
, wx
.xrc
.XRCID('remove_file_album'), self
.picture_list
.remove_selection
)
184 self
.dir_ctrl
.up_dir()
185 except AttributeError:
186 self
.dir_ctrl
.SetPath(
187 os
.path
.split(self
.dir_ctrl
.GetPath())[0])
188 wx
.EVT_MENU(self
, wx
.xrc
.XRCID('up_dir'), up_dir
)
191 def do_sort(index
, reverse
):
192 common
.sort_index
= index
193 common
.reverse_sort
= reverse
194 self
.picture_list
.sort_items(index
, reverse
)
195 wx
.EVT_MENU(self
, wx
.xrc
.XRCID('sort_name'),
196 lambda e
: do_sort(common
.SORT_NAME
, common
.reverse_sort
))
197 wx
.EVT_MENU(self
, wx
.xrc
.XRCID('sort_date'),
198 lambda e
: do_sort(common
.SORT_DATE
, common
.reverse_sort
))
199 wx
.EVT_MENU(self
, wx
.xrc
.XRCID('sort_size'),
200 lambda e
: do_sort(common
.SORT_SIZE
, common
.reverse_sort
))
201 wx
.EVT_MENU(self
, wx
.xrc
.XRCID('sort_descend'),
202 lambda e
: do_sort(common
.sort_index
,
203 not common
.reverse_sort
))
204 # event to update the sort menu
205 def update_sort_menu(event
):
207 common
.SORT_NAME
: wx
.xrc
.XRCID('sort_name'),
208 common
.SORT_DATE
: wx
.xrc
.XRCID('sort_date'),
209 common
.SORT_SIZE
: wx
.xrc
.XRCID('sort_size'),
211 mb
= self
.GetMenuBar()
212 mb
.Check(menu_to_check
[event
.sort_index
], True)
213 mb
.Check(wx
.xrc
.XRCID('sort_descend'), event
.reverse_sort
)
214 common
.EVT_SORT_CHANGED(self
, update_sort_menu
)
216 def view_image(self
, img
):
218 img is the path to the file to view
220 self
.viewer
.view_image(img
)
223 def on_change_dir(self
, event
):
224 if getattr(self
.dir_ctrl
, 'dont_trigger_change_dir', False):
225 # ugly hack (see PictureList.set_path), to avoid path changes
226 # when the user visits a hidden dir, but the tree isn't showing
232 if not self
.set_path_timer
.Start(100, True):
233 pass # wx.Timer.Start seems to return always False...
234 #print 'impossible to start the timer!'
238 def on_timer(self
, *args
):
239 if self
.which_case
== 0:
240 tree
= self
.dir_ctrl
.GetTreeCtrl()
241 item
= tree
.GetSelection()
242 if item
!= tree
.GetRootItem():
243 self
.picture_list
.set_path(self
.dir_ctrl
.GetPath())
244 elif self
.which_case
== 1:
245 tb
= self
.GetToolBar()
246 tb
.ToggleTool(wx
.xrc
.XRCID('report_view'), True)
247 tb
.ToggleTool(wx
.xrc
.XRCID('thumbs_view'), False)
248 self
.GetMenuBar().Check(wx
.xrc
.XRCID('report_view'), True)
249 self
.picture_list
.show_details()
250 elif self
.which_case
== 2:
251 tb
= self
.GetToolBar()
252 tb
.ToggleTool(wx
.xrc
.XRCID('report_view'), False)
253 tb
.ToggleTool(wx
.xrc
.XRCID('thumbs_view'), True)
254 self
.GetMenuBar().Check(wx
.xrc
.XRCID('thumbs_view'), True)
255 self
.picture_list
.show_thumbs()
256 if wx
.IsBusy(): wx
.EndBusyCursor()
258 def __set_properties(self
):
259 self
.SetTitle(_("Picture Browser - PhotoReider"))
260 self
.SetSize((800, 600))
261 self
.window_2
.SplitHorizontally(self
.window_2_pane_1
,
262 self
.preview_panel
, 10000)
263 self
.window_1
.SplitVertically(self
.window_1_pane_1
,
264 self
.window_1_pane_2
, 350)
265 self
.notebook
.AddPage(self
.dir_ctrl
, _('Dir Tree'))
266 self
.statusbar
.SetStatusWidths([150, 200, -1, 300])
267 if wx
.Platform
== '__WXMSW__':
268 icon
= wx
.Icon('icons/icon.ico', wx
.BITMAP_TYPE_ICO
)
270 icon
= wx
.EmptyIcon()
271 bmp
= wx
.Bitmap("icons/icon.xpm", wx
.BITMAP_TYPE_XPM
)
272 icon
.CopyFromBitmap(bmp
)
275 def __do_layout(self
):
276 sizer_1
= wx
.BoxSizer(wx
.VERTICAL
)
277 sizer_2
= wx
.BoxSizer(wx
.HORIZONTAL
)
278 sizer_3
= wx
.BoxSizer(wx
.VERTICAL
)
279 sizer_4
= wx
.BoxSizer(wx
.VERTICAL
)
280 sizer_2
.Add(self
.window_2
, 1, wx
.EXPAND
, 0)
281 self
.window_1_pane_1
.SetAutoLayout(1)
282 self
.window_1_pane_1
.SetSizer(sizer_2
)
283 sizer_3
.Add(wx
.NotebookSizer(self
.notebook
), 1, wx
.EXPAND
)
284 self
.window_2_pane_1
.SetAutoLayout(1)
285 self
.window_2_pane_1
.SetSizer(sizer_3
)
286 sizer_4
.Add(self
.picture_list
, 1, wx
.EXPAND
)
287 self
.window_1_pane_2
.SetAutoLayout(1)
288 self
.window_1_pane_2
.SetSizer(sizer_4
)
289 sizer_2
.Fit(self
.window_1_pane_1
)
290 sizer_1
.Add(self
.window_1
, 1, wx
.EXPAND
, 0)
291 self
.SetAutoLayout(1)
292 self
.SetSizer(sizer_1
)
295 # end of class PhotoReiderBrowser
298 class PhotoReider(wx
.App
):
299 def init_preferences(self
):
304 'viewer_size': '800, 600',
306 'initial_path': os
.getcwd(),
307 'remember_last_path': '1',
308 'column_sort_index': '0',
310 'interpolation': '1', # Image.NEAREST
311 'default_view': '0', # view details by default
312 'slideshow_fullscreen': '1',
313 'slideshow_delay': '2', # seconds
314 'slideshow_fit': '1',
315 'thumbs_cache_size': '25600', # in Kb
316 'remember_settings': '0',
317 'slideshow_cycle': '0',
318 'slideshow_random': '0',
319 'save_on_rotate': '1',
321 common
.config
= ConfigParser
.ConfigParser(defaults
)
322 common
.config
.read(common
.config_file
)
323 if not common
.config
.has_section('photoreider'):
324 common
.config
.add_section('photoreider')
325 common
.sort_index
= common
.config
.getint('photoreider',
327 common
.reverse_sort
= common
.config
.getboolean('photoreider',
331 self
.init_preferences()
332 wx
.InitAllImageHandlers()
336 locale
.setlocale(locale
.LC_ALL
, '')
337 except locale
.Error
, e
:
338 pass # this is not that bad...
339 # now, gettext initialization
340 localedir
= os
.path
.join(os
.path
.dirname(sys
.argv
[0]), 'i18n')
341 # need to keep a reference to wx.Locale, otherwise dialogs created
342 # later by XRC don't get translated
344 wx
.Locale_AddCatalogLookupPathPrefix(localedir
)
345 _wxloc
= wx
.Locale(wx
.LANGUAGE_DEFAULT
)
346 _wxloc
.AddCatalog('photoreider')
347 # just use the wxPython gettext, this is necessary for XRC and it's
348 # sufficient for the rest. And it's also slightly better on win
349 # (apparently it doesn't require LANG or simila to be set)
351 setattr(__builtin__
, '_', wx
.GetTranslation
)
353 # Setup viewer and path
354 if image_to_show
is not None:
355 globaldefs
.album_instance
= album
.Album(image_to_show
)
357 globaldefs
.album_instance
= album
.Album(common
.config
.get('photoreider', 'initial_path'))
358 viewer_frame
= viewer
.PhotoReiderViewerFrame(
359 None, _("Picture Viewer - PhotoReider"))
360 panel
= viewer
.PhotoReiderViewer(viewer_frame
)
361 frame
= PhotoReiderBrowser(panel
)
363 # Setting up an OnClose method
364 def exit_app(event
=None):
366 if common
.config
.getint('photoreider', 'remember_last_path'):
368 'photoreider', 'initial_path', frame
.picture_list
.path
)
370 out
= open(common
.config_file
, 'w')
371 common
.config
.write(out
)
374 wx
.LogError(_('Unable to save preferences (%s)') % e
)
375 viewer_frame
.Destroy()
377 wx
.CallAfter(wx
.GetApp().ExitMainLoop
)
378 #--------------------------------
380 common
.exit_app
= exit_app
381 wx
.EVT_CLOSE(frame
, common
.exit_app
)
383 self
.SetTopWindow(frame
)
384 frame
.SetPosition((0, 0)) # temporary, should be customizable
385 if image_to_show
is not None and len(globaldefs
.album_instance
.list) != 0 :
387 if not os
.path
.isdir(image_to_show
):
388 frame
.picture_list
.set_path(
389 os
.path
.normpath(os
.path
.dirname(image_to_show
)))
391 frame
.picture_list
.set_path(
392 os
.path
.normpath(image_to_show
))
403 # end of class PhotoReider
410 global image_to_show
, slideshow
413 options
, args
= getopt
.getopt(sys
.argv
[1:], 'sh',
414 ['slideshow', 'help'])
415 except getopt
.GetoptError
:
419 if o
in ('-s', '--slideshow'):
421 elif o
in ('-h', '--help'):
425 image_to_show
= args
[0]
426 if not os
.path
.isabs(image_to_show
):
427 image_to_show
= os
.path
.join(os
.getcwd(), image_to_show
)
431 binary_path
= os
.path
.abspath(sys
.argv
[0])
432 if os
.path
.islink(binary_path
):
433 binary_path
= os
.path
.realpath(binary_path
)
435 pwd_path
= os
.path
.dirname(binary_path
)
442 if err
: stream
= sys
.stderr
443 else: stream
= sys
.stdout
444 print >> stream
, """Usage: %s [OPTIONS] [PATH]
446 -s, --slideshow: starts a slideshow of the images in PATH (which must be given)
447 -h, --help: shows this message and exits
449 If there are no options and PATH is given, shows the image at PATH or starts in dir specified by PATH\
450 """ % os
.path
.basename(sys
.argv
[0])