1 /////////////////////////////////////////////////////////////////////////////// 
   2 // Name:        src/mac/carbon/listbox.cpp 
   4 // Author:      Stefan Csomor 
   8 // Copyright:   (c) Stefan Csomor 
   9 // Licence:     wxWindows licence 
  10 /////////////////////////////////////////////////////////////////////////////// 
  12 #include "wx/wxprec.h" 
  16 #include "wx/listbox.h" 
  22     #include "wx/settings.h" 
  23     #include "wx/arrstr.h" 
  24     #include "wx/dcclient.h" 
  27 IMPLEMENT_DYNAMIC_CLASS(wxListBox
, wxControl
) 
  29 BEGIN_EVENT_TABLE(wxListBox
, wxControl
) 
  32 #include "wx/mac/uma.h" 
  34 // ============================================================================ 
  35 // list box control implementation 
  36 // ============================================================================ 
  38 wxListBox::wxListBox() 
  42 bool wxListBox::Create( 
  47     const wxArrayString
& choices
, 
  49     const wxValidator
& validator
, 
  50     const wxString
& name 
) 
  52     wxCArrayString 
chs(choices
); 
  55         parent
, id
, pos
, size
, chs
.GetCount(), chs
.GetStrings(), 
  56         style
, validator
, name 
); 
  59 wxMacListControl
* wxListBox::GetPeer() const 
  61     wxMacDataBrowserListControl 
*lb 
= wxDynamicCast(m_peer
,wxMacDataBrowserListControl
); 
  62     return lb 
? wx_static_cast(wxMacListControl
*,lb
) : 0 ; 
  65 bool wxListBox::Create( 
  71     const wxString choices
[], 
  73     const wxValidator
& validator
, 
  74     const wxString
& name 
) 
  76     m_macIsUserPane 
= false; 
  78     wxASSERT_MSG( !(style 
& wxLB_MULTIPLE
) || !(style 
& wxLB_EXTENDED
), 
  79                   wxT("only a single listbox selection mode can be specified") ); 
  81     if ( !wxListBoxBase::Create( parent
, id
, pos
, size
, style 
& ~(wxHSCROLL 
| wxVSCROLL
), validator
, name 
) ) 
  84     wxMacDataBrowserListControl
* control 
= new wxMacDataBrowserListControl( this, pos
, size
, style 
); 
  85     control
->SetClientDataType( m_clientDataItemsType 
); 
  88     MacPostControlCreate( pos
, size 
); 
  90     InsertItems( n
, choices
, 0 ); 
  92    // Needed because it is a wxControlWithItems 
  93     SetInitialSize( size 
); 
  98 wxListBox::~wxListBox() 
 101     m_peer
->SetReference( 0 ); 
 104 void wxListBox::FreeData() 
 106     GetPeer()->MacClear(); 
 109 void wxListBox::DoSetFirstItem(int n
) 
 111     GetPeer()->MacScrollTo( n 
); 
 114 void wxListBox::EnsureVisible(int n
) 
 116     GetPeer()->MacScrollTo( n 
); 
 119 void wxListBox::Delete(unsigned int n
) 
 121     wxCHECK_RET( IsValid(n
), wxT("invalid index in wxListBox::Delete") ); 
 123     GetPeer()->MacDelete( n 
); 
 126 int wxListBox::DoAppend(const wxString
& item
) 
 128     InvalidateBestSize(); 
 130     return GetPeer()->MacAppend( item 
); 
 133 void wxListBox::DoSetItems(const wxArrayString
& choices
, void** clientData
) 
 137     unsigned int n 
