]> git.saurik.com Git - wxWidgets.git/blob - src/motif/listbox.cpp
fix for bug 1371386, with some minor mods and cleanup
[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 #ifdef __VMS
16 #define XtParent XTPARENT
17 #define XtDisplay XTDISPLAY
18 #endif
19
20 # include "wx/listbox.h"
21 #include "wx/settings.h"
22 #include "wx/dynarray.h"
23 #include "wx/log.h"
24 #include "wx/utils.h"
25 #include "wx/arrstr.h"
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, wxControl)
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 wxWindow* m_w;
51 public:
52 wxSizeKeeper( wxWindow* w )
53 : m_w( w )
54 {
55 m_w->GetSize( &m_x, &m_y );
56 }
57
58 void Restore()
59 {
60 int x, y;
61
62 m_w->GetSize( &x, &y );
63 if( x != m_x || y != m_y )
64 m_w->SetSize( -1, -1, m_x, m_y );
65 }
66 };
67
68 // ============================================================================
69 // list box control implementation
70 // ============================================================================
71
72 // Listbox item
73 wxListBox::wxListBox()
74 {
75 m_noItems = 0;
76 }
77
78 bool wxListBox::Create(wxWindow *parent, wxWindowID id,
79 const wxPoint& pos,
80 const wxSize& size,
81 int n, const wxString choices[],
82 long style,
83 const wxValidator& validator,
84 const wxString& name)
85 {
86 if( !wxControl::CreateControl( parent, id, pos, size, style,
87 validator, name ) )
88 return false;
89
90 m_noItems = n;
91 m_backgroundColour = * wxWHITE;
92
93 Widget parentWidget = (Widget) parent->GetClientWidget();
94 Display* dpy = XtDisplay(parentWidget);
95
96 Arg args[4];
97 int count = 0;
98 XtSetArg( args[count], XmNlistSizePolicy, XmCONSTANT ); ++count;
99 XtSetArg( args[count], XmNselectionPolicy,
100 ( m_windowStyle & wxLB_MULTIPLE ) ? XmMULTIPLE_SELECT :
101 ( m_windowStyle & wxLB_EXTENDED ) ? XmEXTENDED_SELECT :
102 XmBROWSE_SELECT );
103 ++count;
104 if( m_font.Ok() )
105 {
106 XtSetArg( args[count],
107 (String)wxFont::GetFontTag(), m_font.GetFontTypeC(dpy) );
108 ++count;
109 }
110 if( m_windowStyle & wxLB_ALWAYS_SB )
111 {
112 XtSetArg( args[count], XmNscrollBarDisplayPolicy, XmSTATIC );
113 ++count;
114 }
115
116 Widget listWidget =
117 XmCreateScrolledList(parentWidget,
118 wxConstCast(name.c_str(), char), args, count);
119
120 m_mainWidget = (WXWidget) listWidget;
121
122 Set(n, choices);
123
124 XtManageChild (listWidget);
125
126 wxSize best = GetBestSize();
127 if( size.x != -1 ) best.x = size.x;
128 if( size.y != -1 ) best.y = size.y;
129
130 XtAddCallback (listWidget,
131 XmNbrowseSelectionCallback,
132 (XtCallbackProc) wxListBoxCallback,
133 (XtPointer) this);
134 XtAddCallback (listWidget,
135 XmNextendedSelectionCallback,
136 (XtCallbackProc) wxListBoxCallback,
137 (XtPointer) this);
138 XtAddCallback (listWidget,
139 XmNmultipleSelectionCallback,
140 (XtCallbackProc) wxListBoxCallback,
141 (XtPointer) this);
142 XtAddCallback (listWidget,
143 XmNdefaultActionCallback,
144 (XtCallbackProc) wxListBoxCallback,
145 (XtPointer) this);
146
147 AttachWidget (parent, m_mainWidget, (WXWidget) NULL,
148 pos.x, pos.y, best.x, best.y);
149
150 ChangeBackgroundColour();
151
152 return true;
153 }
154
155 bool wxListBox::Create(wxWindow *parent, wxWindowID id,
156 const wxPoint& pos,
157 const wxSize& size,
158 const wxArrayString& choices,
159 long style,
160 const wxValidator& validator,
161 const wxString& name)
162 {
163 wxCArrayString chs(choices);
164 return Create(parent, id, pos, size, chs.GetCount(), chs.GetStrings(),
165 style, validator, name);
166 }
167
168 wxListBox::~wxListBox()
169 {
170 if( HasClientObjectData() )
171 m_clientDataDict.DestroyData();
172 }
173
174 void wxListBox::SetSelectionPolicy()
175 {
176 Widget listBox = (Widget)m_mainWidget;
177 Arg args[3];
178
179 XtSetArg( args[0], XmNlistSizePolicy, XmCONSTANT );
180
181 XtSetArg( args[1], XmNselectionPolicy,
182 ( m_windowStyle & wxLB_MULTIPLE ) ? XmMULTIPLE_SELECT :
183 ( m_windowStyle & wxLB_EXTENDED ) ? XmEXTENDED_SELECT :
184 XmBROWSE_SELECT );
185
186 XtSetValues( listBox, args, 2 );
187 }
188
189 void wxListBox::DoSetFirstItem( int N )
190 {
191 int count, length;
192
193 if (N < 0)
194 return;
195 XtVaGetValues ((Widget) m_mainWidget,
196 XmNvisibleItemCount, &count,
197 XmNitemCount, &length,
198 NULL);
199 if ((N + count) >= length)
200 N = length - count;
201 XmListSetPos ((Widget) m_mainWidget, N + 1);
202 }
203
204 void wxListBox::Delete(int N)
205 {
206 wxSizeKeeper sk( this );
207 Widget listBox = (Widget) m_mainWidget;
208
209 bool managed = XtIsManaged(listBox);
210
211 if (managed)
212 XtUnmanageChild (listBox);
213
214 XmListDeletePos (listBox, N + 1);
215
216 if (managed)
217 XtManageChild (listBox);
218
219 sk.Restore();
220 m_clientDataDict.Delete(N, HasClientObjectData());
221 m_noItems --;
222 }
223
224 int wxListBox::DoAppend(const wxString& item)
225 {
226 wxSizeKeeper sk( this );
227 Widget listBox = (Widget) m_mainWidget;
228
229 bool managed = XtIsManaged(listBox);
230
231 if (managed)
232 XtUnmanageChild (listBox);
233 int n;
234 XtVaGetValues (listBox, XmNitemCount, &n, NULL);
235 wxXmString text( item );
236 // XmListAddItem(listBox, text, n + 1);
237 XmListAddItemUnselected (listBox, text(), 0);
238
239 // It seems that if the list is cleared, we must re-ask for
240 // selection policy!!
241 SetSelectionPolicy();
242
243 if (managed)
244 XtManageChild (listBox);
245
246 sk.Restore();
247 m_noItems ++;
248
249 return GetCount() - 1;
250 }
251
252 void wxListBox::DoSetItems(const wxArrayString& items, void** clientData)
253 {
254 wxSizeKeeper sk( this );
255 Widget listBox = (Widget) m_mainWidget;
256
257 if( HasClientObjectData() )
258 m_clientDataDict.DestroyData();
259
260 bool managed = XtIsManaged(listBox);
261
262 if (managed)
263 XtUnmanageChild (listBox);
264 XmString *text = new XmString[items.GetCount()];
265 size_t i;
266 for (i = 0; i < items.GetCount(); ++i)
267 text[i] = wxStringToXmString (items[i]);
268
269 if ( clientData )
270 for (i = 0; i < items.GetCount(); ++i)
271 m_clientDataDict.Set(i, (wxClientData*)clientData[i], false);
272
273 XmListAddItems (listBox, text, items.GetCount(), 0);
274 for (i = 0; i < items.GetCount(); i++)
275 XmStringFree (text[i]);
276 delete[] text;
277
278 // It seems that if the list is cleared, we must re-ask for
279 // selection policy!!
280 SetSelectionPolicy();
281
282 if (managed)
283 XtManageChild (listBox);
284
285 sk.Restore();
286
287 m_noItems = items.GetCount();
288 }
289
290 int wxDoFindStringInList(Widget w, const wxString& s)
291 {
292 wxXmString str( s );
293 int *positions = NULL;
294 int no_positions = 0;
295 bool success = XmListGetMatchPos (w, str(),
296 &positions, &no_positions);
297
298 if (success)
299 {
300 int pos = positions[0];
301 if (positions)
302 XtFree ((char *) positions);
303 return pos - 1;
304 }
305 else
306 return -1;
307 }
308
309 int wxListBox::FindString(const wxString& s, bool WXUNUSED(bCase)) const
310 {
311 // FIXME: back to base class for not supported value of bCase
312
313 return wxDoFindStringInList( (Widget)m_mainWidget, s );
314 }
315
316 void wxListBox::Clear()
317 {
318 if (m_noItems <= 0)
319 return;
320
321 wxSizeKeeper sk( this );
322 Widget listBox = (Widget) m_mainWidget;
323
324 XmListDeleteAllItems (listBox);
325 if( HasClientObjectData() )
326 m_clientDataDict.DestroyData();
327
328 sk.Restore();
329
330 m_noItems = 0;
331 }
332
333 void wxListBox::DoSetSelection(int N, bool select)
334 {
335 m_inSetValue = true;
336 if (select)
337 {
338 #if 0
339 if (m_windowStyle & wxLB_MULTIPLE)
340 {
341 int *selections = NULL;
342 int n = GetSelections (&selections);
343
344 // This hack is supposed to work, to make it possible
345 // to select more than one item, but it DOESN'T under Motif 1.1.
346
347 XtVaSetValues ((Widget) m_mainWidget,
348 XmNselectionPolicy, XmMULTIPLE_SELECT,
349 NULL);
350
351 int i;
352 for (i = 0; i < n; i++)
353 XmListSelectPos ((Widget) m_mainWidget,
354 selections[i] + 1, False);
355
356 XmListSelectPos ((Widget) m_mainWidget, N + 1, False);
357
358 XtVaSetValues ((Widget) m_mainWidget,
359 XmNselectionPolicy, XmEXTENDED_SELECT,
360 NULL);
361 }
362 else
363 #endif // 0
364 XmListSelectPos ((Widget) m_mainWidget, N + 1, False);
365
366 }
367 else
368 XmListDeselectPos ((Widget) m_mainWidget, N + 1);
369
370 m_inSetValue = false;
371 }
372
373 bool wxListBox::IsSelected(int N) const
374 {
375 // In Motif, no simple way to determine if the item is selected.
376 wxArrayInt theSelections;
377 int count = GetSelections (theSelections);
378 if (count == 0)
379 return false;
380 else
381 {
382 int j;
383 for (j = 0; j < count; j++)
384 if (theSelections[j] == N)
385 return true;
386 }
387 return false;
388 }
389
390 void wxListBox::DoSetItemClientObject(int n, wxClientData* clientData)
391 {
392 m_clientDataDict.Set(n, clientData, false);
393 }
394
395 wxClientData* wxListBox::DoGetItemClientObject(int n) const
396 {
397 return m_clientDataDict.Get(n);
398 }
399
400 void *wxListBox::DoGetItemClientData(int N) const
401 {
402 return (void*)m_clientDataDict.Get(N);
403 }
404
405 void wxListBox::DoSetItemClientData(int N, void *Client_data)
406 {
407 m_clientDataDict.Set(N, (wxClientData*)Client_data, false);
408 }
409
410 // Return number of selections and an array of selected integers
411 int wxListBox::GetSelections(wxArrayInt& aSelections) const
412 {
413 aSelections.Empty();
414
415 Widget listBox = (Widget) m_mainWidget;
416 int *posList = NULL;
417 int posCnt = 0;
418 bool flag = XmListGetSelectedPos (listBox, &posList, &posCnt);
419 if (flag)
420 {
421 if (posCnt > 0)
422 {
423 aSelections.Alloc(posCnt);
424
425 int i;
426 for (i = 0; i < posCnt; i++)
427 aSelections.Add(posList[i] - 1);
428
429 XtFree ((char *) posList);
430 return posCnt;
431 }
432 else
433 return 0;
434 }
435 else
436 return 0;
437 }
438
439 // Get single selection, for single choice list items
440 int wxDoGetSelectionInList(Widget listBox)
441 {
442 int *posList = NULL;
443 int posCnt = 0;
444 bool flag = XmListGetSelectedPos (listBox, &posList, &posCnt);
445 if (flag)
446 {
447 int id = -1;
448 if (posCnt > 0)
449 id = posList[0] - 1;
450 XtFree ((char *) posList);
451 return id;
452 }
453 else
454 return -1;
455 }
456
457 int wxListBox::GetSelection() const
458 {
459 return wxDoGetSelectionInList((Widget) m_mainWidget);
460 }
461
462 // Find string for position
463 wxString wxDoGetStringInList( Widget listBox, int n )
464 {
465 XmString *strlist;
466 int count;
467 XtVaGetValues( listBox,
468 XmNitemCount, &count,
469 XmNitems, &strlist,
470 NULL );
471 if( n < count && n >= 0 )
472 return wxXmStringToString( strlist[n] );
473 else
474 return wxEmptyString;
475 }
476
477 wxString wxListBox::GetString( int n ) const
478 {
479 return wxDoGetStringInList( (Widget)m_mainWidget, n );
480 }
481
482 void wxListBox::DoInsertItems(const wxArrayString& items, int pos)
483 {
484 wxSizeKeeper sk( this );
485 Widget listBox = (Widget) m_mainWidget;
486
487 bool managed = XtIsManaged(listBox);
488
489 if (managed)
490 XtUnmanageChild(listBox);
491
492 XmString *text = new XmString[items.GetCount()];
493 size_t i;
494 // Steve Hammes: Motif 1.1 compatibility
495 // #if XmVersion > 1100
496 // Corrected by Sergey Krasnov from Steve Hammes' code
497 #if XmVersion > 1001
498 for (i = 0; i < items.GetCount(); i++)
499 text[i] = wxStringToXmString(items[i]);
500 XmListAddItemsUnselected(listBox, text, items.GetCount(), pos+1);
501 #else
502 for (i = 0; i < items.GetCount(); i++)
503 {
504 text[i] = wxStringToXmString(items[i]);
505 // Another Sergey correction
506 XmListAddItemUnselected(listBox, text[i], pos+i+1);
507 }
508 #endif
509 for (i = 0; i < items.GetCount(); i++)
510 XmStringFree(text[i]);
511 delete[] text;
512
513 // It seems that if the list is cleared, we must re-ask for
514 // selection policy!!
515 SetSelectionPolicy();
516
517 if (managed)
518 XtManageChild(listBox);
519
520 sk.Restore();
521
522 m_noItems += items.GetCount();
523 }
524
525 void wxListBox::SetString(int N, const wxString& s)
526 {
527 wxSizeKeeper sk( this );
528 Widget listBox = (Widget) m_mainWidget;
529
530 wxXmString text( s );
531
532 // delete the item and add it again.
533 // FIXME isn't there a way to change it in place?
534 XmListDeletePos (listBox, N+1);
535 XmListAddItem (listBox, text(), N+1);
536
537 sk.Restore();
538 }
539
540 void wxListBox::Command (wxCommandEvent & event)
541 {
542 if (event.GetExtraLong())
543 SetSelection (event.GetInt());
544 else
545 {
546 Deselect (event.GetInt());
547 return;
548 }
549 ProcessCommand (event);
550 }
551
552 void wxListBoxCallback (Widget WXUNUSED(w), XtPointer clientData,
553 XmListCallbackStruct * cbs)
554 {
555 wxListBox *item = (wxListBox *) clientData;
556
557 if (item->InSetValue())
558 return;
559
560 wxEventType evtType;
561
562 if( cbs->reason == XmCR_DEFAULT_ACTION )
563 evtType = wxEVT_COMMAND_LISTBOX_DOUBLECLICKED;
564 else
565 evtType = wxEVT_COMMAND_LISTBOX_SELECTED;
566
567 int n = cbs->item_position - 1;
568 wxCommandEvent event (evtType, item->GetId());
569 if ( item->HasClientObjectData() )
570 event.SetClientObject( item->GetClientObject(n) );
571 else if ( item->HasClientUntypedData() )
572 event.SetClientData( item->GetClientData(n) );
573 event.SetInt(n);
574 event.SetExtraLong(true);
575 event.SetEventObject(item);
576 event.SetString( item->GetString( n ) );
577
578 int x = -1;
579 if( NULL != cbs->event && cbs->event->type == ButtonRelease )
580 {
581 XButtonEvent* evt = (XButtonEvent*)cbs->event;
582
583 x = evt->x;
584 }
585
586 switch (cbs->reason)
587 {
588 case XmCR_MULTIPLE_SELECT:
589 case XmCR_BROWSE_SELECT:
590 #if wxUSE_CHECKLISTBOX
591 item->DoToggleItem( n, x );
592 #endif
593 case XmCR_DEFAULT_ACTION:
594 item->GetEventHandler()->ProcessEvent(event);
595 break;
596 case XmCR_EXTENDED_SELECT:
597 switch (cbs->selection_type)
598 {
599 case XmINITIAL:
600 case XmADDITION:
601 case XmMODIFICATION:
602 item->DoToggleItem( n, x );
603 item->GetEventHandler()->ProcessEvent(event);
604 break;
605 }
606 break;
607 }
608 }
609
610 WXWidget wxListBox::GetTopWidget() const
611 {
612 return (WXWidget) XtParent( (Widget) m_mainWidget );
613 }
614
615 void wxListBox::ChangeBackgroundColour()
616 {
617 wxWindow::ChangeBackgroundColour();
618
619 Widget parent = XtParent ((Widget) m_mainWidget);
620 Widget hsb, vsb;
621
622 XtVaGetValues (parent,
623 XmNhorizontalScrollBar, &hsb,
624 XmNverticalScrollBar, &vsb,
625 NULL);
626
627 /* TODO: should scrollbars be affected? Should probably have separate
628 * function to change them (by default, taken from wxSystemSettings)
629 */
630 wxColour backgroundColour = wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE);
631 wxDoChangeBackgroundColour((WXWidget) hsb, backgroundColour, true);
632 wxDoChangeBackgroundColour((WXWidget) vsb, backgroundColour, true);
633
634 XtVaSetValues (hsb,
635 XmNtroughColor, backgroundColour.AllocColour(XtDisplay(hsb)),
636 NULL);
637 XtVaSetValues (vsb,
638 XmNtroughColor, backgroundColour.AllocColour(XtDisplay(vsb)),
639 NULL);
640
641 // MBN: why change parent's background? It looks really ugly.
642 // wxDoChangeBackgroundColour((WXWidget) parent, m_backgroundColour, true);
643 }
644
645 void wxListBox::ChangeForegroundColour()
646 {
647 wxWindow::ChangeForegroundColour();
648
649 Widget parent = XtParent ((Widget) m_mainWidget);
650 Widget hsb, vsb;
651
652 XtVaGetValues(parent,
653 XmNhorizontalScrollBar, &hsb,
654 XmNverticalScrollBar, &vsb,
655 NULL);
656
657 /* TODO: should scrollbars be affected? Should probably have separate
658 function to change them (by default, taken from wxSystemSettings)
659
660 wxDoChangeForegroundColour((WXWidget) hsb, m_foregroundColour);
661 wxDoChangeForegroundColour((WXWidget) vsb, m_foregroundColour);
662 wxDoChangeForegroundColour((WXWidget) parent, m_foregroundColour);
663 */
664 }
665
666 int wxListBox::GetCount() const
667 {
668 return m_noItems;
669 }
670
671 #define LIST_SCROLL_SPACING 6
672
673 wxSize wxDoGetListBoxBestSize( Widget listWidget, const wxWindow* window )
674 {
675 int max;
676 Dimension spacing, highlight, xmargin, ymargin, shadow;
677 int width = 0;
678 int x, y;
679
680 XtVaGetValues( listWidget,
681 XmNitemCount, &max,
682 XmNlistSpacing, &spacing,
683 XmNhighlightThickness, &highlight,
684 XmNlistMarginWidth, &xmargin,
685 XmNlistMarginHeight, &ymargin,
686 XmNshadowThickness, &shadow,
687 NULL );
688
689 for( size_t i = 0; i < (size_t)max; ++i )
690 {
691 window->GetTextExtent( wxDoGetStringInList( listWidget, i ), &x, &y );
692 width = wxMax( width, x );
693 }
694
695 // use some arbitrary value if there are no strings
696 if( width == 0 )
697 width = 100;
698
699 // get my
700 window->GetTextExtent( "v", &x, &y );
701
702 // make it a little larger than widest string, plus the scrollbar
703 width += wxSystemSettings::GetMetric( wxSYS_VSCROLL_X )
704 + 2 * highlight + LIST_SCROLL_SPACING + 2 * xmargin + 2 * shadow;
705
706 // at least 3 items, at most 10
707 int height = wxMax( 3, wxMin( 10, max ) ) *
708 ( y + spacing + 2 * highlight ) + 2 * ymargin + 2 * shadow;
709
710 return wxSize( width, height );
711 }
712
713 wxSize wxListBox::DoGetBestSize() const
714 {
715 return wxDoGetListBoxBestSize( (Widget)m_mainWidget, this );
716 }