Subversion Repositories tpanel

Rev

Rev 479 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * Copyright (C) 2020 to 2023 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 <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#ifdef __ANDROID__
#include <android/log.h>
#endif

#include <include/core/SkFont.h>
#include <include/core/SkFontMetrics.h>
#include <include/core/SkTextBlob.h>

#include "tlock.h"
#include "tresources.h"
#include "tpagemanager.h"
#include "tpage.h"
#include "tdrawimage.h"
#include "texpat++.h"
#include "tconfig.h"
#include "terror.h"
#include "ttpinit.h"
#if TESTMODE == 1
#include "testmode.h"
#endif

#if __cplusplus < 201402L
#   error "This module requires at least C++14 standard!"
#else
#   if __cplusplus < 201703L
#       include <experimental/filesystem>
namespace fs = std::experimental::filesystem;
#       warning "Support for C++14 and experimental filesystem will be removed in a future version!"
#   else
#       include <filesystem>
#       ifdef __ANDROID__
namespace fs = std::__fs::filesystem;
#       else
namespace fs = std::filesystem;
#       endif
#   endif
#endif

using std::string;
using std::vector;
using std::map;
using std::pair;
using namespace Button;
using namespace Expat;

extern TPageManager *gPageManager;

TPage::TPage(const string& name)
{
    DECL_TRACER("TPage::TPage(const string& name)");
    TError::clear();

    if (gPageManager)
    {
        if (!_setBackground)
            _setBackground = gPageManager->getCallbackBG();

        if (!_displayButton)
            _displayButton = gPageManager->getCallbackDB();

        if (!_callDropPage)
            _callDropPage = gPageManager->getCallDropPage();

        if (!_callDropSubPage)
            _callDropSubPage = gPageManager->getCallDropSubPage();

        if (!_playVideo)
            _playVideo = gPageManager->getCallbackPV();
    }

    if (name.compare("_progress") == 0)
    {
        addProgress();
        return ;
    }

    initialize(name);
}

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

    MSG_DEBUG("Destroing page " << mPage.pageID << ": " << mPage.name);
    BUTTONS_T *p = getButtons();
    BUTTONS_T *next = nullptr;

    while (p)
    {
        next = p->next;
        delete p->button;
        delete p;
        p = next;
    }

    setButtons(nullptr);
    mSubPages.clear();
}

