1 /* Label.cpp
2  * Copyright (C) 2018, 2019  Sven Jähnichen
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
16  */
17 
18 #include "Label.hpp"
19 #include "Window.hpp"
20 #include <locale>
21 #include <codecvt>
22 
23 namespace BWidgets
24 {
Label()25 Label::Label () : Label (0.0, 0.0, 0.0, 0.0, "label", "") {}
26 
Label(const double x,const double y,const double width,const double height,const std::string & text)27 Label::Label (const double x, const double y, const double width, const double height, const std::string& text) :
28 		Label (x, y, width, height, text, text) {}
29 
Label(const double x,const double y,const double width,const double height,const std::string & name,const std::string & text)30 Label::Label (const double x, const double y, const double width, const double height, const std::string& name, const std::string& text) :
31 		Widget (x, y, width, height, name),
32 		labelColors (BWIDGETS_DEFAULT_TEXT_COLORS),
33 		labelFont (BWIDGETS_DEFAULT_FONT),
34 		labelText (text),
35 		oldText (text),
36 		u32labelText (),
37 		editable (false),
38 		editMode (false),
39 		cursorFrom (0),
40 		cursorTo ()
41 {
42 	cbfunction_[BEvents::EventType::POINTER_DRAG_EVENT] = Widget::defaultCallback;
43 	setDraggable (true);
44 
45 	labelFont.setTextAlign (BWIDGETS_DEFAULT_LABEL_ALIGN);
46 	labelFont.setTextVAlign (BWIDGETS_DEFAULT_LABEL_VALIGN);
47 
48 	std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> convert;
49 	u32labelText = convert.from_bytes (labelText);
50 }
51 
operator =(const Label & that)52 Label& Label::operator= (const Label& that)
53 {
54 	labelColors = that.labelColors;
55 	labelFont = that.labelFont;
56 	labelText = that.labelText;
57 	u32labelText = that.u32labelText;
58 	editable = that.editable;
59 	editMode = that.editMode;
60 	cursorFrom = that.cursorFrom;
61 	cursorTo = that.cursorTo;
62 	Widget::operator= (that);
63 
64 	oldText = labelText;
65 
66 	return *this;
67 }
68 
clone() const69 Widget* Label::clone () const {return new Label (*this);}
70 
setText(const std::string & text)71 void Label::setText (const std::string& text)
72 {
73 	if (text != labelText)
74 	{
75 		setEditMode (false);
76 		labelText = text;
77 		std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> convert;
78 		u32labelText = convert.from_bytes (labelText);
79 		size_t sz = u32labelText.length ();
80 		if (cursorFrom < sz) cursorFrom = sz;
81 		if (cursorTo < sz) cursorTo = sz;
82 		update ();
83 		oldText = labelText;
84 	}
85 }
86 
getText() const87 std::string Label::getText () const {return labelText;}
88 
setTextColors(const BColors::ColorSet & colorset)89 void Label::setTextColors (const BColors::ColorSet& colorset)
90 {
91 	if (labelColors != colorset)
92 	{
93 		labelColors = colorset;
94 		update ();
95 	}
96 }
getTextColors()97 BColors::ColorSet* Label::getTextColors () {return &labelColors;}
98 
setFont(const BStyles::Font & font)99 void Label::setFont (const BStyles::Font& font)
100 {
101 	labelFont = font;
102 	update ();
103 }
getFont()104 BStyles::Font* Label::getFont () {return &labelFont;}
105 
getTextWidth(std::string & text)106 double Label::getTextWidth (std::string& text)
107 {
108 	double textwidth = 0.0;
109 	cairo_t* cr = cairo_create (widgetSurface_);
110 	cairo_text_extents_t ext = labelFont.getTextExtents(cr, text.c_str ());
111 	textwidth = ext.width;
112 	cairo_destroy (cr);
113 	return textwidth;
114 }
115 
resize()116 void Label::resize ()
117 {
118 	// Get label text size
119 	cairo_t* cr = cairo_create (widgetSurface_);
120 	cairo_text_extents_t ext = labelFont.getTextExtents(cr, labelText.c_str ());
121 	double w = ext.width;
122 	double h = (ext.height > labelFont.getFontSize() ? ext.height : labelFont.getFontSize());
123 	BUtilities::Point contExt = BUtilities::Point (w + 2 * getXOffset () + 2, h + 2 * getYOffset () + 2);
124 	cairo_destroy (cr);
125 
126 	// Or use embedded widgets size, if bigger
127 	for (Widget* w : children_)
128 	{
129 		if (w->getPosition ().x + w->getWidth () > contExt.x) contExt.x = w->getPosition ().x + w->getWidth();
130 		if (w->getPosition ().y + w->getHeight () > contExt.y) contExt.y = w->getPosition ().y + w->getHeight();
131 	}
132 
133 	Label::resize (contExt);
134 }
135 
resize(const double width,const double height)136 void Label::resize (const double width, const double height) {Label::resize (BUtilities::Point (width, height));}
resize(const BUtilities::Point extends)137 void Label::resize (const BUtilities::Point extends) {Widget::resize (extends);}
138 
applyTheme(BStyles::Theme & theme)139 void Label::applyTheme (BStyles::Theme& theme) {applyTheme (theme, name_);}
140 
applyTheme(BStyles::Theme & theme,const std::string & name)141 void Label::applyTheme (BStyles::Theme& theme, const std::string& name)
142 {
143 	Widget::applyTheme (theme, name);
144 
145 	// Color
146 	void* colorsPtr = theme.getStyle(name, BWIDGETS_KEYWORD_TEXTCOLORS);
147 	if (colorsPtr) labelColors = *((BColors::ColorSet*) colorsPtr);
148 
149 	// Font
150 	void* fontPtr = theme.getStyle(name, BWIDGETS_KEYWORD_FONT);
151 	if (fontPtr) labelFont = *((BStyles::Font*) fontPtr);
152 
153 	if (colorsPtr || fontPtr) update ();
154 }
155 
setEditable(const bool status)156 void Label::setEditable (const bool status)
157 {
158 	editable = status;
159 	if (editMode) update ();
160 }
161 
isEditable() const162 bool Label::isEditable () const {return editable;}
163 
setEditMode(const bool mode)164 void Label::setEditMode (const bool mode)
165 {
166 	if (mode != editMode)
167 	{
168 		editMode = mode;
169 		update ();
170 	}
171 }
172 
getEditMode() const173 bool Label::getEditMode () const {return editMode;}
174 
setCursor(const size_t pos)175 void Label::setCursor (const size_t pos) {setCursor (pos, pos);}
176 
setCursor(const size_t from,const size_t to)177 void Label::setCursor (const size_t from, const size_t to)
178 {
179 	size_t cf = from;
180 	size_t ct = to;
181 
182 	// Check limits
183 	size_t s32 = u32labelText.length ();
184 	if (cf > s32) cf = s32;
185 	if (ct > s32) ct = s32;
186 
187 	// Apply changes
188 	if ((cf != cursorFrom) || (ct != cursorTo))
189 	{
190 		cursorFrom = cf;
191 		cursorTo = ct;
192 		update ();
193 	}
194 }
195 
applyEdit()196 void Label::applyEdit ()
197 {
198 	if (main_) main_->getKeyGrabStack()->remove (this);
199 	setEditMode (false);
200 	if (labelText != oldText)
201 	{
202 		postMessage (BWIDGETS_LABEL_TEXT_CHANGED_MESSAGE, BUtilities::makeAny<std::string> (labelText));
203 		oldText = labelText;
204 	}
205 }
206 
discardEdit()207 void Label::discardEdit ()
208 {
209 	if (main_) main_->getKeyGrabStack()->remove (this);
210 	setEditMode (false);
211 	if (labelText != oldText) setText (oldText);
212 }
213 
onKeyPressed(BEvents::KeyEvent * event)214 void Label::onKeyPressed (BEvents::KeyEvent* event)
215 {
216 	if
217 	(
218 		editable &&
219 		event &&
220 		(event->getWidget () == this) &&
221 		main_ &&
222 		(main_->getKeyGrabStack()->getGrab(0)->getWidget() == this)
223 	)
224 	{
225 		uint32_t key = event->getKey ();
226 
227 		switch (key)
228 		{
229 			case 8:		// Backspace
230 			{
231 				size_t cf = cursorFrom;
232 				size_t ct = cursorTo;
233 				if (ct < cf) { cf = cursorTo; ct = cursorFrom; }
234 
235 				if (cf != ct) u32labelText.erase (cf, ct - cf);
236 				else if (cf > 0)
237 				{
238 					u32labelText.erase (cf - 1, 1);
239 					--cf;
240 				}
241 
242 				std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> convert;
243 				labelText = convert.to_bytes (u32labelText);
244 				setCursor (cf);
245 			}
246 			break;
247 
248 			case 13:	// Enter
249 			applyEdit ();
250 			break;
251 
252 			case 27:	// Escape
253 			discardEdit ();
254 			break;
255 
256 			case 127:	// Delete
257 			{
258 				size_t cf = cursorFrom;
259 				size_t ct = cursorTo;
260 				if (ct < cf) { cf = cursorTo; ct = cursorFrom; }
261 
262 				if (cf != ct) u32labelText.erase (cf, ct - cf);
263 				else if (cf < u32labelText.size ()) u32labelText.erase (cf, 1);
264 
265 				std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> convert;
266 				labelText = convert.to_bytes (u32labelText);
267 				setCursor (cf);
268 				update ();
269 			}
270 			break;
271 
272 			case PUGL_KEY_LEFT :
273 			if (cursorFrom > 0) setCursor (cursorFrom - 1);
274 			break;
275 
276 			case PUGL_KEY_RIGHT :
277 			setCursor (cursorFrom + 1);
278 			break;
279 
280 			default:
281 			{
282 				if ((key >= 0x20) && (key < 0x7F))
283 				{
284 					size_t cf = cursorFrom;
285 					size_t ct = cursorTo;
286 					if (ct < cf) { cf = cursorTo; ct = cursorFrom; }
287 
288 					if (cf != ct) u32labelText.erase (cf, ct - cf);
289 					u32labelText.insert (u32labelText.begin () + cf, key);
290 
291 					std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> convert;
292 					labelText = convert.to_bytes (u32labelText);
293 					setCursor (cf + 1);
294 				}
295 			}
296 			break;
297 		}
298 	}
299 
300 	cbfunction_[BEvents::EventType::KEY_PRESS_EVENT] (event);
301 }
302 
onKeyReleased(BEvents::KeyEvent * event)303 void Label::onKeyReleased (BEvents::KeyEvent* event) {cbfunction_[BEvents::EventType::KEY_RELEASE_EVENT] (event);}
304 
onButtonClicked(BEvents::PointerEvent * event)305 void Label::onButtonClicked (BEvents::PointerEvent* event)
306 {
307 	if (editable && (event) && (event->getWidget () == this) && (main_))
308 	{
309 		main_->getKeyGrabStack()->add (this);
310 		setEditMode (true);
311 		size_t cursor = getCursorFromCoords (event->getPosition ());
312 		setCursor (cursor, cursor);
313 	}
314 
315 	cbfunction_[BEvents::EventType::BUTTON_PRESS_EVENT] (event);
316 }
317 
onPointerDragged(BEvents::PointerEvent * event)318 void Label::onPointerDragged (BEvents::PointerEvent* event)
319 {
320 	if
321 	(
322 		editable &&
323 		editMode &&
324 		(event) &&
325 		(event->getWidget () == this) &&
326 		(main_) &&
327 		(main_->getKeyGrabStack()->getGrab(0)->getWidget() == this)
328 	)
329 	{
330 		size_t cursor = getCursorFromCoords (event->getPosition ());
331 		setCursor (cursorFrom, cursor);
332 	}
333 
334 	cbfunction_[BEvents::EventType::POINTER_DRAG_EVENT] (event);
335 }
336 
getCursorFromCoords(const BUtilities::Point & position)337 size_t Label::getCursorFromCoords (const BUtilities::Point& position)
338 {
339 	size_t cursor = u32labelText.length ();
340 	if ((!widgetSurface_) || (cairo_surface_status (widgetSurface_) != CAIRO_STATUS_SUCCESS)) return 0;
341 
342 	cairo_t* cr = cairo_create (widgetSurface_);
343 
344 	if (cairo_status (cr) == CAIRO_STATUS_SUCCESS)
345 	{
346 		std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> convert;
347 
348 		double xoff = getXOffset ();
349 		//double yoff = getYOffset ();
350 		double w = getEffectiveWidth ();
351 		//double h = getEffectiveHeight ();
352 
353 		cairo_text_extents_t ext = labelFont.getTextExtents (cr, "|" + labelText + "|");
354 		cairo_text_extents_t ext0 = labelFont.getTextExtents(cr, "|");
355 
356 		double x0;
357 
358 		switch (labelFont.getTextAlign ())
359 		{
360 		case BStyles::TEXT_ALIGN_LEFT:		x0 = - ext.x_bearing;
361 							break;
362 		case BStyles::TEXT_ALIGN_CENTER:	x0 = w / 2 - (ext.width - 2 * ext0.width - 2 * ext0.x_bearing) / 2;
363 							break;
364 		case BStyles::TEXT_ALIGN_RIGHT:		x0 = w - (ext.width - 2 * ext0.width - 2 * ext0.x_bearing);
365 							break;
366 		default:				x0 = 0;
367 		}
368 
369 		std::u32string u32fragment = U"";
370 		for (size_t i = 0; i < u32labelText.length (); ++i)
371 		{
372 			u32fragment += u32labelText[i];
373 			std::string fragment = convert.to_bytes (u32fragment);
374 			cairo_text_extents_t ext1 = labelFont.getTextExtents(cr, "|" + fragment + "|");
375 
376 			if (position.x < xoff + x0 + ext1.width - 2 * ext0.width - 2 * ext0.x_bearing)
377 			{
378 				cursor = i;
379 				break;
380 			}
381 		}
382 
383 		cairo_destroy (cr);
384 	}
385 
386 	return cursor;
387 }
388 
draw(const BUtilities::RectArea & area)389 void Label::draw (const BUtilities::RectArea& area)
390 {
391 	if ((!widgetSurface_) || (cairo_surface_status (widgetSurface_) != CAIRO_STATUS_SUCCESS)) return;
392 
393 	// Draw super class widget elements first
394 	Widget::draw (area);
395 
396 	cairo_t* cr = cairo_create (widgetSurface_);
397 
398 	if (cairo_status (cr) == CAIRO_STATUS_SUCCESS)
399 	{
400 		// Limit cairo-drawing area
401 		cairo_rectangle (cr, area.getX (), area.getY (), area.getWidth (), area.getHeight ());
402 		cairo_clip (cr);
403 
404 		double xoff = getXOffset ();
405 		double yoff = getYOffset ();
406 		double w = getEffectiveWidth ();
407 		double h = getEffectiveHeight ();
408 
409 		cairo_text_extents_t ext = labelFont.getTextExtents(cr, "|" + labelText + "|");
410 		cairo_text_extents_t ext0 = labelFont.getTextExtents(cr, "|");
411 		cairo_select_font_face (cr, labelFont.getFontFamily ().c_str (), labelFont.getFontSlant (), labelFont.getFontWeight ());
412 		cairo_set_font_size (cr, labelFont.getFontSize ());
413 
414 		double x0, y0;
415 
416 		switch (labelFont.getTextAlign ())
417 		{
418 		case BStyles::TEXT_ALIGN_LEFT:		x0 = 0;
419 							break;
420 		case BStyles::TEXT_ALIGN_CENTER:	x0 = w / 2 - (ext.width - 2 * ext0.width - 2 * ext0.x_bearing) / 2;
421 							break;
422 		case BStyles::TEXT_ALIGN_RIGHT:		x0 = w - (ext.width - 2 * ext0.width - 2 * ext0.x_bearing);
423 							break;
424 		default:				x0 = 0;
425 		}
426 
427 		switch (labelFont.getTextVAlign ())
428 		{
429 		case BStyles::TEXT_VALIGN_TOP:		y0 = - ext.y_bearing;
430 							break;
431 		case BStyles::TEXT_VALIGN_MIDDLE:	y0 = h / 2 - ext.height / 2 - ext.y_bearing;
432 							break;
433 		case BStyles::TEXT_VALIGN_BOTTOM:	y0 = h - ext.height - ext.y_bearing;
434 							break;
435 		default:				y0 = 0;
436 		}
437 
438 		if (editable && editMode)
439 		{
440 			std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> convert;
441 
442 			size_t cf = cursorFrom;
443 			size_t ct = cursorTo;
444 			if (ct < cf) { cf = cursorTo; ct = cursorFrom; }
445 			std::string s1 = convert.to_bytes (u32labelText.substr (0, cf));
446 			std::string s2 = convert.to_bytes (u32labelText.substr (cf, ct - cf));
447 			std::string s3 = convert.to_bytes (u32labelText.substr (ct, std::u32string::npos));
448 
449 			cairo_text_extents_t ext1 = labelFont.getTextExtents(cr, "|" + s1 + "|");
450 			cairo_text_extents_t ext2 = labelFont.getTextExtents(cr, "|" + s2 + "|");
451 
452 			double w1 = ext1.width - 2 * ext0.width - 2 * ext0.x_bearing;
453 			double w2 = ext2.width - 2 * ext0.width - 2 * ext0.x_bearing;
454 
455 			BColors::Color lc = *labelColors.getColor (BColors::ACTIVE);
456 			cairo_set_source_rgba (cr, lc.getRed (), lc.getGreen (), lc.getBlue (), lc.getAlpha ());
457 			cairo_set_line_width (cr, 1.0);
458 			cairo_rectangle (cr, xoff + x0 + w1, yoff + y0, w2, -ext0.height);
459 			cairo_stroke_preserve (cr);
460 			cairo_fill (cr);
461 
462 			cairo_set_source_rgba (cr, lc.getRed (), lc.getGreen (), lc.getBlue (), lc.getAlpha ());
463 			cairo_move_to (cr, xoff + x0, yoff + y0);
464 			cairo_show_text (cr, s1.c_str ());
465 
466 			cairo_set_source_rgba (cr, 1 - lc.getRed (), 1 - lc.getGreen (), 1 - lc.getBlue (), lc.getAlpha ());
467 			cairo_move_to (cr, xoff + x0 + w1, yoff + y0);
468 			cairo_show_text (cr, s2.c_str ());
469 
470 			cairo_set_source_rgba (cr, lc.getRed (), lc.getGreen (), lc.getBlue (), lc.getAlpha ());
471 			cairo_move_to (cr, xoff + x0 + w1 + w2, yoff + y0);
472 			cairo_show_text (cr, s3.c_str ());
473 		}
474 
475 		else
476 		{
477 
478 			BColors::Color lc = *labelColors.getColor (getState ());
479 			cairo_set_source_rgba (cr, lc.getRed (), lc.getGreen (), lc.getBlue (), lc.getAlpha ());
480 			cairo_move_to (cr, xoff + x0, yoff + y0);
481 			cairo_show_text (cr, labelText.c_str ());
482 		}
483 	}
484 
485 	cairo_destroy (cr);
486 }
487 
488 }
489