Sun, 28 Feb 2016 15:23:42 +0100
Continued porting the web browser.
- ported the site info dialog
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/SiteInfo/SiteInfoDialog.py Sun Feb 28 15:23:42 2016 +0100 @@ -0,0 +1,286 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2011 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a dialog to show some information about a site. +""" + +from __future__ import unicode_literals + +from PyQt5.QtCore import pyqtSlot, QUrl, Qt +from PyQt5.QtGui import QPixmap, QImage +from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply +from PyQt5.QtWidgets import QDialog, QTreeWidgetItem, QGraphicsScene, QMenu, \ + QApplication, QGraphicsPixmapItem + +from E5Gui import E5MessageBox, E5FileDialog + +from .Ui_SiteInfoDialog import Ui_SiteInfoDialog + +from ..Tools import Scripts, WebBrowserTools + +import UI.PixmapCache + + +class SiteInfoDialog(QDialog, Ui_SiteInfoDialog): + """ + Class implementing a dialog to show some information about a site. + """ + okStyle = "QLabel { color : white; background-color : green; }" + nokStyle = "QLabel { color : white; background-color : red; }" + + def __init__(self, browser, parent=None): + """ + Constructor + + @param browser reference to the browser window (HelpBrowser) + @param parent reference to the parent widget (QWidget) + """ + super(SiteInfoDialog, self).__init__(parent) + self.setupUi(self) + self.setWindowFlags(Qt.Window) + + # put icons + self.tabWidget.setTabIcon( + 0, UI.PixmapCache.getIcon("siteinfo-general.png")) + self.tabWidget.setTabIcon( + 1, UI.PixmapCache.getIcon("siteinfo-media.png")) + + self.__imageReply = None + + self.__baseUrl = browser.url() + title = browser.title() + + # populate General tab + self.heading.setText("<b>{0}</b>".format(title)) + self.siteAddressLabel.setText(self.__baseUrl.toString()) + if self.__baseUrl.scheme() in ["https"]: + self.securityLabel.setStyleSheet(SiteInfoDialog.okStyle) + self.securityLabel.setText('<b>Connection is encrypted.</b>') + else: + self.securityLabel.setStyleSheet(SiteInfoDialog.nokStyle) + self.securityLabel.setText('<b>Connection is not encrypted.</b>') + browser.page().runJavaScript( + "document.charset", + lambda res: self.encodingLabel.setText(res)) + + # populate Meta tags + browser.page().runJavaScript(Scripts.getAllMetaAttributes(), + self.__processMetaAttributes) + + # populate Media tab + browser.page().runJavaScript(Scripts.getAllImages(), + self.__processImageTags) + + def __processImageTags(self, res): + """ + Private method to process the image tags. + + @param res result of the JavaScript script + @type list of dict + """ + for img in res: + src = img["src"] + alt = img["alt"] + if not alt: + if src.find("/") == -1: + alt = src + else: + pos = src.rfind("/") + alt = src[pos + 1:] + + if not src or not alt: + continue + + QTreeWidgetItem(self.imagesTree, [alt, src]) + + for col in range(self.imagesTree.columnCount()): + self.imagesTree.resizeColumnToContents(col) + if self.imagesTree.columnWidth(0) > 300: + self.imagesTree.setColumnWidth(0, 300) + self.imagesTree.setCurrentItem(self.imagesTree.topLevelItem(0)) + self.imagesTree.setContextMenuPolicy(Qt.CustomContextMenu) + self.imagesTree.customContextMenuRequested.connect( + self.__imagesTreeContextMenuRequested) + + def __processMetaAttributes(self, res): + """ + Private method to process the meta attributes. + + @param res result of the JavaScript script + @type list of dict + """ + for meta in res: + content = meta["content"] + name = meta["name"] + if not name: + name = meta["httpequiv"] + + if not name or not content: + continue + + if meta["charset"]: + self.encodingLabel.setText(meta["charset"]) + if "charset=" in content: + self.encodingLabel.setText( + content[content.index("charset=") + 8:]) + + QTreeWidgetItem(self.tagsTree, [name, content]) + for col in range(self.tagsTree.columnCount()): + self.tagsTree.resizeColumnToContents(col) + + @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem) + def on_imagesTree_currentItemChanged(self, current, previous): + """ + Private slot to show a preview of the selected image. + + @param current current image entry (QTreeWidgetItem) + @param previous old current entry (QTreeWidgetItem) + """ + if current is None: + return + + imageUrl = QUrl(current.text(1)) + if imageUrl.isRelative(): + imageUrl = self.__baseUrl.resolved(imageUrl) + + pixmap = QPixmap() + loading = False + + if imageUrl.scheme() == "data": + encodedUrl = current.text(1).encode("utf-8") + imageData = encodedUrl[encodedUrl.find(",") + 1:] + pixmap = WebBrowserTools.pixmapFromByteArray(imageData) + elif imageUrl.scheme() == "file": + pixmap = QPixmap(imageUrl.toLocalFile()) + elif imageUrl.scheme() == "qrc": + pixmap = QPixmap(imageUrl.toString()[3:]) + else: + if self.__imageReply is not None: + self.__imageReply.deleteLater() + self.__imageReply = None + + from WebBrowser.WebBrowserWindow import WebBrowserWindow + self.__imageReply = WebBrowserWindow.networkManager().get( + QNetworkRequest(imageUrl)) + self.__imageReply.finished.connect(self.__imageReplyFinished) + loading = True + self.__showLoadingText() + + if not loading: + self.__showPixmap(pixmap) + + @pyqtSlot() + def __imageReplyFinished(self): + """ + Private slot handling the loading of an image. + """ + if self.__imageReply.error() != QNetworkReply.NoError: + return + + data = self.__imageReply.readAll() + self.__showPixmap(QPixmap.fromImage(QImage.fromData(data))) + + def __showPixmap(self, pixmap): + """ + Private method to show a pixmap in the preview pane. + + @param pixmap pixmap to be shown + @type QPixmap + """ + scene = QGraphicsScene(self.imagePreview) + if pixmap.isNull(): + scene.addText(self.tr("Preview not available.")) + else: + scene.addPixmap(pixmap) + self.imagePreview.setScene(scene) + + def __showLoadingText(self): + """ + Private method to show some text while loading an image. + """ + scene = QGraphicsScene(self.imagePreview) + scene.addText(self.tr("Loading...")) + self.imagePreview.setScene(scene) + + def __imagesTreeContextMenuRequested(self, pos): + """ + Private slot to show a context menu for the images list. + + @param pos position for the menu (QPoint) + """ + itm = self.imagesTree.itemAt(pos) + if itm is None: + return + + menu = QMenu() + menu.addAction( + self.tr("Copy Image Location to Clipboard"), + self.__copyAction).setData(itm.text(1)) + menu.addAction( + self.tr("Copy Image Name to Clipboard"), + self.__copyAction).setData(itm.text(0)) + menu.addSeparator() + menu.addAction( + self.tr("Save Image"), + self.__saveImage).setData(self.imagesTree.indexOfTopLevelItem(itm)) + menu.exec_(self.imagesTree.viewport().mapToGlobal(pos)) + + def __copyAction(self): + """ + Private slot to copy the image URL or the image name to the clipboard. + """ + act = self.sender() + QApplication.clipboard().setText(act.data()) + + def __saveImage(self): + """ + Private slot to save the selected image to disk. + """ + act = self.sender() + index = act.data() + itm = self.imagesTree.topLevelItem(index) + if itm is None: + return + + if not self.imagePreview.scene() or \ + len(self.imagePreview.scene().items()) == 0: + return + + pixmapItem = self.imagePreview.scene().items()[0] + if not isinstance(pixmapItem, QGraphicsPixmapItem): + return + + if pixmapItem.pixmap().isNull(): + E5MessageBox.warning( + self, + self.tr("Save Image"), + self.tr( + """<p>This preview is not available.</p>""")) + return + + imageFileName = WebBrowserTools.getFileNameFromUrl(QUrl(itm.text(1))) + index = imageFileName.rfind(".") + if index != -1: + imageFileName = imageFileName[:index] + ".png" + + filename = E5FileDialog.getSaveFileName( + self, + self.tr("Save Image"), + imageFileName, + self.tr("All Files (*)"), + E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite)) + + if not filename: + return + + if not pixmapItem.pixmap().save(filename, "PNG"): + E5MessageBox.critical( + self, + self.tr("Save Image"), + self.tr( + """<p>Cannot write to file <b>{0}</b>.</p>""") + .format(filename)) + return
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/SiteInfo/SiteInfoDialog.ui Sun Feb 28 15:23:42 2016 +0100 @@ -0,0 +1,262 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>SiteInfoDialog</class> + <widget class="QDialog" name="SiteInfoDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>700</width> + <height>550</height> + </rect> + </property> + <property name="windowTitle"> + <string>Site Information</string> + </property> + <property name="sizeGripEnabled"> + <bool>true</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="heading"> + <property name="text"> + <string notr="true"/> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QTabWidget" name="tabWidget"> + <property name="currentIndex"> + <number>0</number> + </property> + <widget class="QWidget" name="generalTab"> + <attribute name="title"> + <string>General</string> + </attribute> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Site Address:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="siteAddressLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Encoding:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="encodingLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QLabel" name="label_9"> + <property name="text"> + <string>Meta tags of site:</string> + </property> + </widget> + </item> + <item> + <widget class="QTreeWidget" name="tagsTree"> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="rootIsDecorated"> + <bool>false</bool> + </property> + <property name="itemsExpandable"> + <bool>false</bool> + </property> + <property name="wordWrap"> + <bool>false</bool> + </property> + <column> + <property name="text"> + <string>Tag</string> + </property> + </column> + <column> + <property name="text"> + <string>Value</string> + </property> + </column> + </widget> + </item> + <item> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="0" column="0" colspan="4"> + <widget class="QLabel" name="label_4"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string><b>Security information</b></string> + </property> + </widget> + </item> + <item row="1" column="0"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="securityLabel"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="1" column="2"> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </widget> + <widget class="QWidget" name="mediaTab"> + <attribute name="title"> + <string>Media</string> + </attribute> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <item> + <widget class="QTreeWidget" name="imagesTree"> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="rootIsDecorated"> + <bool>false</bool> + </property> + <property name="itemsExpandable"> + <bool>false</bool> + </property> + <column> + <property name="text"> + <string>Image</string> + </property> + </column> + <column> + <property name="text"> + <string>Image Address</string> + </property> + </column> + </widget> + </item> + <item> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string><b>Preview</b></string> + </property> + </widget> + </item> + <item> + <widget class="QGraphicsView" name="imagePreview"/> + </item> + </layout> + </widget> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Close</set> + </property> + </widget> + </item> + </layout> + </widget> + <tabstops> + <tabstop>tabWidget</tabstop> + <tabstop>tagsTree</tabstop> + <tabstop>imagesTree</tabstop> + <tabstop>imagePreview</tabstop> + <tabstop>buttonBox</tabstop> + </tabstops> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>SiteInfoDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>SiteInfoDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/SiteInfo/__init__.py Sun Feb 28 15:23:42 2016 +0100 @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2011 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Package implementing the site info widgets. +"""
--- a/WebBrowser/Tools/Scripts.py Sun Feb 28 12:48:12 2016 +0100 +++ b/WebBrowser/Tools/Scripts.py Sun Feb 28 15:23:42 2016 +0100 @@ -158,6 +158,29 @@ return source.format(pos.x(), pos.y()) +def getAllImages(): + """ + Function generating a script to extract all image tags of a web page. + + @return script to extract image tags + @rtype str + """ + source = """ + (function() { + var out = []; + var imgs = document.getElementsByTagName('img'); + for (var i = 0; i < imgs.length; ++i) { + var e = imgs[i]; + out.push({ + src: e.src, + alt: e.alt + }); + } + return out; + })()""" + return source + + def getAllMetaAttributes(): """ Function generating a script to extract all meta attributes of a web page. @@ -174,7 +197,8 @@ out.push({ name: e.getAttribute('name'), content: e.getAttribute('content'), - httpequiv: e.getAttribute('http-equiv') + httpequiv: e.getAttribute('http-equiv'), + charset: e.getAttribute('charset') }); } return out;
--- a/WebBrowser/Tools/WebBrowserTools.py Sun Feb 28 12:48:12 2016 +0100 +++ b/WebBrowser/Tools/WebBrowserTools.py Sun Feb 28 15:23:42 2016 +0100 @@ -16,6 +16,7 @@ import os from PyQt5.QtCore import QFile, QByteArray, QUrl +from PyQt5.QtGui import QPixmap def readAllFileContents(filename): @@ -132,3 +133,19 @@ .replace("<", "")\ .replace(">", "")\ .replace("|", "") + + +def pixmapFromByteArray(data): + """ + Module function to convert a byte array to a pixmap. + + @param data data for the pixmap + @type bytes or QByteArray + @return extracted pixmap + @rtype QPixmap + """ + pixmap = QPixmap() + barray = QByteArray.fromBase64(data) + pixmap.loadFromData(barray) + + return pixmap
--- a/WebBrowser/WebBrowserView.py Sun Feb 28 12:48:12 2016 +0100 +++ b/WebBrowser/WebBrowserView.py Sun Feb 28 15:23:42 2016 +0100 @@ -889,9 +889,8 @@ menu.addAction(self.__mw.findAct) menu.addSeparator() menu.addAction(self.__mw.pageSourceAct) - # TODO: Site Info -## menu.addSeparator() -## menu.addAction(self.__mw.siteInfoAct) + menu.addSeparator() + menu.addAction(self.__mw.siteInfoAct) if self.url().scheme() in ["http", "https"]: menu.addSeparator()
--- a/WebBrowser/WebBrowserWindow.py Sun Feb 28 12:48:12 2016 +0100 +++ b/WebBrowser/WebBrowserWindow.py Sun Feb 28 15:23:42 2016 +0100 @@ -1661,23 +1661,22 @@ self.feedsManagerAct.triggered.connect(self.__showFeedsManager) self.__actions.append(self.feedsManagerAct) - # TODO: Site Info -## self.siteInfoAct = E5Action( -## self.tr('Siteinfo Dialog'), -## UI.PixmapCache.getIcon("helpAbout.png"), -## self.tr('&Siteinfo Dialog...'), -## QKeySequence(self.tr("Ctrl+Shift+I", "Help|Siteinfo Dialog")), -## 0, self, 'webbrowser_siteinfo') -## self.siteInfoAct.setStatusTip(self.tr( -## 'Open a dialog showing some information about the current site.')) -## self.siteInfoAct.setWhatsThis(self.tr( -## """<b>Siteinfo Dialog...</b>""" -## """<p>Opens a dialog showing some information about the current""" -## """ site.</p>""" -## )) -## if not self.__initShortcutsOnly: -## self.siteInfoAct.triggered.connect(self.__showSiteinfoDialog) -## self.__actions.append(self.siteInfoAct) + self.siteInfoAct = E5Action( + self.tr('Siteinfo Dialog'), + UI.PixmapCache.getIcon("helpAbout.png"), + self.tr('&Siteinfo Dialog...'), + QKeySequence(self.tr("Ctrl+Shift+I", "Help|Siteinfo Dialog")), + 0, self, 'webbrowser_siteinfo') + self.siteInfoAct.setStatusTip(self.tr( + 'Open a dialog showing some information about the current site.')) + self.siteInfoAct.setWhatsThis(self.tr( + """<b>Siteinfo Dialog...</b>""" + """<p>Opens a dialog showing some information about the current""" + """ site.</p>""" + )) + if not self.__initShortcutsOnly: + self.siteInfoAct.triggered.connect(self.__showSiteinfoDialog) + self.__actions.append(self.siteInfoAct) # TODO: User Agents ## self.userAgentManagerAct = E5Action( @@ -1887,8 +1886,7 @@ menu = mb.addMenu(self.tr("&Tools")) menu.setTearOffEnabled(True) menu.addAction(self.feedsManagerAct) - # TODO: Site Info -## menu.addAction(self.siteInfoAct) + menu.addAction(self.siteInfoAct) menu.addSeparator() menu.addAction(self.synchronizationAct) ## menu.addSeparator() @@ -1999,8 +1997,7 @@ toolstb.setObjectName("ToolsToolBar") toolstb.setIconSize(UI.Config.ToolBarIconSize) toolstb.addAction(self.feedsManagerAct) - # TODO: SiteInfo -## toolstb.addAction(self.siteInfoAct) + toolstb.addAction(self.siteInfoAct) toolstb.addSeparator() toolstb.addAction(self.synchronizationAct) @@ -3797,15 +3794,14 @@ feedsManager.newUrl.disconnect(self.openUrlNewTab) feedsManager.rejected.disconnect(self.__feedsManagerClosed) - # TODO: Site Info -## def __showSiteinfoDialog(self): -## """ -## Private slot to show the site info dialog. -## """ -## from .SiteInfo.SiteInfoDialog import SiteInfoDialog -## self.__siteinfoDialog = SiteInfoDialog(self.currentBrowser(), self) -## self.__siteinfoDialog.show() -## + def __showSiteinfoDialog(self): + """ + Private slot to show the site info dialog. + """ + from .SiteInfo.SiteInfoDialog import SiteInfoDialog + self.__siteinfoDialog = SiteInfoDialog(self.currentBrowser(), self) + self.__siteinfoDialog.show() + # TODO: User Agents ## @classmethod ## def userAgentsManager(cls):
--- a/eric6.e4p Sun Feb 28 12:48:12 2016 +0100 +++ b/eric6.e4p Sun Feb 28 15:23:42 2016 +0100 @@ -1361,6 +1361,8 @@ <Source>WebBrowser/PersonalInformationManager/PersonalInformationManager.py</Source> <Source>WebBrowser/PersonalInformationManager/__init__.py</Source> <Source>WebBrowser/SearchWidget.py</Source> + <Source>WebBrowser/SiteInfo/SiteInfoDialog.py</Source> + <Source>WebBrowser/SiteInfo/__init__.py</Source> <Source>WebBrowser/Sync/DirectorySyncHandler.py</Source> <Source>WebBrowser/Sync/FtpSyncHandler.py</Source> <Source>WebBrowser/Sync/SyncAssistantDialog.py</Source> @@ -1827,6 +1829,7 @@ <Form>WebBrowser/Passwords/PasswordsDialog.ui</Form> <Form>WebBrowser/PersonalInformationManager/PersonalDataDialog.ui</Form> <Form>WebBrowser/SearchWidget.ui</Form> + <Form>WebBrowser/SiteInfo/SiteInfoDialog.ui</Form> <Form>WebBrowser/Sync/SyncCheckPage.ui</Form> <Form>WebBrowser/Sync/SyncDataPage.ui</Form> <Form>WebBrowser/Sync/SyncDirectorySettingsPage.ui</Form>