/[ascend]/trunk/pygtk/canvas/port.py
ViewVC logotype

Contents of /trunk/pygtk/canvas/port.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1788 - (show annotations) (download) (as text)
Mon Jun 9 11:42:33 2008 UTC (16 years ago) by jpye
File MIME type: text/x-python
File size: 14388 byte(s)
PortConstraints working now for moving of blocks, but not for resizing left
side of blocks.
1 import pygtk
2 pygtk.require('2.0')
3
4 import math
5 import gtk
6 import cairo
7
8 from gaphas.item import Item
9 from gaphas.state import observed, reversible_property, disable_dispatching
10 from gaphas.tool import HandleTool
11
12 from gaphas import Canvas, GtkView, View
13 from gaphas.item import Line, SW, NE, NW, SE, Element, Handle
14 from gaphas.tool import HoverTool, PlacementTool, HandleTool, ToolChain
15 from gaphas.tool import ItemTool, RubberbandTool
16 from gaphas.geometry import point_on_rectangle, distance_rectangle_point
17 from gaphas.constraint import LineConstraint, LessThanConstraint, EqualsConstraint, Constraint, _update, BalanceConstraint
18 from gaphas.canvas import CanvasProjection
19 from gaphas.solver import Variable, solvable, WEAK, NORMAL, STRONG, VERY_STRONG
20
21 from gaphas.painter import ItemPainter
22 from gaphas import state
23 from gaphas.util import text_extents
24
25 from gaphas import painter
26 #painter.DEBUG_DRAW_BOUNDING_BOX = True
27
28 #------------------------------------------------------------------------------
29
30 class Port(object):
31 """
32 Ports are special places onto which Handles can be connected, specifically
33 when users are drawing 'connector' lines between modelling 'blocks'.
34
35 A subclass of Item called 'Block' will be given the ability to store
36 an array of these Ports, in such a way that gluing methods will be able to
37 select appropriate ports for a given connector line. The Ports also give
38 data about where these available connected are located graphically, in the
39 Block's local coordinate system.
40
41 It is not intended that the number or location of ports would be editable
42 once a port is instantiated. Only the connection of a port to a handle
43 is editable by the user.
44
45 It is intended that subclasses to the Port class would be created to provide
46 finer control over whether or not a certain handle can be permitted to
47 connect to any given port.
48
49 Attributes:
50 - block: Block to which Port belongs
51 - connected_to: Handle to which port is connected, if any
52 - x: port x-location in item's local coordinate system
53 - y: port y-location in item's local coordinate system
54
55 Private:
56 - _x: port x-location in item's local coordinate system
57 - _y: port y-location in item's local coordinate system
58 - _connected_to: Handle to which port is connected, if any
59 """
60
61 _x = solvable()
62 _y = solvable()
63
64 def __init__(self, block, strength = STRONG):
65 self.block = block
66 self._x.strength = strength
67 self._y.strength = strength
68 self._connected_to = None
69
70 @observed
71 def _set_x(self, x):
72 self._x = x
73
74 x = reversible_property(lambda s: s._x, _set_x, bind={'x': lambda self: float(self.x) })
75 disable_dispatching(_set_x)
76
77 @observed
78 def _set_y(self, y):
79 self._y = y
80
81 y = reversible_property(lambda s: s._y, _set_y, bind={'y': lambda self: float(self.y) })
82 disable_dispatching(_set_y)
83
84 @observed
85 def _set_connected_to(self, connected_to):
86 self._connected_to = connected_to
87
88 connected_to = reversible_property(lambda s: s._connected_to,
89 _set_connected_to)
90
91 def draw(self, context):
92 """
93 Render the item to a canvas view.
94 Context contains the following attributes:
95
96 - cairo: the Cairo Context use this one to draw
97 - view: the view that is to be rendered to
98 - selected, focused, hovered, dropzone: view state of items (True/False)
99 - draw_all: a request to draw everything, for bounding box calculation
100 """
101 pass
102
103 def point(self, x, y):
104 """
105 Get the distance from a point (``x``, ``y``) to the item.
106 ``x`` and ``y`` are in item coordinates.
107
108 Defined here because a port is just a 'point' at this stage.
109 """
110 return math.sqrt((x-self.x)**2 + (y-self.y)**2)
111
112 @observed
113 def _set_pos(self, pos):
114 """
115 Set handle position (Item coordinates).
116 """
117 self.x, self.y = pos
118
119 pos = property(lambda s: (s.x, s.y), _set_pos)
120
121 class PointConstraint(Constraint):
122 """
123 Ensure that point B is always kept on top of point A
124
125 Attributes:
126 _A: first point, defined by (x,y)
127 _B: second point, defined by (x,y)
128 """
129
130 def __init__(self, A, B):
131 print "A =",A
132 print "A[0] =",A[0].variable()
133 print "A[0].strength =",A[0].variable().strength
134
135 print "B =",B
136 print "B[0] =",B[0].variable()
137 print "B[0].strength =",B[0].variable().strength
138
139 # assert isinstance(p1[0],Variable)
140 # assert isinstance(p1[1],Variable)
141 # assert isinstance(p2[0],Variable)
142 # assert isinstance(p2[1],Variable)
143
144 super(PointConstraint, self).__init__(A[0],A[1],B[0],B[1])
145 self._A = A
146 self._B = B
147
148 def solve_for(self, var=None):
149 print "Solving PointConstraint",self,"for var",var
150
151 _update(self._B[0], self._A[0].value)
152 _update(self._B[1], self._A[1].value)
153
154 class Block(Element):
155 """
156 This is an ASCEND 'block' in the canvas-based modeller. The block will have
157 sets of input and output ports to which connector lines can be 'glued'.
158 The block will also have a corresponding ASCEND MODEL type, and a name
159 which will be used in ASCEND to refer to this block. Each of the ports will
160 be special visual elements, but note that these are not 'handles', because
161 they can not be used to resize/modify the element.
162 """
163
164 def __init__(self, label="unnamed", width=10, height=10):
165
166 self.ports = []
167 self.label = label
168 super(Block, self).__init__(width, height)
169
170 def draw(self, context):
171 #print 'Box.draw', self
172 c = context.cairo
173 nw = self._handles[NW]
174 c.rectangle(nw.x, nw.y, self.width, self.height)
175 if context.hovered:
176 c.set_source_rgba(.8,.8,1, .8)
177 else:
178 c.set_source_rgba(1,1,1, .8)
179 c.fill_preserve()
180 c.set_source_rgb(0,0,0.8)
181 c.stroke()
182
183 phalfsize = 3
184 for p in self.ports:
185 c.rectangle(p.x - phalfsize, p.y - phalfsize, 2*phalfsize, 2*phalfsize)
186 if p.connected_to is None:
187 c.set_source_rgba(0.8,0.8,1, 0.8)
188 else:
189 c.set_source_rgba(1,0,0,1)
190 c.fill_preserve()
191 c.set_source_rgb(0.8,0.8,0)
192 c.stroke()
193
194 def glue(self,item, handle, ix, iy):
195 gluerange = 10
196 mindist = -1;
197 minport = None
198 for p in self.ports:
199 dist = math.sqrt((ix-p.x)**2 + (iy-p.y)**2)
200 if dist < gluerange:
201 if not minport or dist<mindist:
202 mindist = dist
203 minport = p
204 return mindist, minport
205
206 def pre_update(self,context):
207 print "PRE-UPDATE BLOCK"
208
209 class DefaultBlock(Block):
210 """
211 This is a 'default block' with a certain number of input and output ports
212 shown depending on the values sent to __init__. It is drawn as a simple
213 box with the input ports on the left and the output ports on the right.
214 """
215
216 def __init__(self, label="unnamed", width=10, height=10, inputs=2, outputs=3):
217
218 super(DefaultBlock, self).__init__(label, width, height)
219
220 eq = EqualsConstraint
221 bal = BalanceConstraint
222 handles = self._handles
223 h_nw = handles[NW]
224 h_ne = handles[NE]
225 h_sw = handles[SW]
226 h_se = handles[SE]
227
228 for i in range(inputs):
229 p = Port(self)
230 self.ports.append(p)
231 self._constraints.append(eq(p.x, h_nw.x))
232 self._constraints.append(bal(band=(h_nw.y, h_sw.y),v=p.y, balance=(0.5 + i)/inputs))
233
234 for i in range(outputs):
235 p = Port(self)
236 self.ports.append(p)
237 self._constraints.append(eq(p.x, h_ne.x))
238 self._constraints.append(bal(band=(h_ne.y,h_se.y),v=p.y, balance=(0.5 + i)/outputs))
239
240 def draw(self, context):
241 # draw the box itself
242 c = context.cairo
243 nw = self._handles[NW]
244 c.rectangle(nw.x, nw.y, self.width, self.height)
245 if context.hovered:
246 c.set_source_rgba(.8,.8,1, .8)
247 else:
248 c.set_source_rgba(1,1,1, .8)
249 c.fill_preserve()
250 c.set_source_rgb(0,0,0.8)
251 c.stroke()
252
253 # now the draw the ports using the base class
254 super(DefaultBlock, self).draw(context)
255
256 class PortConnectingHandleTool(HandleTool):
257 """
258 This is a HandleTool which supports the connection of lines to the Ports
259 of Blocks, for the purpose of building up process flow diagrams, control
260 diagrams, etc, for the proposed canvas-based modeller of ASCEND.
261 """
262
263 def glue(self, view, item, handle, wx, wy):
264 """
265 This allows the tool to glue a handle to any connectable Port of a Block.
266 The distance from the item to the handle is determined in canvas
267 coordinates, using a 10 pixel glue distance.
268
269 Returns the closest Port that is within the glue distance.
270 """
271 if not handle.connectable:
272 return
273
274 # Make glue distance depend on the zoom ratio (should be about 10 pixels)
275 inverse = cairo.Matrix(*view.matrix)
276 inverse.invert()
277 #glue_distance, dummy = inverse.transform_distance(10, 0)
278 glue_distance = 10
279 glue_port = None
280 glue_point = None
281 #print "Gluing..."
282 for i in view.canvas.get_all_items():
283 if not hasattr(i,'ports'):
284 continue
285 if not i is item:
286 #print "Trying glue to",i
287 v2i = view.get_matrix_v2i(i).transform_point
288 ix, iy = v2i(wx, wy)
289 distance, port = i.glue(item, handle, ix, iy)
290 # Transform distance to world coordinates
291 #distance, dumy = matrix_i2w(i).transform_distance(distance, 0)
292 if not port is None and distance <= glue_distance:
293 glue_distance = distance
294 i2v = view.get_matrix_i2v(i).transform_point
295 glue_point = i2v(port.x, port.y)
296 glue_port = port
297 else:
298 print "i is item"
299 if glue_point:
300 v2i = view.get_matrix_v2i(item).transform_point
301 handle.x, handle.y = v2i(*glue_point)
302 #print "Found glue point ",handle.x,handle.y
303 return glue_port
304
305 def connect(self, view, item, handle, wx, wy):
306 """
307 Connect a handle to a port. 'item' is the line to which the handle
308 belongs; wx and wy are the location of the cursor, so we run the 'glue'
309 routine to find the desired gluing point, then make the connection to
310 the object which 'glue' returns, which will be a Port object (in the
311 context of this tool).
312
313 In this "method" the following assumptios are made:
314 1. Only ``Port``s of ``Block``s will accept connections from handles.
315 2. The only items with connectable handles are ``Line``s
316
317 """
318
319 # create a special local handle_disconnect function
320 def handle_disconnect():
321 try:
322 view.canvas.solver.remove_constraint(handle.connection_data)
323 except KeyError:
324 print 'constraint was already removed for', item, handle
325 pass # constraint was alreasy removed
326 else:
327 print 'constraint removed for', item, handle
328 handle.connected_port.connected_to = None
329 handle.connection_data = None
330 handle.connection_port = None
331 handle.connected_to = None
332
333 # Remove disconnect handler:
334 handle.disconnect = lambda: 0
335
336 #print 'Handle.connect', view, item, handle, wx, wy
337 glue_port = self.glue(view, item, handle, wx, wy)
338
339 if glue_port and hasattr(handle,'connected_port') and handle.connected_port is glue_port:
340 try:
341 view.canvas.solver.remove_constraint(handle.connection_data)
342 except KeyError:
343 pass
344 else:
345 # ie no glue_port found, or the handle connected to something else
346 if handle.connected_to:
347 handle.disconnect()
348
349 if glue_port:
350 if isinstance(glue_port, Port):
351 print "Gluing to port",glue_port
352
353 print "handle.pos =",handle.pos
354 print "glue_port =",glue_port
355 print "glue_port.pos = ",glue_port.pos
356 print "glue_port.block =",glue_port.block
357
358 handle.connection_data = PointConstraint(
359 B=CanvasProjection(handle.pos,item)
360 ,A=CanvasProjection(glue_port.pos, glue_port.block)
361 )
362 view.canvas.solver.add_constraint(handle.connection_data)
363 #glue_port.block._constraints.append(handle.connection_data)
364
365 handle.connected_to = glue_port.block
366 handle.connected_port = glue_port
367 handle.disconnect = handle_disconnect
368 glue_port.connected_to = handle
369
370 def disconnect(self, view, item, handle):
371 if handle.connected_to:
372 print 'Handle.disconnect', view, item, handle
373 view.canvas.solver.remove_constraint(handle.connection_data)
374
375
376 def DefaultExampleTool():
377 """
378 The default tool chain build from HoverTool, ItemTool and HandleTool.
379 """
380 chain = ToolChain()
381 chain.append(HoverTool())
382 chain.append(PortConnectingHandleTool())
383 chain.append(ItemTool())
384 chain.append(RubberbandTool())
385 return chain
386
387 class BlockCanvas(Canvas):
388 def update_constraints(self, items):
389 """
390 Update constraints. Also variables may be marked as dirty before the
391 constraint solver kicks in.
392 """
393 # request solving of external constraints associated with dirty items
394 request_resolve = self._solver.request_resolve
395 for item in items:
396 if hasattr(item,'ports'):
397 for p in item.ports:
398 request_resolve(p.x)
399 request_resolve(p.y)
400
401 super(BlockCanvas,self).update_constraints(items)
402
403
404
405
406
407 # vim: sw=4:et:ai

john.pye@anu.edu.au
ViewVC Help
Powered by ViewVC 1.1.22