/////////////////////////////////////////////////////////////////////////////
// Name:        src/osx/cocoa/toolbar.mm
// Purpose:     wxToolBar
// Author:      Stefan Csomor
// Created:     04/01/98
// Copyright:   (c) Stefan Csomor
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////////

#include "wx/wxprec.h"

#if wxUSE_TOOLBAR

#ifndef WX_PRECOMP
    #include "wx/wx.h"
#endif

#include "wx/toolbar.h"
#include "wx/app.h"
#include "wx/osx/private.h"
#include "wx/osx/private/available.h"
#include "wx/private/bmpbndl.h"
#include "wx/geometry.h"
#include "wx/sysopt.h"

const short kwxMacToolBarToolDefaultWidth = 16;
const short kwxMacToolBarToolDefaultHeight = 16;
const short kwxMacToolBarTopMargin = 4;
const short kwxMacToolBarLeftMargin =  4;
const short kwxMacToolBorder = 0;
const short kwxMacToolSpacing = 6;

wxBEGIN_EVENT_TABLE(wxToolBar, wxToolBarBase)
    EVT_PAINT( wxToolBar::OnPaint )
wxEND_EVENT_TABLE()


#pragma mark -
#pragma mark Tool Implementation

// ----------------------------------------------------------------------------
// private classes
// ----------------------------------------------------------------------------

class wxToolBarTool;

@interface wxNSToolBarButton : NSButton
{
    wxToolBarTool* impl;
}

- (id)initWithFrame:(NSRect)frame;
- (void) clickedAction: (id) sender;
- (void)setImplementation: (wxToolBarTool *) theImplementation;
- (wxToolBarTool*) implementation;
- (BOOL) isFlipped;

@end

// We have a dual implementation for each tool, WXWidget and NSToolbarItem*

// when embedding native controls in the native toolbar we must make sure the
// control does not get deleted behind our backs, so the retain count gets increased
// (after creation it is 1), first be the creation of the custom NSToolbarItem wrapper
// object, and second by the code 'creating' the custom HIView (which is the same as the
// already existing native control, therefore we just increase the ref count)
// when this view is removed from the native toolbar its count gets decremented again
// and when the HITooolbarItem wrapper object gets destroyed it is decremented as well
// so in the end the control lives with a refcount of one and can be disposed of by the
// wxControl code. For embedded controls on a non-native toolbar this ref count is less
// so we can only test against a range, not a specific value of the refcount.

class wxToolBarTool : public wxToolBarToolBase
{
public:
    wxToolBarTool(
        wxToolBar *tbar,
        int id,
        const wxString& label,
        const wxBitmapBundle& bmpNormal,
        const wxBitmapBundle& bmpDisabled,
        wxItemKind kind,
        wxObject *clientData,
        const wxString& shortHelp,
        const wxString& longHelp );

    wxToolBarTool(wxToolBar *tbar, wxControl *control, const wxString& label)
        : wxToolBarToolBase(tbar, control, label)
    {
        Init();
        if (control != nullptr)
            SetControlHandle( (WXWidget) control->GetHandle() );
    }

    virtual ~wxToolBarTool()
    {
        ClearControl();
    }

    WXWidget GetControlHandle()
    {
        return (WXWidget) m_controlHandle;
    }

    void SetControlHandle( WXWidget handle )
    {
        m_controlHandle = handle;
    }

    void SetPosition( const wxPoint& position );

    void ClearControl()
    {
        if ( m_controlHandle )
        {
            if ( !IsControl() )
            {
                [m_controlHandle removeFromSuperview];
                [m_controlHandle release];
            }
            else
            {
                // the embedded control is not under the responsibility of the tool, it gets disposed of in the
                // proper wxControl destructor
            }
            m_controlHandle = nullptr ;
        }

#if wxOSX_USE_NATIVE_TOOLBAR
        if ( m_toolbarItem )
        {
            [m_toolbarItem release];
            m_toolbarItem = nullptr;
        }
#endif // wxOSX_USE_NATIVE_TOOLBAR
    }

    wxSize GetSize() const
    {
        wxSize curSize;

        if ( IsControl() )
        {
            curSize = GetControl()->GetSize();
        }
        else if ( IsButton() )
        {
            // curSize = GetToolBar()->GetToolSize();
            NSRect best = [(wxNSToolBarButton*)m_controlHandle frame];
            curSize = wxSize(best.size.width, best.size.height);
        }
        else
        {
            // separator size
            curSize = GetToolBar()->GetToolSize();
            if ( GetToolBar()->IsVertical() )
                curSize.y /= 4;
            else
                curSize.x /= 4;
        }

        return curSize;
    }

    wxPoint GetPosition() const
    {
        return wxPoint( m_x, m_y );
    }

    bool Enable( bool enable ) override;

    void UpdateImages();

    void UpdateToggleImage( bool toggle );

    void UpdateLabel()
    {
        wxToolBar *tbar = (wxToolBar*) GetToolBar();
        int style = (tbar ? tbar->GetWindowStyleFlag() : 0);

        // strip mnemonics from the label for compatibility with the usual
        // labels in wxStaticText sense
        wxCFStringRef l(wxStripMenuCodes(m_label));
        wxCFStringRef sh( GetShortHelp() );
#if wxOSX_USE_NATIVE_TOOLBAR
       if ( m_toolbarItem )
        {
            [m_toolbarItem setLabel:l.AsNSString()];

            [m_toolbarItem setToolTip:sh.AsNSString()];
        }
#endif
        if ( IsButton() )
        {
            NSButton* const btn = (NSButton*)m_controlHandle;

            [btn setTitle:l.AsNSString()];

            if ( style & wxTB_NOICONS )
                [btn setImagePosition:NSNoImage];
            else if ( style & wxTB_TEXT )
                [btn setImagePosition:NSImageAbove];
            else
                [btn setImagePosition:NSImageOnly];

            if ( (style & (wxTB_NOICONS | wxTB_TEXT)) != 0 )
            {
                [btn sizeToFit];
            }
            else if (tbar)
            {
                wxSize toolsize = tbar->GetToolSize();
                NSRect frame = [m_controlHandle frame];
                frame.size.width = toolsize.x;
                frame.size.height = toolsize.y + 2;
                [btn setFrame:frame];
            }
        }

        if ( m_controlHandle )
        {
            [m_controlHandle setToolTip:sh.AsNSString()];
        }
    }

