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 <vector>
#include <thread>
#include <mutex>

#include "tpagemanager.h"
#include "tcolor.h"
#include "terror.h"
#include "ticons.h"
#include "tbutton.h"
#include "tprjresources.h"
#include "tresources.h"

using std::vector;
using std::string;
using std::map;
using std::pair;
using std::to_string;
using std::thread;
using std::atomic;
using std::mutex;
using std::bind;

TIcons *gIcons = nullptr;
TPrjResources *gPrjResources = nullptr;
TPageManager *gPageManager = nullptr;
thread::id gPageManagerId;
extern amx::TAmxNet *gAmxNet;

mutex surface_mutex;
atomic<bool> runDoCommand;
bool prg_stopped = false;

TPageManager::TPageManager()
{
    surface_mutex.lock();
    DECL_TRACER("TPageManager::TPageManager()");

    gPageManager = this;
    gPageManagerId = thread::id();
    runDoCommand = false;
    mTSettings = new TSettings(TConfig::getProjectPath());

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

    gPrjResources = new TPrjResources(mTSettings->getResourcesList());

    mPalette = new TPalette();
    vector<PALETTE_SETUP>::iterator iterPal;
    vector<PALETTE_SETUP> pal = mTSettings->getSettings().palettes;

    for (iterPal = pal.begin(); iterPal != pal.end(); iterPal++)
        mPalette->initialize(iterPal->file);

    if (!TError::isError())
        TColor::setPalette(mPalette);

    mFonts = new TFont();

    if (TError::isError())
    {
        MSG_ERROR("Initializing fonts was not successfull!");
        surface_mutex.unlock();
        return;
    }

    gIcons = new TIcons();

    if (TError::isError())
    {
        MSG_ERROR("Initializing icons was not successfull!");
        surface_mutex.unlock();
        return;
    }

    mPageList = new TPageList();

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

    PAGELIST_T page;

    if (!mTSettings->getSettings().powerUpPage.empty())
    {
        if (readPage(mTSettings->getSettings().powerUpPage) == false)
        {
            surface_mutex.unlock();
            return;
        }

        MSG_TRACE("Found power up page " << mTSettings->getSettings().powerUpPage);
        page = findPage(mTSettings->getSettings().powerUpPage);
        mActualPage = page.pageID;
    }

    TPage *pg = getPage(mActualPage);

    vector<string> popups = mTSettings->getSettings().powerUpPopup;
    vector<string>::iterator iter;

    for (iter = popups.begin(); iter != popups.end(); iter++)
    {
        if (readSubPage(*iter) == false)
        {
            surface_mutex.unlock();
            return;
        }

        MSG_TRACE("Found power up popup " << *iter);

        if (pg)
        {
            TSubPage *spage = getSubPage(*iter);
            pg->addSubPage(spage);
        }
    }

    MSG_INFO("Registering commands ...");
    REG_CMD(doAPG, "@APG");
    REG_CMD(doCPG, "@CPG");
    REG_CMD(doDPG, "@DPG");
//    REG_CMD(doPDR, "@PDR");
    REG_CMD(doPHE, "@PHE");
    REG_CMD(doPHP, "@PHP");
    REG_CMD(doPHT, "@PHT");
    REG_CMD(doPPA, "@PPA");
    REG_CMD(doPPF, "@PPF");
    REG_CMD(doPPF, "PPOF");
    REG_CMD(doPPG, "@PPG");
    REG_CMD(doPPG, "PPOG");
    REG_CMD(doPPK, "@PPK");
    REG_CMD(doPPM, "@PPM");
    REG_CMD(doPPN, "@PPN");
    REG_CMD(doPPN, "PPON");
    REG_CMD(doPPT, "@PPT");
    REG_CMD(doPPX, "@PPX");
    REG_CMD(doPSE, "@PSE");
    REG_CMD(doPSP, "@PSP");
    REG_CMD(doPST, "@PSP");
    REG_CMD(doPAGE, "PAGE");

    REG_CMD(doAPF, "^APF");
    REG_CMD(doBMP, "^BMP");
    REG_CMD(doBOP, "^BOP");
    REG_CMD(doBSP, "^BSP");
    REG_CMD(doBWW, "^BWW");
    REG_CMD(doCPF, "^CPF");
    REG_CMD(doDPF, "^DPF");
    REG_CMD(doENA, "^ENA");
    REG_CMD(doFON, "^FON");
    REG_CMD(doICO, "^ICO");
    REG_CMD(doSHO, "^SHO");
    REG_CMD(doTXT, "^TXT");

    REG_CMD(doBBR, "^BBR");
    REG_CMD(doRMF, "^RMF");

    REG_CMD(doON, "ON");
    REG_CMD(doOFF, "OFF");
    REG_CMD(doLEVEL, "LEVEL");
    REG_CMD(doBLINK, "BLINK");

    // Registering callback to receive controller events.
    MSG_INFO("Starting communication with controller ...");
    mAmxNet = new amx::TAmxNet();
    MSG_INFO("Registering callback for received commands ...");
    mAmxNet->setCallback(bind(&TPageManager::doCommand, this, std::placeholders::_1));
    mAmxNet->setPanelID(TConfig::getChannel());

    try
    {
        MSG_INFO("Starting thread for communication with controller ...");
        mThreadAmxNet = std::thread([=] { mAmxNet->Run(); });
        MSG_INFO("Thread started. Detaching ...");
        mThreadAmxNet.detach();
        MSG_INFO("Thread is running and detached.");
    }
    catch (std::exception& e)
    {
        MSG_ERROR("Error starting the AmxNet thread: " << e.what());
    }

    surface_mutex.unlock();
}

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

    PCHAIN_T *p = mPchain;
    PCHAIN_T *next = nullptr;

    try
    {
        while (p)
        {
            next = p->next;

            if (p->page)
                delete p->page;

            delete p;
            p = next;
        }

        SPCHAIN_T *sp = mSPchain;
        SPCHAIN_T *snext = nullptr;

        while (sp)
        {
            snext = sp->next;

            if (sp->page)
                delete sp->page;

            delete sp;
            sp = snext;
        }

        mPchain = nullptr;
        mSPchain = nullptr;
        setPChain(mPchain);
        setSPChain(mSPchain);

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

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

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

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

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

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

        if (gPrjResources)
        {
            delete gPrjResources;
            gPrjResources = nullptr;
        }
    }
    catch (std::exception& e)
    {
        MSG_ERROR("Memory error: " << e.what());
    }
}

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

    surface_mutex.lock();
    dropAllSubPages();
    dropAllPages();

    if (mTSettings)
        mTSettings->loadSettings();
    else
        mTSettings = new TSettings(TConfig::getProjectPath());

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

    if (!gPrjResources)
        gPrjResources = new TPrjResources(mTSettings->getResourcesList());

    if (!mPalette)
        mPalette = new TPalette();
    else
        mPalette->reset();

    vector<PALETTE_SETUP>::iterator iterPal;
    vector<PALETTE_SETUP> pal = mTSettings->getSettings().palettes;

    for (iterPal = pal.begin(); iterPal != pal.end(); iterPal++)
        mPalette->initialize(iterPal->file);

    if (!TError::isError())
        TColor::setPalette(mPalette);

    if (!mFonts)
        mFonts = new TFont();
    else
        mFonts->initialize();

    if (TError::isError())
    {
        MSG_ERROR("Initializing fonts was not successfull!");
        surface_mutex.unlock();
        return;
    }

    if (!gIcons)
        gIcons = new TIcons();
    else
        gIcons->initialize();

    if (TError::isError())
    {
        MSG_ERROR("Initializing icons was not successfull!");
        surface_mutex.unlock();
        return;
    }

    if (!mPageList)
        mPageList = new TPageList();
    else
        mPageList->initialize();

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

    PAGELIST_T page;

    if (!mTSettings->getSettings().powerUpPage.empty())
    {
        if (readPage(mTSettings->getSettings().powerUpPage) == false)
        {
            surface_mutex.unlock();
            return;
        }

        MSG_TRACE("Found power up page " << mTSettings->getSettings().powerUpPage);
        page = findPage(mTSettings->getSettings().powerUpPage);
        mActualPage = page.pageID;
    }

    TPage *pg = getPage(mActualPage);

    vector<string> popups = mTSettings->getSettings().powerUpPopup;
    vector<string>::iterator iter;

    for (iter = popups.begin(); iter != popups.end(); iter++)
    {
        if (readSubPage(*iter) == false)
        {
            surface_mutex.unlock();
            return;
        }

        MSG_TRACE("Found power up popup " << *iter);

        if (pg)
        {
            TSubPage *spage = getSubPage(*iter);
            pg->addSubPage(spage);
        }
    }

    // Start the thread
    if (!mAmxNet || !mThreadAmxNet.joinable())
    {
        try
        {
            if (!mAmxNet)
            {
                mAmxNet = new amx::TAmxNet();
                mAmxNet->setCallback(bind(&TPageManager::doCommand, this, std::placeholders::_1));
            }

            if (!mThreadAmxNet.joinable())
            {
                MSG_INFO("Starting thread for communication with controller ...");
                mThreadAmxNet = std::thread([=] { mAmxNet->Run(); });
                MSG_INFO("Thread started. Detaching ...");
                mThreadAmxNet.detach();
                MSG_INFO("Thread is running and detached.");
            }
        }
        catch (std::exception& e)
        {
            MSG_ERROR("Error starting the AmxNet thread: " << e.what());
        }
    }

    surface_mutex.unlock();
}

/*
 * The following method is called by the class TAmxNet whenever an event from
 * the controller occured.
 */
