MacroRecorder: in Datei speichern und aus ihr laden

Für Fragen rund um die Ide und zum Debugger
br_klaus
Beiträge: 244
Registriert: Do 21. Jan 2010, 22:33
OS, Lazarus, FPC: Windows Vista (L 0.9.31 FPC 2.5.1)
CPU-Target: 32Bit
Wohnort: z.z. Brasilien, sonst 82335 Berg-Leoni (südlich von München)

MacroRecorder: in Datei speichern und aus ihr laden

Beitrag von br_klaus »

Hallo, Martin_frb,

habe jetzt einen Versuch gestartet, ein Makro am Ende des Recordings zu speichern und bei Playback daraus zu laden.
Ich nehme eine fixe Datei TlazMacroFile mit Namen TLazMacroFileName = 'LazMacro.mrf' (Macro Recorder File)

In TsynMacroRecorder gibt es ja schon die beiden Prozeduren LoadFromFile und SaveToFile.

Dann wird diese Datei wie folg initilaisiert:

Code: Alles auswählen

Create: 
  fLazMacroFileName := ExtractFileDir(ParamStrUTF8(0))  // Lazarus Directory
                    + PathDelim + 'LazMacro.mrf';  // = Macro Record File

Und dazu die zwei Prozeduren LoadFromLazMacroFile und SaveToLazMacroFile, abgewandelt von LoadFromFile und SaveToFile.

Die sehen dann so aus:

Code: Alles auswählen

procedure TCustomSynMacroRecorder.LoadFromLazMacroFile;
var
  F : TFileStream; newage:longint;
  const age:longint = 0;  // Datei Datum des Erstellens
    begin
  if fileexists(fLazMacroFilename) then
  F := TFileStream.Create(fLazMacroFilename, fmOpenRead) else
  F := TFileStream.Create(fLazMacroFilename, fmCreate);
  newage:= fileage(fLazMacroFileName);
  if newage = age then exit; // Makrodatei  wurde nicht verändert, events müssen nicht neu geladen werden  
          //-  Gibt es noch eine andere Möglichkeit, die Änderung einer Datei zu verfolgen?
  age:= newage;
  try
    LoadFromStream(F);
 finally
    F.Free;
  end;
    end;
 
procedure TCustomSynMacroRecorder.SaveToLazMacroFile;
var
  F : TFileStream; c:integer;
begin
  F := TFileStream.Create(LazMacroFileName, fmCreate);
  try
    SaveToStream(F);
  finally
    F.Free;
  end;
end;
Jetzt brauch ich nur noch den genauen Zeitpunkt, wann die Makros in/aus der Macro-Datei gespeichert/gelesen werden sollen. Am besten wohl sofort nach Beendigung des Recording bzw unmittelbar bevor die fEvents hintereinander abgearbeitet werden.

In procedure TCustomSynMacroRecorder.OnCommand() heißt es u.a.

Code: Alles auswählen

FStartPlayBack := False;
 
  case State of
    msStopped:
      if Command = RecordCommandID then
      begin
        RecordMacro( TCustomSynEdit( Sender ) );
        Handled := True;
      end
      else if Command = PlaybackCommandID then
      begin
        FStartPlayBack := True;
        Handled := True;
      end;
    msPlaying:
      ;
    msPaused:
      if Command = PlaybackCommandID then
      begin
        Resume;
        Handled := True;
      end;
    msRecording:
      if Command = PlaybackCommandID then
      begin
        Pause;
        Handled := True;
      end
      else if Command = RecordCommandID then
      begin
		// <--- hier Events in MacroDatei speichern
        Stop;
        Handled := True;
      end;
  end;
D.h. also Einfügen von SaveTo..File, bevor Stop aufgerufen wird.

Der Zeitpunkt für LoadFrom..File scheint mir in PlaybackMacro angebracht zu sein, unmittelbar bevor die events abagearbeitet werden:

Code: Alles auswählen

procedure TCustomSynMacroRecorder.PlaybackMacro(aEditor: TCustomSynEdit);
var
  cEvent: integer;
begin
  if State <> msStopped then
    Error( sCannotPlay );
  fState := msPlaying;
  try
    StateChanged;
		//  <---  hier Events aus MacroDatei laden
    for cEvent := 0 to EventCount -1 do
      Events[ cEvent ].Playback( aEditor );
  finally
    fState := msStopped;
    StateChanged;
  end;
end;
Ich habe das gemacht, es funktioniert auch!
Aber nur zwei Mal hintereinander kann ich Playback machen. Dann streikt das Programm, es folgt die ErrorMeldung: "Kann <MakroDatei> nicht öffnen. OK = weiter, Abbrechen = Programm abbrechen"
Woran kann das liegen?

Beim genaueren Anschauen der Prozeduren LoadFromStream und SaveToStream in den einzelnen EventKlassen (TsynMacroEvent) fand ich bei einigen EventKlassen Unterschied zwischen Save und Load: bei Save wird noch zusätzlich das ec-Command übertragen, aber nicht bei allen Eventklassen (nicht bei TSynDataEvent, TSynPositionEvent)
Mi scheint das ein Bug zu sein. Vielleicht hat der sogar diesen Fehler verursacht?

Denn in der Prozedur LoadFromStreamEx des MacroRecorders heißt es:

Code: Alles auswählen

