Esempio n. 1
0
    def contextMenuEvent(self, event):
        menu = QMenu(self)
        remove_icon = resources.icon(name='delete')
        remove_action = QAction(remove_icon, 'Remove', menu)
        remove_action.setStatusTip(consts.DELETE_PROJECT_TOOLTIP)
        remove_action.setToolTip(consts.DELETE_PROJECT_TOOLTIP)
        remove_action.triggered.connect(self._on_remove_project)

        folder_icon = resources.icon(name='open_folder', extension='png')
        folder_action = QAction(folder_icon, 'Open in Browser', menu)
        folder_action.setStatusTip(consts.OPEN_PROJECT_IN_EXPLORER_TOOLTIP)
        folder_action.setToolTip(consts.OPEN_PROJECT_IN_EXPLORER_TOOLTIP)
        folder_action.triggered.connect(self._on_open_in_browser)

        image_icon = resources.icon(name='picture', extension='png')
        set_image_action = QAction(image_icon, 'Set Project Image', menu)
        set_image_action.setToolTip(consts.SET_PROJECT_IMAGE_TOOLTIP)
        set_image_action.setStatusTip(consts.SET_PROJECT_IMAGE_TOOLTIP)
        set_image_action.triggered.connect(self._on_set_project_image)

        for action in [
                remove_action, None, folder_action, None, set_image_action
        ]:
            if action is None:
                menu.addSeparator()
            else:
                menu.addAction(action)

        menu.exec_(self.mapToGlobal(event.pos()))
Esempio n. 2
0
    def show_context_menu(self):
        """
        Creates and shows the context menu for the search widget
        :return: QAction
        """

        menu = QMenu(self)
        standard_menu = self.createStandardContextMenu()
        standard_menu.setTitle('Edit')
        menu.addMenu(standard_menu)

        sub_menu = QMenu(menu)
        sub_menu.setTitle('Space Operator')
        menu.addMenu(sub_menu)

        or_action = QAction('OR', menu)
        or_action.setCheckable(True)
        or_callback = partial(self.set_space_operator, 'or')
        or_action.triggered.connect(or_callback)
        if self.space_operator() == 'or':
            or_action.setChecked(True)
        sub_menu.addAction(or_action)

        and_action = QAction('AND', menu)
        and_action.setCheckable(True)
        and_callback = partial(self.set_space_operator, 'and')
        and_action.triggered.connect(and_callback)
        if self.space_operator() == 'and':
            and_action.setChecked(True)
        sub_menu.addAction(and_action)

        action = menu.exec_(QCursor.pos())

        return action
Esempio n. 3
0
    def contextMenuEvent(self, event):
        menu = QMenu(self)
        remove_icon = resources.icon(name='delete')
        remove_action = QAction(remove_icon, 'Remove', menu)
        remove_tooltip = 'Delete selected project'
        remove_action.setStatusTip(remove_tooltip)
        remove_action.setToolTip(remove_tooltip)
        remove_action.triggered.connect(self._on_remove_project)

        folder_icon = resources.icon(name='open_folder', extension='png')
        folder_action = QAction(folder_icon, 'Open in Browser', menu)
        open_project_in_explorer_tooltip = 'Open project folder in explorer'
        folder_action.setStatusTip(open_project_in_explorer_tooltip)
        folder_action.setToolTip(open_project_in_explorer_tooltip)
        folder_action.triggered.connect(self._on_open_in_browser)

        image_icon = resources.icon(name='picture', extension='png')
        set_image_action = QAction(image_icon, 'Set Project Image', menu)
        set_project_image_tooltip = 'Set the image used by the project'
        set_image_action.setToolTip(set_project_image_tooltip)
        set_image_action.setStatusTip(set_project_image_tooltip)
        set_image_action.triggered.connect(self._on_set_project_image)

        for action in [remove_action, None, folder_action, None, set_image_action]:
            if action is None:
                menu.addSeparator()
            else:
                menu.addAction(action)

        menu.exec_(self.mapToGlobal(event.pos()))
Esempio n. 4
0
    def _on_open_menu(self):
        """
        Internal function that is called when the user opens the context menu of the tab menu bar
        :param pos: QPos
        """

        menu = QMenu(self)
        menu.addAction(
            QAction('Rename Current Tab', self, triggered=self._on_rename_tab))
        menu.exec_(QCursor.pos())
Esempio n. 5
0
    def handlePopupMenu(self):
        """
        Called when user right-clicks on a trace
        """
        menu = QMenu()

        selected = self.selectedDataNames()
        if len(selected) != 0:
            menu.addAction(self.actionDeleteTrace)

        menu.exec_(QCursor.pos())
Esempio n. 6
0
    def handlePopupMenu(self):
        """
        Called when user right-clicks on a trace
        """
        menu = QMenu()

        selected = self.selectedDataNames()
        if len(selected) != 0:
            menu.addAction(self.actionDeleteTrace)

        menu.exec_(QCursor.pos())
Esempio n. 7
0
    def _on_show_menu(self):
        """
        Internal callback that shows field menu using the actions from the data
        """

        menu = QMenu(self)

        actions = self.data().get('actions', list())
        for action in actions:
            name = action.get('name', 'No name found')
            enabled = action.get('enabled', True)
            callback = action.get('callback')
            fn = partial(self._action_callback, callback)
            action = menu.addAction(name)
            action.setEnabled(enabled)
            action.triggered.connect(fn)

        point = QCursor.pos()
        point.setX(point.x() + 3)
        point.setY(point.y() + 3)

        self._action_result = None

        menu.exec_(point)

        if self._action_result is not None:
            self.set_value(self._action_result)
Esempio n. 8
0
    def create_settings_menu(self):
        """
        Creates and returns the settings menu for this widget
        :return: QMenu
        """

        menu = QMenu('Item View', self)
        menu.setIcon(resources.icon('eye'))
        menu.addSeparator()

        # copy_text_menu = self.tree_widget().create_copy_text_menu()
        # menu.addMenu(copy_text_menu)
        # menu.addSeparator()

        size_action = action.SliderAction('Size', menu)
        size_action.slider().setMinimum(10)
        size_action.slider().setMaximum(200)
        size_action.slider().setValue(self.zoom_amount())
        size_action.slider().valueChanged.connect(self.set_zoom_amount)
        menu.addAction(size_action)

        border_action = action.SliderAction('Border', menu)
        border_action.slider().setMinimum(0)
        border_action.slider().setMaximum(20)
        border_action.slider().setValue(self.padding())
        border_action.slider().valueChanged.connect(self.set_padding)
        menu.addAction(border_action)

        spacing_action = action.SliderAction('Spacing', menu)
        spacing_action.slider().setMinimum(self.DEFAULT_MIN_SPACING)
        spacing_action.slider().setMaximum(self.DEFAULT_MAX_SPACING)
        spacing_action.slider().setValue(self.spacing())
        spacing_action.slider().valueChanged.connect(self.set_spacing)
        menu.addAction(spacing_action)

        menu.addAction(action.SeparatorAction('Label Options', menu))

        for option in item.LabelDisplayOption.values():
            option_action = QAction(option.title(), menu)
            option_action.setCheckable(True)
            option_action.setChecked(option == self.label_display_option())
            option_callback = partial(self.set_label_display_option, option)
            option_action.triggered.connect(option_callback)
            menu.addAction(option_action)

        return menu
Esempio n. 9
0
    def _create_header_menu(self, column):
        """
        Internal function that creates a new header menu
        :param column, iht
        :return: QMenu
        """

        menu = QMenu(self)
        label = self.label_from_column(column)
        hide_action = menu.addAction('Hide "{}"'.format(label))
        hide_callback = partial(self.setColumnHidden, column, True)
        hide_action.triggered.connect(hide_callback)
        menu.addSeparator()
        resize_action = menu.addAction('Resize to Contents')
        resize_callback = partial(self.resizeColumnToContents, column)
        resize_action.triggered.connect(resize_callback)

        return menu
Esempio n. 10
0
    def create_copy_text_menu(self):
        """
        Creates a menu to cpoy the selected item data to the clipboard
        :return: QMenu
        """

        menu = QMenu('Copy Text', self)
        if self.selectedItems():
            for column in range(self.columnCount()):
                label = self.label_from_column(column)
                action = menu.addAction(label)
                action_callback = partial(self.copy_text, column)
                action.triggered.connect(action_callback)
        else:
            action = menu.addAction('No items selected')
            action.setEnabled(False)

        return menu
Esempio n. 11
0
class ExternalCodeList(QListWidget):

    directoriesChanged = Signal(object)

    def __init__(self, parent=None):
        super(ExternalCodeList, self).__init__(parent)

        self.setAlternatingRowColors(True)
        self.setSelectionMode(self.NoSelection)
        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self._on_item_menu)

        self._create_context_menu()

    def get_directories(self):
        count = self.count()
        found = list()
        if not count:
            return found
        for i in range(count):
            item = self.item(i)
            if not item:
                continue
            found.append(str(item.text()))

        return found

    def refresh(self, directories):
        directories = python.force_list(directories)
        self.clear()
        for diretory_found in directories:
            name = diretory_found
            if not path_utils.is_dir(diretory_found):
                name = 'Directory Not Valid! {}'.format(diretory_found)
            item = QListWidgetItem()
            item.setText(name)
            item.setSizeHint(QSize(20, 25))
            self.addItem(item)

    def _create_context_menu(self):
        self._context_menu = QMenu()
        remove_action = self._context_menu.addAction('Remove')
        remove_action.setIcon(resources.icon('trash'))
        remove_action.triggered.connect(self._on_remove_action)

    def _on_item_menu(self, pos):
        item = self.itemAt(pos)
        if not item:
            return
        self._context_menu.exec_(self.viewport().mapToGlobal(pos))

    def _on_remove_action(self):
        index = self.currentIndex()
        self.takeItem(index.row())
        self.directoriesChanged.emit(self.get_directories())
Esempio n. 12
0
    def create_items_menu(self, items=None):
        """
        Creates the item menu for the given items
        :param items: list(LibraryItem)
        :return: QMenu
        """

        item = items or self.selected_item()
        menu = QMenu(self)
        if item:
            try:
                item.context_menu(menu)
            except Exception as e:
                LOGGER.exception(e)
        else:
            item_action = QAction(menu)
            item_action.setText('No Item selected')
            item_action.setDisabled(True)
            menu.addAction(item_action)

        return menu
Esempio n. 13
0
class implicitPinCall(Node):
    def __init__(self, name, graph):
        super(implicitPinCall, self).__init__(name, graph)
        self.inExec = self.addInputPin('inp', DataTypes.Exec, self.compute, hideLabel=True)
        self.uidInp = self.addInputPin('UUID', DataTypes.String)
        self.outExec = self.addOutputPin('out', DataTypes.Exec, hideLabel=True)
        self.menu = QMenu()
        self.actionFindPin = self.menu.addAction('Find pin')
        self.actionFindPin.triggered.connect(self.OnFindPin)

    def contextMenuEvent(self, event):
        self.menu.exec_(event.screenPos())

    @staticmethod
    def pinTypeHints():
        return {'inputs': [DataTypes.String, DataTypes.Exec], 'outputs': [DataTypes.Exec]}

    @staticmethod
    def category():
        return 'FlowControl'

    @staticmethod
    def keywords():
        return []

    @staticmethod
    def description():
        return 'Implicit execution pin call by provided <a href="https://ru.wikipedia.org/wiki/UUID"> uuid</a>.\nUse this when pins are far from each other.'

    def OnFindPin(self):
        uidStr = self.uidInp.getData()
        if len(uidStr) == 0:
            return
        try:
            uid = uuid.UUID(uidStr)
            pin = self.graph().pins[uid]
            self.graph().centerOn(pin)
            pin.highlight()
        except Exception as e:
            print(e)
            pass

    def compute(self):
        uidStr = self.uidInp.getData()
        if len(uidStr) == 0:
            return
        uid = uuid.UUID(uidStr)
        if uid in self.graph().pins:
            pin = self.graph().pins[uid]
            if not pin.hasConnections():
                pin.call()
Esempio n. 14
0
    def _create_hide_column_menu(self):
        """
        Internal function that creates the hide column menu
        :return: QMenu
        """

        menu = QMenu('Show/Hide Column', self)
        show_all_action = menu.addAction('Show All')
        show_all_action.triggered.connect(self.show_all_columns)
        hide_all_action = menu.addAction('Hide All')
        hide_all_action.triggered.connect(self.hide_all_columns)
        menu.addSeparator()
        for column in range(self.columnCount()):
            label = self.label_from_column(column)
            is_hidden = self.isColumnHidden(column)
            action = menu.addAction(label)
            action.setCheckable(True)
            action.setChecked(not is_hidden)
            action_callback = partial(self.setColumnHidden, column,
                                      not is_hidden)
            action.triggered.connect(action_callback)

        return menu
Esempio n. 15
0
class makeArray(Node):
    def __init__(self, name, graph):
        super(makeArray, self).__init__(name, graph)
        self.out0 = self.addOutputPin('Array', DataTypes.Array)
        self.menu = QMenu()
        self.action = self.menu.addAction('add input')
        self.action.triggered.connect(self.addInPin)

    def addInPin(self):
        if len(self.inputs) == 0:
            p = self.addInputPin(str(len(self.inputs)),
                                 DataTypes.Any,
                                 constraint="1")
        else:
            p = self.addInputPin(str(len(self.inputs)),
                                 DataTypes.Any,
                                 constraint="1")
            p.setType(self.inputs.items()[0][1])
        p.setDeletable()
        pinAffects(p, self.out0)

    def contextMenuEvent(self, event):
        self.menu.exec_(event.screenPos())

    @staticmethod
    def pinTypeHints():
        return {'inputs': supportedDataTypesList, 'outputs': [DataTypes.Array]}

    @staticmethod
    def category():
        return 'GenericTypes'

    @staticmethod
    def keywords():
        return []

    @staticmethod
    def description():
        return 'Genertic array'

    def postCreate(self, jsonTemplate):
        Node.postCreate(self, jsonTemplate)

        # restore dynamically created inputs
        for inp in jsonTemplate['inputs']:
            p = PinWidgetBase.deserialize(self, inp)
            pinAffects(p, self.out0)

    def compute(self):
        self.out0.setData(list([i.getData() for i in self.inputs.values()]))
Esempio n. 16
0
class sequence(Node):
    def __init__(self, name, graph):
        super(sequence, self).__init__(name, graph)
        self.inExecPin = self.addInputPin('inExec',
                                          DataTypes.Exec,
                                          self.compute,
                                          hideLabel=True)
        self.menu = QMenu()
        self.action = self.menu.addAction('add pin')
        self.action.triggered.connect(self.addOutPin)

    def addOutPin(self):
        p = self.addOutputPin(str(len(self.outputs)), DataTypes.Exec)
        pinAffects(self.inExecPin, p)

    def contextMenuEvent(self, event):
        self.menu.exec_(event.screenPos())

    @staticmethod
    def pinTypeHints():
        return {'inputs': [DataTypes.Exec], 'outputs': [DataTypes.Exec]}

    @staticmethod
    def category():
        return 'FlowControl'

    @staticmethod
    def keywords():
        return []

    @staticmethod
    def description():
        return 'The Sequence node allows for a single execution pulse to trigger a series of events in order. The node may have any number of outputs, all of which get called as soon as the Sequence node receives an input. They will always get called in order, but without any delay. To a typical user, the outputs will likely appear to have been triggered simultaneously.'

    def postCreate(self, jsonTemplate):
        Node.postCreate(self, jsonTemplate)

        # restore dynamically created  outputs
        if len(jsonTemplate['outputs']) == 0:
            self.addOutPin()
            self.addOutPin()
        else:
            for out in jsonTemplate['outputs']:
                PinWidgetBase.deserialize(self, out)

    def compute(self):
        for out in self.outputs.values():
            out.call()
