1 /********************************************************************************
2 *                                                                               *
3 *                       R e a l S l i d e r   W i d g e t                       *
4 *                                                                               *
5 *********************************************************************************
6 * Copyright (C) 1997,2021 by Jeroen van der Zijp.   All Rights Reserved.        *
7 *********************************************************************************
8 * This library is free software; you can redistribute it and/or modify          *
9 * it under the terms of the GNU Lesser General Public License as published by   *
10 * the Free Software Foundation; either version 3 of the License, or             *
11 * (at your option) any later version.                                           *
12 *                                                                               *
13 * This library is distributed in the hope that it will be useful,               *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of                *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                 *
16 * GNU Lesser General Public License for more details.                           *
17 *                                                                               *
18 * You should have received a copy of the GNU Lesser General Public License      *
19 * along with this program.  If not, see <http://www.gnu.org/licenses/>          *
20 ********************************************************************************/
21 #include "xincs.h"
22 #include "fxver.h"
23 #include "fxdefs.h"
24 #include "fxmath.h"
25 #include "fxkeys.h"
26 #include "FXArray.h"
27 #include "FXHash.h"
28 #include "FXMutex.h"
29 #include "FXStream.h"
30 #include "FXString.h"
31 #include "FXSize.h"
32 #include "FXPoint.h"
33 #include "FXRectangle.h"
34 #include "FXStringDictionary.h"
35 #include "FXSettings.h"
36 #include "FXRegistry.h"
37 #include "FXEvent.h"
38 #include "FXWindow.h"
39 #include "FXDCWindow.h"
40 #include "FXApp.h"
41 #include "FXRealSlider.h"
42 
43 
44 
45 
46 /*
47   Notes:
48   - Maybe add bindings for arrow keys for value changes.
49   - Yes, this *does* have a lot in common with FXSlider and its
50     probably a good idea to give FXRealSlider and FXSlider a common
51     base class at some point.
52 */
53 
54 #define TICKSIZE        4           // Length of ticks
55 #define OVERHANG        4           // Default amount of overhang
56 #define MINOVERHANG     3           // Minimal amount of overhang
57 #define HEADINSIDEBAR   20          // Default for inside bar head size
58 #define HEADOVERHANGING 9           // Default for overhanging head size
59 
60 #define REALSLIDER_MASK (REALSLIDER_VERTICAL|REALSLIDER_ARROW_UP|REALSLIDER_ARROW_DOWN|REALSLIDER_INSIDE_BAR|REALSLIDER_TICKS_TOP|REALSLIDER_TICKS_BOTTOM)
61 
62 using namespace FX;
63 
64 /*******************************************************************************/
65 
66 namespace FX {
67 
68 // Map
69 FXDEFMAP(FXRealSlider) FXRealSliderMap[]={
70   FXMAPFUNC(SEL_PAINT,0,FXRealSlider::onPaint),
71   FXMAPFUNC(SEL_MOTION,0,FXRealSlider::onMotion),
72   FXMAPFUNC(SEL_MOUSEWHEEL,0,FXRealSlider::onMouseWheel),
73   FXMAPFUNC(SEL_LEFTBUTTONPRESS,0,FXRealSlider::onLeftBtnPress),
74   FXMAPFUNC(SEL_LEFTBUTTONRELEASE,0,FXRealSlider::onLeftBtnRelease),
75   FXMAPFUNC(SEL_MIDDLEBUTTONPRESS,0,FXRealSlider::onMiddleBtnPress),
76   FXMAPFUNC(SEL_MIDDLEBUTTONRELEASE,0,FXRealSlider::onMiddleBtnRelease),
77   FXMAPFUNC(SEL_KEYPRESS,0,FXRealSlider::onKeyPress),
78   FXMAPFUNC(SEL_KEYRELEASE,0,FXRealSlider::onKeyRelease),
79   FXMAPFUNC(SEL_UNGRABBED,0,FXRealSlider::onUngrabbed),
80   FXMAPFUNC(SEL_QUERY_TIP,0,FXRealSlider::onQueryTip),
81   FXMAPFUNC(SEL_QUERY_HELP,0,FXRealSlider::onQueryHelp),
82   FXMAPFUNC(SEL_TIMEOUT,FXRealSlider::ID_AUTOSLIDE,FXRealSlider::onAutoSlide),
83   FXMAPFUNC(SEL_COMMAND,FXRealSlider::ID_SETVALUE,FXRealSlider::onCmdSetValue),
84   FXMAPFUNC(SEL_COMMAND,FXRealSlider::ID_SETINTVALUE,FXRealSlider::onCmdSetIntValue),
85   FXMAPFUNC(SEL_COMMAND,FXRealSlider::ID_GETINTVALUE,FXRealSlider::onCmdGetIntValue),
86   FXMAPFUNC(SEL_COMMAND,FXRealSlider::ID_SETLONGVALUE,FXRealSlider::onCmdSetLongValue),
87   FXMAPFUNC(SEL_COMMAND,FXRealSlider::ID_GETLONGVALUE,FXRealSlider::onCmdGetLongValue),
88   FXMAPFUNC(SEL_COMMAND,FXRealSlider::ID_SETREALVALUE,FXRealSlider::onCmdSetRealValue),
89   FXMAPFUNC(SEL_COMMAND,FXRealSlider::ID_GETREALVALUE,FXRealSlider::onCmdGetRealValue),
90   FXMAPFUNC(SEL_COMMAND,FXRealSlider::ID_SETINTRANGE,FXRealSlider::onCmdSetIntRange),
91   FXMAPFUNC(SEL_COMMAND,FXRealSlider::ID_GETINTRANGE,FXRealSlider::onCmdGetIntRange),
92   FXMAPFUNC(SEL_COMMAND,FXRealSlider::ID_SETREALRANGE,FXRealSlider::onCmdSetRealRange),
93   FXMAPFUNC(SEL_COMMAND,FXRealSlider::ID_GETREALRANGE,FXRealSlider::onCmdGetRealRange),
94   FXMAPFUNC(SEL_COMMAND,FXRealSlider::ID_SETHELPSTRING,FXRealSlider::onCmdSetHelp),
95   FXMAPFUNC(SEL_COMMAND,FXRealSlider::ID_GETHELPSTRING,FXRealSlider::onCmdGetHelp),
96   FXMAPFUNC(SEL_COMMAND,FXRealSlider::ID_SETTIPSTRING,FXRealSlider::onCmdSetTip),
97   FXMAPFUNC(SEL_COMMAND,FXRealSlider::ID_GETTIPSTRING,FXRealSlider::onCmdGetTip),
98   };
99 
100 
101 // Object implementation
FXIMPLEMENT(FXRealSlider,FXFrame,FXRealSliderMap,ARRAYNUMBER (FXRealSliderMap))102 FXIMPLEMENT(FXRealSlider,FXFrame,FXRealSliderMap,ARRAYNUMBER(FXRealSliderMap))
103 
104 
105 // Make a slider
106 FXRealSlider::FXRealSlider(){
107   flags|=FLAG_ENABLED;
108   headPos=0;
109   headSize=0;
110   slotSize=0;
111   slotColor=0;
112   dragPoint=0;
113   range[0]=0.0;
114   range[1]=0.0;
115   delta=0.0;
116   incr=0.01;
117   gran=0.0;
118   pos=0.0;
119   }
120 
121 
122 // Make a slider
FXRealSlider(FXComposite * p,FXObject * tgt,FXSelector sel,FXuint opts,FXint x,FXint y,FXint w,FXint h,FXint pl,FXint pr,FXint pt,FXint pb)123 FXRealSlider::FXRealSlider(FXComposite* p,FXObject* tgt,FXSelector sel,FXuint opts,FXint x,FXint y,FXint w,FXint h,FXint pl,FXint pr,FXint pt,FXint pb):FXFrame(p,opts,x,y,w,h,pl,pr,pt,pb){
124   flags|=FLAG_ENABLED;
125   baseColor=getApp()->getBaseColor();
126   hiliteColor=getApp()->getHiliteColor();
127   shadowColor=getApp()->getShadowColor();
128   borderColor=getApp()->getBorderColor();
129   target=tgt;
130   message=sel;
131   headPos=0;
132   headSize=(options&REALSLIDER_INSIDE_BAR)?HEADINSIDEBAR:HEADOVERHANGING;
133   slotSize=5;
134   slotColor=getApp()->getBackColor();
135   dragPoint=0;
136   range[0]=0.0;
137   range[1]=1.0;
138   delta=0.0;
139   incr=0.01;
140   gran=0.0;
141   pos=0.5;
142   }
143 
144 
145 // Enable the window
enable()146 void FXRealSlider::enable(){
147   if(!(flags&FLAG_ENABLED)){
148     FXFrame::enable();
149     update();
150     }
151   }
152 
153 
154 // Disable the window
disable()155 void FXRealSlider::disable(){
156   if(flags&FLAG_ENABLED){
157     FXFrame::disable();
158     update();
159     }
160   }
161 
162 
163 // Get default width
getDefaultWidth()164 FXint FXRealSlider::getDefaultWidth(){
165   FXint w;
166   if(options&REALSLIDER_VERTICAL){
167     if(options&REALSLIDER_INSIDE_BAR) w=4+headSize/2;
168     else if(options&(REALSLIDER_ARROW_LEFT|REALSLIDER_ARROW_RIGHT)) w=slotSize+MINOVERHANG*2+headSize/2;
169     else w=slotSize+MINOVERHANG*2;
170     if(options&REALSLIDER_TICKS_LEFT) w+=TICKSIZE;
171     if(options&REALSLIDER_TICKS_RIGHT) w+=TICKSIZE;
172     }
173   else{
174     w=headSize+4;
175     }
176   return w+padleft+padright+(border<<1);
177   }
178 
179 
180 // Get default height
getDefaultHeight()181 FXint FXRealSlider::getDefaultHeight(){
182   FXint h;
183   if(options&REALSLIDER_VERTICAL){
184     h=headSize+4;
185     }
186   else{
187     if(options&REALSLIDER_INSIDE_BAR) h=4+headSize/2;
188     else if(options&(REALSLIDER_ARROW_UP|REALSLIDER_ARROW_DOWN)) h=slotSize+2*MINOVERHANG+headSize/2;
189     else h=slotSize+MINOVERHANG*2;
190     if(options&REALSLIDER_TICKS_TOP) h+=TICKSIZE;
191     if(options&REALSLIDER_TICKS_BOTTOM) h+=TICKSIZE;
192     }
193   return h+padtop+padbottom+(border<<1);
194   }
195 
196 
197 // Returns true because a slider can receive focus
canFocus() const198 FXbool FXRealSlider::canFocus() const { return true; }
199 
200 
201 // Layout changed; even though the position is still
202 // the same, the head may have to be moved.
layout()203 void FXRealSlider::layout(){
204   setValue(pos);
205   flags&=~FLAG_DIRTY;
206   }
207 
208 
209 // Set help using a message
onCmdSetHelp(FXObject *,FXSelector,void * ptr)210 long FXRealSlider::onCmdSetHelp(FXObject*,FXSelector,void* ptr){
211   setHelpText(*((FXString*)ptr));
212   return 1;
213   }
214 
215 
216 // Get help using a message
onCmdGetHelp(FXObject *,FXSelector,void * ptr)217 long FXRealSlider::onCmdGetHelp(FXObject*,FXSelector,void* ptr){
218   *((FXString*)ptr)=getHelpText();
219   return 1;
220   }
221 
222 
223 // Set tip using a message
onCmdSetTip(FXObject *,FXSelector,void * ptr)224 long FXRealSlider::onCmdSetTip(FXObject*,FXSelector,void* ptr){
225   setTipText(*((FXString*)ptr));
226   return 1;
227   }
228 
229 
230 // Get tip using a message
onCmdGetTip(FXObject *,FXSelector,void * ptr)231 long FXRealSlider::onCmdGetTip(FXObject*,FXSelector,void* ptr){
232   *((FXString*)ptr)=getTipText();
233   return 1;
234   }
235 
236 
237 // We were asked about tip text
onQueryTip(FXObject * sender,FXSelector sel,void * ptr)238 long FXRealSlider::onQueryTip(FXObject* sender,FXSelector sel,void* ptr){
239   if(FXFrame::onQueryTip(sender,sel,ptr)) return 1;
240   if((flags&FLAG_TIP) && !tip.empty()){
241     sender->handle(this,FXSEL(SEL_COMMAND,ID_SETSTRINGVALUE),(void*)&tip);
242     return 1;
243     }
244   return 0;
245   }
246 
247 
248 // We were asked about status text
onQueryHelp(FXObject * sender,FXSelector sel,void * ptr)249 long FXRealSlider::onQueryHelp(FXObject* sender,FXSelector sel,void* ptr){
250   if(FXFrame::onQueryHelp(sender,sel,ptr)) return 1;
251   if((flags&FLAG_HELP) && !help.empty()){
252     sender->handle(this,FXSEL(SEL_COMMAND,ID_SETSTRINGVALUE),(void*)&help);
253     return 1;
254     }
255   return 0;
256   }
257 
258 
259 // Update value from a message
onCmdSetValue(FXObject *,FXSelector,void * ptr)260 long FXRealSlider::onCmdSetValue(FXObject*,FXSelector,void* ptr){
261   setValue((FXdouble)(FXival)ptr);
262   return 1;
263   }
264 
265 
266 // Update value from a message
onCmdSetIntValue(FXObject *,FXSelector,void * ptr)267 long FXRealSlider::onCmdSetIntValue(FXObject*,FXSelector,void* ptr){
268   setValue((FXdouble)*((FXint*)ptr));
269   return 1;
270   }
271 
272 
273 // Obtain value from text field
onCmdGetIntValue(FXObject *,FXSelector,void * ptr)274 long FXRealSlider::onCmdGetIntValue(FXObject*,FXSelector,void* ptr){
275   *((FXint*)ptr)=(FXint)getValue();
276   return 1;
277   }
278 
279 
280 // Update value from a message
onCmdSetLongValue(FXObject *,FXSelector,void * ptr)281 long FXRealSlider::onCmdSetLongValue(FXObject*,FXSelector,void* ptr){
282   setValue((FXdouble)*((FXlong*)ptr));
283   return 1;
284   }
285 
286 
287 // Obtain value with a message
onCmdGetLongValue(FXObject *,FXSelector,void * ptr)288 long FXRealSlider::onCmdGetLongValue(FXObject*,FXSelector,void* ptr){
289   *((FXlong*)ptr)=(FXlong)getValue();
290   return 1;
291   }
292 
293 
294 // Update value from a message
onCmdSetRealValue(FXObject *,FXSelector,void * ptr)295 long FXRealSlider::onCmdSetRealValue(FXObject*,FXSelector,void* ptr){
296   setValue(*((FXdouble*)ptr));
297   return 1;
298   }
299 
300 
301 // Obtain value with a message
onCmdGetRealValue(FXObject *,FXSelector,void * ptr)302 long FXRealSlider::onCmdGetRealValue(FXObject*,FXSelector,void* ptr){
303   *((FXdouble*)ptr)=getValue();
304   return 1;
305   }
306 
307 
308 // Update range from a message
onCmdSetIntRange(FXObject *,FXSelector,void * ptr)309 long FXRealSlider::onCmdSetIntRange(FXObject*,FXSelector,void* ptr){
310   setRange((FXdouble)((FXint*)ptr)[0],(FXdouble)((FXint*)ptr)[1]);
311   return 1;
312   }
313 
314 
315 // Get range with a message
onCmdGetIntRange(FXObject *,FXSelector,void * ptr)316 long FXRealSlider::onCmdGetIntRange(FXObject*,FXSelector,void* ptr){
317   ((FXint*)ptr)[0]=(FXint)range[0];
318   ((FXint*)ptr)[1]=(FXint)range[1];
319   return 1;
320   }
321 
322 
323 // Update range from a message
onCmdSetRealRange(FXObject *,FXSelector,void * ptr)324 long FXRealSlider::onCmdSetRealRange(FXObject*,FXSelector,void* ptr){
325   setRange(((FXdouble*)ptr)[0],((FXdouble*)ptr)[1]);
326   return 1;
327   }
328 
329 
330 // Get range with a message
onCmdGetRealRange(FXObject *,FXSelector,void * ptr)331 long FXRealSlider::onCmdGetRealRange(FXObject*,FXSelector,void* ptr){
332   ((FXdouble*)ptr)[0]=range[0];
333   ((FXdouble*)ptr)[1]=range[1];
334   return 1;
335   }
336 
337 
338 // Pressed LEFT button
onLeftBtnPress(FXObject *,FXSelector,void * ptr)339 long FXRealSlider::onLeftBtnPress(FXObject*,FXSelector,void* ptr){
340   FXEvent *event=(FXEvent*)ptr;
341   FXdouble p=pos;
342   flags&=~FLAG_TIP;
343   handle(this,FXSEL(SEL_FOCUS_SELF,0),ptr);
344   if(isEnabled()){
345     grab();
346     getApp()->removeTimeout(this,ID_AUTOSLIDE);
347     if(target && target->tryHandle(this,FXSEL(SEL_LEFTBUTTONPRESS,message),ptr)) return 1;
348     flags&=~FLAG_UPDATE;
349     if(options&REALSLIDER_VERTICAL){
350       if(event->win_y<headPos){
351         getApp()->addTimeout(this,ID_AUTOSLIDE,getApp()->getScrollDelay(),(void*)(FXival)1);
352         p+=incr;
353         }
354       else if(event->win_y>(headPos+headSize)){
355         getApp()->addTimeout(this,ID_AUTOSLIDE,getApp()->getScrollDelay(),(void*)(FXival)-1);
356         p-=incr;
357         }
358       else{
359         dragPoint=event->win_y-headPos;
360         flags|=FLAG_PRESSED;
361         }
362       }
363     else{
364       if(event->win_x<headPos){
365         getApp()->addTimeout(this,ID_AUTOSLIDE,getApp()->getScrollDelay(),(void*)(FXival)-1);
366         p-=incr;
367         }
368       else if(event->win_x>(headPos+headSize)){
369         getApp()->addTimeout(this,ID_AUTOSLIDE,getApp()->getScrollDelay(),(void*)(FXival)1);
370         p+=incr;
371         }
372       else{
373         dragPoint=event->win_x-headPos;
374         flags|=FLAG_PRESSED;
375         }
376       }
377     if(0.0<gran) p=gran*Math::round(p/gran);
378     if(p<range[0]) p=range[0];
379     if(p>range[1]) p=range[1];
380     if(p!=pos){
381       setValue(p);
382       flags|=FLAG_CHANGED;
383       if(target) target->tryHandle(this,FXSEL(SEL_CHANGED,message),(void*)&pos);
384       }
385     return 1;
386     }
387   return 0;
388   }
389 
390 
391 // Released Left button
onLeftBtnRelease(FXObject *,FXSelector,void * ptr)392 long FXRealSlider::onLeftBtnRelease(FXObject*,FXSelector,void* ptr){
393   FXuint flgs=flags;
394   if(isEnabled()){
395     ungrab();
396     getApp()->removeTimeout(this,ID_AUTOSLIDE);
397     setValue(pos);                                                 // Hop to exact position
398     flags&=~FLAG_PRESSED;
399     flags&=~FLAG_CHANGED;
400     flags|=FLAG_UPDATE;
401     if(target && target->tryHandle(this,FXSEL(SEL_LEFTBUTTONRELEASE,message),ptr)) return 1;
402     if(flgs&FLAG_CHANGED){
403       if(target) target->tryHandle(this,FXSEL(SEL_COMMAND,message),(void*)&pos);
404       }
405     return 1;
406     }
407   return 0;
408   }
409 
410 
411 // Moving
onMotion(FXObject *,FXSelector,void * ptr)412 long FXRealSlider::onMotion(FXObject*,FXSelector,void* ptr){
413   FXEvent *event=(FXEvent*)ptr;
414   FXint xx,yy,ww,hh,lo,hi,h,travel;
415   FXdouble p;
416   if(!isEnabled()) return 0;
417   if(flags&FLAG_PRESSED){
418     yy=border+padtop+2;
419     xx=border+padleft+2;
420     hh=height-(border<<1)-padtop-padbottom-4;
421     ww=width-(border<<1)-padleft-padright-4;
422     if(options&REALSLIDER_VERTICAL){
423       h=event->win_y-dragPoint;
424       travel=hh-headSize;
425       if(h<yy) h=yy;
426       if(h>yy+travel) h=yy+travel;
427       if(h!=headPos){
428         FXMINMAX(lo,hi,headPos,h);
429         headPos=h;
430         update(border,lo-1,width-(border<<1),hi+headSize+2-lo);
431         }
432       if(travel>0)
433         p=range[0]+((range[1]-range[0])*(yy+travel-h))/travel;
434       else
435         p=range[0];
436       }
437     else{
438       h=event->win_x-dragPoint;
439       travel=ww-headSize;
440       if(h<xx) h=xx;
441       if(h>xx+travel) h=xx+travel;
442       if(h!=headPos){
443         FXMINMAX(lo,hi,headPos,h);
444         headPos=h;
445         update(lo-1,border,hi+headSize+2-lo,height-(border<<1));
446         }
447       if(travel>0)
448         p=range[0]+((range[1]-range[0])*(h-xx))/travel;
449       else
450         p=range[0];
451       }
452     if(0.0<gran) p=gran*Math::round(p/gran);
453     if(p<range[0]) p=range[0];
454     if(p>range[1]) p=range[1];
455     if(pos!=p){
456       pos=p;
457       flags|=FLAG_CHANGED;
458       if(target) target->tryHandle(this,FXSEL(SEL_CHANGED,message),(void*)&pos);
459       }
460     return 1;
461     }
462   return 0;
463   }
464 
465 
466 // Pressed middle or right
onMiddleBtnPress(FXObject *,FXSelector,void * ptr)467 long FXRealSlider::onMiddleBtnPress(FXObject*,FXSelector,void* ptr){
468   FXEvent *event=(FXEvent*)ptr;
469   FXint xx,yy,ww,hh,lo,hi,h,travel;
470   FXdouble p;
471   flags&=~FLAG_TIP;
472   handle(this,FXSEL(SEL_FOCUS_SELF,0),ptr);
473   if(isEnabled()){
474     grab();
475     if(target && target->tryHandle(this,FXSEL(SEL_MIDDLEBUTTONPRESS,message),ptr)) return 1;
476     dragPoint=headSize/2;
477     yy=border+padtop+2;
478     xx=border+padleft+2;
479     hh=height-(border<<1)-padtop-padbottom-4;
480     ww=width-(border<<1)-padleft-padright-4;
481     flags&=~FLAG_UPDATE;
482     flags|=FLAG_PRESSED;
483     if(options&REALSLIDER_VERTICAL){
484       h=event->win_y-dragPoint;
485       travel=hh-headSize;
486       if(h<yy) h=yy;
487       if(h>yy+travel) h=yy+travel;
488       if(h!=headPos){
489         FXMINMAX(lo,hi,headPos,h);
490         headPos=h;
491         update(border,lo-1,width-(border<<1),hi+headSize+2-lo);
492         }
493       if(travel>0)
494         p=range[0]+((range[1]-range[0])*(yy+travel-h))/travel;
495       else
496         p=range[0];
497       }
498     else{
499       h=event->win_x-dragPoint;
500       travel=ww-headSize;
501       if(h<xx) h=xx;
502       if(h>xx+travel) h=xx+travel;
503       if(h!=headPos){
504         FXMINMAX(lo,hi,headPos,h);
505         headPos=h;
506         update(lo-1,border,hi+headSize+2-lo,height-(border<<1));
507         }
508       if(travel>0)
509         p=range[0]+((range[1]-range[0])*(h-xx))/travel;
510       else
511         p=range[0];
512       }
513     if(0.0<gran) p=gran*Math::round(p/gran);
514     if(p<range[0]) p=range[0];
515     if(p>range[1]) p=range[1];
516     if(p!=pos){
517       pos=p;
518       flags|=FLAG_CHANGED;
519       if(target) target->tryHandle(this,FXSEL(SEL_CHANGED,message),(void*)&pos);
520       }
521     return 1;
522     }
523   return 0;
524   }
525 
526 
527 // Released middle button
onMiddleBtnRelease(FXObject *,FXSelector,void * ptr)528 long FXRealSlider::onMiddleBtnRelease(FXObject*,FXSelector,void* ptr){
529   FXuint flgs=flags;
530   if(isEnabled()){
531     ungrab();
532     getApp()->removeTimeout(this,ID_AUTOSLIDE);
533     flags&=~FLAG_PRESSED;
534     flags&=~FLAG_CHANGED;
535     flags|=FLAG_UPDATE;
536     setValue(pos);                         // Hop to exact position
537     if(target && target->tryHandle(this,FXSEL(SEL_MIDDLEBUTTONRELEASE,message),ptr)) return 1;
538     if(flgs&FLAG_CHANGED){
539       if(target) target->tryHandle(this,FXSEL(SEL_COMMAND,message),(void*)&pos);
540       }
541     return 1;
542     }
543   return 0;
544   }
545 
546 
547 // Mouse wheel
onMouseWheel(FXObject *,FXSelector,void * ptr)548 long FXRealSlider::onMouseWheel(FXObject*,FXSelector,void* ptr){
549   FXEvent *event=(FXEvent*)ptr;
550   FXdouble p=pos+incr*(event->code/120);
551   if(p<range[0]) p=range[0];
552   if(p>range[1]) p=range[1];
553   if(pos!=p){
554     setValue(p);
555     if(target) target->tryHandle(this,FXSEL(SEL_COMMAND,message),(void*)&pos);
556     }
557   return 1;
558   }
559 
560 
561 // Keyboard press
onKeyPress(FXObject *,FXSelector,void * ptr)562 long FXRealSlider::onKeyPress(FXObject*,FXSelector,void* ptr){
563   FXEvent* event=(FXEvent*)ptr;
564   if(isEnabled()){
565     if(target && target->tryHandle(this,FXSEL(SEL_KEYPRESS,message),ptr)) return 1;
566     switch(event->code){
567       case KEY_Left:
568       case KEY_KP_Left:
569         if(!(options&REALSLIDER_VERTICAL)) goto dec;
570         break;
571       case KEY_Right:
572       case KEY_KP_Right:
573         if(!(options&REALSLIDER_VERTICAL)) goto inc;
574         break;
575       case KEY_Up:
576       case KEY_KP_Up:
577         if(options&REALSLIDER_VERTICAL) goto inc;
578         break;
579       case KEY_Down:
580       case KEY_KP_Down:
581         if(options&REALSLIDER_VERTICAL) goto dec;
582         break;
583       case KEY_plus:
584       case KEY_KP_Add:
585 inc:    setValue(pos+incr,true);
586         return 1;
587       case KEY_minus:
588       case KEY_KP_Subtract:
589 dec:    setValue(pos-incr,true);
590         return 1;
591       }
592     }
593   return 0;
594   }
595 
596 
597 // Keyboard release
onKeyRelease(FXObject *,FXSelector,void * ptr)598 long FXRealSlider::onKeyRelease(FXObject*,FXSelector,void* ptr){
599   FXEvent* event=(FXEvent*)ptr;
600   if(isEnabled()){
601     if(target && target->tryHandle(this,FXSEL(SEL_KEYRELEASE,message),ptr)) return 1;
602     switch(event->code){
603       case KEY_Left:
604       case KEY_KP_Left:
605       case KEY_Right:
606       case KEY_KP_Right:
607         if(!(options&REALSLIDER_VERTICAL)) return 1;
608         break;
609       case KEY_Up:
610       case KEY_KP_Up:
611       case KEY_Down:
612       case KEY_KP_Down:
613         if(options&REALSLIDER_VERTICAL) return 1;
614         break;
615       case KEY_plus:
616       case KEY_KP_Add:
617       case KEY_KP_Subtract:
618       case KEY_minus:
619         return 1;
620       }
621     }
622   return 0;
623   }
624 
625 
626 // The widget lost the grab for some reason
onUngrabbed(FXObject * sender,FXSelector sel,void * ptr)627 long FXRealSlider::onUngrabbed(FXObject* sender,FXSelector sel,void* ptr){
628   FXFrame::onUngrabbed(sender,sel,ptr);
629   getApp()->removeTimeout(this,ID_AUTOSLIDE);
630   flags&=~FLAG_PRESSED;
631   flags&=~FLAG_CHANGED;
632   flags|=FLAG_UPDATE;
633   return 1;
634   }
635 
636 
637 // Automatically move slider while holding down mouse
onAutoSlide(FXObject *,FXSelector,void * ptr)638 long FXRealSlider::onAutoSlide(FXObject*,FXSelector,void* ptr){
639   FXint dir=(FXint)(FXival)ptr;
640   FXdouble p=pos+incr*dir;
641   if(p<=range[0]){
642     p=range[0];
643     }
644   else if(p>=range[1]){
645     p=range[1];
646     }
647   else{
648     getApp()->addTimeout(this,ID_AUTOSLIDE,getApp()->getScrollSpeed(),(void*)(FXival)dir);
649     }
650   if(p!=pos){
651     setValue(p);
652     flags|=FLAG_CHANGED;
653     if(target) target->tryHandle(this,FXSEL(SEL_CHANGED,message),(void*)&pos);
654     return 1;
655     }
656   return 0;
657   }
658 
659 
660 // Draw horizontal ticks
drawHorzTicks(FXDCWindow & dc,FXint,FXint y,FXint,FXint)661 void FXRealSlider::drawHorzTicks(FXDCWindow& dc,FXint,FXint y,FXint,FXint){
662   FXdouble interval=range[1]-range[0];
663   FXint travel,offset,p;
664   FXdouble v,d;
665   if(0.0<interval){
666     d=delta;
667     if(d<=0.0) d=incr;
668     dc.setForeground(FXRGB(0,0,0));
669     travel=width-(border<<1)-padleft-padright-headSize-4;
670     offset=border+padleft+2+headSize/2;
671     for(v=range[0]; v<=range[1]; v+=d){
672       p=offset+(FXint)(0.5+((travel*(v-range[0]))/interval));
673       dc.fillRectangle(p,y,1,TICKSIZE);
674       }
675     }
676   }
677 
678 
679 // Draw vertical ticks
drawVertTicks(FXDCWindow & dc,FXint x,FXint,FXint,FXint)680 void FXRealSlider::drawVertTicks(FXDCWindow& dc,FXint x,FXint,FXint,FXint){
681   FXdouble interval=range[1]-range[0];
682   FXint travel,offset,p;
683   FXdouble v,d;
684   if(0.0<interval){
685     d=delta;
686     if(d<=0.0) d=incr;
687     dc.setForeground(FXRGB(0,0,0));
688     travel=height-(border<<1)-padtop-padbottom-headSize-4;
689     offset=height-border-padbottom-2-headSize/2;
690     for(v=range[0]; v<=range[1]; v+=d){
691       p=offset-(FXint)(0.5+((travel*(v-range[0]))/interval));
692       dc.fillRectangle(x,p,TICKSIZE,1);
693       }
694     }
695   }
696 
697 
698 // Draw slider head
drawSliderHead(FXDCWindow & dc,FXint x,FXint y,FXint w,FXint h)699 void FXRealSlider::drawSliderHead(FXDCWindow& dc,FXint x,FXint y,FXint w,FXint h){
700   FXint m;
701   dc.setForeground(baseColor);
702   dc.fillRectangle(x,y,w,h);
703   if(options&REALSLIDER_VERTICAL){
704     m=(h>>1);
705     if(options&REALSLIDER_ARROW_LEFT){
706       dc.setForeground(hiliteColor);
707       dc.drawLine(x+m,y,x+w-1,y);
708       dc.drawLine(x,y+m,x+m,y);
709       dc.setForeground(shadowColor);
710       dc.drawLine(x+1,y+h-m-1,x+m+1,y+h-1);
711       dc.drawLine(x+m,y+h-2,x+w-1,y+h-2);
712       dc.drawLine(x+w-2,y+1,x+w-2,y+h-1);
713       dc.setForeground(borderColor);
714       dc.drawLine(x,y+h-m-1,x+m,y+h-1);
715       dc.drawLine(x+w-1,y+h-1,x+w-1,y);
716       dc.fillRectangle(x+m,y+h-1,w-m,1);
717       }
718     else if(options&REALSLIDER_ARROW_RIGHT){
719       dc.setForeground(hiliteColor);
720       dc.drawLine(x,y,x+w-m-1,y);
721       dc.drawLine(x,y+1,x,y+h-1);
722       dc.drawLine(x+w-1,y+m,x+w-m-1,y);
723 #ifdef WIN32
724       dc.setForeground(shadowColor);
725       dc.drawLine(x+w-1,y+h-m-2,x+w-m-2,y+h-1);
726       dc.drawLine(x+1,y+h-2,x+w-m-1,y+h-2);
727       dc.setForeground(borderColor);
728       dc.drawLine(x+w,y+h-m-2,x+w-m-1,y+h-1);
729       dc.drawLine(x,y+h-1,x+w-m-1,y+h-1);
730 #else
731       dc.setForeground(shadowColor);
732       dc.drawLine(x+w-2,y+h-m-1,x+w-m-2,y+h-1);
733       dc.drawLine(x+1,y+h-2,x+w-m-1,y+h-2);
734       dc.setForeground(borderColor);
735       dc.drawLine(x+w-1,y+h-m-1,x+w-m-1,y+h-1);
736       dc.drawLine(x,y+h-1,x+w-m-1,y+h-1);
737 #endif
738       }
739     else if(options&REALSLIDER_INSIDE_BAR){
740       drawDoubleRaisedRectangle(dc,x,y,w,h);
741       dc.setForeground(shadowColor);
742       dc.drawLine(x+1,y+m-1,x+w-2,y+m-1);
743       dc.setForeground(hiliteColor);
744       dc.drawLine(x+1,y+m,x+w-2,y+m);
745       }
746     else{
747       drawDoubleRaisedRectangle(dc,x,y,w,h);
748       }
749     }
750   else{
751     m=(w>>1);
752     if(options&REALSLIDER_ARROW_UP){
753       dc.setForeground(hiliteColor);
754       dc.drawLine(x,y+m,x+m,y);
755       dc.drawLine(x,y+m,x,y+h-1);
756       dc.setForeground(shadowColor);
757       dc.drawLine(x+w-1,y+m+1,x+w-m-1,y+1);
758       dc.drawLine(x+w-2,y+m+1,x+w-2,y+h-1);
759       dc.drawLine(x+1,y+h-2,x+w-2,y+h-2);
760       dc.setForeground(borderColor);
761       dc.drawLine(x+w-1,y+m,x+w-m-1,y);
762       dc.drawLine(x+w-1,y+m,x+w-1,y+h-1);
763       dc.fillRectangle(x,y+h-1,w,1);
764       }
765     else if(options&REALSLIDER_ARROW_DOWN){
766       dc.setForeground(hiliteColor);
767       dc.drawLine(x,y,x+w-1,y);
768       dc.drawLine(x,y+1,x,y+h-m-1);
769       dc.drawLine(x,y+h-m-1,x+m,y+h-1);
770       dc.setForeground(shadowColor);
771       dc.drawLine(x+w-2,y+1,x+w-2,y+h-m-1);
772       dc.drawLine(x+w-1,y+h-m-2,x+w-m-1,y+h-2);
773       dc.setForeground(borderColor);
774       dc.drawLine(x+w-1,y+h-m-1,x+w-m-1,y+h-1);
775       dc.fillRectangle(x+w-1,y,1,h-m);
776       }
777     else if(options&REALSLIDER_INSIDE_BAR){
778       drawDoubleRaisedRectangle(dc,x,y,w,h);
779       dc.setForeground(shadowColor);
780       dc.drawLine(x+m-1,y+1,x+m-1,y+h-2);
781       dc.setForeground(hiliteColor);
782       dc.drawLine(x+m,y+1,x+m,y+h-1);
783       }
784     else{
785       drawDoubleRaisedRectangle(dc,x,y,w,h);
786       }
787     }
788   }
789 
790 
791 // Handle repaint
onPaint(FXObject *,FXSelector,void * ptr)792 long FXRealSlider::onPaint(FXObject*,FXSelector,void* ptr){
793   FXDCWindow dc(this,(FXEvent*)ptr);
794   FXint tx,ty,hhs=headSize/2;
795   FXint xx,yy,ww,hh;
796 
797   // Repaint background
798   dc.setForeground(backColor);
799   dc.fillRectangle(0,0,width,height);
800 
801   // Repaint border
802   drawFrame(dc,0,0,width,height);
803 
804   // Slot placement
805   xx=border+padleft;
806   yy=border+padtop;
807   ww=width-(border<<1)-padleft-padright;
808   hh=height-(border<<1)-padtop-padbottom;
809 
810   // Draw the slot
811   if(options&REALSLIDER_VERTICAL){
812 
813     // Adjust slot placement for tickmarks
814     if(options&REALSLIDER_TICKS_LEFT){ xx+=TICKSIZE; ww-=TICKSIZE; }
815     if(options&REALSLIDER_TICKS_RIGHT){ ww-=TICKSIZE; }
816 
817     // Draw slider
818     if(options&REALSLIDER_INSIDE_BAR){
819       drawDoubleSunkenRectangle(dc,xx,yy,ww,hh);
820       dc.setStipple(STIPPLE_GRAY);
821       dc.setForeground(slotColor);
822       dc.setBackground(baseColor);
823       dc.setFillStyle(FILL_OPAQUESTIPPLED);
824       dc.fillRectangle(xx+2,yy+2,ww-4,hh-4);
825       dc.setFillStyle(FILL_SOLID);
826       if(options&REALSLIDER_TICKS_LEFT) drawVertTicks(dc,border+padleft,yy,ww,hh);
827       if(options&REALSLIDER_TICKS_RIGHT) drawVertTicks(dc,width-padright-border-TICKSIZE,yy,ww,hh);
828       if(isEnabled()) drawSliderHead(dc,xx+2,headPos,ww-4,headSize);
829       }
830     else{
831       if(options&REALSLIDER_ARROW_LEFT) tx=xx+hhs+(ww-slotSize-hhs)/2;
832       else if(options&REALSLIDER_ARROW_RIGHT) tx=xx+(ww-slotSize-hhs)/2;
833       else tx=xx+(ww-slotSize)/2;
834       drawDoubleSunkenRectangle(dc,tx,yy,slotSize,hh);
835       dc.setForeground(slotColor);
836       dc.fillRectangle(tx+2,yy+2,slotSize-4,hh-4);
837       if(options&REALSLIDER_TICKS_LEFT) drawVertTicks(dc,border+padleft,yy,ww,hh);
838       if(options&REALSLIDER_TICKS_RIGHT) drawVertTicks(dc,width-padright-border-TICKSIZE,yy,ww,hh);
839       if(isEnabled()) drawSliderHead(dc,xx,headPos,ww,headSize);
840       }
841     }
842   else{
843 
844     // Adjust slot placement for tickmarks
845     if(options&REALSLIDER_TICKS_TOP){ yy+=TICKSIZE; hh-=TICKSIZE; }
846     if(options&REALSLIDER_TICKS_BOTTOM){ hh-=TICKSIZE; }
847 
848     // Draw slider
849     if(options&REALSLIDER_INSIDE_BAR){
850       drawDoubleSunkenRectangle(dc,xx,yy,ww,hh);
851       dc.setForeground(slotColor);
852       dc.setStipple(STIPPLE_GRAY);
853       dc.setForeground(slotColor);
854       dc.setBackground(baseColor);
855       dc.setFillStyle(FILL_OPAQUESTIPPLED);
856       dc.fillRectangle(xx+2,yy+2,ww-4,hh-4);
857       dc.setFillStyle(FILL_SOLID);
858       if(options&REALSLIDER_TICKS_TOP) drawHorzTicks(dc,xx,border+padtop,ww,hh);
859       if(options&REALSLIDER_TICKS_BOTTOM) drawHorzTicks(dc,xx,height-border-padbottom-TICKSIZE,ww,hh);
860       if(isEnabled()) drawSliderHead(dc,headPos,yy+2,headSize,hh-4);
861       }
862     else{
863       if(options&REALSLIDER_ARROW_UP) ty=yy+hhs+(hh-slotSize-hhs)/2;
864       else if(options&REALSLIDER_ARROW_DOWN) ty=yy+(hh-slotSize-hhs)/2;
865       else ty=yy+(hh-slotSize)/2;
866       drawDoubleSunkenRectangle(dc,xx,ty,ww,slotSize);
867       dc.setForeground(slotColor);
868       dc.fillRectangle(xx+2,ty+2,ww-4,slotSize-4);
869       if(options&REALSLIDER_TICKS_TOP) drawHorzTicks(dc,xx,border+padtop,ww,hh);
870       if(options&REALSLIDER_TICKS_BOTTOM) drawHorzTicks(dc,xx,height-border-padbottom-TICKSIZE,ww,hh);
871       if(isEnabled()) drawSliderHead(dc,headPos,yy,headSize,hh);
872       }
873     }
874   return 1;
875   }
876 
877 
878 // Set slider range; this also revalidates the position,
879 // and possibly moves the head [even if the position was still OK,
880 // the head might still have to be moved to the exact position].
setRange(FXdouble lo,FXdouble hi,FXbool notify)881 void FXRealSlider::setRange(FXdouble lo,FXdouble hi,FXbool notify){
882   if(lo>hi){ fxerror("%s::setRange: trying to set negative range.\n",getClassName()); }
883   if(range[0]!=lo || range[1]!=hi){
884     if(options&(REALSLIDER_TICKS_TOP|REALSLIDER_TICKS_BOTTOM)) update();
885     range[0]=lo;
886     range[1]=hi;
887     setValue(pos,notify);
888     }
889   }
890 
891 
892 // Set position; this should always cause the head to reflect
893 // the exact [discrete] value representing pos, even if several
894 // head positions may represent the same position!
895 // Also, the minimal amount is repainted, as one sometimes as very
896 // large/wide sliders.
setValue(FXdouble p,FXbool notify)897 void FXRealSlider::setValue(FXdouble p,FXbool notify){
898   FXdouble interval=range[1]-range[0];
899   FXint travel,lo,hi,h;
900   if(p<range[0]) p=range[0];
901   if(p>range[1]) p=range[1];
902   if(options&REALSLIDER_VERTICAL){
903     travel=height-(border<<1)-padtop-padbottom-headSize-4;
904     h=height-border-padbottom-2-headSize;
905     if(0<interval) h-=(FXint)(0.5+((travel*(p-range[0]))/interval));
906     if(h!=headPos){
907       FXMINMAX(lo,hi,headPos,h);
908       headPos=h;
909       update(border,lo-1,width-(border<<1),hi+headSize+2-lo);
910       }
911     }
912   else{
913     travel=width-(border<<1)-padleft-padright-headSize-4;
914     h=border+padleft+2;
915     if(0<interval) h+=(FXint)(0.5+((travel*(p-range[0]))/interval));
916     if(h!=headPos){
917       FXMINMAX(lo,hi,headPos,h);
918       headPos=h;
919       update(lo-1,border,hi+headSize+2-lo,height-(border<<1));
920       }
921     }
922   if(pos!=p){
923     pos=p;
924     if(notify && target){target->tryHandle(this,FXSEL(SEL_COMMAND,message),(void*)&pos);}
925     }
926   }
927 
928 
929 // Get slider options
getSliderStyle() const930 FXuint FXRealSlider::getSliderStyle() const {
931   return (options&REALSLIDER_MASK);
932   }
933 
934 
935 // Set slider options
setSliderStyle(FXuint style)936 void FXRealSlider::setSliderStyle(FXuint style){
937   FXuint opts=(options&~REALSLIDER_MASK) | (style&REALSLIDER_MASK);
938   if(options!=opts){
939     headSize=(opts&REALSLIDER_INSIDE_BAR)?HEADINSIDEBAR:HEADOVERHANGING;
940     options=opts;
941     recalc();
942     update();
943     }
944   }
945 
946 
947 // Set head size
setHeadSize(FXint hs)948 void FXRealSlider::setHeadSize(FXint hs){
949   if(headSize!=hs){
950     headSize=hs;
951     recalc();
952     update();
953     }
954   }
955 
956 
957 // Set slot size
setSlotSize(FXint bs)958 void FXRealSlider::setSlotSize(FXint bs){
959   if(slotSize!=bs){
960     slotSize=bs;
961     recalc();
962     update();
963     }
964   }
965 
966 
967 // Set slider increment
setIncrement(FXdouble inc)968 void FXRealSlider::setIncrement(FXdouble inc){
969   if(inc<=0.0){ fxerror("%s::setIncrement: negative or zero increment specified.\n",getClassName()); }
970   incr=inc;
971   }
972 
973 
974 // Change slider granularity
setGranularity(FXdouble gr)975 void FXRealSlider::setGranularity(FXdouble gr){
976   if(gr<0.0){ fxerror("%s::setGranularity: negative granularity specified.\n",getClassName()); }
977   gran=gr;
978   }
979 
980 
981 // Set slot color
setSlotColor(FXColor clr)982 void FXRealSlider::setSlotColor(FXColor clr){
983   if(slotColor!=clr){
984     slotColor=clr;
985     update();
986     }
987   }
988 
989 
990 // Change the delta between ticks
setTickDelta(FXdouble dist)991 void FXRealSlider::setTickDelta(FXdouble dist){
992   if(dist<0.0) dist=0.0;
993   if(delta!=dist){
994     if(options&(REALSLIDER_TICKS_TOP|REALSLIDER_TICKS_BOTTOM)) update();
995     delta=dist;
996     }
997   }
998 
999 
1000 // Save object to stream
save(FXStream & store) const1001 void FXRealSlider::save(FXStream& store) const {
1002   FXFrame::save(store);
1003   store << headSize;
1004   store << slotSize;
1005   store << slotColor;
1006   store << range[0];
1007   store << range[1];
1008   store << delta;
1009   store << incr;
1010   store << gran;
1011   store << pos;
1012   store << help;
1013   store << tip;
1014   }
1015 
1016 
1017 // Load object from stream
load(FXStream & store)1018 void FXRealSlider::load(FXStream& store){
1019   FXFrame::load(store);
1020   store >> headSize;
1021   store >> slotSize;
1022   store >> slotColor;
1023   store >> range[0];
1024   store >> range[1];
1025   store >> delta;
1026   store >> incr;
1027   store >> gran;
1028   store >> pos;
1029   store >> help;
1030   store >> tip;
1031   }
1032 
1033 
1034 // Delete
~FXRealSlider()1035 FXRealSlider::~FXRealSlider(){
1036   getApp()->removeTimeout(this,ID_AUTOSLIDE);
1037   }
1038 
1039 }
1040