Subversion Repositories tpanel

Rev

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

/*
 * Copyright (C) 2020, 2021 by Andreas Theofilu <andreas@theosys.at>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 */

#include <exception>

#include "tresources.h"
#include "treadxml.h"
#include "tsubpage.h"
#include "tconfig.h"
#include "terror.h"

using std::string;
using std::vector;
using namespace Button;

TSubPage::TSubPage(const string& name)
    : mFile(name)
{
    DECL_TRACER("TSubPage::TSubPage(const string& path)");
    TError::clear();
    string path = makeFileName(TConfig::getProjectPath(), name);

    if (isValidFile())
        mFName = getFileName();
    else
    {
        MSG_ERROR("Either the path \"" << TConfig::getProjectPath() << "\" or the file name \"" << name << "\" is invalid!");
        TError::setError();
        return;
    }

    initialize();
}

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

    if (mSubpage.name.empty())
    {
        MSG_WARNING("Invalid page found!");
        return;
    }

    MSG_DEBUG("Destroing subpage " << mSubpage.pageID << ": " << mSubpage.name);

    BUTTONS_T *b = mButtons;
    BUTTONS_T *next = nullptr;

    while (b)
    {
        next = b->next;

        if (b->button)
            delete b->button;

        delete b;
        b = next;
    }

    mButtons = nullptr;
}

