1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/osx/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 #include "wx/osx/private.h"
29 // ============================================================================
30 // list box control implementation
31 // ============================================================================
33 wxWidgetImplType
* wxWidgetImpl::CreateListBox( wxWindowMac
* wxpeer
,
34 wxWindowMac
* WXUNUSED(parent
),
35 wxWindowID
WXUNUSED(id
),
39 long WXUNUSED(extraStyle
))
41 wxMacDataBrowserListControl
* control
= new wxMacDataBrowserListControl( wxpeer
, pos
, size
, style
);
42 // TODO CHECK control->SetClientDataType( m_clientDataItemsType );
46 int wxListBox::DoListHitTest(const wxPoint
& inpoint
) const
50 // There are few reasons why this is complicated:
51 // 1) There is no native HitTest function for Mac
52 // 2) GetDataBrowserItemPartBounds only works on visible items
53 // 3) We can't do it through GetDataBrowserTableView[Item]RowHeight
54 // because what it returns is basically inaccurate in the context
55 // of the coordinates we want here, but we use this as a guess
56 // for where the first visible item lies
58 wxPoint point
= inpoint
;
60 // get column property ID (req. for call to itempartbounds)
61 DataBrowserTableViewColumnID colId
= 0;
62 err
= GetDataBrowserTableViewColumnProperty(m_peer
->GetControlRef(), 0, &colId
);
63 wxCHECK_MSG(err
== noErr
, wxNOT_FOUND
, wxT("Unexpected error from GetDataBrowserTableViewColumnProperty"));
65 // OK, first we need to find the first visible item we have -
66 // this will be the "low" for our binary search. There is no real
67 // easy way around this, as we will need to do a SLOW linear search
68 // until we find a visible item, but we can do a cheap calculation
69 // via the row height to speed things up a bit
70 UInt32 scrollx
, scrolly
;
71 err
= GetDataBrowserScrollPosition(m_peer
->GetControlRef(), &scrollx
, &scrolly
);
72 wxCHECK_MSG(err
== noErr
, wxNOT_FOUND
, wxT("Unexpected error from GetDataBrowserScrollPosition"));
75 err
= GetDataBrowserTableViewRowHeight(m_peer
->GetControlRef(), &height
);
76 wxCHECK_MSG(err
== noErr
, wxNOT_FOUND
, wxT("Unexpected error from GetDataBrowserTableViewRowHeight"));
78 // these indices are 0-based, as usual, so we need to add 1 to them when
79 // passing them to data browser functions which use 1-based indices
80 int low
= scrolly
/ height
,
81 high
= GetCount() - 1;
83 // search for the first visible item (note that the scroll guess above
84 // is the low bounds of where the item might lie so we only use that as a
85 // starting point - we should reach it within 1 or 2 iterations of the loop)
89 err
= GetDataBrowserItemPartBounds(
90 m_peer
->GetControlRef(), low
+ 1, colId
,
91 kDataBrowserPropertyEnclosingPart
,
92 &bounds
); // note +1 to translate to Mac ID
96 // errDataBrowserItemNotFound is expected as it simply means that the
97 // item is not currently visible -- but other errors are not
98 wxCHECK_MSG( err
== errDataBrowserItemNotFound
, wxNOT_FOUND
,
99 wxT("Unexpected error from GetDataBrowserItemPartBounds") );
104 // NOW do a binary search for where the item lies, searching low again if
105 // we hit an item that isn't visible
106 while ( low
<= high
)
108 int mid
= (low
+ high
) / 2;
111 err
= GetDataBrowserItemPartBounds(
112 m_peer
->GetControlRef(), mid
+ 1, colId
,
113 kDataBrowserPropertyEnclosingPart
,
114 &bounds
); //note +1 to trans to mac id
115 wxCHECK_MSG( err
== noErr
|| err
== errDataBrowserItemNotFound
,
117 wxT("Unexpected error from GetDataBrowserItemPartBounds") );
119 if ( err
== errDataBrowserItemNotFound
)
121 // item not visible, attempt to find a visible one
125 else // visible item, do actual hitttest
127 // if point is within the bounds, return this item (since we assume
128 // all x coords of items are equal we only test the x coord in
130 if ((point
.x
>= bounds
.left
&& point
.x
<= bounds
.right
) &&
131 (point
.y
>= bounds
.top
&& point
.y
<= bounds
.bottom
) )
137 if ( point
.y
< bounds
.top
)
138 // index(bounds) greater then key(point)
141 // index(bounds) less then key(point)
149 // ============================================================================
150 // data browser based implementation
151 // ============================================================================
153 wxMacListBoxItem::wxMacListBoxItem()
158 wxMacListBoxItem::~wxMacListBoxItem()
162 OSStatus
wxMacListBoxItem::GetSetData(wxMacDataItemBrowserControl
*owner
,
163 DataBrowserPropertyID property
,
164 DataBrowserItemDataRef itemData
,
167 wxMacDataBrowserListControl
*lb
= wxDynamicCast(owner
,wxMacDataBrowserListControl
);
168 OSStatus err
= errDataBrowserPropertyNotSupported
;
171 if ( property
>= kMinColumnId
)
173 wxMacDataBrowserColumn
* col
= lb
->GetColumnFromProperty( property
);
174 unsigned int n
= owner
->GetLineFromItem( this );
175 wxListBox
*list
= wxDynamicCast( owner
->GetWXPeer() , wxListBox
);
176 wxMacDataBrowserCellValue
valueholder(itemData
);
177 list
->GetValueCallback( n
, col
, valueholder
);
183 if ( property
== kDataBrowserItemIsEditableProperty
)
185 DataBrowserPropertyID propertyToEdit
;
186 GetDataBrowserItemDataProperty( itemData
, &propertyToEdit
);
187 wxMacDataBrowserColumn
* col
= lb
->GetColumnFromProperty( propertyToEdit
);
189 verify_noerr(SetDataBrowserItemDataBooleanValue( itemData
, col
->IsEditable() ));
198 if ( property
>= kMinColumnId
)
200 wxMacDataBrowserColumn
* col
= lb
->GetColumnFromProperty( property
);
202 unsigned int n
= owner
->GetLineFromItem( this );
203 wxListBox
*list
= wxDynamicCast( owner
->GetWXPeer() , wxListBox
);
204 wxMacDataBrowserCellValue
valueholder(itemData
);
205 list
->SetValueCallback( n
, col
, valueholder
);
208 // we have to change this behind the back, since Check() would be triggering another update round
209 bool newVal = !m_isChecked;
210 verify_noerr(SetDataBrowserItemDataButtonValue( itemData, newVal ? kThemeButtonOn : kThemeButtonOff ));
211 m_isChecked = newVal;
214 wxCommandEvent event( wxEVT_COMMAND_CHECKLISTBOX_TOGGLED, checklist->GetId() );
215 event.SetInt( owner->GetLineFromItem( this ) );
216 event.SetEventObject( checklist );
217 checklist->HandleWindowEvent( event );
224 // call inherited if not ours
225 if ( err
== errDataBrowserPropertyNotSupported
)
227 err
= wxMacDataItem::GetSetData(owner
, property
, itemData
, changeValue
);
233 void wxMacListBoxItem::Notification(wxMacDataItemBrowserControl
*owner
,
234 DataBrowserItemNotification message
,
235 DataBrowserItemDataRef
WXUNUSED(itemData
) ) const
237 wxMacDataBrowserListControl
*lb
= wxDynamicCast(owner
,wxMacDataBrowserListControl
);
239 // we want to depend on as little as possible to make sure tear-down of controls is safe
241 if ( message
== kDataBrowserItemRemoved
)
247 wxListBox
*list
= wxDynamicCast( lb
->GetWXPeer() , wxListBox
);
248 wxCHECK_RET( list
!= NULL
, wxT("Listbox expected"));
250 if (message
== kDataBrowserItemDoubleClicked
)
252 unsigned int n
= owner
->GetLineFromItem( this );
253 list
->HandleLineEvent( n
, true );
258 IMPLEMENT_DYNAMIC_CLASS( wxMacDataBrowserListControl
, wxMacDataItemBrowserControl
)
260 wxMacDataBrowserListControl::wxMacDataBrowserListControl( wxWindow
*peer
, const wxPoint
& pos
, const wxSize
& size
, long style
)
261 : wxMacDataItemBrowserControl( peer
, pos
, size
, style
)
265 OSStatus err
= noErr
;
266 m_clientDataItemsType
= wxClientData_None
;
267 if ( style
& wxLB_SORT
)
268 m_sortOrder
= SortOrder_Text_Ascending
;
270 DataBrowserSelectionFlags options
= kDataBrowserDragSelect
;
271 if ( style
& wxLB_MULTIPLE
)
273 options
|= kDataBrowserAlwaysExtendSelection
| kDataBrowserCmdTogglesSelection
;
275 else if ( style
& wxLB_EXTENDED
)
277 options
|= kDataBrowserCmdTogglesSelection
;
281 options
|= kDataBrowserSelectOnlyOne
;
283 err
= SetSelectionFlags( options
);
286 DataBrowserListViewColumnDesc columnDesc
;
287 columnDesc
.headerBtnDesc
.titleOffset
= 0;
288 columnDesc
.headerBtnDesc
.version
= kDataBrowserListViewLatestHeaderDesc
;
290 columnDesc
.headerBtnDesc
.btnFontStyle
.flags
=
291 kControlUseFontMask
| kControlUseJustMask
;
293 columnDesc
.headerBtnDesc
.btnContentInfo
.contentType
= kControlNoContent
;
294 columnDesc
.headerBtnDesc
.btnFontStyle
.just
= teFlushDefault
;
295 columnDesc
.headerBtnDesc
.btnFontStyle
.font
= kControlFontViewSystemFont
;
296 columnDesc
.headerBtnDesc
.btnFontStyle
.style
= normal
;
297 columnDesc
.headerBtnDesc
.titleString
= NULL
;
299 columnDesc.headerBtnDesc.minimumWidth = 0;
300 columnDesc.headerBtnDesc.maximumWidth = 10000;
302 columnDesc.propertyDesc.propertyID = kTextColumnId;
303 columnDesc.propertyDesc.propertyType = kDataBrowserTextType;
304 columnDesc.propertyDesc.propertyFlags = kDataBrowserTableViewSelectionColumn;
305 columnDesc.propertyDesc.propertyFlags |= kDataBrowserListViewTypeSelectColumn;
307 verify_noerr( AddColumn( &columnDesc, kDataBrowserListViewAppendColumn ) );
309 columnDesc
.headerBtnDesc
.minimumWidth
= 0;
310 columnDesc
.headerBtnDesc
.maximumWidth
= 0;
311 columnDesc
.propertyDesc
.propertyID
= kNumericOrderColumnId
;
312 columnDesc
.propertyDesc
.propertyType
= kDataBrowserPropertyRelevanceRankPart
;
313 columnDesc
.propertyDesc
.propertyFlags
= kDataBrowserTableViewSelectionColumn
;
314 columnDesc
.propertyDesc
.propertyFlags
|= kDataBrowserListViewTypeSelectColumn
;
316 verify_noerr( AddColumn( &columnDesc
, kDataBrowserListViewAppendColumn
) );
319 SetDataBrowserSortProperty( m_controlRef , kTextColumnId);
320 if ( m_sortOrder == SortOrder_Text_Ascending )
322 SetDataBrowserSortProperty( m_controlRef , kTextColumnId);
323 SetDataBrowserSortOrder( m_controlRef , kDataBrowserOrderIncreasing);
328 SetDataBrowserSortProperty( m_controlRef
, kNumericOrderColumnId
);
329 SetDataBrowserSortOrder( m_controlRef
, kDataBrowserOrderIncreasing
);
332 verify_noerr( AutoSizeColumns() );
333 verify_noerr( SetHiliteStyle(kDataBrowserTableViewFillHilite
) );
334 verify_noerr( SetHeaderButtonHeight( 0 ) );
335 err
= SetHasScrollBars( (style
& wxHSCROLL
) != 0 , true );
337 // shouldn't be necessary anymore under 10.2
338 m_peer
->SetData( kControlNoPart
, kControlDataBrowserIncludesFrameAndFocusTag
, (Boolean
)false );
339 m_peer
->SetNeedsFocusRect( true );
343 wxMacDataBrowserListControl::~wxMacDataBrowserListControl()
347 void wxMacDataBrowserListControl::ItemNotification(
348 DataBrowserItemID itemID
,
349 DataBrowserItemNotification message
,
350 DataBrowserItemDataRef itemData
)
352 wxListBox
*list
= wxDynamicCast( GetWXPeer() , wxListBox
);
353 wxCHECK_RET( list
!= NULL
, wxT("Listbox expected"));
355 if (list
->HasMultipleSelection() && (message
== kDataBrowserSelectionSetChanged
) && (!list
->MacGetBlockEvents()))
357 list
->CalcAndSendEvent();
361 if ((message
== kDataBrowserSelectionSetChanged
) && (!list
->MacGetBlockEvents()))
363 wxCommandEvent
event( wxEVT_COMMAND_LISTBOX_SELECTED
, list
->GetId() );
365 int sel
= list
->GetSelection();
366 if ((sel
< 0) || (sel
> (int) list
->GetCount())) // OS X can select an item below the last item (why?)
368 list
->HandleLineEvent( sel
, false );
372 // call super for item level(wxMacDataItem->Notification) callback processing
373 wxMacDataItemBrowserControl::ItemNotification( itemID
, message
, itemData
);
378 wxWindow * wxMacDataBrowserListControl::GetPeer() const
380 return wxDynamicCast( wxMacControl::GetWX() , wxWindow );
388 wxMacDataBrowserColumn
* wxMacDataBrowserListControl::DoInsertColumn( unsigned int pos
, DataBrowserPropertyID property
,
389 const wxString
& title
, bool editable
,
390 DataBrowserPropertyType colType
, SInt16 just
, int width
)
392 DataBrowserListViewColumnDesc columnDesc
;
393 columnDesc
.headerBtnDesc
.titleOffset
= 0;
394 columnDesc
.headerBtnDesc
.version
= kDataBrowserListViewLatestHeaderDesc
;
396 columnDesc
.headerBtnDesc
.btnFontStyle
.flags
=
397 kControlUseFontMask
| kControlUseJustMask
;
399 columnDesc
.headerBtnDesc
.btnContentInfo
.contentType
= kControlContentTextOnly
;
400 columnDesc
.headerBtnDesc
.btnFontStyle
.just
= just
;
401 columnDesc
.headerBtnDesc
.btnFontStyle
.font
= kControlFontViewSystemFont
;
402 columnDesc
.headerBtnDesc
.btnFontStyle
.style
= normal
;
404 // TODO: Why is m_font not defined when we enter wxLC_LIST mode, but is
405 // defined for other modes?
408 enc
= m_font
.GetEncoding();
410 enc
= wxLocale::GetSystemEncoding();
411 wxCFStringRef
cfTitle( title
, enc
);
412 columnDesc
.headerBtnDesc
.titleString
= cfTitle
;
414 columnDesc
.headerBtnDesc
.minimumWidth
= 0;
415 columnDesc
.headerBtnDesc
.maximumWidth
= 30000;
417 columnDesc
.propertyDesc
.propertyID
= property
;
418 columnDesc
.propertyDesc
.propertyType
= colType
;
419 columnDesc
.propertyDesc
.propertyFlags
= kDataBrowserListViewSortableColumn
;
420 columnDesc
.propertyDesc
.propertyFlags
|= kDataBrowserListViewTypeSelectColumn
;
421 columnDesc
.propertyDesc
.propertyFlags
|= kDataBrowserListViewNoGapForIconInHeaderButton
;
424 columnDesc
.propertyDesc
.propertyFlags
|= kDataBrowserPropertyIsMutable
;
426 verify_noerr( AddColumn( &columnDesc
, pos
) );
430 wxMacDataBrowserControl::SetColumnWidth(property
, width
);
433 wxMacDataBrowserColumn
*col
= new wxMacDataBrowserColumn( property
, colType
, editable
);
435 m_columns
.Insert( col
, pos
);
440 wxListWidgetColumn
* wxMacDataBrowserListControl::InsertTextColumn( unsigned pos
, const wxString
& title
, bool editable
,
441 wxAlignment just
, int defaultWidth
)
443 DataBrowserPropertyID property
= kMinColumnId
+ m_nextColumnId
++;
445 SInt16 j
= teFlushLeft
;
446 if ( just
& wxALIGN_RIGHT
)
448 else if ( just
& wxALIGN_CENTER_HORIZONTAL
)
451 return DoInsertColumn( pos
, property
, title
, editable
, kDataBrowserTextType
, just
, defaultWidth
);
454 wxListWidgetColumn
* wxMacDataBrowserListControl::InsertCheckColumn( unsigned pos
, const wxString
& title
, bool editable
,
455 wxAlignment just
, int defaultWidth
)
457 DataBrowserPropertyID property
= kMinColumnId
+ m_nextColumnId
++;
459 SInt16 j
= teFlushLeft
;
460 if ( just
& wxALIGN_RIGHT
)
462 else if ( just
& wxALIGN_CENTER_HORIZONTAL
)
465 return DoInsertColumn( pos
, property
, title
, editable
, kDataBrowserCheckboxType
, just
, defaultWidth
);
468 wxMacDataBrowserColumn
* wxMacDataBrowserListControl::GetColumnFromProperty( DataBrowserPropertyID property
)
470 for ( unsigned int i
= 0; i
< m_columns
.size() ; ++ i
)
471 if ( m_columns
[i
]->GetProperty() == property
)
478 wxMacDataItem* wxMacDataBrowserListControl::ListGetLineItem( unsigned int n )
480 return (wxMacDataItem*) GetItemFromLine(n);
484 unsigned int wxMacDataBrowserListControl::ListGetCount() const
486 return MacGetCount();
489 void wxMacDataBrowserListControl::ListDelete( unsigned int n
)
494 void wxMacDataBrowserListControl::ListInsert( unsigned int n
)
496 MacInsert( n
, new wxMacListBoxItem() );
499 void wxMacDataBrowserListControl::ListClear()
504 void wxMacDataBrowserListControl::ListDeselectAll()
506 wxMacDataItemBrowserSelectionSuppressor
suppressor(this);
507 SetSelectedAllItems( kDataBrowserItemsRemove
);
510 void wxMacDataBrowserListControl::ListSetSelection( unsigned int n
, bool select
, bool multi
)
512 wxMacDataItem
* item
= (wxMacDataItem
*) GetItemFromLine(n
);
513 wxMacDataItemBrowserSelectionSuppressor
suppressor(this);
515 if ( IsItemSelected( item
) != select
)
518 SetSelectedItem( item
, multi
? kDataBrowserItemsAdd
: kDataBrowserItemsAssign
);
520 SetSelectedItem( item
, kDataBrowserItemsRemove
);
526 bool wxMacDataBrowserListControl::ListIsSelected( unsigned int n
) const
528 wxMacDataItem
* item
= (wxMacDataItem
*) GetItemFromLine(n
);
529 return IsItemSelected( item
);
532 int wxMacDataBrowserListControl::ListGetSelection() const
534 wxMacDataItemPtr first
, last
;
535 GetSelectionAnchor( &first
, &last
);
539 return GetLineFromItem( first
);
545 int wxMacDataBrowserListControl::ListGetSelections( wxArrayInt
& aSelections
) const
548 wxArrayMacDataItemPtr selectedItems
;
549 GetItems( wxMacDataBrowserRootContainer
, false , kDataBrowserItemIsSelected
, selectedItems
);
551 int count
= selectedItems
.GetCount();
553 for ( int i
= 0; i
< count
; ++i
)
555 aSelections
.Add(GetLineFromItem(selectedItems
[i
]));
561 void wxMacDataBrowserListControl::ListScrollTo( unsigned int n
)
564 GetScrollPosition( &top
, &left
) ;
565 wxMacDataItem
* item
= (wxMacDataItem
*) GetItemFromLine( n
);
567 // there is a bug in RevealItem that leads to situations
568 // in large lists, where the item does not get scrolled
569 // into sight, so we do a pre-scroll if necessary
571 GetRowHeight( (DataBrowserItemID
) item
, &height
) ;
572 UInt32 linetop
= n
* ((UInt32
) height
);
573 UInt32 linebottom
= linetop
+ height
;
575 GetControlBounds( m_controlRef
, &rect
);
577 if ( linetop
< top
|| linebottom
> (top
+ rect
.bottom
- rect
.top
) )
578 SetScrollPosition( wxMax( n
-2, 0 ) * ((UInt32
)height
) , left
) ;
580 RevealItem( item
, kDataBrowserRevealWithoutSelecting
);
583 void wxMacDataBrowserListControl::UpdateLine( unsigned int n
, wxListWidgetColumn
* col
)
585 wxMacDataBrowserColumn
* dbcol
= dynamic_cast<wxMacDataBrowserColumn
*> (col
);
586 wxMacDataItem
* item
= (wxMacDataItem
*) GetItemFromLine( n
);
587 UpdateItem(wxMacDataBrowserRootContainer
, item
, dbcol
? dbcol
->GetProperty() : kDataBrowserNoItem
);
590 void wxMacDataBrowserListControl::UpdateLineToEnd( unsigned int n
)
592 // with databrowser inserting does not need updating the entire model, it's done by databrowser itself
593 wxMacDataItem
* item
= (wxMacDataItem
*) GetItemFromLine( n
);
594 UpdateItem(wxMacDataBrowserRootContainer
, item
, kDataBrowserNoItem
);
599 void wxMacDataBrowserCellValue::Set( CFStringRef value
)
601 SetDataBrowserItemDataText( m_data
, value
);
604 void wxMacDataBrowserCellValue::Set( const wxString
& value
)
606 wxCFStringRef
cf(value
);
607 SetDataBrowserItemDataText( m_data
, (CFStringRef
) cf
);
610 void wxMacDataBrowserCellValue::Set( int value
)
612 SetDataBrowserItemDataValue( m_data
, value
);
613 // SetDataBrowserItemDataButtonValue( m_data, value ? kThemeButtonOn : kThemeButtonOff);
616 int wxMacDataBrowserCellValue::GetIntValue() const
619 GetDataBrowserItemDataValue( m_data
, &value
);
623 wxString
wxMacDataBrowserCellValue::GetStringValue() const
626 GetDataBrowserItemDataText ( m_data
, &value
);
627 wxCFStringRef
cf(value
);
628 return cf
.AsString();
633 // in case we need that one day
635 // ============================================================================
636 // HIView owner-draw-based implementation
637 // ============================================================================
639 static pascal void ListBoxDrawProc(
640 ControlRef browser
, DataBrowserItemID item
, DataBrowserPropertyID property
,
641 DataBrowserItemState itemState
, const Rect
*itemRect
, SInt16 depth
, Boolean isColorDevice
)
643 CFStringRef cfString
;
644 ThemeDrawingState themeState
;
647 GetThemeDrawingState( &themeState
);
648 cfString
= CFStringCreateWithFormat( NULL
, NULL
, CFSTR("Row %d"), item
);
650 // In this sample we handle the "selected" state; all others fall through to our "active" state
651 if ( itemState
== kDataBrowserItemIsSelected
)
653 ThemeBrush colorBrushID
;
655 // TODO: switch over to wxSystemSettingsNative::GetColour() when kThemeBrushSecondaryHighlightColor
656 // is incorporated Panther DB starts using kThemeBrushSecondaryHighlightColor
657 // for inactive browser highlighting
658 if ( !IsControlActive( browser
) )
659 colorBrushID
= kThemeBrushSecondaryHighlightColor
;
661 colorBrushID
= kThemeBrushPrimaryHighlightColor
;
663 // First paint the hilite rect, then the text on top
664 SetThemePen( colorBrushID
, 32, true );
665 PaintRect( itemRect
);
666 SetThemeDrawingState( themeState
, false );
669 DrawThemeTextBox( cfString
, kThemeApplicationFont
, kThemeStateActive
, true, itemRect
, teFlushDefault
, NULL
);
670 SetThemeDrawingState( themeState
, true );
672 if ( cfString
!= NULL
)
673 CFRelease( cfString
);
679 #endif // wxUSE_LISTBOX