Subversion Repositories tpanel

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 andreas 1
/*
15 andreas 2
 * Copyright (C) 2020, 2021 by Andreas Theofilu <andreas@theosys.at>
2 andreas 3
 *
4
 * This program is free software; you can redistribute it and/or modify
5
 * it under the terms of the GNU General Public License as published by
6
 * the Free Software Foundation; either version 3 of the License, or
7
 * (at your option) any later version.
8
 *
9
 * This program is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program; if not, write to the Free Software Foundation,
16
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
17
 */
3 andreas 18
 
19
#include <string>
20
#include <memory>
6 andreas 21
 
22
#include <include/core/SkPixmap.h>
3 andreas 23
#include <include/core/SkSize.h>
4 andreas 24
#include <include/core/SkColor.h>
7 andreas 25
#include <include/core/SkFont.h>
26
#include <include/core/SkTypeface.h>
8 andreas 27
#include <include/core/SkFontMetrics.h>
19 andreas 28
#include <include/core/SkTextBlob.h>
8 andreas 29
#include <include/core/SkRegion.h>
30
#include <include/core/SkImageFilter.h>
31
#include <include/effects/SkImageFilters.h>
32
#include <include/core/SkPath.h>
10 andreas 33
#include <include/core/SkSurfaceProps.h>
26 andreas 34
#include <include/core/SkFilterQuality.h>
6 andreas 35
 
23 andreas 36
#ifdef __ANDROID__
37
#include <QtAndroidExtras/QAndroidJniObject>
38
#include <QtAndroid>
39
#endif
40
 
2 andreas 41
#include "tbutton.h"
3 andreas 42
#include "terror.h"
43
#include "tconfig.h"
4 andreas 44
#include "tresources.h"
8 andreas 45
#include "ticons.h"
14 andreas 46
#include "tamxnet.h"
47
#include "tobject.h"
48
#include "tpagemanager.h"
2 andreas 49
 
15 andreas 50
using std::exception;
3 andreas 51
using std::string;
52
using std::vector;
53
using std::unique_ptr;
54
using std::map;
55
using std::pair;
15 andreas 56
using std::thread;
57
using std::atomic;
58
using std::mutex;
21 andreas 59
using std::bind;
2 andreas 60
using namespace Button;
61
 
21 andreas 62
#define MAX_BUFFER      65536
63
 
8 andreas 64
extern TIcons *gIcons;
14 andreas 65
extern amx::TAmxNet *gAmxNet;
26 andreas 66
extern TPageManager *gPageManager;
8 andreas 67
 
21 andreas 68
atomic<bool> TButton::mAniRunning;
69
THR_REFRESH_t *TButton::mThrRefresh = nullptr;
70
 
15 andreas 71
mutex mutex_button;
72
mutex mutex_bargraph;
73
mutex mutex_sysdraw;
74
 
4 andreas 75
SYSBORDER_t sysBorders[] = {
8 andreas 76
    {  1, (char *)"Single Line",         0, (char *)"solid",   1,   0 },
77
    {  2, (char *)"Double Line",         0, (char *)"solid",   2,   0 },
78
    {  3, (char *)"Quad Line",           0, (char *)"solid",   4,   0 },
79
    {  4, (char *)"Picture Frame",       0, (char *)"double",  0,   0 },
80
    {  5, (char *)"Circle 15",           8, (char *)"solid",   2,  15 },
81
    {  6, (char *)"Circle 25",           9, (char *)"solid",   2,  25 },
82
    {  7, (char *)"Circle 35",          10, (char *)"solid",   2,  35 },
83
    {  8, (char *)"Circle 45",          11, (char *)"solid",   2,  45 },
84
    {  9, (char *)"Circle 55",          12, (char *)"solid",   2,  55 },
85
    { 10, (char *)"Circle 65",          13, (char *)"solid",   2,  65 },
86
    { 11, (char *)"Circle 75",          14, (char *)"solid",   2,  75 },
87
    { 12, (char *)"Circle 85",          15, (char *)"solid",   2,  85 },
88
    { 13, (char *)"Circle 95",          16, (char *)"solid",   2,  95 },
89
    { 14, (char *)"Circle 105",         17, (char *)"solid",   2, 105 },
90
    { 15, (char *)"Circle 115",         18, (char *)"solid",   2, 115 },
91
    { 16, (char *)"Circle 125",         19, (char *)"solid",   2, 125 },
92
    { 17, (char *)"Circle 135",         20, (char *)"solid",   2, 135 },
93
    { 18, (char *)"Circle 145",         21, (char *)"solid",   2, 145 },
94
    { 19, (char *)"Circle 155",         22, (char *)"solid",   2, 155 },
95
    { 20, (char *)"Circle 165",         23, (char *)"solid",   2, 165 },
96
    { 21, (char *)"Circle 175",         24, (char *)"solid",   2, 175 },
97
    { 22, (char *)"Circle 185",         25, (char *)"solid",   2, 185 },
98
    { 23, (char *)"Circle 195",         26, (char *)"solid",   2, 195 },
99
    { 24, (char *)"AMX Elite Inset _L",  0, (char *)"groove", 10,   0 },
100
    { 25, (char *)"AMX Elite Raised _L", 0, (char *)"ridge",  10,   0 },
101
    { 26, (char *)"AMX Elite Inset _M",  0, (char *)"groove",  5,   0 },
102
    { 27, (char *)"AMX Elite Raised _M", 0, (char *)"ridge",   5,   0 },
103
    { 28, (char *)"AMX Elite Inset _S",  0, (char *)"groove",  2,   0 },
104
    { 29, (char *)"AMX Elite Raised _S", 0, (char *)"ridge",   2,   0 },
105
    { 30, (char *)"Bevel Inset _L",      0, (char *)"inset",  10,   0 },
106
    { 31, (char *)"Bevel Raised _L",     0, (char *)"outset", 10,   0 },
107
    { 32, (char *)"Bevel Inset _M",      0, (char *)"inset",   5,   0 },
108
    { 33, (char *)"Bevel Raised _M",     0, (char *)"outset",  5,   0 },
109
    { 34, (char *)"Bevel Inset _S",      0, (char *)"inset",   2,   0 },
110
    { 35, (char *)"Bevel Raised _S",     0, (char *)"outset",  2,   0 },
111
    {  0, nullptr, 0, nullptr, 0, 0 }
4 andreas 112
};
113
 
15 andreas 114
SYSBUTTONS_t sysButtons[] = {
115
    {   8, MULTISTATE_BARGRAPH,  12, 0,  11 },  // Connection status
116
    { 141, GENERAL,               2, 0,   0 },  // Standard time
117
    { 142, GENERAL,               2, 0,   0 },  // Time AM/PM
118
    { 143, GENERAL,               2, 0,   0 },  // 24 hour time
119
    { 151, GENERAL,               2, 0,   0 },  // Date weekday
120
    { 152, GENERAL,               2, 0,   0 },  // Date mm/dd
121
    { 153, GENERAL,               2, 0,   0 },  // Date dd/mm
122
    { 154, GENERAL,               2, 0,   0 },  // Date mm/dd/yyyy
123
    { 155, GENERAL,               2, 0,   0 },  // Date dd/mm/yyyy
124
    { 156, GENERAL,               2, 0,   0 },  // Date month dd, yyyy
125
    { 157, GENERAL,               2, 0,   0 },  // Date dd month, yyyy
126
    { 158, GENERAL,               2, 0,   0 },  // Date yyyy-mm-dd
127
    { 234, GENERAL,               2, 0,   0 },  // Battery charging/not charging
128
    { 242, BARGRAPH,              2, 0, 100 },  // Battery level
129
    {   0, NONE,                  0, 0,   0 }   // Terminate
130
};
4 andreas 131
 
3 andreas 132
TButton::TButton()
2 andreas 133
{
3 andreas 134
    DECL_TRACER("TButton::TButton()");
16 andreas 135
 
21 andreas 136
    mAniRunning = false;
16 andreas 137
    mLastBlink.clear();
2 andreas 138
}
139
 
3 andreas 140
TButton::~TButton()
2 andreas 141
{
3 andreas 142
    DECL_TRACER("TButton::~TButton()");
143
 
144
    map<int, IMAGE_t>::iterator iter;
145
 
146
    for (iter = mImages.begin(); iter != mImages.end(); iter++)
147
    {
6 andreas 148
        if (!iter->second.imageMi.empty())
3 andreas 149
            iter->second.imageMi.reset();
150
 
6 andreas 151
        if (!iter->second.imageBm.empty())
3 andreas 152
            iter->second.imageBm.reset();
153
    }
154
 
155
    mImages.clear();
15 andreas 156
 
157
    if (ap == 0 && ad == 8)
158
    {
159
        if (gAmxNet)
160
            gAmxNet->deregNetworkState(mHandle);
161
    }
162
 
163
    if (ap == 0 && ((ad >= 141 && ad <= 143) || (ad >= 151 && ad <= 158)))
164
    {
165
        if (gAmxNet)
166
            gAmxNet->deregTimer(mHandle);
167
    }
168
 
169
    if (mTimer)
170
    {
171
        mTimer->stop();
172
 
173
        while (mTimer->isRunning())
174
            usleep(50);
175
 
176
        delete mTimer;
177
    }
21 andreas 178
 
179
    THR_REFRESH_t *next, *p = mThrRefresh;
180
 
181
    while (p)
182
    {
183
        if (p->mImageRefresh)
184
        {
185
            p->mImageRefresh->stop();
186
 
187
            while (p->mImageRefresh->isRunning())
188
                usleep(50);
189
 
190
            delete p->mImageRefresh;
191
            p->mImageRefresh = nullptr;
192
        }
193
 
194
        next = p->next;
195
        delete p;
196
        p = next;
197
    }
2 andreas 198
}
3 andreas 199
 
200
void TButton::initialize(TReadXML *xml, mxml_node_t *node)
201
{
202
    DECL_TRACER("TButton::initialize(TReadXML *xml, mxml_node_t *node)");
203
 
204
    if (!xml || !node)
205
    {
206
        MSG_ERROR("Invalid NULL parameter passed!");
207
        TError::setError();
208
        return;
209
    }
210
 
211
    mxml_node_t *child = xml->getFirstChild(node);
212
    string stype = xml->getAttributeFromNode(node, "type");
213
    type = getButtonType(stype);
214
    MSG_DEBUG("Button type: " << stype << " --> " << type);
215
 
216
    while(child)
217
    {
218
        string ename = xml->getElementName(child);
219
 
220
        if (ename.compare("bi") == 0)
221
            bi = xml->getIntFromNode(child);
222
        else if (ename.compare("na") == 0)
223
            na = xml->getTextFromNode(child);
224
        else if (ename.compare("bd") == 0)
225
            bd = xml->getTextFromNode(child);
226
        else if (ename.compare("lt") == 0)
227
            lt = xml->getIntFromNode(child);
228
        else if (ename.compare("tp") == 0)
229
            tp = xml->getIntFromNode(child);
230
        else if (ename.compare("wt") == 0)
231
            wt = xml->getIntFromNode(child);
232
        else if (ename.compare("ht") == 0)
233
            ht = xml->getIntFromNode(child);
234
        else if (ename.compare("zo") == 0)
235
            zo = xml->getIntFromNode(child);
236
        else if (ename.compare("hs") == 0)
237
            hs = xml->getTextFromNode(child);
238
        else if (ename.compare("bs") == 0)
239
            bs = xml->getTextFromNode(child);
240
        else if (ename.compare("fb") == 0)
241
            fb = getButtonFeedback(xml->getTextFromNode(child));
242
        else if (ename.compare("ap") == 0)
243
            ap = xml->getIntFromNode(child);
244
        else if (ename.compare("ad") == 0)
245
            ad = xml->getIntFromNode(child);
246
        else if (ename.compare("ch") == 0)
247
            ch = xml->getIntFromNode(child);
248
        else if (ename.compare("cp") == 0)
249
            cp = xml->getIntFromNode(child);
250
        else if (ename.compare("lp") == 0)
251
            lp = xml->getIntFromNode(child);
252
        else if (ename.compare("lv") == 0)
253
            lv = xml->getIntFromNode(child);
254
        else if (ename.compare("dr") == 0)
255
            dr = xml->getTextFromNode(child);
256
        else if (ename.compare("va") == 0)
257
            va = xml->getIntFromNode(child);
258
        else if (ename.compare("rm") == 0)
259
            rm = xml->getIntFromNode(child);
260
        else if (ename.compare("nu") == 0)
261
            nu = xml->getIntFromNode(child);
262
        else if (ename.compare("nd") == 0)
263
            nd = xml->getIntFromNode(child);
264
        else if (ename.compare("ar") == 0)
265
            ar = xml->getIntFromNode(child);
266
        else if (ename.compare("ru") == 0)
267
            ru = xml->getIntFromNode(child);
268
        else if (ename.compare("rd") == 0)
269
            rd = xml->getIntFromNode(child);
270
        else if (ename.compare("lu") == 0)
271
            lu = xml->getIntFromNode(child);
272
        else if (ename.compare("ld") == 0)
273
            ld = xml->getIntFromNode(child);
274
        else if (ename.compare("rv") == 0)
275
            rv = xml->getIntFromNode(child);
276
        else if (ename.compare("rl") == 0)
277
            rl = xml->getIntFromNode(child);
278
        else if (ename.compare("rh") == 0)
279
            rh = xml->getIntFromNode(child);
280
        else if (ename.compare("ri") == 0)
281
            ri = xml->getIntFromNode(child);
282
        else if (ename.compare("rn") == 0)
283
            rn = xml->getIntFromNode(child);
284
        else if (ename.compare("if") == 0)
285
            _if = xml->getTextFromNode(child);
286
        else if (ename.compare("sd") == 0)
287
            sd = xml->getTextFromNode(child);
288
        else if (ename.compare("sc") == 0)
289
            sc = xml->getTextFromNode(child);
290
        else if (ename.compare("mt") == 0)
291
            mt = xml->getIntFromNode(child);
292
        else if (ename.compare("dt") == 0)
293
            dt = xml->getTextFromNode(child);
294
        else if (ename.compare("im") == 0)
295
            im = xml->getTextFromNode(child);
296
        else if (ename.compare("op") == 0)
297
            op = xml->getTextFromNode(child);
298
        else if (ename.compare("pf") == 0)
299
        {
300
            PUSH_FUNC_T pf;
301
            pf.pfName = xml->getTextFromNode(child);
302
            pf.pfType = xml->getAttributeFromNode(child, "type");
303
            pushFunc.push_back(pf);
304
        }
305
        else if (ename.compare("sr") == 0)
306
        {
307
            mxml_node_t *n = xml->getFirstChild(child);
308
            SR_T bsr;
309
            bsr.number = atoi(xml->getAttributeFromNode(child, "number").c_str());
310
 
311
            while (n)
312
            {
313
                string e = xml->getElementName(n);
314
 
315
                if (e.compare("do") == 0)
316
                    bsr._do = xml->getTextFromNode(n);
317
                else if (e.compare("bs") == 0)
318
                    bsr.bs = xml->getTextFromNode(n);
319
                else if (e.compare("mi") == 0)
320
                    bsr.mi = xml->getTextFromNode(n);
321
                else if (e.compare("cb") == 0)
322
                    bsr.cb = xml->getTextFromNode(n);
323
                else if (e.compare("cf") == 0)
324
                    bsr.cf = xml->getTextFromNode(n);
325
                else if (e.compare("ct") == 0)
326
                    bsr.ct = xml->getTextFromNode(n);
327
                else if (e.compare("ec") == 0)
328
                    bsr.ec = xml->getTextFromNode(n);
329
                else if (e.compare("bm") == 0)
330
                {
331
                    bsr.bm = xml->getTextFromNode(n);
332
                    bsr.dynamic = ((atoi(xml->getAttributeFromNode(n, "dynamic").c_str()) == 1) ? true : false);
333
                }
334
                else if (e.compare("sd") == 0)
335
                    bsr.sd = xml->getTextFromNode(n);
336
                else if (e.compare("sb") == 0)
337
                    bsr.sb = xml->getIntFromNode(n);
338
                else if (e.compare("ii") == 0)
339
                    bsr.ii = xml->getIntFromNode(n);
340
                else if (e.compare("ji") == 0)
341
                    bsr.ji = xml->getIntFromNode(n);
342
                else if (e.compare("jb") == 0)
343
                    bsr.jb = xml->getIntFromNode(n);
10 andreas 344
                else if (e.compare("bx") == 0)
345
                    bsr.bx = xml->getIntFromNode(n);
346
                else if (e.compare("by") == 0)
347
                    bsr.by = xml->getIntFromNode(n);
3 andreas 348
                else if (e.compare("ix") == 0)
349
                    bsr.ix = xml->getIntFromNode(n);
350
                else if (e.compare("iy") == 0)
351
                    bsr.iy = xml->getIntFromNode(n);
352
                else if (e.compare("fi") == 0)
353
                    bsr.fi = xml->getIntFromNode(n);
354
                else if (e.compare("te") == 0)
355
                    bsr.te = xml->getTextFromNode(n);
356
                else if (e.compare("jt") == 0)
357
                    bsr.jt = (TEXT_ORIENTATION)xml->getIntFromNode(n);
358
                else if (e.compare("tx") == 0)
359
                    bsr.tx = xml->getIntFromNode(n);
360
                else if (e.compare("ty") == 0)
361
                    bsr.ty = xml->getIntFromNode(n);
362
                else if (e.compare("ww") == 0)
363
                    bsr.ww = xml->getIntFromNode(n);
364
                else if (e.compare("et") == 0)
365
                    bsr.et = xml->getIntFromNode(n);
366
                else if (e.compare("oo") == 0)
367
                    bsr.oo = xml->getIntFromNode(n);
368
 
369
                n = xml->getNextChild(n);
370
            }
371
 
372
            sr.push_back(bsr);
373
        }
374
 
375
        child = xml->getNextChild(child);
376
 
377
        if (xml->getElementName(child).compare("button") == 0)
378
        {
379
            MSG_WARNING("Unexpected element \"button\" found! Please check XML file.");
380
            break;
381
        }
382
    }
383
}
384
 
385
BUTTONTYPE TButton::getButtonType(const string& bt)
386
{
387
    DECL_TRACER("TButton::getButtonType(const string& bt)");
388
 
389
    if (bt.compare("general") == 0)
390
        return GENERAL;
391
    else if (bt.compare("multi-state general") == 0)
392
        return MULTISTATE_GENERAL;
393
    else if (bt.compare("bargraph") == 0)
394
        return BARGRAPH;
395
    else if (bt.compare("multi-state bargraph") == 0)
396
        return MULTISTATE_BARGRAPH;
397
    else if (bt.compare("joistick") == 0)
398
        return JOISTICK;
399
    else if (bt.compare("text input") == 0)
400
        return TEXT_INPUT;
401
    else if (bt.compare("computer control") == 0)
402
        return COMPUTER_CONTROL;
403
    else if (bt.compare("take note") == 0)
404
        return TAKE_NOTE;
405
    else if (bt.compare("sub-page view") == 0)
406
        return SUBPAGE_VIEW;
407
 
408
    return NONE;
409
}
410
 
