1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/gtk/spinctrl.cpp
3 // Purpose: wxSpinCtrl
4 // Author: Robert
5 // Modified by:
6 // Copyright: (c) 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_SPINCTRL
14
15 #include "wx/spinctrl.h"
16
17 #ifndef WX_PRECOMP
18 #include "wx/textctrl.h" // for wxEVT_TEXT
19 #include "wx/math.h" // wxRound()
20 #include "wx/utils.h"
21 #include "wx/wxcrtvararg.h"
22 #endif
23
24 #include "wx/private/spinctrl.h"
25
26 #include "wx/gtk/private.h"
27
28 //-----------------------------------------------------------------------------
29 // data
30 //-----------------------------------------------------------------------------
31
32 extern bool g_blockEventsOnDrag;
33
34 //-----------------------------------------------------------------------------
35 // "value_changed"
36 //-----------------------------------------------------------------------------
37
38 extern "C" {
39 static void
gtk_value_changed(GtkSpinButton * spinbutton,wxSpinCtrlGTKBase * win)40 gtk_value_changed(GtkSpinButton* spinbutton, wxSpinCtrlGTKBase* win)
41 {
42 if (g_blockEventsOnDrag)
43 return;
44
45 if (wxIsKindOf(win, wxSpinCtrl))
46 {
47 wxSpinEvent event(wxEVT_SPINCTRL, win->GetId());
48 event.SetEventObject( win );
49 event.SetPosition(static_cast<wxSpinCtrl*>(win)->GetValue());
50 event.SetString(gtk_entry_get_text(GTK_ENTRY(spinbutton)));
51 win->HandleWindowEvent( event );
52 }
53 else // wxIsKindOf(win, wxSpinCtrlDouble)
54 {
55 wxSpinDoubleEvent event( wxEVT_SPINCTRLDOUBLE, win->GetId());
56 event.SetEventObject( win );
57 event.SetValue(static_cast<wxSpinCtrlDouble*>(win)->GetValue());
58 event.SetString(gtk_entry_get_text(GTK_ENTRY(spinbutton)));
59 win->HandleWindowEvent( event );
60 }
61 }
62 }
63
64 //-----------------------------------------------------------------------------
65 // "changed"
66 //-----------------------------------------------------------------------------
67
68 extern "C" {
69 static void
gtk_changed(GtkSpinButton * spinbutton,wxSpinCtrl * win)70 gtk_changed(GtkSpinButton* spinbutton, wxSpinCtrl* win)
71 {
72 wxCommandEvent event( wxEVT_TEXT, win->GetId() );
73 event.SetEventObject( win );
74 event.SetString(gtk_entry_get_text(GTK_ENTRY(spinbutton)));
75 event.SetInt(win->GetValue());
76 win->HandleWindowEvent( event );
77 }
78 }
79
80 // ----------------------------------------------------------------------------
81 // wxSpinCtrlEventDisabler: helper to temporarily disable GTK+ events
82 // ----------------------------------------------------------------------------
83
84 class wxSpinCtrlEventDisabler
85 {
86 public:
wxSpinCtrlEventDisabler(wxSpinCtrlGTKBase * spin)87 explicit wxSpinCtrlEventDisabler(wxSpinCtrlGTKBase* spin)
88 : m_spin(spin)
89 {
90 m_spin->GtkDisableEvents();
91 }
92
~wxSpinCtrlEventDisabler()93 ~wxSpinCtrlEventDisabler()
94 {
95 m_spin->GtkEnableEvents();
96 }
97
98 private:
99 wxSpinCtrlGTKBase* const m_spin;
100
101 wxDECLARE_NO_COPY_CLASS(wxSpinCtrlEventDisabler);
102 };
103
104 //-----------------------------------------------------------------------------
105 // wxSpinCtrlGTKBase
106 //-----------------------------------------------------------------------------
107
wxBEGIN_EVENT_TABLE(wxSpinCtrlGTKBase,wxSpinCtrlBase)108 wxBEGIN_EVENT_TABLE(wxSpinCtrlGTKBase, wxSpinCtrlBase)
109 EVT_CHAR(wxSpinCtrlGTKBase::OnChar)
110 wxEND_EVENT_TABLE()
111
112 bool wxSpinCtrlGTKBase::Create(wxWindow *parent, wxWindowID id,
113 const wxString& value,
114 const wxPoint& pos, const wxSize& size,
115 long style,
116 double min, double max, double initial, double inc,
117 const wxString& name)
118 {
119 if (!PreCreation( parent, pos, size ) ||
120 !CreateBase( parent, id, pos, size, style, wxDefaultValidator, name ))
121 {
122 wxFAIL_MSG( wxT("wxSpinCtrlGTKBase creation failed") );
123 return false;
124 }
125
126 m_widget = gtk_spin_button_new_with_range(min, max, inc);
127 g_object_ref(m_widget);
128
129 gtk_spin_button_set_value( GTK_SPIN_BUTTON(m_widget), initial);
130
131 gfloat align;
132 if ( HasFlag(wxALIGN_RIGHT) )
133 align = 1.0;
134 else if ( HasFlag(wxALIGN_CENTRE) )
135 align = 0.5;
136 else
137 align = 0.0;
138
139 gtk_entry_set_alignment(GTK_ENTRY(m_widget), align);
140
141 GtkSetEntryWidth();
142
143 gtk_spin_button_set_wrap( GTK_SPIN_BUTTON(m_widget),
144 (int)(m_windowStyle & wxSP_WRAP) );
145
146 g_signal_connect_after(m_widget, "value_changed", G_CALLBACK(gtk_value_changed), this);
147 g_signal_connect_after(m_widget, "changed", G_CALLBACK(gtk_changed), this);
148
149 m_parent->DoAddChild( this );
150
151 PostCreation(size);
152
153 if (!value.empty())
154 {
155 SetValue(value);
156 }
157
158 return true;
159 }
160
DoGetValue() const161 double wxSpinCtrlGTKBase::DoGetValue() const
162 {
163 wxCHECK_MSG( (m_widget != NULL), 0, wxT("invalid spin button") );
164
165 // Get value directly from current control text, just as
166 // gtk_spin_button_update() would do. Calling gtk_spin_button_update() causes
167 // a redraw, which causes an idle event, so if GetValue() is called from
168 // a UI update handler, you get a never ending sequence of idle events. It
169 // also forces the text into valid range, which wxMSW GetValue() does not do.
170 static unsigned sig_id;
171 if (sig_id == 0)
172 sig_id = g_signal_lookup("input", GTK_TYPE_SPIN_BUTTON);
173 double value;
174 int handled = 0;
175 g_signal_emit(m_widget, sig_id, 0, &value, &handled);
176 if (!handled)
177 value = g_strtod(gtk_entry_get_text(GTK_ENTRY(m_widget)), NULL);
178 GtkAdjustment* adj =
179 gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(m_widget));
180 const double lower = gtk_adjustment_get_lower(adj);
181 const double upper = gtk_adjustment_get_upper(adj);
182 if (value < lower)
183 value = lower;
184 else if (value > upper)
185 value = upper;
186
187 return value;
188 }
189
DoGetMin() const190 double wxSpinCtrlGTKBase::DoGetMin() const
191 {
192 wxCHECK_MSG( (m_widget != NULL), 0, wxT("invalid spin button") );
193
194 double min = 0;
195 gtk_spin_button_get_range( GTK_SPIN_BUTTON(m_widget), &min, NULL);
196 return min;
197 }
198
DoGetMax() const199 double wxSpinCtrlGTKBase::DoGetMax() const
200 {
201 wxCHECK_MSG( (m_widget != NULL), 0, wxT("invalid spin button") );
202
203 double max = 0;
204 gtk_spin_button_get_range( GTK_SPIN_BUTTON(m_widget), NULL, &max);
205 return max;
206 }
207
DoGetIncrement() const208 double wxSpinCtrlGTKBase::DoGetIncrement() const
209 {
210 wxCHECK_MSG( (m_widget != NULL), 0, wxT("invalid spin button") );
211
212 double inc = 0;
213 gtk_spin_button_get_increments( GTK_SPIN_BUTTON(m_widget), &inc, NULL);
214 return inc;
215 }
216
GetSnapToTicks() const217 bool wxSpinCtrlGTKBase::GetSnapToTicks() const
218 {
219 wxCHECK_MSG(m_widget, false, "invalid spin button");
220
221 return gtk_spin_button_get_snap_to_ticks( GTK_SPIN_BUTTON(m_widget) ) != 0;
222 }
223
SetValue(const wxString & value)224 void wxSpinCtrlGTKBase::SetValue( const wxString& value )
225 {
226 wxCHECK_RET( (m_widget != NULL), wxT("invalid spin button") );
227
228 double n;
229 if ( wxSscanf(value, "%lg", &n) == 1 )
230 {
231 // a number - set it, let DoSetValue round for int value
232 DoSetValue(n);
233 return;
234 }
235
236 // invalid number - set text as is (wxMSW compatible)
237 wxSpinCtrlEventDisabler disable(this);
238 gtk_entry_set_text( GTK_ENTRY(m_widget), wxGTK_CONV( value ) );
239 }
240
DoSetValue(double value)241 void wxSpinCtrlGTKBase::DoSetValue( double value )
242 {
243 wxCHECK_RET( (m_widget != NULL), wxT("invalid spin button") );
244
245 wxSpinCtrlEventDisabler disable(this);
246 gtk_spin_button_set_value( GTK_SPIN_BUTTON(m_widget), value);
247 }
248
SetSnapToTicks(bool snap_to_ticks)249 void wxSpinCtrlGTKBase::SetSnapToTicks(bool snap_to_ticks)
250 {
251 wxCHECK_RET( (m_widget != NULL), "invalid spin button" );
252
253 gtk_spin_button_set_snap_to_ticks( GTK_SPIN_BUTTON(m_widget), snap_to_ticks);
254 }
255
SetSelection(long from,long to)256 void wxSpinCtrlGTKBase::SetSelection(long from, long to)
257 {
258 // translate from wxWidgets conventions to GTK+ ones: (-1, -1) means the
259 // entire range
260 if ( from == -1 && to == -1 )
261 {
262 from = 0;
263 to = INT_MAX;
264 }
265
266 gtk_editable_select_region( GTK_EDITABLE(m_widget), (gint)from, (gint)to );
267 }
268
DoSetRange(double minVal,double maxVal)269 void wxSpinCtrlGTKBase::DoSetRange(double minVal, double maxVal)
270 {
271 wxCHECK_RET( (m_widget != NULL), wxT("invalid spin button") );
272
273 // Negative values in the range are allowed only if base == 10
274 if ( !wxSpinCtrlImpl::IsBaseCompatibleWithRange(minVal, maxVal, GetBase()) )
275 {
276 return;
277 }
278
279 wxSpinCtrlEventDisabler disable(this);
280 gtk_spin_button_set_range( GTK_SPIN_BUTTON(m_widget), minVal, maxVal);
281
282 InvalidateBestSize();
283
284 GtkSetEntryWidth();
285 }
286
DoSetIncrement(double inc)287 void wxSpinCtrlGTKBase::DoSetIncrement(double inc)
288 {
289 wxCHECK_RET( m_widget, "invalid spin button" );
290
291 wxSpinCtrlEventDisabler disable(this);
292
293 // With GTK2, gtk_spin_button_set_increments() does not emit the GtkAdjustment
294 // "changed" signal, which is needed to properly update the state of the control
295 GtkAdjustment* adj = gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(m_widget));
296 gtk_adjustment_set_step_increment(adj, inc);
297 }
298
GtkDisableEvents()299 void wxSpinCtrlGTKBase::GtkDisableEvents()
300 {
301 g_signal_handlers_block_by_func(m_widget, (void*)gtk_value_changed, this);
302 g_signal_handlers_block_by_func(m_widget, (void*)gtk_changed, this);
303 }
304
GtkEnableEvents()305 void wxSpinCtrlGTKBase::GtkEnableEvents()
306 {
307 g_signal_handlers_unblock_by_func(m_widget, (void*)gtk_value_changed, this);
308 g_signal_handlers_unblock_by_func(m_widget, (void*)gtk_changed, this);
309 }
310
OnChar(wxKeyEvent & event)311 void wxSpinCtrlGTKBase::OnChar( wxKeyEvent &event )
312 {
313 wxCHECK_RET( m_widget != NULL, wxT("invalid spin ctrl") );
314
315 if (event.GetKeyCode() == WXK_RETURN)
316 {
317 wxWindow *top_frame = wxGetTopLevelParent(m_parent);
318
319 if ( GTK_IS_WINDOW(top_frame->m_widget) )
320 {
321 GtkWindow *window = GTK_WINDOW(top_frame->m_widget);
322 if ( window )
323 {
324 GtkWidget* widgetDef = gtk_window_get_default_widget(window);
325
326 if ( widgetDef )
327 {
328 gtk_widget_activate(widgetDef);
329 return;
330 }
331 }
332 }
333 }
334
335 if ((event.GetKeyCode() == WXK_RETURN) && (m_windowStyle & wxTE_PROCESS_ENTER))
336 {
337 wxCommandEvent evt( wxEVT_TEXT_ENTER, m_windowId );
338 evt.SetEventObject(this);
339 GtkSpinButton *gsb = GTK_SPIN_BUTTON(m_widget);
340 wxString val = wxGTK_CONV_BACK( gtk_entry_get_text( &gsb->entry ) );
341 evt.SetString( val );
342 if (HandleWindowEvent(evt)) return;
343 }
344
345 event.Skip();
346 }
347
GTKGetWindow(wxArrayGdkWindows & windows) const348 GdkWindow *wxSpinCtrlGTKBase::GTKGetWindow(wxArrayGdkWindows& windows) const
349 {
350 #ifdef __WXGTK3__
351 GTKFindWindow(m_widget, windows);
352 #else
353 GtkSpinButton* spinbutton = GTK_SPIN_BUTTON(m_widget);
354
355 windows.push_back(spinbutton->entry.text_area);
356 windows.push_back(spinbutton->panel);
357 #endif
358
359 return NULL;
360 }
361
DoGetSizeFromTextSize(int xlen,int ylen) const362 wxSize wxSpinCtrlGTKBase::DoGetSizeFromTextSize(int xlen, int ylen) const
363 {
364 wxASSERT_MSG( m_widget, wxS("GetSizeFromTextSize called before creation") );
365
366 // This is a bit stupid as we typically compute xlen by measuring some
367 // string of digits in the first place, but there doesn't seem to be
368 // anything better to do (unless we add some GetSizeFromNumberOfDigits()).
369 const double widthDigit = GetTextExtent("0123456789").GetWidth() / 10.0;
370 const int numDigits = wxRound(xlen / widthDigit);
371
372 const gint widthChars = gtk_entry_get_width_chars(GTK_ENTRY(m_widget));
373 gtk_entry_set_width_chars(GTK_ENTRY(m_widget), numDigits);
374
375 wxSize tsize = GTKGetPreferredSize(m_widget);
376
377 gtk_entry_set_width_chars(GTK_ENTRY(m_widget), widthChars);
378
379 // Check if the user requested a non-standard height.
380 if ( ylen > 0 )
381 tsize.IncBy(0, ylen - GetCharHeight());
382
383 return tsize;
384 }
385
386 // static
387 wxVisualAttributes
GetClassDefaultAttributes(wxWindowVariant WXUNUSED (variant))388 wxSpinCtrlGTKBase::GetClassDefaultAttributes(wxWindowVariant WXUNUSED(variant))
389 {
390 return GetDefaultAttributesFromGTKWidget(gtk_spin_button_new_with_range(0, 100, 1), true);
391 }
392
393 //-----------------------------------------------------------------------------
394 // wxSpinCtrl
395 //-----------------------------------------------------------------------------
396
397 extern "C"
398 {
399
400 static gboolean
wx_gtk_spin_input(GtkSpinButton * spin,gdouble * val,wxSpinCtrl * win)401 wx_gtk_spin_input(GtkSpinButton* spin, gdouble* val, wxSpinCtrl* win)
402 {
403 // We might use g_ascii_strtoll() here but it's 2.12+ only, so use our own
404 // wxString function even if this requires an extra conversion.
405 const wxString
406 text(wxString::FromUTF8(gtk_entry_get_text(GTK_ENTRY(spin))));
407
408 long lval;
409 if ( !text.ToLong(&lval, win->GetBase()) )
410 return FALSE;
411
412 *val = lval;
413
414 return TRUE;
415 }
416
417 static gint
wx_gtk_spin_output(GtkSpinButton * spin,wxSpinCtrl * win)418 wx_gtk_spin_output(GtkSpinButton* spin, wxSpinCtrl* win)
419 {
420 const gint val = gtk_spin_button_get_value_as_int(spin);
421
422 gtk_entry_set_text
423 (
424 GTK_ENTRY(spin),
425 wxSpinCtrlImpl::FormatAsHex(val, win->GetMax()).utf8_str()
426 );
427
428 return TRUE;
429 }
430
431 } // extern "C"
432
GtkSetEntryWidth()433 void wxSpinCtrl::GtkSetEntryWidth()
434 {
435 const int minVal = static_cast<int>(DoGetMin());
436 const int maxVal = static_cast<int>(DoGetMax());
437
438 gtk_entry_set_width_chars
439 (
440 GTK_ENTRY(m_widget),
441 wxSpinCtrlImpl::GetMaxValueLength(minVal, maxVal, GetBase())
442 );
443 }
444
SetBase(int base)445 bool wxSpinCtrl::SetBase(int base)
446 {
447 // Currently we only support base 10 and 16. We could add support for base
448 // 8 quite easily but wxMSW doesn't support it natively so don't bother
449 // with doing something wxGTK-specific here.
450 if ( base != 10 && base != 16 )
451 return false;
452
453 if ( base == m_base )
454 return true;
455
456 // For negative values in the range only base == 10 is allowed
457 if ( !wxSpinCtrlImpl::IsBaseCompatibleWithRange(static_cast<int>(DoGetMin()), static_cast<int>(DoGetMax()), base) )
458 return false;
459
460 m_base = base;
461
462 // We need to be able to enter letters for any base greater than 10.
463 gtk_spin_button_set_numeric( GTK_SPIN_BUTTON(m_widget), m_base <= 10 );
464
465 if ( m_base != 10 )
466 {
467 g_signal_connect( m_widget, "input",
468 G_CALLBACK(wx_gtk_spin_input), this);
469 g_signal_connect( m_widget, "output",
470 G_CALLBACK(wx_gtk_spin_output), this);
471 }
472 else
473 {
474 g_signal_handlers_disconnect_by_func(m_widget,
475 (gpointer)wx_gtk_spin_input,
476 this);
477 g_signal_handlers_disconnect_by_func(m_widget,
478 (gpointer)wx_gtk_spin_output,
479 this);
480 }
481
482 InvalidateBestSize();
483
484 GtkSetEntryWidth();
485
486 // Update the displayed text after changing the base it uses.
487 SetValue(GetValue());
488
489 return true;
490 }
491
492 //-----------------------------------------------------------------------------
493 // wxSpinCtrlDouble
494 //-----------------------------------------------------------------------------
495
496 wxIMPLEMENT_DYNAMIC_CLASS(wxSpinCtrlDouble, wxSpinCtrlGTKBase);
497
GtkSetEntryWidth()498 void wxSpinCtrlDouble::GtkSetEntryWidth()
499 {
500 const unsigned digits = GetDigits();
501 const int lenMin = wxString::Format("%.*f", digits, GetMin()).length();
502 const int lenMax = wxString::Format("%.*f", digits, GetMax()).length();
503
504 gtk_entry_set_width_chars(GTK_ENTRY(m_widget), wxMax(lenMin, lenMax));
505 }
506
GetDigits() const507 unsigned wxSpinCtrlDouble::GetDigits() const
508 {
509 wxCHECK_MSG( m_widget, 0, "invalid spin button" );
510
511 return gtk_spin_button_get_digits( GTK_SPIN_BUTTON(m_widget) );
512 }
513
SetDigits(unsigned digits)514 void wxSpinCtrlDouble::SetDigits(unsigned digits)
515 {
516 wxCHECK_RET( m_widget, "invalid spin button" );
517
518 wxSpinCtrlEventDisabler disable(this);
519 gtk_spin_button_set_digits( GTK_SPIN_BUTTON(m_widget), digits);
520
521 InvalidateBestSize();
522
523 GtkSetEntryWidth();
524 }
525
526 #endif // wxUSE_SPINCTRL
527