diff --git a/NodeGraphQt/base/graph.py b/NodeGraphQt/base/graph.py index 31dbaf48..59eafe6b 100644 --- a/NodeGraphQt/base/graph.py +++ b/NodeGraphQt/base/graph.py @@ -26,6 +26,7 @@ class QWidgetDrops(QtWidgets.QWidget): + def __init__(self): super(QWidgetDrops, self).__init__() self.setAcceptDrops(True) @@ -61,7 +62,8 @@ def dropEvent(self, event): class NodeGraph(QtCore.QObject): """ - The ``NodeGraph`` class is the main controller for managing all nodes. + The ``NodeGraph`` class is the main controller for managing all nodes + and the node graph. Inherited from: :class:`PySide2.QtCore.QObject` @@ -153,18 +155,24 @@ def __init__(self, parent=None): self._current_node_space = None self._editable = True - tab = QtWidgets.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_Tab), self._viewer) - tab.activated.connect(self._toggle_tab_search) - self._viewer.need_show_tab_search.connect(self._toggle_tab_search) - self._wire_signals() self._node_space_bar = node_space_bar(self) self._auto_update = True def __repr__(self): - return '<{} object at {}>'.format(self.__class__.__name__, hex(id(self))) + return '<{} object at {}>'.format( + self.__class__.__name__, hex(id(self))) def _wire_signals(self): + """ + Connect up all the signals and slots here. + """ + # hard coded tab search. + tab = QtWidgets.QShortcut( + QtGui.QKeySequence(QtCore.Qt.Key_Tab), self._viewer) + tab.activated.connect(self._toggle_tab_search) + self._viewer.need_show_tab_search.connect(self._toggle_tab_search) + # internal signals. self._viewer.search_triggered.connect(self._on_search_triggered) self._viewer.connection_sliced.connect(self._on_connection_sliced) @@ -231,7 +239,7 @@ def _on_property_bin_changed(self, node_id, prop_name, prop_value): Args: node_id (str): node id. prop_name (str): node property name. - prop_value (object): python object. + prop_value (object): python built in types. """ if not self._editable: return @@ -391,6 +399,16 @@ def model(self): """ return self._model + @property + def node_factory(self): + """ + Return the node factory object used by the node graph. + + Returns: + NodeFactory: node factory. + """ + return self._node_factory + @property def widget(self): """ diff --git a/NodeGraphQt/base/model.py b/NodeGraphQt/base/model.py index 8260da03..a2df1217 100644 --- a/NodeGraphQt/base/model.py +++ b/NodeGraphQt/base/model.py @@ -3,10 +3,10 @@ from collections import defaultdict from ..constants import (NODE_PROP, - NODE_PROP_QLABEL, - NODE_PROP_QLINEEDIT, - NODE_PROP_QCHECKBOX, - NODE_PROP_COLORPICKER) + NODE_PROP_QLABEL, + NODE_PROP_QLINEEDIT, + NODE_PROP_QCHECKBOX, + NODE_PROP_COLORPICKER) from ..errors import NodePropertyError @@ -23,7 +23,7 @@ def __init__(self, node): self.data_type = 'NoneType' def __repr__(self): - return '<{}(\'{}\') @ {}>'.format( + return '<{}(\'{}\') object at {}>'.format( self.__class__.__name__, self.name, hex(id(self))) @property @@ -94,6 +94,10 @@ def __init__(self): 'outputs': NODE_PROP, } + def __repr__(self): + return '<{}(\'{}\') object at {}>'.format( + self.__class__.__name__, self.name, self.id) + def add_property(self, name, value, items=None, range=None, widget_type=NODE_PROP, tab='Properties', ext=None, funcs=None): @@ -240,15 +244,23 @@ def to_dict(self): output_ports = [] for name, model in node_dict.pop('inputs').items(): if self.dynamic_port: - input_ports.append({'name': name, 'multi_connection': model.multi_connection, - 'display_name': model.display_name, 'data_type': model.data_type}) + input_ports.append({ + 'name': name, + 'multi_connection': model.multi_connection, + 'display_name': model.display_name, + 'data_type': model.data_type + }) connected_ports = model.to_dict['connected_ports'] if connected_ports: inputs[name] = connected_ports for name, model in node_dict.pop('outputs').items(): if self.dynamic_port: - output_ports.append({'name': name, 'multi_connection': model.multi_connection, - 'display_name': model.display_name, 'data_type': model.data_type}) + output_ports.append({ + 'name': name, + 'multi_connection': model.multi_connection, + 'display_name': model.display_name, + 'data_type': model.data_type + }) connected_ports = model.to_dict['connected_ports'] if connected_ports: outputs[name] = connected_ports diff --git a/NodeGraphQt/base/node.py b/NodeGraphQt/base/node.py index 9d94f3c2..9f16df7f 100644 --- a/NodeGraphQt/base/node.py +++ b/NodeGraphQt/base/node.py @@ -281,9 +281,11 @@ def create_property(self, name, value, items=None, range=None, widget_type (int): widget flag to display in the ``PropertiesBinWidget`` tab (str): name of the widget tab to display in the properties bin. ext (str): file ext of ``NODE_PROP_FILE`` - funcs (list) list of functions for NODE_PROP_BUTTON + funcs (list[function]) list of functions for NODE_PROP_BUTTON """ - self.model.add_property(name, value, items, range, widget_type, tab, ext, funcs) + self.model.add_property( + name, value, items, range, widget_type, tab, ext, funcs + ) def properties(self): """ @@ -317,7 +319,7 @@ def set_property(self, name, value): Args: name (str): name of the property. - value (object): property data. + value (object): property data (python built in types). """ # prevent signals from causing a infinite loop. diff --git a/NodeGraphQt/base/port.py b/NodeGraphQt/base/port.py index 33ffc55b..29aaeee3 100644 --- a/NodeGraphQt/base/port.py +++ b/NodeGraphQt/base/port.py @@ -26,7 +26,8 @@ def __init__(self, node, port): def __repr__(self): port = str(self.__class__.__name__) - return '<{}("{}") object at {}>'.format(port, self.name(), hex(id(self))) + return '<{}("{}") object at {}>'.format( + port, self.name(), hex(id(self))) @property def view(self): diff --git a/NodeGraphQt/pkg_info.py b/NodeGraphQt/pkg_info.py index b2ef4608..bf8d8663 100644 --- a/NodeGraphQt/pkg_info.py +++ b/NodeGraphQt/pkg_info.py @@ -1,6 +1,6 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -__version__ = '0.1.1' +__version__ = '0.1.2' __status__ = 'Work in Progress' __license__ = 'MIT' diff --git a/NodeGraphQt/qgraphics/node_abstract.py b/NodeGraphQt/qgraphics/node_abstract.py index ec46b457..c74b222c 100644 --- a/NodeGraphQt/qgraphics/node_abstract.py +++ b/NodeGraphQt/qgraphics/node_abstract.py @@ -38,6 +38,12 @@ def boundingRect(self): return QtCore.QRectF(0.0, 0.0, self._width, self._height) def mousePressEvent(self, event): + """ + Re-implemented to update "self._properties['selected']" attribute. + + Args: + event (QtWidgets.QGraphicsSceneMouseEvent): mouse event. + """ self._properties['selected'] = True super(AbstractNodeItem, self).mousePressEvent(event) diff --git a/NodeGraphQt/qgraphics/node_base.py b/NodeGraphQt/qgraphics/node_base.py index 8ddd57ba..d02af940 100644 --- a/NodeGraphQt/qgraphics/node_base.py +++ b/NodeGraphQt/qgraphics/node_base.py @@ -197,12 +197,23 @@ def paint(self, painter, option, widget): painter.restore() def mousePressEvent(self, event): + """ + Re-implemented to ignore event if LMB is over port collision area. + + Args: + event (QtWidgets.QGraphicsSceneMouseEvent): mouse event. + """ if event.button() == QtCore.Qt.LeftButton: - start = PortItem().boundingRect().width() - PORT_FALLOFF - end = self.boundingRect().width() - start - x_pos = event.pos().x() - if not start <= x_pos <= end: - event.ignore() + for p in self._input_items.keys(): + if p.hovered: + event.ignore() + super(NodeItem, self).mousePressEvent(event) + return + for p in self._output_items.keys(): + if p.hovered: + event.ignore() + super(NodeItem, self).mousePressEvent(event) + return super(NodeItem, self).mousePressEvent(event) def mouseReleaseEvent(self, event): diff --git a/NodeGraphQt/qgraphics/port.py b/NodeGraphQt/qgraphics/port.py index 952e64c1..fb4646ef 100644 --- a/NodeGraphQt/qgraphics/port.py +++ b/NodeGraphQt/qgraphics/port.py @@ -61,11 +61,13 @@ def paint(self, painter, option, widget): """ painter.save() - ### display the falloff colision ### + # display falloff collision for debugging + # ---------------------------------------------------------------------- # pen = QtGui.QPen(QtGui.QColor(255, 255, 255, 80), 0.8) # pen.setStyle(QtCore.Qt.DotLine) # painter.setPen(pen) # painter.drawRect(self.boundingRect()) + # ---------------------------------------------------------------------- rect_w = self._width / 1.8 rect_h = self._height / 1.8 @@ -123,8 +125,6 @@ def itemChange(self, change, value): return super(PortItem, self).itemChange(change, value) def mousePressEvent(self, event): - # if event.modifiers() != QtCore.Qt.AltModifier: - # self.viewer_start_connection() super(PortItem, self).mousePressEvent(event) def mouseReleaseEvent(self, event): diff --git a/NodeGraphQt/widgets/file_dialog.py b/NodeGraphQt/widgets/dialogs.py similarity index 54% rename from NodeGraphQt/widgets/file_dialog.py rename to NodeGraphQt/widgets/dialogs.py index 0fdccc54..14d0855a 100644 --- a/NodeGraphQt/widgets/file_dialog.py +++ b/NodeGraphQt/widgets/dialogs.py @@ -14,10 +14,11 @@ def set_dir(file): current_dir = os.path.split(file)[0] -class file_dialog(object): +class FileDialog(object): @staticmethod - def getSaveFileName(parent=None, title="Save File", file_dir=None, ext_filter="*"): + def getSaveFileName(parent=None, title="Save File", file_dir=None, + ext_filter="*"): if not file_dir: file_dir = current_dir file_dlg = QtWidgets.QFileDialog.getSaveFileName( @@ -28,7 +29,8 @@ def getSaveFileName(parent=None, title="Save File", file_dir=None, ext_filter="* return file_dlg @staticmethod - def getOpenFileName(parent=None, title="Open File", file_dir=None, ext_filter="*"): + def getOpenFileName(parent=None, title="Open File", file_dir=None, + ext_filter="*"): if not file_dir: file_dir = current_dir @@ -42,10 +44,25 @@ def getOpenFileName(parent=None, title="Open File", file_dir=None, ext_filter="* return file_dlg -def messageBox(text, title , buttons): - msg = QtWidgets.QMessageBox() - msg.setStyleSheet(STYLE_MESSAGEBOX) - msg.setWindowTitle(title) - msg.setInformativeText(text) - msg.setStandardButtons(buttons) - return msg.exec_() \ No newline at end of file +class BaseDialog(object): + + @staticmethod + def message_dialog(text, title): + dlg = QtWidgets.QMessageBox() + dlg.setStyleSheet(STYLE_MESSAGEBOX) + dlg.setWindowTitle(title) + dlg.setInformativeText(text) + dlg.setStandardButtons(QtWidgets.QMessageBox.Ok) + return dlg.exec_() + + @staticmethod + def question_dialog(text, title): + dlg = QtWidgets.QMessageBox() + dlg.setStyleSheet(STYLE_MESSAGEBOX) + dlg.setWindowTitle(title) + dlg.setInformativeText(text) + dlg.setStandardButtons( + QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No + ) + dlg.exec_() + return bool(dlg == QtWidgets.QMessageBox.Yes) diff --git a/NodeGraphQt/widgets/node_space_bar.py b/NodeGraphQt/widgets/node_space_bar.py index 9beab8a3..c7e5564c 100644 --- a/NodeGraphQt/widgets/node_space_bar.py +++ b/NodeGraphQt/widgets/node_space_bar.py @@ -3,6 +3,7 @@ class node_space_bar(QtWidgets.QWidget): + def __init__(self, graph): super(node_space_bar, self).__init__() self.setMaximumHeight(20) @@ -55,4 +56,4 @@ def set_node(self, node): self.add_node(node) self._layout.addStretch() - self.update() \ No newline at end of file + self.update() diff --git a/NodeGraphQt/widgets/node_tree.py b/NodeGraphQt/widgets/node_tree.py index f349dedb..cdae62b7 100644 --- a/NodeGraphQt/widgets/node_tree.py +++ b/NodeGraphQt/widgets/node_tree.py @@ -30,10 +30,11 @@ class NodeTreeWidget(QtWidgets.QTreeWidget): def __init__(self, parent=None, node_graph=None): super(NodeTreeWidget, self).__init__(parent) self.setDragDropMode(QtWidgets.QAbstractItemView.DragOnly) + self.setWindowTitle('Node Tree') self.setHeaderHidden(True) self._factory = None self._custom_labels = {} - self._set_node_factory(node_graph._node_factory) + self._set_node_factory(node_graph.node_factory) def __repr__(self): return '<{} object at {}>'.format(self.__class__.__name__, hex(id(self))) diff --git a/NodeGraphQt/widgets/node_widgets.py b/NodeGraphQt/widgets/node_widgets.py index 0df14600..c185fce6 100644 --- a/NodeGraphQt/widgets/node_widgets.py +++ b/NodeGraphQt/widgets/node_widgets.py @@ -1,5 +1,5 @@ #!/usr/bin/python -from .file_dialog import file_dialog +from .dialogs import FileDialog from .properties import _ValueEdit from .stylesheet import * @@ -421,7 +421,7 @@ def __init__(self, parent=None, name='', label='', text='', ext="*"): self._ext = ext def _on_select_file(self): - file_path = file_dialog.getOpenFileName(ext_filter=self._ext) + file_path = FileDialog.getOpenFileName(ext_filter=self._ext) file = file_path[0] or None if file: self.value = file diff --git a/NodeGraphQt/widgets/properties.py b/NodeGraphQt/widgets/properties.py index 006451f9..4b470eb9 100644 --- a/NodeGraphQt/widgets/properties.py +++ b/NodeGraphQt/widgets/properties.py @@ -1,6 +1,7 @@ #!/usr/bin/python from collections import defaultdict +from .dialogs import FileDialog from .. import QtWidgets, QtCore, QtGui from ..constants import (NODE_PROP_QLABEL, NODE_PROP_QLINEEDIT, @@ -18,7 +19,6 @@ NODE_PROP_FLOAT, NODE_PROP_INT, NODE_PROP_BUTTON) -from .file_dialog import file_dialog class BaseProperty(QtWidgets.QWidget): @@ -312,9 +312,9 @@ def set_file_dir(self, dir): self._file_dir = dir def _on_select_file(self): - file_path = file_dialog.getOpenFileName(self, - file_dir=self._file_dir, - ext_filter=self._ext) + file_path = FileDialog.getOpenFileName(self, + file_dir=self._file_dir, + ext_filter=self._ext) file = file_path[0] or None if file: self.set_value(file) @@ -337,9 +337,9 @@ def set_value(self, value): class PropFileSavePath(PropFilePath): def _on_select_file(self): - file_path = file_dialog.getSaveFileName(self, - file_dir=self._file_dir, - ext_filter=self._ext) + file_path = FileDialog.getSaveFileName(self, + file_dir=self._file_dir, + ext_filter=self._ext) file = file_path[0] or None if file: self.set_value(file) diff --git a/NodeGraphQt/widgets/scene.py b/NodeGraphQt/widgets/scene.py index 6b3e33e9..998b5d0c 100644 --- a/NodeGraphQt/widgets/scene.py +++ b/NodeGraphQt/widgets/scene.py @@ -16,13 +16,12 @@ def __init__(self, parent=None): self.background_color = VIEWER_BG_COLOR self.grid_color = VIEWER_GRID_COLOR self._grid_mode = VIEWER_GRID_LINES - self.setBackgroundBrush(self._bg_qcolor) self.editable = True def __repr__(self): - return '{}.{}(\'{}\')'.format(self.__module__, - self.__class__.__name__, - self.viewer()) + cls_name = str(self.__class__.__name__) + return '<{}("{}") object at {}>'.format( + cls_name, self.viewer(), hex(id(self))) def _draw_text(self, painter, pen): font = QtGui.QFont() @@ -34,6 +33,15 @@ def _draw_text(self, painter, pen): painter.drawText(parent.mapToScene(pos), 'Not Editable') def _draw_grid(self, painter, rect, pen, grid_size): + """ + draws the grid lines in the scene. + + Args: + painter (QtGui.QPainter): painter object. + rect (QtCore.QRectF): rect object. + pen (QtGui.QPen): pen object. + grid_size (int): grid size. + """ left = int(rect.left()) right = int(rect.right()) top = int(rect.top()) @@ -56,6 +64,15 @@ def _draw_grid(self, painter, rect, pen, grid_size): painter.drawLines(lines) def _draw_dots(self, painter, rect, pen, grid_size): + """ + draws the grid dots in the scene. + + Args: + painter (QtGui.QPainter): painter object. + rect (QtCore.QRectF): rect object. + pen (QtGui.QPen): pen object. + grid_size (int): grid size. + """ zoom = self.viewer().get_zoom() if zoom < 0: grid_size = int(abs(zoom) / 0.3 + 1) * grid_size @@ -71,7 +88,8 @@ def _draw_dots(self, painter, rect, pen, grid_size): pen.setWidth(grid_size / 10) painter.setPen(pen) - [painter.drawPoint(int(x), int(y)) for x in range(first_left, right, grid_size) + [painter.drawPoint(int(x), int(y)) + for x in range(first_left, right, grid_size) for y in range(first_top, bottom, grid_size)] def drawBackground(self, painter, rect): @@ -91,7 +109,7 @@ def drawBackground(self, painter, rect): pen = QtGui.QPen(QtGui.QColor(*self.grid_color), 0.65) self._draw_grid(painter, rect, pen, VIEWER_GRID_SIZE) - color = self._bg_qcolor.darker(150) + color = QtGui.QColor(*self._bg_color).darker(150) if zoom < -0.0: color = color.darker(100 - int(zoom * 110)) pen = QtGui.QPen(color, 0.65) @@ -153,4 +171,4 @@ def background_color(self): @background_color.setter def background_color(self, color=(0, 0, 0)): self._bg_color = color - self._bg_qcolor = QtGui.QColor(*self._bg_color) + self.setBackgroundBrush(QtGui.QColor(*self._bg_color)) diff --git a/NodeGraphQt/widgets/tab_search.py b/NodeGraphQt/widgets/tab_search.py index 17b09090..c638be34 100644 --- a/NodeGraphQt/widgets/tab_search.py +++ b/NodeGraphQt/widgets/tab_search.py @@ -1,8 +1,9 @@ #!/usr/bin/python -from .. import QtCore, QtWidgets, QtGui -from .stylesheet import STYLE_TABSEARCH, STYLE_TABSEARCH_LIST, STYLE_QMENU -from collections import OrderedDict import re +from collections import OrderedDict + +from .stylesheet import STYLE_TABSEARCH, STYLE_TABSEARCH_LIST, STYLE_QMENU +from .. import QtCore, QtWidgets, QtGui class TabSearchCompleter(QtWidgets.QCompleter): @@ -113,19 +114,8 @@ def set_nodes(self, node_dict=None): self._completer.setModel(self._model) -def fuzzyFinder(key, collection): - suggestions = [] - pattern = '.*?'.join(key.lower()) - regex = re.compile(pattern) - for item in collection: - match = regex.search(item.lower()) - if match: - suggestions.append((len(match.group()), match.start(), item)) - - return [x for _, _, x in sorted(suggestions)] - - class TabSearchMenuWidget(QtWidgets.QMenu): + search_submitted = QtCore.Signal(str) def __init__(self, node_dict=None): @@ -141,20 +131,21 @@ def __init__(self, node_dict=None): if self._node_dict: self._generate_items_from_node_dict() - searchWidget = QtWidgets.QWidgetAction(self) - searchWidget.setDefaultWidget(self.line_edit) - self.addAction(searchWidget) + search_widget = QtWidgets.QWidgetAction(self) + search_widget.setDefaultWidget(self.line_edit) + self.addAction(search_widget) self.setStyleSheet(STYLE_QMENU) self._actions = {} self._menus = {} self._searched_actions = [] - self.line_edit.returnPressed.connect(self._on_search_submitted) - self.line_edit.textChanged.connect(self._on_text_changed) - self.rebuild = False self._block_submit = False + self.rebuild = False + + self._wire_signals() + def __repr__(self): return '<{} at {}>'.format(self.__class__.__name__, hex(id(self))) @@ -162,6 +153,22 @@ def keyPressEvent(self, event): super(TabSearchMenuWidget, self).keyPressEvent(event) self.line_edit.keyPressEvent(event) + @staticmethod + def _fuzzy_finder(key, collection): + suggestions = [] + pattern = '.*?'.join(key.lower()) + regex = re.compile(pattern) + for item in collection: + match = regex.search(item.lower()) + if match: + suggestions.append((len(match.group()), match.start(), item)) + + return [x for _, _, x in sorted(suggestions)] + + def _wire_signals(self): + self.line_edit.returnPressed.connect(self._on_search_submitted) + self.line_edit.textChanged.connect(self._on_text_changed) + def _on_text_changed(self, text): self._clear_actions() @@ -171,7 +178,7 @@ def _on_text_changed(self, text): self._set_menu_visible(False) - action_names = fuzzyFinder(text, self._actions.keys()) + action_names = self._fuzzy_finder(text, self._actions.keys()) self._searched_actions = [self._actions[name] for name in action_names] self.addActions(self._searched_actions) diff --git a/NodeGraphQt/widgets/viewer.py b/NodeGraphQt/widgets/viewer.py index 5d41784b..428c1da7 100644 --- a/NodeGraphQt/widgets/viewer.py +++ b/NodeGraphQt/widgets/viewer.py @@ -2,7 +2,11 @@ # -*- coding: utf-8 -*- import math +from .dialogs import BaseDialog, FileDialog +from .scene import NodeScene +from .tab_search import TabSearchMenuWidget from .. import QtGui, QtCore, QtWidgets, QtOpenGL +from ..base.menu import BaseMenu from ..constants import (IN_PORT, OUT_PORT, PIPE_LAYOUT_CURVED) from ..qgraphics.node_abstract import AbstractNodeItem @@ -10,10 +14,6 @@ from ..qgraphics.pipe import Pipe, LivePipe from ..qgraphics.port import PortItem from ..qgraphics.slicer import SlicerPipe -from ..base.menu import BaseMenu -from .scene import NodeScene -from .tab_search import TabSearchMenuWidget -from .file_dialog import file_dialog, messageBox ZOOM_MIN = -0.95 ZOOM_MAX = 2.0 @@ -452,6 +452,15 @@ def dragLeaveEvent(self, event): event.ignore() def keyPressEvent(self, event): + """ + Key press event re-implemented to update the states for attributes: + - ALT_state + - CTRL_state + - SHIFT_state + + Args: + event (QtGui.QKeyEvent): key event. + """ self.ALT_state = event.modifiers() == QtCore.Qt.AltModifier self.CTRL_state = event.modifiers() == QtCore.Qt.ControlModifier self.SHIFT_state = event.modifiers() == QtCore.Qt.ShiftModifier @@ -464,6 +473,15 @@ def keyPressEvent(self, event): super(NodeViewer, self).keyPressEvent(event) def keyReleaseEvent(self, event): + """ + Key release event re-implemented to update the states for attributes: + - ALT_state + - CTRL_state + - SHIFT_state + + Args: + event (QtGui.QKeyEvent): key event. + """ self.ALT_state = event.modifiers() == QtCore.Qt.AltModifier self.CTRL_state = event.modifiers() == QtCore.Qt.ControlModifier self.SHIFT_state = event.modifiers() == QtCore.Qt.ShiftModifier @@ -709,7 +727,11 @@ def establish_connection(self, start_port, end_port): @staticmethod def acyclic_check(start_port, end_port): """ - validate the connection so it doesn't loop itself. + Validate the node connections so it doesn't loop itself. + + Args: + start_port (PortItem): port item. + end_port (PortItem): port item. Returns: bool: True if port connection is valid. @@ -741,8 +763,8 @@ def tab_search_toggle(self): state = not self._search_widget.isVisible() if state: rect = self._search_widget.rect() - new_pos = QtCore.QPoint(pos.x() - rect.width() / 2, - pos.y() - rect.height() / 2) + new_pos = QtCore.QPoint(int(pos.x() - rect.width() / 2), + int(pos.y() - rect.height() / 2)) self._search_widget.move(new_pos) self._search_widget.setVisible(state) rect = self.mapToScene(rect).boundingRect() @@ -761,19 +783,35 @@ def context_menus(self): @staticmethod def question_dialog(text, title='Node Graph'): - dlg = messageBox(text, title, QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) - return dlg == QtWidgets.QMessageBox.Yes + """ + Prompt node viewer question dialog widget with "yes", "no" buttons. + + Args: + text (str): dialog text. + title (str): dialog window title. + + Returns: + bool: true if user click yes. + """ + return BaseDialog.question_dialog(text, title) @staticmethod def message_dialog(text, title='Node Graph'): - messageBox(text, title, QtWidgets.QMessageBox.Ok) + """ + Prompt node viewer message dialog widget with "ok" button. + + Args: + text (str): dialog text. + title (str): dialog window title. + """ + BaseDialog.message_dialog(text, title) def load_dialog(self, current_dir=None, ext=None): ext = '*{} '.format(ext) if ext else '' ext_filter = ';;'.join([ 'Node Graph ({}*json)'.format(ext), 'All Files (*)' ]) - file_dlg = file_dialog.getOpenFileName( + file_dlg = FileDialog.getOpenFileName( self, 'Open File', current_dir, ext_filter) file = file_dlg[0] or None return file @@ -783,7 +821,7 @@ def save_dialog(self, current_dir=None, ext=None): ext_type = '.{}'.format(ext) if ext else '.json' ext_map = {'Node Graph ({}*json)'.format(ext_label): ext_type, 'All Files (*)': ''} - file_dlg = file_dialog.getSaveFileName( + file_dlg = FileDialog.getSaveFileName( self, 'Save Session', current_dir, ';;'.join(ext_map.keys())) file_path = file_dlg[0] if not file_path: @@ -795,31 +833,56 @@ def save_dialog(self, current_dir=None, ext=None): return file_path def all_pipes(self): - pipes = [] + """ + Returns all pipe qgraphic items. + + Returns: + list[Pipe]: instances of pipe items. + """ excl = [self._LIVE_PIPE, self._SLICER_PIPE] - for item in self.scene().items(): - if isinstance(item, Pipe) and item not in excl: - pipes.append(item) - return pipes + return [i for i in self.scene().items() + if isinstance(i, Pipe) and i not in excl] def all_nodes(self): - nodes = [] - for item in self.scene().items(): - if isinstance(item, AbstractNodeItem): - nodes.append(item) - return nodes + """ + Returns all node qgraphic items. + + Returns: + list[AbstractNodeItem]: instances of node items. + """ + return [i for i in self.scene().items() + if isinstance(i, AbstractNodeItem)] def selected_nodes(self): - nodes = [item for item in self.scene().selectedItems() \ + """ + Returns selected node qgraphic items. + + Returns: + list[AbstractNodeItem]: instances of node items. + """ + nodes = [item for item in self.scene().selectedItems() if isinstance(item, AbstractNodeItem)] return nodes def selected_pipes(self): - pipes = [item for item in self.scene().selectedItems() \ + """ + Returns selected pipe qgraphic items. + + Returns: + list[Pipe]: pipe items. + """ + pipes = [item for item in self.scene().selectedItems() if isinstance(item, Pipe)] return pipes def selected_items(self): + """ + Return selected graphic items in the scene. + + Returns: + tuple(list[AbstractNodeItem], list[Pipe]): + selected (node items, pipe items). + """ nodes = [] pipes = [] for item in self.scene().selectedItems(): @@ -830,6 +893,13 @@ def selected_items(self): return nodes, pipes def add_node(self, node, pos=None): + """ + Add node item into the scene. + + Args: + node (AbstractNodeItem): node item instance. + pos (tuple or list): node scene position. + """ pos = pos or (self._previous_pos.x(), self._previous_pos.y()) node.pre_init(self, pos) self.scene().addItem(node) @@ -837,6 +907,12 @@ def add_node(self, node, pos=None): @staticmethod def remove_node(node): + """ + Remove node item from the scene. + + Args: + node (AbstractNodeItem): node item instance. + """ if isinstance(node, AbstractNodeItem): node.delete() @@ -889,25 +965,57 @@ def center_selection(self, nodes=None): self.centerOn(rect.center().x(), rect.center().y()) def get_pipe_layout(self): + """ + Returns the pipe layout mode. + + Returns: + int: pipe layout mode. + """ return self._pipe_layout def set_pipe_layout(self, layout): + """ + Sets the pipe layout mode and redraw all pipe items in the scene. + + Args: + layout (int): pipe layout mode. (see the contants module) + """ self._pipe_layout = layout for pipe in self.all_pipes(): pipe.draw_path(pipe.input_port, pipe.output_port) def reset_zoom(self, cent=None): - self._scene_range = QtCore.QRectF(0, 0, self.size().width(), self.size().height()) + """ + Reset the viewer zoom level. + + Args: + cent (QtCore.QPoint): specified center. + """ + self._scene_range = QtCore.QRectF(0, 0, + self.size().width(), + self.size().height()) if cent: self._scene_range.translate(cent - self._scene_range.center()) self._update_scene() def get_zoom(self): + """ + Returns the viewer zoom level. + + Returns: + float: zoom level. + """ transform = self.transform() cur_scale = (transform.m11(), transform.m22()) return float('{:0.2f}'.format(cur_scale[0] - 1.0)) def set_zoom(self, value=0.0): + """ + Set the viewer zoom level. + + Args: + value (float): zoom level + """ if value == 0.0: self.reset_zoom() return