Subversion Repositories tpanel

Rev

Blame | Last modification | View Log | RSS feed

/*
 * Copyright (C) 2022, 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 <string>

#include <include/core/SkFont.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 "tpageinterface.h"
#include "tsystemsound.h"
#include "ttpinit.h"
#include "tconfig.h"
#include "tresources.h"
#include "tpagemanager.h"
#include "tintborder.h"
#include "terror.h"

#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;

bool TPageInterface::drawText(PAGE_T& pinfo, SkBitmap *img)
{
    MSG_TRACE("TPageInterface::drawText(PAGE_T& pinfo, SkImage& img)");

    if (pinfo.sr.empty() || pinfo.sr[0].te.empty())
        return true;

    MSG_DEBUG("Searching for font number " << pinfo.sr[0].fi << " with text " << pinfo.sr[0].te);
    FONT_T font = mFonts->getFont(pinfo.sr[0].fi);

    if (font.file.empty())
    {
        MSG_WARNING("No font file name found for font " << pinfo.sr[0].fi);
        return false;
    }

    SkCanvas canvas(*img, SkSurfaceProps(1, kUnknown_SkPixelGeometry));
    sk_sp<SkTypeface> typeFace = mFonts->getTypeFace(pinfo.sr[0].fi);

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

    SkScalar fontSizePt = ((SkScalar)font.size * 1.322);    // Calculate points from pixels (close up)
    SkFont skFont(typeFace, fontSizePt);                    // Skia require the font size in points

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

    SkFontMetrics metrics;
    skFont.getMetrics(&metrics);
    int lines = numberLines(pinfo.sr[0].te);

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

        if (!pinfo.sr[0].ww)
            textLines = splitLine(pinfo.sr[0].te);
        else
        {
            textLines = splitLine(pinfo.sr[0].te, pinfo.width, pinfo.height, skFont, paint);
            lines = (int)textLines.size();
        }

        MSG_DEBUG("Calculated number of lines: " << textLines.size());
        int lineHeight = calcLineHeight(pinfo.sr[0].te, skFont);
        int totalHeight = lineHeight * lines;

        if (totalHeight > pinfo.height)
        {
            lines = pinfo.height / lineHeight;
            totalHeight = lineHeight * lines;
        }

        MSG_DEBUG("Line height: " << lineHeight << ", total height: " << totalHeight);
        Button::POSITION_t position = calcImagePosition(&pinfo, pinfo.width, totalHeight, Button::SC_TEXT, 0);
        MSG_DEBUG("Position frame: l: " << position.left << ", t: " << position.top << ", w: " << position.width << ", h: " << position.height);

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

        vector<string>::iterator iter;
        int line = 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);
            Button::POSITION_t pos = calcImagePosition(&pinfo, rect.width(), lineHeight, Button::SC_TEXT, 1);

            if (!pos.valid)
            {
                MSG_ERROR("Error calculating the text position!");
                TError::setError();
                return false;
            }
            MSG_DEBUG("Triing to print line: " << *iter);

            SkScalar startX = (SkScalar)pos.left;
            SkScalar startY = (SkScalar)position.top + lineHeight * line;
            MSG_DEBUG("x=" << startX << ", y=" << startY);
            canvas.drawTextBlob(blob, startX, startY + lineHeight / 2 + 4, paint);
            line++;

            if (line > lines)
                break;
        }
    }
    else    // single line
    {
        sk_sp<SkTextBlob> blob = SkTextBlob::MakeFromString(pinfo.sr[0].te.c_str(), skFont);
        SkRect rect;
        skFont.measureText(pinfo.sr[0].te.c_str(), pinfo.sr[0].te.length(), SkTextEncoding::kUTF8, &rect, &paint);
        Button::POSITION_t position = calcImagePosition(&pinfo, rect.width(), (rect.height() * (float)lines), Button::SC_TEXT, 0);

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

        MSG_DEBUG("Printing line " << pinfo.sr[0].te);
        SkScalar startX = (SkScalar)position.left;
        SkScalar startY = (SkScalar)position.top + metrics.fCapHeight; // + metrics.fLeading; // (metrics.fAscent * -1.0);
        canvas.drawTextBlob(blob, startX, startY, paint);
    }

    return true;
}

bool TPageInterface::drawFrame(PAGE_T& pinfo, SkBitmap* bm)
{
    DECL_TRACER("TPageInterface::drawFrame(PAGE_T& pinfo, SkBitmap* bm)");

    int instance = 0;

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

    // First we look into our internal border table
    Border::TIntBorder *intBorder = new Border::TIntBorder;

    if (intBorder && intBorder->drawBorder(bm, pinfo.sr[instance].bs, pinfo.width, pinfo.height, pinfo.sr[instance].cb))
    {
        delete intBorder;
        return true;
    }

    if (intBorder)
    {
        delete intBorder;
        intBorder = nullptr;
    }

    // Try to find the border in the system table
    BORDER_t bd;
    bool classExist = (gPageManager && gPageManager->getSystemDraw());

    if (!classExist)
        return false;

    string borderName = pinfo.sr[0].bs;

    if (!gPageManager->getSystemDraw()->getBorder(borderName, TSystemDraw::LT_OFF, &bd, borderName))
        return false;

    MSG_DEBUG("System border \"" << borderName << "\" found.");
    SkColor color = TColor::getSkiaColor(pinfo.sr[instance].cb);      // border color
    MSG_DEBUG("Button color: #" << std::setw(6) << std::setfill('0') << std::hex << color << std::dec);
    // Load images
    SkBitmap imgB, imgBR, imgR, imgTR, imgT, imgTL, imgL, imgBL;

    if (!getBorderFragment(bd.b, bd.b_alpha, &imgB, color))
        return false;

    MSG_DEBUG("Got images \"" << bd.b << "\" and \"" << bd.b_alpha << "\" with size " << imgB.info().width() << " x " << imgB.info().height());
    if (!getBorderFragment(bd.br, bd.br_alpha, &imgBR, color))
        return false;

    MSG_DEBUG("Got images \"" << bd.br << "\" and \"" << bd.br_alpha << "\" with size " << imgBR.info().width() << " x " << imgBR.info().height());
    if (!getBorderFragment(bd.r, bd.r_alpha, &imgR, color))
        return false;

    MSG_DEBUG("Got images \"" << bd.r << "\" and \"" << bd.r_alpha << "\" with size " << imgR.info().width() << " x " << imgR.info().height());
    if (!getBorderFragment(bd.tr, bd.tr_alpha, &imgTR, color))
        return false;

    MSG_DEBUG("Got images \"" << bd.tr << "\" and \"" << bd.tr_alpha << "\" with size " << imgTR.info().width() << " x " << imgTR.info().height());
    if (getBorderFragment(bd.t, bd.t_alpha, &imgT, color))
        return false;

    MSG_DEBUG("Got images \"" << bd.t << "\" and \"" << bd.t_alpha << "\" with size " << imgT.info().width() << " x " << imgT.info().height());
    if (!getBorderFragment(bd.tl, bd.tl_alpha, &imgTL, color))
        return false;

    MSG_DEBUG("Got images \"" << bd.tl << "\" and \"" << bd.tl_alpha << "\" with size " << imgTL.info().width() << " x " << imgTL.info().height());
    if (!getBorderFragment(bd.l, bd.l_alpha, &imgL, color))
        return false;

    MSG_DEBUG("Got images \"" << bd.l << "\" and \"" << bd.l_alpha << "\" with size " << imgL.info().width() << " x " << imgL.info().height());
    if (!getBorderFragment(bd.bl, bd.bl_alpha, &imgBL, color))
        return false;

    MSG_DEBUG("Got images \"" << bd.bl << "\" and \"" << bd.bl_alpha << "\" with size " << imgBL.info().width() << " x " << imgBL.info().height());
    MSG_DEBUG("Button image size: " << (imgTL.info().width() + imgT.info().width() + imgTR.info().width()) << " x " << (imgTL.info().height() + imgL.info().height() + imgBL.info().height()));
    MSG_DEBUG("Total size: " << pinfo.width << " x " << pinfo.height);
    stretchImageWidth(&imgB, pinfo.width - imgBL.info().width() - imgBR.info().width());
    stretchImageWidth(&imgT, pinfo.width - imgTL.info().width() - imgTR.info().width());
    stretchImageHeight(&imgL, pinfo.height - imgTL.info().height() - imgBL.info().height());
    stretchImageHeight(&imgR, pinfo.height - imgTR.info().height() - imgBR.info().height());
    MSG_DEBUG("Stretched button image size: " << (imgTL.info().width() + imgT.info().width() + imgTR.info().width()) << " x " << (imgTL.info().height() + imgL.info().height() + imgBL.info().height()));
    // Draw the frame
    SkBitmap frame;
    allocPixels(bm->info().width(), bm->info().height(), &frame);
    frame.eraseColor(SK_ColorTRANSPARENT);
    SkCanvas target(*bm, SkSurfaceProps());
    SkCanvas canvas(frame, SkSurfaceProps());
    SkPaint paint;

    paint.setBlendMode(SkBlendMode::kSrcOver);
    paint.setAntiAlias(true);
    sk_sp<SkImage> _image = SkImages::RasterFromBitmap(imgB);   // bottom
    canvas.drawImage(_image, imgBL.info().width(), pinfo.height - imgB.info().height(), SkSamplingOptions(), &paint);
    _image = SkImages::RasterFromBitmap(imgT);                  // top
    canvas.drawImage(_image, imgTL.info().width(), 0, SkSamplingOptions(), &paint);
    _image = SkImages::RasterFromBitmap(imgBR);                 // bottom right
    canvas.drawImage(_image, pinfo.width - imgBR.info().width(), pinfo.height - imgBR.info().height(), SkSamplingOptions(), &paint);
    _image = SkImages::RasterFromBitmap(imgTR);                 // top right
    canvas.drawImage(_image, pinfo.width - imgTR.info().width(), 0, SkSamplingOptions(), &paint);
    _image = SkImages::RasterFromBitmap(imgTL);                 // top left
    canvas.drawImage(_image, 0, 0, SkSamplingOptions(), &paint);
    _image = SkImages::RasterFromBitmap(imgBL);                 // bottom left
    canvas.drawImage(_image, 0, pinfo.height - imgBL.info().height(), SkSamplingOptions(), &paint);
    _image = SkImages::RasterFromBitmap(imgL);                  // left
    canvas.drawImage(_image, 0, imgTL.info().height(), SkSamplingOptions(), &paint);
    _image = SkImages::RasterFromBitmap(imgR);                  // right
    canvas.drawImage(_image, pinfo.width - imgR.info().width(), imgTR.info().height(), SkSamplingOptions(), &paint);

    Border::TIntBorder iborder;
    iborder.erasePart(bm, frame, Border::ERASE_OUTSIDE, imgL.info().width());
    _image = SkImages::RasterFromBitmap(frame);
    paint.setBlendMode(SkBlendMode::kSrcATop);
    target.drawImage(_image, 0, 0, SkSamplingOptions(), &paint);
    return true;
}

Button::POSITION_t TPageInterface::calcImagePosition(PAGE_T *page, int width, int height, Button::CENTER_CODE cc, int line)
{
    DECL_TRACER("TPageInterface::calcImagePosition(PAGE_T *page, int with, int height, CENTER_CODE code, int number)");

    if (!page)
        return Button::POSITION_t();

    Button::SR_T act_sr;
    Button::POSITION_t position;
    int ix, iy;

    if (page->sr.size() == 0)
    {
        if (sr.size() == 0)
            return position;

        act_sr = sr.at(0);
    }
    else
        act_sr = page->sr.at(0);

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

    switch (cc)
    {
        case Button::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 Button::SC_BITMAP:
            code = act_sr.jb;
            ix = act_sr.bx;
            iy = act_sr.by;
            dbgCC = "BITMAP";
            rwt = std::min(page->width - border * 2, width);
            rht = std::min(page->height - border_size * 2, height);
            break;

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

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

    switch (code)
    {
        case 0: // absolute position
            position.left = ix;

            if (cc == Button::SC_TEXT && line > 0)
                position.top = iy + height * line;
            else
                position.top = iy;

            if (cc == Button::SC_BITMAP && ix < 0 && rwt < width)
                position.left *= -1;

            if (cc == Button::SC_BITMAP && iy < 0 && rht < height)
                position.top += -1;

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

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

                if (line > 0)
                    position.top = height * line;
                else
                    position.top = border;
            }

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

        case 2: // center, top
            if (cc == Button::SC_TEXT)
            {
                if (line > 0)
                    position.top = height * line;
                else
                    position.top = border;
            }

            position.left = (page->width - rwt) / 2;
            position.height = rht;
            position.width = rwt;
        break;

        case 3: // right, top
            position.left = page->width - rwt;

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

                if (line > 0)
                    position.top = height * line;
                else
                    position.top = border;
            }

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

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

                if (line > 0)
                    position.top = ((page->height - rht) / 2) + (height / 2 * line);
                else
                    position.top = (page->height - rht) / 2;
            }
            else
                position.top = (page->height - rht) / 2;

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

        case 6: // right, middle
            position.left = page->width - rwt;

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

                if (line > 0)
                    position.top = ((page->height - rht) / 2) + (height / 2 * line);
                else
                    position.top = (page->height - rht) / 2;
            }
            else
                position.top = (page->height - rht) / 2;

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

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

                if (line > 0)
                    position.top = (page->height - rht) - height * line;
                else
                    position.top = page->height - rht;
            }
            else
                position.top = page->height - rht;

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

        case 8: // center, bottom
            position.left = (page->width - rwt) / 2;

            if (cc == Button::SC_TEXT)
            {
                if (line > 0)
                    position.top = (page->height - rht) - height * line;
                else
                    position.top = page->height - rht;
            }
            else
                position.top = page->height - rht;

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

        case 9: // right, bottom
            position.left = page->width - rwt;

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

                if (line > 0)
                    position.top = (page->height - rht) - height * line;
                else
                    position.top = page->height - rht;
            }
            else
                position.top = page->height - rht;
        break;

        default: // center, middle
            position.left = (page->width - rwt) / 2;

            if (cc == Button::SC_TEXT)
            {
                if (line > 0)
                    position.top = ((page->height - rht) / 2) + (height / 2 * line);
                else
                    position.top = (page->height - rht) / 2;
            }
            else
                position.top = (page->height - 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 TPageInterface::calcLineHeight(const string& text, SkFont& font)
{
    DECL_TRACER("TPageInterface::calcLineHeight(const string& text, SkFont& font)");

    sk_sp<SkTextBlob> blob = SkTextBlob::MakeFromString(text.c_str(), font);
    SkRect rect = blob.get()->bounds();
    return rect.height();
}

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

    int lines = 1;

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

    MSG_DEBUG("Detected " << lines << " lines.");
    return lines;
}

Button::BUTTONS_T *TPageInterface::addButton(Button::TButton* button)
{
    DECL_TRACER("*TPageInterface::addButton(TButton* button)");

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

    // We try to add this button to the list of system buttons which will
    // succeed only if it is one of the supported system buttons.
    addSysButton(button);

    try
    {
        Button::BUTTONS_T *chain = new Button::BUTTONS_T;
        chain->button = button;
        chain->next = nullptr;
        chain->previous = nullptr;
        Button::BUTTONS_T *bts = mButtons;

        if (bts)
        {
            Button::BUTTONS_T *p = bts;

            while (p && p->next)
                p = p->next;

            p->next = chain;
            chain->previous = p;
        }
        else
            mButtons = chain;

        return chain;
    }
    catch (std::exception& e)
    {
        MSG_ERROR("Memory error: " << e.what());
        TError::setError();
    }

    return nullptr;
}

bool TPageInterface::hasButton(int id)
{
    DECL_TRACER("TPageInterface::hasButton(int id)");

    Button::BUTTONS_T *bt = mButtons;

    while (bt)
    {
        if (bt->button && bt->button->getButtonIndex() == id)
            return true;

        bt = bt->next;
    }

    return false;
}

Button::TButton *TPageInterface::getButton(int id)
{
    DECL_TRACER("TPageInterface::getButton(int id)");

    Button::BUTTONS_T *bt = mButtons;

    while (bt)
    {
        if (bt->button && bt->button->getButtonIndex() == id)
            return bt->button;

        bt = bt->next;
    }

    return nullptr;
}

vector<Button::TButton *> TPageInterface::getButtons(int ap, int ad)
{
    DECL_TRACER("TPageInterface::getButtons(int ap, int ad)");

    vector<Button::TButton *> list;
    Button::BUTTONS_T *bt = mButtons;

    while (bt)
    {
        if (bt->button->getAddressPort() == ap && bt->button->getAddressChannel() == ad)
            list.push_back(bt->button);

        bt = bt->next;
    }

    return list;
}

vector<Button::TButton *> TPageInterface::getAllButtons()
{
    DECL_TRACER("TPageInterface::getAllButtons()");

    vector<Button::TButton *> list;
    Button::BUTTONS_T *bt = mButtons;

    while(bt)
    {
        list.push_back(bt->button);
        bt = bt->next;
    }

    return list;
}

Button::TButton *TPageInterface::getFirstButton()
{
    DECL_TRACER("TPageInterface::getFirstButton()");

    mLastButton = 0;

    if (mButtons)
        return mButtons->button;

    return nullptr;
}

Button::TButton *TPageInterface::getNextButton()
{
    DECL_TRACER("TPageInterface::getNextButton()");

    Button::BUTTONS_T *but = mButtons;
    int count = 0;
    mLastButton++;

    while (but)
    {
        if (but->button && count == mLastButton)
            return but->button;

        but = but->next;
        count++;
    }

    return nullptr;
}

Button::TButton *TPageInterface::getLastButton()
{
    DECL_TRACER("TPageInterface::getLastButton()");

    Button::BUTTONS_T *but = mButtons;
    mLastButton = 0;

    while (but && but->next)
    {
        mLastButton++;
        but = but->next;
    }

    if (!but)
        return nullptr;

    return but->button;
}

Button::TButton *TPageInterface::getPreviousButton()
{
    DECL_TRACER("TPageInterface::getPreviousButton()");

    Button::BUTTONS_T *but = mButtons;
    int count = 0;

    if (mLastButton)
        mLastButton--;
    else
        return nullptr;

    while (but)
    {
        if (but->button && count == mLastButton)
            return but->button;

        but = but->next;
        count++;
    }

    return nullptr;
}

/*
 * Sort the button according to their Z-order.
 * The button with the highest Z-order will be the last button in the chain.
 * The algorithm is a bubble sort algorithm.
 */