void TPage::initialize(const string& nm)
{
    DECL_TRACER("TPage::initialize(const string& name)");

    string projectPath = TConfig::getProjectPath();

    if (!fs::exists(projectPath + "/prj.xma"))
    {
        MSG_ERROR("Directory " << projectPath << " doesn't exist!");
        return;
    }

    makeFileName(projectPath, nm);

    MSG_DEBUG("Using path: " << projectPath << " and file: " << nm);

    if (isValidFile())
        mPath = getFileName();

    TExpat xml(mPath);

    if (!TTPInit::isTP5())
        xml.setEncoding(ENC_CP1250);
    else
        xml.setEncoding(ENC_UTF8);

    if (!xml.parse())
        return;

    int depth = 0;
    size_t index = 0;
    size_t oldIndex = 0;
    vector<ATTRIBUTE_t> attrs;

    if ((index = xml.getElementIndex("page", &depth)) == TExpat::npos)
    {
        MSG_ERROR("Element \"page\" with attribute \"type\" was not found!");
        TError::setError();
        return;
    }

    attrs = xml.getAttributes();
    string type = xml.getAttribute("type", attrs);

    if (type.compare("page") != 0)
    {
        MSG_ERROR("Invalid page type \"" << type << "\"!");
        TError::setError();
        return;
    }

    TError::clear();
    depth++;
    string ename, content;

    while ((index = xml.getNextElementFromIndex(index, &ename, &content, &attrs)) != TExpat::npos)
    {
        string e = ename;
        MSG_DEBUG("Element: " << ename << " at index " << index);

        if (e.compare("pageID") == 0)
            mPage.pageID = xml.convertElementToInt(content);
        else if (e.compare("name") == 0)
            mPage.name = content;
        else if (e.compare("width") == 0)
            mPage.width = xml.convertElementToInt(content);
        else if (e.compare("height") == 0)
            mPage.height = xml.convertElementToInt(content);
        else if (e.compare("button") == 0)
        {
            TButton *button = new TButton();
            TPageInterface::registerListCallback<TPage>(button, this);

            button->setPalette(mPalette);
            button->setFonts(getFonts());
            button->registerCallback(_displayButton);
            button->regCallPlayVideo(_playVideo);
            index = button->initialize(&xml, index);
            button->setParentSize(mPage.width, mPage.height);

            if (TError::isError())
            {
                MSG_WARNING("Button \"" << button->getButtonName() << "\" deleted because of an error: " << TError::getErrorMsg());
                delete button;
                return;
            }

            button->setHandle(((mPage.pageID << 16) & 0xffff0000) | button->getButtonIndex());
            button->createButtons();
            addButton(button);
            string sType1 = xml.getElementTypeStr(index);
            string sType2 = xml.getElementTypeStr(index+1);
            MSG_DEBUG("Element type 1: " << sType1);
            MSG_DEBUG("Element type 2: " << sType2);

            if (xml.isElementTypeEnd(index+1))
                index++;        // Jump over the end tag of the button.
        }
        else if (e.compare("sr") == 0)
        {
            SR_T bsr;
            bsr.number = xml.getAttributeInt("number", attrs);
            MSG_DEBUG("Page " << mPage.name << " at State " << bsr.number);
            index++;

            while ((index = xml.getNextElementFromIndex(index, &ename, &content, &attrs)) != TExpat::npos)
            {
                if (ename.compare("bs") == 0)
                    bsr.bs = content;
                else if (ename.compare("cb") == 0)
                    bsr.cb = content;
                else if (ename.compare("cf") == 0)
                    bsr.cf = content;
                else if (ename.compare("ct") == 0)
                    bsr.ct = content;
                else if (ename.compare("ec") == 0)
                    bsr.ec = content;
                else if (ename.compare("bm") == 0)
                    bsr.bm = content;
                else if (ename.compare("mi") == 0)
                    bsr.mi = content;
                else if (ename.compare("fi") == 0)
                    bsr.fi = xml.convertElementToInt(content);
                else if (ename.compare("te") == 0)
                    bsr.te = content;
                else if (ename.compare("tx") == 0)
                    bsr.tx = xml.convertElementToInt(content);
                else if (ename.compare("ty") == 0)
                    bsr.ty = xml.convertElementToInt(content);
                else if (ename.compare("et") == 0)
                    bsr.et = xml.convertElementToInt(content);
                else if (ename.compare("ww") == 0)
                    bsr.ww = xml.convertElementToInt(content);
                else if (ename.compare("jt") == 0)
                    bsr.jt = (Button::ORIENTATION)xml.convertElementToInt(content);
                else if (ename.compare("jb") == 0)
                    bsr.jb = (Button::ORIENTATION)xml.convertElementToInt(content);
                else if (ename.compare("bitmapEntry") == 0)
                {
                    string fname;
                    BITMAPS_t bitmapEntry;
                    MSG_DEBUG("Section: " << ename);

                    while ((index = xml.getNextElementFromIndex(index, &fname, &content, &attrs)) != TExpat::npos)
                    {
                        if (fname.compare("fileName") == 0)
                            bitmapEntry.fileName = content;
                        else if (fname.compare("justification") == 0)
                            bitmapEntry.justification = static_cast<ORIENTATION>(xml.convertElementToInt(content));
                        else if (fname.compare("offsetX") == 0)
                            bitmapEntry.offsetX = xml.convertElementToInt(content);
                        else if (fname.compare("offsetY") == 0)
                            bitmapEntry.offsetY = xml.convertElementToInt(content);

                        oldIndex = index;
                    }

                    MSG_DEBUG("Found image: " << bitmapEntry.fileName << ", justification: " << bitmapEntry.justification << ", Offset: " << bitmapEntry.offsetX << "x" << bitmapEntry.offsetY);
                    bsr.bitmaps.push_back(bitmapEntry);

                    if (index == TExpat::npos)
                        index = oldIndex + 1;
                }

                oldIndex = index;
            }

            sr.push_back(bsr);

            if (index == TExpat::npos)
                index = oldIndex + 1;
        }
    }

    MSG_DEBUG("Setting SR with " << sr.size() << " elements");
    setSR(sr);

    if (TPageInterface::getButtons())
        sortButtons();
}

