2 # -*- coding: UTF-8 -*-
5 # graphical user interface for pdf2swf
7 # Part of the swftools package.
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
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,
39 ID_SELECT_EVEN, ID_DOC_INFO,
45 Return the standard location on this platform for application data
47 sp = wx.StandardPaths.Get()
48 return sp.GetUserDataDir()
51 if not os.path.exists(GetDataDir()):
52 os.makedirs(GetDataDir())
54 config = wx.FileConfig(
55 localFilename=os.path.join(GetDataDir(), "options"))
61 self.__doc = Document()
66 self.__progress = None
68 self.__can_save = False
69 self.__can_viewinfo = False
71 self.view = PdfFrame()
72 wx.GetApp().SetTopWindow(self.view)
73 # Call Show after the current and pending event
74 # handlers have been completed. Otherwise on MSW
75 # we see the frame been draw and after that we saw
77 wx.CallAfter(self.view.Show)
79 self.options = OptionsDialog(self.view)
80 self.__ReadConfigurationFile()
82 self.view.toolbar_preview_type.SetSelection(0)
84 Publisher.subscribe(self.OnPageChanged, "PAGE_CHANGED")
85 Publisher.subscribe(self.OnFileLoaded, "FILE_LOADED")
86 Publisher.subscribe(self.OnFileNotLoaded, "FILE_NOT_LOADED")
87 Publisher.subscribe(self.OnDiffSizes, "DIFF_SIZES")
88 Publisher.subscribe(self.OnThumbnailAdded, "THUMBNAIL_ADDED")
89 Publisher.subscribe(self.OnThumbnailDone, "THUMBNAIL_DONE")
90 Publisher.subscribe(self.OnProgressBegin, "SWF_BEGIN_SAVE")
91 Publisher.subscribe(self.OnProgressUpdate, "SWF_PAGE_SAVED")
92 Publisher.subscribe(self.OnProgressDone, "SWF_FILE_SAVED")
93 Publisher.subscribe(self.OnCombineError, "SWF_COMBINE_ERROR")
94 Publisher.subscribe(self.OnFileDroped, "FILE_DROPED")
95 Publisher.subscribe(self.OnFilesDroped, "FILES_DROPED")
96 Publisher.subscribe(self.OnPluginOnePagePerFileNotSupported,
97 "PLUGIN_ONE_PAGE_PER_FILE_NOT_SUPPORTED")
98 Publisher.subscribe(self.OnPluginError, "PLUGIN_ERROR")
100 self.view.Bind(wx.EVT_MENU, self.OnMenuOpen, id=wx.ID_OPEN)
101 self.view.Bind(wx.EVT_MENU, self.OnMenuSave, id=wx.ID_SAVE)
102 self.view.Bind(wx.EVT_MENU, self.OnMenuSaveSelected, id=wx.ID_SAVEAS)
103 self.view.Bind(wx.EVT_MENU, self.OnMenuExit, id=wx.ID_EXIT)
104 self.view.Bind(wx.EVT_MENU_RANGE, self.OnFileHistory,
105 id=wx.ID_FILE1, id2=wx.ID_FILE9)
107 self.view.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=wx.ID_SAVE)
108 self.view.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=wx.ID_SAVEAS)
110 self.view.Bind(wx.EVT_MENU, self.OnMenuSelectAll, id=wx.ID_SELECTALL)
111 self.view.Bind(wx.EVT_MENU,
112 self.OnMenuInvertSelection, id=ID_INVERT_SELECTION)
113 self.view.Bind(wx.EVT_MENU, self.OnMenuSelectOdd, id=ID_SELECT_ODD)
114 self.view.Bind(wx.EVT_MENU, self.OnMenuSelectEven, id=ID_SELECT_EVEN)
115 self.view.Bind(wx.EVT_MENU, self.OnMenuOptions, id=wx.ID_PREFERENCES)
117 self.view.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=wx.ID_SELECTALL)
118 self.view.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=ID_INVERT_SELECTION)
119 self.view.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=ID_SELECT_ODD)
120 self.view.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=ID_SELECT_EVEN)
122 self.view.Bind(wx.EVT_MENU, self.OnAbout, id=wx.ID_ABOUT)
124 self.view.Bind(wx.EVT_MENU, self.OnZoom, id=wx.ID_ZOOM_IN)
125 self.view.Bind(wx.EVT_MENU, self.OnZoom, id=wx.ID_ZOOM_OUT)
126 self.view.Bind(wx.EVT_MENU, self.OnZoom, id=wx.ID_ZOOM_100)
127 self.view.Bind(wx.EVT_MENU, self.OnFit, id=wx.ID_ZOOM_FIT)
128 self.view.Bind(wx.EVT_MENU, self.OnShowDocInfo, id=ID_DOC_INFO)
130 self.view.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=wx.ID_ZOOM_IN)
131 self.view.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=wx.ID_ZOOM_OUT)
132 self.view.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=wx.ID_ZOOM_100)
133 self.view.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=wx.ID_ZOOM_FIT)
134 self.view.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUIInfo, id=ID_DOC_INFO)
136 self.view.page_list.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnSelectItem)
137 self.view.Bind(wx.EVT_CLOSE, self.OnMenuExit)
139 self.view.toolbar_preview_type.Bind(wx.EVT_CHOICE,
142 # statusbar cancel thumbanails generation button
143 self.view.statusbar.btn_cancel.Bind(wx.EVT_BUTTON,
144 self.OnThumbnailCancel)
146 # Don't know where the problem is (python/xpython or wxwidgets/wxpython)
147 # but I found that this hack was necessary to avoid the app enter in a
148 # idle state. We must, for example, move the mouse inside the app
149 # for threads continue their job.
150 # There is no need for this when freezing with other utils, like
151 # py2exe, pyinstaller, cxfreeze
152 if "wxMSW" in wx.PlatformInfo:
153 self.timer = wx.Timer(self.view)
154 self.view.Bind(wx.EVT_TIMER, lambda evt: None)
157 def OnFilesDroped(self, evt):
158 dlg = wx.MessageDialog(self.view,
159 u"You must drop only one file.",
161 style=wx.OK, pos=wx.DefaultPosition)
165 def OnFileDroped(self, message):
166 self.__Load(message.data["filename"])
168 def OnPluginOnePagePerFileNotSupported(self, message):
169 self.Message(u"Selected viewer does not support "
170 u"one page per file. ")
172 def OnPluginError(self, message):
173 self.Message(u"Error applying selected viewer")
175 def OnFileHistory(self, evt):
176 # get the file based on the menu ID
177 fileNum = evt.GetId() - wx.ID_FILE1
178 filename = self.view.filehistory.GetHistoryFile(fileNum)
180 self.__Load(filename)
182 def OnProgressBegin(self, message):
183 pages = message.data["pages"]
185 wx.PD_APP_MODAL|wx.PD_ELAPSED_TIME|
186 wx.PD_REMAINING_TIME|wx.PD_CAN_ABORT|
189 self.__progress = ProgressDialog(u"Saving...",
190 u"Start saving SWF pages",
192 parent=self.view, style=style)
193 self.__progress.Show()
194 self.view.SetStatusText(u"Saving document...")
196 def OnProgressUpdate(self, message):
197 pagenr = message.data["pagenr"]
198 pages = message.data["pages"]
200 keep_running = self.__progress.Update(
202 u"Saving SWF page %d of %d" % (pagenr, pages)
205 if not keep_running and self.__threads.has_key("progress"):
206 self.view.SetStatusText(u"Cancelling...")
207 self.__threads.pop("progress").Stop()
210 def OnProgressDone(self, message):
211 if self.__threads.has_key("progress"): # it goes all the way?
212 self.__threads.pop("progress")
213 self.view.SetStatusText(u"SWF document saved successfully.")
215 self.view.SetStatusText(u"")
217 self.__progress.Destroy()
218 self.__progress = None
220 def OnCombineError(self, message):
221 from wx.lib.dialogs import ScrolledMessageDialog
222 ScrolledMessageDialog(self.view, message.data, u"Notice").ShowModal()
225 def OnThumbnailAdded(self, message):
226 self.view.statusbar.SetGaugeValue(message.data['pagenr'])
227 tot = self.view.page_list.GetItemCount()
228 self.view.SetStatusText(u"Generating thumbnails %s/%d" %
229 (message.data['pagenr'], tot), 0)
231 def OnThumbnailDone(self, message):
232 self.view.statusbar.SetGaugeValue(0)
233 self.view.SetStatusText(u"", 0)
234 if self.__threads.has_key("thumbnails"):
235 self.__threads.pop("thumbnails")
236 self.view.SendSizeEvent()
238 def OnThumbnailCancel(self, event):
239 if self.__threads.has_key("thumbnails"):
240 self.__threads["thumbnails"].Stop()
242 def OnSelectItem(self, event):
243 self.__doc.ChangePage(event.GetIndex() + 1)
245 def OnPreviewType(self, event):
246 filename = self.__doc.filename
248 self.__Load(filename)
250 def OnPageChanged(self, message):
251 # ignore if we have more than one item selected
252 if self.view.page_list.GetSelectedItemCount() > 1:
255 self.view.page_preview.DisplayPage(message.data)
258 name = wx.GetApp().GetAppName()
259 filename = os.path.basename(self.__doc.filename)
260 if self.__doc.title != "n/a":
261 t = "%s - %s (%s)" % (name, filename, self.__doc.title)
263 t = "%s - %s" % (name, filename)
264 self.view.SetTitle(t)
266 def OnFileLoaded(self, message):
268 self.__progress.Destroy()
269 self.__progress = None
271 self.__can_viewinfo = True
276 if self.__doc.oktocopy == 'no':
277 self.__can_save = False
278 self.view.page_list.DisplayEmptyThumbnails(0)
279 self.view.page_preview.Clear()
280 self.view.SetStatusText(u"")
282 u"This PDF disallows copying, cannot be converted."
286 #if not self.__doc.oktoprint:
287 self.view.SetStatusText(u"Document loaded successfully.")
289 self.view.page_list.DisplayEmptyThumbnails(message.data["pages"])
290 thumbs = self.__doc.GetThumbnails()
291 t = self.view.page_list.DisplayThumbnails(thumbs)
292 self.__threads["thumbnails"] = t
293 self.view.statusbar.SetGaugeRange(message.data["pages"])
296 def OnFileNotLoaded(self, message):
297 self.__can_save = False
298 self.__can_viewinfo = False
300 self.view.SetStatusText(u"")
302 u"Could not open file %s" % message.data['filename']
305 def OnDiffSizes(self, message):
306 # just let the user know- for now, we can't handle this properly
308 u"In this PDF, width or height are not the same for "
309 u"each page. This might cause problems if you export "
310 u"pages of different dimensions into the same SWF file."
313 def OnMenuOpen(self, event):
314 dlg = wx.FileDialog(self.view, u"Choose PDF File:",
315 style=wx.OPEN|wx.CHANGE_DIR,
316 wildcard = u"PDF files (*.pdf)|*.pdf|all files (*.*)|*.*")
318 if dlg.ShowModal() == wx.ID_OK:
319 filename = dlg.GetPath()
320 self.__Load(filename)
322 def OnMenuSave(self, event, pages=None):
323 defaultFile = self.__doc.lastsavefile
324 if "wxMSW" in wx.PlatformInfo:
328 self.view.SetStatusText(u"")
329 dlg = wx.FileDialog(self.view, u"Choose Save Filename:",
330 style = wx.SAVE | wx.OVERWRITE_PROMPT,
331 defaultFile=os.path.basename(defaultFile),
332 wildcard=u"SWF files (*.swf)|*.swf"
333 "|all files (%s)|%s" % (allFiles, allFiles))
336 if dlg.ShowModal() == wx.ID_OK:
337 menubar = self.view.GetMenuBar()
338 one_file_per_page = menubar.IsChecked(ID_ONE_PAGE_PER_FILE)
340 self.__threads["progress"] = self.__doc.SaveSWF(dlg.GetPath(),
344 def OnUpdateUI(self, event):
345 menubar = self.view.GetMenuBar()
346 menubar.Enable(event.GetId(), self.__can_save)
348 self.view.GetToolBar().EnableTool(event.GetId(), self.__can_save)
350 def OnUpdateUIInfo(self, event):
351 menubar = self.view.GetMenuBar()
352 menubar.Enable(event.GetId(), self.__can_viewinfo)
354 self.view.GetToolBar().EnableTool(event.GetId(), self.__can_viewinfo)
356 def OnMenuSaveSelected(self, event):
358 page = self.view.page_list.GetFirstSelected()
362 page = self.view.page_list.GetNextSelected(page)
367 self.OnMenuSave(event, pages)
369 def OnMenuExit(self, event):
370 self.view.SetStatusText(u"Cleaning up...")
372 # Stop any running thread
376 self.view.filehistory.Save(config)
378 # A little extra cleanup is required for the FileHistory control
379 del self.view.filehistory
381 # Save quality options
382 dirpath = GetDataDir()
383 data = self.options.quality_panel.pickle()
385 f = file(os.path.join(dirpath, 'quality.pkl'), 'wb')
391 # Save viewer options
393 f = file(os.path.join(dirpath, 'viewers.pkl'), 'wb')
394 data = self.options.viewers_panel.pickle()
402 def OnMenuSelectAll(self, event):
403 for i in range(0, self.view.page_list.GetItemCount()):
404 self.view.page_list.Select(i, True)
406 def OnMenuInvertSelection(self, event):
407 for i in range(0, self.view.page_list.GetItemCount()):
408 self.view.page_list.Select(i, not self.view.page_list.IsSelected(i))
410 def OnMenuSelectOdd(self, event):
411 for i in range(0, self.view.page_list.GetItemCount()):
412 self.view.page_list.Select(i, not bool(i%2))
414 def OnMenuSelectEven(self, event):
415 for i in range(0, self.view.page_list.GetItemCount()):
416 self.view.page_list.Select(i, bool(i%2))
418 def OnMenuOptions(self, event):
419 self.options.ShowModal()
421 def OnFit(self, event):
422 self.__doc.Fit(self.view.page_preview.GetClientSize())
424 def OnZoom(self, event):
430 self.__doc.Zoom(zoom[event.GetId()])
432 def OnShowDocInfo(self, event):
433 info = InfoDialog(self.view)
434 info.info.display(self.__doc)
437 def OnAbout(self, evt):
438 AboutDialog(self.view)
440 def __Load(self, filename):
441 self.__can_save = True
443 self.view.SetStatusText(u"Loading document...")
444 self.__busy = wx.BusyInfo(u"One moment please, "
445 u"opening pdf document...")
447 self.view.filehistory.AddFileToHistory(filename)
449 # I dont care if this, for some reason,
450 # give some error. I just swallow it
451 os.chdir(os.path.dirname(filename))
455 # Need to delay the file load a little bit
456 # for the BusyInfo get a change to repaint itself
457 #wx.FutureCall(150, self.__doc.Load, filename)
458 sel = self.view.toolbar_preview_type.GetSelection()
461 0 : [('bitmap', '1'), ('poly2bitmap', '0'), ('bitmapfonts', '1'),],
462 1 : [('bitmap', '0'), ('poly2bitmap', '1'), ('bitmapfonts', '0'),],
463 2 : [('bitmap', '0'), ('poly2bitmap', '0'), ('bitmapfonts', '0'),],
465 self.__doc.preview_parameters = PREV_TYPE[sel]
466 wx.CallAfter(self.__doc.Load, filename)
468 def __StopThreads(self):
469 for n, t in self.__threads.items():
475 for n, t in self.__threads.items():
476 running = running + t.IsRunning()
479 def __ReadConfigurationFile(self):
481 self.view.filehistory.Load(config)
483 dirpath = GetDataDir()
485 f = file(os.path.join(dirpath, 'quality.pkl'), 'rb')
488 data = pickle.load(f)
489 self.options.quality_panel.unpickle(data)
492 # u"Error loading quality settings. "
493 # u"They will be reset to defaults. ")
499 f = file(os.path.join(dirpath, 'viewers.pkl'), 'rb')
502 data = pickle.load(f)
503 self.options.viewers_panel.unpickle(data)
506 # u"Error loading viewers settings. "
507 # u"They will be reset to defaults. ")
513 def Message(self, message):
514 dlg = wx.MessageDialog(self.view,
516 style=wx.OK, pos=wx.DefaultPosition)