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