1 /********************************************************************************
2 *                                                                               *
3 *                                D i a l   W i d g e t                          *
4 *                                                                               *
5 *********************************************************************************
6 * Copyright (C) 1998,2020 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 "FXDial.h"
42 
43 
44 /*
45   Notes:
46   - Contributed by: Guoqing Tian.
47   - Position decoupled from angle.
48   - Add some API's.
49   - Properly handle cyclic/non cyclic stuff.
50   - Callbacks should report position in the void* ptr.
51   - Keep notchangle>=0, as % of negative numbers is implementation defined.
52   - Not yet happy with keyboard/wheel mode valuator.
53   - Visual cue for focus:- please no ugly border!
54   - Maybe add some delta-mode whereby we report changes?
55 */
56 
57 #define DIALWIDTH     12
58 #define DIALDIAMETER  40
59 #define NUMSIDECOLORS 16
60 #define DIAL_MASK     (DIAL_HORIZONTAL|DIAL_CYCLIC|DIAL_HAS_NOTCH)
61 
62 using namespace FX;
63 
64 /*******************************************************************************/
65 
66 namespace FX {
67 
68 // Map
69 FXDEFMAP(FXDial) FXDialMap[]={
70   FXMAPFUNC(SEL_PAINT,0,FXDial::onPaint),
71   FXMAPFUNC(SEL_MOTION,0,FXDial::onMotion),
72   FXMAPFUNC(SEL_MOUSEWHEEL,0,FXDial::onMouseWheel),
73   FXMAPFUNC(SEL_LEFTBUTTONPRESS,0,FXDial::onLeftBtnPress),
74   FXMAPFUNC(SEL_LEFTBUTTONRELEASE,0,FXDial::onLeftBtnRelease),
75   FXMAPFUNC(SEL_KEYPRESS,0,FXDial::onKeyPress),
76   FXMAPFUNC(SEL_KEYRELEASE,0,FXDial::onKeyRelease),
77   FXMAPFUNC(SEL_UNGRABBED,0,FXDial::onUngrabbed),
78   FXMAPFUNC(SEL_QUERY_TIP,0,FXDial::onQueryTip),
79   FXMAPFUNC(SEL_QUERY_HELP,0,FXDial::onQueryHelp),
80   FXMAPFUNC(SEL_COMMAND,FXDial::ID_SETVALUE,FXDial::onCmdSetValue),
81   FXMAPFUNC(SEL_COMMAND,FXDial::ID_SETINTVALUE,FXDial::onCmdSetIntValue),
82   FXMAPFUNC(SEL_COMMAND,FXDial::ID_GETINTVALUE,FXDial::onCmdGetIntValue),
83   FXMAPFUNC(SEL_COMMAND,FXDial::ID_SETLONGVALUE,FXDial::onCmdSetLongValue),
84   FXMAPFUNC(SEL_COMMAND,FXDial::ID_GETLONGVALUE,FXDial::onCmdGetLongValue),
85   FXMAPFUNC(SEL_COMMAND,FXDial::ID_SETREALVALUE,FXDial::onCmdSetRealValue),
86   FXMAPFUNC(SEL_COMMAND,FXDial::ID_GETREALVALUE,FXDial::onCmdGetRealValue),
87   FXMAPFUNC(SEL_COMMAND,FXDial::ID_SETINTRANGE,FXDial::onCmdSetIntRange),
88   FXMAPFUNC(SEL_COMMAND,FXDial::ID_GETINTRANGE,FXDial::onCmdGetIntRange),
89   FXMAPFUNC(SEL_COMMAND,FXDial::ID_SETREALRANGE,FXDial::onCmdSetRealRange),
90   FXMAPFUNC(SEL_COMMAND,FXDial::ID_GETREALRANGE,FXDial::onCmdGetRealRange),
91   FXMAPFUNC(SEL_COMMAND,FXDial::ID_SETHELPSTRING,FXDial::onCmdSetHelp),
92   FXMAPFUNC(SEL_COMMAND,FXDial::ID_GETHELPSTRING,FXDial::onCmdGetHelp),
93   FXMAPFUNC(SEL_COMMAND,FXDial::ID_SETTIPSTRING,FXDial::onCmdSetTip),
94   FXMAPFUNC(SEL_COMMAND,FXDial::ID_GETTIPSTRING,FXDial::onCmdGetTip),
95   };
96 
97 
98 // Object implementation
FXIMPLEMENT(FXDial,FXFrame,FXDialMap,ARRAYNUMBER (FXDialMap))99 FXIMPLEMENT(FXDial,FXFrame,FXDialMap,ARRAYNUMBER(FXDialMap))
100 
101 FXDial::FXDial(){
102   flags|=FLAG_ENABLED;
103   notchAngle=0;
104   notchSpacing=0;
105   notchOffset=0;
106   notchColor=0;
107   dragPoint=0;
108   dragPos=0;
109   range[0]=0;
110   range[1]=0;
111   incr=0;
112   pos=0;
113   }
114 
115 
116 // Make a window
FXDial(FXComposite * p,FXObject * tgt,FXSelector sel,FXuint opts,FXint x,FXint y,FXint w,FXint h,FXint pl,FXint pr,FXint pt,FXint pb)117 FXDial::FXDial(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){
118   flags|=FLAG_ENABLED;
119   target=tgt;
120   message=sel;
121   notchAngle=0;
122   notchSpacing=90;
123   notchOffset=0;
124   notchColor=FXRGB(255,128,0);
125   dragPoint=0;
126   dragPos=0;
127   range[0]=0;
128   range[1]=359;
129   incr=360;
130   pos=0;
131   }
132 
133 
134 // Get minimum width
getDefaultWidth()135 FXint FXDial::getDefaultWidth(){
136   FXint w=(options&DIAL_HORIZONTAL)?DIALDIAMETER:DIALWIDTH;
137   return w+padleft+padright+(border<<1);
138   }
139 
140 
141 // Get minimum height
getDefaultHeight()142 FXint FXDial::getDefaultHeight(){
143   FXint h=(options&DIAL_HORIZONTAL)?DIALWIDTH:DIALDIAMETER;
144   return h+padtop+padbottom+(border<<1);
145   }
146 
147 
148 // Returns true because a dial can receive focus
canFocus() const149 FXbool FXDial::canFocus() const { return true; }
150 
151 
152 // Set help using a message
onCmdSetHelp(FXObject *,FXSelector,void * ptr)153 long FXDial::onCmdSetHelp(FXObject*,FXSelector,void* ptr){
154   setHelpText(*((FXString*)ptr));
155   return 1;
156   }
157 
158 
159 // Get help using a message
onCmdGetHelp(FXObject *,FXSelector,void * ptr)160 long FXDial::onCmdGetHelp(FXObject*,FXSelector,void* ptr){
161   *((FXString*)ptr)=getHelpText();
162   return 1;
163   }
164 
165 
166 // Set tip using a message
onCmdSetTip(FXObject *,FXSelector,void * ptr)167 long FXDial::onCmdSetTip(FXObject*,FXSelector,void* ptr){
168   setTipText(*((FXString*)ptr));
169   return 1;
170   }
171 
172 
173 // Get tip using a message
onCmdGetTip(FXObject *,FXSelector,void * ptr)174 long FXDial::onCmdGetTip(FXObject*,FXSelector,void* ptr){
175   *((FXString*)ptr)=getTipText();
176   return 1;
177   }
178 
179 
180 // We were asked about tip text
onQueryTip(FXObject * sender,FXSelector sel,void * ptr)181 long FXDial::onQueryTip(FXObject* sender,FXSelector sel,void* ptr){
182   if(FXFrame::onQueryTip(sender,sel,ptr)) return 1;
183   if((flags&FLAG_TIP) && !tip.empty()){
184     sender->handle(this,FXSEL(SEL_COMMAND,ID_SETSTRINGVALUE),(void*)&tip);
185     return 1;
186     }
187   return 0;
188   }
189 
190 
191 // We were asked about status text
onQueryHelp(FXObject * sender,FXSelector sel,void * ptr)192 long FXDial::onQueryHelp(FXObject* sender,FXSelector sel,void* ptr){
193   if(FXFrame::onQueryHelp(sender,sel,ptr)) return 1;
194   if((flags&FLAG_HELP) && !help.empty()){
195     sender->handle(this,FXSEL(SEL_COMMAND,ID_SETSTRINGVALUE),(void*)&help);
196     return 1;
197     }
198   return 0;
199   }
200 
201 
202 // Update value from a message
onCmdSetValue(FXObject *,FXSelector,void * ptr)203 long FXDial::onCmdSetValue(FXObject*,FXSelector,void* ptr){
204   setValue((FXint)(FXival)ptr);
205   return 1;
206   }
207 
208 
209 // Update value from a message
onCmdSetIntValue(FXObject *,FXSelector,void * ptr)210 long FXDial::onCmdSetIntValue(FXObject*,FXSelector,void* ptr){
211   setValue(*((FXint*)ptr));
212   return 1;
213   }
214 
215 
216 // Obtain value from text field
onCmdGetIntValue(FXObject *,FXSelector,void * ptr)217 long FXDial::onCmdGetIntValue(FXObject*,FXSelector,void* ptr){
218   *((FXint*)ptr)=getValue();
219   return 1;
220   }
221 
222 
223 // Update value from a message
onCmdSetLongValue(FXObject *,FXSelector,void * ptr)224 long FXDial::onCmdSetLongValue(FXObject*,FXSelector,void* ptr){
225   setValue((FXint)*((FXlong*)ptr));
226   return 1;
227   }
228 
229 
230 // Obtain value with a message
onCmdGetLongValue(FXObject *,FXSelector,void * ptr)231 long FXDial::onCmdGetLongValue(FXObject*,FXSelector,void* ptr){
232   *((FXlong*)ptr)=(FXlong)getValue();
233   return 1;
234   }
235 
236 
237 // Update value from a message
onCmdSetRealValue(FXObject *,FXSelector,void * ptr)238 long FXDial::onCmdSetRealValue(FXObject*,FXSelector,void* ptr){
239   setValue((FXint)*((FXdouble*)ptr));
240   return 1;
241   }
242 
243 
244 // Obtain value from text field
onCmdGetRealValue(FXObject *,FXSelector,void * ptr)245 long FXDial::onCmdGetRealValue(FXObject*,FXSelector,void* ptr){
246   *((FXdouble*)ptr) = (FXdouble)getValue();
247   return 1;
248   }
249 
250 
251 // Update range from a message
onCmdSetIntRange(FXObject *,FXSelector,void * ptr)252 long FXDial::onCmdSetIntRange(FXObject*,FXSelector,void* ptr){
253   setRange(((FXint*)ptr)[0],((FXint*)ptr)[1]);
254   return 1;
255   }
256 
257 
258 // Get range with a message
onCmdGetIntRange(FXObject *,FXSelector,void * ptr)259 long FXDial::onCmdGetIntRange(FXObject*,FXSelector,void* ptr){
260   getRange(((FXint*)ptr)[0],((FXint*)ptr)[1]);
261   return 1;
262   }
263 
264 
265 // Update range from a message
onCmdSetRealRange(FXObject *,FXSelector,void * ptr)266 long FXDial::onCmdSetRealRange(FXObject*,FXSelector,void* ptr){
267   setRange((FXint) ((FXdouble*)ptr)[0],(FXint) ((FXdouble*)ptr)[1]);
268   return 1;
269   }
270 
271 
272 // Get range with a message
onCmdGetRealRange(FXObject *,FXSelector,void * ptr)273 long FXDial::onCmdGetRealRange(FXObject*,FXSelector,void* ptr){
274   ((FXdouble*)ptr)[0]=(FXdouble)range[0];
275   ((FXdouble*)ptr)[1]=(FXdouble)range[1];
276   return 1;
277   }
278 
279 
280 // Pressed LEFT button
onLeftBtnPress(FXObject *,FXSelector,void * ptr)281 long FXDial::onLeftBtnPress(FXObject*,FXSelector,void* ptr){
282   FXEvent *event=(FXEvent*)ptr;
283   flags&=~FLAG_TIP;
284   handle(this,FXSEL(SEL_FOCUS_SELF,0),ptr);
285   if(isEnabled()){
286     grab();
287     if(target && target->tryHandle(this,FXSEL(SEL_LEFTBUTTONPRESS,message),ptr)) return 1;
288     if(options&DIAL_HORIZONTAL)
289       dragPoint=event->win_x;
290     else
291       dragPoint=event->win_y;
292     dragPos=pos;
293     flags|=FLAG_PRESSED;
294     flags&=~FLAG_UPDATE;
295     return 1;
296     }
297   return 0;
298   }
299 
300 
301 // Released LEFT button
onLeftBtnRelease(FXObject *,FXSelector,void * ptr)302 long FXDial::onLeftBtnRelease(FXObject*,FXSelector,void* ptr){
303   FXuint changed=(flags&FLAG_CHANGED);
304   if(isEnabled()){
305     ungrab();
306     flags|=FLAG_UPDATE;
307     flags&=~FLAG_PRESSED;
308     flags&=~FLAG_CHANGED;
309     if(target && target->tryHandle(this,FXSEL(SEL_LEFTBUTTONRELEASE,message),ptr)) return 1;
310     if(changed && target) target->tryHandle(this,FXSEL(SEL_COMMAND,message),(void*)(FXival)pos);
311     return 1;
312     }
313   return 0;
314   }
315 
316 
317 // The widget lost the grab for some reason
onUngrabbed(FXObject * sender,FXSelector sel,void * ptr)318 long FXDial::onUngrabbed(FXObject* sender,FXSelector sel,void* ptr){
319   FXFrame::onUngrabbed(sender,sel,ptr);
320   flags&=~FLAG_PRESSED;
321   flags&=~FLAG_CHANGED;
322   flags|=FLAG_UPDATE;
323   return 1;
324   }
325 
326 
327 // Moving
onMotion(FXObject *,FXSelector,void * ptr)328 long FXDial::onMotion(FXObject*,FXSelector,void* ptr){
329   FXEvent *event=(FXEvent*)ptr;
330   FXint travel,size,delta,newpos,tmp;
331   if(flags&FLAG_PRESSED){
332     if(options&DIAL_HORIZONTAL){
333       size=width-(border<<1);
334       travel=event->win_x-dragPoint;
335       }
336     else{
337       size=height-(border<<1);
338       travel=dragPoint-event->win_y;
339       }
340     if(size<100) size=100;
341     if(travel){
342       delta=(incr*travel)/(2*size);
343       if(options&DIAL_CYCLIC){
344         tmp=dragPos+delta-range[0];
345         while(tmp<0) tmp+=(range[1]-range[0]+1);
346         newpos=range[0]+tmp%(range[1]-range[0]+1);
347         }
348       else{
349         if(dragPos+delta<range[0]) newpos=range[0];
350         else if(dragPos+delta>range[1]) newpos=range[1];
351         else newpos=dragPos+delta;
352         }
353       if(pos!=newpos){
354         pos=newpos;
355         FXASSERT(range[0]<=pos && pos<=range[1]);
356         notchAngle=(notchOffset+(3600*(pos-range[0]))/incr)%3600;
357         update(border+padleft+1,border+padtop+1,width-(border<<1)-padleft-padright-2,height-(border<<1)-padtop-padbottom-2);
358         flags|=FLAG_CHANGED;
359         if(target) target->tryHandle(this,FXSEL(SEL_CHANGED,message),(void*)(FXival)pos);
360         return 1;
361         }
362       }
363     }
364   return 0;
365   }
366 
367 
368 // Mouse wheel (Thanks to  "Lyle Johnson" <lyle@knology.net>)
onMouseWheel(FXObject *,FXSelector,void * ptr)369 long FXDial::onMouseWheel(FXObject*,FXSelector,void* ptr){
370   FXEvent *event=(FXEvent*)ptr;
371   FXint delta,newpos,tmp,mod;
372 
373   // Determine the change in dial units; this probably still needs
374   // tweaking. The formula below adjusts the dial position by 1/36
375   // of a revolution for each "hop" of the mousewheel.
376   delta=(event->code*incr)/4320;
377 
378   // Determine new dial position
379   if(options&DIAL_CYCLIC){
380     mod=range[1]-range[0]+1;
381     tmp=pos+delta-range[0];
382     while(tmp<0) tmp+=mod;
383     newpos=range[0]+tmp%mod;        // FIXME small problem if range[1]-range[0]+1 is UINT_MAX
384     }
385   else{
386     if(pos+delta<range[0]) newpos=range[0];
387     else if(pos+delta>range[1]) newpos=range[1];
388     else newpos=pos+delta;
389     }
390   if(pos!=newpos){
391     pos=newpos;
392     FXASSERT(range[0]<=pos && pos<=range[1]);
393     notchAngle=(notchOffset+(3600*(pos-range[0]))/incr)%3600;
394     update(border+padleft+1,border+padtop+1,width-(border<<1)-padleft-padright-2,height-(border<<1)-padtop-padbottom-2);
395     if(target) target->tryHandle(this,FXSEL(SEL_COMMAND,message),(void*)(FXival)pos);
396     }
397   return 1;
398   }
399 
400 
401 // Keyboard press
onKeyPress(FXObject *,FXSelector,void * ptr)402 long FXDial::onKeyPress(FXObject*,FXSelector,void* ptr){
403   FXEvent* event=(FXEvent*)ptr;
404   if(isEnabled()){
405     if(target && target->tryHandle(this,FXSEL(SEL_KEYPRESS,message),ptr)) return 1;
406     switch(event->code){
407       case KEY_Left:
408       case KEY_KP_Left:
409         if(options&DIAL_HORIZONTAL) goto dec;
410         break;
411       case KEY_Right:
412       case KEY_KP_Right:
413         if(options&DIAL_HORIZONTAL) goto inc;
414         break;
415       case KEY_Up:
416       case KEY_KP_Up:
417         if(!(options&DIAL_HORIZONTAL)) goto inc;
418         break;
419       case KEY_Down:
420       case KEY_KP_Down:
421         if(!(options&DIAL_HORIZONTAL)) goto dec;
422         break;
423       case KEY_plus:
424       case KEY_KP_Add:
425 inc:    setValue(pos+1,true);
426         return 1;
427       case KEY_minus:
428       case KEY_KP_Subtract:
429 dec:    setValue(pos-1,true);
430         return 1;
431       }
432     }
433   return 0;
434   }
435 
436 
437 // Keyboard release
onKeyRelease(FXObject *,FXSelector,void * ptr)438 long FXDial::onKeyRelease(FXObject*,FXSelector,void* ptr){
439   FXEvent* event=(FXEvent*)ptr;
440   if(isEnabled()){
441     if(target && target->tryHandle(this,FXSEL(SEL_KEYRELEASE,message),ptr)) return 1;
442     switch(event->code){
443       case KEY_Left:
444       case KEY_KP_Left:
445       case KEY_Right:
446       case KEY_KP_Right:
447         if(options&DIAL_HORIZONTAL) return 1;
448         break;
449       case KEY_Up:
450       case KEY_KP_Up:
451       case KEY_Down:
452       case KEY_KP_Down:
453         if(!(options&DIAL_HORIZONTAL)) return 1;
454         break;
455       case KEY_plus:
456       case KEY_KP_Add:
457       case KEY_KP_Subtract:
458       case KEY_minus:
459         return 1;
460       }
461     }
462   return 0;
463   }
464 
465 
466 // Handle repaint
onPaint(FXObject *,FXSelector,void * ptr)467 long FXDial::onPaint(FXObject*,FXSelector,void* ptr){
468   const FXdouble fac=0.5*PI/((FXdouble)(NUMSIDECOLORS-1));
469   FXEvent *event=(FXEvent*)ptr;
470   FXint i,size,u,d,lu,ld,t,r,fm,to,off,ang;
471   FXuint rmax,gmax,bmax,red,green,blue;
472   FXint lt,rt,tp,bm;
473   FXdouble mid,tmp;
474   FXDCWindow dc(this,event);
475 
476   // Paint background
477   dc.setForeground(backColor);
478   dc.fillRectangle(0,0,width,height);
479 
480   off=(notchAngle+3600)%notchSpacing;
481   fm=off/notchSpacing;
482   to=(off+1800-notchSpacing+1)/notchSpacing;
483 
484   // Rectangle of dial
485   lt=border+padleft+1;
486   rt=width-border-padright-2;
487   tp=border+padtop+1;
488   bm=height-border-padbottom-2;
489 
490   // Colors for sides
491   rmax=(126*FXREDVAL(backColor))/100;
492   gmax=(126*FXGREENVAL(backColor))/100;
493   bmax=(126*FXBLUEVAL(backColor))/100;
494   rmax=FXMIN(rmax,255);
495   gmax=FXMIN(gmax,255);
496   bmax=FXMIN(bmax,255);
497 
498   // Horizontal dial
499   if(options&DIAL_HORIZONTAL){
500     size=rt-lt;
501     r=size/2-1;
502     mid=0.5*(lt+rt);
503     for(i=fm; i<=to; i++){
504       ang=i*notchSpacing+off;
505       t=(FXint)(mid-r*Math::cos(0.1*DTOR*ang));
506       if((options&DIAL_HAS_NOTCH) && (ang+3600)%3600==notchAngle){
507         dc.setForeground(hiliteColor);
508         dc.drawLine(t-1,tp,t-1,bm);
509         dc.setForeground(notchColor);
510         dc.drawLine(t,tp,t,bm);
511         dc.drawLine(t+1,tp,t+1,bm);
512         dc.setForeground(borderColor);
513         dc.drawLine(t+2,tp,t+2,bm);
514         }
515       else{
516         if(ang<200){
517           dc.setForeground(shadowColor);
518           dc.drawLine(t,tp,t,bm);
519           dc.setForeground(borderColor);
520           dc.drawLine(t+1,tp,t+1,bm);
521           }
522         else if(ang<300){
523           dc.setForeground(borderColor);
524           dc.drawLine(t,tp,t,bm);
525           }
526         else if(ang<600){
527           dc.setForeground(hiliteColor);
528           dc.drawLine(t,tp,t,bm);
529           dc.setForeground(borderColor);
530           dc.drawLine(t+1,tp,t+1,bm);
531           }
532         else if(ang<1200){
533           dc.setForeground(hiliteColor);
534           dc.drawLine(t-1,tp,t-1,bm);
535           dc.drawLine(t,tp,t,bm);
536           dc.setForeground(borderColor);
537           dc.drawLine(t+1,tp,t+1,bm);
538           }
539         else if(ang<1500){
540           dc.setForeground(hiliteColor);
541           dc.drawLine(t,tp,t,bm);
542           dc.setForeground(borderColor);
543           dc.drawLine(t+1,tp,t+1,bm);
544           }
545         else if(ang<1600){
546           dc.setForeground(borderColor);
547           dc.drawLine(t,tp,t,bm);
548           }
549         else{
550           dc.setForeground(shadowColor);
551           dc.drawLine(t,tp,t,bm);
552           dc.setForeground(borderColor);
553           dc.drawLine(t-1,tp,t-1,bm);
554           }
555         }
556       }
557     dc.drawLine(lt,tp,lt,bm);
558     dc.drawLine(rt,tp,rt,bm);
559     lu=lt;
560     ld=rt;
561     for(i=0; i<NUMSIDECOLORS; i++){
562       tmp=r*Math::cos(fac*i);
563       u=(FXint)(mid-tmp);
564       d=(FXint)(mid+tmp);
565       red=(rmax*i)/(NUMSIDECOLORS-1);
566       green=(gmax*i)/(NUMSIDECOLORS-1);
567       blue=(bmax*i)/(NUMSIDECOLORS-1);
568       dc.setForeground(FXRGB(red,green,blue));
569       dc.drawLine(lu,tp,u,tp);
570       dc.drawLine(ld,tp,d,tp);
571       dc.drawLine(lu,bm,u,bm);
572       dc.drawLine(ld,bm,d,bm);
573       lu=u;
574       ld=d;
575       }
576     dc.drawLine(lu,tp,ld,tp);
577     dc.drawLine(lu,bm,ld,bm);
578     }
579 
580   // Vertical dial
581   else{
582     size=bm-tp;
583     r=size/2-1;
584     mid=0.5*(tp+bm);
585     for(i=fm; i<=to; i++){
586       ang=i*notchSpacing+off;
587       t=(FXint)(mid+r*Math::cos(0.1*DTOR*ang));
588       if((options&DIAL_HAS_NOTCH) && (ang+3600)%3600==notchAngle){
589         dc.setForeground(hiliteColor);
590         dc.drawLine(lt,t-1,rt,t-1);
591         dc.setForeground(notchColor);
592         dc.drawLine(lt,t,rt,t);
593         dc.drawLine(lt,t+1,rt,t+1);
594         dc.setForeground(borderColor);
595         dc.drawLine(lt,t+2,rt,t+2);
596         }
597       else{
598         if(ang<200){
599           dc.setForeground(borderColor);
600           dc.drawLine(lt,t,rt,t);
601           dc.setForeground(shadowColor);
602           dc.drawLine(lt,t-1,rt,t-1);
603           }
604         else if(ang<300){
605           dc.setForeground(borderColor);
606           dc.drawLine(lt,t,rt,t);
607           }
608         else if(ang<600){
609           dc.setForeground(hiliteColor);
610           dc.drawLine(lt,t,rt,t);
611           dc.setForeground(borderColor);
612           dc.drawLine(lt,t+1,rt,t+1);
613           }
614         else if(ang<1200){
615           dc.setForeground(hiliteColor);
616           dc.drawLine(lt,t-1,rt,t-1);
617           dc.drawLine(lt,t,rt,t);
618           dc.setForeground(borderColor);
619           dc.drawLine(lt,t+1,rt,t+1);
620           }
621         else if(ang<1500){
622           dc.setForeground(hiliteColor);
623           dc.drawLine(lt,t,rt,t);
624           dc.setForeground(borderColor);
625           dc.drawLine(lt,t+1,rt,t+1);
626           }
627         else if(ang<1600){
628           dc.setForeground(borderColor);
629           dc.drawLine(lt,t,rt,t);
630           }
631         else{
632           dc.setForeground(borderColor);
633           dc.drawLine(lt,t,rt,t);
634           dc.setForeground(shadowColor);
635           dc.drawLine(lt,t+1,rt,t+1);
636           }
637         }
638       }
639     dc.drawLine(lt,tp,rt,tp);
640     dc.drawLine(lt,bm,rt,bm);
641     lu=tp;
642     ld=bm;
643     for(i=0; i<NUMSIDECOLORS; i++){
644       tmp=r*Math::cos(fac*i);
645       u=(FXint)(mid-tmp);
646       d=(FXint)(mid+tmp);
647       red=(rmax*i)/(NUMSIDECOLORS-1);
648       green=(gmax*i)/(NUMSIDECOLORS-1);
649       blue=(bmax*i)/(NUMSIDECOLORS-1);
650       dc.setForeground(FXRGB(red,green,blue));
651       dc.drawLine(lt,lu,lt,u);
652       dc.drawLine(lt,ld,lt,d);
653       dc.drawLine(rt,lu,rt,u);
654       dc.drawLine(rt,ld,rt,d);
655       lu=u;
656       ld=d;
657       }
658     dc.drawLine(lt,lu,lt,ld);
659     dc.drawLine(rt,lu,rt,ld);
660     }
661 
662   // Border
663   drawFrame(dc,0,0,width,height);
664 
665   // Inner rectangle
666   dc.setForeground(shadowColor);
667   dc.drawRectangle(lt-1,tp-1,rt-lt+2,bm-tp+2);
668   return 1;
669   }
670 
671 
672 // Set dial range
setRange(FXint lo,FXint hi,FXbool notify)673 void FXDial::setRange(FXint lo,FXint hi,FXbool notify){
674   if(lo>hi){ fxerror("%s::setRange: trying to set negative range.\n",getClassName()); }
675   if(range[0]!=lo || range[1]!=hi){
676     range[0]=lo;
677     range[1]=hi;
678     setValue(pos,notify);
679     }
680   }
681 
682 
683 // Set dial value
setValue(FXint p,FXbool notify)684 void FXDial::setValue(FXint p,FXbool notify){
685   FXint n;
686   if(p<range[0]) p=range[0];
687   if(p>range[1]) p=range[1];
688   n=(notchOffset+(3600*(p-range[0]))/incr)%3600;
689   if(n!=notchAngle){
690     notchAngle=n;
691     update();
692     }
693   if(p!=pos){
694     pos=p;
695     if(notify && target){target->tryHandle(this,FXSEL(SEL_COMMAND,message),(void*)(FXival)pos);}
696     }
697   }
698 
699 
700 // Change increment, i.e. the amount of pos change per revolution
setRevolutionIncrement(FXint i)701 void FXDial::setRevolutionIncrement(FXint i){
702   incr=FXMAX(1,i);
703   notchAngle=(notchOffset+(3600*(pos-range[0]))/incr)%3600;
704   update();
705   }
706 
707 
708 // Change notch spacing
setNotchSpacing(FXint spacing)709 void FXDial::setNotchSpacing(FXint spacing){
710   if(spacing<1) spacing=1;
711   if(spacing>3600) spacing=3600;
712   while(3600%spacing) spacing--;    // Should be a divisor of 3600
713   if(notchSpacing!=spacing){
714     notchSpacing=spacing;
715     update();
716     }
717   }
718 
719 
720 // Change notch offset
setNotchOffset(FXint offset)721 void FXDial::setNotchOffset(FXint offset){
722   if(offset>3600) offset=3600;
723   if(offset<-3600) offset=-3600;
724   offset=(offset+3600)%3600;
725   if(offset!=notchOffset){
726     notchOffset=offset;
727     notchAngle=(notchOffset+(3600*(pos-range[0]))/incr)%3600;
728     update();
729     }
730   }
731 
732 
733 // Get dial options
getDialStyle() const734 FXuint FXDial::getDialStyle() const {
735   return (options&DIAL_MASK);
736   }
737 
738 
739 // Set dial options
setDialStyle(FXuint style)740 void FXDial::setDialStyle(FXuint style){
741   FXuint opts=(options&~DIAL_MASK) | (style&DIAL_MASK);
742   if(options!=opts){
743     options=opts;
744     recalc();
745     }
746   }
747 
748 
749 // Save object to stream
save(FXStream & store) const750 void FXDial::save(FXStream& store) const {
751   FXFrame::save(store);
752   store << notchAngle;
753   store << notchSpacing;
754   store << notchOffset;
755   store << notchColor;
756   store << range[0];
757   store << range[1];
758   store << incr;
759   store << pos;
760   store << help;
761   store << tip;
762   }
763 
764 
765 // Load object from stream
load(FXStream & store)766 void FXDial::load(FXStream& store){
767   FXFrame::load(store);
768   store >> notchAngle;
769   store >> notchSpacing;
770   store >> notchOffset;
771   store >> notchColor;
772   store >> range[0];
773   store >> range[1];
774   store >> incr;
775   store >> pos;
776   store >> help;
777   store >> tip;
778   }
779 
780 
781 // Change the Center Notch color
setNotchColor(FXColor clr)782 void FXDial::setNotchColor(FXColor clr){
783   if(clr!=notchColor){
784     notchColor=clr;
785     update();
786     }
787   }
788 
789 
790 // Change help text
setHelpText(const FXString & text)791 void FXDial::setHelpText(const FXString& text){
792   help=text;
793   }
794 
795 
796 // Change tip text
setTipText(const FXString & text)797 void FXDial::setTipText(const FXString& text){
798   tip=text;
799   }
800 
801 }
802 
803