682 lines
15 KiB
C++
682 lines
15 KiB
C++
/*
|
|
* A Spin box widget
|
|
* Nana C++ Library(http://www.nanapro.org)
|
|
* Copyright(C) 2003-2015 Jinhao(cnjinhao@hotmail.com)
|
|
*
|
|
* Distributed under the Boost Software License, Version 1.0.
|
|
* (See accompanying file LICENSE_1_0.txt or copy at
|
|
* http://www.boost.org/LICENSE_1_0.txt)
|
|
*
|
|
* @file: nana/gui/widgets/spinbox.cpp
|
|
*/
|
|
|
|
#include <nana/gui/widgets/spinbox.hpp>
|
|
#include <nana/gui/widgets/skeletons/text_editor.hpp>
|
|
#include <nana/gui/element.hpp>
|
|
#include <nana/gui/timer.hpp>
|
|
|
|
namespace nana
|
|
{
|
|
arg_spinbox::arg_spinbox(spinbox& wdg): widget(wdg)
|
|
{}
|
|
|
|
namespace drawerbase
|
|
{
|
|
namespace spinbox
|
|
{
|
|
class event_agent
|
|
: public widgets::skeletons::textbase_event_agent_interface
|
|
{
|
|
public:
|
|
event_agent(::nana::spinbox& wdg)
|
|
: widget_(wdg)
|
|
{}
|
|
|
|
void first_change() override{} //empty, because spinbox does not have this event.
|
|
|
|
void text_changed() override
|
|
{
|
|
widget_.events().text_changed.emit(::nana::arg_spinbox{ widget_ });
|
|
}
|
|
private:
|
|
::nana::spinbox & widget_;
|
|
};
|
|
|
|
enum class buttons
|
|
{
|
|
none, increase, decrease
|
|
};
|
|
|
|
class range_interface
|
|
{
|
|
public:
|
|
virtual ~range_interface() = default;
|
|
|
|
virtual std::wstring value() const = 0;
|
|
virtual bool value(const std::wstring&) = 0;
|
|
virtual bool check_value(const std::wstring&) const = 0;
|
|
virtual void spin(bool increase) = 0;
|
|
};
|
|
|
|
template<typename T>
|
|
class range_numeric
|
|
: public range_interface
|
|
{
|
|
public:
|
|
range_numeric(T vbegin, T vlast, T step)
|
|
: begin_{ vbegin }, last_{ vlast }, step_{ step }, value_{ vbegin }
|
|
{}
|
|
|
|
std::wstring value() const override
|
|
{
|
|
std::wstringstream ss;
|
|
ss << value_;
|
|
return ss.str();
|
|
}
|
|
|
|
bool value(const std::wstring& value_str) override
|
|
{
|
|
std::wstringstream ss;
|
|
ss << value_str;
|
|
|
|
T v;
|
|
ss >> v;
|
|
if (begin_ <= v && v <= last_)
|
|
{
|
|
value_ = v;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool check_value(const std::wstring& value_str) const override
|
|
{
|
|
std::wstringstream ss;
|
|
ss << value_str;
|
|
|
|
T v;
|
|
ss >> v;
|
|
return (begin_ <= v && v <= last_);
|
|
}
|
|
|
|
void spin(bool increase) override
|
|
{
|
|
if (increase)
|
|
{
|
|
value_ += step_;
|
|
if (value_ > last_)
|
|
value_ = last_;
|
|
}
|
|
else
|
|
{
|
|
value_ -= step_;
|
|
if (value_ < begin_)
|
|
value_ = begin_;
|
|
}
|
|
}
|
|
private:
|
|
T begin_;
|
|
T last_;
|
|
T step_;
|
|
T value_;
|
|
};
|
|
|
|
class range_text
|
|
: public range_interface
|
|
{
|
|
public:
|
|
range_text(std::initializer_list<std::string> & initlist)
|
|
{
|
|
for (auto & s : initlist)
|
|
{
|
|
texts_.emplace_back(::nana::charset(s, ::nana::unicode::utf8));
|
|
}
|
|
}
|
|
|
|
range_text(std::initializer_list<std::wstring>& initlist)
|
|
: texts_(initlist)
|
|
{}
|
|
|
|
std::wstring value() const override
|
|
{
|
|
if (texts_.empty())
|
|
return{};
|
|
|
|
return texts_[pos_];
|
|
}
|
|
|
|
bool value(const std::wstring& value_str) override
|
|
{
|
|
auto i = std::find(texts_.cbegin(), texts_.cend(), value_str);
|
|
if (i != texts_.cend())
|
|
{
|
|
pos_ = i - texts_.cbegin();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool check_value(const std::wstring& str) const override
|
|
{
|
|
for (auto & s : texts_)
|
|
{
|
|
if (s.find(str) != s.npos)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void spin(bool increase) override
|
|
{
|
|
if (texts_.empty())
|
|
return;
|
|
|
|
if (increase)
|
|
{
|
|
++pos_;
|
|
if (texts_.size() <= pos_)
|
|
pos_ = texts_.size() - 1;
|
|
}
|
|
else
|
|
{
|
|
--pos_;
|
|
if (texts_.size() <= pos_)
|
|
pos_ = 0;
|
|
}
|
|
}
|
|
private:
|
|
std::vector<std::wstring> texts_;
|
|
std::size_t pos_{0};
|
|
};
|
|
|
|
class implementation
|
|
{
|
|
public:
|
|
implementation()
|
|
{
|
|
//Sets a timer for continous spin when mouse button is pressed.
|
|
timer_.elapse([this]
|
|
{
|
|
range_->spin(buttons::increase == spin_stated_);
|
|
_m_text();
|
|
API::update_window(editor_->window_handle());
|
|
|
|
auto intv = timer_.interval();
|
|
if (intv > 50)
|
|
timer_.interval(intv / 2);
|
|
});
|
|
|
|
timer_.interval(600);
|
|
}
|
|
|
|
void attach(::nana::widget& wdg, ::nana::paint::graphics& graph)
|
|
{
|
|
auto wd = wdg.handle();
|
|
graph_ = &graph;
|
|
auto scheme = static_cast<::nana::widgets::skeletons::text_editor_scheme*>(API::dev::get_scheme(wd));
|
|
editor_ = new ::nana::widgets::skeletons::text_editor(wd, graph, scheme);
|
|
editor_->multi_lines(false);
|
|
editor_->set_accept([this](::nana::char_t ch)
|
|
{
|
|
auto str = editor_->text();
|
|
str += ch;
|
|
return range_->check_value(str);
|
|
});
|
|
|
|
evt_agent_.reset(new event_agent(static_cast<nana::spinbox&>(wdg)));
|
|
editor_->textbase().set_event_agent(evt_agent_.get());
|
|
|
|
if (!range_)
|
|
range_.reset(new range_numeric<int>(0, 100, 1));
|
|
|
|
_m_text();
|
|
|
|
API::tabstop(wd);
|
|
API::eat_tabstop(wd, true);
|
|
API::effects_edge_nimbus(wd, effects::edge_nimbus::active);
|
|
API::effects_edge_nimbus(wd, effects::edge_nimbus::over);
|
|
reset_text_area();
|
|
}
|
|
|
|
void detach()
|
|
{
|
|
delete editor_;
|
|
editor_ = nullptr;
|
|
}
|
|
|
|
::nana::string value() const
|
|
{
|
|
return range_->value();
|
|
}
|
|
|
|
bool value(const ::nana::string& value_str)
|
|
{
|
|
if (!range_->value(value_str))
|
|
return false;
|
|
|
|
_m_text();
|
|
return true;
|
|
}
|
|
|
|
void set_range(std::unique_ptr<range_interface> ptr)
|
|
{
|
|
range_.swap(ptr);
|
|
|
|
_m_text();
|
|
}
|
|
|
|
void qualify(std::wstring&& prefix, std::wstring&& suffix)
|
|
{
|
|
surround_.prefix = std::move(prefix);
|
|
surround_.suffix = std::move(suffix);
|
|
|
|
if (editor_)
|
|
{
|
|
_m_text();
|
|
API::update_window(editor_->window_handle());
|
|
}
|
|
}
|
|
|
|
void draw_spins()
|
|
{
|
|
_m_draw_spins(buttons::none);
|
|
}
|
|
|
|
void render()
|
|
{
|
|
editor_->render(API::is_focus_window(editor_->window_handle()));
|
|
_m_draw_spins(spin_stated_);
|
|
}
|
|
|
|
::nana::widgets::skeletons::text_editor* editor() const
|
|
{
|
|
return editor_;
|
|
}
|
|
|
|
void mouse_wheel(bool upwards)
|
|
{
|
|
range_->spin(!upwards);
|
|
_m_text();
|
|
}
|
|
|
|
bool mouse_button(const ::nana::arg_mouse& arg, bool pressed)
|
|
{
|
|
if (!pressed)
|
|
{
|
|
API::capture_window(editor_->window_handle(), false);
|
|
timer_.stop();
|
|
timer_.interval(600);
|
|
}
|
|
|
|
if (buttons::none != spin_stated_)
|
|
{
|
|
//Spins the value when mouse button is released
|
|
if (pressed)
|
|
{
|
|
API::capture_window(editor_->window_handle(), true);
|
|
range_->spin(buttons::increase == spin_stated_);
|
|
_m_text();
|
|
timer_.start();
|
|
}
|
|
_m_draw_spins(spin_stated_);
|
|
return true;
|
|
}
|
|
|
|
|
|
bool refreshed = false;
|
|
if (pressed)
|
|
refreshed = editor_->mouse_down(arg.left_button, arg.pos);
|
|
else
|
|
refreshed = editor_->mouse_up(arg.left_button, arg.pos);
|
|
|
|
if (refreshed)
|
|
_m_draw_spins(buttons::none);
|
|
|
|
return refreshed;
|
|
}
|
|
|
|
bool mouse_move(bool left_button, const ::nana::point& pos)
|
|
{
|
|
if (editor_->mouse_move(left_button, pos))
|
|
{
|
|
editor_->reset_caret();
|
|
render();
|
|
return true;
|
|
}
|
|
|
|
auto btn = _m_where(pos);
|
|
if (buttons::none != btn)
|
|
{
|
|
spin_stated_ = btn;
|
|
_m_draw_spins(btn);
|
|
return true;
|
|
}
|
|
else if (buttons::none != spin_stated_)
|
|
{
|
|
spin_stated_ = buttons::none;
|
|
_m_draw_spins(buttons::none);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void reset_text_area()
|
|
{
|
|
auto spins_r = _m_spins_area();
|
|
if (spins_r.x == 0)
|
|
editor_->text_area({});
|
|
else
|
|
editor_->text_area({ 2, 2, graph_->width() - spins_r.width - 2, spins_r.height - 2 });
|
|
}
|
|
private:
|
|
void _m_text()
|
|
{
|
|
if (editor_)
|
|
{
|
|
std::wstring text = surround_.prefix + range_->value() + surround_.suffix;
|
|
editor_->text(std::move(text));
|
|
_m_draw_spins(spin_stated_);
|
|
}
|
|
}
|
|
|
|
::nana::rectangle _m_spins_area() const
|
|
{
|
|
auto size = API::window_size(editor_->window_handle());
|
|
if (size.width > 18)
|
|
return{ static_cast<int>(size.width - 16), 0, 16, size.height };
|
|
|
|
return{ 0, 0, size.width, size.height };
|
|
}
|
|
|
|
buttons _m_where(const ::nana::point& pos) const
|
|
{
|
|
auto spins_r = _m_spins_area();
|
|
if (spins_r.is_hit(pos))
|
|
{
|
|
if (pos.y < spins_r.y + static_cast<int>(spins_r.height / 2))
|
|
return buttons::increase;
|
|
|
|
return buttons::decrease;
|
|
}
|
|
return buttons::none;
|
|
}
|
|
|
|
void _m_draw_spins(buttons spins)
|
|
{
|
|
auto estate = API::element_state(editor_->window_handle());
|
|
|
|
auto spin_r0 = _m_spins_area();
|
|
spin_r0.height /= 2;
|
|
|
|
auto spin_r1 = spin_r0;
|
|
spin_r1.y += static_cast<int>(spin_r0.height);
|
|
spin_r1.height = _m_spins_area().height - spin_r0.height;
|
|
|
|
::nana::color bgcolor{ 3, 65, 140 };
|
|
facade<element::arrow> arrow;
|
|
facade<element::button> button;
|
|
|
|
auto spin_state = (buttons::increase == spins ? estate : element_state::normal);
|
|
button.draw(*graph_, bgcolor, colors::white, spin_r0, spin_state);
|
|
spin_r0.x += 5;
|
|
arrow.draw(*graph_, bgcolor, colors::white, spin_r0, spin_state);
|
|
|
|
spin_state = (buttons::decrease == spins ? estate : element_state::normal);
|
|
button.draw(*graph_, bgcolor, colors::white, spin_r1, spin_state);
|
|
spin_r1.x += 5;
|
|
arrow.direction(direction::south);
|
|
arrow.draw(*graph_, bgcolor, colors::white, spin_r1, spin_state);
|
|
}
|
|
private:
|
|
::nana::paint::graphics * graph_{nullptr};
|
|
::nana::widgets::skeletons::text_editor * editor_{nullptr};
|
|
std::unique_ptr<event_agent> evt_agent_;
|
|
buttons spin_stated_{ buttons::none };
|
|
std::unique_ptr<range_interface> range_;
|
|
::nana::timer timer_;
|
|
|
|
struct surround_data
|
|
{
|
|
std::wstring prefix;
|
|
std::wstring suffix;
|
|
}surround_;
|
|
};
|
|
|
|
//class drawer
|
|
drawer::drawer()
|
|
: impl_(new implementation)
|
|
{}
|
|
|
|
drawer::~drawer()
|
|
{
|
|
delete impl_;
|
|
}
|
|
|
|
implementation* drawer::impl() const
|
|
{
|
|
return impl_;
|
|
}
|
|
|
|
//Overrides drawer_trigger
|
|
void drawer::attached(widget_reference wdg, graph_reference graph)
|
|
{
|
|
impl_->attach(wdg, graph);
|
|
}
|
|
|
|
void drawer::refresh(graph_reference)
|
|
{
|
|
impl_->render();
|
|
}
|
|
|
|
void drawer::focus(graph_reference, const arg_focus&)
|
|
{
|
|
impl_->render();
|
|
impl_->editor()->reset_caret();
|
|
API::lazy_refresh();
|
|
}
|
|
|
|
void drawer::mouse_wheel(graph_reference, const arg_wheel& arg)
|
|
{
|
|
impl_->mouse_wheel(arg.upwards);
|
|
impl_->editor()->reset_caret();
|
|
API::lazy_refresh();
|
|
}
|
|
|
|
void drawer::mouse_down(graph_reference, const arg_mouse& arg)
|
|
{
|
|
if (impl_->mouse_button(arg, true))
|
|
API::lazy_refresh();
|
|
}
|
|
|
|
void drawer::mouse_up(graph_reference, const arg_mouse& arg)
|
|
{
|
|
if (impl_->mouse_button(arg, false))
|
|
API::lazy_refresh();
|
|
}
|
|
|
|
void drawer::mouse_move(graph_reference, const arg_mouse& arg)
|
|
{
|
|
if (impl_->mouse_move(arg.left_button, arg.pos))
|
|
API::lazy_refresh();
|
|
}
|
|
|
|
void drawer::mouse_leave(graph_reference, const arg_mouse&)
|
|
{
|
|
impl_->render();
|
|
API::lazy_refresh();
|
|
}
|
|
|
|
void drawer::key_press(graph_reference, const arg_keyboard& arg)
|
|
{
|
|
if (impl_->editor()->move(arg.key))
|
|
{
|
|
impl_->editor()->reset_caret();
|
|
impl_->draw_spins();
|
|
API::lazy_refresh();
|
|
}
|
|
}
|
|
|
|
void drawer::key_char(graph_reference, const arg_keyboard& arg)
|
|
{
|
|
if (impl_->editor()->respone_keyboard(arg.key))
|
|
{
|
|
impl_->draw_spins();
|
|
API::lazy_refresh();
|
|
}
|
|
}
|
|
|
|
void drawer::resized(graph_reference graph, const arg_resized& arg)
|
|
{
|
|
impl_->reset_text_area();
|
|
impl_->render();
|
|
impl_->editor()->reset_caret();
|
|
API::lazy_refresh();
|
|
}
|
|
}
|
|
}//end namespace drawerbase
|
|
|
|
spinbox::spinbox()
|
|
{}
|
|
|
|
spinbox::spinbox(window wd, bool visible)
|
|
{
|
|
this->create(wd, visible);
|
|
}
|
|
|
|
spinbox::spinbox(window wd, const nana::rectangle& r, bool visible)
|
|
{
|
|
this->create(wd, r, visible);
|
|
}
|
|
|
|
void spinbox::editable(bool accept)
|
|
{
|
|
internal_scope_guard lock;
|
|
auto editor = get_drawer_trigger().impl()->editor();
|
|
if (editor)
|
|
editor->editable(accept);
|
|
}
|
|
|
|
bool spinbox::editable() const
|
|
{
|
|
auto editor = get_drawer_trigger().impl()->editor();
|
|
return (editor ? editor->attr().editable : false);
|
|
}
|
|
|
|
void spinbox::range(int begin, int last, int step)
|
|
{
|
|
using namespace drawerbase::spinbox;
|
|
get_drawer_trigger().impl()->set_range(std::unique_ptr<range_interface>(new range_numeric<int>(begin, last, step)));
|
|
API::refresh_window(handle());
|
|
}
|
|
|
|
void spinbox::range(double begin, double last, double step)
|
|
{
|
|
using namespace drawerbase::spinbox;
|
|
get_drawer_trigger().impl()->set_range(std::unique_ptr<range_interface>(new range_numeric<double>(begin, last, step)));
|
|
API::refresh_window(handle());
|
|
}
|
|
|
|
void spinbox::range(std::initializer_list<std::string> steps_utf8)
|
|
{
|
|
using namespace drawerbase::spinbox;
|
|
get_drawer_trigger().impl()->set_range(std::unique_ptr<range_interface>(new range_text(steps_utf8)));
|
|
API::refresh_window(handle());
|
|
}
|
|
|
|
void spinbox::range(std::initializer_list<std::wstring> steps)
|
|
{
|
|
using namespace drawerbase::spinbox;
|
|
get_drawer_trigger().impl()->set_range(std::unique_ptr<range_interface>(new range_text(steps)));
|
|
API::refresh_window(handle());
|
|
}
|
|
|
|
::nana::string spinbox::value() const
|
|
{
|
|
internal_scope_guard lock;
|
|
if (handle())
|
|
return get_drawer_trigger().impl()->value();
|
|
return{};
|
|
}
|
|
|
|
void spinbox::value(const ::nana::string& s)
|
|
{
|
|
internal_scope_guard lock;
|
|
if (handle())
|
|
{
|
|
if (get_drawer_trigger().impl()->value(s))
|
|
API::refresh_window(handle());
|
|
}
|
|
}
|
|
|
|
int spinbox::to_int() const
|
|
{
|
|
return ::nana::stoi(value());
|
|
}
|
|
|
|
double spinbox::to_double() const
|
|
{
|
|
return ::nana::stod(value());
|
|
}
|
|
|
|
void spinbox::set_accept(std::function<bool(::nana::char_t)> pred)
|
|
{
|
|
internal_scope_guard lock;
|
|
auto editor = get_drawer_trigger().impl()->editor();
|
|
if (editor)
|
|
editor->set_accept(std::move(pred));
|
|
}
|
|
|
|
void spinbox::set_accept_integer()
|
|
{
|
|
using accepts = ::nana::widgets::skeletons::text_editor::accepts;
|
|
auto editor = get_drawer_trigger().impl()->editor();
|
|
if (editor)
|
|
editor->set_accept(accepts::integer);
|
|
}
|
|
|
|
void spinbox::set_accept_real()
|
|
{
|
|
using accepts = ::nana::widgets::skeletons::text_editor::accepts;
|
|
auto editor = get_drawer_trigger().impl()->editor();
|
|
if (editor)
|
|
editor->set_accept(accepts::real);
|
|
}
|
|
|
|
void spinbox::remove_accept()
|
|
{
|
|
using accepts = ::nana::widgets::skeletons::text_editor::accepts;
|
|
auto editor = get_drawer_trigger().impl()->editor();
|
|
if (editor)
|
|
editor->set_accept(accepts::no_restrict);
|
|
}
|
|
|
|
void spinbox::qualify(std::wstring prefix, std::wstring suffix)
|
|
{
|
|
get_drawer_trigger().impl()->qualify(std::move(prefix), std::move(suffix));
|
|
}
|
|
|
|
void spinbox::qualify(const std::string & prefix_utf8, const std::string& suffix_utf8)
|
|
{
|
|
qualify(static_cast<std::wstring>(::nana::charset(prefix_utf8, ::nana::unicode::utf8)), static_cast<std::wstring>(::nana::charset(suffix_utf8, ::nana::unicode::utf8)));
|
|
}
|
|
|
|
::nana::string spinbox::_m_caption() const
|
|
{
|
|
internal_scope_guard lock;
|
|
auto editor = get_drawer_trigger().impl()->editor();
|
|
return (editor ? editor->text() : nana::string());
|
|
}
|
|
|
|
void spinbox::_m_caption(::nana::string&& text)
|
|
{
|
|
internal_scope_guard lock;
|
|
auto editor = get_drawer_trigger().impl()->editor();
|
|
if (editor)
|
|
{
|
|
editor->text(std::move(text));
|
|
API::refresh_window(*this);
|
|
}
|
|
}
|
|
}//end namespace nana
|