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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1775 - (show annotations) (download) (as text)
Wed May 21 09:51:59 2008 UTC (14 years, 4 months ago) by jpye
File MIME type: text/x-python
File size: 10503 byte(s)
Silenced som runtime debug output for datareader and solver.
Added first shot at canvas gui code using Gaphas.
1 from gaphas.item import Item
2 from gaphas.state import observed, reversible_property
3 from gaphas.tool import HandleTool
4
5 class Port(object):
6 """
7 Ports are special places onto which Handles can be connected, specifically
8 when users are drawing 'connector' lines between modelling 'blocks'.
9
10 A subclass of Item called 'Block' will be given the ability to store
11 an array of these Ports, in such a way that gluing methods will be able to
12 select appropriate ports for a given connector line. The Ports also give
13 data about where these available connected are located graphically, in the
14 Block's local coordinate system.
15
16 It is not intended that the number or location of ports would be editable
17 once a port is instantiated. Only the connection of a port to a handle
18 is editable by the user.
19
20 It is intended that subclasses to the Port class would be created to provide
21 finer control over whether or not a certain handle can be permitted to
22 connect to any given port.
23
24 We don't include a 'connected_to' attribute in this class because the
25 Handle objects belonging to the Line can keep track of those connections.
26
27 Attributes:
28 - block: Block to which Port belongs
29 - connectable: whether or not handles can currently be connected to this port
30
31 Private:
32 - _x: port x-location in item's local coordinate system
33 - _y: port y-location in item's local coordinate system
34 - _connectable
35
36 """
37
38 def __init__(self, block, x, y):
39 self.block = block
40 self._connectable = True
41 self._x = x
42 self._y = y
43
44 @observed
45 def _set_connectable(self, connectable):
46 """
47 A port must be set to be unconnectable if it already has
48 something connected to it.
49 """
50 self._connectable = connectable
51
52 connectable = reversible_property(lambda s: s._connectable, _set_connectable)
53
54 def draw(self, context):
55 """
56 Render the item to a canvas view.
57 Context contains the following attributes:
58
59 - cairo: the Cairo Context use this one to draw
60 - view: the view that is to be rendered to
61 - selected, focused, hovered, dropzone: view state of items (True/False)
62 - draw_all: a request to draw everything, for bounding box calculation
63 """
64 pass
65
66 def point(self, x, y):
67 """
68 Get the distance from a point (``x``, ``y``) to the item.
69 ``x`` and ``y`` are in item coordinates.
70
71 Defined here because a port is just a 'point' at this stage.
72 """
73 return math.sqrt((x-self.x)**2 + (y-self.y)**2)
74
75 class Block(Item):
76 """
77 A block is an Item with a list of Ports, plus some special connection logic
78 for when Handles are brought into the area.
79 """
80
81 def __init__(self):
82 super(Block, self).__init__()
83 self._ports = []
84
85 def glue(self, item, handle, x, y):
86 """
87 Special glue method used by the PortConnectingHandleTool to find
88 a connection point. This method looks at all the Ports on this Block
89 and returns the distance to, and a reference to, the closest Port.
90 """
91 pmin = None
92 for p in self._ports:
93 d = p.point(x,y);
94 if pmin is None or d < dmin:
95 dmin = d
96 pmin = p
97 return dmin, p
98
99
100 class PortConnectingHandleTool(HandleTool):
101 """
102 This is a HandleTool which supports a simple connection algorithm,
103 using LineConstraint.
104 """
105
106 def glue(self, view, item, handle, wx, wy):
107 """
108 This allows the tool to glue a handle to any connectable Port of a Block.
109 The distance from the item to the handle is determined in canvas
110 coordinates, using a 10 pixel glue distance.
111
112 Returns the closest Port that is within the glue distance.
113 """
114 if not handle.connectable:
115 return
116
117 # Make glue distance depend on the zoom ratio (should be about 10 pixels)
118 inverse = Matrix(*view.matrix)
119 inverse.invert()
120 #glue_distance, dummy = inverse.transform_distance(10, 0)
121 glue_distance = 10
122 glue_port = None
123 glue_item = None
124 for i in view.canvas.get_all_items():
125 if not i is item:
126 v2i = view.get_matrix_v2i(i).transform_point
127 ix, iy = v2i(wx, wy)
128 try:
129 distance, port = i.glue(item, handle, ix, iy)
130 # Transform distance to world coordinates
131 #distance, dumy = matrix_i2w(i).transform_distance(distance, 0)
132 if distance <= glue_distance:
133 glue_distance = distance
134 i2v = view.get_matrix_i2v(i).transform_point
135 glue_point = i2v(port.x, port.y)
136 glue_port = port
137 except AttributeError:
138 pass
139 if glue_point:
140 v2i = view.get_matrix_v2i(item).transform_point
141 handle.x, handle.y = v2i(*glue_point)
142 return glue_port
143
144 def connect(self, view, item, handle, wx, wy):
145 """
146 Connect a handle to another item.
147
148 In this "method" the following assumptios are made:
149 1. The only item that accepts handle connections are the Box instances
150 2. The only items with connectable handles are ``Line``s
151
152 """
153
154 """
155
156
157 NOW I'M STUCK!
158
159
160 """
161
162
163
164
165 def handle_disconnect():
166 try:
167 view.canvas.solver.remove_constraint(handle._connect_constraint)
168 except KeyError:
169 print 'constraint was already removed for', item, handle
170 pass # constraint was alreasy removed
171 else:
172 print 'constraint removed for', item, handle
173 handle._connect_constraint = None
174 handle.connected_to = None
175 # Remove disconnect handler:
176 handle.disconnect = lambda: 0
177
178 #print 'Handle.connect', view, item, handle, wx, wy
179 glue_item = self.glue(view, item, handle, wx, wy)
180 if glue_item and glue_item is handle.connected_to:
181 try:
182 view.canvas.solver.remove_constraint(handle._connect_constraint)
183 except KeyError:
184 pass # constraint was already removed
185
186 h1, h2 = side(handle, glue_item)
187 handle._connect_constraint = LineConstraint(line=(CanvasProjection(h1.pos, glue_item),
188 CanvasProjection(h2.pos, glue_item)),
189 point=CanvasProjection(handle.pos, item))
190 view.canvas.solver.add_constraint(handle._connect_constraint)
191
192 handle.disconnect = handle_disconnect
193 return
194
195 # drop old connetion
196 if handle.connected_to:
197 handle.disconnect()
198
199 if glue_item:
200 if isinstance(glue_item, Box):
201 h1, h2 = side(handle, glue_item)
202
203 # Make a constraint that keeps into account item coordinates.
204 handle._connect_constraint = \
205 LineConstraint(line=(CanvasProjection(h1.pos, glue_item),
206 CanvasProjection(h2.pos, glue_item)),
207 point=CanvasProjection(handle.pos, item))
208 view.canvas.solver.add_constraint(handle._connect_constraint)
209
210 handle.connected_to = glue_item
211 handle.disconnect = handle_disconnect
212
213 def disconnect(self, view, item, handle):
214 if handle.connected_to:
215 #print 'Handle.disconnect', view, item, handle
216 view.canvas.solver.remove_constraint(handle._connect_constraint)
217
218
219
220 from gaphas.view import View
221 import pygtk
222 pygtk.require('2.0')
223
224 import math
225 import gtk
226
227
228 def create_window(canvas, zoom=1.0):
229 view = View()
230
231 w = gtk.Window()
232 h = gtk.HBox()
233 w.add(h)
234
235 # VBox contains buttons that can be used to manipulate the canvas:
236 v = gtk.VBox()
237 v.set_property('border-width', 3)
238 v.set_property('spacing', 2)
239 f = gtk.Frame()
240 f.set_property('border-width', 1)
241 f.add(v)
242 h.pack_start(f, expand=False)
243
244 v.add(gtk.Label('Item placement:'))
245
246 b = gtk.Button('Add box')
247
248 def on_clicked(button, view):
249 #view.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.CROSSHAIR))
250 view.tool.grab(PlacementTool(MyBox, HandleTool(), 2))
251
252 b.connect('clicked', on_clicked, view)
253 v.add(b)
254
255 b = gtk.Button('Add line')
256
257 def on_clicked(button):
258 view.tool.grab(PlacementTool(MyLine, HandleTool(), 1))
259
260 b.connect('clicked', on_clicked)
261 v.add(b)
262
263 v.add(gtk.Label('Zooming:'))
264
265 b = gtk.Button('Zoom in')
266
267 def on_clicked(button):
268 view.zoom(1.2)
269
270 b.connect('clicked', on_clicked)
271 v.add(b)
272
273 b = gtk.Button('Zoom out')
274
275 def on_clicked(button):
276 view.zoom(1/1.2)
277
278 b.connect('clicked', on_clicked)
279 v.add(b)
280
281 v.add(gtk.Label('Misc:'))
282
283 b = gtk.Button('Split line')
284
285 def on_clicked(button):
286 if isinstance(view.focused_item, Line):
287 view.focused_item.split_segment(0)
288 view.queue_draw_item(view.focused_item, handles=True)
289
290 b.connect('clicked', on_clicked)
291 v.add(b)
292
293 # b = gtk.Button('Cursor')
294 #
295 # def on_clicked(button, li):
296 # c = li[0]
297 # li[0] = (c+2) % 154
298 # button.set_label('Cursor %d' % c)
299 # button.window.set_cursor(gtk.gdk.Cursor(c))
300 #
301 # b.connect('clicked', on_clicked, [0])
302 # v.add(b)
303
304 # Add the actual View:
305
306 t = gtk.Table(2,2)
307 h.add(t)
308
309 w.connect('destroy', gtk.main_quit)
310
311 view.canvas = canvas
312 view.zoom(zoom)
313 view.set_size_request(150, 120)
314 hs = gtk.HScrollbar(view.hadjustment)
315 vs = gtk.VScrollbar(view.vadjustment)
316 t.attach(view, 0, 1, 0, 1)
317 t.attach(hs, 0, 1, 1, 2, xoptions=gtk.FILL, yoptions=gtk.FILL)
318 t.attach(vs, 1, 2, 0, 1, xoptions=gtk.FILL, yoptions=gtk.FILL)
319
320 w.show_all()
321
322 def handle_changed(view, item, what):
323 print what, 'changed: ', item
324
325 view.connect('focus-changed', handle_changed, 'focus')
326 view.connect('hover-changed', handle_changed, 'hover')
327 view.connect('selection-changed', handle_changed, 'selection')
328
329
330
331 if __name__=="__main__":
332 from gaphas.canvas import Canvas
333 canvas = Canvas()
334
335 create_window(canvas)
336
337
338
339 # vim: sw=4:et:ai

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