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 	if (labelText != oldText)
65 	{
66 		postMessage (BWIDGETS_LABEL_TEXT_CHANGED_MESSAGE, BUtilities::makeAny<std::string> (labelText));
67 		oldText = labelText;
68 	}
69 
70 	return *this;
71 }
72 
clone() const73 Widget* Label::clone () const {return new Label (*this);}
74 
setText(const std::string & text)75 void Label::setText (const std::string& text)
76 {
77 	if (text != labelText)
78 	{
79 		labelText = text;
80 		std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> convert;
81 		u32labelText = convert.from_bytes (labelText);
82 		size_t sz = u32labelText.length ();
83 		if (cursorFrom < sz) cursorFrom = sz;
84 		if (cursorTo < sz) cursorTo = sz;
85 		update ();
86 		if (labelText != oldText)
87 		{
88 			postMessage (BWIDGETS_LABEL_TEXT_CHANGED_MESSAGE, BUtilities::makeAny<std::string> (labelText));
89 			oldText = labelText;
90 		}
91 	}
92 }
93 
getText() const94 std::string Label::getText () const {return labelText;}
95 
setTextColors(const BColors::ColorSet & colorset)96 void Label::setTextColors (const BColors::ColorSet& colorset)
97 {
98 	if (labelColors != colorset)
99 	{
100 		labelColors = colorset;
101 		update ();
102 	}
103 }
getTextColors()104 BColors::ColorSet* Label::getTextColors () {return &labelColors;}
105 
setFont(const BStyles::Font & font)106 void Label::setFont (const BStyles::Font& font)
107 {
108 	labelFont = font;
109 	update ();
110 }
getFont()111 BStyles::Font* Label::getFont () {return &labelFont;}
112 
getTextWidth(std::string & text)113 double Label::getTextWidth (std::string& text)
114 {
115 	double textwidth = 0.0;
116 	cairo_t* cr = cairo_create (widgetSurface_);
117 	cairo_text_extents_t ext = labelFont.getTextExtents(cr, text.c_str ());
118 	textwidth = ext.width;
119 	cairo_destroy (cr);
120 	return textwidth;
121 }
122 
resize()123 void Label::resize ()
124 {
125 	// Get label text size
126 	cairo_t* cr = cairo_create (widgetSurface_);
127 	cairo_text_extents_t ext = labelFont.getTextExtents(cr, labelText.c_str ());
128 	double w = ext.width;
129 	double h = (ext.height > labelFont.getFontSize() ? ext.height : labelFont.getFontSize());
130 	BUtilities::Point contExt = BUtilities::Point (w + 2 * getXOffset () + 2, h + 2 * getYOffset () + 2);
131 	cairo_destroy (cr);
132 
133 	// Or use embedded widgets size, if bigger
134 	for (Widget* w : children_)
135 	{
136 		if (w->getPosition ().x + w->getWidth () > contExt.x) contExt.x = w->getPosition ().x + w->getWidth();
137 		if (w->getPosition ().y + w->getHeight () > contExt.y) contExt.y = w->getPosition ().y + w->getHeight();
138 	}
139 
140 	Label::resize (contExt);
141 }
142 
resize(const double width,const double height)143 void Label::resize (const double width, const double height) {Label::resize (BUtilities::Point (width, height));}
resize(const BUtilities::Point extends)144 void Label::resize (const BUtilities::Point extends) {Widget::resize (extends);}
145 
applyTheme(BStyles::Theme & theme)146 void Label::applyTheme (BStyles::Theme& theme) {applyTheme (theme, name_);}
147 
applyTheme(BStyles::Theme & theme,const std::string & name)148 void Label::applyTheme (BStyles::Theme& theme, const std::string& name)
149 {
150 	Widget::applyTheme (theme, name);
151 
152 	// Color
153 	void* colorsPtr = theme.getStyle(name, BWIDGETS_KEYWORD_TEXTCOLORS);
154 	if (colorsPtr) labelColors = *((BColors::ColorSet*) colorsPtr);
155 
156 	// Font
157 	void* fontPtr = theme.getStyle(name, BWIDGETS_KEYWORD_FONT);
158 	if (fontPtr) labelFont = *((BStyles::Font*) fontPtr);
159 
160 	if (colorsPtr || fontPtr) update ();
161 }
162 
setEditable(const bool status)163 void Label::setEditable (const bool status)
164 {
165 	editable = true;
166 	if (editMode) update ();
167 }
168 
isEditable() const169 bool Label::isEditable () const {return editable;}
170 
setEditMode(const bool mode)171 void Label::setEditMode (const bool mode)
172 {
173 	if (mode != editMode)
174 	{
175 		editMode = mode;
176 		update ();
177 		if (editable) postMessage (BWIDGETS_LABEL_EDIT_ENTERED_MESSAGE, BUtilities::makeAny<bool> (editMode));
178 	}
179 }
180 
getEditMode() const181 bool Label::getEditMode () const {return editMode;}
182 
setCursor(const size_t pos)183 void Label::setCursor (const size_t pos) {setCursor (pos, pos);}
184 
setCursor(const size_t from,const size_t to)185 void Label::setCursor (const size_t from, const size_t to)
186 {
187 	size_t cf = from;
188 	size_t ct = to;
189 
190 	// Check limits
191 	size_t s32 = u32labelText.length ();
192 	if (cf > s32) cf = s32;
193 	if (ct > s32) ct = s32;
194 
195 	// Apply changes
196 	if ((cf != cursorFrom) || (ct != cursorTo))
197 	{
198 		cursorFrom = cf;
199 		cursorTo = ct;
200 		update ();
201 	}
202 }
203 
applyEdit()204 void Label::applyEdit ()
205 {
206 	if (main_) main_->getKeyGrabStack()->remove (this);
207 	setEditMode (false);
208 	if (labelText != oldText)
209 	{
210 		postMessage (BWIDGETS_LABEL_TEXT_CHANGED_MESSAGE, BUtilities::makeAny<std::string> (labelText));
211 		oldText = labelText;
212 	}
213 }
214 
discardEdit()215 void Label::discardEdit ()
216 {
217 	if (main_) main_->getKeyGrabStack()->remove (this);
218 	setEditMode (false);
219 	if (labelText != oldText) setText (oldText);
220 }
221 
onKeyPressed(BEvents::KeyEvent * event)222 void Label::onKeyPressed (BEvents::KeyEvent* event)
223 {
224 	if
225 	(
226 		editable &&
227 		event &&
228 		(event->getWidget () == this) &&
229 		main_ &&
230 		(main_->getKeyGrabStack()->getGrab(0)->getWidget() == this)
231 	)
232 	{
233 		uint32_t key = event->getKey ();
234 
235 		switch (key)
236 		{
237 			case 8:		// Backspace
238 			{
239 				size_t cf = cursorFrom;
240 				size_t ct = cursorTo;
241 				if (ct < cf) { cf = cursorTo; ct = cursorFrom; }
242 
243 				if (cf != ct) u32labelText.erase (cf, ct - cf);
244 				else if (cf > 0)
245 				{
246 					u32labelText.erase (cf - 1, 1);
247 					--cf;
248 				}
249 
250 				std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> convert;
251 				labelText = convert.to_bytes (u32labelText);
252 				setCursor (cf);
253 			}
254 			break;
255 
256 			case 13:	// Enter
257 			applyEdit ();
258 			break;
259 
260 			case 27:	// Escape
261 			discardEdit ();
262 			break;
263 
264 			case 127:	// Delete
265 			{
266 				size_t cf = cursorFrom;
267 				size_t ct = cursorTo;
268 				if (ct < cf) { cf = cursorTo; ct = cursorFrom; }
269 
270 				if (cf != ct) u32labelText.erase (cf, ct - cf);
271 				else if (cf < u32labelText.size ()) u32labelText.erase (cf, 1);
272 
273 				std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> convert;
274 				labelText = convert.to_bytes (u32labelText);
275 				setCursor (cf);
276 				update ();
277 			}
278 			break;
279 
280 			case PUGL_KEY_LEFT :
281 			if (cursorFrom > 0) setCursor (cursorFrom - 1);
282 			break;
283 
284 			case PUGL_KEY_RIGHT :
285 			setCursor (cursorFrom + 1);
286 			break;
287 
288 			default:
289 			{
290 				if ((key >= 0x20) && (key < 0x7F))
291 				{
292 					size_t cf = cursorFrom;
293 					size_t ct = cursorTo;
294 					if (ct < cf) { cf = cursorTo; ct = cursorFrom; }
295 
296 					if (cf != ct) u32labelText.erase (cf, ct - cf);
297 					u32labelText.insert (u32labelText.begin () + cf, key);
298 
299 					std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> convert;
300 					labelText = convert.to_bytes (u32labelText);
301 					setCursor (cf + 1);
302 				}
303 			}
304 			break;
305 		}
306 	}
307 
308 	cbfunction_[BEvents::EventType::KEY_PRESS_EVENT] (event);
309 }
310 
onKeyReleased(BEvents::KeyEvent * event)311 void Label::onKeyReleased (BEvents::KeyEvent* event) {cbfunction_[BEvents::EventType::KEY_RELEASE_EVENT] (event);}
312 
onButtonPressed(BEvents::PointerEvent * event)313 void Label::onButtonPressed (BEvents::PointerEvent* event)
314 {
315 	if (editable && (event) && (event->getWidget () == this) && (main_))
316 	{
317 		main_->getKeyGrabStack()->add (this);
318 		setEditMode (true);
319 		size_t cursor = getCursorFromCoords (event->getPosition ());
320 		setCursor (cursor, cursor);
321 	}
322 
323 	cbfunction_[BEvents::EventType::BUTTON_PRESS_EVENT] (event);
324 }
325 
onPointerDragged(BEvents::PointerEvent * event)326 void Label::onPointerDragged (BEvents::PointerEvent* event)
327 {
328 	if
329 	(
330 		editable &&
331 		(event) &&
332 		(event->getWidget () == this) &&
333 		(main_) &&
334 		(main_->getKeyGrabStack()->getGrab(0)->getWidget() == this)
335 	)
336 	{
337 		size_t cursor = getCursorFromCoords (event->getPosition ());
338 		setCursor (cursorFrom, cursor);
339 	}
340 
341 	cbfunction_[BEvents::EventType::POINTER_DRAG_EVENT] (event);
342 }
343 
getCursorFromCoords(const BUtilities::Point & position)344 size_t Label::getCursorFromCoords (const BUtilities::Point& position)
345 {
346 	size_t cursor = u32labelText.length ();
347 	if ((!widgetSurface_) || (cairo_surface_status (widgetSurface_) != CAIRO_STATUS_SUCCESS)) return 0;
348 
349 	cairo_t* cr = cairo_create (widgetSurface_);
350 
351 	if (cairo_status (cr) == CAIRO_STATUS_SUCCESS)
352 	{
353 		std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> convert;
354 
355 		double xoff = getXOffset ();
356 		//double yoff = getYOffset ();
357 		double w = getEffectiveWidth ();
358 		//double h = getEffectiveHeight ();
359 
360 		cairo_text_extents_t ext = labelFont.getTextExtents (cr, "|" + labelText + "|");
361 		cairo_text_extents_t ext0 = labelFont.getTextExtents(cr, "|");
362 
363 		double x0;
364 
365 		switch (labelFont.getTextAlign ())
366 		{
367 		case BStyles::TEXT_ALIGN_LEFT:		x0 = - ext.x_bearing;
368 							break;
369 		case BStyles::TEXT_ALIGN_CENTER:	x0 = w / 2 - (ext.width - 2 * ext0.width - 2 * ext0.x_bearing) / 2;
370 							break;
371 		case BStyles::TEXT_ALIGN_RIGHT:		x0 = w - (ext.width - 2 * ext0.width - 2 * ext0.x_bearing);
372 							break;
373 		default:				x0 = 0;
374 		}
375 
376 		std::u32string u32fragment = U"";
377 		for (size_t i = 0; i < u32labelText.length (); ++i)
378 		{
379 			u32fragment += u32labelText[i];
380 			std::string fragment = convert.to_bytes (u32fragment);
381 			cairo_text_extents_t ext1 = labelFont.getTextExtents(cr, "|" + fragment + "|");
382 
383 			if (position.x < xoff + x0 + ext1.width - 2 * ext0.width - 2 * ext0.x_bearing)
384 			{
385 				cursor = i;
386 				break;
387 			}
388 		}
389 
390 		cairo_destroy (cr);
391 	}
392 
393 	return cursor;
394 }
395 
draw(const BUtilities::RectArea & area)396 void Label::draw (const BUtilities::RectArea& area)
397 {
398 	if ((!widgetSurface_) || (cairo_surface_status (widgetSurface_) != CAIRO_STATUS_SUCCESS)) return;
399 
400 	// Draw super class widget elements first
401 	Widget::draw (area);
402 
403 	cairo_t* cr = cairo_create (widgetSurface_);
404 
405 	if (cairo_status (cr) == CAIRO_STATUS_SUCCESS)
406 	{
407 		// Limit cairo-drawing area
408 		cairo_rectangle (cr, area.getX (), area.getY (), area.getWidth (), area.getHeight ());
409 		cairo_clip (cr);
410 
411 		double xoff = getXOffset ();
412 		double yoff = getYOffset ();
413 		double w = getEffectiveWidth ();
414 		double h = getEffectiveHeight ();
415 
416 		cairo_text_extents_t ext = labelFont.getTextExtents(cr, "|" + labelText + "|");
417 		cairo_text_extents_t ext0 = labelFont.getTextExtents(cr, "|");
418 		cairo_select_font_face (cr, labelFont.getFontFamily ().c_str (), labelFont.getFontSlant (), labelFont.getFontWeight ());
419 		cairo_set_font_size (cr, labelFont.getFontSize ());
420 
421 		double x0, y0;
422 
423 		switch (labelFont.getTextAlign ())
424 		{
425 		case BStyles::TEXT_ALIGN_LEFT:		x0 = 0;
426 							break;
427 		case BStyles::TEXT_ALIGN_CENTER:	x0 = w / 2 - (ext.width - 2 * ext0.width - 2 * ext0.x_bearing) / 2;
428 							break;
429 		case BStyles::TEXT_ALIGN_RIGHT:		x0 = w - (ext.width - 2 * ext0.width - 2 * ext0.x_bearing);
430 							break;
431 		default:				x0 = 0;
432 		}
433 
434 		switch (labelFont.getTextVAlign ())
435 		{
436 		case BStyles::TEXT_VALIGN_TOP:		y0 = - ext.y_bearing;
437 							break;
438 		case BStyles::TEXT_VALIGN_MIDDLE:	y0 = h / 2 - ext.height / 2 - ext.y_bearing;
439 							break;
440 		case BStyles::TEXT_VALIGN_BOTTOM:	y0 = h - ext.height - ext.y_bearing;
441 							break;
442 		default:				y0 = 0;
443 		}
444 
445 		if (editable && editMode)
446 		{
447 			std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> convert;
448 
449 			size_t cf = cursorFrom;
450 			size_t ct = cursorTo;
451 			if (ct < cf) { cf = cursorTo; ct = cursorFrom; }
452 			std::string s1 = convert.to_bytes (u32labelText.substr (0, cf));
453 			std::string s2 = convert.to_bytes (u32labelText.substr (cf, ct - cf));
454 			std::string s3 = convert.to_bytes (u32labelText.substr (ct, std::u32string::npos));
455 
456 			cairo_text_extents_t ext1 = labelFont.getTextExtents(cr, "|" + s1 + "|");
457 			cairo_text_extents_t ext2 = labelFont.getTextExtents(cr, "|" + s2 + "|");
458 
459 			double w1 = ext1.width - 2 * ext0.width - 2 * ext0.x_bearing;
460 			double w2 = ext2.width - 2 * ext0.width - 2 * ext0.x_bearing;
461 
462 			BColors::Color lc = *labelColors.getColor (BColors::ACTIVE);
463 			cairo_set_source_rgba (cr, lc.getRed (), lc.getGreen (), lc.getBlue (), lc.getAlpha ());
464 			cairo_set_line_width (cr, 1.0);
465 			cairo_rectangle (cr, xoff + x0 + w1, yoff + y0, w2, -ext0.height);
466 			cairo_stroke_preserve (cr);
467 			cairo_fill (cr);
468 
469 			cairo_set_source_rgba (cr, lc.getRed (), lc.getGreen (), lc.getBlue (), lc.getAlpha ());
470 			cairo_move_to (cr, xoff + x0, yoff + y0);
471 			cairo_show_text (cr, s1.c_str ());
472 
473 			cairo_set_source_rgba (cr, 1 - lc.getRed (), 1 - lc.getGreen (), 1 - lc.getBlue (), lc.getAlpha ());
474 			cairo_move_to (cr, xoff + x0 + w1, yoff + y0);
475 			cairo_show_text (cr, s2.c_str ());
476 
477 			cairo_set_source_rgba (cr, lc.getRed (), lc.getGreen (), lc.getBlue (), lc.getAlpha ());
478 			cairo_move_to (cr, xoff + x0 + w1 + w2, yoff + y0);
479 			cairo_show_text (cr, s3.c_str ());
480 		}
481 
482 		else
483 		{
484 
485 			BColors::Color lc = *labelColors.getColor (getState ());
486 			cairo_set_source_rgba (cr, lc.getRed (), lc.getGreen (), lc.getBlue (), lc.getAlpha ());
487 			cairo_move_to (cr, xoff + x0, yoff + y0);
488 			cairo_show_text (cr, labelText.c_str ());
489 		}
490 	}
491 
492 	cairo_destroy (cr);
493 }
494 
495 }
496