Continued porting the web browser. QtWebEngine

Tue, 23 Feb 2016 20:00:40 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Tue, 23 Feb 2016 20:00:40 +0100
branch
QtWebEngine
changeset 4768
57da9217196b
parent 4767
0bace7c5ebc9
child 4769
2b6f7e026cdc

Continued porting the web browser.

- started adding the Downloads stuff

WebBrowser/Download/DownloadAskActionDialog.py file | annotate | diff | comparison | revisions
WebBrowser/Download/DownloadAskActionDialog.ui file | annotate | diff | comparison | revisions
WebBrowser/Download/DownloadItem.py file | annotate | diff | comparison | revisions
WebBrowser/Download/DownloadItem.ui file | annotate | diff | comparison | revisions
WebBrowser/Download/DownloadManager.py file | annotate | diff | comparison | revisions
WebBrowser/Download/DownloadManager.ui file | annotate | diff | comparison | revisions
WebBrowser/Download/DownloadModel.py file | annotate | diff | comparison | revisions
WebBrowser/Download/DownloadUtilities.py file | annotate | diff | comparison | revisions
WebBrowser/Download/__init__.py file | annotate | diff | comparison | revisions
WebBrowser/FlashCookieManager/FlashCookieManagerDialog.py file | annotate | diff | comparison | revisions
WebBrowser/OpenSearch/OpenSearchManager.py file | annotate | diff | comparison | revisions
WebBrowser/VirusTotal/VirusTotalApi.py file | annotate | diff | comparison | revisions
WebBrowser/WebBrowserPage.py file | annotate | diff | comparison | revisions
eric6.e4p file | annotate | diff | comparison | revisions
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebBrowser/Download/DownloadAskActionDialog.py	Tue Feb 23 20:00:40 2016 +0100
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2011 - 2016 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a dialog to ask for a download action.
+"""
+
+from __future__ import unicode_literals
+
+from PyQt5.QtWidgets import QDialog
+
+from .Ui_DownloadAskActionDialog import Ui_DownloadAskActionDialog
+
+import Preferences
+
+
+class DownloadAskActionDialog(QDialog, Ui_DownloadAskActionDialog):
+    """
+    Class implementing a dialog to ask for a download action.
+    """
+    def __init__(self, fileName, mimeType, baseUrl, parent=None):
+        """
+        Constructor
+        
+        @param fileName file name (string)
+        @param mimeType mime type (string)
+        @param baseUrl URL (string)
+        @param parent reference to the parent widget (QWidget)
+        """
+        super(DownloadAskActionDialog, self).__init__(parent)
+        self.setupUi(self)
+        
+        self.infoLabel.setText("<b>{0}</b>".format(fileName))
+        self.typeLabel.setText(mimeType)
+        self.siteLabel.setText(baseUrl)
+        
+        if not Preferences.getWebBrowser("VirusTotalEnabled") or \
+           Preferences.getWebBrowser("VirusTotalServiceKey") == "":
+            self.scanButton.setHidden(True)
+        
+        msh = self.minimumSizeHint()
+        self.resize(max(self.width(), msh.width()), msh.height())
+    
+    def getAction(self):
+        """
+        Public method to get the selected action.
+        
+        @return selected action ("save", "open", "scan" or "cancel")
+        """
+        if self.openButton.isChecked():
+            return "open"
+        elif self.scanButton.isChecked():
+            return "scan"
+        elif self.saveButton.isChecked():
+            return "save"
+        else:
+            # should not happen, but keep it safe
+            return "cancel"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebBrowser/Download/DownloadAskActionDialog.ui	Tue Feb 23 20:00:40 2016 +0100
@@ -0,0 +1,205 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>DownloadAskActionDialog</class>
+ <widget class="QDialog" name="DownloadAskActionDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>500</width>
+    <height>209</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>What to do?</string>
+  </property>
+  <property name="sizeGripEnabled">
+   <bool>true</bool>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QLabel" name="label_3">
+     <property name="text">
+      <string>You are about to download this file:</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <layout class="QGridLayout" name="gridLayout_2">
+     <item row="0" column="0">
+      <widget class="QLabel" name="label_4">
+       <property name="text">
+        <string notr="true"/>
+       </property>
+      </widget>
+     </item>
+     <item row="0" column="1" colspan="2">
+      <widget class="QLabel" name="infoLabel">
+       <property name="text">
+        <string/>
+       </property>
+       <property name="wordWrap">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+     <item row="1" column="1">
+      <widget class="QLabel" name="label_5">
+       <property name="text">
+        <string>Type:</string>
+       </property>
+      </widget>
+     </item>
+     <item row="1" column="2">
+      <widget class="QLabel" name="typeLabel">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="text">
+        <string/>
+       </property>
+      </widget>
+     </item>
+     <item row="2" column="1">
+      <widget class="QLabel" name="label_6">
+       <property name="text">
+        <string>From:</string>
+       </property>
+      </widget>
+     </item>
+     <item row="2" column="2">
+      <widget class="QLabel" name="siteLabel">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="text">
+        <string/>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <layout class="QGridLayout" name="gridLayout">
+     <item row="1" column="0">
+      <widget class="QLabel" name="label_2">
+       <property name="text">
+        <string notr="true"/>
+       </property>
+      </widget>
+     </item>
+     <item row="1" column="1">
+      <widget class="QRadioButton" name="openButton">
+       <property name="toolTip">
+        <string>Select to open the downloaded file</string>
+       </property>
+       <property name="text">
+        <string>&amp;Open File</string>
+       </property>
+      </widget>
+     </item>
+     <item row="2" column="1">
+      <widget class="QRadioButton" name="scanButton">
+       <property name="statusTip">
+        <string>Select to scan the file with VirusTotal</string>
+       </property>
+       <property name="text">
+        <string>Scan with &amp;VirusTotal</string>
+       </property>
+      </widget>
+     </item>
+     <item row="2" column="2" rowspan="2">
+      <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 row="3" column="1">
+      <widget class="QRadioButton" name="saveButton">
+       <property name="toolTip">
+        <string>Select to save the file</string>
+       </property>
+       <property name="text">
+        <string>&amp;Save File</string>
+       </property>
+       <property name="checked">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+     <item row="0" column="0" colspan="2">
+      <widget class="QLabel" name="label">
+       <property name="text">
+        <string>&lt;b&gt;What do you want to do?&lt;/b&gt;</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <tabstops>
+  <tabstop>openButton</tabstop>
+  <tabstop>scanButton</tabstop>
+  <tabstop>saveButton</tabstop>
+  <tabstop>buttonBox</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>DownloadAskActionDialog</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>DownloadAskActionDialog</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/Download/DownloadItem.py	Tue Feb 23 20:00:40 2016 +0100
@@ -0,0 +1,721 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2010 - 2016 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a widget controlling a download.
+"""
+
+from __future__ import unicode_literals
+try:
+    str = unicode
+except NameError:
+    pass
+
+from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QTime, QFile, QFileInfo, \
+    QUrl, QIODevice, QCryptographicHash, PYQT_VERSION_STR
+from PyQt5.QtGui import QPalette, QDesktopServices
+from PyQt5.QtWidgets import QWidget, QStyle, QDialog
+from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply
+
+from E5Gui import E5FileDialog
+
+from .Ui_DownloadItem import Ui_DownloadItem
+
+from .DownloadUtilities import timeString, dataString
+##from ..HelpUtilities import parseContentDisposition
+
+import UI.PixmapCache
+import Preferences
+
+
+class DownloadItem(QWidget, Ui_DownloadItem):
+    """
+    Class implementing a widget controlling a download.
+    
+    @signal statusChanged() emitted upon a status change of a download
+    @signal downloadFinished() emitted when a download finished
+    @signal progress(int, int) emitted to signal the download progress
+    """
+    statusChanged = pyqtSignal()
+    downloadFinished = pyqtSignal()
+    progress = pyqtSignal(int, int)
+    
+    Downloading = 0
+    DownloadSuccessful = 1
+    DownloadCancelled = 2
+    
+    def __init__(self, reply=None, requestFilename=False, webPage=None,
+                 download=False, parent=None, mainWindow=None):
+        """
+        Constructor
+        
+        @keyparam reply reference to the network reply object (QNetworkReply)
+        @keyparam requestFilename flag indicating to ask the user for a
+            filename (boolean)
+        @keyparam webPage reference to the web page object the download
+            originated from (QWebPage)
+        @keyparam download flag indicating a download operation (boolean)
+        @keyparam parent reference to the parent widget (QWidget)
+        @keyparam mainWindow reference to the main window (HelpWindow)
+        """
+        super(DownloadItem, self).__init__(parent)
+        self.setupUi(self)
+        
+        p = self.infoLabel.palette()
+        p.setColor(QPalette.Text, Qt.darkGray)
+        self.infoLabel.setPalette(p)
+        
+        self.progressBar.setMaximum(0)
+        
+        self.__isFtpDownload = reply is not None and \
+            reply.url().scheme() == "ftp"
+        
+        self.tryAgainButton.setIcon(UI.PixmapCache.getIcon("restart.png"))
+        self.tryAgainButton.setEnabled(False)
+        self.tryAgainButton.setVisible(False)
+        self.stopButton.setIcon(UI.PixmapCache.getIcon("stopLoading.png"))
+        self.pauseButton.setIcon(UI.PixmapCache.getIcon("pause.png"))
+        self.openButton.setIcon(UI.PixmapCache.getIcon("open.png"))
+        self.openButton.setEnabled(False)
+        self.openButton.setVisible(False)
+        if self.__isFtpDownload:
+            self.stopButton.setEnabled(False)
+            self.stopButton.setVisible(False)
+            self.pauseButton.setEnabled(False)
+            self.pauseButton.setVisible(False)
+        
+        self.__state = DownloadItem.Downloading
+        
+        icon = self.style().standardIcon(QStyle.SP_FileIcon)
+        self.fileIcon.setPixmap(icon.pixmap(48, 48))
+        
+        self.__mainWindow = mainWindow
+        self.__reply = reply
+        self.__requestFilename = requestFilename
+        self.__page = webPage
+        self.__pageUrl = webPage and webPage.mainFrame().url() or QUrl()
+        self.__toDownload = download
+        self.__bytesReceived = 0
+        self.__bytesTotal = -1
+        self.__downloadTime = QTime()
+        self.__output = QFile()
+        self.__fileName = ""
+        self.__originalFileName = ""
+        self.__startedSaving = False
+        self.__finishedDownloading = False
+        self.__gettingFileName = False
+        self.__canceledFileSelect = False
+        self.__autoOpen = False
+        
+        self.__sha1Hash = QCryptographicHash(QCryptographicHash.Sha1)
+        self.__md5Hash = QCryptographicHash(QCryptographicHash.Md5)
+        
+        if not requestFilename:
+            self.__requestFilename = \
+                Preferences.getUI("RequestDownloadFilename")
+        
+        self.__initialize()
+    
+    def __initialize(self, tryAgain=False):
+        """
+        Private method to (re)initialize the widget.
+        
+        @param tryAgain flag indicating a retry (boolean)
+        """
+        if self.__reply is None:
+            return
+        
+        self.__startedSaving = False
+        self.__finishedDownloading = False
+        self.__bytesReceived = 0
+        self.__bytesTotal = -1
+        
+        self.__sha1Hash.reset()
+        self.__md5Hash.reset()
+        
+        # start timer for the download estimation
+        self.__downloadTime.start()
+        
+        # attach to the reply object
+        self.__url = self.__reply.url()
+        self.__reply.setParent(self)
+        self.__reply.setReadBufferSize(16 * 1024 * 1024)
+        self.__reply.readyRead.connect(self.__readyRead)
+        self.__reply.error.connect(self.__networkError)
+        self.__reply.downloadProgress.connect(self.__downloadProgress)
+        self.__reply.metaDataChanged.connect(self.__metaDataChanged)
+        self.__reply.finished.connect(self.__finished)
+        
+        # reset info
+        self.infoLabel.clear()
+        self.progressBar.setValue(0)
+        self.__getFileName()
+        
+        if self.__reply.error() != QNetworkReply.NoError:
+            self.__networkError()
+            self.__finished()
+    
+    def __getFileName(self):
+        """
+        Private method to get the file name to save to from the user.
+        """
+        if self.__gettingFileName:
+            return
+        
+        from WebBrowser.WebBrowserWindow import WebBrowserWindow
+        downloadDirectory = WebBrowserWindow\
+            .downloadManager().downloadDirectory()
+        
+        if self.__fileName:
+            fileName = self.__fileName
+            originalFileName = self.__originalFileName
+            self.__toDownload = True
+            ask = False
+        else:
+            defaultFileName, originalFileName = \
+                self.__saveFileName(downloadDirectory)
+            fileName = defaultFileName
+            self.__originalFileName = originalFileName
+            ask = True
+        self.__autoOpen = False
+        if not self.__toDownload:
+            from .DownloadAskActionDialog import DownloadAskActionDialog
+            url = self.__reply.url()
+            dlg = DownloadAskActionDialog(
+                QFileInfo(originalFileName).fileName(),
+                self.__reply.header(QNetworkRequest.ContentTypeHeader),
+                "{0}://{1}".format(url.scheme(), url.authority()),
+                self)
+            if dlg.exec_() == QDialog.Rejected or dlg.getAction() == "cancel":
+                self.progressBar.setVisible(False)
+                self.__reply.close()
+                self.on_stopButton_clicked()
+                self.filenameLabel.setText(
+                    self.tr("Download canceled: {0}").format(
+                        QFileInfo(defaultFileName).fileName()))
+                self.__canceledFileSelect = True
+                return
+            
+            if dlg.getAction() == "scan":
+                self.__mainWindow.requestVirusTotalScan(url)
+                
+                self.progressBar.setVisible(False)
+                self.__reply.close()
+                self.on_stopButton_clicked()
+                self.filenameLabel.setText(
+                    self.tr("VirusTotal scan scheduled: {0}").format(
+                        QFileInfo(defaultFileName).fileName()))
+                self.__canceledFileSelect = True
+                return
+            
+            self.__autoOpen = dlg.getAction() == "open"
+            if PYQT_VERSION_STR >= "5.0.0":
+                from PyQt5.QtCore import QStandardPaths
+                tempLocation = QStandardPaths.storageLocation(
+                    QStandardPaths.TempLocation)
+            else:
+                from PyQt5.QtGui import QDesktopServices
+                tempLocation = QDesktopServices.storageLocation(
+                    QDesktopServices.TempLocation)
+            fileName = tempLocation + '/' + \
+                QFileInfo(fileName).completeBaseName()
+        
+        if ask and not self.__autoOpen and self.__requestFilename:
+            self.__gettingFileName = True
+            fileName = E5FileDialog.getSaveFileName(
+                None,
+                self.tr("Save File"),
+                defaultFileName,
+                "")
+            self.__gettingFileName = False
+            if not fileName:
+                self.progressBar.setVisible(False)
+                self.__reply.close()
+                self.on_stopButton_clicked()
+                self.filenameLabel.setText(
+                    self.tr("Download canceled: {0}")
+                        .format(QFileInfo(defaultFileName).fileName()))
+                self.__canceledFileSelect = True
+                return
+        
+        fileInfo = QFileInfo(fileName)
+        WebBrowserWindow.downloadManager()\
+            .setDownloadDirectory(fileInfo.absoluteDir().absolutePath())
+        self.filenameLabel.setText(fileInfo.fileName())
+        
+        self.__output.setFileName(fileName + ".part")
+        self.__fileName = fileName
+        
+        # check file path for saving
+        saveDirPath = QFileInfo(self.__fileName).dir()
+        if not saveDirPath.exists():
+            if not saveDirPath.mkpath(saveDirPath.absolutePath()):
+                self.progressBar.setVisible(False)
+                self.on_stopButton_clicked()
+                self.infoLabel.setText(self.tr(
+                    "Download directory ({0}) couldn't be created.")
+                    .format(saveDirPath.absolutePath()))
+                return
+        
+        self.filenameLabel.setText(QFileInfo(self.__fileName).fileName())
+        if self.__requestFilename:
+            self.__readyRead()
+    
+    def __saveFileName(self, directory):
+        """
+        Private method to calculate a name for the file to download.
+        
+        @param directory name of the directory to store the file into (string)
+        @return proposed filename and original filename (string, string)
+        """
+        path = parseContentDisposition(self.__reply)
+        info = QFileInfo(path)
+        baseName = info.completeBaseName()
+        endName = info.suffix()
+        
+        origName = baseName
+        if endName:
+            origName += '.' + endName
+        
+        name = directory + baseName
+        if endName:
+            name += '.' + endName
+            if not self.__requestFilename:
+                # do not overwrite, if the user is not being asked
+                i = 1
+                while QFile.exists(name):
+                    # file exists already, don't overwrite
+                    name = directory + baseName + ('-{0:d}'.format(i))
+                    if endName:
+                        name += '.' + endName
+                    i += 1
+        return name, origName
+    
+    def __open(self):
+        """
+        Private slot to open the downloaded file.
+        """
+        info = QFileInfo(self.__output)
+        url = QUrl.fromLocalFile(info.absoluteFilePath())
+        QDesktopServices.openUrl(url)
+    
+    @pyqtSlot()
+    def on_tryAgainButton_clicked(self):
+        """
+        Private slot to retry the download.
+        """
+        self.retry()
+    
+    def retry(self):
+        """
+        Public slot to retry the download.
+        """
+        if not self.tryAgainButton.isEnabled():
+            return
+        
+        self.tryAgainButton.setEnabled(False)
+        self.tryAgainButton.setVisible(False)
+        self.openButton.setEnabled(False)
+        self.openButton.setVisible(False)
+        if not self.__isFtpDownload:
+            self.stopButton.setEnabled(True)
+            self.stopButton.setVisible(True)
+            self.pauseButton.setEnabled(True)
+            self.pauseButton.setVisible(True)
+        self.progressBar.setVisible(True)
+        
+        if self.__page:
+            nam = self.__page.networkAccessManager()
+        else:
+            from WebBrowser.WebBrowserWindow import WebBrowserWindow
+            nam = WebBrowserWindow.networkAccessManager()
+        reply = nam.get(QNetworkRequest(self.__url))
+        if self.__output.exists():
+            self.__output.remove()
+        self.__output = QFile()
+        self.__reply = reply
+        self.__initialize(tryAgain=True)
+        self.__state = DownloadItem.Downloading
+        self.statusChanged.emit()
+    
+    @pyqtSlot(bool)
+    def on_pauseButton_clicked(self, checked):
+        """
+        Private slot to pause the download.
+        
+        @param checked flag indicating the state of the button (boolean)
+        """
+        if checked:
+            self.__reply.readyRead.disconnect(self.__readyRead)
+            self.__reply.setReadBufferSize(16 * 1024)
+        else:
+            self.__reply.readyRead.connect(self.__readyRead)
+            self.__reply.setReadBufferSize(16 * 1024 * 1024)
+            self.__readyRead()
+    
+    @pyqtSlot()
+    def on_stopButton_clicked(self):
+        """
+        Private slot to stop the download.
+        """
+        self.cancelDownload()
+    
+    def cancelDownload(self):
+        """
+        Public slot to stop the download.
+        """
+        self.setUpdatesEnabled(False)
+        if not self.__isFtpDownload:
+            self.stopButton.setEnabled(False)
+            self.stopButton.setVisible(False)
+            self.pauseButton.setEnabled(False)
+            self.pauseButton.setVisible(False)
+        self.tryAgainButton.setEnabled(True)
+        self.tryAgainButton.setVisible(True)
+        self.openButton.setEnabled(False)
+        self.openButton.setVisible(False)
+        self.setUpdatesEnabled(True)
+        self.__state = DownloadItem.DownloadCancelled
+        self.__reply.abort()
+        self.downloadFinished.emit()
+    
+    @pyqtSlot()
+    def on_openButton_clicked(self):
+        """
+        Private slot to open the downloaded file.
+        """
+        self.openFile()
+    
+    def openFile(self):
+        """
+        Public slot to open the downloaded file.
+        """
+        info = QFileInfo(self.__fileName)
+        url = QUrl.fromLocalFile(info.absoluteFilePath())
+        QDesktopServices.openUrl(url)
+    
+    def openFolder(self):
+        """
+        Public slot to open the folder containing the downloaded file.
+        """
+        info = QFileInfo(self.__fileName)
+        url = QUrl.fromLocalFile(info.absolutePath())
+        QDesktopServices.openUrl(url)
+    
+    def __readyRead(self):
+        """
+        Private slot to read the available data.
+        """
+        if self.__requestFilename and not self.__output.fileName():
+            return
+        
+        if not self.__output.isOpen():
+            # in case someone else has already put a file there
+            if not self.__requestFilename:
+                self.__getFileName()
+            if not self.__output.open(QIODevice.WriteOnly):
+                self.infoLabel.setText(
+                    self.tr("Error opening save file: {0}")
+                    .format(self.__output.errorString()))
+                self.on_stopButton_clicked()
+                self.statusChanged.emit()
+                return
+            self.statusChanged.emit()
+        
+        buffer = self.__reply.readAll()
+        self.__sha1Hash.addData(buffer)
+        self.__md5Hash.addData(buffer)
+        bytesWritten = self.__output.write(buffer)
+        if bytesWritten == -1:
+            self.infoLabel.setText(
+                self.tr("Error saving: {0}")
+                    .format(self.__output.errorString()))
+            self.on_stopButton_clicked()
+        else:
+            self.__startedSaving = True
+            if self.__finishedDownloading:
+                self.__finished()
+    
+    def __networkError(self):
+        """
+        Private slot to handle a network error.
+        """
+        self.infoLabel.setText(
+            self.tr("Network Error: {0}")
+                .format(self.__reply.errorString()))
+        self.tryAgainButton.setEnabled(True)
+        self.tryAgainButton.setVisible(True)
+        self.downloadFinished.emit()
+    
+    def __metaDataChanged(self):
+        """
+        Private slot to handle a change of the meta data.
+        """
+        locationHeader = self.__reply.header(QNetworkRequest.LocationHeader)
+        if locationHeader and locationHeader.isValid():
+            self.__url = QUrl(locationHeader)
+            from WebBrowser.WebBrowserWindow import WebBrowserWindow
+            self.__reply = WebBrowserWindow\
+                .networkAccessManager().get(QNetworkRequest(self.__url))
+            self.__initialize()
+    
+    def __downloadProgress(self, bytesReceived, bytesTotal):
+        """
+        Private method to show the download progress.
+        
+        @param bytesReceived number of bytes received (integer)
+        @param bytesTotal number of total bytes (integer)
+        """
+        self.__bytesReceived = bytesReceived
+        self.__bytesTotal = bytesTotal
+        currentValue = 0
+        totalValue = 0
+        if bytesTotal > 0:
+            currentValue = bytesReceived * 100 / bytesTotal
+            totalValue = 100
+        self.progressBar.setValue(currentValue)
+        self.progressBar.setMaximum(totalValue)
+        
+        self.progress.emit(currentValue, totalValue)
+        self.__updateInfoLabel()
+    
+    def bytesTotal(self):
+        """
+        Public method to get the total number of bytes of the download.
+        
+        @return total number of bytes (integer)
+        """
+        if self.__bytesTotal == -1:
+            self.__bytesTotal = self.__reply.header(
+                QNetworkRequest.ContentLengthHeader)
+            if self.__bytesTotal is None:
+                self.__bytesTotal = -1
+        return self.__bytesTotal
+    
+    def bytesReceived(self):
+        """
+        Public method to get the number of bytes received.
+        
+        @return number of bytes received (integer)
+        """
+        return self.__bytesReceived
+    
+    def remainingTime(self):
+        """
+        Public method to get an estimation for the remaining time.
+        
+        @return estimation for the remaining time (float)
+        """
+        if not self.downloading():
+            return -1.0
+        
+        if self.bytesTotal() == -1:
+            return -1.0
+        
+        cSpeed = self.currentSpeed()
+        if cSpeed != 0:
+            timeRemaining = (self.bytesTotal() - self.bytesReceived()) / cSpeed
+        else:
+            timeRemaining = 1
+        
+        # ETA should never be 0
+        if timeRemaining == 0:
+            timeRemaining = 1
+        
+        return timeRemaining
+    
+    def currentSpeed(self):
+        """
+        Public method to get an estimation for the download speed.
+        
+        @return estimation for the download speed (float)
+        """
+        if not self.downloading():
+            return -1.0
+        
+        return self.__bytesReceived * 1000.0 / self.__downloadTime.elapsed()
+    
+    def __updateInfoLabel(self):
+        """
+        Private method to update the info label.
+        """
+        if self.__reply.error() != QNetworkReply.NoError:
+            return
+        
+        bytesTotal = self.bytesTotal()
+        running = not self.downloadedSuccessfully()
+        
+        speed = self.currentSpeed()
+        timeRemaining = self.remainingTime()
+        
+        info = ""
+        if running:
+            remaining = ""
+            
+            if bytesTotal > 0:
+                remaining = timeString(timeRemaining)
+            
+            info = self.tr("{0} of {1} ({2}/sec)\n{3}")\
+                .format(
+                    dataString(self.__bytesReceived),
+                    bytesTotal == -1 and self.tr("?")
+                    or dataString(bytesTotal),
+                    dataString(int(speed)),
+                    remaining)
+        else:
+            if self.__bytesReceived == bytesTotal or bytesTotal == -1:
+                info = self.tr("{0} downloaded\nSHA1: {1}\nMD5: {2}")\
+                    .format(dataString(self.__output.size()),
+                            str(self.__sha1Hash.result().toHex(),
+                                encoding="ascii"),
+                            str(self.__md5Hash.result().toHex(),
+                                encoding="ascii")
+                            )
+            else:
+                info = self.tr("{0} of {1} - Stopped")\
+                    .format(dataString(self.__bytesReceived),
+                            dataString(bytesTotal))
+        self.infoLabel.setText(info)
+    
+    def downloading(self):
+        """
+        Public method to determine, if a download is in progress.
+        
+        @return flag indicating a download is in progress (boolean)
+        """
+        return self.__state == DownloadItem.Downloading
+    
+    def downloadedSuccessfully(self):
+        """
+        Public method to check for a successful download.
+        
+        @return flag indicating a successful download (boolean)
+        """
+        return self.__state == DownloadItem.DownloadSuccessful
+    
+    def downloadCanceled(self):
+        """
+        Public method to check, if the download was cancelled.
+        
+        @return flag indicating a canceled download (boolean)
+        """
+        return self.__state == DownloadItem.DownloadCancelled
+    
+    def __finished(self):
+        """
+        Private slot to handle the download finished.
+        """
+        self.__finishedDownloading = True
+        if not self.__startedSaving:
+            return
+        
+        noError = self.__reply.error() == QNetworkReply.NoError
+        
+        self.progressBar.setVisible(False)
+        if not self.__isFtpDownload:
+            self.stopButton.setEnabled(False)
+            self.stopButton.setVisible(False)
+            self.pauseButton.setEnabled(False)
+            self.pauseButton.setVisible(False)
+        self.openButton.setEnabled(noError)
+        self.openButton.setVisible(noError)
+        self.__output.close()
+        if QFile.exists(self.__fileName):
+            QFile.remove(self.__fileName)
+        self.__output.rename(self.__fileName)
+        self.__updateInfoLabel()
+        self.__state = DownloadItem.DownloadSuccessful
+        self.statusChanged.emit()
+        self.downloadFinished.emit()
+        
+        if self.__autoOpen:
+            self.__open()
+    
+    def canceledFileSelect(self):
+        """
+        Public method to check, if the user canceled the file selection.
+        
+        @return flag indicating cancellation (boolean)
+        """
+        return self.__canceledFileSelect
+    
+    def setIcon(self, icon):
+        """
+        Public method to set the download icon.
+        
+        @param icon reference to the icon to be set (QIcon)
+        """
+        self.fileIcon.setPixmap(icon.pixmap(48, 48))
+    
+    def fileName(self):
+        """
+        Public method to get the name of the output file.
+        
+        @return name of the output file (string)
+        """
+        return self.__fileName
+    
+    def absoluteFilePath(self):
+        """
+        Public method to get the absolute path of the output file.
+        
+        @return absolute path of the output file (string)
+        """
+        return QFileInfo(self.__fileName).absoluteFilePath()
+    
+    def getData(self):
+        """
+        Public method to get the relevant download data.
+        
+        @return tuple of URL, save location, flag and the
+            URL of the related web page (QUrl, string, boolean,QUrl)
+        """
+        return (self.__url, QFileInfo(self.__fileName).filePath(),
+                self.downloadedSuccessfully(), self.__pageUrl)
+    
+    def setData(self, data):
+        """
+        Public method to set the relevant download data.
+        
+        @param data tuple of URL, save location, flag and the
+            URL of the related web page (QUrl, string, boolean, QUrl)
+        """
+        self.__url = data[0]
+        self.__fileName = data[1]
+        self.__pageUrl = data[3]
+        self.__isFtpDownload = self.__url.scheme() == "ftp"
+        
+        self.filenameLabel.setText(QFileInfo(self.__fileName).fileName())
+        self.infoLabel.setText(self.__fileName)
+        
+        self.stopButton.setEnabled(False)
+        self.stopButton.setVisible(False)
+        self.pauseButton.setEnabled(False)
+        self.pauseButton.setVisible(False)
+        self.openButton.setEnabled(data[2])
+        self.openButton.setVisible(data[2])
+        self.tryAgainButton.setEnabled(not data[2])
+        self.tryAgainButton.setVisible(not data[2])
+        if data[2]:
+            self.__state = DownloadItem.DownloadSuccessful
+        else:
+            self.__state = DownloadItem.DownloadCancelled
+        self.progressBar.setVisible(False)
+    
+    def getInfoData(self):
+        """
+        Public method to get the text of the info label.
+        
+        @return text of the info label (string)
+        """
+        return self.infoLabel.text()
+    
+    def getPageUrl(self):
+        """
+        Public method to get the URL of the download page.
+        
+        @return URL of the download page (QUrl)
+        """
+        return self.__pageUrl
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebBrowser/Download/DownloadItem.ui	Tue Feb 23 20:00:40 2016 +0100
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>DownloadItem</class>
+ <widget class="QWidget" name="DownloadItem">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>400</width>
+    <height>82</height>
+   </rect>
+  </property>
+  <property name="sizePolicy">
+   <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+    <horstretch>0</horstretch>
+    <verstretch>0</verstretch>
+   </sizepolicy>
+  </property>
+  <property name="windowTitle">
+   <string/>
+  </property>
+  <layout class="QHBoxLayout" name="horizontalLayout">
+   <item>
+    <widget class="QLabel" name="fileIcon">
+     <property name="text">
+      <string>Icon</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <layout class="QVBoxLayout" name="verticalLayout">
+     <item>
+      <widget class="QLabel" name="filenameLabel">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="text">
+        <string>Filename</string>
+       </property>
+       <property name="alignment">
+        <set>Qt::AlignCenter</set>
+       </property>
+       <property name="wordWrap">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QProgressBar" name="progressBar"/>
+     </item>
+     <item>
+      <widget class="QLabel" name="infoLabel">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="text">
+        <string notr="true">Info</string>
+       </property>
+       <property name="alignment">
+        <set>Qt::AlignCenter</set>
+       </property>
+       <property name="wordWrap">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="QToolButton" name="tryAgainButton">
+     <property name="toolTip">
+      <string>Press to repeat the download</string>
+     </property>
+     <property name="text">
+      <string/>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QToolButton" name="pauseButton">
+     <property name="toolTip">
+      <string>Press to pause the download</string>
+     </property>
+     <property name="text">
+      <string/>
+     </property>
+     <property name="checkable">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QToolButton" name="stopButton">
+     <property name="toolTip">
+      <string>Press to cancel the download</string>
+     </property>
+     <property name="text">
+      <string/>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QToolButton" name="openButton">
+     <property name="toolTip">
+      <string>Press to open the downloaded file</string>
+     </property>
+     <property name="text">
+      <string/>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <tabstops>
+  <tabstop>tryAgainButton</tabstop>
+  <tabstop>pauseButton</tabstop>
+  <tabstop>stopButton</tabstop>
+  <tabstop>openButton</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebBrowser/Download/DownloadManager.py	Tue Feb 23 20:00:40 2016 +0100
@@ -0,0 +1,527 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2010 - 2016 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the download manager class.
+"""
+
+from __future__ import unicode_literals
+
+from PyQt5.QtCore import pyqtSlot, Qt, QModelIndex, QFileInfo
+from PyQt5.QtGui import QCursor
+from PyQt5.QtWidgets import QDialog, QStyle, QFileIconProvider, QMenu, \
+    QApplication
+from PyQt5.QtNetwork import QNetworkRequest
+from PyQt5.QtWebKit import QWebSettings
+
+from E5Gui import E5MessageBox
+
+from .Ui_DownloadManager import Ui_DownloadManager
+
+from .DownloadModel import DownloadModel
+
+from WebBrowser.WebBrowserWindow import WebBrowserWindow
+
+from Utilities.AutoSaver import AutoSaver
+import UI.PixmapCache
+import Preferences
+
+
+class DownloadManager(QDialog, Ui_DownloadManager):
+    """
+    Class implementing the download manager.
+    """
+    RemoveNever = 0
+    RemoveExit = 1
+    RemoveSuccessFullDownload = 2
+    
+    def __init__(self, parent=None):
+        """
+        Constructor
+        
+        @param parent reference to the parent widget (QWidget)
+        """
+        super(DownloadManager, self).__init__(parent)
+        self.setupUi(self)
+        self.setWindowFlags(Qt.Window)
+        
+        self.__saveTimer = AutoSaver(self, self.save)
+        
+        self.__model = DownloadModel(self)
+        self.__manager = Helpviewer.HelpWindow.HelpWindow\
+            .networkAccessManager()
+        
+        self.__iconProvider = None
+        self.__downloads = []
+        self.__downloadDirectory = ""
+        self.__loaded = False
+        
+        self.setDownloadDirectory(Preferences.getUI("DownloadPath"))
+        
+        self.downloadsView.setShowGrid(False)
+        self.downloadsView.verticalHeader().hide()
+        self.downloadsView.horizontalHeader().hide()
+        self.downloadsView.setAlternatingRowColors(True)
+        self.downloadsView.horizontalHeader().setStretchLastSection(True)
+        self.downloadsView.setModel(self.__model)
+        self.downloadsView.setContextMenuPolicy(Qt.CustomContextMenu)
+        self.downloadsView.customContextMenuRequested.connect(
+            self.__customContextMenuRequested)
+        
+        self.__load()
+    
+    def __customContextMenuRequested(self, pos):
+        """
+        Private slot to handle the context menu request for the bookmarks tree.
+        
+        @param pos position the context menu was requested (QPoint)
+        """
+        menu = QMenu()
+        
+        selectedRowsCount = len(
+            self.downloadsView.selectionModel().selectedRows())
+        
+        if selectedRowsCount == 1:
+            row = self.downloadsView.selectionModel().selectedRows()[0].row()
+            itm = self.__downloads[row]
+            if itm.downloadCanceled():
+                menu.addAction(
+                    UI.PixmapCache.getIcon("restart.png"),
+                    self.tr("Retry"), self.__contextMenuRetry)
+            else:
+                if itm.downloadedSuccessfully():
+                    menu.addAction(
+                        UI.PixmapCache.getIcon("open.png"),
+                        self.tr("Open"), self.__contextMenuOpen)
+                elif itm.downloading():
+                    menu.addAction(
+                        UI.PixmapCache.getIcon("stopLoading.png"),
+                        self.tr("Cancel"), self.__contextMenuCancel)
+                    menu.addSeparator()
+                menu.addAction(
+                    self.tr("Open Containing Folder"),
+                    self.__contextMenuOpenFolder)
+            menu.addSeparator()
+            menu.addAction(
+                self.tr("Go to Download Page"),
+                self.__contextMenuGotoPage)
+            menu.addAction(
+                self.tr("Copy Download Link"),
+                self.__contextMenuCopyLink)
+            menu.addSeparator()
+        menu.addAction(self.tr("Select All"), self.__contextMenuSelectAll)
+        if selectedRowsCount > 1 or \
+           (selectedRowsCount == 1 and
+            not self.__downloads[
+                self.downloadsView.selectionModel().selectedRows()[0].row()]
+                .downloading()):
+            menu.addSeparator()
+            menu.addAction(
+                self.tr("Remove From List"),
+                self.__contextMenuRemoveSelected)
+        
+        menu.exec_(QCursor.pos())
+    
+    def shutdown(self):
+        """
+        Public method to stop the download manager.
+        """
+        self.__saveTimer.changeOccurred()
+        self.__saveTimer.saveIfNeccessary()
+        self.close()
+    
+    def activeDownloads(self):
+        """
+        Public method to get the number of active downloads.
+        
+        @return number of active downloads (integer)
+        """
+        count = 0
+        
+        for download in self.__downloads:
+            if download.downloading():
+                count += 1
+        return count
+    
+    def allowQuit(self):
+        """
+        Public method to check, if it is ok to quit.
+        
+        @return flag indicating allowance to quit (boolean)
+        """
+        if self.activeDownloads() > 0:
+            res = E5MessageBox.yesNo(
+                self,
+                self.tr(""),
+                self.tr("""There are %n downloads in progress.\n"""
+                        """Do you want to quit anyway?""", "",
+                        self.activeDownloads()),
+                icon=E5MessageBox.Warning)
+            if not res:
+                self.show()
+                return False
+        return True
+    
+    def download(self, requestOrUrl, requestFileName=False, mainWindow=None):
+        """
+        Public method to download a file.
+        
+        @param requestOrUrl reference to a request object (QNetworkRequest)
+            or a URL to be downloaded (QUrl)
+        @keyparam requestFileName flag indicating to ask for the
+            download file name (boolean)
+        @keyparam mainWindow reference to the main window (HelpWindow)
+        """
+        request = QNetworkRequest(requestOrUrl)
+        if request.url().isEmpty():
+            return
+        self.handleUnsupportedContent(
+            self.__manager.get(request),
+            requestFileName=requestFileName,
+            download=True,
+            mainWindow=mainWindow)
+    
+    def handleUnsupportedContent(self, reply, requestFileName=False,
+                                 webPage=None, download=False,
+                                 mainWindow=None):
+        """
+        Public method to handle unsupported content by downloading the
+        referenced resource.
+        
+        @param reply reference to the reply object (QNetworkReply)
+        @keyparam requestFileName indicating to ask for a filename
+            (boolean)
+        @keyparam webPage reference to the web page (HelpWebPage)
+        @keyparam download flag indicating a download request (boolean)
+        @keyparam mainWindow reference to the main window (HelpWindow)
+        """
+        if reply is None or reply.url().isEmpty():
+            return
+        
+        size = reply.header(QNetworkRequest.ContentLengthHeader)
+        if size == 0:
+            return
+        
+        from .DownloadItem import DownloadItem
+        itm = DownloadItem(
+            reply=reply, requestFilename=requestFileName,
+            webPage=webPage, download=download, parent=self,
+            mainWindow=mainWindow)
+        self.__addItem(itm)
+        
+        if itm.canceledFileSelect():
+            return
+        
+        if not self.isVisible():
+            self.show()
+        
+        self.activateWindow()
+        self.raise_()
+    
+    def __addItem(self, itm):
+        """
+        Private method to add a download to the list of downloads.
+        
+        @param itm reference to the download item (DownloadItem)
+        """
+        itm.statusChanged.connect(self.__updateRow)
+        itm.downloadFinished.connect(self.__finished)
+        
+        row = len(self.__downloads)
+        self.__model.beginInsertRows(QModelIndex(), row, row)
+        self.__downloads.append(itm)
+        self.__model.endInsertRows()
+        
+        self.downloadsView.setIndexWidget(self.__model.index(row, 0), itm)
+        icon = self.style().standardIcon(QStyle.SP_FileIcon)
+        itm.setIcon(icon)
+        self.downloadsView.setRowHeight(row, itm.sizeHint().height() * 1.5)
+        # just in case the download finished before the constructor returned
+        self.__updateRow(itm)
+        self.changeOccurred()
+        self.__updateActiveItemCount()
+    
+    def __updateRow(self, itm=None):
+        """
+        Private slot to update a download item.
+        
+        @param itm reference to the download item (DownloadItem)
+        """
+        if itm is None:
+            itm = self.sender()
+        
+        if itm not in self.__downloads:
+            return
+        
+        row = self.__downloads.index(itm)
+        
+        if self.__iconProvider is None:
+            self.__iconProvider = QFileIconProvider()
+        
+        icon = self.__iconProvider.icon(QFileInfo(itm.fileName()))
+        if icon.isNull():
+            icon = self.style().standardIcon(QStyle.SP_FileIcon)
+        itm.setIcon(icon)
+        
+        oldHeight = self.downloadsView.rowHeight(row)
+        self.downloadsView.setRowHeight(
+            row,
+            max(oldHeight, itm.minimumSizeHint().height() * 1.5))
+        
+        remove = False
+        globalSettings = QWebSettings.globalSettings()
+        if not itm.downloading() and \
+           globalSettings.testAttribute(QWebSettings.PrivateBrowsingEnabled):
+            remove = True
+        
+        if itm.downloadedSuccessfully() and \
+           self.removePolicy() == DownloadManager.RemoveSuccessFullDownload:
+            remove = True
+        
+        if remove:
+            self.__model.removeRow(row)
+        
+        self.cleanupButton.setEnabled(
+            (len(self.__downloads) - self.activeDownloads()) > 0)
+        
+        # record the change
+        self.changeOccurred()
+    
+    def removePolicy(self):
+        """
+        Public method to get the remove policy.
+        
+        @return remove policy (integer)
+        """
+        return Preferences.getWebBrowser("DownloadManagerRemovePolicy")
+    
+    def setRemovePolicy(self, policy):
+        """
+        Public method to set the remove policy.
+        
+        @param policy policy to be set
+            (DownloadManager.RemoveExit, DownloadManager.RemoveNever,
+             DownloadManager.RemoveSuccessFullDownload)
+        """
+        assert policy in (DownloadManager.RemoveExit,
+                          DownloadManager.RemoveNever,
+                          DownloadManager.RemoveSuccessFullDownload)
+        
+        if policy == self.removePolicy():
+            return
+        
+        Preferences.getWebBrowser("DownloadManagerRemovePolicy", self.policy)
+    
+    def save(self):
+        """
+        Public method to save the download settings.
+        """
+        if not self.__loaded:
+            return
+        
+        Preferences.setWebBrowser("DownloadManagerSize", self.size())
+        Preferences.setWebBrowser("DownloadManagerPosition", self.pos())
+        if self.removePolicy() == DownloadManager.RemoveExit:
+            return
+        
+        downloads = []
+        for download in self.__downloads:
+            downloads.append(download.getData())
+        Preferences.setWebBrowser("DownloadManagerDownloads", downloads)
+    
+    def __load(self):
+        """
+        Private method to load the download settings.
+        """
+        if self.__loaded:
+            return
+        
+        size = Preferences.getWebBrowser("DownloadManagerSize")
+        if size.isValid():
+            self.resize(size)
+        pos = Preferences.getWebBrowser("DownloadManagerPosition")
+        self.move(pos)
+        
+        downloads = Preferences.getWebBrowser("DownloadManagerDownloads")
+        for download in downloads:
+            if not download[0].isEmpty() and \
+               download[1] != "":
+                from .DownloadItem import DownloadItem
+                itm = DownloadItem(parent=self)
+                itm.setData(download)
+                self.__addItem(itm)
+        self.cleanupButton.setEnabled(
+            (len(self.__downloads) - self.activeDownloads()) > 0)
+        
+        self.__loaded = True
+        self.__updateActiveItemCount()
+    
+    def cleanup(self):
+        """
+        Public slot to cleanup the downloads.
+        """
+        self.on_cleanupButton_clicked()
+    
+    @pyqtSlot()
+    def on_cleanupButton_clicked(self):
+        """
+        Private slot cleanup the downloads.
+        """
+        if len(self.__downloads) == 0:
+            return
+        
+        self.__model.removeRows(0, len(self.__downloads))
+        if len(self.__downloads) == 0 and \
+           self.__iconProvider is not None:
+            self.__iconProvider = None
+        
+        self.changeOccurred()
+        self.__updateActiveItemCount()
+    
+    def __updateItemCount(self):
+        """
+        Private method to update the count label.
+        """
+        count = len(self.__downloads)
+        self.countLabel.setText(self.tr("%n Download(s)", "", count))
+    
+    def __updateActiveItemCount(self):
+        """
+        Private method to update the window title.
+        """
+        count = self.activeDownloads()
+        if count > 0:
+            self.setWindowTitle(
+                self.tr("Downloading %n file(s)", "", count))
+        else:
+            self.setWindowTitle(self.tr("Downloads"))
+    
+    def __finished(self):
+        """
+        Private slot to handle a finished download.
+        """
+        self.__updateActiveItemCount()
+        if self.isVisible():
+            QApplication.alert(self)
+    
+    def setDownloadDirectory(self, directory):
+        """
+        Public method to set the current download directory.
+        
+        @param directory current download directory (string)
+        """
+        self.__downloadDirectory = directory
+        if self.__downloadDirectory != "":
+            self.__downloadDirectory += "/"
+    
+    def downloadDirectory(self):
+        """
+        Public method to get the current download directory.
+        
+        @return current download directory (string)
+        """
+        return self.__downloadDirectory
+    
+    def count(self):
+        """
+        Public method to get the number of downloads.
+        
+        @return number of downloads (integer)
+        """
+        return len(self.__downloads)
+    
+    def downloads(self):
+        """
+        Public method to get a reference to the downloads.
+        
+        @return reference to the downloads (list of DownloadItem)
+        """
+        return self.__downloads
+    
+    def changeOccurred(self):
+        """
+        Public method to signal a change.
+        """
+        self.__saveTimer.changeOccurred()
+        self.__updateItemCount()
+    
+    ###########################################################################
+    ## Context menu related methods below
+    ###########################################################################
+    
+    def __currentItem(self):
+        """
+        Private method to get a reference to the current item.
+        
+        @return reference to the current item (DownloadItem)
+        """
+        index = self.downloadsView.currentIndex()
+        if index and index.isValid():
+            row = index.row()
+            return self.__downloads[row]
+        
+        return None
+    
+    def __contextMenuRetry(self):
+        """
+        Private method to retry of the download.
+        """
+        itm = self.__currentItem()
+        if itm is not None:
+            itm.retry()
+    
+    def __contextMenuOpen(self):
+        """
+        Private method to open the downloaded file.
+        """
+        itm = self.__currentItem()
+        if itm is not None:
+            itm.openFile()
+    
+    def __contextMenuOpenFolder(self):
+        """
+        Private method to open the folder containing the downloaded file.
+        """
+        itm = self.__currentItem()
+        if itm is not None:
+            itm.openFolder()
+    
+    def __contextMenuCancel(self):
+        """
+        Private method to cancel the current download.
+        """
+        itm = self.__currentItem()
+        if itm is not None:
+            itm.cancelDownload()
+    
+    def __contextMenuGotoPage(self):
+        """
+        Private method to open the download page.
+        """
+        itm = self.__currentItem()
+        if itm is not None:
+            url = itm.getPageUrl()
+            WebBrowserWindow.mainWindow().openUrl(url, "")
+    
+    def __contextMenuCopyLink(self):
+        """
+        Private method to copy the download link to the clipboard.
+        """
+        itm = self.__currentItem()
+        if itm is not None:
+            url = itm.getPageUrl().toString()
+            QApplication.clipboard().setText(url)
+    
+    def __contextMenuSelectAll(self):
+        """
+        Private method to select all downloads.
+        """
+        self.downloadsView.selectAll()
+    
+    def __contextMenuRemoveSelected(self):
+        """
+        Private method to remove the selected downloads from the list.
+        """
+        self.downloadsView.removeSelected()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebBrowser/Download/DownloadManager.ui	Tue Feb 23 20:00:40 2016 +0100
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>DownloadManager</class>
+ <widget class="QDialog" name="DownloadManager">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>400</width>
+    <height>300</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Downloads</string>
+  </property>
+  <property name="sizeGripEnabled">
+   <bool>true</bool>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <item row="0" column="0" colspan="3">
+    <widget class="E5TableView" name="downloadsView"/>
+   </item>
+   <item row="1" column="0">
+    <layout class="QHBoxLayout" name="horizontalLayout_2">
+     <item>
+      <widget class="QPushButton" name="cleanupButton">
+       <property name="enabled">
+        <bool>false</bool>
+       </property>
+       <property name="toolTip">
+        <string>Press to clean up the list of downloads</string>
+       </property>
+       <property name="text">
+        <string>Clear List</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>40</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+    </layout>
+   </item>
+   <item row="1" column="1">
+    <widget class="QLabel" name="countLabel">
+     <property name="text">
+      <string>0 Items</string>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="2">
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <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>
+      <widget class="QDialogButtonBox" name="buttonBox">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="standardButtons">
+        <set>QDialogButtonBox::Close</set>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>E5TableView</class>
+   <extends>QTableView</extends>
+   <header>E5Gui/E5TableView.h</header>
+  </customwidget>
+ </customwidgets>
+ <tabstops>
+  <tabstop>downloadsView</tabstop>
+  <tabstop>cleanupButton</tabstop>
+  <tabstop>buttonBox</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>DownloadManager</receiver>
+   <slot>close()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>330</x>
+     <y>274</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>294</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebBrowser/Download/DownloadModel.py	Tue Feb 23 20:00:40 2016 +0100
@@ -0,0 +1,116 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2010 - 2016 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the download model.
+"""
+
+from __future__ import unicode_literals
+
+from PyQt5.QtCore import Qt, QAbstractListModel, QModelIndex, QMimeData, QUrl
+
+
+class DownloadModel(QAbstractListModel):
+    """
+    Class implementing the download model.
+    """
+    def __init__(self, manager, parent=None):
+        """
+        Constructor
+        
+        @param manager reference to the download manager (DownloadManager)
+        @param parent reference to the parent object (QObject)
+        """
+        super(DownloadModel, self).__init__(parent)
+        
+        self.__manager = manager
+    
+    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() < 0 or index.row() >= self.rowCount(index.parent()):
+            return None
+        
+        if role == Qt.ToolTipRole:
+            if self.__manager.downloads()[index.row()]\
+                    .downloadedSuccessfully():
+                return self.__manager.downloads()[index.row()].getInfoData()
+        
+        return None
+    
+    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.count()
+    
+    def removeRows(self, row, count, parent=QModelIndex()):
+        """
+        Public method to remove bookmarks from the model.
+        
+        @param row row of the first bookmark to remove (integer)
+        @param count number of bookmarks to remove (integer)
+        @param parent index of the parent bookmark node (QModelIndex)
+        @return flag indicating successful removal (boolean)
+        """
+        if parent.isValid():
+            return False
+        
+        if row < 0 or count <= 0 or row + count > self.rowCount(parent):
+            return False
+        
+        lastRow = row + count - 1
+        for i in range(lastRow, row - 1, -1):
+            if not self.__manager.downloads()[i].downloading():
+                self.beginRemoveRows(parent, i, i)
+                del self.__manager.downloads()[i]
+                self.endRemoveRows()
+        self.__manager.changeOccurred()
+        return True
+    
+    def flags(self, index):
+        """
+        Public method to get flags for an item.
+        
+        @param index index of the node cell (QModelIndex)
+        @return flags (Qt.ItemFlags)
+        """
+        if index.row() < 0 or index.row() >= self.rowCount(index.parent()):
+            return Qt.NoItemFlags
+        
+        defaultFlags = QAbstractListModel.flags(self, index)
+        
+        itm = self.__manager.downloads()[index.row()]
+        if itm.downloadedSuccessfully():
+            return defaultFlags | Qt.ItemIsDragEnabled
+        
+        return defaultFlags
+    
+    def mimeData(self, indexes):
+        """
+        Public method to return the mime data.
+        
+        @param indexes list of indexes (QModelIndexList)
+        @return mime data (QMimeData)
+        """
+        mimeData = QMimeData()
+        urls = []
+        for index in indexes:
+            if index.isValid():
+                itm = self.__manager.downloads()[index.row()]
+                urls.append(QUrl.fromLocalFile(itm.absoluteFilePath()))
+        mimeData.setUrls(urls)
+        return mimeData
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebBrowser/Download/DownloadUtilities.py	Tue Feb 23 20:00:40 2016 +0100
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2010 - 2016 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing some utility functions for the Download package.
+"""
+
+from __future__ import unicode_literals
+
+from PyQt5.QtCore import QCoreApplication
+
+
+def timeString(timeRemaining):
+    """
+    Module function to format the given time.
+    
+    @param timeRemaining time to be formatted (float)
+    @return time string (string)
+    """
+    if timeRemaining > 60:
+        minutes = int(timeRemaining / 60)
+        seconds = int(timeRemaining % 60)
+        remaining = QCoreApplication.translate(
+            "DownloadUtilities",
+            "%n:{0:02} minutes remaining""", "",
+            minutes).format(seconds)
+    else:
+        seconds = int(timeRemaining)
+        remaining = QCoreApplication.translate(
+            "DownloadUtilities",
+            "%n seconds remaining", "", seconds)
+    
+    return remaining
+
+
+def dataString(size):
+    """
+    Module function to generate a formatted size string.
+    
+    @param size size to be formatted (integer)
+    @return formatted data string (string)
+    """
+    unit = ""
+    if size < 1024:
+        unit = QCoreApplication.translate("DownloadUtilities", "Bytes")
+    elif size < 1024 * 1024:
+        size /= 1024
+        unit = QCoreApplication.translate("DownloadUtilities", "KiB")
+    elif size < 1024 * 1024 * 1024:
+        size /= 1024 * 1024
+        unit = QCoreApplication.translate("DownloadUtilities", "MiB")
+    else:
+        size /= 1024 * 1024 * 1024
+        unit = QCoreApplication.translate("DownloadUtilities", "GiB")
+    return "{0:.1f} {1}".format(size, unit)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebBrowser/Download/__init__.py	Tue Feb 23 20:00:40 2016 +0100
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2010 - 2016 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Package implementing all download related modules.
+"""
--- a/WebBrowser/FlashCookieManager/FlashCookieManagerDialog.py	Tue Feb 23 19:07:31 2016 +0100
+++ b/WebBrowser/FlashCookieManager/FlashCookieManagerDialog.py	Tue Feb 23 20:00:40 2016 +0100
@@ -419,7 +419,7 @@
         for row in range(self.blackList.count()):
             blackList.append(self.blackList.item(row).text())
         
-        Preferences.setHelp("FlashCookiesWhitelist", whiteList)
-        Preferences.setHelp("FlashCookiesBlacklist", blackList)
+        Preferences.setWebBrowser("FlashCookiesWhitelist", whiteList)
+        Preferences.setWebBrowser("FlashCookiesBlacklist", blackList)
         
         evt.accept()
--- a/WebBrowser/OpenSearch/OpenSearchManager.py	Tue Feb 23 19:07:31 2016 +0100
+++ b/WebBrowser/OpenSearch/OpenSearchManager.py	Tue Feb 23 20:00:40 2016 +0100
@@ -369,12 +369,12 @@
         
         self.saveDirectory(self.enginesDirectory())
         
-        Preferences.setHelp("WebSearchEngine", self.__current)
+        Preferences.setWebBrowser("WebSearchEngine", self.__current)
         keywords = []
         for k in self.__keywords:
             if self.__keywords[k]:
                 keywords.append((k, self.__keywords[k].name()))
-        Preferences.setHelp("WebSearchKeywords", keywords)
+        Preferences.setWebBrowser("WebSearchKeywords", keywords)
     
     def loadDirectory(self, dirName):
         """
