1/////////////////////////////////////////////////////////////////////////////
2// Name:        src/osx/cocoa/button.mm
3// Purpose:     wxButton
4// Author:      Stefan Csomor
5// Modified by:
6// Created:     1998-01-01
7// Copyright:   (c) Stefan Csomor
8// Licence:     wxWindows licence
9/////////////////////////////////////////////////////////////////////////////
10
11#include "wx/wxprec.h"
12
13#ifndef WX_PRECOMP
14#include "wx/object.h"
15#endif
16
17#include "wx/button.h"
18#include "wx/toplevel.h"
19#include "wx/tglbtn.h"
20
21#include "wx/osx/private.h"
22
23#if wxUSE_MARKUP
24    #include "wx/osx/cocoa/private/markuptoattr.h"
25#endif // wxUSE_MARKUP
26
27
28@implementation wxNSButton
29
30+ (void)initialize
31{
32    static BOOL initialized = NO;
33    if (!initialized)
34    {
35        initialized = YES;
36        wxOSXCocoaClassAddWXMethods( self );
37    }
38}
39
40- (int) intValue
41{
42    switch ( [self state] )
43    {
44        case NSOnState:
45            return 1;
46        case NSMixedState:
47            return 2;
48        default:
49            return 0;
50    }
51}
52
53- (void) setIntValue: (int) v
54{
55    switch( v )
56    {
57        case 2:
58            [self setState:NSMixedState];
59            break;
60        case 1:
61            [self setState:NSOnState];
62            break;
63        default :
64            [self setState:NSOffState];
65            break;
66    }
67}
68
69- (void) setTrackingTag: (NSTrackingRectTag)tag
70{
71    rectTag = tag;
72}
73
74- (NSTrackingRectTag) trackingTag
75{
76    return rectTag;
77}
78
79@end
80
81@interface NSView(PossibleSizeMethods)
82- (NSControlSize)controlSize;
83@end
84
85wxButtonCocoaImpl::wxButtonCocoaImpl(wxWindowMac *wxpeer, wxNSButton *v)
86: wxWidgetCocoaImpl(wxpeer, v)
87{
88    SetNeedsFrame(false);
89}
90
91void wxButtonCocoaImpl::SetBitmap(const wxBitmap& bitmap)
92{
93    // switch bezel style for plain pushbuttons
94    if ( bitmap.IsOk() )
95    {
96        if ([GetNSButton() bezelStyle] == NSRoundedBezelStyle)
97            [GetNSButton() setBezelStyle:NSRegularSquareBezelStyle];
98    }
99    else
100    {
101        [GetNSButton() setBezelStyle:NSRoundedBezelStyle];
102    }
103
104    wxWidgetCocoaImpl::SetBitmap(bitmap);
105}
106
107#if wxUSE_MARKUP
108void wxButtonCocoaImpl::SetLabelMarkup(const wxString& markup)
109{
110    wxMarkupToAttrString toAttr(GetWXPeer(), markup);
111    NSMutableAttributedString *attrString = toAttr.GetNSAttributedString();
112
113    // Button text is always centered.
114    NSMutableParagraphStyle *
115    paragraphStyle = [[NSMutableParagraphStyle alloc] init];
116    [paragraphStyle setAlignment: NSCenterTextAlignment];
117    [attrString addAttribute:NSParagraphStyleAttributeName
118                       value:paragraphStyle
119                       range:NSMakeRange(0, [attrString length])];
120    [paragraphStyle release];
121
122    [GetNSButton() setAttributedTitle:attrString];
123}
124#endif // wxUSE_MARKUP
125
126void wxButtonCocoaImpl::SetPressedBitmap( const wxBitmap& bitmap )
127{
128    NSButton* button = GetNSButton();
129    [button setAlternateImage: bitmap.GetNSImage()];
130#if wxUSE_TOGGLEBTN
131    if ( GetWXPeer()->IsKindOf(wxCLASSINFO(wxToggleButton)) )
132    {
133        [button setButtonType:NSToggleButton];
134    }
135    else
136#endif
137    {
138        [button setButtonType:NSMomentaryChangeButton];
139    }
140}
141
142void wxButtonCocoaImpl::GetLayoutInset(int &left , int &top , int &right, int &bottom) const
143{
144    left = top = right = bottom = 0;
145    NSControlSize size = NSRegularControlSize;
146    if ( [m_osxView respondsToSelector:@selector(controlSize)] )
147        size = [m_osxView controlSize];
148    else if ([m_osxView respondsToSelector:@selector(cell)])
149    {
150        id cell = [(id)m_osxView cell];
151        if ([cell respondsToSelector:@selector(controlSize)])
152            size = [cell controlSize];
153    }
154
155    if ( [GetNSButton() bezelStyle] == NSRoundedBezelStyle )
156    {
157        switch( size )
158        {
159            case NSRegularControlSize:
160                left = right = 6;
161                top = 4;
162                bottom = 7;
163                break;
164            case NSSmallControlSize:
165                left = right = 5;
166                top = 4;
167                bottom = 6;
168                break;
169            case NSMiniControlSize:
170                left = right = 1;
171                top = 0;
172                bottom = 1;
173                break;
174        }
175    }
176}
177
178void wxButtonCocoaImpl::SetAcceleratorFromLabel(const wxString& label)
179{
180    const int accelPos = wxControl::FindAccelIndex(label);
181    if ( accelPos != wxNOT_FOUND )
182    {
183        wxString accelstring(label[accelPos + 1]); // Skip '&' itself
184        accelstring.MakeLower();
185        wxCFStringRef cfText(accelstring);
186        [GetNSButton() setKeyEquivalent:cfText.AsNSString()];
187        [GetNSButton() setKeyEquivalentModifierMask:NSCommandKeyMask];
188    }
189    else
190    {
191        [GetNSButton() setKeyEquivalent:@""];
192    }
193}
194
195NSButton *wxButtonCocoaImpl::GetNSButton() const
196{
197    wxASSERT( [m_osxView isKindOfClass:[NSButton class]] );
198
199    return static_cast<NSButton *>(m_osxView);
200}
201
202// Set bezel style depending on the wxBORDER_XXX flags specified by the style
203// and also accounting for the label (bezels are different for multiline
204// buttons and normal ones) and the ID (special bezel is used for help button).
205//
206// This is extern because it's also used in src/osx/cocoa/tglbtn.mm.
207extern "C"
208void
209SetBezelStyleFromBorderFlags(NSButton *v,
210                             long style,
211                             wxWindowID winid,
212                             const wxString& label = wxString(),
213                             const wxBitmap& bitmap = wxBitmap())
214{
215    // We can't display a custom label inside a button with help bezel style so
216    // we only use it if we are using the default label. wxButton itself checks
217    // if the label is just "Help" in which case it discards it and passes us
218    // an empty string.
219    if ( winid == wxID_HELP && label.empty() )
220    {
221        [v setBezelStyle:NSHelpButtonBezelStyle];
222    }
223    else
224    {
225        // We can't use rounded bezel styles neither for multiline buttons nor
226        // for buttons containing (big) icons as they are only meant to be used
227        // at certain sizes, so the style used depends on whether the label is
228        // single or multi line.
229        const bool
230            isSimpleText = (label.find_first_of("\n\r") == wxString::npos)
231                                && (!bitmap.IsOk() || bitmap.GetHeight() < 20);
232
233        NSBezelStyle bezel;
234        switch ( style & wxBORDER_MASK )
235        {
236            case wxBORDER_NONE:
237                bezel = NSShadowlessSquareBezelStyle;
238                [v setBordered:NO];
239                break;
240
241            case wxBORDER_SIMPLE:
242                bezel = NSShadowlessSquareBezelStyle;
243                break;
244
245            case wxBORDER_SUNKEN:
246                bezel = isSimpleText ? NSTexturedRoundedBezelStyle
247                                     : NSSmallSquareBezelStyle;
248                break;
249
250            default:
251                wxFAIL_MSG( "Unknown border style" );
252                // fall through
253
254            case 0:
255            case wxBORDER_STATIC:
256            case wxBORDER_RAISED:
257            case wxBORDER_THEME:
258                bezel = isSimpleText ? NSRoundedBezelStyle
259                                     : NSRegularSquareBezelStyle;
260                break;
261        }
262
263        [v setBezelStyle:bezel];
264    }
265}
266
267// Set the keyboard accelerator key from the label (e.g. "Click &Me")
268void wxButton::OSXUpdateAfterLabelChange(const wxString& label)
269{
270    wxButtonCocoaImpl *impl = static_cast<wxButtonCocoaImpl*>(GetPeer());
271
272    // Update the bezel style as may be necessary if our new label is multi
273    // line while the old one wasn't (or vice versa).
274    SetBezelStyleFromBorderFlags(impl->GetNSButton(),
275                                 GetWindowStyle(),
276                                 GetId(),
277                                 label);
278
279
280    // Skip setting the accelerator for the default buttons as this would
281    // overwrite the default "Enter" which should be preserved.
282    wxTopLevelWindow * const
283        tlw = wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow);
284    if ( tlw )
285    {
286        if ( tlw->GetDefaultItem() == this )
287            return;
288    }
289
290    impl->SetAcceleratorFromLabel(label);
291}
292
293
294wxWidgetImplType* wxWidgetImpl::CreateButton( wxWindowMac* wxpeer,
295                                    wxWindowMac* WXUNUSED(parent),
296                                    wxWindowID winid,
297                                    const wxString& label,
298                                    const wxPoint& pos,
299                                    const wxSize& size,
300                                    long style,
301                                    long WXUNUSED(extraStyle))
302{
303    NSRect r = wxOSXGetFrameForControl( wxpeer, pos , size ) ;
304    wxNSButton* v = [[wxNSButton alloc] initWithFrame:r];
305
306    SetBezelStyleFromBorderFlags(v, style, winid, label);
307
308    [v setButtonType:NSMomentaryPushInButton];
309    wxButtonCocoaImpl* const impl = new wxButtonCocoaImpl( wxpeer, v );
310    impl->SetAcceleratorFromLabel(label);
311    return impl;
312}
313
314void wxWidgetCocoaImpl::SetDefaultButton( bool isDefault )
315{
316    if ( [m_osxView isKindOfClass:[NSButton class]] )
317    {
318        if ( isDefault )
319        {
320            [(NSButton*)m_osxView setKeyEquivalent: @"\r" ];
321            [(NSButton*)m_osxView setKeyEquivalentModifierMask: 0];
322        }
323        else
324            [(NSButton*)m_osxView setKeyEquivalent: @"" ];
325    }
326}
327
328void wxWidgetCocoaImpl::PerformClick()
329{
330    if ([m_osxView isKindOfClass:[NSControl class]])
331        [(NSControl*)m_osxView performClick:nil];
332}
333
334#if wxUSE_BMPBUTTON
335
336wxWidgetImplType* wxWidgetImpl::CreateBitmapButton( wxWindowMac* wxpeer,
337                                                   wxWindowMac* WXUNUSED(parent),
338                                                   wxWindowID winid,
339                                                   const wxBitmap& bitmap,
340                                                   const wxPoint& pos,
341                                                   const wxSize& size,
342                                                   long style,
343                                                   long WXUNUSED(extraStyle))
344{
345    NSRect r = wxOSXGetFrameForControl( wxpeer, pos , size ) ;
346    wxNSButton* v = [[wxNSButton alloc] initWithFrame:r];
347
348    SetBezelStyleFromBorderFlags(v, style, winid, wxString(), bitmap);
349
350    if (bitmap.IsOk())
351        [v setImage:bitmap.GetNSImage() ];
352
353    [v setButtonType:NSMomentaryPushInButton];
354    wxWidgetCocoaImpl* c = new wxButtonCocoaImpl( wxpeer, v );
355    return c;
356}
357
358#endif // wxUSE_BMPBUTTON
359
360//
361// wxDisclosureButton implementation
362//
363
364@interface wxDisclosureNSButton : NSButton
365{
366
367    BOOL isOpen;
368}
369
370- (void) updateImage;
371
372- (void) toggle;
373
374+ (NSImage *)rotateImage: (NSImage *)image;
375
376@end
377
378static const char * disc_triangle_xpm[] = {
379"10 9 4 1",
380"   c None",
381".  c #737373",
382"+  c #989898",
383"-  c #c6c6c6",
384" .-       ",
385" ..+-     ",
386" ....+    ",
387" ......-  ",
388" .......- ",
389" ......-  ",
390" ....+    ",
391" ..+-     ",
392" .-       ",
393};
394
395@implementation wxDisclosureNSButton
396
397+ (void)initialize
398{
399    static BOOL initialized = NO;
400    if (!initialized)
401    {
402        initialized = YES;
403        wxOSXCocoaClassAddWXMethods( self );
404    }
405}
406
407- (id) initWithFrame:(NSRect) frame
408{
409    self = [super initWithFrame:frame];
410    isOpen = NO;
411    [self setImagePosition:NSImageLeft];
412    [self updateImage];
413    return self;
414}
415
416- (int) intValue
417{
418    return isOpen ? 1 : 0;
419}
420
421- (void) setIntValue: (int) v
422{
423    isOpen = ( v != 0 );
424    [self updateImage];
425}
426
427- (void) toggle
428{
429    isOpen = !isOpen;
430    [self updateImage];
431}
432
433wxCFRef<NSImage*> downArray ;
434
435- (void) updateImage
436{
437    static wxBitmap trianglebm(disc_triangle_xpm);
438    if ( downArray.get() == NULL )
439    {
440        downArray.reset( [[wxDisclosureNSButton rotateImage:trianglebm.GetNSImage()] retain] );
441    }
442
443    if ( isOpen )
444        [self setImage:(NSImage*)downArray.get()];
445    else
446        [self setImage:trianglebm.GetNSImage()];
447}
448
449+ (NSImage *)rotateImage: (NSImage *)image
450{
451    NSSize imageSize = [image size];
452    NSSize newImageSize = NSMakeSize(imageSize.height, imageSize.width);
453    NSImage* newImage = [[NSImage alloc] initWithSize: newImageSize];
454
455    [newImage lockFocus];
456
457    NSAffineTransform* tm = [NSAffineTransform transform];
458    [tm translateXBy:newImageSize.width/2 yBy:newImageSize.height/2];
459    [tm rotateByDegrees:-90];
460    [tm translateXBy:-newImageSize.width/2 yBy:-newImageSize.height/2];
461    [tm concat];
462
463
464    [image drawInRect:NSMakeRect(0,0,newImageSize.width, newImageSize.height)
465        fromRect:NSZeroRect operation:NSCompositeCopy fraction:1.0];
466
467    [newImage unlockFocus];
468    return [newImage autorelease];
469}
470
471@end
472
473class wxDisclosureTriangleCocoaImpl : public wxWidgetCocoaImpl
474{
475public :
476    wxDisclosureTriangleCocoaImpl(wxWindowMac* peer , WXWidget w) :
477        wxWidgetCocoaImpl(peer, w)
478    {
479    }
480
481    ~wxDisclosureTriangleCocoaImpl()
482    {
483    }
484
485    virtual void controlAction(WXWidget slf, void* _cmd, void *sender)
486    {
487        wxDisclosureNSButton* db = (wxDisclosureNSButton*)m_osxView;
488        [db toggle];
489        wxWidgetCocoaImpl::controlAction(slf, _cmd, sender );
490    }
491};
492
493wxWidgetImplType* wxWidgetImpl::CreateDisclosureTriangle( wxWindowMac* wxpeer,
494                                    wxWindowMac* WXUNUSED(parent),
495                                    wxWindowID winid,
496                                    const wxString& label,
497                                    const wxPoint& pos,
498                                    const wxSize& size,
499                                    long style,
500                                    long WXUNUSED(extraStyle))
501{
502    NSRect r = wxOSXGetFrameForControl( wxpeer, pos , size ) ;
503    wxDisclosureNSButton* v = [[wxDisclosureNSButton alloc] initWithFrame:r];
504    if ( !label.empty() )
505        [v setTitle:wxCFStringRef(label).AsNSString()];
506
507    SetBezelStyleFromBorderFlags(v, style, winid, label);
508
509    return new wxDisclosureTriangleCocoaImpl( wxpeer, v );
510}
511