    void Action()
    {
        wxToolBar *tbar = (wxToolBar*) GetToolBar();
        if (CanBeToggled())
        {
            bool    shouldToggle;

            shouldToggle = !IsToggled();
            tbar->ToggleTool( GetId(), shouldToggle );
        }

        tbar->OnLeftClick( GetId(), IsToggled() );
    }

#if wxOSX_USE_NATIVE_TOOLBAR
    void SetToolbarItemRef( NSToolbarItem* ref )
    {
        if ( m_toolbarItem )
            [m_toolbarItem release];

        m_toolbarItem = ref;
    }

    NSToolbarItem* GetToolbarItemRef() const
    {
        return m_toolbarItem;
    }

    void SetIndex( CFIndex idx )
    {
        m_index = idx;
    }

    CFIndex GetIndex() const
    {
        return m_index;
    }

    virtual void SetLabel(const wxString& label) override
    {
        wxToolBarToolBase::SetLabel(label);
        UpdateLabel();
    }

    virtual bool SetShortHelp(const wxString& help) override
    {
        if ( !wxToolBarToolBase::SetShortHelp(help) )
            return false;

        UpdateLabel();

        return true;
    }
#endif // wxOSX_USE_NATIVE_TOOLBAR

private:
    void Init()
    {
        m_controlHandle = nullptr;

#if wxOSX_USE_NATIVE_TOOLBAR
        m_toolbarItem = nullptr;
        m_index = -1;
#endif
    }

    WXWidget m_controlHandle;
    wxCoord     m_x;
    wxCoord     m_y;
    wxBitmap    m_alternateBitmap;

#if wxOSX_USE_NATIVE_TOOLBAR
    NSToolbarItem* m_toolbarItem;
    // position in its toolbar, -1 means not inserted
    CFIndex m_index;
#endif
};

#if wxOSX_USE_NATIVE_TOOLBAR

@interface wxNSToolbarItem : NSToolbarItem
{
    wxToolBarTool* impl;
}

- (id) initWithItemIdentifier: (NSString*) identifier;
- (void)setImplementation: (wxToolBarTool *) theImplementation;
- (wxToolBarTool*) implementation;
- (void) clickedAction: (id) sender;
- (BOOL) validateToolbarItem:(NSToolbarItem *)theItem;

@end


@interface wxNSToolbarDelegate : NSObject <NSToolbarDelegate>
{
    bool m_isSelectable;
}

- (void)setSelectable:(bool) value;

- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag;

- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar*)toolbar;

- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar*)toolbar;

- (NSArray *)toolbarSelectableItemIdentifiers:(NSToolbar *)toolbar;

@end


@interface wxNSToolbar : NSToolbar
{
    wxNSToolbarDelegate* toolbarDelegate;
}

- (id)initWithIdentifier:(NSString *)identifier;
- (void) dealloc;

@end

#endif


#if wxOSX_USE_NATIVE_TOOLBAR

@implementation wxNSToolbarItem

- (id)initWithItemIdentifier: (NSString*) identifier
{
    if ( self = [super initWithItemIdentifier:identifier] )
    {
        impl = nullptr;
        [self setTarget: self];
        [self setAction: @selector(clickedAction:)];
    }
    return self;
}

- (void) clickedAction: (id) sender
{
    wxUnusedVar(sender);
    if ( impl )
    {
        impl->Action();
    }
}

- (void)setImplementation: (wxToolBarTool *) theImplementation
{
    impl = theImplementation;
}

- (wxToolBarTool*) implementation
{
    return impl;
}

- (BOOL)validateToolbarItem:(NSToolbarItem *)theItem
{
    wxUnusedVar(theItem);
    return impl->IsEnabled() ? YES:NO;
}

@end

@implementation wxNSToolbarDelegate

- (id)init
{
    if ( self = [super init] )
    {
        m_isSelectable = false;
    }
    return self;
}

- (void)setSelectable:(bool) value
{
    wxUnusedVar(value);
    m_isSelectable = true;
}

- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar*)toolbar
{
    wxUnusedVar(toolbar);
    return nil;
}

- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar*)toolbar
{
    wxUnusedVar(toolbar);
    return nil;
}

- (NSArray *)toolbarSelectableItemIdentifiers:(NSToolbar *)toolbar
{
  if ( m_isSelectable )
      return [[toolbar items] valueForKey:@"itemIdentifier"];
  else
      return nil;
}

- (NSToolbarItem*) toolbar:(NSToolbar*) toolbar itemForItemIdentifier:(NSString*) itemIdentifier willBeInsertedIntoToolbar:(BOOL) flag
{
    wxUnusedVar(toolbar);

    // This must be consistent with FormatToolId().
    wxToolBarTool* tool = nullptr;
    void* macToolbar;
    if ( sscanf([itemIdentifier UTF8String], "%p:%p", &macToolbar, &tool) == 2 && tool )
    {
        wxNSToolbarItem* item = (wxNSToolbarItem*) tool->GetToolbarItemRef();
        if ( flag && tool->IsControl() )
        {
            NSView* view = tool->GetControl()->GetHandle();
            [view removeFromSuperview];
            [item setView:view];
            wxSize sz = tool->GetControl()->GetSize();
            NSSize size = NSMakeSize(sz.x, sz.y);
            [item setMaxSize:size];
            [item setMinSize:size];
            [view setHidden:NO];
        }
        return item;
    }
    return nil;
}

@end


@implementation wxNSToolbar

- (id)initWithIdentifier:(NSString *)identifier
{
    if (self = [super initWithIdentifier:identifier])
    {
        toolbarDelegate = [[wxNSToolbarDelegate alloc] init];
        [self setDelegate:toolbarDelegate];
    }
    return self;
}

- (void)dealloc
{
    [toolbarDelegate release];
    [super dealloc];
}

@end

#endif

@implementation wxNSToolBarButton

- (id)initWithFrame:(NSRect)frame
{
    if ( self = [super initWithFrame:frame] )
    {
        impl = nullptr;
        [self setTarget: self];
        [self setAction: @selector(clickedAction:)];
    }
    return self;
}