void TPage::addProgress()
{
    DECL_TRACER("TPage::addProgress()");

    if (!gPageManager)
    {
        MSG_WARNING("The page manager is still not initialized!");
        return;
    }

    Button::SR_T bsr;
    mPage.pageID = 300;
    mPage.name = "_progress";
    mPage.width = gPageManager->getSettings()->getWidth();
    mPage.height = gPageManager->getSettings()->getHeight();
    double unit = (double)mPage.height / 10.0;
    MSG_DEBUG("One unit is " << unit);
    // Background of page
    bsr.number = 1;
    bsr.cf = "#106010ff";
    bsr.ct = "#ffffffff";
    bsr.cb = "#009000ff";
    bsr.ec = "#ffffffff";
    bsr.fi = 21;
    sr.push_back(bsr);
    // Text field 1 to show status messages
    Button::EXTBUTTON_t bt;
    bt.type = GENERAL;
    bt.bi = 1;
    bt.na = "Line1";
    bt.tp = (int)(unit * 2.0);
    bt.lt = (int)(((double)mPage.width - ((double)mPage.width / 100.0 * 80.0)) / 2.0);
    bt.wt = (int)((double)mPage.width / 100.0 * 80.0);    // Line take 80% of available width
    bt.ht = (int)(unit / 100.0 * 80.0);
    MSG_DEBUG("Dimensions button 1: lt: " << bt.lt << ", tp: " << bt.tp << ", wt: " << bt.wt << ", ht: " << bt.ht);
    bt.zo = 1;
    bt.ap = 0;
    bt.ad = 160;
    bsr.cf = "#000000ff";
    bt.sr.push_back(bsr);
    bsr.number = 2;
    bt.sr.push_back(bsr);
    TButton *button = new TButton();
    button->setPalette(mPalette);
    button->setFonts(getFonts());
    button->registerCallback(_displayButton);
    button->regCallPlayVideo(_playVideo);
    button->createSoftButton(bt);
    button->setParentSize(mPage.width, mPage.height);

    if (TError::isError())
    {
        MSG_WARNING("Button \"" << button->getButtonName() << "\" deleted because of an error!");
        delete button;
        return;
    }

    button->setHandle(((mPage.pageID << 16) & 0xffff0000) | bt.bi);
    button->createButtons();
    addButton(button);
    // Text field 2 to show status messages
    bt.bi = 2;
    bt.na = "Line2";
    bt.tp = (int)(unit * 7.0);
    MSG_DEBUG("Dimensions button 2: lt: " << bt.lt << ", tp: " << bt.tp << ", wt: " << bt.wt << ", ht: " << bt.ht);
    bt.zo = 2;
    bt.ad = 161;
    button = new TButton();
    button->setPalette(mPalette);
    button->setFonts(getFonts());
    button->registerCallback(_displayButton);
    button->regCallPlayVideo(_playVideo);
    button->createSoftButton(bt);
    button->setParentSize(mPage.width, mPage.height);

    if (TError::isError())
    {
        MSG_WARNING("Button \"" << button->getButtonName() << "\" deleted because of an error!");
        delete button;
        return;
    }

    button->setHandle(((mPage.pageID << 16) & 0xffff0000) | bt.bi);
    button->createButtons();
    addButton(button);
    // Progress bar 1 (overall status)
    bt.type = BARGRAPH;
    bt.bi = 3;
    bt.na = "Bar1";
    bt.tp = (int)(unit * 3.0);
    bt.lt = (int)(((double)mPage.width - ((double)mPage.width / 100.0 * 80.0)) / 2.0);
    bt.wt = (int)((double)mPage.width / 100.0 * 80.0);    // Line take 80% of available width
    bt.ht = (int)unit;
    MSG_DEBUG("Dimensions bargraph 1: lt: " << bt.lt << ", tp: " << bt.tp << ", wt: " << bt.wt << ", ht: " << bt.ht);
    bt.zo = 3;
    bt.ap = 0;
    bt.ad = 162;
    bt.lp = 0;
    bt.lv = 162;
    bt.rl = 1;
    bt.rh = 100;
    bt.sc = "#ffffffff";
    bt.dr = "horizontal";
    bsr.number = 1;
    bsr.cf = "#0e0e0eff";
    bsr.ct = "#ffffffff";
    bsr.cb = "#009000ff";
    bt.sr.clear();
    bt.sr.push_back(bsr);
    bsr.number = 2;
    bsr.cf = "#ffffffff";
    bt.sr.push_back(bsr);
    button = new TButton();
    button->setPalette(mPalette);
    button->setFonts(getFonts());
    button->registerCallback(_displayButton);
    button->regCallPlayVideo(_playVideo);
    button->createSoftButton(bt);
    button->setParentSize(mPage.width, mPage.height);

    if (TError::isError())
    {
        MSG_WARNING("Button \"" << button->getButtonName() << "\" deleted because of an error!");
        delete button;
        return;
    }

    button->setHandle(((mPage.pageID << 16) & 0xffff0000) | bt.bi);
    button->createButtons();
    addButton(button);
    // Progress bar 2 (details)
    bt.bi = 4;
    bt.na = "Bar2";
    bt.tp = (int)(unit * 5.0);
    MSG_DEBUG("Dimensions bargraph 2: lt: " << bt.lt << ", tp: " << bt.tp << ", wt: " << bt.wt << ", ht: " << bt.ht);
    bt.zo = 4;
    bt.ad = 163;
    bt.lv = 163;
    button = new TButton();
    button->setPalette(mPalette);
    button->setFonts(getFonts());
    button->registerCallback(_displayButton);
    button->regCallPlayVideo(_playVideo);
    button->createSoftButton(bt);
    button->setParentSize(mPage.width, mPage.height);

    if (TError::isError())
    {
        MSG_WARNING("Button \"" << button->getButtonName() << "\" deleted because of an error!");
        delete button;
        return;
    }

    button->setHandle(((mPage.pageID << 16) & 0xffff0000) | bt.bi);
    button->createButtons();
    addButton(button);
}

