src/eric7/WebBrowser/WebBrowserTabWidget.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 9162
8b75b1668583
child 9221
bf71ee032bb4
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2010 - 2022 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 PyQt6.QtCore import pyqtSignal, pyqtSlot, Qt, QUrl, QMarginsF
13 from PyQt6.QtGui import QIcon, QPixmap, QPainter, QPageLayout
14 from PyQt6.QtWidgets import (
15 QWidget, QHBoxLayout, QMenu, QToolButton, QDialog
16 )
17 from PyQt6.QtPrintSupport import QPrinter, QPrintDialog, QAbstractPrintDialog
18
19 from EricWidgets.EricTabWidget import EricTabWidget
20 from EricWidgets import EricMessageBox
21 from EricWidgets.EricApplication import ericApp
22 from EricGui.EricOverrideCursor import EricOverrideCursor
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(EricTabWidget):
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(QMarginsF(
656 Preferences.getPrinter("LeftMargin") * 10,
657 Preferences.getPrinter("TopMargin") * 10,
658 Preferences.getPrinter("RightMargin") * 10,
659 Preferences.getPrinter("BottomMargin") * 10),
660 QPageLayout.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 = EricMessageBox.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 EricMessageBox.No | EricMessageBox.Yes,
713 EricMessageBox.No)
714 if res == EricMessageBox.No:
715 return
716 browser.page().printToPdf(
717 lambda pdf: self.__pdfGeneratedForSave(filePath, pdf),
718 pageLayout)
719 elif Globals.isLinuxPlatform():
720 printer = QPrinter(mode=QPrinter.PrinterMode.HighResolution)
721 if Preferences.getPrinter("ColorMode"):
722 printer.setColorMode(QPrinter.ColorMode.Color)
723 else:
724 printer.setColorMode(QPrinter.ColorMode.GrayScale)
725 printerName = Preferences.getPrinter("PrinterName")
726 if printerName:
727 printer.setPrinterName(printerName)
728 printer.setOutputFormat(QPrinter.OutputFormat.PdfFormat)
729 if name:
730 printer.setOutputFileName(name)
731 printer.setResolution(Preferences.getPrinter("Resolution"))
732
733 printDialog = QPrintDialog(printer, self)
734 if printDialog.exec() == QDialog.DialogCode.Accepted:
735 browser.render(printer)
736
737 def __pdfGeneratedForSave(self, filePath, pdfData):
738 """
739 Private slot to save the generated PDF data to a file.
740
741 @param filePath path to save the PDF to
742 @type str
743 @param pdfData generated PDF document
744 @type QByteArray
745 """
746 if pdfData.size() == 0:
747 return
748
749 try:
750 with open(filePath, "wb") as f:
751 f.write(pdfData)
752 except OSError as err:
753 EricMessageBox.critical(
754 self,
755 self.tr("Print to PDF"),
756 self.tr("""<p>The PDF could not be written to file <b>{0}"""
757 """</b>.</p><p><b>Error:</b> {1}</p>""").format(
758 filePath, str(err)),
759 EricMessageBox.Ok)
760
761 @pyqtSlot()
762 def printPreviewBrowser(self, browser=None):
763 """
764 Public slot called to show a print preview of the displayed file.
765
766 @param browser reference to the browser to be printed (WebBrowserView)
767 """
768 from PyQt6.QtPrintSupport import QPrintPreviewDialog
769
770 if browser is None:
771 browser = self.currentBrowser()
772
773 printer = QPrinter(mode=QPrinter.PrinterMode.HighResolution)
774 if Preferences.getPrinter("ColorMode"):
775 printer.setColorMode(QPrinter.ColorMode.Color)
776 else:
777 printer.setColorMode(QPrinter.ColorMode.GrayScale)
778 if Preferences.getPrinter("FirstPageFirst"):
779 printer.setPageOrder(QPrinter.PageOrder.FirstPageFirst)
780 else:
781 printer.setPageOrder(QPrinter.PageOrder.LastPageFirst)
782 printer.setPageMargins(QMarginsF(
783 Preferences.getPrinter("LeftMargin") * 10,
784 Preferences.getPrinter("TopMargin") * 10,
785 Preferences.getPrinter("RightMargin") * 10,
786 Preferences.getPrinter("BottomMargin") * 10),
787 QPageLayout.Unit.Millimeter
788 )
789 printerName = Preferences.getPrinter("PrinterName")
790 if printerName:
791 printer.setPrinterName(printerName)
792 printer.setResolution(Preferences.getPrinter("Resolution"))
793
794 preview = QPrintPreviewDialog(printer, self)
795 preview.resize(800, 750)
796 preview.paintRequested.connect(
797 lambda p: self.__printPreviewRequested(p, browser))
798 preview.exec()
799
800 def __printPreviewRequested(self, printer, browser):
801 """
802 Private slot to generate the print preview.
803
804 @param printer reference to the printer object
805 @type QPrinter
806 @param browser reference to the browser to be printed
807 @type WebBrowserView
808 """
809 with EricOverrideCursor():
810 browser.page().execPrintPage(printer, 10 * 1000)
811
812 def __sourceChanged(self, url, browser):
813 """
814 Private slot to handle a change of a browsers source.
815
816 @param url URL of the new site
817 @type QUrl
818 @param browser reference to the web browser
819 @type WebBrowserView
820 """
821 self.sourceChanged.emit(browser, url)
822
823 if browser == self.currentBrowser():
824 self.currentUrlChanged.emit(url)
825
826 def __titleChanged(self, title, browser):
827 """
828 Private slot to handle a change of a browsers title.
829
830 @param title new title
831 @type str
832 @param browser reference to the web browser
833 @type WebBrowserView
834 """
835 index = self.indexOf(browser)
836 if title == "":
837 title = browser.url().toString()
838
839 self.setTabText(index, self.__elide(title.replace("&", "&&")))
840 self.setTabToolTip(index, title)
841
842 self.titleChanged.emit(browser, title)
843
844 def __elide(self, txt, mode=Qt.TextElideMode.ElideRight, length=40):
845 """
846 Private method to elide some text.
847
848 @param txt text to be elided (string)
849 @param mode elide mode (Qt.TextElideMode)
850 @param length amount of characters to be used (integer)
851 @return the elided text (string)
852 """
853 if mode == Qt.TextElideMode.ElideNone or len(txt) < length:
854 return txt
855 elif mode == Qt.TextElideMode.ElideLeft:
856 return "...{0}".format(txt[-length:])
857 elif mode == Qt.TextElideMode.ElideMiddle:
858 return "{0}...{1}".format(txt[:length // 2], txt[-(length // 2):])
859 elif mode == Qt.TextElideMode.ElideRight:
860 return "{0}...".format(txt[:length])
861 else:
862 # just in case
863 return txt
864
865 def preferencesChanged(self):
866 """
867 Public slot to handle a change of preferences.
868 """
869 for browser in self.browsers():
870 browser.preferencesChanged()
871
872 for urlbar in self.__stackedUrlBar.urlBars():
873 urlbar.preferencesChanged()
874
875 def __loadFinished(self, ok, browser):
876 """
877 Private method to handle the loadFinished signal.
878
879 @param ok flag indicating the result
880 @type bool
881 @param browser reference to the web browser
882 @type WebBrowserView
883 """
884 if ok:
885 self.showMessage.emit(self.tr("Finished loading"))
886 else:
887 self.showMessage.emit(self.tr("Failed to load"))
888
889 def __loadProgress(self, progress, browser):
890 """
891 Private method to handle the loadProgress signal.
892
893 Note: This works around wegengine not sending a loadFinished
894 signal for navigation on the same page.
895
896 @param progress load progress in percent
897 @type int
898 @param browser reference to the web browser
899 @type WebBrowserView
900 """
901 index = self.indexOf(browser)
902 if progress == 0:
903 # page loading has started
904 anim = self.animationLabel(index, "loadingAnimation", 40)
905 if not anim:
906 self.setTabIcon(index, UI.PixmapCache.getIcon("loading"))
907 else:
908 self.setTabIcon(index, QIcon())
909 self.setTabText(index, self.tr("Loading..."))
910 self.setTabToolTip(index, self.tr("Loading..."))
911 self.showMessage.emit(self.tr("Loading..."))
912
913 self.__mainWindow.setLoadingActions(True)
914 elif progress == 100:
915 import WebBrowser.WebBrowserWindow
916 self.resetAnimation(index)
917 self.setTabIcon(
918 index, WebBrowser.WebBrowserWindow.WebBrowserWindow.icon(
919 browser.url()))
920 self.showMessage.emit(self.tr("Finished loading"))
921
922 self.__mainWindow.setLoadingActions(False)
923
924 def __iconChanged(self, browser):
925 """
926 Private slot to handle a change of the web site icon.
927
928 @param browser reference to the web browser
929 @type WebBrowserView
930 """
931 self.setTabIcon(
932 self.indexOf(browser),
933 browser.icon())
934 self.__mainWindow.bookmarksManager().faviconChanged(browser.url())
935
936 def getSourceFileList(self):
937 """
938 Public method to get a list of all opened Qt help files.
939
940 @return dictionary with tab id as key and host/namespace as value
941 """
942 sourceList = {}
943 for i in range(self.count()):
944 browser = self.widget(i)
945 if (
946 browser is not None and
947 browser.source().isValid()
948 ):
949 sourceList[i] = browser.source().host()
950
951 return sourceList
952
953 def shallShutDown(self):
954 """
955 Public method to check, if the application should be shut down.
956
957 @return flag indicating a shut down (boolean)
958 """
959 if self.count() > 1 and Preferences.getWebBrowser(
960 "WarnOnMultipleClose"):
961 mb = EricMessageBox.EricMessageBox(
962 EricMessageBox.Information,
963 self.tr("Are you sure you want to close the window?"),
964 self.tr("""Are you sure you want to close the window?\n"""
965 """You have %n tab(s) open.""", "", self.count()),
966 modal=True,
967 parent=self)
968 quitButton = mb.addButton(
969 self.tr("&Quit"), EricMessageBox.AcceptRole)
970 quitButton.setIcon(UI.PixmapCache.getIcon("exit"))
971 closeTabButton = mb.addButton(
972 self.tr("C&lose Current Tab"), EricMessageBox.AcceptRole)
973 closeTabButton.setIcon(UI.PixmapCache.getIcon("tabClose"))
974 mb.addButton(EricMessageBox.Cancel)
975 mb.exec()
976 if mb.clickedButton() == quitButton:
977 return True
978 else:
979 if mb.clickedButton() == closeTabButton:
980 self.closeBrowser()
981 return False
982
983 return True
984
985 def stackedUrlBar(self):
986 """
987 Public method to get a reference to the stacked url bar.
988
989 @return reference to the stacked url bar (StackedUrlBar)
990 """
991 return self.__stackedUrlBar
992
993 def currentUrlBar(self):
994 """
995 Public method to get a reference to the current url bar.
996
997 @return reference to the current url bar (UrlBar)
998 """
999 return self.__stackedUrlBar.currentWidget()
1000
1001 def urlBarForView(self, view):
1002 """
1003 Public method to get a reference to the UrlBar associated with the
1004 given view.
1005
1006 @param view reference to the view to get the urlbar for
1007 @type WebBrowserView
1008 @return reference to the associated urlbar
1009 @rtype UrlBar
1010 """
1011 for urlbar in self.__stackedUrlBar.urlBars():
1012 if urlbar.browser() is view:
1013 return urlbar
1014
1015 return None
1016
1017 def __lineEditReturnPressed(self, edit):
1018 """
1019 Private slot to handle the entering of an URL.
1020
1021 @param edit reference to the line edit
1022 @type UrlBar
1023 """
1024 url = self.__guessUrlFromPath(edit.text())
1025 if ericApp().keyboardModifiers() == Qt.KeyboardModifier.AltModifier:
1026 self.newBrowser(url)
1027 else:
1028 self.currentBrowser().setSource(url)
1029 self.currentBrowser().setFocus()
1030
1031 def __pathSelected(self, path):
1032 """
1033 Private slot called when a URL is selected from the completer.
1034
1035 @param path path to be shown (string)
1036 """
1037 url = self.__guessUrlFromPath(path)
1038 self.currentBrowser().setSource(url)
1039
1040 def __guessUrlFromPath(self, path):
1041 """
1042 Private method to guess an URL given a path string.
1043
1044 @param path path string to guess an URL for (string)
1045 @return guessed URL (QUrl)
1046 """
1047 manager = self.__mainWindow.openSearchManager()
1048 path = Utilities.fromNativeSeparators(path)
1049 url = manager.convertKeywordSearchToUrl(path)
1050 if url.isValid():
1051 return url
1052
1053 try:
1054 url = QUrl.fromUserInput(path)
1055 except AttributeError:
1056 url = QUrl(path)
1057
1058 if (
1059 url.scheme() == "about" and
1060 url.path() == "home"
1061 ):
1062 url = QUrl("eric:home")
1063
1064 if url.scheme() in ["s", "search"]:
1065 url = manager.currentEngine().searchUrl(url.path().strip())
1066
1067 if (
1068 url.scheme() != "" and
1069 (url.host() != "" or url.path() != "")
1070 ):
1071 return url
1072
1073 urlString = Preferences.getWebBrowser("DefaultScheme") + path.strip()
1074 url = QUrl.fromEncoded(urlString.encode("utf-8"),
1075 QUrl.ParsingMode.TolerantMode)
1076
1077 return url
1078
1079 def __currentChanged(self, index):
1080 """
1081 Private slot to handle an index change.
1082
1083 @param index new index (integer)
1084 """
1085 self.__stackedUrlBar.setCurrentIndex(index)
1086
1087 browser = self.browserAt(index)
1088 if browser is not None:
1089 if browser.url() == "" and browser.hasFocus():
1090 self.__stackedUrlBar.currentWidget.setFocus()
1091 elif browser.url() != "":
1092 browser.setFocus()
1093
1094 def restoreClosedTab(self, act):
1095 """
1096 Public slot to restore the most recently closed tab.
1097
1098 @param act reference to the action that triggered
1099 @type QAction
1100 """
1101 if not self.canRestoreClosedTab():
1102 return
1103
1104 tab = self.__closedTabsManager.getClosedTabAt(act.data())
1105
1106 self.newBrowser(tab.url.toString(), position=tab.position)
1107
1108 def canRestoreClosedTab(self):
1109 """
1110 Public method to check, if closed tabs can be restored.
1111
1112 @return flag indicating that closed tabs can be restored (boolean)
1113 """
1114 return self.__closedTabsManager.isClosedTabAvailable()
1115
1116 def restoreAllClosedTabs(self):
1117 """
1118 Public slot to restore all closed tabs.
1119 """
1120 if not self.canRestoreClosedTab():
1121 return
1122
1123 for tab in self.__closedTabsManager.allClosedTabs():
1124 self.newBrowser(tab.url.toString(), position=tab.position)
1125 self.__closedTabsManager.clearList()
1126
1127 def clearClosedTabsList(self):
1128 """
1129 Public slot to clear the list of closed tabs.
1130 """
1131 self.__closedTabsManager.clearList()
1132
1133 def __aboutToShowClosedTabsMenu(self):
1134 """
1135 Private slot to populate the closed tabs menu.
1136 """
1137 fm = self.__closedTabsMenu.fontMetrics()
1138 try:
1139 maxWidth = fm.horizontalAdvance('m') * 40
1140 except AttributeError:
1141 maxWidth = fm.width('m') * 40
1142
1143 self.__closedTabsMenu.clear()
1144 for index, tab in enumerate(self.__closedTabsManager.allClosedTabs()):
1145 title = fm.elidedText(tab.title, Qt.TextElideMode.ElideRight,
1146 maxWidth)
1147 act = self.__closedTabsMenu.addAction(
1148 self.__mainWindow.icon(tab.url), title)
1149 act.setData(index)
1150 act.triggered.connect(lambda: self.restoreClosedTab(act))
1151 self.__closedTabsMenu.addSeparator()
1152 self.__closedTabsMenu.addAction(
1153 self.tr("Restore All Closed Tabs"), self.restoreAllClosedTabs)
1154 self.__closedTabsMenu.addAction(
1155 self.tr("Clear List"), self.clearClosedTabsList)
1156
1157 def closedTabsManager(self):
1158 """
1159 Public slot to get a reference to the closed tabs manager.
1160
1161 @return reference to the closed tabs manager (ClosedTabsManager)
1162 """
1163 return self.__closedTabsManager
1164
1165 def __closedTabAvailable(self, avail):
1166 """
1167 Private slot to handle changes of the availability of closed tabs.
1168
1169 @param avail flag indicating the availability of closed tabs (boolean)
1170 """
1171 self.__closedTabsButton.setEnabled(avail)
1172 self.__restoreClosedTabAct.setEnabled(avail)
1173
1174 ####################################################
1175 ## Methods below implement session related functions
1176 ####################################################
1177
1178 def getSessionData(self):
1179 """
1180 Public method to populate the session data.
1181
1182 @return dictionary containing the session data
1183 @rtype dict
1184 """
1185 sessionData = {}
1186
1187 # 1. current index
1188 sessionData["CurrentTabIndex"] = self.currentIndex()
1189
1190 # 2. tab data
1191 sessionData["Tabs"] = []
1192 for index in range(self.count()):
1193 browser = self.widget(index)
1194 data = browser.getSessionData()
1195 sessionData["Tabs"].append(data)
1196
1197 return sessionData
1198
1199 def loadFromSessionData(self, sessionData):
1200 """
1201 Public method to load the session data.
1202
1203 @param sessionData dictionary containing the session data as
1204 generated by getSessionData()
1205 @type dict
1206 """
1207 tabCount = self.count()
1208
1209 # 1. load tab data
1210 if "Tabs" in sessionData:
1211 loadTabOnActivate = Preferences.getWebBrowser(
1212 "LoadTabOnActivation")
1213 for data in sessionData["Tabs"]:
1214 browser = self.newBrowser(restoreSession=True)
1215 if loadTabOnActivate:
1216 browser.storeSessionData(data)
1217 title, urlStr, icon = browser.extractSessionMetaData(data)
1218 index = self.indexOf(browser)
1219 self.setTabText(index, title)
1220 self.setTabIcon(index, icon)
1221 else:
1222 browser.loadFromSessionData(data)
1223
1224 # 2. set tab index
1225 if (
1226 "CurrentTabIndex" in sessionData and
1227 sessionData["CurrentTabIndex"] >= 0
1228 ):
1229 index = tabCount + sessionData["CurrentTabIndex"]
1230 self.setCurrentIndex(index)
1231 self.browserAt(index).activateSession()

eric ide

mercurial