void TPageManager::doCommand(const amx::ANET_COMMAND& cmd)
{
    DECL_TRACER("TPageManager::doCommand(const amx::ANET_COMMAND& cmd)");

    mCommands.push_back(cmd);

    if (mBusy)
        return;

    mBusy = true;
    string com;

    while (mCommands.size() > 0)
    {
        amx::ANET_COMMAND& bef = mCommands.at(0);
        mCommands.erase(mCommands.begin());

        switch (bef.MC)
        {
            case 0x0006:
            case 0x0018:        // feedback channel on
                com.assign("ON-");
                com.append(to_string(bef.data.chan_state.channel));
                parseCommand(bef.device1, bef.data.chan_state.port, com);
            break;

            case 0x0007:
            case 0x0019:        // feedback channel off
                com.assign("OFF-");
                com.append(to_string(bef.data.chan_state.channel));
                parseCommand(bef.device1, bef.data.chan_state.port, com);
            break;

            case 0x000a:        // level value change
                com = "LEVEL-";
                com += to_string(bef.data.message_value.value);
                com += ",";

                switch (bef.data.message_value.type)
                {
                    case 0x10: com += to_string(bef.data.message_value.content.byte); break;
                    case 0x11: com += to_string(bef.data.message_value.content.ch); break;
                    case 0x20: com += to_string(bef.data.message_value.content.integer); break;
                    case 0x21: com += to_string(bef.data.message_value.content.sinteger); break;
                    case 0x40: com += to_string(bef.data.message_value.content.dword); break;
                    case 0x41: com += to_string(bef.data.message_value.content.sdword); break;
                    case 0x4f: com += to_string(bef.data.message_value.content.fvalue); break;
                    case 0x8f: com += to_string(bef.data.message_value.content.dvalue); break;
                }

                parseCommand(bef.device1, bef.data.chan_state.port, com);
            break;

            case 0x000c:        // Command string
            {
                amx::ANET_MSG_STRING msg = bef.data.message_string;

                if (msg.length < strlen((char *)&msg.content))
                {
                    mCmdBuffer.append((char *)&msg.content);
                    break;
                }
                else if (mCmdBuffer.length() > 0)
                {
                    mCmdBuffer.append((char *)&msg.content);
                    size_t len = (mCmdBuffer.length() >= sizeof(msg.content)) ? (sizeof(msg.content)-1) : mCmdBuffer.length();
                    strncpy((char *)&msg.content, mCmdBuffer.c_str(), len);
                    msg.content[len] = 0;
                }

                com.assign(cp1250ToUTF8((char *)&msg.content));
                parseCommand( bef.device1, bef.data.chan_state.port, com);
                mCmdBuffer.clear();
            }
            break;

            case 0x0502:    // Blink message (contains date and time)
                com = "BLINK-" + to_string(bef.data.blinkMessage.hour) + ":";
                com += to_string(bef.data.blinkMessage.minute) + ":";
                com += to_string(bef.data.blinkMessage.second) + ",";
                com += to_string(bef.data.blinkMessage.year) + "-";
                com += to_string(bef.data.blinkMessage.month) + "-";
                com += to_string(bef.data.blinkMessage.day) + ",";
                com += to_string(bef.data.blinkMessage.weekday) + ",";
                com += ((bef.data.blinkMessage.LED & 0x0001) ? "ON" : "OFF");
                parseCommand(0, 0, com);
            break;

            case 0x1000:        // Filetransfer
            {
                amx::ANET_FILETRANSFER ftr = bef.data.filetransfer;

                if (ftr.ftype == 0)
                {
                    switch(ftr.function)
                    {
                        case 0x0100:    // Syncing directory
                            com = "#FTR-SYNC:0:";
                            com.append((char*)&ftr.data[0]);
                            parseCommand(bef.device1, bef.port1, com);
                        break;

                        case 0x0104:    // Delete file
                            com = "#FTR-SYNC:"+to_string(bef.count)+":Deleting files ... ("+to_string(bef.count)+"%)";
                            parseCommand(bef.device1, bef.port1, com);
                        break;

                        case 0x0105:    // start filetransfer
                            com = "#FTR-START";
                            parseCommand(bef.device1, bef.port1, com);
                        break;
                    }
                }
                else
                {
                    switch(ftr.function)
                    {
                        case 0x0003:    // Received part of file
                        case 0x0004:    // End of file
                            com = "#FTR-FTRPART:"+to_string(bef.count)+":"+to_string(ftr.info1);
                            parseCommand(bef.device1, bef.port1, com);
                        break;

                        case 0x0007:    // End of file transfer
                        {
                            initialize();
                            com = "#FTR-END";
                            parseCommand(bef.device1, bef.port1, com);
                        }
                        break;

                        case 0x0102:    // Receiving file
                            com = "#FTR-FTRSTART:"+to_string(bef.count)+":"+to_string(ftr.info1)+":";
                            com.append((char*)&ftr.data[0]);
                            parseCommand(bef.device1, bef.port1, com);
                        break;
                    }
                }
            }
            break;
        }
    }

    runDoCommand = false;
    mBusy = false;
}

/*
 * The following function must be called to start the "panel".
 */
bool TPageManager::run()
{
    DECL_TRACER("TPageManager::run()");

    if (mActualPage <= 0)
        return false;

    surface_mutex.lock();
    TPage *pg = getPage(mActualPage);
    pg->setFonts(mFonts);
    pg->registerCallback(_setBackground);
    pg->registerCallbackFT(_setText);
    pg->regCallPlayVideo(_callPlayVideo);

    if (!pg || !_setPage || !mTSettings)
    {
        surface_mutex.unlock();
        return false;
    }

    _setPage((pg->getNumber() << 16) & 0xffff0000, mTSettings->getWith(), mTSettings->getHeight());
    pg->show();

    TSubPage *subPg = pg->getFirstSubPage();

    while (subPg)
    {
        subPg->setFonts(mFonts);
        subPg->registerCallback(_setBackground);
        subPg->registerCallbackDB(_displayButton);
        subPg->regCallDropSubPage(_callDropSubPage);
        subPg->registerCallbackFT(_setText);
        subPg->regCallPlayVideo(_callPlayVideo);

        if (_setSubPage)
        {
            MSG_DEBUG("Drawing page " << subPg->getNumber() << ": " << subPg->getName() << "...");
            _setSubPage(((subPg->getNumber() << 16) & 0xffff0000), subPg->getLeft(), subPg->getTop(), subPg->getWidth(), subPg->getHeight());
            subPg->show();
        }

        subPg = pg->getNextSubPage();
    }

    surface_mutex.unlock();
    return true;
}

TPage *TPageManager::getPage(int pageID)
{
    DECL_TRACER("TPageManager::getPage(int pageID)");

    PCHAIN_T *p = mPchain;

    while (p)
    {
        if (p->page->getNumber() == pageID)
            return p->page;

        p = p->next;
    }

    MSG_DEBUG("Page " << pageID << " not found!");
    return nullptr;
}

TPage *TPageManager::getPage(const string& name)
{
    DECL_TRACER("TPageManager::getPage(const string& name)");

    PCHAIN_T *p = mPchain;

    while (p)
    {
        if (p->page->getName().compare(name) == 0)
            return p->page;

        p = p->next;
    }

    MSG_DEBUG("Page " << name << " not found!");
    return nullptr;
}

TPage *TPageManager::loadPage(PAGELIST_T& pl)
{
    DECL_TRACER("TPageManager::loadPage(PAGELIST_T& pl)");

    if (!pl.isValid)
        return nullptr;

    TPage *pg = getPage(pl.pageID);

    if (!pg)
    {
        if (!readPage(pl.pageID))
            return nullptr;

        pg = getPage(pl.pageID);

        if (!pg)
        {
            MSG_ERROR("Error loading page " << pl.pageID << ", " << pl.name << " from file " << pl.file << "!");
            return nullptr;
        }
    }

    return pg;
}

bool TPageManager::setPage(int PageID)
{
    DECL_TRACER("TPageManager::setPage(int PageID)");

    if (mActualPage == PageID)
        return true;

    TPage *pg = getPage(mActualPage);
    mPreviousPage = mActualPage;

    if (pg)
        pg->drop();

    mActualPage = 0;
    PAGELIST_T listPg = findPage(PageID);

    if ((pg = loadPage(listPg)) == nullptr)
        return false;

    mActualPage = PageID;
    pg->show();
    return true;
}

bool TPageManager::setPage(const string& name)
{
    DECL_TRACER("TPageManager::setPage(const string& name)");

    TPage *pg = getPage(mActualPage);

    if (pg && pg->getName().compare(name) == 0)
        return true;

    mPreviousPage = mActualPage;

    if (pg)
        pg->drop();

    mActualPage = 0;
    PAGELIST_T listPg = findPage(name);

    if ((pg = loadPage(listPg)) == nullptr)
        return false;

    mActualPage = pg->getNumber();
    pg->show();
    return true;
}

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

    SPCHAIN_T *p = mSPchain;

    while(p)
    {
        if (p->page->getNumber() == pageID)
            return p->page;

        p = p->next;
    }

    return nullptr;
}

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

    SPCHAIN_T *p = mSPchain;

    while (p)
    {
        if (p->page->getName().compare(name) == 0)
            return p->page;

        p = p->next;
    }

    return nullptr;
}

bool TPageManager::readPages()
{
    DECL_TRACER("TPageManager::readPages()");

    if (!mPageList)
    {
        MSG_ERROR("Page list is not initialized!");
        TError::setError();
        return false;
    }

    // Read all pages
    vector<PAGELIST_T>::iterator pgIter;
    vector<PAGELIST_T> pageList = mPageList->getPagelist();

    for (pgIter = pageList.begin(); pgIter != pageList.end(); pgIter++)
    {
        TPage *page = new TPage(pgIter->name+".xml");

        if (TError::isError())
        {
            delete page;
            return false;
        }

        page->setPalette(mPalette);
        page->setFonts(mFonts);
        page->registerCallback(_setBackground);
        page->registerCallbackDB(_displayButton);
        page->registerCallbackFT(_setText);
        page->regCallPlayVideo(_callPlayVideo);

        if (!addPage(page))
            return false;
    }

    vector<SUBPAGELIST_T>::iterator spgIter;
    vector<SUBPAGELIST_T> subPageList = mPageList->getSupPageList();

    for (spgIter = subPageList.begin(); spgIter != subPageList.end(); spgIter++)
    {
        TSubPage *page = new TSubPage(spgIter->name+".xml");

        if (TError::isError())
        {
            delete page;
            return false;
        }

        page->setPalette(mPalette);
        page->setFonts(mFonts);
        page->registerCallback(_setBackground);
        page->registerCallbackDB(_displayButton);
        page->regCallDropSubPage(_callDropSubPage);
        page->registerCallbackFT(_setText);
        page->regCallPlayVideo(_callPlayVideo);
        page->setGroup(spgIter->group);

        if (!addSubPage(page))
            return false;
    }

    return true;
}

