1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/osx/carbon/listbox.cpp
4 // Author: Stefan Csomor
7 // Copyright: (c) Stefan Csomor
8 // Licence: wxWindows licence
9 ///////////////////////////////////////////////////////////////////////////////
11 #include "wx/wxprec.h"
15 #include "wx/listbox.h"
21 #include "wx/settings.h"
22 #include "wx/arrstr.h"
23 #include "wx/dcclient.h"
26 #include "wx/osx/private.h"
28 // ============================================================================
29 // list box control implementation
30 // ============================================================================
32 wxWidgetImplType
* wxWidgetImpl::CreateListBox( wxWindowMac
* wxpeer
,
33 wxWindowMac
* WXUNUSED(parent
),
34 wxWindowID
WXUNUSED(id
),
38 long WXUNUSED(extraStyle
))
40 wxMacDataBrowserListControl
* control
= new wxMacDataBrowserListControl( wxpeer
, pos
, size
, style
);
41 // TODO CHECK control->SetClientDataType( m_clientDataItemsType );
45 int wxMacDataBrowserListControl::DoListHitTest(const wxPoint
& inpoint
) const
49 // There are few reasons why this is complicated:
50 // 1) There is no native HitTest function for Mac
51 // 2) GetDataBrowserItemPartBounds only works on visible items
52 // 3) We can't do it through GetDataBrowserTableView[Item]RowHeight
53 // because what it returns is basically inaccurate in the context
54 // of the coordinates we want here, but we use this as a guess
55 // for where the first visible item lies
57 wxPoint point
= inpoint
;
59 // get column property ID (req. for call to itempartbounds)
60 DataBrowserTableViewColumnID colId
= 0;
61 err
= GetDataBrowserTableViewColumnProperty(GetControlRef(), 0, &colId
);
62 wxCHECK_MSG(err
== noErr
, wxNOT_FOUND
, wxT("Unexpected error from GetDataBrowserTableViewColumnProperty"));
64 // OK, first we need to find the first visible item we have -
65 // this will be the "low" for our binary search. There is no real
66 // easy way around this, as we will need to do a SLOW linear search
67 // until we find a visible item, but we can do a cheap calculation
68 // via the row height to speed things up a bit
69 UInt32 scrollx
, scrolly
;
70 err
= GetDataBrowserScrollPosition(GetControlRef(), &scrollx
, &scrolly
);
71 wxCHECK_MSG(err
== noErr
, wxNOT_FOUND
, wxT("Unexpected error from GetDataBrowserScrollPosition"));
74 err
= GetDataBrowserTableViewRowHeight(GetControlRef(), &height
);
75 wxCHECK_MSG(err
== noErr
, wxNOT_FOUND
, wxT("Unexpected error from GetDataBrowserTableViewRowHeight"));
77 wxListBox
*list
= wxDynamicCast( GetWXPeer() , wxListBox
);
79 // these indices are 0-based, as usual, so we need to add 1 to them when
80 // passing them to data browser functions which use 1-based indices
81 int low
= scrolly
/ height
,
82 high
= list
->GetCount() - 1;
84 // search for the first visible item (note that the scroll guess above
85 // is the low bounds of where the item might lie so we only use that as a
86 // starting point - we should reach it within 1 or 2 iterations of the loop)
90 err
= GetDataBrowserItemPartBounds(
91 GetControlRef(), low
+ 1, colId
,
92 kDataBrowserPropertyEnclosingPart
,
93 &bounds
); // note +1 to translate to Mac ID
97 // errDataBrowserItemNotFound is expected as it simply means that the
98 // item is not currently visible -- but other errors are not
99 wxCHECK_MSG( err
== errDataBrowserItemNotFound
, wxNOT_FOUND
,
100 wxT("Unexpected error from GetDataBrowserItemPartBounds") );
105 // NOW do a binary search for where the item lies, searching low again if
106 // we hit an item that isn't visible
107 while ( low
<= high
)
109 int mid
= (low
+ high
) / 2;
112 err
= GetDataBrowserItemPartBounds(
113 GetControlRef(), mid
+ 1, colId
,
114 kDataBrowserPropertyEnclosingPart
,
115 &bounds
); //note +1 to trans to mac id
116 wxCHECK_MSG( err
== noErr
|| err
== errDataBrowserItemNotFound
,
118 wxT("Unexpected error from GetDataBrowserItemPartBounds") );
120 if ( err
== errDataBrowserItemNotFound
)
122 // item not visible, attempt to find a visible one
126 else // visible item, do actual hitttest
128 // if point is within the bounds, return this item (since we assume
129 // all x coords of items are equal we only test the x coord in
131 if ((point
.x
>= bounds
.left
&& point
.x
<= bounds
.right
) &&
132 (point
.y
>= bounds
.top
&& point
.y
<= bounds
.bottom
) )
138 if ( point
.y
< bounds
.top
)
139 // index(bounds) greater than key(point)
142 // index(bounds) less than key(point)
150 // ============================================================================
151 // data browser based implementation
152 // ============================================================================
154 wxMacListBoxItem::wxMacListBoxItem()
159 wxMacListBoxItem::~wxMacListBoxItem()
163 OSStatus
wxMacListBoxItem::GetSetData(wxMacDataItemBrowserControl
*owner
,
164 DataBrowserPropertyID property
,
165 DataBrowserItemDataRef itemData
,
168 wxMacDataBrowserListControl
*lb
= wxDynamicCast(owner
,wxMacDataBrowserListControl
);
169 OSStatus err
= errDataBrowserPropertyNotSupported
;
172 if ( property
>= kMinColumnId
)
174 wxMacDataBrowserColumn
* col
= lb
->GetColumnFromProperty( property
);
175 unsigned int n
= owner
->GetLineFromItem( this );
176 wxListBox
*list
= wxDynamicCast( owner
->GetWXPeer() , wxListBox
);
177 wxMacDataBrowserCellValue
valueholder(itemData
);
178 list
->GetValueCallback( n
, col
, valueholder
);
184 if ( property
== kDataBrowserItemIsEditableProperty
)
186 DataBrowserPropertyID propertyToEdit
;
187 GetDataBrowserItemDataProperty( itemData
, &propertyToEdit
);
188 wxMacDataBrowserColumn
* col
= lb
->GetColumnFromProperty( propertyToEdit
);
190 verify_noerr(SetDataBrowserItemDataBooleanValue( itemData
, col
->IsEditable() ));
199 if ( property
>= kMinColumnId
)
201 wxMacDataBrowserColumn
* col
= lb
->GetColumnFromProperty( property
);
203 unsigned int n
= owner
->GetLineFromItem( this );
204 wxListBox
*list
= wxDynamicCast( owner
->GetWXPeer() , wxListBox
);
205 wxMacDataBrowserCellValue
valueholder(itemData
);
206 list
->SetValueCallback( n
, col
, valueholder
);
209 // we have to change this behind the back, since Check() would be triggering another update round
210 bool newVal = !m_isChecked;
211 verify_noerr(SetDataBrowserItemDataButtonValue( itemData, newVal ? kThemeButtonOn : kThemeButtonOff ));
212 m_isChecked = newVal;
215 wxCommandEvent event( wxEVT_CHECKLISTBOX, checklist->GetId() );
216 event.SetInt( owner->GetLineFromItem( this ) );
217 event.SetEventObject( checklist );
218 checklist->HandleWindowEvent( event );
225 // call inherited if not ours
226 if ( err
== errDataBrowserPropertyNotSupported
)
228 err
= wxMacDataItem::GetSetData(owner
, property
, itemData
, changeValue
);
234 void wxMacListBoxItem::Notification(wxMacDataItemBrowserControl
*owner
,
235 DataBrowserItemNotification message
,
236 DataBrowserItemDataRef
WXUNUSED(itemData
) ) const
238 wxMacDataBrowserListControl
*lb
= wxDynamicCast(owner
,wxMacDataBrowserListControl
);
240 // we want to depend on as little as possible to make sure tear-down of controls is safe
242 if ( message
== kDataBrowserItemRemoved
)
248 wxListBox
*list
= wxDynamicCast( lb
->GetWXPeer() , wxListBox
);
249 wxCHECK_RET( list
!= NULL
, wxT("Listbox expected"));
251 if (message
== kDataBrowserItemDoubleClicked
)
253 unsigned int n
= owner
->GetLineFromItem( this );
254 list
->HandleLineEvent( n
, true );
259 IMPLEMENT_DYNAMIC_CLASS( wxMacDataBrowserListControl
, wxMacDataItemBrowserControl
)
261 wxMacDataBrowserListControl::wxMacDataBrowserListControl( wxWindow
*peer
, const wxPoint
& pos
, const wxSize
& size
, long style
)
262 : wxMacDataItemBrowserControl( peer
, pos
, size
, style
)
266 OSStatus err
= noErr
;
267 m_clientDataItemsType
= wxClientData_None
;
268 if ( style
& wxLB_SORT
)
269 m_sortOrder
= SortOrder_Text_Ascending
;
271 DataBrowserSelectionFlags options
= kDataBrowserDragSelect
;
272 if ( style
& wxLB_MULTIPLE
)
274 options
|= kDataBrowserAlwaysExtendSelection
| kDataBrowserCmdTogglesSelection
;
276 else if ( style
& wxLB_EXTENDED
)
278 options
|= kDataBrowserCmdTogglesSelection
;
282 options
|= kDataBrowserSelectOnlyOne
;
284 err
= SetSelectionFlags( options
);
287 DataBrowserListViewColumnDesc columnDesc
;
288 columnDesc
.headerBtnDesc
.titleOffset
= 0;
289 columnDesc
.headerBtnDesc
.version
= kDataBrowserListViewLatestHeaderDesc
;
291 columnDesc
.headerBtnDesc
.btnFontStyle
.flags
=
292 kControlUseFontMask
| kControlUseJustMask
;
294 columnDesc
.headerBtnDesc
.btnContentInfo
.contentType
= kControlNoContent
;
295 columnDesc
.headerBtnDesc
.btnFontStyle
.just
= teFlushDefault
;
296 columnDesc
.headerBtnDesc
.btnFontStyle
.font
= kControlFontViewSystemFont
;
297 columnDesc
.headerBtnDesc
.btnFontStyle
.style
= normal
;
298 columnDesc
.headerBtnDesc
.titleString
= NULL
;
300 columnDesc.headerBtnDesc.minimumWidth = 0;
301 columnDesc.headerBtnDesc.maximumWidth = 10000;
303 columnDesc.propertyDesc.propertyID = kTextColumnId;
304 columnDesc.propertyDesc.propertyType = kDataBrowserTextType;
305 columnDesc.propertyDesc.propertyFlags = kDataBrowserTableViewSelectionColumn;
306 columnDesc.propertyDesc.propertyFlags |= kDataBrowserListViewTypeSelectColumn;
308 verify_noerr( AddColumn( &columnDesc, kDataBrowserListViewAppendColumn ) );
310 columnDesc
.headerBtnDesc
.minimumWidth
= 0;
311 columnDesc
.headerBtnDesc
.maximumWidth
= 0;
312 columnDesc
.propertyDesc
.propertyID
= kNumericOrderColumnId
;
313 columnDesc
.propertyDesc
.propertyType
= kDataBrowserPropertyRelevanceRankPart
;
314 columnDesc
.propertyDesc
.propertyFlags
= kDataBrowserTableViewSelectionColumn
;
315 columnDesc
.propertyDesc
.propertyFlags
|= kDataBrowserListViewTypeSelectColumn
;
317 verify_noerr( AddColumn( &columnDesc
, kDataBrowserListViewAppendColumn
) );
320 SetDataBrowserSortProperty( m_controlRef , kTextColumnId);
321 if ( m_sortOrder == SortOrder_Text_Ascending )
323 SetDataBrowserSortProperty( m_controlRef , kTextColumnId);
324 SetDataBrowserSortOrder( m_controlRef , kDataBrowserOrderIncreasing);
329 SetDataBrowserSortProperty( m_controlRef
, kNumericOrderColumnId
);
330 SetDataBrowserSortOrder( m_controlRef
, kDataBrowserOrderIncreasing
);
333 verify_noerr( AutoSizeColumns() );
334 verify_noerr( SetHiliteStyle(kDataBrowserTableViewFillHilite
) );
335 verify_noerr( SetHeaderButtonHeight( 0 ) );
336 err
= SetHasScrollBars( (style
& wxHSCROLL
) != 0 , true );
338 // shouldn't be necessary anymore under 10.2
339 GetPeer()->SetData( kControlNoPart
, kControlDataBrowserIncludesFrameAndFocusTag
, (Boolean
)false );
340 GetPeer()->SetNeedsFocusRect( true );
344 wxMacDataBrowserListControl::~wxMacDataBrowserListControl()
348 void wxMacDataBrowserListControl::ItemNotification(
349 DataBrowserItemID itemID
,
350 DataBrowserItemNotification message
,
351 DataBrowserItemDataRef itemData
)
353 wxListBox
*list
= wxDynamicCast( GetWXPeer() , wxListBox
);
354 wxCHECK_RET( list
!= NULL
, wxT("Listbox expected"));
356 if (list
->HasMultipleSelection() && (message
== kDataBrowserSelectionSetChanged
) && (!list
->MacGetBlockEvents()))
358 list
->CalcAndSendEvent();
362 if ((message
== kDataBrowserSelectionSetChanged
) && (!list
->MacGetBlockEvents()))
364 wxCommandEvent
event( wxEVT_LISTBOX
, list
->GetId() );
366 int sel
= list
->GetSelection();
367 if ((sel
< 0) || (sel
> (int) list
->GetCount())) // OS X can select an item below the last item (why?)
369 list
->HandleLineEvent( sel
, false );
373 // call super for item level(wxMacDataItem->Notification) callback processing
374 wxMacDataItemBrowserControl::ItemNotification( itemID
, message
, itemData
);
379 wxWindow * wxMacDataBrowserListControl::GetPeer() const
381 return wxDynamicCast( wxMacControl::GetWX() , wxWindow );
389 wxMacDataBrowserColumn
* wxMacDataBrowserListControl::DoInsertColumn( unsigned int pos
, DataBrowserPropertyID property
,
390 const wxString
& title
, bool editable
,
391 DataBrowserPropertyType colType
, SInt16 just
, int width
)
393 DataBrowserListViewColumnDesc columnDesc
;
394 columnDesc
.headerBtnDesc
.titleOffset
= 0;
395 columnDesc
.headerBtnDesc
.version
= kDataBrowserListViewLatestHeaderDesc
;
397 columnDesc
.headerBtnDesc
.btnFontStyle
.flags
=
398 kControlUseFontMask
| kControlUseJustMask
;
400 columnDesc
.headerBtnDesc
.btnContentInfo
.contentType
= kControlContentTextOnly
;
401 columnDesc
.headerBtnDesc
.btnFontStyle
.just
= just
;
402 columnDesc
.headerBtnDesc
.btnFontStyle
.font
= kControlFontViewSystemFont
;
403 columnDesc
.headerBtnDesc
.btnFontStyle
.style
= normal
;
405 // TODO: Why is m_font not defined when we enter wxLC_LIST mode, but is
406 // defined for other modes?
409 enc
= m_font
.GetEncoding();
411 enc
= wxLocale::GetSystemEncoding();
412 wxCFStringRef
cfTitle( title
, enc
);
413 columnDesc
.headerBtnDesc
.titleString
= cfTitle
;
415 columnDesc
.headerBtnDesc
.minimumWidth
= 0;
416 columnDesc
.headerBtnDesc
.maximumWidth
= 30000;
418 columnDesc
.propertyDesc
.propertyID
= property
;
419 columnDesc
.propertyDesc
.propertyType
= colType
;
420 columnDesc
.propertyDesc
.propertyFlags
= kDataBrowserListViewSortableColumn
;
421 columnDesc
.propertyDesc
.propertyFlags
|= kDataBrowserListViewTypeSelectColumn
;
422 columnDesc
.propertyDesc
.propertyFlags
|= kDataBrowserListViewNoGapForIconInHeaderButton
;
425 columnDesc
.propertyDesc
.propertyFlags
|= kDataBrowserPropertyIsMutable
;
427 verify_noerr( AddColumn( &columnDesc
, pos
) );
431 wxMacDataBrowserControl::SetColumnWidth(property
, width
);
434 wxMacDataBrowserColumn
*col
= new wxMacDataBrowserColumn( property
, colType
, editable
);
436 m_columns
.Insert( col
, pos
);
441 wxListWidgetColumn
* wxMacDataBrowserListControl::InsertTextColumn( unsigned pos
, const wxString
& title
, bool editable
,
442 wxAlignment just
, int defaultWidth
)
444 DataBrowserPropertyID property
= kMinColumnId
+ m_nextColumnId
++;
446 SInt16 j
= teFlushLeft
;
447 if ( just
& wxALIGN_RIGHT
)
449 else if ( just
& wxALIGN_CENTER_HORIZONTAL
)
452 return DoInsertColumn( pos
, property
, title
, editable
, kDataBrowserTextType
, just
, defaultWidth
);
455 wxListWidgetColumn
* wxMacDataBrowserListControl::InsertCheckColumn( unsigned pos
, const wxString
& title
, bool editable
,
456 wxAlignment just
, int defaultWidth
)
458 DataBrowserPropertyID property
= kMinColumnId
+ m_nextColumnId
++;
460 SInt16 j
= teFlushLeft
;
461 if ( just
& wxALIGN_RIGHT
)
463 else if ( just
& wxALIGN_CENTER_HORIZONTAL
)
466 return DoInsertColumn( pos
, property
, title
, editable
, kDataBrowserCheckboxType
, just
, defaultWidth
);
469 wxMacDataBrowserColumn
* wxMacDataBrowserListControl::GetColumnFromProperty( DataBrowserPropertyID property
)
471 for ( unsigned int i
= 0; i
< m_columns
.size() ; ++ i
)
472 if ( m_columns
[i
]->GetProperty() == property
)
479 wxMacDataItem* wxMacDataBrowserListControl::ListGetLineItem( unsigned int n )
481 return (wxMacDataItem*) GetItemFromLine(n);
485 unsigned int wxMacDataBrowserListControl::ListGetCount() const
487 return MacGetCount();
490 void wxMacDataBrowserListControl::ListDelete( unsigned int n
)
495 void wxMacDataBrowserListControl::ListInsert( unsigned int n
)
497 MacInsert( n
, new wxMacListBoxItem() );
500 void wxMacDataBrowserListControl::ListClear()
505 void wxMacDataBrowserListControl::ListDeselectAll()
507 wxMacDataItemBrowserSelectionSuppressor
suppressor(this);
508 SetSelectedAllItems( kDataBrowserItemsRemove
);
511 void wxMacDataBrowserListControl::ListSetSelection( unsigned int n
, bool select
, bool multi
)
513 wxMacDataItem
* item
= (wxMacDataItem
*) GetItemFromLine(n
);
514 wxMacDataItemBrowserSelectionSuppressor
suppressor(this);
516 if ( IsItemSelected( item
) != select
)
519 SetSelectedItem( item
, multi
? kDataBrowserItemsAdd
: kDataBrowserItemsAssign
);
521 SetSelectedItem( item
, kDataBrowserItemsRemove
);
527 bool wxMacDataBrowserListControl::ListIsSelected( unsigned int n
) const
529 wxMacDataItem
* item
= (wxMacDataItem
*) GetItemFromLine(n
);
530 return IsItemSelected( item
);
533 int wxMacDataBrowserListControl::ListGetSelection() const
535 wxMacDataItemPtr first
, last
;
536 GetSelectionAnchor( &first
, &last
);
540 return GetLineFromItem( first
);
546 int wxMacDataBrowserListControl::ListGetSelections( wxArrayInt
& aSelections
) const
549 wxArrayMacDataItemPtr selectedItems
;
550 GetItems( wxMacDataBrowserRootContainer
, false , kDataBrowserItemIsSelected
, selectedItems
);
552 int count
= selectedItems
.GetCount();
554 for ( int i
= 0; i
< count
; ++i
)
556 aSelections
.Add(GetLineFromItem(selectedItems
[i
]));
562 void wxMacDataBrowserListControl::ListScrollTo( unsigned int n
)
565 GetScrollPosition( &top
, &left
) ;
566 wxMacDataItem
* item
= (wxMacDataItem
*) GetItemFromLine( n
);
568 // there is a bug in RevealItem that leads to situations
569 // in large lists, where the item does not get scrolled
570 // into sight, so we do a pre-scroll if necessary
572 GetRowHeight( (DataBrowserItemID
) item
, &height
) ;
573 UInt32 linetop
= n
* ((UInt32
) height
);
574 UInt32 linebottom
= linetop
+ height
;
576 GetControlBounds( m_controlRef
, &rect
);
578 if ( linetop
< top
|| linebottom
> (top
+ rect
.bottom
- rect
.top
) )
579 SetScrollPosition( wxMax( n
-2, 0 ) * ((UInt32
)height
) , left
) ;
581 RevealItem( item
, kDataBrowserRevealWithoutSelecting
);
584 void wxMacDataBrowserListControl::UpdateLine( unsigned int n
, wxListWidgetColumn
* col
)
586 wxMacDataBrowserColumn
* dbcol
= dynamic_cast<wxMacDataBrowserColumn
*> (col
);
587 wxMacDataItem
* item
= (wxMacDataItem
*) GetItemFromLine( n
);
588 UpdateItem(wxMacDataBrowserRootContainer
, item
, dbcol
? dbcol
->GetProperty() : kDataBrowserNoItem
);
591 void wxMacDataBrowserListControl::UpdateLineToEnd( unsigned int n
)
593 // with databrowser inserting does not need updating the entire model, it's done by databrowser itself
594 wxMacDataItem
* item
= (wxMacDataItem
*) GetItemFromLine( n
);
595 UpdateItem(wxMacDataBrowserRootContainer
, item
, kDataBrowserNoItem
);
600 void wxMacDataBrowserCellValue::Set( CFStringRef value
)
602 SetDataBrowserItemDataText( m_data
, value
);
605 void wxMacDataBrowserCellValue::Set( const wxString
& value
)
607 wxCFStringRef
cf(value
);
608 SetDataBrowserItemDataText( m_data
, (CFStringRef
) cf
);
611 void wxMacDataBrowserCellValue::Set( int value
)
613 SetDataBrowserItemDataValue( m_data
, value
);
616 void wxMacDataBrowserCellValue::Check( bool check
)
618 SetDataBrowserItemDataButtonValue( m_data
, check
? kThemeButtonOn
: kThemeButtonOff
);
621 int wxMacDataBrowserCellValue::GetIntValue() const
624 GetDataBrowserItemDataValue( m_data
, &value
);
628 wxString
wxMacDataBrowserCellValue::GetStringValue() const
631 GetDataBrowserItemDataText ( m_data
, &value
);
632 wxCFStringRef
cf(value
);
633 return cf
.AsString();
638 // in case we need that one day
640 // ============================================================================
641 // HIView owner-draw-based implementation
642 // ============================================================================
644 static pascal void ListBoxDrawProc(
645 ControlRef browser
, DataBrowserItemID item
, DataBrowserPropertyID property
,
646 DataBrowserItemState itemState
, const Rect
*itemRect
, SInt16 depth
, Boolean isColorDevice
)
648 CFStringRef cfString
;
649 ThemeDrawingState themeState
;
652 GetThemeDrawingState( &themeState
);
653 cfString
= CFStringCreateWithFormat( NULL
, NULL
, CFSTR("Row %d"), item
);
655 // In this sample we handle the "selected" state; all others fall through to our "active" state
656 if ( itemState
== kDataBrowserItemIsSelected
)
658 ThemeBrush colorBrushID
;
660 // TODO: switch over to wxSystemSettingsNative::GetColour() when kThemeBrushSecondaryHighlightColor
661 // is incorporated Panther DB starts using kThemeBrushSecondaryHighlightColor
662 // for inactive browser highlighting
663 if ( !IsControlActive( browser
) )
664 colorBrushID
= kThemeBrushSecondaryHighlightColor
;
666 colorBrushID
= kThemeBrushPrimaryHighlightColor
;
668 // First paint the hilite rect, then the text on top
669 SetThemePen( colorBrushID
, 32, true );
670 PaintRect( itemRect
);
671 SetThemeDrawingState( themeState
, false );
674 DrawThemeTextBox( cfString
, kThemeApplicationFont
, kThemeStateActive
, true, itemRect
, teFlushDefault
, NULL
);
675 SetThemeDrawingState( themeState
, true );
677 if ( cfString
!= NULL
)
678 CFRelease( cfString
);
684 #endif // wxUSE_LISTBOX