Beispiel #1
0
class DeltaEmittingMixin:
	"""base class mixin for objects emitting deltas when their state changes
	2 signals:
		- deltaEmitted : fired when a new semantic delta is added - up to user to define
			what counts here
		- stateChanged : fired when the absolute final state of object changes,
			as result of delta being applied or rolled back, or anything else
			suggested to also emit a representative delta of the final change
			(though these deltas should not be tracked or captured)

	"""
	def __init__(self):

		self.deltaEmitted = Signal(name="deltaEmitted")
		self.stateChanged = Signal(name="stateChanged")

	# def applyDelta(self, delta:DeltaAtom):
	# 	"""FOR NOW I'm leaving the behaviour to apply / revert deltas in the Delta
	# 	objects themselves - I think it should be defined by compatible
	# 	classes in this method, but the only real advantage we get out of that
	# 	(aside from reducing code fragmentation) is the ability to reuse delta objects
	# 	across multiple types of objects (just be interpreting them differently) -
	# 	but only if this interpretation has to be MORE different across objects than
	# 	can be captured in the Delta doDelta() method anyway"""
	# 	pass

	def emitDelta(self, delta:DeltaAtom, apply=True):
		self.deltaEmitted.emit(delta)

		if apply:
			delta.doDelta(self)
Beispiel #2
0
class DataRef(object):
    """ wrapper for consistent references to primitive data types """
    def __init__(self, val):
        self._val = val
        self.onDataSet = Signal(name="onDataSet")

    def __repr__(self):
        return self._val

    def __call__(self, *args, **kwargs):
        """set new value for wrapper"""
        if args:
            self._val = args[0]
        return self.value()

    def __str__(self):
        return str(self._val)

    def __bool__(self):
        return bool(self._val)

    def value(self):
        return self._val

    def set(self, newVal):
        if newVal == self._val:
            return
        oldVal = self._val
        self._val = newVal
        self.onDataSet.emit(oldVal, newVal)
Beispiel #3
0
	def __init__(self, instance, name:str, defaultValue, desc=""):
		super(ToolFieldBase, self).__init__(instance)
		self.name = name
		self.desc = desc
		self.defaultValue = defaultValue
		# self.valueProxy = ToolFieldProxy.getProxy(self.baseValue)
		self.valueChanged = Signal()
		self.valueChanged.connect(self.onValueChanged)
		self.valueProxy = self.newProxy()
Beispiel #4
0
    def __init__(self, transactionCls=Transaction):
        self._transactionCls = transactionCls
        self._currentTransaction: transactionCls = None

        # integer count to track current frames -
        # can only ever have 1 master top-level transaction open
        self._transactionFrameDepth = 0

        self.transactionOpened = Signal()
        self.transactionClosed = Signal()
Beispiel #5
0
class ToolFieldBase(InstanceDescriptorInterface):
	"""uses descriptor syntax but creates and stores
	dynamic attributes on the instance object
	I didn't want to make it this complicated I swear

	really, really suffering to make this instance specific
	signals need to be per-instance as well
	"""

	def __init__(self, instance, name:str, defaultValue, desc=""):
		super(ToolFieldBase, self).__init__(instance)
		self.name = name
		self.desc = desc
		self.defaultValue = defaultValue
		# self.valueProxy = ToolFieldProxy.getProxy(self.baseValue)
		self.valueChanged = Signal()
		self.valueChanged.connect(self.onValueChanged)
		self.valueProxy = self.newProxy()


	def newProxy(self)->ToolFieldProxy:
		"""create a new proxy object and connect it to
		this object's signals"""
		valueProxy: ToolFieldProxy = ToolFieldProxy.getProxy(
			copy.deepcopy(self.defaultValue))
		valueProxy._proxyData["objectChanged"].connect(self.valueChanged)
		return valueProxy

	def delegatedGet(self, instance, owner):
		return self.valueProxy

	def delegatedSet(self, instance, value):
		self.setValue(value)

	# def __get__(self, instance, objType=None):
	# 	"""returns instance-specific proxy managed by this
	# 	tool field"""
	# 	return self._getInstanceProxy(instance)
	#
	# def __set__(self, instance, value):
	# 	"""if attribute is directly set, update
	# 	value and proxy references"""
	# 	self.setValue(value)

	def setValue(self, value):
		"""only emit changed value if value has actually changed"""
		oldValue = copy.deepcopy(self.valueProxy._proxyResult())
		self.valueProxy._proxyLink.setProxyTarget(value)
		if value != oldValue:
			self.valueChanged.emit()

	def onValueChanged(self, *args, **kwargs):
		"""fired when core object is modified"""
		print("tool field value changed")
Beispiel #6
0
    def __init__(self, name: str, value=None):

        # signals emitting DeltaAtom objects
        self.deltasChanged = Signal()  # emitted on caller request
        self.stateChanged = Signal(
        )  # emitted when absolute final result of tree changes
        self.gatheringDeltas = True

        # internal tree attrs
        self._name = name
        self._value = value
        self._parent = None
Beispiel #7
0
    def __init__(self,
                 parent=None,
                 nameDelegateCls=TreeNameDelegate,
                 valueDelegateCls=TreeValueDelegate,
                 contextMenu: TreeMenu = None,
                 showValues=True,
                 showRoot=False,
                 scanForWidgets=False):
        super(TreeView, self).__init__(parent)
        self.root = None
        self.nameDelegateCls = nameDelegateCls
        self.valueDelegateCls = valueDelegateCls
        self.highlights = {}  # dict of tree addresses to highlight
        self.valuesShown = showValues
        self.showRoot = showRoot
        self.scanForWidgets = scanForWidgets

        # either pass this at init or assign dynamically in subclasses
        self.context = contextMenu

        self.contentChanged = Signal()
        self.sizeChanged = Signal()
        self.currentSelected = None
        self.editedIndex = None  # stored on editing
        self.scrollPos = self.verticalScrollBar().value()
        self.actions = {}

        # set delegate classes
        self.setItemDelegateForColumn(0, self.nameDelegateCls(self))
        self.setItemDelegateForColumn(1, self.valueDelegateCls(self))

        # convenience wrappers
        self.keyState = KeyState()
        self.sel = SelectionModelContainer(self.selectionModel(), parent=self)

        # internal stores for drawing overrides
        self._liveInputRoots = weakref.WeakSet()
        self._deltaTrackingRoots = weakref.WeakSet()
        self._deltaCreatedBranches = weakref.WeakSet()
        self._deltaModifiedBranches = weakref.WeakSet()
        self._deltaDeletedBranches = weakref.WeakSet()
        self._widgetBranches = weakref.WeakKeyDictionary()

        self.savedSelectedTrees = []
        self.savedExpandedTrees = []

        self.makeBehaviour()
        self.makeAppearance()
        self.makeConnections()
        self.makeBaseActions()

        self.expandAll()
        self.showValues(showValues)