= choices
.GetCount(); 
 139     for ( size_t i 
= 0; i 
< n
; ++i 
) 
 143             Append( choices
[i
], clientData
[i
] ); 
 146             Append( choices
[i
] ); 
 151 int wxListBox::FindString(const wxString
& s
, bool bCase
) const 
 153     for ( size_t i 
= 0; i 
< GetCount(); ++ i 
) 
 155         if (s
.IsSameAs( GetString( i 
), bCase
) ) 
 162 void wxListBox::Clear() 
 167 void wxListBox::DoSetSelection(int n
, bool select
) 
 169     wxCHECK_RET( n 
== wxNOT_FOUND 
|| IsValid(n
), 
 170         wxT("invalid index in wxListBox::SetSelection") ); 
 172     if ( n 
== wxNOT_FOUND 
) 
 173         GetPeer()->MacDeselectAll(); 
 175         GetPeer()->MacSetSelection( n
, select
, HasMultipleSelection() ); 
 178 bool wxListBox::IsSelected(int n
) const 
 180     wxCHECK_MSG( IsValid(n
), false, wxT("invalid index in wxListBox::Selected") ); 
 182     return GetPeer()->MacIsSelected( n 
); 
 185 void *wxListBox::DoGetItemClientData(unsigned int n
) const 
 187     wxCHECK_MSG( IsValid(n
), NULL
, wxT("invalid index in wxListBox::GetClientData")); 
 188     return GetPeer()->MacGetClientData( n 
); 
 191 wxClientData 
*wxListBox::DoGetItemClientObject(unsigned int n
) const 
 193     return (wxClientData
*)DoGetItemClientData( n 
); 
 196 void wxListBox::DoSetItemClientData(unsigned int n
, void *clientData
) 
 198     wxCHECK_RET( IsValid(n
), wxT("invalid index in wxListBox::SetClientData") ); 
 199     GetPeer()->MacSetClientData( n 
, clientData
); 
 202 void wxListBox::DoSetItemClientObject(unsigned int n
, wxClientData
* clientData
) 
 204     DoSetItemClientData(n
, clientData
); 
 207 // Return number of selections and an array of selected integers 
 208 int wxListBox::GetSelections(wxArrayInt
& aSelections
) const 
 210     return GetPeer()->MacGetSelections( aSelections 
); 
 213 // Get single selection, for single choice list items 
 214 int wxListBox::GetSelection() const 
 216     return GetPeer()->MacGetSelection(); 
 219 // Find string for position 
 220 wxString 
wxListBox::GetString(unsigned int n
) const 
 222     wxCHECK_MSG( IsValid(n
), wxEmptyString
, wxT("invalid index in wxListBox::GetString") ); 
 223     return GetPeer()->MacGetString(n
); 
 226 void wxListBox::DoInsertItems(const wxArrayString
& items
, unsigned int pos
) 
 228     wxCHECK_RET( IsValidInsert(pos
), wxT("invalid index in wxListBox::InsertItems") ); 
 230     InvalidateBestSize(); 
 232     GetPeer()->MacInsert( pos
, items 
); 
 235 void wxListBox::SetString(unsigned int n
, const wxString
& s
) 
 237     GetPeer()->MacSetString( n
, s 
); 
 240 wxSize 
wxListBox::DoGetBestSize() const 
 242     int lbWidth 
= 100;  // some defaults 
 247 #if wxMAC_USE_CORE_GRAPHICS 
 248         wxClientDC 
dc(const_cast<wxListBox
*>(this)); 
 249         dc
.SetFont(GetFont()); 
 251         wxMacPortStateHelper 
st( UMAGetWindowPort( (WindowRef
)MacGetTopLevelWindowRef() ) ); 
 253         // TODO: clean this up 
 256             ::TextFont( m_font
.MacGetFontNum() ); 
 257             ::TextSize( m_font
.MacGetFontSize() ); 
 258             ::TextFace( m_font
.MacGetFontStyle() ); 
 262             ::TextFont( kFontIDMonaco 
); 
 267         // Find the widest line 
 268         for (unsigned int i 
= 0; i 
< GetCount(); i
++) 
 270             wxString 
str( GetString( i 
) ); 
 271 #if wxMAC_USE_CORE_GRAPHICS 
 272             wxCoord width
, height 
; 
 273             dc
.GetTextExtent( str 
, &width
, &height
); 
 277             Point bounds 