void TSubPage::initialize()
{
    DECL_TRACER("TSubPage::initialize()");

    if (mFName.empty())
        return;

    TError::clear();
    TReadXML reader(mFName);

    if (TError::isError())
    {
        MSG_DEBUG("Stopped scanning the subpage " << mFile << " due to previous errors!");
        return;
    }

    reader.findElement("page", "type");

    if (!reader.success())
    {
        MSG_ERROR("Element \"page\" with attribute \"type\" was not found! Invalid XML file!");
        TError::setError();
        return;
    }

    if (reader.getAttribute("type").compare("subpage") != 0)
    {
        MSG_ERROR("The type " << reader.getAttribute("type") << " is invalid for a subpage!");
        TError::setError();
        return;
    }

    mSubpage.popupType = reader.getAttribute("popupType");
    mxml_node_t *node = reader.getFirstChild();

    while (node)
    {
        string ename = reader.getElementName(node);

        if (ename.compare("pageID") == 0)
            mSubpage.pageID = reader.getIntFromNode(node);
        else if (ename.compare("name") == 0)
            mSubpage.name = reader.getTextFromNode(node);
        else if (ename.compare("left") == 0)
            mSubpage.left = reader.getIntFromNode(node);
        else if (ename.compare("top") == 0)
            mSubpage.top = reader.getIntFromNode(node);
        else if (ename.compare("width") == 0)
            mSubpage.width = reader.getIntFromNode(node);
        else if (ename.compare("height") == 0)
            mSubpage.height = reader.getIntFromNode(node);
        else if (ename.compare("group") == 0)
            mSubpage.group = reader.getTextFromNode(node);
        else if (ename.compare("showEffect") == 0)
            mSubpage.showEffect = (SHOWEFFECT)reader.getIntFromNode(node);
        else if (ename.compare("showTime") == 0)
            mSubpage.showTime = reader.getIntFromNode(node);
        else if (ename.compare("hideTime") == 0)
            mSubpage.hideTime = reader.getIntFromNode(node);
        else if (ename.compare("hideEffect") == 0)
            mSubpage.hideEffect = (SHOWEFFECT)reader.getIntFromNode(node);
        else if (ename.compare("timeout") == 0)
            mSubpage.timeout = reader.getIntFromNode(node);
        else if (ename.compare("button") == 0)      // Read a button
        {
            try
            {
                TButton *button = new TButton();
                button->setPalette(mPalette);
                button->setFonts(mFonts);
                button->initialize(&reader, node);
                button->setParentSize(mSubpage.width, mSubpage.height);
                button->registerCallback(_displayButton);
                button->registerCallbackFT(_setText);
                button->regCallPlayVideo(_playVideo);

                if (TError::isError())
                {
                    MSG_ERROR("Dropping button because of previous errors!");
                    delete button;
                    return;
                }

                button->setHandle(((mSubpage.pageID << 16) & 0xffff0000) | button->getButtonIndex());
                button->createButtons();
                addButton(button);
            }
            catch (std::exception& e)
            {
                MSG_ERROR("Memory exception: " << e.what());
                TError::setError();
                return;
            }
        }
        else if (ename.compare("sr") == 0)
        {
            SR_T sr;
            sr.number = atoi(reader.getAttributeFromNode(node, "number").c_str());
            mxml_node_t *n = reader.getFirstChild(node);

            while (n)
            {
                string ename = reader.getElementName(n);

                if (ename.compare("bs") == 0)
                    sr.bs = reader.getTextFromNode(n);
                else if (ename.compare("cb") == 0)
                    sr.cb = reader.getTextFromNode(n);
                else if (ename.compare("cf") == 0)
                    sr.cf = reader.getTextFromNode(n);
                else if (ename.compare("ct") == 0)
                    sr.ct = reader.getTextFromNode(n);
                else if (ename.compare("ec") == 0)
                    sr.ec = reader.getTextFromNode(n);
                else if (ename.compare("bm") == 0)
                    sr.bm = reader.getTextFromNode(n);
                else if (ename.compare("ji") == 0)
                    sr.ji = reader.getIntFromNode(n);
                else if (ename.compare("jb") == 0)
                    sr.jb = reader.getIntFromNode(n);
                else if (ename.compare("fi") == 0)
                    sr.fi = reader.getIntFromNode(n);
                else if (ename.compare("ii") == 0)
                    sr.ii = reader.getIntFromNode(n);
                else if (ename.compare("ix") == 0)
                    sr.ix = reader.getIntFromNode(n);
                else if (ename.compare("iy") == 0)
                    sr.iy = reader.getIntFromNode(n);
                else if (ename.compare("oo") == 0)
                    sr.oo = reader.getIntFromNode(n);

                n = reader.getNextChild(n);
            }

            mSubpage.sr.push_back(sr);
        }

        node = reader.getNextChild();
    }

    // Here the sort function could be called. But it's not necessary because
    // the buttons are stored in ascending Z order. Therefor the following
    // method call is commented out.
    // sortButtons();
}

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

    if (!_setBackground)
    {
        MSG_WARNING("No callback \"setBackground\" was set!");
        return;
    }

    ulong handle = (mSubpage.pageID << 16) & 0xffff0000;
    MSG_DEBUG("Processing page " << mSubpage.pageID << ": " << mSubpage.name);
    // Draw the background, if any
    if (mSubpage.sr.size() > 0 && !mSubpage.sr[0].bm.empty())
    {
        MSG_DEBUG("Loading image " << mSubpage.sr[0].bm);
        sk_sp<SkData> rawImage = readImage(mSubpage.sr[0].bm);

        if (rawImage)
        {
            SkBitmap bm;
            MSG_DEBUG("Decoding image ...");

            if (!DecodeDataToBitmap(rawImage, &bm))
                MSG_WARNING("Problem while decoding image " << mSubpage.sr[0].bm);

            SkImageInfo info = bm.info();
            size_t rowBytes = info.minRowBytes();
            size_t size = info.computeByteSize(rowBytes);

            MSG_DEBUG("Setting background with image of size " << size);
            _setBackground(handle, (unsigned char *)bm.getPixels(), size, rowBytes, TColor::getColor(mSubpage.sr[0].cf));
        }
        else
        {
            MSG_WARNING("Couldn't read image " << mSubpage.sr[0].bm);

            if (mSubpage.sr.size() > 0)
                _setBackground(handle, nullptr, 0, 0, TColor::getColor(mSubpage.sr[0].cf));
        }
    }
    else if (mSubpage.sr.size() > 0)
    {
        MSG_DEBUG("Calling \"setBackground\" with no image ...");
        _setBackground(handle, nullptr, 0, 0, TColor::getColor(mSubpage.sr[0].cf));
    }

    // Draw the buttons
    BUTTONS_T *button = mButtons;

    while (button)
    {
        if (button->button)
        {
            MSG_DEBUG("Drawing button " << button->button->getButtonIndex() << ": " << button->button->getButtonName());
            button->button->registerCallback(_displayButton);
            button->button->registerCallbackFT(_setText);
            button->button->regCallPlayVideo(_playVideo);
            button->button->setFonts(mFonts);
            button->button->setPalette(mPalette);
            button->button->createButtons();

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

            button->button->show();
        }

        button = button->next;
    }

    // Mark page as visible
    mVisible = true;
}

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

    if (mVisible && _callDropSubPage)
        _callDropSubPage((mSubpage.pageID << 16) & 0xffff0000);

    // Set all elements of subpage invisible
    BUTTONS_T *bt = mButtons;

    while (bt)
    {
        bt->button->hide();
        bt = bt->next;
    }

    mZOrder = -1;
    mVisible = false;
}

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

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

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

        if (bts)
        {
            BUTTONS_T *p = bts;

            while (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 TSubPage::hasButton(int id)
{
    DECL_TRACER("TSubPage::hasButton(int id)");

    BUTTONS_T *bt = mButtons;

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

        bt = bt->next;
    }

    return false;
}

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

    BUTTONS_T *bt = mButtons;

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

        bt = bt->next;
    }

    return nullptr;
}

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

    vector<TButton *> list;
    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;
}

