Ensure that the NSTableColumn width is that of its largest item to enable
[wxWidgets.git] / src / cocoa / listbox.mm
1 /////////////////////////////////////////////////////////////////////////////
2 // Name:        src/cocoa/listbox.mm
3 // Purpose:     wxListBox
4 // Author:      David Elliott
5 // Modified by:
6 // Created:     2003/03/18
7 // Id:          $Id$
8 // Copyright:   (c) 2003 David Elliott
9 // Licence:     wxWidgets 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/app.h"
21 #endif //WX_PRECOMP
22
23 #include "wx/cocoa/string.h"
24 #include "wx/cocoa/autorelease.h"
25 #include "wx/cocoa/ObjcRef.h"
26 #include "wx/cocoa/private/scrollview.h"
27 #include "wx/cocoa/NSTableDataSource.h"
28
29 #import <Foundation/NSArray.h>
30 #import <Foundation/NSEnumerator.h>
31 #import <AppKit/NSTableView.h>
32 #import <AppKit/NSTableColumn.h>
33 #import <AppKit/NSScrollView.h>
34 #import <AppKit/NSCell.h>
35   
36 // ============================================================================
37 // @class wxCocoaListBoxNSTableDataSource
38 // ============================================================================
39 // 2.8 hack: We can't add an i-var to wxListBox so we add one here
40 @interface wxCocoaListBoxNSTableDataSource : wxCocoaNSTableDataSource
41 {
42     BOOL m_needsUpdate;
43 }
44
45 @end
46 WX_DECLARE_GET_OBJC_CLASS(wxCocoaListBoxNSTableDataSource,wxCocoaNSTableDataSource)
47
48 @implementation wxCocoaListBoxNSTableDataSource
49 // No methods
50 @end
51 WX_IMPLEMENT_GET_OBJC_CLASS_WITH_UNIQUIFIED_SUPERCLASS(wxCocoaListBoxNSTableDataSource,wxCocoaNSTableDataSource)
52
53
54 // ============================================================================
55 // helper functions
56 // ============================================================================
57
58 static CGFloat _TableColumnMaxWidthForItems(NSTableColumn *tableColumn, NSArray *items)
59 {
60     wxAutoNSAutoreleasePool pool;
61
62     NSCell *dataCell = [[[tableColumn dataCell] copy] autorelease];
63     CGFloat width = 0.0f;
64     NSEnumerator *itemEnum = [items objectEnumerator];
65     NSString *item;
66     while( (item = [itemEnum nextObject]) != nil )
67     {
68         [dataCell setStringValue: item];
69         NSSize itemSize = [dataCell cellSize];
70         CGFloat itemWidth = itemSize.width;
71         if(itemWidth > width)
72             width = itemWidth;
73     }
74     return width;
75 }
76
77 static void _SetWidthOfTableColumnToFitItems(NSTableColumn *tableColumn, NSArray *items)
78 {
79     CGFloat width = _TableColumnMaxWidthForItems(tableColumn, items);
80     [tableColumn setWidth:width];
81     [tableColumn setMinWidth:width];
82 }
83
84 // ============================================================================
85 // class wxListBox
86 // ============================================================================
87
88 IMPLEMENT_DYNAMIC_CLASS(wxListBox, wxControlWithItems)
89 BEGIN_EVENT_TABLE(wxListBox, wxListBoxBase)
90     EVT_IDLE(wxListBox::_WxCocoa_OnIdle)
91 END_EVENT_TABLE()
92 WX_IMPLEMENT_COCOA_OWNER(wxListBox,NSTableView,NSControl,NSView)
93
94 bool wxListBox::Create(wxWindow *parent, wxWindowID winid,
95             const wxPoint& pos,
96             const wxSize& size,
97             const wxArrayString& choices,
98             long style,
99             const wxValidator& validator,
100             const wxString& name)
101 {
102     wxCArrayString chs(choices);
103
104     return Create(parent, winid, pos, size, chs.GetCount(), chs.GetStrings(),
105                   style, validator, name);
106 }
107
108 bool wxListBox::Create(wxWindow *parent, wxWindowID winid,
109             const wxPoint& pos,
110             const wxSize& size,
111             int n, const wxString choices[],
112             long style,
113             const wxValidator& validator,
114             const wxString& name)
115 {
116 /*
117 wxLB_SINGLE
118 Single-selection list.
119
120 wxLB_MULTIPLE
121 Multiple-selection list: the user can toggle multiple items on and off.
122
123 wxLB_EXTENDED
124 Extended-selection list: the user can select multiple items using the SHIFT key and the mouse or special key combinations.
125
126 wxLB_HSCROLL
127 Create horizontal scrollbar if contents are too wide (Windows only).
128
129 wxLB_ALWAYS_SB
130 Always show a vertical scrollbar.
131
132 wxLB_NEEDED_SB
133 Only create a vertical scrollbar if needed.
134
135 wxLB_SORT
136 The listbox contents are sorted in alphabetical order.
137 */
138     wxAutoNSAutoreleasePool pool;
139     if(!CreateControl(parent,winid,pos,size,style,validator,name))
140         return false;
141
142     // Provide the data
143     m_cocoaItems = wxGCSafeRetain([NSMutableArray arrayWithCapacity:n]);
144     for(int i=0; i < n; i++)
145     {
146         [m_cocoaItems addObject: wxNSStringWithWxString(choices[i])];
147     }
148     // Remove everything
149     m_itemClientData.Clear();
150     // Initialize n elements to NULL
151     m_itemClientData.SetCount(n,NULL);
152
153     SetNSTableView([[NSTableView alloc] initWithFrame: MakeDefaultNSRect(size)]);
154     [m_cocoaNSView release];
155     [GetNSTableView() setHeaderView: nil];
156
157     // Set up the data source
158     m_cocoaDataSource = [[WX_GET_OBJC_CLASS(wxCocoaListBoxNSTableDataSource) alloc] init];
159     [GetNSTableView() setDataSource:m_cocoaDataSource];
160
161     // Add the single column
162     NSTableColumn *tableColumn = [[NSTableColumn alloc] initWithIdentifier:nil];
163     [GetNSTableView() addTableColumn: tableColumn];
164     [tableColumn release];
165
166     [GetNSTableView() sizeToFit];
167     // Finish
168     if(m_parent)
169         m_parent->CocoaAddChild(this);
170     // NSTableView does WEIRD things with sizes.  Wrapping it in an
171     // NSScrollView seems to be the only reasonable solution.
172     CocoaCreateNSScrollView();
173     SetInitialFrameRect(pos,size);
174
175     [m_wxCocoaScrollView->GetNSScrollView() setHasVerticalScroller:YES];
176     // Pre-10.3: Always show vertical scroller, never show horizontal scroller
177     // Post-10.3: Show scrollers dynamically (turn them both on, set auto-hide)
178     if([m_wxCocoaScrollView->GetNSScrollView() respondsToSelector:@selector(setAutohidesScrollers:)])
179     {
180         [m_wxCocoaScrollView->GetNSScrollView() setHasHorizontalScroller:YES];
181         [m_wxCocoaScrollView->GetNSScrollView() setAutohidesScrollers:YES];
182     }
183
184     // Set up extended/multiple selection flags
185     if ((style & wxLB_EXTENDED) || (style & wxLB_MULTIPLE))
186         //diff is that mult requires shift down for multi selection
187         [GetNSTableView() setAllowsMultipleSelection:true];
188
189     [GetNSTableView() setAllowsColumnSelection:false];
190     _SetWidthOfTableColumnToFitItems(tableColumn, m_cocoaItems);
191     return true;
192 }
193
194 wxListBox::~wxListBox()
195 {
196     [GetNSTableView() setDataSource: nil];
197     [m_cocoaDataSource release];
198     wxGCSafeRelease(m_cocoaItems);
199     m_cocoaItems = nil;
200     DisassociateNSTableView(GetNSTableView());
201 }
202
203 bool wxListBox::_WxCocoa_GetNeedsUpdate()
204 {
205     return static_cast<wxCocoaListBoxNSTableDataSource*>(m_cocoaDataSource)->m_needsUpdate;
206 }
207
208 void wxListBox::_WxCocoa_SetNeedsUpdate(bool needsUpdate)
209 {
210     static_cast<wxCocoaListBoxNSTableDataSource*>(m_cocoaDataSource)->m_needsUpdate = needsUpdate;
211 }
212
213 void wxListBox::_WxCocoa_OnIdle(wxIdleEvent &event)
214 {
215     event.Skip();
216     if(_WxCocoa_GetNeedsUpdate())
217     {
218         _SetWidthOfTableColumnToFitItems([[GetNSTableView() tableColumns] objectAtIndex:0], m_cocoaItems);
219         [GetNSTableView() tile];
220         [GetNSTableView() reloadData];
221         _WxCocoa_SetNeedsUpdate(false);
222     }
223 }
224
225 int wxListBox::CocoaDataSource_numberOfRows()
226 {
227     return [m_cocoaItems count];
228 }
229
230 struct objc_object* wxListBox::CocoaDataSource_objectForTableColumn(
231         WX_NSTableColumn tableColumn, int rowIndex)
232 {
233     return [m_cocoaItems objectAtIndex:rowIndex];
234 }
235
236 // pure virtuals from wxListBoxBase
237 bool wxListBox::IsSelected(int n) const
238 {
239     return [GetNSTableView() isRowSelected: n];
240 }
241
242 void wxListBox::DoSetSelection(int n, bool select)
243 {
244     if(select)
245         [GetNSTableView() selectRow: n byExtendingSelection:NO];
246     else
247         [GetNSTableView() deselectRow: n];
248 }
249
250 int wxListBox::GetSelections(wxArrayInt& aSelections) const
251 {
252     aSelections.Clear();
253     NSEnumerator *enumerator = [GetNSTableView() selectedRowEnumerator];
254     while(NSNumber *num = [enumerator nextObject])
255     {
256         aSelections.Add([num intValue]);
257     }
258     return [GetNSTableView() numberOfSelectedRows];
259 }
260
261 int wxListBox::DoInsertItems(const wxArrayStringsAdapter & items, unsigned int pos, void **clientData, wxClientDataType type)
262 {
263     wxAutoNSAutoreleasePool pool;
264
265     const unsigned int numItems = items.GetCount();
266     for ( unsigned int i = 0; i < numItems; ++i, ++pos )
267     {
268         [m_cocoaItems insertObject: wxNSStringWithWxString(items[i])
269             atIndex: pos];
270         m_itemClientData.Insert(NULL, pos);
271         AssignNewItemClientData(pos, clientData, i, type);
272     }
273
274     _WxCocoa_SetNeedsUpdate(true);
275     return pos - 1;
276 }
277
278 void wxListBox::DoSetFirstItem(int n)
279 {
280     [m_cocoaItems exchangeObjectAtIndex:0 withObjectAtIndex:n];
281     void* pOld = m_itemClientData[n];
282     m_itemClientData[n] = m_itemClientData[0];
283     m_itemClientData[0] = pOld;
284     _WxCocoa_SetNeedsUpdate(true);
285 }
286
287
288 // pure virtuals from wxItemContainer
289     // deleting items
290 void wxListBox::DoClear()
291 {
292     [m_cocoaItems removeAllObjects];
293     m_itemClientData.Clear();
294     _WxCocoa_SetNeedsUpdate(true);
295 }
296
297 void wxListBox::DoDeleteOneItem(unsigned int n)
298 {
299     [m_cocoaItems removeObjectAtIndex:n];
300     m_itemClientData.RemoveAt(n);
301     _WxCocoa_SetNeedsUpdate(true);
302 }
303
304     // accessing strings
305 unsigned int wxListBox::GetCount() const
306 {
307     return (unsigned int)[m_cocoaItems count];
308 }
309
310 wxString wxListBox::GetString(unsigned int n) const
311 {
312     return wxStringWithNSString([m_cocoaItems objectAtIndex:n]);
313 }
314
315 void wxListBox::SetString(unsigned int n, const wxString& s)
316 {
317     wxAutoNSAutoreleasePool pool;
318     [m_cocoaItems removeObjectAtIndex:n];
319     [m_cocoaItems insertObject: wxNSStringWithWxString(s) atIndex: n];
320     _WxCocoa_SetNeedsUpdate(true);
321 }
322
323 int wxListBox::FindString(const wxString& s, bool bCase) const
324 {
325     // FIXME: use wxItemContainerImmutable::FindString for bCase parameter
326     wxAutoNSAutoreleasePool pool;
327     return [m_cocoaItems indexOfObject:wxNSStringWithWxString(s)];
328 }
329
330     // selection
331 int wxListBox::GetSelection() const
332 {
333     return [GetNSTableView() selectedRow];
334 }
335
336 void wxListBox::DoSetItemClientData(unsigned int n, void* clientData)
337 {
338     m_itemClientData[n] = clientData;
339 }
340
341 void* wxListBox::DoGetItemClientData(unsigned int n) const
342 {
343     return m_itemClientData[n];
344 }
345
346 #endif // wxUSE_LISTBOX