PROGRAM graph3d;
{ Business-Grafik in 3D }
USES graphics, intuition, exec;

{$ path "ram:include/", "pas:include/" }
{  incl "intuition.lib", "graphics.lib", "exec.lib" }
{$ incl "dos.lib", "req.lib", "intuition/preferences.h" }
{$ incl "workbench/startup.h", "libraries/dosextens.h", "graphics/gfxbase.h"  }
{$ opt q,s+,i+ } { Laufzeitprüfungen: nur Stack und Indizes }

CONST nmax=100;
      strspace = 4000;    { ca. 2*nmax*20 -> im Mittel Platz für 20 Zeichen }
      version='$VER: Graph3D 1.43  (28.04.96)';
      DEUTSCH = 'D';

TYPE r_vekt = RECORD x,y,z: real END;
     fl_vekt = RECORD x,y: real END;
     IntArr10 = ARRAY[1..10] OF Integer;
     WordArr40 = ARRAY[1..40] OF Word;
     chefstring = String[40];
     str80 = String[80];

{ Alle etwas größeren Variablen werden als STATIC deklariert, damit KICK-Pascal }
{ sie nicht auf den Stack packt (Argh!). }

VAR zr: ARRAY[1..nmax,1..nmax] OF real; STATIC;
    skz: real;
    StrHalde: ARRAY[1..strspace] OF char; STATIC;
    xtitel,ytitel: ARRAY[1..nmax] OF Str; STATIC;
    titel,einheit,filename: str80; STATIC;
    ext: string[5];
    b,o,r,e1,e2,pr0,pr1,pr0m,pr1m: r_vekt; STATIC;
    fl_quader: ARRAY[1..8] OF fl_vekt; STATIC;
    off,pfl0,pfl1: fl_vekt;
    rd,rb,phi,theta: Real;
    mag,gap: Real;
    schraeg,ende,notaus,quickdraw: Boolean;
    datei: Text;
    nx,ny,modus: Integer;
    grace,horiz,vert: Integer;

    { ab hier für Systemprogrammierung: }
VAR areabuffer: ARRAY[1..250] OF Word; STATIC;
    MyAreaInfo: AreaInfo;
    tmp: TmpRas;
    Strip,LastMenu: p_Menu;
    LastItem, LastSubItem: p_MenuItem;
    WinGad: ARRAY[1..6] OF Gadget; STATIC;
    PropInf1,PropInf2: PropInfo; STATIC;
    MoveDat1,MoveDat2: ARRAY[1..6] OF Integer;
    Bild: ARRAY[1..4] OF Image; STATIC;
    ChipSpc: ARRAY[1..5] OF ^WordArr40;
    wintitle,scrtitle: Str80; STATIC;
    palette: ARRAY[0..3] OF Long;
    myprocess: p_Process;
    NeuesWindow: NewWindow; STATIC;
    MyWindow,oldwindowptr: p_Window;
    Rast: p_RastPort;
    Con,Upt,armem: Ptr;
    MyMsg: p_IntuiMessage;
    topazAttr: TextAttr;
    NSTags: ARRAY[1..5] OF TagItem; STATIC;
    NeuerScreen: ExtNewScreen; STATIC;
    MyScreen: p_Screen;
    charx,chary,baseline: Word;  { beschreiben den Font des Screens }
    breite,hoehe: Integer;
    { für die Reuester: }
    MyRequest: Requester; STATIC;
    ReqGad: ARRAY[1..10] OF Gadget; STATIC;
    StrInf: ARRAY[1..10] OF StringInfo; STATIC;
    ITxt: ARRAY[1..10] OF IntuiText; STATIC;
    Bord: ARRAY [1..8] OF Border; STATIC;
    Coords: ARRAY[1..4] OF IntArr10; STATIC;
    ubuf: str80; { einer für alle ;-) }
    muell: ARRAY[0..31] OF Byte;
    { für die req.library: }
    MyFileReq: p_ReqFileRequester;
    pfad: ARRAY[0..DSIZE] OF Char; STATIC;
    name: ARRAY[0..FCHARS] OF Char; STATIC;
    pfadname: ARRAY[-DSIZE..FCHARS] OF Char; STATIC;

