mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-07-17 00:56:34 +02:00
2012 lines
58 KiB
ObjectPascal
2012 lines
58 KiB
ObjectPascal
{ $Id: $}
|
||
{ --------------------------------------------
|
||
cocoaprivate.pp - Cocoa internal classes
|
||
--------------------------------------------
|
||
|
||
This unit contains the private classhierarchy for the Cocoa implemetations
|
||
|
||
*****************************************************************************
|
||
This file is part of the Lazarus Component Library (LCL)
|
||
|
||
See the file COPYING.modifiedLGPL.txt, included in this distribution,
|
||
for details about the license.
|
||
*****************************************************************************
|
||
}
|
||
unit CocoaPrivate;
|
||
|
||
{$mode objfpc}{$H+}
|
||
{$modeswitch objectivec1}
|
||
{$modeswitch objectivec2}
|
||
{$interfaces corba}
|
||
{$include cocoadefines.inc}
|
||
|
||
{.$DEFINE COCOA_DEBUG_SETBOUNDS}
|
||
{.$DEFINE COCOA_SPIN_DEBUG}
|
||
{.$DEFINE COCOA_SPINEDIT_INSIDE_CONTAINER}
|
||
{.$DEFINE COCOA_SUPERVIEW_HEIGHT}
|
||
|
||
interface
|
||
|
||
uses
|
||
// rtl+ftl
|
||
Types, Classes, SysUtils,
|
||
// Libs
|
||
MacOSAll, CocoaAll, CocoaUtils, CocoaGDIObjects, CocoaCursor,
|
||
cocoa_extra,
|
||
// LCL
|
||
LCLType,
|
||
LazUTF8;
|
||
|
||
const
|
||
SPINEDIT_DEFAULT_STEPPER_WIDTH = 15;
|
||
SPINEDIT_EDIT_SPACING_FOR_SELECTION = 4;
|
||
STATUSBAR_DEFAULT_HEIGHT = 18;
|
||
|
||
type
|
||
// Some components might be using CocoaPrivate for use of LCLObjCBoolean
|
||
// Thus this declaration needs to be here.
|
||
LCLObjCBoolean = cocoa_extra.LCLObjCBoolean;
|
||
|
||
{ ICommonCallback }
|
||
|
||
ICommonCallback = interface
|
||
// mouse events
|
||
function MouseUpDownEvent(Event: NSEvent; AForceAsMouseUp: Boolean = False; AOverrideBlock: Boolean = False): Boolean;
|
||
procedure MouseClick;
|
||
function MouseMove(Event: NSEvent): Boolean;
|
||
|
||
// KeyEvXXX methods were introduced to allow a better control
|
||
// over when Cocoa keys processing is being called.
|
||
// (The initial KeyEvent() replicates Carbon implementation, and it's not
|
||
// suitable for Cocoa, due to the use of OOP and the extual "inherited Key..."needs to be called
|
||
// where for Carbon there's a special fucntion to call the "next event handler" present)
|
||
//
|
||
// The desired use is as following:
|
||
// Call KeyEvPrepare and pass NSEvent object
|
||
// after that call KeyEvBefore and pass a flag if AllowCocoaHandle
|
||
//
|
||
// The call would populate the flag. If it's "True" you should call "inherited" method (to let Cocoa handle the key).
|
||
// If the flag returned "False", you should not call inherited.
|
||
//
|
||
// No matter what the flag value was you should call KeyEvAfter.
|
||
procedure KeyEvBefore(Event: NSEvent; out AllowCocoaHandle: boolean);
|
||
procedure KeyEvAfter;
|
||
procedure KeyEvAfterDown(out AllowCocoaHandle: boolean);
|
||
procedure KeyEvHandled;
|
||
procedure SetTabSuppress(ASuppress: Boolean);
|
||
|
||
// only Cocoa Event Mechanism (no LCL Event), if the IME is in use
|
||
function IsCocoaOnlyState: Boolean;
|
||
procedure SetCocoaOnlyState( state:Boolean );
|
||
|
||
function scrollWheel(Event: NSEvent): Boolean;
|
||
function CanFocus: Boolean;
|
||
// size, pos events
|
||
procedure frameDidChange(sender: id);
|
||
procedure boundsDidChange(sender: id);
|
||
// misc events
|
||
procedure Draw(ctx: NSGraphicsContext; const bounds, dirty: NSRect);
|
||
procedure DrawBackground(ctx: NSGraphicsContext; const bounds, dirty: NSRect);
|
||
procedure DrawOverlay(ctx: NSGraphicsContext; const bounds, dirty: NSRect);
|
||
procedure BecomeFirstResponder;
|
||
procedure ResignFirstResponder;
|
||
procedure DidBecomeKeyNotification;
|
||
procedure DidResignKeyNotification;
|
||
procedure SendOnChange;
|
||
procedure SendOnTextChanged;
|
||
procedure scroll(isVert: Boolean; Pos: Integer; AScrollPart: NSScrollerPart = NSScrollerNoPart);
|
||
// non event methods
|
||
function DeliverMessage(Msg: Cardinal; WParam: WParam; LParam: LParam): LResult;
|
||
function GetPropStorage: TStringList;
|
||
function GetContext: TCocoaContext;
|
||
function GetTarget: TObject;
|
||
function GetHasCaret: Boolean;
|
||
function GetCallbackObject: TObject;
|
||
procedure SetHasCaret(AValue: Boolean);
|
||
function GetIsOpaque: Boolean;
|
||
procedure SetIsOpaque(AValue: Boolean);
|
||
function GetShouldBeEnabled: Boolean;
|
||
// the method is called, when handle is being destroyed.
|
||
// the callback object to stay alive a little longer than LCL object (Target)
|
||
// thus it needs to know that LCL object has been destroyed.
|
||
// After this called has been removed, any Cocoa events should not be
|
||
// forwarded to LCL target
|
||
procedure RemoveTarget;
|
||
|
||
procedure InputClientInsertText(const utf8: string);
|
||
|
||
// properties
|
||
property HasCaret: Boolean read GetHasCaret write SetHasCaret;
|
||
property IsOpaque: Boolean read GetIsOpaque write SetIsOpaque;
|
||
property CocoaOnlyState: Boolean read IsCocoaOnlyState write SetCocoaOnlyState;
|
||
end;
|
||
|
||
{ LCLObjectExtension }
|
||
|
||
LCLObjectExtension = objccategory(NSObject)
|
||
function lclIsEnabled: Boolean; message 'lclIsEnabled';
|
||
procedure lclSetEnabled(AEnabled: Boolean); message 'lclSetEnabled:';
|
||
function lclIsVisible: Boolean; message 'lclIsVisible';
|
||
procedure lclSetVisible(AVisible: Boolean); message 'lclSetVisible:';
|
||
function lclWindowState: Integer; message 'lclWindowState';
|
||
|
||
procedure lclInvalidateRect(const r: TRect); message 'lclInvalidateRect:';
|
||
procedure lclInvalidate; message 'lclInvalidate';
|
||
procedure lclUpdate; message 'lclUpdate';
|
||
|
||
// Returns the position of the view or window, in the immediate
|
||
// parent (view or screen), relative to its client coordinates system
|
||
// Left and Top are always returned in LCL coordinate system.
|
||
procedure lclLocalToScreen(var X, Y: Integer); message 'lclLocalToScreen::';
|
||
procedure lclScreenToLocal(var X, Y: Integer); message 'lclScreenToLocal::';
|
||
function lclParent: id; message 'lclParent';
|
||
function lclFrame: TRect; message 'lclFrame';
|
||
procedure lclSetFrame(const r: TRect); message 'lclSetFrame:';
|
||
|
||
// returns rectangle describing deltas to get "Layout" rectangle from "Frame" rectangle
|
||
// left, top - return offsets from top-left corner of the control (not reversed as in Cocoa coordinates)
|
||
// (values are typically positive)
|
||
// right, bottom - offsets for bottom-right corner
|
||
// (typically negative)
|
||
function lclGetFrameToLayoutDelta: TRect; message 'lclGetFrameToLayoutDelta';
|
||
|
||
function lclClientFrame: TRect; message 'lclClientFrame';
|
||
function lclGetCallback: ICommonCallback; message 'lclGetCallback';
|
||
procedure lclClearCallback; message 'lclClearCallback';
|
||
function lclGetPropStorage: TStringList; message 'lclGetPropStorage';
|
||
function lclGetTarget: TObject; message 'lclGetTarget';
|
||
function lclDeliverMessage(Msg: Cardinal; WParam: WParam; LParam: LParam): LResult; message 'lclDeliverMessage:::';
|
||
function lclContentView: NSView; message 'lclContentView';
|
||
procedure lclOffsetMousePos(var Point: NSPoint); message 'lclOffsetMousePos:';
|
||
procedure lclExpectedKeys(var wantTabs, wantArrows, wantReturn, wantAll: Boolean); message 'lclExpectedKeys::::';
|
||
function lclIsMouseInAuxArea(Event: NSEvent): Boolean; message 'lclMouseInAuxArea:';
|
||
end;
|
||
|
||
{ LCLViewExtension }
|
||
|
||
LCLViewExtension = objccategory(NSView)
|
||
function lclInitWithCreateParams(const AParams: TCreateParams): id; message 'lclInitWithCreateParams:';
|
||
function lclIsEnabled: Boolean; message 'lclIsEnabled'; reintroduce;
|
||
procedure lclSetEnabled(AEnabled: Boolean); message 'lclSetEnabled:'; reintroduce;
|
||
|
||
function lclIsVisible: Boolean; message 'lclIsVisible'; reintroduce;
|
||
procedure lclSetVisible(AVisible: Boolean); message 'lclSetVisible:'; reintroduce;
|
||
function lclIsPainting: Boolean; message 'lclIsPainting';
|
||
procedure lclInvalidateRect(const r: TRect); message 'lclInvalidateRect:'; reintroduce;
|
||
procedure lclInvalidate; message 'lclInvalidate'; reintroduce;
|
||
procedure lclUpdate; message 'lclUpdate'; reintroduce;
|
||
procedure lclLocalToScreen(var X, Y: Integer); message 'lclLocalToScreen::'; reintroduce;
|
||
procedure lclScreenToLocal(var X, Y: Integer); message 'lclScreenToLocal::'; reintroduce;
|
||
function lclParent: id; message 'lclParent'; reintroduce;
|
||
function lclFrame: TRect; message 'lclFrame'; reintroduce;
|
||
procedure lclSetFrame(const r: TRect); message 'lclSetFrame:'; reintroduce;
|
||
function lclClientFrame: TRect; message 'lclClientFrame'; reintroduce;
|
||
function lclContentView: NSView; message 'lclContentView'; reintroduce;
|
||
procedure lclOffsetMousePos(var Point: NSPoint); message 'lclOffsetMousePos:'; reintroduce;
|
||
end;
|
||
|
||
{ LCLControlExtension }
|
||
|
||
LCLControlExtension = objccategory(NSControl)
|
||
function lclIsEnabled: Boolean; message 'lclIsEnabled'; reintroduce;
|
||
procedure lclSetEnabled(AEnabled: Boolean); message 'lclSetEnabled:'; reintroduce;
|
||
end;
|
||
|
||
{ TCocoaCustomControl }
|
||
|
||
TCocoaCustomControl = objcclass(NSControl, NSTextInputClientProtocol)
|
||
private
|
||
fstr : NSString;
|
||
|
||
isdrawing : integer;
|
||
faileddraw : Boolean;
|
||
|
||
_inIME: Boolean;
|
||
private
|
||
function getWindowEditor(): NSTextView; message 'getWindowEditor';
|
||
procedure DoCallInputClientInsertText(nsText:NSString); message 'DoCallInputClientInsertText:';
|
||
public
|
||
callback: ICommonCallback;
|
||
auxMouseByParent: Boolean;
|
||
procedure dealloc; override;
|
||
function acceptsFirstResponder: LCLObjCBoolean; override;
|
||
procedure drawRect(dirtyRect: NSRect); override;
|
||
function lclGetCallback: ICommonCallback; override;
|
||
procedure lclClearCallback; override;
|
||
function lclIsMouseInAuxArea(Event: NSevent): Boolean; override;
|
||
// mouse
|
||
function acceptsFirstMouse(event: NSEvent): LCLObjCBoolean; override;
|
||
procedure mouseDown(event: NSEvent); override;
|
||
procedure mouseUp(event: NSEvent); override;
|
||
procedure rightMouseDown(event: NSEvent); override;
|
||
procedure rightMouseUp(event: NSEvent); override;
|
||
procedure rightMouseDragged(event: NSEvent); override;
|
||
procedure otherMouseDown(event: NSEvent); override;
|
||
procedure otherMouseUp(event: NSEvent); override;
|
||
procedure otherMouseDragged(event: NSEvent); override;
|
||
procedure mouseDragged(event: NSEvent); override;
|
||
procedure mouseEntered(event: NSEvent); override;
|
||
procedure mouseExited(event: NSEvent); override;
|
||
procedure mouseMoved(event: NSEvent); override;
|
||
procedure scrollWheel(event: NSEvent); override;
|
||
// nsview
|
||
procedure setFrame(aframe: NSRect); override;
|
||
// value
|
||
procedure setStringValue(avalue: NSString); override;
|
||
function stringValue: NSString; override;
|
||
procedure addSubView(aview: NSView); override;
|
||
|
||
public
|
||
// NSTextInputClientProtocol related.
|
||
// implements a base NSTextInputClient for non-editable LCL CustomControl,
|
||
// like Form, Grid, ListView, that are not system control and not FullEditControl.
|
||
// 1. when using IME in these controls, a temporary and one-time editor is shown
|
||
// at the bottom of the control, supporting IME such as Chinese.
|
||
// 2. refers to MacOS Finder, when using IME in the file list view,
|
||
// a small window will pop up at the bottom of the screen for input.
|
||
// the text can then be used for filename starting character match.
|
||
// 3. it is useful for implementing IME support for controls that do not
|
||
// have a text input window.
|
||
procedure keyDown(theEvent: NSEvent); override;
|
||
procedure insertText_replacementRange (aString: id; replacementRange: NSRange);
|
||
procedure setMarkedText_selectedRange_replacementRange (aString: id; selectedRange: NSRange; replacementRange: NSRange);
|
||
procedure unmarkText;
|
||
function selectedRange: NSRange;
|
||
function markedRange: NSRange;
|
||
function hasMarkedText: LCLObjCBoolean;
|
||
function attributedSubstringForProposedRange_actualRange (aRange: NSRange; actualRange: NSRangePointer): NSAttributedString;
|
||
function validAttributesForMarkedText: NSArray;
|
||
function firstRectForCharacterRange_actualRange (aRange: NSRange; actualRange: NSRangePointer): NSRect;
|
||
function characterIndexForPoint (aPoint: NSPoint): NSUInteger;
|
||
procedure doCommandBySelector (aSelector: SEL); override;
|
||
end;
|
||
|
||
{ ICocoaIMEControl }
|
||
|
||
// IME Parameters for Cocoa Interface internal and LCL Full Control Edit
|
||
// intentionally keep the Record type, emphasizing that it is only a simple type,
|
||
// only used as parameters, don‘t put into logical functions
|
||
TCocoaIMEParameters = record
|
||
text: ShortString; // Marked Text
|
||
textCharLength: Integer; // length in code point
|
||
textByteLength: Integer; // length in bytes
|
||
textNSLength: Integer; // length in code unit (NSString)
|
||
selectedStart: Integer; // selected range start in code point
|
||
selectedLength: Integer; // selected range length in code point
|
||
eatAmount: Integer; // delete char out of Marked Text
|
||
isFirstCall: Boolean; // if first in the IME session
|
||
end;
|
||
|
||
// the LCL Component that need Cocoa IME support need to
|
||
// implement this simple interface
|
||
// class LazSynCocoaIMM in SynEdit Component for reference
|
||
// class ATSynEdit_Adapter_CocoaIME in ATSynEdit Component for reference
|
||
ICocoaIMEControl = interface
|
||
procedure IMESessionBegin;
|
||
procedure IMESessionEnd;
|
||
procedure IMEUpdateIntermediateText( var params: TCocoaIMEParameters );
|
||
procedure IMEInsertFinalText( var params: TCocoaIMEParameters );
|
||
function IMEGetTextBound( var params: TCocoaIMEParameters ) : TRect;
|
||
end;
|
||
|
||
{ TCocoaFullControlEdit }
|
||
|
||
// backend of LCL Full Control Edit Component (such as SynEdit/ATSynEdit)
|
||
// Key Class for Cocoa IME support
|
||
// 1. obtain IME capability from Cocoa by implementing NSTextInputClientProtocol
|
||
// 2. synchronize IME data with LCL via ICocoaIMEControl
|
||
TCocoaFullControlEdit = objcclass(TCocoaCustomControl)
|
||
private
|
||
_currentParams: TCocoaIMEParameters;
|
||
_currentMarkedText: NSString;
|
||
public
|
||
imeHandler: ICocoaIMEControl;
|
||
public
|
||
procedure keyDown(theEvent: NSEvent); override;
|
||
procedure mouseDown(event: NSEvent); override;
|
||
procedure mouseUp(event: NSEvent); override;
|
||
function resignFirstResponder: ObjCBOOL; override;
|
||
|
||
procedure setMarkedText_selectedRange_replacementRange (aString: id; newRange: NSRange; replacementRange: NSRange); override;
|
||
procedure insertText_replacementRange (aString: id; replacementRange: NSRange); override;
|
||
procedure unmarkText; override;
|
||
function markedRange: NSRange; override;
|
||
function selectedRange: NSRange; override;
|
||
function hasMarkedText: LCLObjCBoolean; override;
|
||
function firstRectForCharacterRange_actualRange ({%H-}aRange: NSRange; {%H-}actualRange: NSRangePointer): NSRect; override;
|
||
end;
|
||
|
||
TStatusItemData = record
|
||
Text : NSString;
|
||
Width : Integer;
|
||
Align : TAlignment;
|
||
end;
|
||
|
||
TStatusItemDataArray = array of TStatusItemData;
|
||
|
||
{ TCocoaStatusBar }
|
||
|
||
IStatusBarCallback = interface {(ICommonCallback) // not needed to inherit from ICommonCallback}
|
||
function GetBarsCount: Integer;
|
||
//todo: consider the use Cocoa native types, instead of FPC TAlignment
|
||
function GetBarItem(idx: Integer; var txt: String;
|
||
var width: Integer; var align: TAlignment): Boolean;
|
||
procedure DrawPanel(idx: Integer; const r: TRect);
|
||
end;
|
||
|
||
TCocoaStatusBar = objcclass(TCocoaCustomControl)
|
||
public
|
||
//StatusBar : TStatusBar;
|
||
barcallback : IStatusBarCallback;
|
||
panelCell : NSCell;
|
||
procedure drawRect(dirtyRect: NSRect); override;
|
||
procedure dealloc; override;
|
||
end;
|
||
|
||
{ TCocoaGroupBox }
|
||
|
||
TCocoaGroupBox = objcclass(NSBox)
|
||
public
|
||
callback: ICommonCallback;
|
||
function acceptsFirstResponder: LCLObjCBoolean; override;
|
||
function lclGetCallback: ICommonCallback; override;
|
||
procedure lclClearCallback; override;
|
||
function lclClientFrame: TRect; override;
|
||
function lclContentView: NSView; override;
|
||
function lclGetFrameToLayoutDelta: TRect; override;
|
||
end;
|
||
|
||
|
||
const
|
||
PROGRESS_REG_HEIGHT = 16; // no longer applies on later macOS version
|
||
PROGRESS_SMALL_HEIGHT = 10;
|
||
|
||
type
|
||
{ TCocoaProgressIndicator }
|
||
|
||
TCocoaProgressIndicator = objcclass(NSProgressIndicator)
|
||
callback: ICommonCallback;
|
||
function acceptsFirstResponder: LCLObjCBoolean; override;
|
||
function lclGetCallback: ICommonCallback; override;
|
||
procedure lclClearCallback; override;
|
||
function lclGetFrameToLayoutDelta: TRect; override;
|
||
procedure lclSetFrame(const r: TRect); override;
|
||
// mouse
|
||
function acceptsFirstMouse(event: NSEvent): LCLObjCBoolean; override;
|
||
procedure mouseDown(event: NSEvent); override;
|
||
procedure mouseUp(event: NSEvent); override;
|
||
procedure rightMouseDown(event: NSEvent); override;
|
||
procedure rightMouseUp(event: NSEvent); override;
|
||
procedure rightMouseDragged(event: NSEvent); override;
|
||
procedure otherMouseDown(event: NSEvent); override;
|
||
procedure otherMouseUp(event: NSEvent); override;
|
||
procedure otherMouseDragged(event: NSEvent); override;
|
||
procedure mouseDragged(event: NSEvent); override;
|
||
procedure mouseMoved(event: NSEvent); override;
|
||
procedure scrollWheel(event: NSEvent); override;
|
||
end;
|
||
|
||
{ TManualTicks }
|
||
|
||
TManualTicks = class(TObject)
|
||
count : integer;
|
||
//todo: keep sorted and do binary search
|
||
ticks : array of Integer;
|
||
draw : Boolean;
|
||
function AddTick(atick: integer): Boolean;
|
||
end;
|
||
|
||
{ TCocoaSlider }
|
||
|
||
TCocoaSlider = objcclass(NSSlider)
|
||
callback : ICommonCallback;
|
||
intval : Integer;
|
||
man : TManualTicks;
|
||
|
||
procedure dealloc; override;
|
||
procedure drawRect(dirtyRect: NSRect); override;
|
||
|
||
function acceptsFirstResponder: LCLObjCBoolean; override;
|
||
function lclGetCallback: ICommonCallback; override;
|
||
procedure lclClearCallback; override;
|
||
//
|
||
procedure SnapToInteger(AExtraFactor: Integer = 0); message 'SnapToInteger:';
|
||
procedure sliderAction(sender: id); message 'sliderAction:';
|
||
// mouse
|
||
function acceptsFirstMouse(event: NSEvent): LCLObjCBoolean; override;
|
||
procedure mouseDown(event: NSEvent); override;
|
||
procedure mouseUp(event: NSEvent); override;
|
||
procedure rightMouseDown(event: NSEvent); override;
|
||
procedure rightMouseUp(event: NSEvent); override;
|
||
procedure rightMouseDragged(event: NSEvent); override;
|
||
procedure otherMouseDown(event: NSEvent); override;
|
||
procedure otherMouseUp(event: NSEvent); override;
|
||
procedure otherMouseDragged(event: NSEvent); override;
|
||
procedure mouseDragged(event: NSEvent); override;
|
||
procedure mouseMoved(event: NSEvent); override;
|
||
procedure scrollWheel(event: NSEvent); override;
|
||
|
||
procedure lclAddManTick(atick : integer); message 'lclAddManTick:';
|
||
procedure lclSetManTickDraw(adraw: Boolean); message 'lclSetManTickDraw:';
|
||
procedure lclExpectedKeys(var wantTabs, wantArrows, wantReturn, wantAll: Boolean); override;
|
||
end;
|
||
|
||
TCocoaSliderCell = objcclass(NSSliderCell)
|
||
end;
|
||
|
||
procedure SetViewDefaults(AView: NSView);
|
||
function CheckMainThread: Boolean;
|
||
function GetNSViewSuperViewHeight(view: NSView): CGFloat;
|
||
|
||
procedure SetNSControlSize(ctrl: NSView; newHeight, miniHeight, smallHeight: Integer; AutoChangeFont: Boolean);
|
||
|
||
// these constants are missing from CocoaAll for some reason
|
||
const
|
||
NSTextAlignmentLeft = 0;
|
||
NSTextAlignmentRight = 1; // it's 2 for iOS and family
|
||
NSTextAlignmentCenter = 2; // it's 1 for iOS and family
|
||
NSTextAlignmentJustified = 3;
|
||
NSTextAlignmentNatural = 4;
|
||
|
||
var
|
||
// todo: this should be a threadvar
|
||
TrackedControl : NSObject = nil;
|
||
|
||
function isCallbackForSameObject(cb1, cb2: ICommonCallback): Boolean;
|
||
|
||
function NSViewIsLCLEnabled(v: NSView): Boolean;
|
||
function NSObjectIsLCLEnabled(obj: NSObject): Boolean;
|
||
function NSViewCanFocus(v: NSView): Boolean;
|
||
|
||
implementation
|
||
|
||
function NSObjectIsLCLEnabled(obj: NSObject): Boolean;
|
||
begin
|
||
if obj.isKindOfClass(NSView) then
|
||
Result := NSViewIsLCLEnabled(NSView(obj))
|
||
else
|
||
Result := obj.lclIsEnabled;
|
||
end;
|
||
|
||
function NSViewIsLCLEnabled(v: NSView): Boolean;
|
||
begin
|
||
Result := true;
|
||
while Assigned(v) do
|
||
begin
|
||
if not v.lclIsEnabled then begin
|
||
Result := false;
|
||
break;
|
||
end;
|
||
v:=v.superview;
|
||
end;
|
||
end;
|
||
|
||
function NSViewCanFocus(v: NSView): Boolean;
|
||
var
|
||
cb: ICommonCallback;
|
||
begin
|
||
if Assigned(v) then
|
||
begin
|
||
cb := v.lclGetCallback;
|
||
if Assigned(cb) then
|
||
Result := cb.CanFocus
|
||
else
|
||
Result := true;
|
||
end
|
||
else
|
||
Result := false;
|
||
end;
|
||
|
||
function isCallbackForSameObject(cb1, cb2: ICommonCallback): Boolean;
|
||
begin
|
||
Result := Assigned(cb1) and Assigned(cb2);
|
||
if Result then
|
||
Result := (cb1 = cb2) or (cb1.GetTarget = cb2.GetTarget);
|
||
end;
|
||
|
||
procedure SetViewDefaults(AView: NSView);
|
||
begin
|
||
if not Assigned(AView) then Exit;
|
||
AView.setAutoresizingMask(NSViewMinYMargin or NSViewMaxXMargin);
|
||
end;
|
||
|
||
function CheckMainThread: Boolean;
|
||
begin
|
||
Result := NSThread.currentThread.isMainThread;
|
||
end;
|
||
|
||
function GetNSViewSuperViewHeight(view: NSView): CGFloat;
|
||
begin
|
||
Result := -1;
|
||
if not Assigned(view) then Exit;
|
||
if not Assigned(view.superview) then Exit;
|
||
//if view.superview.isKindOfClass_(TCocoaTabPageView) then
|
||
//Result := TCocoaTabPageView(view.superview).tabview.contentRect.size.height
|
||
//else
|
||
Result := view.superview.frame.size.height;
|
||
{$IFDEF COCOA_SUPERVIEW_HEIGHT}
|
||
WriteLn(Format('GetNSViewSuperViewHeight Result=%f', [Result]));
|
||
{$ENDIF}
|
||
end;
|
||
|
||
{ TManualTicks }
|
||
|
||
function TManualTicks.AddTick(atick: integer): Boolean;
|
||
var
|
||
i : integer;
|
||
begin
|
||
//todo: must be a binary search
|
||
for i:=0 to length(ticks)-1 do
|
||
if ticks[i]=atick then begin
|
||
Result:=false;
|
||
Exit;
|
||
end;
|
||
|
||
// adding new tick
|
||
if length(ticks)=count then begin
|
||
if count=0 then SetLength(ticks, 8)
|
||
else SetLength(ticks, count * 2);
|
||
end;
|
||
ticks[count]:=atick;
|
||
inc(count);
|
||
Result := true;
|
||
end;
|
||
|
||
{ TCocoaGroupBox }
|
||
|
||
function TCocoaGroupBox.lclClientFrame: TRect;
|
||
var
|
||
v : NSView;
|
||
begin
|
||
v:=contentView;
|
||
if not Assigned(v) then
|
||
Result := inherited lclClientFrame
|
||
else
|
||
if v.isFlipped then
|
||
Result := NSRectToRect( v.frame )
|
||
else
|
||
NSToLCLRect(v.frame, frame.size.height, Result);
|
||
end;
|
||
|
||
function TCocoaGroupBox.lclContentView: NSView;
|
||
begin
|
||
Result := NSView(contentView);
|
||
end;
|
||
|
||
function TCocoaGroupBox.lclGetFrameToLayoutDelta: TRect;
|
||
begin
|
||
Result.Left := 3;
|
||
Result.Right := -3;
|
||
Result.Top := 0;
|
||
Result.Bottom := -4;
|
||
end;
|
||
|
||
function TCocoaGroupBox.acceptsFirstResponder: LCLObjCBoolean;
|
||
begin
|
||
Result := True;
|
||
end;
|
||
|
||
function TCocoaGroupBox.lclGetCallback: ICommonCallback;
|
||
begin
|
||
Result := callback;
|
||
end;
|
||
|
||
procedure TCocoaGroupBox.lclClearCallback;
|
||
begin
|
||
callback := nil;
|
||
end;
|
||
|
||
{ TCocoaCustomControl }
|
||
|
||
function getNSStringObject( const aString: id ) : NSString;
|
||
begin
|
||
if aString.isKindOfClass( NSAttributedString ) then
|
||
Result:= NSAttributedString( aString ).string_
|
||
else
|
||
Result:= NSString( aString );
|
||
end;
|
||
|
||
function TCocoaCustomControl.getWindowEditor(): NSTextView;
|
||
begin
|
||
Result:= NSTextView( self.window.fieldEditor_forObject(true,nil) );
|
||
end;
|
||
|
||
procedure TCocoaCustomControl.setStringValue(avalue: NSString);
|
||
begin
|
||
if Assigned(fstr) then fstr.release;
|
||
if ASsigned(avalue) then
|
||
fstr:=avalue.copyWithZone(nil)
|
||
else
|
||
fstr:=nil;
|
||
inherited setStringValue(avalue);
|
||
end;
|
||
|
||
function TCocoaCustomControl.stringValue: NSString;
|
||
begin
|
||
Result:=fstr;
|
||
end;
|
||
|
||
procedure TCocoaCustomControl.addSubView(aview: NSView);
|
||
begin
|
||
inherited addSubView(aview);
|
||
|
||
if Assigned(aview) then
|
||
begin
|
||
// forcing LCL compatible "auto-move" mode. Sticking to left/top corner
|
||
if not autoresizesSubviews then
|
||
{$ifdef BOOLFIX}
|
||
setAutoresizesSubviews_(Ord(true));
|
||
{$else}
|
||
setAutoresizesSubviews(true);
|
||
{$endif}
|
||
aview.setAutoresizingMask(NSViewMaxXMargin or NSViewMinYMargin);
|
||
end;
|
||
end;
|
||
|
||
procedure TCocoaCustomControl.keyDown(theEvent: NSEvent);
|
||
var
|
||
textView: NSView;
|
||
isFirst: Boolean;
|
||
begin
|
||
if (not _inIME) and (theEvent.keyCode in [kVK_Return, kVK_ANSI_KeypadEnter, kVK_Escape]) then
|
||
begin
|
||
inherited;
|
||
exit;
|
||
end;
|
||
|
||
isFirst:= not _inIME;
|
||
inputContext.handleEvent(theEvent);
|
||
if _inIME and isFirst then
|
||
begin
|
||
textView:= getWindowEditor();
|
||
textView.setFrameSize( NSMakeSize(self.frame.size.width,16) );
|
||
self.addSubView( textView );
|
||
end
|
||
else if not _inIME then
|
||
inputContext.discardMarkedText;
|
||
end;
|
||
|
||
procedure TCocoaCustomControl.DoCallInputClientInsertText(nsText:NSString);
|
||
begin
|
||
if Assigned(callback) then
|
||
callback.InputClientInsertText(nsText.UTF8String);
|
||
nsText.release;
|
||
end;
|
||
|
||
// in TCocoaCustomControl, such as Form, Grid, ListView,
|
||
// after inputting text, another control may be focused.
|
||
// in insertText_replacementRange(), Cocoa/InputContext doesn't like it,
|
||
// so calling InputClientInsertText() asynchronously.
|
||
procedure TCocoaCustomControl.insertText_replacementRange(aString: id;
|
||
replacementRange: NSRange);
|
||
var
|
||
nsText: NSString;
|
||
begin
|
||
if not _inIME then exit;
|
||
|
||
unmarkText;
|
||
|
||
nsText:= getNSStringObject(aString).copy;
|
||
performSelector_withObject_afterDelay(ObjCSelector('DoCallInputClientInsertText:'), nsText, 0 );
|
||
end;
|
||
|
||
procedure TCocoaCustomControl.setMarkedText_selectedRange_replacementRange(
|
||
aString: id; selectedRange: NSRange; replacementRange: NSRange);
|
||
var
|
||
textView: NSTextView;
|
||
nsText: NSString;
|
||
begin
|
||
nsText:= getNSStringObject(aString);
|
||
if nsText.length > 0 then
|
||
begin
|
||
_inIME:= true;
|
||
textView:= getWindowEditor();
|
||
if Assigned(textView) then
|
||
textView.setMarkedText_selectedRange_replacementRange(aString,selectedRange,replacementRange);
|
||
end
|
||
else
|
||
unmarkText;
|
||
end;
|
||
|
||
function TCocoaCustomControl.hasMarkedText: LCLObjCBoolean;
|
||
begin
|
||
Result := _inIME;
|
||
end;
|
||
|
||
procedure TCocoaCustomControl.unmarkText;
|
||
var
|
||
textView: NSTextView;
|
||
begin
|
||
_inIME:= false;
|
||
textView:= getWindowEditor();
|
||
if Assigned(textView) then
|
||
textView.removeFromSuperview;
|
||
end;
|
||
|
||
function TCocoaCustomControl.firstRectForCharacterRange_actualRange(
|
||
aRange: NSRange; actualRange: NSRangePointer): NSRect;
|
||
var
|
||
point: NSPoint;
|
||
rect: NSRect;
|
||
begin
|
||
point:= self.convertPoint_toView(NSZeroPoint, nil);
|
||
rect:= NSMakeRect(point.x, point.y, 0, 16);
|
||
Result:= self.window.convertRectToScreen(rect);
|
||
end;
|
||
|
||
function TCocoaCustomControl.selectedRange: NSRange;
|
||
var
|
||
textView: NSText;
|
||
begin
|
||
textView:= getWindowEditor();
|
||
if not Assigned(textView) then
|
||
Result:= NSMakeRange( NSNotFound, 0 )
|
||
else
|
||
Result:= textView.selectedRange;
|
||
end;
|
||
|
||
function TCocoaCustomControl.markedRange: NSRange;
|
||
var
|
||
textView: NSTextView;
|
||
begin
|
||
textView:= getWindowEditor();
|
||
if not Assigned(textView) then
|
||
Result:= NSMakeRange( NSNotFound, 0 )
|
||
else
|
||
Result:= textView.markedRange;
|
||
end;
|
||
|
||
function TCocoaCustomControl.attributedSubstringForProposedRange_actualRange(
|
||
aRange: NSRange; actualRange: NSRangePointer): NSAttributedString;
|
||
begin
|
||
Result := nil;
|
||
end;
|
||
|
||
function TCocoaCustomControl.validAttributesForMarkedText: NSArray;
|
||
begin
|
||
Result := nil;
|
||
end;
|
||
|
||
function TCocoaCustomControl.characterIndexForPoint(aPoint: NSPoint
|
||
): NSUInteger;
|
||
begin
|
||
Result := 0;
|
||
end;
|
||
|
||
procedure TCocoaCustomControl.doCommandBySelector(aSelector: SEL);
|
||
begin
|
||
inherited doCommandBySelector(ASelector);
|
||
end;
|
||
|
||
procedure TCocoaCustomControl.dealloc;
|
||
begin
|
||
if Assigned(fstr) then fstr.release;
|
||
inherited dealloc;
|
||
end;
|
||
|
||
function TCocoaCustomControl.acceptsFirstResponder: LCLObjCBoolean;
|
||
begin
|
||
Result := True;
|
||
end;
|
||
|
||
function TCocoaCustomControl.acceptsFirstMouse(event: NSEvent): LCLObjCBoolean;
|
||
begin
|
||
// By default, a mouse-down event in a window that isn’t the key window
|
||
// simply brings the window forward and makes it key; the event isn’t sent
|
||
// to the NSView object over which the mouse click occurs. The NSView can
|
||
// claim an initial mouse-down event, however, by overriding acceptsFirstMouse: to return YES.
|
||
// see bug #33034
|
||
Result:=true;
|
||
end;
|
||
|
||
procedure TCocoaCustomControl.drawRect(dirtyRect: NSRect);
|
||
begin
|
||
if isdrawing=0 then faileddraw:=false;
|
||
inc(isdrawing);
|
||
inherited drawRect(dirtyRect);
|
||
|
||
// Implement Color property
|
||
if Assigned(callback) then
|
||
callback.DrawBackground(NSGraphicsContext.currentContext, bounds, dirtyRect);
|
||
|
||
if CheckMainThread and Assigned(callback) then
|
||
callback.Draw(NSGraphicsContext.currentContext, bounds, dirtyRect);
|
||
dec(isdrawing);
|
||
|
||
if (isdrawing=0) and (faileddraw) then
|
||
begin
|
||
// Similar to Carbon. Cocoa doesn't welcome changing a framerects during paint event
|
||
// If such thing happens, the results are pretty much inpredicatable. #32970
|
||
// TreeView tries to updatedScrollBars during paint event. That sometimes is causing
|
||
// the frame to be changed (i.e. scroll bar showed or hidden, resized the client rect)
|
||
// as a result, the final image is shown up-side-down.
|
||
//
|
||
// Below is an attempt to prevent graphical artifacts and to redraw
|
||
// the control again.
|
||
inherited drawRect(dirtyRect);
|
||
|
||
if Assigned(callback) then
|
||
callback.DrawBackground(NSGraphicsContext.currentContext, bounds, dirtyRect);
|
||
|
||
if CheckMainThread and Assigned(callback) then
|
||
callback.Draw(NSGraphicsContext.currentContext, bounds, dirtyRect);
|
||
end;
|
||
end;
|
||
|
||
function TCocoaCustomControl.lclGetCallback: ICommonCallback;
|
||
begin
|
||
Result := callback;
|
||
end;
|
||
|
||
procedure TCocoaCustomControl.lclClearCallback;
|
||
begin
|
||
callback := nil;
|
||
end;
|
||
|
||
function TCocoaCustomControl.lclIsMouseInAuxArea(Event: NSevent): Boolean;
|
||
begin
|
||
if auxMouseByParent and Assigned(superview) then
|
||
Result := superview.lclIsMouseInAuxArea(Event)
|
||
else
|
||
Result := false;
|
||
end;
|
||
|
||
procedure TCocoaCustomControl.mouseDown(event: NSEvent);
|
||
begin
|
||
if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
|
||
inherited mouseDown(event);
|
||
end;
|
||
|
||
procedure TCocoaCustomControl.mouseDragged(event: NSEvent);
|
||
begin
|
||
window.disableCursorRects;
|
||
|
||
if not Assigned(callback) or not callback.MouseMove(event) then
|
||
// calling inherited causes the drag event to be passed to the
|
||
// parent controls
|
||
|
||
//inherited mouseDragged(event);
|
||
;
|
||
end;
|
||
|
||
procedure TCocoaCustomControl.mouseEntered(event: NSEvent);
|
||
begin
|
||
inherited mouseEntered(event);
|
||
end;
|
||
|
||
procedure TCocoaCustomControl.mouseExited(event: NSEvent);
|
||
begin
|
||
inherited mouseExited(event);
|
||
end;
|
||
|
||
procedure TCocoaCustomControl.mouseMoved(event: NSEvent);
|
||
begin
|
||
if not Assigned(callback) or not callback.MouseMove(event) then
|
||
inherited mouseMoved(event);
|
||
end;
|
||
|
||
procedure TCocoaCustomControl.scrollWheel(event: NSEvent);
|
||
begin
|
||
if not Assigned(callback) or not callback.scrollWheel(event) then
|
||
inherited scrollWheel(event);
|
||
end;
|
||
|
||
procedure TCocoaCustomControl.setFrame(aframe: NSRect);
|
||
begin
|
||
if NSEqualRects(aframe, frame) then Exit;
|
||
if isdrawing>0 then
|
||
faileddraw := true;
|
||
|
||
inherited setFrame(aframe);
|
||
// it actually should come from a notifcation
|
||
if Assigned(callback) then callback.frameDidChange(self);
|
||
end;
|
||
|
||
procedure TCocoaCustomControl.mouseUp(event: NSEvent);
|
||
begin
|
||
if not window.areCursorRectsEnabled then
|
||
begin
|
||
window.enableCursorRects;
|
||
window.resetCursorRects;
|
||
CursorHelper.SetCursorAtMousePos;
|
||
end;
|
||
|
||
if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
|
||
inherited mouseUp(event);
|
||
end;
|
||
|
||
procedure TCocoaCustomControl.rightMouseDown(event: NSEvent);
|
||
begin
|
||
if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
|
||
inherited rightMouseDown(event);
|
||
end;
|
||
|
||
procedure TCocoaCustomControl.rightMouseUp(event: NSEvent);
|
||
begin
|
||
if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
|
||
inherited rightMouseUp(event);
|
||
end;
|
||
|
||
procedure TCocoaCustomControl.rightMouseDragged(event: NSEvent);
|
||
begin
|
||
if not Assigned(callback) or not callback.MouseMove(event) then
|
||
inherited rightMouseDragged(event);
|
||
end;
|
||
|
||
procedure TCocoaCustomControl.otherMouseDown(event: NSEvent);
|
||
begin
|
||
if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
|
||
inherited otherMouseDown(event);
|
||
end;
|
||
|
||
procedure TCocoaCustomControl.otherMouseUp(event: NSEvent);
|
||
begin
|
||
if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
|
||
inherited otherMouseUp(event);
|
||
end;
|
||
|
||
procedure TCocoaCustomControl.otherMouseDragged(event: NSEvent);
|
||
begin
|
||
if not Assigned(callback) or not callback.MouseMove(event) then
|
||
inherited otherMouseDragged(event);
|
||
end;
|
||
|
||
|
||
{ TCocoaIMEParameters }
|
||
|
||
// set text and length in params
|
||
procedure setIMEParamsText( var params: TCocoaIMEParameters; const nsText: NSString );
|
||
begin
|
||
params.text := NSStringToString( nsText );
|
||
params.textCharLength := UTF8Length( params.text );
|
||
params.textByteLength := Length( params.text );
|
||
params.textNSLength := nsText.length;
|
||
end;
|
||
|
||
// set selected range in code point
|
||
procedure setIMESelectedRange( var params: TCocoaIMEParameters; const nsText: NSString; range: NSRange );
|
||
begin
|
||
if range.location<>NSNotFound then
|
||
begin
|
||
if range.location>nsText.length then
|
||
range.location:= 0;
|
||
if range.location+range.length>nsText.length then
|
||
range.length:= nsText.length-range.location;
|
||
end;
|
||
|
||
if range.location=NSNotFound then
|
||
params.selectedStart:= 0
|
||
else
|
||
params.selectedStart:= UTF8Length( nsText.substringToIndex(range.location).UTF8String );
|
||
|
||
if range.length=0 then
|
||
params.selectedLength:= 0
|
||
else
|
||
params.selectedLength:= UTF8Length( nsText.substringWithRange(range).UTF8String );
|
||
end;
|
||
|
||
{ TCocoaFullControlEdit }
|
||
|
||
{
|
||
for IME Key Down:
|
||
Key step for IME (such as Chinese/Japanese/Korean and DeadKeys)
|
||
1. forward key event to NSInputContext
|
||
2. NSInputContext will call TCocoaFullControlEdit(NSTextControlClient)
|
||
and then call LCL via imeHandler
|
||
}
|
||
procedure TCocoaFullControlEdit.keyDown(theEvent: NSEvent);
|
||
begin
|
||
inputContext.handleEvent(theEvent);
|
||
end;
|
||
|
||
{
|
||
for IME Close:
|
||
1. mouseDown() will not be called when click in the IME Popup Window,
|
||
so it must be clicking outside the IME Popup Windows,
|
||
which should end the IME input
|
||
2. Cocoa had called setMarkedText_selectedRange_replacementRange()
|
||
or insertText_replacementRange() first, then mouseDown() here
|
||
3. NSInputContext.handleEvent() just close IME window here
|
||
4. LCL actually handle mouse event
|
||
}
|
||
procedure TCocoaFullControlEdit.mouseDown(event: NSEvent);
|
||
begin
|
||
inputContext.handleEvent(event);
|
||
Inherited;
|
||
end;
|
||
|
||
procedure TCocoaFullControlEdit.mouseUp(event: NSEvent);
|
||
begin
|
||
inputContext.handleEvent(event);
|
||
Inherited;
|
||
end;
|
||
|
||
// prevent switch to other control when in IME input state
|
||
function TCocoaFullControlEdit.resignFirstResponder: ObjCBOOL;
|
||
begin
|
||
Result := not hasMarkedText();
|
||
end;
|
||
|
||
function isIMEDuplicateCall( const newParams, currentParams: TCocoaIMEParameters ) : Boolean;
|
||
begin
|
||
Result:= false;
|
||
if newParams.isFirstCall then exit;
|
||
if newParams.text <> currentParams.text then exit;
|
||
if newParams.selectedStart<>currentParams.selectedStart then exit;
|
||
if newParams.selectedLength<>currentParams.selectedLength then exit;
|
||
Result:= true;
|
||
end;
|
||
|
||
// send Marked/Intermediate Text to LCL Edit Control which has IME Handler
|
||
// Key step for IME (such as Chinese/Japanese/Korean and DeadKeys)
|
||
procedure TCocoaFullControlEdit.setMarkedText_selectedRange_replacementRange(
|
||
aString: id; newRange: NSRange; replacementRange: NSRange);
|
||
var
|
||
params : TCocoaIMEParameters;
|
||
nsText : NSString;
|
||
begin
|
||
params.isFirstCall:= not hasMarkedText();
|
||
|
||
// no markedText before, the first call
|
||
if params.isFirstCall then imeHandler.IMESessionBegin;
|
||
|
||
// get IME Intermediate Text
|
||
nsText:= getNSStringObject( aString );
|
||
setIMEParamsText( params, nsText );
|
||
|
||
// some IME want to select subRange of Intermediate Text
|
||
// such as Japanese
|
||
setIMESelectedRange( params, nsText, newRange );
|
||
|
||
// some IME incorrectly call setMarkedText() twice with the same parameters
|
||
if isIMEDuplicateCall( params, _currentParams ) then
|
||
exit;
|
||
|
||
// some IME want to eat some chars
|
||
// such as inputting DeadKeys
|
||
if replacementRange.location<>NSNotFound then
|
||
params.eatAmount:= 1 - replacementRange.location
|
||
else
|
||
params.eatAmount:= 0;
|
||
|
||
// Key Step to update(display) Marked/Intermediate Text
|
||
imeHandler.IMEUpdateIntermediateText( params );
|
||
|
||
if params.textNSLength=0 then
|
||
begin
|
||
// cancel Marked/Intermediate Text
|
||
imeHandler.IMESessionEnd;
|
||
unmarkText;
|
||
end
|
||
else
|
||
begin
|
||
// update Marked/Intermediate Text internal status
|
||
_currentParams:= params;
|
||
_currentMarkedText.release;
|
||
_currentMarkedText:= nsText;
|
||
_currentMarkedText.retain;
|
||
end;
|
||
end;
|
||
|
||
{
|
||
send final Text to LCL Edit Control which has IME Handler
|
||
Key step for IME (such as Chinese/Japanese/Korean and DeadKeys)
|
||
1. if in IME input state, handle text via imeHandler.IMEInsertFinalText()
|
||
2. otherwise via lclGetCallback.InputClientInsertText,
|
||
mainly for maximum forward compatibility with TCocoaCustomControl
|
||
}
|
||
procedure TCocoaFullControlEdit.insertText_replacementRange(aString: id;
|
||
replacementRange: NSRange);
|
||
var
|
||
params: TCocoaIMEParameters;
|
||
nsText : NSString;
|
||
begin
|
||
params.isFirstCall:= not hasMarkedText();
|
||
|
||
// IME final text
|
||
nsText:= getNSStringObject( aString );
|
||
setIMEParamsText( params, nsText );
|
||
|
||
// some IME want to eat some chars, such as inputting DeadKeys
|
||
if replacementRange.location<>NSNotFound then
|
||
params.eatAmount:= 1 - replacementRange.location
|
||
else
|
||
params.eatAmount:= 0;
|
||
|
||
if (not params.isFirstCall) or (params.eatAmount<>0) then
|
||
// insert IME final text
|
||
imeHandler.IMEInsertFinalText( params )
|
||
else
|
||
// insert normal text (without IME) by LCLControl.IntfUTF8KeyPress()
|
||
lclGetCallback.InputClientInsertText( params.text );
|
||
|
||
if not params.isFirstCall then
|
||
begin
|
||
imeHandler.IMESessionEnd;
|
||
unmarkText;
|
||
end;
|
||
end;
|
||
|
||
// cursor tracking
|
||
function TCocoaFullControlEdit.firstRectForCharacterRange_actualRange(
|
||
aRange: NSRange; actualRange: NSRangePointer): NSRect;
|
||
var
|
||
params: TCocoaIMEParameters;
|
||
rect : TRect;
|
||
begin
|
||
params:= _currentParams;
|
||
setIMESelectedRange( params, _currentMarkedText, aRange );
|
||
params.isFirstCall:= not hasMarkedText();
|
||
|
||
rect:= imeHandler.IMEGetTextBound( params );
|
||
LCLToNSRect( rect, NSGlobalScreenBottom, Result );
|
||
end;
|
||
|
||
procedure TCocoaFullControlEdit.unmarkText;
|
||
begin
|
||
setIMEParamsText( _currentParams, nil );
|
||
_currentParams.selectedStart:= 0;
|
||
_currentParams.selectedLength:= 0;
|
||
_currentParams.eatAmount:= 0;
|
||
_currentParams.isFirstCall:= true;
|
||
_currentMarkedText.release;
|
||
_currentMarkedText:= nil;
|
||
end;
|
||
|
||
function TCocoaFullControlEdit.markedRange: NSRange;
|
||
begin
|
||
if _currentParams.textNSLength=0 then
|
||
Result:= NSMakeRange( NSNotFound, 0 )
|
||
else
|
||
Result:= NSMakeRange( 0, _currentParams.textNSLength );
|
||
end;
|
||
|
||
function TCocoaFullControlEdit.selectedRange: NSRange;
|
||
begin
|
||
if _currentParams.textNSLength=0 then
|
||
Result:= NSMakeRange( 0, 0 )
|
||
else
|
||
Result:= NSMakeRange( _currentParams.selectedStart, _currentParams.selectedLength );
|
||
end;
|
||
|
||
function TCocoaFullControlEdit.hasMarkedText: LCLObjCBoolean;
|
||
begin
|
||
Result:= ( _currentParams.textNSLength > 0 );
|
||
end;
|
||
|
||
{ LCLObjectExtension }
|
||
|
||
function LCLObjectExtension.lclIsEnabled: Boolean;
|
||
begin
|
||
Result := False;
|
||
end;
|
||
|
||
procedure LCLObjectExtension.lclSetEnabled(AEnabled: Boolean);
|
||
begin
|
||
end;
|
||
|
||
function LCLObjectExtension.lclIsVisible: Boolean;
|
||
begin
|
||
Result := False;
|
||
end;
|
||
|
||
procedure LCLObjectExtension.lclSetVisible(AVisible: Boolean);
|
||
begin
|
||
end;
|
||
|
||
function LCLObjectExtension.lclWindowState: Integer;
|
||
begin
|
||
Result := SIZE_RESTORED;
|
||
end;
|
||
|
||
procedure LCLObjectExtension.lclInvalidateRect(const r: TRect);
|
||
begin
|
||
end;
|
||
|
||
procedure LCLObjectExtension.lclInvalidate;
|
||
begin
|
||
end;
|
||
|
||
procedure LCLObjectExtension.lclUpdate;
|
||
begin
|
||
end;
|
||
|
||
procedure LCLObjectExtension.lclLocalToScreen(var X,Y: Integer);
|
||
begin
|
||
end;
|
||
|
||
procedure LCLObjectExtension.lclScreenToLocal(var X, Y: Integer);
|
||
begin
|
||
end;
|
||
|
||
function LCLObjectExtension.lclParent:id;
|
||
begin
|
||
Result:=nil;
|
||
end;
|
||
|
||
function LCLObjectExtension.lclFrame:TRect;
|
||
begin
|
||
FillChar(Result, sizeof(Result), 0);
|
||
end;
|
||
|
||
procedure LCLObjectExtension.lclSetFrame(const r:TRect);
|
||
begin
|
||
|
||
end;
|
||
|
||
function LCLObjectExtension.lclGetFrameToLayoutDelta: TRect;
|
||
begin
|
||
Result.Top := 0;
|
||
Result.Left := 0;
|
||
Result.Right := 0;
|
||
Result.Bottom := 0;
|
||
end;
|
||
|
||
function LCLObjectExtension.lclClientFrame:TRect;
|
||
begin
|
||
FillChar(Result, sizeof(Result), 0);
|
||
end;
|
||
|
||
function LCLObjectExtension.lclGetCallback: ICommonCallback;
|
||
begin
|
||
Result := nil;
|
||
end;
|
||
|
||
procedure LCLObjectExtension.lclClearCallback;
|
||
begin
|
||
end;
|
||
|
||
function LCLObjectExtension.lclGetPropStorage: TStringList;
|
||
var
|
||
Callback: ICommonCallback;
|
||
begin
|
||
Callback := lclGetCallback;
|
||
if Assigned(Callback) then
|
||
Result := Callback.GetPropStorage
|
||
else
|
||
Result := nil;
|
||
end;
|
||
|
||
function LCLObjectExtension.lclGetTarget: TObject;
|
||
var
|
||
Callback: ICommonCallback;
|
||
begin
|
||
Callback := lclGetCallback;
|
||
if Assigned(Callback) then
|
||
Result := Callback.GetTarget
|
||
else
|
||
Result := nil;
|
||
end;
|
||
|
||
function LCLObjectExtension.lclDeliverMessage(Msg: Cardinal; WParam: WParam; LParam: LParam): LResult;
|
||
var
|
||
Callback: ICommonCallback;
|
||
begin
|
||
Callback := lclGetCallback;
|
||
if Assigned(Callback) then
|
||
Result := Callback.DeliverMessage(Msg, WParam, LParam)
|
||
else
|
||
Result := 0;
|
||
end;
|
||
|
||
function LCLObjectExtension.lclContentView: NSView;
|
||
begin
|
||
Result := nil;
|
||
end;
|
||
|
||
procedure LCLObjectExtension.lclOffsetMousePos(var Point: NSPoint);
|
||
begin
|
||
|
||
end;
|
||
|
||
procedure LCLObjectExtension.lclExpectedKeys(var wantTabs, wantArrows,
|
||
wantReturn, wantAll: Boolean);
|
||
begin
|
||
wantTabs := false;
|
||
wantArrows := false;
|
||
wantReturn := false;
|
||
wantAll := false;
|
||
end;
|
||
|
||
{ The method should return TRUE, if mouse is located above an auxilary area
|
||
of a (composited) control, and thus MOUSE MOVE event should not be propagated
|
||
to LCL. For example, controls with Scrollbars should not report mouse events
|
||
if mouse cursor is above ScrollBar and scroll bar is visible. (ScrollBar = Auxillary area)
|
||
|
||
By default, the whole area is considered to be non-auxillary and must be
|
||
reported to LCL.
|
||
}
|
||
function LCLObjectExtension.lclIsMouseInAuxArea(Event: NSEvent): Boolean;
|
||
begin
|
||
Result := false;
|
||
end;
|
||
|
||
{ LCLControlExtension }
|
||
|
||
function RectToViewCoord(view: NSView; const r: TRect): NSRect;
|
||
var
|
||
b: NSRect;
|
||
begin
|
||
b := view.bounds;
|
||
Result.origin.x := r.Left;
|
||
Result.size.width := r.Right - r.Left;
|
||
Result.size.height := r.Bottom - r.Top;
|
||
if Assigned(view) and (view.isFlipped) then
|
||
Result.origin.y := r.Top
|
||
else
|
||
Result.origin.y := b.size.height - r.Bottom;
|
||
end;
|
||
|
||
function LCLControlExtension.lclIsEnabled:Boolean;
|
||
begin
|
||
Result := IsEnabled;
|
||
end;
|
||
|
||
procedure LCLControlExtension.lclSetEnabled(AEnabled:Boolean);
|
||
begin
|
||
{$ifdef BOOLFIX}
|
||
SetEnabled_( Ord(AEnabled and NSViewIsLCLEnabled(self.superview) ));
|
||
{$else}
|
||
SetEnabled( AEnabled and NSViewIsLCLEnabled(self.superview) );
|
||
{$endif}
|
||
inherited lclSetEnabled(AEnabled);
|
||
end;
|
||
|
||
function LCLViewExtension.lclInitWithCreateParams(const AParams: TCreateParams): id;
|
||
var
|
||
p: NSView;
|
||
ns: NSRect;
|
||
{$IFDEF COCOA_DEBUG_SETBOUNDS}
|
||
pstr: string;
|
||
{$ENDIF}
|
||
begin
|
||
p := nil;
|
||
if (AParams.WndParent <> 0) then
|
||
p := NSView(AParams.WndParent).lclContentView;
|
||
|
||
if Assigned(p) then
|
||
LCLToNSRect(Types.Bounds(AParams.X, AParams.Y, AParams.Width, AParams.Height),
|
||
p.frame.size.height, ns)
|
||
else
|
||
ns := GetNSRect(AParams.X, AParams.Y, AParams.Width, AParams.Height);
|
||
|
||
{$IFDEF COCOA_DEBUG_SETBOUNDS}
|
||
if Assigned(p) then
|
||
begin
|
||
pstr := NSStringToString(p.className);
|
||
if NSStringToString(NSObject(AParams.WndParent).className) = 'TCocoaTabPage' then
|
||
pstr := pstr + ' ' + NSStringToString(TCocoaTabPage(AParams.WndParent).label_);
|
||
end
|
||
else
|
||
pstr := '';
|
||
WriteLn(Format('[LCLViewExtension.lclInitWithCreateParams] Class=%s Caption=%s ParentClass=%s ParentClassView=%s rect=%d %d %d %d Visible=%d',
|
||
[NSStringToString(Self.className), AParams.Caption,
|
||
NSStringToString(NSObject(AParams.WndParent).className), pstr,
|
||
Round(ns.Origin.x), Round(ns.Origin.y), Round(ns.size.width), Round(ns.size.height),
|
||
AParams.Style and WS_VISIBLE]));
|
||
{$ENDIF}
|
||
|
||
Result := initWithFrame(ns);
|
||
if not Assigned(Result) then
|
||
Exit;
|
||
|
||
{$ifdef BOOLFIX}
|
||
setHidden_(Ord(AParams.Style and WS_VISIBLE = 0));
|
||
{$else}
|
||
setHidden(AParams.Style and WS_VISIBLE = 0);
|
||
{$endif}
|
||
|
||
if Assigned(p) then
|
||
p.lclContentView.addSubview(Result);
|
||
SetViewDefaults(Result);
|
||
end;
|
||
|
||
function LCLViewExtension.lclIsEnabled: Boolean;
|
||
begin
|
||
Result := true;
|
||
end;
|
||
|
||
procedure LCLViewExtension.lclSetEnabled(AEnabled: Boolean);
|
||
var
|
||
cb : ICommonCallback;
|
||
obj : NSObject;
|
||
begin
|
||
for obj in subviews do begin
|
||
cb := obj.lclGetCallback;
|
||
obj.lclSetEnabled(AEnabled and ((not Assigned(cb)) or cb.GetShouldBeEnabled) );
|
||
end;
|
||
end;
|
||
|
||
function LCLViewExtension.lclIsVisible: Boolean;
|
||
begin
|
||
Result := not isHidden;
|
||
end;
|
||
|
||
procedure LCLViewExtension.lclSetVisible(AVisible: Boolean);
|
||
begin
|
||
{$ifdef BOOLFIX}
|
||
setHidden_(Ord(not AVisible));
|
||
{$else}
|
||
setHidden(not AVisible);
|
||
{$endif}
|
||
{$IFDEF COCOA_DEBUG_SETBOUNDS}
|
||
WriteLn(Format('LCLViewExtension.lclSetVisible: %s AVisible=%d',
|
||
[NSStringToString(Self.ClassName), Integer(AVisible)]));
|
||
{$ENDIF}
|
||
end;
|
||
|
||
function LCLViewExtension.lclIsPainting: Boolean;
|
||
begin
|
||
Result := Assigned(lclGetCallback) and Assigned(lclGetCallback.GetContext);
|
||
end;
|
||
|
||
procedure LCLViewExtension.lclInvalidateRect(const r:TRect);
|
||
var
|
||
view : NSView;
|
||
begin
|
||
view:=lclContentView;
|
||
if Assigned(view) then
|
||
view.setNeedsDisplayInRect(RectToViewCoord(view, r))
|
||
else
|
||
self.setNeedsDisplayInRect(RectToViewCoord(Self, r));
|
||
//todo: it might be necessary to always invalidate self
|
||
// just need to get offset of the contentView relative for self
|
||
end;
|
||
|
||
procedure LCLViewExtension.lclInvalidate;
|
||
begin
|
||
{$ifdef BOOLFIX}
|
||
setNeedsDisplay__(Ord(True));
|
||
{$else}
|
||
setNeedsDisplay_(True);
|
||
{$endif}
|
||
end;
|
||
|
||
procedure LCLViewExtension.lclUpdate;
|
||
begin
|
||
{$ifdef BOOLFIX}
|
||
setNeedsDisplay__(Ord(True));
|
||
{$else}
|
||
setNeedsDisplay_(True);
|
||
{$endif}
|
||
//display;
|
||
end;
|
||
|
||
procedure LCLViewExtension.lclLocalToScreen(var X, Y:Integer);
|
||
var
|
||
P: NSPoint;
|
||
|
||
begin
|
||
// 1. convert to window base
|
||
// Convert from View-lcl to View-cocoa
|
||
P.x := X;
|
||
if isFlipped then
|
||
p.y := Y
|
||
else
|
||
P.y := frame.size.height-y; // convert to Cocoa system
|
||
|
||
// Convert from View-cocoa to Window-cocoa
|
||
P := convertPoint_ToView(P, nil);
|
||
|
||
// Convert from Window-cocoa to Window-lcl
|
||
X := Round(P.X);
|
||
Y := Round(window.frame.size.height-P.Y); // convert to LCL system
|
||
|
||
// 2. convert window to screen
|
||
// Use window function to convert fomr Window-lcl to Screen-lcl
|
||
window.lclLocalToScreen(X, Y);
|
||
end;
|
||
|
||
procedure LCLViewExtension.lclScreenToLocal(var X, Y: Integer);
|
||
var
|
||
P: NSPoint;
|
||
begin
|
||
// 1. convert from screen to window
|
||
// use window function to onvert from Screen-lcl to Window-lcl
|
||
window.lclScreenToLocal(X, Y);
|
||
// Convert from Window-lcl to Window-cocoa
|
||
P.x := X;
|
||
P.y := Round(window.frame.size.height-Y); // convert to Cocoa system
|
||
|
||
// 2. convert from window to local
|
||
// Convert from Window-cocoa to View-cocoa
|
||
P := convertPoint_FromView(P, nil);
|
||
|
||
// Convert from View-cocoa to View-lcl
|
||
X := Round(P.x);
|
||
if isFlipped then
|
||
Y := Round(p.y)
|
||
else
|
||
Y := Round(frame.size.height-P.y); // convert to Cocoa system
|
||
end;
|
||
|
||
function LCLViewExtension.lclParent:id;
|
||
begin
|
||
Result := superView;
|
||
end;
|
||
|
||
function LCLViewExtension.lclFrame: TRect;
|
||
var
|
||
v: NSView;
|
||
begin
|
||
v := superview;
|
||
if Assigned(v) and not v.isFlipped then
|
||
NSToLCLRect(frame, v.frame.size.height, Result)
|
||
else
|
||
Result := NSRectToRect(frame);
|
||
AddLayoutToFrame( lclGetFrameToLayoutDelta, Result);
|
||
end;
|
||
|
||
procedure LCLViewExtension.lclSetFrame(const r: TRect);
|
||
var
|
||
ns: NSRect;
|
||
svHeight: CGFloat;
|
||
rr : TRect;
|
||
begin
|
||
rr := r;
|
||
SubLayoutFromFrame( lclGetFrameToLayoutDelta, rr);
|
||
|
||
svHeight := GetNSViewSuperViewHeight(Self);
|
||
if Assigned(superview) and not superview.isFlipped then
|
||
begin
|
||
LCLToNSRect(rr, svHeight, ns)
|
||
end
|
||
else
|
||
ns := RectToNSRect(rr);
|
||
|
||
if ns.size.width<1 then ns.size.width:=1;
|
||
if ns.size.height<1 then ns.size.height:=1;
|
||
|
||
{$IFDEF COCOA_DEBUG_SETBOUNDS}
|
||
WriteLn(Format('LCLViewExtension.lclSetFrame: %s Bounds=%s height=%d ns_pos=%d %d ns_size=%d %d',
|
||
[NSStringToString(Self.ClassName), dbgs(r), Round(svHeight),
|
||
Round(ns.origin.x), Round(ns.origin.y), Round(ns.size.width), Round(ns.size.height)]));
|
||
{$ENDIF}
|
||
setFrame(ns);
|
||
end;
|
||
|
||
function LCLViewExtension.lclClientFrame: TRect;
|
||
begin
|
||
Result := lclFrame;
|
||
Types.OffsetRect(Result, -Result.Left, -Result.Top);
|
||
end;
|
||
|
||
function LCLViewExtension.lclContentView: NSView;
|
||
begin
|
||
Result := self;
|
||
end;
|
||
|
||
procedure LCLViewExtension.lclOffsetMousePos(var Point: NSPoint);
|
||
var
|
||
es : NSScrollView;
|
||
r : NSRect;
|
||
dlt : TRect;
|
||
begin
|
||
Point := convertPoint_fromView(Point, nil);
|
||
|
||
es := enclosingScrollView;
|
||
if es.documentView <> self then es := nil;
|
||
if not isFlipped then
|
||
Point.y := bounds.size.height - Point.y;
|
||
|
||
if Assigned(es) then
|
||
begin
|
||
r := es.documentVisibleRect;
|
||
if isFlipped then
|
||
Point.y := Point.y - r.origin.y
|
||
else
|
||
Point.y := Point.y - (es.documentView.frame.size.height - r.size.height - r.origin.y);
|
||
Point.X := Point.X - r.origin.x;
|
||
end;
|
||
|
||
dlt := lclGetFrameToLayoutDelta;
|
||
Point.X := Point.X - dlt.Left;
|
||
Point.Y := Point.Y - dlt.Top;
|
||
end;
|
||
|
||
{ TCocoaStatusBar }
|
||
|
||
procedure TCocoaStatusBar.drawRect(dirtyRect: NSRect);
|
||
var
|
||
R : TRect;
|
||
i : Integer;
|
||
cs : NSString;
|
||
nr : NSRect;
|
||
dr : NSRect;
|
||
al : TAlignment;
|
||
x : Integer;
|
||
txt : string;
|
||
cnt : Integer;
|
||
w : Integer;
|
||
const
|
||
CocoaAlign: array [TAlignment] of Integer = (NSNaturalTextAlignment, NSRightTextAlignment, NSCenterTextAlignment);
|
||
begin
|
||
if not Assigned(barcallback) then Exit;
|
||
|
||
if not Assigned(panelCell) then Exit;
|
||
|
||
panelCell.setControlView(Self);
|
||
|
||
r := lclClientFrame();
|
||
nr.origin.y := 0;
|
||
nr.size.height := self.lclFrame.Height;
|
||
|
||
x:=0;
|
||
cnt := barcallback.GetBarsCount;
|
||
for i:=0 to cnt - 1 do begin
|
||
|
||
txt := '';
|
||
w := 0;
|
||
al := taLeftJustify;
|
||
|
||
if not barcallback.GetBarItem(i, txt, w, al) then Continue;
|
||
|
||
if i = cnt - 1 then w := r.Right - x;
|
||
nr.size.width := w;
|
||
nr.origin.x := x;
|
||
|
||
// dr - draw rect. should be 1 pixel wider
|
||
// and 1 pixel taller, than the actual rect.
|
||
// to produce a better visual effect
|
||
dr := nr;
|
||
dr.size.width := dr.size.width + 1;
|
||
dr.size.height := dr.size.height + 1;
|
||
dr.origin.y := dr.origin.y-1;
|
||
|
||
cs := NSStringUtf8(txt);
|
||
panelCell.setTitle(cs);
|
||
panelCell.setAlignment(CocoaAlign[al]);
|
||
panelCell.drawWithFrame_inView(dr, Self);
|
||
cs.release;
|
||
barcallback.DrawPanel(i, NSRectToRect(nr));
|
||
inc(x, w);
|
||
if x > r.Right then break; // no place left
|
||
end;
|
||
end;
|
||
|
||
procedure TCocoaStatusBar.dealloc;
|
||
begin
|
||
if Assigned(panelCell) then panelCell.release;
|
||
inherited;
|
||
end;
|
||
|
||
{ TCocoaProgressIndicator }
|
||
|
||
function TCocoaProgressIndicator.acceptsFirstResponder: LCLObjCBoolean;
|
||
begin
|
||
Result:=True;
|
||
end;
|
||
|
||
function TCocoaProgressIndicator.lclGetCallback: ICommonCallback;
|
||
begin
|
||
Result:=callback;
|
||
end;
|
||
|
||
procedure TCocoaProgressIndicator.lclClearCallback;
|
||
begin
|
||
callback:=nil;
|
||
end;
|
||
|
||
function TCocoaProgressIndicator.lclGetFrameToLayoutDelta: TRect;
|
||
begin
|
||
case controlSize of
|
||
NSSmallControlSize, NSMiniControlSize:
|
||
begin
|
||
Result.Left := 1;
|
||
Result.Right := -1;
|
||
Result.Top := 0;
|
||
Result.Bottom := -2;
|
||
end;
|
||
else
|
||
Result.Left := 2;
|
||
Result.Right := -2;
|
||
Result.Top := 0;
|
||
Result.Bottom := -4;
|
||
end;
|
||
end;
|
||
|
||
procedure TCocoaProgressIndicator.lclSetFrame(const r: TRect);
|
||
begin
|
||
SetNSControlSize(self, r.Bottom - r.Top, 0, PROGRESS_SMALL_HEIGHT, true);
|
||
inherited lclSetFrame(r);
|
||
end;
|
||
|
||
function TCocoaProgressIndicator.acceptsFirstMouse(event: NSEvent): LCLObjCBoolean;
|
||
begin
|
||
Result:=true;
|
||
end;
|
||
|
||
procedure TCocoaProgressIndicator.mouseDown(event: NSEvent);
|
||
begin
|
||
if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
|
||
begin
|
||
inherited mouseDown(event);
|
||
|
||
callback.MouseUpDownEvent(event, true);
|
||
end;
|
||
end;
|
||
|
||
procedure TCocoaProgressIndicator.mouseUp(event: NSEvent);
|
||
begin
|
||
if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
|
||
inherited mouseUp(event);
|
||
end;
|
||
|
||
procedure TCocoaProgressIndicator.rightMouseDown(event: NSEvent);
|
||
begin
|
||
if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
|
||
inherited rightMouseDown(event);
|
||
end;
|
||
|
||
procedure TCocoaProgressIndicator.rightMouseUp(event: NSEvent);
|
||
begin
|
||
if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
|
||
inherited rightMouseUp(event);
|
||
end;
|
||
|
||
procedure TCocoaProgressIndicator.rightMouseDragged(event: NSEvent);
|
||
begin
|
||
if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
|
||
inherited rightMouseDragged(event);
|
||
end;
|
||
|
||
procedure TCocoaProgressIndicator.otherMouseDown(event: NSEvent);
|
||
begin
|
||
if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
|
||
inherited otherMouseDown(event);
|
||
end;
|
||
|
||
procedure TCocoaProgressIndicator.otherMouseUp(event: NSEvent);
|
||
begin
|
||
if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
|
||
inherited otherMouseUp(event);
|
||
end;
|
||
|
||
procedure TCocoaProgressIndicator.otherMouseDragged(event: NSEvent);
|
||
begin
|
||
if not Assigned(callback) or not callback.MouseMove(event) then
|
||
inherited otherMouseDragged(event);
|
||
end;
|
||
|
||
procedure TCocoaProgressIndicator.mouseDragged(event: NSEvent);
|
||
begin
|
||
if not Assigned(callback) or not callback.MouseMove(event) then
|
||
inherited mouseDragged(event);
|
||
end;
|
||
|
||
procedure TCocoaProgressIndicator.mouseMoved(event: NSEvent);
|
||
begin
|
||
if not Assigned(callback) or not callback.MouseMove(event) then
|
||
inherited mouseMoved(event);
|
||
end;
|
||
|
||
procedure TCocoaProgressIndicator.scrollWheel(event: NSEvent);
|
||
begin
|
||
if not Assigned(callback) or not callback.scrollWheel(event) then
|
||
inherited scrollWheel(event);
|
||
end;
|
||
|
||
|
||
{ TCocoaSlider }
|
||
|
||
function GetManTicks(slider: TCocoaSlider): TManualTicks;
|
||
begin
|
||
if not Assigned(slider.man) then
|
||
slider.man := TManualTicks.Create;
|
||
Result := slider.man;
|
||
end;
|
||
|
||
procedure TCocoaSlider.dealloc;
|
||
begin
|
||
man.Free;
|
||
inherited dealloc;
|
||
end;
|
||
|
||
procedure TCocoaSlider.drawRect(dirtyRect: NSRect);
|
||
var
|
||
i : integer;
|
||
nr : NSRect;
|
||
xr : NSRect;
|
||
dr : NSRect;
|
||
nm : integer;
|
||
ctx : NSGraphicsContext;
|
||
pth : NSBezierPath;
|
||
begin
|
||
if not Assigned(man) or (not man.draw) then begin
|
||
inherited drawRect(dirtyRect);
|
||
Exit;
|
||
end;
|
||
|
||
nm := round(maxValue - minValue);
|
||
if nm = 0 then Exit;
|
||
if numberOfTickMarks < 2 then Exit;
|
||
|
||
nr := rectOfTickMarkAtIndex(0);
|
||
xr := rectOfTickMarkAtIndex(1);
|
||
|
||
ctx := NSGraphicsContext.currentContext;
|
||
pth := NSBezierPath.bezierPath;
|
||
NSColor.controlShadowColor.setFill;
|
||
dr:=nr;
|
||
dr.origin.y := dr.origin.y + 1;
|
||
dr.size.height := dr.size.height - 1;
|
||
for i := 0 to man.count - 1 do begin
|
||
dr.origin.x := round(nr.origin.x + (xr.origin.x - nr.origin.x) * (man.ticks[i] - minValue) / nm);
|
||
pth.fillRect(dr);
|
||
end;
|
||
inherited drawRect(dirtyRect);
|
||
end;
|
||
|
||
function TCocoaSlider.acceptsFirstResponder: LCLObjCBoolean;
|
||
begin
|
||
Result := True;
|
||
end;
|
||
|
||
function TCocoaSlider.lclGetCallback: ICommonCallback;
|
||
begin
|
||
Result:=callback;
|
||
end;
|
||
|
||
procedure TCocoaSlider.lclClearCallback;
|
||
begin
|
||
callback := nil;
|
||
end;
|
||
|
||
procedure TCocoaSlider.SnapToInteger(AExtraFactor: Integer);
|
||
begin
|
||
setIntValue(Round(doubleValue() + AExtraFactor));
|
||
end;
|
||
|
||
procedure TCocoaSlider.sliderAction(sender: id);
|
||
var
|
||
newval: Integer;
|
||
begin
|
||
SnapToInteger();
|
||
newval := intValue;
|
||
if newval <> intval then begin
|
||
intval := newval;
|
||
// OnChange event
|
||
if callback <> nil then
|
||
callback.SendOnChange();
|
||
end;
|
||
end;
|
||
|
||
function TCocoaSlider.acceptsFirstMouse(event: NSEvent): LCLObjCBoolean;
|
||
begin
|
||
Result:=true;
|
||
end;
|
||
|
||
procedure TCocoaSlider.mouseDown(event: NSEvent);
|
||
begin
|
||
if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
|
||
begin
|
||
inherited mouseDown(event);
|
||
|
||
callback.MouseUpDownEvent(event, true);
|
||
end;
|
||
end;
|
||
|
||
procedure TCocoaSlider.mouseUp(event: NSEvent);
|
||
begin
|
||
if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
|
||
inherited mouseUp(event);
|
||
end;
|
||
|
||
procedure TCocoaSlider.rightMouseDown(event: NSEvent);
|
||
begin
|
||
if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
|
||
inherited rightMouseDown(event);
|
||
end;
|
||
|
||
procedure TCocoaSlider.rightMouseUp(event: NSEvent);
|
||
begin
|
||
if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
|
||
inherited rightMouseUp(event);
|
||
end;
|
||
|
||
procedure TCocoaSlider.rightMouseDragged(event: NSEvent);
|
||
begin
|
||
if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
|
||
inherited rightMouseDragged(event);
|
||
end;
|
||
|
||
procedure TCocoaSlider.otherMouseDown(event: NSEvent);
|
||
begin
|
||
if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
|
||
inherited otherMouseDown(event);
|
||
end;
|
||
|
||
procedure TCocoaSlider.otherMouseUp(event: NSEvent);
|
||
begin
|
||
if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
|
||
inherited otherMouseUp(event);
|
||
end;
|
||
|
||
procedure TCocoaSlider.otherMouseDragged(event: NSEvent);
|
||
begin
|
||
if not Assigned(callback) or not callback.MouseMove(event) then
|
||
inherited otherMouseDragged(event);
|
||
end;
|
||
|
||
procedure TCocoaSlider.mouseDragged(event: NSEvent);
|
||
begin
|
||
if not Assigned(callback) or not callback.MouseMove(event) then
|
||
inherited mouseDragged(event);
|
||
end;
|
||
|
||
procedure TCocoaSlider.mouseMoved(event: NSEvent);
|
||
begin
|
||
if not Assigned(callback) or not callback.MouseMove(event) then
|
||
inherited mouseMoved(event);
|
||
end;
|
||
|
||
procedure TCocoaSlider.scrollWheel(event: NSEvent);
|
||
begin
|
||
if not Assigned(callback) or not callback.scrollWheel(event) then
|
||
inherited scrollWheel(event);
|
||
end;
|
||
|
||
procedure TCocoaSlider.lclAddManTick(atick: integer);
|
||
var
|
||
mn : TManualTicks;
|
||
begin
|
||
mn := GetManTicks(self);
|
||
if mn.AddTick(atick) then
|
||
begin
|
||
if mn.draw then self.setNeedsDisplay_(true);
|
||
end;
|
||
end;
|
||
|
||
procedure TCocoaSlider.lclSetManTickDraw(adraw: Boolean);
|
||
var
|
||
mn : TManualTicks;
|
||
begin
|
||
mn := GetManTicks(self);
|
||
if mn.draw=adraw then Exit;
|
||
mn.draw:=adraw;
|
||
self.setNeedsDisplay_(true);
|
||
end;
|
||
|
||
procedure TCocoaSlider.lclExpectedKeys(var wantTabs, wantArrows, wantReturn,
|
||
wantAll: Boolean);
|
||
begin
|
||
wantTabs := false;
|
||
wantArrows := true;
|
||
wantReturn := false;
|
||
wantAll := false;
|
||
end;
|
||
|
||
type
|
||
NSViewControlSizeExt = objccategory external (NSView)
|
||
function controlSize: Integer; message 'controlSize';
|
||
procedure setControlSize(ASize: Integer); message 'setControlSize:';
|
||
function cell: id; message 'cell';
|
||
procedure setFont(afont: NSFont); message 'setFont:';
|
||
end;
|
||
|
||
procedure SetNSControlSize(ctrl: NSView; newHeight, miniHeight, smallHeight: Integer; AutoChangeFont: Boolean);
|
||
var
|
||
sz : NSControlSize;
|
||
begin
|
||
if (miniHeight>0) and (newHeight<=miniHeight) then
|
||
sz:=NSMiniControlSize
|
||
else if (smallHeight>0) and (newHeight<=smallHeight) then
|
||
sz:=NSSmallControlSize
|
||
else
|
||
sz:=NSRegularControlSize;
|
||
|
||
if ctrl.respondsToSelector(ObjCSelector('setControlSize:')) then
|
||
ctrl.setControlSize(sz)
|
||
else if ctrl.respondsToSelector(ObjCSelector('cell')) then
|
||
begin
|
||
if NSCell(ctrl.cell).controlSize<>sz then
|
||
NSCell(ctrl.cell).setControlSize(sz);
|
||
end;
|
||
if AutoChangeFont and (ctrl.respondsToSelector(ObjCSelector('setFont:'))) then
|
||
ctrl.setFont(NSFont.systemFontOfSize(NSFont.systemFontSizeForControlSize(sz)));
|
||
end;
|
||
|
||
|
||
end.
|
||
|