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)controlTextDidEndEditing:(NSNotification *) aNotification
94{
95    wxUnusedVar(aNotification);
96    wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( self );
97    if ( impl )
98    {
99        wxNSTextFieldControl* timpl = dynamic_cast<wxNSTextFieldControl*>(impl);
100        if ( timpl )
101            timpl->UpdateInternalSelectionFromEditor(fieldEditor);
102        impl->DoNotifyFocusLost();
103    }
104}
105
106- (void)comboBoxWillPopUp:(NSNotification *)notification
107{
108    wxUnusedVar(notification);
109    wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( self );
110    if( impl && impl->ShouldSendEvents() )
111    {
112        wxComboBox* wxpeer = static_cast<wxComboBox*>(impl->GetWXPeer());
113        if( wxpeer )
114        {
115            wxCommandEvent event(wxEVT_COMBOBOX_DROPDOWN, wxpeer->GetId());
116            event.SetEventObject( wxpeer );
117            wxpeer->GetEventHandler()->ProcessEvent( event );
118        }
119    }
120}
121
122- (void)comboBoxWillDismiss:(NSNotification *)notification
123{
124    wxUnusedVar(notification);
125    wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( self );
126    if( impl && impl->ShouldSendEvents() )
127    {
128        wxComboBox* wxpeer = static_cast<wxComboBox*>(impl->GetWXPeer());
129        if( wxpeer )
130        {
131            wxCommandEvent event(wxEVT_COMBOBOX_CLOSEUP, wxpeer->GetId());
132            event.SetEventObject( wxpeer );
133            wxpeer->GetEventHandler()->ProcessEvent( event );
134        }
135    }
136}
137
138- (void)comboBoxSelectionDidChange:(NSNotification *)notification
139{
140    wxUnusedVar(notification);
141    wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( self );
142    if ( impl && impl->ShouldSendEvents())
143    {
144        wxComboBox* wxpeer = static_cast<wxComboBox*>(impl->GetWXPeer());
145        if ( wxpeer ) {
146            const int sel = wxpeer->GetSelection();
147
148            wxCommandEvent event(wxEVT_COMBOBOX, wxpeer->GetId());
149            event.SetEventObject( wxpeer );
150            event.SetInt( sel );
151            event.SetString( wxpeer->GetString(sel) );
152            // For some reason, wxComboBox::GetValue will not return the newly selected item
153            // while we're inside this callback, so use AddPendingEvent to make sure
154            // GetValue() returns the right value.
155
156            wxpeer->GetEventHandler()->AddPendingEvent( event );
157
158        }
159    }
160}
161@end
162
163wxNSComboBoxControl::wxNSComboBoxControl( wxComboBox *wxPeer, WXWidget w )
164    : wxNSTextFieldControl(wxPeer, wxPeer, w)
165{
166    m_comboBox = (NSComboBox*)w;
167}
168
169wxNSComboBoxControl::~wxNSComboBoxControl()
170{
171}
172
173void wxNSComboBoxControl::mouseEvent(WX_NSEvent event, WXWidget slf, void *_cmd)
174{
175    // NSComboBox has its own event loop, which reacts very badly to our synthetic
176    // events used to signal when a wxEvent is posted, so during that time we switch
177    // the wxEventLoop::WakeUp implementation to a lower-level version
178
179    bool reset = false;
180    wxEventLoop* const loop = (wxEventLoop*) wxEventLoopBase::GetActive();
181
182    if ( loop != NULL && [event type] == NSLeftMouseDown )
183    {
184        reset = true;
185        loop->OSXUseLowLevelWakeup(true);
186    }
187
188    wxOSX_EventHandlerPtr superimpl = (wxOSX_EventHandlerPtr) [[slf superclass] instanceMethodForSelector:(SEL)_cmd];
189    superimpl(slf, (SEL)_cmd, event);
190
191    if ( reset )
192    {
193        loop->OSXUseLowLevelWakeup(false);
194    }
195}
196
197int wxNSComboBoxControl::GetSelectedItem() const
198{
199    return [m_comboBox indexOfSelectedItem];
200}
201
202void wxNSComboBoxControl::SetSelectedItem(int item)
203{
204    SendEvents(false);
205
206    if ( item != wxNOT_FOUND )
207    {
208        wxASSERT_MSG( item >= 0 && item < [m_comboBox numberOfItems],
209                      "Inavlid item index." );
210        [m_comboBox selectItemAtIndex: item];
211    }
212    else // remove current selection (if we have any)
213    {
214        const int sel = GetSelectedItem();
215        if ( sel != wxNOT_FOUND )
216            [m_comboBox deselectItemAtIndex:sel];
217    }
218
219    SendEvents(true);
220}
221
222int wxNSComboBoxControl::GetNumberOfItems() const
223{
224    return [m_comboBox numberOfItems];
225}
226
227void wxNSComboBoxControl::InsertItem(int pos, const wxString& item)
228{
229    [m_comboBox insertItemWithObjectValue:wxCFStringRef( item , m_wxPeer->GetFont().GetEncoding() ).AsNSString() atIndex:pos];
230}
231
232void wxNSComboBoxControl::RemoveItem(int pos)
233{
234    SendEvents(false);
235    [m_comboBox removeItemAtIndex:pos];
236    SendEvents(true);
237}
238
239void wxNSComboBoxControl::Clear()
240{
241    SendEvents(false);
242    [m_comboBox removeAllItems];
243    [m_comboBox setStringValue:@""];
244    SendEvents(true);
245}
246
247wxString wxNSComboBoxControl::GetStringAtIndex(int pos) const
248{
249    return wxCFStringRef::AsString([m_comboBox itemObjectValueAtIndex:pos], m_wxPeer->GetFont().GetEncoding());
250}
251
252int wxNSComboBoxControl::FindString(const wxString& text) const
253{
254    NSInteger nsresult = [m_comboBox indexOfItemWithObjectValue:wxCFStringRef( text , m_wxPeer->GetFont().GetEncoding() ).AsNSString()];
255
256    int result;
257    if (nsresult == NSNotFound)
258        result = wxNOT_FOUND;
259    else
260        result = (int) nsresult;
261    return result;
262}
263
264void wxNSComboBoxControl::Popup()
265{
266    id ax = NSAccessibilityUnignoredDescendant(m_comboBox);
267    [ax accessibilitySetValue: [NSNumber numberWithBool: YES] forAttribute: NSAccessibilityExpandedAttribute];
268}
269
270void wxNSComboBoxControl::Dismiss()
271{
272    id ax = NSAccessibilityUnignoredDescendant(m_comboBox);
273    [ax accessibilitySetValue: [NSNumber numberWithBool: NO] forAttribute: NSAccessibilityExpandedAttribute];
274}
275
276void wxNSComboBoxControl::SetEditable(bool editable)
277{
278    // TODO: unfortunately this does not work, setEditable just means the same as CB_READONLY
279    // I don't see a way to access the text field directly
280
281    // Behavior NONE <- SELECTECTABLE
282    [m_comboBox setEditable:editable];
283}
284
285wxWidgetImplType* wxWidgetImpl::CreateComboBox( wxComboBox* wxpeer,
286                                    wxWindowMac* WXUNUSED(parent),
287                                    wxWindowID WXUNUSED(id),
288                                    wxMenu* WXUNUSED(menu),
289                                    const wxPoint& pos,
290                                    const wxSize& size,
291                                    long style,
292                                    long WXUNUSED(extraStyle))
293{
294    NSRect r = wxOSXGetFrameForControl( wxpeer, pos , size ) ;
295    wxNSComboBox* v = [[wxNSComboBox alloc] initWithFrame:r];
296    [v setNumberOfVisibleItems:13];
297    if (style & wxCB_READONLY)
298        [v setEditable:NO];
299    wxNSComboBoxControl* c = new wxNSComboBoxControl( wxpeer, v );
300    return c;
301}
302
303wxSize wxComboBox::DoGetBestSize() const
304{
305    int lbWidth = GetCount() > 0 ? 20 : 100;  // some defaults
306    wxSize baseSize = wxWindow::DoGetBestSize();
307    int lbHeight = baseSize.y;
308    int wLine;
309
310    {
311        wxClientDC dc(const_cast<wxComboBox*>(this));
312
313        // Find the widest line
314        for(unsigned int i = 0; i < GetCount(); i++)
315        {
316            wxString str(GetString(i));
317
318            wxCoord width, height ;
319            dc.GetTextExtent( str , &width, &height);
320            wLine = width ;
321
322            lbWidth = wxMax( lbWidth, wLine ) ;
323        }
324
325        // Add room for the popup arrow
326        lbWidth += 2 * lbHeight ;
327    }
328
329    return wxSize( lbWidth, lbHeight );
330}
331
332#endif // wxUSE_COMBOBOX
333