411
FEEDBACK TButton::getButtonFeedback(const string& fb)
412
{
413
    DECL_TRACER("TButton::getButtonFeedback(const string& fb)");
414
 
415
    if (fb.compare("channel") == 0)
416
        return FB_CHANNEL;
417
    else if (fb.compare("inverted channel") == 0)
418
        return FB_INV_CHANNEL;
419
    else if (fb.compare("always on") == 0)
420
        return FB_ALWAYS_ON;
421
    else if (fb.compare("momentary") == 0)
422
        return FB_MOMENTARY;
423
    else if (fb.compare("blink") == 0)
424
        return FB_BLINK;
425
 
426
    return FB_NONE;
427
}
428
 
4 andreas 429
bool TButton::createButtons()
3 andreas 430
{
6 andreas 431
    DECL_TRACER("TButton::createButtons()");
3 andreas 432
 
433
    // Get the images, if there any
434
    vector<SR_T>::iterator srIter;
6 andreas 435
    int i = 0;
3 andreas 436
 
437
    for (srIter = sr.begin(); srIter != sr.end(); srIter++)
438
    {
439
        int number = srIter->number;
440
        IMAGE_t img;
441
 
21 andreas 442
        if (srIter->sb > 0)
443
            continue;
444
 
3 andreas 445
        if (!srIter->mi.empty())        // Do we have a chameleon image?
446
        {
4 andreas 447
            sk_sp<SkData> image;
448
 
449
            if (!(image = readImage(srIter->mi)))
3 andreas 450
                return false;
451
 
6 andreas 452
            DecodeDataToBitmap(image, &img.imageMi);
3 andreas 453
 
6 andreas 454
            if (img.imageMi.empty())
3 andreas 455
            {
456
                MSG_WARNING("Could not create a picture for element " << number << " on button " << bi << " (" << na << ")");
457
                return false;
458
            }
459
 
6 andreas 460
            srIter->mi_width = img.imageMi.dimensions().width();
461
            srIter->mi_height = img.imageMi.dimensions().height();
3 andreas 462
        }
463
 
464
        if (!srIter->bm.empty())        // Do we have a bitmap?
465
        {
4 andreas 466
            sk_sp<SkData> image;
467
 
468
            if (!(image = readImage(srIter->bm)))
3 andreas 469
                return false;
470
 
6 andreas 471
            DecodeDataToBitmap(image, &img.imageBm);
3 andreas 472
 
6 andreas 473
            if (img.imageBm.empty())
3 andreas 474
            {
475
                MSG_WARNING("Could not create a picture for element " << number << " on button " << bi << " (" << na << ")");
476
                return false;
477
            }
478
 
6 andreas 479
            srIter->bm_width = img.imageBm.dimensions().width();
480
            srIter->bm_height = img.imageBm.dimensions().height();
3 andreas 481
        }
482
 
483
        mImages.insert(pair<int, IMAGE_t>(number, img));
6 andreas 484
        i++;
3 andreas 485
    }
486
 
4 andreas 487
    return true;
488
}
3 andreas 489
 
16 andreas 490
void TButton::refresh()
491
{
492
    DECL_TRACER("TButton::refresh()");
493
 
494
    makeElement();
495
}
496
 
15 andreas 497
bool TButton::makeElement(int instance)
498
{
499
    DECL_TRACER("TButton::makeElement()");
500
 
501
    int inst = mActInstance;
502
 
503
    if (instance >= 0)
504
        inst = instance;
505
 
506
    if (type == MULTISTATE_GENERAL && ar == 1)
507
        return drawButtonMultistateAni();
508
    else if (type == BARGRAPH)
509
        return drawBargraph(inst, mLastLevel);
510
    else
511
        return drawButton(inst);
512
 
513
    return false;
514
}
515
 
14 andreas 516
bool TButton::setActive(int instance)
517
{
518
    DECL_TRACER("TButton::setActive(int instance)");
519
 
520
    if ((size_t)instance >= sr.size())
521
    {
522
        MSG_ERROR("Instance " << instance << " is higher than the maximum instance " << sr.size() << "!");
523
        return false;
524
    }
525
 
526
    if (instance == mActInstance)
527
    {
528
        MSG_TRACE("Not necessary to set instance " << instance << " again.");
529
        return true;
530
    }
531
 
532
    mActInstance = instance;
15 andreas 533
    makeElement(instance);
14 andreas 534
 
535
    return true;
536
}
537
 
538
bool TButton::setIcon(int id, int instance)
539
{
540
    DECL_TRACER("TButton::setIcon(int id, int instance)");
541
 
542
    if ((size_t)instance >= sr.size())
543
    {
544
        MSG_ERROR("Instance " << instance << " does not exist!");
545
        return false;
546
    }
547
 
548
    sr[instance].ii = id;
16 andreas 549
    return makeElement(instance);
14 andreas 550
}
551
 
552
bool TButton::setIcon(const string& icon, int instance)
553
{
554
    DECL_TRACER("TButton::setIcon(const string& icon, int instance)");
555
 
556
    if ((size_t)instance >= sr.size())
557
    {
558
        MSG_ERROR("Instance " << instance << " does not exist!");
559
        return false;
560
    }
561
 
562
    if (!gIcons)
563
    {
564
        gIcons = new TIcons();
565
 
566
        if (TError::isError())
567
        {
568
            MSG_ERROR("Error initializing icons!");
569
            return false;
570
        }
571
    }
572
 
573
    int id = gIcons->getNumber(icon);
574
 
575
    if (id == -1)
576
    {
577
        MSG_WARNING("Icon " << icon << " not found!");
578
        return false;
579
    }
580
 
581
    sr[instance].ii = id;
16 andreas 582
    return makeElement(instance);
14 andreas 583
}
584
 
585
bool TButton::revokeIcon(int instance)
586
{
587
    DECL_TRACER("TButton::revokeIcon(int instance)");
588
 
589
    if ((size_t)instance >= sr.size())
590
    {
591
        MSG_ERROR("Instance " << instance << " does not exist!");
592
        return false;
593
    }
594
 
595
    sr[instance].ii = 0;
16 andreas 596
    return makeElement(instance);
14 andreas 597
}
598
 
599
bool TButton::setText(const string& txt, int instance)
600
{
601
    DECL_TRACER("TButton::setText(const string& txt, int instance)");
602
 
603
    if ((size_t)instance >= sr.size())
604
    {
605
        MSG_ERROR("Instance " << instance << " does not exist!");
606
        return false;
607
    }
608
 
609
    sr[instance].te = txt;
16 andreas 610
    return makeElement(instance);
14 andreas 611
}
612
 
16 andreas 613
bool TButton::setBitmap(const string& file, int instance)
614
{
615
    DECL_TRACER("TButton::setBitmap(const string& file, int instance)");
616
 
617
    if (file.empty() || instance >= (int)sr.size())
618
    {
619
        MSG_ERROR("Invalid parameters!");
620
        return false;
621
    }
622
 
623
    sr[instance].bm = file;
624
    map<int, IMAGE_t>::iterator iter = mImages.find(sr[instance].number);
625
 
626
    if (iter != mImages.end())
627
        mImages.erase(iter);
628
 
629
    if (!createButtons())
630
        return false;
631
 
632
    return makeElement(instance);
633
}
634
 
635
bool TButton::setOpacity(int op, int instance)
636
{
637
    DECL_TRACER("TButton::setOpacity(int op, int instance)");
638
 
639
    if ((size_t)instance >= sr.size())
640
    {
641
        MSG_ERROR("Instance " << instance << " does not exist!");
642
        return false;
643
    }
644
 
645
    if (op < 0 || op < 255)
646
    {
647
        MSG_ERROR("Invalid opacity " << op << "!");
648
        return false;
649
    }
650
 
651
    sr[instance].oo = op;
652
    return makeElement(instance);
653
}
654
 
655
bool TButton::setFont(int id, int instance)
656
{
657
    DECL_TRACER("TButton::setFont(int id)");
658
 
659
    if (instance < 0 || (size_t)instance >= sr.size())
660
    {
661
        MSG_ERROR("Instance " << instance << " does not exist!");
662
        return false;
663
    }
664
 
665
    sr[instance].fi = id;
666
    return makeElement(instance);
667
}
668
 
669
void TButton::setLeft(int left)
670
{
671
    DECL_TRACER("TButton::setLeft(int left)");
672
 
673
    if (left < 0)
674
        return;
675
 
676
    lt = left;
677
    makeElement(mActInstance);
678
}
679
 
680
void TButton::setTop(int top)
681
{
682
    DECL_TRACER("TButton::setTop(int top)");
683
 
684
    if (top < 0)
685
        return;
686
 
687
    tp = top;
688
    makeElement(mActInstance);
689
}
690
 
691
void TButton::setLeftTop(int left, int top)
692
{
693
    DECL_TRACER("TButton::setLeftTop(int left, int top)");
694
 
695
    if (top < 0 || left < 0)
696
        return;
697
 
698
    lt = left;
699
    tp = top;
700
    makeElement(mActInstance);
701
}
702
 
21 andreas 703
void TButton::setResourceName(const string& name, int instance)
704
{
705
    DECL_TRACER("TButton::setResourceName(const string& name, int instance)");
706
 
707
    if (instance < 0 || instance >= (int)sr.size())
708
    {
709
        MSG_ERROR("Invalid instance " << instance);
710
        return;
711
    }
712
 
713
    if (!sr[instance].dynamic)
714
    {
715
        MSG_ERROR("Button " << bi << ": " << na << " is not a resource button!");
716
        return;
717
    }
718
 
719
    sr[instance].bm = name;
720
}
721
 
16 andreas 722
bool TButton::setWorWrap(bool state, int instance)
723
{
724
    DECL_TRACER("TButton::setWorWrap(bool state, int instance)");
725
 
726
    if (instance < 0 || instance >= (int)sr.size())
727
    {
728
        MSG_ERROR("Invalid instance " << instance);
729
        return false;
730
    }
731
 
732
    sr[instance].ww = state ? 1 : 0;
733
    return makeElement(instance);
734
}
735
 
