/*
 * Copyright (C) 2026 Linux Studio Plugins Project <https://lsp-plug.in/>
 *           (C) 2026 Vladimir Sadovnikov <sadko4u@gmail.com>
 *
 * This file is part of lsp-tk-lib
 * Created on: 28 сент. 2020 г.
 *
 * lsp-tk-lib is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * lsp-tk-lib is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with lsp-tk-lib. If not, see <https://www.gnu.org/licenses/>.
 */

#include <lsp-plug.in/tk/tk.h>
#include <lsp-plug.in/tk/helpers/draw.h>
#include <lsp-plug.in/common/debug.h>
#include <lsp-plug.in/common/alloc.h>
#include <lsp-plug.in/stdlib/math.h>
#include <private/tk/style/BuiltinStyle.h>

namespace lsp
{
    namespace tk
    {
        namespace style
        {
            static const char *label_text_color[] =
            {
                "label.0.text.color",
                "label.1.text.color",
                "label.2.text.color",
                "label.3.text.color",
                "label.4.text.color"
            };

            static const char *label_layout[] =
            {
                "label.0.layout",
                "label.1.layout",
                "label.2.layout",
                "label.3.layout",
                "label.4.layout"
            };

            static const char *label_text_layout[] =
            {
                "label.0.text.layout",
                "label.1.text.layout",
                "label.2.text.layout",
                "label.3.text.layout",
                "label.4.text.layout"
            };

            static const char *label_visible[] =
            {
                "label.0.visible",
                "label.1.visible",
                "label.2.visible",
                "label.3.visible",
                "label.4.visible"
            };

            LSP_TK_STYLE_IMPL_BEGIN(AudioSample, WidgetContainer)
                // Bind
                sWaveBorder.bind("wave.border", this);
                sFadeInBorder.bind("fade_in.border", this);
                sFadeOutBorder.bind("fade_out.border", this);
                sStretchBorder.bind("stretch.border", this);
                sLoopBorder.bind("loop.border", this);
                sPlayBorder.bind("play.border", this);
                sLineWidth.bind("line.width", this);
                sMaxAmplitude.bind("amplitude.max", this);
                sLineColor.bind("line.color", this);
                sConstraints.bind("size.constraints", this);
                sSGroups.bind("stereo_groups", this);
                sMainTextLayout.bind("main.text.layout", this);
                sMainFont.bind("main.font", this);
                sMainColor.bind("main.color", this);
                sMainVisibility.bind("main.visibility", this);
                sChannelVisibility.bind("channel.visibility", this);
                sLabelFont.bind("label.font", this);
                sLabelBgColor.bind("label.bg.color", this);
                sLabelRadius.bind("label.radius", this);
                sBorder.bind("border.size", this);
                sBorderRadius.bind("border.radius", this);
                sBorderFlat.bind("border.flat", this);
                sGlass.bind("glass", this);
                sColor.bind("color", this);
                sBorderColor.bind("border.color", this);
                sGlassColor.bind("glass.color", this);
                sStretchColor.bind("stretch.color", this);
                sLoopColor.bind("loop.color", this);
                sPlayColor.bind("play.color", this);
                sStretchBorderColor.bind("stretch.border.color", this);
                sLoopBorderColor.bind("loop.border.color", this);
                sIPadding.bind("ipadding", this);

                for (size_t i=0; i<LABELS; ++i)
                {
                    sLabelColor[i].bind(label_text_color[i], this);
                    sLabelLayout[i].bind(label_layout[i], this);
                    sLabelTextLayout[i].bind(label_text_layout[i], this);
                    sLabelVisibility[i].bind(label_visible[i], this);
                }

                // Configure
                sWaveBorder.set(1);
                sFadeInBorder.set(1);
                sFadeOutBorder.set(1);
                sStretchBorder.set(1);
                sLoopBorder.set(1);
                sPlayBorder.set(2);
                sLineWidth.set(1);
                sMaxAmplitude.set(1.0f);
                sLineColor.set("#ffffff");
                sConstraints.set_all(-1);
                sActive.set(false);
                sSGroups.set(false);

                sMainTextLayout.set(0.0f, 0.0f);
                sMainFont.set_size(16.0f);
                sMainFont.set_bold(true);
                sMainColor.set("#00ff00");
                sMainVisibility.set(false);
                sChannelVisibility.set(true);
                sLabelFont.set_size(10.0f);
                sLabelBgColor.set("#44000000");
                sLabelRadius.set(4);

                sBorder.set(4);
                sBorderRadius.set(12);
                sBorderFlat.set(false);
                sGlass.set(true);
                sColor.set("#000000");
                sBorderColor.set("#000000");
                sGlassColor.set("#ffffff");
                sStretchColor.set("#8800ff00");
                sLoopColor.set("#8800ffff");
                sPlayColor.set("#ffffff");
                sStretchBorderColor.set("#00ff00");
                sLoopBorderColor.set("#00ffff");
                sIPadding.set(1);

                for (size_t i=0; i<LABELS; ++i)
                {
                    sLabelColor[i].set("#ffffff");
                    sLabelLayout[i].set(0.0f, 0.0f, 0.0f, 0.0f);
                    sLabelTextLayout[i].set(0.0f, 0.0f);
                    sLabelVisibility[i].set(false);
                }

                // Override
                sActive.override();
                sMainFont.override();
                sLabelFont.override();
            LSP_TK_STYLE_IMPL_END
            LSP_TK_BUILTIN_STYLE(AudioSample, "AudioSample", "root");
        } /* namespace style */

        template <class T>
        static inline bool arrays_differ(const lltl::parray<T> & a, const lltl::parray<T> & b)
        {
            if (a.size() != b.size())
                return true;
            for (size_t i=0, n=a.size(); i<n; ++i)
                if (a.uget(i) != b.uget(i))
                    return true;
            return false;
        }

        const w_class_t AudioSample::metadata           = { "AudioSample", &WidgetContainer::metadata };

