Wiki copymirror

This is a mirror of the wiki at pascal.chat. Editing, history and sign-in are there.

Cocoa Internals

The page is about the 64 bit Cocoa Widgetset internal implementation. The page should be useful for Cocoa widgetset developers (maintainers and/or contributors) as well as any developers who need to use the Cocoa-specific API.

Minimum Version

Enumeration in Objective-C classes

:* 10.5 - spiritual

:* 10.6 - de facto (a lot of 10.6 only APIs are used without any actual verification of the proper macOS version)

There's a reason to keep the backwards compatibility for 10.6. This is the last system that supports Rosetta applications to allow running PowerPC applications on an Intel processor.

Compiler Support

APIs that not (yet) provided by a corresponding version of FPC (delivered with Lazarus), should be declared at cocoa_extra.pas header

It's desired that the legacy version (i.e. deprecated APIs) would be wrapped into the minimum version defines.

If an application using LCL to be deployed to App Store, Apple would verify the used APIs and ban, if any older APIs are used.

Recompilation

If you tried to modify Cocoa-Widget and the next attempt at the project or its Cocoa compilation throws an unexpected error like "cocoawscommon.pas(y:x) Error: identifier idents no member "lclFrame"", you need to delete all Cocoa compiled units.

text
 ./lazarus/lcl/units/i386-darwin/cocoa

or

text
 ./lazarus/lcl/units/x86_64-darwin/cocoa

(to be reported - compiler issue with ignoring extensions)

Reconfigure LCL package

Another way is to configure the LCL package to always remove .o files before recompilation of the package itself. (The same effect as removing it from command line):

text
sh -c "rm -f ../units/$(TargetCPU)-$(TargetOS)/$(LCLWidgetType)/*"

Note that use of sh is necessary, because Lazarus IDE doesn't process * (asterisk)

LCL specific ObjC classes

In order to control and handle an NSView's behavior LCL uses descendent classes from standard Cocoa controls. That is, for NSWindow, TCocoaWindow is introduced. The descedants are used for the purpose of "overriding" default class implementation, where it is needed. In some cases using delegate classes is not enough.

There are two ways, in order to provide a common LCL(friendly) API interface for all Cocoa classes or sub-classes:

Code Style

The following requirement applies for Cocoa widgetset. Other widgetset may be following some other rules (historically), but in general LCL follows the same requirements.

pascal
 A := B; 
 A := B * C;
pascal
 procedure B;
 begin
   if A then // 2 character spacing from begin
   begin
     Start Here // 2 character spacing from begin
     Next Line
   end
   else
   begin
     Another Line
   end;
 end;
pascal
  if not Assigned(A) then
  begin
    Result := nil;
  end;

Using for-in instead of for

When dealing with arrays, it's common for Pascal to write the code using "Integer" type array index like:

delphi
var
  i : integer;
  l : TList;
...
 for i:=0 to l.Count-1 do
   ...

Cocoa provides it's own implementation of arrays using NSArray class. Writing the the loop in the same manner:

delphi
var
  i : integer;
  l : NSArray;
...
 for i:=0 to l.count-1 do
   ...

is possible, though not desired.

The reason for that, is that NSArray "count" is an NSUInteger type for Cocoa. This type has a different size on 32 and 64 platforms. (While "Integer" type is always 32 bit in FPC.)

Thus while the code written above might work just fine on 32-platform, it might cause a range check error (if such check is enabled) on a 64 bit machine. For example, when count is zero. Here's a simplest (no ObjC) example of the problem:

delphi
{$R+}
function Count: qword;
begin
  Result := 0;
end;
var
  i: Integer;
begin
  for i := 0 to Count-1 do // <- throws a range check error, because QWord result doesn't fit for Integer index
    WriteLn(i);
end.

However, running a loop from elements of an array is quite a common task. For example - changing attributes of all children controls, due to some action.

Instead of using a higher size Index variable (i.e. PtrInt or PtrUInt), it's handy to use for-in loop. Most common Objective C classes provide a special feature (NSFastEnumerator protocol) to do enumeration through its member. It works for the 32 bit and well as the 64 bit platform, in the same manner, but requires the Objective C 2.0 runtime version (with objectivec2 modeswitch enabled).

An iteration for-in loop might look like this:

delphi
var
  obj : NSObject;
  arr : NSArray;
...
 for obj in arr do
   ...

Writing a Cocoa app without the LCL

This is useful for testing hard to debug bugs in the LCL. Here is an example program written in Pascal-Cocoa which creates a window with 2 buttons and a simple set of menus:

