Jump to content

Alexander Halser

Members
  • Content Count

    59
  • Joined

  • Last visited

  • Days Won

    1

Posts posted by Alexander Halser


  1. IMO the gestures are designed for real touchscreens and not for touchpads. I believe this has never worked with touchpads.

     

    Anyway, I have implemented a solution for myself by patching FMX.Platform.Mac, which was already patched anyway to work around the Sonoma scaling bug on MacOS. This is certainly not for everyone, but works like a charm. It uses the Angle parameter for an alternative zoom gesture (Angle is used in rotation gestures, for zoom it's always zero). Your app needs to respond to this accordingly. Most importantly, it doesn't break regular zoom gestures coming from real touch screens.

     

    procedure TFMXViewBase.magnifyWithEvent(event: NSEvent);
    var
      ...
    begin
      ...
    
      if FGestureControl <> nil then
      begin
        LTouches := event.touchesMatchingPhase(NSTouchPhaseTouching, NSView(Super));
        if LTouches.count >= 2 then
        begin
          LTouchesArray := LTouches.allObjects;
          LTouch := TNSTouch.Wrap(LTouchesArray.objectAtIndex(0));
          LDeviceSize := LTouch.deviceSize;
          FEventInfo.Distance := 0; //reset the distance
          // Find the greatest distance between the touches.
          for I := 0 to LTouches.count - 2 do
          begin
            LTouch := TNSTouch.Wrap(LTouchesArray.objectAtIndex(I));
            LPoint := LTouch.normalizedPosition;
            for J := 1 to LTouches.count - 1 do
            begin
              LTouch := TNSTouch.Wrap(LTouchesArray.objectAtIndex(J));
              LPoint2 := LTouch.normalizedPosition;
    
              Distance := Round(Sqrt(Sqr(LPoint.x * LDeviceSize.width - LPoint2.x * LDeviceSize.width) +
                Sqr(LPoint.y * LDeviceSize.height - LPoint2.y * LDeviceSize.height)));
              if Distance > FEventInfo.Distance then
                FEventInfo.Distance := Distance;
            end;
    
            FEventInfo.GestureID := igiZoom;
            if Supports(FGestureControl, IGestureControl, GestureObj) then
              GestureObj.CMGesture(FEventInfo);
            FEventInfo.Flags := [];
          end
        end
    {ECS/ALEX}
        else if LTouches.count = 0 then
        begin
          FEventInfo.Distance := 0;
          FEventInfo.Angle := event.magnification;
    
          FEventInfo.GestureID := igiZoom;
          if Supports(FGestureControl, IGestureControl, GestureObj) then
            GestureObj.CMGesture(FEventInfo);
          FEventInfo.Flags := [];
        end;
      end
    {ECS/ALEX}
      else
        //send the message up the responder chain
        NSView(Super).magnifyWithEvent(event);
    end;

     

    • Thanks 1

  2. Quote

    Have you investigated whether macOS sends regular touch events in addition to magnifyWithEvent when zooming?

    No, I haven't. 

    But the magnifyWithEvent comes reliably and this is what Delphi actually evaluates. I am not that deep into native MacOS development, so I rather stay with the methods that Delphi already implements.


  3. The problem is in FMX.Platform.Mac:

     

    procedure TFMXViewBase.magnifyWithEvent(event: NSEvent);
    var
      ...
    begin
      ...
    
      if FGestureControl <> nil then
      begin
        LTouches := event.touchesMatchingPhase(NSTouchPhaseTouching, NSView(Super));
    {ECS/ALEX
     Here comes our problem: LTouches.count is zero, that's why the gesture event is not fired.
     I assume that we have to deal with "event.magnification" to determine "FEventInfo.Distance".
    }
        if LTouches.count >= 2 then
        begin
          LTouchesArray := LTouches.allObjects;
          LTouch := TNSTouch.Wrap(LTouchesArray.objectAtIndex(0));
          LDeviceSize := LTouch.deviceSize;
          ...

    I am currently experimenting with the event.magnification value (which is a float value), to match FEventInfo.Distance in a way that the example from the official docs keeps working the way it promises, but doesn't.

     

    However, I have a hard time to believe that this has never worked. If Emba has dedicated examples, there must have been a time when this did work properly. It perhaps depends on the MacOS version. My own version of MacOS that I'm using for testing is relatively old (Big Sur), with a second Mac used by one of my colleagues running Sonoma. The gesture is not fired on either machine, but may have been working on OS versions < Big Sur. 

     

    If it has never worked with touchpad gestures, it might have worked and still work with a real touch screen. Is anyone competent to comment on that - does the igiZoom gesture work as advertised with touch screen Mac (we don't have them here, so I cannot test this)?


  4. Yes, rotate works as described in the docs. Zoom and pan do not. There's no event at all for these.

    The pan gesture (swiping up or down with 2 fingers) obviously gets translated into a mouse wheel message.


  5. 1 minute ago, Sherlock said:

    You should do this only for Windows environments. So be shure to wrap that in appropriate $IFDEFs in case you develop multi platform (what FMX is designed for).

    Sure.

    Winapi.Windows.pas and SetWindowLong would be difficult to compile anyway for MacOS, iOS and Android 😉

     

    Are there any other negative side effects known?


  6. Partial answer: in VCL the behavior is triggered by MainFormOnTaskbar := true;

     

    More detailed answer: it is the param WS_EX_APPWINDOW in the window GWL_EXSTYLE that makes the window appear (automatically set by MainFormOnTaskbar).


    In FMX, the main form can be made visible in the selection list by setting the form's (Windows) window handle manually:
     

    procedure TForm1.Button1Click(Sender: TObject);
    var
      WND: HWND;
    begin
      wnd := FmxHandleToHWND(self.handle);
      SetWindowLong(wnd, GWL_EXSTYLE, GetWindowLong(wnd, GWL_EXSTYLE) or WS_EX_APPWINDOW);
    
      //now the form is visible in the "Also snap..." list
    end;

     

    New question: Any known downside or side effects due to this modification?

    • Like 1

  7. When pressing the Windows key plus the right or left arrow key simultaneously, the window is moved to fill the right or left half of the screen, respectively. This works for FMX forms as well, out of the box. At least with D11, not sure about older FMX versions.

     

    However, when you release the Win key, Windows usually offers (if enabled in global settings) a list of windows to fill the other half of the screen. And this is the issue I am wondering about:

    • A VCL form is shown in this "also snap..." list.
    • An FMX form is not shown.

     

    I assume that either a param in CreateParams is responsible for this behavior, or a Windows message needs to be handled to enable it (which is implemented in VCL but not in FMX for Windows). Does anyone know what causes the FMX window to be hidden and how to overcome it?

     

    snap-vcl.png

    snap-fmx.png


  8. I just realized, more or less by accident, that an FMX TBitmap can load WebP image files. I tested a few of them, but they seem to be all perfectly fine.

    bmp := TBitmap.create;
    bmp.LoadFromFile('C:\Users\Alexander\Pictures\_test.webp');
    
    bmp.SaveToFile('C:\Users\Alexander\Pictures\_test.png'); //works
    bmp.SaveToFile('C:\Users\Alexander\Pictures\_test.webp'); //this fails!

    The load dialog in the IDE does not include webp, so you cannot select it. You can rename a webp image to ".bmp" and load it though. Internally, the TFixedMultiResBitmap converts it to PNG data, which heavily increases the binary image data stored in the form. It's like you had used a PNG in the first place. So, when using webP for UI display, it probably makes sense to store them as RC_DATA resources and load from a resource stream at runtime.

     

    Saving WebP fails with an error.

     

    The webp feature seems to come from the operating system. GDI+ explicitly mentions WEBP among other formats:

    Image File Format Constants (Gdiplusimaging.h) - Win32 apps | Microsoft Learn

     

    Does anyone know since when GDI+ supports WebP? 

    Was this implemented in Windows 8 or Windows 10? I am pretty sure that Windows 7 GDI+ did not support it. Which means that an FMX app that runs on Win7 would not be able to load it at runtime.

     

    fmx-tbitmap-webp.jpg


  9. Registration of the url scheme will open your app, when such a link is being clicked (e.g. "myregisteredprotocol://this&is&url&data=whatever").

     

    However, to receive and decode the url part, your application must implement the IURLEventHandler interface and add an event handler.

    Send me a PM if you need code for that.


  10. Quote

    Is this an alternative to ewriter files

    eWriter is an always compressed single-file format, created by our tool Help+Manual. It has a couple of advantages over simple zipped HTML, but is a proprietary format.

     

    Ziphelp, on the other hand, is an open protocol. Maybe zipped, maybe not, you choose. You can create it with standard tools and view it with Delphi's on-board components. That's the entire point of this endeavour: a format/protocol for application help, that is open on both ends (creator and viewer) without dependency on any particular tool.

     

    Quote

    How much change is needed in Delphi VCL applications

    Between 3 and 20 lines of code. Check out the VCL example in the download file. It implements all help methods including F1 help with standard TControl.HelpContext and TControl.HelpKeyword properties, plus a validity check [of context numbers] and iteration of parent controls. If you want to skip the latter, you are basically down to 3-5 lines of code.

     

    Quote

    And does a download of Ziphelp attract the usual Windows 'block' on zip files?

    Does not apply. If you want to zip-pack your HTML content, you most probably distribute it with your app. If you choose the online version, then there's no zip. 


  11. Quote

    what stops you from using this same demo and concept on iOS or Android

    A personal preference, no technical obstacle. 

     

    A mobile app I want to be lean and small. So, I'd put the help system on the app's website and let it refer to the online version, displaying the help in the regular system browser. The demo has an option that illustrates this (though the demo uses its internal TWebBrowser to display the result). In other words: I would not use the zip functionality for a mobile app and rather remove the reference to system.zip in ziphelp.pas for those platforms with a compiler switch (plus the single line that uses it). The rationale behind this consideration is that a mobile app goes to one of the stores and automated updates are very likely, minimizing the risk of a version mismatch between app and help. Furthermore I expect a mobile device to be online anyway. Both points may not apply to a desktop app. But it's really a personal preference.


  12. Quote

    I would think it is not derived from the MS-CHM Format

    No, it is not derived from CHM. Because CHM is basically Windows only and not cross-platform.

    Quote

    Or is this based on any existing, well-known XML format ?

    Yes. plain HTML5 and based on the regular "sitemap" XML schema.

     

    When you create CHM files for a Windows application, you typically use an authoring tool for this and don't craft the HTML in Notepad. Those authoring tools which create CHM files usually create plain HTML5 as well. The example help you see in the demo project was created with Help+Manual, it's plain vanilla HTML5 and Javascript.

     

    But not just Help+Manual produces this output, Madcap Flare does, Adobe Robohelp does, you name it. And most authoring tools offer some kind of syntax to use the plain HTML5 output for context-sensitive help. Unfortunately, everyone has their own syntax, there is no established standard. And because of a missing standard, there are no viewer applications, as long as everyone does their own thing.

     

    Ziphelp is an attempt and a ready-to-use solution for this dilemma. It is as open as possible, because it is based on the standard sitemap protocol. This is the same sitemap.xml that you use on your website to help Google index it. Ziphelp is basically a legal extension of this protocol. Open for everyone to produce content that includes a Ziphelp sitemap and open for everyone to create an HTML viewer that can read it.

     

    To give it a head start, we have created a freely distributable HTML viewer that reads the Ziphelp protocol (https://www.helpandmanual.com/ewriter/). Some people prefer this solution because it's less work for them.

     

    But for Delphi developers, I think that a native cross-platform implementation is more appealing. Here, everything is under your control and your application can interactively question the help system if it actually "supports help context number 45327" or if that number is outdated in the UI of your app (and you should clean it up, but at least you don't let your end users run into an 404 error). This interactivity is missing even with CHM files.

     

    So, here you go. A free Delphi implementation that supports HTML5 in a zip archive, uncompressed HTML or web-based content. It all works with the same protocol and the same mechanism, on all platforms. You can create this HTML5 help system with our tool Help+Manual, of course. But you don't have to. If you prefer a different authoring tool, go for it.

     

    • Like 1

  13. We have just released an open-source Delphi implementation for a HTML-based cross-platform application help system.

     

    Information page:

    Ziphelp - an open cross-platform help format

     

    Direct download link:

    https://www.helpandmanual.com/download/delphi-ziphelp-demo-source.zip

     

     

    About Ziphelp

    On the surface, Ziphelp is basically HTML in a compressed zip archive, hence the name. Pack any folder with HTML files into a zip archive and you are good to go.

     

    But Ziphelp is more than that, it is a protocol based on the standard sitemap protocol, designed to give a help viewer (here: the Delphi implementation) extended information about the content of the help system. That is help context numbers, help keywords, associative keywords, page titles. This information is missing in a standard sitemap.xml. Ziphelp extends the sitemap protocol and enables direct communication of an app with the help system.

     

    The Ziphelp protocol is not just for zip archives. It works inside an archive, with uncompressed folders or with HTML content loaded directly from a website. A Delphi desktop app may be deployed with local application help, uncompressed, zipped or embedded. A mobile or web application might not ship with local HTML content, but refer to an online version instead. The mechanism is the same in both cases and the TZiphelp component implements this for Delphi.

     

    Demo Application

    A demo app for VCL and FMX (Win/MacOS) is included. The demo builds on standard Delphi components (TWebBrowser) to display the help system.

     

     

     

    fmx-ziphelpdemo-mac.png

    fmx-ziphelpdemo-win.png

    • Like 4
    • Thanks 1

  14. 5 hours ago, Alexander Sviridenkov said:

    GPImage has internal DPI (same as screen DPI on image creation) and this DPI do affect image drawing

    It really does? I wasn't aware of that. Maybe we have just been lucky and were always using that one DrawImage method, that works. We draw pictures with the GDI+ function below. Bounds is the destination rectangle, already calculated in device pixels.

    procedure DoGDIPlusDraw(DC: HDC; bmp: TBitmap; bounds: TRect);
    var
      gpgraphics: TGPGraphics;
      gpimage:    TGPBitmap;
      Width, Height: UINT;
    begin
      gpgraphics := TGPGraphics.Create(DC);
      gpimage    := TGPBitmap.Create(bmp.Handle, bmp.palette);
      try
        Width  := gpimage.GetWidth;
        Height := gpimage.GetHeight;
    
        gpgraphics.SetInterpolationMode(InterpolationModeHighQualityBicubic);   // 7
        gpgraphics.DrawImage(gpimage,
          bounds,      // destination rectangle
          0, 0,        // upper-left corner of source rectangle
          Width,       // width of source rectangle
          Height,      // height of source rectangle
          UnitPixel);
    
      finally
        gpimage.Free;
        gpgraphics.Free;
      end;
    end;

     


  15. Without trying your example ... 

    When you print an image, the screen DPI doesn't matter at all. You are dealing with printer DPI instead. Nothing else.

     

    TGPBitmap loads the PNG image and that image has a defined size in pixels. With TGPBitmap.DrawImage the picture is rendered onto a Windows device, which can be a screen device or a printer device. For GDI+ this is more or less the same (some technical printer details omitted). Fact is, GDI+ does the stretching and resampling to make the image fit into the rectangle you specify. And that target rectangle must be in device pixels. If the device is a printer, get LOGPIXELSX from the printer canvas and relate it to 96 dpi (= Windows standard display resolution at 100%).

     

    // get printer device DPI
            DC := GetDC(Printer.Canvas.Handle);
            try
              nLogPx := GetDeviceCaps(DC, LOGPIXELSX);
              memo1.Lines.Add(Format('Printer Device: %d', [nLogPx]))
            finally
              ReleaseHDC(DC);
            end;
    
            rectImage.Create(
              MulDiv(10, nLogPx, 96),
              MulDiv(10, nLogPx, 96),
              MulDiv(100, nLogPx, 96),
              MulDiv(100, nLogPx, 96)
            );

    (The code example is untested, typed from memory. Point is, get the resolution of the printer canvas, not the screen.)


  16. On 8/4/2023 at 9:48 AM, dormky said:

    I have checkboxes that get initialized in FormCreate. This means that the OnClick callback gets called

    That doesn't apply to checkboxes alone. Radio buttons, radio groups, checked menu items, actions, OnResize events, you name it. Some forms have plenty of details to initialize. We have been using update counters for that, since more than 20 years. Like, every TForm that does some initialization which may auto-trigger events, has an IsUpdating property. The setter increases/decreases the value, the getter returns a boolean. If IsUpdating returns true (> 0), the checkboxes do not execute their OnChange event, manually arranged panels do not update, etc.

    constructor TForm1.Create(AOwner: TObject);
    begin
      inherited;
      FIsUpdating := 0;
      IsUpdating := true; //increases FIsUpdating
      try
        //do your init here
      finally
        IsUpdating := false;
      end;
    end;
    
    procedure TForm1.SetIsUpdating(value: boolean);
    begin
      if value then
        inc(FIsUpdating)
      else 
        dec(FIsUpdating);
    end;
    
    function TForm1.GetIsUpdating: boolean;
    begin
      result := FIsUpdating > 0;
    end;

    A counter is more flexible and error-tolerant than using the csUpdating flag in ComponentState. It can be used to update corresponding controls in an OnChange event as well, without triggering their OnChange. The counter enables nested IsUpdating without worries. The only thing to remember is to consequently use try-finally.


  17. PDFium lib does all you need. We use it with an internal application that deals with PDF pages. Add or remove pages, split pages into several smaller ones, re-combine smaller pages into one printer page. PDFium can extract text as well.

     

    Untitled.png

×