1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/osx/cocoa/listbox.mm
4 // Author: Stefan Csomor
7 // RCS-ID: $Id: listbox.cpp 54820 2008-07-29 20:04:11Z SC $
8 // Copyright: (c) Stefan Csomor
9 // Licence: wxWindows licence
10 ///////////////////////////////////////////////////////////////////////////////
12 #include "wx/wxprec.h"
16 #include "wx/listbox.h"
23 #include "wx/settings.h"
24 #include "wx/arrstr.h"
25 #include "wx/dcclient.h"
28 #include "wx/osx/private.h"
34 class wxListWidgetCocoaImpl;
36 @interface wxNSTableDataSource : NSObject
37 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
38 <NSTableViewDataSource>
41 wxListWidgetCocoaImpl* impl;
44 - (id)tableView:(NSTableView *)aTableView
45 objectValueForTableColumn:(NSTableColumn *)aTableColumn
46 row:(NSInteger)rowIndex;
48 - (void)tableView:(NSTableView *)aTableView
49 setObjectValue:(id)value forTableColumn:(NSTableColumn *)aTableColumn
50 row:(NSInteger)rowIndex;
52 - (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView;
54 - (void)setImplementation: (wxListWidgetCocoaImpl *) theImplementation;
55 - (wxListWidgetCocoaImpl*) implementation;
59 @interface wxNSTableView : NSTableView
60 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
72 class wxCocoaTableColumn;
74 @interface wxNSTableColumn : NSTableColumn
76 wxCocoaTableColumn* column;
79 - (void) setColumn: (wxCocoaTableColumn*) col;
81 - (wxCocoaTableColumn*) column;
85 class WXDLLIMPEXP_CORE wxCocoaTableColumn : public wxListWidgetColumn
88 wxCocoaTableColumn( wxNSTableColumn* column, bool editable )
89 : m_column( column ), m_editable(editable)
97 wxNSTableColumn* GetNSTableColumn() const { return m_column ; }
99 bool IsEditable() const { return m_editable; }
102 wxNSTableColumn* m_column;
106 NSString* column1 = @"1";
108 class wxListWidgetCocoaImpl : public wxWidgetCocoaImpl, public wxListWidgetImpl
111 wxListWidgetCocoaImpl( wxWindowMac* peer, NSScrollView* view, wxNSTableView* tableview, wxNSTableDataSource* data );
113 ~wxListWidgetCocoaImpl();
115 virtual wxListWidgetColumn* InsertTextColumn( unsigned pos, const wxString& title, bool editable = false,
116 wxAlignment just = wxALIGN_LEFT , int defaultWidth = -1) ;
117 virtual wxListWidgetColumn* InsertCheckColumn( unsigned pos , const wxString& title, bool editable = false,
118 wxAlignment just = wxALIGN_LEFT , int defaultWidth = -1) ;
122 virtual void ListDelete( unsigned int n ) ;
123 virtual void ListInsert( unsigned int n ) ;
124 virtual void ListClear() ;
128 virtual void ListDeselectAll();
130 virtual void ListSetSelection( unsigned int n, bool select, bool multi ) ;
131 virtual int ListGetSelection() const ;
133 virtual int ListGetSelections( wxArrayInt& aSelections ) const ;
135 virtual bool ListIsSelected( unsigned int n ) const ;
139 virtual void ListScrollTo( unsigned int n ) ;
143 virtual unsigned int ListGetCount() const ;
145 int ListGetColumnType( int col )
149 virtual void UpdateLine( unsigned int n, wxListWidgetColumn* col = NULL ) ;
150 virtual void UpdateLineToEnd( unsigned int n);
152 virtual void controlDoubleAction(WXWidget slf, void* _cmd, void *sender);
154 wxNSTableView* m_tableView ;
156 wxNSTableDataSource* m_dataSource;
163 @implementation wxNSTableColumn
172 - (void) setColumn: (wxCocoaTableColumn*) col
177 - (wxCocoaTableColumn*) column
184 class wxNSTableViewCellValue : public wxListWidgetCellValue
187 wxNSTableViewCellValue( id &v ) : value(v)
191 virtual ~wxNSTableViewCellValue() {}
193 virtual void Set( CFStringRef v )
195 value = [[(NSString*)v retain] autorelease];
197 virtual void Set( const wxString& value )
199 Set( (CFStringRef) wxCFStringRef( value ) );
201 virtual void Set( int v )
203 value = [NSNumber numberWithInt:v];
206 virtual int GetIntValue() const
208 if ( [value isKindOfClass:[NSNumber class]] )
209 return [ (NSNumber*) value intValue ];
214 virtual wxString GetStringValue() const
216 if ( [value isKindOfClass:[NSString class]] )
217 return wxCFStringRef::AsString( (NSString*) value );
219 return wxEmptyString;
226 @implementation wxNSTableDataSource
235 - (void)setImplementation: (wxListWidgetCocoaImpl *) theImplementation
237 impl = theImplementation;
240 - (wxListWidgetCocoaImpl*) implementation
245 - (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
247 wxUnusedVar(aTableView);
249 return impl->ListGetCount();
253 - (id)tableView:(NSTableView *)aTableView
254 objectValueForTableColumn:(NSTableColumn *)aTableColumn
255 row:(NSInteger)rowIndex
257 wxUnusedVar(aTableView);
258 wxNSTableColumn* tablecol = (wxNSTableColumn *)aTableColumn;
259 wxListBox* lb = dynamic_cast<wxListBox*>(impl->GetWXPeer());
260 wxCocoaTableColumn* col = [tablecol column];
262 wxNSTableViewCellValue cellvalue(value);
263 lb->GetValueCallback(rowIndex, col, cellvalue);
267 - (void)tableView:(NSTableView *)aTableView
268 setObjectValue:(id)value forTableColumn:(NSTableColumn *)aTableColumn
269 row:(NSInteger)rowIndex
271 wxUnusedVar(aTableView);
272 wxNSTableColumn* tablecol = (wxNSTableColumn *)aTableColumn;
273 wxListBox* lb = dynamic_cast<wxListBox*>(impl->GetWXPeer());
274 wxCocoaTableColumn* col = [tablecol column];
275 wxNSTableViewCellValue cellvalue(value);
276 lb->SetValueCallback(rowIndex, col, cellvalue);
281 @implementation wxNSTableView
285 static BOOL initialized = NO;
289 wxOSXCocoaClassAddWXMethods( self );
293 - (void) tableViewSelectionDidChange: (NSNotification *) notification
295 wxUnusedVar(notification);
297 int row = [self selectedRow];
305 wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( self );
306 wxListBox *list = static_cast<wxListBox*> ( impl->GetWXPeer());
307 wxCHECK_RET( list != NULL , wxT("Listbox expected"));
309 wxCommandEvent event( wxEVT_COMMAND_LISTBOX_SELECTED, list->GetId() );
311 if ((row < 0) || (row > (int) list->GetCount())) // OS X can select an item below the last item
314 if ( !list->MacGetBlockEvents() )
315 list->HandleLineEvent( row, false );
326 wxListWidgetCocoaImpl::wxListWidgetCocoaImpl( wxWindowMac* peer, NSScrollView* view, wxNSTableView* tableview, wxNSTableDataSource* data ) :
327 wxWidgetCocoaImpl( peer, view ), m_tableView(tableview), m_dataSource(data)
329 InstallEventHandler( tableview );
332 wxListWidgetCocoaImpl::~wxListWidgetCocoaImpl()
334 [m_dataSource release];
337 unsigned int wxListWidgetCocoaImpl::ListGetCount() const
339 wxListBox* lb = dynamic_cast<wxListBox*> ( GetWXPeer() );
340 return lb->GetCount();
347 wxListWidgetColumn* wxListWidgetCocoaImpl::InsertTextColumn( unsigned pos, const wxString& WXUNUSED(title), bool editable,
348 wxAlignment WXUNUSED(just), int defaultWidth)
350 wxNSTableColumn* col1 = [[wxNSTableColumn alloc] init];
351 [col1 setEditable:editable];
353 unsigned formerColCount = [m_tableView numberOfColumns];
355 // there's apparently no way to insert at a specific position
356 [m_tableView addTableColumn:col1 ];
357 if ( pos < formerColCount )
358 [m_tableView moveColumn:formerColCount toColumn:pos];
360 if ( defaultWidth >= 0 )
362 [col1 setMaxWidth:defaultWidth];
363 [col1 setMinWidth:defaultWidth];
364 [col1 setWidth:defaultWidth];
368 [col1 setMaxWidth:1000];
369 [col1 setMinWidth:10];
370 // temporary hack, because I cannot get the automatic column resizing
372 [col1 setWidth:1000];
374 [col1 setResizingMask: NSTableColumnAutoresizingMask];
375 wxCocoaTableColumn* wxcol = new wxCocoaTableColumn( col1, editable );
376 [col1 setColumn:wxcol];
378 // owned by the tableview
383 wxListWidgetColumn* wxListWidgetCocoaImpl::InsertCheckColumn( unsigned pos , const wxString& WXUNUSED(title), bool editable,
384 wxAlignment WXUNUSED(just), int defaultWidth )
386 wxNSTableColumn* col1 = [[wxNSTableColumn alloc] init];
387 [col1 setEditable:editable];
389 // set your custom cell & set it up
390 NSButtonCell* checkbox = [[NSButtonCell alloc] init];
391 [checkbox setTitle:@""];
392 [checkbox setButtonType:NSSwitchButton];
393 [col1 setDataCell:checkbox] ;
396 unsigned formerColCount = [m_tableView numberOfColumns];
398 // there's apparently no way to insert at a specific position
399 [m_tableView addTableColumn:col1 ];
400 if ( pos < formerColCount )
401 [m_tableView moveColumn:formerColCount toColumn:pos];
403 if ( defaultWidth >= 0 )
405 [col1 setMaxWidth:defaultWidth];
406 [col1 setMinWidth:defaultWidth];
407 [col1 setWidth:defaultWidth];
410 [col1 setResizingMask: NSTableColumnNoResizing];
411 wxCocoaTableColumn* wxcol = new wxCocoaTableColumn( col1, editable );
412 [col1 setColumn:wxcol];
414 // owned by the tableview
421 // inserting / removing lines
424 void wxListWidgetCocoaImpl::ListInsert( unsigned int WXUNUSED(n) )
426 [m_tableView reloadData];
429 void wxListWidgetCocoaImpl::ListDelete( unsigned int WXUNUSED(n) )
431 [m_tableView reloadData];
434 void wxListWidgetCocoaImpl::ListClear()
436 [m_tableView reloadData];
441 void wxListWidgetCocoaImpl::ListDeselectAll()
443 [m_tableView deselectAll:nil];
446 void wxListWidgetCocoaImpl::ListSetSelection( unsigned int n, bool select, bool multi )
450 [m_tableView selectRow: n byExtendingSelection:multi];
452 [m_tableView deselectRow: n];
456 int wxListWidgetCocoaImpl::ListGetSelection() const
458 return [m_tableView selectedRow];
461 int wxListWidgetCocoaImpl::ListGetSelections( wxArrayInt& aSelections ) const
465 int count = ListGetCount();
467 for ( int i = 0; i < count; ++i)
469 if ([m_tableView isRowSelected:count])
473 return aSelections.Count();
476 bool wxListWidgetCocoaImpl::ListIsSelected( unsigned int n ) const
478 return [m_tableView isRowSelected:n];
483 void wxListWidgetCocoaImpl::ListScrollTo( unsigned int n )
485 [m_tableView scrollRowToVisible:n];
489 void wxListWidgetCocoaImpl::UpdateLine( unsigned int WXUNUSED(n), wxListWidgetColumn* WXUNUSED(col) )
492 [m_tableView reloadData];
495 void wxListWidgetCocoaImpl::UpdateLineToEnd( unsigned int WXUNUSED(n))
498 [m_tableView reloadData];
501 void wxListWidgetCocoaImpl::controlDoubleAction(WXWidget WXUNUSED(slf),void* WXUNUSED(_cmd), void *WXUNUSED(sender))
503 wxListBox *list = static_cast<wxListBox*> ( GetWXPeer());
504 wxCHECK_RET( list != NULL , wxT("Listbox expected"));
506 int sel = [m_tableView clickedRow];
507 if ((sel < 0) || (sel > (int) list->GetCount())) // OS X can select an item below the last item (why?)
510 list->HandleLineEvent( sel, true );
516 wxWidgetImplType* wxWidgetImpl::CreateListBox( wxWindowMac* wxpeer,
517 wxWindowMac* WXUNUSED(parent),
518 wxWindowID WXUNUSED(id),
522 long WXUNUSED(extraStyle))
524 NSRect r = wxOSXGetFrameForControl( wxpeer, pos , size ) ;
525 NSScrollView* scrollview = [[NSScrollView alloc] initWithFrame:r];
527 // use same scroll flags logic as msw
529 [scrollview setHasVerticalScroller:YES];
531 if ( style & wxLB_HSCROLL )
532 [scrollview setHasHorizontalScroller:YES];
534 [scrollview setAutohidesScrollers: ((style & wxLB_ALWAYS_SB) ? NO : YES)];
536 // setting up the true table
538 wxNSTableView* tableview = [[wxNSTableView alloc] init];
539 [tableview setDelegate:tableview];
540 // only one multi-select mode available
541 if ( (style & wxLB_EXTENDED) || (style & wxLB_MULTIPLE) )
542 [tableview setAllowsMultipleSelection:YES];
544 // simple listboxes have no header row
545 [tableview setHeaderView:nil];
547 if ( style & wxLB_HSCROLL )
548 [tableview setColumnAutoresizingStyle:NSTableViewNoColumnAutoresizing];
550 [tableview setColumnAutoresizingStyle:NSTableViewLastColumnOnlyAutoresizingStyle];
552 wxNSTableDataSource* ds = [[ wxNSTableDataSource alloc] init];
553 [tableview setDataSource:ds];
554 [scrollview setDocumentView:tableview];
557 wxListWidgetCocoaImpl* c = new wxListWidgetCocoaImpl( wxpeer, scrollview, tableview, ds );
559 // temporary hook for dnd
560 [tableview registerForDraggedTypes:[NSArray arrayWithObjects:
561 NSStringPboardType, NSFilenamesPboardType, NSTIFFPboardType, NSPICTPboardType, NSPDFPboardType, nil]];
563 [ds setImplementation:c];
567 int wxListBox::DoListHitTest(const wxPoint& WXUNUSED(inpoint)) const
572 // There are few reasons why this is complicated:
573 // 1) There is no native HitTest function for Mac
574 // 2) GetDataBrowserItemPartBounds only works on visible items
575 // 3) We can't do it through GetDataBrowserTableView[Item]RowHeight
576 // because what it returns is basically inaccurate in the context
577 // of the coordinates we want here, but we use this as a guess
578 // for where the first visible item lies
580 wxPoint point = inpoint;
582 // get column property ID (req. for call to itempartbounds)
583 DataBrowserTableViewColumnID colId = 0;
584 err = GetDataBrowserTableViewColumnProperty(m_peer->GetControlRef(), 0, &colId);
585 wxCHECK_MSG(err == noErr, wxNOT_FOUND, wxT("Unexpected error from GetDataBrowserTableViewColumnProperty"));
587 // OK, first we need to find the first visible item we have -
588 // this will be the "low" for our binary search. There is no real
589 // easy way around this, as we will need to do a SLOW linear search
590 // until we find a visible item, but we can do a cheap calculation
591 // via the row height to speed things up a bit
592 UInt32 scrollx, scrolly;
593 err = GetDataBrowserScrollPosition(m_peer->GetControlRef(), &scrollx, &scrolly);
594 wxCHECK_MSG(err == noErr, wxNOT_FOUND, wxT("Unexpected error from GetDataBrowserScrollPosition"));
597 err = GetDataBrowserTableViewRowHeight(m_peer->GetControlRef(), &height);
598 wxCHECK_MSG(err == noErr, wxNOT_FOUND, wxT("Unexpected error from GetDataBrowserTableViewRowHeight"));
600 // these indices are 0-based, as usual, so we need to add 1 to them when
601 // passing them to data browser functions which use 1-based indices
602 int low = scrolly / height,
603 high = GetCount() - 1;
605 // search for the first visible item (note that the scroll guess above
606 // is the low bounds of where the item might lie so we only use that as a
607 // starting point - we should reach it within 1 or 2 iterations of the loop)
608 while ( low <= high )
611 err = GetDataBrowserItemPartBounds(
612 m_peer->GetControlRef(), low + 1, colId,
613 kDataBrowserPropertyEnclosingPart,
614 &bounds); // note +1 to translate to Mac ID
618 // errDataBrowserItemNotFound is expected as it simply means that the
619 // item is not currently visible -- but other errors are not
620 wxCHECK_MSG( err == errDataBrowserItemNotFound, wxNOT_FOUND,
621 wxT("Unexpected error from GetDataBrowserItemPartBounds") );
626 // NOW do a binary search for where the item lies, searching low again if
627 // we hit an item that isn't visible
628 while ( low <= high )
630 int mid = (low + high) / 2;
633 err = GetDataBrowserItemPartBounds(
634 m_peer->GetControlRef(), mid + 1, colId,
635 kDataBrowserPropertyEnclosingPart,
636 &bounds); //note +1 to trans to mac id
637 wxCHECK_MSG( err == noErr || err == errDataBrowserItemNotFound,
639 wxT("Unexpected error from GetDataBrowserItemPartBounds") );
641 if ( err == errDataBrowserItemNotFound )
643 // item not visible, attempt to find a visible one
647 else // visible item, do actual hitttest
649 // if point is within the bounds, return this item (since we assume
650 // all x coords of items are equal we only test the x coord in
652 if ((point.x >= bounds.left && point.x <= bounds.right) &&
653 (point.y >= bounds.top && point.y <= bounds.bottom) )
659 if ( point.y < bounds.top )
660 // index(bounds) greater then key(point)
663 // index(bounds) less then key(point)
671 #endif // wxUSE_LISTBOX