"""Base code for presentation elements."""from__future__importannotationsimportastimportloggingimportrefromtypingimportTYPE_CHECKING,Generic,Self,TypeVarfromgaphas.itemimportMatricesfromgaphor.core.modeling.elementimportElement,Handler,Id,UnlinkEventfromgaphor.core.modeling.eventimportRevertibleEventfromgaphor.core.modeling.propertiesimportrelation_many,relation_oneifTYPE_CHECKING:fromgaphor.core.modeling.diagramimportDiagramlog=logging.getLogger(__name__)S=TypeVar("S",bound=Element)defliteral_eval(value:str):returnast.literal_eval(re.sub("\r|\n","",value))
[docs]classPresentation(Matrices,Element,Generic[S]):"""A special type of :obj:`Element` that can be displayed on a :obj:`Diagram`. Subtypes of ``Presentation`` should implement the :obj:`gaphas.item.Item` protocol. """def__init__(self,diagram:Diagram,id:Id|None=None)->None:super().__init__(id=id,model=diagram.model)self.diagram=diagramself._original_diagram:Diagram|None=diagramdefupdate(_event):self.request_update()self._watcher=self.watcher(default_handler=update)self.watch("subject")self.watch("children")self.watch("diagram",self._on_diagram_changed)self.watch("parent",self._on_parent_changed)self.matrix.add_handler(self._on_matrix_changed)subject:relation_one[S]diagram:relation_one[Diagram]parent:relation_one[Presentation]children:relation_many[Presentation]
[docs]defrequest_update(self)->None:"""Mark this presentation object for update. Updates are orchestrated by diagrams. """ifself.diagram:self.diagram.request_update(self)
[docs]defwatch(self,path:str,handler:Handler|None=None)->Self:"""Watch a certain path of elements starting with ``self``. The handler is optional and will default to a simple :obj:`request_update`. Watches should be set in the constructor, so they can be registered and unregistered in one shot. .. code:: self.watch("subject[NamedElement].name") This interface is fluent: returns ``self``. """self._watcher.watch(path,handler)returnself
[docs]defchange_parent(self,new_parent:Presentation|None)->None:"""Change the parent and update the item's matrix so the item visually remains in the same place."""old_parent=self.parentifnew_parentisold_parent:returnself.parent=new_parentm=self.matrixifold_parent:m=m*old_parent.matrix_i2cifnew_parent:m=m*new_parent.matrix_i2c.inverse()self.matrix.set(*m)
defcss_nodes(self):"""CSS nodes present in this element. Returns a sequence of CSS nodes. """return()defload(self,name,value):ifname=="matrix":self.matrix.set(*literal_eval(value))elifname=="parent":ifself.parentandself.parentisnotvalue:raiseValueError(f"Parent can not be set twice on {self}")super().load(name,value)self.parent.matrix_i2c.add_handler(self._on_matrix_changed)self._on_matrix_changed(None,())else:super().load(name,value)definner_unlink(self,_unlink_event:UnlinkEvent)->None:self._watcher.unsubscribe_all()self.matrix.remove_handler(self._on_matrix_changed)ifself.parent:self.parent.matrix_i2c.remove_handler(self._on_matrix_changed)ifdiagram:=self._original_diagram:diagram.connections.remove_connections_to_item(self)self._original_diagram=Nonesuper().inner_unlink(UnlinkEvent(self,diagram=diagram))def_on_diagram_changed(self,event):new_value=event.new_valueifnew_valueandnew_valueisnotself._original_diagram:self.diagram=self._original_diagramraiseValueError("Can't change diagram of a presentation")def_on_parent_changed(self,event):ifold_parent:=event.old_value:old_parent.matrix_i2c.remove_handler(self._on_matrix_changed)ifnew_parent:=event.new_value:new_parent.matrix_i2c.add_handler(self._on_matrix_changed)self._on_matrix_changed(None,())def_on_matrix_changed(self,matrix,old_value):ifself.parent:self.matrix_i2c.set(*(self.matrix*self.parent.matrix_i2c))else:self.matrix_i2c.set(*self.matrix)self.request_update()ifmatrixisself.matrix:self.handle(MatrixUpdated(self,old_value))