- (void) clickedAction: (id) sender
{
    wxUnusedVar(sender);
    if ( impl )
    {
        impl->Action();
    }
}

- (void)setImplementation: (wxToolBarTool *) theImplementation
{
    impl = theImplementation;
}

- (wxToolBarTool*) implementation
{
    return impl;
}

- (BOOL) isFlipped
{
    return YES;
}

@end

bool wxToolBarTool::Enable( bool enable )
{
    if ( wxToolBarToolBase::Enable( enable ) == false )
        return false;

    if ( IsControl() )
    {
        GetControl()->Enable( enable );
    }
    else if ( IsButton() )
    {
#if wxOSX_USE_NATIVE_TOOLBAR
        if ( m_toolbarItem != nullptr )
            [m_toolbarItem setEnabled:enable];
#endif

        if ( m_controlHandle != nullptr )
            [(NSControl*)m_controlHandle setEnabled:enable];
    }

    return true;
}

void wxToolBarTool::SetPosition( const wxPoint& position )
{
    m_x = position.x;
    m_y = position.y;

    int mac_x = position.x;
    int mac_y = position.y;

    if ( IsButton() )
    {
        NSRect frame = [m_controlHandle frame];
        if ( frame.origin.x != mac_x || frame.origin.y != mac_y )
        {
            frame.origin.x = mac_x;
            frame.origin.y = mac_y;
            [m_controlHandle setFrame:frame];
        }
    }
    else if ( IsControl() )
    {
        // embedded native controls are moved by the OS
#if wxOSX_USE_NATIVE_TOOLBAR
        if ( ((wxToolBar*)GetToolBar())->MacWantsNativeToolbar() == false )
#endif
        {
            GetControl()->Move( position );
        }
    }
    else
    {
        NSRect frame = [m_controlHandle frame];
        if ( frame.origin.x != mac_x || frame.origin.y != mac_y )
        {
            frame.origin.x = mac_x;
            frame.origin.y = mac_y;
            [m_controlHandle setFrame:frame];
        }
    }
}

void wxToolBarTool::UpdateImages()
{
    [(NSButton*) m_controlHandle setImage:wxOSXGetImageFromBundle(m_bmpNormal)];

    if ( CanBeToggled() )
    {
        // TODO CS this should use the best current representation, or optionally iterate through all
        wxSize sz = m_bmpNormal.GetDefaultSize();
        m_alternateBitmap = wxBitmap();
        m_alternateBitmap.Create(sz.x, sz.y, -1); // TODO CS m_alternateBitmap.CreateWithDIPSize(sz, m_bmpNormal.GetScaleFactor());
        m_alternateBitmap.UseAlpha();
        wxMemoryDC dc;

        dc.SelectObject(m_alternateBitmap);
        // This color corresponds to OS X Yosemite's rendering of selected toolbar items
        // See also https://github.com/wxWidgets/wxWidgets/issues/16645
        wxColour grey(0xB9, 0xB9, 0xB9);
        dc.SetBackground(*wxTRANSPARENT_BRUSH);
        dc.Clear();
        dc.SetPen(grey);
        dc.SetBrush(grey);
        dc.DrawRoundedRectangle( 0, 0, sz.x, sz.y, 3 );
        dc.DrawBitmap( m_bmpNormal.GetBitmap(sz), 0, 0, true );
        dc.SelectObject( wxNullBitmap );

        [(NSButton*) m_controlHandle setAlternateImage:m_alternateBitmap.GetNSImage()];
    }
    UpdateToggleImage( CanBeToggled() && IsToggled() );
}

void wxToolBarTool::UpdateToggleImage( bool toggle )
{
#if wxOSX_USE_NATIVE_TOOLBAR
    // Avoid setting the image if we're not showing icons because the image may
    // be invalid.
    wxToolBar *tbar = (wxToolBar*) GetToolBar();
    int style = tbar ? tbar->GetWindowStyleFlag() : 0;
    if ( m_toolbarItem != nullptr && !(style & wxTB_NOICONS) )
    {
        // the native toolbar item only has a 'selected' state (one for one toolbar)
        // so we emulate the toggle here
        if ( CanBeToggled() && toggle )
            [m_toolbarItem setImage:m_alternateBitmap.GetNSImage()];
        else
            [m_toolbarItem setImage:wxOSXGetImageFromBundle(m_bmpNormal)];
    }
#endif

    if ( IsButton() )
        [(NSButton*)m_controlHandle setState:(toggle ? NSOnState : NSOffState)];
}

wxToolBarTool::wxToolBarTool(
    wxToolBar *tbar,
    int id,
    const wxString& label,
    const wxBitmapBundle& bmpNormal,
    const wxBitmapBundle& bmpDisabled,
    wxItemKind kind,
    wxObject *clientData,
    const wxString& shortHelp,
    const wxString& longHelp )
    :
    wxToolBarToolBase(
        tbar, id, label, bmpNormal, bmpDisabled, kind,
        clientData, shortHelp, longHelp )
{
    Init();
}

#pragma mark -
#pragma mark Toolbar Implementation

wxToolBarToolBase *wxToolBar::CreateTool(
    int id,
    const wxString& label,
    const wxBitmapBundle& bmpNormal,
    const wxBitmapBundle& bmpDisabled,
    wxItemKind kind,
    wxObject *clientData,
    const wxString& shortHelp,
    const wxString& longHelp )
{
    return new wxToolBarTool(
        this, id, label, bmpNormal, bmpDisabled, kind,
        clientData, shortHelp, longHelp );
}

wxToolBarToolBase *
wxToolBar::CreateTool(wxControl *control, const wxString& label)
{
    return new wxToolBarTool(this, control, label);
}

void wxToolBar::Init()
{
    m_maxWidth = -1;
    m_maxHeight = -1;
    m_defaultWidth = kwxMacToolBarToolDefaultWidth;
    m_defaultHeight = kwxMacToolBarToolDefaultHeight;

#if wxOSX_USE_NATIVE_TOOLBAR
    m_macToolbar = nullptr;
    m_macUsesNativeToolbar = false;
#endif
}

// also for the toolbar we have the dual implementation:
// only when MacInstallNativeToolbar is called is the native toolbar set as the window toolbar

