home *** CD-ROM | disk | FTP | other *** search
/ PC Plus SuperCD (UK) 2000 March / pcp161b.iso / handson / archive / Issue145 / Delphi / MacroEd / maced.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  1998-07-10  |  16.2 KB  |  516 lines

  1. unit Maced;
  2. { -------------------------------------
  3.   --- PC Plus sample Delphi program ---
  4.   --- Text editor with 'macros'     ---
  5.   --- Author: Huw Collingbourne     ---
  6.   -------------------------------------
  7.  
  8.   -------------------------------------------------
  9.   IMPORTANT NOTES ON NEW DRAG-DROP-ENABLED VERSION:
  10.   -------------------------------------------------
  11.   This new version adds the ability to drag a file from Explorer
  12.   When the file is dropped, it is loaded if it is found to be of the
  13.   appropriate type (TXT or PAS) otherwise an error message is
  14.   shown. There is a limit to the file size possible to load (32k in Delphi 1,
  15.   closer to 64K in 32-bit Delphis).
  16.  
  17.   Note: No checks are made for file size. For a more robust application,
  18.   you may either want to add Exception handling to recover from errors
  19.   caused by attempting to load very long files or you may want to convert this
  20.   application (using Delphi 2 or above) to use a RichEdit control instead
  21.   of a Memo.
  22.  
  23.   The main code relevant to drag-drop file loading is indicated by a
  24.   comment starting with these characters
  25.           {!!
  26.   -------------------------------------------------
  27.  
  28.  
  29.  
  30.   The following notes explain how to use the 'macro' feature....
  31.   USAGE:
  32.  
  33.   RECORD a new macro (or edit an existing macro): Press CTRL-R
  34.  
  35.   PLAY BACK a macro: Press CTRL+ALT+(a char)
  36.   -------------------------------------
  37.  
  38.  
  39.   NOTES:
  40.   MACROS
  41.   * Macros are stored as strings. The first letter in the macro text
  42.   is the 'activator char' and will not be displayed when the macro is run.
  43.   e.g.  if the macro test is
  44.          'Xrated'
  45.         -'X' is the activator char, so you can run the macro by
  46.              pressing CTRL-ALT-X
  47.         - the macro text that appears in the editor will be
  48.              'rated'
  49.  
  50.   * A list of macros is stored in a TStringList object called Macros
  51.  
  52.   * Saving and Loading macros.
  53.   At first sight it might seem that you should be able to save
  54.   and load macros in the same way as the text in the Memo, using Delphi's
  55.   SaveToFile and LoadFromFile methods.
  56.  
  57.   The trouble is that each item of macro text may include carriage returns.
  58.   A 3-line macro would be saved in the form of 3 separate lines of text.
  59.   On reloading, you would end up with 3 different macros!
  60.  
  61.   To get around this problem, I've created a simple record containing a string
  62.   and written typed files of these records. This approach also gives me the
  63.   option of adding extra fields to the record - such as macro control keys
  64.   (CTRL, ALT, SHIFT etc.), should I wish to extend the features of the macros
  65.   at a later stage.
  66.  
  67.  
  68.   }
  69.  
  70. interface
  71.  
  72. uses
  73.   SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  74.   Forms, Dialogs, Menus, StdCtrls, ExtCtrls,
  75.   Macrec,   { use Macrec unit }
  76.   ShellAPI; {!! use ShellAPI unit }
  77.  
  78. type
  79.   TMainForm = class(TForm)
  80.     MainMenu1: TMainMenu;
  81.     EditorMemo: TMemo;
  82.     OpenDialog1: TOpenDialog;
  83.     SaveDialog1: TSaveDialog;
  84.     FileMnu: TMenuItem;
  85.     FileNew: TMenuItem;
  86.     FileSave: TMenuItem;
  87.     FileLoad: TMenuItem;
  88.     N1: TMenuItem;
  89.     FileExit: TMenuItem;
  90.     FileSaveAs: TMenuItem;
  91.     StatusPanel: TPanel;
  92.     Macros1: TMenuItem;
  93.     SaveMac: TMenuItem;
  94.     LoadMac: TMenuItem;
  95.     ClearMac: TMenuItem;
  96.     RecordMac: TMenuItem;
  97.     DisplayMac: TMenuItem;
  98.     procedure FileNewClick(Sender: TObject);
  99.     procedure FileSaveClick(Sender: TObject);
  100.     procedure FileSaveAsClick(Sender: TObject);
  101.     procedure FileLoadClick(Sender: TObject);
  102.     procedure FileExitClick(Sender: TObject);
  103.     procedure EditorMemoKeyDown(Sender: TObject; var Key: Word;
  104.       Shift: TShiftState);
  105.     procedure EditorMemoKeyUp(Sender: TObject; var Key: Word;
  106.       Shift: TShiftState);
  107.     procedure FormActivate(Sender: TObject);
  108.     procedure EditorMemoKeyPress(Sender: TObject; var Key: Char);
  109.     procedure FormCreate(Sender: TObject);
  110.     procedure FormDestroy(Sender: TObject);
  111.     procedure LoadMacClick(Sender: TObject);
  112.     procedure ClearMacClick(Sender: TObject);
  113.     procedure SaveMacClick(Sender: TObject);
  114.     procedure RecordMacClick(Sender: TObject);
  115.     procedure DisplayMacClick(Sender: TObject);
  116.   private
  117.     { Private declarations }
  118.  
  119.   public
  120.     { Public declarations }
  121.     function ConfirmFileSave(FileName : string) : boolean;
  122.     procedure GetCursorPos( var LinePos, ColPos : LongInt );
  123.     procedure ShowCursorPos;
  124.  
  125.     { ** Macro methods }
  126.     procedure ClearMacros;
  127.     procedure RecordMacro;
  128.     function GetMacStringIndex( ch: char ) : integer;
  129.     function GetMacString( ch : char ) : string;
  130.  
  131.        {!! declare proc to respond to dragdrop messages }
  132.     procedure RespondToMessage(var Msg: Tmsg; var Handled: Boolean);
  133.     function FileTypeOK( fname : string ) : boolean;
  134.     procedure TryToLoadFile( fname  : string);
  135.   end;
  136.  
  137. const
  138. { codes for special 'editing' characters }
  139.   CR = chr(13);    { carriage return }
  140.   LF = chr(10);    { linefeed        }
  141.   CRLF = CR + LF;
  142.   BS = chr(8);     { backspace       }
  143.   DEL = chr(16);   { delete          }
  144.   MACROFILE        = 'Macros.mac';
  145.  
  146. type               { define record for file operations }
  147.   StringRecord = record
  148.     str : string[255];
  149.   end;
  150.  
  151. var
  152.   MainForm         : TMainForm;
  153.   TextFileName     : string;       { ** current file name                      }
  154.   MacroFileName    : string;       { ** current macro file name                }
  155.   Macros           : TStringList;  { ** store macros in this StringList        }
  156.  
  157. implementation
  158.  
  159. {$R *.DFM}
  160.  
  161. function TMainForm.FileTypeOK( fname : string ) : boolean;
  162. { check file extsnion to see if it matches one of the expected file types }
  163. { if so, return true, else return false }
  164. var
  165.    Ext : string;
  166. begin
  167.   Ext := UpperCase(ExtractFileExt(fname));
  168.   if (Ext = '.TXT') or (Ext = '.PAS') then
  169.      result := true
  170.   else
  171.      result := false;
  172. end;
  173.  
  174.  
  175. procedure TMainForm.TryToLoadFile( fname : string);
  176. begin
  177.    if FileExists( fname ) then
  178.    begin
  179.      EditorMemo.Lines.LoadFromFile( fname );
  180.      Caption := 'Text Editor - ' + ExtractFilename( fname );
  181.      TextFileName := fname; { ** reset TextFileName on FileLoad }
  182.    end
  183.    else
  184.      MessageDlg('Sorry. Can''t load this file. '+ fname +
  185.                 ' does not exist!',
  186.                  mtInformation, [mbOK], 0);
  187. end;
  188.  
  189. procedure TMainForm.RespondToMessage(var Msg: Tmsg; var Handled: Boolean);
  190. {!! Respond to filename that's been drag-dropped from Explorer. Load
  191.   file if it's a valid text file, else display error message }
  192. const
  193.   BUFFLEN = 255;
  194. var
  195.   buffer : array[0..BUFFLEN] of char;
  196.   fname : string;
  197. begin
  198.    if Msg.Message = WM_DROPFILES then
  199.    begin
  200.       DragQueryFile(Msg.WParam, 0, buffer, BUFFLEN);
  201.       fname  := StrPas(buffer);
  202.       DragFinish(Msg.WParam);
  203.       Handled := True;
  204.       if FileTypeOK( fname ) then
  205.          TryToLoadFile( fname )
  206.       else
  207.          MessageDlg('Sorry. Cannot load files with the extension '+
  208.                  ExtractFileExt(fname) +
  209.                 '!',  mtInformation, [mbOK], 0);
  210.    end;
  211. end;
  212.  
  213. { Macro Methods }
  214.  
  215. procedure TMainForm.RecordMacro;
  216. { Pop up a window in which a new macro can be recorded or an existing
  217.   macro can be edited. }
  218. var
  219.   macChar     : char;
  220.   macText     : string;
  221.   macPos      : integer;
  222. begin
  223.     macText := '';
  224.     macChar := macText[1];
  225.     macPos  := -1;
  226.     if MacroForm.ShowModal = mrOK then  { if user chooses to use the newly     }
  227.       begin                             { recorded or edited macro...          }
  228.         macChar := UpCase(MacroForm.KeyCharEdit.Text[1]);{ get activator       }
  229.         macText := macChar + MacroForm.MacroMemo.Text;   { char and macro      }
  230.         macpos := GetMacStringIndex( macChar );          { text from MacroForm }
  231.         if macPos > - 1 then            { if there is a previous macro with the}
  232.           Macros.Delete( macPos );      { activator char, delete it.           }
  233.         Macros.Add( macText );          { Add the new macro to the Macros list }
  234.       end;
  235. end;
  236.  
  237. procedure TMainForm.ClearMacros;
  238. { delete strings from StringList of macros }
  239. begin
  240.    Macros.Clear;
  241. end;
  242.  
  243. function TMainForm.GetMacStringIndex( ch: char ) : integer;
  244. {** Return the index of a macro string in Macros StringList,
  245.   based on the position of its initial 'activator' character }
  246. var
  247.   i          : integer;
  248.   stopsearch : boolean; { true when macro is found or no more macros remain    }
  249.   macroPos   : integer;
  250. begin
  251.    i := 0;
  252.    macroPos := -1;        { no macro found, by default        }
  253.    stopsearch := false;
  254.    if Macros.Count = 0 then
  255.        stopsearch := true; { check there are macros available }
  256.      While not ( stopsearch ) do
  257.      begin
  258.                           { try to match the character, ch, with the first char}
  259.                           { of the item at index i in the Macros StringList    }
  260.        if ( Macros[i][1] = UpCase(ch) ) then
  261.           begin
  262.             macroPos := i;
  263.             stopsearch := true;
  264.           end
  265.        else i := i+1;
  266.        if (i = Macros.Count) then
  267.           stopsearch := true; { don't try to search beyond last item in Macros }
  268.      end;
  269.       GetMacStringIndex := macroPos;
  270. end;
  271.  
  272.  
  273.  
  274.  
  275. function TMainForm.GetMacString( ch : char ) : string;
  276. {** Return a string associated with the HotKey char, ch }
  277. var
  278.   macstr     : string;  { the macro string to be returned. Blank if no match   }
  279.   macpos     : integer;
  280. begin                   { Macros StringList                                    }
  281.    macstr := '';
  282.    macpos := GetMacStringIndex( ch );
  283.    if macpos > -1 then { if a macro is already assigned to activator char, ch  }
  284.      begin            { return the associated string minus the initial char    }
  285.       macstr := Macros[macpos];
  286.       macstr := copy( macstr, 2, Length(macstr)-1);
  287.      end;
  288.    GetMacString := macstr;
  289. end;
  290.  
  291.  
  292. function TMainForm.ConfirmFileSave(FileName : string) : boolean;
  293. { put up a dialog box to confirm that the existing file should be
  294.   overwritten. This function returns True if the Yes button is pressed,
  295.   otherwise it returns false. Note that you can test for the
  296.   buttons used in dialogs with these constants: mrNone,mrOk,mrCancel,
  297.   mrAbort,mrRetry,mrIgnore,mrYes,mrNo,mrAbort,mrRetry,mrIgnore,mrAll }
  298.  
  299. begin
  300.     if MessageDlg(FileName + ' already exists. Save anyway?',
  301.                         mtConfirmation, mbYesNoCancel, 0)
  302.                         = mrYes then
  303.       ConfirmFileSave := true
  304.     else
  305.       ConfirmFileSave := false;
  306. end;
  307.  
  308. procedure TMainForm.FileNewClick(Sender: TObject);
  309. begin
  310.   EditorMemo.Clear;
  311.   TextFileName := '' ; { ** on FileNew rest TextFileName to '' }
  312.   OpenDialog1.Filename := '*.*';
  313.   Caption := 'Text Editor - [Untitled]';
  314. end;
  315.  
  316. procedure TMainForm.FileSaveClick(Sender: TObject);
  317. {** If TextFileName has been assigned, save text to file.
  318.     Otherwise, pass processing to FileSaveAs }
  319. begin
  320.   if TextFileName <> '' then
  321.     EditorMemo.Lines.SaveToFile(TextFileName)
  322.   else FileSaveAsClick(Sender);
  323. end;
  324.  
  325. procedure TMainForm.FileSaveAsClick(Sender: TObject);
  326. var
  327.    SaveFile : boolean;
  328. begin
  329.    SaveFile := true;
  330.    with SaveDialog1 do
  331.     if Execute then
  332.     begin
  333.       if FileExists(FileName) then
  334.          SaveFile := ConfirmFileSave(FileName);
  335.       If SaveFile then
  336.       begin
  337.          EditorMemo.Lines.SaveToFile(Filename);
  338.          Caption := 'Text Editor - ' + ExtractFilename(FileName);
  339.          TextFileName := FileName; {** on file SaveAs, set TextFileName }
  340.       end;
  341.     end;
  342. end;
  343.  
  344. procedure TMainForm.FileLoadClick(Sender: TObject);
  345. begin
  346.   with OpenDialog1 do
  347.     if Execute then
  348.          TryToLoadFile(FileName);
  349. end;
  350.  
  351. procedure TMainForm.FileExitClick(Sender: TObject);
  352. begin
  353.   Close;
  354. end;
  355.  
  356. procedure TMainForm.GetCursorPos( var LinePos, ColPos : LongInt );
  357. { Assign the current Line Number and Column Number of the cursor to the
  358.   arguments: LinePos and ColPos.
  359.   Note the 'var' parameters - var parameters are passed by reference so the
  360.   values of the original variables are changed by actions inside this procedure}
  361. begin
  362.    LinePos := SendMessage(EditorMemo.Handle, EM_LINEFROMCHAR, EditorMemo.SelStart,0);
  363.    ColPos := (SendMessage(EditorMemo.Handle, EM_LINEINDEX, LinePos, 0));
  364.    ColPos := EditorMemo.SelStart - ColPos;
  365. end;
  366.  
  367. procedure TMainForm.ShowCursorPos;
  368. var
  369.    LineNum, ColNum : LongInt;
  370. begin
  371.    GetCursorPos( LineNum, ColNum );
  372.    StatusPanel.Caption := (' Line: ' + IntToStr(LineNum) +
  373.                        ', Col: ' + IntToStr((ColNum)));
  374. end;
  375.  
  376.  
  377.  
  378. procedure TMainForm.EditorMemoKeyDown(Sender: TObject; var Key: Word;
  379.   Shift: TShiftState);
  380. { When a keydown event is fired, check to see if a 'shift' key or keys
  381.   such as CTRL or ALT has been pressed along with a character A to z.
  382.   If so, see if macro text is associated with this HotKey. If so,
  383.   insert the text into the current line of the Memo }
  384. var
  385.    LineNum, ColNum, StartPos : LongInt;
  386.    aLine, st: string;
  387. begin
  388.   if (ssCtrl in Shift) then
  389.      if (ssAlt in Shift) and (chr(Key) in ['A'..'Z','a'..'z'] ) then
  390.      begin
  391.       st := GetMacString(chr(Key));
  392.       if st <> '' then
  393.       begin
  394.         GetCursorPos( LineNum, ColNum );
  395.         StartPos := EditorMemo.selStart;
  396.         aLine := EditorMemo.Lines[LineNum];      { get current line from Memo  }
  397.         insert(st, aLine, ColNum+1 );            { insert Macro text           }
  398.         EditorMemo.Lines[LineNum] := aLine;      { put altered line in Memo    }
  399.         EditorMemo.selStart := StartPos + Length(st);{ put cursor after mactext}
  400.       end;
  401.     end
  402.     else if (UpCase(chr(Key)) = 'R') then
  403.          RecordMacro;
  404. end;
  405.  
  406.  
  407.  
  408. procedure TMainForm.EditorMemoKeyUp(Sender: TObject; var Key: Word;
  409.   Shift: TShiftState);
  410. begin
  411.   ShowCursorPos;
  412. end;
  413.  
  414. procedure TMainForm.FormActivate(Sender: TObject);
  415. begin
  416.   ShowCursorPos;
  417. end;
  418.  
  419. procedure TMainForm.EditorMemoKeyPress(Sender: TObject; var Key: Char);
  420. { Some key combinations generate foreign or non-printable characters
  421.   - e.g. CTRL-ALT-E = Θ
  422.   All ASCII characters are handled by this method. We just filter out
  423.   unwanted characters. We do allow CR+LF, however!
  424. }
  425. begin    { filter foreign + nonprintable chars }
  426.  If not (Key in [CR, LF, BS, DEL, ' '..'~']) then
  427.    Key := Chr(0);
  428. end;
  429.  
  430. procedure TMainForm.FormCreate(Sender: TObject);
  431. { Init global variables }
  432. begin
  433.   TextFileName := '';
  434.   Macros := TStringList.Create;
  435.   {!! set up app to accept files dragged from Explorer }
  436.   DragAcceptFiles(MainForm.Handle, true);
  437.   Application.OnMessage := RespondToMessage;
  438. end;
  439.  
  440. procedure TMainForm.FormDestroy(Sender: TObject);
  441. begin
  442.   Macros.Free; {** destroy StringList on exit }
  443. end;
  444.  
  445. procedure TMainForm.LoadMacClick(Sender: TObject);
  446. { Load Macros }
  447. var
  448.   RecFile : File of StringRecord;
  449.   Rec     : StringRecord;
  450. begin
  451.   if FileExists(MACROFILE) then
  452.   begin
  453.     Macros.Clear; { get rid of existing macros }
  454.     AssignFile(RecFile, MACROFILE);
  455.     Reset(RecFile);
  456.     while not Eof(RecFile) do
  457.     begin
  458.       Read(RecFile,Rec);
  459.       Macros.Add(Rec.str);
  460.     end;
  461.     CloseFile(RecFile);
  462.   end
  463.   else
  464.       MessageDlg('Sorry. Macro file "'+ MACROFILE +
  465.                           '" does not exist!',
  466.                        mtInformation, [mbOK], 0);
  467. end;
  468.  
  469. procedure TMainForm.ClearMacClick(Sender: TObject);
  470. begin
  471.   ClearMacros;
  472. end;
  473.  
  474. procedure TMainForm.SaveMacClick(Sender: TObject);
  475. var
  476.    SaveFile : boolean;
  477.    RecFile  : file of StringRecord;
  478.    aRecord  : StringRecord;
  479.    i        : integer;
  480. begin
  481.    SaveFile := false;
  482.    if Macros.Count = 0 then
  483.       ShowMessage( 'There are no macros to save!' )
  484.    else
  485.    begin
  486.       if FileExists(MACROFILE) then
  487.          SaveFile := ConfirmFileSave(MACROFILE)
  488.       else
  489.          SaveFile := true;
  490.       if SaveFile then
  491.          begin
  492.            { write a file of stringRecords }
  493.            AssignFile(RecFile, MACROFILE );
  494.            Rewrite(RecFile);
  495.            for i := 0 to Macros.Count - 1 do
  496.            begin
  497.              aRecord.str := Macros[i];
  498.              Write(RecFile, aRecord );
  499.            end;
  500.            CloseFile(RecFile);
  501.          end;
  502.    end;
  503. end;
  504.  
  505. procedure TMainForm.RecordMacClick(Sender: TObject);
  506. begin
  507.   RecordMacro;
  508. end;
  509.  
  510. procedure TMainForm.DisplayMacClick(Sender: TObject);
  511. begin
  512.   EditorMemo.Lines.AddStrings(Macros);
  513. end;
  514.  
  515. end.
  516.