1 /*
2  * Copyright (C) 2020 Linux Studio Plugins Project <https://lsp-plug.in/>
3  *           (C) 2020 Vladimir Sadovnikov <sadko4u@gmail.com>
4  *
5  * This file is part of lsp-plugins
6  * Created on: 7 июл. 2017 г.
7  *
8  * lsp-plugins is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU Lesser General Public License as published by
10  * the Free Software Foundation, either version 3 of the License, or
11  * any later version.
12  *
13  * lsp-plugins is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with lsp-plugins. If not, see <https://www.gnu.org/licenses/>.
20  */
21 
22 #include <ui/ctl/ctl.h>
23 
24 #define TMP_BUF_SIZE        128
25 
26 namespace lsp
27 {
28     namespace ctl
29     {
30         const ctl_class_t CtlLabel::metadata = { "CtlLabel", &CtlWidget::metadata };
31 
32         //---------------------------------------------------------------------
33         // PopupWindow implementation
34 
PopupWindow(CtlLabel * label,LSPDisplay * dpy)35         CtlLabel::PopupWindow::PopupWindow(CtlLabel *label, LSPDisplay *dpy): LSPWindow(dpy),
36             sBox(dpy),
37             sValue(dpy),
38             sUnits(dpy),
39             sApply(dpy),
40             sCancel(dpy)
41         {
42             pLabel      = label;
43         }
44 
~PopupWindow()45         CtlLabel::PopupWindow::~PopupWindow()
46         {
47             pLabel      = NULL;
48         }
49 
init()50         status_t CtlLabel::PopupWindow::init()
51         {
52             // Initialize components
53             status_t res = LSPWindow::init();
54             if (res == STATUS_OK)
55                 res = sBox.init();
56             if (res == STATUS_OK)
57                 res = sValue.init();
58             if (res == STATUS_OK)
59                 res = sUnits.init();
60             if (res == STATUS_OK)
61                 res = sApply.init();
62             if (res == STATUS_OK)
63                 res = sCancel.init();
64 
65             if (res != STATUS_OK)
66                 return res;
67 
68             sBox.set_horizontal();
69             sBox.set_spacing(2);
70             sBox.add(&sValue);
71             sBox.add(&sUnits);
72             sBox.add(&sApply);
73             sBox.add(&sCancel);
74 
75             this->slots()->bind(LSPSLOT_MOUSE_DOWN, CtlLabel::slot_mouse_button, pLabel);
76             this->slots()->bind(LSPSLOT_MOUSE_UP, CtlLabel::slot_mouse_button, pLabel);
77 
78             sValue.slots()->bind(LSPSLOT_KEY_UP, CtlLabel::slot_key_up, pLabel);
79             sValue.slots()->bind(LSPSLOT_CHANGE, CtlLabel::slot_change_value, pLabel);
80             sValue.set_min_width(64);
81 
82             sUnits.padding()->set_left(4);
83 
84             sApply.title()->set("actions.apply");
85             sApply.slots()->bind(LSPSLOT_SUBMIT, CtlLabel::slot_submit_value, pLabel);
86 
87             sCancel.title()->set("actions.cancel");
88             sCancel.slots()->bind(LSPSLOT_SUBMIT, CtlLabel::slot_cancel_value, pLabel);
89 
90             this->add(&sBox);
91             this->set_border(1);
92             this->padding()->set(4, 2, 2, 2);
93 
94             return STATUS_OK;
95         }
96 
destroy()97         void CtlLabel::PopupWindow::destroy()
98         {
99             sValue.destroy();
100             sUnits.destroy();
101             sApply.destroy();
102             sBox.destroy();
103 
104             LSPWindow::destroy();
105         }
106 
notify(ui_atom_t property)107         void CtlLabel::Listener::notify(ui_atom_t property)
108         {
109             if (pLabel == NULL)
110                 return;
111             if (property == pLabel->nAtomID)
112                 pLabel->commit_value();
113         }
114 
115         //---------------------------------------------------------------------
116         // CtlLabel implementation
117 
CtlLabel(CtlRegistry * src,LSPLabel * widget,ctl_label_type_t type)118         CtlLabel::CtlLabel(CtlRegistry *src, LSPLabel *widget, ctl_label_type_t type):
119             CtlWidget(src, widget),
120             sListener(this)
121         {
122             pClass          = &metadata;
123 
124             pPort           = NULL;
125             enType          = type;
126             fValue          = 0.0f;
127             bDetailed       = true;
128             bSameLine       = false;
129             bReadOnly       = false;
130             nUnits          = U_NONE - 1;
131             nPrecision      = -1;
132             nAtomID         = -1;
133             pPopup          = NULL;
134         }
135 
~CtlLabel()136         CtlLabel::~CtlLabel()
137         {
138             do_destroy();
139         }
140 
destroy()141         void CtlLabel::destroy()
142         {
143             do_destroy();
144         }
145 
do_destroy()146         void CtlLabel::do_destroy()
147         {
148             sListener.pLabel = NULL;
149 
150             LSPLabel *lbl = widget_cast<LSPLabel>(pWidget);
151             if (lbl == NULL)
152                 return;
153 
154             if (nAtomID >= 0)
155             {
156                 LSPStyle *style = lbl->style();
157                 style->unbind(nAtomID, &sListener);
158                 nAtomID = -1;
159             }
160 
161             if (pPopup != NULL)
162             {
163                 pPopup->destroy();
164                 delete pPopup;
165                 pPopup = NULL;
166             }
167 
168             pWidget = NULL;
169         }
170 
init()171         void CtlLabel::init()
172         {
173             CtlWidget::init();
174 
175             if (pWidget == NULL)
176                 return;
177 
178             LSPLabel *lbl = widget_cast<LSPLabel>(pWidget);
179             if (lbl == NULL)
180                 return;
181 
182             LSPStyle *style = lbl->style();
183             LSPDisplay *dpy = lbl->display();
184             nAtomID = dpy->atom_id("language");
185             if (nAtomID >= 0)
186                 style->bind(nAtomID, PT_STRING, &sListener);
187 
188             // Initialize color controllers
189             sColor.init_hsl(pRegistry, lbl, lbl->font()->color(), A_COLOR, A_HUE_ID, A_SAT_ID, A_LIGHT_ID);
190             lbl->slot(LSPSLOT_MOUSE_DBL_CLICK)->bind(slot_dbl_click, this);
191         }
192 
set(const char * name,const char * value)193         void CtlLabel::set(const char *name, const char *value)
194         {
195             LSPLabel *lbl = widget_cast<LSPLabel>(pWidget);
196             if ((lbl != NULL) && (enType == CTL_LABEL_TEXT))
197                 set_lc_attr(A_TEXT, lbl->text(), name, value);
198 
199             CtlWidget::set(name, value);
200         }
201 
set(widget_attribute_t att,const char * value)202         void CtlLabel::set(widget_attribute_t att, const char *value)
203         {
204             LSPLabel *lbl = widget_cast<LSPLabel>(pWidget);
205 
206             switch (att)
207             {
208                 case A_ID:
209                     BIND_PORT(pRegistry, pPort, value);
210                     break;
211                 case A_UNITS:
212                     if (enType == CTL_LABEL_TEXT)
213                         return;
214                     if (!strcmp(value, "default"))
215                         nUnits      = U_NONE - 1;
216                     else
217                         nUnits      = decode_unit(value);
218                     break;
219                 case A_FONT_SIZE:
220                     if (lbl != NULL)
221                         PARSE_FLOAT(value, lbl->font()->set_size(__));
222                     break;
223                 case A_VALIGN:
224                     if (lbl != NULL)
225                         PARSE_FLOAT(value, lbl->set_valign(__));
226                     break;
227                 case A_HALIGN:
228                     if (lbl != NULL)
229                         PARSE_FLOAT(value, lbl->set_halign(__));
230                     break;
231                 case A_DETAILED:
232                     PARSE_BOOL(value, bDetailed = __);
233                     break;
234                 case A_SAME_LINE:
235                     PARSE_BOOL(value, bSameLine = __);
236                     break;
237                 case A_READ_ONLY:
238                     PARSE_BOOL(value, bReadOnly = __);
239                     break;
240                 case A_PRECISION:
241                     PARSE_INT(value, nPrecision = __);
242                     break;
243                 case A_BORDER:
244                     PARSE_INT(value, lbl->set_border(__));
245                     break;
246                 default:
247                 {
248                     sColor.set(att, value);
249                     CtlWidget::set(att, value);
250                     break;
251                 }
252             }
253         }
254 
notify(CtlPort * port)255         void CtlLabel::notify(CtlPort *port)
256         {
257             CtlWidget::notify(port);
258             if (pPort == port)
259                 commit_value();
260         }
261 
commit_value()262         void CtlLabel::commit_value()
263         {
264             // Get metadata and value
265             if (pPort == NULL)
266                 return;
267 
268             const port_t *mdata = pPort->metadata();
269             if (mdata == NULL)
270                 return;
271             fValue      = pPort->get_value();
272 
273             // Get label widget
274             LSPLabel *lbl = widget_cast<LSPLabel>(pWidget);
275             if (lbl == NULL)
276                 return;
277 
278             // Analyze type of the label
279             bool detailed       = bDetailed;
280 
281             switch (enType)
282             {
283                 case CTL_LABEL_TEXT:
284                     if ((mdata != NULL) && (mdata->name != NULL))
285                         lbl->text()->set_raw(mdata->name);
286                     return;
287 
288                 case CTL_LABEL_PARAM:
289                 {
290                     // Encode units
291                     LSPLocalString sunit;
292                     if (nUnits != (U_NONE - 1))
293                         sunit.set(unit_lc_key(nUnits));
294                     else
295                         sunit.set(unit_lc_key((is_decibel_unit(mdata->unit)) ? U_DB : mdata->unit));
296                     if (mdata->unit == U_BOOL)
297                         detailed = false;
298 
299                     // Form the final text
300                     LSPString text, funit;
301                     calc::Parameters params;
302 
303                     if (mdata->name != NULL)
304                         text.set_utf8(mdata->name);
305                     sunit.format(&funit, lbl);
306 
307 
308                     if ((detailed) && (funit.length() > 0))
309                     {
310                         if (text.length() > 0)
311                             text.append_ascii(" (");
312                         else
313                             text.append('(');
314                         text.append(&funit);
315                         text.append(')');
316                     }
317 
318                     // Update text
319                     const char *key = "labels.values.desc_name";
320                     if ((detailed) && (funit.length() > 0))
321                     {
322                         if (text.length() > 0)
323                             key = (bSameLine) ? "labels.values.desc_single_line" : "labels.values.desc_multi_line";
324                         else
325                             key = "labels.values.desc_unit";
326                     }
327 
328                     params.add_string("name", &text);
329                     params.add_string("unit", &funit);
330 
331                     lbl->text()->set(key, &params);
332                     break;
333                 }
334 
335                 case CTL_LABEL_VALUE:
336                 {
337                     // Encode units
338                     LSPLocalString sunit;
339                     if (nUnits != (U_NONE - 1))
340                         sunit.set(unit_lc_key(nUnits));
341                     else
342                         sunit.set(unit_lc_key((is_decibel_unit(mdata->unit)) ? U_DB : mdata->unit));
343 
344                     // Format the value
345                     char buf[TMP_BUF_SIZE];
346                     calc::Parameters params;
347                     LSPString text, funit;
348 
349                     format_value(buf, TMP_BUF_SIZE, mdata, fValue, nPrecision);
350                     text.set_ascii(buf);
351                     sunit.format(&funit, lbl);
352                     if (mdata->unit == U_BOOL)
353                     {
354                         text.prepend_ascii("labels.bool.");
355                         sunit.set(&text);
356                         sunit.format(&text, lbl);
357                         detailed = false;
358                     }
359 
360                     // Update text
361                     const char *key = "labels.values.fmt_value";
362                     if ((detailed) && (funit.length() > 0))
363                         key = (bSameLine) ? "labels.values.fmt_single_line" : "labels.values.fmt_multi_line";
364 
365                     params.add_string("value", &text);
366                     params.add_string("unit", &funit);
367 
368                     lbl->text()->set(key, &params);
369                     break;
370                 }
371 
372                 case CTL_STATUS_CODE:
373                 {
374                     status_t code = fValue;
375                     const char *text = get_status_lc_key(code);
376                     if (status_is_success(code))
377                         init_color(C_STATUS_OK, lbl->font()->color());
378                     else if (status_is_preliminary(code))
379                         init_color(C_STATUS_WARN, lbl->font()->color());
380                     else
381                         init_color(C_STATUS_ERROR, lbl->font()->color());
382                     lbl->text()->set(text);
383                     break;
384                 }
385 
386                 default:
387                     break;
388             }
389         }
390 
apply_value(const LSPString * value)391         bool CtlLabel::apply_value(const LSPString *value)
392         {
393             const port_t *meta = (pPort != NULL) ? pPort->metadata() : NULL;
394             if ((meta == NULL) || (!IS_IN_PORT(meta)))
395                 return false;
396 
397             float fv;
398             status_t res = parse_value(&fv, value->get_utf8(), meta);
399             if (res != STATUS_OK)
400                 return false;
401 
402             pPort->set_value(fv);
403             pPort->notify_all();
404             return true;
405         }
406 
end()407         void CtlLabel::end()
408         {
409             if (pPort != NULL)
410                 commit_value();
411 
412             // Get label widget
413             LSPLabel *lbl = widget_cast<LSPLabel>(pWidget);
414             if (lbl != NULL)
415             {
416                 lbl->set_min_width(nMinWidth);
417                 lbl->set_min_height(nMinHeight);
418             }
419 
420             CtlWidget::end();
421         }
422 
slot_submit_value(LSPWidget * sender,void * ptr,void * data)423         status_t CtlLabel::slot_submit_value(LSPWidget *sender, void *ptr, void *data)
424         {
425             // Get control pointer
426             CtlLabel *_this = static_cast<CtlLabel *>(ptr);
427             if ((_this == NULL) || (_this->pPopup == NULL))
428                 return STATUS_OK;
429 
430             // Apply value
431             PopupWindow *popup  = _this->pPopup;
432             LSPString value;
433             if (popup->sValue.get_text(&value) == STATUS_OK)
434             {
435                 // The deploy should be always successful
436                 if (!_this->apply_value(&value))
437                     return STATUS_OK;
438             }
439 
440             // Hide the popup window
441             if (popup != NULL)
442             {
443                 popup->hide();
444                 if (popup->queue_destroy() == STATUS_OK)
445                     _this->pPopup  = NULL;
446             }
447 
448             return STATUS_OK;
449         }
450 
slot_cancel_value(LSPWidget * sender,void * ptr,void * data)451         status_t CtlLabel::slot_cancel_value(LSPWidget *sender, void *ptr, void *data)
452         {
453             // Get control pointer
454             CtlLabel *_this = static_cast<CtlLabel *>(ptr);
455             if ((_this == NULL) || (_this->pPopup == NULL))
456                 return STATUS_OK;
457 
458             // Hide the widget and queue for destroy
459             PopupWindow *popup  = _this->pPopup;
460             if (popup != NULL)
461             {
462                 popup->hide();
463                 if (popup->queue_destroy() == STATUS_OK)
464                     _this->pPopup  = NULL;
465             }
466 
467             return STATUS_OK;
468         }
469 
slot_dbl_click(LSPWidget * sender,void * ptr,void * data)470         status_t CtlLabel::slot_dbl_click(LSPWidget *sender, void *ptr, void *data)
471         {
472             // Get control pointer
473             CtlLabel *_this = static_cast<CtlLabel *>(ptr);
474             if ((_this == NULL) || (_this->enType != CTL_LABEL_VALUE) || (_this->bReadOnly))
475                 return STATUS_OK;
476 
477             // Get port metadata
478             const port_t *mdata = (_this->pPort != NULL) ? _this->pPort->metadata() : NULL;
479             if ((mdata == NULL) || (!IS_IN_PORT(mdata)))
480                 return STATUS_OK;
481 
482             // Set-up units
483             const char *u_key = NULL;
484             if (_this->nUnits != (U_NONE - 1))
485                 u_key  = unit_lc_key(_this->nUnits);
486             else
487                 u_key  = unit_lc_key((is_decibel_unit(mdata->unit)) ? U_DB : mdata->unit);
488             if ((mdata->unit == U_BOOL) || (mdata->unit == U_ENUM))
489                 u_key  = NULL;
490 
491             // Get label widget
492             LSPLabel *lbl = widget_cast<LSPLabel>(_this->pWidget);
493             if (lbl == NULL)
494                 return STATUS_OK;
495 
496             // Create popup window if required
497             PopupWindow *popup  = _this->pPopup;
498             if (popup == NULL)
499             {
500                 popup           = new PopupWindow(_this, lbl->display());
501                 status_t res    = popup->init();
502                 if (res != STATUS_OK)
503                 {
504                     delete popup;
505                     return res;
506                 }
507 
508                 popup->set_border_style(BS_POPUP);
509                 popup->actions()->set_actions(WA_POPUP);
510 
511                 _this->pPopup   = popup;
512             }
513 
514             // Get location of popup window
515             realize_t r;
516             r.nLeft     = 0;
517             r.nTop      = 0;
518             r.nWidth    = 0;
519             r.nHeight   = 0;
520 
521             LSPWindow *parent = widget_cast<LSPWindow>(lbl->toplevel());
522             if (parent != NULL)
523                 parent->get_absolute_geometry(&r);
524 
525             // Set-up value
526             char buf[TMP_BUF_SIZE];
527             format_value(buf, TMP_BUF_SIZE, mdata, _this->fValue, _this->nPrecision);
528             popup->sValue.set_text(buf);
529             popup->sValue.selection()->set_all();
530 
531             if (u_key != NULL)
532             {
533                 if (popup->sUnits.text()->set(u_key) != STATUS_OK)
534                     u_key = NULL;
535             }
536 
537             popup->sUnits.set_visible(u_key != NULL);
538 
539             popup->move(r.nLeft + lbl->left(), r.nTop + lbl->top());
540             popup->show(lbl);
541             popup->grab_events(GRAB_NORMAL);
542 
543             // Set focus
544             popup->sValue.take_focus();
545 
546             return STATUS_OK;
547         }
548 
slot_mouse_button(LSPWidget * sender,void * ptr,void * data)549         status_t CtlLabel::slot_mouse_button(LSPWidget *sender, void *ptr, void *data)
550         {
551             // Get control pointer
552             CtlLabel *_this = static_cast<CtlLabel *>(ptr);
553             if ((_this == NULL) || (_this->pPopup == NULL))
554                 return STATUS_OK;
555 
556             // Get event
557             ws_event_t *ev = reinterpret_cast<ws_event_t *>(data);
558             if (ev == NULL)
559                 return STATUS_BAD_ARGUMENTS;
560 
561             // Hide popup window without any action
562             PopupWindow *popup  = _this->pPopup;
563             if (!popup->inside(ev->nLeft, ev->nTop))
564             {
565                 popup->hide();
566                 if (popup->queue_destroy() == STATUS_OK)
567                     _this->pPopup  = NULL;
568             }
569 
570             return STATUS_OK;
571         }
572 
slot_key_up(LSPWidget * sender,void * ptr,void * data)573         status_t CtlLabel::slot_key_up(LSPWidget *sender, void *ptr, void *data)
574         {
575             // Get control pointer
576             CtlLabel *_this = static_cast<CtlLabel *>(ptr);
577             if ((_this == NULL) || (_this->pPopup == NULL))
578                 return STATUS_OK;
579 
580             // Should be keyboard event
581             ws_event_t *ev = reinterpret_cast<ws_event_t *>(data);
582             if ((ev == NULL) || (ev->nType != UIE_KEY_UP))
583                 return STATUS_BAD_ARGUMENTS;
584 
585             // Hide popup window
586             ws_code_t key = LSPKeyboardHandler::translate_keypad(ev->nCode);
587 
588             PopupWindow *popup  = _this->pPopup;
589             if (key == WSK_RETURN)
590             {
591                 // Deploy new value
592                 LSPString value;
593                 if (popup->sValue.get_text(&value) == STATUS_OK)
594                 {
595                     if (!_this->apply_value(&value))
596                         return STATUS_OK;
597                 }
598             }
599 
600             if ((key == WSK_RETURN) || (key == WSK_ESCAPE))
601             {
602                 popup->hide();
603                 if (popup->queue_destroy() == STATUS_OK)
604                     _this->pPopup  = NULL;
605             }
606             return STATUS_OK;
607         }
608 
slot_change_value(LSPWidget * sender,void * ptr,void * data)609         status_t CtlLabel::slot_change_value(LSPWidget *sender, void *ptr, void *data)
610         {
611             // Get control pointer
612             CtlLabel *_this = static_cast<CtlLabel *>(ptr);
613             if ((_this == NULL) || (_this->pPopup == NULL))
614                 return STATUS_OK;
615 
616             // Get port metadata
617             const port_t *meta = (_this->pPort != NULL) ? _this->pPort->metadata() : NULL;
618             if ((meta == NULL) || (!IS_IN_PORT(meta)))
619                 return false;
620 
621             // Get popup window
622             PopupWindow *popup  = _this->pPopup;
623             if (popup == NULL)
624                 return STATUS_OK;
625 
626             // Validate input
627             LSPString value;
628             color_t color = C_RED;
629             if (popup->sValue.get_text(&value) == STATUS_OK)
630             {
631                 if (parse_value(NULL, value.get_utf8(), meta) == STATUS_OK)
632                     color   = C_BACKGROUND;
633             }
634 
635             // Update color
636             Color cl;
637             popup->display()->theme()->get_color(color, &cl);
638             popup->sValue.font()->color()->copy(&cl);
639 
640             return STATUS_OK;
641         }
642 
643     } /* namespace ctl */
644 } /* namespace lsp */
645