(*
  .name       Windows
  .task       window-management
  .release    1.0
  .language   Oberon-2
  .translator Amiga Oberon 3.11
  .system     AmigaOS 2.04/2.1/3.0
  .author     Joachim Barheine
  .address    Hochgrevestraße 3, D-38640 Goslar
  .copyright  (c) 1994 by Joachim Barheine
*)

(* .info: 07/03/95, 15:25:26, version 218 *)

MODULE Windows;

IMPORT
  SYS:= SYSTEM,

  ASCII,
  DiskFont,
  Err:= ErrCodes,
  Exec,
  GT:= GadTools,
  Gfx:= Graphics,
  GUI,
  I:= Intuition,
  IE:= InputEvent,
  IO:= IOServer,
  K:= Kernel,
  M:= Macro,
  S:= Strings,
  Sett:= Settings,
  Str:= StrPool,
  T:= Texts,
  Util:= Utility;

CONST
  idcmpFlags= LONGSET{I.closeWindow, I.rawKey, I.menuPick,
                      I.newSize, I.refreshWindow, I.mouseButtons, I.mouseMove,
                      I.gadgetUp, I.gadgetDown, I.activeWindow,
                      I.inactiveWindow, I.intuiTicks};
  zoom= I.IBox(30, 20, 200, 50);

  (* gadget IDs *)
  leftID= 0;
  rightID= 1;
  upID= 2;
  downID= 3;
  horizID= 4;
  vertID= 5;

  (* WindowDesc.flags values (status-flags) *)
  markDef*    = 0;  (* mark exists *)
  vertMark*   = 1;  (* mark is vertical *)
  selected*   = 2;  (* (last) active window *)
  keepTitle   = 3;  (* displaying temporary message *)
  selectDown  = 4;  (* select button held down *)
  gadgetDown  = 5;  (* gadget selected *)
  busy*       = 6;  (* editor is busy *)
  resized     = 7;  (* window was just resized *)
  smart       = 8;  (* window is type smart refresh *)

TYPE
  ArgArray10 = ARRAY 10 OF LONGINT;

  LineInfo= RECORD
    pos: LONGINT;               (* text position *)
    len, width: INTEGER;        (* line length, width   *)
    tabs: BOOLEAN;              (* containing tabs? *)
    isFold: SHORTINT;           (* IsFold() return val. *)
  END;
  LineInfoArray = UNTRACED POINTER TO ARRAY OF LineInfo;

  Window* = UNTRACED POINTER TO WindowDesc;
  WindowDesc* = RECORD (IO.WindowDesc)
    lineInfo: LineInfoArray;            (* list of lines in window *)
    pos- : LONGINT;                     (* window text position *)
    win- : I.WindowPtr;                 (* intuition window *)
    first- , last- : LONGINT;           (* position of first and last character *)
    ox- : INTEGER;                      (* window position *)
    oy- : LONGINT;
    cx- , cy- : INTEGER;                (* display position of cursor *)
    prefCol: INTEGER;                   (* preferred cursor position *)
    markBegin- , markEnd- : LONGINT;    (* position of marked text *)
    width- , height- : INTEGER;         (* window lines, columns *)
    cursorOff: INTEGER;                 (* 0 means cursor is visible *)
    displayOff: INTEGER;                (* 0 means display is on *)
    busy: INTEGER;                      (* 0 means editor is not busy *)
    maxWidth: INTEGER;                  (* width of longest line on display *)
    maxX, maxY: INTEGER;                (* maximum x, maximum y *)
    maxLine: INTEGER;                   (* maximim line *)
    title: ARRAY 100 OF CHAR;           (* window title string *)
    titleArgs: ArgArray10;              (* window title contents *)
    horizPGad, vertPGad, upGad, downGad,
    leftGad, rightGad: I.GadgetPtr;     (* scroller-gadgets *)
    flags- : SET;                       (* status-flags *)
    mtxPlanes: BOOLEAN;                 (* marked-text planes active? *)
    gad: I.GadgetPtr;                   (* temporary gadget variable *)
  END;

  WindowRef= UNTRACED POINTER TO WindowRefDesc;
  WindowRefDesc = RECORD (K.ANYDesc)
    w: Window;
  END;

VAR
  readBuf, drawBuf: ARRAY 1024 OF CHAR;
  font: Gfx.TextFontPtr;  (* text font *)
  fontOpen: BOOLEAN;      (* font open? *)
  windowData: K.DynArray;
  leftImg, rightImg, upImg, downImg, sizeImg: I.ImagePtr;
  normalTextPlanes, markedTextPlanes: SHORTSET;
  closeProc* : PROCEDURE (w: Window): SHORTINT; (* user hits close-gadget *)
  active- : Window;       (* (last) active window ('HandleInput()') *)

(* -- misc: -- *)

PROCEDURE Win* (w: Window): I.WindowPtr;

BEGIN
  IF w # NIL THEN RETURN w.win ELSE RETURN NIL END;
END Win;

(* -- basic: -- *)

PROCEDURE OpenTextFont;

