Cocoa Internals/Application

From Lazarus wiki
Jump to: navigation, search

Cocoa's Application API pretty much matches what LCL provides. It could be possible to simply call NSApp apis, that correspond to LCL APIs. However, there's a major difference, that makes a simple solution not possible. LCL Application provides a methods HandleMessages and ProcessMessagess allowing a manual control over the event-loop.

NSApp doesn't provide any corresponding counter parts, thus it's necessary to process events loops manually.

Manual Event Loop

Cocoa Widgetset implements even loops manually. Apple documentation advises that it's possible, though could be complicated.

The actual event processing code could be found at AppWaitMessage and AppProcessMessage.

procedure TCocoaWidgetSet.AppWaitMessage;
var
  event : NSEvent;
  pool:NSAutoReleasePool;
begin
  pool := NSAutoreleasePool.alloc.init;
  event := NSApp.nextEventMatchingMask_untilDate_inMode_dequeue(NSAnyEventMask, NSDate.distantFuture, NSDefaultRunLoopMode, true);
  if event <> nil then
  begin
    if (event.type_ = NSApplicationDefined) and (event.subtype = LCLEventSubTypeMessage) and (event.data1 = LM_NULL) and (event.data2 = AppHandle) then
      CheckSynchronize
    else
      NSApp.sendEvent(event);
    NSApp.updateWindows;
  end;
  pool.release;
end;

Sub-classing NSApplication

With a manual event loop implementation, there's another problem showed up.

NSWindows are behaving differently depending if NSApplication has started or not. (running property returning TRUE or FALSE). The Apple documentation doesn't mention that specifically, except for the following note:

Important
Many AppKit classes rely on the NSApplication class and may not work properly until this class is fully initialized.
As a result, you should not, for example, attempt to invoke methods of other AppKit classes from an initialization method of an NSApplication subclass.

If NSApplication is not running, then NSWindows would not pass the focus to another Window with the application. Instead they would keep the focus (if hidden) or make no window a focused window (if closed). (see #32177)

There are two options to make NSApplication running flag to set to true:

  • use NSApp default run method (which could not be used, due to the need of manual event processing)
  • subclass NSApplication, override "isRunning" (getter of "running" property)

The sub-classing option is used, introducing TCocoaApplication class. It overrides two methods:

  • run - the code is actually LCL Loop procedure, which would process ObjC events loop manually
  • isRunning - returns true, if run method was actually called.

Clipboard

In Cocoa, clipboard is named Pasteboard (class NSPasteboard);

The global (system-wide) pasteboard is available via NSPasteboard.generalPasteboard method. Primary and Secondary selection boards are just unique named NSPasteboards allocated at start.

The whole implementation of API is in TCocoaWidgetSet class. (todo: could/should be separated)

The older (10.5 and earlier) APIs are used for operations. (starting with 10.6 Apple suggests using the new API, but the old one has not been deprecated so far)

There's not NSPasteboard change notification in place (unlike Windows), thus the only way to track, if the content got changed (in LCL/Windows terms - clipboard ownership was lost) is through checking "changeCount" property. The property is checked on EVERY event processed (which might impact efficiency). The only way to get rid of that is to rewrite Clipboard LCL object. (instead of relying on a notification - it should check for the change at "Get" data calls)

See Also