1 # -*- coding: utf-8 -*-
2 # Copyright (C) 2007-2022 CEA/DEN, EDF R&D, OPEN CASCADE
4 # This library is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU Lesser General Public
6 # License as published by the Free Software Foundation; either
7 # version 2.1 of the License, or (at your option) any later version.
9 # This library is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 # Lesser General Public License for more details.
14 # You should have received a copy of the GNU Lesser General Public
15 # License along with this library; if not, write to the Free Software
16 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
22 This module is imported from C++ SalomeApp_Application and initialized
23 (call to initialize function with 4 parameters)
24 module : 0 if it is plugins manager at the application level, 1 if it is at the module level
25 name : the name of the plugins manager. This name is used to build the name of the plugins files
26 basemenuname : the name of the menu into we want to add the menu of the plugins ("Tools" for example)
27 menuname : the name of plugins menu
29 A plugins manager is created when calling initialize.
31 The plugins manager creates a submenu <menuname> in the <basemenuname>
34 The plugins manager searches in $HOME/.config/salome/Plugins,
35 $HOME/$APPLI/Plugins, $SALOME_PLUGINS_PATH directories files named
36 <name>_plugins.py and executes them.
38 These files should contain python code that register functions into
41 Example of a plugins manager with name salome. It searches files with
42 name salome_plugins.py (example follows)::
44 import salome_pluginsmanager
47 from qtsalome import QMessageBox
48 QMessageBox.about(None, "About SALOME pluginmanager", "SALOME plugins manager in SALOME virtual application ")
50 salome_pluginsmanager.AddFunction('About plugins','About SALOME pluginmanager',about)
52 All entries in menu are added in the same order as the calls to
53 AddFunction. It is possible to customize this presentation by getting
54 the entries list (salome_pluginsmanager.entries()) and modifying it in
55 place. For example, you can do that :
56 salome_pluginsmanager.entries().sort() to order them alphabetically or
57 salome_pluginsmanager.entries().remove("a") to remove the entry named "a".
59 It is possible to put entries in submenus. You only need to give a
60 name with / to the entry. for example::
62 salome_pluginsmanager.AddFunction('a/b/About','About SALOME pluginmanager',about)
64 will add 2 submenus a and b before creating the entry.
66 In short to add a plugin:
68 1. import the python module salome_pluginsmanager (in your
69 salome_plugins.py or <module>_plugins.py)
71 2. write a function with one argument context (it's an object with 3
74 3. register the function with a call to AddFunction (entry in menu plugins,
79 - sg : the SALOME Swig interface
80 - study : the SALOME study object that must be used to execute the plugin
84 import os,sys,traceback
85 from qtsalome import *
90 if sys.platform == "win32":
93 # Get SALOME PyQt interface
95 sgPyQt = SalomePyQt.SalomePyQt()
97 # Get SALOME Swig interface
99 sg = libSALOME_Swig.SALOMEGUI_Swig()
102 current_plugins_manager=None
104 def initialize(module,name,basemenuname,menuname):
105 if name not in plugins:
111 d=sgPyQt.getDesktop()
112 if d in plugins[name]:return
113 plugins[name][d]=PluginsManager(module,name,basemenuname,menuname)
115 plugins[name].append(PluginsManager(module,name,basemenuname,menuname))
118 def __init__(self,sgpyqt):
120 self.study=salome.myStudy
122 def find_menu(smenu):
123 lmenus=smenu.split("|")
124 # Take first element from the list
125 main=lmenus.pop(0).strip()
126 menu=sgPyQt.getPopupMenu(main)
127 return findMenu(lmenus,menu)
129 def findMenu(lmenu,menu):
130 if not lmenu:return menu
131 # Take first element from the list
132 m=lmenu.pop(0).strip()
133 for a in menu.actions():
136 return findMenu(lmenu,a.menu())
138 PLUGIN_PATH_PATTERN="share/salome/plugins"
139 MATCH_ENDING_PATTERN="_plugins.py"
140 from salome.kernel.syshelper import walktree
141 from salome.kernel.logger import Logger
142 #from salome.kernel.termcolor import GREEN
143 logger=Logger("PluginsManager") #,color=GREEN)
144 # VSR 21/11/2011 : do not show infos in the debug mode
147 class PluginsManager:
148 def __init__(self,module,name,basemenuname,menuname):
150 self.basemenuname=basemenuname
151 self.menuname=menuname
158 self.plugins_files=[]
161 # MODULES plugins directory.
162 # The SALOME modules may provides natively some plugins. These
163 # MODULES plugins are supposed to be located in the
164 # installation folder of the module, in the subdirectory
165 # "share/salome/plugins". We first look for these directories.
167 for key in os.environ.keys():
168 if key.endswith("_ROOT_DIR"):
169 rootpath=os.environ[key]
170 dirpath=os.path.join(rootpath,PLUGIN_PATH_PATTERN)
171 if os.path.isdir(dirpath) and dirpath not in self.plugindirs + searched:
172 logger.debug("Looking for plugins in the directory %s ..."%dirpath)
173 walktree(dirpath,self.analyseFile)
174 if dirpath not in self.plugindirs and dirpath not in searched:
175 searched.append(dirpath)
177 # USER plugins directory
178 user_dir = os.path.expanduser("~/.config/salome/Plugins")
179 self.plugindirs.append(user_dir)
180 logger.info("The user directory %s has been added to plugin paths"%user_dir)
181 # obsolete: USER plugins directory
182 # (for compatibility reasons only; new plugins should be stored in ~/.config/salome/Plugins)
183 user_obsolete_dir = os.path.expanduser("~/.salome/Plugins")
184 self.plugindirs.append(user_obsolete_dir)
185 logger.info("The user directory %s has been added to plugin paths (deprecated)"%user_obsolete_dir)
187 # APPLI plugins directory
188 appli=os.getenv("APPLI")
190 appli_dir=os.path.join(os.path.expanduser("~"),appli,"Plugins")
191 self.plugindirs.append(appli_dir)
192 logger.info("The APPLI directory %s has been added to plugin paths"%appli_dir)
194 #SALOME_PLUGINS_PATH environment variable (list of directories separated by ":")
195 pluginspath=os.getenv("SALOME_PLUGINS_PATH")
197 for directory in pluginspath.split(SEP):
198 self.plugindirs.append(directory)
199 logger.info("The directory %s has been added to plugin paths"%directory)
201 self.basemenu = find_menu(self.basemenuname)
204 self.menu=QMenu(self.menuname)
205 mid=sgPyQt.createMenu(self.menu.menuAction(),self.basemenuname)
207 self.menu=QMenu(self.menuname,self.basemenu)
208 self.basemenu.addMenu(self.menu)
209 self.toolbar=sgPyQt.createTool(self.menuname)
211 self.menu.menuAction().setVisible(False)
213 self.basemenu.aboutToShow.connect(self.importPlugins)
215 self.importPlugins() # to create toolbar immediately
217 def analyseFile(self,filename):
219 This function checks if the specified file is a plugins python
220 module and add the directory name of this file to the list of
221 plugin paths. This function is aimed to be used as the callback
222 function of the walktree algorithm.
224 if str(filename).endswith(MATCH_ENDING_PATTERN):
225 dirpath=os.path.dirname(filename)
226 if dirpath not in self.plugindirs:
227 self.plugindirs.append(dirpath)
228 logger.debug("The directory %s has been added to plugin paths"%dirpath)
230 def AddFunction(self,name,description,script,icon=None):
231 """ Add a plugin function
233 self.registry[name]=script,description,icon
234 self.entries.append(name)
236 def handler(obj=self,script=script):
238 script(Context(sgPyQt))
240 s=traceback.format_exc()
241 QMessageBox.warning(None,"Exception occured",s)
243 self.handlers[name]=handler
245 def importPlugins(self):
246 """Execute the salome_plugins file that contains plugins definition """
248 prefix_ior = "--iorfakens="
249 presence_ior = [elt for elt in QApplication.arguments() if elt[:len(prefix_ior)]==prefix_ior]
250 if any(presence_ior):
251 ior_fake_ns = presence_ior[-1][len(prefix_ior):]
252 if self.lasttime ==0 or salome.myStudy == None:
253 salome.salome_init(embedded=True,iorfakensfile=ior_fake_ns)
258 plugins_file_name=self.name+MATCH_ENDING_PATTERN
259 for directory in self.plugindirs:
260 plugins_file = os.path.join(directory,plugins_file_name)
261 if os.path.isfile(plugins_file):
262 plugins_files.append((directory,plugins_file))
263 lasttime=max(lasttime,os.path.getmtime(plugins_file))
267 if not plugins_files:
268 self.registry.clear()
269 self.handlers.clear()
273 self.menu.menuAction().setVisible(False)
276 if self.plugins_files != plugins_files or lasttime > self.lasttime:
277 global current_plugins_manager
278 current_plugins_manager=self
279 self.registry.clear()
280 self.handlers.clear()
282 self.lasttime=lasttime
283 for directory,plugins_file in plugins_files:
284 logger.debug("look for python path: %s"%directory)
285 if directory not in sys.path:
286 sys.path.insert(0,directory)
287 logger.debug("The directory %s has been added to PYTHONPATH"%directory)
289 with open(plugins_file, 'rb') as fp:
290 exec(compile(fp.read(), plugins_file, 'exec'), globals(), {})
292 logger.critical("Error while loading plugins from file %s"%plugins_file)
293 traceback.print_exc()
297 def updateMenu(self):
298 """Update the Plugins menu"""
300 sgPyQt.clearTool(self.menuname)
301 for entry in self.entries:
302 names=entry.split("/")
303 if len(names) < 1:continue
307 #create or get submenus
309 for action in parentMenu.actions():
312 submenus[str(menu.title())]=menu
313 while len(names) > 1:
318 amenu=QMenu(name,parentMenu)
319 parentMenu.addMenu(amenu)
324 act=parentMenu.addAction(name,self.handlers[entry])
325 act.setStatusTip(self.registry[entry][1])
326 icon = self.registry[entry][2] if len(self.registry[entry])>2 else None
327 if icon is not None and not icon.isNull() and icon.availableSizes():
329 sgPyQt.createTool(act, self.toolbar)
331 self.menu.menuAction().setVisible(True)
333 def AddFunction(name,description,script,icon=None):
334 """ Add a plugin function
335 Called by a user to register a function (script)
337 return current_plugins_manager.AddFunction(name,description,script,icon)
340 """ Return the list of entries in menu: can be sorted or modified in place to customize menu content """
341 return current_plugins_manager.entries