358 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			358 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/*
 | 
						|
 *	A Tooltip 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/tooltip.cpp
 | 
						|
 */
 | 
						|
 | 
						|
#include <nana/gui/tooltip.hpp>
 | 
						|
#include <nana/gui/widgets/label.hpp>
 | 
						|
#include <nana/gui/timer.hpp>
 | 
						|
#include <nana/gui/screen.hpp>
 | 
						|
#include <memory>
 | 
						|
 | 
						|
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 + static_cast<int>(sz.width) > scr_area.right())
 | 
						|
					pos.x = scr_area.right() - static_cast<int>(sz.width);
 | 
						|
				if (pos.x < scr_area.x)
 | 
						|
					pos.x = scr_area.x;
 | 
						|
 | 
						|
				if (pos.y + static_cast<int>(sz.height) >= scr_area.bottom())
 | 
						|
					pos.y = scr_area.bottom() - static_cast<int>(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<category::root_tag, drawer>,
 | 
						|
					public tooltip_interface
 | 
						|
			{
 | 
						|
				typedef widget_object<category::root_tag, drawer> base_type;
 | 
						|
			public:
 | 
						|
				tip_form()
 | 
						|
					:	base_type(nana::rectangle(), appear::bald<appear::floating>()),
 | 
						|
						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<unsigned>(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<window, nana::string> pair_t;
 | 
						|
 | 
						|
				typedef std::function<void(tooltip_interface*)> 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<nana::tooltip::factory_if_type>& factory()
 | 
						|
				{
 | 
						|
					static std::shared_ptr<nana::tooltip::factory_if_type> fp;
 | 
						|
					if (nullptr == fp)
 | 
						|
						fp = std::make_shared<tip_form_factory>();
 | 
						|
 | 
						|
					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<tooltip_interface, deleter_type>(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<tooltip_interface, deleter_type>(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<tooltip_interface, deleter_type> window_;
 | 
						|
				std::vector<pair_t> 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
 |