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