GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/gui/theme.cpp Lines: 394 469 84.0 %
Date: 2021-03-17 Branches: 656 1362 48.2 %

Line Branch Exec Source
1
/*
2
 *  The ManaPlus Client
3
 *  Copyright (C) 2008  The Legend of Mazzeroth Development Team
4
 *  Copyright (C) 2009  Aethyra Development Team
5
 *  Copyright (C) 2009  The Mana World Development Team
6
 *  Copyright (C) 2009-2010  The Mana Developers
7
 *  Copyright (C) 2011-2019  The ManaPlus Developers
8
 *  Copyright (C) 2019-2021  Andrei Karas
9
 *
10
 *  This file is part of The ManaPlus Client.
11
 *
12
 *  This program is free software; you can redistribute it and/or modify
13
 *  it under the terms of the GNU General Public License as published by
14
 *  the Free Software Foundation; either version 2 of the License, or
15
 *  any later version.
16
 *
17
 *  This program is distributed in the hope that it will be useful,
18
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20
 *  GNU General Public License for more details.
21
 *
22
 *  You should have received a copy of the GNU General Public License
23
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
24
 */
25
26
#include "gui/theme.h"
27
28
#include "configuration.h"
29
#include "graphicsmanager.h"
30
31
#include "const/gui/theme.h"
32
33
#include "fs/virtfs/fs.h"
34
#include "fs/virtfs/list.h"
35
36
#include "gui/skin.h"
37
#include "gui/themeinfo.h"
38
39
#include "resources/imagerect.h"
40
41
#include "resources/dye/dyepalette.h"
42
43
#include "resources/image/image.h"
44
45
#include "resources/loaders/imageloader.h"
46
#include "resources/loaders/imagesetloader.h"
47
#include "resources/loaders/subimageloader.h"
48
#include "resources/loaders/subimagesetloader.h"
49
#include "resources/loaders/xmlloader.h"
50
51
#include "utils/dtor.h"
52
#include "utils/foreach.h"
53
54
#include "debug.h"
55
56
1
static std::string defaultThemePath;
57
58
1
std::string Theme::mThemePath;
59
1
std::string Theme::mThemeName;
60
1
std::string Theme::mScreenDensity;
61
62
Theme *theme = nullptr;
63
64
// Set the theme path...
65
454
static void initDefaultThemePath()
66
{
67
2270
    defaultThemePath = branding.getStringValue("guiThemePath");
68
69
908
    logger->log("defaultThemePath: " + defaultThemePath);
70

1816
    if (!defaultThemePath.empty() &&
71

1362
        VirtFs::isDirectory(defaultThemePath))
72
    {
73
        return;
74
    }
75
    defaultThemePath = "themes/";
76
}
77
78
227
Theme::Theme() :
79
    Palette(CAST_S32(ThemeColorId::THEME_COLORS_END) * THEME_PALETTES),
80
    mSkins(),
81
    mMinimumOpacity(-1.0F),
82
    mProgressColors(ProgressColors(CAST_SIZE(
83
1135
                    ProgressColorId::THEME_PROG_END)))
