30 class PluginManager(QObject): |
31 class PluginManager(QObject): |
31 """ |
32 """ |
32 Class implementing the Plugin Manager. |
33 Class implementing the Plugin Manager. |
33 |
34 |
34 @signal shutdown() emitted at shutdown of the IDE |
35 @signal shutdown() emitted at shutdown of the IDE |
35 @signal pluginAboutToBeActivated(modulName, pluginObject) emitted just before a |
36 @signal pluginAboutToBeActivated(modulName, pluginObject) emitted just |
36 plugin is activated |
37 before a plugin is activated |
37 @signal pluginActivated(modulName, pluginObject) emitted just after a plugin |
38 @signal pluginActivated(modulName, pluginObject) emitted just after |
38 was activated |
39 a plugin was activated |
39 @signal allPlugginsActivated() emitted at startup after all plugins have |
40 @signal allPlugginsActivated() emitted at startup after all plugins have |
40 been activated |
41 been activated |
41 @signal pluginAboutToBeDeactivated(modulName, pluginObject) emitted just before a |
42 @signal pluginAboutToBeDeactivated(modulName, pluginObject) emitted just |
42 plugin is deactivated |
43 before a plugin is deactivated |
43 @signal pluginDeactivated(modulName, pluginObject) emitted just after a plugin |
44 @signal pluginDeactivated(modulName, pluginObject) emitted just after |
44 was deactivated |
45 a plugin was deactivated |
45 """ |
46 """ |
46 shutdown = pyqtSignal() |
47 shutdown = pyqtSignal() |
47 pluginAboutToBeActivated = pyqtSignal(str, object) |
48 pluginAboutToBeActivated = pyqtSignal(str, object) |
48 pluginActivated = pyqtSignal(str, object) |
49 pluginActivated = pyqtSignal(str, object) |
49 allPlugginsActivated = pyqtSignal() |
50 allPlugginsActivated = pyqtSignal() |
190 del self.pluginDirs["user"] |
192 del self.pluginDirs["user"] |
191 del self.pluginDirs["global"] |
193 del self.pluginDirs["global"] |
192 |
194 |
193 if not os.path.exists(self.pluginDirs["eric5"]): |
195 if not os.path.exists(self.pluginDirs["eric5"]): |
194 return (False, |
196 return (False, |
195 self.trUtf8("The internal plugin directory <b>{0}</b> does not exits.")\ |
197 self.trUtf8( |
196 .format(self.pluginDirs["eric5"])) |
198 "The internal plugin directory <b>{0}</b>" |
|
199 " does not exits.").format(self.pluginDirs["eric5"])) |
197 |
200 |
198 return (True, "") |
201 return (True, "") |
199 |
202 |
200 def __pluginModulesExist(self): |
203 def __pluginModulesExist(self): |
201 """ |
204 """ |
202 Private method to check, if there are plugins available. |
205 Private method to check, if there are plugins available. |
203 |
206 |
204 @return flag indicating the availability of plugins (boolean) |
207 @return flag indicating the availability of plugins (boolean) |
205 """ |
208 """ |
206 if self.__develPluginFile and not os.path.exists(self.__develPluginFile): |
209 if self.__develPluginFile and \ |
|
210 not os.path.exists(self.__develPluginFile): |
207 return False |
211 return False |
208 |
212 |
209 self.__foundCoreModules = self.getPluginModules(self.pluginDirs["eric5"]) |
213 self.__foundCoreModules = self.getPluginModules( |
|
214 self.pluginDirs["eric5"]) |
210 if "global" in self.pluginDirs: |
215 if "global" in self.pluginDirs: |
211 self.__foundGlobalModules = \ |
216 self.__foundGlobalModules = \ |
212 self.getPluginModules(self.pluginDirs["global"]) |
217 self.getPluginModules(self.pluginDirs["global"]) |
213 if "user" in self.pluginDirs: |
218 if "user" in self.pluginDirs: |
214 self.__foundUserModules = \ |
219 self.__foundUserModules = \ |
304 """ |
309 """ |
305 try: |
310 try: |
306 fname = "{0}.py".format(os.path.join(directory, name)) |
311 fname = "{0}.py".format(os.path.join(directory, name)) |
307 module = imp.load_source(name, fname) |
312 module = imp.load_source(name, fname) |
308 if not hasattr(module, "autoactivate"): |
313 if not hasattr(module, "autoactivate"): |
309 module.error = \ |
314 module.error = self.trUtf8( |
310 self.trUtf8("Module is missing the 'autoactivate' attribute.") |
315 "Module is missing the 'autoactivate' attribute.") |
311 self.__failedModules[name] = module |
316 self.__failedModules[name] = module |
312 raise PluginLoadError(name) |
317 raise PluginLoadError(name) |
313 if getattr(module, "autoactivate"): |
318 if getattr(module, "autoactivate"): |
314 self.__inactiveModules[name] = module |
319 self.__inactiveModules[name] = module |
315 else: |
320 else: |
367 del self.__failedModules[name] |
372 del self.__failedModules[name] |
368 |
373 |
369 self.__modulesCount -= 1 |
374 self.__modulesCount -= 1 |
370 return True |
375 return True |
371 |
376 |
372 def removePluginFromSysModules(self, pluginName, package, internalPackages): |
377 def removePluginFromSysModules(self, pluginName, package, |
373 """ |
378 internalPackages): |
374 Public method to remove a plugin and all related modules from sys.modules. |
379 """ |
|
380 Public method to remove a plugin and all related modules from |
|
381 sys.modules. |
375 |
382 |
376 @param pluginName name of the plugin module (string) |
383 @param pluginName name of the plugin module (string) |
377 @param package name of the plugin package (string) |
384 @param package name of the plugin package (string) |
378 @param internalPackages list of intenal packages (list of string) |
385 @param internalPackages list of intenal packages (list of string) |
379 @return flag indicating the plugin module was found in sys.modules (boolean) |
386 @return flag indicating the plugin module was found in sys.modules |
|
387 (boolean) |
380 """ |
388 """ |
381 packages = [package] + internalPackages |
389 packages = [package] + internalPackages |
382 found = False |
390 found = False |
383 if not package: |
391 if not package: |
384 package = "__None__" |
392 package = "__None__" |
385 for moduleName in list(sys.modules.keys())[:]: |
393 for moduleName in list(sys.modules.keys())[:]: |
386 if moduleName == pluginName or moduleName.split(".")[0] in packages: |
394 if moduleName == pluginName or \ |
|
395 moduleName.split(".")[0] in packages: |
387 found = True |
396 found = True |
388 del sys.modules[moduleName] |
397 del sys.modules[moduleName] |
389 return found |
398 return found |
390 |
399 |
391 def initOnDemandPlugins(self): |
400 def initOnDemandPlugins(self): |
429 except PluginActivationError: |
438 except PluginActivationError: |
430 return |
439 return |
431 |
440 |
432 def activatePlugins(self): |
441 def activatePlugins(self): |
433 """ |
442 """ |
434 Public method to activate all plugins having the "autoactivate" attribute |
443 Public method to activate all plugins having the "autoactivate" |
435 set to True. |
444 attribute set to True. |
436 """ |
445 """ |
437 savedInactiveList = Preferences.Prefs.settings.value(self.__inactivePluginsKey) |
446 savedInactiveList = Preferences.Prefs.settings.value( |
|
447 self.__inactivePluginsKey) |
438 if self.__develPluginName is not None and \ |
448 if self.__develPluginName is not None and \ |
439 savedInactiveList is not None and \ |
449 savedInactiveList is not None and \ |
440 self.__develPluginName in savedInactiveList: |
450 self.__develPluginName in savedInactiveList: |
441 savedInactiveList.remove(self.__develPluginName) |
451 savedInactiveList.remove(self.__develPluginName) |
442 names = sorted(self.__inactiveModules.keys()) |
452 names = sorted(self.__inactiveModules.keys()) |
528 @exception PluginClassFormatError raised to indicate an invalid |
539 @exception PluginClassFormatError raised to indicate an invalid |
529 plug-in class format |
540 plug-in class format |
530 """ |
541 """ |
531 try: |
542 try: |
532 if not hasattr(module, "version"): |
543 if not hasattr(module, "version"): |
533 raise PluginModuleFormatError(module.eric5PluginModuleName, "version") |
544 raise PluginModuleFormatError( |
|
545 module.eric5PluginModuleName, "version") |
534 if not hasattr(module, "className"): |
546 if not hasattr(module, "className"): |
535 raise PluginModuleFormatError(module.eric5PluginModuleName, "className") |
547 raise PluginModuleFormatError( |
|
548 module.eric5PluginModuleName, "className") |
536 className = getattr(module, "className") |
549 className = getattr(module, "className") |
537 if not hasattr(module, className): |
550 if not hasattr(module, className): |
538 raise PluginModuleFormatError(module.eric5PluginModuleName, className) |
551 raise PluginModuleFormatError( |
|
552 module.eric5PluginModuleName, className) |
539 pluginClass = getattr(module, className) |
553 pluginClass = getattr(module, className) |
540 if not hasattr(pluginClass, "__init__"): |
554 if not hasattr(pluginClass, "__init__"): |
541 raise PluginClassFormatError(module.eric5PluginModuleName, |
555 raise PluginClassFormatError(module.eric5PluginModuleName, |
542 className, "__init__") |
556 className, "__init__") |
543 if not hasattr(pluginClass, "activate"): |
557 if not hasattr(pluginClass, "activate"): |
631 |
646 |
632 def getPluginInfos(self): |
647 def getPluginInfos(self): |
633 """ |
648 """ |
634 Public method to get infos about all loaded plugins. |
649 Public method to get infos about all loaded plugins. |
635 |
650 |
636 @return list of tuples giving module name (string), plugin name (string), |
651 @return list of tuples giving module name (string), plugin name |
637 version (string), autoactivate (boolean), active (boolean), |
652 (string), version (string), autoactivate (boolean), active |
638 short description (string), error flag (boolean) |
653 (boolean), short description (string), error flag (boolean) |
639 """ |
654 """ |
640 infos = [] |
655 infos = [] |
641 |
656 |
642 for name in list(self.__activeModules.keys()): |
657 for name in list(self.__activeModules.keys()): |
643 pname, shortDesc, error, version = \ |
658 pname, shortDesc, error, version = \ |
644 self.__getShortInfo(self.__activeModules[name]) |
659 self.__getShortInfo(self.__activeModules[name]) |
645 infos.append((name, pname, version, True, True, shortDesc, error)) |
660 infos.append((name, pname, version, True, True, shortDesc, error)) |
646 for name in list(self.__inactiveModules.keys()): |
661 for name in list(self.__inactiveModules.keys()): |
647 pname, shortDesc, error, version = \ |
662 pname, shortDesc, error, version = \ |
648 self.__getShortInfo(self.__inactiveModules[name]) |
663 self.__getShortInfo(self.__inactiveModules[name]) |
649 infos.append((name, pname, version, True, False, shortDesc, error)) |
664 infos.append( |
|
665 (name, pname, version, True, False, shortDesc, error)) |
650 for name in list(self.__onDemandActiveModules.keys()): |
666 for name in list(self.__onDemandActiveModules.keys()): |
651 pname, shortDesc, error, version = \ |
667 pname, shortDesc, error, version = \ |
652 self.__getShortInfo(self.__onDemandActiveModules[name]) |
668 self.__getShortInfo(self.__onDemandActiveModules[name]) |
653 infos.append((name, pname, version, False, True, shortDesc, error)) |
669 infos.append( |
|
670 (name, pname, version, False, True, shortDesc, error)) |
654 for name in list(self.__onDemandInactiveModules.keys()): |
671 for name in list(self.__onDemandInactiveModules.keys()): |
655 pname, shortDesc, error, version = \ |
672 pname, shortDesc, error, version = \ |
656 self.__getShortInfo(self.__onDemandInactiveModules[name]) |
673 self.__getShortInfo(self.__onDemandInactiveModules[name]) |
657 infos.append((name, pname, version, False, False, shortDesc, error)) |
674 infos.append( |
|
675 (name, pname, version, False, False, shortDesc, error)) |
658 for name in list(self.__failedModules.keys()): |
676 for name in list(self.__failedModules.keys()): |
659 pname, shortDesc, error, version = \ |
677 pname, shortDesc, error, version = \ |
660 self.__getShortInfo(self.__failedModules[name]) |
678 self.__getShortInfo(self.__failedModules[name]) |
661 infos.append((name, pname, version, False, False, shortDesc, error)) |
679 infos.append( |
|
680 (name, pname, version, False, False, shortDesc, error)) |
662 return infos |
681 return infos |
663 |
682 |
664 def __getShortInfo(self, module): |
683 def __getShortInfo(self, module): |
665 """ |
684 """ |
666 Private method to extract the short info from a module. |
685 Private method to extract the short info from a module. |
707 else: |
726 else: |
708 # should not happen |
727 # should not happen |
709 return None |
728 return None |
710 |
729 |
711 details["moduleName"] = name |
730 details["moduleName"] = name |
712 details["moduleFileName"] = getattr(module, "eric5PluginModuleFilename", "") |
731 details["moduleFileName"] = getattr( |
|
732 module, "eric5PluginModuleFilename", "") |
713 details["pluginName"] = getattr(module, "name", "") |
733 details["pluginName"] = getattr(module, "name", "") |
714 details["version"] = getattr(module, "version", "") |
734 details["version"] = getattr(module, "version", "") |
715 details["author"] = getattr(module, "author", "") |
735 details["author"] = getattr(module, "author", "") |
716 details["description"] = getattr(module, "longDescription", "") |
736 details["description"] = getattr(module, "longDescription", "") |
717 details["autoactivate"] = autoactivate |
737 details["autoactivate"] = autoactivate |
799 """ |
820 """ |
800 Public method to get data to display information about a plugins |
821 Public method to get data to display information about a plugins |
801 external tool. |
822 external tool. |
802 |
823 |
803 @return list of dictionaries containing the data. Each dictionary must |
824 @return list of dictionaries containing the data. Each dictionary must |
804 either contain data for the determination or the data to be displayed.<br /> |
825 either contain data for the determination or the data to be |
|
826 displayed.<br /> |
805 A dictionary of the first form must have the following entries: |
827 A dictionary of the first form must have the following entries: |
806 <ul> |
828 <ul> |
807 <li>programEntry - indicator for this dictionary form (boolean), |
829 <li>programEntry - indicator for this dictionary form |
808 always True</li> |
830 (boolean), always True</li> |
809 <li>header - string to be diplayed as a header (string)</li> |
831 <li>header - string to be diplayed as a header (string)</li> |
810 <li>exe - the executable (string)</li> |
832 <li>exe - the executable (string)</li> |
811 <li>versionCommand - commandline parameter for the exe (string)</li> |
833 <li>versionCommand - commandline parameter for the exe |
812 <li>versionStartsWith - indicator for the output line containing |
834 (string)</li> |
813 the version (string)</li> |
835 <li>versionStartsWith - indicator for the output line |
|
836 containing the version (string)</li> |
814 <li>versionPosition - number of element containing the |
837 <li>versionPosition - number of element containing the |
815 version (integer)</li> |
838 version (integer)</li> |
816 <li>version - version to be used as default (string)</li> |
839 <li>version - version to be used as default (string)</li> |
817 <li>versionCleanup - tuple of two integers giving string positions |
840 <li>versionCleanup - tuple of two integers giving string |
818 start and stop for the version string (tuple of integers)</li> |
841 positions start and stop for the version string |
|
842 (tuple of integers)</li> |
819 </ul> |
843 </ul> |
820 A dictionary of the second form must have the following entries: |
844 A dictionary of the second form must have the following entries: |
821 <ul> |
845 <ul> |
822 <li>programEntry - indicator for this dictionary form (boolean), |
846 <li>programEntry - indicator for this dictionary form |
823 always False</li> |
847 (boolean), always False</li> |
824 <li>header - string to be diplayed as a header (string)</li> |
848 <li>header - string to be diplayed as a header (string)</li> |
825 <li>text - entry text to be shown (string)</li> |
849 <li>text - entry text to be shown (string)</li> |
826 <li>version - version text to be shown (string)</li> |
850 <li>version - version text to be shown (string)</li> |
827 </ul> |
851 </ul> |
828 """ |
852 """ |
843 |
867 |
844 return infos |
868 return infos |
845 |
869 |
846 def getPluginConfigData(self): |
870 def getPluginConfigData(self): |
847 """ |
871 """ |
848 Public method to get the config data of all active, non on-demand plugins |
872 Public method to get the config data of all active, non on-demand |
849 used by the configuration dialog. |
873 plugins used by the configuration dialog. |
850 |
874 |
851 Plugins supporting this functionality must provide the plugin module |
875 Plugins supporting this functionality must provide the plugin module |
852 function 'getConfigData' returning a dictionary with unique keys |
876 function 'getConfigData' returning a dictionary with unique keys |
853 of lists with the following list contents: |
877 of lists with the following list contents: |
854 <dl> |
878 <dl> |
855 <dt>display string</dt> |
879 <dt>display string</dt> |
856 <dd>string shown in the selection area of the configuration page. |
880 <dd>string shown in the selection area of the configuration page. |
857 This should be a localized string</dd> |
881 This should be a localized string</dd> |
858 <dt>pixmap name</dt> |
882 <dt>pixmap name</dt> |
859 <dd>filename of the pixmap to be shown next to the display string</dd> |
883 <dd>filename of the pixmap to be shown next to the display |
|
884 string</dd> |
860 <dt>page creation function</dt> |
885 <dt>page creation function</dt> |
861 <dd>plugin module function to be called to create the configuration |
886 <dd>plugin module function to be called to create the configuration |
862 page. The page must be subclasses from |
887 page. The page must be subclasses from |
863 Preferences.ConfigurationPages.ConfigurationPageBase and must |
888 Preferences.ConfigurationPages.ConfigurationPageBase and must |
864 implement a method called 'save' to save the settings. A parent |
889 implement a method called 'save' to save the settings. A parent |
865 entry will be created in the selection list, if this value is None.</dd> |
890 entry will be created in the selection list, if this value is |
|
891 None.</dd> |
866 <dt>parent key</dt> |
892 <dt>parent key</dt> |
867 <dd>dictionary key of the parent entry or None, if this defines a |
893 <dd>dictionary key of the parent entry or None, if this defines a |
868 toplevel entry.</dd> |
894 toplevel entry.</dd> |
869 <dt>reference to configuration page</dt> |
895 <dt>reference to configuration page</dt> |
870 <dd>This will be used by the configuration dialog and must always be None</dd> |
896 <dd>This will be used by the configuration dialog and must always |
|
897 be None</dd> |
871 </dl> |
898 </dl> |
872 |
899 |
873 @return plug-in configuration data |
900 @return plug-in configuration data |
874 """ |
901 """ |
875 configData = {} |
902 configData = {} |
900 @return flag indicating, if the plugin is active (boolean) |
927 @return flag indicating, if the plugin is active (boolean) |
901 """ |
928 """ |
902 return pluginName in self.__activeModules or \ |
929 return pluginName in self.__activeModules or \ |
903 pluginName in self.__onDemandActiveModules |
930 pluginName in self.__onDemandActiveModules |
904 |
931 |
905 ############################################################################ |
932 ########################################################################### |
906 ## Specialized plugin module handling methods below |
933 ## Specialized plugin module handling methods below |
907 ############################################################################ |
934 ########################################################################### |
908 |
935 |
909 ############################################################################ |
936 ########################################################################### |
910 ## VCS related methods below |
937 ## VCS related methods below |
911 ############################################################################ |
938 ########################################################################### |
912 |
939 |
913 def getVcsSystemIndicators(self): |
940 def getVcsSystemIndicators(self): |
914 """ |
941 """ |
915 Public method to get the Vcs System indicators. |
942 Public method to get the Vcs System indicators. |
916 |
943 |
917 Plugins supporting this functionality must support the module function |
944 Plugins supporting this functionality must support the module function |
918 getVcsSystemIndicator returning a dictionary with indicator as key and |
945 getVcsSystemIndicator returning a dictionary with indicator as key and |
919 a tuple with the vcs name (string) and vcs display string (string). |
946 a tuple with the vcs name (string) and vcs display string (string). |
920 |
947 |
921 @return dictionary with indicator as key and a list of tuples as values. |
948 @return dictionary with indicator as key and a list of tuples as |
922 Each tuple contains the vcs name (string) and vcs display string (string). |
949 values. Each tuple contains the vcs name (string) and vcs display |
|
950 string (string). |
923 """ |
951 """ |
924 vcsDict = {} |
952 vcsDict = {} |
925 |
953 |
926 for name, module in \ |
954 for name, module in \ |
927 list(self.__onDemandActiveModules.items()) + \ |
955 list(self.__onDemandActiveModules.items()) + \ |
963 try: |
992 try: |
964 os.mkdir(downloadDir, 0o755) |
993 os.mkdir(downloadDir, 0o755) |
965 except (OSError, IOError) as err: |
994 except (OSError, IOError) as err: |
966 E5MessageBox.critical(self.__ui, |
995 E5MessageBox.critical(self.__ui, |
967 self.trUtf8("Plugin Manager Error"), |
996 self.trUtf8("Plugin Manager Error"), |
968 self.trUtf8("""<p>The plugin download directory <b>{0}</b> """ |
997 self.trUtf8( |
969 """could not be created. Please configure it """ |
998 """<p>The plugin download directory""" |
970 """via the configuration dialog.</p>""" |
999 """ <b>{0}</b> could not be created. Please""" |
971 """<p>Reason: {1}</p>""")\ |
1000 """ configure it via the configuration""" |
|
1001 """ dialog.</p><p>Reason: {1}</p>""") |
972 .format(downloadDir, str(err))) |
1002 .format(downloadDir, str(err))) |
973 downloadDir = "" |
1003 downloadDir = "" |
974 |
1004 |
975 Preferences.setPluginManager("DownloadPath", downloadDir) |
1005 Preferences.setPluginManager("DownloadPath", downloadDir) |
976 |
1006 |