        AudioSample::AudioSample(Display *dpy):
            WidgetContainer(dpy),
            vChannels(&sProperties, &sIListener),
            vEnvelopes(&sProperties, &sIListener),
            sWaveBorder(&sProperties),
            sFadeInBorder(&sProperties),
            sFadeOutBorder(&sProperties),
            sStretchBorder(&sProperties),
            sLoopBorder(&sProperties),
            sPlayBorder(&sProperties),
            sLineWidth(&sProperties),
            sMaxAmplitude(&sProperties),
            sLineColor(&sProperties),
            sConstraints(&sProperties),
            sSGroups(&sProperties),
            sMainText(&sProperties),
            sMainTextLayout(&sProperties),
            sMainFont(&sProperties),
            sMainColor(&sProperties),
            sMainVisibility(&sProperties),
            sChannelVisibility(&sProperties),
            sLabelFont(&sProperties),
            sLabelBgColor(&sProperties),
            sLabelRadius(&sProperties),
            sBorder(&sProperties),
            sBorderRadius(&sProperties),
            sBorderFlat(&sProperties),
            sGlass(&sProperties),
            sColor(&sProperties),
            sBorderColor(&sProperties),
            sGlassColor(&sProperties),
            sStretchColor(&sProperties),
            sLoopColor(&sProperties),
            sPlayColor(&sProperties),
            sStretchBorderColor(&sProperties),
            sLoopBorderColor(&sProperties),
            sIPadding(&sProperties),
            sPopup(&sProperties)
        {
            for (size_t i=0; i<LABELS; ++i)
            {
                sLabel[i].listener(&sProperties);
                sLabelColor[i].listener(&sProperties);
                sLabelLayout[i].listener(&sProperties);
                sLabelTextLayout[i].listener(&sProperties);
                sLabelVisibility[i].listener(&sProperties);
            }

            nBMask              = 0;
            nXFlags             = 0;

            sGraph.nLeft        = 0;
            sGraph.nTop         = 0;
            sGraph.nWidth       = 0;
            sGraph.nHeight      = 0;

            pGlass              = NULL;

            pClass              = &metadata;
        }

        AudioSample::~AudioSample()
        {
            nFlags     |= FINALIZED;
            do_destroy();
        }

        void AudioSample::destroy()
        {
            nFlags     |= FINALIZED;
            WidgetContainer::destroy();
            do_destroy();
        }

        void AudioSample::drop_glass()
        {
            if (pGlass != NULL)
            {
                pGlass->destroy();
                delete pGlass;
                pGlass      = NULL;
            }
        }

        void AudioSample::do_destroy()
        {
            // Unlink all items
            for (size_t i=0, n=vChannels.size(); i<n; ++i)
            {
                AudioChannel *item   = vChannels.get(i);
                if (item != NULL)
                    unlink_widget(item);
            }

            for (size_t i=0, n=vEnvelopes.size(); i<n; ++i)
            {
                AudioEnvelope *item  = vEnvelopes.get(i);
                if (item != NULL)
                    unlink_widget(item);
            }

            // Drop glass
            drop_glass();

            // Flush containers
            vChannels.flush();
            vEnvelopes.flush();
            vVisibleChannels.flush();
            vVisibleEnvelopes.flush();
        }

        status_t AudioSample::init()
        {
            status_t res = WidgetContainer::init();
            if (res != STATUS_OK)
                return res;

            // Init listener
            sIListener.bind_all(this, on_add_item, on_remove_item);

            // Bind properties
            sWaveBorder.bind("wave.border", &sStyle);
            sFadeInBorder.bind("fade_in.border", &sStyle);
            sFadeOutBorder.bind("fade_out.border", &sStyle);
            sStretchBorder.bind("stretch.border", &sStyle);
            sLoopBorder.bind("loop.border", &sStyle);
            sPlayBorder.bind("play.border", &sStyle);
            sLineWidth.bind("line.width", &sStyle);
            sMaxAmplitude.bind("amplitude.max", &sStyle);
            sLineColor.bind("line.color", &sStyle);
            sConstraints.bind("size.constraints", &sStyle);
            sSGroups.bind("stereo_groups", &sStyle);
            sMainText.bind(&sStyle, pDisplay->dictionary());
            sMainTextLayout.bind("main.text.layout", &sStyle);
            sMainFont.bind("main.font", &sStyle);
            sMainColor.bind("main.color", &sStyle);
            sMainVisibility.bind("main.visibility", &sStyle);
            sChannelVisibility.bind("channel.visibility", &sStyle);
            sLabelFont.bind("label.font", &sStyle);
            sLabelBgColor.bind("label.bg.color", &sStyle);
            sLabelRadius.bind("label.radius", &sStyle);
            sBorder.bind("border.size", &sStyle);
            sBorderRadius.bind("border.radius", &sStyle);
            sBorderFlat.bind("border.flat", &sStyle);
            sGlass.bind("glass", &sStyle);
            sColor.bind("color", &sStyle);
            sStretchColor.bind("stretch.color", &sStyle);
            sLoopColor.bind("loop.color", &sStyle);
            sPlayColor.bind("play.color", &sStyle);
            sStretchBorderColor.bind("stretch.border.color", &sStyle);
            sLoopBorderColor.bind("loop.border.color", &sStyle);
            sBorderColor.bind("border.color", &sStyle);
            sGlassColor.bind("glass.color", &sStyle);
            sIPadding.bind("ipadding", &sStyle);

            for (size_t i=0; i<LABELS; ++i)
            {
                LSPString id;

                sLabel[i].bind(&sStyle, pDisplay->dictionary());
                sLabelColor[i].bind(style::label_text_color[i], &sStyle);
                sLabelLayout[i].bind(style::label_layout[i], &sStyle);
                sLabelTextLayout[i].bind(style::label_text_layout[i], &sStyle);
                sLabelVisibility[i].bind(style::label_visible[i], &sStyle);
            }

            // Add slots
            handler_id_t id = sSlots.add(SLOT_SUBMIT, slot_on_submit, self());

            return (id >= 0) ? STATUS_OK : -id;
        }

        void AudioSample::property_changed(Property *prop)
        {
            WidgetContainer::property_changed(prop);

            if (prop->one_of(vChannels, vEnvelopes))
                query_resize();

            if (prop->one_of(sFadeInBorder, sFadeOutBorder, sStretchBorder, sLoopBorder, sPlayBorder, sLineWidth, sMaxAmplitude))
                query_draw();

            if ((sMainText.is(prop)) && (sMainVisibility.get()))
                query_draw();
            if ((sMainTextLayout.is(prop)) && (sMainVisibility.get()))
                query_draw();
            if ((sMainFont.is(prop)) && (sMainVisibility.get()))
                query_draw();

            if (prop->one_of(sWaveBorder, sConstraints, sSGroups, sBorder, sBorderRadius, sIPadding))
                query_resize();

            if (prop->one_of(sMainVisibility, sChannelVisibility, sLabelFont, sLabelRadius, sBorderFlat))
                query_draw();

            if (prop->one_of(
                sColor, sBorderColor, sGlassColor, sStretchColor, sLoopColor, sPlayColor,
                sStretchBorderColor, sLoopBorderColor, sLineColor, sLabelBgColor))
                query_draw();

            for (size_t i=0; i<LABELS; ++i)
            {
                if (prop->one_of(sLabelColor[i], sLabelLayout[i], sLabelTextLayout[i], sLabelVisibility[i]))
                    query_draw();
            }
        }

