]> git.saurik.com Git - wxWidgets.git/blob - src/mac/carbon/listbox.cpp
Performance optimization
[wxWidgets.git] / src / mac / carbon / listbox.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/mac/carbon/listbox.cpp
3 // Purpose: wxListBox
4 // Author: Stefan Csomor
5 // Modified by:
6 // Created: 1998-01-01
7 // RCS-ID: $Id$
8 // Copyright: (c) Stefan Csomor
9 // Licence: wxWindows licence
10 ///////////////////////////////////////////////////////////////////////////////
11
12 #include "wx/wxprec.h"
13
14 #if wxUSE_LISTBOX
15
16 #include "wx/listbox.h"
17
18 #ifndef WX_PRECOMP
19 #include "wx/log.h"
20 #include "wx/intl.h"
21 #include "wx/utils.h"
22 #include "wx/settings.h"
23 #include "wx/arrstr.h"
24 #include "wx/dcclient.h"
25 #endif
26
27 IMPLEMENT_DYNAMIC_CLASS(wxListBox, wxControlWithItems)
28
29 BEGIN_EVENT_TABLE(wxListBox, wxControl)
30 END_EVENT_TABLE()
31
32 #include "wx/mac/uma.h"
33
34 // ============================================================================
35 // list box control implementation
36 // ============================================================================
37
38 wxListBox::wxListBox()
39 {
40 }
41
42 bool wxListBox::Create(
43 wxWindow *parent,
44 wxWindowID id,
45 const wxPoint& pos,
46 const wxSize& size,
47 const wxArrayString& choices,
48 long style,
49 const wxValidator& validator,
50 const wxString& name )
51 {
52 wxCArrayString chs(choices);
53
54 return Create(
55 parent, id, pos, size, chs.GetCount(), chs.GetStrings(),
56 style, validator, name );
57 }
58
59 wxMacListControl* wxListBox::GetPeer() const
60 {
61 wxMacDataBrowserListControl *lb = wxDynamicCast(m_peer,wxMacDataBrowserListControl);
62 return lb ? wx_static_cast(wxMacListControl*,lb) : 0 ;
63 }
64
65 bool wxListBox::Create(
66 wxWindow *parent,
67 wxWindowID id,
68 const wxPoint& pos,
69 const wxSize& size,
70 int n,
71 const wxString choices[],
72 long style,
73 const wxValidator& validator,
74 const wxString& name )
75 {
76 m_macIsUserPane = false;
77
78 wxASSERT_MSG( !(style & wxLB_MULTIPLE) || !(style & wxLB_EXTENDED),
79 wxT("only a single listbox selection mode can be specified") );
80
81 if ( !wxListBoxBase::Create( parent, id, pos, size, style & ~(wxHSCROLL | wxVSCROLL), validator, name ) )
82 return false;
83
84 wxMacDataBrowserListControl* control = new wxMacDataBrowserListControl( this, pos, size, style );
85 // TODO CHECK control->SetClientDataType( m_clientDataItemsType );
86 m_peer = control;
87
88 MacPostControlCreate( pos, size );
89
90 Append(n, choices);
91
92 // Needed because it is a wxControlWithItems
93 SetInitialSize( size );
94
95 return true;
96 }
97
98 wxListBox::~wxListBox()
99 {
100 FreeData();
101 m_peer->SetReference( 0 );
102 }
103
104 void wxListBox::FreeData()
105 {
106 GetPeer()->MacClear();
107 }
108
109 void wxListBox::DoSetFirstItem(int n)
110 {
111 GetPeer()->MacScrollTo( n );
112 }
113
114 void wxListBox::EnsureVisible(int n)
115 {
116 GetPeer()->MacScrollTo( n );
117 }
118
119 void wxListBox::DoDeleteOneItem(unsigned int n)
120 {
121 wxCHECK_RET( IsValid(n), wxT("invalid index in wxListBox::Delete") );
122
123 GetPeer()->MacDelete( n );
124 }
125
126 int wxListBox::DoInsertItems(const wxArrayStringsAdapter& items,
127 unsigned int pos,
128 void **clientData,
129 wxClientDataType type)
130 {
131 InvalidateBestSize();
132
133 GetPeer()->MacInsert( pos, items );
134 const unsigned int count = items.GetCount();
135 if ( clientData )
136 {
137 for (unsigned int i = 0; i < count; ++i)
138 AssignNewItemClientData( pos + i, clientData, i, type );
139 }
140
141 return pos + count - 1;
142 }
143
144 int wxListBox::FindString(const wxString& s, bool bCase) const
145 {
146 for ( size_t i = 0; i < GetCount(); ++ i )
147 {
148 if (s.IsSameAs( GetString( i ), bCase) )
149 return (int)i;
150 }
151
152 return wxNOT_FOUND;
153 }
154
155 void wxListBox::DoClear()
156 {
157 FreeData();
158 }
159
160 void wxListBox::DoSetSelection(int n, bool select)
161 {
162 wxCHECK_RET( n == wxNOT_FOUND || IsValid(n),
163 wxT("invalid index in wxListBox::SetSelection") );
164
165 if ( n == wxNOT_FOUND )
166 GetPeer()->MacDeselectAll();
167 else
168 GetPeer()->MacSetSelection( n, select, HasMultipleSelection() );
169 }
170
171 bool wxListBox::IsSelected(int n) const
172 {
173 wxCHECK_MSG( IsValid(n), false, wxT("invalid index in wxListBox::Selected") );
174
175 return GetPeer()->MacIsSelected( n );
176 }
177
178 void *wxListBox::DoGetItemClientData(unsigned int n) const
179 {
180 wxCHECK_MSG( IsValid(n), NULL, wxT("invalid index in wxListBox::GetClientData"));
181 return GetPeer()->MacGetClientData( n );
182 }
183
184 void wxListBox::DoSetItemClientData(unsigned int n, void *clientData)
185 {
186 wxCHECK_RET( IsValid(n), wxT("invalid index in wxListBox::SetClientData") );
187 GetPeer()->MacSetClientData( n , clientData);
188 }
189
190 // Return number of selections and an array of selected integers
191 int wxListBox::GetSelections(wxArrayInt& aSelections) const
192 {
193 return GetPeer()->MacGetSelections( aSelections );
194 }
195
196 // Get single selection, for single choice list items
197 int wxListBox::GetSelection() const
198 {
199 return GetPeer()->MacGetSelection();
200 }
201
202 // Find string for position
203 wxString wxListBox::GetString(unsigned int n) const
204 {
205 wxCHECK_MSG( IsValid(n), wxEmptyString, wxT("invalid index in wxListBox::GetString") );
206 return GetPeer()->MacGetString(n);
207 }
208
209 void wxListBox::SetString(unsigned int n, const wxString& s)
210 {
211 GetPeer()->MacSetString( n, s );
212 }
213
214 wxSize wxListBox::DoGetBestSize() const
215 {
216 int lbWidth = 100; // some defaults
217 int lbHeight = 110;
218 int wLine;
219
220 {
221 wxClientDC dc(const_cast<wxListBox*>(this));
222 dc.SetFont(GetFont());
223
224 // Find the widest line
225 for (unsigned int i = 0; i < GetCount(); i++)
226 {
227 wxString str( GetString( i ) );
228
229 wxCoord width, height ;
230 dc.GetTextExtent( str , &width, &height);
231 wLine = width ;
232 lbWidth = wxMax( lbWidth, wLine );
233 }
234
235 // Add room for the scrollbar
236 lbWidth += wxSystemSettings::GetMetric( wxSYS_VSCROLL_X );
237
238 // And just a bit more
239 int cy = 12;
240
241 wxCoord width, height ;
242 dc.GetTextExtent( wxT("XX") , &width, &height);
243 int cx = width ;
244 lbWidth += cx;
245
246 // don't make the listbox too tall (limit height to around 10 items)
247 // but don't make it too small neither
248 lbHeight = wxMax( (cy + 4) * wxMin( wxMax( GetCount(), 3 ), 10 ), 70 );
249 }
250
251 return wxSize( lbWidth, lbHeight );
252 }
253
254 unsigned int wxListBox::GetCount() const
255 {
256 return GetPeer()->MacGetCount();
257 }
258
259 void wxListBox::Refresh(bool eraseBack, const wxRect *rect)
260 {
261 wxControl::Refresh( eraseBack, rect );
262 }
263
264 // Some custom controls depend on this
265 /* static */ wxVisualAttributes
266 wxListBox::GetClassDefaultAttributes(wxWindowVariant WXUNUSED(variant))
267 {
268 wxVisualAttributes attr;
269
270 attr.colFg = wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOWTEXT );
271 attr.colBg = wxSystemSettings::GetColour( wxSYS_COLOUR_LISTBOX );
272 attr.font = wxSystemSettings::GetFont( wxSYS_DEFAULT_GUI_FONT );
273
274 return attr;
275 }
276
277 int wxListBox::DoListHitTest(const wxPoint& inpoint) const
278 {
279 OSStatus err;
280
281 // There are few reasons why this is complicated:
282 // 1) There is no native HitTest function for Mac
283 // 2) GetDataBrowserItemPartBounds only works on visible items
284 // 3) We can't do it through GetDataBrowserTableView[Item]RowHeight
285 // because what it returns is basically inaccurate in the context
286 // of the coordinates we want here, but we use this as a guess
287 // for where the first visible item lies
288
289 wxPoint point = inpoint;
290
291 // get column property ID (req. for call to itempartbounds)
292 DataBrowserTableViewColumnID colId = 0;
293 err = GetDataBrowserTableViewColumnProperty(m_peer->GetControlRef(), 0, &colId);
294 wxCHECK_MSG(err == noErr, wxNOT_FOUND, wxT("Unexpected error from GetDataBrowserTableViewColumnProperty"));
295
296 // OK, first we need to find the first visible item we have -
297 // this will be the "low" for our binary search. There is no real
298 // easy way around this, as we will need to do a SLOW linear search
299 // until we find a visible item, but we can do a cheap calculation
300 // via the row height to speed things up a bit
301 UInt32 scrollx, scrolly;
302 err = GetDataBrowserScrollPosition(m_peer->GetControlRef(), &scrollx, &scrolly);
303 wxCHECK_MSG(err == noErr, wxNOT_FOUND, wxT("Unexpected error from GetDataBrowserScrollPosition"));
304
305 UInt16 height;
306 err = GetDataBrowserTableViewRowHeight(m_peer->GetControlRef(), &height);
307 wxCHECK_MSG(err == noErr, wxNOT_FOUND, wxT("Unexpected error from GetDataBrowserTableViewRowHeight"));
308
309 // these indices are 0-based, as usual, so we need to add 1 to them when
310 // passing them to data browser functions which use 1-based indices
311 int low = scrolly / height,
312 high = GetCount() - 1;
313
314 // search for the first visible item (note that the scroll guess above
315 // is the low bounds of where the item might lie so we only use that as a
316 // starting point - we should reach it within 1 or 2 iterations of the loop)
317 while ( low <= high )
318 {
319 Rect bounds;
320 err = GetDataBrowserItemPartBounds(
321 m_peer->GetControlRef(), low + 1, colId,
322 kDataBrowserPropertyEnclosingPart,
323 &bounds); // note +1 to translate to Mac ID
324 if ( err == noErr )
325 break;
326
327 // errDataBrowserItemNotFound is expected as it simply means that the
328 // item is not currently visible -- but other errors are not
329 wxCHECK_MSG( err == errDataBrowserItemNotFound, wxNOT_FOUND,
330 wxT("Unexpected error from GetDataBrowserItemPartBounds") );
331
332 low++;
333 }
334
335 // NOW do a binary search for where the item lies, searching low again if
336 // we hit an item that isn't visible
337 while ( low <= high )
338 {
339 int mid = (low + high) / 2;
340
341 Rect bounds;
342 err = GetDataBrowserItemPartBounds(
343 m_peer->GetControlRef(), mid + 1, colId,
344 kDataBrowserPropertyEnclosingPart,
345 &bounds); //note +1 to trans to mac id
346 wxCHECK_MSG( err == noErr || err == errDataBrowserItemNotFound,
347 wxNOT_FOUND,
348 wxT("Unexpected error from GetDataBrowserItemPartBounds") );
349
350 if ( err == errDataBrowserItemNotFound )
351 {
352 // item not visible, attempt to find a visible one
353 // search lower
354 high = mid - 1;
355 }
356 else // visible item, do actual hitttest
357 {
358 // if point is within the bounds, return this item (since we assume
359 // all x coords of items are equal we only test the x coord in
360 // equality)
361 if ((point.x >= bounds.left && point.x <= bounds.right) &&
362 (point.y >= bounds.top && point.y <= bounds.bottom) )
363 {
364 // found!
365 return mid;
366 }
367
368 if ( point.y < bounds.top )
369 // index(bounds) greater then key(point)
370 high = mid - 1;
371 else
372 // index(bounds) less then key(point)
373 low = mid + 1;
374 }
375 }
376
377 return wxNOT_FOUND;
378 }
379
380 // ============================================================================
381 // data browser based implementation
382 // ============================================================================
383
384 wxMacListBoxItem::wxMacListBoxItem()
385 :wxMacDataItem()
386 {
387 }
388
389 wxMacListBoxItem::~wxMacListBoxItem()
390 {
391 }
392
393 void wxMacListBoxItem::Notification(wxMacDataItemBrowserControl *owner ,
394 DataBrowserItemNotification message,
395 DataBrowserItemDataRef WXUNUSED(itemData) ) const
396 {
397 wxMacDataBrowserListControl *lb = wxDynamicCast(owner,wxMacDataBrowserListControl);
398
399 // we want to depend on as little as possible to make sure tear-down of controls is safe
400
401 if ( message == kDataBrowserItemRemoved)
402 {
403 if ( lb != NULL && lb->GetClientDataType() == wxClientData_Object )
404 {
405 delete (wxClientData*) (m_data);
406 }
407
408 delete this;
409 return;
410 }
411
412 wxListBox *list = wxDynamicCast( owner->GetPeer() , wxListBox );
413 wxCHECK_RET( list != NULL , wxT("Listbox expected"));
414
415 bool trigger = false;
416 wxCommandEvent event( wxEVT_COMMAND_LISTBOX_SELECTED, list->GetId() );
417 switch (message)
418 {
419 case kDataBrowserItemDeselected:
420 if ( list->HasMultipleSelection() )
421 trigger = !lb->IsSelectionSuppressed();
422 break;
423
424 case kDataBrowserItemSelected:
425 trigger = !lb->IsSelectionSuppressed();
426 break;
427
428 case kDataBrowserItemDoubleClicked:
429 event.SetEventType( wxEVT_COMMAND_LISTBOX_DOUBLECLICKED );
430 trigger = true;
431 break;
432
433 default:
434 break;
435 }
436
437 if ( trigger )
438 {
439 event.SetEventObject( list );
440 if ( list->HasClientObjectData() )
441 event.SetClientObject( (wxClientData*) m_data );
442 else if ( list->HasClientUntypedData() )
443 event.SetClientData( m_data );
444 event.SetString( m_label );
445 event.SetInt( owner->GetLineFromItem( this ) );
446 event.SetExtraLong( list->HasMultipleSelection() ? message == kDataBrowserItemSelected : true );
447
448 // direct notification is not always having the listbox GetSelection()
449 // having in synch with event, so use wxPostEvent instead
450 // list->HandleWindowEvent(event);
451
452 wxPostEvent( list->GetEventHandler(), event );
453 }
454 }
455
456 IMPLEMENT_DYNAMIC_CLASS( wxMacDataBrowserListControl , wxMacDataItemBrowserControl )
457
458 wxMacDataBrowserListControl::wxMacDataBrowserListControl( wxWindow *peer, const wxPoint& pos, const wxSize& size, long style)
459 : wxMacDataItemBrowserControl( peer, pos, size, style )
460 {
461 OSStatus err = noErr;
462 m_clientDataItemsType = wxClientData_None;
463 if ( style & wxLB_SORT )
464 m_sortOrder = SortOrder_Text_Ascending;
465
466 DataBrowserSelectionFlags options = kDataBrowserDragSelect;
467 if ( style & wxLB_MULTIPLE )
468 {
469 options |= kDataBrowserAlwaysExtendSelection | kDataBrowserCmdTogglesSelection;
470 }
471 else if ( style & wxLB_EXTENDED )
472 {
473 options |= kDataBrowserCmdTogglesSelection;
474 }
475 else
476 {
477 options |= kDataBrowserSelectOnlyOne;
478 }
479 err = SetSelectionFlags( options );
480 verify_noerr( err );
481
482 DataBrowserListViewColumnDesc columnDesc;
483 columnDesc.headerBtnDesc.titleOffset = 0;
484 columnDesc.headerBtnDesc.version = kDataBrowserListViewLatestHeaderDesc;
485
486 columnDesc.headerBtnDesc.btnFontStyle.flags =
487 kControlUseFontMask | kControlUseJustMask;
488
489 columnDesc.headerBtnDesc.btnContentInfo.contentType = kControlNoContent;
490 columnDesc.headerBtnDesc.btnFontStyle.just = teFlushDefault;
491 columnDesc.headerBtnDesc.btnFontStyle.font = kControlFontViewSystemFont;
492 columnDesc.headerBtnDesc.btnFontStyle.style = normal;
493 columnDesc.headerBtnDesc.titleString = NULL;
494
495 columnDesc.headerBtnDesc.minimumWidth = 0;
496 columnDesc.headerBtnDesc.maximumWidth = 10000;
497
498 columnDesc.propertyDesc.propertyID = kTextColumnId;
499 columnDesc.propertyDesc.propertyType = kDataBrowserTextType;
500 columnDesc.propertyDesc.propertyFlags = kDataBrowserTableViewSelectionColumn;
501 columnDesc.propertyDesc.propertyFlags |= kDataBrowserListViewTypeSelectColumn;
502
503 verify_noerr( AddColumn( &columnDesc, kDataBrowserListViewAppendColumn ) );
504
505 columnDesc.headerBtnDesc.minimumWidth = 0;
506 columnDesc.headerBtnDesc.maximumWidth = 0;
507 columnDesc.propertyDesc.propertyID = kNumericOrderColumnId;
508 columnDesc.propertyDesc.propertyType = kDataBrowserPropertyRelevanceRankPart;
509 columnDesc.propertyDesc.propertyFlags = kDataBrowserTableViewSelectionColumn;
510 columnDesc.propertyDesc.propertyFlags |= kDataBrowserListViewTypeSelectColumn;
511
512 verify_noerr( AddColumn( &columnDesc, kDataBrowserListViewAppendColumn ) );
513
514 SetDataBrowserSortProperty( m_controlRef , kTextColumnId);
515 if ( m_sortOrder == SortOrder_Text_Ascending )
516 {
517 SetDataBrowserSortProperty( m_controlRef , kTextColumnId);
518 SetDataBrowserSortOrder( m_controlRef , kDataBrowserOrderIncreasing);
519 }
520 else
521 {
522 SetDataBrowserSortProperty( m_controlRef , kNumericOrderColumnId);
523 SetDataBrowserSortOrder( m_controlRef , kDataBrowserOrderIncreasing);
524 }
525
526 verify_noerr( AutoSizeColumns() );
527 verify_noerr( SetHiliteStyle(kDataBrowserTableViewFillHilite ) );
528 verify_noerr( SetHeaderButtonHeight( 0 ) );
529 err = SetHasScrollBars( (style & wxHSCROLL) != 0 , true );
530 #if 0
531 // shouldn't be necessary anymore under 10.2
532 m_peer->SetData( kControlNoPart, kControlDataBrowserIncludesFrameAndFocusTag, (Boolean)false );
533 m_peer->SetNeedsFocusRect( true );
534 #endif
535 }
536
537 wxMacDataBrowserListControl::~wxMacDataBrowserListControl()
538 {
539 }
540
541 wxWindow * wxMacDataBrowserListControl::GetPeer() const
542 {
543 return wxDynamicCast( wxMacControl::GetPeer() , wxWindow );
544 }
545
546 wxMacDataItem* wxMacDataBrowserListControl::CreateItem()
547 {
548 return new wxMacListBoxItem();
549 }
550
551 #if 0
552
553 // in case we need that one day
554
555 // ============================================================================
556 // HIView owner-draw-based implementation
557 // ============================================================================
558
559 static pascal void ListBoxDrawProc(
560 ControlRef browser, DataBrowserItemID item, DataBrowserPropertyID property,
561 DataBrowserItemState itemState, const Rect *itemRect, SInt16 depth, Boolean isColorDevice )
562 {
563 CFStringRef cfString;
564 ThemeDrawingState themeState;
565 long systemVersion;
566
567 GetThemeDrawingState( &themeState );
568 cfString = CFStringCreateWithFormat( NULL, NULL, CFSTR("Row %d"), item );
569
570 // In this sample we handle the "selected" state; all others fall through to our "active" state
571 if ( itemState == kDataBrowserItemIsSelected )
572 {
573 ThemeBrush colorBrushID;
574
575 // TODO: switch over to wxSystemSettingsNative::GetColour() when kThemeBrushSecondaryHighlightColor
576 // is incorporated Panther DB starts using kThemeBrushSecondaryHighlightColor
577 // for inactive browser highlighting
578 if ( !IsControlActive( browser ) )
579 colorBrushID = kThemeBrushSecondaryHighlightColor;
580 else
581 colorBrushID = kThemeBrushPrimaryHighlightColor;
582
583 // First paint the hilite rect, then the text on top
584 SetThemePen( colorBrushID, 32, true );
585 PaintRect( itemRect );
586 SetThemeDrawingState( themeState, false );
587 }
588
589 DrawThemeTextBox( cfString, kThemeApplicationFont, kThemeStateActive, true, itemRect, teFlushDefault, NULL );
590 SetThemeDrawingState( themeState, true );
591
592 if ( cfString != NULL )
593 CFRelease( cfString );
594 }
595
596 #endif
597
598
599 #endif // wxUSE_LISTBOX