--- a/WebBrowser/VirusTotal/VirusTotalApi.py	Tue Feb 23 19:07:31 2016 +0100
+++ b/WebBrowser/VirusTotal/VirusTotalApi.py	Tue Feb 23 20:00:40 2016 +0100
@@ -83,7 +83,7 @@
         """
         Private method to load the settings.
         """
-        if Preferences.getHelp("VirusTotalSecure"):
+        if Preferences.getWebBrowser("VirusTotalSecure"):
             protocol = "https"
         else:
             protocol = "http"
@@ -159,7 +159,7 @@
         request.setHeader(QNetworkRequest.ContentTypeHeader,
                           "application/x-www-form-urlencoded")
         params = QByteArray("apikey={0}&url=".format(
-            Preferences.getHelp("VirusTotalServiceKey")).encode("utf-8"))\
+            Preferences.getWebBrowser("VirusTotalServiceKey")).encode("utf-8"))\
             .append(QUrl.toPercentEncoding(url.toString()))
         
         import WebBrowser.WebBrowserWindow
@@ -203,7 +203,7 @@
         request.setHeader(QNetworkRequest.ContentTypeHeader,
                           "application/x-www-form-urlencoded")
         params = QByteArray("apikey={0}&resource={1}".format(
-            Preferences.getHelp("VirusTotalServiceKey"), scanId)
+            Preferences.getWebBrowser("VirusTotalServiceKey"), scanId)
             .encode("utf-8"))
         
         import WebBrowser.WebBrowserWindow
