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, ¶ms); 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, ¶ms); 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