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