/*
 * 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 TSubPage::sortButtons()
{
    DECL_TRACER("TSubPage::sortButtons()");

    bool turned = true;

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

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

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

                    if (pprev)
                        pprev->next = button;

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

                    if (!pprev)
                        mButtons = button;

                    button = next;

                    if (next)
                        next->previous = prev;

                    turned = true;
                    continue;
                }
            }

            button = button->next;
        }
    }

    return true;
}

RECT_T TSubPage::getRegion()
{
    DECL_TRACER("TSubPage::getRegion()");
    return {mSubpage.left, mSubpage.top, mSubpage.width, mSubpage.height};
}

/**
 * This method is called indirectly from the GUI after a mouse click. If This
 * subpage matches the clicked coordinates, than the elements are tested. If
 * an element is found that matches the coordinates it gets the click. It
 * depends on the kind of element what happens.
 */
void TSubPage::doClick(int x, int y, bool pressed)
{
    DECL_TRACER("TSubPage::doClick(int x, int y)");

    MSG_DEBUG("X=" << x << ", Y=" << y);
    BUTTONS_T *button = mButtons;
    // Find last button
    while (button->next)
        button = button->next;

    // Scan in reverse order
    while (button)
    {
        TButton *but = button->button;
        MSG_DEBUG("Button " << but->getButtonIndex() << ": lx=" << but->getLeftPosition() << ", ly=" << but->getTopPosition() << ", rx=" << (but->getLeftPosition() + but->getWidth()) << ", ry=" << (but->getTopPosition() + but->getHeight()));

        if (x >= but->getLeftPosition() && x <= (but->getLeftPosition() + but->getWidth()) &&
            y >= but->getTopPosition() && y <= (but->getTopPosition() + but->getHeight()))
        {
            MSG_DEBUG("Clicking button " << but->getButtonIndex() << ": " << but->getButtonName() << " to state " << (pressed ? "PRESS" : "RELEASE"));
            int btX = x - but->getLeftPosition();
            int btY = y - but->getTopPosition();

            if (but->doClick(btX, btY, pressed))
                break;
        }

        button = button->previous;
    }
}