1 /********************************************************************************
2 * *
3 * S p i n B u t t o n *
4 * *
5 *********************************************************************************
6 * Copyright (C) 1998,2005 by Lyle Johnson. All Rights Reserved. *
7 *********************************************************************************
8 * This library is free software; you can redistribute it and/or *
9 * modify it under the terms of the GNU Lesser General Public *
10 * License as published by the Free Software Foundation; either *
11 * version 2.1 of the License, or (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 GNU *
16 * Lesser General Public License for more details. *
17 * *
18 * You should have received a copy of the GNU Lesser General Public *
19 * License along with this library; if not, write to the Free Software *
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *
21 *********************************************************************************
22 * $Id: FXSpinner.cpp,v 1.57 2005/01/16 16:06:07 fox Exp $ *
23 ********************************************************************************/
24 #include "xincs.h"
25 #include "fxver.h"
26 #include "fxdefs.h"
27 #include "fxkeys.h"
28 #include "FXHash.h"
29 #include "FXThread.h"
30 #include "FXStream.h"
31 #include "FXString.h"
32 #include "FXSize.h"
33 #include "FXPoint.h"
34 #include "FXRectangle.h"
35 #include "FXRegistry.h"
36 #include "FXAccelTable.h"
37 #include "FXApp.h"
38 #include "FXLabel.h"
39 #include "FXTextField.h"
40 #include "FXArrowButton.h"
41 #include "FXSpinner.h"
42
43
44 /*
45 To do:
46 - Should this also be derived from FXTextField instead?
47 - Sends SEL_COMMAND; should it send SEL_CHANGED for each repeat, then SEL_COMMAND
48 at end?
49 - Should block SEL_UPDATE until after sending SEL_COMMAND.
50 - Make the value->text and text->value virtual.
51 */
52
53 #define BUTTONWIDTH 14
54
55 #define INTMAX 2147483647
56 #define INTMIN (-INTMAX-1)
57
58 #define SPINNER_MASK (SPIN_CYCLIC|SPIN_NOTEXT|SPIN_NOMAX|SPIN_NOMIN)
59
60 using namespace FX;
61
62 /*******************************************************************************/
63
64 namespace FX {
65
66
67 // Message map
68 FXDEFMAP(FXSpinner) FXSpinnerMap[]={
69 FXMAPFUNC(SEL_KEYPRESS,0,FXSpinner::onKeyPress),
70 FXMAPFUNC(SEL_KEYRELEASE,0,FXSpinner::onKeyRelease),
71 FXMAPFUNC(SEL_FOCUS_SELF,0,FXSpinner::onFocusSelf),
72 FXMAPFUNC(SEL_COMMAND,FXSpinner::ID_ENTRY,FXSpinner::onCmdEntry),
73 FXMAPFUNC(SEL_CHANGED,FXSpinner::ID_ENTRY,FXSpinner::onChgEntry),
74 FXMAPFUNC(SEL_MOUSEWHEEL,FXSpinner::ID_ENTRY,FXSpinner::onWheelEntry),
75 FXMAPFUNC(SEL_UPDATE,FXSpinner::ID_INCREMENT,FXSpinner::onUpdIncrement),
76 FXMAPFUNC(SEL_COMMAND,FXSpinner::ID_INCREMENT,FXSpinner::onCmdIncrement),
77 FXMAPFUNC(SEL_UPDATE,FXSpinner::ID_DECREMENT,FXSpinner::onUpdDecrement),
78 FXMAPFUNC(SEL_COMMAND,FXSpinner::ID_DECREMENT,FXSpinner::onCmdDecrement),
79 FXMAPFUNC(SEL_COMMAND,FXSpinner::ID_SETVALUE,FXSpinner::onCmdSetValue),
80 FXMAPFUNC(SEL_COMMAND,FXSpinner::ID_SETINTVALUE,FXSpinner::onCmdSetIntValue),
81 FXMAPFUNC(SEL_COMMAND,FXSpinner::ID_GETINTVALUE,FXSpinner::onCmdGetIntValue),
82 FXMAPFUNC(SEL_COMMAND,FXSpinner::ID_SETINTRANGE,FXSpinner::onCmdSetIntRange),
83 FXMAPFUNC(SEL_COMMAND,FXSpinner::ID_GETINTRANGE,FXSpinner::onCmdGetIntRange),
84 };
85
86
87 // Object implementation
FXIMPLEMENT(FXSpinner,FXPacker,FXSpinnerMap,ARRAYNUMBER (FXSpinnerMap))88 FXIMPLEMENT(FXSpinner,FXPacker,FXSpinnerMap,ARRAYNUMBER(FXSpinnerMap))
89
90
91 // Construct spinner out of two buttons and a text field
92 FXSpinner::FXSpinner(){
93 flags|=FLAG_ENABLED;
94 textField=(FXTextField*)-1L;
95 upButton=(FXArrowButton*)-1L;
96 downButton=(FXArrowButton*)-1L;
97 range[0]=INTMIN;
98 range[1]=INTMAX;
99 incr=1;
100 pos=0;
101 }
102
103
104 // Construct spinner out of two buttons and a text field
FXSpinner(FXComposite * p,FXint cols,FXObject * tgt,FXSelector sel,FXuint opts,FXint x,FXint y,FXint w,FXint h,FXint pl,FXint pr,FXint pt,FXint pb)105 FXSpinner::FXSpinner(FXComposite *p,FXint cols,FXObject *tgt,FXSelector sel,FXuint opts,FXint x,FXint y,FXint w,FXint h,FXint pl,FXint pr,FXint pt,FXint pb):
106 FXPacker(p,opts,x,y,w,h,0,0,0,0,0,0){
107 flags|=FLAG_ENABLED;
108 target=tgt;
109 message=sel;
110 textField=new FXTextField(this,cols,this,ID_ENTRY,TEXTFIELD_INTEGER|JUSTIFY_RIGHT,0,0,0,0,pl,pr,pt,pb);
111 upButton=new FXArrowButton(this,this,FXSpinner::ID_INCREMENT,FRAME_RAISED|FRAME_THICK|ARROW_UP|ARROW_REPEAT, 0,0,0,0, 0,0,0,0);
112 downButton=new FXArrowButton(this,this,FXSpinner::ID_DECREMENT,FRAME_RAISED|FRAME_THICK|ARROW_DOWN|ARROW_REPEAT, 0,0,0,0, 0,0,0,0);
113 range[0]=(options&SPIN_NOMIN) ? INTMIN : 0;
114 range[1]=(options&SPIN_NOMAX) ? INTMAX : 100;
115 textField->setText("0");
116 incr=1;
117 pos=0;
118 }
119
120
121 // Get default width
getDefaultWidth()122 FXint FXSpinner::getDefaultWidth(){
123 FXint tw=0;
124 if(!(options&SPIN_NOTEXT)) tw=textField->getDefaultWidth();
125 return tw+BUTTONWIDTH+(border<<1);
126 }
127
128
129 // Get default height
getDefaultHeight()130 FXint FXSpinner::getDefaultHeight(){
131 return textField->getDefaultHeight()+(border<<1);
132 }
133
134
135 // Enable the widget
enable()136 void FXSpinner::enable(){
137 if(!(flags&FLAG_ENABLED)){
138 FXPacker::enable();
139 textField->enable();
140 upButton->enable();
141 downButton->enable();
142 }
143 }
144
145
146 // Disable the widget
disable()147 void FXSpinner::disable(){
148 if(flags&FLAG_ENABLED){
149 FXPacker::disable();
150 textField->disable();
151 upButton->disable();
152 downButton->disable();
153 }
154 }
155
156
157 // Recompute layout
layout()158 void FXSpinner::layout(){
159 FXint buttonWidth,buttonHeight,textWidth,textHeight;
160
161 textHeight=height-2*border;
162 buttonHeight=textHeight>>1;
163
164 // Only the buttons:- place buttons to take up the whole space!
165 if(options&SPIN_NOTEXT){
166 buttonWidth=width-2*border;
167 upButton->position(border,border,buttonWidth,buttonHeight);
168 downButton->position(border,height-buttonHeight-border,buttonWidth,buttonHeight);
169 }
170
171 // Buttons plus the text; buttons are default width, text stretches to fill the rest
172 else{
173 buttonWidth=BUTTONWIDTH;
174 textWidth=width-buttonWidth-2*border;
175 textField->position(border,border,textWidth,textHeight);
176 upButton->position(border+textWidth,border,buttonWidth,buttonHeight);
177 downButton->position(border+textWidth,height-buttonHeight-border,buttonWidth,buttonHeight);
178 }
179 flags&=~FLAG_DIRTY;
180 }
181
182
183 // Respond to increment message
onUpdIncrement(FXObject * sender,FXSelector,void *)184 long FXSpinner::onUpdIncrement(FXObject* sender,FXSelector,void*){
185 if(isEnabled() && ((options&SPIN_CYCLIC) || (pos<range[1])))
186 sender->handle(this,FXSEL(SEL_COMMAND,ID_ENABLE),NULL);
187 else
188 sender->handle(this,FXSEL(SEL_COMMAND,ID_DISABLE),NULL);
189 return 1;
190 }
191
192
193 // Respond to increment message
onCmdIncrement(FXObject *,FXSelector,void *)194 long FXSpinner::onCmdIncrement(FXObject*,FXSelector,void*){
195 if(isEnabled() && isEditable()){
196 increment();
197 if(target) target->tryHandle(this,FXSEL(SEL_COMMAND,message),(void*)(FXival)pos);
198 return 1;
199 }
200 return 0;
201 }
202
203
204 // Disable decrement if at low end already
onUpdDecrement(FXObject * sender,FXSelector,void *)205 long FXSpinner::onUpdDecrement(FXObject* sender,FXSelector,void*){
206 if(isEnabled() && ((options&SPIN_CYCLIC) || (range[0]<pos)))
207 sender->handle(this,FXSEL(SEL_COMMAND,ID_ENABLE),NULL);
208 else
209 sender->handle(this,FXSEL(SEL_COMMAND,ID_DISABLE),NULL);
210 return 1;
211 }
212
213
214 // Respond to decrement message
onCmdDecrement(FXObject *,FXSelector,void *)215 long FXSpinner::onCmdDecrement(FXObject*,FXSelector,void*){
216 if(isEnabled() && isEditable()){
217 decrement();
218 if(target) target->tryHandle(this,FXSEL(SEL_COMMAND,message),(void*)(FXival)pos);
219 return 1;
220 }
221 return 0;
222 }
223
224
225 // Rolling mouse wheel in text field works as if hitting up or down buttons
onWheelEntry(FXObject *,FXSelector,void * ptr)226 long FXSpinner::onWheelEntry(FXObject*,FXSelector,void* ptr){
227 if(isEnabled() && isEditable()){
228 if(((FXEvent*)ptr)->code>0)
229 increment();
230 else
231 decrement();
232 if(target) target->tryHandle(this,FXSEL(SEL_COMMAND,message),(void*)(FXival)pos);
233 return 1;
234 }
235 return 0;
236 }
237
238
239 // Text field changed
onChgEntry(FXObject *,FXSelector,void *)240 long FXSpinner::onChgEntry(FXObject*,FXSelector,void*){
241 register FXint value=FXIntVal(textField->getText());
242 if(value<range[0]) value=range[0];
243 if(value>range[1]) value=range[1];
244 if(value!=pos){
245 pos=value;
246 if(target) target->tryHandle(this,FXSEL(SEL_CHANGED,message),(void*)(FXival)pos);
247 }
248 return 1;
249 }
250
251
252 // Text field command
onCmdEntry(FXObject *,FXSelector,void *)253 long FXSpinner::onCmdEntry(FXObject*,FXSelector,void*){
254 textField->setText(FXStringVal(pos)); // Put back adjusted value
255 if(target) target->tryHandle(this,FXSEL(SEL_COMMAND,message),(void*)(FXival)pos);
256 return 1;
257 }
258
259
260 // Keyboard press
onKeyPress(FXObject * sender,FXSelector sel,void * ptr)261 long FXSpinner::onKeyPress(FXObject* sender,FXSelector sel,void* ptr){
262 FXEvent* event=(FXEvent*)ptr;
263 if(isEnabled()){
264 if(target && target->tryHandle(this,FXSEL(SEL_KEYPRESS,message),ptr)) return 1;
265 switch(event->code){
266 case KEY_Up:
267 case KEY_KP_Up:
268 if(isEditable()){
269 increment();
270 if(target) target->tryHandle(this,FXSEL(SEL_COMMAND,message),(void*)(FXival)pos);
271 }
272 else{
273 getApp()->beep();
274 }
275 return 1;
276 case KEY_Down:
277 case KEY_KP_Down:
278 if(isEditable()){
279 decrement();
280 if(target) target->tryHandle(this,FXSEL(SEL_COMMAND,message),(void*)(FXival)pos);
281 }
282 else{
283 getApp()->beep();
284 }
285 return 1;
286 default:
287 return textField->handle(sender,sel,ptr);
288 }
289 }
290 return 0;
291 }
292
293
294 // Keyboard release
onKeyRelease(FXObject * sender,FXSelector sel,void * ptr)295 long FXSpinner::onKeyRelease(FXObject* sender,FXSelector sel,void* ptr){
296 FXEvent* event=(FXEvent*)ptr;
297 if(isEnabled()){
298 if(target && target->tryHandle(this,FXSEL(SEL_KEYRELEASE,message),ptr)) return 1;
299 switch(event->code){
300 case KEY_Up:
301 case KEY_KP_Up:
302 case KEY_Down:
303 case KEY_KP_Down:
304 return 1;
305 default:
306 return textField->handle(sender,sel,ptr);
307 }
308 }
309 return 0;
310 }
311
312
313 // Force focus on the text field
onFocusSelf(FXObject * sender,FXSelector,void * ptr)314 long FXSpinner::onFocusSelf(FXObject* sender,FXSelector,void* ptr){
315 return textField->handle(sender,FXSEL(SEL_FOCUS_SELF,0),ptr);
316 }
317
318
319 // Update value from a message
onCmdSetValue(FXObject *,FXSelector,void * ptr)320 long FXSpinner::onCmdSetValue(FXObject*,FXSelector,void* ptr){
321 setValue((FXint)(FXival)ptr);
322 return 1;
323 }
324
325
326 // Update value from a message
onCmdSetIntValue(FXObject *,FXSelector,void * ptr)327 long FXSpinner::onCmdSetIntValue(FXObject*,FXSelector,void* ptr){
328 setValue(*((FXint*)ptr));
329 return 1;
330 }
331
332
333 // Obtain value from spinner
onCmdGetIntValue(FXObject *,FXSelector,void * ptr)334 long FXSpinner::onCmdGetIntValue(FXObject*,FXSelector,void* ptr){
335 *((FXint*)ptr)=getValue();
336 return 1;
337 }
338
339
340 // Update range from a message
onCmdSetIntRange(FXObject *,FXSelector,void * ptr)341 long FXSpinner::onCmdSetIntRange(FXObject*,FXSelector,void* ptr){
342 setRange(((FXint*)ptr)[0],((FXint*)ptr)[1]);
343 return 1;
344 }
345
346
347 // Get range with a message
onCmdGetIntRange(FXObject *,FXSelector,void * ptr)348 long FXSpinner::onCmdGetIntRange(FXObject*,FXSelector,void* ptr){
349 getRange(((FXint*)ptr)[0],((FXint*)ptr)[1]);
350 return 1;
351 }
352
353
354 // Increment spinner
increment()355 void FXSpinner::increment(){
356 if(range[0]<range[1]){
357 if(options&SPIN_CYCLIC){
358 setValue(range[0] + (pos+incr-range[0]) % (range[1]-range[0]+1));
359 }
360 else{
361 setValue(pos+incr);
362 }
363 }
364 }
365
366
367 // Decrement spinner
decrement()368 void FXSpinner::decrement(){
369 if(range[0]<range[1]){
370 if(options&SPIN_CYCLIC){
371 setValue(range[0] + (pos+(range[1]-range[0]+1-incr)-range[0]) % (range[1]-range[0]+1));
372 }
373 else{
374 setValue(pos-incr);
375 }
376 }
377 }
378
379
380 // True if spinner is cyclic
isCyclic() const381 FXbool FXSpinner::isCyclic() const {
382 return (options&SPIN_CYCLIC)!=0;
383 }
384
385
386 // Set spinner cyclic mode
setCyclic(FXbool cyclic)387 void FXSpinner::setCyclic(FXbool cyclic){
388 if(cyclic) options|=SPIN_CYCLIC; else options&=~SPIN_CYCLIC;
389 }
390
391
392 // Set spinner range; this also revalidates the position,
setRange(FXint lo,FXint hi)393 void FXSpinner::setRange(FXint lo,FXint hi){
394 if(lo>hi){ fxerror("%s::setRange: trying to set negative range.\n",getClassName()); }
395 if(range[0]!=lo || range[1]!=hi){
396 range[0]=lo;
397 range[1]=hi;
398 setValue(pos);
399 }
400 }
401
402
403 // Set new value
setValue(FXint value)404 void FXSpinner::setValue(FXint value){
405 if(value<range[0]) value=range[0];
406 if(value>range[1]) value=range[1];
407 if(pos!=value){
408 textField->setText(FXStringVal(value));
409 pos=value;
410 }
411 }
412
413
414 // Change value increment
setIncrement(FXint inc)415 void FXSpinner::setIncrement(FXint inc){
416 if(inc<=0){ fxerror("%s::setIncrement: negative or zero increment specified.\n",getClassName()); }
417 incr=inc;
418 }
419
420
421 // True if text supposed to be visible
isTextVisible() const422 FXbool FXSpinner::isTextVisible() const {
423 return textField->shown();
424 }
425
426
427 // Change text visibility
setTextVisible(FXbool shown)428 void FXSpinner::setTextVisible(FXbool shown){
429 FXuint opts=shown?(options&~SPIN_NOTEXT):(options|SPIN_NOTEXT);
430 if(options!=opts){
431 options=opts;
432 recalc();
433 }
434 }
435
436
437 // Set the font used in the text field|
setFont(FXFont * fnt)438 void FXSpinner::setFont(FXFont *fnt) {
439 textField->setFont(fnt);
440 }
441
442
443 // Return the font used in the text field
getFont() const444 FXFont *FXSpinner::getFont() const {
445 return textField->getFont();
446 }
447
448
449 // Set help text
setHelpText(const FXString & text)450 void FXSpinner::setHelpText(const FXString& text){
451 textField->setHelpText(text);
452 upButton->setHelpText(text);
453 downButton->setHelpText(text);
454 }
455
456
457 // Get help text
getHelpText() const458 const FXString& FXSpinner::getHelpText() const {
459 return textField->getHelpText();
460 }
461
462
463 // Set tip text
setTipText(const FXString & text)464 void FXSpinner::setTipText(const FXString& text){
465 textField->setTipText(text);
466 upButton->setTipText(text);
467 downButton->setTipText(text);
468 }
469
470
471
472 // Get tip text
getTipText() const473 const FXString& FXSpinner::getTipText() const {
474 return textField->getTipText();
475 }
476
477
478 // Change spinner style
setSpinnerStyle(FXuint style)479 void FXSpinner::setSpinnerStyle(FXuint style){
480 FXuint opts=(options&~SPINNER_MASK) | (style&SPINNER_MASK);
481 if(options!=opts){
482 if(opts&SPIN_NOMIN) range[0]=INTMIN;
483 if(opts&SPIN_NOMAX) range[1]=INTMAX;
484 options=opts;
485 recalc();
486 }
487 }
488
489
490 // Get spinner style
getSpinnerStyle() const491 FXuint FXSpinner::getSpinnerStyle() const {
492 return (options&SPINNER_MASK);
493 }
494
495
496 // Allow editing of the text field
setEditable(FXbool edit)497 void FXSpinner::setEditable(FXbool edit){
498 textField->setEditable(edit);
499 }
500
501
502 // Return TRUE if text field is editable
isEditable() const503 FXbool FXSpinner::isEditable() const {
504 return textField->isEditable();
505 }
506
507 // Change color of the up arrow
setUpArrowColor(FXColor clr)508 void FXSpinner::setUpArrowColor(FXColor clr){
509 upButton->setArrowColor(clr);
510 }
511
512 // Return color of the up arrow
getUpArrowColor() const513 FXColor FXSpinner::getUpArrowColor() const {
514 return upButton->getArrowColor();
515 }
516
517 // Change color of the down arrow
setDownArrowColor(FXColor clr)518 void FXSpinner::setDownArrowColor(FXColor clr){
519 downButton->setArrowColor(clr);
520 }
521
522 // Return color of the the down arrow
getDownArrowColor() const523 FXColor FXSpinner::getDownArrowColor() const {
524 return downButton->getArrowColor();
525 }
526
527 // Change text color
setTextColor(FXColor clr)528 void FXSpinner::setTextColor(FXColor clr){
529 textField->setTextColor(clr);
530 }
531
532 // Return text color
getTextColor() const533 FXColor FXSpinner::getTextColor() const {
534 return textField->getTextColor();
535 }
536
537 // Change selected background color
setSelBackColor(FXColor clr)538 void FXSpinner::setSelBackColor(FXColor clr){
539 textField->setSelBackColor(clr);
540 }
541
542 // Return selected background color
getSelBackColor() const543 FXColor FXSpinner::getSelBackColor() const {
544 return textField->getSelBackColor();
545 }
546
547 // Change selected text color
setSelTextColor(FXColor clr)548 void FXSpinner::setSelTextColor(FXColor clr){
549 textField->setSelTextColor(clr);
550 }
551
552 // Return selected text color
getSelTextColor() const553 FXColor FXSpinner::getSelTextColor() const {
554 return textField->getSelTextColor();
555 }
556
557 // Changes the cursor color
setCursorColor(FXColor clr)558 void FXSpinner::setCursorColor(FXColor clr){
559 textField->setCursorColor(clr);
560 }
561
562 // Return the cursor color
getCursorColor() const563 FXColor FXSpinner::getCursorColor() const {
564 return textField->getCursorColor();
565 }
566
567
568 // Change number of columns
setNumColumns(FXint ncols)569 void FXSpinner::setNumColumns(FXint ncols){
570 textField->setNumColumns(ncols);
571 }
572
573
574 // Return number of columns
getNumColumns() const575 FXint FXSpinner::getNumColumns() const {
576 return textField->getNumColumns();
577 }
578
579
580 // Save object to stream
save(FXStream & store) const581 void FXSpinner::save(FXStream& store) const {
582 FXPacker::save(store);
583 store << textField;
584 store << upButton;
585 store << downButton;
586 store << range[0] << range[1];
587 store << incr;
588 store << pos;
589 }
590
591
592 // Load object from stream
load(FXStream & store)593 void FXSpinner::load(FXStream& store){
594 FXPacker::load(store);
595 store >> textField;
596 store >> upButton;
597 store >> downButton;
598 store >> range[0] >> range[1];
599 store >> incr;
600 store >> pos;
601 }
602
603
604 // Destruct spinner:- trash it!
~FXSpinner()605 FXSpinner::~FXSpinner(){
606 textField=(FXTextField*)-1L;
607 upButton=(FXArrowButton*)-1L;
608 downButton=(FXArrowButton*)-1L;
609 }
610
611 }
612