84
{
85
227
    initDefaultThemePath();
86
87

908
    config.addListener("guialpha", this);
88
89
227
    mColors[CAST_SIZE(ThemeColorId::HIGHLIGHT)].ch = 'H';
90
227
    mColors[CAST_SIZE(ThemeColorId::CHAT)].ch = 'C';
91
227
    mColors[CAST_SIZE(ThemeColorId::GM)].ch = 'G';
92
227
    mColors[CAST_SIZE(ThemeColorId::GLOBAL)].ch = 'g';
93
227
    mColors[CAST_SIZE(ThemeColorId::PLAYER)].ch = 'Y';
94
227
    mColors[CAST_SIZE(ThemeColorId::WHISPER_TAB)].ch = 'W';
95
227
    mColors[CAST_SIZE(ThemeColorId::WHISPER_TAB_OFFLINE)].ch = 'w';
96
227
    mColors[CAST_SIZE(ThemeColorId::IS)].ch = 'I';
97
227
    mColors[CAST_SIZE(ThemeColorId::PARTY_CHAT_TAB)].ch = 'P';
98
227
    mColors[CAST_SIZE(ThemeColorId::GUILD_CHAT_TAB)].ch = 'U';
99
227
    mColors[CAST_SIZE(ThemeColorId::SERVER)].ch = 'S';
100
227
    mColors[CAST_SIZE(ThemeColorId::LOGGER)].ch = 'L';
101
227
    mColors[CAST_SIZE(ThemeColorId::HYPERLINK)].ch = '<';
102
227
    mColors[CAST_SIZE(ThemeColorId::SELFNICK)].ch = 's';
103
227
    mColors[CAST_SIZE(ThemeColorId::OLDCHAT)].ch = 'o';
104
227
    mColors[CAST_SIZE(ThemeColorId::AWAYCHAT)].ch = 'a';
105
227
    mCharColors['H'] = CAST_S32(ThemeColorId::HIGHLIGHT);
106
227
    mCharColors['C'] = CAST_S32(ThemeColorId::CHAT);
107
227
    mCharColors['G'] = CAST_S32(ThemeColorId::GM);
108
227
    mCharColors['g'] = CAST_S32(ThemeColorId::GLOBAL);
109
227
    mCharColors['Y'] = CAST_S32(ThemeColorId::PLAYER);
110
227
    mCharColors['W'] = CAST_S32(ThemeColorId::WHISPER_TAB);
111
227
    mCharColors['w'] = CAST_S32(ThemeColorId::WHISPER_TAB_OFFLINE);
112
227
    mCharColors['I'] = CAST_S32(ThemeColorId::IS);
113
227
    mCharColors['P'] = CAST_S32(ThemeColorId::PARTY_CHAT_TAB);
114
227
    mCharColors['U'] = CAST_S32(ThemeColorId::GUILD_CHAT_TAB);
115
227
    mCharColors['S'] = CAST_S32(ThemeColorId::SERVER);
116
227
    mCharColors['L'] = CAST_S32(ThemeColorId::LOGGER);
117
227
    mCharColors['<'] = CAST_S32(ThemeColorId::HYPERLINK);
118
227
    mCharColors['s'] = CAST_S32(ThemeColorId::SELFNICK);
119
227
    mCharColors['o'] = CAST_S32(ThemeColorId::OLDCHAT);
120
227
    mCharColors['a'] = CAST_S32(ThemeColorId::AWAYCHAT);
121
122
    // here need use outlined colors
123
454
    mCharColors['H' | 0x80]
124
227
        = CAST_S32(ThemeColorId::HIGHLIGHT_OUTLINE);
125
227
    mCharColors['C' | 0x80] = CAST_S32(ThemeColorId::CHAT_OUTLINE);
126
227
    mCharColors['G' | 0x80] = CAST_S32(ThemeColorId::GM_OUTLINE);
127
227
    mCharColors['g' | 0x80] = CAST_S32(ThemeColorId::GLOBAL_OUTLINE);
128
227
    mCharColors['Y' | 0x80] = CAST_S32(ThemeColorId::PLAYER_OUTLINE);
129
454
    mCharColors['W' | 0x80]
130
227
        = CAST_S32(ThemeColorId::WHISPER_TAB_OUTLINE);
131
454
    mCharColors['w' | 0x80]
132
227
        = CAST_S32(ThemeColorId::WHISPER_TAB_OFFLINE_OUTLINE);
133
227
    mCharColors['I' | 0x80] = CAST_S32(ThemeColorId::IS_OUTLINE);
134
454
    mCharColors['P' | 0x80]
135
227
        = CAST_S32(ThemeColorId::PARTY_CHAT_TAB_OUTLINE);
136
454
    mCharColors['U' | 0x80]
137
227
        = CAST_S32(ThemeColorId::GUILD_CHAT_TAB_OUTLINE);
138
227
    mCharColors['S' | 0x80] = CAST_S32(ThemeColorId::SERVER_OUTLINE);
139
227
    mCharColors['L' | 0x80] = CAST_S32(ThemeColorId::LOGGER_OUTLINE);
140
454
    mCharColors['<' | 0x80]
141
227
        = CAST_S32(ThemeColorId::HYPERLINK_OUTLINE);
142
227
    mCharColors['s' | 0x80] = CAST_S32(ThemeColorId::SELFNICK_OUTLINE);
143
227
    mCharColors['o' | 0x80] = CAST_S32(ThemeColorId::OLDCHAT_OUTLINE);
144
227
    mCharColors['a' | 0x80] = CAST_S32(ThemeColorId::AWAYCHAT_OUTLINE);
145
227
}
146
147
1284
Theme::~Theme()
148
{
149
428
    delete_all(mSkins);
150
856
    config.removeListener("guialpha", this);
151
    CHECKLISTENERS
152
428
    delete_all(mProgressColors);
153
428
}
154
155
24
Color Theme::getProgressColor(const ProgressColorIdT type,
156
                              const float progress)
157
{
158
24
    int color[3] = {0, 0, 0};
159
160
24
    if (theme != nullptr)
161
    {
162
        const DyePalette *const dye
163
48
            = theme->mProgressColors[CAST_SIZE(type)];
164
165
24
        if (dye != nullptr)
166
        {
167
24
            dye->getColor(progress, color);
168
        }
169
        else
170
        {
171
            logger->log("color not found: "
172
                + toString(CAST_S32(type)));
173
        }
174
    }
175
176
24
    return Color(color[0], color[1], color[2], 255U);
177
}
178
179
2232
Skin *Theme::load(const std::string &filename,
180
                  const std::string &filename2,
181
                  const bool full,
182
                  const std::string &restrict defaultPath)
