1 /////////////////////////////////////////////////////////////////////////////
2 // Name:        src/gtk/choice.cpp
3 // Purpose:
4 // Author:      Robert Roebling
5 // Copyright:   (c) 1998 Robert Roebling
6 // Licence:     wxWindows licence
7 /////////////////////////////////////////////////////////////////////////////
8 
9 #include "wx/wxprec.h"
10 
11 #if wxUSE_CHOICE || wxUSE_COMBOBOX
12 
13 #include "wx/choice.h"
14 
15 #ifndef WX_PRECOMP
16     #include "wx/arrstr.h"
17 #endif
18 
19 #include "wx/gtk/private.h"
20 #include "wx/gtk/private/eventsdisabler.h"
21 #include "wx/gtk/private/value.h"
22 
23 // ----------------------------------------------------------------------------
24 // GTK callbacks
25 // ----------------------------------------------------------------------------
26 
27 extern "C" {
28 
29 static void
gtk_choice_changed_callback(GtkWidget * WXUNUSED (widget),wxChoice * choice)30 gtk_choice_changed_callback( GtkWidget *WXUNUSED(widget), wxChoice *choice )
31 {
32     choice->SendSelectionChangedEvent(wxEVT_CHOICE);
33 }
34 
35 }
36 
37 //-----------------------------------------------------------------------------
38 // wxChoice
39 //-----------------------------------------------------------------------------
40 
Init()41 void wxChoice::Init()
42 {
43     m_strings = NULL;
44     m_stringCellIndex = 0;
45 }
46 
Create(wxWindow * parent,wxWindowID id,const wxPoint & pos,const wxSize & size,const wxArrayString & choices,long style,const wxValidator & validator,const wxString & name)47 bool wxChoice::Create( wxWindow *parent, wxWindowID id,
48                        const wxPoint &pos, const wxSize &size,
49                        const wxArrayString& choices,
50                        long style, const wxValidator& validator,
51                        const wxString &name )
52 {
53     wxCArrayString chs(choices);
54 
55     return Create( parent, id, pos, size, chs.GetCount(), chs.GetStrings(),
56                    style, validator, name );
57 }
58 
Create(wxWindow * parent,wxWindowID id,const wxPoint & pos,const wxSize & size,int n,const wxString choices[],long style,const wxValidator & validator,const wxString & name)59 bool wxChoice::Create( wxWindow *parent, wxWindowID id,
60                        const wxPoint &pos, const wxSize &size,
61                        int n, const wxString choices[],
62                        long style, const wxValidator& validator,
63                        const wxString &name )
64 {
65     if (!PreCreation( parent, pos, size ) ||
66         !CreateBase( parent, id, pos, size, style, validator, name ))
67     {
68         wxFAIL_MSG( wxT("wxChoice creation failed") );
69         return false;
70     }
71 
72     if ( IsSorted() )
73     {
74         // if our m_strings != NULL, Append() will check for it and insert
75         // items in the correct order
76         m_strings = new wxGtkCollatedArrayString;
77     }
78 
79 #ifdef __WXGTK3__
80     m_widget = gtk_combo_box_text_new();
81 #else
82     m_widget = gtk_combo_box_new_text();
83 #endif
84     g_object_ref(m_widget);
85 
86     Append(n, choices);
87 
88     m_parent->DoAddChild( this );
89 
90     PostCreation(size);
91 
92     g_signal_connect_after (m_widget, "changed",
93                             G_CALLBACK (gtk_choice_changed_callback), this);
94 
95     return true;
96 }
97 
~wxChoice()98 wxChoice::~wxChoice()
99 {
100     Clear();
101     delete m_strings;
102 
103  #ifdef __WXGTK3__
104     // At least with GTK+ 3.22.9, destroying a shown combobox widget results in
105     // a Gtk-CRITICAL debug message when the assertion fails inside a signal
106     // handler called from gtk_widget_unrealize(), which is annoying, so avoid
107     // it by hiding the widget before destroying it -- this doesn't look right,
108     // but shouldn't do any harm neither.
109     Hide();
110  #endif // __WXGTK3__
111 }
112 
GTKHandleFocusOut()113 bool wxChoice::GTKHandleFocusOut()
114 {
115     if ( wx_is_at_least_gtk2(10) )
116     {
117         gboolean isShown;
118         g_object_get( m_widget, "popup-shown", &isShown, NULL );
119 
120         // Don't send "focus lost" events if the focus is grabbed by our own
121         // popup, it counts as part of this window, even though wx doesn't know
122         // about it (and can't, because GtkComboBox doesn't expose it).
123         if ( isShown )
124             return true;
125     }
126 
127     return wxChoiceBase::GTKHandleFocusOut();
128 }
129 
GTKInsertComboBoxTextItem(unsigned int n,const wxString & text)130 void wxChoice::GTKInsertComboBoxTextItem( unsigned int n, const wxString& text )
131 {
132 #ifdef __WXGTK3__
133     gtk_combo_box_text_insert_text(GTK_COMBO_BOX_TEXT(m_widget), n, wxGTK_CONV(text));
134 #else
135     gtk_combo_box_insert_text( GTK_COMBO_BOX( m_widget ), n, wxGTK_CONV( text ) );
136 #endif
137 }
138 
DoInsertItems(const wxArrayStringsAdapter & items,unsigned int pos,void ** clientData,wxClientDataType type)139 int wxChoice::DoInsertItems(const wxArrayStringsAdapter & items,
140                             unsigned int pos,
141                             void **clientData, wxClientDataType type)
142 {
143     wxCHECK_MSG( m_widget != NULL, -1, wxT("invalid control") );
144 
145     wxASSERT_MSG( !IsSorted() || (pos == GetCount()),
146                  wxT("In a sorted choice data could only be appended"));
147 
148     const int count = items.GetCount();
149 
150     int n = wxNOT_FOUND;
151 
152     for ( int i = 0; i < count; ++i )
153     {
154         n = pos + i;
155         // If sorted, use this wxSortedArrayStrings to determine
156         // the right insertion point
157         if (m_strings)
158             n = m_strings->Add(items[i]);
159 
160         GTKInsertComboBoxTextItem( n, items[i] );
161 
162         m_clientData.Insert( NULL, n );
163         AssignNewItemClientData(n, clientData, i, type);
164     }
165 
166     InvalidateBestSize();
167 
168     return n;
169 }
170 
DoSetItemClientData(unsigned int n,void * clientData)171 void wxChoice::DoSetItemClientData(unsigned int n, void* clientData)
172 {
173     m_clientData[n] = clientData;
174 }
175 
DoGetItemClientData(unsigned int n) const176 void* wxChoice::DoGetItemClientData(unsigned int n) const
177 {
178     return m_clientData[n];
179 }
180 
DoClear()181 void wxChoice::DoClear()
182 {
183     wxCHECK_RET( m_widget != NULL, wxT("invalid control") );
184 
185     wxGtkEventsDisabler<wxChoice> noEvents(this);
186 
187     GtkComboBox* combobox = GTK_COMBO_BOX( m_widget );
188     GtkTreeModel* model = gtk_combo_box_get_model( combobox );
189     if (model)
190         gtk_list_store_clear(GTK_LIST_STORE(model));
191 
192     m_clientData.Clear();
193 
194     if (m_strings)
195         m_strings->Clear();
196 
197     InvalidateBestSize();
198 }
199 
DoDeleteOneItem(unsigned int n)200 void wxChoice::DoDeleteOneItem(unsigned int n)
201 {
202     wxCHECK_RET( m_widget != NULL, wxT("invalid control") );
203     wxCHECK_RET( IsValid(n), wxT("invalid index in wxChoice::Delete") );
204 
205     GtkComboBox* combobox = GTK_COMBO_BOX( m_widget );
206     GtkTreeModel* model = gtk_combo_box_get_model( combobox );
207     GtkListStore* store = GTK_LIST_STORE(model);
208     GtkTreeIter iter;
209     if ( !gtk_tree_model_iter_nth_child(model, &iter, NULL, n) )
210     {
211         // This is really not supposed to happen for a valid index.
212         wxFAIL_MSG(wxS("Item unexpectedly not found."));
213         return;
214     }
215     gtk_list_store_remove( store, &iter );
216 
217     m_clientData.RemoveAt( n );
218     if ( m_strings )
219         m_strings->RemoveAt( n );
220 
221     InvalidateBestSize();
222 }
223 
FindString(const wxString & item,bool bCase) const224 int wxChoice::FindString( const wxString &item, bool bCase ) const
225 {
226     wxCHECK_MSG( m_widget != NULL, wxNOT_FOUND, wxT("invalid control") );
227 
228     GtkComboBox* combobox = GTK_COMBO_BOX( m_widget );
229     GtkTreeModel* model = gtk_combo_box_get_model( combobox );
230     GtkTreeIter iter;
231     gtk_tree_model_get_iter_first( model, &iter );
232     if (!gtk_list_store_iter_is_valid(GTK_LIST_STORE(model), &iter ))
233         return -1;
234     int count = 0;
235     do
236     {
237         wxGtkValue value;
238         gtk_tree_model_get_value( model, &iter, m_stringCellIndex, value );
239         wxString str = wxGTK_CONV_BACK( g_value_get_string( value ) );
240 
241         if (item.IsSameAs( str, bCase ) )
242             return count;
243 
244         count++;
245     }
246     while ( gtk_tree_model_iter_next(model, &iter) );
247 
248     return wxNOT_FOUND;
249 }
250 
GetSelection() const251 int wxChoice::GetSelection() const
252 {
253     return gtk_combo_box_get_active( GTK_COMBO_BOX( m_widget ) );
254 }
255 
SetString(unsigned int n,const wxString & text)256 void wxChoice::SetString(unsigned int n, const wxString &text)
257 {
258     wxCHECK_RET( m_widget != NULL, wxT("invalid control") );
259 
260     GtkComboBox* combobox = GTK_COMBO_BOX( m_widget );
261     wxCHECK_RET( IsValid(n), wxT("invalid index") );
262 
263     GtkTreeModel *model = gtk_combo_box_get_model( combobox );
264     GtkTreeIter iter;
265     if (gtk_tree_model_iter_nth_child (model, &iter, NULL, n))
266     {
267         wxGtkValue value(G_TYPE_STRING);
268         g_value_set_string( value, wxGTK_CONV( text ) );
269         gtk_list_store_set_value( GTK_LIST_STORE(model), &iter, m_stringCellIndex, value );
270     }
271 
272     InvalidateBestSize();
273 }
274 
GetString(unsigned int n) const275 wxString wxChoice::GetString(unsigned int n) const
276 {
277     wxCHECK_MSG( m_widget != NULL, wxEmptyString, wxT("invalid control") );
278 
279     GtkComboBox* combobox = GTK_COMBO_BOX( m_widget );
280     GtkTreeModel *model = gtk_combo_box_get_model( combobox );
281     GtkTreeIter iter;
282     if (!gtk_tree_model_iter_nth_child (model, &iter, NULL, n))
283     {
284         wxFAIL_MSG( "invalid index" );
285         return wxString();
286     }
287 
288     wxGtkValue value;
289     gtk_tree_model_get_value( model, &iter, m_stringCellIndex, value );
290     return wxGTK_CONV_BACK( g_value_get_string( value ) );
291 }
292 
GetCount() const293 unsigned int wxChoice::GetCount() const
294 {
295     wxCHECK_MSG( m_widget != NULL, 0, wxT("invalid control") );
296 
297     GtkComboBox* combobox = GTK_COMBO_BOX( m_widget );
298     GtkTreeModel* model = gtk_combo_box_get_model( combobox );
299     GtkTreeIter iter;
300     gtk_tree_model_get_iter_first( model, &iter );
301     if (!gtk_list_store_iter_is_valid(GTK_LIST_STORE(model), &iter ))
302         return 0;
303     unsigned int ret = 1;
304     while (gtk_tree_model_iter_next( model, &iter ))
305         ret++;
306     return ret;
307 }
308 
SetSelection(int n)309 void wxChoice::SetSelection( int n )
310 {
311     wxCHECK_RET( m_widget != NULL, wxT("invalid control") );
312 
313     wxGtkEventsDisabler<wxChoice> noEvents(this);
314 
315     GtkComboBox* combobox = GTK_COMBO_BOX( m_widget );
316     gtk_combo_box_set_active( combobox, n );
317 }
318 
SetColumns(int n)319 void wxChoice::SetColumns(int n)
320 {
321     gtk_combo_box_set_wrap_width(GTK_COMBO_BOX(m_widget), n);
322 }
323 
GetColumns() const324 int wxChoice::GetColumns() const
325 {
326     return gtk_combo_box_get_wrap_width(GTK_COMBO_BOX(m_widget));
327 }
328 
GTKDisableEvents()329 void wxChoice::GTKDisableEvents()
330 {
331     g_signal_handlers_block_by_func(m_widget,
332                                 (gpointer) gtk_choice_changed_callback, this);
333 }
334 
GTKEnableEvents()335 void wxChoice::GTKEnableEvents()
336 {
337     g_signal_handlers_unblock_by_func(m_widget,
338                                 (gpointer) gtk_choice_changed_callback, this);
339 }
340 
GTKGetWindow(wxArrayGdkWindows & WXUNUSED (windows)) const341 GdkWindow *wxChoice::GTKGetWindow(wxArrayGdkWindows& WXUNUSED(windows)) const
342 {
343     return gtk_widget_get_window(m_widget);
344 }
345 
DoGetBestSize() const346 wxSize wxChoice::DoGetBestSize() const
347 {
348     // Get the height of the control from GTK+ itself, but use our own version
349     // to compute the width large enough to show all our strings as GTK+
350     // doesn't seem to take the control contents into account.
351     return GetSizeFromTextSize(wxChoiceBase::DoGetBestSize().x);
352 }
353 
DoGetSizeFromTextSize(int xlen,int ylen) const354 wxSize wxChoice::DoGetSizeFromTextSize(int xlen, int ylen) const
355 {
356     wxASSERT_MSG( m_widget, wxS("GetSizeFromTextSize called before creation") );
357 
358     // a GtkEntry for wxComboBox and a GtkCellView for wxChoice
359     GtkWidget* childPart = gtk_bin_get_child(GTK_BIN(m_widget));
360 
361     // We are interested in the difference of sizes between the whole contol
362     // and its child part. I.e. arrow, separators, etc.
363     GtkRequisition req;
364     gtk_widget_get_preferred_size(childPart, NULL, &req);
365     wxSize totalS = GTKGetPreferredSize(m_widget);
366 
367     wxSize tsize(xlen + totalS.x - req.width, totalS.y);
368 
369     // For a wxChoice, not for wxComboBox, add some margins
370     if ( !GTK_IS_ENTRY(childPart) )
371         tsize.IncBy(5, 0);
372 
373     // Perhaps the user wants something different from CharHeight
374     if ( ylen > 0 )
375         tsize.IncBy(0, ylen - GetCharHeight());
376 
377     return tsize;
378 }
379 
DoApplyWidgetStyle(GtkRcStyle * style)380 void wxChoice::DoApplyWidgetStyle(GtkRcStyle *style)
381 {
382     GTKApplyStyle(m_widget, style);
383     GTKApplyStyle(gtk_bin_get_child(GTK_BIN(m_widget)), style);
384 }
385 
386 // static
387 wxVisualAttributes
GetClassDefaultAttributes(wxWindowVariant WXUNUSED (variant))388 wxChoice::GetClassDefaultAttributes(wxWindowVariant WXUNUSED(variant))
389 {
390     return GetDefaultAttributesFromGTKWidget(gtk_combo_box_new());
391 }
392 
393 #endif // wxUSE_CHOICE || wxUSE_COMBOBOX
394