Esempio n. 17
0
class InputWidgetRaw(QWidget, IInputWidget):
    """
    This type of widget can be used as a base class for complex ui generated by designer
    """
    def __init__(self,
                 parent=None,
                 dataSetCallback=None,
                 defaultValue=None,
                 **kwds):
        super(InputWidgetRaw, self).__init__(parent=parent, **kwds)
        self._defaultValue = defaultValue
        # fuction with signature void(object)
        # this will set data to pin
        self.dataSetCallback = dataSetCallback
        self._widget = None
        self._menu = QMenu()
        self.actionReset = self._menu.addAction("ResetValue")
        self.actionReset.triggered.connect(self.onResetValue)

    def setWidgetValueNoSignals(self, value):
        self.blockWidgetSignals(True)
        self.setWidgetValue(value)
        self.blockWidgetSignals(False)

    def setWidget(self, widget):
        self._widget = widget

    def getWidget(self):
        assert (self._widget is not None)
        return self._widget

    def onResetValue(self):
        self.setWidgetValue(self._defaultValue)

    def setWidgetValue(self, value):
        '''to widget'''
        pass

    def widgetValueUpdated(self, value):
        '''from widget'''
        pass

    def contextMenuEvent(self, event):
        self._menu.exec_(event.globalPos())
Esempio n. 18
0
    def handlePopupMenu(self):
        """
        Called when user right-clicks on the sources tree
        """
        menu = QMenu()
        menu.addAction(self.actionAdd)

        selected = self.main.treeView.selectedItems()
        if len(selected) != 0:
            if 'config' in selected[0].source.__dict__:
                menu.addAction(self.actionConfig)
            menu.addAction(self.actionDelete)

        menu.exec_(QCursor.pos())
Esempio n. 19
0
    def handlePopupMenu(self):
        """
        Called when user right-clicks on the sources tree
        """
        menu = QMenu()
        menu.addAction(self.actionAdd)

        selected = self.main.treeView.selectedItems()
        if len(selected) != 0:
            if "config" in selected[0].source.__dict__:
                menu.addAction(self.actionConfig)
            menu.addAction(self.actionDelete)

        menu.exec_(QCursor.pos())
Esempio n. 20
0
class makeM44Array(Node):
    def __init__(self, name, graph):
        super(makeM44Array, self).__init__(name, graph)
        self.out0 = self.addOutputPin('matrices', DataTypes.Array)
        self.menu = QMenu()
        self.action = self.menu.addAction('add input')
        self.action.triggered.connect(self.addInPin)

    def addInPin(self):
        p = self.addInputPin(str(len(self.inputs)), DataTypes.Matrix44)
        pinAffects(p, self.out0)

    def contextMenuEvent(self, event):
        self.menu.exec_(event.screenPos())

    @staticmethod
    def pinTypeHints():
        return {'inputs': [DataTypes.Matrix44], 'outputs': [DataTypes.Array]}

    @staticmethod
    def category():
        return 'GenericTypes'

    @staticmethod
    def keywords():
        return []

    @staticmethod
    def description():
        return 'Array of matrix44.'

    def postCreate(self, jsonTemplate):
        Node.postCreate(self, jsonTemplate)

        # restore dynamically created inputs
        for inp in jsonTemplate['inputs']:
            p = PinWidgetBase.deserialize(self, inp)
            pinAffects(p, self.out0)

    def compute(self):
        self.out0.setData(list([i.getData() for i in self.inputs.values()]))
Esempio n. 21
0
class pythonNode(Node, NodeBase):
    def __init__(self, name, graph):
        super(pythonNode, self).__init__(name, graph)
        self.menu = QMenu()
        self.actionEdit = self.menu.addAction('edit')
        self.actionEdit.triggered.connect(self.openEditor)
        self.actionEdit.setIcon(QtGui.QIcon(':/icons/resources/py.png'))
        self.actionExport = self.menu.addAction('export')
        self.actionExport.triggered.connect(self.export)
        self.editorUUID = None
        self.bKillEditor = True
        self.label().icon = QtGui.QImage(':/icons/resources/py.png')
        self.currentComputeCode = Node.jsonTemplate()['computeCode']
        self.color = Colors.NodeNameRect

    @staticmethod
    def pinTypeHints():
        return {'inputs': [], 'outputs': []}

    def computeCode(self):
        return self.currentComputeCode

    def openEditor(self):
        self.editorUUID = uuid.uuid4()
        self.graph().codeEditors[self.editorUUID] = WCodeEditor(
            self.graph(), self, self.editorUUID)
        self.graph().codeEditors[self.editorUUID].show()

    def kill(self):
        if self.editorUUID in self.graph().codeEditors:
            ed = self.graph().codeEditors.pop(self.editorUUID)
            ed.deleteLater()
        Node.kill(self)

    @staticmethod
    def category():
        return 'Utils'

    def postCreate(self, jsonTemplate):
        # restore compute
        self.currentComputeCode = jsonTemplate['computeCode']
        foo = WCodeEditor.wrapCodeToFunction('compute',
                                             jsonTemplate['computeCode'])
        exec(foo)
        self.compute = MethodType(compute, self, Node)

        # restore pins
        for inpJson in jsonTemplate['inputs']:
            pin = None
            if inpJson['dataType'] == DataTypes.Exec:
                pin = self.addInputPin(inpJson['name'], inpJson['dataType'],
                                       self.compute, inpJson['bLabelHidden'])
                pin.uid = uuid.UUID(inpJson['uuid'])
            else:
                pin = self.addInputPin(inpJson['name'], inpJson['dataType'],
                                       None, inpJson['bLabelHidden'])
                pin.uid = uuid.UUID(inpJson['uuid'])
            pin.setData(inpJson['value'])
        for outJson in jsonTemplate['outputs']:
            pin = self.addOutputPin(outJson['name'], outJson['dataType'], None,
                                    outJson['bLabelHidden'])
            pin.uid = uuid.UUID(outJson['uuid'])
            pin.setData(outJson['value'])

        self.bCallable = self.isCallable()

        Node.postCreate(self, jsonTemplate)

        # restore node label
        self.label().setPlainText(jsonTemplate['meta']['label'])

    def wrapCodeToFunction(self, fooName, code):
        foo = "def {}(self):".format(fooName)
        lines = [i for i in code.split('\n') if len(i) > 0]
        for line in lines:
            foo += '\n\t\t{}'.format(line)
        return foo

    def export(self):
        # restore compute
        #print self.currentComputeCode
        foo = self.wrapCodeToFunction('compute', self.currentComputeCode)

        inputs = []
        portString = ""
        for obj in self.inputs.values():
            if obj.dataType == DataTypes.Exec:
                comp = "self.compute"
            else:
                comp = "None"
            portString += """\n        self.{0} = self.addInputPin("{0}", {1},{2},hideLabel={3})""".format(
                obj.name, str(obj.dataType), comp, obj.bLabelHidden)

        for obj in self.outputs.values():
            comp = "None"
            portString += """\n        self.{0} = self.addOutputPin("{0}", {1},hideLabel={2})""".format(
                obj.name, str(obj.dataType), obj.bLabelHidden)

        self._implementPlugin(self.name, portString, foo)

    def contextMenuEvent(self, event):
        self.menu.exec_(event.screenPos())

    def mouseDoubleClickEvent(self, event):
        #Node.mouseDoubleClickEvent( event)
        self.OnDoubleClick(self.mapToScene(event.pos()))
        event.accept()

    def OnDoubleClick(self, pos):
        self.openEditor()

    @staticmethod
    def keywords():
        return ['Code', 'Expression']

    @staticmethod
    def description():
        return 'default description'

    def _implementPlugin(self, name, ports, computeCode):
        from . import _nodeClasses
        from ..FunctionLibraries import _foos
        from ..SubGraphs import _subgraphClasses
        from .. import SubGraphs
        existing_nodes = [n for n in _nodeClasses]
        existing_nodes += [n for n in _foos]
        existing_nodes += [n for n in _subgraphClasses]
        from .. import Nodes
        #file_path = "{0}/{1}.py".format(os.path.dirname(__file__), name)
        #existing_nodes = [n.split(".")[0] for n in os.listdir(os.path.dirname(__file__)) if n.endswith(".py") and "__init__" not in n]
        name_filter = "Node Files (*.py)"
        pth = QFileDialog.getSaveFileName(filter=name_filter)
        if not pth == '':
            if type(pth) in [tuple, list]:
                file_path = pth[0]
            else:
                file_path = pth
            path, name = os.path.split(file_path)
            name, ext = os.path.splitext(name)
            if name in existing_nodes:
                print("[ERROR] Node {0} already exists! Chose another name".
                      format(name))
                return

            NodeTemplate = """from ..Core.AbstractGraph import *
from ..Core.Settings import *
from ..Core import Node

class {0}(Node):
    def __init__(self, name, graph):
        super({0}, self).__init__(name, graph){1}

        for i in self.inputs.values():
            for o in self.outputs.values():
                pinAffects(i, o)        
    @staticmethod
    def pinTypeHints():
        '''
            used by nodebox to suggest supported pins
            when drop wire from pin into empty space
        '''
        return {{'inputs': [DataTypes.Bool], 'outputs': [DataTypes.Bool]}}

    @staticmethod
    def category():
        '''
            used by nodebox to place in tree
            to make nested one - use '|' like this ( 'CatName|SubCatName' )
        '''
        return 'Common'

    @staticmethod
    def keywords():
        '''
            used by nodebox filter while typing
        '''
        return []

    @staticmethod
    def description():
        '''
            used by property view and node box widgets
        '''
        return 'default description'
    {2}    
""".format(name, ports, computeCode)

            # write to file. delete older if needed
            with open(file_path, "wb") as f:
                f.write(NodeTemplate)
            print(
                "[INFO] Node {0} been created.\nIn order to appear in node box, restart application."
                .format(name))
            if OS_PLATFORM == 'Windows':
                os.system(file_path)
            else:
                os.system(file_path)
            reload(Nodes)
            Nodes._getClasses()
Esempio n. 22
0
class UIPinBase(QGraphicsWidget):
    '''
    Pin ui wrapper
    '''

    # Event called when pin is connected
    OnPinConnected = QtCore.Signal(object)
    # Event called when pin is disconnected
    OnPinDisconnected = QtCore.Signal(object)
    # Event called when data been set
    dataBeenSet = QtCore.Signal(object)
    # Event called when pin name changes
    displayNameChanged = QtCore.Signal(str)
    OnPinChanged = QtCore.Signal(object)
    OnPinDeleted = QtCore.Signal(object)

    def __init__(self, owningNode, raw_pin):
        super(UIPinBase, self).__init__()
        self.setGraphicsItem(self)
        self.setFlag(QGraphicsWidget.ItemSendsGeometryChanges)
        self.setCacheMode(self.DeviceCoordinateCache)
        self.setAcceptHoverEvents(True)
        self.setZValue(1)
        self.setParentItem(owningNode)

        self.UiNode = weakref.ref(owningNode)
        self._rawPin = raw_pin
        self._rawPin.serializationHook.connect(self.serializationHook)
        self._rawPin.containerTypeChanged.connect(self.onContainerTypeChanged)
        self._displayName = self._rawPin.name
        self._rawPin.setWrapper(self)
        self._rawPin.killed.connect(self.kill)
        self._rawPin.nameChanged.connect(self.setDisplayName)

        # Context menu for pin
        self.menu = QMenu()
        self.menu.addAction("Rename").triggered.connect(self.onRename)
        self.menu.addAction("Remove").triggered.connect(self._rawPin.kill)
        self.actionDisconnect = self.menu.addAction('Disconnect all')
        self.actionDisconnect.triggered.connect(self._rawPin.disconnectAll)
        self.actionResetValue = self.menu.addAction("Reset value")
        self.actionResetValue.triggered.connect(self.resetToDefault)
        if self._rawPin._structure == PinStructure.Multi:
            self.menu.addAction("changeStructure").triggered.connect(
                self.selectStructure)

        # GUI
        self._font = QtGui.QFont("Consolas")
        self._font.setPointSize(6)
        self.pinSize = 6
        self.hovered = False
        self.bLabelHidden = False
        self._pinColor = QtGui.QColor(*self._rawPin.color())
        self._labelColor = QtCore.Qt.white
        self._execPen = QtGui.QPen(Colors.White, 0.5, QtCore.Qt.SolidLine)
        self._dirty_pen = QtGui.QPen(Colors.DirtyPen, 0.5, QtCore.Qt.DashLine,
                                     QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin)

        self.uiConnectionList = []

        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum)
        self.pinCircleDrawOffset = QtCore.QPointF()

    @property
    def labelColor(self):
        return self._labelColor

    @labelColor.setter
    def labelColor(self, value):
        self._labelColor = value

    def pinCenter(self):
        """Point relative to pin widget, where circle is drawn."""

        frame = QtCore.QRectF(QtCore.QPointF(0, 0), self.geometry().size())
        halfPinSize = self.pinSize / 2
        pinX = 0 + halfPinSize + self.pinSize - halfPinSize
        pinY = (frame.height() / 2)
        if self.direction == PinDirection.Output:
            pinX = frame.width() - self.pinSize + halfPinSize
        result = QtCore.QPointF(pinX, pinY)
        if self.owningNode().collapsed:
            labelHeight = self.owningNode().labelHeight
            labelHeight += self.owningNode().nodeLayout.spacing()
            if self.direction == PinDirection.Input:
                result = self.mapFromParent(QtCore.QPointF(0, labelHeight))
            if self.direction == PinDirection.Output:
                result = self.mapFromParent(
                    QtCore.QPointF(
                        self.owningNode().sizeHint(None, None).width(),
                        labelHeight))
        return result

    def onContainerTypeChanged(self, *args, **kwargs):
        # underlined pin is changed to list or dict
        # update to redraw shape
        self.update()

    def setLabel(self, labelItem):
        if self._label is None:
            self._label = weakref.ref(labelItem)

    def displayName(self):
        return self._displayName

    def setDisplayName(self, displayName):
        if displayName != self._displayName:
            self._displayName = displayName
            self.displayNameChanged.emit(self._displayName)
            self.prepareGeometryChange()
            self.updateGeometry()
            self.update()

    def jsonEncoderClass(self):
        return self._rawPin.jsonEncoderClass()

    def jsonDecoderClass(self):
        return self._rawPin.jsonDecoderClass()

    @property
    def owningNode(self):
        return self.UiNode

    @property
    def constraint(self):
        return self._rawPin.constraint

    @property
    def isAny(self):
        return self._rawPin.isAny()

    def setMenuItemEnabled(self, actionName, bEnabled):
        for action in self.menu.actions():
            if action.text() == actionName:
                if bEnabled != action.isEnabled() and action.isVisible():
                    action.setEnabled(bEnabled)
                    action.setVisible(bEnabled)

    def syncRenamable(self):
        renamingEnabled = self._rawPin.optionEnabled(
            PinOptions.RenamingEnabled)
        # self._label()._isEditable = renamingEnabled
        self.setMenuItemEnabled("Rename", renamingEnabled)

    def onRename(self):
        name, confirmed = QInputDialog.getText(None, "Rename",
                                               "Enter new pin name")
        if confirmed and name != self.name and name != "":
            uniqueName = self._rawPin.owningNode().graph(
            ).graphManager.getUniqName(name)
            self.setName(uniqueName)
            self.setDisplayName(uniqueName)
            self.owningNode().invalidateNodeLayouts()
            self.owningNode().updateNodeShape()

    def syncDynamic(self):
        self.setMenuItemEnabled("Remove",
                                self._rawPin.optionEnabled(PinOptions.Dynamic))

    @property
    def dirty(self):
        return self._rawPin.dirty

    @dirty.setter
    def dirty(self, value):
        self._rawPin.dirty = value

    def resetToDefault(self):
        self.setData(self.defaultValue())

    def defaultValue(self):
        return self._rawPin.defaultValue()

    def currentData(self):
        return self._rawPin.currentData()

    @property
    def name(self):
        return self._rawPin.name

    def getName(self):
        return self._rawPin.getName()

    def hasConnections(self):
        return self._rawPin.hasConnections()

    def setClean(self):
        self._rawPin.setClean()

    def setDirty(self):
        self._rawPin.setDirty()

    @property
    def _data(self):
        return self._rawPin._data

    @_data.setter
    def _data(self, value):
        self._rawPin._data = value

    @property
    def affects(self):
        return self._rawPin.affects

    @property
    def direction(self):
        return self._rawPin.direction

    @property
    def affected_by(self):
        return self._rawPin.affected_by

    def supportedDataTypes(self):
        return self._rawPin.supportedDataTypes()

    @property
    def connections(self):
        return self.uiConnectionList

    @property
    def uid(self):
        return self._rawPin._uid

    @uid.setter
    def uid(self, value):
        self._rawPin._uid = value

    def color(self):
        return self._pinColor

    def setName(self, newName, force=False):
        return self._rawPin.setName(newName, force=force)

    def setData(self, value):
        self._rawPin.setData(value)
        self.dataBeenSet.emit(value)

    def getData(self):
        return self._rawPin.getData()

    def highlight(self):
        # TODO: draw svg arrow instead
        self.bAnimate = True
        t = QtCore.QTimeLine(900, self)
        t.setFrameRange(0, 100)
        t.frameChanged[int].connect(self.animFrameChanged)
        t.finished.connect(self.animationFinished)
        t.start()

    def call(self):
        self._rawPin.call()
        for e in self.connections:
            e.highlight()

    def kill(self, *args, **kwargs):
        """this will be called after raw pin is deleted
        """
        scene = self.scene()
        if scene is None:
            # already deleted
            del self
            return

        if self._rawPin.direction == PinDirection.Input:
            self.owningNode().inputsLayout.removeItem(self)
        else:
            self.owningNode().outputsLayout.removeItem(self)

        self.OnPinDeleted.emit(self)
        scene.removeItem(self)
        self.owningNode().updateNodeShape()

    def assignRawPin(self, rawPin):
        if rawPin is not self._rawPin:
            self._rawPin = rawPin
            self.call = rawPin.call
            self._rawPin.setWrapper(self)
            self._pinColor = QtGui.QColor(*self._rawPin.color())

    def serializationHook(self, *args, **kwargs):
        data = {}
        data['bLabelHidden'] = self.bLabelHidden
        data['displayName'] = self.displayName()
        return data

    def serialize(self):
        return self._rawPin.serialize()

    def getContainer(self):
        return self._container

    def isExec(self):
        return self._rawPin.isExec()

    @property
    def dataType(self):
        return self._rawPin.dataType

    def sizeHint(self, which, constraint):
        height = QtGui.QFontMetrics(self._font).height()
        width = self.pinSize * 2
        if not self.bLabelHidden:
            width += QtGui.QFontMetrics(self._font).width(self.displayName())
        if not self.isVisible():
            width = 0
            height = 0
        return QtCore.QSizeF(width, height)

    def shape(self):
        path = QtGui.QPainterPath()
        path.addEllipse(self.boundingRect())
        return path

    def isList(self):
        return self._rawPin.isList()

    def isArray(self):
        return self._rawPin.isArray()

    def paint(self, painter, option, widget):
        if self.isArray():
            PinPainter.asArrayPin(self, painter, option, widget)
        else:
            PinPainter.asValuePin(self, painter, option, widget)

    def contextMenuEvent(self, event):
        self.menu.exec_(event.screenPos())

    def getLayout(self):
        if self.direction == PinDirection.Input:
            return self.owningNode().inputsLayout
        else:
            return self.owningNode().outputsLayout

    def hoverEnterEvent(self, event):
        super(UIPinBase, self).hoverEnterEvent(event)
        self.update()
        self.hovered = True
        hoverMessage = "Data: {0}\r\nDirty: {1}".format(
            str(self._rawPin.currentData()), self._rawPin.dirty)
        self.setToolTip(hoverMessage)
        event.accept()

    def hoverLeaveEvent(self, event):
        super(UIPinBase, self).hoverLeaveEvent(event)
        self.update()
        self.hovered = False

    def pinConnected(self, other):
        self.OnPinConnected.emit(other)
        self.update()

    def pinDisconnected(self, other):
        self.OnPinDisconnected.emit(other)
        self.update()

    def selectStructure(self):
        item, ok = QInputDialog.getItem(None, "", "",
                                        ([i.name for i in list(PinStructure)]),
                                        0, False)
        if ok and item:
            self._rawPin.changeStructure(PinStructure[item], True)
