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