= {0, 0}; 
 280             // NB: what if m_font.Ok() == false ??? 
 281             ::GetThemeTextDimensions( 
 282                 wxMacCFStringHolder( str
, m_font
.GetEncoding() ), 
 283                 kThemeCurrentPortFont
, 
 290             wLine 
= ::TextWidth( str
.c_str(), 0, str
.length() ); 
 293             lbWidth 
= wxMax( lbWidth
, wLine 
); 
 296         // Add room for the scrollbar 
 297         lbWidth 
+= wxSystemSettings::GetMetric( wxSYS_VSCROLL_X 
); 
 299         // And just a bit more 
 301 #if wxMAC_USE_CORE_GRAPHICS 
 302         wxCoord width
, height 
; 
 303         dc
.GetTextExtent( wxT("XX") , &width
, &height
); 
 306         int cx 
= ::TextWidth( "XX", 0, 1 ); 
 310         // don't make the listbox too tall (limit height to around 10 items) 
 311         // but don't make it too small neither 
 312         lbHeight 
= wxMax( (cy 
+ 4) * wxMin( wxMax( GetCount(), 3 ), 10 ), 70 ); 
 315     return wxSize( lbWidth
, lbHeight 
); 
 318 unsigned int wxListBox::GetCount() const 
 320     return GetPeer()->MacGetCount(); 
 323 void wxListBox::Refresh(bool eraseBack
, const wxRect 
*rect
) 
 325     wxControl::Refresh( eraseBack
, rect 
); 
 328 // Some custom controls depend on this 
 329 /* static */ wxVisualAttributes
 
 330 wxListBox::GetClassDefaultAttributes(wxWindowVariant 
WXUNUSED(variant
)) 
 332     wxVisualAttributes attr
; 
 334     attr
.colFg 
= wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOWTEXT 
); 
 335     attr
.colBg 
= wxSystemSettings::GetColour( wxSYS_COLOUR_LISTBOX 
); 
 336     attr
.font 
= wxSystemSettings::GetFont( wxSYS_DEFAULT_GUI_FONT 
); 
 341 int wxListBox::DoListHitTest(const wxPoint
& inpoint
) const 
 345     // There are few reasons why this is complicated: 
 346     // 1) There is no native HitTest function for Mac 
 347     // 2) GetDataBrowserItemPartBounds only works on visible items 
 348     // 3) We can't do it through GetDataBrowserTableView[Item]RowHeight 
 349     //    because what it returns is basically inaccurate in the context 
 350     //    of the coordinates we want here, but we use this as a guess 
 351     //    for where the first visible item lies 
 353     wxPoint point 
= inpoint
; 
 355     // interestingly enough 10.2 (and below?) have GetDataBrowserItemPartBounds 
 356     // giving root window coordinates but 10.3 and above give client coordinates 
 357     // so we only compare using root window coordinates on 10.3 and up 
 358     if ( UMAGetSystemVersion() < 0x1030 ) 
 359         MacClientToRootWindow(&point
.x
, &point
.y
); 
 361     // get column property ID (req. for call to itempartbounds) 
 362     DataBrowserTableViewColumnID colId 
= 0; 
 363     err 
= GetDataBrowserTableViewColumnProperty(m_peer
->GetControlRef(), 0, &colId
); 
 364     wxCHECK_MSG(err 
== noErr
, wxNOT_FOUND
, wxT("Unexpected error from GetDataBrowserTableViewColumnProperty")); 
 366     // OK, first we need to find the first visible item we have - 
 367     // this will be the "low" for our binary search. There is no real 
 368     // easy way around this, as we will need to do a SLOW linear search 
 369     // until we find a visible item, but we can do a cheap calculation 
 370     // via the row height to speed things up a bit 
 371     UInt32 scrollx
, scrolly
; 
 372     err 
= GetDataBrowserScrollPosition(m_peer
->GetControlRef(), &scrollx
, &scrolly
); 
 373     wxCHECK_MSG(err 
== noErr
, wxNOT_FOUND
, wxT("Unexpected error from GetDataBrowserScrollPosition")); 
 376     err 
= GetDataBrowserTableViewRowHeight(m_peer
->GetControlRef(), &height
); 
 377     wxCHECK_MSG(err 
== noErr
, wxNOT_FOUND
, wxT("Unexpected error from GetDataBrowserTableViewRowHeight")); 
 379     // these indices are 0-based, as usual, so we need to add 1 to them when 
 380     // passing them to data browser functions which use 1-based indices 
 381     int low 
