Subversion Repositories tpanel

Rev

Rev 20 | Blame | Last modification | View Log | RSS feed

/*
 * Copyright (C) 2020, 2021 by Andreas Theofilu <andreas@theosys.at>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 */

#include <string>
#include <memory>

#include <include/core/SkPixmap.h>
#include <include/core/SkSize.h>
#include <include/core/SkColor.h>
#include <include/core/SkFont.h>
#include <include/core/SkTypeface.h>
#include <include/core/SkFontMetrics.h>
#include <include/core/SkTextBlob.h>
#include <include/core/SkRegion.h>
#include <include/core/SkImageFilter.h>
#include <include/effects/SkImageFilters.h>
#include <include/core/SkPath.h>
#include <include/core/SkSurfaceProps.h>

#include "tbutton.h"
#include "terror.h"
#include "tconfig.h"
#include "tresources.h"
#include "ticons.h"
#include "tamxnet.h"
#include "tobject.h"
#include "tpagemanager.h"

using std::exception;
using std::string;
using std::vector;
using std::unique_ptr;
using std::map;
using std::pair;
using std::thread;
using std::atomic;
using std::mutex;
using std::bind;
using namespace Button;

#define MAX_BUFFER      65536

extern TIcons *gIcons;
extern amx::TAmxNet *gAmxNet;

atomic<bool> TButton::mAniRunning;
THR_REFRESH_t *TButton::mThrRefresh = nullptr;

mutex mutex_button;
mutex mutex_bargraph;
mutex mutex_sysdraw;

SYSBORDER_t sysBorders[] = {
    {  1, (char *)"Single Line",         0, (char *)"solid",   1,   0 },
    {  2, (char *)"Double Line",         0, (char *)"solid",   2,   0 },
    {  3, (char *)"Quad Line",           0, (char *)"solid",   4,   0 },
    {  4, (char *)"Picture Frame",       0, (char *)"double",  0,   0 },
    {  5, (char *)"Circle 15",           8, (char *)"solid",   2,  15 },
    {  6, (char *)"Circle 25",           9, (char *)"solid",   2,  25 },
    {  7, (char *)"Circle 35",          10, (char *)"solid",   2,  35 },
    {  8, (char *)"Circle 45",          11, (char *)"solid",   2,  45 },
    {  9, (char *)"Circle 55",          12, (char *)"solid",   2,  55 },
    { 10, (char *)"Circle 65",          13, (char *)"solid",   2,  65 },
    { 11, (char *)"Circle 75",          14, (char *)"solid",   2,  75 },
    { 12, (char *)"Circle 85",          15, (char *)"solid",   2,  85 },
    { 13, (char *)"Circle 95",          16, (char *)"solid",   2,  95 },
    { 14, (char *)"Circle 105",         17, (char *)"solid",   2, 105 },
    { 15, (char *)"Circle 115",         18, (char *)"solid",   2, 115 },
    { 16, (char *)"Circle 125",         19, (char *)"solid",   2, 125 },
    { 17, (char *)"Circle 135",         20, (char *)"solid",   2, 135 },
    { 18, (char *)"Circle 145",         21, (char *)"solid",   2, 145 },
    { 19, (char *)"Circle 155",         22, (char *)"solid",   2, 155 },
    { 20, (char *)"Circle 165",         23, (char *)"solid",   2, 165 },
    { 21, (char *)"Circle 175",         24, (char *)"solid",   2, 175 },
    { 22, (char *)"Circle 185",         25, (char *)"solid",   2, 185 },
    { 23, (char *)"Circle 195",         26, (char *)"solid",   2, 195 },
    { 24, (char *)"AMX Elite Inset _L",  0, (char *)"groove", 10,   0 },
    { 25, (char *)"AMX Elite Raised _L", 0, (char *)"ridge",  10,   0 },
    { 26, (char *)"AMX Elite Inset _M",  0, (char *)"groove",  5,   0 },
    { 27, (char *)"AMX Elite Raised _M", 0, (char *)"ridge",   5,   0 },
    { 28, (char *)"AMX Elite Inset _S",  0, (char *)"groove",  2,   0 },
    { 29, (char *)"AMX Elite Raised _S", 0, (char *)"ridge",   2,   0 },
    { 30, (char *)"Bevel Inset _L",      0, (char *)"inset",  10,   0 },
    { 31, (char *)"Bevel Raised _L",     0, (char *)"outset", 10,   0 },
    { 32, (char *)"Bevel Inset _M",      0, (char *)"inset",   5,   0 },
    { 33, (char *)"Bevel Raised _M",     0, (char *)"outset",  5,   0 },
    { 34, (char *)"Bevel Inset _S",      0, (char *)"inset",   2,   0 },
    { 35, (char *)"Bevel Raised _S",     0, (char *)"outset",  2,   0 },
    {  0, nullptr, 0, nullptr, 0, 0 }
};

SYSBUTTONS_t sysButtons[] = {
    {   8, MULTISTATE_BARGRAPH,  12, 0,  11 },  // Connection status
    { 141, GENERAL,               2, 0,   0 },  // Standard time
    { 142, GENERAL,               2, 0,   0 },  // Time AM/PM
    { 143, GENERAL,               2, 0,   0 },  // 24 hour time
    { 151, GENERAL,               2, 0,   0 },  // Date weekday
    { 152, GENERAL,               2, 0,   0 },  // Date mm/dd
    { 153, GENERAL,               2, 0,   0 },  // Date dd/mm
    { 154, GENERAL,               2, 0,   0 },  // Date mm/dd/yyyy
    { 155, GENERAL,               2, 0,   0 },  // Date dd/mm/yyyy
    { 156, GENERAL,               2, 0,   0 },  // Date month dd, yyyy
    { 157, GENERAL,               2, 0,   0 },  // Date dd month, yyyy
    { 158, GENERAL,               2, 0,   0 },  // Date yyyy-mm-dd
    { 234, GENERAL,               2, 0,   0 },  // Battery charging/not charging
    { 242, BARGRAPH,              2, 0, 100 },  // Battery level
    {   0, NONE,                  0, 0,   0 }   // Terminate
};

TButton::TButton()
{
    DECL_TRACER("TButton::TButton()");

    mAniRunning = false;
    mLastBlink.clear();
}

TButton::~TButton()
{
    DECL_TRACER("TButton::~TButton()");

    map<int, IMAGE_t>::iterator iter;

    for (iter = mImages.begin(); iter != mImages.end(); iter++)
    {
        if (!iter->second.imageMi.empty())
            iter->second.imageMi.reset();

        if (!iter->second.imageBm.empty())
            iter->second.imageBm.reset();
    }

    mImages.clear();

    if (ap == 0 && ad == 8)
    {
        if (gAmxNet)
            gAmxNet->deregNetworkState(mHandle);
    }

    if (ap == 0 && ((ad >= 141 && ad <= 143) || (ad >= 151 && ad <= 158)))
    {
        if (gAmxNet)
            gAmxNet->deregTimer(mHandle);
    }

    if (mTimer)
    {
        mTimer->stop();

        while (mTimer->isRunning())
            usleep(50);

        delete mTimer;
    }

    THR_REFRESH_t *next, *p = mThrRefresh;

    while (p)
    {
        if (p->mImageRefresh)
        {
            p->mImageRefresh->stop();

            while (p->mImageRefresh->isRunning())
                usleep(50);

            delete p->mImageRefresh;
            p->mImageRefresh = nullptr;
        }

        next = p->next;
        delete p;
        p = next;
    }
}

void TButton::initialize(TReadXML *xml, mxml_node_t *node)
{
    DECL_TRACER("TButton::initialize(TReadXML *xml, mxml_node_t *node)");

    if (!xml || !node)
    {
        MSG_ERROR("Invalid NULL parameter passed!");
        TError::setError();
        return;
    }

    mxml_node_t *child = xml->getFirstChild(node);
    string stype = xml->getAttributeFromNode(node, "type");
    type = getButtonType(stype);
    MSG_DEBUG("Button type: " << stype << " --> " << type);

    while(child)
    {
        string ename = xml->getElementName(child);

        if (ename.compare("bi") == 0)
            bi = xml->getIntFromNode(child);
        else if (ename.compare("na") == 0)
            na = xml->getTextFromNode(child);
        else if (ename.compare("bd") == 0)
            bd = xml->getTextFromNode(child);
        else if (ename.compare("lt") == 0)
            lt = xml->getIntFromNode(child);
        else if (ename.compare("tp") == 0)
            tp = xml->getIntFromNode(child);
        else if (ename.compare("wt") == 0)
            wt = xml->getIntFromNode(child);
        else if (ename.compare("ht") == 0)
            ht = xml->getIntFromNode(child);
        else if (ename.compare("zo") == 0)
            zo = xml->getIntFromNode(child);
        else if (ename.compare("hs") == 0)
            hs = xml->getTextFromNode(child);
        else if (ename.compare("bs") == 0)
            bs = xml->getTextFromNode(child);
        else if (ename.compare("fb") == 0)
            fb = getButtonFeedback(xml->getTextFromNode(child));
        else if (ename.compare("ap") == 0)
            ap = xml->getIntFromNode(child);
        else if (ename.compare("ad") == 0)
            ad = xml->getIntFromNode(child);
        else if (ename.compare("ch") == 0)
            ch = xml->getIntFromNode(child);
        else if (ename.compare("cp") == 0)
            cp = xml->getIntFromNode(child);
        else if (ename.compare("lp") == 0)
            lp = xml->getIntFromNode(child);
        else if (ename.compare("lv") == 0)
            lv = xml->getIntFromNode(child);
        else if (ename.compare("dr") == 0)
            dr = xml->getTextFromNode(child);
        else if (ename.compare("va") == 0)
            va = xml->getIntFromNode(child);
        else if (ename.compare("rm") == 0)
            rm = xml->getIntFromNode(child);
        else if (ename.compare("nu") == 0)
            nu = xml->getIntFromNode(child);
        else if (ename.compare("nd") == 0)
            nd = xml->getIntFromNode(child);
        else if (ename.compare("ar") == 0)
            ar = xml->getIntFromNode(child);
        else if (ename.compare("ru") == 0)
            ru = xml->getIntFromNode(child);
        else if (ename.compare("rd") == 0)
            rd = xml->getIntFromNode(child);
        else if (ename.compare("lu") == 0)
            lu = xml->getIntFromNode(child);
        else if (ename.compare("ld") == 0)
            ld = xml->getIntFromNode(child);
        else if (ename.compare("rv") == 0)
            rv = xml->getIntFromNode(child);
        else if (ename.compare("rl") == 0)
            rl = xml->getIntFromNode(child);
        else if (ename.compare("rh") == 0)
            rh = xml->getIntFromNode(child);
        else if (ename.compare("ri") == 0)
            ri = xml->getIntFromNode(child);
        else if (ename.compare("rn") == 0)
            rn = xml->getIntFromNode(child);
        else if (ename.compare("if") == 0)
            _if = xml->getTextFromNode(child);
        else if (ename.compare("sd") == 0)
            sd = xml->getTextFromNode(child);
        else if (ename.compare("sc") == 0)
            sc = xml->getTextFromNode(child);
        else if (ename.compare("mt") == 0)
            mt = xml->getIntFromNode(child);
        else if (ename.compare("dt") == 0)
            dt = xml->getTextFromNode(child);
        else if (ename.compare("im") == 0)
            im = xml->getTextFromNode(child);
        else if (ename.compare("op") == 0)
            op = xml->getTextFromNode(child);
        else if (ename.compare("pf") == 0)
        {
            PUSH_FUNC_T pf;
            pf.pfName = xml->getTextFromNode(child);
            pf.pfType = xml->getAttributeFromNode(child, "type");
            pushFunc.push_back(pf);
        }
        else if (ename.compare("sr") == 0)
        {
            mxml_node_t *n = xml->getFirstChild(child);
            SR_T bsr;
            bsr.number = atoi(xml->getAttributeFromNode(child, "number").c_str());

            while (n)
            {
                string e = xml->getElementName(n);

                if (e.compare("do") == 0)
                    bsr._do = xml->getTextFromNode(n);
                else if (e.compare("bs") == 0)
                    bsr.bs = xml->getTextFromNode(n);
                else if (e.compare("mi") == 0)
                    bsr.mi = xml->getTextFromNode(n);
                else if (e.compare("cb") == 0)
                    bsr.cb = xml->getTextFromNode(n);
                else if (e.compare("cf") == 0)
                    bsr.cf = xml->getTextFromNode(n);
                else if (e.compare("ct") == 0)
                    bsr.ct = xml->getTextFromNode(n);
                else if (e.compare("ec") == 0)
                    bsr.ec = xml->getTextFromNode(n);
                else if (e.compare("bm") == 0)
                {
                    bsr.bm = xml->getTextFromNode(n);
                    bsr.dynamic = ((atoi(xml->getAttributeFromNode(n, "dynamic").c_str()) == 1) ? true : false);
                }
                else if (e.compare("sd") == 0)
                    bsr.sd = xml->getTextFromNode(n);
                else if (e.compare("sb") == 0)
                    bsr.sb = xml->getIntFromNode(n);
                else if (e.compare("ii") == 0)
                    bsr.ii = xml->getIntFromNode(n);
                else if (e.compare("ji") == 0)
                    bsr.ji = xml->getIntFromNode(n);
                else if (e.compare("jb") == 0)
                    bsr.jb = xml->getIntFromNode(n);
                else if (e.compare("bx") == 0)
                    bsr.bx = xml->getIntFromNode(n);
                else if (e.compare("by") == 0)
                    bsr.by = xml->getIntFromNode(n);
                else if (e.compare("ix") == 0)
                    bsr.ix = xml->getIntFromNode(n);
                else if (e.compare("iy") == 0)
                    bsr.iy = xml->getIntFromNode(n);
                else if (e.compare("fi") == 0)
                    bsr.fi = xml->getIntFromNode(n);
                else if (e.compare("te") == 0)
                    bsr.te = xml->getTextFromNode(n);
                else if (e.compare("jt") == 0)
                    bsr.jt = (TEXT_ORIENTATION)xml->getIntFromNode(n);
                else if (e.compare("tx") == 0)
                    bsr.tx = xml->getIntFromNode(n);
                else if (e.compare("ty") == 0)
                    bsr.ty = xml->getIntFromNode(n);
                else if (e.compare("ww") == 0)
                    bsr.ww = xml->getIntFromNode(n);
                else if (e.compare("et") == 0)
                    bsr.et = xml->getIntFromNode(n);
                else if (e.compare("oo") == 0)
                    bsr.oo = xml->getIntFromNode(n);

                n = xml->getNextChild(n);
            }

            sr.push_back(bsr);
        }

        child = xml->getNextChild(child);

        if (xml->getElementName(child).compare("button") == 0)
        {
            MSG_WARNING("Unexpected element \"button\" found! Please check XML file.");
            break;
        }
    }
}