BEGIN
  font:= Gfx.OpenFont(Sett.fontSpec);
  IF font = NIL THEN
    font:= DiskFont.OpenDiskFont(Sett.fontSpec);
    IF font = NIL THEN
      font:= Gfx.OpenFont(Sett.defFontSpec);
    END;
  END;
  K.Assert(font # NIL, Err.windowsNoFont);
END OpenTextFont;

PROCEDURE SetWriteMask(w: Window; marked: BOOLEAN);

BEGIN
  IF marked THEN
    IF ~w.mtxPlanes THEN Gfx.SafeSetWriteMask(w.win.rPort, markedTextPlanes) END;
  ELSE
    IF w.mtxPlanes THEN Gfx.SafeSetWriteMask(w.win.rPort, normalTextPlanes) END;
  END;
  w.mtxPlanes:= marked;
END SetWriteMask;

(* show if range [a..b] is visible *)
PROCEDURE Visible(w: Window; a, b: LONGINT): BOOLEAN;

BEGIN
  RETURN (b >= w.first) & (a <= w.last);
END Visible;

PROCEDURE LinesMarked(w: Window; y0, y1: INTEGER): BOOLEAN;

BEGIN
  RETURN (markDef IN w.flags) & (w.markEnd >= w.lineInfo[y0].pos)
         & (w.markBegin < w.lineInfo[y1].pos + w.lineInfo[y1].len);
END LinesMarked;

(* width of a horizontal tabulator at x *)
PROCEDURE TabWidth(x: INTEGER): INTEGER;

BEGIN
  RETURN Sett.tabAlign - (x MOD Sett.tabAlign);
END TabWidth;

PROCEDURE CharWidth(c: CHAR; x: INTEGER): INTEGER;

BEGIN
  IF c = ASCII.ht THEN RETURN TabWidth(x) ELSE RETURN 1 END;
END CharWidth;

(* get the position of the character at window ('x'; 'y') *)
PROCEDURE GetPos(w: Window; x, y: INTEGER): LONGINT;

VAR
  t: T.Text;
  pos: LONGINT;
  lWidth, lLen: INTEGER;
  i, wd, ox: INTEGER;

BEGIN
  t:= w.text(T.Text);
  ox:= w.ox;
  pos:= w.lineInfo[y].pos;
  lLen:= w.lineInfo[y].len;
  IF ~w.lineInfo[y].tabs THEN
    IF ox + x >= lLen THEN
      RETURN pos + lLen - 1;
    ELSE
      RETURN pos + ox + x;
    END;
  ELSE
    lWidth:= w.lineInfo[y].width;
    IF ox + x >= lWidth THEN
      RETURN pos + lLen - 1;
    ELSE
      i:= 0;
      wd:= CharWidth(t.GetChar(pos), 0);
      WHILE (i < lWidth) & (i + wd <= ox + x) DO
        INC(i, wd); INC(pos);
        wd:= CharWidth(t.GetChar(pos), i);
      END;
      RETURN pos;
    END;
  END;
END GetPos;

(* get display column of column 'col' in display line 'y' *)
PROCEDURE GetX(w: Window; col, y: INTEGER): INTEGER;

VAR
  t: T.Text;
  x: INTEGER;
  p: LONGINT;

BEGIN
  IF w.lineInfo[y].tabs THEN
    t:= w.text(T.Text);
    x:= 0;
    FOR p:= w.lineInfo[y].pos TO w.lineInfo[y].pos + col - 1 DO
      INC(x, CharWidth(t.GetChar(p), x));
    END;
    RETURN x - w.ox;
  ELSE
    RETURN col - w.ox;
  END;
END GetX;

(* get display line of position 'pos' *)
PROCEDURE GetY(w: Window; pos: LONGINT): INTEGER;

VAR
  y: INTEGER;

BEGIN
  FOR y:= 0 TO w.maxLine DO
    IF pos < w.lineInfo[y].pos + w.lineInfo[y].len THEN RETURN y END;
  END;
END GetY;

(* get ('x'; 'y') of text position 'pos' *)
PROCEDURE GetXY(w: Window; VAR x, y: INTEGER; pos: LONGINT): BOOLEAN;

BEGIN
  IF (pos >= w.first) & (pos <= w.last) THEN
    y:= GetY(w, pos);
    x:= GetX(w, SHORT(pos - w.lineInfo[y].pos), y);
    RETURN TRUE;
  ELSE
    RETURN FALSE;
  END;
END GetXY;

(* -- graphics: -- *)

(* fill a rect (x0; y0) x (x1; y1) with color 'color' *)
PROCEDURE ClearRect(w: Window; x0, y0, x1, y1: INTEGER);

BEGIN
  SetWriteMask(w, (markDef IN w.flags) & Visible(w, w.markBegin, w.markEnd));
  Gfx.SetAPen(w.win.rPort, IO.backPen);
  Gfx.RectFill(w.win.rPort, x0 * font.xSize, y0 * font.ySize, x1 * font.xSize, y1 * font.ySize);
  Gfx.SetAPen(w.win.rPort, IO.textPen);  (* default pen *)
END ClearRect;

(* write a complement rect (cursor) at ('w.cx'; 'w.cy') *)
PROCEDURE XorCursor(w: Window);

BEGIN
  IF w.displayOff >= 0 THEN
    SetWriteMask(w, TRUE);
    Gfx.SetDrMd(w.win.rPort, Gfx.jam2 + SHORTSET{Gfx.complement});
    Gfx.RectFill(w.win.rPort, w.cx * font.xSize, w.cy * font.ySize,
                 (w.cx + 1) * font.xSize - 1, (w.cy + 1) * font.ySize - 1);
    Gfx.SetDrMd(w.win.rPort, Gfx.jam2);
  END;
END XorCursor;

PROCEDURE (w: Window) CursorOn* ;

BEGIN
  IF w.cursorOff < 0 THEN
    INC(w.cursorOff);
    IF w.cursorOff = 0 THEN XorCursor(w) END;
  END;
END CursorOn;

PROCEDURE (w: Window) CursorOff* ;

BEGIN
  IF w.cursorOff = 0 THEN XorCursor(w) END;
  DEC(w.cursorOff);
END CursorOff;

(* copy a rectange ('x0'; 'y0') x ('x1'; 'y1') to ('x0 + dx'; 'y0 + dy') *)
PROCEDURE ScrollRect(w: Window; dx, dy, x0, y0, x1, y1: INTEGER);

BEGIN
  IF w.displayOff >= 0 THEN
    SetWriteMask(w, (markDef IN w.flags) & Visible(w, w.markBegin, w.markEnd));
    Gfx.ScrollRaster(w.win.rPort, dx * font.xSize, dy * font.ySize,
                     x0 * font.xSize, y0 * font.ySize,
                     (x1 + 1) * font.xSize - 1, (y1 + 1) * font.ySize - 1);
  END;
END ScrollRect;

PROCEDURE^ DrawLines(w: Window; y0, y1: INTEGER; overwrite: BOOLEAN);

(* check if window needs refreshing after ScrollRect *)
PROCEDURE (w: Window) Refresh* ;

BEGIN
  SetWriteMask(w, (markDef IN w.flags) & Visible(w, w.markBegin, w.markEnd));
  IF resized IN w.flags THEN
    EXCL(w.flags, resized);
    I.BeginRefresh(w.win);
    I.EndRefresh(w.win, I.LTRUE);
    w.Draw;
  ELSIF Gfx.layerRefresh IN w.win.rPort.layer.flags THEN
    I.BeginRefresh(w.win);
    w.CursorOff;
    DrawLines(w, 0, w.maxY, TRUE);
    w.CursorOn;
    I.EndRefresh(w.win, I.LTRUE);
  END;
END Refresh;

(* build line array for line 'y0' to line 'y1' *)
PROCEDURE Organize(w: Window; y0, y1: INTEGER);

VAR
  pos: LONGINT;
  x, y: INTEGER;
  t: T.Text;
  c: CHAR;
  tabs: BOOLEAN;

BEGIN
  t:= w.text(T.Text);
  IF y0 = 0 THEN
    pos:= w.first;  (* 'w.lineInfo[0]' not valid *)
  ELSE
    pos:= w.lineInfo[y0 - 1].pos + w.lineInfo[y0 - 1].len;
  END;
  FOR y:= y0 TO y1 DO
    w.lineInfo[y].pos:= pos;
    w.lineInfo[y].isFold:= t.IsFold(pos);
    tabs:= FALSE;
    x:= 0; c:= t.GetChar(pos);
    WHILE (c # ASCII.nul) & (c # ASCII.lf) DO
      tabs:= tabs OR (c = ASCII.ht);
      INC(x, CharWidth(c, x));
      INC(pos);
      c:= t.GetChar(pos);
    END;
    w.lineInfo[y].tabs:= tabs;
    w.lineInfo[y].len:= SHORT(pos - w.lineInfo[y].pos) + 1;
    w.lineInfo[y].width:= x + 1;
    IF (y = w.maxY) OR (pos = t.len) THEN
      w.last:= pos; w.maxLine:= y;
      RETURN;
    END;
    INC(pos);
  END;
END Organize;

(* write line['x0'..'x1'] at line 'y' *)
PROCEDURE DrawLine(w: Window; y, x0, x1: INTEGER; overwrite: BOOLEAN);

VAR
  p0, p1, c0, c1, cm: LONGINT;
  m0, m1: INTEGER;
  i, len: INTEGER;
  lVWidth: INTEGER;  (* width of visible line part *)
  lPos: LONGINT;
  rp: Gfx.RastPortPtr;
  marked: BOOLEAN;  (* text marked? *)
  isFold: SHORTINT;   (* type of text: noFold/closedFold/openFold *)
  textPen, markedTextPen, backPen, markedBackPen: INTEGER;

  PROCEDURE InitGfx;

    PROCEDURE SetDefPens;

    BEGIN
      textPen:= IO.textPen; markedTextPen:= IO.markedTextPen;
      backPen:= IO.backPen; markedBackPen:= IO.markedBackPen;
    END SetDefPens;

  BEGIN
    SetWriteMask(w, overwrite OR marked);
    CASE isFold OF
      T.noFold, T.openFold:
      SetDefPens;
    | T.closedFold, T.openFoldHeader:
      CASE Sett.foldHeader OF
        Sett.foldHInvers:
        SetDefPens;
        Gfx.SetDrMd(rp, Gfx.jam2 + SHORTSET{Gfx.inversvid})
      | Sett.foldHHighlight:
        textPen:= IO.highTextPen; markedTextPen:= IO.markedHighTextPen;
        backPen:= IO.backPen; markedBackPen:= IO.markedBackPen;
        Gfx.SetAPen(rp, textPen); Gfx.SetBPen(rp, backPen);
      | Sett.foldHBold:
        SetDefPens;
        IF Gfx.SetSoftStyle(rp, SHORTSET{Gfx.bold}, SHORTSET{0..7}) = SHORTSET{} THEN END;
      END;
    END;
  END InitGfx;

  PROCEDURE ResetGfx;

  BEGIN
    IF (isFold = T.closedFold) OR (isFold = T.openFoldHeader) THEN
      CASE Sett.foldHeader OF
        Sett.foldHInvers:
        Gfx.SetDrMd(rp, Gfx.jam2);
      | Sett.foldHHighlight:
        Gfx.SetAPen(rp, IO.textPen); Gfx.SetBPen(rp, IO.backPen);
      | Sett.foldHBold:
        IF Gfx.SetSoftStyle(rp, Gfx.normal, SHORTSET{0..7}) = SHORTSET{} THEN END;
      END;
    ELSIF (isFold = T.openFold) & (w.ox = 0) THEN
      Gfx.SetAPen(rp, IO.textPen);
      Gfx.RectFill(rp, 0, y * font.ySize + font.ySize * 3 DIV 8,
                   font.xSize DIV 2 - 1, (y + 1) * font.ySize - font.ySize * 3 DIV 8 - 1);
    END;
  END ResetGfx;

  PROCEDURE DrawTo(VAR i0, x0: INTEGER; x: INTEGER);

  VAR
    l, n, w: INTEGER;
    c: CHAR;

  BEGIN
    l:= 0;
    WHILE (x0 <= x) & (i0 < len) DO
      c:= readBuf[i0]; INC(i0);
      w:= CharWidth(c, x0);
      CASE c OF
        ASCII.ht           : FOR n:= l TO l + w - 1 DO drawBuf[n]:= " " END;
      | ASCII.lf, ASCII.nul: drawBuf[l]:= " ";
      ELSE
        drawBuf[l]:= c;
      END;
      INC(x0, w); INC(l, w);
    END;
    IF l > 0 THEN Gfx.Text(rp, drawBuf, l) END;  (* flush *)
  END DrawTo;

  PROCEDURE Clip(VAR x0, x1: INTEGER);

  BEGIN
    IF x0 < 0 THEN x0:= 0 END;
    IF x1 > w.maxX THEN x1:= w.maxX END;
  END Clip;

BEGIN
  IF (w.displayOff >= 0) & (x0 <= x1) THEN
    Clip(x0, x1);
    rp:= w.win.rPort;
    Gfx.Move(rp, x0 * font.xSize, y * font.ySize + font.baseline);
    lVWidth:= w.lineInfo[y].width - w.ox;
    IF (lVWidth > 0) & (x0 < lVWidth) THEN
      lPos:= w.lineInfo[y].pos;
      isFold:= w.lineInfo[y].isFold;
      p0:= GetPos(w, x0, y); c0:= p0 - lPos;
      p1:= GetPos(w, x1, y); c1:= p1 - lPos;
      i:= 0; len:= SHORT(p1 - p0) + 1;
      w.text(T.Text).Read(p0, readBuf, len);
      marked:= (markDef IN w.flags) & (w.markBegin < p1) & (w.markEnd >= p0);
      InitGfx;
      IF marked THEN
        cm:= w.markBegin - lPos;
        IF cm <= c0 THEN m0:= x0 ELSE m0:= GetX(w, SHORT(cm), y) END;
        cm:= w.markEnd - lPos;
        IF cm >= c1 THEN m1:= x1 ELSE m1:= GetX(w, SHORT(cm), y) END;
        Clip(m0, m1);
        DrawTo(i, x0, m0 - 1);
        Gfx.SetAPen(rp, markedTextPen); Gfx.SetBPen(rp, markedBackPen);
        DrawTo(i, x0, m1);
        Gfx.SetAPen(rp, textPen); Gfx.SetBPen(rp, backPen)
      END;
      DrawTo(i, x0, x1);
      ResetGfx;
    END;
    IF overwrite & (x0 <= x1) THEN SetWriteMask(w, TRUE); Gfx.ClearEOL(rp) END;
  END;
END DrawLine;

(* draw lines 'y0'..'y1' *)
PROCEDURE DrawLines(w: Window; y0, y1: INTEGER; overwrite: BOOLEAN);

VAR
  y: INTEGER;

BEGIN
  IF w.displayOff >= 0 THEN
    FOR y:= y0 TO y1 DO
      IF (y <= w.maxLine) & (w.ox < w.lineInfo[y].width) THEN
        DrawLine(w, y, 0, w.maxX, overwrite);
      ELSIF overwrite THEN
        SetWriteMask(w, TRUE);
        Gfx.Move(w.win.rPort, 0, y * font.ySize + font.baseline);
        Gfx.ClearEOL(w.win.rPort);
      END;
    END;
  END;
END DrawLines;

(* draw text['p0'..'p1'] *)
PROCEDURE (w: Window) DrawRange* (p0, p1: LONGINT);

VAR
  x0, y0, x1, y1: INTEGER;

BEGIN
  IF w.displayOff >= 0 THEN
    w.CursorOff;
    K.SortL(p0, p1);
    IF Visible(w, p0, p1) THEN
      IF p0 < w.first THEN p0:= w.first END;
      IF p1 > w.last THEN p1:= w.last END;
      IF GetXY(w, x0, y0, p0) & GetXY(w, x1, y1, p1) THEN
        IF y0 = y1 THEN
          DrawLine(w, y0, x0, x1, TRUE);
        ELSE
          DrawLines(w, y0, y1, TRUE);
        END;
      END;
    END;
    w.CursorOn;
  END;
END DrawRange;

PROCEDURE UpdateHSize(w: Window);

VAR
  l: INTEGER;

BEGIN
  w.maxWidth:= 0;
  FOR l:= 0 TO w.maxLine DO
    IF w.lineInfo[l].width > w.maxWidth THEN w.maxWidth:= w.lineInfo[l].width END;
  END;
END UpdateHSize;

(* draw window title *)
PROCEDURE (w: Window) UpdateWindowTitle*;

VAR
  a: ArgArray10;
  name: ARRAY 31 OF CHAR;

  PROCEDURE CmpArgs(VAR a, b: ArgArray10): BOOLEAN;

  VAR
    i: INTEGER;

  BEGIN
    FOR i:= 1 TO 9 DO
      IF a[i] # b[i] THEN RETURN FALSE END;
    END;
    RETURN TRUE;
  END CmpArgs;

BEGIN
  IF (w.displayOff >= 0) & ~((busy IN w.flags) OR (keepTitle IN w.flags)) THEN
    COPY(w.text(T.Text).name, name);
    a[0]:= w.id;
    a[1]:= SYS.ADR(name);
    a[2]:= w.text(T.Text).changes;
    a[3]:= w.ox + w.cx;
    a[4]:= w.oy + w.cy;
    a[5]:= w.text(T.Text).lines;
    IF w.text.insert THEN a[6]:= SYS.ADR(Str.insertOn^) ELSE a[6]:= SYS.ADR(Str.insertOff^) END;
    IF w.text.wordWrap THEN a[7]:= SYS.ADR(Str.wordWrapOn^) ELSE a[7]:= NIL END;
    IF Sett.recording THEN a[8]:= SYS.ADR(Str.recording^) ELSE a[8]:= NIL END;
    a[9]:= SYS.VAL(LONGINT, w.text);
    IF ~CmpArgs(a, w.titleArgs) THEN
      w.titleArgs:= a;
      K.FormatString(w.title, Str.windowTitle^, a);
      I.SetWindowTitles(w.win, SYS.ADR(w.title), -1);
    END;
  END;
END UpdateWindowTitle;

PROCEDURE (w: Window) UpdateVProp*;

VAR
  t: T.Text;

BEGIN
  t:= w.text(T.Text);
  IF (w.displayOff >= 0) &  (I.SetGadgetAttrs(w.vertPGad^, w.win, NIL,
                             I.pgaVisible, w.height, I.pgaTotal, t.lines,
                             I.pgaTop, w.oy, Util.done) = 0) THEN
  END;
END UpdateVProp;

PROCEDURE (w: Window) UpdateHProp*;

BEGIN
  UpdateHSize(w);
  IF (w.displayOff >= 0) & (I.SetGadgetAttrs(w.horizPGad^, w.win, NIL,
                            I.pgaVisible, w.width, I.pgaTotal, w.maxWidth,
                            I.pgaTop, w.ox, Util.done) = 0) THEN
  END;
END UpdateHProp;

(* draw text *)
PROCEDURE (w: Window) Draw* ;

BEGIN
  w.CursorOff;
  IF (w.maxY > w.maxLine) & (w.oy > 0) THEN
    REPEAT
      w.first:= w.text(T.Text).LineBegin(w.first - 1);
      DEC(w.oy);
      INC(w.cy);
      INC(w.maxLine);
    UNTIL (w.oy = 0) OR (w.maxLine = w.maxY);
    Organize(w, 0, w.maxY);
  END;
  UpdateHSize(w);  (* make 'w.maxWidth' valid *)
  IF (w.width - (w.maxWidth - w.ox) > 0) & (w.ox > 0) THEN
    REPEAT
      DEC(w.ox);
      INC(w.cx);
    UNTIL (w.width - (w.maxWidth - w.ox) = 0) OR (w.ox = 0);
  END;
  DrawLines(w, 0, w.maxY, TRUE);
  w.CursorOn;
END Draw;

PROCEDURE (w: Window) Mark* (p0, p1: LONGINT);

VAR
  m0, m1: LONGINT;

BEGIN
  K.SortL(p0, p1);
  IF p1 >= w.text(T.Text).len THEN p1:= w.text(T.Text).len - 1 END;
  m0:= w.markBegin; m1:= w.markEnd;
  w.markBegin:= p0; w.markEnd:= p1;
  w.CursorOff;
  IF markDef IN w.flags THEN
    IF p0 # m0 THEN w.DrawRange(p0, m0) END;
    IF p1 # m1 THEN w.DrawRange(p1, m1) END;
  ELSE
    INCL(w.flags, markDef);
    w.DrawRange(p0, p1);
  END;
  w.CursorOn;
END Mark;

(* remove mark if it exists *)
PROCEDURE (w: Window) UnMark*;

BEGIN
  IF markDef IN w.flags THEN
    EXCL(w.flags, markDef);
    w.CursorOff;
    w.DrawRange(w.markBegin, w.markEnd);
    w.CursorOn;
  END;
END UnMark;

(* window was activated *)
PROCEDURE (w: Window) Select;

BEGIN
  IF ~(selected IN w.flags) THEN
    w.CursorOn;
    INCL(w.flags, selected);
    active:= w;
  END;
END Select;

(* window was deactivated *)
PROCEDURE (w: Window) Deselect;

BEGIN
  IF selected IN w.flags THEN
    w.CursorOff;
    EXCL(w.flags, selected);
  END;
END Deselect;

PROCEDURE (w: Window) DisplayOn* ;

BEGIN
  IF w.displayOff < 0 THEN
    INC(w.displayOff);
    IF w.displayOff = 0 THEN  (* switch it on *)
      w.Draw;
      w.UpdateWindowTitle;
      w.UpdateHProp; w.UpdateVProp;
    END;
    w.CursorOn;
  END;
END DisplayOn;

PROCEDURE (w: Window) DisplayOff* ;

BEGIN
  w.CursorOff;
  DEC(w.displayOff);
END DisplayOff;

PROCEDURE DisposeObj(obj: Exec.APTR);

BEGIN
  IF obj # NIL THEN
    I.DisposeObject(obj);
  END;
END DisposeObj;

(* dispose the scroller gadgets *)
PROCEDURE DisposeGadgets(w: Window);

BEGIN
  DisposeObj(w.leftGad);
  DisposeObj(w.rightGad);
  DisposeObj(w.upGad);
  DisposeObj(w.downGad);
  DisposeObj(w.horizPGad);
  DisposeObj(w.vertPGad);
END DisposeGadgets;

(* intuition: ClearMenuStrip() *)
PROCEDURE (w: Window) RemMenu* ;

BEGIN
  IF w.win.menuStrip # NIL THEN I.ClearMenuStrip(w.win) END;
END RemMenu;

(* Intuition: SetMenuStrip() *)
PROCEDURE (w: Window) SetMenu* (menu: I.MenuPtr);

BEGIN
  IF I.SetMenuStrip(w.win, menu^) THEN END;
END SetMenu;

(* Intuition: ResetMenuStrip() *)
PROCEDURE (w: Window) ResetMenu* (menu: I.MenuPtr);

BEGIN
  IF I.ResetMenuStrip(w.win, menu^) THEN END;
END ResetMenu;

(* close the window *)
PROCEDURE (w: Window) Close*;

VAR
  dummy: INTEGER;

  PROCEDURE StripIMsg;

  VAR
    iMsg, next: Exec.NodePtr;

  BEGIN
    iMsg:= IO.userInput.msgList.head;
    WHILE iMsg.succ # NIL DO
      next:= iMsg(I.IntuiMessage).execMessage.node.succ;
      IF iMsg(I.IntuiMessage).idcmpWindow = w.win THEN
        Exec.Remove(iMsg); Exec.ReplyMsg(iMsg);
      END;
      iMsg:= next;
    END;
  END StripIMsg;

BEGIN
  windowData.Delete(SHORT(SYS.VAL(LONGINT, w.win.userData)));
  Exec.Forbid;
  StripIMsg;
  w.win.userPort:= NIL;
  I.OldModifyIDCMP(w.win, LONGSET{});
  Exec.Permit;
  w.Close^;             (* message to 'IOServer' *)
  I.CloseWindow(w.win); w.win:= NIL;
  DisposeGadgets(w);
  DISPOSE(w.lineInfo);
  IO.Refresh;  (* window was closed --> others might need a refreshing *)
END Close;

PROCEDURE^ (w: Window) SetPos* (pos: LONGINT);

(* load new text into window *)
PROCEDURE (w: Window) NewText* (t: IO.Text);

BEGIN
  w.CursorOff;
  w.NewText^(t);  (* message to 'IOServer' *)
  w.pos:= 0;
  w.cx:= 0;
  w.cy:= 0;
  w.prefCol:= 0;
  w.ox:= 0;
  w.oy:= 0;
  w.first:= 0;
  Organize(w, 0, w.maxY);
  w.SetPos(t(T.Text).LastChange());
  w.Draw;
  w.UpdateWindowTitle;
  w.UpdateHProp; w.UpdateVProp;
  w.CursorOn;
END NewText;

PROCEDURE (w: Window) Open* (t: IO.Text): BOOLEAN;

  PROCEDURE OpenWindow(): BOOLEAN;

  VAR
    refreshTag: LONGINT;

    PROCEDURE CreateGadgets(w: Window);

    VAR
      rBor, tBor, bBor: INTEGER;
      borderless: I.LONGBOOL;
      prev: I.GadgetPtr;

      PROCEDURE NewButton(VAR button: I.GadgetPtr;
                          id: INTEGER;
                          borderTag: LONGINT;
                          image: I.ImagePtr;
                          relRight, relBottom: INTEGER);

      BEGIN
        button:= I.NewObject(NIL, "buttongclass", I.gaID, id,
                             I.gaPrevious, prev,
                             I.gaImmediate, I.LTRUE,
                             I.gaGZZGadget, I.LTRUE,
                             borderTag, I.LTRUE,
                             I.gaImage, image,
                             I.gaRelRight, relRight,
                             I.gaRelBottom, relBottom,
                             I.gaWidth, image.width,
                             I.gaHeight, image.height,
                             Util.done, 0);
        prev:= button;
      END NewButton;

    BEGIN
      rBor:= upImg.width;
      bBor:= sizeImg.height;
      tBor:= IO.screen.wBorTop + IO.screen.font.ySize + 1;
      borderless:= I.BoolToLong(IO.drawInfo.depth > 1);
      prev:= NIL;
      NewButton(w.leftGad, leftID, I.gaBottomBorder, leftImg,
                -(2 * leftImg.width + rBor) + 1, -leftImg.height + 1);
      NewButton(w.rightGad, rightID, I.gaBottomBorder, rightImg,
                -(rightImg.width + rBor) + 1, -rightImg.height + 1);
      NewButton(w.downGad, downID, I.gaRightBorder, downImg,
                -downImg.width + 1, -(downImg.height + bBor) + 1);
      NewButton(w.upGad, upID, I.gaRightBorder, upImg,
                -upImg.width + 1, -(2 * upImg.height + bBor) + 1);
      w.horizPGad:= I.NewObject(NIL, "propgclass", I.gaID, horizID,
                              I.gaRelVerify, I.LTRUE,
                              I.gaImmediate, I.LTRUE,
                              I.gaPrevious, w.upGad,
                              I.gaGZZGadget, I.LTRUE,
                              I.gaBottomBorder, I.LTRUE,
                              I.pgaBorderless, borderless,
                              I.pgaNewLook, I.LTRUE,
                              I.gaLeft, leftImg.width DIV 6 + 1,
                              I.gaRelBottom, -(leftImg.height * 4 DIV 5) + 1,
                              I.gaRelWidth, -(2 * leftImg.width + 1 + rBor) - 2 * leftImg.width DIV 6,
                              I.gaHeight, leftImg.height * 3 DIV 5,
                              I.pgaFreedom, I.lorientVert,
                              I.pgaTotal, 0,
                              Util.done);
      w.vertPGad:= I.NewObject(NIL, "propgclass", I.gaID, vertID,
                              I.gaRelVerify, I.LTRUE,
                              I.gaImmediate, I.LTRUE,
                              I.gaPrevious, w.horizPGad,
                              I.gaGZZGadget, I.LTRUE,
                              I.gaRightBorder, I.LTRUE,
                              I.pgaBorderless, borderless,
                              I.pgaNewLook, I.LTRUE,
                              I.gaRelRight, -(downImg.width * 4 DIV 5) + 1,
                              I.gaTop, tBor + (upImg.height DIV 8),
                              I.gaRelHeight, -(2 * upImg.height + tBor + bBor + 2 * upImg.height DIV 8),
                              I.gaWidth, upImg.width * 3 DIV 5,
                              I.pgaFreedom, I.lorientHoriz,
                              I.pgaTotal, 0,
                              Util.done);

      IF (w.leftGad = NIL) OR (w.rightGad = NIL) OR (w.upGad = NIL) OR (w.downGad = NIL)
         OR (w.horizPGad = NIL) OR (w.vertPGad = NIL) THEN
        DisposeGadgets(w);
        K.Assert(FALSE, Err.windowsNoGadgets);
      END;
    END CreateGadgets;

  BEGIN
    CreateGadgets(w);
    IF (Exec.AvailMem(LONGSET{Exec.chip}) > 512 * 1024)
      & (Exec.AvailMem(LONGSET{}) > 1024 * 1024) THEN
      refreshTag:= I.waSmartRefresh;
    ELSE
      refreshTag:= I.waSimpleRefresh;
    END;
    w.win:= I.OpenWindowTagsA(NIL,
                              I.waPubScreen, IO.screen,
                              I.waLeft, Sett.wLeft,
                              I.waTop, Sett.wTop,
                              I.waWidth, Sett.wWidth,
                              I.waHeight, Sett.wHeight,
                              I.waMinWidth, 200,
                              I.waMinHeight, 50,
                              I.waMaxWidth, -1,
                              I.waMaxHeight, -1,
                              I.waTitle, SYS.ADR(Str.edt),
                              I.waDragBar, I.LTRUE,
                              I.waDepthGadget, I.LTRUE,
                              I.waCloseGadget, I.LTRUE,
                              I.waSizeGadget, I.LTRUE,
                              I.waGadgets, w.leftGad,
                              I.waActivate, I.LTRUE,
                              I.waSizeBRight, I.LTRUE,
                              I.waSizeBBottom, I.LTRUE,
                              I.waAutoAdjust, I.LTRUE,
                              I.waGimmeZeroZero, I.LTRUE,
                              refreshTag, I.LTRUE,
                              I.waNoCareRefresh, I.BoolToLong(refreshTag = I.waSmartRefresh),
                              I.waReportMouse, I.LTRUE,
                              I.waRptQueue, 3,
                              I.waZoom, SYS.ADR(zoom),
                              I.waIDCMP, LONGSET{},
                              I.waNewLookMenus, I.LTRUE,
                              Util.done);
    IF w.win # NIL THEN
      K.Assert(w.Open^(t), Err.windowsNoWindow);  (* message to 'IOServer' *)
      w.win.userPort:= IO.userInput;  (* create IDCMP *)
      K.Assert(I.ModifyIDCMP(w.win, idcmpFlags), Err.windowsNoUserPort);
      IO.AddAppWindow(w, w.win);
      RETURN TRUE;
    ELSE
      RETURN FALSE;
    END;
  END OpenWindow;

  PROCEDURE Register;

  VAR
    i: INTEGER;
    n: WindowRef;

  BEGIN
    i:= 0; WHILE (i < windowData.len) & (windowData.Get(i) # NIL) DO INC(i) END;
    NEW(n);
    n.w:= w; w.win.userData:= i;
    windowData.Put(n, i);
  END Register;

  PROCEDURE Display;

  BEGIN
    w.DisplayOff;
    Gfx.SetFont(w.win.rPort, font);
    Gfx.SetDrMd(w.win.rPort, Gfx.jam2);
    Gfx.SetAPen(w.win.rPort, 1); Gfx.SetBPen(w.win.rPort, 0);
    w.UpdateHProp; w.UpdateVProp;
    w.UpdateWindowTitle;
    w.Select;
    w.SetPos(t(T.Text).LastChange());
    w.DisplayOn;
  END Display;

  PROCEDURE Init;

  BEGIN
    w.pos:= 0;
    w.first:= 0;
    (* w.last --> Organize(w) *)
    w.ox:= 0;
    w.oy:= 0;
    w.cx:= 0;
    w.cy:= 0;
    w.prefCol:= 0;
    w.cursorOff:= -1; (* off *)
    w.displayOff:= 0; (* on *)
    w.busy:= 0;  (* not busy *)
    w.flags:= {};
    IF I.noCareRefresh IN w.win.flags THEN INCL(w.flags, smart) END;
    w.width:= w.win.gzzWidth DIV font.xSize;
    w.height:= w.win.gzzHeight DIV font.ySize;
    w.maxX:= w.width - 1;
    w.maxY:= w.height -1;
    NEW(w.lineInfo, w.height);
    Organize(w, 0, w.maxY);
  END Init;

BEGIN
  IF OpenWindow() THEN
    Init;
    Register;
    Display;
    RETURN TRUE;
  ELSE
    RETURN FALSE;
  END;
END Open;

(* show that 'edt' is busy *)
PROCEDURE (w: Window) Busy* (info: ARRAY OF CHAR);

(* $CopyArrays- *)

BEGIN
  INCL(w.flags, busy);
  IF (w.displayOff >= 0) & (w.busy = 0) THEN
    IF K.intVer >= 39 THEN
      I.SetWindowPointer(w.win, I.waBusyPointer, I.LTRUE,
                         I.waPointerDelay, I.LTRUE, Util.done);
    END;
    IF info # "" THEN
      w.titleArgs:= ArgArray10(0,0,0,0,0,0,0,0,0,0);
      COPY(info, w.title);
      I.SetWindowTitles(w.win, SYS.ADR(w.title), -1);
    END;
  END;
  DEC(w.busy);
  EXCL(w.flags, keepTitle);
END Busy;

(* show that 'edt' has stopped being busy *)
PROCEDURE (w: Window) BusyDone*;

BEGIN
  IF w.busy < 0 THEN
    INC(w.busy);
    IF w.busy = 0 THEN
      EXCL(w.flags, busy);
      IF K.intVer >= 39 THEN
        I.SetWindowPointer(w.win, I.waPointer, NIL, I.waPointerDelay, I.LTRUE, Util.done);
      END;
      w.UpdateWindowTitle;
    END;
  END;
END BusyDone;

PROCEDURE (w: Window) RestoreStatus*;

BEGIN
  w.RestoreStatus^;
  IF (w.cursorOff # 0) OR (w.busy # 0) OR (w.displayOff # 0) THEN
    WHILE w.cursorOff < 0 DO w.CursorOn END;
    WHILE w.busy < 0 DO w.BusyDone END;
    WHILE w.displayOff < 0 DO w.DisplayOn END;
    w.Draw;     (* redraw *)
  END;
END RestoreStatus;

(* set window title to 'msg' *)
PROCEDURE (w: Window) Title* (msg: ARRAY OF CHAR);

(* $CopyArrays- *)

BEGIN
  w.titleArgs:= ArgArray10(0,0,0,0,0,0,0,0,0,0);
  COPY(msg, w.title);
  I.SetWindowTitles(w.win, SYS.ADR(w.title), -1);
  INCL(w.flags, keepTitle);
END Title;

(* -- buffer cursor: -- *)

(* scroll left/right: cursor off *)
PROCEDURE (w: Window) ScrollDX* (dx: INTEGER);

  PROCEDURE DrawNewPart(x0, x1: INTEGER; overwrite: BOOLEAN);

  VAR
    y: INTEGER;

  BEGIN
    SetWriteMask(w, (markDef IN w.flags) & Visible(w, w.markBegin, w.markEnd));
    FOR y:= 0 TO w.maxLine DO DrawLine(w, y, x0, x1, overwrite) END;
  END DrawNewPart;

BEGIN
  UpdateHSize(w);  (* make 'w.maxWidth' valid *)
  IF dx < -w.ox THEN
    dx:= -w.ox;
  ELSIF (dx > 0) & (w.ox + w.maxX + dx >= w.maxWidth) THEN
    IF w.maxWidth > w.width THEN
      dx:= w.maxWidth - (w.ox + w.width) - 1;
    ELSE
      RETURN;
    END;
  END;
  w.CursorOff;
  INC(w.ox, dx);
  IF ABS(dx) < w.width THEN
    ScrollRect(w, dx, 0, 0, 0, w.maxX, w.maxLine);
    IF dx < 0 THEN
      DrawNewPart(0, -dx - 1, FALSE);
    ELSIF dx > 0 THEN
      DrawNewPart(w.width - dx, w.maxX, FALSE);
    END;
    w.Refresh;
  ELSE
    DrawNewPart(0, w.maxX, TRUE);
  END;
  w.CursorOn;
END ScrollDX;

(* move cursor left/right: *)
PROCEDURE (w: Window) MoveDX* (dx: INTEGER);

VAR
  newX: INTEGER;

BEGIN
  IF w.ox + w.cx + dx < 0 THEN
    dx:= -(w.ox + w.cx);
  ELSIF w.ox + w.cx + dx >= w.lineInfo[w.cy].width THEN
    dx:= w.lineInfo[w.cy].width - (w.ox + w.cx) - 1;
  END;
  w.CursorOff;
  newX:= w.cx + dx;
  IF newX > w.maxX THEN
    w.ScrollDX(newX - w.maxX);
    w.cx:= w.maxX;
  ELSIF newX < 0 THEN
    w.ScrollDX(newX);
    w.cx:= 0;
  ELSE
    w.cx:= newX;
  END;
  w.pos:= GetPos(w, w.cx, w.cy);
  w.cx:= GetX(w, SHORT(w.pos-w.lineInfo[w.cy].pos), w.cy);
  w.prefCol:= w.ox + w.cx;
  w.CursorOn;
END MoveDX;

(* scroll up/down: cursor off *)
PROCEDURE (w: Window) ScrollDY* (dy: LONGINT);

VAR
  dispDY, y: INTEGER;

  PROCEDURE DrawNewPart(y0, y1: INTEGER; overwrite: BOOLEAN);

  BEGIN
    IF overwrite OR LinesMarked(w, y0, y1) THEN
      SetWriteMask(w, TRUE);
      Organize(w, y0, y1);
      DrawLines(w, y0, y1, overwrite);
    ELSE
      Organize(w, y0, y1);
      SetWriteMask(w, overwrite OR LinesMarked(w, y0, y1));
      DrawLines(w, y0, y1, overwrite);
    END;
  END DrawNewPart;

BEGIN
  IF (w.oy + dy + w.maxY >= w.text(T.Text).lines) THEN
    IF w.oy > 0 THEN
      dy:= w.text(T.Text).lines - (w.oy + w.maxY) - 1;
    ELSIF dy > 0 THEN
      RETURN;
    END;
  END;
  IF dy < -w.oy THEN
    dy:= -w.oy;
  END;
  w.CursorOff;
  INC(w.oy, dy);
  IF ABS(dy) < w.height THEN
    dispDY:= SHORT(dy);
    ScrollRect(w, 0, dispDY, 0, 0, w.maxX, w.maxY);
    IF dispDY > 0 THEN
      FOR y:= dispDY TO w.maxY DO w.lineInfo[y - dispDY]:= w.lineInfo[y] END;
      w.first:= w.lineInfo[0].pos;
      DrawNewPart(w.maxY - dispDY + 1, w.maxY, FALSE);
    ELSIF dispDY < 0 THEN
      FOR y:= 1 TO -dispDY DO w.first:= w.text(T.Text).LineBegin(w.first - 1) END;
      FOR y:= w.maxY TO -dispDY BY -1 DO w.lineInfo[y]:= w.lineInfo[y + dispDY] END;
      w.last:= w.lineInfo[w.maxY].pos + w.lineInfo[w.maxY].len - 1;
      DrawNewPart(0, -dispDY - 1, FALSE);
    END;
    w.Refresh;
  ELSE
    w.first:= w.text(T.Text).GetLinePos(w.oy, w.first, w.oy-dy);
    DrawNewPart(0, w.maxY, TRUE);
  END;
  w.CursorOn;
END ScrollDY;

(* set cursor in line 'y', column 'w.cx' if possible *)
PROCEDURE (w: Window) SetCursor(y: INTEGER);

VAR
  prefCol: INTEGER;

BEGIN
  w.CursorOff;
  prefCol:= w.prefCol;
  IF prefCol >= w.lineInfo[y].width THEN prefCol:= w.lineInfo[y].width - 1 END;
  IF prefCol - w.ox > w.maxX THEN
    w.ScrollDX(prefCol - w.ox - w.maxX);
  ELSIF prefCol < w.ox THEN
    w.ScrollDX(prefCol - w.ox);
  END;
  w.cx:= prefCol - w.ox; w.cy:= y;
  w.pos:= GetPos(w, w.cx, y);
  w.CursorOn;
END SetCursor;

PROCEDURE (w: Window) ResetCursor*;

VAR
  y, i: INTEGER;

BEGIN
  w.prefCol:= w.ox + w.cx;
  y:= -w.maxLine;
  FOR i:= 0 TO w.maxLine DO
    IF (ABS(i - w.cy) < ABS(y - w.cy)) & (w.lineInfo[i].width > w.ox + w.prefCol) THEN
      y:= i;
    END;
  END;
  IF y = -w.maxLine THEN y:= w.cy END;
  w.SetCursor(y);
END ResetCursor;

(* move cursor up/down: *)
PROCEDURE (w: Window) MoveDY* (dy: LONGINT);

VAR
  newY: LONGINT;
  y: LONGINT;

BEGIN
  IF w.oy + w.cy + dy < 0 THEN
    dy:= -(w.oy + w.cy);
  ELSIF w.oy + w.cy + dy >= w.text(T.Text).lines THEN
    dy:= w.text(T.Text).lines - (w.oy + w.cy) - 1;
  END;
  w.CursorOff;
  newY:= w.cy + dy;
  IF newY > w.maxY THEN
    w.ScrollDY(newY - w.maxY);
    w.SetCursor(w.maxY);
  ELSIF newY < 0 THEN
    w.ScrollDY(newY);
    w.SetCursor(0);
  ELSE
    w.SetCursor(SHORT(newY));
  END;
  w.CursorOn;
END MoveDY;

(* $StackChk+ *)
(* move to position 'pos'; this does not rely on 'w.cx' and 'w.cy' *)
PROCEDURE (w: Window) SetPos* (pos: LONGINT);

VAR
  x, dy: INTEGER;

  PROCEDURE Near(pos: LONGINT; VAR dy: INTEGER): BOOLEAN;

  VAR
    p: LONGINT;

  BEGIN
    IF w.pos <= w.text(T.Text).len THEN
      dy:= 0;
      IF pos > w.last THEN
        p:= w.last;
        REPEAT
          p:= w.text(T.Text).LineEnd(p + 1);
          INC(dy);
        UNTIL (pos <= p) OR (dy > w.height);
      ELSIF pos < w.first THEN
        p:= w.first;
        REPEAT
          p:= w.text(T.Text).LineBegin(p - 1);
          DEC(dy);
        UNTIL (pos >= p) OR (dy < -w.height);
      END;
      RETURN ABS(dy) <= w.height;
    ELSE
      RETURN FALSE;
    END;
  END Near;

BEGIN
  IF pos < 0 THEN
    pos:= 0;
  ELSIF pos > w.text(T.Text).len THEN
    pos:= w.text(T.Text).len;
  END;
  w.CursorOff;
  IF GetXY(w, x, w.cy, pos) THEN
    IF x < 0 THEN
      w.ScrollDX(x);
    ELSIF x > w.maxX THEN
      w.ScrollDX(x - w.maxX);
    END;
    w.cx:= GetX(w, SHORT(pos - w.lineInfo[w.cy].pos), w.cy);
    w.prefCol:= w.ox + w.cx;
    w.pos:= pos;
  ELSE
    IF Near(pos, dy) THEN
      w.ScrollDY(dy);
    ELSE
      w.oy:= w.text(T.Text).GetPosLine(pos, w.first, w.oy);
      w.first:= w.text(T.Text).LineBegin(pos);
      Organize(w, 0, w.maxY);
      w.Draw;
    END;
    w.UpdateVProp;
    w.SetPos(pos);
  END;
  w.CursorOn;
END SetPos;
(* $StackChk= *)

PROCEDURE (w: Window) SetCol* (column: INTEGER);

VAR
  dx: INTEGER;

BEGIN
  dx:= column - (w.ox + w.cx);
  IF dx # 0 THEN w.MoveDX(dx) END;
END SetCol;

PROCEDURE (w: Window) SetLine* (line: LONGINT);

VAR
  dy: LONGINT;

BEGIN
  dy:= line - (w.oy + w.cy);
  IF dy # 0 THEN w.MoveDY(dy) END;
END SetLine;

(* -- buffer edit: -- *)

PROCEDURE ShiftLines(w: Window; from: INTEGER; inc: LONGINT);

VAR
  y: INTEGER;

BEGIN
  FOR y:= from TO w.maxLine DO INC(w.lineInfo[y].pos, inc) END;
  w.last:= w.lineInfo[w.maxLine].pos + w.lineInfo[w.maxLine].len - 1;
END ShiftLines;

(* insert 'len' characters text containing 'lines' linefeeds and 'tabs' tabulators at 'pos' *)
PROCEDURE (w: Window) Insert* (pos, len, lines, tabs: LONGINT);

VAR
  x, y: INTEGER;
  li: LineInfo;

  PROCEDURE AdjustMarkPos;

  BEGIN
    IF (markDef IN w.flags) & (pos <= w.markEnd) THEN
      INC(w.markEnd, len);
      IF pos <= w.markBegin THEN
        INC(w.markBegin, len);
      END;
    END;
  END AdjustMarkPos;

BEGIN
  IF Visible (w, pos, pos) THEN
    y:= GetY(w, pos);
    w.CursorOff;
    IF lines = 0 THEN
      Organize(w, y, y);
      ShiftLines(w, y + 1, len);
      x:= GetX(w, SHORT(pos - w.lineInfo[y].pos), y);
      IF (tabs = 0) & (w.lineInfo[y].width = w.lineInfo[y].len)
         & (x >= w.ox) & (x + len <= w.maxX)
         & ((x > 0) OR (w.lineInfo[y].isFold # T.openFold)) THEN
        ScrollRect(w, -SHORT(len), 0, x, y, w.maxX, y);
        AdjustMarkPos;
        DrawLine(w, y, x, x + SHORT(len) - 1, FALSE);
      ELSE
        AdjustMarkPos;
        DrawLine(w, y, x, w.maxX, TRUE);
      END;
    ELSE
      li:= w.lineInfo[y];  (* keep lineinfo *)
      Organize(w, y, w.maxY);
      IF len = 1 THEN
        IF pos = li.pos THEN    (* begin of line *)
          ScrollRect(w, 0, -1, 0, y, w.maxX, w.maxY);
          AdjustMarkPos;
          DrawLine(w, y, 0, w.maxX, FALSE);
        ELSIF pos = li.pos + li.len - 1 THEN    (* end of line *)
          ScrollRect(w, 0, -1, 0, y + 1, w.maxX, w.maxY);
          AdjustMarkPos;
          IF y < w.maxY THEN DrawLine(w, y + 1, 0, w.maxX, FALSE) END;
        ELSIF y < w.maxY THEN
          ScrollRect(w, 0, -1, 0, y + 1, w.maxX, w.maxY);
          AdjustMarkPos;
          DrawLines(w, y, y + 1, TRUE);
        ELSE
          AdjustMarkPos;
          DrawLine(w, y, 0, w.maxX, TRUE);
        END;
      ELSE
        AdjustMarkPos;
        DrawLines(w, y, w.maxY, TRUE);
      END;
    END;
    IF pos <= w.pos THEN w.SetPos(w.pos + len) END;
    w.CursorOn;
  ELSIF pos < w.first THEN  (* before window area *)
    INC(w.oy, lines);
    INC(w.first, len);
    INC(w.pos, len);
    ShiftLines(w, 0, len);
    AdjustMarkPos;
  END;
END Insert;

(* delete 'len' characters text containing 'lines' linefeeds and 'tabs' tabulators at 'pos' *)
PROCEDURE (w: Window) Delete* (pos, len, lines, tabs: LONGINT);

VAR
  y, y1: INTEGER;   (* line(pos), line(pos + len - 1) *)
  x: INTEGER;       (* display x coordinate of pos *)
  p: LONGINT;

  (* recalculate marker positions *)
  PROCEDURE AdjustMarkPos;

  BEGIN
    IF (markDef IN w.flags) & (pos <= w.markEnd) THEN
      IF pos + len <= w.markBegin THEN
        DEC(w.markBegin, len);
      ELSIF pos < w.markBegin THEN
        w.markBegin:= pos;
      END;
      IF pos + len - 1 <= w.markEnd THEN
        DEC(w.markEnd, len);
      ELSE
        w.markEnd:= pos - 1;
      END;
      IF w.markBegin > w.markEnd THEN EXCL(w.flags, markDef) END;
    END;
  END AdjustMarkPos;

BEGIN
  IF Visible(w, pos, pos) THEN  (* visible *)
    y:= GetY(w, pos);
    w.CursorOff;
    IF lines = 0 THEN
      Organize(w, y, y);
      ShiftLines(w, y + 1, -len);
      x:= GetX(w, SHORT(pos - w.lineInfo[y].pos), y);
      IF (tabs = 0) & ~w.lineInfo[y].tabs & (x >= w.ox) & (x + len <= w.maxX) THEN
        ScrollRect(w, SHORT(len), 0, x, y, w.maxX, y);
        AdjustMarkPos;
        DrawLine(w, y, w.width - SHORT(len), w.maxX, FALSE);
      ELSE
        AdjustMarkPos;
        DrawLine(w, y, x, w.maxX, TRUE);
      END;
    ELSE
      AdjustMarkPos;
      Organize(w, 0, w.maxY);
      IF (w.maxY > w.maxLine) & (w.oy > 0) THEN
        w.Draw;  (* redraw completely *)
      ELSE
        DrawLines(w, y, w.maxY, TRUE);
      END;
    END;
    IF pos + len < w.pos THEN
      w.SetPos(w.pos - len);
    ELSIF pos < w.pos THEN
      w.SetPos(pos);  (* reset *)
    END;
    w.CursorOn;
  ELSIF pos + len < w.first THEN  (* not visible *)
    DEC(w.oy, lines);
    DEC(w.first, len);
    DEC(w.pos, len);
    ShiftLines(w, 0, -len);
    AdjustMarkPos;
  ELSIF pos < w.first THEN
    w.first:= w.text(T.Text).LineBegin(pos);
    w.oy:= w.text(T.Text).GetPosLine(w.first, 0, 0);
    Organize(w, 0, w.maxY);
    AdjustMarkPos;
    w.CursorOff;
    w.Draw;
    IF pos + len <= w.pos THEN w.SetPos(w.pos - len) ELSE w.SetPos(pos) END;
    w.CursorOn;
  END;
END Delete;

(* is fold at w.pos? *)
PROCEDURE (w: Window) IsFold*(): SHORTINT;

BEGIN
  RETURN w.lineInfo[w.cy].isFold;
END IsFold;

(* create new fold *)
PROCEDURE (w: Window) NewFold* (begin, end: LONGINT);

VAR
  y0, y1: INTEGER;

BEGIN
  IF Visible(w, begin, end) THEN
    IF begin > w.first THEN y0:= GetY(w, begin) ELSE y0:= 0 END;
    IF end < w.last THEN y1:= GetY(w, end) ELSE y1:= w.maxLine END;
    w.lineInfo[y0].isFold:= w.text(T.Text).IsFold(w.lineInfo[y0].pos);
    FOR y0:= y0 + 1 TO y1 DO w.lineInfo[y0].isFold:= T.openFold END;
    w.DrawRange(begin, end);
  END;
END NewFold;

PROCEDURE (w: Window) OpenFold* (pos, p0, len, lines, tabs: LONGINT);

VAR
  x, y: INTEGER;

BEGIN
  w.CursorOff;
  IF GetXY(w, x, y, pos) THEN
    w.lineInfo[y].isFold:= T.openFoldHeader;
    DrawLine(w, y, 0, w.maxX, TRUE);
  END;
  w.Insert(p0, len, lines, tabs);
  w.CursorOn;
END OpenFold;

PROCEDURE (w: Window) CloseFold* (pos, p0, len, lines, tabs: LONGINT);

VAR
  x, y: INTEGER;

BEGIN
  w.CursorOff;
  IF GetXY(w, x, y, pos) THEN
    w.lineInfo[y].isFold:= T.closedFold;
    DrawLine(w, y, 0, w.maxX, TRUE);
  END;
  w.Delete(p0, len, lines, tabs);
  w.CursorOn;
END CloseFold;

PROCEDURE (w: Window) ResolveFold* (begin, end: LONGINT);

VAR
  y0, y1: INTEGER;

BEGIN
  IF Visible(w, begin, end) THEN
    IF begin >= w.first THEN y0:= GetY(w, begin) ELSE y0:= 0 END;
    IF end <= w.last THEN y1:= GetY(w, end) ELSE y1:= w.maxLine END;
    FOR y0:= y0 TO y1 DO w.lineInfo[y0].isFold:= T.noFold END;
    w.DrawRange(begin, end);
  END;
END ResolveFold;

PROCEDURE (w: Window) Update* ;

BEGIN
  w.UpdateWindowTitle;
END Update;

(* -- intuition -- *)

(* set number of columns *)
PROCEDURE (w: Window) SetWidth* (cols: INTEGER);

BEGIN
  I.SizeWindow(w.win, (cols - w.width) * font.xSize, 0);
END SetWidth;

(* set number of lines *)
PROCEDURE (w: Window) SetHeight* (lines: INTEGER);

BEGIN
  I.SizeWindow(w.win, 0, (lines - w.height) * font.ySize);
END SetHeight;

(* window was resized *)
PROCEDURE (w: Window) NewSize;

BEGIN
  w.CursorOff;
  w.width:= w.win.gzzWidth DIV font.xSize;
  w.height:= w.win.gzzHeight DIV font.ySize;
  w.maxX:= w.width - 1;
  w.maxY:= w.height - 1;
  ClearRect(w, w.width, 0, w.width + 1, w.height + 1);   (* clear right border *)
  ClearRect(w, 0, w.height, w.width + 1, w.height + 1);  (* clear bottom border *)
  DISPOSE(w.lineInfo);
  NEW(w.lineInfo, w.height);
  Organize(w, 0, w.maxY);
  w.SetPos(w.pos);  (* reset cursor *)
  w.UpdateHProp; w.UpdateVProp;
  w.CursorOn;
END NewSize;

PROCEDURE^ CalcPlanes;

(* prefs have changed *)
PROCEDURE (w: Window) NewPrefs* ;

BEGIN
  CalcPlanes;
  OpenTextFont;
  Gfx.SetFont(w.win.rPort, font);
  w.NewSize;
  w.Draw;
END NewPrefs;

(* -- IO -- *)

PROCEDURE HandleInput* (): SHORTINT;

VAR
  t: T.Text;
  w: Window;
  msg: SHORTINT;            (* IO return msg *)
  msgSrc: SHORTINT;         (* source of message *)
  iMsg: I.IntuiMessagePtr;  (* intuition message *)
  class: LONGSET;           (* message class *)
  code: INTEGER;            (* message code *)
  qual: SET;                (* qualifiers *)
  iAdr: SYS.ADDRESS;        (* for correct dead key conversion *)
  ticks: LONGINT;           (* intuiticks counter *)
  linesBlock, foldBlock: BOOLEAN;      (* mark complete lines *)
  secs0, secs1: LONGINT;
  dcSecs0, dcSecs1, dcMics0, dcMics1: LONGINT;
  p0, p1, oldP0, oldP1: LONGINT;          (* block marks *)
  c0, c1: CHAR;
  cstr: ARRAY 1 OF CHAR;
  mod: BOOLEAN;             (* modulator *)

  PROCEDURE SetMousePos;

  BEGIN
    w.SetLine(w.win.gzzMouseY DIV font.ySize + w.oy);
    w.SetCol(w.win.gzzMouseX DIV font.xSize + w.ox);
  END SetMousePos;

  PROCEDURE Mark;

  BEGIN
    SetMousePos;
    IF w.pos # p1 THEN
      p1:= w.pos;
      IF linesBlock THEN
        IF p0 <= p1 THEN
          p0:= t.LineBegin(p0);
          p1:= t.LineEnd(p1);
        ELSE
          p0:= t.LineEnd(p0);
          p1:= t.LineBegin(p1);
        END;
      END;
      w.Mark(p0, p1);
    END;
  END Mark;

  PROCEDURE DoArrowGads;

  BEGIN
    CASE w.gad.gadgetID OF
      upID:    w.ScrollDY(-1);
               w.UpdateVProp;
    | downID:  w.ScrollDY(1);
               w.UpdateVProp;
    | leftID:  w.ScrollDX(-1);
    | rightID: w.ScrollDX(1);
    ELSE
    END;
  END DoArrowGads;

  PROCEDURE DoPropGads;

  VAR
    hPos, vPos : LONGINT;

  BEGIN
    IF (w.gad.gadgetID = horizID)
      & (I.GetAttr(I.pgaTop, w.gad, hPos) # 0) THEN
      w.ScrollDX(SHORT(hPos) - w.ox);
    ELSIF (w.gad.gadgetID = vertID)
      & (I.GetAttr(I.pgaTop, w.gad, vPos) # 0) THEN
      w.ScrollDY(vPos - w.oy);
    END;
  END DoPropGads;

  PROCEDURE IndentLF;

  VAR
    p: LONGINT;
    i: INTEGER;
    c: CHAR;
    ind: ARRAY 51 OF CHAR;

  BEGIN
    ind[0]:= ASCII.lf; i:= 1;
    p:= w.lineInfo[w.cy].pos;
    c:= t.GetChar(p);
    WHILE (i < 51) & ((c = " ") OR (c = ASCII.ht)) DO
      ind[i]:= c; INC(i); INC(p);
      c:= t.GetChar(p);
    END;
    IF (c = ASCII.lf) & (c = ASCII.nul) THEN i:= 1 END;
    t.Insert(w.pos, ind, i);
    IF Sett.recording & (Sett.recText IN Sett.macroFlags) THEN
      M.AddCommand("'InsASCII 10'", FALSE);
      FOR i:= 1 TO i-1 DO
        IF ind[i]= " " THEN
          M.AddCommand("'Insert 32'", FALSE);
        ELSE
          M.AddCommand("'InsASCII 9'", FALSE);
        END;
      END;
    END;
  END IndentLF;

  PROCEDURE StartTimer;

  BEGIN
    IF ~(I.intuiTicks IN w.win.idcmpFlags) THEN I.OldModifyIDCMP(w.win, w.win.idcmpFlags + LONGSET{I.intuiTicks}) END;
  END StartTimer;

  PROCEDURE StopTimer;

  BEGIN
    IF I.intuiTicks IN w.win.idcmpFlags THEN I.OldModifyIDCMP(w.win, w.win.idcmpFlags - LONGSET{I.intuiTicks}) END;
  END StopTimer;

  PROCEDURE RecordCmd(fmt: ARRAY OF CHAR; arg: LONGINT);

  VAR
    cmd: ARRAY 30 OF CHAR;
    a: ARRAY 1 OF LONGINT;

  (* $CopyArrays- *)

  BEGIN
    IF Sett.recording THEN
      a[0]:= arg;
      K.FormatString(cmd, fmt, a);
      M.AddCommand(cmd, FALSE);
    END;
  END RecordCmd;

  PROCEDURE RecMousePos;

  BEGIN
    IF Sett.recMouse IN Sett.macroFlags THEN
      RecordCmd("'Set POSITION %ld'", w.pos);
    END;
  END RecMousePos;

  PROCEDURE RecMouseMark;

  BEGIN
    IF Sett.recMouse IN Sett.macroFlags THEN
      RecordCmd("'Set POSITION %ld'", w.markBegin);
      M.AddCommand("'BeginMark'", FALSE);
      RecordCmd("'Set POSITION %ld'", w.markEnd);
      M.AddCommand("'EndMark'", FALSE);
    END;
  END RecMouseMark;

  PROCEDURE RecNewFold;

  BEGIN
    IF Sett.recMouse IN Sett.macroFlags THEN
      RecMouseMark;
      RecordCmd("'NewFold'; 'CloseFold'", 0);
    END;
  END RecNewFold;

  PROCEDURE RecOpenFold;

  BEGIN
    IF Sett.recMouse IN Sett.macroFlags THEN
      RecordCmd("'OpenFold'", 0);
    END;
  END RecOpenFold;

  PROCEDURE RecCloseFold;

  BEGIN
    IF Sett.recMouse IN Sett.macroFlags THEN
      RecordCmd("'CloseFold'", 0);
    END;
  END RecCloseFold;

  PROCEDURE RecInsertChar(c: ARRAY OF CHAR);

  VAR
    a: ARRAY 1 OF LONGINT;
    cmd: ARRAY 12 OF CHAR;

  (* $CopyArrays- *)

  BEGIN
    IF Sett.recText IN Sett.macroFlags THEN
      CASE c[0] OF
        ASCII.lf, ASCII.cr, ASCII.ht, "'", '"', "`":
          RecordCmd("'InsASCII %ld'", ORD(c[0]));
      ELSE
        RecordCmd("'Insert `%s`'", SYS.ADR(c));
      END;
    END;
  END RecInsertChar;

  PROCEDURE InsertChar(ch: CHAR);

  VAR
    c: ARRAY 2 OF CHAR;

  BEGIN
    c[0]:= ch; c[1]:= ASCII.nul;
    t.Insert(w.pos, c, 1);
    RecInsertChar(c);
  END InsertChar;

  PROCEDURE PutChar(ch: CHAR);

  VAR
    c: ARRAY 2 OF CHAR;
    nextPos: LONGINT;

  BEGIN
    IF Sett.recText IN Sett.macroFlags THEN
      c[0]:= ch; c[1]:= ASCII.nul;
      RecInsertChar(c);
      RecordCmd("'Delete'", 0);
    END;
    nextPos:= w.pos + 1;
    t.PutChar(w.pos, ch);
    IF w.pos # nextPos THEN w.SetPos(nextPos) END;
  END PutChar;

BEGIN
  mod:= FALSE;
  w:= NIL;
  msg:= IO.msgLocal;
  ticks:= 0;
  secs0:= 0; dcSecs0:= 0; dcMics0:= 0;
  linesBlock:= FALSE; foldBlock:= FALSE;
  REPEAT
    iMsg:= Exec.GetMsg(IO.userInput);
    WHILE iMsg = NIL DO
      IF w # NIL THEN StartTimer END;
      msgSrc:= IO.Wait();
      IF msgSrc = IO.rexxMsg THEN
        RETURN IO.msgRexxCtrl;
      ELSIF msgSrc = IO.appMsg THEN
        RETURN IO.msgAppCtrl;
      END;
      iMsg:= Exec.GetMsg(IO.userInput);
    END;

    w:= windowData.Get(SHORT(SYS.VAL(LONGINT, iMsg.idcmpWindow.userData)))(WindowRef).w;
    t:= w.text(T.Text);
    class:= iMsg.class;
    code:= iMsg.code;
    qual:= iMsg.qualifier;
    iAdr:= iMsg.iAddress;
    secs1:= iMsg.time.secs; dcSecs1:= secs1;
    dcMics1:= iMsg.time.micro;
    Exec.ReplyMsg(iMsg);

    IF I.intuiTicks IN class THEN
      IF (gadgetDown IN w.flags) & (w.gad.gadgetID <= downID) THEN  (* repeat *)
        IF I.selected IN w.gad.flags THEN
          DoArrowGads;
        ELSE
          w.ResetCursor;
          w.CursorOn;
          EXCL(w.flags, gadgetDown);
        END;
      ELSIF selectDown IN w.flags THEN  (* mark block? *)
        Mark;
      ELSE  (* use breaks for slow operations *)
        INC(ticks);
        CASE ticks OF
           1: EXCL(w.flags, resized);
              IF mod THEN w.UpdateWindowTitle ELSE w.UpdateVProp END;
              mod:= ~mod;
        |  2: w.UpdateHProp;
        |  3: w.UpdateVProp;
        |  4: w.UpdateWindowTitle;
        |  5: w.prefCol:= w.ox + w.cx;
        | 10: IO.UpdateWindowTitle(w.text);
        | 12: IO.UpdateHProp(w.text);
        | 15: IO.UpdateVProp(w.text);
        | 19: EXCL(w.flags, keepTitle)
        | 20: IO.TestAutosave;
        ELSE
          IF ticks > 30 THEN t.Update(w.pos) END;
        END;
      END;
    ELSE
      IF ~(I.mouseMove IN class) THEN StopTimer; ticks:= 0 END;
      CASE GUI.IMsgClass(class) OF
        I.rawKey:
          IF ~IO.DoRawKey(msg, w, code, qual) & IO.ANSICode(c0, code, qual, iAdr) THEN
            w.CursorOff;
            IF c0 = ASCII.ht THEN
              InsertChar(ASCII.ht);
            ELSIF (c0 = ASCII.lf) OR (c0 = ASCII.cr) THEN
              IF t.autoIndent
                & ((w.pos = t.len) OR (t.GetChar(w.pos) = ASCII.lf)) THEN
                IndentLF;
              ELSE
                InsertChar(ASCII.lf);
              END;
            ELSE
              c1:= c0;
              IF t.insert OR (t.GetChar(w.pos) = ASCII.lf) THEN
                InsertChar(c1);
              ELSE
                PutChar(c1);
              END;
              IF t.wordWrap & (w.ox + w.cx >= Sett.margin) THEN
                cstr[0]:= ASCII.lf;
                t.Insert(t.WordBegin(w.pos - 1), cstr, 1);  (* word wrap *)
              END;
            END;
            w.CursorOn;
          END;
      | I.menuPick:
          IF code # I.menuNull THEN IO.DoMenu(msg, w, code) END;
      | I.mouseButtons:
          IF code = I.selectDown THEN      (* begin drag select *)
            INCL(w.flags, selectDown);
            foldBlock:= IE.control IN qual;
            linesBlock:= (IE.lShift IN qual) OR (IE.rShift IN qual) OR foldBlock;
            INCL(w.win.flags, I.rmbTrap);
            secs0:= secs1;
            IF markDef IN w.flags THEN
              oldP0:= w.markBegin; oldP1:= w.markEnd;  (* keep marks *)
            ELSE
              oldP0:= -1;
            END;
            SetMousePos;
            IF linesBlock THEN
              p0:= t.LineBegin(w.pos); p1:= t.LineEnd(w.pos);
            ELSE
              p0:= w.pos; p1:= w.pos;
            END;
            StartTimer;
          ELSIF code = I.selectUp THEN
            EXCL(w.flags, selectDown);
            EXCL(w.win.flags, I.rmbTrap);
            IF foldBlock THEN
              RecNewFold;
              IF t.NewFold(w.markBegin, w.markEnd, TRUE) THEN
                w.UnMark;
              ELSE
                GUI.Flash;
              END;
            ELSIF I.DoubleClick(dcSecs0, dcMics0, dcSecs1, dcMics1) THEN
              CASE w.IsFold() OF
                T.noFold:       (* ignore *)
              | T.closedFold:
                  IF t.OpenFold(w.pos) THEN
                    RecOpenFold;
                  ELSE
                    GUI.Flash;
                  END;
              | T.openFold, T.openFoldHeader:
                  IF t.CloseFold(w.pos) THEN
                    RecCloseFold;
                  ELSE
                    GUI.Flash;
                  END;
              END;
            ELSIF ~linesBlock & (secs1 = secs0) THEN  (* set cursor *)
              IF oldP0 = -1 THEN w.UnMark ELSE w.Mark(oldP0, oldP1) END;
              RecMousePos;
            ELSE
              RecMouseMark;
            END;
            dcSecs0:= dcSecs1; dcMics0:= dcMics1;
          ELSIF code = I.menuUp THEN       (* cancel drag select *)
            w.UnMark; w.SetPos(p0);
          END;
      | I.mouseMove:
          IF gadgetDown IN w.flags THEN
            DoPropGads;
          ELSIF selectDown IN w.flags THEN
            Mark;
          END;
      | I.gadgetDown:
          w.gad:= iAdr;
          INCL(w.flags, gadgetDown);
          w.CursorOff;
          IF w.gad.gadgetID <= downID THEN StartTimer END;  (* arrow gadgets' repeat *)
      | I.gadgetUp:
          DoPropGads;
          w.ResetCursor;
          w.CursorOn;
          EXCL(w.flags, gadgetDown);
      | I.refreshWindow:
          w.Refresh;
      | I.newSize:
          INCL(w.flags, resized); w.NewSize;
          IF smart IN w.flags THEN w.Refresh END;
      | I.activeWindow:
          w.Select;
      | I.inactiveWindow:
          w.Deselect;
      | I.closeWindow:
          msg:= closeProc(w);
      END;  (* case *)
    END;  (* if *)
  UNTIL msg # IO.msgLocal;
  active:= w;
  RETURN msg;
END HandleInput;

PROCEDURE GetImageObj(VAR image: I.ImagePtr; which: LONGINT);

VAR
  size: LONGINT;

BEGIN
  IF Sett.scrollerSize = Sett.scrSizeNormal THEN
    size:= I.sysisizeMedres;
  ELSE
    size:= I.sysisizeHires;
  END;
  image:= I.NewObject(NIL, "sysiclass", I.sysiaDrawInfo, IO.drawInfo,
                      I.sysiaWhich, which, I.sysiaSize, size, Util.done);
  K.Assert(image # NIL, Err.windowsNoImages);
END GetImageObj;

PROCEDURE CalcPlanes;

TYPE
  Word = ARRAY 2 OF SYS.BYTE;

VAR
  x: Word;

BEGIN
  x:= SYS.VAL(Word, IO.textPen);
  normalTextPlanes:= SYS.VAL(SHORTSET, x[1]);
  IF Sett.foldHeader = Sett.foldHHighlight THEN
    x:= SYS.VAL(Word, IO.highTextPen);
    normalTextPlanes:= normalTextPlanes + SYS.VAL(SHORTSET, x[1]);
  END;
  IF Exec.m68020 IN Exec.SysBase.attnFlags THEN  (* fast processor? *)
    markedTextPlanes:= SHORTSET{0..7};
  ELSE
    markedTextPlanes:= normalTextPlanes;
  END;
END CalcPlanes;

BEGIN
  active:= NIL;
  leftImg:= NIL;
  rightImg:= NIL;
  downImg:= NIL;
  upImg:= NIL;
  sizeImg:= NIL;
  closeProc:= NIL;
  font:= NIL;
  CalcPlanes;
  windowData.New(5, 5);
  GetImageObj(leftImg, I.leftImage);
  GetImageObj(rightImg, I.rightImage);
  GetImageObj(upImg, I.upImage);
  GetImageObj(downImg, I.downImage);
  GetImageObj(sizeImg, I.sizeImage);
  OpenTextFont;

CLOSE
  IF font # NIL THEN Gfx.CloseFont(font) END;
  windowData.Dispose;
  DisposeObj(leftImg);
  DisposeObj(rightImg);
  DisposeObj(downImg);
  DisposeObj(upImg);
  DisposeObj(sizeImg);
END Windows.
