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: 23 окт. 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/tk/tk.h> 23 #include <ui/tk/helpers/draw.h> 24 #include <ui/tk/helpers/mime.h> 25 #include <core/files/url.h> 26 #include <dsp/dsp.h> 27 28 namespace lsp 29 { 30 namespace tk 31 { 32 const w_class_t LSPAudioFile::metadata = { "LSPAudioFile", &LSPWidget::metadata }; 33 channel_t(LSPWidget * widget)34 LSPAudioFile::channel_t::channel_t(LSPWidget *widget): 35 sColor(widget), 36 sFadeColor(widget), 37 sLineColor(widget) 38 { 39 nSamples = 0; 40 nCapacity = 0; 41 vSamples = NULL; 42 43 nFadeIn = 0.0f; 44 nFadeOut = 0.0f; 45 } 46 AudioFileSink(LSPAudioFile * af)47 LSPAudioFile::AudioFileSink::AudioFileSink(LSPAudioFile *af): LSPUrlSink("file://") 48 { 49 pWidget = af; 50 } 51 ~AudioFileSink()52 LSPAudioFile::AudioFileSink::~AudioFileSink() 53 { 54 pWidget = NULL; 55 } 56 unbind()57 void LSPAudioFile::AudioFileSink::unbind() 58 { 59 pWidget = NULL; 60 } 61 commit_url(const LSPString * url)62 status_t LSPAudioFile::AudioFileSink::commit_url(const LSPString *url) 63 { 64 LSPString decoded; 65 status_t res = (url->starts_with_ascii("file://")) ? 66 url_decode(&decoded, url, 7) : 67 url_decode(&decoded, url); 68 69 if (res != STATUS_OK) 70 return res; 71 72 lsp_trace("Set file path to %s", decoded.get_native()); 73 pWidget->sFileName.swap(&decoded); 74 pWidget->slots()->execute(LSPSLOT_SUBMIT, pWidget, NULL); 75 76 return STATUS_OK; 77 } 78 LSPAudioFile(LSPDisplay * dpy)79 LSPAudioFile::LSPAudioFile(LSPDisplay *dpy): 80 LSPWidget(dpy), 81 sHint(this), 82 sFont(dpy, this), 83 sHintFont(dpy, this), 84 sConstraints(this), 85 sDialog(dpy), 86 sColor(this), 87 sAxisColor(this) 88 { 89 pClass = &metadata; 90 pGlass = NULL; 91 pGraph = NULL; 92 nBtnWidth = 0; 93 nBtnHeight = 0; 94 nBMask = 0; 95 nBorder = 4; 96 nRadius = 10; 97 nStatus = 0; 98 pPopup = NULL; 99 100 nDecimSize = 0; 101 vDecimX = NULL; 102 vDecimY = NULL; 103 104 pSink = NULL; 105 } 106 ~LSPAudioFile()107 LSPAudioFile::~LSPAudioFile() 108 { 109 destroy_data(); 110 } 111 init()112 status_t LSPAudioFile::init() 113 { 114 status_t result = LSPWidget::init(); 115 if (result != STATUS_OK) 116 return result; 117 118 pSink = new AudioFileSink(this); 119 if (pSink == NULL) 120 return STATUS_NO_MEM; 121 pSink->acquire(); 122 123 sHint.bind(); 124 125 sFont.init(); 126 sFont.set_size(10); 127 sFont.set_bold(true); 128 129 sHintFont.init(); 130 sHintFont.set_size(16); 131 sHintFont.set_bold(true); 132 133 init_color(C_GLASS, &sColor); 134 init_color(C_GRAPH_LINE, &sAxisColor); 135 init_color(C_GRAPH_TEXT, sFont.color()); 136 init_color(C_STATUS_OK, sHintFont.color()); 137 138 // Initialize dialog 139 LSP_STATUS_ASSERT(sDialog.init()); 140 141 sDialog.title()->set("titles.load_audio_file"); 142 LSPFileFilter *f = sDialog.filter(); 143 { 144 LSPFileFilterItem ffi; 145 ffi.pattern()->set("*.wav"); 146 ffi.title()->set("files.audio.wave"); 147 ffi.set_extension(".wav"); 148 f->add(&ffi); 149 150 ffi.pattern()->set("*"); 151 ffi.title()->set("files.all"); 152 ffi.set_extension(""); 153 f->add(&ffi); 154 } 155 f->set_default(0); 156 157 sDialog.action_title()->set("actions.load"); 158 sDialog.bind_action(slot_on_dialog_submit, self()); 159 sDialog.slots()->bind(LSPSLOT_HIDE, slot_on_dialog_close, self()); 160 161 // Initialize slots 162 ui_handler_id_t id = 0; 163 id = sSlots.add(LSPSLOT_SUBMIT, slot_on_submit, self()); 164 if (id >= 0) id = sSlots.add(LSPSLOT_CLOSE, slot_on_close, self()); 165 if (id >= 0) id = sSlots.add(LSPSLOT_ACTIVATE, slot_on_close, self()); 166 if (id < 0) return -id; 167 168 return result; 169 } 170 slot_on_submit(LSPWidget * sender,void * ptr,void * data)171 status_t LSPAudioFile::slot_on_submit(LSPWidget *sender, void *ptr, void *data) 172 { 173 LSPAudioFile *_this = widget_ptrcast<LSPAudioFile>(ptr); 174 return (_this != NULL) ? _this->on_submit() : STATUS_BAD_ARGUMENTS; 175 } 176 slot_on_close(LSPWidget * sender,void * ptr,void * data)177 status_t LSPAudioFile::slot_on_close(LSPWidget *sender, void *ptr, void *data) 178 { 179 LSPAudioFile *_this = widget_ptrcast<LSPAudioFile>(ptr); 180 return (_this != NULL) ? _this->on_close() : STATUS_BAD_ARGUMENTS; 181 } 182 slot_on_activate(LSPWidget * sender,void * ptr,void * data)183 status_t LSPAudioFile::slot_on_activate(LSPWidget *sender, void *ptr, void *data) 184 { 185 LSPAudioFile *_this = widget_ptrcast<LSPAudioFile>(ptr); 186 return (_this != NULL) ? _this->on_activate() : STATUS_BAD_ARGUMENTS; 187 } 188 destroy()189 void LSPAudioFile::destroy() 190 { 191 destroy_data(); 192 LSPWidget::destroy(); 193 } 194 set_file_name(const char * text)195 status_t LSPAudioFile::set_file_name(const char *text) 196 { 197 if (text == NULL) 198 sFileName.truncate(); 199 else if (!sFileName.set_native(text)) 200 return STATUS_NO_MEM; 201 query_draw(); 202 return STATUS_OK; 203 } 204 set_file_name(const LSPString * text)205 status_t LSPAudioFile::set_file_name(const LSPString *text) 206 { 207 if (!sFileName.set(text)) 208 return STATUS_NO_MEM; 209 query_draw(); 210 return STATUS_OK; 211 } 212 create_channel(color_t color)213 LSPAudioFile::channel_t *LSPAudioFile::create_channel(color_t color) 214 { 215 channel_t *ch = new channel_t(this); 216 if (ch == NULL) 217 return NULL; 218 219 init_color(color, &ch->sColor); 220 init_color(C_YELLOW, &ch->sFadeColor); 221 init_color(C_YELLOW, &ch->sLineColor); 222 ch->sFadeColor.alpha(0.5f); 223 224 return ch; 225 } 226 destroy_channel(channel_t * channel)227 void LSPAudioFile::destroy_channel(channel_t *channel) 228 { 229 if (channel == NULL) 230 return; 231 if (channel->vSamples != NULL) 232 { 233 lsp_free(channel->vSamples); 234 channel->vSamples = 0; 235 } 236 delete channel; 237 } 238 destroy_data()239 void LSPAudioFile::destroy_data() 240 { 241 // Destroy sink 242 if (pSink != NULL) 243 { 244 pSink->unbind(); 245 pSink->release(); 246 pSink = NULL; 247 } 248 249 // Destroy surfaces 250 drop_glass(); 251 252 if (pGraph != NULL) 253 { 254 pGraph->destroy(); 255 delete pGraph; 256 pGraph = NULL; 257 } 258 259 if (vDecimX != NULL) 260 lsp_free(vDecimX); 261 vDecimX = NULL; 262 vDecimY = NULL; 263 nDecimSize = 0; 264 265 // Destroy dialog 266 sDialog.destroy(); 267 268 // Destroy all channel data 269 size_t n = vChannels.size(); 270 for (size_t i=0; i<n; ++i) 271 { 272 channel_t *c = vChannels.at(i); 273 if (c == NULL) 274 continue; 275 destroy_channel(c); 276 } 277 vChannels.flush(); 278 } 279 set_channels(size_t n)280 status_t LSPAudioFile::set_channels(size_t n) 281 { 282 size_t nc = vChannels.size(); 283 if (n < nc) // There are more channels present than requested 284 { 285 // Remove channels 286 while ((nc--) > n) 287 { 288 channel_t *c = vChannels.at(n); 289 if (!vChannels.remove(n)) 290 return STATUS_NO_MEM; 291 if (c == NULL) 292 continue; 293 destroy_channel(c); 294 } 295 296 query_resize(); 297 } 298 else if (n > nc) // There are more channels requested than present 299 { 300 while ((nc++) < n) 301 { 302 channel_t *c = create_channel((nc & 1) ? C_LEFT_CHANNEL : C_RIGHT_CHANNEL); 303 if (c == NULL) 304 return STATUS_NO_MEM; 305 if (!vChannels.add(c)) 306 { 307 destroy_channel(c); 308 return STATUS_NO_MEM; 309 } 310 } 311 312 query_resize(); 313 } 314 315 return STATUS_OK; 316 } 317 add_channel()318 status_t LSPAudioFile::add_channel() 319 { 320 size_t nc = vChannels.size(); 321 channel_t *c = create_channel(((++nc) & 1) ? C_LEFT_CHANNEL : C_RIGHT_CHANNEL); 322 if (c == NULL) 323 return STATUS_NO_MEM; 324 if (!vChannels.add(c)) 325 { 326 destroy_channel(c); 327 return STATUS_NO_MEM; 328 } 329 330 query_resize(); 331 332 return STATUS_OK; 333 } 334 add_channels(size_t n)335 status_t LSPAudioFile::add_channels(size_t n) 336 { 337 size_t nc = vChannels.size(); 338 n += nc; 339 340 query_resize(); 341 342 while ((nc++) < n) 343 { 344 channel_t *c = create_channel((nc & 1) ? C_LEFT_CHANNEL : C_RIGHT_CHANNEL); 345 if (c == NULL) 346 return STATUS_NO_MEM; 347 if (!vChannels.add(c)) 348 { 349 destroy_channel(c); 350 return STATUS_NO_MEM; 351 } 352 } 353 354 return STATUS_OK; 355 } 356 remove_channel(size_t i)357 status_t LSPAudioFile::remove_channel(size_t i) 358 { 359 channel_t *c = vChannels.get(i); 360 if (c == NULL) 361 return STATUS_BAD_ARGUMENTS; 362 if (!vChannels.remove(i)) 363 return STATUS_NO_MEM; 364 365 destroy_channel(c); 366 query_resize(); 367 return STATUS_OK; 368 } 369 swap_channels(size_t a,size_t b)370 status_t LSPAudioFile::swap_channels(size_t a, size_t b) 371 { 372 if (!vChannels.swap(a, b)) 373 return STATUS_BAD_ARGUMENTS; 374 375 query_draw(); 376 return STATUS_OK; 377 } 378 set_channel_fade_in(size_t i,float value)379 status_t LSPAudioFile::set_channel_fade_in(size_t i, float value) 380 { 381 channel_t *c = vChannels.get(i); 382 if (c == NULL) 383 return STATUS_BAD_ARGUMENTS; 384 if (c->nFadeIn == value) 385 return STATUS_OK; 386 c->nFadeIn = value; 387 query_draw(); 388 return STATUS_OK; 389 } 390 set_channel_fade_out(size_t i,float value)391 status_t LSPAudioFile::set_channel_fade_out(size_t i, float value) 392 { 393 channel_t *c = vChannels.get(i); 394 if (c == NULL) 395 return STATUS_BAD_ARGUMENTS; 396 if (c->nFadeOut == value) 397 return STATUS_OK; 398 c->nFadeOut = value; 399 query_draw(); 400 return STATUS_OK; 401 } 402 set_channel_data(size_t i,size_t samples,const float * data)403 status_t LSPAudioFile::set_channel_data(size_t i, size_t samples, const float *data) 404 { 405 channel_t *c = vChannels.get(i); 406 if (c == NULL) 407 return STATUS_BAD_ARGUMENTS; 408 409 size_t allocate = ALIGN_SIZE(samples, 16); 410 if (c->nCapacity < allocate) 411 { 412 float *ptr = lsp_trealloc(float, c->vSamples, allocate); 413 if (ptr == NULL) 414 return STATUS_NO_MEM; 415 c->vSamples = ptr; 416 c->nCapacity = allocate; 417 } 418 419 dsp::copy(c->vSamples, data, samples); 420 c->nSamples = samples; 421 query_draw(); 422 423 return STATUS_OK; 424 } 425 clear_channel_data(size_t i)426 status_t LSPAudioFile::clear_channel_data(size_t i) 427 { 428 channel_t *c = vChannels.get(i); 429 if (c == NULL) 430 return STATUS_BAD_ARGUMENTS; 431 if (c->nSamples <= 0) 432 return STATUS_OK; 433 434 c->nSamples = 0; 435 c->nCapacity = 0; 436 if (c->vSamples != NULL) 437 { 438 lsp_free(c->vSamples); 439 c->vSamples = NULL; 440 } 441 442 query_draw(); 443 return STATUS_OK; 444 } 445 clear_all_channel_data()446 status_t LSPAudioFile::clear_all_channel_data() 447 { 448 size_t n = vChannels.size(); 449 if (n <= 0) 450 return STATUS_OK; 451 452 for (size_t i=0; i<n; ++i) 453 { 454 channel_t *c = vChannels.at(i); 455 if (c == NULL) 456 continue; 457 458 c->nSamples = 0; 459 c->nCapacity = 0; 460 if (c->vSamples != NULL) 461 { 462 lsp_free(c->vSamples); 463 c->vSamples = NULL; 464 } 465 } 466 467 query_draw(); 468 return STATUS_OK; 469 } 470 render_channel(ISurface * s,channel_t * c,ssize_t y,ssize_t w,ssize_t h)471 void LSPAudioFile::render_channel(ISurface *s, channel_t *c, ssize_t y, ssize_t w, ssize_t h)//, const Color &fill, const Color &wire) 472 { 473 if ((c->vSamples == NULL) || (c->nSamples <= 0) || (w <= 0)) 474 return; 475 476 // Prepare palette 477 Color color(c->sColor); 478 Color line_col(c->sLineColor); 479 Color fade_col(c->sFadeColor); 480 color.scale_lightness(brightness()); 481 line_col.scale_lightness(brightness()); 482 fade_col.scale_lightness(brightness()); 483 484 // Start and end points 485 vDecimY[0] = 0.0f; 486 vDecimY[w+1] = 0.0f; 487 float *dst = &vDecimY[1]; 488 const float *src= c->vSamples; 489 size_t width = w; 490 float k = float(c->nSamples) / float(width); 491 492 // Perform decimation 493 if (c->nSamples == width) // 1:1 copy 494 dsp::copy(dst, src, width); 495 else if (c->nSamples < width) // Extension 496 { 497 for (size_t i=0; i<width; ++i) 498 *(dst++) = src[size_t(i*k)]; 499 } 500 else // Decimation 501 { 502 size_t x1 = 0; 503 504 for (size_t i=0; i<width;) 505 { 506 // Calculate the second coordinate 507 size_t x2 = (++i) * k; 508 if (x2 >= c->nSamples) 509 x2 = c->nSamples-1; 510 511 // Find the maximum value between x1 and x2 512 *dst = src[x1]; 513 while ((++x1) < x2) 514 if ((*dst) < src[x2]) 515 *dst = src[x2]; 516 ++dst; 517 x1 = x2; // remember the new value of x1 518 } 519 } 520 521 // Apply transformations 522 for (size_t i=0; i<size_t(w+2); ++i) 523 vDecimY[i] = y + vDecimY[i] * h; 524 525 // Draw 526 s->draw_poly(vDecimX, vDecimY, w+2, 1.0f, color, line_col); 527 528 // What's with fade-in 529 if (c->nFadeIn > 0) 530 { 531 Color fill(c->sFadeColor); 532 fill.alpha(1.0f - (1.0f - fill.alpha()) * 0.5f); 533 vDecimY[0] = 0.0f; 534 vDecimY[1] = c->nFadeIn * k; 535 vDecimY[2] = 0.0f; 536 vDecimY[3] = y; 537 vDecimY[4] = y + h; 538 vDecimY[5] = y + h; 539 s->draw_poly(&vDecimY[0], &vDecimY[3], 3, 1.0f, fill, fade_col); 540 } 541 if (c->nFadeOut > 0) 542 { 543 Color fill(c->sFadeColor); 544 fill.alpha(1.0f - (1.0f - fill.alpha()) * 0.5f); 545 vDecimY[0] = w; 546 vDecimY[1] = w - c->nFadeOut * k; 547 vDecimY[2] = w; 548 vDecimY[3] = y; 549 vDecimY[4] = y + h; 550 vDecimY[5] = y + h; 551 s->draw_poly(&vDecimY[0], &vDecimY[3], 3, 1.0f, fill, fade_col); 552 } 553 } 554 render_graph(ISurface * s,ssize_t w,ssize_t h)555 ISurface *LSPAudioFile::render_graph(ISurface *s, ssize_t w, ssize_t h) 556 { 557 size_t channels = vChannels.size(); 558 559 if (channels <= 0) 560 { 561 if (pGraph != NULL) 562 { 563 pGraph->destroy(); 564 delete pGraph; 565 pGraph = NULL; 566 } 567 } 568 // Check surface 569 if (pGraph != NULL) 570 { 571 if ((w != ssize_t(pGraph->width())) || (h != ssize_t(pGraph->height()))) 572 { 573 pGraph->destroy(); 574 delete pGraph; 575 pGraph = NULL; 576 } 577 } 578 579 // Create new surface if needed 580 if (pGraph == NULL) 581 { 582 if (s == NULL) 583 return NULL; 584 pGraph = s->create(w, h); 585 if (pGraph == NULL) 586 return NULL; 587 } 588 589 // Prepare palette 590 Color color(sColor); 591 Color axis_color(sAxisColor); 592 color.scale_lightness(brightness()); 593 axis_color.scale_lightness(brightness()); 594 595 // Clear canvas 596 pGraph->clear(color); 597 float aa = pGraph->get_antialiasing(); 598 599 // Init decimation buffer 600 if (nStatus & AF_SHOW_DATA) 601 { 602 size_t sz_decim = ALIGN_SIZE(w+2, 16); // 2 additional points at start and end 603 if (nDecimSize < sz_decim) 604 { 605 // Try to allocate memory 606 float *ptr = lsp_trealloc(float, vDecimX, sz_decim * 2); 607 if (ptr == NULL) 608 return pGraph; 609 610 // Store new pointers 611 vDecimX = ptr; 612 vDecimY = &ptr[sz_decim]; 613 nDecimSize = sz_decim; 614 } 615 616 // Initialize decimation buffer 617 vDecimX[0] = -1.0f; 618 for (ssize_t i=0; i<=w; ++i) 619 vDecimX[i+1] = float(i); 620 621 // Calculate number of pairs 622 size_t pairs = (channels + 1) >> 1;; 623 float delta = float(h)/float(pairs); 624 625 for (size_t i=0, ci=0; i<pairs; ++i) 626 { 627 ssize_t ys = i * delta, ye = (i + 1) * delta; 628 ssize_t yc = (ye + ys) >> 1; 629 630 pGraph->set_antialiasing(true); 631 channel_t *c = vChannels.at(ci); 632 if (c != NULL) 633 render_channel(pGraph, c, yc, w, ys - yc); //, fill, c->sColor); 634 635 if ((++ci) >= channels) 636 ci--; 637 c = vChannels.at(ci); 638 if (c != NULL) 639 render_channel(pGraph, c, yc, w, ye - yc); //, fill, c->sColor); 640 ++ci; 641 642 pGraph->set_antialiasing(false); 643 pGraph->line(0.0f, yc, w, yc, 1.0f, axis_color); 644 } 645 } 646 647 // Draw file name 648 if ((nStatus & AF_SHOW_FNAME) && (sFileName.length() > 0)) 649 { 650 ssize_t index1 = sFileName.rindex_of('/') + 1; 651 ssize_t index2 = sFileName.rindex_of('\\') + 1; 652 if (index1 < index2) 653 index1 = index2; 654 if ((index1 < 0) || (index1 >= ssize_t(sFileName.length()))) 655 index1 = 0; 656 657 font_parameters_t fp; 658 text_parameters_t tp; 659 660 sFont.get_parameters(pGraph, &fp); 661 sFont.get_text_parameters(pGraph, &tp, &sFileName, index1); 662 663 Color cl(color, 0.25f); 664 size_t bw = 4; 665 pGraph->set_antialiasing(true); 666 pGraph->fill_round_rect(0, h - bw - fp.Height, tp.Width + bw * 2, fp.Height + bw, bw, SURFMASK_ALL_CORNER, cl); 667 pGraph->set_antialiasing(false); 668 sFont.draw(pGraph, bw - tp.XBearing, h - bw*0.5f - fp.Descent, &sFileName, index1); 669 } 670 671 if (nStatus & AF_SHOW_HINT) 672 { 673 LSPString hint; 674 sHint.format(&hint); 675 676 if (!hint.is_empty()) 677 { 678 font_parameters_t fp; 679 text_parameters_t tp; 680 681 pGraph->set_antialiasing(false); 682 sHintFont.get_parameters(pGraph, &fp); 683 sHintFont.get_text_parameters(pGraph, &tp, &hint); 684 685 sHintFont.draw(pGraph, (w - tp.Width) * 0.5f, (h - fp.Height) * 0.5f + fp.Ascent, &hint); 686 } 687 } 688 689 pGraph->set_antialiasing(aa); 690 691 return pGraph; 692 } 693 draw(ISurface * s)694 void LSPAudioFile::draw(ISurface *s) 695 { 696 // Determine left and top coordinates 697 ssize_t bs = nBorder + nRadius * M_SQRT2 * 0.5f; 698 ssize_t bl = sPadding.left(); 699 ssize_t bt = sPadding.top(); 700 ssize_t bw = sSize.nWidth - sPadding.horizontal(); 701 ssize_t bh = sSize.nHeight - sPadding.vertical(); 702 ssize_t rl = bl + bs; 703 ssize_t rt = bt + bs; 704 ssize_t gw = bw - bs*2; 705 ssize_t gh = bh - bs*2; 706 707 ssize_t xbw = nBorder; 708 709 // Prepare palette 710 Color bg_color(sBgColor); 711 Color color(sColor); 712 color.scale_lightness(brightness()); 713 714 // Draw background 715 s->fill_frame( 716 0, 0, sSize.nWidth, sSize.nHeight, 717 bl + xbw, bt + xbw, bw - xbw*2, bh - xbw*2, 718 bg_color); 719 720 s->fill_round_rect(bl, bt, bw, bh, nRadius, SURFMASK_ALL_CORNER, color); 721 722 // Draw main contents 723 if ((gw > 0) && (gh > 0)) 724 { 725 ISurface *cv = render_graph(s, gw, gh); 726 if (cv != NULL) 727 { 728 if (nStatus & AF_PRESSED) 729 s->draw(cv, rl + 1, rt + 1, float(gw - 2.0f) / gw, float(gh - 2.0f) / gh); 730 else 731 s->draw(cv, rl, rt); 732 } 733 } 734 735 // Draw the glass and the border 736 ISurface *cv = create_border_glass(s, &pGlass, bw, bh, nBorder + ((nStatus & AF_PRESSED) ? 1 : 0), nRadius, SURFMASK_ALL_CORNER, color); 737 if (cv != NULL) 738 s->draw(cv, bl, bt); 739 } 740 drop_glass()741 void LSPAudioFile::drop_glass() 742 { 743 if (pGlass != NULL) 744 { 745 pGlass->destroy(); 746 delete pGlass; 747 pGlass = NULL; 748 } 749 } 750 hide()751 bool LSPAudioFile::hide() 752 { 753 bool result = LSPWidget::hide(); 754 if (pGlass != NULL) 755 { 756 pGlass->destroy(); 757 delete pGlass; 758 pGlass = NULL; 759 } 760 761 if (pGraph != NULL) 762 { 763 pGraph->destroy(); 764 delete pGraph; 765 pGraph = NULL; 766 } 767 768 return result; 769 } 770 set_radius(size_t radius)771 status_t LSPAudioFile::set_radius(size_t radius) 772 { 773 if (nRadius == radius) 774 return STATUS_OK; 775 nRadius = radius; 776 query_resize(); 777 return STATUS_OK; 778 } 779 set_border(size_t border)780 status_t LSPAudioFile::set_border(size_t border) 781 { 782 if (nBorder == border) 783 return STATUS_OK; 784 nBorder = border; 785 query_resize(); 786 return STATUS_OK; 787 } 788 set_path(const LSPString * path)789 status_t LSPAudioFile::set_path(const LSPString *path) 790 { 791 if (!sPath.set(path)) 792 return STATUS_NO_MEM; 793 return (sDialog.visible()) ? sDialog.set_path(&sPath) : STATUS_OK; 794 } 795 set_path(const char * path)796 status_t LSPAudioFile::set_path(const char *path) 797 { 798 if (!sPath.set_native(path)) 799 return STATUS_NO_MEM; 800 return (sDialog.visible()) ? sDialog.set_path(&sPath) : STATUS_OK; 801 } 802 set_show_data(bool value)803 void LSPAudioFile::set_show_data(bool value) 804 { 805 size_t flags = nStatus; 806 nStatus = (value) ? nStatus | AF_SHOW_DATA : nStatus & (~AF_SHOW_DATA); 807 if (nStatus == flags) 808 return; 809 query_draw(); 810 } 811 set_show_hint(bool value)812 void LSPAudioFile::set_show_hint(bool value) 813 { 814 size_t flags = nStatus; 815 nStatus = (value) ? nStatus | AF_SHOW_HINT : nStatus & (~AF_SHOW_HINT); 816 if (nStatus == flags) 817 return; 818 query_draw(); 819 } 820 set_show_file_name(bool value)821 void LSPAudioFile::set_show_file_name(bool value) 822 { 823 size_t flags = nStatus; 824 nStatus = (value) ? nStatus | AF_SHOW_FNAME : nStatus & (~AF_SHOW_FNAME); 825 if (nStatus == flags) 826 return; 827 query_draw(); 828 } 829 size_request(size_request_t * r)830 void LSPAudioFile::size_request(size_request_t *r) 831 { 832 size_t nc = vChannels.size(); 833 nc = (nc + 1) & (~1); // Round up to 2 834 835 ssize_t bs = nBorder + nRadius * M_SQRT2 * 0.5f; 836 r->nMinWidth = 16; 837 r->nMinHeight = nc * 16; 838 if (r->nMinHeight < 16) 839 r->nMinHeight = 16; 840 r->nMaxWidth = -1; 841 r->nMaxHeight = -1; 842 843 // Add external size constraints 844 sConstraints.apply(r); 845 846 // Add padding and size 847 r->nMinWidth += (bs << 1) + sPadding.horizontal(); 848 r->nMinHeight += (bs << 1) + sPadding.vertical(); 849 if (r->nMaxWidth >= 0) 850 r->nMaxWidth += (bs << 1) + sPadding.horizontal(); 851 if (r->nMaxHeight >= 0) 852 r->nMaxHeight += (bs << 1) + sPadding.vertical(); 853 } 854 check_mouse_over(ssize_t x,ssize_t y)855 bool LSPAudioFile::check_mouse_over(ssize_t x, ssize_t y) 856 { 857 x -= sSize.nLeft; 858 y -= sSize.nTop; 859 860 if ((x < ssize_t(sPadding.left())) || (x > ssize_t(sSize.nWidth -sPadding.right()))) 861 return false; 862 if ((y < ssize_t(sPadding.top())) || (y > ssize_t(sSize.nHeight - size_t(sPadding.bottom())))) 863 return false; 864 865 // Check special case: corners 866 if (x < ssize_t(nRadius)) 867 { 868 if (y < ssize_t(nRadius)) 869 { 870 float dx = nRadius - x, dy = nRadius - y; 871 return (dx*dx + dy*dy) <= nRadius * nRadius; 872 } 873 else if (y > ssize_t(sSize.nHeight - nRadius)) 874 { 875 float dx = nRadius - x, dy = y - sSize.nHeight + nRadius; 876 return (dx*dx + dy*dy) <= nRadius * nRadius; 877 } 878 } 879 else if (x > ssize_t(sSize.nWidth + nRadius)) 880 { 881 if (y < ssize_t(nRadius)) 882 { 883 float dx = x - sSize.nWidth + nRadius, dy = nRadius - y; 884 return (dx*dx + dy*dy) <= nRadius * nRadius; 885 } 886 else if (y > ssize_t(sSize.nHeight - nRadius)) 887 { 888 float dx = x - sSize.nWidth + nRadius, dy = y - sSize.nHeight + nRadius; 889 return (dx*dx + dy*dy) <= nRadius * nRadius; 890 } 891 } 892 893 return true; 894 } 895 on_mouse_down(const ws_event_t * e)896 status_t LSPAudioFile::on_mouse_down(const ws_event_t *e) 897 { 898 nBMask |= (1 << e->nCode); 899 size_t flags = nStatus; 900 nStatus = (nBMask == (1 << MCB_LEFT)) && (check_mouse_over(e->nLeft, e->nTop)) ? nStatus | AF_PRESSED : nStatus & (~AF_PRESSED); 901 if (flags != nStatus) 902 { 903 drop_glass(); 904 query_draw(); 905 } 906 return STATUS_OK; 907 } 908 slot_on_dialog_submit(LSPWidget * sender,void * ptr,void * data)909 status_t LSPAudioFile::slot_on_dialog_submit(LSPWidget *sender, void *ptr, void *data) 910 { 911 // Cast widget 912 LSPAudioFile *_this = widget_ptrcast<LSPAudioFile>(ptr); 913 if (_this == NULL) 914 return STATUS_BAD_STATE; 915 916 // Get selected file 917 status_t result = _this->sDialog.get_selected_file(&_this->sFileName); 918 if (result != STATUS_OK) 919 return result; 920 921 // OK, file name was submitted 922 _this->query_draw(); 923 return _this->sSlots.execute(LSPSLOT_SUBMIT, _this, data); 924 } 925 slot_on_dialog_close(LSPWidget * sender,void * ptr,void * data)926 status_t LSPAudioFile::slot_on_dialog_close(LSPWidget *sender, void *ptr, void *data) 927 { 928 // Cast widget 929 LSPAudioFile *_this = widget_ptrcast<LSPAudioFile>(ptr); 930 if (_this == NULL) 931 return STATUS_BAD_STATE; 932 933 // Remember the last path used 934 _this->sDialog.get_path(&_this->sPath); 935 return _this->sSlots.execute(LSPSLOT_CLOSE, _this, data); 936 } 937 on_mouse_up(const ws_event_t * e)938 status_t LSPAudioFile::on_mouse_up(const ws_event_t *e) 939 { 940 bool pressed = (nBMask == (1 << MCB_LEFT)) && (check_mouse_over(e->nLeft, e->nTop)); 941 942 size_t flags = nStatus; 943 nBMask &= ~(1 << e->nCode); 944 if (nBMask == 0) 945 nStatus &= ~AF_PRESSED; 946 947 if (flags != nStatus) 948 { 949 drop_glass(); 950 query_draw(); 951 } 952 953 if (nBMask == 0) 954 { 955 if ((pressed) && (e->nCode == MCB_LEFT)) 956 { 957 status_t result = sSlots.execute(LSPSLOT_ACTIVATE, NULL); 958 if (result == STATUS_OK) 959 { 960 sDialog.set_path(&sPath); 961 sDialog.show(this); 962 } 963 } 964 else if (e->nCode == MCB_RIGHT) 965 { 966 if (pPopup != NULL) 967 pPopup->show(this, e); 968 } 969 } 970 971 return STATUS_OK; 972 } 973 on_mouse_move(const ws_event_t * e)974 status_t LSPAudioFile::on_mouse_move(const ws_event_t *e) 975 { 976 // bool pressed = bPressed; 977 // bPressed = (nBMask == (1 << MCB_LEFT)) && (check_mouse_over(e->nLeft, e->nTop)); 978 979 size_t flags = nStatus; 980 nStatus = (nBMask == (1 << MCB_LEFT)) && (check_mouse_over(e->nLeft, e->nTop)) ? nStatus | AF_PRESSED : nStatus & (~AF_PRESSED); 981 if (flags != nStatus) 982 { 983 drop_glass(); 984 query_draw(); 985 } 986 987 return STATUS_OK; 988 } 989 on_mouse_dbl_click(const ws_event_t * e)990 status_t LSPAudioFile::on_mouse_dbl_click(const ws_event_t *e) 991 { 992 if (e->nCode != MCB_RIGHT) 993 return STATUS_OK; 994 995 sFileName.truncate(); 996 lsp_trace("mouse double click"); 997 // nFileStatus = STATUS_UNSPECIFIED; 998 return sSlots.execute(LSPSLOT_SUBMIT, NULL); 999 } 1000 on_submit()1001 status_t LSPAudioFile::on_submit() 1002 { 1003 return STATUS_OK; 1004 } 1005 on_close()1006 status_t LSPAudioFile::on_close() 1007 { 1008 return STATUS_OK; 1009 } 1010 on_activate()1011 status_t LSPAudioFile::on_activate() 1012 { 1013 return STATUS_OK; 1014 } 1015 on_drag_request(const ws_event_t * e,const char * const * ctype)1016 status_t LSPAudioFile::on_drag_request(const ws_event_t *e, const char * const *ctype) 1017 { 1018 lsp_trace("Accepting drag"); 1019 ssize_t idx = pSink->select_mime_type(ctype); 1020 if (idx >= 0) 1021 pDisplay->accept_drag(pSink, DRAG_COPY, true, &sSize); 1022 else 1023 pDisplay->reject_drag(); 1024 return STATUS_OK; 1025 } 1026 1027 } /* namespace tk */ 1028 } /* namespace lsp */ 1029