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