bool wxToolBar::Create(
    wxWindow *parent,
    wxWindowID id,
    const wxPoint& pos,
    const wxSize& size,
    long style,
    const wxString& name )
{
    if ( !wxToolBarBase::Create( parent, id, pos, size, style, wxDefaultValidator, name ) )
        return false;

    FixupStyle();

    OSStatus err = noErr;

#if wxOSX_USE_NATIVE_TOOLBAR

    if (parent->IsKindOf(CLASSINFO(wxFrame)) && wxSystemOptions::GetOptionInt(wxT("mac.toolbar.no-native")) != 1)
    {
        wxString identifier = wxString::Format( wxT("%p"), this );
        wxCFStringRef cfidentifier(identifier);
        NSToolbar* tb =  [[wxNSToolbar alloc] initWithIdentifier:cfidentifier.AsNSString()];

        m_macToolbar = tb ;

        if (m_macToolbar != nullptr)
        {
            NSToolbarDisplayMode mode = NSToolbarDisplayModeDefault;
            NSToolbarSizeMode displaySize = NSToolbarSizeModeSmall;

            if ( style & wxTB_NOICONS )
                mode = NSToolbarDisplayModeLabelOnly;
            else if ( style & wxTB_TEXT )
                mode = NSToolbarDisplayModeIconAndLabel;
            else
                mode = NSToolbarDisplayModeIconOnly;

            [tb setDisplayMode:mode];
            [tb setSizeMode:displaySize];
       }
    }
#endif // wxOSX_USE_NATIVE_TOOLBAR

    return (err == noErr);
}

wxToolBar::~wxToolBar()
{
    // removal only works while the toolbar is there
    wxFrame *frame = wxDynamicCast(GetParent(), wxFrame);
    if ( frame && frame->GetToolBar() == this )
    {
        frame->SetToolBar(nullptr);
    }

#if wxOSX_USE_NATIVE_TOOLBAR
    [(NSToolbar*)m_macToolbar setDelegate:nil];
    [(NSToolbar*)m_macToolbar release];
    m_macToolbar = nullptr;
#endif // wxOSX_USE_NATIVE_TOOLBAR
}

bool wxToolBar::Show( bool show )
{
    WXWindow tlw = MacGetTopLevelWindowRef();
    bool bResult = (tlw != nullptr);

    if (bResult)
    {
#if wxOSX_USE_NATIVE_TOOLBAR
        bool ownToolbarInstalled = false;
        MacTopLevelHasNativeToolbar( &ownToolbarInstalled );
        if (ownToolbarInstalled)
        {
            bResult = ([(NSToolbar*)m_macToolbar isVisible] != show);
            if ( bResult )
                [(NSToolbar*)m_macToolbar setVisible:show];
        }
        else
            bResult = wxToolBarBase::Show( show );
#else

        bResult = wxToolBarBase::Show( show );
#endif
    }

    return bResult;
}

bool wxToolBar::IsShown() const
{
    bool bResult;

#if wxOSX_USE_NATIVE_TOOLBAR
    bool ownToolbarInstalled;

    MacTopLevelHasNativeToolbar( &ownToolbarInstalled );
    if (ownToolbarInstalled)
    {
        bResult = [(NSToolbar*)m_macToolbar isVisible];
    }
    else
        bResult = wxToolBarBase::IsShown();
#else

    bResult = wxToolBarBase::IsShown();
#endif

    return bResult;
}

void wxToolBar::DoGetSize( int *width, int *height ) const
{
#if wxOSX_USE_NATIVE_TOOLBAR
    bool    ownToolbarInstalled;

    MacTopLevelHasNativeToolbar( &ownToolbarInstalled );
    if ( ownToolbarInstalled )
    {
        WXWindow tlw = MacGetTopLevelWindowRef();
        CGFloat toolbarHeight = 0;
        NSRect windowFrame = NSMakeRect(0, 0, 0, 0);

        if(m_macToolbar && [(NSToolbar*)m_macToolbar isVisible])
        {
            windowFrame = [NSWindow contentRectForFrameRect:[tlw frame]
                                styleMask:[tlw styleMask]];
            toolbarHeight = NSHeight(windowFrame)
                        - NSHeight([[tlw contentView] frame]);
        }

        if ( width != nullptr )
            *width = (int)windowFrame.size.width;
        if ( height != nullptr )
            *height = (int)toolbarHeight;
    }
    else
        wxToolBarBase::DoGetSize( width, height );

#else
    wxToolBarBase::DoGetSize( width, height );
#endif
}

void wxToolBar::DoGetPosition(int*x, int *y) const
{
#if wxOSX_USE_NATIVE_TOOLBAR
    bool    ownToolbarInstalled;

    MacTopLevelHasNativeToolbar( &ownToolbarInstalled );
    if ( ownToolbarInstalled )
    {
        WXWindow tlw = MacGetTopLevelWindowRef();
        CGFloat toolbarHeight = 0;
        NSRect windowFrame = NSMakeRect(0, 0, 0, 0);

        if(m_macToolbar && [(NSToolbar*)m_macToolbar isVisible])
        {
            windowFrame = [NSWindow contentRectForFrameRect:[tlw frame]
                                                  styleMask:[tlw styleMask]];
            toolbarHeight = NSHeight(windowFrame)
            - NSHeight([[tlw contentView] frame]);
        }

        // it is extending to the north of the content area

        if ( x != nullptr )
            *x = 0;
        if ( y != nullptr )
            *y = -toolbarHeight;
    }
    else
        wxToolBarBase::DoGetPosition( x, y );

#else
    wxToolBarBase::DoGetPosition( x, y );
#endif
}

wxSize wxToolBar::DoGetBestSize() const
{
    // was updated in Realize()

    wxSize size = GetMinSize();

    return size;
}

void wxToolBar::SetWindowStyleFlag( long style )
{
    wxToolBarBase::SetWindowStyleFlag( style );

#if wxOSX_USE_NATIVE_TOOLBAR
    if (m_macToolbar != nullptr)
    {
        NSToolbarDisplayMode mode = NSToolbarDisplayModeDefault;

        if ( style & wxTB_NOICONS )
            mode = NSToolbarDisplayModeLabelOnly;
        else if ( style & wxTB_TEXT )
            mode = NSToolbarDisplayModeIconAndLabel;
        else
            mode = NSToolbarDisplayModeIconOnly;

        [(NSToolbar*) m_macToolbar setDisplayMode:mode];
    }
#endif

    wxToolBarTool *tool;
    wxToolBarToolsList::compatibility_iterator node = m_tools.GetFirst();
    while ( node )
    {
        tool = (wxToolBarTool *) node->GetData();
        if ( tool != nullptr )
        {
            tool->UpdateLabel();
        }

        node = node->GetNext();
    }

    InvalidateBestSize();
}