SkBitmap& TPage::getBgImage()
{
    DECL_TRACER("TPage::getBgImage()");

    if (!mBgImage.empty())
        return mBgImage;

    bool haveImage = false;
    MSG_DEBUG("Creating image for page " << mPage.pageID << ": " << mPage.name);
    SkBitmap target;

    if (!allocPixels(mPage.width, mPage.height, &target))
        return mBgImage;

    target.eraseColor(TColor::getSkiaColor(mPage.sr[0].cf));
    // Draw the background, if any
    if (mPage.sr.size() > 0 && (!mPage.sr[0].bm.empty() || !mPage.sr[0].mi.empty()))
    {
        TDrawImage dImage;
        dImage.setWidth(mPage.width);
        dImage.setHeight(mPage.height);
        dImage.setSr(mPage.sr);

        if (!mPage.sr[0].bm.empty())
        {
            MSG_DEBUG("Loading image " << mPage.sr[0].bm);
            sk_sp<SkData> rawImage = readImage(mPage.sr[0].bm);
            SkBitmap bm;

            if (rawImage)
            {
                MSG_DEBUG("Decoding image BM ...");

                if (!DecodeDataToBitmap(rawImage, &bm))
                {
                    MSG_WARNING("Problem while decoding image " << mPage.sr[0].bm);
                }
                else if (!bm.empty())
                {
                    dImage.setImageBm(bm);
                    SkImageInfo info = bm.info();
                    mPage.sr[0].bm_width = info.width();
                    mPage.sr[0].bm_height = info.height();
                    haveImage = true;
                }
                else
                {
                    MSG_WARNING("BM image " << mPage.sr[0].bm << " seems to be empty!");
                }
            }
        }

        if (!mPage.sr[0].mi.empty())
        {
            MSG_DEBUG("Loading image " << mPage.sr[0].mi);
            sk_sp<SkData> rawImage = readImage(mPage.sr[0].mi);
            SkBitmap mi;

            if (rawImage)
            {
                MSG_DEBUG("Decoding image MI ...");

                if (!DecodeDataToBitmap(rawImage, &mi))
                {
                    MSG_WARNING("Problem while decoding image " << mPage.sr[0].mi);
                }
                else if (!mi.empty())
                {
                    dImage.setImageMi(mi);
                    SkImageInfo info = mi.info();
                    mPage.sr[0].mi_width = info.width();
                    mPage.sr[0].mi_height = info.height();
                    haveImage = true;
                }
                else
                {
                    MSG_WARNING("MI image " << mPage.sr[0].mi << " seems to be empty!");
                }
            }
        }

        if (haveImage)
        {
            dImage.setSr(mPage.sr);

            if (!dImage.drawImage(&target))
                return mBgImage;

            if (!mPage.sr[0].te.empty())
            {
                if (!drawText(mPage, &target))
                    return mBgImage;
            }

            if (mPage.sr[0].oo < 255 && mPage.sr[0].te.empty() && mPage.sr[0].bs.empty())
                setOpacity(&target, mPage.sr[0].oo);

#ifdef _SCALE_SKIA_
            size_t rowBytes = info.minRowBytes();
            size_t size = info.computeByteSize(rowBytes);
            SkImageInfo info = target.info();

            if (gPageManager && gPageManager->getScaleFactor() != 1.0)
            {
                SkPaint paint;
                int left, top;

                paint.setBlendMode(SkBlendMode::kSrc);
                paint.setFilterQuality(kHigh_SkFilterQuality);
                // Calculate new dimension
                double scaleFactor = gPageManager->getScaleFactor();
                MSG_DEBUG("Using scale factor " << scaleFactor);
                int lwidth = (int)((double)info.width() * scaleFactor);
                int lheight = (int)((double)info.height() * scaleFactor);
                int twidth = (int)((double)mPage.width * scaleFactor);
                int theight = (int)((double)mPage.height * scaleFactor);
                calcPosition(lwidth, lheight, &left, &top);
                // Create a canvas and draw new image
                sk_sp<SkImage> im = SkImage::MakeFromBitmap(target);

                if (!allocPixels(twidth, theight, &target))
                    return;

                target.eraseColor(TColor::getSkiaColor(mPage.sr[0].cf));
                SkCanvas can(target, SkSurfaceProps());
                SkRect rect = SkRect::MakeXYWH(left, top, lwidth, lheight);
                can.drawImageRect(im, rect, &paint);
                MSG_DEBUG("Scaled size of background image: " << left << ", " << top << ", " << lwidth << ", " << lheight);
            }
#endif
        }
    }

    if (mPage.sr.size() > 0 && !mPage.sr[0].te.empty())
    {
        MSG_DEBUG("Drawing a text only on background image ...");

        if (drawText(mPage, &target))
            haveImage = true;
    }

    // Check for a frame and draw it if there is one.
    if (!mPage.sr[0].bs.empty())
    {
        if (drawFrame(mPage, &target))
            haveImage = true;
    }

    if (haveImage)
    {
        if (mPage.sr[0].oo < 255)
            setOpacity(&target, mPage.sr[0].oo);

        mBgImage = target;
    }

    return mBgImage;
}

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

    if (!_setBackground)
    {
        if (gPageManager && gPageManager->getCallbackBG())
            _setBackground = gPageManager->getCallbackBG();
        else
        {
            MSG_WARNING("No callback \"setBackground\" was set!");
#if TESTMODE == 1
            setScreenDone();
#endif
            return;
        }
    }

    bool haveImage = false;
    ulong handle = (mPage.pageID << 16) & 0xffff0000;
    MSG_DEBUG("Processing page " << mPage.pageID);
    SkBitmap target;

    if (!allocPixels(mPage.width, mPage.height, &target))
    {
#if TESTMODE == 1
        setScreenDone();
#endif
        return;
    }

    if (sr.empty())
    {
        MSG_WARNING("Page " << mPage.name << " (" << mPage.pageID << "): The SR is empty!");
    }

    if (sr.size() > 0)
        target.eraseColor(TColor::getSkiaColor(sr[0].cf));
    else
        target.eraseColor(SK_ColorTRANSPARENT);

    // Draw the background, if any
    if (sr.size() > 0 && (!sr[0].bm.empty() || !sr[0].mi.empty() || sr[0].bitmaps.size() > 0))
    {
        TDrawImage dImage;
        dImage.setWidth(mPage.width);
        dImage.setHeight(mPage.height);
        dImage.setSr(sr);

        if (!TTPInit::isTP5() && !sr[0].bm.empty())
        {
            MSG_DEBUG("Loading image " << sr[0].bm);
            sk_sp<SkData> rawImage = readImage(sr[0].bm);
            SkBitmap bm;

            if (rawImage && !rawImage->isEmpty())
            {
                MSG_DEBUG("Decoding image BM ...");

                if (!DecodeDataToBitmap(rawImage, &bm))
                {
                    MSG_WARNING("Problem while decoding image " << sr[0].bm);
                }
                else if (!bm.isNull() && !bm.empty())
                {
                    dImage.setImageBm(bm);
                    SkImageInfo info = bm.info();
                    sr[0].bm_width = info.width();
                    sr[0].bm_height = info.height();
                    haveImage = true;
                    MSG_DEBUG("Image " << sr[0].bm << " has dimension " << sr[0].bm_width << " x " << sr[0].bm_height);
                }
                else
                {
                    MSG_WARNING("BM image " << sr[0].bm << " seems to be empty!");
                }
            }
        }
        else if (TTPInit::isTP5() && sr[0].bitmaps.size() > 0)
        {
            vector<BITMAPS_t>::iterator iter;
            SkBitmap image;

            for (iter = sr[0].bitmaps.begin(); iter != sr[0].bitmaps.end(); ++iter)
            {
                MSG_DEBUG("Loading TP5 image " << iter->fileName);
                sk_sp<SkData> rawImage = readImage(iter->fileName);
                SkBitmap bm;

                if (rawImage && !rawImage->isEmpty())
                {
                    MSG_DEBUG("Decoding image BM ...");

                    if (!DecodeDataToBitmap(rawImage, &bm))
                    {
                        MSG_WARNING("Problem while decoding image " << iter->fileName);
                    }
                    else if (!bm.isNull() && !bm.empty())
                    {
                        dImage.setImageBm(bm);
                        SkImageInfo info = bm.info();
                        haveImage = true;
                        MSG_DEBUG("Image " << iter->fileName << " has dimension " << bm.width() << " x " << bm.height());
                    }
                    else
                    {
                        MSG_WARNING("BM image " << sr[0].bm << " seems to be empty!");
                    }
                }
            }
        }

        MSG_DEBUG("haveImage: " << (haveImage ? "TRUE" : "FALSE"));

        if (!sr[0].mi.empty())
        {
            MSG_DEBUG("Loading image " << sr[0].mi);
            sk_sp<SkData> rawImage = readImage(sr[0].mi);
            SkBitmap mi;

            if (rawImage && !rawImage->isEmpty())
            {
                MSG_DEBUG("Decoding image MI ...");

                if (!DecodeDataToBitmap(rawImage, &mi))
                {
                    MSG_WARNING("Problem while decoding image " << sr[0].mi);
                }
                else if (!mi.isNull() && !mi.empty())
                {
                    dImage.setImageMi(mi);
                    SkImageInfo info = mi.info();
                    sr[0].mi_width = info.width();
                    sr[0].mi_height = info.height();
                    haveImage = true;
                }
                else
                {
                    MSG_WARNING("MI image " << sr[0].mi << " seems to be empty!");
                }
            }
        }

        if (haveImage)
        {
            dImage.setSr(sr);

            if (!dImage.drawImage(&target))
            {
#if TESTMODE == 1
                setScreenDone();
#endif
                return;
            }

            if (!sr[0].te.empty())
            {
                if (!drawText(mPage, &target))
                {
#if TESTMODE == 1
                    setScreenDone();
#endif
                    return;
                }
            }

#ifdef _SCALE_SKIA_
            if (gPageManager && gPageManager->getScaleFactor() != 1.0)
            {
                SkPaint paint;
                int left, top;
                SkImageInfo info = target.info();

                paint.setBlendMode(SkBlendMode::kSrc);
                paint.setFilterQuality(kHigh_SkFilterQuality);
                // Calculate new dimension
                double scaleFactor = gPageManager->getScaleFactor();
                MSG_DEBUG("Using scale factor " << scaleFactor);
                int lwidth = (int)((double)info.width() * scaleFactor);
                int lheight = (int)((double)info.height() * scaleFactor);
                int twidth = (int)((double)width * scaleFactor);
                int theight = (int)((double)height * scaleFactor);
                calcPosition(lwidth, lheight, &left, &top);
                // Create a canvas and draw new image
                sk_sp<SkImage> im = SkImage::MakeFromBitmap(target);

                if (!allocPixels(twidth, theight, &target))
                {
#if TESTMODE == 1
                    setScreenDone();
#endif
                    return;
                }

                target.eraseColor(TColor::getSkiaColor(sr[0].cf));
                SkCanvas can(target, SkSurfaceProps());
                SkRect rect = SkRect::MakeXYWH(left, top, lwidth, lheight);
                can.drawImageRect(im, rect, &paint);
                MSG_DEBUG("Scaled size of background image: " << left << ", " << top << ", " << lwidth << ", " << lheight);
            }
#endif
        }
    }

    if (sr.size() > 0 && !sr[0].te.empty())
    {
        MSG_DEBUG("Drawing text on background image ...");

        if (drawText(mPage, &target))
            haveImage = true;
    }

    // Check for a frame and draw it if there is one.
    if (sr.size() > 0 && !sr[0].bs.empty())
    {
        if (drawFrame(mPage, &target))
            haveImage = true;
    }

    if (haveImage)
    {
        SkImageInfo info = target.info();
        TBitmap image((unsigned char *)target.getPixels(), info.width(), info.height());

        if (sr.size() > 0)
        {
#ifdef _OPAQUE_SKIA_
            _setBackground(handle, image, target.info().width(), target.info().height(), TColor::getColor(sr[0].cf));
#else
            _setBackground(handle, image, target.info().width(), target.info().height(), TColor::getColor(sr[0].cf), sr[0].oo);
#endif
        }
    }
    else if (sr.size() > 0 && !haveImage)
    {
        MSG_DEBUG("Calling \"setBackground\" with no image ...");
#ifdef _OPAQUE_SKIA_
        _setBackground(handle, TBitmap(), 0, 0, TColor::getColor(sr[0].cf));
#else
        _setBackground(handle, TBitmap(), 0, 0, TColor::getColor(sr[0].cf), sr[0].oo);
#endif
    }

    // Draw the buttons
    BUTTONS_T *button = TPageInterface::getButtons();

    while (button)
    {
        if (button->button)
        {
            MSG_DEBUG("Drawing button " << button->button->getButtonIndex() << ": " << button->button->getButtonName());
            button->button->registerCallback(_displayButton);
            button->button->regCallPlayVideo(_playVideo);
            TPageInterface::registerListCallback<TPage>(button->button, this);
            button->button->setFonts(getFonts());
            button->button->setPalette(mPalette);
            button->button->createButtons();

            if (sr.size() > 0)
                button->button->setGlobalOpacity(sr[0].oo);

            button->button->show();
        }

        button = button->next;
    }

    // Mark page as visible
    mVisible = true;

    if (gPageManager && gPageManager->getPageFinished())
        gPageManager->getPageFinished()(handle);
}

