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