Skip to content

Commit ffcd74d

Browse files
committed
refactor clean up & implement set_context_menu_from_file()
1 parent 93c5127 commit ffcd74d

File tree

13 files changed

+165
-137
lines changed

13 files changed

+165
-137
lines changed

NodeGraphQt/base/graph.py

Lines changed: 112 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -158,21 +158,13 @@ def __init__(self, parent=None, **kwargs):
158158
kwargs.get('viewer') or NodeViewer(undo_stack=self._undo_stack))
159159
self._viewer.set_layout_direction(layout_direction)
160160

161-
self._build_context_menu()
162161
self._register_builtin_nodes()
163162
self._wire_signals()
164163

165164
def __repr__(self):
166165
return '<{}("root") object at {}>'.format(
167166
self.__class__.__name__, hex(id(self)))
168167

169-
def _build_context_menu(self):
170-
"""
171-
build the essential menus commands for the graph context menu.
172-
"""
173-
from NodeGraphQt.base.graph_actions import build_context_menu
174-
build_context_menu(self)
175-
176168
def _register_builtin_nodes(self):
177169
"""
178170
Register the default builtin nodes to the :meth:`NodeGraph.node_factory`
@@ -703,6 +695,117 @@ def get_context_menu(self, menu):
703695
elif menu == 'nodes':
704696
return NodesMenu(self, menus[menu])
705697

698+
@staticmethod
699+
def _build_context_menu_command(menu, data):
700+
"""
701+
Create context menu command from serialized data.
702+
703+
Args:
704+
menu (NodeGraphQt.NodeGraphMenu or NodeGraphQt.NodesMenu):
705+
menu object.
706+
data (dict): serialized menu command data.
707+
"""
708+
import sys
709+
import importlib.util
710+
711+
full_path = os.path.abspath(data['file'])
712+
base_dir, file_name = os.path.split(full_path)
713+
base_name = os.path.basename(base_dir)
714+
file_name, _ = file_name.split('.')
715+
716+
mod_name = '{}.{}'.format(base_name, file_name)
717+
718+
spec = importlib.util.spec_from_file_location(mod_name, full_path)
719+
mod = importlib.util.module_from_spec(spec)
720+
sys.modules[mod_name] = mod
721+
spec.loader.exec_module(mod)
722+
723+
cmd_func = getattr(mod, data['function_name'])
724+
cmd_name = data.get('label') or '<command>'
725+
cmd_shortcut = data.get('shortcut')
726+
menu.add_command(cmd_name, cmd_func, cmd_shortcut)
727+
728+
def _build_context_menu(self, menu, menu_data):
729+
"""
730+
Populate context menu from a dictionary.
731+
732+
Args:
733+
menu (NodeGraphQt.NodeGraphMenu or NodeGraphQt.NodesMenu):
734+
parent context menu.
735+
menu_data (list[dict] or dict): serialized menu data.
736+
"""
737+
if not menu:
738+
raise ValueError('No context menu named: "{}"'.format(menu))
739+
740+
if isinstance(menu_data, dict):
741+
item_type = menu_data.get('type')
742+
if item_type == 'separator':
743+
menu.add_separator()
744+
elif item_type == 'command':
745+
self._build_context_menu_command(menu, menu_data)
746+
elif item_type == 'menu':
747+
sub_menu = menu.add_menu(menu_data['label'])
748+
items = menu_data.get('items', [])
749+
self._build_context_menu(sub_menu, items)
750+
elif isinstance(menu_data, list):
751+
for item_data in menu_data:
752+
self._build_context_menu(menu, item_data)
753+
754+
def set_context_menu(self, menu_name, data):
755+
"""
756+
Populate a context menu from serialized data.
757+
758+
serialized menu data example:
759+
760+
.. highlight:: python
761+
.. code-block:: python
762+
763+
[
764+
{
765+
'type': 'menu',
766+
'label': 'test sub menu',
767+
'items': [
768+
{
769+
'type': 'command',
770+
'label': 'test command',
771+
'file': '../path/to/my/test_module.py',
772+
'function': 'run_test',
773+
'shortcut': 'Ctrl+b'
774+
},
775+
776+
]
777+
},
778+
]
779+
780+
Args:
781+
menu_name (str): name of the parent context menu to populate under.
782+
data (dict): serialized menu data.
783+
"""
784+
context_menu = self.get_context_menu(menu_name)
785+
self._build_context_menu(context_menu, data)
786+
787+
def set_context_menu_from_file(self, file_path, menu=None):
788+
"""
789+
Populate a context menu from a serialized json file.
790+
791+
Menu Types:
792+
- ``"graph"`` context menu from the node graph.
793+
- ``"nodes"`` context menu for the nodes.
794+
795+
Args:
796+
menu (str): name of the parent context menu to populate under.
797+
file_path (str): serialized menu commands json file.
798+
"""
799+
menu = menu or 'graph'
800+
if not os.path.isfile(file_path):
801+
return
802+
803+
with open(file_path) as f:
804+
data = json.load(f)
805+
context_menu = self.get_context_menu(menu)
806+
self._build_context_menu(context_menu, data)
807+
808+
706809
def disable_context_menu(self, disabled=True, name='all'):
707810
"""
708811
Disable/Enable context menus from the node graph.
@@ -1255,7 +1358,7 @@ def get_unique_name(self, name):
12551358
if name not in node_names:
12561359
return name
12571360

