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