183
{
184
    // Check if this skin was already loaded
185
186
4464
    const SkinIterator skinIterator = mSkins.find(filename);
187
4464
    if (mSkins.end() != skinIterator)
188
    {
189
1394
        if (skinIterator->second != nullptr)
190
1394
            skinIterator->second->instances++;
191
        return skinIterator->second;
192
    }
193
194
838
    Skin *skin = nullptr;
195
838
    if (mScreenDensity.empty())
196
    {   // if no density detected
197
838
        skin = readSkin(filename, full);
198


1016
        if ((skin == nullptr) && !filename2.empty() && filename2 != filename)
199
89
            skin = readSkin(filename2, full);
200

839
        if ((skin == nullptr) && filename2 != "window.xml")
201
4
            skin = readSkin("window.xml", full);
202
    }
203
    else
204
    {   // first use correct density images
205
        const std::string endStr("_" + mScreenDensity + ".xml");
206
        std::string name = filename;
207
        if (findCutLast(name, ".xml"))
208
            skin = readSkin(name + endStr, full);
209
        if (skin == nullptr)
210
            skin = readSkin(filename, full);
211
        if ((skin == nullptr) && !filename2.empty() && filename2 != filename)
212
        {
213
            name = filename2;
214
            if (findCutLast(name, ".xml"))
215
                skin = readSkin(name + endStr, full);
216
            if (skin == nullptr)
217
                skin = readSkin(filename2, full);
218
        }
219
        if ((skin == nullptr) && filename2 != "window.xml")
220
        {
221
            skin = readSkin("window" + endStr, full);
222
            if (skin == nullptr)
223
                skin = readSkin("window.xml", full);
224
        }
225
    }
226
227
838
    if (skin == nullptr)
228
    {
229
        // Try falling back on the defaultPath if this makes sense
230
        if (filename != defaultPath)
231
        {
232
            logger->log("Error loading skin '%s', falling back on default.",
233
                        filename.c_str());
234
235
            skin = readSkin(defaultPath, full);
236
        }
237
238
        if (skin == nullptr)
239
        {
240
            logger->log(strprintf("Error: Loading default skin '%s' failed. "
241
                                  "Make sure the skin file is valid.",
242
                                  defaultPath.c_str()));
243
        }
244
    }
245
246
838
    mSkins[filename] = skin;
247
838
    return skin;
248
}
249
250
2210
void Theme::unload(Skin *const skin)
251
{
252
2210
    if (skin == nullptr)
253
        return;
254
2210
    skin->instances --;
255
2210
    if (skin->instances == 0)
256
    {
257
1664
        SkinIterator it = mSkins.begin();
258
1664
        const SkinIterator it_end = mSkins.end();
259
3332
        while (it != it_end)
260
        {
261
3332
            if (it->second == skin)
262
            {
263
832
                mSkins.erase(it);
264
                break;
265
            }
266
            ++ it;
267
        }
268
832
        delete skin;
269
    }
270
}
271
272
void Theme::setMinimumOpacity(const float minimumOpacity)
273
{
274
    if (minimumOpacity > 1.0F)
275
        return;
276
277
    mMinimumOpacity = minimumOpacity;
278
    updateAlpha();
279
}
280
281
2873
void Theme::updateAlpha()
282
{
283
8619
    FOR_EACH (SkinIterator, iter, mSkins)
284
    {
285
        Skin *const skin = iter->second;
286
        if (skin != nullptr)
287
            skin->updateAlpha(mMinimumOpacity);
288
    }
289
2873
}
290
291
2873
void Theme::optionChanged(const std::string &name A_UNUSED)
292
{
293
2873
    updateAlpha();
294
2873
}
295
296
78
struct SkinParameter final
297
{
298
    A_DEFAULT_COPY(SkinParameter)
299
    int index;
300
    std::string name;
301
};
302
303
3
static const SkinParameter skinParam[] =
304
{
305
    {0, "top-left-corner"},
306
    {0, "standart"},
307
    {0, "up"},
308
    {0, "hstart"},
309
    {0, "in"},
310
    {0, "normal"},
311
    {1, "top-edge"},
312
    {1, "highlighted"},
313
    {1, "down"},
314
    {1, "hmiddle"},
315
    {1, "in-highlighted"},
316
    {1, "checked"},
317
    {2, "top-right-corner"},
318
    {2, "pressed"},
319
    {2, "left"},
320
    {2, "hend"},
321
    {2, "out"},
322
    {2, "disabled"},
323
    {3, "left-edge"},
324
    {3, "disabled"},
325
    {3, "right"},
326
    {3, "hgrip"},
327
    {3, "out-highlighted"},
328
    {3, "disabled-checked"},
329
    {4, "bg-quad"},
330
    {4, "vstart"},
331
    {4, "normal-highlighted"},
332
    {5, "right-edge"},
333
    {5, "vmiddle"},
334
    {5, "checked-highlighted"},
335
    {6, "bottom-left-corner"},
336
    {6, "vend"},
337
    {7, "bottom-edge"},
338
    {7, "vgrip"},
339
    {8, "bottom-right-corner"},
340


















71
};
341
342
3
static const SkinParameter imageParam[] =
343
{
344
    {0, "closeImage"},
345
    {1, "closeImageHighlighted"},
346
    {2, "stickyImageUp"},
347
    {3, "stickyImageDown"},
348


9
};
349
350
1676
struct SkinHelper final
351
{
352
838
    SkinHelper() :
353
        partType(),
354
        xPos(),
355
        yPos(),
356
        width(),
357
        height(),
358
        rect(),
359
        node(),
360
1676
        image()
361
    {
362
    }
363
364
    A_DELETE_COPY(SkinHelper)
365
366
    std::string partType;
367
    int xPos;
368
    int yPos;
369
    int width;
370
    int height;
371
    ImageRect *rect;
372
    XmlNodePtr *node;
373
    Image *image;
374
375
5121
    bool loadList(const SkinParameter *const params,
376
                  const size_t size) A_NONNULL(2)
377
    {
378
103128
        for (size_t f = 0; f < size; f ++)
379
        {
380
102928
            const SkinParameter &param = params[f];
381
102928
            if (partType == param.name)
382
            {
383
4921
                rect->grid[param.index] = Loader::getSubImage(
384
                    image,
385
                    xPos, yPos,
386
                    width, height);
387
4921
                return true;
388
            }
389
        }
390
        return false;
391
    }
392
};
393
394
928
Skin *Theme::readSkin(const std::string &filename, const bool full)
395
{
396
928
    if (filename.empty())
397
        return nullptr;
398
399
922
    const std::string path = resolveThemePath(filename);
400

1844
    if (!VirtFs::exists(path))
401
        return nullptr;
402
    XML::Document *const doc = Loader::getXml(path,
403
        UseVirtFs_true,
404
838
        SkipError_true);
405
838
    if (doc == nullptr)
406
        return nullptr;
407
838
    XmlNodeConstPtr rootNode = doc->rootNode();
408


838
    if ((rootNode == nullptr) || !xmlNameEqual(rootNode, "skinset"))
409
    {
410
        doc->decRef();
411
        return nullptr;
412
    }
413
414

3352
    const std::string skinSetImage = XML::getProperty(rootNode, "image", "");
415
416
838
    if (skinSetImage.empty())
417
    {
418
        logger->log1("Theme::readSkin(): Skinset does not define an image!");
419
        doc->decRef();
420
        return nullptr;
421
    }
422
423
838
    Image *const dBorders = Theme::getImageFromTheme(skinSetImage);
424
1676
    ImageRect *const border = new ImageRect;
425
1676
    ImageRect *const images = new ImageRect;
426
838
    int padding = 3;
427
838
    int titlePadding = 4;
428
838
    int titlebarHeight = 0;
429
838
    int titlebarHeightRelative = 0;
430
838
    int closePadding = 3;
431
838
    int stickySpacing = 3;
432
838
    int stickyPadding = 3;
433
838
    int resizePadding = 2;
434
1676
    StringIntMap *const mOptions = new StringIntMap;
435
436
    // iterate <widget>'s
437
3352
    for_each_xml_child_node(widgetNode, rootNode)
438
    {
439

2514
        if (!xmlNameEqual(widgetNode, "widget"))
440
1676
            continue;
441
442
        const std::string widgetType =
443

4190
                XML::getProperty(widgetNode, "type", "unknown");
444
838
        if (widgetType == "Window")
445
        {
446
1676
            SkinHelper helper;
447
838
            const int globalXPos = XML::getProperty(widgetNode, "xpos", 0);
448
838
            const int globalYPos = XML::getProperty(widgetNode, "ypos", 0);
449
18348
            for_each_xml_child_node(partNode, widgetNode)
450
            {
451

17510
                if (xmlNameEqual(partNode, "part"))
452
                {
453

27090
                    helper.partType = XML::getProperty(
454
5418
                        partNode, "type", "unknown");
455
10836
                    helper.xPos = XML::getProperty(
456
5418
                        partNode, "xpos", 0) + globalXPos;
457
10836
                    helper.yPos = XML::getProperty(
458
5418
                        partNode, "ypos", 0) + globalYPos;
459
5418
                    helper.width = XML::getProperty(partNode, "width", 0);
460
5418
                    helper.height = XML::getProperty(partNode, "height", 0);
461

5418
                    if ((helper.width == 0) || (helper.height == 0))
462
                        continue;
463
4921
                    helper.image = dBorders;
464
465
4921
                    helper.rect = border;
466

4921
                    if (!helper.loadList(skinParam,
467
                        sizeof(skinParam) / sizeof(SkinParameter)))
468
                    {
469
200
                        helper.rect = images;
470
                        helper.loadList(imageParam,
471
200
                            sizeof(imageParam) / sizeof(SkinParameter));
472
                    }
473
                }
474


12092
                else if (full && xmlNameEqual(partNode, "option"))
475
                {
476
                    const std::string name = XML::getProperty(
477

6160
                        partNode, "name", "");
478
1232
                    if (name == "padding")
479
                    {
480
466
                        padding = XML::getProperty(partNode, "value", 3);
481
                    }
482
766
                    else if (name == "titlePadding")
483
                    {
484
1
                        titlePadding = XML::getProperty(partNode, "value", 4);
485
                    }
486
765
                    else if (name == "closePadding")
487
                    {
488
                        closePadding = XML::getProperty(partNode, "value", 3);
489
                    }
490
765
                    else if (name == "stickySpacing")
491
                    {
492
41
                        stickySpacing = XML::getProperty(partNode, "value", 3);
493
                    }
494
724
                    else if (name == "stickyPadding")
495
                    {
496
                        stickyPadding = XML::getProperty(partNode, "value", 3);
497
                    }
498
724
                    else if (name == "titlebarHeight")
499
                    {
500
                        titlebarHeight = XML::getProperty(
501
57
                            partNode, "value", 0);
502
                    }
503
667
                    else if (name == "titlebarHeightRelative")
504
                    {
505
                        titlebarHeightRelative = XML::getProperty(
506
                            partNode, "value", 0);
507
                    }
508
667
                    else if (name == "resizePadding")
509
                    {
510
                        resizePadding = XML::getProperty(
511
                            partNode, "value", 2);
512
                    }
513
                    else
514
                    {
515

667
                        (*mOptions)[name] = XML::getProperty(
516
                            partNode, "value", 0);
517
                    }
518
                }
519
            }
520
        }
521
        else
522
        {
523
            logger->log("Theme::readSkin(): Unknown widget type '%s'",
524
                        widgetType.c_str());
525
        }
526
    }
527
528
838
    if (dBorders != nullptr)
529
838
        dBorders->decRef();
530
531

3352
    (*mOptions)["closePadding"] = closePadding;
532

3352
    (*mOptions)["stickyPadding"] = stickyPadding;
533

3352
    (*mOptions)["stickySpacing"] = stickySpacing;
534

3352
    (*mOptions)["titlebarHeight"] = titlebarHeight;
535

3352
    (*mOptions)["titlebarHeightRelative"] = titlebarHeightRelative;
536

3352
    (*mOptions)["resizePadding"] = resizePadding;
537
538
    Skin *const skin = new Skin(border, images, filename, "", padding,
539

3352
        titlePadding, mOptions);
540
838
    delete images;
541
838
    skin->updateAlpha(mMinimumOpacity);
542
838
    doc->decRef();
543
    return skin;
544
}
545
546
227
bool Theme::tryThemePath(const std::string &themeName)
547
{
548
227
    if (!themeName.empty())
549
    {
550
227
        const std::string path = defaultThemePath + themeName;
551

454
        if (VirtFs::exists(path))
552
        {
553
227
            mThemePath = path;
554
227
            mThemeName = themeName;
555
227
            if (theme != nullptr)
556

908
                theme->loadColors("");
557
454
            return true;
558
        }
559
    }
560
561
    return false;
562
}
563
564
2
void Theme::fillSkinsList(StringVect &list)
565
{
566

10
    VirtFs::getDirs(branding.getStringValue("guiThemePath"), list);
567
2
    std::sort(list.begin(), list.end());
568
2
}
569
570
2
void Theme::fillFontsList(StringVect &list)
571
{
572
2
    VirtFs::permitLinks(true);
573

10
    VirtFs::getFiles(branding.getStringValue("fontsPath"), list);
574
4
    std::sort(list.begin(), list.end());
575
2
    VirtFs::permitLinks(false);
576
2
}
577
578
2
void Theme::fillSoundsList(StringVect &list)
579
{
580
    VirtFs::List *const skins = VirtFs::enumerateFiles(
581

10
        branding.getStringValue("systemsounds"));
582
583
58
    FOR_EACH (StringVectCIter, i, skins->names)
584
    {
585

276
        if (!VirtFs::isDirectory((branding.getStringValue(
586
138
            "systemsounds") + *i)))
587
        {
588
138
            std::string str = *i;
589

184
            if (findCutLast(str, ".ogg"))
590
38
                list.push_back(str);
591
        }
592
    }
593
594
2
    VirtFs::freeList(skins);
595
4
    std::sort(list.begin(), list.end());
596
2
}
597
598
227
void Theme::selectSkin()
599
{
600
227
    prepareThemePath();
601
454
    mScreenDensity = graphicsManager.getDensityString();
602
227
}
603
604
227
void Theme::prepareThemePath()
605
{
606
227
    initDefaultThemePath();
607
608
227
    mThemePath.clear();
609
227
    mThemeName.clear();
610
611
    // Try theme from settings
612

1135
    if (tryThemePath(config.getStringValue("theme")))
613
        return;
614
615
    // Try theme from branding
616
    if (tryThemePath(branding.getStringValue("theme")))
617
        return;
618
619
    if (mThemePath.empty())
620
        mThemePath = "graphics/gui";
621
622
    theme->loadColors(mThemePath);
623
624
    logger->log("Selected Theme: " + mThemePath);
625
}
626
627
3436
std::string Theme::resolveThemePath(const std::string &path)
628
{
629
    // Need to strip off any dye info for the existence tests
630
3436
    const int pos = CAST_S32(path.find('|'));
631
6872
    std::string file;
632
3436
    if (pos > 0)
633
        file = path.substr(0, pos);
634
    else
635
        file = path;
636
637
    // File with path
638
3436
    if (file.find('/') != std::string::npos)
639
    {
640
        // Might be a valid path already
641
        if (VirtFs::exists(file))
642
            return path;
643
    }
644
645
    // Try the theme
646
10308
    file = pathJoin(getThemePath(), file);
647
648

6872
    if (VirtFs::exists(file))
649
3930
        return pathJoin(getThemePath(), path);
650
651
    // Backup
652

7355
    return pathJoin(branding.getStringValue("guiPath"), path);
653
}
654
655
844
Image *Theme::getImageFromTheme(const std::string &path)
656
{
657
1688
    return Loader::getImage(resolveThemePath(path));
658
}
659
660
153
ImageSet *Theme::getImageSetFromTheme(const std::string &path,
661
                                      const int w, const int h)
