X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/dbeddfb93d3479d03d8ec4c0121dfbe3bbcc422b..cb6b81bc94108bcb0a702ac5fdaf4944fa7706a0:/src/osx/cocoa/button.mm diff --git a/src/osx/cocoa/button.mm b/src/osx/cocoa/button.mm index ec39699324..35c2c4d941 100644 --- a/src/osx/cocoa/button.mm +++ b/src/osx/cocoa/button.mm @@ -4,299 +4,505 @@ // Author: Stefan Csomor // Modified by: // Created: 1998-01-01 -// RCS-ID: $Id: button.cpp 54845 2008-07-30 14:52:41Z SC $ // Copyright: (c) Stefan Csomor // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// #include "wx/wxprec.h" -#include "wx/button.h" - #ifndef WX_PRECOMP - #include "wx/panel.h" - #include "wx/toplevel.h" - #include "wx/dcclient.h" +#include "wx/object.h" #endif -#include "wx/stockitem.h" +#include "wx/button.h" +#include "wx/toplevel.h" +#include "wx/tglbtn.h" #include "wx/osx/private.h" -wxSize wxButton::DoGetBestSize() const -{ - if ( GetId() == wxID_HELP ) - return wxSize( 20 , 20 ) ; +#if wxUSE_MARKUP + #include "wx/osx/cocoa/private/markuptoattr.h" +#endif // wxUSE_MARKUP - wxSize sz = GetDefaultSize() ; - switch (GetWindowVariant()) +@implementation wxNSButton + ++ (void)initialize +{ + static BOOL initialized = NO; + if (!initialized) { - case wxWINDOW_VARIANT_NORMAL: - case wxWINDOW_VARIANT_LARGE: - sz.y = 23 ; - break; + initialized = YES; + wxOSXCocoaClassAddWXMethods( self ); + } +} - case wxWINDOW_VARIANT_SMALL: - sz.y = 17 ; - break; +- (int) intValue +{ + switch ( [self state] ) + { + case NSOnState: + return 1; + case NSMixedState: + return 2; + default: + return 0; + } +} - case wxWINDOW_VARIANT_MINI: - sz.y = 15 ; +- (void) setIntValue: (int) v +{ + switch( v ) + { + case 2: + [self setState:NSMixedState]; break; - - default: + case 1: + [self setState:NSOnState]; + break; + default : + [self setState:NSOffState]; break; } +} - wxRect r ; - - m_peer->GetBestRect(&r); +- (void) setTrackingTag: (NSTrackingRectTag)tag +{ + rectTag = tag; +} - if ( r.GetWidth() == 0 && r.GetHeight() == 0 ) - { - } - sz.x = r.GetWidth(); - sz.y = r.GetHeight(); +- (NSTrackingRectTag) trackingTag +{ + return rectTag; +} - int wBtn = 96; - - if ((wBtn > sz.x) || ( GetWindowStyle() & wxBU_EXACTFIT)) - sz.x = wBtn; +@end -#if wxOSX_USE_CARBON - Rect bestsize = { 0 , 0 , 0 , 0 } ; - m_peer->GetBestRect( &bestsize ) ; +@interface NSView(PossibleSizeMethods) +- (NSControlSize)controlSize; +@end + +wxButtonCocoaImpl::wxButtonCocoaImpl(wxWindowMac *wxpeer, wxNSButton *v) +: wxWidgetCocoaImpl(wxpeer, v) +{ + SetNeedsFrame(false); +} - int wBtn; - if ( EmptyRect( &bestsize ) || ( GetWindowStyle() & wxBU_EXACTFIT) ) +void wxButtonCocoaImpl::SetBitmap(const wxBitmap& bitmap) +{ + // switch bezel style for plain pushbuttons + if ( bitmap.IsOk() ) + { + if ([GetNSButton() bezelStyle] == NSRoundedBezelStyle) + [GetNSButton() setBezelStyle:NSRegularSquareBezelStyle]; + } + else { - Point bounds; + [GetNSButton() setBezelStyle:NSRoundedBezelStyle]; + } + + wxWidgetCocoaImpl::SetBitmap(bitmap); +} - ControlFontStyleRec controlFont; - OSStatus err = m_peer->GetData( kControlEntireControl, kControlFontStyleTag, &controlFont ); - verify_noerr( err ); +#if wxUSE_MARKUP +void wxButtonCocoaImpl::SetLabelMarkup(const wxString& markup) +{ + wxMarkupToAttrString toAttr(GetWXPeer(), markup); + NSMutableAttributedString *attrString = toAttr.GetNSAttributedString(); + + // Button text is always centered. + NSMutableParagraphStyle * + paragraphStyle = [[NSMutableParagraphStyle alloc] init]; + [paragraphStyle setAlignment: NSCenterTextAlignment]; + [attrString addAttribute:NSParagraphStyleAttributeName + value:paragraphStyle + range:NSMakeRange(0, [attrString length])]; + [paragraphStyle release]; + + [GetNSButton() setAttributedTitle:attrString]; +} +#endif // wxUSE_MARKUP - wxCFStringRef str( m_label, GetFont().GetEncoding() ); +void wxButtonCocoaImpl::SetPressedBitmap( const wxBitmap& bitmap ) +{ + NSButton* button = GetNSButton(); + [button setAlternateImage: bitmap.GetNSImage()]; + if ( GetWXPeer()->IsKindOf(wxCLASSINFO(wxToggleButton)) ) + { + [button setButtonType:NSToggleButton]; + } + else + { + [button setButtonType:NSMomentaryChangeButton]; + } +} -#if wxOSX_USE_ATSU_TEXT - SInt16 baseline; - if ( m_font.MacGetThemeFontID() != kThemeCurrentPortFont ) - { - err = GetThemeTextDimensions( - (!m_label.empty() ? (CFStringRef)str : CFSTR(" ")), - m_font.MacGetThemeFontID(), kThemeStateActive, false, &bounds, &baseline ); - verify_noerr( err ); - } - else -#endif +void wxButtonCocoaImpl::GetLayoutInset(int &left , int &top , int &right, int &bottom) const +{ + left = top = right = bottom = 0; + NSControlSize size = NSRegularControlSize; + if ( [m_osxView respondsToSelector:@selector(controlSize)] ) + size = [m_osxView controlSize]; + else if ([m_osxView respondsToSelector:@selector(cell)]) + { + id cell = [(id)m_osxView cell]; + if ([cell respondsToSelector:@selector(controlSize)]) + size = [cell controlSize]; + } + + if ( [GetNSButton() bezelStyle] == NSRoundedBezelStyle ) + { + switch( size ) { - wxClientDC dc(const_cast(this)); - wxCoord width, height ; - dc.GetTextExtent( m_label , &width, &height); - bounds.h = width; - bounds.v = height; + case NSRegularControlSize: + left = right = 6; + top = 4; + bottom = 8; + break; + case NSSmallControlSize: + left = right = 5; + top = 4; + bottom = 7; + break; + case NSMiniControlSize: + left = right = 1; + top = 0; + bottom = 2; + break; } + } +} - wBtn = bounds.h + sz.y; +void wxButtonCocoaImpl::SetAcceleratorFromLabel(const wxString& label) +{ + const int accelPos = wxControl::FindAccelIndex(label); + if ( accelPos != wxNOT_FOUND ) + { + wxString accelstring(label[accelPos + 1]); // Skip '&' itself + accelstring.MakeLower(); + wxCFStringRef cfText(accelstring); + [GetNSButton() setKeyEquivalent:cfText.AsNSString()]; + [GetNSButton() setKeyEquivalentModifierMask:NSCommandKeyMask]; } else { - wBtn = bestsize.right - bestsize.left ; - // non 'normal' window variants don't return the correct height - // sz.y = bestsize.bottom - bestsize.top ; + [GetNSButton() setKeyEquivalent:@""]; } - if ((wBtn > sz.x) || ( GetWindowStyle() & wxBU_EXACTFIT)) - sz.x = wBtn; -#endif +} - return sz ; +NSButton *wxButtonCocoaImpl::GetNSButton() const +{ + wxASSERT( [m_osxView isKindOfClass:[NSButton class]] ); + + return static_cast(m_osxView); } -wxSize wxButton::GetDefaultSize() +// Set bezel style depending on the wxBORDER_XXX flags specified by the style +// and also accounting for the label (bezels are different for multiline +// buttons and normal ones) and the ID (special bezel is used for help button). +// +// This is extern because it's also used in src/osx/cocoa/tglbtn.mm. +extern "C" +void +SetBezelStyleFromBorderFlags(NSButton *v, + long style, + wxWindowID winid, + const wxString& label = wxString(), + const wxBitmap& bitmap = wxBitmap()) { - int wBtn = 70 ; - int hBtn = 20 ; + // We can't display a custom label inside a button with help bezel style so + // we only use it if we are using the default label. wxButton itself checks + // if the label is just "Help" in which case it discards it and passes us + // an empty string. + if ( winid == wxID_HELP && label.empty() ) + { + [v setBezelStyle:NSHelpButtonBezelStyle]; + } + else + { + // We can't use rounded bezel styles neither for multiline buttons nor + // for buttons containing (big) icons as they are only meant to be used + // at certain sizes, so the style used depends on whether the label is + // single or multi line. + const bool + isSimpleText = (label.find_first_of("\n\r") == wxString::npos) + && (!bitmap.IsOk() || bitmap.GetHeight() < 20); + + NSBezelStyle bezel; + switch ( style & wxBORDER_MASK ) + { + case wxBORDER_NONE: + bezel = NSShadowlessSquareBezelStyle; + [v setBordered:NO]; + break; + + case wxBORDER_SIMPLE: + bezel = NSShadowlessSquareBezelStyle; + break; + + case wxBORDER_SUNKEN: + bezel = isSimpleText ? NSTexturedRoundedBezelStyle + : NSSmallSquareBezelStyle; + break; - return wxSize(wBtn, hBtn); + default: + wxFAIL_MSG( "Unknown border style" ); + // fall through + + case 0: + case wxBORDER_STATIC: + case wxBORDER_RAISED: + case wxBORDER_THEME: + bezel = isSimpleText ? NSRoundedBezelStyle + : NSRegularSquareBezelStyle; + break; + } + + [v setBezelStyle:bezel]; + } +} + +// Set the keyboard accelerator key from the label (e.g. "Click &Me") +void wxButton::OSXUpdateAfterLabelChange(const wxString& label) +{ + wxButtonCocoaImpl *impl = static_cast(GetPeer()); + + // Update the bezel style as may be necessary if our new label is multi + // line while the old one wasn't (or vice versa). + SetBezelStyleFromBorderFlags(impl->GetNSButton(), + GetWindowStyle(), + GetId(), + label); + + + // Skip setting the accelerator for the default buttons as this would + // overwrite the default "Enter" which should be preserved. + wxTopLevelWindow * const + tlw = wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow); + if ( tlw ) + { + if ( tlw->GetDefaultItem() == this ) + return; + } + + impl->SetAcceleratorFromLabel(label); } -@implementation wxNSButton -- (id)initWithFrame:(NSRect)frame +wxWidgetImplType* wxWidgetImpl::CreateButton( wxWindowMac* wxpeer, + wxWindowMac* WXUNUSED(parent), + wxWindowID winid, + const wxString& label, + const wxPoint& pos, + const wxSize& size, + long style, + long WXUNUSED(extraStyle)) { - [super initWithFrame:frame]; - impl = NULL; - [self setTarget: self]; - [self setAction: @selector(clickedAction:)]; - return self; + NSRect r = wxOSXGetFrameForControl( wxpeer, pos , size ) ; + wxNSButton* v = [[wxNSButton alloc] initWithFrame:r]; + + SetBezelStyleFromBorderFlags(v, style, winid, label); + + [v setButtonType:NSMomentaryPushInButton]; + wxButtonCocoaImpl* const impl = new wxButtonCocoaImpl( wxpeer, v ); + impl->SetAcceleratorFromLabel(label); + return impl; } -- (void) clickedAction: (id) sender +void wxWidgetCocoaImpl::SetDefaultButton( bool isDefault ) { - if ( impl ) + if ( [m_osxView isKindOfClass:[NSButton class]] ) { - wxWindow* wxpeer = (wxWindow*) impl->GetWXPeer(); - if ( wxpeer ) - wxpeer->HandleClicked(0); + if ( isDefault ) + { + [(NSButton*)m_osxView setKeyEquivalent: @"\r" ]; + [(NSButton*)m_osxView setKeyEquivalentModifierMask: 0]; + } + else + [(NSButton*)m_osxView setKeyEquivalent: @"" ]; } } -- (void)setImplementation: (wxWidgetImpl *) theImplementation +void wxWidgetCocoaImpl::PerformClick() { - impl = theImplementation; + if ([m_osxView isKindOfClass:[NSControl class]]) + [(NSControl*)m_osxView performClick:nil]; } -- (wxWidgetImpl*) implementation +#if wxUSE_BMPBUTTON + +wxWidgetImplType* wxWidgetImpl::CreateBitmapButton( wxWindowMac* wxpeer, + wxWindowMac* WXUNUSED(parent), + wxWindowID winid, + const wxBitmap& bitmap, + const wxPoint& pos, + const wxSize& size, + long style, + long WXUNUSED(extraStyle)) { - return impl; + NSRect r = wxOSXGetFrameForControl( wxpeer, pos , size ) ; + wxNSButton* v = [[wxNSButton alloc] initWithFrame:r]; + + SetBezelStyleFromBorderFlags(v, style, winid, wxString(), bitmap); + + if (bitmap.IsOk()) + [v setImage:bitmap.GetNSImage() ]; + + [v setButtonType:NSMomentaryPushInButton]; + wxWidgetCocoaImpl* c = new wxButtonCocoaImpl( wxpeer, v ); + return c; } -- (BOOL) isFlipped +#endif // wxUSE_BMPBUTTON + +// +// wxDisclosureButton implementation +// + +@interface wxDisclosureNSButton : NSButton { - return YES; + + BOOL isOpen; } -- (int) intValue +- (void) updateImage; + +- (void) toggle; + ++ (NSImage *)rotateImage: (NSImage *)image; + +@end + +static const char * disc_triangle_xpm[] = { +"10 9 4 1", +" c None", +". c #737373", +"+ c #989898", +"- c #c6c6c6", +" .- ", +" ..+- ", +" ....+ ", +" ......- ", +" .......- ", +" ......- ", +" ....+ ", +" ..+- ", +" .- ", +}; + +@implementation wxDisclosureNSButton + ++ (void)initialize { - switch ( [self state] ) + static BOOL initialized = NO; + if (!initialized) { - case NSOnState: - return 1; - case NSMixedState: - return 2; - default: - return 0; + initialized = YES; + wxOSXCocoaClassAddWXMethods( self ); } } +- (id) initWithFrame:(NSRect) frame +{ + self = [super initWithFrame:frame]; + isOpen = NO; + [self setImagePosition:NSImageLeft]; + [self updateImage]; + return self; +} + +- (int) intValue +{ + return isOpen ? 1 : 0; +} + - (void) setIntValue: (int) v { - switch( v ) - { - case 2: - [self setState:NSMixedState]; - break; - case 1: - [self setState:NSOnState]; - break; - default : - [self setState:NSOffState]; - break; - } + isOpen = ( v != 0 ); + [self updateImage]; } -@end +- (void) toggle +{ + isOpen = !isOpen; + [self updateImage]; +} +wxCFRef downArray ; -wxWidgetImplType* wxWidgetImpl::CreateButton( wxWindowMac* wxpeer, - wxWindowMac* parent, - wxWindowID id, - const wxString& label, - const wxPoint& pos, - const wxSize& size, - long style, - long extraStyle) +- (void) updateImage { - NSView* sv = (wxpeer->GetParent()->GetHandle() ); - - NSRect r = wxOSXGetFrameForControl( wxpeer, pos , size ) ; - wxNSButton* v = [[wxNSButton alloc] initWithFrame:r]; - - if ( id == wxID_HELP ) + static wxBitmap trianglebm(disc_triangle_xpm); + if ( downArray.get() == NULL ) { - [v setBezelStyle:NSHelpButtonBezelStyle]; + downArray.reset( [[wxDisclosureNSButton rotateImage:trianglebm.GetNSImage()] retain] ); } + + if ( isOpen ) + [self setImage:(NSImage*)downArray.get()]; else + [self setImage:trianglebm.GetNSImage()]; +} + ++ (NSImage *)rotateImage: (NSImage *)image +{ + NSSize imageSize = [image size]; + NSSize newImageSize = NSMakeSize(imageSize.height, imageSize.width); + NSImage* newImage = [[NSImage alloc] initWithSize: newImageSize]; + + [newImage lockFocus]; + + NSAffineTransform* tm = [NSAffineTransform transform]; + [tm translateXBy:newImageSize.width/2 yBy:newImageSize.height/2]; + [tm rotateByDegrees:-90]; + [tm translateXBy:-newImageSize.width/2 yBy:-newImageSize.height/2]; + [tm concat]; + + + [image drawInRect:NSMakeRect(0,0,newImageSize.width, newImageSize.height) + fromRect:NSZeroRect operation:NSCompositeCopy fraction:1.0]; + + [newImage unlockFocus]; + return [newImage autorelease]; +} + +@end + +class wxDisclosureTriangleCocoaImpl : public wxWidgetCocoaImpl +{ +public : + wxDisclosureTriangleCocoaImpl(wxWindowMac* peer , WXWidget w) : + wxWidgetCocoaImpl(peer, w) { - [v setBezelStyle:NSRoundedBezelStyle]; - } - - [v setButtonType:NSMomentaryPushInButton]; - [sv addSubview:v]; - wxWidgetCocoaImpl* c = new wxWidgetCocoaImpl( wxpeer, v ); - [v setImplementation:c]; - return c; -/* - OSStatus err; - Rect bounds = wxMacGetBoundsForControl( wxpeer , pos , size ) ; - wxMacControl* peer = new wxMacControl(wxpeer) ; - if ( id == wxID_HELP ) - { - ControlButtonContentInfo info ; - info.contentType = kControlContentIconRef ; - GetIconRef(kOnSystemDisk, kSystemIconsCreator, kHelpIcon, &info.u.iconRef); - err = CreateRoundButtonControl( - MAC_WXHWND(parent->MacGetTopLevelWindowRef()), - &bounds, kControlRoundButtonNormalSize, - &info, peer->GetControlRefAddr() ); } - else if ( label.Find('\n' ) == wxNOT_FOUND && label.Find('\r' ) == wxNOT_FOUND) + + ~wxDisclosureTriangleCocoaImpl() { - // Button height is static in Mac, can't be changed, so we need to force it here - int maxHeight; - switch (wxpeer->GetWindowVariant() ) - { - case wxWINDOW_VARIANT_NORMAL: - case wxWINDOW_VARIANT_LARGE: - maxHeight = 20 ; - break; - case wxWINDOW_VARIANT_SMALL: - maxHeight = 17; - case wxWINDOW_VARIANT_MINI: - maxHeight = 15; - default: - break; - } - bounds.bottom = bounds.top + maxHeight ; - wxpeer->SetMaxSize( wxSize( wxpeer->GetMaxWidth() , maxHeight )); - err = CreatePushButtonControl( - MAC_WXHWND(parent->MacGetTopLevelWindowRef()), - &bounds, CFSTR(""), peer->GetControlRefAddr() ); } - else + + virtual void controlAction(WXWidget slf, void* _cmd, void *sender) { - ControlButtonContentInfo info ; - info.contentType = kControlNoContent ; - err = CreateBevelButtonControl( - MAC_WXHWND(parent->MacGetTopLevelWindowRef()) , &bounds, CFSTR(""), - kControlBevelButtonLargeBevel, kControlBehaviorPushbutton, - &info, 0, 0, 0, peer->GetControlRefAddr() ); + wxDisclosureNSButton* db = (wxDisclosureNSButton*)m_osxView; + [db toggle]; + wxWidgetCocoaImpl::controlAction(slf, _cmd, sender ); } - verify_noerr( err ); - return peer; - */ -} +}; -void wxWidgetCocoaImpl::SetDefaultButton( bool isDefault ) -{ - if ( [m_osxView isKindOfClass:[NSButton class]] ) - [(NSButton*)m_osxView setKeyEquivalent: isDefault ? @"\r" : nil ]; -} - -void wxWidgetCocoaImpl::PerformClick() -{ -} - -wxWidgetImplType* wxWidgetImpl::CreateDisclosureTriangle( wxWindowMac* wxpeer, - wxWindowMac* parent, - wxWindowID id, +wxWidgetImplType* wxWidgetImpl::CreateDisclosureTriangle( wxWindowMac* wxpeer, + wxWindowMac* WXUNUSED(parent), + wxWindowID winid, const wxString& label, - const wxPoint& pos, + const wxPoint& pos, const wxSize& size, - long style, - long extraStyle) + long style, + long WXUNUSED(extraStyle)) { - NSView* sv = (wxpeer->GetParent()->GetHandle() ); - NSRect r = wxOSXGetFrameForControl( wxpeer, pos , size ) ; - wxNSButton* v = [[wxNSButton alloc] initWithFrame:r]; - [v setBezelStyle:NSDisclosureBezelStyle]; - [v setButtonType:NSOnOffButton]; - [v setTitle:wxCFStringRef( label).AsNSString()]; - [v setImagePosition:NSImageRight]; - [sv addSubview:v]; - wxWidgetCocoaImpl* c = new wxWidgetCocoaImpl( wxpeer, v ); - [v setImplementation:c]; - return c; + wxDisclosureNSButton* v = [[wxDisclosureNSButton alloc] initWithFrame:r]; + if ( !label.empty() ) + [v setTitle:wxCFStringRef(label).AsNSString()]; + + SetBezelStyleFromBorderFlags(v, style, winid, label); + + return new wxDisclosureTriangleCocoaImpl( wxpeer, v ); }