bool TPageManager::readPage(const std::string& name)
{
    DECL_TRACER("TPageManager::readPage(const std::string& name)");

    PAGELIST_T page = findPage(name);

    if (page.pageID <= 0)
    {
        MSG_ERROR("Page " << name << " not found!");
        return false;
    }

    TPage *pg = new TPage(page.name+".xml");

    if (TError::isError())
    {
        delete pg;
        return false;
    }

    pg->setPalette(mPalette);
    pg->setFonts(mFonts);
    pg->registerCallback(_setBackground);
    pg->registerCallbackDB(_displayButton);
    pg->registerCallbackFT(_setText);
    pg->regCallPlayVideo(_callPlayVideo);

    if (!addPage(pg))
        return false;

    return true;
}

bool TPageManager::readPage(int ID)
{
    DECL_TRACER("TPageManager::readPage(int ID)");

    TError::clear();
    PAGELIST_T page = findPage(ID);

    if (page.pageID <= 0)
    {
        MSG_ERROR("Page with ID " << ID << " not found!");
        return false;
    }

    TPage *pg = new TPage(page.name+".xml");

    if (TError::isError())
    {
        delete pg;
        return false;
    }

    pg->setPalette(mPalette);
    pg->setFonts(mFonts);
    pg->registerCallback(_setBackground);
    pg->registerCallbackDB(_displayButton);
    pg->registerCallbackFT(_setText);
    pg->regCallPlayVideo(_callPlayVideo);

    if (!addPage(pg))
        return false;

    return true;
}

bool TPageManager::readSubPage(const std::string& name)
{
    DECL_TRACER("TPageManager::readSubPage(const std::string& name)");

    TError::clear();
    SUBPAGELIST_T page = findSubPage(name);

    if (page.pageID <= 0)
    {
        MSG_ERROR("Subpage " << name << " not found!");
        return false;
    }

    if (haveSubPage(name))
        return true;

    TSubPage *pg = new TSubPage(page.name+".xml");

    if (TError::isError())
    {
        delete pg;
        return false;
    }

    pg->setPalette(mPalette);
    pg->setFonts(mFonts);
    pg->registerCallback(_setBackground);
    pg->registerCallbackDB(_displayButton);
    pg->regCallDropSubPage(_callDropSubPage);
    pg->registerCallbackFT(_setText);
    pg->regCallPlayVideo(_callPlayVideo);
    pg->setGroup(page.group);

    if (!addSubPage(pg))
    {
        delete pg;
        return false;
    }

    return true;
}

bool TPageManager::readSubPage(int ID)
{
    DECL_TRACER("TPageManager::readSubPage(int ID)");

    TError::clear();
    SUBPAGELIST_T page = findSubPage(ID);

    if (page.pageID <= 0)
    {
        MSG_ERROR("Subpage with ID " << ID << " not found!");
        return false;
    }

    TSubPage *pg = new TSubPage(page.name+".xml");

    if (TError::isError())
    {
        delete pg;
        return false;
    }

    pg->setPalette(mPalette);
    pg->setFonts(mFonts);
    pg->registerCallback(_setBackground);
    pg->registerCallbackDB(_displayButton);
    pg->regCallDropSubPage(_callDropSubPage);
    pg->registerCallbackFT(_setText);
    pg->regCallPlayVideo(_callPlayVideo);
    pg->setGroup(page.group);

    if (!addSubPage(pg))
        return false;

    return true;
}

/******************** Internal private methods *********************/

PAGELIST_T TPageManager::findPage(const std::string& name)
{
    DECL_TRACER("TPageManager::findPage(const std::string& name)");

    vector<PAGELIST_T>::iterator pgIter;
    vector<PAGELIST_T> pageList = mPageList->getPagelist();

    for (pgIter = pageList.begin(); pgIter != pageList.end(); pgIter++)
    {
        if (pgIter->name.compare(name) == 0)
            return *pgIter;
    }

    return PAGELIST_T();
}

PAGELIST_T TPageManager::findPage(int ID)
{
    DECL_TRACER("TPageManager::findPage(int ID)");

    vector<PAGELIST_T>::iterator pgIter;
    vector<PAGELIST_T> pageList = mPageList->getPagelist();

    for (pgIter = pageList.begin(); pgIter != pageList.end(); pgIter++)
    {
        if (pgIter->pageID == ID)
            return *pgIter;
    }

    return PAGELIST_T();
}

SUBPAGELIST_T TPageManager::findSubPage(const std::string& name)
{
    DECL_TRACER("TPageManager::findSubPage(const std::string& name)");

    vector<SUBPAGELIST_T>::iterator pgIter;
    vector<SUBPAGELIST_T> pageList = mPageList->getSupPageList();

    for (pgIter = pageList.begin(); pgIter != pageList.end(); pgIter++)
    {
        if (pgIter->name.compare(name) == 0)
            return *pgIter;
    }

    return SUBPAGELIST_T();
}

SUBPAGELIST_T TPageManager::findSubPage(int ID)
{
    DECL_TRACER("TPageManager::findSubPage(int ID)");

    vector<SUBPAGELIST_T>::iterator pgIter;
    vector<SUBPAGELIST_T> pageList = mPageList->getSupPageList();

    for (pgIter = pageList.begin(); pgIter != pageList.end(); pgIter++)
    {
        if (pgIter->pageID == ID)
            return *pgIter;
    }

    return SUBPAGELIST_T();
}

bool TPageManager::addPage(TPage* pg)
{
    DECL_TRACER("TPageManager::addPage(TPage* pg)");

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

    PCHAIN_T *chain = new PCHAIN_T;
    chain->page = pg;
    chain->next = nullptr;

    if (mPchain)
    {
        PCHAIN_T *p = mPchain;

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

        p->next = chain;
    }
    else
    {
        mPchain = chain;
        setPChain(mPchain);
    }

    MSG_DEBUG("Added page " << chain->page->getName());
    return true;
}

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

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

    if (haveSubPage(pg->getNumber()))
    {
        MSG_ERROR("Subpage " << pg->getNumber() << ", " << pg->getName() << " is already in chain!");
        return false;
    }

    SPCHAIN_T *chain = new SPCHAIN_T;
    chain->page = pg;
    chain->next = nullptr;

    if (mSPchain)
    {
        SPCHAIN_T *p = mSPchain;

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

        p->next = chain;
    }
    else
    {
        mSPchain = chain;
        setSPChain(mSPchain);
    }

    MSG_DEBUG("Added subpage " << chain->page->getName());
    return true;
}

void TPageManager::dropAllPages()
{
    DECL_TRACER("TPageManager::dropAllPages()");

    PCHAIN_T *pg = mPchain;
    PCHAIN_T *next = nullptr;

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

        if (pg->page)
        {
            if (_callDropPage)
                _callDropPage((pg->page->getNumber() << 16) & 0xffff0000);

            delete pg->page;
        }

        delete pg;
        pg = next;
    }

    mPchain = nullptr;
    setPChain(mPchain);
}

void TPageManager::dropAllSubPages()
{
    DECL_TRACER("TPageManager::dropAllSubPages()");

    SPCHAIN_T *spg = mSPchain;
    SPCHAIN_T *next;

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

        if (spg->page)
        {
            if (_callDropSubPage)
                _callDropSubPage((spg->page->getNumber() << 16) & 0xffff0000);

            delete spg->page;
        }

        delete spg;
        spg = next;
    }

    mSPchain = nullptr;
    setSPChain(mSPchain);
}

TPage *TPageManager::getActualPage()
{
    return getPage(mActualPage);
}

TSubPage *TPageManager::getFirstSubPage()
{
    DECL_TRACER("TPageManager::getFirstSubPage()");
    TPage *pg = getPage(mActualPage);

    if (!pg)
        return nullptr;

    return pg->getFirstSubPage();
}

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

    TPage *pg = getPage(mActualPage);

    if (pg)
        return pg->getNextSubPage();

    return nullptr;
}

TSubPage *TPageManager::getFirstSubPageGroup(const string& group)
{
    DECL_TRACER("TPageManager::getFirstSubPageGroup(const string& group)");

    if (group.empty())
    {
        MSG_WARNING("Empty group name is invalid. Ignoring it!");
        mActualGroupName.clear();
        mActualGroupPage = nullptr;
        return nullptr;
    }

    mActualGroupName = group;
    TSubPage *pg = getFirstSubPage();

    while (pg)
    {
        MSG_DEBUG("Evaluating group " << pg->getGroupName() << " with " << group);

        if (pg->getGroupName().compare(group) == 0)
        {
            mActualGroupPage = pg;
            return pg;
        }

        pg = getNextSubPage();
    }

    mActualGroupName.clear();
    mActualGroupPage = nullptr;
    return nullptr;
}

TSubPage *TPageManager::getNextSubPageGroup()
{
    DECL_TRACER("TPageManager::getNextSubPageGroup()");

    if (mActualGroupName.empty())
        return nullptr;

    TSubPage *pg = getFirstSubPage();
    bool found = false;

    while (pg)
    {
        MSG_DEBUG("Evaluating group " << pg->getGroupName() << " with " << mActualGroupName);

        if (!found && pg == mActualGroupPage)
        {
            pg = getNextSubPage();
            found = true;
            continue;
        }

        if (found && pg->getGroupName().compare(mActualGroupName) == 0)
        {
            mActualGroupPage = pg;
            return pg;
        }

        pg = getNextSubPage();
    }

    mActualGroupName.clear();
    mActualGroupPage = nullptr;
    return nullptr;
}

TSubPage *TPageManager::getNextSubPageGroup(const string& group, TSubPage* pg)
{
    DECL_TRACER("TPageManager::getNextSubPageGroup(const string& group, TSubPage* pg)");

    if (group.empty() || !pg)
        return nullptr;

    TSubPage *page = getFirstSubPage();
    bool found = false;

    while (page)
    {
        MSG_DEBUG("Evaluating group " << pg->getGroupName() << " with " << group);

        if (!found && pg == page)
        {
            page = getNextSubPage();
            found = true;
            continue;
        }

        if (found && page->getGroupName().compare(group) == 0)
            return page;

        page = getNextSubPage();
    }

    return nullptr;
}