{ *#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#* }
{ *#*#*#*#*#*#*#*#*#*#*#*#  Ausgabeformatierung  #*#*#*#*#*#*#*#*#*#*#*#* }
{ *#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#* }

FUNCTION max(a,b: Real): Real;
BEGIN
  IF a>b THEN  max := a  ELSE  max := b;
END;

FUNCTION min(a,b: Real): Real;
BEGIN
  IF a<b THEN  min := a  ELSE  min := b;
END;

FUNCTION glatt(x: Real): Real;
{ aus funktion.mod übernommen, 29.09.93 }
VAR ziffer,potenz: Real;
BEGIN
  IF x=0 THEN
    glatt := x
  ELSE BEGIN
    potenz := Pwr10(Round(Ln(Abs(x))/Ln(10)-0.5)) * x/Abs(x);
    { ln x/ln 10  ist log10(x), x/abs x  ist sgn(x) }
    ziffer := x/potenz;
    IF ziffer>7.5 THEN
      glatt := 10*potenz
    ELSE IF ziffer>3.5 THEN
      glatt := 5*potenz
    ELSE IF ziffer>1.5 THEN
      glatt := 2*potenz
    ELSE
      glatt := 1*potenz
  END
END;

PROCEDURE itoa(l: long; anz: integer; vz, fill: char; VAR xstr: chefstring);
{ Longinteger mit anz Stellen in String wandeln, von links wird mit <fill>
  aufgefüllt (z. B. '0', ' ', '*'). Falls Zahl zu lang, Leerstring als
  Fehlermeldung. }
{ <vz>: hier kann '+', '-' oder ' ' stehen. Vor negative <l> wird aber
  auf jeden Fall ein '-' gesetzt, vor positive nie. Zweck von vz='-':
  '-0' ermöglichen. }
{ aus Matrizen.p übernommen, 29.09.93 }
VAR i: Integer;
BEGIN
  IF l<0 THEN vz := '-';
  IF (l>0) AND (vz='-') THEN vz := '+';
  l := Abs(l);
  xstr := '';
  REPEAT
    xstr := Chr(l MOD 10 + Ord('0')) + xstr;
    l := l DIV 10;
  UNTIL l=0;
  IF (vz='+') OR (vz='-') THEN   { gültiges Vorzeichen }
    xstr := vz + xstr;
  anz := anz - Length(xstr);
  IF anz<0 THEN
    xstr := ''
  ELSE
    FOR i := 1 TO anz DO
      xstr := fill + xstr;
END;

PROCEDURE f77fix(r: Real; b,s: Integer; VAR ausg: chefstring);
{ Zahl r wie mit dem Fortran-Formatbeschreiber  Fb.s  umwandeln }
{ aus Matrizen.p übernommen, 29.09.93 }
VAR i,j,pos: Integer;
    teil: chefstring;
    vz: Char;
BEGIN
  { letzte auszugebende Stelle jetzt schon runden, nur die Nachkommastellen }
  { zu runden, kann Fehler ergeben! }
  IF abs(r*pwr10(s))<MaxLongInt THEN
    r := Round(r*pwr10(s)) * pwr10(-s);
  IF r<0 THEN vz := '-'  ELSE  vz := ' ';
  IF s>0 THEN BEGIN
    { Nachkommastellen }
    itoa(Trunc(Frac(Abs(r))*Pwr10(s)), s, ' ', '0', ausg);
    ausg := '.' + ausg;
  END ELSE
    ausg := '';
  itoa(Trunc(r), b - Length(ausg), vz, ' ', teil);
  IF teil = '' THEN BEGIN   { Fehler, Zahl paßt nicht }
    ausg := '';
    FOR i := 1 TO b DO
      ausg := ausg + '*';
  END ELSE
    ausg := teil + ausg;
END;

PROCEDURE ftoa(r: real; s: integer; VAR xstr: chefstring);
{ Zahl r mit maximal s signifikanten Stellen in String umwandeln, abschließende }
{ Nullen hinter dem Komma werden abgeschnitten. }
{ Schweren Bug beseitigt, z. B. r=1.0 ergab "0.1"! (10/95) }
VAR i,j,pos: integer;
    teil: chefstring;
    x: real;
BEGIN
  IF r=0 THEN
    xstr := '0'
  ELSE BEGIN
    x := Abs(r);
    pos := Round(Ln(x)/Ln(10)-0.5);    { die Zehnerpotenz, in der die
              erste Ziffer <>0 steht, für Darstellung x=?.???*pwr10(pos) }
    { Mantisse s-stellig erzeugen: }
    xstr := IntStr(Round(x/pwr10(pos-s+1)));
    { manchmal (bei 1.00E??) wird pos falsch berechnet, korrigieren! }
    j := Length(xstr); IF j>s THEN Inc(pos);
    { überflüssige Nullen wegwerfen: }
    s := j; WHILE xstr[s] = '0' DO BEGIN
      xstr[s] := chr(0); Dec(s)
    END;
    IF (pos>s+5) OR (pos<-3) THEN BEGIN
      { Exponentialdarstellung ratsam }
      IF s>1 THEN BEGIN     { Komma an Stelle 2 einpatchen }
        i := Length(xstr)+1; WHILE i>=2 DO BEGIN
          xstr[i+1] := xstr[i]; Dec(i);
        END;
        xstr[2] := '.';
      END;
      IF pos<0 THEN  xstr := xstr + 'E-' ELSE  xstr := xstr + 'E+';
      xstr := xstr + IntStr(Abs(pos) DIV 10);
      xstr := xstr + IntStr(Abs(pos) MOD 10);
    END ELSE BEGIN
      { gewöhnliche Dezimalschreibweise }
      IF s<=pos+1 THEN
        { keine Nachkommastellen, evtl. Nullen anhängen }
        FOR i := 1 TO pos+1-s DO  xstr := xstr + '0'
      ELSE IF pos<0 THEN BEGIN
        { führende Nullen: '0.'+... }
        teil := '0.';  FOR i := 1 TO abs(pos)-1 DO teil := teil + '0';
        xstr := teil + xstr;
      END ELSE BEGIN
        { Komma an Stelle pos+2 einpatchen }
        i := Length(xstr)+1; WHILE i>=pos+2 DO BEGIN
          xstr[i+1] := xstr[i]; Dec(i);
        END;
        xstr[pos+2] := '.';
      END;
    END;
    IF r<0 THEN  xstr := '-' + xstr;
  END;
END;

{ *#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#* }
{ *#*#*#*#*#*#*#*#*#*#*#* 3D-Vektorbehandlung *#*#*#*#*#*#*#*#*#*#*#*#*#* }
{ *#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#* }

PROCEDURE projektion(pr: r_vekt; VAR pf: fl_vekt);
VAR v: r_vekt;
    vr: Real;
BEGIN
  { Vektor v=pr-b in Richtung von r (Blickrichtung) "normieren" }
  vr := (pr.x-b.x)*r.x+(pr.y-b.y)*r.y+(pr.z-b.z)*r.z;
  v.x := (pr.x-b.x)/vr;
  v.y := (pr.y-b.y)/vr;
  v.z := (pr.z-b.z)/vr;
  { auf die Schirmvektoren e1, e2 projizieren }
  pf.x := v.x*e1.x + v.y*e1.y + v.z*e1.z;
  pf.y := v.x*e2.x + v.y*e2.y + v.z*e2.z;
  { zurechtrücken }
  pf.x := off.x + pf.x*mag*horiz;
  pf.y := off.y + pf.y*mag*vert;
END;

PROCEDURE vektoren;
{ initialisiert die Projektionsvektoren für einen Beobachter in den (auf rd }
{ bezogenen) Kugelkoordinaten rb, phi, theta }
VAR rr: Real;
BEGIN
  { Raumdiagonale rd: }
  rd := Sqrt(Sqr(pr1.x-pr0.x)+Sqr(pr1.y-pr0.y)+Sqr(pr1.z-pr0.z));
  { Aufpunkt o für Blickrichtung: }
  o.x := (pr0.x+pr1.x)/2;
  o.y := (pr0.y+pr1.y)/2;
  o.z := (pr0.z+pr1.z)/2;
  { Beobachterpunkt b: }
  b.x := o.x + rd*rb*Cos(phi)*Sin(theta);
  b.y := o.y + rd*rb*Sin(phi)*Sin(theta);
  b.z := o.z + rd*rb*Cos(theta);
  { daraus Richtungsvektor r berechnen und normieren }
  rr := Sqrt(Sqr(o.x-b.x)+Sqr(o.y-b.y)+Sqr(o.z-b.z));
  r.x := (o.x-b.x)/rr;
  r.y := (o.y-b.y)/rr;
  r.z := (o.z-b.z)/rr;
  { Basisvektoren e1, e2 der "Rückwand der Lochkamera" bestimmen, dies sind
  zum Glück einfach die Einheitsvektoren e-Phi, e-Theta.
  Die verkehrte y-Achse des Bildschirms ist hierin berücksichtigt! }
  e1.x := -Sin(phi);
  e1.y :=  Cos(phi);
  e1.z := 0;
  e2.x := Cos(theta)*Cos(phi);
  e2.y := Cos(theta)*Sin(phi);
  e2.z := -Sin(theta);
END;

{ *#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#* }
{ *#*#*#*#*#*#*#*#*#*#*  diverse Systemoperationen  *#*#*#*#*#*#*#*#*#*#* }
{ *#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#* }

FUNCTION abbruch: Boolean;
{ wird von zeitaufwendigen Zeichenroutinen abgefragt }
VAR gad: p_Gadget;
    m,i,s: Integer;
BEGIN
  abbruch := False;
  MyMsg := Get_Msg(Upt);
  IF MyMsg<>Nil THEN BEGIN
    IF MyMsg^.Class=GADGETUP THEN BEGIN
      gad := MyMsg^.IAddress;
      IF gad^.GadgetID=2 THEN  abbruch := True;   { Not-Aus Gadget }
    END;
    IF MyMsg^.Class=MENUPICK THEN BEGIN
      m := MyMsg^.Code AND $1F;
      i := (MyMsg^.Code SHR 5) AND $3F;
      s := (MyMsg^.Code SHR 11) AND $1F;
      IF (m=2) AND (i=8) THEN  abbruch := True;  { Menüpunkt "Halt" }
    END;
    Reply_Msg(MyMsg);
  END;
END;

PROCEDURE desaster(meldung: str80);
{ erzeugt einen Alert }
VAR egal: Boolean;
    buf: String[100];
    xpos: Integer;
BEGIN
  xpos := 320 - 4*Length(meldung);
  buf := '   '+meldung;
  buf[1] := Chr(Hi(xpos)); buf[2] := Chr(Lo(xpos));
  buf[3] := Chr(18);
  buf [Length(meldung)+5] := Chr(0);
  egal := DisplayAlert(RECOVERY_ALERT,buf,32);
END;

PROCEDURE writepalette;
VAR i: integer;
BEGIN
  FOR i := 0 TO 3 DO
    SetRGB4(^MyScreen^.ViewPort,i,(palette[i] div 256) AND 15,
    (palette[i] div 16) AND 15, palette[i] AND 15);
END;

PROCEDURE getpalette;
VAR i: integer;
BEGIN
  FOR i := 0 TO 3 DO
    palette[i] := GetRGB4(MyScreen^.ViewPort.ColorMap,i);
END;

PROCEDURE defcolors;
BEGIN
  { 2.0-"Pewter"-Palette }
  palette[0] := $CCB; palette[1] := $003;
  palette[2] := $FFF; palette[3] := $9AB;
  writepalette;
END;

PROCEDURE clonecolors;
{ Farben der Workbench übernehmen }
VAR prefs: Preferences;
    i: integer;
BEGIN
  IF GetPrefs(^prefs, SizeOf(Preferences))<>Nil THEN BEGIN
    palette[0] := prefs.color0; palette[1] := prefs.color1;
    palette[2] := prefs.color2; palette[3] := prefs.color3;
    writepalette;
  END;
END;

PROCEDURE settitles(normal: Boolean);
BEGIN
  {$ if def DEUTSCH }
  scrtitle := filename+'  '+IntStr(nx)+' Spalten, '+IntStr(ny)+' Zeilen';
  {$ else }
  scrtitle := filename+'  '+IntStr(nx)+' Columns, '+IntStr(ny)+' Lines';
  {$ endif }
  IF normal THEN BEGIN
    wintitle := titel;
  END ELSE
    wintitle := Copy(version,7,Length(version)-6);
  SetWindowTitles(MyWindow,wintitle,scrtitle);
END;

{ *#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#* }
{ *#*#*#*#*#*#*#*#*#*#*#*#*#*#  Zeichnen  *#*#*#*#*#*#*#*#*#*#*#*#*#*#*#* }
{ *#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#* }

PROCEDURE quader;
{ projiziert die Eckpunkte des 'Quaders im R³' und ermittelt das der
Projektion umbeschriebene Rechteck }
VAR i: Integer;
    pr: r_vekt;
BEGIN
  FOR i := 1 TO 8 DO BEGIN
    pr.x := pr0.x + (pr1.x-pr0.x)*((i-1) MOD 2);
    pr.y := pr0.y + (pr1.y-pr0.y)*(((i-1) DIV 2) MOD 2);
    pr.z := pr0.z + (pr1.z-pr0.z)*(((i-1) DIV 4) MOD 2);
    projektion(pr,fl_quader[i]);
    IF (i=1) OR (fl_quader[i].x>pfl1.x) THEN  pfl1.x := fl_quader[i].x;
    IF (i=1) OR (fl_quader[i].y>pfl1.y) THEN  pfl1.y := fl_quader[i].y;
    IF (i=1) OR (fl_quader[i].x<pfl0.x) THEN  pfl0.x := fl_quader[i].x;
    IF (i=1) OR (fl_quader[i].y<pfl0.y) THEN  pfl0.y := fl_quader[i].y;
  END;
END;

PROCEDURE skizze;
{ Rückwände des dargestellten Quaders andeuten }
  PROCEDURE auf(i: Integer); BEGIN
    Move(Rast,Round(fl_quader[i].x),Round(fl_quader[i].y));
  END;
  PROCEDURE nach(i: Integer); BEGIN
    Draw(Rast,Round(fl_quader[i].x),Round(fl_quader[i].y));
  END;
BEGIN
  quader;       { Eckpunkte projizieren }
  SetRast(Rast,0);      { löschen }
  SetAPen(Rast,1);      { schwarz }
  auf(1); nach(2); nach(6); nach(5); nach(7); nach(3); nach(1); nach(5);
  auf(2); nach(4); nach(3);
END;

PROCEDURE schrift(ausg: chefstring; abst: integer; p0,p1: fl_vekt; ernst: boolean);
{ Ordnet den String ausg auf dem Strahl aus p0 durch p1 an, dabei abst
Leerzeichen.
Greift auf globale Variablen pfl0, pfl1 zu, um tatsächlichen Platzbedarf
der beschrifteten Zeichnung zu ermitteln }
VAR delta,step: fl_vekt;
    i,j,l: Integer;
    unsinn: Long;
    charx,chary,baseline: Word;    { Font des Windows, nicht des Screens }
BEGIN
  charx := Rast^.TxWidth;
  chary := Rast^.TxHeight;
  baseline := Rast^.TxBaseline;
  delta.x := p1.x - p0.x;
  delta.y := p1.y - p0.y;
  IF abs(delta.x/charx)>abs(delta.y/chary) THEN BEGIN
    step.x := charx*delta.x/abs(delta.x);
    step.y := charx*delta.y/abs(delta.x);
  END ELSE BEGIN
    step.x := chary*delta.x/abs(delta.y);
    step.y := chary*delta.y/abs(delta.y);
  END;
  p1.x := p0.x + abst*step.x;
  p1.y := p0.y + abst*step.y;
  IF NOT schraeg THEN BEGIN
    { normale horizontale Schrift, nützlich für Zahlen }
    step.x := charx*delta.x/abs(delta.x);
    step.y := 0;
  END;
  l := length(ausg);
  SetDrMd(Rast,JAM1);
  IF ernst THEN FOR i := 1 TO l DO BEGIN
    IF step.x>=0 THEN
      j := i
    ELSE
      j := l-i+1;
    Move(Rast,Round(p1.x+(i-1)*step.x)-charx div 2,
              Round(p1.y+(i-1)*step.y)+baseline-chary div 2);
    unsinn := _Text(Rast,ausg[j],1);
  END;
  { benötigten Platz aus der Position des ersten und letzten Zeichens
  ermitteln: }
  pfl1.x := max(pfl1.x, p1.x + charx/2);
  pfl1.y := max(pfl1.y, p1.y + chary/2);
  pfl0.x := min(pfl0.x, p1.x - charx/2);
  pfl0.y := min(pfl0.y, p1.y - chary/2);
  pfl1.x := max(pfl1.x, p1.x + (l-1)*step.x + charx/2);
  pfl1.y := max(pfl1.y, p1.y + (l-1)*step.y + chary/2);
  pfl0.x := min(pfl0.x, p1.x + (l-1)*step.x - charx/2);
  pfl0.y := min(pfl0.y, p1.y + (l-1)*step.y - chary/2);
END;

PROCEDURE skalen(ernst: boolean);
VAR textoff,step,p0,p1: fl_vekt;
    pr: r_vekt;
    i, gst,nkst: integer;
    bez: chefstring;
BEGIN
  IF ernst THEN skizze;         { Rückwände und Boden zeichnen }
  { x-/y-Skalierung und Beschriftung in Abhängigkeit vom Modus }
  textoff.x := 0;  { Verschiebung der Beschriftung vom Skalenstrich weg }
  textoff.y := 0;
  CASE modus OF
    1: BEGIN      { (nx-1)·(ny-1) Felder }
      step.x := (pr1.x - pr0.x)/(nx-1);
      step.y := (pr1.y - pr0.y)/(ny-1);
    END;
    2: BEGIN      { (nx-1)·ny Felder }
      step.x := (pr1.x - pr0.x)/(nx-1);
      step.y := (pr1.y - pr0.y)/ny;
      textoff.y := step.y*(1 + gap)/2;
    END;
    3: BEGIN      { nx·(ny-1) Felder }
      step.x := (pr1.x - pr0.x)/nx;
      textoff.x := step.x*(1 + gap)/2;
      step.y := (pr1.y - pr0.y)/(ny-1);
    END;
    4: BEGIN      { nx·ny Felder }
      step.x := (pr1.x - pr0.x)/nx;
      textoff.x := step.x*(1 + gap)/2;
      step.y := (pr1.y - pr0.y)/ny;
      textoff.y := step.y*(1 + gap)/2;
    END;
  END;
  IF abbruch THEN BEGIN
    notaus := True; Exit END;
  { z-Linien zuerst zeichnen, da sie nur Ziffern tragen, die ruhig
  beschädigt werden dürfen: }
  { Zunächst herausfinden, wieviele Nachkommastellen und Gesamtstellen für die
  Ausgabe der z-Werte benötigt werden. }
  nkst := 0;
  WHILE skz*Pwr10(nkst)<1 DO  Inc(nkst);
  gst := 1; IF pr0.z<0 THEN gst := 2;
  WHILE pr1.z/Pwr10(gst)>1 DO  Inc(gst);
  WHILE -pr0.z/Pwr10(gst)>0.1 DO  Inc(gst);
  IF nkst>0 THEN
    gst := gst + nkst + 1;
  FOR i := Round(pr0.z/skz + 0.5) TO Round(pr1.z/skz - 0.5) DO BEGIN
    pr.z := i*skz;
    f77fix(pr.z,gst,nkst,bez);
    pr.x := pr0.x; pr.y := pr1.y;
    projektion(pr,p0);
    Move(Rast,Round(p0.x),Round(p0.y));
    pr.y := pr0.y;
    projektion(pr,p0);
    IF ernst THEN Draw(Rast,Round(p0.x),Round(p0.y));
    pr.x := pr1.x;
    projektion(pr,p0);
    IF ernst THEN Draw(Rast,Round(p0.x),Round(p0.y));
    pr.x := pr.x + rd/10;
    projektion(pr,p1);
    schrift(bez,1,p0,p1,ernst);
  END;
  { und noch den Titel der z-Achse }
  pr.z := pr1.z+skz;
  projektion(pr,p1);
  pr.x := pr.x - rd/10;
  projektion(pr,p0);
  schrift(einheit,-length(einheit) div 2,p0,p1,ernst);
  IF abbruch THEN BEGIN
    notaus := True; Exit END;
  { x-Linien }
  FOR i := 1 TO nx DO IF (xtitel[i]<>'') OR (modus IN [3,4]) THEN BEGIN
    pr.x := pr0.x + step.x*(i - 1);
    pr.y := pr0.y; pr.z := pr1.z;
    projektion(pr,p0);
    Move(Rast,Round(p0.x),Round(p0.y));
    pr.z := pr0.z;
    projektion(pr,p0);
    IF ernst THEN Draw(Rast,Round(p0.x),Round(p0.y));
    pr.y := pr1.y;
    projektion(pr,p0);
    IF ernst THEN Draw(Rast,Round(p0.x),Round(p0.y));
    pr.x := pr.x + textoff.x;
    projektion(pr,p0);
    pr.y := pr.y + rd/10;
    projektion(pr,p1);
    bez := xtitel[i];
    schrift(bez,1,p0,p1,ernst);
  END;
  IF abbruch THEN BEGIN
    notaus := True; Exit END;
  { y-Linien, das gleiche }
  FOR i := 1 TO ny DO IF (ytitel[i]<>'') OR (modus IN [2,4]) THEN BEGIN
    pr.y := pr0.y + step.y*(i - 1);
    pr.x := pr0.x; pr.z := pr1.z;
    projektion(pr,p0);
    Move(Rast,Round(p0.x),Round(p0.y));
    pr.z := pr0.z;
    projektion(pr,p0);
    IF ernst THEN Draw(Rast,Round(p0.x),Round(p0.y));
    pr.x := pr1.x;
    projektion(pr,p0);
    IF ernst THEN Draw(Rast,Round(p0.x),Round(p0.y));
    pr.y := pr.y + textoff.y;
    projektion(pr,p0);
    pr.x := pr.x + rd/10;
    projektion(pr,p1);
    bez := ytitel[i];
    schrift(bez,1,p0,p1,ernst);
  END;
END;

PROCEDURE zentriere;
{ wählt Offset und Vergrößerung für optimalen Bildausschnitt }
VAR i: integer;
    frei: fl_vekt;
BEGIN
  off.x := 0;
  off.y := 0;
  mag := 1;
  quader;       { die acht Eckpunkte projizieren und Rahmen ermitteln }
  frei.x := MyWindow^.GZZWidth-2*grace;
  frei.y := MyWindow^.GZZHeight-2*grace;
  mag := min(frei.x/(pfl1.x-pfl0.x), frei.y/(pfl1.y-pfl0.y));
  off.x := grace - mag*pfl0.x + (frei.x-mag*(pfl1.x-pfl0.x))/2;
  off.y := grace - mag*pfl0.y + (frei.y-mag*(pfl1.y-pfl0.y))/2;
END;

PROCEDURE zentr_m_text;
{ Durch Beschriftung nichtlinearer Zusammenhang zwischen Vergrößerung und
Platzbedarf der Zeichnung, darum Iteration nach Regula Falsi für den
Überstand fehler in Abhängigkeit von mag }
VAR fehler1,fehler2,mag0,mag1,mag2: real;
    frei: fl_vekt;
BEGIN
  frei.x := MyWindow^.GZZWidth-2*grace;
  frei.y := MyWindow^.GZZHeight-2*grace;
  zentriere;
  mag1 := mag;          { 1. Näherung für mag }
  mag0 := mag;
  quader;
  skalen(False);        { Wirkung ausprobieren }
  IF notaus THEN
    Exit;
  fehler1 := max((pfl1.x-pfl0.x)/frei.x, (pfl1.y-pfl0.y)/frei.y) - 1;
  mag2 := mag1/(fehler1 + 1);
  { 2. Näherung, tut so als wäre Größe trotz Text proportional zu mag }
  mag := mag2;
  quader;
  skalen(False);
  IF notaus THEN
    Exit;
  fehler2 := max((pfl1.x-pfl0.x)/frei.x, (pfl1.y-pfl0.y)/frei.y) - 1;
  mag := (fehler2*mag1 - fehler1*mag2)/(fehler2 - fehler1); { Regula Falsi }
  { winzige (oder sogar kopfstehende) Bilder vermeiden: }
  IF mag<mag0/3 THEN mag := mag0/3;
  off.x := 0;
  off.y := 0;
  quader;
  skalen(False);
  off.x := grace - pfl0.x + (frei.x - (pfl1.x-pfl0.x))/2;
  off.y := grace - pfl0.y + (frei.y - (pfl1.y-pfl0.y))/2;
END;

PROCEDURE netz;
{ Oberfläche als (nx-1)·(ny-1) Vierecke zeichnen. Diagonale Vorgehensweise, }
{ um Fehler bei Hidden-Line zu vermeiden. Die beiden letzten Zeilen werden }
{ zwischengespeichert, damit nicht jeder Punkt viermal projiziert werden }
{ muß. }
VAR dx,dy: real;
    i,ii,j,k,akt,vor1,vor2: integer;
    p: ARRAY[1..4] OF fl_vekt;
    zeile: ARRAY[1..3,1..nmax] OF fl_vekt;
    pr: r_vekt;
    status: LongInt;
BEGIN
  dx := (pr1.x - pr0.x)/(nx-1);
  dy := (pr1.y - pr0.y)/(ny-1);
  akt := 1;
  vor2 := 2;
  vor1 := 3;
  FOR ii := 1 TO nx+ny DO BEGIN
    FOR j := 1 TO ii DO BEGIN
      i := 1+ii-j;
      IF (i<=nx) AND (j<=ny) THEN BEGIN
        pr.x := pr0.x + (i-1)*dx;
        pr.y := pr0.y + (j-1)*dy;
        pr.z := zr[i,j];
        projektion(pr,zeile[akt,j]);
        IF (i>1) AND (j>1) THEN BEGIN
          p[1] := zeile[akt,j];
          p[2] := zeile[vor1,j];
          p[3] := zeile[vor2,j-1];
          p[4] := zeile[vor1,j-1];
          SetDrMd(Rast,JAM1);
          status := AreaMove(Rast,Round(p[4].x),Round(p[4].y));
          FOR k := 1 TO 4 DO
            status := AreaDraw(Rast,Round(p[k].x),Round(p[k].y));
          SetAPen(Rast,3);      { rot }
          status := AreaEnd(Rast);
          SetAPen(Rast,1);      { schwarz }
          Move(Rast,Round(p[4].x),Round(p[4].y));
          FOR k := 1 TO 4 DO
            Draw(Rast,Round(p[k].x),Round(p[k].y));
        END;
      END;
      IF abbruch THEN
        Exit;
    END;
    vor2 := vor1;
    vor1 := akt;
    akt := akt mod 3 + 1;
  END;
END;

PROCEDURE saeulen;
{ Stellt zr(nx,ny) als Bänder in x-Richtung (modus=2), Bänder in y-Richtung
(modus=3) bzw. als Säulendiagramm (modus=4) dar. Diagonale Vorgehensweise
hilft diesmal leider nicht, darum Hiddenline-Fehler bei Betrachtung aus
x- oder y-Achsenrichtung. }
VAR nxvar,nyvar,i,j,k: integer;
    dx,dy,a,b,c: real;
    pr: r_vekt;
    p: ARRAY[1..8] OF fl_vekt;
    status: LongInt;

  PROCEDURE viereck(i1,i2,i3,i4: integer; flfarb,lfarb: long);
  VAR i: ARRAY[1..4] OF integer;
      k: integer;
  BEGIN
    i[1] := i1; i[2] := i2; i[3] := i3; i[4] := i4;
    SetDrMd(Rast,JAM1);
    SetAPen(Rast,flfarb);
    status := AreaMove(Rast,Round(p[i4].x),Round(p[i4].y));
    FOR k := 1 TO 4 DO
      status := AreaDraw(Rast,Round(p[i[k]].x),Round(p[i[k]].y));
    status := AreaEnd(Rast);
    SetAPen(Rast,lfarb);
    Move(Rast,Round(p[i4].x),Round(p[i4].y));
    FOR k := 1 TO 4 DO
      Draw(Rast,Round(p[i[k]].x),Round(p[i[k]].y));
  END;

BEGIN
  { Oberfläche als ny bzw. nx Bänder zu (nx-1) bzw. (ny-1) Flächen zeichnen
  bzw. als nx·ny rechteckige Säulen }
  IF modus=2 THEN nxvar := nx-1  ELSE nxvar := nx;
  IF modus=3 THEN nyvar := ny-1  ELSE nyvar := ny;
  dx := (pr1.x - pr0.x)/nxvar;
  dy := (pr1.y - pr0.y)/nyvar;
  FOR i := 1 TO nxvar DO
    FOR j := 1 TO nyvar DO BEGIN
      { 8 Eckpunkte der Säule bzw. schiefen Säule projizieren }
      FOR k := 1 TO 8 DO BEGIN
        { Ecke auswählen als Tripel von (0 oder 1) }
        a := (k-1) mod 2;
        b := ((k-1) div 2) mod 2;
        c := ((k-1) div 4) mod 2;
        IF modus<>2 THEN
          a := max(a,gap);
        IF modus<>3 THEN
          b := max(b,gap);
        pr.x := pr0.x + (i-1+a)*dx;
        pr.y := pr0.y + (j-1+b)*dy;
        CASE modus OF
          2: pr.z := pr0.z + c*(zr[i+Round(a),j]-pr0.z);
          3: pr.z := pr0.z + c*(zr[i,j+Round(b)]-pr0.z);
          4: pr.z := pr0.z + c*(zr[i,j]-pr0.z);
        END;
        projektion(pr,p[k]);
      END;
      { Deckfläche und zwei vordere Seitenflächen zeichnen }
      viereck(5,6,8,7, 3,1);    { rot, schwarz }
      viereck(2,4,8,6, 2,1);    { weiß, schwarz }
      viereck(3,4,8,7, 2,1);
      IF abbruch THEN
        Exit;
    END;
END;

PROCEDURE darstellen;
VAR i: integer;
BEGIN
  SetRast(Rast,0);    { Bildschirm löschen }
  notaus := False;      { für Abbruchüberprüfung in den Unterrroutinen }
  zentr_m_text;
  IF notaus THEN BEGIN
    skizze; Exit END;
  skalen(True);
  IF modus=1 THEN
    netz          { Netzdiagramm }
  ELSE
    saeulen;      { behandelt Säulen- und Bänderdiagramme zusammen }
END;

PROCEDURE refresh;
{ Bilschirm neu aufbauen, für quickdraw=True: Skizze, sonst Zeichnung }
BEGIN
  settitles(True);
  IF quickdraw THEN BEGIN
    zentriere;
    skizze;
  END ELSE
    darstellen;
END;

{ *#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#* }
{ *#*#*#*#*#*#*#*#*#*#*#*#*  Datenverwaltung  *#*#*#*#*#*#*#*#*#*#*#*#*#* }
{ *#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#* }

PROCEDURE swapxy;
{ Tabelle im Speicher transponieren }
VAR i, j, m: integer;
    hilf: chefstring;
BEGIN
  IF nx>ny THEN m := nx ELSE m := ny;
  FOR i := 1 TO m DO BEGIN
    Exchange(xtitel[i],ytitel[i]);
    FOR j := 1 TO i-1 DO
      Exchange(zr[i,j], zr[j,i]);
  END;
  Exchange(nx, ny);
  Exchange(pr0.x, pr0.y);
  Exchange(pr1.x, pr1.y);
  IF modus=2 THEN
    modus := 3
  ELSE IF modus=3 THEN
    modus := 2;
  vektoren;
END;

PROCEDURE mirrorx;
{ Reihenfolge der Spalten umkehren }
VAR i, j: integer;
    hilf: chefstring;
BEGIN
  FOR i := 1 TO nx div 2 DO BEGIN
    exchange(xtitel[i], xtitel[nx+1-i]);
    FOR j := 1 TO ny DO
      exchange(zr[i,j], zr[nx+1-i,j]);
  END;
END;

PROCEDURE mirrory;
{ Reihenfolge der Zeilen umkehren }
VAR i, j: integer;
    hilf: chefstring;
BEGIN
  FOR j := 1 TO ny div 2 DO BEGIN
    exchange(ytitel[j], ytitel[ny+1-j]);
    FOR i := 1 TO nx DO
      exchange(zr[i,j], zr[i,ny-j+1]);
  END;
END;

PROCEDURE makeborder(n, x0,y0, x1,y1: Integer; raised: Boolean);
{ legt in Coords[n] und Bord[2*n-1], Bord[2*n] einen 3D-Rahmen an }
VAR c1,c2: Word;
BEGIN
  c1 := 1; c2 := 1;
  IF raised THEN c2 := 2 ELSE c1 := 2;
  Coords[n] := IntArr10(x0,y1, x1,y1, x1,y0, x0,y0, x0,y1);
  Bord[2*n-1] := Border(0,0,c1,0,JAM1,3,^Coords[n][1],^Bord[2*n]);
  Bord[2*n]   := Border(0,0,c2,0,JAM1,3,^Coords[n][5],Nil);
END;

FUNCTION move_row(line: Boolean): Boolean;
{ Requester öffnen, Zeile bzw. Spalte verschieben lassen }
{ Rückgabewert: Datenbestand wirklich geändert? }
TYPE strarr=ARRAY[1..4] OF Str;
VAR texte: strarr;
    l: ARRAY[1..4] OF Integer;
    gad: p_Gadget;
    ende,genehmigt,soso: Boolean;
    warte,eventclass: Long;
    i,j,di,n,x1,x2,lmax,off: Integer;
    b,h: Word;
    buf: ARRAY[1..4] OF chefstring;
BEGIN
  move_row := False;
  IF line THEN BEGIN
    n := ny; buf[3] := ytitel[1]; buf[4] := ytitel[n];
  END ELSE BEGIN
    n := nx; buf[3] := xtitel[1]; buf[4] := xtitel[n];
  END;
  buf[1] := '1'; buf[2] := IntStr(n);
  ubuf := '';
  {$ if def DEUTSCH }
  texte := strarr('Spalte verschieben:','an Position:','OK','Abbruch');
  IF line THEN texte[1] := 'Zeile verschieben:';
  {$ else }
  texte := strarr('Move column:','To position:','OK','Cancel');
  IF line THEN texte[1] := 'Move line:';
  {$ endif }
  FOR i := 1 TO 4 DO l[i] := Length(texte[i]);
  lmax := l[1]; IF l[2]>l[1] THEN lmax := l[2];
  x1 := 15 + (lmax+1)*charx; x2 := x1 + 5*8;
  b := x1 + 20*8 + 15;
  h := 44 + 3*chary;
  off := (chary-8) DIV 2;
  FOR i := 1 TO 2 DO
    ITxt[i] := IntuiText(1,2,JAM1,15,12+(i-1)*(chary+8),Nil,texte[i],^ITxt[i+1]);
  ITxt[2].NextText := Nil;
  FOR i := 1 TO 2 DO
    ReqGad[i] := Gadget(^ReqGad[i+1],x1,12+(i-1)*(chary+8)+off,4*8,8,
        GADGHCOMP,RELVERIFY OR _LONGINT,STRGADGET OR REQGADGET,
        ^Bord[1],Nil,Nil,0,^StrInf[i],i,Nil);
  FOR i := 3 TO 4 DO
    ReqGad[i] := Gadget(^ReqGad[i+1],x2,12+(i-3)*(chary+8)+off,15*8,8,GADGHCOMP,
        RELVERIFY,STRGADGET OR REQGADGET,Nil, Nil,Nil,0,^StrInf[i],i,Nil);
  FOR i := 1 TO 4 DO
    StrInf[i] := StringInfo(^buf[i],^ubuf,0,39,0,0,0,0,0,0,Nil,0,Nil);
  FOR i := 5 TO 6 DO
    ReqGad[i] := Gadget(^ReqGad[i+1],10,32+2*chary,9*charx+2,chary+2,
        GADGHCOMP, RELVERIFY OR ENDGADGET, BOOLGADGET OR REQGADGET,
        ^Bord[3],Nil,^ITxt[i-2],0,Nil,i,Nil);
  FOR i := 3 TO 4 DO
    ITxt[i] := IntuiText(1,2,JAM1,1+(9-l[i])*charx DIV 2,1,Nil,texte[i],Nil);
  ReqGad[6].LeftEdge := b-(11+9*charx);
  ReqGad[6].NextGadget := Nil;
  makeborder(1, -1,-1, 4*8, 8, False);
  makeborder(2,  0, 0, 9*charx+1,chary+1, True);
  makeborder(3,  0, 0, b-1,h-1, True);
  MyRequest := Requester(Nil,40,30,b,h,0,0,^ReqGad[1],^Bord[5],
                         ^ITxt[1],0,3,Nil,muell,Nil,Nil,Nil,muell);
  IF Request(^MyRequest,MyWindow) THEN BEGIN
    { Ereignisse abfragen }
    ende := False;
    REPEAT
      warte := Wait(-1);
      REPEAT              { Schleife, da mehrere Ereignisse möglich }
        MyMsg := Get_Msg(Upt);
        IF MyMsg <> Nil THEN BEGIN
          eventclass := MyMsg^.Class;
          gad := MyMsg^.IAddress;
          Reply_Msg(MyMsg);             { so schnell wie möglich antworten! }
          IF eventclass=REQSET THEN
            soso := ActivateGadget(^ReqGad[1],MyWindow,^MyRequest);
          IF eventclass=GADGETUP THEN
            CASE gad^.GadgetID OF
              1,2: BEGIN
                i := gad^.GadgetID
                j := StrInf[i].LongInt;
                IF j<1 THEN j := 1; IF j>n THEN j := n;
                buf[i] := IntStr(j);
                IF line THEN buf[i+2] := ytitel[j]
                  ELSE buf[i+2] := xtitel[j];
                IF i=1 THEN x1 := j ELSE x2 := j;
                soso := ActivateGadget(^ReqGad[3-i],MyWindow,^MyRequest);
                RefreshGadgets(^ReqGad[1],MyWindow,^MyRequest);
              END;
              5: genehmigt := True;
              6: genehmigt := False;
              OTHERWISE;
            END;
          IF eventclass=REQCLEAR THEN ende := True;
        END;
      UNTIL MyMsg = Nil;
    UNTIL ende;
    IF genehmigt THEN BEGIN
      IF x1<x2 THEN di := 1 ELSE di := -1;
      i := x1; WHILE i<>x2 DO BEGIN
        IF line THEN BEGIN
          Exchange(ytitel[i],ytitel[i+di]);
          FOR j := 1 TO nx DO Exchange(zr[j,i],zr[j,i+di]);
        END ELSE BEGIN
          Exchange(xtitel[i],xtitel[i+di]);
          FOR j := 1 TO ny DO Exchange(zr[i,j],zr[i+di,j]);
        END;
        i := i + di;
      END;
      move_row := True;
    END;
  END;
END;

{ *#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#* }
{ *#*#*#*#*#*#*#*#*#*#*#*#*#*  Abmessungen  *#*#*#*#*#*#*#*#*#*#*#*#*#*#* }
{ *#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#* }

PROCEDURE merken;
BEGIN
  pr0m := pr0; pr1m := pr1;
END;

PROCEDURE erinnern;
BEGIN
  pr0 := pr0m; pr1 := pr1m;
  vektoren;
END;

PROCEDURE tauschen;
VAR hilf: r_vekt;
BEGIN
  hilf := pr0m; pr0m := pr0; pr0 := hilf;
  hilf := pr1m; pr1m := pr1; pr1 := hilf;
  vektoren;
END;

PROCEDURE best_guess;
{ Abmessungen und Skalenteilung "optimal" einstellen }
VAR zmin,zmax,dz: Real;
    i,j: Integer;
BEGIN
  zmin := zr[1,1]; zmax := zr[1,1];
  FOR j := 1 TO ny DO
    FOR i := 1 TO nx DO BEGIN
      IF zr[i,j]<zmin THEN zmin := zr[i,j];
      IF zr[i,j]>zmax THEN zmax := zr[i,j];
    END;
  { Skalenteilung auf der z-Achse }
  dz := zmax-zmin;
  IF dz=0 THEN dz := 1;
  IF (zmin>0) AND (zmin<dz/2) THEN
    pr0.z := 0
  ELSE
    pr0.z := zmin-0.05*dz;
  skz := glatt(dz/10);
  pr1.z := zmax+0.05*dz;
  pr0.x := 0;
  pr1.x := 1.1*dz*sqrt(nx/ny);
  pr0.y := 0;
  pr1.y := 1.1*dz*sqrt(ny/nx);
  vektoren;
END;

FUNCTION bereichstest: Boolean;
{ überprüfen, ob neugewählte Abmessungen mit der Datei verträglich sind }
{ und erzeugt ggf. einen Requester }
TYPE strarr=ARRAY[1..4] OF Str;
VAR texte: strarr;
    egal: Boolean;
    meldung,buf: chefstring;
    zmin,zmax,dz: Real;
    lmax,i,j: Integer;
BEGIN
  bereichstest := True;
  zmin := zr[1,1]; zmax := zr[1,1];
  FOR j := 1 TO ny DO
    FOR i := 1 TO nx DO BEGIN
      IF zr[i,j]<zmin THEN zmin := zr[i,j];
      IF zr[i,j]>zmax THEN zmax := zr[i,j];
    END;
  dz := zmax - zmin;
  IF max(zmax-pr1.z,pr0.z-zmin)>dz/10 THEN BEGIN
    bereichstest := False;
    ftoa(zmin,4,meldung);
    ftoa(zmax,4,buf);
    settitles(False);
    {$ if def DEUTSCH }
    meldung := 'für Wertebereich '+meldung+' .. '+buf;
    texte := strarr('Gewählter z-Ausschnitt zu klein',meldung,
             'Ändern Sie das besser.',' Mach Ich ');
    {$ else }
    meldung := 'for data range '+meldung+' .. '+buf;
    texte := strarr('Chosen z-range not sufficient',meldung,
             'You''d better fix that.',' Aye, Sir! ');
    {$ endif }
    lmax := 0; FOR i := 1 TO 3 DO
      IF lmax<Length(texte[i]) THEN lmax := Length(texte[i]);
    FOR i := 1 TO 3 DO
      ITxt[i] := IntuiText(2,1,JAM1,(2+lmax-Length(texte[i]))*charx DIV 2,
               i*(chary+2),Nil,texte[i],^ITxt[i+1]);
    ITxt[3].NextText := Nil;
    ITxt[4] := Intuitext(2,1,JAM1,6,3,Nil,texte[4],Nil);
    egal := AutoRequest(MyWindow,^ITxt[1],Nil,^ITxt[4],0,0,
            (6+lmax)*charx,5*(chary+2)+30);
    settitles(True);
  END;
END;

FUNCTION gr_aendern: Boolean;
{ Requester öffnen, z-Bereich, Skalenteilung, x/z und y/z einlesen }
{ Rückgabewert: Abmessungen wirklich geändert? }
TYPE strarr=ARRAY[1..7] OF Str;
VAR texte: strarr;
    l: ARRAY[1..7] OF Integer;
    xz,yz: Real;
    gad: p_Gadget;
    ende,genehmigt,soso: Boolean;
    warte,eventclass: Long;
    i,x1,x2,lmax,off: Integer;
    b,h: Word;
    buf: ARRAY[1..5] OF chefstring;
BEGIN
  gr_aendern := False;
  xz := (pr1.x-pr0.x)/(pr1.z-pr0.z);
  yz := (pr1.y-pr0.y)/(pr1.z-pr0.z);
  ftoa(pr0.z,4,buf[1]);
  ftoa(pr1.z,4,buf[2]);
  ftoa(skz,4,buf[3]);
  ftoa(xz,4,buf[4]);
  ftoa(yz,4,buf[5]);
  ubuf := '';
  {$ if def DEUTSCH }
  texte := strarr('Wertebereich:','Skalenteilung:','relative Achsenlängen:',
           'x/z:','y/z:','OK','Abbruch');
  {$ else }
  texte := strarr('z-Range:','z-Step:','Relative length of axes:',
           'x/z:','y/z:','OK','Cancel');
  {$ endif }
  FOR i := 1 TO 7 DO l[i] := Length(texte[i]);
  lmax := l[1]; IF l[2]>l[1] THEN lmax := l[2];
  x1 := 15 + (lmax+1)*charx;
  lmax := l[4]; IF l[5]>l[4] THEN lmax := l[5];
  x2 := 15 + (lmax+1)*charx;
  b := 30 + l[3]*charx; IF b<x1+95 THEN b := x1+95;
  h := 84 + 7*chary;
  off := (chary-8) DIV 2;
  FOR i := 1 TO 5 DO BEGIN
    ITxt[i] := IntuiText(1,2,JAM1,15,20+i*(chary+8),Nil,texte[i],^ITxt[i+1]);
    ReqGad[i] := Gadget(^ReqGad[i+1],x1,12+(i-1)*(chary+8)+off,80,8,GADGHCOMP,
        RELVERIFY,STRGADGET OR REQGADGET,^Bord[1],Nil,Nil,0,^StrInf[i],i,Nil);
    StrInf[i] := StringInfo(^buf[i],^ubuf,0,20,0,0,0,0,0,0,Nil,0,Nil);
  END;
  ITxt[1].TopEdge := 12;
  ITxt[2].TopEdge := 28 + 2*chary;
  FOR i := 4 TO 5 DO BEGIN
    ReqGad[i].LeftEdge := x2; ReqGad[i].TopEdge := 20+i*(chary+8)+off; END;
  ITxt[5].NextText := Nil;
  FOR i := 6 TO 7 DO BEGIN
    ReqGad[i] := Gadget(^ReqGad[i+1],10,72+6*chary,9*charx+2,chary+2,
        GADGHCOMP, RELVERIFY OR ENDGADGET, BOOLGADGET OR REQGADGET,
        ^Bord[3],Nil,^ITxt[i],0,Nil,i,Nil);
    ITxt[i] := IntuiText(1,2,JAM1,1+(9-l[i])*charx DIV 2,1,Nil,texte[i],Nil);
  END;
  ReqGad[7].LeftEdge := b-(11+9*charx);
  ReqGad[7].NextGadget := Nil;
  makeborder(1, -1,-1, 80, 8, False);
  makeborder(2,  0, 0, 9*charx+1,chary+1, True);
  makeborder(3,  0, 0, b-1,h-1, True);
  MyRequest := Requester(Nil,40,30,b,h,0,0,^ReqGad[1],^Bord[5],
                         ^ITxt[1],0,3,Nil,muell,Nil,Nil,Nil,muell);
  IF Request(^MyRequest,MyWindow) THEN BEGIN
    { Ereignisse abfragen }
    ende := False;
    REPEAT
      warte := Wait(-1);
      REPEAT              { Schleife, da mehrere Ereignisse möglich }
        MyMsg := Get_Msg(Upt);
        IF MyMsg <> Nil THEN BEGIN
          eventclass := MyMsg^.Class;
          gad := MyMsg^.IAddress;
          Reply_Msg(MyMsg);             { so schnell wie möglich antworten! }
          IF eventclass=REQSET THEN
            soso := ActivateGadget(^ReqGad[1],MyWindow,^MyRequest);
          IF eventclass=GADGETUP THEN
            CASE gad^.GadgetID OF
              1..4: soso := ActivateGadget(gad^.NextGadget,MyWindow,^MyRequest);
              6: genehmigt := True;
              7: genehmigt := False;
              OTHERWISE;
            END;
          IF eventclass=REQCLEAR THEN ende := True;
        END;
      UNTIL MyMsg = Nil;
    UNTIL ende;
    IF genehmigt THEN BEGIN
      val(buf[1],pr0.z,i);
      val(buf[2],pr1.z,i);
      IF pr1.z=pr0.z THEN pr1.z := pr0.z + 1;
      IF pr1.z<pr0.z THEN BEGIN
        xz := pr0.z; pr0.z := pr1.z; pr1.z := xz;
      END;
      val(buf[3],skz,i);
      IF skz<=0 THEN skz := glatt(pr1.z-pr0.z);
      val(buf[4],xz,i);
      IF xz=0 THEN xz := 1;
      pr0.x := 0;
      pr1.x := abs(xz)*(pr1.z-pr0.z);
      val(buf[5],yz,i);
      IF yz=0 THEN yz := 1;
      pr0.y := 0;
      pr1.y := abs(yz)*(pr1.z-pr0.z);
      vektoren;
      gr_aendern := True;
    END;
  END;
END;

{ *#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#* }
{ *#*#*#*#*#*#*#*#*#*#*#*#  diverse Requester  #*#*#*#*#*#*#*#*#*#*#*#*#* }
{ *#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#* }

PROCEDURE infotext;
TYPE strarr=ARRAY[1..5] OF Str;
VAR texte: strarr;
    egal: Boolean;
    i,lmax: Integer;
BEGIN
  settitles(False);
  {$ if def DEUTSCH }
  texte := strarr('Autor: Wilhelm Nöker','Hertastr. 8 / D-44388 Dortmund',
           'Compiler: KICK-Pascal 2.12','von MAXON Computer',' Verstanden ');
  {$ else }
  texte := strarr('Author: Wilhelm Nöker','Hertastr. 8 / D-44388 Dortmund',
           'Compiler: KICK-Pascal 2.12','by MAXON Computer',' Roger ');
  {$ endif }
  lmax := 0; FOR i := 1 TO 4 DO
    IF lmax<Length(texte[i]) THEN lmax := Length(texte[i]);
  FOR i := 1 TO 4 DO
    ITxt[i] := IntuiText(2,1,JAM1,(2+lmax-Length(texte[i]))*charx DIV 2,
               i*(chary+2),Nil,texte[i],^ITxt[i+1]);
  ITxt[4].NextText := Nil;
  FOR i := 3 TO 4 DO
    ITxt[i].TopEdge := ITxt[i].TopEdge + chary DIV 2;
  ITxt[5] := Intuitext(2,1,JAM1,6,3,Nil,texte[5],Nil);
  egal := AutoRequest(MyWindow,^ITxt[1],Nil,^ITxt[5],0,0,
          (6+lmax)*charx,6*(chary+2)+30);
  settitles(True);
END;

PROCEDURE trunc_warning;
{ Informiert den Benutzer, daß nur ein Teil seiner Daten gelesen wurde }
TYPE strarr=ARRAY[1..3] OF Str;
VAR texte: strarr;
    egal: Boolean;
    meldung: chefstring;
    lmax,i,j: Integer;
BEGIN
  settitles(False);
  {$ if def DEUTSCH }
  meldung := 'nur '+IntStr(nx)+' × '+IntStr(ny)+' Daten gelesen!';
  texte := strarr('Zu große Datei,',meldung,' Hmm ');
  {$ else }
  meldung := 'read only '+IntStr(nx)+' × '+IntStr(ny)+' data!';
  texte := strarr('File too large,',meldung,' Oops ');
  {$ endif }
  lmax := 0; FOR i := 1 TO 2 DO
    IF lmax<Length(texte[i]) THEN lmax := Length(texte[i]);
  FOR i := 1 TO 2 DO
    ITxt[i] := IntuiText(2,1,JAM1,(2+lmax-Length(texte[i]))*charx DIV 2,
             i*(chary+2),Nil,texte[i],^ITxt[i+1]);
  ITxt[2].NextText := Nil;
  ITxt[3] := Intuitext(2,1,JAM1,6,3,Nil,texte[3],Nil);
  egal := AutoRequest(MyWindow,^ITxt[1],Nil,^ITxt[3],0,0,
          (6+lmax)*charx,5*(chary+2)+30);
  settitles(True);
END;

PROCEDURE simple_request(msgtext,buttontext: Str);
VAR egal: Boolean;
    b: Integer;
BEGIN
  settitles(False);
  ITxt[1] := IntuiText(2,1,JAM1,10,10,Nil,msgtext,Nil);
  ITxt[2] := IntuiText(2,1,JAM1,6,3,Nil,buttontext,Nil);
  egal := AutoRequest(MyWindow,^ITxt[1],Nil,^ITxt[2],0,0,
          (6+Length(msgtext))*charx,3*(chary+2)+30);
  settitles(True);
END;

PROCEDURE confirm_snap;
VAR vergiss: boolean;
BEGIN
  {$ if def DEUTSCH }
  simple_request('Abmessungen gemerkt',' Danke ');
  {$ else }
  simple_request('Snapshot confirmed',' Thanks ');
  {$ endif }
END;

PROCEDURE dosfehler(nr: integer);
VAR meldung: chefstring;
BEGIN
  {$ if def DEUTSCH }
  meldung := 'DOS-Fehler Nr. '+IntStr(nr);
  simple_request(meldung,' Verstanden ');
  {$ else }
  meldung := 'DOS-error no. '+IntStr(nr);
  simple_request(meldung,' I see ');
  {$ endif }
END;

FUNCTION intreq(vorgabe: Long; hinweis: str80): Long;
{ Requester öffnen und eine Integerzahl einlesen }
VAR ende,soso: boolean;
    warte,eventclass: Long;
    b,h: Word;
    buf: chefstring;
BEGIN
  intreq := vorgabe;
  buf := IntStr(vorgabe);
  ubuf := '';
  b := charx*length(hinweis)+30;  IF b<80+30  THEN b := 80+30;
  h := chary + 8 + 20;
  ITxt[1] := IntuiText(1,3,JAM1,15,6,Nil,hinweis,Nil);
  StrInf[1] := StringInfo(^buf,^ubuf,0,20,0,0,0,0,0,0,Nil,0,Nil);
  ReqGad[1] := Gadget(Nil,(b-80) div 2,chary+12,80,8,GADGHCOMP,
      RELVERIFY OR ENDGADGET OR _LONGINT OR STRINGRIGHT, STRGADGET OR REQGADGET,
      ^Bord[1], Nil,Nil,0,^StrInf[1],2,Nil);
  makeborder(1, -1,-1, 80, 8, False);
  makeborder(2,  0, 0, b-1,h-1, True);
  MyRequest := Requester(Nil,40,30,b,h,0,0,^ReqGad[1],^Bord[3],
                         ^ITxt[1],0,3,Nil,muell,Nil,Nil,Nil,muell);
  IF Request(^MyRequest,MyWindow) THEN BEGIN
    { Ereignisse abfragen }
    ende := False;
    REPEAT
      warte := Wait(-1);
      REPEAT              { Schleife, da mehrere Ereignisse möglich }
        MyMsg := Get_Msg(Upt);
        IF MyMsg <> Nil THEN BEGIN
          eventclass := MyMsg^.Class;
          Reply_Msg(MyMsg);             { so schnell wie möglich antworten! }
          IF eventclass=REQSET THEN
            soso := ActivateGadget(^ReqGad[1],MyWindow,^MyRequest);
          IF eventclass=REQCLEAR THEN ende := True;
        END;
      UNTIL MyMsg = Nil;
    UNTIL ende;
    intreq := StrInf[1].LongInt;
  END;
END;

PROCEDURE korridor_eing;
VAR eing: long;
BEGIN
  {$ if def DEUTSCH }
  eing := intreq(Round(100*gap),'Korridorbreite in %:');
  {$ else }
  eing := intreq(Round(100*gap),'Width of corridors, %:');
  {$ endif }
  IF abs(eing)<100 THEN
    gap := abs(eing)/100;
END;

{ *#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#* }
{ *#*#*#*#*#*#*#*#*#*#*#*#*  Dateioperationen  #*#*#*#*#*#*#*#*#*#*#*#*#* }
{ *#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#* }

FUNCTION fileselect(was_los: str80; speichern: Boolean;
                                   VAR selected: str80): Boolean;
{ Benutzt (wenn vorhanden) den Filerequester der req.library }
VAR i,p,l: Integer;
    Msg: p_IntuiMessage;
    ende: Boolean;
    class: Long;
    b,h: Word;
    buf,ubuf: str80;
BEGIN
  fileselect := False;
  l := Length(selected);
  { selected in pfad und name spalten }
  p := 0; FOR i := 1 TO l DO
    IF selected[i] IN ['/',':'] THEN p := i;
  IF p=0 THEN pfad := '' ELSE pfad := Copy(selected,1,p);
  IF p=l THEN name := '' ELSE name := Copy(selected,p+1,l-p);
  IF ReqBase<>Nil THEN BEGIN  { *** "req.library" benutzen }
    WITH MyFileReq^ DO BEGIN
      VersionNumber := REQVERSION;
      Title := was_los;
      PathName := pfadname;   { Str-Zeiger auf meinen Puffer setzen }
      Dir := pfad;
      _File := name;
      WindowLeftEdge := 128;
      WindowTopEdge := 25;
      Flags := FRQABSOLUTEXYM;
      IF speichern THEN
        Flags := Flags OR FRQSAVINGM
      ELSE
        Flags := Flags OR FRQLOADINGM;
      Hide := '';
      Show := '#?'+ext;
      dirnamescolor := 2;
      devicenamescolor := 2;
    END;
    IF _FileRequester(MyFileReq) THEN BEGIN
      fileselect := True;
      selected := pfadname;
    END;
  END
END;

FUNCTION laden(name: str80): boolean;
{ Datei lesen, gibt bei Fehlern False zurück }
VAR i,j,result,k,hz: integer;
    zeile: Str80;
    dummy: Real;
    truncated: Boolean;
  FUNCTION auf_halde(zeile: str80): Str;
  VAR i: integer;
  BEGIN
    auf_halde := Ptr(^StrHalde[hz]);
    i := 0;
    REPEAT
      Inc(i);
      StrHalde[hz] := zeile[i];
      IF (hz<strspace) THEN Inc(hz);
    UNTIL (i=80) OR (zeile[i]=chr(0));
  END;
BEGIN
  SetPointer(MyWindow, ChipSpc[5], 16, 16, -6, 0);
  Reset(datei,name);
  result := IOResult;
  IF result=0 THEN BEGIN
    laden := True;
    hz := 1;    { Zeiger der String-Halde zurücksetzen }
    ReadLn(datei,titel);
    ReadLn(datei,einheit);
    ReadLn(datei,ny);
    FOR j := 1 TO ny DO BEGIN
      ReadLn(datei,zeile);
      IF j<=nmax THEN ytitel[j] := auf_halde(zeile);
    END;
    ReadLn(datei,nx);
    truncated := False;
    FOR i := 1 TO nx DO BEGIN
      ReadLn(datei,zeile);
      IF i<=nmax THEN xtitel[i] := auf_halde(zeile);
      FOR j := 1 TO ny DO IF (i>nmax) OR (j>nmax) THEN BEGIN
        ReadLn(datei,dummy); truncated := True;
      END ELSE
        ReadLn(datei,zr[i,j]);
      ReadLn(datei,zeile);
    END;
    IF nx>nmax THEN nx := nmax;
    IF ny>nmax THEN ny := nmax;
    best_guess; { Abmessungen und Skalenteilung einstellen }
    IF NOT eof(datei) THEN ReadLn(datei,zeile);
    IF zeile='INFO' THEN BEGIN
      { Parameter lesen }
      ReadLn(datei,pr0.z,skz,pr1.z);
      ReadLn(datei,pr1.x);
      ReadLn(datei,pr1.y);
      ReadLn(datei,modus);
      ReadLn(datei,gap);
      ReadLn(datei,i); schraeg := i<>0;
    END;
    Close(datei);
    vektoren;
    IF truncated THEN trunc_warning;
  END ELSE BEGIN
    dosfehler(result);
    laden := False;
  END;
  ClearPointer(MyWindow);
END;

FUNCTION speichern(name: str80): boolean;
{ Speichern, True, falls erfolgreich. }
VAR i,j,result: integer;
    dz: real;
BEGIN
  SetPointer(MyWindow, ChipSpc[5], 16, 16, -6, 0);
  Rewrite(datei,name);
  result := IOResult;
  IF result=0 THEN BEGIN
    speichern := True;
    WriteLn(datei,titel);
    WriteLn(datei,einheit);
    WriteLn(datei);
    WriteLn(datei,ny);
    FOR j := 1 TO ny DO WriteLn(datei,ytitel[j]);
    WriteLn(datei);
    WriteLn(datei,nx);
    FOR i := 1 TO nx DO BEGIN
      WriteLn(datei,xtitel[i]);
      FOR j := 1 TO ny DO WriteLn(datei,zr[i,j]);
      WriteLn(datei);
    END;
    { Einstellungen mit abspeichern }
    WriteLn(datei,'INFO');
    WriteLn(datei,pr0.z,' ',skz,' ',pr1.z,'  (z0 dz z1)');
    WriteLn(datei,pr1.x-pr0.x,'  (xlen)');
    WriteLn(datei,pr1.y-pr0.y,'  (ylen)');
    WriteLn(datei,modus:2,'  (mode)');
    WriteLn(datei,gap,'  (corridors)');
    WriteLn(datei,Ord(schraeg):2,'  (fancy texts)');
    Close(datei);
  END ELSE BEGIN
    dosfehler(result);
    speichern := False;
  END;
  ClearPointer(MyWindow);
END;

PROCEDURE force_extension(VAR name: str80);
{ An einen Dateinamen die Extension ext='.3D' anhängen, sofern sie noch nicht }
{ existiert. }
VAR konform: boolean;
    i: integer;
BEGIN
  konform := True;
  FOR i := 1 TO length(ext) DO
    IF upcase(ext[i]) <> upcase(name[length(name)-length(ext)+i]) THEN
      konform := False;
  IF NOT konform THEN
    name := name + ext;
END;

{ *#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#* }
{ *#*#*#*#*#*#*#*#*#*#*#*#*# Initialisierungen #*#*#*#*#*#*#*#*#*#*#*#*#* }
{ *#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#* }

PROCEDURE varinit;
BEGIN
  ext := '.3D';
  schraeg := True;
  quickdraw := False;
  modus := 1;
  horiz := 2;   { x/y-Auflösung }
  vert := 2;
  gap := 0.2;   { Zwischenraum zwischen Säulen oder Bändern, 0..1 }
  grace := 2;
  rb := 2.0;    { Beobachterposition in bezogenen Kugelkoordinaten }
  phi := Pi/4;
  theta := Pi/4;
END;

PROCEDURE demodaten;
{ Demodatensatz erzeugen (reiner Unfug). }
VAR i,j: integer;
BEGIN
  filename := 'RAM:Demo.3D';
  titel := 'Star Trek (The Next Generation) cast characters';
  nx := 8;
  ny := 6;
  xtitel[1] := 'Jean-Luc';  xtitel[5] := 'Imzadi';
  xtitel[2] := 'Der Biker'; xtitel[6] := 'Weasley';
  xtitel[3] := 'Data';      xtitel[7] := 'Dr. Crusher';
  xtitel[4] := 'Geordie';   xtitel[8] := 'Microbrain';
  ytitel[1] := '1st Season';  ytitel[2] := '2nd Season';
  ytitel[3] := '3rd Season';  ytitel[4] := '4th Season';
  ytitel[5] := '5th Season';  ytitel[6] := '6th Season';
  pr0.x := 0; pr1.x := 240;
  pr0.y := 0; pr1.y := 180;
  pr0.z := 0; pr1.z := 200; skz := 50;
  einheit := 'Funny Moments';
  FOR i := 1 TO nx DO
    FOR j := 1 TO ny DO
      zr[i,j] := Round(150*Exp(-Sqr(j-2)/4)*Exp(-Sqr(i-4)/8))+Random(50);
  modus := 3;
  vektoren;
END;

{ *#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#* }
{ *#*#*#*#*#*#*#*#*#*#*# System-Initialisierungen *#*#*#*#*#*#*#*#*#*#*#* }
{ *#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#* }

PROCEDURE bilder;
{ Image-Strukturen initialisieren. Wichtig: Der ChipMem-Speicher ChipSpc[] }
{ muß bereits reserviert sein !!! }
{ WB 2.0-Farben: 00: grau, 01: schwarz, 10: weiß, 11: blau }
BEGIN
  ChipSpc[1]^ := WordArr40(
    %0000000000000001,%1111111100000000,
    %0000000000000111,%1111100100000000,
    %0000000000011111,%1110000100000000,
    %0000000001111111,%1000000100000000,
    %0000000111111110,%0000000100000000,
    %0000000111111000,%0000000100000000,
    %0000000001100000,%0000000100000000,
    %0000110000000000,%0000000100000000,
    %0000000000000000,%0000000100000000,
    %1111111111111111,%1111111100000000,
    %1000000000000000,%0100000000000000,
    %1000000000000001,%0000000000000000,
    %1000000000000100,%0000000000000000,
    %1000000000010000,%0000000000000000,
    %1000000001000000,%0000000000000000,
    %1000001100000000,%0000000000000000,
    %1000011111000000,%0000000000000000,
    %1000000000000000,%0000000000000000,
    %1000000000000000,%0000000000000000,
    %0000000000000000,%0000000000000000 )
  Bild[1] := Image(0,1,24,10,2,ChipSpc[1],%11,%00,Nil);
  ChipSpc[2]^ := WordArr40(
    %0000000000000000,%0000000100000000,
    %0000011000000000,%0011100100000000,
    %0000111111000001,%1111000100000000,
    %0000000111101111,%1000000100000000,
    %0000000000111100,%0000000100000000,
    %0000000011100001,%1000000100000000,
    %0000001100000000,%0110000100000000,
    %0000010000000000,%0000100100000000,
    %0000000000000000,%0000000100000000,
    %1111111111111111,%1111111100000000,
    %1000000000000000,%0000000000000000,
    %1000000000000000,%0000000000000000,
    %1000000000000000,%0000000000000000,
    %1000000000000000,%0000000000000000,
    %1000000000000000,%0000000000000000,
    %1000000000000000,%0000000000000000,
    %1000000000000000,%0000000000000000,
    %1000000000000000,%0000000000000000,
    %1000000000000000,%0000000000000000,
    %0000000000000000,%0000000000000000 )
  Bild[2] := Image(0,1,24,10,2,ChipSpc[2],%11,%00,Nil);
  ChipSpc[3]^ := WordArr40(
    %0000000000000000,%0000000100000000,
    %0000001111000011,%1100000100000000,
    %0000001111011011,%1100000100000000,
    %0000011111111111,%1110000100000000,
    %0000011111111111,%1110000100000000,
    %0000001111011011,%1100000100000000,
    %0000011111000011,%1110000100000000,
    %0000011111100111,%1110000100000000,
    %0000000000000000,%0000000100000000,
    %1111111111111111,%1111111100000000,
    %1000000000000000,%0000000000000000,
    %1000000000000000,%0000000000000000,
    %1000000101001001,%0100000000000000,
    %1000000000001000,%0000000000000000,
    %1000000000001000,%0000000000000000,
    %1000000000001000,%0000000000000000,
    %1000000000000000,%0000000000000000,
    %1000000000000000,%0000000000000000,
    %1000000000000000,%0000000000000000,
    %0000000000000000,%0000000000000000 )
  Bild[3] := Image(0,1,24,10,2,ChipSpc[3],%11,%00,Nil);
  ChipSpc[4]^ := WordArr40(
    %0000000000000000,%0000000100000000,
    %0000000111111100,%0000000100000000,
    %0000011100000111,%0000000100000000,
    %0000110001000001,%1000000100000000,
    %0000110011000001,%1000000100000000,
    %0000110000000001,%1000000100000000,
    %0000011100000111,%0000000100000000,
    %0000000111111100,%0000000100000000,
    %0000000000000000,%0000000100000000,
    %1111111111111111,%1111111100000000,
    %1000000000000000,%0000000000000000,
    %1000000000000000,%0000000000000000,
    %1000000000000000,%0000000000000000,
    %1000000001000000,%0000000000000000,
    %1000000011000000,%0000000000000000,
    %1000000000000000,%0000000000000000,
    %1000000000000000,%1000000000000000,
    %1000000000000001,%1110000000000000,
    %1000000000000000,%0111100000000000,
    %1000000000000000,%0000000000000000 )
  Bild[4] := Image(0,1,24,10,2,ChipSpc[4],%11,%00,Nil);
  ChipSpc[5]^ := WordArr40(
      $0000,$0000,
      $0400,$07C0,$0000,$07C0,$0100,$0380,$0000,$07E0,
      $07C0,$1FF8,$1FF0,$3FEC,$3FF8,$7FDE,$3FF8,$7FBE,
      $7FFC,$FF7F,$7EFC,$FFFF,$7FFC,$FFFF,$3FF8,$7FFE,
      $3FF8,$7FFE,$1FF0,$3FFC,$07C0,$1FF8,$0000,$07E0,
      $0000,$0000, 0,0,0,0   { Busy-Pointer, ist eigentlich WordArr36 ... }
    );
END;

PROCEDURE gadgetsetup(bleft,btop,bright,bbot: byte);
{ Gadgets aufbauen, Image-Strukturen müssen bereits initialisiert sein! }
{ Ihr Aussehen wird soweit möglich den angegebenen Randstärken des Fensters }
{ angepaßt. }
VAR i: Integer;
BEGIN
  { Gadgets in der Titelleiste }
  FOR i := 1 TO 4 DO BEGIN
    WinGad[i] := Gadget(^WinGad[i+1],-172+24*i,0, 24,11,
        GADGHCOMP OR GRELRIGHT OR GADGIMAGE,
        RELVERIFY OR GADGIMMEDIATE OR TOPBORDER, BOOLGADGET OR GZZGADGET,
        ^Bild[i], Nil,Nil,0,Nil, i, Nil);
    IF (btop=10) OR (btop=12) THEN BEGIN
      { Die beiden Fälle, in denen ein 11 Pixel hohes Gadget extrem mies }
      { aussehen würde (schließen den Fall Kick 1.3 mit ein ;-) }
      WinGad[i].Height := btop; Bild[i].Height := 9;
    END ELSE
      Bild[i].Height := 10;
  END;
  { Proportionalgadgets im rechten und unteren Rand }
  WinGad[5] := Gadget(^WinGad[6],-bright+2,btop,bright-2,-(btop+bbot)+1,
               GADGHCOMP OR GRELHEIGHT OR GRELRIGHT,
               GADGIMMEDIATE OR RIGHTBORDER,PROPGADGET OR GZZGADGET,
               ^MoveDat1,Nil,Nil,0,^PropInf1,5,Nil);
  PropInf1 := PropInfo(FREEVERT OR AUTOKNOB,$8000,$8000,0,$8000 DIV 5,
                       0,0,0,0,0,0);
  WinGad[6] := Gadget(Nil,bleft,-bbot+2,-(bleft+bright)+1,bbot-2,
               GADGHCOMP OR GRELWIDTH OR GRELBOTTOM,
               GADGIMMEDIATE OR BOTTOMBORDER,PROPGADGET OR GZZGADGET,
               ^MoveDat2,Nil,Nil,0,^PropInf2,6,Nil);
  PropInf2 := PropInfo(FREEHORIZ OR AUTOKNOB,$8000,$8000,$8000 DIV 5,0,
                       0,0,0,0,0,0);
END;

{ *** ein paar Routinen für die Menüs: }

PROCEDURE AddMenu (dx: Integer; name: Str);
VAR m: p_Menu;
    it: IntuiText;
    x: Integer;
BEGIN
  x := dx;
  IF LastMenu<>Nil THEN x := x + LastMenu^.LeftEdge + LastMenu^.Width;
  it := IntuiText(0, 1, JAM1, 0, 0, MyWindow^.WScreen^.Font, name, Nil);
  New (m);
  m^ := Menu(Nil, x, 0, IntuiTextLength(^it) + 8,
               MyWindow^.WScreen^.Font^.ta_YSize, MENUENABLED,
               name, Nil, 0, 0, 0, 0);
  IF LastMenu=Nil THEN Strip := m
    ELSE LastMenu^.NextMenu := m;
  LastMenu := m; LastItem := Nil;
END;

PROCEDURE AddItem (dy: Integer;  Flag: Word;  name: Str;  Com: Char);
VAR i: p_MenuItem;
    t: p_IntuiText;
    w,y: Integer;
BEGIN
  IF LastMenu=Nil THEN Error('MenItem without Menu!');
  y := dy;
  IF LastItem<>Nil THEN y := y + LastItem^.TopEdge + LastItem^.Height;
  New(i); New(t);
  IF com>' ' THEN Flag := Flag OR COMMSEQ;
  t^ := IntuiText(0,1, JAM1, 2,1, MyWindow^.WScreen^.Font, name, Nil);
  w := IntuiTextLength(t);
  i^ := MenuItem(Nil, 0,y, w + 4,MyWindow^.WScreen^.Font^.ta_YSize + 2,
                 Flag OR ITEMTEXT OR ITEMENABLED OR HIGHCOMP,
                 0, t, Nil, Com, Nil, 0);
  IF LastItem=Nil THEN LastMenu^.FirstItem := i
    ELSE LastItem^.NextItem := i;
  LastItem := i; LastSubItem := Nil;
END;

PROCEDURE AddSubItem (dy: Integer; Flag: Word; name: Str;  Com: Char);
VAR s: p_MenuItem;
    t: p_IntuiText;
    w,y: Integer;
BEGIN
  IF LastItem=Nil THEN Error('SubItem without MenItem');
  y := dy;
  IF LastSubItem<>Nil THEN y := y + LastSubItem^.TopEdge + LastSubItem^.Height;
  New(s); New(t);
  If com>' ' THEN Flag := Flag OR COMMSEQ;
  t^ := IntuiText(0,1, JAM1, 2,1, MyWindow^.WScreen^.Font, name, Nil);
  w := IntuiTextLength(t);
  s^ := MenuItem(Nil, LastItem^.Width-12, y, w+4,MyWindow^.WScreen^.Font^.ta_YSize + 2,
                   Flag OR ITEMTEXT OR ITEMENABLED OR HIGHCOMP,
                   0, t, Nil, Com, Nil, 0);
  IF LastSubItem=Nil THEN LastItem^.SubItem := s
    ELSE LastSubItem^.NextItem := s;
  LastSubItem := s;
END;

PROCEDURE MutEx(exc: LongInt);
VAR i: p_MenuItem;
BEGIN
  i := LastItem;
  IF i=Nil THEN Error('no Item for MutEx');
  IF LastSubItem<>Nil THEN i := LastSubItem;
  i^.MutualExclude := exc;
  i^.Flags := i^.Flags AND NOT MENUTOGGLE;
END;

PROCEDURE ItEnable(really: Boolean);
VAR i: p_MenuItem;
BEGIN
  i := LastItem;
  IF i=Nil THEN Error('no Item for ItEnable');
  IF LastSubItem<>Nil THEN i := LastSubItem;
  IF NOT really THEN
    i^.Flags := i^.Flags AND NOT ITEMENABLED;
END;

PROCEDURE CalcMenuWidth(f: p_MenuItem);
{ alle Einträge einer Menüspalte auf gleiche Breite bringen }
VAR i: p_MenuItem;
    t: p_IntuiText;
    max, w: Integer;
BEGIN
  i := f;
  max := 8;
  WHILE i<>Nil DO BEGIN
    t := i^.ItemFill;
    w := 2 + IntuiTextLength(t) + t^.LeftEdge;
    IF i^.Flags AND COMMSEQ<>0 THEN w := w + 48;
    IF w>max THEN max := w;
    i := i^.NextItem;
  END;
  i := f;
  WHILE i<>Nil DO BEGIN
    i^.Width := max
    i := i^.NextItem
  END;
END;

PROCEDURE MenuWidths;
{ CalcMenuWidth auf alle Menüs und Untermenüs anwenden }
VAR m: p_Menu;
    i: p_MenuItem;
BEGIN
  m := Strip;
  WHILE m<>Nil DO BEGIN
    i := m^.FirstItem;
    IF i<>Nil THEN CalcMenuWidth(i);
    WHILE i<>Nil DO BEGIN
      IF i^.SubItem<>Nil THEN
        CalcMenuWidth(i^.SubItem);
      i := i^.NextItem;
    END;
    m := m^.NextMenu;
  END;
END;

PROCEDURE create_menu;
CONST chk = CHECKIT OR MENUTOGGLE;
      chkon = chk OR CHECKED;
VAR egal: Boolean;
BEGIN
  LastMenu := Nil;
  {$ if def DEUTSCH }
  AddMenu(10, 'Projekt');
    AddItem(0, 0, 'Öffnen',          'O'); ItEnable(ReqBase<>Nil);
    AddItem(0, 0, 'Sichern',         'S');
    AddItem(0, 0, 'Sichern als ...', 'A'); ItEnable(ReqBase<>Nil);
    AddItem(5, 0, 'Ende',            'Q');
    AddItem(0, 0, 'Sichern & Ende',  'X');
    AddItem(5, 0, 'Info',            '?');
  AddMenu(20, 'Daten');
    AddItem(0, 0, 'Achsen tauschen',    ' ');
    AddItem(5, 0, 'Zeilen spiegeln',    ' ');
    AddItem(0, 0, 'Spalten spiegeln',   ' ');
    AddItem(5, 0, 'Zeile verschieben',  'L');
    AddItem(0, 0, 'Spalte verschieben', 'C');
  AddMenu(20, 'Darstellung');
    AddItem(0, chkon, '  Netz',         ' '); MutEx(%1110);
    AddItem(0, chk,   '  Zeilenbänder', ' '); MutEx(%1101);
    AddItem(0, chk,   '  Spaltenbänder',' '); MutEx(%1011);
    AddItem(0, chk,   '  Säulen',       ' '); MutEx(%0111);
    AddItem(5, 0,     'Abmessungen  »', ' ');
      AddSubItem(0, 0,  'ändern',       'M');
      AddSubItem(0, 0,  'optimal',      ' ');
      AddSubItem(0, 0,  'merken',       ' ');
      AddSubItem(0, 0,  'zurückholen',  ' ');
    AddItem(0, 0,     'Korridore',      'K');
    AddItem(0, chk,   '  3D-Schrift',   ' ');
    AddItem(5, 0,     'Zeichnen',       'D');
    AddItem(0, 0,     'Abbruch',        'H');
    AddItem(0, 0,     'Refreshs als  »', ' ');
      AddSubItem(0, chkon, '  Skizze',  '0'); MutEx(%10);
      AddSubItem(0, chk,   '  Vollbild','+'); MutEx(%01);
  AddMenu(20, 'Extras');
    AddItem(0, chkon, '  Interlace',   'I');
    AddItem(0, 0,     'Farben  »',     ' ');
      AddSubItem(0, 0,  'Palette',     'P'); ItEnable(ReqBase<>Nil);
      AddSubItem(0, 0,  'Workbench',   ' ');
      AddSubItem(0, 0,  'Default',     ' ');
    AddItem(0, 0,     'Font ...',      'F'); ItEnable(ReqBase<>Nil);
  {$ else }
  AddMenu(10, 'Project');
    AddItem(0, 0, 'Open',        'O'); ItEnable(ReqBase<>Nil);
    AddItem(0, 0, 'Save',        'S');
    AddItem(0, 0, 'Save as ...', 'A'); ItEnable(ReqBase<>Nil);
    AddItem(5, 0, 'Quit',        'Q');
    AddItem(0, 0, 'Save & Quit', 'X');
    AddItem(5, 0, 'About',       '?');
  AddMenu(20, 'Data');
    AddItem(0, 0, 'Swap Axes',      ' ');
    AddItem(5, 0, 'Mirror Lines',   ' ');
    AddItem(0, 0, 'Mirror Columns', ' ');
    AddItem(5, 0, 'Move Line',      'L');
    AddItem(0, 0, 'Move Column',    'C');
  AddMenu(20, 'Display');
    AddItem(0, chkon, '  Surface',     ' '); MutEx(%1110);
    AddItem(0, chk,   '  Lines',       ' '); MutEx(%1101);
    AddItem(0, chk,   '  Columns',     ' '); MutEx(%1011);
    AddItem(0, chk,   '  Fields',      ' '); MutEx(%0111);
    AddItem(5, 0,     'Dimensions  »', ' ');
      AddSubItem(0, 0,  'Enter',       'M');
      AddSubItem(0, 0,  'Find Best',   ' ');
      AddSubItem(0, 0,  'Snapshot',    ' ');
      AddSubItem(0, 0,  'Recall',      ' ');
    AddItem(0, 0,     'Corridors',     'K');
    AddItem(0, chk,   '  Fancy Texts', ' ');
    AddItem(5, 0,     'Draw',          'D');
    AddItem(0, 0,     'Halt',          'H');
    AddItem(0, 0,     'Auto Redraw as  »', ' ');
      AddSubItem(0, chkon, '  Sketch', '0'); MutEx(%10);
      AddSubItem(0, chk,   '  Diagram','+'); MutEx(%01);
  AddMenu(20, 'Outfit');
    AddItem(0, chkon, '  Interlace', 'I');
    AddItem(0, 0,     'Colors  »',   ' ');
      AddSubItem(0, 0,  'Palette',   'P'); ItEnable(ReqBase<>Nil);
      AddSubItem(0, 0,  'Workbench', ' ');
      AddSubItem(0, 0,  'Default',   ' ');
    AddItem(0, 0,     'Font ...',    'F'); ItEnable(ReqBase<>Nil);
  {$ endif }
  MenuWidths;
  egal := SetMenuStrip(MyWindow,Strip);
END;

PROCEDURE clear_menu;
{ Die Arbeit von create_menu rückgängig machen }
VAR m, m2: p_Menu;
    i, i2: p_MenuItem;
    t:     p_IntuiText;
BEGIN
  IF Strip<>Nil THEN ClearMenuStrip(MyWindow);
  m := Strip;
  WHILE m<>Nil DO BEGIN
    i := m^.FirstItem;
    WHILE i<>Nil DO BEGIN
      i2 := i;
      t := i^.ItemFill;
      i := i^.NextItem;
      Dispose(t);
      Dispose(i2)
    END;
    m2 := m;
    m := m^.NextMenu;
    Dispose(m2)
  END;
  LastMenu := Nil; Strip := Nil;
END;

PROCEDURE sysclean;
{ Das Werk von sysinit rückgängig machen, wird bei Programmende aufgerufen. }
VAR i: Integer;
BEGIN
  FOR i := 1 TO 5 DO BEGIN
    IF ChipSpc[i]<>Nil THEN Free_Mem(Long(ChipSpc[i]),SizeOf(WordArr40));
    ChipSpc[i] := Nil;
  END;
  IF IntuitionBase<>Nil THEN CloseLibrary(IntuitionBase);
  IF GfxBase<>Nil THEN CloseLibrary(GfxBase);
  IF ReqBase<>Nil THEN BEGIN
    PurgeFiles(MyFileReq);
    CloseLibrary(ReqBase);
  END;
  IF MyFileReq<>Nil THEN Free_Mem(Long(MyFileReq),SizeOf(ReqFileRequester));
  MyFileReq := Nil;
  IntuitionBase := Nil; GfxBase := Nil; ReqBase := Nil;
END;

PROCEDURE screenclean;
{ Das Werk von screeninit rückgängig machen, wird bei jeder Screenmode- }
{ Umschaltung aufgerufen. }
VAR egal: word;
BEGIN
  IF oldwindowptr<>Nil THEN myprocess^.pr_WindowPtr := oldwindowptr;
  IF MyWindow<>Nil THEN BEGIN
    egal := RemoveGList(MyWindow,^WinGad[1],6);
    clear_menu; Strip := Nil;
    CloseWindow(MyWindow);
    MyWindow := Nil;
  END;
  IF MyScreen<>Nil THEN IF CloseScreen(MyScreen) THEN; MyScreen := Nil;
  IF armem<>Nil THEN FreeRaster(armem,breite,hoehe); armem := Nil;
END;

PROCEDURE sysinit;
{ Libraries öffnen, Programmstart-Argumente auswerten, Gadgetbilder aufbauen }
VAR i,j,len: integer;
    s: str80;
    hail: p_WBStartup;
    arg: p_WBArg;
    olddir: BPTR;
BEGIN
  { zuerst Zeiger für den zugehörigen ExitServer initialisieren: }
  FOR i := 1 TO 5 DO ChipSpc[i] := Nil; MyFileReq := Nil;
  IntuitionBase := Nil; GfxBase := Nil; ReqBase := Nil;
  { Filerequester-Struktur anlegen, muß mit Nullen vorbesetzt sein! }
  MyFileReq := Ptr(Alloc_Mem(SizeOf(ReqFileRequester),MEMF_CLEAR));
  { Intuition-Version >= 1.2, da ich z. B. ActivateGadget() benutze: }
  IntuitionBase := OpenLibrary('intuition.library',33);
  GfxBase := OpenLibrary('graphics.library',0);
  ReqBase := OpenLibrary('req.library',0);
  IF IntuitionBase=Nil THEN  Error('You need intuition.library V33+!');
  IF GfxBase=Nil THEN  Error('Can''t open graphics.library ... hm?');
  IF ReqBase=Nil THEN  {desaster('Can''t open req.library !!!')};
  { Bei fehlender req.library werden nur ein paar Menues gesperrt ... }
  FOR i := 1 TO 5 DO
    ChipSpc[i] := Ptr(Alloc_Mem(SizeOf(WordArr40),MEMF_CHIP));
  bilder;
  filename := '';
  IF fromWB THEN BEGIN                    { WB-Start }
    hail := StartupMessage;
    arg := hail^.sm_ArgList;
    olddir := CurrentDir(arg^.wa_Lock);   { ins richtige Verzeichnis wechseln }
    IF hail^.sm_NumArgs>1 THEN BEGIN      { mit Argumentdateien gestartet? }
      { auf nächsten WBArg-Zeiger zugreifen: }
      arg := Ptr(Long(arg)+SizeOf(WBArg));
      olddir := CurrentDir(arg^.wa_Lock);
      filename := arg^.wa_Name;
    END;
  END ELSE BEGIN                          { CLI-Start }
    len := ParameterLen; IF len >79 THEN len := 79;
    s := Copy(ParameterStr,1,len);
    { eigentlichen Dateinamen herausfinden (von Trennzeichen eingeschlossen: }
    i := 1; WHILE (s[i]<=' ') AND (i<=len) DO Inc(i);
    j := i; WHILE (s[i]>' ') AND (j<=len) DO Inc(j);
    IF s[i]='"' THEN BEGIN { in "" eingeschlossener Name }
      Inc(i); j := i; WHILE (s[j]<>'"') AND (j<=len) DO Inc(j);
    END;
    IF j>i THEN BEGIN
      filename := Copy(s,i,j-i); force_extension(filename);
    END;
  END;
END;

FUNCTION RASSIZE (w,h : Long) : Long;  { tolle 3.1-Includes :-P }
BEGIN
 RASSIZE:=(h*((w+15) DIV 8) AND $FFFE);
END;

PROCEDURE screeninit;
{ Screen, Window, Menue und Gadgets installieren }
VAR flags, pen, egal: Word;
    theGfxBase: p_GfxBase;
BEGIN
  { zuerst Zeiger für den zugehörigen ExitServer initialisieren: }
  MyScreen := Nil; armem := Nil; MyWindow := Nil; Con := Nil; Strip := Nil;
  oldwindowptr := Nil;
  { DrawInfo-Pens für den Screen angeben, damit die Fenster darauf unter }
  { 2.0 gut aussehen. Programm ist trotzdem unter 1.3 lauffähig! (Trick: Die }
  { ExtNewScreen-Struktur, die von 1.3 für eine gewönliche NewScreen-Struktur }
  { gehalten wird, da das Flag NS_EXTENDED für 1.3 keine Bedeutung hat.) }
  pen := $FFFF; { Zeichen für "der Rest nach Default" }
  NSTags[1] := TagItem(SA_Pens,Long(^pen));
  NSTags[2] := TagItem(TAG_DONE,0);
  theGfxBase := GfxBase;
  breite := theGfxBase^.NormalDisplayColumns * horiz DIV 2;
  flags := GENLOCK_VIDEO
  IF horiz=2 THEN flags := flags OR HIRES;
  IF vert=2 THEN  flags := flags OR LACE;
  topazAttr := TextAttr('topaz.font',8,FS_NORMAL,FPF_ROMFONT);
  NeuerScreen := ExtNewScreen(0,0,breite,STDSCREENHEIGHT,2,0,1,flags,
       NS_EXTENDED OR CUSTOMSCREEN,Nil,'Graph3D-Screen',Nil,Nil,^NSTags[1]);
  MyScreen := OpenScreen(^NeuerScreen);
  IF MyScreen = Nil THEN Error('Cannot open screen!');
  charx := MyScreen^.RastPort.TxWidth;  { Screenfont, für Text in Requestern }
  chary := MyScreen^.RastPort.TxHeight;
  baseline := MyScreen^.RastPort.TxBaseline;
  hoehe := MyScreen^.Height;
  NeuesWindow := NewWindow(0,MyScreen^.BarHeight+1,breite,
      hoehe-MyScreen^.Barheight-1,2,1, GADGETUP OR GADGETDOWN OR _CLOSEWINDOW
      OR MENUPICK OR NEWSIZE OR REQCLEAR OR REQSET,
      ACTIVATE OR WINDOWSIZING OR WINDOWCLOSE OR WINDOWDEPTH OR WINDOWDRAG
      OR SIZEBRIGHT OR SIZEBBOTTOM OR GIMMEZEROZERO,
      Nil, Nil, 'Graph3D',MyScreen,Nil,220,100,breite,hoehe,CUSTOMSCREEN);
  MyWindow := OpenWindow(^NeuesWindow);
  IF MyWindow = Nil THEN Error('Cannot open window!');
  create_menu;
  Rast := MyWindow^.RPort;
  Upt := MyWindow^.Userport;
  gadgetsetup(MyWindow^.BorderLeft,MyWindow^.BorderTop,MyWindow^.BorderRight,
    MyWindow^.BorderBottom);
  egal := AddGList(MyWindow,^WinGad[1],1,6,Nil);
  RefreshGadgets(^WinGad[1],MyWindow,Nil);
  armem := AllocRaster(breite,hoehe);  { Speicher fuer Areas }
  IF armem=Nil THEN Error('Cannot allocate temporary raster!');
  InitArea(^MyAreaInfo,^areabuffer[1],100);
  Rast^.TmpRas := InitTmpRas(^tmp,armem,RASSIZE(breite,hoehe));
  Rast^.AreaInfo := ^MyAreaInfo;
  { meine Task finden und System Requests auf meinen Screen umleiten }
  myprocess := ptr(FindTask(Nil));
  oldwindowptr := myprocess^.pr_WindowPtr;
  myprocess^.pr_WindowPtr := MyWindow;
END;

{ *#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#* }
{ *#*#*#*#*#*#*#*#*#*#*#* Ereignisverarbeitung #*#*#*#*#*#*#*#*#*#*#*#*#* }
{ *#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#* }

FUNCTION wrongviewmode: boolean;
{ Stellt fest, ob der momentan offene Screen die gewünschte vertikale }
{ Auflösung hat. }
VAR is_laced: boolean;
BEGIN
  is_laced := (MyScreen^.ViewPort.Modes AND LACE)<>0;
  wrongviewmode := ((vert=1) AND is_laced) OR ((vert=2) AND NOT is_laced);
END;

PROCEDURE check(m,i,s: Integer; really: Boolean);
VAR it: p_MenuItem;
    code: Long;
BEGIN
  code := m + (i SHL 5) + (s SHL 11);
  it := ItemAddress(Strip,code);
  IF really THEN
    it^.Flags := it^.Flags OR CHECKED
  ELSE
    it^.Flags := it^.Flags AND NOT CHECKED;
END;

FUNCTION has_check(m,i,s: Integer): Boolean;
VAR it: p_MenuItem;
    code: Long;
BEGIN
  code := m + (i SHL 5) + (s SHL 11);
  it := ItemAddress(Strip,code);
  has_check := (it^.Flags AND CHECKED)<>0;
END;

PROCEDURE write_checks;
{ Variablen ins Menue übertragen }
VAR i: integer;
BEGIN
  check(2,6,-1, schraeg);
  FOR i := 1 TO 4 DO
    check(2,i-1,-1, (i = modus));
  check(3,0,-1, (vert=2));
  check(2,9,0, quickdraw);
  check(2,9,1, NOT quickdraw);
END;

PROCEDURE check_checks;
{ Menuehäkchen in Programmvariablen übernehmen }
VAR i: integer;
    egal: boolean;
BEGIN
  schraeg := has_check(2,6,-1);
  modus := 1;
  FOR i := 1 TO 4 DO
    IF has_check(2,i-1,-1) THEN modus := i;
  IF has_check(3,0,-1) THEN vert := 2 ELSE vert := 1;
  quickdraw := has_check(2,9,0);
END;

PROCEDURE menuhandling(item: word);
{ Menu-Handhabung }
VAR men,menitem,subitem: integer;
    item_address: ^MenuItem;
    hallo: long;
    update: Boolean;
    tryname: str80;
BEGIN
  update := False;
  WHILE item<>MENUNULL DO BEGIN
    { item nach Menue, Menuepunkt und Untermenue aufschlüsseln }
    men := item AND $1F;
    menitem := (item SHR 5) AND $3F;
    subitem := (item SHR 11) AND $1F;
    { und schon mal zum nächsten vorrücken: }
    item_address := ItemAddress(Strip,item);
    item := item_address^.NextSelect;
    IF men=0 THEN       { 1. Menue: Projekt }
      CASE menitem OF
        0: BEGIN
            tryname := filename;
            IF fileselect('Tabellendaten einlesen',False,tryname) THEN
              IF laden(tryname) THEN BEGIN
                filename := tryname; write_checks; update := True;
              END;
          END;
        1: IF NOT speichern(filename) THEN;
        2: BEGIN
            tryname := filename;
            IF fileselect('Daten und Parameter sichern',True,tryname) THEN BEGIN
              force_extension(tryname);
              IF speichern(tryname) THEN filename := tryname;
            END;
          END;
        3: ende := True;
        4: IF speichern(filename) THEN ende := True;
        5: infotext;
      OTHERWISE;
      END;
    IF men=1 THEN BEGIN      { 2. Menue: Daten }
      update := True;
      CASE menitem OF
        0: BEGIN swapxy; write_checks; END;
        1: mirrorx;
        2: mirrory;
        3: update := move_row(True);
        4: update := move_row(False);
        OTHERWISE;
      END;
    END;
    IF men=2 THEN       { 3. Menue: Darstellung }
      CASE menitem OF
        0..3: update := True;
        4: CASE subitem OF
             0: IF gr_aendern THEN update := bereichstest;
             1: BEGIN best_guess; update := True; END;
             2: BEGIN merken; confirm_snap; END;
             3: BEGIN erinnern; update := bereichstest; END;
           OTHERWISE;
           END;
         5: BEGIN korridor_eing; update := True; END;
         6: update := True;
         7: BEGIN darstellen; update := False; END;
      OTHERWISE;
      END;
    IF men=3 THEN       { 4. Menue: Extras }
      CASE menitem OF
        1: CASE subitem OF
             0: IF ReqBase<>Nil THEN hallo := ColorRequester(0);
             1: clonecolors;
             2: defcolors;
             OTHERWISE;
           END;
        OTHERWISE;
      END;
  END;
  IF update THEN refresh;
END;

PROCEDURE gadgetprimaer(g:p_Gadget);
{ Ereignisse bei GADGETDOWN }
VAR MyInfo: ^PropInfo;
    alt: real;
BEGIN
  IF g<>Nil THEN
    CASE g^.GadgetID OF
      3: IF rb<20 THEN BEGIN    { wegzoomen }
          rb := rb * 1.3; vektoren; skizze;
        END;
      4: IF rb>0.5 THEN BEGIN   { ranzoomen }
          rb := rb / 1.3; vektoren; skizze;
        END;
      5: BEGIN   { vertikale Beobachterposition aus Prop übernehmen }
        MyInfo := g^.SpecialInfo;
        REPEAT
          alt := theta; theta := (MyInfo^.VertPot*Pi/2)/MAXPOT;
          IF theta<>alt THEN BEGIN vektoren; zentriere; skizze; END;
        UNTIL (g^.flags AND SELECTED) = 0;
        refresh;
      END;
      6: BEGIN   { dasselbe für horizontale Beobachterposition }
        MyInfo := g^.SpecialInfo;
        REPEAT
          alt := phi; phi := (MyInfo^.HorizPot*Pi/2)/MAXPOT;
          IF phi<>alt THEN BEGIN vektoren; zentriere; skizze; END;
        UNTIL (g^.flags AND SELECTED) = 0;
        refresh;
      END;
      OTHERWISE;
    END;
END;

PROCEDURE gadgetfolge(g:p_Gadget);
{ Ereignisse bei GADGETUP }
BEGIN
  IF g<>Nil THEN
    CASE g^.GadgetID OF
      1: darstellen;
      2: skizze;
      3,4: refresh;   { wegzoomen/ranzoomen abschließen }
      OTHERWISE;
    END;
END;

{ *#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#* }
{ *#*#*#*#*#*#*#*#*#*#*#*#*#  Hauptprogramm  #*#*#*#*#*#*#*#*#*#*#*#*#*#* }
{ *#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#* }

VAR l,eventclass: Long;
    address: Ptr;
    code: Word;

BEGIN
  varinit;
  AddExitServer(sysclean); sysinit;
  AddExitServer(screenclean); screeninit;
  defcolors;
  IF filename='' THEN
    demodaten
  ELSE IF NOT laden(filename) THEN
    demodaten;
  write_checks;
  refresh;
  merken;
  ende := False;
  REPEAT
    l := Wait(-1);
    REPEAT              { Schleife, da mehrere Ereignisse möglich }
      check_checks;     { Häkchen auslesen, bevor eine Aktion stattfindet }
      MyMsg := Get_Msg(Upt);
      IF MyMsg<>Nil THEN BEGIN
        eventclass := MyMsg^.Class;
        code := MyMsg^.Code;
        address := MyMsg^.IAddress;
        Reply_Msg(MyMsg);             { so schnell wie möglich antworten! }
        CASE eventclass OF
          _CLOSEWINDOW:
            ende := True;
          NEWSIZE:
            refresh;
          GADGETDOWN:
            gadgetprimaer(address);
          GADGETUP:
            gadgetfolge(address);
          MENUPICK:
            menuhandling(code);
        OTHERWISE;
        END;
      END;
    UNTIL MyMsg = Nil;
    IF wrongviewmode THEN BEGIN
      screenclean; screeninit;   { neuen Screen mit/ohne Interlace öffnen }
      writepalette; write_checks; refresh;
    END;
  UNTIL ende;
  screenclean; sysclean;
END.

