1 /////////////////////////////////////////////////////////////////////////////
2 // Name:        src/gtk/combobox.cpp
3 // Purpose:
4 // Author:      Robert Roebling
5 // Copyright:   (c) 1998 Robert Roebling
6 // Licence:     wxWindows licence
7 /////////////////////////////////////////////////////////////////////////////
8 
9 // For compilers that support precompilation, includes "wx.h".
10 #include "wx/wxprec.h"
11 
12 #if wxUSE_COMBOBOX
13 
14 #include "wx/combobox.h"
15 
16 #ifndef WX_PRECOMP
17     #include "wx/intl.h"
18     #include "wx/settings.h"
19     #include "wx/textctrl.h"    // for wxEVT_TEXT
20     #include "wx/arrstr.h"
21 #endif
22 
23 #include "wx/gtk/private.h"
24 
25 // ----------------------------------------------------------------------------
26 // GTK callbacks
27 // ----------------------------------------------------------------------------
28 
29 extern "C" {
30 
31 static void
gtkcombobox_changed_callback(GtkWidget * WXUNUSED (widget),wxComboBox * combo)32 gtkcombobox_changed_callback( GtkWidget *WXUNUSED(widget), wxComboBox *combo )
33 {
34     combo->SendSelectionChangedEvent(wxEVT_COMBOBOX);
35 }
36 
37 static void
gtkcombobox_popupshown_callback(GObject * WXUNUSED (gobject),GParamSpec * WXUNUSED (param_spec),wxComboBox * combo)38 gtkcombobox_popupshown_callback(GObject *WXUNUSED(gobject),
39                                 GParamSpec *WXUNUSED(param_spec),
40                                 wxComboBox *combo)
41 {
42     gboolean isShown;
43     g_object_get( combo->m_widget, "popup-shown", &isShown, NULL );
44     wxCommandEvent event( isShown ? wxEVT_COMBOBOX_DROPDOWN
45                                   : wxEVT_COMBOBOX_CLOSEUP,
46                           combo->GetId() );
47     event.SetEventObject( combo );
48 
49 #ifndef __WXGTK3__
50     // Process the close up event once the combobox is already closed with GTK+
51     // 2, otherwise changing the combobox from its handler result in errors.
52     if ( !isShown )
53     {
54         combo->GetEventHandler()->AddPendingEvent( event );
55     }
56     else
57 #endif // GTK+ < 3
58     {
59         combo->HandleWindowEvent( event );
60     }
61 }
62 
63 }
64 
65 //-----------------------------------------------------------------------------
66 // wxComboBox
67 //-----------------------------------------------------------------------------
68 
wxBEGIN_EVENT_TABLE(wxComboBox,wxChoice)69 wxBEGIN_EVENT_TABLE(wxComboBox, wxChoice)
70     EVT_CHAR(wxComboBox::OnChar)
71 
72     EVT_MENU(wxID_CUT, wxComboBox::OnCut)
73     EVT_MENU(wxID_COPY, wxComboBox::OnCopy)
74     EVT_MENU(wxID_PASTE, wxComboBox::OnPaste)
75     EVT_MENU(wxID_UNDO, wxComboBox::OnUndo)
76     EVT_MENU(wxID_REDO, wxComboBox::OnRedo)
77     EVT_MENU(wxID_CLEAR, wxComboBox::OnDelete)
78     EVT_MENU(wxID_SELECTALL, wxComboBox::OnSelectAll)
79 
80     EVT_UPDATE_UI(wxID_CUT, wxComboBox::OnUpdateCut)
81     EVT_UPDATE_UI(wxID_COPY, wxComboBox::OnUpdateCopy)
82     EVT_UPDATE_UI(wxID_PASTE, wxComboBox::OnUpdatePaste)
83     EVT_UPDATE_UI(wxID_UNDO, wxComboBox::OnUpdateUndo)
84     EVT_UPDATE_UI(wxID_REDO, wxComboBox::OnUpdateRedo)
85     EVT_UPDATE_UI(wxID_CLEAR, wxComboBox::OnUpdateDelete)
86     EVT_UPDATE_UI(wxID_SELECTALL, wxComboBox::OnUpdateSelectAll)
87 wxEND_EVENT_TABLE()
88 
89 wxComboBox::~wxComboBox()
90 {
91     if (m_entry)
92     {
93         GTKDisconnect(m_entry);
94         g_object_remove_weak_pointer(G_OBJECT(m_entry), (void**)&m_entry);
95     }
96 }
97 
Init()98 void wxComboBox::Init()
99 {
100     m_entry = NULL;
101 }
102 
Create(wxWindow * parent,wxWindowID id,const wxString & value,const wxPoint & pos,const wxSize & size,const wxArrayString & choices,long style,const wxValidator & validator,const wxString & name)103 bool wxComboBox::Create( wxWindow *parent, wxWindowID id,
104                          const wxString& value,
105                          const wxPoint& pos, const wxSize& size,
106                          const wxArrayString& choices,
107                          long style, const wxValidator& validator,
108                          const wxString& name )
109 {
110     wxCArrayString chs(choices);
111 
112     return Create( parent, id, value, pos, size, chs.GetCount(),
113                    chs.GetStrings(), style, validator, name );
114 }
115 
Create(wxWindow * parent,wxWindowID id,const wxString & value,const wxPoint & pos,const wxSize & size,int n,const wxString choices[],long style,const wxValidator & validator,const wxString & name)116 bool wxComboBox::Create( wxWindow *parent, wxWindowID id, const wxString& value,
117                          const wxPoint& pos, const wxSize& size,
118                          int n, const wxString choices[],
119                          long style, const wxValidator& validator,
120                          const wxString& name )
121 {
122     if (!PreCreation( parent, pos, size ) ||
123         !CreateBase( parent, id, pos, size, style, validator, name ))
124     {
125         wxFAIL_MSG( wxT("wxComboBox creation failed") );
126         return false;
127     }
128 
129     if (HasFlag(wxCB_SORT))
130         m_strings = new wxGtkCollatedArrayString();
131 
132     GTKCreateComboBoxWidget();
133 
134     if (HasFlag(wxBORDER_NONE))
135     {
136         // Doesn't seem to work
137         // g_object_set (m_widget, "has-frame", FALSE, NULL);
138     }
139 
140     GtkEntry * const entry = GetEntry();
141 
142     if ( entry )
143     {
144         // Set it up to trigger default item on enter key press
145         gtk_entry_set_activates_default( entry,
146                                          !HasFlag(wxTE_PROCESS_ENTER) );
147 
148         gtk_editable_set_editable(GTK_EDITABLE(entry), true);
149 #ifdef __WXGTK3__
150         gtk_entry_set_width_chars(entry, 0);
151 #endif
152     }
153 
154     Append(n, choices);
155 
156     m_parent->DoAddChild( this );
157 
158     if ( entry )
159         m_focusWidget = GTK_WIDGET( entry );
160 
161     PostCreation(size);
162 
163     if ( entry )
164     {
165         if (style & wxCB_READONLY)
166         {
167             // this will assert and do nothing if the value is not in our list
168             // of strings which is the desired behaviour (for consistency with
169             // wxMSW and also because it doesn't make sense to have a string
170             // which is not a possible choice in a read-only combobox)
171             SetStringSelection(value);
172             gtk_editable_set_editable(GTK_EDITABLE(entry), false);
173         }
174         else // editable combobox
175         {
176             // any value is accepted, even if it's not in our list
177             gtk_entry_set_text( entry, wxGTK_CONV(value) );
178         }
179 
180         GTKConnectChangedSignal();
181         GTKConnectInsertTextSignal(entry);
182         GTKConnectClipboardSignals(GTK_WIDGET(entry));
183     }
184 
185     g_signal_connect_after (m_widget, "changed",
186                         G_CALLBACK (gtkcombobox_changed_callback), this);
187 
188     if ( wx_is_at_least_gtk2(10) )
189     {
190         g_signal_connect (m_widget, "notify::popup-shown",
191                           G_CALLBACK (gtkcombobox_popupshown_callback), this);
192     }
193 
194     return true;
195 }
196 
GTKCreateComboBoxWidget()197 void wxComboBox::GTKCreateComboBoxWidget()
198 {
199 #ifdef __WXGTK3__
200     m_widget = gtk_combo_box_text_new_with_entry();
201 #else
202     m_widget = gtk_combo_box_entry_new_text();
203 #endif
204     g_object_ref(m_widget);
205 
206     m_entry = GTK_ENTRY(gtk_bin_get_child(GTK_BIN(m_widget)));
207     g_object_add_weak_pointer(G_OBJECT(m_entry), (void**)&m_entry);
208 }
209 
GetEditable() const210 GtkEditable *wxComboBox::GetEditable() const
211 {
212     return GTK_EDITABLE(m_entry);
213 }
214 
OnChar(wxKeyEvent & event)215 void wxComboBox::OnChar( wxKeyEvent &event )
216 {
217     switch ( event.GetKeyCode() )
218     {
219         case WXK_RETURN:
220             if ( HasFlag(wxTE_PROCESS_ENTER) && GetEntry() )
221             {
222                 // GTK automatically selects an item if its in the list
223                 wxCommandEvent eventEnter(wxEVT_TEXT_ENTER, GetId());
224                 eventEnter.SetString( GetValue() );
225                 eventEnter.SetInt( GetSelection() );
226                 eventEnter.SetEventObject( this );
227 
228                 if ( HandleWindowEvent(eventEnter) )
229                 {
230                     // Catch GTK event so that GTK doesn't open the drop
231                     // down list upon RETURN.
232                     return;
233                 }
234 
235                 // We disable built-in default button activation when
236                 // wxTE_PROCESS_ENTER is used, but we still should activate it
237                 // if the event wasn't handled, so do it from here.
238                 if ( ClickDefaultButtonIfPossible() )
239                     return;
240             }
241             break;
242     }
243 
244     event.Skip();
245 }
246 
GTKDisableEvents()247 void wxComboBox::GTKDisableEvents()
248 {
249     EnableTextChangedEvents(false);
250 
251     g_signal_handlers_block_by_func(m_widget,
252         (gpointer)gtkcombobox_changed_callback, this);
253     g_signal_handlers_block_by_func(m_widget,
254         (gpointer)gtkcombobox_popupshown_callback, this);
255 }
256 
GTKEnableEvents()257 void wxComboBox::GTKEnableEvents()
258 {
259     EnableTextChangedEvents(true);
260 
261     g_signal_handlers_unblock_by_func(m_widget,
262         (gpointer)gtkcombobox_changed_callback, this);
263     g_signal_handlers_unblock_by_func(m_widget,
264         (gpointer)gtkcombobox_popupshown_callback, this);
265 }
266 
GetConnectWidget()267 GtkWidget* wxComboBox::GetConnectWidget()
268 {
269     return GTK_WIDGET( GetEntry() );
270 }
271 
GTKGetWindow(wxArrayGdkWindows &) const272 GdkWindow* wxComboBox::GTKGetWindow(wxArrayGdkWindows& /* windows */) const
273 {
274 #ifdef __WXGTK3__
275     return GTKFindWindow(GTK_WIDGET(GetEntry()));
276 #else
277     return gtk_entry_get_text_window(GetEntry());
278 #endif
279 }
280 
281 // static
282 wxVisualAttributes
GetClassDefaultAttributes(wxWindowVariant WXUNUSED (variant))283 wxComboBox::GetClassDefaultAttributes(wxWindowVariant WXUNUSED(variant))
284 {
285 #ifdef __WXGTK3__
286     return GetDefaultAttributesFromGTKWidget(gtk_combo_box_new_with_entry(), true);
287 #else
288     return GetDefaultAttributesFromGTKWidget(gtk_combo_box_entry_new(), true);
289 #endif
290 }
291 
Clear()292 void wxComboBox::Clear()
293 {
294     wxTextEntry::Clear();
295     wxItemContainer::Clear();
296 }
297 
SetValue(const wxString & value)298 void wxComboBox::SetValue(const wxString& value)
299 {
300     if ( HasFlag(wxCB_READONLY) )
301         SetStringSelection(value);
302     else
303         wxTextEntry::SetValue(value);
304 }
305 
SetString(unsigned int n,const wxString & text)306 void wxComboBox::SetString(unsigned int n, const wxString& text)
307 {
308     wxChoice::SetString(n, text);
309 
310     if ( static_cast<int>(n) == GetSelection() )
311     {
312         // We also need to update the currently shown text, for consistency
313         // with wxMSW and also because it makes sense as leaving the old string
314         // in the text but not in the list would be confusing to the user.
315         SetValue(text);
316 
317         // And we need to keep the selection unchanged, modifying the item is
318         // not supposed to deselect it.
319         SetSelection(n);
320     }
321 }
322 
323 // ----------------------------------------------------------------------------
324 // standard event handling
325 // ----------------------------------------------------------------------------
326 
OnCut(wxCommandEvent & WXUNUSED (event))327 void wxComboBox::OnCut(wxCommandEvent& WXUNUSED(event))
328 {
329     Cut();
330 }
331 
OnCopy(wxCommandEvent & WXUNUSED (event))332 void wxComboBox::OnCopy(wxCommandEvent& WXUNUSED(event))
333 {
334     Copy();
335 }
336 
OnPaste(wxCommandEvent & WXUNUSED (event))337 void wxComboBox::OnPaste(wxCommandEvent& WXUNUSED(event))
338 {
339     Paste();
340 }
341 
OnUndo(wxCommandEvent & WXUNUSED (event))342 void wxComboBox::OnUndo(wxCommandEvent& WXUNUSED(event))
343 {
344     Undo();
345 }
346 
OnRedo(wxCommandEvent & WXUNUSED (event))347 void wxComboBox::OnRedo(wxCommandEvent& WXUNUSED(event))
348 {
349     Redo();
350 }
351 
OnDelete(wxCommandEvent & WXUNUSED (event))352 void wxComboBox::OnDelete(wxCommandEvent& WXUNUSED(event))
353 {
354     RemoveSelection();
355 }
356 
OnSelectAll(wxCommandEvent & WXUNUSED (event))357 void wxComboBox::OnSelectAll(wxCommandEvent& WXUNUSED(event))
358 {
359     SelectAll();
360 }
361 
OnUpdateCut(wxUpdateUIEvent & event)362 void wxComboBox::OnUpdateCut(wxUpdateUIEvent& event)
363 {
364     event.Enable( CanCut() );
365 }
366 
OnUpdateCopy(wxUpdateUIEvent & event)367 void wxComboBox::OnUpdateCopy(wxUpdateUIEvent& event)
368 {
369     event.Enable( CanCopy() );
370 }
371 
OnUpdatePaste(wxUpdateUIEvent & event)372 void wxComboBox::OnUpdatePaste(wxUpdateUIEvent& event)
373 {
374     event.Enable( CanPaste() );
375 }
376 
OnUpdateUndo(wxUpdateUIEvent & event)377 void wxComboBox::OnUpdateUndo(wxUpdateUIEvent& event)
378 {
379     event.Enable( CanUndo() );
380 }
381 
OnUpdateRedo(wxUpdateUIEvent & event)382 void wxComboBox::OnUpdateRedo(wxUpdateUIEvent& event)
383 {
384     event.Enable( CanRedo() );
385 }
386 
OnUpdateDelete(wxUpdateUIEvent & event)387 void wxComboBox::OnUpdateDelete(wxUpdateUIEvent& event)
388 {
389     event.Enable(HasSelection() && IsEditable()) ;
390 }
391 
OnUpdateSelectAll(wxUpdateUIEvent & event)392 void wxComboBox::OnUpdateSelectAll(wxUpdateUIEvent& event)
393 {
394     event.Enable(!wxTextEntry::IsEmpty());
395 }
396 
Popup()397 void wxComboBox::Popup()
398 {
399      gtk_combo_box_popup( GTK_COMBO_BOX(m_widget) );
400 }
401 
Dismiss()402 void wxComboBox::Dismiss()
403 {
404     gtk_combo_box_popdown( GTK_COMBO_BOX(m_widget) );
405 }
406 
DoGetSizeFromTextSize(int xlen,int ylen) const407 wxSize wxComboBox::DoGetSizeFromTextSize(int xlen, int ylen) const
408 {
409     wxSize tsize( wxChoice::DoGetSizeFromTextSize(xlen, ylen) );
410 
411     GtkEntry* entry = GetEntry();
412     if (entry)
413     {
414         // Add the margins we have previously set, but only the horizontal border
415         // as vertical one has been taken account in the previous call.
416         // Also get other GTK+ margins.
417         tsize.IncBy(GTKGetEntryMargins(entry).x, 0);
418     }
419 
420     return tsize;
421 }
422 
423 #endif // wxUSE_COMBOBOX
424