delphi
program cocoa_without_lcl;

{$mode objfpc}{$H+}
{$modeswitch objectivec1}

uses
  CocoaAll, classes, sysutils;

type
  { TMyDelegate }
  TMyDelegate = objcclass(NSObject)
  public
    procedure HandleButtonClick_A(sender: id); message 'HandleButtonClick_A:';
    procedure HandleButtonClick_B(sender: id); message 'HandleButtonClick_B:';
    procedure HandleMenuClick_A(sender: id); message 'HandleMenuClick_A:';
    procedure HandleMenuClick_B(sender: id); message 'HandleMenuClick_B:';
  end;

var
  appName: NSString;
  window: NSWindow;
  pool: NSAutoreleasePool;
  lText: NSTextField;
  lButton: NSButton;
  lDelegate: TMyDelegate;
  //
  MainMenu_A: NSMenu;
  TopItem_A: NSMenuItem;
  TopMenu_A: NSMenu;
  MenuItem_A: NSMenuItem;
  //
  MainMenu_B: NSMenu;
  TopItem_B: NSMenuItem;
  TopMenu_B: NSMenu;
  MenuItem_B: NSMenuItem;

procedure TMyDelegate.HandleButtonClick_A(sender: id);
begin
  NSApp.setMainMenu(MainMenu_A);
end;

procedure TMyDelegate.HandleButtonClick_B(sender: id);
begin
  NSApp.setMainMenu(MainMenu_B);
end;

procedure TMyDelegate.HandleMenuClick_A(sender: id);
var
  Str: string;
begin
  Str := 'A '+inttostr(random(888));
  window.setTitle(NSSTR(@Str[1]));
end;

procedure TMyDelegate.HandleMenuClick_B(sender: id);
var
  Str: string;
begin
  Str := 'B '+inttostr(random(888));
  window.setTitle(NSSTR(@Str[1]));
end;

procedure CreateMenus;
var
  nsTitle: NSString;
  nsKey  : NSString;

  function CreateMenuItem(AName, AShortcut: string; ASelector: SEL): NSMenuItem;
  begin
    nsKey := NSSTR(PChar(AShortcut));
    nsTitle := NSSTR(PChar(AName));
    Result := NSMenuItem.alloc.initWithTitle_action_keyEquivalent(nsTitle, ASelector, nsKey);
    Result.setKeyEquivalentModifierMask(NSCommandKeyMask);
    nsTitle.release;
    nsKey.release;
    Result.setKeyEquivalentModifierMask(NSCommandKeyMask);
    Result.setTarget(lDelegate);
  end;

begin
  MainMenu_A := NSMenu.alloc.initWithTitle(NSSTR('Menu A'));
  TopItem_A := CreateMenuItem('TopItem A', '', nil);
  MainMenu_A.insertItem_atIndex(TopItem_A, 0);
  TopMenu_A := NSMenu.alloc.initWithTitle(NSSTR('TopMenu A'));
  TopItem_A.setSubmenu(TopMenu_A);
  MenuItem_A := CreateMenuItem('MenuItem A', 'A', objcselector('HandleMenuClick_A:'));
  MenuItem_A.setTarget(lDelegate);
  TopMenu_A.insertItem_atIndex(MenuItem_A, 0);

  MainMenu_B := NSMenu.alloc.initWithTitle(NSSTR('Menu B'));
  TopItem_B := CreateMenuItem('TopItem B', '', nil);
  MainMenu_B.insertItem_atIndex(TopItem_B, 0);
  TopMenu_B := NSMenu.alloc.initWithTitle(NSSTR('TopMenu B'));
  TopItem_B.setSubmenu(TopMenu_B);
  MenuItem_B := CreateMenuItem('MenuItem B', 'B', objcselector('HandleMenuClick_B:'));
  MenuItem_B.setTarget(lDelegate);
  TopMenu_B.insertItem_atIndex(MenuItem_B, 0);
end;