TSubPage *TPageManager::getTopPage()
{
    DECL_TRACER("TPageManager::getTopPage()");

    // Scan for all occupied regions
    vector<RECT_T> regions;

    TSubPage *pg = getFirstSubPage();

    while (pg)
    {
        RECT_T r = pg->getRegion();
        regions.push_back(r);
        pg = getNextSubPage();
    }

    // Now scan all pages against all regions to find the top most
    pg = getFirstSubPage();
    TSubPage *top = nullptr;
    int zPos = 0;

    while (pg)
    {
        RECT_T r = pg->getRegion();
        vector<RECT_T>::iterator iter;
        int zo = 0;

        for (iter = regions.begin(); iter != regions.end(); iter++)
        {
            if (doOverlap(*iter, r) && zPos > zo)
                top = pg;

            zo++;
        }

        pg = getNextSubPage();
        zPos++;
    }

    return top;
}

TSubPage *TPageManager::getCoordMatch(int x, int y)
{
    DECL_TRACER("TPageManager::getCoordMatch(int x, int y)");

    // Reverse order of pages
    map<int, TSubPage *> zOrder;
    TSubPage *pg = getFirstSubPage();

    while (pg)
    {
        if (pg->isVisible())
            zOrder.insert(pair<int, TSubPage *>(pg->getZOrder(), pg));

        pg = getNextSubPage();
    }

    // Iterate in reverse order through array
    map<int, TSubPage *>::reverse_iterator iter;

    for (iter = zOrder.rbegin(); iter != zOrder.rend(); iter++)
    {
        RECT_T r = iter->second->getRegion();

        if (r.left <= x && (r.left + r.width) >= x &&
            r.top <= y && (r.top + r.height) >= y)
        {
            MSG_DEBUG("Click matches subpage " << iter->second->getNumber() << " (" << iter->second->getName() << ")");
            return iter->second;
        }
    }

    return nullptr;
}

bool TPageManager::doOverlap(RECT_T r1, RECT_T r2)
{
    DECL_TRACER("TPageManager::doOverlap(RECT_T r1, RECT_T r2)");

    // If one rectangle is on left side of other
    if (r1.left >= r2.left || r2.left >= r1.left)
        return false;

    // If one rectangle is above other
    if (r1.top <= r2.top || r2.top <= r1.top)
        return false;

    return true;
}

bool TPageManager::havePage(const string& name)
{
    DECL_TRACER("TPageManager::havePage(const string& name)");

    if (name.empty())
        return false;

    PCHAIN_T *pg = mPchain;

    while (pg)
    {
        if (pg->page && pg->page->getName().compare(name) == 0)
            return true;

        pg = pg->next;
    }

    return false;
}

bool TPageManager::haveSubPage(const string& name)
{
    DECL_TRACER("TPageManager::haveSubPage(const string& name)");

    if (name.empty())
        return false;

    SPCHAIN_T *pg = mSPchain;

    while (pg)
    {
        if (pg->page && pg->page->getName().compare(name) == 0)
        {
            MSG_DEBUG("Subpage " << pg->page->getNumber() << ", " << name << " found.");
            return true;
        }

        pg = pg->next;
    }

    MSG_DEBUG("Subpage " << name << " not found.");
    return false;
}

bool TPageManager::haveSubPage(int id)
{
    DECL_TRACER("TPageManager::haveSubPage(int id)");

    SPCHAIN_T *pg = mSPchain;

    while (pg)
    {
        if (pg->page && pg->page->getNumber() == id)
        {
            MSG_DEBUG("Subpage " << pg->page->getNumber() << ", " << pg->page->getName() << " found.");
            return true;
        }

        pg = pg->next;
    }

    MSG_DEBUG("Subpage " << id << " not found.");
    return false;
}

bool TPageManager::haveSubPage(const string& page, const string& name)
{
    DECL_TRACER("TPageManager::haveSubPage(const string& page, const string& name)");

    TPage *pg = getPage(page);

    if (!pg)
        return false;

    TSubPage *spg = pg->getFirstSubPage();

    while (spg)
    {
        if (spg->getName().compare(name) == 0)
        {
            MSG_DEBUG("Subpage " << spg->getNumber() << ", " << name << " found.");
            return true;
        }

        spg = pg->getNextSubPage();
    }

    MSG_DEBUG("Subpage " << name << " not found on page " << page << ".");
    return false;
}

bool TPageManager::haveSubPage(const string& page, int id)
{
    DECL_TRACER("TPageManager::haveSubPage(const string& page, int id)");

    TPage *pg = getPage(page);

    if (!pg)
        return false;

    TSubPage *spg = pg->getFirstSubPage();

    while (spg)
    {
        if (spg->getNumber() == id)
        {
            MSG_DEBUG("Subpage " << spg->getNumber() << ", " << spg->getName() << " found.");
            return true;
        }

        spg = pg->getNextSubPage();
    }

    MSG_DEBUG("Subpage " << id << " on page " << page << " not found.");
    return false;
}

void TPageManager::closeGroup(const string& group)
{
    DECL_TRACER("TPageManager::closeGroup(const string& group)");

    SPCHAIN_T *pg = mSPchain;

    while (pg)
    {
        if (pg->page->getGroupName().compare(group) == 0 && pg->page->isVisible())
        {
            pg->page->regCallDropSubPage(_callDropSubPage);
            pg->page->drop();
            break;
        }

        pg = pg->next;
    }
}

void TPageManager::showSubPage(const string& name)
{
    DECL_TRACER("TPageManager::showSubPage(const string& name)");

    if (name.empty())
        return;

    TPage *page = getActualPage();

    if (!page)
    {
        MSG_ERROR("No actual page loaded!");
        return;
    }

    TSubPage *pg = getSubPage(name);

    if (!pg)
    {
        MSG_DEBUG("Subpage " << name << " not in memory. Reading it ...");

        if (!readSubPage(name))
            return;

        if ((pg = getSubPage(name)) == nullptr)
            return;

        page->addSubPage(pg);
    }

    string group = pg->getGroupName();

    if (!group.empty())
    {
        TSubPage *sub = getFirstSubPageGroup(group);

        while(sub)
        {
            if (sub->isVisible() && sub->getNumber() != pg->getNumber())
                sub->drop();

            sub = getNextSubPageGroup(group, sub);
        }
    }

    if (!pg->isVisible())
    {
        TPage *page = getPage(mActualPage);

        if (!page)
        {
            MSG_ERROR("No active page found! Internal error.");
            return;
        }

        if (!haveSubPage(pg->getNumber()) && !page->addSubPage(pg))
            return;

        pg->setZOrder(page->getNextZOrder());

        if (_setSubPage)
            _setSubPage((pg->getNumber() << 16) & 0xffff0000, pg->getLeft(), pg->getTop(), pg->getWidth(), pg->getHeight());
    }

    pg->show();
}

void TPageManager::hideSubPage(const string& name)
{
    DECL_TRACER("TPageManager::hideSubPage(const string& name)");

    if (name.empty())
        return;

    TPage *page = getPage(mActualPage);

    if (!page)
    {
        MSG_ERROR("No active page found! Internal error.");
        return;
    }

    TSubPage *pg = getSubPage(name);

    if (pg)
    {
        if (pg->getZOrder() == page->getActZOrder())
            page->decZOrder();

        pg->drop();
    }
}

/*
 * Catch the mouse presses and scan all pages and subpages for an element to
 * receive the klick.
 */
void TPageManager::mouseEvent(int x, int y, bool pressed)
{
    surface_mutex.lock();
    DECL_TRACER("TPageManager::mouseEvent(int x, int y, bool pressed)");

    TError::clear();
    int realX = x - mFirstLeftPixel;
    int realY = y - mFirstTopPixel;
    MSG_DEBUG("Mouse at " << realX << ", " << realY << ", state " << ((pressed) ? "PRESSED" : "RELEASED"));
    TSubPage *subPage = getCoordMatch(realX, realY);

    if (!subPage)
    {
        surface_mutex.unlock();
        return;
    }

    MSG_DEBUG("Subpage " << subPage->getNumber() << ": size: left=" << subPage->getLeft() << ", top=" << subPage->getTop() << ", width=" << subPage->getWidth() << ", height=" << subPage->getHeight());
    subPage->doClick(realX - subPage->getLeft(), realY - subPage->getTop(), pressed);
    surface_mutex.unlock();
}

vector<Button::TButton *> TPageManager::collectButtons(vector<MAP_T>& map)
{
    DECL_TRACER("TPageManager::collectButtons(vector<MAP_T>& map)");

    vector<Button::TButton *> buttons;
    vector<MAP_T>::iterator iter;

    for (iter = map.begin(); iter != map.end(); iter++)
    {
        if (iter->pg < 500)     // Main page?
        {
            TPage *page;

            if ((page = getPage(iter->pg)) == nullptr)
            {
                MSG_TRACE("Page " << iter->pg << ", " << iter->pn << " not found in memory. Reading it ...");

                if (!readPage(iter->pg))
                    return buttons;

                page = getPage(iter->pg);
                addPage(page);
            }

            Button::TButton *bt = page->getButton(iter->bt);

            if (bt)
                buttons.push_back(bt);
        }
        else
        {
            TSubPage *subpage;

            if ((subpage = getSubPage(iter->pg)) == nullptr)
            {
                MSG_TRACE("Subpage " << iter->pg << ", " << iter->pn << " not found in memory. Reading it ...");

                if (!readSubPage(iter->pg))
                    return buttons;

                subpage = getSubPage(iter->pg);
                TPage *page = getActualPage();

                if (!page)
                {
                    MSG_ERROR("No actual page loaded!");
                    return buttons;
                }

                page->addSubPage(subpage);
            }

            Button::TButton *bt = subpage->getButton(iter->bt);

            if (bt)
                buttons.push_back(bt);
        }
    }

    return buttons;
}

/****************************************************************************
 * The following functions implements one of the commands the panel accepts.
 ****************************************************************************/

void TPageManager::doON(int port, vector<int>& channels, vector<string>& pars)
{
    DECL_TRACER("TPageManager::doON(int port, vector<int>& channels, vector<string>& pars)");

    if (pars.empty())
    {
        MSG_WARNING("Command ON needs 1 parameter! Ignoring command.");
        return;
    }

    TError::clear();
    int c = atoi(pars[0].c_str());

    if (c <= 0)
    {
        MSG_WARNING("Invalid channel " << c << "! Ignoring command.");
        return;
    }

    vector<int> chans = { c };
    vector<MAP_T> map = findButtons(port, chans, TYPE_CM);

    if (TError::isError() || map.empty())
        return;

    vector<Button::TButton *> buttons = collectButtons(map);
    vector<Button::TButton *>::iterator mapIter;

    for (mapIter = buttons.begin(); mapIter != buttons.end(); mapIter++)
    {
        Button::TButton *bt = *mapIter;

        if (bt->getButtonType() == Button::GENERAL)
            bt->setActive(1);
    }
}