Esempio n. 23
0
class MultiSelectComboBox(QFrame):
    '''
    This class offers comboBox to select multiple objects at once
    '''
    selectionDone = Signal(list)

    def __init__(self, parent=None, msg='--Select--', triState=False):
        super(MultiSelectComboBox, self).__init__(parent)
        loadUi(osp.join(uiPath, 'multiSelectComboBox.ui'), self)

        self.triState = triState
        self.msg = msg
        self.menu = QMenu(self)
        self.menu.setStyleSheet("QMenu { menu-scrollable: 1; }")
        self.button.setMenu(self.menu)

        self.menu.hideEvent = self.menuHideEvent
        self.setHintText(self.msg)

    def addDefaultWidgets(self):
        button = QPushButton('Invert Selection', self)
        button.clicked.connect(self.invertSelection)
        checkableAction = QWidgetAction(self.menu)
        checkableAction.setDefaultWidget(button)
        self.menu.addAction(checkableAction)
        btn = self.addItem('Select All')
        btn.clicked.connect(lambda: self.toggleAll(btn))
        self.menu.addSeparator()

    def invertSelection(self):
        for cBox in self.getWidgetItems():
            cBox.setChecked(not cBox.isChecked())
        self.toggleSelectAllButton()

    def toggleAll(self, btn):
        for cBox in self.getWidgetItems():
            cBox.setCheckState(btn.checkState())

    def toggleSelectAllButton(self):
        flag = True
        for cBox in self.getWidgetItems():
            if not cBox.isChecked():
                flag = False
                break
        for action in self.menu.actions():
            if type(action) == QAction:
                continue
            widget = action.defaultWidget()
            if widget.text() == 'Select All':
                widget.setChecked(flag)

    def setHintText(self, text):
        self.button.setText(text)

    def menuHideEvent(self, event):
        items = self.getSelectedItems()
        if items:
            s = ''
            if len(items) > 2:
                s = ',...'
            self.button.setText(','.join(items[:2]) + s)
        else:
            self.setHintText(self.msg)
        if event:
            QMenu.hideEvent(self.menu, event)
        self.selectionDone.emit(items)

    def getSelectedItems(self):
        return [
            cBox.text().strip() for cBox in self.getWidgetItems()
            if cBox.isChecked()
        ]

    def getState(self):
        return {
            cBox.text().strip(): cBox.checkState()
            for cBox in self.getWidgetItems()
        }

    def getWidgetItems(self):
        return [
            cBox for cBox in [
                action.defaultWidget() for action in self.menu.actions()
                if not type(action) is QAction
            ] if cBox.text().strip() != 'Select All'
            and cBox.text().strip() != 'Invert Selection'
        ]

    def getItems(self):
        return [cBox.text().strip() for cBox in self.getWidgetItems()]

    def addItem(self, item, selected=False):
        checkBox = QCheckBox(item, self.menu)
        checkBox.setTristate(self.triState)
        if selected:
            checkBox.setChecked(True)
        checkableAction = QWidgetAction(self.menu)
        checkableAction.setDefaultWidget(checkBox)
        self.menu.addAction(checkableAction)
        return checkBox

    def appendItems(self, items, selected=None):
        self.addItems(items, selected=selected, clear=False)

    def addItems(self, items, selected=None, clear=True):
        items = sorted(items)
        if clear:
            self.clearItems()
        self.addDefaultWidgets()
        for item in items:
            sel = False
            if selected and item in selected:
                sel = True
            btn = self.addItem(item, sel)
            btn.clicked.connect(self.toggleSelectAllButton)
        if selected:
            self.menuHideEvent(None)

    def clearItems(self):
        self.menu.clear()
        self.setHintText(self.msg)
Esempio n. 24
0
class MayaShelf(abstract_shelf.AbstractShelf, object):

    def __init__(self, name='MayaShelf', label_background=(0, 0, 0, 0), label_color=(0.9, 0.9, 0.9),
                 category_icon=None, enable_labels=True):
        super(MayaShelf, self).__init__(name=name, label_background=label_background, label_color=label_color,
                                        category_icon=category_icon, enable_labels=enable_labels)

    @staticmethod
    def add_menu_item(parent, label, command='', icon=''):
        """
        Adds a menu item with the given attributes
        :param parent:
        :param label:
        :param command:
        :param icon:
        :return:
        """

        return maya.cmds.menuItem(parent=parent, label=label, command=command, image=icon or '')

    @staticmethod
    def add_sub_menu(parent, label, icon=None):
        """
        Adds a sub menu item with the given label and icon to the given parent popup menu
        :param parent:
        :param label:
        :param icon:
        :return:
        """

        return maya.cmds.menuItem(parent=parent, label=label, icon=icon or '', subMenu=True)

    def create(self, delete_if_exists=True):
        """
        Creates a new shelf
        """

        if delete_if_exists:
            if gui.shelf_exists(shelf_name=self._name):
                gui.delete_shelf(shelf_name=self._name)
        else:
            assert not gui.shelf_exists(self._name), 'Shelf with name {} already exists!'.format(self._name)

        self._name = gui.create_shelf(name=self._name)

        # ========================================================================================================

        self._category_btn = QPushButton('')
        if self._category_icon:
            self._category_btn.setIcon(self._category_icon)
        self._category_btn.setIconSize(QSize(18, 18))
        self._category_menu = QMenu(self._category_btn)
        self._category_btn.setStyleSheet(
            'QPushButton::menu-indicator {image: url(myindicator.png);'
            'subcontrol-position: right center;subcontrol-origin: padding;left: -2px;}')
        self._category_btn.setMenu(self._category_menu)
        self._category_lbl = QLabel('MAIN')
        self._category_lbl.setAlignment(Qt.AlignCenter)
        font = self._category_lbl.font()
        font.setPointSize(6)
        self._category_lbl.setFont(font)
        menu_ptr = maya.OpenMayaUI.MQtUtil.findControl(self._name)
        menu_widget = qtutils.wrapinstance(menu_ptr, QWidget)
        menu_widget.layout().addWidget(self._category_btn)
        menu_widget.layout().addWidget(self._category_lbl)

        self.add_separator()

    def set_as_active(self):
        """
        Sets this shelf as active shelf in current DCC session
        """

        main_shelf = maya.mel.eval("$_tempVar = $gShelfTopLevel")
        maya.cmds.tabLayout(main_shelf, edit=True, selectTab=self._name)

    def add_button(self, label, tooltip=None, icon='customIcon.png', command=None,
                   double_command=None, command_type='python'):
        """
        Adds a shelf button width the given parameters
        :param label:
        :param tooltip:
        :param icon:
        :param command:
        :param double_command:
        :param command_type:
        :return:
        """

        maya.cmds.setParent(self._name)
        command = command or ''
        double_command = double_command or ''

        if not self._enable_labels:
            label = ''

        return maya.cmds.shelfButton(width=37, height=37, image=icon or '', label=label, command=command,
                                     doubleClickCommand=double_command, annotation=tooltip or '',
                                     imageOverlayLabel=label, overlayLabelBackColor=self._label_background,
                                     overlayLabelColor=self._label_color, sourceType=command_type)

    def add_separator(self):
        """
        Adds a separator to shelf
        :param parent:
        :return:
        """

        maya.cmds.separator(
            parent=self._name, manage=True, visible=True, horizontal=False, style='shelf',
            enableBackground=False, preventOverride=False)

    def build_category(self, shelf_file, category_name):

        self._category_lbl.setText(category_name.upper())

        self.load_category(shelf_file, 'general', clear=True)
        if category_name != 'general':
            self.add_separator()
            self.load_category(shelf_file, category_name, clear=False)

    def build_categories(self, shelf_file, categories):
        """
        Builds all categories given
        :param categories: list<str>, list of categories to build
        """

        self._category_lbl.setText('ALL')

        self.load_category(shelf_file, 'general', clear=True)
        for cat in categories:
            if cat == 'general':
                continue
            self.add_separator()
            self.load_category(shelf_file, cat, clear=False)

    def load_category(self, shelf_file, category_name, clear=True):
        """
        Loads into a shelf all the items of given category name, if exists
        :param category_name: str, name of the category
        """

        if clear:
            self.clear_list()
            # self.add_separator()

        with open(shelf_file) as f:
            shelf_data = json.load(f, object_pairs_hook=OrderedDict)

            for item, item_data in shelf_data.items():
                if item != category_name:
                    continue

                for i in item_data:
                    icon = i.get('icon')
                    command = i.get('command')
                    annotation = i.get('annotation')
                    label = i.get('label')

                    if annotation == 'separator':
                        self.add_separator()
                    else:
                        self.add_button(label=label, command=command, icon=icon, tooltip=annotation)
                return

    def build(self, shelf_file):
        """
        Builds shelf from JSON file
        :param shelf_file: str
        """

        first_item = None

        all_categories = list()

        with open(shelf_file) as f:
            shelf_data = json.load(f, object_pairs_hook=OrderedDict)

            for i, item in enumerate(shelf_data.keys()):
                if i == 0:
                    first_item = item

                category_action = self._category_menu.addAction(item.title())
                category_action.triggered.connect(partial(self.build_category, shelf_file, item))
                all_categories.append(item)

            category_action = self._category_menu.addAction('All')
            category_action.triggered.connect(partial(self.build_categories, shelf_file, all_categories))

        if first_item:
            self.load_category(shelf_file, first_item, clear=False)

    def clear_list(self):
        """
        Clears all the elements of the shelf
        """

        if gui.shelf_exists(shelf_name=self._name):
            menu_items = maya.cmds.shelfLayout(self._name, query=True, childArray=True)
            for item in menu_items:
                try:
                    maya.cmds.deleteUI(item)
                except Exception:
                    pass
Esempio n. 25
0
class scene_inputs(Node):
    def __init__(self, name, graph):
        super(scene_inputs, self).__init__(name, graph)
        self.menu = QMenu()
        self.action = self.menu.addAction('add port')
        self.action.triggered.connect(self.addInPin)
        self.setPos(self.graph().mapToScene(self.graph().viewport().rect().x(),self.graph().viewport().rect().y()+50) )
        self.asGraphSides = True
        self.sizes[4] = 0
        self.sizes[5] = 0

        self.setFlag(QGraphicsItem.ItemIsMovable,False)
        self.setFlag(QGraphicsItem.ItemIsFocusable,False)
        self.setFlag(QGraphicsItem.ItemIsSelectable,False)
        self.setFlag(QGraphicsItem.ItemSendsGeometryChanges,False)
        
        #self.setFlag(QGraphicsItem.ItemIgnoresTransformations)
        self.label().hide() 
        self.sender = sender()    
    def addInPin(self):
        const = len(self.outputs)+1
        p = self.addOutputPin("input_%i"%const, DataTypes.Any,editable=True)
        #p.dynamic = True
        p.setDeletable()
        self.sender.pinCreated.emit(p)

    def contextMenuEvent(self, event):
        self.menu.exec_(event.screenPos())

    @staticmethod
    def pinTypeHints():
        return {'inputs': [], 'outputs': [supportedDataTypesList]}

    @staticmethod
    def category():
        return '__hiden__'

    @staticmethod
    def keywords():
        return []

    @staticmethod
    def description():
        return 'Genertic array'

    def postCreate(self, jsonTemplate):
        Node.postCreate(self, jsonTemplate)

        # restore dynamically created inputs
        for inp in jsonTemplate['outputs']:
            PinWidgetBase.deserialize(self, inp)
            #pinAffects(p, self.out0)
    def boundingRect(self):
        rect = self.childrenBoundingRect()
        rect.setHeight(self.graph().boundingRect.height())
        self.setPos(self.graph().boundingRect.topLeft().x(),self.graph().boundingRect.topLeft().y()+50)
        #rect.setWidth(self.graph().boundingRect.width()/30)
        return rect         
    def paint(self, painter, option, widget):

        NodePainter.asGraphSides(self, painter, option, widget)
