|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2007 - 2019 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the Plugin installation dialog. |
|
8 """ |
|
9 |
|
10 from __future__ import unicode_literals |
|
11 |
|
12 import os |
|
13 import sys |
|
14 import shutil |
|
15 import zipfile |
|
16 import compileall |
|
17 import glob |
|
18 try: # Py3 |
|
19 import urllib.parse as parse |
|
20 except (ImportError): |
|
21 import urlparse as parse # __IGNORE_WARNING__ |
|
22 |
|
23 from PyQt5.QtCore import pyqtSlot, Qt, QDir, QFileInfo |
|
24 from PyQt5.QtWidgets import QWidget, QDialogButtonBox, QAbstractButton, \ |
|
25 QApplication, QDialog, QVBoxLayout |
|
26 |
|
27 from E5Gui import E5FileDialog |
|
28 from E5Gui.E5MainWindow import E5MainWindow |
|
29 |
|
30 from .Ui_PluginInstallDialog import Ui_PluginInstallDialog |
|
31 |
|
32 import Utilities |
|
33 import Preferences |
|
34 |
|
35 from Utilities.uic import compileUiFiles |
|
36 |
|
37 |
|
38 class PluginInstallWidget(QWidget, Ui_PluginInstallDialog): |
|
39 """ |
|
40 Class implementing the Plugin installation dialog. |
|
41 """ |
|
42 def __init__(self, pluginManager, pluginFileNames, parent=None): |
|
43 """ |
|
44 Constructor |
|
45 |
|
46 @param pluginManager reference to the plugin manager object |
|
47 @param pluginFileNames list of plugin files suggested for |
|
48 installation (list of strings) |
|
49 @param parent parent of this dialog (QWidget) |
|
50 """ |
|
51 super(PluginInstallWidget, self).__init__(parent) |
|
52 self.setupUi(self) |
|
53 |
|
54 if pluginManager is None: |
|
55 # started as external plugin installer |
|
56 from .PluginManager import PluginManager |
|
57 self.__pluginManager = PluginManager(doLoadPlugins=False) |
|
58 self.__external = True |
|
59 else: |
|
60 self.__pluginManager = pluginManager |
|
61 self.__external = False |
|
62 |
|
63 self.__backButton = self.buttonBox.addButton( |
|
64 self.tr("< Back"), QDialogButtonBox.ActionRole) |
|
65 self.__nextButton = self.buttonBox.addButton( |
|
66 self.tr("Next >"), QDialogButtonBox.ActionRole) |
|
67 self.__finishButton = self.buttonBox.addButton( |
|
68 self.tr("Install"), QDialogButtonBox.ActionRole) |
|
69 |
|
70 self.__closeButton = self.buttonBox.button(QDialogButtonBox.Close) |
|
71 self.__cancelButton = self.buttonBox.button(QDialogButtonBox.Cancel) |
|
72 |
|
73 userDir = self.__pluginManager.getPluginDir("user") |
|
74 if userDir is not None: |
|
75 self.destinationCombo.addItem( |
|
76 self.tr("User plugins directory"), |
|
77 userDir) |
|
78 |
|
79 globalDir = self.__pluginManager.getPluginDir("global") |
|
80 if globalDir is not None and os.access(globalDir, os.W_OK): |
|
81 self.destinationCombo.addItem( |
|
82 self.tr("Global plugins directory"), |
|
83 globalDir) |
|
84 |
|
85 self.__installedDirs = [] |
|
86 self.__installedFiles = [] |
|
87 |
|
88 self.__restartNeeded = False |
|
89 |
|
90 downloadDir = QDir(Preferences.getPluginManager("DownloadPath")) |
|
91 for pluginFileName in pluginFileNames: |
|
92 fi = QFileInfo(pluginFileName) |
|
93 if fi.isRelative(): |
|
94 pluginFileName = QFileInfo( |
|
95 downloadDir, fi.fileName()).absoluteFilePath() |
|
96 self.archivesList.addItem(pluginFileName) |
|
97 self.archivesList.sortItems() |
|
98 |
|
99 self.__currentIndex = 0 |
|
100 self.__selectPage() |
|
101 |
|
102 def restartNeeded(self): |
|
103 """ |
|
104 Public method to check, if a restart of the IDE is required. |
|
105 |
|
106 @return flag indicating a restart is required (boolean) |
|
107 """ |
|
108 return self.__restartNeeded |
|
109 |
|
110 def __createArchivesList(self): |
|
111 """ |
|
112 Private method to create a list of plugin archive names. |
|
113 |
|
114 @return list of plugin archive names (list of strings) |
|
115 """ |
|
116 archivesList = [] |
|
117 for row in range(self.archivesList.count()): |
|
118 archivesList.append(self.archivesList.item(row).text()) |
|
119 return archivesList |
|
120 |
|
121 def __selectPage(self): |
|
122 """ |
|
123 Private method to show the right wizard page. |
|
124 """ |
|
125 self.wizard.setCurrentIndex(self.__currentIndex) |
|
126 if self.__currentIndex == 0: |
|
127 self.__backButton.setEnabled(False) |
|
128 self.__nextButton.setEnabled(self.archivesList.count() > 0) |
|
129 self.__finishButton.setEnabled(False) |
|
130 self.__closeButton.hide() |
|
131 self.__cancelButton.show() |
|
132 elif self.__currentIndex == 1: |
|
133 self.__backButton.setEnabled(True) |
|
134 self.__nextButton.setEnabled(self.destinationCombo.count() > 0) |
|
135 self.__finishButton.setEnabled(False) |
|
136 self.__closeButton.hide() |
|
137 self.__cancelButton.show() |
|
138 else: |
|
139 self.__backButton.setEnabled(True) |
|
140 self.__nextButton.setEnabled(False) |
|
141 self.__finishButton.setEnabled(True) |
|
142 self.__closeButton.hide() |
|
143 self.__cancelButton.show() |
|
144 |
|
145 msg = self.tr( |
|
146 "Plugin ZIP-Archives:\n{0}\n\nDestination:\n{1} ({2})")\ |
|
147 .format("\n".join(self.__createArchivesList()), |
|
148 self.destinationCombo.currentText(), |
|
149 self.destinationCombo.itemData( |
|
150 self.destinationCombo.currentIndex()) |
|
151 ) |
|
152 self.summaryEdit.setPlainText(msg) |
|
153 |
|
154 @pyqtSlot() |
|
155 def on_addArchivesButton_clicked(self): |
|
156 """ |
|
157 Private slot to select plugin ZIP-archives via a file selection dialog. |
|
158 """ |
|
159 dn = Preferences.getPluginManager("DownloadPath") |
|
160 archives = E5FileDialog.getOpenFileNames( |
|
161 self, |
|
162 self.tr("Select plugin ZIP-archives"), |
|
163 dn, |
|
164 self.tr("Plugin archive (*.zip)")) |
|
165 |
|
166 if archives: |
|
167 matchflags = Qt.MatchFixedString |
|
168 if not Utilities.isWindowsPlatform(): |
|
169 matchflags |= Qt.MatchCaseSensitive |
|
170 for archive in archives: |
|
171 if len(self.archivesList.findItems(archive, matchflags)) == 0: |
|
172 # entry not in list already |
|
173 self.archivesList.addItem(archive) |
|
174 self.archivesList.sortItems() |
|
175 |
|
176 self.__nextButton.setEnabled(self.archivesList.count() > 0) |
|
177 |
|
178 @pyqtSlot() |
|
179 def on_archivesList_itemSelectionChanged(self): |
|
180 """ |
|
181 Private slot called, when the selection of the archives list changes. |
|
182 """ |
|
183 self.removeArchivesButton.setEnabled( |
|
184 len(self.archivesList.selectedItems()) > 0) |
|
185 |
|
186 @pyqtSlot() |
|
187 def on_removeArchivesButton_clicked(self): |
|
188 """ |
|
189 Private slot to remove archives from the list. |
|
190 """ |
|
191 for archiveItem in self.archivesList.selectedItems(): |
|
192 itm = self.archivesList.takeItem( |
|
193 self.archivesList.row(archiveItem)) |
|
194 del itm |
|
195 |
|
196 self.__nextButton.setEnabled(self.archivesList.count() > 0) |
|
197 |
|
198 @pyqtSlot(QAbstractButton) |
|
199 def on_buttonBox_clicked(self, button): |
|
200 """ |
|
201 Private slot to handle the click of a button of the button box. |
|
202 |
|
203 @param button reference to the button pressed (QAbstractButton) |
|
204 """ |
|
205 if button == self.__backButton: |
|
206 self.__currentIndex -= 1 |
|
207 self.__selectPage() |
|
208 elif button == self.__nextButton: |
|
209 self.__currentIndex += 1 |
|
210 self.__selectPage() |
|
211 elif button == self.__finishButton: |
|
212 self.__finishButton.setEnabled(False) |
|
213 self.__installPlugins() |
|
214 if not Preferences.getPluginManager("ActivateExternal"): |
|
215 Preferences.setPluginManager("ActivateExternal", True) |
|
216 self.__restartNeeded = True |
|
217 self.__closeButton.show() |
|
218 self.__cancelButton.hide() |
|
219 |
|
220 def __installPlugins(self): |
|
221 """ |
|
222 Private method to install the selected plugin archives. |
|
223 |
|
224 @return flag indicating success (boolean) |
|
225 """ |
|
226 res = True |
|
227 self.summaryEdit.clear() |
|
228 for archive in self.__createArchivesList(): |
|
229 self.summaryEdit.append( |
|
230 self.tr("Installing {0} ...").format(archive)) |
|
231 ok, msg, restart = self.__installPlugin(archive) |
|
232 res = res and ok |
|
233 if ok: |
|
234 self.summaryEdit.append(self.tr(" ok")) |
|
235 else: |
|
236 self.summaryEdit.append(msg) |
|
237 if restart: |
|
238 self.__restartNeeded = True |
|
239 self.summaryEdit.append("\n") |
|
240 if res: |
|
241 self.summaryEdit.append(self.tr( |
|
242 """The plugins were installed successfully.""")) |
|
243 else: |
|
244 self.summaryEdit.append(self.tr( |
|
245 """Some plugins could not be installed.""")) |
|
246 |
|
247 return res |
|
248 |
|
249 def __installPlugin(self, archiveFilename): |
|
250 """ |
|
251 Private slot to install the selected plugin. |
|
252 |
|
253 @param archiveFilename name of the plugin archive |
|
254 file (string) |
|
255 @return flag indicating success (boolean), error message |
|
256 upon failure (string) and flag indicating a restart |
|
257 of the IDE is required (boolean) |
|
258 """ |
|
259 installedPluginName = "" |
|
260 |
|
261 archive = archiveFilename |
|
262 destination = self.destinationCombo.itemData( |
|
263 self.destinationCombo.currentIndex()) |
|
264 |
|
265 # check if archive is a local url |
|
266 url = parse.urlparse(archive) |
|
267 if url[0].lower() == 'file': |
|
268 archive = url[2] |
|
269 |
|
270 # check, if the archive exists |
|
271 if not os.path.exists(archive): |
|
272 return False, \ |
|
273 self.tr( |
|
274 """<p>The archive file <b>{0}</b> does not exist. """ |
|
275 """Aborting...</p>""").format(archive), \ |
|
276 False |
|
277 |
|
278 # check, if the archive is a valid zip file |
|
279 if not zipfile.is_zipfile(archive): |
|
280 return False, \ |
|
281 self.tr( |
|
282 """<p>The file <b>{0}</b> is not a valid plugin """ |
|
283 """ZIP-archive. Aborting...</p>""").format(archive), \ |
|
284 False |
|
285 |
|
286 # check, if the destination is writeable |
|
287 if not os.access(destination, os.W_OK): |
|
288 return False, \ |
|
289 self.tr( |
|
290 """<p>The destination directory <b>{0}</b> is not """ |
|
291 """writeable. Aborting...</p>""").format(destination), \ |
|
292 False |
|
293 |
|
294 zipFile = zipfile.ZipFile(archive, "r") |
|
295 |
|
296 # check, if the archive contains a valid plugin |
|
297 pluginFound = False |
|
298 pluginFileName = "" |
|
299 for name in zipFile.namelist(): |
|
300 if self.__pluginManager.isValidPluginName(name): |
|
301 installedPluginName = name[:-3] |
|
302 pluginFound = True |
|
303 pluginFileName = name |
|
304 break |
|
305 |
|
306 if not pluginFound: |
|
307 return False, \ |
|
308 self.tr( |
|
309 """<p>The file <b>{0}</b> is not a valid plugin """ |
|
310 """ZIP-archive. Aborting...</p>""").format(archive), \ |
|
311 False |
|
312 |
|
313 # parse the plugin module's plugin header |
|
314 pluginSource = Utilities.decode(zipFile.read(pluginFileName))[0] |
|
315 packageName = "" |
|
316 internalPackages = [] |
|
317 needsRestart = False |
|
318 pyqtApi = 0 |
|
319 doCompile = True |
|
320 for line in pluginSource.splitlines(): |
|
321 if line.startswith("packageName"): |
|
322 tokens = line.split("=") |
|
323 if tokens[0].strip() == "packageName" and \ |
|
324 tokens[1].strip()[1:-1] != "__core__": |
|
325 if tokens[1].strip()[0] in ['"', "'"]: |
|
326 packageName = tokens[1].strip()[1:-1] |
|
327 else: |
|
328 if tokens[1].strip() == "None": |
|
329 packageName = "None" |
|
330 elif line.startswith("internalPackages"): |
|
331 tokens = line.split("=") |
|
332 token = tokens[1].strip()[1:-1] |
|
333 # it is a comma separated string |
|
334 internalPackages = [p.strip() for p in token.split(",")] |
|
335 elif line.startswith("needsRestart"): |
|
336 tokens = line.split("=") |
|
337 needsRestart = tokens[1].strip() == "True" |
|
338 elif line.startswith("pyqtApi"): |
|
339 tokens = line.split("=") |
|
340 try: |
|
341 pyqtApi = int(tokens[1].strip()) |
|
342 except ValueError: |
|
343 pass |
|
344 elif line.startswith("doNotCompile"): |
|
345 tokens = line.split("=") |
|
346 if tokens[1].strip() == "True": |
|
347 doCompile = False |
|
348 elif line.startswith("# End-Of-Header"): |
|
349 break |
|
350 |
|
351 if not packageName: |
|
352 return False, \ |
|
353 self.tr( |
|
354 """<p>The plugin module <b>{0}</b> does not contain """ |
|
355 """a 'packageName' attribute. Aborting...</p>""")\ |
|
356 .format(pluginFileName), \ |
|
357 False |
|
358 |
|
359 if pyqtApi < 2: |
|
360 return False, \ |
|
361 self.tr( |
|
362 """<p>The plugin module <b>{0}</b> does not conform""" |
|
363 """ with the PyQt v2 API. Aborting...</p>""")\ |
|
364 .format(pluginFileName), \ |
|
365 False |
|
366 |
|
367 # check, if it is a plugin, that collides with others |
|
368 if not os.path.exists(os.path.join(destination, pluginFileName)) and \ |
|
369 packageName != "None" and \ |
|
370 os.path.exists(os.path.join(destination, packageName)): |
|
371 return False, \ |
|
372 self.tr("""<p>The plugin package <b>{0}</b> exists. """ |
|
373 """Aborting...</p>""")\ |
|
374 .format(os.path.join(destination, packageName)), \ |
|
375 False |
|
376 |
|
377 if os.path.exists(os.path.join(destination, pluginFileName)) and \ |
|
378 packageName != "None" and \ |
|
379 not os.path.exists(os.path.join(destination, packageName)): |
|
380 return False, \ |
|
381 self.tr("""<p>The plugin module <b>{0}</b> exists. """ |
|
382 """Aborting...</p>""")\ |
|
383 .format(os.path.join(destination, pluginFileName)), \ |
|
384 False |
|
385 |
|
386 activatePlugin = False |
|
387 if not self.__external: |
|
388 activatePlugin = \ |
|
389 not self.__pluginManager.isPluginLoaded( |
|
390 installedPluginName) or \ |
|
391 (self.__pluginManager.isPluginLoaded(installedPluginName) and |
|
392 self.__pluginManager.isPluginActive(installedPluginName)) |
|
393 # try to unload a plugin with the same name |
|
394 self.__pluginManager.unloadPlugin(installedPluginName) |
|
395 |
|
396 # uninstall existing plug-in first to get clean conditions |
|
397 if packageName != "None" and \ |
|
398 not os.path.exists( |
|
399 os.path.join(destination, packageName, "__init__.py")): |
|
400 # package directory contains just data, don't delete it |
|
401 self.__uninstallPackage(destination, pluginFileName, "") |
|
402 else: |
|
403 self.__uninstallPackage(destination, pluginFileName, packageName) |
|
404 |
|
405 # clean sys.modules |
|
406 reload_ = self.__pluginManager.removePluginFromSysModules( |
|
407 installedPluginName, packageName, internalPackages) |
|
408 |
|
409 # now do the installation |
|
410 self.__installedDirs = [] |
|
411 self.__installedFiles = [] |
|
412 try: |
|
413 if packageName != "None": |
|
414 namelist = sorted(zipFile.namelist()) |
|
415 tot = len(namelist) |
|
416 prog = 0 |
|
417 self.progress.setMaximum(tot) |
|
418 QApplication.processEvents() |
|
419 for name in namelist: |
|
420 self.progress.setValue(prog) |
|
421 QApplication.processEvents() |
|
422 prog += 1 |
|
423 if name == pluginFileName or \ |
|
424 name.startswith("{0}/".format(packageName)) or \ |
|
425 name.startswith("{0}\\".format(packageName)): |
|
426 outname = name.replace("/", os.sep) |
|
427 outname = os.path.join(destination, outname) |
|
428 if outname.endswith("/") or outname.endswith("\\"): |
|
429 # it is a directory entry |
|
430 outname = outname[:-1] |
|
431 if not os.path.exists(outname): |
|
432 self.__makedirs(outname) |
|
433 else: |
|
434 # it is a file |
|
435 d = os.path.dirname(outname) |
|
436 if not os.path.exists(d): |
|
437 self.__makedirs(d) |
|
438 f = open(outname, "wb") |
|
439 f.write(zipFile.read(name)) |
|
440 f.close() |
|
441 self.__installedFiles.append(outname) |
|
442 self.progress.setValue(tot) |
|
443 # now compile user interface files |
|
444 compileUiFiles(os.path.join(destination, packageName), True) |
|
445 else: |
|
446 outname = os.path.join(destination, pluginFileName) |
|
447 f = open(outname, "w", encoding="utf-8") |
|
448 f.write(pluginSource) |
|
449 f.close() |
|
450 self.__installedFiles.append(outname) |
|
451 except os.error as why: |
|
452 self.__rollback() |
|
453 return False, \ |
|
454 self.tr( |
|
455 "Error installing plugin. Reason: {0}").format(str(why)), \ |
|
456 False |
|
457 except IOError as why: |
|
458 self.__rollback() |
|
459 return False, \ |
|
460 self.tr( |
|
461 "Error installing plugin. Reason: {0}").format(str(why)), \ |
|
462 False |
|
463 except OSError as why: |
|
464 self.__rollback() |
|
465 return False, \ |
|
466 self.tr( |
|
467 "Error installing plugin. Reason: {0}").format(str(why)), \ |
|
468 False |
|
469 except Exception: |
|
470 sys.stderr.write("Unspecific exception installing plugin.\n") |
|
471 self.__rollback() |
|
472 return False, \ |
|
473 self.tr("Unspecific exception installing plugin."), \ |
|
474 False |
|
475 |
|
476 # now compile the plugins |
|
477 if doCompile: |
|
478 dirName = os.path.join(destination, packageName) |
|
479 files = os.path.join(destination, pluginFileName) |
|
480 if sys.version_info[0] == 2: |
|
481 dirName = dirName.encode(sys.getfilesystemencoding()) |
|
482 files = files.encode(sys.getfilesystemencoding()) |
|
483 os.path.join_unicode = False |
|
484 compileall.compile_dir(dirName, quiet=True) |
|
485 compileall.compile_file(files, quiet=True) |
|
486 os.path.join_unicode = True |
|
487 |
|
488 if not self.__external: |
|
489 # now load and activate the plugin |
|
490 self.__pluginManager.loadPlugin(installedPluginName, destination, |
|
491 reload_) |
|
492 if activatePlugin: |
|
493 self.__pluginManager.activatePlugin(installedPluginName) |
|
494 |
|
495 return True, "", needsRestart |
|
496 |
|
497 def __rollback(self): |
|
498 """ |
|
499 Private method to rollback a failed installation. |
|
500 """ |
|
501 for fname in self.__installedFiles: |
|
502 if os.path.exists(fname): |
|
503 os.remove(fname) |
|
504 for dname in self.__installedDirs: |
|
505 if os.path.exists(dname): |
|
506 shutil.rmtree(dname) |
|
507 |
|
508 def __makedirs(self, name, mode=0o777): |
|
509 """ |
|
510 Private method to create a directory and all intermediate ones. |
|
511 |
|
512 This is an extended version of the Python one in order to |
|
513 record the created directories. |
|
514 |
|
515 @param name name of the directory to create (string) |
|
516 @param mode permission to set for the new directory (integer) |
|
517 """ |
|
518 head, tail = os.path.split(name) |
|
519 if not tail: |
|
520 head, tail = os.path.split(head) |
|
521 if head and tail and not os.path.exists(head): |
|
522 self.__makedirs(head, mode) |
|
523 if tail == os.curdir: |
|
524 # xxx/newdir/. exists if xxx/newdir exists |
|
525 return |
|
526 os.mkdir(name, mode) |
|
527 self.__installedDirs.append(name) |
|
528 |
|
529 def __uninstallPackage(self, destination, pluginFileName, packageName): |
|
530 """ |
|
531 Private method to uninstall an already installed plugin to prepare |
|
532 the update. |
|
533 |
|
534 @param destination name of the plugin directory (string) |
|
535 @param pluginFileName name of the plugin file (string) |
|
536 @param packageName name of the plugin package (string) |
|
537 """ |
|
538 if packageName == "" or packageName == "None": |
|
539 packageDir = None |
|
540 else: |
|
541 packageDir = os.path.join(destination, packageName) |
|
542 pluginFile = os.path.join(destination, pluginFileName) |
|
543 |
|
544 try: |
|
545 if packageDir and os.path.exists(packageDir): |
|
546 shutil.rmtree(packageDir) |
|
547 |
|
548 fnameo = "{0}o".format(pluginFile) |
|
549 if os.path.exists(fnameo): |
|
550 os.remove(fnameo) |
|
551 |
|
552 fnamec = "{0}c".format(pluginFile) |
|
553 if os.path.exists(fnamec): |
|
554 os.remove(fnamec) |
|
555 |
|
556 pluginDirCache = os.path.join( |
|
557 os.path.dirname(pluginFile), "__pycache__") |
|
558 if os.path.exists(pluginDirCache): |
|
559 pluginFileName = os.path.splitext( |
|
560 os.path.basename(pluginFile))[0] |
|
561 for fnameo in glob.glob( |
|
562 os.path.join(pluginDirCache, |
|
563 "{0}*.pyo".format(pluginFileName))): |
|
564 os.remove(fnameo) |
|
565 for fnamec in glob.glob( |
|
566 os.path.join(pluginDirCache, |
|
567 "{0}*.pyc".format(pluginFileName))): |
|
568 os.remove(fnamec) |
|
569 |
|
570 os.remove(pluginFile) |
|
571 except (IOError, OSError, os.error): |
|
572 # ignore some exceptions |
|
573 pass |
|
574 |
|
575 |
|
576 class PluginInstallDialog(QDialog): |
|
577 """ |
|
578 Class for the dialog variant. |
|
579 """ |
|
580 def __init__(self, pluginManager, pluginFileNames, parent=None): |
|
581 """ |
|
582 Constructor |
|
583 |
|
584 @param pluginManager reference to the plugin manager object |
|
585 @param pluginFileNames list of plugin files suggested for |
|
586 installation (list of strings) |
|
587 @param parent reference to the parent widget (QWidget) |
|
588 """ |
|
589 super(PluginInstallDialog, self).__init__(parent) |
|
590 self.setSizeGripEnabled(True) |
|
591 |
|
592 self.__layout = QVBoxLayout(self) |
|
593 self.__layout.setContentsMargins(0, 0, 0, 0) |
|
594 self.setLayout(self.__layout) |
|
595 |
|
596 self.cw = PluginInstallWidget(pluginManager, pluginFileNames, self) |
|
597 size = self.cw.size() |
|
598 self.__layout.addWidget(self.cw) |
|
599 self.resize(size) |
|
600 self.setWindowTitle(self.cw.windowTitle()) |
|
601 |
|
602 self.cw.buttonBox.accepted.connect(self.accept) |
|
603 self.cw.buttonBox.rejected.connect(self.reject) |
|
604 |
|
605 def restartNeeded(self): |
|
606 """ |
|
607 Public method to check, if a restart of the IDE is required. |
|
608 |
|
609 @return flag indicating a restart is required (boolean) |
|
610 """ |
|
611 return self.cw.restartNeeded() |
|
612 |
|
613 |
|
614 class PluginInstallWindow(E5MainWindow): |
|
615 """ |
|
616 Main window class for the standalone dialog. |
|
617 """ |
|
618 def __init__(self, pluginFileNames, parent=None): |
|
619 """ |
|
620 Constructor |
|
621 |
|
622 @param pluginFileNames list of plugin files suggested for |
|
623 installation (list of strings) |
|
624 @param parent reference to the parent widget (QWidget) |
|
625 """ |
|
626 super(PluginInstallWindow, self).__init__(parent) |
|
627 self.cw = PluginInstallWidget(None, pluginFileNames, self) |
|
628 size = self.cw.size() |
|
629 self.setCentralWidget(self.cw) |
|
630 self.resize(size) |
|
631 self.setWindowTitle(self.cw.windowTitle()) |
|
632 |
|
633 self.setStyle(Preferences.getUI("Style"), |
|
634 Preferences.getUI("StyleSheet")) |
|
635 |
|
636 self.cw.buttonBox.accepted.connect(self.close) |
|
637 self.cw.buttonBox.rejected.connect(self.close) |