22 andreas 736
void TButton::_TimerCallback(ulong)
15 andreas 737
{
738
    mLastBlink.second++;
739
    int months[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
740
 
741
    if ((mLastBlink.year % 4) == 0)
742
        months[1] = 29;
743
 
744
    if (mLastBlink.second > 59)
745
    {
746
        mLastBlink.minute++;
747
        mLastBlink.second = 0;
748
 
749
        if (mLastBlink.minute > 59)
750
        {
751
            mLastBlink.hour++;
752
            mLastBlink.minute = 0;
753
 
754
            if (mLastBlink.hour >= 24)
755
            {
756
                mLastBlink.hour = 0;
757
                mLastBlink.weekday++;
758
                mLastBlink.day++;
759
 
760
                if (mLastBlink.weekday > 7)
761
                    mLastBlink.weekday = 0;
762
 
763
                if (mLastBlink.day > months[mLastBlink.month-1])
764
                {
765
                    mLastBlink.day = 1;
766
                    mLastBlink.month++;
767
 
768
                    if (mLastBlink.month > 12)
769
                    {
770
                        mLastBlink.year++;
771
                        mLastBlink.month = 1;
772
                    }
773
                }
774
            }
775
        }
776
    }
777
 
778
    funcTimer(mLastBlink);
779
}
780
 
23 andreas 781
void TButton::_BatteryCallback(ulong)
782
{
783
    DECL_TRACER("TButton::_BatteryCallback(ulong)");
784
 
785
    mBatteryTimerRunning = true;
786
    funcBattery();
787
}
788
 
21 andreas 789
void TButton::_imageRefresh(const string& url)
790
{
791
    DECL_TRACER("TButton::_imageRefresh(const string& url)");
792
 
793
    ulong parent = mHandle & 0xffff0000;
794
    getDrawOrder(sr[mActInstance]._do, (DRAW_ORDER *)&mDOrder);
795
 
796
    if (TError::isError())
797
    {
798
        TError::clear();
799
        return;
800
    }
801
 
802
    SkBitmap imgButton;
803
    imgButton.allocN32Pixels(wt, ht);
804
 
805
    for (int i = 0; i < ORD_ELEM_COUNT; i++)
806
    {
807
        if (mDOrder[i] == ORD_ELEM_FILL)
808
        {
809
            if (!buttonFill(&imgButton, mActInstance))
810
                return;
811
        }
812
        else if (mDOrder[i] == ORD_ELEM_BITMAP)
813
        {
814
            RESOURCE_T resource = gPrjResources->findResource(sr[mActInstance].bm);
815
 
816
            if (resource.protocol.empty())
817
            {
818
                MSG_ERROR("Resource " << sr[mActInstance].bm << " not found!");
819
                return;
820
            }
821
 
822
            try
823
            {
824
                char *content = nullptr;
825
                size_t length = 0, contentlen = 0;
826
 
827
                if ((content = gPrjResources->tcall(&length, url, resource.user, resource.password)) == nullptr)
828
                    return;
829
 
830
                contentlen = gPrjResources->getContentSize();
831
 
832
                if (content == nullptr)
833
                {
834
                    MSG_ERROR("Server returned no or invalid content!");
835
                    return;
836
                }
837
 
838
                sk_sp<SkData> data = SkData::MakeWithCopy(content, contentlen);
839
 
840
                if (!data)
841
                {
842
                    MSG_ERROR("Could not make an image!");
843
                    return;
844
                }
845
 
846
                SkBitmap image;
847
 
848
                if (!DecodeDataToBitmap(data, &image))
849
                {
850
                    MSG_ERROR("Error creating an image!");
851
                    return;
852
                }
853
 
854
                loadImage(&imgButton, image, mActInstance);
855
            }
856
            catch (std::exception& e)
857
            {
858
                MSG_ERROR(e.what());
859
                return;
860
            }
861
        }
862
        else if (mDOrder[i] == ORD_ELEM_ICON)
863
        {
864
            if (!buttonIcon(&imgButton, mActInstance))
865
                return;
866
        }
867
        else if (mDOrder[i] == ORD_ELEM_TEXT)
868
        {
869
            if (!buttonText(&imgButton, mActInstance))
870
                return;
871
        }
872
        else if (mDOrder[i] == ORD_ELEM_BORDER)
873
        {
874
            if (!buttonBorder(&imgButton, mActInstance))
875
                return;
876
        }
877
    }
878
 
879
    if (mGlobalOO >= 0 || sr[mActInstance].oo >= 0) // Take overall opacity into consideration
880
    {
881
        SkBitmap ooButton;
882
        int w = imgButton.width();
883
        int h = imgButton.height();
884
        ooButton.allocN32Pixels(w, h, true);
885
        SkCanvas canvas(ooButton);
886
        SkIRect irect = SkIRect::MakeXYWH(0, 0, w, h);
887
        SkRegion region;
888
        region.setRect(irect);
889
        SkScalar oo;
890
 
891
        if (mGlobalOO >= 0 && sr[mActInstance].oo >= 0)
892
        {
893
            oo = std::min((SkScalar)mGlobalOO, (SkScalar)sr[mActInstance].oo);
894
            MSG_DEBUG("Set global overal opacity to " << oo);
895
        }
896
        else if (sr[mActInstance].oo >= 0)
897
        {
898
            oo = (SkScalar)sr[mActInstance].oo;
899
            MSG_DEBUG("Set overal opacity to " << oo);
900
        }
901
        else
902
        {
903
            oo = (SkScalar)mGlobalOO;
904
            MSG_DEBUG("Set global overal opacity to " << oo);
905
        }
906
 
907
        SkScalar alpha = 1.0 / 255.0 * oo;
908
        MSG_DEBUG("Calculated alpha value: " << alpha);
909
        SkPaint paint;
910
        paint.setAlphaf(alpha);
911
        paint.setImageFilter(SkImageFilters::AlphaThreshold(region, 0.0, alpha, nullptr));
912
        canvas.drawBitmap(imgButton, 0, 0, &paint);
913
        imgButton.erase(SK_ColorTRANSPARENT, {0, 0, w, h});
914
        imgButton = ooButton;
915
    }
916
 
917
    mLastImage = imgButton;
918
    size_t rowBytes = imgButton.info().minRowBytes();
919
 
920
    if (!prg_stopped && visible && _displayButton)
26 andreas 921
    {
922
        int rwidth = wt;
923
        int rheight = ht;
924
        int rleft = lt;
925
        int rtop = tp;
926
 
927
        if (gPageManager && gPageManager->getScaleFactor() != 1.0)
928
        {
929
            rwidth = (int)((double)wt * gPageManager->getScaleFactor());
930
            rheight = (int)((double)ht * gPageManager->getScaleFactor());
931
            rleft = (int)((double)lt * gPageManager->getScaleFactor());
932
            rtop = (int)((double)tp * gPageManager->getScaleFactor());
933
 
934
            SkPaint paint;
935
            paint.setBlendMode(SkBlendMode::kSrc);
936
            paint.setFilterQuality(kHigh_SkFilterQuality);
28 andreas 937
            // Calculate new dimension
26 andreas 938
            SkImageInfo info = imgButton.info();
939
            int width = (int)((double)info.width() * gPageManager->getScaleFactor());
940
            int height = (int)((double)info.height() * gPageManager->getScaleFactor());
28 andreas 941
            // Create a canvas and draw new image
942
            sk_sp<SkImage> im = SkImage::MakeFromBitmap(imgButton);
943
            imgButton.allocN32Pixels(width, height);
944
            imgButton.eraseColor(SK_ColorTRANSPARENT);
26 andreas 945
            SkCanvas can(imgButton, SkSurfaceProps(1, kUnknown_SkPixelGeometry));
946
            SkRect rect = SkRect::MakeXYWH(0, 0, width, height);
947
            can.drawImageRect(im, rect, &paint);
28 andreas 948
            rowBytes = imgButton.info().minRowBytes();
949
            mLastImage = imgButton;
26 andreas 950
        }
951
 
952
        _displayButton(mHandle, parent, (unsigned char *)imgButton.getPixels(), rwidth, rheight, rowBytes, rleft, rtop);
953
    }
21 andreas 954
}
955
 
15 andreas 956
void TButton::registerSystemButton()
957
{
958
    DECL_TRACER("TButton::registerSystemButton()");
959
 
960
    if (mSystemReg)
961
        return;
962
 
963
    // If this is a special system button, register it to receive the state
964
    if (ap == 0 && ad == 8)     // Connection status?
965
    {
966
        MSG_TRACE("Try to register button " << na << " as connection status ...");
967
 
968
        if (gAmxNet)
969
        {
970
            gAmxNet->registerNetworkState(bind(&TButton::funcNetwork, this, std::placeholders::_1), mHandle);
971
            mSystemReg = true;
972
            MSG_TRACE("Button registered");
973
        }
974
        else
975
            MSG_WARNING("Network class not initialized!");
976
 
977
    }
978
    else if (ap == 0 && ((ad >= 141 && ad <= 143) || (ad >= 151 && ad <= 158))) // time or date
979
    {
980
        MSG_TRACE("Try to register button " << na << " as time/date ...");
981
 
982
        if (gAmxNet)
983
        {
984
            gAmxNet->registerTimer(bind(&TButton::funcTimer, this, std::placeholders::_1), mHandle);
985
            mSystemReg = true;
986
            MSG_TRACE("Button registered");
987
        }
988
        else
989
            MSG_WARNING("Network class not initialized!");
990
 
991
        if (ad >= 141 && ad <= 143 && !mTimer)
992
        {
993
            mTimer = new TTimer;
994
            mTimer->setInterval(std::chrono::milliseconds(1000));   // 1 second
995
            mTimer->registerCallback(bind(&TButton::_TimerCallback, this, std::placeholders::_1));
996
            mTimer->run();
997
        }
998
    }
23 andreas 999
    else if (!mBatteryTimerRunning && ap == 0 && (ad == 242 || ad == 234))   // Battery status
1000
    {
26 andreas 1001
#ifdef __ANDROID__
23 andreas 1002
        // Invoke battery activity
1003
        QAndroidJniObject activity = QtAndroid::androidActivity();
1004
        QAndroidJniObject::callStaticMethod<void>("org/qtproject/theosys/BatteryState", "Init", "(Landroid/app/Activity;)V", activity.object());
1005
        activity.callStaticMethod<void>("org/qtproject/theosys/BatteryState", "InstallBatteryListener", "()V");
1006
#endif
1007
        funcBattery();
1008
        mBatteryTimer = new TTimer;
1009
        mBatteryTimer->setInterval(std::chrono::milliseconds(60000));   // 60 second
1010
        mBatteryTimer->registerCallback(bind(&TButton::_BatteryCallback, this, std::placeholders::_1));
1011
        mBatteryTimer->run();
1012
    }
15 andreas 1013
}
1014
 
1015
void TButton::addPushFunction(string& func, string& page)
1016
{
1017
    DECL_TRACER("TButton::addPushFunction(string& func, string& page)");
1018
 
1019
    vector<string> allFunc = { "Stan", "Prev", "Show", "Hide", "Togg", "ClearG", "ClearP", "ClearA" };
1020
    vector<string>::iterator iter;
1021
 
1022
    for (iter = allFunc.begin(); iter != allFunc.end(); ++iter)
1023
    {
1024
        if (iter->compare(func) == 0)
1025
        {
1026
            bool found = false;
1027
            vector<PUSH_FUNC_T>::iterator iterPf;
1028
 
1029
            for (iterPf = pushFunc.begin(); iterPf != pushFunc.end(); iterPf++)
1030
            {
1031
                if (iterPf->pfType.compare(func) == 0)
1032
                {
1033
                    iterPf->pfName = page;
1034
                    found = true;
1035
                    break;
1036
                }
1037
            }
1038
 
1039
            if (!found)
1040
            {
1041
                PUSH_FUNC_T pf;
1042
                pf.pfType = func;
1043
                pf.pfName = page;
1044
                pushFunc.push_back(pf);
1045
            }
1046
        }
1047
 
1048
        break;
1049
    }
1050
}
1051
 
16 andreas 1052
void TButton::clearPushFunction(const string& action)
1053
{
1054
    DECL_TRACER("TButton::clearPushFunction(const string& action)");
1055
 
1056
    if (pushFunc.empty())
1057
        return;
1058
 
1059
    vector<PUSH_FUNC_T>::iterator iter;
1060
 
1061
    for (iter = pushFunc.begin(); iter != pushFunc.end(); iter++)
1062
    {
1063
        if (iter->pfName.compare(action) == 0)
1064
        {
1065
            pushFunc.erase(iter);
1066
            return;
1067
        }
1068
    }
1069
}
1070
 
8 andreas 1071
void TButton::getDrawOrder(const std::string& sdo, DRAW_ORDER *order)
4 andreas 1072
{
8 andreas 1073
    DECL_TRACER("TButton::getDrawOrder(const std::string& sdo, DRAW_ORDER *order)");
4 andreas 1074
 
8 andreas 1075
    if (!order)
1076
        return;
1077
 
1078
    if (sdo.empty() || sdo.length() != 10)
4 andreas 1079
    {
8 andreas 1080
        *order     = ORD_ELEM_FILL;
1081
        *(order+1) = ORD_ELEM_BITMAP;
1082
        *(order+2) = ORD_ELEM_ICON;
1083
        *(order+3) = ORD_ELEM_TEXT;
1084
        *(order+4) = ORD_ELEM_BORDER;
1085
        return;
1086
    }
1087
 
1088
    int elems = sdo.length() / 2;
1089
 
1090
    for (int i = 0; i < elems; i++)
1091
    {
1092
        int e = atoi(sdo.substr(i * 2, 2).c_str());
1093
 
1094
        if (e < 1 || e > 5)
1095
        {
1096
            MSG_ERROR("Invalid draw order \"" << sdo << "\"!");
1097
            TError::setError();
1098
            return;
1099
        }
1100
 
1101
        *(order+i) = (DRAW_ORDER)e;
1102
    }
1103
}
1104
 
1105
bool TButton::buttonFill(SkBitmap* bm, int instance)
1106
{
1107
    DECL_TRACER("TButton::buttonFill(SkBitmap* bm, int instance)");
1108
 
1109
    if (!bm)
1110
    {
1111
        MSG_ERROR("Invalid bitmap!");
4 andreas 1112
        return false;
1113
    }
1114
 
8 andreas 1115
    SkColor color = TColor::getSkiaColor(sr[instance].cf);
1116
    bm->eraseColor(color);
1117
    return true;
1118
}
1119
 
1120
bool TButton::buttonBitmap(SkBitmap* bm, int instance)
1121
{
1122
    DECL_TRACER("TButton::buttonBitmap(SkBitmap* bm, int instane)");
1123
 
21 andreas 1124
    if (sr[instance].dynamic)
1125
        return buttonDynamic(bm, instance);
1126
 
29 andreas 1127
    if (!sr[instance].mi.empty() && sr[instance].bs.empty())       // Chameleon image?
4 andreas 1128
    {
20 andreas 1129
        MSG_TRACE("Chameleon image ...");
4 andreas 1130
        map<int, IMAGE_t>::iterator iterImages = mImages.find(sr[instance].number);
6 andreas 1131
        SkBitmap imgRed(iterImages->second.imageMi);
24 andreas 1132
        SkBitmap imgMask;
4 andreas 1133
 
24 andreas 1134
        if (!sr[instance].bm.empty())
1135
            imgMask.installPixels(iterImages->second.imageBm.pixmap());
1136
        else
1137
        {
1138
            imgMask.allocN32Pixels(sr[instance].mi_width, sr[instance].mi_height);
1139
            imgMask.eraseColor(SK_ColorTRANSPARENT);
1140
        }
1141
 
20 andreas 1142
        SkBitmap img = drawImageButton(imgRed, imgMask, sr[instance].mi_width, sr[instance].mi_height, TColor::getSkiaColor(sr[instance].cf), TColor::getSkiaColor(sr[instance].cb));
1143
 
1144
        if (img.empty())
4 andreas 1145
        {
20 andreas 1146
            MSG_ERROR("Error creating the cameleon image \"" << sr[instance].mi << "\" / \"" << sr[instance].bm << "\"!");
1147
            TError::setError();
1148
            return false;
1149
        }
4 andreas 1150
 
20 andreas 1151
        SkCanvas ctx(img, SkSurfaceProps(1, kUnknown_SkPixelGeometry));
1152
        SkImageInfo info = img.info();
1153
        SkPaint paint;
24 andreas 1154
        paint.setBlendMode(SkBlendMode::kSrcOver);
20 andreas 1155
        ctx.drawBitmap(imgMask, 0, 0, &paint);
4 andreas 1156
 
20 andreas 1157
        POSITION_t position = calcImagePosition(sr[instance].mi_width, sr[instance].mi_height, SC_BITMAP, instance);
7 andreas 1158
 
20 andreas 1159
        if (!position.valid)
1160
        {
1161
            MSG_ERROR("Error calculating the position of the image for button number " << bi << ": " << na);
1162
            TError::setError();
1163
            return false;
4 andreas 1164
        }
10 andreas 1165
 
20 andreas 1166
        SkCanvas can(*bm, SkSurfaceProps(1, kUnknown_SkPixelGeometry));
1167
        paint.setBlendMode(SkBlendMode::kSrc);
21 andreas 1168
 
1169
        if (sr[instance].sb == 0)
1170
            can.drawBitmap(img, position.left, position.top, &paint);
1171
        else    // Scale to fit
1172
        {
1173
            SkRect rect = SkRect::MakeXYWH(position.left, position.top, position.width, position.height);
1174
            sk_sp<SkImage> im = SkImage::MakeFromBitmap(img);
1175
            can.drawImageRect(im, rect, &paint);
1176
        }
4 andreas 1177
    }
1178
    else if (!sr[instance].bm.empty())
1179
    {
20 andreas 1180
        MSG_TRACE("Drawing normal image ...");
4 andreas 1181
        map<int, IMAGE_t>::iterator iterImages = mImages.find(sr[instance].number);
6 andreas 1182
        SkBitmap image = iterImages->second.imageBm;
4 andreas 1183
 
6 andreas 1184
        if (image.empty())
4 andreas 1185
        {
1186
            MSG_ERROR("Error creating the image \"" << sr[instance].bm << "\"!");
1187
            TError::setError();
1188
            return false;
1189
        }
1190
 
1191
        POSITION_t position = calcImagePosition(sr[instance].bm_width, sr[instance].bm_height, SC_BITMAP, instance);
1192
 
1193
        if (!position.valid)
1194
        {
1195
            MSG_ERROR("Error calculating the position of the image for button number " << bi);
1196
            TError::setError();
1197
            return false;
1198
        }
1199
 
10 andreas 1200
        MSG_DEBUG("Putting bitmap on top of image ...");
1201
        SkPaint paint;
1202
        paint.setBlendMode(SkBlendMode::kSrc);
1203
        SkCanvas can(*bm, SkSurfaceProps(1, kUnknown_SkPixelGeometry));
21 andreas 1204
 
1205
        if (sr[instance].sb == 0)
1206
            can.drawBitmap(image, position.left, position.top, &paint);
1207
        else    // Scale to fit
1208
        {
1209
            SkRect rect = SkRect::MakeXYWH(position.left, position.top, position.width, position.height);
1210
            sk_sp<SkImage> im = SkImage::MakeFromBitmap(image);
1211
            can.drawImageRect(im, rect, &paint);
1212
        }
6 andreas 1213
    }
8 andreas 1214
    else
6 andreas 1215
    {
8 andreas 1216
        MSG_DEBUG("No bitmap defined.");
1217
    }
4 andreas 1218
 
8 andreas 1219
    return true;
1220
}
4 andreas 1221
 
21 andreas 1222
bool TButton::buttonDynamic(SkBitmap* bm, int instance)
1223
{
1224
    DECL_TRACER("TButton::buttonDynamic(SkBitmap* bm, int instance)");
1225
 
1226
    if (!gPrjResources)
1227
    {
1228
        MSG_ERROR("Internal error: Global resource class not initialized!");
1229
        return false;
1230
    }
1231
 
1232
    if (!sr[instance].dynamic)
1233
    {
1234
        MSG_WARNING("Button " << bi << ": \"" << na << "\" is not for remote image!");
1235
        return false;
1236
    }
1237
 
1238
    RESOURCE_T resource = gPrjResources->findResource(1, sr[instance].bm);
1239
 
1240
    if (resource.protocol.empty())
1241
    {
1242
        MSG_ERROR("Resource " << sr[instance].bm << " not found!");
1243
        return false;
1244
    }
1245
 
1246
    string path = resource.path;
1247
 
1248
    if (!resource.file.empty())
1249
        path += "/" + resource.file;
1250
 
1251
    string url = gPrjResources->makeURL(toLower(resource.protocol), resource.host, 0, path);
1252
 
1253
    try
1254
    {
1255
        MSG_TRACE("Starting thread for loading a dynamic image ...");
1256
        mThrRes = std::thread([=] { this->funcResource(&resource, url, bm, instance); });
1257
        MSG_TRACE("Thread started. Detaching ...");
1258
        mThrRes.detach();
1259
        MSG_TRACE("Thread is running and detached.");
1260
    }
1261
    catch (std::exception& e)
1262
    {
28 andreas 1263
        MSG_ERROR("Error starting the resource thread: " << e.what());
21 andreas 1264
    }
1265
 
1266
    return true;
1267
}
1268
 
1269
void TButton::funcResource(const RESOURCE_T* resource, const std::string& url, SkBitmap* bm, int instance)
1270
{
1271
    DECL_TRACER("TButton::funcResource(RESOURCE_T* resource, std::string& url, SkBitmap* bm, int instance)");
1272
 
1273
    if (resource->refresh > 0 && !resource->dynamo)      // Periodically refreshing image?
1274
    {
1275
        MSG_DEBUG("Retrieving periodicaly refreshed image");
1276
 
1277
        ulong parent = (mHandle >> 16) & 0x0000ffff;
1278
 
1279
        if (!mHandle || !parent || bi <= 1)
1280
        {
1281
            MSG_ERROR("Invalid button. Can't make a dynamo image!");
1282
            return;
1283
        }
1284
 
1285
        THR_REFRESH_t *thref = _findResource(mHandle, parent, bi);
1286
        TImageRefresh *mImageRefresh = nullptr;
1287
 
1288
        if (!thref)
1289
        {
1290
            MSG_DEBUG("Creating a new refresh thread");
1291
            mImageRefresh = new TImageRefresh();
1292
            mImageRefresh->registerCallback(bind(&TButton::_imageRefresh, this, std::placeholders::_1));
1293
            mImageRefresh->setInterval(std::chrono::seconds(resource->refresh));
1294
            mImageRefresh->setUsername(resource->user);
1295
            mImageRefresh->setPassword(resource->password);
1296
 
1297
            if (resource->preserve)
1298
                mImageRefresh->setRunOnce();
1299
 
1300
            _addResource(mImageRefresh, mHandle, parent, bi);
1301
        }
1302
        else
1303
        {
1304
            mImageRefresh = thref->mImageRefresh;
1305
 
1306
            if (!mImageRefresh)
1307
            {
1308
                MSG_ERROR("Error creating a new refresh class!");
1309
                return;
1310
            }
1311
 
1312
            mImageRefresh->setInterval(std::chrono::seconds(resource->refresh));
1313
            mImageRefresh->setUsername(resource->user);
1314
            mImageRefresh->setPassword(resource->password);
1315
 
1316
            if (resource->preserve)
1317
                mImageRefresh->setRunOnce();
1318
        }
1319
 
1320
        if (!mImageRefresh->isRunning())
1321
        {
1322
            MSG_DEBUG("Starting a refresh thread.");
1323
            mImageRefresh->run(url);
1324
        }
1325
    }
1326
    else if (resource->refresh == 0 && !resource->dynamo)
1327
    {
1328
        MSG_DEBUG("Retrieving single image");
1329
 
1330
        try
1331
        {
1332
            char *content = nullptr;
1333
            size_t length = 0, contentlen = 0;
1334
 
1335
            if ((content = gPrjResources->tcall(&length, url, resource->user, resource->password)) == nullptr)
1336
                return;
1337
 
1338
            contentlen = gPrjResources->getContentSize();
1339
            MSG_DEBUG("Loaded " << contentlen << " bytes:");
1340
            sk_sp<SkData> data = SkData::MakeWithCopy(content, contentlen);
1341
 
1342
            if (!data)
1343
            {
1344
                MSG_ERROR("Error making image data!");
1345
                return;
1346
            }
1347
 
1348
            SkBitmap image;
1349
 
1350
            if (!DecodeDataToBitmap(data, &image))
1351
            {
1352
                MSG_ERROR("Error creating an image!");
1353
                return;
1354
            }
1355
 
1356
            loadImage(bm, image, instance);
1357
            return;
1358
        }
1359
        catch (std::exception& e)
1360
        {
1361
            MSG_ERROR(e.what());
1362
            return;
1363
        }
1364
    }
1365
    else
1366
    {
1367
        MSG_DEBUG("Retrieving a video");
1368
 
1369
        if (_playVideo && !prg_stopped)
1370
        {
1371
            ulong parent = (mHandle >> 16) & 0x0000ffff;
1372
            _playVideo(mHandle, parent, lt, tp, wt, ht, url, resource->user, resource->password);
1373
        }
1374
    }
1375
}
1376
 
23 andreas 1377
void TButton::funcBattery()
1378
{
1379
    DECL_TRACER("TButton::funcBattery()");
1380
#ifdef __ANDROID__
1381
    if(!QAndroidJniObject::isClassAvailable("org/qtproject/theosys/BatteryState"))
1382
    {
1383
        MSG_ERROR("Missing Java class \"BatteryState\"!");
1384
        return;
1385
    }
1386
 
1387
    int level = QAndroidJniObject::callStaticMethod<jint>("org/qtproject/theosys/BatteryState", "getLevel", "()I");
1388
    bool charge = QAndroidJniObject::callStaticMethod<jboolean>("org/qtproject/theosys/BatteryState", "isCharging", "()Z");
1389
    MSG_DEBUG("Battery level: " << level << ", Charging: " << (charge ? "true" : "false"));
1390
    int iLevel = (int)((float)(rh - rl) / 100.0 * (float)level);
1391
#else
1392
    // TODO: Add code to read battery from a laptop or the like
29 andreas 1393
    int iLevel = 50;
24 andreas 1394
    int level = 0;
1395
    bool charge = false;
23 andreas 1396
#endif
1397
    // Battery level is always a bargraph
1398
    if (ad == 242)
1399
    {
1400
        mEnabled = !charge;
1401
 
1402
        if (!mEnabled && visible)
1403
            hide(true);
1404
        else if (mEnabled)
1405
        {
1406
            visible = true;
1407
            drawBargraph(mActInstance, iLevel, visible);
1408
        }
1409
    }
1410
    else if (ad == 234)
1411
    {
1412
        mEnabled = charge;
1413
 
1414
        if (!mEnabled && visible)
1415
            hide(true);
1416
        else if (mEnabled)
1417
        {
1418
            visible = true;
1419
            drawBargraph(mActInstance, iLevel, visible);
1420
        }
1421
    }
1422
}
1423
 
21 andreas 1424
bool TButton::loadImage(SkBitmap* bm, SkBitmap& image, int instance)
1425
{
1426
    DECL_TRACER("TButton::loadImage(SkBitmap* bm, SkBitmap& image, int instance)");
1427
 
1428
    SkImageInfo info = image.info();
1429
    POSITION_t position = calcImagePosition(info.width(), info.height(), SC_BITMAP, instance);
1430
 
1431
    if (!position.valid)
1432
    {
1433
        MSG_ERROR("Error calculating the position of the image for button number " << bi);
1434
        return false;
1435
    }
1436
 
1437
    MSG_DEBUG("Putting bitmap on top of image ...");
1438
    SkPaint paint;
1439
    paint.setBlendMode(SkBlendMode::kSrc);
1440
 
1441
    SkCanvas can(*bm, SkSurfaceProps(1, kUnknown_SkPixelGeometry));
1442
 
1443
    if (sr[instance].sb == 0)
1444
        can.drawBitmap(image, position.left, position.top, &paint);
1445
    else    // Scale to fit
1446
    {
1447
        SkRect rect = SkRect::MakeXYWH(position.left, position.top, position.width, position.height);
1448
        sk_sp<SkImage> im = SkImage::MakeFromBitmap(image);
1449
        can.drawImageRect(im, rect, &paint);
1450
    }
1451
 
1452
    return true;
1453
}
1454
 
23 andreas 1455
bool TButton::barLevel(SkBitmap* bm, int, int level)
15 andreas 1456
{
1457
    DECL_TRACER("TButton::barLevel(SkBitmap* bm, int instance, int level)");
1458
 
30 andreas 1459
    if (!sr[0].mi.empty()  && sr[0].bs.empty() && !sr[1].bm.empty())       // Chameleon image?
15 andreas 1460
    {
1461
        MSG_DEBUG("Chameleon image ...");
1462
        map<int, IMAGE_t>::iterator iterImages1 = mImages.find(sr[0].number);
1463
        map<int, IMAGE_t>::iterator iterImages2 = mImages.find(sr[1].number);
1464
        SkBitmap imgRed(iterImages1->second.imageMi);
1465
        SkBitmap imgMask(iterImages2->second.imageBm);
1466
 
20 andreas 1467
        SkBitmap img;
1468
        SkPixmap pixmapRed = imgRed.pixmap();
1469
        SkPixmap pixmapMask;
15 andreas 1470
 
20 andreas 1471
        if (!imgMask.empty())
1472
            pixmapMask = imgMask.pixmap();
15 andreas 1473
 
20 andreas 1474
        int width = sr[0].mi_width;
1475
        int height = sr[0].mi_height;
1476
        int startX = 0;
1477
        int startY = 0;
15 andreas 1478
 
20 andreas 1479
        if (dr.compare("horizontal") == 0)
1480
            width = (int)((double)width / ((double)rh - (double)rl) * (double)level);
1481
        else
1482
            height = (int)((double)height / ((double)rh - (double)rl) * (double)level);
1483
 
1484
        if ((!ri && dr.compare("horizontal") != 0) ||   // range inverted?
1485
            (ri && dr.compare("horizontal") == 0))
1486
        {
15 andreas 1487
            if (dr.compare("horizontal") == 0)
20 andreas 1488
                startX = sr[0].mi_width - width;
15 andreas 1489
            else
20 andreas 1490
                startY = sr[0].mi_height - height;
15 andreas 1491
 
20 andreas 1492
            width = sr[0].mi_width;
1493
            height = sr[0].mi_height;
1494
        }
15 andreas 1495
 
20 andreas 1496
        img.allocPixels(SkImageInfo::MakeN32Premul(sr[0].mi_width, sr[0].mi_height));
1497
        SkCanvas canvas(img);
1498
        SkColor col1 = TColor::getSkiaColor(sr[1].cf);
1499
        SkColor col2 = TColor::getSkiaColor(sr[1].cb);
15 andreas 1500
 
20 andreas 1501
        for (int ix = 0; ix < sr[0].mi_width; ix++)
1502
        {
1503
            for (int iy = 0; iy < sr[0].mi_height; iy++)
1504
            {
1505
                SkPaint paint;
1506
                SkColor pixel;
15 andreas 1507
 
20 andreas 1508
                if (ix >= startX && ix < width && iy >= startY && iy < height)
15 andreas 1509
                {
20 andreas 1510
                    SkColor pixelRed = pixmapRed.getColor(ix, iy);
1511
                    SkColor pixelMask;
15 andreas 1512
 
20 andreas 1513
                    if (!imgMask.empty())
1514
                        pixelMask = pixmapMask.getColor(ix, iy);
15 andreas 1515
                    else
20 andreas 1516
                        pixelMask = SK_ColorWHITE;
15 andreas 1517
 
20 andreas 1518
                    pixel = baseColor(pixelRed, pixelMask, col1, col2);
15 andreas 1519
                }
20 andreas 1520
                else
1521
                    pixel = SK_ColorTRANSPARENT;
15 andreas 1522
 
20 andreas 1523
                paint.setColor(pixel);
1524
                canvas.drawPoint(ix, iy, paint);
15 andreas 1525
            }
20 andreas 1526
        }
15 andreas 1527
 
20 andreas 1528
        if (img.empty())
1529
        {
1530
            MSG_ERROR("Error creating the cameleon image \"" << sr[0].mi << "\" / \"" << sr[0].bm << "\"!");
1531
            TError::setError();
1532
            return false;
1533
        }
15 andreas 1534
 
20 andreas 1535
        MSG_DEBUG("Putting image together ...");
1536
        SkCanvas ctx(img, SkSurfaceProps(1, kUnknown_SkPixelGeometry));
1537
        SkPaint paint;
1538
        paint.setBlendMode(SkBlendMode::kSrcATop);
1539
        ctx.drawBitmap(imgMask, 0, 0, &paint);
15 andreas 1540
 
20 andreas 1541
        POSITION_t position = calcImagePosition(sr[0].mi_width, sr[0].mi_height, SC_BITMAP, 0);
15 andreas 1542
 
20 andreas 1543
        if (!position.valid)
1544
        {
1545
            MSG_ERROR("Error calculating the position of the image for button number " << bi << ": " << na);
1546
            TError::setError();
1547
            return false;
15 andreas 1548
        }
1549
 
20 andreas 1550
        SkCanvas can(*bm, SkSurfaceProps(1, kUnknown_SkPixelGeometry));
1551
        paint.setBlendMode(SkBlendMode::kSrc);
1552
        can.drawBitmap(img, position.left, position.top, &paint);
15 andreas 1553
    }
1554
    else if (!sr[1].bm.empty())
1555
    {
23 andreas 1556
        MSG_TRACE("Drawing normal image ...");
1557
        map<int, IMAGE_t>::iterator iterImages1 = mImages.find(sr[0].number);
1558
        map<int, IMAGE_t>::iterator iterImages2 = mImages.find(sr[1].number);
1559
        SkBitmap image1 = iterImages1->second.imageBm;
1560
        SkBitmap image2 = iterImages2->second.imageBm;
15 andreas 1561
 
23 andreas 1562
        if (image1.empty())
15 andreas 1563
        {
23 andreas 1564
            MSG_ERROR("Error creating the image \"" << sr[0].bm << "\"!");
15 andreas 1565
            TError::setError();
1566
            return false;
1567
        }
1568
 
23 andreas 1569
        if (image2.empty())
1570
        {
1571
            MSG_ERROR("Error creating the image \"" << sr[1].bm << "\"!");
1572
            TError::setError();
1573
            return false;
1574
        }
1575
 
1576
        int width = sr[1].bm_width;
1577
        int height = sr[1].bm_height;
15 andreas 1578
        int startX = 0;
1579
        int startY = 0;
1580
 
1581
        if (dr.compare("horizontal") == 0)
1582
            width = (int)((double)width / ((double)rh - (double)rl) * (double)level);
1583
        else
1584
            height = (int)((double)height / ((double)rh - (double)rl) * (double)level);
1585
 
1586
        if ((!ri && dr.compare("horizontal") != 0) ||   // range inverted?
1587
            (ri && dr.compare("horizontal") == 0))
1588
        {
1589
            if (dr.compare("horizontal") == 0)
1590
                startX = sr[0].mi_width - width;
1591
            else
1592
                startY = sr[0].mi_height - height;
1593
 
1594
            width = sr[0].mi_width;
1595
            height = sr[0].mi_height;
1596
        }
1597
 
28 andreas 1598
        MSG_DEBUG("dr=" << dr << ", startX=" << startX << ", startY=" << startY << ", width=" << width << ", height=" << height << ", level=" << level);
23 andreas 1599
        MSG_TRACE("Creating bargraph ...");
1600
        SkBitmap img;
1601
        img.allocPixels(SkImageInfo::MakeN32Premul(sr[1].bm_width, sr[1].bm_height));
1602
        SkCanvas canvas(img);
1603
 
1604
        for (int ix = 0; ix < sr[1].bm_width; ix++)
1605
        {
1606
            for (int iy = 0; iy < sr[1].bm_height; iy++)
1607
            {
1608
                SkPaint paint;
1609
                SkColor pixel;
1610
 
1611
                if (ix >= startX && ix < width && iy >= startY && iy < height)
1612
                    pixel = image2.getColor(ix, iy);
1613
                else
1614
                    pixel = SK_ColorTRANSPARENT;
1615
 
1616
                paint.setColor(pixel);
1617
                canvas.drawPoint(ix, iy, paint);
1618
            }
1619
        }
1620
 
1621
        MSG_TRACE("Putting bitmap on top of image ...");
15 andreas 1622
        SkPaint paint;
23 andreas 1623
        paint.setBlendMode(SkBlendMode::kSrcATop);
1624
        SkCanvas ctx(image1, SkSurfaceProps(1, kUnknown_SkPixelGeometry));
1625
        ctx.drawBitmap(img, 0, 0, &paint);
15 andreas 1626
        paint.setBlendMode(SkBlendMode::kSrc);
1627
        SkCanvas can(*bm, SkSurfaceProps(1, kUnknown_SkPixelGeometry));
23 andreas 1628
        can.drawBitmap(image1, 0, 0, &paint);
15 andreas 1629
    }
1630
    else
1631
    {
1632
        MSG_DEBUG("No bitmap defined.");
1633
        int width = sr[1].mi_width;
1634
        int height = sr[1].mi_height;
1635
        int startX = 0;
1636
        int startY = 0;
1637
 
1638
        if (dr.compare("horizontal") == 0)
1639
            width = (int)((double)width / ((double)rh - (double)rl) * (double)level);
1640
        else
1641
            height = (int)((double)height / ((double)rh - (double)rl) * (double)level);
1642
 
1643
        if ((!ri && dr.compare("horizontal") != 0) ||   // range inverted?
1644
            (ri && dr.compare("horizontal") == 0))
1645
        {
1646
            if (dr.compare("horizontal") == 0)
1647
                startX = sr[0].mi_width - width;
1648
            else
1649
                startY = sr[0].mi_height - height;
1650
 
1651
            width = sr[0].mi_width;
1652
            height = sr[0].mi_height;
1653
        }
1654
 
23 andreas 1655
        MSG_DEBUG("Drawing graphics on top of image ...");
15 andreas 1656
        SkPaint paint;
1657
        paint.setBlendMode(SkBlendMode::kSrc);
1658
        SkCanvas can(*bm, SkSurfaceProps(1, kUnknown_SkPixelGeometry));
20 andreas 1659
        paint.setStyle(SkPaint::kFill_Style);
1660
        paint.setAntiAlias(true);
1661
        paint.setStrokeWidth(4);
1662
        paint.setColor(TColor::getSkiaColor(sr[1].cb));
1663
 
15 andreas 1664
        SkRect dst;
1665
        dst.setXYWH(startX, startY, width, height);
20 andreas 1666
        can.drawRect(dst, paint);
15 andreas 1667
    }
1668
 
1669
    return true;
1670
}
1671
 
8 andreas 1672
bool TButton::buttonIcon(SkBitmap* bm, int instance)
1673
{
1674
    DECL_TRACER("TButton::buttonIcon(SkBitmap* bm, int instance)");
1675
 
1676
    if (sr[instance].ii <= 0)
1677
    {
1678
        MSG_TRACE("No icon defined!");
1679
        return true;
7 andreas 1680
    }
1681
 
8 andreas 1682
    MSG_DEBUG("Drawing an icon ...");
1683
 
1684
    if (!gIcons)
1685
    {
1686
        gIcons = new TIcons();
1687
 
1688
        if (TError::isError())
1689
        {
1690
            MSG_ERROR("Error initializing icons!");
1691
            return false;
1692
        }
1693
    }
1694
 
1695
    string file = gIcons->getFile(sr[instance].ii);
1696
    MSG_DEBUG("Loading icon file " << file);
1697
    sk_sp<SkData> image;
1698
    SkBitmap icon;
1699
 
1700
    if (!(image = readImage(file)))
1701
        return false;
1702
 
1703
    DecodeDataToBitmap(image, &icon);
1704
 
1705
    if (icon.empty())
1706
    {
1707
        MSG_WARNING("Could not create an icon for element " << sr[instance].ii << " on button " << bi << " (" << na << ")");
1708
        return false;
1709
    }
1710
 
1711
    SkImageInfo info = icon.info();
1712
    POSITION_t position = calcImagePosition(icon.width(), icon.height(), SC_ICON, instance);
1713
 
1714
    if (!position.valid)
1715
    {
1716
        MSG_ERROR("Error calculating the position of the image for button number " << bi);
1717
        TError::setError();
1718
        return false;
1719
    }
1720
 
10 andreas 1721
    MSG_DEBUG("Putting Icon on top of bitmap ...");
1722
    SkPaint paint;
1723
    paint.setBlendMode(SkBlendMode::kSrcOver);
1724
    SkCanvas can(*bm, SkSurfaceProps(1, kUnknown_SkPixelGeometry));
8 andreas 1725
 
17 andreas 1726
    if (position.overflow)
1727
    {
1728
        SkIRect irect;
1729
        SkRect bdst;
1730
        SkBitmap dst;
1731
        int left = (position.left >= 0) ? 0 : position.left * -1;
1732
        int top = (position.top >= 0) ? 0 : position.top * -1;
1733
        int width = std::min(wt, info.width());
1734
        int height = std::min(ht, info.height());
1735
        irect.setXYWH(left, top, width, height);
1736
        bm->getBounds(&bdst);
1737
        can.drawBitmapRect(icon, irect, bdst, &paint);
1738
    }
1739
    else
1740
        can.drawBitmap(icon, position.left, position.top, &paint);
1741
 
8 andreas 1742
    return true;
1743
}
1744
 
1745
bool TButton::buttonText(SkBitmap* bm, int instance)
1746
{
1747
    DECL_TRACER("TButton::buttonText(SkBitmap* bm, int instance)");
1748
 
7 andreas 1749
    if (!sr[instance].te.empty() && _setText && mFonts)       // Is there a text to display?
1750
    {
8 andreas 1751
        MSG_DEBUG("Searching for font number " << sr[instance].fi << " with text " << sr[instance].te);
7 andreas 1752
        FONT_T font = mFonts->getFont(sr[instance].fi);
1753
 
1754
        if (!font.file.empty())
6 andreas 1755
        {
10 andreas 1756
            SkCanvas canvas(*bm, SkSurfaceProps(1, kUnknown_SkPixelGeometry));
7 andreas 1757
            sk_sp<SkTypeface> typeFace = mFonts->getTypeFace(sr[instance].fi);
1758
 
1759
            if (!typeFace)
1760
            {
1761
                MSG_ERROR("Error creating type face " << font.fullName);
1762
                TError::setError();
1763
                return false;
1764
            }
1765
 
19 andreas 1766
            SkScalar fontSizePt = ((SkScalar)font.size * 1.322);
8 andreas 1767
            SkFont skFont(typeFace, fontSizePt);
19 andreas 1768
 
8 andreas 1769
            SkPaint paint;
1770
            paint.setAntiAlias(true);
1771
            SkColor color = TColor::getSkiaColor(sr[instance].ct);
1772
            paint.setColor(color);
1773
            paint.setStyle(SkPaint::kFill_Style);
1774
 
1775
            SkFontMetrics metrics;
1776
            skFont.getMetrics(&metrics);
1777
            int lines = numberLines(sr[instance].te);
1778
            MSG_DEBUG("Found " << lines << " lines.");
1779
 
1780
            if (lines > 1 || sr[instance].ww)
7 andreas 1781
            {
8 andreas 1782
                vector<string> textLines;
1783
 
1784
                if (!sr[instance].ww)
1785
                    textLines = splitLine(sr[instance].te);
1786
                else
1787
                {
1788
                    textLines = splitLine(sr[instance].te, wt, ht, skFont, paint);
1789
                    lines = textLines.size();
1790
                }
1791
 
1792
                vector<string>::iterator iter;
1793
                SkScalar Y = 1.0;
1794
 
1795
                for (iter = textLines.begin(); iter != textLines.end(); iter++)
1796
                {
19 andreas 1797
                    sk_sp<SkTextBlob> blob = SkTextBlob::MakeFromString(iter->c_str(), skFont);
8 andreas 1798
                    SkRect rect;
1799
                    skFont.measureText(iter->c_str(), iter->length(), SkTextEncoding::kUTF8, &rect, &paint);
1800
                    MSG_DEBUG("Triing to print line: " << *iter);
1801
                    POSITION_t position = calcImagePosition(rect.width(), rect.height()*lines, SC_TEXT, instance);
1802
 
1803
                    if (!position.valid)
1804
                    {
1805
                        MSG_ERROR("Error calculating the text position!");
1806
                        TError::setError();
1807
                        return false;
1808
                    }
1809
 
10 andreas 1810
                    SkScalar startX = (SkScalar)position.left;
1811
                    SkScalar startY = (SkScalar)position.top;
1812
 
1813
                    if (Y > 1.0)
1814
                        startY += metrics.fCapHeight; //  metrics.fDescent + metrics.fLeading;
1815
                    else
1816
                        startY += metrics.fCapHeight + metrics.fLeading; //(metrics.fAscent * -1.0);
1817
 
1818
                    MSG_DEBUG("fAvgCharWidth: " << metrics.fAvgCharWidth);
1819
                    MSG_DEBUG("fCapHeight: " << metrics.fCapHeight);
1820
                    MSG_DEBUG("fAscent:    " << metrics.fAscent);
1821
                    MSG_DEBUG("fDescent:   " << metrics.fDescent);
1822
                    MSG_DEBUG("fLeading:   " << metrics.fLeading);
1823
                    MSG_DEBUG("fXHeight:   " << metrics.fXHeight);
1824
                    MSG_DEBUG("x=" << startX << ", y=" << startY*Y);
19 andreas 1825
                    canvas.drawTextBlob(blob, startX, startY, paint);
8 andreas 1826
                    Y += 1.0;
1827
                }
7 andreas 1828
            }
8 andreas 1829
            else    // single line
1830
            {
19 andreas 1831
                sk_sp<SkTextBlob> blob = SkTextBlob::MakeFromString(sr[instance].te.c_str(), skFont);
8 andreas 1832
                SkRect rect;
1833
                skFont.measureText(sr[instance].te.c_str(), sr[instance].te.length(), SkTextEncoding::kUTF8, &rect, &paint);
1834
                POSITION_t position = calcImagePosition(rect.width(), (rect.height() * (float)lines), SC_TEXT, instance);
7 andreas 1835
 
8 andreas 1836
                if (!position.valid)
1837
                {
1838
                    MSG_ERROR("Error calculating the text position!");
1839
                    TError::setError();
1840
                    return false;
1841
                }
1842
 
1843
                MSG_DEBUG("Printing line " << sr[instance].te);
10 andreas 1844
                SkScalar startX = (SkScalar)position.left;
1845
                SkScalar startY = (SkScalar)position.top + metrics.fCapHeight; // + metrics.fLeading; // (metrics.fAscent * -1.0);
1846
 
1847
                MSG_DEBUG("fAvgCharWidth: " << metrics.fAvgCharWidth);
1848
                MSG_DEBUG("fCapHeight: " << metrics.fCapHeight);
1849
                MSG_DEBUG("fAscent:    " << metrics.fAscent);
1850
                MSG_DEBUG("fDescent:   " << metrics.fDescent);
1851
                MSG_DEBUG("fLeading:   " << metrics.fLeading);
1852
                MSG_DEBUG("fXHeight:   " << metrics.fXHeight);
1853
                MSG_DEBUG("x=" << startX << ", y=" << startY);
19 andreas 1854
                canvas.drawTextBlob(blob, startX, startY, paint);
8 andreas 1855
            }
1856
        }
1857
    }
1858
 
1859
    return true;
1860
}
1861
 
1862
bool TButton::buttonBorder(SkBitmap* bm, int instance)
1863
{
1864
    DECL_TRACER("TButton::buttonBorder(SkBitmap* bm, int instance)");
1865
 
1866
    if (sr[instance].bs.empty())
1867
    {
1868
        MSG_DEBUG("No border defined.");
1869
        return true;
1870
    }
1871
 
1872
    int i = 0;
1873
 
1874
    while (sysBorders[i].id)
1875
    {
1876
        if (sr[instance].bs.compare(sysBorders[i].name) == 0)
1877
        {
1878
            MSG_DEBUG("Border " << sysBorders[i].name << " found.");
10 andreas 1879
            SkCanvas canvas(*bm, SkSurfaceProps(1, kUnknown_SkPixelGeometry));
7 andreas 1880
            SkPaint paint;
8 andreas 1881
            SkColor color = TColor::getSkiaColor(sr[instance].cb);
7 andreas 1882
 
8 andreas 1883
            paint.setColor(color);
1884
            paint.setBlendMode(SkBlendMode::kSrc);
1885
            paint.setStyle(SkPaint::kStroke_Style);
1886
            SkRRect outher, inner;
1887
 
1888
            switch (sysBorders[i].id)
1889
            {
1890
                case 1: // Single Frame
1891
                case 2: // Double Frame
1892
                case 3: // Quad Frame
1893
                    paint.setStrokeWidth(sysBorders[i].width);
1894
                    canvas.drawRect(calcRect(wt, ht, sysBorders[i].width), paint);
1895
                break;
1896
 
1897
                // FIXME: Picture frame is missing
1898
 
1899
                case 5: // Circle 15
1900
                    paint.setStrokeWidth(sysBorders[i].width);
1901
                    canvas.drawRoundRect(calcRect(wt, ht, sysBorders[i].width), 7.0, 7.0, paint);
1902
                    outher = SkRRect::MakeRect({0, 0, (SkScalar)wt, (SkScalar)ht});
1903
                    inner = SkRRect::MakeRectXY({0, 0, (SkScalar)wt, (SkScalar)ht}, 7.0, 7.0);
1904
                    paint.setColor(SK_ColorTRANSPARENT);
1905
                    canvas.drawDRRect(outher, inner, paint);
1906
                break;
1907
 
1908
                case 6: // Circle 25
1909
                    paint.setStrokeWidth(sysBorders[i].width);
1910
                    canvas.drawRoundRect(calcRect(wt, ht, sysBorders[i].width), 14.0, 14.0, paint);
1911
                    outher = SkRRect::MakeRect({0, 0, (SkScalar)wt, (SkScalar)ht});
1912
                    inner = SkRRect::MakeRectXY({0, 0, (SkScalar)wt, (SkScalar)ht}, 14.0, 14.0);
1913
                    paint.setColor(SK_ColorTRANSPARENT);
1914
                    canvas.drawDRRect(outher, inner, paint);
1915
                break;
1916
 
1917
                case 7: // Circle 35
1918
                    paint.setStrokeWidth(sysBorders[i].width);
1919
                    canvas.drawRoundRect(calcRect(wt, ht, sysBorders[i].width), 21.0, 21.0, paint);
1920
                    outher = SkRRect::MakeRect({0, 0, (SkScalar)wt, (SkScalar)ht});
1921
                    inner = SkRRect::MakeRectXY({0, 0, (SkScalar)wt, (SkScalar)ht}, 21.0, 21.0);
1922
                    paint.setColor(SK_ColorTRANSPARENT);
1923
                    canvas.drawDRRect(outher, inner, paint);
1924
                break;
1925
 
1926
                case 8: // Circle 45
1927
                    paint.setStrokeWidth(sysBorders[i].width);
1928
                    canvas.drawRoundRect(calcRect(wt, ht, sysBorders[i].width), 28.0, 28.0, paint);
1929
                    outher = SkRRect::MakeRect({0, 0, (SkScalar)wt, (SkScalar)ht});
1930
                    inner = SkRRect::MakeRectXY({0, 0, (SkScalar)wt, (SkScalar)ht}, 28.0, 28.0);
1931
                    paint.setColor(SK_ColorTRANSPARENT);
1932
                    canvas.drawDRRect(outher, inner, paint);
1933
                break;
1934
 
1935
                case 9: // Circle 55
1936
                    paint.setStrokeWidth(sysBorders[i].width);
1937
                    canvas.drawRoundRect(calcRect(wt, ht, sysBorders[i].width), 35.0, 35.0, paint);
1938
                    outher = SkRRect::MakeRect({0, 0, (SkScalar)wt, (SkScalar)ht});
1939
                    inner = SkRRect::MakeRectXY({0, 0, (SkScalar)wt, (SkScalar)ht}, 35.0, 35.0);
1940
                    paint.setColor(SK_ColorTRANSPARENT);
1941
                    canvas.drawDRRect(outher, inner, paint);
1942
                break;
1943
 
1944
                case 10: // Circle 65
1945
                    paint.setStrokeWidth(sysBorders[i].width);
1946
                    canvas.drawRoundRect(calcRect(wt, ht, sysBorders[i].width), 42.0, 42.0, paint);
1947
                    outher = SkRRect::MakeRect({0, 0, (SkScalar)wt, (SkScalar)ht});
1948
                    inner = SkRRect::MakeRectXY({0, 0, (SkScalar)wt, (SkScalar)ht}, 42.0, 42.0);
1949
                    paint.setColor(SK_ColorTRANSPARENT);
1950
                    canvas.drawDRRect(outher, inner, paint);
1951
                break;
1952
 
1953
                case 11: // Circle 75
1954
                    paint.setStrokeWidth(sysBorders[i].width);
1955
                    canvas.drawRoundRect(calcRect(wt, ht, sysBorders[i].width), 49.0, 49.0, paint);
1956
                    outher = SkRRect::MakeRect({0, 0, (SkScalar)wt, (SkScalar)ht});
1957
                    inner = SkRRect::MakeRectXY({0, 0, (SkScalar)wt, (SkScalar)ht}, 49.0, 49.0);
1958
                    paint.setColor(SK_ColorTRANSPARENT);
1959
                    canvas.drawDRRect(outher, inner, paint);
1960
                break;
1961
 
1962
                case 12: // Circle 85
1963
                    paint.setStrokeWidth(sysBorders[i].width);
1964
                    canvas.drawRoundRect(calcRect(wt, ht, sysBorders[i].width), 56.0, 56.0, paint);
1965
                    outher = SkRRect::MakeRect({0, 0, (SkScalar)wt, (SkScalar)ht});
1966
                    inner = SkRRect::MakeRectXY({0, 0, (SkScalar)wt, (SkScalar)ht}, 56.0, 56.0);
1967
                    paint.setColor(SK_ColorTRANSPARENT);
1968
                    canvas.drawDRRect(outher, inner, paint);
1969
                break;
1970
 
1971
                case 13: // Circle 95
1972
                    paint.setStrokeWidth(sysBorders[i].width);
1973
                    canvas.drawRoundRect(calcRect(wt, ht, sysBorders[i].width), 63.0, 63.0, paint);
1974
                    outher = SkRRect::MakeRect({0, 0, (SkScalar)wt, (SkScalar)ht});
1975
                    inner = SkRRect::MakeRectXY({0, 0, (SkScalar)wt, (SkScalar)ht}, 63.0, 63.0);
1976
                    paint.setColor(SK_ColorTRANSPARENT);
1977
                    canvas.drawDRRect(outher, inner, paint);
1978
                break;
1979
 
1980
                case 14: // Circle 105
1981
                    paint.setStrokeWidth(sysBorders[i].width);
1982
                    canvas.drawRoundRect(calcRect(wt, ht, sysBorders[i].width), 70.0, 70.0, paint);
1983
                    outher = SkRRect::MakeRect({0, 0, (SkScalar)wt, (SkScalar)ht});
1984
                    inner = SkRRect::MakeRectXY({0, 0, (SkScalar)wt, (SkScalar)ht}, 70.0, 70.0);
1985
                    paint.setColor(SK_ColorTRANSPARENT);
1986
                    canvas.drawDRRect(outher, inner, paint);
1987
                break;
1988
 
1989
                case 15: // Circle 115
1990
                    paint.setStrokeWidth(sysBorders[i].width);
1991
                    canvas.drawRoundRect(calcRect(wt, ht, sysBorders[i].width), 77.0, 77.0, paint);
1992
                    outher = SkRRect::MakeRect({0, 0, (SkScalar)wt, (SkScalar)ht});
1993
                    inner = SkRRect::MakeRectXY({0, 0, (SkScalar)wt, (SkScalar)ht}, 77.0, 77.0);
1994
                    paint.setColor(SK_ColorTRANSPARENT);
1995
                    canvas.drawDRRect(outher, inner, paint);
1996
                break;
1997
 
1998
                case 16: // Circle 125
1999
                    paint.setStrokeWidth(sysBorders[i].width);
2000
                    canvas.drawRoundRect(calcRect(wt, ht, sysBorders[i].width), 84.0, 84.0, paint);
2001
                    outher = SkRRect::MakeRect({0, 0, (SkScalar)wt, (SkScalar)ht});
2002
                    inner = SkRRect::MakeRectXY({0, 0, (SkScalar)wt, (SkScalar)ht}, 84.0, 84.0);
2003
                    paint.setColor(SK_ColorTRANSPARENT);
2004
                    canvas.drawDRRect(outher, inner, paint);
2005
                break;
2006
            }
6 andreas 2007
        }
8 andreas 2008
 
2009
        i++;
4 andreas 2010
    }
2011
 
3 andreas 2012
    return true;
2013
}
2014
 
8 andreas 2015
int TButton::numberLines(const string& str)
2016
{
2017
    DECL_TRACER("TButton::numberLines(const string& str)");
2018
 
2019
    int lines = 1;
2020
 
2021
    for (size_t i = 0; i < str.length(); i++)
2022
    {
2023
        if (str.at(i) == '\n')
2024
            lines++;
2025
    }
2026
 
2027
    return lines;
2028
}
2029
 
2030
vector<string> TButton::splitLine(const string& str)
2031
{
2032
    DECL_TRACER("TButton::splitLine(const string& str)");
2033
 
2034
    vector<string> lines;
2035
    string sl;
2036
    size_t len = str.length();
2037
 
2038
    for (size_t i = 0; i < len; i++)
2039
    {
2040
        if (str.at(i) == '\r')  // ignore bloating byte coming from brain death windows
2041
            continue;
2042
 
2043
        if (str.at(i) == '\n')
2044
        {
2045
            lines.push_back(sl);
2046
            sl.clear();
2047
            continue;
2048
        }
2049
 
2050
        sl.append(str.substr(i, 1));
2051
    }
2052
 
2053
    if (!sl.empty())
2054
        lines.push_back(sl);
2055
 
2056
    return lines;
2057
}
2058
 
2059
vector<string> TButton::splitLine(const string& str, int width, int height, SkFont& font, SkPaint& paint)
2060
{
2061
    DECL_TRACER("TButton::splitLine(const string& str, SkFont& font)");
2062
 
2063
    SkRect rect;
2064
    size_t len = str.length();
2065
    vector<string> lines;
2066
    SkScalar lnHeight = font.getSize();
2067
    int maxLines = (int)((SkScalar)height / lnHeight);
2068
 
2069
    for (size_t i = 0; i < len; i++)
2070
    {
2071
        string part = str.substr(0, i + 1);
2072
        font.measureText(part.c_str(), part.length(), SkTextEncoding::kUTF8, &rect, &paint);
2073
 
2074
        if (rect.width() > width)
2075
        {
2076
            string ln = part.substr(0, part.length() - 1);
2077
            lines.push_back(ln);
2078
            part = part.substr(part.length() - 1);
2079
 
2080
            if (lines.size() >= (size_t)maxLines)
2081
                return lines;
2082
        }
2083
 
2084
        if ((i + 1) == len)
2085
            lines.push_back(part);
2086
    }
2087
 
2088
    return lines;
2089
}
2090
 
2091
SkRect TButton::calcRect(int width, int height, int pen)
2092
{
2093
    DECL_TRACER("TButton::calcRect(int width, int height, int pen)");
2094
    SkRect rect;
2095
 
2096
    SkScalar left = (SkScalar)pen / 2.0;
2097
    SkScalar top = (SkScalar)pen / 2.0;
2098
    SkScalar w = (SkScalar)width - (SkScalar)pen / 2.0 - left;
2099
    SkScalar h = (SkScalar)height - (SkScalar)pen / 2.0 - top;
2100
    rect.setXYWH(left, top, w, h);
2101
    return rect;
2102
}
2103
 
15 andreas 2104
void TButton::runAnimation()
8 andreas 2105
{
15 andreas 2106
    DECL_TRACER("TButton::runAnimation()");
8 andreas 2107
 
15 andreas 2108
    mAniRunning = true;
2109
    int instance = 0;
2110
    int max = (int)sr.size();
2111
    ulong tm = nu * 100 + nd * 100;
2112
 
2113
    while (mAniRunning)
2114
    {
2115
        if (!drawButton(instance))
2116
            break;
2117
 
2118
        instance++;
2119
 
2120
        if (instance >= max)
2121
            instance = 0;
2122
 
2123
        std::this_thread::sleep_for(std::chrono::milliseconds(tm));
2124
    }
2125
 
2126
    mAniRunning = false;
2127
}
2128
 
2129
bool TButton::drawButtonMultistateAni()
2130
{
2131
    DECL_TRACER("TButton::drawButtonMultistate(int instance, bool show)");
2132
 
2133
    if (mAniRunning || mThrAni.joinable())
2134
    {
23 andreas 2135
        MSG_TRACE("Animation is already running!");
15 andreas 2136
        return true;
2137
    }
2138
 
2139
    try
2140
    {
2141
        mThrAni = thread([=] { runAnimation(); });
2142
        mThrAni.detach();
2143
    }
2144
    catch (exception& e)
2145
    {
2146
        MSG_ERROR("Error starting the button animation thread: " << e.what());
2147
        return false;
2148
    }
2149
 
2150
    return true;
2151
}
2152
 
2153
bool TButton::drawButton(int instance, bool show)
2154
{
2155
    mutex_button.lock();
2156
    DECL_TRACER("TButton::drawButton(int instance, bool show)");
2157
 
8 andreas 2158
    if ((size_t)instance >= sr.size() || instance < 0)
2159
    {
2160
        MSG_ERROR("Instance " << instance << " is out of bounds!");
2161
        TError::setError();
15 andreas 2162
        mutex_button.unlock();
8 andreas 2163
        return false;
2164
    }
2165
 
26 andreas 2166
    if (!_displayButton && gPageManager)
2167
        _displayButton = gPageManager->getCallbackDB();
2168
 
14 andreas 2169
    if (!visible || instance != mActInstance || !_displayButton)
2170
    {
15 andreas 2171
        bool db = (_displayButton != nullptr);
23 andreas 2172
        MSG_DEBUG("Button " << bi << ", \"" << na << "\" at instance " << instance << " is not to draw!");
15 andreas 2173
        MSG_DEBUG("Visible: " << (visible ? "YES" : "NO") << ", Instance/actual instance: " << instance << "/" << mActInstance << ", callback: " << (db ? "PRESENT" : "N/A"));
2174
        mutex_button.unlock();
14 andreas 2175
        return true;
2176
    }
2177
 
8 andreas 2178
    ulong parent = mHandle & 0xffff0000;
2179
    getDrawOrder(sr[instance]._do, (DRAW_ORDER *)&mDOrder);
2180
 
2181
    if (TError::isError())
15 andreas 2182
    {
2183
        mutex_button.unlock();
8 andreas 2184
        return false;
15 andreas 2185
    }
8 andreas 2186
 
2187
    SkBitmap imgButton;
10 andreas 2188
    imgButton.allocN32Pixels(wt, ht);
8 andreas 2189
 
2190
    for (int i = 0; i < ORD_ELEM_COUNT; i++)
2191
    {
2192
        if (mDOrder[i] == ORD_ELEM_FILL)
2193
        {
2194
            if (!buttonFill(&imgButton, instance))
15 andreas 2195
            {
2196
                mutex_button.unlock();
8 andreas 2197
                return false;
15 andreas 2198
            }
8 andreas 2199
        }
2200
        else if (mDOrder[i] == ORD_ELEM_BITMAP)
2201
        {
21 andreas 2202
            if (!sr[instance].dynamic && !buttonBitmap(&imgButton, instance))
15 andreas 2203
            {
2204
                mutex_button.unlock();
8 andreas 2205
                return false;
15 andreas 2206
            }
21 andreas 2207
            else if (sr[instance].dynamic && !buttonDynamic(&imgButton, instance))
2208
            {
2209
                mutex_button.unlock();
2210
                return false;
2211
            }
8 andreas 2212
        }
2213
        else if (mDOrder[i] == ORD_ELEM_ICON)
2214
        {
2215
            if (!buttonIcon(&imgButton, instance))
15 andreas 2216
            {
2217
                mutex_button.unlock();
8 andreas 2218
                return false;
15 andreas 2219
            }
8 andreas 2220
        }
2221
        else if (mDOrder[i] == ORD_ELEM_TEXT)
2222
        {
2223
            if (!buttonText(&imgButton, instance))
15 andreas 2224
            {
2225
                mutex_button.unlock();
8 andreas 2226
                return false;
15 andreas 2227
            }
8 andreas 2228
        }
2229
        else if (mDOrder[i] == ORD_ELEM_BORDER)
2230
        {
2231
            if (!buttonBorder(&imgButton, instance))
15 andreas 2232
            {
2233
                mutex_button.unlock();
8 andreas 2234
                return false;
15 andreas 2235
            }
8 andreas 2236
        }
2237
    }
2238
 
10 andreas 2239
    if (mGlobalOO >= 0 || sr[instance].oo >= 0) // Take overall opacity into consideration
2240
    {
2241
        SkBitmap ooButton;
2242
        int w = imgButton.width();
2243
        int h = imgButton.height();
2244
        ooButton.allocN32Pixels(w, h, true);
2245
        SkCanvas canvas(ooButton);
2246
        SkIRect irect = SkIRect::MakeXYWH(0, 0, w, h);
2247
        SkRegion region;
2248
        region.setRect(irect);
2249
        SkScalar oo;
2250
 
2251
        if (mGlobalOO >= 0 && sr[instance].oo >= 0)
2252
        {
2253
            oo = std::min((SkScalar)mGlobalOO, (SkScalar)sr[instance].oo);
2254
            MSG_DEBUG("Set global overal opacity to " << oo);
2255
        }
2256
        else if (sr[instance].oo >= 0)
2257
        {
2258
            oo = (SkScalar)sr[instance].oo;
2259
            MSG_DEBUG("Set overal opacity to " << oo);
2260
        }
2261
        else
2262
        {
2263
            oo = (SkScalar)mGlobalOO;
2264
            MSG_DEBUG("Set global overal opacity to " << oo);
2265
        }
2266
 
2267
        SkScalar alpha = 1.0 / 255.0 * oo;
2268
        MSG_DEBUG("Calculated alpha value: " << alpha);
2269
        SkPaint paint;
2270
        paint.setAlphaf(alpha);
2271
        paint.setImageFilter(SkImageFilters::AlphaThreshold(region, 0.0, alpha, nullptr));
2272
        canvas.drawBitmap(imgButton, 0, 0, &paint);
2273
        imgButton.erase(SK_ColorTRANSPARENT, {0, 0, w, h});
2274
        imgButton = ooButton;
2275
    }
2276
 
15 andreas 2277
    mLastImage = imgButton;
8 andreas 2278
    size_t rowBytes = imgButton.info().minRowBytes();
2279
 
31 andreas 2280
    if (!prg_stopped)
26 andreas 2281
    {
2282
        int rwidth = wt;
2283
        int rheight = ht;
2284
        int rleft = lt;
2285
        int rtop = tp;
8 andreas 2286
 
26 andreas 2287
        if (gPageManager && gPageManager->getScaleFactor() != 1.0)
2288
        {
2289
            rwidth = (int)((double)wt * gPageManager->getScaleFactor());
2290
            rheight = (int)((double)ht * gPageManager->getScaleFactor());
2291
            rleft = (int)((double)lt * gPageManager->getScaleFactor());
2292
            rtop = (int)((double)tp * gPageManager->getScaleFactor());
2293
 
2294
            SkPaint paint;
2295
            paint.setBlendMode(SkBlendMode::kSrc);
2296
            paint.setFilterQuality(kHigh_SkFilterQuality);
28 andreas 2297
            // Calculate new dimension
26 andreas 2298
            SkImageInfo info = imgButton.info();
2299
            int width = (int)((double)info.width() * gPageManager->getScaleFactor());
2300
            int height = (int)((double)info.height() * gPageManager->getScaleFactor());
28 andreas 2301
            MSG_DEBUG("Button dimension: " << width << " x " << height);
2302
            // Create a canvas and draw new image
2303
            sk_sp<SkImage> im = SkImage::MakeFromBitmap(imgButton);
2304
            imgButton.allocN32Pixels(width, height);
2305
            imgButton.eraseColor(SK_ColorTRANSPARENT);
26 andreas 2306
            SkCanvas can(imgButton, SkSurfaceProps(1, kUnknown_SkPixelGeometry));
2307
            SkRect rect = SkRect::MakeXYWH(0, 0, width, height);
2308
            can.drawImageRect(im, rect, &paint);
28 andreas 2309
            MSG_DEBUG("Old rowBytes: " << rowBytes);
2310
            rowBytes = imgButton.info().minRowBytes();
2311
            MSG_DEBUG("New rowBytes: " << rowBytes);
2312
            mLastImage = imgButton;
26 andreas 2313
        }
2314
 
31 andreas 2315
        if (show)
2316
            _displayButton(mHandle, parent, (unsigned char *)imgButton.getPixels(), rwidth, rheight, rowBytes, rleft, rtop);
26 andreas 2317
    }
2318
 
15 andreas 2319
    mutex_button.unlock();
8 andreas 2320
    return true;
2321
}
2322
 
15 andreas 2323
bool TButton::drawBargraph(int instance, int level, bool show)
2324
{
2325
    mutex_bargraph.lock();
2326
    DECL_TRACER("TButton::drawBargraph(int instance, bool show)");
2327
 
2328
    if ((size_t)instance >= sr.size() || instance < 0)
2329
    {
2330
        MSG_ERROR("Instance " << instance << " is out of bounds!");
2331
        TError::setError();
2332
        mutex_bargraph.unlock();
2333
        return false;
2334
    }
2335
 
2336
    if (level < rl)
2337
        mLastLevel = rl;
2338
    else if (level > rh)
2339
        mLastLevel = rh;
2340
    else
2341
        mLastLevel = level;
2342
 
2343
    if (!visible || instance != mActInstance || !_displayButton)
2344
    {
2345
        bool db = (_displayButton != nullptr);
23 andreas 2346
        MSG_DEBUG("Bargraph " << bi << ", \"" << na << "\" at instance " << instance << " with level " << mLastLevel << " is not to draw!");
15 andreas 2347
        MSG_DEBUG("Visible: " << (visible ? "YES" : "NO") << ", Instance/actual instance: " << instance << "/" << mActInstance << ", callback: " << (db ? "PRESENT" : "N/A"));
2348
        mutex_bargraph.unlock();
2349
        return true;
2350
    }
2351
 
2352
    ulong parent = mHandle & 0xffff0000;
20 andreas 2353
 
2354
    if (type == BARGRAPH)
2355
        getDrawOrder(sr[1]._do, (DRAW_ORDER *)&mDOrder);
2356
    else
2357
        getDrawOrder(sr[instance]._do, (DRAW_ORDER *)&mDOrder);
2358
 
15 andreas 2359
    if (TError::isError())
2360
    {
2361
        mutex_bargraph.unlock();
2362
        return false;
2363
    }
2364
 
2365
    SkBitmap imgButton;
2366
    imgButton.allocN32Pixels(wt, ht);
2367
 
2368
    for (int i = 0; i < ORD_ELEM_COUNT; i++)
2369
    {
2370
        if (mDOrder[i] == ORD_ELEM_FILL)
2371
        {
2372
            if (!buttonFill(&imgButton, instance))
2373
            {
2374
                mutex_bargraph.unlock();
2375
                return false;
2376
            }
2377
        }
2378
        else if (mDOrder[i] == ORD_ELEM_BITMAP)
2379
        {
2380
            if (!barLevel(&imgButton, instance, mLastLevel))
2381
            {
2382
                mutex_bargraph.unlock();
2383
                return false;
2384
            }
2385
        }
2386
        else if (mDOrder[i] == ORD_ELEM_ICON)
2387
        {
2388
            if (!buttonIcon(&imgButton, instance))
2389
            {
2390
                mutex_bargraph.unlock();
2391
                return false;
2392
            }
2393
        }
2394
        else if (mDOrder[i] == ORD_ELEM_TEXT)
2395
        {
2396
            if (!buttonText(&imgButton, instance))
2397
            {
2398
                mutex_bargraph.unlock();
2399
                return false;
2400
            }
2401
        }
2402
        else if (mDOrder[i] == ORD_ELEM_BORDER)
2403
        {
2404
            if (!buttonBorder(&imgButton, instance))
2405
            {
2406
                mutex_bargraph.unlock();
2407
                return false;
2408
            }
2409
        }
2410
    }
2411
 
2412
    if (mGlobalOO >= 0 || sr[instance].oo >= 0) // Take overall opacity into consideration
2413
    {
2414
        SkBitmap ooButton;
2415
        int w = imgButton.width();
2416
        int h = imgButton.height();
2417
        ooButton.allocN32Pixels(w, h, true);
2418
        SkCanvas canvas(ooButton);
2419
        SkIRect irect = SkIRect::MakeXYWH(0, 0, w, h);
2420
        SkRegion region;
2421
        region.setRect(irect);
2422
        SkScalar oo;
2423
 
2424
        if (mGlobalOO >= 0 && sr[instance].oo >= 0)
2425
        {
2426
            oo = std::min((SkScalar)mGlobalOO, (SkScalar)sr[instance].oo);
2427
            MSG_DEBUG("Set global overal opacity to " << oo);
2428
        }
2429
        else if (sr[instance].oo >= 0)
2430
        {
2431
            oo = (SkScalar)sr[instance].oo;
2432
            MSG_DEBUG("Set overal opacity to " << oo);
2433
        }
2434
        else
2435
        {
2436
            oo = (SkScalar)mGlobalOO;
2437
            MSG_DEBUG("Set global overal opacity to " << oo);
2438
        }
2439
 
2440
        SkScalar alpha = 1.0 / 255.0 * oo;
2441
        MSG_DEBUG("Calculated alpha value: " << alpha);
2442
        SkPaint paint;
2443
        paint.setAlphaf(alpha);
2444
        paint.setImageFilter(SkImageFilters::AlphaThreshold(region, 0.0, alpha, nullptr));
2445
        canvas.drawBitmap(imgButton, 0, 0, &paint);
2446
        imgButton.erase(SK_ColorTRANSPARENT, {0, 0, w, h});
2447
        imgButton = ooButton;
2448
    }
2449
 
2450
    mLastImage = imgButton;
2451
    size_t rowBytes = imgButton.info().minRowBytes();
2452
 
21 andreas 2453
    if (!prg_stopped && show && visible && instance == mActInstance && _displayButton)
26 andreas 2454
    {
2455
        int rwidth = wt;
2456
        int rheight = ht;
2457
        int rleft = lt;
2458
        int rtop = tp;
15 andreas 2459
 
26 andreas 2460
        if (gPageManager && gPageManager->getScaleFactor() != 1.0)
2461
        {
2462
            rwidth = (int)((double)wt * gPageManager->getScaleFactor());
2463
            rheight = (int)((double)ht * gPageManager->getScaleFactor());
2464
            rleft = (int)((double)lt * gPageManager->getScaleFactor());
2465
            rtop = (int)((double)tp * gPageManager->getScaleFactor());
2466
 
2467
            SkPaint paint;
2468
            paint.setBlendMode(SkBlendMode::kSrc);
2469
            paint.setFilterQuality(kHigh_SkFilterQuality);
28 andreas 2470
            // Calculate new dimension
26 andreas 2471
            SkImageInfo info = imgButton.info();
2472
            int width = (int)((double)info.width() * gPageManager->getScaleFactor());
2473
            int height = (int)((double)info.height() * gPageManager->getScaleFactor());
28 andreas 2474
            // Create a canvas and draw new image
2475
            sk_sp<SkImage> im = SkImage::MakeFromBitmap(imgButton);
2476
            imgButton.allocN32Pixels(width, height);
2477
            imgButton.eraseColor(SK_ColorTRANSPARENT);
26 andreas 2478
            SkCanvas can(imgButton, SkSurfaceProps(1, kUnknown_SkPixelGeometry));
2479
            SkRect rect = SkRect::MakeXYWH(0, 0, width, height);
2480
            can.drawImageRect(im, rect, &paint);
28 andreas 2481
            rowBytes = imgButton.info().minRowBytes();
2482
            mLastImage = imgButton;
26 andreas 2483
        }
2484
 
2485
        _displayButton(mHandle, parent, (unsigned char *)imgButton.getPixels(), rwidth, rheight, rowBytes, rleft, rtop);
2486
    }
2487
 
15 andreas 2488
    mutex_bargraph.unlock();
2489
    return true;
2490
}
2491
 
4 andreas 2492
POSITION_t TButton::calcImagePosition(int width, int height, CENTER_CODE cc, int number)
2493
{
2494
    DECL_TRACER("TButton::calcImagePosition(int with, int height, CENTER_CODE code, int number)");
2495
 
2496
    SR_T act_sr;
2497
    POSITION_t position;
8 andreas 2498
    int ix, iy;
4 andreas 2499
 
2500
    if (sr.size() == 0)
2501
        return position;
2502
 
2503
    if (number <= 0)
2504
        act_sr = sr.at(0);
2505
    else if ((size_t)number < sr.size())
2506
        act_sr = sr.at(number);
2507
    else
2508
        return position;
2509
 
2510
    int border_size = getBorderSize(act_sr.bs);
8 andreas 2511
    int code, border = border_size;
10 andreas 2512
    string dbgCC;
17 andreas 2513
    int rwt = 0, rht = 0;
4 andreas 2514
 
2515
    switch (cc)
3 andreas 2516
    {
8 andreas 2517
        case SC_ICON:
2518
            code = act_sr.ji;
2519
            ix = act_sr.ix;
2520
            iy = act_sr.iy;
2521
            border = border_size = 0;
10 andreas 2522
            dbgCC = "ICON";
17 andreas 2523
            rwt = width;
2524
            rht = height;
8 andreas 2525
        break;
2526
 
2527
        case SC_BITMAP:
2528
            code = act_sr.jb;
10 andreas 2529
            ix = act_sr.bx;
2530
            iy = act_sr.by;
2531
            dbgCC = "BITMAP";
17 andreas 2532
            rwt = std::min(wt - border * 2, width);
2533
            rht = std::min(ht - border_size * 2, height);
8 andreas 2534
        break;
2535
 
2536
        case SC_TEXT:
2537
            code = act_sr.jt;
2538
            ix = act_sr.tx;
2539
            iy = act_sr.ty;
10 andreas 2540
            dbgCC = "TEXT";
2541
            border += 4;
17 andreas 2542
            rwt = std::min(wt - border * 2, width);
2543
            rht = std::min(ht - border_size * 2, height);
8 andreas 2544
        break;
3 andreas 2545
    }
4 andreas 2546
 
17 andreas 2547
    if (width > rwt || height > rht)
2548
        position.overflow = true;
8 andreas 2549
 
4 andreas 2550
    switch (code)
2551
    {
2552
        case 0: // absolute position
8 andreas 2553
            position.left = ix;
2554
            position.top = iy;
10 andreas 2555
            position.width = rwt;
2556
            position.height = rht;
4 andreas 2557
        break;
2558
 
2559
        case 1: // top, left
10 andreas 2560
            if (cc == SC_TEXT)
2561
                position.left = border;
2562
 
2563
            position.width = rwt;
2564
            position.height = rht;
4 andreas 2565
        break;
2566
 
2567
        case 2: // center, top
10 andreas 2568
            position.left = (wt - rwt) / 2;
2569
            position.height = rht;
2570
            position.width = rwt;
4 andreas 2571
        break;
2572
 
2573
        case 3: // right, top
10 andreas 2574
            position.left = wt - rwt;
19 andreas 2575
 
2576
            if (cc == SC_TEXT)
2577
                position.left = (((position.left - border) < 0) ? 0 : position.left - border);
2578
 
10 andreas 2579
            position.width = rwt;
2580
            position.height = rht;
4 andreas 2581
        break;
2582
 
2583
        case 4: // left, middle
10 andreas 2584
            if (cc == SC_TEXT)
2585
                position.left = border;
2586
 
2587
            position.top = (ht - rht) / 2;
2588
            position.width = rwt;
2589
            position.height = rht;
4 andreas 2590
        break;
2591
 
2592
        case 6: // right, middle
10 andreas 2593
            position.left = wt - rwt;
19 andreas 2594
 
2595
            if (cc == SC_TEXT)
2596
                position.left = (((position.left - border) < 0) ? 0 : position.left - border);
2597
 
10 andreas 2598
            position.top = (ht - rht) / 2;
2599
            position.width = rwt;
2600
            position.height = rht;
4 andreas 2601
        break;
2602
 
2603
        case 7: // left, bottom
10 andreas 2604
            if (cc == SC_TEXT)
2605
                position.left = border_size;
2606
 
2607
            position.top = ht - rht;
2608
            position.width = rwt;
2609
            position.height = rht;
4 andreas 2610
        break;
2611
 
2612
        case 8: // center, bottom
10 andreas 2613
            position.left = (wt - rwt) / 2;
2614
            position.top = ht - rht;
2615
            position.width = rwt;
2616
            position.height = rht;
4 andreas 2617
        break;
2618
 
2619
        case 9: // right, bottom
10 andreas 2620
            position.left = wt - rwt;
19 andreas 2621
 
2622
            if (cc == SC_TEXT)
2623
                position.left = (((position.left - border) < 0) ? 0 : position.left - border);
2624
 
10 andreas 2625
            position.top = ht - rht;
4 andreas 2626
        break;
2627
 
2628
        default: // center, middle
10 andreas 2629
            position.left = (wt - rwt) / 2;
2630
            position.top = (ht - rht) / 2;
2631
            position.width = rwt;
2632
            position.height = rht;
4 andreas 2633
    }
2634
 
17 andreas 2635
    MSG_DEBUG("Type: " << dbgCC << ", PosType=" << code << ", Position: x=" << position.left << ", y=" << position.top << ", w=" << position.width << ", h=" << position.height << ", Overflow: " << (position.overflow ? "YES" : "NO"));
4 andreas 2636
    position.valid = true;
2637
    return position;
2638
}
2639
 
2640
int TButton::getBorderSize(const std::string& name)
2641
{
2642
    DECL_TRACER("TButton::getBorderSize(const std::string& name)");
2643
 
2644
    for (int i = 0; sysBorders[i].name != nullptr; i++)
2645
    {
2646
        if (name.compare(sysBorders[i].name) == 0)
8 andreas 2647
        {
2648
            MSG_DEBUG("Border size: " << sysBorders[i].width);
4 andreas 2649
            return sysBorders[i].width;
8 andreas 2650
        }
4 andreas 2651
    }
2652
 
8 andreas 2653
    MSG_DEBUG("Border size: 0");
4 andreas 2654
    return 0;
2655
}
2656
 
2657
void TButton::calcImageSizePercent(int imWidth, int imHeight, int btWidth, int btHeight, int btFrame, int *realX, int *realY)
2658
{
2659
    DECL_TRACER("TButton::clacImageSizePercent(int imWidth, int imHeight, int btWidth, int btHeight, int btFrame, int *realX, int *realY)");
2660
 
2661
    int spX = btWidth - (btFrame * 2);
2662
    int spY = btHeight - (btFrame * 2);
2663
 
2664
    if (imWidth <= spX && imHeight <= spY)
2665
    {
2666
        *realX = imWidth;
2667
        *realY = imHeight;
2668
        return;
2669
    }
2670
 
2671
    int oversizeX = 0, oversizeY = 0;
2672
 
2673
    if (imWidth > spX)
2674
        oversizeX = imWidth - spX;
2675
 
2676
    if (imHeight > spY)
2677
        oversizeY = imHeight - spY;
2678
 
2679
    double percent = 0.0;
2680
 
2681
    if (oversizeX > oversizeY)
2682
        percent = 100.0 / (double)imWidth * (double)spX;
3 andreas 2683
    else
4 andreas 2684
        percent = 100.0 / (double)imHeight * (double)spY;
2685
 
2686
    *realX = (int)(percent / 100.0 * (double)imWidth);
2687
    *realY = (int)(percent / 100.0 * (double)imHeight);
2688
}
2689
 
10 andreas 2690
SkBitmap TButton::drawImageButton(SkBitmap& imgRed, SkBitmap& imgMask, int width, int height, SkColor col1, SkColor col2)
4 andreas 2691
{
6 andreas 2692
    DECL_TRACER("TButton::drawImageButton(SkImage& imgRed, SkImage& imgMask, int width, int height, SkColor col1, SkColor col2)");
4 andreas 2693
 
6 andreas 2694
    SkPixmap pixmapRed = imgRed.pixmap();
7 andreas 2695
    SkPixmap pixmapMask;
4 andreas 2696
 
7 andreas 2697
    if (!imgMask.empty())
2698
        pixmapMask = imgMask.pixmap();
4 andreas 2699
 
7 andreas 2700
    SkBitmap maskBm;
2701
    maskBm.allocPixels(SkImageInfo::MakeN32Premul(width, height));
2702
    SkCanvas canvas(maskBm);
3 andreas 2703
 
4 andreas 2704
    for (int ix = 0; ix < width; ix++)
2705
    {
2706
        for (int iy = 0; iy < height; iy++)
3 andreas 2707
        {
4 andreas 2708
            SkColor pixelRed = pixmapRed.getColor(ix, iy);
7 andreas 2709
            SkColor pixelMask;
3 andreas 2710
 
7 andreas 2711
            if (!imgMask.empty())
2712
                pixelMask = pixmapMask.getColor(ix, iy);
2713
            else
2714
                pixelMask = SK_ColorWHITE;
3 andreas 2715
 
10 andreas 2716
            SkColor pixel = baseColor(pixelRed, pixelMask, col1, col2);
20 andreas 2717
            uint32_t alpha = SkColorGetA(pixel);
7 andreas 2718
            SkPaint paint;
20 andreas 2719
 
2720
            if (alpha == 0)
2721
                pixel = pixelMask;
2722
 
4 andreas 2723
            paint.setColor(pixel);
7 andreas 2724
            canvas.drawPoint(ix, iy, paint);
3 andreas 2725
        }
2726
    }
2727
 
7 andreas 2728
    return maskBm;
3 andreas 2729
}
6 andreas 2730
 
2731
void TButton::show()
2732
{
2733
    DECL_TRACER("TButton::show()");
2734
 
15 andreas 2735
    visible = true;
2736
    makeElement();
6 andreas 2737
 
15 andreas 2738
    if (isSystemButton() && !mSystemReg)
2739
        registerSystemButton();
2740
}
2741
 
2742
void TButton::showLastButton()
2743
{
2744
    DECL_TRACER("TButton::showLastButton()");
2745
 
2746
    if (mLastImage.empty())
2747
        return;
2748
 
21 andreas 2749
    if (!prg_stopped && visible && _displayButton)
6 andreas 2750
    {
15 andreas 2751
        ulong parent = mHandle & 0xffff0000;
2752
        size_t rowBytes = mLastImage.info().minRowBytes();
26 andreas 2753
        int rwidth = wt;
2754
        int rheight = ht;
2755
        int rleft = lt;
2756
        int rtop = tp;
2757
 
2758
        if (gPageManager && gPageManager->getScaleFactor() != 1.0)
2759
        {
2760
            rwidth = (int)((double)wt * gPageManager->getScaleFactor());
2761
            rheight = (int)((double)ht * gPageManager->getScaleFactor());
2762
            rleft = (int)((double)lt * gPageManager->getScaleFactor());
2763
            rtop = (int)((double)tp * gPageManager->getScaleFactor());
31 andreas 2764
            MSG_DEBUG("Scaled dimensions: l=" << rleft << ", t=" << rtop << ", w=" << rwidth << ", h=" << rheight);
26 andreas 2765
        }
2766
 
2767
        _displayButton(mHandle, parent, (unsigned char *)mLastImage.getPixels(), rwidth, rheight, rowBytes, rleft, rtop);
15 andreas 2768
    }
2769
}
2770
 
2771
void TButton::hide(bool total)
2772
{
2773
    DECL_TRACER("TButton::hide()");
2774
 
2775
    if (type == MULTISTATE_GENERAL && ar == 1)
2776
        mAniRunning = false;
2777
 
21 andreas 2778
    if (!prg_stopped && total)
15 andreas 2779
    {
26 andreas 2780
        int rwidth = wt;
2781
        int rheight = ht;
2782
        int rleft = lt;
2783
        int rtop = tp;
2784
 
2785
        if (gPageManager && gPageManager->getScaleFactor() != 1.0)
2786
        {
2787
            rwidth = (int)((double)wt * gPageManager->getScaleFactor());
2788
            rheight = (int)((double)ht * gPageManager->getScaleFactor());
2789
            rleft = (int)((double)lt * gPageManager->getScaleFactor());
2790
            rtop = (int)((double)tp * gPageManager->getScaleFactor());
2791
        }
2792
 
28 andreas 2793
        SkBitmap imgButton;
2794
        imgButton.allocN32Pixels(rwidth, rheight);
2795
        imgButton.eraseColor(SK_ColorTRANSPARENT);
2796
        ulong parent = mHandle & 0xffff0000;
2797
        size_t rowBytes = imgButton.info().minRowBytes();
2798
 
26 andreas 2799
        _displayButton(mHandle, parent, (unsigned char *)imgButton.getPixels(), rwidth, rheight, rowBytes, rleft, rtop);
15 andreas 2800
    }
2801
 
2802
    visible = false;
2803
}
2804
 
2805
bool TButton::isClickable()
2806
{
2807
    DECL_TRACER("TButton::isClickable()");
2808
 
16 andreas 2809
    if (mEnabled && ((cp != 0 && ch != 0) || !op.empty() || !pushFunc.empty()) && hs.compare("passThru") != 0)
15 andreas 2810
        return true;
2811
 
2812
    return false;
2813
}
2814
 
2815
/**
2816
 * Handling of system button "connection state". It consists of 12 states
2817
 * indicating the network status. The states have the following meaning:
2818
 *
2819
 * 0      Diconnected (never was connected before since startup)
2820
 * 1 - 6  Connected (blink may be shown with dark and light green)
2821
 * 7, 8   Disconnected (timeout or loss of connection)
2822
 * 9 - 11 Connection in progress
2823
 */
2824
void TButton::funcNetwork(int state)
2825
{
2826
    mutex_sysdraw.lock();
2827
    DECL_TRACER("TButton::funcNetwork(int state)");
2828
 
2829
    mLastLevel = state;
2830
    mActInstance = state;
2831
 
2832
    if (visible)
2833
        makeElement(state);
2834
 
2835
    mutex_sysdraw.unlock();
2836
}
2837
 
2838
/**
2839
 * Handling the timer event from the controller. This comes usualy every
2840
 * 20th part of a second (1 second / 20)
2841
 */
2842
void TButton::funcTimer(const amx::ANET_BLINK& blink)
2843
{
2844
    mutex_sysdraw.lock();
2845
    DECL_TRACER("TButton::funcTimer(const amx::ANET_BLINK& blink)");
2846
 
2847
    string tm;
2848
    std::stringstream sstr;
2849
 
2850
    switch (ad)
2851
    {
2852
        case 141:   // Standard time
2853
            sstr << std::setw(2) << std::setfill('0') << (int)blink.hour << ":"
2854
                 << std::setw(2) << std::setfill('0') << (int)blink.minute << ":"
2855
                 << std::setw(2) << std::setfill('0') << (int)blink.second;
2856
            mLastBlink = blink;
2857
        break;
2858
 
2859
        case 142:   // Time AM/PM
6 andreas 2860
        {
15 andreas 2861
            int hour = (blink.hour > 12) ? (blink.hour - 12) : blink.hour;
2862
            sstr << std::setw(2) << std::setfill('0') << hour << ":"
2863
                 << std::setw(2) << std::setfill('0') << (int)blink.minute << " ";
2864
 
2865
            if (blink.hour <= 12)
2866
                sstr << "AM";
2867
            else
2868
                sstr << "PM";
2869
 
2870
            mLastBlink = blink;
6 andreas 2871
        }
15 andreas 2872
        break;
6 andreas 2873
 
15 andreas 2874
        case 143:   // Time 24 hours
2875
            sstr << std::setw(2) << std::setfill('0') << (int)blink.hour << ":"
2876
                 << std::setw(2) << std::setfill('0') << (int)blink.minute;
2877
            mLastBlink = blink;
2878
        break;
2879
 
2880
        case 151:   // Weekday
2881
            switch (blink.weekday)
2882
            {
2883
                case 0: sstr << "Monday"; break;
2884
                case 1: sstr << "Tuesday"; break;
2885
                case 2: sstr << "Wednesday"; break;
2886
                case 3: sstr << "Thursday"; break;
2887
                case 4: sstr << "Friday"; break;
2888
                case 5: sstr << "Saturday"; break;
2889
                case 6: sstr << "Sunday"; break;
2890
            }
2891
        break;
2892
 
2893
        case 152:   // Date mm/dd
2894
            sstr << (int)blink.month << "/" << (int)blink.day;
2895
        break;
2896
 
2897
        case 153:   // Date dd/mm
2898
            sstr << (int)blink.day << "/" << (int)blink.month;
2899
        break;
2900
 
2901
        case 154:   // Date mm/dd/yyyy
2902
            sstr << (int)blink.month << "/" << (int)blink.day << "/" << (int)blink.year;
2903
        break;
2904
 
2905
        case 155:   // Date dd/mm/yyyy
2906
            sstr << blink.day << "/" << blink.month << "/" << blink.year;
2907
        break;
2908
 
2909
        case 156:   // Date month dd/yyyy
2910
            switch (blink.month)
2911
            {
2912
                case 1:  sstr << "January"; break;
2913
                case 2:  sstr << "February"; break;
2914
                case 3:  sstr << "March"; break;
2915
                case 4:  sstr << "April"; break;
2916
                case 5:  sstr << "May"; break;
2917
                case 6:  sstr << "June"; break;
2918
                case 7:  sstr << "July"; break;
2919
                case 8:  sstr << "August"; break;
2920
                case 9:  sstr << "September"; break;
2921
                case 10:  sstr << "October"; break;
2922
                case 11:  sstr << "November"; break;
2923
                case 12:  sstr << "December"; break;
2924
            }
2925
 
2926
            sstr << " " << (int)blink.day << "/" << (int)blink.year;
2927
        break;
2928
 
2929
        case 157:   // Date dd month yyyy
2930
            sstr << (int)blink.day;
2931
 
2932
            switch (blink.month)
2933
            {
2934
                case 1:  sstr << "January"; break;
2935
                case 2:  sstr << "February"; break;
2936
                case 3:  sstr << "March"; break;
2937
                case 4:  sstr << "April"; break;
2938
                case 5:  sstr << "May"; break;
2939
                case 6:  sstr << "June"; break;
2940
                case 7:  sstr << "July"; break;
2941
                case 8:  sstr << "August"; break;
2942
                case 9:  sstr << "September"; break;
2943
                case 10:  sstr << "October"; break;
2944
                case 11:  sstr << "November"; break;
2945
                case 12:  sstr << "December"; break;
2946
            }
2947
 
2948
            sstr << " " << (int)blink.year;
2949
        break;
2950
 
2951
        case 158:   // Date yyyy-mm-dd
2952
            sstr << (int)blink.year << "-" << (int)blink.month << "-" << (int)blink.day;
2953
        break;
6 andreas 2954
    }
15 andreas 2955
 
2956
    vector<SR_T>::iterator iter;
2957
    tm = sstr.str();
2958
 
2959
    for (iter = sr.begin(); iter != sr.end(); iter++)
2960
        iter->te = tm;
2961
 
2962
    if (visible)
2963
        makeElement(mActInstance);
2964
 
2965
    mutex_sysdraw.unlock();
6 andreas 2966
}
7 andreas 2967
 
15 andreas 2968
bool TButton::isPixelTransparent(int x, int y)
2969
{
2970
    DECL_TRACER("TButton::isPixelTransparent(int x, int y)");
2971
 
2972
    if (mLastImage.empty())
2973
    {
2974
        MSG_ERROR("Internal error: No image for button available!");
2975
        return true;
2976
    }
2977
 
31 andreas 2978
    if (x < 0 || x >= mLastImage.info().width() || y < 0 || y >= mLastImage.info().height())
2979
    {
2980
        MSG_ERROR("The X or Y coordinate is out of bounds!");
2981
        MSG_ERROR("X=" << x << ", Y=" << y << ", width=" << mLastImage.info().width() << ", height=" << mLastImage.info().height());
2982
        return false;
2983
    }
2984
 
15 andreas 2985
    float alpha = mLastImage.getAlphaf(x, y);
2986
 
2987
    if (alpha != 0.0)
2988
        return false;
2989
 
2990
    return true;
2991
}
2992
 
2993
/**
2994
 * This button got the click because it matches the coordinates of a mouse
2995
 * click. It checkes whether it is clickable or not. If it is clickable, it
2996
 * depends on the type of element what happens.
2997
 */
2998
bool TButton::doClick(int x, int y, bool pressed)
2999
{
11 andreas 3000
    DECL_TRACER("TButton::doClick(bool pressed)");
14 andreas 3001
    amx::ANET_SEND scmd;
15 andreas 3002
    int instance = 0;
31 andreas 3003
    int sx = x, sy = y;
11 andreas 3004
 
15 andreas 3005
    if (!isClickable())
3006
        return false;
3007
 
31 andreas 3008
    // To be able to test a button for a transparent pixel, we must scale
3009
    // the coordinates because the test is made on the last drawn image and
3010
    // this image is scaled (if scaling is activated).
3011
    if (TConfig::getScale() && gPageManager && gPageManager->getScaleFactor() != 1.0)
3012
    {
3013
        double scaleFactor = gPageManager->getScaleFactor();
3014
        sx = (int)((double)x * scaleFactor);
3015
        sy = (int)((double)y * scaleFactor);
3016
    }
3017
 
11 andreas 3018
    if (type == GENERAL)
3019
    {
30 andreas 3020
        MSG_TRACE("Button type: GENERAL");
3021
 
11 andreas 3022
        if (fb == FB_MOMENTARY)
3023
        {
3024
            if (pressed)
15 andreas 3025
                instance = 1;
14 andreas 3026
            else
15 andreas 3027
                instance = 0;
3028
 
30 andreas 3029
            MSG_TRACE("Flavor FB_MOMENTARY, instance=" << instance);
15 andreas 3030
            mActInstance = instance;
3031
            // we ignore the state of the method, because it doesn't matter
3032
            // for the message to the controller.
3033
            drawButton(instance, false);
3034
            // If there is nothing in "hs", then it depends on the pixel of the
3035
            // layer. Only if the pixel the coordinates point to are not
3036
            // transparent, the button takes the click.
31 andreas 3037
            if (hs.empty() && isPixelTransparent(sx, sy))
15 andreas 3038
                return false;
3039
 
3040
            if (pushFunc.empty())   // Don't draw the button if it has a push function defined
3041
                showLastButton();
3042
            else
14 andreas 3043
                mActInstance = 0;
15 andreas 3044
        }
3045
        else if (fb == FB_CHANNEL || fb == FB_NONE)
3046
        {
3047
            if (pressed)
3048
                instance = 1;
3049
            else
3050
                instance = 0;
14 andreas 3051
 
30 andreas 3052
            MSG_TRACE("Flavor FB_CHANNEL, instance=" << instance);
15 andreas 3053
            // we ignore the state of the method, because it doesn't matter
3054
            // for the message to the controller.
3055
            drawButton(instance, false);
3056
            // If there is nothing in "hs", then it depends on the pixel of the
3057
            // layer. Only if the pixel the coordinates point to are not
3058
            // transparent, the button takes the click.
31 andreas 3059
            if (hs.empty() && isPixelTransparent(sx, sy))
15 andreas 3060
                return false;
14 andreas 3061
        }
15 andreas 3062
        else if (fb == FB_INV_CHANNEL)
3063
        {
3064
            if (pressed)
3065
                instance = 0;
3066
            else
3067
                instance = 1;
14 andreas 3068
 
30 andreas 3069
            MSG_TRACE("Flavor FB_INV_CHANNEL, instance=" << instance);
15 andreas 3070
            // we ignore the state of the method, because it doesn't matter
3071
            // for the message to the controller.
3072
            drawButton(instance, false);
3073
            // If there is nothing in "hs", then it depends on the pixel of the
3074
            // layer. Only if the pixel the coordinates point to are not
3075
            // transparent, the button takes the click.
31 andreas 3076
            if (hs.empty() && isPixelTransparent(sx, sy))
15 andreas 3077
                return false;
3078
        }
3079
        else if (fb == FB_ALWAYS_ON)
14 andreas 3080
        {
15 andreas 3081
            instance = 1;
3082
            mActInstance = 1;
30 andreas 3083
            MSG_TRACE("Flavor FB_ALWAYS_ON, instance=" << instance);
15 andreas 3084
            // we ignore the state of the method, because it doesn't matter
3085
            // for the message to the controller.
3086
            drawButton(instance, false);
3087
            // If there is nothing in "hs", then it depends on the pixel of the
3088
            // layer. Only if the pixel the coordinates point to are not
3089
            // transparent, the button takes the click.
31 andreas 3090
            if (hs.empty() && isPixelTransparent(sx, sy))
15 andreas 3091
                return false;
3092
        }
3093
 
3094
        if ((cp && ch) || !op.empty())
3095
        {
14 andreas 3096
            scmd.device = TConfig::getChannel();
3097
            scmd.port = cp;
3098
            scmd.channel = ch;
3099
 
15 andreas 3100
            if (op.empty())
3101
            {
3102
                if (instance)
3103
                    scmd.MC = 0x0084;
3104
                else
3105
                    scmd.MC = 0x0085;
3106
            }
14 andreas 3107
            else
15 andreas 3108
            {
3109
                scmd.MC = 0x008b;
3110
                scmd.msg = op;
3111
            }
14 andreas 3112
 
3113
            MSG_DEBUG("Button " << bi << ", " << na << " with handle " << TObject::handleToString(mHandle));
3114
            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") << ")");
3115
 
3116
            if (gAmxNet)
15 andreas 3117
            {
3118
                if (scmd.MC != 0x008b || (pressed && scmd.MC == 0x008b))
3119
                    gAmxNet->sendCommand(scmd);
3120
            }
14 andreas 3121
            else
3122
                MSG_WARNING("Missing global class TAmxNet. Can't send a message!");
3123
        }
3124
    }
15 andreas 3125
    else if (type == MULTISTATE_GENERAL)
3126
    {
3127
        if ((cp && ch) || !op.empty())
3128
        {
3129
            scmd.device = TConfig::getChannel();
3130
            scmd.port = cp;
3131
            scmd.channel = ch;
14 andreas 3132
 
15 andreas 3133
            if (op.empty())
3134
            {
3135
                if (pressed || fb == FB_ALWAYS_ON)
3136
                    scmd.MC = 0x0084;
3137
                else
3138
                    scmd.MC = 0x0085;
3139
            }
3140
            else
3141
            {
3142
                scmd.MC = 0x008b;
3143
                scmd.msg = op;
3144
            }
3145
 
3146
            MSG_DEBUG("Button " << bi << ", " << na << " with handle " << TObject::handleToString(mHandle));
3147
            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") << ")");
3148
 
3149
            if (gAmxNet)
3150
            {
3151
                if (scmd.MC != 0x008b || (pressed && scmd.MC == 0x008b))
3152
                    gAmxNet->sendCommand(scmd);
3153
            }
3154
            else
3155
                MSG_WARNING("Missing global class TAmxNet. Can't send a message!");
3156
        }
3157
    }
