1 // -*- C++ -*-
2 /* GG is a GUI for OpenGL.
3    Copyright (C) 2003-2008 T. Zachary Laine
4 
5    This library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public License
7    as published by the Free Software Foundation; either version 2.1
8    of the License, or (at your option) any later version.
9 
10    This library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14 
15    You should have received a copy of the GNU Lesser General Public
16    License along with this library; if not, write to the Free
17    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
18    02111-1307 USA
19 
20    If you do not wish to comply with the terms of the LGPL please
21    contact the author as other terms are available for a fee.
22 
23    Zach Laine
24    whatwasthataddress@gmail.com */
25 
26 /** \file Spin.h \brief Contains the Spin class template, which provides a
27     spin-box control that allows the user to select a value from a range an
28     arbitrary type (int, double, an enum, etc.). */
29 
30 #ifndef _GG_Spin_h_
31 #define _GG_Spin_h_
32 
33 #include <GG/Button.h>
34 #include <GG/Edit.h>
35 #include <GG/GUI.h>
36 #include <GG/StyleFactory.h>
37 #include <GG/WndEvent.h>
38 
39 #include <cmath>
40 #include <limits>
41 
42 
43 namespace GG {
44 
45 // forward declaration of helper functions and classes
46 namespace spin_details {
47     template <typename T> T mod(T, T);
48     template <typename T> T div(T, T);
49 }
50 
51 
52 /** \brief A spin box control.
53 
54     This control class is templated so that arbitrary data types can be used
55     with Spin.  All the built-in numeric types are supported by the code here.
56     If you want to use some other type, such as an enum type, you need to
57     define operator+(), operator-(), and template specializations of
58     spin_details::mod() and spin_details::div().  Spin controls are optionally
59     directly editable by the user.  When the user inputs a value that is not
60     valid for the Spin's parameters (not on a step boundary, or outside the
61     allowed range), the input gets locked to the nearest valid value.  The
62     user is responsible for selecting a min, max, and step size that make
63     sense.  For instance, min = 0, max = 4, step = 3 may produce odd results
64     if the user increments all the way to the top, then back down, to produce
65     the sequence 0, 3, 4, 1, 0.  To avoid this, choose the values so that (max
66     - min) mod step == 0.  It is possible to provide custom buttons for a Spin
67     to use; if you choose to add custom buttons, make sure they look alright
68     at arbitrary sizes, and note that Spin buttons are always H wide by H/2
69     tall, where H is the height of the Spin, less the thickness of the Spin's
70     border. */
71 template <typename T>
72 class Spin : public Control
73 {
74 public:
75     /** \name Signal Types */ ///@{
76     /** emitted whenever the value of the Spin has changed */
77     typedef typename boost::signals2::signal<void (T)> ValueChangedSignalType;
78     //@}
79 
80     /** \name Structors */ ///@{
81     /** Ctor that does not required height. Height is determined from the font
82         and point size used.*/
83     Spin(T value, T step, T min, T max, bool edits, const std::shared_ptr<Font>& font,
84          Clr color, Clr text_color = CLR_BLACK);
85     void CompleteConstruction() override;
86 
87     ~Spin();
88     //@}
89 
90     /** \name Accessors */ ///@{
91     Pt      MinUsableSize() const override;
92 
93     T       Value() const;              ///< returns the current value of the control's text
94     T       StepSize() const;           ///< returns the step size of the control
95     T       MinValue() const;           ///< returns the minimum value of the control
96     T       MaxValue() const;           ///< returns the maximum value of the control
97     bool    Editable() const;           ///< returns true if the spinbox can have its value typed in directly
98 
99     X       ButtonWidth() const;        ///< returns the width used for the up and down buttons
100 
101     Clr     TextColor() const;          ///< returns the text color
102     Clr     InteriorColor() const;      ///< returns the the interior color of the control
103     Clr     HiliteColor() const;        ///< returns the color used to render hiliting around selected text
104     Clr     SelectedTextColor() const;  ///< returns the color used to render selected text
105 
106     mutable ValueChangedSignalType ValueChangedSignal; ///< the value changed signal object for this Spin
107     //@}
108 
109     /** \name Mutators */ ///@{
110     void Render() override;
111     void SizeMove(const Pt& ul, const Pt& lr) override;
112     void Disable(bool b = true) override;
113     void SetColor(Clr c) override;
114     void Incr();  ///< increments the value of the control's text by StepSize(), up to at most MaxValue()
115     void Decr();  ///< decrements the value of the control's text by StepSize(), down to at least MinValue()
116 
117     /** sets the value of the control's text to \a value, locked to the
118       * range [MinValue(), MaxValue()]*/
119     void SetValue(T value);
120     void SetStepSize(T step);           ///< sets the step size of the control to \a step
121     void SetMinValue(T value);          ///< sets the minimum value of the control to \a value
122     void SetMaxValue(T value);          ///< sets the maximum value of the control to \a value
123 
124     void SetButtonWidth(X width);       ///< sets the width used for the up and down buttons
125     void SetTextColor(Clr c);           ///< sets the text color
126     void SetInteriorColor(Clr c);       ///< sets the interior color of the control
127     void SetHiliteColor(Clr c);         ///< sets the color used to render hiliting around selected text
128     void SetSelectedTextColor(Clr c);   ///< sets the color used to render selected text
129     //@}
130 
131 protected:
132     typedef T ValueType;
133 
134     enum {BORDER_THICK = 2, PIXEL_MARGIN = 5};
135 
136     /** \name Accessors */ ///@{
137     Button* UpButton() const;   ///< returns a pointer to the Button control used as this control's up button
138     Button* DownButton() const; ///< returns a pointer to the Button control used as this control's down button
139     Edit*   GetEdit() const;    ///< returns a pointer to the Edit control used to render this control's text and accept keyboard input
140     //@}
141 
142     /** \name Mutators */ ///@{
143     void KeyPress(Key key, std::uint32_t key_code_point, Flags<ModKey> mod_keys) override;
144     void MouseWheel(const Pt& pt, int move, Flags<ModKey> mod_keys) override;
145     bool EventFilter(Wnd* w, const WndEvent& event) override;
146     virtual void SetEditTextFromValue();
147     //@}
148 
149     std::shared_ptr<Edit> m_edit;
150 
151 private:
152     void ConnectSignals();
153     void ValueUpdated(const std::string& val_text);
154     void IncrImpl(bool signal);
155     void DecrImpl(bool signal);
156     void SetValueImpl(T value, bool signal);
157 
158     T          m_value;
159     T          m_step_size;
160     T          m_min_value;
161     T          m_max_value;
162 
163     bool       m_editable;
164 
165     std::shared_ptr<Button> m_up_button;
166     std::shared_ptr<Button> m_down_button;
167 
168     X          m_button_width = GG::X(15);
169 
170     static void ValueChangedEcho(const T& value);
171 };
172 
173 
174 // template implementations
175 template <typename T>
Spin(T value,T step,T min,T max,bool edits,const std::shared_ptr<Font> & font,Clr color,Clr text_color)176 Spin<T>::Spin(T value, T step, T min, T max, bool edits, const std::shared_ptr<Font>& font, Clr color,
177               Clr text_color/* = CLR_BLACK*/) :
178     Control(X0, Y0, X1, font->Height() + 2 * PIXEL_MARGIN, INTERACTIVE),
179     m_value(value),
180     m_step_size(step),
181     m_min_value(min),
182     m_max_value(max),
183     m_editable(edits)
184 {
185     const auto& style = GetStyleFactory();
186     Control::SetColor(color);
187     m_edit = style->NewSpinEdit("", font, CLR_ZERO, text_color, CLR_ZERO);
188     auto small_font = GUI::GetGUI()->GetFont(font, static_cast<int>(font->PointSize() * 0.75));
189     m_up_button = style->NewSpinIncrButton(small_font, color);
190     m_down_button = style->NewSpinDecrButton(small_font, color);
191 
192     if (INSTRUMENT_ALL_SIGNALS)
193         ValueChangedSignal.connect(&ValueChangedEcho);
194 }
195 
196 template <typename T>
CompleteConstruction()197 void Spin<T>::CompleteConstruction()
198 {
199     const auto& style = GetStyleFactory();
200     m_edit->InstallEventFilter(shared_from_this());
201     m_up_button->InstallEventFilter(shared_from_this());
202     m_down_button->InstallEventFilter(shared_from_this());
203     AttachChild(m_edit);
204     AttachChild(m_up_button);
205     AttachChild(m_down_button);
206     ConnectSignals();
207     SizeMove(UpperLeft(), LowerRight());
208     Spin<T>::SetEditTextFromValue();
209 }
210 
211 template <typename T>
~Spin()212 Spin<T>::~Spin()
213 {}
214 
215 template <typename T>
MinUsableSize()216 Pt Spin<T>::MinUsableSize() const
217 {
218     Pt edit_min = m_edit->MinUsableSize();
219     Pt up_min = m_up_button->MinUsableSize();
220     Pt down_min = m_down_button->MinUsableSize();
221     return Pt(edit_min.x + std::max(up_min.x, down_min.x) + 2 * BORDER_THICK,
222               std::max(up_min.y + down_min.y, edit_min.y) + 2 * BORDER_THICK);
223 }
224 
225 template <typename T>
Value()226 T Spin<T>::Value() const
227 { return m_value; }
228 
229 template <typename T>
StepSize()230 T Spin<T>::StepSize() const
231 { return m_step_size; }
232 
233 template <typename T>
MinValue()234 T Spin<T>::MinValue() const
235 { return m_min_value; }
236 
237 template <typename T>
MaxValue()238 T Spin<T>::MaxValue() const
239 { return m_max_value; }
240 
241 template <typename T>
Editable()242 bool Spin<T>::Editable() const
243 { return m_editable; }
244 
245 template <typename T>
ButtonWidth()246 X Spin<T>::ButtonWidth() const
247 { return m_button_width; }
248 
249 template <typename T>
TextColor()250 Clr Spin<T>::TextColor() const
251 { return m_edit->TextColor(); }
252 
253 template <typename T>
InteriorColor()254 Clr Spin<T>::InteriorColor() const
255 { return m_edit->InteriorColor(); }
256 
257 template <typename T>
HiliteColor()258 Clr Spin<T>::HiliteColor() const
259 { return m_edit->HiliteColor(); }
260 
261 template <typename T>
SelectedTextColor()262 Clr Spin<T>::SelectedTextColor() const
263 { return m_edit->SelectedTextColor(); }
264 
265 template <typename T>
Render()266 void Spin<T>::Render()
267 {
268     Clr color_to_use = Disabled() ? DisabledColor(Color()) : Color();
269     Clr int_color_to_use = Disabled() ? DisabledColor(InteriorColor()) : InteriorColor();
270     Pt ul = UpperLeft(), lr = LowerRight();
271     BeveledRectangle(ul, lr, int_color_to_use, color_to_use, false, BORDER_THICK);
272 }
273 
274 template <typename T>
SizeMove(const Pt & ul,const Pt & lr)275 void Spin<T>::SizeMove(const Pt& ul, const Pt& lr)
276 {
277     Wnd::SizeMove(ul, lr);
278     const X BUTTON_X_POS = Width() - m_button_width - BORDER_THICK;
279     const Y BUTTONS_HEIGHT = Height() - 2 * BORDER_THICK; // height of *both* buttons
280     m_edit->SizeMove(Pt(), Pt(Width() - m_button_width, Height()));
281     m_up_button->SizeMove(Pt(BUTTON_X_POS, Y(BORDER_THICK)),
282                           Pt(BUTTON_X_POS + m_button_width, BORDER_THICK + BUTTONS_HEIGHT / 2));
283     m_down_button->SizeMove(Pt(BUTTON_X_POS, BORDER_THICK + BUTTONS_HEIGHT / 2),
284                             Pt(BUTTON_X_POS + m_button_width, BORDER_THICK + BUTTONS_HEIGHT));
285 }
286 
287 template <typename T>
Disable(bool b)288 void Spin<T>::Disable(bool b/* = true*/)
289 {
290     Control::Disable(b);
291     m_edit->Disable(b);
292     m_up_button->Disable(b);
293     m_down_button->Disable(b);
294 }
295 
296 template <typename T>
SetColor(Clr c)297 void Spin<T>::SetColor(Clr c)
298 {
299     Control::SetColor(c);
300     m_up_button->SetColor(c);
301     m_down_button->SetColor(c);
302 }
303 
304 template <typename T>
Incr()305 void Spin<T>::Incr()
306 { SetValueImpl(m_value + m_step_size, false); }
307 
308 template <typename T>
Decr()309 void Spin<T>::Decr()
310 { SetValueImpl(m_value - m_step_size, false); }
311 
312 template <typename T>
SetValue(T value)313 void Spin<T>::SetValue(T value)
314 { SetValueImpl(value, false); }
315 
316 template <typename T>
SetStepSize(T step)317 void Spin<T>::SetStepSize(T step)
318 {
319     m_step_size = step;
320     SetValue(m_value);
321 }
322 
323 template <typename T>
SetMinValue(T value)324 void Spin<T>::SetMinValue(T value)
325 {
326     m_min_value = value;
327     if (m_value < m_min_value)
328         SetValue(m_min_value);
329 }
330 
331 template <typename T>
SetMaxValue(T value)332 void Spin<T>::SetMaxValue(T value)
333 {
334     m_max_value = value;
335     if (m_max_value < m_value)
336         SetValue(m_max_value);
337 }
338 
339 template <typename T>
SetTextColor(Clr c)340 void Spin<T>::SetTextColor(Clr c)
341 { m_edit->SetTextColor(c); }
342 
343 template <typename T>
SetButtonWidth(X width)344 void Spin<T>::SetButtonWidth(X width)
345 {
346     if (1 <= width) {
347         if (Width() - 2 * BORDER_THICK - 1 < width)
348             width = Width() - 2 * BORDER_THICK - 1;
349         m_button_width = width;
350         SizeMove(RelativeUpperLeft(), RelativeLowerRight());
351     }
352 }
353 
354 template <typename T>
SetInteriorColor(Clr c)355 void Spin<T>::SetInteriorColor(Clr c)
356 { m_edit->SetInteriorColor(c); }
357 
358 template <typename T>
SetHiliteColor(Clr c)359 void Spin<T>::SetHiliteColor(Clr c)
360 { m_edit->SetHiliteColor(c); }
361 
362 template <typename T>
SetSelectedTextColor(Clr c)363 void Spin<T>::SetSelectedTextColor(Clr c)
364 { m_edit->SetSelectedTextColor(c); }
365 
366 template <typename T>
UpButton()367 Button* Spin<T>::UpButton() const
368 { return m_up_button.get(); }
369 
370 template <typename T>
DownButton()371 Button* Spin<T>::DownButton() const
372 { return m_down_button.get(); }
373 
374 template <typename T>
GetEdit()375 Edit* Spin<T>::GetEdit() const
376 { return m_edit.get(); }
377 
378 template <typename T>
KeyPress(Key key,std::uint32_t key_code_point,Flags<ModKey> mod_keys)379 void Spin<T>::KeyPress(Key key, std::uint32_t key_code_point, Flags<ModKey> mod_keys)
380 {
381     if (Disabled()) {
382         Control::KeyPress(key, key_code_point, mod_keys);
383         return;
384     }
385 
386     switch (key) {
387     case GGK_HOME:
388         SetValueImpl(m_min_value, true);
389         break;
390     case GGK_END:
391         SetValueImpl(m_max_value, true);
392         break;
393     case GGK_PAGEUP:
394     case GGK_UP:
395     case GGK_KP_PLUS:
396         IncrImpl(true);
397         break;
398     case GGK_PAGEDOWN:
399     case GGK_DOWN:
400     case GGK_KP_MINUS:
401         DecrImpl(true);
402         break;
403     default:
404         break;
405     }
406 }
407 
408 template <typename T>
MouseWheel(const Pt & pt,int move,Flags<ModKey> mod_keys)409 void Spin<T>::MouseWheel(const Pt& pt, int move, Flags<ModKey> mod_keys)
410 {
411     if (Disabled()) {
412         Control::MouseWheel(pt, move, mod_keys);
413         return;
414     }
415 
416     for (int i = 0; i < move; ++i)
417         IncrImpl(true);
418     for (int i = 0; i < -move; ++i)
419         DecrImpl(true);
420 }
421 
422 template <typename T>
EventFilter(Wnd * w,const WndEvent & event)423 bool Spin<T>::EventFilter(Wnd* w, const WndEvent& event)
424 {
425     if (w == m_edit.get()) {
426         if (!m_editable && event.Type() == WndEvent::GainingFocus) {
427             GUI::GetGUI()->SetFocusWnd(shared_from_this());
428             return true;
429         } else {
430             return !m_editable;
431         }
432     }
433     return false;
434 }
435 
436 template <typename T>
SetEditTextFromValue()437 void Spin<T>::SetEditTextFromValue()
438 {
439     if (m_edit)
440         m_edit->SetText(std::to_string(m_value));
441 }
442 
443 template <typename T>
ConnectSignals()444 void Spin<T>::ConnectSignals()
445 {
446 #if BOOST_VERSION >= 106000
447     using boost::placeholders::_1;
448 #endif
449 
450     m_edit->FocusUpdateSignal.connect(boost::bind(&Spin::ValueUpdated, this, _1));
451     m_up_button->LeftClickedSignal.connect(boost::bind(&Spin::IncrImpl, this, true));
452     m_down_button->LeftClickedSignal.connect(boost::bind(&Spin::DecrImpl, this, true));
453 }
454 
455 template <typename T>
ValueUpdated(const std::string & val_text)456 void Spin<T>::ValueUpdated(const std::string& val_text)
457 {
458     T value;
459     try {
460         value = boost::lexical_cast<T>(val_text);
461     } catch (boost::bad_lexical_cast) {
462         SetValueImpl(m_min_value, true);
463         return;
464     }
465     SetValueImpl(value, true);
466 }
467 
468 template <typename T>
IncrImpl(bool signal)469 void Spin<T>::IncrImpl(bool signal)
470 { SetValueImpl(static_cast<T>(m_value + m_step_size), signal); }
471 
472 template <typename T>
DecrImpl(bool signal)473 void Spin<T>::DecrImpl(bool signal)
474 { SetValueImpl(static_cast<T>(m_value - m_step_size), signal); }
475 
476 template <typename T>
SetValueImpl(T value,bool signal)477 void Spin<T>::SetValueImpl(T value, bool signal)
478 {
479     //std::cout << "Spin<T>::SetValueImpl(" << value << ", " << signal << ")" << std::endl;
480     T old_value = m_value;
481     if (value < m_min_value) {
482         m_value = m_min_value;
483     } else if (m_max_value < value) {
484         m_value = m_max_value;
485     } else {
486         // if the value supplied does not equal a valid value
487         if (std::abs(spin_details::mod(static_cast<T>(value - m_min_value), m_step_size)) >
488             std::numeric_limits<T>::epsilon())
489         {
490             // find nearest valid value to the one supplied
491             T closest_below =
492                 static_cast<T>(
493                     spin_details::div(static_cast<T>(value - m_min_value), m_step_size)
494                         * m_step_size
495                     + m_min_value);
496             T closest_above =
497                 static_cast<T>(closest_below + m_step_size);
498             //std::cout << " ... closest below: " << closest_below << "  above: " << closest_above << std::endl;
499             m_value =
500                 ((value - closest_below) < (closest_above - value) ?
501                  closest_below : closest_above);
502         } else {
503             m_value = value;
504         }
505     }
506     SetEditTextFromValue();
507     if (signal && m_value != old_value)
508         ValueChangedSignal(m_value);
509 }
510 
511 template <typename T>
ValueChangedEcho(const T & value)512 void Spin<T>::ValueChangedEcho(const T& value)
513 { std::cerr << "GG SIGNAL : Spin<>::ValueChangedSignal(value=" << value << ")\n"; }
514 
515 
516 namespace spin_details {
517     // provides a typesafe mod function
518     template <typename T>
mod(T dividend,T divisor)519     inline T mod (T dividend, T divisor) {return static_cast<T>(dividend % divisor);}
520 
521     // template specializations
522     template <> inline
523     float mod<float> (float dividend, float divisor) {return std::fmod(dividend, divisor);}
524     template <> inline
525     double mod<double> (double dividend, double divisor) {return std::fmod(dividend, divisor);}
526     template <> inline
527     long double mod<long double> (long double dividend, long double divisor) {return std::fmod(dividend, divisor);}
528 
529     // provides a typesafe div function
530     template <typename T>
div(T dividend,T divisor)531     inline T div (T dividend, T divisor) {return static_cast<T>(dividend / divisor);}
532 
533     // template specializations
534     template <> inline
535     float div<float> (float dividend, float divisor) {return std::floor(dividend / divisor);}
536     template <> inline
537     double div<double> (double dividend, double divisor) {return std::floor(dividend / divisor);}
538     template <> inline
539     long double div<long double> (long double dividend, long double divisor) {return std::floor(dividend / divisor);}
540 } // namespace spin_details
541 
542 } // namespace GG
543 
544 #endif
545 
546