bool TPage::addSubPage(TSubPage* pg)
{
    DECL_TRACER("TPage::addSubPage(TSubPage* pg)");

    if (!pg)
    {
        MSG_ERROR("Parameter is NULL!");
        TError::setError();
        return false;
    }

    if (mSubPages.empty())
        mZOrder = 0;

#if __cplusplus < 201703L
    map<int, TSubPage *>::iterator iter = mSubPages.find(pg->getNumber());

    if (iter != mSubPages.end() && iter->second != pg)
        iter->second = pg;
    else
        mSubPages.insert(pair<int, TSubPage *>(pg->getNumber(), pg));
#else
    mSubPages.insert_or_assign(pg->getNumber(), pg);
#endif
    mLastSubPage = 0;
    return true;
}

bool TPage::removeSubPage(int ID)
{
    DECL_TRACER("TPage::removeSubPage(int ID)");

    map<int, TSubPage *>::iterator iter = mSubPages.find(ID);

    if (iter != mSubPages.end())
    {
        mSubPages.erase(iter);
        return true;
    }

    return false;
}

bool TPage::removeSubPage(const std::string& nm)
{
    DECL_TRACER("TPage::removeSubPage(const std::string& nm)");

    if (mSubPages.empty())
        return false;

    map<int, TSubPage *>::iterator iter;

    for (iter = mSubPages.begin(); iter != mSubPages.end(); ++iter)
    {
        if (iter->second->getName() == nm)
        {
            mSubPages.erase(iter);
            return true;
        }
    }

    return false;
}