3158
 
3159
    /* FIXME: Move the following to class TPageManager!
3160
     *        To do that, the preconditions are to be implemented. It must be
3161
     *        possible to find the button and get access to the credentials
3162
     *        of it.
3163
     */
14 andreas 3164
    if (!pushFunc.empty() && pressed)
3165
    {
3166
        vector<PUSH_FUNC_T>::iterator iter;
3167
 
3168
        for (iter = pushFunc.begin(); iter != pushFunc.end(); iter++)
3169
        {
15 andreas 3170
            if (iter->pfType == "sShow")            // show popup
11 andreas 3171
            {
14 andreas 3172
                if (gPageManager)
3173
                    gPageManager->showSubPage(iter->pfName);
11 andreas 3174
            }
15 andreas 3175
            else if (iter->pfType == "sHide")       // hide popup
11 andreas 3176
            {
14 andreas 3177
                if (gPageManager)
3178
                    gPageManager->hideSubPage(iter->pfName);
11 andreas 3179
            }
15 andreas 3180
            else if (iter->pfType == "scGroup")     // hide group
14 andreas 3181
            {
3182
                if (gPageManager)
3183
                    gPageManager->closeGroup(iter->pfName);
3184
            }
15 andreas 3185
            else if (iter->pfType == "Stan")        // Flip to standard page
14 andreas 3186
            {
3187
                if (gPageManager)
3188
                {
15 andreas 3189
                    TPage *page = gPageManager->getActualPage();
3190
 
3191
                    if (!page)
3192
                    {
3193
                        MSG_DEBUG("Internal error: No actual page found!");
3194
                        return false;
3195
                    }
3196
 
3197
                    TSettings *settings = gPageManager->getSettings();
3198
 
3199
                    if (settings->getPowerUpPage().compare(page->getName()) != 0)
3200
                        gPageManager->setPage(settings->getPowerUpPage());
3201
                }
3202
            }
3203
            else if (iter->pfType == "Prev")        // Flip to previous page
3204
            {
3205
                if (gPageManager)
3206
                {
3207
                    int old = gPageManager->getPreviousPageNumber();
3208
 
3209
                    if (old > 0)
3210
                        gPageManager->setPage(old);
3211
                }
3212
            }
3213
            else if (iter->pfType == "sToggle")     // Toggle popup state
3214
            {
3215
                if (gPageManager)
3216
                {
14 andreas 3217
                    TSubPage *page = gPageManager->getSubPage(iter->pfName);
11 andreas 3218
 
14 andreas 3219
                    if (page && page->isVisible())
3220
                        gPageManager->hideSubPage(iter->pfName);
3221
                    else if (page)
3222
                        gPageManager->showSubPage(iter->pfName);
3223
                }
3224
            }
11 andreas 3225
        }
3226
    }
