CLX and Cursors

by Matthias Thoma, member of Project JEDI -- 2001 Spirit of Delphi Award Winner

The CLX (Component Library Crossplatform) supports Bitmaps and Icons, as files and as resources, but misses support for cursors. This article will fill that gap and show how to create QCursors and - for crossplatform development even more important - how to load and use windows cursor resources.

QCursor

A custom QCursor consists of a Bitmap and mask. The bitmap and the mask are combined and form the cursor shape. Unfortunately, QT doesn't support multi color or animated cursors. QT3, Trolltechs latest release, contains support for native operating system cursor handles, so as soon as Kylix supports it (or a later version) there is the opportunity to use multi color cursors in a platform independent way.

Bitmap   Mask  
0 0 Transparent  
1 0 Unused
0 1 White
1 1 Black

Commonly, both the bitmap and the mask are implemented as QBitmaps. As an alternative one could also use an 1 bpp (monochrome) pixmap in connection with a pixmap mask. Nonetheless, in this article the first way will be used solely.

Designing a new cursor is maybe the hardest part at all. Unfortunately, there are currently no suitable GUI tools available. That is the reason why I wrote a simple but effective tool which converts common pictures into a QCursor bitmap and mask combination. The picture doesn't need to be monochrome - black and white pixels are kept while any other colour is interpreted as transparent. The bmp2cur tool is part of this articles codecentral submission and can be obtained from my homepage separately as well.

The usage is simple. bmp2cur is a console application - the only parameter it expects is the filename of the image. All output is written to stdout.

mth@pc1> bmp2cur /home/mth/mybmp/test1.bmp
// bmp2cur
// (C) 2002 Matthias Thoma   All rights reserved.

type
  TCustomCursor = record
    Bits: array[0..24*3-1] of Byte;
    Mask: array[0..24*3-1] of Byte;
    HotSpot: TPoint;
    end;

const
   MyCustomCursor: TCustomCursor = (
    Bits: ($FF,$FF,$FF,$FF,$FF,$FF,
           $FF,$FF,$FF,$FF,$FF,$FF,
           $FF,$FF,$FF,$FF,$FF,$FF,
           $FF,$FF,$FF,$FF,$FF,$FF,
           $FF,$FF,$FF,$FF,$FF,$FF,
           $00,$00,$00,$00,$00,$00,
           $00,$00,$00,$00,$00,$00,
           $00,$00,$00,$00,$00,$00,
           $00,$00,$00,$00,$00,$00,
           $00,$00,$00,$00,$00,$00,
           $00,$00,$00,$00,$00,$00,
           $00,$00,$00,$00,$00,$00);
    Mask: ($FF,$FF,$FF,$FF,$FF,$FF,
           $FF,$FF,$FF,$FF,$FF,$FF,
           $FF,$FF,$FF,$FF,$FF,$FF,
           $FF,$FF,$FF,$FF,$FF,$FF,
           $FF,$FF,$FF,$FF,$FF,$FF,
           $00,$00,$00,$00,$00,$00,
           $00,$00,$00,$00,$00,$00,
           $00,$00,$00,$00,$00,$00,
           $00,$00,$00,$00,$00,$00,
           $00,$00,$00,$00,$00,$00,
           $00,$00,$00,$00,$00,$00,
           $FF,$FF,$FF,$FF,$FF,$FF);
    Hotspot: (X: 0; Y:0)
   );

Once the Bitmap and the Mask are created you can simply put them into two QBitmaps and create the QCursor. The following examples demonstrates that:


var
  BitsBmp: QBitmapH;
  MaskBmp: QBitmapH;
  CusorHandle: QCursorH;

const
  Bits: array[1..32*4] of Byte = (
    $FF,$FF,$FF,$FF,$C0,$01,$80,$03, $C0,$01,$80,$03,$C0,$01,$80,$03,
    $C0,$01,$80,$03,$C0,$01,$80,$03, $C0,$01,$80,$03,$C0,$01,$80,$03,
    $C0,$01,$80,$03,$C0,$01,$80,$03, $C0,$01,$80,$03,$C0,$01,$80,$03,
    $C0,$01,$80,$03,$C0,$01,$80,$03, $C0,$01,$80,$03,$FF,$FF,$FF,$FF,
    $C0,$01,$80,$03,$C0,$01,$80,$03, $C0,$01,$80,$03,$C0,$01,$80,$03,
    $C0,$01,$80,$03,$C0,$01,$80,$03, $C0,$01,$80,$03,$C0,$01,$80,$03,
    $C0,$01,$80,$03,$C0,$01,$80,$03, $C0,$01,$80,$03,$C0,$01,$80,$03,
    $C0,$01,$80,$03,$C0,$01,$80,$03, $C0,$01,$80,$03,$FF,$FF,$FF,$FF
  );

  Mask: array[1..32*4] of Byte = (
    $FF,$FF,$FF,$FF,$C0,$01,$80,$03, $C0,$01,$80,$03,$C0,$01,$80,$03,
    $C0,$01,$80,$03,$C0,$01,$80,$03, $C0,$01,$80,$03,$C0,$01,$80,$03,
    $C0,$01,$80,$03,$C0,$01,$80,$03, $C0,$01,$80,$03,$C0,$01,$80,$03,
    $C0,$01,$80,$03,$C0,$01,$80,$03, $C0,$01,$80,$03,$FF,$FF,$FF,$FF,
    $C0,$01,$80,$03,$C0,$01,$80,$03, $C0,$01,$80,$03,$C0,$01,$80,$03,
    $C0,$01,$80,$03,$C0,$01,$80,$03, $C0,$01,$80,$03,$C0,$01,$80,$03,
    $C0,$01,$80,$03,$C0,$01,$80,$03, $C0,$01,$80,$03,$C0,$01,$80,$03,
    $C0,$01,$80,$03,$C0,$01,$80,$03, $C0,$01,$80,$03,$FF,$FF,$FF,$FF
  );


