-
Notifications
You must be signed in to change notification settings - Fork 2
/
Twins.cp
248 lines (215 loc) · 7.76 KB
/
Twins.cp
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
MODULE ObxTwins;
(**
project = "BlackBox"
organization = "www.oberon.ch"
contributors = "Oberon microsystems"
version = "System/Rsrc/About"
copyright = "System/Rsrc/About"
license = "Docu/BB-License"
changes = ""
issues = ""
**)
IMPORT Ports, Services, Stores, Models, Views, Controllers, Properties, TextViews;
CONST
minVersion = 3; maxVersion = 3; (* old versions 0 .. 2 of ObxTwin views cannot be read anymore *)
border = Ports.mm;
TYPE
Identity = POINTER TO RECORD (Models.Model) END; (* dummy model *)
Context = POINTER TO RECORD (Models.Context)
base: View; (* container view *)
view: Views.View; (* contained view *)
l, t, r, b: INTEGER (* cached bounding box of contained view *)
END;
View = POINTER TO RECORD (Views.View)
top, bottom: Context;
ident: Identity; (* temporary dummy model *)
focus: Context (* current focus; either top or bottom *)
END;
(* Context *)
PROCEDURE (c: Context) ThisModel (): Models.Model;
BEGIN
RETURN NIL (* don't give the embedded views information about the dummy twin model *)
END ThisModel;
PROCEDURE (c: Context) GetSize (OUT w, h: INTEGER);
BEGIN
w := c.r - c.l;
h := c.b - c.t
END GetSize;
PROCEDURE (c: Context) MakeVisible (l, t, r, b: INTEGER);
VAR w, h, sep: INTEGER;
BEGIN
IF c.base.top = c THEN (* top view *)
c.base.context.MakeVisible(l + border, t + border, r + border, b + border)
ELSE (* bottom view *)
c.base.context.GetSize(w, h); sep := h DIV 3;
c.base.context.MakeVisible(l + border, t + sep + border, r + border, b + sep + border)
END
END MakeVisible;
PROCEDURE (c: Context) Consider (VAR p: Models.Proposal);
BEGIN
c.base.context.Consider(p)
END Consider;
PROCEDURE (c: Context) Normalize (): BOOLEAN;
BEGIN
RETURN c.base.context.Normalize()
END Normalize;
PROCEDURE NewContext (v: Views.View; base: View): Context;
VAR c: Context;
BEGIN
NEW(c);
c.view := v; c.base := base;
v.InitContext(c); Stores.Join(v, base);
RETURN c
END NewContext;
PROCEDURE CopyOf (source: Context; shallow: BOOLEAN; base: View): Context;
VAR v: Views.View;
BEGIN
v := Views.CopyOf(source.view, shallow );
RETURN NewContext(v, base)
END CopyOf;
(* View *)
PROCEDURE RecalcLayout (v: View);
VAR w, h, sep: INTEGER; c: Context;
BEGIN
v.context.GetSize(w, h);
sep := h DIV 3; (* separate the two views at 1/3 of the container's height *)
c := v.top; c.l := border; c.t := border; c.r := w - border; c.b := sep - border;
c := v.bottom; c.l := border; c.t := sep + border; c.r := w - border; c.b := h - border
END RecalcLayout;
PROCEDURE SetFocus (v: Views.View; x, y: INTEGER): BOOLEAN;
VAR p: Properties.FocusPref;
BEGIN (* determine whether v should be focused when the mouse is clicked at (x, y) in v *)
p.hotFocus := FALSE;
p.atLocation := TRUE; p.x := x; p.y := y;
p.setFocus := FALSE;
Views.HandlePropMsg(v, p);
RETURN p.setFocus
END SetFocus;
PROCEDURE (v: View) CopyFromModelView (source: Views.View; model: Models.Model);
VAR shallow: BOOLEAN;
BEGIN
WITH source: View DO
shallow := model = source.ident; (* shallow copy if both views share the same model *)
Stores.Join(v, model);
v.top := CopyOf(source.top, shallow, v);
v.bottom := CopyOf(source.bottom, shallow, v);
v.ident := model(Identity);
v.focus := v.bottom
END
END CopyFromModelView;
PROCEDURE (v: View) Internalize (VAR rd: Stores.Reader);
VAR version: INTEGER; h: Views.View;
BEGIN
rd.ReadVersion(minVersion, maxVersion, version);
IF ~rd.cancelled THEN
Views.ReadView(rd, h); v.top := NewContext(h, v);
Views.ReadView(rd, h); v.bottom := NewContext(h, v);
NEW(v.ident); Stores.Join(v, v.ident);
v.focus := v.bottom
END
END Internalize;
PROCEDURE (v: View) Externalize (VAR wr: Stores.Writer);
BEGIN
wr.WriteVersion(maxVersion);
Views.WriteView(wr, v.top.view);
Views.WriteView(wr, v.bottom.view)
END Externalize;
PROCEDURE (v: View) Neutralize;
BEGIN
v.focus.view.Neutralize
END Neutralize;
PROCEDURE (v: View) ThisModel (): Models.Model;
BEGIN
RETURN v.ident
END ThisModel;
PROCEDURE (v: View) Restore (f: Views.Frame; l, t, r, b: INTEGER);
VAR d: INTEGER; c: Context;
BEGIN
RecalcLayout(v);
(* install the subframes for the subviews *)
c := v.top; Views.InstallFrame(f, c.view, c.l, c.t, 0, v.focus = c);
c := v.bottom; Views.InstallFrame(f, c.view, c.l, c.t, 1, v.focus = c);
(* draw frame around the subviews *)
d := 2 * f.dot;
c := v.top;
IF (c.t - d < c.b + d) & (c.l - d < c.r + d) THEN f.DrawRect(c.l - d, c.t - d, c.r + d, c.b + d, f.dot, Ports.black) END;
c := v.bottom;
IF (c.t - d < c.b + d) & (c.l - d < c.r + d) THEN f.DrawRect(c.l - d, c.t - d, c.r + d, c.b + d, f.dot, Ports.black) END
END Restore;
PROCEDURE (v: View) HandleCtrlMsg (f: Views.Frame; VAR msg: Views.CtrlMessage;
VAR focus: Views.View);
VAR g: Views.Frame; newFocus: Context; mMsg: Controllers.MarkMsg;
w, h, sep: INTEGER;
BEGIN
WITH msg: Controllers.CursorMessage DO
v.context.GetSize(w, h);
sep := h DIV 3;
IF msg.y >= sep THEN newFocus := v.bottom ELSE newFocus := v.top END;
focus := newFocus.view;
IF (newFocus # v.focus) & ((msg IS Controllers.TrackMsg) OR (msg IS Controllers.DropMsg)) &
SetFocus(focus, msg.x, msg.y) THEN
(* remove marks in old focus *)
mMsg.show := FALSE; mMsg.focus := TRUE;
g := Views.ThisFrame(f, v.focus.view); IF g # NIL THEN Views.ForwardCtrlMsg(g, mMsg) END;
v.focus := newFocus; (* set new focus *)
(* set marks in new focus *)
mMsg.show := TRUE; mMsg.focus := TRUE;
g := Views.ThisFrame(f, v.focus.view); IF g # NIL THEN Views.ForwardCtrlMsg(g, mMsg) END
END
(* the following scrolling-oriented messages are always sent to bottom view, independent of focus *)
| msg: Controllers.PollSectionMsg DO
g := Views.ThisFrame(f, v.bottom.view); IF g # NIL THEN Views.ForwardCtrlMsg(g, msg) END;
IF msg.vertical & ~msg.done THEN
msg.valid := FALSE; msg.done := TRUE (* disable default-scrolling *)
END
| msg: Controllers.ScrollMsg DO
g := Views.ThisFrame(f, v.bottom.view); IF g # NIL THEN Views.ForwardCtrlMsg(g, msg) END;
IF msg.vertical THEN msg.done := TRUE END
| msg: Controllers.PageMsg DO
focus := v.bottom.view
ELSE (* all other messages are sent to the focus, however *)
focus := v.focus.view
END
(* the assignment to focus signals that the view v wants to forward the message to the
corresponding embedded view *)
END HandleCtrlMsg;
PROCEDURE (v: View) HandlePropMsg (VAR msg: Views.PropMessage);
CONST defW = 80 * Ports.mm; defH = 60 * Ports.mm;
BEGIN
WITH msg: Properties.SizePref DO
IF msg.w = Views.undefined THEN msg.w := defW END;
IF msg.h = Views.undefined THEN msg.h := defH END
| msg: Properties.ResizePref DO
msg.verFitToWin := TRUE (* make view always as high as window *)
ELSE
Views.HandlePropMsg(v.bottom.view, msg)
END
END HandlePropMsg;
PROCEDURE (v: View) ConsiderFocusRequestBy (view: Views.View);
BEGIN
IF view = v.top.view THEN
v.focus := v.top
ELSIF view = v.bottom.view THEN
v.focus := v.bottom
ELSE
HALT(20)
END
END ConsiderFocusRequestBy;
PROCEDURE NewTwin* (top, bottom: Views.View): Views.View;
VAR v: View;
BEGIN
NEW(v);
v.top := NewContext(top, v); v.bottom := NewContext(bottom, v);
NEW(v.ident); Stores.Join(v, v.ident); v.focus := v.bottom;
RETURN v
END NewTwin;
(* example twin view with two embedded text views *)
PROCEDURE New* (): Views.View;
BEGIN
RETURN NewTwin(TextViews.dir.New(NIL), TextViews.dir.New(NIL))
END New;
PROCEDURE Deposit*;
BEGIN
Views.Deposit(New())
END Deposit;
END ObxTwins.