        void AudioSample::size_request(ws::size_limit_t *r)
        {
            float scaling       = lsp_max(0.0f, sScaling.get());
            float fscaling      = lsp_max(0.0f, scaling * sFontScaling.get());
            bool sgroups        = sSGroups.get();

            // Estimate the size of area for drawing samples
            ws::size_limit_t sl;
            r->nMinWidth        = 0;
            r->nMinHeight       = 0;
            r->nMaxWidth        = -1;
            r->nMaxHeight       = -1;
            r->nPreWidth        = -1;
            r->nPreHeight       = -1;

            if (sMainVisibility.get())
            {
                ws::text_parameters_t tp;
                LSPString text;
                sMainText.format(&text);
                sMainFont.get_multitext_parameters(pDisplay, &tp, fscaling, &text);
                r->nMinWidth            = tp.Width;
                r->nMinHeight           = tp.Height;
            }
            else
            {
                // Estimate minimum width and height among samples
                lltl::parray<AudioChannel> channels;
                get_visible_channels(&channels);

                for (size_t i=0, n=channels.size(); i<n; ++i)
                {
                    AudioChannel *c     = channels.uget(i);
                    c->constraints()->compute(&sl, scaling);
                    ssize_t h           = lsp_max(0, sl.nMinHeight);
                    r->nMinWidth        = lsp_max(r->nMinWidth, sl.nMinWidth);
                    r->nMinHeight      += (sgroups) ? (h >> 1) : h;
                }

                // Estimate minimum width and height among envelopes
                lltl::parray<AudioEnvelope> envelopes;
                get_visible_envelopes(&envelopes);

                for (size_t i=0, n=envelopes.size(); i<n; ++i)
                {
                    AudioEnvelope *e    = envelopes.uget(i);

                    padding_t padding;
                    ssize_t width       = 0;
                    ssize_t height      = 0;

                    e->ipadding()->compute(&padding, scaling);
                    e->get_min_area_size(&width, &height, scaling);

                    r->nMinWidth        = lsp_max(r->nMinWidth, width + ssize_t(padding.nLeft + padding.nRight));
                    r->nMinHeight       = lsp_max(r->nMinHeight, height + ssize_t(padding.nTop + padding.nBottom));
                }
            }

            // Add padding to the rectangle
            sIPadding.add(r, scaling);

            // Compute additional space around the sample area
            float xr        = lsp_max(0.0f, sBorderRadius.get() * scaling); // external radius
            float bw        = lsp_max(0.0f, sBorder.get() * scaling);       // border size
            float ir        = lsp_max(0.0f, xr - bw);                       // internal radius
            float bp        = (1.0f - M_SQRT1_2) * ir;                      // padding to not to cross internal radius
            ssize_t padding = ceilf(bp + bw);
            ssize_t wh      = lsp_max(padding * 2, xr * 2);                 // minimum possible width and height

            r->nMinWidth    = lsp_max(r->nMinWidth  + padding * 2, wh);
            r->nMinHeight   = lsp_max(r->nMinHeight + padding * 2, wh);

            // Apply size constraints
            sConstraints.apply(r, scaling);
        }

        bool AudioSample::realize(const ws::rectangle_t *r)
        {
            // Call parent class to realize
            bool needs_redraw = WidgetContainer::realize(r);

            lltl::parray<AudioChannel> channels;
            lltl::parray<AudioEnvelope> envelopes;
            get_visible_channels(&channels);
            get_visible_envelopes(&envelopes);

            // Compute the size of area
            float scaling   = lsp_max(0.0f, sScaling.get());
            float xr        = lsp_max(0.0f, ceilf(sBorderRadius.get() * scaling));  // external radius
            float bw        = lsp_max(0.0f, ceilf(sBorder.get() * scaling));        // border size
            float ir        = lsp_max(0.0f, xr - bw);                               // internal radius
            ssize_t padding = ceilf((1.0f - M_SQRT1_2) * ir + bw);                  // padding of internal area

            sGraph.nLeft    = r->nLeft   + padding;
            sGraph.nTop     = r->nTop    + padding;
            sGraph.nWidth   = r->nWidth  - padding*2;
            sGraph.nHeight  = r->nHeight - padding*2;

            sIPadding.enter(&sGraph, scaling);
            if (arrays_differ(vVisibleChannels, channels))
            {
                vVisibleChannels.swap(&channels);
                needs_redraw    = true;
            }
            if (arrays_differ(vVisibleEnvelopes, envelopes))
            {
                vVisibleEnvelopes.swap(&envelopes);
                needs_redraw    = true;
            }

            return needs_redraw;
        }

        void AudioSample::draw_channel1(const ws::rectangle_t *r, ws::ISurface *s, AudioChannel *c, size_t samples, float max_amplitude)
        {
            // Check limits
            if ((samples <= 0) || (r->nWidth <= 1) || (r->nHeight <= 1))
                return;

            float scaling       = lsp_max(0.0f, sScaling.get());
            float bright        = select_brightness();

            // Init decimation buffer
            ssize_t n_draw      = lsp_min(ssize_t(samples), r->nWidth);
            size_t n_points     = n_draw + 2;
            size_t n_decim      = lsp::align_size(n_points, 16); // 2 additional points at start and end

            // Try to allocate memory
            uint8_t *data       = NULL;
            float *x            = lsp::alloc_aligned<float>(data, n_decim * 2);
            float *y            = &x[n_decim];
            if (x == NULL)
                return;
            lsp_finally { lsp::free_aligned(data); };

            bool aa             = s->set_antialiasing(true);
            lsp_finally { s->set_antialiasing(aa); };

            // Form the x and y values
            FloatArray *vsamp   = &c->vSamples;
            float border        = (sWaveBorder.get() > 0) ? lsp_max(1.0f, sWaveBorder.get() * scaling) : 0.0f;
            float dx            = lsp_max(1.0f, float(r->nWidth) / float(samples));
            float kx            = lsp_max(1.0f, float(samples) / float(r->nWidth));
            float ky            = -0.5f * (r->nHeight - border) / max_amplitude;
            float sy            = r->nTop + r->nHeight * 0.5f;

            x[0]                = -1.0f;
            y[0]                = sy;
            x[n_points-1]       = r->nWidth;
            y[n_points-1]       = sy;

            for (ssize_t i=1; i <= n_draw; ++i)
            {
                ssize_t xx          = i - 1;
                x[i]                = xx * dx;
                y[i]                = sy + ky * vsamp->get(ssize_t(xx * kx));
            }

            // Draw the poly
            lsp::Color fill(c->sColor);
            lsp::Color wire(c->sWaveBorderColor);
            fill.scale_lch_luminance(bright);
            wire.scale_lch_luminance(bright);

            s->draw_poly(fill, wire, border, x, y, n_points);
        }

