Jump to content

aehimself

Members
  • Content Count

    1030
  • Joined

  • Last visited

  • Days Won

    22

Posts posted by aehimself


  1. 2 hours ago, dummzeuch said:

    This large Open button does not look good either. I think a panel with an Open and a Cancel button of a standard size on the right hand side would look better. But this is just my personal taste.

    You are right, it definitely looks cleaner:

     

    image.png.fef212efc689bd0ca690beceb1908df2.png

     

    As a bonus, with the Cancel button present I can remove the code to handle the Esc keypress 🙂


  2. Invalid registry key handling (and due to this, an additional version number detection) has been incorporated in TAEDelphiVersions, thank you for pointing this out!

    As for the masking, which Delphi version are you using?

     If MatchesMask('C:\My projects\Project name\Project1.dproj', '*\My projects\Project name\*.dproj') Then
       ShowMessage('Success!')
     Else
       ShowMessage('Fail...');
    
     If MatchesMask('C:\My projects\Project name\Project1.dproj', '*\My projects\*.dproj') Then
       ShowMessage('Success!')
     Else
       ShowMessage('Fail...');
       
     If MatchesMask('C:\Temp\My projects\Project name\Project1.dproj', '*\My projects\*.dproj') Then
       ShowMessage('Success!')
     Else
       ShowMessage('Fail...');

    On 11.2 all shows "Success" for me.


  3. There were numerous updates since the last post:

    • Auto-detect Delphi version is now a valid option in rules
    • Window sizes are saved and restored when launched again
    • Added extensive logging in case something goes wrong
    • An icon now appears on the taskbar if only version selector window is shown
    • Rules can be rearranged with drag & drop and can be renamed
    • Changed how rules appear in the list, making it a bit more eye-friendly
    • Version selector window can be closed with the ESC key
    • If Shift is pressed while a file is launched, rule checks will be bypassed and the version selector will show up instead
    • A single file can be launched from the rule editor (main) window. The launching process is exactly the same so it can be used to test the rules
    • The logic for rule processing and opening a file by any rules has been moved to the main form, now RuleEngine only stores the rules - as it should

    image.thumb.png.b435ed455d0b386de350b0de61d46b9f.png

     

    I also had the chance to move it to my developer VM and can confirm that the following are working properly:

    • Taking over file extensions do preserve icons and descriptions
    • Project Version detection is working properly:
      image.png.e6dd2d23872023790a151505d6a9a36f.png
    • Delphi 7, 10, 10.1, 10.2, 10.4 and 11.2 is detected and can be controlled:
      image.png.c7110bd437a976b42221379bc9d1c591.png

    I did have an issue when starting a file width Delphi 7 as it kept popping messages up that some packages couldn't be loaded. As any file could be launched from the command line I suspect maybe UAC / other permission issue and I might need to add some more parameters to the CreateProcess I'm currently using.

    However, after I pressed Yes on all of these the problem simply went away by itself... so I'll not be able to experiment and debug it anymore 😞


  4. Some items from the to-do list are completed:

    • File associations are now created by copying all details from the current Delphi association. This means that (in theory) icons and descriptions will stay the same, but launched with AE BDSLauncher. Unfortunately I could not test this because my associations were already messed up by Delphi and have no icons of anything... 😕
    • If no rule was able to launch the file, the version selector will select the Delphi version based on the contents of the .dproj, .bdsproj or .dof file. If it was created with a version which is not installed, an error message is shown, and the latest version is selected.
    • Rules are now parsed based on an actual order, not alphabetically. For the time being reordering only can be done with the "Move up" or "Move down" option, I'll extend this with the classic drag & drop in the future.

    Also,

    • Fixed some appearance issues and an AV if the program is started up with no rules
    • Fixed parameter handling. Quotes are now removed if quoted, if unquoted all parameters will be appended to a single file name. Therefore, these two commands will be able to open the file correctly:
      BDSLauncher "C:\Path to my project\Project1.dproj" and
      BDSLauncher C:\Path to my project\Project1.dproj

    I'll probably beautify the detection code to similar of what dzBdsLauncher has as it's a lot more sophisticated than my if-s but at least it was quick to write 🙂

    Due to detection is now possible, I'll probably add it as a possible option for a Delphi version in the rules.

    • Thanks 1

  5. Source can be obtained at https://github.com/aehimself/BDSLauncher.

     

    Some small changes have been made:

    • If no caption filter is set, it means any running instance can be chosen to open the file with. Always use a new instance is now a separate option
    • If there's only one installed version and no running instances are found, no selector will be shown, a new instance is launched automatically
    • File associations can be taken over and given back to the previous Delphi. For the time being this only includes .pas, .dpr, .dproj, .dfm and .groupproj

     

    Left to do:

    • Everything mentioned in the first post
    • At the moment all the file associations are taken over with the description "AE BDSLauncher file" and the same icon as the application. In reality it should be separated, like how original Delphi does it
    • Attempting to guess the Delphi version in .dpr and .dproj files based on @dummzeuch's request

    Feel free to try it out but don't expect it to work perfectly and look nice 🙂


  6. That is actually a good idea and wouldn’t be too hard I presume. If the information was found that version will be selected in the combo box by default.

     

    I’ll also have to rethink when the selector appears. It might be good for some to launch a new instance for each file and will probably find it very annoying that a window pops up each time a file was double clicked… As a last resort a “catch-all” rule could be set up to hide the popup…

    Anyway, I’ll have to think this through.


  7. I had issues like this when I used a combo box to change the style of the application in the OnChange handler. I suppose it had to do something with the dropdown window still visible.

     

    On the other hand don't be surprised, VCL themes are quite unstable. Since upgrading to 11.2 my main application started to throw similar AVs upon closure, but coming from DBGrid's style hook.


  8. For the time being this is a teaser only, sources will be published on GitHub as soon as I finish up the rough edges.

     

    Due to the recent success of detecting and interacting with Delphi IDE versions / instances I decided to put together a new BDSLauncher, which allows you to set up rules to automatically decide which version and which instance of Delphi you want to open a Delphi source file with. If started without any parameters, the rule editor main window will show up:

     

    image.thumb.png.14e35a892f57b86afe896156db9c73af.png

     

    Here a list of file masks can be provided. If the file which is about to be open matches any of these, it will be opened in the version selected under. There are situations when a project has to be open for a .pas (.dfm more likely) to appear correctly. To support this, you can set a string which has to be contained in the IDE caption for it to be selected. If this is empty or there was no instances found with this criteria, a new instance will be launched with the parameters you specify (should be the main project .dproj / .dpk).

     

    If a parameter is specified (and the file indeed exists) two things can happen. Either a rule will decide which version / instance the selected file should be started in, or if there's none a selector will appear:

     

    image.thumb.png.5679a38e9e8fac73e39c0fce0038d9fb.png

     

    If more rules would apply to the source file but the specified instance is not found, the last will be selected alphabetically. In case any rule was selected, no window is shown, only the IDE is launched and / or the source file is opened and the launcher will close shortly after.

     

    In theory, the new launcher should support all Delphi versions from 6 and up, however the DDE component used (to do the heavy lifting) was only tested with 7 and 10, 10.1, 10.2, 10.4 and 11.

    Settings are stored in AppData, so each user with access to the same PC can have different rules set up.

     

    What is left:

    • Deeper testing after first impressions (especially with more Delphi versions. There are a few which I was forced to miss)
    • Button / installer to change file associations to itself instead of the original BDSLauncher. This could also be used to reset these associations if a Delphi (un)installer messed these up
    • More options (e.g. cmd/powershell/PascalScript scripting) to decide between versions / instances
    • Allow manual rule ordering instead of the current alphabetical
    • Add icons and / or redesign UI to be a bit more user friendly...

     

    It's kind of late now here - hence the teaser only - but I wanted to let you guys know that this is coming.

    Expect a new post with an alpha version in the upcoming days.

    • Like 2

  9. My idea seems to be correct after all, I should add my daughter right next to the rubber duck on my desk. I don't know what happened when I tried to run the code under D7 but if I change every call to the WinApi "A" version in D10.4.2 it simply works...

     

    image.thumb.png.e5f0cb9ba5d948f0023b39f78de840f0.png

     

    And yes, I can confirm... Delphi 7 supports DDE, needs the service "DELPHI32" and the topic "system". It just has to be ANSI, not Unicode.

    • Like 1

  10. 3 hours ago, Attila Kovacs said:

    it won't answer on 0/0,  just on 'system'/'bds'

     

    does D7 support DDE anyway?

    I know, this is one of the questions in this topic. As far as I understand, a DDE server should reply with one WM_DDE_ACK message for each service / topic pair it supports, but Delphi doesn't seem to do that. Neither 7, neither 10.4.2.

     

    As for being Unicode it was a good guess but it doesn't work. Running the same code from D7, or changing GlobalAddAtom to GlobalAddAtomA yields no results.

     

    I guess there's only one question. As detection doesn't seem to work, in which documentation / source file I can look for supported DDE service / topic strings for Delphi?

     

    Edit:

     

    Ummm...

     

    image.png.a6fe8953ecf06a8968e95412086f5ff0.png

    • Like 1

  11. I am in a bit of a dazzle here. However it wouldn't be a surprise in general, but is it possible that the Delphi IDE doesn't (and never did) handle WM_DDE_INITIATE correctly?

     

    In a different post, @Attila Kovacs managed to get DDE functioning properly on a modern IDE, but I realized that Delphi 7 for example doesn't reply to any conversation request with service "bds" and topic "system".

    Service is usually the executable name, this is confirmed by the registry entry:

     

    image.thumb.png.af9bd8cbdc4bd694fd5259e4d2eccabf.png

     

    However it says nothing about a topic. I experimented with sending empty strings, nulls, "system" or "DELPHI32" directly to windows and HWND_BROADCAST without success.

     

    According to Microsoft:

    Quote

     

    If the low-order word of lParam is NULL, any server application can respond. If the high-order word of lParam is NULL, any topic is valid. Upon receiving a WM_DDE_INITIATE request with the high-order word of the lParam parameter set to NULL, a server must send a WM_DDE_ACK message for each of the topics it supports.

     

    Basically, broadcasting 0-0 will end up discovering all available DDE services and topics for use.

     

    explorer.exe behaves correctly:

    Quote

    PID: 5148, title: "", class: "DDEMLMom"
    ACK from 4523036: service: PROGMAN, topic: PROGMAN
    ACK from 4850962: service: Shell, topic: AppProperties
    ACK from 4130072: service: Folders, topic: AppProperties

    However Delphi 7 and Delphi 10.4.2 does not reply with anything. Is it possible that the Delphi IDE never handled WM_DDE_INITIATE messages correctly? If yes, what documentation I have to dig up to get DDE details for earlier Delphi IDEs?

     

    The code I used is:

     

    unit Unit1;
    
    interface
    
    uses
      Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
      Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
    
    type
      TForm1 = class(TForm)
        Memo1: TMemo;
        procedure FormCreate(Sender: TObject);
      private
        procedure DdeWndProc(var AMsg: TMessage);
      public
        { Public declarations }
      end;
    
    var
      Form1: TForm1;
    
    implementation
    
    Type
      TDDEInfo = Record
        WindowHandle: HWND;
        ServiceAtom: Word;
        TopicAtom: Word;
      End;
    
      PDDEInfo = ^TDDEInfo;
    
    {$R *.dfm}
    
    Function FindDelphiWindow(inHWND: HWND; inParam: LParam): Boolean; StdCall;
    Var
      ppid: Cardinal;
      title, classname: Array[0..255] Of Char;
    Begin
      // https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms633498(v=vs.85)
      // Result := True   ->   Continue evaluation
      // Result := False  ->   Do not continue evaluation
    
      Result := True;
    
      GetWindowThreadProcessID(inHWND, ppid);
    
    //  if ppid <> 7196 then
    //    Exit;
    
      GetWindowText(inHWND, title, 255);
      GetClassName(inHWND, classname, 255);
    
    
      Form1.Memo1.Lines.Add('PID: ' + ppid.ToString + ', title: "' + title + '", class: "' + classname + '"');
      SendMessage(inHWND, WM_DDE_INITIATE, PDDEInfo(inParam)^.WindowHandle, Makelong(PDDEInfo(inParam)^.ServiceAtom, PDDEInfo(inParam)^.TopicAtom));
    End;
    
    
    procedure TForm1.DdeWndProc(var AMsg: TMessage);
    Var
     service, topic: PChar;
    begin
      if Amsg.Msg = WM_DDE_ACK then
      Begin
        GetMem(service, 256);
        GetMem(topic, 256);
        Try
          GlobalGetAtomName(AMsg.LParamLo, service, 255);
          GlobalGetAtomName(AMsg.LParamHi, topic, 255);
    
          Memo1.Lines.Add('ACK from ' + AMsg.wParam.ToString + ': service: ' + service + ', topic: ' + topic);
        Finally
          FreeMem(service);
          FreeMem(topic);
        End;
    //    PostMessage(HWND(AMsg.wParam), WM_DDE_TERMINATE, AMsg., 0);
      End;
    end;
    
    procedure TForm1.FormCreate(Sender: TObject);
    Var
      di: PDDEInfo;
    Begin
      New(di);
      di^.WindowHandle := AllocateHWND(DdeWndProc);
      Try
        di^.ServiceAtom := 0; // GlobalAddAtom(PChar('DELPHI32'));
        di^.TopicAtom := 0; // GlobalAddAtom(PChar('DELPHI32'));
    
        Memo1.Lines.BeginUpdate;
        Try
          Memo1.Lines.Clear;
    
          EnumWindows(@FindDelphiWindow, LParam(di));
        Finally
          Memo1.Lines.EndUpdate;
        End;
    
    //    GlobalDeleteAtom(di^.ServiceAtom);
    //    GlobalDeleteAtom(di^.TopicAtom);
      Finally
        DeallocateHwnd(di^.WindowHandle);
      End;
    end;
    
    end.


     


  12. So, the sample data you mentioned is not a valid JSON document, but one separate JSON object per line as far as I can see. Maybe this is why TJSONTextReader fails - I don't know, I never used it.

    If the data will always arrive in this format and efficiency / optimization is no concern, you can do something like...

    Var
      line: String;
      jo: TJSONObject;
    //  jv: TJSONValue;
    Begin
      For line In TFile.ReadAlltext('C:\[...]\restaurants.json') Do
      Begin
        jo := TJSONObject(TJSONObject.ParseJSONValue(line));
    
        If Not Assigned(jo) Then
          Continue; // invalid JSON code in one line
    
        Try
          // ShowMessage(jo.GetValue('borough').Value);
          // For jv In jo.GetValue('grades') As TJSONArray Do
          // Begin
          //   ShowMessage((jv As TJSONObject).GetValue('grade').Value);
          //   ((jv As TJSONObject).GetValue('score') As TJSONNumber).AsInt
          // End;
        Finally
          jo.Free;
        End;
      End;
    End;

    The bad thing about this logic is that it reads the whole contents of the file in the memory, which will stay there until processing is complete. With 12 Mb this is not an issue, but you might hit a barrier if the data is in the GBs range.

    In that case you can use a TFileStream, read into a String until sLineBreak (or .Position = .Size) and build each JSON object from this. The processing logic can stay the same.

    • Like 1

  13. @Attila Kovacs Your discovery method is inefficient. DDE_INITIATE is meant to be sent to HWND_BROADCAST and whoever supports it will reply. You also can identify the window who replied, as a WM_DDE_ACK message sent as a reply to DDE_INITIATE contains the DDE server window handle as WParam.

     

    Basically...

    Procedure TAEDDEManager.DiscoveryHandler(Var inMessage: TMessage);
    Var
      whandle: HWND;
      pid: Cardinal;
    Begin
      If inMessage.Msg <> WM_DDE_ACK Then
        Exit;
    
      whandle := inMessage.WParam;
      GetWindowThreadProcessId(whandle, pid);
    
      // Do something with the PID and the window handle of the process
    End;
    
    Procedure TAEDDEManager.RefreshServers;
    Var
      discoverywnd: HWND;
      serviceatom, topicatom: Word;
      msg: TMsg;
      res: Cardinal;
    Begin
      discoverywnd := AllocateHWnd(DiscoveryHandler);
      Try
        serviceatom := GlobalAddAtom(PChar(_service));
        Try
          topicatom := GlobalAddAtom(PChar(_topic));
          Try
            SendMessageTimeout(HWND_BROADCAST, WM_DDE_INITIATE, discoverywnd, Makelong(serviceatom, topicatom), SMTO_BLOCK, 1, res);
    
            While PeekMessage(msg, discoverywnd, 0, 0, PM_REMOVE) Do
            Begin
              TranslateMessage(msg);
              DispatchMessage(msg);
            End;
          Finally
            GlobalDeleteAtom(topicatom);
          End;
        Finally
          GlobalDeleteAtom(serviceatom);
        End;
      Finally
        DeallocateHWnd(_discoverywnd);
      End;
    End;

    And yes, I'll change my logic to window messages too. After long weeks of troubleshooting discovery still locks up after the fourth run when I'm using the DDE API 🙂

     

     


  14. 3 minutes ago, Fr0sT.Brutal said:

    Updating a library used by some 3rd party software mostly turns out to be bad idea.

    Again - you are correct but only if said library is redistributed with a software.

    For example, libmysql and oci is not allowing this (libmariadb might but I never read their EULA) and Oracle is notorious of shipping faulty software. Therefore, I'd update them at all places even if I have issues in only one software.

     

    LibSSL is also a good example from this perspective. If a serious security flaw is found, it's easier to update only 2 files per architecture than scanning your entire drive.


  15. 21 minutes ago, Fr0sT.Brutal said:

    That's religious question and so discussions are useless. There are pro's and contra's for both options, however I'm the fan of portable self-sufficient apps personally

    I completely agree with you there. However, I dislike redundancy. if you have 3 portable 32 bit and 2 portable 64 bit programs using libmysql.dll to connect to a database I'd find it wasteful and harder to maintain in case you are updating the client library.

     

    But as you said - it all comes down to user preferences.


  16. This level of nesting is just awful. Please do not use it, instead refactor your DB structure or modify your query to return the data exactly as you expect it.

    I'm leaving this here only for you to see what spaghetti you need because of bad DB design.

    Type
      TDateOfBirthPhoneNumber = TDictionary<TDateTime, String>;
      TSurnameAndRest = TObjectDictionary<String, TDateOfBirthPhoneNumber>;
      TUsernameAndRest = TObjectDictionary<String, TSurnameAndRest>;
    
      TUserCache = Class
      strict private
        _cache: TUsernameAndRest;
      public
        Constructor Create; ReIntroduce;
        Destructor Destroy; Override;
        Procedure AddRecord(Const inUserName, inSurName: String; Const inDateOfBirth: TDateTime; Const inPhoneNumber: String);
        Function ContainsRecord(Const inUserName, inSurName: String; Const inDateOfBirth: TDateTime; Const inPhoneNumber: String): Boolean;
      End;
    
    implementation
    
    Procedure TUserCache.AddRecord(Const inUserName, inSurName: String; Const inDateOfBirth: TDateTime; Const inPhoneNumber: String);
    Begin
      If Not _cache.ContainsKey(inUserName) Then _cache.Add(inUserName, TSurnameAndRest.Create([doOwnsValues]));
      If Not _cache[inUserName].ContainsKey(inSurname) Then _cache[inUserName].Add(inSurname, TDateOfBirthPhoneNumber.Create);
    
      _cache[inUserName][inSurname].AddOrSetValue(inDateOfBirth, inPhoneNumber);
    End;
    
    Function TUserCache.ContainsRecord(Const inUserName, inSurName: String; Const inDateOfBirth: TDateTime; Const inPhoneNumber: String): Boolean;
    Begin
      Result := _cache.ContainsKey(inUserName) And _cache[inUserName].ContainsKey(inSurname) And
                _cache[inUserName][inSurName].ContainsKey(inDateOfBirth) And
                (_cache[inUserName][inSurName][inDateOfBirth] = inPhoneNumber);
    End;
    
    Constructor TUserCache.Create;
    Begin
      inherited;
    
      _cache := TUsernameAndRest.Create([doOwnsValues]);
    End;
    
    Destructor TUserCache.Destroy;
    Begin
      FreeAndNil(_cache);
    
      inherited;
    End;

    For your information, I did not try this. High possibility of AVs!


  17. On 12/15/2022 at 4:12 PM, Fr0sT.Brutal said:

    I was sure the manner of trashing Windows folder with 3rd party libs have gone long ago.

    There's more than enough trash there already even at a clean installation so it doesn't really count 🙂

     

    Anyway, it's the easiest solution to support all your 32 and 64 bit apps with everything they might need. After all, Microsoft is doing the same with msvcr*.dlls-s when you install the runtime...

    • Like 1

  18. Sorry about it, then. I really thought these methods are already in 10.2.

     

    You can check where an exception is raised in System.JSON in that earlier version and then simply backtrack to see what conditions you need to meet.

     

    Unfortunately I don't have a 10.2 installation anymore to check.


  19. Result:

    image.thumb.png.c16f1feb251a0d10847cabc1fcdb797a.png

     

    The code was written in D11 but I tried to keep it as simple as possible. I hope D4 supports the below calls...

    Type
    
      TTreeNodes = Array Of TTreeNode;
    
    
    procedure TForm1.Button3Click(Sender: TObject);
    Var
     added: TTreeNodes;
    begin
       SetLength(added, 0);
    
       CopyNodes(Source, Target, added);
       Target.Expanded := true;
    end;
    
    
    function TForm1.IsInArray(inTreeNode: TTreeNode; inArray: TTreeNodes): Boolean;
    Var
     a: Integer;
    begin
      Result := False;
    
      For a := 0 To Length(inArray) - 1 Do
        If inArray[a] = inTreeNode Then
        Begin
          Result := True;
          Exit;
        End;
    end;
    
    
    procedure TForm1.CopyNodes(inSource, inDestination: TTreeNode; var inCopied: TTreeNodes);
    Var
     a: Integer;
     tv: TTreeView;
     tn: TTreeNode;
    begin
     tv := inSource.TreeView As TTreeView;
    
     For a := 0 To inSource.Count - 1 Do
      Begin
        If Not IsInArray(inSource.Item[a], inCopied) Then
        Begin
          tn := tv.Items.AddChild(inDestination, inSource.Item[a].Text);
    
          // Do data copying, whatever
    
          SetLength(inCopied, Length(inCopied) + 1);
          inCopied[Length(inCopied) - 1] := tn;
    
          If inSource.Item[a].Count > 0 Then
            CopyNodes(inSource.Item[a], tn, inCopied);
        End;
      End;
    end;

     

    • Thanks 1

  20. 42 minutes ago, Mark- said:

    Thanks, it is not. I have 10.4 but use 10.2. I think I have 10.4 installed on a VM somewhere.

    What overloaded versions you have? Any with TJSONParseOptions will do.

×