Esempio n. 26
0
class PinWidgetBase(QGraphicsWidget, PinBase):
    '''
    This is base class for all ui pins
    '''

    ## Event called when pin is connected
    OnPinConnected = QtCore.Signal(object)
    ## Event called when pin is disconnected
    OnPinDisconnected = QtCore.Signal(object)
    ## Event called when data been set
    dataBeenSet = QtCore.Signal(object)
    ## Event called when pin name changes
    nameChanged = QtCore.Signal(str)
    ## Event called when setUserStruct called
    # used by enums
    userStructChanged = QtCore.Signal(object)
    ## Event called when pin is deleted
    OnPinDeleted = QtCore.Signal(object)
    ## Event called when pin is deleted
    OnPinChanged = QtCore.Signal(object)

    def __init__(self, name, parent, dataType, direction, **kwargs):
        QGraphicsWidget.__init__(self)
        PinBase.__init__(self, name, parent, dataType, direction, **kwargs)
        self.setParentItem(parent)
        self.setCursor(QtCore.Qt.CrossCursor)
        ## context menu for pin
        self.menu = QMenu()
        ## Disconnect all connections
        self.actionDisconnect = self.menu.addAction('disconnect all')
        self.actionDisconnect.triggered.connect(self.disconnectAll)
        ## Copy UUID to buffer
        self.actionCopyUid = self.menu.addAction('copy uid')
        self.actionCopyUid.triggered.connect(self.saveUidToClipboard)

        ## Call exec pin
        self.actionCall = self.menu.addAction('execute')
        self.actionCall.triggered.connect(self.call)
        self.newPos = QtCore.QPointF()
        self.setFlag(QGraphicsWidget.ItemSendsGeometryChanges)
        self.setCacheMode(self.DeviceCoordinateCache)
        self.setAcceptHoverEvents(True)
        self.setZValue(2)
        self.width = 8 + 1
        self.height = 8 + 1
        self.hovered = False
        self.startPos = None
        self.endPos = None
        self._container = None
        self._execPen = QtGui.QPen(Colors.Exec, 0.5, QtCore.Qt.SolidLine)
        self.setGeometry(0, 0, self.width, self.height)
        self._dirty_pen = QtGui.QPen(Colors.DirtyPen, 0.5, QtCore.Qt.DashLine,
                                     QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin)

        self.pinImage = QtGui.QImage(':/icons/resources/array.png')

    def updateConstraint(self, constraint):
        self.constraint = constraint
        if self.parent()._Constraints.has_key(constraint):
            self.parent()._Constraints[constraint].append(self)
        else:
            self.parent()._Constraints[constraint] = [self]

    def setUserStruct(self, inStruct):
        PinBase.setUserStruct(self, inStruct)
        self.userStructChanged.emit(inStruct)

    def setDeletable(self):
        self.deletable = True
        self.actionRemove = self.menu.addAction('remove')
        self.actionRemove.triggered.connect(self.kill)

    def setName(self, newName):
        super(PinWidgetBase, self).setName(newName)
        self.nameChanged.emit(newName)

    def setData(self, value):
        PinBase.setData(self, value)
        self.dataBeenSet.emit(value)

    @property
    def dataType(self):
        return self._dataType

    @dataType.setter
    def dataType(self, value):
        self._dataType = value

    def setDataType(self, value):
        self.dataType = value

    def highlight(self):
        self.bAnimate = True
        t = QtCore.QTimeLine(900, self)
        t.setFrameRange(0, 100)
        t.frameChanged[int].connect(self.animFrameChanged)
        t.finished.connect(self.animationFinished)
        t.start()

    def animFrameChanged(self, value):
        self.width = clamp(math.sin(self._val) * 9, 4.5, 25)
        self.update()
        self._val += 0.1

    def animationFinished(self):
        self.width = 9
        self.update()
        self._val = 0

    @staticmethod
    def color():
        return QtGui.QColor()

    def call(self):
        PinBase.call(self)

    def kill(self):
        self.OnPinDeleted.emit(self)
        PinBase.kill(self)
        self.disconnectAll()
        if hasattr(self.parent(), self.name):
            delattr(self.parent(), self.name)
        if self._container is not None:
            self.parent().graph().scene().removeItem(self._container)
            if self.direction == PinDirection.Input:
                self.parent().inputsLayout.removeItem(self._container)
            else:
                self.parent().outputsLayout.removeItem(self._container)
        #print self.parent().outputs

    @staticmethod
    def deserialize(owningNode, jsonData):

        name = jsonData['name']
        dataType = jsonData['dataType']

        direction = jsonData['direction']
        value = jsonData['value']
        uid = uuid.UUID(jsonData['uuid'])
        bLabelHidden = jsonData['bLabelHidden']
        bDirty = jsonData['bDirty']
        deletable = jsonData['deletable']
        if 'editable' in jsonData:
            editable = jsonData['editable']
        else:
            editable = False
        p = None
        if direction == PinDirection.Input:
            p = owningNode.addInputPin(name,
                                       dataType,
                                       hideLabel=bLabelHidden,
                                       editable=editable)
            p.uid = uid
        else:
            p = owningNode.addOutputPin(name,
                                        dataType,
                                        hideLabel=bLabelHidden,
                                        editable=editable)
            p.uid = uid
        if deletable:
            p.setDeletable()
        if "curr_dataType" in jsonData and jsonData[
                "curr_dataType"] != dataType:
            from ..Pins import CreatePin
            a = CreatePin("", None, jsonData["curr_dataType"], 0)
            p.setType(a)
            del a

        p.setData(value)
        return p

    def serialize(self):
        data = PinBase.serialize(self)
        return data

    def ungrabMouseEvent(self, event):
        super(PinWidgetBase, self).ungrabMouseEvent(event)

    def get_container(self):
        return self._container

    #def translate(self, x, y):
    #    super(PinWidgetBase, self).moveBy(x, y)

    def boundingRect(self):
        if not self.dataType == DataTypes.Exec:
            return QtCore.QRectF(0, -0.5, 8 * 1.5, 8 + 1.0)
        else:
            return QtCore.QRectF(0, -0.5, 10 * 1.5, 10 + 1.0)

    def sizeHint(self, which, constraint):
        return QtCore.QSizeF(self.width, self.height)

    def saveUidToClipboard(self):
        clipboard = QApplication.clipboard()
        clipboard.clear()
        clipboard.setText(str(self.uid))

    def disconnectAll(self):
        trash = []
        for e in self.edge_list:
            if self.uid == e.destination().uid:
                trash.append(e)
            if self.uid == e.source().uid:
                trash.append(e)
        for e in trash:
            self.parent().graph().removeEdge(e)

    def shape(self):
        path = QtGui.QPainterPath()
        path.addEllipse(self.boundingRect())
        return path

    def paint(self, painter, option, widget):
        background_rect = QtCore.QRectF(0, 0, self.width, self.width)
        self.cPos = background_rect
        w = background_rect.width() / 2
        h = background_rect.height() / 2

        linearGrad = QtGui.QRadialGradient(QtCore.QPointF(w, h),
                                           self.width / 2.5)
        if not self._connected:
            linearGrad.setColorAt(0, self.color().darker(280))
            linearGrad.setColorAt(0.5, self.color().darker(280))
            linearGrad.setColorAt(0.65, self.color().lighter(130))
            linearGrad.setColorAt(1, self.color().lighter(70))
        else:
            linearGrad.setColorAt(0, self.color())
            linearGrad.setColorAt(1, self.color())

        if self.hovered:
            linearGrad.setColorAt(1, self.color().lighter(200))
        if self.dataType == DataTypes.Array:
            if self.pinImage:
                painter.drawImage(background_rect, self.pinImage)
            else:
                painter.setBrush(Colors.Array)
                rect = background_rect
                painter.drawRect(rect)
        elif self.dataType == DataTypes.Exec:
            painter.setPen(self._execPen)
            if self._connected:
                painter.setBrush(QtGui.QBrush(self.color()))
            else:
                painter.setBrush(QtCore.Qt.NoBrush)
            arrow = QtGui.QPolygonF([
                QtCore.QPointF(0.0, 0.0),
                QtCore.QPointF(self.width / 2.0, 0.0),
                QtCore.QPointF(self.width, self.height / 2.0),
                QtCore.QPointF(self.width / 2.0, self.height),
                QtCore.QPointF(0, self.height)
            ])
            painter.drawPolygon(arrow)
        else:
            painter.setBrush(QtGui.QBrush(linearGrad))
            rect = background_rect.setX(background_rect.x())
            painter.drawEllipse(background_rect)

    def contextMenuEvent(self, event):
        self.menu.exec_(event.screenPos())

    def getLayout(self):
        if self.direction == PinDirection.Input:
            return self.parent().inputsLayout
        else:
            return self.parent().outputsLayout

    def hoverEnterEvent(self, event):
        super(PinWidgetBase, self).hoverEnterEvent(event)
        self.update()
        self.hovered = True
        self.setToolTip(str(self.currentData()))
        event.accept()

    def hoverLeaveEvent(self, event):
        super(PinWidgetBase, self).hoverLeaveEvent(event)
        self.update()
        self.hovered = False

    def pinConnected(self, other):
        PinBase.pinConnected(self, other)
        #if self.dynamic:
        #    data = self.serialize()
        #    pin = self.deserialize(self.parent(),data)
        #    pin.dynamic=True
        self.OnPinConnected.emit(other)

    def pinDisconnected(self, other):
        PinBase.pinDisconnected(self, other)
        self.OnPinDisconnected.emit(other)
    def _create_button(self,
                       name=None,
                       icon=None,
                       fn=None,
                       status=None,
                       tooltip=None,
                       description=None,
                       instructions=None,
                       whats_this='',
                       settings=None,
                       settings_fn=None,
                       info_message='',
                       error_message='',
                       widgets=None):
        """
        Internal function to easily create buttons for the box widget
        :param name:
        :param icon:
        :param fn:
        :param status:
        :param tooltip:
        :return:
        """

        name = name or ''
        new_btn = CommandButton(parent=self).small()
        if icon:
            if python.is_string(icon):
                icon = resources.icon(icon)
            new_btn.setIcon(icon)
        else:
            new_btn.setText(name)
        new_btn.setObjectName(name.replace(' ', '').lower() or description)
        new_btn.setStatusTip(status or name)
        new_btn.setToolTip(tooltip or name)
        new_btn.setProperty('name', name)
        new_btn.setProperty('description', description or tooltip or status
                            or '')
        new_btn.setProperty('instructions', instructions or '')
        new_btn.setProperty('tooltip_help', {
            'title': name,
            'description': description
        })
        new_btn.setStyleSheet('qproperty-iconSize: 20px 20px;')
        new_btn.setIconSize(QSize(20, 20))
        new_btn.setWhatsThis(whats_this or tooltip or name)
        new_btn.setContextMenuPolicy(Qt.CustomContextMenu)
        new_btn.installEventFilter(self)
        if widgets:
            valid_widgets = list()
            for widget in widgets:
                if hasattr(self, widget):
                    valid_widgets.append(getattr(self, widget))
            if valid_widgets:
                self._widgets[new_btn.objectName()] = valid_widgets
                new_btn.has_menu = True

        if fn:
            info_message = info_message or 'Command "{}" executed successfully!'.format(
                name)
            error_message = error_message or 'Error while executing command "{}"! Check log for more info.'.format(
                name)
            new_btn.clicked.connect(
                partial(self._on_execute_command, name, fn, info_message,
                        error_message))
        new_btn.customContextMenuRequested.connect(self._on_show_context_menu)

        if settings:
            settings_icon = 'menu_dots' if settings and len(
                settings) > 1 else 'settings'
            buttons_widget = QWidget()
            buttons_widget.setStyleSheet(
                'QPushButton::menu-indicator{ image: none; };')
            buttons_layout = layouts.HorizontalLayout(spacing=0,
                                                      margins=(0, 0, 0, 0))
            buttons_widget.setLayout(buttons_layout)

            if len(settings) > 1:
                options_name = '{} Options'.format(name)
                options_description = 'Show {} Menu'.format(options_name)
                options_btn = self._create_button(
                    icon=settings_icon,
                    fn=settings_fn,
                    name=options_name,
                    description=options_description)
                options_menu = QMenu(self)
                options_menu.installEventFilter(self)
                options_btn.setMenu(options_menu)
                for setting in settings:
                    option_name = setting.get('name', None)
                    if not option_name:
                        continue
                    option_fn = setting.get('fn', None)
                    if not option_fn:
                        continue
                    option_description = setting.get('description', '')
                    option_instructions = setting.get('instructions', '')
                    option_icon = resources.icon(
                        setting.get('icon', 'tpRigToolkit'))
                    option_action = QAction(option_icon, option_name, self)
                    option_action.setProperty('description', option_description
                                              or '')
                    option_action.setProperty('instructions',
                                              option_instructions or '')
                    option_info_message = info_message or 'Command "{}" executed successfully!'.format(
                        name)
                    option_error_message = error_message or 'Error while executing command "{}"! ' \
                                                            'Check log for more info.'.format(name)
                    option_action.triggered.connect(
                        partial(self._on_execute_command, option_name,
                                option_fn, option_info_message,
                                option_error_message))

                    options_menu.addAction(option_action)
            else:
                setting = settings[0]
                option_description = setting.get('description', '')
                option_name = setting.get('name', None)
                option_fn = setting.get('fn', None)
                options_btn = self._create_button(
                    icon=settings_icon,
                    fn=option_fn,
                    name=option_name,
                    description=option_description)

            new_btn.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding)
            new_btn.setStyleSheet(
                new_btn.styleSheet() +
                ';border-top-right-radius: 0px; border-bottom-right-radius: 0px; '
                'border-right: 0px;')
            new_btn.setMaximumWidth(28)

            options_btn.setSizePolicy(QSizePolicy.Minimum,
                                      QSizePolicy.Expanding)
            options_btn.setMaximumWidth(18)
            options_btn.setStyleSheet(
                options_btn.styleSheet() +
                ';border-top-left-radius: 0px; border-bottom-left-radius: 0px; '
                'qproperty-iconSize: 12px 12px;')
            buttons_layout.addWidget(new_btn)
            buttons_layout.addWidget(options_btn)

            self._buttons.append(new_btn)

            return buttons_widget

        self._buttons.append(new_btn)

        return new_btn
Esempio n. 28
0
    def _setup_menubar(self):
        """
        Internal function that setups menu bar for the widget
        :return:
        """

        menubar = self.menuBar()

        save_icon = resources.icon('save')
        load_icon = resources.icon('open_folder')
        play_icon = resources.icon('play')
        clear_icon = resources.icon('delete')
        resume_icon = resources.icon('resume')
        undo_icon = resources.icon('undo')
        redo_icon = resources.icon('redo')
        copy_icon = resources.icon('copy')
        cut_icon = resources.icon('cut')
        paste_icon = resources.icon('paste')
        tab_icon = resources.icon('tab')
        quote_icon = resources.icon('quote')
        rename_icon = resources.icon('rename')
        keyboard_icon = resources.icon('keyboard')
        help_icon = resources.icon('help')

        file_menu = QMenu('File', menubar)
        menubar.addMenu(file_menu)
        save_session_action = QAction(save_icon, 'Save Session', file_menu)
        load_script_action = QAction(load_icon, 'Load Script', file_menu)
        save_script_action = QAction(save_icon, 'Save Script', file_menu)
        file_menu.addAction(save_session_action)
        file_menu.addAction(load_script_action)
        file_menu.addAction(save_script_action)
        load_script_action.setShortcut('Ctrl+O')
        # load_script_action.setShortcutContext(Qt.WidgetShortcut)
        save_script_action.setShortcut('Ctrl+S')
        # save_script_action.setShortcutContext(Qt.WidgetShortcut)

        edit_menu = QMenu('Edit', self)
        menubar.addMenu(edit_menu)
        undo_action = QAction(undo_icon, 'Undo', edit_menu)
        redo_action = QAction(redo_icon, 'Redo', edit_menu)
        copy_action = QAction(copy_icon, 'Copy', edit_menu)
        cut_action = QAction(cut_icon, 'Cut', edit_menu)
        paste_action = QAction(paste_icon, 'Paste', edit_menu)
        tab_to_spaces_action = QAction(tab_icon, 'Tab to Spaces', edit_menu)
        comment_action = QAction(quote_icon, 'Comment', edit_menu)
        find_and_replace = QAction(rename_icon, 'Find and Replace', edit_menu)
        edit_menu.addAction(undo_action)
        edit_menu.addAction(redo_action)
        edit_menu.addSeparator()
        edit_menu.addAction(copy_action)
        edit_menu.addAction(cut_action)
        edit_menu.addAction(paste_action)
        edit_menu.addSeparator()
        edit_menu.addAction(tab_to_spaces_action)
        edit_menu.addAction(comment_action)
        edit_menu.addAction(find_and_replace)

        run_menu = QMenu('Run', self)
        menubar.addMenu(run_menu)
        self._execute_all_action = QAction(play_icon, 'Execute All', run_menu)
        self._execute_all_action.setShortcut('Ctrl+Shift+Return')
        self._execute_all_action.setShortcutContext(Qt.ApplicationShortcut)
        self._execute_selected_action = QAction(resume_icon,
                                                'Execute Selected', run_menu)
        self._execute_selected_action.setShortcut('Ctrl+Return')
        self._execute_selected_action.setShortcutContext(
            Qt.WidgetWithChildrenShortcut)
        self._clear_output_action = QAction(clear_icon, 'Clear Output',
                                            run_menu)

        run_menu.addAction(self._execute_all_action)
        run_menu.addAction(self._execute_selected_action)
        run_menu.addAction(self._clear_output_action)

        help_menu = QMenu('Help', self)
        menubar.addMenu(help_menu)
        show_shortcuts_action = QAction(keyboard_icon, 'Show Shortcuts',
                                        help_menu)
        print_help_action = QAction(help_icon, 'Print Help', help_menu)
        help_menu.addAction(show_shortcuts_action)
        help_menu.addSeparator()
        help_menu.addAction(print_help_action)
        undo_action.setShortcut('Ctrl+Z')
        undo_action.setShortcutContext(Qt.WidgetShortcut)
        redo_action.setShortcut('Ctrl+Y')
        redo_action.setShortcutContext(Qt.WidgetShortcut)
        copy_action.setShortcut('Ctrl+C')
        copy_action.setShortcutContext(Qt.WidgetShortcut)
        cut_action.setShortcut('Ctrl+X')
        cut_action.setShortcutContext(Qt.WidgetShortcut)
        paste_action.setShortcut('Ctrl+V')
        paste_action.setShortcutContext(Qt.WidgetShortcut)
        comment_action.setShortcut(QKeySequence(Qt.ALT + Qt.Key_Q))
        comment_action.setShortcutContext(Qt.WidgetShortcut)

        self._execute_all_action.triggered.connect(
            self._controller.execute_script)
        self._execute_selected_action.triggered.connect(
            partial(self._controller.execute_script, True))
        self._clear_output_action.triggered.connect(
            partial(self._controller.set_output_text, ''))
        # open_settings_folder_action.triggered.connect(self._open_settings)
        # show_shortcuts_action.triggered.connect(self._open_shortcuts)
        # print_help_action.triggered.connect(self.editor_help)
        save_session_action.triggered.connect(
            self._controller.save_current_session)
        save_script_action.triggered.connect(self._controller.save_script)
        load_script_action.triggered.connect(self._controller.load_script)
        undo_action.triggered.connect(self._scripts_tab.undo)
        redo_action.triggered.connect(self._scripts_tab.redo)
        copy_action.triggered.connect(self._scripts_tab.copy)
        cut_action.triggered.connect(self._scripts_tab.cut)
        paste_action.triggered.connect(self._scripts_tab.paste)
        tab_to_spaces_action.triggered.connect(
            self._controller.convert_tab_to_spaces)
        # find_and_replace.triggered.connect(self._open_find_replace)
        comment_action.triggered.connect(self._scripts_tab.comment)

        return menubar