        void AudioSample::draw_fades1(const ws::rectangle_t *r, ws::ISurface *s, AudioChannel *c, ssize_t *head_cut, ssize_t *tail_cut, size_t samples)
        {
            // Check limits
            if ((samples <= 0) || (r->nWidth <= 1) || (r->nHeight <= 1))
                return;

            float scaling       = lsp_max(0.0f, sScaling.get());
            float bright        = select_brightness();

            float x[6], y[6];

            bool aa             = s->set_antialiasing(true);
            lsp_finally { s->set_antialiasing(aa); };

            float fi_border     = (sFadeInBorder.get() > 0)  ? lsp_max(1.0f, sFadeInBorder.get()  * scaling) : 0.0f;
            float fo_border     = (sFadeOutBorder.get() > 0) ? lsp_max(1.0f, sFadeOutBorder.get() * scaling) : 0.0f;

            // Draw fade in
            if ((c->sFadeIn.get() > 0) || (c->sHeadCut.get() > 0))
            {
                float xx            = float(c->sFadeIn.get() * r->nWidth) / float(samples);

                // Form the arrays
                x[0]                = r->nLeft;
                x[1]                = xx;
                x[2]                = x[0];
                x[3]                = x[1];
                x[4]                = x[0];
                x[5]                = x[0];

                y[0]                = r->nTop;
                y[1]                = y[0];
                y[2]                = y[0] + (r->nHeight >> 1);
                y[3]                = y[0] + r->nHeight;
                y[4]                = y[3];
                y[5]                = y[0];

                // Apply head cut if set
                if (c->sHeadCut.get() > 0)
                {
                    lsp::Color cut(c->sHeadCutColor);
                    cut.scale_lch_luminance(bright);

                    float dx            = float(c->sHeadCut.get() * r->nWidth) / float(samples);
                    *head_cut           = lsp_max(*head_cut, ssize_t(dx));
                    s->fill_rect(cut, SURFMASK_NONE, 0.0f, r->nLeft, r->nTop, dx, r->nHeight);
                    for (size_t i=0,n=sizeof(x)/sizeof(x[0]); i<n; ++i)
                        x[i]               += dx;
                }

                // Draw fade
                lsp::Color fill(c->sFadeInColor);
                lsp::Color wire(c->sFadeInBorderColor);
                fill.scale_lch_luminance(bright);
                wire.scale_lch_luminance(bright);

                s->draw_poly(fill, wire, fi_border, x, y, 6);
            }

            // Draw fade out
            if ((c->sFadeOut.get() > 0) || (c->sTailCut.get() > 0))
            {
                float xx            = r->nLeft + r->nWidth - float(c->sFadeOut.get() * r->nWidth) / float(samples);

                // Form the arrays
                x[0]                = r->nLeft + r->nWidth;
                x[1]                = xx;
                x[2]                = x[0];
                x[3]                = x[1];
                x[4]                = x[0];
                x[5]                = x[0];

                y[0]                = r->nTop;
                y[1]                = y[0];
                y[2]                = y[0] + (r->nHeight >> 1);
                y[3]                = y[0] + r->nHeight;
                y[4]                = y[3];
                y[5]                = y[0];

                // Apply tail cut if set
                if (c->sTailCut.get() > 0)
                {
                    lsp::Color cut(c->sTailCutColor);
                    cut.scale_lch_luminance(bright);

                    float dx            = float(c->sTailCut.get() * r->nWidth) / float(samples);
                    *tail_cut           = lsp_max(*tail_cut, ssize_t(dx));
                    s->fill_rect(cut, SURFMASK_NONE, 0.0f, r->nLeft + r->nWidth - dx, r->nTop, dx, r->nHeight);
                    for (size_t i=0,n=sizeof(x)/sizeof(x[0]); i<n; ++i)
                        x[i]               -= dx;
                }

                // Draw fade
                lsp::Color fill(c->sFadeOutColor);
                lsp::Color wire(c->sFadeOutBorderColor);
                fill.scale_lch_luminance(bright);
                wire.scale_lch_luminance(bright);

                s->draw_poly(fill, wire, fo_border, x, y, 6);
            }
        }

        void AudioSample::draw_range(const ws::rectangle_t *r, ws::ISurface *s, AudioChannel *c, range_t *range, size_t samples)
        {
            // Check limits
            if ((samples <= 0) || (r->nWidth <= 1) || (r->nHeight <= 1))
                return;

            // Check that the value is enabled
            ssize_t begin   = range->begin->get();
            ssize_t end     = range->end->get();
            if ((begin < 0) && (end < 0))
                return;
            if (begin > end)
                return;

            float scaling       = lsp_max(0.0f, sScaling.get());
            float bright        = select_brightness();
            float border        = (range->border->get() > 0) ? lsp_max(1.0f, range->border->get() * scaling) : 0.0f;
            float xb            = r->nLeft + float(begin * r->nWidth) / float(samples);
            float xe            = r->nLeft + float(end * r->nWidth) / float(samples);

            // Draw the range
            lsp::Color fill(*range->color);
            lsp::Color wire(*range->border_color);
            fill.scale_lch_luminance(bright);
            wire.scale_lch_luminance(bright);

            bool aa             = s->set_antialiasing(true);
            lsp_finally { s->set_antialiasing(aa); };

            s->fill_rect(fill, SURFMASK_NONE, 0.0f, r->nLeft + xb, r->nTop, xe - xb, r->nHeight);
            if (border > 0)
            {
                s->line(wire, xb, r->nTop, xb, r->nTop + r->nHeight, border);
                s->line(wire, xe, r->nTop, xe, r->nTop + r->nHeight, border);
            }
        }

        void AudioSample::draw_play_position(const ws::rectangle_t *r, ws::ISurface *s, AudioChannel *c, size_t samples)
        {
            // Check limits
            if ((samples <= 0) || (r->nWidth <= 1) || (r->nHeight <= 1))
                return;
            ssize_t position = c->play_position()->get();
            ssize_t pborder  = sPlayBorder.get();
            if ((position < 0) || (pborder < 0))
                return;

            float scaling       = lsp_max(0.0f, sScaling.get());
            float bright        = select_brightness();
            float x             = float(r->nLeft + (position * r->nWidth) / samples);
            float border        = lsp_max(1.0f, pborder * scaling);

            lsp::Color wire(sPlayColor);
            wire.scale_lch_luminance(bright);

            bool aa             = s->set_antialiasing(true);
            lsp_finally { s->set_antialiasing(aa); };

            s->line(wire, x, r->nTop, x, r->nTop + r->nHeight, border);
        }