begin
  BitsBmp := QBitmap_create(32, 32, @Bits, False);
  MaskBmp := QBitmap_create(32, 32, @Mask, False);
  
  CursorHandle := QCursor_create(BitsBmp, MaskBmp, 16, 16);
  
  QBitmap_destroy(BitsBmp);
  QBitmap_destroy(MaskBmp);
end;

Of course, it is possible to work with QCursorH directly, but usually it is more Delphi like to install and use it as TCursor. You only need to define a constant like, for instance, crMyCursor and assign an arbitrary value greater than one to it. Afterwards add the QCursorH to the Screen.Cursors property and use it like any other predefined cursor.


const
  crMyCursor = 1001;

begin
  BitsBmp := QBitmap_create(32, 32, @Bits, False);
  MaskBmp := QBitmap_create(32, 32, @Mask, False);
  
  CursorHandle := QCursor_create(BitsBmp, MaskBmp, 16, 16);
  Screen.Cursors[crMyCursor] := CursorHandle;
   
  QBitmap_destroy(BitsBmp);
  QBitmap_destroy(MaskBmp);
end;  

Windows cursors

The real beef of this article is the QWinCursors class - at least for cross platform programmers and people who have to port windows applications. It allows you to load cursor files (.cur) and resources. The cursor file format is pretty well documented - details can be found in [1].

Anyway, one particular detail of a windows cursor is of interest here. The shape of QCursor can consist of three pixel states: black, white and transparent - while a windows cursor can consist of four different pixel states: black, white, transparent and inverse. The QWinCursor class solves that discrepancy by mapping the inverse state to one of the remaining three (transparent, white, black).

The class interface is designed the same way as common CLX classes like TIcon and TBrush.


type
  TWinCursor = class(TGraphic)
  [...]

  public
    property Handle: QCursorH read FHandle;
    property Height: Integer read FHeight;
    property Hotspot: TPoint read GetHotspot;
    property InvMode: TInvMode read FInvMode write FInvMode;
    property Width: Integer read FWidth;

    constructor Create;  reintroduce; overload;
    constructor Create(AHandle: QCursorH); reintroduce; overload;
    destructor Destroy; override;

    procedure Assign(Source: TPersistent); override;
    procedure LoadFromStream(Stream: TStream); override;
    procedure LoadFromMimeSource(MimeSource: TMimeSource); override;
    procedure LoadFromResourceName(Instance: Cardinal; ResourceName: string);
    procedure OwnHandle;
    procedure SaveToMimeSource(MimeSource: TClxMimeSource); override;
    procedure SaveToStream(Stream: TStream); override;

    function ReleaseHandle: QCursorH;
  end;

Most parts of the class interface should be self-explanatory. The only two properties unique to TWinCursor are InvMode and Hotspot. Invmode defines to which state all inverse pixel are mapped, while Hotspot retrieves the hotspot of the cursor. Of course you can also set a new hotspot, but due to QT restrictions this will cause the cursor to be recreated.

LoadCursor and LoadCursorFromFile

function LoadCursor(Instance: Cardinal; CursorName: string): QCursorH;

The QWinCursor unit contains another convenient function known from Delphi: LoadCursor. The first parameter is the Instance of the module that contains the resource. The second parameter is the name of the cursor.

If you are using this function with Delphi/CLX and you are using the Windows unit it might clash with the Windows LoadCursor function. In such cases reference it directly using QWinCursors.LoadCursor, or make sure that the QWinCursors unit is always declared behind the windows unit in the uses clauses.

The following example shows the usage of LoadCursor. It is assuming that you have included a resource containing a cursor called MyCursor.


var
  CusorHandle: QCursorH;

const
  crMyCursor = 1001;

begin
  CursorHandle := LoadCursor(HInstance, 'MyCursor');
  Screen.Cursors[crMyCursor] := CursorHandle;
end;  

or even shorter

  
const
  crMyCursor = 1001;

begin
   Screen.Cursors[crMyCursor] := LoadCursor(HInstance, 'MyCursor');
end;  

Of course, you can also replace an already existing CLX cursors. If you do not like the standard arrow cursor simply change it.

  
begin
   Screen.Cursors[crArrow] := LoadCursor(HInstance, 'MyCursor');
end;  

I hope you have now enough knowledge on QCursors and windows cursor resources to use them in your cross platform applications. If there are still questions left, you found a bug in my code, or you just wanna say hello please feel free to contact me.


Source code


References

[1] Windows Cursor, www.daubnet.com/formats/CUR.html

 

Updated 23 Dec 2002


since 22 Dec 2002