don't ignore font styles in font search
[swftools.git] / wx / lib / app.py
blob4ddaebda7a374721f1409e33d352f49641681594
1 #!/usr/bin/env python
2 # -*- coding: UTF-8 -*-
4 # gpdf2swf.py
5 # graphical user interface for pdf2swf
7 # Part of the swftools package.
8 #
9 # Copyright (c) 2008,2009 Matthias Kramm <kramm@quiss.org>
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 2 of the License, or
14 # (at your option) any later version.
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with this program; if not, write to the Free Software
23 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
25 from __future__ import division
26 import os, sys
27 import wx
28 import time
29 import pickle
31 from lib.wordwrap import wordwrap
32 from wx.lib.pubsub import Publisher
34 from document import Document
35 from gui.dialogs import (ProgressDialog, OptionsDialog, AboutDialog, InfoDialog)
36 from gui.gmain import (PdfFrame,
37 ID_INVERT_SELECTION, ID_SELECT_ODD,
38 ID_ONE_PAGE_PER_FILE,
39 ID_SELECT_EVEN, ID_DOC_INFO,
41 from lib import error
42 from lib import utils
45 def GetDataDir():
46 """
47 Return the standard location on this platform for application data
48 """
49 sp = wx.StandardPaths.Get()
50 return sp.GetUserDataDir()
52 def GetConfig():
53 if not os.path.exists(GetDataDir()):
54 os.makedirs(GetDataDir())
56 config = wx.FileConfig(
57 localFilename=os.path.join(GetDataDir(), "options"))
58 return config
61 class Pdf2Swf:
62 def __init__(self):
63 self.__doc = Document()
65 self.__threads = {}
67 self.__busy = None
68 self.__progress = None
70 self.__can_save = False
71 self.__can_viewinfo = False
73 self.view = PdfFrame()
74 wx.GetApp().SetTopWindow(self.view)
75 sys.excepthook = error.ErrorFrame(self.view)
77 # Call Show after the current and pending event
78 # handlers have been completed. Otherwise on MSW
79 # we see the frame been draw and after that we saw
80 # the menubar appear
81 wx.CallAfter(self.Show)
83 self.options = OptionsDialog(self.view)
84 self.__ReadConfigurationFile()
86 self.view.toolbar_preview_type.SetSelection(0)
88 Publisher.subscribe(self.OnPageChanged, "PAGE_CHANGED")
89 Publisher.subscribe(self.OnFileLoaded, "FILE_LOADED")
90 Publisher.subscribe(self.OnFileNotLoaded, "FILE_NOT_LOADED")
91 Publisher.subscribe(self.OnDiffSizes, "DIFF_SIZES")
92 Publisher.subscribe(self.OnThumbnailAdded, "THUMBNAIL_ADDED")
93 Publisher.subscribe(self.OnThumbnailDone, "THUMBNAIL_DONE")
94 Publisher.subscribe(self.OnProgressBegin, "SWF_BEGIN_SAVE")
95 Publisher.subscribe(self.OnProgressUpdate, "SWF_PAGE_SAVED")
96 Publisher.subscribe(self.OnProgressDone, "SWF_FILE_SAVED")
97 Publisher.subscribe(self.OnCombineError, "SWF_COMBINE_ERROR")
98 Publisher.subscribe(self.OnFileDroped, "FILE_DROPED")
99 Publisher.subscribe(self.OnFilesDroped, "FILES_DROPED")
100 Publisher.subscribe(self.OnPluginOnePagePerFileNotSupported,
101 "PLUGIN_ONE_PAGE_PER_FILE_NOT_SUPPORTED")
102 Publisher.subscribe(self.OnPluginError, "PLUGIN_ERROR")
104 self.view.Bind(wx.EVT_MENU, self.OnMenuOpen, id=wx.ID_OPEN)
105 self.view.Bind(wx.EVT_MENU, self.OnMenuSave, id=wx.ID_SAVE)
106 self.view.Bind(wx.EVT_MENU, self.OnMenuSaveSelected, id=wx.ID_SAVEAS)
107 self.view.Bind(wx.EVT_MENU, self.OnMenuExit, id=wx.ID_EXIT)
108 self.view.Bind(wx.EVT_MENU_RANGE, self.OnFileHistory,
109 id=wx.ID_FILE1, id2=wx.ID_FILE9)
111 self.view.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=wx.ID_SAVE)
112 self.view.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=wx.ID_SAVEAS)
114 self.view.Bind(wx.EVT_MENU, self.OnMenuSelectAll, id=wx.ID_SELECTALL)
115 self.view.Bind(wx.EVT_MENU,
116 self.OnMenuInvertSelection, id=ID_INVERT_SELECTION)
117 self.view.Bind(wx.EVT_MENU, self.OnMenuSelectOdd, id=ID_SELECT_ODD)
118 self.view.Bind(wx.EVT_MENU, self.OnMenuSelectEven, id=ID_SELECT_EVEN)
119 self.view.Bind(wx.EVT_MENU, self.OnMenuOptions, id=wx.ID_PREFERENCES)
121 self.view.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=wx.ID_SELECTALL)
122 self.view.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=ID_INVERT_SELECTION)
123 self.view.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=ID_SELECT_ODD)
124 self.view.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=ID_SELECT_EVEN)
126 self.view.Bind(wx.EVT_MENU, self.OnAbout, id=wx.ID_ABOUT)
128 self.view.Bind(wx.EVT_MENU, self.OnZoom, id=wx.ID_ZOOM_IN)
129 self.view.Bind(wx.EVT_MENU, self.OnZoom, id=wx.ID_ZOOM_OUT)
130 self.view.Bind(wx.EVT_MENU, self.OnZoom, id=wx.ID_ZOOM_100)
131 self.view.Bind(wx.EVT_MENU, self.OnFit, id=wx.ID_ZOOM_FIT)
132 self.view.Bind(wx.EVT_MENU, self.OnShowDocInfo, id=ID_DOC_INFO)
134 self.view.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=wx.ID_ZOOM_IN)
135 self.view.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=wx.ID_ZOOM_OUT)
136 self.view.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=wx.ID_ZOOM_100)
137 self.view.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=wx.ID_ZOOM_FIT)
138 self.view.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUIInfo, id=ID_DOC_INFO)
140 self.view.page_list.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnSelectItem)
141 self.view.Bind(wx.EVT_CLOSE, self.OnMenuExit)
143 self.view.toolbar_preview_type.Bind(wx.EVT_CHOICE,
144 self.OnPreviewType)
146 # statusbar cancel thumbanails generation button
147 self.view.statusbar.btn_cancel.Bind(wx.EVT_BUTTON,
148 self.OnThumbnailCancel)
150 # Don't know where the problem is (python/xpython or wxwidgets/wxpython)
151 # but I found that this hack was necessary to avoid the app enter in a
152 # idle state. We must, for example, move the mouse inside the app
153 # for threads continue their job.
154 # There is no need for this when freezing with other utils, like
155 # py2exe, pyinstaller, cxfreeze
156 if "wxMSW" in wx.PlatformInfo:
157 self.timer = wx.Timer(self.view)
158 self.view.Bind(wx.EVT_TIMER, lambda evt: None)
159 self.timer.Start(50)
161 def OnFilesDroped(self, evt):
162 dlg = wx.MessageDialog(self.view,
163 u"You must drop only one file.",
164 u"Notice",
165 style=wx.OK, pos=wx.DefaultPosition)
166 dlg.ShowModal()
167 dlg.Destroy()
169 def OnFileDroped(self, message):
170 self.__Load(message.data["filename"])
172 def OnPluginOnePagePerFileNotSupported(self, message):
173 self.Message(u"Selected viewer does not support "
174 u"one page per file. ")
176 def OnPluginError(self, message):
177 self.Message(u"Error applying selected viewer")
179 def OnFileHistory(self, evt):
180 # get the file based on the menu ID
181 fileNum = evt.GetId() - wx.ID_FILE1
182 filename = self.view.filehistory.GetHistoryFile(fileNum)
184 self.__Load(filename)
186 def OnProgressBegin(self, message):
187 pages = message.data["pages"]
188 style = (
189 wx.PD_APP_MODAL|wx.PD_ELAPSED_TIME|
190 wx.PD_REMAINING_TIME|wx.PD_CAN_ABORT|
191 wx.PD_AUTO_HIDE
193 if self.__progress:
194 self.__progress.Destroy()
195 self.__progress = ProgressDialog(u"Saving...",
196 u"Start saving SWF pages",
197 maximum=pages,
198 parent=self.view, style=style)
199 self.__progress.Show()
200 self.view.SetStatusText(u"Saving document...")
202 def OnProgressUpdate(self, message):
203 pagenr = message.data["pagenr"]
204 pages = message.data["pages"]
206 keep_running = self.__progress.Update(
207 pagenr,
208 u"Saving SWF page %d of %d" % (pagenr, pages)
211 if not keep_running and self.__threads.has_key("progress"):
212 self.view.SetStatusText(u"Cancelling...")
213 self.__threads.pop("progress").Stop()
214 self.__progress.Hide()
217 def OnProgressDone(self, message):
218 if self.__threads.has_key("progress"): # it goes all the way?
219 self.__threads.pop("progress")
220 self.view.SetStatusText(u"SWF document saved successfully.")
221 else:
222 self.view.SetStatusText(u"")
224 def OnCombineError(self, message):
225 from wx.lib.dialogs import ScrolledMessageDialog
226 ScrolledMessageDialog(self.view, message.data, u"Notice").ShowModal()
229 def OnThumbnailAdded(self, message):
230 self.view.statusbar.SetGaugeValue(message.data['pagenr'])
231 tot = self.view.page_list.GetItemCount()
232 self.view.SetStatusText(u"Generating thumbnails %s/%d" %
233 (message.data['pagenr'], tot), 0)
235 def OnThumbnailDone(self, message):
236 self.view.statusbar.SetGaugeValue(0)
237 self.view.SetStatusText(u"", 0)
238 if self.__threads.has_key("thumbnails"):
239 self.__threads.pop("thumbnails")
240 self.view.SendSizeEvent()
242 def OnThumbnailCancel(self, event):
243 if self.__threads.has_key("thumbnails"):
244 self.__threads["thumbnails"].Stop()
246 def OnSelectItem(self, event):
247 self.__doc.ChangePage(event.GetIndex() + 1)
249 def OnPreviewType(self, event):
250 filename = self.__doc.filename
251 if filename:
252 self.__Load(filename)
254 def OnPageChanged(self, message):
255 # ignore if we have more than one item selected
256 if self.view.page_list.GetSelectedItemCount() > 1:
257 return
259 self.view.page_preview.DisplayPage(message.data)
261 def SetTitle(self):
262 name = wx.GetApp().GetAppName()
263 filename = os.path.basename(self.__doc.filename)
264 if self.__doc.title != "n/a":
265 t = u"%s - %s (%s)" % (name, filename, self.__doc.title)
266 else:
267 t = u"%s - %s" % (name, filename)
268 self.view.SetTitle(t)
270 def OnFileLoaded(self, message):
271 if self.__progress:
272 self.__progress.Destroy()
273 self.__progress = None
275 self.__can_viewinfo = True
276 del self.__busy
278 self.SetTitle()
280 if self.__doc.oktocopy == 'no':
281 self.__can_save = False
282 self.view.page_list.DisplayEmptyThumbnails(0)
283 self.view.page_preview.Clear()
284 self.view.SetStatusText(u"")
285 self.Message(
286 u"This PDF disallows copying, cannot be converted."
288 return
290 #if not self.__doc.oktoprint:
291 self.view.SetStatusText(u"Document loaded successfully.")
293 self.view.page_list.DisplayEmptyThumbnails(message.data["pages"])
294 thumbs = self.__doc.GetThumbnails()
295 t = self.view.page_list.DisplayThumbnails(thumbs)
296 self.__threads["thumbnails"] = t
297 self.view.statusbar.SetGaugeRange(message.data["pages"])
298 #del self.__busy
300 def OnFileNotLoaded(self, message):
301 self.__can_save = False
302 self.__can_viewinfo = False
303 del self.__busy
304 self.view.SetStatusText(u"")
305 self.Message(
306 u"Could not open file %s" % message.data['filename']
309 def OnDiffSizes(self, message):
310 # just let the user know- for now, we can't handle this properly
311 self.Message(
312 u"In this PDF, width or height are not the same for "
313 u"each page. This might cause problems if you export "
314 u"pages of different dimensions into the same SWF file."
317 def OnMenuOpen(self, event):
318 dlg = wx.FileDialog(self.view, u"Choose PDF File:",
319 style=wx.OPEN|wx.CHANGE_DIR,
320 wildcard = u"PDF files (*.pdf)|*.pdf|all files (*.*)|*.*")
322 if dlg.ShowModal() == wx.ID_OK:
323 filename = dlg.GetPath()
324 self.__Load(filename)
326 def OnMenuSave(self, event, pages=None):
327 defaultFile = self.__doc.lastsavefile
328 if "wxMSW" in wx.PlatformInfo:
329 allFiles = "*.*"
330 else:
331 allFiles = "*"
332 self.view.SetStatusText(u"")
333 dlg = wx.FileDialog(self.view, u"Choose Save Filename:",
334 style = wx.SAVE | wx.OVERWRITE_PROMPT,
335 defaultFile=os.path.basename(defaultFile),
336 wildcard=u"SWF files (*.swf)|*.swf"
337 "|all files (%s)|%s" % (allFiles, allFiles))
340 if dlg.ShowModal() == wx.ID_OK:
341 menubar = self.view.GetMenuBar()
342 one_file_per_page = menubar.IsChecked(ID_ONE_PAGE_PER_FILE)
344 self.__threads["progress"] = self.__doc.SaveSWF(dlg.GetPath(),
345 one_file_per_page,
346 pages, self.options)
348 def OnUpdateUI(self, event):
349 menubar = self.view.GetMenuBar()
350 menubar.Enable(event.GetId(), self.__can_save)
352 self.view.GetToolBar().EnableTool(event.GetId(), self.__can_save)
354 def OnUpdateUIInfo(self, event):
355 menubar = self.view.GetMenuBar()
356 menubar.Enable(event.GetId(), self.__can_viewinfo)
358 self.view.GetToolBar().EnableTool(event.GetId(), self.__can_viewinfo)
360 def OnMenuSaveSelected(self, event):
361 pages = []
362 page = self.view.page_list.GetFirstSelected()
363 pages.append(page+1)
365 while True:
366 page = self.view.page_list.GetNextSelected(page)
367 if page == -1:
368 break
369 pages.append(page+1)
371 self.OnMenuSave(event, pages)
373 def OnMenuExit(self, event):
374 self.view.SetStatusText(u"Cleaning up...")
376 # Stop any running thread
377 self.__StopThreads()
379 config = GetConfig()
380 self.view.filehistory.Save(config)
381 config.Flush()
382 # A little extra cleanup is required for the FileHistory control
383 del self.view.filehistory
385 # Save quality options
386 dirpath = GetDataDir()
387 data = self.options.quality_panel.pickle()
388 try:
389 f = file(os.path.join(dirpath, 'quality.pkl'), 'wb')
390 pickle.dump(data, f)
391 f.close()
392 except Exception:
393 pass
395 # Save viewer options
396 try:
397 f = file(os.path.join(dirpath, 'viewers.pkl'), 'wb')
398 data = self.options.viewers_panel.pickle()
399 pickle.dump(data, f)
400 f.close()
401 except Exception:
402 pass
404 self.view.Destroy()
406 def OnMenuSelectAll(self, event):
407 for i in range(0, self.view.page_list.GetItemCount()):
408 self.view.page_list.Select(i, True)
410 def OnMenuInvertSelection(self, event):
411 for i in range(0, self.view.page_list.GetItemCount()):
412 self.view.page_list.Select(i, not self.view.page_list.IsSelected(i))
414 def OnMenuSelectOdd(self, event):
415 for i in range(0, self.view.page_list.GetItemCount()):
416 self.view.page_list.Select(i, not bool(i%2))
418 def OnMenuSelectEven(self, event):
419 for i in range(0, self.view.page_list.GetItemCount()):
420 self.view.page_list.Select(i, bool(i%2))
422 def OnMenuOptions(self, event):
423 self.options.ShowModal()
425 def OnFit(self, event):
426 self.__doc.Fit(self.view.page_preview.GetClientSize())
428 def OnZoom(self, event):
429 zoom = {
430 wx.ID_ZOOM_IN: .1,
431 wx.ID_ZOOM_OUT: -.1,
432 wx.ID_ZOOM_100: 1,
434 self.__doc.Zoom(zoom[event.GetId()])
436 def OnShowDocInfo(self, event):
437 info = InfoDialog(self.view)
438 info.info.display(self.__doc)
439 info.Show()
441 def OnAbout(self, evt):
442 AboutDialog(self.view)
444 def __Load(self, filename):
445 self.__can_save = True
446 self.__StopThreads()
447 self.view.SetStatusText(u"Loading document...")
448 self.__busy = wx.BusyInfo(u"One moment please, "
449 u"opening pdf document...")
451 self.view.filehistory.AddFileToHistory(filename)
452 try:
453 # I dont care if this, for some reason,
454 # give some error. I just swallow it
455 os.chdir(os.path.dirname(filename))
456 except:
457 pass
459 # Need to delay the file load a little bit
460 # for the BusyInfo get a change to repaint itself
461 #wx.FutureCall(150, self.__doc.Load, filename)
462 sel = self.view.toolbar_preview_type.GetSelection()
463 #print sel
464 PREV_TYPE = {
465 0 : [('bitmap', '1'), ('poly2bitmap', '0'), ('bitmapfonts', '1'),
466 ('textonly', '0')],
467 1 : [('bitmap', '0'), ('poly2bitmap', '1'), ('bitmapfonts', '0'),
468 ('textonly', '0')],
469 2 : [('bitmap', '0'), ('poly2bitmap', '0'), ('bitmapfonts', '0'),
470 ('textonly', '0')],
471 3 : [('bitmap', '0'), ('poly2bitmap', '0'), ('bitmapfonts', '0'),
472 ('textonly', '1')],
474 self.__doc.preview_parameters = PREV_TYPE[sel]
475 wx.CallAfter(self.__doc.Load, filename)
477 def __StopThreads(self):
478 for n, t in self.__threads.items():
479 t.Stop()
481 running = True
482 while running:
483 running = False
484 for n, t in self.__threads.items():
485 running = running + t.IsRunning()
486 time.sleep(0.1)
488 def __ReadConfigurationFile(self):
489 config = GetConfig()
490 self.view.filehistory.Load(config)
492 dirpath = GetDataDir()
493 try:
494 f = file(os.path.join(dirpath, 'quality.pkl'), 'rb')
495 #try:
496 if 1:
497 data = pickle.load(f)
498 self.options.quality_panel.unpickle(data)
499 #except:
500 # self.Message(
501 # u"Error loading quality settings. "
502 # u"They will be reset to defaults. ")
503 f.close()
504 except Exception:
505 pass
507 try:
508 f = file(os.path.join(dirpath, 'viewers.pkl'), 'rb')
509 #try:
510 if 1:
511 data = pickle.load(f)
512 self.options.viewers_panel.unpickle(data)
513 #except:
514 # self.Message(
515 # u"Error loading viewers settings. "
516 # u"They will be reset to defaults. ")
517 f.close()
518 except Exception:
519 pass
520 #d = pickle.load(f)
522 def Show(self):
523 self.view.Show()
524 if len(sys.argv) == 2:
525 self.__Load(utils.force_unicode(sys.argv[1]))
527 def Message(self, message):
528 dlg = wx.MessageDialog(self.view,
529 message,
530 style=wx.OK, pos=wx.DefaultPosition)
531 dlg.ShowModal()
532 dlg.Destroy()