Esempio n. 29
0
class UINodeBase(QGraphicsWidget, IPropertiesViewSupport):
    """
    Default node description
    """
    # Event called when node name changes
    displayNameChanged = QtCore.Signal(str)

    def __init__(self,
                 raw_node,
                 w=80,
                 color=Colors.NodeBackgrounds,
                 headColorOverride=None):
        super(UINodeBase, self).__init__()
        self.setFlag(QGraphicsWidget.ItemIsMovable)
        self.setFlag(QGraphicsWidget.ItemIsFocusable)
        self.setFlag(QGraphicsWidget.ItemIsSelectable)
        self.setFlag(QGraphicsWidget.ItemSendsGeometryChanges)
        self.setCacheMode(QGraphicsItem.DeviceCoordinateCache)
        self.setFocusPolicy(QtCore.Qt.StrongFocus)
        self.setAcceptHoverEvents(True)
        self.setZValue(NodeDefaults().Z_LAYER)
        self._rawNode = raw_node
        self._rawNode.setWrapper(self)
        self._rawNode.killed.connect(self.kill)
        self._rawNode.tick.connect(self.Tick)

        self.custom_widget_data = {}
        # node name
        self._displayName = self.name

        # GUI Layout
        self.opt_node_base_color = Colors.NodeBackgrounds
        self.opt_selected_pen_color = Colors.NodeSelectedPenColor
        self.opt_pen_selected_type = QtCore.Qt.SolidLine
        self._collapsed = False
        self._left_stretch = 0
        self.color = color
        self.drawlabel = True
        self.headColorOverride = headColorOverride
        self.headColor = headColorOverride
        self._w = 0
        self.h = 30
        self.minWidth = 25
        self.minHeight = self.h
        self._labelTextColor = QtCore.Qt.white

        self.drawLayoutsDebug = False
        self.nodeLayout = QGraphicsLinearLayout(QtCore.Qt.Vertical)
        self.nodeLayout.setContentsMargins(NodeDefaults().CONTENT_MARGINS,
                                           NodeDefaults().CONTENT_MARGINS,
                                           NodeDefaults().CONTENT_MARGINS,
                                           NodeDefaults().CONTENT_MARGINS)
        self.nodeLayout.setSpacing(NodeDefaults().LAYOUTS_SPACING)
        self.nodeNameFont = QtGui.QFont("Consolas")
        self.nodeNameFont.setPointSize(6)

        self.nodeTypeFont = QtGui.QFont("Consolas")
        self.nodeTypeFont.setPointSize(4)
        self.nodeTypeFont.setItalic(True)

        self.headerLayout = QGraphicsLinearLayout(QtCore.Qt.Horizontal)
        self.nodeLayout.addItem(self.headerLayout)
        self.nodeNameWidget = NodeName(self)
        self.headerLayout.addItem(self.nodeNameWidget)
        self.headerLayout.setContentsMargins(0, 0, 0, 0)
        self.headerLayout.setSpacing(3)

        self.nameActionsSpacer = QGraphicsWidget()
        self.nameActionsSpacer.setObjectName("nameActionsSpacer")
        self.nameActionsSpacer.setSizePolicy(QSizePolicy.Expanding,
                                             QSizePolicy.Maximum)
        self.headerLayout.setSizePolicy(QSizePolicy.Expanding,
                                        QSizePolicy.Maximum)
        self.headerLayout.addItem(self.nameActionsSpacer)
        self.headerLayout.setMaximumHeight(self.labelHeight)

        self.pinsLayout = QGraphicsLinearLayout(QtCore.Qt.Horizontal)
        self.pinsLayout.setContentsMargins(0, 0, 0, 0)
        self.pinsLayout.setSpacing(NodeDefaults().LAYOUTS_SPACING)
        self.nodeLayout.addItem(self.pinsLayout)
        self.nodeLayout.setStretchFactor(self.pinsLayout, 2)
        self.inputsLayout = QGraphicsLinearLayout(QtCore.Qt.Vertical)
        self.inputsLayout.setContentsMargins(0, 0, 0, 0)
        self.inputsLayout.setSpacing(NodeDefaults().LAYOUTS_SPACING)
        self.outputsLayout = QGraphicsLinearLayout(QtCore.Qt.Vertical)
        self.outputsLayout.setContentsMargins(0, 0, 0, 0)
        self.outputsLayout.setSpacing(NodeDefaults().LAYOUTS_SPACING)
        self.pinsLayout.addItem(self.inputsLayout)
        self.pinLayoutSpacer = QGraphicsWidget()
        self.pinLayoutSpacer.setSizePolicy(QSizePolicy.Expanding,
                                           QSizePolicy.Expanding)
        self.pinLayoutSpacer.setObjectName("pinLayoutSpacer")
        self.pinsLayout.addItem(self.pinLayoutSpacer)
        self.pinsLayout.addItem(self.outputsLayout)
        self.setLayout(self.nodeLayout)
        self.svgIcon = QtSvg.QGraphicsSvgItem(self)
        self.svgIcon.setPos(-6, -6)

        self._image = None
        self.canvasRef = None
        self._menu = QMenu()

        # Resizing Options
        self.initialRectWidth = self.minWidth
        self.initialRectHeight = self.minHeight
        self.expanded = True
        self.resizable = False
        self.bResize = False
        self.resizeDirection = (0, 0)
        self.resizeStripsSize = 2
        self.resizeStrips = [0, 0, 0, 0,
                             0]  # Left, Top, Right, Bottom, BottomRight

        self.lastMousePos = QtCore.QPointF()
        self.mousePressPos = QtCore.QPointF()

        # Hiding/Moving By Group/collapse/By Pin
        self.pressedCommentNode = None
        self.owningCommentNode = None
        self.edgesToHide = []
        self.nodesNamesToMove = []
        self.pinsToMove = {}
        self._rect = QtCore.QRectF(0, 0, self.minWidth, self.minHeight)

        # Group pins
        self.inputGroupPins = {}
        self.outputGroupPins = {}

        # Action buttons
        self._actionButtons = set()

        # Core nodes support
        self.isTemp = False
        self.isCommentNode = False

        self.propertyEditor = None

        # collapse action
        self.actionToggleCollapse = self._menu.addAction("ToggleCollapse")
        self.actionToggleCollapse.setToolTip(
            "Toggles node's body collapsed or not")
        self.actionToggleCollapse.triggered.connect(self.toggleCollapsed)
        self.actionToggleCollapse.setData(
            NodeActionButtonInfo(RESOURCES_DIR + "/nodeCollapse.svg",
                                 CollapseNodeActionButton))

    def toggleCollapsed(self):
        self.collapsed = not self.collapsed

    def aboutToCollapse(self, futureCollapseState):
        """Called before collapsing or expanding."""
        pass

    @property
    def collapsed(self):
        return self._collapsed

    @collapsed.setter
    def collapsed(self, bCollapsed):
        if bCollapsed != self._collapsed:
            self._collapsed = bCollapsed
            self.aboutToCollapse(self._collapsed)
            for i in range(0, self.inputsLayout.count()):
                inp = self.inputsLayout.itemAt(i)
                inp.setVisible(not bCollapsed)
            for o in range(0, self.outputsLayout.count()):
                out = self.outputsLayout.itemAt(o)
                out.setVisible(not bCollapsed)
            self.pinLayoutSpacer.setVisible(not bCollapsed)
            self.updateNodeShape()

    @property
    def image(self):
        return self._image

    @image.setter
    def image(self, value):
        self._image = value
        self.svgIcon.renderer().load(value)
        elementName = QtCore.QFileInfo(value).baseName()
        self.svgIcon.setElementId(elementName)
        # self.svgIcon.setPos(self.geometry().topRight())

    def getImageDrawRect(self):
        topRight = self.boundingRect().topRight()
        topRight.setY(-12)
        topRight.setX(self.boundingRect().width() - 12)
        r = self.boundingRect()
        r.setWidth(24)
        r.setHeight(24)
        r.translate(topRight)
        return r

    @property
    def labelTextColor(self):
        return self._labelTextColor

    @labelTextColor.setter
    def labelTextColor(self, value):
        self._labelTextColor = value
        self.nodeNameWidget.setTextColor(self._labelTextColor)

    def __repr__(self):
        graphName = self._rawNode.graph(
        ).name if self._rawNode.graph is not None else str(None)
        return "<class[{0}]; name[{1}]; graph[{2}]>".format(
            self.__class__.__name__, self.getName(), graphName)

    def sizeHint(self, which, constraint):
        return QtCore.QSizeF(self.getNodeWidth(), self.getNodeHeight())

    def setGeometry(self, rect):
        self.prepareGeometryChange()
        super(QGraphicsWidget, self).setGeometry(rect)
        self.setPos(rect.topLeft())

    @property
    def uid(self):
        return self._rawNode._uid

    @uid.setter
    def uid(self, value):
        self._rawNode._uid = value

    @property
    def name(self):
        return self._rawNode.name

    @name.setter
    def name(self, value):
        self._rawNode.setName(value)

    @property
    def displayName(self):
        return self._displayName

    @displayName.setter
    def displayName(self, value):
        self._displayName = value
        self.displayNameChanged.emit(self._displayName)
        self.updateNodeShape()

    @property
    def pins(self):
        return self._rawNode.pins

    @property
    def UIPins(self):
        result = OrderedDict()
        for rawPin in self._rawNode.pins:
            uiPinRef = rawPin.getWrapper()
            if uiPinRef is not None:
                result[rawPin.uid] = uiPinRef()
        return result

    @property
    def UIinputs(self):
        result = OrderedDict()
        for rawPin in self._rawNode.pins:
            if rawPin.direction == PinDirection.Input:
                result[rawPin.uid] = rawPin.getWrapper()()
        return result

    @property
    def UIoutputs(self):
        result = OrderedDict()
        for rawPin in self._rawNode.pins:
            if rawPin.direction == PinDirection.Output:
                result[rawPin.uid] = rawPin.getWrapper()()
        return result

    @property
    def namePinOutputsMap(self):
        result = OrderedDict()
        for rawPin in self._rawNode.pins:
            if rawPin.direction == PinDirection.Output:
                result[rawPin.name] = rawPin.getWrapper()()
        return result

    @property
    def namePinInputsMap(self):
        result = OrderedDict()
        for rawPin in self._rawNode.pins:
            if rawPin.direction == PinDirection.Input:
                result[rawPin.name] = rawPin.getWrapper()()
        return result

    @property
    def w(self):
        return self._w

    @w.setter
    def w(self, value):
        self._w = value

    def getName(self):
        return self._rawNode.getName()

    def isRenamable(self):
        return False

    def setName(self, name):
        self._rawNode.setName(name)

    def getPin(self, name, pinsGroup=PinSelectionGroup.BothSides):
        pin = self._rawNode.getPin(str(name), pinsGroup)
        if pin is not None:
            if pin.getWrapper() is not None:
                return pin.getWrapper()()
        return None

    @staticmethod
    def removePinByName(node, name):
        pin = node.getPin(name)
        if pin:
            pin.kill()

    @staticmethod
    def recreate(node):
        templ = node.serialize()
        uid = node.uid
        node.kill()
        newNode = node.canvas.createNode(templ)
        newNode.uid = uid
        return newNode

    @property
    def isCompoundNode(self):
        return self._rawNode.isCompoundNode

    # TODO: add this to ui node interface
    def serializationHook(self):
        # this will be called by raw node
        # to gather ui specific info
        template = {}
        if self.resizable:
            template['resize'] = {
                'w': self._rect.right(),
                'h': self._rect.bottom()
            }
        template['displayName'] = self.displayName
        template['collapsed'] = self.collapsed
        template['headerHtml'] = self.nodeNameWidget.getHtml()
        return template

    def setHeaderHtml(self, html):
        self.nodeNameWidget.setHtml(html)

    def serialize(self):
        return self._rawNode.serialize()

    def onVisibilityChanged(self, bVisible):
        pass

    def itemChange(self, change, value):
        if change == QGraphicsItem.ItemPositionChange:
            self._rawNode.setPosition(value.x(), value.y())
        if change == QGraphicsItem.ItemVisibleChange:
            if self.owningCommentNode is not None:
                if self.owningCommentNode.collapsed:
                    self.onVisibilityChanged(False)
                else:
                    self.onVisibilityChanged(bool(value))
        return super(UINodeBase, self).itemChange(change, value)

    def isUnderActiveGraph(self):
        return self._rawNode.isUnderActiveGraph()

    def autoAffectPins(self):
        self._rawNode.autoAffectPins()

    def postCreate(self, jsonTemplate=None):
        # create ui pin wrappers
        for i in self._rawNode.getOrderedPins():
            self._createUIPinWrapper(i)

        self.updateNodeShape()
        self.setPos(self._rawNode.x, self._rawNode.y)

        if self.canvasRef().graphManager.activeGraph() != self._rawNode.graph(
        ):
            self.hide()

        if not self.drawlabel:
            self.nodeNameWidget.hide()

        if self.headColorOverride is None:
            if self.isCallable():
                self.headColor = NodeDefaults().CALLABLE_NODE_HEAD_COLOR
            else:
                self.headColor = NodeDefaults().PURE_NODE_HEAD_COLOR
        else:
            self.headColor = self.headColorOverride

        self.createActionButtons()

        headerHtml = self.name
        if jsonTemplate is not None:
            if "collapsed" in jsonTemplate["wrapper"]:
                self.collapsed = jsonTemplate["wrapper"]["collapsed"]
            if "headerHtml" in jsonTemplate["wrapper"]:
                headerHtml = jsonTemplate["wrapper"]["headerHtml"]

        self.setToolTip(self.description())
        if self.resizable:
            w = self.getNodeWidth()
            h = self.getNodeHeight()
            if jsonTemplate is not None:
                if "resize" in jsonTemplate["wrapper"]:
                    w = jsonTemplate["wrapper"]["resize"]["w"]
                    h = jsonTemplate["wrapper"]["resize"]["h"]
            self._rect.setWidth(w)
            self._rect.setHeight(h)
            self.updateNodeShape()

        self.setHeaderHtml(headerHtml)

    def createActionButtons(self):
        # NOTE: actions with action button class specified will be added next to node name
        for action in self._menu.actions():
            actionData = action.data()
            if isinstance(actionData, NodeActionButtonInfo):
                actionButtonClass = actionData.actionButtonClass()
                svgFilePath = actionData.filePath()
                if actionButtonClass is None:
                    actionButtonClass = NodeActionButtonBase
                self.headerLayout.addItem(
                    actionButtonClass(svgFilePath, action, self))
                action.setVisible(False)

    def isCallable(self):
        return self._rawNode.isCallable()

    def category(self):
        return self._rawNode.category()

    def description(self):
        return self._rawNode.description()

    @property
    def packageName(self):
        return self._rawNode.packageName

    def getData(self, pinName):
        if pinName in [p.name for p in self.inputs.values()]:
            p = self.getPin(pinName, PinSelectionGroup.Inputs)
            return p.getData()

    def setData(self, pinName, data):
        if pinName in [p.name for p in self.outputs.values()]:
            p = self.getPin(pinName, PinSelectionGroup.Outputs)
            p.setData(data)

    @property
    def labelHeight(self):
        return self.nodeNameWidget.sizeHint(None, None).height()

    @property
    def labelWidth(self):
        headerWidth = self.nodeNameWidget.sizeHint(None, None).width()

        # actions width. 10 is svg icon size, probably need to move this value to some preferences
        numActions = len(self._actionButtons)
        headerWidth += numActions * 10
        headerWidth += numActions * self.headerLayout.spacing()
        if self.collapsed and not self.resizable:
            headerWidth += self.nameActionsSpacer.boundingRect().width()
        headerWidth += self.headerLayout.spacing(
        ) + NodeDefaults().CONTENT_MARGINS * 2
        return headerWidth

    def getNodeWidth(self):
        width = self.getPinsWidth() + self.pinsLayout.spacing() * 2
        if self.resizable:
            width = max(self._rect.width(), width)
        width = max(width, self.labelWidth)
        return width

    def getNodeHeight(self):
        h = self.nodeNameWidget.sizeHint(None, None).height()
        h += self.nodeLayout.spacing()
        try:
            numInputs = len(self.UIinputs)
            numOutputs = len(self.UIoutputs)
            pins = self.UIinputs.values(
            ) if numInputs > numOutputs else self.UIoutputs.values()
            h += NodeDefaults().CONTENT_MARGINS * 2

            for pin in pins:
                if pin.isVisible():
                    h += pin.sizeHint(
                        None, None).height() + NodeDefaults().LAYOUTS_SPACING
        except:
            pass

        if h < self.minHeight:
            h = self.minHeight

        if self.resizable:
            h = max(self._rect.height(), h)

        if self.collapsed:
            h = max(self.minHeight, self.labelHeight)

        return h

    def getPinsWidth(self):
        iwidth = 0
        owidth = 0
        pinwidth = 0
        pinwidth2 = 0
        for i in self.UIPins.values():
            if i.direction == PinDirection.Input:
                iwidth = max(iwidth, i.sizeHint(None, None).width())
                # pinwidth = max(pinwidth, i.width)
            else:
                owidth = max(owidth, i.sizeHint(None, None).width())
                # pinwidth2 = max(pinwidth2, i.width)
        return iwidth + owidth + pinwidth + pinwidth2 + Spacings.kPinOffset

    def invalidateNodeLayouts(self):
        self.inputsLayout.invalidate()
        self.outputsLayout.invalidate()
        self.pinsLayout.invalidate()
        self.headerLayout.invalidate()
        self.nodeLayout.invalidate()

    def updateNodeShape(self):
        self.prepareGeometryChange()
        self.invalidateNodeLayouts()
        self.updateGeometry()
        self.update()
        self.canvasRef().update()

    def onChangeColor(self, label=False):
        res = QColorDialog.getColor(self.color, None, 'Node color setup')
        if res.isValid():
            res.setAlpha(80)
            self.color = res
            if label:
                self.update()

    def isUnderCollapsedComment(self):
        if self.owningCommentNode is None:
            return False
        else:
            if self.owningCommentNode.collapsed:
                return True

        parent = self.owningCommentNode.owningCommentNode
        while parent is not None:
            upperComment = parent
            if upperComment.collapsed:
                return True
            parent = upperComment.owningCommentNode
        return False

    def getTopMostOwningCollapsedComment(self):
        """Returns top most owning comment. If bCollapsed=True, it will stop when first collapsed comment is found.
        """
        if self.owningCommentNode is None:
            return None
        # build chain of comments collapse states
        topMostComment = self.owningCommentNode
        parent = topMostComment.owningCommentNode

        chain = OrderedDict()
        chain[topMostComment] = topMostComment.collapsed

        while parent is not None:
            topMostComment = parent
            chain[topMostComment] = topMostComment.collapsed
            parent = topMostComment.owningCommentNode

        last = None
        for comment, collapsed in chain.items():
            if not comment.isVisible():
                continue
            if last is not None:
                if collapsed + last.collapsed == 1:
                    topMostComment = last
                    break
                last = comment
            else:
                last = comment

        return topMostComment

    def updateOwningCommentNode(self):

        if self.owningCommentNode is not None and self.owningCommentNode.collapsed:
            return

        collidingItems = self.collidingItems(QtCore.Qt.ContainsItemShape)
        collidingNodes = set()
        for item in collidingItems:
            if item.sceneBoundingRect().contains(
                    self.sceneBoundingRect()) and isinstance(item, UINodeBase):
                if item.isCommentNode:
                    collidingNodes.add(item)
        owningCommentNode = None
        if len(collidingNodes) == 1:
            owningCommentNode = list(collidingNodes)[0]
        elif len(collidingNodes) > 1:
            # find smallest rect
            smallest = list(collidingNodes)[0]
            for commentNode in collidingNodes:
                s1 = smallest.boundingRect().size()
                s2 = commentNode.boundingRect().size()
                if s1.width() > s2.width() and s1.height() > s2.height():
                    smallest = commentNode
                if self in commentNode.owningNodes:
                    commentNode.owningNodes.remove(self)
            owningCommentNode = smallest
        self.owningCommentNode = owningCommentNode
        if self.owningCommentNode is not None:
            if owningCommentNode._rawNode.graph() == self.canvasRef(
            ).graphManager.activeGraph():
                self.owningCommentNode.owningNodes.add(self)

    def getCollidedNodes(self, bFullyCollided=True, classNameFilters=set()):
        collidingItems = self.collidingItems()
        collidingNodes = set()
        for item in collidingItems:
            node = item.topLevelItem()
            if bFullyCollided:
                if self.sceneBoundingRect().contains(node.sceneBoundingRect()):
                    if node is not self and isinstance(node, UINodeBase):
                        if classNameFilters:
                            if node.__class__.__name__ not in classNameFilters:
                                continue
                        if node._rawNode.graph() != self.canvasRef(
                        ).graphManager.activeGraph():
                            continue
                        collidingNodes.add(node)
            else:
                if node is not self and isinstance(node, UINodeBase):
                    if classNameFilters:
                        if node.__class__.__name__ not in classNameFilters:
                            continue
                    if node._rawNode.graph() != self.canvasRef(
                    ).graphManager.activeGraph():
                        continue
                    collidingNodes.add(node)
        return collidingNodes

    def translate(self, x, y):
        super(UINodeBase, self).moveBy(x, y)

    def paint(self, painter, option, widget):
        NodePainter.default(self, painter, option, widget)
        if self.drawLayoutsDebug:
            painter.drawRect(self.headerLayout.geometry())
            painter.drawRect(self.nodeLayout.geometry())
            painter.drawRect(self.inputsLayout.geometry())
            painter.drawRect(self.outputsLayout.geometry())

    def shouldResize(self, cursorPos):
        result = {"resize": False, "direction": self.resizeDirection}
        if self.resizeStrips[0] == 1:
            result["resize"] = True
            result["direction"] = (-1, 0)
        if self.resizeStrips[1] == 1:
            result["resize"] = True
            result["direction"] = (0, -1)
        if self.resizeStrips[2] == 1:
            result["resize"] = True
            result["direction"] = (1, 0)
        elif self.resizeStrips[3] == 1:
            result["resize"] = True
            result["direction"] = (0, 1)
        elif self.resizeStrips[4] == 1:
            result["resize"] = True
            result["direction"] = (1, 1)
        return result

    def contextMenuEvent(self, event):
        self._menu.exec_(event.screenPos())

    def mousePressEvent(self, event):
        self.update()
        self.mousePressPos = event.pos()
        self.pressedCommentNode = self.owningCommentNode
        super(UINodeBase, self).mousePressEvent(event)
        self.mousePressPos = event.scenePos()
        self.origPos = self.pos()
        self.initPos = self.pos()
        self.initialRect = self.boundingRect()
        if self.expanded and self.resizable:
            resizeOpts = self.shouldResize(self.mapToScene(event.pos()))
            if resizeOpts["resize"]:
                self.resizeDirection = resizeOpts["direction"]
                self.initialRectWidth = self.initialRect.width()
                self.initialRectHeight = self.initialRect.height()
                self.setFlag(QGraphicsItem.ItemIsMovable, False)
                self.bResize = True

    def mouseMoveEvent(self, event):
        super(UINodeBase, self).mouseMoveEvent(event)
        # resize
        if self.bResize:
            delta = event.scenePos() - self.mousePressPos
            if self.resizeDirection == (1, 0):
                # right connection resize
                newWidth = delta.x() + self.initialRectWidth
                if newWidth > self.minWidth:
                    self._rect.setWidth(newWidth)
                    self.w = newWidth
                    self.updateNodeShape()
            elif self.resizeDirection == (0, 1):
                newHeight = delta.y() + self.initialRectHeight
                if newHeight > self.minHeight:
                    self._rect.setHeight(newHeight)
                    self.updateNodeShape()
            elif self.resizeDirection == (-1, 0):
                # left connection resize
                posdelta = self.mapToScene(event.pos()) - self.origPos
                posdelta2 = self.mapToScene(event.pos()) - self.initPos
                newWidth = -posdelta2.x() + self.initialRectWidth
                if newWidth > self.minWidth:
                    self.translate(posdelta.x(), 0)
                    self.origPos = self.pos()
                    self._rect.setWidth(newWidth)
                    self.updateNodeShape()
            elif self.resizeDirection == (1, 1):
                newWidth = delta.x() + self.initialRectWidth
                newHeight = delta.y() + self.initialRectHeight
                if newWidth > self.minWidth:
                    self._rect.setWidth(newWidth)
                    self.w = newWidth
                    self.updateNodeShape()
                if newHeight > self.minHeight:
                    self._rect.setHeight(newHeight)
                    self.updateNodeShape()
            self.update()
        self.lastMousePos = event.pos()

    def mouseReleaseEvent(self, event):
        self.bResize = False
        self.update()
        self.updateOwningCommentNode()
        if self.owningCommentNode != self.pressedCommentNode:
            if self.pressedCommentNode is not None:
                if self in self.pressedCommentNode.owningNodes:
                    self.pressedCommentNode.owningNodes.remove(self)
        super(UINodeBase, self).mouseReleaseEvent(event)

    def clone(self):
        templ = self.serialize()
        templ['name'] = self.name
        templ['uuid'] = str(uuid.uuid4())
        for inp in templ['inputs']:
            inp['uuid'] = str(uuid.uuid4())
        for out in templ['outputs']:
            out['uuid'] = str(uuid.uuid4())
        new_node = self.canvasRef().createNode(templ)
        return new_node

    def call(self, name):
        self._rawNode.call(name)

    def createPropertiesWidget(self, propertiesWidget):
        self.propertyEditor = weakref.ref(propertiesWidget)
        baseCategory = CollapsibleFormWidget(headName="Base")

        le_name = QLineEdit(self.getName())
        le_name.setReadOnly(True)
        baseCategory.addWidget("Name", le_name)

        leUid = QLineEdit(str(self._rawNode.graph().name))
        leUid.setReadOnly(True)
        baseCategory.addWidget("Owning graph", leUid)

        text = "{0}".format(self.packageName)
        if self._rawNode.lib:
            text += " | {0}".format(self._rawNode.lib)
        text += " | {0}".format(self._rawNode.__class__.__name__)
        leType = QLineEdit(text)
        leType.setReadOnly(True)
        baseCategory.addWidget("Type", leType)

        self.propertyEditor().addWidget(baseCategory)

        self.createInputWidgets(self.propertyEditor())

        Info = CollapsibleFormWidget(headName="Info", collapsed=True)
        doc = QTextBrowser()
        doc.setOpenExternalLinks(True)
        doc.setHtml(self.description())
        Info.addWidget(widget=doc)
        self.propertyEditor().addWidget(Info)

    def createInputWidgets(self, propertiesWidget):
        # inputs
        if len([i for i in self.UIinputs.values()]) != 0:
            inputsCategory = CollapsibleFormWidget(headName="Inputs")
            sortedInputs = sorted(self.UIinputs.values(), key=lambda x: x.name)
            for inp in sortedInputs:
                if inp.isArray():
                    # TODO: create list input widget
                    continue
                dataSetter = inp.call if inp.isExec() else inp.setData
                w = createInputWidget(inp.dataType, dataSetter,
                                      inp.defaultValue())
                if w:
                    inp.dataBeenSet.connect(w.setWidgetValueNoSignals)
                    w.blockWidgetSignals(True)
                    w.setWidgetValue(inp.currentData())
                    w.blockWidgetSignals(False)
                    w.setObjectName(inp.getName())
                    inputsCategory.addWidget(inp.name, w)
                    if inp.hasConnections():
                        w.setEnabled(False)
            propertiesWidget.addWidget(inputsCategory)
            return inputsCategory

    def getChainedNodes(self):
        nodes = []
        for pin in self.UIinputs.values():
            for connection in pin.connections:
                node = connection.source().topLevelItem()  # topLevelItem
                nodes.append(node)
                nodes += node.getChainedNodes()
        return nodes

    def kill(self, *args, **kwargs):
        scene = self.scene()
        if scene is not None:
            self.scene().removeItem(self)
            del (self)

    def collidesWithCommentNode(self):
        nodes = self.getCollidedNodes()
        result = None
        for n in nodes:
            if n.isCommentNode:
                result = n
                break
        return result

    def handleVisibility(self):
        if self._rawNode.graph() != self.canvasRef().graphManager.activeGraph(
        ):
            # if current graph != node's graph - hide node and connections
            self.hide()
            for uiPin in self.UIPins.values():
                for connection in uiPin.uiConnectionList:
                    connection.hide()
        else:
            # if current graph == node's graph - show it only if its not under collapsed comment node
            collidedCommentNode = self.collidesWithCommentNode()
            if collidedCommentNode is None:
                self.show()
            else:
                if collidedCommentNode.collapsed:
                    self.hide()
                else:
                    self.show()

    def hoverLeaveEvent(self, event):
        self.resizeStrips = [0, 0, 0, 0, 0]
        self.update()

    def hoverMoveEvent(self, event):
        if self.resizable and not self.collapsed:
            height = self.geometry().height()
            width = self.geometry().width()
            rf = NodeDefaults().CORNERS_ROUND_FACTOR

            leftStrip = QtCore.QRectF(0, rf, self.resizeStripsSize,
                                      height - rf * 2)
            topStrip = QtCore.QRectF(rf, 0, width - rf * 2,
                                     self.resizeStripsSize)
            rightStrip = QtCore.QRectF(width - self.resizeStripsSize, rf,
                                       self.resizeStripsSize, height - rf * 2)
            bottomStrip = QtCore.QRectF(rf, height - self.resizeStripsSize,
                                        width - rf * 2, self.resizeStripsSize)
            bottomRightStrip = QtCore.QRectF(width - rf, height - rf, rf, rf)

            # detect where on the node
            self.resizeStrips[0] = 1 if leftStrip.contains(event.pos()) else 0
            self.resizeStrips[1] = 1 if topStrip.contains(event.pos()) else 0
            self.resizeStrips[2] = 1 if rightStrip.contains(event.pos()) else 0
            self.resizeStrips[3] = 1 if bottomStrip.contains(
                event.pos()) else 0
            self.resizeStrips[4] = 1 if bottomRightStrip.contains(
                event.pos()) else 0
            self.update()

    def Tick(self, delta, *args, **kwargs):
        # NOTE: Do not call wrapped raw node Tick method here!
        # this ui node tick called from underlined raw node's emitted signal
        # do here only UI stuff
        # self.handleVisibility()
        pass

    def addGroupContainer(self, portType, groupName="group"):
        container = QGraphicsWidget()
        container.setObjectName('{0}PinGroupContainerWidget'.format(self.name))
        lyt = QGraphicsLinearLayout()
        lyt.setOrientation(QtCore.Qt.Vertical)
        lyt.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
        lyt.setContentsMargins(1, 1, 1, 1)
        container.group_name = EditableLabel(name=groupName,
                                             node=self,
                                             canvas=self.canvasRef())
        font = QtGui.QFont('Consolas')
        font.setBold(True)
        font.setPointSize(500)
        container.group_name._font = font
        container.group_name.nameLabel.setFont(font)
        container.group_name.nameLabel.update()
        container.group_name.setObjectName('{0}_GroupConnector'.format(
            container.group_name))
        container.group_name.setContentsMargins(0, 0, 0, 0)
        container.group_name.setColor(Colors.AbsoluteBlack)
        grpCon = self.addContainer()
        container.groupIcon = UIGroupPinBase(container)
        lyt.addItem(grpCon)
        container.setLayout(lyt)
        if portType == PinDirection.Input:
            container.group_name.nameLabel.setAlignment(QtCore.Qt.AlignLeft
                                                        | QtCore.Qt.AlignTop)
            grpCon.layout().addItem(container.groupIcon)
            grpCon.layout().addItem(container.group_name)
        else:
            container.group_name.nameLabel.setAlignment(QtCore.Qt.AlignRight
                                                        | QtCore.Qt.AlignTop)
            grpCon.layout().addItem(container.group_name)
            grpCon.layout().addItem(container.groupIcon)
        return container

    def addContainer(self):
        container = QGraphicsWidget()
        container.setObjectName('{0}PinContainerWidget'.format(self.name))
        container.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Maximum)
        container.sizeHint(QtCore.Qt.MinimumSize, QtCore.QSizeF(50.0, 10.0))
        lyt = QGraphicsLinearLayout()
        lyt.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
        lyt.setContentsMargins(1, 1, 1, 1)
        container.setLayout(lyt)
        return container

    def _createUIPinWrapper(self,
                            rawPin,
                            index=-1,
                            group=None,
                            linkedPin=None):
        wrapper = rawPin.getWrapper()
        if wrapper is not None:
            return wrapper()

        p = getUIPinInstance(self, rawPin)
        p.call = rawPin.call

        name = rawPin.name
        lblName = name
        if rawPin.direction == PinDirection.Input:
            self.inputsLayout.addItem(p)
            self.inputsLayout.setAlignment(p, QtCore.Qt.AlignLeft)

        elif rawPin.direction == PinDirection.Output:
            self.outputsLayout.addItem(p)
            self.outputsLayout.setAlignment(p, QtCore.Qt.AlignRight)

        p.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

        self.update()
        # self.nodeMainGWidget.update()
        self.updateNodeShape()
        p.syncDynamic()
        p.syncRenamable()
        if self.collapsed:
            p.hide()
        return p

    def collapsePinGroup(self, container):
        for i in range(1, container.layout().count()):
            item = container.layout().itemAt(i)
            pin = item.layout().itemAt(0) if isinstance(
                item.layout().itemAt(0),
                UIPinBase) else item.layout().itemAt(1)
            if pin.hasConnections:
                if pin.direction == PinDirection.Input:
                    for ege in pin.connections:
                        ege.drawDestination = container.layout().itemAt(
                            0).layout().itemAt(0)
                if pin.direction == PinDirection.Output:
                    for ege in pin.connections:
                        ege.drawSource = container.layout().itemAt(
                            0).layout().itemAt(1)
            item.hide()

    def expandPinGroup(self, container):
        for i in range(1, container.layout().count()):
            item = container.layout().itemAt(i)
            pin = item.layout().itemAt(0) if isinstance(
                item.layout().itemAt(0),
                UIPinBase) else item.layout().itemAt(1)
            if pin.hasConnections:
                if pin.direction == PinDirection.Input:
                    for ege in pin.connections:
                        ege.drawDestination = pin
                if pin.direction == PinDirection.Output:
                    for ege in pin.connections:
                        ege.drawSource = pin
            item.show()
