1 /////////////////////////////////////////////////////////////////////////////
2 // Name:        src/gtk/spinbutt.cpp
3 // Purpose:     wxSpinButton
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_SPINBTN
14 
15 #include "wx/spinbutt.h"
16 
17 #include "wx/gtk/private/wrapgtk.h"
18 
19 //-----------------------------------------------------------------------------
20 // data
21 //-----------------------------------------------------------------------------
22 
23 extern bool   g_blockEventsOnDrag;
24 
25 //-----------------------------------------------------------------------------
26 // "value_changed"
27 //-----------------------------------------------------------------------------
28 
29 extern "C" {
30 static void
gtk_value_changed(GtkSpinButton * spinbutton,wxSpinButton * win)31 gtk_value_changed(GtkSpinButton* spinbutton, wxSpinButton* win)
32 {
33     const int pos = gtk_spin_button_get_value_as_int(spinbutton);
34     const int oldPos = win->m_pos;
35     if (g_blockEventsOnDrag || pos == oldPos)
36     {
37         win->m_pos = pos;
38         return;
39     }
40 
41     // Normally we can determine which way we're going by just comparing the
42     // old and the new values.
43     bool up = pos > oldPos;
44 
45     // However we need to account for the possibility of wrapping around.
46     if ( win->HasFlag(wxSP_WRAP) )
47     {
48         // We have no way of distinguishing between wraparound and normal
49         // change when the range is just 1, as pressing either arrow results in
50         // the same change, so don't even try doing it in this case.
51         const int spinMin = win->GetMin();
52         const int spinMax = win->GetMax();
53 
54         if ( spinMax - spinMin > 1 )
55         {
56             if ( up )
57             {
58                 if ( oldPos == spinMin && pos == spinMax )
59                     up = false;
60             }
61             else // down
62             {
63                 if ( oldPos == spinMax && pos == spinMin )
64                     up = true;
65             }
66         }
67     }
68 
69     wxSpinEvent event(up ? wxEVT_SCROLL_LINEUP : wxEVT_SCROLL_LINEDOWN, win->GetId());
70     event.SetPosition(pos);
71     event.SetEventObject(win);
72 
73     if ((win->HandleWindowEvent( event )) &&
74         !event.IsAllowed() )
75     {
76         /* program has vetoed */
77         // this will cause another "value_changed" signal,
78         // but because pos == oldPos nothing will happen
79         gtk_spin_button_set_value(spinbutton, oldPos);
80         return;
81     }
82 
83     win->m_pos = pos;
84 
85     /* always send a thumbtrack event */
86     wxSpinEvent event2(wxEVT_SCROLL_THUMBTRACK, win->GetId());
87     event2.SetPosition(pos);
88     event2.SetEventObject(win);
89     win->HandleWindowEvent(event2);
90 }
91 }
92 
93 //-----------------------------------------------------------------------------
94 // wxSpinButton
95 //-----------------------------------------------------------------------------
96 
wxSpinButton()97 wxSpinButton::wxSpinButton()
98 {
99     m_pos = 0;
100 }
101 
Create(wxWindow * parent,wxWindowID id,const wxPoint & pos,const wxSize & size,long style,const wxString & name)102 bool wxSpinButton::Create(wxWindow *parent,
103                           wxWindowID id,
104                           const wxPoint& pos,
105                           const wxSize& size,
106                           long style,
107                           const wxString& name)
108 {
109     if (!PreCreation(parent, pos, size) ||
110         !CreateBase(parent, id, pos, size, style, wxDefaultValidator, name))
111     {
112         wxFAIL_MSG( wxT("wxSpinButton creation failed") );
113         return false;
114     }
115 
116     m_pos = 0;
117 
118     m_widget = gtk_spin_button_new_with_range(0, 100, 1);
119     g_object_ref(m_widget);
120 
121     gtk_entry_set_width_chars(GTK_ENTRY(m_widget), 0);
122 #if GTK_CHECK_VERSION(3,12,0)
123     if (gtk_check_version(3,12,0) == NULL)
124         gtk_entry_set_max_width_chars(GTK_ENTRY(m_widget), 0);
125 #endif
126 #ifdef __WXGTK3__
127     if (gtk_check_version(3,20,0) == NULL)
128     {
129         GTKApplyCssStyle(
130             "entry { min-width:0; padding-left:0; padding-right:0 }"
131             "button.down { border-style:none }");
132     }
133 #endif
134     gtk_spin_button_set_wrap( GTK_SPIN_BUTTON(m_widget),
135                               (int)(m_windowStyle & wxSP_WRAP) );
136 
137     g_signal_connect_after(
138         m_widget, "value_changed", G_CALLBACK(gtk_value_changed), this);
139 
140     m_parent->DoAddChild( this );
141 
142     PostCreation(size);
143 
144     return true;
145 }
146 
GetMin() const147 int wxSpinButton::GetMin() const
148 {
149     wxCHECK_MSG( (m_widget != NULL), 0, wxT("invalid spin button") );
150 
151     double min;
152     gtk_spin_button_get_range((GtkSpinButton*)m_widget, &min, NULL);
153     return int(min);
154 }
155 
GetMax() const156 int wxSpinButton::GetMax() const
157 {
158     wxCHECK_MSG( (m_widget != NULL), 0, wxT("invalid spin button") );
159 
160     double max;
161     gtk_spin_button_get_range((GtkSpinButton*)m_widget, NULL, &max);
162     return int(max);
163 }
164 
GetValue() const165 int wxSpinButton::GetValue() const
166 {
167     wxCHECK_MSG( (m_widget != NULL), 0, wxT("invalid spin button") );
168 
169     return m_pos;
170 }
171 
SetValue(int value)172 void wxSpinButton::SetValue( int value )
173 {
174     wxCHECK_RET( (m_widget != NULL), wxT("invalid spin button") );
175 
176     GtkDisableEvents();
177     gtk_spin_button_set_value((GtkSpinButton*)m_widget, value);
178     m_pos = int(gtk_spin_button_get_value((GtkSpinButton*)m_widget));
179     GtkEnableEvents();
180 }
181 
SetRange(int minVal,int maxVal)182 void wxSpinButton::SetRange(int minVal, int maxVal)
183 {
184     wxCHECK_RET( (m_widget != NULL), wxT("invalid spin button") );
185 
186     GtkDisableEvents();
187     gtk_spin_button_set_range((GtkSpinButton*)m_widget, minVal, maxVal);
188     m_pos = int(gtk_spin_button_get_value((GtkSpinButton*)m_widget));
189 
190     // Use smaller page increment in the case of a narrow range for convenience
191     // and to limit possible up/down ambiguity in gtk_value_changed() when
192     // wrapping is on (The maximal page increment of 10 is consistent with the
193     // default page increment set by gtk_spin_button_new_with_range(0, 100, 1)
194     // in wxSpinButton::Create().)
195     const int range = maxVal - minVal;
196     int pageInc;
197     if ( range < 10 )
198         pageInc = 1;
199     else if ( range < 20 )
200         pageInc = 2;
201     else if ( range < 50 )
202         pageInc = 5;
203     else
204         pageInc = 10;
205 
206     GtkAdjustment* adj = gtk_spin_button_get_adjustment((GtkSpinButton*)m_widget);
207     gtk_adjustment_set_page_increment(adj, pageInc);
208 
209     GtkEnableEvents();
210 }
211 
DoEnable(bool enable)212 void wxSpinButton::DoEnable(bool enable)
213 {
214     if ( !m_widget )
215         return;
216 
217     base_type::DoEnable(enable);
218 
219     // Work around lack of visual update when enabling
220     if (enable)
221         GTKFixSensitivity(false /* fix even if not under mouse */);
222 }
223 
GtkDisableEvents() const224 void wxSpinButton::GtkDisableEvents() const
225 {
226     g_signal_handlers_block_by_func(m_widget,
227         (void*)gtk_value_changed, const_cast<wxSpinButton*>(this));
228 }
229 
GtkEnableEvents() const230 void wxSpinButton::GtkEnableEvents() const
231 {
232     g_signal_handlers_unblock_by_func(m_widget,
233         (void*)gtk_value_changed, const_cast<wxSpinButton*>(this));
234 }
235 
GTKGetWindow(wxArrayGdkWindows & WXUNUSED_IN_GTK2 (windows)) const236 GdkWindow *wxSpinButton::GTKGetWindow(wxArrayGdkWindows& WXUNUSED_IN_GTK2(windows)) const
237 {
238 #ifdef __WXGTK3__
239     GTKFindWindow(m_widget, windows);
240     return NULL;
241 #else
242     return GTK_SPIN_BUTTON(m_widget)->panel;
243 #endif
244 }
245 
DoGetBestSize() const246 wxSize wxSpinButton::DoGetBestSize() const
247 {
248     wxSize best = base_type::DoGetBestSize();
249 #ifdef __WXGTK3__
250     GtkStyleContext* sc = gtk_widget_get_style_context(m_widget);
251     GtkBorder pad = { 0, 0, 0, 0 };
252     gtk_style_context_get_padding(sc, gtk_style_context_get_state(sc), &pad);
253     best.x -= pad.left + pad.right;
254 #else
255     gtk_widget_ensure_style(m_widget);
256     int w = PANGO_PIXELS(pango_font_description_get_size(m_widget->style->font_desc));
257     w &= ~1;
258     if (w < 6)
259         w = 6;
260     best.x = w + 2 * m_widget->style->xthickness;
261 #endif
262     return best;
263 }
264 
265 // static
266 wxVisualAttributes
GetClassDefaultAttributes(wxWindowVariant WXUNUSED (variant))267 wxSpinButton::GetClassDefaultAttributes(wxWindowVariant WXUNUSED(variant))
268 {
269     return GetDefaultAttributesFromGTKWidget(gtk_spin_button_new_with_range(0, 100, 1));
270 }
271 
272 #endif // wxUSE_SPINBTN
273