@@ -236,7 +236,7 @@
         request.setHeader(QNetworkRequest.ContentTypeHeader,
                           "application/x-www-form-urlencoded")
         params = QByteArray("apikey={0}&resource={1}".format(
-            Preferences.getHelp("VirusTotalServiceKey"), scanId)
+            Preferences.getWebBrowser("VirusTotalServiceKey"), scanId)
             .encode("utf-8"))
         
         import WebBrowser.WebBrowserWindow
@@ -268,7 +268,7 @@
         self.__lastIP = ipAddress
         
         queryItems = [
-            ("apikey", Preferences.getHelp("VirusTotalServiceKey")),
+            ("apikey", Preferences.getWebBrowser("VirusTotalServiceKey")),
             ("ip", ipAddress),
         ]
         url = QUrl(self.GetIpAddressReportUrl)
@@ -331,7 +331,7 @@
         self.__lastDomain = domain
         
         queryItems = [
-            ("apikey", Preferences.getHelp("VirusTotalServiceKey")),
+            ("apikey", Preferences.getWebBrowser("VirusTotalServiceKey")),
             ("domain", domain),
         ]
         url = QUrl(self.GetDomainReportUrl)
--- a/WebBrowser/WebBrowserPage.py	Tue Feb 23 19:07:31 2016 +0100
+++ b/WebBrowser/WebBrowserPage.py	Tue Feb 23 20:00:40 2016 +0100
@@ -443,7 +443,7 @@
 ##        
 ##        @param agent new current user agent string (string)
 ##        """
