src/eric7/PdfViewer/PdfViewerWindow.py

branch
pdf_viewer
changeset 9697
cdaa3cc805f7
child 9698
69e183e4db6f
equal deleted inserted replaced
9696:669dabfa1319 9697:cdaa3cc805f7
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the PDF viewer main window.
8 """
9
10 import os
11 import pathlib
12
13 from PyQt6.QtCore import Qt, pyqtSignal, QSize, pyqtSlot, QPointF
14 from PyQt6.QtGui import QAction, QKeySequence
15 from PyQt6.QtPdf import QPdfDocument
16 from PyQt6.QtPdfWidgets import QPdfView
17 from PyQt6.QtWidgets import (
18 QWhatsThis, QMenu, QTabWidget, QSplitter, QSpinBox
19 )
20
21 from eric7 import Preferences
22 from eric7.EricGui import EricPixmapCache
23 from eric7.EricGui.EricAction import EricAction
24 from eric7.EricWidgets import EricFileDialog, EricMessageBox
25 from eric7.EricWidgets.EricMainWindow import EricMainWindow
26 from eric7.Globals import recentNamePdfFiles
27 from eric7.SystemUtilities import FileSystemUtilities
28
29
30 class PdfViewerWindow(EricMainWindow):
31 """
32 Class implementing the PDF viewer main window.
33
34 @signal editorClosed() emitted after the window was requested to close down
35 """
36
37 editorClosed = pyqtSignal()
38
39 maxMenuFilePathLen = 75
40
41 def __init__(self, fileName="", parent=None, fromEric=False, project=None):
42 """
43 Constructor
44
45 @param fileName name of a file to load on startup
46 @type str
47 @param parent parent widget of this window
48 @type QWidget
49 @param fromEric flag indicating whether it was called from within
50 eric
51 @type bool
52 @param project reference to the project object
53 @type Project
54 """
55 super().__init__(parent)
56 self.setObjectName("eric7_pdf_viewer")
57
58 self.__fromEric = fromEric
59 self.setWindowIcon(EricPixmapCache.getIcon("ericPdf"))
60
61 if not self.__fromEric:
62 self.setStyle(Preferences.getUI("Style"), Preferences.getUI("StyleSheet"))
63
64 self.__pdfDocument = QPdfDocument(self)
65
66 # TODO: insert central widget here
67 self.__cw = QSplitter(Qt.Orientation.Horizontal, self)
68 self.__info = QTabWidget(self)
69 self.__cw.addWidget(self.__info)
70 self.__view = QPdfView(self)
71 self.__view.setDocument(self.__pdfDocument)
72 self.__cw.addWidget(self.__view)
73 self.setCentralWidget(self.__cw)
74
75 g = Preferences.getGeometry("PdfViewerGeometry")
76 if g.isEmpty():
77 s = QSize(1000, 1000)
78 self.resize(s)
79 self.__cw.setSizes([300, 700])
80 else:
81 self.restoreGeometry(g)
82
83 self.__initActions()
84 self.__initMenus()
85 self.__initToolbars()
86 self.__createStatusBar()
87
88 state = Preferences.getPdfViewer("PdfViewerState")
89 self.restoreState(state)
90
91 self.__project = project
92 self.__lastOpenPath = ""
93
94 self.__recent = []
95 self.__loadRecent()
96
97 self.__setCurrentFile("")
98 self.__setViewerTitle("")
99 if fileName:
100 self.__loadPdfFile(fileName)
101
102 self.__checkActions()
103
104 def __initActions(self):
105 """
106 Private method to define the user interface actions.
107 """
108 # list of all actions
109 self.__actions = []
110
111 self.__initFileActions()
112 self.__initHelpActions()
113
114 def __initFileActions(self):
115 """
116 Private method to define the file related user interface actions.
117 """
118 # TODO: not yet implemented
119 self.exitAct = EricAction(
120 self.tr("Quit"),
121 EricPixmapCache.getIcon("exit"),
122 self.tr("&Quit"),
123 QKeySequence(self.tr("Ctrl+Q", "File|Quit")),
124 0,
125 self,
126 "pdfviewer_file_quit",
127 )
128 self.exitAct.setStatusTip(self.tr("Quit the PDF Viewer"))
129 self.exitAct.setWhatsThis(self.tr("""<b>Quit</b><p>Quit the PDF Viewer.</p>"""))
130 self.__actions.append(self.exitAct)
131
132 def __initHelpActions(self):
133 """
134 Private method to create the Help actions.
135 """
136 self.aboutAct = EricAction(
137 self.tr("About"), self.tr("&About"), 0, 0, self, "pdfviewer_help_about"
138 )
139 self.aboutAct.setStatusTip(self.tr("Display information about this software"))
140 self.aboutAct.setWhatsThis(
141 self.tr(
142 """<b>About</b>"""
143 """<p>Display some information about this software.</p>"""
144 )
145 )
146 self.aboutAct.triggered.connect(self.__about)
147 self.__actions.append(self.aboutAct)
148
149 self.aboutQtAct = EricAction(
150 self.tr("About Qt"),
151 self.tr("About &Qt"),
152 0,
153 0,
154 self,
155 "pdfviewer_help_about_qt",
156 )
157 self.aboutQtAct.setStatusTip(
158 self.tr("Display information about the Qt toolkit")
159 )
160 self.aboutQtAct.setWhatsThis(
161 self.tr(
162 """<b>About Qt</b>"""
163 """<p>Display some information about the Qt toolkit.</p>"""
164 )
165 )
166 self.aboutQtAct.triggered.connect(self.__aboutQt)
167 self.__actions.append(self.aboutQtAct)
168
169 self.whatsThisAct = EricAction(
170 self.tr("What's This?"),
171 EricPixmapCache.getIcon("whatsThis"),
172 self.tr("&What's This?"),
173 QKeySequence(self.tr("Shift+F1", "Help|What's This?'")),
174 0,
175 self,
176 "pdfviewer_help_whats_this",
177 )
178 self.whatsThisAct.setStatusTip(self.tr("Context sensitive help"))
179 self.whatsThisAct.setWhatsThis(
180 self.tr(
181 """<b>Display context sensitive help</b>"""
182 """<p>In What's This? mode, the mouse cursor shows an arrow"""
183 """ with a question mark, and you can click on the interface"""
184 """ elements to get a short description of what they do and"""
185 """ how to use them. In dialogs, this feature can be accessed"""
186 """ using the context help button in the titlebar.</p>"""
187 )
188 )
189 self.whatsThisAct.triggered.connect(self.__whatsThis)
190 self.__actions.append(self.whatsThisAct)
191
192 @pyqtSlot()
193 def __checkActions(self):
194 """
195 Private slot to check some actions for their enable/disable status.
196 """
197 # TODO: not yet implemented
198
199 def __initMenus(self):
200 """
201 Private method to create the menus.
202 """
203 mb = self.menuBar()
204
205 menu = mb.addMenu(self.tr("&File"))
206 menu.setTearOffEnabled(True)
207 self.__recentMenu = QMenu(self.tr("Open &Recent Files"), menu)
208
209 # TODO: not yet implemented
210
211 mb.addSeparator()
212
213 menu = mb.addMenu(self.tr("&Help"))
214 menu.addAction(self.aboutAct)
215 menu.addAction(self.aboutQtAct)
216 menu.addSeparator()
217 menu.addAction(self.whatsThisAct)
218
219 def __initToolbars(self):
220 """
221 Private method to create the toolbars.
222 """
223 # create a few widgets needed in the toolbars
224 self.__pageSelector = QSpinBox(self)
225
226 filetb = self.addToolBar(self.tr("File"))
227 filetb.setObjectName("FileToolBar")
228 # TODO: not yet implemented
229 if not self.__fromEric:
230 filetb.addAction(self.exitAct)
231
232 # TODO: not yet implemented
233
234 helptb = self.addToolBar(self.tr("Help"))
235 helptb.setObjectName("HelpToolBar")
236 helptb.addAction(self.whatsThisAct)
237
238 def __createStatusBar(self):
239 """
240 Private method to initialize the status bar.
241 """
242 self.__statusBar = self.statusBar()
243 self.__statusBar.setSizeGripEnabled(True)
244
245 # not yet implemented
246
247 def closeEvent(self, evt):
248 """
249 Protected method handling the close event.
250
251 @param evt reference to the close event
252 @type QCloseEvent
253 """
254 state = self.saveState()
255 Preferences.setPdfViewer("PdfViewerState", state)
256
257 Preferences.setGeometry("PdfViewerGeometry", self.saveGeometry())
258
259 if not self.__fromEric:
260 Preferences.syncPreferences()
261
262 self.__saveRecent()
263
264 evt.accept()
265 self.editorClosed.emit()
266
267 def __setViewerTitle(self, title):
268 """
269 Private method to set the viewer title.
270
271 @param title title to be set
272 @type str
273 """
274 if title:
275 self.setWindowTitle(self.tr("{0} - PDF Viewer").format(title))
276 else:
277 self.setWindowTitle(self.tr("PDF Viewer"))
278
279 def __getErrorString(self, err):
280 """
281 Private method to get an error string for the given error.
282
283 @param err error type
284 @type QPdfDocument.Error
285 @return string for the given error type
286 @rtype str
287 """
288 if err == QPdfDocument.Error.None_:
289 reason = ""
290 elif err == QPdfDocument.Error.DataNotYetAvailable:
291 reason = self.tr("The document is still loading.")
292 elif err == QPdfDocument.Error.FileNotFound:
293 reason = self.tr("The file does not exist.")
294 elif err == QPdfDocument.Error.InvalidFileFormat:
295 reason = self.tr("The file is not a valid PDF file.")
296 elif err == QPdfDocument.Error.IncorrectPassword:
297 reason = self.tr("The password is not correct for this file.")
298 elif err == QPdfDocument.Error.UnsupportedSecurityScheme:
299 reason = self.tr("This kind of PDF file cannot be unlocked.")
300 else:
301 reason = self.tr("Unknown type of error.")
302
303 return reason
304
305 def __loadPdfFile(self, fileName):
306 """
307 Private method to load a PDF file.
308
309 @param fileName path of the PDF file to load
310 @type str
311 """
312 # TODO: not yet implemented
313 err = self.__pdfDocument.load(fileName)
314 if err != QPdfDocument.Error.None_:
315 EricMessageBox.critical(
316 self,
317 self.tr("Load PDF File"),
318 self.tr(
319 """<p>The PDF file <b>{0}</b> could not be loaded.</p>"""
320 """<p>Reason: {1}</p>"""
321 ).format(fileName, self.__getErrorString(err)),
322 )
323 return
324
325 self.__lastOpenPath = os.path.dirname(fileName)
326 self.__setCurrentFile(fileName)
327
328 documentTitle = self.__pdfDocument.metaData(QPdfDocument.MetaDataField.Title)
329 self.__setViewerTitle(documentTitle)
330
331 self.__pageSelected(0)
332 self.__pageSelector.setMaximum(self.__pdfDocument.pageCount() - 1)
333
334 def __openPdfFile(self):
335 """
336 Private slot to open a PDF file.
337 """
338 if (
339 not self.__lastOpenPath
340 and self.__project is not None
341 and self.__project.isOpen()
342 ):
343 self.__lastOpenPath = self.__project.getProjectPath()
344
345 fileName = EricFileDialog.getOpenFileName(
346 self,
347 self.tr("Open PDF File"),
348 self.__lastOpenPath,
349 self.tr("PDF Files (*.pdf);;All Files (*)"),
350 )
351 if fileName:
352 self.__loadPdfFile(fileName)
353
354 self.__checkActions()
355
356 def __pageSelected(self, page):
357 """
358 Private method to navigate to the given page.
359
360 @param page index of the page to be shown
361 @type int
362 """
363 nav = self.__view.pageNavigator()
364 nav.jump(page, QPointF(), nav.currentZoom())
365
366 def __setCurrentFile(self, fileName):
367 """
368 Private method to register the file name of the current file.
369
370 @param fileName name of the file to register
371 @type str
372 """
373 self.__fileName = fileName
374 # insert filename into list of recently opened files
375 self.__addToRecentList(fileName)
376
377 def __strippedName(self, fullFileName):
378 """
379 Private method to return the filename part of the given path.
380
381 @param fullFileName full pathname of the given file
382 @type str
383 @return filename part
384 @rtype str
385 """
386 return pathlib.Path(fullFileName).name
387
388 def __about(self):
389 """
390 Private slot to show a little About message.
391 """
392 EricMessageBox.about(
393 self,
394 self.tr("About eric PDF Viewer"),
395 self.tr(
396 "The eric PDF Viewer is a simple component for viewing PDF files."
397 ),
398 )
399
400 def __aboutQt(self):
401 """
402 Private slot to handle the About Qt dialog.
403 """
404 EricMessageBox.aboutQt(self, "eric PDF Viewer")
405
406 def __whatsThis(self):
407 """
408 Private slot called in to enter Whats This mode.
409 """
410 QWhatsThis.enterWhatsThisMode()
411
412 def __showPreferences(self):
413 """
414 Private slot to set the preferences.
415 """
416 from eric7.Preferences.ConfigurationDialog import (
417 ConfigurationDialog,
418 ConfigurationMode,
419 )
420
421 # TODO: not yet implemented
422
423 @pyqtSlot()
424 def __showFileMenu(self):
425 """
426 Private slot to modify the file menu before being shown.
427 """
428 self.__menuRecentAct.setEnabled(len(self.__recent) > 0)
429
430 @pyqtSlot()
431 def __showRecentMenu(self):
432 """
433 Private slot to set up the recent files menu.
434 """
435 self.__loadRecent()
436
437 self.__recentMenu.clear()
438
439 for idx, rs in enumerate(self.__recent, start=1):
440 formatStr = "&{0:d}. {1}" if idx < 10 else "{0:d}. {1}"
441 act = self.__recentMenu.addAction(
442 formatStr.format(
443 idx,
444 FileSystemUtilities.compactPath(
445 rs, PdfViewerWindow.maxMenuFilePathLen
446 ),
447 )
448 )
449 act.setData(rs)
450 act.setEnabled(pathlib.Path(rs).exists())
451
452 self.__recentMenu.addSeparator()
453 self.__recentMenu.addAction(self.tr("&Clear"), self.__clearRecent)
454
455 @pyqtSlot(QAction)
456 def __openRecentPdfFile(self, act):
457 """
458 Private method to open a file from the list of recently opened files.
459
460 @param act reference to the action that triggered
461 @type QAction
462 """
463 fileName = act.data()
464 if fileName and self.__maybeSave():
465 self.__loadPdfFile(fileName)
466 self.__checkActions()
467
468 @pyqtSlot()
469 def __clearRecent(self):
470 """
471 Private method to clear the list of recently opened files.
472 """
473 self.__recent = []
474
475 def __loadRecent(self):
476 """
477 Private method to load the list of recently opened files.
478 """
479 self.__recent = []
480 Preferences.Prefs.rsettings.sync()
481 rs = Preferences.Prefs.rsettings.value(recentNamePdfFiles)
482 if rs is not None:
483 for f in Preferences.toList(rs):
484 if pathlib.Path(f).exists():
485 self.__recent.append(f)
486
487 def __saveRecent(self):
488 """
489 Private method to save the list of recently opened files.
490 """
491 Preferences.Prefs.rsettings.setValue(recentNamePdfFiles, self.__recent)
492 Preferences.Prefs.rsettings.sync()
493
494 def __addToRecentList(self, fileName):
495 """
496 Private method to add a file name to the list of recently opened files.
497
498 @param fileName name of the file to be added
499 """
500 if fileName:
501 for recent in self.__recent[:]:
502 if FileSystemUtilities.samepath(fileName, recent):
503 self.__recent.remove(recent)
504 self.__recent.insert(0, fileName)
505 maxRecent = Preferences.getPdfViewer("RecentNumber")
506 if len(self.__recent) > maxRecent:
507 self.__recent = self.__recent[:maxRecent]
508 self.__saveRecent()

eric ide

mercurial