Fix bezel used for bitmap buttons in wxOSX/Cocoa.
[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 bezel style depending on the wxBORDER_XXX flags specified by the style
202 // and also accounting for the label (bezels are different for multiline
203 // buttons and normal ones) and the ID (special bezel is used for help button).
204 //
205 // This is extern because it's also used in src/osx/cocoa/tglbtn.mm.
206 extern "C"
207 void
208 SetBezelStyleFromBorderFlags(NSButton *v,
209                              long style,
210                              wxWindowID winid,
211                              const wxString& label = wxString(),
212                              const wxBitmap& bitmap = wxBitmap())
213 {
214     // We can't display a custom label inside a button with help bezel style so
215     // we only use it if we are using the default label. wxButton itself checks
216     // if the label is just "Help" in which case it discards it and passes us
217     // an empty string.
218     if ( winid == wxID_HELP && label.empty() )
219     {
220         [v setBezelStyle:NSHelpButtonBezelStyle];
221     }
222     else
223     {
224         // We can't use rounded bezel styles neither for multiline buttons nor
225         // for buttons containing (big) icons as they are only meant to be used
226         // at certain sizes, so the style used depends on whether the label is
227         // single or multi line.
228         const bool
229             isSimpleText = (label.find_first_of("\n\r") == wxString::npos)
230                                 && (!bitmap.IsOk() || bitmap.GetHeight() < 20);
231
232         NSBezelStyle bezel;
233         switch ( style & wxBORDER_MASK )
234         {
235             case wxBORDER_NONE:
236                 bezel = NSShadowlessSquareBezelStyle;
237                 [v setBordered:NO];
238                 break;
239
240             case wxBORDER_SIMPLE:
241                 bezel = NSShadowlessSquareBezelStyle;
242                 break;
243
244             case wxBORDER_SUNKEN:
245                 bezel = isSimpleText ? NSTexturedRoundedBezelStyle
246                                      : NSSmallSquareBezelStyle;
247                 break;
248
249             default:
250                 wxFAIL_MSG( "Unknown border style" );
251                 // fall through
252
253             case 0:
254             case wxBORDER_STATIC:
255             case wxBORDER_RAISED:
256             case wxBORDER_THEME:
257                 bezel = isSimpleText ? NSRoundedBezelStyle
258                                      : NSRegularSquareBezelStyle;
259                 break;
260         }
261
262         [v setBezelStyle:bezel];
263     }
264 }
265
266 // Set the keyboard accelerator key from the label (e.g. "Click &Me")
267 void wxButton::OSXUpdateAfterLabelChange(const wxString& label)
268 {
269     wxButtonCocoaImpl *impl = static_cast<wxButtonCocoaImpl*>(GetPeer());
270
271     // Update the bezel style as may be necessary if our new label is multi
272     // line while the old one wasn't (or vice versa).
273     SetBezelStyleFromBorderFlags(impl->GetNSButton(),
274                                  GetWindowStyle(),
275                                  GetId(),
276                                  label);
277
278
279     // Skip setting the accelerator for the default buttons as this would
280     // overwrite the default "Enter" which should be preserved.
281     wxTopLevelWindow * const
282         tlw = wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow);
283     if ( tlw )
284     {
285         if ( tlw->GetDefaultItem() == this )
286             return;
287     }
288
289     impl->SetAcceleratorFromLabel(label);
290 }
291
292
293 wxWidgetImplType* wxWidgetImpl::CreateButton( wxWindowMac* wxpeer,
294                                     wxWindowMac* WXUNUSED(parent),
295                                     wxWindowID winid,
296                                     const wxString& label,
297                                     const wxPoint& pos,
298                                     const wxSize& size,
299                                     long style,
300                                     long WXUNUSED(extraStyle))
301 {
302     NSRect r = wxOSXGetFrameForControl( wxpeer, pos , size ) ;
303     wxNSButton* v = [[wxNSButton alloc] initWithFrame:r];
304
305     SetBezelStyleFromBorderFlags(v, style, winid, label);
306
307     [v setButtonType:NSMomentaryPushInButton];
308     wxButtonCocoaImpl* const impl = new wxButtonCocoaImpl( wxpeer, v );
309     impl->SetAcceleratorFromLabel(label);
310     return impl;
311 }
312
313 void wxWidgetCocoaImpl::SetDefaultButton( bool isDefault )
314 {
315     if ( [m_osxView isKindOfClass:[NSButton class]] )
316     {
317         if ( isDefault )
318         {
319             [(NSButton*)m_osxView setKeyEquivalent: @"\r" ];
320             [(NSButton*)m_osxView setKeyEquivalentModifierMask: 0];
321         }
322         else
323             [(NSButton*)m_osxView setKeyEquivalent: @"" ];
324     }
325 }
326
327 void wxWidgetCocoaImpl::PerformClick()
328 {
329     if ([m_osxView isKindOfClass:[NSControl class]])
330         [(NSControl*)m_osxView performClick:nil];
331 }
332
333 #if wxUSE_BMPBUTTON
334
335 wxWidgetImplType* wxWidgetImpl::CreateBitmapButton( wxWindowMac* wxpeer,
336                                                    wxWindowMac* WXUNUSED(parent),
337                                                    wxWindowID winid,
338                                                    const wxBitmap& bitmap,
339                                                    const wxPoint& pos,
340                                                    const wxSize& size,
341                                                    long style,
342                                                    long WXUNUSED(extraStyle))
343 {
344     NSRect r = wxOSXGetFrameForControl( wxpeer, pos , size ) ;
345     wxNSButton* v = [[wxNSButton alloc] initWithFrame:r];
346
347     SetBezelStyleFromBorderFlags(v, style, winid, wxString(), bitmap);
348
349     if (bitmap.IsOk())
350         [v setImage:bitmap.GetNSImage() ];
351
352     [v setButtonType:NSMomentaryPushInButton];
353     wxWidgetCocoaImpl* c = new wxButtonCocoaImpl( wxpeer, v );
354     return c;
355 }
356
357 #endif // wxUSE_BMPBUTTON
358
359 //
360 // wxDisclosureButton implementation
361 //
362
363 @interface wxDisclosureNSButton : NSButton
364 {
365
366     BOOL isOpen;
367 }
368
369 - (void) updateImage;
370
371 - (void) toggle;
372
373 + (NSImage *)rotateImage: (NSImage *)image;
374
375 @end
376
377 static const char * disc_triangle_xpm[] = {
378 "10 9 4 1",
379 "   c None",
380 ".  c #737373",
381 "+  c #989898",
382 "-  c #c6c6c6",
383 " .-       ",
384 " ..+-     ",
385 " ....+    ",
386 " ......-  ",
387 " .......- ",
388 " ......-  ",
389 " ....+    ",
390 " ..+-     ",
391 " .-       ",
392 };
393
394 @implementation wxDisclosureNSButton
395
396 + (void)initialize
397 {
398     static BOOL initialized = NO;
399     if (!initialized)
400     {
401         initialized = YES;
402         wxOSXCocoaClassAddWXMethods( self );
403     }
404 }
405
406 - (id) initWithFrame:(NSRect) frame
407 {
408     self = [super initWithFrame:frame];
409     isOpen = NO;
410     [self setImagePosition:NSImageLeft];
411     [self updateImage];
412     return self;
413 }
414
415 - (int) intValue
416 {
417     return isOpen ? 1 : 0;
418 }
419
420 - (void) setIntValue: (int) v
421 {
422     isOpen = ( v != 0 );
423     [self updateImage];
424 }
425
426 - (void) toggle
427 {
428     isOpen = !isOpen;
429     [self updateImage];
430 }
431
432 wxCFRef<NSImage*> downArray ;
433
434 - (void) updateImage
435 {
436     static wxBitmap trianglebm(disc_triangle_xpm);
437     if ( downArray.get() == NULL )
438     {
439         downArray.reset( [[wxDisclosureNSButton rotateImage:trianglebm.GetNSImage()] retain] );
440     }
441
442     if ( isOpen )
443         [self setImage:(NSImage*)downArray.get()];
444     else
445         [self setImage:trianglebm.GetNSImage()];
446 }
447
448 + (NSImage *)rotateImage: (NSImage *)image
449 {
450     NSSize imageSize = [image size];
451     NSSize newImageSize = NSMakeSize(imageSize.height, imageSize.width);
452     NSImage* newImage = [[NSImage alloc] initWithSize: newImageSize];
453
454     [newImage lockFocus];
455
456     NSAffineTransform* tm = [NSAffineTransform transform];
457     [tm translateXBy:newImageSize.width/2 yBy:newImageSize.height/2];
458     [tm rotateByDegrees:-90];
459     [tm translateXBy:-newImageSize.width/2 yBy:-newImageSize.height/2];
460     [tm concat];
461
462
463     [image drawInRect:NSMakeRect(0,0,newImageSize.width, newImageSize.height)
464         fromRect:NSZeroRect operation:NSCompositeCopy fraction:1.0];
465
466     [newImage unlockFocus];
467     return [newImage autorelease];
468 }
469
470 @end
471
472 class wxDisclosureTriangleCocoaImpl : public wxWidgetCocoaImpl
473 {
474 public :
475     wxDisclosureTriangleCocoaImpl(wxWindowMac* peer , WXWidget w) :
476         wxWidgetCocoaImpl(peer, w)
477     {
478     }
479
480     ~wxDisclosureTriangleCocoaImpl()
481     {
482     }
483
484     virtual void controlAction(WXWidget slf, void* _cmd, void *sender)
485     {
486         wxDisclosureNSButton* db = (wxDisclosureNSButton*)m_osxView;
487         [db toggle];
488         wxWidgetCocoaImpl::controlAction(slf, _cmd, sender );
489     }
490 };
491
492 wxWidgetImplType* wxWidgetImpl::CreateDisclosureTriangle( wxWindowMac* wxpeer,
493                                     wxWindowMac* WXUNUSED(parent),
494                                     wxWindowID winid,
495                                     const wxString& label,
496                                     const wxPoint& pos,
497                                     const wxSize& size,
498                                     long style,
499                                     long WXUNUSED(extraStyle))
500 {
501     NSRect r = wxOSXGetFrameForControl( wxpeer, pos , size ) ;
502     wxDisclosureNSButton* v = [[wxDisclosureNSButton alloc] initWithFrame:r];
503     if ( !label.empty() )
504         [v setTitle:wxCFStringRef(label).AsNSString()];
505
506     SetBezelStyleFromBorderFlags(v, style, winid, label);
507
508     return new wxDisclosureTriangleCocoaImpl( wxpeer, v );
509 }