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