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,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 "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