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 return dynamic_cast<wxMacListControl
*>(m_peer
);
64 bool wxListBox::Create(
70 const wxString choices
[],
72 const wxValidator
& validator
,
73 const wxString
& name
)
75 m_macIsUserPane
= false;
77 wxASSERT_MSG( !(style
& wxLB_MULTIPLE
) || !(style
& wxLB_EXTENDED
),
78 wxT("only a single listbox selection mode can be specified") );
80 if ( !wxListBoxBase::Create( parent
, id
, pos
, size
, style
& ~(wxHSCROLL
| wxVSCROLL
), validator
, name
) )
83 wxMacDataBrowserListControl
* control
= new wxMacDataBrowserListControl( this, pos
, size
, style
);
84 control
->SetClientDataType( m_clientDataItemsType
);
87 MacPostControlCreate( pos
, size
);
89 InsertItems( n
, choices
, 0 );
91 // Needed because it is a wxControlWithItems
92 SetInitialSize( size
);
97 wxListBox::~wxListBox()
100 m_peer
->SetReference( 0 );
103 void wxListBox::FreeData()
105 GetPeer()->MacClear();
108 void wxListBox::DoSetFirstItem(int n
)
110 GetPeer()->MacScrollTo( n
);
113 void wxListBox::EnsureVisible(int n
)
115 GetPeer()->MacScrollTo( n
);
118 void wxListBox::Delete(unsigned int n
)
120 wxCHECK_RET( IsValid(n
), wxT("invalid index in wxListBox::Delete") );
122 GetPeer()->MacDelete( n
);
125 int wxListBox::DoAppend(const wxString
& item
)
127 InvalidateBestSize();
129 return GetPeer()->MacAppend( item
);
132 void wxListBox::DoSetItems(const wxArrayString
& choices
, void** clientData
)
136 unsigned int n
= choices
.GetCount();
138 for ( size_t i
= 0; i
< n
; ++i
)
142 Append( choices
[i
], clientData
[i
] );
145 Append( choices
[i
] );
150 int wxListBox::FindString(const wxString
& s
, bool bCase
) const
152 for ( size_t i
= 0; i
< GetCount(); ++ i
)
154 if (s
.IsSameAs( GetString( i
), bCase
) )
161 void wxListBox::Clear()
166 void wxListBox::DoSetSelection(int n
, bool select
)
168 wxCHECK_RET( n
== wxNOT_FOUND
|| IsValid(n
),
169 wxT("invalid index in wxListBox::SetSelection") );
171 if ( n
== wxNOT_FOUND
)
172 GetPeer()->MacDeselectAll();
174 GetPeer()->MacSetSelection( n
, select
, HasMultipleSelection() );
177 bool wxListBox::IsSelected(int n
) const
179 wxCHECK_MSG( IsValid(n
), false, wxT("invalid index in wxListBox::Selected") );
181 return GetPeer()->MacIsSelected( n
);
184 void *wxListBox::DoGetItemClientData(unsigned int n
) const
186 wxCHECK_MSG( IsValid(n
), NULL
, wxT("invalid index in wxListBox::GetClientData"));
187 return GetPeer()->MacGetClientData( n
);
190 wxClientData
*wxListBox::DoGetItemClientObject(unsigned int n
) const
192 return (wxClientData
*)DoGetItemClientData( n
);
195 void wxListBox::DoSetItemClientData(unsigned int n
, void *clientData
)
197 wxCHECK_RET( IsValid(n
), wxT("invalid index in wxListBox::SetClientData") );
198 GetPeer()->MacSetClientData( n
, clientData
);
201 void wxListBox::DoSetItemClientObject(unsigned int n
, wxClientData
* clientData
)
203 DoSetItemClientData(n
, clientData
);
206 // Return number of selections and an array of selected integers
207 int wxListBox::GetSelections(wxArrayInt
& aSelections
) const
209 return GetPeer()->MacGetSelections( aSelections
);
212 // Get single selection, for single choice list items
213 int wxListBox::GetSelection() const
215 return GetPeer()->MacGetSelection();
218 // Find string for position
219 wxString
wxListBox::GetString(unsigned int n
) const
221 wxCHECK_MSG( IsValid(n
), wxEmptyString
, wxT("invalid index in wxListBox::GetString") );
222 return GetPeer()->MacGetString(n
);
225 void wxListBox::DoInsertItems(const wxArrayString
& items
, unsigned int pos
)
227 wxCHECK_RET( IsValidInsert(pos
), wxT("invalid index in wxListBox::InsertItems") );
229 InvalidateBestSize();
231 GetPeer()->MacInsert( pos
, items
);
234 void wxListBox::SetString(unsigned int n
, const wxString
& s
)
236 GetPeer()->MacSetString( n
, s
);
239 wxSize
wxListBox::DoGetBestSize() const
241 int lbWidth
= 100; // some defaults
246 #if wxMAC_USE_CORE_GRAPHICS
247 wxClientDC
dc(const_cast<wxListBox
*>(this));
249 wxMacPortStateHelper
st( UMAGetWindowPort( (WindowRef
)MacGetTopLevelWindowRef() ) );
251 // TODO: clean this up
254 ::TextFont( m_font
.MacGetFontNum() );
255 ::TextSize( m_font
.MacGetFontSize() );
256 ::TextFace( m_font
.MacGetFontStyle() );
260 ::TextFont( kFontIDMonaco
);
265 // Find the widest line
266 for (unsigned int i
= 0; i
< GetCount(); i
++)
268 wxString
str( GetString( i
) );
269 #if wxMAC_USE_CORE_GRAPHICS
270 wxCoord width
, height
;
271 dc
.GetTextExtent( str
, &width
, &height
);
275 Point bounds
= {0, 0};
278 // NB: what if m_font.Ok() == false ???
279 ::GetThemeTextDimensions(
280 wxMacCFStringHolder( str
, m_font
.GetEncoding() ),
281 kThemeCurrentPortFont
,
288 wLine
= ::TextWidth( str
.c_str(), 0, str
.length() );
291 lbWidth
= wxMax( lbWidth
, wLine
);
295 // Add room for the scrollbar
296 lbWidth
+= wxSystemSettings::GetMetric( wxSYS_VSCROLL_X
);
298 // And just a bit more
300 #if wxMAC_USE_CORE_GRAPHICS
301 wxCoord width
, height
;
302 dc
.GetTextExtent( wxT("X") , &width
, &height
);
305 int cx
= ::TextWidth( "X", 0, 1 );
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 );
314 return wxSize( lbWidth
, lbHeight
);
317 unsigned int wxListBox::GetCount() const
319 return GetPeer()->MacGetCount();
322 void wxListBox::Refresh(bool eraseBack
, const wxRect
*rect
)
324 wxControl::Refresh( eraseBack
, rect
);
327 // Some custom controls depend on this
328 /* static */ wxVisualAttributes
329 wxListBox::GetClassDefaultAttributes(wxWindowVariant
WXUNUSED(variant
))
331 wxVisualAttributes attr
;
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
);
340 int wxListBox::DoListHitTest(const wxPoint
& inpoint
) const
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
352 wxPoint point
= inpoint
;
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
);
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"));
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"));
375 err
= GetDataBrowserTableViewRowHeight(m_peer
->GetControlRef(), &height
);
376 wxCHECK_MSG(err
== noErr
, wxNOT_FOUND
, wxT("Unexpected error from GetDataBrowserTableViewRowHeight"));
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;
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
)
389 err
= GetDataBrowserItemPartBounds(
390 m_peer
->GetControlRef(), low
+ 1, colId
,
391 kDataBrowserPropertyEnclosingPart
,
392 &bounds
); // note +1 to translate to Mac ID
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") );
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
)
408 int mid
= (low
+ high
) / 2;
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
,
417 wxT("Unexpected error from GetDataBrowserItemPartBounds") );
419 if ( err
== errDataBrowserItemNotFound
)
421 // item not visible, attempt to find a visible one
425 else // visible item, do actual hitttest
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
430 if ((point
.x
>= bounds
.left
&& point
.x
<= bounds
.right
) &&
431 (point
.y
>= bounds
.top
&& point
.y
<= bounds
.bottom
) )
437 if ( point
.y
< bounds
.top
)
438 // index(bounds) greater then key(point)
441 // index(bounds) less then key(point)
449 // ============================================================================
450 // data browser based implementation
451 // ============================================================================
453 wxMacListBoxItem::wxMacListBoxItem()
458 wxMacListBoxItem::~wxMacListBoxItem()
462 void wxMacListBoxItem::Notification(wxMacDataItemBrowserControl
*owner
,
463 DataBrowserItemNotification message
,
464 DataBrowserItemDataRef itemData
) const
466 wxMacDataBrowserListControl
*lb
= dynamic_cast<wxMacDataBrowserListControl
*>(owner
);
468 // we want to depend on as little as possible to make sure tear-down of controls is safe
470 if ( message
== kDataBrowserItemRemoved
)
472 if ( lb
!= NULL
&& lb
->GetClientDataType() == wxClientData_Object
)
474 delete (wxClientData
*) (m_data
);
481 wxListBox
*list
= wxDynamicCast( owner
->GetPeer() , wxListBox
);
482 wxCHECK_RET( list
!= NULL
, wxT("Listbox expected"));
484 bool trigger
= false;
485 wxCommandEvent
event( wxEVT_COMMAND_LISTBOX_SELECTED
, list
->GetId() );
488 case kDataBrowserItemDeselected
:
489 if ( list
->HasMultipleSelection() )
490 trigger
= !lb
->IsSelectionSuppressed();
493 case kDataBrowserItemSelected
:
494 trigger
= !lb
->IsSelectionSuppressed();
497 case kDataBrowserItemDoubleClicked
:
498 event
.SetEventType( wxEVT_COMMAND_LISTBOX_DOUBLECLICKED
);
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 );
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);
521 wxPostEvent( list
->GetEventHandler(), event
);
525 wxMacDataBrowserListControl::wxMacDataBrowserListControl( wxWindow
*peer
, const wxPoint
& pos
, const wxSize
& size
, long style
)
526 : wxMacDataItemBrowserControl( peer
, pos
, size
, style
)
528 OSStatus err
= noErr
;
529 m_clientDataItemsType
= wxClientData_None
;
530 if ( style
& wxLB_SORT
)
531 m_sortOrder
= SortOrder_Text_Ascending
;
533 DataBrowserSelectionFlags options
= kDataBrowserDragSelect
;
534 if ( style
& wxLB_MULTIPLE
)
536 options
|= kDataBrowserAlwaysExtendSelection
| kDataBrowserCmdTogglesSelection
;
538 else if ( style
& wxLB_EXTENDED
)
540 options
|= kDataBrowserCmdTogglesSelection
;
544 options
|= kDataBrowserSelectOnlyOne
;
546 err
= SetSelectionFlags( options
);
549 DataBrowserListViewColumnDesc columnDesc
;
550 columnDesc
.headerBtnDesc
.titleOffset
= 0;
551 columnDesc
.headerBtnDesc
.version
= kDataBrowserListViewLatestHeaderDesc
;
553 columnDesc
.headerBtnDesc
.btnFontStyle
.flags
=
554 kControlUseFontMask
| kControlUseJustMask
;
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
;
562 columnDesc
.headerBtnDesc
.minimumWidth
= 0;
563 columnDesc
.headerBtnDesc
.maximumWidth
= 10000;
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
;
572 verify_noerr( AddColumn( &columnDesc
, kDataBrowserListViewAppendColumn
) );
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
;
583 verify_noerr( AddColumn( &columnDesc
, kDataBrowserListViewAppendColumn
) );
585 SetDataBrowserSortProperty( m_controlRef
, kTextColumnId
);
586 if ( m_sortOrder
== SortOrder_Text_Ascending
)
588 SetDataBrowserSortProperty( m_controlRef
, kTextColumnId
);
589 SetDataBrowserSortOrder( m_controlRef
, kDataBrowserOrderIncreasing
);
593 SetDataBrowserSortProperty( m_controlRef
, kNumericOrderColumnId
);
594 SetDataBrowserSortOrder( m_controlRef
, kDataBrowserOrderIncreasing
);
597 verify_noerr( AutoSizeColumns() );
598 verify_noerr( SetHiliteStyle(kDataBrowserTableViewFillHilite
) );
599 verify_noerr( SetHeaderButtonHeight( 0 ) );
600 err
= SetHasScrollBars( (style
& wxHSCROLL
) != 0 , true );
602 // shouldn't be necessary anymore under 10.2
603 m_peer
->SetData( kControlNoPart
, kControlDataBrowserIncludesFrameAndFocusTag
, (Boolean
)false );
604 m_peer
->SetNeedsFocusRect( true );
608 wxMacDataBrowserListControl::~wxMacDataBrowserListControl()
612 wxWindow
* wxMacDataBrowserListControl::GetPeer() const
614 return wxDynamicCast( wxMacControl::GetPeer() , wxWindow
);
617 wxMacDataItem
* wxMacDataBrowserListControl::CreateItem()
619 return new wxMacListBoxItem();
624 // in case we need that one day
626 // ============================================================================
627 // HIView owner-draw-based implementation
628 // ============================================================================
630 static pascal void ListBoxDrawProc(
631 ControlRef browser
, DataBrowserItemID item
, DataBrowserPropertyID property
,
632 DataBrowserItemState itemState
, const Rect
*itemRect
, SInt16 depth
, Boolean isColorDevice
)
634 CFStringRef cfString
;
635 ThemeDrawingState themeState
;
638 GetThemeDrawingState( &themeState
);
639 cfString
= CFStringCreateWithFormat( NULL
, NULL
, CFSTR("Row %d"), item
);
641 // In this sample we handle the "selected" state; all others fall through to our "active" state
642 if ( itemState
== kDataBrowserItemIsSelected
)
644 ThemeBrush colorBrushID
;
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
;
653 colorBrushID
= kThemeBrushPrimaryHighlightColor
;
655 // First paint the hilite rect, then the text on top
656 SetThemePen( colorBrushID
, 32, true );
657 PaintRect( itemRect
);
658 SetThemeDrawingState( themeState
, false );
661 DrawThemeTextBox( cfString
, kThemeApplicationFont
, kThemeStateActive
, true, itemRect
, teFlushDefault
, NULL
);
662 SetThemeDrawingState( themeState
, true );
664 if ( cfString
!= NULL
)
665 CFRelease( cfString
);
671 #endif // wxUSE_LISTBOX