Rev 20 | 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 <string>
#include <memory>
#include <include/core/SkPixmap.h>
#include <include/core/SkSize.h>
#include <include/core/SkColor.h>
#include <include/core/SkFont.h>
#include <include/core/SkTypeface.h>
#include <include/core/SkFontMetrics.h>
#include <include/core/SkTextBlob.h>
#include <include/core/SkRegion.h>
#include <include/core/SkImageFilter.h>
#include <include/effects/SkImageFilters.h>
#include <include/core/SkPath.h>
#include <include/core/SkSurfaceProps.h>
#include "tbutton.h"
#include "terror.h"
#include "tconfig.h"
#include "tresources.h"
#include "ticons.h"
#include "tamxnet.h"
#include "tobject.h"
#include "tpagemanager.h"
using std::exception;
using std::string;
using std::vector;
using std::unique_ptr;
using std::map;
using std::pair;
using std::thread;
using std::atomic;
using std::mutex;
using std::bind;
using namespace Button;
#define MAX_BUFFER 65536
extern TIcons *gIcons;
extern amx::TAmxNet *gAmxNet;
atomic<bool> TButton::mAniRunning;
THR_REFRESH_t *TButton::mThrRefresh = nullptr;
mutex mutex_button;
mutex mutex_bargraph;
mutex mutex_sysdraw;
SYSBORDER_t sysBorders[] = {
{ 1, (char *)"Single Line", 0, (char *)"solid", 1, 0 },
{ 2, (char *)"Double Line", 0, (char *)"solid", 2, 0 },
{ 3, (char *)"Quad Line", 0, (char *)"solid", 4, 0 },
{ 4, (char *)"Picture Frame", 0, (char *)"double", 0, 0 },
{ 5, (char *)"Circle 15", 8, (char *)"solid", 2, 15 },
{ 6, (char *)"Circle 25", 9, (char *)"solid", 2, 25 },
{ 7, (char *)"Circle 35", 10, (char *)"solid", 2, 35 },
{ 8, (char *)"Circle 45", 11, (char *)"solid", 2, 45 },
{ 9, (char *)"Circle 55", 12, (char *)"solid", 2, 55 },
{ 10, (char *)"Circle 65", 13, (char *)"solid", 2, 65 },
{ 11, (char *)"Circle 75", 14, (char *)"solid", 2, 75 },
{ 12, (char *)"Circle 85", 15, (char *)"solid", 2, 85 },
{ 13, (char *)"Circle 95", 16, (char *)"solid", 2, 95 },
{ 14, (char *)"Circle 105", 17, (char *)"solid", 2, 105 },
{ 15, (char *)"Circle 115", 18, (char *)"solid", 2, 115 },
{ 16, (char *)"Circle 125", 19, (char *)"solid", 2, 125 },
{ 17, (char *)"Circle 135", 20, (char *)"solid", 2, 135 },
{ 18, (char *)"Circle 145", 21, (char *)"solid", 2, 145 },
{ 19, (char *)"Circle 155", 22, (char *)"solid", 2, 155 },
{ 20, (char *)"Circle 165", 23, (char *)"solid", 2, 165 },
{ 21, (char *)"Circle 175", 24, (char *)"solid", 2, 175 },
{ 22, (char *)"Circle 185", 25, (char *)"solid", 2, 185 },
{ 23, (char *)"Circle 195", 26, (char *)"solid", 2, 195 },
{ 24, (char *)"AMX Elite Inset _L", 0, (char *)"groove", 10, 0 },
{ 25, (char *)"AMX Elite Raised _L", 0, (char *)"ridge", 10, 0 },
{ 26, (char *)"AMX Elite Inset _M", 0, (char *)"groove", 5, 0 },
{ 27, (char *)"AMX Elite Raised _M", 0, (char *)"ridge", 5, 0 },
{ 28, (char *)"AMX Elite Inset _S", 0, (char *)"groove", 2, 0 },
{ 29, (char *)"AMX Elite Raised _S", 0, (char *)"ridge", 2, 0 },
{ 30, (char *)"Bevel Inset _L", 0, (char *)"inset", 10, 0 },
{ 31, (char *)"Bevel Raised _L", 0, (char *)"outset", 10, 0 },
{ 32, (char *)"Bevel Inset _M", 0, (char *)"inset", 5, 0 },
{ 33, (char *)"Bevel Raised _M", 0, (char *)"outset", 5, 0 },
{ 34, (char *)"Bevel Inset _S", 0, (char *)"inset", 2, 0 },
{ 35, (char *)"Bevel Raised _S", 0, (char *)"outset", 2, 0 },
{ 0, nullptr, 0, nullptr, 0, 0 }
};
SYSBUTTONS_t sysButtons[] = {
{ 8, MULTISTATE_BARGRAPH, 12, 0, 11 }, // Connection status
{ 141, GENERAL, 2, 0, 0 }, // Standard time
{ 142, GENERAL, 2, 0, 0 }, // Time AM/PM
{ 143, GENERAL, 2, 0, 0 }, // 24 hour time
{ 151, GENERAL, 2, 0, 0 }, // Date weekday
{ 152, GENERAL, 2, 0, 0 }, // Date mm/dd
{ 153, GENERAL, 2, 0, 0 }, // Date dd/mm
{ 154, GENERAL, 2, 0, 0 }, // Date mm/dd/yyyy
{ 155, GENERAL, 2, 0, 0 }, // Date dd/mm/yyyy
{ 156, GENERAL, 2, 0, 0 }, // Date month dd, yyyy
{ 157, GENERAL, 2, 0, 0 }, // Date dd month, yyyy
{ 158, GENERAL, 2, 0, 0 }, // Date yyyy-mm-dd
{ 234, GENERAL, 2, 0, 0 }, // Battery charging/not charging
{ 242, BARGRAPH, 2, 0, 100 }, // Battery level
{ 0, NONE, 0, 0, 0 } // Terminate
};
TButton::TButton()
{
DECL_TRACER("TButton::TButton()");
mAniRunning = false;
mLastBlink.clear();
}
TButton::~TButton()
{
DECL_TRACER("TButton::~TButton()");
map<int, IMAGE_t>::iterator iter;
for (iter = mImages.begin(); iter != mImages.end(); iter++)
{
if (!iter->second.imageMi.empty())
iter->second.imageMi.reset();
if (!iter->second.imageBm.empty())
iter->second.imageBm.reset();
}
mImages.clear();
if (ap == 0 && ad == 8)
{
if (gAmxNet)
gAmxNet->deregNetworkState(mHandle);
}
if (ap == 0 && ((ad >= 141 && ad <= 143) || (ad >= 151 && ad <= 158)))
{
if (gAmxNet)
gAmxNet->deregTimer(mHandle);
}
if (mTimer)
{
mTimer->stop();
while (mTimer->isRunning())
usleep(50);
delete mTimer;
}
THR_REFRESH_t *next, *p = mThrRefresh;
while (p)
{
if (p->mImageRefresh)
{
p->mImageRefresh->stop();
while (p->mImageRefresh->isRunning())
usleep(50);
delete p->mImageRefresh;
p->mImageRefresh = nullptr;
}
next = p->next;
delete p;
p = next;
}
}
void TButton::initialize(TReadXML *xml, mxml_node_t *node)
{
DECL_TRACER("TButton::initialize(TReadXML *xml, mxml_node_t *node)");
if (!xml || !node)
{
MSG_ERROR("Invalid NULL parameter passed!");
TError::setError();
return;
}
mxml_node_t *child = xml->getFirstChild(node);
string stype = xml->getAttributeFromNode(node, "type");
type = getButtonType(stype);
MSG_DEBUG("Button type: " << stype << " --> " << type);
while(child)
{
string ename = xml->getElementName(child);
if (ename.compare("bi") == 0)
bi = xml->getIntFromNode(child);
else if (ename.compare("na") == 0)
na = xml->getTextFromNode(child);
else if (ename.compare("bd") == 0)
bd = xml->getTextFromNode(child);
else if (ename.compare("lt") == 0)
lt = xml->getIntFromNode(child);
else if (ename.compare("tp") == 0)
tp = xml->getIntFromNode(child);
else if (ename.compare("wt") == 0)
wt = xml->getIntFromNode(child);
else if (ename.compare("ht") == 0)
ht = xml->getIntFromNode(child);
else if (ename.compare("zo") == 0)
zo = xml->getIntFromNode(child);
else if (ename.compare("hs") == 0)
hs = xml->getTextFromNode(child);
else if (ename.compare("bs") == 0)
bs = xml->getTextFromNode(child);
else if (ename.compare("fb") == 0)
fb = getButtonFeedback(xml->getTextFromNode(child));
else if (ename.compare("ap") == 0)
ap = xml->getIntFromNode(child);
else if (ename.compare("ad") == 0)
ad = xml->getIntFromNode(child);
else if (ename.compare("ch") == 0)
ch = xml->getIntFromNode(child);
else if (ename.compare("cp") == 0)
cp = xml->getIntFromNode(child);
else if (ename.compare("lp") == 0)
lp = xml->getIntFromNode(child);
else if (ename.compare("lv") == 0)
lv = xml->getIntFromNode(child);
else if (ename.compare("dr") == 0)
dr = xml->getTextFromNode(child);
else if (ename.compare("va") == 0)
va = xml->getIntFromNode(child);
else if (ename.compare("rm") == 0)
rm = xml->getIntFromNode(child);
else if (ename.compare("nu") == 0)
nu = xml->getIntFromNode(child);
else if (ename.compare("nd") == 0)
nd = xml->getIntFromNode(child);
else if (ename.compare("ar") == 0)
ar = xml->getIntFromNode(child);
else if (ename.compare("ru") == 0)
ru = xml->getIntFromNode(child);
else if (ename.compare("rd") == 0)
rd = xml->getIntFromNode(child);
else if (ename.compare("lu") == 0)
lu = xml->getIntFromNode(child);
else if (ename.compare("ld") == 0)
ld = xml->getIntFromNode(child);
else if (ename.compare("rv") == 0)
rv = xml->getIntFromNode(child);
else if (ename.compare("rl") == 0)
rl = xml->getIntFromNode(child);
else if (ename.compare("rh") == 0)
rh = xml->getIntFromNode(child);
else if (ename.compare("ri") == 0)
ri = xml->getIntFromNode(child);
else if (ename.compare("rn") == 0)
rn = xml->getIntFromNode(child);
else if (ename.compare("if") == 0)
_if = xml->getTextFromNode(child);
else if (ename.compare("sd") == 0)
sd = xml->getTextFromNode(child);
else if (ename.compare("sc") == 0)
sc = xml->getTextFromNode(child);
else if (ename.compare("mt") == 0)
mt = xml->getIntFromNode(child);
else if (ename.compare("dt") == 0)
dt = xml->getTextFromNode(child);
else if (ename.compare("im") == 0)
im = xml->getTextFromNode(child);
else if (ename.compare("op") == 0)
op = xml->getTextFromNode(child);
else if (ename.compare("pf") == 0)
{
PUSH_FUNC_T pf;
pf.pfName = xml->getTextFromNode(child);
pf.pfType = xml->getAttributeFromNode(child, "type");
pushFunc.push_back(pf);
}
else if (ename.compare("sr") == 0)
{
mxml_node_t *n = xml->getFirstChild(child);
SR_T bsr;
bsr.number = atoi(xml->getAttributeFromNode(child, "number").c_str());
while (n)
{
string e = xml->getElementName(n);
if (e.compare("do") == 0)
bsr._do = xml->getTextFromNode(n);
else if (e.compare("bs") == 0)
bsr.bs = xml->getTextFromNode(n);
else if (e.compare("mi") == 0)
bsr.mi = xml->getTextFromNode(n);
else if (e.compare("cb") == 0)
bsr.cb = xml->getTextFromNode(n);
else if (e.compare("cf") == 0)
bsr.cf = xml->getTextFromNode(n);
else if (e.compare("ct") == 0)
bsr.ct = xml->getTextFromNode(n);
else if (e.compare("ec") == 0)
bsr.ec = xml->getTextFromNode(n);
else if (e.compare("bm") == 0)
{
bsr.bm = xml->getTextFromNode(n);
bsr.dynamic = ((atoi(xml->getAttributeFromNode(n, "dynamic").c_str()) == 1) ? true : false);
}
else if (e.compare("sd") == 0)
bsr.sd = xml->getTextFromNode(n);
else if (e.compare("sb") == 0)
bsr.sb = xml->getIntFromNode(n);
else if (e.compare("ii") == 0)
bsr.ii = xml->getIntFromNode(n);
else if (e.compare("ji") == 0)
bsr.ji = xml->getIntFromNode(n);
else if (e.compare("jb") == 0)
bsr.jb = xml->getIntFromNode(n);
else if (e.compare("bx") == 0)
bsr.bx = xml->getIntFromNode(n);
else if (e.compare("by") == 0)
bsr.by = xml->getIntFromNode(n);
else if (e.compare("ix") == 0)
bsr.ix = xml->getIntFromNode(n);
else if (e.compare("iy") == 0)
bsr.iy = xml->getIntFromNode(n);
else if (e.compare("fi") == 0)
bsr.fi = xml->getIntFromNode(n);
else if (e.compare("te") == 0)
bsr.te = xml->getTextFromNode(n);
else if (e.compare("jt") == 0)
bsr.jt = (TEXT_ORIENTATION)xml->getIntFromNode(n);
else if (e.compare("tx") == 0)
bsr.tx = xml->getIntFromNode(n);
else if (e.compare("ty") == 0)
bsr.ty = xml->getIntFromNode(n);
else if (e.compare("ww") == 0)
bsr.ww = xml->getIntFromNode(n);
else if (e.compare("et") == 0)
bsr.et = xml->getIntFromNode(n);
else if (e.compare("oo") == 0)
bsr.oo = xml->getIntFromNode(n);
n = xml->getNextChild(n);
}
sr.push_back(bsr);
}
child = xml->getNextChild(child);
if (xml->getElementName(child).compare("button") == 0)
{
MSG_WARNING("Unexpected element \"button\" found! Please check XML file.");
break;
}
}
}
BUTTONTYPE TButton::getButtonType(const string& bt)
{
DECL_TRACER("TButton::getButtonType(const string& bt)");
if (bt.compare("general") == 0)
return GENERAL;
else if (bt.compare("multi-state general") == 0)
return MULTISTATE_GENERAL;
else if (bt.compare("bargraph") == 0)
return BARGRAPH;
else if (bt.compare("multi-state bargraph") == 0)
return MULTISTATE_BARGRAPH;
else if (bt.compare("joistick") == 0)
return JOISTICK;
else if (bt.compare("text input") == 0)
return TEXT_INPUT;
else if (bt.compare("computer control") == 0)
return COMPUTER_CONTROL;
else if (bt.compare("take note") == 0)
return TAKE_NOTE;
else if (bt.compare("sub-page view") == 0)
return SUBPAGE_VIEW;
return NONE;
}
FEEDBACK TButton::getButtonFeedback(const string& fb)
{
DECL_TRACER("TButton::getButtonFeedback(const string& fb)");
if (fb.compare("channel") == 0)
return FB_CHANNEL;
else if (fb.compare("inverted channel") == 0)
return FB_INV_CHANNEL;
else if (fb.compare("always on") == 0)
return FB_ALWAYS_ON;
else if (fb.compare("momentary") == 0)
return FB_MOMENTARY;
else if (fb.compare("blink") == 0)
return FB_BLINK;
return FB_NONE;
}
bool TButton::createButtons()
{
DECL_TRACER("TButton::createButtons()");
// Get the images, if there any
vector<SR_T>::iterator srIter;
int i = 0;
for (srIter = sr.begin(); srIter != sr.end(); srIter++)
{
int number = srIter->number;
IMAGE_t img;
if (srIter->sb > 0)
continue;
if (!srIter->mi.empty()) // Do we have a chameleon image?
{
sk_sp<SkData> image;
if (!(image = readImage(srIter->mi)))
return false;
DecodeDataToBitmap(image, &img.imageMi);
if (img.imageMi.empty())
{
MSG_WARNING("Could not create a picture for element " << number << " on button " << bi << " (" << na << ")");
return false;
}
srIter->mi_width = img.imageMi.dimensions().width();
srIter->mi_height = img.imageMi.dimensions().height();
}
if (!srIter->bm.empty()) // Do we have a bitmap?
{
sk_sp<SkData> image;
if (!(image = readImage(srIter->bm)))
return false;
DecodeDataToBitmap(image, &img.imageBm);
if (img.imageBm.empty())
{
MSG_WARNING("Could not create a picture for element " << number << " on button " << bi << " (" << na << ")");
return false;
}
srIter->bm_width = img.imageBm.dimensions().width();
srIter->bm_height = img.imageBm.dimensions().height();
}
mImages.insert(pair<int, IMAGE_t>(number, img));
i++;
}
return true;
}
void TButton::refresh()
{
DECL_TRACER("TButton::refresh()");
makeElement();
}
bool TButton::makeElement(int instance)
{
DECL_TRACER("TButton::makeElement()");
int inst = mActInstance;
if (instance >= 0)
inst = instance;
if (type == MULTISTATE_GENERAL && ar == 1)
return drawButtonMultistateAni();
else if (type == BARGRAPH)
return drawBargraph(inst, mLastLevel);
else
return drawButton(inst);
return false;
}
bool TButton::setActive(int instance)
{
DECL_TRACER("TButton::setActive(int instance)");
if ((size_t)instance >= sr.size())
{
MSG_ERROR("Instance " << instance << " is higher than the maximum instance " << sr.size() << "!");
return false;
}
if (instance == mActInstance)
{
MSG_TRACE("Not necessary to set instance " << instance << " again.");
return true;
}
mActInstance = instance;
makeElement(instance);
return true;
}
bool TButton::setIcon(int id, int instance)
{
DECL_TRACER("TButton::setIcon(int id, int instance)");
if ((size_t)instance >= sr.size())
{
MSG_ERROR("Instance " << instance << " does not exist!");
return false;
}
sr[instance].ii = id;
return makeElement(instance);
}
bool TButton::setIcon(const string& icon, int instance)
{
DECL_TRACER("TButton::setIcon(const string& icon, int instance)");
if ((size_t)instance >= sr.size())
{
MSG_ERROR("Instance " << instance << " does not exist!");
return false;
}
if (!gIcons)
{
gIcons = new TIcons();
if (TError::isError())
{
MSG_ERROR("Error initializing icons!");
return false;
}
}
int id = gIcons->getNumber(icon);
if (id == -1)
{
MSG_WARNING("Icon " << icon << " not found!");
return false;
}
sr[instance].ii = id;
return makeElement(instance);
}
bool TButton::revokeIcon(int instance)
{
DECL_TRACER("TButton::revokeIcon(int instance)");
if ((size_t)instance >= sr.size())
{
MSG_ERROR("Instance " << instance << " does not exist!");
return false;
}
sr[instance].ii = 0;
return makeElement(instance);
}
bool TButton::setText(const string& txt, int instance)
{
DECL_TRACER("TButton::setText(const string& txt, int instance)");
if ((size_t)instance >= sr.size())
{
MSG_ERROR("Instance " << instance << " does not exist!");
return false;
}
sr[instance].te = txt;
return makeElement(instance);
}
bool TButton::setBitmap(const string& file, int instance)
{
DECL_TRACER("TButton::setBitmap(const string& file, int instance)");
if (file.empty() || instance >= (int)sr.size())
{
MSG_ERROR("Invalid parameters!");
return false;
}
sr[instance].bm = file;
map<int, IMAGE_t>::iterator iter = mImages.find(sr[instance].number);
if (iter != mImages.end())
mImages.erase(iter);
if (!createButtons())
return false;
return makeElement(instance);
}
bool TButton::setOpacity(int op, int instance)
{
DECL_TRACER("TButton::setOpacity(int op, int instance)");
if ((size_t)instance >= sr.size())
{
MSG_ERROR("Instance " << instance << " does not exist!");
return false;
}
if (op < 0 || op < 255)
{
MSG_ERROR("Invalid opacity " << op << "!");
return false;
}
sr[instance].oo = op;
return makeElement(instance);
}
bool TButton::setFont(int id, int instance)
{
DECL_TRACER("TButton::setFont(int id)");
if (instance < 0 || (size_t)instance >= sr.size())
{
MSG_ERROR("Instance " << instance << " does not exist!");
return false;
}
sr[instance].fi = id;
return makeElement(instance);
}
void TButton::setLeft(int left)
{
DECL_TRACER("TButton::setLeft(int left)");
if (left < 0)
return;
lt = left;
makeElement(mActInstance);
}
void TButton::setTop(int top)
{
DECL_TRACER("TButton::setTop(int top)");
if (top < 0)
return;
tp = top;
makeElement(mActInstance);
}
void TButton::setLeftTop(int left, int top)
{
DECL_TRACER("TButton::setLeftTop(int left, int top)");
if (top < 0 || left < 0)
return;
lt = left;
tp = top;
makeElement(mActInstance);
}
void TButton::setResourceName(const string& name, int instance)
{
DECL_TRACER("TButton::setResourceName(const string& name, int instance)");
if (instance < 0 || instance >= (int)sr.size())
{
MSG_ERROR("Invalid instance " << instance);
return;
}
if (!sr[instance].dynamic)
{
MSG_ERROR("Button " << bi << ": " << na << " is not a resource button!");
return;
}
sr[instance].bm = name;
}
bool TButton::setWorWrap(bool state, int instance)
{
DECL_TRACER("TButton::setWorWrap(bool state, int instance)");
if (instance < 0 || instance >= (int)sr.size())
{
MSG_ERROR("Invalid instance " << instance);
return false;
}
sr[instance].ww = state ? 1 : 0;
return makeElement(instance);
}
void TButton::_TimerCallback(ulong counter)
{
mLastBlink.second++;
int months[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
if ((mLastBlink.year % 4) == 0)
months[1] = 29;
if (mLastBlink.second > 59)
{
mLastBlink.minute++;
mLastBlink.second = 0;
if (mLastBlink.minute > 59)
{
mLastBlink.hour++;
mLastBlink.minute = 0;
if (mLastBlink.hour >= 24)
{
mLastBlink.hour = 0;
mLastBlink.weekday++;
mLastBlink.day++;
if (mLastBlink.weekday > 7)
mLastBlink.weekday = 0;
if (mLastBlink.day > months[mLastBlink.month-1])
{
mLastBlink.day = 1;
mLastBlink.month++;
if (mLastBlink.month > 12)
{
mLastBlink.year++;
mLastBlink.month = 1;
}
}
}
}
}
funcTimer(mLastBlink);
}
void TButton::_imageRefresh(const string& url)
{
DECL_TRACER("TButton::_imageRefresh(const string& url)");
ulong parent = mHandle & 0xffff0000;
getDrawOrder(sr[mActInstance]._do, (DRAW_ORDER *)&mDOrder);
if (TError::isError())
{
TError::clear();
return;
}
SkBitmap imgButton;
imgButton.allocN32Pixels(wt, ht);
for (int i = 0; i < ORD_ELEM_COUNT; i++)
{
if (mDOrder[i] == ORD_ELEM_FILL)
{
if (!buttonFill(&imgButton, mActInstance))
return;
}
else if (mDOrder[i] == ORD_ELEM_BITMAP)
{
RESOURCE_T resource = gPrjResources->findResource(sr[mActInstance].bm);
if (resource.protocol.empty())
{
MSG_ERROR("Resource " << sr[mActInstance].bm << " not found!");
return;
}
try
{
char *content = nullptr;
size_t length = 0, contentlen = 0;
if ((content = gPrjResources->tcall(&length, url, resource.user, resource.password)) == nullptr)
return;
contentlen = gPrjResources->getContentSize();
if (content == nullptr)
{
MSG_ERROR("Server returned no or invalid content!");
return;
}
sk_sp<SkData> data = SkData::MakeWithCopy(content, contentlen);
if (!data)
{
MSG_ERROR("Could not make an image!");
return;
}
SkBitmap image;
if (!DecodeDataToBitmap(data, &image))
{
MSG_ERROR("Error creating an image!");
return;
}
loadImage(&imgButton, image, mActInstance);
}
catch (std::exception& e)
{
MSG_ERROR(e.what());
return;
}
}
else if (mDOrder[i] == ORD_ELEM_ICON)
{
if (!buttonIcon(&imgButton, mActInstance))
return;
}
else if (mDOrder[i] == ORD_ELEM_TEXT)
{
if (!buttonText(&imgButton, mActInstance))
return;
}
else if (mDOrder[i] == ORD_ELEM_BORDER)
{
if (!buttonBorder(&imgButton, mActInstance))
return;
}
}
if (mGlobalOO >= 0 || sr[mActInstance].oo >= 0) // Take overall opacity into consideration
{
SkBitmap ooButton;
int w = imgButton.width();
int h = imgButton.height();
ooButton.allocN32Pixels(w, h, true);
SkCanvas canvas(ooButton);
SkIRect irect = SkIRect::MakeXYWH(0, 0, w, h);
SkRegion region;
region.setRect(irect);
SkScalar oo;
if (mGlobalOO >= 0 && sr[mActInstance].oo >= 0)
{
oo = std::min((SkScalar)mGlobalOO, (SkScalar)sr[mActInstance].oo);
MSG_DEBUG("Set global overal opacity to " << oo);
}
else if (sr[mActInstance].oo >= 0)
{
oo = (SkScalar)sr[mActInstance].oo;
MSG_DEBUG("Set overal opacity to " << oo);
}
else
{
oo = (SkScalar)mGlobalOO;
MSG_DEBUG("Set global overal opacity to " << oo);
}
SkScalar alpha = 1.0 / 255.0 * oo;
MSG_DEBUG("Calculated alpha value: " << alpha);
SkPaint paint;
paint.setAlphaf(alpha);
paint.setImageFilter(SkImageFilters::AlphaThreshold(region, 0.0, alpha, nullptr));
canvas.drawBitmap(imgButton, 0, 0, &paint);
imgButton.erase(SK_ColorTRANSPARENT, {0, 0, w, h});
imgButton = ooButton;
}
mLastImage = imgButton;
size_t rowBytes = imgButton.info().minRowBytes();
if (!prg_stopped && visible && _displayButton)
_displayButton(mHandle, parent, (unsigned char *)imgButton.getPixels(), wt, ht, rowBytes, lt, tp);
}
void TButton::registerSystemButton()
{
DECL_TRACER("TButton::registerSystemButton()");
if (mSystemReg)
return;
// If this is a special system button, register it to receive the state
if (ap == 0 && ad == 8) // Connection status?
{
MSG_TRACE("Try to register button " << na << " as connection status ...");
if (gAmxNet)
{
gAmxNet->registerNetworkState(bind(&TButton::funcNetwork, this, std::placeholders::_1), mHandle);
mSystemReg = true;
MSG_TRACE("Button registered");
}
else
MSG_WARNING("Network class not initialized!");
}
else if (ap == 0 && ((ad >= 141 && ad <= 143) || (ad >= 151 && ad <= 158))) // time or date
{
MSG_TRACE("Try to register button " << na << " as time/date ...");
if (gAmxNet)
{
gAmxNet->registerTimer(bind(&TButton::funcTimer, this, std::placeholders::_1), mHandle);
mSystemReg = true;
MSG_TRACE("Button registered");
}
else
MSG_WARNING("Network class not initialized!");
if (ad >= 141 && ad <= 143 && !mTimer)
{
mTimer = new TTimer;
mTimer->setInterval(std::chrono::milliseconds(1000)); // 1 second
mTimer->registerCallback(bind(&TButton::_TimerCallback, this, std::placeholders::_1));
mTimer->run();
}
}
}
void TButton::setHandle(ulong handle)
{
DECL_TRACER("Button::TButton::setHandle(ulong handle)");
mHandle = handle;
}
void TButton::addPushFunction(string& func, string& page)
{
DECL_TRACER("TButton::addPushFunction(string& func, string& page)");
vector<string> allFunc = { "Stan", "Prev", "Show", "Hide", "Togg", "ClearG", "ClearP", "ClearA" };
vector<string>::iterator iter;
for (iter = allFunc.begin(); iter != allFunc.end(); ++iter)
{
if (iter->compare(func) == 0)
{
bool found = false;
vector<PUSH_FUNC_T>::iterator iterPf;
for (iterPf = pushFunc.begin(); iterPf != pushFunc.end(); iterPf++)
{
if (iterPf->pfType.compare(func) == 0)
{
iterPf->pfName = page;
found = true;
break;
}
}
if (!found)
{
PUSH_FUNC_T pf;
pf.pfType = func;
pf.pfName = page;
pushFunc.push_back(pf);
}
}
break;
}
}
void TButton::clearPushFunction(const string& action)
{
DECL_TRACER("TButton::clearPushFunction(const string& action)");
if (pushFunc.empty())
return;
vector<PUSH_FUNC_T>::iterator iter;
for (iter = pushFunc.begin(); iter != pushFunc.end(); iter++)
{
if (iter->pfName.compare(action) == 0)
{
pushFunc.erase(iter);
return;
}
}
}
void TButton::getDrawOrder(const std::string& sdo, DRAW_ORDER *order)
{
DECL_TRACER("TButton::getDrawOrder(const std::string& sdo, DRAW_ORDER *order)");
if (!order)
return;
if (sdo.empty() || sdo.length() != 10)
{
*order = ORD_ELEM_FILL;
*(order+1) = ORD_ELEM_BITMAP;
*(order+2) = ORD_ELEM_ICON;
*(order+3) = ORD_ELEM_TEXT;
*(order+4) = ORD_ELEM_BORDER;
return;
}
int elems = sdo.length() / 2;
for (int i = 0; i < elems; i++)
{
int e = atoi(sdo.substr(i * 2, 2).c_str());
if (e < 1 || e > 5)
{
MSG_ERROR("Invalid draw order \"" << sdo << "\"!");
TError::setError();
return;
}
*(order+i) = (DRAW_ORDER)e;
}
}
bool TButton::buttonFill(SkBitmap* bm, int instance)
{
DECL_TRACER("TButton::buttonFill(SkBitmap* bm, int instance)");
if (!bm)
{
MSG_ERROR("Invalid bitmap!");
return false;
}
SkColor color = TColor::getSkiaColor(sr[instance].cf);
MSG_DEBUG("Filling image of size " << bm->width() << "x" << bm->height() << " with color " << TColor::colorToString(color));
bm->eraseColor(color);
return true;
}
bool TButton::buttonBitmap(SkBitmap* bm, int instance)
{
DECL_TRACER("TButton::buttonBitmap(SkBitmap* bm, int instane)");
if (sr[instance].dynamic)
return buttonDynamic(bm, instance);
if (!sr[instance].mi.empty() && !sr[instance].bm.empty()) // Chameleon image?
{
MSG_TRACE("Chameleon image ...");
map<int, IMAGE_t>::iterator iterImages = mImages.find(sr[instance].number);
SkBitmap imgRed(iterImages->second.imageMi);
SkBitmap imgMask(iterImages->second.imageBm);
SkBitmap img = drawImageButton(imgRed, imgMask, sr[instance].mi_width, sr[instance].mi_height, TColor::getSkiaColor(sr[instance].cf), TColor::getSkiaColor(sr[instance].cb));
if (img.empty())
{
MSG_ERROR("Error creating the cameleon image \"" << sr[instance].mi << "\" / \"" << sr[instance].bm << "\"!");
TError::setError();
return false;
}
SkCanvas ctx(img, SkSurfaceProps(1, kUnknown_SkPixelGeometry));
SkImageInfo info = img.info();
SkPaint paint;
paint.setBlendMode(SkBlendMode::kSrcATop);
ctx.drawBitmap(imgMask, 0, 0, &paint);
POSITION_t position = calcImagePosition(sr[instance].mi_width, sr[instance].mi_height, SC_BITMAP, instance);
if (!position.valid)
{
MSG_ERROR("Error calculating the position of the image for button number " << bi << ": " << na);
TError::setError();
return false;
}
SkCanvas can(*bm, SkSurfaceProps(1, kUnknown_SkPixelGeometry));
paint.setBlendMode(SkBlendMode::kSrc);
if (sr[instance].sb == 0)
can.drawBitmap(img, position.left, position.top, &paint);
else // Scale to fit
{
SkRect rect = SkRect::MakeXYWH(position.left, position.top, position.width, position.height);
sk_sp<SkImage> im = SkImage::MakeFromBitmap(img);
can.drawImageRect(im, rect, &paint);
}
}
else if (!sr[instance].bm.empty())
{
MSG_TRACE("Drawing normal image ...");
map<int, IMAGE_t>::iterator iterImages = mImages.find(sr[instance].number);
SkBitmap image = iterImages->second.imageBm;
if (image.empty())
{
MSG_ERROR("Error creating the image \"" << sr[instance].bm << "\"!");
TError::setError();
return false;
}
SkImageInfo info = image.info();
POSITION_t position = calcImagePosition(sr[instance].bm_width, sr[instance].bm_height, SC_BITMAP, instance);
if (!position.valid)
{
MSG_ERROR("Error calculating the position of the image for button number " << bi);
TError::setError();
return false;
}
MSG_DEBUG("Putting bitmap on top of image ...");
SkPaint paint;
paint.setBlendMode(SkBlendMode::kSrc);
SkCanvas can(*bm, SkSurfaceProps(1, kUnknown_SkPixelGeometry));
if (sr[instance].sb == 0)
can.drawBitmap(image, position.left, position.top, &paint);
else // Scale to fit
{
SkRect rect = SkRect::MakeXYWH(position.left, position.top, position.width, position.height);
sk_sp<SkImage> im = SkImage::MakeFromBitmap(image);
can.drawImageRect(im, rect, &paint);
}
}
else
{
MSG_DEBUG("No bitmap defined.");
}
return true;
}
bool TButton::buttonDynamic(SkBitmap* bm, int instance)
{
DECL_TRACER("TButton::buttonDynamic(SkBitmap* bm, int instance)");
if (!gPrjResources)
{
MSG_ERROR("Internal error: Global resource class not initialized!");
return false;
}
if (!sr[instance].dynamic)
{
MSG_WARNING("Button " << bi << ": \"" << na << "\" is not for remote image!");
return false;
}
RESOURCE_T resource = gPrjResources->findResource(1, sr[instance].bm);
if (resource.protocol.empty())
{
MSG_ERROR("Resource " << sr[instance].bm << " not found!");
return false;
}
string path = resource.path;
if (!resource.file.empty())
path += "/" + resource.file;
string url = gPrjResources->makeURL(toLower(resource.protocol), resource.host, 0, path);
try
{
MSG_TRACE("Starting thread for loading a dynamic image ...");
mThrRes = std::thread([=] { this->funcResource(&resource, url, bm, instance); });
MSG_TRACE("Thread started. Detaching ...");
mThrRes.detach();
MSG_TRACE("Thread is running and detached.");
}
catch (std::exception& e)
{
MSG_ERROR("Error starting the AmxNet thread: " << e.what());
}
return true;
}
void TButton::funcResource(const RESOURCE_T* resource, const std::string& url, SkBitmap* bm, int instance)
{
DECL_TRACER("TButton::funcResource(RESOURCE_T* resource, std::string& url, SkBitmap* bm, int instance)");
if (resource->refresh > 0 && !resource->dynamo) // Periodically refreshing image?
{
MSG_DEBUG("Retrieving periodicaly refreshed image");
ulong parent = (mHandle >> 16) & 0x0000ffff;
if (!mHandle || !parent || bi <= 1)
{
MSG_ERROR("Invalid button. Can't make a dynamo image!");
return;
}
THR_REFRESH_t *thref = _findResource(mHandle, parent, bi);
TImageRefresh *mImageRefresh = nullptr;
if (!thref)
{
MSG_DEBUG("Creating a new refresh thread");
mImageRefresh = new TImageRefresh();
mImageRefresh->registerCallback(bind(&TButton::_imageRefresh, this, std::placeholders::_1));
mImageRefresh->setInterval(std::chrono::seconds(resource->refresh));
mImageRefresh->setUsername(resource->user);
mImageRefresh->setPassword(resource->password);
if (resource->preserve)
mImageRefresh->setRunOnce();
_addResource(mImageRefresh, mHandle, parent, bi);
}
else
{
mImageRefresh = thref->mImageRefresh;
if (!mImageRefresh)
{
MSG_ERROR("Error creating a new refresh class!");
return;
}
mImageRefresh->setInterval(std::chrono::seconds(resource->refresh));
mImageRefresh->setUsername(resource->user);
mImageRefresh->setPassword(resource->password);
if (resource->preserve)
mImageRefresh->setRunOnce();
}
if (!mImageRefresh->isRunning())
{
MSG_DEBUG("Starting a refresh thread.");
mImageRefresh->run(url);
}
}
else if (resource->refresh == 0 && !resource->dynamo)
{
MSG_DEBUG("Retrieving single image");
try
{
char *content = nullptr;
size_t length = 0, contentlen = 0;
if ((content = gPrjResources->tcall(&length, url, resource->user, resource->password)) == nullptr)
return;
contentlen = gPrjResources->getContentSize();
MSG_DEBUG("Loaded " << contentlen << " bytes:");
sk_sp<SkData> data = SkData::MakeWithCopy(content, contentlen);
if (!data)
{
MSG_ERROR("Error making image data!");
return;
}
SkBitmap image;
if (!DecodeDataToBitmap(data, &image))
{
MSG_ERROR("Error creating an image!");
return;
}
loadImage(bm, image, instance);
return;
}
catch (std::exception& e)
{
MSG_ERROR(e.what());
return;
}
}
else
{
MSG_DEBUG("Retrieving a video");
if (_playVideo && !prg_stopped)
{
ulong parent = (mHandle >> 16) & 0x0000ffff;
_playVideo(mHandle, parent, lt, tp, wt, ht, url, resource->user, resource->password);
}
}
}
bool TButton::loadImage(SkBitmap* bm, SkBitmap& image, int instance)
{
DECL_TRACER("TButton::loadImage(SkBitmap* bm, SkBitmap& image, int instance)");
SkImageInfo info = image.info();
POSITION_t position = calcImagePosition(info.width(), info.height(), SC_BITMAP, instance);
if (!position.valid)
{
MSG_ERROR("Error calculating the position of the image for button number " << bi);
return false;
}
MSG_DEBUG("Putting bitmap on top of image ...");
SkPaint paint;
paint.setBlendMode(SkBlendMode::kSrc);
SkCanvas can(*bm, SkSurfaceProps(1, kUnknown_SkPixelGeometry));
if (sr[instance].sb == 0)
can.drawBitmap(image, position.left, position.top, &paint);
else // Scale to fit
{
SkRect rect = SkRect::MakeXYWH(position.left, position.top, position.width, position.height);
sk_sp<SkImage> im = SkImage::MakeFromBitmap(image);
can.drawImageRect(im, rect, &paint);
}
return true;
}
bool TButton::barLevel(SkBitmap* bm, int instance, int level)
{
DECL_TRACER("TButton::barLevel(SkBitmap* bm, int instance, int level)");
if (!sr[0].mi.empty() && !sr[1].bm.empty()) // Chameleon image?
{
MSG_DEBUG("Chameleon image ...");
map<int, IMAGE_t>::iterator iterImages1 = mImages.find(sr[0].number);
map<int, IMAGE_t>::iterator iterImages2 = mImages.find(sr[1].number);
SkBitmap imgRed(iterImages1->second.imageMi);
SkBitmap imgMask(iterImages2->second.imageBm);
SkBitmap img;
SkPixmap pixmapRed = imgRed.pixmap();
SkPixmap pixmapMask;
if (!imgMask.empty())
pixmapMask = imgMask.pixmap();
int width = sr[0].mi_width;
int height = sr[0].mi_height;
int startX = 0;
int startY = 0;
if (dr.compare("horizontal") == 0)
width = (int)((double)width / ((double)rh - (double)rl) * (double)level);
else
height = (int)((double)height / ((double)rh - (double)rl) * (double)level);
if ((!ri && dr.compare("horizontal") != 0) || // range inverted?
(ri && dr.compare("horizontal") == 0))
{
if (dr.compare("horizontal") == 0)
startX = sr[0].mi_width - width;
else
startY = sr[0].mi_height - height;
width = sr[0].mi_width;
height = sr[0].mi_height;
}
img.allocPixels(SkImageInfo::MakeN32Premul(sr[0].mi_width, sr[0].mi_height));
SkCanvas canvas(img);
SkColor col1 = TColor::getSkiaColor(sr[1].cf);
SkColor col2 = TColor::getSkiaColor(sr[1].cb);
for (int ix = 0; ix < sr[0].mi_width; ix++)
{
for (int iy = 0; iy < sr[0].mi_height; iy++)
{
SkPaint paint;
SkColor pixel;
if (ix >= startX && ix < width && iy >= startY && iy < height)
{
SkColor pixelRed = pixmapRed.getColor(ix, iy);
SkColor pixelMask;
if (!imgMask.empty())
pixelMask = pixmapMask.getColor(ix, iy);
else
pixelMask = SK_ColorWHITE;
pixel = baseColor(pixelRed, pixelMask, col1, col2);
}
else
pixel = SK_ColorTRANSPARENT;
paint.setColor(pixel);
canvas.drawPoint(ix, iy, paint);
}
}
if (img.empty())
{
MSG_ERROR("Error creating the cameleon image \"" << sr[0].mi << "\" / \"" << sr[0].bm << "\"!");
TError::setError();
return false;
}
MSG_DEBUG("Putting image together ...");
SkCanvas ctx(img, SkSurfaceProps(1, kUnknown_SkPixelGeometry));
SkImageInfo info = img.info();
SkPaint paint;
paint.setBlendMode(SkBlendMode::kSrcATop);
ctx.drawBitmap(imgMask, 0, 0, &paint);
POSITION_t position = calcImagePosition(sr[0].mi_width, sr[0].mi_height, SC_BITMAP, 0);
if (!position.valid)
{
MSG_ERROR("Error calculating the position of the image for button number " << bi << ": " << na);
TError::setError();
return false;
}
SkCanvas can(*bm, SkSurfaceProps(1, kUnknown_SkPixelGeometry));
paint.setBlendMode(SkBlendMode::kSrc);
can.drawBitmap(img, position.left, position.top, &paint);
}
else if (!sr[1].bm.empty())
{
MSG_DEBUG("Drawing normal image ...");
map<int, IMAGE_t>::iterator iterImages = mImages.find(sr[1].number);
SkBitmap image = iterImages->second.imageBm;
if (image.empty())
{
MSG_ERROR("Error creating the image \"" << sr[instance].bm << "\"!");
TError::setError();
return false;
}
int width = sr[1].mi_width;
int height = sr[1].mi_height;
int startX = 0;
int startY = 0;
if (dr.compare("horizontal") == 0)
width = (int)((double)width / ((double)rh - (double)rl) * (double)level);
else
height = (int)((double)height / ((double)rh - (double)rl) * (double)level);
if ((!ri && dr.compare("horizontal") != 0) || // range inverted?
(ri && dr.compare("horizontal") == 0))
{
if (dr.compare("horizontal") == 0)
startX = sr[0].mi_width - width;
else
startY = sr[0].mi_height - height;
width = sr[0].mi_width;
height = sr[0].mi_height;
}
MSG_DEBUG("Putting bitmap on top of image ...");
SkPaint paint;
paint.setBlendMode(SkBlendMode::kSrc);
paint.setColor(TColor::getSkiaColor(sr[1].cf));
SkCanvas can(*bm, SkSurfaceProps(1, kUnknown_SkPixelGeometry));
SkRect dst;
dst.setXYWH(startX, startY, width, height);
can.drawBitmapRect(image, dst, &paint);
}
else
{
MSG_DEBUG("No bitmap defined.");
int width = sr[1].mi_width;
int height = sr[1].mi_height;
int startX = 0;
int startY = 0;
if (dr.compare("horizontal") == 0)
width = (int)((double)width / ((double)rh - (double)rl) * (double)level);
else
height = (int)((double)height / ((double)rh - (double)rl) * (double)level);
if ((!ri && dr.compare("horizontal") != 0) || // range inverted?
(ri && dr.compare("horizontal") == 0))
{
if (dr.compare("horizontal") == 0)
startX = sr[0].mi_width - width;
else
startY = sr[0].mi_height - height;
width = sr[0].mi_width;
height = sr[0].mi_height;
}
MSG_DEBUG("Putting bitmap on top of image ...");
// SkBitmap image;
// image.allocN32Pixels(wt, ht);
// image.eraseColor(TColor::getSkiaColor(sr[1].cf));
SkPaint paint;
paint.setBlendMode(SkBlendMode::kSrc);
SkCanvas can(*bm, SkSurfaceProps(1, kUnknown_SkPixelGeometry));
paint.setStyle(SkPaint::kFill_Style);
paint.setAntiAlias(true);
paint.setStrokeWidth(4);
paint.setColor(TColor::getSkiaColor(sr[1].cb));
SkRect dst;
dst.setXYWH(startX, startY, width, height);
can.drawRect(dst, paint);
}
return true;
}
bool TButton::buttonIcon(SkBitmap* bm, int instance)
{
DECL_TRACER("TButton::buttonIcon(SkBitmap* bm, int instance)");
if (sr[instance].ii <= 0)
{
MSG_TRACE("No icon defined!");
return true;
}
MSG_DEBUG("Drawing an icon ...");
if (!gIcons)
{
gIcons = new TIcons();
if (TError::isError())
{
MSG_ERROR("Error initializing icons!");
return false;
}
}
string file = gIcons->getFile(sr[instance].ii);
MSG_DEBUG("Loading icon file " << file);
sk_sp<SkData> image;
SkBitmap icon;
if (!(image = readImage(file)))
return false;
DecodeDataToBitmap(image, &icon);
if (icon.empty())
{
MSG_WARNING("Could not create an icon for element " << sr[instance].ii << " on button " << bi << " (" << na << ")");
return false;
}
SkImageInfo info = icon.info();
POSITION_t position = calcImagePosition(icon.width(), icon.height(), SC_ICON, instance);
if (!position.valid)
{
MSG_ERROR("Error calculating the position of the image for button number " << bi);
TError::setError();
return false;
}
MSG_DEBUG("Putting Icon on top of bitmap ...");
SkPaint paint;
paint.setBlendMode(SkBlendMode::kSrcOver);
SkCanvas can(*bm, SkSurfaceProps(1, kUnknown_SkPixelGeometry));
if (position.overflow)
{
SkIRect irect;
SkRect bdst;
SkBitmap dst;
int left = (position.left >= 0) ? 0 : position.left * -1;
int top = (position.top >= 0) ? 0 : position.top * -1;
int width = std::min(wt, info.width());
int height = std::min(ht, info.height());
irect.setXYWH(left, top, width, height);
bm->getBounds(&bdst);
can.drawBitmapRect(icon, irect, bdst, &paint);
}
else
can.drawBitmap(icon, position.left, position.top, &paint);
return true;
}
bool TButton::buttonText(SkBitmap* bm, int instance)
{
DECL_TRACER("TButton::buttonText(SkBitmap* bm, int instance)");
if (!sr[instance].te.empty() && _setText && mFonts) // Is there a text to display?
{
MSG_DEBUG("Searching for font number " << sr[instance].fi << " with text " << sr[instance].te);
FONT_T font = mFonts->getFont(sr[instance].fi);
if (!font.file.empty())
{
SkCanvas canvas(*bm, SkSurfaceProps(1, kUnknown_SkPixelGeometry));
sk_sp<SkTypeface> typeFace = mFonts->getTypeFace(sr[instance].fi);
if (!typeFace)
{
MSG_ERROR("Error creating type face " << font.fullName);
TError::setError();
return false;
}
SkScalar fontSizePt = ((SkScalar)font.size * 1.322);
SkFont skFont(typeFace, fontSizePt);
SkPaint paint;
paint.setAntiAlias(true);
SkColor color = TColor::getSkiaColor(sr[instance].ct);
paint.setColor(color);
paint.setStyle(SkPaint::kFill_Style);
SkFontMetrics metrics;
skFont.getMetrics(&metrics);
int lines = numberLines(sr[instance].te);
MSG_DEBUG("Found " << lines << " lines.");
if (lines > 1 || sr[instance].ww)
{
vector<string> textLines;
if (!sr[instance].ww)
textLines = splitLine(sr[instance].te);
else
{
textLines = splitLine(sr[instance].te, wt, ht, skFont, paint);
lines = textLines.size();
}
vector<string>::iterator iter;
SkScalar Y = 1.0;
for (iter = textLines.begin(); iter != textLines.end(); iter++)
{
sk_sp<SkTextBlob> blob = SkTextBlob::MakeFromString(iter->c_str(), skFont);
SkRect rect;
skFont.measureText(iter->c_str(), iter->length(), SkTextEncoding::kUTF8, &rect, &paint);
MSG_DEBUG("Triing to print line: " << *iter);
POSITION_t position = calcImagePosition(rect.width(), rect.height()*lines, SC_TEXT, instance);
if (!position.valid)
{
MSG_ERROR("Error calculating the text position!");
TError::setError();
return false;
}
SkScalar startX = (SkScalar)position.left;
SkScalar startY = (SkScalar)position.top;
if (Y > 1.0)
startY += metrics.fCapHeight; // metrics.fDescent + metrics.fLeading;
else
startY += metrics.fCapHeight + metrics.fLeading; //(metrics.fAscent * -1.0);
MSG_DEBUG("fAvgCharWidth: " << metrics.fAvgCharWidth);
MSG_DEBUG("fCapHeight: " << metrics.fCapHeight);
MSG_DEBUG("fAscent: " << metrics.fAscent);
MSG_DEBUG("fDescent: " << metrics.fDescent);
MSG_DEBUG("fLeading: " << metrics.fLeading);
MSG_DEBUG("fXHeight: " << metrics.fXHeight);
MSG_DEBUG("x=" << startX << ", y=" << startY*Y);
canvas.drawTextBlob(blob, startX, startY, paint);
Y += 1.0;
}
}
else // single line
{
sk_sp<SkTextBlob> blob = SkTextBlob::MakeFromString(sr[instance].te.c_str(), skFont);
SkRect rect;
skFont.measureText(sr[instance].te.c_str(), sr[instance].te.length(), SkTextEncoding::kUTF8, &rect, &paint);
POSITION_t position = calcImagePosition(rect.width(), (rect.height() * (float)lines), SC_TEXT, instance);
if (!position.valid)
{
MSG_ERROR("Error calculating the text position!");
TError::setError();
return false;
}
MSG_DEBUG("Printing line " << sr[instance].te);
SkScalar startX = (SkScalar)position.left;
SkScalar startY = (SkScalar)position.top + metrics.fCapHeight; // + metrics.fLeading; // (metrics.fAscent * -1.0);
MSG_DEBUG("fAvgCharWidth: " << metrics.fAvgCharWidth);
MSG_DEBUG("fCapHeight: " << metrics.fCapHeight);
MSG_DEBUG("fAscent: " << metrics.fAscent);
MSG_DEBUG("fDescent: " << metrics.fDescent);
MSG_DEBUG("fLeading: " << metrics.fLeading);
MSG_DEBUG("fXHeight: " << metrics.fXHeight);
MSG_DEBUG("x=" << startX << ", y=" << startY);
canvas.drawTextBlob(blob, startX, startY, paint);
}
}
}
return true;
}
bool TButton::buttonBorder(SkBitmap* bm, int instance)
{
DECL_TRACER("TButton::buttonBorder(SkBitmap* bm, int instance)");
if (sr[instance].bs.empty())
{
MSG_DEBUG("No border defined.");
return true;
}
int i = 0;
while (sysBorders[i].id)
{
if (sr[instance].bs.compare(sysBorders[i].name) == 0)
{
MSG_DEBUG("Border " << sysBorders[i].name << " found.");
SkCanvas canvas(*bm, SkSurfaceProps(1, kUnknown_SkPixelGeometry));
SkPaint paint;
SkColor color = TColor::getSkiaColor(sr[instance].cb);
paint.setColor(color);
paint.setBlendMode(SkBlendMode::kSrc);
paint.setStyle(SkPaint::kStroke_Style);
SkRRect outher, inner;
switch (sysBorders[i].id)
{
case 1: // Single Frame
case 2: // Double Frame
case 3: // Quad Frame
paint.setStrokeWidth(sysBorders[i].width);
canvas.drawRect(calcRect(wt, ht, sysBorders[i].width), paint);
break;
// FIXME: Picture frame is missing
case 5: // Circle 15
paint.setStrokeWidth(sysBorders[i].width);
canvas.drawRoundRect(calcRect(wt, ht, sysBorders[i].width), 7.0, 7.0, paint);
outher = SkRRect::MakeRect({0, 0, (SkScalar)wt, (SkScalar)ht});
inner = SkRRect::MakeRectXY({0, 0, (SkScalar)wt, (SkScalar)ht}, 7.0, 7.0);
paint.setColor(SK_ColorTRANSPARENT);
canvas.drawDRRect(outher, inner, paint);
break;
case 6: // Circle 25
paint.setStrokeWidth(sysBorders[i].width);
canvas.drawRoundRect(calcRect(wt, ht, sysBorders[i].width), 14.0, 14.0, paint);
outher = SkRRect::MakeRect({0, 0, (SkScalar)wt, (SkScalar)ht});
inner = SkRRect::MakeRectXY({0, 0, (SkScalar)wt, (SkScalar)ht}, 14.0, 14.0);
paint.setColor(SK_ColorTRANSPARENT);
canvas.drawDRRect(outher, inner, paint);
break;
case 7: // Circle 35
paint.setStrokeWidth(sysBorders[i].width);
canvas.drawRoundRect(calcRect(wt, ht, sysBorders[i].width), 21.0, 21.0, paint);
outher = SkRRect::MakeRect({0, 0, (SkScalar)wt, (SkScalar)ht});
inner = SkRRect::MakeRectXY({0, 0, (SkScalar)wt, (SkScalar)ht}, 21.0, 21.0);
paint.setColor(SK_ColorTRANSPARENT);
canvas.drawDRRect(outher, inner, paint);
break;
case 8: // Circle 45
paint.setStrokeWidth(sysBorders[i].width);
canvas.drawRoundRect(calcRect(wt, ht, sysBorders[i].width), 28.0, 28.0, paint);
outher = SkRRect::MakeRect({0, 0, (SkScalar)wt, (SkScalar)ht});
inner = SkRRect::MakeRectXY({0, 0, (SkScalar)wt, (SkScalar)ht}, 28.0, 28.0);
paint.setColor(SK_ColorTRANSPARENT);
canvas.drawDRRect(outher, inner, paint);
break;
case 9: // Circle 55
paint.setStrokeWidth(sysBorders[i].width);
canvas.drawRoundRect(calcRect(wt, ht, sysBorders[i].width), 35.0, 35.0, paint);
outher = SkRRect::MakeRect({0, 0, (SkScalar)wt, (SkScalar)ht});
inner = SkRRect::MakeRectXY({0, 0, (SkScalar)wt, (SkScalar)ht}, 35.0, 35.0);
paint.setColor(SK_ColorTRANSPARENT);
canvas.drawDRRect(outher, inner, paint);
break;
case 10: // Circle 65
paint.setStrokeWidth(sysBorders[i].width);
canvas.drawRoundRect(calcRect(wt, ht, sysBorders[i].width), 42.0, 42.0, paint);
outher = SkRRect::MakeRect({0, 0, (SkScalar)wt, (SkScalar)ht});
inner = SkRRect::MakeRectXY({0, 0, (SkScalar)wt, (SkScalar)ht}, 42.0, 42.0);
paint.setColor(SK_ColorTRANSPARENT);
canvas.drawDRRect(outher, inner, paint);
break;
case 11: // Circle 75
paint.setStrokeWidth(sysBorders[i].width);
canvas.drawRoundRect(calcRect(wt, ht, sysBorders[i].width), 49.0, 49.0, paint);
outher = SkRRect::MakeRect({0, 0, (SkScalar)wt, (SkScalar)ht});
inner = SkRRect::MakeRectXY({0, 0, (SkScalar)wt, (SkScalar)ht}, 49.0, 49.0);
paint.setColor(SK_ColorTRANSPARENT);
canvas.drawDRRect(outher, inner, paint);
break;
case 12: // Circle 85
paint.setStrokeWidth(sysBorders[i].width);
canvas.drawRoundRect(calcRect(wt, ht, sysBorders[i].width), 56.0, 56.0, paint);
outher = SkRRect::MakeRect({0, 0, (SkScalar)wt, (SkScalar)ht});
inner = SkRRect::MakeRectXY({0, 0, (SkScalar)wt, (SkScalar)ht}, 56.0, 56.0);
paint.setColor(SK_ColorTRANSPARENT);
canvas.drawDRRect(outher, inner, paint);
break;
case 13: // Circle 95
paint.setStrokeWidth(sysBorders[i].width);
canvas.drawRoundRect(calcRect(wt, ht, sysBorders[i].width), 63.0, 63.0, paint);
outher = SkRRect::MakeRect({0, 0, (SkScalar)wt, (SkScalar)ht});
inner = SkRRect::MakeRectXY({0, 0, (SkScalar)wt, (SkScalar)ht}, 63.0, 63.0);
paint.setColor(SK_ColorTRANSPARENT);
canvas.drawDRRect(outher, inner, paint);
break;
case 14: // Circle 105
paint.setStrokeWidth(sysBorders[i].width);
canvas.drawRoundRect(calcRect(wt, ht, sysBorders[i].width), 70.0, 70.0, paint);
outher = SkRRect::MakeRect({0, 0, (SkScalar)wt, (SkScalar)ht});
inner = SkRRect::MakeRectXY({0, 0, (SkScalar)wt, (SkScalar)ht}, 70.0, 70.0);
paint.setColor(SK_ColorTRANSPARENT);
canvas.drawDRRect(outher, inner, paint);
break;
case 15: // Circle 115
paint.setStrokeWidth(sysBorders[i].width);
canvas.drawRoundRect(calcRect(wt, ht, sysBorders[i].width), 77.0, 77.0, paint);
outher = SkRRect::MakeRect({0, 0, (SkScalar)wt, (SkScalar)ht});
inner = SkRRect::MakeRectXY({0, 0, (SkScalar)wt, (SkScalar)ht}, 77.0, 77.0);
paint.setColor(SK_ColorTRANSPARENT);
canvas.drawDRRect(outher, inner, paint);
break;
case 16: // Circle 125
paint.setStrokeWidth(sysBorders[i].width);
canvas.drawRoundRect(calcRect(wt, ht, sysBorders[i].width), 84.0, 84.0, paint);
outher = SkRRect::MakeRect({0, 0, (SkScalar)wt, (SkScalar)ht});
inner = SkRRect::MakeRectXY({0, 0, (SkScalar)wt, (SkScalar)ht}, 84.0, 84.0);
paint.setColor(SK_ColorTRANSPARENT);
canvas.drawDRRect(outher, inner, paint);
break;
}
}
i++;
}
return true;
}
int TButton::numberLines(const string& str)
{
DECL_TRACER("TButton::numberLines(const string& str)");
int lines = 1;
for (size_t i = 0; i < str.length(); i++)
{
if (str.at(i) == '\n')
lines++;
}
return lines;
}
vector<string> TButton::splitLine(const string& str)
{
DECL_TRACER("TButton::splitLine(const string& str)");
vector<string> lines;
string sl;
size_t len = str.length();
for (size_t i = 0; i < len; i++)
{
if (str.at(i) == '\r') // ignore bloating byte coming from brain death windows
continue;
if (str.at(i) == '\n')
{
lines.push_back(sl);
sl.clear();
continue;
}
sl.append(str.substr(i, 1));
}
if (!sl.empty())
lines.push_back(sl);
return lines;
}
vector<string> TButton::splitLine(const string& str, int width, int height, SkFont& font, SkPaint& paint)
{
DECL_TRACER("TButton::splitLine(const string& str, SkFont& font)");
SkRect rect;
size_t len = str.length();
vector<string> lines;
SkScalar lnHeight = font.getSize();
int maxLines = (int)((SkScalar)height / lnHeight);
for (size_t i = 0; i < len; i++)
{
string part = str.substr(0, i + 1);
font.measureText(part.c_str(), part.length(), SkTextEncoding::kUTF8, &rect, &paint);
if (rect.width() > width)
{
string ln = part.substr(0, part.length() - 1);
lines.push_back(ln);
part = part.substr(part.length() - 1);
if (lines.size() >= (size_t)maxLines)
return lines;
}
if ((i + 1) == len)
lines.push_back(part);
}
return lines;
}
SkRect TButton::calcRect(int width, int height, int pen)
{
DECL_TRACER("TButton::calcRect(int width, int height, int pen)");
SkRect rect;
SkScalar left = (SkScalar)pen / 2.0;
SkScalar top = (SkScalar)pen / 2.0;
SkScalar w = (SkScalar)width - (SkScalar)pen / 2.0 - left;
SkScalar h = (SkScalar)height - (SkScalar)pen / 2.0 - top;
rect.setXYWH(left, top, w, h);
return rect;
}
void TButton::runAnimation()
{
DECL_TRACER("TButton::runAnimation()");
mAniRunning = true;
int instance = 0;
int max = (int)sr.size();
ulong tm = nu * 100 + nd * 100;
while (mAniRunning)
{
if (!drawButton(instance))
break;
instance++;
if (instance >= max)
instance = 0;
std::this_thread::sleep_for(std::chrono::milliseconds(tm));
}
mAniRunning = false;
}
bool TButton::drawButtonMultistateAni()
{
DECL_TRACER("TButton::drawButtonMultistate(int instance, bool show)");
if (mAniRunning || mThrAni.joinable())
{
MSG_INFO("Animation is already running!");
return true;
}
try
{
mThrAni = thread([=] { runAnimation(); });
mThrAni.detach();
}
catch (exception& e)
{
MSG_ERROR("Error starting the button animation thread: " << e.what());
return false;
}
return true;
}
bool TButton::drawButton(int instance, bool show)
{
mutex_button.lock();
DECL_TRACER("TButton::drawButton(int instance, bool show)");
if ((size_t)instance >= sr.size() || instance < 0)
{
MSG_ERROR("Instance " << instance << " is out of bounds!");
TError::setError();
mutex_button.unlock();
return false;
}
if (!visible || instance != mActInstance || !_displayButton)
{
bool db = (_displayButton != nullptr);
MSG_INFO("Button " << bi << ", \"" << na << "\" at instance " << instance << " is not to draw!");
MSG_DEBUG("Visible: " << (visible ? "YES" : "NO") << ", Instance/actual instance: " << instance << "/" << mActInstance << ", callback: " << (db ? "PRESENT" : "N/A"));
mutex_button.unlock();
return true;
}
ulong parent = mHandle & 0xffff0000;
getDrawOrder(sr[instance]._do, (DRAW_ORDER *)&mDOrder);
if (TError::isError())
{
mutex_button.unlock();
return false;
}
SkBitmap imgButton;
imgButton.allocN32Pixels(wt, ht);
for (int i = 0; i < ORD_ELEM_COUNT; i++)
{
if (mDOrder[i] == ORD_ELEM_FILL)
{
if (!buttonFill(&imgButton, instance))
{
mutex_button.unlock();
return false;
}
}
else if (mDOrder[i] == ORD_ELEM_BITMAP)
{
if (!sr[instance].dynamic && !buttonBitmap(&imgButton, instance))
{
mutex_button.unlock();
return false;
}
else if (sr[instance].dynamic && !buttonDynamic(&imgButton, instance))
{
mutex_button.unlock();
return false;
}
}
else if (mDOrder[i] == ORD_ELEM_ICON)
{
if (!buttonIcon(&imgButton, instance))
{
mutex_button.unlock();
return false;
}
}
else if (mDOrder[i] == ORD_ELEM_TEXT)
{
if (!buttonText(&imgButton, instance))
{
mutex_button.unlock();
return false;
}
}
else if (mDOrder[i] == ORD_ELEM_BORDER)
{
if (!buttonBorder(&imgButton, instance))
{
mutex_button.unlock();
return false;
}
}
}
if (mGlobalOO >= 0 || sr[instance].oo >= 0) // Take overall opacity into consideration
{
SkBitmap ooButton;
int w = imgButton.width();
int h = imgButton.height();
ooButton.allocN32Pixels(w, h, true);
SkCanvas canvas(ooButton);
SkIRect irect = SkIRect::MakeXYWH(0, 0, w, h);
SkRegion region;
region.setRect(irect);
SkScalar oo;
if (mGlobalOO >= 0 && sr[instance].oo >= 0)
{
oo = std::min((SkScalar)mGlobalOO, (SkScalar)sr[instance].oo);
MSG_DEBUG("Set global overal opacity to " << oo);
}
else if (sr[instance].oo >= 0)
{
oo = (SkScalar)sr[instance].oo;
MSG_DEBUG("Set overal opacity to " << oo);
}
else
{
oo = (SkScalar)mGlobalOO;
MSG_DEBUG("Set global overal opacity to " << oo);
}
SkScalar alpha = 1.0 / 255.0 * oo;
MSG_DEBUG("Calculated alpha value: " << alpha);
SkPaint paint;
paint.setAlphaf(alpha);
paint.setImageFilter(SkImageFilters::AlphaThreshold(region, 0.0, alpha, nullptr));
canvas.drawBitmap(imgButton, 0, 0, &paint);
imgButton.erase(SK_ColorTRANSPARENT, {0, 0, w, h});
imgButton = ooButton;
}
mLastImage = imgButton;
size_t rowBytes = imgButton.info().minRowBytes();
if (!prg_stopped && show && visible && instance == mActInstance && _displayButton)
_displayButton(mHandle, parent, (unsigned char *)imgButton.getPixels(), wt, ht, rowBytes, lt, tp);
mutex_button.unlock();
return true;
}
bool TButton::drawBargraph(int instance, int level, bool show)
{
mutex_bargraph.lock();
DECL_TRACER("TButton::drawBargraph(int instance, bool show)");
if ((size_t)instance >= sr.size() || instance < 0)
{
MSG_ERROR("Instance " << instance << " is out of bounds!");
TError::setError();
mutex_bargraph.unlock();
return false;
}
if (level < rl)
mLastLevel = rl;
else if (level > rh)
mLastLevel = rh;
else
mLastLevel = level;
if (!visible || instance != mActInstance || !_displayButton)
{
bool db = (_displayButton != nullptr);
MSG_INFO("Bargraph " << bi << ", \"" << na << "\" at instance " << instance << " with level " << mLastLevel << " is not to draw!");
MSG_DEBUG("Visible: " << (visible ? "YES" : "NO") << ", Instance/actual instance: " << instance << "/" << mActInstance << ", callback: " << (db ? "PRESENT" : "N/A"));
mutex_bargraph.unlock();
return true;
}
ulong parent = mHandle & 0xffff0000;
if (type == BARGRAPH)
getDrawOrder(sr[1]._do, (DRAW_ORDER *)&mDOrder);
else
getDrawOrder(sr[instance]._do, (DRAW_ORDER *)&mDOrder);
if (TError::isError())
{
mutex_bargraph.unlock();
return false;
}
SkBitmap imgButton;
imgButton.allocN32Pixels(wt, ht);
for (int i = 0; i < ORD_ELEM_COUNT; i++)
{
if (mDOrder[i] == ORD_ELEM_FILL)
{
if (!buttonFill(&imgButton, instance))
{
mutex_bargraph.unlock();
return false;
}
}
else if (mDOrder[i] == ORD_ELEM_BITMAP)
{
if (!barLevel(&imgButton, instance, mLastLevel))
{
mutex_bargraph.unlock();
return false;
}
}
else if (mDOrder[i] == ORD_ELEM_ICON)
{
if (!buttonIcon(&imgButton, instance))
{
mutex_bargraph.unlock();
return false;
}
}
else if (mDOrder[i] == ORD_ELEM_TEXT)
{
if (!buttonText(&imgButton, instance))
{
mutex_bargraph.unlock();
return false;
}
}
else if (mDOrder[i] == ORD_ELEM_BORDER)
{
if (!buttonBorder(&imgButton, instance))
{
mutex_bargraph.unlock();
return false;
}
}
}
if (mGlobalOO >= 0 || sr[instance].oo >= 0) // Take overall opacity into consideration
{
SkBitmap ooButton;
int w = imgButton.width();
int h = imgButton.height();
ooButton.allocN32Pixels(w, h, true);
SkCanvas canvas(ooButton);
SkIRect irect = SkIRect::MakeXYWH(0, 0, w, h);
SkRegion region;
region.setRect(irect);
SkScalar oo;
if (mGlobalOO >= 0 && sr[instance].oo >= 0)
{
oo = std::min((SkScalar)mGlobalOO, (SkScalar)sr[instance].oo);
MSG_DEBUG("Set global overal opacity to " << oo);
}
else if (sr[instance].oo >= 0)
{
oo = (SkScalar)sr[instance].oo;
MSG_DEBUG("Set overal opacity to " << oo);
}
else
{
oo = (SkScalar)mGlobalOO;
MSG_DEBUG("Set global overal opacity to " << oo);
}
SkScalar alpha = 1.0 / 255.0 * oo;
MSG_DEBUG("Calculated alpha value: " << alpha);
SkPaint paint;
paint.setAlphaf(alpha);
paint.setImageFilter(SkImageFilters::AlphaThreshold(region, 0.0, alpha, nullptr));
canvas.drawBitmap(imgButton, 0, 0, &paint);
imgButton.erase(SK_ColorTRANSPARENT, {0, 0, w, h});
imgButton = ooButton;
}
mLastImage = imgButton;
size_t rowBytes = imgButton.info().minRowBytes();
if (!prg_stopped && show && visible && instance == mActInstance && _displayButton)
_displayButton(mHandle, parent, (unsigned char *)imgButton.getPixels(), wt, ht, rowBytes, lt, tp);
mutex_bargraph.unlock();
return true;
}
POSITION_t TButton::calcImagePosition(int width, int height, CENTER_CODE cc, int number)
{
DECL_TRACER("TButton::calcImagePosition(int with, int height, CENTER_CODE code, int number)");
SR_T act_sr;
POSITION_t position;
int ix, iy;
if (sr.size() == 0)
return position;
if (number <= 0)
act_sr = sr.at(0);
else if ((size_t)number < sr.size())
act_sr = sr.at(number);
else
return position;
int border_size = getBorderSize(act_sr.bs);
int code, border = border_size;
string dbgCC;
int rwt = 0, rht = 0;
switch (cc)
{
case SC_ICON:
code = act_sr.ji;
ix = act_sr.ix;
iy = act_sr.iy;
border = border_size = 0;
dbgCC = "ICON";
rwt = width;
rht = height;
break;
case SC_BITMAP:
code = act_sr.jb;
ix = act_sr.bx;
iy = act_sr.by;
dbgCC = "BITMAP";
rwt = std::min(wt - border * 2, width);
rht = std::min(ht - border_size * 2, height);
break;
case SC_TEXT:
code = act_sr.jt;
ix = act_sr.tx;
iy = act_sr.ty;
dbgCC = "TEXT";
border += 4;
rwt = std::min(wt - border * 2, width);
rht = std::min(ht - border_size * 2, height);
break;
}
if (width > rwt || height > rht)
position.overflow = true;
switch (code)
{
case 0: // absolute position
position.left = ix;
position.top = iy;
position.width = rwt;
position.height = rht;
break;
case 1: // top, left
if (cc == SC_TEXT)
position.left = border;
position.width = rwt;
position.height = rht;
break;
case 2: // center, top
position.left = (wt - rwt) / 2;
position.height = rht;
position.width = rwt;
break;
case 3: // right, top
position.left = wt - rwt;
if (cc == SC_TEXT)
position.left = (((position.left - border) < 0) ? 0 : position.left - border);
position.width = rwt;
position.height = rht;
break;
case 4: // left, middle
if (cc == SC_TEXT)
position.left = border;
position.top = (ht - rht) / 2;
position.width = rwt;
position.height = rht;
break;
case 6: // right, middle
position.left = wt - rwt;
if (cc == SC_TEXT)
position.left = (((position.left - border) < 0) ? 0 : position.left - border);
position.top = (ht - rht) / 2;
position.width = rwt;
position.height = rht;
break;
case 7: // left, bottom
if (cc == SC_TEXT)
position.left = border_size;
position.top = ht - rht;
position.width = rwt;
position.height = rht;
break;
case 8: // center, bottom
position.left = (wt - rwt) / 2;
position.top = ht - rht;
position.width = rwt;
position.height = rht;
break;
case 9: // right, bottom
position.left = wt - rwt;
if (cc == SC_TEXT)
position.left = (((position.left - border) < 0) ? 0 : position.left - border);
position.top = ht - rht;
break;
default: // center, middle
position.left = (wt - rwt) / 2;
position.top = (ht - rht) / 2;
position.width = rwt;
position.height = rht;
}
MSG_DEBUG("Type: " << dbgCC << ", PosType=" << code << ", Position: x=" << position.left << ", y=" << position.top << ", w=" << position.width << ", h=" << position.height << ", Overflow: " << (position.overflow ? "YES" : "NO"));
position.valid = true;
return position;
}
int TButton::getBorderSize(const std::string& name)
{
DECL_TRACER("TButton::getBorderSize(const std::string& name)");
for (int i = 0; sysBorders[i].name != nullptr; i++)
{
if (name.compare(sysBorders[i].name) == 0)
{
MSG_DEBUG("Border size: " << sysBorders[i].width);
return sysBorders[i].width;
}
}
MSG_DEBUG("Border size: 0");
return 0;
}
void TButton::calcImageSizePercent(int imWidth, int imHeight, int btWidth, int btHeight, int btFrame, int *realX, int *realY)
{
DECL_TRACER("TButton::clacImageSizePercent(int imWidth, int imHeight, int btWidth, int btHeight, int btFrame, int *realX, int *realY)");
int spX = btWidth - (btFrame * 2);
int spY = btHeight - (btFrame * 2);
if (imWidth <= spX && imHeight <= spY)
{
*realX = imWidth;
*realY = imHeight;
return;
}
int oversizeX = 0, oversizeY = 0;
if (imWidth > spX)
oversizeX = imWidth - spX;
if (imHeight > spY)
oversizeY = imHeight - spY;
double percent = 0.0;
if (oversizeX > oversizeY)
percent = 100.0 / (double)imWidth * (double)spX;
else
percent = 100.0 / (double)imHeight * (double)spY;
*realX = (int)(percent / 100.0 * (double)imWidth);
*realY = (int)(percent / 100.0 * (double)imHeight);
}
SkBitmap TButton::drawImageButton(SkBitmap& imgRed, SkBitmap& imgMask, int width, int height, SkColor col1, SkColor col2)
{
DECL_TRACER("TButton::drawImageButton(SkImage& imgRed, SkImage& imgMask, int width, int height, SkColor col1, SkColor col2)");
SkPixmap pixmapRed = imgRed.pixmap();
SkPixmap pixmapMask;
if (!imgMask.empty())
pixmapMask = imgMask.pixmap();
SkBitmap maskBm;
maskBm.allocPixels(SkImageInfo::MakeN32Premul(width, height));
SkCanvas canvas(maskBm);
for (int ix = 0; ix < width; ix++)
{
for (int iy = 0; iy < height; iy++)
{
SkColor pixelRed = pixmapRed.getColor(ix, iy);
SkColor pixelMask;
if (!imgMask.empty())
pixelMask = pixmapMask.getColor(ix, iy);
else
pixelMask = SK_ColorWHITE;
SkColor pixel = baseColor(pixelRed, pixelMask, col1, col2);
uint32_t alpha = SkColorGetA(pixel);
SkPaint paint;
if (alpha == 0)
pixel = pixelMask;
paint.setColor(pixel);
canvas.drawPoint(ix, iy, paint);
}
}
return maskBm;
}
void TButton::show()
{
DECL_TRACER("TButton::show()");
visible = true;
makeElement();
if (isSystemButton() && !mSystemReg)
registerSystemButton();
}
void TButton::showLastButton()
{
DECL_TRACER("TButton::showLastButton()");
if (mLastImage.empty())
return;
if (!prg_stopped && visible && _displayButton)
{
ulong parent = mHandle & 0xffff0000;
size_t rowBytes = mLastImage.info().minRowBytes();
_displayButton(mHandle, parent, (unsigned char *)mLastImage.getPixels(), wt, ht, rowBytes, lt, tp);
}
}
void TButton::hide(bool total)
{
DECL_TRACER("TButton::hide()");
if (type == MULTISTATE_GENERAL && ar == 1)
mAniRunning = false;
if (!prg_stopped && total)
{
SkBitmap imgButton;
imgButton.allocN32Pixels(wt, ht);
imgButton.eraseColor(SK_ColorTRANSPARENT);
ulong parent = mHandle & 0xffff0000;
size_t rowBytes = imgButton.info().minRowBytes();
_displayButton(mHandle, parent, (unsigned char *)imgButton.getPixels(), wt, ht, rowBytes, lt, tp);
}
visible = false;
}
bool TButton::isClickable()
{
DECL_TRACER("TButton::isClickable()");
if (mEnabled && ((cp != 0 && ch != 0) || !op.empty() || !pushFunc.empty()) && hs.compare("passThru") != 0)
return true;
return false;
}
/**
* Handling of system button "connection state". It consists of 12 states
* indicating the network status. The states have the following meaning:
*
* 0 Diconnected (never was connected before since startup)
* 1 - 6 Connected (blink may be shown with dark and light green)
* 7, 8 Disconnected (timeout or loss of connection)
* 9 - 11 Connection in progress
*/
void TButton::funcNetwork(int state)
{
mutex_sysdraw.lock();
DECL_TRACER("TButton::funcNetwork(int state)");
mLastLevel = state;
mActInstance = state;
if (visible)
makeElement(state);
mutex_sysdraw.unlock();
}
/**
* Handling the timer event from the controller. This comes usualy every
* 20th part of a second (1 second / 20)
*/
void TButton::funcTimer(const amx::ANET_BLINK& blink)
{
mutex_sysdraw.lock();
DECL_TRACER("TButton::funcTimer(const amx::ANET_BLINK& blink)");
string tm;
std::stringstream sstr;
switch (ad)
{
case 141: // Standard time
sstr << std::setw(2) << std::setfill('0') << (int)blink.hour << ":"
<< std::setw(2) << std::setfill('0') << (int)blink.minute << ":"
<< std::setw(2) << std::setfill('0') << (int)blink.second;
mLastBlink = blink;
break;
case 142: // Time AM/PM
{
int hour = (blink.hour > 12) ? (blink.hour - 12) : blink.hour;
sstr << std::setw(2) << std::setfill('0') << hour << ":"
<< std::setw(2) << std::setfill('0') << (int)blink.minute << " ";
if (blink.hour <= 12)
sstr << "AM";
else
sstr << "PM";
mLastBlink = blink;
}
break;
case 143: // Time 24 hours
sstr << std::setw(2) << std::setfill('0') << (int)blink.hour << ":"
<< std::setw(2) << std::setfill('0') << (int)blink.minute;
mLastBlink = blink;
break;
case 151: // Weekday
switch (blink.weekday)
{
case 0: sstr << "Monday"; break;
case 1: sstr << "Tuesday"; break;
case 2: sstr << "Wednesday"; break;
case 3: sstr << "Thursday"; break;
case 4: sstr << "Friday"; break;
case 5: sstr << "Saturday"; break;
case 6: sstr << "Sunday"; break;
}
break;
case 152: // Date mm/dd
sstr << (int)blink.month << "/" << (int)blink.day;
break;
case 153: // Date dd/mm
sstr << (int)blink.day << "/" << (int)blink.month;
break;
case 154: // Date mm/dd/yyyy
sstr << (int)blink.month << "/" << (int)blink.day << "/" << (int)blink.year;
break;
case 155: // Date dd/mm/yyyy
sstr << blink.day << "/" << blink.month << "/" << blink.year;
break;
case 156: // Date month dd/yyyy
switch (blink.month)
{
case 1: sstr << "January"; break;
case 2: sstr << "February"; break;
case 3: sstr << "March"; break;
case 4: sstr << "April"; break;
case 5: sstr << "May"; break;
case 6: sstr << "June"; break;
case 7: sstr << "July"; break;
case 8: sstr << "August"; break;
case 9: sstr << "September"; break;
case 10: sstr << "October"; break;
case 11: sstr << "November"; break;
case 12: sstr << "December"; break;
}
sstr << " " << (int)blink.day << "/" << (int)blink.year;
break;
case 157: // Date dd month yyyy
sstr << (int)blink.day;
switch (blink.month)
{
case 1: sstr << "January"; break;
case 2: sstr << "February"; break;
case 3: sstr << "March"; break;
case 4: sstr << "April"; break;
case 5: sstr << "May"; break;
case 6: sstr << "June"; break;
case 7: sstr << "July"; break;
case 8: sstr << "August"; break;
case 9: sstr << "September"; break;
case 10: sstr << "October"; break;
case 11: sstr << "November"; break;
case 12: sstr << "December"; break;
}
sstr << " " << (int)blink.year;
break;
case 158: // Date yyyy-mm-dd
sstr << (int)blink.year << "-" << (int)blink.month << "-" << (int)blink.day;
break;
}
vector<SR_T>::iterator iter;
tm = sstr.str();
for (iter = sr.begin(); iter != sr.end(); iter++)
iter->te = tm;
if (visible)
makeElement(mActInstance);
mutex_sysdraw.unlock();
}
bool TButton::isPixelTransparent(int x, int y)
{
DECL_TRACER("TButton::isPixelTransparent(int x, int y)");
if (mLastImage.empty())
{
MSG_ERROR("Internal error: No image for button available!");
return true;
}
float alpha = mLastImage.getAlphaf(x, y);
if (alpha != 0.0)
return false;
return true;
}
/**
* This button got the click because it matches the coordinates of a mouse
* click. It checkes whether it is clickable or not. If it is clickable, it
* depends on the type of element what happens.
*/
bool TButton::doClick(int x, int y, bool pressed)
{
DECL_TRACER("TButton::doClick(bool pressed)");
amx::ANET_SEND scmd;
int instance = 0;
if (!isClickable())
return false;
if (type == GENERAL)
{
if (fb == FB_MOMENTARY)
{
if (pressed)
instance = 1;
else
instance = 0;
mActInstance = instance;
// we ignore the state of the method, because it doesn't matter
// for the message to the controller.
drawButton(instance, false);
// If there is nothing in "hs", then it depends on the pixel of the
// layer. Only if the pixel the coordinates point to are not
// transparent, the button takes the click.
if (hs.empty() && isPixelTransparent(x, y))
return false;
if (pushFunc.empty()) // Don't draw the button if it has a push function defined
showLastButton();
else
mActInstance = 0;
}
else if (fb == FB_CHANNEL || fb == FB_NONE)
{
if (pressed)
instance = 1;
else
instance = 0;
// we ignore the state of the method, because it doesn't matter
// for the message to the controller.
drawButton(instance, false);
// If there is nothing in "hs", then it depends on the pixel of the
// layer. Only if the pixel the coordinates point to are not
// transparent, the button takes the click.
if (hs.empty() && isPixelTransparent(x, y))
return false;
}
else if (fb == FB_INV_CHANNEL)
{
if (pressed)
instance = 0;
else
instance = 1;
// we ignore the state of the method, because it doesn't matter
// for the message to the controller.
drawButton(instance, false);
// If there is nothing in "hs", then it depends on the pixel of the
// layer. Only if the pixel the coordinates point to are not
// transparent, the button takes the click.
if (hs.empty() && isPixelTransparent(x, y))
return false;
}
else if (fb == FB_ALWAYS_ON)
{
instance = 1;
mActInstance = 1;
// we ignore the state of the method, because it doesn't matter
// for the message to the controller.
drawButton(instance, false);
// If there is nothing in "hs", then it depends on the pixel of the
// layer. Only if the pixel the coordinates point to are not
// transparent, the button takes the click.
if (hs.empty() && isPixelTransparent(x, y))
return false;
}
if ((cp && ch) || !op.empty())
{
scmd.device = TConfig::getChannel();
scmd.port = cp;
scmd.channel = ch;
if (op.empty())
{
if (instance)
scmd.MC = 0x0084;
else
scmd.MC = 0x0085;
}
else
{
scmd.MC = 0x008b;
scmd.msg = op;
}
MSG_DEBUG("Button " << bi << ", " << na << " with handle " << TObject::handleToString(mHandle));
MSG_DEBUG("Sending to device <" << scmd.device << ":" << scmd.port << ":0> channel " << scmd.channel << " value 0x" << std::setw(2) << std::setfill('0') << std::hex << scmd.MC << " (" << (pressed?"PUSH":"RELEASE") << ")");
if (gAmxNet)
{
if (scmd.MC != 0x008b || (pressed && scmd.MC == 0x008b))
gAmxNet->sendCommand(scmd);
}
else
MSG_WARNING("Missing global class TAmxNet. Can't send a message!");
}
}
else if (type == MULTISTATE_GENERAL)
{
if ((cp && ch) || !op.empty())
{
scmd.device = TConfig::getChannel();
scmd.port = cp;
scmd.channel = ch;
if (op.empty())
{
if (pressed || fb == FB_ALWAYS_ON)
scmd.MC = 0x0084;
else
scmd.MC = 0x0085;
}
else
{
scmd.MC = 0x008b;
scmd.msg = op;
}
MSG_DEBUG("Button " << bi << ", " << na << " with handle " << TObject::handleToString(mHandle));
MSG_DEBUG("Sending to device <" << scmd.device << ":" << scmd.port << ":0> channel " << scmd.channel << " value 0x" << std::setw(2) << std::setfill('0') << std::hex << scmd.MC << " (" << (pressed?"PUSH":"RELEASE") << ")");
if (gAmxNet)
{
if (scmd.MC != 0x008b || (pressed && scmd.MC == 0x008b))
gAmxNet->sendCommand(scmd);
}
else
MSG_WARNING("Missing global class TAmxNet. Can't send a message!");
}
}
/* FIXME: Move the following to class TPageManager!
* To do that, the preconditions are to be implemented. It must be
* possible to find the button and get access to the credentials
* of it.
*/
if (!pushFunc.empty() && pressed)
{
vector<PUSH_FUNC_T>::iterator iter;
for (iter = pushFunc.begin(); iter != pushFunc.end(); iter++)
{
if (iter->pfType == "sShow") // show popup
{
if (gPageManager)
gPageManager->showSubPage(iter->pfName);
}
else if (iter->pfType == "sHide") // hide popup
{
if (gPageManager)
gPageManager->hideSubPage(iter->pfName);
}
else if (iter->pfType == "scGroup") // hide group
{
if (gPageManager)
gPageManager->closeGroup(iter->pfName);
}
else if (iter->pfType == "Stan") // Flip to standard page
{
if (gPageManager)
{
TPage *page = gPageManager->getActualPage();
if (!page)
{
MSG_DEBUG("Internal error: No actual page found!");
return false;
}
TSettings *settings = gPageManager->getSettings();
if (settings->getPowerUpPage().compare(page->getName()) != 0)
gPageManager->setPage(settings->getPowerUpPage());
}
}
else if (iter->pfType == "Prev") // Flip to previous page
{
if (gPageManager)
{
int old = gPageManager->getPreviousPageNumber();
if (old > 0)
gPageManager->setPage(old);
}
}
else if (iter->pfType == "sToggle") // Toggle popup state
{
if (gPageManager)
{
TSubPage *page = gPageManager->getSubPage(iter->pfName);
if (page && page->isVisible())
gPageManager->hideSubPage(iter->pfName);
else if (page)
gPageManager->showSubPage(iter->pfName);
}
}
}
}
return true;
}
/**
* Based on the pixels in the \a basePix, the function decides whether to return
* the value of \a col1 or \a col2. A red pixel returns the color \a col1 and
* a green pixel returns the color \a col2. If there is no red and no green
* pixel, a transparent pixel is returned.
*
* @param basePix
* This is a pixel from a mask containing red and/or green pixels.
*
* @param maskPix
* This is a pixel from a mask containing more or less tranparent pixels. If
* the alpha channel of \a basePix is 0 (transparent) this pixel is returned.
*
* @param col1
* The first color.
*
* @param col2
* The second color.
*
* @return
* An array containing the color for one pixel.
*/
SkColor TButton::baseColor(SkColor basePix, SkColor maskPix, SkColor col1, SkColor col2)
{
uint alpha = SkColorGetA(basePix);
uint red = SkColorGetR(basePix);
uint green = SkColorGetG(basePix);
if (alpha == 0)
return maskPix;
if (red && green)
{
uint newR = (SkColorGetR(col1) + SkColorGetR(col2) / 2) & 0x0ff;
uint newG = (SkColorGetG(col1) + SkColorGetG(col2) / 2) & 0x0ff;
uint newB = (SkColorGetB(col1) + SkColorGetB(col2) / 2) & 0x0ff;
uint newA = (SkColorGetA(col1) + SkColorGetA(col2) / 2) & 0x0ff;
return SkColorSetARGB(newA, newR, newG, newB);
}
if (red)
return col1;
if (green)
return col2;
return SK_ColorTRANSPARENT; // transparent pixel
}
TEXT_EFFECT TButton::textEffect(const std::string& effect)
{
DECL_TRACER("TButton::textEffect(const std::string& effect)");
if (effect == "Outline-S")
return EFFECT_OUTLINE_S;
else if (effect == "Outline-M")
return EFFECT_OUTLINE_M;
else if (effect == "Outline-L")
return EFFECT_OUTLINE_L;
else if (effect == "Outline-X")
return EFFECT_OUTLINE_X;
else if (effect == "Glow-S")
return EFFECT_GLOW_S;
else if (effect == "Glow-M")
return EFFECT_GLOW_M;
else if (effect == "Glow-L")
return EFFECT_GLOW_L;
else if (effect == "Glow-X")
return EFFECT_GLOW_X;
else if (effect == "Soft Drop Shadow 1")
return EFFECT_SOFT_DROP_SHADOW_1;
else if (effect == "Soft Drop Shadow 2")
return EFFECT_SOFT_DROP_SHADOW_2;
else if (effect == "Soft Drop Shadow 3")
return EFFECT_SOFT_DROP_SHADOW_3;
else if (effect == "Soft Drop Shadow 4")
return EFFECT_SOFT_DROP_SHADOW_4;
else if (effect == "Soft Drop Shadow 5")
return EFFECT_SOFT_DROP_SHADOW_5;
else if (effect == "Soft Drop Shadow 6")
return EFFECT_SOFT_DROP_SHADOW_6;
else if (effect == "Soft Drop Shadow 7")
return EFFECT_SOFT_DROP_SHADOW_7;
else if (effect == "Soft Drop Shadow 8")
return EFFECT_SOFT_DROP_SHADOW_8;
else if (effect == "Medium Drop Shadow 1")
return EFFECT_MEDIUM_DROP_SHADOW_1;
else if (effect == "Medium Drop Shadow 2")
return EFFECT_MEDIUM_DROP_SHADOW_2;
else if (effect == "Medium Drop Shadow 3")
return EFFECT_MEDIUM_DROP_SHADOW_3;
else if (effect == "Medium Drop Shadow 4")
return EFFECT_MEDIUM_DROP_SHADOW_4;
else if (effect == "Medium Drop Shadow 5")
return EFFECT_MEDIUM_DROP_SHADOW_5;
else if (effect == "Medium Drop Shadow 6")
return EFFECT_MEDIUM_DROP_SHADOW_6;
else if (effect == "Medium Drop Shadow 7")
return EFFECT_MEDIUM_DROP_SHADOW_7;
else if (effect == "Medium Drop Shadow 8")
return EFFECT_MEDIUM_DROP_SHADOW_8;
else if (effect == "Hard Drop Shadow 1")
return EFFECT_HARD_DROP_SHADOW_1;
else if (effect == "Hard Drop Shadow 2")
return EFFECT_HARD_DROP_SHADOW_2;
else if (effect == "Hard Drop Shadow 3")
return EFFECT_HARD_DROP_SHADOW_3;
else if (effect == "Hard Drop Shadow 4")
return EFFECT_HARD_DROP_SHADOW_4;
else if (effect == "Hard Drop Shadow 5")
return EFFECT_HARD_DROP_SHADOW_5;
else if (effect == "Hard Drop Shadow 6")
return EFFECT_HARD_DROP_SHADOW_6;
else if (effect == "Hard Drop Shadow 7")
return EFFECT_HARD_DROP_SHADOW_7;
else if (effect == "Hard Drop Shadow 8")
return EFFECT_HARD_DROP_SHADOW_8;
else if (effect == "Soft Drop Shadow 1 with outline")
return EFFECT_SOFT_DROP_SHADOW_1_WITH_OUTLINE;
else if (effect == "Soft Drop Shadow 2 with outline")
return EFFECT_SOFT_DROP_SHADOW_2_WITH_OUTLINE;
else if (effect == "Soft Drop Shadow 3 with outline")
return EFFECT_SOFT_DROP_SHADOW_3_WITH_OUTLINE;
else if (effect == "Soft Drop Shadow 4 with outline")
return EFFECT_SOFT_DROP_SHADOW_4_WITH_OUTLINE;
else if (effect == "Soft Drop Shadow 5 with outline")
return EFFECT_SOFT_DROP_SHADOW_5_WITH_OUTLINE;
else if (effect == "Soft Drop Shadow 6 with outline")
return EFFECT_SOFT_DROP_SHADOW_6_WITH_OUTLINE;
else if (effect == "Soft Drop Shadow 7 with outline")
return EFFECT_SOFT_DROP_SHADOW_7_WITH_OUTLINE;
else if (effect == "Soft Drop Shadow 8 with outline")
return EFFECT_SOFT_DROP_SHADOW_8_WITH_OUTLINE;
else if (effect == "Medium Drop Shadow 1 with outline")
return EFFECT_MEDIUM_DROP_SHADOW_1_WITH_OUTLINE;
else if (effect == "Medium Drop Shadow 2 with outline")
return EFFECT_MEDIUM_DROP_SHADOW_2_WITH_OUTLINE;
else if (effect == "Medium Drop Shadow 3 with outline")
return EFFECT_MEDIUM_DROP_SHADOW_3_WITH_OUTLINE;
else if (effect == "Medium Drop Shadow 4 with outline")
return EFFECT_MEDIUM_DROP_SHADOW_4_WITH_OUTLINE;
else if (effect == "Medium Drop Shadow 5 with outline")
return EFFECT_MEDIUM_DROP_SHADOW_5_WITH_OUTLINE;
else if (effect == "Medium Drop Shadow 6 with outline")
return EFFECT_MEDIUM_DROP_SHADOW_6_WITH_OUTLINE;
else if (effect == "Medium Drop Shadow 7 with outline")
return EFFECT_MEDIUM_DROP_SHADOW_7_WITH_OUTLINE;
else if (effect == "Medium Drop Shadow 8 with outline")
return EFFECT_MEDIUM_DROP_SHADOW_8_WITH_OUTLINE;
else if (effect == "Hard Drop Shadow 1 with outline")
return EFFECT_HARD_DROP_SHADOW_1_WITH_OUTLINE;
else if (effect == "Hard Drop Shadow 2 with outline")
return EFFECT_HARD_DROP_SHADOW_2_WITH_OUTLINE;
else if (effect == "Hard Drop Shadow 3 with outline")
return EFFECT_HARD_DROP_SHADOW_3_WITH_OUTLINE;
else if (effect == "Hard Drop Shadow 4 with outline")
return EFFECT_HARD_DROP_SHADOW_4_WITH_OUTLINE;
else if (effect == "Hard Drop Shadow 5 with outline")
return EFFECT_HARD_DROP_SHADOW_5_WITH_OUTLINE;
else if (effect == "Hard Drop Shadow 6 with outline")
return EFFECT_HARD_DROP_SHADOW_6_WITH_OUTLINE;
else if (effect == "Hard Drop Shadow 7 with outline")
return EFFECT_HARD_DROP_SHADOW_7_WITH_OUTLINE;
else if (effect == "Hard Drop Shadow 8 with outline")
return EFFECT_HARD_DROP_SHADOW_8_WITH_OUTLINE;
return EFFECT_NONE;
}
bool TButton::isSystemButton()
{
DECL_TRACER("TButton::isSystemButton()");
int i = 0;
while (sysButtons[i].channel)
{
if (ap == 0 && ad == sysButtons[i].channel)
return true;
i++;
}
return false;
}
THR_REFRESH_t *TButton::_addResource(TImageRefresh* refr, ulong handle, ulong parent, int bi)
{
DECL_TRACER("TButton::_addResource(TImageRefresh* refr, ulong handle, ulong parent, int bi)");
THR_REFRESH_t *p = mThrRefresh;
THR_REFRESH_t *r, *last = p;
if (!refr || !handle || !parent || bi <= 0)
{
MSG_ERROR("Invalid parameter!");
return nullptr;
}
r = new THR_REFRESH_t;
r->mImageRefresh = refr;
r->handle = handle;
r->parent = parent;
r->bi = bi;
r->next = nullptr;
// If the chain is empty, add the new item;
if (!mThrRefresh)
mThrRefresh = r;
else // Find the end and append the item
{
while (p)
{
last = p;
if (p->handle == handle && p->parent == parent && p->bi == bi)
{
MSG_WARNING("Duplicate button found! Didn't add it again.");
delete r;
return p;
}
p = p->next;
}
last->next = r;
}
MSG_DEBUG("New dynamic button added.");
return r;
}
THR_REFRESH_t *TButton::_findResource(ulong handle, ulong parent, int bi)
{
DECL_TRACER("TButton::_findResource(ulong handle, ulong parent, int bi)");
THR_REFRESH_t *p = mThrRefresh;
while (p)
{
if (p->handle == handle && p->parent == parent && p->bi == bi)
return p;
p = p->next;
}
return nullptr;
}