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