Tue, 23 Feb 2016 20:00:40 +0100
Continued porting the web browser.
- started adding the Downloads stuff
--- /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>&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 &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>&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><b>What do you want to do?</b></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>