662
{
663
306
    return Loader::getImageSet(resolveThemePath(path), w, h);
664
}
665
666
#define themeEnumStart(name) #name,
667
#define themeEnum(name) #name,
668
#define themeEnumEnd(name)
669
670
172066
static int readColorType(const std::string &type)
671
{
672
    static const std::string colors[CAST_SIZE(
673
        ThemeColorId::THEME_COLORS_END)] =
674
    {
675
#include "gui/themecolortype.inc"
676




















































































































172524
    };
677
678
172066
    if (type.empty())
679
        return -1;
680
681
42644220
    for (int i = 0; i < CAST_S32(ThemeColorId::THEME_COLORS_END); i++)
682
    {
683
21399063
        if (compareStrI(type, colors[i]) == 0)
684
            return i;
685
    }
686
687
    return -1;
688
}
689
690
THEMECOLORTYPE_VOID
691
692
#undef themeEnumStart
693
#undef themeEnum
694
#undef themeEnumEnd
695
#undef THEMECOLORTYPE_VOID
696
697
101242
static Color readColor(const std::string &description)
698
{
699
101242
    const int size = static_cast<int>(description.length());
700

202484
    if (size < 7 || description[0] != '#')
701
    {
702
        logger->log("Error, invalid theme color palette: %s",
703
                    description.c_str());
704
        return Palette::BLACK;
705
    }
706
707
    unsigned int v = 0;
708
1316146
    for (int i = 1; i < 7; ++i)
709
    {
710
1214904
        signed const char c = description[i];
711
        int n;
712
713
607452
        if ('0' <= c && c <= '9')
714
        {
715
356390
            n = c - '0';
716
        }
717
251062
        else if ('A' <= c && c <= 'F')
718
        {
719
2724
            n = c - 'A' + 10;
720
        }
721
248338
        else if ('a' <= c && c <= 'f')
722
        {
723
248338
            n = c - 'a' + 10;
724
        }
725
        else
726
        {
727
            logger->log("Error, invalid theme color palette: %s",
728
                        description.c_str());
729
            return Palette::BLACK;
730
        }
731
732
607452
        v = (v << 4) | n;
733
    }
734
735
101242
    return Color(v);
736
}
737
738
101242
static GradientTypeT readColorGradient(const std::string &grad)
739
{
740
    static const std::string grads[] =
741
    {
742
        "STATIC",
743
        "PULSE",
744
        "SPECTRUM",
745
        "RAINBOW"
746




101252
    };
747
748
101242
    if (grad.empty())
749
        return GradientType::STATIC;
750
751
908
    for (int i = 0; i < 4; i++)
752
    {
753
908
        if (compareStrI(grad, grads[i]) != 0)
754
            return static_cast<GradientTypeT>(i);
755
    }
756
757
    return GradientType::STATIC;
758
}
759
760
2724
static int readProgressType(const std::string &type)
761
{
762
    static const std::string colors[CAST_SIZE(
763
        ProgressColorId::THEME_PROG_END)] =
764
    {
765
        "HP",
766
        "HP_POISON",
767
        "MP",
768
        "NO_MP",
769
        "EXP",
770
        "INVY_SLOTS",
771
        "WEIGHT",
772
        "JOB",
773
        "UPDATE",
774
        "MONEY",
775
        "ARROWS",
776
        "STATUS"
777








2750
    };
778
779
2724
    if (type.empty())
780
        return -1;
781
782
32688
    for (int i = 0; i < CAST_S32(ProgressColorId::THEME_PROG_END); i++)
783
    {
784
17706
        if (compareStrI(type, colors[i]) == 0)
785
            return i;
786
    }
787
788
    return -1;
789
}
790
791
227
void Theme::loadColors(std::string file)
792
{
793
227
    if (file.empty())
794
        file = "colors.xml";
795
    else
796
        file = pathJoin(file, "colors.xml");
797
798
454
    XML::Document *const doc = Loader::getXml(resolveThemePath(file),
799
        UseVirtFs_true,
800
227
        SkipError_false);
801
227
    if (doc == nullptr)
802
        return;
803
227
    XmlNodeConstPtrConst root = doc->rootNode();
804
805

227
    if ((root == nullptr) || !xmlNameEqual(root, "colors"))
806
    {
807
        logger->log("Error loading colors file: %s", file.c_str());
808
        doc->decRef();
809
        return;
810
    }
811
812
227
    logger->log("Loading colors file: %s", file.c_str());
813
814
7264
    for_each_xml_child_node(paletteNode, root)
815
    {
816
7037
        if (xmlNameEqual(paletteNode, "progressbar"))
817
        {
818
13620
            const int type = readProgressType(XML::getProperty(
819
2724
                paletteNode, "id", ""));
820
2724
            if (type < 0)
821
                continue;
822
823
19068
            mProgressColors[type] = new DyePalette(XML::getProperty(
824

2724
                paletteNode, "color", ""), 6);
825
        }
826
4313
        else if (!xmlNameEqual(paletteNode, "palette"))
827
        {
828
            continue;
829
        }
830
831
3405
        const int paletteId = XML::getProperty(paletteNode, "id", 1);
832
3405
        if (paletteId < 0 || paletteId >= THEME_PALETTES)
833
            continue;
834
835
206570
        for_each_xml_child_node(node, paletteNode)
836
        {
837
203165
            if (xmlNameEqual(node, "color"))
838
            {
839
497130
                const std::string id = XML::getProperty(node, "id", "");
840
101242
                const int type = readColorType(id);
841
101242
                if (type < 0)
842
9080
                    continue;
843
844

497130
                const std::string temp = XML::getProperty(node, "color", "");
845
101242
                if (temp.empty())
846
                    continue;
847
848
101242
                const Color color = readColor(temp);
849
                const GradientTypeT grad = readColorGradient(
850

506210
                    XML::getProperty(node, "effect", ""));
851
101242
                mColors[paletteId * CAST_SIZE(
852
202484
                    ThemeColorId::THEME_COLORS_END) + type].set(
853
101242
                    type, color, grad, 10);
854
855

404968
                if (!findLast(id, "_OUTLINE"))
856
                {
857

141648
                    const int type2 = readColorType(id + "_OUTLINE");
858
70824
                    if (type2 < 0)
859
                        continue;
860
                    const int idx = paletteId
861
61744
                        * CAST_S32(ThemeColorId::THEME_COLORS_END);
862
185232
                    mColors[idx + type2] = mColors[idx + type];
863
                }
864
            }
865
        }
866
    }
867
227
    doc->decRef();
868
}
869
870
#define loadGrid() \
871
    { \
872
        const ImageRect &rect = skin->getBorder(); \
873
        for (int f = start; f <= end; f ++) \
874
        { \
875
            if (rect.grid[f]) \
876
            { \
877
                image.grid[f] = rect.grid[f]; \
878
                image.grid[f]->incRef(); \
879
            } \
880
        } \
881
    }
