Иван Денисов писал(а):
Я не понимаю, почему вы пишите, что контекст хранится в дочернем виде, когда он хранится в родительском.
в том-то и шутка, что это как раз совершенно не обязательно (хранить в родительском), и живёт он именно в nested. смотрите:
Код:
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;
вот так в ObxTwins создаётся nested view. `v.InitContext()` делает всего лишь: `v.context := context`. и контекст этот, созданый, содержит информацию, которая нужна как раз родителю. и в родителе как раз его можно не кэшировать, а попросить у nested и кастануть во что надо. ObxTwins кэширует, но это чисто для удобства: основное место проживания у созданого контекста именно nested view, код можно переделать так, чтобы `top` и `bottom` были Views.View, и брать контексты из них. вот, собственно, так переделать:
Код:
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: Views.View;
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.context = 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): Views.View;
VAR c: Context;
BEGIN
NEW(c);
c.view := v; c.base := base;
v.InitContext(c); Stores.Join(v, base);
RETURN v
END NewContext;
PROCEDURE CopyOf (source: Views.View; shallow: BOOLEAN; base: View): Views.View;
VAR v: Views.View;
BEGIN
v := Views.CopyOf(source, 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.context(Context); c.l := border; c.t := border; c.r := w - border; c.b := sep - border;
c := v.bottom.context(Context); 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.context(Context)
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.context(Context)
END
END Internalize;
PROCEDURE (v: View) Externalize (VAR wr: Stores.Writer);
BEGIN
wr.WriteVersion(maxVersion);
Views.WriteView(wr, v.top);
Views.WriteView(wr, v.bottom)
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.context(Context); Views.InstallFrame(f, c.view, c.l, c.t, 0, v.focus = c);
c := v.bottom.context(Context); 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.context(Context);
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.context(Context);
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.context(Context) ELSE newFocus := v.top.context(Context) 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); 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); IF g # NIL THEN Views.ForwardCtrlMsg(g, msg) END;
IF msg.vertical THEN msg.done := TRUE END
| msg: Controllers.PageMsg DO
focus := v.bottom
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, msg)
END
END HandlePropMsg;
PROCEDURE (v: View) ConsiderFocusRequestBy (view: Views.View);
BEGIN
IF view = v.top THEN
v.focus := v.top.context(Context)
ELSIF view = v.bottom THEN
v.focus := v.bottom.context(Context)
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.context(Context);
RETURN v
END NewTwin;
вуаля: главный контейнер больше не хранит контексты, а получает их у nested views.
то есть, для кода он доступен (и с точки зрения кода, хранится) именно у nested. но нужен — parent. вот такая вот заковыка, которая может немножко сломать мозг.