void TPageManager::doOFF(int port, vector<int>& channels, vector<string>& pars)
{
    DECL_TRACER("TPageManager::doOFF(int port, vector<int>& channels, vector<string>& pars)");

    if (pars.empty())
    {
        MSG_WARNING("Command OFF needs 1 parameter! Ignoring command.");
        return;
    }

    TError::clear();
    int c = atoi(pars[0].c_str());

    if (c <= 0)
    {
        MSG_WARNING("Invalid channel " << c << "! Ignoring command.");
        return;
    }

    vector<int> chans = { c };
    vector<MAP_T> map = findButtons(port, chans, TYPE_CM);

    if (TError::isError() || map.empty())
        return;

    vector<Button::TButton *> buttons = collectButtons(map);
    vector<Button::TButton *>::iterator mapIter;

    for (mapIter = buttons.begin(); mapIter != buttons.end(); mapIter++)
    {
        Button::TButton *bt = *mapIter;

        if (bt->getButtonType() == Button::GENERAL)
            bt->setActive(0);
    }
}

void TPageManager::doLEVEL(int port, vector<int>& channels, vector<string>& pars)
{
    DECL_TRACER("TPageManager::doLEVEL(int port, vector<int>& channels, vector<string>& pars)");

    if (pars.size() < 2)
    {
        MSG_WARNING("Command LEVEL needs 2 parameters! Ignoring command.");
        return;
    }

    TError::clear();
    int c = atoi(pars[0].c_str());
    int level = atoi(pars[1].c_str());

    if (c <= 0)
    {
        MSG_WARNING("Invalid channel " << c << "! Ignoring command.");
        return;
    }

    vector<int> chans = { c };
    vector<MAP_T> map = findBargraphs(port, chans);

    if (TError::isError() || map.empty())
    {
        MSG_WARNING("No bargraphs found!");
        return;
    }

    vector<Button::TButton *> buttons = collectButtons(map);
    vector<Button::TButton *>::iterator mapIter;

    for (mapIter = buttons.begin(); mapIter != buttons.end(); mapIter++)
    {
        Button::TButton *bt = *mapIter;

        if (bt->getButtonType() == Button::BARGRAPH)
            bt->drawBargraph(bt->getActiveInstance(), level);
        else if (bt->getButtonType() == Button::MULTISTATE_BARGRAPH)
        {
            int state = (int)((double)bt->getStateCount() / (double)(bt->getRangeHigh() - bt->getRangeLow()) * (double)level);
            bt->setActive(state);
        }
    }
}

void TPageManager::doBLINK(int port, vector<int>& channels, vector<string>& pars)
{
    DECL_TRACER("TPageManager::doBLINK(int port, vector<int>& channels, vector<string>& pars)");

    if (pars.size() < 4)
    {
        MSG_WARNING("Command BLINK expects 4 parameters! Command ignored.");
        return;
    }

    TError::clear();
    vector<int> sysButtons = { 141, 142, 143, 151, 152, 153, 154, 155, 156, 157, 158 };
    vector<MAP_T> map = findButtons(0, sysButtons);

    if (TError::isError() || map.empty())
    {
        MSG_WARNING("No system buttons found.");
        return;
    }

    vector<Button::TButton *> buttons = collectButtons(map);
    vector<Button::TButton *>::iterator mapIter;

    for (mapIter = buttons.begin(); mapIter != buttons.end(); mapIter++)
    {
        Button::TButton *bt = *mapIter;
        bt->setActive(0);
    }
}

/**
 * Add a specific popup page to a specified popup group if it does not already
 * exist. If the new popup is added to a group which has a popup displayed on
 * the current page along with the new pop-up, the displayed popup will be
 * hidden and the new popup will be displayed.
 */
void TPageManager::doAPG(int port, std::vector<int>& channels, std::vector<std::string>& pars)
{
    DECL_TRACER("TPageManager::doAPG(int port, std::vector<int>& channels, std::vector<std::string>& pars)");

    if (pars.size() < 2)
    {
        MSG_ERROR("Less than 2 parameters!");
        return;
    }

    TError::clear();
    closeGroup(pars[1]);
    TPage *page = getActualPage();

    if (!page)
    {
        MSG_ERROR("No actual page loaded!");
        return;
    }

    TSubPage *subPage = getSubPage(pars[0]);

    if (!subPage)
    {
        if (!readSubPage(pars[0]))
        {
            MSG_ERROR("Error reading subpage " << pars[0]);
            return;
        }

        subPage = getSubPage(pars[0]);
        page->addSubPage(subPage);
    }

    if (!subPage)
    {
        MSG_ERROR("Subpage " << pars[0] << " couldn't either found or created!");
        return;
    }

    subPage->setGroup(pars[1]);
    subPage->setZOrder(page->getNextZOrder());
    subPage->show();
}

/**
 * Clear all popup pages from specified popup group.
 */
void TPageManager::doCPG(int port, std::vector<int>& channels, std::vector<std::string>& pars)
{
    DECL_TRACER("TPageManager::doCPG(int port, std::vector<int>& channels, std::vector<std::string>& pars)");

    if (pars.size() < 1)
    {
        MSG_ERROR("Expecting 1 parameter but got only 1!");
        return;
    }

    TError::clear();
    vector<SUBPAGELIST_T>::iterator pgIter;
    vector<SUBPAGELIST_T> pageList = mPageList->getSupPageList();

    for (pgIter = pageList.begin(); pgIter != pageList.end(); pgIter++)
    {
        if (pgIter->group.compare(pars[0]) == 0)
        {
            pgIter->group.clear();
            TSubPage *pg = getSubPage(pgIter->pageID);

            if (pg)
                pg->setGroup(pgIter->group);
        }
    }
}

/**
 * Delete a specific popup page from specified popup group if it exists.
 */
void TPageManager::doDPG(int port, std::vector<int>& channels, std::vector<std::string>& pars)
{
    DECL_TRACER("TPageManager::doDPG(int port, std::vector<int>& channels, std::vector<std::string>& pars)");

    if (pars.size() < 2)
    {
        MSG_ERROR("Less than 2 parameters!");
        return;
    }

    TError::clear();
    SUBPAGELIST_T listPg = findSubPage(pars[0]);

    if (!listPg.isValid)
        return;

    if (listPg.group.compare(pars[1]) == 0)
    {
        listPg.group.clear();
        TSubPage *pg = getSubPage(listPg.pageID);

        if (pg)
            pg->setGroup(listPg.group);
    }
}

/**
 * Set the hide effect for the specified popup page to the named hide effect.
 */
void TPageManager::doPHE(int port, vector<int>& channels, vector<string>& pars)
{
    DECL_TRACER("TPageManager::doPHE(int port, vector<int>& channels, vector<string>& pars)");

    if (pars.size() < 2)
    {
        MSG_ERROR("Less than 2 parameters!");
        return;
    }

    TError::clear();
    TSubPage *pg = getSubPage(pars[0]);

    if (!pg)
    {
        if (!readSubPage(pars[0]))
            return;

        if ((pg = getSubPage(pars[0])) == nullptr)
            return;

        TPage *page = getActualPage();

        if (page)
            page->addSubPage(pg);
    }

    if (pars[1].compare("fade") == 0)
        pg->setHideEffect(SE_FADE);
    else if (pars[1].compare("slide to left") == 0)
        pg->setHideEffect(SE_SLIDE_LEFT);
    else if (pars[1].compare("slide to right") == 0)
        pg->setHideEffect(SE_SLIDE_RIGHT);
    else if (pars[1].compare("slide to top") == 0)
        pg->setHideEffect(SE_SLIDE_TOP);
    else if (pars[1].compare("slide to bottom") == 0)
        pg->setHideEffect(SE_SLIDE_BOTTOM);
    else if (pars[1].compare("slide to left fade") == 0)
        pg->setHideEffect(SE_SLIDE_LEFT_FADE);
    else if (pars[1].compare("slide to right fade") == 0)
        pg->setHideEffect(SE_SLIDE_RIGHT_FADE);
    else if (pars[1].compare("slide to top fade") == 0)
        pg->setHideEffect(SE_SLIDE_TOP_FADE);
    else if (pars[1].compare("slide to bottom fade") == 0)
        pg->setHideEffect(SE_SLIDE_BOTTOM_FADE);
    else
        pg->setHideEffect(SE_NONE);
}

/**
 * Set the hide effect position. Only 1 coordinate is ever needed for an effect;
 * however, the command will specify both. This command sets the location at
 * which the effect will end at.
 */
void TPageManager::doPHP(int port, vector<int>& channels, vector<string>& pars)
{
    DECL_TRACER("TPageManager::doPHP(int port, vector<int>& channels, vector<string>& pars)");

    if (pars.size() < 2)
    {
        MSG_ERROR("Less than 2 parameters!");
        return;
    }

    TError::clear();
    size_t pos = pars[1].find(",");
    int x, y;

    if (pos == string::npos)
    {
        x = atoi(pars[1].c_str());
        y = 0;
    }
    else
    {
        x = atoi(pars[1].substr(0, pos).c_str());
        y = atoi(pars[1].substr(pos+1).c_str());
    }

    TSubPage *pg = getSubPage(pars[0]);

    if (!pg)
    {
        if (!readSubPage(pars[0]))
            return;

        if ((pg = getSubPage(pars[0])) == nullptr)
            return;

        TPage *page = getActualPage();

        if (page)
            page->addSubPage(pg);
    }

    pg->setHideEndPosition(x, y);
}

/**
 * Set the hide effect time for the specified popup page.
 */
void TPageManager::doPHT(int port, vector<int>& channels, vector<string>& pars)
{
    DECL_TRACER("TPageManager::doPHT(int port, vector<int>& channels, vector<string>& pars)");

    if (pars.size() < 2)
    {
        MSG_ERROR("Less than 2 parameters!");
        return;
    }

    TError::clear();
    TSubPage *pg = getSubPage(pars[0]);

    if (!pg)
    {
        if (!readSubPage(pars[0]))
            return;

        if ((pg = getSubPage(pars[0])) == nullptr)
            return;

        TPage *page = getActualPage();

        if (page)
            page->addSubPage(pg);
    }

    pg->setHideTime(atoi(pars[1].c_str()));
}

