1 |
jpye |
1970 |
|
2 |
jpye |
1945 |
from gaphas.constraint import LineConstraint, LessThanConstraint, EqualsConstraint, Constraint, _update, BalanceConstraint |
3 |
jpye |
1970 |
from gaphas.item import Line, SW, NE, NW, SE, Item, Handle |
4 |
jpye |
1945 |
from gaphas.util import * |
5 |
jpye |
1970 |
from gaphas.connector import PointPort, VariablePoint |
6 |
|
|
from gaphas.solver import solvable, WEAK, NORMAL, STRONG, VERY_STRONG |
7 |
|
|
from gaphas.state import observed, reversible_method, reversible_pair, reversible_property, disable_dispatching |
8 |
|
|
from gaphas.geometry import distance_rectangle_point |
9 |
jpye |
1945 |
|
10 |
jpye |
1979 |
from blockport import BlockPort |
11 |
|
|
from blockinstance import PORT_IN, PORT_OUT |
12 |
|
|
|
13 |
jpye |
1970 |
class ElementNoPorts(Item): |
14 |
|
|
""" |
15 |
|
|
This is a copy of the Element class, but without the declaration |
16 |
|
|
of the LinePorts in the __init__ method. It will be proposed to the |
17 |
|
|
Gaphor team that the Element class be modified like this, because |
18 |
|
|
there is quite a lot of useful code aside from the LinePort definition. |
19 |
|
|
""" |
20 |
jpye |
1945 |
|
21 |
jpye |
1970 |
def __init__(self, width=10, height=10): |
22 |
|
|
super(ElementNoPorts, self).__init__() |
23 |
|
|
self._handles = [ h(strength=VERY_STRONG) for h in [Handle]*4 ] |
24 |
|
|
|
25 |
|
|
handles = self._handles |
26 |
|
|
h_nw = handles[NW] |
27 |
|
|
h_ne = handles[NE] |
28 |
|
|
h_sw = handles[SW] |
29 |
|
|
h_se = handles[SE] |
30 |
|
|
|
31 |
|
|
# no element ports by default |
32 |
|
|
self._ports = [] |
33 |
|
|
|
34 |
|
|
# setup constraints |
35 |
|
|
self.constraint(horizontal=(h_nw.pos, h_ne.pos)) |
36 |
|
|
self.constraint(horizontal=(h_se.pos, h_sw.pos)) |
37 |
|
|
self.constraint(vertical=(h_nw.pos, h_sw.pos)) |
38 |
|
|
self.constraint(vertical=(h_se.pos, h_ne.pos)) |
39 |
|
|
|
40 |
|
|
# create minimal size constraints |
41 |
|
|
self._c_min_w = self.constraint(left_of=(h_nw.pos, h_se.pos), delta=10) |
42 |
|
|
self._c_min_h = self.constraint(above=(h_nw.pos, h_se.pos), delta=10) |
43 |
|
|
|
44 |
|
|
# set width/height when minimal size constraints exist |
45 |
|
|
self.width = width |
46 |
|
|
self.height = height |
47 |
|
|
|
48 |
|
|
|
49 |
|
|
def setup_canvas(self): |
50 |
|
|
super(ElementNoPorts, self).setup_canvas() |
51 |
|
|
|
52 |
|
|
# Set width/height explicitly, so the element will maintain it |
53 |
|
|
self.width = self.width |
54 |
|
|
self.height = self.height |
55 |
|
|
|
56 |
|
|
def _set_width(self, width): |
57 |
|
|
""" |
58 |
|
|
>>> b=ElementNoPorts() |
59 |
|
|
>>> b.width = 20 |
60 |
|
|
>>> b.width |
61 |
|
|
20.0 |
62 |
|
|
>>> b._handles[NW].x |
63 |
|
|
Variable(0, 40) |
64 |
|
|
>>> b._handles[SE].x |
65 |
|
|
Variable(20, 40) |
66 |
|
|
""" |
67 |
|
|
if width < self.min_width: |
68 |
|
|
width = self.min_width |
69 |
|
|
h = self._handles |
70 |
|
|
h[SE].x = h[NW].x + width |
71 |
|
|
|
72 |
|
|
|
73 |
|
|
def _get_width(self): |
74 |
|
|
""" |
75 |
|
|
Width of the box, calculated as the distance from the left and |
76 |
|
|
right handle. |
77 |
|
|
""" |
78 |
|
|
h = self._handles |
79 |
|
|
return float(h[SE].x) - float(h[NW].x) |
80 |
|
|
|
81 |
|
|
width = property(_get_width, _set_width) |
82 |
|
|
|
83 |
|
|
def _set_height(self, height): |
84 |
|
|
""" |
85 |
|
|
>>> b=ElementNoPorts() |
86 |
|
|
>>> b.height = 20 |
87 |
|
|
>>> b.height |
88 |
|
|
20.0 |
89 |
|
|
>>> b.height = 2 |
90 |
|
|
>>> b.height |
91 |
|
|
10.0 |
92 |
|
|
>>> b._handles[NW].y |
93 |
|
|
Variable(0, 40) |
94 |
|
|
>>> b._handles[SE].y |
95 |
|
|
Variable(10, 40) |
96 |
|
|
""" |
97 |
|
|
if height < self.min_height: |
98 |
|
|
height = self.min_height |
99 |
|
|
h = self._handles |
100 |
|
|
h[SE].y = h[NW].y + height |
101 |
|
|
|
102 |
|
|
def _get_height(self): |
103 |
|
|
""" |
104 |
|
|
Height. |
105 |
|
|
""" |
106 |
|
|
h = self._handles |
107 |
|
|
return float(h[SE].y) - float(h[NW].y) |
108 |
|
|
|
109 |
|
|
height = property(_get_height, _set_height) |
110 |
|
|
|
111 |
|
|
@observed |
112 |
|
|
def _set_min_width(self, min_width): |
113 |
|
|
""" |
114 |
|
|
Set minimal width. |
115 |
|
|
""" |
116 |
|
|
if min_width < 0: |
117 |
|
|
raise ValueError, 'Minimal width cannot be less than 0' |
118 |
|
|
|
119 |
|
|
self._c_min_w.delta = min_width |
120 |
|
|
if self.canvas: |
121 |
|
|
self.canvas.solver.request_resolve_constraint(self._c_min_w) |
122 |
|
|
|
123 |
|
|
min_width = reversible_property(lambda s: s._c_min_w.delta, _set_min_width) |
124 |
|
|
|
125 |
|
|
@observed |
126 |
|
|
def _set_min_height(self, min_height): |
127 |
|
|
""" |
128 |
|
|
Set minimal height. |
129 |
|
|
""" |
130 |
|
|
if min_height < 0: |
131 |
|
|
raise ValueError, 'Minimal height cannot be less than 0' |
132 |
|
|
|
133 |
|
|
self._c_min_h.delta = min_height |
134 |
|
|
if self.canvas: |
135 |
|
|
self.canvas.solver.request_resolve_constraint(self._c_min_h) |
136 |
|
|
|
137 |
|
|
min_height = reversible_property(lambda s: s._c_min_h.delta, _set_min_height) |
138 |
|
|
|
139 |
|
|
|
140 |
|
|
def point(self, pos): |
141 |
|
|
""" |
142 |
|
|
Distance from the point (x, y) to the item. |
143 |
|
|
""" |
144 |
|
|
h = self._handles |
145 |
|
|
hnw, hse = h[NW], h[SE] |
146 |
|
|
return distance_rectangle_point(map(float, (hnw.x, hnw.y, hse.x, hse.y)), pos) |
147 |
|
|
|
148 |
|
|
|
149 |
|
|
class BlockItem(ElementNoPorts): |
150 |
jpye |
1945 |
""" |
151 |
|
|
This is an ASCEND 'block' in the canvas-based modeller. The block will have |
152 |
|
|
sets of input and output ports to which connector lines can be 'glued'. |
153 |
|
|
The block will also have a corresponding ASCEND MODEL type, and a name |
154 |
|
|
which will be used in ASCEND to refer to this block. Each of the ports will |
155 |
|
|
be special visual elements, but note that these are not 'handles', because |
156 |
|
|
they can not be used to resize/modify the element. |
157 |
|
|
""" |
158 |
|
|
|
159 |
|
|
def __init__(self, width=64, height=64): |
160 |
|
|
super(BlockItem, self).__init__(width, height) |
161 |
|
|
|
162 |
|
|
def draw(self, context): |
163 |
jpye |
1970 |
""" |
164 |
|
|
We want all ports within ASCEND to have a common appearance, so we |
165 |
|
|
implement the drawing of ports here, and allow sub-classes of BlockItem |
166 |
|
|
to implement the drawing of the other parts of the block, including the |
167 |
|
|
outline etc. |
168 |
|
|
|
169 |
|
|
Connected ports will be coloured red, other ports will be pale blue. |
170 |
|
|
""" |
171 |
|
|
c = context.cairo |
172 |
jpye |
1945 |
phalfsize = 3 |
173 |
jpye |
1970 |
for p in self._ports: |
174 |
|
|
if hasattr(p,"point"): |
175 |
|
|
c.rectangle(p.point.x - phalfsize, p.point.y - phalfsize, 2*phalfsize, 2*phalfsize) |
176 |
|
|
#if p.connected_to is None: |
177 |
jpye |
1945 |
c.set_source_rgba(0.8,0.8,1, 0.8) |
178 |
jpye |
1970 |
#else: |
179 |
|
|
# c.set_source_rgba(1,0,0,1) |
180 |
|
|
c.fill_preserve() |
181 |
|
|
c.set_source_rgb(0.8,0.8,0) |
182 |
|
|
c.stroke() |
183 |
jpye |
1945 |
|
184 |
jpye |
1970 |
# removing the 'glue' method now, as ports are now built in to gaphas. |
185 |
jpye |
1945 |
|
186 |
|
|
def pre_update(self,context): |
187 |
|
|
#print "PRE-UPDATE BLOCK" |
188 |
|
|
pass |
189 |
|
|
|
190 |
|
|
class DefaultBlockItem(BlockItem): |
191 |
|
|
""" |
192 |
|
|
This is a 'default block' with a certain number of input and output ports |
193 |
|
|
shown depending on the values sent to __init__. It is drawn as a simple |
194 |
|
|
box with the input ports on the left and the output ports on the right. |
195 |
jpye |
1970 |
|
196 |
|
|
@TODO Not clear yet whether blocks with 'custom' representations should have |
197 |
|
|
as their parent class: this class or BlockItem. |
198 |
jpye |
1945 |
""" |
199 |
|
|
|
200 |
|
|
def __init__(self, blockinstance): |
201 |
|
|
|
202 |
|
|
self.blockinstance = blockinstance |
203 |
|
|
super(DefaultBlockItem, self).__init__(64, 64) |
204 |
|
|
|
205 |
|
|
eq = EqualsConstraint |
206 |
|
|
bal = BalanceConstraint |
207 |
|
|
handles = self._handles |
208 |
|
|
h_nw = handles[NW] |
209 |
|
|
h_ne = handles[NE] |
210 |
|
|
h_sw = handles[SW] |
211 |
|
|
h_se = handles[SE] |
212 |
|
|
|
213 |
jpye |
1979 |
ninputs = len(blockinstance.blocktype.inputs) |
214 |
|
|
noutputs = len(blockinstance.blocktype.outputs) |
215 |
|
|
ii, oi = (0,0) # input and output index counters |
216 |
jpye |
1970 |
_ports = [] |
217 |
jpye |
1979 |
for i in self.blockinstance.ports: |
218 |
|
|
p = BlockPort(blockinstance, i) |
219 |
|
|
if self.blockinstance.ports[i].type is PORT_IN: |
220 |
|
|
self._constraints.append(eq(p.point.x, h_nw.x)) |
221 |
|
|
self._constraints.append(bal(band=(h_nw.y, h_sw.y),v=p.point.y, balance=(0.5 + ii)/ninputs)) |
222 |
|
|
ii += 1 |
223 |
|
|
elif self.blockinstance.ports[i].type is PORT_OUT: |
224 |
|
|
self._constraints.append(eq(p.point.x, h_ne.x)) |
225 |
|
|
self._constraints.append(bal(band=(h_ne.y,h_se.y),v=p.point.y, balance=(0.5 + oi)/noutputs)) |
226 |
|
|
oi += 1 |
227 |
|
|
else: |
228 |
|
|
raise RuntimeError("Unknown port type") |
229 |
jpye |
1970 |
_ports.append(p) |
230 |
jpye |
1945 |
|
231 |
jpye |
1970 |
self._ports = _ports |
232 |
|
|
|
233 |
jpye |
1945 |
def draw(self, context): |
234 |
|
|
# draw the box itself |
235 |
|
|
c = context.cairo |
236 |
|
|
nw = self._handles[NW] |
237 |
|
|
c.rectangle(nw.x, nw.y, self.width, self.height) |
238 |
|
|
if context.hovered: |
239 |
|
|
c.set_source_rgba(.8,.8,1, .8) |
240 |
|
|
else: |
241 |
|
|
c.set_source_rgba(1,1,1, .8) |
242 |
|
|
c.fill_preserve() |
243 |
|
|
c.set_source_rgb(0,0,0.8) |
244 |
|
|
c.stroke() |
245 |
|
|
|
246 |
|
|
text_center(c,self.width/2,self.height/2,self.blockinstance.name) |
247 |
|
|
|
248 |
|
|
# now the draw the ports using the base class |
249 |
|
|
super(DefaultBlockItem, self).draw(context) |
250 |
jpye |
1954 |
|