        void AudioSample::draw_channel2(const ws::rectangle_t *r, ws::ISurface *s, AudioChannel *c, size_t samples, bool down, float max_amplitude)
        {
            // Check limits
            if ((samples <= 0) || (r->nWidth <= 1) || (r->nHeight <= 1))
                return;

            float scaling       = lsp_max(0.0f, sScaling.get());
            float bright        = select_brightness();

            // Init decimation buffer
            ssize_t n_draw      = lsp_min(ssize_t(samples), r->nWidth);
            size_t n_points     = n_draw + 2;
            size_t n_decim      = lsp::align_size(n_points, 16); // 2 additional points at start and end

            // Try to allocate memory
            uint8_t *data       = NULL;
            float *x            = lsp::alloc_aligned<float>(data, n_decim * 2);
            float *y            = &x[n_decim];
            if (x == NULL)
                return;
            lsp_finally { lsp::free_aligned(data); };

            bool aa             = s->set_antialiasing(true);
            lsp_finally { s->set_antialiasing(aa); };

            // Form the x and y values for sample 1
            FloatArray *vsamp   = &c->vSamples;
            float border        = (sWaveBorder.get() > 0) ? lsp_max(1.0f, sWaveBorder.get() * scaling) : 0.0f;
            float dx            = lsp_max(1.0f, float(r->nWidth) / float(samples));
            float kx            = lsp_max(1.0f, float(samples) / float(r->nWidth));
            float ky            = ((down) ? 1.0f : -1.0f) * (r->nHeight - border) / max_amplitude;
            float sy            = (down) ? r->nTop : r->nTop + r->nHeight;

            x[0]                = -1.0f;
            y[0]                = sy;
            x[n_points-1]       = r->nWidth;
            y[n_points-1]       = sy;

            for (ssize_t i=1; i <= n_draw; ++i)
            {
                ssize_t xx          = i - 1;
                x[i]                = xx * dx;
                y[i]                = sy + ky * fabs(vsamp->get(ssize_t(xx * kx)));
            }

            // Draw the poly
            lsp::Color fill(c->sColor);
            lsp::Color wire(c->sWaveBorderColor);
            fill.scale_lch_luminance(bright);
            wire.scale_lch_luminance(bright);
            s->draw_poly(fill, wire, border, x, y, n_points);
        }

        void AudioSample::draw_fades2(const ws::rectangle_t *r, ws::ISurface *s, AudioChannel *c, ssize_t *head_cut, ssize_t *tail_cut, size_t samples, bool down)
        {
            // Check limits
            if ((samples <= 0) || (r->nWidth <= 1) || (r->nHeight <= 1))
                return;

            float scaling       = lsp_max(0.0f, sScaling.get());
            float bright        = select_brightness();

            float x[4], y[4];

            bool aa             = s->set_antialiasing(true);
            lsp_finally { s->set_antialiasing(aa); };

            float fi_border     = (sFadeInBorder.get()  > 0) ? lsp_max(1.0f, sFadeInBorder.get()  * scaling) : 0.0f;
            float fo_border     = (sFadeOutBorder.get() > 0) ? lsp_max(1.0f, sFadeOutBorder.get() * scaling) : 0.0f;

            // Draw fade in
            if ((c->sFadeIn.get() > 0) || (c->sHeadCut.get() > 0))
            {
                float xx            = float(c->sFadeIn.get() * r->nWidth) / float(samples);

                // Form the arrays
                x[0]                = r->nLeft;
                x[1]                = xx;
                x[2]                = x[0];
                x[3]                = x[0];

                y[0]                = (down) ? r->nTop : r->nTop + r->nHeight;
                y[1]                = (down) ? r->nTop + r->nHeight : r->nTop;
                y[2]                = y[1];
                y[3]                = y[0];

                // Apply head cut if set
                if (c->sHeadCut.get() > 0)
                {
                    lsp::Color cut(c->sHeadCutColor);
                    cut.scale_lch_luminance(bright);

                    float dx            = float(c->sHeadCut.get() * r->nWidth) / float(samples);
                    *head_cut           = lsp_max(*head_cut, ssize_t(dx));
                    s->fill_rect(cut, SURFMASK_NONE, 0.0f, r->nLeft, r->nTop, dx, r->nHeight);
                    for (size_t i=0,n=sizeof(x)/sizeof(x[0]); i<n; ++i)
                        x[i]               += dx;
                }

                // Draw fade
                lsp::Color fill(c->sFadeInColor);
                lsp::Color wire(c->sFadeInBorderColor);
                fill.scale_lch_luminance(bright);
                wire.scale_lch_luminance(bright);

                s->draw_poly(fill, wire, fi_border, x, y, 4);
            }

            // Draw fade out
            if ((c->sFadeOut.get() > 0) || (c->sTailCut.get() > 0))
            {
                float xx            = r->nLeft + r->nWidth - float(c->sFadeOut.get() * r->nWidth) / float(samples);

                // Form the arrays
                x[0]                = r->nLeft + r->nWidth;
                x[1]                = xx;
                x[2]                = x[0];
                x[3]                = x[0];

                y[0]                = (down) ? r->nTop : r->nTop + r->nHeight;
                y[1]                = (down) ? r->nTop + r->nHeight : r->nTop;
                y[2]                = y[1];
                y[3]                = y[0];

                // Apply tail cut if set
                if (c->sTailCut.get() > 0)
                {
                    lsp::Color cut(c->sTailCutColor);
                    cut.scale_lch_luminance(bright);

                    float dx            = float(c->sTailCut.get() * r->nWidth) / float(samples);
                    *tail_cut           = lsp_max(*tail_cut, ssize_t(dx));
                    s->fill_rect(cut, SURFMASK_NONE, 0.0f, r->nLeft + r->nWidth - dx, r->nTop, dx, r->nHeight);
                    for (size_t i=0,n=sizeof(x)/sizeof(x[0]); i<n; ++i)
                        x[i]               -= dx;
                }

                // Draw fade
                lsp::Color fill(c->sFadeOutColor);
                lsp::Color wire(c->sFadeOutBorderColor);
                fill.scale_lch_luminance(bright);
                wire.scale_lch_luminance(bright);

                s->draw_poly(fill, wire, fo_border, x, y, 4);
            }
        }

        void AudioSample::draw_main_text(ws::ISurface *s)
        {
            float scaling       = lsp_max(0.0f, sScaling.get());
            float fscaling      = lsp_max(0.0f, scaling * sFontScaling.get());
            float bright        = select_brightness();

            LSPString text;
            ws::font_parameters_t fp;
            ws::text_parameters_t tp;
            ws::rectangle_t xr;

            // Draw main text
            xr.nLeft            = 0;
            xr.nTop             = 0;
            xr.nWidth           = sGraph.nWidth;
            xr.nHeight          = sGraph.nHeight;

            // Get main text parameters
            sMainText.format(&text);
            sMainFont.get_parameters(s, fscaling, &fp);
            sMainFont.get_multitext_parameters(s, &tp, fscaling, &text);

            // Draw main text
            lsp::Color color(sMainColor);
            color.scale_lch_luminance(bright);

            draw_multiline_text(
                s, &sMainFont, &xr, color, &fp, &tp,
                sMainTextLayout.halign(), sMainTextLayout.valign(), fscaling,
                &text
            );
        }

