Cocoa Internals

From Lazarus wiki
Jump to: navigation, search

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

Recompilation

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

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

or

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

(to be reported - compiler issue with ignoring extensions)

LCL specific ObjC classes

In order to control and handle an NSView's behavior LCL uses decedent classes from standard Cocoa controls. I.e. for NSWindow TCocoaWindow is introduced. The decedent 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:

  • use protocol (similar to Interfaces in Object Pascal form). Any class, sub-classing from standard Cocoa classes, would have to implement the protocol. The downside of this approach is - a lot of code would be duplicated.
  • use categories (similar to Javascript prototypes, not available in Object Pascal. Class-helpers looks alike, but are not the same). Categories allow to add methods (only methods! adding variables is not allowed) to any base class of Objective-C. Any descendants of this class automatically acquire the method, and it's default implementation. Also any subclass, can override the default implementation with its own. Apple doesn't recommend the use of categories (due to ambiguity and possible conflicts among different libraries introducing their own extensions). See more at the Cocoa Internals Extensions page

Handles

  • Window handle (HWND) is always NSView.
    • TCustomWSForm it is content NSView.
    • Any control that has scroll bars (i.e. TCustomWSList) the it its the embedding NSScrollView (TCocoaScrollView)
LCL Control Cocoa Class as .Handle Notes
TMemo NSTextView inside a NSScrollView
TGroupBox TCocoaGroupBox (NSBox) There's also a content view created to hold all the child controls within the box

Code Style

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

  • Operators Keep the operator separated by spaces between operands
 A := B; 
 A := B * C;
  • Blocks The main rule is to make 2 character spacing from the code block start. Begin / Else should start on a new line.
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;
  • Standard Function Name please keep naming in ProperCase. Reserved words should be lower case. Name of (global/local) variables and fields should match declaration. (local variables should start with lower case)
 if not Assigned(A) then
 begin
   Result := nil;
 end;

Themed Drawing / Custom Controls

HITheme API is deprecated by Apple together with Carbon. It's still available in OSX, but for i386 only. Presumably will be completely removed on 64-bit only OSX.

However, some LCL controls cannot be mapped to existing Cocoa provided controls and thus might require some "custom drawing". The drawing should look system native though. In order to achieve that NSCell family could be used. NSCell allows to draw (hit-test and as well as handle user input) for OSX native elements. If Apple to change appears of controls (cells), the LCL application would "pick-up" the look automatically.

In order to be drawn NSCells requires a present of NSView, thus it's hard to use them forthe implementation of LCL TTheme APIs

Example of NSCell usage is CocoaStatusBar drawing

Fonts

NSFont class provides a number of class methods to get proper system font without selecting the font by name. The same of getting system-native font size.

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:

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

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). But this is not happening :(
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

CocoaInt

cocoawinapi.h

cocoalclapi.h

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

See Also