bool TPageInterface::sortButtons()
{
    DECL_TRACER("TPageInterface::sortButtons()");

    bool turned = true;

    while (turned)
    {
        Button::BUTTONS_T *button = mButtons;
        turned = false;

        while (button)
        {
            int zo = button->button->getZOrder();

            if (button->previous)
            {
                if (zo < button->previous->button->getZOrder())
                {
                    Button::BUTTONS_T *pprev = button->previous->previous;
                    Button::BUTTONS_T *prev = button->previous;
                    Button::BUTTONS_T *next = button->next;

                    if (pprev)
                        pprev->next = button;

                    prev->next = next;
                    prev->previous = button;
                    button->next = prev;
                    button->previous = pprev;

                    if (!pprev)
                        setButtons(button);

                    button = next;

                    if (next)
                        next->previous = prev;

                    turned = true;
                    continue;
                }
            }

            button = button->next;
        }
    }

    return true;
}

void TPageInterface::setFonts(TFont *font)
{
    DECL_TRACER("TPageInterface::setFonts(TFont *font)");

    if (!font)
        return;

    mFonts = font;

    Button::BUTTONS_T *button = mButtons;

    while (button)
    {
        button->button->setFonts(font);
        button = button->next;
    }
}

vector<string> TPageInterface::getListContent(ulong handle, int ap, int ta, int ti, int rows, int columns)
{
    DECL_TRACER("TPageInterface::getListContent(ulong handle, int ap, int ta, int ti, int rows, int columns)");

    if (ap == 0 && ta == 0 && ti == 0)
    {
        vector<LIST_t>::iterator iter;

        for (iter = mLists.begin(); iter != mLists.end(); ++iter)
        {
            if (iter->handle == handle)
            {
                return iter->list;
            }
        }

        return vector<string>();
    }

    if (ap == 0 && (ta == SYSTEM_LIST_SYSTEMSOUND || ta == SYSTEM_LIST_SINGLEBEEP)) // System listbox: system sounds and system single beeps
    {
        vector<LIST_t>::iterator iter;

        for (iter = mLists.begin(); iter != mLists.end(); ++iter)
        {
            if (iter->handle == handle)
            {
                iter->ap = ap;
                iter->ta = ta;
                iter->ti = ti;
                iter->rows = rows;
                iter->columns = columns;

                if (iter->selected < 0 && !iter->list.empty())
                {
                    int row = getSystemSelection(ta, iter->list);

                    if (row > 0)
                        iter->selected = row;
                }

                return iter->list;
            }
        }

        TSystemSound sysSound(TConfig::getSystemProjectPath() + "/graphics/sounds");
        vector<string> tmpFiles = sysSound.getAllSingleBeep();
        LIST_t list;
        list.handle = handle;
        list.ap = ap;
        list.ta = ta;
        list.ti = ti;
        list.rows = rows;
        list.columns = columns;
        list.list = tmpFiles;
        list.selected = getSystemSelection(ta, tmpFiles);
        mLists.push_back(list);
        return tmpFiles;
    }
    else if (ap == 0 && ta == SYSTEM_LIST_DOUBLEBEEP)   // System listbox: double beeps
    {
        vector<LIST_t>::iterator iter;

        for (iter = mLists.begin(); iter != mLists.end(); ++iter)
        {
            if (iter->handle == handle)
            {
                iter->ap = ap;
                iter->ta = ta;
                iter->ti = ti;
                iter->rows = rows;
                iter->columns = columns;

                if (iter->selected < 0 && !iter->list.empty())
                {
                    int row = getSystemSelection(ta, iter->list);

                    if (row > 0)
                        iter->selected = row;
                }

                return iter->list;
            }
        }

        TSystemSound sysSound(TConfig::getSystemProjectPath() + "/graphics/sounds");
        vector<string> tmpFiles = sysSound.getAllDoubleBeep();
        LIST_t list;
        list.handle = handle;
        list.ap = ap;
        list.ta = ta;
        list.ti = ti;
        list.rows = rows;
        list.columns = columns;
        list.list = tmpFiles;
        list.selected = getSystemSelection(ta, tmpFiles);
        mLists.push_back(list);
        return tmpFiles;
    }
    else if (ap == 0 && ta == SYSTEM_LIST_SURFACE)  // System listbox: TP4 file (surface file)
    {
        vector<LIST_t>::iterator iter;

        for (iter = mLists.begin(); iter != mLists.end(); ++iter)
        {
            if (iter->handle == handle)
            {
                iter->ap = ap;
                iter->ta = ta;
                iter->ti = ti;
                iter->rows = rows;
                iter->columns = columns;

                if (iter->selected < 0 && !iter->list.empty())
                {
                    int row = getSystemSelection(ta, iter->list);

                    if (row > 0)
                        iter->selected = row;
                }

                return iter->list;
            }
        }

        // Load surface file names from NetLinx over FTP
        TTPInit tt;
        vector<TTPInit::FILELIST_t> fileList = tt.getFileList(".tp4");
        vector<string> tmpFiles;

        if (!fileList.empty())
        {
            vector<TTPInit::FILELIST_t>::iterator iter;

            if (gPageManager)
                gPageManager->clearFtpSurface();

            for (iter = fileList.begin(); iter != fileList.end(); ++iter)
            {
                tmpFiles.push_back(iter->fname);

                if (gPageManager)
                    gPageManager->addFtpSurface(iter->fname, iter->size);
            }
        }

        LIST_t list;
        list.handle = handle;
        list.ap = ap;
        list.ta = ta;
        list.ti = ti;
        list.rows = rows;
        list.columns = columns;
        list.list = tmpFiles;
        list.selected = getSystemSelection(ta, tmpFiles);
        mLists.push_back(list);
        return tmpFiles;
    }

    return vector<string>();
}