-##        Preferences.setHelp("UserAgent", agent)
+##        Preferences.setWebBrowser("UserAgent", agent)
 ##    
 ##    def userAgentForUrl(self, url):
 ##        """
--- a/eric6.e4p	Tue Feb 23 19:07:31 2016 +0100
+++ b/eric6.e4p	Tue Feb 23 20:00:40 2016 +0100
@@ -26,6 +26,54 @@
     <Source>DataViews/PyCoverageDialog.py</Source>
     <Source>DataViews/PyProfileDialog.py</Source>
     <Source>DataViews/__init__.py</Source>
+    <Source>DebugClients/Python/AsyncFile.py</Source>
+    <Source>DebugClients/Python/AsyncIO.py</Source>
+    <Source>DebugClients/Python/DCTestResult.py</Source>
+    <Source>DebugClients/Python/DebugBase.py</Source>
+    <Source>DebugClients/Python/DebugClient.py</Source>
+    <Source>DebugClients/Python/DebugClientBase.py</Source>
+    <Source>DebugClients/Python/DebugClientCapabilities.py</Source>
+    <Source>DebugClients/Python/DebugClientThreads.py</Source>
+    <Source>DebugClients/Python/DebugConfig.py</Source>
+    <Source>DebugClients/Python/DebugProtocol.py</Source>
+    <Source>DebugClients/Python/DebugThread.py</Source>
+    <Source>DebugClients/Python/FlexCompleter.py</Source>
+    <Source>DebugClients/Python/PyProfile.py</Source>
+    <Source>DebugClients/Python/__init__.py</Source>
+    <Source>DebugClients/Python/coverage/__init__.py</Source>
+    <Source>DebugClients/Python/coverage/__main__.py</Source>
+    <Source>DebugClients/Python/coverage/annotate.py</Source>
+    <Source>DebugClients/Python/coverage/backunittest.py</Source>
+    <Source>DebugClients/Python/coverage/backward.py</Source>
+    <Source>DebugClients/Python/coverage/bytecode.py</Source>
+    <Source>DebugClients/Python/coverage/cmdline.py</Source>
+    <Source>DebugClients/Python/coverage/collector.py</Source>
+    <Source>DebugClients/Python/coverage/config.py</Source>
+    <Source>DebugClients/Python/coverage/control.py</Source>
+    <Source>DebugClients/Python/coverage/data.py</Source>
+    <Source>DebugClients/Python/coverage/debug.py</Source>
+    <Source>DebugClients/Python/coverage/env.py</Source>
+    <Source>DebugClients/Python/coverage/execfile.py</Source>
+    <Source>DebugClients/Python/coverage/files.py</Source>
+    <Source>DebugClients/Python/coverage/html.py</Source>
+    <Source>DebugClients/Python/coverage/misc.py</Source>
+    <Source>DebugClients/Python/coverage/monkey.py</Source>
+    <Source>DebugClients/Python/coverage/parser.py</Source>
+    <Source>DebugClients/Python/coverage/phystokens.py</Source>
+    <Source>DebugClients/Python/coverage/pickle2json.py</Source>
+    <Source>DebugClients/Python/coverage/plugin.py</Source>
+    <Source>DebugClients/Python/coverage/plugin_support.py</Source>
+    <Source>DebugClients/Python/coverage/python.py</Source>
+    <Source>DebugClients/Python/coverage/pytracer.py</Source>
+    <Source>DebugClients/Python/coverage/report.py</Source>
+    <Source>DebugClients/Python/coverage/results.py</Source>
+    <Source>DebugClients/Python/coverage/summary.py</Source>
+    <Source>DebugClients/Python/coverage/templite.py</Source>
+    <Source>DebugClients/Python/coverage/test_helpers.py</Source>
+    <Source>DebugClients/Python/coverage/version.py</Source>
+    <Source>DebugClients/Python/coverage/xmlreport.py</Source>
+    <Source>DebugClients/Python/eric6dbgstub.py</Source>
+    <Source>DebugClients/Python/getpass.py</Source>
     <Source>DebugClients/Python3/AsyncFile.py</Source>
     <Source>DebugClients/Python3/AsyncIO.py</Source>
     <Source>DebugClients/Python3/DCTestResult.py</Source>
