Oops, earlier changes broke the "Undecided" schedule. Added a new "system.sched...
[wvapps.git] / photoreider / main.py
blob41601ce8ca91bb386e071bfd9ac359f4a5357f9e
1 # main.py: main module of photoreider
2 # arch-tag: main module of photoreider
3 # license: GPL
5 import wx, wx.xrc
6 import locale, os, sys
7 import glob
9 import resources, common, picture_list, viewer
10 import dirctrl
11 import album
12 import globaldefs
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)
30 else:
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
38 self.options = kwds
39 self.picture_list = picture_list.PictureList(self.window_1_pane_2, -1,
40 self.options, self)
41 # Menu Bar
42 res = wx.xrc.XmlResource_Get()
43 res.Load('resources.xrc')
44 self.SetMenuBar(res.LoadMenuBar('menubar'))
45 self.bind_menubar_events()
46 # Tool Bar
47 res.Load('toolbars.xrc')
48 self.SetToolBar(res.LoadToolBar(self, 'browser_toolbar'))
49 index = common.config.getint('photoreider', 'default_view')
50 if index == 0:
51 self.GetToolBar().ToggleTool(wx.xrc.XRCID('report_view'), True)
52 else:
53 self.GetToolBar().ToggleTool(wx.xrc.XRCID('thumbs_view'), True)
55 self.__do_layout()
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
69 # is messed up...
70 #FIXME
71 wx.CallAfter(self.picture_list.set_path, globaldefs.album_instance.location)
72 else:
73 self.picture_list.set_path(globaldefs.album_instance.location)
75 # dir selection... and thumbs/report view
76 TIMER_ID = wx.NewId()
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(),
81 self.on_change_dir)
83 def on_idle(event):
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,
94 True))
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"),
102 wx.OK|wx.CENTRE|
103 wx.ICON_INFORMATION))
104 def show_hidden(event):
105 if event.IsChecked():
106 self.dir_ctrl.ShowHidden(True)
107 else:
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,
112 common.config_file))
113 def show_keybindigs(event):
114 msg = _("""\
115 SPACE or PG_DOWN: Show next image
116 BACKSPACE or PG_UP: Show previous image
117 HOME: Show first image
118 END: Show last 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
123 F: Fit image
124 + or ]: Zoom in
125 - or [: Zoom out
126 1: Restore original image size
127 K: Remember zoom and rotation settings
128 F5: Refresh image
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
134 """)
135 resources.ScrolledMessageDialog(None, msg,
136 _("PhotoReider Viewer Key Bindings"),
137 67, 21)
138 wx.EVT_MENU(self, wx.xrc.XRCID('viewer_keybindings'), show_keybindigs)
139 def report_view(event):
140 if not wx.IsBusy():
141 wx.BeginBusyCursor()
142 self.which_case = 1
143 self.set_path_timer.Start(150, True)
144 def thumbs_view(event):
145 if not wx.IsBusy():
146 wx.BeginBusyCursor()
147 self.which_case = 2
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)
154 print(path)
155 print(filename)
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)
163 temp.save()
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())
175 temp.save()
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)
182 def up_dir(event):
183 try:
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)
190 # sorting
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):
206 menu_to_check = {
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):
217 """\
218 img is the path to the file to view
220 self.viewer.view_image(img)
221 self.viewer.show()
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
227 # them
228 return
229 if not wx.IsBusy():
230 wx.BeginBusyCursor()
231 self.which_case = 0
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!'
235 #self.on_timer()
236 event.Skip()
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)
269 else:
270 icon = wx.EmptyIcon()
271 bmp = wx.Bitmap("icons/icon.xpm", wx.BITMAP_TYPE_XPM)
272 icon.CopyFromBitmap(bmp)
273 self.SetIcon(icon)
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)
293 self.Layout()
295 # end of class PhotoReiderBrowser
298 class PhotoReider(wx.App):
299 def init_preferences(self):
300 import ConfigParser
301 defaults = {
302 'fit_image': '1',
303 'show_hidden': '0',
304 'viewer_size': '800, 600',
305 'fullscreen': '0',
306 'initial_path': os.getcwd(),
307 'remember_last_path': '1',
308 'column_sort_index': '0',
309 'reverse_sort': '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',
326 'column_sort_index')
327 common.reverse_sort = common.config.getboolean('photoreider',
328 'reverse_sort')
330 def OnInit(self):
331 self.init_preferences()
332 wx.InitAllImageHandlers()
334 # LOCALE STUFF
335 try:
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
343 global _wxloc
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)
350 import __builtin__
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)
356 else:
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):
365 common.exiting(True)
366 if common.config.getint('photoreider', 'remember_last_path'):
367 common.config.set(
368 'photoreider', 'initial_path', frame.picture_list.path)
369 try:
370 out = open(common.config_file, 'w')
371 common.config.write(out)
372 out.close()
373 except Exception, e:
374 wx.LogError(_('Unable to save preferences (%s)') % e)
375 viewer_frame.Destroy()
376 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 :
386 if slideshow:
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)))
390 else:
391 frame.picture_list.set_path(
392 os.path.normpath(image_to_show))
393 viewer_frame.Show()
394 panel.slideshow()
395 else:
396 panel.view_image()
397 panel.SetFocus()
398 else:
399 frame.Show()
401 return True
403 # end of class PhotoReider
406 image_to_show = None
407 slideshow = False
409 def main():
410 global image_to_show, slideshow
411 import getopt
412 try:
413 options, args = getopt.getopt(sys.argv[1:], 'sh',
414 ['slideshow', 'help'])
415 except getopt.GetoptError:
416 usage()
418 for o, a in options:
419 if o in ('-s', '--slideshow'):
420 slideshow = True
421 elif o in ('-h', '--help'):
422 usage(False)
424 if args:
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)
428 elif slideshow:
429 usage()
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)
436 os.chdir(pwd_path)
437 app = PhotoReider(0)
438 app.MainLoop()
441 def usage(err=True):
442 if err: stream = sys.stderr
443 else: stream = sys.stdout
444 print >> stream, """Usage: %s [OPTIONS] [PATH]
445 Valid OPTIONS:
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])
451 sys.exit(err)