TSubPage *TPage::getSubPage(int pageID)
{
    DECL_TRACER("TPage::getSubPage(int pageID)");

    map<int, TSubPage *>::iterator iter = mSubPages.find(pageID);

    if (iter != mSubPages.end())
        return iter->second;

    mLastSubPage = 0;
    return nullptr;
}

TSubPage *TPage::getSubPage(const std::string& name)
{
    DECL_TRACER("TPage::getSubPage(const std::string& name)");

    if (mSubPages.empty())
        return nullptr;

    map<int, TSubPage *>::iterator iter;

    for (iter = mSubPages.begin(); iter != mSubPages.end(); ++iter)
    {
        if (iter->second->getName() == name)
            return iter->second;
    }

    mLastSubPage = 0;
    return nullptr;
}

TSubPage *TPage::getFirstSubPage()
{
    DECL_TRACER("TPage::getFirstSubPage()");

    if (mSubPages.empty())
    {
        MSG_DEBUG("No subpages in chain.");
        mLastSubPage = 0;
        return nullptr;
    }

    TSubPage *pg = mSubPages.begin()->second;

    if (!pg)
    {
        MSG_ERROR("The pointer to the subpage " << mSubPages.begin()->first << " is NULL!");
        return nullptr;
    }

    mLastSubPage = pg->getNumber();
    MSG_DEBUG("Subpage (Z: " << pg->getZOrder() << "): " << pg->getNumber() << ". " << pg->getName());
    return pg;
}