Esempio n. 30
0
class subgraphNode(Node):
    def __init__(self, name, graph):
        from ..Core import Widget as Widget

        super(subgraphNode, self).__init__(name, graph)
        self.menu = QMenu()
        self.actionExport = self.menu.addAction('export')
        self.actionExport.triggered.connect(self.export)

        self._graph = Widget.GraphWidget("graph", self.graph().parent)
        self._graph.outPinCreated.connect(self.createOutput)
        self._graph.inPinCreated.connect(self.createInput)
        self.dlg = MyDialog()
        self.styleSheetEditor = self.graph().styleSheetEditor
        self.dlg.setStyleSheet(self.styleSheetEditor.getStyleSheet())
        self.dlg.setLayout(QtWidgets.QHBoxLayout())
        self.dlg.layout().addWidget(self._graph)
        self._category = "CustomGraphs"
        self._keywords = "CustomGraphs"
        self._description = "Custom SubGraph"
        #self.bCallable = True
        self.dinOutputs = {}
        self.dinInputs = {}

    def createInput(self, pin):
        p = self.addInputPin(pin.name,
                             DataTypes.Any,
                             constraint="in%s" % pin.name)
        p.setType(pin)
        pin.nameChanged.connect(p.setName)
        pin.constraint = "in%s" % pin.name
        self._Constraints["in%s" % pin.name].append(pin)
        self._graph.inputsItem._Constraints["in%s" % pin.name] = [pin, p]
        pin.OnPinDeleted.connect(self.deletePort)
        pin.dataBeenSet.connect(p.setData)
        pinAffects(pin, p)
        #pinAffects(p,pin)
        #p.dataBeenSet.connect(pin.setData)
        self.dinInputs[pin] = p

        for i in self.inputs.values():
            for o in self.outputs.values():
                pinAffects(i, o)

    def createOutput(self, pin):
        p = self.addOutputPin(pin.name,
                              DataTypes.Any,
                              constraint="out%s" % pin.name)
        p.setType(pin)
        pin.nameChanged.connect(p.setName)
        pin.constraint = "out%s" % pin.name
        self._Constraints["out%s" % pin.name].append(pin)
        self._graph.outputsItem._Constraints["out%s" % pin.name] = [pin, p]
        pin.OnPinDeleted.connect(self.deletePort)
        pin.dataBeenSet.connect(p.setData)
        #p.dataBeenSet.connect(pin.setData)
        pinAffects(pin, p)
        #pinAffects(p,pin)
        self.dinOutputs[pin] = p
        for i in self.inputs.values():
            for o in self.outputs.values():
                pinAffects(i, o)

    def serialize(self):
        template = super(subgraphNode, self).serialize()
        graphData = self._graph.getGraphSaveData()
        template["graphData"] = graphData
        return template

    def export(self):
        from . import _nodeClasses
        from ..FunctionLibraries import _foos
        from ..SubGraphs import _subgraphClasses
        from .. import SubGraphs
        existing_nodes = [n for n in _nodeClasses]
        existing_nodes += [n for n in _foos]
        existing_nodes += [n for n in _subgraphClasses]

        graphData = self._graph.getGraphSaveData()
        graphData["Type"] = "subgraph"
        graphData["category"] = self._category
        graphData["keywords"] = self._keywords
        graphData["description"] = self._description
        name_filter = "Graph files (*.pySubgraph)"

        pth = QFileDialog.getSaveFileName(filter=name_filter)
        if not pth == '':
            file_path = pth
            path, name = os.path.split(file_path)
            name, ext = os.path.splitext(name)
            if name in existing_nodes:
                print("[ERROR] Node {0} already exists! Chose another name".
                      format(name))
                return
            # write to file. delete older if needed
            with open(file_path, "wb") as f:

                def to_serializable(val):
                    return {"name": None}
                    return str(val)

                json.dump(graphData,
                          f,
                          skipkeys=True,
                          default=to_serializable,
                          indent=2)
            reload(SubGraphs)
            SubGraphs._getClasses()

    def contextMenuEvent(self, event):
        self.menu.exec_(event.screenPos())

    def postCreate(self, jsonTemplate):

        if "graphData" in jsonTemplate:
            self._graph.loadFromData(jsonTemplate["graphData"])
        # restore pins
        for inp in self._graph.inputsItem.outputs.values():
            self.createInput(inp)
        for out in self._graph.outputsItem.inputs.values():
            self.createOutput(out)
        super(subgraphNode, self).postCreate(jsonTemplate)

    def deletePort(self, pin):
        if pin in self.dinInputs:
            self.dinInputs[pin].kill()
            del self.dinInputs[pin]
        elif pin in self.dinOutputs:
            self.dinOutputs[pin].kill()
            del self.dinOutputs[pin]

    @staticmethod
    def pinTypeHints():
        '''
            used by nodebox to suggest supported pins
            when drop wire from pin into empty space
        '''
        return {'inputs': [], 'outputs': []}

    @staticmethod
    def category():
        return 'Common'

    @staticmethod
    def keywords():
        return []

    @staticmethod
    def description():
        '''
            used by property view and node box widgets
        '''
        return 'Encapsulate a graph inside a node'

    def mouseDoubleClickEvent(self, event):
        #Node.mouseDoubleClickEvent( event)
        self.OnDoubleClick(self.mapToScene(event.pos()))
        event.accept()

    def OnDoubleClick(self, pos):
        self.dlg.show()

    def compute(self):
        for key, value in self.dinInputs.iteritems():
            key.setData(value.getData())
        for key, value in self.dinOutputs.iteritems():
            value.setData(key.getData())
