src/eric7/WebBrowser/SiteInfo/SiteInfoDialog.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 8881
54e42bc2437a
child 9221
bf71ee032bb4
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2011 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a dialog to show some information about a site.
8 """
9
10 from PyQt6.QtCore import pyqtSlot, QUrl, Qt
11 from PyQt6.QtGui import QPixmap, QImage, QPainter, QColor, QBrush
12 from PyQt6.QtNetwork import QNetworkRequest, QNetworkReply
13 from PyQt6.QtWidgets import (
14 QDialog, QTreeWidgetItem, QGraphicsScene, QMenu, QApplication,
15 QGraphicsPixmapItem
16 )
17 try:
18 from PyQt6.QtNetwork import QSslCertificate # __IGNORE_WARNING__
19 SSL = True
20 except ImportError:
21 SSL = False
22
23 from EricWidgets import EricMessageBox, EricFileDialog
24
25 from .Ui_SiteInfoDialog import Ui_SiteInfoDialog
26
27 from ..Tools import Scripts, WebBrowserTools
28 from ..WebBrowserPage import WebBrowserPage
29
30 import UI.PixmapCache
31 import Preferences
32
33 from WebBrowser.WebBrowserWindow import WebBrowserWindow
34
35
36 class SiteInfoDialog(QDialog, Ui_SiteInfoDialog):
37 """
38 Class implementing a dialog to show some information about a site.
39 """
40 securityStyleFormat = "QLabel {{ background-color : {0}; }}"
41
42 def __init__(self, browser, parent=None):
43 """
44 Constructor
45
46 @param browser reference to the browser window (HelpBrowser)
47 @param parent reference to the parent widget (QWidget)
48 """
49 super().__init__(parent)
50 self.setupUi(self)
51 self.setWindowFlags(Qt.WindowType.Window)
52
53 # put icons
54 self.tabWidget.setTabIcon(
55 0, UI.PixmapCache.getIcon("siteinfo-general"))
56 self.tabWidget.setTabIcon(
57 1, UI.PixmapCache.getIcon("siteinfo-media"))
58 if SSL:
59 self.tabWidget.setTabIcon(
60 2, UI.PixmapCache.getIcon("siteinfo-security"))
61
62 self.__imageReply = None
63
64 self.__baseUrl = browser.url()
65 title = browser.title()
66 sslInfo = browser.page().getSslCertificateChain()
67
68 #prepare background of image preview
69 self.__imagePreviewStandardBackground = (
70 self.imagePreview.backgroundBrush()
71 )
72 color1 = QColor(220, 220, 220)
73 color2 = QColor(160, 160, 160)
74 self.__tilePixmap = QPixmap(8, 8)
75 self.__tilePixmap.fill(color1)
76 tilePainter = QPainter(self.__tilePixmap)
77 tilePainter.fillRect(0, 0, 4, 4, color2)
78 tilePainter.fillRect(4, 4, 4, 4, color2)
79 tilePainter.end()
80
81 # populate General tab
82 self.heading.setText("<b>{0}</b>".format(title))
83 self.siteAddressLabel.setText(self.__baseUrl.toString())
84 if self.__baseUrl.scheme() in ["https"]:
85 if WebBrowserWindow.networkManager().isInsecureHost(
86 self.__baseUrl.host()
87 ):
88 self.securityIconLabel.setPixmap(
89 UI.PixmapCache.getPixmap("securityMedium"))
90 self.securityLabel.setStyleSheet(
91 SiteInfoDialog.securityStyleFormat.format(
92 Preferences.getWebBrowser("InsecureUrlColor").name()
93 )
94 )
95 self.securityLabel.setText(self.tr(
96 '<b>Connection is encrypted but may be insecure.</b>'))
97 else:
98 self.securityIconLabel.setPixmap(
99 UI.PixmapCache.getPixmap("securityHigh"))
100 self.securityLabel.setStyleSheet(
101 SiteInfoDialog.securityStyleFormat.format(
102 Preferences.getWebBrowser("SecureUrlColor").name()
103 )
104 )
105 self.securityLabel.setText(
106 self.tr('<b>Connection is encrypted.</b>'))
107 else:
108 self.securityIconLabel.setPixmap(
109 UI.PixmapCache.getPixmap("securityLow"))
110 self.securityLabel.setText(
111 self.tr('<b>Connection is not encrypted.</b>'))
112 browser.page().runJavaScript(
113 "document.charset", WebBrowserPage.SafeJsWorld,
114 lambda res: self.encodingLabel.setText(res))
115
116 # populate the Security tab
117 if sslInfo and SSL:
118 self.sslWidget.showCertificateChain(sslInfo)
119 self.tabWidget.setTabEnabled(2, SSL and bool(sslInfo))
120 self.securityDetailsButton.setEnabled(SSL and bool(sslInfo))
121
122 # populate Meta tags
123 browser.page().runJavaScript(Scripts.getAllMetaAttributes(),
124 WebBrowserPage.SafeJsWorld,
125 self.__processMetaAttributes)
126
127 # populate Media tab
128 browser.page().runJavaScript(Scripts.getAllImages(),
129 WebBrowserPage.SafeJsWorld,
130 self.__processImageTags)
131
132 self.tabWidget.setCurrentIndex(0)
133
134 @pyqtSlot()
135 def on_securityDetailsButton_clicked(self):
136 """
137 Private slot to show security details.
138 """
139 self.tabWidget.setCurrentIndex(
140 self.tabWidget.indexOf(self.securityTab))
141
142 def __processImageTags(self, res):
143 """
144 Private method to process the image tags.
145
146 @param res result of the JavaScript script
147 @type list of dict
148 """
149 for img in res:
150 src = img["src"]
151 alt = img["alt"]
152 if not alt:
153 if src.find("/") == -1:
154 alt = src
155 else:
156 pos = src.rfind("/")
157 alt = src[pos + 1:]
158
159 if not src or not alt:
160 continue
161
162 QTreeWidgetItem(self.imagesTree, [alt, src])
163
164 for col in range(self.imagesTree.columnCount()):
165 self.imagesTree.resizeColumnToContents(col)
166 if self.imagesTree.columnWidth(0) > 300:
167 self.imagesTree.setColumnWidth(0, 300)
168 self.imagesTree.setCurrentItem(self.imagesTree.topLevelItem(0))
169 self.imagesTree.setContextMenuPolicy(
170 Qt.ContextMenuPolicy.CustomContextMenu)
171 self.imagesTree.customContextMenuRequested.connect(
172 self.__imagesTreeContextMenuRequested)
173
174 def __processMetaAttributes(self, res):
175 """
176 Private method to process the meta attributes.
177
178 @param res result of the JavaScript script
179 @type list of dict
180 """
181 for meta in res:
182 content = meta["content"]
183 name = meta["name"]
184 if not name:
185 name = meta["httpequiv"]
186
187 if not name or not content:
188 continue
189
190 if meta["charset"]:
191 self.encodingLabel.setText(meta["charset"])
192 if "charset=" in content:
193 self.encodingLabel.setText(
194 content[content.index("charset=") + 8:])
195
196 QTreeWidgetItem(self.tagsTree, [name, content])
197 for col in range(self.tagsTree.columnCount()):
198 self.tagsTree.resizeColumnToContents(col)
199
200 @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem)
201 def on_imagesTree_currentItemChanged(self, current, previous):
202 """
203 Private slot to show a preview of the selected image.
204
205 @param current current image entry (QTreeWidgetItem)
206 @param previous old current entry (QTreeWidgetItem)
207 """
208 if current is None:
209 return
210
211 imageUrl = QUrl(current.text(1))
212 if imageUrl.isRelative():
213 imageUrl = self.__baseUrl.resolved(imageUrl)
214
215 pixmap = QPixmap()
216 loading = False
217
218 if imageUrl.scheme() == "data":
219 encodedUrl = current.text(1).encode("utf-8")
220 imageData = encodedUrl[encodedUrl.find(b",") + 1:]
221 pixmap = WebBrowserTools.pixmapFromByteArray(imageData)
222 elif imageUrl.scheme() == "file":
223 pixmap = QPixmap(imageUrl.toLocalFile())
224 elif imageUrl.scheme() == "qrc":
225 pixmap = QPixmap(imageUrl.toString()[3:])
226 else:
227 if self.__imageReply is not None:
228 self.__imageReply.deleteLater()
229 self.__imageReply = None
230
231 from WebBrowser.WebBrowserWindow import WebBrowserWindow
232 self.__imageReply = WebBrowserWindow.networkManager().get(
233 QNetworkRequest(imageUrl))
234 self.__imageReply.finished.connect(self.__imageReplyFinished)
235 loading = True
236 self.__showLoadingText()
237
238 if not loading:
239 self.__showPixmap(pixmap)
240
241 @pyqtSlot()
242 def __imageReplyFinished(self):
243 """
244 Private slot handling the loading of an image.
245 """
246 if self.__imageReply.error() != QNetworkReply.NetworkError.NoError:
247 return
248
249 data = self.__imageReply.readAll()
250 self.__showPixmap(QPixmap.fromImage(QImage.fromData(data)))
251
252 def __showPixmap(self, pixmap):
253 """
254 Private method to show a pixmap in the preview pane.
255
256 @param pixmap pixmap to be shown
257 @type QPixmap
258 """
259 scene = QGraphicsScene(self.imagePreview)
260 if pixmap.isNull():
261 self.imagePreview.setBackgroundBrush(
262 self.__imagePreviewStandardBackground)
263 scene.addText(self.tr("Preview not available."))
264 else:
265 self.imagePreview.setBackgroundBrush(QBrush(self.__tilePixmap))
266 scene.addPixmap(pixmap)
267 self.imagePreview.setScene(scene)
268
269 def __showLoadingText(self):
270 """
271 Private method to show some text while loading an image.
272 """
273 self.imagePreview.setBackgroundBrush(
274 self.__imagePreviewStandardBackground)
275 scene = QGraphicsScene(self.imagePreview)
276 scene.addText(self.tr("Loading..."))
277 self.imagePreview.setScene(scene)
278
279 def __imagesTreeContextMenuRequested(self, pos):
280 """
281 Private slot to show a context menu for the images list.
282
283 @param pos position for the menu (QPoint)
284 """
285 itm = self.imagesTree.itemAt(pos)
286 if itm is None:
287 return
288
289 menu = QMenu()
290 act1 = menu.addAction(self.tr("Copy Image Location to Clipboard"))
291 act1.setData(itm.text(1))
292 act1.triggered.connect(lambda: self.__copyAction(act1))
293 act2 = menu.addAction(self.tr("Copy Image Name to Clipboard"))
294 act2.setData(itm.text(0))
295 act2.triggered.connect(lambda: self.__copyAction(act2))
296 menu.addSeparator()
297 act3 = menu.addAction(self.tr("Save Image"))
298 act3.setData(self.imagesTree.indexOfTopLevelItem(itm))
299 act3.triggered.connect(lambda: self.__saveImage(act3))
300 menu.exec(self.imagesTree.viewport().mapToGlobal(pos))
301
302 def __copyAction(self, act):
303 """
304 Private slot to copy the image URL or the image name to the clipboard.
305
306 @param act reference to the action that triggered
307 @type QAction
308 """
309 QApplication.clipboard().setText(act.data())
310
311 def __saveImage(self, act):
312 """
313 Private slot to save the selected image to disk.
314
315 @param act reference to the action that triggered
316 @type QAction
317 """
318 index = act.data()
319 itm = self.imagesTree.topLevelItem(index)
320 if itm is None:
321 return
322
323 if (
324 not self.imagePreview.scene() or
325 len(self.imagePreview.scene().items()) == 0
326 ):
327 return
328
329 pixmapItem = self.imagePreview.scene().items()[0]
330 if not isinstance(pixmapItem, QGraphicsPixmapItem):
331 return
332
333 if pixmapItem.pixmap().isNull():
334 EricMessageBox.warning(
335 self,
336 self.tr("Save Image"),
337 self.tr(
338 """<p>This preview is not available.</p>"""))
339 return
340
341 imageFileName = WebBrowserTools.getFileNameFromUrl(QUrl(itm.text(1)))
342 index = imageFileName.rfind(".")
343 if index != -1:
344 imageFileName = imageFileName[:index] + ".png"
345
346 filename = EricFileDialog.getSaveFileName(
347 self,
348 self.tr("Save Image"),
349 imageFileName,
350 self.tr("All Files (*)"),
351 EricFileDialog.DontConfirmOverwrite)
352
353 if not filename:
354 return
355
356 if not pixmapItem.pixmap().save(filename, "PNG"):
357 EricMessageBox.critical(
358 self,
359 self.tr("Save Image"),
360 self.tr(
361 """<p>Cannot write to file <b>{0}</b>.</p>""")
362 .format(filename))
363 return

eric ide

mercurial