TSubPage *TPage::getNextSubPage()
{
    DECL_TRACER("TPage::getNextSubPage()");

    if (mSubPages.empty())
    {
        MSG_DEBUG("No subpages in chain.");
        mLastSubPage = 0;
        return nullptr;
    }

    if (mLastSubPage <= 0)
        mLastSubPage = mSubPages.begin()->second->getNumber();

    map<int, TSubPage *>::iterator iter = mSubPages.find(mLastSubPage);

    if (iter != mSubPages.end())
        iter++;
    else
    {
        MSG_DEBUG("No more subpages in chain.");
        mLastSubPage = 0;
        return nullptr;
    }

    if (iter != mSubPages.end())
    {
        TSubPage *page = iter->second;
        mLastSubPage = page->getNumber();
        MSG_DEBUG("Subpage (Z: " << page->getZOrder() << "): " << page->getNumber() << ". " << page->getName());
        return page;
    }

    MSG_DEBUG("No more subpages in chain.");
    mLastSubPage = 0;
    return nullptr;
}

TSubPage *TPage::getPrevSubPage()
{
    DECL_TRACER("TPage::getPrevSubPage()");

    if (mSubPages.empty())
    {
        MSG_DEBUG("No last subpage or no subpages at all!");
        mLastSubPage = 0;
        return nullptr;
    }

    if (mLastSubPage < MAX_PAGE_ID)
    {
        map<int, TSubPage *>::iterator iter = mSubPages.end();
        iter--;
        mLastSubPage = iter->first;
    }

    map<int, TSubPage *>::iterator iter = mSubPages.find(mLastSubPage);

    if (iter != mSubPages.end() && iter != mSubPages.begin())
        iter--;
    else
    {
        MSG_DEBUG("No more subpages in chain.");
        mLastSubPage = 0;
        return nullptr;
    }

    TSubPage *page = iter->second;
    mLastSubPage = page->getNumber();
    MSG_DEBUG("Subpage (Z: " << page->getZOrder() << "): " << page->getNumber() << ". " << page->getName());
    return page;
}

TSubPage *TPage::getLastSubPage()
{
    DECL_TRACER("TPage::getLastSubPage()");

    if (mSubPages.empty())
    {
        mLastSubPage = 0;
        MSG_DEBUG("No subpages in cache!");
        return nullptr;
    }

    map<int, TSubPage *>::iterator iter = mSubPages.end();
    iter--;
    TSubPage *pg = iter->second;
    mLastSubPage = pg->getNumber();
    MSG_DEBUG("Subpage (Z: " << pg->getZOrder() << "): " << pg->getNumber() << ". " << pg->getName());
    return pg;
}

