1 { $Id: $}
2 {                  --------------------------------------------
3                   cocoawindows.pas  -  Cocoa internal classes
4                   --------------------------------------------
5 
6  This unit contains the private classhierarchy for the Cocoa implemetations
7 
8  *****************************************************************************
9   This file is part of the Lazarus Component Library (LCL)
10 
11   See the file COPYING.modifiedLGPL.txt, included in this distribution,
12   for details about the license.
13  *****************************************************************************
14 }
15 unit CocoaWindows;
16 
17 {$mode objfpc}{$H+}
18 {$modeswitch objectivec1}
19 {$modeswitch objectivec2}
20 {$interfaces corba}
21 {$include cocoadefines.inc}
22 
23 interface
24 
25 uses
26   // rtl+ftl
27   Types, Classes, SysUtils,
28   CGGeometry,
29   // Libs
30   MacOSAll, CocoaAll, CocoaUtils, CocoaGDIObjects,
31   cocoa_extra, CocoaPrivate, CocoaTextEdits, CocoaScrollers,
32   // LCL
33   //Forms,
34   LCLType, LCLProc;
35 
36 type
37 
38   { LCLWindowExtension }
39 
40   LCLWindowExtension = objccategory(NSWindow)
lclIsVisiblenull41     function lclIsVisible: Boolean; message 'lclIsVisible'; reintroduce;
42     procedure lclSetVisible(AVisible: Boolean); message 'lclSetVisible:'; reintroduce;
lclIsEnablednull43     function lclIsEnabled: Boolean; message 'lclIsEnabled'; reintroduce;
44     procedure lclSetEnabled(AEnabled: Boolean); message 'lclSetEnabled:'; reintroduce;
45 
lclWindowStatenull46     function lclWindowState: Integer; message 'lclWindowState'; reintroduce;
47     procedure lclInvalidateRect(const r: TRect); message 'lclInvalidateRect:'; reintroduce;
48     procedure lclInvalidate; message 'lclInvalidate'; reintroduce;
49     procedure lclUpdate; message 'lclUpdate'; reintroduce;
50     procedure lclRelativePos(var Left, Top: Integer); message 'lclRelativePos::'; reintroduce;
51     procedure lclLocalToScreen(var X, Y: Integer); message 'lclLocalToScreen::'; reintroduce;
52     procedure lclScreenToLocal(var X, Y: Integer); message 'lclScreenToLocal::'; reintroduce;
lclFramenull53     function lclFrame: TRect; message 'lclFrame'; reintroduce;
54     procedure lclSetFrame(const r: TRect); message 'lclSetFrame:'; reintroduce;
lclClientFramenull55     function lclClientFrame: TRect; message 'lclClientFrame'; reintroduce;
lclGetTopBarHeightnull56     function lclGetTopBarHeight:integer; message 'lclGetTopBarHeight'; reintroduce;
57     procedure lclOffsetMousePos(var Point: NSPoint); message 'lclOffsetMousePos:'; reintroduce;
58   end;
59 
60   { IWindowCallback }
61 
62   IWindowCallback = interface(ICommonCallBack)
CanActivatenull63     function CanActivate: Boolean;
64     procedure Activate;
65     procedure Deactivate;
66     procedure CloseQuery(var CanClose: Boolean);
67     procedure Close;
68     procedure Resize;
69     procedure Move;
70     procedure WindowStateChanged;
71 
GetEnablednull72     function GetEnabled: Boolean;
73     procedure SetEnabled(AValue: Boolean);
74 
AcceptFilesDragnull75     function AcceptFilesDrag: Boolean;
76     procedure DropFiles(const FileNames: array of string);
77 
HasCancelControlnull78     function HasCancelControl: Boolean;
HasDefaultControlnull79     function HasDefaultControl: Boolean;
80 
81     property Enabled: Boolean read GetEnabled write SetEnabled;
82   end;
83 
84   { TCocoaPanel }
85 
86   TCocoaPanel = objcclass(NSPanel, NSWindowDelegateProtocol)
87   protected
windowShouldClosenull88     function windowShouldClose(sender : id): LongBool; message 'windowShouldClose:';
89     procedure windowWillClose(notification: NSNotification); message 'windowWillClose:';
90     procedure windowDidBecomeKey(notification: NSNotification); message 'windowDidBecomeKey:';
91     procedure windowDidResignKey(notification: NSNotification); message 'windowDidResignKey:';
92     procedure windowDidResize(notification: NSNotification); message 'windowDidResize:';
93     procedure windowDidMove(notification: NSNotification); message 'windowDidMove:';
94   public
95     callback: IWindowCallback;
acceptsFirstRespondernull96     function acceptsFirstResponder: LCLObjCBoolean; override;
canBecomeKeyWindownull97     function canBecomeKeyWindow: LCLObjCBoolean; override;
becomeFirstRespondernull98     function becomeFirstResponder: LCLObjCBoolean; override;
resignFirstRespondernull99     function resignFirstResponder: LCLObjCBoolean; override;
lclGetCallbacknull100     function lclGetCallback: ICommonCallback; override;
101     procedure lclClearCallback; override;
102     // mouse
103     procedure mouseDown(event: NSEvent); override;
104     procedure mouseUp(event: NSEvent); override;
105     procedure rightMouseDown(event: NSEvent); override;
106     procedure rightMouseUp(event: NSEvent); override;
107     procedure rightMouseDragged(event: NSEvent); override;
108     procedure otherMouseDown(event: NSEvent); override;
109     procedure otherMouseUp(event: NSEvent); override;
110     procedure otherMouseDragged(event: NSEvent); override;
111     procedure mouseDragged(event: NSEvent); override;
112     procedure mouseEntered(event: NSEvent); override;
113     procedure mouseExited(event: NSEvent); override;
114     procedure mouseMoved(event: NSEvent); override;
115   end;
116 
117   { TCocoaWindow }
118 
119   TCocoaWindowContent = objcclass;
120 
121   TCocoaWindow = objcclass(NSWindow, NSWindowDelegateProtocol)
122   protected
123     fieldEditor: TCocoaFieldEditor;
124     firedMouseEvent: Boolean;
125     isInFullScreen: Boolean;
126     orderOutAfterFS : Boolean;
127     fsview: TCocoaWindowContent;
128 
windowShouldClosenull129     function windowShouldClose(sender : id): LongBool; message 'windowShouldClose:';
130     procedure windowWillClose(notification: NSNotification); message 'windowWillClose:';
windowWillReturnFieldEditor_toObjectnull131     function windowWillReturnFieldEditor_toObject(sender: NSWindow; client: id): id; message 'windowWillReturnFieldEditor:toObject:';
132     procedure windowDidBecomeKey(notification: NSNotification); message 'windowDidBecomeKey:';
133     procedure windowDidResignKey(notification: NSNotification); message 'windowDidResignKey:';
134     procedure windowDidResize(notification: NSNotification); message 'windowDidResize:';
135     procedure windowDidMove(notification: NSNotification); message 'windowDidMove:';
136     procedure windowDidMiniaturize(notification: NSNotification); message 'windowDidMiniaturize:';
137     procedure windowDidDeminiaturize(notification: NSNotification); message 'windowDidDeminiaturize:';
138     // fullscreen notifications are only reported for 10.7 fullscreen
139     procedure windowWillEnterFullScreen(notification: NSNotification); message 'windowWillEnterFullScreen:';
140     procedure windowDidEnterFullScreen(notification: NSNotification); message 'windowDidEnterFullScreen:';
141     procedure windowDidExitFullScreen(notification: NSNotification); message 'windowDidExitFullScreen:';
142   public
143     _keyEvCallback: ICommonCallback;
144     callback: IWindowCallback;
145     keepWinLevel : NSInteger;
146     //LCLForm: TCustomForm;
147     procedure dealloc; override;
acceptsFirstRespondernull148     function acceptsFirstResponder: LCLObjCBoolean; override;
canBecomeKeyWindownull149     function canBecomeKeyWindow: LCLObjCBoolean; override;
becomeFirstRespondernull150     function becomeFirstResponder: LCLObjCBoolean; override;
resignFirstRespondernull151     function resignFirstResponder: LCLObjCBoolean; override;
lclGetCallbacknull152     function lclGetCallback: ICommonCallback; override;
153     procedure lclClearCallback; override;
154     // mouse
155     procedure mouseDown(event: NSEvent); override;
156     procedure mouseUp(event: NSEvent); override;
157     procedure rightMouseDown(event: NSEvent); override;
158     procedure rightMouseUp(event: NSEvent); override;
159     procedure rightMouseDragged(event: NSEvent); override;
160     procedure otherMouseDown(event: NSEvent); override;
161     procedure otherMouseUp(event: NSEvent); override;
162     procedure otherMouseDragged(event: NSEvent); override;
163     procedure mouseDragged(event: NSEvent); override;
164     procedure mouseEntered(event: NSEvent); override;
165     procedure mouseExited(event: NSEvent); override;
166     procedure mouseMoved(event: NSEvent); override;
167     procedure scrollWheel(event: NSEvent); override;
168     procedure sendEvent(event: NSEvent); override;
169     // key
170     procedure keyDown(event: NSEvent); override;
171     // menu support
172     procedure lclItemSelected(sender: id); message 'lclItemSelected:';
173 
174     procedure lclSwitchFullScreen(AEnabled: Boolean); message 'lclSwitchFullScreen:';
lclIsFullScreennull175     function lclIsFullScreen: Boolean; message 'lclIsFullScreen';
176   end;
177 
178   { TCocoaDesignOverlay }
179 
180   TCocoaDesignOverlay = objcclass(NSView)
181     callback  : ICommonCallback;
182     procedure drawRect(r: NSRect); override;
acceptsFirstRespondernull183     function acceptsFirstResponder: LCLObjCBoolean; override;
hitTestnull184     function hitTest(aPoint: NSPoint): NSView; override;
lclGetCallbacknull185     function lclGetCallback: ICommonCallback; override;
186     procedure lclClearCallback; override;
187   end;
188 
189   { TCocoaWindowContentDocument }
190 
191   TCocoaWindowContentDocument = objcclass(TCocoaCustomControl)
192   protected
193     procedure didBecomeKeyNotification(sender: NSNotification); message 'didBecomeKeyNotification:';
194     procedure didResignKeyNotification(sender: NSNotification); message 'didResignKeyNotification:';
195   public
196     overlay: NSView;
197     wincallback: IWindowCallback;
lclWindowStatenull198     function lclWindowState: Integer; override;
199     procedure didAddSubview(aview: NSView); override;
200     procedure setNeedsDisplay_(aflag: LCLObjCBoolean); override;
201     procedure setNeedsDisplayInRect(arect: NSRect); override;
202     // NSDraggingDestinationCategory
draggingEnterednull203     function draggingEntered(sender: NSDraggingInfoProtocol): NSDragOperation; override;
performDragOperationnull204     function performDragOperation(sender: NSDraggingInfoProtocol): LCLObjCBoolean; override;
205   end;
206 
207   { TCocoaWindowContent }
208 
209   TCocoaWindowContent = objcclass(TCocoaScrollView)
210   private
211     _stringValue: NSString;
212   public
213     wincallback: IWindowCallback;
214     isembedded: Boolean; // true - if the content is inside of another control, false - if the content is in its own window;
215     preventKeyOnShow: Boolean;
216     ownwin: NSWindow;
217     fswin: NSWindow; // window that was used as a content prior to switching to old-school fullscreen
218     popup_parent: HWND; // if not 0, indicates that we should set the popup parent
performKeyEquivalentnull219     function performKeyEquivalent(event: NSEvent): LCLObjCBoolean; override;
220     procedure resolvePopupParent(); message 'resolvePopupParent';
lclOwnWindownull221     function lclOwnWindow: NSWindow; message 'lclOwnWindow';
222     procedure lclSetFrame(const r: TRect); override;
lclFramenull223     function lclFrame: TRect; override;
224     procedure lclRelativePos(var Left, Top: Integer); override;
225     procedure viewDidMoveToSuperview; override;
226     procedure viewDidMoveToWindow; override;
227     procedure viewWillMoveToWindow(newWindow: CocoaAll.NSWindow); override;
228     procedure dealloc; override;
229     procedure setHidden(aisHidden: LCLObjCBoolean); override;
230 
231     procedure setStringValue(avalue: NSString); message 'setStringValue:';
stringValuenull232     function stringValue: NSString; message 'stringValue';
233   end;
234 
235 implementation
236 
237 { TCocoaDesignOverlay }
238 
239 procedure TCocoaDesignOverlay.drawRect(r: NSRect);
240 begin
241   if Assigned(callback) then
242     callback.DrawOverlay(NSGraphicsContext.currentContext, bounds, r);
243   inherited drawRect(r);
244 end;
245 
TCocoaDesignOverlay.acceptsFirstRespondernull246 function TCocoaDesignOverlay.acceptsFirstResponder: LCLObjCBoolean;
247 begin
248   Result:=false; // no focus
249 end;
250 
hitTestnull251 function TCocoaDesignOverlay.hitTest(aPoint: NSPoint): NSView;
252 begin
253   Result:=nil;  // no mouse
254 end;
255 
lclGetCallbacknull256 function TCocoaDesignOverlay.lclGetCallback: ICommonCallback;
257 begin
258   Result := callback;
259 end;
260 
261 procedure TCocoaDesignOverlay.lclClearCallback;
262 begin
263   callback := nil;
264 end;
265 
266 { TCocoaWindowContent }
267 
lclWindowStatenull268 function TCocoaWindowContentDocument.lclWindowState: Integer;
269 begin
270   if window.lclGetCallback = wincallback then // not embedded
271     Result := window.lclWindowState
272   else
273     Result := inherited lclWindowState
274 end;
275 
276 procedure TCocoaWindowContentDocument.didAddSubview(aview: NSView);
277 const
278   mustHaveSizing = (NSViewWidthSizable or NSViewHeightSizable);
279 begin
280   if Assigned(aview) and Assigned(overlay) and (overlay<>aview) then
281   begin
282     overlay.retain;
283     overlay.removeFromSuperview;
284     addSubview_positioned_relativeTo(overlay, NSWindowAbove, nil);
285     overlay.release;
286     overlay.setFrame(frame);
287     if (overlay.autoresizingMask and mustHaveSizing) <> mustHaveSizing then
288       overlay.setAutoresizingMask(overlay.autoresizingMask or mustHaveSizing);
289   end;
290   inherited didAddSubview(aview);
291 end;
292 
293 procedure TCocoaWindowContentDocument.didBecomeKeyNotification(sender: NSNotification);
294 begin
295   if Assigned(callback) then
296     callback.DidBecomeKeyNotification;
297 end;
298 
299 procedure TCocoaWindowContentDocument.didResignKeyNotification(sender: NSNotification);
300 begin
301   if Assigned(callback) then
302     callback.DidResignKeyNotification;
303 end;
304 
305 { TCocoaWindowContent }
306 
307 procedure NSResponderHotKeys(asender: NSResponder; event: NSEvent; var handled: LCLObjCBoolean; atarget: NSResponder);
308 var
309   undoManager: NSUndoManager;
310   ch : System.WideChar;
311   msk : LongWord; //NSEventModifierFlags;
312   chr : NSString;
313 const
314   NSModsMask = NSShiftKeyMask
315             or NSControlKeyMask
316             or NSAlternateKeyMask
317             or NSCommandKeyMask;
318 begin
319   // todo: system keys could be overriden. thus need to review the current
320   //       keyboard configuration first. See "Key Bindings" at
321   //       https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/EventOverview/TextDefaultsBindings/TextDefaultsBindings.html
322 
323   handled := false;
324   if (event.type_ = NSKeyDown) then
325   begin
326     msk := (event.modifierFlags and NSModsMask);
327     if (msk = 0) then Exit;
328     // characters contain untranslated input characters, for layouts
329     // without latin characters (i.e. greek cyrillic, arabic).
330     // But for Latin based alphabet, "characters" are the same as
331     // "charactersWihtoutModifiers"....The Roman Empire legacy today!
332     //
333     // charatersWithoutModifiers contain translated characters for any layout.
334 
335     // In order for the system shortkeys to work on any layout "characters"
336     // property must be used
337     chr := event.characters;
338     if (chr.length = 0)
339       then ch := #0
340       else ch := System.WideChar(chr.characterAtIndex(0));
341     case ch of
342       'a': if msk= NSCommandKeyMask then
343              handled := NSApplication(NSApp).sendAction_to_from(objcselector('selectAll:'), atarget, asender);
344       'c': if msk = NSCommandKeyMask then
345              handled := NSApplication(NSApp).sendAction_to_from(objcselector('copy:'), atarget, asender);
346       'v': if msk = NSCommandKeyMask then
347              handled := NSApplication(NSApp).sendAction_to_from(objcselector('paste:'), atarget, asender);
348       'x': if msk = NSCommandKeyMask then
349              handled := NSApplication(NSApp).sendAction_to_from(objcselector('cut:'), atarget, asender);
350       'z': if msk = NSCommandKeyMask then
351       begin
352         undoManager := atarget.undoManager;
353         if Assigned(undoManager) and undoManager.canUndo then
354         begin
355           handled := true;
356           undoManager.undo;
357         end;
358       end;
359       else if msk = (NSCommandKeyMask or NSShiftKeyMask) then
360       begin
361         undoManager := atarget.undoManager;
362         if Assigned(undoManager) and undoManager.canRedo then
363         begin
364           handled := true;
365           undoManager.redo;
366         end;
367       end;
368     end;
369   end;
370 end;
371 
performKeyEquivalentnull372 function TCocoaWindowContent.performKeyEquivalent(event: NSEvent): LCLObjCBoolean;
373 var
374   resp : NSResponder;
375   wn   : NSWindow;
376   ch   : System.WideChar;
377 begin
378   Result := false;
379 
380   // If the form has a default or cancel button, capture Return and Escape to
381   // prevent further processing.  Actually clicking the buttons is handled in
382   // the LCL in response to the keyUp
383   if Assigned(wincallback) and (event.modifierFlags_ = 0) then
384   begin
385     ch := NSEventRawKeyChar(event);
386     if (((ch = System.WideChar(NSCarriageReturnCharacter)) and wincallback.HasDefaultControl)
387       or ((ch = #27{Escape}) and wincallback.HasCancelControl)) then
388     begin
389       Result := true;
390       Exit;
391     end;
392   end;
393 
394   // Support Cut/Copy/Paste if the firstResponder is an NSTextView.
395   // This could be done in TCocoaFieldEditor and TCocoaTextView's
396   // performKeyEquivalent, but that wouldn't work for non-LCL edits.
397   // Xcode Cocoa apps rely on the commands existing in the main menu
398   wn := window;
399   if Assigned(wn) then
400   begin
401     resp := wn.firstResponder;
402     if Assigned(resp) and resp.isKindOfClass_(NSTextView) and
403        resp.lclIsEnabled then
404     begin
405       NSResponderHotKeys(self, event, Result, resp);
406       if Result then Exit;
407     end;
408   end;
409 
410   Result := inherited performKeyEquivalent(event);
411 end;
412 
413 procedure TCocoaWindowContent.resolvePopupParent();
414 var
415   lWindow: NSWindow;
416   isfront: Boolean;
417 begin
418   lWindow := nil;
419   if (popup_parent <> 0) then
420   begin
421     if (NSObject(popup_parent).isKindOfClass(TCocoaWindowContent)) then
422     begin
423       if (not TCocoaWindowContent(popup_parent).isembedded) then
424         lWindow := NSWindow(TCocoaWindowContent(popup_parent).window);
425     end
426     else
427     begin
428       lWindow := NSWindow(popup_parent);
429     end;
430   end;
431   if lWindow <> nil then
432   begin
433     isfront:=NSApplication(NSApp).mainWindow=self.window;
434 
435     lWindow.addChildWindow_ordered(Self.window, NSWindowAbove);
436 
437     // adding a window as a child, would bring the "child" form to the bottom
438     // of Zorder. need to restore the order.
439     if isfront then self.window.makeKeyAndOrderFront(nil);
440   end;
441   popup_parent := 0;
442 end;
443 
lclOwnWindownull444 function TCocoaWindowContent.lclOwnWindow: NSWindow;
445 begin
446   if not isembedded then
447     Result := NSWindow(window)
448   else
449     Result := nil;
450 end;
451 
452 procedure TCocoaWindowContent.lclSetFrame(const r: TRect);
453 begin
454   if isembedded then
455     inherited lclSetFrame(r)
456   else
457     window.lclSetFrame(r);
458 end;
459 
TCocoaWindowContent.lclFramenull460 function TCocoaWindowContent.lclFrame: TRect;
461 var
462   wfrm : TRect;
463 begin
464   Result := inherited lclFrame;
465   if not isembedded then
466   begin
467     //Window bounds should return "client rect" in screen coordinates
468     if Assigned(window.screen) then
469       NSToLCLRect(window.frame, NSScreenZeroHeight, wfrm)
470     else
471       wfrm := NSRectToRect(frame);
472     OffsetRect(Result, -Result.Left+wfrm.Left, -Result.Top+wfrm.Top);
473   end;
474 end;
475 
476 procedure TCocoaWindowContent.lclRelativePos(var Left, Top: Integer);
477 begin
478   if isembedded then
479     inherited lclRelativePos(Left, Top)
480   else
481     window.lclRelativePos(Left, Top);
482 end;
483 
484 procedure TCocoaWindowContent.viewDidMoveToSuperview;
485 begin
486   inherited viewDidMoveToSuperview;
487 end;
488 
489 procedure TCocoaWindowContent.viewDidMoveToWindow;
490 begin
491   isembedded := window.contentView <> self;
492   if isembedded then
493   begin
494     ownwin := nil;
495   end
496   else
497   begin
498     ownwin := NSWindow(window);
499     if Assigned(stringValue) then
500       ownwin.setTitle(stringValue);
501   end;
502   inherited viewDidMoveToWindow;
503 end;
504 
505 procedure TCocoaWindowContent.viewWillMoveToWindow(newWindow: CocoaAll.NSWindow);
506 begin
507   if newWindow<>nil then
508      newWindow.setAcceptsMouseMovedEvents(True);
509   if not isembedded and (newWindow <> window) then
510   begin
511     if Assigned(window) then
512       setStringValue(window.title);
513     ownwin := nil;
514     isembedded := false;
515   end;
516   inherited viewWillMoveToWindow(newWindow);
517 end;
518 
519 procedure TCocoaWindowContent.dealloc;
520 begin
521   inherited dealloc;
522 end;
523 
524 procedure TCocoaWindowContent.setHidden(aisHidden: LCLObjCBoolean);
525 var
526   cw : TCocoaWindow;
527 begin
528   if isembedded then
529   begin
530     {$ifdef BOOLFIX}
531     inherited setHidden_(Ord(aisHidden));
532     {$else}
533     inherited setHidden(aisHidden);
534     {$endif}
535   end
536   else
537   begin
538     if aisHidden and window.isVisible then
539     begin
540       if (window.isKindOfClass(TCocoaWindow)) then
541         cw := TCocoaWindow(window)
542       else
543         cw := nil;
544       if cw.lclIsFullScreen then
545       begin
546         cw.orderOutAfterFS := true;
547         cw.lclSwitchFullScreen(false);
548       end else
549         window.orderOut(nil);
550     end
551     else
552     if not aisHidden and not window.isVisible then
553     begin
554       if preventKeyOnShow then // used for Hint-windows, so they would not steal the focus from the current window
555         window.orderFrontRegardless
556       else
557         window.makeKeyAndOrderFront(nil);
558     end;
559   end;
560 end;
561 
562 procedure TCocoaWindowContent.setStringValue(avalue: NSString);
563 begin
564   if _stringValue = avalue then Exit;
565   if Assigned(_stringValue) then _stringValue.release;
566   _stringValue := AValue;
567   if Assigned(_stringValue) then _stringValue.retain;
568 end;
569 
stringValuenull570 function TCocoaWindowContent.stringValue: NSString;
571 begin
572   Result := _stringValue;
573 end;
574 
575 { TCocoaPanel }
576 
windowShouldClosenull577 function TCocoaPanel.windowShouldClose(sender: id): LongBool;
578 var
579   canClose: Boolean;
580 begin
581   canClose := True;
582   if Assigned(callback) then
583     callback.CloseQuery(canClose);
584   Result := canClose;
585 end;
586 
587 procedure TCocoaPanel.windowWillClose(notification: NSNotification);
588 begin
589   if Assigned(callback) then
590     callback.Close;
591 end;
592 
593 procedure TCocoaPanel.windowDidBecomeKey(notification: NSNotification);
594 begin
595   if Assigned(callback) then
596     callback.Activate;
597 end;
598 
599 procedure TCocoaPanel.windowDidResignKey(notification: NSNotification);
600 begin
601   if Assigned(callback) then
602     callback.Deactivate;
603 end;
604 
605 procedure TCocoaPanel.windowDidResize(notification: NSNotification);
606 begin
607   if Assigned(callback) then
608     callback.Resize;
609 end;
610 
611 procedure TCocoaPanel.windowDidMove(notification: NSNotification);
612 begin
613   if Assigned(callback) then
614     callback.Move;
615 end;
616 
TCocoaPanel.acceptsFirstRespondernull617 function TCocoaPanel.acceptsFirstResponder: LCLObjCBoolean;
618 begin
619   Result := True;
620 end;
621 
canBecomeKeyWindownull622 function TCocoaPanel.canBecomeKeyWindow: LCLObjCBoolean;
623 begin
624   Result := Assigned(callback) and callback.CanActivate;
625 end;
626 
becomeFirstRespondernull627 function TCocoaPanel.becomeFirstResponder: LCLObjCBoolean;
628 begin
629   Result := inherited becomeFirstResponder;
630 //  if Assigned(callback) then
631 //    callback.BecomeFirstResponder;
632 end;
633 
resignFirstRespondernull634 function TCocoaPanel.resignFirstResponder: LCLObjCBoolean;
635 begin
636   Result := inherited resignFirstResponder;
637 //  if Assigned(callback) then
638 //    callback.ResignFirstResponder;
639 end;
640 
lclGetCallbacknull641 function TCocoaPanel.lclGetCallback: ICommonCallback;
642 begin
643   Result := callback;
644 end;
645 
646 procedure TCocoaPanel.lclClearCallback;
647 begin
648   callback := nil;
649   contentView.lclClearCallback;
650 end;
651 
652 procedure TCocoaPanel.mouseDown(event: NSEvent);
653 begin
654   if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
655     inherited mouseDown(event);
656 end;
657 
658 procedure TCocoaPanel.mouseUp(event: NSEvent);
659 begin
660   if Assigned(callback) then callback.MouseUpDownEvent(event);
661     inherited mouseUp(event);
662 end;
663 
664 procedure TCocoaPanel.rightMouseDown(event: NSEvent);
665 begin
666   if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
667     inherited rightMouseUp(event);
668 end;
669 
670 procedure TCocoaPanel.rightMouseUp(event: NSEvent);
671 begin
672   if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
673     inherited rightMouseDown(event);
674 end;
675 
676 procedure TCocoaPanel.rightMouseDragged(event: NSEvent);
677 begin
678   if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
679     inherited rightMouseDragged(event);
680 end;
681 
682 procedure TCocoaPanel.otherMouseDown(event: NSEvent);
683 begin
684   if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
685     inherited otherMouseDown(event);
686 end;
687 
688 procedure TCocoaPanel.otherMouseUp(event: NSEvent);
689 begin
690   if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
691     inherited otherMouseUp(event);
692 end;
693 
694 procedure TCocoaPanel.otherMouseDragged(event: NSEvent);
695 begin
696   if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
697     inherited otherMouseDown(event);
698 end;
699 
700 procedure TCocoaPanel.mouseDragged(event: NSEvent);
701 begin
702   if not Assigned(callback) or not callback.MouseMove(event) then
703     inherited mouseDragged(event);
704 end;
705 
706 procedure TCocoaPanel.mouseEntered(event: NSEvent);
707 begin
708   inherited mouseEntered(event);
709 end;
710 
711 procedure TCocoaPanel.mouseExited(event: NSEvent);
712 begin
713   inherited mouseExited(event);
714 end;
715 
716 procedure TCocoaPanel.mouseMoved(event: NSEvent);
717 begin
718   if not Assigned(callback) or not callback.MouseMove(event) then
719     inherited mouseMoved(event);
720 end;
721 
722 { TCocoaWindow }
723 
windowShouldClosenull724 function TCocoaWindow.windowShouldClose(sender: id): LongBool;
725 var
726   canClose: Boolean;
727 begin
728   canClose := True;
729   if Assigned(callback) then
730     callback.CloseQuery(canClose);
731   Result := canClose;
732 end;
733 
TCocoaWindow.windowWillReturnFieldEditor_toObjectnull734 function TCocoaWindow.windowWillReturnFieldEditor_toObject(sender: NSWindow; client: id): id;
735 begin
736   //DebugLn('[TCocoaWindow.windowWillReturnFieldEditor_toObject]');
737   Result := nil;
738 
739   if (NSObject(client).isKindOfClass(NSTextField)) and Assigned(NSObject(client).lclGetCallBack) then
740   begin
741     if (fieldEditor = nil) then
742     begin
743       fieldEditor := TCocoaFieldEditor.alloc.init;
744       fieldEditor.setFieldEditor(True);
745     end;
746     Result := fieldEditor;
747   end;
748 end;
749 
750 procedure TCocoaWindow.windowWillClose(notification: NSNotification);
751 begin
752   if Assigned(callback) then
753     callback.Close;
754 end;
755 
756 procedure TCocoaWindow.windowDidBecomeKey(notification: NSNotification);
757 begin
758   // forcing to keep the level as all other LCL windows
759   // Modal windows tend to "restore" their elevated level
760   // And that doesn't work for modal windows that are "Showing" other windows
761 
762   // Another approach is to set elevated levels for windows, shown during modal session
763   // That requires to revoke the elevated level from windows on closing a window session
764   // This might be the way to go, if FormStyle (such as fsStayOnTop) would come
765   // in conflict with modality
766   if level <> keepWinLevel then begin
767     setLevel(keepWinLevel);
768   end;
769 
770   if Assigned(callback) then
771     callback.Activate;
772 
773   // LCL didn't change focus. TCocoaWindow should not keep the focus for itself
774   // and it should pass it to it's content view
775   if (firstResponder = self)
776     and Assigned(contentView)
777     and (contentView.isKindOfClass(TCocoaWindowContent)) then
778     self.makeFirstResponder( TCocoaWindowContent(contentView).documentView );
779 end;
780 
781 procedure TCocoaWindow.windowDidResignKey(notification: NSNotification);
782 begin
783   if Assigned(callback) then
784     callback.Deactivate;
785 end;
786 
787 procedure TCocoaWindow.windowDidResize(notification: NSNotification);
788 begin
789   if Assigned(callback) then
790     callback.Resize;
791 end;
792 
793 procedure TCocoaWindow.windowDidMove(notification: NSNotification);
794 begin
795   if Assigned(callback) then
796     callback.Move;
797 end;
798 
799 procedure TCocoaWindow.windowDidMiniaturize(notification: NSNotification);
800 begin
801   if Assigned(callback) then
802     callback.WindowStateChanged;
803 end;
804 
805 procedure TCocoaWindow.windowDidDeminiaturize(notification: NSNotification);
806 begin
807   if Assigned(callback) then
808     callback.WindowStateChanged;
809 end;
810 
811 procedure TCocoaWindow.windowWillEnterFullScreen(notification: NSNotification);
812 begin
813   if not isInFullScreen then isInFullScreen := true;
814   // setting fullscreen flag, prior to the "Fullscreen" has actually been enabled.
815   // MacOS does 10.7 fullscreen switch with an animation (that's about 1 second long)
816   // if during that animation there's another call toggleFullScreen() is made
817   // then macOS produces an output "not in fullscreen state" and ignores the call.
818 end;
819 
820 procedure TCocoaWindow.windowDidEnterFullScreen(notification: NSNotification);
821 begin
822   if not isInFullScreen then isInFullScreen := true;
823 end;
824 
825 procedure TCocoaWindow.windowDidExitFullScreen(notification: NSNotification);
826 begin
827   if isInFullScreen then isInFullScreen := false;
828   if orderOutAfterFS then begin
829     self.orderOut(nil);
830     orderOutAfterFS := false;
831   end;
832 end;
833 
834 procedure TCocoaWindow.dealloc;
835 begin
836   if (fieldEditor <> nil) then
837   begin
838     fieldEditor.release;
839     fieldEditor := nil;
840   end;
841   inherited dealloc;
842 end;
843 
acceptsFirstRespondernull844 function TCocoaWindow.acceptsFirstResponder: LCLObjCBoolean;
845 begin
846   Result := True;
847 end;
848 
canBecomeKeyWindownull849 function TCocoaWindow.canBecomeKeyWindow: LCLObjCBoolean;
850 begin
851   Result := Assigned(callback) and callback.CanActivate;
852 end;
853 
becomeFirstRespondernull854 function TCocoaWindow.becomeFirstResponder: LCLObjCBoolean;
855 begin
856   Result := inherited becomeFirstResponder;
857   // uncommenting the following lines starts an endless focus loop
858 
859 //  if Assigned(callback) then
860 //    callback.BecomeFirstResponder;
861 end;
862 
resignFirstRespondernull863 function TCocoaWindow.resignFirstResponder: LCLObjCBoolean;
864 begin
865   Result := inherited resignFirstResponder;
866 //  if Assigned(callback) then
867 //    callback.ResignFirstResponder;
868 end;
869 
lclGetCallbacknull870 function TCocoaWindow.lclGetCallback: ICommonCallback;
871 begin
872   Result := callback;
873 end;
874 
875 procedure TCocoaWindow.lclClearCallback;
876 begin
877   callback := nil;
878   contentView.lclClearCallback;
879 end;
880 
881 procedure TCocoaWindow.mouseDown(event: NSEvent);
882 begin
883   //if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
884     inherited mouseDown(event);
885 end;
886 
887 procedure TCocoaWindow.mouseUp(event: NSEvent);
888 begin
889   //firedMouseEvent:=true;
890   //if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
891     inherited mouseUp(event);
892 end;
893 
894 procedure TCocoaWindow.rightMouseDown(event: NSEvent);
895 begin
896   //if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
897     inherited rightMouseUp(event);
898 end;
899 
900 procedure TCocoaWindow.rightMouseUp(event: NSEvent);
901 begin
902   //if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
903     inherited rightMouseDown(event);
904 end;
905 
906 procedure TCocoaWindow.rightMouseDragged(event: NSEvent);
907 begin
908   //if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
909     inherited rightMouseDragged(event);
910 end;
911 
912 procedure TCocoaWindow.otherMouseDown(event: NSEvent);
913 begin
914   //if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
915     inherited otherMouseDown(event);
916 end;
917 
918 procedure TCocoaWindow.otherMouseUp(event: NSEvent);
919 begin
920   //if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
921     inherited otherMouseUp(event);
922 end;
923 
924 procedure TCocoaWindow.otherMouseDragged(event: NSEvent);
925 begin
926   //if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
927     inherited otherMouseDown(event);
928 end;
929 
930 procedure TCocoaWindow.mouseDragged(event: NSEvent);
931 begin
932   //if not Assigned(callback) or not callback.MouseMove(event) then
933     inherited mouseDragged(event);
934 end;
935 
936 procedure TCocoaWindow.mouseEntered(event: NSEvent);
937 begin
938   inherited mouseEntered(event);
939 end;
940 
941 procedure TCocoaWindow.mouseExited(event: NSEvent);
942 begin
943   inherited mouseExited(event);
944 end;
945 
946 procedure TCocoaWindow.mouseMoved(event: NSEvent);
947 begin
948   // no need to call for callback or anything, because WindowContent
949   // will take care of it anyway
950   inherited mouseMoved(event);
951 end;
952 
953 procedure TCocoaWindow.scrollWheel(event: NSEvent);
954 begin
955   if not Assigned(callback) or not callback.scrollWheel(event) then
956     inherited scrollWheel(event);
957 end;
958 
959 procedure TCocoaWindow.sendEvent(event: NSEvent);
960 var
961   Epos: NSPoint;
962   cr : NSRect;
963   fr : NSRect;
964   prc: Boolean;
965 begin
966   if event.type_ = NSLeftMouseUp then
967   // This code is introduced here for an odd cocoa feature.
968   // mouseUp is not fired, if pressed on Window's title.
969   // (even though mouseDown, mouseMove and mouseDragged are fired)
970   // (there are some information in the internet, that mouseDown is not firing as well)
971   // (however this is not true for macOS 10.12)
972   // The logic below is as following. If mouseUp event arrived
973   // and mouse position is on the title of the form.
974   // then try to process the event. If event was not processed, call mouseUp()
975   // specifically.
976   begin
977     Epos:=event.locationInWindow;
978     fr := frame;
979     fr.origin.x:=0;
980     fr.origin.y:=0;
981     cr := contentRectForFrameRect(fr);
982     if NSPointInRect(Epos, fr) and not NSPointInRect(Epos, cr) then
983     begin
984       firedMouseEvent := false;
985       inherited sendEvent(event);
986       if not firedMouseEvent then mouseUp(event);
987     end
988     else
989       inherited sendEvent(event);
990   end
991   else
992     inherited sendEvent(event);
993 end;
994 
995 procedure TCocoaWindow.keyDown(event: NSEvent);
996 var
997   mn : NSMenu;
998   allowcocoa : Boolean;
999 begin
1000   if performKeyEquivalent(event) then
1001     Exit;
1002 
1003   mn := NSApp.MainMenu;
1004   if Assigned(mn) and mn.performKeyEquivalent(event) then
1005     Exit;
1006 
1007   if Assigned(_keyEvCallback) then
1008   begin
1009     allowcocoa := True;
1010     _keyEvCallback.KeyEvAfterDown(allowcocoa);
1011     if not allowcocoa then
1012       Exit;
1013   end;
1014 
1015   inherited keyDown(event);
1016 end;
1017 
draggingEnterednull1018 function TCocoaWindowContentDocument.draggingEntered(sender: NSDraggingInfoProtocol): NSDragOperation;
1019 begin
1020   Result := NSDragOperationNone;
1021   if (wincallback <> nil) and (wincallback.AcceptFilesDrag) then
1022     Result := sender.draggingSourceOperationMask();
1023 end;
1024 
performDragOperationnull1025 function TCocoaWindowContentDocument.performDragOperation(sender: NSDraggingInfoProtocol): LCLObjCBoolean;
1026 var
1027   draggedURLs{, lClasses}: NSArray;
1028   lFiles: array of string;
1029   i: Integer;
1030   pboard: NSPasteboard;
1031   lNSStr: NSString;
1032   //lClass: pobjc_class;
1033 begin
1034   Result := False;
1035   pboard := sender.draggingPasteboard();
1036 
1037   // Multiple strings
1038   draggedURLs := pboard.propertyListForType(NSFilenamesPboardType);
1039   SetLength(lFiles, draggedURLs.count);
1040   for i := 0 to draggedURLs.count-1 do
1041   begin
1042     lNSStr := NSString(draggedURLs.objectAtIndex(i));
1043     lFiles[i] := NSStringToString(lNSStr);
1044   end;
1045 
1046   // Multiple URLs -> Results in strange URLs with file:// protocol
1047   {if pboard.types.containsObject(NSURLPboardType) then
1048   begin
1049     lClass := NSURL.classClass;
1050     lClasses := NSArray.arrayWithObjects_count(@lClass, 1);
1051     draggedURLs := pboard.readObjectsForClasses_options(lClasses, nil);
1052     SetLength(lFiles, draggedURLs.count);
1053     for i := 0 to draggedURLs.count-1 do
1054     begin
1055       lNSStr := NSURL(draggedURLs.objectAtIndex(i)).absoluteString;
1056       lFiles[i] := NSStringToString(lNSStr);
1057     end;
1058   end;}
1059 
1060   if (Length(lFiles) > 0) and (wincallback <> nil)  then
1061     wincallback.DropFiles(lFiles);
1062   Result := True;
1063 end;
1064 
1065 procedure TCocoaWindowContentDocument.setNeedsDisplay_(aflag: LCLObjCBoolean);
1066 begin
1067   inherited setNeedsDisplay_(aflag);
1068   if Assigned(overlay) then overlay.setNeedsDisplay_(aflag);
1069 end;
1070 
1071 procedure TCocoaWindowContentDocument.setNeedsDisplayInRect(arect: NSRect);
1072 begin
1073   inherited setNeedsDisplayInRect(arect);
1074   if Assigned(overlay) then overlay.setNeedsDisplayInRect(arect);
1075 end;
1076 
1077 procedure TCocoaWindow.lclItemSelected(sender: id);
1078 begin
1079 
1080 end;
1081 
1082 procedure TCocoaWindow.lclSwitchFullScreen(AEnabled: Boolean);
1083 const
1084   fsmask =  NSWindowCollectionBehaviorFullScreenPrimary
1085             or
1086             NSWindowCollectionBehaviorFullScreenAuxiliary;
1087 begin
1088   if isInFullScreen = AEnabled then Exit; // nothing to do
1089 
1090   //todo: there are two flavours of full-screen
1091   //      (soft) macOS 10.7+ toggleFullScreen()
1092   //      (hard) macOS 10.5+ enterFullScreenMode_withOptions()
shouldnull1093   //      the function should be smart enough to figure out the available mode
1094 
1095   isInFullScreen := AEnabled;
1096   if NSAppKitVersionNumber >= NSAppKitVersionNumber10_7 then
1097   begin
1098     if Self.collectionBehavior and fsmask = 0 then
1099       Self.setCollectionBehavior(Self.collectionBehavior or NSWindowCollectionBehaviorFullScreenPrimary);
1100     Self.toggleFullScreen(nil);
1101   end
1102   else
1103   begin
1104     if AEnabled then
1105     begin
1106       fsview := TCocoaWindowContent(contentView);
1107       fsview.fswin := self;
1108       fsview.enterFullScreenMode_withOptions(self.screen, nil);
1109     end else begin
1110       fsview.exitFullScreenModeWithOptions(nil);
1111       self.setContentView(fsview);
1112       fsview := nil;
1113     end;
1114   end;
1115 end;
1116 
TCocoaWindow.lclIsFullScreennull1117 function TCocoaWindow.lclIsFullScreen: Boolean;
1118 begin
1119   Result := isInFullScreen;
1120 end;
1121 
1122 { LCLWindowExtension }
1123 
LCLWindowExtension.lclIsVisiblenull1124 function LCLWindowExtension.lclIsVisible: Boolean;
1125 begin
1126   Result := isVisible;
1127 end;
1128 
1129 procedure LCLWindowExtension.lclSetVisible(AVisible: Boolean);
1130 begin
1131   if AVisible then
1132     orderFrontRegardless
1133   else
1134     orderOut(nil);
1135 end;
1136 
LCLWindowExtension.lclIsEnablednull1137 function LCLWindowExtension.lclIsEnabled: Boolean;
1138 begin
1139   Result := contentView.lclIsEnabled;
1140 end;
1141 
1142 procedure LCLWindowExtension.lclSetEnabled(AEnabled: Boolean);
1143 begin
1144   contentView.lclSetEnabled(AEnabled);
1145 end;
1146 
LCLWindowExtension.lclWindowStatenull1147 function LCLWindowExtension.lclWindowState: Integer;
1148 const
1149   NSFullScreenWindowMask = 1 shl 14;
1150 begin
1151   if isMiniaturized then
1152     Result := SIZE_MINIMIZED
1153   else
1154   if (styleMask and NSFullScreenWindowMask) <> 0 then
1155     Result := SIZE_FULLSCREEN
1156   else
1157   if isZoomed then
1158     Result := SIZE_MAXIMIZED
1159   else
1160     Result := SIZE_RESTORED;
1161 end;
1162 
1163 procedure LCLWindowExtension.lclInvalidateRect(const r: TRect);
1164 begin
1165   contentView.lclInvalidateRect(r);
1166 end;
1167 
1168 procedure LCLWindowExtension.lclInvalidate;
1169 begin
1170   contentView.lclInvalidate;
1171 end;
1172 
1173 procedure LCLWindowExtension.lclUpdate;
1174 begin
1175   contentView.lclUpdate;
1176 end;
1177 
1178 procedure LCLWindowExtension.lclRelativePos(var Left, Top: Integer);
1179 var
1180    f: NSRect;
1181 begin
1182   if Assigned(screen) then
1183   begin
1184     f:=frame;
1185     Left := Round(f.origin.x);
1186     Top := Round(NSScreenZeroHeight - f.size.height - f.origin.y);
1187     //debugln('Top:'+dbgs(Top));
1188   end;
1189 end;
1190 
1191 procedure LCLWindowExtension.lclLocalToScreen(var X, Y:Integer);
1192 var
1193   f: NSRect;
1194 begin
1195   if Assigned(screen) then
1196   begin
1197     f := frame;
1198     inc(X, Round(f.origin.x));
1199     inc(Y, Round(NSScreenZeroHeight - f.size.height - f.origin.y));
1200   end;
1201 end;
1202 
1203 procedure LCLWindowExtension.lclScreenToLocal(var X, Y: Integer);
1204 var
1205   f: NSRect;
1206 begin
1207   if Assigned(screen) then
1208   begin
1209     f := frame;
1210     dec(X, Round(f.origin.x));
1211     dec(Y, Round(screen.frame.size.height - f.size.height - f.origin.y));
1212   end;
1213 end;
1214 
LCLWindowExtension.lclFramenull1215 function LCLWindowExtension.lclFrame: TRect;
1216 begin
1217   if Assigned(contentView) then
1218     Result:=contentView.lclFrame
1219   else
1220   begin
1221     if Assigned(screen) then
1222       NSToLCLRect(frame, NSScreenZeroHeight, Result)
1223     else
1224       Result := NSRectToRect(frame);
1225   end;
1226 end;
1227 
LCLWindowExtension.lclGetTopBarHeightnull1228 function LCLWindowExtension.lclGetTopBarHeight:integer;
1229 var nw,nf: NSRect;
1230 begin
1231   nf:= NSMakeRect (0, 0, 100, 100);
1232   nw:=contentRectForFrameRect(nf);
1233   result:=round(nf.size.height-nw.size.height);
1234 end;
1235 
1236 procedure LCLWindowExtension.lclOffsetMousePos(var Point: NSPoint);
1237 begin
1238   Point.y := contentView.bounds.size.height - Point.y;
1239 end;
1240 
1241 procedure NSScreenGetRect(sc: NSScreen; mainScreenHeight: double; out r: TRect);
1242 var
1243   fr : NSRect;
1244 begin
1245   fr := sc.frame;
1246   r := Bounds(
1247     Round(fr.origin.x),
1248     Round(fr.origin.y - fr.size.height + mainScreenHeight),
1249     Round(fr.size.width), Round(fr.size.height)
1250   );
1251 end;
1252 
1253 procedure NSScreenGetRect(sc: NSScreen; out r: TRect);
1254 begin
1255   NSScreenGetRect(sc, NSScreen.mainScreen.frame.size.height, r);
1256 end;
1257 
1258 procedure LCLWindowExtension.lclSetFrame(const r: TRect);
1259 var
1260   ns : NSRect;
1261   h  : integer;
1262 begin
1263   LCLToNSRect(r, NSScreenZeroHeight, ns);
1264 
1265   // add topbar height
1266   h:=lclGetTopBarHeight;
1267   ns.size.height:=ns.size.height+h;
1268   ns.origin.y:=ns.origin.y-h;
1269   {$ifdef BOOLFIX}
1270   setFrame_display_(ns, Ord(isVisible));
1271   {$else}
1272   setFrame_display(ns, isVisible);
1273   {$endif}
1274 end;
1275 
LCLWindowExtension.lclClientFramenull1276 function LCLWindowExtension.lclClientFrame: TRect;
1277 var
1278   wFrame, cFrame: NSRect;
1279 begin
1280   wFrame := frame;
1281   cFrame := contentRectForFrameRect(wFrame);
1282   Result.Left := Round(cFrame.origin.x - wFrame.origin.x);
1283   Result.Top := Round(wFrame.origin.y + wFrame.size.height - cFrame.origin.y - cFrame.size.height);
1284   Result.Right := Result.Left + Round(cFrame.size.width);
1285   Result.Bottom := Result.Top + Round(cFrame.size.height);
1286 end;
1287 
1288 end.
1289 
1290