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