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