1///////////////////////////////////////////////////////////////////////////////
2// Name:        src/osx/cocoa/listbox.mm
3// Purpose:     wxListBox
4// Author:      Stefan Csomor
5// Modified by:
6// Created:     1998-01-01
7// Copyright:   (c) Stefan Csomor
8// Licence:     wxWindows licence
9///////////////////////////////////////////////////////////////////////////////
10
11#include "wx/wxprec.h"
12
13#if wxUSE_LISTBOX
14
15#include "wx/listbox.h"
16#include "wx/dnd.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#include "wx/osx/private.h"
28
29#include <vector>
30
31// forward decls
32
33class wxListWidgetCocoaImpl;
34
35@interface wxNSTableDataSource : NSObject wxOSX_10_6_AND_LATER(<NSTableViewDataSource>)
36{
37    wxListWidgetCocoaImpl* impl;
38}
39
40- (id)tableView:(NSTableView *)aTableView
41        objectValueForTableColumn:(NSTableColumn *)aTableColumn
42        row:(NSInteger)rowIndex;
43
44- (void)tableView:(NSTableView *)aTableView
45        setObjectValue:(id)value forTableColumn:(NSTableColumn *)aTableColumn
46        row:(NSInteger)rowIndex;
47
48- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView;
49
50- (void)setImplementation: (wxListWidgetCocoaImpl *) theImplementation;
51- (wxListWidgetCocoaImpl*) implementation;
52
53@end
54
55@interface wxNSTableView : NSTableView wxOSX_10_6_AND_LATER(<NSTableViewDelegate>)
56{
57}
58
59@end
60
61//
62// table column
63//
64
65class wxCocoaTableColumn;
66
67@interface wxNSTableColumn : NSTableColumn
68{
69    wxCocoaTableColumn* column;
70}
71
72- (void) setColumn: (wxCocoaTableColumn*) col;
73
74- (wxCocoaTableColumn*) column;
75
76@end
77
78class WXDLLIMPEXP_CORE wxCocoaTableColumn : public wxListWidgetColumn
79{
80public :
81    wxCocoaTableColumn( wxNSTableColumn* column, bool editable )
82        : m_column( column ), m_editable(editable)
83    {
84    }
85
86    ~wxCocoaTableColumn()
87    {
88    }
89
90    wxNSTableColumn* GetNSTableColumn() const { return m_column ; }
91
92    bool IsEditable() const { return m_editable; }
93
94protected :
95    wxNSTableColumn* m_column;
96    bool m_editable;
97} ;
98
99NSString* column1 = @"1";
100
101class wxListWidgetCocoaImpl : public wxWidgetCocoaImpl, public wxListWidgetImpl
102{
103public :
104    wxListWidgetCocoaImpl( wxWindowMac* peer, NSScrollView* view, wxNSTableView* tableview, wxNSTableDataSource* data );
105
106    ~wxListWidgetCocoaImpl();
107
108    virtual wxListWidgetColumn*     InsertTextColumn( unsigned pos, const wxString& title, bool editable = false,
109                                wxAlignment just = wxALIGN_LEFT , int defaultWidth = -1)  ;
110    virtual wxListWidgetColumn*     InsertCheckColumn( unsigned pos , const wxString& title, bool editable = false,
111                                wxAlignment just = wxALIGN_LEFT , int defaultWidth =  -1)  ;
112
113    // add and remove
114
115    virtual void            ListDelete( unsigned int n ) ;
116    virtual void            ListInsert( unsigned int n ) ;
117    virtual void            ListClear() ;
118
119    // selecting
120
121    virtual void            ListDeselectAll();
122
123    virtual void            ListSetSelection( unsigned int n, bool select, bool multi ) ;
124    virtual int             ListGetSelection() const ;
125
126    virtual int             ListGetSelections( wxArrayInt& aSelections ) const ;
127
128    virtual bool            ListIsSelected( unsigned int n ) const ;
129
130    // display
131
132    virtual void            ListScrollTo( unsigned int n ) ;
133
134    // accessing content
135
136    virtual unsigned int    ListGetCount() const ;
137    virtual int             DoListHitTest( const wxPoint& inpoint ) const;
138
139    int                     ListGetColumnType( int col )
140    {
141        return col;
142    }
143    virtual void            UpdateLine( unsigned int n, wxListWidgetColumn* col = NULL ) ;
144    virtual void            UpdateLineToEnd( unsigned int n);
145
146    virtual void            controlDoubleAction(WXWidget slf, void* _cmd, void *sender);
147
148
149protected :
150    wxNSTableView*          m_tableView ;
151
152    wxNSTableDataSource*    m_dataSource;
153} ;
154
155//
156// implementations
157//
158
159@implementation wxNSTableColumn
160
161- (id) init
162{
163    self = [super init];
164    column = nil;
165    return self;
166}
167
168- (void) setColumn: (wxCocoaTableColumn*) col
169{
170    column = col;
171}
172
173- (wxCocoaTableColumn*) column
174{
175    return column;
176}
177
178@end
179
180class wxNSTableViewCellValue : public wxListWidgetCellValue
181{
182public :
183    wxNSTableViewCellValue( id &v ) : value(v)
184    {
185    }
186
187    virtual ~wxNSTableViewCellValue() {}
188
189    virtual void Set( CFStringRef v )
190    {
191        value = [[(NSString*)v retain] autorelease];
192    }
193    virtual void Set( const wxString& value )
194    {
195        Set( (CFStringRef) wxCFStringRef( value ) );
196    }
197    virtual void Set( int v )
198    {
199        value = [NSNumber numberWithInt:v];
200    }
201
202    virtual int GetIntValue() const
203    {
204        if ( [value isKindOfClass:[NSNumber class]] )
205            return [ (NSNumber*) value intValue ];
206
207        return 0;
208    }
209
210    virtual wxString GetStringValue() const
211    {
212        if ( [value isKindOfClass:[NSString class]] )
213            return wxCFStringRef::AsString( (NSString*) value );
214
215        return wxEmptyString;
216    }
217
218protected:
219    id& value;
220} ;
221
222@implementation wxNSTableDataSource
223
224- (id) init
225{
226    self = [super init];
227    impl = nil;
228    return self;
229}
230
231- (void)setImplementation: (wxListWidgetCocoaImpl *) theImplementation
232{
233    impl = theImplementation;
234}
235
236- (wxListWidgetCocoaImpl*) implementation
237{
238    return impl;
239}
240
241- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
242{
243    wxUnusedVar(aTableView);
244    if ( impl )
245        return impl->ListGetCount();
246    return 0;
247}
248
249- (id)tableView:(NSTableView *)aTableView
250        objectValueForTableColumn:(NSTableColumn *)aTableColumn
251        row:(NSInteger)rowIndex
252{
253    wxUnusedVar(aTableView);
254    wxNSTableColumn* tablecol = (wxNSTableColumn *)aTableColumn;
255    wxListBox* lb = dynamic_cast<wxListBox*>(impl->GetWXPeer());
256    wxCocoaTableColumn* col = [tablecol column];
257    id value = nil;
258    wxNSTableViewCellValue cellvalue(value);
259    lb->GetValueCallback(rowIndex, col, cellvalue);
260    return value;
261}
262
263- (void)tableView:(NSTableView *)aTableView
264        setObjectValue:(id)value forTableColumn:(NSTableColumn *)aTableColumn
265        row:(NSInteger)rowIndex
266{
267    wxUnusedVar(aTableView);
268    wxNSTableColumn* tablecol = (wxNSTableColumn *)aTableColumn;
269    wxListBox* lb = dynamic_cast<wxListBox*>(impl->GetWXPeer());
270    wxCocoaTableColumn* col = [tablecol column];
271    wxNSTableViewCellValue cellvalue(value);
272    lb->SetValueCallback(rowIndex, col, cellvalue);
273}
274
275@end
276
277@implementation wxNSTableView
278
279+ (void)initialize
280{
281    static BOOL initialized = NO;
282    if (!initialized)
283    {
284        initialized = YES;
285        wxOSXCocoaClassAddWXMethods( self );
286    }
287}
288
289- (void) tableViewSelectionDidChange: (NSNotification *) notification
290{
291    wxUnusedVar(notification);
292
293    int row = [self selectedRow];
294
295    wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( self );
296    wxListBox* const list = wxDynamicCast(impl->GetWXPeer(), wxListBox);
297    wxCHECK_RET( list != NULL , "Associated control should be a wxListBox" );
298
299    list->MacHandleSelectionChange(row);
300}
301
302- (void)setFont:(NSFont *)aFont
303{
304    NSArray *tableColumns = [self tableColumns];
305    unsigned int columnIndex = [tableColumns count];
306    while (columnIndex--)
307        [[(NSTableColumn *)[tableColumns objectAtIndex:columnIndex] dataCell] setFont:aFont];
308
309    [self setRowHeight:[gNSLayoutManager defaultLineHeightForFont:aFont]+2];
310}
311
312- (void) setControlSize:(NSControlSize) size
313{
314    NSArray *tableColumns = [self tableColumns];
315    unsigned int columnIndex = [tableColumns count];
316    while (columnIndex--)
317        [[(NSTableColumn *)[tableColumns objectAtIndex:columnIndex] dataCell] setControlSize:size];
318}
319
320@end
321
322//
323//
324//
325
326wxListWidgetCocoaImpl::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
332wxListWidgetCocoaImpl::~wxListWidgetCocoaImpl()
333{
334    [m_dataSource release];
335}
336
337unsigned int wxListWidgetCocoaImpl::ListGetCount() const
338{
339    wxListBox* lb = dynamic_cast<wxListBox*> ( GetWXPeer() );
340    return lb->GetCount();
341}
342
343//
344// columns
345//
346
347wxListWidgetColumn* 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
376    wxListBox *list = static_cast<wxListBox*> ( GetWXPeer());
377    if ( list != NULL )
378        [[col1 dataCell] setFont:list->GetFont().OSXGetNSFont()];
379
380    wxCocoaTableColumn* wxcol = new wxCocoaTableColumn( col1, editable );
381    [col1 setColumn:wxcol];
382
383    // owned by the tableview
384    [col1 release];
385    return wxcol;
386}
387
388wxListWidgetColumn* wxListWidgetCocoaImpl::InsertCheckColumn( unsigned pos , const wxString& WXUNUSED(title), bool editable,
389                                wxAlignment WXUNUSED(just), int defaultWidth )
390{
391   wxNSTableColumn* col1 = [[wxNSTableColumn alloc] init];
392    [col1 setEditable:editable];
393
394    // set your custom cell & set it up
395    NSButtonCell* checkbox = [[NSButtonCell alloc] init];
396    [checkbox setTitle:@""];
397    [checkbox setButtonType:NSSwitchButton];
398    [col1 setDataCell:checkbox] ;
399
400    wxListBox *list = static_cast<wxListBox*> ( GetWXPeer());
401    if ( list != NULL )
402    {
403        NSControlSize size = NSRegularControlSize;
404
405        switch ( list->GetWindowVariant() )
406        {
407            case wxWINDOW_VARIANT_NORMAL :
408                size = NSRegularControlSize;
409                break ;
410
411            case wxWINDOW_VARIANT_SMALL :
412                size = NSSmallControlSize;
413                break ;
414
415            case wxWINDOW_VARIANT_MINI :
416                size = NSMiniControlSize;
417                break ;
418
419            case wxWINDOW_VARIANT_LARGE :
420                size = NSRegularControlSize;
421                break ;
422
423            default:
424                break ;
425        }
426
427        [[col1 dataCell] setControlSize:size];
428        // although there is no text, it may help to get the correct vertical layout
429        [[col1 dataCell] setFont:list->GetFont().OSXGetNSFont()];
430    }
431
432    [checkbox release];
433
434    unsigned formerColCount = [m_tableView numberOfColumns];
435
436    // there's apparently no way to insert at a specific position
437    [m_tableView addTableColumn:col1 ];
438    if ( pos < formerColCount )
439        [m_tableView moveColumn:formerColCount toColumn:pos];
440
441    if ( defaultWidth >= 0 )
442    {
443        [col1 setMaxWidth:defaultWidth];
444        [col1 setMinWidth:defaultWidth];
445        [col1 setWidth:defaultWidth];
446    }
447
448    [col1 setResizingMask: NSTableColumnNoResizing];
449    wxCocoaTableColumn* wxcol = new wxCocoaTableColumn( col1, editable );
450    [col1 setColumn:wxcol];
451
452    // owned by the tableview
453    [col1 release];
454    return wxcol;
455}
456
457
458//
459// inserting / removing lines
460//
461
462void wxListWidgetCocoaImpl::ListInsert( unsigned int WXUNUSED(n) )
463{
464    [m_tableView reloadData];
465}
466
467void wxListWidgetCocoaImpl::ListDelete( unsigned int WXUNUSED(n) )
468{
469    [m_tableView reloadData];
470}
471
472void wxListWidgetCocoaImpl::ListClear()
473{
474    [m_tableView reloadData];
475}
476
477// selecting
478
479void wxListWidgetCocoaImpl::ListDeselectAll()
480{
481    [m_tableView deselectAll:nil];
482}
483
484void wxListWidgetCocoaImpl::ListSetSelection( unsigned int n, bool select, bool multi )
485{
486    // TODO
487    if ( select )
488        [m_tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:n]
489		     byExtendingSelection:multi];
490    else
491        [m_tableView deselectRow: n];
492
493}
494
495int wxListWidgetCocoaImpl::ListGetSelection() const
496{
497    return [m_tableView selectedRow];
498}
499
500int wxListWidgetCocoaImpl::ListGetSelections( wxArrayInt& aSelections ) const
501{
502    aSelections.Empty();
503
504    int count = ListGetCount();
505
506    for ( int i = 0; i < count; ++i)
507    {
508        if ([m_tableView isRowSelected:i])
509        aSelections.Add(i);
510    }
511
512    return aSelections.Count();
513}
514
515bool wxListWidgetCocoaImpl::ListIsSelected( unsigned int n ) const
516{
517    return [m_tableView isRowSelected:n];
518}
519
520// display
521
522void wxListWidgetCocoaImpl::ListScrollTo( unsigned int n )
523{
524    [m_tableView scrollRowToVisible:n];
525}
526
527
528void wxListWidgetCocoaImpl::UpdateLine( unsigned int WXUNUSED(n), wxListWidgetColumn* WXUNUSED(col) )
529{
530    // TODO optimize
531    [m_tableView reloadData];
532}
533
534void wxListWidgetCocoaImpl::UpdateLineToEnd( unsigned int WXUNUSED(n))
535{
536    // TODO optimize
537    [m_tableView reloadData];
538}
539
540void wxListWidgetCocoaImpl::controlDoubleAction(WXWidget WXUNUSED(slf),void* WXUNUSED(_cmd), void *WXUNUSED(sender))
541{
542    wxListBox *list = static_cast<wxListBox*> ( GetWXPeer());
543    wxCHECK_RET( list != NULL , wxT("Listbox expected"));
544
545    int sel = [m_tableView clickedRow];
546    if ((sel < 0) || (sel > (int) list->GetCount()))  // OS X can select an item below the last item (why?)
547       return;
548
549    list->HandleLineEvent( sel, true );
550}
551
552// accessing content
553
554
555wxWidgetImplType* wxWidgetImpl::CreateListBox( wxWindowMac* wxpeer,
556                                    wxWindowMac* WXUNUSED(parent),
557                                    wxWindowID WXUNUSED(id),
558                                    const wxPoint& pos,
559                                    const wxSize& size,
560                                    long style,
561                                    long WXUNUSED(extraStyle))
562{
563    NSRect r = wxOSXGetFrameForControl( wxpeer, pos , size ) ;
564    NSScrollView* scrollview = [[NSScrollView alloc] initWithFrame:r];
565
566    // use same scroll flags logic as msw
567
568    [scrollview setHasVerticalScroller:YES];
569
570    if ( style & wxLB_HSCROLL )
571        [scrollview setHasHorizontalScroller:YES];
572
573    [scrollview setAutohidesScrollers: ((style & wxLB_ALWAYS_SB) ? NO : YES)];
574
575    // setting up the true table
576
577    wxNSTableView* tableview = [[wxNSTableView alloc] init];
578    [tableview setDelegate:tableview];
579    // only one multi-select mode available
580    if ( (style & wxLB_EXTENDED) || (style & wxLB_MULTIPLE) )
581        [tableview setAllowsMultipleSelection:YES];
582
583    // simple listboxes have no header row
584    [tableview setHeaderView:nil];
585
586    if ( style & wxLB_HSCROLL )
587        [tableview setColumnAutoresizingStyle:NSTableViewNoColumnAutoresizing];
588    else
589        [tableview setColumnAutoresizingStyle:NSTableViewLastColumnOnlyAutoresizingStyle];
590
591    wxNSTableDataSource* ds = [[ wxNSTableDataSource alloc] init];
592    [tableview setDataSource:ds];
593    [scrollview setDocumentView:tableview];
594    [tableview release];
595
596    wxListWidgetCocoaImpl* c = new wxListWidgetCocoaImpl( wxpeer, scrollview, tableview, ds );
597
598    // temporary hook for dnd
599 //   [tableview registerForDraggedTypes:[NSArray arrayWithObjects:
600 //       NSStringPboardType, NSFilenamesPboardType, (NSString*) kPasteboardTypeFileURLPromise, NSTIFFPboardType, NSPICTPboardType, NSPDFPboardType, nil]];
601
602    [ds setImplementation:c];
603    return c;
604}
605
606int wxListWidgetCocoaImpl::DoListHitTest(const wxPoint& inpoint) const
607{
608    // translate inpoint to listpoint via scrollview
609    NSPoint p = wxToNSPoint( m_osxView, inpoint );
610    p = [m_osxView convertPoint:p toView:m_tableView];
611    // hittest using new point
612    NSInteger i = [m_tableView rowAtPoint:p];
613    return i;
614}
615
616#endif // wxUSE_LISTBOX
617