diff --git a/NodeGraphQt/__init__.py b/NodeGraphQt/__init__.py index 44495ef1..3914cd13 100644 --- a/NodeGraphQt/__init__.py +++ b/NodeGraphQt/__init__.py @@ -61,7 +61,7 @@ def __init__(self): from .widgets.node_widgets import NodeBaseWidget from .custom_widgets.nodes_tree import NodesTreeWidget from .custom_widgets.nodes_palette import NodesPaletteWidget -from .custom_widgets.properties_bin import PropertiesBinWidget +from .custom_widgets.properties_bin.node_property_widgets import PropertiesBinWidget __version__ = VERSION diff --git a/NodeGraphQt/base/model.py b/NodeGraphQt/base/model.py index 5bd59e04..6b7a53df 100644 --- a/NodeGraphQt/base/model.py +++ b/NodeGraphQt/base/model.py @@ -2,14 +2,7 @@ import json from collections import defaultdict -from NodeGraphQt.constants import ( - LayoutDirectionEnum, - NODE_PROP, - NODE_PROP_QLABEL, - NODE_PROP_QLINEEDIT, - NODE_PROP_QCHECKBOX, - NODE_PROP_COLORPICKER -) +from NodeGraphQt.constants import LayoutDirectionEnum, NodePropWidgetEnum from NodeGraphQt.errors import NodePropertyError @@ -97,21 +90,21 @@ def __init__(self): # temp store the property widget types. # (deleted when node is added to the graph) self._TEMP_property_widget_types = { - 'type_': NODE_PROP_QLABEL, - 'id': NODE_PROP_QLABEL, - 'icon': NODE_PROP, - 'name': NODE_PROP_QLINEEDIT, - 'color': NODE_PROP_COLORPICKER, - 'border_color': NODE_PROP, - 'text_color': NODE_PROP_COLORPICKER, - 'disabled': NODE_PROP_QCHECKBOX, - 'selected': NODE_PROP, - 'width': NODE_PROP, - 'height': NODE_PROP, - 'pos': NODE_PROP, - 'layout_direction': NODE_PROP, - 'inputs': NODE_PROP, - 'outputs': NODE_PROP, + 'type_': NodePropWidgetEnum.QLABEL.value, + 'id': NodePropWidgetEnum.QLABEL.value, + 'icon': NodePropWidgetEnum.HIDDEN.value, + 'name': NodePropWidgetEnum.QLINE_EDIT.value, + 'color': NodePropWidgetEnum.COLOR_PICKER.value, + 'border_color': NodePropWidgetEnum.HIDDEN.value, + 'text_color': NodePropWidgetEnum.COLOR_PICKER.value, + 'disabled': NodePropWidgetEnum.QCHECK_BOX.value, + 'selected': NodePropWidgetEnum.HIDDEN.value, + 'width': NodePropWidgetEnum.HIDDEN.value, + 'height': NodePropWidgetEnum.HIDDEN.value, + 'pos': NodePropWidgetEnum.HIDDEN.value, + 'layout_direction': NodePropWidgetEnum.HIDDEN.value, + 'inputs': NodePropWidgetEnum.HIDDEN.value, + 'outputs': NodePropWidgetEnum.HIDDEN.value, } def __repr__(self): @@ -119,7 +112,7 @@ def __repr__(self): self.__class__.__name__, self.name, self.id) def add_property(self, name, value, items=None, range=None, - widget_type=NODE_PROP, tab=None): + widget_type=None, tab=None): """ add custom property. @@ -127,10 +120,11 @@ def add_property(self, name, value, items=None, range=None, name (str): name of the property. value (object): data. items (list[str]): items used by widget type NODE_PROP_QCOMBO. - range (tuple)): min, max values used by NODE_PROP_SLIDER. + range (tuple): min, max values used by NODE_PROP_SLIDER. widget_type (int): widget type flag. tab (str): widget tab name. """ + widget_type = widget_type or NodePropWidgetEnum.HIDDEN.value tab = tab or 'Properties' if name in self.properties.keys(): @@ -150,10 +144,14 @@ def add_property(self, name, value, items=None, range=None, if range: self._TEMP_property_attrs[name]['range'] = range else: - attrs = {self.type_: {name: { - 'widget_type': widget_type, - 'tab': tab - }}} + attrs = { + self.type_: { + name: { + 'widget_type': widget_type, + 'tab': tab + } + } + } if items: attrs[self.type_][name]['items'] = items if range: diff --git a/NodeGraphQt/base/node.py b/NodeGraphQt/base/node.py index f723d5db..32eba8fb 100644 --- a/NodeGraphQt/base/node.py +++ b/NodeGraphQt/base/node.py @@ -1,7 +1,7 @@ #!/usr/bin/python from NodeGraphQt.base.commands import PropertyChangedCmd from NodeGraphQt.base.model import NodeModel -from NodeGraphQt.constants import NODE_PROP +from NodeGraphQt.constants import NodePropWidgetEnum class _ClassProperty(object): @@ -294,7 +294,7 @@ def set_selected(self, selected=True): self.set_property('selected', selected) def create_property(self, name, value, items=None, range=None, - widget_type=NODE_PROP, tab=None): + widget_type=None, tab=None): """ Creates a custom property to the node. @@ -303,34 +303,21 @@ def create_property(self, name, value, items=None, range=None, :class:`NodeGraphQt.PropertiesBinWidget` Hint: - Here are some constants variables used to define the node - widget type in the ``PropertiesBinWidget``. - - - :attr:`NodeGraphQt.constants.NODE_PROP` - - :attr:`NodeGraphQt.constants.NODE_PROP_QLABEL` - - :attr:`NodeGraphQt.constants.NODE_PROP_QLINEEDIT` - - :attr:`NodeGraphQt.constants.NODE_PROP_QTEXTEDIT` - - :attr:`NodeGraphQt.constants.NODE_PROP_QCOMBO` - - :attr:`NodeGraphQt.constants.NODE_PROP_QCHECKBOX` - - :attr:`NodeGraphQt.constants.NODE_PROP_QSPINBOX` - - :attr:`NodeGraphQt.constants.NODE_PROP_COLORPICKER` - - :attr:`NodeGraphQt.constants.NODE_PROP_FILE` - - :attr:`NodeGraphQt.constants.NODE_PROP_VECTOR2` - - :attr:`NodeGraphQt.constants.NODE_PROP_VECTOR3` - - :attr:`NodeGraphQt.constants.NODE_PROP_VECTOR4` - - :attr:`NodeGraphQt.constants.NODE_PROP_FLOAT` - - :attr:`NodeGraphQt.constants.NODE_PROP_INT` - - :attr:`NodeGraphQt.constants.NODE_PROP_BUTTON` + To see all the available property widget types to display in + the ``PropertiesBinWidget`` widget checkout + :attr:`NodeGraphQt.constants.NodePropWidgetEnum`. Args: name (str): name of the property. value (object): data. - items (list[str]): items used by widget type ``NODE_PROP_QCOMBO`` - range (tuple or list): ``(min, max)`` values used by ``NODE_PROP_SLIDER`` + items (list[str]): items used by widget type attr:`NodePropWidgetEnum.QCOMBO_BOX` + range (tuple or list): ``(min, max)`` values used by :attr:`NodePropWidgetEnum.SLIDER` widget_type (int): widget flag to display in the :class:`NodeGraphQt.PropertiesBinWidget` - tab (str): name of the widget tab to display in the properties bin. + tab (str): name of the widget tab to display in the + :class:`NodeGraphQt.PropertiesBinWidget`. """ + widget_type = widget_type or NodePropWidgetEnum.HIDDEN.value self.model.add_property(name, value, items, range, widget_type, tab) def properties(self): @@ -494,4 +481,3 @@ def set_layout_direction(self, value=0): """ self.model.layout_direction = value self.view.layout_direction = value - diff --git a/NodeGraphQt/constants.py b/NodeGraphQt/constants.py index 47f53954..4744dfbe 100644 --- a/NodeGraphQt/constants.py +++ b/NodeGraphQt/constants.py @@ -203,37 +203,46 @@ class PipeLayoutEnum(Enum): # === PROPERTY BIN WIDGET === -#: Property type will hidden in the properties bin (default). -NODE_PROP = 0 -#: Property type represented with a QLabel widget in the properties bin. -NODE_PROP_QLABEL = 2 -#: Property type represented with a QLineEdit widget in the properties bin. -NODE_PROP_QLINEEDIT = 3 -#: Property type represented with a QTextEdit widget in the properties bin. -NODE_PROP_QTEXTEDIT = 4 -#: Property type represented with a QComboBox widget in the properties bin. -NODE_PROP_QCOMBO = 5 -#: Property type represented with a QCheckBox widget in the properties bin. -NODE_PROP_QCHECKBOX = 6 -#: Property type represented with a QSpinBox widget in the properties bin. -NODE_PROP_QSPINBOX = 7 -#: Property type represented with a ColorPicker widget in the properties bin. -NODE_PROP_COLORPICKER = 8 -#: Property type represented with a Slider widget in the properties bin. -NODE_PROP_SLIDER = 9 -#: Property type represented with a file selector widget in the properties bin. -NODE_PROP_FILE = 10 -#: Property type represented with a file save widget in the properties bin. -NODE_PROP_FILE_SAVE = 11 -#: Property type represented with a vector2 widget in the properties bin. -NODE_PROP_VECTOR2 = 12 -#: Property type represented with vector3 widget in the properties bin. -NODE_PROP_VECTOR3 = 13 -#: Property type represented with vector4 widget in the properties bin. -NODE_PROP_VECTOR4 = 14 -#: Property type represented with float widget in the properties bin. -NODE_PROP_FLOAT = 15 -#: Property type represented with int widget in the properties bin. -NODE_PROP_INT = 16 -#: Property type represented with button widget in the properties bin. -NODE_PROP_BUTTON = 17 +class NodePropWidgetEnum(Enum): + """ + Mapping used for the :class:`NodeGraphQt.PropertiesBinWidget` to display a + node property in the specified widget type. + + :py:mod:`NodeGraphQt.constants.NodePropWidgetEnum` + """ + #: Node property will be hidden in the ``PropertiesBinWidget`` (default). + HIDDEN = 0 + #: Node property represented with a ``QLabel`` widget. + QLABEL = 2 + #: Node property represented with a ``QLineEdit`` widget. + QLINE_EDIT = 3 + #: Node property represented with a ``QTextEdit`` widget. + QTEXT_EDIT = 4 + #: Node property represented with a ``QComboBox`` widget. + QCOMBO_BOX = 5 + #: Node property represented with a ``QCheckBox`` widget. + QCHECK_BOX = 6 + #: Node property represented with a ``QSpinBox`` widget. + QSPIN_BOX = 7 + #: Node property represented with a ``QDoubleSpinBox`` widget. + QDOUBLESPIN_BOX = 8 + #: Node property represented with a ColorPicker widget. + COLOR_PICKER = 9 + #: Node property represented with a Slider widget. + SLIDER = 10 + #: Node property represented with a file selector widget. + FILE_OPEN = 11 + #: Node property represented with a file save widget. + FILE_SAVE = 12 + #: Node property represented with a vector2 widget. + VECTOR2 = 13 + #: Node property represented with vector3 widget. + VECTOR3 = 14 + #: Node property represented with vector4 widget. + VECTOR4 = 15 + #: Node property represented with float line edit widget. + FLOAT = 16 + #: Node property represented with int line edit widget. + INT = 17 + #: Node property represented with button widget. + BUTTON = 18 diff --git a/NodeGraphQt/custom_widgets/properties.py b/NodeGraphQt/custom_widgets/properties.py deleted file mode 100644 index 0eb2c66a..00000000 --- a/NodeGraphQt/custom_widgets/properties.py +++ /dev/null @@ -1,1087 +0,0 @@ -#!/usr/bin/python -from collections import defaultdict - -from Qt import QtWidgets, QtCore, QtGui - -from NodeGraphQt.constants import (NODE_PROP_QLABEL, - NODE_PROP_QLINEEDIT, - NODE_PROP_QTEXTEDIT, - NODE_PROP_QCOMBO, - NODE_PROP_QCHECKBOX, - NODE_PROP_QSPINBOX, - NODE_PROP_COLORPICKER, - NODE_PROP_SLIDER, - NODE_PROP_FILE, - NODE_PROP_FILE_SAVE, - NODE_PROP_VECTOR2, - NODE_PROP_VECTOR3, - NODE_PROP_VECTOR4, - NODE_PROP_FLOAT, - NODE_PROP_INT, - NODE_PROP_BUTTON) -from NodeGraphQt.widgets.dialogs import FileDialog - - -class BaseProperty(QtWidgets.QWidget): - """ - Base widget class for a node property. - """ - - value_changed = QtCore.Signal(str, object) - - def set_value(self, value): - raise NotImplementedError - - def get_value(self): - raise NotImplementedError - - -class PropColorPicker(BaseProperty): - """ - Color picker widget for a node property. - """ - - def __init__(self, parent=None): - super(PropColorPicker, self).__init__(parent) - self._color = (0, 0, 0) - self._button = QtWidgets.QPushButton() - self._vector = PropVector3() - self._vector.set_value([0, 0, 0]) - self._update_color() - - self._button.clicked.connect(self._on_select_color) - self._vector.value_changed.connect(self._on_vector_changed) - layout = QtWidgets.QHBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.addWidget(self._button, 0, QtCore.Qt.AlignLeft) - layout.addWidget(self._vector, 1, QtCore.Qt.AlignLeft) - - def _on_vector_changed(self, o, value): - self._color = tuple(value) - self._update_color() - self.value_changed.emit(self.toolTip(), value) - - def _on_select_color(self): - color = QtWidgets.QColorDialog.getColor( - QtGui.QColor.fromRgbF(*self.get_value()) - ) - if color.isValid(): - self.set_value(color.getRgb()) - - def _update_vector(self): - self._vector.set_value(list(self._color)) - - def _update_color(self): - c = [int(max(min(i, 255), 0)) for i in self._color] - hex_color = '#{0:02x}{1:02x}{2:02x}'.format(*c) - self._button.setStyleSheet( - ''' - QPushButton {{background-color: rgba({0}, {1}, {2}, 255);}} - QPushButton::hover {{background-color: rgba({0}, {1}, {2}, 200);}} - '''.format(*c) - ) - self._button.setToolTip( - 'rgb: {}\nhex: {}'.format(self._color[:3], hex_color) - ) - - def get_value(self): - return self._color[:3] - - def set_value(self, value): - if value != self.get_value(): - self._color = value - self._update_color() - self._update_vector() - self.value_changed.emit(self.toolTip(), value) - - -class PropSlider(BaseProperty): - """ - Slider widget for a node property. - """ - - def __init__(self, parent=None): - super(PropSlider, self).__init__(parent) - self._block = False - self._slider = QtWidgets.QSlider() - self._spnbox = QtWidgets.QSpinBox() - self._init() - - def _init(self): - self._slider.setOrientation(QtCore.Qt.Horizontal) - self._slider.setTickPosition(QtWidgets.QSlider.TicksBelow) - self._slider.setSizePolicy(QtWidgets.QSizePolicy.Expanding, - QtWidgets.QSizePolicy.Preferred) - self._spnbox.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons) - layout = QtWidgets.QHBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.addWidget(self._spnbox) - layout.addWidget(self._slider) - self._spnbox.valueChanged.connect(self._on_spnbox_changed) - self._slider.valueChanged.connect(self._on_slider_changed) - # store the original press event. - self._slider_mouse_press_event = self._slider.mousePressEvent - self._slider.mousePressEvent = self._on_slider_mouse_press - self._slider.mouseReleaseEvent = self._on_slider_mouse_release - - def _on_slider_mouse_press(self, event): - self._block = True - self._slider_mouse_press_event(event) - - def _on_slider_mouse_release(self, event): - self.value_changed.emit(self.toolTip(), self.get_value()) - self._block = False - - def _on_slider_changed(self, value): - self._spnbox.setValue(value) - - def _on_spnbox_changed(self, value): - if value != self._slider.value(): - self._slider.setValue(value) - if not self._block: - self.value_changed.emit(self.toolTip(), self.get_value()) - - def get_value(self): - return self._spnbox.value() - - def set_value(self, value): - if value != self.get_value(): - self._block = True - self._spnbox.setValue(value) - self.value_changed.emit(self.toolTip(), value) - self._block = False - - def set_min(self, value=0): - self._spnbox.setMinimum(value) - self._slider.setMinimum(value) - - def set_max(self, value=0): - self._spnbox.setMaximum(value) - self._slider.setMaximum(value) - - -class PropLabel(QtWidgets.QLabel): - """ - Label widget for a node property. - """ - - value_changed = QtCore.Signal(str, object) - - def get_value(self): - return self.text() - - def set_value(self, value): - if value != self.get_value(): - self.setText(str(value)) - self.value_changed.emit(self.toolTip(), value) - - -class PropLineEdit(QtWidgets.QLineEdit): - """ - LineEdit widget for a node property. - """ - - value_changed = QtCore.Signal(str, object) - - def __init__(self, parent=None): - super(PropLineEdit, self).__init__(parent) - self.editingFinished.connect(self._on_editing_finished) - - def _on_editing_finished(self): - self.value_changed.emit(self.toolTip(), self.text()) - - def get_value(self): - return self.text() - - def set_value(self, value): - _value = str(value) - if _value != self.get_value(): - self.setText(_value) - self.value_changed.emit(self.toolTip(), _value) - - -class PropTextEdit(QtWidgets.QTextEdit): - """ - TextEdit widget for a node property. - """ - - value_changed = QtCore.Signal(str, object) - - def __init__(self, parent=None): - super(PropTextEdit, self).__init__(parent) - self.__prev_text = '' - - def focusInEvent(self, event): - super(PropTextEdit, self).focusInEvent(event) - self.__prev_text = self.toPlainText() - - def focusOutEvent(self, event): - super(PropTextEdit, self).focusOutEvent(event) - if self.__prev_text != self.toPlainText(): - self.value_changed.emit(self.toolTip(), self.toPlainText()) - self.__prev_text = '' - - def get_value(self): - return self.toPlainText() - - def set_value(self, value): - _value = str(value) - if _value != self.get_value(): - self.setPlainText(_value) - self.value_changed.emit(self.toolTip(), _value) - - -class PropComboBox(QtWidgets.QComboBox): - """ - ComboBox widget for a node property. - """ - - value_changed = QtCore.Signal(str, object) - - def __init__(self, parent=None): - super(PropComboBox, self).__init__(parent) - self.currentIndexChanged.connect(self._on_index_changed) - - def _on_index_changed(self): - self.value_changed.emit(self.toolTip(), self.get_value()) - - def items(self): - """ - returns items from the combobox. - - Returns: - list[str]: list of strings. - """ - return [self.itemText(i) for i in range(self.count())] - - def set_items(self, items): - """ - Set items on the combobox. - - Args: - items (list[str]): list of strings. - """ - self.clear() - self.addItems(items) - - def get_value(self): - return self.currentText() - - def set_value(self, value): - if value != self.get_value(): - idx = self.findText(value, QtCore.Qt.MatchExactly) - self.setCurrentIndex(idx) - if idx >= 0: - self.value_changed.emit(self.toolTip(), value) - - -class PropCheckBox(QtWidgets.QCheckBox): - """ - CheckBox widget for a node property. - """ - - value_changed = QtCore.Signal(str, object) - - def __init__(self, parent=None): - super(PropCheckBox, self).__init__(parent) - self.clicked.connect(self._on_clicked) - - def _on_clicked(self): - self.value_changed.emit(self.toolTip(), self.get_value()) - - def get_value(self): - return self.isChecked() - - def set_value(self, value): - if value != self.get_value(): - self.setChecked(value) - self.value_changed.emit(self.toolTip(), value) - - -class PropSpinBox(QtWidgets.QSpinBox): - """ - SpinBox widget for a node property. - """ - - value_changed = QtCore.Signal(str, object) - - def __init__(self, parent=None): - super(PropSpinBox, self).__init__(parent) - self.setButtonSymbols(self.NoButtons) - self.valueChanged.connect(self._on_value_change) - - def _on_value_change(self, value): - self.value_changed.emit(self.toolTip(), value) - - def get_value(self): - return self.value() - - def set_value(self, value): - if value != self.get_value(): - self.setValue(value) - - -class PropFilePath(BaseProperty): - - def __init__(self, parent=None): - super(PropFilePath, self).__init__(parent) - self._ledit = QtWidgets.QLineEdit() - self._ledit.setAlignment(QtCore.Qt.AlignLeft) - self._ledit.editingFinished.connect(self._on_value_change) - self._ledit.clearFocus() - - icon = self.style().standardIcon(QtWidgets.QStyle.StandardPixmap(21)) - _button = QtWidgets.QPushButton() - _button.setIcon(icon) - _button.clicked.connect(self._on_select_file) - - hbox = QtWidgets.QHBoxLayout(self) - hbox.setContentsMargins(0, 0, 0, 0) - hbox.addWidget(self._ledit) - hbox.addWidget(_button) - - self._ext = '*' - self._file_directory = None - - def _on_select_file(self): - file_path = FileDialog.getOpenFileName(self, - file_dir=self._file_directory, - ext_filter=self._ext) - file = file_path[0] or None - if file: - self.set_value(file) - - def _on_value_change(self, value=None): - if value is None: - value = self._ledit.text() - self.value_changed.emit(self.toolTip(), value) - - def set_file_ext(self, ext=None): - self._ext = ext or '*' - - def set_file_directory(self, directory): - self._file_directory = directory - - def get_value(self): - return self._ledit.text() - - def set_value(self, value): - _value = str(value) - if _value != self.get_value(): - self._ledit.setText(_value) - self._on_value_change(_value) - - -class PropFileSavePath(PropFilePath): - - def _on_select_file(self): - file_path = FileDialog.getSaveFileName(self, - file_dir=self._file_directory, - ext_filter=self._ext) - file = file_path[0] or None - if file: - self.set_value(file) - - -class _ValueMenu(QtWidgets.QMenu): - - mouseMove = QtCore.Signal(object) - mouseRelease = QtCore.Signal(object) - stepChange = QtCore.Signal() - - def __init__(self, parent=None): - super(_ValueMenu, self).__init__(parent) - self.step = 1 - self.last_action = None - self.steps = [] - - def set_steps(self, steps): - self.clear() - self.steps = steps - for step in steps: - self._add_action(step) - - def _add_action(self, step): - action = QtWidgets.QAction(str(step), self) - action.step = step - self.addAction(action) - - def mouseMoveEvent(self, event): - self.mouseMove.emit(event) - super(_ValueMenu, self).mouseMoveEvent(event) - - action = self.actionAt(event.pos()) - if action: - if action is not self.last_action: - self.stepChange.emit() - self.last_action = action - self.step = action.step - elif self.last_action: - self.setActiveAction(self.last_action) - - def mousePressEvent(self, event): - return - - def mouseReleaseEvent(self, event): - self.mouseRelease.emit(event) - super(_ValueMenu, self).mouseReleaseEvent(event) - - def set_data_type(self, dt): - if dt is int: - new_steps = [] - for step in self.steps: - if '.' not in str(step): - new_steps.append(step) - self.set_steps(new_steps) - elif dt is float: - self.set_steps(self.steps) - - -class _ValueEdit(QtWidgets.QLineEdit): - - valueChanged = QtCore.Signal(object) - - def __init__(self, parent=None): - super(_ValueEdit, self).__init__(parent) - self.mid_state = False - self._data_type = float - self.setText('0') - - self.pre_x = None - self.pre_val = None - self._step = 1 - self._speed = 0.1 - - self.editingFinished.connect(self._on_text_changed) - - self.menu = _ValueMenu() - self.menu.mouseMove.connect(self.mouseMoveEvent) - self.menu.mouseRelease.connect(self.mouseReleaseEvent) - self.menu.stepChange.connect(self._reset) - steps = [0.001, 0.01, 0.1, 1, 10, 100, 1000] - self.menu.set_steps(steps) - - self.set_data_type(float) - - def _on_text_changed(self): - self.valueChanged.emit(self.value()) - - def _reset(self): - self.pre_x = None - - def mouseMoveEvent(self, event): - if self.mid_state: - if self.pre_x is None: - self.pre_x = event.x() - self.pre_val = self.value() - else: - self.set_step(self.menu.step) - delta = event.x() - self.pre_x - value = self.pre_val + int(delta * self._speed) * self._step - self.setValue(value) - self._on_text_changed() - - super(_ValueEdit, self).mouseMoveEvent(event) - - def mousePressEvent(self, event): - if event.button() == QtCore.Qt.MiddleButton: - self.mid_state = True - self._reset() - self.menu.exec_(QtGui.QCursor.pos()) - super(_ValueEdit, self).mousePressEvent(event) - - def mouseReleaseEvent(self, event): - self.menu.close() - self.mid_state = False - super(_ValueEdit, self).mouseReleaseEvent(event) - - def set_step(self, step): - self._step = step - - def set_data_type(self, dt): - if dt is int: - self.setValidator(QtGui.QIntValidator()) - elif dt is float: - self.setValidator(QtGui.QDoubleValidator()) - self._data_type = dt - self.menu.set_data_type(dt) - - def _convert_text(self, text): - # int("1.0") will return error - # so we use int(float("1.0")) - try: - value = float(text) - except: - value = 0.0 - if self._data_type is int: - value = int(value) - return value - - def value(self): - if self.text().startswith('.'): - text = '0' + self.text() - self.setText(text) - return self._convert_text(self.text()) - - def setValue(self, value): - if value != self.value(): - self.setText(str(self._convert_text(value))) - - -class _Slider(QtWidgets.QSlider): - - def __init__(self, parent=None): - super(_Slider, self).__init__(parent) - self.setOrientation(QtCore.Qt.Horizontal) - self.setTickPosition(QtWidgets.QSlider.TicksBelow) - self.setSizePolicy(QtWidgets.QSizePolicy.Expanding, - QtWidgets.QSizePolicy.Preferred) - - def _update_value(self, x): - value = (self.maximum() - self.minimum()) * x / self.width() + self.minimum() - self.setValue(value) - - def mousePressEvent(self, event): - if event.button() == QtCore.Qt.LeftButton: - self._update_value(event.pos().x()) - super(_Slider, self).mousePressEvent(event) - - -class _ValueSliderEdit(QtWidgets.QWidget): - - valueChanged = QtCore.Signal(object) - - def __init__(self, parent=None): - super(_ValueSliderEdit, self).__init__(parent) - self._edit = _ValueEdit() - self._edit.valueChanged.connect(self._on_edit_changed) - self._edit.setMaximumWidth(70) - self._slider = _Slider() - self._slider.valueChanged.connect(self._on_slider_changed) - - hbox = QtWidgets.QHBoxLayout() - hbox.setContentsMargins(0, 0, 0, 0) - hbox.addWidget(self._edit) - hbox.addWidget(self._slider) - self.setLayout(hbox) - - self._mul = 1000.0 - self.set_min(0) - self.set_max(10) - self.set_data_type(float) - self._lock = False - - def _on_edit_changed(self, value): - self._set_slider_value(value) - self.valueChanged.emit(self._edit.value()) - - def _on_slider_changed(self, value): - if self._lock: - self._lock = False - return - value = value / float(self._mul) - self._edit.setValue(value) - self._on_edit_changed(value) - - def _set_slider_value(self, value): - value = int(value * self._mul) - - if value == self._slider.value(): - return - self._lock = True - _min = self._slider.minimum() - _max = self._slider.maximum() - if _min <= value <= _max: - self._slider.setValue(value) - elif value < _min and self._slider.value() != _min: - self._slider.setValue(_min) - elif value > _max and self._slider.value() != _max: - self._slider.setValue(_max) - - def set_min(self, value=0): - self._slider.setMinimum(int(value * self._mul)) - - def set_max(self, value=10): - self._slider.setMaximum(int(value * self._mul)) - - def set_data_type(self, dt): - _min = int(self._slider.minimum() / self._mul) - _max = int(self._slider.maximum() / self._mul) - if dt is int: - self._mul = 1.0 - elif dt is float: - self._mul = 1000.0 - - self.set_min(_min) - self.set_max(_max) - self._edit.set_data_type(dt) - - def value(self): - return self._edit.value() - - def setValue(self, value): - self._edit.setValue(value) - self._on_edit_changed(value) - - -class _DoubleSpinBox(QtWidgets.QDoubleSpinBox): - - def __init__(self, parent=None): - super(_DoubleSpinBox, self).__init__(parent) - self.setButtonSymbols(self.NoButtons) - self.setRange(-9999999999999999.0, 9999999999999999.0) - self.setDecimals(16) - self.setValue(0) - self.setStyleSheet('QDoubleSpinBox{ border:1px solid }') - - def textFromValue(self, value): - return str(value) - - -class PropVector(BaseProperty): - - def __init__(self, parent=None, dim=3): - super(PropVector, self).__init__(parent) - self._value = [] - self._items = [] - self._can_emit = True - - hbox = QtWidgets.QHBoxLayout(self) - hbox.setSpacing(2) - hbox.setContentsMargins(0, 0, 0, 0) - for i in range(dim): - self._add_item(i, hbox) - - def set_data_type(self, dt): - [item.set_data_type(dt) for item in self._items] - - def _add_item(self, index, hbox): - _ledit = _ValueEdit() - _ledit.index = index - _ledit.valueChanged.connect( - lambda: self._on_value_change(_ledit.value(), _ledit.index) - ) - - hbox.addWidget(_ledit) - self._value.append(0.0) - self._items.append(_ledit) - - def _on_value_change(self, value=None, index=None): - if self._can_emit: - if index is not None: - self._value[index] = value - self.value_changed.emit(self.toolTip(), self._value) - self.value_changed.emit(self.toolTip(), self._value) - - def _update_items(self): - for index, value in enumerate(self._value): - if index < len(self._items) and self._items[index].value() != value: - self._items[index].setValue(value) - - def get_value(self): - return self._value - - def set_value(self, value): - if value != self.get_value(): - # cast a new list. - self._value = list(value) - self._can_emit = False - self._update_items() - self._can_emit = True - self._on_value_change() - - -class PropVector2(PropVector): - - def __init__(self, parent=None): - super(PropVector2, self).__init__(parent, 2) - - -class PropVector3(PropVector): - - def __init__(self, parent=None): - super(PropVector3, self).__init__(parent, 3) - - -class PropVector4(PropVector): - - def __init__(self, parent=None): - super(PropVector4, self).__init__(parent, 4) - - -class PropFloat(_ValueSliderEdit): - - value_changed = QtCore.Signal(str, object) - - def __init__(self, parent=None): - super(PropFloat, self).__init__(parent) - self.valueChanged.connect(self._on_value_changed) - - def _on_value_changed(self, value): - self.value_changed.emit(self.toolTip(), value) - - def get_value(self): - return self.value() - - def set_value(self, value): - if value != self.get_value(): - self.setValue(value) - self.value_changed.emit(self.toolTip(), value) - - -class PropInt(PropFloat): - - def __init__(self, parent=None): - super(PropInt, self).__init__(parent) - self.set_data_type(int) - - -class PropButton(QtWidgets.QPushButton): - - value_changed = QtCore.Signal(str, object) - - def __init__(self, parent=None): - super(PropButton, self).__init__(parent) - - def set_value(self, value, node=None): - # value: list of functions - if type(value) is not list: - return - for func in value: - self.clicked.connect(lambda: func(node)) - - def get_value(self): - return None - - -WIDGET_MAP = { - NODE_PROP_QLABEL: PropLabel, - NODE_PROP_QLINEEDIT: PropLineEdit, - NODE_PROP_QTEXTEDIT: PropTextEdit, - NODE_PROP_QCOMBO: PropComboBox, - NODE_PROP_QCHECKBOX: PropCheckBox, - NODE_PROP_QSPINBOX: PropSpinBox, - NODE_PROP_COLORPICKER: PropColorPicker, - NODE_PROP_SLIDER: PropSlider, - - NODE_PROP_FILE: PropFilePath, - NODE_PROP_FILE_SAVE: PropFileSavePath, - NODE_PROP_VECTOR2: PropVector2, - NODE_PROP_VECTOR3: PropVector3, - NODE_PROP_VECTOR4: PropVector4, - NODE_PROP_FLOAT: PropFloat, - NODE_PROP_INT: PropInt, - NODE_PROP_BUTTON: PropButton -} - - -# main property widgets. -# ============================================================================== - - -class PropListWidget(QtWidgets.QWidget): - """ - Node properties list displayed under a tab in the NodePropWidget widget. - """ - - def __init__(self, parent=None): - super(PropListWidget, self).__init__(parent) - self.__layout = QtWidgets.QGridLayout() - self.__layout.setColumnStretch(1, 1) - self.__layout.setSpacing(6) - - layout = QtWidgets.QVBoxLayout(self) - layout.setAlignment(QtCore.Qt.AlignTop) - layout.addLayout(self.__layout) - - def __repr__(self): - return '<{} object at {}>'.format( - self.__class__.__name__, hex(id(self)) - ) - - def add_widget(self, name, widget, value, label=None): - """ - Add a property widget to the window. - - Args: - name (str): property name to be displayed. - widget (BaseProperty): property widget. - value (object): property value. - label (str): custom label to display. - """ - widget.setToolTip(name) - widget.set_value(value) - if label is None: - label = name - row = self.__layout.rowCount() - if row > 0: - row += 1 - - label_flags = QtCore.Qt.AlignCenter | QtCore.Qt.AlignRight - if widget.__class__.__name__ == 'PropTextEdit': - label_flags = label_flags | QtCore.Qt.AlignTop - - self.__layout.addWidget(QtWidgets.QLabel(label), row, 0, label_flags) - self.__layout.addWidget(widget, row, 1) - - def get_widget(self, name): - """ - Returns the property widget from the name. - - Args: - name (str): property name. - - Returns: - QtWidgets.QWidget: property widget. - """ - for row in range(self.__layout.rowCount()): - item = self.__layout.itemAtPosition(row, 1) - if item and name == item.widget().toolTip(): - return item.widget() - - -class NodePropWidget(QtWidgets.QWidget): - """ - Node properties widget for display a Node object. - - Args: - parent (QtWidgets.QWidget): parent object. - node (NodeGraphQt.BaseNode): node. - """ - - #: signal (node_id, prop_name, prop_value) - property_changed = QtCore.Signal(str, str, object) - property_closed = QtCore.Signal(str) - - def __init__(self, parent=None, node=None): - super(NodePropWidget, self).__init__(parent) - self.__node_id = node.id - self.__tab_windows = {} - self.__tab = QtWidgets.QTabWidget() - - close_btn = QtWidgets.QPushButton() - close_btn.setIcon(QtGui.QIcon( - self.style().standardPixmap(QtWidgets.QStyle.SP_DialogCancelButton) - )) - close_btn.setMaximumWidth(40) - close_btn.setToolTip('close property') - close_btn.clicked.connect(self._on_close) - - self.name_wgt = PropLineEdit() - self.name_wgt.setToolTip('name') - self.name_wgt.set_value(node.name()) - self.name_wgt.value_changed.connect(self._on_property_changed) - - self.type_wgt = QtWidgets.QLabel(node.type_) - self.type_wgt.setAlignment(QtCore.Qt.AlignRight) - self.type_wgt.setToolTip('type_') - font = self.type_wgt.font() - font.setPointSize(10) - self.type_wgt.setFont(font) - - name_layout = QtWidgets.QHBoxLayout() - name_layout.setContentsMargins(0, 0, 0, 0) - name_layout.addWidget(QtWidgets.QLabel('name')) - name_layout.addWidget(self.name_wgt) - name_layout.addWidget(close_btn) - layout = QtWidgets.QVBoxLayout(self) - layout.setSpacing(4) - layout.addLayout(name_layout) - layout.addWidget(self.__tab) - layout.addWidget(self.type_wgt) - self._read_node(node) - - def __repr__(self): - return '<{} object at {}>'.format( - self.__class__.__name__, hex(id(self)) - ) - - def _on_close(self): - """ - called by the close button. - """ - self.property_closed.emit(self.__node_id) - - def _on_property_changed(self, name, value): - """ - slot function called when a property widget has changed. - - Args: - name (str): property name. - value (object): new value. - """ - self.property_changed.emit(self.__node_id, name, value) - - def _read_node(self, node): - """ - Populate widget from a node. - - Args: - node (NodeGraphQt.BaseNode): node class. - """ - model = node.model - graph_model = node.graph.model - - common_props = graph_model.get_node_common_properties(node.type_) - - # sort tabs and properties. - tab_mapping = defaultdict(list) - for prop_name, prop_val in model.custom_properties.items(): - tab_name = model.get_tab_name(prop_name) - tab_mapping[tab_name].append((prop_name, prop_val)) - - # add tabs. - for tab in sorted(tab_mapping.keys()): - if tab != 'Node': - self.add_tab(tab) - - # populate tab properties. - for tab in sorted(tab_mapping.keys()): - prop_window = self.__tab_windows[tab] - for prop_name, value in tab_mapping[tab]: - wid_type = model.get_widget_type(prop_name) - if wid_type == 0: - continue - - _WidgetClass = WIDGET_MAP.get(wid_type) - widget = _WidgetClass() - if prop_name in common_props.keys(): - if 'items' in common_props[prop_name].keys(): - widget.set_items(common_props[prop_name]['items']) - if 'range' in common_props[prop_name].keys(): - prop_range = common_props[prop_name]['range'] - widget.set_min(prop_range[0]) - widget.set_max(prop_range[1]) - - prop_window.add_widget(prop_name, widget, value, - prop_name.replace('_', ' ')) - widget.value_changed.connect(self._on_property_changed) - - # add "Node" tab properties. - self.add_tab('Node') - default_props = ['color', 'text_color', 'disabled', 'id'] - prop_window = self.__tab_windows['Node'] - for prop_name in default_props: - wid_type = model.get_widget_type(prop_name) - _WidgetClass = WIDGET_MAP.get(wid_type) - - widget = _WidgetClass() - prop_window.add_widget(prop_name, - widget, - model.get_property(prop_name), - prop_name.replace('_', ' ')) - - widget.value_changed.connect(self._on_property_changed) - - self.type_wgt.setText(model.get_property('type_')) - - def node_id(self): - """ - Returns the node id linked to the widget. - - Returns: - str: node id - """ - return self.__node_id - - def add_widget(self, name, widget, tab='Properties'): - """ - add new node property widget. - - Args: - name (str): property name. - widget (BaseProperty): property widget. - tab (str): tab name. - """ - if tab not in self._widgets.keys(): - tab = 'Properties' - window = self.__tab_windows[tab] - window.add_widget(name, widget) - widget.value_changed.connect(self._on_property_changed) - - def add_tab(self, name): - """ - add a new tab. - - Args: - name (str): tab name. - - Returns: - PropListWidget: tab child widget. - """ - if name in self.__tab_windows.keys(): - raise AssertionError('Tab name {} already taken!'.format(name)) - self.__tab_windows[name] = PropListWidget(self) - self.__tab.addTab(self.__tab_windows[name], name) - return self.__tab_windows[name] - - def get_widget(self, name): - """ - get property widget. - - Args: - name (str): property name. - - Returns: - QtWidgets.QWidget: property widget. - """ - if name == 'name': - return self.name_wgt - for tab_name, prop_win in self.__tab_windows.items(): - widget = prop_win.get_widget(name) - if widget: - return widget - - -if __name__ == '__main__': - import sys - from NodeGraphQt import BaseNode, NodeGraph - - - class TestNode(BaseNode): - NODE_NAME = 'test node' - - def __init__(self): - super(TestNode, self).__init__() - self.create_property('label_test', 'Test Text', - widget_type=NODE_PROP_QLABEL) - self.create_property('line_edit', 'Test Text', - widget_type=NODE_PROP_QLINEEDIT) - self.create_property('color_picker', (0, 0, 255), - widget_type=NODE_PROP_COLORPICKER) - self.create_property('integer', 10, - widget_type=NODE_PROP_QSPINBOX) - self.create_property('list', 'item1', - items=['item1', 'item2', 'item3'], - widget_type=NODE_PROP_QCOMBO) - self.create_property('range', 50, - range=(45, 55), - widget_type=NODE_PROP_SLIDER) - self.create_property('text_edit', 'Test Text', - widget_type=NODE_PROP_QTEXTEDIT, - tab='text') - - - def prop_changed(node_id, prop_name, prop_value): - print('-' * 100) - print(node_id, prop_name, prop_value) - - - def prop_close(node_id): - print('=' * 100) - print(node_id) - - - app = QtWidgets.QApplication(sys.argv) - - graph = NodeGraph() - graph.register_node(TestNode) - - test_node = graph.create_node('nodeGraphQt.nodes.TestNode') - - node_prop = NodePropWidget(node=test_node) - node_prop.property_changed.connect(prop_changed) - node_prop.property_closed.connect(prop_close) - node_prop.show() - - app.exec_() diff --git a/NodeGraphQt/custom_widgets/properties_bin.py b/NodeGraphQt/custom_widgets/properties_bin.py deleted file mode 100644 index 2d1ba8b4..00000000 --- a/NodeGraphQt/custom_widgets/properties_bin.py +++ /dev/null @@ -1,327 +0,0 @@ -#!/usr/bin/python -from Qt import QtWidgets, QtCore, QtGui, QtCompat - -from NodeGraphQt.custom_widgets.properties import NodePropWidget - - -class PropertiesDelegate(QtWidgets.QStyledItemDelegate): - - def paint(self, painter, option, index): - """ - Args: - painter (QtGui.QPainter): - option (QtGui.QStyleOptionViewItem): - index (QtCore.QModelIndex): - """ - painter.save() - painter.setRenderHint(QtGui.QPainter.Antialiasing, False) - painter.setPen(QtCore.Qt.NoPen) - - # draw background. - bg_clr = option.palette.midlight().color() - painter.setBrush(QtGui.QBrush(bg_clr)) - painter.drawRect(option.rect) - - # draw border. - border_width = 1 - if option.state & QtWidgets.QStyle.State_Selected: - bdr_clr = option.palette.highlight().color() - painter.setPen(QtGui.QPen(bdr_clr, 1.5)) - else: - bdr_clr = option.palette.alternateBase().color() - painter.setPen(QtGui.QPen(bdr_clr, 1)) - - painter.setBrush(QtCore.Qt.NoBrush) - painter.drawRect(QtCore.QRect( - option.rect.x() + border_width, - option.rect.y() + border_width, - option.rect.width() - (border_width * 2), - option.rect.height() - (border_width * 2)) - ) - painter.restore() - - -class PropertiesList(QtWidgets.QTableWidget): - - def __init__(self, parent=None): - super(PropertiesList, self).__init__(parent) - self.setItemDelegate(PropertiesDelegate()) - self.setColumnCount(1) - self.setShowGrid(False) - self.verticalHeader().hide() - self.horizontalHeader().hide() - - QtCompat.QHeaderView.setSectionResizeMode( - self.verticalHeader(), QtWidgets.QHeaderView.ResizeToContents) - QtCompat.QHeaderView.setSectionResizeMode( - self.horizontalHeader(), 0, QtWidgets.QHeaderView.Stretch) - self.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) - - def wheelEvent(self, event): - delta = event.delta() * 0.2 - self.verticalScrollBar().setValue( - self.verticalScrollBar().value() - delta - ) - - -class PropertiesBinWidget(QtWidgets.QWidget): - """ - The :class:`NodeGraphQt.PropertiesBinWidget` is a list widget for displaying - and editing a nodes properties. - - .. image:: _images/prop_bin.png - :width: 950px - - .. code-block:: python - :linenos: - - from NodeGraphQt import NodeGraph, PropertiesBinWidget - - # create node graph. - graph = NodeGraph() - - # create properties bin widget. - properties_bin = PropertiesBinWidget(parent=None, node_graph=graph) - properties_bin.show() - - Args: - parent (QtWidgets.QWidget): parent of the new widget. - node_graph (NodeGraphQt.NodeGraph): node graph. - """ - - #: Signal emitted (node_id, prop_name, prop_value) - property_changed = QtCore.Signal(str, str, object) - - def __init__(self, parent=None, node_graph=None): - super(PropertiesBinWidget, self).__init__(parent) - self.setWindowTitle('Properties Bin') - self._prop_list = PropertiesList() - self._limit = QtWidgets.QSpinBox() - self._limit.setToolTip('Set display nodes limit.') - self._limit.setMaximum(10) - self._limit.setMinimum(0) - self._limit.setValue(2) - self._limit.valueChanged.connect(self.__on_limit_changed) - self.resize(450, 400) - - self._block_signal = False - - self._lock = False - self.btn_lock = QtWidgets.QPushButton('Lock') - self.btn_lock.setToolTip( - 'Lock the properties bin prevent nodes from being loaded.') - self.btn_lock.clicked.connect(self.lock_bin) - - btn_clr = QtWidgets.QPushButton('Clear') - btn_clr.setToolTip('Clear the properties bin.') - btn_clr.clicked.connect(self.clear_bin) - - top_layout = QtWidgets.QHBoxLayout() - top_layout.setSpacing(2) - top_layout.addWidget(self._limit) - top_layout.addStretch(1) - top_layout.addWidget(self.btn_lock) - top_layout.addWidget(btn_clr) - - layout = QtWidgets.QVBoxLayout(self) - layout.addLayout(top_layout) - layout.addWidget(self._prop_list, 1) - - # wire up node graph. - node_graph.add_properties_bin(self) - node_graph.node_double_clicked.connect(self.add_node) - node_graph.nodes_deleted.connect(self.__on_nodes_deleted) - node_graph.property_changed.connect(self.__on_graph_property_changed) - - def __repr__(self): - return '<{} object at {}>'.format(self.__class__.__name__, hex(id(self))) - - def __on_prop_close(self, node_id): - items = self._prop_list.findItems(node_id, QtCore.Qt.MatchExactly) - [self._prop_list.removeRow(i.row()) for i in items] - - def __on_limit_changed(self, value): - rows = self._prop_list.rowCount() - if rows > value: - self._prop_list.removeRow(rows - 1) - - def __on_nodes_deleted(self, nodes): - """ - Slot function when a node has been deleted. - - Args: - nodes (list[str]): list of node ids. - """ - [self.__on_prop_close(n) for n in nodes] - - def __on_graph_property_changed(self, node, prop_name, prop_value): - """ - Slot function that updates the property bin from the node graph signal. - - Args: - node (NodeGraphQt.NodeObject): - prop_name (str): node property name. - prop_value (object): node property value. - """ - properties_widget = self.prop_widget(node) - if not properties_widget: - return - - property_window = properties_widget.get_widget(prop_name) - - if property_window and prop_value != property_window.get_value(): - self._block_signal = True - property_window.set_value(prop_value) - self._block_signal = False - - def __on_property_widget_changed(self, node_id, prop_name, prop_value): - """ - Slot function triggered when a property widget value has changed. - - Args: - node_id (str): node id. - prop_name (str): node property name. - prop_value (object): node property value. - """ - if not self._block_signal: - self.property_changed.emit(node_id, prop_name, prop_value) - - def limit(self): - """ - Returns the limit for how many nodes can be loaded into the bin. - - Returns: - int: node limit. - """ - return int(self._limit.value()) - - def set_limit(self, limit): - """ - Set limit of nodes to display. - - Args: - limit (int): node limit. - """ - self._limit.setValue(limit) - - def add_node(self, node): - """ - Add node to the properties bin. - - Args: - node (NodeGraphQt.NodeObject): node object. - """ - if self.limit() == 0 or self._lock: - return - - rows = self._prop_list.rowCount() - if rows >= self.limit(): - self._prop_list.removeRow(rows - 1) - - itm_find = self._prop_list.findItems(node.id, QtCore.Qt.MatchExactly) - if itm_find: - self._prop_list.removeRow(itm_find[0].row()) - - self._prop_list.insertRow(0) - prop_widget = NodePropWidget(node=node) - prop_widget.property_changed.connect(self.__on_property_widget_changed) - prop_widget.property_closed.connect(self.__on_prop_close) - self._prop_list.setCellWidget(0, 0, prop_widget) - - item = QtWidgets.QTableWidgetItem(node.id) - self._prop_list.setItem(0, 0, item) - self._prop_list.selectRow(0) - - def remove_node(self, node): - """ - Remove node from the properties bin. - - Args: - node (str or NodeGraphQt.BaseNode): node id or node object. - """ - node_id = node if isinstance(node, str) else node.id - self.__on_prop_close(node_id) - - def lock_bin(self): - """ - Lock/UnLock the properties bin. - """ - self._lock = not self._lock - if self._lock: - self.btn_lock.setText('UnLock') - else: - self.btn_lock.setText('Lock') - - def clear_bin(self): - """ - Clear the properties bin. - """ - self._prop_list.setRowCount(0) - - def prop_widget(self, node): - """ - Returns the node property widget. - - Args: - node (str or NodeGraphQt.NodeObject): node id or node object. - - Returns: - NodePropWidget: node property widget. - """ - node_id = node if isinstance(node, str) else node.id - itm_find = self._prop_list.findItems(node_id, QtCore.Qt.MatchExactly) - if itm_find: - item = itm_find[0] - return self._prop_list.cellWidget(item.row(), 0) - - -if __name__ == '__main__': - import sys - from NodeGraphQt import BaseNode, NodeGraph - from NodeGraphQt.constants import (NODE_PROP_QLABEL, - NODE_PROP_QLINEEDIT, - NODE_PROP_QCOMBO, - NODE_PROP_QSPINBOX, - NODE_PROP_COLORPICKER, - NODE_PROP_SLIDER) - - - class TestNode(BaseNode): - NODE_NAME = 'test node' - - def __init__(self): - super(TestNode, self).__init__() - self.create_property('label_test', 'foo bar', - widget_type=NODE_PROP_QLABEL) - self.create_property('text_edit', 'hello', - widget_type=NODE_PROP_QLINEEDIT) - self.create_property('color_picker', (0, 0, 255), - widget_type=NODE_PROP_COLORPICKER) - self.create_property('integer', 10, - widget_type=NODE_PROP_QSPINBOX) - self.create_property('list', 'foo', - items=['foo', 'bar'], - widget_type=NODE_PROP_QCOMBO) - self.create_property('range', 50, - range=(45, 55), - widget_type=NODE_PROP_SLIDER) - - def prop_changed(node_id, prop_name, prop_value): - print('-'*100) - print(node_id, prop_name, prop_value) - - - app = QtWidgets.QApplication(sys.argv) - - graph = NodeGraph() - graph.register_node(TestNode) - - prop_bin = PropertiesBinWidget(node_graph=graph) - prop_bin.property_changed.connect(prop_changed) - - node = graph.create_node('nodeGraphQt.nodes.TestNode') - - prop_bin.add_node(node) - prop_bin.show() - - app.exec_() diff --git a/NodeGraphQt/custom_widgets/properties_bin/__init__.py b/NodeGraphQt/custom_widgets/properties_bin/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/NodeGraphQt/custom_widgets/properties_bin/custom_widget_color_picker.py b/NodeGraphQt/custom_widgets/properties_bin/custom_widget_color_picker.py new file mode 100644 index 00000000..8fc9fa79 --- /dev/null +++ b/NodeGraphQt/custom_widgets/properties_bin/custom_widget_color_picker.py @@ -0,0 +1,64 @@ +#!/usr/bin/python +from Qt import QtWidgets, QtCore, QtGui + +from .custom_widget_vectors import PropVector3 +from .prop_widgets_abstract import BaseProperty + + +class PropColorPickerRGB(BaseProperty): + """ + Color picker widget for a node property. + """ + + def __init__(self, parent=None): + super(PropColorPickerRGB, self).__init__(parent) + self._color = (0, 0, 0) + self._button = QtWidgets.QPushButton() + self._vector = PropVector3() + self._vector.set_value([0, 0, 0]) + self._update_color() + + self._button.clicked.connect(self._on_select_color) + self._vector.value_changed.connect(self._on_vector_changed) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(self._button, 0, QtCore.Qt.AlignLeft) + layout.addWidget(self._vector, 1, QtCore.Qt.AlignLeft) + + def _on_vector_changed(self, _, value): + self._color = tuple(value) + self._update_color() + self.value_changed.emit(self.toolTip(), value) + + def _on_select_color(self): + current_color = QtGui.QColor(*self.get_value()) + color = QtWidgets.QColorDialog.getColor(current_color, self) + if color.isValid(): + self.set_value(color.getRgb()) + + def _update_vector(self): + self._vector.set_value(self._color) + + def _update_color(self): + c = [int(max(min(i, 255), 0)) for i in self._color] + hex_color = '#{0:02x}{1:02x}{2:02x}'.format(*c) + self._button.setStyleSheet( + ''' + QPushButton {{background-color: rgba({0}, {1}, {2}, 255);}} + QPushButton::hover {{background-color: rgba({0}, {1}, {2}, 200);}} + '''.format(*c) + ) + self._button.setToolTip( + 'rgb: {}\nhex: {}'.format(self._color[:3], hex_color) + ) + + def get_value(self): + return self._color[:3] + + def set_value(self, value): + if value != self.get_value(): + self._color = value + self._update_color() + self._update_vector() + self.value_changed.emit(self.toolTip(), value) diff --git a/NodeGraphQt/custom_widgets/properties_bin/custom_widget_file_paths.py b/NodeGraphQt/custom_widgets/properties_bin/custom_widget_file_paths.py new file mode 100644 index 00000000..59eae72a --- /dev/null +++ b/NodeGraphQt/custom_widgets/properties_bin/custom_widget_file_paths.py @@ -0,0 +1,75 @@ +#!/usr/bin/python +from Qt import QtWidgets, QtCore + +from NodeGraphQt.widgets.dialogs import FileDialog +from .prop_widgets_abstract import BaseProperty + + +class PropFilePath(BaseProperty): + """ + Displays a node property as a "QFileDialog" open widget in the + PropertiesBin. + """ + + def __init__(self, parent=None): + super(PropFilePath, self).__init__(parent) + self._ledit = QtWidgets.QLineEdit() + self._ledit.setAlignment(QtCore.Qt.AlignLeft) + self._ledit.editingFinished.connect(self._on_value_change) + self._ledit.clearFocus() + + icon = self.style().standardIcon(QtWidgets.QStyle.StandardPixmap(21)) + _button = QtWidgets.QPushButton() + _button.setIcon(icon) + _button.clicked.connect(self._on_select_file) + + hbox = QtWidgets.QHBoxLayout(self) + hbox.setContentsMargins(0, 0, 0, 0) + hbox.addWidget(self._ledit) + hbox.addWidget(_button) + + self._ext = '*' + self._file_directory = None + + def _on_select_file(self): + file_path = FileDialog.getOpenFileName(self, + file_dir=self._file_directory, + ext_filter=self._ext) + file = file_path[0] or None + if file: + self.set_value(file) + + def _on_value_change(self, value=None): + if value is None: + value = self._ledit.text() + self.value_changed.emit(self.toolTip(), value) + + def set_file_ext(self, ext=None): + self._ext = ext or '*' + + def set_file_directory(self, directory): + self._file_directory = directory + + def get_value(self): + return self._ledit.text() + + def set_value(self, value): + _value = str(value) + if _value != self.get_value(): + self._ledit.setText(_value) + self._on_value_change(_value) + + +class PropFileSavePath(PropFilePath): + """ + Displays a node property as a "QFileDialog" save widget in the + PropertiesBin. + """ + + def _on_select_file(self): + file_path = FileDialog.getSaveFileName(self, + file_dir=self._file_directory, + ext_filter=self._ext) + file = file_path[0] or None + if file: + self.set_value(file) diff --git a/NodeGraphQt/custom_widgets/properties_bin/custom_widget_slider.py b/NodeGraphQt/custom_widgets/properties_bin/custom_widget_slider.py new file mode 100644 index 00000000..021d317f --- /dev/null +++ b/NodeGraphQt/custom_widgets/properties_bin/custom_widget_slider.py @@ -0,0 +1,70 @@ +#!/usr/bin/python +from Qt import QtWidgets, QtCore + +from .prop_widgets_abstract import BaseProperty + + +class PropSlider(BaseProperty): + """ + Displays a node property as a "Slider" widget in the PropertiesBin + widget. + """ + + def __init__(self, parent=None): + super(PropSlider, self).__init__(parent) + self._block = False + self._slider = QtWidgets.QSlider() + self._spinbox = QtWidgets.QSpinBox() + self._init() + + def _init(self): + self._slider.setOrientation(QtCore.Qt.Horizontal) + self._slider.setTickPosition(QtWidgets.QSlider.TicksBelow) + self._slider.setSizePolicy(QtWidgets.QSizePolicy.Expanding, + QtWidgets.QSizePolicy.Preferred) + self._spinbox.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons) + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(self._spinbox) + layout.addWidget(self._slider) + self._spinbox.valueChanged.connect(self._on_spnbox_changed) + self._slider.valueChanged.connect(self._on_slider_changed) + # store the original press event. + self._slider_mouse_press_event = self._slider.mousePressEvent + self._slider.mousePressEvent = self._on_slider_mouse_press + self._slider.mouseReleaseEvent = self._on_slider_mouse_release + + def _on_slider_mouse_press(self, event): + self._block = True + self._slider_mouse_press_event(event) + + def _on_slider_mouse_release(self, event): + self.value_changed.emit(self.toolTip(), self.get_value()) + self._block = False + + def _on_slider_changed(self, value): + self._spinbox.setValue(value) + + def _on_spnbox_changed(self, value): + if value != self._slider.value(): + self._slider.setValue(value) + if not self._block: + self.value_changed.emit(self.toolTip(), self.get_value()) + + def get_value(self): + return self._spinbox.value() + + def set_value(self, value): + if value != self.get_value(): + self._block = True + self._spinbox.setValue(value) + self.value_changed.emit(self.toolTip(), value) + self._block = False + + def set_min(self, value=0): + self._spinbox.setMinimum(value) + self._slider.setMinimum(value) + + def set_max(self, value=0): + self._spinbox.setMaximum(value) + self._slider.setMaximum(value) diff --git a/NodeGraphQt/custom_widgets/properties_bin/custom_widget_value_edit.py b/NodeGraphQt/custom_widgets/properties_bin/custom_widget_value_edit.py new file mode 100644 index 00000000..157c55d7 --- /dev/null +++ b/NodeGraphQt/custom_widgets/properties_bin/custom_widget_value_edit.py @@ -0,0 +1,207 @@ +#!/usr/bin/python +from Qt import QtWidgets, QtCore, QtGui + + +class _NumberValueMenu(QtWidgets.QMenu): + + mouseMove = QtCore.Signal(object) + mouseRelease = QtCore.Signal(object) + stepChange = QtCore.Signal() + + def __init__(self, parent=None): + super(_NumberValueMenu, self).__init__(parent) + self.step = 1 + self.steps = [] + self.last_action = None + + def __repr__(self): + return '<{}() object at {}>'.format( + self.__class__.__name__, hex(id(self))) + + # re-implemented. + + def mousePressEvent(self, event): + """ + Disabling the mouse press event. + """ + return + + def mouseReleaseEvent(self, event): + """ + Additional functionality to emit signal. + """ + self.mouseRelease.emit(event) + super(_NumberValueMenu, self).mouseReleaseEvent(event) + + def mouseMoveEvent(self, event): + """ + Additional functionality to emit step changed signal. + """ + self.mouseMove.emit(event) + super(_NumberValueMenu, self).mouseMoveEvent(event) + action = self.actionAt(event.pos()) + if action: + if action is not self.last_action: + self.stepChange.emit() + self.last_action = action + self.step = action.step + elif self.last_action: + self.setActiveAction(self.last_action) + + def _add_step_action(self, step): + action = QtWidgets.QAction(str(step), self) + action.step = step + self.addAction(action) + + def set_steps(self, steps): + self.clear() + self.steps = steps + for step in steps: + self._add_step_action(step) + + def set_data_type(self, data_type): + if data_type is int: + new_steps = [] + for step in self.steps: + if '.' not in str(step): + new_steps.append(step) + self.set_steps(new_steps) + elif data_type is float: + self.set_steps(self.steps) + + +class _NumberValueEdit(QtWidgets.QLineEdit): + + valueChanged = QtCore.Signal(object) + + def __init__(self, parent=None, data_type=float): + super(_NumberValueEdit, self).__init__(parent) + self.setToolTip('"MMB + Drag Left/Right" to change values.') + self.setText('0') + + self._MMB_STATE = False + self._previous_x = None + self._previous_value = None + self._step = 1 + self._speed = 0.1 + self._data_type = float + + self._menu = _NumberValueMenu() + self._menu.mouseMove.connect(self.mouseMoveEvent) + self._menu.mouseRelease.connect(self.mouseReleaseEvent) + self._menu.stepChange.connect(self._reset_previous_x) + self._menu.set_steps([0.001, 0.01, 0.1, 1, 10, 100, 1000]) + + self.editingFinished.connect(self._on_text_changed) + + self.set_data_type(data_type) + + def __repr__(self): + return '<{}() object at {}>'.format( + self.__class__.__name__, hex(id(self))) + + # re-implemented + + def mouseMoveEvent(self, event): + if self._MMB_STATE: + if self._previous_x is None: + self._previous_x = event.x() + self._previous_value = self.get_value() + else: + self._step = self._menu.step + delta = event.x() - self._previous_x + value = self._previous_value + value = value + int(delta * self._speed) * self._step + self.set_value(value) + self._on_text_changed() + super(_NumberValueEdit, self).mouseMoveEvent(event) + + def mousePressEvent(self, event): + if event.button() == QtCore.Qt.MiddleButton: + self._MMB_STATE = True + self._reset_previous_x() + self._menu.exec_(QtGui.QCursor.pos()) + super(_NumberValueEdit, self).mousePressEvent(event) + + def mouseReleaseEvent(self, event): + self._menu.close() + self._MMB_STATE = False + super(_NumberValueEdit, self).mouseReleaseEvent(event) + + def keyPressEvent(self, event): + super(_NumberValueEdit, self).keyPressEvent(event) + if event.key() == QtCore.Qt.Key_Up: + return + elif event.key() == QtCore.Qt.Key_Down: + return + + # private + + def _reset_previous_x(self): + self._previous_x = None + + def _on_text_changed(self): + self.valueChanged.emit(self.get_value()) + + def _convert_text(self, text): + # int("1.0") will return error + # so we use int(float("1.0")) + try: + value = float(text) + except: + value = 0.0 + if self._data_type is int: + value = int(value) + return value + + # public + + def set_data_type(self, data_type): + self._data_type = data_type + self._menu.set_data_type(data_type) + if data_type is int: + self.setValidator(QtGui.QIntValidator()) + elif data_type is float: + self.setValidator(QtGui.QDoubleValidator()) + + def set_steps(self, steps=None): + steps = steps or [0.001, 0.01, 0.1, 1, 10, 100, 1000] + self._menu.set_steps(steps) + + def get_value(self): + if self.text().startswith('.'): + text = '0' + self.text() + self.setText(text) + return self._convert_text(self.text()) + + def set_value(self, value): + if value != self.get_value(): + self.setText(str(self._convert_text(value))) + + +class IntValueEdit(_NumberValueEdit): + + def __init__(self, parent=None): + super(IntValueEdit, self).__init__(parent, data_type=int) + + +class FloatValueEdit(_NumberValueEdit): + + def __init__(self, parent=None): + super(FloatValueEdit, self).__init__(parent, data_type=float) + + +if __name__ == '__main__': + app = QtWidgets.QApplication([]) + + int_edit = IntValueEdit() + int_edit.set_steps([1, 10]) + float_edit = FloatValueEdit() + + widget = QtWidgets.QWidget() + layout = QtWidgets.QVBoxLayout(widget) + layout.addWidget(int_edit) + layout.addWidget(float_edit) + widget.show() + + app.exec_() diff --git a/NodeGraphQt/custom_widgets/properties_bin/custom_widget_vectors.py b/NodeGraphQt/custom_widgets/properties_bin/custom_widget_vectors.py new file mode 100644 index 00000000..c075763c --- /dev/null +++ b/NodeGraphQt/custom_widgets/properties_bin/custom_widget_vectors.py @@ -0,0 +1,103 @@ +#!/usr/bin/python +from Qt import QtWidgets + +from .custom_widget_value_edit import _NumberValueEdit +from .prop_widgets_abstract import BaseProperty + + +class _PropVector(BaseProperty): + """ + Base widget for the PropVector widgets. + """ + + def __init__(self, parent=None, fields=0): + super(_PropVector, self).__init__(parent) + self._value = [] + self._items = [] + self._can_emit = True + + layout = QtWidgets.QHBoxLayout(self) + layout.setSpacing(2) + layout.setContentsMargins(0, 0, 0, 0) + for i in range(fields): + self._add_item(i) + + def _add_item(self, index): + _ledit = _NumberValueEdit() + _ledit.index = index + _ledit.valueChanged.connect( + lambda: self._on_value_change(_ledit.get_value(), _ledit.index) + ) + + self.layout().addWidget(_ledit) + self._value.append(0.0) + self._items.append(_ledit) + + def _on_value_change(self, value=None, index=None): + if self._can_emit: + if index is not None: + self._value[index] = value + self.value_changed.emit(self.toolTip(), self._value) + self.value_changed.emit(self.toolTip(), self._value) + + def _update_items(self): + if not isinstance(self._value, (list, tuple)): + raise TypeError('Value "{}" must be either list or tuple.' + .format(self._value)) + for index, value in enumerate(self._value): + if (index + 1) > len(self._items): + continue + if self._items[index].get_value() != value: + self._items[index].set_value(value) + + def set_data_type(self, data_type): + for item in self._items: + item.set_data_type(data_type) + + def get_value(self): + return self._value + + def set_value(self, value=None): + value = list(value) + if value != self.get_value(): + self._value = value + self._can_emit = False + self._update_items() + self._can_emit = True + self._on_value_change() + + +class PropVector2(_PropVector): + """ + Displays a node property as a "Vector2" widget in the PropertiesBin + widget. + + Useful for display X,Y data. + """ + + def __init__(self, parent=None): + super(PropVector2, self).__init__(parent, 2) + + +class PropVector3(_PropVector): + """ + Displays a node property as a "Vector3" widget in the PropertiesBin + widget. + + Useful for displaying x,y,z data. + """ + + def __init__(self, parent=None): + super(PropVector3, self).__init__(parent, 3) + + +class PropVector4(_PropVector): + """ + Displays a node property as a "Vector4" widget in the PropertiesBin + widget. + + Useful for display r,g,b,a data. + """ + + def __init__(self, parent=None): + super(PropVector4, self).__init__(parent, 4) diff --git a/NodeGraphQt/custom_widgets/properties_bin/node_property_factory.py b/NodeGraphQt/custom_widgets/properties_bin/node_property_factory.py new file mode 100644 index 00000000..45617ae0 --- /dev/null +++ b/NodeGraphQt/custom_widgets/properties_bin/node_property_factory.py @@ -0,0 +1,58 @@ +from NodeGraphQt.constants import NodePropWidgetEnum +from .custom_widget_color_picker import PropColorPickerRGB +from .custom_widget_file_paths import PropFilePath, PropFileSavePath +from .custom_widget_slider import PropSlider +from .custom_widget_value_edit import FloatValueEdit, IntValueEdit +from .custom_widget_vectors import PropVector2, PropVector3, PropVector4 +from .prop_widgets_base import ( + PropLabel, + PropLineEdit, + PropTextEdit, + PropComboBox, + PropCheckBox, + PropSpinBox, + PropDoubleSpinBox +) + + +class NodePropertyWidgetFactory(object): + """ + Node property widget factory for mapping the corresponding property widget + to the Properties bin. + """ + + def __init__(self): + self._widget_mapping = { + NodePropWidgetEnum.HIDDEN.value: None, + # base widgets. + NodePropWidgetEnum.QLABEL.value: PropLabel, + NodePropWidgetEnum.QLINE_EDIT.value: PropLineEdit, + NodePropWidgetEnum.QTEXT_EDIT.value: PropTextEdit, + NodePropWidgetEnum.QCOMBO_BOX.value: PropComboBox, + NodePropWidgetEnum.QCHECK_BOX.value: PropCheckBox, + NodePropWidgetEnum.QSPIN_BOX.value: PropSpinBox, + NodePropWidgetEnum.QDOUBLESPIN_BOX.value: PropDoubleSpinBox, + # custom widgets. + NodePropWidgetEnum.COLOR_PICKER.value: PropColorPickerRGB, + NodePropWidgetEnum.SLIDER.value: PropSlider, + NodePropWidgetEnum.FILE_OPEN.value: PropFilePath, + NodePropWidgetEnum.FILE_SAVE.value: PropFileSavePath, + NodePropWidgetEnum.VECTOR2.value: PropVector2, + NodePropWidgetEnum.VECTOR3.value: PropVector3, + NodePropWidgetEnum.VECTOR4.value: PropVector4, + NodePropWidgetEnum.FLOAT.value: FloatValueEdit, + NodePropWidgetEnum.INT.value: IntValueEdit, + } + + def get_widget(self, widget_type=NodePropWidgetEnum.HIDDEN.value): + """ + Return a new instance of a node property widget. + + Args: + widget_type (int): widget type index. + + Returns: + BaseProperty: node property widget. + """ + if widget_type in self._widget_mapping: + return self._widget_mapping[widget_type]() diff --git a/NodeGraphQt/custom_widgets/properties_bin/node_property_widgets.py b/NodeGraphQt/custom_widgets/properties_bin/node_property_widgets.py new file mode 100644 index 00000000..519d5ca5 --- /dev/null +++ b/NodeGraphQt/custom_widgets/properties_bin/node_property_widgets.py @@ -0,0 +1,601 @@ +#!/usr/bin/python +from collections import defaultdict + +from Qt import QtWidgets, QtCore, QtGui, QtCompat + +from .node_property_factory import NodePropertyWidgetFactory +from .prop_widgets_base import PropLineEdit + + +class _PropertiesDelegate(QtWidgets.QStyledItemDelegate): + + def paint(self, painter, option, index): + """ + Args: + painter (QtGui.QPainter): + option (QtGui.QStyleOptionViewItem): + index (QtCore.QModelIndex): + """ + painter.save() + painter.setRenderHint(QtGui.QPainter.Antialiasing, False) + painter.setPen(QtCore.Qt.NoPen) + + # draw background. + bg_clr = option.palette.midlight().color() + painter.setBrush(QtGui.QBrush(bg_clr)) + painter.drawRect(option.rect) + + # draw border. + border_width = 1 + if option.state & QtWidgets.QStyle.State_Selected: + bdr_clr = option.palette.highlight().color() + painter.setPen(QtGui.QPen(bdr_clr, 1.5)) + else: + bdr_clr = option.palette.alternateBase().color() + painter.setPen(QtGui.QPen(bdr_clr, 1)) + + painter.setBrush(QtCore.Qt.NoBrush) + painter.drawRect(QtCore.QRect( + option.rect.x() + border_width, + option.rect.y() + border_width, + option.rect.width() - (border_width * 2), + option.rect.height() - (border_width * 2)) + ) + painter.restore() + + +class _PropertiesList(QtWidgets.QTableWidget): + + def __init__(self, parent=None): + super(_PropertiesList, self).__init__(parent) + self.setItemDelegate(_PropertiesDelegate()) + self.setColumnCount(1) + self.setShowGrid(False) + self.verticalHeader().hide() + self.horizontalHeader().hide() + + QtCompat.QHeaderView.setSectionResizeMode( + self.verticalHeader(), QtWidgets.QHeaderView.ResizeToContents) + QtCompat.QHeaderView.setSectionResizeMode( + self.horizontalHeader(), 0, QtWidgets.QHeaderView.Stretch) + self.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) + + def wheelEvent(self, event): + delta = event.delta() * 0.2 + self.verticalScrollBar().setValue( + self.verticalScrollBar().value() - delta + ) + + +class _PropertiesContainer(QtWidgets.QWidget): + """ + Node properties container widget that displays nodes properties under + a tab in the ``NodePropWidget`` widget. + """ + + def __init__(self, parent=None): + super(_PropertiesContainer, self).__init__(parent) + self.__layout = QtWidgets.QGridLayout() + self.__layout.setColumnStretch(1, 1) + self.__layout.setSpacing(6) + + layout = QtWidgets.QVBoxLayout(self) + layout.setAlignment(QtCore.Qt.AlignTop) + layout.addLayout(self.__layout) + + def __repr__(self): + return '<{} object at {}>'.format( + self.__class__.__name__, hex(id(self)) + ) + + def add_widget(self, name, widget, value, label=None): + """ + Add a property widget to the window. + + Args: + name (str): property name to be displayed. + widget (BaseProperty): property widget. + value (object): property value. + label (str): custom label to display. + """ + widget.setToolTip(name) + widget.set_value(value) + if label is None: + label = name + row = self.__layout.rowCount() + if row > 0: + row += 1 + + label_flags = QtCore.Qt.AlignCenter | QtCore.Qt.AlignRight + if widget.__class__.__name__ == 'PropTextEdit': + label_flags = label_flags | QtCore.Qt.AlignTop + + self.__layout.addWidget(QtWidgets.QLabel(label), row, 0, label_flags) + self.__layout.addWidget(widget, row, 1) + + def get_widget(self, name): + """ + Returns the property widget from the name. + + Args: + name (str): property name. + + Returns: + QtWidgets.QWidget: property widget. + """ + for row in range(self.__layout.rowCount()): + item = self.__layout.itemAtPosition(row, 1) + if item and name == item.widget().toolTip(): + return item.widget() + + +class NodePropWidget(QtWidgets.QWidget): + """ + Node properties widget for display a Node object. + + Args: + parent (QtWidgets.QWidget): parent object. + node (NodeGraphQt.BaseNode): node. + """ + + #: signal (node_id, prop_name, prop_value) + property_changed = QtCore.Signal(str, str, object) + property_closed = QtCore.Signal(str) + + def __init__(self, parent=None, node=None): + super(NodePropWidget, self).__init__(parent) + self.__node_id = node.id + self.__tab_windows = {} + self.__tab = QtWidgets.QTabWidget() + + close_btn = QtWidgets.QPushButton() + close_btn.setIcon(QtGui.QIcon( + self.style().standardPixmap(QtWidgets.QStyle.SP_DialogCancelButton) + )) + close_btn.setMaximumWidth(40) + close_btn.setToolTip('close property') + close_btn.clicked.connect(self._on_close) + + self.name_wgt = PropLineEdit() + self.name_wgt.setToolTip('name') + self.name_wgt.set_value(node.name()) + self.name_wgt.value_changed.connect(self._on_property_changed) + + self.type_wgt = QtWidgets.QLabel(node.type_) + self.type_wgt.setAlignment(QtCore.Qt.AlignRight) + self.type_wgt.setToolTip('type_') + font = self.type_wgt.font() + font.setPointSize(10) + self.type_wgt.setFont(font) + + name_layout = QtWidgets.QHBoxLayout() + name_layout.setContentsMargins(0, 0, 0, 0) + name_layout.addWidget(QtWidgets.QLabel('name')) + name_layout.addWidget(self.name_wgt) + name_layout.addWidget(close_btn) + layout = QtWidgets.QVBoxLayout(self) + layout.setSpacing(4) + layout.addLayout(name_layout) + layout.addWidget(self.__tab) + layout.addWidget(self.type_wgt) + self._read_node(node) + + def __repr__(self): + return '<{} object at {}>'.format( + self.__class__.__name__, hex(id(self)) + ) + + def _on_close(self): + """ + called by the close button. + """ + self.property_closed.emit(self.__node_id) + + def _on_property_changed(self, name, value): + """ + slot function called when a property widget has changed. + + Args: + name (str): property name. + value (object): new value. + """ + self.property_changed.emit(self.__node_id, name, value) + + def _read_node(self, node): + """ + Populate widget from a node. + + Args: + node (NodeGraphQt.BaseNode): node class. + """ + model = node.model + graph_model = node.graph.model + + common_props = graph_model.get_node_common_properties(node.type_) + + # sort tabs and properties. + tab_mapping = defaultdict(list) + for prop_name, prop_val in model.custom_properties.items(): + tab_name = model.get_tab_name(prop_name) + tab_mapping[tab_name].append((prop_name, prop_val)) + + # add tabs. + for tab in sorted(tab_mapping.keys()): + if tab != 'Node': + self.add_tab(tab) + + # property widget factory. + widget_factory = NodePropertyWidgetFactory() + + # populate tab properties. + for tab in sorted(tab_mapping.keys()): + prop_window = self.__tab_windows[tab] + for prop_name, value in tab_mapping[tab]: + wid_type = model.get_widget_type(prop_name) + if wid_type == 0: + continue + + widget = widget_factory.get_widget(wid_type) + if prop_name in common_props.keys(): + if 'items' in common_props[prop_name].keys(): + widget.set_items(common_props[prop_name]['items']) + if 'range' in common_props[prop_name].keys(): + prop_range = common_props[prop_name]['range'] + widget.set_min(prop_range[0]) + widget.set_max(prop_range[1]) + + prop_window.add_widget(prop_name, widget, value, + prop_name.replace('_', ' ')) + widget.value_changed.connect(self._on_property_changed) + + # add "Node" tab properties. + self.add_tab('Node') + default_props = ['color', 'text_color', 'disabled', 'id'] + prop_window = self.__tab_windows['Node'] + for prop_name in default_props: + wid_type = model.get_widget_type(prop_name) + widget = widget_factory.get_widget(wid_type) + prop_window.add_widget(prop_name, + widget, + model.get_property(prop_name), + prop_name.replace('_', ' ')) + + widget.value_changed.connect(self._on_property_changed) + + self.type_wgt.setText(model.get_property('type_')) + + def node_id(self): + """ + Returns the node id linked to the widget. + + Returns: + str: node id + """ + return self.__node_id + + def add_widget(self, name, widget, tab='Properties'): + """ + add new node property widget. + + Args: + name (str): property name. + widget (BaseProperty): property widget. + tab (str): tab name. + """ + if tab not in self._widgets.keys(): + tab = 'Properties' + window = self.__tab_windows[tab] + window.add_widget(name, widget) + widget.value_changed.connect(self._on_property_changed) + + def add_tab(self, name): + """ + add a new tab. + + Args: + name (str): tab name. + + Returns: + PropListWidget: tab child widget. + """ + if name in self.__tab_windows.keys(): + raise AssertionError('Tab name {} already taken!'.format(name)) + self.__tab_windows[name] = _PropertiesContainer(self) + self.__tab.addTab(self.__tab_windows[name], name) + return self.__tab_windows[name] + + def get_widget(self, name): + """ + get property widget. + + Args: + name (str): property name. + + Returns: + QtWidgets.QWidget: property widget. + """ + if name == 'name': + return self.name_wgt + for tab_name, prop_win in self.__tab_windows.items(): + widget = prop_win.get_widget(name) + if widget: + return widget + + +class PropertiesBinWidget(QtWidgets.QWidget): + """ + The :class:`NodeGraphQt.PropertiesBinWidget` is a list widget for displaying + and editing a nodes properties. + + .. image:: _images/prop_bin.png + :width: 950px + + .. code-block:: python + :linenos: + + from NodeGraphQt import NodeGraph, PropertiesBinWidget + + # create node graph. + graph = NodeGraph() + + # create properties bin widget. + properties_bin = PropertiesBinWidget(parent=None, node_graph=graph) + properties_bin.show() + + Args: + parent (QtWidgets.QWidget): parent of the new widget. + node_graph (NodeGraphQt.NodeGraph): node graph. + """ + + #: Signal emitted (node_id, prop_name, prop_value) + property_changed = QtCore.Signal(str, str, object) + + def __init__(self, parent=None, node_graph=None): + super(PropertiesBinWidget, self).__init__(parent) + self.setWindowTitle('Properties Bin') + self._prop_list = _PropertiesList() + self._limit = QtWidgets.QSpinBox() + self._limit.setToolTip('Set display nodes limit.') + self._limit.setMaximum(10) + self._limit.setMinimum(0) + self._limit.setValue(2) + self._limit.valueChanged.connect(self.__on_limit_changed) + self.resize(450, 400) + + self._block_signal = False + + self._lock = False + self.btn_lock = QtWidgets.QPushButton('Lock') + self.btn_lock.setToolTip( + 'Lock the properties bin prevent nodes from being loaded.') + self.btn_lock.clicked.connect(self.lock_bin) + + btn_clr = QtWidgets.QPushButton('Clear') + btn_clr.setToolTip('Clear the properties bin.') + btn_clr.clicked.connect(self.clear_bin) + + top_layout = QtWidgets.QHBoxLayout() + top_layout.setSpacing(2) + top_layout.addWidget(self._limit) + top_layout.addStretch(1) + top_layout.addWidget(self.btn_lock) + top_layout.addWidget(btn_clr) + + layout = QtWidgets.QVBoxLayout(self) + layout.addLayout(top_layout) + layout.addWidget(self._prop_list, 1) + + # wire up node graph. + node_graph.add_properties_bin(self) + node_graph.node_double_clicked.connect(self.add_node) + node_graph.nodes_deleted.connect(self.__on_nodes_deleted) + node_graph.property_changed.connect(self.__on_graph_property_changed) + + def __repr__(self): + return '<{} object at {}>'.format(self.__class__.__name__, hex(id(self))) + + def __on_prop_close(self, node_id): + items = self._prop_list.findItems(node_id, QtCore.Qt.MatchExactly) + [self._prop_list.removeRow(i.row()) for i in items] + + def __on_limit_changed(self, value): + rows = self._prop_list.rowCount() + if rows > value: + self._prop_list.removeRow(rows - 1) + + def __on_nodes_deleted(self, nodes): + """ + Slot function when a node has been deleted. + + Args: + nodes (list[str]): list of node ids. + """ + [self.__on_prop_close(n) for n in nodes] + + def __on_graph_property_changed(self, node, prop_name, prop_value): + """ + Slot function that updates the property bin from the node graph signal. + + Args: + node (NodeGraphQt.NodeObject): + prop_name (str): node property name. + prop_value (object): node property value. + """ + properties_widget = self.prop_widget(node) + if not properties_widget: + return + + property_window = properties_widget.get_widget(prop_name) + + if property_window and prop_value != property_window.get_value(): + self._block_signal = True + property_window.set_value(prop_value) + self._block_signal = False + + def __on_property_widget_changed(self, node_id, prop_name, prop_value): + """ + Slot function triggered when a property widget value has changed. + + Args: + node_id (str): node id. + prop_name (str): node property name. + prop_value (object): node property value. + """ + if not self._block_signal: + self.property_changed.emit(node_id, prop_name, prop_value) + + def limit(self): + """ + Returns the limit for how many nodes can be loaded into the bin. + + Returns: + int: node limit. + """ + return int(self._limit.value()) + + def set_limit(self, limit): + """ + Set limit of nodes to display. + + Args: + limit (int): node limit. + """ + self._limit.setValue(limit) + + def add_node(self, node): + """ + Add node to the properties bin. + + Args: + node (NodeGraphQt.NodeObject): node object. + """ + if self.limit() == 0 or self._lock: + return + + rows = self._prop_list.rowCount() + if rows >= self.limit(): + self._prop_list.removeRow(rows - 1) + + itm_find = self._prop_list.findItems(node.id, QtCore.Qt.MatchExactly) + if itm_find: + self._prop_list.removeRow(itm_find[0].row()) + + self._prop_list.insertRow(0) + prop_widget = NodePropWidget(node=node) + prop_widget.property_changed.connect(self.__on_property_widget_changed) + prop_widget.property_closed.connect(self.__on_prop_close) + self._prop_list.setCellWidget(0, 0, prop_widget) + + item = QtWidgets.QTableWidgetItem(node.id) + self._prop_list.setItem(0, 0, item) + self._prop_list.selectRow(0) + + def remove_node(self, node): + """ + Remove node from the properties bin. + + Args: + node (str or NodeGraphQt.BaseNode): node id or node object. + """ + node_id = node if isinstance(node, str) else node.id + self.__on_prop_close(node_id) + + def lock_bin(self): + """ + Lock/UnLock the properties bin. + """ + self._lock = not self._lock + if self._lock: + self.btn_lock.setText('UnLock') + else: + self.btn_lock.setText('Lock') + + def clear_bin(self): + """ + Clear the properties bin. + """ + self._prop_list.setRowCount(0) + + def prop_widget(self, node): + """ + Returns the node property widget. + + Args: + node (str or NodeGraphQt.NodeObject): node id or node object. + + Returns: + NodePropWidget: node property widget. + """ + node_id = node if isinstance(node, str) else node.id + itm_find = self._prop_list.findItems(node_id, QtCore.Qt.MatchExactly) + if itm_find: + item = itm_find[0] + return self._prop_list.cellWidget(item.row(), 0) + + +if __name__ == '__main__': + import sys + from NodeGraphQt import BaseNode, NodeGraph + from NodeGraphQt.constants import NodePropWidgetEnum + + + class _TestNode(BaseNode): + + __identifier__ = 'property.test' + NODE_NAME = 'test node' + + def __init__(self): + super(_TestNode, self).__init__() + self.create_property( + 'label_test', + value='foo bar', + widget_type=NodePropWidgetEnum.QLABEL.value + ) + self.create_property( + 'text_edit', + value='text edit test', + widget_type=NodePropWidgetEnum.QLABEL.value + ) + self.create_property( + 'color_picker', + value=(0, 0, 255), + widget_type=NodePropWidgetEnum.COLOR_PICKER.value + ) + self.create_property( + 'integer', + value=10, + widget_type=NodePropWidgetEnum.QSPIN_BOX.value + ) + self.create_property( + 'list', + value='itm2', + items=['itm1', 'itm2', 'itm3'], + widget_type=NodePropWidgetEnum.QCOMBO_BOX.value + ) + self.create_property( + 'range', + value=50, + range=(45, 55), + widget_type=NodePropWidgetEnum.SLIDER.value + ) + + def _prop_changed(node_id, prop_name, prop_value): + print('-'*100) + print(node_id, prop_name, prop_value) + + + app = QtWidgets.QApplication(sys.argv) + + graph = NodeGraph() + graph.register_node(_TestNode) + + prop_bin = PropertiesBinWidget(node_graph=graph) + prop_bin.resize(800, 600) + prop_bin.property_changed.connect(_prop_changed) + + node = graph.create_node('property.test._TestNode') + + prop_bin.add_node(node) + prop_bin.show() + + app.exec_() diff --git a/NodeGraphQt/custom_widgets/properties_bin/prop_widgets_abstract.py b/NodeGraphQt/custom_widgets/properties_bin/prop_widgets_abstract.py new file mode 100644 index 00000000..86974647 --- /dev/null +++ b/NodeGraphQt/custom_widgets/properties_bin/prop_widgets_abstract.py @@ -0,0 +1,36 @@ +#!/usr/bin/python +from Qt import QtWidgets, QtCore + + +class BaseProperty(QtWidgets.QWidget): + """ + Base class for a custom node property widget to be displayed in the + PropertiesBin widget. + + Inherits from: :class:`PySide2.QtWidgets.QWidget` + """ + + value_changed = QtCore.Signal(str, object) + + def __repr__(self): + return '<{}() object at {}>'.format( + self.__class__.__name__, hex(id(self))) + + def get_value(self): + """ + + Returns: + object: + """ + raise NotImplementedError + + def set_value(self, value): + """ + + Args: + value (object): + + Returns: + object: + """ + raise NotImplementedError diff --git a/NodeGraphQt/custom_widgets/properties_bin/prop_widgets_base.py b/NodeGraphQt/custom_widgets/properties_bin/prop_widgets_base.py new file mode 100644 index 00000000..040458c5 --- /dev/null +++ b/NodeGraphQt/custom_widgets/properties_bin/prop_widgets_base.py @@ -0,0 +1,252 @@ +#!/usr/bin/python +from Qt import QtWidgets, QtCore + + +class PropLabel(QtWidgets.QLabel): + """ + Displays a node property as a "QLabel" widget in the PropertiesBin widget. + """ + + value_changed = QtCore.Signal(str, object) + + def __repr__(self): + return '<{}() object at {}>'.format( + self.__class__.__name__, hex(id(self))) + + def get_value(self): + return self.text() + + def set_value(self, value): + if value != self.get_value(): + self.setText(str(value)) + self.value_changed.emit(self.toolTip(), value) + + +class PropLineEdit(QtWidgets.QLineEdit): + """ + Displays a node property as a "QLineEdit" widget in the PropertiesBin + widget. + """ + + value_changed = QtCore.Signal(str, object) + + def __init__(self, parent=None): + super(PropLineEdit, self).__init__(parent) + self.editingFinished.connect(self._on_editing_finished) + + def __repr__(self): + return '<{}() object at {}>'.format( + self.__class__.__name__, hex(id(self))) + + def _on_editing_finished(self): + self.value_changed.emit(self.toolTip(), self.text()) + + def get_value(self): + return self.text() + + def set_value(self, value): + _value = str(value) + if _value != self.get_value(): + self.setText(_value) + self.value_changed.emit(self.toolTip(), _value) + + +class PropTextEdit(QtWidgets.QTextEdit): + """ + Displays a node property as a "QTextEdit" widget in the PropertiesBin + widget. + """ + + value_changed = QtCore.Signal(str, object) + + def __init__(self, parent=None): + super(PropTextEdit, self).__init__(parent) + self._prev_text = '' + + def __repr__(self): + return '<{}() object at {}>'.format( + self.__class__.__name__, hex(id(self))) + + def focusInEvent(self, event): + super(PropTextEdit, self).focusInEvent(event) + self._prev_text = self.toPlainText() + + def focusOutEvent(self, event): + super(PropTextEdit, self).focusOutEvent(event) + if self._prev_text != self.toPlainText(): + self.value_changed.emit(self.toolTip(), self.toPlainText()) + self._prev_text = '' + + def get_value(self): + return self.toPlainText() + + def set_value(self, value): + _value = str(value) + if _value != self.get_value(): + self.setPlainText(_value) + self.value_changed.emit(self.toolTip(), _value) + + +class PropComboBox(QtWidgets.QComboBox): + """ + Displays a node property as a "QComboBox" widget in the PropertiesBin + widget. + """ + + value_changed = QtCore.Signal(str, object) + + def __init__(self, parent=None): + super(PropComboBox, self).__init__(parent) + self.currentIndexChanged.connect(self._on_index_changed) + + def __repr__(self): + return '<{}() object at {}>'.format( + self.__class__.__name__, hex(id(self))) + + def _on_index_changed(self): + self.value_changed.emit(self.toolTip(), self.get_value()) + + def items(self): + """ + Returns items from the combobox. + + Returns: + list[str]: list of strings. + """ + return [self.itemText(i) for i in range(self.count())] + + def set_items(self, items): + """ + Set items on the combobox. + + Args: + items (list[str]): list of strings. + """ + self.clear() + self.addItems(items) + + def get_value(self): + return self.currentText() + + def set_value(self, value): + if value != self.get_value(): + idx = self.findText(value, QtCore.Qt.MatchExactly) + self.setCurrentIndex(idx) + if idx >= 0: + self.value_changed.emit(self.toolTip(), value) + + +class PropCheckBox(QtWidgets.QCheckBox): + """ + Displays a node property as a "QCheckBox" widget in the PropertiesBin + widget. + """ + + value_changed = QtCore.Signal(str, object) + + def __init__(self, parent=None): + super(PropCheckBox, self).__init__(parent) + self.clicked.connect(self._on_clicked) + + def __repr__(self): + return '<{}() object at {}>'.format( + self.__class__.__name__, hex(id(self))) + + def _on_clicked(self): + self.value_changed.emit(self.toolTip(), self.get_value()) + + def get_value(self): + return self.isChecked() + + def set_value(self, value): + _value = bool(value) + if _value != self.get_value(): + self.setChecked(_value) + self.value_changed.emit(self.toolTip(), _value) + + +class PropSpinBox(QtWidgets.QSpinBox): + """ + Displays a node property as a "QSpinBox" widget in the PropertiesBin widget. + """ + + value_changed = QtCore.Signal(str, object) + + def __init__(self, parent=None): + super(PropSpinBox, self).__init__(parent) + self.setButtonSymbols(self.NoButtons) + self.valueChanged.connect(self._on_value_change) + + def __repr__(self): + return '<{}() object at {}>'.format( + self.__class__.__name__, hex(id(self))) + + def _on_value_change(self, value): + self.value_changed.emit(self.toolTip(), value) + + def get_value(self): + return self.value() + + def set_value(self, value): + if value != self.get_value(): + self.setValue(value) + + +class PropDoubleSpinBox(QtWidgets.QDoubleSpinBox): + """ + Displays a node property as a "QDoubleSpinBox" widget in the PropertiesBin + widget. + """ + + value_changed = QtCore.Signal(str, object) + + def __init__(self, parent=None): + super(PropDoubleSpinBox, self).__init__(parent) + self.setButtonSymbols(self.NoButtons) + self.valueChanged.connect(self._on_value_change) + + def __repr__(self): + return '<{}() object at {}>'.format( + self.__class__.__name__, hex(id(self))) + + def _on_value_change(self, value): + self.value_changed.emit(self.toolTip(), value) + + def get_value(self): + return self.value() + + def set_value(self, value): + if value != self.get_value(): + self.setValue(value) + + +# class PropPushButton(QtWidgets.QPushButton): +# """ +# Displays a node property as a "QPushButton" widget in the PropertiesBin +# widget. +# """ +# +# value_changed = QtCore.Signal(str, object) +# button_clicked = QtCore.Signal(str, object) +# +# def __init__(self, parent=None): +# super(PropPushButton, self).__init__(parent) +# self.clicked.connect(self.button_clicked.emit) +# +# def set_on_click_func(self, func, node): +# """ +# Sets slot function for the PropPushButton widget. +# +# Args: +# func (function): property slot function. +# node (NodeGraphQt.NodeObject): node object. +# """ +# if not callable(func): +# raise TypeError('var func is not a function.') +# self.clicked.connect(lambda: func(node)) +# +# def get_value(self): +# return +# +# def set_value(self, value): +# return diff --git a/NodeGraphQt/nodes/backdrop_node.py b/NodeGraphQt/nodes/backdrop_node.py index 628cd570..2ccd23c5 100644 --- a/NodeGraphQt/nodes/backdrop_node.py +++ b/NodeGraphQt/nodes/backdrop_node.py @@ -1,6 +1,6 @@ #!/usr/bin/python from NodeGraphQt.base.node import NodeObject -from NodeGraphQt.constants import NODE_PROP_QTEXTEDIT +from NodeGraphQt.constants import NodePropWidgetEnum from NodeGraphQt.qgraphics.node_backdrop import BackdropNodeItem @@ -24,7 +24,7 @@ def __init__(self, qgraphics_views=None): # override base default color. self.model.color = (5, 129, 138, 255) self.create_property('backdrop_text', '', - widget_type=NODE_PROP_QTEXTEDIT, + widget_type=NodePropWidgetEnum.QTEXT_EDIT.value, tab='Backdrop') def on_backdrop_updated(self, update_prop, value=None): diff --git a/NodeGraphQt/nodes/base_node.py b/NodeGraphQt/nodes/base_node.py index f8c04d17..fb386a23 100644 --- a/NodeGraphQt/nodes/base_node.py +++ b/NodeGraphQt/nodes/base_node.py @@ -3,11 +3,7 @@ from NodeGraphQt.base.node import NodeObject from NodeGraphQt.base.port import Port -from NodeGraphQt.constants import (NODE_PROP_QLABEL, - NODE_PROP_QLINEEDIT, - NODE_PROP_QCOMBO, - NODE_PROP_QCHECKBOX, - PortTypeEnum) +from NodeGraphQt.constants import NodePropWidgetEnum, PortTypeEnum from NodeGraphQt.errors import (PortError, PortRegistrationError, NodeWidgetError) @@ -70,7 +66,7 @@ def update_model(self): self.model.set_property(name, val) for name, widget in self.view.widgets.items(): - self.model.set_property(name, widget.value) + self.model.set_property(name, widget.get_value()) def set_layout_direction(self, value=0): """ @@ -142,7 +138,7 @@ def get_widget(self, name): """ return self.view.widgets.get(name) - def add_custom_widget(self, widget, widget_type=NODE_PROP_QLABEL, tab=None): + def add_custom_widget(self, widget, widget_type=None, tab=None): """ Add a custom node widget into the node. @@ -155,12 +151,15 @@ def add_custom_widget(self, widget, widget_type=NODE_PROP_QLABEL, tab=None): Args: widget (NodeBaseWidget): node widget class object. widget_type: widget flag to display in the - :class:`NodeGraphQt.PropertiesBinWidget` (default: QLabel). + :class:`NodeGraphQt.PropertiesBinWidget` + (default: :attr:`NodePropWidgetEnum.HIDDEN`). tab (str): name of the widget tab to display in. """ if not isinstance(widget, NodeBaseWidget): raise NodeWidgetError( '\'widget\' must be an instance of a NodeBaseWidget') + + widget_type = widget_type or NodePropWidgetEnum.HIDDEN.value self.create_property(widget.get_name(), widget.get_value(), widget_type=widget_type, @@ -185,10 +184,13 @@ def add_combo_menu(self, name, label='', items=None, tab=None): items (list[str]): items to be added into the menu. tab (str): name of the widget tab to display in. """ - items = items or [] self.create_property( - name, items[0], items=items, widget_type=NODE_PROP_QCOMBO, tab=tab) - + name, + value=items[0] if items else None, + items=items or [], + widget_type=NodePropWidgetEnum.QCOMBO_BOX.value, + tab=tab + ) widget = NodeComboBox(self.view, name, label, items) widget.value_changed.connect(lambda k, v: self.set_property(k, v)) self.view.add_widget(widget) @@ -210,7 +212,11 @@ def add_text_input(self, name, label='', text='', tab=None): tab (str): name of the widget tab to display in. """ self.create_property( - name, text, widget_type=NODE_PROP_QLINEEDIT, tab=tab) + name, + value=text, + widget_type=NodePropWidgetEnum.QLINE_EDIT.value, + tab=tab + ) widget = NodeLineEdit(self.view, name, label, text) widget.value_changed.connect(lambda k, v: self.set_property(k, v)) self.view.add_widget(widget) @@ -233,7 +239,11 @@ def add_checkbox(self, name, label='', text='', state=False, tab=None): tab (str): name of the widget tab to display in. """ self.create_property( - name, state, widget_type=NODE_PROP_QCHECKBOX, tab=tab) + name, + value=state, + widget_type=NodePropWidgetEnum.QCHECK_BOX.value, + tab=tab + ) widget = NodeCheckBox(self.view, name, label, text, state) widget.value_changed.connect(lambda k, v: self.set_property(k, v)) self.view.add_widget(widget) diff --git a/NodeGraphQt/pkg_info.py b/NodeGraphQt/pkg_info.py index 823c0626..027da795 100644 --- a/NodeGraphQt/pkg_info.py +++ b/NodeGraphQt/pkg_info.py @@ -1,6 +1,6 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -__version__ = '0.4.0' +__version__ = '0.5.0' __status__ = 'Work in Progress' __license__ = 'MIT' diff --git a/NodeGraphQt/qgraphics/node_base.py b/NodeGraphQt/qgraphics/node_base.py index ada4ae9f..7b6a3c7d 100644 --- a/NodeGraphQt/qgraphics/node_base.py +++ b/NodeGraphQt/qgraphics/node_base.py @@ -1024,4 +1024,4 @@ def from_dict(self, node_dict): widgets = node_dict.pop('widgets', {}) for name, value in widgets.items(): if self._widgets.get(name): - self._widgets[name].value = value + self._widgets[name].get_value = value diff --git a/setup.cfg b/setup.cfg index da69c535..68c630e4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = NodeGraphQt -version=0.4.0 +version=0.5.0 author=Johnny Chan license = MIT License license_file = LICENSE.md