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