int TPageInterface::getSystemSelection(int ta, vector<string>& list)
{
    DECL_TRACER("TPageInterface::setSystemSelection(int ta, vector<string>* list)");

    vector<string>::iterator iterSel;
    string sel;

    if (ta == SYSTEM_LIST_SURFACE)
        sel = TConfig::getFtpSurface();
    if (ta == SYSTEM_LIST_SYSTEMSOUND)
        sel = TConfig::getSystemSound();
    else if (ta == SYSTEM_LIST_SINGLEBEEP)
        sel = TConfig::getSingleBeepSound();
    else if (ta == SYSTEM_LIST_DOUBLEBEEP)
        sel = TConfig::getDoubleBeepSound();
    else
        return -1;

    int row = 1;

    for (iterSel = list.begin(); iterSel != list.end(); ++iterSel)
    {
        if (iterSel->compare(sel) == 0)
            return row;

        row++;
    }

    return -1;
}

string TPageInterface::getListRow(int ti, int row)
{
    DECL_TRACER("TPageInterface::getListRow(ulong handle, int ti, int row)");

    vector<LIST_t>::iterator iter;

    for (iter = mLists.begin(); iter != mLists.end(); ++iter)
    {
        if (iter->ti == ti)
        {
            if (row < 1 || (size_t)row > iter->list.size())
                return string();

            return iter->list[row-1];
        }
    }

    return string();
}

