Sat, 02 Sep 2017 20:00:57 +0200
Started implementing a downloader and installer for web browser spell check dictionaries.
--- a/E5XML/Config.py Sat Sep 02 19:05:28 2017 +0200 +++ b/E5XML/Config.py Sat Sep 02 20:00:57 2017 +0200 @@ -37,5 +37,8 @@ # version number of the highlighting styles file highlightingStylesFileFormatVersion = "4.3" +# version number of the web browser spell check dictionaries list file +dictionariesListFileFormatVersion = "1.0" + # # eflag: noqa = M702
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/E5XML/SpellCheckDictionariesReader.py Sat Sep 02 20:00:57 2017 +0200 @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module to read the web browser spell check dictionaries list file. +""" + +from __future__ import unicode_literals + +from .Config import dictionariesListFileFormatVersion +from .XMLStreamReaderBase import XMLStreamReaderBase + +import Preferences + + +class SpellCheckDictionariesReader(XMLStreamReaderBase): + """ + Class to read the web browser spell check dictionaries list file. + """ + supportedVersions = ["1.0",] + + def __init__(self, data, entryCallback): + """ + Constructor + + @param data reference to the data array to read XML from (QByteArray) + @param entryCallback reference to a function to be called once the + data for a dictionary has been read (function) + """ + XMLStreamReaderBase.__init__(self, data) + + self.__entryCallback = entryCallback + + self.version = "" + + def readXML(self): + """ + Public method to read and parse the XML document. + """ + while not self.atEnd(): + self.readNext() + if self.isStartElement(): + if self.name() == "Dictionaries": + self.version = self.attribute( + "version", + dictionariesListFileFormatVersion) + if self.version not in self.supportedVersions: + self.raiseUnsupportedFormatVersion(self.version) + elif self.name() == "DictionariesUrl": + url = self.readElementText() + Preferences.setWebBrowser("SpellCheckDictionariesUrl", url) + elif self.name() == "Dictionary": + self.__readDictionary() + else: + self._skipUnknownElement() + + self.showErrorMessage() + + def __readDictionary(self): + """ + Private method to read the plug-in info. + """ + dictionaryInfo = {"short": "", + "filename": "", + } + + while not self.atEnd(): + self.readNext() + if self.isEndElement() and self.name() == "Dictionary": + self.__entryCallback( + dictionaryInfo["short"], dictionaryInfo["filename"]) + break + + if self.isStartElement(): + if self.name() == "Short": + dictionaryInfo["short"] = self.readElementText() + elif self.name() == "Filename": + dictionaryInfo["filename"] = self.readElementText() + else: + self.raiseUnexpectedStartTag(self.name())
--- a/PluginManager/PluginRepositoryDialog.py Sat Sep 02 19:05:28 2017 +0200 +++ b/PluginManager/PluginRepositoryDialog.py Sat Sep 02 20:00:57 2017 +0200 @@ -482,12 +482,11 @@ def __downloadFileDone(self): """ Private method called, after the file has been downloaded - from the internet. + from the Internet. """ self.__updateButton.setEnabled(True) self.__downloadCancelButton.setEnabled(False) - self.__onlineStateChanged( - self.__isOnline()) + self.__onlineStateChanged(self.__isOnline()) ok = True reply = self.sender() @@ -625,7 +624,7 @@ def __updateStatus(self, filename, version): """ - Private method to check, if the given archive update status. + Private method to check the given archive update status. @param filename data for the filename field (string) @param version data for the version field (string)
--- a/Preferences/ConfigurationPages/WebBrowserSpellCheckingPage.py Sat Sep 02 19:05:28 2017 +0200 +++ b/Preferences/ConfigurationPages/WebBrowserSpellCheckingPage.py Sat Sep 02 20:00:57 2017 +0200 @@ -9,6 +9,8 @@ from __future__ import unicode_literals +import os + from PyQt5.QtCore import pyqtSlot, Qt, QCoreApplication, QDir, QLibraryInfo, \ QLocale from PyQt5.QtWidgets import QListWidgetItem @@ -39,7 +41,7 @@ self.on_spellCheckEnabledCheckBox_clicked() if Globals.isMacPlatform(): - dictionaryDirectories = { + self.__dictionaryDirectories = { QDir.cleanPath( QCoreApplication.applicationDirPath() + "/../Resources/qtwebengine_dictionaries"), @@ -49,7 +51,7 @@ "/Resources/qtwebengine_dictionaries"), } else: - dictionaryDirectories = { + self.__dictionaryDirectories = { QDir.cleanPath( QCoreApplication.applicationDirPath() + "/qtwebengine_dictionaries"), @@ -58,9 +60,23 @@ "/qtwebengine_dictionaries"), } self.spellCheckDictionaryDirectoriesEdit.setPlainText( - "\n".join(dictionaryDirectories)) + "\n".join(self.__dictionaryDirectories)) + + self.__writeableDirectories = [] + for directory in self.__dictionaryDirectories: + if os.access(directory, os.W_OK): + self.__writeableDirectories.append(directory) + self.installButton.setEnabled(bool(self.__writeableDirectories)) - for path in dictionaryDirectories: + self.__populateDictionariesList() + + def __populateDictionariesList(self): + """ + Private method to populate the spell checking dictionaries list. + """ + self.spellCheckLanguagesList.clear() + + for path in self.__dictionaryDirectories: directory = QDir(path) fileNames = directory.entryList(["*.bdic"]) for fileName in fileNames: @@ -91,7 +107,9 @@ if self.spellCheckLanguagesList.count(): self.noLanguagesLabel.hide() + self.spellCheckLanguagesList.show() else: + self.noLanguagesLabel.show() self.spellCheckLanguagesList.hide() def save(self): @@ -138,6 +156,19 @@ lang = QLocale.languageToString(loc.language()) languageString = "{0}/{1} [{2}]".format(lang, country, language) return languageString + + @pyqtSlot() + def on_installButton_clicked(self): + """ + Private slot to install spell checking dictionaries. + """ + from WebBrowser.SpellCheck.InstallDictionariesDialog import \ + InstallDictionariesDialog + dlg = InstallDictionariesDialog(self.__writeableDirectories, self) + dlg.exec_() + # TODO: Implement this dialog + + self.__populateDictionariesList() def create(dlg):
--- a/Preferences/ConfigurationPages/WebBrowserSpellCheckingPage.ui Sat Sep 02 19:05:28 2017 +0200 +++ b/Preferences/ConfigurationPages/WebBrowserSpellCheckingPage.ui Sat Sep 02 20:00:57 2017 +0200 @@ -7,7 +7,7 @@ <x>0</x> <y>0</y> <width>499</width> - <height>550</height> + <height>583</height> </rect> </property> <layout class="QVBoxLayout" name="verticalLayout_3"> @@ -102,6 +102,46 @@ </widget> </item> <item> + <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="QPushButton" name="installButton"> + <property name="toolTip"> + <string>Press to open a dialog to install spell checking dictionaries</string> + </property> + <property name="text"> + <string>Install Dictionaries...</string> + </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> <spacer name="verticalSpacer"> <property name="orientation"> <enum>Qt::Vertical</enum>
--- a/Preferences/__init__.py Sat Sep 02 19:05:28 2017 +0200 +++ b/Preferences/__init__.py Sat Sep 02 20:00:57 2017 +0200 @@ -1090,6 +1090,9 @@ # Spell Checking "SpellCheckEnabled": False, "SpellCheckLanguages": [], + "SpellCheckDictionariesUrl": + "https://eric-ide.python-projects.org/qwebengine_dictionaries/" \ + "dictionaries.xml", # Sync "SyncEnabled": False, "SyncBookmarks": True,
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/SpellCheck/InstallDictionariesDialog.py Sat Sep 02 20:00:57 2017 +0200 @@ -0,0 +1,270 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a dialog to install spell checking dictionaries. +""" + +from __future__ import unicode_literals + +from PyQt5.QtCore import pyqtSlot, Qt, QUrl +from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QAbstractButton, \ + QListWidgetItem +from PyQt5.QtNetwork import QNetworkConfigurationManager, QNetworkRequest, \ + QNetworkReply + +from E5Gui import E5MessageBox + +from .Ui_InstallDictionariesDialog import Ui_InstallDictionariesDialog + +from WebBrowser.WebBrowserWindow import WebBrowserWindow + +import Preferences + + +class InstallDictionariesDialog(QDialog, Ui_InstallDictionariesDialog): + """ + Class implementing a dialog to install spell checking dictionaries. + """ + FilenameRole = Qt.UserRole + + def __init__(self, writeableDirectories, parent=None): + """ + Constructor + + @param writeableDirectories list of writable directories + @type list of str + @param parent reference to the parent widget + @type QWidget + """ + super(InstallDictionariesDialog, self).__init__(parent) + self.setupUi(self) + + self.__refreshButton = self.buttonBox.addButton( + self.tr("Refresh"), QDialogButtonBox.ActionRole) + self.__installButton = self.buttonBox.addButton( + self.tr("Install Selected"), QDialogButtonBox.ActionRole) + self.__installButton.setEnabled(False) + self.__cancelButton = self.buttonBox.addButton( + self.tr("Cancel"), QDialogButtonBox.ActionRole) + self.__cancelButton.setEnabled(False) + + self.locationComboBox.addItems(writeableDirectories) + + self.dictionariesUrlEdit.setText( + Preferences.getWebBrowser("SpellCheckDictionariesUrl")) + + if Preferences.getUI("DynamicOnlineCheck"): + self.__networkConfigurationManager = \ + QNetworkConfigurationManager(self) + self.__onlineStateChanged( + self.__networkConfigurationManager.isOnline()) + self.__networkConfigurationManager.onlineStateChanged.connect( + self.__onlineStateChanged) + else: + self.__networkConfigurationManager = None + self.__onlineStateChanged(True) + self.__replies = [] + + self.__downloadCancelled = False + self.__dictionariesToDownload = [] + + self.__populateList() + + @pyqtSlot(bool) + def __onlineStateChanged(self, online): + """ + Private slot handling online state changes. + + @param online flag indicating the online status + @type bool + """ + self.__refreshButton.setEnabled(online) + if online: + msg = self.tr("Network Status: online") + else: + msg = self.tr("Network Status: offline") + self.statusLabel.setText(msg) + + def __isOnline(self): + """ + Private method to check the online status. + + @return flag indicating the online status + @rtype bool + """ + if self.__networkConfigurationManager is not None: + return self.__networkConfigurationManager.isOnline() + else: + return True + + @pyqtSlot(QAbstractButton) + def on_buttonBox_clicked(self, button): + """ + Private slot to handle the click of a button of the button box. + + @param button reference to the button pressed + @type QAbstractButton + """ + if button == self.__refreshButton: + self.__populateList() + elif button == self.__cancelButton: + self.__downloadCancel() + elif button == self.__installButton: + self.__installSelected() + + @pyqtSlot() + def on_dictionariesList_itemSelectionChanged(self): + """ + Private slot to handle a change of the selection. + """ + self.__installButton.setEnabled( + len(self.dictionariesList.selectedItems()) > 0) + + def __populateList(self): + """ + Private method to populate the list of available plugins. + """ + self.dictionariesList.clear() + self.downloadProgress.setValue(0) + + if self.__isOnline(): + self.__refreshButton.setEnabled(False) + self.__installButton.setEnabled(False) + self.__cancelButton.setEnabled(True) + + url = self.dictionariesUrlEdit.text() + self.statusLabel.setText(url) + + self.__downloadCancelled = False + + request = QNetworkRequest(QUrl(url)) + request.setAttribute(QNetworkRequest.CacheLoadControlAttribute, + QNetworkRequest.AlwaysNetwork) + reply = WebBrowserWindow.networkManager().get(request) + reply.finished.connect(self.__listFileDownloaded) + reply.downloadProgress.connect(self.__downloadProgress) + self.__replies.append(reply) + + def __listFileDownloaded(self): + """ + Private method called, after the dictionaries list file has been + downloaded from the Internet. + """ + self.__refreshButton.setEnabled(True) + self.__cancelButton.setEnabled(False) + self.__onlineStateChanged(self.__isOnline()) + + reply = self.sender() + if reply in self.__replies: + self.__replies.remove(reply) + if reply.error() != QNetworkReply.NoError: + if not self.__downloadCancelled: + E5MessageBox.warning( + self, + self.tr("Error downloading dictionaries list"), + self.tr( + """<p>Could not download the dictionaries list""" + """ from {0}.</p><p>Error: {1}</p>""" + ).format(self.repositoryUrlEdit.text(), + reply.errorString()) + ) + self.downloadProgress.setValue(0) + reply.deleteLater() + return + + listFileData = reply.readAll() + reply.deleteLater() + + # extract the dictionaries + from E5XML.SpellCheckDictionariesReader import \ + SpellCheckDictionariesReader + reader = SpellCheckDictionariesReader(listFileData, self.addEntry) + reader.readXML() + url = Preferences.getWebBrowser("SpellCheckDictionariesUrl") + if url != self.dictionariesUrlEdit.text(): + self.dictionariesUrlEdit.setText(url) + E5MessageBox.warning( + self, + self.tr("Dictionaries URL Changed"), + self.tr( + """The URL of the spell check dictionaries has""" + """ changed. Select the "Refresh" button to get""" + """ the new dictionaries list.""")) + + def __downloadCancel(self): + """ + Private slot to cancel the current download. + """ + if self.__replies: + reply = self.__replies[0] + self.__downloadCancelled = True + self.__dictionariesToDownload = [] + reply.abort() + + def __downloadProgress(self, done, total): + """ + Private slot to show the download progress. + + @param done number of bytes downloaded so far + @type int + @param total total bytes to be downloaded + @type int + """ + if total: + self.downloadProgress.setMaximum(total) + self.downloadProgress.setValue(done) + + def addEntry(self, short, filename): + """ + Public method to add an entry to the list. + + @param short data for the description field + @type str + @param filename data for the filename field + @type str + """ + itm = QListWidgetItem(short, self.dictionariesList) + itm.setData(InstallDictionariesDialog.FilenameRole, filename) + + def __installSelected(self): + """ + Private method to install the selected dictionaries. + """ + self.__dictionariesToDownload = [ + itm.data(InstallDictionariesDialog.FilenameRole) + for itm in self.dictionariesList.selectedItems() + ] + + self.__refreshButton.setEnabled(False) + self.__installButton.setEnabled(False) + self.__cancelButton.setEnabled(True) + + self.__downloadCancelled = False + + self.__downloadDictionary() + + def __downloadDictionary(self): + """ + Private slot to download a dictionary. + """ + # TODO: implement this + # use __installDictionary as finish slot + + def __installDictionary(self): + """ + Private slot to install the downloaded dictionary. + """ + # TODO: implement this + + if not bool(self.__dictionariesToDownload): + self.__installationFinished() + + def __installationFinished(self): + """ + Private method called after all selected dictionaries have been + installed. + """ + # TODO: implement this
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/SpellCheck/InstallDictionariesDialog.ui Sat Sep 02 20:00:57 2017 +0200 @@ -0,0 +1,171 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>InstallDictionariesDialog</class> + <widget class="QDialog" name="InstallDictionariesDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>676</width> + <height>653</height> + </rect> + </property> + <property name="windowTitle"> + <string>Spellcheck Dictionaries</string> + </property> + <property name="sizeGripEnabled"> + <bool>true</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Installation Location:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="locationComboBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Select the location for the dictionaries installation</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QListWidget" name="dictionariesList"> + <property name="toolTip"> + <string>Shows the list of available dictionaries</string> + </property> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="selectionMode"> + <enum>QAbstractItemView::ExtendedSelection</enum> + </property> + <property name="sortingEnabled"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="Line" name="line"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QProgressBar" name="downloadProgress"> + <property name="toolTip"> + <string>Shows the progress of the current download</string> + </property> + <property name="value"> + <number>0</number> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="statusLabel"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Dictionaries URL:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="dictionariesUrlEdit"> + <property name="toolTip"> + <string>Shows the dictionaries URL</string> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="dictionariesUrlEditButton"> + <property name="toolTip"> + <string>Press to edit the dictionaries URL</string> + </property> + <property name="text"> + <string>Edit URL</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Close</set> + </property> + </widget> + </item> + </layout> + </widget> + <tabstops> + <tabstop>locationComboBox</tabstop> + <tabstop>dictionariesList</tabstop> + <tabstop>dictionariesUrlEdit</tabstop> + <tabstop>dictionariesUrlEditButton</tabstop> + </tabstops> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>InstallDictionariesDialog</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>InstallDictionariesDialog</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/SpellCheck/__init__.py Sat Sep 02 20:00:57 2017 +0200 @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Detlev Offenbach <detlev@die-offenbachs.de> +# + + +""" +Package implementing Spell Checking related modules. +"""
--- a/eric6.e4p Sat Sep 02 19:05:28 2017 +0200 +++ b/eric6.e4p Sat Sep 02 20:00:57 2017 +0200 @@ -184,6 +184,7 @@ <Source>E5XML/SessionWriter.py</Source> <Source>E5XML/ShortcutsReader.py</Source> <Source>E5XML/ShortcutsWriter.py</Source> + <Source>E5XML/SpellCheckDictionariesReader.py</Source> <Source>E5XML/TasksReader.py</Source> <Source>E5XML/TasksWriter.py</Source> <Source>E5XML/TemplatesReader.py</Source> @@ -1427,6 +1428,8 @@ <Source>WebBrowser/SpeedDial/SpeedDialReader.py</Source> <Source>WebBrowser/SpeedDial/SpeedDialWriter.py</Source> <Source>WebBrowser/SpeedDial/__init__.py</Source> + <Source>WebBrowser/SpellCheck/InstallDictionariesDialog.py</Source> + <Source>WebBrowser/SpellCheck/__init__.py</Source> <Source>WebBrowser/StatusBar/ImagesIcon.py</Source> <Source>WebBrowser/StatusBar/JavaScriptIcon.py</Source> <Source>WebBrowser/StatusBar/JavaScriptSettingsDialog.py</Source> @@ -1948,6 +1951,7 @@ <Form>WebBrowser/SearchWidget.ui</Form> <Form>WebBrowser/Session/SessionManagerDialog.ui</Form> <Form>WebBrowser/SiteInfo/SiteInfoDialog.ui</Form> + <Form>WebBrowser/SpellCheck/InstallDictionariesDialog.ui</Form> <Form>WebBrowser/StatusBar/JavaScriptSettingsDialog.ui</Form> <Form>WebBrowser/Sync/SyncCheckPage.ui</Form> <Form>WebBrowser/Sync/SyncDataPage.ui</Form>