|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2007 - 2009 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the Plugin Manager. |
|
8 """ |
|
9 |
|
10 import os |
|
11 import sys |
|
12 import imp |
|
13 |
|
14 from PyQt4.QtCore import * |
|
15 from PyQt4.QtGui import QPixmap, QMessageBox |
|
16 |
|
17 from PluginExceptions import * |
|
18 |
|
19 import UI.PixmapCache |
|
20 |
|
21 import Utilities |
|
22 import Preferences |
|
23 |
|
24 from eric4config import getConfig |
|
25 |
|
26 class PluginManager(QObject): |
|
27 """ |
|
28 Class implementing the Plugin Manager. |
|
29 |
|
30 @signal shutdown() emitted at shutdown of the IDE |
|
31 @signal pluginAboutToBeActivated(modulName, pluginObject) emitted just before a |
|
32 plugin is activated |
|
33 @signal pluginActivated(modulName, pluginObject) emitted just after a plugin |
|
34 was activated |
|
35 @signal allPlugginsActivated() emitted at startup after all plugins have |
|
36 been activated |
|
37 @signal pluginAboutToBeDeactivated(modulName, pluginObject) emitted just before a |
|
38 plugin is deactivated |
|
39 @signal pluginDeactivated(modulName, pluginObject) emitted just after a plugin |
|
40 was deactivated |
|
41 """ |
|
42 def __init__(self, parent = None, doLoadPlugins = True, develPlugin = None): |
|
43 """ |
|
44 Constructor |
|
45 |
|
46 The Plugin Manager deals with three different plugin directories. |
|
47 The first is the one, that is part of eric4 (eric4/Plugins). The |
|
48 second one is the global plugin directory called 'eric4plugins', |
|
49 which is located inside the site-packages directory. The last one |
|
50 is the user plugin directory located inside the .eric4 directory |
|
51 of the users home directory. |
|
52 |
|
53 @param parent reference to the parent object (QObject) |
|
54 @keyparam doLoadPlugins flag indicating, that plugins should |
|
55 be loaded (boolean) |
|
56 @keyparam develPlugin filename of a plugin to be loaded for |
|
57 development (string) |
|
58 """ |
|
59 QObject.__init__(self, parent) |
|
60 |
|
61 self.__ui = parent |
|
62 self.__develPluginFile = develPlugin |
|
63 self.__develPluginName = None |
|
64 |
|
65 self.__inactivePluginsKey = "PluginManager/InactivePlugins" |
|
66 |
|
67 self.pluginDirs = { |
|
68 "eric4" : os.path.join(getConfig('ericDir'), "Plugins"), |
|
69 "global" : os.path.join(Utilities.getPythonModulesDirectory(), |
|
70 "eric4plugins"), |
|
71 "user" : os.path.join(Utilities.getConfigDir(), "eric4plugins"), |
|
72 } |
|
73 self.__priorityOrder = ["eric4", "global", "user"] |
|
74 |
|
75 self.__defaultDownloadDir = os.path.join(Utilities.getConfigDir(), "Downloads") |
|
76 |
|
77 self.__activePlugins = {} |
|
78 self.__inactivePlugins = {} |
|
79 self.__onDemandActivePlugins = {} |
|
80 self.__onDemandInactivePlugins = {} |
|
81 self.__activeModules = {} |
|
82 self.__inactiveModules = {} |
|
83 self.__onDemandActiveModules = {} |
|
84 self.__onDemandInactiveModules = {} |
|
85 self.__failedModules = {} |
|
86 |
|
87 self.__foundCoreModules = [] |
|
88 self.__foundGlobalModules = [] |
|
89 self.__foundUserModules = [] |
|
90 |
|
91 self.__modulesCount = 0 |
|
92 |
|
93 pdirsExist, msg = self.__pluginDirectoriesExist() |
|
94 if not pdirsExist: |
|
95 raise PluginPathError(msg) |
|
96 |
|
97 if doLoadPlugins: |
|
98 if not self.__pluginModulesExist(): |
|
99 raise PluginModulesError |
|
100 |
|
101 self.__insertPluginsPaths() |
|
102 |
|
103 self.__loadPlugins() |
|
104 |
|
105 self.__checkPluginsDownloadDirectory() |
|
106 |
|
107 def finalizeSetup(self): |
|
108 """ |
|
109 Public method to finalize the setup of the plugin manager. |
|
110 """ |
|
111 for module in self.__onDemandInactiveModules.values() + \ |
|
112 self.__onDemandActiveModules.values(): |
|
113 if hasattr(module, "moduleSetup"): |
|
114 module.moduleSetup() |
|
115 |
|
116 def getPluginDir(self, key): |
|
117 """ |
|
118 Public method to get the path of a plugin directory. |
|
119 |
|
120 @return path of the requested plugin directory (string) |
|
121 """ |
|
122 if key not in ["global", "user"]: |
|
123 return None |
|
124 else: |
|
125 try: |
|
126 return self.pluginDirs[key] |
|
127 except KeyError: |
|
128 return None |
|
129 |
|
130 def __pluginDirectoriesExist(self): |
|
131 """ |
|
132 Private method to check, if the plugin folders exist. |
|
133 |
|
134 If the plugin folders don't exist, they are created (if possible). |
|
135 |
|
136 @return tuple of a flag indicating existence of any of the plugin |
|
137 directories (boolean) and a message (string) |
|
138 """ |
|
139 if self.__develPluginFile: |
|
140 path = Utilities.splitPath(self.__develPluginFile)[0] |
|
141 fname = os.path.join(path, "__init__.py") |
|
142 if not os.path.exists(fname): |
|
143 try: |
|
144 f = open(fname, "wb") |
|
145 f.close() |
|
146 except IOError: |
|
147 return (False, |
|
148 self.trUtf8("Could not create a package for {0}.")\ |
|
149 .format(self.__develPluginFile)) |
|
150 |
|
151 if Preferences.getPluginManager("ActivateExternal"): |
|
152 fname = os.path.join(self.pluginDirs["user"], "__init__.py") |
|
153 if not os.path.exists(fname): |
|
154 if not os.path.exists(self.pluginDirs["user"]): |
|
155 os.mkdir(self.pluginDirs["user"], 0755) |
|
156 try: |
|
157 f = open(fname, "wb") |
|
158 f.close() |
|
159 except IOError: |
|
160 del self.pluginDirs["user"] |
|
161 |
|
162 if not os.path.exists(self.pluginDirs["global"]) and \ |
|
163 os.access(Utilities.getPythonModulesDirectory(), os.W_OK): |
|
164 # create the global plugins directory |
|
165 os.mkdir(self.pluginDirs["global"], 0755) |
|
166 fname = os.path.join(self.pluginDirs["global"], "__init__.py") |
|
167 f = open() |
|
168 f.write('# -*- coding: utf-8 -*-' + os.linesep) |
|
169 f.write(os.linesep) |
|
170 f.write('"""' + os.linesep) |
|
171 f.write('Package containing the global plugins.' + os.linesep) |
|
172 f.write('"""' + os.linesep) |
|
173 f.close() |
|
174 if not os.path.exists(self.pluginDirs["global"]): |
|
175 del self.pluginDirs["global"] |
|
176 else: |
|
177 del self.pluginDirs["user"] |
|
178 del self.pluginDirs["global"] |
|
179 |
|
180 if not os.path.exists(self.pluginDirs["eric4"]): |
|
181 return (False, |
|
182 self.trUtf8("The internal plugin directory <b>{0}</b> does not exits.")\ |
|
183 .format(self.pluginDirs["eric4"])) |
|
184 |
|
185 return (True, "") |
|
186 |
|
187 def __pluginModulesExist(self): |
|
188 """ |
|
189 Private method to check, if there are plugins available. |
|
190 |
|
191 @return flag indicating the availability of plugins (boolean) |
|
192 """ |
|
193 if self.__develPluginFile and not os.path.exists(self.__develPluginFile): |
|
194 return False |
|
195 |
|
196 self.__foundCoreModules = self.getPluginModules(self.pluginDirs["eric4"]) |
|
197 if "global" in self.pluginDirs: |
|
198 self.__foundGlobalModules = \ |
|
199 self.getPluginModules(self.pluginDirs["global"]) |
|
200 if "user" in self.pluginDirs: |
|
201 self.__foundUserModules = \ |
|
202 self.getPluginModules(self.pluginDirs["user"]) |
|
203 |
|
204 return len(self.__foundCoreModules + self.__foundGlobalModules + \ |
|
205 self.__foundUserModules) > 0 |
|
206 |
|
207 def getPluginModules(self, pluginPath): |
|
208 """ |
|
209 Public method to get a list of plugin modules. |
|
210 |
|
211 @param pluginPath name of the path to search (string) |
|
212 @return list of plugin module names (list of string) |
|
213 """ |
|
214 pluginFiles = [f[:-3] for f in os.listdir(pluginPath) \ |
|
215 if self.isValidPluginName(f)] |
|
216 return pluginFiles[:] |
|
217 |
|
218 def isValidPluginName(self, pluginName): |
|
219 """ |
|
220 Public methode to check, if a file name is a valid plugin name. |
|
221 |
|
222 Plugin modules must start with "Plugin" and have the extension ".py". |
|
223 |
|
224 @param pluginName name of the file to be checked (string) |
|
225 @return flag indicating a valid plugin name (boolean) |
|
226 """ |
|
227 return pluginName.startswith("Plugin") and pluginName.endswith(".py") |
|
228 |
|
229 def __insertPluginsPaths(self): |
|
230 """ |
|
231 Private method to insert the valid plugin paths intos the search path. |
|
232 """ |
|
233 for key in self.__priorityOrder: |
|
234 if key in self.pluginDirs: |
|
235 if not self.pluginDirs[key] in sys.path: |
|
236 sys.path.insert(2, self.pluginDirs[key]) |
|
237 UI.PixmapCache.addSearchPath(self.pluginDirs[key]) |
|
238 |
|
239 if self.__develPluginFile: |
|
240 path = Utilities.splitPath(self.__develPluginFile)[0] |
|
241 if not path in sys.path: |
|
242 sys.path.insert(2, path) |
|
243 UI.PixmapCache.addSearchPath(path) |
|
244 |
|
245 def __loadPlugins(self): |
|
246 """ |
|
247 Private method to load the plugins found. |
|
248 """ |
|
249 develPluginName = "" |
|
250 if self.__develPluginFile: |
|
251 develPluginPath, develPluginName = \ |
|
252 Utilities.splitPath(self.__develPluginFile) |
|
253 if self.isValidPluginName(develPluginName): |
|
254 develPluginName = develPluginName[:-3] |
|
255 |
|
256 for pluginName in self.__foundCoreModules: |
|
257 # global and user plugins have priority |
|
258 if pluginName not in self.__foundGlobalModules and \ |
|
259 pluginName not in self.__foundUserModules and \ |
|
260 pluginName != develPluginName: |
|
261 self.loadPlugin(pluginName, self.pluginDirs["eric4"]) |
|
262 |
|
263 for pluginName in self.__foundGlobalModules: |
|
264 # user plugins have priority |
|
265 if pluginName not in self.__foundUserModules and \ |
|
266 pluginName != develPluginName: |
|
267 self.loadPlugin(pluginName, self.pluginDirs["global"]) |
|
268 |
|
269 for pluginName in self.__foundUserModules: |
|
270 if pluginName != develPluginName: |
|
271 self.loadPlugin(pluginName, self.pluginDirs["user"]) |
|
272 |
|
273 if develPluginName: |
|
274 self.loadPlugin(develPluginName, develPluginPath) |
|
275 self.__develPluginName = develPluginName |
|
276 |
|
277 def loadPlugin(self, name, directory, reload_ = False): |
|
278 """ |
|
279 Public method to load a plugin module. |
|
280 |
|
281 Initially all modules are inactive. Modules that are requested on |
|
282 demand are sorted out and are added to the on demand list. Some |
|
283 basic validity checks are performed as well. Modules failing these |
|
284 checks are added to the failed modules list. |
|
285 |
|
286 @param name name of the module to be loaded (string) |
|
287 @param directory name of the plugin directory (string) |
|
288 @param reload_ flag indicating to reload the module (boolean) |
|
289 """ |
|
290 try: |
|
291 fname = "%s.py" % os.path.join(directory, name) |
|
292 module = imp.load_source(name, fname) |
|
293 if not hasattr(module, "autoactivate"): |
|
294 module.error = \ |
|
295 self.trUtf8("Module is missing the 'autoactivate' attribute.") |
|
296 self.__failedModules[name] = module |
|
297 raise PluginLoadError(name) |
|
298 if getattr(module, "autoactivate"): |
|
299 self.__inactiveModules[name] = module |
|
300 else: |
|
301 if not hasattr(module, "pluginType") or \ |
|
302 not hasattr(module, "pluginTypename"): |
|
303 module.error = \ |
|
304 self.trUtf8("Module is missing the 'pluginType' " |
|
305 "and/or 'pluginTypename' attributes.") |
|
306 self.__failedModules[name] = module |
|
307 raise PluginLoadError(name) |
|
308 else: |
|
309 self.__onDemandInactiveModules[name] = module |
|
310 module.eric4PluginModuleName = name |
|
311 module.eric4PluginModuleFilename = fname |
|
312 self.__modulesCount += 1 |
|
313 if reload_: |
|
314 reload(module) |
|
315 except PluginLoadError: |
|
316 print "Error loading plugin module:", name |
|
317 except StandardError, err: |
|
318 module = imp.new_module(name) |
|
319 module.error = \ |
|
320 self.trUtf8("Module failed to load. Error: {0}").format(unicode(err)) |
|
321 self.__failedModules[name] = module |
|
322 print "Error loading plugin module:", name |
|
323 print unicode(err) |
|
324 |
|
325 def unloadPlugin(self, name, directory): |
|
326 """ |
|
327 Public method to unload a plugin module. |
|
328 |
|
329 @param name name of the module to be unloaded (string) |
|
330 @param directory name of the plugin directory (string) |
|
331 @return flag indicating success (boolean) |
|
332 """ |
|
333 fname = "%s.py" % os.path.join(directory, name) |
|
334 if name in self.__onDemandActiveModules and \ |
|
335 self.__onDemandActiveModules[name].eric4PluginModuleFilename == fname: |
|
336 # cannot unload an ondemand plugin, that is in use |
|
337 return False |
|
338 |
|
339 if name in self.__activeModules and \ |
|
340 self.__activeModules[name].eric4PluginModuleFilename == fname: |
|
341 self.deactivatePlugin(name) |
|
342 |
|
343 if name in self.__inactiveModules and \ |
|
344 self.__inactiveModules[name].eric4PluginModuleFilename == fname: |
|
345 try: |
|
346 del self.__inactivePlugins[name] |
|
347 except KeyError: |
|
348 pass |
|
349 del self.__inactiveModules[name] |
|
350 elif name in self.__onDemandInactiveModules and \ |
|
351 self.__onDemandInactiveModules[name].eric4PluginModuleFilename == fname: |
|
352 try: |
|
353 del self.__onDemandInactivePlugins[name] |
|
354 except KeyError: |
|
355 pass |
|
356 del self.__onDemandInactiveModules[name] |
|
357 elif name in self.__failedModules: |
|
358 del self.__failedModules[name] |
|
359 |
|
360 self.__modulesCount -= 1 |
|
361 return True |
|
362 |
|
363 def removePluginFromSysModules(self, pluginName, package, internalPackages): |
|
364 """ |
|
365 Public method to remove a plugin and all related modules from sys.modules. |
|
366 |
|
367 @param pluginName name of the plugin module (string) |
|
368 @param package name of the plugin package (string) |
|
369 @param internalPackages list of intenal packages (list of string) |
|
370 @return flag indicating the plugin module was found in sys.modules (boolean) |
|
371 """ |
|
372 packages = [package] + internalPackages |
|
373 found = False |
|
374 if not package: |
|
375 package = "__None__" |
|
376 for moduleName in sys.modules.keys()[:]: |
|
377 if moduleName == pluginName or moduleName.split(".")[0] in packages: |
|
378 found = True |
|
379 del sys.modules[moduleName] |
|
380 return found |
|
381 |
|
382 def initOnDemandPlugins(self): |
|
383 """ |
|
384 Public method to create plugin objects for all on demand plugins. |
|
385 |
|
386 Note: The plugins are not activated. |
|
387 """ |
|
388 names = sorted(self.__onDemandInactiveModules.keys()) |
|
389 for name in names: |
|
390 self.initOnDemandPlugin(name) |
|
391 |
|
392 def initOnDemandPlugin(self, name): |
|
393 """ |
|
394 Public method to create a plugin object for the named on demand plugin. |
|
395 |
|
396 Note: The plugin is not activated. |
|
397 """ |
|
398 try: |
|
399 try: |
|
400 module = self.__onDemandInactiveModules[name] |
|
401 except KeyError: |
|
402 return None |
|
403 |
|
404 if not self.__canActivatePlugin(module): |
|
405 raise PluginActivationError(module.eric4PluginModuleName) |
|
406 version = getattr(module, "version") |
|
407 className = getattr(module, "className") |
|
408 pluginClass = getattr(module, className) |
|
409 pluginObject = None |
|
410 if name not in self.__onDemandInactivePlugins: |
|
411 pluginObject = pluginClass(self.__ui) |
|
412 pluginObject.eric4PluginModule = module |
|
413 pluginObject.eric4PluginName = className |
|
414 pluginObject.eric4PluginVersion = version |
|
415 self.__onDemandInactivePlugins[name] = pluginObject |
|
416 except PluginActivationError: |
|
417 return None |
|
418 |
|
419 def activatePlugins(self): |
|
420 """ |
|
421 Public method to activate all plugins having the "autoactivate" attribute |
|
422 set to True. |
|
423 """ |
|
424 ial = Preferences.Prefs.settings.value(self.__inactivePluginsKey) |
|
425 if ial.isValid(): |
|
426 savedInactiveList = ial.toStringList() |
|
427 else: |
|
428 savedInactiveList = None |
|
429 if self.__develPluginName is not None and \ |
|
430 savedInactiveList is not None and \ |
|
431 self.__develPluginName in savedInactiveList: |
|
432 savedInactiveList.remove(self.__develPluginName) |
|
433 names = sorted(self.__inactiveModules.keys()) |
|
434 for name in names: |
|
435 if savedInactiveList is None or name not in savedInactiveList: |
|
436 self.activatePlugin(name) |
|
437 self.emit(SIGNAL("allPlugginsActivated()")) |
|
438 |
|
439 def activatePlugin(self, name, onDemand = False): |
|
440 """ |
|
441 Public method to activate a plugin. |
|
442 |
|
443 @param name name of the module to be activated |
|
444 @keyparam onDemand flag indicating activation of an |
|
445 on demand plugin (boolean) |
|
446 @return reference to the initialized plugin object |
|
447 """ |
|
448 try: |
|
449 try: |
|
450 if onDemand: |
|
451 module = self.__onDemandInactiveModules[name] |
|
452 else: |
|
453 module = self.__inactiveModules[name] |
|
454 except KeyError: |
|
455 return None |
|
456 |
|
457 if not self.__canActivatePlugin(module): |
|
458 raise PluginActivationError(module.eric4PluginModuleName) |
|
459 version = getattr(module, "version") |
|
460 className = getattr(module, "className") |
|
461 pluginClass = getattr(module, className) |
|
462 pluginObject = None |
|
463 if onDemand and name in self.__onDemandInactivePlugins: |
|
464 pluginObject = self.__onDemandInactivePlugins[name] |
|
465 elif not onDemand and name in self.__inactivePlugins: |
|
466 pluginObject = self.__inactivePlugins[name] |
|
467 else: |
|
468 pluginObject = pluginClass(self.__ui) |
|
469 self.emit(SIGNAL("pluginAboutToBeActivated"), name, pluginObject) |
|
470 try: |
|
471 obj, ok = pluginObject.activate() |
|
472 except TypeError: |
|
473 module.error = self.trUtf8("Incompatible plugin activation method.") |
|
474 obj = None |
|
475 ok = True |
|
476 except StandardError, err: |
|
477 module.error = unicode(err) |
|
478 obj = None |
|
479 ok = False |
|
480 if not ok: |
|
481 return None |
|
482 |
|
483 self.emit(SIGNAL("pluginActivated"), name, pluginObject) |
|
484 pluginObject.eric4PluginModule = module |
|
485 pluginObject.eric4PluginName = className |
|
486 pluginObject.eric4PluginVersion = version |
|
487 |
|
488 if onDemand: |
|
489 self.__onDemandInactiveModules.pop(name) |
|
490 try: |
|
491 self.__onDemandInactivePlugins.pop(name) |
|
492 except KeyError: |
|
493 pass |
|
494 self.__onDemandActivePlugins[name] = pluginObject |
|
495 self.__onDemandActiveModules[name] = module |
|
496 else: |
|
497 self.__inactiveModules.pop(name) |
|
498 try: |
|
499 self.__inactivePlugins.pop(name) |
|
500 except KeyError: |
|
501 pass |
|
502 self.__activePlugins[name] = pluginObject |
|
503 self.__activeModules[name] = module |
|
504 return obj |
|
505 except PluginActivationError: |
|
506 return None |
|
507 |
|
508 def __canActivatePlugin(self, module): |
|
509 """ |
|
510 Private method to check, if a plugin can be activated. |
|
511 |
|
512 @param module reference to the module to be activated |
|
513 @return flag indicating, if the module satisfies all requirements |
|
514 for being activated (boolean) |
|
515 """ |
|
516 try: |
|
517 if not hasattr(module, "version"): |
|
518 raise PluginModuleFormatError(module.eric4PluginModuleName, "version") |
|
519 if not hasattr(module, "className"): |
|
520 raise PluginModuleFormatError(module.eric4PluginModuleName, "className") |
|
521 className = getattr(module, "className") |
|
522 if not hasattr(module, className): |
|
523 raise PluginModuleFormatError(module.eric4PluginModuleName, className) |
|
524 pluginClass = getattr(module, className) |
|
525 if not hasattr(pluginClass, "__init__"): |
|
526 raise PluginClassFormatError(module.eric4PluginModuleName, |
|
527 className, "__init__") |
|
528 if not hasattr(pluginClass, "activate"): |
|
529 raise PluginClassFormatError(module.eric4PluginModuleName, |
|
530 className, "activate") |
|
531 if not hasattr(pluginClass, "deactivate"): |
|
532 raise PluginClassFormatError(module.eric4PluginModuleName, |
|
533 className, "deactivate") |
|
534 return True |
|
535 except PluginModuleFormatError, e: |
|
536 print repr(e) |
|
537 return False |
|
538 except PluginClassFormatError, e: |
|
539 print repr(e) |
|
540 return False |
|
541 |
|
542 def deactivatePlugin(self, name, onDemand = False): |
|
543 """ |
|
544 Public method to deactivate a plugin. |
|
545 |
|
546 @param name name of the module to be deactivated |
|
547 @keyparam onDemand flag indicating deactivation of an |
|
548 on demand plugin (boolean) |
|
549 """ |
|
550 try: |
|
551 if onDemand: |
|
552 module = self.__onDemandActiveModules[name] |
|
553 else: |
|
554 module = self.__activeModules[name] |
|
555 except KeyError: |
|
556 return |
|
557 |
|
558 if self.__canDeactivatePlugin(module): |
|
559 pluginObject = None |
|
560 if onDemand and name in self.__onDemandActivePlugins: |
|
561 pluginObject = self.__onDemandActivePlugins[name] |
|
562 elif not onDemand and name in self.__activePlugins: |
|
563 pluginObject = self.__activePlugins[name] |
|
564 if pluginObject: |
|
565 self.emit(SIGNAL("pluginAboutToBeDeactivated"), name, pluginObject) |
|
566 pluginObject.deactivate() |
|
567 self.emit(SIGNAL("pluginDeactivated"), name, pluginObject) |
|
568 |
|
569 if onDemand: |
|
570 self.__onDemandActiveModules.pop(name) |
|
571 self.__onDemandActivePlugins.pop(name) |
|
572 self.__onDemandInactivePlugins[name] = pluginObject |
|
573 self.__onDemandInactiveModules[name] = module |
|
574 else: |
|
575 self.__activeModules.pop(name) |
|
576 try: |
|
577 self.__activePlugins.pop(name) |
|
578 except KeyError: |
|
579 pass |
|
580 self.__inactivePlugins[name] = pluginObject |
|
581 self.__inactiveModules[name] = module |
|
582 |
|
583 def __canDeactivatePlugin(self, module): |
|
584 """ |
|
585 Private method to check, if a plugin can be deactivated. |
|
586 |
|
587 @param module reference to the module to be deactivated |
|
588 @return flag indicating, if the module satisfies all requirements |
|
589 for being deactivated (boolean) |
|
590 """ |
|
591 return getattr(module, "deactivateable", True) |
|
592 |
|
593 def getPluginObject(self, type_, typename, maybeActive = False): |
|
594 """ |
|
595 Public method to activate an ondemand plugin given by type and typename. |
|
596 |
|
597 @param type_ type of the plugin to be activated (string) |
|
598 @param typename name of the plugin within the type category (string) |
|
599 @keyparam maybeActive flag indicating, that the plugin may be active |
|
600 already (boolean) |
|
601 @return reference to the initialized plugin object |
|
602 """ |
|
603 for name, module in self.__onDemandInactiveModules.items(): |
|
604 if getattr(module, "pluginType") == type_ and \ |
|
605 getattr(module, "pluginTypename") == typename: |
|
606 return self.activatePlugin(name, onDemand = True) |
|
607 |
|
608 if maybeActive: |
|
609 for name, module in self.__onDemandActiveModules.items(): |
|
610 if getattr(module, "pluginType") == type_ and \ |
|
611 getattr(module, "pluginTypename") == typename: |
|
612 self.deactivatePlugin(name, onDemand = True) |
|
613 return self.activatePlugin(name, onDemand = True) |
|
614 |
|
615 return None |
|
616 |
|
617 def getPluginInfos(self): |
|
618 """ |
|
619 Public method to get infos about all loaded plugins. |
|
620 |
|
621 @return list of tuples giving module name (string), plugin name (string), |
|
622 version (string), autoactivate (boolean), active (boolean), |
|
623 short description (string), error flag (boolean) |
|
624 """ |
|
625 infos = [] |
|
626 |
|
627 for name in self.__activeModules.keys(): |
|
628 pname, shortDesc, error, version = \ |
|
629 self.__getShortInfo(self.__activeModules[name]) |
|
630 infos.append((name, pname, version, True, True, shortDesc, error)) |
|
631 for name in self.__inactiveModules.keys(): |
|
632 pname, shortDesc, error, version = \ |
|
633 self.__getShortInfo(self.__inactiveModules[name]) |
|
634 infos.append((name, pname, version, True, False, shortDesc, error)) |
|
635 for name in self.__onDemandActiveModules.keys(): |
|
636 pname, shortDesc, error, version = \ |
|
637 self.__getShortInfo(self.__onDemandActiveModules[name]) |
|
638 infos.append((name, pname, version, False, True, shortDesc, error)) |
|
639 for name in self.__onDemandInactiveModules.keys(): |
|
640 pname, shortDesc, error, version = \ |
|
641 self.__getShortInfo(self.__onDemandInactiveModules[name]) |
|
642 infos.append((name, pname, version, False, False, shortDesc, error)) |
|
643 for name in self.__failedModules.keys(): |
|
644 pname, shortDesc, error, version = \ |
|
645 self.__getShortInfo(self.__failedModules[name]) |
|
646 infos.append((name, pname, version, False, False, shortDesc, error)) |
|
647 return infos |
|
648 |
|
649 def __getShortInfo(self, module): |
|
650 """ |
|
651 Private method to extract the short info from a module. |
|
652 |
|
653 @param module module to extract short info from |
|
654 @return short info as a tuple giving plugin name (string), |
|
655 short description (string), error flag (boolean) and |
|
656 version (string) |
|
657 """ |
|
658 name = getattr(module, "name", "") |
|
659 shortDesc = getattr(module, "shortDescription", "") |
|
660 version = getattr(module, "version", "") |
|
661 error = getattr(module, "error", "") != "" |
|
662 return name, shortDesc, error, version |
|
663 |
|
664 def getPluginDetails(self, name): |
|
665 """ |
|
666 Public method to get detailed information about a plugin. |
|
667 |
|
668 @param name name of the module to get detailed infos about (string) |
|
669 @return details of the plugin as a dictionary |
|
670 """ |
|
671 details = {} |
|
672 |
|
673 autoactivate = True |
|
674 active = True |
|
675 |
|
676 if name in self.__activeModules: |
|
677 module = self.__activeModules[name] |
|
678 elif name in self.__inactiveModules: |
|
679 module = self.__inactiveModules[name] |
|
680 active = False |
|
681 elif name in self.__onDemandActiveModules: |
|
682 module = self.__onDemandActiveModules[name] |
|
683 autoactivate = False |
|
684 elif name in self.__onDemandInactiveModules: |
|
685 module = self.__onDemandInactiveModules[name] |
|
686 autoactivate = False |
|
687 active = False |
|
688 elif name in self.__failedModules: |
|
689 module = self.__failedModules[name] |
|
690 autoactivate = False |
|
691 active = False |
|
692 else: |
|
693 # should not happen |
|
694 return None |
|
695 |
|
696 details["moduleName"] = name |
|
697 details["moduleFileName"] = getattr(module, "eric4PluginModuleFilename", "") |
|
698 details["pluginName"] = getattr(module, "name", "") |
|
699 details["version"] = getattr(module, "version", "") |
|
700 details["author"] = getattr(module, "author", "") |
|
701 details["description"] = getattr(module, "longDescription", "") |
|
702 details["autoactivate"] = autoactivate |
|
703 details["active"] = active |
|
704 details["error"] = getattr(module, "error", "") |
|
705 |
|
706 return details |
|
707 |
|
708 def shutdown(self): |
|
709 """ |
|
710 Public method called to perform actions upon shutdown of the IDE. |
|
711 """ |
|
712 names = [] |
|
713 for name in self.__inactiveModules.keys(): |
|
714 names.append(name) |
|
715 Preferences.Prefs.settings.setValue(self.__inactivePluginsKey, QVariant(names)) |
|
716 |
|
717 self.emit(SIGNAL("shutdown()")) |
|
718 |
|
719 def getPluginDisplayStrings(self, type_): |
|
720 """ |
|
721 Public method to get the display strings of all plugins of a specific type. |
|
722 |
|
723 @param type_ type of the plugins (string) |
|
724 @return dictionary with name as key and display string as value |
|
725 (dictionary of string) |
|
726 """ |
|
727 pluginDict = {} |
|
728 |
|
729 for name, module in \ |
|
730 self.__onDemandActiveModules.items() + self.__onDemandInactiveModules.items(): |
|
731 if getattr(module, "pluginType") == type_ and \ |
|
732 getattr(module, "error", "") == "": |
|
733 plugin_name = getattr(module, "pluginTypename") |
|
734 if hasattr(module, "displayString"): |
|
735 try: |
|
736 disp = module.displayString() |
|
737 except TypeError: |
|
738 disp = getattr(module, "displayString") |
|
739 if disp != "": |
|
740 pluginDict[plugin_name] = disp |
|
741 else: |
|
742 pluginDict[plugin_name] = plugin_name |
|
743 |
|
744 return pluginDict |
|
745 |
|
746 def getPluginPreviewPixmap(self, type_, name): |
|
747 """ |
|
748 Public method to get a preview pixmap of a plugin of a specific type. |
|
749 |
|
750 @param type_ type of the plugin (string) |
|
751 @param name name of the plugin type (string) |
|
752 @return preview pixmap (QPixmap) |
|
753 """ |
|
754 for modname, module in \ |
|
755 self.__onDemandActiveModules.items() + self.__onDemandInactiveModules.items(): |
|
756 if getattr(module, "pluginType") == type_ and \ |
|
757 getattr(module, "pluginTypename") == name: |
|
758 if hasattr(module, "previewPix"): |
|
759 return module.previewPix() |
|
760 else: |
|
761 return QPixmap() |
|
762 |
|
763 return QPixmap() |
|
764 |
|
765 def getPluginApiFiles(self, language): |
|
766 """ |
|
767 Public method to get the list of API files installed by a plugin. |
|
768 |
|
769 @param language language of the requested API files (string) |
|
770 @return list of API filenames (list of string) |
|
771 """ |
|
772 apis = [] |
|
773 |
|
774 for module in self.__activeModules.values() + \ |
|
775 self.__onDemandActiveModules.values(): |
|
776 if hasattr(module, "apiFiles"): |
|
777 apis.extend(module.apiFiles(language)) |
|
778 |
|
779 return apis |
|
780 |
|
781 def getPluginExeDisplayData(self): |
|
782 """ |
|
783 Public method to get data to display information about a plugins |
|
784 external tool. |
|
785 |
|
786 @return list of dictionaries containing the data. Each dictionary must |
|
787 either contain data for the determination or the data to be displayed.<br /> |
|
788 A dictionary of the first form must have the following entries: |
|
789 <ul> |
|
790 <li>programEntry - indicator for this dictionary form (boolean), |
|
791 always True</li> |
|
792 <li>header - string to be diplayed as a header (string)</li> |
|
793 <li>exe - the executable (string)</li> |
|
794 <li>versionCommand - commandline parameter for the exe (string)</li> |
|
795 <li>versionStartsWith - indicator for the output line containing |
|
796 the version (string)</li> |
|
797 <li>versionPosition - number of element containing the |
|
798 version (integer)</li> |
|
799 <li>version - version to be used as default (string)</li> |
|
800 <li>versionCleanup - tuple of two integers giving string positions |
|
801 start and stop for the version string (tuple of integers)</li> |
|
802 </ul> |
|
803 A dictionary of the second form must have the following entries: |
|
804 <ul> |
|
805 <li>programEntry - indicator for this dictionary form (boolean), |
|
806 always False</li> |
|
807 <li>header - string to be diplayed as a header (string)</li> |
|
808 <li>text - entry text to be shown (string)</li> |
|
809 <li>version - version text to be shown (string)</li> |
|
810 </ul> |
|
811 """ |
|
812 infos = [] |
|
813 |
|
814 for module in self.__activeModules.values() + \ |
|
815 self.__inactiveModules.values(): |
|
816 if hasattr(module, "exeDisplayData"): |
|
817 infos.append(module.exeDisplayData()) |
|
818 for module in self.__onDemandActiveModules.values() + \ |
|
819 self.__onDemandInactiveModules.values(): |
|
820 if hasattr(module, "exeDisplayData"): |
|
821 infos.append(module.exeDisplayData()) |
|
822 |
|
823 return infos |
|
824 |
|
825 def getPluginConfigData(self): |
|
826 """ |
|
827 Public method to get the config data of all active, non on-demand plugins |
|
828 used by the configuration dialog. |
|
829 |
|
830 Plugins supporting this functionality must provide the plugin module |
|
831 function 'getConfigData' returning a dictionary with unique keys |
|
832 of lists with the following list contents: |
|
833 <dl> |
|
834 <dt>display string</dt> |
|
835 <dd>string shown in the selection area of the configuration page. |
|
836 This should be a localized string</dd> |
|
837 <dt>pixmap name</dt> |
|
838 <dd>filename of the pixmap to be shown next to the display string</dd> |
|
839 <dt>page creation function</dt> |
|
840 <dd>plugin module function to be called to create the configuration |
|
841 page. The page must be subclasses from |
|
842 Preferences.ConfigurationPages.ConfigurationPageBase and must |
|
843 implement a method called 'save' to save the settings. A parent |
|
844 entry will be created in the selection list, if this value is None.</dd> |
|
845 <dt>parent key</dt> |
|
846 <dd>dictionary key of the parent entry or None, if this defines a |
|
847 toplevel entry.</dd> |
|
848 <dt>reference to configuration page</dt> |
|
849 <dd>This will be used by the configuration dialog and must always be None</dd> |
|
850 </dl> |
|
851 """ |
|
852 configData = {} |
|
853 for module in self.__activeModules.values() + \ |
|
854 self.__onDemandActiveModules.values() + \ |
|
855 self.__onDemandInactiveModules.values(): |
|
856 if hasattr(module, 'getConfigData'): |
|
857 configData.update(module.getConfigData()) |
|
858 return configData |
|
859 |
|
860 def isPluginLoaded(self, pluginName): |
|
861 """ |
|
862 Public method to check, if a certain plugin is loaded. |
|
863 |
|
864 @param pluginName name of the plugin to check for (string or QString) |
|
865 @return flag indicating, if the plugin is loaded (boolean) |
|
866 """ |
|
867 return pluginName in self.__activeModules or \ |
|
868 pluginName in self.__inactiveModules or \ |
|
869 pluginName in self.__onDemandActiveModules or \ |
|
870 pluginName in self.__onDemandInactiveModules |
|
871 |
|
872 def isPluginActive(self, pluginName): |
|
873 """ |
|
874 Public method to check, if a certain plugin is active. |
|
875 |
|
876 @param pluginName name of the plugin to check for (string or QString) |
|
877 @return flag indicating, if the plugin is active (boolean) |
|
878 """ |
|
879 return pluginName in self.__activeModules or \ |
|
880 pluginName in self.__onDemandActiveModules |
|
881 |
|
882 ############################################################################ |
|
883 ## Specialized plugin module handling methods below |
|
884 ############################################################################ |
|
885 |
|
886 ############################################################################ |
|
887 ## VCS related methods below |
|
888 ############################################################################ |
|
889 |
|
890 def getVcsSystemIndicators(self): |
|
891 """ |
|
892 Public method to get the Vcs System indicators. |
|
893 |
|
894 Plugins supporting this functionality must support the module function |
|
895 getVcsSystemIndicator returning a dictionary with indicator as key and |
|
896 a tuple with the vcs name (string) and vcs display string (string). |
|
897 |
|
898 @return dictionary with indicator as key and a list of tuples as values. |
|
899 Each tuple contains the vcs name (string) and vcs display string (string). |
|
900 """ |
|
901 vcsDict = {} |
|
902 |
|
903 for name, module in \ |
|
904 self.__onDemandActiveModules.items() + self.__onDemandInactiveModules.items(): |
|
905 if getattr(module, "pluginType") == "version_control": |
|
906 if hasattr(module, "getVcsSystemIndicator"): |
|
907 res = module.getVcsSystemIndicator() |
|
908 for indicator, vcsData in res.items(): |
|
909 if indicator in vcsDict: |
|
910 vcsDict[indicator].append(vcsData) |
|
911 else: |
|
912 vcsDict[indicator] = [vcsData] |
|
913 |
|
914 return vcsDict |
|
915 |
|
916 def deactivateVcsPlugins(self): |
|
917 """ |
|
918 Public method to deactivated all activated VCS plugins. |
|
919 """ |
|
920 for name, module in self.__onDemandActiveModules.items(): |
|
921 if getattr(module, "pluginType") == "version_control": |
|
922 self.deactivatePlugin(name, True) |
|
923 |
|
924 def __checkPluginsDownloadDirectory(self): |
|
925 """ |
|
926 Private slot to check for the existence of the plugins download directory. |
|
927 """ |
|
928 downloadDir = Preferences.getPluginManager("DownloadPath") |
|
929 if not downloadDir: |
|
930 downloadDir = self.__defaultDownloadDir |
|
931 |
|
932 if not os.path.exists(downloadDir): |
|
933 try: |
|
934 os.mkdir(downloadDir, 0755) |
|
935 except (OSError, IOError), err: |
|
936 # try again with (possibly) new default |
|
937 downloadDir = self.__defaultDownloadDir |
|
938 if not os.path.exists(downloadDir): |
|
939 try: |
|
940 os.mkdir(downloadDir, 0755) |
|
941 except (OSError, IOError), err: |
|
942 QMessageBox.critical(self.__ui, |
|
943 self.trUtf8("Plugin Manager Error"), |
|
944 self.trUtf8("""<p>The plugin download directory <b>{0}</b> """ |
|
945 """could not be created. Please configure it """ |
|
946 """via the configuration dialog.</p>""" |
|
947 """<p>Reason: {1}</p>""")\ |
|
948 .format(downloadDir, unicode(err))) |
|
949 downloadDir = "" |
|
950 |
|
951 Preferences.setPluginManager("DownloadPath", downloadDir) |
|
952 |
|
953 def preferencesChanged(self): |
|
954 """ |
|
955 Public slot to react to changes in configuration. |
|
956 """ |
|
957 self.__checkPluginsDownloadDirectory() |