/**
 * Close all popups on a specified page. If the page name is empty, the current
 * page is used. Same as the ’Clear Page’ command in TPDesign4.
 */
void TPageManager::doPPA(int port, std::vector<int>& channels, std::vector<std::string>& pars)
{
    DECL_TRACER("TPageManager::doPPA(int port, std::vector<int>& channels, std::vector<std::string>& pars)");

    TError::clear();
    TPage *pg;

    if (pars.size() == 0)
        pg = getPage(mActualPage);
    else
        pg = getPage(pars[0]);

    if (!pg)
        return;

    pg->drop();
    pg->resetZOrder();
}

/**
 * Deactivate a specific popup page on either a specified page or the current
 * page. If the page name is empty, the current page is used. If the popup page
 * is part of a group, the whole group is deactivated. This command works in
 * the same way as the ’Hide Popup’ command in TPDesign4.
 */
void TPageManager::doPPF(int port, std::vector<int>& channels, std::vector<std::string>& pars)
{
    DECL_TRACER("TPageManager::doPPF(int port, std::vector<int>& channels, std::vector<std::string>& pars)");

    if (pars.size() < 1)
    {
        MSG_ERROR("At least 1 parameter is expected!");
        return;
    }

    TError::clear();
    hideSubPage(pars[0]);
}

/**
 * Toggle a specific popup page on either a specified page or the current page.
 * If the page name is empty, the current page is used. Toggling refers to the
 * activating/deactivating (On/Off) of a popup page. This command works in the
 * same way as the ’Toggle Popup’ command in TPDesign4.
 */
void TPageManager::doPPG(int port, std::vector<int>& channels, std::vector<std::string>& pars)
{
    DECL_TRACER("TPageManager::doPPG(int port, std::vector<int>& channels, std::vector<std::string>& pars)");

    if (pars.size() < 1)
    {
        MSG_ERROR("At least 1 parameter is expected!");
        return;
    }

    TError::clear();
    TPage *page = getPage(mActualPage);

    if (!page)
    {
        MSG_ERROR("No active page found! Internal error.");
        return;
    }

    TSubPage *pg = getSubPage(pars[0]);

    if (!pg)
        return;

    if (pg->isVisible())
    {
        if (pg->getZOrder() == page->getActZOrder())
            page->decZOrder();

        pg->drop();
        return;
    }

    TSubPage *sub = getFirstSubPageGroup(pg->getGroupName());

    while(sub)
    {
        if (sub->getGroupName().compare(pg->getGroupName()) == 0 && sub->isVisible())
            sub->drop();

        sub = getNextSubPageGroup(pg->getGroupName(), sub);
    }

    pg->show();
    pg->setZOrder(page->getNextZOrder());
}

/**
 * Kill refers to the deactivating (Off) of a popup window from all pages. If
 * the pop-up page is part of a group, the whole group is deactivated. This
 * command works in the same way as the 'Clear Group' command in TPDesign 4.
 */
void TPageManager::doPPK(int port, std::vector<int>& channels, std::vector<std::string>& pars)
{
    DECL_TRACER("TPageManager::doPPK(int port, std::vector<int>& channels, std::vector<std::string>& pars)");

    if (pars.size() < 1)
    {
        MSG_ERROR("At least 1 parameter is expected!");
        return;
    }

    TError::clear();
    TPage *page = getPage(mActualPage);

    if (!page)
    {
        MSG_ERROR("No active page found! Internal error.");
        return;
    }

    TSubPage *pg = getSubPage(pars[0]);

    if (pg)
    {
        if (pg->getZOrder() == page->getActZOrder())
            page->decZOrder();

        pg->drop();
    }
}

/**
 * Set the modality of a specific popup page to Modal or NonModal.
 * A Modal popup page, when active, only allows you to use the buttons and
 * features on that popup page. All other buttons on the panel page are
 * inactivated.
 */
void TPageManager::doPPM(int port, std::vector<int>& channels, std::vector<std::string>& pars)
{
    DECL_TRACER("TPageManager::doPPM(int port, std::vector<int>& channels, std::vector<std::string>& pars)");

    if (pars.size() < 2)
    {
        MSG_ERROR("Expecting 2 parameters!");
        return;
    }

    TError::clear();
    TSubPage *pg = getSubPage(pars[0]);

    if (pg)
    {
        if (pars[1] == "1" || pars[1].compare("modal") == 0 || pars[1].compare("MODAL") == 0)
            pg->setModal(1);
        else
            pg->setModal(0);
    }
}

/**
 * Activate a specific popup page to launch on either a specified page or the
 * current page. If the page name is empty, the current page is used. If the
 * popup page is already on, do not re-draw it. This command works in the same
 * way as the ’Show Popup’ command in TPDesign4.
 */
void TPageManager::doPPN(int port, std::vector<int>& channels, std::vector<std::string>& pars)
{
    DECL_TRACER("TPageManager::doPPN(int port, std::vector<int>& channels, std::vector<std::string>& pars)");

    if (pars.size() < 1)
    {
        MSG_ERROR("At least 1 parameter is expected!");
        return;
    }

    TError::clear();
    showSubPage(pars[0]);
}

/**
 * Set a specific popup page to timeout within a specified time. If timeout is
 * empty, popup page will clear the timeout.
 */
void TPageManager::doPPT(int port, vector<int>& channels, vector<string>& pars)
{
    DECL_TRACER("TPageManager::doPPT(int port, vector<int>& channels, vector<string>& pars)");

    if (pars.size() < 2)
    {
        MSG_ERROR("Expecting 2 parameters!");
        return;
    }

    TError::clear();
    TSubPage *pg = getSubPage(pars[0]);

    if (!pg)
    {
        if (!readSubPage(pars[0]))
            return;

        if ((pg = getSubPage(pars[0])) == nullptr)
            return;

        TPage *page = getActualPage();

        if (page)
            page->addSubPage(pg);
    }

    pg->setTimeout(atoi(pars[1].c_str()));
}

/**
 * Close all popups on all pages. This command works in the same way as the
 * 'Clear All' command in TPDesign 4.
 */
void TPageManager::doPPX(int port, std::vector<int>& channels, std::vector<std::string>& pars)
{
    DECL_TRACER("TPageManager::doPPX(int port, std::vector<int>& channels, std::vector<std::string>& pars)");

    TError::clear();
    PCHAIN_T *chain = mPchain;

    while(chain)
    {
        TSubPage *sub = chain->page->getFirstSubPage();

        while (sub)
        {
            MSG_DEBUG("Dopping subpage " << sub->getNumber() << ", \"" << sub->getName() << "\".");
            sub->drop();
            sub = chain->page->getNextSubPage();
        }

        chain = chain->next;
    }

    TPage *page = getPage(mActualPage);

    if (!page)
    {
        MSG_ERROR("No active page found! Internal error.");
        return;
    }

    page->resetZOrder();
}

/**
 * Set the show effect for the specified popup page to the named show effect.
 */
void TPageManager::doPSE(int port, vector<int>& channels, vector<string>& pars)
{
    DECL_TRACER("TPageManager::doPSE(int port, vector<int>& channels, vector<string>& pars)");

    if (pars.size() < 2)
    {
        MSG_ERROR("Less than 2 parameters!");
        return;
    }

    TError::clear();
    TSubPage *pg = getSubPage(pars[0]);

    if (!pg)
    {
        if (!readSubPage(pars[0]))
            return;

        if ((pg = getSubPage(pars[0])) == nullptr)
            return;

        TPage *page = getActualPage();

        if (page)
            page->addSubPage(pg);
    }

    if (pars[1].compare("fade") == 0)
        pg->setShowEffect(SE_FADE);
    else if (pars[1].compare("slide to left") == 0)
        pg->setShowEffect(SE_SLIDE_LEFT);
    else if (pars[1].compare("slide to right") == 0)
        pg->setShowEffect(SE_SLIDE_RIGHT);
    else if (pars[1].compare("slide to top") == 0)
        pg->setShowEffect(SE_SLIDE_TOP);
    else if (pars[1].compare("slide to bottom") == 0)
        pg->setShowEffect(SE_SLIDE_BOTTOM);
    else if (pars[1].compare("slide to left fade") == 0)
        pg->setShowEffect(SE_SLIDE_LEFT_FADE);
    else if (pars[1].compare("slide to right fade") == 0)
        pg->setShowEffect(SE_SLIDE_RIGHT_FADE);
    else if (pars[1].compare("slide to top fade") == 0)
        pg->setShowEffect(SE_SLIDE_TOP_FADE);
    else if (pars[1].compare("slide to bottom fade") == 0)
        pg->setShowEffect(SE_SLIDE_BOTTOM_FADE);
    else
        pg->setShowEffect(SE_NONE);
}

void TPageManager::doPSP(int port, vector<int>& channels, vector<string>& pars)
{
    DECL_TRACER("TPageManager::doPSP(int port, vector<int>& channels, vector<string>& pars)");

    if (pars.size() < 2)
    {
        MSG_ERROR("Less than 2 parameters!");
        return;
    }

    TError::clear();
    size_t pos = pars[1].find(",");
    int x, y;

    if (pos == string::npos)
    {
        x = atoi(pars[1].c_str());
        y = 0;
    }
    else
    {
        x = atoi(pars[1].substr(0, pos).c_str());
        y = atoi(pars[1].substr(pos+1).c_str());
    }

    TSubPage *pg = getSubPage(pars[0]);

    if (!pg)
    {
        if (!readSubPage(pars[0]))
            return;

        if ((pg = getSubPage(pars[0])) == nullptr)
            return;

        TPage *page = getActualPage();

        if (page)
            page->addSubPage(pg);
    }

    pg->setShowEndPosition(x, y);
}

/**
 * Set the show effect time for the specified popup page.
 */
