1/////////////////////////////////////////////////////////////////////////////
2// Name:        src/gtk/choice.cpp
3// Purpose:
4// Author:      Robert Roebling
5// Id:          $Id: choice.cpp 54967 2008-08-04 15:33:13Z JS $
6// Copyright:   (c) 1998 Robert Roebling
7// Licence:     wxWindows licence
8/////////////////////////////////////////////////////////////////////////////
9
10#include "wx/wxprec.h"
11
12#if wxUSE_CHOICE
13
14#include "wx/choice.h"
15
16#ifndef WX_PRECOMP
17    #include "wx/arrstr.h"
18#endif
19
20// FIXME: We use GtkOptionMenu which has been deprecated since GTK+ 2.3.0 in
21// favour of GtkComboBox.
22// Later use GtkComboBox if GTK+ runtime version is new enough.
23#include <gtk/gtkversion.h>
24#if defined(GTK_DISABLE_DEPRECATED) && GTK_CHECK_VERSION(2,3,0)
25#undef GTK_DISABLE_DEPRECATED
26#endif
27
28#include "wx/gtk/private.h"
29
30//-----------------------------------------------------------------------------
31// data
32//-----------------------------------------------------------------------------
33
34extern bool   g_blockEventsOnDrag;
35
36//-----------------------------------------------------------------------------
37// "activate"
38//-----------------------------------------------------------------------------
39
40extern "C" {
41static void gtk_choice_clicked_callback( GtkWidget *WXUNUSED(widget), wxChoice *choice )
42{
43    if (g_isIdle)
44      wxapp_install_idle_handler();
45
46    if (!choice->m_hasVMT) return;
47
48    if (g_blockEventsOnDrag) return;
49
50    int selection = wxNOT_FOUND;
51
52    selection = gtk_option_menu_get_history( GTK_OPTION_MENU(choice->GetHandle()) );
53
54    choice->m_selection_hack = selection;
55
56    wxCommandEvent event(wxEVT_COMMAND_CHOICE_SELECTED, choice->GetId() );
57    int n = choice->GetSelection();
58
59    event.SetInt( n );
60    event.SetString( choice->GetStringSelection() );
61    event.SetEventObject(choice);
62
63    if ( choice->HasClientObjectData() )
64        event.SetClientObject( choice->GetClientObject(n) );
65    else if ( choice->HasClientUntypedData() )
66        event.SetClientData( choice->GetClientData(n) );
67
68    choice->GetEventHandler()->ProcessEvent(event);
69}
70}
71
72//-----------------------------------------------------------------------------
73// wxChoice
74//-----------------------------------------------------------------------------
75
76IMPLEMENT_DYNAMIC_CLASS(wxChoice,wxControl)
77
78wxChoice::wxChoice()
79{
80    m_strings = (wxSortedArrayString *)NULL;
81}
82
83bool wxChoice::Create( wxWindow *parent, wxWindowID id,
84                       const wxPoint &pos, const wxSize &size,
85                       const wxArrayString& choices,
86                       long style, const wxValidator& validator,
87                       const wxString &name )
88{
89    wxCArrayString chs(choices);
90
91    return Create( parent, id, pos, size, chs.GetCount(), chs.GetStrings(),
92                   style, validator, name );
93}
94
95bool wxChoice::Create( wxWindow *parent, wxWindowID id,
96                       const wxPoint &pos, const wxSize &size,
97                       int n, const wxString choices[],
98                       long style, const wxValidator& validator, const wxString &name )
99{
100    m_needParent = true;
101#if (GTK_MINOR_VERSION > 0)
102    m_acceptsFocus = true;
103#endif
104
105    if (!PreCreation( parent, pos, size ) ||
106        !CreateBase( parent, id, pos, size, style, validator, name ))
107    {
108        wxFAIL_MSG( wxT("wxChoice creation failed") );
109        return false;
110    }
111
112    m_widget = gtk_option_menu_new();
113
114    if ( style & wxCB_SORT )
115    {
116        // if our m_strings != NULL, DoAppend() will check for it and insert
117        // items in the correct order
118        m_strings = new wxSortedArrayString;
119    }
120
121    // If we have items, GTK will choose the first item by default
122    m_selection_hack = n > 0 ? 0 : wxNOT_FOUND;
123
124    GtkWidget *menu = gtk_menu_new();
125
126    for (unsigned int i = 0; i < (unsigned int)n; i++)
127    {
128        GtkAddHelper(menu, i, choices[i]);
129    }
130
131    gtk_option_menu_set_menu( GTK_OPTION_MENU(m_widget), menu );
132
133    m_parent->DoAddChild( this );
134
135    PostCreation(size);
136    SetInitialSize(size); // need this too because this is a wxControlWithItems
137
138    return true;
139}
140
141wxChoice::~wxChoice()
142{
143    Clear();
144
145    delete m_strings;
146}
147
148int wxChoice::DoAppend( const wxString &item )
149{
150    wxCHECK_MSG( m_widget != NULL, -1, wxT("invalid choice control") );
151
152    GtkWidget *menu = gtk_option_menu_get_menu( GTK_OPTION_MENU(m_widget) );
153
154    return GtkAddHelper(menu, GetCount(), item);
155}
156
157int wxChoice::DoInsert(const wxString &item, unsigned int pos)
158{
159    wxCHECK_MSG( m_widget != NULL, -1, wxT("invalid choice control") );
160    wxCHECK_MSG( IsValidInsert(pos), -1, wxT("invalid index"));
161
162    if (pos == GetCount())
163        return DoAppend(item);
164
165    GtkWidget *menu = gtk_option_menu_get_menu( GTK_OPTION_MENU(m_widget) );
166
167    // if the item to insert is at or before the selection, and the selection is valid
168    if (((int)pos <= m_selection_hack) && (m_selection_hack != wxNOT_FOUND))
169    {
170        // move the selection forward one
171        m_selection_hack++;
172    }
173
174    return GtkAddHelper(menu, pos, item);
175}
176
177void wxChoice::DoSetItemClientData(unsigned int n, void* clientData)
178{
179    wxCHECK_RET( m_widget != NULL, wxT("invalid choice control") );
180
181    wxList::compatibility_iterator node = m_clientList.Item( n );
182    wxCHECK_RET( node, wxT("invalid index in wxChoice::DoSetItemClientData") );
183
184    node->SetData( (wxObject*) clientData );
185}
186
187void* wxChoice::DoGetItemClientData(unsigned int n) const
188{
189    wxCHECK_MSG( m_widget != NULL, NULL, wxT("invalid choice control") );
190
191    wxList::compatibility_iterator node = m_clientList.Item( n );
192    wxCHECK_MSG( node, NULL, wxT("invalid index in wxChoice::DoGetItemClientData") );
193
194    return node->GetData();
195}
196
197void wxChoice::DoSetItemClientObject(unsigned int n, wxClientData* clientData)
198{
199    wxCHECK_RET( m_widget != NULL, wxT("invalid choice control") );
200
201    wxList::compatibility_iterator node = m_clientList.Item( n );
202    wxCHECK_RET( node, wxT("invalid index in wxChoice::DoSetItemClientObject") );
203
204    // wxItemContainer already deletes data for us
205
206    node->SetData( (wxObject*) clientData );
207}
208
209wxClientData* wxChoice::DoGetItemClientObject(unsigned int n) const
210{
211    wxCHECK_MSG( m_widget != NULL, (wxClientData*) NULL, wxT("invalid choice control") );
212
213    wxList::compatibility_iterator node = m_clientList.Item( n );
214    wxCHECK_MSG( node, (wxClientData *)NULL,
215                 wxT("invalid index in wxChoice::DoGetItemClientObject") );
216
217    return (wxClientData*) node->GetData();
218}
219
220void wxChoice::Clear()
221{
222    wxCHECK_RET( m_widget != NULL, wxT("invalid choice") );
223
224    gtk_option_menu_remove_menu( GTK_OPTION_MENU(m_widget) );
225    GtkWidget *menu = gtk_menu_new();
226    gtk_option_menu_set_menu( GTK_OPTION_MENU(m_widget), menu );
227
228    if ( HasClientObjectData() )
229    {
230        // destroy the data (due to Robert's idea of using wxList<wxObject>
231        // and not wxList<wxClientData> we can't just say
232        // m_clientList.DeleteContents(true) - this would crash!
233        wxList::compatibility_iterator node = m_clientList.GetFirst();
234        while ( node )
235        {
236            delete (wxClientData *)node->GetData();
237            node = node->GetNext();
238        }
239    }
240    m_clientList.Clear();
241
242    if ( m_strings )
243        m_strings->Clear();
244
245    // begin with no selection
246    m_selection_hack = wxNOT_FOUND;
247}
248
249void wxChoice::Delete(unsigned int n)
250{
251    wxCHECK_RET( m_widget != NULL, wxT("invalid choice") );
252    wxCHECK_RET( IsValid(n), _T("invalid index in wxChoice::Delete") );
253
254    // VZ: apparently GTK+ doesn't have a built-in function to do it (not even
255    //     in 2.0), hence this dumb implementation -- still better than nothing
256    unsigned int i;
257    const unsigned int count = GetCount();
258
259    // if the item to delete is before the selection, and the selection is valid
260    if (((int)n < m_selection_hack) && (m_selection_hack != wxNOT_FOUND))
261    {
262        // move the selection back one
263        m_selection_hack--;
264    }
265    else if ((int)n == m_selection_hack)
266    {
267        // invalidate the selection
268        m_selection_hack = wxNOT_FOUND;
269    }
270
271    const bool hasClientData = m_clientDataItemsType != wxClientData_None;
272    const bool hasObjectData = m_clientDataItemsType == wxClientData_Object;
273
274    wxList::compatibility_iterator node = m_clientList.GetFirst();
275
276    wxArrayString items;
277    wxArrayPtrVoid itemsData;
278    items.Alloc(count);
279    for ( i = 0; i < count; i++ )
280    {
281        if ( i != n )
282        {
283            items.Add(GetString(i));
284            if ( hasClientData )
285            {
286                // also save the client data
287                itemsData.Add(node->GetData());
288            }
289        }
290        else // need to delete the client object too
291        {
292            if ( hasObjectData )
293            {
294                delete (wxClientData *)node->GetData();
295            }
296        }
297
298        if ( hasClientData )
299        {
300            node = node->GetNext();
301        }
302    }
303
304    if ( hasObjectData )
305    {
306        // prevent Clear() from destroying all client data
307        m_clientDataItemsType = wxClientData_None;
308    }
309
310    Clear();
311
312    for ( i = 0; i < count - 1; i++ )
313    {
314        Append(items[i]);
315
316        if ( hasObjectData )
317            SetClientObject(i, (wxClientData *)itemsData[i]);
318        else if ( hasClientData )
319            SetClientData(i, itemsData[i]);
320    }
321}
322
323int wxChoice::FindString( const wxString &string, bool bCase ) const
324{
325    wxCHECK_MSG( m_widget != NULL, wxNOT_FOUND, wxT("invalid choice") );
326
327    // If you read this code once and you think you understand
328    // it, then you are very wrong. Robert Roebling.
329
330    GtkMenuShell *menu_shell = GTK_MENU_SHELL( gtk_option_menu_get_menu( GTK_OPTION_MENU(m_widget) ) );
331    int count = 0;
332    GList *child = menu_shell->children;
333    while (child)
334    {
335        GtkBin *bin = GTK_BIN( child->data );
336        GtkLabel *label = (GtkLabel *) NULL;
337        if (bin->child)
338            label = GTK_LABEL(bin->child);
339        if (!label)
340            label = GTK_LABEL(GTK_BIN(m_widget)->child);
341
342        wxASSERT_MSG( label != NULL , wxT("wxChoice: invalid label") );
343
344         wxString tmp( wxGTK_CONV_BACK( gtk_label_get_text( label) ) );
345        if (string.IsSameAs( tmp, bCase ))
346            return count;
347
348        child = child->next;
349        count++;
350    }
351
352    return wxNOT_FOUND;
353}
354
355int wxChoice::GetSelection() const
356{
357    wxCHECK_MSG( m_widget != NULL, wxNOT_FOUND, wxT("invalid choice") );
358
359    return m_selection_hack;
360
361}
362
363void wxChoice::SetString(unsigned int n, const wxString& str)
364{
365    wxCHECK_RET( m_widget != NULL, wxT("invalid choice") );
366
367    GtkMenuShell *menu_shell = GTK_MENU_SHELL( gtk_option_menu_get_menu( GTK_OPTION_MENU(m_widget) ) );
368    unsigned int count = 0;
369    GList *child = menu_shell->children;
370    while (child)
371    {
372        GtkBin *bin = GTK_BIN( child->data );
373        if (count == n)
374        {
375            GtkLabel *label = (GtkLabel *) NULL;
376            if (bin->child)
377                label = GTK_LABEL(bin->child);
378            if (!label)
379                label = GTK_LABEL(GTK_BIN(m_widget)->child);
380
381            wxASSERT_MSG( label != NULL , wxT("wxChoice: invalid label") );
382
383            gtk_label_set_text( label, wxGTK_CONV( str ) );
384
385            InvalidateBestSize();
386
387            return;
388        }
389        child = child->next;
390        count++;
391    }
392}
393
394wxString wxChoice::GetString(unsigned int n) const
395{
396    wxCHECK_MSG( m_widget != NULL, wxEmptyString, wxT("invalid choice") );
397
398    GtkMenuShell *menu_shell = GTK_MENU_SHELL( gtk_option_menu_get_menu( GTK_OPTION_MENU(m_widget) ) );
399    unsigned int count = 0;
400    GList *child = menu_shell->children;
401    while (child)
402    {
403        GtkBin *bin = GTK_BIN( child->data );
404        if (count == n)
405        {
406            GtkLabel *label = (GtkLabel *) NULL;
407            if (bin->child)
408                label = GTK_LABEL(bin->child);
409            if (!label)
410                label = GTK_LABEL(GTK_BIN(m_widget)->child);
411
412            wxASSERT_MSG( label != NULL , wxT("wxChoice: invalid label") );
413
414            return wxString( wxGTK_CONV_BACK( gtk_label_get_text( label) ) );
415        }
416        child = child->next;
417        count++;
418    }
419
420    wxFAIL_MSG( wxT("wxChoice: invalid index in GetString()") );
421
422    return wxEmptyString;
423}
424
425unsigned int wxChoice::GetCount() const
426{
427    wxCHECK_MSG( m_widget != NULL, 0, wxT("invalid choice") );
428
429    GtkMenuShell *menu_shell = GTK_MENU_SHELL( gtk_option_menu_get_menu( GTK_OPTION_MENU(m_widget) ) );
430    unsigned int count = 0;
431    GList *child = menu_shell->children;
432    while (child)
433    {
434        count++;
435        child = child->next;
436    }
437    return count;
438}
439
440void wxChoice::SetSelection( int n )
441{
442    wxCHECK_RET( m_widget != NULL, wxT("invalid choice") );
443
444    int tmp = n;
445    gtk_option_menu_set_history( GTK_OPTION_MENU(m_widget), (gint)tmp );
446
447    // set the local selection variable manually
448    if ((n >= 0) && ((unsigned int)n < GetCount()))
449    {
450        // a valid selection has been made
451        m_selection_hack = n;
452    }
453    else if ((n == wxNOT_FOUND) || (GetCount() == 0))
454    {
455        // invalidates the selection if there are no items
456        // or if it is specifically set to wxNOT_FOUND
457        m_selection_hack = wxNOT_FOUND;
458    }
459    else
460    {
461        // this selects the first item by default if the selection is out of bounds
462        m_selection_hack = 0;
463    }
464}
465
466void wxChoice::DoApplyWidgetStyle(GtkRcStyle *style)
467{
468    GtkMenuShell *menu_shell = GTK_MENU_SHELL( gtk_option_menu_get_menu( GTK_OPTION_MENU(m_widget) ) );
469
470    gtk_widget_modify_style( m_widget, style );
471    gtk_widget_modify_style( GTK_WIDGET( menu_shell ), style );
472
473    GList *child = menu_shell->children;
474    while (child)
475    {
476        gtk_widget_modify_style( GTK_WIDGET( child->data ), style );
477
478        GtkBin *bin = GTK_BIN( child->data );
479        GtkWidget *label = (GtkWidget *) NULL;
480        if (bin->child)
481            label = bin->child;
482        if (!label)
483            label = GTK_BIN(m_widget)->child;
484
485        gtk_widget_modify_style( label, style );
486
487        child = child->next;
488    }
489}
490
491int wxChoice::GtkAddHelper(GtkWidget *menu, unsigned int pos, const wxString& item)
492{
493    wxCHECK_MSG(pos<=m_clientList.GetCount(), -1, wxT("invalid index"));
494
495    GtkWidget *menu_item = gtk_menu_item_new_with_label( wxGTK_CONV( item ) );
496
497    unsigned int index;
498    if ( m_strings )
499    {
500        // sorted control, need to insert at the correct index
501        index = m_strings->Add(item);
502
503        gtk_menu_shell_insert( GTK_MENU_SHELL(menu), menu_item, index );
504
505        if ( index )
506        {
507            m_clientList.Insert( m_clientList.Item(index - 1),
508                                 (wxObject*) NULL );
509        }
510        else
511        {
512            m_clientList.Insert( (wxObject*) NULL );
513        }
514    }
515    else
516    {
517        // don't call wxChoice::GetCount() from here because it doesn't work
518        // if we're called from ctor (and GtkMenuShell is still NULL)
519
520        // normal control, just append
521        if (pos == m_clientList.GetCount())
522        {
523            gtk_menu_shell_append( GTK_MENU_SHELL(menu), menu_item );
524            m_clientList.Append( (wxObject*) NULL );
525            index = m_clientList.GetCount() - 1;
526        }
527        else
528        {
529            gtk_menu_shell_insert( GTK_MENU_SHELL(menu), menu_item, pos );
530            m_clientList.Insert( pos, (wxObject*) NULL );
531            index = pos;
532        }
533    }
534
535    if (GTK_WIDGET_REALIZED(m_widget))
536    {
537        gtk_widget_realize( menu_item );
538        gtk_widget_realize( GTK_BIN(menu_item)->child );
539
540        ApplyWidgetStyle();
541    }
542
543    // The best size of a wxChoice should probably
544    // be changed everytime the control has been
545    // changed, but at least after adding an item
546    // it has to change. Adapted from Matt Ownby.
547    InvalidateBestSize();
548
549    g_signal_connect_after (menu_item, "activate",
550                            G_CALLBACK (gtk_choice_clicked_callback),
551                            this);
552
553    gtk_widget_show( menu_item );
554
555    // return the index of the item in the control
556    return index;
557}
558
559wxSize wxChoice::DoGetBestSize() const
560{
561    wxSize ret( wxControl::DoGetBestSize() );
562
563    // we know better our horizontal extent: it depends on the longest string
564    // we have
565    ret.x = 0;
566    if ( m_widget )
567    {
568        int width;
569        unsigned int count = GetCount();
570        for ( unsigned int n = 0; n < count; n++ )
571        {
572            GetTextExtent( GetString(n), &width, NULL, NULL, NULL );
573            if ( width > ret.x )
574                ret.x = width;
575        }
576
577        // add extra for the choice "=" button
578
579        // VZ: I don't know how to get the right value, it seems to be in
580        //     GtkOptionMenuProps struct from gtkoptionmenu.c but we can't get
581        //     to it - maybe we can use gtk_option_menu_size_request() for this
582        //     somehow?
583        //
584        //     This default value works only for the default GTK+ theme (i.e.
585        //     no theme at all) (FIXME)
586        static const int widthChoiceIndicator = 35;
587        ret.x += widthChoiceIndicator;
588    }
589
590    // but not less than the minimal width
591    if ( GetCount() == 0 && ret.x < 80 )
592        ret.x = 80;
593
594    // If this request_size is called with no entries then
595    // the returned height is wrong. Give it a reasonable
596    // default value.
597    if (ret.y <= 18)
598        ret.y = 8 + GetCharHeight();
599
600    CacheBestSize(ret);
601    return ret;
602}
603
604GdkWindow *wxChoice::GTKGetWindow(wxArrayGdkWindows& WXUNUSED(windows)) const
605{
606    return GTK_BUTTON(m_widget)->event_window;
607}
608
609// static
610wxVisualAttributes
611wxChoice::GetClassDefaultAttributes(wxWindowVariant WXUNUSED(variant))
612{
613    return GetDefaultAttributesFromGTKWidget(gtk_option_menu_new);
614}
615
616
617#endif // wxUSE_CHOICE
618