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