void TPageManager::doPST(int port, vector<int>& channels, vector<string>& pars)
{
    DECL_TRACER("TPageManager::doPST(int port, vector<int>& channels, vector<string>& pars)");

    if (pars.size() < 2)
    {
        MSG_ERROR("Less than 2 parameters!");
        return;
    }

    TError::clear();
    size_t pos = pars[1].find(",");
    int x, y;

    if (pos == string::npos)
    {
        x = atoi(pars[1].c_str());
        y = 0;
    }
    else
    {
        x = atoi(pars[1].substr(0, pos).c_str());
        y = atoi(pars[1].substr(pos+1).c_str());
    }

    TSubPage *pg = getSubPage(pars[0]);

    if (!pg)
    {
        if (!readSubPage(pars[0]))
            return;

        if ((pg = getSubPage(pars[0])) == nullptr)
            return;

        TPage *page = getActualPage();

        if (page)
            page->addSubPage(pg);
    }

    pg->setShowTime(atoi(pars[1].c_str()));
}

/**
 * Flips to a page with a specified page name. If the page is currently active,
 * it will not redraw the page.
 */
void TPageManager::doPAGE(int port, std::vector<int>& channels, std::vector<std::string>& pars)
{
    DECL_TRACER("TPageManager::doPAGE(int port, std::vector<int>& channels, std::vector<std::string>& pars)");

    if (pars.empty())
    {
        MSG_WARNING("Got no page parameter!");
        return;
    }

    TError::clear();
    setPage(pars[0]);
}

/**
 * Add page flip action to a button if it does not already exist.
 */
void TPageManager::doAPF(int port, vector<int>& channels, vector<string>& pars)
{
    DECL_TRACER("TPageManager::doAPF(int port, vector<int>& channels, vector<string>& pars)");

    if (pars.size() < 2)
    {
        MSG_ERROR("Expecting 2 parameters but got " << pars.size() << "! Ignoring command.");
        return;
    }

    TError::clear();
    string action = pars[0];
    string pname = pars[1];

    vector<MAP_T> map = findButtons(port, channels);

    if (TError::isError() || map.empty())
        return;

    vector<Button::TButton *> buttons = collectButtons(map);
    vector<Button::TButton *>::iterator mapIter;

    for (mapIter = buttons.begin(); mapIter != buttons.end(); mapIter++)
    {
        Button::TButton *bt = *mapIter;
        setButtonCallbacks(bt);
        bt->addPushFunction(action, pname);
    }
}

/**
 * Assign a picture to those buttons with a defined address range.
 */
void TPageManager::doBMP(int port, vector<int>& channels, vector<string>& pars)
{
    DECL_TRACER("TPageManager::doBMP(int port, vector<int>& channels, vector<string>& pars)");

    if (pars.size() < 2)
    {
        MSG_ERROR("Expecting 2 parameters but got " << pars.size() << "! Ignoring command.");
        return;
    }

    TError::clear();
    int btState = atoi(pars[0].c_str());
    string bitmap = pars[1];

    vector<MAP_T> map = findButtons(port, channels);

    if (TError::isError() || map.empty())
        return;

    vector<Button::TButton *> buttons = collectButtons(map);
    vector<Button::TButton *>::iterator mapIter;

    for (mapIter = buttons.begin(); mapIter != buttons.end(); mapIter++)
    {
        Button::TButton *bt = *mapIter;
        setButtonCallbacks(bt);

        if (btState == 0)       // All instances?
        {
            int bst = bt->getNumberInstances();
            MSG_DEBUG("Setting bitmal " << bitmap << " on all " << bst << " instances...");

            for (int i = 0; i < bst; i++)
                bt->setBitmap(bitmap, i);
        }
        else
            bt->setBitmap(bitmap, btState);
    }
}

void TPageManager::setButtonCallbacks(Button::TButton *bt)
{
    bt->registerCallback(_displayButton);
    bt->registerCallbackFT(_setText);
    bt->regCallPlayVideo(_callPlayVideo);
    bt->setFonts(mFonts);
    bt->setPalette(mPalette);
}

/**
 * Set the button opacity. The button opacity can be specified as a decimal
 * between 0 - 255, where zero (0) is invisible and 255 is opaque, or as a
 * HEX code, as used in the color commands by preceding the HEX code with
 * the # sign. In this case, #00 becomes invisible and #FF becomes opaque.
 * If the opacity is set to zero (0), this does not make the button inactive,
 * only invisible.
 */
void TPageManager::doBOP(int port, vector<int>& channels, vector<string>& pars)
{
    DECL_TRACER("TPageManager::doBOP(int port, vector<int>& channels, vector<string>& pars)");

    if (pars.size() < 2)
    {
        MSG_ERROR("Expecting 2 parameters but got " << pars.size() << "! Ignoring command.");
        return;
    }

    TError::clear();
    int btState = atoi(pars[0].c_str());
    int btOpacity = 0;

    if (pars[1].at(0) == '#')
        btOpacity = (int)strtol(pars[1].substr(1).c_str(), NULL, 16);
    else
        btOpacity = atoi(pars[1].c_str());

    vector<MAP_T> map = findButtons(port, channels);

    if (TError::isError() || map.empty())
        return;

    vector<Button::TButton *> buttons = collectButtons(map);
    vector<Button::TButton *>::iterator mapIter;

    for (mapIter = buttons.begin(); mapIter != buttons.end(); mapIter++)
    {
        Button::TButton *bt = *mapIter;
        setButtonCallbacks(bt);

        if (btState == 0)       // All instances?
        {
            int bst = bt->getNumberInstances();
            MSG_DEBUG("Setting opacity " << btOpacity << " on all " << bst << " instances...");

            for (int i = 0; i < bst; i++)
                bt->setOpacity(btOpacity, i);
        }
        else
            bt->setOpacity(btOpacity, btState);
    }
}

/**
 * Set the button size and its position on the page.
 */
void TPageManager::doBSP(int port, vector<int>& channels, vector<string>& pars)
{
    DECL_TRACER("TPageManager::doBSP(int port, vector<int>& channels, vector<string>& pars)");

    if (pars.size() < 1)
    {
        MSG_ERROR("Expecting at least 1 parameter but got " << pars.size() << "! Ignoring command.");
        return;
    }

    TError::clear();
    bool bLeft = false, bTop = false, bRight = false, bBottom = false;
    int x, y;
    vector<string>::iterator iter;

    for (iter = pars.begin(); iter != pars.end(); iter++)
    {
        if (iter->compare("left") == 0)
            bLeft = true;
        else if (iter->compare("top") == 0)
            bTop = true;
        else if (iter->compare("right") == 0)
            bRight = true;
        else if (iter->compare("bottom") == 0)
            bBottom = true;
    }


    vector<MAP_T> map = findButtons(port, channels);

    if (TError::isError() || map.empty())
        return;

    vector<Button::TButton *> buttons = collectButtons(map);
    vector<Button::TButton *>::iterator mapIter;

    for (mapIter = buttons.begin(); mapIter != buttons.end(); mapIter++)
    {
        Button::TButton *bt = *mapIter;
        setButtonCallbacks(bt);

        if (bLeft)
            x = 0;

        if (bTop)
            y = 0;

        if (bRight)
        {
            ulong handle = bt->getHandle();
            int parentID = (handle >> 16) & 0x0000ffff;
            int pwidth = 0;

            if (parentID < 500)
            {
                TPage *pg = getPage(parentID);

                if (!pg)
                {
                    MSG_ERROR("Internal error: Page " << parentID << " not found!");
                    return;
                }

                pwidth = pg->getWidth();
            }
            else
            {
                TSubPage *spg = getSubPage(parentID);

                if (!spg)
                {
                    MSG_ERROR("Internal error: Subpage " << parentID << " not found!");
                    return;
                }

                pwidth = spg->getWidth();
            }

            x = pwidth - bt->getWidth();
        }

        if (bBottom)
        {
            ulong handle = bt->getHandle();
            int parentID = (handle >> 16) & 0x0000ffff;
            int pheight = 0;

            if (parentID < 500)
            {
                TPage *pg = getPage(parentID);

                if (!pg)
                {
                    MSG_ERROR("Internal error: Page " << parentID << " not found!");
                    return;
                }

                pheight = pg->getHeight();
            }
            else
            {
                TSubPage *spg = getSubPage(parentID);

                if (!spg)
                {
                    MSG_ERROR("Internal error: Subpage " << parentID << " not found!");
                    return;
                }

                pheight = spg->getHeight();
            }

            y = pheight - bt->getHeight();
        }

        bt->setLeftTop(x, y);
    }
}

/**
 * Set the button word wrap feature to those buttons with a defined address
 * range. By default, word-wrap is Off.
 */
void TPageManager::doBWW(int port, vector<int>& channels, vector<string>& pars)
{
    DECL_TRACER("TPageManager::doBWW(int port, vector<int>& channels, vector<string>& pars)");

    if (pars.size() < 1)
    {
        MSG_ERROR("Expecting 1 parameter but got " << pars.size() << "! Ignoring command.");
        return;
    }

    TError::clear();
    int btState = atoi(pars[0].c_str());

    vector<MAP_T> map = findButtons(port, channels);

    if (TError::isError() || map.empty())
        return;

    vector<Button::TButton *> buttons = collectButtons(map);
    vector<Button::TButton *>::iterator mapIter;

    for (mapIter = buttons.begin(); mapIter != buttons.end(); mapIter++)
    {
        Button::TButton *bt = *mapIter;
        setButtonCallbacks(bt);

        if (btState == 0)       // All instances?
        {
            int bst = bt->getNumberInstances();
            MSG_DEBUG("Setting word wrap on all " << bst << " instances...");

            for (int i = 0; i < bst; i++)
                bt->setWorWrap(true, i);
        }
        else
            bt->setWorWrap(true, btState - 1);
    }
}

/**
 * Clear all page flips from a button.
 */
void TPageManager::doCPF(int port, vector<int>& channels, vector<string>& pars)
{
    DECL_TRACER("TPageManager::doCPF(int port, vector<int>& channels, vector<string>& pars)");

    TError::clear();
    vector<MAP_T> map = findButtons(port, channels);

    if (TError::isError() || map.empty())
        return;

    vector<Button::TButton *> buttons = collectButtons(map);
    vector<Button::TButton *>::iterator mapIter;

    for (mapIter = buttons.begin(); mapIter != buttons.end(); mapIter++)
    {
        Button::TButton *bt = *mapIter;
        setButtonCallbacks(bt);
        bt->clearPushFunctions();
    }
}