Beispiel #8
0
class DeltaTracker:
	"""base class for component tracking atomic deltas
	of the format shown above"""
	def __init__(self):
		self._deltaStack : T.List[DeltaAtom] = []
		self._deltaIndex = -1 # which is "current" active delta
		self.deltasChanged = Signal(name="deltasChanged")

	@property
	def deltaStack(self)->T.Tuple[DeltaAtom]:
		"""return read-only view of deltas"""
		return tuple(self._deltaStack)

	@property
	def currentDelta(self)->DeltaAtom:
		return self.deltaStack[self._deltaIndex]

	def addDelta(self, delta:DeltaAtom):
		"""append delta to the stack
		if index is not at last,
		discard deltas beyond it"""
		self._deltaStack = self._deltaStack[
		                   :len(self._deltaStack) + self._deltaIndex + 1]
		self._deltaStack.append(delta)
		self._deltaIndex = -1
		self.deltasChanged.emit()

	def applyDeltas(self, targetObj):
		"""apply contained deltas to the target object
		up to current delta index"""
		for i in self.deltaStack[
		         :len(self._deltaStack) + self._deltaIndex + 1]:
			i.doDelta(targetObj, )

	def clearDeltas(self):
		self._deltaStack.clear()
		self.deltasChanged.emit()

	def undo(self, targetObj, moveIndex=True):
		"""roll back a single delta"""
		self.deltaStack[self._deltaIndex].undoDelta(targetObj)
		if moveIndex:
			self._deltaIndex = self._deltaIndex - 1
		self.deltasChanged.emit()

	def redo(self, targetObj, moveIndex=True):
		"""reapply a single delta"""
		redoIndex = self._deltaIndex + 1
		if redoIndex >= len(self.deltaStack):
			raise RuntimeError("no deltas left to redo")
		self.deltaStack[self._deltaIndex + 1].doDelta(targetObj)
		if moveIndex:
			self._deltaIndex = self._deltaIndex + 1
		self.deltasChanged.emit()
Beispiel #9
0
    def __init__(self, baseTree: "Tree", uid=""):
        super(TransformChain, self).__init__(uid)
        self.baseTree: "Tree" = None
        self.transformers: Ty.List[Transformation] = []
        self.dirty = True
        # self.deltaTracker : TreeDeltaTracker = None
        self._resultTree = None

        self.resultChanged = Signal()
        self.markedDirty = Signal()
        self.transformChanged = Signal()
        self.transformChanged.connect(self.onTransformChanged)
        self.setBaseTree(baseTree)
Beispiel #10
0
class TransformChain(UidElement):
    """holds a sequence of transforms to apply to a copy of the base object
	here it's a tree

	all the messing around with coredata is dumb - just operate
	on trees and match() the result

	SPECIAL CASE delta transform, always comes last in chain

	"""

    uidInstanceMap: Ty.Dict[str, "TransformChain"] = {}

    def __init__(self, baseTree: "Tree", uid=""):
        super(TransformChain, self).__init__(uid)
        self.baseTree: "Tree" = None
        self.transformers: Ty.List[Transformation] = []
        self.dirty = True
        # self.deltaTracker : TreeDeltaTracker = None
        self._resultTree = None

        self.resultChanged = Signal()
        self.markedDirty = Signal()
        self.transformChanged = Signal()
        self.transformChanged.connect(self.onTransformChanged)
        self.setBaseTree(baseTree)

    def addTransform(self, transformObj: Transformation):
        self.transformers.append(transformObj)

    def removeTransform(self, transformObj: Transformation):
        self.transformers.remove(transformObj)

    def setBaseTree(self, baseTree: "Tree"):
        self.baseTree = baseTree
        self.onBaseTreeChanged()

    def setDirty(self, state=True):
        self.dirty = state
        self.markedDirty.emit()

    def onBaseTreeChanged(self, *args, **kwargs):
        """very inefficient for now, only need apply
		when struture changes"""
        for branch in self.baseTree.allBranches(includeSelf=True):
            for i in branch.signals():
                i.connect(self.onBaseTreeChanged)
        self.setDirty(True)

    def onTransformChanged(self, *args, **kwargs):
        self.setDirty(True)

    def deltaTransforms(self):
        deltaTransforms = [
            i for i in self.transformers if isinstance(i, DeltaTransformation)
        ]
        return deltaTransforms

    def getDeltaTransform(self):
        """if no tracker is found, create one as a transform
		look up the latest one in the chain
		"""
        if not self.deltaTransforms():
            tf = DeltaTransformation()
            self.addTransform(tf)
            tf.deltasChanged.connect(self.transformChanged)
        return self.deltaTransforms()[-1]

    def resultTree(self) -> "Tree":
        """return result tree of all known transforms"""
        if self.dirty or self._resultTree is None:
            #resultTree = self.baseTree.copy()
            resultTree = self.baseTree._copyAndXorUids(self.baseTree,
                                                       saltUid=self.uid,
                                                       andCoreData=True)
            resultTree.muteSignals(recursive=True)
            self.markedDirty.mute()

            # apply transform objects
            for i in self.transformers:
                resultTree = i.transformTree(resultTree)
                assert isinstance(resultTree, TreeBase), \
                 f"Incorrect type {resultTree} returned from transformation {i}"

            self._resultTree = resultTree
            self.dirty = False
            resultTree.activateSignals(recursive=True)
            self.markedDirty.activate()
            self.resultChanged.emit()

        return self._resultTree
