support multi-line labels, add different native styles for buttons as we did for...
[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() && [GetNSButton() bezelStyle] == NSRoundedBezelStyle )
99             [GetNSButton() setBezelStyle:NSRegularSquareBezelStyle ];
100
101         wxWidgetCocoaImpl::SetBitmap(bitmap);
102     }
103
104 #if wxUSE_MARKUP
105     virtual void SetLabelMarkup(const wxString& markup)
106     {
107         wxMarkupToAttrString toAttr(GetWXPeer(), markup);
108         NSMutableAttributedString *attrString = toAttr.GetNSAttributedString();
109
110         // Button text is always centered.
111         NSMutableParagraphStyle *
112             paragraphStyle = [[NSMutableParagraphStyle alloc] init];
113         [paragraphStyle setAlignment: NSCenterTextAlignment];
114         [attrString addAttribute:NSParagraphStyleAttributeName
115                     value:paragraphStyle
116                     range:NSMakeRange(0, [attrString length])];
117         [paragraphStyle release];
118
119         [GetNSButton() setAttributedTitle:attrString];
120     }
121 #endif // wxUSE_MARKUP
122
123     void SetPressedBitmap( const wxBitmap& bitmap )
124     {
125         NSButton* button = GetNSButton();
126         [button setAlternateImage: bitmap.GetNSImage()];
127         [button setButtonType:NSMomentaryChangeButton];
128     }
129
130     void GetLayoutInset(int &left , int &top , int &right, int &bottom) const
131     {
132         left = top = right = bottom = 0;
133         NSControlSize size = NSRegularControlSize;
134         if ( [m_osxView respondsToSelector:@selector(controlSize)] )
135             size = [m_osxView controlSize];
136         else if ([m_osxView respondsToSelector:@selector(cell)])
137         {
138             id cell = [(id)m_osxView cell];
139             if ([cell respondsToSelector:@selector(controlSize)])
140                 size = [cell controlSize];
141         }
142         
143         if ( [GetNSButton() bezelStyle] == NSRoundedBezelStyle )
144         {
145             switch( size )
146             {
147                 case NSRegularControlSize:
148                     left = right = 6;
149                     top = 4;
150                     bottom = 8;
151                     break;
152                 case NSSmallControlSize:
153                     left = right = 5;
154                     top = 4;
155                     bottom = 7;
156                     break;
157                 case NSMiniControlSize:
158                     left = right = 1;
159                     top = 0;
160                     bottom = 2;
161                     break;
162             }
163         }
164     }
165     
166     
167 private:
168     NSButton *GetNSButton() const
169     {
170         wxASSERT( [m_osxView isKindOfClass:[NSButton class]] );
171
172         return static_cast<NSButton *>(m_osxView);
173     }
174 };
175
176 } // anonymous namespace
177
178 extern "C" void SetBezelStyleFromBorderFlags(NSButton *v, long style);
179     
180 // set bezel style depending on the wxBORDER_XXX flags specified by the style
181 void SetBezelStyleFromBorderFlags(NSButton *v, long style)
182 {
183     if ( style & wxBORDER_NONE )
184     {
185         [v setBezelStyle:NSShadowlessSquareBezelStyle];
186         [v setBordered:NO];
187     }
188     else // we do have a border
189     {
190         // see trac #11128 for a thorough discussion
191         if ( (style & wxBORDER_MASK) == wxBORDER_RAISED )
192             [v setBezelStyle:NSRegularSquareBezelStyle];
193         else if ( (style & wxBORDER_MASK) == wxBORDER_SUNKEN )
194             [v setBezelStyle:NSSmallSquareBezelStyle];
195         else if ( (style & wxBORDER_MASK) == wxBORDER_SIMPLE )
196             [v setBezelStyle:NSShadowlessSquareBezelStyle];
197         else
198             [v setBezelStyle:NSRegularSquareBezelStyle];
199     }
200 }
201
202
203 wxWidgetImplType* wxWidgetImpl::CreateButton( wxWindowMac* wxpeer,
204                                     wxWindowMac* WXUNUSED(parent),
205                                     wxWindowID id,
206                                     const wxString& label,
207                                     const wxPoint& pos,
208                                     const wxSize& size,
209                                     long style,
210                                     long WXUNUSED(extraStyle))
211 {
212     NSRect r = wxOSXGetFrameForControl( wxpeer, pos , size ) ;
213     wxNSButton* v = [[wxNSButton alloc] initWithFrame:r];
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 ( id == wxID_HELP && label.empty() )
220     {
221         [v setBezelStyle:NSHelpButtonBezelStyle];
222     }
223     else
224     {
225         if ( style & wxBORDER_NONE )
226         {
227             [v setBezelStyle:NSShadowlessSquareBezelStyle];
228             [v setBordered:NO];
229         }
230         else 
231         {
232             // the following styles only exist for certain sizes, so avoid them for
233             // multi-line
234             if ( label.Find('\n' ) == wxNOT_FOUND && label.Find('\r' ) == wxNOT_FOUND)
235             {
236                 if ( (style & wxBORDER_MASK) == wxBORDER_RAISED )
237                     [v setBezelStyle:NSRoundedBezelStyle];
238                 else if ( (style & wxBORDER_MASK) == wxBORDER_SUNKEN )
239                     [v setBezelStyle:NSTexturedRoundedBezelStyle];
240                 else if ( (style & wxBORDER_MASK) == wxBORDER_SIMPLE )
241                     [v setBezelStyle:NSShadowlessSquareBezelStyle];
242                 else
243                     [v setBezelStyle:NSRoundedBezelStyle];
244             }
245             else 
246             {
247                 if ( (style & wxBORDER_MASK) == wxBORDER_RAISED )
248                     [v setBezelStyle:NSRegularSquareBezelStyle];
249                 else if ( (style & wxBORDER_MASK) == wxBORDER_SUNKEN )
250                     [v setBezelStyle:NSSmallSquareBezelStyle];
251                 else if ( (style & wxBORDER_MASK) == wxBORDER_SIMPLE )
252                     [v setBezelStyle:NSShadowlessSquareBezelStyle];
253                 else
254                     [v setBezelStyle:NSRegularSquareBezelStyle];
255             }
256
257         }
258     }
259     
260     [v setButtonType:NSMomentaryPushInButton];
261     return new wxButtonCocoaImpl( wxpeer, v );
262 }
263
264 void wxWidgetCocoaImpl::SetDefaultButton( bool isDefault )
265 {
266     if ( [m_osxView isKindOfClass:[NSButton class]] )
267     {
268         if ( isDefault )
269             [(NSButton*)m_osxView setKeyEquivalent: @"\r" ];
270         else
271             [(NSButton*)m_osxView setKeyEquivalent: @"" ];
272     }
273 }
274
275 void wxWidgetCocoaImpl::PerformClick()
276 {
277     if ([m_osxView isKindOfClass:[NSControl class]])
278         [(NSControl*)m_osxView performClick:nil];
279 }
280
281 #if wxUSE_BMPBUTTON
282
283 wxWidgetImplType* wxWidgetImpl::CreateBitmapButton( wxWindowMac* wxpeer,
284                                                    wxWindowMac* WXUNUSED(parent),
285                                                    wxWindowID WXUNUSED(id),
286                                                    const wxBitmap& bitmap,
287                                                    const wxPoint& pos,
288                                                    const wxSize& size,
289                                                    long style,
290                                                    long WXUNUSED(extraStyle))
291 {
292     NSRect r = wxOSXGetFrameForControl( wxpeer, pos , size ) ;
293     wxNSButton* v = [[wxNSButton alloc] initWithFrame:r];
294
295     SetBezelStyleFromBorderFlags(v, style);
296
297     if (bitmap.IsOk())
298         [v setImage:bitmap.GetNSImage() ];
299
300     [v setButtonType:NSMomentaryPushInButton];
301     wxWidgetCocoaImpl* c = new wxButtonCocoaImpl( wxpeer, v );
302     return c;
303 }
304
305 #endif // wxUSE_BMPBUTTON
306
307 //
308 // wxDisclosureButton implementation
309 //
310
311 @interface wxDisclosureNSButton : NSButton
312 {
313
314     BOOL isOpen;
315 }
316
317 - (void) updateImage;
318
319 - (void) toggle;
320
321 + (NSImage *)rotateImage: (NSImage *)image;
322
323 @end
324
325 static const char * disc_triangle_xpm[] = {
326 "10 9 4 1",
327 "   c None",
328 ".  c #737373",
329 "+  c #989898",
330 "-  c #c6c6c6",
331 " .-       ",
332 " ..+-     ",
333 " ....+    ",
334 " ......-  ",
335 " .......- ",
336 " ......-  ",
337 " ....+    ",
338 " ..+-     ",
339 " .-       ",
340 };
341
342 @implementation wxDisclosureNSButton
343
344 + (void)initialize
345 {
346     static BOOL initialized = NO;
347     if (!initialized)
348     {
349         initialized = YES;
350         wxOSXCocoaClassAddWXMethods( self );
351     }
352 }
353
354 - (id) initWithFrame:(NSRect) frame
355 {
356     self = [super initWithFrame:frame];
357     isOpen = NO;
358     [self setImagePosition:NSImageLeft];
359     [self updateImage];
360     return self;
361 }
362
363 - (int) intValue
364 {
365     return isOpen ? 1 : 0;
366 }
367
368 - (void) setIntValue: (int) v
369 {
370     isOpen = ( v != 0 );
371     [self updateImage];
372 }
373
374 - (void) toggle
375 {
376     isOpen = !isOpen;
377     [self updateImage];
378 }
379
380 wxCFRef<NSImage*> downArray ;
381
382 - (void) updateImage
383 {
384     static wxBitmap trianglebm(disc_triangle_xpm);
385     if ( downArray.get() == NULL )
386     {
387         downArray.reset( [[wxDisclosureNSButton rotateImage:trianglebm.GetNSImage()] retain] );
388     }
389
390     if ( isOpen )
391         [self setImage:(NSImage*)downArray.get()];
392     else
393         [self setImage:trianglebm.GetNSImage()];
394 }
395
396 + (NSImage *)rotateImage: (NSImage *)image
397 {
398     NSSize imageSize = [image size];
399     NSSize newImageSize = NSMakeSize(imageSize.height, imageSize.width);
400     NSImage* newImage = [[NSImage alloc] initWithSize: newImageSize];
401
402     [newImage lockFocus];
403
404     NSAffineTransform* tm = [NSAffineTransform transform];
405     [tm translateXBy:newImageSize.width/2 yBy:newImageSize.height/2];
406     [tm rotateByDegrees:-90];
407     [tm translateXBy:-newImageSize.width/2 yBy:-newImageSize.height/2];
408     [tm concat];
409
410
411     [image drawInRect:NSMakeRect(0,0,newImageSize.width, newImageSize.height)
412         fromRect:NSZeroRect operation:NSCompositeCopy fraction:1.0];
413
414     [newImage unlockFocus];
415     return [newImage autorelease];
416 }
417
418 @end
419
420 class wxDisclosureTriangleCocoaImpl : public wxWidgetCocoaImpl
421 {
422 public :
423     wxDisclosureTriangleCocoaImpl(wxWindowMac* peer , WXWidget w) :
424         wxWidgetCocoaImpl(peer, w)
425     {
426     }
427
428     ~wxDisclosureTriangleCocoaImpl()
429     {
430     }
431
432     virtual void controlAction(WXWidget slf, void* _cmd, void *sender)
433     {
434         wxDisclosureNSButton* db = (wxDisclosureNSButton*)m_osxView;
435         [db toggle];
436         wxWidgetCocoaImpl::controlAction(slf, _cmd, sender );
437     }
438 };
439
440 wxWidgetImplType* wxWidgetImpl::CreateDisclosureTriangle( wxWindowMac* wxpeer,
441                                     wxWindowMac* WXUNUSED(parent),
442                                     wxWindowID WXUNUSED(winid),
443                                     const wxString& label,
444                                     const wxPoint& pos,
445                                     const wxSize& size,
446                                     long style,
447                                     long WXUNUSED(extraStyle))
448 {
449     NSRect r = wxOSXGetFrameForControl( wxpeer, pos , size ) ;
450     wxDisclosureNSButton* v = [[wxDisclosureNSButton alloc] initWithFrame:r];
451     if ( !label.empty() )
452         [v setTitle:wxCFStringRef(label).AsNSString()];
453
454     SetBezelStyleFromBorderFlags(v, style);
455
456     return new wxDisclosureTriangleCocoaImpl( wxpeer, v );
457 }