{ $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=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) 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.