void TPageInterface::setGlobalSettings(Button::TButton* button)
{
    DECL_TRACER("TPageInterface::setGlobalSettings(TButton* button)");

    if (!button)
        return;

    button->setFontOnly(sr[0].fi, 0);
    button->setTextColorOnly(sr[0].ct, 0);
    button->setTextEffectColorOnly(sr[0].ec, 0);

    if (button->getListAp() == 0 && button->getListTi() >= SYSTEM_PAGE_START)
        button->setTextJustificationOnly(4, 0, 0, 0);
}

void TPageInterface::setSelectedRow(ulong handle, int row)
{
    DECL_TRACER("TPageInterface::setSelectedRow(ulong handle, int row)");

    if (row < 1)
        return;

    vector<LIST_t>::iterator iter;

    for (iter = mLists.begin(); iter != mLists.end(); ++iter)
    {
        if (iter->handle == handle)
        {
            if ((size_t)row <= iter->list.size())
                iter->selected = row;

            MSG_DEBUG("Row was set to " << row << " for item " << handleToString(handle));
            return;
        }
    }
}

int TPageInterface::getSelectedRow(ulong handle)
{
    DECL_TRACER("TPageInterface::getSelectedRow(ulong handle)");

    vector<LIST_t>::iterator iter;

    for (iter = mLists.begin(); iter != mLists.end(); ++iter)
    {
        if (iter->handle == handle)
            return iter->selected;
    }

    return -1;
}

