Better fix
[wxWidgets.git] / src / motif / listbox.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/motif/listbox.cpp
3 // Purpose: wxListBox
4 // Author: Julian Smart
5 // Modified by:
6 // Created: 17/09/98
7 // RCS-ID: $Id$
8 // Copyright: (c) Julian Smart
9 // Licence: wxWindows licence
10 ///////////////////////////////////////////////////////////////////////////////
11
12 // For compilers that support precompilation, includes "wx.h".
13 #include "wx/wxprec.h"
14
15 #if wxUSE_LISTBOX
16
17 #include "wx/listbox.h"
18
19 #ifndef WX_PRECOMP
20 #include "wx/dynarray.h"
21 #include "wx/log.h"
22 #include "wx/utils.h"
23 #include "wx/settings.h"
24 #include "wx/arrstr.h"
25 #endif
26
27 #ifdef __VMS__
28 #pragma message disable nosimpint
29 #endif
30 #include <Xm/List.h>
31 #ifdef __VMS__
32 #pragma message enable nosimpint
33 #endif
34 #include "wx/motif/private.h"
35
36 IMPLEMENT_DYNAMIC_CLASS(wxListBox, wxControlWithItems)
37
38 static void wxListBoxCallback(Widget w,
39 XtPointer clientData,
40 XmListCallbackStruct * cbs);
41
42 // ----------------------------------------------------------------------------
43 // wxSizeKeeper
44 // ----------------------------------------------------------------------------
45
46 // helper class to reduce code duplication
47 class wxSizeKeeper
48 {
49 int m_x, m_y;
50 int m_w, m_h;
51 wxWindow* m_wnd;
52 public:
53 wxSizeKeeper( wxWindow* w )
54 : m_wnd( w )
55 {
56 m_wnd->GetSize( &m_w, &m_h );
57 m_wnd->GetPosition( &m_x, &m_y );
58 }
59
60 void Restore()
61 {
62 int x, y;
63
64 m_wnd->GetSize( &x, &y );
65 if( x != m_x || y != m_y )
66 m_wnd->SetSize( m_x, m_y, m_w, m_h );
67 }
68 };
69
70 // ============================================================================
71 // list box control implementation
72 // ============================================================================
73
74 // Listbox item
75 wxListBox::wxListBox()
76 {
77 m_noItems = 0;
78 }
79
80 bool wxListBox::Create(wxWindow *parent, wxWindowID id,
81 const wxPoint& pos,
82 const wxSize& size,
83 int n, const wxString choices[],
84 long style,
85 const wxValidator& validator,
86 const wxString& name)
87 {
88 if( !wxControl::CreateControl( parent, id, pos, size, style,
89 validator, name ) )
90 return false;
91 PreCreation();
92
93 m_noItems = (unsigned int)n;
94
95 Widget parentWidget = (Widget) parent->GetClientWidget();
96 Display* dpy = XtDisplay(parentWidget);
97
98 Arg args[4];
99 int count = 0;
100 XtSetArg( args[count], XmNlistSizePolicy, XmCONSTANT ); ++count;
101 XtSetArg( args[count], XmNselectionPolicy,
102 ( m_windowStyle & wxLB_MULTIPLE ) ? XmMULTIPLE_SELECT :
103 ( m_windowStyle & wxLB_EXTENDED ) ? XmEXTENDED_SELECT :
104 XmBROWSE_SELECT );
105 ++count;
106 if( m_font.Ok() )
107 {
108 XtSetArg( args[count],
109 (String)wxFont::GetFontTag(), m_font.GetFontTypeC(dpy) );
110 ++count;
111 }
112 if( m_windowStyle & wxLB_ALWAYS_SB )
113 {
114 XtSetArg( args[count], XmNscrollBarDisplayPolicy, XmSTATIC );
115 ++count;
116 }
117
118 Widget listWidget =
119 XmCreateScrolledList(parentWidget,
120 name.char_str(), args, count);
121
122 m_mainWidget = (WXWidget) listWidget;
123
124 Set(n, choices);
125
126 XtManageChild (listWidget);
127
128 wxSize best = GetBestSize();
129 if( size.x != -1 ) best.x = size.x;
130 if( size.y != -1 ) best.y = size.y;
131
132 XtAddCallback (listWidget,
133 XmNbrowseSelectionCallback,
134 (XtCallbackProc) wxListBoxCallback,
135 (XtPointer) this);
136 XtAddCallback (listWidget,
137 XmNextendedSelectionCallback,
138 (XtCallbackProc) wxListBoxCallback,
139 (XtPointer) this);
140 XtAddCallback (listWidget,
141 XmNmultipleSelectionCallback,
142 (XtCallbackProc) wxListBoxCallback,
143 (XtPointer) this);
144 XtAddCallback (listWidget,
145 XmNdefaultActionCallback,
146 (XtCallbackProc) wxListBoxCallback,
147 (XtPointer) this);
148
149 PostCreation();
150 AttachWidget (parent, m_mainWidget, (WXWidget) NULL,
151 pos.x, pos.y, best.x, best.y);
152
153 return true;
154 }
155
156 bool wxListBox::Create(wxWindow *parent, wxWindowID id,
157 const wxPoint& pos,
158 const wxSize& size,
159 const wxArrayString& choices,
160 long style,
161 const wxValidator& validator,
162 const wxString& name)
163 {
164 wxCArrayString chs(choices);
165 return Create(parent, id, pos, size, chs.GetCount(), chs.GetStrings(),
166 style, validator, name);
167 }
168
169 void wxListBox::SetSelectionPolicy()
170 {
171 Widget listBox = (Widget)m_mainWidget;
172 Arg args[3];
173
174 XtSetArg( args[0], XmNlistSizePolicy, XmCONSTANT );
175
176 XtSetArg( args[1], XmNselectionPolicy,
177 ( m_windowStyle & wxLB_MULTIPLE ) ? XmMULTIPLE_SELECT :
178 ( m_windowStyle & wxLB_EXTENDED ) ? XmEXTENDED_SELECT :
179 XmBROWSE_SELECT );
180
181 XtSetValues( listBox, args, 2 );
182 }
183
184 void wxListBox::DoSetFirstItem( int N )
185 {
186 int count, length;
187
188 if (!IsValid(N))
189 return;
190
191 XtVaGetValues ((Widget) m_mainWidget,
192 XmNvisibleItemCount, &count,
193 XmNitemCount, &length,
194 NULL);
195 if ((N + count) >= length)
196 N = length - count;
197 XmListSetPos ((Widget) m_mainWidget, N + 1);
198 }
199
200 void wxListBox::DoDeleteOneItem(unsigned int n)
201 {
202 Widget listBox = (Widget) m_mainWidget;
203
204 XmListDeletePos (listBox, n + 1);
205
206 wxListBoxBase::DoDeleteOneItem(n);
207 m_noItems --;
208 }
209
210 int wxDoFindStringInList(Widget w, const wxString& s)
211 {
212 wxXmString str( s );
213 int *positions = NULL;
214 int no_positions = 0;
215 bool success = XmListGetMatchPos (w, str(),
216 &positions, &no_positions);
217
218 if (success && positions)
219 {
220 int pos = positions[0];
221 XtFree ((char *) positions);
222 return pos - 1;
223 }
224 else
225 return -1;
226 }
227
228 int wxListBox::FindString(const wxString& s, bool WXUNUSED(bCase)) const
229 {
230 // FIXME: back to base class for not supported value of bCase
231
232 return wxDoFindStringInList( (Widget)m_mainWidget, s );
233 }
234
235 void wxListBox::DoClear()
236 {
237 if (!m_noItems)
238 return;
239
240 wxSizeKeeper sk( this );
241 Widget listBox = (Widget) m_mainWidget;
242
243 XmListDeleteAllItems (listBox);
244
245 sk.Restore();
246
247 wxListBoxBase::DoClear();
248 m_noItems = 0;
249 }
250
251 void wxListBox::DoSetSelection(int N, bool select)
252 {
253 m_inSetValue = true;
254 if (select)
255 {
256 #if 0
257 if (m_windowStyle & wxLB_MULTIPLE)
258 {
259 int *selections = NULL;
260 int n = GetSelections (&selections);
261
262 // This hack is supposed to work, to make it possible
263 // to select more than one item, but it DOESN'T under Motif 1.1.
264
265 XtVaSetValues ((Widget) m_mainWidget,
266 XmNselectionPolicy, XmMULTIPLE_SELECT,
267 NULL);
268
269 int i;
270 for (i = 0; i < n; i++)
271 XmListSelectPos ((Widget) m_mainWidget,
272 selections[i] + 1, False);
273
274 XmListSelectPos ((Widget) m_mainWidget, N + 1, False);
275
276 XtVaSetValues ((Widget) m_mainWidget,
277 XmNselectionPolicy, XmEXTENDED_SELECT,
278 NULL);
279 }
280 else
281 #endif // 0
282 XmListSelectPos ((Widget) m_mainWidget, N + 1, False);
283
284 }
285 else
286 XmListDeselectPos ((Widget) m_mainWidget, N + 1);
287
288 m_inSetValue = false;
289 }
290
291 bool wxListBox::IsSelected(int N) const
292 {
293 // In Motif, no simple way to determine if the item is selected.
294 wxArrayInt theSelections;
295 int count = GetSelections (theSelections);
296 if (count == 0)
297 return false;
298 else
299 {
300 int j;
301 for (j = 0; j < count; j++)
302 if (theSelections[j] == N)
303 return true;
304 }
305 return false;
306 }
307
308 // Return number of selections and an array of selected integers
309 int wxListBox::GetSelections(wxArrayInt& aSelections) const
310 {
311 aSelections.Empty();
312
313 Widget listBox = (Widget) m_mainWidget;
314 int *posList = NULL;
315 int posCnt = 0;
316 bool flag = XmListGetSelectedPos (listBox, &posList, &posCnt);
317 if (flag)
318 {
319 if (posCnt > 0)
320 {
321 aSelections.Alloc(posCnt);
322
323 int i;
324 for (i = 0; i < posCnt; i++)
325 aSelections.Add(posList[i] - 1);
326
327 XtFree ((char *) posList);
328 return posCnt;
329 }
330 else
331 return 0;
332 }
333 else
334 return 0;
335 }
336
337 // Get single selection, for single choice list items
338 int wxDoGetSelectionInList(Widget listBox)
339 {
340 int *posList = NULL;
341 int posCnt = 0;
342 bool flag = XmListGetSelectedPos (listBox, &posList, &posCnt);
343 if (flag)
344 {
345 int id = -1;
346 if (posCnt > 0)
347 id = posList[0] - 1;
348 XtFree ((char *) posList);
349 return id;
350 }
351 else
352 return -1;
353 }
354
355 int wxListBox::GetSelection() const
356 {
357 return wxDoGetSelectionInList((Widget) m_mainWidget);
358 }
359
360 // Find string for position
361 wxString wxDoGetStringInList( Widget listBox, int n )
362 {
363 XmString *strlist;
364 int count;
365 XtVaGetValues( listBox,
366 XmNitemCount, &count,
367 XmNitems, &strlist,
368 NULL );
369 if( n < count && n >= 0 )
370 return wxXmStringToString( strlist[n] );
371 else
372 return wxEmptyString;
373 }
374
375 wxString wxListBox::GetString(unsigned int n) const
376 {
377 return wxDoGetStringInList( (Widget)m_mainWidget, n );
378 }
379
380 int wxListBox::DoInsertItems(const wxArrayStringsAdapter & items,
381 unsigned int pos,
382 void **clientData, wxClientDataType type)
383 {
384 Widget listBox = (Widget) m_mainWidget;
385
386 const unsigned int numItems = items.GetCount();
387
388 XmString *text = new XmString[numItems];
389 unsigned int i;
390 #if XmVersion > 1001
391 for (i = 0; i < numItems; i++)
392 {
393 text[i] = wxStringToXmString(items[i]);
394 }
395 XmListAddItemsUnselected(listBox, text, numItems, GetMotifPosition(pos));
396 InsertNewItemsClientData(pos, numItems, clientData, type);
397 #else
398 AllocClientData(numItems);
399
400 unsigned int idx = pos;
401 for ( i = 0; i < numItems; i++, idx++ )
402 {
403 text[i] = wxStringToXmString(items[i]);
404 XmListAddItemUnselected(listBox, text[i], GetMotifPosition(idx));
405 InsertNewItemClientData(idx, clientData, i, type);
406 }
407 #endif
408 for (i = 0; i < numItems; i++)
409 XmStringFree(text[i]);
410 delete[] text;
411
412 m_noItems += numItems;
413
414 SetSelectionPolicy();
415
416 return pos + numItems - 1;
417 }
418
419 void wxListBox::SetString(unsigned int n, const wxString& s)
420 {
421 wxSizeKeeper sk( this );
422 Widget listBox = (Widget) m_mainWidget;
423
424 wxXmString text( s );
425
426 // delete the item and add it again.
427 // FIXME isn't there a way to change it in place?
428 XmListDeletePos (listBox, n+1);
429 XmListAddItem (listBox, text(), n+1);
430
431 sk.Restore();
432 }
433
434 void wxListBox::Command (wxCommandEvent & event)
435 {
436 if (event.GetExtraLong())
437 SetSelection (event.GetInt());
438 else
439 {
440 Deselect (event.GetInt());
441 return;
442 }
443 ProcessCommand (event);
444 }
445
446 void wxListBoxCallback (Widget WXUNUSED(w), XtPointer clientData,
447 XmListCallbackStruct * cbs)
448 {
449 wxListBox *item = (wxListBox *) clientData;
450
451 if (item->InSetValue())
452 return;
453
454 wxEventType evtType;
455
456 if( cbs->reason == XmCR_DEFAULT_ACTION )
457 evtType = wxEVT_COMMAND_LISTBOX_DOUBLECLICKED;
458 else
459 evtType = wxEVT_COMMAND_LISTBOX_SELECTED;
460
461 int n = cbs->item_position - 1;
462 wxCommandEvent event (evtType, item->GetId());
463 if ( item->HasClientObjectData() )
464 event.SetClientObject( item->GetClientObject(n) );
465 else if ( item->HasClientUntypedData() )
466 event.SetClientData( item->GetClientData(n) );
467 event.SetInt(n);
468 event.SetExtraLong(true);
469 event.SetEventObject(item);
470 event.SetString( item->GetString( n ) );
471
472 int x = -1;
473 if( NULL != cbs->event && cbs->event->type == ButtonRelease )
474 {
475 XButtonEvent* evt = (XButtonEvent*)cbs->event;
476
477 x = evt->x;
478 }
479
480 switch (cbs->reason)
481 {
482 case XmCR_MULTIPLE_SELECT:
483 case XmCR_BROWSE_SELECT:
484 #if wxUSE_CHECKLISTBOX
485 item->DoToggleItem( n, x );
486 #endif
487 case XmCR_DEFAULT_ACTION:
488 item->HandleWindowEvent(event);
489 break;
490 case XmCR_EXTENDED_SELECT:
491 switch (cbs->selection_type)
492 {
493 case XmINITIAL:
494 case XmADDITION:
495 case XmMODIFICATION:
496 item->DoToggleItem( n, x );
497 item->HandleWindowEvent(event);
498 break;
499 }
500 break;
501 }
502 }
503
504 WXWidget wxListBox::GetTopWidget() const
505 {
506 return (WXWidget) XtParent( (Widget) m_mainWidget );
507 }
508
509 void wxListBox::ChangeBackgroundColour()
510 {
511 wxWindow::ChangeBackgroundColour();
512
513 Widget parent = XtParent ((Widget) m_mainWidget);
514 Widget hsb, vsb;
515
516 XtVaGetValues (parent,
517 XmNhorizontalScrollBar, &hsb,
518 XmNverticalScrollBar, &vsb,
519 NULL);
520
521 /* TODO: should scrollbars be affected? Should probably have separate
522 * function to change them (by default, taken from wxSystemSettings)
523 */
524 wxColour backgroundColour = wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE);
525 wxDoChangeBackgroundColour((WXWidget) hsb, backgroundColour, true);
526 wxDoChangeBackgroundColour((WXWidget) vsb, backgroundColour, true);
527
528 XtVaSetValues (hsb,
529 XmNtroughColor, backgroundColour.AllocColour(XtDisplay(hsb)),
530 NULL);
531 XtVaSetValues (vsb,
532 XmNtroughColor, backgroundColour.AllocColour(XtDisplay(vsb)),
533 NULL);
534
535 // MBN: why change parent's background? It looks really ugly.
536 // wxDoChangeBackgroundColour((WXWidget) parent, m_backgroundColour, true);
537 }
538
539 void wxListBox::ChangeForegroundColour()
540 {
541 wxWindow::ChangeForegroundColour();
542
543 Widget parent = XtParent ((Widget) m_mainWidget);
544 Widget hsb, vsb;
545
546 XtVaGetValues(parent,
547 XmNhorizontalScrollBar, &hsb,
548 XmNverticalScrollBar, &vsb,
549 NULL);
550
551 /* TODO: should scrollbars be affected? Should probably have separate
552 function to change them (by default, taken from wxSystemSettings)
553
554 wxDoChangeForegroundColour((WXWidget) hsb, m_foregroundColour);
555 wxDoChangeForegroundColour((WXWidget) vsb, m_foregroundColour);
556 wxDoChangeForegroundColour((WXWidget) parent, m_foregroundColour);
557 */
558 }
559
560 unsigned int wxListBox::GetCount() const
561 {
562 return m_noItems;
563 }
564
565 #define LIST_SCROLL_SPACING 6
566
567 wxSize wxDoGetListBoxBestSize( Widget listWidget, const wxWindow* window )
568 {
569 int max;
570 Dimension spacing, highlight, xmargin, ymargin, shadow;
571 int width = 0;
572 int x, y;
573
574 XtVaGetValues( listWidget,
575 XmNitemCount, &max,
576 XmNlistSpacing, &spacing,
577 XmNhighlightThickness, &highlight,
578 XmNlistMarginWidth, &xmargin,
579 XmNlistMarginHeight, &ymargin,
580 XmNshadowThickness, &shadow,
581 NULL );
582
583 for( size_t i = 0; i < (size_t)max; ++i )
584 {
585 window->GetTextExtent( wxDoGetStringInList( listWidget, i ), &x, &y );
586 width = wxMax( width, x );
587 }
588
589 // use some arbitrary value if there are no strings
590 if( width == 0 )
591 width = 100;
592
593 // get my
594 window->GetTextExtent( "v", &x, &y );
595
596 // make it a little larger than widest string, plus the scrollbar
597 width += wxSystemSettings::GetMetric( wxSYS_VSCROLL_X )
598 + 2 * highlight + LIST_SCROLL_SPACING + 2 * xmargin + 2 * shadow;
599
600 // at least 3 items, at most 10
601 int height = wxMax( 3, wxMin( 10, max ) ) *
602 ( y + spacing + 2 * highlight ) + 2 * ymargin + 2 * shadow;
603
604 return wxSize( width, height );
605 }
606
607 wxSize wxListBox::DoGetBestSize() const
608 {
609 return wxDoGetListBoxBestSize( (Widget)m_mainWidget, this );
610 }
611
612 #endif // wxUSE_LISTBOX