1258-
regex = re.compile(r'[\w ]+(?: )*(\d+)')
1361+
regex = re.compile(r'\w+ (\d+)$')
12591362
search = regex.search(name)
12601363
if not search:
12611364
for x in range(1, len(node_names) + 2):

NodeGraphQt/base/menu.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#!/usr/bin/python
2+
import re
23
from distutils.version import LooseVersion
34

45
from Qt import QtGui, QtCore
@@ -129,6 +130,18 @@ def add_command(self, name, func=None, shortcut=None):
129130
action.graph = self._graph
130131
if LooseVersion(QtCore.qVersion()) >= LooseVersion('5.10'):
131132
action.setShortcutVisibleInContextMenu(True)
133+
134+
if isinstance(shortcut, str):
135+
search = re.search(r'(?:\.|)QKeySequence\.(\w+)', shortcut)
136+
if search:
137+
shortcut = getattr(QtGui.QKeySequence, search.group(1))
138+
elif all([i in ['Alt', 'Enter'] for i in shortcut.split('+')]):
139+
shortcut = QtGui.QKeySequence(
140+
QtCore.Qt.ALT + QtCore.Qt.Key_Return
141+
)
142+
elif all([i in ['Return', 'Enter'] for i in shortcut.split('+')]):
143+
shortcut = QtCore.Qt.Key_Return
144+
132145
if shortcut:
133146
action.setShortcut(shortcut)
134147
if func:

NodeGraphQt/custom_widgets/nodes_palette.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ def paint(self, painter, option, index):
102102

103103
font = painter.font()
104104
font_metrics = QtGui.QFontMetrics(font)
105-
font_width = font_metrics.horizontalAdvance(item.text().replace(' ', '_'))
105+
font_width = font_metrics.width(item.text().replace(' ', '_'))
106106
font_height = font_metrics.height()
107107
text_rect = QtCore.QRectF(
108108
sub_rect.center().x() - (font_width / 2),

NodeGraphQt/widgets/dialogs.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def message_dialog(text='', title='Message'):
4848
dlg.setWindowTitle(title)
4949
dlg.setInformativeText(text)
5050
dlg.setStandardButtons(QtWidgets.QMessageBox.Ok)
51-
return dlg.exec()
51+
return dlg.exec_()
5252

5353
@staticmethod
5454
def question_dialog(text='', title='Are you sure?'):
@@ -58,5 +58,5 @@ def question_dialog(text='', title='Are you sure?'):
5858
dlg.setStandardButtons(
5959
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No
6060
)
61-
result = dlg.exec()
61+
result = dlg.exec_()
6262
return bool(result == QtWidgets.QMessageBox.Yes)

NodeGraphQt/widgets/viewer_nav.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def paint(self, painter, option, index):
7575

7676
font = painter.font()
7777
font_metrics = QtGui.QFontMetrics(font)
78-
font_width = font_metrics.horizontalAdvance(
78+
font_width = font_metrics.width(
7979
item.text().replace(' ', '_')
8080
)
8181
font_height = font_metrics.height()
@@ -138,7 +138,7 @@ def add_label_item(self, label, node_id):
138138
item = QtGui.QStandardItem(label)
139139
item.setToolTip(node_id)
140140
metrics = QtGui.QFontMetrics(item.font())
141-
width = metrics.horizontalAdvance(item.text()) + 30
141+
width = metrics.width(item.text()) + 30
142142
item.setSizeHint(QtCore.QSize(width, 20))
143143
self.model().appendRow(item)
144144
self.selectionModel().setCurrentIndex(

basic_example.py renamed to examples/basic_example.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,10 @@
1313
)
1414

1515
# import example nodes from the "example_nodes" package
16-
from examples import group_node
17-
from examples.custom_nodes import (
18-
basic_nodes,
19-
custom_ports_node,
20-
widget_nodes,
21-
)
16+
from nodes import basic_nodes, custom_ports_node, group_node, widget_nodes
2217

2318
if __name__ == '__main__':
19+
2420
# handle SIGINT to make the app terminate on CTRL+C
2521
signal.signal(signal.SIGINT, signal.SIG_DFL)
2622

@@ -30,6 +26,7 @@
3026

3127
# create graph controller.
3228
graph = NodeGraph()
29+
graph.set_context_menu_from_file('/Users/jchan/PycharmProjects/NodeGraphQt/examples/hotkeys/hotkeys.json')
3330

3431
# registered example nodes.
3532
graph.register_nodes([
File renamed without changes.

0 commit comments

Comments
 (0)