Source code for gui.menus

#
##
##  SPDX-FileCopyrightText: © 2007-2024 Benedict Verhegghe <bverheg@gmail.com>
##  SPDX-License-Identifier: GPL-3.0-or-later
##
##  This file is part of pyFormex 3.5  (Thu Feb  8 19:11:13 CET 2024)
##  pyFormex is a tool for generating, manipulating and transforming 3D
##  geometrical models by sequences of mathematical operations.
##  Home page: https://pyformex.org
##  Project page: https://savannah.nongnu.org/projects/pyformex/
##  Development: https://gitlab.com/bverheg/pyformex
##  Distributed under the GNU General Public License version 3 or later.
##
##  This program is free software: you can redistribute it and/or modify
##  it under the terms of the GNU General Public License as published by
##  the Free Software Foundation, either version 3 of the License, or
##  (at your option) any later version.
##
##  This program is distributed in the hope that it will be useful,
##  but WITHOUT ANY WARRANTY; without even the implied warranty of
##  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
##  GNU General Public License for more details.
##
##  You should have received a copy of the GNU General Public License
##  along with this program.  If not, see http://www.gnu.org/licenses/.
##
"""pyFormex plugin menus.

This module contains the functions to detect and load the pyFormex
plugin menus.
"""
import sys
from types import ModuleType
import importlib

import pyformex as pf
from pyformex.gui.menu import Menu
from pyformex.gui.menus import (File, Settings, Viewport)  # REQUIRED


# TODO: turn the menu_setup attribute into a setup argument?
[docs]class PluginMenu(): """A registered reloadable plugin menu. A plugin menu is a pull down menu installed in the top menu bar of the GUI. It is defined in a Python module living in an accessible package. The default package is ``pyformex.gui.menus``. The module should define an attribute ``menu_items`` which is a list of menu items usable to create a :class:`~pyformex.gui.menu.Menu` instance. The plugin module can also define a function ``menu_setup(menu)``. If so, this function is called when the plugin menu is (re)loaded. The function takes the created menu as a parameter. It can e.g. be used to enable/disable menu options, to dynamically change the menu contents at runtime, to reset the PluginMenu's module or for anything you want to be done on reloading the menu. Creating a PluginMenu creates and registers the menu, but does not automatically show it. Use the :meth:`show` method to let it appear in the menubar. See the ``pyformex.gui.menus`` ``pyformex.apps`` packages for examples. Parameters ---------- name: str The name of the menu. This is the name as appearing in the menu bar. This is normally a single short capitalized word. It should be different from all other plugin menus (unless you want to override another plugin with the same name). modname: str, optional The name of the module that contains the plugin menu. The default value is the name converted to lower case and with '_menu' appended. package: str, optional The dotted name of the package containing the plugin module. The default is 'pyformex.gui.menus'. menuctrl: str, optional Defines if and how automatic menu control items should be added. A string starting with 's' will insert them as as submenu, a string starting with 'i' will insert them inline with the other menu options, any other string will not show the menu control items. The menu control currently contains two items: reload the menu, and close the menu. THe default is to insert them as a submenu. """ # The register is kept in order of registering _register_ = {} def __init__(self, name, *, modname=None, package='pyformex.gui.menus', menuctrl='submenu', persistent=False): self.name = name self.modname = modname if modname else self.name self.pkg = package self.module = None # not loaded self.parent = None # Two ways for the extras if menuctrl.startswith('i'): # 1. as normal items in the menu self._extras = [ ("Reload Menu", self.reload), ("Close Menu", self.close), ] elif menuctrl.startswith('s'): # 2. as a small submenu self._extras = [ ('Menu Control', [ ("Reload", self.reload), ("Close", self.close), ]) ] else: self._extras = [] self.persistent = bool(persistent) self.__class__._register_[self.name] = self @property def fullname(self): return f"{self.pkg}.{self.modname}" @property def nicename(self): return self.name + ' Menu'
[docs] def show(self, parent=None, before='help', reload=False): """Show the menu.""" if pf.debugon(pf.DEBUG.PLUGIN): print(f" {'Rel' if reload else 'L'}oading plugin menu:", self.fullname) if parent is None: parent = pf.GUI.menu if reload and self.parent: # first close (using old parent) action = self.parent.action(self.name) before = self.parent.nextItem(action) self.close() # import/reload the module importlib.import_module(self.fullname) self.module = module = sys.modules.get(self.fullname, None) if module: if reload: importlib.reload(module) else: pf.error("No such module: %s" % self.fullname) self.parent = parent # set the new parent if self.module and not self.parent[self.name]: self.menu = Menu(self.name, items=self.module.menu_items + self._extras, func=getattr(self.module, 'menu_func', None), parent=self.parent, before=before) # Process the special enable function setup = getattr(module, 'menu_setup', None) if callable(setup): setup(self.menu)
def reload(self): self.show(reload=True)
[docs] def close(self): """Close the menu.""" if pf.debugon(pf.DEBUG.PLUGIN): print(" Closing plugin menu:", self.name) if self.parent: self.parent.removeItem(self.name)
[docs] @classmethod def list(clas): """Return a list of registered plugin menus. Returns ------- list of tuple A list of tuples (name, nicename), where name is the module name of the plugin and Nice Name is the beautified displayed name for user readability. Thus 'Geometry' can be displayed as 'Geometry Menu'). """ return [(m.name, m.nicename) for m in clas._register_.values()]
# Register the pyFormex plugin menus PluginMenu('File', persistent=True) PluginMenu('Settings', persistent=True) PluginMenu('Viewport', persistent=True) PluginMenu('Camera') PluginMenu('Actions') PluginMenu('Geometry', persistent=True) PluginMenu('Mesh') PluginMenu('Surface') PluginMenu('Tools') PluginMenu('Draw2d') PluginMenu('Nurbs') # PluginMenu('Dxf') # -> attic # PluginMenu('Bifmesh') # -> bifmesh app # PluginMenu('Jobs') # -> fe app # PluginMenu('Postproc') # -> fe app PluginMenu('Help')
[docs]def loadConfiguredPlugins(ok_plugins=None, parent=None): """Load or unload plugin menus. Loads the specified plugins and unloads all others. If None specified, load the user configured plugins. """ if pf.debugon(pf.DEBUG.MENU): print(f"{ok_plugins=}, {parent=}") if ok_plugins is None: ok_plugins = pf.cfg['gui/menus'] elif ok_plugins == 'all': ok_plugins = list(PluginMenu._register_.keys()) if parent is None: parent = pf.GUI.menu if pf.debugon(pf.DEBUG.MENU): print("Configured plugins:", ok_plugins) for p in PluginMenu._register_.values(): # this defines the order ! if pf.debugon(pf.DEBUG.MENU): print("Plugin menu:", p.name) if p.name in ok_plugins: p.show(parent=parent) elif p.parent and not p.persistent: p.close()
# End