882
883
210
void Theme::loadRect(ImageRect &image,
884
                     const std::string &name,
885
                     const std::string &name2,
886
                     const int start,
887
                     const int end)
888
{
889
    Skin *const skin = load(name,
890
        name2,
891
        false,
892
420
        Theme::getThemePath());
893
210
    if (skin != nullptr)
894
    {
895

210
        loadGrid()
896
210
        unload(skin);
897
    }
898
210
}
899
900
43
Skin *Theme::loadSkinRect(ImageRect &image,
901
                          const std::string &name,
902
                          const std::string &name2,
903
                          const int start,
904
                          const int end)
905
{
906
    Skin *const skin = load(name,
907
        name2,
908
        true,
909
86
        Theme::getThemePath());
910
43
    if (skin != nullptr)
911

43
        loadGrid()
912
43
    return skin;
913
}
914
915
262
void Theme::unloadRect(const ImageRect &rect,
916
                       const int start,
917
                       const int end)
918
{
919
2620
    for (int f = start; f <= end; f ++)
920
    {
921
2358
        if (rect.grid[f] != nullptr)
922
1286
            rect.grid[f]->decRef();
923
    }
924
262
}
925
926
59
Image *Theme::getImageFromThemeXml(const std::string &name,
927
                                   const std::string &name2)
