Improve default size calc of wxComboBox
[wxWidgets.git] / src / gtk / combobox.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/gtk/combobox.cpp
3 // Purpose:
4 // Author: Robert Roebling
5 // Id: $Id$
6 // Copyright: (c) 1998 Robert Roebling
7 // Licence: wxWindows licence
8 /////////////////////////////////////////////////////////////////////////////
9
10 // For compilers that support precompilation, includes "wx.h".
11 #include "wx/wxprec.h"
12
13 #if wxUSE_COMBOBOX
14
15 #include "wx/combobox.h"
16
17 #ifndef WX_PRECOMP
18 #include "wx/intl.h"
19 #include "wx/settings.h"
20 #include "wx/textctrl.h" // for wxEVT_COMMAND_TEXT_UPDATED
21 #include "wx/arrstr.h"
22 #endif
23
24 #include "wx/gtk/private.h"
25
26 // ----------------------------------------------------------------------------
27 // GTK callbacks
28 // ----------------------------------------------------------------------------
29
30 extern "C" {
31 static void
32 gtkcombobox_text_changed_callback( GtkWidget *WXUNUSED(widget), wxComboBox *combo )
33 {
34 if (!combo->m_hasVMT) return;
35
36 wxCommandEvent event( wxEVT_COMMAND_TEXT_UPDATED, combo->GetId() );
37 event.SetString( combo->GetValue() );
38 event.SetEventObject( combo );
39 combo->HandleWindowEvent( event );
40 }
41
42 static void
43 gtkcombobox_changed_callback( GtkWidget *WXUNUSED(widget), wxComboBox *combo )
44 {
45 if (!combo->m_hasVMT) return;
46
47 if (combo->GetSelection() == -1)
48 return;
49
50 wxCommandEvent event( wxEVT_COMMAND_COMBOBOX_SELECTED, combo->GetId() );
51 event.SetInt( combo->GetSelection() );
52 event.SetString( combo->GetStringSelection() );
53 event.SetEventObject( combo );
54 combo->HandleWindowEvent( event );
55 }
56 }
57
58 //-----------------------------------------------------------------------------
59 // wxComboBox
60 //-----------------------------------------------------------------------------
61
62 IMPLEMENT_DYNAMIC_CLASS(wxComboBox,wxControl)
63
64 BEGIN_EVENT_TABLE(wxComboBox, wxControl)
65 EVT_CHAR(wxComboBox::OnChar)
66
67 EVT_MENU(wxID_CUT, wxComboBox::OnCut)
68 EVT_MENU(wxID_COPY, wxComboBox::OnCopy)
69 EVT_MENU(wxID_PASTE, wxComboBox::OnPaste)
70 EVT_MENU(wxID_UNDO, wxComboBox::OnUndo)
71 EVT_MENU(wxID_REDO, wxComboBox::OnRedo)
72 EVT_MENU(wxID_CLEAR, wxComboBox::OnDelete)
73 EVT_MENU(wxID_SELECTALL, wxComboBox::OnSelectAll)
74
75 EVT_UPDATE_UI(wxID_CUT, wxComboBox::OnUpdateCut)
76 EVT_UPDATE_UI(wxID_COPY, wxComboBox::OnUpdateCopy)
77 EVT_UPDATE_UI(wxID_PASTE, wxComboBox::OnUpdatePaste)
78 EVT_UPDATE_UI(wxID_UNDO, wxComboBox::OnUpdateUndo)
79 EVT_UPDATE_UI(wxID_REDO, wxComboBox::OnUpdateRedo)
80 EVT_UPDATE_UI(wxID_CLEAR, wxComboBox::OnUpdateDelete)
81 EVT_UPDATE_UI(wxID_SELECTALL, wxComboBox::OnUpdateSelectAll)
82 END_EVENT_TABLE()
83
84 bool wxComboBox::Create( wxWindow *parent, wxWindowID id,
85 const wxString& value,
86 const wxPoint& pos, const wxSize& size,
87 const wxArrayString& choices,
88 long style, const wxValidator& validator,
89 const wxString& name )
90 {
91 wxCArrayString chs(choices);
92
93 return Create( parent, id, value, pos, size, chs.GetCount(),
94 chs.GetStrings(), style, validator, name );
95 }
96
97 bool wxComboBox::Create( wxWindow *parent, wxWindowID id, const wxString& value,
98 const wxPoint& pos, const wxSize& size,
99 int n, const wxString choices[],
100 long style, const wxValidator& validator,
101 const wxString& name )
102 {
103 m_strings = NULL;
104 m_ignoreNextUpdate = false;
105 m_prevSelection = 0;
106
107 if (!PreCreation( parent, pos, size ) ||
108 !CreateBase( parent, id, pos, size, style, validator, name ))
109 {
110 wxFAIL_MSG( wxT("wxComboBox creation failed") );
111 return false;
112 }
113
114 if (HasFlag(wxCB_SORT))
115 m_strings = new wxSortedArrayString();
116
117 m_widget = gtk_combo_box_entry_new_text();
118
119 if (HasFlag(wxBORDER_NONE))
120 {
121 // Doesn't seem to work
122 // g_object_set (m_widget, "has-frame", FALSE, NULL);
123 }
124
125 GtkEntry * const entry = GetEntry();
126
127 gtk_entry_set_editable( entry, TRUE );
128
129 Append(n, choices);
130
131 m_parent->DoAddChild( this );
132
133 m_focusWidget = GTK_WIDGET( entry );
134
135 PostCreation(size);
136
137 ConnectWidget( m_widget );
138
139 gtk_entry_set_text( entry, wxGTK_CONV(value) );
140
141 if (style & wxCB_READONLY)
142 gtk_entry_set_editable( entry, FALSE );
143
144 g_signal_connect_after (entry, "changed",
145 G_CALLBACK (gtkcombobox_text_changed_callback), this);
146
147 g_signal_connect_after (m_widget, "changed",
148 G_CALLBACK (gtkcombobox_changed_callback), this);
149
150
151 SetInitialSize(size); // need this too because this is a wxControlWithItems
152
153 return true;
154 }
155
156 GtkEntry *wxComboBox::GetEntry() const
157 {
158 return GTK_ENTRY(GTK_BIN(m_widget)->child);
159 }
160
161 GtkEditable *wxComboBox::GetEditable() const
162 {
163 return GTK_EDITABLE( GTK_BIN(m_widget)->child );
164 }
165
166 wxComboBox::~wxComboBox()
167 {
168 Clear();
169
170 delete m_strings;
171 }
172
173 int wxComboBox::DoInsertItems(const wxArrayStringsAdapter & items,
174 unsigned int pos,
175 void **clientData, wxClientDataType type)
176 {
177 wxCHECK_MSG( m_widget != NULL, -1, wxT("invalid combobox") );
178
179 wxASSERT_MSG( !IsSorted() || (pos == GetCount()),
180 _T("In a sorted combobox data could only be appended"));
181
182 const int count = items.GetCount();
183
184 int n = wxNOT_FOUND;
185
186 GtkComboBox* combobox = GTK_COMBO_BOX( m_widget );
187 for( int i = 0; i < count; ++i )
188 {
189 n = pos + i;
190 // If sorted, use this wxSortedArrayStrings to determine
191 // the right insertion point
192 if(m_strings)
193 n = m_strings->Add(items[i]);
194
195 gtk_combo_box_insert_text( combobox, n, wxGTK_CONV( items[i] ) );
196
197 m_clientData.Insert( NULL, n );
198 AssignNewItemClientData(n, clientData, i, type);
199 }
200
201 InvalidateBestSize();
202
203 return n;
204 }
205
206 void wxComboBox::DoSetItemClientData(unsigned int n, void* clientData)
207 {
208 m_clientData[n] = clientData;
209 }
210
211 void* wxComboBox::DoGetItemClientData(unsigned int n) const
212 {
213 return m_clientData[n];
214 }
215
216 void wxComboBox::DoClear()
217 {
218 wxCHECK_RET( m_widget != NULL, wxT("invalid combobox") );
219
220 DisableEvents();
221
222 GtkComboBox* combobox = GTK_COMBO_BOX( m_widget );
223 const unsigned int count = GetCount();
224 for (unsigned int i = 0; i < count; i++)
225 gtk_combo_box_remove_text( combobox, 0 );
226
227 m_clientData.Clear();
228
229 if(m_strings)
230 m_strings->Clear();
231
232 EnableEvents();
233
234 InvalidateBestSize();
235 }
236
237 void wxComboBox::DoDeleteOneItem(unsigned int n)
238 {
239 wxCHECK_RET( m_widget != NULL, wxT("invalid combobox") );
240
241 wxCHECK_RET( IsValid(n), wxT("invalid index") );
242
243 GtkComboBox* combobox = GTK_COMBO_BOX( m_widget );
244 gtk_combo_box_remove_text( combobox, n );
245
246 m_clientData.RemoveAt( n );
247 if(m_strings)
248 m_strings->RemoveAt( n );
249
250 InvalidateBestSize();
251 }
252
253 void wxComboBox::SetString(unsigned int n, const wxString &text)
254 {
255 wxCHECK_RET( m_widget != NULL, wxT("invalid combobox") );
256
257 GtkComboBox* combobox = GTK_COMBO_BOX( m_widget );
258 wxCHECK_RET( IsValid(n), wxT("invalid index") );
259
260 GtkTreeModel *model = gtk_combo_box_get_model( combobox );
261 GtkTreeIter iter;
262 if (gtk_tree_model_iter_nth_child (model, &iter, NULL, n))
263 {
264 GValue value = { 0, };
265 g_value_init( &value, G_TYPE_STRING );
266 g_value_set_string( &value, wxGTK_CONV( text ) );
267 gtk_list_store_set_value( GTK_LIST_STORE(model), &iter, 0, &value );
268 g_value_unset( &value );
269 }
270
271 InvalidateBestSize();
272 }
273
274 int wxComboBox::FindString( const wxString &item, bool bCase ) const
275 {
276 wxCHECK_MSG( m_widget != NULL, wxNOT_FOUND, wxT("invalid combobox") );
277
278 GtkComboBox* combobox = GTK_COMBO_BOX( m_widget );
279 GtkTreeModel* model = gtk_combo_box_get_model( combobox );
280 GtkTreeIter iter;
281 gtk_tree_model_get_iter_first( model, &iter );
282 if (!gtk_list_store_iter_is_valid(GTK_LIST_STORE(model), &iter ))
283 return -1;
284 int count = 0;
285 do
286 {
287 GValue value = { 0, };
288 gtk_tree_model_get_value( model, &iter, 0, &value );
289 wxString str = wxGTK_CONV_BACK( g_value_get_string( &value ) );
290 g_value_unset( &value );
291
292 if (item.IsSameAs( str, bCase ) )
293 return count;
294
295 count++;
296 }
297 while ( gtk_tree_model_iter_next(model, &iter) );
298
299 return wxNOT_FOUND;
300 }
301
302 int wxComboBox::GetSelection() const
303 {
304 GtkComboBox* combobox = GTK_COMBO_BOX( m_widget );
305 return gtk_combo_box_get_active( combobox );
306 }
307
308 int wxComboBox::GetCurrentSelection() const
309 {
310 wxCHECK_MSG( m_widget != NULL, -1, wxT("invalid combobox") );
311
312 GtkComboBox* combobox = GTK_COMBO_BOX( m_widget );
313 return gtk_combo_box_get_active( combobox );
314 }
315
316 wxString wxComboBox::GetString(unsigned int n) const
317 {
318 wxCHECK_MSG( m_widget != NULL, wxEmptyString, wxT("invalid combobox") );
319
320 wxString str;
321
322 GtkComboBox* combobox = GTK_COMBO_BOX( m_widget );
323 GtkTreeModel *model = gtk_combo_box_get_model( combobox );
324 GtkTreeIter iter;
325 if (gtk_tree_model_iter_nth_child (model, &iter, NULL, n))
326 {
327 GValue value = { 0, };
328 gtk_tree_model_get_value( model, &iter, 0, &value );
329 wxString tmp = wxGTK_CONV_BACK( g_value_get_string( &value ) );
330 g_value_unset( &value );
331 return tmp;
332 }
333
334 return str;
335 }
336
337 unsigned int wxComboBox::GetCount() const
338 {
339 wxCHECK_MSG( m_widget != NULL, 0, wxT("invalid combobox") );
340
341 GtkComboBox* combobox = GTK_COMBO_BOX( m_widget );
342 GtkTreeModel* model = gtk_combo_box_get_model( combobox );
343 GtkTreeIter iter;
344 gtk_tree_model_get_iter_first( model, &iter );
345 if (!gtk_list_store_iter_is_valid(GTK_LIST_STORE(model), &iter ))
346 return 0;
347 unsigned int ret = 1;
348 while (gtk_tree_model_iter_next( model, &iter ))
349 ret++;
350 return ret;
351 }
352
353 void wxComboBox::SetSelection( int n )
354 {
355 wxCHECK_RET( m_widget != NULL, wxT("invalid combobox") );
356
357 DisableEvents();
358
359 GtkComboBox* combobox = GTK_COMBO_BOX( m_widget );
360 gtk_combo_box_set_active( combobox, n );
361
362 EnableEvents();
363 }
364
365 void wxComboBox::OnChar( wxKeyEvent &event )
366 {
367 switch ( event.GetKeyCode() )
368 {
369 case WXK_RETURN:
370 if ( HasFlag(wxTE_PROCESS_ENTER) )
371 {
372 // GTK automatically selects an item if its in the list
373 wxCommandEvent eventEnter(wxEVT_COMMAND_TEXT_ENTER, GetId());
374 eventEnter.SetString( GetValue() );
375 eventEnter.SetInt( GetSelection() );
376 eventEnter.SetEventObject( this );
377
378 if ( HandleWindowEvent(eventEnter) )
379 {
380 // Catch GTK event so that GTK doesn't open the drop
381 // down list upon RETURN.
382 return;
383 }
384 }
385
386 // On enter key press, we must give a signal to default control,
387 // Otherwise, nothing happens when pressing Enter from inside a
388 // combo box in a dialog.
389 wxWindow *top_frame = wxGetTopLevelParent(this);
390 if( top_frame && GTK_IS_WINDOW(top_frame->m_widget) )
391 {
392 GtkWindow *window = GTK_WINDOW(top_frame->m_widget);
393 if ( window->default_widget )
394 gtk_widget_activate( window->default_widget );
395 }
396 break;
397 }
398
399 event.Skip();
400 }
401
402 void wxComboBox::DisableEvents()
403 {
404 g_signal_handlers_block_by_func(GTK_BIN(m_widget)->child,
405 (gpointer)gtkcombobox_text_changed_callback, this);
406
407 g_signal_handlers_block_by_func(m_widget,
408 (gpointer)gtkcombobox_changed_callback, this);
409 }
410
411 void wxComboBox::EnableEvents()
412 {
413 g_signal_handlers_unblock_by_func(GTK_BIN(m_widget)->child,
414 (gpointer)gtkcombobox_text_changed_callback, this);
415
416 g_signal_handlers_unblock_by_func(m_widget,
417 (gpointer)gtkcombobox_changed_callback, this);
418 }
419
420 GtkWidget* wxComboBox::GetConnectWidget()
421 {
422 return GTK_WIDGET( GetEntry() );
423 }
424
425 GdkWindow *wxComboBox::GTKGetWindow(wxArrayGdkWindows& windows) const
426 {
427 wxUnusedVar(windows);
428
429 return GetEntry()->text_area;
430 }
431
432 wxSize wxComboBox::DoGetBestSize() const
433 {
434 // strangely, this returns a width of 188 pixels from GTK+ (?)
435 wxSize ret( wxControl::DoGetBestSize() );
436
437 // we know better our horizontal extent: it depends on the longest string
438 // in the combobox
439 if ( m_widget )
440 {
441 ret.x = 60; // start with something "sensible"
442 int width;
443 unsigned int count = GetCount();
444 for ( unsigned int n = 0; n < count; n++ )
445 {
446 GetTextExtent(GetString(n), &width, NULL, NULL, NULL );
447 if ( width + 40 > ret.x ) // 40 for drop down arrow and space around text
448 ret.x = width + 40;
449 }
450 }
451
452 // empty combobox should have some reasonable default size too
453 if ((GetCount() == 0) && (ret.x < 80))
454 ret.x = 80;
455
456 CacheBestSize(ret);
457 return ret;
458 }
459
460 // static
461 wxVisualAttributes
462 wxComboBox::GetClassDefaultAttributes(wxWindowVariant WXUNUSED(variant))
463 {
464 return GetDefaultAttributesFromGTKWidget(gtk_combo_box_entry_new, true);
465 }
466
467 // ----------------------------------------------------------------------------
468 // standard event handling
469 // ----------------------------------------------------------------------------
470
471 void wxComboBox::OnCut(wxCommandEvent& WXUNUSED(event))
472 {
473 Cut();
474 }
475
476 void wxComboBox::OnCopy(wxCommandEvent& WXUNUSED(event))
477 {
478 Copy();
479 }
480
481 void wxComboBox::OnPaste(wxCommandEvent& WXUNUSED(event))
482 {
483 Paste();
484 }
485
486 void wxComboBox::OnUndo(wxCommandEvent& WXUNUSED(event))
487 {
488 Undo();
489 }
490
491 void wxComboBox::OnRedo(wxCommandEvent& WXUNUSED(event))
492 {
493 Redo();
494 }
495
496 void wxComboBox::OnDelete(wxCommandEvent& WXUNUSED(event))
497 {
498 RemoveSelection();
499 }
500
501 void wxComboBox::OnSelectAll(wxCommandEvent& WXUNUSED(event))
502 {
503 SelectAll();
504 }
505
506 void wxComboBox::OnUpdateCut(wxUpdateUIEvent& event)
507 {
508 event.Enable( CanCut() );
509 }
510
511 void wxComboBox::OnUpdateCopy(wxUpdateUIEvent& event)
512 {
513 event.Enable( CanCopy() );
514 }
515
516 void wxComboBox::OnUpdatePaste(wxUpdateUIEvent& event)
517 {
518 event.Enable( CanPaste() );
519 }
520
521 void wxComboBox::OnUpdateUndo(wxUpdateUIEvent& event)
522 {
523 event.Enable( CanUndo() );
524 }
525
526 void wxComboBox::OnUpdateRedo(wxUpdateUIEvent& event)
527 {
528 event.Enable( CanRedo() );
529 }
530
531 void wxComboBox::OnUpdateDelete(wxUpdateUIEvent& event)
532 {
533 event.Enable(HasSelection() && IsEditable()) ;
534 }
535
536 void wxComboBox::OnUpdateSelectAll(wxUpdateUIEvent& event)
537 {
538 event.Enable(!wxTextEntry::IsEmpty());
539 }
540
541 #endif // wxUSE_COMBOBOX