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/private/available.h"
24#include "wx/osx/cocoa/private/textimpl.h"
25
26// work in progress
27
28@interface wxNSTableDataSource : NSObject <NSComboBoxDataSource>
29{
30    wxNSComboBoxControl* impl;
31}
32
33- (NSInteger)numberOfItemsInComboBox:(NSComboBox *)aComboBox;
34- (id)comboBox:(NSComboBox *)aComboBox objectValueForItemAtIndex:(NSInteger)index;
35
36@end
37
38
39@implementation wxNSComboBox
40
41+ (void)initialize
42{
43    static BOOL initialized = NO;
44    if (!initialized)
45    {
46        initialized = YES;
47        wxOSXCocoaClassAddWXMethods( self );
48    }
49}
50
51- (void) dealloc
52{
53    [fieldEditor release];
54    [super dealloc];
55}
56
57// Over-riding NSComboBox onKeyDown method doesn't work for key events.
58// Ensure that we can use our own wxNSTextFieldEditor to catch key events.
59// See windowWillReturnFieldEditor in nonownedwnd.mm.
60// Key events will be caught and handled via wxNSTextFieldEditor onkey...
61// methods in textctrl.mm.
62
63- (void) setFieldEditor:(wxNSTextFieldEditor*) editor
64{
65    if ( editor != fieldEditor )
66    {
67        [editor retain];
68        [fieldEditor release];
69        fieldEditor = editor;
70    }
71}
72
73- (wxNSTextFieldEditor*) fieldEditor
74{
75    return fieldEditor;
76}
77
78- (void)controlTextDidChange:(NSNotification *)aNotification
79{
80    wxUnusedVar(aNotification);
81    wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( self );
82    if ( impl && impl->ShouldSendEvents() )
83    {
84        wxWindow* wxpeer = (wxWindow*) impl->GetWXPeer();
85        if ( wxpeer ) {
86            wxCommandEvent event(wxEVT_TEXT, wxpeer->GetId());
87            event.SetEventObject( wxpeer );
88            event.SetString( static_cast<wxComboBox*>(wxpeer)->GetValue() );
89            wxpeer->HandleWindowEvent( event );
90        }
91    }
92}
93
94- (void)controlTextDidEndEditing:(NSNotification *) aNotification
95{
96    wxUnusedVar(aNotification);
97    wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( self );
98    if ( impl )
99    {
100        wxNSTextFieldControl* timpl = dynamic_cast<wxNSTextFieldControl*>(impl);
101        if ( timpl )
102            timpl->UpdateInternalSelectionFromEditor(fieldEditor);
103        impl->DoNotifyFocusLost();
104    }
105}
106
107- (void)comboBoxWillPopUp:(NSNotification *)notification
108{
109    wxUnusedVar(notification);
110    wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( self );
111    if( impl && impl->ShouldSendEvents() )
112    {
113        wxComboBox* wxpeer = static_cast<wxComboBox*>(impl->GetWXPeer());
114        if( wxpeer )
115        {
116            wxCommandEvent event(wxEVT_COMBOBOX_DROPDOWN, wxpeer->GetId());
117            event.SetEventObject( wxpeer );
118            wxpeer->GetEventHandler()->ProcessEvent( event );
119        }
120    }
121}
122
123- (void)comboBoxWillDismiss:(NSNotification *)notification
124{
125    wxUnusedVar(notification);
126    wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( self );
127    if( impl && impl->ShouldSendEvents() )
128    {
129        wxComboBox* wxpeer = static_cast<wxComboBox*>(impl->GetWXPeer());
130        if( wxpeer )
131        {
132            wxCommandEvent event(wxEVT_COMBOBOX_CLOSEUP, wxpeer->GetId());
133            event.SetEventObject( wxpeer );
134            wxpeer->GetEventHandler()->ProcessEvent( event );
135        }
136    }
137}
138
139- (void)comboBoxSelectionDidChange:(NSNotification *)notification
140{
141    wxUnusedVar(notification);
142    wxNSComboBoxControl* const
143        impl = (wxNSComboBoxControl* ) wxWidgetImpl::FindFromWXWidget( self );
144    if ( impl && impl->ShouldSendEvents())
145    {
146        wxComboBox* wxpeer = static_cast<wxComboBox*>(impl->GetWXPeer());
147        if ( wxpeer ) {
148            const int sel = wxpeer->GetSelection();
149            const wxString& val = wxpeer->GetString(sel);
150
151            // We need to manually set the new value because at this time it
152            // still contains the old value, but we want GetValue() to return
153            // the new one if it's called from an event handler invoked below.
154            impl->SetStringValue(val);
155
156            wxCommandEvent event(wxEVT_COMBOBOX, wxpeer->GetId());
157            event.SetEventObject( wxpeer );
158            event.SetInt( sel );
159            event.SetString( val );
160            wxpeer->HandleWindowEvent( event );
161
162            wxCommandEvent eventText(wxEVT_TEXT, wxpeer->GetId());
163            eventText.SetEventObject( wxpeer );
164            eventText.SetString( val );
165            wxpeer->HandleWindowEvent( eventText );
166        }
167    }
168}
169
170
171- (BOOL)control:(NSControl*)control textView:(NSTextView*)textView doCommandBySelector:(SEL)commandSelector
172{
173    wxUnusedVar(textView);
174    wxUnusedVar(control);
175
176    BOOL handled = NO;
177
178    // send back key events wx' common code knows how to handle
179
180    wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( self );
181    if ( impl  )
182    {
183        wxWindow* wxpeer = (wxWindow*) impl->GetWXPeer();
184        if ( wxpeer )
185        {
186            if (commandSelector == @selector(insertNewline:))
187            {
188                [textView insertNewlineIgnoringFieldEditor:self];
189                handled = YES;
190            }
191            else if ( commandSelector == @selector(insertTab:))
192            {
193                [textView insertTabIgnoringFieldEditor:self];
194                handled = YES;
195            }
196            else if ( commandSelector == @selector(insertBacktab:))
197            {
198                [textView insertTabIgnoringFieldEditor:self];
199                handled = YES;
200            }
201        }
202    }
203
204    return handled;
205}
206
207@end
208
209wxNSComboBoxControl::wxNSComboBoxControl( wxComboBox *wxPeer, WXWidget w )
210    : wxNSTextFieldControl(wxPeer, wxPeer, w)
211{
212    m_comboBox = (NSComboBox*)w;
213}
214
215wxNSComboBoxControl::~wxNSComboBoxControl()
216{
217}
218
219void wxNSComboBoxControl::mouseEvent(WX_NSEvent event, WXWidget slf, void *_cmd)
220{
221    // NSComboBox has its own event loop, which reacts very badly to our synthetic
222    // events used to signal when a wxEvent is posted, so during that time we switch
223    // the wxEventLoop::WakeUp implementation to a lower-level version
224
225    bool reset = false;
226    wxEventLoop* const loop = (wxEventLoop*) wxEventLoopBase::GetActive();
227
228    if ( loop != NULL && [event type] == NSLeftMouseDown )
229    {
230        reset = true;
231        loop->OSXUseLowLevelWakeup(true);
232    }
233
234    wxOSX_EventHandlerPtr superimpl = (wxOSX_EventHandlerPtr) [[slf superclass] instanceMethodForSelector:(SEL)_cmd];
235    superimpl(slf, (SEL)_cmd, event);
236
237    if ( reset )
238    {
239        loop->OSXUseLowLevelWakeup(false);
240    }
241}
242
243int wxNSComboBoxControl::GetSelectedItem() const
244{
245    return [m_comboBox indexOfSelectedItem];
246}
247
248void wxNSComboBoxControl::SetSelectedItem(int item)
249{
250    SendEvents(false);
251
252    if ( item != wxNOT_FOUND )
253    {
254        wxASSERT_MSG( item >= 0 && item < [m_comboBox numberOfItems],
255                      "Inavlid item index." );
256        [m_comboBox selectItemAtIndex: item];
257    }
258    else // remove current selection (if we have any)
259    {
260        const int sel = GetSelectedItem();
261        if ( sel != wxNOT_FOUND )
262            [m_comboBox deselectItemAtIndex:sel];
263    }
264
265    SendEvents(true);
266}
267
268int wxNSComboBoxControl::GetNumberOfItems() const
269{
270    return [m_comboBox numberOfItems];
271}
272
273void wxNSComboBoxControl::InsertItem(int pos, const wxString& item)
274{
275    wxCFStringRef itemLabel(  item, m_wxPeer->GetFont().GetEncoding() );
276    NSString* const cocoaStr = itemLabel.AsNSString();
277
278    if ( m_wxPeer->HasFlag(wxCB_SORT) )
279    {
280        NSArray* const objectValues = m_comboBox.objectValues;
281
282        pos = [objectValues indexOfObject: cocoaStr
283                            inSortedRange: NSMakeRange(0, objectValues.count)
284                            options: NSBinarySearchingInsertionIndex
285                            usingComparator: ^(id obj1, id obj2)
286                                {
287                                    return [obj1 caseInsensitiveCompare: obj2];
288                                }];
289    }
290
291    [m_comboBox insertItemWithObjectValue:cocoaStr atIndex:pos];
292}
293
294void wxNSComboBoxControl::RemoveItem(int pos)
295{
296    SendEvents(false);
297    [m_comboBox removeItemAtIndex:pos];
298    SendEvents(true);
299}
300
301void wxNSComboBoxControl::Clear()
302{
303    SendEvents(false);
304    [m_comboBox removeAllItems];
305    [m_comboBox setStringValue:@""];
306    SendEvents(true);
307}
308
309wxString wxNSComboBoxControl::GetStringAtIndex(int pos) const
310{
311    return wxCFStringRef::AsString([m_comboBox itemObjectValueAtIndex:pos], m_wxPeer->GetFont().GetEncoding());
312}
313
314int wxNSComboBoxControl::FindString(const wxString& text) const
315{
316    NSInteger nsresult = [m_comboBox indexOfItemWithObjectValue:wxCFStringRef( text , m_wxPeer->GetFont().GetEncoding() ).AsNSString()];
317
318    int result;
319    if (nsresult == NSNotFound)
320        result = wxNOT_FOUND;
321    else
322        result = (int) nsresult;
323    return result;
324}
325
326void wxNSComboBoxControl::Popup()
327{
328    id ax = NSAccessibilityUnignoredDescendant(m_comboBox);
329    [ax setAccessibilityExpanded: YES];
330}
331
332void wxNSComboBoxControl::Dismiss()
333{
334    id ax = NSAccessibilityUnignoredDescendant(m_comboBox);
335    [ax setAccessibilityExpanded: NO];
336}
337
338void wxNSComboBoxControl::SetEditable(bool editable)
339{
340    [m_comboBox setEditable:editable];
341
342    // When the combobox isn't editable, make sure it is still selectable so the text can be copied
343    if ( !editable )
344        [m_comboBox setSelectable:YES];
345}
346
347wxWidgetImplType* wxWidgetImpl::CreateComboBox( wxComboBox* wxpeer,
348                                    wxWindowMac* WXUNUSED(parent),
349                                    wxWindowID WXUNUSED(id),
350                                    wxMenu* WXUNUSED(menu),
351                                    const wxPoint& pos,
352                                    const wxSize& size,
353                                    long style,
354                                    long WXUNUSED(extraStyle))
355{
356    NSRect r = wxOSXGetFrameForControl( wxpeer, pos , size ) ;
357    wxNSComboBox* v = [[wxNSComboBox alloc] initWithFrame:r];
358    if (WX_IS_MACOS_AVAILABLE(10, 13))
359        [v setNumberOfVisibleItems:999];
360    else
361        [v setNumberOfVisibleItems:13];
362
363    wxNSComboBoxControl* c = new wxNSComboBoxControl( wxpeer, v );
364
365    if (style & wxCB_READONLY)
366        c->SetEditable(false);
367
368    return c;
369}
370
371wxSize wxComboBox::DoGetBestSize() const
372{
373    int lbWidth = GetCount() > 0 ? 20 : 100;  // some defaults
374    wxSize baseSize = wxWindow::DoGetBestSize();
375    int lbHeight = baseSize.y;
376    int wLine;
377
378    {
379        wxClientDC dc(const_cast<wxComboBox*>(this));
380
381        // Find the widest line
382        for(unsigned int i = 0; i < GetCount(); i++)
383        {
384            wxString str(GetString(i));
385
386            wxCoord width, height ;
387            dc.GetTextExtent( str , &width, &height);
388            wLine = width ;
389
390            lbWidth = wxMax( lbWidth, wLine ) ;
391        }
392
393        // Add room for the popup arrow
394        lbWidth += 2 * lbHeight ;
395    }
396
397    return wxSize( lbWidth, lbHeight );
398}
399
400#endif // wxUSE_COMBOBOX
401