BUTTONTYPE TButton::getButtonType(const string& bt)
{
    DECL_TRACER("TButton::getButtonType(const string& bt)");

    if (bt.compare("general") == 0)
        return GENERAL;
    else if (bt.compare("multi-state general") == 0)
        return MULTISTATE_GENERAL;
    else if (bt.compare("bargraph") == 0)
        return BARGRAPH;
    else if (bt.compare("multi-state bargraph") == 0)
        return MULTISTATE_BARGRAPH;
    else if (bt.compare("joistick") == 0)
        return JOISTICK;
    else if (bt.compare("text input") == 0)
        return TEXT_INPUT;
    else if (bt.compare("computer control") == 0)
        return COMPUTER_CONTROL;
    else if (bt.compare("take note") == 0)
        return TAKE_NOTE;
    else if (bt.compare("sub-page view") == 0)
        return SUBPAGE_VIEW;

    return NONE;
}

FEEDBACK TButton::getButtonFeedback(const string& fb)
{
    DECL_TRACER("TButton::getButtonFeedback(const string& fb)");

    if (fb.compare("channel") == 0)
        return FB_CHANNEL;
    else if (fb.compare("inverted channel") == 0)
        return FB_INV_CHANNEL;
    else if (fb.compare("always on") == 0)
        return FB_ALWAYS_ON;
    else if (fb.compare("momentary") == 0)
        return FB_MOMENTARY;
    else if (fb.compare("blink") == 0)
        return FB_BLINK;

    return FB_NONE;
}

bool TButton::createButtons()
{
    DECL_TRACER("TButton::createButtons()");

    // Get the images, if there any
    vector<SR_T>::iterator srIter;
    int i = 0;

    for (srIter = sr.begin(); srIter != sr.end(); srIter++)
    {
        int number = srIter->number;
        IMAGE_t img;

        if (srIter->sb > 0)
            continue;

        if (!srIter->mi.empty())        // Do we have a chameleon image?
        {
            sk_sp<SkData> image;

            if (!(image = readImage(srIter->mi)))
                return false;

            DecodeDataToBitmap(image, &img.imageMi);

            if (img.imageMi.empty())
            {
                MSG_WARNING("Could not create a picture for element " << number << " on button " << bi << " (" << na << ")");
                return false;
            }

            srIter->mi_width = img.imageMi.dimensions().width();
            srIter->mi_height = img.imageMi.dimensions().height();
        }

        if (!srIter->bm.empty())        // Do we have a bitmap?
        {
            sk_sp<SkData> image;

            if (!(image = readImage(srIter->bm)))
                return false;

            DecodeDataToBitmap(image, &img.imageBm);

            if (img.imageBm.empty())
            {
                MSG_WARNING("Could not create a picture for element " << number << " on button " << bi << " (" << na << ")");
                return false;
            }

            srIter->bm_width = img.imageBm.dimensions().width();
            srIter->bm_height = img.imageBm.dimensions().height();
        }

        mImages.insert(pair<int, IMAGE_t>(number, img));
        i++;
    }

    return true;
}

void TButton::refresh()
{
    DECL_TRACER("TButton::refresh()");

    makeElement();
}

bool TButton::makeElement(int instance)
{
    DECL_TRACER("TButton::makeElement()");

    int inst = mActInstance;

    if (instance >= 0)
        inst = instance;

    if (type == MULTISTATE_GENERAL && ar == 1)
        return drawButtonMultistateAni();
    else if (type == BARGRAPH)
        return drawBargraph(inst, mLastLevel);
    else
        return drawButton(inst);

    return false;
}

bool TButton::setActive(int instance)
{
    DECL_TRACER("TButton::setActive(int instance)");

    if ((size_t)instance >= sr.size())
    {
        MSG_ERROR("Instance " << instance << " is higher than the maximum instance " << sr.size() << "!");
        return false;
    }

    if (instance == mActInstance)
    {
        MSG_TRACE("Not necessary to set instance " << instance << " again.");
        return true;
    }

    mActInstance = instance;
    makeElement(instance);

    return true;
}

bool TButton::setIcon(int id, int instance)
{
    DECL_TRACER("TButton::setIcon(int id, int instance)");

    if ((size_t)instance >= sr.size())
    {
        MSG_ERROR("Instance " << instance << " does not exist!");
        return false;
    }

    sr[instance].ii = id;
    return makeElement(instance);
}

bool TButton::setIcon(const string& icon, int instance)
{
    DECL_TRACER("TButton::setIcon(const string& icon, int instance)");

    if ((size_t)instance >= sr.size())
    {
        MSG_ERROR("Instance " << instance << " does not exist!");
        return false;
    }

    if (!gIcons)
    {
        gIcons = new TIcons();

        if (TError::isError())
        {
            MSG_ERROR("Error initializing icons!");
            return false;
        }
    }

    int id = gIcons->getNumber(icon);

    if (id == -1)
    {
        MSG_WARNING("Icon " << icon << " not found!");
        return false;
    }

    sr[instance].ii = id;
    return makeElement(instance);
}

bool TButton::revokeIcon(int instance)
{
    DECL_TRACER("TButton::revokeIcon(int instance)");

    if ((size_t)instance >= sr.size())
    {
        MSG_ERROR("Instance " << instance << " does not exist!");
        return false;
    }

    sr[instance].ii = 0;
    return makeElement(instance);
}

bool TButton::setText(const string& txt, int instance)
{
    DECL_TRACER("TButton::setText(const string& txt, int instance)");

    if ((size_t)instance >= sr.size())
    {
        MSG_ERROR("Instance " << instance << " does not exist!");
        return false;
    }

    sr[instance].te = txt;
    return makeElement(instance);
}

bool TButton::setBitmap(const string& file, int instance)
{
    DECL_TRACER("TButton::setBitmap(const string& file, int instance)");

    if (file.empty() || instance >= (int)sr.size())
    {
        MSG_ERROR("Invalid parameters!");
        return false;
    }

    sr[instance].bm = file;
    map<int, IMAGE_t>::iterator iter = mImages.find(sr[instance].number);

    if (iter != mImages.end())
        mImages.erase(iter);

    if (!createButtons())
        return false;

    return makeElement(instance);
}

bool TButton::setOpacity(int op, int instance)
{
    DECL_TRACER("TButton::setOpacity(int op, int instance)");

    if ((size_t)instance >= sr.size())
    {
        MSG_ERROR("Instance " << instance << " does not exist!");
        return false;
    }

    if (op < 0 || op < 255)
    {
        MSG_ERROR("Invalid opacity " << op << "!");
        return false;
    }

    sr[instance].oo = op;
    return makeElement(instance);
}

bool TButton::setFont(int id, int instance)
{
    DECL_TRACER("TButton::setFont(int id)");

    if (instance < 0 || (size_t)instance >= sr.size())
    {
        MSG_ERROR("Instance " << instance << " does not exist!");
        return false;
    }

    sr[instance].fi = id;
    return makeElement(instance);
}

void TButton::setLeft(int left)
{
    DECL_TRACER("TButton::setLeft(int left)");

    if (left < 0)
        return;

    lt = left;
    makeElement(mActInstance);
}

void TButton::setTop(int top)
{
    DECL_TRACER("TButton::setTop(int top)");

    if (top < 0)
        return;

    tp = top;
    makeElement(mActInstance);
}

void TButton::setLeftTop(int left, int top)
{
    DECL_TRACER("TButton::setLeftTop(int left, int top)");

    if (top < 0 || left < 0)
        return;

    lt = left;
    tp = top;
    makeElement(mActInstance);
}

void TButton::setResourceName(const string& name, int instance)
{
    DECL_TRACER("TButton::setResourceName(const string& name, int instance)");

    if (instance < 0 || instance >= (int)sr.size())
    {
        MSG_ERROR("Invalid instance " << instance);
        return;
    }

    if (!sr[instance].dynamic)
    {
        MSG_ERROR("Button " << bi << ": " << na << " is not a resource button!");
        return;
    }

    sr[instance].bm = name;
}

bool TButton::setWorWrap(bool state, int instance)
{
    DECL_TRACER("TButton::setWorWrap(bool state, int instance)");

    if (instance < 0 || instance >= (int)sr.size())
    {
        MSG_ERROR("Invalid instance " << instance);
        return false;
    }

    sr[instance].ww = state ? 1 : 0;
    return makeElement(instance);
}

