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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1785 - (show annotations) (download) (as text)
Sun Jun 1 03:41:26 2008 UTC (16 years ago) by jpye
File MIME type: text/x-python
File size: 13777 byte(s)
Removed 'Box' stuff from test code.
Rewrote PointConstraint to ignore the 'var' arg of 'solve_for'.
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 We don't include a 'connected_to' attribute in this class because the
50 Handle objects belonging to the Line can keep track of those connections.
51
52 Attributes:
53 - block: Block to which Port belongs
54 - connectable: whether or not handles can currently be connected to this port
55
56 Private:
57 - _x: port x-location in item's local coordinate system
58 - _y: port y-location in item's local coordinate system
59 - _connectable
60
61 """
62
63 _x = solvable()
64 _y = solvable()
65
66 def __init__(self, block, strength = STRONG):
67 self.block = block
68 self._x.strength = strength
69 self._y.strength = strength
70 self._connectable = True
71
72 @observed
73 def _set_x(self, x):
74 self._x = x
75
76 x = reversible_property(lambda s: s._x, _set_x, bind={'x': lambda self: float(self.x) })
77 disable_dispatching(_set_x)
78
79 @observed
80 def _set_y(self, y):
81 self._y = y
82
83 y = reversible_property(lambda s: s._y, _set_y, bind={'y': lambda self: float(self.y) })
84 disable_dispatching(_set_y)
85
86 @observed
87 def _set_connectable(self, connectable):
88 """
89 A port must be set to be unconnectable if it already has
90 something connected to it.
91 """
92 self._connectable = connectable
93
94 connectable = reversible_property(lambda s: s._connectable, _set_connectable)
95
96 def draw(self, context):
97 """
98 Render the item to a canvas view.
99 Context contains the following attributes:
100
101 - cairo: the Cairo Context use this one to draw
102 - view: the view that is to be rendered to
103 - selected, focused, hovered, dropzone: view state of items (True/False)
104 - draw_all: a request to draw everything, for bounding box calculation
105 """
106 pass
107
108 def point(self, x, y):
109 """
110 Get the distance from a point (``x``, ``y``) to the item.
111 ``x`` and ``y`` are in item coordinates.
112
113 Defined here because a port is just a 'point' at this stage.
114 """
115 return math.sqrt((x-self.x)**2 + (y-self.y)**2)
116
117 @observed
118 def _set_pos(self, pos):
119 """
120 Set handle position (Item coordinates).
121 """
122 self.x, self.y = pos
123
124 pos = property(lambda s: (s.x, s.y), _set_pos)
125
126 class PointConstraint(Constraint):
127 """
128 Ensure that point B is always kept on top of point A
129
130 Attributes:
131 _A: first point, defined by (x,y)
132 _B: second point, defined by (x,y)
133 """
134
135 def __init__(self, A, B):
136 print "A =",A
137 print "A[0] =",A[0].variable()
138 print "A[0].strength =",A[0].variable().strength
139
140 print "B =",B
141 print "B[0] =",B[0].variable()
142 print "B[0].strength =",B[0].variable().strength
143
144 # assert isinstance(p1[0],Variable)
145 # assert isinstance(p1[1],Variable)
146 # assert isinstance(p2[0],Variable)
147 # assert isinstance(p2[1],Variable)
148
149 super(PointConstraint, self).__init__(A[0],A[1],B[0],B[1])
150 self._A = A
151 self._B = B
152
153 def solve_for(self, var=None):
154 print "Solving PointConstraint",self,"for var",var
155
156 _update(self._B[0], self._A[0].value)
157 _update(self._B[1], self._A[1].value)
158
159 class Block(Element):
160 """
161 This is an ASCEND 'block' in the canvas-based modeller. The block will have
162 sets of input and output ports to which connector lines can be 'glued'.
163 The block will also have a corresponding ASCEND MODEL type, and a name
164 which will be used in ASCEND to refer to this block. Each of the ports will
165 be special visual elements, but note that these are not 'handles', because
166 they can not be used to resize/modify the element.
167 """
168
169 def __init__(self, label="unnamed", width=10, height=10):
170
171 self.ports = []
172 self.label = label
173 super(Block, self).__init__(width, height)
174
175 def draw(self, context):
176 #print 'Box.draw', self
177 c = context.cairo
178 nw = self._handles[NW]
179 c.rectangle(nw.x, nw.y, self.width, self.height)
180 if context.hovered:
181 c.set_source_rgba(.8,.8,1, .8)
182 else:
183 c.set_source_rgba(1,1,1, .8)
184 c.fill_preserve()
185 c.set_source_rgb(0,0,0.8)
186 c.stroke()
187
188 phalfsize = 3
189 for p in self.ports:
190 c.rectangle(p.x - phalfsize, p.y - phalfsize, 2*phalfsize, 2*phalfsize)
191 if p.connectable:
192 c.set_source_rgba(0.8,0.8,1, 0.8)
193 else:
194 c.set_source_rgba(1,0,0,1)
195 c.fill_preserve()
196 c.set_source_rgb(0.8,0.8,0)
197 c.stroke()
198
199 def glue(self,item, handle, ix, iy):
200 gluerange = 10
201 mindist = -1;
202 minport = None
203 for p in self.ports:
204 dist = math.sqrt((ix-p.x)**2 + (iy-p.y)**2)
205 if dist < gluerange:
206 if not minport or dist<mindist:
207 mindist = dist
208 minport = p
209 return mindist, minport
210
211 class DefaultBlock(Block):
212 """
213 This is a 'default block' with a certain number of input and output ports
214 shown depending on the values sent to __init__. It is drawn as a simple
215 box with the input ports on the left and the output ports on the right.
216 """
217
218 def __init__(self, label="unnamed", width=10, height=10, inputs=2, outputs=3):
219
220 super(DefaultBlock, self).__init__(label, width, height)
221
222 eq = EqualsConstraint
223 bal = BalanceConstraint
224 handles = self._handles
225 h_nw = handles[NW]
226 h_ne = handles[NE]
227 h_sw = handles[SW]
228 h_se = handles[SE]
229
230 for i in range(inputs):
231 p = Port(self)
232 self.ports.append(p)
233 self._constraints.append(eq(p.x, h_nw.x))
234 self._constraints.append(bal(band=(h_nw.y, h_sw.y),v=p.y, balance=(0.5 + i)/inputs))
235
236 for i in range(outputs):
237 p = Port(self)
238 self.ports.append(p)
239 self._constraints.append(eq(p.x, h_ne.x))
240 self._constraints.append(bal(band=(h_ne.y,h_se.y),v=p.y, balance=(0.5 + i)/outputs))
241
242 def draw(self, context):
243 # draw the box itself
244 c = context.cairo
245 nw = self._handles[NW]
246 c.rectangle(nw.x, nw.y, self.width, self.height)
247 if context.hovered:
248 c.set_source_rgba(.8,.8,1, .8)
249 else:
250 c.set_source_rgba(1,1,1, .8)
251 c.fill_preserve()
252 c.set_source_rgb(0,0,0.8)
253 c.stroke()
254
255 # now the draw the ports using the base class
256 super(DefaultBlock, self).draw(context)
257
258 class PortConnectingHandleTool(HandleTool):
259 """
260 This is a HandleTool which supports the connection of lines to the Ports
261 of Blocks, for the purpose of building up process flow diagrams, control
262 diagrams, etc, for the proposed canvas-based modeller of ASCEND.
263 """
264
265 def glue(self, view, item, handle, wx, wy):
266 """
267 This allows the tool to glue a handle to any connectable Port of a Block.
268 The distance from the item to the handle is determined in canvas
269 coordinates, using a 10 pixel glue distance.
270
271 Returns the closest Port that is within the glue distance.
272 """
273 if not handle.connectable:
274 return
275
276 # Make glue distance depend on the zoom ratio (should be about 10 pixels)
277 inverse = cairo.Matrix(*view.matrix)
278 inverse.invert()
279 #glue_distance, dummy = inverse.transform_distance(10, 0)
280 glue_distance = 10
281 glue_port = None
282 glue_point = None
283 #print "Gluing..."
284 for i in view.canvas.get_all_items():
285 if not hasattr(i,'ports'):
286 continue
287 if not i is item:
288 #print "Trying glue to",i
289 v2i = view.get_matrix_v2i(i).transform_point
290 ix, iy = v2i(wx, wy)
291 distance, port = i.glue(item, handle, ix, iy)
292 # Transform distance to world coordinates
293 #distance, dumy = matrix_i2w(i).transform_distance(distance, 0)
294 if not port is None and distance <= glue_distance:
295 glue_distance = distance
296 i2v = view.get_matrix_i2v(i).transform_point
297 glue_point = i2v(port.x, port.y)
298 glue_port = port
299 else:
300 print "i is item"
301 if glue_point:
302 v2i = view.get_matrix_v2i(item).transform_point
303 handle.x, handle.y = v2i(*glue_point)
304 #print "Found glue point ",handle.x,handle.y
305 return glue_port
306
307 def connect(self, view, item, handle, wx, wy):
308 """
309 Connect a handle to a port. 'item' is the line to which the handle
310 belongs; wx and wy are the location of the cursor, so we run the 'glue'
311 routine to find the desired gluing point, then make the connection to
312 the object which 'glue' returns, which will be a Port object (in the
313 context of this tool).
314
315 In this "method" the following assumptios are made:
316 1. Only ``Port``s of ``Block``s will accept connections from handles.
317 2. The only items with connectable handles are ``Line``s
318
319 """
320
321 # create a special local handle_disconnect function
322 def handle_disconnect():
323 try:
324 view.canvas.solver.remove_constraint(handle.connection_data)
325 except KeyError:
326 print 'constraint was already removed for', item, handle
327 pass # constraint was alreasy removed
328 else:
329 print 'constraint removed for', item, handle
330 handle.connected_to.connectable = True
331 handle.connection_data = None
332 handle.connected_to = None
333
334 # Remove disconnect handler:
335 handle.disconnect = lambda: 0
336
337 #print 'Handle.connect', view, item, handle, wx, wy
338 glue_port = self.glue(view, item, handle, wx, wy)
339
340 if glue_port:
341 if glue_port is handle.connected_to:
342 hand
343
344 if glue_port and glue_port is handle.connected_to:
345 try:
346 view.canvas.solver.remove_constraint(handle.connection_data)
347 except KeyError:
348 pass
349 else:
350 # ie no glue_port found, or the handle connected to something else
351 if handle.connected_to:
352 handle.disconnect()
353
354 if glue_port:
355 if isinstance(glue_port, Port):
356 print "Gluing to port",glue_port
357
358 print "handle.pos =",handle.pos
359 print "glue_port =",glue_port
360 print "glue_port.pos = ",glue_port.pos
361 print "glue_port.block =",glue_port.block
362
363 handle.connection_data = PointConstraint(
364 B=CanvasProjection(handle.pos,item)
365 ,A=CanvasProjection(glue_port.pos, glue_port.block)
366 )
367 view.canvas.solver.add_constraint(handle.connection_data)
368 #glue_port.block._constraints.append(handle.connection_data)
369
370 handle.connected_to = glue_port
371 handle.disconnect = handle_disconnect
372 glue_port.connectable = False
373
374 def disconnect(self, view, item, handle):
375 if handle.connected_to:
376 print 'Handle.disconnect', view, item, handle
377 view.canvas.solver.remove_constraint(handle.connection_data)
378
379
380 def DefaultExampleTool():
381 """
382 The default tool chain build from HoverTool, ItemTool and HandleTool.
383 """
384 chain = ToolChain()
385 chain.append(HoverTool())
386 chain.append(PortConnectingHandleTool())
387 chain.append(ItemTool())
388 chain.append(RubberbandTool())
389 return chain
390
391 # vim: sw=4:et:ai

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