Continued porting the web browser. QtWebEngine

Sun, 07 Feb 2016 18:08:48 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sun, 07 Feb 2016 18:08:48 +0100
branch
QtWebEngine
changeset 4726
c26e2a2dc0cb
parent 4725
b19ff70ba509
child 4727
62b50a24fb59

Continued porting the web browser.

- added a web icon loader
- added a web icon provider
- added the zoom manager

Preferences/__init__.py file | annotate | diff | comparison | revisions
WebBrowser/JavaScript/AutoFillJsObject.py file | annotate | diff | comparison | revisions
WebBrowser/Network/FollowRedirectReply.py file | annotate | diff | comparison | revisions
WebBrowser/Network/NetworkManager.py file | annotate | diff | comparison | revisions
WebBrowser/Tools/Scripts.py file | annotate | diff | comparison | revisions
WebBrowser/Tools/WebIconLoader.py file | annotate | diff | comparison | revisions
WebBrowser/Tools/WebIconProvider.py file | annotate | diff | comparison | revisions
WebBrowser/UrlBar/FavIconLabel.py file | annotate | diff | comparison | revisions
WebBrowser/UrlBar/UrlBar.py file | annotate | diff | comparison | revisions
WebBrowser/WebBrowserPage.py file | annotate | diff | comparison | revisions
WebBrowser/WebBrowserTabBar.py file | annotate | diff | comparison | revisions
WebBrowser/WebBrowserTabWidget.py file | annotate | diff | comparison | revisions
WebBrowser/WebBrowserView.py file | annotate | diff | comparison | revisions
WebBrowser/WebBrowserWindow.py file | annotate | diff | comparison | revisions
WebBrowser/ZoomManager/ZoomManager.py file | annotate | diff | comparison | revisions
WebBrowser/ZoomManager/ZoomValuesDialog.py file | annotate | diff | comparison | revisions
WebBrowser/ZoomManager/ZoomValuesDialog.ui file | annotate | diff | comparison | revisions
WebBrowser/ZoomManager/ZoomValuesModel.py file | annotate | diff | comparison | revisions
WebBrowser/ZoomManager/__init__.py file | annotate | diff | comparison | revisions
eric6.e4p file | annotate | diff | comparison | revisions
eric6_browser.py file | annotate | diff | comparison | revisions
icons/default/network-server.png file | annotate | diff | comparison | revisions
--- a/Preferences/__init__.py	Sat Feb 06 17:43:59 2016 +0100
+++ b/Preferences/__init__.py	Sun Feb 07 18:08:48 2016 +0100
@@ -1015,6 +1015,7 @@
         "WarnOnMultipleClose": True,
         "DefaultScheme": "https://",
         "UserStyleSheet": "",
+        "ZoomValuesDB": "{}",       # empty JSON dictionary
     }
     
     @classmethod
--- a/WebBrowser/JavaScript/AutoFillJsObject.py	Sat Feb 06 17:43:59 2016 +0100
+++ b/WebBrowser/JavaScript/AutoFillJsObject.py	Sun Feb 07 18:08:48 2016 +0100
@@ -36,6 +36,15 @@
     def formSubmitted(self, urlStr, userName, password, data):
         """
         Public slot passing form data to the auto fill manager.
+        
+        @param urlStr form submission URL
+        @type str
+        @param userName name of the user
+        @type str
+        @param password user password
+        @type str
+        @param data data to be submitted
+        @type QByteArray
         """
         # TODO: AutoFill
         pass
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebBrowser/Network/FollowRedirectReply.py	Sun Feb 07 18:08:48 2016 +0100
@@ -0,0 +1,114 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2012 - 2016 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a network reply delegate allowing to check redirects.
+"""
+
+from __future__ import unicode_literals
+
+from PyQt5.QtCore import pyqtSignal, QObject
+from PyQt5.QtNetwork import QNetworkRequest
+
+
+class FollowRedirectReply(QObject):
+    """
+    Class implementing a network reply delegate allowing to check redirects.
+    """
+    finished = pyqtSignal()
+    
+    def __init__(self, url, manager, maxRedirects=5):
+        """
+        Constructor
+        
+        @param url URL to get (QUrl)
+        @param manager reference to the network access manager
+            (QNetworkAccessManager)
+        @keyparam maxRedirects maximum allowed redirects (integer)
+        """
+        super(FollowRedirectReply, self).__init__()
+        
+        self.__manager = manager
+        self.__maxRedirects = maxRedirects
+        self.__redirectCount = 0
+        
+        self.__reply = self.__manager.get(QNetworkRequest(url))
+        self.__reply.finished.connect(self.__replyFinished)
+    
+    def reply(self):
+        """
+        Public method to get the reply object.
+        
+        @return reference to the reply object (QNetworkReply)
+        """
+        return self.__reply
+    
+    def originalUrl(self):
+        """
+        Public method to get the original URL.
+        
+        @return original URL (QUrl)
+        """
+        return self.__reply.request().url()
+    
+    def url(self):
+        """
+        Public method to get the final URL (after redirects).
+        
+        @return final URL (QUrl)
+        """
+        return self.__reply.url()
+    
+    def error(self):
+        """
+        Public method to get the error information.
+        
+        @return error code (QNetworkReply.NetworkError)
+        """
+        return self.__reply.error()
+    
+    def errorString(self):
+        """
+        Public method to get the error message.
+        
+        @return error message (string)
+        """
+        return self.__reply.errorString()
+    
+    def readAll(self):
+        """
+        Public method to read all received data.
+        
+        @return received raw data (QByteArray)
+        """
+        return self.__reply.readAll()
+    
+    def close(self):
+        """
+        Public method to close the data stream.
+        """
+        self.__reply.close()
+    
+    def __replyFinished(self):
+        """
+        Private slot handling the receipt of the requested data.
+        """
+        replyStatus = self.__reply.attribute(
+            QNetworkRequest.HttpStatusCodeAttribute)
+        if (replyStatus != 301 and replyStatus != 302) or \
+           self.__redirectCount == self.__maxRedirects:
+            self.finished.emit()
+            return
+        
+        self.__redirectCount += 1
+        
+        redirectUrl = self.__reply.attribute(
+            QNetworkRequest.RedirectionTargetAttribute)
+        self.__reply.close()
+        self.__reply.deleteLater()
+        self.__reply = None
+        
+        self.__reply = self.__manager.get(QNetworkRequest(redirectUrl))
+        self.__reply.finished.connect(self.__replyFinished)
--- a/WebBrowser/Network/NetworkManager.py	Sat Feb 06 17:43:59 2016 +0100
+++ b/WebBrowser/Network/NetworkManager.py	Sun Feb 07 18:08:48 2016 +0100
@@ -7,6 +7,8 @@
 Module implementing a network manager class.
 """
 
+from __future__ import unicode_literals
+
 from PyQt5.QtNetwork import QNetworkAccessManager
 
 from E5Gui import E5MessageBox
@@ -34,7 +36,7 @@
     
     def certificateError(self, error, view):
         """
-        Protected method to handle SSL certificate errors.
+        Public method to handle SSL certificate errors.
         
         @param error object containing the certificate error information
         @type QWebEngineCertificateError
--- a/WebBrowser/Tools/Scripts.py	Sat Feb 06 17:43:59 2016 +0100
+++ b/WebBrowser/Tools/Scripts.py	Sun Feb 07 18:08:48 2016 +0100
@@ -56,6 +56,8 @@
     """
     Function generating a script to set a user style sheet.
     
