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