merged in Ricardo Pedroso's new pdf2swf gui
[swftools.git] / wx / app.py
diff --git a/wx/app.py b/wx/app.py
new file mode 100644 (file)
index 0000000..204ddaa
--- /dev/null
+++ b/wx/app.py
@@ -0,0 +1,413 @@
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+#
+# gpdf2swf.py
+# graphical user interface for pdf2swf
+#
+# Part of the swftools package.
+# 
+# Copyright (c) 2008,2009 Matthias Kramm <kramm@quiss.org> 
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */
+
+from __future__ import division
+import os
+import wx
+import time
+import pickle
+
+from wx.lib.wordwrap import wordwrap
+from wx.lib.pubsub import Publisher
+
+from document import Document
+from gui import (PdfFrame, ProgressDialog, OptionsDialog, AboutDialog,
+                 ID_INVERT_SELECTION, ID_SELECT_ODD, ID_SELECT_EVEN)
+
+
+def GetDataDir():
+    """
+    Return the standard location on this platform for application data
+    """
+    sp = wx.StandardPaths.Get()
+    return sp.GetUserDataDir()
+
+def GetConfig():
+    if not os.path.exists(GetDataDir()):
+        os.makedirs(GetDataDir())
+
+    config = wx.FileConfig(
+        localFilename=os.path.join(GetDataDir(), "options"))
+    return config
+
+
+class Pdf2Swf:
+    def __init__(self):
+        self.__doc = Document()
+
+        self.__threads = {}
+
+        self.__busy = None
+        self.__progress = None
+
+        self.__can_save = False
+
+        self.view = PdfFrame()
+        wx.GetApp().SetTopWindow(self.view)
+        # Call Show after the current and pending event
+        # handlers have been completed. Otherwise on MSW
+        # we see the frame been draw and after that we saw
+        # the menubar appear
+        wx.CallAfter(self.view.Show)
+
+        self.options = OptionsDialog(self.view)
+        self.__ReadConfigurationFile()
+
+        Publisher.subscribe(self.OnPageChanged, "PAGE_CHANGED")
+        Publisher.subscribe(self.OnFileLoaded, "FILE_LOADED")
+        Publisher.subscribe(self.OnDiffSizes, "DIFF_SIZES")
+        Publisher.subscribe(self.OnThumbnailAdded, "THUMBNAIL_ADDED")
+        Publisher.subscribe(self.OnThumbnailDone, "THUMBNAIL_DONE")
+        Publisher.subscribe(self.OnProgressBegin, "SWF_BEGIN_SAVE")
+        Publisher.subscribe(self.OnProgressUpdate, "SWF_PAGE_SAVED")
+        Publisher.subscribe(self.OnProgressDone, "SWF_FILE_SAVED")
+        Publisher.subscribe(self.OnCombineError, "SWF_COMBINE_ERROR")
+        Publisher.subscribe(self.OnFileDroped, "FILE_DROPED")
+        Publisher.subscribe(self.OnFilesDroped, "FILES_DROPED")
+
+        self.view.Bind(wx.EVT_MENU, self.OnMenuOpen, id=wx.ID_OPEN)
+        self.view.Bind(wx.EVT_MENU, self.OnMenuSave, id=wx.ID_SAVE)
+        self.view.Bind(wx.EVT_MENU, self.OnMenuSaveSelected, id=wx.ID_SAVEAS)
+        self.view.Bind(wx.EVT_MENU, self.OnMenuExit, id=wx.ID_EXIT)
+        self.view.Bind(wx.EVT_MENU_RANGE, self.OnFileHistory,
+                       id=wx.ID_FILE1, id2=wx.ID_FILE9)
+
+        self.view.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=wx.ID_SAVE)
+        self.view.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=wx.ID_SAVEAS)
+
+        self.view.Bind(wx.EVT_MENU, self.OnMenuSelectAll, id=wx.ID_SELECTALL)
+        self.view.Bind(wx.EVT_MENU,
+                       self.OnMenuInvertSelection, id=ID_INVERT_SELECTION)
+        self.view.Bind(wx.EVT_MENU, self.OnMenuSelectOdd, id=ID_SELECT_ODD)
+        self.view.Bind(wx.EVT_MENU, self.OnMenuSelectEven, id=ID_SELECT_EVEN)
+        self.view.Bind(wx.EVT_MENU, self.OnMenuOptions, id=wx.ID_PREFERENCES)
+
+        self.view.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=wx.ID_SELECTALL)
+        self.view.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=ID_INVERT_SELECTION)
+        self.view.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=ID_SELECT_ODD)
+        self.view.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=ID_SELECT_EVEN)
+
+        self.view.Bind(wx.EVT_MENU, self.OnAbout, id=wx.ID_ABOUT)
+
+        self.view.Bind(wx.EVT_MENU, self.OnZoom, id=wx.ID_ZOOM_IN)
+        self.view.Bind(wx.EVT_MENU, self.OnZoom, id=wx.ID_ZOOM_OUT)
+        self.view.Bind(wx.EVT_MENU, self.OnZoom, id=wx.ID_ZOOM_100)
+        self.view.Bind(wx.EVT_MENU, self.OnFit, id=wx.ID_ZOOM_FIT)
+
+        self.view.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=wx.ID_ZOOM_IN)
+        self.view.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=wx.ID_ZOOM_OUT)
+        self.view.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=wx.ID_ZOOM_100)
+        self.view.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=wx.ID_ZOOM_FIT)
+
+        self.view.page_list.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnSelectItem)
+        self.view.Bind(wx.EVT_CLOSE, self.OnMenuExit)
+
+        # statusbar cancel thumbanails generation button
+        self.view.statusbar.btn_cancel.Bind(wx.EVT_BUTTON,
+                                            self.OnThumbnailCancel)
+
+    def OnFilesDroped(self, evt):
+        dlg = wx.MessageDialog(self.view,
+                      u"You must drop only one file.",
+                      u"Notice",
+                      style=wx.OK, pos=wx.DefaultPosition)
+        dlg.ShowModal()
+        dlg.Destroy()
+
+    def OnFileDroped(self, message):
+        self.__Load(message.data["filename"])
+
+    def OnFileHistory(self, evt):
+        # get the file based on the menu ID
+        fileNum = evt.GetId() - wx.ID_FILE1
+        filename = self.view.filehistory.GetHistoryFile(fileNum)
+
+        self.__Load(filename)
+
+    def OnProgressBegin(self, message):
+        if self.__progress:
+            self.__progress.Destroy()
+        pages = message.data["pages"]
+        style = (
+                 wx.PD_APP_MODAL|wx.PD_ELAPSED_TIME|
+                 wx.PD_REMAINING_TIME|wx.PD_CAN_ABORT|
+                 wx.PD_AUTO_HIDE
+                )
+        self.__progress = ProgressDialog(u"Saving...",
+                                       u"Start saving SWF pages",
+                                       maximum=pages-1,
+                                       parent=self.view, style=style)
+        self.__progress.Show()
+        self.view.SetStatusText(u"Saving document...")
+
+    def OnProgressUpdate(self, message):
+        pagenr = message.data["pagenr"]
+        pagenr0 = pagenr - 1 # 0 based
+        pages = message.data["pages"]
+
+        keep_running, skip = self.__progress.Update(
+                                 pagenr0,
+                                 u"Saving SWF page %d of %d" % (pagenr, pages)
+                             )
+        if not keep_running and self.__threads.has_key("progress"):
+            self.view.SetStatusText(u"Cancelling...")
+            self.__threads.pop("progress").Stop()
+
+        # Send size events to resize the progress dialog,
+        # this will allow the progress message label to resize accordingly.
+        # Here we minimize that events every 10 times.
+        if pagenr0 % 10 == 0:
+            self.__progress.SendSizeEvent()
+
+    def OnProgressDone(self, message):
+        self.__progress.Hide()
+        if self.__threads.has_key("progress"): # it goes all the way?
+            self.__threads.pop("progress")
+            self.view.SetStatusText(u"SWF document saved successfully.")
+        else:
+            self.view.SetStatusText(u"")
+
+    def OnCombineError(self, message):
+        from wx.lib.dialogs import ScrolledMessageDialog
+        ScrolledMessageDialog(self.view, message.data, u"Notice").ShowModal()
+
+
+    def OnThumbnailAdded(self, message):
+        self.view.statusbar.SetGaugeValue(message.data['pagenr'])
+        tot = self.view.page_list.GetItemCount()
+        self.view.SetStatusText(u"Generating thumbnails %s/%d" %
+                                (message.data['pagenr'], tot), 0)
+
+    def OnThumbnailDone(self, message):
+        self.view.statusbar.SetGaugeValue(0)
+        self.view.SetStatusText(u"", 0)
+        if self.__threads.has_key("thumbnails"):
+            self.__threads.pop("thumbnails")
+
+    def OnThumbnailCancel(self, event):
+        if self.__threads.has_key("thumbnails"):
+            self.__threads["thumbnails"].Stop()
+
+    def OnSelectItem(self, event):
+        self.__doc.ChangePage(event.GetIndex() + 1)
+
+    def OnPageChanged(self, message):
+        # ignore if we have more than one item selected
+        if self.view.page_list.GetSelectedItemCount() > 1:
+            return
+
+        self.view.page_preview.DisplayPage(message.data)
+
+    def OnFileLoaded(self, message):
+        if self.__progress:
+            self.__progress.Destroy()
+            self.__progress = None
+
+        self.view.SetStatusText(u"Document loaded successfully.")
+        self.view.page_list.DisplayEmptyThumbnails(message.data["pages"])
+        thumbs = self.__doc.GetThumbnails()
+        t = self.view.page_list.DisplayThumbnails(thumbs)
+        self.__threads["thumbnails"] = t
+        self.view.statusbar.SetGaugeRange(message.data["pages"])
+        del self.__busy
+
+    def OnDiffSizes(self, message):
+        # just let the user know- for now, we can't handle this properly
+        self.Message(
+                    u"In this PDF, width or height are not the same for "
+                    u"each page. This might cause problems if you export "
+                    u"pages of different dimensions into the same SWF file.",
+                    u"Notice")
+
+    def OnMenuOpen(self, event):
+        dlg = wx.FileDialog(self.view, u"Choose PDF File:",
+                     style = wx.FD_CHANGE_DIR|wx.FD_OPEN,
+                     wildcard = u"PDF files (*.pdf)|*.pdf|all files (*.*)|*.*")
+
+        if dlg.ShowModal() == wx.ID_OK:
+            filename = dlg.GetPath()
+            self.__Load(filename)
+
+    def OnMenuSave(self, event, pages=None):
+        defaultFile = self.__doc.lastsavefile
+        allFiles = "*.*" if "wxMSW" in wx.PlatformInfo else "*"
+        self.view.SetStatusText(u"")
+        dlg = wx.FileDialog(self.view, u"Choose Save Filename:",
+                       style = wx.SAVE | wx.OVERWRITE_PROMPT,
+                       defaultFile=defaultFile,
+                       wildcard=u"SWF files (*.swf)|*.swf"
+                                 "|all files (%s)|%s" % (allFiles, allFiles))
+
+        if dlg.ShowModal() == wx.ID_OK:
+            self.__threads["progress"] = self.__doc.SaveSWF(dlg.GetPath(),
+                                                         pages, self.options)
+
+    def OnUpdateUI(self, event):
+        menubar = self.view.GetMenuBar()
+        menubar.Enable(event.GetId(), self.__can_save)
+
+        self.view.GetToolBar().EnableTool(event.GetId(), self.__can_save)
+
+    def OnMenuSaveSelected(self, event):
+        pages = []
+        page = self.view.page_list.GetFirstSelected()
+        pages.append(page+1)
+
+        while True:
+            page = self.view.page_list.GetNextSelected(page)
+            if page == -1:
+                break
+            pages.append(page+1)
+
+        self.OnMenuSave(event, pages)
+
+    def OnMenuExit(self, event):
+        self.view.SetStatusText(u"Cleaning up...")
+
+        # Stop any running thread
+        self.__StopThreads()
+
+        config = GetConfig()
+        self.view.filehistory.Save(config)
+        config.Flush()
+        # A little extra cleanup is required for the FileHistory control
+        del self.view.filehistory
+
+        # Save quality options
+        dirpath = GetDataDir()
+        data = self.options.quality_panel.pickle()
+        try:
+            f = file(os.path.join(dirpath, 'quality.pkl'), 'wb')
+            pickle.dump(data, f)
+            f.close()
+        except IOError:
+            pass
+
+        # Save viewer options
+        try:
+            f = file(os.path.join(dirpath, 'viewers.pkl'), 'wb')
+            data = self.options.viewers_panel.pickle()
+            pickle.dump(data, f)
+            f.close()
+        except Exception, e:
+            pass
+
+        self.view.Destroy()
+
+    def OnMenuSelectAll(self, event):
+        for i in range(0, self.view.page_list.GetItemCount()):
+            self.view.page_list.Select(i, True)
+
+    def OnMenuInvertSelection(self, event):
+        for i in range(0, self.view.page_list.GetItemCount()):
+            self.view.page_list.Select(i, not self.view.page_list.IsSelected(i))
+
+    def OnMenuSelectOdd(self, event):
+        for i in range(0, self.view.page_list.GetItemCount()):
+            self.view.page_list.Select(i, not bool(i%2))
+
+    def OnMenuSelectEven(self, event):
+        for i in range(0, self.view.page_list.GetItemCount()):
+            self.view.page_list.Select(i, bool(i%2))
+
+    def OnMenuOptions(self, event):
+        self.options.ShowModal()
+
+    def OnFit(self, event):
+        self.__doc.Fit(self.view.page_preview.GetClientSize())
+
+    def OnZoom(self, event):
+        zoom = {
+              wx.ID_ZOOM_IN: .1,
+              wx.ID_ZOOM_OUT: -.1,
+              wx.ID_ZOOM_100: 1,
+        }
+        self.__doc.Zoom(zoom[event.GetId()])
+
+    def OnAbout(self, evt):
+        AboutDialog(self.view)
+
+    def __Load(self, filename):
+        self.__can_save = True
+        self.__StopThreads()
+        self.view.SetStatusText(u"Loading document...")
+        self.__busy = wx.BusyInfo(u"One moment please, "
+                                  u"opening pdf document...")
+
+        self.view.filehistory.AddFileToHistory(filename)
+
+        # Need to delay the file load a little bit
+        # for the BusyInfo get a change to repaint itself
+        wx.FutureCall(150, self.__doc.Load, filename)
+
+    def __StopThreads(self):
+        for n, t in self.__threads.items():
+            t.Stop()
+
+        running = True
+        while running:
+            running = False
+            for n, t in self.__threads.items():
+                running = running + t.IsRunning()
+            time.sleep(0.1)
+
+    def __ReadConfigurationFile(self):
+        config = GetConfig()
+        self.view.filehistory.Load(config)
+
+        dirpath = GetDataDir()
+        try:
+            f = file(os.path.join(dirpath, 'quality.pkl'), 'rb')
+            try:
+                data = pickle.load(f)
+                self.options.quality_panel.unpickle(data)
+            except:
+                self.Message(
+                      u"Error loading quality settings. "
+                      u"They will be reset to defaults. ")
+            f.close()
+        except IOError:
+            pass
+
+        try:
+            f = file(os.path.join(dirpath, 'viewers.pkl'), 'rb')
+            try:
+                data = pickle.load(f)
+                self.options.viewers_panel.unpickle(data)
+            except:
+                self.Message(
+                      u"Error loading viewers settings. "
+                      u"They will be reset to defaults. ")
+            f.close()
+        except IOError:
+            pass
+        #d = pickle.load(f)
+
+    def Message(self, message):
+        dlg = wx.MessageDialog(self.view,
+                      message,
+                      style=wx.OK, pos=wx.DefaultPosition)
+        dlg.ShowModal()
+        dlg.Destroy()
+