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