forked from gaphor/gaphor
-
Notifications
You must be signed in to change notification settings - Fork 0
/
diagramtools.py
329 lines (271 loc) · 10.6 KB
/
diagramtools.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
"""
Tools for handling items on the canvas.
Although Gaphas has quite a few useful tools, some tools need to be extended:
- PlacementTool: should perform undo
- HandleTool: shoudl support adapter based connection protocol
- TextEditTool: should support adapter based edit protocol
"""
import gtk
from cairo import Matrix
from zope import component
from gaphas.geometry import distance_point_point, distance_point_point_fast, \
distance_line_point, distance_rectangle_point
from gaphas.tool import Tool, HandleTool, PlacementTool as _PlacementTool, \
ToolChain, HoverTool, ItemTool, RubberbandTool, ConnectHandleTool
from gaphas.aspect import Connector, ItemConnector
from gaphor.core import inject, Transaction, transactional
from gaphor.diagram.interfaces import IEditor, IConnect
from gaphor.diagram.diagramline import DiagramLine
@Connector.when_type(DiagramLine)
class DiagramItemConnector(ItemConnector):
"""
Handle Tool (acts on item handles) that uses the IConnect protocol
to connect items to one-another.
It also adds handles to lines when a line is grabbed on the middle of
a line segment (points are drawn by the LineSegmentPainter).
"""
def allow(self, sink):
adapter = component.queryMultiAdapter((sink.item, self.item), IConnect)
return adapter and adapter.allow(self.handle, sink.port)
@transactional
def connect(self, sink):
"""
Create connection at handle level and at model level.
"""
handle = self.handle
item = self.item
cinfo = item.canvas.get_connection(handle)
try:
callback = DisconnectHandle(self.item, self.handle)
if cinfo and cinfo.connected is sink.item:
# reconnect only constraint - leave model intact
log.debug('performing reconnect constraint')
constraint = sink.port.constraint(item.canvas, item, handle, sink.item)
item.canvas.reconnect_item(item, handle, constraint=constraint)
else:
if cinfo:
# first disconnect
self.disconnect()
# new connection
self.connect_handle(sink, callback=callback)
# adapter requires both ends to be connected.
self.model_connect(sink, cinfo)
except Exception, e:
log.error('Error during connect', e)
def model_connect(self, sink, cinfo):
"""
Connecting requires the handles to be connected before the model
level connection is made.
Note that once this method is called, the glue() method has done that
for us.
"""
handle = self.handle
item = self.item
#cinfo = item.canvas.get_connection(handle)
adapter = component.queryMultiAdapter((sink.item, item), IConnect)
log.debug('Connect on model level ' + str(adapter))
assert adapter is not None
assert handle in adapter.line.handles()
assert sink.port in adapter.element.ports()
#if cinfo:
# # reconnect handle and model (no disconnect)
# log.debug('performing reconnect handle + model')
# adapter.reconnect(handle, sink.port)
#else:
# just connect, create new elements and stuff like that.
#log.debug('performing connect')
adapter.connect(handle, sink.port)
@transactional
def disconnect(self):
print 'performing disconnect'
super(DiagramItemConnector, self).disconnect()
class DisconnectHandle(object):
"""
Callback for items disconnection using the adapters.
(this is an object so it can be serialized using pickle)
:Variables:
item
Connecting item.
handle
Handle of connecting item.
"""
def __init__(self, item, handle):
self.item = item
self.handle = handle
def __call__(self):
handle = self.handle
item = self.item
canvas = self.item.canvas
cinfo = canvas.get_connection(handle)
if cinfo:
adapter = component.queryMultiAdapter((cinfo.connected, item), IConnect)
adapter.disconnect(handle)
class TextEditTool(Tool):
"""
Text edit tool. Allows for elements that can adapt to the
IEditable interface to be edited.
"""
def create_edit_window(self, x, y, text, *args):
"""
Create a popup window with some editable text.
"""
view = self.view
window = gtk.Window()
window.set_property('decorated', False)
window.set_property('skip-taskbar-hint', True)
window.set_resize_mode(gtk.RESIZE_IMMEDIATE)
#window.set_modal(True)
window.set_parent_window(view.window)
buffer = gtk.TextBuffer()
if text:
buffer.set_text(text)
startiter, enditer = buffer.get_bounds()
buffer.move_mark_by_name('selection_bound', startiter)
buffer.move_mark_by_name('insert', enditer)
text_view = gtk.TextView()
text_view.set_buffer(buffer)
#text_view.set_border_width(2)
text_view.set_left_margin(2)
text_view.set_right_margin(2)
text_view.show()
frame = gtk.Frame()
frame.set_shadow_type(gtk.SHADOW_IN)
#frame.set_border_width(1)
frame.add(text_view)
frame.show()
window.add(frame)
#window.set_border_width(1)
window.size_allocate(gtk.gdk.Rectangle(int(x), int(y), 50, 50))
#window.move(int(x), int(y))
cursor_pos = view.get_toplevel().get_screen().get_display().get_pointer()
window.move(cursor_pos[1], cursor_pos[2])
window.connect('focus-out-event', self._on_focus_out_event,
buffer, *args)
text_view.connect('key-press-event', self._on_key_press_event,
buffer, *args)
#text_view.set_size_request(50, 50)
window.show()
#text_view.grab_focus()
#window.set_uposition(event.x, event.y)
#window.focus
@transactional
def submit_text(self, widget, buffer, editor):
"""
Submit the final text to the edited item.
"""
text = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter())
editor.update_text(text)
widget.get_toplevel().destroy()
def on_double_click(self, event):
view = self.view
item = view.hovered_item
if item:
try:
editor = IEditor(item)
except TypeError:
# Could not adapt to IEditor
return False
x, y = view.get_matrix_v2i(item).transform_point(event.x, event.y)
if editor.is_editable(x, y):
text = editor.get_text()
# get item at cursor
self.create_edit_window(event.x, event.y,
text, editor)
return True
def _on_key_press_event(self, widget, event, buffer, editor):
if event.keyval == gtk.keysyms.Return and \
not event.state & (gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK):
self.submit_text(widget, buffer, editor)
elif event.keyval == gtk.keysyms.Escape:
widget.get_toplevel().destroy()
def _on_focus_out_event(self, widget, event, buffer, editor):
self.submit_text(widget, buffer, editor)
class PlacementTool(_PlacementTool):
"""
PlacementTool is used to place items on the canvas.
"""
def __init__(self, view, item_factory, after_handler=None, handle_index=-1):
"""
item_factory is a callable. It is used to create a CanvasItem
that is displayed on the diagram.
"""
_PlacementTool.__init__(self, view, factory=item_factory,
handle_tool=ConnectHandleTool(),
handle_index=handle_index)
self.after_handler = after_handler
self._tx = None
@transactional
def create_item(self, pos):
return self._create_item(pos)
def on_button_press(self, event):
self._tx = Transaction()
view = self.view
view.unselect_all()
if _PlacementTool.on_button_press(self, event):
try:
opposite = self.new_item.opposite(self.new_item.handles()[self._handle_index])
except (KeyError, AttributeError):
pass
else:
# Connect opposite handle first, using the HandleTool's
# mechanisms
# First make sure all matrices are updated:
view.canvas.update_matrix(self.new_item)
view.update_matrix(self.new_item)
vpos = event.x, event.y
item = self.handle_tool.glue(self.new_item, opposite, vpos)
if item:
self.handle_tool.connect(self.new_item, opposite, vpos)
return True
return False
def on_button_release(self, event):
try:
if self.after_handler:
self.after_handler(self.new_item)
return _PlacementTool.on_button_release(self, event)
finally:
self._tx.commit()
self._tx = None
class TransactionalToolChain(ToolChain):
"""
In addition to a normal toolchain, this chain begins an undo-transaction
at button-press and commits the transaction at button-release.
"""
def __init__(self, view=None):
super(TransactionalToolChain, self).__init__(view)
self._tx = None
def on_button_press(self, event):
self._tx = Transaction()
return ToolChain.on_button_press(self, event)
def on_button_release(self, event):
try:
return ToolChain.on_button_release(self, event)
finally:
if self._tx:
self._tx.commit()
self._tx = None
def on_double_click(self, event):
tx = Transaction()
try:
return ToolChain.on_double_click(self, event)
finally:
tx.commit()
def on_triple_click(self, event):
tx = Transaction()
try:
return ToolChain.on_triple_click(self, event)
finally:
tx.commit()
def DefaultTool():
"""
The default tool chain build from HoverTool, ItemTool and HandleTool.
"""
from gaphor.ui.groupingtools import GroupItemTool
chain = TransactionalToolChain()
chain.append(HoverTool())
chain.append(ConnectHandleTool())
chain.append(GroupItemTool())
chain.append(TextEditTool())
chain.append(RubberbandTool())
return chain
# vim:sw=4:et:ai