15 andreas 3227
 
3228
    return true;
11 andreas 3229
}
3230
 
7 andreas 3231
/**
3232
 * Based on the pixels in the \a basePix, the function decides whether to return
3233
 * the value of \a col1 or \a col2. A red pixel returns the color \a col1 and
3234
 * a green pixel returns the color \a col2. If there is no red and no green
3235
 * pixel, a transparent pixel is returned.
3236
 *
3237
 * @param basePix
3238
 * This is a pixel from a mask containing red and/or green pixels.
3239
 *
3240
 * @param maskPix
3241
 * This is a pixel from a mask containing more or less tranparent pixels. If
3242
 * the alpha channel of \a basePix is 0 (transparent) this pixel is returned.
3243
 *
3244
 * @param col1
3245
 * The first color.
3246
 *
3247
 * @param col2
3248
 * The second color.
3249
 *
3250
 * @return
3251
 * An array containing the color for one pixel.
3252
 */
10 andreas 3253
SkColor TButton::baseColor(SkColor basePix, SkColor maskPix, SkColor col1, SkColor col2)
7 andreas 3254
{
3255
    uint alpha = SkColorGetA(basePix);
23 andreas 3256
    uint green = SkColorGetG(basePix);
3257
#ifndef __ANDROID__
7 andreas 3258
    uint red = SkColorGetR(basePix);
23 andreas 3259
#else
3260
    uint red = SkColorGetB(basePix);
3261
#endif
7 andreas 3262
 
3263
    if (alpha == 0)
3264
        return maskPix;
3265
 
3266
    if (red && green)
3267
    {
23 andreas 3268
#ifdef __ANDROID__
3269
        uint newB = (SkColorGetR(col1) + SkColorGetR(col2) / 2) & 0x0ff;
3270
        uint newG = (SkColorGetG(col1) + SkColorGetG(col2) / 2) & 0x0ff;
3271
        uint newR = (SkColorGetB(col1) + SkColorGetB(col2) / 2) & 0x0ff;
3272
#else
7 andreas 3273
        uint newR = (SkColorGetR(col1) + SkColorGetR(col2) / 2) & 0x0ff;
3274
        uint newG = (SkColorGetG(col1) + SkColorGetG(col2) / 2) & 0x0ff;
3275
        uint newB = (SkColorGetB(col1) + SkColorGetB(col2) / 2) & 0x0ff;
23 andreas 3276
#endif
7 andreas 3277
        uint newA = (SkColorGetA(col1) + SkColorGetA(col2) / 2) & 0x0ff;
3278
 
3279
        return SkColorSetARGB(newA, newR, newG, newB);
3280
    }
3281
 
3282
    if (red)
3283
        return col1;
3284
 
3285
    if (green)
3286
        return col2;
3287
 
3288
    return SK_ColorTRANSPARENT; // transparent pixel
3289
}
3290
 
