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