Syntax10.Scn.Fnt Syntax10i.Scn.Fnt StampElems Alloc 30 Jan 95 Syntax10b.Scn.Fnt MODULE CaptionEdit; (* Copyright: ww IMPORT Oberon, MenuViewers, Viewers, TextFrames, Texts, Display, Fonts, Input, Modules, Files; CONST (* Model *) remove* = 10; insert* = 11; move* = 12; tofront* = 13; VersionName = "CaptionEdit (ww 15 Nov 94)"; (* Frames *) ML = 2; MM = 1; MR = 0; Space = 5; NoFocus* = 0; PointFocus* = 1; CaretFocus* = 2; (* Editor *) Menu = "System.Close System.Copy System.Grow CaptionEdit.Store "; TYPE (* Model *) Panel* = POINTER TO PanelDesc; Caption* = POINTER TO CaptionDesc; Notifier* = PROCEDURE (panel: Panel; caption: Caption; op, beg, end: LONGINT); CaptionDesc* = RECORD(Texts.TextDesc) host*: Panel; next*: Caption; x*, y*: LONGINT; inserted: BOOLEAN END; PanelDesc* = RECORD first*: Caption; notify*: Notifier END; (* Frames *) CapInfo = POINTER TO CapInfoDesc; CapInfoDesc = RECORD next: CapInfo; text: Caption; x, y, w, h, baseH: INTEGER; ok, marked: BOOLEAN END; Location* = RECORD cap*: Caption; pos*: LONGINT; x*, y*: INTEGER END; Frame* = POINTER TO FrameDesc; FrameDesc* = RECORD(Display.FrameDesc) panel*: Panel; x0*, y0*: LONGINT; hasSel*: BOOLEAN; selTime*: LONGINT; selBeg*, selEnd*: Location; focus*: INTEGER; focusPos*: Location; subFocus*: Display.Frame; grid*: LONGINT; info: CapInfo END; UpdateMsg* = RECORD(Display.FrameMsg) panel*: Panel; cap*: Caption; op*, beg*, end*: LONGINT END; w, wattr: Texts.Writer; framePat: Display.Pattern; PROCEDURE ASSERT(b: BOOLEAN); BEGIN IF ~b THEN HALT(99) END END ASSERT; (* Model *) PROCEDURE Insert*(host: Panel; cap: Caption; x, y: LONGINT); VAR q: Caption; BEGIN ASSERT(~cap.inserted); q := host.first; IF q # NIL THEN WHILE q.next # NIL DO q := q.next END; q.next := cap ELSE host.first := cap END; cap.next := NIL; cap.inserted := TRUE; cap.host := host; cap.x := x; cap.y := y; host.notify(host, cap, insert, 0, 0) END Insert; PROCEDURE Remove*(cap: Caption); VAR q: Caption; host: Panel; BEGIN IF cap.inserted THEN host := cap.host; q := host.first; IF q # cap THEN WHILE q.next # cap DO q := q.next END; q.next := cap.next ELSE host.first := cap.next END; cap.inserted := FALSE; host.notify(host, cap, remove, 0, 0) END END Remove; PROCEDURE Move*(cap: Caption; x, y: LONGINT); VAR host: Panel; BEGIN cap.x := x; cap.y := y; host := cap.host; host.notify(host, cap, move, 0, 0) END Move; PROCEDURE BringToFront*(cap: Caption); VAR q: Caption; host: Panel; BEGIN IF cap.inserted & (cap.next # NIL) THEN host := cap.host; q := host.first; IF q # cap THEN WHILE q.next # cap DO q := q.next END; q.next := cap.next ELSE host.first := cap.next END; WHILE q.next # NIL DO q := q.next END; q.next := cap; cap.next := NIL; host.notify(host, cap, tofront, 0, 0) END END BringToFront; PROCEDURE NotifyPanel*(t: Texts.Text; op: INTEGER; beg, end: LONGINT); VAR c: Caption; p: Panel; BEGIN c := t(Caption); p := c.host; IF c.inserted THEN IF (op = Texts.delete) & (c.len = 0) THEN Remove(c) ELSE p.notify(p, c, op, beg, end) END ELSIF (p # NIL) & (op = Texts.insert) THEN Insert(p, c, c.x, c.y) END END NotifyPanel; PROCEDURE OpenCaption*(cap: Caption; text: Texts.Text; beg, end: LONGINT); VAR buf: Texts.Buffer; BEGIN Texts.Open(cap, ""); cap.notify := NotifyPanel; cap.inserted := FALSE; IF (text # NIL) & (beg < end) THEN NEW(buf); Texts.OpenBuf(buf); Texts.Save(text, beg, end, buf); Texts.Append(cap, buf) END END OpenCaption; PROCEDURE OpenPanel*(p: Panel; notifier: Notifier); BEGIN p.first := NIL; p.notify := notifier END OpenPanel; PROCEDURE LoadPanel*(VAR r: Files.Rider; p: Panel); VAR i: LONGINT; c, prev, anchor: Caption; BEGIN OpenPanel(p, p.notify); NEW(anchor); prev := anchor; Files.ReadLInt(r, i); WHILE i # 0 DO NEW(c); OpenCaption(c, NIL, 0, 0); prev.next := c; prev := c; c.host := p; c.inserted := TRUE; Files.ReadLInt(r, c.x); Files.ReadLInt(r, c.y); Texts.Load(r, c); DEC(i) END; p.first := anchor.next END LoadPanel; PROCEDURE ThisPanel*(f: Files.File; notifier: Notifier): Panel; VAR ch: CHAR; p: Panel; r:Files.Rider; BEGIN NEW(p); OpenPanel(p, notifier); IF f # NIL THEN Files.Set(r, f, 0); Files.Read(r, ch); IF ch = 0F7X THEN Files.Read(r, ch); IF ch = 1X THEN LoadPanel(r, p) END END END; RETURN p END ThisPanel; PROCEDURE StorePanel*(VAR r: Files.Rider; p: Panel); VAR i: LONGINT; c: Caption; BEGIN i := 0; c := p.first; WHILE c # NIL DO INC(i); c := c.next END; Files.WriteLInt(r, i); c := p.first; WHILE c # NIL DO Files.WriteLInt(r, c.x); Files.WriteLInt(r, c.y); Texts.Store(r, c); c := c.next END END StorePanel; PROCEDURE File*(p: Panel; name: ARRAY OF CHAR): Files.File; VAR f: Files.File; r: Files.Rider; BEGIN f := Files.New(name); Files.Set(r, f, 0); Files.Write(r, 0F7X); Files.Write(r, 1X); StorePanel(r, p); RETURN f END File; (* Frames *) (* Measurement *) PROCEDURE MeasureChar(VAR r: Texts.Reader; ch: CHAR; VAR w, minY, maxY: INTEGER); VAR chX, chY, chW, chH, voff: INTEGER; pat: Display.Pattern; msg: TextFrames.DisplayMsg; BEGIN voff := r.fnt.height * r.voff DIV 64; IF r.elem # NIL THEN msg.prepare := TRUE; msg.fnt := r.fnt; msg.col := r.col; msg.pos := Texts.Pos(r) - 1; msg.indent := 0; msg.Y0 := r.fnt.minY; r.elem.handle(r.elem, msg); IF r.elem IS TextFrames.Parc THEN r.elem.W := 0; r.elem.H := 0; msg.Y0 := 0 END; w := SHORT(r.elem.W DIV TextFrames.Unit); minY := voff + msg.Y0; maxY := SHORT(r.elem.H DIV TextFrames.Unit) + minY ELSE Display.GetChar(r.fnt.raster, ch, w, chX, chY, chW, chH, pat); minY := r.fnt.minY + voff; maxY := r.fnt.maxY + voff END END MeasureChar; PROCEDURE GetBoundingBox(t: Texts.Text; beg, end: LONGINT; VAR w, h, baseH: INTEGER); VAR pos: LONGINT; minY, maxY, w1, minY1, maxY1: INTEGER; ch: CHAR; r: Texts.Reader; BEGIN w := 0; minY := 0; maxY := 0; Texts.OpenReader(r, t, 0); Texts.Read(r, ch); pos := 1; WHILE ~r.eot DO MeasureChar(r, ch, w1, minY1, maxY1); IF minY1 < minY THEN minY := minY1 END; IF maxY1 > maxY THEN maxY := maxY1 END; IF (pos > beg) & (pos <= end) THEN w := w + w1 END; Texts.Read(r, ch); INC(pos) END; h := maxY - minY; baseH := -minY END GetBoundingBox; PROCEDURE Width(t: Texts.Text; beg, end: LONGINT): INTEGER; VAR w, h, baseH: INTEGER; BEGIN GetBoundingBox(t, beg, end, w, h, baseH); RETURN w END Width; PROCEDURE Offset(t: Texts.Text; dX: INTEGER): LONGINT; VAR i: LONGINT; w, minY, maxY: INTEGER; ch: CHAR; r: Texts.Reader; BEGIN i := 0; Texts.OpenReader(r, t, 0); WHILE (i < t.len) & (dX > 0) DO Texts.Read(r, ch); INC(i); MeasureChar(r, ch, w, minY, maxY); dX := dX - w END; IF (dX < 0) & (i # 0) THEN DEC(i) END; RETURN i END Offset; (* Caption_Info *) PROCEDURE NewInfo(f: Frame; cap: Caption; w, h, baseH: INTEGER): CapInfo; VAR info: CapInfo; BEGIN NEW(info); info.text := cap; info.x := SHORT(cap.x - f.x0); info.y := SHORT(cap.y - f.y0); info.w := w; info.h := h; info.baseH := baseH; info.ok := FALSE; info.marked := FALSE; RETURN info END NewInfo; PROCEDURE ThisCaption(f: Frame; x, y: INTEGER): CapInfo; VAR c, this: CapInfo; BEGIN x := x - f.X; y := y - (f.Y + f.H); c := f.info; this := NIL; WHILE c # NIL DO IF (c.x <= x) & (x < c.x + c.w) & (c.y <= y) & (y < c.y + c.h) THEN this := c END; c := c.next END; RETURN this END ThisCaption; PROCEDURE SetReader(f: Frame; x, y: INTEGER; VAR r: Texts.Reader; VAR cap: CapInfo); VAR t: Caption; BEGIN cap := ThisCaption(f, x, y); IF cap # NIL THEN t := cap.text; Texts.OpenReader(r, t, Offset(t, x - f.X - cap.x)) END END SetReader; PROCEDURE InfoAbout(f: Frame; cap: Caption): CapInfo; VAR c: CapInfo; BEGIN c := f.info; WHILE (c # NIL) & (c.text # cap) DO c := c.next END; RETURN c END InfoAbout; PROCEDURE InsertInfo(f: Frame; cap: CapInfo); VAR info, p: CapInfo; q, t: Caption; BEGIN info := f.info; p := NIL; q := f.panel.first; t := cap.text; WHILE q # t DO IF (info # NIL) & (q = info.text) THEN p := info; info := info.next END; q := q.next END; IF p # NIL THEN p.next := cap ELSE f.info := cap END; cap.next := info END InsertInfo; PROCEDURE RemoveInfo(f: Frame; cap: CapInfo); VAR p, q: CapInfo; BEGIN p := f.info; IF p = cap THEN f.info := cap.next ELSE q := p.next; WHILE q # cap DO p := q; q := q.next END; p.next := cap.next END END RemoveInfo; (* Overlaps *) PROCEDURE MarkOverlap(x, y: LONGINT; w, h: INTEGER; cap: CapInfo); VAR r, t, cX, cY: LONGINT; BEGIN r := x + w; t := y + h; WHILE cap # NIL DO cX := cap.text.x; cY := cap.text.y; IF (x < cX + cap.w) & (cX < r) & (y < cY + cap.h) & (cY < t) THEN cap.ok := FALSE END; cap := cap.next END END MarkOverlap; PROCEDURE HasOverlap(cap: CapInfo): BOOLEAN; VAR l, r, b, t: INTEGER; BEGIN l := cap.x; r := l + cap.w; b := cap.y; t := b + cap.h; cap := cap.next; WHILE (cap # NIL) & ((cap.x >= r) OR (cap.x + cap.w <= l) OR (cap.y >= t) OR (cap.y + cap.h <= b)) DO cap := cap.next END; RETURN cap # NIL END HasOverlap; (* Subframe handling *) PROCEDURE ThisSubFrame(parent: Frame; x, y: INTEGER): Display.Frame; VAR f: Display.Frame; BEGIN f := parent.dsc; WHILE (f # NIL) & ((x < f.X) OR (x >= f.X + f.W) OR (y < f.Y) OR (y >= f.Y + f.H)) DO f := f.next END; RETURN f END ThisSubFrame; PROCEDURE CloseSubFrames(parent: Frame; x, y, w, h: INTEGER); VAR r, t: INTEGER; f, p: Display.Frame; msg: MenuViewers.ModifyMsg; BEGIN r := x + w; t := y + h; p := parent.dsc; IF p # NIL THEN f := p.next; WHILE f # NIL DO IF (x < f.X + f.W) & (f.X < r) & (y < f.Y + f.H) & (f.Y < t) THEN p.next := f.next; msg.id := MenuViewers.reduce; msg.dY := 0; msg.Y := f.Y; msg.H := 0; f.handle(f, msg) ELSE p := f END; f := f.next END; f := parent.dsc; IF (x < f.X + f.W) & (f.X < r) & (y < f.Y + f.H) & (f.Y < t) THEN parent.dsc := f.next; msg.id := MenuViewers.reduce; msg.dY := 0; msg.Y := f.Y; msg.H := 0; f.handle(f, msg) END END END CloseSubFrames; PROCEDURE ShiftSubFrames(parent: Frame; y, h, dY: INTEGER); VAR t: INTEGER; f: Display.Frame; msg: MenuViewers.ModifyMsg; BEGIN IF dY < 0 THEN CloseSubFrames(parent, parent.X, y + dY, parent.W, -dY) ELSE CloseSubFrames(parent, parent.X, y + h, parent.W, dY) END; f := parent.dsc; t := y + h; WHILE f # NIL DO IF (y < f.Y + f.H) & (f.Y < t) THEN f.Y := f.Y + dY; msg.id := MenuViewers.reduce; msg.dY := 0; msg.Y := f.Y; msg.H := f.H; f.handle(f, msg) END; f := f.next END END ShiftSubFrames; (* Marker drawing *) PROCEDURE FlipCross(clip: Display.Frame; x, y: INTEGER); BEGIN Display.CopyPatternC(clip, Display.white, Display.cross, x - 5, y - 5, Display.invert) END FlipCross; PROCEDURE FlipCaret(clip: Display.Frame; x, y: INTEGER); BEGIN Display.CopyPatternC(clip, Display.white, Display.hook, x, y - 6, Display.invert) END FlipCaret; PROCEDURE InvertRect(clip: Display.Frame; x, y, w, h: INTEGER); VAR pinX, pinY: INTEGER; BEGIN pinX := clip.X; pinY := clip.Y + clip.H + 1; IF w < 0 THEN x := x + w; w := -w END; IF h < 0 THEN y := y + h; h := -h END; Display.ReplPatternC(clip, Display.white, framePat, x, y, w, 1, pinX, pinY, Display.invert); Display.ReplPatternC(clip, Display.white, framePat, x, y + h, w, 1, pinX, pinY, Display.invert); Display.ReplPatternC(clip, Display.white, framePat, x, y, 1, h, pinX, pinY, Display.invert); Display.ReplPatternC(clip, Display.white, framePat, x + w, y, 1, h, pinX, pinY, Display.invert) END InvertRect; (* Mark removing *) PROCEDURE RemoveSelection*(f: Frame); VAR x, y: INTEGER; BEGIN IF f.hasSel THEN f.hasSel := FALSE; x := f.selBeg.x; y := f.selBeg.y; Display.ReplConstC(f, Display.white, x, y, f.selEnd.x - x, f.selEnd.y - y, Display.invert) END END RemoveSelection; PROCEDURE Defocus*(f: Frame); VAR subF: Display.Frame; msg: Oberon.ControlMsg; BEGIN IF f.focus = PointFocus THEN FlipCross(f, f.focusPos.x, f.focusPos.y) ELSIF f.focus = CaretFocus THEN FlipCaret(f, f.focusPos.x, f.focusPos.y) END; f.focus := NoFocus; IF f.subFocus # NIL THEN subF := f.subFocus; msg.id := Oberon.defocus; subF.handle(subF, msg) END END Defocus; PROCEDURE UnmarkCaption*(f: Frame; cap: Caption); VAR info: CapInfo; BEGIN info := InfoAbout(f, cap); IF (info # NIL) & info.marked THEN InvertRect(f, f.X + info.x, f.Y + f.H + info.y, info.w - 1, info.h - 1); info.marked := FALSE END END UnmarkCaption; PROCEDURE UnmarkAllCaptions*(f: Frame); VAR c: CapInfo; BEGIN c := f.info; WHILE c # NIL DO IF c.marked THEN InvertRect(f, f.X + c.x, f.Y + f.H + c.y, c.w - 1, c.h - 1); c.marked := FALSE END; c := c.next END END UnmarkAllCaptions; (* Subfocus *) PROCEDURE PassSubFocus*(f: Frame; cap: Caption; new: Display.Frame); VAR info: CapInfo; old: Display.Frame; r: Texts.Reader; ctrl: Oberon.ControlMsg; msg: TextFrames.FocusMsg; BEGIN old := f.subFocus; IF old # NIL THEN ctrl.id := Oberon.defocus; old.handle(old, ctrl); InvertRect(f, old.X - 1, old.Y - 1, old.W + 2, old.H + 2); f.subFocus := NIL; SetReader(f, old.X, old.Y, r, info); Texts.ReadElem(r); IF r.elem # NIL THEN msg.focus := FALSE; msg.elemFrame := old; msg.frame := f; r.elem.handle(r.elem, msg) END END; IF new # NIL THEN info := InfoAbout(f, cap); IF info # NIL THEN Defocus(f); IF HasOverlap(info) THEN BringToFront(cap) END; IF cap = f.selBeg.cap THEN RemoveSelection(f)END; Texts.OpenReader(r, cap, Offset(cap, new.X - f.X - info.x)); Texts.ReadElem(r); msg.focus := TRUE; msg.elemFrame := new; msg.frame := f; r.elem.handle(r.elem, msg); InvertRect(f, new.X - 1, new.Y - 1, new.W + 2, new.H + 2); f.subFocus := new END END END PassSubFocus; (* Focus *) PROCEDURE SetCaret*(f: Frame; cap: Caption; pos: LONGINT); VAR x, y: INTEGER; info: CapInfo; BEGIN IF pos < 0 THEN pos := 0 ELSIF pos > cap.len THEN pos := cap.len END; IF ~((f.focus = CaretFocus) & (f.focusPos.cap = cap) & (f.focusPos.pos = pos)) THEN info := InfoAbout(f, cap); IF info # NIL THEN Defocus(f); PassSubFocus(f, NIL, NIL); IF HasOverlap(info) THEN BringToFront(cap) END; x := f.X + info.x + Space + Width(cap, 0, pos); y := f.Y + f.H + info.y + info.baseH; IF (f.X <= x) & (x < f.X + f.W) & (f.Y <= y) & (y < f.Y + f.H) THEN Oberon.PassFocus(Viewers.This(f.X, f.Y)); f.focus := CaretFocus; f.focusPos.cap := cap; f.focusPos.pos := pos; f.focusPos.x := x; f.focusPos.y := y; FlipCaret(f, x, y) END END END END SetCaret; PROCEDURE SetFocus*(f: Frame; x, y: INTEGER); BEGIN IF (f.X <= x) & (x < f.X + f.W) & (f.Y <= y) & (y < f.Y + f.H) THEN IF ~((f.focus = PointFocus) & (f.focusPos.x = x) & (f.focusPos.y = y)) THEN PassSubFocus(f, NIL, NIL); Oberon.PassFocus(Viewers.This(f.X, f.Y)); f.focus := PointFocus; f.focusPos.x := x; f.focusPos.y := y; FlipCross(f, x, y) END END END SetFocus; (* Selection *) PROCEDURE SetSelection*(f: Frame; cap: Caption; beg, end: LONGINT); VAR x, y, w, h: INTEGER; info: CapInfo; BEGIN IF beg < 0 THEN beg := 0 END; IF end > cap.len THEN end := cap.len END; IF f.hasSel & (f.selBeg.cap = cap) & (f.selBeg.pos = beg) & (beg < end) THEN IF f.selEnd.pos # end THEN info := InfoAbout(f, cap); x := f.X + info.x + Space + Width(cap, 0, end); w := f.selEnd.x - x; f.selEnd.pos := end; f.selEnd.x := x; IF w < 0 THEN x := x + w; w := -w END; Display.ReplConstC(f, Display.white, x, f.selBeg.y, w, f.selEnd.y - f.selBeg.y, Display.invert) END; f.selTime := Oberon.Time() ELSE RemoveSelection(f); info := InfoAbout(f, cap); IF (info # NIL) & (beg < end) THEN IF HasOverlap(info) THEN BringToFront(cap) END; IF (f.subFocus # NIL) & (info = ThisCaption(f, f.subFocus.X, f.subFocus.Y)) THEN PassSubFocus(f, NIL, NIL) END; x := f.X + info.x + Space + Width(cap, 0, beg); w := Width(cap, beg, end); y := f.Y + f.H + info.y + Space; h := info.h - 2 * Space; f.hasSel := TRUE; f.selTime := Oberon.Time(); f.selBeg.cap := cap; f.selBeg.pos := beg; f.selBeg.x := x; f.selBeg.y := y; f.selEnd.cap := cap; f.selEnd.pos := end; f.selEnd.x := x + w; f.selEnd.y := y + h; Display.ReplConstC(f, Display.white, x, y, w, h, Display.invert) END END END SetSelection; (* marked captions *) PROCEDURE IsMarked*(f: Frame; cap: Caption): BOOLEAN; VAR info: CapInfo; BEGIN info := InfoAbout(f, cap); RETURN (info # NIL) & info.marked END IsMarked; PROCEDURE MarkCaption*(f: Frame; cap: Caption); VAR info: CapInfo; BEGIN info := InfoAbout(f, cap); IF (info # NIL) & ~info.marked THEN InvertRect(f, f.X + info.x, f.Y + f.H + info.y, info.w - 1, info.h - 1); info.marked := TRUE END END MarkCaption; (* Neutralize *) PROCEDURE NeutralizeArea(f: Frame; x, y, w, h: INTEGER); VAR r, t, fx, fy: INTEGER; c: CapInfo; f1: Display.Frame; BEGIN UnmarkAllCaptions(f); r := x + w; t := y + h; IF f.subFocus # NIL THEN f1 := f.subFocus; IF (f.X + x < f1.X + f1.W) & (f1.X < f.X + r) & (f.Y + f.H + y < f1.Y + f1.H) & (f1.Y < f.Y + f.H + t) THEN PassSubFocus(f, NIL, NIL) END END; IF f.focus = PointFocus THEN fx := SHORT(f.focusPos.x - f.X + f.x0); fy := SHORT(f.focusPos.y - f.Y - f.H + f.y0); IF (x <= fx + 5) & (fx - 5 < r) & (y <= fy + 5) & (fy - 5 < t) THEN Defocus(f) END ELSIF f.focus = CaretFocus THEN c := InfoAbout(f, f.focusPos.cap); IF (x < c.x + c.w) & (c.x < r) & (y < c.y + c.h) & (c.y < t) THEN Defocus(f) END END; IF f.hasSel THEN c := InfoAbout(f, f.selBeg.cap); IF (x < c.x + c.w) & (c.x < r) & (y < c.y + c.h) & (c.y < t) THEN RemoveSelection(f) END END END NeutralizeArea; PROCEDURE Neutralize*(f: Frame); BEGIN UnmarkAllCaptions(f); PassSubFocus(f, NIL, NIL); Defocus(f); RemoveSelection(f) END Neutralize; (* Drawing *) PROCEDURE DrawBackground(f: Frame; x, y, w, h: INTEGER); VAR g, r, t, x0: INTEGER; s: LONGINT; BEGIN Display.ReplConstC(f, Display.black, x, y, w, h, Display.replace); g := SHORT(f.grid); IF g > 0 THEN r := x + w; t := y + h; WHILE g < 20 DO g := g * 2 END; s := f.x0 - f.X; x0 := SHORT(((x + s - 1) DIV g + 1) * g - s); s := f.y0 - f.Y - f.H; y := SHORT(((y + s - 1) DIV g + 1) * g - s); WHILE y < t DO x := x0; WHILE x < r DO Display.DotC(f, Display.white, x, y, Display.replace); x := x + g END; y := y + g END END END DrawBackground; PROCEDURE DrawCaption(f: Frame; cap: CapInfo; beg, end: LONGINT); VAR x, y, w, h, baseH, pX, pY, voff, dx, chX, chY, chW, chH: INTEGER; ch: CHAR; t: Caption; pat: Display.Pattern; r: Texts.Reader; msg: TextFrames.DisplayMsg; BEGIN y := f.Y + f.H + cap.y; h := cap.h; baseH := cap.baseH; t := cap.text; pX := f.X; pY := f.Y + f.H; x := f.X + cap.x; w := Width(t, beg, end); IF beg # 0 THEN x := x + Space + Width(t, 0, beg) ELSE w := w + Space END; IF end = t.len THEN w := w + Space END; Oberon.RemoveMarks(x, y, w, h); CloseSubFrames(f, x, y, w, h); MarkOverlap(f.x0 + x - f.X, f.y0 + y - (f.Y + f.H), w, h, cap.next); Display.ReplConstC(f, Display.black, x, y, w, h, Display.replace); Display.ReplPatternC(f, Display.white, framePat, x, y, w, 1, pX, pY, Display.replace); Display.ReplPatternC(f, Display.white, framePat, x, y + h - 1, w, 1, pX, pY, Display.replace); IF beg = 0 THEN x := x; w := w; Display.ReplPatternC(f, Display.white, framePat, x, y, 1, h, pX, pY, Display.replace) END; IF end = t.len THEN w := w; Display.ReplPatternC(f, Display.white, framePat, x + w - 1, y, 1, h, pX, pY, Display.replace); Display.ReplConstC(f, Display.white, x + w - Space, y, Space, Space, Display.replace) END; IF beg = 0 THEN x := x + Space END; Texts.OpenReader(r, t, beg); WHILE (beg < end) & (x < f.X + f.W) DO Texts.Read(r, ch); INC(beg); voff := r.fnt.height * r.voff DIV 64; IF r.elem # NIL THEN IF ~(r.elem IS TextFrames.Parc) THEN msg.prepare := TRUE; msg.fnt := r.fnt; msg.col := r.col; msg.pos := beg - 1; msg.indent := 0; msg.Y0 := r.fnt.minY; chW := SHORT(r.elem.W DIV TextFrames.Unit); chH := SHORT(r.elem.H DIV TextFrames.Unit); r.elem.handle(r.elem, msg); chY := y + baseH + voff; IF (f.X <= x) & (f.X + f.W >= x + chW) & (f.Y <= chY + msg.Y0) & (f.Y + f.H >= chY + msg.Y0 + chH) THEN msg.prepare := FALSE; msg.X0 := x; msg.Y0 := chY + r.fnt.minY; msg.frame := f; msg.elemFrame := NIL; r.elem.handle(r.elem, msg); IF msg.elemFrame # NIL THEN msg.elemFrame.next := f.dsc; f.dsc := msg.elemFrame END; END; x := x + chW END; ELSE Display.GetChar(r.fnt.raster, ch, dx, chX, chY, chW, chH, pat); Display.CopyPatternC(f, r.col, pat, x + chX, y + baseH + voff + chY, Display.replace); x := x + dx END END; cap.ok := TRUE END DrawCaption; PROCEDURE Restore(f: Frame); VAR c: CapInfo; BEGIN c := f.info; WHILE c # NIL DO IF ~c.ok THEN NeutralizeArea(f, c.x, c.y, c.w, c.h); DrawCaption(f, c, 0, c.text.len) END; c := c.next END END Restore; (* View Modification *) PROCEDURE Reduce*(f: Frame; y, h, dy: INTEGER); VAR boarder: INTEGER; c, p: CapInfo; df: Display.Frame; BEGIN Oberon.RemoveMarks(f.X, f.Y, f.W, f.H); Neutralize(f); IF h # 0 THEN IF dy # 0 THEN Display.CopyBlock(f.X, y + dy, f.W, h, f.X, y, Display.replace); ShiftSubFrames(f, y + dy, h, -dy) ELSE CloseSubFrames(f, f.X, f.Y, f.W, f.H - h) END; f.Y := y; f.H := h; boarder := -f.H; c := f.info; p := NIL; WHILE c # NIL DO IF c.y + c.h > boarder THEN IF ~c.ok OR (c.y < boarder) THEN DrawCaption(f, c, 0, c.text.len) END; p := c ELSIF p = NIL THEN f.info := c.next ELSE p.next := c.next END; c := c.next END ELSE CloseSubFrames(f, f.X, f.Y, f.W, f.H); f.Y := y; f.H := h; f.info := NIL END; df := f; df.X := f.X; df.W := f.W; df.Y := f.Y; df.H := f.H END Reduce; PROCEDURE Extend*(f: Frame; y, h, dy: INTEGER); VAR l, r, b, t: LONGINT; cw, ch, baseH, dh: INTEGER; info, p, q: CapInfo; c: Caption; BEGIN Oberon.RemoveMarks(f.X, y, f.W, h); Neutralize(f); IF dy # 0 THEN Display.CopyBlock(f.X, y, f.W, f.H, f.X, y + dy, Display.replace); ShiftSubFrames(f, y, f.H, dy) END; l := f.x0; r := l + f.W; b := f.y0 - h; t := f.y0 - f.H; dh := h - f.H; f.Y := y; f.H := h; DrawBackground(f, f.X, f.Y, f.W, dh); c := f.panel.first; q := f.info; p := NIL; WHILE c # NIL DO IF (q # NIL) & (q.text = c) THEN IF ~q.ok OR (c.y < t) THEN DrawCaption(f, q, 0, c.len) END; p := q; q := q.next ELSIF (c.x < r) & (c.y < t) THEN GetBoundingBox(c, 0, c.len, cw, ch, baseH); cw := cw + 2 * Space; ch := ch + 2 * Space; IF (l < c.x + cw) & (b < c.y + ch) THEN info := NewInfo(f, c, cw, ch, baseH + Space); IF p # NIL THEN p.next := info ELSE f.info := info END; info.next := q; p := info; DrawCaption(f, info, 0, c.len) END END; c := c.next END END Extend; PROCEDURE Scroll*(f: Frame; dx, dy: LONGINT); VAR y, h: INTEGER; BEGIN IF (dx # 0) OR (dy # 0) THEN y := f.Y; h := f.H; Reduce(f, y + h, 0, 0); f.x0 := f.x0 - dx; f.y0 := f.y0 - dy; Extend(f, y, h, 0) END END Scroll; (* Update *) PROCEDURE MarkMenu(f: Frame); VAR ch: CHAR; v: Viewers.Viewer; t: Texts.Text; r: Texts.Reader; BEGIN v := Viewers.This(f.X, f.Y); IF (v IS MenuViewers.Viewer) & (v.dsc # NIL) & (v.dsc IS TextFrames.Frame) THEN t := v.dsc(TextFrames.Frame).text; IF t.len > 0 THEN Texts.OpenReader(r, t, t.len - 1); Texts.Read(r, ch) ELSE ch := 0X END; IF ch # "!" THEN Texts.Write(w, "!"); Texts.Append(t, w.buf) END END END MarkMenu; PROCEDURE ClearScreen(f: Frame; x, y: LONGINT; w, h: INTEGER); VAR x1, y1: INTEGER; BEGIN MarkOverlap(x, y, w, h, f.info); x1 := SHORT(f.X + x - f.x0); y1 := SHORT(f.Y + f.H + y - f.y0); Oberon.RemoveMarks(x1, y1, w, h); CloseSubFrames(f, x1, y1, w, h); DrawBackground(f, x1, y1, w, h) END ClearScreen; PROCEDURE Update*(f: Frame; op: LONGINT; cap: Caption; beg, end: LONGINT); VAR w, h, baseH: INTEGER; info: CapInfo; BEGIN MarkMenu(f); info := InfoAbout(f, cap); IF info # NIL THEN IF op = tofront THEN NeutralizeArea(f, info.x, info.y, info.w, info.h); RemoveInfo(f, info); InsertInfo(f, info); DrawCaption(f, info, 0, cap.len) ELSIF op = move THEN NeutralizeArea(f, info.x, info.y, info.w, info.h); ClearScreen(f, f.x0 + info.x, f.y0 + info.y, info.w, info.h); info.x := SHORT(cap.x - f.x0); info.y := SHORT(cap.y - f.y0); NeutralizeArea(f, info.x, info.y, info.w, info.h); IF (info.x < f.W) & (info.x + info.w > 0) & (info.y < 0) & (info.y + info.h > -f.H) THEN DrawCaption(f, info, 0, cap.len) ELSE RemoveInfo(f, info) END; Restore(f) ELSIF op = remove THEN NeutralizeArea(f, info.x, info.y, info.w, info.h); RemoveInfo(f, info); ClearScreen(f, cap.x, cap.y, info.w, info.h); Restore(f) ELSE NeutralizeArea(f, info.x, info.y, info.w, info.h); GetBoundingBox(cap, 0, cap.len, w, h, baseH); w := w + 2 * Space; h := h + 2 * Space; NeutralizeArea(f, info.x, info.y, w, h); IF w < info.w THEN ClearScreen(f, cap.x + w, cap.y, info.w - w, info.h) END; IF h < info.h THEN ClearScreen(f, cap.x, cap.y + h, info.w, info.h - h) END; IF ((op # Texts.delete) & (op # Texts.insert)) OR (h # info.h) THEN beg := 0 END; info.w := w; info.h := h; info.baseH := baseH + Space; IF (info.x < f.W) & (info.x + info.w > 0) & (info.y < 0) & (info.y + info.h > -f.H) THEN DrawCaption(f, info, beg, cap.len) ELSE RemoveInfo(f, info) END; Restore(f) END ELSIF (op # remove) & (op # Texts.delete) & (cap.x < f.x0 + f.W) & (cap.y < f.y0) THEN GetBoundingBox(cap, 0, cap.len, w, h, baseH); w := w + 2 * Space; h := h + 2 * Space; IF (f.x0 < cap.x + w) & (f.y0 - f.H < cap.y + h) THEN NeutralizeArea(f, SHORT(cap.x), SHORT(cap.y), w, h); info := NewInfo(f, cap, w, h, baseH + Space); InsertInfo(f, info); DrawCaption(f, info, 0, cap.len) END END END Update; (* Commands *) PROCEDURE Call*(f: Frame; cap: Caption; pos: LONGINT; load: BOOLEAN); VAR i, j, res: INTEGER; par: Oberon.ParList; s: Texts.Scanner; BEGIN Texts.OpenScanner(s, cap, pos); Texts.Scan(s); IF s.class = Texts.Name THEN i := 0; WHILE (i < s.len) & (s.s[i] # ".") DO INC(i) END; j := i + 1; WHILE (j < s.len) & (s.s[j] # ".") DO INC(j) END; IF (j = s.len) & (s.s[i] = ".") THEN NEW(par); par.text := cap; par.pos := Texts.Pos(s) - 1; par.frame := f; Oberon.Call(s.s, par, load, res); IF res > 0 THEN Texts.WriteString(w, "Call error: "); Texts.WriteString(w, Modules.importing); IF res = 1 THEN Texts.WriteString(w, " not found"); ELSIF res = 2 THEN Texts.WriteString(w, " not an obj-file"); ELSIF res = 3 THEN Texts.WriteString(w, " imports "); Texts.WriteString(w, Modules.imported); Texts.WriteString(w, " with bad key") ELSIF res = 4 THEN Texts.WriteString(w, " corrupted obj file") ELSIF res = 6 THEN Texts.WriteString(w, " has too many imports") ELSIF res = 7 THEN Texts.WriteString(w, " not enough space") END; Texts.WriteLn(w); Texts.Append(Oberon.Log, w.buf) ELSIF res < 0 THEN INC(i); WHILE i < s.len DO Texts.Write(w, s.s[i]); INC(i) END; Texts.WriteString(w, " not found"); Texts.WriteLn(w); Texts.Append(Oberon.Log, w.buf) END END END END Call; PROCEDURE MarkArea*(f: Frame; x, y, w, h: INTEGER); VAR l, r, b, t: INTEGER; info: CapInfo; BEGIN IF w < 0 THEN x := x + w; w := -w END; IF h < 0 THEN y := y + h; h := -h END; l := x - f.X; r := l + w; b := y - (f.Y + f.H); t := b + h; info := f.info; WHILE info # NIL DO IF (l <= info.x) & (r >= info.x + info.w) & (b <= info.y) & (t >= info.y + info.h) THEN MarkCaption(f, info.text) END; info := info.next END END MarkArea; PROCEDURE RememberMarked(f: Frame; VAR mem: ARRAY OF CapInfo); VAR i: LONGINT; info: CapInfo; BEGIN info := f.info; i := 0; WHILE info # NIL DO IF info.marked THEN mem[i] := info; INC(i) END; info := info.next END; mem[i] := NIL END RememberMarked; PROCEDURE MoveMarked*(f: Frame; dX, dY: INTEGER); VAR i: LONGINT; info: CapInfo; text: Caption; mem: ARRAY 1024 OF CapInfo; BEGIN RememberMarked(f, mem); i := 0; info := mem[0]; WHILE info # NIL DO text := info.text; Move(text, text.x + dX, text.y + dY); INC(i); info := mem[i] END; i := 0; info := mem[0]; WHILE info # NIL DO MarkCaption(f, info.text); INC(i); info := mem[i] END END MoveMarked; PROCEDURE DeleteMarked*(f: Frame); VAR i: LONGINT; info: CapInfo; text: Caption; mem: ARRAY 1024 OF CapInfo; BEGIN RememberMarked(f, mem); i := 0; info := mem[0]; PassSubFocus(f, NIL, NIL); WHILE info # NIL DO text := info.text; Texts.Delete(text, 0, text.len); INC(i); info := mem[i] END; IF i = 1 THEN info := mem[0]; SetFocus(f, f.X + info.x, f.Y + f.H + info.y) END END DeleteMarked; (* Input *) PROCEDURE CallMenuCommand(f: Frame; cap: Caption; beg: LONGINT); VAR ch: CHAR; c: Caption; v: Viewers.Viewer; t: Texts.Text; buf: Texts.Buffer; r: Texts.Reader; BEGIN v := Viewers.This(f.X, f.Y); IF (v IS MenuViewers.Viewer) & (v.dsc # NIL) & (v.dsc IS TextFrames.Frame) THEN t := v.dsc(TextFrames.Frame).text; Texts.OpenReader(r, t, 0); Texts.Read(r, ch); IF ~r.eot THEN NEW(c); IF ch = 22X THEN REPEAT Texts.Read(r, ch) UNTIL r.eot OR (ch = 22X); OpenCaption(c, t, 1, Texts.Pos(r) - 1) ELSE REPEAT Texts.Read(r, ch) UNTIL r.eot OR (ch = "|"); OpenCaption(c, t, 0, Texts.Pos(r) - 1) END; NEW(buf); Texts.OpenBuf(buf); Texts.Save(cap, beg, cap.len, buf); Texts.Append(c, buf); Call(f, c, 0, FALSE) END END END CallMenuCommand; PROCEDURE SetAttributes(VAR w: Texts.Writer; c: Caption; pos: LONGINT; fnt: Fonts.Font; col, voff: SHORTINT); VAR ch: CHAR; r: Texts.Reader; BEGIN Texts.OpenReader(r, c, pos); Texts.Read(r, ch); IF (r.eot OR (ch <= " ")) & (pos > 0) THEN DEC(pos); Texts.OpenReader(r, c, pos); Texts.Read(r, ch) END; IF ~r.eot THEN Texts.SetFont(w, r.fnt); Texts.SetColor(w, r.col); Texts.SetOffset(w, r.voff) ELSE Texts.SetFont(w, fnt); Texts.SetColor(w, col); Texts.SetOffset(w, voff) END END SetAttributes; PROCEDURE Visible(fnt: Fonts.Font; ch: CHAR): BOOLEAN; VAR pat: Display.Pattern; dx, x, y, w, h: INTEGER; BEGIN Display.GetChar(fnt.raster, ch, dx, x, y, w, h, pat); RETURN dx > 0 END Visible; PROCEDURE Consume*(f: Frame; ch: CHAR; fnt: Fonts.Font; col, voff: SHORTINT); VAR pos: LONGINT; c: Caption; info: CapInfo; BEGIN IF f.focus = CaretFocus THEN c := f.focusPos.cap; pos := f.focusPos.pos; IF ch = 0DX THEN (* CR *) SetSelection(f, c, 0, pos) ELSIF ch = 0AX THEN (* LF *) CallMenuCommand(f, c, 0) ELSIF ch = 0ACX THEN (* BRK *) Call(f, c, 0, FALSE); Texts.Delete(c, 0, c.len) ELSIF ch = 0C4X THEN (* <- *) IF pos > 0 THEN SetCaret(f, c, pos - 1) END ELSIF ch = 0C3X THEN (* -> *) IF pos < c.len THEN SetCaret(f, c, pos + 1) END ELSIF ch = 7FX THEN (* DEL *) IF pos # 0 THEN IF c.len # 1 THEN Texts.Delete(c, pos - 1, pos); SetCaret(f, c, pos - 1) ELSE info := InfoAbout(f, c); Texts.Delete(c, pos - 1, pos); SetFocus(f, f.X + info.x, f.Y + f.H + info.y) END END ELSIF (ch = 9X) OR (ch >= " ") THEN SetAttributes(wattr, c, pos, fnt, col, voff); IF Visible(wattr.fnt, ch) THEN Texts.Write(wattr, ch); Texts.Insert(c, pos, wattr.buf); SetCaret(f, c, pos + 1) END END ELSIF (f.focus = PointFocus) & ((ch = 9X) OR ((ch >= " ") & (ch # 7FX))) & Visible(fnt, ch) THEN NEW(c); OpenCaption(c, NIL, 0, 0); Insert(f.panel, c, f.focusPos.x - f.X + f.x0, f.focusPos.y - (f.Y + f.H) + f.y0); SetAttributes(wattr, c, 0, fnt, col, voff); Texts.Write(wattr, ch); Texts.Insert(c, 0, wattr.buf); SetCaret(f, c, 1) END END Consume; PROCEDURE ConsumeElem*(f: Frame; e: Texts.Elem); VAR pos: LONGINT; c: Caption; BEGIN IF f.focus = CaretFocus THEN c := f.focusPos.cap; pos := f.focusPos.pos; SetAttributes(wattr, c, pos, Fonts.Default, Display.white, 0); Texts.WriteElem(wattr, e); Texts.Insert(c, pos, wattr.buf); SetCaret(f, c, pos + 1) ELSIF f.focus = PointFocus THEN NEW(c); OpenCaption(c, NIL, 0, 0); Insert(f.panel, c, f.focusPos.x - f.X + f.x0, f.focusPos.y - (f.Y + f.H) + f.y0); SetAttributes(wattr, c, 0, Fonts.Default, Display.white, 0); Texts.WriteElem(wattr, e); Texts.Insert(c, 0, wattr.buf); SetCaret(f, c, 1) END END ConsumeElem; PROCEDURE CopyOver*(f: Frame; text: Texts.Text; beg, end: LONGINT); VAR pos: LONGINT; buf: Texts.Buffer; c: Caption; BEGIN IF f.focus = CaretFocus THEN c := f.focusPos.cap; pos := f.focusPos.pos; NEW(buf); Texts.OpenBuf(buf); Texts.Save(text, beg, end, buf); Texts.Insert(c, pos, buf); SetCaret(f, c, pos + end - beg) ELSIF f.focus = PointFocus THEN NEW(c); OpenCaption(c, text, beg, end); Insert(f.panel, c, f.x0 + f.focusPos.x - f.X, f.y0 + f.focusPos.y - (f.Y + f.H)); SetCaret(f, c, c.len) END END CopyOver; (* Mouse Tracking *) PROCEDURE AlignToGrid(f: Frame; VAR x, y: INTEGER); VAR g, h, s: LONGINT; BEGIN g := f.grid; h := g DIV 2; IF g > 0 THEN s := f.x0 - f.X; x := SHORT(((x + s + h) DIV g) * g - s); s := f.y0 - f.Y - f.H; y := SHORT(((y + s + h) DIV g) * g - s) END END AlignToGrid; PROCEDURE TrackMouse(f: Frame; VAR x, y: INTEGER; VAR keys, keySum: SET; align: BOOLEAN); VAR keys0: SET; x0, y0: INTEGER; BEGIN keys0 := keys; x0 := x; y0 := y; REPEAT Input.Mouse(keys, x, y); keySum := keySum + keys; Oberon.DrawCursor(Oberon.Mouse, Oberon.Arrow, x, y); IF x < f.X THEN x := f.X ELSIF x >= f.X + f.W THEN x := f.X + f.W END; IF y < f.Y THEN y := f.Y ELSIF y >= f.Y + f.H THEN y := f.Y + f.H END; IF align THEN AlignToGrid(f, x, y) END UNTIL (keys # keys0) OR (x # x0) OR (y # y0) END TrackMouse; PROCEDURE TrackFocus*(f: Frame; VAR x, y: INTEGER; VAR keySum: SET); VAR keys: SET; BEGIN AlignToGrid(f, x, y); REPEAT SetFocus(f, x, y); TrackMouse(f, x, y, keys, keySum, TRUE) UNTIL keys = {} END TrackFocus; PROCEDURE TrackCaption*(f: Frame; cap: Caption; VAR x, y, dx, dy: INTEGER; VAR keySum: SET); VAR keys: SET; x0, y0, x1, y1: INTEGER; info: CapInfo; BEGIN AlignToGrid(f, x, y); x0 := x; y0 := y; info := InfoAbout(f, cap); x1 := f.X + info.x; y1 := f.Y + f.H + info.y; AlignToGrid(f, x1, y1); REPEAT InvertRect(f, x1, y1, info.w - 1, info.h - 1); TrackMouse(f, x, y, keys, keySum, TRUE); InvertRect(f, x1, y1, info.w - 1, info.h - 1); x1 := x1 - x0 + x; x0 := x; y1 := y1 - y0 + y; y0 := y UNTIL keys = {}; dx := x1 - f.X - info.x; dy := y1 - f.Y - f.H - info.y END TrackCaption; PROCEDURE TrackArea*(f: Frame; VAR x, y: INTEGER; VAR keySum: SET); VAR keys: SET; x0, y0, x1, y1: INTEGER; BEGIN x0 := x; y0 := y; x1 := x; y1 := y; REPEAT TrackMouse(f, x, y, keys, keySum, FALSE); IF x # x1 THEN InvertRect(f, x1, y0, x - x1, y1 - y0); x1 := x END; IF y # y1 THEN InvertRect(f, x0, y1, x1 - x0, y - y1); y1 := y END UNTIL keys = {}; InvertRect(f, x, y, x0 - x, y0 - y) END TrackArea; PROCEDURE TrackCaret*(f: Frame; cap: Caption; VAR x, y: INTEGER; VAR keySum: SET); VAR keys: SET; BEGIN REPEAT SetCaret(f, cap, Offset(cap, SHORT(x - f.X + f.x0 - cap.x))); TrackMouse(f, x, y, keys, keySum, FALSE) UNTIL keys = {} END TrackCaret; PROCEDURE LocateWord(f: Frame; cap: Caption; x0: INTEGER; VAR beg: LONGINT; VAR x, w: INTEGER); VAR pos, end: LONGINT; ch: CHAR; r: Texts.Reader; BEGIN pos := Offset(cap, SHORT(x0 - f.X + f.x0 - cap.x - Space)) + 1; beg := 0; end := 0; Texts.OpenReader(r, cap, 0); Texts.Read(r, ch); WHILE ~r.eot & (Texts.Pos(r) <= pos) DO beg := Texts.Pos(r) - 1; IF ~r.eot & (r.elem # NIL) THEN Texts.Read(r, ch) ELSE WHILE ~r.eot & (ch > " ") DO Texts.Read(r, ch) END END; end := Texts.Pos(r) - 1; WHILE ~r.eot & (r.elem = NIL) & (ch <= " ") DO Texts.Read(r, ch) END END; x := SHORT(f.X + cap.x + Space + Width(cap, 0, beg) - f.x0); w := Width(cap, beg, end) END LocateWord; PROCEDURE TrackWord*(f: Frame; cap: Caption; x, y: INTEGER; VAR keySum: SET; VAR beg: LONGINT); CONST H = 2; VAR x0, y0, x1, w0, w1: INTEGER; keys: SET; BEGIN y0 := SHORT(cap.y - f.y0 + f.Y + f.H + Space); LocateWord(f, cap, x, beg, x0, w0); Display.ReplConstC(f, Display.white, x0, y0, w0, H, Display.invert); REPEAT TrackMouse(f, x, y, keys, keySum, FALSE); LocateWord(f, cap, x, beg, x1, w1); IF x0 # x1 THEN Display.ReplConstC(f, Display.white, x0, y0, w0, H, Display.invert); x0 := x1; w0 := w1; Display.ReplConstC(f, Display.white, x0, y0, w0, H, Display.invert); END UNTIL keys = {}; Display.ReplConstC(f, Display.white, x0, y0, w0, H, Display.invert) END TrackWord; PROCEDURE TrackSelection*(f: Frame; cap: Caption; VAR x, y: INTEGER; VAR keySum: SET); VAR keys: SET; beg, end: LONGINT; BEGIN beg := Offset(cap, SHORT(x - f.X + f.x0 - cap.x - Space)); IF f.hasSel & (f.selBeg.cap = cap) THEN IF beg = cap.len THEN beg := f.selBeg.pos ELSIF (f.selBeg.pos = beg) & (f.selEnd.pos = beg + 1) THEN beg := 0 END END; REPEAT end := Offset(cap, SHORT(x - f.X + f.x0 - cap.x - Space)) + 1; IF end <= beg THEN end := beg + 1 END; SetSelection(f, cap, beg, end); TrackMouse(f, x, y, keys, keySum, FALSE) UNTIL keys = {} END TrackSelection; PROCEDURE TouchElem*(f: Frame; cap: Caption; VAR x, y: INTEGER; VAR keySum: SET); VAR pos: LONGINT; l, b, w, minY, maxY: INTEGER; ch: CHAR; info: CapInfo; r: Texts.Reader; msg: TextFrames.TrackMsg; BEGIN pos := Offset(cap, SHORT(x - f.X + f.x0 - cap.x - Space)); Texts.OpenReader(r, cap, pos); Texts.Read(r, ch); IF (r.elem # NIL) & (keySum = {MM}) THEN info := InfoAbout(f, cap); MeasureChar(r, ch, w, minY, maxY); l := f.X + info.x + Space + Width(cap, 0, pos); b := f.Y + f.H + info.y + info.baseH + minY; IF (l <= x) & (x < l + w) & (b <= y) & (y < b + maxY - minY) & (f.X <= l) & (l + w < f.X + f.W) & (f.Y <= b) & (b + maxY - minY < f.Y + f.H) THEN msg.fnt := r.fnt; msg.col := r.col; msg.pos := pos; msg.frame := f; msg.X := x; msg.Y := y; msg.keys := keySum; msg.X0 := l; msg.Y0 := b - minY + r.fnt.height * r.voff DIV 64 + r.fnt.minY; r.elem.handle(r.elem, msg); Input.Mouse(keySum, x, y) END END END TouchElem; PROCEDURE Edit*(f: Frame; x, y: INTEGER; keys: SET); VAR k: SET; beg, end, time, grid: LONGINT; x0, y0, dx, dy: INTEGER; ch: CHAR; c: CapInfo; subF: Display.Frame; text: Texts.Text; r: Texts.Reader; msg: Oberon.CopyOverMsg; BEGIN IF keys # {} THEN c := ThisCaption(f, x, y); x0 := x; y0 := y; IF c = NIL THEN (* on background *) IF keys = {ML} THEN TrackFocus(f, x, y, keys); IF (keys = {ML, MM}) & (f.focus # NoFocus) THEN Oberon.GetSelection(text, beg, end, time); IF time >= 0 THEN CopyOver(f, text, beg, end) END END ELSIF keys = {MM} THEN REPEAT TrackMouse(f, x, y, k, keys, TRUE) UNTIL k = {}; IF keys = {MM} THEN grid := f.grid; IF grid > 0 THEN dx := SHORT(((x - x0 + grid DIV 2) DIV grid) * grid); dy := SHORT(((y - y0 + grid DIV 2) DIV grid) * grid); MoveMarked(f, dx, dy) ELSE MoveMarked(f, x - x0, y - y0) END ELSIF keys = {MM, ML} THEN Scroll(f, f.x0, f.y0) ELSIF keys = {MM, MR} THEN Scroll(f, x - x0, y - y0) END ELSIF keys = {MR} THEN UnmarkAllCaptions(f); TrackArea(f, x, y, keys); IF keys = {MR} THEN MarkArea(f, x, y, x0 - x, y0 - y) ELSIF keys = {MR, ML} THEN MarkArea(f, x, y, x0 - x, y0 - y); DeleteMarked(f) END END ELSIF (x >= c.x + c.w + f.X - Space) & (y < c.y + f.Y + f.H + Space) THEN (* on hot spot *) IF keys = {ML} THEN Oberon.DrawCursor(Oberon.Mouse, Oberon.Arrow, x, y) ELSIF keys = {MM} THEN AlignToGrid(f, x, y); TrackCaption(f, c.text, x, y, dx, dy, keys); IF (keys = {MM}) & ((x # x0) OR (y # y0)) THEN Move(c.text, c.text.x + dx, c.text.y + dy) END ELSIF keys = {MR} THEN InvertRect(f, f.X + c.x, f.Y + f.H + c.y, c.w - 1, c.h - 1); REPEAT TrackMouse(f, x, y, k, keys, FALSE) UNTIL k = {}; InvertRect(f, f.X + c.x, f.Y + f.H + c.y, c.w - 1, c.h - 1); IF keys = {MR} THEN IF IsMarked(f, c.text) THEN UnmarkCaption(f, c.text) ELSE MarkCaption(f, c.text) END ELSIF keys = {MR, MM} THEN msg.text := c.text; msg.beg := 0; msg.end := c.text.len; Oberon.FocusViewer.handle(Oberon.FocusViewer, msg) ELSIF (keys = {MR, ML}) THEN PassSubFocus(f, NIL, NIL); Texts.Delete(c.text, 0, c.text.len); SetFocus(f, f.X + c.x, f.Y + f.H + c.y) END END ELSE (* within caption *) IF HasOverlap(c) THEN BringToFront(c.text) END; subF := ThisSubFrame(f, x, y); IF (subF # NIL) & (keys = {ML}) THEN REPEAT TrackMouse(f, x, y, k, keys, FALSE) UNTIL k = {}; IF keys = {ML} THEN PassSubFocus(f, c.text, subF) END ELSE TouchElem(f, c.text, x, y, keys); IF keys = {ML} THEN TrackCaret(f, c.text, x, y, keys); IF f.focus # NoFocus THEN IF keys = {ML, MM} THEN Oberon.GetSelection(text, beg, end, time); IF time >= 0 THEN CopyOver(f, text, beg, end) END ELSIF keys = {ML, MR} THEN Oberon.GetSelection(text, beg, end, time); IF time >= 0 THEN Texts.OpenReader(r, c.text, f.focusPos.pos); Texts.Read(r, ch); Texts.ChangeLooks(text, beg, end, {0, 1, 2}, r.fnt, r.col, r.voff) END END END ELSIF keys = {MM} THEN TrackWord(f, c.text, x, y, keys, beg); IF ~(MR IN keys) THEN Call(f, c.text, beg, ML IN keys) ELSIF keys = {MM, MR} THEN CallMenuCommand(f, c.text, beg) END ELSIF keys = {MR} THEN TrackSelection(f, c.text, x, y, keys); IF f.hasSel THEN beg := f.selBeg.pos; IF keys = {MR, ML} THEN Texts.Delete(c.text, beg, f.selEnd.pos); IF c.text.len > 0 THEN SetCaret(f, c.text, beg) ELSE SetFocus(f, f.X + c.x, f.Y + f.H + c.y) END ELSIF keys = {MR, MM} THEN msg.text := c.text; msg.beg := beg; msg.end := f.selEnd.pos; Oberon.FocusViewer.handle(Oberon.FocusViewer, msg) END END END END END ELSE Oberon.DrawCursor(Oberon.Mouse, Oberon.Arrow, x, y) END END Edit; (* Frame Data *) PROCEDURE OpenFrame*(f: Frame; handler: Display.Handler; panel: Panel; x0, y0, grid: LONGINT); BEGIN f.handle := handler; f.panel := panel; f.x0 := x0; f.y0 := y0; f.grid := grid; f.focus := NoFocus; f.hasSel := FALSE; f.subFocus := NIL; f.info := NIL; f.selBeg.cap := NIL; f.selEnd.cap := NIL; f.focusPos.cap := NIL END OpenFrame; PROCEDURE Copy*(src, dest: Frame); BEGIN OpenFrame(dest, src.handle, src.panel, src.x0, src.y0, src.grid) END Copy; (* General *) PROCEDURE NotifyElems* (parent: Frame; VAR msg: Display.FrameMsg); VAR f: Display.Frame; BEGIN IF msg IS TextFrames.NotifyMsg THEN msg(TextFrames.NotifyMsg).frame := parent END; f := parent.dsc; WHILE f # NIL DO f.handle(f, msg); f := f.next END END NotifyElems; PROCEDURE Handler*(f: Display.Frame; VAR msg: Display.FrameMsg); VAR self, copy: Frame; subf: Display.Frame; BEGIN self := f(Frame); IF msg IS Oberon.CopyMsg THEN NEW(copy); Copy(self, copy); msg(Oberon.CopyMsg).F := copy ELSIF msg IS Oberon.ControlMsg THEN NotifyElems(self, msg); WITH msg: Oberon.ControlMsg DO IF msg.id = Oberon.neutralize THEN NotifyElems(self, msg); Neutralize(self) ELSIF msg.id = Oberon.defocus THEN Defocus(self) END END ELSIF msg IS Oberon.InputMsg THEN WITH msg: Oberon.InputMsg DO subf := self.subFocus; IF msg.id = Oberon.track THEN IF (subf # NIL) & (subf.X <= msg.X) & (msg.X < subf.X + subf.W) & (subf.Y <= msg.Y) & (msg.Y < subf.Y + subf.H) THEN subf.handle(subf, msg) ELSE Edit(self, msg.X, msg.Y, msg.keys) END ELSIF msg.id = Oberon.consume THEN IF self.focus # NoFocus THEN Consume(self, msg.ch, msg.fnt, msg.col, msg.voff) ELSIF subf # NIL THEN subf.handle(subf, msg) END END END ELSIF msg IS Oberon.CopyOverMsg THEN NotifyElems(self, msg); WITH msg: Oberon.CopyOverMsg DO CopyOver(self, msg.text, msg.beg, msg.end) END ELSIF msg IS Oberon.SelectionMsg THEN NotifyElems(self, msg); WITH msg: Oberon.SelectionMsg DO IF self.hasSel & (msg.time < self.selTime) THEN msg.time := self.selTime; msg.text := self.selBeg.cap; msg.beg := self.selBeg.pos; msg.end := self.selEnd.pos END END ELSIF msg IS TextFrames.InsertElemMsg THEN subf := self.subFocus; IF self.focus # NoFocus THEN ConsumeElem(self, msg(TextFrames.InsertElemMsg).e) ELSIF subf # NIL THEN subf.handle(subf, msg) END ELSIF msg IS MenuViewers.ModifyMsg THEN WITH msg: MenuViewers.ModifyMsg DO IF msg.id = MenuViewers.reduce THEN Reduce(self, msg.Y, msg.H, msg.dY) ELSIF msg.id = MenuViewers.extend THEN Extend(self, msg.Y, msg.H, msg.dY) END END ELSIF msg IS UpdateMsg THEN NotifyElems(self, msg); WITH msg: UpdateMsg DO IF msg.panel = self.panel THEN Update(self, msg.op, msg.cap, msg.beg, msg.end) END END ELSE NotifyElems(self, msg) END END Handler; PROCEDURE NewFrame*(panel: Panel; x0, y0, grid: LONGINT): Frame; VAR f: Frame; BEGIN NEW(f); OpenFrame(f, Handler, panel, x0, y0, grid); RETURN f END NewFrame; PROCEDURE NotifyDisplay*(panel: Panel; caption: Caption; op, beg, end: LONGINT); VAR msg: UpdateMsg; BEGIN msg.panel := panel; msg.cap := caption; msg.op := op; msg.beg := beg; msg.end := end; Viewers.Broadcast(msg) END NotifyDisplay; (* Editor *) PROCEDURE UnmarkMenu(f: Frame); VAR ch: CHAR; t: Texts.Text; v: Viewers.Viewer; r: Texts.Reader; BEGIN v := Viewers.This(f.X, f.Y); IF (v IS MenuViewers.Viewer) & (v.dsc # NIL) & (v.dsc IS TextFrames.Frame) THEN t := v.dsc(TextFrames.Frame).text; IF t.len > 0 THEN Texts.OpenReader(r, t, t.len - 1); Texts.Read(r, ch); IF ch = "!" THEN Texts.Delete(t, t.len - 1, t.len) END END END END UnmarkMenu; PROCEDURE OpenViewer(x, y: INTEGER); VAR i, j, beg, end, time: LONGINT; ch: CHAR; name: ARRAY 66 OF CHAR; p: Panel; v: MenuViewers.Viewer; text: Texts.Text; s: Texts.Scanner; BEGIN Texts.OpenScanner(s, Oberon.Par.text, Oberon.Par.pos); Texts.Scan(s); IF (s.line # 0) OR ((s.class = Texts.Char) & (s.c = "^")) THEN Oberon.GetSelection(text, beg, end, time); IF time >= 0 THEN Texts.OpenScanner(s, text, beg); Texts.Scan(s) END END; IF (s.line = 0) & ((s.class = Texts.Name) OR (s.class = Texts.String)) THEN p := ThisPanel(Files.Old(s.s), NotifyDisplay); IF s.class = Texts.String THEN name[0] := 22X; i := 1 ELSE name[0] := 22X; i := 0 END; j := 0; REPEAT ch := s.s[j]; name[j + i] := ch; INC(j) UNTIL ch = 0X; IF s.class = Texts.String THEN name[j] := 22X; name[j + 1] := 0X END; v := MenuViewers.New( TextFrames.NewMenu(name, Menu), NewFrame(p, 0, 0, 5), TextFrames.menuH, x, y END END OpenViewer; PROCEDURE SysOpen*; VAR x, y: INTEGER; BEGIN Oberon.AllocateSystemViewer(Oberon.Mouse.X, x, y); OpenViewer(x, y) END SysOpen; PROCEDURE Open*; VAR x, y: INTEGER; BEGIN Oberon.AllocateUserViewer(Oberon.Mouse.X, x, y); OpenViewer(x, y) END Open; PROCEDURE Store*; VAR beg, end, time: LONGINT; i: INTEGER; ch: CHAR; frame: Frame; v: Viewers.Viewer; text: Texts.Text; s: Texts.Scanner; f: Files.File; bak: ARRAY 68 OF CHAR; BEGIN frame := NIL; IF (Oberon.Par.vwr IS MenuViewers.Viewer) & (Oberon.Par.vwr.dsc = Oberon.Par.frame) & (Oberon.Par.frame.next IS Frame) THEN frame := Oberon.Par.frame.next(Frame); Texts.OpenScanner(s, Oberon.Par.frame(TextFrames.Frame).text, 0); Texts.Scan(s) ELSE v := Oberon.MarkedViewer(); IF (v IS MenuViewers.Viewer) & (v.dsc.next IS Frame) THEN frame := v.dsc.next(Frame); Texts.OpenScanner(s, Oberon.Par.text, Oberon.Par.pos); Texts.Scan(s); IF (s.line = 0) & (s.class = Texts.Char) & (s.c = "^") THEN Oberon.GetSelection(text, beg, end, time); IF time >= 0 THEN Texts.OpenScanner(s, text, beg); Texts.Scan(s) END ELSIF (s.line # 0) OR ((s.class = Texts.Char) & (s.c = "*")) THEN Texts.OpenScanner(s, v.dsc(TextFrames.Frame).text, 0); Texts.Scan(s) END END END; IF (frame # NIL) & (s.line = 0) & ((s.class = Texts.Name) OR (s.class = Texts.String)) THEN Texts.WriteString(w, "CaptionEdit.Store "); Texts.WriteString(w, s.s); Texts.Write(w, " "); Texts.Append(Oberon.Log, w.buf); f := File(frame.panel, s.s); i := 0; ch := s.s[0]; WHILE ch # 0X DO bak[i] := ch; INC(i); ch := s.s[i] END; bak[i] := "."; INC(i); bak[i] := "B"; INC(i); bak[i] := "a"; INC(i); bak[i] := "k"; INC(i); bak[i] := 0X; Files.Rename(s.s, bak, i); Files.Register(f); Texts.WriteInt(w, Files.Length(f), 0); Texts.WriteLn(w); Texts.Append(Oberon.Log, w.buf); UnmarkMenu(frame) END END Store; PROCEDURE SetGrid*; VAR beg, end, time: LONGINT; h: INTEGER; frame: Frame; v: Viewers.Viewer; text: Texts.Text; s: Texts.Scanner; BEGIN frame := NIL; IF (Oberon.Par.vwr IS MenuViewers.Viewer) & (Oberon.Par.vwr.dsc = Oberon.Par.frame) & (Oberon.Par.frame.next IS Frame) THEN frame := Oberon.Par.frame.next(Frame) ELSE v := Oberon.MarkedViewer(); IF (v IS MenuViewers.Viewer) & (v.dsc.next IS Frame) THEN frame := v.dsc.next(Frame) END END; IF frame # NIL THEN Texts.OpenScanner(s, Oberon.Par.text, Oberon.Par.pos); Texts.Scan(s); IF (s.line # 0) OR ((s.class = Texts.Char) & (s.c = "^")) THEN Oberon.GetSelection(text, beg, end, time); IF time >= 0 THEN Texts.OpenScanner(s, text, beg); Texts.Scan(s) END END; IF (s.line = 0) & (s.class = Texts.Int) & (s.i >= 0) THEN frame.grid := s.i; h := frame.H; Reduce(frame, frame.Y, 0, 0); Extend(frame, frame.Y, h, 0) END END END SetGrid; BEGIN framePat := Display.grey1; Texts.OpenWriter(w); Texts.OpenWriter(wattr); Texts.WriteString(w, VersionName); Texts.WriteLn(w); Texts.Append(Oberon.Log, w.buf) END CaptionEdit.