while (aSrc.Position < aSrc.Size) and (i < cnt) do
  begin
    iCommand := 0;
    aSrc.Read( iCommand, SizeOf(TSynEditorCommand) );  // ==> es wird immer zuerst das ec-Command gelesen
    iEvent := CreateMacroEvent( iCommand );
    iEvent.Initialize( iCommand, #0, nil );
    iEvent.LoadFromStream( aSrc );   //  ==> Aufruf des jeweiligen LoadFromStream
    fEvents.Add( iEvent );
    Inc(i);
  end;
hingegen beim SaveToStream jeweils events[ .. ].SaveToStream

Es wird also jedes Mal erst ec-Command gelesen (und deshalb muß es ja wohl auch auch als erstes bei SaveToStream geschrieben werden).
(Warum nicht dieses ecCommand als weitere property, die dann initialisiert wird?)

Ich habe dann diese Veränderungen gemacht, neu kompiliert – wieder das gleiche Ergebnis: beim dritten Aufruf von Playback kommt wieder Fehlermeldung „Unable to open File ... “.
Es muß also noch irgendeinen anderen Grund geben....

Kannst Du Dir denken, was das sein könnte?


Außerdem gibt es noch algemein ein Problem beim Playback: Wenn bei den Events auch F9 dabei ist, also Run bis zum nächsten Fehler, und ich noch vor Beendigung des Runs Playback sende, dann bricht Lazarus ganz zusammen. Dann geht (fast) überhaupt nichts mahr. Ich kann noch schrieben, Cursor bewegen, auch Dateien öffnen, aber nicht mehr schließen, alle Buttons scheinen deaktiviert. So kannich auch nichts mehr speichern, es bleibt nur der Taskmanager, um Lazarus zu beenden.

Man bräuchte also eine globale Variable CompilerBusy o.ä.. Solange die auf TRUE gesetzt ist, soll das Playback nicht möglich sein.
Ich denke, die könnte man auch am besten gleich (fast) zu Beginn von PlaybackMacro() abfragen.
Ich wollte die Abfrage mit der Variablen (IDE)ToolStatus <> itNone machen, weil die von der MainIDE jedesmal gesetzt wird, aber ich finde keine Möglichkeit, vom MacroRecorder auf diese zuzugreifen...

Herzlichen Dank!

martin_frb
Beiträge: 586
Registriert: Mi 25. Mär 2009, 21:12
OS, Lazarus, FPC: Laz trunk / fpc latest release / Win and other
CPU-Target: mostly 32 bit

Re: MacroRecorder: in Datei speichern und aus ihr laden

Beitrag von martin_frb »

Bin unterwegs. Antwort in ein paar tagen

br_klaus
Beiträge: 244
Registriert: Do 21. Jan 2010, 22:33
OS, Lazarus, FPC: Windows Vista (L 0.9.31 FPC 2.5.1)
CPU-Target: 32Bit
Wohnort: z.z. Brasilien, sonst 82335 Berg-Leoni (südlich von München)

Re: MacroRecorder: in Datei speichern und aus ihr laden

Beitrag von br_klaus »

Habe jetzt den Grund gefunden. Als ich nämlich, nachdem in Lazarus wieder alles blockiert war, die MakroDatei öffnen wollte, erhielt ich die Windows-Meldung: geht nicht, Datei wird gerade von einem anderen Programm benützt. D.h. die Datei war noch immer offen.

Das war ein Programmierfehler:
- ich hatte vergessen, vor dem Verlassen der Prozedur LoadFromLazMacroFile den Filestream wieder aus dem Speicher zu nehmen.
- Außerdem musste ich am Beginn des Ladens aus der MakroDatei abfragen, ob fevents überhaupt schon kreiert wurden (falls ich nämlich gleich mit Playback beginnen will, ohne erst einen Makro neu zu erstellen) und,
- wenn die Anzahl der Events = 0, dann musste auch die Dateigröße auf 0 gesetzt werden (die blieb nämlich auf 4: 4 mal das Byte #0).

Meine Prozeduren sehen jetzt so aus:

Code: Alles auswählen

procedure TCustomSynMacroRecorder.LoadFromLazMacroFile;
  Label NoRead;
  var
    F : TFileStream; newage:longint;
    const age:longint = 0;
  begin
     if fileexists(fLazMacroFilename) then
     begin
       F := TFileStream.Create(fLazMacroFilename, fmOpenRead);
       newage:=FileGetDate(F.Handle);
         if age = newage then goto NoRead; // exit, because no change made in file
         age:=newage;
         if f.Size = 0 then goto NoRead;   // exit, because file empty
     end else
     begin
         F := TFileStream.Create(fLazMacroFilename, fmCreate);
         age:=FileGetDate(F.Handle);
   NoRead:
         f.free;
         exit;
     end;
     try
      LoadFromStream(F);
    finally
      F.Free;
    end;
  end;

und

Code: Alles auswählen

procedure TCustomSynMacroRecorder.SaveToLazMacroFile;
var
  F : TFileStream;
begin
  F := TFileStream.Create(fLazMacroFileName, fmCreate);
  if fevents.Count=0 then
      begin
        f.Size := 0;
        f.free;
        exit;  // create empty file
      end;
  try
    SaveToStream(F);
  finally
    F.Free;
  end;
end;

Und bei der Prozedur LoadFromStreamEx mußte ich noch eine kleine Ergänzung machen:

Code: Alles auswählen

procedure TCustomSynMacroRecorder.LoadFromStreamEx(aSrc: TStream;
  aClear: boolean);
var
  iCommand : TSynEditorCommand;
  iEvent: TSynMacroEvent;
  cnt, i : Integer;
begin
  if fevents = nil then fevents := TList.Create;  // <---  ergänzt, sonst kann nicht Stop aufgerufen werden
  Stop;
  if aClear then
    Clear;
  fEvents := TList.Create;
  cnt := 0;
  aSrc.Read(cnt, sizeof(cnt));
  i := 0;
  fEvents.Capacity := aSrc.Size div SizeOf( TSynEditorCommand );
  while (aSrc.Position < aSrc.Size) and (i < cnt) do
  begin
    iCommand := 0;
    aSrc.Read( iCommand, SizeOf(TSynEditorCommand) );
    iEvent := CreateMacroEvent( iCommand );
    iEvent.Initialize( iCommand, #0, nil );
    iEvent.LoadFromStream( aSrc );
    fEvents.Add( iEvent );
    Inc(i);
  end;
end;
 
Jetzt funktioniert es.
 
Ich brauche nur noch zwei Batch-Dateien SaveToMacro.bat und CopyToLazMacro.bat, um dann diese Makrodatei in einem anderen Namen abzuspeichern, bzw. durch einen anderen Makro zu ersetzen.
 

Code: Alles auswählen

SaveToMacro.bat:
    copy /b LazMacro.mrf %1.mrf
    exit
 
CopyToLazacro.bat:
    del LazMacro.mrf
    copy /b %1.mrf $_$
    ren $_$ LazMacro.mrf
    exit
Und am besten das Ganze in einem eigenen Verzeichnis <Lazarus>\Macros (in TSynMacroRecorder.Create)
 

Code: Alles auswählen

// LazMacroFile
  MacroDir := ExtractFileDir(ParamStrUTF8(0))  // Lazarus Directory
  + PathDelim + 'Macros';
  if not DirectoryExists(MacroDir) then
      CreateDir(MacroDir);
  fLazMacroFileName := MacroDir + PathDelim + 'LazMacro.mrf';

Für die Abfrage des IDEToolSatus (ob <> itNone) habe ich mir einen Clone des IDEToolStatus erstellt: SMRToolStatus mit ganz denselben Bezeichnungen, der dann beim Setzen des IDEToolStatus auch gesetzt wird.
 

Code: Alles auswählen

type
  TSMRToolStatus = (itNone, itExiting, itBuilder, itDebugger,
                               itCodeTools, itCodeToolAborting, itCustom);
  var SMRToolStatus: TSMRToolStatus;
 
  procedure Set_SMRToolStatus(n:byte);
und die Implementierung

Code: Alles auswählen

procedure Set_SMRToolStatus(n:byte);
  begin
    SMRToolStatus := TSMRToolStatus(n)
  end;

Dazu der Aufruf in mainbase ( uses SynMacroRecorder hinzugefügt):

Code: Alles auswählen

procedure TMainIDEBase.SetToolStatus(const AValue: TIDEToolStatus);
begin
  if FToolStatus=AValue then exit;
  FToolStatus:=AValue;
  Set_SMRToolStatus(Byte(FToolStatus));  // eingefügt
  UpdateCaption;
end;

Nur was den Zeitpunkt betrifft, wo abgefragt werden soll, ob der Compiler noch aktiv ist (ToolStatus <> itNone), da scheint der von mir gewählte (am Beginn von PlaybackMacro) Probleme zu bereiten.
Denn jetzt bekommt Lazarus ein ungewöhnliches Verhalten: nach der Abwicklung eines Makros, der am Schluß F9 enthält, zeigt das Nachrichtenfenster wohl die richtige Zeile an, aber der Cursor und die hervorgehobene Zeile wandern nach jedem Playback eine Zeile weiter zurück. Erst wenn ich dann die entsprechende Nachrichtenzeile anklicke, springt auch der Cursor zur richtigen Fehlerzeile
-------------------------------
Ich vermisse einen ShortCut: Springe zur (auch: nächsten, vorigen) im Nachrichtenfenster angezeigten Zeile. Intern wird ein solches Kommando ja verwendet, aber ich sehe keinen Tastatur-ShortCut dafür.
-------------------------------
Der Zeitpunkt muß wohl etwas früher gelegt werden, da wo Playback zum ersten Mal als Kommando erscheint...


Übrigens ist mir noch etwas bei der Prozedur Create des MacroRecorders aufgefallen: Wozu werden da noch einmal die 2 ShortCuts für Record/Playback genannt, um dann dieses Array zu bekommen? Genügen denn nicht die beiden ec-Commands bzw mc-Commands für Play/Record.
Und wenn statt des array{ShortCut] ein array[IDEShortCut] verendet würde, dann könnte man noch viel mehr Möglichkeiten implementieren.
Ich denke überhaupt: wäre es nicht sinnvoller, in Lazarus anstelle von ShortCuts generell IDEShortCuts zu gebrauchen? Dann könnte man viel flexibler programmieren und neue Features implementieren.

Ich denke da zB bei Record an folgende Möglichkeiten

Code: Alles auswählen

Shift ^ R,    R => Record Beginn/Ende
                   E => Makros editieren (für später...)
                   <n> = 1..9  => Makro wiederholen 2^^n-1 mal, geht nur wenn kein Playback
                   <n> = 0 => Wiederholung abbrechen, geht immer
                   I => Wiederholung unterbrechen (=interrupt) an/aus
Dazu bräuchte es nur eine interne Wiederholungsvariable, die normalerweise auf 0 gesetzt ist, aber hier vom Benutzer gesetzt werden kann.

Wäre das kein Vorschlag?

martin_frb
Beiträge: 586
Registriert: Mi 25. Mär 2009, 21:12
OS, Lazarus, FPC: Laz trunk / fpc latest release / Win and other
CPU-Target: mostly 32 bit

Re: MacroRecorder: in Datei speichern und aus ihr laden

Beitrag von martin_frb »

Danke erst mal für die Mühe. Ich werde noch genauer schauen. Im Moment hab ich nur den Text gelesen, der Code steht noch aus...
Außerdem gibt es noch algemein ein Problem beim Playback: Wenn bei den Events auch F9 dabei ist, also Run bis zum nächsten Fehler, und ich noch vor Beendigung des Runs Playback sende, dann bricht Lazarus ganz zusammen. Dann geht (fast) überhaupt nichts mahr. Ich kann noch schrieben, Cursor bewegen, auch Dateien öffnen, aber nicht mehr schließen, alle Buttons scheinen deaktiviert. So kannich auch nichts mehr speichern, es bleibt nur der Taskmanager, um Lazarus zu beenden.
Gilt/Galt das nur für geladenen Macros? Ich kann das nicht reproduzieren.
Allerdings ist der Macro Rekorder für den Debugger ungeeignet. Wenn mehrere F9,F8,F7 enthalten sind, sendet der Macro recorder die alle in einem Stück. Und der Debugger ignoriert dann alle ausser dem ersten (oder mit Glück den ersten zwei).

Bitte genaue schritte ("Project neu", application, do this, do that)


Generell zum speichern der Macros. Da gibt es noch verschiedene Überlegungen:
1) Load/Save müssen in SourceEditor. Aber siehe auch unten.
Selbst wenn Sie in SynMacro wären, müssen die Shortcuts in der IDE definiert und dann übertragen werden....

2) XML container (per macro, alle events können ein cdata sein)
Es wird eine Versionsnummer benötigt. Sonst gibt es Probleme wenn später mal Änderungen kommen

3) Laden. Was wenn der Macro ein Kommando von einer package (Jedi) aufruft. Wenn die package de-installiert wurde gibt es das Kommando nicht mehr. Wird es ignoriert? Gibt es eine Warnung?


