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