1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2011 - 2019 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 __future__ import unicode_literals |
|
11 |
|
12 import os |
|
13 |
|
14 from PyQt5.QtCore import pyqtSlot, QUrl, Qt, QFile |
|
15 from PyQt5.QtGui import QPixmap, QCursor, QPainter, QColor, QBrush |
|
16 from PyQt5.QtWidgets import QDialog, QTreeWidgetItem, QGraphicsScene, QMenu, \ |
|
17 QApplication, QListWidgetItem |
|
18 from PyQt5.QtWebKit import QWebSettings |
|
19 |
|
20 from E5Gui import E5MessageBox, E5FileDialog |
|
21 |
|
22 try: |
|
23 from .Ui_SiteInfoDialog import Ui_SiteInfoDialog # __IGNORE_WARNING__ |
|
24 SSL = True |
|
25 except ImportError: |
|
26 from .Ui_SiteInfoNoSslDialog import Ui_SiteInfoDialog # __IGNORE_WARNING__ |
|
27 SSL = False |
|
28 |
|
29 from ..Download.DownloadUtilities import dataString |
|
30 |
|
31 import UI.PixmapCache |
|
32 from Globals import qVersionTuple |
|
33 |
|
34 |
|
35 class SiteInfoDialog(QDialog, Ui_SiteInfoDialog): |
|
36 """ |
|
37 Class implementing a dialog to show some information about a site. |
|
38 """ |
|
39 okStyle = "QLabel { color : white; background-color : green; }" |
|
40 nokStyle = "QLabel { color : white; background-color : red; }" |
|
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(SiteInfoDialog, self).__init__(parent) |
|
50 self.setupUi(self) |
|
51 self.setWindowFlags(Qt.Window) |
|
52 |
|
53 # put icons |
|
54 self.tabWidget.setTabIcon( |
|
55 0, UI.PixmapCache.getIcon("siteinfo-general.png")) |
|
56 self.tabWidget.setTabIcon( |
|
57 1, UI.PixmapCache.getIcon("siteinfo-media.png")) |
|
58 self.tabWidget.setTabIcon( |
|
59 2, UI.PixmapCache.getIcon("siteinfo-databases.png")) |
|
60 if SSL: |
|
61 self.tabWidget.setTabIcon( |
|
62 3, UI.PixmapCache.getIcon("siteinfo-security.png")) |
|
63 |
|
64 self.__mainFrame = browser.page().mainFrame() |
|
65 self.__baseUrl = browser.url() |
|
66 title = browser.title() |
|
67 sslInfo = browser.page().getSslCertificateChain() |
|
68 |
|
69 #prepare background of image preview |
|
70 self.__imagePreviewStandardBackground = \ |
|
71 self.imagePreview.backgroundBrush() |
|
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.__mainFrame.baseUrl().toString()) |
|
84 self.sizeLabel.setText(dataString(browser.page().totalBytes())) |
|
85 encoding = "" |
|
86 |
|
87 # populate Meta tags |
|
88 meta = self.__mainFrame.findAllElements("meta") |
|
89 for element in meta: |
|
90 content = element.attribute("content") |
|
91 name = element.attribute("name") |
|
92 if not name: |
|
93 name = element.attribute("http-equiv") |
|
94 if element.attribute("charset"): |
|
95 encoding = element.attribute("charset") |
|
96 if "charset=" in content: |
|
97 encoding = content[content.index("charset=") + 8:] |
|
98 |
|
99 if not content or not name: |
|
100 continue |
|
101 |
|
102 QTreeWidgetItem(self.tagsTree, [name, content]) |
|
103 for col in range(self.tagsTree.columnCount()): |
|
104 self.tagsTree.resizeColumnToContents(col) |
|
105 |
|
106 if not encoding: |
|
107 encoding = QWebSettings.globalSettings().defaultTextEncoding() |
|
108 self.encodingLabel.setText(encoding) |
|
109 |
|
110 # populate the Security info and the Security tab |
|
111 if sslInfo and \ |
|
112 ((qVersionTuple() >= (5, 0, 0) and |
|
113 not sslInfo[0].isBlacklisted()) or |
|
114 (qVersionTuple() < (5, 0, 0) and sslInfo[0].isValid())): |
|
115 self.securityLabel.setStyleSheet(SiteInfoDialog.okStyle) |
|
116 self.securityLabel.setText('<b>Connection is encrypted.</b>') |
|
117 if SSL: |
|
118 self.sslWidget.showCertificateChain(sslInfo) |
|
119 self.securityDetailsButton.setEnabled(True) |
|
120 else: |
|
121 self.securityDetailsButton.setEnabled(False) |
|
122 else: |
|
123 self.securityLabel.setStyleSheet(SiteInfoDialog.nokStyle) |
|
124 self.securityLabel.setText('<b>Connection is not encrypted.</b>') |
|
125 self.securityDetailsButton.setEnabled(False) |
|
126 self.tabWidget.setTabEnabled( |
|
127 self.tabWidget.indexOf(self.securityTab), False) |
|
128 |
|
129 # populate Media tab |
|
130 images = self.__mainFrame.findAllElements("img") |
|
131 for element in images: |
|
132 src = element.attribute("src") |
|
133 alt = element.attribute("alt") |
|
134 if src and src.startswith("data:"): |
|
135 continue |
|
136 if not alt: |
|
137 if src.find("/") == -1: |
|
138 alt = src |
|
139 else: |
|
140 pos = src.find("/") |
|
141 alt = src[pos + 1:] |
|
142 |
|
143 if not src or not alt: |
|
144 continue |
|
145 |
|
146 QTreeWidgetItem(self.imagesTree, [alt, src]) |
|
147 for col in range(self.imagesTree.columnCount()): |
|
148 self.imagesTree.resizeColumnToContents(col) |
|
149 if self.imagesTree.columnWidth(0) > 300: |
|
150 self.imagesTree.setColumnWidth(0, 300) |
|
151 self.imagesTree.setCurrentItem(self.imagesTree.topLevelItem(0)) |
|
152 self.imagesTree.setContextMenuPolicy(Qt.CustomContextMenu) |
|
153 self.imagesTree.customContextMenuRequested.connect( |
|
154 self.__imagesTreeContextMenuRequested) |
|
155 |
|
156 # populate the Databases tab |
|
157 databases = self.__mainFrame.securityOrigin().databases() |
|
158 counter = 0 |
|
159 for database in databases: |
|
160 itm = QListWidgetItem(self.databasesList) |
|
161 itm.setText(database.displayName()) |
|
162 itm.setData(Qt.UserRole, counter) |
|
163 counter += 1 |
|
164 |
|
165 if counter == 0: |
|
166 itm = QListWidgetItem(self.databasesList) |
|
167 itm.setText(self.tr("No databases are used by this page.")) |
|
168 itm.setFlags(itm.flags() & Qt.ItemIsSelectable) |
|
169 |
|
170 @pyqtSlot() |
|
171 def on_securityDetailsButton_clicked(self): |
|
172 """ |
|
173 Private slot to show security details. |
|
174 """ |
|
175 self.tabWidget.setCurrentIndex( |
|
176 self.tabWidget.indexOf(self.securityTab)) |
|
177 |
|
178 @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem) |
|
179 def on_imagesTree_currentItemChanged(self, current, previous): |
|
180 """ |
|
181 Private slot to show a preview of the selected image. |
|
182 |
|
183 @param current current image entry (QTreeWidgetItem) |
|
184 @param previous old current entry (QTreeWidgetItem) |
|
185 """ |
|
186 if current is None: |
|
187 return |
|
188 |
|
189 imageUrl = QUrl(current.text(1)) |
|
190 if imageUrl.isRelative(): |
|
191 imageUrl = self.__baseUrl.resolved(imageUrl) |
|
192 |
|
193 import Helpviewer.HelpWindow |
|
194 cache = Helpviewer.HelpWindow.HelpWindow.networkAccessManager().cache() |
|
195 if cache: |
|
196 cacheData = cache.data(imageUrl) |
|
197 else: |
|
198 cacheData = None |
|
199 pixmap = QPixmap() |
|
200 invalidPixmap = False |
|
201 scene = QGraphicsScene(self.imagePreview) |
|
202 if not cacheData: |
|
203 invalidPixmap = True |
|
204 else: |
|
205 pixmap.loadFromData(cacheData.readAll()) |
|
206 if pixmap.isNull(): |
|
207 invalidPixmap = True |
|
208 if invalidPixmap: |
|
209 self.imagePreview.setBackgroundBrush( |
|
210 self.__imagePreviewStandardBackground) |
|
211 scene.addText(self.tr("Preview not available.")) |
|
212 else: |
|
213 self.imagePreview.setBackgroundBrush(QBrush(self.__tilePixmap)) |
|
214 scene.addPixmap(pixmap) |
|
215 self.imagePreview.setScene(scene) |
|
216 |
|
217 def __imagesTreeContextMenuRequested(self, pos): |
|
218 """ |
|
219 Private slot to show a context menu for the images list. |
|
220 |
|
221 @param pos position for the menu (QPoint) |
|
222 """ |
|
223 itm = self.imagesTree.itemAt(pos) |
|
224 if itm is None: |
|
225 return |
|
226 |
|
227 menu = QMenu() |
|
228 act = menu.addAction(self.tr("Copy Image Location to Clipboard")) |
|
229 act.setData(itm.text(1)) |
|
230 act.triggered.connect(lambda: self.__copyAction(act)) |
|
231 act = menu.addAction(self.tr("Copy Image Name to Clipboard")) |
|
232 act.setData(itm.text(0)) |
|
233 act.triggered.connect(lambda: self.__copyAction(act)) |
|
234 menu.addSeparator() |
|
235 act = menu.addAction(self.tr("Save Image")) |
|
236 act.setData(self.imagesTree.indexOfTopLevelItem(itm)) |
|
237 act.triggered.connect(lambda: self.__saveImage(act)) |
|
238 menu.exec_(QCursor.pos()) |
|
239 |
|
240 def __copyAction(self, act): |
|
241 """ |
|
242 Private slot to copy the image URL or the image name to the clipboard. |
|
243 |
|
244 @param act reference to the action that triggered |
|
245 @type QAction |
|
246 """ |
|
247 QApplication.clipboard().setText(act.data()) |
|
248 |
|
249 def __saveImage(self, act): |
|
250 """ |
|
251 Private slot to save the selected image to disk. |
|
252 |
|
253 @param act reference to the action that triggered |
|
254 @type QAction |
|
255 """ |
|
256 index = act.data() |
|
257 itm = self.imagesTree.topLevelItem(index) |
|
258 if itm is None: |
|
259 return |
|
260 |
|
261 imageUrl = QUrl(itm.text(1)) |
|
262 if not imageUrl.host(): |
|
263 imageUrl.setHost(QUrl(self.siteAddressLabel.text()).host()) |
|
264 imageUrl.setScheme(QUrl(self.siteAddressLabel.text()).scheme()) |
|
265 |
|
266 import Helpviewer.HelpWindow |
|
267 cache = Helpviewer.HelpWindow.HelpWindow.networkAccessManager().cache() |
|
268 if cache: |
|
269 cacheData = cache.data(imageUrl) |
|
270 else: |
|
271 cacheData = None |
|
272 if not cacheData: |
|
273 E5MessageBox.critical( |
|
274 self, |
|
275 self.tr("Save Image"), |
|
276 self.tr("""This image is not available.""")) |
|
277 return |
|
278 |
|
279 downloadDirectory = Helpviewer.HelpWindow.HelpWindow\ |
|
280 .downloadManager().downloadDirectory() |
|
281 fn = os.path.join(downloadDirectory, os.path.basename(itm.text(1))) |
|
282 filename = E5FileDialog.getSaveFileName( |
|
283 self, |
|
284 self.tr("Save Image"), |
|
285 fn, |
|
286 self.tr("All Files (*)"), |
|
287 E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite)) |
|
288 |
|
289 if not filename: |
|
290 return |
|
291 |
|
292 f = QFile(filename) |
|
293 if not f.open(QFile.WriteOnly): |
|
294 E5MessageBox.critical( |
|
295 self, |
|
296 self.tr("Save Image"), |
|
297 self.tr( |
|
298 """<p>Cannot write to file <b>{0}</b>.</p>""") |
|
299 .format(filename)) |
|
300 return |
|
301 f.write(cacheData.readAll()) |
|
302 f.close() |
|
303 |
|
304 @pyqtSlot(QListWidgetItem, QListWidgetItem) |
|
305 def on_databasesList_currentItemChanged(self, current, previous): |
|
306 """ |
|
307 Private slot to show data about the selected database. |
|
308 |
|
309 @param current current database entry (QTreeWidgetItem) |
|
310 @param previous old current entry (QTreeWidgetItem) |
|
311 """ |
|
312 if current is None: |
|
313 return |
|
314 |
|
315 dbId = current.data(Qt.UserRole) |
|
316 databases = self.__mainFrame.securityOrigin().databases() |
|
317 |
|
318 if dbId >= len(databases): |
|
319 return |
|
320 |
|
321 db = databases[dbId] |
|
322 self.databaseName.setText( |
|
323 "{0} ({1})".format(db.displayName(), db.name())) |
|
324 self.databasePath.setText(db.fileName()) |
|
325 self.databaseSize.setText(dataString(db.size())) |
|