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