Ich würde die macros lieber in der Projekt Session speichern. (Später alternativ auch IDE global). Und dann ein Fenster das alle Macros listet. Dort kann dann import/export von einzelnen Macros möglich seien....
Das ist natürlich mehr Arbeit. Und ich will deinen Patch nicht zu lange aufschieben.

br_klaus
Beiträge: 244
Registriert: Do 21. Jan 2010, 22:33
OS, Lazarus, FPC: Windows Vista (L 0.9.31 FPC 2.5.1)
CPU-Target: 32Bit
Wohnort: z.z. Brasilien, sonst 82335 Berg-Leoni (südlich von München)

Re: MacroRecorder: in Datei speichern und aus ihr laden

Beitrag von br_klaus »

martin_frb hat geschrieben:Gilt/Galt das nur für geladenen Macros? Ich kann das nicht reproduzieren.
Allerdings ist der Macro Rekorder für den Debugger ungeeignet. Wenn mehrere F9,F8,F7 enthalten sind, sendet der Macro recorder die alle in einem Stück. Und der Debugger ignoriert dann alle ausser dem ersten (oder mit Glück den ersten zwei).

3) Laden. Was wenn der Macro ein Kommando von einer package (Jedi) aufruft. Wenn die package de-installiert wurde gibt es das Kommando nicht mehr. Wird es ignoriert? Gibt es eine Warnung?
Ja, das betraf die geladenen Makros (obwohl sie ja nicht mehr aus der Datei gelesen werden, falls diese nicht verändert wurde).

