home *** CD-ROM | disk | FTP | other *** search
/ Power-Programmierung / CD1.mdf / magazine / pctchnqs / 1991 / number5 / tvtsr / popedit.doc next >
Text File  |  1991-04-12  |  17KB  |  546 lines

  1. POPEDIT - A TSR Editor Based on Borland's TVEDIT Demonstration Program
  2. ----------------------------------------------------------------------
  3. Prepared by Richard Sadowsky, TurboPower Software, 4/91
  4.  
  5. This document describes how to turn Borland's TVEDIT program into a useful
  6. TSR. Borland's license agreement prohibits the distribution of their demo
  7. program. By following these instructions, you can build a popup editor from
  8. their demo. We will call the popup editor POPEDIT.
  9.  
  10. POPEDIT can be compiled as either a swappable or non-swappable TSR. A file
  11. named POPEDIT.INC will control whether POPEDIT is swappable or not. To create
  12. a swappable TSR, define the conditional Swappable in POPEDIT.INC as shown:
  13.  
  14. {$DEFINE Swappable}
  15.  
  16. To create a standard TSR, POPEDIT.INC should not define Swappable. Placing a
  17. period between the { and the $ will cause the text to be taken as a comment,
  18. not a conditional define, as in:
  19.  
  20. {.$DEFINE Swappable}
  21.  
  22. Now, we create the main program file for POPEDIT. The following is the
  23. contents of POPEDIT.PAS:
  24.  
  25.  
  26. {$M 8192,8192,655360}
  27. {$X+,S-,R-}
  28. program PopEdit;
  29.   {-A popup version of Borland's TVEDIT program}
  30.  
  31. {$I POPEDIT.INC}
  32. uses Dos, Objects, Drivers, Memory, Views, Menus, Dialogs,
  33.   StdDlg, MsgBox, App, Buffers, Editors, PopEditM,
  34.   {$IFDEF Swappable}
  35.   OpSwap
  36.   {$ELSE}
  37.   OpTsr
  38.   {$ENDIF}
  39.   ;
  40.  
  41. begin
  42.   TsrInit;
  43. end.
  44.  
  45.  
  46. Now comes the hard part. We need to create a file called POPEDITM.PAS to
  47. implement the application code. The easiest way to do this is to copy
  48. TVEDIT.PAS to POPEDITM.PAS. Now follow the instructions below to create a TSR
  49. out of TVEDIT:
  50.  
  51.   1) Delete all text up to the first occurance of the word const (except
  52.      Borland's copyright). The first constant definition looks like this:
  53.  
  54. const
  55.   HeapSize = 32 * (1024 div 16)
  56.  
  57.      That means the program statement, the $M and other compiler directives,
  58.      and the uses list all need to be deleted.
  59.  
  60.   2) Above the word const, add the following lines:
  61.  
  62. {$X+,S-,R-,V-}
  63.  
  64. {$I OPDEFINE.INC}
  65. {$I POPEDIT.INC}
  66.  
  67. unit PopEditM;
  68.  
  69. interface
  70.  
  71. uses Dos, Objects, Drivers, Memory, Views, Menus, Dialogs,
  72.   StdDlg, MsgBox, App, Calc, Buffers, Editors,
  73.   TvScreen,
  74.   {$IFDEF Swappable}
  75.   OpSwap1
  76.   {$ELSE}
  77.   OpInt,
  78.   OpTsr
  79.   {$ENDIF}
  80.   ;
  81.  
  82.   3) Scroll down until you come to the definition of the variable ClipWindow.
  83.      After this declaration add the following lines:
  84.  
  85. procedure TsrInit;
  86.   {-Initialize our TSR and go resident}
  87.  
  88. implementation
  89.  
  90.   4) Executing a program within a TSR is quite a difficult task. TVEDIT
  91.      contains a menu option for a DOS Shell. You need to comment out this
  92.      feature. The easiest way to do this is just to comment out the body of
  93.      the procedure DosShell. It may also be desirable to remove the Dos Shell
  94.      option from the Files submenu (be careful with your parentheses if you do
  95.      so).
  96.  
  97.   5) Now go to the bottom of the file, and delete the main program block. This
  98.      is the code that says:
  99.  
  100.  
  101. begin
  102.   EditorApp.Init;
  103.   EditorApp.Run;
  104.   EditorApp.Done;
  105. end.
  106.  
  107.  
  108.   6) Now add the following large block of code (approximately 360 lines) to
  109.      the end of the file after the definition of TEditorApp.OutOfMemory:
  110.  
  111.   {=====================================================================}
  112. const
  113.   {$IFDEF Swappable}
  114.   ResidentHeap = 256 * 1024;
  115.   {$ELSE}
  116.   ResidentHeap = 128 * 1024;
  117.   {$ENDIF}
  118.  
  119.   MaxParas     : Word = ResidentHeap div 16;
  120.  
  121.   ModuleName     : String[7] = 'POPEDIT'; {module name for standard interface}
  122.   OurHotKey      : Word = $0844;  {Alt + F10}
  123.  
  124.   {$IFDEF Swappable}
  125.   SwapPathName   : String[64] = 'C:\';
  126.   SwapName1      : String[12] = 'POPEDIT1.$$$';
  127.   SwapName2      : String[12] = 'POPEDIT2.$$$';
  128.   {$ENDIF}
  129.   FirstTimeInPopup : Boolean = True;
  130.  
  131. procedure StartupDialog;
  132. var
  133.   Control : Word;
  134. begin
  135.   Control := MessageBox(^C'POPEDIT 1.0' +
  136.                         ^M^C'Installing as a TSR' +
  137.                         ^M^C'Press Alt-F10 to popup',
  138.                         Nil, mfInformation + mfOKCancel);
  139.   if Control = cmCancel then begin
  140.     EditorApp.Done;
  141.     Halt;
  142.   end;
  143. end;
  144.  
  145. function ShutDownDialog : Boolean;
  146.   {-Returns True if OK to shut down}
  147. var
  148.   Control : Word;
  149. begin
  150.   Control := MessageBox(^C'Unloading resident POPEDIT',
  151.                         Nil, mfInformation + mfOKCancel);
  152.   ShutDownDialog := Control = cmOK;
  153. end;
  154.  
  155. procedure ReadyBuffers;
  156. var
  157.   H : Word;
  158. begin
  159.   if FirstTimeInPopup then begin
  160.     {if we've never been in popup, we need to init Buffers unit}
  161.     H := PtrRec(HeapEnd).Seg - PtrRec(HeapPtr).Seg;
  162.     if H > HeapSize then
  163.       BufHeapSize := H - HeapSize
  164.     else
  165.       BufHeapSize := 0;
  166.     InitBuffers;
  167.     ClipWindow := OpenEditor('', False);
  168.     if ClipWindow <> nil then begin
  169.       Clipboard := ClipWindow^.Editor;
  170.       Clipboard^.CanUndo := False;
  171.     end;
  172.     FirstTimeInPopup := False;
  173.   end;
  174. end;
  175.  
  176. procedure TurnOffTurboVision;
  177. begin
  178.   Drivers.HideMouse;
  179.   DoneEvents;
  180.   DoneVideo;
  181.   DoneMemory;
  182. end;
  183.  
  184. procedure TurnOnTurboVision;
  185. begin
  186.   InitMemory;
  187.   InitVideo;
  188.   InitEvents;
  189.   (* InitSysError; *) {!! do not call in a popup !!}
  190.   EditorApp.Redraw;
  191.   Drivers.ShowMouse;
  192. end;
  193.  
  194. procedure SaveMouse(var MSP : MouseStatePtr);
  195. var
  196.   MStateSize : Word;
  197. begin
  198.   MSP := nil;
  199.   if TvScreen.MouseInstalled then begin
  200.     MStateSize := TvScreen.MouseStateBufferSize;
  201.     if (MStateSize = 0) or (MStateSize > MaxAvail) then
  202.       Exit;
  203.     TvScreen.SaveMouseState(MSP);
  204.     TvScreen.InitializeMouse;
  205.   end
  206. end;
  207.  
  208. procedure RestoreMouse(MSP : MouseStatePtr);
  209. begin
  210.   if (MSP <> nil) then
  211.     TvScreen.RestoreMouseState(MSP);
  212. end;
  213.  
  214. {$IFDEF Swappable}
  215. procedure PopEditMain; Far;
  216. {$ELSE}
  217. procedure PopEditMain(var Regs : Registers); Far;
  218. {$ENDIF}
  219. var
  220.   MSP : MouseStatePtr;
  221.   ScreenBuf : Pointer;
  222.   XY, SL : Word;
  223.  
  224. begin
  225.   {save mouse, screen and cursor state}
  226.   SaveMouse(MSP);
  227.   ReinitVideo;
  228.   if not InTextMode then begin
  229.     RestoreMouse(MSP);
  230.     Exit;
  231.   end;
  232.   if not SaveScreen(ScreenBuf) then begin
  233.     RestoreMouse(MSP);
  234.     Exit;
  235.   end;
  236.   GetCursorState(XY, SL);
  237.   ReadyBuffers;
  238.  
  239.   {Turn TV back on}
  240.   TurnOnTurboVision;
  241.  
  242.   {call application's Run method}
  243.   EditorApp.Run;
  244.  
  245.   {Shut down TV again}
  246.   TurnOffTurboVision;
  247.  
  248.   {restore mouse, screen and cursor state}
  249.   RestoreScreen(ScreenBuf);
  250.   RestoreCursorState(XY, SL);
  251.   RestoreMouse(MSP);
  252. end;
  253.  
  254. function AttemptDisable : Byte;
  255. begin
  256.   if SafeToDisable then begin
  257.     {turn TV on}
  258.     ReadyBuffers;
  259.     TurnOnTurboVision;
  260.     {display unload dialog box, allow user to cancel unload}
  261.     if ShutDownDialog then begin
  262.       Drivers.HideMouse;
  263.       {call the application's Done destructor}
  264.       EditorApp.Done;
  265.       {attempt to disable the TSR}
  266.       AttemptDisable := Ord(DisableTSR);
  267.     end
  268.     else begin
  269.       {user cancelled unload attempt}
  270.       TurnOffTurboVision;
  271.       AttemptDisable := 2;
  272.     end;
  273.   end;
  274. end;
  275.  
  276. {$IFDEF Swappable}
  277. procedure OurDisablePopup; Far;
  278. begin
  279.   LongInt(CSSwapData^.ThisIFC.UserData) := LongInt(AttemptDisable);
  280. end;
  281. {$ELSE}
  282. procedure OurDisablePopup(BP : Word); Interrupt;
  283. var
  284.   Regs : IntRegisters absolute BP;
  285.   SavePSP : Word;
  286. begin
  287.   SavePSP := GetPSP;
  288.   SetPSP(PrefixSeg);
  289.   Regs.AL := AttemptDisable;
  290.   SetPSP(SavePSP);
  291. end;
  292. {$ENDIF}
  293.  
  294. procedure UnloadFromCommandLine;
  295.   {-Unload resident copy of POPEDIT (if possible) and report results}
  296. var
  297.   IFC : IfcPtr;
  298.   {$IFDEF Swappable}
  299.   SaveSwapMsg : Boolean;
  300.   {$ELSE}
  301.   Regs : IntRegisters;
  302.   {$ENDIF}
  303.   Result : Byte;
  304. begin
  305.   RestoreAllVectors;
  306.   IFC := ModulePtrByName(ModuleName);   {get the IFCPtr for this module}
  307.   if IFC <> nil then begin              {make sure it is already installed}
  308.     {$IFDEF Swappable}
  309.     SaveSwapMsg := IFC^.CSDataPtr^.SwapMsgOn;  {save state of swap messages}
  310.     IFC^.CSDataPtr^.SwapMsgOn := False; {disable swap messages}
  311.     IFC^.CmdEntryPtr;                   {call the CmdEntryPtr}
  312.     Result := Byte(LongInt(IFC^.UserData));
  313.     IFC^.CSDataPtr^.SwapMsgOn := SaveSwapMsg;  {restore state of swap messages}
  314.     {$ELSE}
  315.     Regs.AH := Ord('U');
  316.     EmulateInt(Regs, IFC^.CmdEntryPtr);
  317.     Result := Regs.AL;
  318.     {$ENDIF}
  319.     case Result of
  320.       0 : WriteLn('Unable to unload POPEDIT');
  321.       1 : WriteLn('POPEDIT unloaded');
  322.       2 : WriteLn('Unload cancelled by user');
  323.       else
  324.         WriteLn('Serious error attempting to unload POPEDIT');
  325.     end;
  326.   end
  327.   else
  328.     WriteLn('POPEDIT not installed.');
  329.   Halt;
  330. end;
  331.  
  332. procedure Abort(S : String);
  333. begin
  334.   WriteLn(S);
  335.   Halt(1);
  336. end;
  337.  
  338. procedure TsrInit;
  339. var
  340.   CmdLine : String;
  341.  
  342. begin
  343.   if ParamCount > 0 then begin
  344.     CmdLine := ParamStr(1);
  345.     if (Length(CmdLine) = 2) and (CmdLine[1] in ['/','-']) and
  346.        (UpCase(CmdLine[2]) = 'U') then begin
  347.       UnloadFromCommandLine;
  348.       Halt;
  349.     end;
  350.   end;
  351.   {check to see if we're already installed}
  352.   if ModuleInstalled(ModuleName) then
  353.     Abort('POPEDIT is already loaded. Aborting...');
  354.  
  355.   {install the module with special external interface to allow disabling}
  356.   {$IFDEF Swappable}
  357.   InstallModule(ModuleName,OurDisablePopup);
  358.   {$ELSE}
  359.   InstallModule(ModuleName, @OurDisablePopup);
  360.   {$ENDIF}
  361.  
  362.   {check to see if SideKick is loaded}
  363.   if SideKickLoaded then
  364.     Abort('Can''t be loaded after SideKick!');
  365.  
  366.   if not DefinePop(OurHotKey, PopEditMain,
  367.                    Ptr(SSeg, SPtr)
  368.                    {$IFNDEF Swappable}
  369.                    , True
  370.                    {$ENDIF}
  371.                    ) then
  372.     Abort('Unable to define popup');
  373.  
  374.   EditorApp.Init;
  375.  
  376.   {display a dialog box, if user cancels, application will not go resident}
  377.   StartUpDialog;
  378.  
  379.   {TEditorApp.Init initializes the clipboard window. It must be destroyed,
  380.    since it uses Buffers and we are about to call DoneBuffers. The clipboard
  381.    will be reinitialized by the popup the first time it is called. It would
  382.    have been easy to modify TEditorApp.Init not to initialize the clipboard,
  383.    but we wanted to avoid changing as much of the application code as possible
  384.    to make it easier to describe how to make this TSR.}
  385.   DeskTop^.Delete(ClipWindow);
  386.   Dispose(ClipWindow, Done);
  387.  
  388.   {Shut down TV's system error handlers}
  389.   DRIVERS.DoneSysError;
  390.  
  391.   {Shut down TV's usage of video, memory and events}
  392.   TurnOffTurboVision;
  393.  
  394.   {Shut down Buffers unit}
  395.   BUFFERS.DoneBuffers;
  396.  
  397.   {$IFDEF Swappable}
  398.   {$IFDEF SupportXms}
  399.   SwapUseXms := True;
  400.   {$ENDIF}
  401.   {$ENDIF}
  402.  
  403.   PopupsOn;
  404.   {$IFDEF Swappable}
  405.   {terminate and stay resident}
  406.   StayResSwap(ParagraphsToKeep + MaxParas, 0,
  407.               SwapPathName + SwapName1,
  408.               SwapPathName + SwapName2, True);
  409.   {if we get here we failed}
  410.   {$ELSE}
  411.   StayRes(ParagraphsToKeep + MaxParas, 0);
  412.   {$ENDIF}
  413.   WriteLn('Error going resident - press return');
  414.   ReadLn;
  415. end;
  416.  
  417. end.
  418.  
  419.  
  420.   7) This step is optional. A well behaved TSR should call interrupt 28h while
  421.      waiting for keystrokes. If it fails to do so, no other TSR (that needs
  422.      DOS services) will be able to popup if you are popped up over the DOS
  423.      command line. The code Borland uses within Turbo Vision to read the
  424.      keyboard does not call int 28h. A perfect place to call int 28h is within
  425.      the TApplication's Idle method. To do so, you'll need to add the line
  426.      marked with {!!} to the definition of the TEditorApp object:
  427.  
  428. type
  429.   PEditorApp = ^TEditorApp;
  430.   TEditorApp = object(TApplication)
  431.     constructor Init;
  432.     destructor Done; virtual;
  433.     procedure HandleEvent(var Event: TEvent); virtual;
  434.     procedure InitMenuBar; virtual;
  435.     procedure InitStatusLine; virtual;
  436.     procedure OutOfMemory; virtual;
  437.     procedure Idle; Virtual;                           {!!}
  438.   end;
  439.  
  440.      Now we need to add an implementation for the Idle method. Somewhere after
  441.      the definition of TEditorApp, add the following:
  442.  
  443.  
  444. procedure TEditorApp.Idle;
  445. begin
  446.   TApplication.Idle;
  447.   asm
  448.     int     28h
  449.   end;
  450. end;
  451.  
  452.  
  453.   8) Be sure you've created the file POPEDIT.INC and set it to the desired
  454.      state (with Swappable defined or not). Now compile POPEDIT.PAS. In
  455.      addition to the units used by TVEDIT, POPEDIT will use TVSCREEN, and
  456.      either OPSWAP and OPSWAP1 (for swappable TSRs) or OPINT and OPTSR (for
  457.      standard TSRs).
  458.  
  459.  
  460. Swappable versus Standard TSRs
  461. ------------------------------
  462. This program is most useful if turned into a swappable TSR. As a swapper, it
  463. will take less then 7K of RAM away from other applications. When its hotkey is
  464. invoked, it will swap out whatever is above it (to EMS, XMS, or disk, based on
  465. availability), and swap itself in. TurboPower's documentation explains the
  466. pros and cons of swappable TSRs.
  467.  
  468. If you choose to make POPEDIT a standard TSR, it will take a good chunk of
  469. RAM. As coded above, it will take 240K bytes of RAM.
  470.  
  471.  
  472. Running POPEDIT
  473. ---------------
  474. To load POPEDIT as a RAM resident program (a TSR), simply type POPEDIT.
  475. POPEDIT will present you with a dialog box. If you select OK, then POPEDIT
  476. will attempt to go resident. If you select Cancel, POPEDIT will terminate
  477. without remaining resident.
  478.  
  479. Note that for a swappable TSR the program shown above will swap to C:\ if EMS
  480. or XMS is not available. Therefore there would have to be sufficient hard disk
  481. space on C: for the swap files. See the TurboPower demonstration swappable
  482. TSRs DESKPOP or PSCREEN for examples of more flexible control over the
  483. resource used to swap to and from.
  484.  
  485. The code above defines Alt-F10 as the hotkey. When the hotkey is pressed, the
  486. application will popup. It is then essentially the same as TVEDIT. To exit the
  487. popup and return to the underlying application (or DOS command line), press
  488. Alt-X or select Exit from the POPEDIT Files menu. When you press the hotkey
  489. again, POPEDIT will pop back up in the same state it was when you last exited.
  490.  
  491. If you have any files opened and modified when you exit the popup, POPEDIT
  492. will ask if you wish to save them. It is a good idea when using a text editor
  493. implemented as a TSR to save your files before popping down (exiting the
  494. popup), so you don't forget to save them before turning the machine off or
  495. rebooting.
  496.  
  497. POPEDIT has the ability to unload itself. This is done by running POPEDIT
  498. again with the command line switch /u. If it is safe to unload POPEDIT, you
  499. will be prompted by a dialog box. If you select OK, then POPEDIT will be
  500. unloaded. If you select CANCEL, then POPEDIT will not be unloaded. See the
  501. TurboPower documentation for information on unloading TSRs.
  502.  
  503. POPEDIT can popup only over text video modes. If you try to popup over
  504. graphics, POPEDIT will politely refuse to popup.
  505.  
  506.  
  507. Technical Issues
  508. ----------------
  509. Turning TVEDIT into a TSR proved to be quite a chore. In addition to the
  510. typical problems encountered writing any TSR that uses Turbo Vision, TVEDIT
  511. introduced some interesting problems.
  512.  
  513. In addition to calling TApplication.Init, TEditorApp.Init calls InitBuffers
  514. from the BUFFERS unit, and instantiates a TEditWindow object for its
  515. clipboard. The TEditWindow uses the facilities of the BUFFERS unit. BUFFERS is
  516. a unit that implements a simple moveable memory manager. In a TSR application,
  517. BUFFERS presents a problem, since the actual size of the heap isn't known
  518. until the application goes resident. Therefore, DoneBuffers has to be called
  519. before going resident (or alternatively, never call InitBuffers or instantiate
  520. the TEditWindow in the first place, we chose the DoneBuffers technique to
  521. limit the number of changes needed to Borland's source code). Before calling
  522. DoneBuffers, we must destruct any objects using memory from the BUFFERS unit.
  523. The code:
  524.  
  525.   DeskTop^.Delete(ClipWindow);
  526.   Dispose(ClipWindow, Done);
  527.  
  528. from the TSRInit procedure disposes of the PEditWindow instantiated in
  529. TEditorApp.Init, clearing the way for DoneBuffers to be called before going
  530. resident.
  531.  
  532. Of course, this means that the first time we popup, the BUFFERS unit is
  533. uninitialized and no clipboard has been instantiated. The typed constant
  534. FirstTimeInPopup allows the popup to properly detect when it is popped up for
  535. the first time so InitBuffers can be called and the clipboard instantiated.
  536.  
  537. The BUFFERS unit is tightly intertwined with the EDITORS unit. While objects
  538. from the EDITORS unit are instantiated, it would be a serious error to call
  539. DoneBuffers. Therefore the BUFFERS unit can not be turned on and off like many
  540. of the other Turbo Vision facilities. Since BUFFERS contains no event handlers,
  541. there is no need to turn it off once it has been initialized the first time
  542. the popup is called.
  543.  
  544. Due to the use of the BUFFERS unit, code within the popup and to unload the
  545. TSR is more complicated then it is for simpler Turbo Vision programs.
  546.