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