/* * A Tooltip Implementation * Copyright(C) 2003-2013 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/tooltip.cpp */ #include #include #include #include #include namespace nana { namespace drawerbase { namespace tooltip { class drawer : public drawer_trigger { private: void refresh(graph_reference graph) { graph.rectangle(false, colors::black); graph.rectangle(::nana::rectangle(graph.size()).pare_off(1), true, {0xf0, 0xf0, 0xf0}); } }; nana::point pos_by_screen(nana::point pos, const nana::size& sz, bool overlap_allowed) { auto scr_area = screen().from_point(pos).workarea(); if (pos.x + sz.width > scr_area.x + scr_area.width) pos.x = static_cast(scr_area.x + scr_area.width - sz.width); if (pos.x < scr_area.x) pos.x = scr_area.x; if (pos.y + sz.height >= scr_area.y + scr_area.height) pos.y = static_cast(scr_area.y + scr_area.height - sz.height); else if (!overlap_allowed) pos.y += 20; //Add some pixels to avoid overlapping between cursor and tip window. if (pos.y < scr_area.y) pos.y = scr_area.y; return pos; } class tip_form : public widget_object, public tooltip_interface { typedef widget_object base_type; public: tip_form() : base_type(nana::rectangle(), appear::bald()), duration_(0) { API::take_active(this->handle(), false, nullptr); label_.create(*this); label_.format(true); label_.transparent(true); } private: //tooltip_interface implementation bool tooltip_empty() const override { return this->empty(); } void tooltip_text(const nana::string& text) override { label_.caption(text); auto text_s = label_.measure(screen().from_window(label_).workarea().width * 2 / 3); this->size(nana::size{ text_s.width + 10, text_s.height + 10 }); label_.move(rectangle{ 5, 5, text_s.width, text_s.height }); timer_.reset(); if (duration_) { timer_.interval(static_cast(duration_)); timer_.elapse(std::bind(&tip_form::_m_tick_duration, this)); } else { timer_.interval(500); timer_.elapse(std::bind(&tip_form::_m_tick, this)); } timer_.start(); } virtual nana::size tooltip_size() const override { return this->size(); } virtual void tooltip_move(const nana::point& scr_pos, bool ignore_pos) override { ignore_pos_ = ignore_pos; pos_ = scr_pos; if (duration_) { this->move(scr_pos.x, scr_pos.y); this->show(); } } virtual void duration(std::size_t d) override { duration_ = d; timer_.reset(); } private: void _m_tick() { nana::point pos; if (ignore_pos_) { pos = API::cursor_position(); //The cursor must be stay here for half second. if (pos != pos_) { pos_ = pos; return; } pos = pos_by_screen(pos, size(), false); } else pos = pos_; timer_.stop(); move(pos.x, pos.y); show(); } void _m_tick_duration() { timer_.reset(); this->close(); } private: timer timer_; nana::label label_; nana::point pos_; bool ignore_pos_; std::size_t duration_; };//end class tip_form class controller { typedef std::pair pair_t; typedef std::function deleter_type; class tip_form_factory : public nana::tooltip::factory_if_type { tooltip_interface * create() override { return new tip_form; } void destroy(tooltip_interface* p) override { delete p; } }; public: static std::shared_ptr& factory() { static std::shared_ptr fp; if (nullptr == fp) fp = std::make_shared(); return fp; } //external synchronization. static controller* instance(bool destroy = false) { static controller* ptr; if(destroy) { delete ptr; ptr = nullptr; } else if(nullptr == ptr) { ptr = new controller; } return ptr; } void set(window wd, const nana::string& str) { if (str.empty()) _m_untip(wd); else _m_get(wd).second = str; } void show(const nana::string& text) { if (nullptr == window_ || window_->tooltip_empty()) { auto fp = factory(); window_ = std::unique_ptr(fp->create(), [fp](tooltip_interface* ti) { fp->destroy(ti); }); } window_->duration(0); window_->tooltip_text(text); window_->tooltip_move(API::cursor_position(), true); } void show_duration(window wd, point pos, const nana::string& text, std::size_t duration) { if (nullptr == window_ || window_->tooltip_empty()) { auto fp = factory(); window_ = std::unique_ptr(fp->create(), [fp](tooltip_interface* ti) { fp->destroy(ti); }); } window_->duration(duration); window_->tooltip_text(text); pos = pos_by_screen(pos, window_->tooltip_size(), true); window_->tooltip_move(pos, false); } void close() { window_.reset(); //Destroy the tooltip controller when there are not tooltips. if (cont_.empty()) instance(true); } private: void _m_enter(const arg_mouse& arg) { pair_t & pr = _m_get(arg.window_handle); if(pr.second.size()) { this->show(pr.second); } } void _m_leave(const arg_mouse&) { close(); } void _m_destroy(const arg_destroy& arg) { _m_untip(arg.window_handle); } void _m_untip(window wd) { for (auto i = cont_.begin(); i != cont_.end(); ++i) { if (i->first == wd) { cont_.erase(i); if (cont_.empty()) { window_.reset(); instance(true); } return; } } } private: pair_t& _m_get(window wd) { for (auto & pr : cont_) { if (pr.first == wd) return pr; } auto & events = API::events(wd); events.mouse_enter.connect([this](const arg_mouse& arg){ _m_enter(arg); }); auto leave_fn = std::bind(&controller::_m_leave, this, std::placeholders::_1); events.mouse_leave.connect(leave_fn); events.mouse_down.connect(leave_fn); events.destroy.connect([this](const arg_destroy& arg){ _m_destroy(arg); }); cont_.emplace_back(wd, nana::string()); return cont_.back(); } private: std::unique_ptr window_; std::vector cont_; }; }//namespace tooltip }//namespace drawerbase //class tooltip typedef drawerbase::tooltip::controller ctrl; void tooltip::set(window wd, const nana::string& text) { if(false == API::empty_window(wd)) { internal_scope_guard lock; ctrl::instance()->set(wd, text); } } void tooltip::show(window wd, point pos, const nana::string& text, std::size_t duration) { internal_scope_guard lock; API::calc_screen_point(wd, pos); ctrl::instance()->show_duration(wd, pos, text, duration); } void tooltip::close() { internal_scope_guard lock; ctrl::instance()->close(); } void tooltip::_m_hold_factory(factory_interface* p) { ctrl::factory().reset(p); } //end class tooltip }//namespace nana