Esempio n. 31
0
class UIConnection(QGraphicsPathItem):
    """UIConnection is a cubic spline curve. It represents connecton between two pins.
    """
    def __init__(self, source, destination, canvas):
        QGraphicsPathItem.__init__(self)
        self.setAcceptedMouseButtons(QtCore.Qt.LeftButton)
        self.setAcceptHoverEvents(True)
        self.setFlag(QGraphicsPathItem.ItemIsSelectable)
        self._menu = QMenu()
        self.actionDisconnect = self._menu.addAction("Disconnect")
        self.actionDisconnect.triggered.connect(self.kill)
        self._uid = uuid4()
        self.canvasRef = weakref.ref(canvas)
        self.source = weakref.ref(source)
        self.destination = weakref.ref(destination)
        self.drawSource = self.source()
        self.drawDestination = self.destination()

        # Overrides for getting endpoints positions
        # if None - pin centers will be used
        self.sourcePositionOverride = None
        self.destinationPositionOverride = None

        self.mPath = QtGui.QPainterPath()

        self.cp1 = QtCore.QPointF(0.0, 0.0)
        self.cp2 = QtCore.QPointF(0.0, 0.0)

        self.setZValue(NodeDefaults().Z_LAYER - 1)

        self.color = self.source().color()
        self.selectedColor = self.color.lighter(150)

        self.thickness = 1
        if source.isExec():
            self.thickness = 2

        self.pen = QtGui.QPen(self.color, self.thickness, QtCore.Qt.SolidLine,
                              QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin)

        points = self.getEndPoints()
        self.updateCurve(points[0], points[1])

        self.setPen(self.pen)

        self.source().update()
        self.destination().update()
        self.fade = 0.0
        self.source().uiConnectionList.append(self)
        self.destination().uiConnectionList.append(self)
        self.source().pinConnected(self.destination())
        self.destination().pinConnected(self.source())

    def setSelected(self, value):
        super(UIConnection, self).setSelected(value)

    def isUnderCollapsedComment(self):
        srcNode = self.source().owningNode()
        dstNode = self.destination().owningNode()
        srcComment = srcNode.owningCommentNode
        dstComment = dstNode.owningCommentNode
        if srcComment is not None and dstComment is not None and srcComment == dstComment and srcComment.collapsed:
            return True
        return False

    def isUnderActiveGraph(self):
        return self.canvasRef().graphManager.activeGraph() == self.source(
        )._rawPin.owningNode().graph()

    def __repr__(self):
        return "{0} -> {1}".format(self.source().getFullName(),
                                   self.destination().getFullName())

    def setColor(self, color):
        self.pen.setColor(color)
        self.color = color

    def updateEndpointsPositions(self):
        srcNode = self.source().owningNode()
        dstNode = self.destination().owningNode()

        srcComment = srcNode.owningCommentNode
        if srcComment is not None:
            # if comment is collapsed or under another comment, move point to top most collapsed comment's right side
            srcNodeUnderCollapsedComment = srcComment.isUnderCollapsedComment()
            topMostCollapsedComment = srcNode.getTopMostOwningCollapsedComment(
            )
            if srcComment.collapsed:
                rightSideEndpointGetter = srcComment.getRightSideEdgesPoint
                if srcNodeUnderCollapsedComment:
                    rightSideEndpointGetter = topMostCollapsedComment.getRightSideEdgesPoint
                self.sourcePositionOverride = rightSideEndpointGetter
            else:
                if srcNodeUnderCollapsedComment:
                    self.sourcePositionOverride = topMostCollapsedComment.getRightSideEdgesPoint
                else:
                    self.sourcePositionOverride = None
        else:
            # if no comment return source point back to pin
            self.sourcePositionOverride = None

        # Same for right hand side
        dstComment = dstNode.owningCommentNode
        if dstComment is not None:
            dstNodeUnderCollapsedComment = dstComment.isUnderCollapsedComment()
            topMostCollapsedComment = dstNode.getTopMostOwningCollapsedComment(
            )
            if dstComment.collapsed:
                rightSideEndpointGetter = dstComment.getLeftSideEdgesPoint
                if dstNodeUnderCollapsedComment:
                    rightSideEndpointGetter = topMostCollapsedComment.getLeftSideEdgesPoint
                self.destinationPositionOverride = rightSideEndpointGetter
            else:
                if dstNodeUnderCollapsedComment:
                    self.destinationPositionOverride = topMostCollapsedComment.getLeftSideEdgesPoint
                else:
                    self.destinationPositionOverride = None
        else:
            self.destinationPositionOverride = None

    def Tick(self):
        # check if this instance represents existing connection
        # if not - destroy
        if not arePinsConnected(self.source()._rawPin,
                                self.destination()._rawPin):
            self.canvasRef().removeConnection(self)

        if self.drawSource._rawPin.isExec(
        ) or self.drawDestination._rawPin.isExec():
            if self.thickness != 2:
                self.thickness = 2
                self.pen.setWidthF(self.thickness)
                self.update()

        if self.isSelected():
            self.pen.setColor(self.selectedColor)
        else:
            self.pen.setColor(self.color)
        self.update()

    def contextMenuEvent(self, event):
        self._menu.exec_(event.screenPos())

    @property
    def uid(self):
        return self._uid

    @uid.setter
    def uid(self, value):
        if self._uid in self.canvasRef().connections:
            self.canvasRef().connections[value] = self.canvasRef(
            ).connections.pop(self._uid)
            self._uid = value

    @staticmethod
    def deserialize(data, graph):
        srcUUID = UUID(data['sourceUUID'])
        dstUUID = UUID(data['destinationUUID'])
        # if srcUUID in graph.pins and dstUUID in graph.pins:
        srcPin = graph.findPinByUid(srcUUID)
        assert (srcPin is not None)
        dstPin = graph.findPinByUid(dstUUID)
        assert (dstPin is not None)
        connection = graph.connectPinsInternal(srcPin, dstPin)
        assert (connection is not None)
        connection.uid = UUID(data['uuid'])

    def serialize(self):
        script = {
            'sourceUUID': str(self.source().uid),
            'destinationUUID': str(self.destination().uid),
            'sourceName': self.source()._rawPin.getFullName(),
            'destinationName': self.destination()._rawPin.getFullName(),
            'uuid': str(self.uid)
        }
        return script

    def __str__(self):
        return '{0} >>> {1}'.format(self.source()._rawPin.getFullName(),
                                    self.destination()._rawPin.getFullName())

    def drawThick(self):
        self.pen.setWidthF(self.thickness + (self.thickness / 1.5))
        f = 0.5
        r = abs(lerp(self.color.red(), Colors.Yellow.red(), clamp(f, 0, 1)))
        g = abs(lerp(self.color.green(), Colors.Yellow.green(), clamp(f, 0,
                                                                      1)))
        b = abs(lerp(self.color.blue(), Colors.Yellow.blue(), clamp(f, 0, 1)))
        self.pen.setColor(QtGui.QColor.fromRgb(r, g, b))

    def restoreThick(self):
        self.pen.setWidthF(self.thickness)
        self.pen.setColor(self.color)

    def hoverEnterEvent(self, event):
        super(UIConnection, self).hoverEnterEvent(event)
        self.drawThick()
        self.update()

    def getEndPoints(self):
        p1 = self.drawSource.scenePos() + self.drawSource.pinCenter()
        if self.sourcePositionOverride is not None:
            p1 = self.sourcePositionOverride()

        p2 = self.drawDestination.scenePos() + self.drawDestination.pinCenter()
        if self.destinationPositionOverride is not None:
            p2 = self.destinationPositionOverride()
        return p1, p2

    def mousePressEvent(self, event):
        super(UIConnection, self).mousePressEvent(event)
        event.accept()

    def mouseReleaseEvent(self, event):
        super(UIConnection, self).mouseReleaseEvent(event)
        event.accept()

    def mouseMoveEvent(self, event):
        super(UIConnection, self).mouseMoveEvent(event)
        event.accept()

    def hoverLeaveEvent(self, event):
        super(UIConnection, self).hoverLeaveEvent(event)
        self.restoreThick()
        self.update()

    def source_port_name(self):
        return self.source().getFullName()

    def shape(self):
        qp = QtGui.QPainterPathStroker()
        qp.setWidth(10.0)
        qp.setCapStyle(QtCore.Qt.SquareCap)
        return qp.createStroke(self.path())

    def updateCurve(self, p1, p2):
        xDistance = p2.x() - p1.x()
        multiply = 3
        self.mPath = QtGui.QPainterPath()

        self.mPath.moveTo(p1)
        if xDistance < 0:
            self.mPath.cubicTo(
                QtCore.QPoint(p1.x() + xDistance / -multiply, p1.y()),
                QtCore.QPoint(p2.x() - xDistance / -multiply, p2.y()), p2)
        else:
            self.mPath.cubicTo(
                QtCore.QPoint(p1.x() + xDistance / multiply, p1.y()),
                QtCore.QPoint(p2.x() - xDistance / 2, p2.y()), p2)

        self.setPath(self.mPath)

    def kill(self):
        self.canvasRef().removeConnection(self)

    def paint(self, painter, option, widget):

        option.state &= ~QStyle.State_Selected

        lod = self.canvasRef().getLodValueFromCurrentScale(5)

        self.setPen(self.pen)
        p1, p2 = self.getEndPoints()

        if lod >= 5:
            self.mPath = QtGui.QPainterPath()
            self.mPath.moveTo(p1)
            self.mPath.lineTo(p2)
        else:
            xDistance = p2.x() - p1.x()
            vDistance = p2.y() - p1.y()
            offset = abs(xDistance) * 0.5
            defOffset = 150
            if abs(xDistance) < defOffset:
                offset = defOffset / 2
            if abs(vDistance) < 20:
                offset = abs(xDistance) * 0.3
            multiply = 2
            self.mPath = QtGui.QPainterPath()
            self.mPath.moveTo(p1)
            if xDistance < 0:
                self.cp1 = QtCore.QPoint(p1.x() + offset, p1.y())
                self.cp2 = QtCore.QPoint(p2.x() - offset, p2.y())
            else:
                self.cp2 = QtCore.QPoint(p2.x() - offset, p2.y())
                self.cp1 = QtCore.QPoint(p1.x() + offset, p1.y())
            self.mPath.cubicTo(self.cp1, self.cp2, p2)

        self.setPath(self.mPath)
        super(UIConnection, self).paint(painter, option, widget)
