Mac Porting a programme using Lazarus from Windows to Mac Windows

Introduction

The issues and experiences in porting an application from Windows to Mac are outlined. I (Greg Vinall) have a 'Fitness' programme written in Object Pascal using the Lazarus IDE. Having recently purchased a Macbook Air, I ported the application to the Mac.


Installation of Lazarus

Installation of Lazarus was a straight forward three-step process:

  • Read the how-to
  • download and install xcode 4 (for the debugger) from the Apple Store (using the standard application in the Dock)
  • download and install Lazarus for Mac OS X from here.


Preparing to code

It was important to get one's head around the differences in the look and feel of the Mac GUI to that of the Windows GUI. I found the following couple of links informative:


Points to note when coding

The following points are detailed further in the article.

  • Buttons are smaller and rounded on the Mac.
  • The default Font is a different size.
  • There is an 'Apple' menu as the first menu, and is manditory.
  • The location and naming of menu items, such as 'Preferences...'/'Options' are different.
  • The Menu is not part of the form, and thus alters the positioning of controls.
  • Shortcut keys for common functions (eg Save) differ.
  • Dialog and AboutBoxes are generally not modal.
  • Location of configuration and other supporting files differ
  • Invisible tabsheets cannot have controls created by code
  • TcomboBox onselect events are fired inconsistently


Buttons are smaller and rounded on the Mac, and the default Font is a different size

Lazarus automatically makes buttons rounded if their height is 22 or less. Thus I put in conditional code to change every button on the form, when compiling on the Mac:

    {$IFDEF DARWIN}
    for i := 0 to frmMainGUI.ControlCount -1 do
        if frmMainGUI.Controls[i] is TButton then
            with frmMainGUI.Controls[i] do begin
                // Change the 'Height' to 22, and
                // Increase the width to accommodate the Apple Font
                Height := 22;
                Width  := 62;
            end;
    {$ENDIF}
                    

There is an 'Apple' menu as the first menu, and the location and naming of menu items, such as 'Preferences...'/'Options' are different

Every Apple application has an 'Apple' menu, with the 'Preferences...' (generally called 'Options' in the Windows environment) a submenu.

Conditionally define menu controls, and create and configure them. Firstly, in the definition of your form, define the controls:

    {$IFDEF DARWIN}
        AppMenu     : TMenuItem;
        AppAboutCmd : TMenuItem;
        AppSep1Cmd  : TMenuItem;
        AppPrefCmd  : TMenuItem;
    {$ENDIF}
                    

Secondly, create and configure them (assuming that you have a Menu control called 'MainMenu')

    {$IFDEF DARWIN}
        // Add 'Apple' Menu
        AppMenu := TMenuItem.Create(Self);  // Application menu
        AppMenu.Caption := #$EF#$A3#$BF;    // Unicode Apple logo char
        MainMenu.Items.Insert(0, AppMenu);

        // Add 'About' as item in application menu
        AppAboutCmd := TMenuItem.Create(Self);
        AppAboutCmd.Caption := 'About ' + BundleName;  // Bundlename is the name of one of your global variables.
        AppAboutCmd.OnClick := @mnuAboutClick;         // mnuAboutClick is the procedure to show the AboutBox.
        AppMenu.Add(AppAboutCmd);

        // Add Separator to application menu
        AppSep1Cmd := TMenuItem.Create(Self);
        AppSep1Cmd.Caption := '-';
        AppMenu.Add(AppSep1Cmd);

        // Add 'Preferences'/'Options' to application menu
        AppPrefCmd := TMenuItem.Create(Self);
        AppPrefCmd.Caption := 'Preferences...';
        AppPrefCmd.Shortcut := ShortCut(VK_OEM_COMMA, [ssMeta]);
        AppPrefCmd.OnClick := @OptionsCmdClick;
        AppMenu.Add(AppPrefCmd);
    {$ENDIF}
                    

Thirdly, remove the Exit submenu from the 'File' menu, and the 'About' submenu from the 'Help' menu, which is the normal location for these menus in the Windows environment, and assumes that you have these already created in your windows application, and called mnuFileExit and mnuHelpAbout:

    {$IFDEF DARWIN}
        mnuFileExit.Visible := False;
        mnuHelpAbout.Visible := False;
    {$ENDIF}
                    

The Menu is not part of the form, and thus alters the positioning of controls

I have controls along the bottom of my form, and in the Mac environment, with no menu as part of the form, leaves too much space at the bottom. I adjust the form's height:

    {$IFDEF DARWIN}
        frmMainGui.Height := 507;
    {$ENDIF}
                    

Shortcut keys for common functions (eg Save) differ

For example, 'Save' on Windows is Ctrl-S, while on the Apple, it is Meta-S. To re-assign the keys on the Mac, use the unit LCLtype, and conditionally compile as per the example:

    {$IFDEF DARWIN}
        mnuEditCopy.Shortcut      := ShortCut(VK_C, [ssMeta]);      // Meta-C       [ Command-C on Windows ]
        mnuEditPaste.Shortcut     := ShortCut(VK_V, [ssMeta]);      // Meta-V       [ Command-V on Windows ]
        mnuEditUndo.Shortcut      := ShortCut(VK_Z, [ssMeta]);      // Meta-Z       [ Command-Z on Windows ]
        mnuEditCut.Shortcut       := ShortCut(VK_X, [ssMeta]);      // Meta-X       [ Command-X on Windows ]
        mnuEditSelectAll.Shortcut := ShortCut(VK_A, [ssMeta]);      // Meta-Z       [ Command-A on Windows ]
        mnuGoFirst.Shortcut       := ShortCut(VK_UP, [ssMeta]);     // Meta-Up      [ Command-Up on Windows ]
        mnuGoPrevious.Shortcut    := ShortCut(VK_UP, []);           // Up
        mnuGoNext.Shortcut        := ShortCut(VK_DOWN, []);         // Down
        mnuGoLast.Shortcut        := ShortCut(VK_DOWN, [ssMeta]);   // Meta-Down    [ Command-Down on Windows ]
        mnuFileSave.Shortcut      := ShortCut(VK_S, [ssMeta]);      // Meta-S       [ Command-S on Windows ]
        mnuFilePrint.Shortcut     := ShortCut(VK_P, [ssCtrl]);      // Ctrl-P
    {$ENDIF}
                    