= scrolly 
/ height
, 
 382         high 
= GetCount() - 1; 
 384     // search for the first visible item (note that the scroll guess above 
 385     // is the low bounds of where the item might lie so we only use that as a 
 386     // starting point - we should reach it within 1 or 2 iterations of the loop) 
 387     while ( low 
<= high 
) 
 390         err 
= GetDataBrowserItemPartBounds( 
 391             m_peer
->GetControlRef(), low 
+ 1, colId
, 
 392             kDataBrowserPropertyEnclosingPart
, 
 393             &bounds
); // note +1 to translate to Mac ID 
 397         // errDataBrowserItemNotFound is expected as it simply means that the 
 398         // item is not currently visible -- but other errors are not 
 399         wxCHECK_MSG( err 
== errDataBrowserItemNotFound
, wxNOT_FOUND
, 
 400                      wxT("Unexpected error from GetDataBrowserItemPartBounds") ); 
 405     // NOW do a binary search for where the item lies, searching low again if 
 406     // we hit an item that isn't visible 
 407     while ( low 
<= high 
) 
 409         int mid 
= (low 
+ high
) / 2; 
 412         err 
= GetDataBrowserItemPartBounds( 
 413             m_peer
->GetControlRef(), mid 
+ 1, colId
, 
 414             kDataBrowserPropertyEnclosingPart
, 
 415             &bounds
); //note +1 to trans to mac id 
 416         wxCHECK_MSG( err 
== noErr 
|| err 
== errDataBrowserItemNotFound
, 
 418                      wxT("Unexpected error from GetDataBrowserItemPartBounds") ); 
 420         if ( err 
== errDataBrowserItemNotFound 
) 
 422             // item not visible, attempt to find a visible one 
 426         else // visible item, do actual hitttest 
 428             // if point is within the bounds, return this item (since we assume 
 429             // all x coords of items are equal we only test the x coord in 
 431             if ((point
.x 
>= bounds
.left 
&& point
.x 
<= bounds
.right
) && 
 432                 (point
.y 
>= bounds
.top 
&& point
.y 
<= bounds
.bottom
) ) 
 438             if ( point
.y 
< bounds
.top 
) 
 439                 // index(bounds) greater then key(point) 
 442                 // index(bounds) less then key(point) 
 450 // ============================================================================ 
 451 // data browser based implementation 
 452 // ============================================================================ 
 454 wxMacListBoxItem::wxMacListBoxItem() 
 459 wxMacListBoxItem::~wxMacListBoxItem() 
 463 void wxMacListBoxItem::Notification(wxMacDataItemBrowserControl 
*owner 
, 
 464     DataBrowserItemNotification message
, 
 465     DataBrowserItemDataRef itemData 
) const 
 467     wxMacDataBrowserListControl 
*lb 
= wxDynamicCast(owner
,wxMacDataBrowserListControl
); 
 469     // we want to depend on as little as possible to make sure tear-down of controls is safe 
 471     if ( message 
== kDataBrowserItemRemoved
) 
 473         if ( lb 
!= NULL 
&& lb
->GetClientDataType() == wxClientData_Object 
) 
 475             delete (wxClientData
*) (m_data
); 
 482     wxListBox 
*list 
= wxDynamicCast( owner
->GetPeer() , wxListBox 
); 
 483     wxCHECK_RET( list 
!= NULL 
, wxT("Listbox expected")); 
 485     bool trigger 
= false; 
 486     wxCommandEvent 
event( wxEVT_COMMAND_LISTBOX_SELECTED
, list
->GetId() ); 
 489         case kDataBrowserItemDeselected
: 
 490             if ( list
->HasMultipleSelection() ) 
 491                 trigger 
= !lb
->IsSelectionSuppressed(); 
 494         case kDataBrowserItemSelected
: 
 495             trigger 
= !lb
->IsSelectionSuppressed(); 
 498         case kDataBrowserItemDoubleClicked
: 
 499             event
.SetEventType( wxEVT_COMMAND_LISTBOX_DOUBLECLICKED 
); 
 509         event
