gtk slider code simplification and better event determination
[wxWidgets.git] / src / gtk / slider.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/gtk/slider.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_SLIDER
14
15 #include "wx/slider.h"
16
17 #ifndef WX_PRECOMP
18 #include "wx/utils.h"
19 #endif
20
21 #include "wx/gtk/private.h"
22
23 //-----------------------------------------------------------------------------
24 // data
25 //-----------------------------------------------------------------------------
26
27 extern bool g_blockEventsOnDrag;
28
29 // ----------------------------------------------------------------------------
30 // helper functions
31 // ----------------------------------------------------------------------------
32
33 // process a scroll event
34 static void
35 ProcessScrollEvent(wxSlider *win, wxEventType evtType)
36 {
37 const int orient = win->HasFlag(wxSL_VERTICAL) ? wxVERTICAL
38 : wxHORIZONTAL;
39
40 const int value = win->GetValue();
41
42 // if we have any "special" event (i.e. the value changed by a line or a
43 // page), send this specific event first
44 if ( evtType != wxEVT_NULL )
45 {
46 wxScrollEvent event( evtType, win->GetId(), value, orient );
47 event.SetEventObject( win );
48 win->GetEventHandler()->ProcessEvent( event );
49 }
50
51 // but, in any case, except if we're dragging the slider (and so the change
52 // is not definitive), send a generic "changed" event
53 if ( evtType != wxEVT_SCROLL_THUMBTRACK )
54 {
55 wxScrollEvent event(wxEVT_SCROLL_CHANGED, win->GetId(), value, orient);
56 event.SetEventObject( win );
57 win->GetEventHandler()->ProcessEvent( event );
58 }
59
60 // and also generate a command event for compatibility
61 wxCommandEvent event( wxEVT_COMMAND_SLIDER_UPDATED, win->GetId() );
62 event.SetEventObject( win );
63 event.SetInt( value );
64 win->GetEventHandler()->ProcessEvent( event );
65 }
66
67 static inline wxEventType GtkScrollTypeToWx(int scrollType)
68 {
69 wxEventType eventType;
70 switch (scrollType)
71 {
72 case GTK_SCROLL_STEP_BACKWARD:
73 case GTK_SCROLL_STEP_LEFT:
74 case GTK_SCROLL_STEP_UP:
75 eventType = wxEVT_SCROLL_LINEUP;
76 break;
77 case GTK_SCROLL_STEP_DOWN:
78 case GTK_SCROLL_STEP_FORWARD:
79 case GTK_SCROLL_STEP_RIGHT:
80 eventType = wxEVT_SCROLL_LINEDOWN;
81 break;
82 case GTK_SCROLL_PAGE_BACKWARD:
83 case GTK_SCROLL_PAGE_LEFT:
84 case GTK_SCROLL_PAGE_UP:
85 eventType = wxEVT_SCROLL_PAGEUP;
86 break;
87 case GTK_SCROLL_PAGE_DOWN:
88 case GTK_SCROLL_PAGE_FORWARD:
89 case GTK_SCROLL_PAGE_RIGHT:
90 eventType = wxEVT_SCROLL_PAGEDOWN;
91 break;
92 case GTK_SCROLL_START:
93 eventType = wxEVT_SCROLL_TOP;
94 break;
95 case GTK_SCROLL_END:
96 eventType = wxEVT_SCROLL_BOTTOM;
97 break;
98 case GTK_SCROLL_JUMP:
99 eventType = wxEVT_SCROLL_THUMBTRACK;
100 break;
101 default:
102 wxFAIL_MSG(_T("Unknown GtkScrollType"));
103 eventType = wxEVT_NULL;
104 break;
105 }
106 return eventType;
107 }
108
109 // Determine if increment is the same as +/-x, allowing for some small
110 // difference due to possible inexactness in floating point arithmetic
111 static inline bool IsScrollIncrement(double increment, double x)
112 {
113 wxASSERT(increment > 0);
114 const double tolerance = 1.0 / 1024;
115 return fabs(increment - fabs(x)) < tolerance;
116 }
117
118 //-----------------------------------------------------------------------------
119 // "value_changed"
120 //-----------------------------------------------------------------------------
121
122 extern "C" {
123 static void
124 gtk_value_changed(GtkRange* range, wxSlider* win)
125 {
126 if (g_isIdle) wxapp_install_idle_handler();
127
128 if (!win->m_hasVMT) return;
129 if (g_blockEventsOnDrag) return;
130
131 GtkAdjustment* adj = range->adjustment;
132 const int pos = int(round(adj->value));
133 const double oldPos = win->m_pos;
134 win->m_pos = adj->value;
135 if (win->m_blockScrollEvent)
136 {
137 win->m_scrollEventType = GTK_SCROLL_NONE;
138 return;
139 }
140
141 wxEventType eventType = wxEVT_NULL;
142 if (win->m_isScrolling)
143 {
144 eventType = wxEVT_SCROLL_THUMBTRACK;
145 }
146 else if (win->m_scrollEventType != GTK_SCROLL_NONE)
147 {
148 // Scroll event from "move-slider" (keyboard)
149 eventType = GtkScrollTypeToWx(win->m_scrollEventType);
150 }
151 else if (win->m_mouseButtonDown)
152 {
153 // Difference from last change event
154 const double diff = adj->value - oldPos;
155 const bool isDown = diff > 0;
156
157 if (IsScrollIncrement(adj->page_increment, diff))
158 {
159 eventType = isDown ? wxEVT_SCROLL_PAGEDOWN : wxEVT_SCROLL_PAGEUP;
160 }
161 else if (wxIsSameDouble(adj->value, 0))
162 {
163 eventType = wxEVT_SCROLL_PAGEUP;
164 }
165 else if (wxIsSameDouble(adj->value, adj->upper))
166 {
167 eventType = wxEVT_SCROLL_PAGEDOWN;
168 }
169 else
170 {
171 // Assume track event
172 eventType = wxEVT_SCROLL_THUMBTRACK;
173 // Remember that we're tracking
174 win->m_isScrolling = true;
175 }
176 }
177
178 win->m_scrollEventType = GTK_SCROLL_NONE;
179
180 // If integral position has changed
181 if (int(round(oldPos)) != pos)
182 {
183 wxCHECK_RET(eventType != wxEVT_NULL, _T("Unknown slider scroll event type"));
184 ProcessScrollEvent(win, eventType);
185 win->m_needThumbRelease = eventType == wxEVT_SCROLL_THUMBTRACK;
186 }
187 }
188 }
189
190 //-----------------------------------------------------------------------------
191 // "move_slider" (keyboard event)
192 //-----------------------------------------------------------------------------
193
194 extern "C" {
195 static void
196 gtk_move_slider(GtkRange*, GtkScrollType scrollType, wxSlider* win)
197 {
198 // Save keyboard scroll type for "value_changed" handler
199 win->m_scrollEventType = scrollType;
200 }
201 }
202
203 //-----------------------------------------------------------------------------
204 // "button_press_event"
205 //-----------------------------------------------------------------------------
206
207 extern "C" {
208 static gboolean
209 gtk_button_press_event(GtkWidget*, GdkEventButton*, wxSlider* win)
210 {
211 win->m_mouseButtonDown = true;
212
213 return false;
214 }
215 }
216
217 // Single shot idle callback, to generate thumb release event, and
218 // truncate position to integral value. Idle callback is used
219 // because position cannot be changed from button release event.
220 extern "C" {
221 static gboolean
222 idle_thumbrelease(void* data)
223 {
224 wxSlider* win = (wxSlider*)data;
225 win->m_isScrolling = false;
226 if (win->m_needThumbRelease)
227 {
228 win->m_needThumbRelease = false;
229 ProcessScrollEvent(win, wxEVT_SCROLL_THUMBRELEASE);
230 }
231 // Keep slider at an integral position
232 win->BlockScrollEvent();
233 gtk_range_set_value((GtkRange*)win->m_widget, win->GetValue());
234 win->UnblockScrollEvent();
235 return false;
236 }
237 }
238
239 //-----------------------------------------------------------------------------
240 // "button_release_event"
241 //-----------------------------------------------------------------------------
242
243 extern "C" {
244 static gboolean
245 gtk_button_release_event(GtkWidget*, GdkEventButton*, wxSlider* win)
246 {
247 win->m_mouseButtonDown = false;
248 if (win->m_isScrolling)
249 {
250 g_idle_add(idle_thumbrelease, win);
251 }
252 return false;
253 }
254 }
255
256 //-----------------------------------------------------------------------------
257 // "format_value"
258 //-----------------------------------------------------------------------------
259
260 extern "C" {
261 static gchar* gtk_format_value(GtkScale*, double value, void*)
262 {
263 // Format value as nearest integer
264 return g_strdup_printf("%d", int(round(value)));
265 }
266 }
267
268 //-----------------------------------------------------------------------------
269 // wxSlider
270 //-----------------------------------------------------------------------------
271
272 IMPLEMENT_DYNAMIC_CLASS(wxSlider,wxControl)
273
274 wxSlider::wxSlider()
275 {
276 m_pos = 0;
277 m_scrollEventType = 0;
278 m_needThumbRelease = false;
279 }
280
281 bool wxSlider::Create(wxWindow *parent, wxWindowID id,
282 int value, int minValue, int maxValue,
283 const wxPoint& pos, const wxSize& size,
284 long style, const wxValidator& validator, const wxString& name )
285 {
286 m_acceptsFocus = true;
287 m_needParent = true;
288
289 if (!PreCreation( parent, pos, size ) ||
290 !CreateBase( parent, id, pos, size, style, validator, name ))
291 {
292 wxFAIL_MSG( wxT("wxSlider creation failed") );
293 return false;
294 }
295
296 m_pos = 0;
297 m_scrollEventType = 0;
298 m_needThumbRelease = false;
299
300 if (style & wxSL_VERTICAL)
301 m_widget = gtk_vscale_new( (GtkAdjustment *) NULL );
302 else
303 m_widget = gtk_hscale_new( (GtkAdjustment *) NULL );
304
305 gtk_scale_set_draw_value((GtkScale*)m_widget, (style & wxSL_LABELS) != 0);
306 // Keep full precision in position value
307 gtk_scale_set_digits((GtkScale*)m_widget, -1);
308
309 if (style & wxSL_INVERSE)
310 gtk_range_set_inverted( GTK_RANGE(m_widget), TRUE );
311
312 g_signal_connect(m_widget, "button_press_event", G_CALLBACK(gtk_button_press_event), this);
313 g_signal_connect(m_widget, "button_release_event", G_CALLBACK(gtk_button_release_event), this);
314 g_signal_connect(m_widget, "move_slider", G_CALLBACK(gtk_move_slider), this);
315 g_signal_connect(m_widget, "format_value", G_CALLBACK(gtk_format_value), NULL);
316 g_signal_connect(m_widget, "value_changed", G_CALLBACK(gtk_value_changed), this);
317
318 SetRange( minValue, maxValue );
319 SetValue( value );
320
321 m_parent->DoAddChild( this );
322
323 PostCreation(size);
324
325 return true;
326 }
327
328 int wxSlider::GetValue() const
329 {
330 return int(round(m_pos));
331 }
332
333 void wxSlider::SetValue( int value )
334 {
335 if (GetValue() != value)
336 {
337 BlockScrollEvent();
338 gtk_range_set_value((GtkRange*)m_widget, value);
339 UnblockScrollEvent();
340 }
341 }
342
343 void wxSlider::SetRange( int minValue, int maxValue )
344 {
345 BlockScrollEvent();
346 gtk_range_set_range((GtkRange*)m_widget, minValue, maxValue);
347 gtk_range_set_increments((GtkRange*)m_widget, 1, (maxValue - minValue + 9) / 10);
348 UnblockScrollEvent();
349 }
350
351 int wxSlider::GetMin() const
352 {
353 return int(((GtkRange*)m_widget)->adjustment->lower);
354 }
355
356 int wxSlider::GetMax() const
357 {
358 return int(((GtkRange*)m_widget)->adjustment->upper);
359 }
360
361 void wxSlider::SetPageSize( int pageSize )
362 {
363 BlockScrollEvent();
364 gtk_range_set_increments((GtkRange*)m_widget, 1, pageSize);
365 UnblockScrollEvent();
366 }
367
368 int wxSlider::GetPageSize() const
369 {
370 return int(((GtkRange*)m_widget)->adjustment->page_increment);
371 }
372
373 // GTK does not support changing the size of the slider
374 void wxSlider::SetThumbLength(int)
375 {
376 }
377
378 int wxSlider::GetThumbLength() const
379 {
380 return 0;
381 }
382
383 void wxSlider::SetLineSize( int WXUNUSED(lineSize) )
384 {
385 }
386
387 int wxSlider::GetLineSize() const
388 {
389 return 0;
390 }
391
392 bool wxSlider::IsOwnGtkWindow( GdkWindow *window )
393 {
394 GtkRange *range = GTK_RANGE(m_widget);
395 return (range->event_window == window);
396 }
397
398 // static
399 wxVisualAttributes
400 wxSlider::GetClassDefaultAttributes(wxWindowVariant WXUNUSED(variant))
401 {
402 return GetDefaultAttributesFromGTKWidget(gtk_vscale_new);
403 }
404
405 #endif // wxUSE_SLIDER