1 /********************************************************************************
2 *                                                                               *
3 *                  R a d i o   B u t t o n    O b j e c t                       *
4 *                                                                               *
5 *********************************************************************************
6 * Copyright (C) 1998,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 "FXRadioButton.h"
42 
43 /*
44   To do:
45   - Need check-style also (stay in when pressed, pop out when unpressed).
46   - Who owns the icon(s)?
47   - Arrow buttons should auto-repeat with a timer of some kind
48   - "&Label\tTooltip\tHelptext\thttp://server/application/helponitem.html"
49   - CheckButton should send SEL_COMMAND.
50   - Default button mode:- should somehow get focus.
51   - Weird state change still possible using both keyboard and mouse if two
52     radio buttons are involved.
53 */
54 
55 
56 #define RADIOBUTTON_MASK  (RADIOBUTTON_AUTOGRAY|RADIOBUTTON_AUTOHIDE)
57 
58 using namespace FX;
59 
60 /*******************************************************************************/
61 
62 namespace FX {
63 
64 // Map
65 FXDEFMAP(FXRadioButton) FXRadioButtonMap[]={
66   FXMAPFUNC(SEL_UPDATE,0,FXRadioButton::onUpdate),
67   FXMAPFUNC(SEL_PAINT,0,FXRadioButton::onPaint),
68   FXMAPFUNC(SEL_ENTER,0,FXRadioButton::onEnter),
69   FXMAPFUNC(SEL_LEAVE,0,FXRadioButton::onLeave),
70   FXMAPFUNC(SEL_FOCUSIN,0,FXRadioButton::onFocusIn),
71   FXMAPFUNC(SEL_FOCUSOUT,0,FXRadioButton::onFocusOut),
72   FXMAPFUNC(SEL_UNGRABBED,0,FXRadioButton::onUngrabbed),
73   FXMAPFUNC(SEL_LEFTBUTTONPRESS,0,FXRadioButton::onLeftBtnPress),
74   FXMAPFUNC(SEL_LEFTBUTTONRELEASE,0,FXRadioButton::onLeftBtnRelease),
75   FXMAPFUNC(SEL_KEYPRESS,0,FXRadioButton::onKeyPress),
76   FXMAPFUNC(SEL_KEYRELEASE,0,FXRadioButton::onKeyRelease),
77   FXMAPFUNC(SEL_KEYPRESS,FXWindow::ID_HOTKEY,FXRadioButton::onHotKeyPress),
78   FXMAPFUNC(SEL_KEYRELEASE,FXWindow::ID_HOTKEY,FXRadioButton::onHotKeyRelease),
79   FXMAPFUNC(SEL_COMMAND,FXRadioButton::ID_CHECK,FXRadioButton::onCheck),
80   FXMAPFUNC(SEL_COMMAND,FXRadioButton::ID_UNCHECK,FXRadioButton::onUncheck),
81   FXMAPFUNC(SEL_COMMAND,FXRadioButton::ID_UNKNOWN,FXRadioButton::onUnknown),
82   FXMAPFUNC(SEL_COMMAND,FXRadioButton::ID_SETVALUE,FXRadioButton::onCmdSetValue),
83   FXMAPFUNC(SEL_COMMAND,FXRadioButton::ID_SETINTVALUE,FXRadioButton::onCmdSetIntValue),
84   FXMAPFUNC(SEL_COMMAND,FXRadioButton::ID_GETINTVALUE,FXRadioButton::onCmdGetIntValue),
85   };
86 
87 
88 // Object implementation
FXIMPLEMENT(FXRadioButton,FXLabel,FXRadioButtonMap,ARRAYNUMBER (FXRadioButtonMap))89 FXIMPLEMENT(FXRadioButton,FXLabel,FXRadioButtonMap,ARRAYNUMBER(FXRadioButtonMap))
90 
91 
92 // Deserialization
93 FXRadioButton::FXRadioButton(){
94   radioColor=0;
95   diskColor=0;
96   check=false;
97   oldcheck=false;
98   }
99 
100 
101 // Make a check button
FXRadioButton(FXComposite * p,const FXString & text,FXObject * tgt,FXSelector sel,FXuint opts,FXint x,FXint y,FXint w,FXint h,FXint pl,FXint pr,FXint pt,FXint pb)102 FXRadioButton::FXRadioButton(FXComposite* p,const FXString& text,FXObject* tgt,FXSelector sel,FXuint opts,FXint x,FXint y,FXint w,FXint h,FXint pl,FXint pr,FXint pt,FXint pb):FXLabel(p,text,NULL,opts,x,y,w,h,pl,pr,pt,pb){
103   radioColor=getApp()->getForeColor();
104   diskColor=getApp()->getBackColor();
105   target=tgt;
106   message=sel;
107   check=false;
108   oldcheck=false;
109   }
110 
111 
112 // If window can have focus
canFocus() const113 FXbool FXRadioButton::canFocus() const { return true; }
114 
115 
116 // Get default width
getDefaultWidth()117 FXint FXRadioButton::getDefaultWidth(){
118   FXint tw=0,s=0,w;
119   if(!label.empty()){
120     tw=labelWidth(label);
121     s=4;
122     }
123   if(!(options&(ICON_AFTER_TEXT|ICON_BEFORE_TEXT))) w=FXMAX(tw,13); else w=tw+13+s;
124   return padleft+padright+w+(border<<1);
125   }
126 
127 
128 // Get default height
getDefaultHeight()129 FXint FXRadioButton::getDefaultHeight(){
130   FXint th=0,h;
131   if(!label.empty()){
132     th=labelHeight(label);
133     }
134   if(!(options&(ICON_ABOVE_TEXT|ICON_BELOW_TEXT))) h=FXMAX(th,13); else h=th+13;
135   return padtop+padbottom+h+(border<<1);
136   }
137 
138 
139 // Check button
setCheck(FXuchar s,FXbool notify)140 void FXRadioButton::setCheck(FXuchar s,FXbool notify){
141   if(check!=s){
142     check=s;
143     update();
144     if(notify && target){target->tryHandle(this,FXSEL(SEL_COMMAND,message),(void*)(FXuval)check);}
145     }
146   }
147 
148 
149 // Change state to checked
onCheck(FXObject *,FXSelector,void *)150 long FXRadioButton::onCheck(FXObject*,FXSelector,void*){
151   setCheck(true);
152   return 1;
153   }
154 
155 
156 // Change state to unchecked
onUncheck(FXObject *,FXSelector,void *)157 long FXRadioButton::onUncheck(FXObject*,FXSelector,void*){
158   setCheck(false);
159   return 1;
160   }
161 
162 
163 // Change state to indeterminate
onUnknown(FXObject *,FXSelector,void *)164 long FXRadioButton::onUnknown(FXObject*,FXSelector,void*){
165   setCheck(maybe);
166   return 1;
167   }
168 
169 
170 // Update value from a message
onCmdSetValue(FXObject *,FXSelector,void * ptr)171 long FXRadioButton::onCmdSetValue(FXObject*,FXSelector,void* ptr){
172   setCheck((FXuchar)(FXuval)ptr);
173   return 1;
174   }
175 
176 
177 // Update value from a message
onCmdSetIntValue(FXObject *,FXSelector,void * ptr)178 long FXRadioButton::onCmdSetIntValue(FXObject*,FXSelector,void* ptr){
179   setCheck((FXuchar)*((FXint*)ptr));
180   return 1;
181   }
182 
183 
184 // Obtain value from text field
onCmdGetIntValue(FXObject *,FXSelector,void * ptr)185 long FXRadioButton::onCmdGetIntValue(FXObject*,FXSelector,void* ptr){
186   *((FXint*)ptr)=getCheck();
187   return 1;
188   }
189 
190 
191 // Implement auto-hide or auto-gray modes
onUpdate(FXObject * sender,FXSelector sel,void * ptr)192 long FXRadioButton::onUpdate(FXObject* sender,FXSelector sel,void* ptr){
193   if(!FXLabel::onUpdate(sender,sel,ptr)){
194     if(options&RADIOBUTTON_AUTOHIDE){if(shown()){hide();recalc();}}
195     if(options&RADIOBUTTON_AUTOGRAY){disable();}
196     }
197   return 1;
198   }
199 
200 
201 // Gained focus
onFocusIn(FXObject * sender,FXSelector sel,void * ptr)202 long FXRadioButton::onFocusIn(FXObject* sender,FXSelector sel,void* ptr){
203   FXLabel::onFocusIn(sender,sel,ptr);
204   update(border,border,width-(border<<1),height-(border<<1));
205   return 1;
206   }
207 
208 
209 // Lost focus
onFocusOut(FXObject * sender,FXSelector sel,void * ptr)210 long FXRadioButton::onFocusOut(FXObject* sender,FXSelector sel,void* ptr){
211   FXLabel::onFocusOut(sender,sel,ptr);
212   update(border,border,width-(border<<1),height-(border<<1));
213   return 1;
214   }
215 
216 
217 // Entered button
onEnter(FXObject * sender,FXSelector sel,void * ptr)218 long FXRadioButton::onEnter(FXObject* sender,FXSelector sel,void* ptr){
219   FXLabel::onEnter(sender,sel,ptr);
220   if(isEnabled() && (flags&FLAG_PRESSED)) setCheck(true);
221   return 1;
222   }
223 
224 
225 // Left button
onLeave(FXObject * sender,FXSelector sel,void * ptr)226 long FXRadioButton::onLeave(FXObject* sender,FXSelector sel,void* ptr){
227   FXLabel::onLeave(sender,sel,ptr);
228   if(isEnabled() && (flags&FLAG_PRESSED)) setCheck(oldcheck);
229   return 1;
230   }
231 
232 
233 // Pressed mouse button
onLeftBtnPress(FXObject *,FXSelector,void * ptr)234 long FXRadioButton::onLeftBtnPress(FXObject*,FXSelector,void* ptr){
235   handle(this,FXSEL(SEL_FOCUS_SELF,0),ptr);
236   flags&=~FLAG_TIP;
237   if(isEnabled() && !(flags&FLAG_PRESSED)){
238     grab();
239     if(target && target->tryHandle(this,FXSEL(SEL_LEFTBUTTONPRESS,message),ptr)) return 1;
240     oldcheck=check;
241     setCheck(true);
242     flags|=FLAG_PRESSED;
243     flags&=~FLAG_UPDATE;
244     return 1;
245     }
246   return 0;
247   }
248 
249 
250 // Released mouse button
onLeftBtnRelease(FXObject *,FXSelector,void * ptr)251 long FXRadioButton::onLeftBtnRelease(FXObject*,FXSelector,void* ptr){
252   if(isEnabled() && (flags&FLAG_PRESSED)){
253     ungrab();
254     if(target && target->tryHandle(this,FXSEL(SEL_LEFTBUTTONRELEASE,message),ptr)) return 1;
255     flags|=FLAG_UPDATE;
256     flags&=~FLAG_PRESSED;
257     if(check!=oldcheck && target) target->tryHandle(this,FXSEL(SEL_COMMAND,message),(void*)(FXuval)true);
258     return 1;
259     }
260   return 0;
261   }
262 
263 
264 // Lost the grab for some reason
onUngrabbed(FXObject * sender,FXSelector sel,void * ptr)265 long FXRadioButton::onUngrabbed(FXObject* sender,FXSelector sel,void* ptr){
266   FXLabel::onUngrabbed(sender,sel,ptr);
267   setCheck(oldcheck);
268   flags&=~FLAG_PRESSED;
269   flags|=FLAG_UPDATE;
270   return 1;
271   }
272 
273 
274 // Key Press
onKeyPress(FXObject *,FXSelector,void * ptr)275 long FXRadioButton::onKeyPress(FXObject*,FXSelector,void* ptr){
276   FXEvent* event=(FXEvent*)ptr;
277   flags&=~FLAG_TIP;
278   if(isEnabled()){
279     if(target && target->tryHandle(this,FXSEL(SEL_KEYPRESS,message),ptr)) return 1;
280     if(!(flags&FLAG_PRESSED) && (event->code==KEY_space || event->code==KEY_KP_Space)){
281       oldcheck=check;
282       setCheck(true);
283       flags|=FLAG_PRESSED;
284       flags&=~FLAG_UPDATE;
285       return 1;
286       }
287     }
288   return 0;
289   }
290 
291 
292 // Key Release
onKeyRelease(FXObject *,FXSelector,void * ptr)293 long FXRadioButton::onKeyRelease(FXObject*,FXSelector,void* ptr){
294   FXEvent* event=(FXEvent*)ptr;
295   if(isEnabled()){
296     if(target && target->tryHandle(this,FXSEL(SEL_KEYRELEASE,message),ptr)) return 1;
297     if((flags&FLAG_PRESSED) && (event->code==KEY_space || event->code==KEY_KP_Space)){
298       flags|=FLAG_UPDATE;
299       flags&=~FLAG_PRESSED;
300       if(check!=oldcheck && target) target->tryHandle(this,FXSEL(SEL_COMMAND,message),(void*)(FXuval)true);
301       return 1;
302       }
303     }
304   return 0;
305   }
306 
307 
308 // Hot key combination pressed
onHotKeyPress(FXObject *,FXSelector,void * ptr)309 long FXRadioButton::onHotKeyPress(FXObject*,FXSelector,void* ptr){
310   handle(this,FXSEL(SEL_FOCUS_SELF,0),ptr);
311   flags&=~FLAG_TIP;
312   if(isEnabled() && !(flags&FLAG_PRESSED)){
313     oldcheck=check;
314     setCheck(true);
315     flags|=FLAG_PRESSED;
316     flags&=~FLAG_UPDATE;
317     }
318   return 1;
319   }
320 
321 
322 // Hot key combination released
onHotKeyRelease(FXObject *,FXSelector,void *)323 long FXRadioButton::onHotKeyRelease(FXObject*,FXSelector,void*){
324   flags&=~FLAG_TIP;
325   if(isEnabled() && (flags&FLAG_PRESSED)){
326     flags|=FLAG_UPDATE;
327     flags&=~FLAG_PRESSED;
328     if(check!=oldcheck && target) target->tryHandle(this,FXSEL(SEL_COMMAND,message),(void*)(FXuval)true);
329     }
330   return 1;
331   }
332 
333 
334 // Handle repaint
onPaint(FXObject *,FXSelector,void * ptr)335 long FXRadioButton::onPaint(FXObject*,FXSelector,void* ptr){
336   FXEvent *ev=(FXEvent*)ptr;
337   FXint tw=0,th=0,tx,ty,ix,iy;
338   FXRectangle recs[6];
339   FXDCWindow dc(this,ev);
340 
341   dc.setForeground(backColor);
342   dc.fillRectangle(ev->rect.x,ev->rect.y,ev->rect.w,ev->rect.h);
343 
344   if(!label.empty()){
345     tw=labelWidth(label);
346     th=labelHeight(label);
347     }
348 
349   just_x(tx,ix,tw,13);
350   just_y(ty,iy,th,13);
351 
352 
353 /*
354       012345678901
355 
356    0      SSSS      0
357    1    SSBBBBSS    1
358    2   SBB    BBW   2
359    3   SB  BB  OW   3
360    4  SB  BBBB  OW  4
361    5  SB BBBBBB OW  5
362    6  SB BBBBBB OW  6
363    7  SB  BBBB  OW  7
364    8   SB  BB  OW   8
365    9   SBO    OOW   9
366    0    WWOOOOWW    0
367    1      WWWW      1
368 
369       012345678901
370 */
371 
372   // Inside
373   recs[0].x=ix+4; recs[0].y=iy+2; recs[0].w=4; recs[0].h=1;
374   recs[1].x=ix+3; recs[1].y=iy+3; recs[1].w=6; recs[1].h=1;
375   recs[2].x=ix+2; recs[2].y=iy+4; recs[2].w=8; recs[2].h=4;
376   recs[3].x=ix+3; recs[3].y=iy+8; recs[3].w=6; recs[3].h=1;
377   recs[4].x=ix+4; recs[4].y=iy+9; recs[4].w=4; recs[4].h=1;
378   if(!isEnabled())                   // fix by Daniel Gehriger (gehriger@linkcad.com)
379     dc.setForeground(baseColor);
380   else
381     dc.setForeground(diskColor);
382   dc.fillRectangles(recs,5);
383 
384   // Top left outside
385   recs[0].x=ix+4; recs[0].y=iy+0; recs[0].w=4; recs[0].h=1;
386   recs[1].x=ix+2; recs[1].y=iy+1; recs[1].w=2; recs[1].h=1;
387   recs[2].x=ix+8; recs[2].y=iy+1; recs[2].w=2; recs[2].h=1;
388   recs[3].x=ix+1; recs[3].y=iy+2; recs[3].w=1; recs[3].h=2;
389   recs[4].x=ix+0; recs[4].y=iy+4; recs[4].w=1; recs[4].h=4;
390   recs[5].x=ix+1; recs[5].y=iy+8; recs[5].w=1; recs[5].h=2;
391   dc.setForeground(shadowColor);
392   dc.fillRectangles(recs,6);
393 
394   // Top left inside
395   recs[0].x=ix+4; recs[0].y=iy+1; recs[0].w=4; recs[0].h=1;
396   recs[1].x=ix+2; recs[1].y=iy+2; recs[1].w=2; recs[1].h=1;
397   recs[2].x=ix+8; recs[2].y=iy+2; recs[2].w=2; recs[2].h=1;
398   recs[3].x=ix+2; recs[3].y=iy+3; recs[3].w=1; recs[3].h=1;
399   recs[4].x=ix+1; recs[4].y=iy+4; recs[4].w=1; recs[4].h=4;
400   recs[5].x=ix+2; recs[5].y=iy+8; recs[5].w=1; recs[5].h=2;
401   dc.setForeground(borderColor);
402   dc.fillRectangles(recs,6);
403 
404   // Bottom right outside
405   recs[0].x=ix+10;recs[0].y=iy+2; recs[0].w=1; recs[0].h=2;
406   recs[1].x=ix+11;recs[1].y=iy+4; recs[1].w=1; recs[1].h=4;
407   recs[2].x=ix+10;recs[2].y=iy+8; recs[2].w=1; recs[2].h=2;
408   recs[3].x=ix+8; recs[3].y=iy+10;recs[3].w=2; recs[3].h=1;
409   recs[4].x=ix+2; recs[4].y=iy+10;recs[4].w=2; recs[4].h=1;
410   recs[5].x=ix+4; recs[5].y=iy+11;recs[5].w=4; recs[5].h=1;
411   dc.setForeground(hiliteColor);
412   dc.fillRectangles(recs,6);
413 
414   // Bottom right inside
415   recs[0].x=ix+9; recs[0].y=iy+3; recs[0].w=1; recs[0].h=1;
416   recs[1].x=ix+10;recs[1].y=iy+4; recs[1].w=1; recs[1].h=4;
417   recs[2].x=ix+9; recs[2].y=iy+8; recs[2].w=1; recs[2].h=1;
418   recs[3].x=ix+8; recs[3].y=iy+9; recs[3].w=2; recs[3].h=1;
419   recs[4].x=ix+3; recs[4].y=iy+9; recs[4].w=1; recs[4].h=1;
420   recs[5].x=ix+4; recs[5].y=iy+10;recs[5].w=4; recs[5].h=1;
421   dc.setForeground(baseColor);
422   dc.fillRectangles(recs,6);
423 
424   // Ball inside
425   if(check!=false){
426     recs[0].x=ix+5; recs[0].y=iy+4; recs[0].w=2; recs[0].h=1;
427     recs[1].x=ix+4; recs[1].y=iy+5; recs[1].w=4; recs[1].h=2;
428     recs[2].x=ix+5; recs[2].y=iy+7; recs[2].w=2; recs[2].h=1;
429     if(isEnabled())
430       dc.setForeground(radioColor);
431     else
432       dc.setForeground(shadowColor);
433     dc.fillRectangles(recs,3);
434     }
435 
436   // Label
437   if(!label.empty()){
438     dc.setFont(font);
439     if(isEnabled()){
440       dc.setForeground(textColor);
441       drawLabel(dc,label,hotoff,tx,ty,tw,th);
442       if(hasFocus()){
443         dc.drawFocusRectangle(tx-1,ty-1,tw+2,th+2);
444         }
445       }
446     else{
447       dc.setForeground(hiliteColor);
448       drawLabel(dc,label,hotoff,tx+1,ty+1,tw,th);
449       dc.setForeground(shadowColor);
450       drawLabel(dc,label,hotoff,tx,ty,tw,th);
451       }
452     }
453   drawFrame(dc,0,0,width,height);
454   return 1;
455   }
456 
457 
458 // Set radio color
setRadioColor(FXColor clr)459 void FXRadioButton::setRadioColor(FXColor clr){
460   if(radioColor!=clr){
461     radioColor=clr;
462     update();
463     }
464   }
465 
466 
467 // Set disk color
setDiskColor(FXColor clr)468 void FXRadioButton::setDiskColor(FXColor clr){
469   if(clr!=diskColor){
470     diskColor=clr;
471     update();
472     }
473   }
474 
475 
476 // Change radio button style
setRadioButtonStyle(FXuint style)477 void FXRadioButton::setRadioButtonStyle(FXuint style){
478   FXuint opts=(options&~RADIOBUTTON_MASK) | (style&RADIOBUTTON_MASK);
479   if(options!=opts){
480     options=opts;
481     update();
482     }
483   }
484 
485 
486 // Return current radio button style
getRadioButtonStyle() const487 FXuint FXRadioButton::getRadioButtonStyle() const {
488   return (options&RADIOBUTTON_MASK);
489   }
490 
491 
492 // Save object to stream
save(FXStream & store) const493 void FXRadioButton::save(FXStream& store) const {
494   FXLabel::save(store);
495   store << radioColor;
496   store << diskColor;
497   }
498 
499 
500 // Load object from stream
load(FXStream & store)501 void FXRadioButton::load(FXStream& store){
502   FXLabel::load(store);
503   store >> radioColor;
504   store >> diskColor;
505   }
506 
507 }
508 
509