home *** CD-ROM | disk | FTP | other *** search
- (* Modul : FFEX *)
- (* Projekt : FFEX (Fast Fractal Exploration Set) *)
- (* Autor : Robert Brandner *)
- (* Funktion : Hauptmodul von FFEX - Menⁿabfragen, Zoom, Berechnungen ... *)
- (* Copyright: Robert Brandner *)
- (* Schillerstr. 3 *)
- (* A-8280 Fⁿrstenfeld *)
- (* AUSTRIA *)
-
- MODULE FFEX;
-
- FROM Menu IMPORT SetMenu, MenuNum, ItemNum, SubNum, NextSelect;
- FROM Render IMPORT SetPixel,GetPixel,FastIter16,FastIter32,LongRealIter,
- SetNormalPointer,SetZZZPointer,SetZoomPointer;
- FROM Request IMPORT Info, GetLimits, Request;
- FROM ArpReq IMPORT GetFileName;
- FROM IlbmInOut IMPORT LoadILBM, SaveILBM;
-
- FROM Arts IMPORT Assert, TermProcedure;
- FROM SYSTEM IMPORT ADR, ADDRESS, CAST, INLINE, LONGSET;
- FROM GfxMacros IMPORT RasSize;
- FROM Graphics IMPORT ViewModes,ViewModeSet,FontFlags,FontFlagSet,
- TextFontPtr, OpenFont,normalFont,TextAttr,CloseFont,
- LoadRGB4, SetRGB4, RastPortPtr,RectFill,SetAPen,
- SetRast, Move, Draw, SetDrMd, DrawModeSet,DrawModes,
- jam1, BltBitMap;
- FROM Intuition IMPORT IntuiMessagePtr, menuNull, ShowTitle, ModifyIDCMP,
- ScreenPtr,NewScreen,OpenScreen,CloseScreen,
- WindowPtr,NewWindow,OpenWindow,CloseWindow,
- WindowFlags,WindowFlagSet,ScreenFlags,ScreenFlagSet,
- customScreen, IDCMPFlagSet, IDCMPFlags, ScreenToFront,
- ClearMenuStrip, menuDown, menuUp, selectDown, selectUp;
- FROM Exec IMPORT FindTask, TaskPtr, GetMsg, ReplyMsg, WaitPort, CopyMem;
- FROM Dos IMPORT ProcessPtr;
-
- CONST
- SCREENTITLE="Fast Fractal Exploration Set 4.0";
- TOPAZ="topaz.font";
- LIMIT=4; (* bis zu dieser Gr÷▀e werden Rechtecke gevierte(i)lt *)
- ESCAPE=045H;
- MENUFLAGS=IDCMPFlagSet{menuVerify,menuPick,mouseButtons};
- STARTPIC="FFEXStart.pic";
-
- TYPE
- IterProc=PROCEDURE(LONGREAL,LONGREAL,LONGINT):LONGINT;
-
- VAR
- ns : NewScreen;
- nw : NewWindow;
- Iterations: IterProc;
- win : WindowPtr;
- scr : ScreenPtr;
- topaz80 : TextFontPtr;
- attr : TextAttr;
- thisTask : ProcessPtr;
- QUIT,title: BOOLEAN;
- yadr : ARRAY[0..512] OF LONGINT;
- rp : RastPortPtr;
- msg : IntuiMessagePtr;
- class : IDCMPFlagSet;
- code : CARDINAL;
- xres,yres,depth,i,maxcol: INTEGER;
- xmin,ymin,xmax,ymax : LONGREAL; (* Koord. des zu zeichnenden Bildes *)
- xminr,yminr,xmaxr,ymaxr : LONGREAL; (* Koord. des letzten gez. Bildes *)
- x1,y1,x2,y2 : LONGREAL; (* Hilfsvariablen *)
- maxiter : LONGINT;
- zx,zy,zdx,zdy : INTEGER; (* Zoomrahmen *)
- fileok : BOOLEAN;
- fname : ARRAY[0..255] OF CHAR; (* fⁿr Filerequester *)
- saved : BOOLEAN;
- mess : ARRAY[0..80] OF CHAR;
- no,yes : ARRAY[0..9] OF CHAR;
-
- PROCEDURE TextColorsOn; FORWARD;
- PROCEDURE TextColorsOff; FORWARD;
- PROCEDURE CreateDisplay(w,h,d:INTEGER); FORWARD;
-
- PROCEDURE LoadIff(fn:BOOLEAN);
- VAR
- i,planebytes,ok:LONGINT;
- lscr:ScreenPtr;
- BEGIN
- IF NOT saved THEN
- mess:="This picture is not saved!|If you continue, it will be lost!";
- yes:="CONTINUE"; no:="CANCEL";
- IF NOT Request(win,mess,yes,no) THEN RETURN END;
- END;
- INCL(win^.flags,rmbTrap);
- ModifyIDCMP(win,IDCMPFlagSet{});
- IF fn THEN
- TextColorsOn;
- fileok:=GetFileName(win,ADR("Load File"),ADR(fname));
- TextColorsOff;
- ELSE
- fileok:=TRUE
- END;
- IF fileok THEN
- SetZZZPointer(win);
- fileok:=LoadILBM(fname,win,lscr,xminr,yminr,xmaxr,ymaxr,maxiter);
- IF fileok THEN
- xmin:=xminr; ymin:=yminr; xmax:=xmaxr; ymax:=ymaxr;
- IF (lscr^.width#xres) OR (lscr^.height#yres) THEN
- CreateDisplay(lscr^.width,lscr^.height,INTEGER(lscr^.bitMap.depth));
- END;
- saved:=TRUE;
- ShowTitle(scr,FALSE);
- ok:=BltBitMap(ADR(lscr^.bitMap),0,0,ADR(scr^.bitMap),0,0,
- xres,yres,0C0H,0FFH,NIL);
- ShowTitle(scr,title);
- CloseScreen(lscr);
- END;
- SetNormalPointer(win);
- END;
- EXCL(win^.flags,rmbTrap);
- ModifyIDCMP(win,MENUFLAGS);
- END LoadIff;
-
- PROCEDURE SaveIff;
- BEGIN
- ClearMenuStrip(win);
- ModifyIDCMP(win,IDCMPFlagSet{});
- TextColorsOn;
- fileok:=GetFileName(win,ADR("Save File"),ADR(fname));
- TextColorsOff;
- IF fileok THEN
- INCL(win^.flags,rmbTrap);
- SetZZZPointer(win); ShowTitle(scr,FALSE);
- fileok:=SaveILBM(fname,scr,xminr,yminr,xmaxr,ymaxr,maxiter);
- saved:=fileok;
- SetNormalPointer(win); ShowTitle(scr,title); EXCL(win^.flags,rmbTrap);
- END;
- ModifyIDCMP(win,MENUFLAGS);
- SetMenu(win);
- END SaveIff;
-
- (*** Prozeduren fⁿr Screen und Window ********************************)
-
- PROCEDURE ColorTable; (* $E- *)
- BEGIN
- INLINE(00000H,00FF0H,00FD0H,00FB0H,00F80H,00F60H,00F40H,00F20H,
- 00F00H,00F02H,00F05H,00F07H,00F09H,00F0BH,00F0DH,00F0FH,
- 00D0FH,00B0FH,0090FH,0070FH,0050FH,0030FH,0010FH,0001FH,
- 0003FH,0005FH,0007FH,0009FH,000BFH,000DFH,000FFH,00DDDH);
- END ColorTable;
-
-
- PROCEDURE CloseIfOpen;
- BEGIN
- thisTask:=CAST(ProcessPtr,FindTask(NIL));
- thisTask^.windowPtr:=NIL;
- IF win#NIL THEN CloseWindow(win); win:=NIL END;
- IF scr#NIL THEN CloseScreen(scr); scr:=NIL END;
- END CloseIfOpen;
-
-
- PROCEDURE CreateDisplay(w,h,d:INTEGER);
- BEGIN
- xres:=w; yres:=h; depth:=d; (* Werte fⁿr DrawFractal/Zoom merken *)
- IF NOT saved THEN
- mess:="This picture is not saved!|If you continue, it will be lost!";
- yes:="CONTINUE"; no:="CANCEL";
- IF NOT Request(win,mess,yes,no) THEN RETURN END;
- END;
- CloseIfOpen;
- WITH ns DO
- width:=w; height:=h; depth:=d; detailPen:=6; blockPen:=1;
- viewModes:=ViewModeSet{};
- IF w>320 THEN INCL(viewModes,hires) END;
- IF h>256 THEN INCL(viewModes,lace) END;
- type:=customScreen+ScreenFlagSet{screenBehind};
- font:=ADR(attr); defaultTitle:=ADR(SCREENTITLE);
- gadgets:=NIL; customBitMap:=NIL;
- END;
- scr:=OpenScreen(ns);
- Assert(scr#NIL,ADR("OpenScreen() failed!"));
- LoadRGB4(ADR(scr^.viewPort),ADR(ColorTable),32);
- IF d=5 THEN maxcol:=31 ELSE maxcol:=15 END;
- WITH nw DO
- width:=w; height:=h; detailPen:=3; blockPen:=1;
- idcmpFlags:=IDCMPFlagSet{menuVerify, menuPick, mouseButtons};
- flags:=WindowFlagSet{reportMouse,backDrop,borderless,
- activate,noCareRefresh};
- firstGadget:=NIL;checkMark:=NIL;title:=NIL;
- screen:=scr; bitMap:=NIL;
- minWidth:=0; minHeight:=0; maxWidth:=-1; maxHeight:=-1;
- type:=customScreen;
- END;
- win:=OpenWindow(nw);
- Assert(win#NIL,ADR("OpenWindow() failed!"));
- rp:=win^.rPort;
- FOR i:=0 TO h-1 DO (* Zeilenadressen berechnen *)
- yadr[i]:=LONGINT(i)*LONGINT(rp^.bitMap^.bytesPerRow);
- END;
- ScreenToFront(scr);
- thisTask:=CAST(ProcessPtr,FindTask(NIL)); (* Systemrequester auf *)
- thisTask^.windowPtr:=win; (* eigenem Screen. *)
- SetMenu(win);
- title:=TRUE; ShowTitle(scr,title);
- END CreateDisplay;
-
-
- (*** Prozeduren fⁿr Fraktalgrafik ************************************)
-
- PROCEDURE DrawFractal(rmin,rmax,imin,imax:LONGREAL;
- maxcol:INTEGER;
- maxiter:LONGINT);
- VAR
- r,cxr,cyr,dxr,dyr:LONGREAL;
- lc:LONGINT;
- exit:BOOLEAN;
-
- PROCEDURE HLine(xmin,xmax,y:INTEGER); (* waagrechte Linie *)
- BEGIN
- cxr:=rmin+LONGREAL(xmin)*dxr;cyr:=imin+LONGREAL(y)*dyr;
- FOR i:=xmin TO xmax DO
- lc:=Iterations(cxr,cyr,maxiter);cxr:=cxr+dxr;
- SetPixel(i,yadr[y],lc,maxiter,maxcol,ADR(rp^.bitMap^.planes[0]));
- END
- END HLine;
-
- PROCEDURE VLine(ymin,ymax,x:INTEGER); (* senkrechte Linie *)
- BEGIN
- cxr:=rmin+LONGREAL(x)*dxr;cyr:=imin+LONGREAL(ymin)*dyr;
- FOR i:=ymin TO ymax DO
- lc:=Iterations(cxr,cyr,maxiter);cyr:=cyr+dyr;
- SetPixel(x,yadr[i],lc,maxiter,maxcol,ADR(rp^.bitMap^.planes[0]));
- END
- END VLine;
-
- PROCEDURE Rectangle(xmin,ymin,xmax,ymax:INTEGER);
- VAR
- eq:BOOLEAN;
- dx2,dy2,k:INTEGER;
- BEGIN
- IF exit THEN RETURN END;
- msg:=GetMsg(win^.userPort);
- IF msg#NIL THEN
- ReplyMsg(msg);
- IF msg^.code=ESCAPE THEN exit:=TRUE END;
- END;
- dx2:=(xmax-xmin);dy2:=(ymax-ymin);
- IF (dx2<2) OR (dy2<2) THEN RETURN END;
- IF (dx2<LIMIT) OR (dy2<LIMIT) THEN
- FOR k:=ymin+1 TO ymax-1 DO HLine(xmin+1,xmax-1,k) END;
- RETURN;
- END;
- dx2:=(1+dx2) DIV 2;dy2:=(1+dy2) DIV 2;
- lc:=GetPixel(xmin,yadr[ymin],depth, ADR(rp^.bitMap^.planes[0]));
- i:=xmin;eq:=TRUE;
- REPEAT
- INC(i);
- eq:=(lc=GetPixel(i,yadr[ymin],depth,ADR(rp^.bitMap^.planes[0]))) &
- (lc=GetPixel(i,yadr[ymax],depth,ADR(rp^.bitMap^.planes[0])));
- UNTIL (i=xmax) OR NOT eq;
- IF eq THEN
- i:=ymin;
- REPEAT
- INC(i);
- eq:=(lc=GetPixel(xmin,yadr[i],depth,ADR(rp^.bitMap^.planes[0]))) &
- (lc=GetPixel(xmax,yadr[i],depth,ADR(rp^.bitMap^.planes[0])));
- UNTIL (i=ymax) OR NOT eq;
- END;
- IF eq THEN (* ganzer Rand einfΣrbig => Rechteck einfΣrbig *)
- SetAPen(rp,lc);RectFill(rp,xmin,ymin,xmax,ymax);
- ELSE
- (* Rechteck vierteln, und alle Viertel testen *)
- HLine(xmin+1,xmax-1,ymin+dy2);
- VLine(ymin+1,ymax-1,xmin+dx2);
- Rectangle(xmin,ymin,xmin+dx2,ymin+dy2);
- Rectangle(xmin+dx2,ymin,xmax,ymin+dy2);
- Rectangle(xmin,ymin+dy2,xmin+dx2,ymax);
- Rectangle(xmin+dx2,ymin+dy2,xmax,ymax);
- END;
- END Rectangle;
-
- BEGIN (* DrawFractal *)
- INCL(win^.flags,rmbTrap);
- ShowTitle(scr,FALSE); SetZZZPointer(win);
- ModifyIDCMP(win,IDCMPFlagSet{rawKey});
- ClearMenuStrip(win);
- saved:=FALSE;
- SetRast(rp,0);
- dxr:=(rmax-rmin)/LONGREAL(xres);
- dyr:=(imax-imin)/LONGREAL(yres);
- IF zx#-1 THEN (* wenn Zoomrahmen, dann diesen Ausschnitt zeichnen *)
- r:=rmin;
- rmin:=r+LONGREAL(zx-zdx)*dxr;
- rmax:=r+LONGREAL(zx+zdx)*dxr;
- r:=imin;
- imin:=r+LONGREAL(zy-zdy)*dyr;
- imax:=r+LONGREAL(zy+zdy)*dyr;
- dxr:=(rmax-rmin)/LONGREAL(xres);
- dyr:=(imax-imin)/LONGREAL(yres);
- zx:=-1; (* Damit der Rahmen anschlie▀end nicht gezeichnet wird *)
- END;
- xminr:=rmin; xmaxr:=rmax; yminr:=imin; ymaxr:=imax;
- xmin:=rmin; xmax:=rmax; ymin:=imin; ymax:=imax;
- exit:=FALSE;
- HLine(0,xres-1,0);VLine(0,yres-1,xres-1); (* Anfangs- *)
- HLine(0,xres-1,yres-1);VLine(0,yres-1,0); (* rechteck *)
- Rectangle(0,0,xres-1,yres-1);
- EXCL(win^.flags,rmbTrap);
- SetMenu(win);
- ModifyIDCMP(win,IDCMPFlagSet{menuPick,menuVerify,mouseButtons});
- ShowTitle(scr,title); SetNormalPointer(win);
- END DrawFractal;
-
-
- (*** Prozeduren fⁿr Zoom *********************************************)
-
- PROCEDURE DrawFrame(x1,y1,dx,dy:INTEGER);
- BEGIN
- SetAPen(rp,15); (* 4 Bitplanes beeinflussen *)
- SetDrMd(rp,DrawModeSet{complement}); (* Bild nicht zerst÷ren *)
- Move(rp,x1-dx,y1-dy);
- Draw(rp,x1+dx,y1-dy);Draw(rp,x1+dx,y1+dy);
- Draw(rp,x1-dx,y1+dy);Draw(rp,x1-dx,y1-dy);
- SetDrMd(rp,jam1);
- END DrawFrame;
-
-
- PROCEDURE Zoom; (* Ausschnitt wΣhlen, unverzerrt! *)
- VAR
- x1,y1,dx,dy,mx,my:INTEGER;
- r,dxr,dyr:LONGREAL;
- BEGIN
- IF zx#-1 THEN DrawFrame(zx,zy,zdx,zdy) END; (* Rahmen l÷schen *)
- xmin:=xminr; xmax:=xmaxr;
- ymin:=yminr; ymax:=ymaxr;
- ModifyIDCMP(win,IDCMPFlagSet{mouseButtons,mouseMove});
- ShowTitle(scr,FALSE);
- SetZoomPointer(win);
- REPEAT (* warten bis Maustaste gedrⁿckt *)
- WaitPort(win^.userPort);
- msg:=GetMsg(win^.userPort);
- ReplyMsg(msg);
- UNTIL (mouseButtons IN msg^.class);
- IF (msg^.code = menuDown) THEN (* RMB => Abbruch *)
- ShowTitle(scr,title); SetNormalPointer(win);
- ModifyIDCMP(win,MENUFLAGS);
- zx:=-1;
- RETURN;
- END;
- IF (msg^.code = selectDown) THEN (* LMB => Zoomen *)
- x1:=msg^.mouseX; y1:=msg^.mouseY; (* Mittelpunkt *)
- dx:=0;dy:=0;
- DrawFrame(x1,y1,dx,dy); (* ersten Rahmen zeichnen *)
- LOOP
- msg:=GetMsg(win^.userPort);
- IF msg#NIL THEN
- IF (mouseButtons IN msg^.class) & (msg^.code = menuDown) THEN
- DrawFrame(x1,y1,dx,dy); (* Rahmen l÷schen *)
- zx:=-1; (* merken, da▀ Rahmen gel÷scht *)
- ReplyMsg(msg);
- ShowTitle(scr,title); SetNormalPointer(win);
- ModifyIDCMP(win,MENUFLAGS);
- RETURN;
- END;
- IF (mouseButtons IN msg^.class) & (msg^.code = selectUp) THEN
- (* Position der Maus merken (fⁿr Rechteck) *)
- mx:=msg^.mouseX;my:=msg^.mouseY;
- ReplyMsg(msg);
- zx:=x1; zy:=y1; zdx:=dx; zdy:=dy; (* Merken, wo der Rahmen ist *)
- EXIT;
- END;
- IF (mouseMove IN msg^.class) THEN
- DrawFrame(x1,y1,dx,dy); (* alten Rand l÷schen *)
- mx:=msg^.mouseX;my:=msg^.mouseY;
- dx:=x1-mx; dy:=y1-my;
- dx:=ABS(dx); dy:=ABS(dy);
- (* Skalierung. hoffentlich richtig *)
- IF (dy>dx) THEN
- dx:=(xres*dy)/yres;
- ELSE
- dy:=(yres*dx)/xres;
- END;
- DrawFrame(x1,y1,dx,dy); (* neuen Rand zeichnen *)
- END;
- ReplyMsg(msg);
- END;
- END;
- END;
- ShowTitle(scr,title); SetNormalPointer(win);
- ModifyIDCMP(win,MENUFLAGS);
- END Zoom;
-
-
- (*** Prozeduren fⁿr Userinterface ************************************)
-
- PROCEDURE TextColorsOn;
- BEGIN
- SetRGB4(ADR(scr^.viewPort),1,13,13,13);
- SetRGB4(ADR(scr^.viewPort),2,8,8,8);
- SetRGB4(ADR(scr^.viewPort),3,5,5,5);
- SetRGB4(ADR(scr^.viewPort),30,5,5,5);
- SetRGB4(ADR(scr^.viewPort),14,5,5,5);
- SetRGB4(ADR(scr^.viewPort),28,14,14,14);
- SetRGB4(ADR(scr^.viewPort),12,14,14,14);
- SetRGB4(ADR(scr^.viewPort),15,13,13,13);
- END TextColorsOn;
-
- PROCEDURE TextColorsOff;
- BEGIN
- LoadRGB4(ADR(scr^.viewPort),ADR(ColorTable),32);
- END TextColorsOff;
-
- PROCEDURE MenuHandler;
- VAR
- selection, menuNum, itemNum, subNum : CARDINAL;
- BEGIN
- LOOP
- msg:=GetMsg(win^.userPort);
- IF msg=NIL THEN EXIT END;
- class:=msg^.class;
- code :=msg^.code;
- IF class=IDCMPFlagSet{menuVerify} THEN
- TextColorsOn;
- END;
- ReplyMsg(msg); (* mu▀ nach menuVerify-Abfrage stehen *)
- IF (mouseButtons IN class) & (code=menuUp) THEN
- TextColorsOff;
- END;
- IF menuPick IN class THEN
- selection:=code;
- WHILE selection#menuNull DO
- menuNum:=MenuNum(selection);
- itemNum:=ItemNum(selection);
- subNum :=SubNum(selection);
- CASE menuNum OF
- 0 : (* Project Menⁿ *)
- CASE itemNum OF
- 0 : LoadIff(TRUE); zx:=-1;|
- 1 : SaveIff;| (* -""- *)
- 2 : ClearMenuStrip(win);
- ModifyIDCMP(win,IDCMPFlagSet{});
- TextColorsOn; Info(win); TextColorsOff;
- SetMenu(win);
- ModifyIDCMP(win,MENUFLAGS);
- EXIT;|
- 3 : IF NOT saved THEN
- mess:="Really quit?!|Picture will be lost!";
- yes:="QUIT!"; no:="NO!";
- IF Request(win,mess,yes,no) THEN
- QUIT:=TRUE; EXIT;
- END
- ELSE
- QUIT:=TRUE; EXIT;
- END;|
- ELSE;
- END;|
- 1 : (* Setup Menⁿ *)
- CASE itemNum OF
- 0 : (* Aufl÷sung *)
- CASE subNum OF
- 0 : zx:=-1; CreateDisplay(320, 256, 5); EXIT;|
- 1 : zx:=-1; CreateDisplay(640, 256, 4); EXIT;|
- 2 : zx:=-1; CreateDisplay(640, 512, 4); EXIT;|
- ELSE;
- END;|
- 1 : (* Algorithmen *)
- CASE subNum OF
- 0 : Iterations:=FastIter16;|
- 1 : Iterations:=FastIter32;|
- 2 : Iterations:=LongRealIter;|
- ELSE;
- END;|
- 2 : ClearMenuStrip(win);
- ModifyIDCMP(win,IDCMPFlagSet{});
- TextColorsOn;
- GetLimits(scr,xmin,xmax,ymin,ymax,maxiter);
- TextColorsOff;
- IF (zx#-1) AND
- ((xminr#xmin) OR (xmaxr#xmax) OR (* bei neuen *)
- (yminr#ymin) OR (ymaxr#ymax)) THEN (* Grenzen *)
- DrawFrame(zx,zy,zdx,zdy); zx:=-1; (* Rahmen *)
- END; (* l÷schen *)
- SetMenu(win);
- ModifyIDCMP(win,MENUFLAGS);|
- ELSE;
- END;|
- 2 : (* Action Menⁿ *)
- CASE itemNum OF
- 0 : TextColorsOff; title:=NOT title; ShowTitle(scr, title);|
- 1 : TextColorsOff; Zoom;|
- 2 : TextColorsOff;
- DrawFractal(xmin,xmax,ymin,ymax,maxcol,maxiter); |
- ELSE;
- END;|
- ELSE;
- END;
- selection:=NextSelect(selection);
- END; (* WHILE *)
- END; (* IF *)
- END; (* LOOP *)
- END MenuHandler;
-
-
- PROCEDURE CleanUp;
- BEGIN
- CloseIfOpen;
- IF topaz80#NIL THEN CloseFont(topaz80) END;
- END CleanUp;
-
-
- BEGIN
- TermProcedure(CleanUp);
- WITH attr DO
- name :=ADR(TOPAZ); ySize:=8; style:=normalFont;
- flags:=FontFlagSet{romFont,designed};
- END;
- topaz80:=OpenFont(ADR(attr));
- saved:=TRUE;
- zx:=-1; (* noch kein Zoomrahmen gezeichnet *)
- title:=TRUE; (* Titlebar sichtbar *)
- QUIT:=FALSE;
- xres:=320; yres:=256; depth:=5; (* Anfangsaufl÷sung *)
- Iterations:=FastIter16;
- fname:="FFEX_Start.pic";
- CreateDisplay(xres, yres, depth);
- LoadIff(FALSE);
- REPEAT
- WaitPort(win^.userPort);
- MenuHandler;
- UNTIL QUIT;
- END FFEX.
-