#if wxOSX_USE_NATIVE_TOOLBAR
bool wxToolBar::MacWantsNativeToolbar()
{
    return m_macUsesNativeToolbar;
}

bool wxToolBar::MacTopLevelHasNativeToolbar(bool *ownToolbarInstalled) const
{
    bool bResultV = false;

    if (ownToolbarInstalled != nullptr)
        *ownToolbarInstalled = false;

    WXWindow tlw = MacGetTopLevelWindowRef();
    if (tlw != nullptr)
    {
        NSToolbar* curToolbarRef = [tlw toolbar];
        bResultV = (curToolbarRef != nullptr);
        if (bResultV && (ownToolbarInstalled != nullptr))
            *ownToolbarInstalled = (curToolbarRef == m_macToolbar);
    }

    return bResultV;
}

bool wxToolBar::MacInstallNativeToolbar(bool usesNative)
{
    bool bResult = false;

    if (usesNative && (m_macToolbar == nullptr))
        return bResult;

    if (usesNative && HasFlag(wxTB_LEFT|wxTB_RIGHT|wxTB_BOTTOM) )
        return bResult;

    WXWindow tlw = MacGetTopLevelWindowRef();
    if (tlw == nullptr)
        return bResult;

    // check the existing toolbar
    NSToolbar* curToolbarRef = [tlw toolbar];

    m_macUsesNativeToolbar = usesNative;

    if (m_macUsesNativeToolbar)
    {
        // only install toolbar if there isn't one installed already
        if (curToolbarRef == nullptr)
        {
            bResult = true;
            // Note that we apparently need to make the toolbar visible before
            // setting it as the window's toolbar to avoid some mysterious
            // crashes under macOS 14, see #24560.
            [(NSToolbar*) m_macToolbar setVisible:YES];
            [tlw setToolbar:(NSToolbar*) m_macToolbar];

            GetPeer()->Move(0,0,0,0 );
            SetSize( wxSIZE_AUTO_WIDTH, 0 );
            GetPeer()->SetVisibility( false );
            wxToolBarBase::Show( false );
        }
    }
    else
    {
        // only deinstall toolbar if this is the installed one
        if (m_macToolbar == curToolbarRef)
        {
            bResult = true;
            [(NSToolbar*) m_macToolbar setVisible:NO];
            MacUninstallNativeToolbar();
            GetPeer()->SetVisibility( true );
        }
    }

    if (bResult)
        InvalidateBestSize();

// wxLogDebug( wxT("    --> [%lx] - result [%s]"), (long)this, bResult ? wxT("T") : wxT("F") );
    return bResult;
}

void wxToolBar::MacUninstallNativeToolbar()
{
    if (!m_macToolbar)
        return;

    WXWindow tlw = MacGetTopLevelWindowRef();
    if (tlw)
        [tlw setToolbar:nil];
}
#endif

void wxToolBar::DoLayout()
{
    int maxToolWidth = 0;
    int maxToolHeight = 0;

    int tw, th;
    GetSize( &tw, &th );

    // find the maximum tool width and height
    // and the number of stretchable items
    int numStretchableSpaces = 0;
    wxToolBarTool *tool;
    wxToolBarToolsList::compatibility_iterator node = m_tools.GetFirst();
    while ( node )
    {
        tool = (wxToolBarTool *) node->GetData();
        if ( tool != nullptr )
        {
            wxSize  sz = tool->GetSize();

            if ( sz.x > maxToolWidth )
                maxToolWidth = sz.x;
            if ( sz.y > maxToolHeight )
                maxToolHeight = sz.y;
            if ( tool->IsStretchableSpace() )
                numStretchableSpaces++;
        }

        node = node->GetNext();
    }

    // layout non-native toolbar

    bool isHorizontal =  !IsVertical();

    int maxWidth = 0;
    int maxHeight = 0;

    int x = m_xMargin + kwxMacToolBarLeftMargin;
    int y = m_yMargin + kwxMacToolBarTopMargin;

    node = m_tools.GetFirst();
    while ( node )
    {
        tool = (wxToolBarTool*) node->GetData();
        if ( tool == nullptr )
        {
            node = node->GetNext();
            continue;
        }

        // set tool position:
        // for the moment just perform a single row/column alignment
        wxSize  cursize = tool->GetSize();
        if ( x + cursize.x > maxWidth )
            maxWidth = x + cursize.x;
        if ( y + cursize.y > maxHeight )
            maxHeight = y + cursize.y;

        // update the item positioning state
        if ( !isHorizontal )
            y += cursize.y + kwxMacToolSpacing;
        else
            x += cursize.x + kwxMacToolSpacing;

        node = node->GetNext();
    }

    if ( isHorizontal )
    {
        // if not set yet, only one row
        if ( m_maxRows <= 0 )
            SetRows( 1 );

        maxWidth += m_xMargin + kwxMacToolBarLeftMargin;
        m_minWidth = maxWidth;
        m_minHeight = m_maxHeight = maxToolHeight + 2 * (m_yMargin + kwxMacToolBarTopMargin);
    }
    else
    {
        // if not set yet, have one column
        if ( (GetToolsCount() > 0) && (m_maxRows <= 0) )
            SetRows( GetToolsCount() );

        maxHeight += m_yMargin + kwxMacToolBarTopMargin;
        m_minHeight = maxHeight;
        m_minWidth = m_maxWidth = maxToolWidth + 2 * (m_yMargin + kwxMacToolBarTopMargin);
    }

    int totalStretchableSpace = 0;
    int spacePerStretchable = 0;
    if ( numStretchableSpaces > 0 )
    {
        if ( isHorizontal )
            totalStretchableSpace = tw - maxWidth;
        else
            totalStretchableSpace = th - maxHeight;

        if ( totalStretchableSpace > 0 )
            spacePerStretchable = totalStretchableSpace / numStretchableSpaces;
    }

    // perform real positioning

    x = m_xMargin + kwxMacToolBarLeftMargin;
    y = m_yMargin + kwxMacToolBarTopMargin;

    node = m_tools.GetFirst();
    int currentStretchable = 0;
    while ( node )
    {
        tool = (wxToolBarTool*) node->GetData();
        if ( tool == nullptr )
        {
            node = node->GetNext();
            continue;
        }

        wxSize  cursize = tool->GetSize();
        if ( tool->IsStretchableSpace() )
        {
            ++currentStretchable;
            int thisSpace = currentStretchable == numStretchableSpaces ?
            totalStretchableSpace - (currentStretchable-1)*spacePerStretchable :
            spacePerStretchable;
            if ( isHorizontal )
                cursize.x += thisSpace;
            else
                cursize.y += thisSpace;
        }

        if ( !isHorizontal )
        {
            int x1 = x + ( maxToolWidth - cursize.x ) / 2;
            tool->SetPosition( wxPoint(x1, y) );
        }
        else
        {
            int y1 = y + ( maxToolHeight - cursize.y ) / 2;
            tool->SetPosition( wxPoint(x, y1) );
        }

        // update the item positioning state
        if ( !isHorizontal )
            y += cursize.y + kwxMacToolSpacing;
        else
            x += cursize.x + kwxMacToolSpacing;

        node = node->GetNext();
    }

}