string TPageInterface::getSelectedItem(ulong handle)
{
    DECL_TRACER("TPageInterface::getSelectedItem(ulong handle)");

    vector<LIST_t>::iterator iter;

    for (iter = mLists.begin(); iter != mLists.end(); ++iter)
    {
        if (iter->handle == handle)
        {
            if (iter->selected > 0 && (size_t)iter->selected <= iter->list.size())
                return iter->list[iter->selected-1];

            ulong nPage = (handle >> 16) & 0x0000ffff;
            ulong nButt = handle & 0x0000ffff;
            string sel;

            if (nPage == SYSTEM_SUBPAGE_SURFACE && nButt == 1)
                sel = TConfig::getFtpSurface();
            if (nPage == SYSTEM_SUBPAGE_SYSTEMSOUND && nButt == 1)
                sel = TConfig::getSystemSound();
            else if (nPage == SYSTEM_SUBPAGE_SINGLEBEEP && nButt == 1)
                sel = TConfig::getSingleBeepSound();
            else if (nPage == SYSTEM_SUBPAGE_DOUBLEBEEP && nButt == 1)
                sel = TConfig::getDoubleBeepSound();
            else
                return string();

            if (iter->list.empty())
                return string();

            vector<string>::iterator iterSel;
            int row = 1;

            for (iterSel = iter->list.begin(); iterSel != iter->list.end(); ++iterSel)
            {
                if (iterSel->compare(sel) == 0)
                {
                    iter->selected = row;
                    return sel;
                }

                row++;
            }
        }
    }

    return string();
}