Dialog and AboutBoxes are generally not modal

In the Windows environment, make the AboutBox appear modally, while on the Mac environment, do not:

    {$IFDEF DARWIN}
        procedure TfrmMainGUI.mnuAboutClick(Sender: TObject);
        begin
          {$IFDEF DARWIN}
            if Assigned(frmAboutBox) then Exit;
            frmAboutBox := TfrmAboutBox.Create(Application);
            frmAboutBox.Show;
          {$ELSE}
            frmAboutBox := TfrmAboutBox.Create(Application);
            try
              frmAboutBox.ShowModal;
            finally
              frmAboutBox.Free;
            end;
          {$ENDIF}
        end;
    {$ENDIF}
                    

In the About Box Form, conditionally compile for the Mac:

  • make the 'Close' button invisible (in the FormCreate procedure)
  • Unassign the frmAboutBox global variable (in the FormClose procedure)

    procedure TfrmAboutBox.FormClose(Sender: TObject; var CloseAction: TCloseAction
      );
    begin
      {$IFDEF DARWIN}
        CloseAction := caFree;
        frmAboutBox := nil;
      {$ENDIF}
    end;

    procedure TfrmAboutBox.FormCreate(Sender: TObject);
    begin
      // According to the Apple guidelines, About Boxes do not have a 'close' button
      {$IFDEF DARWIN}
        Button1.Visible:=False;
      {$ENDIF}
    end;
                    

Location of configuration and other supporting files differ

I have not really got a handle on the location of support files on the Mac. Two websites were helpful:

The final conclusion, although not convincingly decisive, was to store the database in:

  • "C:\Documents and Settings\user\Local Settings\Application Data\vfitness" on Windows (XP)
  • "~/Library/vfitness" on the Mac
    function TfrmMainGUI.GetDBpath():string;
    var
        temp:string;
    begin
        {$ifdef Darwin}
            temp := '~/Library/vfitness';
        {$else}
            temp := GetAppConfigDir(False);
        {$endif}
        Result := ExpandFileName(temp + '/vfitnessDB');
    end;
                    

Invisible tabsheets cannot have controls created by code

Controls created by code cannot have as their parent an invisible tabsheet. A workaround is to remove the controls from the tabsheet before making the tabsheet invisible:

procedure TvAddEdit.RemoveParentFromAEcontrols;
var
  i: integer;
begin
  for i := 0 to MaxFields do
    AE[i].Parent:=nil;
  dteDate.Parent    := nil;
  lblDate.Parent    := nil;
  cmbCourses.Parent := nil;
  lblCourse.Parent  := nil;
  btnNew.Parent     := nil;
  btnGo.Parent      := nil;
end;

procedure TvAddEdit.AddParentToAEcontrols;
var
  i: integer;
begin
  for i := 0 to MaxFields do
    AE[i].Parent:=TabPage;
  dteDate.Parent    := TabPage;
  lblDate.Parent    := TabPage;
  cmbCourses.Parent := TabPage;
  lblCourse.Parent  := TabPage;
  btnNew.Parent     := TabPage;
  btnGo.Parent      := TabPage;
end;
                    

TcomboBox onselect events are fired inconsistently

On the Mac, the onselect event in the TcomboBox is fired when setting the itemindex programmatically. It is not, in the Windows environment. The following code was added, after the itemindex is set. Note that 'cmbTableNamesSelect' is the name of my onselect procedure:

  {$ifdef windows}
  cmbTableNamesSelect(nil);
  {$endif}
                    

Source Code

The second edition of the source code is available here, and some Data Files are available here. The Data Files need to be saved to ~/ where ~ maps to your library/vfitness on a Mac, and to your local appdata directory\vfitness v2\ on Windows.

The second edition has the following changes:

  • the header of the sorted column is highlighted in light olive green (sorting is performed by clicking on the header)
  • extensive filtering of the data is possible
  • there is something on the 'options' tab, but is very experimental at this stage
  • also on the 'options' tab is the ability to edit the underlying data tables, including:
    • hide tables and columns
    • reorder the tables and columns
    • change the default field to plot on the X and Y axes
    • change the name of the field as seen in the column header
    • note that there is an edit box to add a new field and table, but this has not been implemented yet
  • another graph type has been added...points (to go with the line and bar graph). Note that the 'mixed' option will mix a bar graph with the latest selected point or line graph. The 'points' graph type was added mainly because changing the field to plot on the X axis caused the line and bar graphs to look quite messy.
  • both grid lines for the left and right axes are possible
  • the colour to plot the graph may be changed by clicking on the coloured box in the legend
  • beware when deleting courses. Currently there is no checking to make sure that the course is not used in another table. The database has not been configured with constraints either.
  • there are some TODO's in mainGUI.pas

You may send me comments by clicking here and making the obvious changes to the address. Please keep the suggestions constructive, and try to avoid trolling. Complements will be gladly accepted.