928
{
929
59
    if (theme == nullptr)
930
        return nullptr;
931
932
59
    Skin *const skin = theme->load(name,
933
        name2,
934
        false,
935
118
        Theme::getThemePath());
936
59
    if (skin != nullptr)
937
    {
938
59
        const ImageRect &rect = skin->getBorder();
939
59
        if (rect.grid[0] != nullptr)
940
        {
941
53
            Image *const image = rect.grid[0];
942
53
            image->incRef();
943
53
            theme->unload(skin);
944
53
            return image;
945
        }
946
6
        theme->unload(skin);
947
    }
948
    return nullptr;
949
}
950
951
1
ImageSet *Theme::getImageSetFromThemeXml(const std::string &name,
952
                                         const std::string &name2,
953
                                         const int w, const int h)
954
{
955
1
    if (theme == nullptr)
956
        return nullptr;
957
958
1
    Skin *const skin = theme->load(name,
959
        name2,
960
        false,
961
2
        Theme::getThemePath());
962
1
    if (skin != nullptr)
963
    {
964
1
        const ImageRect &rect = skin->getBorder();
965
1
        if (rect.grid[0] != nullptr)
966
        {
967
            Image *const image = rect.grid[0];
968
            const SDL_Rect &rect2 = image->mBounds;
969
            if ((rect2.w != 0U) && (rect2.h != 0U))
970
            {
971
                ImageSet *const imageSet = Loader::getSubImageSet(
972
                    image, w, h);
973
                theme->unload(skin);
974
                return imageSet;
975
            }
976
        }
977
1
        theme->unload(skin);
978
    }
979
    return nullptr;
980
}
981
982
#define readValue(name) \
983
        { \
984
            tmpData = reinterpret_cast<XmlChar*>( \
985
                XmlNodeGetContent(infoNode)); \
986
            info->name = tmpData; \
987
            XmlFree(tmpData); \
988
        }
