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