void TPage::drop()
{
    DECL_TRACER("TPage::drop()");

    // remove all subpages, if there are any
#if TESTMODE == 1
    _block_screen = true;
#endif
    if (!mSubPages.empty())
    {
        map<int, TSubPage *>::iterator iter;

        for (iter = mSubPages.begin(); iter != mSubPages.end(); ++iter)
        {
            if (iter->second)
                iter->second->drop();
        }
    }
#if TESTMODE == 1
    _block_screen = false;
#endif

    // remove all buttons, if there are any
    BUTTONS_T *bt = TPageInterface::getButtons();

    while (bt)
    {
        MSG_DEBUG("Dropping button " << handleToString(bt->button->getHandle()));
        bt->button->invalidate();
        bt = bt->next;
    }

    if (gPageManager && gPageManager->getCallDropPage())
    {
        ulong handle = (mPage.pageID << 16) & 0xffff0000;
        gPageManager->getCallDropPage()(handle);
    }

    mZOrder = ZORDER_INVALID;
    mVisible = false;
}
#ifdef _SCALE_SKIA_
void TPage::calcPosition(int im_width, int im_height, int *left, int *top, bool scale)
#else
void TPage::calcPosition(int im_width, int im_height, int *left, int *top)
#endif
{
    DECL_TRACER("TPage::calcPosition(int im_width, int im_height, int *left, int *top)");

    int nw = mPage.width;
    int nh = mPage.height;
#ifdef _SCALE_SKIA_
    if (scale && gPageManager && gPageManager->getScaleFactor() != 1.0)
    {
        nw = (int)((double)width * gPageManager->getScaleFactor());
        nh = (int)((double)height * gPageManager->getScaleFactor());
    }
#endif
    switch (sr[0].jb)
    {
        case 0: // absolute position
            *left = sr[0].bx;
            *top = sr[0].by;
#ifdef _SCALE_SKIA_
            if (scale && gPageManager && gPageManager->getScaleFactor() != 1.0)
            {
                *left = (int)((double)sr[0].bx * gPageManager->getScaleFactor());
                *left = (int)((double)sr[0].by * gPageManager->getScaleFactor());
            }
#endif
        break;

        case 1: // top, left
            *left = 0;
            *top = 0;
        break;

        case 2: // center, top
            *left = (nw - im_width) / 2;
            *top = 0;
        break;

        case 3: // right, top
            *left = nw - im_width;
            *top = 0;
        break;

        case 4: // left, middle
            *left = 0;
            *top = (nh - im_height) / 2;
        break;

        case 6: // right, middle
            *left = nw - im_width;
            *top = (nh - im_height) / 2;
        break;

        case 7: // left, bottom
            *left = 0;
            *top = nh - im_height;
        break;

        case 8: // center, bottom
            *left = (nw - im_width) / 2;
            *top = nh - im_height;
        break;

        case 9: // right, bottom
            *left = nw - im_width;
            *top = nh - im_height;
        break;

        default:    // center middle
            *left = (nw - im_width) / 2;
            *top = (nh - im_height) / 2;
    }

    if (*left < 0)
        *left = 0;

    if (*top < 0)
        *top = 0;
}

void TPage::sortSubpages()
{
    DECL_TRACER("TPage::sortSubpage()");

    mSubPagesSorted.clear();

    if (mSubPages.empty())
        return;

    map<int, TSubPage *>::iterator iter;

    for (iter = mSubPages.begin(); iter != mSubPages.end(); ++iter)
    {
        if (iter->second->getZOrder() >= 0)
        {
            mSubPagesSorted.insert(pair<int, TSubPage *>(iter->second->getZOrder(), iter->second));
            MSG_DEBUG("Page " << iter->second->getNumber() << " (" << iter->second->getName() << "): sorted in with Z-Order " << iter->second->getZOrder());
        }
    }
}

map<int, TSubPage *>& TPage::getSortedSubpages(bool force)
{
    DECL_TRACER("TPage::getSortedSubpages(bool force)");

    if (mSubPagesSorted.empty() || force)
        sortSubpages();

    return mSubPagesSorted;
}

int TPage::getNextZOrder()
{
    DECL_TRACER("TPage::getNextZOrder()");

    // Find highest z-order number
    int cnt = 0;
    map<int, TSubPage *>::iterator iter;

    for (iter = mSubPages.begin(); iter != mSubPages.end(); ++iter)
    {
        int zo = iter->second->getZOrder();

        if (iter->second && zo != ZORDER_INVALID)
        {
            if (cnt < zo)
                cnt = zo;
        }
    }

    mZOrder = cnt + 1;
    MSG_DEBUG("New Z-order: " << mZOrder);
    return mZOrder;
}

int TPage::decZOrder()
{
    DECL_TRACER("TPage::decZOrder()");

    // Find highest z-order number
    int cnt = 0;
    map<int, TSubPage *>::iterator iter;

    for (iter = mSubPages.begin(); iter != mSubPages.end(); ++iter)
    {
        int zo = iter->second->getZOrder();

        if (iter->second && zo != ZORDER_INVALID)
        {
            if (cnt < zo)
                cnt = zo;
        }
    }

    mZOrder = cnt;
    return mZOrder;
}