        void AudioSample::draw_label(ws::ISurface *s, size_t idx)
        {
            float scaling       = lsp_max(0.0f, sScaling.get());
            float fscaling      = lsp_max(0.0f, scaling * sFontScaling.get());
            float bright        = select_brightness();

            ws::font_parameters_t fp;
            ws::text_parameters_t tp;
            ws::rectangle_t       xr, gr;
            ws::size_limit_t      sr;
            LSPString text;

            // Get label text parameters
            sLabel[idx].format(&text);
            sLabelFont.get_parameters(s, fscaling, &fp);
            sLabelFont.get_multitext_parameters(s, &tp, fscaling, &text);

            ssize_t rad         = (sLabelRadius.get() > 0) ? lsp_max(1.0f, sLabelRadius.get() * scaling) : 0.0f;
            size_t padding      = ceilf(rad * M_SQRT1_2);

            // Allocate space for the label
            sr.nMinWidth        = tp.Width  + padding * 2;
            sr.nMinHeight       = tp.Height + padding * 2;
            sr.nMaxWidth        = sGraph.nWidth;
            sr.nMaxHeight       = sGraph.nHeight;
            sr.nPreWidth        = -1;
            sr.nPreHeight       = -1;

            gr.nLeft            = 0;
            gr.nTop             = 0;
            gr.nWidth           = sGraph.nWidth;
            gr.nHeight          = sGraph.nHeight;

            sLabelLayout[idx].apply(&xr, &gr, &sr);

            bool aa             = s->set_antialiasing(true);
            lsp_finally { s->set_antialiasing(aa); };

            // Draw label background
            lsp::Color color(sLabelBgColor);
            color.scale_lch_luminance(bright);
            s->fill_rect(color, SURFMASK_ALL_CORNER, rad, &xr);

            // Draw label text
            xr.nLeft           += padding;
            xr.nTop            += padding;
            xr.nWidth          -= padding * 2;
            xr.nHeight         -= padding * 2;

            color.copy(sLabelColor[idx]);
            color.scale_lch_luminance(bright);

            draw_multiline_text(
                s, &sLabelFont, &xr, color, &fp, &tp,
                sLabelTextLayout[idx].halign(), sLabelTextLayout[idx].valign(), fscaling,
                &text
            );
        }

        void AudioSample::query_draw(size_t flags)
        {
            if (flags & REDRAW_CHILD)
                flags |= REDRAW_SURFACE;
            WidgetContainer::query_draw(flags);
        }

        void AudioSample::draw_channels(ws::ISurface *s)
        {
            // Main parameters
            lsp::Color color;
            const float scaling       = lsp_max(0.0f, sScaling.get());
            const float bright        = select_brightness();

            // Draw all samples
            ssize_t head_cut = 0;
            ssize_t tail_cut = 0;
            size_t items = vVisibleChannels.size();
            if (items > 0)
            {
                ws::rectangle_t xr;
                xr.nLeft            = 0;
                xr.nWidth           = sGraph.nWidth;
                ssize_t line_w      = (sLineWidth.get() > 0) ? lsp_max(1.0f, sLineWidth.get() * scaling) : 0.0f;

                // Estimate overall number of samples
                size_t samples  = 0;
                for (size_t i=0; i<items; ++i)
                {
                    AudioChannel *c = vVisibleChannels.uget(i);
                    samples         = lsp_max(samples, c->samples()->size());
                }

                xr.nHeight          = sGraph.nHeight / items;
                ssize_t place       = xr.nHeight * items;
                ssize_t y           = (sGraph.nHeight - place) / 2;

                // Draw depending of the view type
                if (sSGroups.get())
                {
                    // Draw stereo samples
                    xr.nTop             = y;
                    for (size_t i=0; i<items; ++i)
                    {
                        AudioChannel *c     = vVisibleChannels.uget(i);
                        draw_channel2(&xr, s, c, samples, i & 1, sMaxAmplitude.get());
                        xr.nTop            += xr.nHeight;
                    }

                    // Draw loop and stretch markers
                    xr.nTop             = y;
                    for (size_t i=0; i<items; ++i)
                    {
                        AudioChannel *c     = vVisibleChannels.uget(i);

                        // Define ranges
                        range_t stretch = {
                            &c->sStretchBegin,
                            &c->sStretchEnd,
                            &sStretchBorder,
                            &sStretchColor,
                            &sStretchBorderColor
                        };
                        range_t loop = {
                            &c->sLoopBegin,
                            &c->sLoopEnd,
                            &sLoopBorder,
                            &sLoopColor,
                            &sLoopBorderColor
                        };

                        draw_range(&xr, s, c, &stretch, samples);
                        draw_range(&xr, s, c, &loop, samples);
                        xr.nTop            += xr.nHeight;
                    }

                    // Draw fades
                    xr.nTop             = y;
                    for (size_t i=0; i<items; ++i)
                    {
                        AudioChannel *c     = vVisibleChannels.uget(i);
                        draw_fades2(&xr, s, c, &head_cut, &tail_cut, samples, i & 1);
                        xr.nTop            += xr.nHeight;
                    }

                    // Draw lines
                    color.copy(sLineColor);
                    xr.nTop             = y + xr.nHeight;
                    color.scale_lch_luminance(bright);
                    bool aa             = s->set_antialiasing(false);
                    for (size_t i=0; i<items; i += 2)
                    {
                        s->line(color, xr.nLeft, xr.nTop, xr.nLeft + xr.nWidth, xr.nTop, line_w);
                        xr.nTop            += xr.nHeight * 2;
                    }
                    s->set_antialiasing(aa);

                    // Draw playback position
                    xr.nTop             = y;
                    for (size_t i=0; i<items; ++i)
                    {
                        AudioChannel *c     = vVisibleChannels.uget(i);
                        draw_play_position(&xr, s, c, samples);
                        xr.nTop            += xr.nHeight;
                    }
                }
                else
                {
                    // Draw monophonic samples
                    xr.nTop             = y;
                    for (size_t i=0; i<items; ++i)
                    {
                        AudioChannel *c     = vVisibleChannels.uget(i);
                        draw_channel1(&xr, s, c, samples, sMaxAmplitude.get());
                        xr.nTop            += xr.nHeight;
                    }

                    // Draw loop and stretch markers
                    xr.nTop             = y;
                    for (size_t i=0; i<items; ++i)
                    {
                        AudioChannel *c     = vVisibleChannels.uget(i);

                        // Define ranges
                        range_t stretch = {
                            &c->sStretchBegin,
                            &c->sStretchEnd,
                            &sStretchBorder,
                            &sStretchColor,
                            &sStretchBorderColor
                        };
                        range_t loop = {
                            &c->sLoopBegin,
                            &c->sLoopEnd,
                            &sLoopBorder,
                            &sLoopColor,
                            &sLoopBorderColor
                        };

                        draw_range(&xr, s, c, &stretch, samples);
                        draw_range(&xr, s, c, &loop, samples);
                        xr.nTop            += xr.nHeight;
                    }

                    // Draw fades
                    xr.nTop             = y;
                    for (size_t i=0; i<items; ++i)
                    {
                        AudioChannel *c     = vVisibleChannels.uget(i);
                        draw_fades1(&xr, s, c, &head_cut, &tail_cut, samples);
                        xr.nTop            += xr.nHeight;
                    }

                    // Draw lines
                    color.copy(sLineColor);
                    xr.nTop             = y;
                    color.scale_lch_luminance(bright);
                    float sy            = xr.nHeight * 0.5f;
                    bool aa             = s->set_antialiasing(false);
                    for (size_t i=0; i<items; ++i)
                    {
                        s->line(color, xr.nLeft, xr.nTop + sy, xr.nLeft + xr.nWidth, xr.nTop + sy, line_w);
                        xr.nTop            += xr.nHeight;
                    }
                    s->set_antialiasing(aa);

                    // Draw playback position
                    xr.nTop             = y;
                    for (size_t i=0; i<items; ++i)
                    {
                        AudioChannel *c     = vVisibleChannels.uget(i);
                        draw_play_position(&xr, s, c, samples);
                        xr.nTop            += xr.nHeight;
                    }
                }
            }

            // Draw envelopes
            {
                const ws::point_t origin = s->set_origin(-sGraph.nLeft, -sGraph.nTop);
                lsp_finally { s->set_origin(origin); };

                for (size_t i=0; i<vVisibleEnvelopes.size(); ++i)
                {
                    ws::rectangle_t cr = sGraph;

                    tk::AudioEnvelope *envelope = vEnvelopes.get(i);
                    envelope->ipadding()->enter(&cr, scaling);
                    cr.nLeft       += head_cut;
                    cr.nWidth      -= head_cut + tail_cut;
                    envelope->draw_curve(s, bright, scaling, &cr);
                    envelope->commit_redraw();
                }
            }

            // Draw labels
            for (size_t i=0; i<LABELS; ++i)
            {
                if (!sLabelVisibility[i].get())
                    continue;
                draw_label(s, i);
            }

            // Commit redraw for children
            for (size_t i=0; i<items; ++i)
            {
                AudioChannel *c     = vVisibleChannels.uget(i);
                c->commit_redraw();
            }
        }

