1 /********************************************************************************
2 * *
3 * B u t t o n O b j e c t s *
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 "FXIcon.h"
42 #include "FXShell.h"
43 #include "FXButton.h"
44
45
46 /*
47 Notes:
48 - Use flags for button instead of a whole integer
49 - Add ``flat'' toolbar style also
50 - Need check-style also (stay in when pressed, pop out when unpressed).
51 - Who owns the icon(s)?
52 - Arrow buttons should auto-repeat with a timer of some kind
53 - "&Label\tTooltip\tHelptext\thttp://server/application/helponitem.html"
54 - CheckButton should send SEL_COMMAND.
55 - Default button mode:- should somehow get focus.
56 - Add button multiple-click translations elsewhere
57 - Button should be able to behave like a check (radio) button.
58 - Need to draw ``around'' the icon etc. So it doesn't flash to background.
59 */
60
61 // Button styles
62 #define BUTTON_MASK (BUTTON_AUTOGRAY|BUTTON_AUTOHIDE|BUTTON_TOOLBAR|BUTTON_DEFAULT|BUTTON_INITIAL)
63
64 using namespace FX;
65
66 /*******************************************************************************/
67
68 namespace FX {
69
70 // Map
71 FXDEFMAP(FXButton) FXButtonMap[]={
72 FXMAPFUNC(SEL_UPDATE,0,FXButton::onUpdate),
73 FXMAPFUNC(SEL_PAINT,0,FXButton::onPaint),
74 FXMAPFUNC(SEL_ENTER,0,FXButton::onEnter),
75 FXMAPFUNC(SEL_LEAVE,0,FXButton::onLeave),
76 FXMAPFUNC(SEL_FOCUSIN,0,FXButton::onFocusIn),
77 FXMAPFUNC(SEL_FOCUSOUT,0,FXButton::onFocusOut),
78 FXMAPFUNC(SEL_UNGRABBED,0,FXButton::onUngrabbed),
79 FXMAPFUNC(SEL_LEFTBUTTONPRESS,0,FXButton::onLeftBtnPress),
80 FXMAPFUNC(SEL_LEFTBUTTONRELEASE,0,FXButton::onLeftBtnRelease),
81 FXMAPFUNC(SEL_KEYPRESS,0,FXButton::onKeyPress),
82 FXMAPFUNC(SEL_KEYRELEASE,0,FXButton::onKeyRelease),
83 FXMAPFUNC(SEL_KEYPRESS,FXButton::ID_HOTKEY,FXButton::onHotKeyPress),
84 FXMAPFUNC(SEL_KEYRELEASE,FXButton::ID_HOTKEY,FXButton::onHotKeyRelease),
85 FXMAPFUNC(SEL_COMMAND,FXButton::ID_CHECK,FXButton::onCheck),
86 FXMAPFUNC(SEL_COMMAND,FXButton::ID_UNCHECK,FXButton::onUncheck),
87 FXMAPFUNC(SEL_COMMAND,FXButton::ID_SETVALUE,FXButton::onCmdSetValue),
88 FXMAPFUNC(SEL_COMMAND,FXButton::ID_SETINTVALUE,FXButton::onCmdSetIntValue),
89 FXMAPFUNC(SEL_COMMAND,FXButton::ID_GETINTVALUE,FXButton::onCmdGetIntValue),
90 };
91
92
93 // Object implementation
FXIMPLEMENT(FXButton,FXLabel,FXButtonMap,ARRAYNUMBER (FXButtonMap))94 FXIMPLEMENT(FXButton,FXLabel,FXButtonMap,ARRAYNUMBER(FXButtonMap))
95
96
97 // Deserialization
98 FXButton::FXButton(){
99 state=STATE_UP;
100 }
101
102
103 // Construct and init
FXButton(FXComposite * p,const FXString & text,FXIcon * ic,FXObject * tgt,FXSelector sel,FXuint opts,FXint x,FXint y,FXint w,FXint h,FXint pl,FXint pr,FXint pt,FXint pb)104 FXButton::FXButton(FXComposite* p,const FXString& text,FXIcon* ic,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,ic,opts,x,y,w,h,pl,pr,pt,pb){
105 target=tgt;
106 message=sel;
107 state=STATE_UP;
108 if(options&BUTTON_INITIAL){
109 setInitial(true);
110 setDefault(true);
111 }
112 }
113
114
115 // If window can have focus
canFocus() const116 FXbool FXButton::canFocus() const { return true; }
117
118
119 // Set focus to this widget
setFocus()120 void FXButton::setFocus(){
121 FXLabel::setFocus();
122 if(options&BUTTON_DEFAULT) setDefault(true);
123 update();
124 }
125
126
127 // Kill focus to this widget
killFocus()128 void FXButton::killFocus(){
129 FXLabel::killFocus();
130 if(options&BUTTON_DEFAULT) setDefault(maybe);
131 update();
132 }
133
134
135 // Make widget drawn as default
setDefault(FXuchar flag)136 void FXButton::setDefault(FXuchar flag){
137 FXLabel::setDefault(flag);
138 update();
139 }
140
141
142 // Set button state
setState(FXuint s)143 void FXButton::setState(FXuint s){
144 if(state!=s){
145 state=s;
146 update();
147 }
148 }
149
150
151 // Update value from a message
onCmdSetValue(FXObject *,FXSelector,void * ptr)152 long FXButton::onCmdSetValue(FXObject*,FXSelector,void* ptr){
153 setState((FXuint)(FXuval)ptr);
154 return 1;
155 }
156
157
158 // Update value from a message
onCmdSetIntValue(FXObject *,FXSelector,void * ptr)159 long FXButton::onCmdSetIntValue(FXObject*,FXSelector,void* ptr){
160 setState(*((FXint*)ptr));
161 return 1;
162 }
163
164
165 // Obtain value from text field
onCmdGetIntValue(FXObject *,FXSelector,void * ptr)166 long FXButton::onCmdGetIntValue(FXObject*,FXSelector,void* ptr){
167 *((FXint*)ptr)=getState();
168 return 1;
169 }
170
171
172 // Check the menu button
onCheck(FXObject *,FXSelector,void *)173 long FXButton::onCheck(FXObject*,FXSelector,void*){
174 setState(STATE_ENGAGED);
175 return 1;
176 }
177
178
179 // Check the menu button
onUncheck(FXObject *,FXSelector,void *)180 long FXButton::onUncheck(FXObject*,FXSelector,void*){
181 setState(STATE_UP);
182 return 1;
183 }
184
185
186 // Implement auto-hide or auto-gray modes
onUpdate(FXObject * sender,FXSelector sel,void * ptr)187 long FXButton::onUpdate(FXObject* sender,FXSelector sel,void* ptr){
188 if(!FXLabel::onUpdate(sender,sel,ptr)){
189 if(options&BUTTON_AUTOHIDE){if(shown()){hide();recalc();}}
190 if(options&BUTTON_AUTOGRAY){disable();}
191 }
192 return 1;
193 }
194
195
196 // Gained focus
onFocusIn(FXObject * sender,FXSelector sel,void * ptr)197 long FXButton::onFocusIn(FXObject* sender,FXSelector sel,void* ptr){
198 FXLabel::onFocusIn(sender,sel,ptr);
199 update();
200 return 1;
201 }
202
203
204 // Lost focus
onFocusOut(FXObject * sender,FXSelector sel,void * ptr)205 long FXButton::onFocusOut(FXObject* sender,FXSelector sel,void* ptr){
206 FXLabel::onFocusOut(sender,sel,ptr);
207 update();
208 return 1;
209 }
210
211
212 // Entered button
onEnter(FXObject * sender,FXSelector sel,void * ptr)213 long FXButton::onEnter(FXObject* sender,FXSelector sel,void* ptr){
214 FXLabel::onEnter(sender,sel,ptr);
215 if(isEnabled()){
216 if((flags&FLAG_PRESSED) && (state!=STATE_ENGAGED)) setState(STATE_DOWN);
217 if(options&BUTTON_TOOLBAR) update();
218 }
219 return 1;
220 }
221
222
223 // Left button
onLeave(FXObject * sender,FXSelector sel,void * ptr)224 long FXButton::onLeave(FXObject* sender,FXSelector sel,void* ptr){
225 FXLabel::onLeave(sender,sel,ptr);
226 if(isEnabled()){
227 if((flags&FLAG_PRESSED) && (state!=STATE_ENGAGED)) setState(STATE_UP);
228 if(options&BUTTON_TOOLBAR) update();
229 }
230 return 1;
231 }
232
233
234 // Pressed mouse button
onLeftBtnPress(FXObject *,FXSelector,void * ptr)235 long FXButton::onLeftBtnPress(FXObject*,FXSelector,void* ptr){
236 handle(this,FXSEL(SEL_FOCUS_SELF,0),ptr);
237 flags&=~FLAG_TIP;
238 if(isEnabled() && !(flags&FLAG_PRESSED)){
239 grab();
240 if(target && target->tryHandle(this,FXSEL(SEL_LEFTBUTTONPRESS,message),ptr)) return 1;
241 if(state!=STATE_ENGAGED) setState(STATE_DOWN);
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 FXButton::onLeftBtnRelease(FXObject*,FXSelector,void* ptr){
252 FXbool click=(state==STATE_DOWN);
253 if(isEnabled() && (flags&FLAG_PRESSED)){
254 ungrab();
255 flags|=FLAG_UPDATE;
256 flags&=~FLAG_PRESSED;
257 if(target && target->tryHandle(this,FXSEL(SEL_LEFTBUTTONRELEASE,message),ptr)) return 1;
258 if(state!=STATE_ENGAGED) setState(STATE_UP);
259 if(click && target){ target->tryHandle(this,FXSEL(SEL_COMMAND,message),(void*)(FXuval)1); }
260 return 1;
261 }
262 return 0;
263 }
264
265
266 // Lost the grab for some reason
onUngrabbed(FXObject * sender,FXSelector sel,void * ptr)267 long FXButton::onUngrabbed(FXObject* sender,FXSelector sel,void* ptr){
268 FXLabel::onUngrabbed(sender,sel,ptr);
269 if(state!=STATE_ENGAGED) setState(STATE_UP);
270 flags&=~FLAG_PRESSED;
271 flags|=FLAG_UPDATE;
272 return 1;
273 }
274
275
276 // Key Press
onKeyPress(FXObject *,FXSelector,void * ptr)277 long FXButton::onKeyPress(FXObject*,FXSelector,void* ptr){
278 FXEvent* event=(FXEvent*)ptr;
279 flags&=~FLAG_TIP;
280 if(isEnabled()){
281 if(target && target->tryHandle(this,FXSEL(SEL_KEYPRESS,message),ptr)) return 1;
282 if(!(flags&FLAG_PRESSED) && ((event->code==KEY_space || event->code==KEY_KP_Space) || (isDefault() && (event->code==KEY_Return || event->code==KEY_KP_Enter)))){
283 if(state!=STATE_ENGAGED) setState(STATE_DOWN);
284 flags|=FLAG_PRESSED;
285 flags&=~FLAG_UPDATE;
286 return 1;
287 }
288 }
289 return 0;
290 }
291
292
293 // Key Release
onKeyRelease(FXObject *,FXSelector,void * ptr)294 long FXButton::onKeyRelease(FXObject*,FXSelector,void* ptr){
295 FXEvent* event=(FXEvent*)ptr;
296 FXbool click=(state==STATE_DOWN);
297 if(isEnabled()){
298 if(target && target->tryHandle(this,FXSEL(SEL_KEYRELEASE,message),ptr)) return 1;
299 if((flags&FLAG_PRESSED) && ((event->code==KEY_space || event->code==KEY_KP_Space) || (isDefault() && (event->code==KEY_Return || event->code==KEY_KP_Enter)))){
300 if(state!=STATE_ENGAGED) setState(STATE_UP);
301 flags|=FLAG_UPDATE;
302 flags&=~FLAG_PRESSED;
303 if(click && target){ target->tryHandle(this,FXSEL(SEL_COMMAND,message),(void*)(FXuval)1); }
304 return 1;
305 }
306 }
307 return 0;
308 }
309
310
311 // Hot key combination pressed
onHotKeyPress(FXObject *,FXSelector,void * ptr)312 long FXButton::onHotKeyPress(FXObject*,FXSelector,void* ptr){
313 flags&=~FLAG_TIP;
314 handle(this,FXSEL(SEL_FOCUS_SELF,0),ptr);
315 if(isEnabled() && !(flags&FLAG_PRESSED)){
316 if(state!=STATE_ENGAGED) setState(STATE_DOWN);
317 flags&=~FLAG_UPDATE;
318 flags|=FLAG_PRESSED;
319 }
320 return 1;
321 }
322
323
324 // Hot key combination released
onHotKeyRelease(FXObject *,FXSelector,void *)325 long FXButton::onHotKeyRelease(FXObject*,FXSelector,void*){
326 FXuint click=(state==STATE_DOWN);
327 if(isEnabled() && (flags&FLAG_PRESSED)){
328 if(state!=STATE_ENGAGED) setState(STATE_UP);
329 flags|=FLAG_UPDATE;
330 flags&=~FLAG_PRESSED;
331 if(click && target) target->tryHandle(this,FXSEL(SEL_COMMAND,message),(void*)(FXuval)1);
332 }
333 return 1;
334 }
335
336
337 // Handle repaint
onPaint(FXObject *,FXSelector,void * ptr)338 long FXButton::onPaint(FXObject*,FXSelector,void* ptr){
339 FXint tw=0,th=0,iw=0,ih=0,tx,ty,ix,iy;
340 FXEvent *ev=(FXEvent*)ptr;
341
342 // Start drawing
343 FXDCWindow dc(this,ev);
344
345 // Got a border at all?
346 if(options&(FRAME_RAISED|FRAME_SUNKEN)){
347
348 // Toolbar style
349 if(options&BUTTON_TOOLBAR){
350
351 // Enabled and cursor inside, and up
352 if(isEnabled() && underCursor() && (state==STATE_UP)){
353 dc.setForeground(backColor);
354 dc.fillRectangle(border,border,width-border*2,height-border*2);
355 if(options&FRAME_THICK) drawDoubleRaisedRectangle(dc,0,0,width,height);
356 else drawRaisedRectangle(dc,0,0,width,height);
357 }
358
359 // Enabled and cursor inside and down
360 else if(isEnabled() && underCursor() && (state==STATE_DOWN)){
361 dc.setForeground(backColor);
362 dc.fillRectangle(border,border,width-border*2,height-border*2);
363 if(options&FRAME_THICK) drawDoubleSunkenRectangle(dc,0,0,width,height);
364 else drawSunkenRectangle(dc,0,0,width,height);
365 }
366
367 // Enabled and checked
368 else if(isEnabled() && (state==STATE_ENGAGED)){
369 dc.setForeground(hiliteColor);
370 dc.fillRectangle(border,border,width-border*2,height-border*2);
371 if(options&FRAME_THICK) drawDoubleSunkenRectangle(dc,0,0,width,height);
372 else drawSunkenRectangle(dc,0,0,width,height);
373 }
374
375 // Disabled or unchecked or not under cursor
376 else{
377 dc.setForeground(backColor);
378 dc.fillRectangle(0,0,width,height);
379 }
380 }
381
382 // Normal style
383 else{
384
385 // Default
386 if(isDefault()){
387
388 // Draw in up state if disabled or up
389 if(!isEnabled() || (state==STATE_UP)){
390 dc.setForeground(backColor);
391 dc.fillRectangle(border+1,border+1,width-border*2-1,height-border*2-1);
392 if(options&FRAME_THICK) drawDoubleRaisedRectangle(dc,1,1,width-1,height-1);
393 else drawRaisedRectangle(dc,1,1,width-1,height-1);
394 }
395
396 // Draw sunken if enabled and either checked or pressed
397 else{
398 if(state==STATE_ENGAGED) dc.setForeground(hiliteColor); else dc.setForeground(backColor);
399 dc.fillRectangle(border,border,width-border*2-1,height-border*2-1);
400 if(options&FRAME_THICK) drawDoubleSunkenRectangle(dc,0,0,width-1,height-1);
401 else drawSunkenRectangle(dc,0,0,width-1,height-1);
402 }
403
404 // Black default border
405 drawBorderRectangle(dc,0,0,width,height);
406 }
407
408 // Non-Default
409 else{
410
411 // Draw in up state if disabled or up
412 if(!isEnabled() || (state==STATE_UP)){
413 dc.setForeground(backColor);
414 dc.fillRectangle(border,border,width-border*2,height-border*2);
415 if(options&FRAME_THICK) drawDoubleRaisedRectangle(dc,0,0,width,height);
416 else drawRaisedRectangle(dc,0,0,width,height);
417 }
418
419 // Draw sunken if enabled and either checked or pressed
420 else{
421 if(state==STATE_ENGAGED) dc.setForeground(hiliteColor); else dc.setForeground(backColor);
422 dc.fillRectangle(border,border,width-border*2,height-border*2);
423 if(options&FRAME_THICK) drawDoubleSunkenRectangle(dc,0,0,width,height);
424 else drawSunkenRectangle(dc,0,0,width,height);
425 }
426 }
427 }
428 }
429
430 // No borders
431 else{
432 if(isEnabled() && (state==STATE_ENGAGED)){
433 dc.setForeground(hiliteColor);
434 dc.fillRectangle(0,0,width,height);
435 }
436 else{
437 dc.setForeground(backColor);
438 dc.fillRectangle(0,0,width,height);
439 }
440 }
441
442 // Place text & icon
443 if(!label.empty()){
444 tw=labelWidth(label);
445 th=labelHeight(label);
446 }
447 if(icon){
448 iw=icon->getWidth();
449 ih=icon->getHeight();
450 }
451
452 just_x(tx,ix,tw,iw);
453 just_y(ty,iy,th,ih);
454
455 // Shift a bit when pressed
456 if(state && (options&(FRAME_RAISED|FRAME_SUNKEN))){ ++tx; ++ty; ++ix; ++iy; }
457
458 // Draw enabled state
459 if(isEnabled()){
460 if(icon){
461 dc.drawIcon(icon,ix,iy);
462 }
463 if(!label.empty()){
464 dc.setFont(font);
465 dc.setForeground(textColor);
466 drawLabel(dc,label,hotoff,tx,ty,tw,th);
467 }
468 if(hasFocus()){
469 dc.drawFocusRectangle(border+1,border+1,width-2*border-2,height-2*border-2);
470 }
471 }
472
473 // Draw grayed-out state
474 else{
475 if(icon){
476 dc.drawIconSunken(icon,ix,iy);
477 }
478 if(!label.empty()){
479 dc.setFont(font);
480 dc.setForeground(hiliteColor);
481 drawLabel(dc,label,hotoff,tx+1,ty+1,tw,th);
482 dc.setForeground(shadowColor);
483 drawLabel(dc,label,hotoff,tx,ty,tw,th);
484 }
485 }
486 return 1;
487 }
488
489
490 // Set button style
setButtonStyle(FXuint style)491 void FXButton::setButtonStyle(FXuint style){
492 FXuint opts=(options&~BUTTON_MASK) | (style&BUTTON_MASK);
493 if(options!=opts){
494 options=opts;
495 update();
496 }
497 }
498
499
500 // Get button style
getButtonStyle() const501 FXuint FXButton::getButtonStyle() const {
502 return (options&BUTTON_MASK);
503 }
504
505 }
506