bool wxToolBar::Realize()
{
    if ( !wxToolBarBase::Realize() )
        return false;

    wxToolBarTool *tool;
    wxToolBarToolsList::compatibility_iterator node = m_tools.GetFirst();

#if wxOSX_USE_NATIVE_TOOLBAR
    CFIndex currentPosition = 0;
    bool insertAll = false;

    NSToolbar* refTB = (NSToolbar*)m_macToolbar;

    node = m_tools.GetFirst();
    while ( node )
    {
        tool = (wxToolBarTool*) node->GetData();
        if ( tool == nullptr )
        {
            node = node->GetNext();
            continue;
        }

        // install in native NSToolbar
        if ( refTB )
        {
            NSToolbarItem* hiItemRef = tool->GetToolbarItemRef();
            if ( hiItemRef != nullptr )
            {
                // since setting the help texts is non-virtual we have to update
                // the strings now
                wxCFStringRef sh( tool->GetShortHelp() );
                [hiItemRef setToolTip:sh.AsNSString()];

                if ( insertAll || (tool->GetIndex() != currentPosition) )
                {
                    if ( !insertAll )
                    {
                        insertAll = true;

                        // if this is the first tool that gets newly inserted or repositioned
                        // first remove all 'old' tools from here to the right, because of this
                        // all following tools will have to be reinserted (insertAll).
                        for ( wxToolBarToolsList::compatibility_iterator node2 = m_tools.GetLast();
                             node2 != node;
                             node2 = node2->GetPrevious() )
                        {
                            wxToolBarTool *tool2 = (wxToolBarTool*) node2->GetData();

                            const long idx = tool2->GetIndex();
                            if ( idx != -1 )
                            {
                                [refTB removeItemAtIndex:idx];
                                tool2->SetIndex(-1);
                            }
                        }
                    }

                    wxCFStringRef cfidentifier;
                    NSString *nsItemId;
                    if (tool->GetStyle() == wxTOOL_STYLE_SEPARATOR)
                    {
                        if ( tool->IsStretchable() )
                            nsItemId = NSToolbarFlexibleSpaceItemIdentifier;
                        else
                            nsItemId = NSToolbarSpaceItemIdentifier;
                    }
                    else
                    {
                        cfidentifier = wxCFStringRef(FormatToolId(tool));
                        nsItemId = cfidentifier.AsNSString();
                    }

                    [refTB insertItemWithItemIdentifier:nsItemId atIndex:currentPosition];
                    tool->SetIndex( currentPosition );
                }

                currentPosition++;
            }
        }
        node = node->GetNext();
    }

#endif

    DoLayout();

    // adjust radio items

    bool lastIsRadio = false;
    bool curIsRadio = false;

    node = m_tools.GetFirst();
    while ( node )
    {
        tool = (wxToolBarTool*) node->GetData();
        if ( tool == nullptr )
        {
            node = node->GetNext();
            continue;
        }

        // update radio button (and group) state
        lastIsRadio = curIsRadio;
        curIsRadio = ( tool->IsButton() && (tool->GetKind() == wxITEM_RADIO) );

        if ( !curIsRadio )
        {
            if ( tool->IsToggled() )
                DoToggleTool( tool, true );
        }
        else
        {
            if ( !lastIsRadio )
            {
                if ( tool->Toggle( true ) )
                {
                    DoToggleTool( tool, true );
                }
            }
            else if ( tool->IsToggled() )
            {
                if ( tool->IsToggled() )
                    DoToggleTool( tool, true );

                wxToolBarToolsList::compatibility_iterator  nodePrev = node->GetPrevious();
                while ( nodePrev )
                {
                    wxToolBarToolBase   *toggleTool = nodePrev->GetData();
                    if ( (toggleTool == nullptr) || !toggleTool->IsButton() || (toggleTool->GetKind() != wxITEM_RADIO) )
                        break;

                    if ( toggleTool->Toggle( false ) )
                        DoToggleTool( toggleTool, false );

                    nodePrev = nodePrev->GetPrevious();
                }
            }
        }

        node = node->GetNext();
    }

    InvalidateBestSize();
    SetInitialSize( wxSize(m_minWidth, m_minHeight));

    SendSizeEventToParent();

    return true;
}

void wxToolBar::DoSetSize(int x, int y, int width, int height, int sizeFlags)
{
    wxToolBarBase::DoSetSize(x, y, width, height, sizeFlags);

    DoLayout();
}

void wxToolBar::DoSetToolBitmapSize(const wxSize& size)
{
    m_defaultWidth = size.x + kwxMacToolBorder;
    m_defaultHeight = size.y + kwxMacToolBorder;

#if wxOSX_USE_NATIVE_TOOLBAR
    if (m_macToolbar != nullptr)
    {
        int maxs = wxMax( size.x, size.y );
        NSToolbarSizeMode sizeSpec;

        if ( maxs > 24 )
            sizeSpec = NSToolbarSizeModeRegular;
        else
            sizeSpec = NSToolbarSizeModeSmall;

        [(NSToolbar*) m_macToolbar setSizeMode:sizeSpec ];
    }
#endif
}

