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