Ich denke, daß man den Recorder beim Senden der Shortcuts sicher entsprechend programmieren kann, daß ein Shortcut eben erst dann gesendet wird, wenn "Bahn frei" ist. zB für F9, F8, F7. Aber ist denn dafür nicht der IDETool-Status zuständig (habe den genauen Namen nicht mehr im Kopf). Ebenso wenn der Shortcut Chaos verursachen würde...

Bin inzwischen dabei, das Lesen der ShortCuts und dann das Übertragen an den Recorder um einiges zu vereinfachen... Patch folgt bald.

Herzliche Grüße - und auch GOTTES Segen (an dem ist nämlich alles gelegen, v.a. in unserer heutigen Zeit, wo die Machenschaften der Finsternis immer mehr überhandnehmen).
P. Nikolaus

martin_frb
Beiträge: 586
Registriert: Mi 25. Mär 2009, 21:12
OS, Lazarus, FPC: Laz trunk / fpc latest release / Win and other
CPU-Target: mostly 32 bit

Re: MacroRecorder: in Datei speichern und aus ihr laden

Beitrag von martin_frb »

Fuer F7/8/9 das ist nicht oder nicht nur der Toolstatus. Sondern auch der debugger status (pause, stopped, running,...)

Aber noch mehr. Wenn der Debugger am breakpoint haelt, soll dann sofort der nächste Step gemacht werden? oder soll gewartet werden bis all watches etc fertig sind (fuer debug history)....
Das kann man spaeter noch diskutieren. Aber da bedarf es spezieller Funktionen und Verwaltung der Macros...

br_klaus
Beiträge: 244
Registriert: Do 21. Jan 2010, 22:33
OS, Lazarus, FPC: Windows Vista (L 0.9.31 FPC 2.5.1)
CPU-Target: 32Bit
Wohnort: z.z. Brasilien, sonst 82335 Berg-Leoni (südlich von München)

Re: MacroRecorder: in Datei speichern und aus ihr laden

Beitrag von br_klaus »

Hallo, Martin,

Habe hier einen Versuch gestartet, wie der Macrorecorder laufen könnte mit Wiederholungsrate (es ist nur die Struktur, die übrigen Prozeduren wie in der Unit). Ich nehe Labels, da kann man differenzierter programmieren.

Code: Alles auswählen

Type Status = (stopped, waiting, recording, playing);
{ecCommands:   
               ecRecord                 // ^R,R
               ecEdit                   // ^R,E
               ecAbort                  // ^R,A
               ecInterrupt              // ^R,I
               ecSetCount0..ecSetCount9 // ^R,0..9
               ecPlay                   // ^P
}
 
 var RepeatCount: word;
Procedure OnCommand(Sender:TObject; cmd:word);
  begin
    if (cmd < ecRecord) or (cmd > ecPlay) then
       begin if status = recording then
          begin
            if fevents = nil then StartRecording;
            AddNewEvent(cmd);
          end;
       end;
    else
      OnStatus;
  end;
Procedure SetRepeatCount(cmd:word);
  begin RepeatCount:=1 shl (cmd-ecSetCount0) - 1; end;
 