@@ -75,54 +123,6 @@
     <Source>DebugClients/Python3/coverage/xmlreport.py</Source>
     <Source>DebugClients/Python3/eric6dbgstub.py</Source>
     <Source>DebugClients/Python3/getpass.py</Source>
-    <Source>DebugClients/Python/AsyncFile.py</Source>
-    <Source>DebugClients/Python/AsyncIO.py</Source>
-    <Source>DebugClients/Python/DCTestResult.py</Source>
-    <Source>DebugClients/Python/DebugBase.py</Source>
-    <Source>DebugClients/Python/DebugClient.py</Source>
-    <Source>DebugClients/Python/DebugClientBase.py</Source>
-    <Source>DebugClients/Python/DebugClientCapabilities.py</Source>
-    <Source>DebugClients/Python/DebugClientThreads.py</Source>
-    <Source>DebugClients/Python/DebugConfig.py</Source>
-    <Source>DebugClients/Python/DebugProtocol.py</Source>
-    <Source>DebugClients/Python/DebugThread.py</Source>
-    <Source>DebugClients/Python/FlexCompleter.py</Source>
-    <Source>DebugClients/Python/PyProfile.py</Source>
-    <Source>DebugClients/Python/__init__.py</Source>
-    <Source>DebugClients/Python/coverage/__init__.py</Source>
-    <Source>DebugClients/Python/coverage/__main__.py</Source>
-    <Source>DebugClients/Python/coverage/annotate.py</Source>
-    <Source>DebugClients/Python/coverage/backunittest.py</Source>
-    <Source>DebugClients/Python/coverage/backward.py</Source>
-    <Source>DebugClients/Python/coverage/bytecode.py</Source>
-    <Source>DebugClients/Python/coverage/cmdline.py</Source>
-    <Source>DebugClients/Python/coverage/collector.py</Source>
-    <Source>DebugClients/Python/coverage/config.py</Source>
-    <Source>DebugClients/Python/coverage/control.py</Source>
-    <Source>DebugClients/Python/coverage/data.py</Source>
-    <Source>DebugClients/Python/coverage/debug.py</Source>
-    <Source>DebugClients/Python/coverage/env.py</Source>
-    <Source>DebugClients/Python/coverage/execfile.py</Source>
-    <Source>DebugClients/Python/coverage/files.py</Source>
-    <Source>DebugClients/Python/coverage/html.py</Source>
-    <Source>DebugClients/Python/coverage/misc.py</Source>
-    <Source>DebugClients/Python/coverage/monkey.py</Source>
-    <Source>DebugClients/Python/coverage/parser.py</Source>
-    <Source>DebugClients/Python/coverage/phystokens.py</Source>
-    <Source>DebugClients/Python/coverage/pickle2json.py</Source>
-    <Source>DebugClients/Python/coverage/plugin.py</Source>
-    <Source>DebugClients/Python/coverage/plugin_support.py</Source>
-    <Source>DebugClients/Python/coverage/python.py</Source>
-    <Source>DebugClients/Python/coverage/pytracer.py</Source>
-    <Source>DebugClients/Python/coverage/report.py</Source>
-    <Source>DebugClients/Python/coverage/results.py</Source>
-    <Source>DebugClients/Python/coverage/summary.py</Source>
-    <Source>DebugClients/Python/coverage/templite.py</Source>
-    <Source>DebugClients/Python/coverage/test_helpers.py</Source>
-    <Source>DebugClients/Python/coverage/version.py</Source>
-    <Source>DebugClients/Python/coverage/xmlreport.py</Source>
-    <Source>DebugClients/Python/eric6dbgstub.py</Source>
-    <Source>DebugClients/Python/getpass.py</Source>
     <Source>DebugClients/__init__.py</Source>
     <Source>Debugger/BreakPointModel.py</Source>
     <Source>Debugger/BreakPointViewer.py</Source>
