using native key handling, closes #10406
[wxWidgets.git] / src / osx / cocoa / listbox.mm
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name:        src/osx/cocoa/listbox.mm
3 // Purpose:     wxListBox
4 // Author:      Stefan Csomor
5 // Modified by:
6 // Created:     1998-01-01
7 // RCS-ID:      $Id: listbox.cpp 54820 2008-07-29 20:04:11Z SC $
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 #include "wx/dnd.h"
18
19 #ifndef WX_PRECOMP
20     #include "wx/log.h"
21     #include "wx/intl.h"
22     #include "wx/utils.h"
23     #include "wx/settings.h"
24     #include "wx/arrstr.h"
25     #include "wx/dcclient.h"
26 #endif
27
28 #include "wx/osx/private.h"
29
30 #include <vector>
31
32 // forward decls
33
34 class wxListWidgetCocoaImpl;
35
36 @interface wxNSTableDataSource : NSObject
37 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
38     <NSTableViewDataSource>
39 #endif
40 {
41     wxListWidgetCocoaImpl* impl;
42 }
43
44 - (id)tableView:(NSTableView *)aTableView
45         objectValueForTableColumn:(NSTableColumn *)aTableColumn
46         row:(NSInteger)rowIndex;
47
48 - (void)tableView:(NSTableView *)aTableView
49         setObjectValue:(id)value forTableColumn:(NSTableColumn *)aTableColumn
50         row:(NSInteger)rowIndex;
51
52 - (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView;
53
54 - (void)setImplementation: (wxListWidgetCocoaImpl *) theImplementation;
55 - (wxListWidgetCocoaImpl*) implementation;
56
57 @end
58
59 @interface wxNSTableView : NSTableView
60 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
61     <NSTableViewDelegate>
62 #endif
63 {
64 }
65
66 @end
67
68 //
69 // table column
70 //
71
72 class wxCocoaTableColumn;
73
74 @interface wxNSTableColumn : NSTableColumn
75 {
76     wxCocoaTableColumn* column;
77 }
78
79 - (void) setColumn: (wxCocoaTableColumn*) col;
80
81 - (wxCocoaTableColumn*) column;
82
83 @end
84
85 class WXDLLIMPEXP_CORE wxCocoaTableColumn : public wxListWidgetColumn
86 {
87 public :
88     wxCocoaTableColumn( wxNSTableColumn* column, bool editable )
89         : m_column( column ), m_editable(editable)
90     {
91     }
92
93     ~wxCocoaTableColumn()
94     {
95     }
96
97     wxNSTableColumn* GetNSTableColumn() const { return m_column ; }
98
99     bool IsEditable() const { return m_editable; }
100
101 protected :
102     wxNSTableColumn* m_column;
103     bool m_editable;
104 } ;
105
106 NSString* column1 = @"1";
107
108 class wxListWidgetCocoaImpl : public wxWidgetCocoaImpl, public wxListWidgetImpl
109 {
110 public :
111     wxListWidgetCocoaImpl( wxWindowMac* peer, NSScrollView* view, wxNSTableView* tableview, wxNSTableDataSource* data );
112
113     ~wxListWidgetCocoaImpl();
114
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)  ;
119
120     // add and remove
121
122     virtual void            ListDelete( unsigned int n ) ;
123     virtual void            ListInsert( unsigned int n ) ;
124     virtual void            ListClear() ;
125
126     // selecting
127
128     virtual void            ListDeselectAll();
129
130     virtual void            ListSetSelection( unsigned int n, bool select, bool multi ) ;
131     virtual int             ListGetSelection() const ;
132
133     virtual int             ListGetSelections( wxArrayInt& aSelections ) const ;
134
135     virtual bool            ListIsSelected( unsigned int n ) const ;
136
137     // display
138
139     virtual void            ListScrollTo( unsigned int n ) ;
140
141     // accessing content
142
143     virtual unsigned int    ListGetCount() const ;
144
145     int                     ListGetColumnType( int col )
146     {
147         return col;
148     }
149     virtual void            UpdateLine( unsigned int n, wxListWidgetColumn* col = NULL ) ;
150     virtual void            UpdateLineToEnd( unsigned int n);
151
152     virtual void            controlDoubleAction(WXWidget slf, void* _cmd, void *sender);
153     virtual bool            DoHandleKeyEvent(NSEvent *event);
154
155 protected :
156     wxNSTableView*          m_tableView ;
157
158     wxNSTableDataSource*    m_dataSource;
159 } ;
160
161 //
162 // implementations
163 //
164
165 @implementation wxNSTableColumn
166
167 - (id) init
168 {
169     [super init];
170     column = nil;
171     return self;
172 }
173
174 - (void) setColumn: (wxCocoaTableColumn*) col
175 {
176     column = col;
177 }
178
179 - (wxCocoaTableColumn*) column
180 {
181     return column;
182 }
183
184 @end
185
186 class wxNSTableViewCellValue : public wxListWidgetCellValue
187 {
188 public :
189     wxNSTableViewCellValue( id &v ) : value(v)
190     {
191     }
192
193     virtual ~wxNSTableViewCellValue() {}
194
195     virtual void Set( CFStringRef v )
196     {
197         value = [[(NSString*)v retain] autorelease];
198     }
199     virtual void Set( const wxString& value )
200     {
201         Set( (CFStringRef) wxCFStringRef( value ) );
202     }
203     virtual void Set( int v )
204     {
205         value = [NSNumber numberWithInt:v];
206     }
207
208     virtual int GetIntValue() const
209     {
210         if ( [value isKindOfClass:[NSNumber class]] )
211             return [ (NSNumber*) value intValue ];
212
213         return 0;
214     }
215
216     virtual wxString GetStringValue() const
217     {
218         if ( [value isKindOfClass:[NSString class]] )
219             return wxCFStringRef::AsString( (NSString*) value );
220
221         return wxEmptyString;
222     }
223
224 protected:
225     id& value;
226 } ;
227
228 @implementation wxNSTableDataSource
229
230 - (id) init
231 {
232     [super init];
233     impl = nil;
234     return self;
235 }
236
237 - (void)setImplementation: (wxListWidgetCocoaImpl *) theImplementation
238 {
239     impl = theImplementation;
240 }
241
242 - (wxListWidgetCocoaImpl*) implementation
243 {
244     return impl;
245 }
246
247 - (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
248 {
249     wxUnusedVar(aTableView);
250     if ( impl )
251         return impl->ListGetCount();
252     return 0;
253 }
254
255 - (id)tableView:(NSTableView *)aTableView
256         objectValueForTableColumn:(NSTableColumn *)aTableColumn
257         row:(NSInteger)rowIndex
258 {
259     wxUnusedVar(aTableView);
260     wxNSTableColumn* tablecol = (wxNSTableColumn *)aTableColumn;
261     wxListBox* lb = dynamic_cast<wxListBox*>(impl->GetWXPeer());
262     wxCocoaTableColumn* col = [tablecol column];
263     id value = nil;
264     wxNSTableViewCellValue cellvalue(value);
265     lb->GetValueCallback(rowIndex, col, cellvalue);
266     return value;
267 }
268
269 - (void)tableView:(NSTableView *)aTableView
270         setObjectValue:(id)value forTableColumn:(NSTableColumn *)aTableColumn
271         row:(NSInteger)rowIndex
272 {
273     wxUnusedVar(aTableView);
274     wxNSTableColumn* tablecol = (wxNSTableColumn *)aTableColumn;
275     wxListBox* lb = dynamic_cast<wxListBox*>(impl->GetWXPeer());
276     wxCocoaTableColumn* col = [tablecol column];
277     wxNSTableViewCellValue cellvalue(value);
278     lb->SetValueCallback(rowIndex, col, cellvalue);
279 }
280
281 @end
282
283 @implementation wxNSTableView
284
285 + (void)initialize
286 {
287     static BOOL initialized = NO;
288     if (!initialized)
289     {
290         initialized = YES;
291         wxOSXCocoaClassAddWXMethods( self );
292     }
293 }
294
295 - (void) tableViewSelectionDidChange: (NSNotification *) notification
296 {
297     wxUnusedVar(notification);
298     
299     int row = [self selectedRow];
300     
301     if (row == -1) 
302     {
303         // no row selected
304     } 
305     else 
306     {
307         wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( self );
308         wxListBox *list = static_cast<wxListBox*> ( impl->GetWXPeer());
309         wxCHECK_RET( list != NULL , wxT("Listbox expected"));
310         
311         wxCommandEvent event( wxEVT_COMMAND_LISTBOX_SELECTED, list->GetId() );
312         
313         if ((row < 0) || (row > (int) list->GetCount()))  // OS X can select an item below the last item
314             return;
315         
316         if ( !list->MacGetBlockEvents() )
317             list->HandleLineEvent( row, false );
318     }
319     
320
321
322 @end
323
324 //
325 //
326 //
327
328 wxListWidgetCocoaImpl::wxListWidgetCocoaImpl( wxWindowMac* peer, NSScrollView* view, wxNSTableView* tableview, wxNSTableDataSource* data ) :
329     wxWidgetCocoaImpl( peer, view ), m_tableView(tableview), m_dataSource(data)
330 {
331     InstallEventHandler( tableview );
332 }
333
334 wxListWidgetCocoaImpl::~wxListWidgetCocoaImpl()
335 {
336     [m_dataSource release];
337 }
338
339 unsigned int wxListWidgetCocoaImpl::ListGetCount() const
340 {
341     wxListBox* lb = dynamic_cast<wxListBox*> ( GetWXPeer() );
342     return lb->GetCount();
343 }
344
345 //
346 // columns
347 //
348
349 wxListWidgetColumn* wxListWidgetCocoaImpl::InsertTextColumn( unsigned pos, const wxString& WXUNUSED(title), bool editable,
350                                 wxAlignment WXUNUSED(just), int defaultWidth)
351 {
352     wxNSTableColumn* col1 = [[wxNSTableColumn alloc] init];
353     [col1 setEditable:editable];
354
355     unsigned formerColCount = [m_tableView numberOfColumns];
356
357     // there's apparently no way to insert at a specific position
358     [m_tableView addTableColumn:col1 ];
359     if ( pos < formerColCount )
360         [m_tableView moveColumn:formerColCount toColumn:pos];
361
362     if ( defaultWidth >= 0 )
363     {
364         [col1 setMaxWidth:defaultWidth];
365         [col1 setMinWidth:defaultWidth];
366         [col1 setWidth:defaultWidth];
367     }
368     else
369     {
370         [col1 setMaxWidth:1000];
371         [col1 setMinWidth:10];
372         // temporary hack, because I cannot get the automatic column resizing
373         // to work properly
374         [col1 setWidth:1000];
375     }
376     [col1 setResizingMask: NSTableColumnAutoresizingMask];
377     wxCocoaTableColumn* wxcol = new wxCocoaTableColumn( col1, editable );
378     [col1 setColumn:wxcol];
379
380     // owned by the tableview
381     [col1 release];
382     return wxcol;
383 }
384
385 wxListWidgetColumn* wxListWidgetCocoaImpl::InsertCheckColumn( unsigned pos , const wxString& WXUNUSED(title), bool editable,
386                                 wxAlignment WXUNUSED(just), int defaultWidth )
387 {
388    wxNSTableColumn* col1 = [[wxNSTableColumn alloc] init];
389     [col1 setEditable:editable];
390
391     // set your custom cell & set it up
392     NSButtonCell* checkbox = [[NSButtonCell alloc] init];
393     [checkbox setTitle:@""];
394     [checkbox setButtonType:NSSwitchButton];
395     [col1 setDataCell:checkbox] ;
396     [checkbox release];
397
398     unsigned formerColCount = [m_tableView numberOfColumns];
399
400     // there's apparently no way to insert at a specific position
401     [m_tableView addTableColumn:col1 ];
402     if ( pos < formerColCount )
403         [m_tableView moveColumn:formerColCount toColumn:pos];
404
405     if ( defaultWidth >= 0 )
406     {
407         [col1 setMaxWidth:defaultWidth];
408         [col1 setMinWidth:defaultWidth];
409         [col1 setWidth:defaultWidth];
410     }
411
412     [col1 setResizingMask: NSTableColumnNoResizing];
413     wxCocoaTableColumn* wxcol = new wxCocoaTableColumn( col1, editable );
414     [col1 setColumn:wxcol];
415
416     // owned by the tableview
417     [col1 release];
418     return wxcol;
419 }
420
421
422 //
423 // inserting / removing lines
424 //
425
426 void wxListWidgetCocoaImpl::ListInsert( unsigned int WXUNUSED(n) )
427 {
428     [m_tableView reloadData];
429 }
430
431 void wxListWidgetCocoaImpl::ListDelete( unsigned int WXUNUSED(n) )
432 {
433     [m_tableView reloadData];
434 }
435
436 void wxListWidgetCocoaImpl::ListClear()
437 {
438     [m_tableView reloadData];
439 }
440
441 // selecting
442
443 void wxListWidgetCocoaImpl::ListDeselectAll()
444 {
445     [m_tableView deselectAll:nil];
446 }
447
448 void wxListWidgetCocoaImpl::ListSetSelection( unsigned int n, bool select, bool multi )
449 {
450     // TODO
451     if ( select )
452         [m_tableView selectRow: n byExtendingSelection:multi];
453     else
454         [m_tableView deselectRow: n];
455
456 }
457
458 int wxListWidgetCocoaImpl::ListGetSelection() const
459 {
460     return [m_tableView selectedRow];
461 }
462
463 int wxListWidgetCocoaImpl::ListGetSelections( wxArrayInt& aSelections ) const
464 {
465     aSelections.Empty();
466
467     int count = ListGetCount();
468
469     for ( int i = 0; i < count; ++i)
470     {
471         if ([m_tableView isRowSelected:count])
472         aSelections.Add(i);
473     }
474
475     return aSelections.Count();
476 }
477
478 bool wxListWidgetCocoaImpl::ListIsSelected( unsigned int n ) const
479 {
480     return [m_tableView isRowSelected:n];
481 }
482
483 // display
484
485 void wxListWidgetCocoaImpl::ListScrollTo( unsigned int n )
486 {
487     [m_tableView scrollRowToVisible:n];
488 }
489
490
491 void wxListWidgetCocoaImpl::UpdateLine( unsigned int WXUNUSED(n), wxListWidgetColumn* WXUNUSED(col) )
492 {
493     // TODO optimize
494     [m_tableView reloadData];
495 }
496
497 void wxListWidgetCocoaImpl::UpdateLineToEnd( unsigned int WXUNUSED(n))
498 {
499     // TODO optimize
500     [m_tableView reloadData];
501 }
502
503 void wxListWidgetCocoaImpl::controlDoubleAction(WXWidget WXUNUSED(slf),void* WXUNUSED(_cmd), void *WXUNUSED(sender))
504 {
505     wxListBox *list = static_cast<wxListBox*> ( GetWXPeer());
506     wxCHECK_RET( list != NULL , wxT("Listbox expected"));
507
508     int sel = [m_tableView clickedRow];
509     if ((sel < 0) || (sel > (int) list->GetCount()))  // OS X can select an item below the last item (why?)
510        return;
511
512     list->HandleLineEvent( sel, true );
513 }
514
515 bool wxWidgetCocoaImpl::DoHandleKeyEvent(NSEvent *event)
516 {
517     wxKeyEvent wxevent(wxEVT_KEY_DOWN);
518     SetupKeyEvent( wxevent, event );
519     wxevent.SetEventObject(GetWXPeer());
520     bool result = GetWXPeer()->OSXHandleKeyEvent(wxevent);
521
522     // no interpretKeyEvents here, but rerouting to native keyhandling
523     
524     return result;
525 }
526
527 // accessing content
528
529
530 wxWidgetImplType* wxWidgetImpl::CreateListBox( wxWindowMac* wxpeer,
531                                     wxWindowMac* WXUNUSED(parent),
532                                     wxWindowID WXUNUSED(id),
533                                     const wxPoint& pos,
534                                     const wxSize& size,
535                                     long style,
536                                     long WXUNUSED(extraStyle))
537 {
538     NSRect r = wxOSXGetFrameForControl( wxpeer, pos , size ) ;
539     NSScrollView* scrollview = [[NSScrollView alloc] initWithFrame:r];
540
541     // use same scroll flags logic as msw
542
543     [scrollview setHasVerticalScroller:YES];
544
545     if ( style & wxLB_HSCROLL )
546         [scrollview setHasHorizontalScroller:YES];
547
548     [scrollview setAutohidesScrollers: ((style & wxLB_ALWAYS_SB) ? NO : YES)];
549
550     // setting up the true table
551
552     wxNSTableView* tableview = [[wxNSTableView alloc] init];
553     [tableview setDelegate:tableview];
554     // only one multi-select mode available
555     if ( (style & wxLB_EXTENDED) || (style & wxLB_MULTIPLE) )
556         [tableview setAllowsMultipleSelection:YES];
557
558     // simple listboxes have no header row
559     [tableview setHeaderView:nil];
560
561     if ( style & wxLB_HSCROLL )
562         [tableview setColumnAutoresizingStyle:NSTableViewNoColumnAutoresizing];
563     else
564         [tableview setColumnAutoresizingStyle:NSTableViewLastColumnOnlyAutoresizingStyle];
565
566     wxNSTableDataSource* ds = [[ wxNSTableDataSource alloc] init];
567     [tableview setDataSource:ds];
568     [scrollview setDocumentView:tableview];
569     [tableview release];
570
571     wxListWidgetCocoaImpl* c = new wxListWidgetCocoaImpl( wxpeer, scrollview, tableview, ds );
572
573     // temporary hook for dnd
574     [tableview registerForDraggedTypes:[NSArray arrayWithObjects:
575         NSStringPboardType, NSFilenamesPboardType, NSTIFFPboardType, NSPICTPboardType, NSPDFPboardType, nil]];
576
577     [ds setImplementation:c];
578     return c;
579 }
580
581 int wxListBox::DoListHitTest(const wxPoint& WXUNUSED(inpoint)) const
582 {
583 #if wxOSX_USE_CARBON
584     OSStatus err;
585
586     // There are few reasons why this is complicated:
587     // 1) There is no native HitTest function for Mac
588     // 2) GetDataBrowserItemPartBounds only works on visible items
589     // 3) We can't do it through GetDataBrowserTableView[Item]RowHeight
590     //    because what it returns is basically inaccurate in the context
591     //    of the coordinates we want here, but we use this as a guess
592     //    for where the first visible item lies
593
594     wxPoint point = inpoint;
595
596     // get column property ID (req. for call to itempartbounds)
597     DataBrowserTableViewColumnID colId = 0;
598     err = GetDataBrowserTableViewColumnProperty(m_peer->GetControlRef(), 0, &colId);
599     wxCHECK_MSG(err == noErr, wxNOT_FOUND, wxT("Unexpected error from GetDataBrowserTableViewColumnProperty"));
600
601     // OK, first we need to find the first visible item we have -
602     // this will be the "low" for our binary search. There is no real
603     // easy way around this, as we will need to do a SLOW linear search
604     // until we find a visible item, but we can do a cheap calculation
605     // via the row height to speed things up a bit
606     UInt32 scrollx, scrolly;
607     err = GetDataBrowserScrollPosition(m_peer->GetControlRef(), &scrollx, &scrolly);
608     wxCHECK_MSG(err == noErr, wxNOT_FOUND, wxT("Unexpected error from GetDataBrowserScrollPosition"));
609
610     UInt16 height;
611     err = GetDataBrowserTableViewRowHeight(m_peer->GetControlRef(), &height);
612     wxCHECK_MSG(err == noErr, wxNOT_FOUND, wxT("Unexpected error from GetDataBrowserTableViewRowHeight"));
613
614     // these indices are 0-based, as usual, so we need to add 1 to them when
615     // passing them to data browser functions which use 1-based indices
616     int low = scrolly / height,
617         high = GetCount() - 1;
618
619     // search for the first visible item (note that the scroll guess above
620     // is the low bounds of where the item might lie so we only use that as a
621     // starting point - we should reach it within 1 or 2 iterations of the loop)
622     while ( low <= high )
623     {
624         Rect bounds;
625         err = GetDataBrowserItemPartBounds(
626             m_peer->GetControlRef(), low + 1, colId,
627             kDataBrowserPropertyEnclosingPart,
628             &bounds); // note +1 to translate to Mac ID
629         if ( err == noErr )
630             break;
631
632         // errDataBrowserItemNotFound is expected as it simply means that the
633         // item is not currently visible -- but other errors are not
634         wxCHECK_MSG( err == errDataBrowserItemNotFound, wxNOT_FOUND,
635                      wxT("Unexpected error from GetDataBrowserItemPartBounds") );
636
637         low++;
638     }
639
640     // NOW do a binary search for where the item lies, searching low again if
641     // we hit an item that isn't visible
642     while ( low <= high )
643     {
644         int mid = (low + high) / 2;
645
646         Rect bounds;
647         err = GetDataBrowserItemPartBounds(
648             m_peer->GetControlRef(), mid + 1, colId,
649             kDataBrowserPropertyEnclosingPart,
650             &bounds); //note +1 to trans to mac id
651         wxCHECK_MSG( err == noErr || err == errDataBrowserItemNotFound,
652                      wxNOT_FOUND,
653                      wxT("Unexpected error from GetDataBrowserItemPartBounds") );
654
655         if ( err == errDataBrowserItemNotFound )
656         {
657             // item not visible, attempt to find a visible one
658             // search lower
659             high = mid - 1;
660         }
661         else // visible item, do actual hitttest
662         {
663             // if point is within the bounds, return this item (since we assume
664             // all x coords of items are equal we only test the x coord in
665             // equality)
666             if ((point.x >= bounds.left && point.x <= bounds.right) &&
667                 (point.y >= bounds.top && point.y <= bounds.bottom) )
668             {
669                 // found!
670                 return mid;
671             }
672
673             if ( point.y < bounds.top )
674                 // index(bounds) greater then key(point)
675                 high = mid - 1;
676             else
677                 // index(bounds) less then key(point)
678                 low = mid + 1;
679         }
680     }
681 #endif
682     return wxNOT_FOUND;
683 }
684
685 #endif // wxUSE_LISTBOX