Procedure OnStatus;
  begin
    case status of
        stopped: case cmd of
            ecRecord: status:=recording;
            ecPlay: begin status:=recording; RepeatPlaybaackMacro; end;
            ecSetCount0..ecSetCount9: SetRepeatCount;
            ecEdit: EditMacros;  // Makros editieren, umbenennen, etc.
            end;
        waiting; case cmd of
            ecInterrupt: status:= playing;
            ecSetCount0.. ecSetCount9: SetReatCount;
            ecAbort: status:=stopped;
            end;
        recording: if cmd = ecRecord then begin status:=stopped; Stop; end;
        playing: case cmd of
            ecInterrupt: status:=waiting;
            ecSetCount0: RepeatCount:=0;
            ecAbort: status:=stopped;
            end;
        end;
   end;
 
Procedure RepeatPlaybackMacro;
  var ecnt,n:word; Label RepeatPlay, NextEvent, GetStatus;
  begin
    n:=1 + RepeatCount;
   RepeatPlay:
         if not bereit then goto GetStatus; // bereit = wenn "Bahn frei" ist, das Makro also gesendet werden kann
         ecnt:=0;
         if RepeatCount > 0 then dec(n) else n:=0;
 
   NextEvent:
         fevents[count].Playback;
         inc(ecnt);
 
   GetStatus:
         OnStatus;
         case status of
             playing:
                 begin
                    if ecnt < fevents.count then goto NextEvent;
                    if n > 0 then goto RepeatPlay;
                 end;
             waiting: goto Getstatus;
         end;
       Stop; status:=stopped;
   end;
 
Procedure AddNewEvent(cmd:word);
  begin event:= CreateMacroEvent(cmd);
        fevents.add(event);
  end;

br_klaus
Beiträge: 244
Registriert: Do 21. Jan 2010, 22:33
OS, Lazarus, FPC: Windows Vista (L 0.9.31 FPC 2.5.1)
CPU-Target: 32Bit
Wohnort: z.z. Brasilien, sonst 82335 Berg-Leoni (südlich von München)

Re: MacroRecorder: in Datei speichern und aus ihr laden

Beitrag von br_klaus »

Noch ein Gedanke zum Abfragen ob "Bahn frei": am besten eine Integer-Variable busy, die von allen "kritischen" Programmen beim Eintritt erhöht, beim Austritt wieder erniedrigt wird (statt "exit" dann innerhalb der Prozedur "goto Ende" bzw vor und nach dem Aufruf erhöhen/erniedrigen. Erst wenn busy = 0, also Ruhezustand, soll das nächste Kommando gesendet werden.

Außerdem könnte man ja (und muß es wohl auch) von vornherein bestimmte Kommados ausfiltern, daß die erst gar nicht zum Recorder gleangen.

martin_frb
Beiträge: 586
Registriert: Mi 25. Mär 2009, 21:12
OS, Lazarus, FPC: Laz trunk / fpc latest release / Win and other
CPU-Target: mostly 32 bit

Re: MacroRecorder: in Datei speichern und aus ihr laden

Beitrag von martin_frb »

OK, was ist nun was....
Ersetzen die neuen posts die alten? Oder muss man fuer die neuen posts zuerst die "patches" von den frueheren posts anfuegen.
*** Wie waere es mit echten Patches? ***

Und bitte, bitte: Nicht alles mischen.
- Ein patch fuer streaming, und nur streaming, und sonst nix!
ggf 2 patches fuer streaming, einer fuer das streaming. einer fuer den aufauf.
- Und dann weitersehen....
Der Zeitpunkt für LoadFrom..File scheint mir in PlaybackMacro angebracht zu sein, unmittelbar bevor die events abagearbeitet werden:
Das kann nicht richtig sein. Das sind 2 unabhaengige funktionen....

Laden (und speichern) muss der Benutzer explicit ueber key-combo triggern.

Warum ein neues
procedure TCustomSynMacroRecorder.LoadFromLazMacroFile;
wenn es schon ein load/save to file gibt?

Und warum im "LOAD" ein neues File erzeugen? (fmCreate)?
fLazMacroFileName := ExtractFileDir(ParamStrUTF8(0)) // Lazarus Directory
+ PathDelim + 'LazMacro.mrf'; // = Macro Record File
Das gehoert nicht in SynEdit. Das ist Sache der IDE (SourceEditor).... Ausserdem sollte der FIlename nicht konstant seien.

---------------------------------------------
Ueber die Sache mit der Wiederholung muss nachgedacht werden. Das erzeugt eine Unmenge von Tastaturbelegungen (und wenn der Benutzer ^M1..9 will, muss er 9 muss aendern....)
Ausserdem ist es auf max 9 beschraenkt. Und derzeit ist ^P (play) leicht mehrfach drueckbar...

Was das wiederholen von Tastatur combos mit 2 Anschlaegen betrifft, das ist ein generelles Problem.
---------------------------------------------
"filtern"; Einige Kommandos werden bereits gefiltert (zB editor wechseln). Derzeit kann der Recorder nur in einem Editor aufnehmen. Wechselt man den Editor dann "pausiert" die Aufzeichnung (IIRC).
Die Wiedergabe erfolgt ebenso an nur einen Editor.

Kommandos ausserhalb des Editors, werden nicht aufgezeichnet (zB: einfuegen eines neuen Button1Click ueber den Designer)

---------------------------------------------
"Bahn frei"
1) Es ist nicht so einfach festzustellen, ob noch etwas laueft, und wenn noch etwas laueft, dann ob gewartet werden soll.
Beispiel:
- F9
- Benutzer fuehrt Aktionen in seinem debugtem Project durch, es darf noch kein Breakpoin gesetzt seien.
- F5 setze breakpoint (project laeuft noch)
- weitere Aktionen, um Breakpoint zu ereichen

Aber auch mit mehrfach stepping hab ich das Problem bereits beschrieben.

Debugging und Macro Rekorder sind nicht kompatibel. Und es Bedarf wesentlich groesserer Aenderungen, um das zu fixen.

Ausserdem sollte ein LOOP im Recorder vermieden werden... Obwohl das ggf am Ende doch noetig seien koennte.

