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