WebBrowser/SpellCheck/ManageDictionariesDialog.py

changeset 5870
82e04c70f969
parent 5868
c1a98c164cd3
child 5872
c9a08656feac
equal deleted inserted replaced
5869:70709a460358 5870:82e04c70f969
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2017 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a dialog to install spell checking dictionaries.
8 """
9
10 from __future__ import unicode_literals
11
12 import os
13 import io
14 import zipfile
15 import glob
16 import shutil
17
18 from PyQt5.QtCore import pyqtSlot, Qt, QUrl
19 from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QAbstractButton, \
20 QListWidgetItem
21 from PyQt5.QtNetwork import QNetworkConfigurationManager, QNetworkRequest, \
22 QNetworkReply
23
24 from E5Gui import E5MessageBox
25
26 from .Ui_ManageDictionariesDialog import Ui_ManageDictionariesDialog
27
28 from WebBrowser.WebBrowserWindow import WebBrowserWindow
29
30 import Preferences
31
32
33 class ManageDictionariesDialog(QDialog, Ui_ManageDictionariesDialog):
34 """
35 Class implementing a dialog to install spell checking dictionaries.
36 """
37 FilenameRole = Qt.UserRole
38 UrlRole = Qt.UserRole + 1
39 DocumentationDirRole = Qt.UserRole + 2
40 LocalesRole = Qt.UserRole + 3
41
42 def __init__(self, writeableDirectories, parent=None):
43 """
44 Constructor
45
46 @param writeableDirectories list of writable directories
47 @type list of str
48 @param parent reference to the parent widget
49 @type QWidget
50 """
51 super(ManageDictionariesDialog, self).__init__(parent)
52 self.setupUi(self)
53
54 self.__refreshButton = self.buttonBox.addButton(
55 self.tr("Refresh"), QDialogButtonBox.ActionRole)
56 self.__installButton = self.buttonBox.addButton(
57 self.tr("Install Selected"), QDialogButtonBox.ActionRole)
58 self.__installButton.setEnabled(False)
59 self.__uninstallButton = self.buttonBox.addButton(
60 self.tr("Uninstall Selected"), QDialogButtonBox.ActionRole)
61 self.__uninstallButton.setEnabled(False)
62 self.__cancelButton = self.buttonBox.addButton(
63 self.tr("Cancel"), QDialogButtonBox.ActionRole)
64 self.__cancelButton.setEnabled(False)
65
66 self.locationComboBox.addItems(writeableDirectories)
67
68 self.dictionariesUrlEdit.setText(
69 Preferences.getWebBrowser("SpellCheckDictionariesUrl"))
70
71 if Preferences.getUI("DynamicOnlineCheck"):
72 self.__networkConfigurationManager = \
73 QNetworkConfigurationManager(self)
74 self.__onlineStateChanged(
75 self.__networkConfigurationManager.isOnline())
76 self.__networkConfigurationManager.onlineStateChanged.connect(
77 self.__onlineStateChanged)
78 else:
79 self.__networkConfigurationManager = None
80 self.__onlineStateChanged(True)
81 self.__replies = []
82
83 self.__downloadCancelled = False
84 self.__dictionariesToDownload = []
85
86 self.__populateList()
87
88 @pyqtSlot(bool)
89 def __onlineStateChanged(self, online):
90 """
91 Private slot handling online state changes.
92
93 @param online flag indicating the online status
94 @type bool
95 """
96 self.__refreshButton.setEnabled(online)
97
98 if online:
99 msg = self.tr("Network Status: online")
100 else:
101 msg = self.tr("Network Status: offline")
102 self.statusLabel.setText(msg)
103
104 self.on_dictionariesList_itemSelectionChanged()
105
106 def __isOnline(self):
107 """
108 Private method to check the online status.
109
110 @return flag indicating the online status
111 @rtype bool
112 """
113 if self.__networkConfigurationManager is not None:
114 return self.__networkConfigurationManager.isOnline()
115 else:
116 return True
117
118 @pyqtSlot(QAbstractButton)
119 def on_buttonBox_clicked(self, button):
120 """
121 Private slot to handle the click of a button of the button box.
122
123 @param button reference to the button pressed
124 @type QAbstractButton
125 """
126 if button == self.__refreshButton:
127 self.__populateList()
128 elif button == self.__cancelButton:
129 self.__downloadCancel()
130 elif button == self.__installButton:
131 self.__installSelected()
132 elif button == self.__uninstallButton:
133 self.__uninstallSelected()
134
135 @pyqtSlot()
136 def on_dictionariesList_itemSelectionChanged(self):
137 """
138 Private slot to handle a change of the selection.
139 """
140 self.__installButton.setEnabled(
141 self.locationComboBox.count() > 0 and
142 len(self.dictionariesList.selectedItems()) > 0 and
143 self.__isOnline()
144 )
145
146 self.__uninstallButton.setEnabled(
147 self.locationComboBox.count() > 0 and
148 len([itm
149 for itm in self.dictionariesList.selectedItems()
150 if itm.checkState() == Qt.Checked
151 ])
152 )
153
154 @pyqtSlot(bool)
155 def on_dictionariesUrlEditButton_toggled(self, checked):
156 """
157 Private slot to set the read only status of the dictionaries URL line
158 edit.
159
160 @param checked state of the push button (boolean)
161 """
162 self.dictionariesUrlEdit.setReadOnly(not checked)
163
164 @pyqtSlot(str)
165 def on_locationComboBox_currentTextChanged(self, txt):
166 """
167 Private slot to handle a change of the installation location.
168
169 @param txt installation location
170 @type str
171 """
172 self.__checkInstalledDictionaries()
173
174 def __populateList(self):
175 """
176 Private method to populate the list of available plugins.
177 """
178 self.dictionariesList.clear()
179 self.downloadProgress.setValue(0)
180
181 url = self.dictionariesUrlEdit.text()
182
183 if self.__isOnline():
184 self.__refreshButton.setEnabled(False)
185 self.__installButton.setEnabled(False)
186 self.__uninstallButton.setEnabled(False)
187 self.__cancelButton.setEnabled(True)
188
189 self.statusLabel.setText(url)
190
191 self.__downloadCancelled = False
192
193 request = QNetworkRequest(QUrl(url))
194 request.setAttribute(QNetworkRequest.CacheLoadControlAttribute,
195 QNetworkRequest.AlwaysNetwork)
196 reply = WebBrowserWindow.networkManager().get(request)
197 reply.finished.connect(self.__listFileDownloaded)
198 reply.downloadProgress.connect(self.__downloadProgress)
199 self.__replies.append(reply)
200 else:
201 E5MessageBox.warning(
202 self,
203 self.tr("Error populating list of dictionaries"),
204 self.tr(
205 """<p>Could not download the dictionaries list file"""
206 """ from {0}.</p><p>Error: {1}</p>"""
207 ).format(url, self.tr("Computer is offline.")))
208
209 def __listFileDownloaded(self):
210 """
211 Private method called, after the dictionaries list file has been
212 downloaded from the Internet.
213 """
214 self.__refreshButton.setEnabled(True)
215 self.__cancelButton.setEnabled(False)
216 self.__onlineStateChanged(self.__isOnline())
217
218 self.downloadProgress.setValue(0)
219
220 reply = self.sender()
221 if reply in self.__replies:
222 self.__replies.remove(reply)
223 reply.deleteLater()
224
225 if reply.error() != QNetworkReply.NoError:
226 if not self.__downloadCancelled:
227 E5MessageBox.warning(
228 self,
229 self.tr("Error downloading dictionaries list"),
230 self.tr(
231 """<p>Could not download the dictionaries list"""
232 """ from {0}.</p><p>Error: {1}</p>"""
233 ).format(self.repositoryUrlEdit.text(),
234 reply.errorString())
235 )
236 self.downloadProgress.setValue(0)
237 return
238
239 listFileData = reply.readAll()
240
241 # extract the dictionaries
242 from E5XML.SpellCheckDictionariesReader import \
243 SpellCheckDictionariesReader
244 reader = SpellCheckDictionariesReader(listFileData, self.addEntry)
245 reader.readXML()
246 url = Preferences.getWebBrowser("SpellCheckDictionariesUrl")
247 if url != self.dictionariesUrlEdit.text():
248 self.dictionariesUrlEdit.setText(url)
249 E5MessageBox.warning(
250 self,
251 self.tr("Dictionaries URL Changed"),
252 self.tr(
253 """The URL of the spell check dictionaries has"""
254 """ changed. Select the "Refresh" button to get"""
255 """ the new dictionaries list."""
256 )
257 )
258
259 if self.locationComboBox.count() == 0:
260 # no writable locations available
261 E5MessageBox.warning(
262 self,
263 self.tr("Error installing dictionaries"),
264 self.tr(
265 """<p>None of the dictionary locations is writable by"""
266 """ you. Please download required dictionaries manually"""
267 """ and install them as administrator.</p>"""
268 )
269 )
270
271 self.__checkInstalledDictionaries()
272
273 def __downloadCancel(self):
274 """
275 Private slot to cancel the current download.
276 """
277 if self.__replies:
278 reply = self.__replies[0]
279 self.__downloadCancelled = True
280 self.__dictionariesToDownload = []
281 reply.abort()
282
283 def __downloadProgress(self, done, total):
284 """
285 Private slot to show the download progress.
286
287 @param done number of bytes downloaded so far
288 @type int
289 @param total total bytes to be downloaded
290 @type int
291 """
292 if total:
293 self.downloadProgress.setMaximum(total)
294 self.downloadProgress.setValue(done)
295
296 def addEntry(self, short, filename, url, documentationDir, locales):
297 """
298 Public method to add an entry to the list.
299
300 @param short data for the description field
301 @type str
302 @param filename data for the filename field
303 @type str
304 @param url download URL for the dictionary entry
305 @type str
306 @param documentationDir name of the directory containing the
307 dictionary documentation
308 @type str
309 @param locales list of locales
310 @type list of str
311 """
312 itm = QListWidgetItem(
313 self.tr("{0} ({1})").format(short, " ".join(locales)),
314 self.dictionariesList)
315 itm.setCheckState(Qt.Unchecked)
316
317 itm.setData(ManageDictionariesDialog.FilenameRole, filename)
318 itm.setData(ManageDictionariesDialog.UrlRole, url)
319 itm.setData(ManageDictionariesDialog.DocumentationDirRole,
320 documentationDir)
321 itm.setData(ManageDictionariesDialog.LocalesRole, locales)
322
323 def __checkInstalledDictionaries(self):
324 """
325 Private method to check all installed dictionaries.
326
327 Note: A dictionary is assumed to be installed, if at least one of its
328 binary dictionaries (*.bdic) is found in the selected dictionaries
329 location.
330 """
331 if self.locationComboBox.currentText():
332 installedLocales = set([
333 os.path.splitext(os.path.basename(dic))[0]
334 for dic in glob.glob(
335 os.path.join(self.locationComboBox.currentText(), "*.bdic")
336 )
337 ])
338
339 for row in range(self.dictionariesList.count()):
340 itm = self.dictionariesList.item(row)
341 locales = set(itm.data(ManageDictionariesDialog.LocalesRole))
342 if locales.intersection(installedLocales):
343 itm.setCheckState(Qt.Checked)
344 else:
345 itm.setCheckState(Qt.Unchecked)
346 else:
347 for row in range(self.dictionariesList.count()):
348 itm = self.dictionariesList.item(row)
349 itm.setCheckState(Qt.Unchecked)
350
351 def __installSelected(self):
352 """
353 Private method to install the selected dictionaries.
354 """
355 if self.__isOnline() and bool(self.locationComboBox.currentText()):
356 self.__dictionariesToDownload = [
357 itm.data(ManageDictionariesDialog.UrlRole)
358 for itm in self.dictionariesList.selectedItems()
359 ]
360
361 self.__refreshButton.setEnabled(False)
362 self.__installButton.setEnabled(False)
363 self.__uninstallButton.setEnabled(False)
364 self.__cancelButton.setEnabled(True)
365
366 self.__downloadCancelled = False
367
368 self.__downloadDictionary()
369
370 def __downloadDictionary(self):
371 """
372 Private slot to download a dictionary.
373 """
374 if self.__isOnline():
375 if self.__dictionariesToDownload:
376 url = self.__dictionariesToDownload.pop(0)
377 self.statusLabel.setText(url)
378
379 self.__downloadCancelled = False
380
381 request = QNetworkRequest(QUrl(url))
382 request.setAttribute(QNetworkRequest.CacheLoadControlAttribute,
383 QNetworkRequest.AlwaysNetwork)
384 reply = WebBrowserWindow.networkManager().get(request)
385 reply.finished.connect(self.__installDictionary)
386 reply.downloadProgress.connect(self.__downloadProgress)
387 self.__replies.append(reply)
388 else:
389 self.__installationFinished()
390 else:
391 E5MessageBox.warning(
392 self,
393 self.tr("Error downloading dictionary"),
394 self.tr(
395 """<p>Could not download the requested dictionary file"""
396 """ from {0}.</p><p>Error: {1}</p>"""
397 ).format(url, self.tr("Computer is offline.")))
398
399 self.__installationFinished()
400
401 def __installDictionary(self):
402 """
403 Private slot to install the downloaded dictionary.
404 """
405 reply = self.sender()
406 if reply in self.__replies:
407 self.__replies.remove(reply)
408 reply.deleteLater()
409
410 if reply.error() != QNetworkReply.NoError:
411 if not self.__downloadCancelled:
412 E5MessageBox.warning(
413 self,
414 self.tr("Error downloading dictionary file"),
415 self.tr(
416 """<p>Could not download the requested dictionary"""
417 """ file from {0}.</p><p>Error: {1}</p>"""
418 ).format(reply.url(), reply.errorString())
419 )
420 self.downloadProgress.setValue(0)
421 return
422
423 archiveData = reply.readAll()
424 archiveFile = io.BytesIO(bytes(archiveData))
425 archive = zipfile.ZipFile(archiveFile, "r")
426 if archive.testzip() is not None:
427 E5MessageBox.critical(
428 self,
429 self.tr("Error downloading dictionary"),
430 self.tr(
431 """<p>The downloaded dictionary archive is invalid."""
432 """ Skipping it.</p>""")
433 )
434 else:
435 installDir = self.locationComboBox.currentText()
436 archive.extractall(installDir)
437
438 if self.__dictionariesToDownload:
439 self.__downloadDictionary()
440 else:
441 self.__installationFinished()
442
443 def __installationFinished(self):
444 """
445 Private method called after all selected dictionaries have been
446 installed.
447 """
448 self.__refreshButton.setEnabled(True)
449 self.__cancelButton.setEnabled(False)
450 self.__onlineStateChanged(self.__isOnline())
451
452 self.dictionariesList.clearSelection()
453 self.downloadProgress.setValue(0)
454
455 self.__checkInstalledDictionaries()
456
457 def __uninstallSelected(self):
458 """
459 Private method to uninstall the selected dictionaries.
460 """
461 installLocation = self.locationComboBox.currentText()
462 if not installLocation:
463 return
464
465 itemsToDelete = [
466 itm
467 for itm in self.dictionariesList.selectedItems()
468 if itm.checkState() == Qt.Checked
469 ]
470 for itm in itemsToDelete:
471 documentationDir = itm.data(
472 ManageDictionariesDialog.DocumentationDirRole)
473 shutil.rmtree(os.path.join(installLocation, documentationDir),
474 True)
475
476 locales = itm.data(ManageDictionariesDialog.LocalesRole)
477 for locale in locales:
478 bdic = os.path.join(installLocation, locale + ".bdic")
479 try:
480 os.remove(bdic)
481 except OSError:
482 # ignore silently
483 pass
484
485 self.dictionariesList.clearSelection()
486
487 self.__checkInstalledDictionaries()

eric ide

mercurial