1 /********************************************************************************
2 *                                                                               *
3 *                     A r r o w   B u t t o n    O b j e c 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 "FXArrowButton.h"
42 
43 
44 /*
45   Notes:
46   - Automatic mode works by simply hovering cursor over arrow button; it is
47     used for scrolling menus.
48   - Possible interactions between auto mode and manual pressing..
49 */
50 
51 
52 // Justification
53 #define JUSTIFY_MASK  (JUSTIFY_HZ_APART|JUSTIFY_VT_APART)
54 
55 // Arrow styles
56 #define ARROW_MASK    (ARROW_UP|ARROW_DOWN|ARROW_LEFT|ARROW_RIGHT|ARROW_AUTO|ARROW_REPEAT|ARROW_AUTOGRAY|ARROW_AUTOHIDE|ARROW_TOOLBAR)
57 
58 using namespace FX;
59 
60 /*******************************************************************************/
61 
62 namespace FX {
63 
64 
65 // Map
66 FXDEFMAP(FXArrowButton) FXArrowButtonMap[]={
67   FXMAPFUNC(SEL_UPDATE,0,FXArrowButton::onUpdate),
68   FXMAPFUNC(SEL_PAINT,0,FXArrowButton::onPaint),
69   FXMAPFUNC(SEL_ENTER,0,FXArrowButton::onEnter),
70   FXMAPFUNC(SEL_LEAVE,0,FXArrowButton::onLeave),
71   FXMAPFUNC(SEL_TIMEOUT,FXArrowButton::ID_AUTO,FXArrowButton::onAuto),
72   FXMAPFUNC(SEL_TIMEOUT,FXArrowButton::ID_REPEAT,FXArrowButton::onRepeat),
73   FXMAPFUNC(SEL_LEFTBUTTONPRESS,0,FXArrowButton::onLeftBtnPress),
74   FXMAPFUNC(SEL_LEFTBUTTONRELEASE,0,FXArrowButton::onLeftBtnRelease),
75   FXMAPFUNC(SEL_QUERY_TIP,0,FXArrowButton::onQueryTip),
76   FXMAPFUNC(SEL_QUERY_HELP,0,FXArrowButton::onQueryHelp),
77   FXMAPFUNC(SEL_UNGRABBED,0,FXArrowButton::onUngrabbed),
78   FXMAPFUNC(SEL_KEYPRESS,0,FXArrowButton::onKeyPress),
79   FXMAPFUNC(SEL_KEYRELEASE,0,FXArrowButton::onKeyRelease),
80   FXMAPFUNC(SEL_KEYPRESS,FXArrowButton::ID_HOTKEY,FXArrowButton::onHotKeyPress),
81   FXMAPFUNC(SEL_KEYRELEASE,FXArrowButton::ID_HOTKEY,FXArrowButton::onHotKeyRelease),
82   FXMAPFUNC(SEL_COMMAND,FXArrowButton::ID_SETHELPSTRING,FXArrowButton::onCmdSetHelp),
83   FXMAPFUNC(SEL_COMMAND,FXArrowButton::ID_GETHELPSTRING,FXArrowButton::onCmdGetHelp),
84   FXMAPFUNC(SEL_COMMAND,FXArrowButton::ID_SETTIPSTRING,FXArrowButton::onCmdSetTip),
85   FXMAPFUNC(SEL_COMMAND,FXArrowButton::ID_GETTIPSTRING,FXArrowButton::onCmdGetTip),
86   };
87 
88 
89 // Object implementation
FXIMPLEMENT(FXArrowButton,FXFrame,FXArrowButtonMap,ARRAYNUMBER (FXArrowButtonMap))90 FXIMPLEMENT(FXArrowButton,FXFrame,FXArrowButtonMap,ARRAYNUMBER(FXArrowButtonMap))
91 
92 
93 // For deserialization
94 FXArrowButton::FXArrowButton(){
95   flags|=FLAG_ENABLED;
96   arrowColor=0;
97   arrowSize=9;
98   state=false;
99   fired=false;
100   }
101 
102 
103 // Make a text button
FXArrowButton(FXComposite * p,FXObject * tgt,FXSelector sel,FXuint opts,FXint x,FXint y,FXint w,FXint h,FXint pl,FXint pr,FXint pt,FXint pb)104 FXArrowButton::FXArrowButton(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){
105   flags|=FLAG_ENABLED;
106   target=tgt;
107   message=sel;
108   arrowColor=getApp()->getForeColor();
109   arrowSize=9;
110   state=false;
111   fired=false;
112   }
113 
114 
115 // Get default size
getDefaultWidth()116 FXint FXArrowButton::getDefaultWidth(){
117   return padleft+padright+arrowSize+(border<<1);
118   }
119 
120 
getDefaultHeight()121 FXint FXArrowButton::getDefaultHeight(){
122   return padtop+padbottom+arrowSize+(border<<1);
123   }
124 
125 
126 // Enable the window
enable()127 void FXArrowButton::enable(){
128   if(!(flags&FLAG_ENABLED)){
129     FXFrame::enable();
130     update();
131     }
132   }
133 
134 
135 // Disable the window
disable()136 void FXArrowButton::disable(){
137   if(flags&FLAG_ENABLED){
138     FXFrame::disable();
139     update();
140     }
141   }
142 
143 
144 // Set button state
setState(FXbool s)145 void FXArrowButton::setState(FXbool s){
146   if(state!=s){
147     state=s;
148     update();
149     }
150   }
151 
152 
153 // If window can have focus
canFocus() const154 FXbool FXArrowButton::canFocus() const { return true; }
155 
156 
157 // Implement auto-hide or auto-gray modes
onUpdate(FXObject * sender,FXSelector sel,void * ptr)158 long FXArrowButton::onUpdate(FXObject* sender,FXSelector sel,void* ptr){
159   if(!FXFrame::onUpdate(sender,sel,ptr)){
160     if(options&ARROW_AUTOHIDE){if(shown()){hide();recalc();}}
161     if(options&ARROW_AUTOGRAY){disable();}
162     }
163   return 1;
164   }
165 
166 
167 // Press automatically
onAuto(FXObject *,FXSelector,void *)168 long FXArrowButton::onAuto(FXObject*,FXSelector,void*){
169   setState(true);
170   getApp()->addTimeout(this,ID_REPEAT,getApp()->getScrollSpeed());
171   flags&=~FLAG_UPDATE;
172   fired=false;
173   return 1;
174   }
175 
176 
177 // Entered button
onEnter(FXObject * sender,FXSelector sel,void * ptr)178 long FXArrowButton::onEnter(FXObject* sender,FXSelector sel,void* ptr){
179   FXFrame::onEnter(sender,sel,ptr);
180   if(isEnabled()){
181     if(flags&FLAG_PRESSED){
182       setState(true);
183       }
184     else if(options&ARROW_AUTO){
185       if(options&ARROW_REPEAT) getApp()->addTimeout(this,ID_AUTO,getApp()->getScrollDelay());
186       }
187     if(options&ARROW_TOOLBAR) update();
188     }
189   return 1;
190   }
191 
192 
193 // Left button
onLeave(FXObject * sender,FXSelector sel,void * ptr)194 long FXArrowButton::onLeave(FXObject* sender,FXSelector sel,void* ptr){
195   FXFrame::onLeave(sender,sel,ptr);
196   if(isEnabled()){
197     if(flags&FLAG_PRESSED){
198       setState(false);
199       }
200     else if(options&ARROW_AUTO){
201       setState(false);
202       if(options&ARROW_REPEAT) getApp()->removeTimeout(this,ID_AUTO);
203       flags|=FLAG_UPDATE;
204       fired=false;
205       }
206     if(options&ARROW_TOOLBAR) update();
207     }
208   return 1;
209   }
210 
211 
212 // Pressed mouse button
onLeftBtnPress(FXObject *,FXSelector,void * ptr)213 long FXArrowButton::onLeftBtnPress(FXObject*,FXSelector,void* ptr){
214   handle(this,FXSEL(SEL_FOCUS_SELF,0),ptr);
215   flags&=~FLAG_TIP;
216   if(isEnabled() && !(flags&FLAG_PRESSED)){
217     grab();
218     if(target && target->tryHandle(this,FXSEL(SEL_LEFTBUTTONPRESS,message),ptr)) return 1;
219     setState(true);
220     getApp()->removeTimeout(this,ID_AUTO);
221     if(options&ARROW_REPEAT) getApp()->addTimeout(this,ID_REPEAT,getApp()->getScrollDelay());
222     flags|=FLAG_PRESSED;
223     flags&=~FLAG_UPDATE;
224     fired=false;
225     return 1;
226     }
227   return 0;
228   }
229 
230 
231 // Released mouse button
onLeftBtnRelease(FXObject *,FXSelector,void * ptr)232 long FXArrowButton::onLeftBtnRelease(FXObject*,FXSelector,void* ptr){
233   FXbool click=(!fired && state);
234   if(isEnabled() && (flags&FLAG_PRESSED)){
235     ungrab();
236     flags|=FLAG_UPDATE;
237     flags&=~FLAG_PRESSED;
238     fired=false;
239     getApp()->removeTimeout(this,ID_REPEAT);
240     if(target && target->tryHandle(this,FXSEL(SEL_LEFTBUTTONRELEASE,message),ptr)) return 1;
241     setState(false);
242     if(click && target) target->tryHandle(this,FXSEL(SEL_COMMAND,message),(void*)(FXuval)1);
243     return 1;
244     }
245   return 0;
246   }
247 
248 
249 // Lost the grab for some reason
onUngrabbed(FXObject * sender,FXSelector sel,void * ptr)250 long FXArrowButton::onUngrabbed(FXObject* sender,FXSelector sel,void* ptr){
251   FXFrame::onUngrabbed(sender,sel,ptr);
252   setState(false);
253   getApp()->removeTimeout(this,ID_REPEAT);
254   flags&=~FLAG_PRESSED;
255   flags|=FLAG_UPDATE;
256   fired=false;
257   return 1;
258   }
259 
260 
261 // Repeat a click automatically
onRepeat(FXObject *,FXSelector,void *)262 long FXArrowButton::onRepeat(FXObject*,FXSelector,void*){
263   getApp()->addTimeout(this,ID_REPEAT,getApp()->getScrollSpeed());
264   if(state && target) target->tryHandle(this,FXSEL(SEL_COMMAND,message),(void*)(FXuval)1);
265   fired=true;
266   return 1;
267   }
268 
269 
270 // Key Press
onKeyPress(FXObject *,FXSelector,void * ptr)271 long FXArrowButton::onKeyPress(FXObject*,FXSelector,void* ptr){
272   FXEvent* event=(FXEvent*)ptr;
273   flags&=~FLAG_TIP;
274   if(isEnabled()){
275     if(target && target->tryHandle(this,FXSEL(SEL_KEYPRESS,message),ptr)) return 1;
276     if(!(flags&FLAG_PRESSED) && (event->code==KEY_space || event->code==KEY_KP_Space)){
277       setState(true);
278       getApp()->removeTimeout(this,ID_AUTO);
279       if(options&ARROW_REPEAT) getApp()->addTimeout(this,ID_REPEAT,getApp()->getScrollDelay());
280       flags|=FLAG_PRESSED;
281       flags&=~FLAG_UPDATE;
282       fired=false;
283       return 1;
284       }
285     }
286   return 0;
287   }
288 
289 
290 // Key Release
onKeyRelease(FXObject *,FXSelector,void * ptr)291 long FXArrowButton::onKeyRelease(FXObject*,FXSelector,void* ptr){
292   FXEvent* event=(FXEvent*)ptr;
293   FXbool click=(!fired && state);
294   if(isEnabled()){
295     if(target && target->tryHandle(this,FXSEL(SEL_KEYRELEASE,message),ptr)) return 1;
296     if((flags&FLAG_PRESSED) && (event->code==KEY_space || event->code==KEY_KP_Space)){
297       setState(false);
298       flags|=FLAG_UPDATE;
299       flags&=~FLAG_PRESSED;
300       fired=false;
301       getApp()->removeTimeout(this,ID_REPEAT);
302       if(click && target) target->tryHandle(this,FXSEL(SEL_COMMAND,message),(void*)(FXuval)1);
303       return 1;
304       }
305     }
306   return 0;
307   }
308 
309 
310 // Hot key combination pressed
onHotKeyPress(FXObject *,FXSelector,void * ptr)311 long FXArrowButton::onHotKeyPress(FXObject*,FXSelector,void* ptr){
312   flags&=~FLAG_TIP;
313   handle(this,FXSEL(SEL_FOCUS_SELF,0),ptr);
314   if(isEnabled() && !(flags&FLAG_PRESSED)){
315     setState(true);
316     getApp()->removeTimeout(this,ID_AUTO);
317     if(options&ARROW_REPEAT) getApp()->addTimeout(this,ID_REPEAT,getApp()->getScrollDelay());
318     flags|=FLAG_PRESSED;
319     flags&=~FLAG_UPDATE;
320     fired=false;
321     }
322   return 1;
323   }
324 
325 
326 // Hot key combination released
onHotKeyRelease(FXObject *,FXSelector,void *)327 long FXArrowButton::onHotKeyRelease(FXObject*,FXSelector,void*){
328   FXbool click=(!fired && state);
329   if(isEnabled() && (flags&FLAG_PRESSED)){
330     setState(false);
331     flags|=FLAG_UPDATE;
332     flags&=~FLAG_PRESSED;
333     fired=false;
334     getApp()->removeTimeout(this,ID_REPEAT);
335     if(click && target) target->tryHandle(this,FXSEL(SEL_COMMAND,message),(void*)(FXuval)1);
336     }
337   return 1;
338   }
339 
340 
341 // Set help using a message
onCmdSetHelp(FXObject *,FXSelector,void * ptr)342 long FXArrowButton::onCmdSetHelp(FXObject*,FXSelector,void* ptr){
343   setHelpText(*((FXString*)ptr));
344   return 1;
345   }
346 
347 
348 // Get help using a message
onCmdGetHelp(FXObject *,FXSelector,void * ptr)349 long FXArrowButton::onCmdGetHelp(FXObject*,FXSelector,void* ptr){
350   *((FXString*)ptr)=getHelpText();
351   return 1;
352   }
353 
354 
355 // Set tip using a message
onCmdSetTip(FXObject *,FXSelector,void * ptr)356 long FXArrowButton::onCmdSetTip(FXObject*,FXSelector,void* ptr){
357   setTipText(*((FXString*)ptr));
358   return 1;
359   }
360 
361 
362 // Get tip using a message
onCmdGetTip(FXObject *,FXSelector,void * ptr)363 long FXArrowButton::onCmdGetTip(FXObject*,FXSelector,void* ptr){
364   *((FXString*)ptr)=getTipText();
365   return 1;
366   }
367 
368 
369 // We were asked about tip text
onQueryTip(FXObject * sender,FXSelector sel,void * ptr)370 long FXArrowButton::onQueryTip(FXObject* sender,FXSelector sel,void* ptr){
371   if(FXFrame::onQueryTip(sender,sel,ptr)) return 1;
372   if((flags&FLAG_TIP) && !tip.empty()){
373     sender->handle(this,FXSEL(SEL_COMMAND,ID_SETSTRINGVALUE),(void*)&tip);
374     return 1;
375     }
376   return 0;
377   }
378 
379 
380 // We were asked about status text
onQueryHelp(FXObject * sender,FXSelector sel,void * ptr)381 long FXArrowButton::onQueryHelp(FXObject* sender,FXSelector sel,void* ptr){
382   if(FXFrame::onQueryHelp(sender,sel,ptr)) return 1;
383   if((flags&FLAG_HELP) && !help.empty()){
384     sender->handle(this,FXSEL(SEL_COMMAND,ID_SETSTRINGVALUE),(void*)&help);
385     return 1;
386     }
387   return 0;
388   }
389 
390 
391 // Handle repaint
onPaint(FXObject *,FXSelector,void * ptr)392 long FXArrowButton::onPaint(FXObject*,FXSelector,void* ptr){
393   FXEvent   *ev=(FXEvent*)ptr;
394   FXDCWindow dc(this,ev);
395   FXPoint    points[3];
396   FXint      xx,yy,ww,hh,q;
397 
398 
399   // With borders
400   if(options&(FRAME_RAISED|FRAME_SUNKEN)){
401 
402     // Toolbar style
403     if(options&ARROW_TOOLBAR){
404 
405       // Enabled and cursor inside, and up
406       if(isEnabled() && underCursor() && !state){
407         dc.setForeground(backColor);
408         dc.fillRectangle(border,border,width-border*2,height-border*2);
409         if(options&FRAME_THICK) drawDoubleRaisedRectangle(dc,0,0,width,height);
410         else drawRaisedRectangle(dc,0,0,width,height);
411         }
412 
413       // Enabled and cursor inside and down
414       else if(isEnabled() && state){
415         dc.setForeground(hiliteColor);
416         dc.fillRectangle(border,border,width-border*2,height-border*2);
417         if(options&FRAME_THICK) drawDoubleSunkenRectangle(dc,0,0,width,height);
418         else drawSunkenRectangle(dc,0,0,width,height);
419         }
420 
421       // Disabled or unchecked or not under cursor
422       else{
423         dc.setForeground(backColor);
424         dc.fillRectangle(0,0,width,height);
425         }
426       }
427 
428     // Normal style
429     else{
430 
431       // Draw sunken if enabled and pressed
432       if(isEnabled() && state){
433         dc.setForeground(hiliteColor);
434         dc.fillRectangle(border,border,width-border*2,height-border*2);
435         if(options&FRAME_THICK) drawDoubleSunkenRectangle(dc,0,0,width,height);
436         else drawSunkenRectangle(dc,0,0,width,height);
437         }
438 
439       // Draw in up state if disabled or up
440       else{
441         dc.setForeground(backColor);
442         dc.fillRectangle(border,border,width-border*2,height-border*2);
443         if(options&FRAME_THICK) drawDoubleRaisedRectangle(dc,0,0,width,height);
444         else drawRaisedRectangle(dc,0,0,width,height);
445         }
446       }
447     }
448 
449   // No borders
450   else{
451     if(isEnabled() && state){
452       dc.setForeground(hiliteColor);
453       dc.fillRectangle(0,0,width,height);
454       }
455     else{
456       dc.setForeground(backColor);
457       dc.fillRectangle(0,0,width,height);
458       }
459     }
460 
461   // Compute size of the arrows....
462   ww=width-padleft-padright-(border<<1);
463   hh=height-padtop-padbottom-(border<<1);
464   if(options&(ARROW_UP|ARROW_DOWN)){
465     q=(ww-1)|1; if((q>>1)>hh) q=(hh<<1)-1;
466     ww=q; hh=q>>1;
467     }
468   else{
469     q=(hh-1)|1; if((q>>1)>ww) q=(ww<<1)-1;
470     ww=q>>1; hh=q;
471     }
472 
473   if(options&JUSTIFY_LEFT) xx=padleft+border;
474   else if(options&JUSTIFY_RIGHT) xx=width-ww-padright-border;
475   else xx=(width-ww)/2;
476 
477   if(options&JUSTIFY_TOP) yy=padtop+border;
478   else if(options&JUSTIFY_BOTTOM) yy=height-hh-padbottom-border;
479   else yy=(height-hh)/2;
480 
481   if(state){ ++xx; ++yy; }
482 
483   if(isEnabled())
484     dc.setForeground(arrowColor);
485   else
486     dc.setForeground(shadowColor);
487 
488   // NB Size of arrow should stretch
489   if(options&ARROW_UP){
490     points[0].x=xx+(ww>>1);
491     points[0].y=yy-1;
492     points[1].x=xx;
493     points[1].y=yy+hh;
494     points[2].x=xx+ww;
495     points[2].y=yy+hh;
496     dc.fillPolygon(points,3);
497     }
498   else if(options&ARROW_DOWN){
499     points[0].x=xx+1;
500     points[0].y=yy;
501     points[1].x=xx+ww-1;
502     points[1].y=yy;
503     points[2].x=xx+(ww>>1);
504     points[2].y=yy+hh;
505     dc.fillPolygon(points,3);
506     }
507   else if(options&ARROW_LEFT){
508     points[0].x=xx+ww;
509     points[0].y=yy;
510     points[1].x=xx+ww;
511     points[1].y=yy+hh-1;
512     points[2].x=xx;
513     points[2].y=yy+(hh>>1);
514     dc.fillPolygon(points,3);
515     }
516   else if(options&ARROW_RIGHT){
517     points[0].x=xx;
518     points[0].y=yy;
519     points[1].x=xx;
520     points[1].y=yy+hh-1;
521     points[2].x=xx+ww;
522     points[2].y=yy+(hh>>1);
523     dc.fillPolygon(points,3);
524     }
525   return 1;
526   }
527 
528 
529 // Set arrow style
setArrowStyle(FXuint style)530 void FXArrowButton::setArrowStyle(FXuint style){
531   FXuint opts=(options&~ARROW_MASK) | (style&ARROW_MASK);
532   if(options!=opts){
533     options=opts;
534     update();
535     }
536   }
537 
538 
539 // Get arrow style
getArrowStyle() const540 FXuint FXArrowButton::getArrowStyle() const {
541   return (options&ARROW_MASK);
542   }
543 
544 
545 // Set default arrow size
setArrowSize(FXint size)546 void FXArrowButton::setArrowSize(FXint size){
547   if(size!=arrowSize){
548     arrowSize=size;
549     recalc();
550     }
551   }
552 
553 
554 // Set text color
setArrowColor(FXColor clr)555 void FXArrowButton::setArrowColor(FXColor clr){
556   if(clr!=arrowColor){
557     arrowColor=clr;
558     update();
559     }
560   }
561 
562 
563 // Set text justify style
setJustify(FXuint style)564 void FXArrowButton::setJustify(FXuint style){
565   FXuint opts=(options&~JUSTIFY_MASK) | (style&JUSTIFY_MASK);
566   if(options!=opts){
567     options=opts;
568     update();
569     }
570   }
571 
572 
573 // Get text justify style
getJustify() const574 FXuint FXArrowButton::getJustify() const {
575   return (options&JUSTIFY_MASK);
576   }
577 
578 
579 // Save object to stream
save(FXStream & store) const580 void FXArrowButton::save(FXStream& store) const {
581   FXFrame::save(store);
582   store << arrowColor;
583   store << arrowSize;
584   }
585 
586 
587 // Load object from stream
load(FXStream & store)588 void FXArrowButton::load(FXStream& store){
589   FXFrame::load(store);
590   store >> arrowColor;
591   store >> arrowSize;
592   }
593 
594 
595 // Kill the timer
~FXArrowButton()596 FXArrowButton::~FXArrowButton(){
597   getApp()->removeTimeout(this,ID_AUTO);
598   getApp()->removeTimeout(this,ID_REPEAT);
599   }
600 
601 }
602