mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-06-24 13:08:21 +02:00
971 lines
27 KiB
ObjectPascal
971 lines
27 KiB
ObjectPascal
{ $Id: $}
|
|
{ --------------------------------------------
|
|
cocoatabcontrols.pas - 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 CocoaTabControls;
|
|
|
|
{$mode objfpc}{$H+}
|
|
{$modeswitch objectivec1}
|
|
{$modeswitch objectivec2}
|
|
{$interfaces corba}
|
|
{$include cocoadefines.inc}
|
|
|
|
interface
|
|
|
|
uses
|
|
Types, Classes, SysUtils,
|
|
MacOSAll, CocoaAll, CocoaUtils, CocoaPrivate, CocoaCallback, CocoaConst,
|
|
CocoaCustomControl;
|
|
|
|
type
|
|
|
|
ITabControlCallback = interface(ICommonCallback)
|
|
function shouldSelectTabViewItem(aTabIndex: Integer): Boolean;
|
|
procedure willSelectTabViewItem(aTabIndex: Integer);
|
|
procedure didSelectTabViewItem(aTabIndex: Integer);
|
|
end;
|
|
|
|
{ TCocoaTabPage }
|
|
|
|
TCocoaTabPage = objcclass(NSTabViewItem)
|
|
public
|
|
callback: ICommonCallback;
|
|
function lclGetCallback: ICommonCallback; override;
|
|
procedure lclClearCallback; override;
|
|
function lclFrame: TRect; override;
|
|
function lclClientFrame: TRect; override;
|
|
procedure setLabel(label__: NSString); override;
|
|
end;
|
|
|
|
{ TCocoaTabControl }
|
|
|
|
TCocoaTabControl = objcclass(NSTabView, NSTabViewDelegateProtocol)
|
|
private
|
|
prevarr : NSButton;
|
|
nextarr : NSButton;
|
|
|
|
public
|
|
triggeringByLCL: Boolean;
|
|
|
|
{ various indexes in fulltabs }
|
|
currentIndex : Integer; // index of the current tab
|
|
visibleLeftIndex: Integer; // index shown in TabView on the left
|
|
leftKeepAmount: Integer; // left tab amount to keep, equals currentIndex-visibleLeftIndex
|
|
|
|
procedure attachAllTabs; message 'attachAllTabs';
|
|
procedure updateVariousIndex; message 'updateVariousIndex';
|
|
|
|
public
|
|
callback: ITabControlCallback;
|
|
|
|
fulltabs : NSMutableArray; // the full list of NSTabViewItems
|
|
lclEnabled: Boolean;
|
|
// cocoa
|
|
class function alloc: id; override;
|
|
procedure dealloc; override;
|
|
procedure setFrame(aframe: NSRect); override;
|
|
procedure setTabViewType(newValue: NSTabViewType); override;
|
|
// lcl
|
|
function lclIsEnabled: Boolean; override;
|
|
procedure lclSetEnabled(AEnabled: Boolean); override;
|
|
function lclGetCallback: ICommonCallback; override;
|
|
procedure lclClearCallback; override;
|
|
function lclClientFrame: TRect; override;
|
|
function lclGetFrameToLayoutDelta: TRect; override;
|
|
// NSTabViewDelegateProtocol
|
|
function tabView_shouldSelectTabViewItem(tabView: NSTabView; tabViewItem: NSTabViewItem): Boolean; message 'tabView:shouldSelectTabViewItem:';
|
|
procedure tabView_willSelectTabViewItem(tabView: NSTabView; tabViewItem: NSTabViewItem); message 'tabView:willSelectTabViewItem:';
|
|
procedure tabView_didSelectTabViewItem(tabView: NSTabView; tabViewItem: NSTabViewItem); message 'tabView:didSelectTabViewItem:';
|
|
procedure tabViewDidChangeNumberOfTabViewItems(TabView: NSTabView); message 'tabViewDidChangeNumberOfTabViewItems:';
|
|
// mouse events
|
|
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;
|
|
// lcl
|
|
procedure exttabInsertTabViewItem_atIndex(lTabPage: NSTabViewItem; index: integer);
|
|
message 'exttabInsertTabViewItem:atIndex:';
|
|
procedure exttabMoveTabViewItem_toIndex(lTabPage: NSTabViewItem; NewIndex: integer);
|
|
message 'exttabMoveTabViewItem:toIndex:';
|
|
procedure exttabRemoveTabViewItem(removedTabPage: NSTabViewItem);
|
|
message 'exttabRemoveTabViewItem:';
|
|
function exttabIndexOfTabViewItem(lTabPage: NSTabViewItem): NSInteger;
|
|
message 'exttabIndexOfTabViewItem:';
|
|
procedure extselectTabViewItemAtIndex(index: NSInteger);
|
|
message 'extselectTabViewItemAtIndex:';
|
|
end;
|
|
|
|
{ TCocoaTabControlArrow }
|
|
|
|
TCocoaTabControlArrow = objcclass(NSButton)
|
|
private
|
|
_tabControl: TCocoaTabControl;
|
|
_lastMouseDownTime: NSDate;
|
|
private
|
|
function shouldSpeedUp(): Boolean; message 'shouldSpeedUp';
|
|
public
|
|
procedure mouseDown(theEvent: NSEvent); override;
|
|
procedure prevClick(sender: id); message 'prevClick:';
|
|
procedure nextClick(sender: id); message 'nextClick:';
|
|
end;
|
|
|
|
{ TCocoaTabPageView }
|
|
|
|
TCocoaTabPageView = objcclass(TCocoaCustomControl)
|
|
public
|
|
tabView: TCocoaTabControl;
|
|
tabPage: TCocoaTabPage;
|
|
procedure setFrame(arect: NSRect); override;
|
|
end;
|
|
|
|
function IndexOfTab(ahost: TCocoaTabControl; atab: NSTabViewItem): Integer;
|
|
|
|
// Hack: The function attempts to determine the tabs view
|
|
// if the view is found it would return its frame rect in LCL coordinates
|
|
// if the view cannot be determinted, the function returns false
|
|
// This is implemented as ObjC method, because "prevarr" and "nextarr"
|
|
// are private methods.
|
|
// It's unknown, if it's safe to cache the result, so the search is performed
|
|
// everytime
|
|
function GetTabsRect(tabs: TCocoaTabControl; var r: TRect): Boolean;
|
|
|
|
procedure UpdateTabAndArrowVisibility(aview: TCocoaTabControl);
|
|
|
|
implementation
|
|
|
|
function GetTabsRect(tabs: TCocoaTabControl; var r: TRect): Boolean;
|
|
var
|
|
i : integer;
|
|
sv : NSView;
|
|
f : NSRect;
|
|
begin
|
|
Result:=Assigned(tabs);
|
|
if not Result then Exit;
|
|
|
|
for i:=0 to Integer(tabs.subviews.count)-1 do
|
|
begin
|
|
sv:=NSView(tabs.subviews.objectAtIndex(i));
|
|
if not Assigned(sv)
|
|
or (sv = tabs.nextarr)
|
|
or (sv = tabs.prevarr)
|
|
or (sv.isKindOfClass(TCocoaTabPageView))
|
|
then Continue;
|
|
|
|
f := sv.frame;
|
|
if tabs.isFlipped then
|
|
r := NSRectToRect( f )
|
|
else
|
|
NSToLCLRect( f, tabs.frame.size.height, r );
|
|
Result := true;
|
|
Exit;
|
|
end;
|
|
Result:=false;
|
|
end;
|
|
|
|
function AllocArrowButton(tabControl:TCocoaTabControl; isPrev:Boolean): NSButton;
|
|
var
|
|
btn : TCocoaTabControlArrow;
|
|
begin
|
|
btn := TCocoaTabControlArrow.alloc.initWithFrame(NSZeroRect);
|
|
btn._tabControl := tabControl;
|
|
btn.setBezelStyle(NSRegularSquareBezelStyle);
|
|
btn.setButtonType(NSMomentaryLightButton);
|
|
|
|
if isPrev then
|
|
btn.setTitle( NSSTR_TABCONTROL_PREV_ARROW )
|
|
else
|
|
btn.setTitle( NSSTR_TABCONTROL_NEXT_ARROW );
|
|
|
|
{$ifdef BOOLFIX}
|
|
btn.setBordered_(Ord(false));
|
|
{$else}
|
|
btn.setBordered(false);
|
|
{$endif}
|
|
btn.sizeToFit();
|
|
if not isPrev then btn.setAutoresizingMask(NSViewMinXMargin);
|
|
Result:=btn;
|
|
end;
|
|
|
|
const
|
|
arrow_hofs = 12;
|
|
arrow_vofs = 10;
|
|
|
|
procedure PlaceButton(isPrev: Boolean; abtn: NSButton; dst: NSTabView);
|
|
var
|
|
org: NSPoint;
|
|
begin
|
|
if not assigned(abtn) then Exit;
|
|
|
|
if dst.tabViewType = NSTopTabsBezelBorder then
|
|
org.y := arrow_vofs
|
|
else
|
|
org.y := dst.frame.size.height - abtn.frame.size.height - arrow_vofs;
|
|
|
|
if isPrev then
|
|
org.x := arrow_hofs
|
|
else
|
|
org.x := dst.frame.size.width - abtn.frame.size.width - arrow_hofs;
|
|
|
|
abtn.setFrameOrigin(org);
|
|
end;
|
|
|
|
|
|
procedure AllocPrevNext(aview: TCocoaTabControl);
|
|
begin
|
|
aview.prevarr := AllocArrowButton(aview, true);
|
|
aview.addSubview(aview.prevarr);
|
|
aview.prevarr.setTarget(aview.prevarr);
|
|
aview.prevarr.setAction( ObjCSelector('prevClick:') );
|
|
|
|
aview.nextarr := AllocArrowButton(aview, false);
|
|
aview.addSubview(aview.nextarr);
|
|
aview.nextarr.setTarget(aview.nextarr);
|
|
aview.nextarr.setAction( ObjCSelector('nextClick:') );
|
|
end;
|
|
|
|
// by fine-tuning the algorithm, guarantee that the `selectedTabViewItem`
|
|
// remains unchanged when TabControl is not wide enough,
|
|
// so as to avoid a lot of useless `tabView_didSelectTabViewItem` events are triggered
|
|
// (Resize TabControl, Add Tab, Remove Tab、Prev Tab、Next Tab)
|
|
// 1. change `leftmost` to `currentIndex`, and change the meaning to the index of
|
|
// the currently selected Tab (mapping selectedTabViewItem)
|
|
// 2. currentIndex remains unchanged in ReviseTabs()
|
|
// 3. selectedTabViewItem is no longer removed and then added,
|
|
// but always remains in tabViewItems,
|
|
// thus tabView_didSelectTabViewItem is not triggered anymore
|
|
// 4. taking currentIndex as the intermediate value,
|
|
// try to keep the tabs on the right side, and then the left side
|
|
procedure ReviseTabs(aview: TCocoaTabControl; out ShowPrev,ShowNext: Boolean);
|
|
var
|
|
minw: double;
|
|
i: integer;
|
|
arr: NSArray;
|
|
vi : NSTabViewItem;
|
|
sz : NSSize;
|
|
x,y: integer;
|
|
lw : double;
|
|
tbext : double;
|
|
lwid : array of double;
|
|
xd : double;
|
|
j : integer;
|
|
ofs : integer;
|
|
frw: double;
|
|
v : NSView;
|
|
|
|
tryToKeepIndex : NSInteger;
|
|
leftIndex : Integer;
|
|
begin
|
|
ShowPrev := false;
|
|
ShowNext := false;
|
|
|
|
// ReviseTabs() supports tpTop and tpBottom only
|
|
if (aview.tabViewType=NSLeftTabsBezelBorder) or (aview.tabViewType=NSRightTabsBezelBorder) then exit;
|
|
|
|
if aview.fulltabs.count=0 then exit;
|
|
|
|
tryToKeepIndex:= aview.currentIndex - aview.leftKeepAmount;
|
|
if tryToKeepIndex < 0 then
|
|
tryToKeepIndex:= 0;
|
|
|
|
// AttachAllTabs() has been modified to not remove the selectedTabViewItem first,
|
|
// and no longer trigger tabView_didSelectTabViewItem
|
|
// regardless of whether aview.fulltabs.count>aview.tabViewItems.count,
|
|
// tabs need to be attached because the order may have been adjusted.
|
|
aview.attachAllTabs;
|
|
|
|
minw := aview.minimumSize.width;
|
|
if (minw<aview.frame.size.width) then Exit;
|
|
|
|
arr := aview.tabViewItems;
|
|
|
|
lw := 0;
|
|
SetLength(lwid, arr.count);
|
|
for i := 0 to Integer(arr.count) - 1 do
|
|
begin
|
|
vi := NSTabViewItem( arr.objectAtIndex(i) );
|
|
sz := vi.sizeOfLabel(false);
|
|
lw := lw + sz.width;
|
|
lwid[i] := sz.width;
|
|
end;
|
|
|
|
tbext := (minw - lw) / arr.count;
|
|
for i:=0 to length(lwid)-1 do
|
|
lwid[i] := lwid[i] + tbext;
|
|
|
|
frw := aview.frame.size.width;
|
|
frw := frw - ((arrow_hofs + aview.nextarr.frame.size.width) * 2);
|
|
if frw<0 then frw := 0;
|
|
|
|
ofs := aview.currentIndex;
|
|
if ofs>=length(lwid) then ofs:=length(lwid)-1;
|
|
if (ofs < 0) then ofs:=0;
|
|
|
|
// 1. keep the current tab first
|
|
// selectedTabViewItem is guaranteed to remain in the updated tabViewItems
|
|
xd := lwid[ofs];
|
|
|
|
// 2. try to keep the tabs on the left side, the amount not exceed leftKeepAmount,
|
|
// in order to fix the position of currentTab.
|
|
leftIndex := ofs;
|
|
for i := ofs-1 downto tryToKeepIndex do begin
|
|
if xd + lwid[i] > frw then begin
|
|
ShowPrev := true;
|
|
Break;
|
|
end;
|
|
xd := xd + lwid[i];
|
|
leftIndex := i;
|
|
end;
|
|
|
|
// 3. try to keep the tabs on the right side until it's not wide enough
|
|
// and ShowNext if necessary
|
|
for i := ofs+1 to length(lwid)-1 do begin
|
|
if xd + lwid[i] > frw then begin
|
|
for j:=length(lwid)-1 downto i do
|
|
aview.removeTabViewItem( arr.objectAtIndex(j));
|
|
ShowNext := true;
|
|
Break;
|
|
end;
|
|
xd := xd + lwid[i];
|
|
end;
|
|
|
|
// 4. try to keep the tabs on the left side until it's not wide enough
|
|
// and ShowPrev if necessary
|
|
if leftIndex <= 0 then
|
|
exit;
|
|
for i := leftIndex-1 downto 0 do begin
|
|
if xd + lwid[i] > frw then begin
|
|
ShowPrev := true;
|
|
Break;
|
|
end;
|
|
xd := xd + lwid[i];
|
|
leftIndex := i;
|
|
end;
|
|
for j:=leftIndex-1 downto 0 do
|
|
aview.removeTabViewItem( arr.objectAtIndex(j));
|
|
end;
|
|
|
|
procedure UpdateTabAndArrowVisibility(aview: TCocoaTabControl);
|
|
var
|
|
showNext : Boolean;
|
|
showPrev : Boolean;
|
|
responder: NSResponder;
|
|
begin
|
|
responder:= nil;
|
|
if Assigned(aview.window) then
|
|
responder:= aview.window.firstResponder;
|
|
|
|
ReviseTabs(aview, showPrev, showNExt);
|
|
aview.updateVariousIndex;
|
|
if Assigned(aview.prevarr) then
|
|
begin
|
|
PlaceButton(true, aview.prevarr, aview);
|
|
{$ifdef BOOLFIX}
|
|
aview.prevarr.setHidden_(Ord(not showPrev));
|
|
{$else}
|
|
aview.prevarr.setHidden(not showPrev);
|
|
{$endif}
|
|
end;
|
|
if Assigned(aview.nextarr) then
|
|
begin
|
|
PlaceButton(false, aview.nextarr, aview);
|
|
{$ifdef BOOLFIX}
|
|
aview.nextarr.setHidden_(Ord(not showNext));
|
|
{$else}
|
|
aview.nextarr.setHidden(not showNext);
|
|
{$endif}
|
|
end;
|
|
|
|
if Assigned(aview.window) then begin
|
|
if Assigned(responder) and (responder<>aview.window.firstResponder) then
|
|
aview.window.makeFirstResponder(responder);
|
|
end;
|
|
end;
|
|
|
|
function IndexOfTab(ahost: TCocoaTabControl; atab: NSTabViewItem): Integer;
|
|
var
|
|
idx : NSUInteger;
|
|
begin
|
|
idx := ahost.fulltabs.indexOfObject(atab);
|
|
if idx=NSUIntegerMax then Result:=-1
|
|
else begin
|
|
if idx>MaxInt then Result:=-1
|
|
else Result:=Integer(idx);
|
|
end;
|
|
end;
|
|
|
|
{ TCocoaTabPageView }
|
|
|
|
procedure TCocoaTabPageView.setFrame(arect: NSRect);
|
|
begin
|
|
// It's possible for a tab page view to go in negative height.
|
|
// However, automatic resizing flags (for whatever reason) prevent
|
|
// TCocoaTabPageView to go into negative height (remaining at 0 pixels)
|
|
// The code below makes sure that resizing is actually happening
|
|
if Assigned(superView) and (superView.frame.size.height < arect.size.height) then
|
|
arect.size.height := superView.frame.size.height;
|
|
|
|
inherited setFrame(arect);
|
|
end;
|
|
|
|
{ TCocoaTabPage }
|
|
|
|
function TCocoaTabPage.lclGetCallback: ICommonCallback;
|
|
begin
|
|
Result := callback;
|
|
end;
|
|
|
|
procedure TCocoaTabPage.lclClearCallback;
|
|
begin
|
|
callback := nil;
|
|
end;
|
|
|
|
function TCocoaTabPage.lclFrame: TRect;
|
|
var
|
|
svh: CGFloat;
|
|
lParent: NSTabView;
|
|
begin
|
|
lParent := tabView;
|
|
if lParent <> nil then
|
|
begin
|
|
svh := lParent.contentRect.size.height;
|
|
NSToLCLRect(lParent.contentRect, svh, Result);
|
|
end
|
|
else
|
|
begin
|
|
svh := tabView.frame.size.height;
|
|
NSToLCLRect(tabView.contentRect, svh, Result);
|
|
end;
|
|
{$IFDEF COCOA_DEBUG_TABCONTROL}
|
|
WriteLn('[TCocoaTabPage.lclFrame] '+dbgs(Result)+' '+NSStringToString(Self.label_));
|
|
{$ENDIF}
|
|
end;
|
|
|
|
function TCocoaTabPage.lclClientFrame: TRect;
|
|
begin
|
|
Result := lclFrame();
|
|
end;
|
|
|
|
procedure TCocoaTabPage.setLabel(label__: NSString);
|
|
begin
|
|
inherited setLabel(label__);
|
|
//todo: revise the parent labels
|
|
end;
|
|
|
|
{ TCocoaTabControl }
|
|
|
|
// by ensuring that selectedTabViewItem cannot be removed
|
|
// and tabView_didSelectTabViewItem is not triggered anymore
|
|
procedure TCocoaTabControl.attachAllTabs;
|
|
var
|
|
i : integer;
|
|
itm: NSTabViewItem;
|
|
begin
|
|
// only selectedItem reserved
|
|
for itm{%H-} in tabViewItems do begin
|
|
if itm <> selectedTabViewItem then
|
|
removeTabViewItem( itm );
|
|
end;
|
|
|
|
// insert all tabs in the order of fulltabs again
|
|
i:= 0;
|
|
for itm{%H-} in fulltabs do begin
|
|
if itm <> selectedTabViewItem then
|
|
insertTabViewItem_atIndex( itm, i );
|
|
inc( i );
|
|
end;
|
|
end;
|
|
|
|
procedure TCocoaTabControl.updateVariousIndex;
|
|
begin
|
|
if numberOfTabViewItems > 0 then begin
|
|
visibleLeftIndex:= fulltabs.indexOfObject( tabViewItemAtIndex(0) );
|
|
leftKeepAmount:= currentIndex - visibleLeftIndex;
|
|
end else begin
|
|
visibleLeftIndex:= -1;
|
|
leftKeepAmount:= 0;
|
|
end;
|
|
end;
|
|
|
|
class function TCocoaTabControl.alloc: id;
|
|
begin
|
|
Result := inherited alloc;
|
|
TCocoaTabControl(Result).fulltabs := NSMutableArray(NSMutableArray.alloc).init;
|
|
end;
|
|
|
|
procedure TCocoaTabControl.dealloc;
|
|
begin
|
|
if Assigned(fulltabs) then begin
|
|
fulltabs.release;
|
|
fulltabs := nil;
|
|
end;
|
|
inherited dealloc;
|
|
end;
|
|
|
|
procedure TCocoaTabControl.setFrame(aframe: NSRect);
|
|
begin
|
|
inherited setFrame(aframe);
|
|
|
|
if not Assigned(nextarr) then
|
|
AllocPrevNext( self );
|
|
|
|
UpdateTabAndArrowVisibility(self);
|
|
end;
|
|
|
|
procedure TCocoaTabControl.setTabViewType(newValue: NSTabViewType);
|
|
begin
|
|
Inherited;
|
|
UpdateTabAndArrowVisibility(self);
|
|
end;
|
|
|
|
procedure TCocoaTabControl.extselectTabViewItemAtIndex( index:NSInteger );
|
|
var
|
|
itm: NSTabViewItem;
|
|
visibleIndex: NSInteger;
|
|
oldKeepAmount: Integer;
|
|
begin
|
|
if (index<0) or (index>=fulltabs.count) then Exit;
|
|
|
|
itm:= NSTabViewItem( fulltabs.objectAtIndex(index) );
|
|
visibleIndex:= indexOfTabViewItem( itm );
|
|
if visibleIndex <> NSNotFound then begin
|
|
inherited selectTabViewItemAtIndex( visibleIndex );
|
|
end else begin
|
|
oldKeepAmount:= leftKeepAmount;
|
|
attachAllTabs;
|
|
inherited selectTabViewItemAtIndex( index );
|
|
leftKeepAmount:= oldKeepAmount;
|
|
UpdateTabAndArrowVisibility( self );
|
|
end;
|
|
end;
|
|
|
|
function TCocoaTabControl.lclIsEnabled: Boolean;
|
|
begin
|
|
Result:=lclEnabled and ((Assigned(superview) and superview.lclIsEnabled) or not Assigned(superview));
|
|
end;
|
|
|
|
procedure TCocoaTabControl.lclSetEnabled(AEnabled: Boolean);
|
|
begin
|
|
lclEnabled := AEnabled;
|
|
inherited lclSetEnabled(AEnabled);
|
|
end;
|
|
|
|
function TCocoaTabControl.lclGetCallback: ICommonCallback;
|
|
begin
|
|
Result := callback;
|
|
end;
|
|
|
|
procedure TCocoaTabControl.lclClearCallback;
|
|
begin
|
|
callback := nil;
|
|
end;
|
|
|
|
function TCocoaTabControl.lclClientFrame: TRect;
|
|
var
|
|
r : TRect;
|
|
f : NSRect;
|
|
begin
|
|
case tabViewType of
|
|
NSNoTabsNoBorder:
|
|
begin
|
|
f := frame;
|
|
f.origin.x := 0;
|
|
f.origin.y := 0;
|
|
Result := NSRectToRect( f );
|
|
end;
|
|
else
|
|
if isFlipped then
|
|
Result:=NSRectToRect( contentRect )
|
|
else
|
|
NSToLCLRect( contentRect, frame.size.height, Result );
|
|
end;
|
|
|
|
//if tabs are hidden, frame layout should not be taken into account
|
|
//r:=lclGetFrameToLayoutDelta;
|
|
//Types.OffsetRect(Result, -r.Left, -r.Top);
|
|
end;
|
|
|
|
function TCocoaTabControl.lclGetFrameToLayoutDelta: TRect;
|
|
begin
|
|
case tabViewType of
|
|
NSNoTabsNoBorder: begin
|
|
Result.Left := 0;
|
|
Result.Top := 0;
|
|
Result.Bottom := 0;
|
|
Result.Right := 0;
|
|
end;
|
|
else
|
|
Result.Bottom := -4;
|
|
Result.Top := 6;
|
|
Result.Left := 7;
|
|
Result.Right := -7;
|
|
end;
|
|
end;
|
|
|
|
function TCocoaTabControl.tabView_shouldSelectTabViewItem(tabView: NSTabView;
|
|
tabViewItem: NSTabViewItem): Boolean;
|
|
begin
|
|
Result := True;
|
|
if Assigned(callback) then
|
|
begin
|
|
Result:= callback.shouldSelectTabViewItem( IndexOfTab( self, tabViewItem) );
|
|
end;
|
|
end;
|
|
|
|
procedure TCocoaTabControl.tabView_willSelectTabViewItem(tabView: NSTabView;
|
|
tabViewItem: NSTabViewItem);
|
|
begin
|
|
if Assigned(callback) then
|
|
begin
|
|
callback.willSelectTabViewItem( IndexOfTab( self, tabViewItem) );
|
|
end;
|
|
end;
|
|
|
|
procedure TCocoaTabControl.tabView_didSelectTabViewItem(tabView: NSTabView;
|
|
tabViewItem: NSTabViewItem);
|
|
begin
|
|
//it's called together with "willSelect"
|
|
|
|
if triggeringByLCL then
|
|
exit;
|
|
|
|
currentIndex:= IndexOfTab( self, tabViewItem );
|
|
leftKeepAmount:= currentIndex - visibleLeftIndex;
|
|
|
|
if Assigned(callback) then
|
|
begin
|
|
// Expected LCL Focus changing goes as following:
|
|
// First page becomes visible
|
|
// Then focus is switching to the control of the page
|
|
// In Cocoa world, first "willSelect" runs,
|
|
// then "firstResponder" changes to Window
|
|
// then the views are reorded and the new View becomes a part
|
|
// of views chain (and attaches to the window
|
|
// the view is made "firstResponder"
|
|
// and finally "didSelect" is fired
|
|
callback.didSelectTabViewItem( currentIndex );
|
|
end;
|
|
|
|
// The recent clean up, drove the workaround below unnecessary
|
|
// (at least the problem is not observed)
|
|
// The issue, is that the controls are being placed incorrectly, below
|
|
// the actual height of the control. Refactoring, removed direct LCL bindings.
|
|
// And it seemed to helped with returning invalid control bounds?!
|
|
|
|
// Update the coordinates of all children of this tab
|
|
// Fixes bug 31914: TPageControl problems with Cocoa
|
|
{lTabView := tabViewItem.view.subViews.objectAtIndex(0);
|
|
for i := 0 to lTabView.subViews.count-1 do
|
|
begin
|
|
lCurSubview := lTabView.subViews.objectAtIndex(i);
|
|
lCurCallback := lCurSubview.lclGetCallback();
|
|
if Assigned(lCurCallback) then
|
|
begin
|
|
lLCLControl := TWinControl(lCurCallback.GetTarget());
|
|
lBounds := Classes.Bounds(lLCLControl.Left, lLCLControl.Top, lLCLControl.Width, lLCLControl.Height);
|
|
lCurSubview.lclSetFrame(lBounds);
|
|
end;
|
|
end;}
|
|
end;
|
|
|
|
procedure TCocoaTabControl.tabViewDidChangeNumberOfTabViewItems(
|
|
TabView: NSTabView);
|
|
begin
|
|
|
|
end;
|
|
|
|
procedure TCocoaTabControl.mouseDown(event: NSEvent);
|
|
var
|
|
itm : NSTabViewItem;
|
|
begin
|
|
itm := self.tabViewItemAtPoint( self.convertPoint_fromView(event.locationInWindow, nil ));
|
|
if not Assigned(itm) then
|
|
begin
|
|
inherited mouseDown(event);
|
|
Exit;
|
|
end;
|
|
|
|
if not (Assigned(callback) and callback.MouseUpDownEvent(event, false, true)) then
|
|
begin
|
|
inherited mouseDown(event);
|
|
|
|
if Assigned(callback) then
|
|
begin
|
|
callback.MouseUpDownEvent(event, True);
|
|
end;
|
|
end
|
|
end;
|
|
|
|
procedure TCocoaTabControl.mouseUp(event: NSEvent);
|
|
var
|
|
itm : NSTabViewItem;
|
|
begin
|
|
itm := self.tabViewItemAtPoint( self.convertPoint_fromView(event.locationInWindow, nil ));
|
|
if not Assigned(itm) then
|
|
begin
|
|
inherited mouseUp(event);
|
|
Exit;
|
|
end;
|
|
|
|
if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
|
|
inherited mouseUp(event);
|
|
end;
|
|
|
|
procedure TCocoaTabControl.rightMouseDown(event: NSEvent);
|
|
var
|
|
itm : NSTabViewItem;
|
|
begin
|
|
itm := self.tabViewItemAtPoint( self.convertPoint_fromView(event.locationInWindow, nil ));
|
|
if not Assigned(itm) then
|
|
begin
|
|
inherited rightMouseDown(event);
|
|
Exit;
|
|
end;
|
|
|
|
if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
|
|
inherited rightMouseDown(event);
|
|
end;
|
|
|
|
procedure TCocoaTabControl.rightMouseUp(event: NSEvent);
|
|
var
|
|
itm : NSTabViewItem;
|
|
begin
|
|
itm := self.tabViewItemAtPoint( self.convertPoint_fromView(event.locationInWindow, nil ));
|
|
if not Assigned(itm) then
|
|
begin
|
|
inherited rightMouseUp(event);
|
|
Exit;
|
|
end;
|
|
|
|
if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
|
|
inherited rightMouseUp(event);
|
|
end;
|
|
|
|
procedure TCocoaTabControl.rightMouseDragged(event: NSEvent);
|
|
begin
|
|
if not Assigned(callback) or not callback.MouseMove(event) then
|
|
inherited rightMouseDragged(event);
|
|
end;
|
|
|
|
procedure TCocoaTabControl.otherMouseDown(event: NSEvent);
|
|
var
|
|
itm : NSTabViewItem;
|
|
begin
|
|
itm := self.tabViewItemAtPoint( self.convertPoint_fromView(event.locationInWindow, nil ));
|
|
if not Assigned(itm) then
|
|
begin
|
|
inherited otherMouseDown(event);
|
|
Exit;
|
|
end;
|
|
|
|
if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
|
|
inherited otherMouseDown(event);
|
|
end;
|
|
|
|
procedure TCocoaTabControl.otherMouseUp(event: NSEvent);
|
|
var
|
|
itm : NSTabViewItem;
|
|
begin
|
|
itm := self.tabViewItemAtPoint( self.convertPoint_fromView(event.locationInWindow, nil ));
|
|
if not Assigned(itm) then
|
|
begin
|
|
inherited otherMouseUp(event);
|
|
Exit;
|
|
end;
|
|
|
|
if not Assigned(callback) or not callback.MouseUpDownEvent(event) then
|
|
inherited otherMouseUp(event);
|
|
end;
|
|
|
|
procedure TCocoaTabControl.otherMouseDragged(event: NSEvent);
|
|
begin
|
|
if not Assigned(callback) or not callback.MouseMove(event) then
|
|
inherited otherMouseDragged(event);
|
|
end;
|
|
|
|
procedure TCocoaTabControl.mouseDragged(event: NSEvent);
|
|
begin
|
|
if not Assigned(callback) or not callback.MouseMove(event) then
|
|
inherited mouseDragged(event);
|
|
end;
|
|
|
|
procedure TCocoaTabControl.mouseMoved(event: NSEvent);
|
|
var
|
|
itm : NSTabViewItem;
|
|
begin
|
|
itm := self.tabViewItemAtPoint( self.convertPoint_fromView(event.locationInWindow, nil ));
|
|
if not Assigned(itm) then
|
|
begin
|
|
inherited mouseMoved(event);
|
|
Exit;
|
|
end;
|
|
|
|
if Assigned(callback) then callback.MouseMove(event);
|
|
inherited mouseMoved(event);
|
|
end;
|
|
|
|
procedure TCocoaTabControl.exttabMoveTabViewItem_toIndex(
|
|
lTabPage: NSTabViewItem; NewIndex: integer);
|
|
var
|
|
isMovingCurrentPage: Boolean;
|
|
OldIndex: Integer;
|
|
begin
|
|
if fulltabs.count=0 then
|
|
Exit;
|
|
|
|
OldIndex := exttabIndexOfTabViewItem( lTabPage );
|
|
if OldIndex < 0 then
|
|
Exit;
|
|
|
|
if NewIndex > fulltabs.count then
|
|
NewIndex:= fulltabs.count;
|
|
|
|
isMovingCurrentPage := (OldIndex=currentIndex);
|
|
|
|
fulltabs.removeObjectAtIndex( OldIndex );
|
|
fulltabs.insertObject_atIndex( lTabPage, NewIndex );
|
|
|
|
if isMovingCurrentPage then begin
|
|
currentIndex:= NewIndex;
|
|
leftKeepAmount:= currentIndex - visibleLeftIndex;
|
|
end else begin
|
|
if (OldIndex<currentIndex) and (NewIndex>=currentIndex) then
|
|
dec( currentIndex )
|
|
else if (OldIndex>currentIndex) and (NewIndex<=currentIndex) then
|
|
inc( currentIndex );
|
|
end;
|
|
|
|
UpdateTabAndArrowVisibility( self );
|
|
end;
|
|
|
|
procedure TCocoaTabControl.exttabInsertTabViewItem_atIndex(
|
|
lTabPage:NSTabViewItem; index:integer );
|
|
begin
|
|
if index > fulltabs.count then
|
|
index:= fulltabs.count;
|
|
fulltabs.insertObject_atIndex( lTabPage, index );
|
|
if index <= currentIndex then
|
|
inc( currentIndex );
|
|
|
|
UpdateTabAndArrowVisibility( self );
|
|
end;
|
|
|
|
procedure TCocoaTabControl.exttabRemoveTabViewItem( removedTabPage: NSTabViewItem );
|
|
var
|
|
isRemovingCurrentPage: Boolean;
|
|
removedIndex: Integer;
|
|
nextTabPage: NSTabViewItem;
|
|
prevTabPage: NSTabViewItem;
|
|
begin
|
|
triggeringByLCL:= true;
|
|
try
|
|
removedIndex := exttabIndexOfTabViewItem( removedTabPage );
|
|
if removedIndex < 0 then
|
|
Exit;
|
|
isRemovingCurrentPage:= (removedIndex=currentIndex);
|
|
|
|
fulltabs.removeObjectAtIndex( removedIndex );
|
|
|
|
if isRemovingCurrentPage then begin
|
|
// removing current page
|
|
attachAllTabs;
|
|
if currentIndex = fulltabs.count then begin
|
|
dec( currentIndex );
|
|
removeTabViewItem( removedTabPage );
|
|
end else begin
|
|
selectTabViewItemAtIndex( currentIndex );
|
|
end;
|
|
end else begin
|
|
// not removing current page
|
|
// only fulltabs need to be changed,
|
|
// visible TabView auto updated in UpdateTabAndArrowVisibility()
|
|
if removedIndex < currentIndex then begin
|
|
dec( currentIndex );
|
|
if (removedIndex=visibleLeftIndex) and (removedIndex=currentIndex) then
|
|
leftKeepAmount:= 0;
|
|
end;
|
|
end;
|
|
|
|
UpdateTabAndArrowVisibility( self );
|
|
finally
|
|
triggeringByLCL:= false;
|
|
end;
|
|
end;
|
|
|
|
function TCocoaTabControl.exttabIndexOfTabViewItem(lTabPage: NSTabViewItem
|
|
): NSInteger;
|
|
begin
|
|
Result := fulltabs.indexOfObject(lTabPage);
|
|
if Result = NSNotFound then
|
|
Result:= -1;
|
|
end;
|
|
|
|
{ TCocoaTabControlArrow }
|
|
|
|
procedure TCocoaTabControlArrow.mouseDown(theEvent: NSEvent);
|
|
begin
|
|
_lastMouseDownTime := NSDate.date;
|
|
inherited;
|
|
_lastMouseDownTime := nil;
|
|
end;
|
|
|
|
function TCocoaTabControlArrow.shouldSpeedUp(): Boolean;
|
|
const
|
|
FOUR_MODIFIER_FLAGS = NSShiftKeyMask
|
|
or NSControlKeyMask
|
|
or NSAlternateKeyMask
|
|
or NSCommandKeyMask;
|
|
begin
|
|
if (NSApp.currentEvent.modifierFlags and FOUR_MODIFIER_FLAGS)<>0 then
|
|
exit(true);
|
|
if NSDate.date.timeIntervalSinceDate(_lastMouseDownTime) > NSEvent.doubleClickInterval then
|
|
exit(true);
|
|
Result := false;
|
|
end;
|
|
|
|
procedure TCocoaTabControlArrow.prevClick(sender: id);
|
|
var
|
|
currentIndex: Integer;
|
|
begin
|
|
currentIndex := _tabControl.currentIndex;
|
|
if currentIndex = 0 then
|
|
Exit;
|
|
if shouldSpeedUp() then
|
|
currentIndex := 0
|
|
else
|
|
dec(currentIndex);
|
|
_tabControl.extselectTabViewItemAtIndex(currentIndex);
|
|
end;
|
|
|
|
procedure TCocoaTabControlArrow.nextClick(sender: id);
|
|
var
|
|
currentIndex: Integer;
|
|
begin
|
|
currentIndex := _tabControl.currentIndex;
|
|
if currentIndex = Integer(_tabControl.fulltabs.count) - 1 then
|
|
Exit;
|
|
if shouldSpeedUp() then
|
|
currentIndex := Integer(_tabControl.fulltabs.count) - 1
|
|
else
|
|
inc(currentIndex);
|
|
_tabControl.extselectTabViewItemAtIndex(currentIndex);
|
|
end;
|
|
|
|
end.
|
|
|