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