989
990
#define readIntValue(name) \
991
        { \
992
            tmpData = reinterpret_cast<XmlChar*>( \
993
                XmlNodeGetContent(infoNode)); \
994
            info->name = atoi(tmpData); \
995
            XmlFree(tmpData); \
996
        }
997
998
#define readFloatValue(name) \
999
        { \
1000
            tmpData = reinterpret_cast<XmlChar*>( \
1001
                XmlNodeGetContent(infoNode)); \
1002
            info->name = static_cast<float>(atof(tmpData)); \
1003
            XmlFree(tmpData); \
1004
        }
1005
1006
4
ThemeInfo *Theme::loadInfo(const std::string &themeName)
1007
{
1008
8
    std::string path;
1009
4
    if (themeName.empty())
1010
    {
1011
        path = "graphics/gui/info.xml";
1012
    }
1013
    else
1014
    {
1015

28
        path = pathJoin(defaultThemePath,
1016
            themeName,
1017
4
            "info.xml");
1018
    }
1019

8
    logger->log("loading: " + path);
1020
    XML::Document *const doc = Loader::getXml(path,
1021
        UseVirtFs_true,
1022
4
        SkipError_false);
1023
4
    if (doc == nullptr)
1024
        return nullptr;
1025
4
    XmlNodeConstPtrConst rootNode = doc->rootNode();
1026
1027


4
    if ((rootNode == nullptr) || !xmlNameEqual(rootNode, "info"))
1028
    {
1029
        doc->decRef();
1030
        return nullptr;
1031
    }
1032
1033
4
    ThemeInfo *const info = new ThemeInfo;
1034
1035
4
    const std::string fontSize2("fontSize_" + mScreenDensity);
1036
8
    const std::string npcfontSize2("npcfontSize_" + mScreenDensity);
1037
4
    XmlChar *tmpData = nullptr;
1038
32
    for_each_xml_child_node(infoNode, rootNode)
1039
    {
1040

28
        if (xmlNameEqual(infoNode, "name"))
1041

8
            readValue(name)
1042

24
        else if (xmlNameEqual(infoNode, "copyright"))
1043

8
            readValue(copyright)
1044

20
        else if (xmlNameEqual(infoNode, "font"))
1045
            readValue(font)
1046

20
        else if (xmlNameEqual(infoNode, "boldFont"))
1047
            readValue(boldFont)
1048

20
        else if (xmlNameEqual(infoNode, "particleFont"))
1049
            readValue(particleFont)
1050

20
        else if (xmlNameEqual(infoNode, "helpFont"))
1051
            readValue(helpFont)
1052

20
        else if (xmlNameEqual(infoNode, "secureFont"))
1053
            readValue(secureFont)
1054

20
        else if (xmlNameEqual(infoNode, "npcFont"))
1055
            readValue(npcFont)
1056

20
        else if (xmlNameEqual(infoNode, "japanFont"))
1057
            readValue(japanFont)
1058

20
        else if (xmlNameEqual(infoNode, "chinaFont"))
1059
            readValue(chinaFont)
1060

20
        else if (xmlNameEqual(infoNode, "fontSize"))
1061
            readIntValue(fontSize)
1062

20
        else if (xmlNameEqual(infoNode, "npcfontSize"))
1063
            readIntValue(npcfontSize)
1064

20
        else if (xmlNameEqual(infoNode, "guialpha"))
1065

8
            readFloatValue(guiAlpha)
1066

16
        else if (xmlNameEqual(infoNode, fontSize2.c_str()))
1067
            readIntValue(fontSize)
1068

16
        else if (xmlNameEqual(infoNode, npcfontSize2.c_str()))
1069
            readIntValue(npcfontSize)
1070
    }
1071
4
    doc->decRef();
1072
4
    return info;
1073
}
1074
1075
4
ThemeColorIdT Theme::getIdByChar(const signed char c, bool &valid) const
1076
{
1077
8
    const CharColors::const_iterator it = mCharColors.find(c);
1078
8
    if (it != mCharColors.end())
1079
    {
1080
2
        valid = true;
1081
2
        return static_cast<ThemeColorIdT>((*it).second);
1082
    }
1083
1084
2
    valid = false;
1085
2
    return ThemeColorId::BROWSERBOX;
1086

3
}