martin_frb
Beiträge: 586
Registriert: Mi 25. Mär 2009, 21:12
OS, Lazarus, FPC: Laz trunk / fpc latest release / Win and other
CPU-Target: mostly 32 bit

Re: MacroRecorder: in Datei speichern und aus ihr laden

Beitrag von martin_frb »

martin_frb hat geschrieben: Laden (und speichern) muss der Benutzer explicit ueber key-combo triggern.
Am besten waere es einen Dialog zu haben, in dem man mehrere Macros verwalten kann Alle Macros werden beim Aufzeichnen zur liste hinzugefuegt. Der Dialog erlaubt den aktiven Macro zu Wechseln.

Der Dialog kann dann export/import haben (mit filename dialog)

martin_frb
Beiträge: 586
Registriert: Mi 25. Mär 2009, 21:12
OS, Lazarus, FPC: Laz trunk / fpc latest release / Win and other
CPU-Target: mostly 32 bit

Re: MacroRecorder: in Datei speichern und aus ihr laden

Beitrag von martin_frb »

Es sieht so aus, das eine binaere Datei geschrieben wird....

Das Problem: Wenn man Lazarus neu kompiliert kann sich die Bedeutung aendern. Die Kommandos werden als integer gespeichert. Und fuer einige Kommandos ist der Wert temporär, und ändert sich zwischen Lazarus Versionen...

martin_frb
Beiträge: 586
Registriert: Mi 25. Mär 2009, 21:12
OS, Lazarus, FPC: Laz trunk / fpc latest release / Win and other
CPU-Target: mostly 32 bit

Re: MacroRecorder: in Datei speichern und aus ihr laden

Beitrag von martin_frb »

Ok, ich hab jetzt mal eine Makro-list Fenster hinzugefuegt. (very basic, aber habe nur begrenzt Zeit)

Als naechstes kann man schauen, das man ein *zukunftssicheres* Format zum speichern findet. Das benoetigt auch Fehlererkennung beim laden...
Dann kann man die Makros in der Session speichern, und export/import Buttons hinzufuegen.


Tut mir Leid all meine Antworten, mit den moeglichen Problemen erst so spaet kamen. Aber ich hatte vorher keine Zeit mich durch den Code durch zu arbeiten...

br_klaus
Beiträge: 244
Registriert: Do 21. Jan 2010, 22:33
OS, Lazarus, FPC: Windows Vista (L 0.9.31 FPC 2.5.1)
CPU-Target: 32Bit
Wohnort: z.z. Brasilien, sonst 82335 Berg-Leoni (südlich von München)

Re: MacroRecorder: in Datei speichern und aus ihr laden

Beitrag von br_klaus »

Hallo, martin_frb,

ich bin kein gewiefter Programmierer, wie Du und die, die Lazarus erstellten. Ich bin zuallererst katholischer Priester in einem Kloster in Brasilien (im Kreuzorden, in dem wir sehr das Leiden des HERRN verehren, Seine eucharistische Gegenwart (ewige Anbetung) und die hl. Engel - der Orden wurde durch das berühmt-berüchtigte Werk der hl. Engel (Engelwerk) restauriert),
So verstehe ich auch manche der Probleme nicht so ganz. Ich versuche einfach, mit meinem gesunden Menschenverstand an die Dinge und Probleme heranzugehen, ohne Scheuklappen, um alles möglichst zu vereinfachen (z.B. unnötige Klassen weglassen, wo sie nicht wirklich notwendig sind).

martin_frb hat geschrieben: Ueber die Sache mit der Wiederholung muss nachgedacht werden. Das erzeugt eine Unmenge von Tastaturbelegungen (und wenn der Benutzer ^M1..9 will, muss er 9 muss aendern....)
Ausserdem ist es auf max 9 beschraenkt. Und derzeit ist ^P (play) leicht mehrfach drueckbar...
aber 9 beim IdeShortcut bedeutet doch im Endeffekt 2^9-1 = 511 Wiederholungen. Reicht das nicht?
martin_frb hat geschrieben: Und warum im "LOAD" ein neues File erzeugen? (fmCreate)?

fLazMacroFileName := ExtractFileDir(ParamStrUTF8(0)) // Lazarus Directory
+ PathDelim + 'LazMacro.mrf'; // = Macro Record File

Das gehoert nicht in SynEdit. Das ist Sache der IDE (SourceEditor).... Ausserdem sollte der FIlename nicht konstant seien.
Ich wollte ja einen konstanten Filename, dass der immer als „Referenz“ für den MacroRecorder dient, aus dem dann jedes Mal beim Starten der Wiederholung (sobald die Datei verändert wurde), die Makros-Sequenz gelesen wird. Das Kopieren in eine andere Datei und von einer anderen auf die Makrodatei kann ja leicht mit .bat-Dateien bewerkstelligt werden bzw dann im Programm durch Makro-Edit, wenn das implementiert ist.
Sicher ist es sinnvoller, die Datei nur einmal mit fmCreate zu öffnen bzw, wenn sie bereits existiert, mit fmOpenWriteRead.
martin_frb hat geschrieben: "filtern"; Einige Kommandos werden bereits gefiltert (zB editor wechseln). Derzeit kann der Recorder nur in einem Editor aufnehmen. Wechselt man den Editor dann "pausiert" die Aufzeichnung (IIRC).
Die Wiedergabe erfolgt ebenso an nur einen Editor.
Das läßt sich ja auch ändern. Dann kann man auch „übergreifende“ Makros zulassen, die auch für mehrere Editors gelten.
martin_frb hat geschrieben: Kommandos ausserhalb des Editors, werden nicht aufgezeichnet (zB: einfuegen eines neuen Button1Click ueber den Designer)
Das kann man ja auch ohne weiteres herausfiltern
martin_frb hat geschrieben: "Bahn frei"
1) Es ist nicht so einfach festzustellen, ob noch etwas läuft, und wenn noch etwas läuft, dann ob gewartet werden soll.
Beispiel:
- F9
- Benutzer fuehrt Aktionen in seinem debugtem Project durch, es darf noch kein Breakpoint gesetzt seien.
- F5 setze breakpoint (project laeuft noch)
- weitere Aktionen, um Breakpoint zu ereichen
Am besten auch herausfiltern, bzw. in einer Sequenz nur das erste der Run- (bzw Start-) Commandos speichern. Aber ich denke, wenn Du, wie vorgeschlagen die globale IntegerVariable busy (oder besser: busycount) nimmst (die bei jedem Eintritt in ein „kritisches“ Programm bzw schon vorher erhöht und beim Austritt bzw danach wieder erniedrigt wird), hat man eine gute Möglichkeit, abzuchecken, wann „Bahn frei“ ist, nämlich dann, wenn busycount = 0.
Oder liege ich da falsch?

