|
15 | 15 |
|
16 | 16 | assert sys.version[0] == '3' |
17 | 17 |
|
| 18 | +def parse_load_modules(argv): |
| 19 | + modules = [m.strip() for m in argv.split(",") if m.strip()] |
| 20 | + if not modules: |
| 21 | + return None |
| 22 | + return modules |
| 23 | + |
18 | 24 | from argparse import ArgumentParser |
19 | 25 | parser = ArgumentParser(description='DroneCAN GUI tool') |
20 | 26 |
|
|
28 | 34 | parser.add_argument("--filtered", action='store_true', help="enable filtering of DroneCAN traffic") |
29 | 35 | parser.add_argument("--target-system", help="set the targetted system", type=int, default=0) |
30 | 36 |
|
| 37 | +parser.add_argument("--load-module", type=parse_load_modules, nargs=1, help="Comma-separated list of modules to load (e.g. mod1,mod2).") # exactly one argument required if flag is present |
| 38 | + |
31 | 39 | args = parser.parse_args() |
32 | 40 |
|
33 | 41 | # |
|
95 | 103 | from .widgets.can_adapter_control_panel import spawn_window as spawn_can_adapter_control_panel |
96 | 104 |
|
97 | 105 | from .panels import PANELS |
| 106 | +from .panels import import_panel |
98 | 107 |
|
| 108 | +EXT_PLUGINS = [] |
| 109 | +modules = args.load_module[0] if args.load_module else [] |
| 110 | +if len(modules) > 0: |
| 111 | + for module in modules: |
| 112 | + try: |
| 113 | + panel = import_panel(module) |
| 114 | + EXT_PLUGINS.append(panel) |
| 115 | + except Exception as ex: |
| 116 | + print(f"Unable to load {module}: {ex}") |
| 117 | + print(f"Loaded {len(EXT_PLUGINS)} plugin modules!") |
99 | 118 |
|
100 | 119 | NODE_NAME = 'org.dronecan.gui_tool' |
101 | 120 |
|
@@ -204,6 +223,39 @@ def __init__(self, node, iface_name, iface_kwargs): |
204 | 223 | action.triggered.connect(lambda state, panel=panel: panel.safe_spawn(self, self._node)) |
205 | 224 | panels_menu.addAction(action) |
206 | 225 |
|
| 226 | + # |
| 227 | + # External Modules menu |
| 228 | + # |
| 229 | + def get_or_create_submenu(parent_menu, menu_name): |
| 230 | + """ |
| 231 | + Find a submenu with menu_name under parent_menu, or create it if not found. |
| 232 | + """ |
| 233 | + for action in parent_menu.actions(): |
| 234 | + submenu = action.menu() |
| 235 | + if submenu and submenu.title() == menu_name: |
| 236 | + return submenu |
| 237 | + # Not found, create new submenu |
| 238 | + return parent_menu.addMenu(menu_name) |
| 239 | + |
| 240 | + if len(EXT_PLUGINS) > 0: |
| 241 | + extern_modules_menu = self.menuBar().addMenu('P&lugins') |
| 242 | + for idx, panel in enumerate(EXT_PLUGINS): |
| 243 | + menu_path = getattr(panel, "menu_path", "") |
| 244 | + path_parts = [p for p in menu_path.split("/") if p] |
| 245 | + |
| 246 | + current_menu = extern_modules_menu |
| 247 | + for part in path_parts: |
| 248 | + current_menu = get_or_create_submenu(current_menu, part) |
| 249 | + |
| 250 | + action = QAction(panel.name, self) |
| 251 | + icon = panel.get_icon() |
| 252 | + if icon: |
| 253 | + action.setIcon(icon) |
| 254 | + if idx < 9: |
| 255 | + action.setShortcut(QKeySequence(f'Ctrl+Shift+[,{(idx + 1)}')) |
| 256 | + action.triggered.connect(lambda state, panel=panel: panel.safe_spawn(self, self._node)) |
| 257 | + current_menu.addAction(action) |
| 258 | + |
207 | 259 | # |
208 | 260 | # Help menu |
209 | 261 | # |
|
0 commit comments