// The button size is bigger than the bitmap size
wxSize wxToolBar::GetToolSize() const
{
    return wxSize(m_defaultWidth + kwxMacToolBorder, m_defaultHeight + kwxMacToolBorder);
}

void wxToolBar::SetRows(int nRows)
{
    // avoid resizing the frame uselessly
    if ( nRows != m_maxRows )
        m_maxRows = nRows;
}

void wxToolBar::MacSuperChangedPosition()
{
    wxWindow::MacSuperChangedPosition();

    /*
#if wxOSX_USE_NATIVE_TOOLBAR
    if (! m_macUsesNativeToolbar )
        Realize();
#else

    Realize();
#endif
     */
}

void wxToolBar::SetToolNormalBitmap( int id, const wxBitmapBundle& bitmap )
{
    wxToolBarTool* tool = static_cast<wxToolBarTool*>(FindById(id));
    if ( tool )
    {
        wxCHECK_RET( tool->IsButton(), wxT("Can only set bitmap on button tools."));

        tool->SetNormalBitmap(bitmap);

        // a side-effect of the UpdateToggleImage function is that it always changes the bitmap used on the button.
        tool->UpdateImages();
    }
}

void wxToolBar::SetToolDisabledBitmap( int id, const wxBitmapBundle& bitmap )
{
    wxToolBarTool* tool = static_cast<wxToolBarTool*>(FindById(id));
    if ( tool )
    {
        wxCHECK_RET( tool->IsButton(), wxT("Can only set bitmap on button tools."));

        tool->SetDisabledBitmap(bitmap);

        // TODO:  what to do for this one?
    }
}

wxToolBarToolBase *wxToolBar::FindToolForPosition(wxCoord x, wxCoord y) const
{
    wxToolBarTool *tool;
    wxToolBarToolsList::compatibility_iterator node = m_tools.GetFirst();
    while ( node )
    {
        tool = (wxToolBarTool *)node->GetData();
        if (tool != nullptr)
        {
            wxRect2DInt r( tool->GetPosition(), tool->GetSize() );
            if ( r.Contains( wxPoint( x, y ) ) )
                return tool;
        }

        node = node->GetNext();
    }

    return nullptr;
}

wxString wxToolBar::MacGetToolTipString( wxPoint &pt )
{
    wxToolBarToolBase *tool = FindToolForPosition( pt.x, pt.y );
    if ( tool != nullptr )
        return tool->GetShortHelp();

    return wxEmptyString;
}

void wxToolBar::DoEnableTool(wxToolBarToolBase * WXUNUSED(t), bool WXUNUSED(enable))
{
    // everything already done in the tool's Enable implementation
}

void wxToolBar::DoToggleTool(wxToolBarToolBase *t, bool toggle)
{
    wxToolBarTool *tool = (wxToolBarTool *)t;
    if ( ( tool != nullptr ) && tool->IsButton() )
        tool->UpdateToggleImage( toggle );
}

bool wxToolBar::DoInsertTool(size_t WXUNUSED(pos), wxToolBarToolBase *toolBase)
{
    wxToolBarTool *tool = static_cast< wxToolBarTool*>(toolBase );
    if (tool == nullptr)
        return false;

    long style = GetWindowStyleFlag();

    wxSize toolSize = GetToolSize();
    WXWidget controlHandle = nullptr;
    NSRect toolrect = NSMakeRect(0, 0, toolSize.x, toolSize.y + 2 );

#if wxOSX_USE_NATIVE_TOOLBAR
    wxString label = tool->GetLabel();
    if (m_macToolbar && !label.empty() )
    {
        // strip mnemonics from the label for compatibility
        // with the usual labels in wxStaticText sense
        label = wxStripMenuCodes(label);
    }
#endif // wxOSX_USE_NATIVE_TOOLBAR

    switch (tool->GetStyle())
    {
        case wxTOOL_STYLE_SEPARATOR:
            {
                wxASSERT( tool->GetControlHandle() == nullptr );
                toolSize.x /= 4;
                toolSize.y /= 4;
                if ( IsVertical() )
                    toolrect.size.height = toolSize.y;
                else
                    toolrect.size.width = toolSize.x;

                // in flat style we need a visual separator
#if wxOSX_USE_NATIVE_TOOLBAR
                if (m_macToolbar != nullptr)
                {
                    NSString * nsItemId = nil;

                    if ( tool->IsStretchable() )
                        nsItemId = NSToolbarFlexibleSpaceItemIdentifier;
                    else
                        nsItemId = NSToolbarSpaceItemIdentifier;

                    NSToolbarItem* item = [[NSToolbarItem alloc] initWithItemIdentifier:nsItemId];
                    tool->SetToolbarItemRef( item );
                }
#endif // wxOSX_USE_NATIVE_TOOLBAR

                NSBox* box = [[NSBox alloc] initWithFrame:toolrect];
                [box setBoxType:NSBoxSeparator];
                controlHandle = box;
                tool->SetControlHandle( controlHandle );
            }
            break;

        case wxTOOL_STYLE_BUTTON:
            {
                wxASSERT( tool->GetControlHandle() == nullptr );

                wxNSToolBarButton* v = [[wxNSToolBarButton alloc] initWithFrame:toolrect];

                [v setBezelStyle:NSSmallSquareBezelStyle];
                [[v cell] setControlSize:NSSmallControlSize];
                [v setFont:[NSFont fontWithName:[[v font] fontName] size:[NSFont systemFontSizeForControlSize:NSSmallControlSize]]];
                [v setBordered:NO];
                [v setButtonType: ( tool->CanBeToggled() ? NSToggleButton : NSMomentaryPushInButton )];
                [v setImplementation:tool];

                controlHandle = v;

#if wxOSX_USE_NATIVE_TOOLBAR
                if (m_macToolbar != nullptr)
                {
                    wxString identifier = FormatToolId(tool);
                    wxCFStringRef cfidentifier( identifier );
                    wxNSToolbarItem* item = [[wxNSToolbarItem alloc] initWithItemIdentifier:cfidentifier.AsNSString() ];
                    [item setImplementation:tool];
                    tool->SetToolbarItemRef( item );
                }

#endif // wxOSX_USE_NATIVE_TOOLBAR
                tool->SetControlHandle( controlHandle );
                if ( !(style & wxTB_NOICONS) )
                    tool->UpdateImages();
                tool->UpdateLabel();

#if 0
                InstallControlEventHandler(
                    (WXWidget) controlHandle, GetwxMacToolBarToolEventHandlerUPP(),
                    GetEventTypeCount(eventList), eventList, tool, nullptr );
#endif
            }
            break;

        case wxTOOL_STYLE_CONTROL:

#if wxOSX_USE_NATIVE_TOOLBAR
            if (m_macToolbar != nullptr)
            {
                WXWidget view = (WXWidget) tool->GetControl()->GetHandle() ;
                wxCHECK_MSG( view, false, wxT("control must be non-null") );

                wxString identifier = FormatToolId(tool);
                wxCFStringRef cfidentifier( identifier );
                wxNSToolbarItem* item = [[wxNSToolbarItem alloc] initWithItemIdentifier:cfidentifier.AsNSString() ];
                [item setImplementation:tool];
                tool->SetToolbarItemRef( item );
           }
#else
            // right now there's nothing to do here
#endif
            tool->UpdateLabel();
            break;

        default:
            break;
    }

    if ( controlHandle )
    {
        WXWidget container = (WXWidget) GetHandle();
        wxASSERT_MSG( container != nullptr, wxT("No valid Mac container control") );

//        SetControlVisibility( controlHandle, true, true );
        [container addSubview:controlHandle];
    }

    // nothing special to do here - we relayout in Realize() later
    InvalidateBestSize();

    return true;

}

