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