Example #1
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)
Example #2
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()))
Example #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()))
Example #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())
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())
Example #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())
Example #7
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())
Example #8
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()
Example #9
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())
Example #10
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()]))
Example #11
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())
Example #12
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()
Example #13
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
Example #14
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())
Example #15
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()]))
Example #16
0
    def _on_show_menu(self):
        """
        Internal callback function that is called when menu button is clicked byu the user
        :return: QAction
        """

        menu = QMenu(self)
        self._item_view.context_edit_menu(menu)
        point = QCursor.pos()
        point.setX(point.x() + 3)
        point.setY(point.y() + 3)

        return menu.exec_(point)
Example #17
0
    def _on_show_menu(self):
        """
        Internal callback function triggered when item menu should be opened
        :return: QAction
        """

        menu = QMenu(self)
        self.item().context_edit_menu(menu)
        point = QCursor.pos()
        point.setX(point.x() + 3)
        point.setY(point.y() + 3)

        return menu.exec_(point)
Example #18
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)
Example #19
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)
Example #20
0
class UIConnection(QGraphicsPathItem):
    """UIConnection is a cubic spline curve. It represents connection 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
        self.thicknessMultiplier = 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())

        if self.source().isExec():
            self.bubble = QGraphicsEllipseItem(-2.5, -2.5, 5, 5, self)
            self.bubble.setBrush(self.color)
            self.bubble.setPen(self.pen)

            point = self.mPath.pointAtPercent(0.0)
            self.bubble.setPos(point)

            self.bubble.hide()
            self.source()._rawPin.onExecute.connect(self.performEvaluationFeedback)
            self.shouldAnimate = False
            self.timeline = QtCore.QTimeLine(2000)
            self.timeline.setFrameRange(0, 100)
            self.timeline.frameChanged.connect(self.timelineFrameChanged)
            self.timeline.setLoopCount(0)

    def performEvaluationFeedback(self, *args, **kwargs):
        if self.timeline.state() == QtCore.QTimeLine.State.NotRunning:
            self.shouldAnimate = True
            # spawn bubble
            self.bubble.show()
            self.timeline.start()

    def timelineFrameChanged(self, frameNum):
        percentage = currentProcessorTime() - self.source()._rawPin.getLastExecutionTime()
        self.shouldAnimate = percentage < 0.5
        point = self.mPath.pointAtPercent(float(frameNum) / float(self.timeline.endFrame()))
        self.bubble.setPos(point)
        if not self.shouldAnimate:
            self.timeline.stop()
            self.bubble.hide()

    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.isExec() or self.drawDestination.isExec():
            if self.thickness != 2:
                self.thickness = 2
                self.pen.setWidthF(self.thickness)

        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().getCanvasLodValueFromCurrentScale()

        self.setPen(self.pen)
        p1, p2 = self.getEndPoints()
        if editableStyleSheet().ConnectionMode[0] in [ConnectionTypes.Circuit,ConnectionTypes.ComplexCircuit]:
            sameSide = 0
            offset = 15
            roundnes = editableStyleSheet().ConnectionRoundness[0]
            if self.destination().owningNode()._rawNode.__class__.__name__ in ["reroute", "rerouteExecs"]:
                xDistance = p2.x() - p1.x()
                if xDistance < 0:
                    p2, p1 = self.getEndPoints()
                    sameSide = 1
            if self.source().owningNode()._rawNode.__class__.__name__ in ["reroute", "rerouteExecs"]:
                p11, p22 = self.getEndPoints()
                xDistance = p22.x() - p11.x()
                if xDistance < 0:
                    sameSide = -1
                    p1, p2 = self.getEndPoints()
            self.mPath = ConnectionPainter.BasicCircuit(p1, p2, offset, roundnes, sameSide,lod,editableStyleSheet().ConnectionMode[0]==ConnectionTypes.ComplexCircuit)

        elif editableStyleSheet().ConnectionMode[0] == ConnectionTypes.Cubic:
            self.mPath = ConnectionPainter.Cubic(p1, p2, 150,lod)

        self.setPath(self.mPath)
        
        super(UIConnection, self).paint(painter, option, widget)
Example #21
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)
Example #22
0
class OptionList(QGroupBox, object):
    editModeChanged = Signal(bool)
    valueChanged = Signal()

    FACTORY_CLASS = factory

    def __init__(self, parent=None, option_object=None):
        super(OptionList, self).__init__(parent)
        self._option_object = option_object
        self._parent = parent

        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self._on_item_menu)
        self._context_menu = QMenu()
        self._context_menu.setTearOffEnabled(True)
        self._create_context_menu(self._context_menu, parent=self)

        self._has_first_group = False
        self._disable_auto_expand = False
        self._supress_update = False
        self._central_list = self
        self._option_group_class = OptionListGroup
        self._auto_rename = False

        self.setup_ui()

    def mousePressEvent(self, event):
        """
        Overrides base QGroupBox mousePressEvent function
        :param event: QMouseEvent
        """

        widget_at_mouse = qtutils.get_widget_at_mouse()
        if widget_at_mouse == self:
            self.clear_selection()
        super(OptionList, self).mousePressEvent(event)

    def setup_ui(self):
        self.main_layout = layouts.VerticalLayout(spacing=5,
                                                  margins=(5, 5, 5, 5))
        self.setLayout(self.main_layout)

        self.child_layout = layouts.VerticalLayout(spacing=5,
                                                   margins=(5, 5, 5, 5))
        self.child_layout.setAlignment(Qt.AlignTop)
        self.main_layout.addLayout(self.child_layout)
        self.main_layout.addSpacing(30)

    def get_option_object(self):
        """
        Returns the option object linked to this widget
        :return: object
        """

        return self._option_object

    def set_option_object(self, option_object):
        """
        Sets option object linked to this widget
        :param option_object: object
        """

        self._option_object = option_object

    def update_options(self):
        """
        Updates current widget options
        """

        if not self._option_object:
            LOGGER.warning(
                'Impossible to update options because option object is not defined!'
            )
            return

        options = self._option_object.get_options()

        self._load_widgets(options)

    def get_parent(self):
        """
        Returns parent Option
        """

        parent = self.parent()
        grand_parent = parent.parent()
        if hasattr(grand_parent, 'group'):
            parent = grand_parent
        if not hasattr(parent, 'child_layout'):
            return

        if parent.__class__.__name__.endswith('OptionList'):
            return parent

        return parent

    def add_group(self, name='group', value=True, parent=None):
        """
        Adds new group property to the group box
        :param name: str
        :param value: bool, default value
        :param parent: Option
        """

        if type(name) == bool:
            name = 'group'

        name = self._get_unique_name(name, parent)
        option_object = self.get_option_object()
        self._option_group_class.FACTORY_CLASS = self.FACTORY_CLASS
        group = self._option_group_class(name=name,
                                         option_object=option_object,
                                         parent=self._parent)
        self._create_group_context_menu(group, group._context_menu)
        group.set_expanded(value)
        if self.__class__.__name__.endswith(
                'OptionListGroup') or parent.__class__.__name__.endswith(
                    'OptionListGroup'):
            if dcc.is_maya():
                group.group.set_inset_dark()
        self._handle_parenting(group, parent)
        self._write_options(clear=False)
        self._has_first_group = True

        return group

    def update_current_widget(self, widget=None):
        """
        Function that updates given widget status
        :param widget: QWidget
        """

        if self._parent.is_edit_mode() is False:
            return

        if widget:
            if self.is_selected(widget):
                self.deselect_widget(widget)
                return
            else:
                self.select_widget(widget)
                return

    def is_selected(self, widget):
        """
        Returns whether property widget is selected or not
        :param widget: QWidget
        :return: bool
        """

        if widget in self._parent._current_widgets:
            return True

        return False

    def select_widget(self, widget):
        """
        Selects given Option widget
        :param widget: Option
        """

        if hasattr(widget, 'child_layout'):
            self._deselect_children(widget)

        parent = widget.get_parent()
        if not parent:
            parent = widget.parent()

        out_of_scope = None
        if parent:
            out_of_scope = self.sort_widgets(self._parent._current_widgets,
                                             parent,
                                             return_out_of_scope=True)
        if out_of_scope:
            for sub_widget in out_of_scope:
                self.deselect_widget(sub_widget)

        self._parent._current_widgets.append(widget)
        self._fill_background(widget)

    def deselect_widget(self, widget):
        """
        Deselects given Option widget
        :param widget: Option
        """

        if not self.is_selected(widget):
            return

        widget_index = self._parent._current_widgets.index(widget)
        self._parent._current_widgets.pop(widget_index)
        self._unfill_background(widget)

    def clear_selection(self):
        """
        Clear current selected Option widgets
        """

        for widget in self._parent._current_widgets:
            self._unfill_background(widget)

        self._parent._current_widgets = list()

    def sort_widgets(self, widgets, parent, return_out_of_scope=False):
        """
        Sort current Option widgets
        :param widgets: list(Option)
        :param parent: Options
        :param return_out_of_scope: bool
        :return: list(Option)
        """

        if not hasattr(parent, 'child_layout'):
            return

        item_count = parent.child_layout.count()
        found = list()

        for i in range(item_count):
            item = parent.child_layout.itemAt(i)
            if item:
                widget = item.widget()
                for sub_widget in widgets:
                    if sub_widget == widget:
                        found.append(widget)

        if return_out_of_scope:
            other_found = list()
            for sub_widget in widgets:
                if sub_widget not in found:
                    other_found.append(sub_widget)

            found = other_found

        return found

    def clear_widgets(self):
        """
        Removes all widgets from current group
        """

        self._has_first_group = False
        item_count = self.child_layout.count()
        for i in range(item_count, -1, -1):
            item = self.child_layout.itemAt(i)
            if item:
                widget = item.widget()
                self.child_layout.removeWidget(widget)
                widget.deleteLater()

        self._parent._current_widgets = list()

    def set_edit(self, flag):
        """
        Set the edit mode of the group
        :param flag: bool
        """

        self.editModeChanged.emit(flag)

    def _create_context_menu(self, menu, parent):

        plus_icon = resources.icon('plus')
        string_icon = resources.icon('rename')
        directory_icon = resources.icon('folder')
        file_icon = resources.icon('file')
        integer_icon = resources.icon('number_1')
        float_icon = resources.icon('float_1')
        bool_icon = resources.icon('true_false')
        dict_icon = resources.icon('dictionary')
        list_icon = resources.icon('list')
        group_icon = resources.icon('group_objects')
        script_icon = resources.icon('source_code')
        title_icon = resources.icon('label')
        color_icon = resources.icon('palette')
        clear_icon = resources.icon('clean')
        copy_icon = resources.icon('copy')
        paste_icon = resources.icon('paste')

        create_menu = menu.findChild(QMenu, 'createMenu')
        if not create_menu:
            create_menu = menu.addMenu(plus_icon, 'Add Options')
            create_menu.setObjectName('createMenu')
            add_string_action = QAction(string_icon, 'Add String', create_menu)
            create_menu.addAction(add_string_action)
            add_directory_action = QAction(directory_icon, 'Add Directory',
                                           create_menu)
            create_menu.addAction(add_directory_action)
            add_file_action = QAction(file_icon, 'Add File', create_menu)
            create_menu.addAction(add_file_action)
            add_integer_action = QAction(integer_icon, 'Add Integer',
                                         create_menu)
            create_menu.addAction(add_integer_action)
            add_float_action = QAction(float_icon, 'Add Float', create_menu)
            create_menu.addAction(add_float_action)
            add_bool_action = QAction(bool_icon, 'Add Bool', create_menu)
            create_menu.addAction(add_bool_action)
            add_list_action = QAction(list_icon, 'Add List', create_menu)
            create_menu.addAction(add_list_action)
            add_dict_action = QAction(dict_icon, 'Add Dictionary', create_menu)
            create_menu.addAction(add_dict_action)
            add_group_action = QAction(group_icon, 'Add Group', create_menu)
            create_menu.addAction(add_group_action)
            add_script_action = QAction(script_icon, 'Add Script', create_menu)
            create_menu.addAction(add_script_action)
            add_title_action = QAction(title_icon, 'Add Title', create_menu)
            create_menu.addAction(add_title_action)
            add_color_action = QAction(color_icon, 'Add Color', create_menu)
            create_menu.addAction(add_color_action)
            add_vector3f_action = QAction(color_icon, 'Add Vector 3 float',
                                          create_menu)
            create_menu.addAction(add_vector3f_action)
            menu.addSeparator()
            parent.copy_action = QAction(copy_icon, 'Copy', menu)
            menu.addAction(parent.copy_action)
            parent.copy_action.setVisible(False)
            parent.paste_action = QAction(paste_icon, 'Paste', menu)
            menu.addAction(parent.paste_action)
            parent.paste_action.setVisible(False)
            menu.addSeparator()
            clear_action = QAction(clear_icon, 'Clear', menu)
            menu.addAction(clear_action)

            add_string_action.triggered.connect(
                partial(parent._add_option, 'string'))
            add_directory_action.triggered.connect(
                partial(parent._add_option, 'directory'))
            add_file_action.triggered.connect(
                partial(parent._add_option, 'file'))
            add_integer_action.triggered.connect(
                partial(parent._add_option, 'integer'))
            add_float_action.triggered.connect(
                partial(parent._add_option, 'float'))
            add_bool_action.triggered.connect(
                partial(parent._add_option, 'boolean'))
            add_list_action.triggered.connect(
                partial(parent._add_option, 'list'))
            add_dict_action.triggered.connect(
                partial(parent._add_option, 'dictionary'))
            add_group_action.triggered.connect(parent.add_group)
            add_title_action.triggered.connect(
                partial(parent._add_option, 'title'))
            add_color_action.triggered.connect(
                partial(parent._add_option, 'color'))
            add_vector3f_action.triggered.connect(
                partial(parent._add_option, 'vector3f'))
            add_script_action.triggered.connect(
                partial(parent._add_option, 'script'))
            clear_action.triggered.connect(parent._clear_action)

        return create_menu

    def _create_group_context_menu(self, group, menu):
        self._create_context_menu(menu=menu, parent=group)

        group.copy_action.setVisible(False)

        string_icon = resources.icon('rename')
        remove_icon = resources.icon('trash')

        rename_action = QAction(string_icon, 'Rename', menu)
        menu.addAction(rename_action)
        remove_action = QAction(remove_icon, 'Remove', menu)
        menu.addAction(remove_action)

        rename_action.triggered.connect(group.rename)
        remove_action.triggered.connect(group.remove)

        return menu

    def _add_option(self, option_type, name=None, value=None, parent=None):
        if option_type is None:
            return

        if option_type == 'group':
            new_option = self.add_group('group')
        else:
            option_object = self.get_option_object()
            name = self._get_unique_name(name or option_type, parent=parent)
            new_option = self.FACTORY_CLASS.add_option(
                option_type,
                name=name,
                value=value,
                parent=parent,
                main_widget=self._parent,
                option_object=option_object)
            if new_option:
                self._handle_parenting(new_option, parent=parent)
                self._write_options(clear=False)
            else:
                LOGGER.warning('Option of type "{}" is not supported!'.format(
                    option_type))

        return new_option

    def _add_custom_option(self,
                           option_type,
                           name=None,
                           value=None,
                           parent=None):
        pass

    def _get_unique_name(self, name, parent=None):
        """
        Internal function that returns unique name for the new group
        :param name: str
        :param parent: QWidget
        :return: str
        """

        found = self._get_widget_names(parent)
        while name in found:
            name = name_utils.increment_last_number(name)

        return name

    def _get_widget_names(self, parent=None):
        """
        Internal function that returns current stored widget names
        :param parent: Option
        :return: list(str)
        """

        if parent:
            scope = parent
        else:
            scope = self

        item_count = scope.child_layout.count()
        found = list()
        for i in range(item_count):
            item = scope.child_layout.itemAt(i)
            widget = item.widget()
            label = widget.get_name()
            found.append(label)

        return found

    def _get_unique_name(self, name, parent):
        """
        Internal function that returns unique name for the new group
        :param name: str
        :param parent: QWidget
        :return: str
        """

        found = self._get_widget_names(parent)
        while name in found:
            name = name_utils.increment_last_number(name)

        return name

    def _handle_parenting(self, widget, parent):
        """
        Internal function that handles parenting of given widget and its parent
        :param widget: Options
        :param parent: Options
        """

        widget.widgetClicked.connect(self.update_current_widget)
        # widget.editModeChanged.connect(self._on_activate_edit_mode)

        if parent:
            parent.child_layout.addWidget(widget)
            if hasattr(widget, 'updateValues'):
                widget.updateValues.connect(parent._write_options)
        else:
            self.child_layout.addWidget(widget)
            if hasattr(widget, 'updateValues'):
                widget.updateValues.connect(self._write_options)

        if self._auto_rename:
            widget.rename()

    def _get_path(self, widget):
        """
        Internal function that return option path of given option
        :param widget: Options
        :return: str
        """

        parent = widget.get_parent()
        path = ''
        parents = list()
        if parent:
            sub_parent = parent
            while sub_parent:
                if issubclass(sub_parent.__class__, OptionList) and not \
                        sub_parent.__class__.__name__.endswith('OptionListGroup'):
                    break
                name = sub_parent.get_name()
                parents.append(name)
                sub_parent = sub_parent.get_parent()

        parents.reverse()

        for sub_parent in parents:
            path += '{}.'.format(sub_parent)

        if hasattr(widget, 'child_layout'):
            path = path + widget.get_name() + '.'
        else:
            path = path + widget.get_name()

        return path

    def _load_widgets(self, options):
        """
        Internal function that loads widget with given options
        :param options: dict
        """

        self.clear_widgets()
        if not options:
            return

        self._supress_update = True
        self._disable_auto_expand = True
        self._auto_rename = False

        try:
            for option in options:
                option_type = None
                if type(option[1]) == list:
                    if option[0] == 'list':
                        value = option[1]
                        option_type = 'list'
                    else:
                        value = option[1][0]
                        option_type = option[1][1]
                    # value = option[1][0]
                    # option_type = option[1][1]
                else:
                    value = option[1]

                split_name = option[0].split('.')
                if split_name[-1] == '':
                    search_group = string.join(split_name[:-2], '.')
                    name = split_name[-2]
                else:
                    search_group = string.join(split_name[:-1], '.')
                    name = split_name[-1]

                widget = self._find_group_widget(search_group)
                if not widget:
                    widget = self

                is_group = False
                if split_name[-1] == '':
                    is_group = True
                    parent_name = string.join(split_name[:-1], '.')
                    group = self._find_group_widget(parent_name)
                    if not group:
                        self.add_group(name, value, widget)

                if len(split_name) > 1 and split_name[-1] != '':
                    search_group = string.join(split_name[:-2], '.')
                    after_search_group = string.join(split_name[:-1], '.')
                    group_name = split_name[-2]
                    group_widget = self._find_group_widget(search_group)
                    widget = self._find_group_widget(after_search_group)
                    if not widget:
                        self.add_group(group_name, value, group_widget)
                        widget = self._find_group_widget(after_search_group)

                if not is_group:
                    if not option_type:
                        if type(value) == unicode or type(value) == str:
                            option_type = 'string'
                        elif type(value) == float:
                            option_type = 'float'
                        elif type(option[1]) == int:
                            option_type = 'integer'
                        elif type(option[1]) == bool:
                            option_type = 'boolean'
                        elif type(option[1]) == dict:
                            option_type = 'dictionary'
                        elif type(option[1]) == list:
                            option_type = 'list'
                        elif option[1] is None:
                            option_type = 'title'

                    new_option = self._add_custom_option(
                        option_type, name, value, widget)
                    if not new_option:
                        self._add_option(option_type, name, value, widget)

        except Exception:
            LOGGER.error(traceback.format_exc())
        finally:
            self._disable_auto_expand = False
            # self.setVisible(True)
            # self.setUpdatesEnabled(True)
            self._supress_update = False
            self._auto_rename = True

    def _find_list(self, widget):
        if widget.__class__.__name__.endswith('OptionList'):
            return widget

        parent = widget.get_parent()
        if not parent:
            return

        while not parent.__class__.__name__.endswith('OptionList'):
            parent = parent.get_parent()

        return parent

    def _find_group_widget(self, name):
        """
        Internal function that returns OptionList with given name (if exists)
        :param name: str, name of the group to find
        :return: variant, OptionList or None
        """

        split_name = name.split('.')
        sub_widget = None
        for name in split_name:
            if not sub_widget:
                sub_widget = self
            found = False
            item_count = sub_widget.child_layout.count()
            for i in range(item_count):
                item = sub_widget.child_layout.itemAt(i)
                if item:
                    widget = item.widget()
                    label = widget.get_name()
                    if label == name:
                        sub_widget = widget
                        found = True
                        break
                else:
                    break
            if not found:
                return

        return sub_widget

    def _deselect_children(self, widget):
        """
        Internal function that deselects all the children widgets of the given Option
        :param widget: Option
        """

        children = widget.get_children()
        for child in children:
            self.deselect_widget(child)

    def _clear_action(self):
        """
        Internal function that clears all widgets
        """

        if self.__class__ == OptionList:
            name = 'the list?'
        else:
            name = 'group?'

        item_count = self.child_layout.count()
        if item_count <= 0:
            LOGGER.debug('No widgets to clear ...')
            return

        permission = qtutils.get_permission('Clear all the widgets?',
                                            parent=self)
        if permission:
            self.clear_widgets()
            self._write_options(clear=True)

    def _write_options(self, clear=True):
        """
        Internal function that writes current options into disk
        :param clear: bool
        """

        if not self._option_object:
            LOGGER.warning(
                'Impossible to write options because option object is not defined!'
            )
            return

        if self._supress_update:
            return

        if clear:
            self._write_all()
        else:
            item_count = self.child_layout.count()
            for i in range(0, item_count):
                item = self.child_layout.itemAt(i)
                widget = item.widget()
                widget_type = widget.get_option_type()
                name = self._get_path(widget)
                value = widget.get_value()
                self._option_object.add_option(name, value, None, widget_type)

        self.valueChanged.emit()

    def _write_widget_options(self, widget):
        if not widget:
            return

        if not self._option_object:
            LOGGER.warning(
                'Impossible to write options because option object is not defined!'
            )
            return

        item_count = widget.child_layout.count()
        for i in range(item_count):
            item = widget.child_layout.itemAt(i)
            if item:
                sub_widget = item.widget()
                sub_widget_type = sub_widget.get_option_type()
                name = self._get_path(sub_widget)
                value = sub_widget.get_value()

                self._option_object.add_option(name, value, None,
                                               sub_widget_type)

                if hasattr(sub_widget, 'child_layout'):
                    self._write_widget_options(sub_widget)

    def _write_all(self):

        if not self._option_object:
            LOGGER.warning(
                'Impossible to write options because option object is not defined!'
            )
            return

        self._option_object.clear_options()

        options_list = self._find_list(self)
        self._write_widget_options(options_list)

    def _fill_background(self, widget):
        """
        Internal function used to paint the background color of the group
        :param widget: Option
        """

        palette = widget.palette()
        if not dcc.is_maya():
            palette.setColor(widget.backgroundRole(), Qt.gray)
        else:
            palette.setColor(widget.backgroundRole(),
                             QColor(35, 150, 245, 255))
        widget.setAutoFillBackground(True)
        widget.setPalette(palette)

    def _unfill_background(self, widget):
        """
        Internal function that clears the background color of the group
        :param widget: Option
        """

        palette = widget.palette()
        palette.setColor(widget.backgroundRole(),
                         widget._original_background_color)
        widget.setAutoFillBackground(False)
        widget.setPalette(palette)

    def _on_item_menu(self, pos):
        """
        Internal callback function that is is called when the user right click on an Option
        Pop ups item menu on given position
        :param pos: QPoint
        """

        if not self._parent.is_edit_mode():
            return

        if self._parent.is_widget_to_copy():
            self.paste_action.setVisible(True)

        self._context_menu.exec_(self.mapToGlobal(pos))

    def _on_activate_edit_mode(self):
        """
        Internal callback function that is called when the user presses edit mode button
        """

        self.editModeChanged.emit(True)

    def _on_copy_widget(self):
        """
        Internal callback function that is called when the user copy a Option
        """

        self._parent.set_widget_to_copy(self)

    def _on_paste_widget(self):
        """
        Internal callback function that is called when the user paste a Option
        """

        self.paste_action.setVisible(False)
        widget_to_copy = self._parent.is_widget_to_copy()
        if widget_to_copy.task_option_type == 'group':
            widget_to_copy.copy_to(self)
Example #23
0
class Option(base.BaseWidget, object):
    updateValues = Signal(object)
    widgetClicked = Signal(object)

    def __init__(self, name, parent=None, main_widget=None, *args, **kwargs):

        self._name = name
        self._option_object = None
        self._parent = main_widget

        super(Option, self).__init__(parent=parent)

        self._original_background_color = self.palette().color(
            self.backgroundRole())
        self._option_type = self.get_option_type()
        self._option_widget = self.get_option_widget()
        if self._option_widget:
            self.main_layout.addWidget(self._option_widget)
        self._setup_option_widget_value_change()

        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self._on_item_menu)
        self._context_menu = None

    def get_main_layout(self):
        main_layout = layouts.HorizontalLayout()
        main_layout.setContentsMargins(0, 0, 0, 0)
        main_layout.setSpacing(0)
        return main_layout

    def ui(self):
        super(Option, self).ui()

    def mousePressEvent(self, event):
        super(Option, self).mousePressEvent(event)
        if not event.button() == Qt.LeftButton:
            return
        parent = self.get_parent()
        if parent:
            parent.supress_select = True
        self.widgetClicked.emit(self)

    def get_option_type(self):
        return None

    def get_option_widget(self):
        return None

    def get_name(self):
        name = self._option_widget.get_label_text()
        return name

    def set_name(self, name):
        self._option_widget.set_label_text(name)

    def set_value(self, value):
        pass

    def get_value(self):
        pass

    def get_parent(self):
        parent = self.parent()
        grand_parent = parent.parent()
        if hasattr(grand_parent, 'group'):
            parent = grand_parent
        if not hasattr(parent, 'child_layout'):
            return

        # We cannot use this because of import problems
        # if parent.__class__ == optionlist.OptionList:

        if parent.__class__.__name__ == 'OptionList':
            return parent

        return parent

    def rename(self):
        title = self.get_name()
        new_name = qtutils.get_string_input('Rename Option', old_name=title)
        found = self._get_widget_names()
        if new_name == title or new_name is None or new_name == '':
            return

        while new_name in found:
            new_name = name_utils.increment_last_number(new_name)
        self.set_name(new_name)
        self.updateValues.emit(True)

    def remove(self):
        parent = self.get_parent()
        if self in self._parent._current_widgets:
            remove_index = self._parent._current_widgets.index(self)
            self._parent._current_widgets.pop(remove_index)
        parent.child_layout.removeWidget(self)
        self.deleteLater()
        self.updateValues.emit(True)

    def move_up(self):
        parent = self.get_parent()
        if not parent:
            parent = self.parent()
        layout = parent.child_layout
        index = layout.indexOf(self)
        if index == 0:
            return
        index -= 1
        parent.child_layout.removeWidget(self)
        layout.insertWidget(index, self)
        self.updateValues.emit(True)

    def move_down(self):
        parent = self.get_parent()
        if not parent:
            parent = self.parent()
        layout = parent.child_layout
        index = layout.indexOf(self)
        if index == 0:
            return
        index += 1
        parent.child_layout.removeWidget(self)
        layout.insertWidget(index, self)
        self.updateValues.emit(True)

    def copy_to(self, parent):
        name = self.get_name()
        value = self.get_value()
        new_inst = self.__class__(name)
        new_inst.set_value(value)
        parent.child_layout.addWidget(new_inst)

    def set_option_object(self, option_object):
        self._option_object = option_object

    def _setup_option_widget_value_change(self):
        pass

    def _copy(self):
        self._parent.set_widget_to_copy(self)

    def _get_widget_names(self):
        item_count = self.parent().child_layout.count()
        found = list()
        for i in range(item_count):
            item = self.parent().child_layout.itemAt(i)
            widget = item.widget()
            widget_label = widget.get_name()
            found.append(widget_label)

        return found

    def _create_context_menu(self):
        self._context_menu = QMenu()

        move_up_icon = resources.icon('sort_up')
        move_down_icon = resources.icon('sort_down')
        rename_icon = resources.icon('rename')
        remove_icon = resources.icon('delete')
        copy_icon = resources.icon('copy')

        move_up_action = QAction(move_up_icon, 'Move Up', self._context_menu)
        self._context_menu.addAction(move_up_action)
        move_down_action = QAction(move_down_icon, 'Move Down',
                                   self._context_menu)
        self._context_menu.addAction(move_down_action)
        self._context_menu.addSeparator()
        copy_action = QAction(copy_icon, 'Copy', self._context_menu)
        self._context_menu.addAction(copy_action)
        rename_action = QAction(rename_icon, 'Rename', self._context_menu)
        self._context_menu.addAction(rename_action)
        remove_action = QAction(remove_icon, 'Remove', self._context_menu)
        self._context_menu.addAction(remove_action)

        move_up_action.triggered.connect(self.move_up)
        move_down_action.triggered.connect(self.move_down)
        rename_action.triggered.connect(self.rename)
        remove_action.triggered.connect(self.remove)

    def _on_item_menu(self, pos):
        if not self._parent or not self._parent.is_edit_mode():
            return

        if not self._context_menu:
            self._create_context_menu()

        self._context_menu.exec_(self.mapToGlobal(pos))

    def _on_value_changed(self):
        self.updateValues.emit(False)
Example #24
0
class switchOnString(Node):
    def __init__(self, name, graph):
        super(switchOnString, self).__init__(name, graph)
        self.inExecPin = self.addInputPin('inExec',
                                          DataTypes.Exec,
                                          self.compute,
                                          hideLabel=True)
        self.defaultPin = None
        self.outString = None
        self.inString = self.addInputPin('String', DataTypes.String)
        self.menu = QMenu()
        self.action = self.menu.addAction('add pin')
        self.action.triggered.connect(self.addOutPin)
        self.actionDebug = self.menu.addAction('debug')
        self.actionDebug.triggered.connect(self.OnDebug)
        self._map = {}

    def renameOutPin(self, oldName, newName):
        if oldName in self._map:
            self._map[oldName].setName(newName)

    def OnDebug(self):
        print(self._map.keys())

    def addOutPin(self):
        name = self.getUniqPinName("option")
        p = self.addOutputPin(name, DataTypes.Exec)
        renameAction = p.menu.addAction("rename")
        killAction = p.menu.addAction("kill")

        def OnKill():
            self._map.pop(p.name)
            p.kill()

        killAction.triggered.connect(OnKill)

        def OnRename():
            res = QInputDialog.getText(None, 'Rename pin', 'label')
            if res[1]:
                newName = self.getUniqPinName(res[0])
                self._map[newName] = self._map.pop(p.name)
                p.setName(newName)

        renameAction.triggered.connect(OnRename)
        pinAffects(self.inExecPin, p)
        self._map[name] = p

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

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

    @staticmethod
    def category():
        return 'FlowControl'

    @staticmethod
    def keywords():
        return []

    @staticmethod
    def description():
        return 'Execute output depending on input string'

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

        # restore dynamically created  outputs
        if len(jsonTemplate['outputs']) == 0:
            self.defaultPin = self.addOutputPin('Default', DataTypes.Exec)
            self.outString = self.addOutputPin('stringOut',
                                               DataTypes.String,
                                               hideLabel=True)
            self.addOutPin()
            self.addOutPin()
        else:
            for out in jsonTemplate['outputs']:
                PinWidgetBase.deserialize(self, out)

    def compute(self):
        string = self.inString.getData()
        self.outString.setData(string)
        if string in self._map:
            self._map[string].call()
        else:
            self.defaultPin.call()
Example #25
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())
Example #26
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()
Example #27
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)
Example #28
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()
class UIConnection(QGraphicsPathItem):
    """UIConnection is a cubic spline curve. It represents connection 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
        self.thicknessMultiplier = 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())
        self.prevPos = None
        self.linPath = None
        self.hOffsetL = 0.0
        self.hOffsetR = 0.0
        self.hOffsetLSShape = 0.0
        self.hOffsetRSShape = 0.0
        self.vOffset = 0.0
        self.vOffsetSShape = 0.0
        self.offsetting = 0
        self.snapVToFirst = True
        self.snapVToSecond = False
        self.sShape = False
        self.sameSide = 0
        self.hoverSegment = -1
        self.pressedSegment = -1
        if self.source().isExec():
            self.bubble = QGraphicsEllipseItem(-2.5, -2.5, 5, 5, self)
            self.bubble.setBrush(self.color)
            self.bubble.setPen(self.pen)

            point = self.mPath.pointAtPercent(0.0)
            self.bubble.setPos(point)

            self.bubble.hide()
            self.source()._rawPin.onExecute.connect(
                self.performEvaluationFeedback)
            self.shouldAnimate = False
            self.timeline = QtCore.QTimeLine(2000)
            self.timeline.setFrameRange(0, 100)
            self.timeline.frameChanged.connect(self.timelineFrameChanged)
            self.timeline.setLoopCount(0)

    def performEvaluationFeedback(self, *args, **kwargs):
        if self.timeline.state() == QtCore.QTimeLine.State.NotRunning:
            self.shouldAnimate = True
            # spawn bubble
            self.bubble.show()
            self.timeline.start()

    def timelineFrameChanged(self, frameNum):
        percentage = currentProcessorTime() - self.source(
        )._rawPin.getLastExecutionTime()
        self.shouldAnimate = percentage < 0.5
        point = self.mPath.pointAtPercent(
            float(frameNum) / float(self.timeline.endFrame()))
        self.bubble.setPos(point)
        if not self.shouldAnimate:
            self.timeline.stop()
            self.bubble.hide()

    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.isExec() or self.drawDestination.isExec():
            if self.thickness != 2:
                self.thickness = 2
                self.pen.setWidthF(self.thickness)

        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

    def applyJsonData(self, data):
        hOffsetL = data['hOffsetL']
        if hOffsetL is not None:
            self.hOffsetL = float(hOffsetL)
        hOffsetR = data['hOffsetR']
        if hOffsetR is not None:
            self.hOffsetR = float(hOffsetR)
        hOffsetLSShape = data['hOffsetLSShape']
        if hOffsetLSShape is not None:
            self.hOffsetLSShape = float(hOffsetLSShape)
        hOffsetRSShape = data['hOffsetRSShape']
        if hOffsetRSShape is not None:
            self.hOffsetRSShape = float(hOffsetRSShape)
        vOffset = data['vOffset']
        if vOffset is not None:
            self.vOffset = float(vOffset)
        vOffsetSShape = data['vOffsetSShape']
        if vOffsetSShape is not None:
            self.vOffsetSShape = float(vOffsetSShape)
        snapVToFirst = data['snapVToFirst']
        if snapVToFirst is not None:
            self.snapVToFirst = bool(snapVToFirst)
        snapVToSecond = data['snapVToSecond']
        if snapVToSecond is not None:
            self.snapVToSecond = bool(snapVToSecond)

        self.getEndPoints()

    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),
            'hOffsetL': str(self.hOffsetL),
            'hOffsetR': str(self.hOffsetR),
            'hOffsetLSShape': str(self.hOffsetLSShape),
            'hOffsetRSShape': str(self.hOffsetRSShape),
            'vOffset': str(self.vOffset),
            'vOffsetSShape': str(self.vOffsetSShape),
            'snapVToFirst': int(self.snapVToFirst),
            'snapVToSecond': int(self.snapVToSecond),
        }

        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 hoverLeaveEvent(self, event):
        super(UIConnection, self).hoverLeaveEvent(event)
        self.hoverSegment = -1
        self.restoreThick()
        self.update()

    def hoverMoveEvent(self, event):
        if self.offsetting == 0:
            self.hoverSegment = -1
            if self.linPath is not None:
                tempPath = ConnectionPainter.linearPath(self.linPath)
                t = self.percentageByPoint(event.scenePos(), tempPath)
                segments = []
                for i, pos in enumerate(self.linPath[:-1]):
                    t1 = self.percentageByPoint(pos, tempPath)
                    t2 = self.percentageByPoint(self.linPath[i + 1], tempPath)
                    segments.append([t1, t2])
                for i, seg in enumerate(segments):
                    if t > seg[0] and t < seg[1]:
                        valid = []
                        if not self.sShape:
                            if self.snapVToFirst:
                                valid = [0, 1]
                            elif self.snapVToSecond:
                                valid = [1, 2]
                            else:
                                valid = [1, 2, 3]
                        else:
                            valid = [1, 2, 3]
                        if i in valid:
                            self.hoverSegment = i
                        else:
                            self.hoverSegment = -1

    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()

        if editableStyleSheet().ConnectionMode[0] in [
                ConnectionTypes.Circuit, ConnectionTypes.ComplexCircuit
        ]:
            self.sameSide = 0
            p1n, p2n = p1, p2
            xDistance = p2.x() - p1.x()
            if self.destination().owningNode()._rawNode.__class__.__name__ in [
                    "reroute", "rerouteExecs"
            ]:
                if xDistance < 0:
                    p2n, p1n = p1, p2
                    self.sameSide = 1
            if self.source().owningNode()._rawNode.__class__.__name__ in [
                    "reroute", "rerouteExecs"
            ]:
                if xDistance < 0:
                    p1n, p2n = p1, p2
                    self.sameSide = -1
            p1, p2 = p1n, p2n
        return p1, p2

    def percentageByPoint(self, point, path, precision=0.5, width=20.0):
        percentage = -1.0
        stroker = QtGui.QPainterPathStroker()
        stroker.setWidth(width)
        strokepath = stroker.createStroke(path)
        t = 0.0
        d = []
        while t <= 100.0:
            d.append(
                QtGui.QVector2D(point - path.pointAtPercent(t / 100)).length())
            t += precision
        percentage = d.index(min(d)) * precision
        return percentage

    def mousePressEvent(self, event):
        super(UIConnection, self).mousePressEvent(event)
        t = self.percentageByPoint(event.scenePos(), self.mPath)
        self.prevPos = event.pos()

        if abs(self.mPath.slopeAtPercent(t * 0.01)) < 1:
            self.offsetting = 1
        else:
            self.offsetting = 2

        if self.linPath is not None:
            tempPath = ConnectionPainter.linearPath(self.linPath)
            t = self.percentageByPoint(event.scenePos(), tempPath)
            segments = []
            for i, pos in enumerate(self.linPath[:-1]):
                t1 = self.percentageByPoint(pos, tempPath)
                t2 = self.percentageByPoint(self.linPath[i + 1], tempPath)
                segments.append([t1, t2])
            for i, seg in enumerate(segments):
                if t > seg[0] and t < seg[1]:
                    valid = []
                    if not self.sShape:
                        if self.snapVToFirst:
                            valid = [0, 1]
                        elif self.snapVToSecond:
                            valid = [1, 2]
                        else:
                            valid = [1, 2, 3]
                    else:
                        valid = [1, 2, 3]
                    if i in valid:
                        self.pressedSegment = i
                    else:
                        self.pressedSegment = -1
        p1, p2 = self.getEndPoints()
        offset1 = editableStyleSheet().ConnectionOffset[0]
        offset2 = -offset1
        if self.sameSide == 1:
            offset2 = offset1
        elif self.sameSide == -1:
            offset1 = offset2
        xDistance = (p2.x() + offset2) - (p1.x() + offset1)
        self.sShape = xDistance < 0
        event.accept()

    def mouseReleaseEvent(self, event):
        super(UIConnection, self).mouseReleaseEvent(event)
        self.offsetting = 0
        self.pressedSegment = -1

        event.accept()

    def mouseMoveEvent(self, event):
        super(UIConnection, self).mouseMoveEvent(event)
        if self.prevPos is not None:
            delta = self.prevPos - event.pos()
            p1, p2 = self.getEndPoints()
            if not self.sShape:
                if self.offsetting == 1:
                    doIt = True
                    if self.snapVToFirst and self.pressedSegment != 0:
                        doIt = False
                        self.pressedSegment = -1
                    elif self.snapVToSecond and self.pressedSegment != 2:
                        doIt = False
                        self.pressedSegment = -1
                    elif not self.snapVToFirst and not self.snapVToSecond:
                        if self.pressedSegment != 2:
                            doIt = False
                            self.pressedSegment = -1
                    if doIt:
                        self.vOffset -= float(delta.y())
                        if abs(self.vOffset) <= 3:
                            self.snapVToFirst = True
                            self.pressedSegment = 0
                        else:
                            self.snapVToFirst = False
                        if p1.y() + self.vOffset > p2.y() - 3 and p1.y(
                        ) + self.vOffset < p2.y() + 3:
                            self.snapVToSecond = True
                            self.pressedSegment = 2
                        else:
                            self.snapVToSecond = False
                        if not self.snapVToFirst and self.pressedSegment == 0:
                            self.pressedSegment = 2

                if self.offsetting == 2:
                    if self.snapVToFirst:
                        self.hOffsetR -= float(delta.x())
                    elif self.snapVToSecond:
                        self.hOffsetL -= float(delta.x())
                    else:
                        if self.pressedSegment == 1:
                            self.hOffsetL -= float(delta.x())
                        elif self.pressedSegment == 3:
                            self.hOffsetR -= float(delta.x())
            else:
                if self.offsetting == 1 and self.pressedSegment == 2:
                    self.vOffsetSShape -= float(delta.y())
                elif self.offsetting == 2:
                    if self.pressedSegment == 1:
                        self.hOffsetRSShape -= float(delta.x())
                    elif self.pressedSegment == 3:
                        self.hOffsetLSShape -= float(delta.x())

            self.prevPos = event.pos()

        event.accept()

    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().getCanvasLodValueFromCurrentScale()

        self.setPen(self.pen)
        p1, p2 = self.getEndPoints()
        roundness = editableStyleSheet().ConnectionRoundness[0]
        offset = editableStyleSheet().ConnectionOffset[0]
        offset1 = offset
        offset2 = -offset1
        if self.sameSide == 1:
            offset2 = offset1
        elif self.sameSide == -1:
            offset1 = offset2
        xDistance = (p2.x() + offset2) - (p1.x() + offset1)
        self.sShape = xDistance < 0
        sectionPath = None
        if editableStyleSheet().ConnectionMode[0] == ConnectionTypes.Circuit:
            seg = self.hoverSegment if self.hoverSegment != -1 and self.linPath and self.pressedSegment == -1 else self.pressedSegment
            self.mPath, self.linPath, sectionPath = ConnectionPainter.BasicCircuit(
                p1, p2, offset, roundness, self.sameSide, lod, False,
                self.vOffset, self.hOffsetL, self.vOffsetSShape, self.hOffsetR,
                self.hOffsetRSShape, self.hOffsetLSShape, self.snapVToFirst,
                self.snapVToSecond, seg)
        elif editableStyleSheet(
        ).ConnectionMode[0] == ConnectionTypes.ComplexCircuit:
            self.mPath, self.linPath, sectionPath = ConnectionPainter.BasicCircuit(
                p1, p2, offset, roundness, self.sameSide, lod, True)
        elif editableStyleSheet().ConnectionMode[0] == ConnectionTypes.Cubic:
            self.mPath = ConnectionPainter.Cubic(p1, p2, 150, lod)
            self.linPath = None
        elif editableStyleSheet().ConnectionMode[0] == ConnectionTypes.Linear:
            self.mPath = ConnectionPainter.Linear(p1, p2, offset, roundness,
                                                  lod)
            self.linPath = None
        if self.snapVToSecond and self.offsetting == 0:
            self.vOffset = p2.y() - p1.y()
        self.setPath(self.mPath)

        super(UIConnection, self).paint(painter, option, widget)
        pen = QtGui.QPen()
        pen.setColor(editableStyleSheet().MainColor)
        pen.setWidthF(self.thickness + (self.thickness / 1.5))
        painter.setPen(pen)
        if sectionPath:
            painter.drawPath(sectionPath)
Example #30
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)
Example #31
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.editorUUID = None
        self.bKillEditor = True
        self.label().icon = QtGui.QImage(':/icons/resources/py.png')
        self.currentComputeCode = Node.jsonTemplate()['computeCode']

    @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 contextMenuEvent(self, event):
        self.menu.exec_(event.screenPos())

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

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