3291
TEXT_EFFECT TButton::textEffect(const std::string& effect)
3292
{
3293
    DECL_TRACER("TButton::textEffect(const std::string& effect)");
3294
 
3295
    if (effect == "Outline-S")
3296
        return EFFECT_OUTLINE_S;
3297
    else if (effect == "Outline-M")
3298
        return EFFECT_OUTLINE_M;
3299
    else if (effect == "Outline-L")
3300
        return EFFECT_OUTLINE_L;
3301
    else if (effect == "Outline-X")
3302
        return EFFECT_OUTLINE_X;
3303
    else if (effect == "Glow-S")
3304
        return EFFECT_GLOW_S;
3305
    else if (effect == "Glow-M")
3306
        return EFFECT_GLOW_M;
3307
    else if (effect == "Glow-L")
3308
        return EFFECT_GLOW_L;
3309
    else if (effect == "Glow-X")
3310
        return EFFECT_GLOW_X;
3311
    else if (effect == "Soft Drop Shadow 1")
3312
        return EFFECT_SOFT_DROP_SHADOW_1;
3313
    else if (effect == "Soft Drop Shadow 2")
3314
        return EFFECT_SOFT_DROP_SHADOW_2;
3315
    else if (effect == "Soft Drop Shadow 3")
3316
        return EFFECT_SOFT_DROP_SHADOW_3;
3317
    else if (effect == "Soft Drop Shadow 4")
3318
        return EFFECT_SOFT_DROP_SHADOW_4;
3319
    else if (effect == "Soft Drop Shadow 5")
3320
        return EFFECT_SOFT_DROP_SHADOW_5;
3321
    else if (effect == "Soft Drop Shadow 6")
3322
        return EFFECT_SOFT_DROP_SHADOW_6;
3323
    else if (effect == "Soft Drop Shadow 7")
3324
        return EFFECT_SOFT_DROP_SHADOW_7;
3325
    else if (effect == "Soft Drop Shadow 8")
3326
        return EFFECT_SOFT_DROP_SHADOW_8;
3327
    else if (effect == "Medium Drop Shadow 1")
3328
        return EFFECT_MEDIUM_DROP_SHADOW_1;
3329
    else if (effect == "Medium Drop Shadow 2")
3330
        return EFFECT_MEDIUM_DROP_SHADOW_2;
3331
    else if (effect == "Medium Drop Shadow 3")
3332
        return EFFECT_MEDIUM_DROP_SHADOW_3;
3333
    else if (effect == "Medium Drop Shadow 4")
3334
        return EFFECT_MEDIUM_DROP_SHADOW_4;
3335
    else if (effect == "Medium Drop Shadow 5")
3336
        return EFFECT_MEDIUM_DROP_SHADOW_5;
3337
    else if (effect == "Medium Drop Shadow 6")
3338
        return EFFECT_MEDIUM_DROP_SHADOW_6;
3339
    else if (effect == "Medium Drop Shadow 7")
3340
        return EFFECT_MEDIUM_DROP_SHADOW_7;
3341
    else if (effect == "Medium Drop Shadow 8")
3342
        return EFFECT_MEDIUM_DROP_SHADOW_8;
3343
    else if (effect == "Hard Drop Shadow 1")
3344
        return EFFECT_HARD_DROP_SHADOW_1;
3345
    else if (effect == "Hard Drop Shadow 2")
3346
        return EFFECT_HARD_DROP_SHADOW_2;
3347
    else if (effect == "Hard Drop Shadow 3")
3348
        return EFFECT_HARD_DROP_SHADOW_3;
3349
    else if (effect == "Hard Drop Shadow 4")
3350
        return EFFECT_HARD_DROP_SHADOW_4;
3351
    else if (effect == "Hard Drop Shadow 5")
3352
        return EFFECT_HARD_DROP_SHADOW_5;
3353
    else if (effect == "Hard Drop Shadow 6")
3354
        return EFFECT_HARD_DROP_SHADOW_6;
3355
    else if (effect == "Hard Drop Shadow 7")
3356
        return EFFECT_HARD_DROP_SHADOW_7;
3357
    else if (effect == "Hard Drop Shadow 8")
3358
        return EFFECT_HARD_DROP_SHADOW_8;
3359
    else if (effect == "Soft Drop Shadow 1 with outline")
3360
        return EFFECT_SOFT_DROP_SHADOW_1_WITH_OUTLINE;
3361
    else if (effect == "Soft Drop Shadow 2 with outline")
3362
        return EFFECT_SOFT_DROP_SHADOW_2_WITH_OUTLINE;
3363
    else if (effect == "Soft Drop Shadow 3 with outline")
3364
        return EFFECT_SOFT_DROP_SHADOW_3_WITH_OUTLINE;
3365
    else if (effect == "Soft Drop Shadow 4 with outline")
3366
        return EFFECT_SOFT_DROP_SHADOW_4_WITH_OUTLINE;
3367
    else if (effect == "Soft Drop Shadow 5 with outline")
3368
        return EFFECT_SOFT_DROP_SHADOW_5_WITH_OUTLINE;
3369
    else if (effect == "Soft Drop Shadow 6 with outline")
3370
        return EFFECT_SOFT_DROP_SHADOW_6_WITH_OUTLINE;
3371
    else if (effect == "Soft Drop Shadow 7 with outline")
3372
        return EFFECT_SOFT_DROP_SHADOW_7_WITH_OUTLINE;
3373
    else if (effect == "Soft Drop Shadow 8 with outline")
3374
        return EFFECT_SOFT_DROP_SHADOW_8_WITH_OUTLINE;
3375
    else if (effect == "Medium Drop Shadow 1 with outline")
3376
        return EFFECT_MEDIUM_DROP_SHADOW_1_WITH_OUTLINE;
3377
    else if (effect == "Medium Drop Shadow 2 with outline")
3378
        return EFFECT_MEDIUM_DROP_SHADOW_2_WITH_OUTLINE;
3379
    else if (effect == "Medium Drop Shadow 3 with outline")
3380
        return EFFECT_MEDIUM_DROP_SHADOW_3_WITH_OUTLINE;
3381
    else if (effect == "Medium Drop Shadow 4 with outline")
3382
        return EFFECT_MEDIUM_DROP_SHADOW_4_WITH_OUTLINE;
3383
    else if (effect == "Medium Drop Shadow 5 with outline")
3384
        return EFFECT_MEDIUM_DROP_SHADOW_5_WITH_OUTLINE;
3385
    else if (effect == "Medium Drop Shadow 6 with outline")
3386
        return EFFECT_MEDIUM_DROP_SHADOW_6_WITH_OUTLINE;
3387
    else if (effect == "Medium Drop Shadow 7 with outline")
3388
        return EFFECT_MEDIUM_DROP_SHADOW_7_WITH_OUTLINE;
3389
    else if (effect == "Medium Drop Shadow 8 with outline")
3390
        return EFFECT_MEDIUM_DROP_SHADOW_8_WITH_OUTLINE;
3391
    else if (effect == "Hard Drop Shadow 1 with outline")
3392
        return EFFECT_HARD_DROP_SHADOW_1_WITH_OUTLINE;
3393
    else if (effect == "Hard Drop Shadow 2 with outline")
3394
        return EFFECT_HARD_DROP_SHADOW_2_WITH_OUTLINE;
3395
    else if (effect == "Hard Drop Shadow 3 with outline")
3396
        return EFFECT_HARD_DROP_SHADOW_3_WITH_OUTLINE;
3397
    else if (effect == "Hard Drop Shadow 4 with outline")
3398
        return EFFECT_HARD_DROP_SHADOW_4_WITH_OUTLINE;
3399
    else if (effect == "Hard Drop Shadow 5 with outline")
3400
        return EFFECT_HARD_DROP_SHADOW_5_WITH_OUTLINE;
3401
    else if (effect == "Hard Drop Shadow 6 with outline")
3402
        return EFFECT_HARD_DROP_SHADOW_6_WITH_OUTLINE;
3403
    else if (effect == "Hard Drop Shadow 7 with outline")
3404
        return EFFECT_HARD_DROP_SHADOW_7_WITH_OUTLINE;
3405
    else if (effect == "Hard Drop Shadow 8 with outline")
3406
        return EFFECT_HARD_DROP_SHADOW_8_WITH_OUTLINE;
3407
 
3408
    return EFFECT_NONE;
3409
}
15 andreas 3410
 