martin_frb hat geschrieben: Das Problem: Wenn man Lazarus neu kompiliert kann sich die Bedeutung aendern. Die Kommandos werden als integer gespeichert. Und fuer einige Kommandos ist der Wert temporär, und ändert sich zwischen Lazarus Versionen...
In dem Fall wäre es besser, (was ich eh vorschlagen würde), die Makros nicht als Integer-Commands zu speichern, sondern als IDEShortcuts bzw deren entsprechenden DWORD-Wert (mit der Funktion

Code: Alles auswählen

function keyval(ide:TIDEShortCut):DWORD;
begin with ide do result := shortcut(shiftkey1) shl 16 + shortcut(shiftkey2); end;
Dann ist jede Verwechslung ausgeschlossen (es sei denn, die IDEShortcuts würden sich auch von einer Version zur anderen ändern...).

Ich bin gerade dabei, den Start von Lazarus dahingehend zu vereinfachen, daß das Programm selber aus den Dateien mit den Angaben der Commands (LazIDECommands und SynKeyEditCmds, soviel ich mich erinnere) diese als Strings herauszulesen und so in einem Record mit Hilfe von EditorCommandToDescriptionString(cmd) und SetSingle bzw SetCombo die entsprechenden IDEShortCuts zu speichern;
Dann kann man sich auch all die die übrigen Klassen und Funktionen aus Keymapping sparen und durch einfache set of... ersetzen.

Um die Konfusion zwischen ShortCut und IDEShortCut zu vermeiden, habe ich einen neuen Typ

Code: Alles auswählen

TShiftKey  = record key:word; shift:TShiftState; end 
const Nullkey: TShiftKey = (key:0; shift:[]);
und definiere dann auch TIdeShortCut folgendermaßen:

Code: Alles auswählen

TIDEShortCut = record
    case integer of
     0: (key1:word; shift1:TShiftState; key2:word; shift2: TShiftState);
     1: (ShiftKey1,ShiftKey2:TShiftKey);
  end;
Mit diesem und der Hilfe einiger Hilfsprogramme läßt sich dann auch ganz leicht das Donwkey-Event abfragen:

Code: Alles auswählen

function Keyfound(ide:TIdeShortcut):boolean  //ja, wenn dieser IDEShortcut in der RecordListe gespeichert ist
   function KeyWaiting(key:TshiftKey):boolean  //ja, wenn zu diesem ersten ShiftKey1 noch weitere ShiftKey2 <> Nullkey existieren
Dann folgende Prozedur, um die Ideshortcuts beim KeyDown-Event abzufangen:

Code: Alles auswählen

procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
   var d:TIDEShortCut;  
   cmd:word; nr:integer;
   const waitkey: TShiftKey = (key:0; shift: []);
   Label Verarbeiten;
begin
  if (key<16) or (key>18) then      // key = 16..18  wenn Shift oder Alt oder Ctrl zuletzt gedrückt
  begin
   if waitkey.key>0 then  
     begin
      d:=IDEShortCut(waitkey, key,shift);
      waitkey:= NullKey;
      if keyfound(d,cmd,nr) then
       begin
         goto Verarbeiten;
       end;
     end
   else
     begin
       waitkey:=ShiftKey(key,shift);
       d:= IdeShortCut(waitkey, 0, []);
       if keyfound(d,cmd,nr) then
        begin
          waitkey:=Nullkey;
          goto Verarbeiten;
        end;
       if not keywaiting(waitkey) then
          waitkey:=nullkey;
     end;
     exit;
  end;
  exit;
Verarbeiten: // OnCommand(cmd, nr, ideshortcut,...)
     begin
       d:=d;
 
     end;
 
 end;
Intern kann man sicher weiterhin auch die Commands als WORD verarbeiten, aber gepeichert (im MacroRecorder) werden nur die IDESchortCuts bzw deren DWORD-Äquivalent: Shortcut(shiftkey1) shl 16 + Shortcut(shiftkey2).
martin_frb hat geschrieben: Als naechstes kann man schauen, das man ein *zukunftssicheres* Format zum speichern findet. Das benoetigt auch Fehlererkennung beim laden...
Dann kann man die Makros in der Session speichern, und export/import Buttons hinzufuegen.
Ich denke, das lässt sich ja auch leicht mit meiner Funktion foundkey abfragen. Denn dann werden nur diejenigen IdeShortcuts bzw deren DWORD-Wert gespeichert, zu denen auch ein Command existiert.

Noch habe ich ein paar Probleme beim Lesen der Commands. Denn ich denke, da wäre es auch sinnvoller, wenn der gleiche Commandname ecxxx0..9 10 Mal erscheint, nur differenziert durch letzten Buchstaben = Ziffer0..9, dann könnte man das auch reduzieren auf ecxxx0 (weil da öfter eine eigene description) und ecxxx1 und den Rest (2..9) erledigt das Programm intern.

martin_frb
Beiträge: 586
Registriert: Mi 25. Mär 2009, 21:12
OS, Lazarus, FPC: Laz trunk / fpc latest release / Win and other
CPU-Target: mostly 32 bit

Re: MacroRecorder: in Datei speichern und aus ihr laden

Beitrag von martin_frb »

Also zunächst mal bin ich ja erstmals jedem dankbar, der wenigsten probiert selbst etwas zu machen. In so fern: Danke.

Grundsätzlich funktioniert dein Ansatz ja (fast). Die meisten IDE Kommandos haben eine feste Nummer. nur ein paar (z.B. Syncro edit) haben eine feste und eine sich ändernde. Und der Macro-Recorder verwendet die sich ändernde....

Es ist aber auch eine Frage des Anspruches. Und des (manchmal zweifelhaften) Versuch Dinge zukunftssicher zu machen.
Und zugegeben auch den Ansprüchen des "Maintainers": Am ende muss der (in diesem Falle ich), spaeter alle weiteren Entwicklungen, mit dem in Einrlang bringen.

Als erstes muss bedacht werden: SynEdit ist eine normale Komponente. Und wird auch in vielen anderen Apps, außer der IDE verwendet. Änderungen an SynEdit müssen dem gerecht werden.

Hier einmal was ich denke wie load/save funktionieren soll:
* Lazarus speichert Settings entweder
- im primary-config-path (bevorzugt XML, kann CDATA sein)
- project.lps (Session / XML, CDATA)
weitere Files sollten nicht verwendet werden.
Für Makros kommt sowohl die Projekt-session (per Projekt) als auch global (Projekt übergreifend) in Betracht.
Speicher/Laden erfolgt bei IDE start/exit oder Project open/close.
* Eine Import/Export Function, mit file-dialog. Der User bestimmt selbst wo gespeichert wird.
Dies kann dem neuen "Editor Macros" Fenster (menu "View") hinzugefügt werden.

Meiner Erfahrung nach entspricht dies auch den Erwartungen vieler der anderen Lazarus Entwickler. Und macht es für diese auch einfacher später die Implementierung zu verstehen.

Zum Format der Datei.
Für alle Tastatur Kommandos gibt es nahmen, und diese sind zur runtime verfuegbar (ecLeft, ecSelLeft, ecChar, ecDelete, ...). Im Prinzip TCustomSynMacroRecorder.GetAsString

Allerdings, muss auch Erweiterbarkeit sein. Vielleicht kann man den Macro Recorder später mal mit Pascal-Scrpt verbinden. Dann waere es gut, wenn das Format ausbaubar ist. Meine ide
ecLeft;
ecChar(#9);
ecChar('A');

Außerdem muss der Parser um eine gute Fehler-Erkennung erweitert werden.

Den neuen Parser will ich in der IDE. Nicht in SynEdit. Das macht es einfacher ihn später zu ändern. Wenn er erst mal in SynEdit ist, muss ich immer damit rechnen, das jede Änderung, die Apps von anderen Usern beeinflussen. IDE code, ist normalerweise nicht in User apps.
Der Code kann in die "Editor Macro" Fenster unit.
br_klaus hat geschrieben: Um die Konfusion zwischen ShortCut und IDEShortCut zu vermeiden, habe ich einen neuen Typ
Entsprechen der Tastatur Belegung. Die kann aber geändert werden.
br_klaus hat geschrieben: aber 9 beim IdeShortcut bedeutet doch im Endeffekt 2^9-1 = 511 Wiederholungen. Reicht das nicht?
Versteh ich nicht. Ich will ja nicht nur 1,2,4,8, 16, .. mal wiederholen, sondern vielleicht auch 10,11 oder 12mal. Soll der nutzer dann binaer Zahlen eingeben?

Zum wiederholen wollte ich einen SpintEdit in dem neuen Fenster einbauen. Dann allerdings nur uebr den "Play" button. Nicht für Tastatur.


Zum Thema "warten bis das vorhergehende Kommando Fertig ist. DAs ist nict einfach. Ich habe schon mehrere Beispiele gegeben.
- "Kompilieren" hat ein klar definiertes Ende
- Aber "Run" hat das nicht.
ecRun, ecStepOver muss warten, bis die app einen breakpoint erreicht.
ecRun, ecPause darf nicht warten
ecRun, ecToggleBreakpoint kann sofort, oder kann warten.

br_klaus
Beiträge: 244
Registriert: Do 21. Jan 2010, 22:33
OS, Lazarus, FPC: Windows Vista (L 0.9.31 FPC 2.5.1)
CPU-Target: 32Bit
Wohnort: z.z. Brasilien, sonst 82335 Berg-Leoni (südlich von München)

Re: MacroRecorder: in Datei speichern und aus ihr laden

Beitrag von br_klaus »

martin_frb hat geschrieben: Versteh ich nicht. Ich will ja nicht nur 1,2,4,8, 16, .. mal wiederholen, sondern vielleicht auch 10,11 oder 12mal. Soll der nutzer dann binaer Zahlen eingeben?
Dann vielleicht mit Einlesen durch Zahlstring, abgeschlossen mit #13.
martin_frb hat geschrieben: - im primary-config-path (bevorzugt XML, kann CDATA sein)
Was ist denn CDATA?
martin_frb hat geschrieben: weitere Files sollten nicht verwendet werden.
Und warum nicht? Ist das ein "Dogma"?

martin_frb hat geschrieben: Meine ide
ecLeft;
ecChar(#9);
ecChar('A');
Das versteh ich nicht so ganz. Wo soll denn das hin? Und warum da ein TAB drin? Oder ist das ein Beispiel für den Macrorecorder?
Gehen da nicht trotzdem IdeShortcuts (wo nur key1 belegt ist)?

martin_frb hat geschrieben: Entsprechend der Tastatur Belegung. Die kann aber geändert werden.
Läßt sich das nicht mit den anderen Keypress-Events abgleichen? Bei einem ist das Ergebnis char, und das ist, glaube ich, unabhängig von der Tastaturbelegung.

Antworten