-
Notifications
You must be signed in to change notification settings - Fork 34
/
view.py
258 lines (212 loc) · 9.18 KB
/
view.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
# Copyright (C) 2015-2017 DLR
#
# All rights reserved. This program and the accompanying materials are made
# available under the terms of the Eclipse Public License v1.0 which
# accompanies this distribution, and is available at
# http://www.eclipse.org/legal/epl-v10.html
#
# Contributors:
# Franz Steinmetz <franz.steinmetz@dlr.de>
# Matthias Buettner <matthias.buettner@dlr.de>
# Rico Belder <rico.belder@dlr.de>
# Sebastian Brunner <sebastian.brunner@dlr.de>
from contextlib import contextmanager
from gtkmvc.observer import Observer
from gaphas.view import GtkView
from gaphas.item import Element
from rafcon.gui.mygaphas.painter import BoundingBoxPainter
class ExtendedGtkView(GtkView, Observer):
hovered_handle = None
_selection = None
def __init__(self, graphical_editor_v, selection_m, *args):
GtkView.__init__(self, *args)
Observer.__init__(self, selection_m, True)
self.observe_model(selection_m)
self._selection = selection_m
self.no_focus_change = False
self._bounding_box_painter = BoundingBoxPainter(self)
self.graphical_editor = graphical_editor_v
def get_port_at_point(self, vpos, distance=10, exclude=None, exclude_port_fun=None):
"""
Find item with port closest to specified position.
List of items to be ignored can be specified with `exclude`
parameter.
Tuple is returned
- found item
- closest, connectable port
- closest point on found port (in view coordinates)
:Parameters:
vpos
Position specified in view coordinates.
distance
Max distance from point to a port (default 10)
exclude
Set of items to ignore.
"""
# Method had to be inherited, as the base method has a bug:
# It misses the statement max_dist = d
v2i = self.get_matrix_v2i
vx, vy = vpos
max_dist = distance
port = None
glue_pos = None
item = None
rect = (vx - distance, vy - distance, distance * 2, distance * 2)
items = self.get_items_in_rectangle(rect, reverse=True)
for i in items:
if exclude and i in exclude:
continue
for p in i.ports():
if not p.connectable:
continue
if exclude_port_fun and exclude_port_fun(p):
continue
ix, iy = v2i(i).transform_point(vx, vy)
pg, d = p.glue((ix, iy))
if d > max_dist:
continue
max_dist = d
item = i
port = p
# transform coordinates from connectable item space to view
# space
i2v = self.get_matrix_i2v(i).transform_point
glue_pos = i2v(*pg)
return item, port, glue_pos
def get_item_at_point_exclude(self, pos, selected=True, exclude=None):
"""
Return the topmost item located at ``pos`` (x, y).
Parameters:
- selected: if False returns first non-selected item
- exclude: if specified don't check for these items
"""
items = self._qtree.find_intersect((pos[0], pos[1], 1, 1))
for item in self._canvas.sort(items, reverse=True):
if not selected and item in self.selected_items:
continue # skip selected items
if item in exclude:
continue
v2i = self.get_matrix_v2i(item)
ix, iy = v2i.transform_point(*pos)
if item.point((ix, iy)) < 0.5:
return item
return None
def redraw_complete_screen(self):
self.queue_draw_area(0, 0, self.allocation[2], self.allocation[3])
def get_zoom_factor(self):
"""Returns the current zoom factor of the view
The zoom factor can be read out from the view's matrix. _matrix[0] should be equal _matrix[3]. Index 0 is for
the zoom in x direction, index 3 for the y direction
:return: Current zoom factor
"""
return self._matrix[0]
def pixel_to_cairo(self, pixel):
"""Helper function to convert pixels to cairo units
The conversion is depending on the view. The critical parameter is the current zooming factor. The equation is:
cairo units = pixels / zoom factor
:param float pixel: Number of pixels to convert
:return: Number of cairo units corresponding to given number of pixels
:rtype: float
"""
zoom = self.get_zoom_factor()
return pixel / zoom
def queue_draw_item(self, *items):
"""Extends the base class method to allow Ports to be passed as item
:param items: Items that are to be redrawn
"""
gaphas_items = []
for item in items:
if isinstance(item, Element):
gaphas_items.append(item)
else:
try:
gaphas_items.append(item.parent)
except AttributeError:
pass
super(ExtendedGtkView, self).queue_draw_item(*gaphas_items)
@Observer.observe("selection_changed_signal", signal=True)
def _on_selection_changed_externally(self, selection_m, signal_name, signal_msg):
selected_items = self._get_selected_items()
previously_selected_items = set(self.canvas.get_view_for_model(model) for model in signal_msg.arg.old_selection)
affected_items = selected_items ^ previously_selected_items
self.queue_draw_item(*affected_items)
self.emit('selection-changed', selected_items)
@contextmanager
def _suppress_selection_events(self):
self.relieve_model(self._selection)
try:
yield
finally:
self.observe_model(self._selection)
def select_item(self, items):
""" Select an items. This adds `items` to the set of selected items. """
if not items:
return
elif not hasattr(items, "__iter__"):
items = (items,)
selection_changed = False
with self._suppress_selection_events():
for item in items:
self.queue_draw_item(item)
if item.model not in self._selection:
self._selection.add(item.model)
selection_changed = True
if selection_changed:
self.emit('selection-changed', self._get_selected_items())
def unselect_item(self, item):
""" Unselect an item. """
self.queue_draw_item(item)
if item.model in self._selection:
with self._suppress_selection_events():
self._selection.remove(item.model)
self.emit('selection-changed', self._get_selected_items())
def unselect_all(self):
""" Clearing the selected_item also clears the focused_item. """
items = self._get_selected_items()
with self._suppress_selection_events():
self._selection.clear()
self.queue_draw_item(*items)
self.emit('selection-changed', self._get_selected_items())
def _get_selected_items(self):
""" Return an Item (e.g. StateView) for each model (e.g. StateModel) in the current selection """
return set(self.canvas.get_view_for_model(model) for model in self._selection)
def handle_new_selection(self, items):
""" Determines the selection
The selection is based on the previous selection, the currently pressed keys and the passes newly selected items
:param items: The newly selected item(s)
"""
if items is None:
items = ()
elif not hasattr(items, "__iter__"):
items = (items,)
models = set(item.model for item in items)
self._selection.handle_new_selection(models)
selected_items = property(_get_selected_items, select_item, unselect_all, "Items selected by the view")
@Observer.observe("focus_signal", signal=True)
def _on_focus_changed_externally(self, selection_m, signal_name, signal_msg):
previous_focus = self.canvas.get_view_for_model(signal_msg.arg.old_focus)
current_focus = self.canvas.get_view_for_model(signal_msg.arg.new_focus)
self.queue_draw_item(previous_focus, current_focus)
self.emit('focus-changed', current_focus)
def _get_focused_item(self):
""" Returns the currently focused item """
focused_model = self._selection.focus
if not focused_model:
return None
return self.canvas.get_view_for_model(focused_model)
def _set_focused_item(self, item):
""" Sets the focus to the passed item"""
if not item:
return self._del_focused_item()
if item.model is not self._selection.focus:
self.queue_draw_item(self._focused_item, item)
if self.no_focus_change:
self._selection.set(item.model)
return
self._selection.focus = item.model
self.emit('focus-changed', item)
def _del_focused_item(self):
""" Clears the focus """
del self._selection.focus
focused_item = property(_get_focused_item, _set_focused_item, _del_focused_item,
"The item with focus (receives key events a.o.)")