/**
 * @brief getBorderFragment - get part of border
 * The method reads a border image fragment from the disk and converts it to
 * the border color. If there is a base image and an alpha mask image, the
 * pixels of the alpha mask are converted to the border color and then the base
 * image is layed over the mask image.
 * In case there is no base image, an image with the same size as the mask image
 * is created and filled transparaent.
 *
 * @param path      The path and file name of the base image.
 * @param pathAlpha The path and file name of the alpha mask image.
 * @param image     A pointer to an empty bitmap.
 * @param color     The border color
 *
 * @return In case the images exists and were loaded successfully, TRUE is
 * returned.
 */
bool TPageInterface::getBorderFragment(const string& path, const string& pathAlpha, SkBitmap* image, SkColor color)
{
    DECL_TRACER("TPageInterface::getBorderFragment(const string& path, const string& pathAlpha, SkBitmap* image, SkColor color)");

    if (!image)
    {
        MSG_ERROR("Invalid pointer to image!");
        return false;
    }

    sk_sp<SkData> im;
    SkBitmap bm;
    bool haveBaseImage = false;

    // If the path ends with "alpha.png" then it is a mask image. This not what
    // we want first unless this is the only image available.
    if (!endsWith(path, "alpha.png") || pathAlpha.empty())
    {
        if (!path.empty() && retrieveImage(path, image))
        {
            haveBaseImage = true;
            // Underly the pixels with the border color
            if (pathAlpha.empty() || !fs::exists(pathAlpha) || path == pathAlpha)
            {
                SkImageInfo info = image->info();
                SkBitmap b;
                allocPixels(info.width(), info.height(), &b);
                b.eraseColor(SK_ColorTRANSPARENT);

                for (int x = 0; x < info.width(); ++x)
                {
                    for (int y = 0; y < info.height(); ++y)
                    {
                        SkColor alpha = SkColorGetA(image->getColor(x, y));
                        uint32_t *pix = b.getAddr32(x, y);

                        if (alpha > 0)
                            *pix = color;
                    }
                }

                SkPaint paint;
                paint.setAntiAlias(true);
                paint.setBlendMode(SkBlendMode::kDstATop);
                SkCanvas can(*image);
                sk_sp<SkImage> _image = SkImages::RasterFromBitmap(b);
                can.drawImage(_image, 0, 0, SkSamplingOptions(), &paint);
            }
        }
    }

    // If there is no valid path return.
    if (pathAlpha.empty())
        return haveBaseImage;

    // On error retrieving the image, return.
    if (!retrieveImage(pathAlpha, &bm))
        return haveBaseImage;

    // If there was no base image loaded, allocate the space for an image
    // filled transparent. Make it the same size as the mask image.
    if (!haveBaseImage)
    {
        allocPixels(bm.info().width(), bm.info().height(), image);
        image->eraseColor(SK_ColorTRANSPARENT);
    }

    // Only if the base image and the mask image have the same size, which
    // should be the case, then the visible pixels of the mask image are
    // colored by the border color.
    if (image->info().dimensions() == bm.info().dimensions())
    {
        for (int y = 0; y < image->info().height(); ++y)
        {
            for (int x = 0; x < image->info().width(); ++x)
            {
                SkColor col = bm.getColor(x, y);
                SkColor alpha = SkColorGetA(col);
                uint32_t *pix = bm.getAddr32(x, y);

                if (alpha == 0)
                    *pix = SK_ColorTRANSPARENT;
                else
                    *pix = SkColorSetA(color, alpha);
            }
        }
    }

    // Here we draw the border fragment over the base image.
    SkPaint paint;
    paint.setAntiAlias(true);
    paint.setBlendMode(SkBlendMode::kDstATop);
    SkCanvas can(*image);
    sk_sp<SkImage> _image = SkImages::RasterFromBitmap(bm);
    can.drawImage(_image, 0, 0, SkSamplingOptions(), &paint);

    return true;
}

