Subversion Repositories tpanel

Rev

Rev 289 | 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 "terror.h"

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[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;
    }

    // Try to find the border in the system table
    BORDER_t bd, bda;
    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
    SkColor bgColor = TColor::getSkiaColor(pinfo.sr[instance].cf);    // fill 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;

    imgB = retrieveBorderImage(bd.b, bda.b, color, bgColor);

    if (imgB.empty())
        return false;

    MSG_DEBUG("Got images " << bd.b << " and " << bda.b << " with size " << imgB.info().width() << " x " << imgB.info().height());
    imgBR = retrieveBorderImage(bd.br, bda.br, color, bgColor);

    if (imgBR.empty())
        return false;

    MSG_DEBUG("Got images " << bd.br << " and " << bda.br << " with size " << imgBR.info().width() << " x " << imgBR.info().height());
    imgR = retrieveBorderImage(bd.r, bda.r, color, bgColor);

    if (imgR.empty())
        return false;

    MSG_DEBUG("Got images " << bd.r << " and " << bda.r << " with size " << imgR.info().width() << " x " << imgR.info().height());
    imgTR = retrieveBorderImage(bd.tr, bda.tr, color, bgColor);

    if (imgTR.empty())
        return false;

    MSG_DEBUG("Got images " << bd.tr << " and " << bda.tr << " with size " << imgTR.info().width() << " x " << imgTR.info().height());
    imgT = retrieveBorderImage(bd.t, bda.t, color, bgColor);

    if (imgT.empty())
        return false;

    MSG_DEBUG("Got images " << bd.t << " and " << bda.t << " with size " << imgT.info().width() << " x " << imgT.info().height());
    imgTL = retrieveBorderImage(bd.tl, bda.tl, color, bgColor);

    if (imgTL.empty())
        return false;

    MSG_DEBUG("Got images " << bd.tl << " and " << bda.tl << " with size " << imgTL.info().width() << " x " << imgTL.info().height());
    imgL = retrieveBorderImage(bd.l, bda.l, color, bgColor);

    if (imgL.empty())
        return false;

    MSG_DEBUG("Got images " << bd.l << " and " << bda.l << " with size " << imgL.info().width() << " x " << imgL.info().height());
    imgBL = retrieveBorderImage(bd.bl, bda.bl, color, bgColor);

    if (imgBL.empty())
        return false;

    MSG_DEBUG("Got images " << bd.bl << " and " << bda.bl << " 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
    SkPaint paint;
    SkCanvas canvas(*bm, SkSurfaceProps());

    paint.setBlendMode(SkBlendMode::kSrc);
    paint.setColor(color);
    sk_sp<SkImage> _image = SkImage::MakeFromBitmap(imgB);                                                                              // Bottom
    canvas.drawImage(_image, imgBL.info().width(), pinfo.height - imgB.info().height(), SkSamplingOptions(), &paint);
    _image = SkImage::MakeFromBitmap(imgBR);                                                                                            // Corner bottom right
    canvas.drawImage(_image, pinfo.width - imgBR.info().width(), pinfo.height - imgBR.info().height(), SkSamplingOptions(), &paint);
    _image = SkImage::MakeFromBitmap(imgR);                                                                                             // Right
    canvas.drawImage(_image, pinfo.width - imgR.info().width(), imgTR.info().height(), SkSamplingOptions(), &paint);
    _image = SkImage::MakeFromBitmap(imgTR);                                                                                            // Corner top right
    canvas.drawImage(_image, pinfo.width - imgTR.info().width(), 0, SkSamplingOptions(), &paint);
    _image = SkImage::MakeFromBitmap(imgT);                                                                                             // Top
    canvas.drawImage(_image, imgTL.info().width(), 0, SkSamplingOptions(), &paint);
    _image = SkImage::MakeFromBitmap(imgTL);                                                                                            // Corner top left
    canvas.drawImage(_image, 0, 0, SkSamplingOptions(), &paint);
    _image = SkImage::MakeFromBitmap(imgL);                                                                                             // Left
    canvas.drawImage(_image, 0, imgTL.info().height(), SkSamplingOptions(), &paint);
    _image = SkImage::MakeFromBitmap(imgBL);                                                                                            // Corner bottom left
    canvas.drawImage(_image, 0, pinfo.height - imgBL.info().height(), 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();
}

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)");

    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 = SkImage::MakeFromBitmap(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 = SkImage::MakeFromBitmap(*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 = SkImage::MakeFromBitmap(*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 = SkImage::MakeFromBitmap(*bm);
    canvas.drawImage(_image, 0, 0, SkSamplingOptions(), &paint);
    bm->erase(SK_ColorTRANSPARENT, {0, 0, w, h});
    *bm = ooButton;
    return true;
}
#endif