Esempio n. 32
0
class UIPinBase(QGraphicsWidget):
    """UI pin wrapper.
    """

    # Event called when pin is connected
    OnPinConnected = QtCore.Signal(object)
    # Event called when pin is disconnected
    OnPinDisconnected = QtCore.Signal(object)
    # Event called when data been set
    dataBeenSet = QtCore.Signal(object)
    # Event called when pin name changes
    displayNameChanged = QtCore.Signal(str)
    OnPinChanged = QtCore.Signal(object)
    OnPinDeleted = QtCore.Signal(object)

    def __init__(self, owningNode, raw_pin):
        """UI wrapper for :class:`PyFlow.Core.PinBase`

        :param owningNode: Owning node
        :type owningNode: :class:`PyFlow.UI.Canvas.NodeBase`
        :param raw_pin: PinBase reference
        :type raw_pin: :class:`PyFlow.Core.PinBase`
        """

        super(UIPinBase, self).__init__()
        self.setGraphicsItem(self)
        self.setFlag(QGraphicsWidget.ItemSendsGeometryChanges)
        self.setCacheMode(self.DeviceCoordinateCache)
        self.setAcceptHoverEvents(True)
        self.setZValue(1)
        self.setParentItem(owningNode)

        self.UiNode = weakref.ref(owningNode)
        self._rawPin = raw_pin
        self.watchWidget = None
        if self._rawPin is not None:
            self._rawPin.serializationHook.connect(self.serializationHook)
            self._rawPin.containerTypeChanged.connect(
                self.onContainerTypeChanged)
            self._displayName = self._rawPin.name
            self._rawPin.setWrapper(self)
            self._rawPin.killed.connect(self.kill)
            self._rawPin.nameChanged.connect(self.setDisplayName)

            # Context menu for pin
            self.menu = QMenu()
            self.menu.addAction("Rename").triggered.connect(self.onRename)
            self.menu.addAction("Remove").triggered.connect(self._rawPin.kill)
            self.actionDisconnect = self.menu.addAction('Disconnect all')
            self.actionDisconnect.triggered.connect(self._rawPin.disconnectAll)
            self.actionResetValue = self.menu.addAction("Reset value")
            self.actionResetValue.triggered.connect(self.resetToDefault)
            if self._rawPin._structure == PinStructure.Multi:
                self.menu.addAction("changeStructure").triggered.connect(
                    self.selectStructure)
            self.actionWatchValue = self.menu.addAction("Watch")
            self.actionWatchValue.triggered.connect(self.toggleWatchValue)
            self._rawPin.dataBeenSet.connect(self.updateWatchWidgetValue)
            self.actionCopyPath = self.menu.addAction("Copy path")
            self.actionCopyPath.triggered.connect(self.onCopyPathToClipboard)

        # GUI
        self._font = QtGui.QFont("Consolas")
        self._font.setPointSize(6)
        self.pinSize = 6
        self.hovered = False
        self.bLabelHidden = False
        if self._rawPin is not None:
            self._pinColor = QtGui.QColor(*self._rawPin.color())
        self._labelColor = QtCore.Qt.white
        self._execPen = QtGui.QPen(Colors.White, 0.5, QtCore.Qt.SolidLine)
        self._dirty_pen = QtGui.QPen(Colors.DirtyPen, 0.5, QtCore.Qt.DashLine,
                                     QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin)

        self.uiConnectionList = []

        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum)
        self.pinCircleDrawOffset = QtCore.QPointF()
        # TODO: This is check is for PinGroup. Improve it
        if self._rawPin is not None:
            self.setToolTip(self._rawPin.description)

    def onCopyPathToClipboard(self):
        QApplication.clipboard().clear()
        QApplication.clipboard().setText(self.path())

    def toggleWatchValue(self):
        if self.watchWidget is not None:
            self.scene().removeItem(self.watchWidget)
            self.watchWidget = None
        else:
            scene = self.owningNode().canvasRef().scene()
            self.watchWidget = WatchItem()
            scene.addItem(self.watchWidget)
            self.watchWidget.setZValue(NodeDefaults().Z_LAYER + 1)
            self.updateWatchWidgetPosition()
            self.updateWatchWidgetValue(self.currentData())

    def path(self):
        return self._rawPin.path()

    def updateWatchWidgetPosition(self):
        if self.watchWidget is not None:
            scenePos = self.sceneBoundingRect().bottomLeft(
            ) if self.direction == PinDirection.Input else self.sceneBoundingRect(
            ).bottomRight()
            self.watchWidget.setPos(scenePos)

    def updateWatchWidgetValue(self, *args, **kwargs):
        if self.watchWidget is not None:
            content = "Value: {0}".format(str(self.currentData()))
            if self.isAny:
                content += "\nActive data type: {0}".format(
                    self._rawPin.activeDataType)
                content += "\nSuper: {0}".format(self._rawPin.super)
            self.watchWidget.setPlainText(content)
            self.updateWatchWidgetPosition()

    def heartBeat(self):
        self.updateWatchWidgetPosition()

    def getInputWidgetVariant(self):
        return self._rawPin.getInputWidgetVariant()

    @property
    def labelColor(self):
        return self._labelColor

    @labelColor.setter
    def labelColor(self, value):
        self._labelColor = value

    def pinCenter(self):
        """Point relative to pin widget, where circle is drawn."""

        frame = QtCore.QRectF(QtCore.QPointF(0, 0), self.geometry().size())
        halfPinSize = self.pinSize / 2
        pinX = self.pinSize
        pinY = (frame.height() / 2)
        if not self.bLabelHidden:
            if self.direction == PinDirection.Output:
                pinX = frame.width() - self.pinSize + halfPinSize
        result = QtCore.QPointF(pinX, pinY)
        if self.owningNode().collapsed:
            labelHeight = self.owningNode().labelHeight
            #labelHeight += self.owningNode().nodeLayout.spacing()
            if self.direction == PinDirection.Input:
                result = self.mapFromItem(self.owningNode(),
                                          QtCore.QPointF(0, labelHeight))
            if self.direction == PinDirection.Output:
                result = self.mapFromItem(
                    self.owningNode(),
                    QtCore.QPointF(
                        self.owningNode().sizeHint(None, None).width(),
                        labelHeight))
        return result

    def onContainerTypeChanged(self, *args, **kwargs):
        # underlined pin is changed to list or dict
        # update to redraw shape
        self.update()

    def setLabel(self, labelItem):
        if self._label is None:
            self._label = weakref.ref(labelItem)

    def displayName(self):
        return self._displayName

    def setDisplayName(self, displayName):
        if displayName != self._displayName:
            self._displayName = displayName
            self.displayNameChanged.emit(self._displayName)
            self.prepareGeometryChange()
            self.updateGeometry()
            self.update()

    def jsonEncoderClass(self):
        return self._rawPin.jsonEncoderClass()

    def jsonDecoderClass(self):
        return self._rawPin.jsonDecoderClass()

    @property
    def owningNode(self):
        return self.UiNode

    @property
    def constraint(self):
        return self._rawPin.constraint

    @property
    def isAny(self):
        return self._rawPin.isAny()

    def setMenuItemEnabled(self, actionName, bEnabled):
        for action in self.menu.actions():
            if action.text() == actionName:
                if bEnabled != action.isEnabled() and action.isVisible():
                    action.setEnabled(bEnabled)
                    action.setVisible(bEnabled)

    def syncRenamable(self):
        renamingEnabled = self._rawPin.optionEnabled(
            PinOptions.RenamingEnabled)
        # self._label()._isEditable = renamingEnabled
        self.setMenuItemEnabled("Rename", renamingEnabled)

    def onRename(self):
        name, confirmed = QInputDialog.getText(None, "Rename",
                                               "Enter new pin name")
        if confirmed and name != self.name and name != "":
            uniqueName = self._rawPin.owningNode().getUniqPinName(name)
            self.setName(uniqueName)
            self.setDisplayName(uniqueName)
            self.owningNode().invalidateNodeLayouts()
            self.owningNode().updateNodeShape()

    def syncDynamic(self):
        self.setMenuItemEnabled("Remove",
                                self._rawPin.optionEnabled(PinOptions.Dynamic))

    @property
    def structureType(self):
        return self._rawPin.structureType

    @property
    def dirty(self):
        return self._rawPin.dirty

    @dirty.setter
    def dirty(self, value):
        self._rawPin.dirty = value

    def resetToDefault(self):
        self.setData(self.defaultValue())

    def defaultValue(self):
        return self._rawPin.defaultValue()

    def currentData(self):
        return self._rawPin.currentData()

    @property
    def name(self):
        return self._rawPin.name

    def getFullName(self):
        return self._rawPin.getFullName()

    def hasConnections(self):
        return self._rawPin.hasConnections()

    def setClean(self):
        self._rawPin.setClean()

    def setDirty(self):
        self._rawPin.setDirty()

    @property
    def _data(self):
        return self._rawPin._data

    @_data.setter
    def _data(self, value):
        self._rawPin._data = value

    @property
    def affects(self):
        return self._rawPin.affects

    @property
    def direction(self):
        return self._rawPin.direction

    @property
    def affected_by(self):
        return self._rawPin.affected_by

    def supportedDataTypes(self):
        return self._rawPin.supportedDataTypes()

    @property
    def connections(self):
        return self.uiConnectionList

    @property
    def uid(self):
        return self._rawPin._uid

    @uid.setter
    def uid(self, value):
        self._rawPin._uid = value

    def color(self):
        return self._pinColor

    def setName(self, newName, force=False):
        return self._rawPin.setName(newName, force=force)

    def setData(self, value):
        self._rawPin.setData(value)
        self.dataBeenSet.emit(value)

    def getData(self):
        return self._rawPin.getData()

    def call(self):
        self._rawPin.call()

    def kill(self, *args, **kwargs):
        """this will be called after raw pin is deleted
        """
        scene = self.scene()
        if scene is None:
            del self
            return

        if self._rawPin.direction == PinDirection.Input:
            self.owningNode().inputsLayout.removeItem(self)
        else:
            self.owningNode().outputsLayout.removeItem(self)

        self.OnPinDeleted.emit(self)
        try:
            scene = self.scene()
            if scene is None:
                del self
                return
            scene.removeItem(self)
            self.owningNode().updateNodeShape()
        except:
            pass

    def assignRawPin(self, rawPin):
        if rawPin is not self._rawPin:
            self._rawPin = rawPin
            self.call = rawPin.call
            self._rawPin.setWrapper(self)
            self._pinColor = QtGui.QColor(*self._rawPin.color())

    def serializationHook(self, *args, **kwargs):
        data = {}
        data['bLabelHidden'] = self.bLabelHidden
        data['displayName'] = self.displayName()
        return data

    def serialize(self):
        return self._rawPin.serialize()

    def getContainer(self):
        return self._container

    def isExec(self):
        return self._rawPin.isExec()

    @property
    def dataType(self):
        return self._rawPin.dataType

    def sizeHint(self, which, constraint):
        height = QtGui.QFontMetrics(self._font).height()
        width = self.pinSize * 2
        if not self.bLabelHidden:
            width += QtGui.QFontMetrics(self._font).width(self.displayName())
        return QtCore.QSizeF(width, height)

    def shape(self):
        path = QtGui.QPainterPath()
        path.addEllipse(self.boundingRect())
        return path

    def isArray(self):
        return self._rawPin.isArray()

    def isDict(self):
        return self._rawPin.isDict()

    def paint(self, painter, option, widget):
        if self.isArray():
            PinPainter.asArrayPin(self, painter, option, widget)
        elif self.isDict():
            PinPainter.asDictPin(self, painter, option, widget)
        else:
            PinPainter.asValuePin(self, painter, option, widget)

    def contextMenuEvent(self, event):
        self.menu.exec_(event.screenPos())

    def getLayout(self):
        if self.direction == PinDirection.Input:
            return self.owningNode().inputsLayout
        else:
            return self.owningNode().outputsLayout

    @property
    def description(self):
        return self._rawPin.description

    @description.setter
    def description(self, value):
        self._rawPin.description = value
        self.setToolTip(self._rawPin.description)

    def hoverEnterEvent(self, event):
        super(UIPinBase, self).hoverEnterEvent(event)
        self.update()
        self.hovered = True
        event.accept()

    def hoverLeaveEvent(self, event):
        super(UIPinBase, self).hoverLeaveEvent(event)
        self.update()
        self.hovered = False

    def pinConnected(self, other):
        self.OnPinConnected.emit(other)
        self.update()

    def pinDisconnected(self, other):
        self.OnPinDisconnected.emit(other)
        self.update()

    def selectStructure(self):
        item, ok = QInputDialog.getItem(None, "", "",
                                        ([i.name for i in list(PinStructure)]),
                                        0, False)
        if ok and item:
            self._rawPin.changeStructure(PinStructure[item], True)