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