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
,
47 Return the standard location on this platform for application data
49 sp
= wx
.StandardPaths
.Get()
50 return sp
.GetUserDataDir()
53 if not os
.path
.exists(GetDataDir()):
54 os
.makedirs(GetDataDir())
56 config
= wx
.FileConfig(
57 localFilename
=os
.path
.join(GetDataDir(), "options"))
63 self
.__doc
= Document()
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
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
,
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)
161 def OnFilesDroped(self
, evt
):
162 dlg
= wx
.MessageDialog(self
.view
,
163 u
"You must drop only one file.",
165 style
=wx
.OK
, pos
=wx
.DefaultPosition
)
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"]
189 wx
.PD_APP_MODAL|wx
.PD_ELAPSED_TIME|
190 wx
.PD_REMAINING_TIME|wx
.PD_CAN_ABORT|
194 self
.__progress
.Destroy()
195 self
.__progress
= ProgressDialog(u
"Saving...",
196 u
"Start saving SWF 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(
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.")
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
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:
259 self
.view
.page_preview
.DisplayPage(message
.data
)
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
)
267 t
= u
"%s - %s" % (name
, filename
)
268 self
.view
.SetTitle(t
)
270 def OnFileLoaded(self
, message
):
272 self
.__progress
.Destroy()
273 self
.__progress
= None
275 self
.__can
_viewinfo
= True
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
"")
286 u
"This PDF disallows copying, cannot be converted."
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"])
300 def OnFileNotLoaded(self
, message
):
301 self
.__can
_save
= False
302 self
.__can
_viewinfo
= False
304 self
.view
.SetStatusText(u
"")
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
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
:
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(),
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
):
362 page
= self
.view
.page_list
.GetFirstSelected()
366 page
= self
.view
.page_list
.GetNextSelected(page
)
371 self
.OnMenuSave(event
, pages
)
373 def OnMenuExit(self
, event
):
374 self
.view
.SetStatusText(u
"Cleaning up...")
376 # Stop any running thread
380 self
.view
.filehistory
.Save(config
)
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()
389 f
= file(os
.path
.join(dirpath
, 'quality.pkl'), 'wb')
395 # Save viewer options
397 f
= file(os
.path
.join(dirpath
, 'viewers.pkl'), 'wb')
398 data
= self
.options
.viewers_panel
.pickle()
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
):
434 self
.__doc
.Zoom(zoom
[event
.GetId()])
436 def OnShowDocInfo(self
, event
):
437 info
= InfoDialog(self
.view
)
438 info
.info
.display(self
.__doc
)
441 def OnAbout(self
, evt
):
442 AboutDialog(self
.view
)
444 def __Load(self
, filename
):
445 self
.__can
_save
= True
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
)
453 # I dont care if this, for some reason,
454 # give some error. I just swallow it
455 os
.chdir(os
.path
.dirname(filename
))
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()
465 0 : [('bitmap', '1'), ('poly2bitmap', '0'), ('bitmapfonts', '1'),
467 1 : [('bitmap', '0'), ('poly2bitmap', '1'), ('bitmapfonts', '0'),
469 2 : [('bitmap', '0'), ('poly2bitmap', '0'), ('bitmapfonts', '0'),
471 3 : [('bitmap', '0'), ('poly2bitmap', '0'), ('bitmapfonts', '0'),
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():
484 for n
, t
in self
.__threads
.items():
485 running
= running
+ t
.IsRunning()
488 def __ReadConfigurationFile(self
):
490 self
.view
.filehistory
.Load(config
)
492 dirpath
= GetDataDir()
494 f
= file(os
.path
.join(dirpath
, 'quality.pkl'), 'rb')
497 data
= pickle
.load(f
)
498 self
.options
.quality_panel
.unpickle(data
)
501 # u"Error loading quality settings. "
502 # u"They will be reset to defaults. ")
508 f
= file(os
.path
.join(dirpath
, 'viewers.pkl'), 'rb')
511 data
= pickle
.load(f
)
512 self
.options
.viewers_panel
.unpickle(data
)
515 # u"Error loading viewers settings. "
516 # u"They will be reset to defaults. ")
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
,
530 style
=wx
.OK
, pos
=wx
.DefaultPosition
)