+    @param css style sheet to be applied
+    @type str
     @return script to set a user style sheet
     @rtype str
     """
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebBrowser/Tools/WebIconLoader.py	Sun Feb 07 18:08:48 2016 +0100
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2016 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing an object to load web site icons.
+"""
+
+from __future__ import unicode_literals
+
+from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject
+from PyQt5.QtGui import QIcon, QPixmap, QImage
+
+from ..Network.FollowRedirectReply import FollowRedirectReply
+
+import WebBrowser.WebBrowserWindow
+
+
+class WebIconLoader(QObject):
+    """
+    Class implementing a loader for web site icons.
+    
+    @signal iconLoaded(icon) emitted when the con has been loaded
+    """
+    iconLoaded = pyqtSignal(QIcon)
+    
+    def __init__(self, url, parent=None):
+        """
+        Constructor
+        
+        @param url URL to fetch the icon from
+        @type QUrl
+        @param parent reference to the parent object
+        @type QObject
+        """
+        super(WebIconLoader, self).__init__(parent)
+        
+        networkManager = \
+            WebBrowser.WebBrowserWindow.WebBrowserWindow.networkManager()
+        self.__reply = FollowRedirectReply(url, networkManager)
+        self.__reply.finished.connect(self.__finished)
+    
+    @pyqtSlot()
+    def __finished(self):
+        """
+        Private slot handling the downloaded icon.
+        """
+        # ignore any errors and emit an empty icon in this case
+        data = self.__reply.readAll()
+        icon = QIcon(QPixmap.fromImage(QImage.fromData(data)))
+        self.iconLoaded.emit(icon)
+        
+        self.__reply.deleteLater()
+        self.__reply = None
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebBrowser/Tools/WebIconProvider.py	Sun Feb 07 18:08:48 2016 +0100
@@ -0,0 +1,208 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2016 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module containing a web site icon storage object.
+"""
+
+from __future__ import unicode_literals
+
+import json
+import os
+
+from PyQt5.QtCore import pyqtSignal, QObject, QByteArray, QBuffer, QIODevice, \
+    QUrl
+from PyQt5.QtGui import QIcon, QPixmap, QImage
+
+from Utilities.AutoSaver import AutoSaver
+
+import UI.PixmapCache
+
+
+class WebIconProvider(QObject):
+    """
+    Class implementing a web site icon storage.
+    """
+    changed = pyqtSignal()
+    
+    def __init__(self, parent=None):
+        """
+        Constructor
+        
+        @param parent reference to the parent object (QObject)
+        """
+        super(WebIconProvider, self).__init__(parent)
+        
+        self.__encoding = "iso-8859-1"
+        self.__iconsFileName = "web_site_icons.json"
+        self.__iconDatabasePath = ""    # saving of icons disabled
+        
+        self.__iconsDB = {}
+        self.__loaded = False
+        
+        self.__saveTimer = AutoSaver(self, self.save)
+        
+        self.changed.connect(self.__saveTimer.changeOccurred)
+    
+    def setIconDatabasePath(self, path):
+        """
+        Public method to set the path for the web site icons store.
+        
+        @param path path to store the icons file to
+        @type str
+        """
+        if path != self.__iconDatabasePath:
+            self.close()
+        
+        self.__iconDatabasePath = path
+    
+    def iconDatabasePath(self):
+        """
+        Public method o get the path for the web site icons store.
+        
+        @return path to store the icons file to
+        @rtype str
+        """
+        return self.__iconDatabasePath
+    
+    def close(self):
+        """
+        Public method to close the web icon provider.
+        """
+        self.__saveTimer.saveIfNeccessary()
+        self.__loaded = False
+        self.__iconsDB = {}
+    
+    def load(self):
+        """
+        Public method to load the bookmarks.
+        """
+        if self.__loaded:
+            return
+        
+        if self.__iconDatabasePath:
+            filename = os.path.join(self.__iconDatabasePath,
+                                    self.__iconsFileName)
+            try:
+                f = open(filename, "r")
+                db = json.load(f)
+                f.close()
+            except (IOError, OSError):
+                # ignore silentyl
+                db = {}
+            
+            self.__iconsDB = {}
+            for url, data in db.items():
+                self.__iconsDB[url] = QIcon(QPixmap.fromImage(QImage.fromData(
+                    QByteArray(data.encode(self.__encoding)))))
+        
+        self.__loaded = True
+    
+    def save(self):
+        """
+        Public method to save the zoom values.
+        """
+        if not self.__loaded:
+            return
+        
+        if self.__iconDatabasePath:
+            db = {}
+            for url, icon in self.__iconsDB.items():
+                ba = QByteArray()
+                buffer = QBuffer(ba)
+                buffer.open(QIODevice.WriteOnly)
+                icon.pixmap(32).toImage().save(buffer, "PNG")
+                db[url] = bytes(buffer.data()).decode(self.__encoding)
+            
+            filename = os.path.join(self.__iconDatabasePath,
+                                    self.__iconsFileName)
+            try:
+                f = open(filename, "w")
+                json.dump(db, f)
+                f.close()
+            except (IOError, OSError):
+                # ignore silentyl
+                pass
+    
+    def saveIcon(self, view):
+        """
+        Public method to save a web site icon.
+        
+        @param view reference to the view object
+        @type WebBrowserView
+        """
+        scheme = view.url().scheme()
+        if scheme in ["eric", "about", "qthelp", "file", "abp", "ftp"]:
+            return
+        
+        self.load()
+        
+        if view.mainWindow().isPrivate():
+            return
+        
+        urlStr = self.__urlToString(view.url())
+        self.__iconsDB[urlStr] = view.icon()
+        
+        self.changed.emit()
+    
+    def __urlToString(self, url):
+        """
+        Private method to convert an URL to a string.
+        
+        @param url URL to be converted
+        @type QUrl
+        @return string representation of the URL
+        @rtype str
+        """
+        return url.toString(QUrl.PrettyDecoded | QUrl.RemoveUserInfo |
+                            QUrl.RemoveFragment)
+    
+    def iconForUrl(self, url):
+        """
+        Public method to get an icon for an URL.
+        
+        @param url URL to get icon for
+        @type QUrl
+        @return icon for the URL
+        @rtype QIcon
+        """
+        scheme = url.scheme()
+        if scheme in ["eric", "about"]:
+            return UI.PixmapCache.getIcon("ericWeb.png")
+        elif scheme == "qthelp":
+            return UI.PixmapCache.getIcon("qthelp.png")
+        elif scheme == "file":
+            return UI.PixmapCache.getIcon("fileMisc.png")
+        elif scheme == "abp":
+            return UI.PixmapCache.getIcon("adBlockPlus.png")
+        elif scheme == "ftp":
+            return UI.PixmapCache.getIcon("network-server.png")
+        
+        self.load()
+        
+        urlStr = self.__urlToString(url)
+        if url in self.__iconsDB:
+            return self.__iconsDB[urlStr]
+        else:
+            return UI.PixmapCache.getIcon("defaultIcon.png")
+
+
+__WebIconProvider = None
+
+
+def instance():
+    """
+    Global function to get a reference to the web icon provider and create it,
+    if it hasn't been yet.
+    
+    @return reference to the web icon provider object
+    @rtype WebIconProvider
+    """
+    global __WebIconProvider
+    
+    if __WebIconProvider is None:
+        __WebIconProvider = WebIconProvider()
+    
+    return __WebIconProvider
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebBrowser/UrlBar/FavIconLabel.py	Sun Feb 07 18:08:48 2016 +0100
@@ -0,0 +1,99 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2010 - 2016 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the label to show the web site icon.
+"""
+
+from __future__ import unicode_literals
+try:
+    str = unicode
+except NameError:
+    pass
+
+from PyQt5.QtCore import Qt, QPoint, QMimeData
+from PyQt5.QtGui import QDrag, QPixmap
+from PyQt5.QtWidgets import QLabel, QApplication
+
+
+class FavIconLabel(QLabel):
+    """
+    Class implementing the label to show the web site icon.
+    """
+    def __init__(self, parent=None):
+        """
+        Constructor
+        
+        @param parent reference to the parent widget (QWidget)
+        """
+        super(FavIconLabel, self).__init__(parent)
+        
+        self.__browser = None
+        self.__dragStartPos = QPoint()
+        
+        self.setFocusPolicy(Qt.NoFocus)
+        self.setCursor(Qt.ArrowCursor)
+        self.setMinimumSize(16, 16)
+        self.resize(16, 16)
+        
+        self.__browserIconChanged()
+    
+    def __browserIconChanged(self):
+        """
+        Private slot to set the icon.
+        """
+        if self.__browser:
+            self.setPixmap(
+                self.__browser.icon().pixmap(16, 16))
+    
+    def __clearIcon(self):
+        """
+        Private slot to clear the icon.
+        """
+        self.setPixmap(QPixmap())
+    
+    def setBrowser(self, browser):
+        """
+        Public method to set the browser connection.
+        
+        @param browser reference to the browser widegt (HelpBrowser)
+        """
+        self.__browser = browser
+        self.__browser.loadFinished.connect(self.__browserIconChanged)
+        self.__browser.iconChanged.connect(self.__browserIconChanged)
+        self.__browser.loadStarted.connect(self.__clearIcon)
+    
+    def mousePressEvent(self, evt):
+        """
+        Protected method to handle mouse press events.
+        
+        @param evt reference to the mouse event (QMouseEvent)
+        """
+        if evt.button() == Qt.LeftButton:
+            self.__dragStartPos = evt.pos()
+        super(FavIconLabel, self).mousePressEvent(evt)
+    
+    def mouseMoveEvent(self, evt):
+        """
+        Protected method to handle mouse move events.
+        
+        @param evt reference to the mouse event (QMouseEvent)
+        """
+        if evt.button() == Qt.LeftButton and \
+           (evt.pos() - self.__dragStartPos).manhattanLength() > \
+                QApplication.startDragDistance() and \
+           self.__browser is not None:
+            drag = QDrag(self)
+            mimeData = QMimeData()
+            title = self.__browser.title()
+            if title == "":
+                title = str(self.__browser.url().toEncoded(), encoding="utf-8")
+            mimeData.setText(title)
+            mimeData.setUrls([self.__browser.url()])
+            p = self.pixmap()
+            if p:
+                drag.setPixmap(p)
+            drag.setMimeData(mimeData)
+            drag.exec_()
--- a/WebBrowser/UrlBar/UrlBar.py	Sat Feb 06 17:43:59 2016 +0100
+++ b/WebBrowser/UrlBar/UrlBar.py	Sun Feb 07 18:08:48 2016 +0100
@@ -27,7 +27,7 @@
 
 import WebBrowser.WebBrowserWindow
 