@@ -1289,6 +1289,12 @@
     <Source>WebBrowser/Bookmarks/XbelWriter.py</Source>
     <Source>WebBrowser/Bookmarks/__init__.py</Source>
     <Source>WebBrowser/ClosedTabsManager.py</Source>
+    <Source>WebBrowser/Download/DownloadAskActionDialog.py</Source>
+    <Source>WebBrowser/Download/DownloadItem.py</Source>
+    <Source>WebBrowser/Download/DownloadManager.py</Source>
+    <Source>WebBrowser/Download/DownloadModel.py</Source>
+    <Source>WebBrowser/Download/DownloadUtilities.py</Source>
+    <Source>WebBrowser/Download/__init__.py</Source>
     <Source>WebBrowser/FeaturePermissions/FeaturePermissionBar.py</Source>
     <Source>WebBrowser/FeaturePermissions/FeaturePermissionManager.py</Source>
     <Source>WebBrowser/FeaturePermissions/FeaturePermissionsDialog.py</Source>
@@ -1787,6 +1793,9 @@
     <Form>WebBrowser/Bookmarks/BookmarkPropertiesDialog.ui</Form>
     <Form>WebBrowser/Bookmarks/BookmarksDialog.ui</Form>
     <Form>WebBrowser/Bookmarks/BookmarksImportDialog.ui</Form>