Beispiel #11
0
class TreeInterface(TreeBase):

    keyType = (str, T.Sequence[str])

    separatorChar = "/"  # string char allowing splitting string keys
    parentChar = "^"  # string char denoting direct parent

    # behaviour for generating branches
    branchesInheritType = False

    StructureEvents = StructureEvents
    LookupMode = LookupMode

    # properties and descriptors
    TreePropertyDescriptor = TreePropertyDescriptor
    TreeBranchDescriptor = TreeBranchDescriptor

    # strings used for properties
    LOOKUP_CREATE_KEY = "_lookupCreate"
    DESCRIPTION_KEY = "_desc"
    OPTION_KEY = "_options"
    READ_ONLY_KEY = "_readOnly"

    DEFAULT_PROP_KEY = "_default"
    default = TreePropertyDescriptor(DEFAULT_PROP_KEY,
                                     default=None,
                                     inherited=True)

    lookupCreate = TreePropertyDescriptor(LOOKUP_CREATE_KEY,
                                          default=False,
                                          inherited=True)

    description = TreePropertyDescriptor(DESCRIPTION_KEY,
                                         default="",
                                         inherited=False)

    readOnly = TreePropertyDescriptor(OPTION_KEY,
                                      default=False,
                                      inherited=True)

    # discrete values for tree, like enum, True/False, etc
    options: optionType = TreePropertyDescriptor(READ_ONLY_KEY,
                                                 default=False,
                                                 inherited=False)

    def __init__(self, name: str, value=None):

        # signals emitting DeltaAtom objects
        self.deltasChanged = Signal()  # emitted on caller request
        self.stateChanged = Signal(
        )  # emitted when absolute final result of tree changes
        self.gatheringDeltas = True

        # internal tree attrs
        self._name = name
        self._value = value
        self._parent = None

    def signals(self):
        return (self.deltasChanged, self.stateChanged)

    @classmethod
    def defaultBranchCls(cls):
        """might be an idea to have an instance version of this,
		to define per-branch areas of the tree to inherit types,
		while other areas stay as defaults"""
        if cls.branchesInheritType:
            return cls
        raise NotImplementedError

    @property
    def name(self) -> str:
        return self._name

    @name.setter
    def name(self, val: str):
        self.setName(val)

    def _evalDefault(self):
        if callable(self.default):
            return self.default(self)
        return self.default

    @property
    def value(self) -> T:
        if self._value is None and self.default is not None:
            self._value = self._evalDefault()
        return self._value

    @value.setter
    def value(self, val: T):
        self.setValue(val)

    v = value

    # connected nodes
    @property
    def parent(self) -> (TreeType):
        """return this tree's parent object"""
        raise NotImplementedError

    @property
    def root(self) -> TreeType:
        branch = self
        while branch.parent is not None:
            branch = branch.parent
        return branch

    @property
    def branchMap(self) -> dict[str, TreeType]:
        """return a nice view of {tree name : tree}
		generated from uid map"""
        raise NotImplementedError

    @property
    def isLeaf(self) -> bool:
        return not self.branches

    @property
    def leaves(self) -> list[TreeType]:
        """returns branches under this branch
		which do not have branches of their own"""
        return [i for i in self.allBranches(False) if i.isLeaf]

    def keys(self) -> tuple[str]:
        return tuple(self.branchMap.keys())

    @property
    def branches(self) -> list[TreeType]:
        """return a list of immediate branches of this tree"""
        return list(self.branchMap.values())

    @property
    def siblings(self) -> list[TreeType]:
        if self.parent:
            return [i for i in self.parent.branches if i is not self]
        return []

    def allBranches(self, includeSelf=True, depthFirst=True) -> list[TreeType]:
        """ returns list of all tree objects
		depth first"""
        found = [self] if includeSelf else []
        if depthFirst:
            for i in self.branches:
                found.extend(i.allBranches(includeSelf=True, depthFirst=True))
        else:
            found.extend(self.branches)
            for i in self.branches:
                found.extend(i.allBranches(includeSelf=False,
                                           depthFirst=False))
        return found

    def _ownIndex(self) -> int:
        if self.parent:
            return self.parent.index(self.name)
        else:
            return -1

    def index(self, lookup=None, *args, **kwargs) -> int:
        if lookup is None:  # get tree's own index
            return self._ownIndex()
        if lookup in self.branchMap.keys():
            return list(self.branchMap.keys()).index(lookup, *args, **kwargs)
        else:
            return -1

    def flattenedIndex(self) -> int:
        """ return the index of this branch if entire tree were flattened """
        index = self.index()
        if self.parent:
            index += self.parent.flattenedIndex() + 1
        return index

    def trunk(self, includeSelf=True) -> list[TreeType]:
        """return sequence of ancestor trees in descending order to this tree"""
        baseList = [self] if includeSelf else []
        if not self.parent:
            return baseList
        return self.parent.trunk(includeSelf=False) + [self.parent] + baseList

    def depth(self) -> int:
        """return int depth of this tree from root"""
        return len(self.trunk(includeSelf=False))

    # addresses

    def address(self, includeSelf=False, _prev=None) -> list[str]:
        """if uid, return path by uids
		else return nice string paths"""
        prev = _prev or []
        token = self.name
        root = self.root
        if not prev and includeSelf:
            prev = [token]
        if root is self:
            return prev
        prev.insert(0, token)
        return self.parent.address(_prev=prev)

    def stringAddress(self, includeRoot=False) -> str:
        """ returns the address sequence joined by the tree separator """
        base = self.separatorChar.join(self.address())
        if includeRoot:
            base = self.root.name + self.separatorChar + base
        return base

    def commonParent(self, otherBranch: TreeType) -> TreeType:
        """ return the lowest common parent between given branches
		or None
		if one branch is direct parent of the other,
		that branch will be returned

		this uses absolute parents, ignoring breakpoints
		"""
        # print("commonParent")
        if self.root is not otherBranch.root:
            return None
        otherTrunk = set(otherBranch.trunk(includeSelf=True))
        # otherTrunk.add(otherBranch)
        test = self
        while test not in otherTrunk:
            test = test.parent
        return test

    def relAddress(self, fromBranch=None):
        """ retrieve the relative path from the given branch to this one"""

        # check that branches share a common tree (root)
        common = self.commonParent(fromBranch)
        if not common:
            raise LookupError("Branches {} and {} "
                              "do not share a common root".format(
                                  self, fromBranch))

        addr = []
        commonDepth = common.depth
        # parent tokens to navigate up from other
        for i in range(commonDepth - fromBranch.depth):
            addr.append(self.root.parentToken)
        # add address to this node
        addr.extend(self.address(includeSelf=False)[commonDepth - 1:])
        return addr

    # calling - main features of "tree syntax"
    @classmethod
    def parseAddressTokens(cls, *tokenArgs: keyType) -> list[str]:
        """ address may be arbitrarily nested strings,
		each potentially containing separators
		return uniform flat list of tokens
		:type tokenArgs : tuple"""
        # first flatten address
        flat = list(flatten(tokenArgs))
        # check for any string tokens that need splitting
        for i, token in enumerate(tuple(flat)):
            if isinstance(token, str):
                flat[i] = token.split(cls.separatorChar)

        # flatten any tuples that the string formatting produced
        return flatten(flat)

    def _branchFromToken(self, token: keyType) -> (TreeType, None):
        """ given single address token, return a known branch or none """
        if token == self.parentChar:
            return self.parent
        if token not in self.branchMap.keys():
            return None
        return self.branchMap[token]

    def _getBranchInternal(self, lookup: keyType) -> (TreeType, None):
        """look up a single layer of branch from first of given tokens"""
        if not lookup:
            return self
        name = lookup.pop(0)
        found = self._branchFromToken(name)
        if not found:
            return None
        return found._getBranchInternal(lookup)

    def getBranch(self, key: keyType) -> (TreeType, None):
        key = self.parseAddressTokens((key, ))
        return self._getBranchInternal(key)

    def get(self, key: keyType, default=None):
        """return branch value"""
        result = self.getBranch(key)
        if result:
            return result.value
        return default

    def __call__(self, *address: keyType, create=None, **kwargs) -> TreeType:
        """ index into tree hierarchy via address sequence,
		return matching branch"""

        address = self.parseAddressTokens(address)
        if not address:
            return self

        # all input coerced to list
        first = str(address.pop(0))

        found = self.getBranch(first)
        if found:
            return found(address, create=create, **kwargs)

        # if create is passed directly, use it -
        # else use lookupcreate default
        activeCreate = create if create is not None else self.lookupCreate

        # if branch should not be created, lookup is invalid
        if not activeCreate:
            raise LookupError("tree {} has no child {}".format(self, first))

        # create new child branch for lookup
        obj = self._createChildBranch(first, kwargs)

        # add it to this tree
        self.addChild(obj)
        branch = self.getBranch(first)

        return branch(*address, create=create, **kwargs)

    def __setitem__(self, key: (str, tuple), value: T, **kwargs):
        """ assuming that setting tree values is far more frequent than
		setting actual tree objects """
        self(key, **kwargs).value = value

    def __getitem__(self, address: (str, tuple), **kwargs) -> T:
        """ returns direct value of lookup branch
		:rtype T
		"""
        return self(address, **kwargs).value

    # signal systems
    def emitStackDelta(self, delta: TreeDeltaAtom, apply=True):
        """emit a semantic delta after a caller modification to this data structure
		"""
        if not self.gatheringDeltas:
            return
        self.deltasChanged.emit(delta)
        if apply:
            delta.doDelta(self.root)

    def emitStateDelta(self, delta: TreeDeltaAtom):
        """emit a raw delta for any end change to this tree's state
		"""
        self.stateChanged.emit(delta)

    # referencing
    def getRef(self) -> TreeReference:
        """return a persistent reference to this branch"""
        return TreeReference(self)

    # writing functions
    # _private set() functions called from within deltas

    def setName(self, name: str):
        delta = TreeNameDelta(self, oldValue=self.name, newValue=name)
        self.emitStackDelta(delta, apply=True)

    def _setName(self, name: str):
        """override here """
        self._name = name

    def setValue(self, value):
        delta = TreeValueDelta(self, oldValue=self.value, newValue=value)
        self.emitStackDelta(delta, apply=True)

    def _setValue(self, value):
        self._value = value
        # final value state delta
        # delta = TreeValueDelta(self, oldValue=self.value, newValue=value)
        # self.emitStateDelta(delta)

    # parenting

    def setIndex(self, index: int):
        """ reorders tree branch to given index
		negative indices not yet supported """
        if not self.parent:
            return
        if index < 0:  # ?
            index = len(self.siblings) + index

        delta = TreeStructureDelta(
            self,
            self.parent,
            self.parent,
            oldIndex=self.index(),
            newIndex=index,
            eventCode=StructureEvents.branchIndexChanged)
        self.emitStackDelta(delta, apply=True)

        self.parent.coreData().branchDatas.remove(self.coreData())
        self.parent.coreData().branchDatas.insert(index, self.coreData())

        # emit signal of parent
        if self._signalsActive:
            self.parent.structureChanged(
                self, self.parent, self.parent,
                self.StructureEvents.branchIndexChanged)

    def _setIndex(self, index: int):
        raise NotImplementedError

    def _getRemoveParentAndTarget(
        self,
        address: keyType = None,
    ) -> tuple[TreeType, TreeType]:
        """return parent, child targets for removal
		if no target is given, remove the branch this method is
		called on, eg branchA.remove() -> removes branchA from its parent"""
        if not address:
            if not self.parent:
                return None, self
            return self.parent, self
        if isinstance(address, TreeBase):
            branch = address
        else:
            branch = self(address, create=False)
        return branch.parent, branch

    def remove(
        self,
        address: (keyType, TreeInterface, None) = None,
    ):
        """removes address, or just removes the tree if no address is given"""
        parent, removeTarget = self._getRemoveParentAndTarget(address)
        if parent is not self:
            return parent.remove(removeTarget)
        elif parent is None:  # branch had no parent to begin with
            return

        delta = TreeDeletionDelta(removeTarget, self, removeTarget.name,
                                  removeTarget.value, removeTarget.properties)
        self.emitStackDelta(delta, apply=True)

    def _remove(self, branch: TreeType):
        """ pass a branch in this tree to remove"""
        raise NotImplementedError

    def addChild(self,
                 newBranch: TreeInterface,
                 index: int = None) -> TreeType:
        """called on parent to add new child node
		determine if branch was already known to current root -
		if so, branch is effectively moved
		if not, branch is effectively created
		"""

        # get correct index
        index = index if index is not None else len(self.branches)

        currentRoot = newBranch.root
        delta = TreeStructureDelta(newBranch,
                                   newBranch.parent,
                                   self,
                                   oldIndex=newBranch.index(),
                                   newIndex=index,
                                   eventCode=self.StructureEvents.branchAdded)
        self.emitStackDelta(delta, apply=True)

    def _addChild(self, newBranch: TreeInterface, index: int) -> TreeType:
        raise NotImplementedError

    def _setParent(self, parentBranch: TreeInterface):
        """only internal, not to be used in client code"""
        # disconnect and reconnect signal to root
        for currentRootSignal, newRootSignal, selfSignal in zip(
                self.root.signals(), parentBranch.root.signals(),
                self.signals()):
            selfSignal.disconnect(currentRootSignal)
            selfSignal.connect(newRootSignal)

    def _createChildBranch(self, name, kwargs) -> TreeType:
        """called internally when a branch is created on lookup"""
        # check if branch should inherit directly
        if self.branchesInheritType:
            obj = self.__class__(name, None)
        else:
            obj = self.defaultBranchCls()(name=name)
        return obj

    # other behaviour
    @property
    def properties(self) -> dict:
        raise NotImplementedError

    def getProperty(self, key: str, default=None):
        return self.properties.get(key, default)

    def setProperty(self, key: str, value):
        oldValue = self.getProperty(key, default=FailToFind)
        delta = TreePropertyDelta(self, key, oldValue, value)
        self.emitStackDelta(delta, apply=True)

    def _setProperty(self, key: str, value):
        self.properties[key] = value

    def _removeProperty(self, key):
        if key in self.properties:
            self.properties.pop(key)

    def getInherited(self, key, default=None, returnBranch=False):
        """return first instance of key found in trunk properties
		if returnBranch, returns the first node in trunk that
		includes key
		"""
        lookup = self.getProperty(key, FailToFind)
        if lookup is not FailToFind:
            return lookup
        if self.parent:
            return lookup if lookup is not FailToFind else self.parent.getInherited(
                key, default=default, returnBranch=returnBranch)
        return default

    #region breakpoints
    # for defining distinct regions within contiguous hierarchy
    @property
    def breakTags(self) -> set[str]:
        return set(self.getProperty("breakTags", set()))

    def setBreakTags(self, val=set()):
        if isinstance(val, str):
            val = {val}
        self.setProperty("breakTags", set(val))

    @property
    def isBreakRoot(self):
        """checks if this branch is a "main" breakpoint"""
        return "main" in self.breakTags

    def setBreakRoot(self):
        self.setBreakTags(self.breakTags.union({"main"}))

    def breakRoot(self) -> TreeType:
        """return the first parent with 'main' in its break tags"""
        return self.getBreak(("main", ))

    @property
    def absoluteRoot(self) -> TreeType:
        """returns physical top of current hierarchy, ignoring breakpoints"""
        return self.parent.absoluteRoot if self.parent else self

    breakTagType = T.Union[T.Sequence[str], str]

    def isBreak(self, breakTags: breakTagType = ("main", )):
        """check if this tree is a breakpoint
		for any of the given tags"""
        breakTags = (breakTags, ) if isinstance(breakTags, str) else breakTags
        return set(breakTags) <= self.breakTags

    def getBreak(self, breakTags: breakTagType = ("main", )) -> TreeType:
        """return the first matching breakpoint in this tree's trunk
		"""
        if not breakTags:
            return self.absoluteRoot
        branch = self
        while branch is not self.absoluteRoot:
            if branch.isBreak(breakTags):
                return branch
            branch = branch.parent
        return None

    #endregion
