Salome HOME
8a415f265f72cebdc1c7757ebb6c659f5f551459
[modules/yacs.git] / src / pyqt / gui / GraphViewer.py
1 # Copyright (C) 2006-2020  CEA/DEN, EDF R&D
2 #
3 # This library is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU Lesser General Public
5 # License as published by the Free Software Foundation; either
6 # version 2.1 of the License, or (at your option) any later version.
7 #
8 # This library is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 # Lesser General Public License for more details.
12 #
13 # You should have received a copy of the GNU Lesser General Public
14 # License along with this library; if not, write to the Free Software
15 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
16 #
17 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
18 #
19
20 import sys
21 from qt import *
22 from qtcanvas import *
23 import math
24
25 class DynamicTip( QToolTip ):
26   def __init__( self, parent ):
27     QToolTip.__init__( self, parent )
28
29   def maybeTip( self, pos ):
30     pos2=self.parentWidget().viewportToContents(pos)
31     point = self.parentWidget().inverseWorldMatrix().map(pos2)
32     ilist = self.parentWidget().canvas().collisions(point) #QCanvasItemList ilist
33     for each_item in ilist:
34       if hasattr(each_item,"tooltip"):
35         each_item.tooltip(self,pos)
36         return
37       elif hasattr(each_item,"getObj"):
38         each_item.getObj().tooltip(self,pos)
39         return
40
41 class GraphViewer(QCanvasView):
42   def __init__(self,c,parent,name,f):
43     QCanvasView.__init__(self,c,parent,name,f)
44     self.__moving=0
45     self.__connecting=0
46     self.__moving_start= 0
47     # for highlighting selections
48     self.selectPen=QPen(QColor(255,255,0),2,Qt.DashLine)
49     self.selectBrush=QBrush(Qt.red)
50     self.selectStyle = Qt.Dense5Pattern
51     self.selected=None
52     self.tooltip = DynamicTip( self )
53
54   def contentsMouseDoubleClickEvent(self,e): # QMouseEvent e
55     point = self.inverseWorldMatrix().map(e.pos())
56     ilist = self.canvas().collisions(point) #QCanvasItemList ilist
57     for each_item in ilist:
58       if each_item.rtti()==984376:
59         if not each_item.hit(point):
60           continue
61       if e.button()== Qt.LeftButton:
62         if hasattr(each_item,"handleDoubleClick"):
63           each_item.handleDoubleClick(point)
64           self.canvas().update()
65           return
66
67
68   def contentsMousePressEvent(self,e): # QMouseEvent e
69     p=e.globalPos()
70     point = self.inverseWorldMatrix().map(e.pos())
71     ilist = self.canvas().collisions(point) #QCanvasItemList ilist
72     for each_item in ilist:
73       if each_item.rtti()==984376:
74         if not each_item.hit(point):
75           continue
76       if e.button()== Qt.RightButton:
77         #Right button click
78         self.__moving=0
79         self.__connecting=0
80         if hasattr(each_item,"popup"):
81           menu=each_item.popup(self)
82           if menu:
83             menu.exec_loop( QCursor.pos() )
84             self.canvas().update()
85         elif hasattr(each_item,"getObj"):
86           menu=each_item.getObj().popup(self)
87           if menu:
88             menu.exec_loop( QCursor.pos() )
89             self.canvas().update()
90
91       elif e.button()== Qt.LeftButton:
92         #Left button click
93         if self.__connecting:
94           #We are linking objects
95           if hasattr(each_item,"getObj"):
96             #a connection is ending
97             self.__connecting.link(each_item.getObj().item)
98             #self.__connecting.link(each_item.getObj())
99             self.canvas().update()
100           self.__connecting=0
101         else:
102           #We are moving or selecting a composite object
103           each_item.selected()
104           self.__moving=each_item
105           self.__moving_start=point
106           self.canvas().update()
107       return
108     if e.button()== Qt.RightButton:
109       menu=self.popup()
110       if menu:
111         menu.exec_loop( QCursor.pos() )
112         self.canvas().update()
113     self.__moving=0
114     self.__connecting=0
115     QCanvasView.contentsMousePressEvent(self,e)
116
117   def selectItem(self,item):
118     #print "selectItem",item
119     if self.selected:
120       try:
121         self.deselectObj(self.selected)
122       except:
123         pass
124       self.selected=None
125     #need to find equivalent canvas item 
126     for citem in self.canvas().allItems():
127       if hasattr(citem,"item") and citem.item is item:
128         self.selected=citem
129         self.selectObj(self.selected)
130         break
131     self.canvas().update()
132
133   def selectObj(self,obj):
134     if obj:
135       obj._origPen = obj.pen()
136       obj._origBrush = obj.brush()
137       obj._origStyle = obj.brush().style()
138       obj.setPen(self.selectPen)
139       #obj.setBrush(self.selectBrush)
140
141   def deselectObj(self,obj):
142     if obj:
143       obj.setPen(obj._origPen)
144       #obj.setBrush(obj._origBrush)
145
146   def popup(self):
147     menu=QPopupMenu()
148     caption = QLabel( "<font color=darkblue><u><b>View Menu</b></u></font>", self )
149     caption.setAlignment( Qt.AlignCenter )
150     menu.insertItem( caption )
151     menu.insertItem("add Node", self.addNode)
152     return menu
153
154   #def layout(self,rankdir):
155   #  print rankdir
156
157   def updateCanvas(self):
158     #Par defaut, Qt n'efface pas le background. Seul repaintContents
159     #semble le faire. Utile apres un popup ou un resize avec scrollbars
160     #Peut-on l'utiliser partout ? Pb de performance ?
161     self.repaintContents(True)
162     #self.canvas().update()
163
164   def addNode(self):
165     print("addNode")    
166
167   def zoomIn(self):
168     m = self.worldMatrix()
169     m.scale( 2.0, 2.0 )
170     self.setWorldMatrix( m )
171
172   def zoomOut(self):
173     m = self.worldMatrix()
174     m.scale( 0.5, 0.5 )
175     self.setWorldMatrix( m )
176
177   def clear(self):
178     ilist = self.canvas().allItems()
179     for each_item in ilist:
180       if each_item:
181         each_item.setCanvas(None)
182         del each_item
183     self.canvas().update()
184
185   def connecting(self,obj):
186     """Method called by an item to notify canvasView a connection has begun"""
187     print("connecting",obj)
188     self.__connecting=obj
189
190   def contentsMouseMoveEvent(self,e):
191     if  self.__moving :
192       point = self.inverseWorldMatrix().map(e.pos())
193       self.__moving.moveBy(point.x()-self.__moving_start.x(),point.y()-self.__moving_start.y())
194       self.__moving_start = point
195       self.canvas().update()
196     else:
197       #self.tooltip.maybeTip(point)
198       QCanvasView.contentsMouseMoveEvent(self,e)
199
200 class ImageItem(QCanvasRectangle):
201     def __init__(self,img,canvas):
202       QCanvasRectangle.__init__(self,canvas)
203
204       self.imageRTTI=984376
205       self.image=img
206       self.pixmap=QPixmap()
207       self.setSize(self.image.width(), self.image.height())
208       self.pixmap.convertFromImage(self.image, Qt.OrderedAlphaDither);
209
210     def rtti(self):
211       return self.imageRTTI
212
213     def hit(self,p):
214       ix = p.x()-self.x()
215       iy = p.y()-self.y()
216       if not self.image.valid( ix , iy ):
217         return False
218       self.pixel = self.image.pixel( ix, iy )
219       return  (qAlpha( self.pixel ) != 0)
220
221     def drawShape(self,p):
222       p.drawPixmap( self.x(), self.y(), self.pixmap )
223
224 class LinkItem(QCanvasLine):
225   def __init__(self,fromPort, toPort,canvas):
226     QCanvasLine.__init__(self,canvas)
227     self.setPen(QPen(Qt.black))
228     self.setBrush(QBrush(Qt.red))
229     fromPort.addOutLink(self)
230     toPort.addInLink(self)
231     self.setPoints(int(fromPort.x()),int(fromPort.y()), int(toPort.x()), int(toPort.y()))
232     self.setZ(min(fromPort.z(),toPort.z())-1)
233     self.setVisible(True)
234     self.arrow = QCanvasPolygon(self.canvas())
235     self.arrow.setBrush(QBrush(Qt.black))
236     self.setArrow()
237     self.arrow.show()
238
239   def setFromPoint(self,x,y):
240     self.setPoints(x,y,self.endPoint().x(),self.endPoint().y())
241     self.setArrow()
242
243   def setToPoint(self,x,y):
244     self.setPoints(self.startPoint().x(), self.startPoint().y(),x,y)
245     self.setArrow()
246
247   def moveBy(self,dx,dy):
248     pass
249
250   def setArrow(self):
251     x1,y1=self.startPoint().x(),self.startPoint().y()
252     x2,y2=self.endPoint().x(),self.endPoint().y()
253     d=math.hypot(x2-x1,y2-y1)
254     sina=(y2-y1)/d
255     cosa=(x2-x1)/d
256     x=(x1+x2)/2.
257     y=(y1+y2)/2.
258     pa=QPointArray(3)
259     pa.setPoint(0, QPoint(x+10*cosa,y+10*sina))
260     pa.setPoint(1, QPoint(x-5*sina,y+5*cosa))
261     pa.setPoint(2, QPoint(x+5*sina,y-5*cosa))
262     self.arrow.setPoints(pa)
263
264   def popup(self,canvasView):
265     menu=QPopupMenu()
266     caption = QLabel( "<font color=darkblue><u><b>Node Menu</b></u></font>",menu )
267     caption.setAlignment( Qt.AlignCenter )
268     menu.insertItem( caption )
269     menu.insertItem("Delete", self.delete)
270     return menu
271
272   def delete(self):
273     print("delete link")
274
275   def tooltip(self,view,pos):
276     r = QRect(pos.x(), pos.y(), pos.x()+10, pos.y()+10)
277     s = QString( "link: %d,%d" % (r.center().x(), r.center().y()) )
278     view.tip( r, s )
279
280 class PortItem(QCanvasEllipse):
281   def __init__(self,node,canvas):
282     QCanvasEllipse.__init__(self,6,6,canvas)
283     self.setPen(QPen(Qt.black))
284     self.setBrush(QBrush(Qt.red))
285     self.setZ(node.z()+1)
286     self.node=node
287
288   def moveBy(self,dx,dy):
289     self.node.moveBy(dx,dy)
290
291   def myMove(self,dx,dy):
292     QCanvasEllipse.moveBy(self,dx,dy)
293
294   def getObj(self):
295     return self
296
297   def popup(self,canvasView):
298     self.context=canvasView
299     menu=QPopupMenu()
300     caption = QLabel( "<font color=darkblue><u><b>Port Menu</b></u></font>",menu )
301     caption.setAlignment( Qt.AlignCenter )
302     menu.insertItem( caption )
303     menu.insertItem("Connect", self.connect)
304     return menu
305
306   def connect(self):
307     print("connect",self.context)
308     self.context.connecting(self)
309
310   def link(self,obj):
311     print("link:",obj)
312
313   def tooltip(self,view,pos):
314     r = QRect(self.x(), self.y(), self.width(), self.height())
315     s = QString( "port: %d,%d" % (r.center().x(), r.center().y()) )
316     view.tip( r, s )
317
318 class InPortItem(PortItem):
319   def __init__(self,node,canvas):
320     PortItem.__init__(self,node,canvas)
321     self.__inList=[]
322
323   def myMove(self,dx,dy):
324     PortItem.myMove(self,dx,dy)
325     for link in self.__inList:
326       link.setToPoint( int(self.x()), int(self.y()) )
327
328   def link(self,obj):
329     print("link:",obj)
330     if isinstance(obj,OutPortItem):
331       #Connection possible
332       l=LinkItem(obj,self,self.canvas())
333
334   def addInLink(self,link):
335     self.__inList.append(link)
336
337 class OutPortItem(PortItem):
338   def __init__(self,node,canvas):
339     PortItem.__init__(self,node,canvas)
340     self.__outList=[]
341
342   def myMove(self,dx,dy):
343     PortItem.myMove(self,dx,dy)
344     for link in self.__outList:
345       link.setFromPoint( int(self.x()), int(self.y()) )
346
347   def link(self,obj):
348     print("link:",obj)
349     if isinstance(obj,InPortItem):
350       #Connection possible
351       l=LinkItem(self,obj,self.canvas())
352
353   def addOutLink(self,link):
354     self.__outList.append(link)
355
356 class Cell(QCanvasRectangle):
357   def __init__(self,canvas):
358     QCanvasRectangle.__init__(self,canvas)
359     self.setSize(50,50)
360
361     self.inports=[]
362     self.outports=[]
363
364     p=InPortItem(self,canvas)
365     p.myMove(0,25)
366     self.inports.append(p)
367     p=OutPortItem(self,canvas)
368     p.myMove(50,25)
369     self.outports.append(p)
370
371   def moveBy(self,dx,dy):
372     QCanvasRectangle.moveBy(self,dx,dy)
373     for p in self.inports:
374       p.myMove(dx,dy)
375     for p in self.outports:
376       p.myMove(dx,dy)
377
378   def show(self):
379     QCanvasRectangle.show(self)
380     for p in self.inports:
381       p.show()
382     for p in self.outports:
383       p.show()
384
385   def getObj(self):
386     return self
387
388   def popup(self,canvasView):
389     menu=QPopupMenu()
390     caption = QLabel( "<font color=darkblue><u><b>Node Menu</b></u></font>",menu )
391     caption.setAlignment( Qt.AlignCenter )
392     menu.insertItem( caption )
393     menu.insertItem("Browse", self.browse)
394     return menu
395
396   def tooltip(self,view,pos):
397     r = QRect(self.x(), self.y(), self.width(), self.height())
398     s = QString( "node: %d,%d" % (r.center().x(), r.center().y()) )
399     view.tip( r, s )
400
401   def browse(self):
402     print("browse")
403
404   def selected(self):
405     print("selected")
406
407 if __name__=='__main__':
408   app=QApplication(sys.argv)
409   box=QMainWindow()
410   QToolBar(box,"toolbar")
411   canvas=QCanvas(800,600)
412   canvas.setAdvancePeriod(30)
413   m=GraphViewer(canvas,box,"example",0)
414   box.setCentralWidget(m)
415   c1=Cell(canvas)
416   c1.moveBy(150,150)
417   c1.show()
418   c2=Cell(canvas)
419   c2.moveBy(250,150)
420   c2.show()
421   c1.outports[0].link(c2.inports[0])
422
423   qApp.setMainWidget(box)
424   box.show()
425   app.exec_loop()
426
427