Fix crash when auto-sizing a wxDataViewCtrl column.
[wxWidgets.git] / src / osx / cocoa / combobox.mm
1 /////////////////////////////////////////////////////////////////////////////
2 // Name:        src/osx/cocoa/combobox.mm
3 // Purpose:     wxChoice
4 // Author:      Stefan Csomor
5 // Modified by:
6 // Created:     1998-01-01
7 // Copyright:   (c) Stefan Csomor
8 // Licence:     wxWindows licence
9 /////////////////////////////////////////////////////////////////////////////
10
11 #include "wx/wxprec.h"
12
13 #if wxUSE_COMBOBOX
14
15 #include "wx/combobox.h"
16 #include "wx/evtloop.h"
17
18 #ifndef WX_PRECOMP
19     #include "wx/menu.h"
20     #include "wx/dcclient.h"
21 #endif
22
23 #include "wx/osx/cocoa/private/textimpl.h"
24
25 // work in progress
26
27 @interface wxNSTableDataSource : NSObject wxOSX_10_6_AND_LATER(<NSComboBoxDataSource>)
28 {
29     wxNSComboBoxControl* impl;
30 }
31
32 - (NSInteger)numberOfItemsInComboBox:(NSComboBox *)aComboBox;
33 - (id)comboBox:(NSComboBox *)aComboBox objectValueForItemAtIndex:(NSInteger)index;
34
35 @end
36
37
38 @implementation wxNSComboBox
39
40 + (void)initialize
41 {
42     static BOOL initialized = NO;
43     if (!initialized) 
44     {
45         initialized = YES;
46         wxOSXCocoaClassAddWXMethods( self );
47     }
48 }
49
50 - (void) dealloc
51 {
52     [fieldEditor release];
53     [super dealloc];
54 }
55
56 // Over-riding NSComboBox onKeyDown method doesn't work for key events.
57 // Ensure that we can use our own wxNSTextFieldEditor to catch key events.
58 // See windowWillReturnFieldEditor in nonownedwnd.mm.
59 // Key events will be caught and handled via wxNSTextFieldEditor onkey...
60 // methods in textctrl.mm.
61
62 - (void) setFieldEditor:(wxNSTextFieldEditor*) editor
63 {
64     if ( editor != fieldEditor )
65     {
66         [editor retain];
67         [fieldEditor release];
68         fieldEditor = editor;
69     }
70 }
71
72 - (wxNSTextFieldEditor*) fieldEditor
73 {
74     return fieldEditor;
75 }
76
77 - (void)controlTextDidChange:(NSNotification *)aNotification
78 {
79     wxUnusedVar(aNotification);
80     wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( self );
81     if ( impl && impl->ShouldSendEvents() )
82     {
83         wxWindow* wxpeer = (wxWindow*) impl->GetWXPeer();
84         if ( wxpeer ) {
85             wxCommandEvent event(wxEVT_TEXT, wxpeer->GetId());
86             event.SetEventObject( wxpeer );
87             event.SetString( static_cast<wxComboBox*>(wxpeer)->GetValue() );
88             wxpeer->HandleWindowEvent( event );
89         }
90     }
91 }
92
93 - (void)comboBoxSelectionDidChange:(NSNotification *)notification
94 {
95     wxUnusedVar(notification);
96     wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( self );
97     if ( impl && impl->ShouldSendEvents())
98     {
99         wxComboBox* wxpeer = static_cast<wxComboBox*>(impl->GetWXPeer());
100         if ( wxpeer ) {
101             const int sel = wxpeer->GetSelection();
102
103             wxCommandEvent event(wxEVT_COMBOBOX, wxpeer->GetId());
104             event.SetEventObject( wxpeer );
105             event.SetInt( sel );
106             event.SetString( wxpeer->GetString(sel) );
107             // For some reason, wxComboBox::GetValue will not return the newly selected item 
108             // while we're inside this callback, so use AddPendingEvent to make sure
109             // GetValue() returns the right value.
110
111             wxpeer->GetEventHandler()->AddPendingEvent( event );
112
113         }
114     }
115 }
116 @end
117
118 wxNSComboBoxControl::wxNSComboBoxControl( wxComboBox *wxPeer, WXWidget w )
119     : wxNSTextFieldControl(wxPeer, wxPeer, w)
120 {
121     m_comboBox = (NSComboBox*)w;
122 }
123
124 wxNSComboBoxControl::~wxNSComboBoxControl()
125 {
126 }
127
128 void wxNSComboBoxControl::mouseEvent(WX_NSEvent event, WXWidget slf, void *_cmd)
129 {
130     // NSComboBox has its own event loop, which reacts very badly to our synthetic
131     // events used to signal when a wxEvent is posted, so during that time we switch
132     // the wxEventLoop::WakeUp implementation to a lower-level version
133     
134     bool reset = false;
135     wxEventLoop* const loop = (wxEventLoop*) wxEventLoopBase::GetActive();
136
137     if ( loop != NULL && [event type] == NSLeftMouseDown )
138     {
139         reset = true;
140         loop->OSXUseLowLevelWakeup(true);
141     }
142     
143     wxOSX_EventHandlerPtr superimpl = (wxOSX_EventHandlerPtr) [[slf superclass] instanceMethodForSelector:(SEL)_cmd];
144     superimpl(slf, (SEL)_cmd, event);
145  
146     if ( reset )
147     {
148         loop->OSXUseLowLevelWakeup(false);
149     }
150 }
151
152 int wxNSComboBoxControl::GetSelectedItem() const
153 {
154     return [m_comboBox indexOfSelectedItem];
155 }
156
157 void wxNSComboBoxControl::SetSelectedItem(int item)
158 {
159     SendEvents(false);
160
161     if ( item != wxNOT_FOUND )
162     {
163         wxASSERT_MSG( item >= 0 && item < [m_comboBox numberOfItems],
164                       "Inavlid item index." );
165         [m_comboBox selectItemAtIndex: item];
166     }
167     else // remove current selection (if we have any)
168     {
169         const int sel = GetSelectedItem();
170         if ( sel != wxNOT_FOUND )
171             [m_comboBox deselectItemAtIndex:sel];
172     }
173
174     SendEvents(true);
175 }
176
177 int wxNSComboBoxControl::GetNumberOfItems() const
178 {
179     return [m_comboBox numberOfItems];
180 }
181
182 void wxNSComboBoxControl::InsertItem(int pos, const wxString& item)
183 {
184     [m_comboBox insertItemWithObjectValue:wxCFStringRef( item , m_wxPeer->GetFont().GetEncoding() ).AsNSString() atIndex:pos];
185 }
186
187 void wxNSComboBoxControl::RemoveItem(int pos)
188 {
189     SendEvents(false);
190     [m_comboBox removeItemAtIndex:pos];
191     SendEvents(true);
192 }
193
194 void wxNSComboBoxControl::Clear()
195 {
196     SendEvents(false);
197     [m_comboBox removeAllItems];
198     [m_comboBox setStringValue:@""];
199     SendEvents(true);
200 }
201
202 wxString wxNSComboBoxControl::GetStringAtIndex(int pos) const
203 {
204     return wxCFStringRef::AsString([m_comboBox itemObjectValueAtIndex:pos], m_wxPeer->GetFont().GetEncoding());
205 }
206
207 int wxNSComboBoxControl::FindString(const wxString& text) const
208 {
209     NSInteger nsresult = [m_comboBox indexOfItemWithObjectValue:wxCFStringRef( text , m_wxPeer->GetFont().GetEncoding() ).AsNSString()];
210
211     int result;
212     if (nsresult == NSNotFound)
213         result = wxNOT_FOUND;
214     else
215         result = (int) nsresult;
216     return result;
217 }
218
219 void wxNSComboBoxControl::Popup()
220 {
221     id ax = NSAccessibilityUnignoredDescendant(m_comboBox);
222     [ax accessibilitySetValue: [NSNumber numberWithBool: YES] forAttribute: NSAccessibilityExpandedAttribute];
223 }
224
225 void wxNSComboBoxControl::Dismiss()
226 {
227     id ax = NSAccessibilityUnignoredDescendant(m_comboBox);
228     [ax accessibilitySetValue: [NSNumber numberWithBool: NO] forAttribute: NSAccessibilityExpandedAttribute];
229 }
230
231 void wxNSComboBoxControl::SetEditable(bool editable)
232 {
233     // TODO: unfortunately this does not work, setEditable just means the same as CB_READONLY
234     // I don't see a way to access the text field directly
235     
236     // Behavior NONE <- SELECTECTABLE
237     [m_comboBox setEditable:editable];
238 }
239
240 wxWidgetImplType* wxWidgetImpl::CreateComboBox( wxComboBox* wxpeer, 
241                                     wxWindowMac* WXUNUSED(parent), 
242                                     wxWindowID WXUNUSED(id), 
243                                     wxMenu* WXUNUSED(menu),
244                                     const wxPoint& pos, 
245                                     const wxSize& size,
246                                     long style, 
247                                     long WXUNUSED(extraStyle))
248 {
249     NSRect r = wxOSXGetFrameForControl( wxpeer, pos , size ) ;
250     wxNSComboBox* v = [[wxNSComboBox alloc] initWithFrame:r];
251     if (style & wxCB_READONLY)
252         [v setEditable:NO];
253     wxNSComboBoxControl* c = new wxNSComboBoxControl( wxpeer, v );
254     return c;
255 }
256
257 wxSize wxComboBox::DoGetBestSize() const
258 {
259     int lbWidth = GetCount() > 0 ? 20 : 100;  // some defaults
260     wxSize baseSize = wxWindow::DoGetBestSize();
261     int lbHeight = baseSize.y;
262     int wLine;
263     
264     {
265         wxClientDC dc(const_cast<wxComboBox*>(this));
266         
267         // Find the widest line
268         for(unsigned int i = 0; i < GetCount(); i++)
269         {
270             wxString str(GetString(i));
271             
272             wxCoord width, height ;
273             dc.GetTextExtent( str , &width, &height);
274             wLine = width ;
275             
276             lbWidth = wxMax( lbWidth, wLine ) ;
277         }
278         
279         // Add room for the popup arrow
280         lbWidth += 2 * lbHeight ;
281     }
282     
283     return wxSize( lbWidth, lbHeight );
284 }
285
286 #endif // wxUSE_COMBOBOX