void TButton::_TimerCallback(ulong counter)
{
    mLastBlink.second++;
    int months[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

    if ((mLastBlink.year % 4) == 0)
        months[1] = 29;

    if (mLastBlink.second > 59)
    {
        mLastBlink.minute++;
        mLastBlink.second = 0;

        if (mLastBlink.minute > 59)
        {
            mLastBlink.hour++;
            mLastBlink.minute = 0;

            if (mLastBlink.hour >= 24)
            {
                mLastBlink.hour = 0;
                mLastBlink.weekday++;
                mLastBlink.day++;

                if (mLastBlink.weekday > 7)
                    mLastBlink.weekday = 0;

                if (mLastBlink.day > months[mLastBlink.month-1])
                {
                    mLastBlink.day = 1;
                    mLastBlink.month++;

                    if (mLastBlink.month > 12)
                    {
                        mLastBlink.year++;
                        mLastBlink.month = 1;
                    }
                }
            }
        }
    }

    funcTimer(mLastBlink);
}

void TButton::_imageRefresh(const string& url)
{
    DECL_TRACER("TButton::_imageRefresh(const string& url)");

    ulong parent = mHandle & 0xffff0000;
    getDrawOrder(sr[mActInstance]._do, (DRAW_ORDER *)&mDOrder);

    if (TError::isError())
    {
        TError::clear();
        return;
    }

    SkBitmap imgButton;
    imgButton.allocN32Pixels(wt, ht);

    for (int i = 0; i < ORD_ELEM_COUNT; i++)
    {
        if (mDOrder[i] == ORD_ELEM_FILL)
        {
            if (!buttonFill(&imgButton, mActInstance))
                return;
        }
        else if (mDOrder[i] == ORD_ELEM_BITMAP)
        {
            RESOURCE_T resource = gPrjResources->findResource(sr[mActInstance].bm);

            if (resource.protocol.empty())
            {
                MSG_ERROR("Resource " << sr[mActInstance].bm << " not found!");
                return;
            }

            try
            {
                char *content = nullptr;
                size_t length = 0, contentlen = 0;

                if ((content = gPrjResources->tcall(&length, url, resource.user, resource.password)) == nullptr)
                    return;

                contentlen = gPrjResources->getContentSize();

                if (content == nullptr)
                {
                    MSG_ERROR("Server returned no or invalid content!");
                    return;
                }

                sk_sp<SkData> data = SkData::MakeWithCopy(content, contentlen);

                if (!data)
                {
                    MSG_ERROR("Could not make an image!");
                    return;
                }

                SkBitmap image;

                if (!DecodeDataToBitmap(data, &image))
                {
                    MSG_ERROR("Error creating an image!");
                    return;
                }

                loadImage(&imgButton, image, mActInstance);
            }
            catch (std::exception& e)
            {
                MSG_ERROR(e.what());
                return;
            }
        }
        else if (mDOrder[i] == ORD_ELEM_ICON)
        {
            if (!buttonIcon(&imgButton, mActInstance))
                return;
        }
        else if (mDOrder[i] == ORD_ELEM_TEXT)
        {
            if (!buttonText(&imgButton, mActInstance))
                return;
        }
        else if (mDOrder[i] == ORD_ELEM_BORDER)
        {
            if (!buttonBorder(&imgButton, mActInstance))
                return;
        }
    }

    if (mGlobalOO >= 0 || sr[mActInstance].oo >= 0) // Take overall opacity into consideration
    {
        SkBitmap ooButton;
        int w = imgButton.width();
        int h = imgButton.height();
        ooButton.allocN32Pixels(w, h, true);
        SkCanvas canvas(ooButton);
        SkIRect irect = SkIRect::MakeXYWH(0, 0, w, h);
        SkRegion region;
        region.setRect(irect);
        SkScalar oo;

        if (mGlobalOO >= 0 && sr[mActInstance].oo >= 0)
        {
            oo = std::min((SkScalar)mGlobalOO, (SkScalar)sr[mActInstance].oo);
            MSG_DEBUG("Set global overal opacity to " << oo);
        }
        else if (sr[mActInstance].oo >= 0)
        {
            oo = (SkScalar)sr[mActInstance].oo;
            MSG_DEBUG("Set overal opacity to " << oo);
        }
        else
        {
            oo = (SkScalar)mGlobalOO;
            MSG_DEBUG("Set global overal opacity to " << oo);
        }

        SkScalar alpha = 1.0 / 255.0 * oo;
        MSG_DEBUG("Calculated alpha value: " << alpha);
        SkPaint paint;
        paint.setAlphaf(alpha);
        paint.setImageFilter(SkImageFilters::AlphaThreshold(region, 0.0, alpha, nullptr));
        canvas.drawBitmap(imgButton, 0, 0, &paint);
        imgButton.erase(SK_ColorTRANSPARENT, {0, 0, w, h});
        imgButton = ooButton;
    }

    mLastImage = imgButton;
    size_t rowBytes = imgButton.info().minRowBytes();

    if (!prg_stopped && visible && _displayButton)
        _displayButton(mHandle, parent, (unsigned char *)imgButton.getPixels(), wt, ht, rowBytes, lt, tp);
}

void TButton::registerSystemButton()
{
    DECL_TRACER("TButton::registerSystemButton()");

    if (mSystemReg)
        return;

    // If this is a special system button, register it to receive the state
    if (ap == 0 && ad == 8)     // Connection status?
    {
        MSG_TRACE("Try to register button " << na << " as connection status ...");

        if (gAmxNet)
        {
            gAmxNet->registerNetworkState(bind(&TButton::funcNetwork, this, std::placeholders::_1), mHandle);
            mSystemReg = true;
            MSG_TRACE("Button registered");
        }
        else
            MSG_WARNING("Network class not initialized!");

    }
    else if (ap == 0 && ((ad >= 141 && ad <= 143) || (ad >= 151 && ad <= 158))) // time or date
    {
        MSG_TRACE("Try to register button " << na << " as time/date ...");

        if (gAmxNet)
        {
            gAmxNet->registerTimer(bind(&TButton::funcTimer, this, std::placeholders::_1), mHandle);
            mSystemReg = true;
            MSG_TRACE("Button registered");
        }
        else
            MSG_WARNING("Network class not initialized!");

        if (ad >= 141 && ad <= 143 && !mTimer)
        {
            mTimer = new TTimer;
            mTimer->setInterval(std::chrono::milliseconds(1000));   // 1 second
            mTimer->registerCallback(bind(&TButton::_TimerCallback, this, std::placeholders::_1));
            mTimer->run();
        }
    }
}

void TButton::setHandle(ulong handle)
{
    DECL_TRACER("Button::TButton::setHandle(ulong handle)");

    mHandle = handle;
}

void TButton::addPushFunction(string& func, string& page)
{
    DECL_TRACER("TButton::addPushFunction(string& func, string& page)");

    vector<string> allFunc = { "Stan", "Prev", "Show", "Hide", "Togg", "ClearG", "ClearP", "ClearA" };
    vector<string>::iterator iter;

    for (iter = allFunc.begin(); iter != allFunc.end(); ++iter)
    {
        if (iter->compare(func) == 0)
        {
            bool found = false;
            vector<PUSH_FUNC_T>::iterator iterPf;

            for (iterPf = pushFunc.begin(); iterPf != pushFunc.end(); iterPf++)
            {
                if (iterPf->pfType.compare(func) == 0)
                {
                    iterPf->pfName = page;
                    found = true;
                    break;
                }
            }

            if (!found)
            {
                PUSH_FUNC_T pf;
                pf.pfType = func;
                pf.pfName = page;
                pushFunc.push_back(pf);
            }
        }

        break;
    }
}

void TButton::clearPushFunction(const string& action)
{
    DECL_TRACER("TButton::clearPushFunction(const string& action)");

    if (pushFunc.empty())
        return;

    vector<PUSH_FUNC_T>::iterator iter;

    for (iter = pushFunc.begin(); iter != pushFunc.end(); iter++)
    {
        if (iter->pfName.compare(action) == 0)
        {
            pushFunc.erase(iter);
            return;
        }
    }
}

void TButton::getDrawOrder(const std::string& sdo, DRAW_ORDER *order)
{
    DECL_TRACER("TButton::getDrawOrder(const std::string& sdo, DRAW_ORDER *order)");

    if (!order)
        return;

    if (sdo.empty() || sdo.length() != 10)
    {
        *order     = ORD_ELEM_FILL;
        *(order+1) = ORD_ELEM_BITMAP;
        *(order+2) = ORD_ELEM_ICON;
        *(order+3) = ORD_ELEM_TEXT;
        *(order+4) = ORD_ELEM_BORDER;
        return;
    }

    int elems = sdo.length() / 2;

    for (int i = 0; i < elems; i++)
    {
        int e = atoi(sdo.substr(i * 2, 2).c_str());

        if (e < 1 || e > 5)
        {
            MSG_ERROR("Invalid draw order \"" << sdo << "\"!");
            TError::setError();
            return;
        }

        *(order+i) = (DRAW_ORDER)e;
    }
}

bool TButton::buttonFill(SkBitmap* bm, int instance)
{
    DECL_TRACER("TButton::buttonFill(SkBitmap* bm, int instance)");

    if (!bm)
    {
        MSG_ERROR("Invalid bitmap!");
        return false;
    }

    SkColor color = TColor::getSkiaColor(sr[instance].cf);
    MSG_DEBUG("Filling image of size " << bm->width() << "x" << bm->height() << " with color " << TColor::colorToString(color));
    bm->eraseColor(color);
    return true;
}

bool TButton::buttonBitmap(SkBitmap* bm, int instance)
{
    DECL_TRACER("TButton::buttonBitmap(SkBitmap* bm, int instane)");

    if (sr[instance].dynamic)
        return buttonDynamic(bm, instance);

    if (!sr[instance].mi.empty() && !sr[instance].bm.empty())       // Chameleon image?
    {
        MSG_TRACE("Chameleon image ...");
        map<int, IMAGE_t>::iterator iterImages = mImages.find(sr[instance].number);
        SkBitmap imgRed(iterImages->second.imageMi);
        SkBitmap imgMask(iterImages->second.imageBm);

        SkBitmap img = drawImageButton(imgRed, imgMask, sr[instance].mi_width, sr[instance].mi_height, TColor::getSkiaColor(sr[instance].cf), TColor::getSkiaColor(sr[instance].cb));

        if (img.empty())
        {
            MSG_ERROR("Error creating the cameleon image \"" << sr[instance].mi << "\" / \"" << sr[instance].bm << "\"!");
            TError::setError();
            return false;
        }

        SkCanvas ctx(img, SkSurfaceProps(1, kUnknown_SkPixelGeometry));
        SkImageInfo info = img.info();
        SkPaint paint;
        paint.setBlendMode(SkBlendMode::kSrcATop);
        ctx.drawBitmap(imgMask, 0, 0, &paint);

        POSITION_t position = calcImagePosition(sr[instance].mi_width, sr[instance].mi_height, SC_BITMAP, instance);

        if (!position.valid)
        {
            MSG_ERROR("Error calculating the position of the image for button number " << bi << ": " << na);
            TError::setError();
            return false;
        }

        SkCanvas can(*bm, SkSurfaceProps(1, kUnknown_SkPixelGeometry));
        paint.setBlendMode(SkBlendMode::kSrc);

        if (sr[instance].sb == 0)
            can.drawBitmap(img, position.left, position.top, &paint);
        else    // Scale to fit
        {
            SkRect rect = SkRect::MakeXYWH(position.left, position.top, position.width, position.height);
            sk_sp<SkImage> im = SkImage::MakeFromBitmap(img);
            can.drawImageRect(im, rect, &paint);
        }
    }
    else if (!sr[instance].bm.empty())
    {
        MSG_TRACE("Drawing normal image ...");
        map<int, IMAGE_t>::iterator iterImages = mImages.find(sr[instance].number);
        SkBitmap image = iterImages->second.imageBm;

        if (image.empty())
        {
            MSG_ERROR("Error creating the image \"" << sr[instance].bm << "\"!");
            TError::setError();
            return false;
        }

        SkImageInfo info = image.info();
        POSITION_t position = calcImagePosition(sr[instance].bm_width, sr[instance].bm_height, SC_BITMAP, instance);

        if (!position.valid)
        {
            MSG_ERROR("Error calculating the position of the image for button number " << bi);
            TError::setError();
            return false;
        }

        MSG_DEBUG("Putting bitmap on top of image ...");
        SkPaint paint;
        paint.setBlendMode(SkBlendMode::kSrc);
        SkCanvas can(*bm, SkSurfaceProps(1, kUnknown_SkPixelGeometry));

        if (sr[instance].sb == 0)
            can.drawBitmap(image, position.left, position.top, &paint);
        else    // Scale to fit
        {
            SkRect rect = SkRect::MakeXYWH(position.left, position.top, position.width, position.height);
            sk_sp<SkImage> im = SkImage::MakeFromBitmap(image);
            can.drawImageRect(im, rect, &paint);
        }
    }
    else
    {
        MSG_DEBUG("No bitmap defined.");
    }

    return true;
}

bool TButton::buttonDynamic(SkBitmap* bm, int instance)
{
    DECL_TRACER("TButton::buttonDynamic(SkBitmap* bm, int instance)");

    if (!gPrjResources)
    {
        MSG_ERROR("Internal error: Global resource class not initialized!");
        return false;
    }

    if (!sr[instance].dynamic)
    {
        MSG_WARNING("Button " << bi << ": \"" << na << "\" is not for remote image!");
        return false;
    }

    RESOURCE_T resource = gPrjResources->findResource(1, sr[instance].bm);

    if (resource.protocol.empty())
    {
        MSG_ERROR("Resource " << sr[instance].bm << " not found!");
        return false;
    }

    string path = resource.path;

    if (!resource.file.empty())
        path += "/" + resource.file;

    string url = gPrjResources->makeURL(toLower(resource.protocol), resource.host, 0, path);

    try
    {
        MSG_TRACE("Starting thread for loading a dynamic image ...");
        mThrRes = std::thread([=] { this->funcResource(&resource, url, bm, instance); });
        MSG_TRACE("Thread started. Detaching ...");
        mThrRes.detach();
        MSG_TRACE("Thread is running and detached.");
    }
    catch (std::exception& e)
    {
        MSG_ERROR("Error starting the AmxNet thread: " << e.what());
    }

    return true;
}

void TButton::funcResource(const RESOURCE_T* resource, const std::string& url, SkBitmap* bm, int instance)
{
    DECL_TRACER("TButton::funcResource(RESOURCE_T* resource, std::string& url, SkBitmap* bm, int instance)");

    if (resource->refresh > 0 && !resource->dynamo)      // Periodically refreshing image?
    {
        MSG_DEBUG("Retrieving periodicaly refreshed image");

        ulong parent = (mHandle >> 16) & 0x0000ffff;

        if (!mHandle || !parent || bi <= 1)
        {
            MSG_ERROR("Invalid button. Can't make a dynamo image!");
            return;
        }

        THR_REFRESH_t *thref = _findResource(mHandle, parent, bi);
        TImageRefresh *mImageRefresh = nullptr;

        if (!thref)
        {
            MSG_DEBUG("Creating a new refresh thread");
            mImageRefresh = new TImageRefresh();
            mImageRefresh->registerCallback(bind(&TButton::_imageRefresh, this, std::placeholders::_1));
            mImageRefresh->setInterval(std::chrono::seconds(resource->refresh));
            mImageRefresh->setUsername(resource->user);
            mImageRefresh->setPassword(resource->password);

            if (resource->preserve)
                mImageRefresh->setRunOnce();

            _addResource(mImageRefresh, mHandle, parent, bi);
        }
        else
        {
            mImageRefresh = thref->mImageRefresh;

            if (!mImageRefresh)
            {
                MSG_ERROR("Error creating a new refresh class!");
                return;
            }

            mImageRefresh->setInterval(std::chrono::seconds(resource->refresh));
            mImageRefresh->setUsername(resource->user);
            mImageRefresh->setPassword(resource->password);

            if (resource->preserve)
                mImageRefresh->setRunOnce();
        }

        if (!mImageRefresh->isRunning())
        {
            MSG_DEBUG("Starting a refresh thread.");
            mImageRefresh->run(url);
        }
    }
    else if (resource->refresh == 0 && !resource->dynamo)
    {
        MSG_DEBUG("Retrieving single image");

        try
        {
            char *content = nullptr;
            size_t length = 0, contentlen = 0;

            if ((content = gPrjResources->tcall(&length, url, resource->user, resource->password)) == nullptr)
                return;

            contentlen = gPrjResources->getContentSize();
            MSG_DEBUG("Loaded " << contentlen << " bytes:");
            sk_sp<SkData> data = SkData::MakeWithCopy(content, contentlen);

            if (!data)
            {
                MSG_ERROR("Error making image data!");
                return;
            }

            SkBitmap image;

            if (!DecodeDataToBitmap(data, &image))
            {
                MSG_ERROR("Error creating an image!");
                return;
            }

            loadImage(bm, image, instance);
            return;
        }
        catch (std::exception& e)
        {
            MSG_ERROR(e.what());
            return;
        }
    }
    else
    {
        MSG_DEBUG("Retrieving a video");

        if (_playVideo && !prg_stopped)
        {
            ulong parent = (mHandle >> 16) & 0x0000ffff;
            _playVideo(mHandle, parent, lt, tp, wt, ht, url, resource->user, resource->password);
        }
    }
}

bool TButton::loadImage(SkBitmap* bm, SkBitmap& image, int instance)
{
    DECL_TRACER("TButton::loadImage(SkBitmap* bm, SkBitmap& image, int instance)");

    SkImageInfo info = image.info();
    POSITION_t position = calcImagePosition(info.width(), info.height(), SC_BITMAP, instance);

    if (!position.valid)
    {
        MSG_ERROR("Error calculating the position of the image for button number " << bi);
        return false;
    }

    MSG_DEBUG("Putting bitmap on top of image ...");
    SkPaint paint;
    paint.setBlendMode(SkBlendMode::kSrc);

    SkCanvas can(*bm, SkSurfaceProps(1, kUnknown_SkPixelGeometry));

    if (sr[instance].sb == 0)
        can.drawBitmap(image, position.left, position.top, &paint);
    else    // Scale to fit
    {
        SkRect rect = SkRect::MakeXYWH(position.left, position.top, position.width, position.height);
        sk_sp<SkImage> im = SkImage::MakeFromBitmap(image);
        can.drawImageRect(im, rect, &paint);
    }

    return true;
}

bool TButton::barLevel(SkBitmap* bm, int instance, int level)
{
    DECL_TRACER("TButton::barLevel(SkBitmap* bm, int instance, int level)");

    if (!sr[0].mi.empty() && !sr[1].bm.empty())       // Chameleon image?
    {
        MSG_DEBUG("Chameleon image ...");
        map<int, IMAGE_t>::iterator iterImages1 = mImages.find(sr[0].number);
        map<int, IMAGE_t>::iterator iterImages2 = mImages.find(sr[1].number);
        SkBitmap imgRed(iterImages1->second.imageMi);
        SkBitmap imgMask(iterImages2->second.imageBm);

        SkBitmap img;
        SkPixmap pixmapRed = imgRed.pixmap();
        SkPixmap pixmapMask;

        if (!imgMask.empty())
            pixmapMask = imgMask.pixmap();

        int width = sr[0].mi_width;
        int height = sr[0].mi_height;
        int startX = 0;
        int startY = 0;

        if (dr.compare("horizontal") == 0)
            width = (int)((double)width / ((double)rh - (double)rl) * (double)level);
        else
            height = (int)((double)height / ((double)rh - (double)rl) * (double)level);

        if ((!ri && dr.compare("horizontal") != 0) ||   // range inverted?
            (ri && dr.compare("horizontal") == 0))
        {
            if (dr.compare("horizontal") == 0)
                startX = sr[0].mi_width - width;
            else
                startY = sr[0].mi_height - height;

            width = sr[0].mi_width;
            height = sr[0].mi_height;
        }

        img.allocPixels(SkImageInfo::MakeN32Premul(sr[0].mi_width, sr[0].mi_height));
        SkCanvas canvas(img);
        SkColor col1 = TColor::getSkiaColor(sr[1].cf);
        SkColor col2 = TColor::getSkiaColor(sr[1].cb);

        for (int ix = 0; ix < sr[0].mi_width; ix++)
        {
            for (int iy = 0; iy < sr[0].mi_height; iy++)
            {
                SkPaint paint;
                SkColor pixel;

                if (ix >= startX && ix < width && iy >= startY && iy < height)
                {
                    SkColor pixelRed = pixmapRed.getColor(ix, iy);
                    SkColor pixelMask;

                    if (!imgMask.empty())
                        pixelMask = pixmapMask.getColor(ix, iy);
                    else
                        pixelMask = SK_ColorWHITE;

                    pixel = baseColor(pixelRed, pixelMask, col1, col2);
                }
                else
                    pixel = SK_ColorTRANSPARENT;

                paint.setColor(pixel);
                canvas.drawPoint(ix, iy, paint);
            }
        }

        if (img.empty())
        {
            MSG_ERROR("Error creating the cameleon image \"" << sr[0].mi << "\" / \"" << sr[0].bm << "\"!");
            TError::setError();
            return false;
        }

        MSG_DEBUG("Putting image together ...");
        SkCanvas ctx(img, SkSurfaceProps(1, kUnknown_SkPixelGeometry));
        SkImageInfo info = img.info();
        SkPaint paint;
        paint.setBlendMode(SkBlendMode::kSrcATop);
        ctx.drawBitmap(imgMask, 0, 0, &paint);

        POSITION_t position = calcImagePosition(sr[0].mi_width, sr[0].mi_height, SC_BITMAP, 0);

        if (!position.valid)
        {
            MSG_ERROR("Error calculating the position of the image for button number " << bi << ": " << na);
            TError::setError();
            return false;
        }

        SkCanvas can(*bm, SkSurfaceProps(1, kUnknown_SkPixelGeometry));
        paint.setBlendMode(SkBlendMode::kSrc);
        can.drawBitmap(img, position.left, position.top, &paint);
    }
    else if (!sr[1].bm.empty())
    {
        MSG_DEBUG("Drawing normal image ...");
        map<int, IMAGE_t>::iterator iterImages = mImages.find(sr[1].number);
        SkBitmap image = iterImages->second.imageBm;

        if (image.empty())
        {
            MSG_ERROR("Error creating the image \"" << sr[instance].bm << "\"!");
            TError::setError();
            return false;
        }

        int width = sr[1].mi_width;
        int height = sr[1].mi_height;
        int startX = 0;
        int startY = 0;

        if (dr.compare("horizontal") == 0)
            width = (int)((double)width / ((double)rh - (double)rl) * (double)level);
        else
            height = (int)((double)height / ((double)rh - (double)rl) * (double)level);

        if ((!ri && dr.compare("horizontal") != 0) ||   // range inverted?
            (ri && dr.compare("horizontal") == 0))
        {
            if (dr.compare("horizontal") == 0)
                startX = sr[0].mi_width - width;
            else
                startY = sr[0].mi_height - height;

            width = sr[0].mi_width;
            height = sr[0].mi_height;
        }

        MSG_DEBUG("Putting bitmap on top of image ...");
        SkPaint paint;
        paint.setBlendMode(SkBlendMode::kSrc);
        paint.setColor(TColor::getSkiaColor(sr[1].cf));
        SkCanvas can(*bm, SkSurfaceProps(1, kUnknown_SkPixelGeometry));
        SkRect dst;
        dst.setXYWH(startX, startY, width, height);
        can.drawBitmapRect(image, dst, &paint);
    }
    else
    {
        MSG_DEBUG("No bitmap defined.");
        int width = sr[1].mi_width;
        int height = sr[1].mi_height;
        int startX = 0;
        int startY = 0;

        if (dr.compare("horizontal") == 0)
            width = (int)((double)width / ((double)rh - (double)rl) * (double)level);
        else
            height = (int)((double)height / ((double)rh - (double)rl) * (double)level);

        if ((!ri && dr.compare("horizontal") != 0) ||   // range inverted?
            (ri && dr.compare("horizontal") == 0))
        {
            if (dr.compare("horizontal") == 0)
                startX = sr[0].mi_width - width;
            else
                startY = sr[0].mi_height - height;

            width = sr[0].mi_width;
            height = sr[0].mi_height;
        }

        MSG_DEBUG("Putting bitmap on top of image ...");
//        SkBitmap image;
//        image.allocN32Pixels(wt, ht);
//        image.eraseColor(TColor::getSkiaColor(sr[1].cf));
        SkPaint paint;
        paint.setBlendMode(SkBlendMode::kSrc);
        SkCanvas can(*bm, SkSurfaceProps(1, kUnknown_SkPixelGeometry));
        paint.setStyle(SkPaint::kFill_Style);
        paint.setAntiAlias(true);
        paint.setStrokeWidth(4);
        paint.setColor(TColor::getSkiaColor(sr[1].cb));

        SkRect dst;
        dst.setXYWH(startX, startY, width, height);
        can.drawRect(dst, paint);
    }

    return true;
}

bool TButton::buttonIcon(SkBitmap* bm, int instance)
{
    DECL_TRACER("TButton::buttonIcon(SkBitmap* bm, int instance)");

    if (sr[instance].ii <= 0)
    {
        MSG_TRACE("No icon defined!");
        return true;
    }

    MSG_DEBUG("Drawing an icon ...");

    if (!gIcons)
    {
        gIcons = new TIcons();

        if (TError::isError())
        {
            MSG_ERROR("Error initializing icons!");
            return false;
        }
    }

    string file = gIcons->getFile(sr[instance].ii);
    MSG_DEBUG("Loading icon file " << file);
    sk_sp<SkData> image;
    SkBitmap icon;

    if (!(image = readImage(file)))
        return false;

    DecodeDataToBitmap(image, &icon);

    if (icon.empty())
    {
        MSG_WARNING("Could not create an icon for element " << sr[instance].ii << " on button " << bi << " (" << na << ")");
        return false;
    }

    SkImageInfo info = icon.info();
    POSITION_t position = calcImagePosition(icon.width(), icon.height(), SC_ICON, instance);

    if (!position.valid)
    {
        MSG_ERROR("Error calculating the position of the image for button number " << bi);
        TError::setError();
        return false;
    }

    MSG_DEBUG("Putting Icon on top of bitmap ...");
    SkPaint paint;
    paint.setBlendMode(SkBlendMode::kSrcOver);
    SkCanvas can(*bm, SkSurfaceProps(1, kUnknown_SkPixelGeometry));

    if (position.overflow)
    {
        SkIRect irect;
        SkRect bdst;
        SkBitmap dst;
        int left = (position.left >= 0) ? 0 : position.left * -1;
        int top = (position.top >= 0) ? 0 : position.top * -1;
        int width = std::min(wt, info.width());
        int height = std::min(ht, info.height());
        irect.setXYWH(left, top, width, height);
        bm->getBounds(&bdst);
        can.drawBitmapRect(icon, irect, bdst, &paint);
    }
    else
        can.drawBitmap(icon, position.left, position.top, &paint);

    return true;
}

bool TButton::buttonText(SkBitmap* bm, int instance)
{
    DECL_TRACER("TButton::buttonText(SkBitmap* bm, int instance)");

    if (!sr[instance].te.empty() && _setText && mFonts)       // Is there a text to display?
    {
        MSG_DEBUG("Searching for font number " << sr[instance].fi << " with text " << sr[instance].te);
        FONT_T font = mFonts->getFont(sr[instance].fi);

        if (!font.file.empty())
        {
            SkCanvas canvas(*bm, SkSurfaceProps(1, kUnknown_SkPixelGeometry));
            sk_sp<SkTypeface> typeFace = mFonts->getTypeFace(sr[instance].fi);

            if (!typeFace)
            {
                MSG_ERROR("Error creating type face " << font.fullName);
                TError::setError();
                return false;
            }

            SkScalar fontSizePt = ((SkScalar)font.size * 1.322);
            SkFont skFont(typeFace, fontSizePt);

            SkPaint paint;
            paint.setAntiAlias(true);
            SkColor color = TColor::getSkiaColor(sr[instance].ct);
            paint.setColor(color);
            paint.setStyle(SkPaint::kFill_Style);

            SkFontMetrics metrics;
            skFont.getMetrics(&metrics);
            int lines = numberLines(sr[instance].te);
            MSG_DEBUG("Found " << lines << " lines.");

            if (lines > 1 || sr[instance].ww)
            {
                vector<string> textLines;

                if (!sr[instance].ww)
                    textLines = splitLine(sr[instance].te);
                else
                {
                    textLines = splitLine(sr[instance].te, wt, ht, skFont, paint);
                    lines = textLines.size();
                }

                vector<string>::iterator iter;
                SkScalar Y = 1.0;

                for (iter = textLines.begin(); iter != textLines.end(); iter++)
                {
                    sk_sp<SkTextBlob> blob = SkTextBlob::MakeFromString(iter->c_str(), skFont);
                    SkRect rect;
                    skFont.measureText(iter->c_str(), iter->length(), SkTextEncoding::kUTF8, &rect, &paint);
                    MSG_DEBUG("Triing to print line: " << *iter);
                    POSITION_t position = calcImagePosition(rect.width(), rect.height()*lines, SC_TEXT, instance);

                    if (!position.valid)
                    {
                        MSG_ERROR("Error calculating the text position!");
                        TError::setError();
                        return false;
                    }

                    SkScalar startX = (SkScalar)position.left;
                    SkScalar startY = (SkScalar)position.top;

                    if (Y > 1.0)
                        startY += metrics.fCapHeight; //  metrics.fDescent + metrics.fLeading;
                    else
                        startY += metrics.fCapHeight + metrics.fLeading; //(metrics.fAscent * -1.0);

                    MSG_DEBUG("fAvgCharWidth: " << metrics.fAvgCharWidth);
                    MSG_DEBUG("fCapHeight: " << metrics.fCapHeight);
                    MSG_DEBUG("fAscent:    " << metrics.fAscent);
                    MSG_DEBUG("fDescent:   " << metrics.fDescent);
                    MSG_DEBUG("fLeading:   " << metrics.fLeading);
                    MSG_DEBUG("fXHeight:   " << metrics.fXHeight);
                    MSG_DEBUG("x=" << startX << ", y=" << startY*Y);
                    canvas.drawTextBlob(blob, startX, startY, paint);
                    Y += 1.0;
                }
            }
            else    // single line
            {
                sk_sp<SkTextBlob> blob = SkTextBlob::MakeFromString(sr[instance].te.c_str(), skFont);
                SkRect rect;
                skFont.measureText(sr[instance].te.c_str(), sr[instance].te.length(), SkTextEncoding::kUTF8, &rect, &paint);
                POSITION_t position = calcImagePosition(rect.width(), (rect.height() * (float)lines), SC_TEXT, instance);

                if (!position.valid)
                {
                    MSG_ERROR("Error calculating the text position!");
                    TError::setError();
                    return false;
                }

                MSG_DEBUG("Printing line " << sr[instance].te);
                SkScalar startX = (SkScalar)position.left;
                SkScalar startY = (SkScalar)position.top + metrics.fCapHeight; // + metrics.fLeading; // (metrics.fAscent * -1.0);

                MSG_DEBUG("fAvgCharWidth: " << metrics.fAvgCharWidth);
                MSG_DEBUG("fCapHeight: " << metrics.fCapHeight);
                MSG_DEBUG("fAscent:    " << metrics.fAscent);
                MSG_DEBUG("fDescent:   " << metrics.fDescent);
                MSG_DEBUG("fLeading:   " << metrics.fLeading);
                MSG_DEBUG("fXHeight:   " << metrics.fXHeight);
                MSG_DEBUG("x=" << startX << ", y=" << startY);
                canvas.drawTextBlob(blob, startX, startY, paint);
            }
        }
    }

    return true;
}

bool TButton::buttonBorder(SkBitmap* bm, int instance)
{
    DECL_TRACER("TButton::buttonBorder(SkBitmap* bm, int instance)");

    if (sr[instance].bs.empty())
    {
        MSG_DEBUG("No border defined.");
        return true;
    }

    int i = 0;

    while (sysBorders[i].id)
    {
        if (sr[instance].bs.compare(sysBorders[i].name) == 0)
        {
            MSG_DEBUG("Border " << sysBorders[i].name << " found.");
            SkCanvas canvas(*bm, SkSurfaceProps(1, kUnknown_SkPixelGeometry));
            SkPaint paint;
            SkColor color = TColor::getSkiaColor(sr[instance].cb);

            paint.setColor(color);
            paint.setBlendMode(SkBlendMode::kSrc);
            paint.setStyle(SkPaint::kStroke_Style);
            SkRRect outher, inner;

            switch (sysBorders[i].id)
            {
                case 1: // Single Frame
                case 2: // Double Frame
                case 3: // Quad Frame
                    paint.setStrokeWidth(sysBorders[i].width);
                    canvas.drawRect(calcRect(wt, ht, sysBorders[i].width), paint);
                break;

                // FIXME: Picture frame is missing

                case 5: // Circle 15
                    paint.setStrokeWidth(sysBorders[i].width);
                    canvas.drawRoundRect(calcRect(wt, ht, sysBorders[i].width), 7.0, 7.0, paint);
                    outher = SkRRect::MakeRect({0, 0, (SkScalar)wt, (SkScalar)ht});
                    inner = SkRRect::MakeRectXY({0, 0, (SkScalar)wt, (SkScalar)ht}, 7.0, 7.0);
                    paint.setColor(SK_ColorTRANSPARENT);
                    canvas.drawDRRect(outher, inner, paint);
                break;

                case 6: // Circle 25
                    paint.setStrokeWidth(sysBorders[i].width);
                    canvas.drawRoundRect(calcRect(wt, ht, sysBorders[i].width), 14.0, 14.0, paint);
                    outher = SkRRect::MakeRect({0, 0, (SkScalar)wt, (SkScalar)ht});
                    inner = SkRRect::MakeRectXY({0, 0, (SkScalar)wt, (SkScalar)ht}, 14.0, 14.0);
                    paint.setColor(SK_ColorTRANSPARENT);
                    canvas.drawDRRect(outher, inner, paint);
                break;

                case 7: // Circle 35
                    paint.setStrokeWidth(sysBorders[i].width);
                    canvas.drawRoundRect(calcRect(wt, ht, sysBorders[i].width), 21.0, 21.0, paint);
                    outher = SkRRect::MakeRect({0, 0, (SkScalar)wt, (SkScalar)ht});
                    inner = SkRRect::MakeRectXY({0, 0, (SkScalar)wt, (SkScalar)ht}, 21.0, 21.0);
                    paint.setColor(SK_ColorTRANSPARENT);
                    canvas.drawDRRect(outher, inner, paint);
                break;

                case 8: // Circle 45
                    paint.setStrokeWidth(sysBorders[i].width);
                    canvas.drawRoundRect(calcRect(wt, ht, sysBorders[i].width), 28.0, 28.0, paint);
                    outher = SkRRect::MakeRect({0, 0, (SkScalar)wt, (SkScalar)ht});
                    inner = SkRRect::MakeRectXY({0, 0, (SkScalar)wt, (SkScalar)ht}, 28.0, 28.0);
                    paint.setColor(SK_ColorTRANSPARENT);
                    canvas.drawDRRect(outher, inner, paint);
                break;

                case 9: // Circle 55
                    paint.setStrokeWidth(sysBorders[i].width);
                    canvas.drawRoundRect(calcRect(wt, ht, sysBorders[i].width), 35.0, 35.0, paint);
                    outher = SkRRect::MakeRect({0, 0, (SkScalar)wt, (SkScalar)ht});
                    inner = SkRRect::MakeRectXY({0, 0, (SkScalar)wt, (SkScalar)ht}, 35.0, 35.0);
                    paint.setColor(SK_ColorTRANSPARENT);
                    canvas.drawDRRect(outher, inner, paint);
                break;

                case 10: // Circle 65
                    paint.setStrokeWidth(sysBorders[i].width);
                    canvas.drawRoundRect(calcRect(wt, ht, sysBorders[i].width), 42.0, 42.0, paint);
                    outher = SkRRect::MakeRect({0, 0, (SkScalar)wt, (SkScalar)ht});
                    inner = SkRRect::MakeRectXY({0, 0, (SkScalar)wt, (SkScalar)ht}, 42.0, 42.0);
                    paint.setColor(SK_ColorTRANSPARENT);
                    canvas.drawDRRect(outher, inner, paint);
                break;

                case 11: // Circle 75
                    paint.setStrokeWidth(sysBorders[i].width);
                    canvas.drawRoundRect(calcRect(wt, ht, sysBorders[i].width), 49.0, 49.0, paint);
                    outher = SkRRect::MakeRect({0, 0, (SkScalar)wt, (SkScalar)ht});
                    inner = SkRRect::MakeRectXY({0, 0, (SkScalar)wt, (SkScalar)ht}, 49.0, 49.0);
                    paint.setColor(SK_ColorTRANSPARENT);
                    canvas.drawDRRect(outher, inner, paint);
                break;

                case 12: // Circle 85
                    paint.setStrokeWidth(sysBorders[i].width);
                    canvas.drawRoundRect(calcRect(wt, ht, sysBorders[i].width), 56.0, 56.0, paint);
                    outher = SkRRect::MakeRect({0, 0, (SkScalar)wt, (SkScalar)ht});
                    inner = SkRRect::MakeRectXY({0, 0, (SkScalar)wt, (SkScalar)ht}, 56.0, 56.0);
                    paint.setColor(SK_ColorTRANSPARENT);
                    canvas.drawDRRect(outher, inner, paint);
                break;

                case 13: // Circle 95
                    paint.setStrokeWidth(sysBorders[i].width);
                    canvas.drawRoundRect(calcRect(wt, ht, sysBorders[i].width), 63.0, 63.0, paint);
                    outher = SkRRect::MakeRect({0, 0, (SkScalar)wt, (SkScalar)ht});
                    inner = SkRRect::MakeRectXY({0, 0, (SkScalar)wt, (SkScalar)ht}, 63.0, 63.0);
                    paint.setColor(SK_ColorTRANSPARENT);
                    canvas.drawDRRect(outher, inner, paint);
                break;

                case 14: // Circle 105
                    paint.setStrokeWidth(sysBorders[i].width);
                    canvas.drawRoundRect(calcRect(wt, ht, sysBorders[i].width), 70.0, 70.0, paint);
                    outher = SkRRect::MakeRect({0, 0, (SkScalar)wt, (SkScalar)ht});
                    inner = SkRRect::MakeRectXY({0, 0, (SkScalar)wt, (SkScalar)ht}, 70.0, 70.0);
                    paint.setColor(SK_ColorTRANSPARENT);
                    canvas.drawDRRect(outher, inner, paint);
                break;

                case 15: // Circle 115
                    paint.setStrokeWidth(sysBorders[i].width);
                    canvas.drawRoundRect(calcRect(wt, ht, sysBorders[i].width), 77.0, 77.0, paint);
                    outher = SkRRect::MakeRect({0, 0, (SkScalar)wt, (SkScalar)ht});
                    inner = SkRRect::MakeRectXY({0, 0, (SkScalar)wt, (SkScalar)ht}, 77.0, 77.0);
                    paint.setColor(SK_ColorTRANSPARENT);
                    canvas.drawDRRect(outher, inner, paint);
                break;

                case 16: // Circle 125
                    paint.setStrokeWidth(sysBorders[i].width);
                    canvas.drawRoundRect(calcRect(wt, ht, sysBorders[i].width), 84.0, 84.0, paint);
                    outher = SkRRect::MakeRect({0, 0, (SkScalar)wt, (SkScalar)ht});
                    inner = SkRRect::MakeRectXY({0, 0, (SkScalar)wt, (SkScalar)ht}, 84.0, 84.0);
                    paint.setColor(SK_ColorTRANSPARENT);
                    canvas.drawDRRect(outher, inner, paint);
                break;
            }
        }

        i++;
    }

    return true;
}

int TButton::numberLines(const string& str)
{
    DECL_TRACER("TButton::numberLines(const string& str)");

    int lines = 1;

    for (size_t i = 0; i < str.length(); i++)
    {
        if (str.at(i) == '\n')
            lines++;
    }

    return lines;
}

vector<string> TButton::splitLine(const string& str)
{
    DECL_TRACER("TButton::splitLine(const string& str)");

    vector<string> lines;
    string sl;
    size_t len = str.length();

    for (size_t i = 0; i < len; i++)
    {
        if (str.at(i) == '\r')  // ignore bloating byte coming from brain death windows
            continue;

        if (str.at(i) == '\n')
        {
            lines.push_back(sl);
            sl.clear();
            continue;
        }

        sl.append(str.substr(i, 1));
    }

    if (!sl.empty())
        lines.push_back(sl);

    return lines;
}

vector<string> TButton::splitLine(const string& str, int width, int height, SkFont& font, SkPaint& paint)
{
    DECL_TRACER("TButton::splitLine(const string& str, SkFont& font)");

    SkRect rect;
    size_t len = str.length();
    vector<string> lines;
    SkScalar lnHeight = font.getSize();
    int maxLines = (int)((SkScalar)height / lnHeight);

    for (size_t i = 0; i < len; i++)
    {
        string part = str.substr(0, i + 1);
        font.measureText(part.c_str(), part.length(), SkTextEncoding::kUTF8, &rect, &paint);

        if (rect.width() > width)
        {
            string ln = part.substr(0, part.length() - 1);
            lines.push_back(ln);
            part = part.substr(part.length() - 1);

            if (lines.size() >= (size_t)maxLines)
                return lines;
        }

        if ((i + 1) == len)
            lines.push_back(part);
    }

    return lines;
}

SkRect TButton::calcRect(int width, int height, int pen)
{
    DECL_TRACER("TButton::calcRect(int width, int height, int pen)");
    SkRect rect;

    SkScalar left = (SkScalar)pen / 2.0;
    SkScalar top = (SkScalar)pen / 2.0;
    SkScalar w = (SkScalar)width - (SkScalar)pen / 2.0 - left;
    SkScalar h = (SkScalar)height - (SkScalar)pen / 2.0 - top;
    rect.setXYWH(left, top, w, h);
    return rect;
}

void TButton::runAnimation()
{
    DECL_TRACER("TButton::runAnimation()");

    mAniRunning = true;
    int instance = 0;
    int max = (int)sr.size();
    ulong tm = nu * 100 + nd * 100;

    while (mAniRunning)
    {
        if (!drawButton(instance))
            break;

        instance++;

        if (instance >= max)
            instance = 0;

        std::this_thread::sleep_for(std::chrono::milliseconds(tm));
    }

    mAniRunning = false;
}

bool TButton::drawButtonMultistateAni()
{
    DECL_TRACER("TButton::drawButtonMultistate(int instance, bool show)");

    if (mAniRunning || mThrAni.joinable())
    {
        MSG_INFO("Animation is already running!");
        return true;
    }

    try
    {
        mThrAni = thread([=] { runAnimation(); });
        mThrAni.detach();
    }
    catch (exception& e)
    {
        MSG_ERROR("Error starting the button animation thread: " << e.what());
        return false;
    }

    return true;
}

bool TButton::drawButton(int instance, bool show)
{
    mutex_button.lock();
    DECL_TRACER("TButton::drawButton(int instance, bool show)");

    if ((size_t)instance >= sr.size() || instance < 0)
    {
        MSG_ERROR("Instance " << instance << " is out of bounds!");
        TError::setError();
        mutex_button.unlock();
        return false;
    }

    if (!visible || instance != mActInstance || !_displayButton)
    {
        bool db = (_displayButton != nullptr);
        MSG_INFO("Button " << bi << ", \"" << na << "\" at instance " << instance << " is not to draw!");
        MSG_DEBUG("Visible: " << (visible ? "YES" : "NO") << ", Instance/actual instance: " << instance << "/" << mActInstance << ", callback: " << (db ? "PRESENT" : "N/A"));
        mutex_button.unlock();
        return true;
    }

    ulong parent = mHandle & 0xffff0000;
    getDrawOrder(sr[instance]._do, (DRAW_ORDER *)&mDOrder);

    if (TError::isError())
    {
        mutex_button.unlock();
        return false;
    }

    SkBitmap imgButton;
    imgButton.allocN32Pixels(wt, ht);

    for (int i = 0; i < ORD_ELEM_COUNT; i++)
    {
        if (mDOrder[i] == ORD_ELEM_FILL)
        {
            if (!buttonFill(&imgButton, instance))
            {
                mutex_button.unlock();
                return false;
            }
        }
        else if (mDOrder[i] == ORD_ELEM_BITMAP)
        {
            if (!sr[instance].dynamic && !buttonBitmap(&imgButton, instance))
            {
                mutex_button.unlock();
                return false;
            }
            else if (sr[instance].dynamic && !buttonDynamic(&imgButton, instance))
            {
                mutex_button.unlock();
                return false;
            }
        }
        else if (mDOrder[i] == ORD_ELEM_ICON)
        {
            if (!buttonIcon(&imgButton, instance))
            {
                mutex_button.unlock();
                return false;
            }
        }
        else if (mDOrder[i] == ORD_ELEM_TEXT)
        {
            if (!buttonText(&imgButton, instance))
            {
                mutex_button.unlock();
                return false;
            }
        }
        else if (mDOrder[i] == ORD_ELEM_BORDER)
        {
            if (!buttonBorder(&imgButton, instance))
            {
                mutex_button.unlock();
                return false;
            }
        }
    }

    if (mGlobalOO >= 0 || sr[instance].oo >= 0) // Take overall opacity into consideration
    {
        SkBitmap ooButton;
        int w = imgButton.width();
        int h = imgButton.height();
        ooButton.allocN32Pixels(w, h, true);
        SkCanvas canvas(ooButton);
        SkIRect irect = SkIRect::MakeXYWH(0, 0, w, h);
        SkRegion region;
        region.setRect(irect);
        SkScalar oo;

        if (mGlobalOO >= 0 && sr[instance].oo >= 0)
        {
            oo = std::min((SkScalar)mGlobalOO, (SkScalar)sr[instance].oo);
            MSG_DEBUG("Set global overal opacity to " << oo);
        }
        else if (sr[instance].oo >= 0)
        {
            oo = (SkScalar)sr[instance].oo;
            MSG_DEBUG("Set overal opacity to " << oo);
        }
        else
        {
            oo = (SkScalar)mGlobalOO;
            MSG_DEBUG("Set global overal opacity to " << oo);
        }

        SkScalar alpha = 1.0 / 255.0 * oo;
        MSG_DEBUG("Calculated alpha value: " << alpha);
        SkPaint paint;
        paint.setAlphaf(alpha);
        paint.setImageFilter(SkImageFilters::AlphaThreshold(region, 0.0, alpha, nullptr));
        canvas.drawBitmap(imgButton, 0, 0, &paint);
        imgButton.erase(SK_ColorTRANSPARENT, {0, 0, w, h});
        imgButton = ooButton;
    }

    mLastImage = imgButton;
    size_t rowBytes = imgButton.info().minRowBytes();

    if (!prg_stopped && show && visible && instance == mActInstance && _displayButton)
        _displayButton(mHandle, parent, (unsigned char *)imgButton.getPixels(), wt, ht, rowBytes, lt, tp);

    mutex_button.unlock();
    return true;
}

bool TButton::drawBargraph(int instance, int level, bool show)
{
    mutex_bargraph.lock();
    DECL_TRACER("TButton::drawBargraph(int instance, bool show)");

    if ((size_t)instance >= sr.size() || instance < 0)
    {
        MSG_ERROR("Instance " << instance << " is out of bounds!");
        TError::setError();
        mutex_bargraph.unlock();
        return false;
    }

    if (level < rl)
        mLastLevel = rl;
    else if (level > rh)
        mLastLevel = rh;
    else
        mLastLevel = level;

    if (!visible || instance != mActInstance || !_displayButton)
    {
        bool db = (_displayButton != nullptr);
        MSG_INFO("Bargraph " << bi << ", \"" << na << "\" at instance " << instance << " with level " << mLastLevel << " is not to draw!");
        MSG_DEBUG("Visible: " << (visible ? "YES" : "NO") << ", Instance/actual instance: " << instance << "/" << mActInstance << ", callback: " << (db ? "PRESENT" : "N/A"));
        mutex_bargraph.unlock();
        return true;
    }

    ulong parent = mHandle & 0xffff0000;
    
    if (type == BARGRAPH)
        getDrawOrder(sr[1]._do, (DRAW_ORDER *)&mDOrder);
    else
        getDrawOrder(sr[instance]._do, (DRAW_ORDER *)&mDOrder);
    
    if (TError::isError())
    {
        mutex_bargraph.unlock();
        return false;
    }

    SkBitmap imgButton;
    imgButton.allocN32Pixels(wt, ht);

    for (int i = 0; i < ORD_ELEM_COUNT; i++)
    {
        if (mDOrder[i] == ORD_ELEM_FILL)
        {
            if (!buttonFill(&imgButton, instance))
            {
                mutex_bargraph.unlock();
                return false;
            }
        }
        else if (mDOrder[i] == ORD_ELEM_BITMAP)
        {
            if (!barLevel(&imgButton, instance, mLastLevel))
            {
                mutex_bargraph.unlock();
                return false;
            }
        }
        else if (mDOrder[i] == ORD_ELEM_ICON)
        {
            if (!buttonIcon(&imgButton, instance))
            {
                mutex_bargraph.unlock();
                return false;
            }
        }
        else if (mDOrder[i] == ORD_ELEM_TEXT)
        {
            if (!buttonText(&imgButton, instance))
            {
                mutex_bargraph.unlock();
                return false;
            }
        }
        else if (mDOrder[i] == ORD_ELEM_BORDER)
        {
            if (!buttonBorder(&imgButton, instance))
            {
                mutex_bargraph.unlock();
                return false;
            }
        }
    }

    if (mGlobalOO >= 0 || sr[instance].oo >= 0) // Take overall opacity into consideration
    {
        SkBitmap ooButton;
        int w = imgButton.width();
        int h = imgButton.height();
        ooButton.allocN32Pixels(w, h, true);
        SkCanvas canvas(ooButton);
        SkIRect irect = SkIRect::MakeXYWH(0, 0, w, h);
        SkRegion region;
        region.setRect(irect);
        SkScalar oo;

        if (mGlobalOO >= 0 && sr[instance].oo >= 0)
        {
            oo = std::min((SkScalar)mGlobalOO, (SkScalar)sr[instance].oo);
            MSG_DEBUG("Set global overal opacity to " << oo);
        }
        else if (sr[instance].oo >= 0)
        {
            oo = (SkScalar)sr[instance].oo;
            MSG_DEBUG("Set overal opacity to " << oo);
        }
        else
        {
            oo = (SkScalar)mGlobalOO;
            MSG_DEBUG("Set global overal opacity to " << oo);
        }

        SkScalar alpha = 1.0 / 255.0 * oo;
        MSG_DEBUG("Calculated alpha value: " << alpha);
        SkPaint paint;
        paint.setAlphaf(alpha);
        paint.setImageFilter(SkImageFilters::AlphaThreshold(region, 0.0, alpha, nullptr));
        canvas.drawBitmap(imgButton, 0, 0, &paint);
        imgButton.erase(SK_ColorTRANSPARENT, {0, 0, w, h});
        imgButton = ooButton;
    }

    mLastImage = imgButton;
    size_t rowBytes = imgButton.info().minRowBytes();

    if (!prg_stopped && show && visible && instance == mActInstance && _displayButton)
        _displayButton(mHandle, parent, (unsigned char *)imgButton.getPixels(), wt, ht, rowBytes, lt, tp);

    mutex_bargraph.unlock();
    return true;
}

POSITION_t TButton::calcImagePosition(int width, int height, CENTER_CODE cc, int number)
{
    DECL_TRACER("TButton::calcImagePosition(int with, int height, CENTER_CODE code, int number)");

    SR_T act_sr;
    POSITION_t position;
    int ix, iy;

    if (sr.size() == 0)
        return position;

    if (number <= 0)
        act_sr = sr.at(0);
    else if ((size_t)number < sr.size())
        act_sr = sr.at(number);
    else
        return position;

    int border_size = getBorderSize(act_sr.bs);
    int code, border = border_size;
    string dbgCC;
    int rwt = 0, rht = 0;

    switch (cc)
    {
        case SC_ICON:
            code = act_sr.ji;
            ix = act_sr.ix;
            iy = act_sr.iy;
            border = border_size = 0;
            dbgCC = "ICON";
            rwt = width;
            rht = height;
        break;

        case SC_BITMAP:
            code = act_sr.jb;
            ix = act_sr.bx;
            iy = act_sr.by;
            dbgCC = "BITMAP";
            rwt = std::min(wt - border * 2, width);
            rht = std::min(ht - border_size * 2, height);
        break;

        case SC_TEXT:
            code = act_sr.jt;
            ix = act_sr.tx;
            iy = act_sr.ty;
            dbgCC = "TEXT";
            border += 4;
            rwt = std::min(wt - border * 2, width);
            rht = std::min(ht - border_size * 2, height);
        break;
    }

    if (width > rwt || height > rht)
        position.overflow = true;

    switch (code)
    {
        case 0: // absolute position
            position.left = ix;
            position.top = iy;
            position.width = rwt;
            position.height = rht;
        break;

        case 1: // top, left
            if (cc == SC_TEXT)
                position.left = border;

            position.width = rwt;
            position.height = rht;
        break;

        case 2: // center, top
            position.left = (wt - rwt) / 2;
            position.height = rht;
            position.width = rwt;
        break;

        case 3: // right, top
            position.left = wt - rwt;

            if (cc == SC_TEXT)
                position.left = (((position.left - border) < 0) ? 0 : position.left - border);

            position.width = rwt;
            position.height = rht;
        break;

        case 4: // left, middle
            if (cc == SC_TEXT)
                position.left = border;

            position.top = (ht - rht) / 2;
            position.width = rwt;
            position.height = rht;
        break;

        case 6: // right, middle
            position.left = wt - rwt;

            if (cc == SC_TEXT)
                position.left = (((position.left - border) < 0) ? 0 : position.left - border);

            position.top = (ht - rht) / 2;
            position.width = rwt;
            position.height = rht;
        break;

        case 7: // left, bottom
            if (cc == SC_TEXT)
                position.left = border_size;

            position.top = ht - rht;
            position.width = rwt;
            position.height = rht;
        break;

        case 8: // center, bottom
            position.left = (wt - rwt) / 2;
            position.top = ht - rht;
            position.width = rwt;
            position.height = rht;
        break;

        case 9: // right, bottom
            position.left = wt - rwt;

            if (cc == SC_TEXT)
                position.left = (((position.left - border) < 0) ? 0 : position.left - border);

            position.top = ht - rht;
        break;

        default: // center, middle
            position.left = (wt - rwt) / 2;
            position.top = (ht - rht) / 2;
            position.width = rwt;
            position.height = rht;
    }

    MSG_DEBUG("Type: " << dbgCC << ", PosType=" << code << ", Position: x=" << position.left << ", y=" << position.top << ", w=" << position.width << ", h=" << position.height << ", Overflow: " << (position.overflow ? "YES" : "NO"));
    position.valid = true;
    return position;
}

int TButton::getBorderSize(const std::string& name)
{
    DECL_TRACER("TButton::getBorderSize(const std::string& name)");

    for (int i = 0; sysBorders[i].name != nullptr; i++)
    {
        if (name.compare(sysBorders[i].name) == 0)
        {
            MSG_DEBUG("Border size: " << sysBorders[i].width);
            return sysBorders[i].width;
        }
    }

    MSG_DEBUG("Border size: 0");
    return 0;
}

void TButton::calcImageSizePercent(int imWidth, int imHeight, int btWidth, int btHeight, int btFrame, int *realX, int *realY)
{
    DECL_TRACER("TButton::clacImageSizePercent(int imWidth, int imHeight, int btWidth, int btHeight, int btFrame, int *realX, int *realY)");

    int spX = btWidth - (btFrame * 2);
    int spY = btHeight - (btFrame * 2);

    if (imWidth <= spX && imHeight <= spY)
    {
        *realX = imWidth;
        *realY = imHeight;
        return;
    }

    int oversizeX = 0, oversizeY = 0;

    if (imWidth > spX)
        oversizeX = imWidth - spX;

    if (imHeight > spY)
        oversizeY = imHeight - spY;

    double percent = 0.0;

    if (oversizeX > oversizeY)
        percent = 100.0 / (double)imWidth * (double)spX;
    else
        percent = 100.0 / (double)imHeight * (double)spY;

    *realX = (int)(percent / 100.0 * (double)imWidth);
    *realY = (int)(percent / 100.0 * (double)imHeight);
}

SkBitmap TButton::drawImageButton(SkBitmap& imgRed, SkBitmap& imgMask, int width, int height, SkColor col1, SkColor col2)
{
    DECL_TRACER("TButton::drawImageButton(SkImage& imgRed, SkImage& imgMask, int width, int height, SkColor col1, SkColor col2)");

    SkPixmap pixmapRed = imgRed.pixmap();
    SkPixmap pixmapMask;

    if (!imgMask.empty())
        pixmapMask = imgMask.pixmap();

    SkBitmap maskBm;
    maskBm.allocPixels(SkImageInfo::MakeN32Premul(width, height));
    SkCanvas canvas(maskBm);

    for (int ix = 0; ix < width; ix++)
    {
        for (int iy = 0; iy < height; iy++)
        {
            SkColor pixelRed = pixmapRed.getColor(ix, iy);
            SkColor pixelMask;

            if (!imgMask.empty())
                pixelMask = pixmapMask.getColor(ix, iy);
            else
                pixelMask = SK_ColorWHITE;

            SkColor pixel = baseColor(pixelRed, pixelMask, col1, col2);
            uint32_t alpha = SkColorGetA(pixel);
            SkPaint paint;
            
            if (alpha == 0)
                pixel = pixelMask;

            paint.setColor(pixel);
            canvas.drawPoint(ix, iy, paint);
        }
    }

    return maskBm;
}

void TButton::show()
{
    DECL_TRACER("TButton::show()");

    visible = true;
    makeElement();

    if (isSystemButton() && !mSystemReg)
        registerSystemButton();
}

void TButton::showLastButton()
{
    DECL_TRACER("TButton::showLastButton()");

    if (mLastImage.empty())
        return;

    if (!prg_stopped && visible && _displayButton)
    {
        ulong parent = mHandle & 0xffff0000;
        size_t rowBytes = mLastImage.info().minRowBytes();
        _displayButton(mHandle, parent, (unsigned char *)mLastImage.getPixels(), wt, ht, rowBytes, lt, tp);
    }
}

void TButton::hide(bool total)
{
    DECL_TRACER("TButton::hide()");

    if (type == MULTISTATE_GENERAL && ar == 1)
        mAniRunning = false;

    if (!prg_stopped && total)
    {
        SkBitmap imgButton;
        imgButton.allocN32Pixels(wt, ht);
        imgButton.eraseColor(SK_ColorTRANSPARENT);
        ulong parent = mHandle & 0xffff0000;
        size_t rowBytes = imgButton.info().minRowBytes();
        _displayButton(mHandle, parent, (unsigned char *)imgButton.getPixels(), wt, ht, rowBytes, lt, tp);
    }

    visible = false;
}

bool TButton::isClickable()
{
    DECL_TRACER("TButton::isClickable()");

    if (mEnabled && ((cp != 0 && ch != 0) || !op.empty() || !pushFunc.empty()) && hs.compare("passThru") != 0)
        return true;

    return false;
}

/**
 * Handling of system button "connection state". It consists of 12 states
 * indicating the network status. The states have the following meaning:
 *
 * 0      Diconnected (never was connected before since startup)
 * 1 - 6  Connected (blink may be shown with dark and light green)
 * 7, 8   Disconnected (timeout or loss of connection)
 * 9 - 11 Connection in progress
 */
void TButton::funcNetwork(int state)
{
    mutex_sysdraw.lock();
    DECL_TRACER("TButton::funcNetwork(int state)");

    mLastLevel = state;
    mActInstance = state;

    if (visible)
        makeElement(state);

    mutex_sysdraw.unlock();
}

/**
 * Handling the timer event from the controller. This comes usualy every
 * 20th part of a second (1 second / 20)
 */
void TButton::funcTimer(const amx::ANET_BLINK& blink)
{
    mutex_sysdraw.lock();
    DECL_TRACER("TButton::funcTimer(const amx::ANET_BLINK& blink)");

    string tm;
    std::stringstream sstr;

    switch (ad)
    {
        case 141:   // Standard time
            sstr << std::setw(2) << std::setfill('0') << (int)blink.hour << ":"
                 << std::setw(2) << std::setfill('0') << (int)blink.minute << ":"
                 << std::setw(2) << std::setfill('0') << (int)blink.second;
            mLastBlink = blink;
        break;

        case 142:   // Time AM/PM
        {
            int hour = (blink.hour > 12) ? (blink.hour - 12) : blink.hour;
            sstr << std::setw(2) << std::setfill('0') << hour << ":"
                 << std::setw(2) << std::setfill('0') << (int)blink.minute << " ";

            if (blink.hour <= 12)
                sstr << "AM";
            else
                sstr << "PM";

            mLastBlink = blink;
        }
        break;

        case 143:   // Time 24 hours
            sstr << std::setw(2) << std::setfill('0') << (int)blink.hour << ":"
                 << std::setw(2) << std::setfill('0') << (int)blink.minute;
            mLastBlink = blink;
        break;

        case 151:   // Weekday
            switch (blink.weekday)
            {
                case 0: sstr << "Monday"; break;
                case 1: sstr << "Tuesday"; break;
                case 2: sstr << "Wednesday"; break;
                case 3: sstr << "Thursday"; break;
                case 4: sstr << "Friday"; break;
                case 5: sstr << "Saturday"; break;
                case 6: sstr << "Sunday"; break;
            }
        break;

        case 152:   // Date mm/dd
            sstr << (int)blink.month << "/" << (int)blink.day;
        break;

        case 153:   // Date dd/mm
            sstr << (int)blink.day << "/" << (int)blink.month;
        break;

        case 154:   // Date mm/dd/yyyy
            sstr << (int)blink.month << "/" << (int)blink.day << "/" << (int)blink.year;
        break;

        case 155:   // Date dd/mm/yyyy
            sstr << blink.day << "/" << blink.month << "/" << blink.year;
        break;

        case 156:   // Date month dd/yyyy
            switch (blink.month)
            {
                case 1:  sstr << "January"; break;
                case 2:  sstr << "February"; break;
                case 3:  sstr << "March"; break;
                case 4:  sstr << "April"; break;
                case 5:  sstr << "May"; break;
                case 6:  sstr << "June"; break;
                case 7:  sstr << "July"; break;
                case 8:  sstr << "August"; break;
                case 9:  sstr << "September"; break;
                case 10:  sstr << "October"; break;
                case 11:  sstr << "November"; break;
                case 12:  sstr << "December"; break;
            }

            sstr << " " << (int)blink.day << "/" << (int)blink.year;
        break;

        case 157:   // Date dd month yyyy
            sstr << (int)blink.day;

            switch (blink.month)
            {
                case 1:  sstr << "January"; break;
                case 2:  sstr << "February"; break;
                case 3:  sstr << "March"; break;
                case 4:  sstr << "April"; break;
                case 5:  sstr << "May"; break;
                case 6:  sstr << "June"; break;
                case 7:  sstr << "July"; break;
                case 8:  sstr << "August"; break;
                case 9:  sstr << "September"; break;
                case 10:  sstr << "October"; break;
                case 11:  sstr << "November"; break;
                case 12:  sstr << "December"; break;
            }

            sstr << " " << (int)blink.year;
        break;

        case 158:   // Date yyyy-mm-dd
            sstr << (int)blink.year << "-" << (int)blink.month << "-" << (int)blink.day;
        break;
    }

    vector<SR_T>::iterator iter;
    tm = sstr.str();

    for (iter = sr.begin(); iter != sr.end(); iter++)
        iter->te = tm;

    if (visible)
        makeElement(mActInstance);

    mutex_sysdraw.unlock();
}

bool TButton::isPixelTransparent(int x, int y)
{
    DECL_TRACER("TButton::isPixelTransparent(int x, int y)");

    if (mLastImage.empty())
    {
        MSG_ERROR("Internal error: No image for button available!");
        return true;
    }

    float alpha = mLastImage.getAlphaf(x, y);

    if (alpha != 0.0)
        return false;

    return true;
}

/**
 * This button got the click because it matches the coordinates of a mouse
 * click. It checkes whether it is clickable or not. If it is clickable, it
 * depends on the type of element what happens.
 */
bool TButton::doClick(int x, int y, bool pressed)
{
    DECL_TRACER("TButton::doClick(bool pressed)");
    amx::ANET_SEND scmd;
    int instance = 0;

    if (!isClickable())
        return false;

    if (type == GENERAL)
    {
        if (fb == FB_MOMENTARY)
        {
            if (pressed)
                instance = 1;
            else
                instance = 0;

            mActInstance = instance;
            // we ignore the state of the method, because it doesn't matter
            // for the message to the controller.
            drawButton(instance, false);
            // If there is nothing in "hs", then it depends on the pixel of the
            // layer. Only if the pixel the coordinates point to are not
            // transparent, the button takes the click.
            if (hs.empty() && isPixelTransparent(x, y))
                return false;

            if (pushFunc.empty())   // Don't draw the button if it has a push function defined
                showLastButton();
            else
                mActInstance = 0;
        }
        else if (fb == FB_CHANNEL || fb == FB_NONE)
        {
            if (pressed)
                instance = 1;
            else
                instance = 0;

            // we ignore the state of the method, because it doesn't matter
            // for the message to the controller.
            drawButton(instance, false);
            // If there is nothing in "hs", then it depends on the pixel of the
            // layer. Only if the pixel the coordinates point to are not
            // transparent, the button takes the click.
            if (hs.empty() && isPixelTransparent(x, y))
                return false;
        }
        else if (fb == FB_INV_CHANNEL)
        {
            if (pressed)
                instance = 0;
            else
                instance = 1;

            // we ignore the state of the method, because it doesn't matter
            // for the message to the controller.
            drawButton(instance, false);
            // If there is nothing in "hs", then it depends on the pixel of the
            // layer. Only if the pixel the coordinates point to are not
            // transparent, the button takes the click.
            if (hs.empty() && isPixelTransparent(x, y))
                return false;
        }
        else if (fb == FB_ALWAYS_ON)
        {
            instance = 1;
            mActInstance = 1;
            // we ignore the state of the method, because it doesn't matter
            // for the message to the controller.
            drawButton(instance, false);
            // If there is nothing in "hs", then it depends on the pixel of the
            // layer. Only if the pixel the coordinates point to are not
            // transparent, the button takes the click.
            if (hs.empty() && isPixelTransparent(x, y))
                return false;
        }

        if ((cp && ch) || !op.empty())
        {
            scmd.device = TConfig::getChannel();
            scmd.port = cp;
            scmd.channel = ch;

            if (op.empty())
            {
                if (instance)
                    scmd.MC = 0x0084;
                else
                    scmd.MC = 0x0085;
            }
            else
            {
                scmd.MC = 0x008b;
                scmd.msg = op;
            }

            MSG_DEBUG("Button " << bi << ", " << na << " with handle " << TObject::handleToString(mHandle));
            MSG_DEBUG("Sending to device <" << scmd.device << ":" << scmd.port << ":0> channel " << scmd.channel << " value 0x" << std::setw(2) << std::setfill('0') << std::hex << scmd.MC << " (" << (pressed?"PUSH":"RELEASE") << ")");

            if (gAmxNet)
            {
                if (scmd.MC != 0x008b || (pressed && scmd.MC == 0x008b))
                    gAmxNet->sendCommand(scmd);
            }
            else
                MSG_WARNING("Missing global class TAmxNet. Can't send a message!");
        }
    }
    else if (type == MULTISTATE_GENERAL)
    {
        if ((cp && ch) || !op.empty())
        {
            scmd.device = TConfig::getChannel();
            scmd.port = cp;
            scmd.channel = ch;

            if (op.empty())
            {
                if (pressed || fb == FB_ALWAYS_ON)
                    scmd.MC = 0x0084;
                else
                    scmd.MC = 0x0085;
            }
            else
            {
                scmd.MC = 0x008b;
                scmd.msg = op;
            }

            MSG_DEBUG("Button " << bi << ", " << na << " with handle " << TObject::handleToString(mHandle));
            MSG_DEBUG("Sending to device <" << scmd.device << ":" << scmd.port << ":0> channel " << scmd.channel << " value 0x" << std::setw(2) << std::setfill('0') << std::hex << scmd.MC << " (" << (pressed?"PUSH":"RELEASE") << ")");

            if (gAmxNet)
            {
                if (scmd.MC != 0x008b || (pressed && scmd.MC == 0x008b))
                    gAmxNet->sendCommand(scmd);
            }
            else
                MSG_WARNING("Missing global class TAmxNet. Can't send a message!");
        }
    }

    /* FIXME: Move the following to class TPageManager!
     *        To do that, the preconditions are to be implemented. It must be
     *        possible to find the button and get access to the credentials
     *        of it.
     */
    if (!pushFunc.empty() && pressed)
    {
        vector<PUSH_FUNC_T>::iterator iter;

        for (iter = pushFunc.begin(); iter != pushFunc.end(); iter++)
        {
            if (iter->pfType == "sShow")            // show popup
            {
                if (gPageManager)
                    gPageManager->showSubPage(iter->pfName);
            }
            else if (iter->pfType == "sHide")       // hide popup
            {
                if (gPageManager)
                    gPageManager->hideSubPage(iter->pfName);
            }
            else if (iter->pfType == "scGroup")     // hide group
            {
                if (gPageManager)
                    gPageManager->closeGroup(iter->pfName);
            }
            else if (iter->pfType == "Stan")        // Flip to standard page
            {
                if (gPageManager)
                {
                    TPage *page = gPageManager->getActualPage();

                    if (!page)
                    {
                        MSG_DEBUG("Internal error: No actual page found!");
                        return false;
                    }

                    TSettings *settings = gPageManager->getSettings();

                    if (settings->getPowerUpPage().compare(page->getName()) != 0)
                        gPageManager->setPage(settings->getPowerUpPage());
                }
            }
            else if (iter->pfType == "Prev")        // Flip to previous page
            {
                if (gPageManager)
                {
                    int old = gPageManager->getPreviousPageNumber();

                    if (old > 0)
                        gPageManager->setPage(old);
                }
            }
            else if (iter->pfType == "sToggle")     // Toggle popup state
            {
                if (gPageManager)
                {
                    TSubPage *page = gPageManager->getSubPage(iter->pfName);

                    if (page && page->isVisible())
                        gPageManager->hideSubPage(iter->pfName);
                    else if (page)
                        gPageManager->showSubPage(iter->pfName);
                }
            }
        }
    }

    return true;
}

/**
 * Based on the pixels in the \a basePix, the function decides whether to return
 * the value of \a col1 or \a col2. A red pixel returns the color \a col1 and
 * a green pixel returns the color \a col2. If there is no red and no green
 * pixel, a transparent pixel is returned.
 *
 * @param basePix
 * This is a pixel from a mask containing red and/or green pixels.
 *
 * @param maskPix
 * This is a pixel from a mask containing more or less tranparent pixels. If
 * the alpha channel of \a basePix is 0 (transparent) this pixel is returned.
 *
 * @param col1
 * The first color.
 *
 * @param col2
 * The second color.
 *
 * @return
 * An array containing the color for one pixel.
 */
SkColor TButton::baseColor(SkColor basePix, SkColor maskPix, SkColor col1, SkColor col2)
{
    uint alpha = SkColorGetA(basePix);
    uint red = SkColorGetR(basePix);
    uint green = SkColorGetG(basePix);

    if (alpha == 0)
        return maskPix;

    if (red && green)
    {
        uint newR = (SkColorGetR(col1) + SkColorGetR(col2) / 2) & 0x0ff;
        uint newG = (SkColorGetG(col1) + SkColorGetG(col2) / 2) & 0x0ff;
        uint newB = (SkColorGetB(col1) + SkColorGetB(col2) / 2) & 0x0ff;
        uint newA = (SkColorGetA(col1) + SkColorGetA(col2) / 2) & 0x0ff;

        return SkColorSetARGB(newA, newR, newG, newB);
    }

    if (red)
        return col1;

    if (green)
        return col2;

    return SK_ColorTRANSPARENT; // transparent pixel
}

TEXT_EFFECT TButton::textEffect(const std::string& effect)
{
    DECL_TRACER("TButton::textEffect(const std::string& effect)");

    if (effect == "Outline-S")
        return EFFECT_OUTLINE_S;
    else if (effect == "Outline-M")
        return EFFECT_OUTLINE_M;
    else if (effect == "Outline-L")
        return EFFECT_OUTLINE_L;
    else if (effect == "Outline-X")
        return EFFECT_OUTLINE_X;
    else if (effect == "Glow-S")
        return EFFECT_GLOW_S;
    else if (effect == "Glow-M")
        return EFFECT_GLOW_M;
    else if (effect == "Glow-L")
        return EFFECT_GLOW_L;
    else if (effect == "Glow-X")
        return EFFECT_GLOW_X;
    else if (effect == "Soft Drop Shadow 1")
        return EFFECT_SOFT_DROP_SHADOW_1;
    else if (effect == "Soft Drop Shadow 2")
        return EFFECT_SOFT_DROP_SHADOW_2;
    else if (effect == "Soft Drop Shadow 3")
        return EFFECT_SOFT_DROP_SHADOW_3;
    else if (effect == "Soft Drop Shadow 4")
        return EFFECT_SOFT_DROP_SHADOW_4;
    else if (effect == "Soft Drop Shadow 5")
        return EFFECT_SOFT_DROP_SHADOW_5;
    else if (effect == "Soft Drop Shadow 6")
        return EFFECT_SOFT_DROP_SHADOW_6;
    else if (effect == "Soft Drop Shadow 7")
        return EFFECT_SOFT_DROP_SHADOW_7;
    else if (effect == "Soft Drop Shadow 8")
        return EFFECT_SOFT_DROP_SHADOW_8;
    else if (effect == "Medium Drop Shadow 1")
        return EFFECT_MEDIUM_DROP_SHADOW_1;
    else if (effect == "Medium Drop Shadow 2")
        return EFFECT_MEDIUM_DROP_SHADOW_2;
    else if (effect == "Medium Drop Shadow 3")
        return EFFECT_MEDIUM_DROP_SHADOW_3;
    else if (effect == "Medium Drop Shadow 4")
        return EFFECT_MEDIUM_DROP_SHADOW_4;
    else if (effect == "Medium Drop Shadow 5")
        return EFFECT_MEDIUM_DROP_SHADOW_5;
    else if (effect == "Medium Drop Shadow 6")
        return EFFECT_MEDIUM_DROP_SHADOW_6;
    else if (effect == "Medium Drop Shadow 7")
        return EFFECT_MEDIUM_DROP_SHADOW_7;
    else if (effect == "Medium Drop Shadow 8")
        return EFFECT_MEDIUM_DROP_SHADOW_8;
    else if (effect == "Hard Drop Shadow 1")
        return EFFECT_HARD_DROP_SHADOW_1;
    else if (effect == "Hard Drop Shadow 2")
        return EFFECT_HARD_DROP_SHADOW_2;
    else if (effect == "Hard Drop Shadow 3")
        return EFFECT_HARD_DROP_SHADOW_3;
    else if (effect == "Hard Drop Shadow 4")
        return EFFECT_HARD_DROP_SHADOW_4;
    else if (effect == "Hard Drop Shadow 5")
        return EFFECT_HARD_DROP_SHADOW_5;
    else if (effect == "Hard Drop Shadow 6")
        return EFFECT_HARD_DROP_SHADOW_6;
    else if (effect == "Hard Drop Shadow 7")
        return EFFECT_HARD_DROP_SHADOW_7;
    else if (effect == "Hard Drop Shadow 8")
        return EFFECT_HARD_DROP_SHADOW_8;
    else if (effect == "Soft Drop Shadow 1 with outline")
        return EFFECT_SOFT_DROP_SHADOW_1_WITH_OUTLINE;
    else if (effect == "Soft Drop Shadow 2 with outline")
        return EFFECT_SOFT_DROP_SHADOW_2_WITH_OUTLINE;
    else if (effect == "Soft Drop Shadow 3 with outline")
        return EFFECT_SOFT_DROP_SHADOW_3_WITH_OUTLINE;
    else if (effect == "Soft Drop Shadow 4 with outline")
        return EFFECT_SOFT_DROP_SHADOW_4_WITH_OUTLINE;
    else if (effect == "Soft Drop Shadow 5 with outline")
        return EFFECT_SOFT_DROP_SHADOW_5_WITH_OUTLINE;
    else if (effect == "Soft Drop Shadow 6 with outline")
        return EFFECT_SOFT_DROP_SHADOW_6_WITH_OUTLINE;
    else if (effect == "Soft Drop Shadow 7 with outline")
        return EFFECT_SOFT_DROP_SHADOW_7_WITH_OUTLINE;
    else if (effect == "Soft Drop Shadow 8 with outline")
        return EFFECT_SOFT_DROP_SHADOW_8_WITH_OUTLINE;
    else if (effect == "Medium Drop Shadow 1 with outline")
        return EFFECT_MEDIUM_DROP_SHADOW_1_WITH_OUTLINE;
    else if (effect == "Medium Drop Shadow 2 with outline")
        return EFFECT_MEDIUM_DROP_SHADOW_2_WITH_OUTLINE;
    else if (effect == "Medium Drop Shadow 3 with outline")
        return EFFECT_MEDIUM_DROP_SHADOW_3_WITH_OUTLINE;
    else if (effect == "Medium Drop Shadow 4 with outline")
        return EFFECT_MEDIUM_DROP_SHADOW_4_WITH_OUTLINE;
    else if (effect == "Medium Drop Shadow 5 with outline")
        return EFFECT_MEDIUM_DROP_SHADOW_5_WITH_OUTLINE;
    else if (effect == "Medium Drop Shadow 6 with outline")
        return EFFECT_MEDIUM_DROP_SHADOW_6_WITH_OUTLINE;
    else if (effect == "Medium Drop Shadow 7 with outline")
        return EFFECT_MEDIUM_DROP_SHADOW_7_WITH_OUTLINE;
    else if (effect == "Medium Drop Shadow 8 with outline")
        return EFFECT_MEDIUM_DROP_SHADOW_8_WITH_OUTLINE;
    else if (effect == "Hard Drop Shadow 1 with outline")
        return EFFECT_HARD_DROP_SHADOW_1_WITH_OUTLINE;
    else if (effect == "Hard Drop Shadow 2 with outline")
        return EFFECT_HARD_DROP_SHADOW_2_WITH_OUTLINE;
    else if (effect == "Hard Drop Shadow 3 with outline")
        return EFFECT_HARD_DROP_SHADOW_3_WITH_OUTLINE;
    else if (effect == "Hard Drop Shadow 4 with outline")
        return EFFECT_HARD_DROP_SHADOW_4_WITH_OUTLINE;
    else if (effect == "Hard Drop Shadow 5 with outline")
        return EFFECT_HARD_DROP_SHADOW_5_WITH_OUTLINE;
    else if (effect == "Hard Drop Shadow 6 with outline")
        return EFFECT_HARD_DROP_SHADOW_6_WITH_OUTLINE;
    else if (effect == "Hard Drop Shadow 7 with outline")
        return EFFECT_HARD_DROP_SHADOW_7_WITH_OUTLINE;
    else if (effect == "Hard Drop Shadow 8 with outline")
        return EFFECT_HARD_DROP_SHADOW_8_WITH_OUTLINE;

    return EFFECT_NONE;
}

bool TButton::isSystemButton()
{
    DECL_TRACER("TButton::isSystemButton()");

    int i = 0;

    while (sysButtons[i].channel)
    {
        if (ap == 0 && ad == sysButtons[i].channel)
            return true;

        i++;
    }

    return false;
}

THR_REFRESH_t *TButton::_addResource(TImageRefresh* refr, ulong handle, ulong parent, int bi)
{
    DECL_TRACER("TButton::_addResource(TImageRefresh* refr, ulong handle, ulong parent, int bi)");

    THR_REFRESH_t *p = mThrRefresh;
    THR_REFRESH_t *r, *last = p;

    if (!refr || !handle || !parent || bi <= 0)
    {
        MSG_ERROR("Invalid parameter!");
        return nullptr;
    }

    r = new THR_REFRESH_t;
    r->mImageRefresh = refr;
    r->handle = handle;
    r->parent = parent;
    r->bi = bi;
    r->next = nullptr;

    // If the chain is empty, add the new item;
    if (!mThrRefresh)
        mThrRefresh = r;
    else    // Find the end and append the item
    {
        while (p)
        {
            last = p;

            if (p->handle == handle && p->parent == parent && p->bi == bi)
            {
                MSG_WARNING("Duplicate button found! Didn't add it again.");
                delete r;
                return p;
            }

            p = p->next;
        }

        last->next = r;
    }

    MSG_DEBUG("New dynamic button added.");
    return r;
}

THR_REFRESH_t *TButton::_findResource(ulong handle, ulong parent, int bi)
{
    DECL_TRACER("TButton::_findResource(ulong handle, ulong parent, int bi)");

    THR_REFRESH_t *p = mThrRefresh;

    while (p)
    {
        if (p->handle == handle && p->parent == parent && p->bi == bi)
            return p;

        p = p->next;
    }

    return nullptr;
}