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