threads fix
[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 gdk_threads_enter();
225 wxSlider* win = (wxSlider*)data;
226 win->m_isScrolling = false;
227 if (win->m_needThumbRelease)
228 {
229 win->m_needThumbRelease = false;
230 ProcessScrollEvent(win, wxEVT_SCROLL_THUMBRELEASE);
231 }
232 // Keep slider at an integral position
233 win->BlockScrollEvent();
234 gtk_range_set_value((GtkRange*)win->m_widget, win->GetValue());
235 win->UnblockScrollEvent();
236 gdk_threads_leave();
237 return false;
238 }
239 }
240
241 //-----------------------------------------------------------------------------
242 // "button_release_event"
243 //-----------------------------------------------------------------------------
244
245 extern "C" {
246 static gboolean
247 gtk_button_release_event(GtkWidget*, GdkEventButton*, wxSlider* win)
248 {
249 win->m_mouseButtonDown = false;
250 if (win->m_isScrolling)
251 {
252 g_idle_add(idle_thumbrelease, win);
253 }
254 return false;
255 }
256 }
257
258 //-----------------------------------------------------------------------------
259 // "format_value"
260 //-----------------------------------------------------------------------------
261
262 extern "C" {
263 static gchar* gtk_format_value(GtkScale*, double value, void*)
264 {
265 // Format value as nearest integer
266 return g_strdup_printf("%d", int(round(value)));
267 }
268 }
269
270 //-----------------------------------------------------------------------------
271 // wxSlider
272 //-----------------------------------------------------------------------------
273
274 IMPLEMENT_DYNAMIC_CLASS(wxSlider,wxControl)
275
276 wxSlider::wxSlider()
277 {
278 m_pos = 0;
279 m_scrollEventType = 0;
280 m_needThumbRelease = false;
281 }
282
283 bool wxSlider::Create(wxWindow *parent, wxWindowID id,
284 int value, int minValue, int maxValue,
285 const wxPoint& pos, const wxSize& size,
286 long style, const wxValidator& validator, const wxString& name )
287 {
288 m_acceptsFocus = true;
289 m_needParent = true;
290
291 if (!PreCreation( parent, pos, size ) ||
292 !CreateBase( parent, id, pos, size, style, validator, name ))
293 {
294 wxFAIL_MSG( wxT("wxSlider creation failed") );
295 return false;
296 }
297
298 m_pos = 0;
299 m_scrollEventType = 0;
300 m_needThumbRelease = false;
301
302 if (style & wxSL_VERTICAL)
303 m_widget = gtk_vscale_new( (GtkAdjustment *) NULL );
304 else
305 m_widget = gtk_hscale_new( (GtkAdjustment *) NULL );
306
307 gtk_scale_set_draw_value((GtkScale*)m_widget, (style & wxSL_LABELS) != 0);
308 // Keep full precision in position value
309 gtk_scale_set_digits((GtkScale*)m_widget, -1);
310
311 if (style & wxSL_INVERSE)
312 gtk_range_set_inverted( GTK_RANGE(m_widget), TRUE );
313
314 g_signal_connect(m_widget, "button_press_event", G_CALLBACK(gtk_button_press_event), this);
315 g_signal_connect(m_widget, "button_release_event", G_CALLBACK(gtk_button_release_event), this);
316 g_signal_connect(m_widget, "move_slider", G_CALLBACK(gtk_move_slider), this);
317 g_signal_connect(m_widget, "format_value", G_CALLBACK(gtk_format_value), NULL);
318 g_signal_connect(m_widget, "value_changed", G_CALLBACK(gtk_value_changed), this);
319
320 SetRange( minValue, maxValue );
321 SetValue( value );
322
323 m_parent->DoAddChild( this );
324
325 PostCreation(size);
326
327 return true;
328 }
329
330 int wxSlider::GetValue() const
331 {
332 return int(round(m_pos));
333 }
334
335 void wxSlider::SetValue( int value )
336 {
337 if (GetValue() != value)
338 {
339 BlockScrollEvent();
340 gtk_range_set_value((GtkRange*)m_widget, value);
341 UnblockScrollEvent();
342 }
343 }
344
345 void wxSlider::SetRange( int minValue, int maxValue )
346 {
347 BlockScrollEvent();
348 gtk_range_set_range((GtkRange*)m_widget, minValue, maxValue);
349 gtk_range_set_increments((GtkRange*)m_widget, 1, (maxValue - minValue + 9) / 10);
350 UnblockScrollEvent();
351 }
352
353 int wxSlider::GetMin() const
354 {
355 return int(((GtkRange*)m_widget)->adjustment->lower);
356 }
357
358 int wxSlider::GetMax() const
359 {
360 return int(((GtkRange*)m_widget)->adjustment->upper);
361 }
362
363 void wxSlider::SetPageSize( int pageSize )
364 {
365 BlockScrollEvent();
366 gtk_range_set_increments((GtkRange*)m_widget, 1, pageSize);
367 UnblockScrollEvent();
368 }
369
370 int wxSlider::GetPageSize() const
371 {
372 return int(((GtkRange*)m_widget)->adjustment->page_increment);
373 }
374
375 // GTK does not support changing the size of the slider
376 void wxSlider::SetThumbLength(int)
377 {
378 }
379
380 int wxSlider::GetThumbLength() const
381 {
382 return 0;
383 }
384
385 void wxSlider::SetLineSize( int WXUNUSED(lineSize) )
386 {
387 }
388
389 int wxSlider::GetLineSize() const
390 {
391 return 0;
392 }
393
394 bool wxSlider::IsOwnGtkWindow( GdkWindow *window )
395 {
396 GtkRange *range = GTK_RANGE(m_widget);
397 return (range->event_window == window);
398 }
399
400 // static
401 wxVisualAttributes
402 wxSlider::GetClassDefaultAttributes(wxWindowVariant WXUNUSED(variant))
403 {
404 return GetDefaultAttributesFromGTKWidget(gtk_vscale_new);
405 }
406
407 #endif // wxUSE_SLIDER