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