-##from .FavIconLabel import FavIconLabel
+from .FavIconLabel import FavIconLabel
 ##from .SslLabel import SslLabel
 ##
 import UI.PixmapCache
@@ -60,9 +60,8 @@
 ##        self.__bmInactiveIcon = QIcon(
 ##            self.__bmActiveIcon.pixmap(16, 16, QIcon.Disabled))
         
-        # TODO: FavIcon
-##        self.__favicon = FavIconLabel(self)
-##        self.addWidget(self.__favicon, E5LineEdit.LeftSide)
+        self.__favicon = FavIconLabel(self)
+        self.addWidget(self.__favicon, E5LineEdit.LeftSide)
         
         # TODO: SSL
 ##        self.__sslLabel = SslLabel(self)
@@ -120,8 +119,7 @@
         @param browser reference to the browser widget (WebBrowserView)
         """
         self.__browser = browser
-        # TODO: FavIcon
-##        self.__favicon.setBrowser(browser)
+        self.__favicon.setBrowser(browser)
         
         self.__browser.urlChanged.connect(self.__browserUrlChanged)
         self.__browser.loadProgress.connect(self.update)
--- a/WebBrowser/WebBrowserPage.py	Sat Feb 06 17:43:59 2016 +0100
+++ b/WebBrowser/WebBrowserPage.py	Sun Feb 07 18:08:48 2016 +0100
@@ -201,7 +201,7 @@
     
     def acceptNavigationRequest(self, url, type_, isMainFrame):
         """
-        Protected method to determine, if a request may be accepted.
+        Public method to determine, if a request may be accepted.
         
         @param url URL to navigate to
         @type QUrl
@@ -490,7 +490,7 @@
 ##            .userAgentForUrl(url)
 ##        if agent == "":
 ##            # no agent string specified for the given host -> use global one
-##            agent = Preferences.getHelp("UserAgent")
+##            agent = Preferences.getWebBrowser("UserAgent")
 ##            if agent == "":
 ##                # no global agent string specified -> use default one
 ##                agent = QWebPage.userAgentForUrl(self, url)
@@ -774,7 +774,7 @@
     
     def certificateError(self, error):
         """
-        Protected method to handle SSL certificate errors.
+        Public method to handle SSL certificate errors.
         
         @param error object containing the certificate error information
         @type QWebEngineCertificateError
--- a/WebBrowser/WebBrowserTabBar.py	Sat Feb 06 17:43:59 2016 +0100
+++ b/WebBrowser/WebBrowserTabBar.py	Sun Feb 07 18:08:48 2016 +0100
@@ -86,7 +86,7 @@
         super(WebBrowserTabBar, self).mouseMoveEvent(evt)
         
         # TODO: page preview
-##        if Preferences.getHelp("ShowPreview"):
+##        if Preferences.getWebBrowser("ShowPreview"):
 ##            # Find the tab under the mouse
 ##            i = 0
 ##            tabIndex = -1
@@ -117,7 +117,7 @@
         @param evt reference to the leave event (QEvent)
         """
         # TODO: page preview
-##        if Preferences.getHelp("ShowPreview"):
+##        if Preferences.getWebBrowser("ShowPreview"):
 ##            # If leave tabwidget then hide previous tab preview
 ##            if self.__previewPopup is not None:
 ##                self.__previewPopup.hide()
@@ -132,7 +132,7 @@
         @param evt reference to the mouse press event (QMouseEvent)
         """
         # TODO: page preview
-##        if Preferences.getHelp("ShowPreview"):
+##        if Preferences.getWebBrowser("ShowPreview"):
 ##            if self.__previewPopup is not None:
 ##                self.__previewPopup.hide()
 ##            self.__currentTabPreviewIndex = -1
@@ -151,7 +151,7 @@
         """
         # TODO: page preview
 ##        if evt.type() == QEvent.ToolTip and \
-##           Preferences.getHelp("ShowPreview"):
+##           Preferences.getWebBrowser("ShowPreview"):
 ##            # suppress tool tips if we are showing previews
 ##            evt.setAccepted(True)
 ##            return True
@@ -166,7 +166,7 @@
         """
         pass
         # TODO: page preview
-##        if Preferences.getHelp("ShowPreview"):
+##        if Preferences.getWebBrowser("ShowPreview"):
 ##            if self.__previewPopup is not None:
 ##                self.__previewPopup.hide()
 ##            self.__currentTabPreviewIndex = -1
--- a/WebBrowser/WebBrowserTabWidget.py	Sat Feb 06 17:43:59 2016 +0100
+++ b/WebBrowser/WebBrowserTabWidget.py	Sun Feb 07 18:08:48 2016 +0100
@@ -348,7 +348,7 @@
         browser.forwardAvailable.connect(self.__mainWindow.setForwardAvailable)
         browser.loadStarted.connect(self.__loadStarted)
         browser.loadFinished.connect(self.__loadFinished)
-        browser.iconUrlChanged.connect(self.__iconUrlChanged)
+        browser.iconChanged.connect(self.__iconChanged)
         browser.search.connect(self.newBrowser)
         browser.page().windowCloseRequested.connect(
             self.__windowCloseRequested)
@@ -796,21 +796,18 @@
             
             self.__mainWindow.setLoadingActions(False)
     
-    def __iconUrlChanged(self, url):
+    def __iconChanged(self):
         """
-        Private slot to handle a change of the icon URL.
-        
-        @param url URL of the icon
-        @type QUrl
+        Private slot to handle a change of the web site icon.
         """
         browser = self.sender()
         
         if browser is not None and isinstance(browser, QWidget):
-            import WebBrowser.WebBrowserWindow
             self.setTabIcon(
                 self.indexOf(browser),
-                WebBrowser.WebBrowserWindow.WebBrowserWindow.icon(url))
+                browser.icon())
             # TODO: Bookmarks
+##            import WebBrowser.WebBrowserWindow
 ##            WebBrowser.WebBrowserWindow.WebBrowserWindow.bookmarksManager()\
 ##                .iconChanged(url)
     
--- a/WebBrowser/WebBrowserView.py	Sat Feb 06 17:43:59 2016 +0100
+++ b/WebBrowser/WebBrowserView.py	Sun Feb 07 18:08:48 2016 +0100
@@ -18,7 +18,7 @@
     QUrl, QBuffer, QIODevice, QFileInfo, Qt, QTimer, QEvent, \
     QRect, QFile, QPoint, QByteArray, qVersion
 from PyQt5.QtGui import QDesktopServices, QClipboard, QMouseEvent, QColor, \
-    QPalette
+    QPalette, QIcon
 from PyQt5.QtWidgets import qApp, QStyle, QMenu, QApplication, QInputDialog, \
     QLineEdit, QLabel, QToolTip, QFrame, QDialog
 from PyQt5.QtPrintSupport import QPrinter, QPrintDialog
@@ -30,6 +30,8 @@
 import WebBrowser
 from .WebBrowserPage import WebBrowserPage
 
+from .Tools.WebIconLoader import WebIconLoader
+
 import Preferences
 import UI.PixmapCache
 import Globals
@@ -51,6 +53,7 @@
     @signal highlighted(str) emitted, when the mouse hovers over a link
     @signal search(QUrl) emitted, when a search is requested
     @signal zoomValueChanged(int) emitted to signal a change of the zoom value
+    @signal iconChanged() emitted to signal a changed web site icon
     """
     sourceChanged = pyqtSignal(QUrl)
     forwardAvailable = pyqtSignal(bool)