        void AudioSample::draw(ws::ISurface *s, bool force)
        {
            // Draw background
            const float bright        = select_brightness();
            lsp::Color color(sColor);
            color.scale_lch_luminance(bright);
            s->clear(color);

            // Draw main mesh
            if (sChannelVisibility.get())
                draw_channels(s);

            // Draw main text if it is required to be shown
            if (sMainVisibility.get())
                draw_main_text(s);
        }

        void AudioSample::render(ws::ISurface *s, const ws::rectangle_t *area, bool force)
        {
            if (nFlags & REDRAW_SURFACE)
                force = true;

            float scaling   = lsp_max(0.0f, sScaling.get());
            float xr        = lsp_max(0.0f, sBorderRadius.get() * scaling); // external radius
            float bw        = lsp_max(0.0f, sBorder.get() * scaling);       // border size
            float bright    = select_brightness();
            bool pressed    = nXFlags & XF_DOWN;

            // Prepare palette
            ws::ISurface *cv;
            lsp::Color color(sColor);
            lsp::Color bg_color;
            get_actual_bg_color(bg_color);
            color.scale_lch_luminance(bright);

            s->clip_begin(area);
            {
                // Draw widget background
                s->fill_rect(bg_color, SURFMASK_NONE, 0.0f, &sSize);

                bool aa             = s->set_antialiasing(true);
                lsp_finally { s->set_antialiasing(aa); };

                s->fill_rect(color, SURFMASK_ALL_CORNER, xr, &sSize);

                // Get surface of widget
                cv  = get_surface(s, sGraph.nWidth, sGraph.nHeight);
                if (cv != NULL)
                {
                    // Draw the surface
                    if (pressed)
                    {
                        ssize_t xbw         = lsp_max(1.0f, scaling);
                        ws::rectangle_t xr  = sGraph;
                        xr.nLeft           += xbw;
                        xr.nTop            += xbw;

                        xr.nWidth           = lsp_max(0, xr.nWidth  - xbw * 2);
                        xr.nHeight          = lsp_max(0, xr.nHeight - xbw * 2);

                        float sx            = float(xr.nWidth ) / float(cv->width());
                        float sy            = float(xr.nHeight) / float(cv->height());

                        s->draw(cv, xr.nLeft, xr.nTop, sx, sy, 0.0f);
                    }
                    else
                        s->draw(cv, sGraph.nLeft, sGraph.nTop, 1.0f, 1.0f, 0.0f);
                }

                // Draw the glass and the border
                color.copy(sGlassColor);
                bg_color.copy(sColor);
                color.scale_lch_luminance(bright);
                bg_color.scale_lch_luminance(bright);

                // Update border width if widget is in pressed state
                if (pressed)
                    bw         += lsp_max(1.0f, scaling);

                bool flat   = sBorderFlat.get();
                if (sGlass.get())
                {
                    cv = create_border_glass(&pGlass, s,
                            color, bg_color,
                            SURFMASK_ALL_CORNER, bw, xr,
                            sSize.nWidth, sSize.nHeight, flat
                        );
                    if (cv != NULL)
                        s->draw(cv, sSize.nLeft, sSize.nTop, 1.0f, 1.0f, 0.0f);
                }
                else
                {
                    drop_glass();
                    draw_border(s, bg_color, SURFMASK_ALL_CORNER, bw, xr, &sSize, flat);
                }
            }
            s->clip_end();
        }

        void AudioSample::hide_widget()
        {
            WidgetContainer::hide_widget();
            drop_glass();
        }

        void AudioSample::get_visible_channels(lltl::parray<AudioChannel> *dst)
        {
            for (size_t i=0, n=vChannels.size(); i<n; ++i)
            {
                AudioChannel *c = vChannels.get(i);
                if ((c == NULL) || (!c->visibility()->get()))
                    continue;
                if (!dst->add(c))
                    return;
            }
        }

        void AudioSample::get_visible_envelopes(lltl::parray<AudioEnvelope> *dst)
        {
            for (size_t i=0, n=vEnvelopes.size(); i<n; ++i)
            {
                AudioEnvelope *e = vEnvelopes.get(i);
                if ((e == NULL) || (!e->visibility()->get()))
                    continue;
                if (!dst->add(e))
                    return;
            }
        }