begin
  // Autorelease pool, app and window creation
  pool := NSAutoreleasePool.new;
  NSApp := NSApplication.sharedApplication;
  NSApp.setActivationPolicy(NSApplicationActivationPolicyRegular);
  appName := NSProcessInfo.processInfo.processName;
  window := NSWindow.alloc.initWithContentRect_styleMask_backing_defer(NSMakeRect(0, 0, 200, 200),
    NSTitledWindowMask or NSClosableWindowMask or NSMiniaturizableWindowMask,
    NSBackingStoreBuffered, False).autorelease;
  lDelegate := TMyDelegate.alloc.init;

  // text label
  lText := NSTextField.alloc.initWithFrame(NSMakeRect(50, 50, 120, 50)).autorelease;
  lText.setBezeled(False);
  lText.setDrawsBackground(False);
  lText.setEditable(False);
  lText.setSelectable(False);
  lText.setStringValue(NSSTR('NSTextField'));
  window.contentView.addSubview(lText);

  // button
  lButton := NSButton.alloc.initWithFrame(NSMakeRect(50, 100, 120, 50)).autorelease;
  window.contentView.addSubview(lButton);
  lButton.setTitle(NSSTR('Button A!'));
  lButton.setButtonType(NSMomentaryLightButton);
  lButton.setBezelStyle(NSRoundedBezelStyle);
  // button event handler setting
  lButton.setTarget(lDelegate);
  lButton.setAction(ObjCSelector(lDelegate.HandleButtonClick_A));

  // button B
  lButton := NSButton.alloc.initWithFrame(NSMakeRect(50, 150, 120, 50)).autorelease;
  window.contentView.addSubview(lButton);
  lButton.setTitle(NSSTR('Button B!'));
  lButton.setButtonType(NSMomentaryLightButton);
  lButton.setBezelStyle(NSRoundedBezelStyle);
  // button event handler setting
  lButton.setTarget(lDelegate);
  lButton.setAction(ObjCSelector(lDelegate.HandleButtonClick_B));

  // Menus
  CreateMenus();
  NSApp.setMainMenu(MainMenu_A);

  // Window showing and app running
  window.center;
  window.setTitle(appName);
  window.makeKeyAndOrderFront(nil);
  NSApp.activateIgnoringOtherApps(true);
  NSApp.run;
end.

Units

The units should use the ".pas" extension, not ".pp" for consistency. Use of "include" is not welcome and is kept there for legacy reasons only (not to lose the history, if a file is merged).

Unit

Description

CocoaPrivate

The unit contains most of the Cocoa classes overrides. The unit should stay LCL-classes free as much as possible (i.e. should not use Forms, StdCtrls, etc. Though using LCLTypes or LCLProc is ok).

CocoaUtils

The set of utility functions mostly to convert types (between LCL and Cocoa types)

Input constant conversion utilities are also placed here.

Cocoa_Extra

The purpose of the unit is to full-fill missing FPC headers declarations for macOS. (whatever is missing at CocoaAll).

Should not contain any additional functions, except for external and constant declarations.

CocoaButtons

Controls that are considered buttons (except for NSPopupButton) are declared in this unit.

CocoaCarent

The unit provides the code to implement caret for Cocoa. Cocoa itself doesn't have any "caret" specific API.

CocoaDatepicker

The unit contains the implementation of cocoa calendar control

CocoaGDIObjects

Highlever wrappers over Cocoa object to implement LCL GDI handles.

CocoaScrollers

All controls related to scrolling: TScrollBar and variants of ScrollView are implemented here

CocoaTabControls

Classes that are used to implement tabcontrol (and tabpage) are declared here

CocoaTables

The master table class is declared here. (used for TListView and TListBox).

Supplemental classes for NSCell and NSView based tables are also in this unit

CocoaTextEdits

Text editting controls are implemented here. Including Combobox and NSpopupBox (readonly combobox)

CocoaTheme

Theme drawing class.(todo: get rid of "customdrawing" parts)

CocoaWindows

Classes related to the window and its contents are implemented here

CocoaInt

cocoawinapih.inc / cocoawinapi.inc

cocoalclapih.inc /cocoalclapi.inc

The Widgetset class API (winApi-style) are implemented here.

TCocoaApplication class is also declared and implemented in this unit. Both Widgetset object and code of TCocoaApplication class are mutually using each other.

Widgetset interfacing units

CocoaWSCommon

The unit contains common bindings between native Cocoa (obcjclasses implemented in CocoaPrivate) and LCL. The actual callbacks interfaces are implemented here. Most of the "messaging" to LCL is also implemented here

Callback Objects

Why so Boolean?

Cocoa Widgetset introduces two ObjC-specific boolean types:

Both types were introduced to handle the issues with the Boolean type parameter. (Resulting Boolean value seems to be working as expected on x86_64).

However, the intent of each type is different:

See also


From the Free Pascal wiki, page "Cocoa Internals", as it stood on 2022-01-15 after a change by Skalogryz, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's Licensing page. If you have concerns about this material, message the admin.

Revision 4, last changed by WikiArchive .