SkBitmap TPageInterface::retrieveBorderImage(const string& pa, const string& pb, SkColor color, SkColor bgColor)
{
    DECL_TRACER("TPageInterface::retrieveBorderImage(const string& pa, const string& pb, SkColor color, SkColor bgColor)");

    SkBitmap bm, bma;

    if (!pa.empty() && !retrieveImage(pa, &bm))
        return SkBitmap();

    if (!pb.empty() && !retrieveImage(pb, &bma))
        return SkBitmap();

    return colorImage(bm, bma, color, bgColor, false);
}

bool TPageInterface::retrieveImage(const string& path, SkBitmap* image)
{
    DECL_TRACER("TPageInterface::retrieveImage(const string& path, SkBitmap* image)");

    if (path.empty() || !image)
    {
        MSG_WARNING("One or all of the parameters are invalid!");
        return false;
    }

    sk_sp<SkData> im;

    if (!(im = readImage(path)))
        return false;

    DecodeDataToBitmap(im, image);

    if (image->empty())
    {
        MSG_WARNING("Could not create the image " << path);
        return false;
    }

    return true;
}

SkBitmap TPageInterface::colorImage(SkBitmap& base, SkBitmap& alpha, SkColor col, SkColor bg, bool useBG)
{
    DECL_TRACER("TPageInterface::colorImage(SkBitmap *img, int width, int height, SkColor col, SkColor bg, bool useBG)");

    int width = base.info().width();
    int height = base.info().height();

    if (width <= 0 || height <= 0)
    {
        MSG_WARNING("Got invalid width or height! (width: " << width << ", height: " << height << ")");
        return SkBitmap();
    }

    if (!alpha.empty())
    {
        if (width != alpha.info().width() || height != alpha.info().height())
        {
            MSG_ERROR("Base and alpha masks have different size!");
            return SkBitmap();
        }
    }

    SkBitmap maskBm;

    if (!allocPixels(width, height, &maskBm))
        return SkBitmap();

    maskBm.eraseColor(SK_ColorTRANSPARENT);

    for (int ix = 0; ix < width; ix++)
    {
        for (int iy = 0; iy < height; iy++)
        {
            SkColor pixelAlpha = 0;

            if (!alpha.empty())
                pixelAlpha = alpha.getColor(ix, iy);
            else
                pixelAlpha = base.getColor(ix, iy);

            uint32_t *wpix = maskBm.getAddr32(ix, iy);

            if (!wpix)
            {
                MSG_ERROR("No pixel buffer!");
                break;
            }

            uint32_t ala = SkColorGetA(pixelAlpha);

            if (ala == 0 && !useBG)
                pixelAlpha = col;
            else if (ala == 0)
                pixelAlpha = bg;
            else
            {
                // We've to change the red and the blue color channel because
                // of an error in the Skia library.
                uint32_t red = SkColorGetR(col);
                uint32_t green = SkColorGetG(col);
                uint32_t blue = SkColorGetB(col);

                if (alpha.empty())
                {
                    uint32_t pred = SkColorGetR(pixelAlpha);
                    uint32_t pgreen = SkColorGetG(pixelAlpha);
                    uint32_t pblue = SkColorGetB(pixelAlpha);
                    uint32_t maxChan = SkColorGetG(SK_ColorWHITE);

                    red   = ((pred == maxChan) ? pred : red);
                    green = ((pgreen == maxChan) ? pgreen : green);
                    blue  = ((pblue == maxChan) ? pblue : blue);
                }
                else if (ala == 0)
                    red = green = blue = 0;

                pixelAlpha = SkColorSetARGB(ala, red, green, blue);
            }

            *wpix = pixelAlpha;
        }
    }

    if (!alpha.empty())
    {
        SkPaint paint;
        paint.setBlendMode(SkBlendMode::kSrcOver);
        SkCanvas can(maskBm);
        sk_sp<SkImage> _image = SkImages::RasterFromBitmap(base);
        can.drawImage(_image, 0, 0, SkSamplingOptions(), &paint);
    }

    return maskBm;
}

