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