+    <Form>WebBrowser/Download/DownloadAskActionDialog.ui</Form>
+    <Form>WebBrowser/Download/DownloadItem.ui</Form>
+    <Form>WebBrowser/Download/DownloadManager.ui</Form>
     <Form>WebBrowser/FeaturePermissions/FeaturePermissionsDialog.ui</Form>
     <Form>WebBrowser/Feeds/FeedEditDialog.ui</Form>
     <Form>WebBrowser/Feeds/FeedsDialog.ui</Form>
@@ -1847,14 +1856,14 @@
   <Interfaces/>
   <Others>
     <Other>.hgignore</Other>
+    <Other>APIs/Python/zope-2.10.7.api</Other>
+    <Other>APIs/Python/zope-2.11.2.api</Other>
+    <Other>APIs/Python/zope-3.3.1.api</Other>
     <Other>APIs/Python3/PyQt4.bas</Other>
     <Other>APIs/Python3/PyQt5.bas</Other>
     <Other>APIs/Python3/QScintilla2.bas</Other>
     <Other>APIs/Python3/eric6.api</Other>
     <Other>APIs/Python3/eric6.bas</Other>
-    <Other>APIs/Python/zope-2.10.7.api</Other>
-    <Other>APIs/Python/zope-2.11.2.api</Other>
-    <Other>APIs/Python/zope-3.3.1.api</Other>
     <Other>APIs/QSS/qss.api</Other>
     <Other>APIs/Ruby/Ruby-1.8.7.api</Other>
     <Other>APIs/Ruby/Ruby-1.8.7.bas</Other>
@@ -1863,8 +1872,8 @@
     <Other>CSSs</Other>
     <Other>CodeTemplates</Other>
     <Other>DTDs</Other>
+    <Other>DebugClients/Python/coverage/doc</Other>
     <Other>DebugClients/Python3/coverage/doc</Other>
-    <Other>DebugClients/Python/coverage/doc</Other>
     <Other>DesignerTemplates</Other>
     <Other>Dictionaries</Other>
     <Other>Documentation/Help</Other>

eric ide

mercurial