/**
 * Delete page flips from button if it already exists.
 */
void TPageManager::doDPF(int port, vector<int>& channels, vector<string>& pars)
{
    DECL_TRACER("TPageManager::doDPF(int port, vector<int>& channels, vector<string>& pars)");

    if (pars.size() < 1)
    {
        MSG_ERROR("Expecting at least 1 parameter but got " << pars.size() << "! Ignoring command.");
        return;
    }

    TError::clear();
    string action = pars[0];
    string pname;

    if (pars.size() >= 2)
    {
        pname = pars[1];
        vector<Button::TButton *> list;
        // First we search for a subpage because this is more likely
        TSubPage *spg = getSubPage(pname);

        if (spg)
            list = spg->getButtons(port, channels[0]);
        else    // Then for a page
        {
            TPage *pg = getPage(pname);

            if (pg)
                list = pg->getButtons(port, channels[0]);
            else
            {
                MSG_WARNING("The name " << pname << " doesn't name either a page or a subpage!");
                return;
            }
        }

        if (list.empty())
            return;

        vector<Button::TButton *>::iterator it;

        for (it = list.begin(); it != list.end(); it++)
        {
            Button::TButton *bt = *it;
            setButtonCallbacks(bt);
            bt->clearPushFunction(action);
        }

        return;
    }

    // Here we don't have a page name
    vector<MAP_T> map = findButtons(port, channels);

    if (TError::isError() || map.empty())
        return;

    vector<Button::TButton *> buttons = collectButtons(map);
    vector<Button::TButton *>::iterator mapIter;

    for (mapIter = buttons.begin(); mapIter != buttons.end(); mapIter++)
    {
        Button::TButton *bt = *mapIter;
        setButtonCallbacks(bt);
        bt->clearPushFunction(action);
    }
}

/**
 * Enable or disable buttons with a set variable text range.
 */
void TPageManager::doENA(int port, vector<int>& channels, vector<string>& pars)
{
    DECL_TRACER("TPageManager::doENA(int port, vector<int>& channels, vector<string>& pars)");

    if (pars.empty())
    {
        MSG_ERROR("Expecting 1 parameter but got none! Ignoring command.");
        return;
    }

    TError::clear();
    int cvalue = atoi(pars[0].c_str());

    vector<MAP_T> map = findButtons(port, channels);

    if (TError::isError() || map.empty())
        return;

    vector<Button::TButton *> buttons = collectButtons(map);
    vector<Button::TButton *>::iterator mapIter;

    for (mapIter = buttons.begin(); mapIter != buttons.end(); mapIter++)
    {
        Button::TButton *bt = *mapIter;
        setButtonCallbacks(bt);
        bt->setEnable(((cvalue)?true:false));
    }
}

/**
 * Set a font to a specific Font ID value for those buttons with a defined
 * address range. Font ID numbers are generated by the TPDesign4 programmers
 * report.
 */
void TPageManager::doFON(int port, vector<int>& channels, vector<string>& pars)
{
    DECL_TRACER("TPageManager::doFON(int port, vector<int>& channels, vector<string>& pars)");

    if (pars.size() < 2)
    {
        MSG_ERROR("Expecting 2 parameters but got " << pars.size() << "! Ignoring command.");
        return;
    }

    TError::clear();
    int btState = atoi(pars[0].c_str());
    int fvalue = atoi(pars[1].c_str());

    vector<MAP_T> map = findButtons(port, channels);

    if (TError::isError() || map.empty())
        return;

    vector<Button::TButton *> buttons = collectButtons(map);
    vector<Button::TButton *>::iterator mapIter;

    for (mapIter = buttons.begin(); mapIter != buttons.end(); mapIter++)
    {
        Button::TButton *bt = *mapIter;
        setButtonCallbacks(bt);

        if (btState == 0)       // All instances?
        {
            int bst = bt->getNumberInstances();
            MSG_DEBUG("Setting font " << fvalue << " on all " << bst << " instances...");

            for (int i = 0; i < bst; i++)
                bt->setFont(fvalue, i);
        }
        else
            bt->setFont(fvalue, btState - 1);
    }
}

/**
 * Set the icon to a button.
 */
void TPageManager::doICO(int port, vector<int>& channels, vector<string>& pars)
{
    DECL_TRACER("TPageManager::doICO(int port, vector<int>& channels, vector<string>& pars)");

    if (pars.size() < 2)
    {
        MSG_ERROR("Expecting 2 parameters but got " << pars.size() << "! Ignoring command.");
        return;
    }

    TError::clear();
    int btState = atoi(pars[0].c_str());
    int iconIdx = atoi(pars[1].c_str());

    vector<MAP_T> map = findButtons(port, channels);

    if (TError::isError() || map.empty())
        return;

    vector<Button::TButton *> buttons = collectButtons(map);
    vector<Button::TButton *>::iterator mapIter;

    for (mapIter = buttons.begin(); mapIter != buttons.end(); mapIter++)
    {
        Button::TButton *bt = *mapIter;
        setButtonCallbacks(bt);

        if (btState == 0)       // All instances?
        {
            int bst = bt->getNumberInstances();
            MSG_DEBUG("Setting Icon on all " << bst << " instances...");

            for (int i = 0; i < bst; i++)
            {
                if (iconIdx > 0)
                    bt->setIcon(iconIdx, i);
                else
                    bt->revokeIcon(i);
            }
        }
        else if (iconIdx > 0)
            bt->setIcon(iconIdx, btState - 1);
        else
            bt->revokeIcon(btState - 1);
    }
}

/**
 * Show or hide a button with a set variable text range.
 */
void TPageManager::doSHO(int port, vector<int>& channels, vector<string>& pars)
{
    DECL_TRACER("TPageManager::doSHO(int port, vector<int>& channels, vector<string>& pars)");

    if (pars.empty())
    {
        MSG_ERROR("Expecting 1 parameter but got none! Ignoring command.");
        return;
    }

    TError::clear();
    int cvalue = atoi(pars[0].c_str());

    vector<MAP_T> map = findButtons(port, channels);

    if (TError::isError() || map.empty())
        return;

    vector<Button::TButton *> buttons = collectButtons(map);
    vector<Button::TButton *>::iterator mapIter;

    for (mapIter = buttons.begin(); mapIter != buttons.end(); mapIter++)
    {
        Button::TButton *bt = *mapIter;
        setButtonCallbacks(bt);
        bt->setVisible(((cvalue)?true:false));
        bt->refresh();
    }
}

/**
 * Assign a text string to those buttons with a defined address range.
 * Sets Non-Unicode text.
 */
void TPageManager::doTXT(int port, vector<int>& channels, vector<string>& pars)
{
    DECL_TRACER("TPageManager::doTXT(int port, vector<int>& channels, vector<string>& pars)");

    if (pars.size() < 1)
    {
        MSG_ERROR("Expecting 1 parameters but got none! Ignoring command.");
        return;
    }

    TError::clear();
    int btState = atoi(pars[0].c_str());
    string text;

    if (pars.size() > 1)
        text = pars[1];

    vector<MAP_T> map = findButtons(port, channels);

    if (TError::isError() || map.empty())
        return;

    vector<Button::TButton *> buttons = collectButtons(map);
    vector<Button::TButton *>::iterator mapIter;

    for (mapIter = buttons.begin(); mapIter != buttons.end(); mapIter++)
    {
        Button::TButton *bt = *mapIter;
        setButtonCallbacks(bt);

        if (btState == 0)       // All instances?
        {
            int bst = bt->getNumberInstances();
            MSG_DEBUG("Setting TXT on all " << bst << " instances...");

            for (int i = 0; i < bst; i++)
                bt->setText(text, i);
        }
        else
            bt->setText(text, btState - 1);
    }
}

void TPageManager::doBBR(int port, vector<int>& channels, vector<string>& pars)
{
    DECL_TRACER("TPageManager::doBBR(int port, vector<int>& channels, vector<string>& pars)");

    if (pars.size() < 2)
    {
        MSG_ERROR("Expecting 2 parameters but got none! Ignoring command.");
        return;
    }

    TError::clear();
    int btState = atoi(pars[0].c_str());
    string resName = pars[1];

    vector<MAP_T> map = findButtons(port, channels);

    if (TError::isError() || map.empty())
        return;

    vector<Button::TButton *> buttons = collectButtons(map);
    vector<Button::TButton *>::iterator mapIter;

    for (mapIter = buttons.begin(); mapIter != buttons.end(); mapIter++)
    {
        Button::TButton *bt = *mapIter;
        setButtonCallbacks(bt);

        if (btState == 0)       // All instances?
        {
            int bst = bt->getNumberInstances();
            MSG_DEBUG("Setting BBR on all " << bst << " instances...");

            for (int i = 0; i < bst; i++)
                bt->setResourceName(resName, i);
        }
        else
            bt->setResourceName(resName, btState - 1);
    }
}

void TPageManager::doRMF(int port, vector<int>& channels, vector<string>& pars)
{
    DECL_TRACER("TPageManager::doRMF(int port, vector<int>& channels, vector<string>& pars)");

    if (pars.size() < 2)
    {
        MSG_ERROR("Expecting 2 parameters but got none! Ignoring command.");
        return;
    }

    string name = pars[0];
    string data = pars[1];

    vector<string> parts = StrSplit(data, "%");
    RESOURCE_T res;
    vector<string>::iterator sIter;

    for (sIter = parts.begin(); sIter != parts.end(); sIter++)
    {
        const char *s = sIter->c_str();
        MSG_DEBUG("Parsing \"" << s << "\" with token << " << *s);

        switch(*s)
        {
            case 'P':
                if (*(s+1) == '0')
                    res.protocol = "HTTP";
                else
                    res.protocol = "FTP";
            break;

            case 'U': res.user = sIter->substr(1); break;
            case 'S': res.password = sIter->substr(1); break;
            case 'H': res.host = sIter->substr(1); break;
            case 'F': res.file = sIter->substr(1); break;
            case 'A': res.path = sIter->substr(1); break;
            case 'R': res.refresh = atoi(sIter->substr(1).c_str()); break;

            default:
                MSG_WARNING("Option " << sIter->at(0) << " is currently not implemented!");
        }
    }

    if (gPrjResources)
        gPrjResources->setResource(name, res.protocol, res.host, res.path, res.file, res.user, res.password, res.refresh);
}