 3c4f8ae6d9
			
		
	
	
		3c4f8ae6d9
		
	
	
	
	
		
			
			no key_press and key_release after destroying caret under X11 missing some key_press events menu text color issue
		
			
				
	
	
		
			877 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			877 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
|  *	A Label Control Implementation
 | |
|  *	Nana C++ Library(http://www.nanapro.org)
 | |
|  *	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: source/gui/widgets/label.cpp
 | |
|  *	@author: Jinhao
 | |
|  *	@contributors: Ariel Vina-Rodriguez
 | |
|  */
 | |
| 
 | |
| #include <nana/gui/widgets/label.hpp>
 | |
| #include <nana/unicode_bidi.hpp>
 | |
| #include <nana/gui/widgets/skeletons/text_token_stream.hpp>
 | |
| #include <nana/system/platform.hpp>
 | |
| #include <stdexcept>
 | |
| #include <sstream>
 | |
| 
 | |
| namespace nana
 | |
| {
 | |
| 	namespace drawerbase
 | |
| 	{
 | |
| 		namespace label
 | |
| 		{
 | |
| 			class renderer
 | |
| 			{
 | |
| 				typedef widgets::skeletons::dstream::linecontainer::iterator iterator;
 | |
| 
 | |
| 				struct pixel_tag
 | |
| 				{
 | |
| 					int x_base;				//The x position where this line starts.
 | |
| 					std::size_t pixels;
 | |
| 					std::size_t baseline;	//The baseline for drawing text.
 | |
| 					std::vector<iterator> values;	//line values
 | |
| 				};
 | |
| 
 | |
| 				//this is a helper variable, it just keeps the status while drawing.
 | |
| 				struct render_status
 | |
| 				{
 | |
| 					unsigned allowed_width;
 | |
| 					align text_align;
 | |
| 					align_v text_align_v;
 | |
| 
 | |
| 					nana::point pos;
 | |
| 					std::vector<pixel_tag> pixels;
 | |
| 					std::size_t index;
 | |
| 				};
 | |
| 
 | |
| 				struct traceable
 | |
| 				{
 | |
| 					nana::rectangle r;
 | |
| 					nana::string target;
 | |
| 					nana::string url;
 | |
| 				};
 | |
| 
 | |
| 			public:
 | |
| 				typedef nana::paint::graphics& graph_reference;
 | |
| 				typedef widgets::skeletons::dstream dstream;
 | |
| 				typedef widgets::skeletons::fblock fblock;
 | |
| 				typedef widgets::skeletons::data data;
 | |
| 
 | |
| 				void parse(const nana::string& s)
 | |
| 				{
 | |
| 					dstream_.parse(s, format_enabled_);
 | |
| 				}
 | |
| 
 | |
| 				bool format(bool fm)
 | |
| 				{
 | |
| 					if (fm == format_enabled_)
 | |
| 						return false;
 | |
| 					
 | |
| 					format_enabled_ = fm;
 | |
| 					return true;
 | |
| 				}
 | |
| 
 | |
| 				void render(graph_reference graph, const ::nana::color& fgcolor, align th, align_v tv)
 | |
| 				{
 | |
| 					traceable_.clear();
 | |
| 
 | |
| 					nana::paint::font ft = graph.typeface();	//used for restoring the font
 | |
| 
 | |
| 					const unsigned def_line_pixels = graph.text_extent_size(STR(" "), 1).height;
 | |
| 
 | |
| 					font_ = ft;
 | |
| 					fblock_ = nullptr;
 | |
| 
 | |
| 					_m_set_default(ft, fgcolor);
 | |
| 
 | |
| 					_m_measure(graph);
 | |
| 
 | |
| 					render_status rs;
 | |
| 
 | |
| 					rs.allowed_width = graph.size().width;
 | |
| 					rs.text_align = th;
 | |
| 					rs.text_align_v = tv;
 | |
| 
 | |
| 					std::deque<std::vector<pixel_tag> > pixel_lines;
 | |
| 
 | |
| 					std::size_t extent_v_pixels = 0;	//the pixels, in height, that text will be painted.
 | |
| 
 | |
| 					for (auto & line : dstream_)
 | |
| 					{
 | |
| 						_m_line_pixels(line, def_line_pixels, rs);
 | |
| 
 | |
| 						for (auto & m : rs.pixels)
 | |
| 							extent_v_pixels += m.pixels;
 | |
| 
 | |
| 						pixel_lines.emplace_back(std::move(rs.pixels));
 | |
| 
 | |
| 						if(extent_v_pixels >= graph.height())
 | |
| 							break;
 | |
| 					}
 | |
| 
 | |
| 					if((tv != align_v::top) && extent_v_pixels < graph.height())
 | |
| 					{
 | |
| 						if(align_v::center == tv)
 | |
| 							rs.pos.y = static_cast<int>(graph.height() - extent_v_pixels) >> 1;
 | |
| 						else if(align_v::bottom == tv)
 | |
| 							rs.pos.y = static_cast<int>(graph.height() - extent_v_pixels);
 | |
| 					}
 | |
| 					else
 | |
| 						rs.pos.y = 0;
 | |
| 
 | |
| 					auto pixels_iterator = pixel_lines.begin();
 | |
| 
 | |
| 					for (auto & line : dstream_)
 | |
| 					{
 | |
| 						if (rs.pos.y >= static_cast<int>(graph.height()))
 | |
| 							break;
 | |
| 
 | |
| 						rs.index = 0;
 | |
| 						rs.pixels.clear();
 | |
| 
 | |
| 						rs.pixels.swap(*pixels_iterator++);
 | |
| 
 | |
| 						rs.pos.x = rs.pixels.front().x_base;
 | |
| 
 | |
| 						//Stop drawing when it goes out of range.
 | |
| 						if(false == _m_each_line(graph, line, rs))
 | |
| 							break;
 | |
| 
 | |
| 						rs.pos.y += static_cast<int>(rs.pixels.back().pixels);
 | |
| 					}
 | |
| 
 | |
| 					graph.typeface(ft);
 | |
| 				}
 | |
| 
 | |
| 				bool find(int x, int y, nana::string& target, nana::string& url) const
 | |
| 				{
 | |
| 					for (auto & t : traceable_)
 | |
| 					{
 | |
| 						if(t.r.is_hit(x, y))
 | |
| 						{
 | |
| 							target = t.target;
 | |
| 							url = t.url;
 | |
| 							return true;
 | |
| 						}
 | |
| 					}
 | |
| 
 | |
| 					return false;
 | |
| 				}
 | |
| 
 | |
| 				::nana::size measure(graph_reference graph, unsigned limited, align th, align_v tv)
 | |
| 				{
 | |
| 					::nana::size retsize;
 | |
| 
 | |
| 					auto ft = graph.typeface();	//used for restoring the font
 | |
| 
 | |
| 					const unsigned def_line_pixels = graph.text_extent_size(STR(" "), 1).height;
 | |
| 
 | |
| 					font_ = ft;
 | |
| 					fblock_ = nullptr;
 | |
| 
 | |
| 					_m_set_default(ft, colors::black);
 | |
| 					_m_measure(graph);
 | |
| 
 | |
| 					render_status rs;
 | |
| 
 | |
| 					rs.allowed_width = limited;
 | |
| 					rs.text_align = th;
 | |
| 					rs.text_align_v = tv;
 | |
| 
 | |
| 					for(auto i = dstream_.begin(), end = dstream_.end(); i != end; ++i)
 | |
| 					{
 | |
| 						rs.pixels.clear();
 | |
| 						unsigned w = _m_line_pixels(*i, def_line_pixels, rs);
 | |
| 
 | |
| 						if(limited && (w > limited))
 | |
| 							w = limited;
 | |
| 
 | |
| 						if(retsize.width < w)
 | |
| 							retsize.width = w;
 | |
| 
 | |
| 						for (auto & px : rs.pixels)
 | |
| 							retsize.height += static_cast<unsigned>(px.pixels);
 | |
| 					}
 | |
| 
 | |
| 					return retsize;
 | |
| 				}
 | |
| 			private:
 | |
| 				//Manage the fblock for a specified rectangle if it is a traceable fblock.
 | |
| 				void _m_inser_if_traceable(int x, int y, const nana::size& sz, widgets::skeletons::fblock* fbp)
 | |
| 				{
 | |
| 					if(fbp->target.size() || fbp->url.size())
 | |
| 					{
 | |
| 						traceable tr;
 | |
| 						tr.r.x = x;
 | |
| 						tr.r.y = y;
 | |
| 						tr.r.width = sz.width;
 | |
| 						tr.r.height = sz.height;
 | |
| 						tr.target = fbp->target;
 | |
| 						tr.url = fbp->url;
 | |
| 
 | |
| 						traceable_.push_back(tr);
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				void _m_set_default(const ::nana::paint::font& ft, const ::nana::color& fgcolor)
 | |
| 				{
 | |
| 					def_.font_name = ft.name();
 | |
| 					def_.font_size = ft.size();
 | |
| 					def_.font_bold = ft.bold();
 | |
| 					def_.fgcolor = fgcolor;
 | |
| 				}
 | |
| 
 | |
| 				const ::nana::color& _m_fgcolor(nana::widgets::skeletons::fblock* fp)
 | |
| 				{
 | |
| 					while(fp->fgcolor.invisible())
 | |
| 					{
 | |
| 						fp = fp->parent;
 | |
| 						if(nullptr == fp)
 | |
| 							return def_.fgcolor;
 | |
| 					}
 | |
| 					return fp->fgcolor;
 | |
| 				}
 | |
| 
 | |
| 				std::size_t _m_font_size(nana::widgets::skeletons::fblock* fp)
 | |
| 				{
 | |
| 					while(fp->font_size == 0xFFFFFFFF)
 | |
| 					{
 | |
| 						fp = fp->parent;
 | |
| 						if(nullptr == fp)
 | |
| 							return def_.font_size;
 | |
| 					}
 | |
| 					return fp->font_size;
 | |
| 				}
 | |
| 
 | |
| 				bool _m_bold(nana::widgets::skeletons::fblock* fp)
 | |
| 				{
 | |
| 					while(fp->bold_empty)
 | |
| 					{
 | |
| 						fp = fp->parent;
 | |
| 						if(nullptr == fp)
 | |
| 							return def_.font_bold;
 | |
| 					}
 | |
| 					return fp->bold;
 | |
| 				}
 | |
| 
 | |
| 				const nana::string& _m_fontname(nana::widgets::skeletons::fblock* fp)
 | |
| 				{
 | |
| 					while(fp->font.empty())
 | |
| 					{
 | |
| 						fp = fp->parent;
 | |
| 						if(nullptr == fp)
 | |
| 							return def_.font_name;
 | |
| 					}
 | |
| 					return fp->font;
 | |
| 				}
 | |
| 
 | |
| 				void _m_change_font(graph_reference graph, nana::widgets::skeletons::fblock* fp)
 | |
| 				{
 | |
| 					if(fp != fblock_)
 | |
| 					{
 | |
| 						const nana::string& name = _m_fontname(fp);
 | |
| 						auto fontsize = static_cast<unsigned>(_m_font_size(fp));
 | |
| 						bool bold = _m_bold(fp);
 | |
| 
 | |
| 						if((fontsize != font_.size()) || bold != font_.bold() || name != font_.name())
 | |
| 						{
 | |
| 							font_.make(name.data(), fontsize, bold);
 | |
| 							graph.typeface(font_);
 | |
| 						}
 | |
| 						fblock_ = fp;
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				void _m_measure(graph_reference graph)
 | |
| 				{
 | |
| 					nana::paint::font ft = font_;
 | |
| 					for (auto & line : dstream_)
 | |
| 					{
 | |
| 						for (auto & value : line)
 | |
| 						{
 | |
| 							_m_change_font(graph, value.fblock_ptr);
 | |
| 							value.data_ptr->measure(graph);
 | |
| 						}
 | |
| 					}
 | |
| 					if(font_ != ft)
 | |
| 					{
 | |
| 						font_ = ft;
 | |
| 						graph.typeface(ft);
 | |
| 						fblock_ = nullptr;
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				void _m_align_x_base(const render_status& rs, pixel_tag & px, unsigned w)
 | |
| 				{
 | |
| 					switch(rs.text_align)
 | |
| 					{
 | |
| 					case align::left:
 | |
| 						px.x_base = 0;
 | |
| 						break;
 | |
| 					case align::center:
 | |
| 						px.x_base = (static_cast<int>(rs.allowed_width - w) >> 1);
 | |
| 						break;
 | |
| 					case align::right:
 | |
| 						px.x_base = static_cast<int>(rs.allowed_width - w);
 | |
| 						break;
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				unsigned _m_line_pixels(dstream::linecontainer& line, unsigned def_line_pixels, render_status & rs)
 | |
| 				{
 | |
| 					if (line.empty())
 | |
| 					{
 | |
| 						pixel_tag px;
 | |
| 						px.baseline = 0;
 | |
| 						px.pixels = def_line_pixels;
 | |
| 						px.x_base = 0;
 | |
| 
 | |
| 						rs.pixels.push_back(px);
 | |
| 
 | |
| 						return 0;
 | |
| 					}
 | |
| 
 | |
| 					unsigned total_w = 0;
 | |
| 					unsigned w = 0;
 | |
| 					unsigned max_ascent = 0;
 | |
| 					unsigned max_descent = 0;
 | |
| 					unsigned max_px = 0;
 | |
| 
 | |
| 					//Bidi reorder is requried here
 | |
| 
 | |
| 					std::vector<iterator> line_values;
 | |
| 
 | |
| 					for(auto i = line.begin(), end = line.end(); i != end; ++i)
 | |
| 					{
 | |
| 						data * data_ptr = i->data_ptr;
 | |
| 						nana::size sz = data_ptr->size();
 | |
| 						total_w += sz.width;
 | |
| 
 | |
| 						unsigned as = 0;	//ascent
 | |
| 						unsigned ds = 0;	//descent
 | |
| 
 | |
| 						if(fblock::aligns::baseline == i->fblock_ptr->text_align)
 | |
| 						{
 | |
| 							as = static_cast<unsigned>(data_ptr->ascent());
 | |
| 							ds = static_cast<unsigned>(sz.height - as);
 | |
| 
 | |
| 							if(max_descent < ds)
 | |
| 								max_descent = ds;
 | |
| 
 | |
| 							if((false == data_ptr->is_text()) && (sz.height < max_ascent + max_descent))
 | |
| 								sz.height = max_ascent + max_descent;
 | |
| 						}
 | |
| 
 | |
| 						if(w + sz.width <= rs.allowed_width)
 | |
| 						{
 | |
| 							w += sz.width;
 | |
| 
 | |
| 							if(max_ascent < as)		max_ascent = as;
 | |
| 							if(max_descent < ds)	max_descent = ds;
 | |
| 							if(max_px < sz.height)	max_px = sz.height;
 | |
| 							line_values.push_back(i);
 | |
| 						}
 | |
| 						else
 | |
| 						{
 | |
| 							if(w)
 | |
| 							{
 | |
| 								pixel_tag px;
 | |
| 
 | |
| 								_m_align_x_base(rs, px, w);
 | |
| 
 | |
| 								if(max_ascent + max_descent > max_px)
 | |
| 									max_px = max_descent + max_ascent;
 | |
| 								else
 | |
| 									max_ascent = max_px - max_descent;
 | |
| 
 | |
| 								px.pixels = max_px;
 | |
| 								px.baseline = max_ascent;
 | |
| 								px.values.swap(line_values);
 | |
| 
 | |
| 								rs.pixels.push_back(px);
 | |
| 
 | |
| 								w = sz.width;
 | |
| 								max_px = sz.height;
 | |
| 								max_ascent = as;
 | |
| 								max_descent = ds;
 | |
| 								line_values.push_back(i);
 | |
| 							}
 | |
| 							else
 | |
| 							{
 | |
| 								pixel_tag px;
 | |
| 
 | |
| 								_m_align_x_base(rs, px, sz.width);
 | |
| 								px.pixels = sz.height;
 | |
| 								px.baseline = as;
 | |
| 
 | |
| 								px.values.push_back(i);
 | |
| 
 | |
| 								rs.pixels.push_back(px);
 | |
| 								max_px = 0;
 | |
| 								max_ascent = max_descent = 0;
 | |
| 							}
 | |
| 						}
 | |
| 					}
 | |
| 
 | |
| 					if (max_px)
 | |
| 					{
 | |
| 						pixel_tag px;
 | |
| 
 | |
| 						_m_align_x_base(rs, px, w);
 | |
| 
 | |
| 						if (max_ascent + max_descent > max_px)
 | |
| 							max_px = max_descent + max_ascent;
 | |
| 						else
 | |
| 							max_ascent = max_px - max_descent;
 | |
| 
 | |
| 						px.pixels = max_px;
 | |
| 						px.baseline = max_ascent;
 | |
| 						px.values.swap(line_values);
 | |
| 						rs.pixels.push_back(px);
 | |
| 					}
 | |
| 					return total_w;
 | |
| 				}
 | |
| 
 | |
| 				bool _m_each_line(graph_reference graph, dstream::linecontainer& line, render_status& rs)
 | |
| 				{
 | |
| 					nana::string text;
 | |
| 					iterator block_start;
 | |
| 
 | |
| 					const int lastpos = static_cast<int>(graph.height()) - 1;
 | |
| 
 | |
| 					for(auto i = rs.pixels.begin(), end = rs.pixels.end(); i != end; ++i)
 | |
| 					{
 | |
| 						for (auto & render_iterator : i->values)
 | |
| 						{
 | |
| 							auto & value = *render_iterator;
 | |
| 							if(false == value.data_ptr->is_text())
 | |
| 							{
 | |
| 								if(text.size())
 | |
| 								{
 | |
| 									_m_draw_block(graph, text, block_start, rs);
 | |
| 									if(lastpos <= rs.pos.y)
 | |
| 										return false;
 | |
| 									text.clear();
 | |
| 								}
 | |
| 								nana::size sz = value.data_ptr->size();
 | |
| 
 | |
| 								pixel_tag px = rs.pixels[rs.index];
 | |
| 								if ((rs.allowed_width < rs.pos.x + sz.width) && (rs.pos.x != px.x_base))
 | |
| 								{
 | |
| 									//Change a line.
 | |
| 									rs.pos.y += static_cast<int>(px.pixels);
 | |
| 									px = rs.pixels[++rs.index];
 | |
| 									rs.pos.x = px.x_base;
 | |
| 								}
 | |
| 
 | |
| 								int y = rs.pos.y + _m_text_top(px, value.fblock_ptr, value.data_ptr);
 | |
| 
 | |
| 								value.data_ptr->nontext_render(graph, rs.pos.x, y);
 | |
| 								_m_inser_if_traceable(rs.pos.x, y, sz, value.fblock_ptr);
 | |
| 								rs.pos.x += static_cast<int>(sz.width);
 | |
| 
 | |
| 								if(lastpos < y)
 | |
| 									return false;
 | |
| 							}
 | |
| 							else
 | |
| 							{
 | |
| 								//hold the block while the text is empty,
 | |
| 								//it stands for the first block
 | |
| 								if(text.empty())
 | |
| 									block_start = render_iterator;
 | |
| 
 | |
| 								text += value.data_ptr->text();
 | |
| 							}
 | |
| 						}
 | |
| 
 | |
| 						if(text.size())
 | |
| 						{
 | |
| 							_m_draw_block(graph, text, block_start, rs);
 | |
| 							text.clear();
 | |
| 						}
 | |
| 					}
 | |
| 					return (rs.pos.y <= lastpos);
 | |
| 				}
 | |
| 
 | |
| 				static bool _m_overline(const render_status& rs, int right, bool equal_required)
 | |
| 				{
 | |
| 					if(align::left == rs.text_align)
 | |
| 						return (equal_required ? right >= static_cast<int>(rs.allowed_width) : right > static_cast<int>(rs.allowed_width));
 | |
| 
 | |
| 					return (equal_required ? rs.pixels[rs.index].x_base <= 0 : rs.pixels[rs.index].x_base < 0);
 | |
| 				}
 | |
| 
 | |
| 				static int _m_text_top(const pixel_tag& px, fblock* fblock_ptr, const data* data_ptr)
 | |
| 				{
 | |
| 					switch(fblock_ptr->text_align)
 | |
| 					{
 | |
| 					case fblock::aligns::center:
 | |
| 						return static_cast<int>(px.pixels - data_ptr->size().height) / 2;
 | |
| 					case fblock::aligns::bottom:
 | |
| 						return static_cast<int>(px.pixels - data_ptr->size().height);
 | |
| 					case fblock::aligns::baseline:
 | |
| 						return static_cast<int>(px.baseline - (data_ptr->is_text() ? data_ptr->ascent() : data_ptr->size().height));
 | |
| 					default:	break;
 | |
| 					}
 | |
| 					return 0;
 | |
| 				}
 | |
| 
 | |
| 				void _m_draw_block(graph_reference graph, const nana::string& s, dstream::linecontainer::iterator block_start, render_status& rs)
 | |
| 				{
 | |
| 					nana::unicode_bidi bidi;
 | |
| 					std::vector<nana::unicode_bidi::entity> reordered;
 | |
| 					bidi.linestr(s.data(), s.length(), reordered);
 | |
| 
 | |
| 					pixel_tag px = rs.pixels[rs.index];
 | |
| 
 | |
| 					for(auto & bidi : reordered)
 | |
| 					{
 | |
| 						std::size_t pos = bidi.begin - s.data();
 | |
| 						std::size_t len = bidi.end - bidi.begin;
 | |
| 
 | |
| 						while (true)
 | |
| 						{
 | |
| 							auto i = block_start;
 | |
| 
 | |
| 							//Text range indicates the position of text where begin to output
 | |
| 							//The output length is the min between len and the second of text range.
 | |
| 							auto text_range = _m_locate(i, pos);
 | |
| 
 | |
| 							if (text_range.second > len)
 | |
| 								text_range.second = len;
 | |
| 
 | |
| 							fblock * fblock_ptr = i->fblock_ptr;
 | |
| 							data * data_ptr = i->data_ptr;
 | |
| 
 | |
| 							const int w = static_cast<int>(rs.allowed_width) - rs.pos.x;
 | |
| 							nana::size sz = data_ptr->size();
 | |
| 							if ((static_cast<int>(sz.width) > w) && (rs.pos.x != px.x_base))
 | |
| 							{
 | |
| 								//Change a new line
 | |
| 								rs.pos.y += static_cast<int>(px.pixels);
 | |
| 								px = rs.pixels[++rs.index];
 | |
| 								rs.pos.x = px.x_base;
 | |
| 							}
 | |
| 
 | |
| 							const int y = rs.pos.y + _m_text_top(px, fblock_ptr, data_ptr);
 | |
| 
 | |
| 							_m_change_font(graph, fblock_ptr);
 | |
| 
 | |
| 							if (text_range.second == data_ptr->text().length())
 | |
| 							{
 | |
| 								graph.string({ rs.pos.x, y }, data_ptr->text(), _m_fgcolor(fblock_ptr));
 | |
| 							}
 | |
| 							else
 | |
| 							{
 | |
| 								nana::string str = data_ptr->text().substr(text_range.first, text_range.second);
 | |
| 								graph.string({ rs.pos.x, y }, str, _m_fgcolor(fblock_ptr));
 | |
| 								sz = graph.text_extent_size(str);
 | |
| 							}
 | |
| 							_m_inser_if_traceable(rs.pos.x, y, sz, fblock_ptr);
 | |
| 							rs.pos.x += static_cast<int>(sz.width);
 | |
| 
 | |
| 							if(text_range.second < len)
 | |
| 							{
 | |
| 								len -= text_range.second;
 | |
| 								pos += text_range.second;
 | |
| 							}
 | |
| 							else
 | |
| 								break;
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				std::pair<std::size_t, std::size_t> _m_locate(dstream::linecontainer::iterator& i, std::size_t pos)
 | |
| 				{
 | |
| 					std::size_t n = i->data_ptr->text().length();
 | |
| 					while(pos >= n)
 | |
| 					{
 | |
| 						pos -= n;
 | |
| 						n = (++i)->data_ptr->text().length();
 | |
| 					}
 | |
| 
 | |
| 					return{ pos, n - pos };
 | |
| 				}
 | |
| 			private:
 | |
| 				dstream dstream_;
 | |
| 				bool format_enabled_ = false;
 | |
| 				::nana::widgets::skeletons::fblock * fblock_ = nullptr;
 | |
| 				std::deque<traceable> traceable_;
 | |
| 
 | |
| 				::nana::paint::font font_;
 | |
| 				struct def_font_tag
 | |
| 				{
 | |
| 					::nana::string font_name;
 | |
| 					std::size_t font_size;
 | |
| 					bool	font_bold;
 | |
| 					::nana::color fgcolor;
 | |
| 				}def_;
 | |
| 			};
 | |
| 
 | |
| 			//class trigger
 | |
| 			//@brief: Draw the label
 | |
| 				struct trigger::impl_t
 | |
| 				{
 | |
| 					widget * wd{nullptr};
 | |
| 					paint::graphics * graph{nullptr};
 | |
| 
 | |
| 					align	text_align{align::left};
 | |
| 					align_v	text_align_v;
 | |
| 
 | |
| 					class renderer renderer;
 | |
| 
 | |
| 					nana::string target;	//It indicates which target is tracing.
 | |
| 					nana::string url;
 | |
| 
 | |
| 					void add_listener(std::function<void(command, const nana::string&)>&& fn)
 | |
| 					{
 | |
| 						listener_.emplace_back(std::move(fn));
 | |
| 					}
 | |
| 
 | |
| 					void call_listener(command cmd, const nana::string& tar)
 | |
| 					{
 | |
| 						for (auto & fn : listener_)
 | |
| 							fn(cmd, tar);
 | |
| 					}
 | |
| 				private:
 | |
| 					std::vector<std::function<void(command, const nana::string&)>> listener_;
 | |
| 				};
 | |
| 
 | |
| 				trigger::trigger()
 | |
| 					:impl_(new impl_t)
 | |
| 				{}
 | |
| 
 | |
| 				trigger::~trigger()
 | |
| 				{
 | |
| 					delete impl_;
 | |
| 				}
 | |
| 
 | |
| 				trigger::impl_t * trigger::impl() const
 | |
| 				{
 | |
| 					return impl_;
 | |
| 				}
 | |
| 
 | |
| 				void trigger::attached(widget_reference widget, graph_reference graph)
 | |
| 				{
 | |
| 					impl_->graph = &graph;
 | |
| 					impl_->wd = &widget;
 | |
| 				}
 | |
| 
 | |
| 				void trigger::mouse_move(graph_reference, const arg_mouse& arg)
 | |
| 				{
 | |
| 					nana::string target, url;
 | |
| 
 | |
| 					if(impl_->renderer.find(arg.pos.x, arg.pos.y, target, url))
 | |
| 					{
 | |
| 						int cur_state = 0;
 | |
| 						if(target != impl_->target)
 | |
| 						{
 | |
| 							if(impl_->target.size())
 | |
| 							{
 | |
| 								impl_->call_listener(command::leave, impl_->target);
 | |
| 								cur_state = 1;	//Set arrow
 | |
| 							}
 | |
| 
 | |
| 							impl_->target = target;
 | |
| 
 | |
| 							if(target.size())
 | |
| 							{
 | |
| 								impl_->call_listener(command::enter, impl_->target);
 | |
| 								cur_state = 2;	//Set hand
 | |
| 							}
 | |
| 						}
 | |
| 						if (url != impl_->url)
 | |
| 						{
 | |
| 							if (impl_->url.size())
 | |
| 								cur_state = 1;	//Set arrow
 | |
| 
 | |
| 							impl_->url = url;
 | |
| 
 | |
| 							if (url.size())
 | |
| 								cur_state = 2;	//Set hand
 | |
| 						}
 | |
| 
 | |
| 						if (cur_state)
 | |
| 							impl_->wd->cursor(1 == cur_state ? cursor::arrow : cursor::hand);
 | |
| 					}
 | |
| 					else
 | |
| 					{
 | |
| 						bool restore = false;
 | |
| 						if (impl_->target.size())
 | |
| 						{
 | |
| 							impl_->call_listener(command::leave, impl_->target);
 | |
| 							impl_->target.clear();
 | |
| 							restore = true;
 | |
| 						}
 | |
| 
 | |
| 						if (impl_->url.size())
 | |
| 						{
 | |
| 							impl_->url.clear();
 | |
| 							restore = true;
 | |
| 						}
 | |
| 
 | |
| 						if(restore)
 | |
| 							impl_->wd->cursor(cursor::arrow);
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				void trigger::mouse_leave(graph_reference, const arg_mouse&)
 | |
| 				{
 | |
| 					if(impl_->target.size())
 | |
| 					{
 | |
| 						impl_->call_listener(command::leave, impl_->target);
 | |
| 						impl_->target.clear();
 | |
| 						impl_->wd->cursor(cursor::arrow);
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				void trigger::click(graph_reference, const arg_mouse&)
 | |
| 				{
 | |
| 					//make a copy, because the listener may popup a window, and then
 | |
| 					//user moves the mouse. it will reset the url when the mouse is moving out from the element.
 | |
| 					auto url = impl_->url;
 | |
| 
 | |
| 					if(impl_->target.size())
 | |
| 						impl_->call_listener(command::click, impl_->target);
 | |
| 
 | |
| 					system::open_url(url);
 | |
| 				}
 | |
| 
 | |
| 				void trigger::refresh(graph_reference graph)
 | |
| 				{
 | |
| 					if(nullptr == impl_->wd) return;
 | |
| 
 | |
| 					window wd = impl_->wd->handle();
 | |
| 					if(bground_mode::basic != API::effects_bground_mode(wd))
 | |
| 						graph.rectangle(true, API::bgcolor(wd));
 | |
| 
 | |
| 					impl_->renderer.render(graph, API::fgcolor(wd), impl_->text_align, impl_->text_align_v);
 | |
| 				}
 | |
| 
 | |
| 			//end class label_drawer
 | |
| 		}//end namespace label
 | |
| 	}//end namespace drawerbase
 | |
| 
 | |
| 
 | |
| 	//
 | |
| 	//class label
 | |
| 		label::label(){}
 | |
| 
 | |
| 		label::label(window wd, bool visible)
 | |
| 		{
 | |
| 			create(wd, rectangle(), visible);
 | |
|             bgcolor(API::bgcolor(wd));
 | |
| 		}
 | |
| 
 | |
| 		label::label(window wd, const nana::string& text, bool visible)
 | |
| 		{
 | |
| 			create(wd, rectangle(), visible);
 | |
| 			bgcolor(API::bgcolor(wd));
 | |
| 			caption(text);
 | |
| 		}
 | |
| 
 | |
| 		label::label(window wd, const nana::char_t* text, bool visible)
 | |
| 		{
 | |
| 			create(wd, rectangle(), visible);
 | |
| 			bgcolor(API::bgcolor(wd));
 | |
| 			caption(text);
 | |
| 		}
 | |
| 
 | |
| 		label::label(window wd, const rectangle& r, bool visible)
 | |
| 		{
 | |
| 			create(wd, r, visible);
 | |
| 			bgcolor(API::bgcolor(wd));
 | |
| 		}
 | |
| 
 | |
| 		label& label::transparent(bool enabled)
 | |
| 		{
 | |
| 			if(enabled)
 | |
| 				API::effects_bground(*this, effects::bground_transparent(0), 0.0);
 | |
| 			else
 | |
| 				API::effects_bground_remove(*this);
 | |
| 			return *this;
 | |
| 		}
 | |
| 
 | |
| 		bool label::transparent() const
 | |
| 		{
 | |
| 			return (bground_mode::basic == API::effects_bground_mode(*this));
 | |
| 		}
 | |
| 
 | |
| 		label& label::format(bool f)
 | |
| 		{
 | |
| 			auto impl = get_drawer_trigger().impl();
 | |
| 
 | |
| 			if(impl->renderer.format(f))
 | |
| 			{
 | |
| 				window wd = *this;
 | |
| 				impl->renderer.parse(API::dev::window_caption(wd));
 | |
| 				API::refresh_window(wd);
 | |
| 			}
 | |
| 			return *this;
 | |
| 		}
 | |
| 
 | |
| 		label& label::add_format_listener(std::function<void(command, const nana::string&)> f)
 | |
| 		{
 | |
| 			get_drawer_trigger().impl()->add_listener(std::move(f));
 | |
| 			return *this;
 | |
| 		}
 | |
| 
 | |
| 		nana::size label::measure(unsigned limited) const
 | |
| 		{
 | |
| 			if(empty())
 | |
| 				return nana::size();
 | |
| 
 | |
| 			auto impl = get_drawer_trigger().impl();
 | |
| 			
 | |
| 			//First Check the graph of label
 | |
| 			//Then take a substitute for graph when the graph of label is zero-sized.
 | |
| 			nana::paint::graphics * graph_ptr = impl->graph;
 | |
| 			nana::paint::graphics substitute;
 | |
| 			if(graph_ptr->empty())
 | |
| 			{
 | |
| 				graph_ptr = &substitute;
 | |
| 				graph_ptr->make({ 10, 10 });
 | |
| 			}
 | |
| 
 | |
| 			return impl->renderer.measure(*graph_ptr, limited, impl->text_align, impl->text_align_v);
 | |
| 		}
 | |
| 
 | |
| 		::nana::size label::measure(paint::graphics& graph, const ::nana::string& str, unsigned allowed_width_in_pixel, bool format_enabled, align h_align, align_v v_align)
 | |
| 		{
 | |
| 			drawerbase::label::renderer rd;
 | |
| 			rd.format(format_enabled);
 | |
| 			rd.parse(str);
 | |
| 			return rd.measure(graph, allowed_width_in_pixel, h_align, v_align);
 | |
| 		}
 | |
| 
 | |
| 		label& label::text_align(align th, align_v tv)
 | |
| 		{
 | |
| 			internal_scope_guard lock;
 | |
| 			auto impl = get_drawer_trigger().impl();
 | |
| 
 | |
| 			if (th != impl->text_align || tv != impl->text_align_v)
 | |
| 			{
 | |
| 				impl->text_align = th;
 | |
| 				impl->text_align_v = tv;
 | |
| 				API::refresh_window(*this);
 | |
| 			}
 | |
| 
 | |
| 			return *this;
 | |
| 		}
 | |
| 
 | |
| 		void label::_m_caption(nana::string&& str)
 | |
| 		{
 | |
| 			internal_scope_guard lock;
 | |
| 			window wd = *this;
 | |
| 			get_drawer_trigger().impl()->renderer.parse(str);
 | |
| 			API::dev::window_caption(wd, std::move(str));
 | |
| 			API::refresh_window(wd);
 | |
| 		}
 | |
| 	//end class label
 | |
| }//end namespace nana
 |