/* * A Combox Implementation * 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/combox.cpp */ #include #include #include #include #include #include #include namespace nana { arg_combox::arg_combox(combox& wdg): widget(wdg) {} namespace drawerbase { namespace combox { class event_agent : public widgets::skeletons::textbase_event_agent_interface { public: event_agent(::nana::combox& wdg) : widget_(wdg) {} void first_change() override{} //empty, because combox does not have this event. void text_changed() override { widget_.events().text_changed.emit(::nana::arg_combox{ widget_ }); } private: ::nana::combox & widget_; }; struct item : public float_listbox::item_interface { public: std::shared_ptr key; nana::paint::image item_image; nana::string item_text; mutable std::shared_ptr any_ptr; item(std::shared_ptr && kv) : key(std::move(kv)) { } item(nana::string&& s) : item_text(std::move(s)) {} private: //implement item_interface methods const nana::paint::image & image() const override { return item_image; } const nana::char_t* text() const override { return item_text.data(); } }; class drawer_impl { public: using graph_reference = paint::graphics&; using widget_reference = widget&; enum class parts{none, text, push_button}; drawer_impl() { state_.focused = false; state_.button_state = element_state::normal; state_.pointer_where = parts::none; state_.lister = nullptr; } void renderer(drawerbase::float_listbox::item_renderer* ir) { item_renderer_ = ir; } void attached(widget_reference wd, graph_reference graph) { widget_ = static_cast< ::nana::combox*>(&wd); auto scheme = dynamic_cast< ::nana::widgets::skeletons::text_editor_scheme*>(API::dev::get_scheme(wd)); editor_ = new widgets::skeletons::text_editor(widget_->handle(), graph, scheme); editor_->multi_lines(false); editable(false); graph_ = &graph; evt_agent_.reset(new event_agent{ static_cast(wd) }); editor_->textbase().set_event_agent(evt_agent_.get()); } void detached() { delete editor_; editor_ = nullptr; graph_ = nullptr; } void insert(nana::string&& text) { items_.emplace_back(std::make_shared(std::move(text))); API::refresh_window(widget_->handle()); } nana::any * anyobj(std::size_t pos, bool allocate_if_empty) const { if(pos >= items_.size()) return nullptr; auto & any_ptr = items_[pos]->any_ptr; if (allocate_if_empty && (nullptr == any_ptr)) any_ptr = std::make_shared(); return any_ptr.get(); } void text_area(const nana::size& s) { nana::rectangle r(2, 2, s.width > 19 ? s.width - 19 : 0, s.height > 4 ? s.height - 4 : 0); if(image_enabled_) { unsigned place = image_pixels_ + 2; r.x += place; if(r.width > place) r.width -= place; } editor_->text_area(r); } widgets::skeletons::text_editor * editor() const { return editor_; } widget* widget_ptr() const { return widget_; } void clear() { items_.clear(); module_.items.clear(); module_.index = nana::npos; } void editable(bool enb) { if(editor_) { editor_->editable(enb); if(enb) editor_->ext_renderer().background = nullptr; else editor_->ext_renderer().background = std::bind(&drawer_impl::_m_draw_background, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); editor_->enable_background(enb); editor_->enable_background_counterpart(!enb); API::refresh_window(widget_->handle()); } } bool editable() const { return (editor_ && editor_->attr().editable); } bool calc_where(graph_reference graph, int x, int y) { auto new_where = parts::none; if(1 < x && x < static_cast(graph.width()) - 2 && 1 < y && y < static_cast(graph.height()) - 2) { if((editor_->attr().editable == false) || (static_cast(graph.width()) - 22 <= x)) new_where = parts::push_button; else new_where = parts::text; } if (new_where == state_.pointer_where) return false; state_.pointer_where = new_where; return true; } void set_mouse_over(bool mo) { state_.button_state = (mo ? element_state::hovered : element_state::normal); state_.pointer_where = parts::none; } void set_mouse_press(bool mp) { state_.button_state = (mp ? element_state::pressed : element_state::hovered); } void set_focused(bool f) { if(editor_) { state_.focused = f; if(editor_->attr().editable) editor_->select(f); } } bool has_lister() const { return (state_.lister != nullptr); } void open_lister_if_push_button_positioned() { if((nullptr == state_.lister) && !items_.empty() && (parts::push_button == state_.pointer_where)) { module_.items.clear(); std::copy(items_.cbegin(), items_.cend(), std::back_inserter(module_.items)); state_.lister = &form_loader()(widget_->handle(), nana::rectangle(0, widget_->size().height, widget_->size().width, 10), true); state_.lister->renderer(item_renderer_); state_.lister->set_module(module_, image_pixels_); state_.item_index_before_selection = module_.index; //The lister window closes by itself. I just take care about the destroy event. //The event should be destroy rather than unload. Because the unload event is invoked while //the lister is not closed, if popuping a message box, the lister will cover the message box. state_.lister->events().destroy.connect_unignorable([this] { _m_lister_close_sig(); }); } } void scroll_items(bool upwards) { if(state_.lister) state_.lister->scroll_items(upwards); } void move_items(bool upwards, bool circle) { if (state_.lister) { state_.lister->move_items(upwards, circle); return; } auto pos = module_.index; if (upwards) { if (pos && (pos < items_.size())) --pos; else if (circle) pos = items_.size() - 1; } else { if ((pos + 1) < items_.size()) ++pos; else if (circle) pos = 0; } if (pos != module_.index) option(pos, false); } void draw() { bool enb = widget_->enabled(); if(editor_) { text_area(widget_->size()); editor_->render(state_.focused); } _m_draw_push_button(enb); _m_draw_image(); } std::size_t the_number_of_options() const { return items_.size(); } std::size_t option() const { return (module_.index < items_.size() ? module_.index : nana::npos); } void option(std::size_t index, bool ignore_condition) { if(items_.size() <= index) return; std::size_t old_index = module_.index; module_.index = index; if(nullptr == widget_) return; //Test if the current item or text is different from selected. if(ignore_condition || (old_index != index) || (items_[index]->item_text != widget_->caption())) { auto pos = API::cursor_position(); API::calc_window_point(widget_->handle(), pos); if (calc_where(*graph_, pos.x, pos.y)) state_.button_state = element_state::normal; editor_->text(items_[index]->item_text); _m_draw_push_button(widget_->enabled()); _m_draw_image(); widget_->events().selected.emit(::nana::arg_combox(*widget_)); } } std::size_t at_key(std::shared_ptr&& p) { std::size_t pos = 0; for (auto & m : items_) { if (m->key && detail::pred_equal_by_less(m->key.get(), p.get())) return pos; ++pos; } pos = items_.size(); items_.emplace_back(std::make_shared(std::move(p))); //Redraw, because the state of push button is changed when a first new item is created. if (0 == pos) API::refresh_window(*widget_); return pos; } void erase(detail::key_interface * kv) { std::size_t pos = 0; for (auto & m : items_) { if (m->key && detail::pred_equal_by_less(m->key.get(), kv)) { erase(pos); return; } ++pos; } } item& at(std::size_t pos) { return *items_.at(pos); } const item& at(std::size_t pos) const { return *items_.at(pos); } void erase(std::size_t pos) { if (pos >= items_.size()) return; if (pos == module_.index) { module_.index = ::nana::npos; this->widget_->caption(L""); } else if ((::nana::npos != module_.index) && (pos < module_.index)) --module_.index; items_.erase(items_.begin() + pos); //Redraw, because the state of push button is changed when the last item is removed. if (items_.empty()) API::refresh_window(*widget_); } void image(std::size_t pos, const nana::paint::image& img) { if (pos < items_.size()) { items_[pos]->item_image = img; if ((false == image_enabled_) && img) { image_enabled_ = true; draw(); } } } bool image_pixels(unsigned px) { if (image_pixels_ == px) return false; image_pixels_ = px; return true; } private: void _m_lister_close_sig() { state_.lister = nullptr; //The lister closes by itself. if ((module_.index != nana::npos) && (module_.index != state_.item_index_before_selection)) { option(module_.index, true); API::update_window(*widget_); } else { //Redraw the widget even though the index has not been changed, //because the push button should be updated due to the state //changed from pressed to normal/hovered. API::refresh_window(*widget_); } } void _m_draw_background(graph_reference graph, const rectangle&, const ::nana::color&) { auto clr_from = colors::button_face_shadow_start; auto clr_to = colors::button_face_shadow_end; int pare_off_px = 1; if (element_state::pressed == state_.button_state) { pare_off_px = 2; std::swap(clr_from, clr_to); } graph.gradual_rectangle(::nana::rectangle(graph.size()).pare_off(pare_off_px), clr_from, clr_to, true); } void _m_draw_push_button(bool enabled) { ::nana::rectangle r{graph_->size()}; r.x = r.right() - 16; r.y = 1; r.width = 16; r.height -= 2; auto estate = state_.button_state; if (enabled && !items_.empty()) { if (has_lister() || (element_state::pressed == estate && state_.pointer_where == parts::push_button)) estate = element_state::pressed; } else estate = element_state::disabled; facade button; button.draw(*graph_, ::nana::color{ 3, 65, 140 }, colors::white, r, estate); facade arrow("solid_triangle"); arrow.direction(::nana::direction::south); r.y += (r.height / 2) - 7; r.width = r.height = 16; arrow.draw(*graph_, {}, colors::white, r, element_state::normal); } void _m_draw_image() { if(items_.size() <= module_.index) return; auto & img = items_[module_.index]->item_image; if(img.empty()) return; unsigned vpix = editor_->line_height(); nana::size imgsz = img.size(); if(imgsz.width > image_pixels_) { unsigned new_h = image_pixels_ * imgsz.height / imgsz.width; if(new_h > vpix) { imgsz.width = vpix * imgsz.width / imgsz.height; imgsz.height = vpix; } else { imgsz.width = image_pixels_; imgsz.height = new_h; } } else if(imgsz.height > vpix) { unsigned new_w = vpix * imgsz.width / imgsz.height; if(new_w > image_pixels_) { imgsz.height = image_pixels_ * imgsz.height / imgsz.width; imgsz.width = image_pixels_; } else { imgsz.height = vpix; imgsz.width = new_w; } } nana::point pos((image_pixels_ - imgsz.width) / 2 + 2, (vpix - imgsz.height) / 2 + 2); img.stretch(img.size(), *graph_, nana::rectangle(pos, imgsz)); } private: std::vector> items_; nana::float_listbox::module_type module_; ::nana::combox * widget_{ nullptr }; nana::paint::graphics * graph_{ nullptr }; drawerbase::float_listbox::item_renderer* item_renderer_{ nullptr }; bool image_enabled_{ false }; unsigned image_pixels_{ 16 }; widgets::skeletons::text_editor * editor_{ nullptr }; std::unique_ptr evt_agent_; struct state_type { bool focused; element_state button_state; parts pointer_where; nana::float_listbox * lister; std::size_t item_index_before_selection; }state_; }; //class trigger trigger::trigger() : drawer_(new drawer_impl) {} trigger::~trigger() { delete drawer_; } drawer_impl& trigger::get_drawer_impl() { return *drawer_; } const drawer_impl& trigger::get_drawer_impl() const { return *drawer_; } void trigger::attached(widget_reference wdg, graph_reference graph) { wdg.bgcolor(colors::white); drawer_->attached(wdg, graph); API::effects_edge_nimbus(wdg, effects::edge_nimbus::active); API::effects_edge_nimbus(wdg, effects::edge_nimbus::over); } void trigger::detached() { drawer_->detached(); } void trigger::refresh(graph_reference) { drawer_->draw(); } void trigger::focus(graph_reference, const arg_focus& arg) { drawer_->set_focused(arg.getting); if(drawer_->widget_ptr()->enabled()) { drawer_->draw(); drawer_->editor()->reset_caret(); API::lazy_refresh(); } } void trigger::mouse_enter(graph_reference, const arg_mouse&) { drawer_->set_mouse_over(true); if(drawer_->widget_ptr()->enabled()) { drawer_->draw(); API::lazy_refresh(); } } void trigger::mouse_leave(graph_reference, const arg_mouse&) { drawer_->set_mouse_over(false); drawer_->editor()->mouse_enter(false); if(drawer_->widget_ptr()->enabled()) { drawer_->draw(); API::lazy_refresh(); } } void trigger::mouse_down(graph_reference graph, const arg_mouse& arg) { drawer_->set_mouse_press(true); if(drawer_->widget_ptr()->enabled()) { auto * editor = drawer_->editor(); if(false == editor->mouse_down(arg.left_button, arg.pos)) drawer_->open_lister_if_push_button_positioned(); drawer_->draw(); if(editor->attr().editable) editor->reset_caret(); API::lazy_refresh(); } } void trigger::mouse_up(graph_reference graph, const arg_mouse& arg) { if (drawer_->widget_ptr()->enabled() && !drawer_->has_lister()) { drawer_->editor()->mouse_up(arg.left_button, arg.pos); drawer_->set_mouse_press(false); drawer_->draw(); API::lazy_refresh(); } } void trigger::mouse_move(graph_reference graph, const arg_mouse& arg) { if(drawer_->widget_ptr()->enabled()) { bool redraw = drawer_->calc_where(graph, arg.pos.x, arg.pos.y); redraw |= drawer_->editor()->mouse_move(arg.left_button, arg.pos); if(redraw) { drawer_->draw(); drawer_->editor()->reset_caret(); API::lazy_refresh(); } } } void trigger::mouse_wheel(graph_reference graph, const arg_wheel& arg) { if(drawer_->widget_ptr()->enabled()) { if(drawer_->has_lister()) drawer_->scroll_items(arg.upwards); else drawer_->move_items(arg.upwards, false); } } void trigger::key_press(graph_reference, const arg_keyboard& arg) { if(!drawer_->widget_ptr()->enabled()) return; if(drawer_->editable()) { bool is_move_up = false; switch(arg.key) { case keyboard::os_arrow_left: case keyboard::os_arrow_right: drawer_->editor()->move(arg.key); drawer_->editor()->reset_caret(); break; case keyboard::os_arrow_up: is_move_up = true; case keyboard::os_arrow_down: drawer_->move_items(is_move_up, true); break; } } else { bool is_move_up = false; switch(arg.key) { case keyboard::os_arrow_left: case keyboard::os_arrow_up: is_move_up = true; case keyboard::os_arrow_right: case keyboard::os_arrow_down: drawer_->move_items(is_move_up, true); break; } } API::lazy_refresh(); } void trigger::key_char(graph_reference graph, const arg_keyboard& arg) { if (drawer_->editor()->respone_keyboard(arg.key)) API::lazy_refresh(); } //end class trigger //class item_proxy item_proxy::item_proxy(drawer_impl* impl, std::size_t pos) : impl_(impl), pos_(pos) {} item_proxy& item_proxy::text(const nana::string& s) { impl_->at(pos_).item_text = s; return *this; } nana::string item_proxy::text() const { return impl_->at(pos_).item_text; } bool item_proxy::selected() const { return pos_ == impl_->option(); } item_proxy& item_proxy::select() { impl_->option(pos_, false); return *this; } item_proxy& item_proxy::icon(const nana::paint::image& img) { impl_->image(pos_, img); if (pos_ == impl_->option()) API::refresh_window(impl_->widget_ptr()->handle()); return *this; } nana::paint::image item_proxy::icon() const { return impl_->at(pos_).item_image; } /// Behavior of Iterator's value_type bool item_proxy::operator == (const nana::string& s) const { if (pos_ == nana::npos) return false; return (impl_->at(pos_).item_text ==s); } bool item_proxy::operator == (const char * s) const { if (pos_ == nana::npos) return false; return (impl_->at(pos_).item_text == static_cast(nana::charset(s, nana::unicode::utf8))); } bool item_proxy::operator == (const wchar_t * s) const { if (pos_ == nana::npos) return false; return (impl_->at(pos_).item_text == s); } /// Behavior of Iterator item_proxy & item_proxy::operator=(const item_proxy& r) { if (this != &r) { impl_ = r.impl_; pos_ = r.pos_; } return *this; } /// Behavior of Iterator item_proxy & item_proxy::operator++() { if (nana::npos != pos_) { if (++pos_ == impl_->the_number_of_options()) pos_ = nana::npos; } return *this; } /// Behavior of Iterator item_proxy item_proxy::operator++(int) { if (pos_ == nana::npos) return *this; item_proxy tmp = *this; if (++pos_ == impl_->the_number_of_options()) pos_ = nana::npos; return tmp; } /// Behavior of Iterator item_proxy& item_proxy::operator*() { return *this; } /// Behavior of Iterator const item_proxy& item_proxy::operator*() const { return *this; } /// Behavior of Iterator item_proxy* item_proxy::operator->() { return this; } /// Behavior of Iterator const item_proxy* item_proxy::operator->() const { return this; } /// Behavior of Iterator bool item_proxy::operator==(const item_proxy& r) const { return (impl_ == r.impl_ && pos_ == r.pos_); } /// Behavior of Iterator bool item_proxy::operator!=(const item_proxy& r) const { return ! this->operator==(r); } nana::any * item_proxy::_m_anyobj(bool alloc_if_empty) const { return impl_->anyobj(pos_, alloc_if_empty); } //end class item_proxy } }//end namespace drawerbase //class combox combox::combox(){} combox::combox(window wd, bool visible) { create(wd, rectangle(), visible); } combox::combox(window wd, nana::string text, bool visible) { create(wd, rectangle(), visible); caption(std::move(text)); } combox::combox(window wd, const nana::char_t* text, bool visible) { create(wd, rectangle(), visible); caption(text); } combox::combox(window wd, const nana::rectangle& r, bool visible) { create(wd, r, visible); } void combox::clear() { internal_scope_guard lock; _m_impl().clear(); API::refresh_window(handle()); } void combox::editable(bool eb) { internal_scope_guard lock; _m_impl().editable(eb); } bool combox::editable() const { internal_scope_guard lock; return _m_impl().editable(); } void combox::set_accept(std::function pred) { internal_scope_guard lock; auto editor = _m_impl().editor(); if(editor) editor->set_accept(std::move(pred)); } combox& combox::push_back(nana::string text) { internal_scope_guard lock; _m_impl().insert(std::move(text)); return *this; } std::size_t combox::the_number_of_options() const { internal_scope_guard lock; return _m_impl().the_number_of_options(); } std::size_t combox::option() const { internal_scope_guard lock; return _m_impl().option(); } void combox::option(std::size_t pos) { internal_scope_guard lock; _m_impl().option(pos, false); API::update_window(handle()); } nana::string combox::text(std::size_t pos) const { internal_scope_guard lock; return _m_impl().at(pos).item_text; } void combox::erase(std::size_t pos) { internal_scope_guard lock; _m_impl().erase(pos); } void combox::renderer(item_renderer* ir) { internal_scope_guard lock; _m_impl().renderer(ir); } void combox::image(std::size_t i, const nana::paint::image& img) { internal_scope_guard lock; if(empty()) return; auto & impl = _m_impl(); impl.image(i, img); if(i == impl.option()) API::refresh_window(*this); } nana::paint::image combox::image(std::size_t pos) const { internal_scope_guard lock; return _m_impl().at(pos).item_image; } void combox::image_pixels(unsigned px) { internal_scope_guard lock; if (_m_impl().image_pixels(px)) API::refresh_window(*this); } nana::string combox::_m_caption() const { internal_scope_guard lock; auto editor = _m_impl().editor(); return (editor ? editor->text() : nana::string()); } void combox::_m_caption(nana::string&& str) { internal_scope_guard lock; auto editor = _m_impl().editor(); if (editor) editor->text(std::move(str)); API::refresh_window(*this); } nana::any * combox::_m_anyobj(std::size_t pos, bool alloc_if_empty) const { internal_scope_guard lock; return _m_impl().anyobj(pos, alloc_if_empty); } auto combox::_m_at_key(std::shared_ptr&& p) -> item_proxy { internal_scope_guard lock; auto & impl = _m_impl(); return item_proxy(&impl, impl.at_key(std::move(p))); } void combox::_m_erase(nana::detail::key_interface* p) { internal_scope_guard lock; _m_impl().erase(p); } drawerbase::combox::drawer_impl & combox::_m_impl() { return get_drawer_trigger().get_drawer_impl(); } const drawerbase::combox::drawer_impl& combox::_m_impl() const { return get_drawer_trigger().get_drawer_impl(); } //end class combox }