don't access m_clientDataItemsType directly
[wxWidgets.git] / src / mac / carbon / listbox.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/mac/carbon/listbox.cpp
3 // Purpose: wxListBox
4 // Author: Stefan Csomor
5 // Modified by:
6 // Created: 1998-01-01
7 // RCS-ID: $Id$
8 // Copyright: (c) Stefan Csomor
9 // Licence: wxWindows licence
10 ///////////////////////////////////////////////////////////////////////////////
11
12 #include "wx/wxprec.h"
13
14 #if wxUSE_LISTBOX
15
16 #include "wx/listbox.h"
17
18 #ifndef WX_PRECOMP
19 #include "wx/log.h"
20 #include "wx/intl.h"
21 #include "wx/utils.h"
22 #include "wx/settings.h"
23 #include "wx/arrstr.h"
24 #include "wx/dcclient.h"
25 #endif
26
27 IMPLEMENT_DYNAMIC_CLASS(wxListBox, wxControlWithItems)
28
29 BEGIN_EVENT_TABLE(wxListBox, wxControl)
30 END_EVENT_TABLE()
31
32 #include "wx/mac/uma.h"
33
34 // ============================================================================
35 // list box control implementation
36 // ============================================================================
37
38 wxListBox::wxListBox()
39 {
40 }
41
42 bool wxListBox::Create(
43 wxWindow *parent,
44 wxWindowID id,
45 const wxPoint& pos,
46 const wxSize& size,
47 const wxArrayString& choices,
48 long style,
49 const wxValidator& validator,
50 const wxString& name )
51 {
52 wxCArrayString chs(choices);
53
54 return Create(
55 parent, id, pos, size, chs.GetCount(), chs.GetStrings(),
56 style, validator, name );
57 }
58
59 wxMacListControl* wxListBox::GetPeer() const
60 {
61 wxMacDataBrowserListControl *lb = wxDynamicCast(m_peer,wxMacDataBrowserListControl);
62 return lb ? wx_static_cast(wxMacListControl*,lb) : 0 ;
63 }
64
65 bool wxListBox::Create(
66 wxWindow *parent,
67 wxWindowID id,
68 const wxPoint& pos,
69 const wxSize& size,
70 int n,
71 const wxString choices[],
72 long style,
73 const wxValidator& validator,
74 const wxString& name )
75 {
76 m_macIsUserPane = false;
77
78 wxASSERT_MSG( !(style & wxLB_MULTIPLE) || !(style & wxLB_EXTENDED),
79 wxT("only a single listbox selection mode can be specified") );
80
81 if ( !wxListBoxBase::Create( parent, id, pos, size, style & ~(wxHSCROLL | wxVSCROLL), validator, name ) )
82 return false;
83
84 wxMacDataBrowserListControl* control = new wxMacDataBrowserListControl( this, pos, size, style );
85 m_peer = control;
86
87 MacPostControlCreate( pos, size );
88
89 Append(n, choices);
90
91 // Needed because it is a wxControlWithItems
92 SetInitialSize( size );
93
94 return true;
95 }
96
97 wxListBox::~wxListBox()
98 {
99 FreeData();
100 m_peer->SetReference( 0 );
101 }
102
103 void wxListBox::FreeData()
104 {
105 GetPeer()->MacClear();
106 }
107
108 void wxListBox::DoSetFirstItem(int n)
109 {
110 GetPeer()->MacScrollTo( n );
111 }
112
113 void wxListBox::EnsureVisible(int n)
114 {
115 GetPeer()->MacScrollTo( n );
116 }
117
118 void wxListBox::DoDeleteOneItem(unsigned int n)
119 {
120 wxCHECK_RET( IsValid(n), wxT("invalid index in wxListBox::Delete") );
121
122 GetPeer()->MacDelete( n );
123 }
124
125 int wxListBox::DoInsertItems(const wxArrayStringsAdapter& items,
126 unsigned int pos,
127 void **clientData,
128 wxClientDataType type)
129 {
130 InvalidateBestSize();
131
132 GetPeer()->MacInsert( pos, items );
133 const unsigned int count = items.GetCount();
134 if ( clientData )
135 {
136 for (unsigned int i = 0; i < count; ++i)
137 AssignNewItemClientData( pos + i, clientData, i, type );
138 }
139
140 return pos + count - 1;
141 }
142
143 int wxListBox::FindString(const wxString& s, bool bCase) const
144 {
145 for ( size_t i = 0; i < GetCount(); ++ i )
146 {
147 if (s.IsSameAs( GetString( i ), bCase) )
148 return (int)i;
149 }
150
151 return wxNOT_FOUND;
152 }
153
154 void wxListBox::DoClear()
155 {
156 FreeData();
157 }
158
159 void wxListBox::DoSetSelection(int n, bool select)
160 {
161 wxCHECK_RET( n == wxNOT_FOUND || IsValid(n),
162 wxT("invalid index in wxListBox::SetSelection") );
163
164 if ( n == wxNOT_FOUND )
165 GetPeer()->MacDeselectAll();
166 else
167 GetPeer()->MacSetSelection( n, select, HasMultipleSelection() );
168 }
169
170 bool wxListBox::IsSelected(int n) const
171 {
172 wxCHECK_MSG( IsValid(n), false, wxT("invalid index in wxListBox::Selected") );
173
174 return GetPeer()->MacIsSelected( n );
175 }
176
177 void *wxListBox::DoGetItemClientData(unsigned int n) const
178 {
179 wxCHECK_MSG( IsValid(n), NULL, wxT("invalid index in wxListBox::GetClientData"));
180 return GetPeer()->MacGetClientData( n );
181 }
182
183 void wxListBox::DoSetItemClientData(unsigned int n, void *clientData)
184 {
185 wxCHECK_RET( IsValid(n), wxT("invalid index in wxListBox::SetClientData") );
186 GetPeer()->MacSetClientData( n , clientData);
187 }
188
189 // Return number of selections and an array of selected integers
190 int wxListBox::GetSelections(wxArrayInt& aSelections) const
191 {
192 return GetPeer()->MacGetSelections( aSelections );
193 }
194
195 // Get single selection, for single choice list items
196 int wxListBox::GetSelection() const
197 {
198 return GetPeer()->MacGetSelection();
199 }
200
201 // Find string for position
202 wxString wxListBox::GetString(unsigned int n) const
203 {
204 wxCHECK_MSG( IsValid(n), wxEmptyString, wxT("invalid index in wxListBox::GetString") );
205 return GetPeer()->MacGetString(n);
206 }
207
208 void wxListBox::SetString(unsigned int n, const wxString& s)
209 {
210 GetPeer()->MacSetString( n, s );
211 }
212
213 wxSize wxListBox::DoGetBestSize() const
214 {
215 int lbWidth = 100; // some defaults
216 int lbHeight = 110;
217 int wLine;
218
219 {
220 #if wxMAC_USE_CORE_GRAPHICS
221 wxClientDC dc(const_cast<wxListBox*>(this));
222 dc.SetFont(GetFont());
223 #else
224 wxMacPortStateHelper st( UMAGetWindowPort( (WindowRef)MacGetTopLevelWindowRef() ) );
225
226 // TODO: clean this up
227 if ( m_font.Ok() )
228 {
229 ::TextFont( m_font.MacGetFontNum() );
230 ::TextSize( m_font.MacGetFontSize() );
231 ::TextFace( m_font.MacGetFontStyle() );
232 }
233 else
234 {
235 ::TextFont( kFontIDMonaco );
236 ::TextSize( 9 );
237 ::TextFace( 0 );
238 }
239 #endif
240 // Find the widest line
241 for (unsigned int i = 0; i < GetCount(); i++)
242 {
243 wxString str( GetString( i ) );
244 #if wxMAC_USE_CORE_GRAPHICS
245 wxCoord width, height ;
246 dc.GetTextExtent( str , &width, &height);
247 wLine = width ;
248 #else
249 #if wxUSE_UNICODE
250 Point bounds = {0, 0};
251 SInt16 baseline;
252
253 // NB: what if m_font.Ok() == false ???
254 ::GetThemeTextDimensions(
255 wxMacCFStringHolder( str, m_font.GetEncoding() ),
256 kThemeCurrentPortFont,
257 kThemeStateActive,
258 false,
259 &bounds,
260 &baseline );
261 wLine = bounds.h;
262 #else
263 wLine = ::TextWidth( str.c_str(), 0, str.length() );
264 #endif
265 #endif
266 lbWidth = wxMax( lbWidth, wLine );
267 }
268
269 // Add room for the scrollbar
270 lbWidth += wxSystemSettings::GetMetric( wxSYS_VSCROLL_X );
271
272 // And just a bit more
273 int cy = 12;
274 #if wxMAC_USE_CORE_GRAPHICS
275 wxCoord width, height ;
276 dc.GetTextExtent( wxT("XX") , &width, &height);
277 int cx = width ;
278 #else
279 int cx = ::TextWidth( "XX", 0, 1 );
280 #endif
281 lbWidth += cx;
282
283 // don't make the listbox too tall (limit height to around 10 items)
284 // but don't make it too small neither
285 lbHeight = wxMax( (cy + 4) * wxMin( wxMax( GetCount(), 3 ), 10 ), 70 );
286 }
287
288 return wxSize( lbWidth, lbHeight );
289 }
290
291 unsigned int wxListBox::GetCount() const
292 {
293 return GetPeer()->MacGetCount();
294 }
295
296 void wxListBox::Refresh(bool eraseBack, const wxRect *rect)
297 {
298 wxControl::Refresh( eraseBack, rect );
299 }
300
301 // Some custom controls depend on this
302 /* static */ wxVisualAttributes
303 wxListBox::GetClassDefaultAttributes(wxWindowVariant WXUNUSED(variant))
304 {
305 wxVisualAttributes attr;
306
307 attr.colFg = wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOWTEXT );
308 attr.colBg = wxSystemSettings::GetColour( wxSYS_COLOUR_LISTBOX );
309 attr.font = wxSystemSettings::GetFont( wxSYS_DEFAULT_GUI_FONT );
310
311 return attr;
312 }
313
314 int wxListBox::DoListHitTest(const wxPoint& inpoint) const
315 {
316 OSStatus err;
317
318 // There are few reasons why this is complicated:
319 // 1) There is no native HitTest function for Mac
320 // 2) GetDataBrowserItemPartBounds only works on visible items
321 // 3) We can't do it through GetDataBrowserTableView[Item]RowHeight
322 // because what it returns is basically inaccurate in the context
323 // of the coordinates we want here, but we use this as a guess
324 // for where the first visible item lies
325
326 wxPoint point = inpoint;
327
328 // interestingly enough 10.2 (and below?) have GetDataBrowserItemPartBounds
329 // giving root window coordinates but 10.3 and above give client coordinates
330 // so we only compare using root window coordinates on 10.3 and up
331 if ( UMAGetSystemVersion() < 0x1030 )
332 MacClientToRootWindow(&point.x, &point.y);
333
334 // get column property ID (req. for call to itempartbounds)
335 DataBrowserTableViewColumnID colId = 0;
336 err = GetDataBrowserTableViewColumnProperty(m_peer->GetControlRef(), 0, &colId);
337 wxCHECK_MSG(err == noErr, wxNOT_FOUND, wxT("Unexpected error from GetDataBrowserTableViewColumnProperty"));
338
339 // OK, first we need to find the first visible item we have -
340 // this will be the "low" for our binary search. There is no real
341 // easy way around this, as we will need to do a SLOW linear search
342 // until we find a visible item, but we can do a cheap calculation
343 // via the row height to speed things up a bit
344 UInt32 scrollx, scrolly;
345 err = GetDataBrowserScrollPosition(m_peer->GetControlRef(), &scrollx, &scrolly);
346 wxCHECK_MSG(err == noErr, wxNOT_FOUND, wxT("Unexpected error from GetDataBrowserScrollPosition"));
347
348 UInt16 height;
349 err = GetDataBrowserTableViewRowHeight(m_peer->GetControlRef(), &height);
350 wxCHECK_MSG(err == noErr, wxNOT_FOUND, wxT("Unexpected error from GetDataBrowserTableViewRowHeight"));
351
352 // these indices are 0-based, as usual, so we need to add 1 to them when
353 // passing them to data browser functions which use 1-based indices
354 int low = scrolly / height,
355 high = GetCount() - 1;
356
357 // search for the first visible item (note that the scroll guess above
358 // is the low bounds of where the item might lie so we only use that as a
359 // starting point - we should reach it within 1 or 2 iterations of the loop)
360 while ( low <= high )
361 {
362 Rect bounds;
363 err = GetDataBrowserItemPartBounds(
364 m_peer->GetControlRef(), low + 1, colId,
365 kDataBrowserPropertyEnclosingPart,
366 &bounds); // note +1 to translate to Mac ID
367 if ( err == noErr )
368 break;
369
370 // errDataBrowserItemNotFound is expected as it simply means that the
371 // item is not currently visible -- but other errors are not
372 wxCHECK_MSG( err == errDataBrowserItemNotFound, wxNOT_FOUND,
373 wxT("Unexpected error from GetDataBrowserItemPartBounds") );
374
375 low++;
376 }
377
378 // NOW do a binary search for where the item lies, searching low again if
379 // we hit an item that isn't visible
380 while ( low <= high )
381 {
382 int mid = (low + high) / 2;
383
384 Rect bounds;
385 err = GetDataBrowserItemPartBounds(
386 m_peer->GetControlRef(), mid + 1, colId,
387 kDataBrowserPropertyEnclosingPart,
388 &bounds); //note +1 to trans to mac id
389 wxCHECK_MSG( err == noErr || err == errDataBrowserItemNotFound,
390 wxNOT_FOUND,
391 wxT("Unexpected error from GetDataBrowserItemPartBounds") );
392
393 if ( err == errDataBrowserItemNotFound )
394 {
395 // item not visible, attempt to find a visible one
396 // search lower
397 high = mid - 1;
398 }
399 else // visible item, do actual hitttest
400 {
401 // if point is within the bounds, return this item (since we assume
402 // all x coords of items are equal we only test the x coord in
403 // equality)
404 if ((point.x >= bounds.left && point.x <= bounds.right) &&
405 (point.y >= bounds.top && point.y <= bounds.bottom) )
406 {
407 // found!
408 return mid;
409 }
410
411 if ( point.y < bounds.top )
412 // index(bounds) greater then key(point)
413 high = mid - 1;
414 else
415 // index(bounds) less then key(point)
416 low = mid + 1;
417 }
418 }
419
420 return wxNOT_FOUND;
421 }
422
423 // ============================================================================
424 // data browser based implementation
425 // ============================================================================
426
427 wxMacListBoxItem::wxMacListBoxItem()
428 :wxMacDataItem()
429 {
430 }
431
432 wxMacListBoxItem::~wxMacListBoxItem()
433 {
434 }
435
436 void wxMacListBoxItem::Notification(wxMacDataItemBrowserControl *owner ,
437 DataBrowserItemNotification message,
438 DataBrowserItemDataRef itemData ) const
439 {
440 wxMacDataBrowserListControl *lb = wxDynamicCast(owner,wxMacDataBrowserListControl);
441
442 // we want to depend on as little as possible to make sure tear-down of controls is safe
443
444 if ( message == kDataBrowserItemRemoved)
445 {
446 if ( lb != NULL && lb->GetClientDataType() == wxClientData_Object )
447 {
448 delete (wxClientData*) (m_data);
449 }
450
451 delete this;
452 return;
453 }
454
455 wxListBox *list = wxDynamicCast( owner->GetPeer() , wxListBox );
456 wxCHECK_RET( list != NULL , wxT("Listbox expected"));
457
458 bool trigger = false;
459 wxCommandEvent event( wxEVT_COMMAND_LISTBOX_SELECTED, list->GetId() );
460 switch (message)
461 {
462 case kDataBrowserItemDeselected:
463 if ( list->HasMultipleSelection() )
464 trigger = !lb->IsSelectionSuppressed();
465 break;
466
467 case kDataBrowserItemSelected:
468 trigger = !lb->IsSelectionSuppressed();
469 break;
470
471 case kDataBrowserItemDoubleClicked:
472 event.SetEventType( wxEVT_COMMAND_LISTBOX_DOUBLECLICKED );
473 trigger = true;
474 break;
475
476 default:
477 break;
478 }
479
480 if ( trigger )
481 {
482 event.SetEventObject( list );
483 if ( list->HasClientObjectData() )
484 event.SetClientObject( (wxClientData*) m_data );
485 else if ( list->HasClientUntypedData() )
486 event.SetClientData( m_data );
487 event.SetString( m_label );
488 event.SetInt( owner->GetLineFromItem( this ) );
489 event.SetExtraLong( list->HasMultipleSelection() ? message == kDataBrowserItemSelected : true );
490
491 // direct notification is not always having the listbox GetSelection()
492 // having in synch with event, so use wxPostEvent instead
493 // list->GetEventHandler()->ProcessEvent(event);
494
495 wxPostEvent( list->GetEventHandler(), event );
496 }
497 }
498
499 IMPLEMENT_DYNAMIC_CLASS( wxMacDataBrowserListControl , wxMacDataItemBrowserControl )
500
501 wxMacDataBrowserListControl::wxMacDataBrowserListControl( wxWindow *peer, const wxPoint& pos, const wxSize& size, long style)
502 : wxMacDataItemBrowserControl( peer, pos, size, style )
503 {
504 OSStatus err = noErr;
505 m_clientDataItemsType = wxClientData_None;
506 if ( style & wxLB_SORT )
507 m_sortOrder = SortOrder_Text_Ascending;
508
509 DataBrowserSelectionFlags options = kDataBrowserDragSelect;
510 if ( style & wxLB_MULTIPLE )
511 {
512 options |= kDataBrowserAlwaysExtendSelection | kDataBrowserCmdTogglesSelection;
513 }
514 else if ( style & wxLB_EXTENDED )
515 {
516 options |= kDataBrowserCmdTogglesSelection;
517 }
518 else
519 {
520 options |= kDataBrowserSelectOnlyOne;
521 }
522 err = SetSelectionFlags( options );
523 verify_noerr( err );
524
525 DataBrowserListViewColumnDesc columnDesc;
526 columnDesc.headerBtnDesc.titleOffset = 0;
527 columnDesc.headerBtnDesc.version = kDataBrowserListViewLatestHeaderDesc;
528
529 columnDesc.headerBtnDesc.btnFontStyle.flags =
530 kControlUseFontMask | kControlUseJustMask;
531
532 columnDesc.headerBtnDesc.btnContentInfo.contentType = kControlNoContent;
533 columnDesc.headerBtnDesc.btnFontStyle.just = teFlushDefault;
534 columnDesc.headerBtnDesc.btnFontStyle.font = kControlFontViewSystemFont;
535 columnDesc.headerBtnDesc.btnFontStyle.style = normal;
536 columnDesc.headerBtnDesc.titleString = NULL;
537
538 columnDesc.headerBtnDesc.minimumWidth = 0;
539 columnDesc.headerBtnDesc.maximumWidth = 10000;
540
541 columnDesc.propertyDesc.propertyID = kTextColumnId;
542 columnDesc.propertyDesc.propertyType = kDataBrowserTextType;
543 columnDesc.propertyDesc.propertyFlags = kDataBrowserTableViewSelectionColumn;
544 #if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_2
545 columnDesc.propertyDesc.propertyFlags |= kDataBrowserListViewTypeSelectColumn;
546 #endif
547
548 verify_noerr( AddColumn( &columnDesc, kDataBrowserListViewAppendColumn ) );
549
550 columnDesc.headerBtnDesc.minimumWidth = 0;
551 columnDesc.headerBtnDesc.maximumWidth = 0;
552 columnDesc.propertyDesc.propertyID = kNumericOrderColumnId;
553 columnDesc.propertyDesc.propertyType = kDataBrowserPropertyRelevanceRankPart;
554 columnDesc.propertyDesc.propertyFlags = kDataBrowserTableViewSelectionColumn;
555 #if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_2
556 columnDesc.propertyDesc.propertyFlags |= kDataBrowserListViewTypeSelectColumn;
557 #endif
558
559 verify_noerr( AddColumn( &columnDesc, kDataBrowserListViewAppendColumn ) );
560
561 SetDataBrowserSortProperty( m_controlRef , kTextColumnId);
562 if ( m_sortOrder == SortOrder_Text_Ascending )
563 {
564 SetDataBrowserSortProperty( m_controlRef , kTextColumnId);
565 SetDataBrowserSortOrder( m_controlRef , kDataBrowserOrderIncreasing);
566 }
567 else
568 {
569 SetDataBrowserSortProperty( m_controlRef , kNumericOrderColumnId);
570 SetDataBrowserSortOrder( m_controlRef , kDataBrowserOrderIncreasing);
571 }
572
573 verify_noerr( AutoSizeColumns() );
574 verify_noerr( SetHiliteStyle(kDataBrowserTableViewFillHilite ) );
575 verify_noerr( SetHeaderButtonHeight( 0 ) );
576 err = SetHasScrollBars( (style & wxHSCROLL) != 0 , true );
577 #if 0
578 // shouldn't be necessary anymore under 10.2
579 m_peer->SetData( kControlNoPart, kControlDataBrowserIncludesFrameAndFocusTag, (Boolean)false );
580 m_peer->SetNeedsFocusRect( true );
581 #endif
582 }
583
584 wxMacDataBrowserListControl::~wxMacDataBrowserListControl()
585 {
586 }
587
588 wxWindow * wxMacDataBrowserListControl::GetPeer() const
589 {
590 return wxDynamicCast( wxMacControl::GetPeer() , wxWindow );
591 }
592
593 wxMacDataItem* wxMacDataBrowserListControl::CreateItem()
594 {
595 return new wxMacListBoxItem();
596 }
597
598 #if 0
599
600 // in case we need that one day
601
602 // ============================================================================
603 // HIView owner-draw-based implementation
604 // ============================================================================
605
606 static pascal void ListBoxDrawProc(
607 ControlRef browser, DataBrowserItemID item, DataBrowserPropertyID property,
608 DataBrowserItemState itemState, const Rect *itemRect, SInt16 depth, Boolean isColorDevice )
609 {
610 CFStringRef cfString;
611 ThemeDrawingState themeState;
612 long systemVersion;
613
614 GetThemeDrawingState( &themeState );
615 cfString = CFStringCreateWithFormat( NULL, NULL, CFSTR("Row %d"), item );
616
617 // In this sample we handle the "selected" state; all others fall through to our "active" state
618 if ( itemState == kDataBrowserItemIsSelected )
619 {
620 ThemeBrush colorBrushID;
621
622 // TODO: switch over to wxSystemSettingsNative::GetColour() when kThemeBrushSecondaryHighlightColor
623 // is incorporated Panther DB starts using kThemeBrushSecondaryHighlightColor
624 // for inactive browser highlighting
625 Gestalt( gestaltSystemVersion, &systemVersion );
626 if ( (systemVersion >= 0x00001030) && !IsControlActive( browser ) )
627 colorBrushID = kThemeBrushSecondaryHighlightColor;
628 else
629 colorBrushID = kThemeBrushPrimaryHighlightColor;
630
631 // First paint the hilite rect, then the text on top
632 SetThemePen( colorBrushID, 32, true );
633 PaintRect( itemRect );
634 SetThemeDrawingState( themeState, false );
635 }
636
637 DrawThemeTextBox( cfString, kThemeApplicationFont, kThemeStateActive, true, itemRect, teFlushDefault, NULL );
638 SetThemeDrawingState( themeState, true );
639
640 if ( cfString != NULL )
641 CFRelease( cfString );
642 }
643
644 #endif
645
646
647 #endif // wxUSE_LISTBOX