Retain/release the NSButtonCell during user action so that if the button
[wxWidgets.git] / src / cocoa / toolbar.mm
1 /////////////////////////////////////////////////////////////////////////////
2 // Name:        src/cocoa/toolbar.mm
3 // Purpose:     wxToolBar
4 // Author:      David Elliott
5 // Modified by:
6 // Created:     2003/08/17
7 // RCS-ID:      $Id$
8 // Copyright:   (c) 2003 David Elliott
9 // Licence:     wxWidgets licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 // ============================================================================
13 // declarations
14 // ============================================================================
15
16 // ----------------------------------------------------------------------------
17 // headers
18 // ----------------------------------------------------------------------------
19
20 // For compilers that support precompilation, includes "wx.h".
21 #include "wx/wxprec.h"
22
23 #if wxUSE_TOOLBAR_NATIVE
24 #ifndef WX_PRECOMP
25     #include "wx/toolbar.h"
26     #include "wx/frame.h"
27     #include "wx/log.h"
28 #endif // WX_PRECOMP
29
30 #include "wx/cocoa/string.h"
31 #include "wx/cocoa/autorelease.h"
32
33 #import <AppKit/NSView.h>
34 #import <AppKit/NSButtonCell.h>
35 #import <AppKit/NSMatrix.h>
36 #import <AppKit/NSImage.h>
37 #import <AppKit/NSEvent.h>
38 #import <AppKit/NSColor.h>
39 #import <AppKit/NSAttributedString.h>
40 #import <AppKit/NSFont.h>
41
42 #include <math.h>
43
44 // ========================================================================
45 // wxToolBarTool
46 // ========================================================================
47 class wxToolBarTool : public wxToolBarToolBase
48 {
49 public:
50     wxToolBarTool(wxToolBar *tbar, int toolid, const wxString& label,
51             const wxBitmap& bitmap1, const wxBitmap& bitmap2,
52             wxItemKind kind, wxObject *clientData,
53             const wxString& shortHelpString, const wxString& longHelpString)
54     :   wxToolBarToolBase(tbar, toolid, label, bitmap1, bitmap2, kind,
55             clientData, shortHelpString, longHelpString)
56     {
57         Init();
58         CreateButtonCell();
59     }
60
61     wxToolBarTool(wxToolBar *tbar, wxControl *control)
62         : wxToolBarToolBase(tbar, control)
63     {
64         Init();
65     }
66     ~wxToolBarTool();
67
68     bool CreateButtonCell();
69
70     // is this a radio button?
71     //
72     // unlike GetKind(), can be called for any kind of tools, not just buttons
73     bool IsRadio() const { return IsButton() && GetKind() == wxITEM_RADIO; }
74
75     NSRect GetFrameRect()
76     {   return m_frameRect; }
77     void SetFrameRect(NSRect frameRect)
78     {   m_frameRect = frameRect; }
79     void DrawTool(NSView *nsview);
80
81     NSButtonCell *GetNSButtonCell()
82     {   return m_cocoaNSButtonCell; }
83 protected:
84     void Init();
85     NSButtonCell *m_cocoaNSButtonCell;
86     NSRect m_frameRect;
87 };
88
89 // ========================================================================
90 // wxToolBarTool
91 // ========================================================================
92 void wxToolBarTool::Init()
93 {
94     m_cocoaNSButtonCell = NULL;
95     m_frameRect = NSZeroRect;
96 }
97
98 void wxToolBar::CocoaToolClickEnded()
99 {
100     wxASSERT(m_mouseDownTool);
101     wxCommandEvent event(wxEVT_COMMAND_MENU_SELECTED, m_mouseDownTool->GetId());
102     InitCommandEvent(event);
103     Command(event);
104 }
105
106 wxToolBarTool::~wxToolBarTool()
107 {
108     [m_cocoaNSButtonCell release];
109 }
110
111 bool wxToolBarTool::CreateButtonCell()
112 {
113     wxAutoNSAutoreleasePool pool;
114
115     NSImage *nsimage = [m_bmpNormal.GetNSImage(true) retain];
116     m_cocoaNSButtonCell = [[NSButtonCell alloc] initTextCell:nil];
117     [m_cocoaNSButtonCell setImage:nsimage];
118     NSAttributedString *attributedTitle = [[NSAttributedString alloc] initWithString:wxNSStringWithWxString(m_label) attributes:[NSDictionary dictionaryWithObject:[NSFont labelFontOfSize:0.0] forKey:NSFontAttributeName]];
119 //    [m_cocoaNSButtonCell setTitle:wxNSStringWithWxString(m_label)];
120     [m_cocoaNSButtonCell setAttributedTitle:[attributedTitle autorelease]];
121
122     // Create an alternate image in the style of NSToolBar
123     if(nsimage)
124     {
125         NSImage *alternateImage = [[NSImage alloc] initWithSize:[nsimage size]];
126         [alternateImage lockFocus];
127         // Paint the entire image with solid black at 50% transparency
128         NSRect imageRect = NSZeroRect;
129         imageRect.size = [alternateImage size];
130         [[NSColor colorWithCalibratedWhite:0.0 alpha:0.5] set];
131         NSRectFill(imageRect);
132         // Composite the original image with the alternate image
133         [nsimage compositeToPoint:NSZeroPoint operation:NSCompositeDestinationAtop];
134         [alternateImage unlockFocus];
135         [m_cocoaNSButtonCell setAlternateImage:alternateImage];
136         [alternateImage release];
137     }
138     [nsimage release];
139
140     NSMutableAttributedString *alternateTitle = [[NSMutableAttributedString alloc] initWithAttributedString:[m_cocoaNSButtonCell attributedTitle]];
141     [alternateTitle applyFontTraits:NSBoldFontMask range:NSMakeRange(0,[alternateTitle length])];
142     [m_cocoaNSButtonCell setAttributedAlternateTitle:alternateTitle];
143     [alternateTitle release];
144
145     // ----
146     [m_cocoaNSButtonCell setImagePosition:NSImageBelow];
147 //    [m_cocoaNSButtonCell setBezeled:NO];
148     [m_cocoaNSButtonCell setButtonType:NSMomentaryChangeButton];
149     [m_cocoaNSButtonCell setBordered:NO];
150 //    [m_cocoaNSButtonCell setHighlightsBy:NSContentsCellMask|NSPushInCellMask];
151 //    [m_cocoaNSButtonCell setShowsStateBy:NSContentsCellMask|NSPushInCellMask];
152     return true;
153 }
154
155 void wxToolBarTool::DrawTool(NSView *nsview)
156 {
157     [m_cocoaNSButtonCell drawWithFrame:m_frameRect inView:nsview];
158 }
159
160 // ========================================================================
161 // wxToolBar
162 // ========================================================================
163 IMPLEMENT_DYNAMIC_CLASS(wxToolBar, wxControl)
164
165 //-----------------------------------------------------------------------------
166 // wxToolBar construction
167 //-----------------------------------------------------------------------------
168
169 void wxToolBar::Init()
170 {
171     m_owningFrame = NULL;
172     m_mouseDownTool = NULL;
173 }
174
175 wxToolBar::~wxToolBar()
176 {
177 }
178
179 bool wxToolBar::Create( wxWindow *parent,
180                         wxWindowID winid,
181                         const wxPoint& pos,
182                         const wxSize& size,
183                         long style,
184                         const wxString& name )
185 {
186     // Call wxControl::Create so we get a wxNonControlNSControl
187     return wxToolBarBase::Create(parent,winid,pos,size,style,wxDefaultValidator,name);
188 }
189
190 wxToolBarToolBase *wxToolBar::CreateTool(int toolid,
191                                          const wxString& text,
192                                          const wxBitmap& bitmap1,
193                                          const wxBitmap& bitmap2,
194                                          wxItemKind kind,
195                                          wxObject *clientData,
196                                          const wxString& shortHelpString,
197                                          const wxString& longHelpString)
198 {
199     return new wxToolBarTool(this, toolid, text, bitmap1, bitmap2, kind,
200                              clientData, shortHelpString, longHelpString);
201 }
202
203 wxToolBarToolBase *wxToolBar::CreateTool(wxControl *control)
204 {
205     return new wxToolBarTool(this, control);
206 }
207
208 void wxToolBar::SetWindowStyleFlag( long style )
209 {
210     wxToolBarBase::SetWindowStyleFlag(style);
211 }
212
213 bool wxToolBar::DoInsertTool(size_t pos, wxToolBarToolBase *toolBase)
214 {
215     return true;
216 }
217
218 bool wxToolBar::DoDeleteTool(size_t WXUNUSED(pos), wxToolBarToolBase *toolBase)
219 {
220     Realize();
221     return true;
222 }
223
224 bool wxToolBar::Cocoa_drawRect(const NSRect &rect)
225 {
226     wxToolBarToolsList::compatibility_iterator node;
227     for(node = m_tools.GetFirst(); node; node = node->GetNext())
228     {
229         wxToolBarTool *tool = static_cast<wxToolBarTool*>(node->GetData());
230         tool->DrawTool(m_cocoaNSView);
231     }
232     return wxToolBarBase::Cocoa_drawRect(rect);
233 }
234
235 static const NSSize toolPadding = { 4.0, 4.0 };
236
237 static NSRect AddToolPadding(NSRect toolRect)
238 {
239         toolRect.origin.x -= toolPadding.width;
240         toolRect.size.width += 2.0*toolPadding.width;
241         toolRect.origin.y -= toolPadding.height;
242         toolRect.size.height += 2.0*toolPadding.height;
243         return toolRect;
244 }
245
246 bool wxToolBar::Cocoa_mouseDragged(WX_NSEvent theEvent)
247 {
248     if(m_mouseDownTool && [m_cocoaNSView
249             mouse:[m_cocoaNSView convertPoint:[theEvent locationInWindow]
250                 fromView:nil]
251             inRect:AddToolPadding(m_mouseDownTool->GetFrameRect())])
252     {
253         NSButtonCell *buttonCell = m_mouseDownTool->GetNSButtonCell();
254         if(buttonCell)
255         {
256             [buttonCell retain];
257             [buttonCell setHighlighted: YES];
258             if([buttonCell trackMouse: theEvent
259                 inRect:AddToolPadding(m_mouseDownTool->GetFrameRect()) ofView:m_cocoaNSView
260                 untilMouseUp:NO])
261             {
262                 CocoaToolClickEnded();
263                 m_mouseDownTool = NULL;
264                 wxLogTrace(wxTRACE_COCOA,wxT("Button was clicked after drag!"));
265             }
266             [buttonCell setHighlighted: NO];
267             [buttonCell release];
268         }
269     }
270     return wxToolBarBase::Cocoa_mouseDragged(theEvent);
271 }
272
273 bool wxToolBar::Cocoa_mouseDown(WX_NSEvent theEvent)
274 {
275     wxToolBarTool *tool = CocoaFindToolForPosition([m_cocoaNSView convertPoint:[theEvent locationInWindow] fromView:nil]);
276     if(tool)
277     {
278         NSButtonCell *buttonCell = tool->GetNSButtonCell();
279         if(buttonCell)
280         {
281             [buttonCell retain];
282             m_mouseDownTool = tool;
283             [buttonCell setHighlighted: YES];
284             if([buttonCell trackMouse: theEvent
285                 inRect:AddToolPadding(tool->GetFrameRect()) ofView:m_cocoaNSView
286                 untilMouseUp:NO])
287             {
288                 CocoaToolClickEnded();
289                 m_mouseDownTool = NULL;
290                 wxLogTrace(wxTRACE_COCOA,wxT("Button was clicked!"));
291             }
292             [buttonCell setHighlighted: NO];
293             [buttonCell release];
294         }
295     }
296     return wxToolBarBase::Cocoa_mouseDown(theEvent);
297 }
298
299 bool wxToolBar::Realize()
300 {
301     wxAutoNSAutoreleasePool pool;
302
303     wxToolBarToolsList::compatibility_iterator node;
304     NSSize totalSize = NSZeroSize;
305     // This is for horizontal, TODO: vertical
306     for(node = m_tools.GetFirst(); node; node = node->GetNext())
307     {
308         wxToolBarTool *tool = static_cast<wxToolBarTool*>(node->GetData());
309         if(tool->IsControl())
310         {
311             totalSize.width = ceil(totalSize.width);
312             wxControl *control = tool->GetControl();
313             wxSize controlSize = control->GetSize();
314             control->SetPosition(wxPoint((wxCoord)totalSize.width,0));
315             totalSize.width += controlSize.x;
316             if(controlSize.y > totalSize.height)
317                 totalSize.height = controlSize.y;
318         }
319         else if(tool->IsSeparator())
320         {
321             totalSize.width += 2.0;
322         }
323         else
324         {
325             NSButtonCell *buttonCell = tool->GetNSButtonCell();
326             NSSize toolSize = [buttonCell cellSize];
327             tool->SetFrameRect(NSMakeRect(totalSize.width+toolPadding.width,toolPadding.height,toolSize.width,toolSize.height));
328             toolSize.width += 2.0*toolPadding.width;
329             toolSize.height += 2.0*toolPadding.height;
330             totalSize.width += toolSize.width;
331             if(toolSize.height > totalSize.height)
332                 totalSize.height = toolSize.height;
333         }
334     }
335     m_bestSize = wxSize((wxCoord)ceil(totalSize.width),(wxCoord)ceil(totalSize.height));
336     if(m_owningFrame)
337         m_owningFrame->UpdateFrameNSView();
338     return true;
339 }
340
341 wxSize wxToolBar::DoGetBestSize() const
342 {
343     return m_bestSize;
344 }
345
346 // ----------------------------------------------------------------------------
347 // wxToolBar tools state
348 // ----------------------------------------------------------------------------
349
350 void wxToolBar::DoEnableTool(wxToolBarToolBase *toolBase, bool enable)
351 {
352 }
353
354 void wxToolBar::DoToggleTool( wxToolBarToolBase *toolBase, bool toggle )
355 {
356 }
357
358 void wxToolBar::DoSetToggle(wxToolBarToolBase * WXUNUSED(tool),
359                             bool WXUNUSED(toggle))
360 {
361 }
362
363 // ----------------------------------------------------------------------------
364 // wxToolBar geometry
365 // ----------------------------------------------------------------------------
366
367 wxToolBarToolBase *wxToolBar::FindToolForPosition(wxCoord x, wxCoord y) const
368 {
369     return NULL;
370 }
371
372 wxToolBarTool *wxToolBar::CocoaFindToolForPosition(const NSPoint& pos) const
373 {
374     wxToolBarToolsList::compatibility_iterator node;
375     for(node = m_tools.GetFirst(); node; node = node->GetNext())
376     {
377         wxToolBarTool *tool = static_cast<wxToolBarTool*>(node->GetData());
378         if(tool->IsControl())
379         {
380             // TODO
381         }
382         else if(tool->IsSeparator())
383         {   // Do nothing
384         }
385         else
386         {
387             if([m_cocoaNSView mouse:pos inRect:AddToolPadding(tool->GetFrameRect())])
388                 return tool;
389         }
390     }
391     return NULL;
392 }
393
394 void wxToolBar::SetMargins( int x, int y )
395 {
396 }
397
398 void wxToolBar::SetToolSeparation( int separation )
399 {
400     m_toolSeparation = separation;
401 }
402
403 void wxToolBar::SetToolShortHelp( int id, const wxString& helpString )
404 {
405 }
406
407 // ----------------------------------------------------------------------------
408 // wxToolBar idle handling
409 // ----------------------------------------------------------------------------
410
411 void wxToolBar::OnInternalIdle()
412 {
413 }
414
415 #endif // wxUSE_TOOLBAR_NATIVE