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 wxMacDataBrowserListControl::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(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(GetControlRef(), &scrollx
, &scrolly
);
72 wxCHECK_MSG(err
== noErr
, wxNOT_FOUND
, wxT("Unexpected error from GetDataBrowserScrollPosition"));
75 err
= GetDataBrowserTableViewRowHeight(GetControlRef(), &height
);
76 wxCHECK_MSG(err
== noErr
, wxNOT_FOUND
, wxT("Unexpected error from GetDataBrowserTableViewRowHeight"));
78 wxListBox
*list
= wxDynamicCast( GetWXPeer() , wxListBox
);
80 // these indices are 0-based, as usual, so we need to add 1 to them when
81 // passing them to data browser functions which use 1-based indices
82 int low
= scrolly
/ height
,
83 high
= list
->GetCount() - 1;
85 // search for the first visible item (note that the scroll guess above
86 // is the low bounds of where the item might lie so we only use that as a
87 // starting point - we should reach it within 1 or 2 iterations of the loop)
91 err
= GetDataBrowserItemPartBounds(
92 GetControlRef(), low
+ 1, colId
,
93 kDataBrowserPropertyEnclosingPart
,
94 &bounds
); // note +1 to translate to Mac ID
98 // errDataBrowserItemNotFound is expected as it simply means that the
99 // item is not currently visible -- but other errors are not
100 wxCHECK_MSG( err
== errDataBrowserItemNotFound
, wxNOT_FOUND
,
101 wxT("Unexpected error from GetDataBrowserItemPartBounds") );
106 // NOW do a binary search for where the item lies, searching low again if
107 // we hit an item that isn't visible
108 while ( low
<= high
)
110 int mid
= (low
+ high
) / 2;
113 err
= GetDataBrowserItemPartBounds(
114 GetControlRef(), mid
+ 1, colId
,
115 kDataBrowserPropertyEnclosingPart
,
116 &bounds
); //note +1 to trans to mac id
117 wxCHECK_MSG( err
== noErr
|| err
== errDataBrowserItemNotFound
,
119 wxT("Unexpected error from GetDataBrowserItemPartBounds") );
121 if ( err
== errDataBrowserItemNotFound
)
123 // item not visible, attempt to find a visible one
127 else // visible item, do actual hitttest
129 // if point is within the bounds, return this item (since we assume
130 // all x coords of items are equal we only test the x coord in
132 if ((point
.x
>= bounds
.left
&& point
.x
<= bounds
.right
) &&
133 (point
.y
>= bounds
.top
&& point
.y
<= bounds
.bottom
) )
139 if ( point
.y
< bounds
.top
)
140 // index(bounds) greater than key(point)
143 // index(bounds) less than key(point)
151 // ============================================================================
152 // data browser based implementation
153 // ============================================================================
155 wxMacListBoxItem::wxMacListBoxItem()
160 wxMacListBoxItem::~wxMacListBoxItem()
164 OSStatus
wxMacListBoxItem::GetSetData(wxMacDataItemBrowserControl
*owner
,
165 DataBrowserPropertyID property
,
166 DataBrowserItemDataRef itemData
,
169 wxMacDataBrowserListControl
*lb
= wxDynamicCast(owner
,wxMacDataBrowserListControl
);
170 OSStatus err
= errDataBrowserPropertyNotSupported
;
173 if ( property
>= kMinColumnId
)
175 wxMacDataBrowserColumn
* col
= lb
->GetColumnFromProperty( property
);
176 unsigned int n
= owner
->GetLineFromItem( this );
177 wxListBox
*list
= wxDynamicCast( owner
->GetWXPeer() , wxListBox
);
178 wxMacDataBrowserCellValue
valueholder(itemData
);
179 list
->GetValueCallback( n
, col
, valueholder
);
185 if ( property
== kDataBrowserItemIsEditableProperty
)
187 DataBrowserPropertyID propertyToEdit
;
188 GetDataBrowserItemDataProperty( itemData
, &propertyToEdit
);
189 wxMacDataBrowserColumn
* col
= lb
->GetColumnFromProperty( propertyToEdit
);
191 verify_noerr(SetDataBrowserItemDataBooleanValue( itemData
, col
->IsEditable() ));
200 if ( property
>= kMinColumnId
)
202 wxMacDataBrowserColumn
* col
= lb
->GetColumnFromProperty( property
);
204 unsigned int n
= owner
->GetLineFromItem( this );
205 wxListBox
*list
= wxDynamicCast( owner
->GetWXPeer() , wxListBox
);
206 wxMacDataBrowserCellValue
valueholder(itemData
);
207 list
->SetValueCallback( n
, col
, valueholder
);
210 // we have to change this behind the back, since Check() would be triggering another update round
211 bool newVal = !m_isChecked;
212 verify_noerr(SetDataBrowserItemDataButtonValue( itemData, newVal ? kThemeButtonOn : kThemeButtonOff ));
213 m_isChecked = newVal;
216 wxCommandEvent event( wxEVT_CHECKLISTBOX, checklist->GetId() );
217 event.SetInt( owner->GetLineFromItem( this ) );
218 event.SetEventObject( checklist );
219 checklist->HandleWindowEvent( event );
226 // call inherited if not ours
227 if ( err
== errDataBrowserPropertyNotSupported
)
229 err
= wxMacDataItem::GetSetData(owner
, property
, itemData
, changeValue
);
235 void wxMacListBoxItem::Notification(wxMacDataItemBrowserControl
*owner
,
236 DataBrowserItemNotification message
,
237 DataBrowserItemDataRef
WXUNUSED(itemData
) ) const
239 wxMacDataBrowserListControl
*lb
= wxDynamicCast(owner
,wxMacDataBrowserListControl
);
241 // we want to depend on as little as possible to make sure tear-down of controls is safe
243 if ( message
== kDataBrowserItemRemoved
)
249 wxListBox
*list
= wxDynamicCast( lb
->GetWXPeer() , wxListBox
);
250 wxCHECK_RET( list
!= NULL
, wxT("Listbox expected"));
252 if (message
== kDataBrowserItemDoubleClicked
)
254 unsigned int n
= owner
->GetLineFromItem( this );
255 list
->HandleLineEvent( n
, true );
260 IMPLEMENT_DYNAMIC_CLASS( wxMacDataBrowserListControl
, wxMacDataItemBrowserControl
)
262 wxMacDataBrowserListControl::wxMacDataBrowserListControl( wxWindow
*peer
, const wxPoint
& pos
, const wxSize
& size
, long style
)
263 : wxMacDataItemBrowserControl( peer
, pos
, size
, style
)
267 OSStatus err
= noErr
;
268 m_clientDataItemsType
= wxClientData_None
;
269 if ( style
& wxLB_SORT
)
270 m_sortOrder
= SortOrder_Text_Ascending
;
272 DataBrowserSelectionFlags options
= kDataBrowserDragSelect
;
273 if ( style
& wxLB_MULTIPLE
)
275 options
|= kDataBrowserAlwaysExtendSelection
| kDataBrowserCmdTogglesSelection
;
277 else if ( style
& wxLB_EXTENDED
)
279 options
|= kDataBrowserCmdTogglesSelection
;
283 options
|= kDataBrowserSelectOnlyOne
;
285 err
= SetSelectionFlags( options
);
288 DataBrowserListViewColumnDesc columnDesc
;
289 columnDesc
.headerBtnDesc
.titleOffset
= 0;
290 columnDesc
.headerBtnDesc
.version
= kDataBrowserListViewLatestHeaderDesc
;
292 columnDesc
.headerBtnDesc
.btnFontStyle
.flags
=
293 kControlUseFontMask
| kControlUseJustMask
;
295 columnDesc
.headerBtnDesc
.btnContentInfo
.contentType
= kControlNoContent
;
296 columnDesc
.headerBtnDesc
.btnFontStyle
.just
= teFlushDefault
;
297 columnDesc
.headerBtnDesc
.btnFontStyle
.font
= kControlFontViewSystemFont
;
298 columnDesc
.headerBtnDesc
.btnFontStyle
.style
= normal
;
299 columnDesc
.headerBtnDesc
.titleString
= NULL
;
301 columnDesc.headerBtnDesc.minimumWidth = 0;
302 columnDesc.headerBtnDesc.maximumWidth = 10000;
304 columnDesc.propertyDesc.propertyID = kTextColumnId;
305 columnDesc.propertyDesc.propertyType = kDataBrowserTextType;
306 columnDesc.propertyDesc.propertyFlags = kDataBrowserTableViewSelectionColumn;
307 columnDesc.propertyDesc.propertyFlags |= kDataBrowserListViewTypeSelectColumn;
309 verify_noerr( AddColumn( &columnDesc, kDataBrowserListViewAppendColumn ) );
311 columnDesc
.headerBtnDesc
.minimumWidth
= 0;
312 columnDesc
.headerBtnDesc
.maximumWidth
= 0;
313 columnDesc
.propertyDesc
.propertyID
= kNumericOrderColumnId
;
314 columnDesc
.propertyDesc
.propertyType
= kDataBrowserPropertyRelevanceRankPart
;
315 columnDesc
.propertyDesc
.propertyFlags
= kDataBrowserTableViewSelectionColumn
;
316 columnDesc
.propertyDesc
.propertyFlags
|= kDataBrowserListViewTypeSelectColumn
;
318 verify_noerr( AddColumn( &columnDesc
, kDataBrowserListViewAppendColumn
) );
321 SetDataBrowserSortProperty( m_controlRef , kTextColumnId);
322 if ( m_sortOrder == SortOrder_Text_Ascending )
324 SetDataBrowserSortProperty( m_controlRef , kTextColumnId);
325 SetDataBrowserSortOrder( m_controlRef , kDataBrowserOrderIncreasing);
330 SetDataBrowserSortProperty( m_controlRef
, kNumericOrderColumnId
);
331 SetDataBrowserSortOrder( m_controlRef
, kDataBrowserOrderIncreasing
);
334 verify_noerr( AutoSizeColumns() );
335 verify_noerr( SetHiliteStyle(kDataBrowserTableViewFillHilite
) );
336 verify_noerr( SetHeaderButtonHeight( 0 ) );
337 err
= SetHasScrollBars( (style
& wxHSCROLL
) != 0 , true );
339 // shouldn't be necessary anymore under 10.2
340 GetPeer()->SetData( kControlNoPart
, kControlDataBrowserIncludesFrameAndFocusTag
, (Boolean
)false );
341 GetPeer()->SetNeedsFocusRect( true );
345 wxMacDataBrowserListControl::~wxMacDataBrowserListControl()
349 void wxMacDataBrowserListControl::ItemNotification(
350 DataBrowserItemID itemID
,
351 DataBrowserItemNotification message
,
352 DataBrowserItemDataRef itemData
)
354 wxListBox
*list
= wxDynamicCast( GetWXPeer() , wxListBox
);
355 wxCHECK_RET( list
!= NULL
, wxT("Listbox expected"));
357 if (list
->HasMultipleSelection() && (message
== kDataBrowserSelectionSetChanged
) && (!list
->MacGetBlockEvents()))
359 list
->CalcAndSendEvent();
363 if ((message
== kDataBrowserSelectionSetChanged
) && (!list
->MacGetBlockEvents()))
365 wxCommandEvent
event( wxEVT_LISTBOX
, list
->GetId() );
367 int sel
= list
->GetSelection();
368 if ((sel
< 0) || (sel
> (int) list
->GetCount())) // OS X can select an item below the last item (why?)
370 list
->HandleLineEvent( sel
, false );
374 // call super for item level(wxMacDataItem->Notification) callback processing
375 wxMacDataItemBrowserControl::ItemNotification( itemID
, message
, itemData
);
380 wxWindow * wxMacDataBrowserListControl::GetPeer() const
382 return wxDynamicCast( wxMacControl::GetWX() , wxWindow );
390 wxMacDataBrowserColumn
* wxMacDataBrowserListControl::DoInsertColumn( unsigned int pos
, DataBrowserPropertyID property
,
391 const wxString
& title
, bool editable
,
392 DataBrowserPropertyType colType
, SInt16 just
, int width
)
394 DataBrowserListViewColumnDesc columnDesc
;
395 columnDesc
.headerBtnDesc
.titleOffset
= 0;
396 columnDesc
.headerBtnDesc
.version
= kDataBrowserListViewLatestHeaderDesc
;
398 columnDesc
.headerBtnDesc
.btnFontStyle
.flags
=
399 kControlUseFontMask
| kControlUseJustMask
;
401 columnDesc
.headerBtnDesc
.btnContentInfo
.contentType
= kControlContentTextOnly
;
402 columnDesc
.headerBtnDesc
.btnFontStyle
.just
= just
;
403 columnDesc
.headerBtnDesc
.btnFontStyle
.font
= kControlFontViewSystemFont
;
404 columnDesc
.headerBtnDesc
.btnFontStyle
.style
= normal
;
406 // TODO: Why is m_font not defined when we enter wxLC_LIST mode, but is
407 // defined for other modes?
410 enc
= m_font
.GetEncoding();
412 enc
= wxLocale::GetSystemEncoding();
413 wxCFStringRef
cfTitle( title
, enc
);
414 columnDesc
.headerBtnDesc
.titleString
= cfTitle
;
416 columnDesc
.headerBtnDesc
.minimumWidth
= 0;
417 columnDesc
.headerBtnDesc
.maximumWidth
= 30000;
419 columnDesc
.propertyDesc
.propertyID
= property
;
420 columnDesc
.propertyDesc
.propertyType
= colType
;
421 columnDesc
.propertyDesc
.propertyFlags
= kDataBrowserListViewSortableColumn
;
422 columnDesc
.propertyDesc
.propertyFlags
|= kDataBrowserListViewTypeSelectColumn
;
423 columnDesc
.propertyDesc
.propertyFlags
|= kDataBrowserListViewNoGapForIconInHeaderButton
;
426 columnDesc
.propertyDesc
.propertyFlags
|= kDataBrowserPropertyIsMutable
;
428 verify_noerr( AddColumn( &columnDesc
, pos
) );
432 wxMacDataBrowserControl::SetColumnWidth(property
, width
);
435 wxMacDataBrowserColumn
*col
= new wxMacDataBrowserColumn( property
, colType
, editable
);
437 m_columns
.Insert( col
, pos
);
442 wxListWidgetColumn
* wxMacDataBrowserListControl::InsertTextColumn( unsigned pos
, const wxString
& title
, bool editable
,
443 wxAlignment just
, int defaultWidth
)
445 DataBrowserPropertyID property
= kMinColumnId
+ m_nextColumnId
++;
447 SInt16 j
= teFlushLeft
;
448 if ( just
& wxALIGN_RIGHT
)
450 else if ( just
& wxALIGN_CENTER_HORIZONTAL
)
453 return DoInsertColumn( pos
, property
, title
, editable
, kDataBrowserTextType
, just
, defaultWidth
);
456 wxListWidgetColumn
* wxMacDataBrowserListControl::InsertCheckColumn( unsigned pos
, const wxString
& title
, bool editable
,
457 wxAlignment just
, int defaultWidth
)
459 DataBrowserPropertyID property
= kMinColumnId
+ m_nextColumnId
++;
461 SInt16 j
= teFlushLeft
;
462 if ( just
& wxALIGN_RIGHT
)
464 else if ( just
& wxALIGN_CENTER_HORIZONTAL
)
467 return DoInsertColumn( pos
, property
, title
, editable
, kDataBrowserCheckboxType
, just
, defaultWidth
);
470 wxMacDataBrowserColumn
* wxMacDataBrowserListControl::GetColumnFromProperty( DataBrowserPropertyID property
)
472 for ( unsigned int i
= 0; i
< m_columns
.size() ; ++ i
)
473 if ( m_columns
[i
]->GetProperty() == property
)
480 wxMacDataItem* wxMacDataBrowserListControl::ListGetLineItem( unsigned int n )
482 return (wxMacDataItem*) GetItemFromLine(n);
486 unsigned int wxMacDataBrowserListControl::ListGetCount() const
488 return MacGetCount();
491 void wxMacDataBrowserListControl::ListDelete( unsigned int n
)
496 void wxMacDataBrowserListControl::ListInsert( unsigned int n
)
498 MacInsert( n
, new wxMacListBoxItem() );
501 void wxMacDataBrowserListControl::ListClear()
506 void wxMacDataBrowserListControl::ListDeselectAll()
508 wxMacDataItemBrowserSelectionSuppressor
suppressor(this);
509 SetSelectedAllItems( kDataBrowserItemsRemove
);
512 void wxMacDataBrowserListControl::ListSetSelection( unsigned int n
, bool select
, bool multi
)
514 wxMacDataItem
* item
= (wxMacDataItem
*) GetItemFromLine(n
);
515 wxMacDataItemBrowserSelectionSuppressor
suppressor(this);
517 if ( IsItemSelected( item
) != select
)
520 SetSelectedItem( item
, multi
? kDataBrowserItemsAdd
: kDataBrowserItemsAssign
);
522 SetSelectedItem( item
, kDataBrowserItemsRemove
);
528 bool wxMacDataBrowserListControl::ListIsSelected( unsigned int n
) const
530 wxMacDataItem
* item
= (wxMacDataItem
*) GetItemFromLine(n
);
531 return IsItemSelected( item
);
534 int wxMacDataBrowserListControl::ListGetSelection() const
536 wxMacDataItemPtr first
, last
;
537 GetSelectionAnchor( &first
, &last
);
541 return GetLineFromItem( first
);
547 int wxMacDataBrowserListControl::ListGetSelections( wxArrayInt
& aSelections
) const
550 wxArrayMacDataItemPtr selectedItems
;
551 GetItems( wxMacDataBrowserRootContainer
, false , kDataBrowserItemIsSelected
, selectedItems
);
553 int count
= selectedItems
.GetCount();
555 for ( int i
= 0; i
< count
; ++i
)
557 aSelections
.Add(GetLineFromItem(selectedItems
[i
]));
563 void wxMacDataBrowserListControl::ListScrollTo( unsigned int n
)
566 GetScrollPosition( &top
, &left
) ;
567 wxMacDataItem
* item
= (wxMacDataItem
*) GetItemFromLine( n
);
569 // there is a bug in RevealItem that leads to situations
570 // in large lists, where the item does not get scrolled
571 // into sight, so we do a pre-scroll if necessary
573 GetRowHeight( (DataBrowserItemID
) item
, &height
) ;
574 UInt32 linetop
= n
* ((UInt32
) height
);
575 UInt32 linebottom
= linetop
+ height
;
577 GetControlBounds( m_controlRef
, &rect
);
579 if ( linetop
< top
|| linebottom
> (top
+ rect
.bottom
- rect
.top
) )
580 SetScrollPosition( wxMax( n
-2, 0 ) * ((UInt32
)height
) , left
) ;
582 RevealItem( item
, kDataBrowserRevealWithoutSelecting
);
585 void wxMacDataBrowserListControl::UpdateLine( unsigned int n
, wxListWidgetColumn
* col
)
587 wxMacDataBrowserColumn
* dbcol
= dynamic_cast<wxMacDataBrowserColumn
*> (col
);
588 wxMacDataItem
* item
= (wxMacDataItem
*) GetItemFromLine( n
);
589 UpdateItem(wxMacDataBrowserRootContainer
, item
, dbcol
? dbcol
->GetProperty() : kDataBrowserNoItem
);
592 void wxMacDataBrowserListControl::UpdateLineToEnd( unsigned int n
)
594 // with databrowser inserting does not need updating the entire model, it's done by databrowser itself
595 wxMacDataItem
* item
= (wxMacDataItem
*) GetItemFromLine( n
);
596 UpdateItem(wxMacDataBrowserRootContainer
, item
, kDataBrowserNoItem
);
601 void wxMacDataBrowserCellValue::Set( CFStringRef value
)
603 SetDataBrowserItemDataText( m_data
, value
);
606 void wxMacDataBrowserCellValue::Set( const wxString
& value
)
608 wxCFStringRef
cf(value
);
609 SetDataBrowserItemDataText( m_data
, (CFStringRef
) cf
);
612 void wxMacDataBrowserCellValue::Set( int value
)
614 SetDataBrowserItemDataValue( m_data
, value
);
617 void wxMacDataBrowserCellValue::Check( bool check
)
619 SetDataBrowserItemDataButtonValue( m_data
, check
? kThemeButtonOn
: kThemeButtonOff
);
622 int wxMacDataBrowserCellValue::GetIntValue() const
625 GetDataBrowserItemDataValue( m_data
, &value
);
629 wxString
wxMacDataBrowserCellValue::GetStringValue() const
632 GetDataBrowserItemDataText ( m_data
, &value
);
633 wxCFStringRef
cf(value
);
634 return cf
.AsString();
639 // in case we need that one day
641 // ============================================================================
642 // HIView owner-draw-based implementation
643 // ============================================================================
645 static pascal void ListBoxDrawProc(
646 ControlRef browser
, DataBrowserItemID item
, DataBrowserPropertyID property
,
647 DataBrowserItemState itemState
, const Rect
*itemRect
, SInt16 depth
, Boolean isColorDevice
)
649 CFStringRef cfString
;
650 ThemeDrawingState themeState
;
653 GetThemeDrawingState( &themeState
);
654 cfString
= CFStringCreateWithFormat( NULL
, NULL
, CFSTR("Row %d"), item
);
656 // In this sample we handle the "selected" state; all others fall through to our "active" state
657 if ( itemState
== kDataBrowserItemIsSelected
)
659 ThemeBrush colorBrushID
;
661 // TODO: switch over to wxSystemSettingsNative::GetColour() when kThemeBrushSecondaryHighlightColor
662 // is incorporated Panther DB starts using kThemeBrushSecondaryHighlightColor
663 // for inactive browser highlighting
664 if ( !IsControlActive( browser
) )
665 colorBrushID
= kThemeBrushSecondaryHighlightColor
;
667 colorBrushID
= kThemeBrushPrimaryHighlightColor
;
669 // First paint the hilite rect, then the text on top
670 SetThemePen( colorBrushID
, 32, true );
671 PaintRect( itemRect
);
672 SetThemeDrawingState( themeState
, false );
675 DrawThemeTextBox( cfString
, kThemeApplicationFont
, kThemeStateActive
, true, itemRect
, teFlushDefault
, NULL
);
676 SetThemeDrawingState( themeState
, true );
678 if ( cfString
!= NULL
)
679 CFRelease( cfString
);
685 #endif // wxUSE_LISTBOX