bool TPageInterface::stretchImageWidth(SkBitmap *bm, int width)
{
    DECL_TRACER("TPageInterface::stretchImageWidth(SkBitmap *bm, int width)");

    if (!bm)
        return false;

    int rwidth = width;
    SkPaint paint;
    paint.setBlendMode(SkBlendMode::kSrc);

    SkImageInfo info = bm->info();
    sk_sp<SkImage> im = SkImages::RasterFromBitmap(*bm);

    if (width <= 0)
        rwidth = info.width() + width;

    if (rwidth <= 0)
        rwidth = 1;

    MSG_DEBUG("Width: " << rwidth << ", Height: " << info.height());

    if (!allocPixels(rwidth, info.height(), bm))
        return false;

    bm->eraseColor(SK_ColorTRANSPARENT);
    SkCanvas can(*bm, SkSurfaceProps());
    SkRect rect = SkRect::MakeXYWH(0, 0, rwidth, info.height());
    can.drawImageRect(im, rect, SkSamplingOptions(), &paint);
    return true;
}

bool TPageInterface::stretchImageHeight(SkBitmap *bm, int height)
{
    DECL_TRACER("TPageInterface::stretchImageHeight(SkBitmap *bm, int height)");

    if (!bm)
        return false;

    int rheight = height;
    SkPaint paint;
    paint.setBlendMode(SkBlendMode::kSrc);

    SkImageInfo info = bm->info();

    if (height <= 0)
        rheight = info.height() + height;

    if (rheight <= 0)
        rheight = 1;

    sk_sp<SkImage> im = SkImages::RasterFromBitmap(*bm);
    MSG_DEBUG("Width: " << info.width() << ", Height: " << rheight);

    if (!allocPixels(info.width(), rheight, bm))
        return false;

    bm->eraseColor(SK_ColorTRANSPARENT);
    SkCanvas can(*bm, SkSurfaceProps());
    SkRect rect = SkRect::MakeXYWH(0, 0, info.width(), rheight);
    can.drawImageRect(im, rect, SkSamplingOptions(), &paint);
    return true;
}

#ifdef _OPAQUE_SKIA_
bool TPageInterface::setOpacity(SkBitmap *bm, int oo)
{
    DECL_TRACER("TPageInterface::setOpacity(SkBitmap *bm, int oo)");

    if (oo < 0 || oo > 255 || !bm)
        return false;

    SkBitmap ooButton;
    int w = bm->info().width();
    int h = bm->info().height();

    if (!allocPixels(w, h, &ooButton))
        return false;

    SkCanvas canvas(ooButton);
    SkIRect irect = SkIRect::MakeXYWH(0, 0, w, h);
    SkRegion region;
    region.setRect(irect);
    SkScalar opaque = (SkScalar)oo;

    SkScalar alpha = 1.0 / 255.0 * opaque;
    MSG_DEBUG("Calculated alpha value: " << alpha << " (oo=" << oo << ")");
    SkPaint paint;
    paint.setAlphaf(alpha);
    sk_sp<SkImage> _image = SkImages::RasterFromBitmap(*bm);
    canvas.drawImage(_image, 0, 0, SkSamplingOptions(), &paint);
    bm->erase(SK_ColorTRANSPARENT, {0, 0, w, h});
    *bm = ooButton;
    return true;
}
#endif