        void AudioSample::on_add_item(void *obj, Property *prop, void *w)
        {
            Widget *item = widget_ptrcast<Widget>(w);
            if (item == NULL)
                return;

            AudioSample *self = widget_ptrcast<AudioSample>(obj);
            if (self == NULL)
                return;

            item->set_parent(self);
            self->query_resize();
        }

        void AudioSample::on_remove_item(void *obj, Property *prop, void *w)
        {
            Widget *item = widget_ptrcast<Widget>(w);
            if (item == NULL)
                return;

            AudioSample *self = widget_ptrcast<AudioSample>(obj);
            if (self == NULL)
                return;

            // Remove widget from supplementary structures
            self->unlink_widget(item);
            self->query_resize();
        }

        status_t AudioSample::add(Widget *widget)
        {
            AudioChannel *channel = widget_cast<AudioChannel>(widget);
            if (channel != NULL)
                return vChannels.add(channel);

            AudioEnvelope *envelope = widget_cast<AudioEnvelope>(widget);
            if (envelope != NULL)
                return vEnvelopes.add(envelope);

            return STATUS_BAD_TYPE;
        }

        status_t AudioSample::remove(Widget *child)
        {
            AudioChannel *channel = widget_cast<AudioChannel>(child);
            if (channel != NULL)
                return vChannels.premove(channel);

            AudioEnvelope *envelope = widget_cast<AudioEnvelope>(child);
            if (envelope != NULL)
                return vEnvelopes.premove(envelope);

            return STATUS_BAD_TYPE;
        }

        status_t AudioSample::remove_all()
        {
            vChannels.clear();
            vEnvelopes.clear();
            return STATUS_OK;
        }

        Widget *AudioSample::find_widget(ssize_t x, ssize_t y)
        {
            for (size_t i=0; i<vEnvelopes.size(); ++i)
            {
                tk::AudioEnvelope *envelope = vEnvelopes.get(i);
                if ((envelope == NULL) || (!envelope->visibility()->get()))
                    continue;

                tk::AudioEnvelope::point_t *point = envelope->find_point(x, y);
                if (point != NULL)
                    return envelope;
            }

            return NULL;
        }

        status_t AudioSample::on_mouse_down(const ws::event_t *e)
        {
            // Handle mouse event
            float scaling   = lsp_max(0.0f, sScaling.get());
            float xr        = lsp_max(0.0f, sBorderRadius.get() * scaling); // external radius

            if (nBMask == 0)
            {
                if (Position::rinside(&sSize, e->nLeft, e->nTop, xr))
                {
                    if (e->nCode == ws::MCB_LEFT)
                        nXFlags    |= XF_LBUTTON;
                    else if (e->nCode == ws::MCB_RIGHT)
                        nXFlags    |= XF_RBUTTON;
                }
            }

            nBMask         |= size_t(1) << e->nCode;

            return handle_mouse_move(e);
        }

        status_t AudioSample::on_mouse_up(const ws::event_t *e)
        {
            size_t mask = nBMask;
            nBMask &= ~(size_t(1) << e->nCode);

            if (mask != (size_t(1) << e->nCode))
                return handle_mouse_move(e);

            // Last button released, process the vent
            size_t flags    = nXFlags;
            nXFlags         = 0;

            float scaling   = lsp_max(0.0f, sScaling.get());
            float xr        = lsp_max(0.0f, sBorderRadius.get() * scaling); // external radius

            if (Position::rinside(&sSize, e->nLeft, e->nTop, xr))
            {
                if ((e->nCode == ws::MCB_LEFT) && (flags & XF_LBUTTON))
                {
                    if (sActive.get())
                        sSlots.execute(SLOT_SUBMIT, this, NULL);
                }
                else if ((e->nCode == ws::MCB_RIGHT) && (flags & XF_RBUTTON))
                {
                    Menu *popup = sPopup.get();
                    if (popup != NULL)
                    {
                        ws::rectangle_t sr;
                        Window *wnd = widget_cast<Window>(this->toplevel());
                        wnd->get_screen_rectangle(&sr);
                        sr.nLeft       += e->nLeft;
                        sr.nTop        += e->nTop;
                        popup->show(this, sr.nLeft, sr.nTop);
                    }
                }
            }

            if (flags != nXFlags)
            {
                drop_glass();
                query_draw();
            }

            return STATUS_OK;
        }

        status_t AudioSample::on_mouse_move(const ws::event_t *e)
        {
            // Widget is not active?
            if (nXFlags == 0)
                return STATUS_OK;

            return handle_mouse_move(e);
        }

        status_t AudioSample::handle_mouse_move(const ws::event_t *e)
        {
            lsp_trace("nBMask = %x, nXFlags = %x", int(nBMask), int(nXFlags));
            if (nXFlags & XF_LBUTTON)
            {
                float scaling   = lsp_max(0.0f, sScaling.get());
                float xr        = lsp_max(0.0f, sBorderRadius.get() * scaling); // external radius
                bool pressed    = (sActive.get()) && (nBMask == ws::MCF_LEFT) && (Position::rinside(&sSize, e->nLeft, e->nTop, xr));

                size_t old      = nXFlags;
                nXFlags         = lsp_setflag(nXFlags, XF_DOWN, pressed);

                if (old != nXFlags)
                {
                    drop_glass();
                    query_draw();
                }
            }

            return STATUS_OK;
        }

        status_t AudioSample::on_before_popup(Menu *menu)
        {
            return STATUS_OK;
        }

        status_t AudioSample::on_popup(Menu *menu)
        {
            return STATUS_OK;
        }

        status_t AudioSample::on_submit()
        {
            return STATUS_OK;
        }

        status_t AudioSample::slot_on_before_popup(Widget *sender, void *ptr, void *data)
        {
            AudioSample *_this = widget_ptrcast<AudioSample>(ptr);
            Menu *_menu = widget_ptrcast<Menu>(sender);
            return (_this != NULL) ? _this->on_before_popup(_menu) : STATUS_BAD_ARGUMENTS;
        }

        status_t AudioSample::slot_on_popup(Widget *sender, void *ptr, void *data)
        {
            AudioSample *_this = widget_ptrcast<AudioSample>(ptr);
            Menu *_menu = widget_ptrcast<Menu>(sender);
            return (_this != NULL) ? _this->on_popup(_menu) : STATUS_BAD_ARGUMENTS;
        }

        status_t AudioSample::slot_on_submit(Widget *sender, void *ptr, void *data)
        {
            AudioSample *_this = widget_ptrcast<AudioSample>(ptr);
            return (_this != NULL) ? _this->on_submit() : STATUS_BAD_ARGUMENTS;
        }
    } /* namespace tk */
} /* namespace lsp */