@@ -58,6 +61,7 @@
     highlighted = pyqtSignal(str)
     search = pyqtSignal(QUrl)
     zoomValueChanged = pyqtSignal(int)
+    iconChanged = pyqtSignal()
     
     ZoomLevels = [
         30, 40, 50, 67, 80, 90,
@@ -90,6 +94,8 @@
         self.__ctrlPressed = False
         self.__isLoading = False
         self.__progress = 0
+        self.__siteIconLoader = None
+        self.__siteIcon = QIcon()
         
         self.__currentZoom = 100
         self.__zoomLevels = WebBrowserView.ZoomLevels[:]
@@ -102,6 +108,7 @@
 ##        self.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks)
 ##        self.linkClicked.connect(self.setSource)
 ##        
+        self.iconUrlChanged.connect(self.__iconUrlChanged)
         self.urlChanged.connect(self.__urlChanged)
 ##        self.statusBarMessage.connect(self.__statusBarMessage)
         self.page().linkHovered.connect(self.__linkHovered)
@@ -125,7 +132,7 @@
         self.setAcceptDrops(True)
         
         # TODO: Access Keys
-##        self.__enableAccessKeys = Preferences.getHelp("AccessKeysEnabled")
+##        self.__enableAccessKeys = Preferences.getWebBrowser("AccessKeysEnabled")
 ##        self.__accessKeysPressed = False
 ##        self.__accessKeyLabels = []
 ##        self.__accessKeyNodes = {}
@@ -223,6 +230,15 @@
 ##        if self.url().toString() == "eric:home":
 ##            self.reload()
     
+    def mainWindow(self):
+        """
+        Public method to get a reference to the main window.
+        
+        @return reference to the main window
+        @rtype WebBrowserWindow
+        """
+        return self.__mw
+    
     # TODO: eliminate requestData, add param to get rid of __ctrlPressed
     def setSource(self, name, requestData=None):
         """
@@ -415,13 +431,12 @@
             zoom manager
         @type bool
         """
-        if value != self.zoomValue():
+        if value != self.__currentZoom:
             self.setZoomFactor(value / 100.0)
             self.__currentZoom = value
-            # TODO: Zoom Manager
-##            if saveValue:
-##                Helpviewer.HelpWindow.HelpWindow.zoomManager().setZoomValue(
-##                    self.url(), value)
+            if saveValue and not self.__mw.isPrivate():
+                from .ZoomManager import ZoomManager
+                ZoomManager.instance().setZoomValue(self.url(), value)
             self.zoomValueChanged.emit(value)
     
     def zoomValue(self):
@@ -481,7 +496,6 @@
         @param callback reference to a function with a bool parameter
         @type function(bool) or None
         """
-        
         findFlags = QWebEnginePage.FindFlags()
         if case:
             findFlags |= QWebEnginePage.FindCaseSensitively
@@ -547,8 +561,8 @@
 ##                UI.PixmapCache.getIcon("mailSend.png"),
 ##                self.tr("Send Link"),
 ##                self.__sendLink).setData(hit.linkUrl())
-##            if Preferences.getHelp("VirusTotalEnabled") and \
-##               Preferences.getHelp("VirusTotalServiceKey") != "":
+##            if Preferences.getWebBrowser("VirusTotalEnabled") and \
+##               Preferences.getWebBrowser("VirusTotalServiceKey") != "":
 ##                menu.addAction(
 ##                    UI.PixmapCache.getIcon("virustotal.png"),
 ##                    self.tr("Scan Link with VirusTotal"),
@@ -580,8 +594,8 @@
 ##                UI.PixmapCache.getIcon("adBlockPlus.png"),
 ##                self.tr("Block Image"), self.__blockImage)\
 ##                .setData(hit.imageUrl().toString())
-##            if Preferences.getHelp("VirusTotalEnabled") and \
-##               Preferences.getHelp("VirusTotalServiceKey") != "":
+##            if Preferences.getWebBrowser("VirusTotalEnabled") and \
+##               Preferences.getWebBrowser("VirusTotalServiceKey") != "":
 ##                menu.addAction(
 ##                    UI.PixmapCache.getIcon("virustotal.png"),
 ##                    self.tr("Scan Image with VirusTotal"),
@@ -1305,7 +1319,7 @@
     
     def eventFilter(self, obj, evt):
         """
-        Protected method to process event for other objects.
+        Public method to process event for other objects.
         
         @param obj reference to object to process events for
         @type QObject
@@ -1377,6 +1391,46 @@
         self.forwardAvailable.emit(self.isForwardAvailable())
         self.backwardAvailable.emit(self.isBackwardAvailable())
     
+    def __iconUrlChanged(self, url):
+        """
+        Private slot to handle the iconUrlChanged signal.
+        
+        @param url URL to get web site icon from
+        @type QUrl
+        """
+        self.__siteIcon = QIcon()
+        if self.__siteIconLoader is not None:
+            self.__siteIconLoader.deleteLater()
+        self.__siteIconLoader = WebIconLoader(url, self)
+        self.__siteIconLoader.iconLoaded.connect(self.__iconLoaded)
+    
+    def __iconLoaded(self, icon):
+        """
+        Private slot handling the loaded web site icon.
+        
+        @param icon web site icon
+        @type QIcon
+        """
+        self.__siteIcon = icon
+        
+        from .Tools import WebIconProvider
+        WebIconProvider.instance().saveIcon(self)
+        
+        self.iconChanged.emit()
+    
+    def icon(self):
+        """
+        Public method to get the web site icon.
+        
+        @return web site icon
+        @rtype QIcon
+        """
+        if not self.__siteIcon.isNull():
+            return QIcon(self.__siteIcon)
+        
+        from .Tools import WebIconProvider
+        return WebIconProvider.instance().iconForUrl(self.url())
+    
 ##    def __statusBarMessage(self, text):
 ##        """
 ##        Private slot to handle the statusBarMessage signal.
@@ -1422,15 +1476,14 @@
         self.__progress = 0
         
         # TODO: ClickToFlash (?)
-##        if Preferences.getHelp("ClickToFlashEnabled"):
+##        if Preferences.getWebBrowser("ClickToFlashEnabled"):
 ##            # this is a hack to make the ClickToFlash button appear
 ##            self.zoomIn()
 ##            self.zoomOut()
         
-        # TODO: Zoom Manager
-##        zoomValue = Helpviewer.HelpWindow.HelpWindow.zoomManager()\
-##            .zoomValue(self.url())
-##        self.setZoomValue(zoomValue)
+        from .ZoomManager import ZoomManager
+        zoomValue = ZoomManager.instance().zoomValue(self.url())
+        self.setZoomValue(zoomValue)
         
         if ok:
             pass
@@ -1794,7 +1847,7 @@
         Public method to indicate a change of the settings.
         """
         # TODO: Access Keys
-##        self.__enableAccessKeys = Preferences.getHelp("AccessKeysEnabled")
+##        self.__enableAccessKeys = Preferences.getWebBrowser("AccessKeysEnabled")
 ##        if not self.__enableAccessKeys:
 ##            self.__hideAccessKeys()
         
--- a/WebBrowser/WebBrowserWindow.py	Sat Feb 06 17:43:59 2016 +0100
+++ b/WebBrowser/WebBrowserWindow.py	Sun Feb 07 18:08:48 2016 +0100
@@ -56,7 +56,9 @@
 ##from .data import javascript_rc     # __IGNORE_WARNING__
 ##
 
-from .Tools import Scripts, WebBrowserTools
+from .Tools import Scripts, WebBrowserTools, WebIconProvider
+
+from .ZoomManager import ZoomManager
 
 
 class WebBrowserWindow(E5MainWindow):
@@ -95,7 +97,6 @@
     _notification = None
 ##    _featurePermissionManager = None
 ##    _flashCookieManager = None
-##    _zoomManager = None
     
     def __init__(self, home, path, parent, name, fromEric=False,
                  initShortcutsOnly=False, searchWord=None,
@@ -178,7 +179,7 @@
 ##            if WebBrowserWindow.UseQtHelp:
 ##                self.__helpEngine = \
 ##                    QHelpEngine(os.path.join(Utilities.getConfigDir(),
-##                                             "browser", "eric6help.qhc"), self)
+##                                             "web_browser", "eric6help.qhc"), self)
 ##                self.__removeOldDocumentation()
 ##                self.__helpEngine.warning.connect(self.__warning)
 ##            else:
@@ -256,7 +257,7 @@
             else:
                 self.restoreGeometry(g)
             
-##            self.__setIconDatabasePath()
+            self.__setIconDatabasePath()
             self.__initWebEngineSettings()
             
             self.__initActions()
@@ -277,7 +278,7 @@
 ##            self.__adBlockIcon = AdBlockIcon(self)
 ##            self.statusBar().addPermanentWidget(self.__adBlockIcon)
 ##            self.__adBlockIcon.setEnabled(
-##                Preferences.getHelp("AdBlockEnabled"))
+##                Preferences.getWebBrowser("AdBlockEnabled"))
 ##            self.__tabWidget.currentChanged[int].connect(
 ##                self.__adBlockIcon.currentChanged)
 ##            self.__tabWidget.sourceChanged.connect(
@@ -353,24 +354,28 @@
         """
         Public method to check, if the web browser was called from within the
         eric IDE.
+        
+        @return flag indicating that the browserw as opened from within eric
+        @rtype bool
         """
         return self.__fromEric
     
-##    def __setIconDatabasePath(self, enable=True):
-##        """
-##        Private method to set the favicons path.
-##        
-##        @param enable flag indicating to enabled icon storage (boolean)
-##        """
-##        if enable:
-##            iconDatabasePath = os.path.join(Utilities.getConfigDir(),
-##                                            "browser", "favicons")
-##            if not os.path.exists(iconDatabasePath):
-##                os.makedirs(iconDatabasePath)
-##        else:
-##            iconDatabasePath = ""   # setting an empty path disables it
-##        QWebSettings.setIconDatabasePath(iconDatabasePath)
-##        
+    def __setIconDatabasePath(self, enable=True):
+        """
+        Private method to set the favicons path.
+        
+        @param enable flag indicating to enabled icon storage (boolean)
+        """
+        if enable:
+            iconDatabasePath = os.path.join(Utilities.getConfigDir(),
+                                            "web_browser", "favicons")
+            if not os.path.exists(iconDatabasePath):
+                os.makedirs(iconDatabasePath)
+        else:
+            iconDatabasePath = ""   # setting an empty path disables it
+        
+        WebIconProvider.instance().setIconDatabasePath(iconDatabasePath)
+        
     def __initWebEngineSettings(self):
         """
         Private method to set the global web settings.
@@ -396,7 +401,7 @@
             QWebEngineSettings.MinimumLogicalFontSize,
             Preferences.getWebBrowser("MinimumLogicalFontSize"))
         
-        styleSheet = Preferences.getHelp("UserStyleSheet")
+        styleSheet = Preferences.getWebBrowser("UserStyleSheet")
         self.__setUserStyleSheet(styleSheet)
         
         settings.setAttribute(
@@ -404,7 +409,7 @@
             Preferences.getWebBrowser("AutoLoadImages"))
 ##        settings.setAttribute(
 ##            QWebSettings.JavaEnabled,
-##            Preferences.getHelp("JavaEnabled"))
+##            Preferences.getWebBrowser("JavaEnabled"))
         settings.setAttribute(
             QWebEngineSettings.JavascriptEnabled,
             Preferences.getWebBrowser("JavaScriptEnabled"))
@@ -416,37 +421,37 @@
             Preferences.getWebBrowser("JavaScriptCanAccessClipboard"))
 ##        settings.setAttribute(
 ##            QWebSettings.PluginsEnabled,
-##            Preferences.getHelp("PluginsEnabled"))
+##            Preferences.getWebBrowser("PluginsEnabled"))
         
 ##        if hasattr(QWebSettings, "PrintElementBackgrounds"):
 ##            settings.setAttribute(
 ##                QWebSettings.PrintElementBackgrounds,
-##                Preferences.getHelp("PrintBackgrounds"))
+##                Preferences.getWebBrowser("PrintBackgrounds"))
 ##        
 ##        if hasattr(QWebSettings, "setOfflineStoragePath"):
 ##            settings.setAttribute(
 ##                QWebSettings.OfflineStorageDatabaseEnabled,
-##                Preferences.getHelp("OfflineStorageDatabaseEnabled"))
+##                Preferences.getWebBrowser("OfflineStorageDatabaseEnabled"))
 ##            webDatabaseDir = os.path.join(
-##                Utilities.getConfigDir(), "browser", "webdatabases")
+##                Utilities.getConfigDir(), "web_browser", "webdatabases")
 ##            if not os.path.exists(webDatabaseDir):
 ##                os.makedirs(webDatabaseDir)
 ##            settings.setOfflineStoragePath(webDatabaseDir)
 ##            settings.setOfflineStorageDefaultQuota(
-##                Preferences.getHelp("OfflineStorageDatabaseQuota") *
+##                Preferences.getWebBrowser("OfflineStorageDatabaseQuota") *
 ##                1024 * 1024)
 ##        
 ##        if hasattr(QWebSettings, "OfflineWebApplicationCacheEnabled"):
 ##            settings.setAttribute(
 ##                QWebSettings.OfflineWebApplicationCacheEnabled,
-##                Preferences.getHelp("OfflineWebApplicationCacheEnabled"))
+##                Preferences.getWebBrowser("OfflineWebApplicationCacheEnabled"))
 ##            appCacheDir = os.path.join(
-##                Utilities.getConfigDir(), "browser", "webappcaches")
+##                Utilities.getConfigDir(), "web_browser", "webappcaches")
 ##            if not os.path.exists(appCacheDir):
 ##                os.makedirs(appCacheDir)
 ##            settings.setOfflineWebApplicationCachePath(appCacheDir)
 ##            settings.setOfflineWebApplicationCacheQuota(
-##                Preferences.getHelp("OfflineWebApplicationCacheQuota") *
+##                Preferences.getWebBrowser("OfflineWebApplicationCacheQuota") *
 ##                1024 * 1024)
 ##        
         if self.isPrivate():
@@ -457,7 +462,7 @@
                 QWebEngineSettings.LocalStorageEnabled,
                 Preferences.getWebBrowser("LocalStorageEnabled"))
 ##        localStorageDir = os.path.join(
-##            Utilities.getConfigDir(), "browser", "weblocalstorage")
+##            Utilities.getConfigDir(), "web_browser", "weblocalstorage")
 ##        if not os.path.exists(localStorageDir):
 ##            os.makedirs(localStorageDir)
 ##        settings.setLocalStoragePath(localStorageDir)
@@ -465,7 +470,7 @@
 ##        if hasattr(QWebSettings, "DnsPrefetchEnabled"):
 ##            settings.setAttribute(
 ##                QWebSettings.DnsPrefetchEnabled,
-##                Preferences.getHelp("DnsPrefetchEnabled"))
+##                Preferences.getWebBrowser("DnsPrefetchEnabled"))
 ##        
         settings.setDefaultTextEncoding(
             Preferences.getWebBrowser("DefaultTextEncoding"))
@@ -488,7 +493,7 @@
 ##        if hasattr(QWebSettings, "SiteSpecificQuirksEnabled"):
 ##            settings.setAttribute(
 ##                QWebSettings.SiteSpecificQuirksEnabled,
-##                Preferences.getHelp("SiteSpecificQuirksEnabled"))
+##                Preferences.getWebBrowser("SiteSpecificQuirksEnabled"))
 ##        
 ##        QWebSecurityOrigin.addLocalScheme("eric")
         settings.setAttribute(
@@ -1639,27 +1644,27 @@
 ##                self.__showSyncDialog)
 ##        self.__actions.append(self.synchronizationAct)
         
-        # TODO: Zoom Manager
-##        self.zoomValuesAct = E5Action(
-##            self.tr('Manage Saved Zoom Values'),
-##            UI.PixmapCache.getIcon("zoomReset.png"),
-##            self.tr('Manage Saved Zoom Values...'),
-##            0, 0,
-##            self, 'webbrowser_manage_zoom_values')
-##        self.zoomValuesAct.setStatusTip(self.tr(
-##            'Manage the saved zoom values'))
-##        self.zoomValuesAct.setWhatsThis(self.tr(
-##            """<b>Manage Saved Zoom Values...</b>"""
-##            """<p>Opens a dialog to manage the saved zoom values.</p>"""
-##        ))
-##        if not self.__initShortcutsOnly:
-##            self.zoomValuesAct.triggered.connect(self.__showZoomValuesDialog)
-##        self.__actions.append(self.zoomValuesAct)
+        self.zoomValuesAct = E5Action(
+            self.tr('Manage Saved Zoom Values'),
+            UI.PixmapCache.getIcon("zoomReset.png"),
+            self.tr('Manage Saved Zoom Values...'),
+            0, 0,
+            self, 'webbrowser_manage_zoom_values')
+        self.zoomValuesAct.setStatusTip(self.tr(
+            'Manage the saved zoom values'))
+        self.zoomValuesAct.setWhatsThis(self.tr(
+            """<b>Manage Saved Zoom Values...</b>"""
+            """<p>Opens a dialog to manage the saved zoom values.</p>"""
+        ))
+        if not self.__initShortcutsOnly:
+            self.zoomValuesAct.triggered.connect(self.__showZoomValuesDialog)
+        self.__actions.append(self.zoomValuesAct)
         
         self.backAct.setEnabled(False)
         self.forwardAct.setEnabled(False)
         
         # now read the keyboard shortcuts for the actions
+        # TODO: change this to webBrowser
         Shortcuts.readShortcuts(helpViewer=self)
     
     def getActions(self):
@@ -1782,8 +1787,8 @@
 ##        if SSL_AVAILABLE:
 ##            menu.addAction(self.certificatesAct)
 ##        menu.addSeparator()
-##        menu.addAction(self.zoomValuesAct)
-##        menu.addSeparator()
+        menu.addAction(self.zoomValuesAct)
+        menu.addSeparator()
 ##        menu.addAction(self.adblockAct)
 ##        menu.addAction(self.flashblockAct)
 ##        menu.addSeparator()
@@ -1991,8 +1996,8 @@
 ##            UI.PixmapCache.getIcon("virustotal.png"),
 ##            self.tr("Domain Report"),
 ##            self.__virusTotalDomainReport)
-##        if not Preferences.getHelp("VirusTotalEnabled") or \
-##           Preferences.getHelp("VirusTotalServiceKey") == "":
+##        if not Preferences.getWebBrowser("VirusTotalEnabled") or \
+##           Preferences.getWebBrowser("VirusTotalServiceKey") == "":
 ##            self.virustotalScanCurrentAct.setEnabled(False)
 ##            self.virustotalIpReportAct.setEnabled(False)
 ##            self.virustotalDomainReportAct.setEnabled(False)
@@ -2356,6 +2361,10 @@
 ##        self.speedDial().close()
 ##        
 ##        self.syncManager().close()
+        
+        ZoomManager.instance().close()
+        
+        WebIconProvider.instance().close()
 ##        
 ##        self.__virusTotal.close()
 ##        
@@ -2375,7 +2384,7 @@
         self.__tabWidget.closeAllBrowsers()
         
         state = self.saveState()
-        Preferences.setWebBrowser("HelpViewerState", state)
+        Preferences.setWebBrowser("WebBrowserState", state)
 
         if Preferences.getWebBrowser("SaveGeometry"):
             if not self.__isFullScreen():
@@ -2631,8 +2640,8 @@
 ##        self.searchEdit.preferencesChanged()
 ##        
 ##        self.__virusTotal.preferencesChanged()
-##        if not Preferences.getHelp("VirusTotalEnabled") or \
-##           Preferences.getHelp("VirusTotalServiceKey") == "":
+##        if not Preferences.getWebBrowser("VirusTotalEnabled") or \
+##           Preferences.getWebBrowser("VirusTotalServiceKey") == "":
 ##            self.virustotalScanCurrentAct.setEnabled(False)
 ##            self.virustotalIpReportAct.setEnabled(False)
 ##            self.virustotalDomainReportAct.setEnabled(False)
@@ -2713,7 +2722,7 @@
 ##            if cls._helpEngine is None:
 ##                cls._helpEngine = \
 ##                    QHelpEngine(os.path.join(Utilities.getConfigDir(),
-##                                             "browser", "eric6help.qhc"))
+##                                             "web_browser", "eric6help.qhc"))
 ##            return cls._helpEngine
 ##        else:
 ##            return None
@@ -3156,7 +3165,7 @@
 ##                    "{0}/flashplayer/help/settings_manager07.html".format(
 ##                        langCode))
 ##            if zoomValues:
-##                self.zoomManager().clear()
+##                ZoomManager.instance().clear()
 ##        
 ##    def __showEnginesConfigurationDialog(self):
 ##        """
@@ -3225,15 +3234,15 @@
 ##        """
 ##        self.featurePermissionManager().showFeaturePermissionsDialog()
 ##        
-##    def __showZoomValuesDialog(self):
-##        """
-##        Private slot to show the zoom values management dialog.
-##        """
-##        from .ZoomManager.ZoomValuesDialog import ZoomValuesDialog
-##        
-##        dlg = ZoomValuesDialog(self)
-##        dlg.exec_()
-##        
+    def __showZoomValuesDialog(self):
+        """
+        Private slot to show the zoom values management dialog.
+        """
+        from .ZoomManager.ZoomValuesDialog import ZoomValuesDialog
+        
+        dlg = ZoomValuesDialog(self)
+        dlg.exec_()
+        
 ##    def __showNetworkMonitor(self):
 ##        """
 ##        Private slot to show the network monitor dialog.
@@ -3481,20 +3490,6 @@
 ##        
 ##        return cls._flashCookieManager
 ##        
-##    @classmethod
-##    def zoomManager(cls):
-##        """
-##        Class method to get a reference to the zoom values manager.
-##        
-##        @return reference to the zoom values manager
-##        @rtype ZoomManager
-##        """
-##        if cls._zoomManager is None:
-##            from .ZoomManager.ZoomManager import ZoomManager
-##            cls._zoomManager = ZoomManager()
-##        
-##        return cls._zoomManager
-##        
     @classmethod
     def mainWindow(cls):
         """
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebBrowser/ZoomManager/ZoomManager.py	Sun Feb 07 18:08:48 2016 +0100
@@ -0,0 +1,228 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2015 - 2016 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a manager for site specific zoom level settings.
+"""
+
+from __future__ import unicode_literals
+
+import json
+
+from PyQt5.QtCore import pyqtSignal, QObject
+
+from Utilities.AutoSaver import AutoSaver
+import Preferences
+
+
+class ZoomManager(QObject):
+    """
+    Class implementing a manager for site specific zoom level settings.
+    """
+    changed = pyqtSignal()
+    
+    def __init__(self, parent=None):
+        """
+        Constructor
+        
+        @param parent reference to the parent object (QObject)
+        """
+        super(ZoomManager, self).__init__(parent)
+        
+        self.__zoomDB = {}
+        
+        self.__saveTimer = AutoSaver(self, self.save)
+        
+        self.changed.connect(self.__saveTimer.changeOccurred)
+        
+        self.__loaded = False
+    
+    def close(self):
+        """
+        Public method to close the zoom manager.
+        """
+        self.__saveTimer.saveIfNeccessary()
+    
+    def load(self):
+        """
+        Public method to load the bookmarks.
+        """
+        if self.__loaded:
+            return
+        
+        dbString = Preferences.getWebBrowser("ZoomValuesDB")
+        if dbString:
+            try:
+                db = json.loads(dbString)
+                self.__zoomDB = db
+            except ValueError:
+                # ignore silently
+                pass
+        
+        self.__loaded = True
+    
+    def save(self):
+        """
+        Public method to save the zoom values.
+        """
+        if not self.__loaded:
+            return
+        
+        dbString = json.dumps(self.__zoomDB)
+        Preferences.setWebBrowser("ZoomValuesDB", dbString)
+    
+    def __keyFromUrl(self, url):
+        """
+        Private method to generate a DB key for an URL.
+        
+        @param url URL to generate a key for
+        @type QUrl
+        @return key for the given URL
+        @rtype str
+        """
+        if url.isEmpty():
+            key = ""
+        else:
+            scheme = url.scheme()
+            host = url.host()
+            if host:
+                key = host
+            elif scheme == "file":
+                path = url.path()
+                key = path.rsplit("/", 1)[0]
+            else:
+                key = ""
+        
+        return key
+    
+    def setZoomValue(self, url, zoomValue):
+        """
+        Public method to record the zoom value for the given URL.
+        
+        Note: Only zoom values not equal 100% are recorded.
+        
+        @param url URL of the page to remember the zoom value for
+        @type QUrl
+        @param zoomValue zoom value for the URL
+        @type int
+        """
+        self.load()
+        
+        key = self.__keyFromUrl(url)
+        if not key:
+            return
+        
+        if ((zoomValue == 100 and key not in self.__zoomDB) or
+                (key in self.__zoomDB and self.__zoomDB[key] == zoomValue)):
+            return
+        
+        if zoomValue == 100:
+            del self.__zoomDB[key]
+        else:
+            self.__zoomDB[key] = zoomValue
+        
+        self.changed.emit()
+    
+    def zoomValue(self, url):
+        """
+        Public method to get the zoom value for an URL.
+        
+        @param url URL of the page to get the zoom value for
+        @type QUrl
+        @return zoomValue zoom value for the URL
+        @rtype int
+        """
+        self.load()
+        
+        key = self.__keyFromUrl(url)
+        if not key:
+            zoom = 100
+        
+        if key in self.__zoomDB:
+            zoom = self.__zoomDB[key]
+        else:
+            # default zoom value (i.e. no zoom)
+            zoom = 100
+        
+        return zoom
+    
+    def clear(self):
+        """
+        Public method to clear the saved zoom values.
+        """
+        self.__zoomDB = {}
+        self.__loaded = True
+        
+        self.changed.emit()
+    
+    def removeZoomValue(self, site):
+        """
+        Public method to remove a zoom value entry.
+        
+        @param site web site name
+        @type str
+        """
+        self.load()
+        
+        if site in self.__zoomDB:
+            del self.__zoomDB[site]
+            self.changed.emit()
+    
+    def allSiteNames(self):
+        """
+        Public method to get a list of all site names.
+        
+        @return sorted list of all site names
+        @rtype list of str
+        """
+        self.load()
+        
+        return sorted(self.__zoomDB.keys())
+    
+    def sitesCount(self):
+        """
+        Public method to get the number of available sites.
+        
+        @return number of sites
+        @rtype int
+        """
+        self.load()
+        
+        return len(self.__zoomDB)
+    
+    def siteInfo(self, site):
+        """
+        Public method to get the zoom value for the site.
+        
+        @param site web site name
+        @type str
+        @return zoom value for the site
+        @rtype int
+        """
+        self.load()
+        
+        if site not in self.__zoomDB:
+            return None
+        
+        return self.__zoomDB[site]
+
+
+__ZoomManager = None
+
+
+def instance():
+    """
+    Global function to get a reference to the zoom manager and create it, if
+    it hasn't been yet.
+    
+    @return reference to the zoom manager object
+    @rtype ZoomManager
+    """
+    global __ZoomManager
+    
+    if __ZoomManager is None:
+        __ZoomManager = ZoomManager()
+    
+    return __ZoomManager
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebBrowser/ZoomManager/ZoomValuesDialog.py	Sun Feb 07 18:08:48 2016 +0100
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2015 - 2016 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a dialog to show all saved zoom values.
+"""
+
+from __future__ import unicode_literals
+
+from PyQt5.QtCore import QSortFilterProxyModel
+from PyQt5.QtGui import QFont, QFontMetrics
+from PyQt5.QtWidgets import QDialog
+
+from .Ui_ZoomValuesDialog import Ui_ZoomValuesDialog
+
+
+class ZoomValuesDialog(QDialog, Ui_ZoomValuesDialog):
+    """
+    Class implementing a dialog to show all saved zoom values.
+    """
+    def __init__(self, parent=None):
+        """
+        Constructor
+        
+        @param parent reference to the parent widget (QWidget)
+        """
+        super(ZoomValuesDialog, self).__init__(parent)
+        self.setupUi(self)
+        
+        self.removeButton.clicked.connect(
+            self.zoomValuesTable.removeSelected)
+        self.removeAllButton.clicked.connect(self.zoomValuesTable.removeAll)
+        
+        from . import ZoomManager
+        from .ZoomValuesModel import ZoomValuesModel
+        
+        self.zoomValuesTable.verticalHeader().hide()
+        self.__zoomValuesModel = ZoomValuesModel(
+            ZoomManager.instance(), self)
+        self.__proxyModel = QSortFilterProxyModel(self)
+        self.__proxyModel.setSourceModel(self.__zoomValuesModel)
+        self.searchEdit.textChanged.connect(
+            self.__proxyModel.setFilterFixedString)
+        self.zoomValuesTable.setModel(self.__proxyModel)
+        
+        fm = QFontMetrics(QFont())
+        height = fm.height() + fm.height() // 3
+        self.zoomValuesTable.verticalHeader().setDefaultSectionSize(height)
+        self.zoomValuesTable.verticalHeader().setMinimumSectionSize(-1)
+        
+        self.__calculateHeaderSizes()
+    
+    def __calculateHeaderSizes(self):
+        """
+        Private method to calculate the section sizes of the horizontal header.
+        """
+        fm = QFontMetrics(QFont())
+        for section in range(self.__zoomValuesModel.columnCount()):
+            header = self.zoomValuesTable.horizontalHeader()\
+                .sectionSizeHint(section)
+            if section == 0:
+                header = fm.width("extraveryveryverylongsitename")
+            elif section == 1:
+                header = fm.width("averagelongzoomvalue")
+            buffer = fm.width("mm")
+            header += buffer
+            self.zoomValuesTable.horizontalHeader()\
+                .resizeSection(section, header)
+        self.zoomValuesTable.horizontalHeader().setStretchLastSection(True)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebBrowser/ZoomManager/ZoomValuesDialog.ui	Sun Feb 07 18:08:48 2016 +0100
@@ -0,0 +1,191 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ZoomValuesDialog</class>
+ <widget class="QDialog" name="ZoomValuesDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>500</width>
+    <height>350</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Saved Zoom Values</string>
+  </property>
+  <property name="sizeGripEnabled">
+   <bool>true</bool>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout_2">
+     <item>
+      <spacer name="horizontalSpacer">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>40</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item>
+      <layout class="QHBoxLayout" name="horizontalLayout">
+       <property name="spacing">
+        <number>0</number>
+       </property>
+       <item>
+        <widget class="E5ClearableLineEdit" name="searchEdit">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="minimumSize">
+          <size>
+           <width>300</width>
+           <height>0</height>
+          </size>
+         </property>
+         <property name="toolTip">
+          <string>Enter search term</string>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="E5TableView" name="zoomValuesTable">
+     <property name="alternatingRowColors">
+      <bool>true</bool>
+     </property>
+     <property name="selectionBehavior">
+      <enum>QAbstractItemView::SelectRows</enum>
+     </property>
+     <property name="textElideMode">
+      <enum>Qt::ElideMiddle</enum>
+     </property>
+     <property name="showGrid">
+      <bool>false</bool>
+     </property>
+     <property name="sortingEnabled">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout_3">
+     <item>
+      <widget class="QPushButton" name="removeButton">
+       <property name="toolTip">
+        <string>Press to remove the selected entries</string>
+       </property>
+       <property name="text">
+        <string>&amp;Remove</string>
+       </property>
+       <property name="autoDefault">
+        <bool>false</bool>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="removeAllButton">
+       <property name="toolTip">
+        <string>Press to remove all entries</string>
+       </property>
+       <property name="text">
+        <string>Remove &amp;All</string>
+       </property>
+       <property name="autoDefault">
+        <bool>false</bool>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <spacer name="horizontalSpacer_2">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>208</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+    </layout>
+   </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>
+ <customwidgets>
+  <customwidget>
+   <class>E5ClearableLineEdit</class>
+   <extends>QLineEdit</extends>
+   <header>E5Gui/E5LineEdit.h</header>
+  </customwidget>
+  <customwidget>
+   <class>E5TableView</class>
+   <extends>QTableView</extends>
+   <header>E5Gui/E5TableView.h</header>
+  </customwidget>
+ </customwidgets>
+ <tabstops>
+  <tabstop>searchEdit</tabstop>
+  <tabstop>zoomValuesTable</tabstop>
+  <tabstop>removeButton</tabstop>
+  <tabstop>removeAllButton</tabstop>
+  <tabstop>buttonBox</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>ZoomValuesDialog</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>237</x>
+     <y>340</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>ZoomValuesDialog</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>325</x>
+     <y>340</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/ZoomManager/ZoomValuesModel.py	Sun Feb 07 18:08:48 2016 +0100
@@ -0,0 +1,129 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2015 - 2016 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a model for zoom values management.
+"""
+
+from __future__ import unicode_literals
+
+from PyQt5.QtCore import Qt, QModelIndex, QAbstractTableModel
+
+
+class ZoomValuesModel(QAbstractTableModel):
+    """
+    Class implementing a model for zoom values management.
+    """
+    def __init__(self, manager, parent=None):
+        """
+        Constructor
+        
+        @param manager reference to the zoom values manager (ZoomManager)
+        @param parent reference to the parent object (QObject)
+        """
+        super(ZoomValuesModel, self).__init__(parent)
+        
+        self.__manager = manager
+        manager.changed.connect(self.__zoomValuesChanged)
+        
+        self.__headers = [
+            self.tr("Website"),
+            self.tr("Zoom Value [%]"),
+        ]
+    
+    def __zoomValuesChanged(self):
+        """
+        Private slot handling a change of the registered zoom values.
+        """
+        self.beginResetModel()
+        self.endResetModel()
+    
+    def removeRows(self, row, count, parent=QModelIndex()):
+        """
+        Public method to remove entries from the model.
+        
+        @param row start row (integer)
+        @param count number of rows to remove (integer)
+        @param parent parent index (QModelIndex)
+        @return flag indicating success (boolean)
+        """
+        if parent.isValid():
+            return False
+        
+        if count <= 0:
+            return False
+        
+        lastRow = row + count - 1
+        
+        self.beginRemoveRows(parent, row, lastRow)
+        
+        siteList = self.__manager.allSiteNames()
+        for index in range(row, lastRow + 1):
+            self.__manager.removeZoomValue(siteList[index])
+        
+        return True
+    
+    def rowCount(self, parent=QModelIndex()):
+        """
+        Public method to get the number of rows of the model.
+        
+        @param parent parent index (QModelIndex)
+        @return number of rows (integer)
+        """
+        if parent.isValid():
+            return 0
+        else:
+            return self.__manager.sitesCount()
+    
+    def columnCount(self, parent=QModelIndex()):
+        """
+        Public method to get the number of columns of the model.
+        
+        @param parent parent index (QModelIndex)
+        @return number of columns (integer)
+        """
+        return len(self.__headers)
+    
+    def data(self, index, role):
+        """
+        Public method to get data from the model.
+        
+        @param index index to get data for (QModelIndex)
+        @param role role of the data to retrieve (integer)
+        @return requested data
+        """
+        if index.row() >= self.__manager.sitesCount() or index.row() < 0:
+            return None
+        
+        site = self.__manager.allSiteNames()[index.row()]
+        siteInfo = self.__manager.siteInfo(site)
+        
+        if siteInfo is None:
+            return None
+        
+        if role == Qt.DisplayRole:
+            if index.column() == 0:
+                return site
+            elif index.column() == 1:
+                return siteInfo
+        
+        return None
+    
+    def headerData(self, section, orientation, role=Qt.DisplayRole):
+        """
+        Public method to get the header data.
+        
+        @param section section number (integer)
+        @param orientation header orientation (Qt.Orientation)
+        @param role data role (integer)
+        @return header data
+        """
+        if orientation == Qt.Horizontal and role == Qt.DisplayRole:
+            try:
+                return self.__headers[section]
+            except IndexError:
+                pass
+        
+        return None
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebBrowser/ZoomManager/__init__.py	Sun Feb 07 18:08:48 2016 +0100
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2015 - 2016 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Package implementing a manager for site specific zoom level settings.
+"""
--- a/eric6.e4p	Sat Feb 06 17:43:59 2016 +0100
+++ b/eric6.e4p	Sun Feb 07 18:08:48 2016 +0100
@@ -1267,12 +1267,16 @@
     <Source>WebBrowser/JavaScript/AutoFillJsObject.py</Source>
     <Source>WebBrowser/JavaScript/ExternalJsObject.py</Source>
     <Source>WebBrowser/JavaScript/__init__.py</Source>
+    <Source>WebBrowser/Network/FollowRedirectReply.py</Source>
     <Source>WebBrowser/Network/NetworkManager.py</Source>
     <Source>WebBrowser/Network/__init__.py</Source>
     <Source>WebBrowser/SearchWidget.py</Source>
     <Source>WebBrowser/Tools/Scripts.py</Source>
     <Source>WebBrowser/Tools/WebBrowserTools.py</Source>
+    <Source>WebBrowser/Tools/WebIconLoader.py</Source>
+    <Source>WebBrowser/Tools/WebIconProvider.py</Source>
     <Source>WebBrowser/Tools/__init__.py</Source>
+    <Source>WebBrowser/UrlBar/FavIconLabel.py</Source>
     <Source>WebBrowser/UrlBar/StackedUrlBar.py</Source>
     <Source>WebBrowser/UrlBar/UrlBar.py</Source>
     <Source>WebBrowser/UrlBar/__init__.py</Source>
@@ -1281,6 +1285,10 @@
     <Source>WebBrowser/WebBrowserTabWidget.py</Source>
     <Source>WebBrowser/WebBrowserView.py</Source>
     <Source>WebBrowser/WebBrowserWindow.py</Source>
+    <Source>WebBrowser/ZoomManager/ZoomManager.py</Source>
+    <Source>WebBrowser/ZoomManager/ZoomValuesDialog.py</Source>
+    <Source>WebBrowser/ZoomManager/ZoomValuesModel.py</Source>
+    <Source>WebBrowser/ZoomManager/__init__.py</Source>
     <Source>WebBrowser/__init__.py</Source>
     <Source>WebBrowser/data/javascript_rc.py</Source>
     <Source>__init__.py</Source>
@@ -1681,6 +1689,7 @@
     <Form>VCS/RepositoryInfoDialog.ui</Form>
     <Form>ViewManager/BookmarkedFilesDialog.ui</Form>
     <Form>WebBrowser/SearchWidget.ui</Form>
+    <Form>WebBrowser/ZoomManager/ZoomValuesDialog.ui</Form>
   </Forms>
   <Translations>
     <Translation>i18n/eric6_cs.qm</Translation>
--- a/eric6_browser.py	Sat Feb 06 17:43:59 2016 +0100
+++ b/eric6_browser.py	Sun Feb 07 18:08:48 2016 +0100
@@ -31,11 +31,20 @@
 import os
 
 # TODO: adjust this when done
-MIN_QT_VERSION = "5.5.0"
+MIN_QT_VERSION = "5.6.0"
 
 from PyQt5.QtCore import qVersion
 if qVersion() < MIN_QT_VERSION:
-    print("You need at least Qt Version {0} to execute the web browser."
+    try:    # Py2
+        import tkMessageBox as messagebox
+    except ImportError:
+        try:    # Py3
+            from tkinter import messagebox
+        except ImportError:
+            sys.exit(100)
+    messagebox.showerror(
+        "eric6 Error",
+        "You need at least Qt Version {0} to execute the web browser."
           .format(MIN_QT_VERSION))
     sys.exit(100)
 
Binary file icons/default/network-server.png has changed

eric ide

mercurial