]> git.saurik.com Git - wxWidgets.git/blob - src/osx/cocoa/listbox.mm
f354c9bc0c0c18d25e5a34fa24369d0b1cdd7879
[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 protected :
154 wxNSTableView* m_tableView ;
155
156 wxNSTableDataSource* m_dataSource;
157 } ;
158
159 //
160 // implementations
161 //
162
163 @implementation wxNSTableColumn
164
165 - (id) init
166 {
167 [super init];
168 column = nil;
169 return self;
170 }
171
172 - (void) setColumn: (wxCocoaTableColumn*) col
173 {
174 column = col;
175 }
176
177 - (wxCocoaTableColumn*) column
178 {
179 return column;
180 }
181
182 @end
183
184 class wxNSTableViewCellValue : public wxListWidgetCellValue
185 {
186 public :
187 wxNSTableViewCellValue( id &v ) : value(v)
188 {
189 }
190
191 virtual ~wxNSTableViewCellValue() {}
192
193 virtual void Set( CFStringRef v )
194 {
195 value = [[(NSString*)v retain] autorelease];
196 }
197 virtual void Set( const wxString& value )
198 {
199 Set( (CFStringRef) wxCFStringRef( value ) );
200 }
201 virtual void Set( int v )
202 {
203 value = [NSNumber numberWithInt:v];
204 }
205
206 virtual int GetIntValue() const
207 {
208 if ( [value isKindOfClass:[NSNumber class]] )
209 return [ (NSNumber*) value intValue ];
210
211 return 0;
212 }
213
214 virtual wxString GetStringValue() const
215 {
216 if ( [value isKindOfClass:[NSString class]] )
217 return wxCFStringRef::AsString( (NSString*) value );
218
219 return wxEmptyString;
220 }
221
222 protected:
223 id& value;
224 } ;
225
226 @implementation wxNSTableDataSource
227
228 - (id) init
229 {
230 [super init];
231 impl = nil;
232 return self;
233 }
234
235 - (void)setImplementation: (wxListWidgetCocoaImpl *) theImplementation
236 {
237 impl = theImplementation;
238 }
239
240 - (wxListWidgetCocoaImpl*) implementation
241 {
242 return impl;
243 }
244
245 - (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
246 {
247 wxUnusedVar(aTableView);
248 if ( impl )
249 return impl->ListGetCount();
250 return 0;
251 }
252
253 - (id)tableView:(NSTableView *)aTableView
254 objectValueForTableColumn:(NSTableColumn *)aTableColumn
255 row:(NSInteger)rowIndex
256 {
257 wxUnusedVar(aTableView);
258 wxNSTableColumn* tablecol = (wxNSTableColumn *)aTableColumn;
259 wxListBox* lb = dynamic_cast<wxListBox*>(impl->GetWXPeer());
260 wxCocoaTableColumn* col = [tablecol column];
261 id value = nil;
262 wxNSTableViewCellValue cellvalue(value);
263 lb->GetValueCallback(rowIndex, col, cellvalue);
264 return value;
265 }
266
267 - (void)tableView:(NSTableView *)aTableView
268 setObjectValue:(id)value forTableColumn:(NSTableColumn *)aTableColumn
269 row:(NSInteger)rowIndex
270 {
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);
277 }
278
279 @end
280
281 @implementation wxNSTableView
282
283 + (void)initialize
284 {
285 static BOOL initialized = NO;
286 if (!initialized)
287 {
288 initialized = YES;
289 wxOSXCocoaClassAddWXMethods( self );
290 }
291 }
292
293 - (void) tableViewSelectionDidChange: (NSNotification *) notification
294 {
295 wxUnusedVar(notification);
296
297 int row = [self selectedRow];
298
299 if (row == -1)
300 {
301 // no row selected
302 }
303 else
304 {
305 wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( self );
306 wxListBox *list = static_cast<wxListBox*> ( impl->GetWXPeer());
307 wxCHECK_RET( list != NULL , wxT("Listbox expected"));
308
309 wxCommandEvent event( wxEVT_COMMAND_LISTBOX_SELECTED, list->GetId() );
310
311 if ((row < 0) || (row > (int) list->GetCount())) // OS X can select an item below the last item
312 return;
313
314 if ( !list->MacGetBlockEvents() )
315 list->HandleLineEvent( row, false );
316 }
317
318 }
319
320 @end
321
322 //
323 //
324 //
325
326 wxListWidgetCocoaImpl::wxListWidgetCocoaImpl( wxWindowMac* peer, NSScrollView* view, wxNSTableView* tableview, wxNSTableDataSource* data ) :
327 wxWidgetCocoaImpl( peer, view ), m_tableView(tableview), m_dataSource(data)
328 {
329 InstallEventHandler( tableview );
330 }
331
332 wxListWidgetCocoaImpl::~wxListWidgetCocoaImpl()
333 {
334 [m_dataSource release];
335 }
336
337 unsigned int wxListWidgetCocoaImpl::ListGetCount() const
338 {
339 wxListBox* lb = dynamic_cast<wxListBox*> ( GetWXPeer() );
340 return lb->GetCount();
341 }
342
343 //
344 // columns
345 //
346
347 wxListWidgetColumn* wxListWidgetCocoaImpl::InsertTextColumn( unsigned pos, const wxString& WXUNUSED(title), bool editable,
348 wxAlignment WXUNUSED(just), int defaultWidth)
349 {
350 wxNSTableColumn* col1 = [[wxNSTableColumn alloc] init];
351 [col1 setEditable:editable];
352
353 unsigned formerColCount = [m_tableView numberOfColumns];
354
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];
359
360 if ( defaultWidth >= 0 )
361 {
362 [col1 setMaxWidth:defaultWidth];
363 [col1 setMinWidth:defaultWidth];
364 [col1 setWidth:defaultWidth];
365 }
366 else
367 {
368 [col1 setMaxWidth:1000];
369 [col1 setMinWidth:10];
370 // temporary hack, because I cannot get the automatic column resizing
371 // to work properly
372 [col1 setWidth:1000];
373 }
374 [col1 setResizingMask: NSTableColumnAutoresizingMask];
375 wxCocoaTableColumn* wxcol = new wxCocoaTableColumn( col1, editable );
376 [col1 setColumn:wxcol];
377
378 // owned by the tableview
379 [col1 release];
380 return wxcol;
381 }
382
383 wxListWidgetColumn* wxListWidgetCocoaImpl::InsertCheckColumn( unsigned pos , const wxString& WXUNUSED(title), bool editable,
384 wxAlignment WXUNUSED(just), int defaultWidth )
385 {
386 wxNSTableColumn* col1 = [[wxNSTableColumn alloc] init];
387 [col1 setEditable:editable];
388
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] ;
394 [checkbox release];
395
396 unsigned formerColCount = [m_tableView numberOfColumns];
397
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];
402
403 if ( defaultWidth >= 0 )
404 {
405 [col1 setMaxWidth:defaultWidth];
406 [col1 setMinWidth:defaultWidth];
407 [col1 setWidth:defaultWidth];
408 }
409
410 [col1 setResizingMask: NSTableColumnNoResizing];
411 wxCocoaTableColumn* wxcol = new wxCocoaTableColumn( col1, editable );
412 [col1 setColumn:wxcol];
413
414 // owned by the tableview
415 [col1 release];
416 return wxcol;
417 }
418
419
420 //
421 // inserting / removing lines
422 //
423
424 void wxListWidgetCocoaImpl::ListInsert( unsigned int WXUNUSED(n) )
425 {
426 [m_tableView reloadData];
427 }
428
429 void wxListWidgetCocoaImpl::ListDelete( unsigned int WXUNUSED(n) )
430 {
431 [m_tableView reloadData];
432 }
433
434 void wxListWidgetCocoaImpl::ListClear()
435 {
436 [m_tableView reloadData];
437 }
438
439 // selecting
440
441 void wxListWidgetCocoaImpl::ListDeselectAll()
442 {
443 [m_tableView deselectAll:nil];
444 }
445
446 void wxListWidgetCocoaImpl::ListSetSelection( unsigned int n, bool select, bool multi )
447 {
448 // TODO
449 if ( select )
450 [m_tableView selectRow: n byExtendingSelection:multi];
451 else
452 [m_tableView deselectRow: n];
453
454 }
455
456 int wxListWidgetCocoaImpl::ListGetSelection() const
457 {
458 return [m_tableView selectedRow];
459 }
460
461 int wxListWidgetCocoaImpl::ListGetSelections( wxArrayInt& aSelections ) const
462 {
463 aSelections.Empty();
464
465 int count = ListGetCount();
466
467 for ( int i = 0; i < count; ++i)
468 {
469 if ([m_tableView isRowSelected:count])
470 aSelections.Add(i);
471 }
472
473 return aSelections.Count();
474 }
475
476 bool wxListWidgetCocoaImpl::ListIsSelected( unsigned int n ) const
477 {
478 return [m_tableView isRowSelected:n];
479 }
480
481 // display
482
483 void wxListWidgetCocoaImpl::ListScrollTo( unsigned int n )
484 {
485 [m_tableView scrollRowToVisible:n];
486 }
487
488
489 void wxListWidgetCocoaImpl::UpdateLine( unsigned int WXUNUSED(n), wxListWidgetColumn* WXUNUSED(col) )
490 {
491 // TODO optimize
492 [m_tableView reloadData];
493 }
494
495 void wxListWidgetCocoaImpl::UpdateLineToEnd( unsigned int WXUNUSED(n))
496 {
497 // TODO optimize
498 [m_tableView reloadData];
499 }
500
501 void wxListWidgetCocoaImpl::controlDoubleAction(WXWidget WXUNUSED(slf),void* WXUNUSED(_cmd), void *WXUNUSED(sender))
502 {
503 wxListBox *list = static_cast<wxListBox*> ( GetWXPeer());
504 wxCHECK_RET( list != NULL , wxT("Listbox expected"));
505
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?)
508 return;
509
510 list->HandleLineEvent( sel, true );
511 }
512
513 // accessing content
514
515
516 wxWidgetImplType* wxWidgetImpl::CreateListBox( wxWindowMac* wxpeer,
517 wxWindowMac* WXUNUSED(parent),
518 wxWindowID WXUNUSED(id),
519 const wxPoint& pos,
520 const wxSize& size,
521 long style,
522 long WXUNUSED(extraStyle))
523 {
524 NSRect r = wxOSXGetFrameForControl( wxpeer, pos , size ) ;
525 NSScrollView* scrollview = [[NSScrollView alloc] initWithFrame:r];
526
527 // use same scroll flags logic as msw
528
529 [scrollview setHasVerticalScroller:YES];
530
531 if ( style & wxLB_HSCROLL )
532 [scrollview setHasHorizontalScroller:YES];
533
534 [scrollview setAutohidesScrollers: ((style & wxLB_ALWAYS_SB) ? NO : YES)];
535
536 // setting up the true table
537
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];
543
544 // simple listboxes have no header row
545 [tableview setHeaderView:nil];
546
547 if ( style & wxLB_HSCROLL )
548 [tableview setColumnAutoresizingStyle:NSTableViewNoColumnAutoresizing];
549 else
550 [tableview setColumnAutoresizingStyle:NSTableViewLastColumnOnlyAutoresizingStyle];
551
552 wxNSTableDataSource* ds = [[ wxNSTableDataSource alloc] init];
553 [tableview setDataSource:ds];
554 [scrollview setDocumentView:tableview];
555 [tableview release];
556
557 wxListWidgetCocoaImpl* c = new wxListWidgetCocoaImpl( wxpeer, scrollview, tableview, ds );
558
559 // temporary hook for dnd
560 [tableview registerForDraggedTypes:[NSArray arrayWithObjects:
561 NSStringPboardType, NSFilenamesPboardType, NSTIFFPboardType, NSPICTPboardType, NSPDFPboardType, nil]];
562
563 [ds setImplementation:c];
564 return c;
565 }
566
567 int wxListBox::DoListHitTest(const wxPoint& WXUNUSED(inpoint)) const
568 {
569 #if wxOSX_USE_CARBON
570 OSStatus err;
571
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
579
580 wxPoint point = inpoint;
581
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"));
586
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"));
595
596 UInt16 height;
597 err = GetDataBrowserTableViewRowHeight(m_peer->GetControlRef(), &height);
598 wxCHECK_MSG(err == noErr, wxNOT_FOUND, wxT("Unexpected error from GetDataBrowserTableViewRowHeight"));
599
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;
604
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 )
609 {
610 Rect bounds;
611 err = GetDataBrowserItemPartBounds(
612 m_peer->GetControlRef(), low + 1, colId,
613 kDataBrowserPropertyEnclosingPart,
614 &bounds); // note +1 to translate to Mac ID
615 if ( err == noErr )
616 break;
617
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") );
622
623 low++;
624 }
625
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 )
629 {
630 int mid = (low + high) / 2;
631
632 Rect bounds;
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,
638 wxNOT_FOUND,
639 wxT("Unexpected error from GetDataBrowserItemPartBounds") );
640
641 if ( err == errDataBrowserItemNotFound )
642 {
643 // item not visible, attempt to find a visible one
644 // search lower
645 high = mid - 1;
646 }
647 else // visible item, do actual hitttest
648 {
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
651 // equality)
652 if ((point.x >= bounds.left && point.x <= bounds.right) &&
653 (point.y >= bounds.top && point.y <= bounds.bottom) )
654 {
655 // found!
656 return mid;
657 }
658
659 if ( point.y < bounds.top )
660 // index(bounds) greater then key(point)
661 high = mid - 1;
662 else
663 // index(bounds) less then key(point)
664 low = mid + 1;
665 }
666 }
667 #endif
668 return wxNOT_FOUND;
669 }
670
671 #endif // wxUSE_LISTBOX