void wxToolBar::DoSetToggle(wxToolBarToolBase *WXUNUSED(tool), bool WXUNUSED(toggle))
{
    wxFAIL_MSG( wxT("not implemented") );
}

bool wxToolBar::DoDeleteTool(size_t WXUNUSED(pos), wxToolBarToolBase *toolbase)
{
    wxToolBarTool* tool = static_cast< wxToolBarTool*>(toolbase );
    wxToolBarToolsList::compatibility_iterator node;
    for ( node = m_tools.GetFirst(); node; node = node->GetNext() )
    {
        wxToolBarToolBase *tool2 = node->GetData();
        if ( tool2 == tool )
        {
            // let node point to the next node in the list
            node = node->GetNext();

            break;
        }
    }

    wxSize sz = ((wxToolBarTool*)tool)->GetSize();

#if wxOSX_USE_NATIVE_TOOLBAR
    CFIndex removeIndex = tool->GetIndex();
#endif

#if wxOSX_USE_NATIVE_TOOLBAR
    if (m_macToolbar != nullptr)
    {
        if ( removeIndex != -1 && m_macToolbar )
        {
            [(NSToolbar*) m_macToolbar removeItemAtIndex:removeIndex];
            tool->SetIndex( -1 );
        }
    }
#endif

    tool->ClearControl();

    // and finally reposition all the controls after this one

    for ( /* node -> first after deleted */; node; node = node->GetNext() )
    {
        wxToolBarTool *tool2 = (wxToolBarTool*) node->GetData();
        wxPoint pt = tool2->GetPosition();

        if ( IsVertical() )
            pt.y -= sz.y;
        else
            pt.x -= sz.x;

        tool2->SetPosition( pt );

#if wxOSX_USE_NATIVE_TOOLBAR
        if (m_macToolbar != nullptr)
        {
            if ( removeIndex != -1 && tool2->GetIndex() > removeIndex )
                tool2->SetIndex( tool2->GetIndex() - 1 );
        }
#endif
    }

    InvalidateBestSize();

    return true;
}

#include <Carbon/Carbon.h>

void wxToolBar::OnPaint(wxPaintEvent& event)
{
#if wxOSX_USE_NATIVE_TOOLBAR
    if ( m_macUsesNativeToolbar )
    {
        // nothing to do here
    }
    else
#endif
    {
        const int direction = GetDirection();
        int w, h;
        GetSize( &w, &h );

        wxPaintDC dc(this);

        wxRect rect(0,0,w,h);

        //  TODO determine whether to use flat appearance in earlier system
        if ( WX_IS_MACOS_AVAILABLE(10, 14 ) )
        {
            // No gradient.
        }
        else
        {
            dc.GradientFillLinear( rect , wxColour( 0xCC,0xCC,0xCC ), wxColour( 0xA8,0xA8,0xA8 ) , wxSOUTH );
        }

        dc.SetPen( wxPen( wxColour( 0x51,0x51,0x51 ) ) );

        if ( direction == wxTB_RIGHT )
            dc.DrawLine(0, 0, 0, h);
        else if ( direction == wxTB_LEFT )
            dc.DrawLine(w-1, 0, w-1, h);
        else if ( direction == wxTB_BOTTOM )
            dc.DrawLine(0, 0, w, 0);
        else if ( direction == wxTB_TOP )
            dc.DrawLine(0, h-1, w, h-1);
    }
    event.Skip();
}

#if wxOSX_USE_NATIVE_TOOLBAR
void wxToolBar::OSXSetSelectableTools(bool set)
{
    wxCHECK_RET( m_macToolbar, "toolbar must be non-null" );
    [(wxNSToolbarDelegate*)[(NSToolbar*)m_macToolbar delegate] setSelectable:set];
}

void wxToolBar::OSXSelectTool(int toolId)
{
    wxToolBarToolBase *tool = FindById(toolId);
    wxCHECK_RET( tool, "invalid tool ID" );
    wxCHECK_RET( m_macToolbar, "toolbar must be non-null" );

    wxString identifier = FormatToolId(tool);
    wxCFStringRef cfidentifier(identifier);
    [(NSToolbar*)m_macToolbar setSelectedItemIdentifier:cfidentifier.AsNSString()];
}

wxString wxToolBar::FormatToolId(const wxToolBarToolBase *tool) const
{
    return wxString::Format("%p:%p", m_macToolbar, tool);
}
#endif // wxOSX_USE_NATIVE_TOOLBAR

#endif // wxUSE_TOOLBAR
