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