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

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

Parent Directory Parent Directory | Revision Log Revision Log


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

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