eric7/WebBrowser/WebBrowserTabWidget.py

branch
eric7
changeset 8312
800c432b34c8
parent 8260
2161475d9639
child 8318
962bce857696
equal deleted inserted replaced
8311:4e8b98454baa 8312:800c432b34c8
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2010 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the central widget showing the web pages.
8 """
9
10 import os
11
12 from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QUrl, QFile, QFileDevice
13 from PyQt5.QtGui import QIcon, QPixmap, QPainter
14 from PyQt5.QtWidgets import (
15 QWidget, QHBoxLayout, QMenu, QToolButton, QDialog
16 )
17 from PyQt5.QtPrintSupport import QPrinter, QPrintDialog, QAbstractPrintDialog
18
19 from E5Gui.E5TabWidget import E5TabWidget
20 from E5Gui import E5MessageBox
21 from E5Gui.E5Application import e5App
22 from E5Gui.E5OverrideCursor import E5OverrideCursor
23
24 from .WebBrowserView import WebBrowserView
25 from .WebBrowserPage import WebBrowserPage
26 from .Tools import WebBrowserTools
27 from . import WebInspector
28
29 import UI.PixmapCache
30
31 import Utilities
32 import Preferences
33 import Globals
34
35
36 def isCupsAvailable():
37 """
38 Static method to test the availability of CUPS.
39
40 @return flag indicating the availability of CUPS
41 @rtype bool
42 """
43 if Globals.isMacPlatform():
44 # OS X/MacOS always have CUPS
45 return True
46 elif Globals.isLinuxPlatform():
47 testPrinter = QPrinter()
48 return testPrinter.supportsMultipleCopies()
49 else:
50 return False
51
52
53 class WebBrowserTabWidget(E5TabWidget):
54 """
55 Class implementing the central widget showing the web pages.
56
57 @signal sourceChanged(WebBrowserView, QUrl) emitted after the URL of a
58 browser has changed
59 @signal currentUrlChanged(QUrl) emitted after the URL of the current
60 browser has changed
61 @signal titleChanged(WebBrowserView, str) emitted after the title of a
62 browser has changed
63 @signal showMessage(str) emitted to show a message in the main window
64 status bar
65 @signal browserOpened(QWidget) emitted after a new browser was created
66 @signal browserClosed(QWidget) emitted after a browser was closed
67 @signal browserZoomValueChanged(int) emitted to signal a change of the
68 current browser's zoom level
69 """
70 sourceChanged = pyqtSignal(WebBrowserView, QUrl)
71 currentUrlChanged = pyqtSignal(QUrl)
72 titleChanged = pyqtSignal(WebBrowserView, str)
73 showMessage = pyqtSignal(str)
74 browserOpened = pyqtSignal(QWidget)
75 browserClosed = pyqtSignal(QWidget)
76 browserZoomValueChanged = pyqtSignal(int)
77
78 def __init__(self, parent):
79 """
80 Constructor
81
82 @param parent reference to the parent widget (QWidget)
83 """
84 super().__init__(parent, dnd=True)
85
86 from .WebBrowserTabBar import WebBrowserTabBar
87 self.__tabBar = WebBrowserTabBar(self)
88 self.setCustomTabBar(True, self.__tabBar)
89
90 self.__mainWindow = parent
91
92 self.setUsesScrollButtons(True)
93 self.setDocumentMode(True)
94 self.setElideMode(Qt.TextElideMode.ElideNone)
95
96 from .ClosedTabsManager import ClosedTabsManager
97 self.__closedTabsManager = ClosedTabsManager(self)
98 self.__closedTabsManager.closedTabAvailable.connect(
99 self.__closedTabAvailable)
100
101 from .UrlBar.StackedUrlBar import StackedUrlBar
102 self.__stackedUrlBar = StackedUrlBar(self)
103 self.__tabBar.tabMoved.connect(self.__stackedUrlBar.moveBar)
104
105 self.__tabContextMenuIndex = -1
106 self.currentChanged[int].connect(self.__currentChanged)
107 self.setTabContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
108 self.customTabContextMenuRequested.connect(self.__showContextMenu)
109
110 self.__rightCornerWidget = QWidget(self)
111 self.__rightCornerWidgetLayout = QHBoxLayout(self.__rightCornerWidget)
112 self.__rightCornerWidgetLayout.setContentsMargins(0, 0, 0, 0)
113 self.__rightCornerWidgetLayout.setSpacing(0)
114
115 self.__navigationMenu = QMenu(self)
116 self.__navigationMenu.aboutToShow.connect(self.__showNavigationMenu)
117 self.__navigationMenu.triggered.connect(self.__navigationMenuTriggered)
118
119 self.__navigationButton = QToolButton(self)
120 self.__navigationButton.setIcon(
121 UI.PixmapCache.getIcon("1downarrow"))
122 self.__navigationButton.setToolTip(
123 self.tr("Show a navigation menu"))
124 self.__navigationButton.setPopupMode(
125 QToolButton.ToolButtonPopupMode.InstantPopup)
126 self.__navigationButton.setMenu(self.__navigationMenu)
127 self.__navigationButton.setEnabled(False)
128 self.__rightCornerWidgetLayout.addWidget(self.__navigationButton)
129
130 self.__closedTabsMenu = QMenu(self)
131 self.__closedTabsMenu.aboutToShow.connect(
132 self.__aboutToShowClosedTabsMenu)
133
134 self.__closedTabsButton = QToolButton(self)
135 self.__closedTabsButton.setIcon(UI.PixmapCache.getIcon("trash"))
136 self.__closedTabsButton.setToolTip(
137 self.tr("Show a navigation menu for closed tabs"))
138 self.__closedTabsButton.setPopupMode(
139 QToolButton.ToolButtonPopupMode.InstantPopup)
140 self.__closedTabsButton.setMenu(self.__closedTabsMenu)
141 self.__closedTabsButton.setEnabled(False)
142 self.__rightCornerWidgetLayout.addWidget(self.__closedTabsButton)
143
144 self.setTabsClosable(True)
145 self.tabCloseRequested.connect(self.closeBrowserAt)
146
147 self.setCornerWidget(
148 self.__rightCornerWidget, Qt.Corner.TopRightCorner)
149
150 self.__newTabButton = QToolButton(self)
151 self.__newTabButton.setIcon(UI.PixmapCache.getIcon("plus"))
152 self.__newTabButton.setToolTip(
153 self.tr("Open a new web browser tab"))
154 self.setCornerWidget(self.__newTabButton, Qt.Corner.TopLeftCorner)
155 self.__newTabButton.clicked.connect(self.__newBrowser)
156
157 self.__initTabContextMenu()
158
159 self.__historyCompleter = None
160
161 def __initTabContextMenu(self):
162 """
163 Private method to create the tab context menu.
164 """
165 self.__tabContextMenu = QMenu(self)
166 self.tabContextNewAct = self.__tabContextMenu.addAction(
167 UI.PixmapCache.getIcon("tabNew"),
168 self.tr('New Tab'), self.newBrowser)
169 self.__tabContextMenu.addSeparator()
170 self.leftMenuAct = self.__tabContextMenu.addAction(
171 UI.PixmapCache.getIcon("1leftarrow"),
172 self.tr('Move Left'), self.__tabContextMenuMoveLeft)
173 self.rightMenuAct = self.__tabContextMenu.addAction(
174 UI.PixmapCache.getIcon("1rightarrow"),
175 self.tr('Move Right'), self.__tabContextMenuMoveRight)
176 self.__tabContextMenu.addSeparator()
177 self.tabContextCloneAct = self.__tabContextMenu.addAction(
178 self.tr("Duplicate Page"), self.__tabContextMenuClone)
179 self.__tabContextMenu.addSeparator()
180 self.tabContextCloseAct = self.__tabContextMenu.addAction(
181 UI.PixmapCache.getIcon("tabClose"),
182 self.tr('Close'), self.__tabContextMenuClose)
183 self.tabContextCloseOthersAct = self.__tabContextMenu.addAction(
184 UI.PixmapCache.getIcon("tabCloseOther"),
185 self.tr("Close Others"), self.__tabContextMenuCloseOthers)
186 self.__tabContextMenu.addAction(
187 self.tr('Close All'), self.closeAllBrowsers)
188 self.__tabContextMenu.addSeparator()
189 self.__tabContextMenu.addAction(
190 UI.PixmapCache.getIcon("printPreview"),
191 self.tr('Print Preview'), self.__tabContextMenuPrintPreview)
192 self.__tabContextMenu.addAction(
193 UI.PixmapCache.getIcon("print"),
194 self.tr('Print'), self.__tabContextMenuPrint)
195 self.__tabContextMenu.addAction(
196 UI.PixmapCache.getIcon("printPdf"),
197 self.tr('Print as PDF'), self.__tabContextMenuPrintPdf)
198 self.__tabContextMenu.addSeparator()
199 if hasattr(WebBrowserPage, "isAudioMuted"):
200 self.__audioAct = self.__tabContextMenu.addAction(
201 "", self.__tabContextMenuAudioMute)
202 self.__tabContextMenu.addSeparator()
203 else:
204 self.__audioAct = None
205 self.__tabContextMenu.addAction(
206 UI.PixmapCache.getIcon("reload"),
207 self.tr('Reload All'), self.reloadAllBrowsers)
208 self.__tabContextMenu.addSeparator()
209 self.__tabContextMenu.addAction(
210 UI.PixmapCache.getIcon("addBookmark"),
211 self.tr('Bookmark All Tabs'), self.__mainWindow.bookmarkAll)
212
213 self.__tabBackContextMenu = QMenu(self)
214 self.__tabBackContextMenu.addAction(
215 self.tr('Close All'), self.closeAllBrowsers)
216 self.__tabBackContextMenu.addAction(
217 UI.PixmapCache.getIcon("reload"),
218 self.tr('Reload All'), self.reloadAllBrowsers)
219 self.__tabBackContextMenu.addAction(
220 UI.PixmapCache.getIcon("addBookmark"),
221 self.tr('Bookmark All Tabs'), self.__mainWindow.bookmarkAll)
222 self.__tabBackContextMenu.addSeparator()
223 self.__restoreClosedTabAct = self.__tabBackContextMenu.addAction(
224 UI.PixmapCache.getIcon("trash"),
225 self.tr('Restore Closed Tab'))
226 self.__restoreClosedTabAct.setEnabled(False)
227 self.__restoreClosedTabAct.setData(0)
228 self.__restoreClosedTabAct.triggered.connect(
229 lambda: self.restoreClosedTab(self.__restoreClosedTabAct))
230
231 def __showContextMenu(self, coord, index):
232 """
233 Private slot to show the tab context menu.
234
235 @param coord the position of the mouse pointer (QPoint)
236 @param index index of the tab the menu is requested for (integer)
237 """
238 coord = self.mapToGlobal(coord)
239 if index == -1:
240 self.__tabBackContextMenu.popup(coord)
241 else:
242 self.__tabContextMenuIndex = index
243 self.leftMenuAct.setEnabled(index > 0)
244 self.rightMenuAct.setEnabled(index < self.count() - 1)
245
246 self.tabContextCloseOthersAct.setEnabled(self.count() > 1)
247
248 if self.__audioAct is not None:
249 if (
250 self.widget(self.__tabContextMenuIndex).page()
251 .isAudioMuted()
252 ):
253 self.__audioAct.setText(self.tr("Unmute Tab"))
254 self.__audioAct.setIcon(
255 UI.PixmapCache.getIcon("audioVolumeHigh"))
256 else:
257 self.__audioAct.setText(self.tr("Mute Tab"))
258 self.__audioAct.setIcon(
259 UI.PixmapCache.getIcon("audioVolumeMuted"))
260
261 self.__tabContextMenu.popup(coord)
262
263 def __tabContextMenuMoveLeft(self):
264 """
265 Private method to move a tab one position to the left.
266 """
267 self.moveTab(self.__tabContextMenuIndex,
268 self.__tabContextMenuIndex - 1)
269
270 def __tabContextMenuMoveRight(self):
271 """
272 Private method to move a tab one position to the right.
273 """
274 self.moveTab(self.__tabContextMenuIndex,
275 self.__tabContextMenuIndex + 1)
276
277 def __tabContextMenuClone(self):
278 """
279 Private method to clone the selected tab.
280 """
281 idx = self.__tabContextMenuIndex
282 if idx < 0:
283 idx = self.currentIndex()
284 if idx < 0 or idx > self.count():
285 return
286
287 url = self.widget(idx).url()
288 self.newBrowser(url)
289
290 def __tabContextMenuClose(self):
291 """
292 Private method to close the selected tab.
293 """
294 self.closeBrowserAt(self.__tabContextMenuIndex)
295
296 def __tabContextMenuCloseOthers(self):
297 """
298 Private slot to close all other tabs.
299 """
300 index = self.__tabContextMenuIndex
301 for i in (
302 list(range(self.count() - 1, index, -1)) +
303 list(range(index - 1, -1, -1))
304 ):
305 self.closeBrowserAt(i)
306
307 def __tabContextMenuPrint(self):
308 """
309 Private method to print the selected tab.
310 """
311 browser = self.widget(self.__tabContextMenuIndex)
312 self.printBrowser(browser)
313
314 def __tabContextMenuPrintPdf(self):
315 """
316 Private method to print the selected tab as PDF.
317 """
318 browser = self.widget(self.__tabContextMenuIndex)
319 self.printBrowserPdf(browser)
320
321 def __tabContextMenuPrintPreview(self):
322 """
323 Private method to show a print preview of the selected tab.
324 """
325 browser = self.widget(self.__tabContextMenuIndex)
326 self.printPreviewBrowser(browser)
327
328 def __tabContextMenuAudioMute(self):
329 """
330 Private method to mute or unmute the selected tab.
331 """
332 page = self.widget(self.__tabContextMenuIndex).page()
333 muted = page.isAudioMuted()
334 page.setAudioMuted(not muted)
335
336 @pyqtSlot(bool)
337 def __recentlyAudibleChanged(self, recentlyAudible, page):
338 """
339 Private slot to react on the audible state of a page.
340
341 @param recentlyAudible flag indicating the new audible state
342 @type bool
343 @param page reference to the web page
344 @type WebBrowserPage
345 """
346 browser = page.view()
347 if browser is None:
348 return
349
350 index = self.indexOf(browser)
351 icon = page.icon()
352
353 if page.isAudioMuted() or (
354 not page.isAudioMuted() and recentlyAudible):
355 pix = QPixmap(32, 32)
356 pix.fill(Qt.GlobalColor.transparent)
357 painter = QPainter(pix)
358 icon.paint(painter, 0, 0, 22, 22)
359 if page.isAudioMuted():
360 audioIcon = UI.PixmapCache.getIcon("audioMuted")
361 else:
362 audioIcon = UI.PixmapCache.getIcon("audioPlaying")
363 audioIcon.paint(painter, 13, 13, 18, 18)
364 painter.end()
365 self.setTabIcon(index, QIcon(pix))
366 else:
367 self.setTabIcon(index, icon)
368
369 @pyqtSlot()
370 def __newBrowser(self):
371 """
372 Private slot to open a new browser tab.
373 """
374 self.newBrowser()
375
376 def newBrowser(self, link=None, position=-1, background=False,
377 restoreSession=False):
378 """
379 Public method to create a new web browser tab.
380
381 @param link link to be shown
382 @type str or QUrl
383 @param position position to create the new tab at or -1 to add it
384 to the end
385 @type int
386 @param background flag indicating to open the tab in the
387 background
388 @type bool
389 @param restoreSession flag indicating a restore session action
390 @type bool
391 @return reference to the new browser
392 @rtype WebBrowserView
393 """
394 if link is None:
395 linkName = ""
396 elif isinstance(link, QUrl):
397 linkName = link.toString()
398 else:
399 linkName = link
400
401 from .UrlBar.UrlBar import UrlBar
402 urlbar = UrlBar(self.__mainWindow, self)
403 if self.__historyCompleter is None:
404 import WebBrowser.WebBrowserWindow
405 from .History.HistoryCompleter import (
406 HistoryCompletionModel, HistoryCompleter
407 )
408 self.__historyCompletionModel = HistoryCompletionModel(self)
409 self.__historyCompletionModel.setSourceModel(
410 WebBrowser.WebBrowserWindow.WebBrowserWindow.historyManager()
411 .historyFilterModel())
412 self.__historyCompleter = HistoryCompleter(
413 self.__historyCompletionModel, self)
414 self.__historyCompleter.activated[str].connect(self.__pathSelected)
415 urlbar.setCompleter(self.__historyCompleter)
416 urlbar.returnPressed.connect(
417 lambda: self.__lineEditReturnPressed(urlbar))
418 if position == -1:
419 self.__stackedUrlBar.addWidget(urlbar)
420 else:
421 self.__stackedUrlBar.insertWidget(position, urlbar)
422
423 browser = WebBrowserView(self.__mainWindow, self)
424 urlbar.setBrowser(browser)
425
426 browser.sourceChanged.connect(
427 lambda url: self.__sourceChanged(url, browser))
428 browser.titleChanged.connect(
429 lambda title: self.__titleChanged(title, browser))
430 browser.highlighted.connect(self.showMessage)
431 browser.backwardAvailable.connect(
432 self.__mainWindow.setBackwardAvailable)
433 browser.forwardAvailable.connect(self.__mainWindow.setForwardAvailable)
434 browser.loadProgress.connect(
435 lambda progress: self.__loadProgress(progress, browser))
436 browser.loadFinished.connect(
437 lambda ok: self.__loadFinished(ok, browser))
438 browser.faviconChanged.connect(
439 lambda: self.__iconChanged(browser))
440 browser.search.connect(self.newBrowser)
441 browser.page().windowCloseRequested.connect(
442 lambda: self.__windowCloseRequested(browser.page()))
443 browser.zoomValueChanged.connect(self.browserZoomValueChanged)
444 if hasattr(WebBrowserPage, "recentlyAudibleChanged"):
445 browser.page().recentlyAudibleChanged.connect(
446 lambda audible: self.__recentlyAudibleChanged(
447 audible, browser.page()))
448 browser.page().printRequested.connect(
449 lambda: self.printBrowser(browser))
450 browser.showMessage.connect(self.showMessage)
451
452 index = (
453 self.addTab(browser, self.tr("..."))
454 if position == -1 else
455 self.insertTab(position, browser, self.tr("..."))
456 )
457 if not background:
458 self.setCurrentIndex(index)
459
460 self.__mainWindow.closeAct.setEnabled(True)
461 self.__mainWindow.closeAllAct.setEnabled(True)
462 self.__navigationButton.setEnabled(True)
463
464 if not restoreSession and not linkName:
465 if Preferences.getWebBrowser("NewTabBehavior") == 0:
466 linkName = "about:blank"
467 elif Preferences.getWebBrowser("NewTabBehavior") == 1:
468 linkName = Preferences.getWebBrowser("HomePage")
469 elif Preferences.getWebBrowser("NewTabBehavior") == 2:
470 linkName = "eric:speeddial"
471
472 if linkName == "eric:blank":
473 linkName = "about:blank"
474
475 if linkName:
476 browser.setSource(QUrl(linkName))
477 if not browser.documentTitle():
478 self.setTabText(
479 index,
480 self.__elide(linkName, Qt.TextElideMode.ElideMiddle)
481 )
482 self.setTabToolTip(index, linkName)
483 else:
484 self.setTabText(
485 index,
486 self.__elide(browser.documentTitle().replace("&", "&&")))
487 self.setTabToolTip(index, browser.documentTitle())
488
489 self.browserOpened.emit(browser)
490
491 return browser
492
493 def newBrowserAfter(self, browser, link=None, background=False):
494 """
495 Public method to create a new web browser tab after a given one.
496
497 @param browser reference to the browser to add after (WebBrowserView)
498 @param link link to be shown (string or QUrl)
499 @param background flag indicating to open the tab in the
500 background (bool)
501 @return reference to the new browser
502 @rtype WebBrowserView
503 """
504 position = self.indexOf(browser) + 1 if browser else -1
505 return self.newBrowser(link, position, background)
506
507 def __showNavigationMenu(self):
508 """
509 Private slot to show the navigation button menu.
510 """
511 self.__navigationMenu.clear()
512 for index in range(self.count()):
513 act = self.__navigationMenu.addAction(
514 self.tabIcon(index), self.tabText(index))
515 act.setData(index)
516
517 def __navigationMenuTriggered(self, act):
518 """
519 Private slot called to handle the navigation button menu selection.
520
521 @param act reference to the selected action (QAction)
522 """
523 index = act.data()
524 if index is not None:
525 self.setCurrentIndex(index)
526
527 def __windowCloseRequested(self, page):
528 """
529 Private slot to handle the windowCloseRequested signal of a browser.
530
531 @param page reference to the web page
532 @type WebBrowserPage
533 """
534 browser = page.view()
535 if browser is None:
536 return
537
538 index = self.indexOf(browser)
539 self.closeBrowserAt(index)
540
541 def reloadAllBrowsers(self):
542 """
543 Public slot to reload all browsers.
544 """
545 for index in range(self.count()):
546 browser = self.widget(index)
547 browser and browser.reload()
548
549 @pyqtSlot()
550 def closeBrowser(self):
551 """
552 Public slot called to handle the close action.
553 """
554 self.closeBrowserAt(self.currentIndex())
555
556 def closeAllBrowsers(self, shutdown=False):
557 """
558 Public slot called to handle the close all action.
559
560 @param shutdown flag indicating a shutdown action
561 @type bool
562 """
563 for index in range(self.count() - 1, -1, -1):
564 self.closeBrowserAt(index, shutdown=shutdown)
565
566 def closeBrowserView(self, browser):
567 """
568 Public method to close the given browser.
569
570 @param browser reference to the web browser view to be closed
571 @type WebBrowserView
572 """
573 index = self.indexOf(browser)
574 self.closeBrowserAt(index)
575
576 def closeBrowserAt(self, index, shutdown=False):
577 """
578 Public slot to close a browser based on its index.
579
580 @param index index of browser to close
581 @type int
582 @param shutdown flag indicating a shutdown action
583 @type bool
584 """
585 browser = self.widget(index)
586 if browser is None:
587 return
588
589 urlbar = self.__stackedUrlBar.widget(index)
590 self.__stackedUrlBar.removeWidget(urlbar)
591 urlbar.deleteLater()
592 del urlbar
593
594 self.__closedTabsManager.recordBrowser(browser, index)
595
596 browser.closeWebInspector()
597 WebInspector.unregisterView(browser)
598 self.removeTab(index)
599 self.browserClosed.emit(browser)
600 browser.deleteLater()
601 del browser
602
603 if self.count() == 0 and not shutdown:
604 self.newBrowser()
605 else:
606 self.currentChanged[int].emit(self.currentIndex())
607
608 def currentBrowser(self):
609 """
610 Public method to get a reference to the current browser.
611
612 @return reference to the current browser (WebBrowserView)
613 """
614 return self.currentWidget()
615
616 def browserAt(self, index):
617 """
618 Public method to get a reference to the browser with the given index.
619
620 @param index index of the browser to get (integer)
621 @return reference to the indexed browser (WebBrowserView)
622 """
623 return self.widget(index)
624
625 def browsers(self):
626 """
627 Public method to get a list of references to all browsers.
628
629 @return list of references to browsers (list of WebBrowserView)
630 """
631 li = []
632 for index in range(self.count()):
633 li.append(self.widget(index))
634 return li
635
636 @pyqtSlot()
637 def printBrowser(self, browser=None):
638 """
639 Public slot called to print the displayed page.
640
641 @param browser reference to the browser to be printed (WebBrowserView)
642 """
643 if browser is None:
644 browser = self.currentBrowser()
645
646 printer = QPrinter(mode=QPrinter.PrinterMode.HighResolution)
647 if Preferences.getPrinter("ColorMode"):
648 printer.setColorMode(QPrinter.ColorMode.Color)
649 else:
650 printer.setColorMode(QPrinter.ColorMode.GrayScale)
651 if Preferences.getPrinter("FirstPageFirst"):
652 printer.setPageOrder(QPrinter.PageOrder.FirstPageFirst)
653 else:
654 printer.setPageOrder(QPrinter.PageOrder.LastPageFirst)
655 printer.setPageMargins(
656 Preferences.getPrinter("LeftMargin") * 10,
657 Preferences.getPrinter("TopMargin") * 10,
658 Preferences.getPrinter("RightMargin") * 10,
659 Preferences.getPrinter("BottomMargin") * 10,
660 QPrinter.Unit.Millimeter
661 )
662 printerName = Preferences.getPrinter("PrinterName")
663 if printerName:
664 printer.setPrinterName(printerName)
665 printer.setResolution(Preferences.getPrinter("Resolution"))
666 documentName = WebBrowserTools.getFileNameFromUrl(browser.url())
667 printer.setDocName(documentName)
668
669 printDialog = QPrintDialog(printer, self)
670 printDialog.setOptions(
671 QAbstractPrintDialog.PrintDialogOption.PrintToFile |
672 QAbstractPrintDialog.PrintDialogOption.PrintShowPageSize
673 )
674 if not Globals.isWindowsPlatform():
675 if isCupsAvailable():
676 printDialog.setOption(
677 QAbstractPrintDialog.PrintDialogOption.PrintCollateCopies)
678 printDialog.setOption(
679 QAbstractPrintDialog.PrintDialogOption.PrintPageRange)
680 if printDialog.exec() == QDialog.DialogCode.Accepted:
681 browser.page().execPrintPage(printer, 10 * 1000)
682
683 @pyqtSlot()
684 def printBrowserPdf(self, browser=None):
685 """
686 Public slot called to print the displayed page to PDF.
687
688 @param browser reference to the browser to be printed (HelpBrowser)
689 """
690 if browser is None:
691 browser = self.currentBrowser()
692
693 name = WebBrowserTools.getFileNameFromUrl(browser.url())
694 if name:
695 name = name.rsplit('.', 1)[0]
696 name += '.pdf'
697 if hasattr(browser.page(), "printToPdf"):
698 from .Tools.PrintToPdfDialog import PrintToPdfDialog
699 if not name:
700 name = "printout.pdf"
701 dlg = PrintToPdfDialog(name, self)
702 if dlg.exec() == QDialog.DialogCode.Accepted:
703 filePath, pageLayout = dlg.getData()
704 if filePath:
705 if os.path.exists(filePath):
706 res = E5MessageBox.warning(
707 self,
708 self.tr("Print to PDF"),
709 self.tr("""<p>The file <b>{0}</b> exists"""
710 """ already. Shall it be"""
711 """ overwritten?</p>""").format(filePath),
712 E5MessageBox.StandardButtons(
713 E5MessageBox.No |
714 E5MessageBox.Yes),
715 E5MessageBox.No)
716 if res == E5MessageBox.No:
717 return
718 browser.page().printToPdf(
719 lambda pdf: self.__pdfGeneratedForSave(filePath, pdf),
720 pageLayout)
721 elif Globals.isLinuxPlatform():
722 printer = QPrinter(mode=QPrinter.PrinterMode.HighResolution)
723 if Preferences.getPrinter("ColorMode"):
724 printer.setColorMode(QPrinter.ColorMode.Color)
725 else:
726 printer.setColorMode(QPrinter.ColorMode.GrayScale)
727 printerName = Preferences.getPrinter("PrinterName")
728 if printerName:
729 printer.setPrinterName(printerName)
730 printer.setOutputFormat(QPrinter.OutputFormat.PdfFormat)
731 if name:
732 printer.setOutputFileName(name)
733 printer.setResolution(Preferences.getPrinter("Resolution"))
734
735 printDialog = QPrintDialog(printer, self)
736 if printDialog.exec() == QDialog.DialogCode.Accepted:
737 browser.render(printer)
738
739 def __pdfGeneratedForSave(self, filePath, pdfData):
740 """
741 Private slot to save the generated PDF data to a file.
742
743 @param filePath path to save the PDF to
744 @type str
745 @param pdfData generated PDF document
746 @type QByteArray
747 """
748 if pdfData.size() == 0:
749 return
750
751 pdfFile = QFile(filePath)
752 if pdfFile.open(QFile.WriteOnly):
753 pdfFile.write(pdfData)
754 pdfFile.close()
755 if pdfFile.error() != QFileDevice.FileError.NoError:
756 E5MessageBox.critical(
757 self,
758 self.tr("Print to PDF"),
759 self.tr("""<p>The PDF could not be written to file <b>{0}"""
760 """</b>.</p><p><b>Error:</b> {1}</p>""").format(
761 filePath, pdfFile.errorString()),
762 E5MessageBox.StandardButtons(
763 E5MessageBox.Ok))
764
765 @pyqtSlot()
766 def printPreviewBrowser(self, browser=None):
767 """
768 Public slot called to show a print preview of the displayed file.
769
770 @param browser reference to the browser to be printed (WebBrowserView)
771 """
772 from PyQt5.QtPrintSupport import QPrintPreviewDialog
773
774 if browser is None:
775 browser = self.currentBrowser()
776
777 printer = QPrinter(mode=QPrinter.PrinterMode.HighResolution)
778 if Preferences.getPrinter("ColorMode"):
779 printer.setColorMode(QPrinter.ColorMode.Color)
780 else:
781 printer.setColorMode(QPrinter.ColorMode.GrayScale)
782 if Preferences.getPrinter("FirstPageFirst"):
783 printer.setPageOrder(QPrinter.PageOrder.FirstPageFirst)
784 else:
785 printer.setPageOrder(QPrinter.PageOrder.LastPageFirst)
786 printer.setPageMargins(
787 Preferences.getPrinter("LeftMargin") * 10,
788 Preferences.getPrinter("TopMargin") * 10,
789 Preferences.getPrinter("RightMargin") * 10,
790 Preferences.getPrinter("BottomMargin") * 10,
791 QPrinter.Unit.Millimeter
792 )
793 printerName = Preferences.getPrinter("PrinterName")
794 if printerName:
795 printer.setPrinterName(printerName)
796 printer.setResolution(Preferences.getPrinter("Resolution"))
797
798 preview = QPrintPreviewDialog(printer, self)
799 preview.resize(800, 750)
800 preview.paintRequested.connect(
801 lambda p: self.__printPreviewRequested(p, browser))
802 preview.exec()
803
804 def __printPreviewRequested(self, printer, browser):
805 """
806 Private slot to generate the print preview.
807
808 @param printer reference to the printer object
809 @type QPrinter
810 @param browser reference to the browser to be printed
811 @type WebBrowserView
812 """
813 with E5OverrideCursor():
814 browser.page().execPrintPage(printer, 10 * 1000)
815
816 def __sourceChanged(self, url, browser):
817 """
818 Private slot to handle a change of a browsers source.
819
820 @param url URL of the new site
821 @type QUrl
822 @param browser reference to the web browser
823 @type WebBrowserView
824 """
825 self.sourceChanged.emit(browser, url)
826
827 if browser == self.currentBrowser():
828 self.currentUrlChanged.emit(url)
829
830 def __titleChanged(self, title, browser):
831 """
832 Private slot to handle a change of a browsers title.
833
834 @param title new title
835 @type str
836 @param browser reference to the web browser
837 @type WebBrowserView
838 """
839 index = self.indexOf(browser)
840 if title == "":
841 title = browser.url().toString()
842
843 self.setTabText(index, self.__elide(title.replace("&", "&&")))
844 self.setTabToolTip(index, title)
845
846 self.titleChanged.emit(browser, title)
847
848 def __elide(self, txt, mode=Qt.TextElideMode.ElideRight, length=40):
849 """
850 Private method to elide some text.
851
852 @param txt text to be elided (string)
853 @param mode elide mode (Qt.TextElideMode)
854 @param length amount of characters to be used (integer)
855 @return the elided text (string)
856 """
857 if mode == Qt.TextElideMode.ElideNone or len(txt) < length:
858 return txt
859 elif mode == Qt.TextElideMode.ElideLeft:
860 return "...{0}".format(txt[-length:])
861 elif mode == Qt.TextElideMode.ElideMiddle:
862 return "{0}...{1}".format(txt[:length // 2], txt[-(length // 2):])
863 elif mode == Qt.TextElideMode.ElideRight:
864 return "{0}...".format(txt[:length])
865 else:
866 # just in case
867 return txt
868
869 def preferencesChanged(self):
870 """
871 Public slot to handle a change of preferences.
872 """
873 for browser in self.browsers():
874 browser.preferencesChanged()
875
876 for urlbar in self.__stackedUrlBar.urlBars():
877 urlbar.preferencesChanged()
878
879 def __loadFinished(self, ok, browser):
880 """
881 Private method to handle the loadFinished signal.
882
883 @param ok flag indicating the result
884 @type bool
885 @param browser reference to the web browser
886 @type WebBrowserView
887 """
888 if ok:
889 self.showMessage.emit(self.tr("Finished loading"))
890 else:
891 self.showMessage.emit(self.tr("Failed to load"))
892
893 def __loadProgress(self, progress, browser):
894 """
895 Private method to handle the loadProgress signal.
896
897 Note: This works around wegengine not sending a loadFinished
898 signal for navigation on the same page.
899
900 @param progress load progress in percent
901 @type int
902 @param browser reference to the web browser
903 @type WebBrowserView
904 """
905 index = self.indexOf(browser)
906 if progress == 0:
907 # page loading has started
908 anim = self.animationLabel(index, "loadingAnimation", 40)
909 if not anim:
910 self.setTabIcon(index, UI.PixmapCache.getIcon("loading"))
911 else:
912 self.setTabIcon(index, QIcon())
913 self.setTabText(index, self.tr("Loading..."))
914 self.setTabToolTip(index, self.tr("Loading..."))
915 self.showMessage.emit(self.tr("Loading..."))
916
917 self.__mainWindow.setLoadingActions(True)
918 elif progress == 100:
919 import WebBrowser.WebBrowserWindow
920 self.resetAnimation(index)
921 self.setTabIcon(
922 index, WebBrowser.WebBrowserWindow.WebBrowserWindow.icon(
923 browser.url()))
924 self.showMessage.emit(self.tr("Finished loading"))
925
926 self.__mainWindow.setLoadingActions(False)
927
928 def __iconChanged(self, browser):
929 """
930 Private slot to handle a change of the web site icon.
931
932 @param browser reference to the web browser
933 @type WebBrowserView
934 """
935 self.setTabIcon(
936 self.indexOf(browser),
937 browser.icon())
938 self.__mainWindow.bookmarksManager().faviconChanged(browser.url())
939
940 def getSourceFileList(self):
941 """
942 Public method to get a list of all opened Qt help files.
943
944 @return dictionary with tab id as key and host/namespace as value
945 """
946 sourceList = {}
947 for i in range(self.count()):
948 browser = self.widget(i)
949 if (
950 browser is not None and
951 browser.source().isValid()
952 ):
953 sourceList[i] = browser.source().host()
954
955 return sourceList
956
957 def shallShutDown(self):
958 """
959 Public method to check, if the application should be shut down.
960
961 @return flag indicating a shut down (boolean)
962 """
963 if self.count() > 1 and Preferences.getWebBrowser(
964 "WarnOnMultipleClose"):
965 mb = E5MessageBox.E5MessageBox(
966 E5MessageBox.Information,
967 self.tr("Are you sure you want to close the window?"),
968 self.tr("""Are you sure you want to close the window?\n"""
969 """You have %n tab(s) open.""", "", self.count()),
970 modal=True,
971 parent=self)
972 quitButton = mb.addButton(
973 self.tr("&Quit"), E5MessageBox.AcceptRole)
974 quitButton.setIcon(UI.PixmapCache.getIcon("exit"))
975 closeTabButton = mb.addButton(
976 self.tr("C&lose Current Tab"), E5MessageBox.AcceptRole)
977 closeTabButton.setIcon(UI.PixmapCache.getIcon("tabClose"))
978 mb.addButton(E5MessageBox.Cancel)
979 mb.exec()
980 if mb.clickedButton() == quitButton:
981 return True
982 else:
983 if mb.clickedButton() == closeTabButton:
984 self.closeBrowser()
985 return False
986
987 return True
988
989 def stackedUrlBar(self):
990 """
991 Public method to get a reference to the stacked url bar.
992
993 @return reference to the stacked url bar (StackedUrlBar)
994 """
995 return self.__stackedUrlBar
996
997 def currentUrlBar(self):
998 """
999 Public method to get a reference to the current url bar.
1000
1001 @return reference to the current url bar (UrlBar)
1002 """
1003 return self.__stackedUrlBar.currentWidget()
1004
1005 def urlBarForView(self, view):
1006 """
1007 Public method to get a reference to the UrlBar associated with the
1008 given view.
1009
1010 @param view reference to the view to get the urlbar for
1011 @type WebBrowserView
1012 @return reference to the associated urlbar
1013 @rtype UrlBar
1014 """
1015 for urlbar in self.__stackedUrlBar.urlBars():
1016 if urlbar.browser() is view:
1017 return urlbar
1018
1019 return None
1020
1021 def __lineEditReturnPressed(self, edit):
1022 """
1023 Private slot to handle the entering of an URL.
1024
1025 @param edit reference to the line edit
1026 @type UrlBar
1027 """
1028 url = self.__guessUrlFromPath(edit.text())
1029 if e5App().keyboardModifiers() == Qt.KeyboardModifier.AltModifier:
1030 self.newBrowser(url)
1031 else:
1032 self.currentBrowser().setSource(url)
1033 self.currentBrowser().setFocus()
1034
1035 def __pathSelected(self, path):
1036 """
1037 Private slot called when a URL is selected from the completer.
1038
1039 @param path path to be shown (string)
1040 """
1041 url = self.__guessUrlFromPath(path)
1042 self.currentBrowser().setSource(url)
1043
1044 def __guessUrlFromPath(self, path):
1045 """
1046 Private method to guess an URL given a path string.
1047
1048 @param path path string to guess an URL for (string)
1049 @return guessed URL (QUrl)
1050 """
1051 manager = self.__mainWindow.openSearchManager()
1052 path = Utilities.fromNativeSeparators(path)
1053 url = manager.convertKeywordSearchToUrl(path)
1054 if url.isValid():
1055 return url
1056
1057 try:
1058 url = QUrl.fromUserInput(path)
1059 except AttributeError:
1060 url = QUrl(path)
1061
1062 if (
1063 url.scheme() == "about" and
1064 url.path() == "home"
1065 ):
1066 url = QUrl("eric:home")
1067
1068 if url.scheme() in ["s", "search"]:
1069 url = manager.currentEngine().searchUrl(url.path().strip())
1070
1071 if (
1072 url.scheme() != "" and
1073 (url.host() != "" or url.path() != "")
1074 ):
1075 return url
1076
1077 urlString = Preferences.getWebBrowser("DefaultScheme") + path.strip()
1078 url = QUrl.fromEncoded(urlString.encode("utf-8"),
1079 QUrl.ParsingMode.TolerantMode)
1080
1081 return url
1082
1083 def __currentChanged(self, index):
1084 """
1085 Private slot to handle an index change.
1086
1087 @param index new index (integer)
1088 """
1089 self.__stackedUrlBar.setCurrentIndex(index)
1090
1091 browser = self.browserAt(index)
1092 if browser is not None:
1093 if browser.url() == "" and browser.hasFocus():
1094 self.__stackedUrlBar.currentWidget.setFocus()
1095 elif browser.url() != "":
1096 browser.setFocus()
1097
1098 def restoreClosedTab(self, act):
1099 """
1100 Public slot to restore the most recently closed tab.
1101
1102 @param act reference to the action that triggered
1103 @type QAction
1104 """
1105 if not self.canRestoreClosedTab():
1106 return
1107
1108 tab = self.__closedTabsManager.getClosedTabAt(act.data())
1109
1110 self.newBrowser(tab.url.toString(), position=tab.position)
1111
1112 def canRestoreClosedTab(self):
1113 """
1114 Public method to check, if closed tabs can be restored.
1115
1116 @return flag indicating that closed tabs can be restored (boolean)
1117 """
1118 return self.__closedTabsManager.isClosedTabAvailable()
1119
1120 def restoreAllClosedTabs(self):
1121 """
1122 Public slot to restore all closed tabs.
1123 """
1124 if not self.canRestoreClosedTab():
1125 return
1126
1127 for tab in self.__closedTabsManager.allClosedTabs():
1128 self.newBrowser(tab.url.toString(), position=tab.position)
1129 self.__closedTabsManager.clearList()
1130
1131 def clearClosedTabsList(self):
1132 """
1133 Public slot to clear the list of closed tabs.
1134 """
1135 self.__closedTabsManager.clearList()
1136
1137 def __aboutToShowClosedTabsMenu(self):
1138 """
1139 Private slot to populate the closed tabs menu.
1140 """
1141 fm = self.__closedTabsMenu.fontMetrics()
1142 try:
1143 maxWidth = fm.horizontalAdvance('m') * 40
1144 except AttributeError:
1145 maxWidth = fm.width('m') * 40
1146
1147 self.__closedTabsMenu.clear()
1148 for index, tab in enumerate(self.__closedTabsManager.allClosedTabs()):
1149 title = fm.elidedText(tab.title, Qt.TextElideMode.ElideRight,
1150 maxWidth)
1151 act = self.__closedTabsMenu.addAction(
1152 self.__mainWindow.icon(tab.url), title)
1153 act.setData(index)
1154 act.triggered.connect(lambda: self.restoreClosedTab(act))
1155 self.__closedTabsMenu.addSeparator()
1156 self.__closedTabsMenu.addAction(
1157 self.tr("Restore All Closed Tabs"), self.restoreAllClosedTabs)
1158 self.__closedTabsMenu.addAction(
1159 self.tr("Clear List"), self.clearClosedTabsList)
1160
1161 def closedTabsManager(self):
1162 """
1163 Public slot to get a reference to the closed tabs manager.
1164
1165 @return reference to the closed tabs manager (ClosedTabsManager)
1166 """
1167 return self.__closedTabsManager
1168
1169 def __closedTabAvailable(self, avail):
1170 """
1171 Private slot to handle changes of the availability of closed tabs.
1172
1173 @param avail flag indicating the availability of closed tabs (boolean)
1174 """
1175 self.__closedTabsButton.setEnabled(avail)
1176 self.__restoreClosedTabAct.setEnabled(avail)
1177
1178 ####################################################
1179 ## Methods below implement session related functions
1180 ####################################################
1181
1182 def getSessionData(self):
1183 """
1184 Public method to populate the session data.
1185
1186 @return dictionary containing the session data
1187 @rtype dict
1188 """
1189 sessionData = {}
1190
1191 # 1. current index
1192 sessionData["CurrentTabIndex"] = self.currentIndex()
1193
1194 # 2. tab data
1195 sessionData["Tabs"] = []
1196 for index in range(self.count()):
1197 browser = self.widget(index)
1198 data = browser.getSessionData()
1199 sessionData["Tabs"].append(data)
1200
1201 return sessionData
1202
1203 def loadFromSessionData(self, sessionData):
1204 """
1205 Public method to load the session data.
1206
1207 @param sessionData dictionary containing the session data as
1208 generated by getSessionData()
1209 @type dict
1210 """
1211 tabCount = self.count()
1212
1213 # 1. load tab data
1214 if "Tabs" in sessionData:
1215 loadTabOnActivate = Preferences.getWebBrowser(
1216 "LoadTabOnActivation")
1217 for data in sessionData["Tabs"]:
1218 browser = self.newBrowser(restoreSession=True)
1219 if loadTabOnActivate:
1220 browser.storeSessionData(data)
1221 title, urlStr, icon = browser.extractSessionMetaData(data)
1222 index = self.indexOf(browser)
1223 self.setTabText(index, title)
1224 self.setTabIcon(index, icon)
1225 else:
1226 browser.loadFromSessionData(data)
1227
1228 # 2. set tab index
1229 if (
1230 "CurrentTabIndex" in sessionData and
1231 sessionData["CurrentTabIndex"] >= 0
1232 ):
1233 index = tabCount + sessionData["CurrentTabIndex"]
1234 self.setCurrentIndex(index)
1235 self.browserAt(index).activateSession()

eric ide

mercurial