.SetEventObject( list 
); 
 510         if ( list
->HasClientObjectData() ) 
 511             event
.SetClientObject( (wxClientData
*) m_data 
); 
 512         else if ( list
->HasClientUntypedData() ) 
 513             event
.SetClientData( m_data 
); 
 514         event
.SetString( m_label 
); 
 515         event
.SetInt( owner
->GetLineFromItem( this ) ); 
 516         event
.SetExtraLong( list
->HasMultipleSelection() ? message 
== kDataBrowserItemSelected 
: true ); 
 518         // direct notification is not always having the listbox GetSelection() 
 519         // having in synch with event, so use wxPostEvent instead 
 520         // list->GetEventHandler()->ProcessEvent(event); 
 522         wxPostEvent( list
->GetEventHandler(), event 
); 
 526 IMPLEMENT_DYNAMIC_CLASS( wxMacDataBrowserListControl 
, wxMacDataItemBrowserControl 
) 
 528 wxMacDataBrowserListControl::wxMacDataBrowserListControl( wxWindow 
*peer
, const wxPoint
& pos
, const wxSize
& size
, long style
) 
 529     : wxMacDataItemBrowserControl( peer
, pos
, size
, style 
) 
 531     OSStatus err 
= noErr
; 
 532     m_clientDataItemsType 
= wxClientData_None
; 
 533     if ( style 
& wxLB_SORT 
) 
 534         m_sortOrder 
= SortOrder_Text_Ascending
; 
 536     DataBrowserSelectionFlags  options 
= kDataBrowserDragSelect
; 
 537     if ( style 
& wxLB_MULTIPLE 
) 
 539         options 
|= kDataBrowserAlwaysExtendSelection 
| kDataBrowserCmdTogglesSelection
; 
 541     else if ( style 
& wxLB_EXTENDED 
) 
 543         options 
|= kDataBrowserCmdTogglesSelection
; 
 547         options 
|= kDataBrowserSelectOnlyOne
; 
 549     err 
= SetSelectionFlags( options 
); 
 552     DataBrowserListViewColumnDesc columnDesc
; 
 553     columnDesc
.headerBtnDesc
.titleOffset 
= 0; 
 554     columnDesc
.headerBtnDesc
.version 
= kDataBrowserListViewLatestHeaderDesc
; 
 556     columnDesc
.headerBtnDesc
.btnFontStyle
.flags 
= 
 557         kControlUseFontMask 
| kControlUseJustMask
; 
 559     columnDesc
.headerBtnDesc
.btnContentInfo
.contentType 
= kControlNoContent
; 
 560     columnDesc
.headerBtnDesc
.btnFontStyle
.just 
= teFlushDefault
; 
 561     columnDesc
.headerBtnDesc
.btnFontStyle
.font 
= kControlFontViewSystemFont
; 
 562     columnDesc
.headerBtnDesc
.btnFontStyle
.style 
= normal
; 
 563     columnDesc
.headerBtnDesc
.titleString 
= NULL
; 
 565     columnDesc
.headerBtnDesc
.minimumWidth 
= 0; 
 566     columnDesc
.headerBtnDesc
.maximumWidth 
= 10000; 
 568     columnDesc
.propertyDesc
.propertyID 
= kTextColumnId
; 
 569     columnDesc
.propertyDesc
.propertyType 
= kDataBrowserTextType
; 
 570     columnDesc
.propertyDesc
.propertyFlags 
= kDataBrowserTableViewSelectionColumn
; 
 571 #if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_2 
 572     columnDesc
.propertyDesc
.propertyFlags 
|= kDataBrowserListViewTypeSelectColumn
; 
 575     verify_noerr( AddColumn( &columnDesc
, kDataBrowserListViewAppendColumn 
) ); 
 577     columnDesc
.headerBtnDesc
.minimumWidth 
= 0; 
 578     columnDesc
.headerBtnDesc
.maximumWidth 
= 0; 
 579     columnDesc
.propertyDesc
.propertyID 
= kNumericOrderColumnId
; 
 580     columnDesc
.propertyDesc
.propertyType 
= kDataBrowserPropertyRelevanceRankPart
; 
 581     columnDesc