Beispiel #12
0
class TreeView(QtWidgets.QTreeView):
    """widget for viewing and editing an Tree
	display values in columns, branches in rows"""
    highlightKind = {
        "error": QtCore.Qt.red,
        "warning": QtCore.Qt.yellow,
        "success": QtCore.Qt.green,
    }
    background = QtGui.QColor(100, 100, 128)

    # {oldBranch, newBranch}
    currentBranchChanged = QtCore.Signal(dict)

    def __init__(self,
                 parent=None,
                 nameDelegateCls=TreeNameDelegate,
                 valueDelegateCls=TreeValueDelegate,
                 contextMenu: TreeMenu = None,
                 showValues=True,
                 showRoot=False,
                 scanForWidgets=False):
        super(TreeView, self).__init__(parent)
        self.root = None
        self.nameDelegateCls = nameDelegateCls
        self.valueDelegateCls = valueDelegateCls
        self.highlights = {}  # dict of tree addresses to highlight
        self.valuesShown = showValues
        self.showRoot = showRoot
        self.scanForWidgets = scanForWidgets

        # either pass this at init or assign dynamically in subclasses
        self.context = contextMenu

        self.contentChanged = Signal()
        self.sizeChanged = Signal()
        self.currentSelected = None
        self.editedIndex = None  # stored on editing
        self.scrollPos = self.verticalScrollBar().value()
        self.actions = {}

        # set delegate classes
        self.setItemDelegateForColumn(0, self.nameDelegateCls(self))
        self.setItemDelegateForColumn(1, self.valueDelegateCls(self))

        # convenience wrappers
        self.keyState = KeyState()
        self.sel = SelectionModelContainer(self.selectionModel(), parent=self)

        # internal stores for drawing overrides
        self._liveInputRoots = weakref.WeakSet()
        self._deltaTrackingRoots = weakref.WeakSet()
        self._deltaCreatedBranches = weakref.WeakSet()
        self._deltaModifiedBranches = weakref.WeakSet()
        self._deltaDeletedBranches = weakref.WeakSet()
        self._widgetBranches = weakref.WeakKeyDictionary()

        self.savedSelectedTrees = []
        self.savedExpandedTrees = []

        self.makeBehaviour()
        self.makeAppearance()
        self.makeConnections()
        self.makeBaseActions()

        self.expandAll()
        self.showValues(showValues)

    def makeBehaviour(self):
        self.setDragEnabled(True)
        self.setAcceptDrops(True)
        self.setDragDropMode(QtWidgets.QAbstractItemView.InternalMove)
        self.setSelectionMode(self.ExtendedSelection)
        self.setSelectionBehavior(self.SelectRows)
        #self.setDragDropMode()
        #self.setDropIndicatorShown()
        self.setAutoScroll(False)
        self.setFocusPolicy(QtCore.Qt.ClickFocus)
        self.setDefaultDropAction(QtCore.Qt.CopyAction)

    def makeAppearance(self):

        # appearance
        if doIcons:
            self.setWindowIcon(QtGui.QIcon(squareCentre))
            self.setStyleSheet(styleSheet)
        self.setSizePolicy(expandingPolicy)

        header = self.header()
        header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
        header.setStretchLastSection(True)

        self.setSizeAdjustPolicy(
            QtWidgets.QAbstractScrollArea.AdjustToContents)
        self.setUniformRowHeights(True)
        self.setIndentation(10)
        #self.setAlternatingRowColors(True)
        self.showDropIndicator()

    def makeConnections(self):
        self.contentChanged.connect(self.onKeyVisibilitySet)
        self.contentChanged.connect(self.resizeToTree)
        self.expanded.connect(self.onExpanded)
        self.collapsed.connect(self.onCollapsed)

    @property
    def tree(self):
        return self.parent().tree

    def model(self) -> TreeModel:
        return super(TreeView, self).model()

    def setModel(self, model: QtCore.QAbstractItemModel) -> None:
        super(TreeView, self).setModel(model)
        self.model().layoutAboutToBeChanged.connect(self.saveAppearance)
        self.model().layoutChanged.connect(self.restoreAppearance)
        self.model().itemChanged.connect(self.onModelItemChanged)
        self.setSelectionModel(QtCore.QItemSelectionModel(self.model()))
        self.sel.setSelectionModel(self.selectionModel())
        self.selectionModel().currentChanged.connect(self.onCurrentChanged)

        for i in (self.model().layoutChanged, self.model().itemChanged,
                  self.model().dataChanged):
            i.connect(self.onTreeChanged)

    def setTree(self, tree):
        """associates widget with Tree object"""
        #self.root = tree.root
        self.root = self.tree

        self.expandAll()
        if self.showRoot:
            self.setRootIndex(self.model().invisibleRootItem().index())
        else:
            self.setRootIndex(self.model().invisibleRootItem().child(
                0, 0).index())

        # no direct tree signal connections -
        # only connect to signals from model to ensure
        # UI fires after model changes

        # # connect tree signals
        # for i in self.root.signals():
        # 	i.connect(self.onTreeChanged)
        self.onTreeChanged()
        return

    def data(self, index, role=QtCore.Qt.DisplayRole):
        """ convenience """
        #self.viewport()
        return self.model().data(index, role)

    def onTreeChanged(self, *args, **kwargs):
        #print("on tree changed")
        self.refreshDrawingInternals()
        self.onKeyVisibilitySet()
        self.resizeToTree()

    def refreshDrawingInternals(self):
        for i in (self._liveInputRoots, self._deltaTrackingRoots,
                  self._deltaCreatedBranches, self._deltaModifiedBranches,
                  self._deltaDeletedBranches):
            i.clear()
        # for i in self.tree.allBranches(): #type:Tree
        # 	if i.liveInput:
        # 		self._liveInputRoots.add(i)
        # 	if i._deltaTracker:
        # 		self._deltaTrackingRoots.add(i)
        #
        # 	if i.isDeltaCreated():
        # 		self._deltaCreatedBranches.add(i)
        # 	elif i.deltaBaseName():
        # 		self._deltaModifiedBranches.add(i)

        if self.scanForWidgets:
            self.generateWidgetsByProperties()

    def boundingRectForBranch(self,
                              branch,
                              includeBranches=True) -> QtCore.QRect:
        index = self.model().rowFromTree(branch)
        rect = self.visualRect(index)
        if includeBranches:
            for i in self.model().allChildren(index):
                rect = rect.united(self.visualRect(i))
        return rect

    #
    # def paintEvent(self, event:QtGui.QPaintEvent) -> None:
    # 	"""draw overlays as post process"""
    # 	painter = QtGui.QPainter(self.viewport())
    # 	refExpand = 3
    # 	result = super(TreeView, self).paintEvent(event)
    #
    # 	for branch in self._liveInputRoots:
    # 		rect = self.boundingRectForBranch(branch, includeBranches=True)
    # 		rect = rect.marginsAdded(QtCore.QMargins(
    # 			refExpand, refExpand, refExpand, refExpand))
    #
    # 		brush = QtGui.QBrush(QtGui.QColor(150, 250, 150, 32))
    # 		painter.setBrush(brush)
    # 		#painter.setPen(QtCore.Qt.PenStyle.NoPen)
    # 		pen = QtGui.QPen(QtGui.QColor(150, 200, 150, 64))
    # 		painter.setPen(pen)
    # 		painter.drawRoundedRect(rect, 4.0, 4.0)
    #
    # 	# check for delta tracking
    # 	deltaExpand = 1
    # 	for branch in self._deltaTrackingRoots:
    # 		rect = self.boundingRectForBranch(branch, includeBranches=True)
    # 		rect = rect.marginsAdded(QtCore.QMargins(
    # 			deltaExpand, deltaExpand, deltaExpand, deltaExpand))
    #
    # 		# purple dots
    # 		pen = QtGui.QPen(QtGui.QColor(160, 80, 240))
    # 		pen.setWidth(1)
    # 		pen.setDashOffset(5)
    # 		pen.setStyle(QtCore.Qt.DashLine)
    # 		painter.setPen(pen)
    # 		painter.drawRoundedRect(rect, 1.0, 1.0)
    #
    # 	self._deltaModifiedBranches.difference_update(self._deltaCreatedBranches)
    #
    # 	# paint rectangles for each delta-affected branch
    # 	for branch in self._deltaCreatedBranches:
    # 		rect = self.boundingRectForBranch(branch, includeBranches=True)
    # 		col = QtGui.QColor(*createColour)
    # 		pen = QtGui.QPen(col)
    # 		brush = QtGui.QBrush(col)
    # 		#brush.color().setAlpha(128)
    # 		painter.setPen(pen)
    # 		painter.setBrush(brush)
    # 		painter.drawRoundedRect(rect, 1.0, 1.0)
    # 	for branch in self._deltaModifiedBranches:
    # 		rect = self.boundingRectForBranch(branch, includeBranches=False)
    # 		rect = rect.marginsRemoved(QtCore.QMargins(1, 1, 1, 1))
    # 		col = QtGui.QColor(*modifyColour)
    # 		pen = QtGui.QPen(col)
    # 		#col.setAlpha(128)
    # 		col = col.lighter(120)
    # 		brush = QtGui.QBrush(col)
    # 		#brush.color().setAlpha(128)
    # 		painter.setPen(pen)
    # 		painter.setBrush(brush)
    # 		painter.drawRoundedRect(rect, 1.0, 1.0)
    #
    # 	# result = super(TreeView, self).paintEvent(event)

    def makeBaseActions(self):
        """sets up copy, add, delete etc actions for branch entries"""

    def showValues(self, state=True):
        """tracks if value column is shown or not"""
        self.setColumnHidden(1, not state)
        self.valuesShown = state

    # region events

    @catchAll
    def mousePressEvent(self, event):
        #print("tree mousePress")
        # if event.isAccepted():
        # 	print("tree event accepted")
        # 	return True
        self.keyState.mousePressed(event)

        # only pass event on editing,
        # need to manage selection separately
        if not (self.keyState.ctrl or self.keyState.shift)\
          or event.button() == QtCore.Qt.RightButton:
            return super(TreeView, self).mousePressEvent(event)
            pass

        index = self.indexAt(event.pos())
        self.onClicked(index)
        event.accept()

    def onClicked(self, index):
        """ manage selection manually """
        # if ctrl, toggle selection
        if self.keyState.ctrl and not self.keyState.shift:
            self.sel.toggle(index)
            self.sel.setCurrent(index)
            return
        elif self.keyState.shift:  # contiguous span

            clickRow = self.model().rowFromIndex(index)
            currentRow = self.model().rowFromIndex(self.sel.current())
            # find physically lowest on screen
            if self.visualRect(clickRow).y() < \
             self.visualRect(currentRow).y():
                fn = self.indexAbove
            else:
                lowest = clickRow
                highest = currentRow
                fn = self.indexBelow
            targets = []
            selStatuses = []
            checkIdx = currentRow
            selRows = self.selectionModel().selectedRows()
            count = 0
            while checkIdx != clickRow and count < 4:
                count += 1
                checkIdx = fn(checkIdx)
                targets.append(checkIdx)
                selStatuses.append(checkIdx in selRows)

            addOrRemove = sum(selStatuses) < len(selStatuses) / 2
            for row in targets:
                self.sel.add(row)

        # set previous selection
        self.sel.setCurrent(index)
        self.currentSelected = index

    def selectedBranches(self) -> Ty.List[Tree]:
        """returns branches for all name and value items selected in ui"""
        branchList = []
        for i in self.sel.rows:
            branch = self.model().branchFromIndex(i)
            if branch in branchList:
                continue
            branchList.append(branch)
        return branchList

    def selectedValues(self) -> Ty.List[Tree]:
        """old return branches whose values ONLY have been selected, not
		names
		used to allow people to set tree branch values"""
        indices = [i for i in self.sel.indices if i.column() == 1]
        return list(map(self.model().branchFromIndex, indices))

    def getContextMenu(self) -> TreeMenu:
        """override however is easiest"""
        return self.context

    def contextMenuEvent(self, event):
        if self.getContextMenu() is None:
            print("No context menu assigned, deferring")
            return super(TreeView, self).contextMenuEvent(event)
        self.context.onContextRequested()
        # pos = event.localPos()
        pos = event.globalPos()
        pos = event.pos()
        # pos = self.viewport().mapFromGlobal( self.mapToGlobal( event.pos()))
        pos = self.mapToGlobal(event.pos())
        menu = self.context.exec_(pos)
        event.accept()

    def onCurrentChanged(self, currentIdx: QtCore.QModelIndex,
                         prevIdx: QtCore.QModelIndex):
        """connected to selection model - convert model indices
		to branches, then emit top-level signal"""
        newBranch = currentIdx.data(treeObjRole)
        prevBranch = prevIdx.data(treeObjRole)
        self.currentBranchChanged.emit({
            "oldBranch": prevBranch,
            "newBranch": newBranch
        })

    def copyEntries(self):
        clip = QtGui.QGuiApplication.clipboard()
        indices = self.selectionModel().selectedRows()
        if not indices:  # nothing to copy
            return
        mime = self.model().mimeData(indices)
        clip.setMimeData(mime)

    def pasteEntries(self):
        indices = self.selectedIndexes()  # i strongly hate
        if not indices:
            return
        index = indices[0]
        clip = QtGui.QGuiApplication.clipboard()
        data = clip.mimeData()
        self.model().dropMimeData(data, QtCore.Qt.CopyAction, 0, 0, index)
        self.sync()

    def dropEvent(self, event):
        super(TreeView, self).dropEvent(event)

    def keyPressEvent(self, event):
        """ bulk of navigation operations,
		for hierarchy navigation aim to emulate maya outliner

		ctrl+D - duplicate
		del - delete

		left/right - select siblings
		up / down - select child / parent

		p - parent selected branches to last selected
		shiftP - parent selected branches to root

		ctrl + shift + left / right - shuffle selected among siblings

		events modify the core tree data structure - model and view
		are rebuilt atop it
		not sure if there is an elegant way to structure this
		going with disgusting battery of if statements

		"""
        self.keyState.keyPressed(event)

        sel = self.selectionModel().selectedRows()
        key = event.key()
        # don't override anything if editing is in progress
        if self.state() == QtWidgets.QTreeView.EditingState or len(sel) == 0:
            #print("tree editing, skipping")
            event.accept()
            return True

        # editing entry
        if key in [QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return]:

            # shift-enter begins editing on value
            if self.keyState.shift:
                idx = sel[0].siblingAtColumn(1)
            else:  # edit name
                idx = sel[0]
            self.editedIndex = idx
            self.edit(idx)
            event.accept()
            return True

        if self.keyState.ctrl and key in \
         (QtCore.Qt.Key_D, QtCore.Qt.Key_C,
          QtCore.Qt.Key_V, QtCore.Qt.Key_X):
            if key == QtCore.Qt.Key_D:  # duplicate
                for row in sel:
                    self.model().duplicateRow(row)
            elif key == QtCore.Qt.Key_C:  # copy
                self.copyEntries()
            elif key == QtCore.Qt.Key_V:
                self.copyEntries()
                for row in sel:
                    self.model().deleteRow(row)
            elif key == QtCore.Qt.Key_V:  # paste
                self.pasteEntries()
            event.accept()
            return True

        # shifting row up or down
        if self.keyState.shift and self.keyState.ctrl:
            self.saveAppearance()
            if key in [QtCore.Qt.Key_Up, QtCore.Qt.Key_Left]:
                for row in sel:
                    self.model().shiftRow(row, up=True)
            elif key in [QtCore.Qt.Key_Down, QtCore.Qt.Key_Right]:
                for row in sel:
                    self.model().shiftRow(row, up=False)
            self.restoreAppearance()
            event.accept()
            #self.sync()
            return True

        # deleting
        if key == QtCore.Qt.Key_Delete:
            #print("deleting")
            for row in sel:
                self.model().deleteRow(row)
            #self.sync()
            event.accept()
            return True

        # reparenting
        if key == QtCore.Qt.Key_P:
            if self.keyState.shift:
                for row in sel:  # unparent row
                    self.model().unParentRow(row)
            elif len(sel) > 1:  # parent
                self.model().parentRows(sel[:-1], sel[-1])
            event.accept()
            return True
        if key in (QtCore.Qt.Key_Tab, QtCore.Qt.Key_Backtab):
            if self.keyState.shift:  # unparent row
                print(sel)
                for row in sel:
                    self.model().unParentRow(row)
            else:  # parent to row directly above
                for row in reversed(sel):
                    adj = self.model().connectedIndices(row)
                    if adj["prev"] and not adj["prev"] == row:
                        self.model().parentRows([row], target=adj["prev"])
            #self.sync()
            event.accept()
            return True

        # direction keys to move cursor
        if key in (QtCore.Qt.Key_Left, QtCore.Qt.Key_Right, QtCore.Qt.Key_Up,
                   QtCore.Qt.Key_Down):
            self.sel.clear()
            if self.sel.current():
                sel.append(self.sel.current())
            for i in sel:
                adj = self.model().connectedIndices(i)
                target = None
                if key == QtCore.Qt.Key_Left:
                    # back one index
                    if adj["prev"]:
                        target = adj["prev"]
                elif key == QtCore.Qt.Key_Right:
                    # forwards one index
                    if adj["next"]:
                        target = adj["next"]
                elif key == QtCore.Qt.Key_Up:
                    # up to parent
                    if adj["parent"]:
                        target = (i.parent())
                    else:
                        target = i
                else:
                    # down to child
                    if i.child(0, 0).isValid():
                        target = i.child(0, 0)
                    else:
                        target = i

                if target:
                    self.sel.add(target)
                if i == self.sel.current():
                    self.sel.setCurrent(target)
            event.accept()
            return True

        return super(TreeView, self).keyPressEvent(event)

    #endregion

    # region appearance
    def resizeToTree(self, *args, **kwargs):
        colWidth = self.sizeHintForColumn(0) + self.sizeHintForColumn(1) + 10
        self.setMinimumWidth(colWidth)
        self.setGeometry(
            0,
            0,
            #baseRect.width() + baseRect.x(),
            colWidth,
            self.viewportSizeHint().height() + self.header().rect().height())
        pass

    def saveAppearance(self):
        """ saves expansion and selection state """
        self.savedSelectedTrees = []
        self.savedExpandedTrees = []
        self.currentSelected = None
        for i in self.selectionModel().selectedRows():
            branch = self.model().treeFromRow(i)
            self.savedSelectedTrees.append(branch)
        for i in self.model().allRows():
            if not self.model().checkIndex(i):
                print("index {} is not valid, skipping".format(i))
            if self.isExpanded(i):
                branch = self.model().treeFromRow(i)
                if branch:
                    self.savedExpandedTrees.append(branch)
        if self.selectionModel().currentIndex().isValid():
            self.currentSelected = self.model().treeFromRow(
                self.selectionModel().currentIndex())
        # save viewport scroll position
        self.scrollPos = self.verticalScrollBar().value()

    def restoreAppearance(self):
        """ restores expansion and selection state """
        self.setRootIndex(self.model().invisibleRootItem().child(0, 0).index())

        self.resizeToTree()

        print(self.savedExpandedTrees, self.savedExpandedTrees)
        for i in self.savedSelectedTrees:
            if not self.model().rowFromTree(i):
                continue

            self.selectionModel().select(
                self.model().rowFromTree(i), QtCore.QItemSelectionModel.Select
                | QtCore.QItemSelectionModel.Rows)
        for i in self.savedExpandedTrees:
            if not self.model().rowFromTree(i):
                print("expanded tree not found ")
                continue
            self.expand(self.model().rowFromTree(i))
            pass
        if self.currentSelected:
            #print("setting current")
            row = self.model().rowFromTree(self.currentSelected)
            self.sel.setCurrent(row)
        self.verticalScrollBar().setValue(self.scrollPos)

        self.resizeToTree()

    def sync(self, *args, **kwargs):
        print("widget sync")
        pass
        self.saveAppearance()
        sel = self.selectionModel()
        # self.model().sync()
        # self.setRootIndex(self.model().invisibleRootItem().child(0, 0).index())
        self.setSelectionModel(sel)
        self.restoreAppearance()
        self.expandAll()
        self.onKeyVisibilitySet()
        self.resizeToTree()

    def addHighlight(self, address, kind):
        """adds a highlight to TreeView line, depending on reason"""
        colour = QtCore.QColor(self.highlightKind[kind])
        self.highlights[address] = kind

    def _recursiveApply(
        self,
        fnName,
        startIndex,
        callSuper=True,
    ):
        """apply a method recursively to all child indices"""
        if callSuper:
            result = getattr(super(TreeView, self), fnName)(startIndex)
        else:
            result = None
        for i in range(self.model().rowCount(startIndex)):
            childIdx = startIndex.child(i, 0)
            getattr(self, fnName)(childIdx)
        return result

    def onExpanded(self, index):
        """ check for shift - recursively expand children if so """
        if self.keyState.shift:
            self._recursiveApply("expand", index)

    def onCollapsed(self, index):
        """ check for shift - recursively expand children if so """
        if self.keyState.shift:
            self._recursiveApply("collapse", index)

    def display(self):
        self.sync()
        print(self.tree.displayStr())

    #endregion

    #
    # def commitData(self, editor):
    # 	print("commit", self.editedIndex)
    # 	# if self.editedIndex:
    # 	# 	#print(self.editedIndex.isValid())
    # 	# 	self.sel.setCurrent(self.editedIndex)
    # 	# 	self.sel.add(self.editedIndex)
    # 	return super(TreeView, self).commitData(editor)
    # 	# if self.editedIndex:
    # 	# 	print(self.editedIndex.isValid())
    # 	# 	self.sel.setCurrent(self.editedIndex)
    # 	# 	self.sel.add(self.editedIndex)

    def select(self, branch=None, path=None, clear=True):
        """ main user selection method """
        if clear:
            self.sel.clear()
        if not (branch or path):  # select -cl 1
            return
        if path:
            result = self.tree.getBranch(path)
            if result is None:
                return None
            branch = [result]
        else:
            if not isinstance(branch, (list, tuple)):
                branch = branch

        for b in branch:
            item = self.model().rowFromTree(b)
            self.sel.add(item.index())

    def focusNextPrevChild(self, direction):
        return False

    # widget generation
    def addValueWidgetForBranchParams(self, branch: Tree):
        """if possible, adds an AtomicWidget for
		the value field of the given branch

		track widgets per tree branch, per view
		"""
        # print("branch", branch, "index", self.model().rowFromTree(branch),
        #       self.model().allRows())
        itemIdx = self.model().rowFromTree(branch)  # type:QtCore.QModelIndex

        valueIdx = itemIdx.sibling(itemIdx.row(), 1)

        if self.indexWidget(valueIdx) is not None:
            """for now do not support changing the type of widget
			for an entry live"""
            # print("found existing widget", self.indexWidget(valueIdx), "for", branch)
            return

        value = branch.value
        params: AtomicWidgetParams = branch.getProperty(UI_PROPERTY_KEY)
        params.value = value

        newWidg = atomicWidgetForParams(params)
        newWidg.setParent(self)
        branchItem: TreeBranchItem = self.model().itemFromIndex(
            self.model().rowFromTree(branch))
        valueItem = branchItem.valueItem()

        newWidg.atomValueChanged.connect(branch.setValue)
        self.setIndexWidget(valueIdx, newWidg)

    def onModelItemChanged(self, item: (TreeBranchItem, TreeValueItem)):
        """when data on model item changes, check if it has an
		index widget - if so, sync its state"""
        if not isinstance(item, TreeValueItem):
            return
        if not isinstance(self.indexWidget(item.index()), AtomicWidget):
            return
        print("index widget", self.indexWidget(item.index()))
        # connect widget signals to UI elements only
        widg: AtomicWidget = self.indexWidget(item.index())
        widg.setAtomValue(item.data(role=2))

    def generateWidgetsByProperties(self, *args, **kwargs):
        """iterate over ui tree adding widgets where uiData
		property is used, set to AtomicWidgetParams"""
        for branch in self.tree.allBranches():
            # print("branch ui params", branch.name, branch.getProperty(UI_PROPERTY_KEY))
            if branch.getProperty(UI_PROPERTY_KEY) is None:
                continue

            if isinstance(branch.getProperty(UI_PROPERTY_KEY),
                          AtomicWidgetParams):
                self.addValueWidgetForBranchParams(branch)

    if T.TYPE_CHECKING:

        def parent(self) -> TreeWidget:
            pass

    def onKeyVisibilitySet(self):
        """update item visibility based on parent widget vis map"""
        print("view onKeyVisibilitySet", self.parent().keyVisibilityPresetMap)
        visMap = self.parent().keyVisibilityPresetMap
        for k, v in visMap.items():
            print("k", self.tree.getBranch(k))
            if self.tree.getBranch(k) is not None:
                if not v:  # hide branch item
                    item = self.model().itemFromIndex(self.model().rowFromTree(
                        self.tree.getBranch(k)))
                    self.setRowHidden(item.row(), item.parent().index(), True)
Beispiel #13
0
	def __init__(self, obj, _proxyLink):
		super(ToolFieldProxy, self).__init__(obj, _proxyLink)
		self._proxyData["objectChanged"] = Signal()
		self._proxyData["cacheValue"] = None
Beispiel #14
0
	def __init__(self):
		self._deltaStack : T.List[DeltaAtom] = []
		self._deltaIndex = -1 # which is "current" active delta
		self.deltasChanged = Signal(name="deltasChanged")
Beispiel #15
0
	def __init__(self):

		self.deltaEmitted = Signal(name="deltaEmitted")
		self.stateChanged = Signal(name="stateChanged")
Beispiel #16
0
 def __init__(self, val):
     self._val = val
     self.onDataSet = Signal(name="onDataSet")