added missing files
[swftools.git] / wx / lib / app.py
1 #!/usr/bin/env python
2 # -*- coding: UTF-8 -*-
3 #
4 # gpdf2swf.py
5 # graphical user interface for pdf2swf
6 #
7 # Part of the swftools package.
8
9 # Copyright (c) 2008,2009 Matthias Kramm <kramm@quiss.org> 
10 #
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.
15 #
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.
20 #
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 */
24
25 from __future__ import division
26 import os
27 import wx
28 import time
29 import pickle
30
31 from lib.wordwrap import wordwrap
32 from wx.lib.pubsub import Publisher
33
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,
40                      )
41
42
43 def GetDataDir():
44     """
45     Return the standard location on this platform for application data
46     """
47     sp = wx.StandardPaths.Get()
48     return sp.GetUserDataDir()
49
50 def GetConfig():
51     if not os.path.exists(GetDataDir()):
52         os.makedirs(GetDataDir())
53
54     config = wx.FileConfig(
55         localFilename=os.path.join(GetDataDir(), "options"))
56     return config
57
58
59 class Pdf2Swf:
60     def __init__(self):
61         self.__doc = Document()
62
63         self.__threads = {}
64
65         self.__busy = None
66         self.__progress = None
67
68         self.__can_save = False
69         self.__can_viewinfo = False
70
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
76         # the menubar appear
77         wx.CallAfter(self.view.Show)
78
79         self.options = OptionsDialog(self.view)
80         self.__ReadConfigurationFile()
81
82         self.view.toolbar_preview_type.SetSelection(0)
83
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")
99
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)
106
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)
109
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)
116
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)
121
122         self.view.Bind(wx.EVT_MENU, self.OnAbout, id=wx.ID_ABOUT)
123
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)
129
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)
135
136         self.view.page_list.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnSelectItem)
137         self.view.Bind(wx.EVT_CLOSE, self.OnMenuExit)
138
139         self.view.toolbar_preview_type.Bind(wx.EVT_CHOICE,
140                                             self.OnPreviewType)
141
142         # statusbar cancel thumbanails generation button
143         self.view.statusbar.btn_cancel.Bind(wx.EVT_BUTTON,
144                                             self.OnThumbnailCancel)
145
146     def OnFilesDroped(self, evt):
147         dlg = wx.MessageDialog(self.view,
148                       u"You must drop only one file.",
149                       u"Notice",
150                       style=wx.OK, pos=wx.DefaultPosition)
151         dlg.ShowModal()
152         dlg.Destroy()
153
154     def OnFileDroped(self, message):
155         self.__Load(message.data["filename"])
156
157     def OnPluginOnePagePerFileNotSupported(self, message):
158         self.Message(u"Selected viewer does not support "
159                      u"one page per file. ")
160
161     def OnPluginError(self, message):
162         self.Message(u"Error applying selected viewer")
163
164     def OnFileHistory(self, evt):
165         # get the file based on the menu ID
166         fileNum = evt.GetId() - wx.ID_FILE1
167         filename = self.view.filehistory.GetHistoryFile(fileNum)
168
169         self.__Load(filename)
170
171     def OnProgressBegin(self, message):
172         pages = message.data["pages"]
173         style = (
174                  wx.PD_APP_MODAL|wx.PD_ELAPSED_TIME|
175                  wx.PD_REMAINING_TIME|wx.PD_CAN_ABORT|
176                  wx.PD_AUTO_HIDE
177                 )
178         self.__progress = ProgressDialog(u"Saving...",
179                                        u"Start saving SWF pages",
180                                        maximum=pages,
181                                        parent=self.view, style=style)
182         self.__progress.Show()
183         self.view.SetStatusText(u"Saving document...")
184
185     def OnProgressUpdate(self, message):
186         pagenr = message.data["pagenr"]
187         pages = message.data["pages"]
188
189         keep_running = self.__progress.Update(
190                                  pagenr,
191                                  u"Saving SWF page %d of %d" % (pagenr, pages)
192                              )
193
194         if not keep_running and self.__threads.has_key("progress"):
195             self.view.SetStatusText(u"Cancelling...")
196             self.__threads.pop("progress").Stop()
197
198
199     def OnProgressDone(self, message):
200         if self.__threads.has_key("progress"): # it goes all the way?
201             self.__threads.pop("progress")
202             self.view.SetStatusText(u"SWF document saved successfully.")
203         else:
204             self.view.SetStatusText(u"")
205
206         self.__progress.Destroy()
207         self.__progress = None
208
209     def OnCombineError(self, message):
210         from wx.lib.dialogs import ScrolledMessageDialog
211         ScrolledMessageDialog(self.view, message.data, u"Notice").ShowModal()
212
213
214     def OnThumbnailAdded(self, message):
215         self.view.statusbar.SetGaugeValue(message.data['pagenr'])
216         tot = self.view.page_list.GetItemCount()
217         self.view.SetStatusText(u"Generating thumbnails %s/%d" %
218                                 (message.data['pagenr'], tot), 0)
219
220     def OnThumbnailDone(self, message):
221         self.view.statusbar.SetGaugeValue(0)
222         self.view.SetStatusText(u"", 0)
223         if self.__threads.has_key("thumbnails"):
224             self.__threads.pop("thumbnails")
225         self.view.SendSizeEvent()
226
227     def OnThumbnailCancel(self, event):
228         if self.__threads.has_key("thumbnails"):
229             self.__threads["thumbnails"].Stop()
230
231     def OnSelectItem(self, event):
232         self.__doc.ChangePage(event.GetIndex() + 1)
233
234     def OnPreviewType(self, event):
235         filename = self.__doc.filename
236         if filename:
237             self.__Load(filename)
238
239     def OnPageChanged(self, message):
240         # ignore if we have more than one item selected
241         if self.view.page_list.GetSelectedItemCount() > 1:
242             return
243
244         self.view.page_preview.DisplayPage(message.data)
245
246     def SetTitle(self):
247         name = wx.GetApp().GetAppName()
248         filename = os.path.basename(self.__doc.filename)
249         if self.__doc.title != "n/a":
250             t = "%s - %s (%s)" % (name, filename, self.__doc.title)
251         else:
252             t = "%s - %s" % (name, filename)
253         self.view.SetTitle(t)
254
255     def OnFileLoaded(self, message):
256         if self.__progress:
257             self.__progress.Destroy()
258             self.__progress = None
259
260         self.__can_viewinfo = True
261         del self.__busy
262
263         self.SetTitle()
264
265         if self.__doc.oktocopy == 'no':
266             self.__can_save = False
267             self.view.page_list.DisplayEmptyThumbnails(0)
268             self.view.page_preview.Clear()
269             self.view.SetStatusText(u"")
270             self.Message(
271                     u"This PDF disallows copying, cannot be converted."
272                     )
273             return
274
275         #if not self.__doc.oktoprint:
276         self.view.SetStatusText(u"Document loaded successfully.")
277
278         self.view.page_list.DisplayEmptyThumbnails(message.data["pages"])
279         thumbs = self.__doc.GetThumbnails()
280         t = self.view.page_list.DisplayThumbnails(thumbs)
281         self.__threads["thumbnails"] = t
282         self.view.statusbar.SetGaugeRange(message.data["pages"])
283         #del self.__busy
284
285     def OnFileNotLoaded(self, message):
286         self.__can_save = False
287         self.__can_viewinfo = False
288         del self.__busy
289         self.view.SetStatusText(u"")
290         self.Message(
291                     u"Could not open file %s" % message.data['filename']
292                     )
293         
294     def OnDiffSizes(self, message):
295         # just let the user know- for now, we can't handle this properly
296         self.Message(
297                     u"In this PDF, width or height are not the same for "
298                     u"each page. This might cause problems if you export "
299                     u"pages of different dimensions into the same SWF file."
300                     )
301
302     def OnMenuOpen(self, event):
303         dlg = wx.FileDialog(self.view, u"Choose PDF File:",
304                      style=wx.OPEN|wx.CHANGE_DIR,
305                      wildcard = u"PDF files (*.pdf)|*.pdf|all files (*.*)|*.*")
306
307         if dlg.ShowModal() == wx.ID_OK:
308             filename = dlg.GetPath()
309             self.__Load(filename)
310
311     def OnMenuSave(self, event, pages=None):
312         defaultFile = self.__doc.lastsavefile
313         if "wxMSW" in wx.PlatformInfo:
314             allFiles = "*.*"
315         else:
316             allFiles = "*"
317         self.view.SetStatusText(u"")
318         dlg = wx.FileDialog(self.view, u"Choose Save Filename:",
319                        style = wx.SAVE | wx.OVERWRITE_PROMPT,
320                        defaultFile=os.path.basename(defaultFile),
321                        wildcard=u"SWF files (*.swf)|*.swf"
322                                  "|all files (%s)|%s" % (allFiles, allFiles))
323
324
325         if dlg.ShowModal() == wx.ID_OK:
326             menubar = self.view.GetMenuBar()
327             one_file_per_page = menubar.IsChecked(ID_ONE_PAGE_PER_FILE)
328
329             self.__threads["progress"] = self.__doc.SaveSWF(dlg.GetPath(),
330                                                          one_file_per_page,
331                                                          pages, self.options)
332
333     def OnUpdateUI(self, event):
334         menubar = self.view.GetMenuBar()
335         menubar.Enable(event.GetId(), self.__can_save)
336
337         self.view.GetToolBar().EnableTool(event.GetId(), self.__can_save)
338
339     def OnUpdateUIInfo(self, event):
340         menubar = self.view.GetMenuBar()
341         menubar.Enable(event.GetId(), self.__can_viewinfo)
342
343         self.view.GetToolBar().EnableTool(event.GetId(), self.__can_viewinfo)
344
345     def OnMenuSaveSelected(self, event):
346         pages = []
347         page = self.view.page_list.GetFirstSelected()
348         pages.append(page+1)
349
350         while True:
351             page = self.view.page_list.GetNextSelected(page)
352             if page == -1:
353                 break
354             pages.append(page+1)
355
356         self.OnMenuSave(event, pages)
357
358     def OnMenuExit(self, event):
359         self.view.SetStatusText(u"Cleaning up...")
360
361         # Stop any running thread
362         self.__StopThreads()
363
364         config = GetConfig()
365         self.view.filehistory.Save(config)
366         config.Flush()
367         # A little extra cleanup is required for the FileHistory control
368         del self.view.filehistory
369
370         # Save quality options
371         dirpath = GetDataDir()
372         data = self.options.quality_panel.pickle()
373         try:
374             f = file(os.path.join(dirpath, 'quality.pkl'), 'wb')
375             pickle.dump(data, f)
376             f.close()
377         except Exception:
378             pass
379
380         # Save viewer options
381         try:
382             f = file(os.path.join(dirpath, 'viewers.pkl'), 'wb')
383             data = self.options.viewers_panel.pickle()
384             pickle.dump(data, f)
385             f.close()
386         except Exception:
387             pass
388
389         self.view.Destroy()
390
391     def OnMenuSelectAll(self, event):
392         for i in range(0, self.view.page_list.GetItemCount()):
393             self.view.page_list.Select(i, True)
394
395     def OnMenuInvertSelection(self, event):
396         for i in range(0, self.view.page_list.GetItemCount()):
397             self.view.page_list.Select(i, not self.view.page_list.IsSelected(i))
398
399     def OnMenuSelectOdd(self, event):
400         for i in range(0, self.view.page_list.GetItemCount()):
401             self.view.page_list.Select(i, not bool(i%2))
402
403     def OnMenuSelectEven(self, event):
404         for i in range(0, self.view.page_list.GetItemCount()):
405             self.view.page_list.Select(i, bool(i%2))
406
407     def OnMenuOptions(self, event):
408         self.options.ShowModal()
409
410     def OnFit(self, event):
411         self.__doc.Fit(self.view.page_preview.GetClientSize())
412
413     def OnZoom(self, event):
414         zoom = {
415               wx.ID_ZOOM_IN: .1,
416               wx.ID_ZOOM_OUT: -.1,
417               wx.ID_ZOOM_100: 1,
418         }
419         self.__doc.Zoom(zoom[event.GetId()])
420
421     def OnShowDocInfo(self, event):
422         info = InfoDialog(self.view)
423         info.info.display(self.__doc)
424         info.Show()
425
426     def OnAbout(self, evt):
427         AboutDialog(self.view)
428
429     def __Load(self, filename):
430         self.__can_save = True
431         self.__StopThreads()
432         self.view.SetStatusText(u"Loading document...")
433         self.__busy = wx.BusyInfo(u"One moment please, "
434                                   u"opening pdf document...")
435
436         self.view.filehistory.AddFileToHistory(filename)
437         try:
438             # I dont care if this, for some reason,
439             # give some error. I just swallow it
440             os.chdir(os.path.dirname(filename))
441         except:
442             pass
443
444         # Need to delay the file load a little bit
445         # for the BusyInfo get a change to repaint itself
446         #wx.FutureCall(150, self.__doc.Load, filename)
447         sel = self.view.toolbar_preview_type.GetSelection()
448         #print sel
449         PREV_TYPE = {
450             0 : [('bitmap', '1'), ('poly2bitmap', '0'), ('bitmapfonts', '1'),],
451             1 : [('bitmap', '0'), ('poly2bitmap', '1'), ('bitmapfonts', '0'),],
452             2 : [('bitmap', '0'), ('poly2bitmap', '0'), ('bitmapfonts', '0'),],
453         }
454         self.__doc.preview_parameters = PREV_TYPE[sel]
455         wx.CallAfter(self.__doc.Load, filename)
456
457     def __StopThreads(self):
458         for n, t in self.__threads.items():
459             t.Stop()
460
461         running = True
462         while running:
463             running = False
464             for n, t in self.__threads.items():
465                 running = running + t.IsRunning()
466             time.sleep(0.1)
467
468     def __ReadConfigurationFile(self):
469         config = GetConfig()
470         self.view.filehistory.Load(config)
471
472         dirpath = GetDataDir()
473         try:
474             f = file(os.path.join(dirpath, 'quality.pkl'), 'rb')
475             #try:
476             if 1:
477                 data = pickle.load(f)
478                 self.options.quality_panel.unpickle(data)
479             #except:
480             #    self.Message(
481             #          u"Error loading quality settings. "
482             #          u"They will be reset to defaults. ")
483             f.close()
484         except Exception:
485             pass
486
487         try:
488             f = file(os.path.join(dirpath, 'viewers.pkl'), 'rb')
489             #try:
490             if 1:
491                 data = pickle.load(f)
492                 self.options.viewers_panel.unpickle(data)
493             #except:
494             #    self.Message(
495             #          u"Error loading viewers settings. "
496             #          u"They will be reset to defaults. ")
497             f.close()
498         except Exception:
499             pass
500         #d = pickle.load(f)
501
502     def Message(self, message):
503         dlg = wx.MessageDialog(self.view,
504                       message,
505                       style=wx.OK, pos=wx.DefaultPosition)
506         dlg.ShowModal()
507         dlg.Destroy()
508