.propertyDesc
.propertyFlags 
= kDataBrowserTableViewSelectionColumn
; 
 582 #if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_2 
 583     columnDesc
.propertyDesc
.propertyFlags 
|= kDataBrowserListViewTypeSelectColumn
; 
 586     verify_noerr( AddColumn( &columnDesc
, kDataBrowserListViewAppendColumn 
) ); 
 588     SetDataBrowserSortProperty( m_controlRef 
, kTextColumnId
); 
 589     if ( m_sortOrder 
== SortOrder_Text_Ascending 
) 
 591         SetDataBrowserSortProperty( m_controlRef 
, kTextColumnId
); 
 592         SetDataBrowserSortOrder( m_controlRef 
, kDataBrowserOrderIncreasing
); 
 596         SetDataBrowserSortProperty( m_controlRef 
, kNumericOrderColumnId
); 
 597         SetDataBrowserSortOrder( m_controlRef 
, kDataBrowserOrderIncreasing
); 
 600     verify_noerr( AutoSizeColumns() ); 
 601     verify_noerr( SetHiliteStyle(kDataBrowserTableViewFillHilite 
) ); 
 602     verify_noerr( SetHeaderButtonHeight( 0 ) ); 
 603     err 
= SetHasScrollBars( (style 
& wxHSCROLL
) != 0 , true ); 
 605     // shouldn't be necessary anymore under 10.2 
 606     m_peer
->SetData( kControlNoPart
, kControlDataBrowserIncludesFrameAndFocusTag
, (Boolean
)false ); 
 607     m_peer
->SetNeedsFocusRect( true ); 
 611 wxMacDataBrowserListControl::~wxMacDataBrowserListControl() 
 615 wxWindow 
* wxMacDataBrowserListControl::GetPeer() const 
 617     return wxDynamicCast( wxMacControl::GetPeer() , wxWindow 
); 
 620 wxMacDataItem
* wxMacDataBrowserListControl::CreateItem() 
 622     return new wxMacListBoxItem(); 
 627 // in case we need that one day 
 629 // ============================================================================ 
 630 // HIView owner-draw-based implementation 
 631 // ============================================================================ 
 633 static pascal void ListBoxDrawProc( 
 634     ControlRef browser
, DataBrowserItemID item
, DataBrowserPropertyID property
, 
 635     DataBrowserItemState itemState
, const Rect 
*itemRect
, SInt16 depth
, Boolean isColorDevice 
) 
 637     CFStringRef cfString
; 
 638     ThemeDrawingState themeState
; 
 641     GetThemeDrawingState( &themeState 
); 
 642     cfString 
= CFStringCreateWithFormat( NULL
, NULL
, CFSTR("Row %d"), item 
); 
 644     //  In this sample we handle the "selected" state; all others fall through to our "active" state 
 645     if ( itemState 
== kDataBrowserItemIsSelected 
) 
 647         ThemeBrush colorBrushID
; 
 649         // TODO: switch over to wxSystemSettingsNative::GetColour() when kThemeBrushSecondaryHighlightColor 
 650         // is incorporated Panther DB starts using kThemeBrushSecondaryHighlightColor 
 651         // for inactive browser highlighting 
 652         Gestalt( gestaltSystemVersion
, &systemVersion 
); 
 653         if ( (systemVersion 
>= 0x00001030) && !IsControlActive( browser 
) ) 
 654             colorBrushID 
= kThemeBrushSecondaryHighlightColor
; 
 656             colorBrushID 
= kThemeBrushPrimaryHighlightColor
; 
 658         // First paint the hilite rect, then the text on top 
 659         SetThemePen( colorBrushID
, 32, true ); 
 660         PaintRect( itemRect 
); 
 661         SetThemeDrawingState( themeState
, false ); 
 664     DrawThemeTextBox( cfString
, kThemeApplicationFont
, kThemeStateActive
, true, itemRect
, teFlushDefault
, NULL 
); 
 665     SetThemeDrawingState( themeState
, true ); 
 667     if ( cfString 
!= NULL 
) 
 668         CFRelease( cfString 
); 
 674 #endif // wxUSE_LISTBOX