1/////////////////////////////////////////////////////////////////////////////
2// Name:        src/motif/choice.cpp
3// Purpose:     wxChoice
4// Author:      Julian Smart
5// Modified by:
6// Created:     17/09/98
7// RCS-ID:      $Id: choice.cpp 50982 2008-01-01 20:38:33Z VZ $
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_CHOICE
16
17#include "wx/choice.h"
18
19#ifndef WX_PRECOMP
20    #include "wx/utils.h"
21    #include "wx/arrstr.h"
22#endif
23
24#ifdef __VMS__
25#pragma message disable nosimpint
26#endif
27#include <Xm/Xm.h>
28#include <Xm/PushBG.h>
29#include <Xm/PushB.h>
30#include <Xm/RowColumn.h>
31#ifdef __VMS__
32#pragma message enable nosimpint
33#endif
34
35#include "wx/motif/private.h"
36
37#define WIDTH_OVERHEAD 48
38#define WIDTH_OVERHEAD_SUBTRACT 40
39#define HEIGHT_OVERHEAD 15
40
41IMPLEMENT_DYNAMIC_CLASS(wxChoice, wxControl)
42
43void wxChoiceCallback (Widget w, XtPointer clientData,
44                       XtPointer ptr);
45
46wxChoice::wxChoice()
47{
48    Init();
49}
50
51void wxChoice::Init()
52{
53    m_noStrings = 0;
54    m_buttonWidget = (WXWidget) 0;
55    m_menuWidget = (WXWidget) 0;
56    m_formWidget = (WXWidget) 0;
57}
58
59bool wxChoice::Create(wxWindow *parent, wxWindowID id,
60                      const wxPoint& pos,
61                      const wxSize& size,
62                      int n, const wxString choices[],
63                      long style,
64                      const wxValidator& validator,
65                      const wxString& name)
66{
67    if ( !CreateControl(parent, id, pos, size, style, validator, name) )
68        return false;
69
70    Widget parentWidget = (Widget) parent->GetClientWidget();
71
72    m_formWidget = (WXWidget) XtVaCreateManagedWidget(name.c_str(),
73        xmRowColumnWidgetClass, parentWidget,
74        XmNmarginHeight, 0,
75        XmNmarginWidth, 0,
76        XmNpacking, XmPACK_TIGHT,
77        XmNorientation, XmHORIZONTAL,
78        XmNresizeWidth, False,
79        XmNresizeHeight, False,
80        NULL);
81
82    XtVaSetValues ((Widget) m_formWidget, XmNspacing, 0, NULL);
83
84    /*
85    * Create the popup menu
86    */
87    m_menuWidget = (WXWidget) XmCreatePulldownMenu ((Widget) m_formWidget,
88                                                    wxMOTIF_STR("choiceMenu"),
89                                                    NULL, 0);
90
91    if (n > 0)
92    {
93        int i;
94        for (i = 0; i < n; i++)
95            Append (choices[i]);
96    }
97
98    /*
99    * Create button
100    */
101    Arg args[10];
102    Cardinal argcnt = 0;
103
104    XtSetArg (args[argcnt], XmNsubMenuId, (Widget) m_menuWidget); ++argcnt;
105    XtSetArg (args[argcnt], XmNmarginWidth, 0); ++argcnt;
106    XtSetArg (args[argcnt], XmNmarginHeight, 0); ++argcnt;
107    XtSetArg (args[argcnt], XmNpacking, XmPACK_TIGHT); ++argcnt;
108    m_buttonWidget = (WXWidget) XmCreateOptionMenu ((Widget) m_formWidget,
109                                                    wxMOTIF_STR("choiceButton"),
110                                                    args, argcnt);
111
112    m_mainWidget = m_buttonWidget;
113
114    XtManageChild ((Widget) m_buttonWidget);
115
116    // New code from Roland Haenel (roland_haenel@ac.cybercity.de)
117    // Some time ago, I reported a problem with wxChoice-items under
118    // Linux and Motif 2.0 (they caused sporadic GPFs). Now it seems
119    // that I have found the code responsible for this behaviour.
120#if XmVersion >= 1002
121#if XmVersion <  2000
122    // JACS, 24/1/99: this seems to cause a malloc crash later on, e.g.
123    // in controls sample.
124    //
125    // Widget optionLabel = XmOptionLabelGadget ((Widget) m_buttonWidget);
126    // XtUnmanageChild (optionLabel);
127#endif
128#endif
129
130    wxSize bestSize = GetBestSize();
131    if( size.x > 0 ) bestSize.x = size.x;
132    if( size.y > 0 ) bestSize.y = size.y;
133
134    XtVaSetValues((Widget) m_formWidget, XmNresizePolicy, XmRESIZE_NONE, NULL);
135
136    ChangeFont(false);
137    ChangeBackgroundColour();
138
139    AttachWidget (parent, m_buttonWidget, m_formWidget,
140                  pos.x, pos.y, bestSize.x, bestSize.y);
141
142    return true;
143}
144
145bool wxChoice::Create(wxWindow *parent, wxWindowID id,
146                      const wxPoint& pos,
147                      const wxSize& size,
148                      const wxArrayString& choices,
149                      long style,
150                      const wxValidator& validator,
151                      const wxString& name)
152{
153    wxCArrayString chs(choices);
154    return Create(parent, id, pos, size, chs.GetCount(), chs.GetStrings(),
155                  style, validator, name);
156}
157
158wxChoice::~wxChoice()
159{
160    // For some reason destroying the menuWidget
161    // can cause crashes on some machines. It will
162    // be deleted implicitly by deleting the parent form
163    // anyway.
164    //  XtDestroyWidget (menuWidget);
165
166    if (GetMainWidget())
167    {
168        DetachWidget(GetMainWidget()); // Removes event handlers
169        DetachWidget(m_formWidget);
170
171        XtDestroyWidget((Widget) m_formWidget);
172        m_formWidget = (WXWidget) 0;
173
174        // Presumably the other widgets have been deleted now, via the form
175        m_mainWidget = (WXWidget) 0;
176        m_buttonWidget = (WXWidget) 0;
177    }
178    if ( HasClientObjectData() )
179        m_clientDataDict.DestroyData();
180}
181
182static inline wxChar* MYcopystring(const wxChar* s)
183{
184    wxChar* copy = new wxChar[wxStrlen(s) + 1];
185    return wxStrcpy(copy, s);
186}
187
188int wxChoice::DoInsert(const wxString& item, unsigned int pos)
189{
190#ifndef XmNpositionIndex
191    wxCHECK_MSG( pos == GetCount(), -1, wxT("insert not implemented"));
192#endif
193    Widget w = XtVaCreateManagedWidget (GetLabelText(item),
194#if wxUSE_GADGETS
195        xmPushButtonGadgetClass, (Widget) m_menuWidget,
196#else
197        xmPushButtonWidgetClass, (Widget) m_menuWidget,
198#endif
199#ifdef XmNpositionIndex
200        XmNpositionIndex, pos,
201#endif
202        NULL);
203
204    wxDoChangeBackgroundColour((WXWidget) w, m_backgroundColour);
205
206    if( m_font.Ok() )
207        wxDoChangeFont( w, m_font );
208
209    m_widgetArray.Insert(w, pos);
210
211    char mnem = wxFindMnemonic (item);
212    if (mnem != 0)
213        XtVaSetValues (w, XmNmnemonic, mnem, NULL);
214
215    XtAddCallback (w, XmNactivateCallback,
216                   (XtCallbackProc) wxChoiceCallback,
217                   (XtPointer) this);
218
219    if (m_noStrings == 0 && m_buttonWidget)
220    {
221        XtVaSetValues ((Widget) m_buttonWidget, XmNmenuHistory, w, NULL);
222        Widget label = XmOptionButtonGadget ((Widget) m_buttonWidget);
223        wxXmString text( item );
224        XtVaSetValues (label,
225            XmNlabelString, text(),
226            NULL);
227    }
228    // need to ditch wxStringList for wxArrayString
229    m_stringList.Insert(pos, MYcopystring(item));
230    m_noStrings ++;
231
232    return pos;
233}
234
235int wxChoice::DoAppend(const wxString& item)
236{
237    return DoInsert(item, GetCount());
238}
239
240void wxChoice::Delete(unsigned int n)
241{
242    Widget w = (Widget)m_widgetArray[n];
243    XtRemoveCallback(w, XmNactivateCallback, (XtCallbackProc)wxChoiceCallback,
244                     (XtPointer)this);
245    m_stringList.Erase(m_stringList.Item(n));
246    m_widgetArray.RemoveAt(size_t(n));
247    m_clientDataDict.Delete(n, HasClientObjectData());
248
249    XtDestroyWidget(w);
250    m_noStrings--;
251}
252
253void wxChoice::Clear()
254{
255    m_stringList.Clear ();
256    unsigned int i;
257    for (i = 0; i < m_noStrings; i++)
258    {
259        XtRemoveCallback((Widget) m_widgetArray[i],
260                         XmNactivateCallback, (XtCallbackProc)wxChoiceCallback,
261                         (XtPointer)this);
262        XtUnmanageChild ((Widget) m_widgetArray[i]);
263        XtDestroyWidget ((Widget) m_widgetArray[i]);
264    }
265    m_widgetArray.Clear();
266    if (m_buttonWidget)
267        XtVaSetValues ((Widget) m_buttonWidget,
268                       XmNmenuHistory, (Widget) NULL,
269                       NULL);
270
271    if ( HasClientObjectData() )
272        m_clientDataDict.DestroyData();
273
274    m_noStrings = 0;
275}
276
277int wxChoice::GetSelection() const
278{
279    XmString text;
280    Widget label = XmOptionButtonGadget ((Widget) m_buttonWidget);
281    XtVaGetValues (label,
282        XmNlabelString, &text,
283        NULL);
284    wxXmString freeMe(text);
285    wxString s = wxXmStringToString( text );
286
287    if (!s.empty())
288    {
289        int i = 0;
290        for (wxStringList::compatibility_iterator node = m_stringList.GetFirst ();
291             node; node = node->GetNext ())
292        {
293            if (wxStrcmp(node->GetData(), s.c_str()) == 0)
294            {
295                return i;
296            }
297            else
298                i++;
299        }            // for()
300
301        return -1;
302    }
303    return -1;
304}
305
306void wxChoice::SetSelection(int n)
307{
308    m_inSetValue = true;
309
310    wxStringList::compatibility_iterator node = m_stringList.Item(n);
311    if (node)
312    {
313#if 0
314        Dimension selectionWidth, selectionHeight;
315#endif
316        wxXmString text( node->GetData() );
317// MBN: this seems silly, at best, and causes wxChoices to be clipped:
318//      will remove "soon"
319#if 0
320        XtVaGetValues ((Widget) m_widgetArray[n],
321                       XmNwidth, &selectionWidth,
322                       XmNheight, &selectionHeight,
323                       NULL);
324#endif
325        Widget label = XmOptionButtonGadget ((Widget) m_buttonWidget);
326        XtVaSetValues (label,
327            XmNlabelString, text(),
328            NULL);
329#if 0
330        XtVaSetValues ((Widget) m_buttonWidget,
331            XmNwidth, selectionWidth, XmNheight, selectionHeight,
332            XmNmenuHistory, (Widget) m_widgetArray[n], NULL);
333#endif
334    }
335    m_inSetValue = false;
336}
337
338wxString wxChoice::GetString(unsigned int n) const
339{
340    wxStringList::compatibility_iterator node = m_stringList.Item(n);
341    if (node)
342        return node->GetData();
343    else
344        return wxEmptyString;
345}
346
347void wxChoice::SetColumns(int n)
348{
349    if (n<1) n = 1 ;
350
351    short numColumns = (short)n ;
352    Arg args[3];
353
354    XtSetArg(args[0], XmNnumColumns, numColumns);
355    XtSetArg(args[1], XmNpacking, XmPACK_COLUMN);
356    XtSetValues((Widget) m_menuWidget,args,2) ;
357}
358
359int wxChoice::GetColumns(void) const
360{
361    short numColumns ;
362
363    XtVaGetValues((Widget) m_menuWidget,XmNnumColumns,&numColumns,NULL) ;
364    return numColumns ;
365}
366
367void wxChoice::SetFocus()
368{
369    XmProcessTraversal(XtParent((Widget)m_mainWidget), XmTRAVERSE_CURRENT);
370}
371
372void wxChoice::DoSetSize(int x, int y, int width, int height, int sizeFlags)
373{
374    XtVaSetValues((Widget) m_formWidget, XmNresizePolicy, XmRESIZE_ANY, NULL);
375    bool managed = XtIsManaged((Widget) m_formWidget);
376
377    if (managed)
378        XtUnmanageChild ((Widget) m_formWidget);
379
380    int actualWidth = width - WIDTH_OVERHEAD_SUBTRACT,
381        actualHeight = height - HEIGHT_OVERHEAD;
382
383    if (width > -1)
384    {
385        unsigned int i;
386        for (i = 0; i < m_noStrings; i++)
387            XtVaSetValues ((Widget) m_widgetArray[i],
388                           XmNwidth, actualWidth,
389                           NULL);
390        XtVaSetValues ((Widget) m_buttonWidget, XmNwidth, actualWidth,
391            NULL);
392    }
393    if (height > -1)
394    {
395#if 0
396        unsigned int i;
397        for (i = 0; i < m_noStrings; i++)
398            XtVaSetValues ((Widget) m_widgetArray[i],
399                           XmNheight, actualHeight,
400                           NULL);
401#endif
402        XtVaSetValues ((Widget) m_buttonWidget, XmNheight, actualHeight,
403            NULL);
404    }
405
406    if (managed)
407        XtManageChild ((Widget) m_formWidget);
408    XtVaSetValues((Widget) m_formWidget, XmNresizePolicy, XmRESIZE_NONE, NULL);
409
410    wxControl::DoSetSize (x, y, width, height, sizeFlags);
411}
412
413void wxChoice::Command(wxCommandEvent & event)
414{
415    SetSelection (event.GetInt());
416    ProcessCommand (event);
417}
418
419void wxChoiceCallback (Widget w, XtPointer clientData, XtPointer WXUNUSED(ptr))
420{
421    wxChoice *item = (wxChoice *) clientData;
422    if (item)
423    {
424        if (item->InSetValue())
425            return;
426
427        int n = item->GetWidgets().Index(w);
428        if (n != wxNOT_FOUND)
429        {
430            wxCommandEvent event(wxEVT_COMMAND_CHOICE_SELECTED, item->GetId());
431            event.SetEventObject(item);
432            event.SetInt(n);
433            event.SetString( item->GetStrings().Item(n)->GetData() );
434            if ( item->HasClientObjectData() )
435                event.SetClientObject( item->GetClientObject(n) );
436            else if ( item->HasClientUntypedData() )
437                event.SetClientData( item->GetClientData(n) );
438            item->ProcessCommand (event);
439        }
440    }
441}
442
443void wxChoice::ChangeFont(bool keepOriginalSize)
444{
445    // Note that this causes the widget to be resized back
446    // to its original size! We therefore have to set the size
447    // back again. TODO: a better way in Motif?
448    if (m_font.Ok())
449    {
450        Display* dpy = XtDisplay((Widget) m_mainWidget);
451        int width, height, width1, height1;
452        GetSize(& width, & height);
453
454        WXString fontTag = wxFont::GetFontTag();
455
456        XtVaSetValues ((Widget) m_formWidget,
457                       fontTag, m_font.GetFontTypeC(dpy),
458                       NULL);
459        XtVaSetValues ((Widget) m_buttonWidget,
460                       fontTag, m_font.GetFontTypeC(dpy),
461                       NULL);
462
463        for( unsigned int i = 0; i < m_noStrings; ++i )
464            XtVaSetValues( (Widget)m_widgetArray[i],
465                           fontTag, m_font.GetFontTypeC(dpy),
466                           NULL );
467
468        GetSize(& width1, & height1);
469        if (keepOriginalSize && (width != width1 || height != height1))
470        {
471            SetSize(wxDefaultCoord, wxDefaultCoord, width, height);
472        }
473    }
474}
475
476void wxChoice::ChangeBackgroundColour()
477{
478    wxDoChangeBackgroundColour(m_formWidget, m_backgroundColour);
479    wxDoChangeBackgroundColour(m_buttonWidget, m_backgroundColour);
480    wxDoChangeBackgroundColour(m_menuWidget, m_backgroundColour);
481    unsigned int i;
482    for (i = 0; i < m_noStrings; i++)
483        wxDoChangeBackgroundColour(m_widgetArray[i], m_backgroundColour);
484}
485
486void wxChoice::ChangeForegroundColour()
487{
488    wxDoChangeForegroundColour(m_formWidget, m_foregroundColour);
489    wxDoChangeForegroundColour(m_buttonWidget, m_foregroundColour);
490    wxDoChangeForegroundColour(m_menuWidget, m_foregroundColour);
491    unsigned int i;
492    for (i = 0; i < m_noStrings; i++)
493        wxDoChangeForegroundColour(m_widgetArray[i], m_foregroundColour);
494}
495
496unsigned int wxChoice::GetCount() const
497{
498    return m_noStrings;
499}
500
501void wxChoice::DoSetItemClientData(unsigned int n, void* clientData)
502{
503    m_clientDataDict.Set(n, (wxClientData*)clientData, false);
504}
505
506void* wxChoice::DoGetItemClientData(unsigned int n) const
507{
508    return (void*)m_clientDataDict.Get(n);
509}
510
511void wxChoice::DoSetItemClientObject(unsigned int n, wxClientData* clientData)
512{
513    // don't delete, wxItemContainer does that for us
514    m_clientDataDict.Set(n, clientData, false);
515}
516
517wxClientData* wxChoice::DoGetItemClientObject(unsigned int n) const
518{
519    return m_clientDataDict.Get(n);
520}
521
522void wxChoice::SetString(unsigned int WXUNUSED(n), const wxString& WXUNUSED(s))
523{
524    wxFAIL_MSG( wxT("wxChoice::SetString not implemented") );
525}
526
527wxSize wxChoice::GetItemsSize() const
528{
529    int x, y, mx = 0, my = 0;
530
531    // get my
532    GetTextExtent( "|", &x, &my );
533
534    wxStringList::compatibility_iterator curr = m_stringList.GetFirst();
535    while( curr )
536    {
537        GetTextExtent( curr->GetData(), &x, &y );
538        mx = wxMax( mx, x );
539        my = wxMax( my, y );
540        curr = curr->GetNext();
541    }
542
543    return wxSize( mx, my );
544}
545
546wxSize wxChoice::DoGetBestSize() const
547{
548    wxSize items = GetItemsSize();
549    // FIXME arbitrary constants
550    return wxSize( ( items.x ? items.x + WIDTH_OVERHEAD : 120 ),
551                     items.y + HEIGHT_OVERHEAD );
552}
553
554#endif // wxUSE_CHOICE
555