3411
bool TButton::isSystemButton()
3412
{
3413
    DECL_TRACER("TButton::isSystemButton()");
3414
 
3415
    int i = 0;
3416
 
3417
    while (sysButtons[i].channel)
3418
    {
3419
        if (ap == 0 && ad == sysButtons[i].channel)
3420
            return true;
3421
 
3422
        i++;
3423
    }
3424
 
3425
    return false;
3426
}
21 andreas 3427
 
3428
THR_REFRESH_t *TButton::_addResource(TImageRefresh* refr, ulong handle, ulong parent, int bi)
3429
{
3430
    DECL_TRACER("TButton::_addResource(TImageRefresh* refr, ulong handle, ulong parent, int bi)");
3431
 
3432
    THR_REFRESH_t *p = mThrRefresh;
3433
    THR_REFRESH_t *r, *last = p;
3434
 
3435
    if (!refr || !handle || !parent || bi <= 0)
3436
    {
3437
        MSG_ERROR("Invalid parameter!");
3438
        return nullptr;
3439
    }
3440
 
3441
    r = new THR_REFRESH_t;
3442
    r->mImageRefresh = refr;
3443
    r->handle = handle;
3444
    r->parent = parent;
3445
    r->bi = bi;
3446
    r->next = nullptr;
3447
 
3448
    // If the chain is empty, add the new item;
3449
    if (!mThrRefresh)
3450
        mThrRefresh = r;
3451
    else    // Find the end and append the item
3452
    {
3453
        while (p)
3454
        {
3455
            last = p;
3456
 
3457
            if (p->handle == handle && p->parent == parent && p->bi == bi)
3458
            {
3459
                MSG_WARNING("Duplicate button found! Didn't add it again.");
3460
                delete r;
3461
                return p;
3462
            }
3463
 
3464
            p = p->next;
3465
        }
3466
 
3467
        last->next = r;
3468
    }
3469
 
3470
    MSG_DEBUG("New dynamic button added.");
3471
    return r;
3472
}
3473
 
3474
THR_REFRESH_t *TButton::_findResource(ulong handle, ulong parent, int bi)
3475
{
3476
    DECL_TRACER("TButton::_findResource(ulong handle, ulong parent, int bi)");
3477
 
3478
    THR_REFRESH_t *p = mThrRefresh;
3479
 
3480
    while (p)
3481
    {
3482
        if (p->handle == handle && p->parent == parent && p->bi == bi)
3483
            return p;
3484
 
3485
        p = p->next;
3486
    }
3487
 
3488
    return nullptr;
3489
}