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
432
static void initDefaultThemePath()
66
{
67
2160
    defaultThemePath = branding.getStringValue("guiThemePath");
68
69
864
    logger->log("defaultThemePath: " + defaultThemePath);
70

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

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

864
    config.addListener("guialpha", this);
88
89
216
    mColors[CAST_SIZE(ThemeColorId::HIGHLIGHT)].ch = 'H';
90
216
    mColors[CAST_SIZE(ThemeColorId::CHAT)].ch = 'C';
91
216
    mColors[CAST_SIZE(ThemeColorId::GM)].ch = 'G';
92
216
    mColors[CAST_SIZE(ThemeColorId::GLOBAL)].ch = 'g';
93
216
    mColors[CAST_SIZE(ThemeColorId::PLAYER)].ch = 'Y';
94
216
    mColors[CAST_SIZE(ThemeColorId::WHISPER_TAB)].ch = 'W';
95
216
    mColors[CAST_SIZE(ThemeColorId::WHISPER_TAB_OFFLINE)].ch = 'w';
96
216
    mColors[CAST_SIZE(ThemeColorId::IS)].ch = 'I';
97
216
    mColors[CAST_SIZE(ThemeColorId::PARTY_CHAT_TAB)].ch = 'P';
98
216
    mColors[CAST_SIZE(ThemeColorId::GUILD_CHAT_TAB)].ch = 'U';
99
216
    mColors[CAST_SIZE(ThemeColorId::SERVER)].ch = 'S';
100
216
    mColors[CAST_SIZE(ThemeColorId::LOGGER)].ch = 'L';
101
216
    mColors[CAST_SIZE(ThemeColorId::HYPERLINK)].ch = '<';
102
216
    mColors[CAST_SIZE(ThemeColorId::SELFNICK)].ch = 's';
103
216
    mColors[CAST_SIZE(ThemeColorId::OLDCHAT)].ch = 'o';
104
216
    mColors[CAST_SIZE(ThemeColorId::AWAYCHAT)].ch = 'a';
105
216
    mCharColors['H'] = CAST_S32(ThemeColorId::HIGHLIGHT);
106
216
    mCharColors['C'] = CAST_S32(ThemeColorId::CHAT);
107
216
    mCharColors['G'] = CAST_S32(ThemeColorId::GM);
108
216
    mCharColors['g'] = CAST_S32(ThemeColorId::GLOBAL);
109
216
    mCharColors['Y'] = CAST_S32(ThemeColorId::PLAYER);
110
216
    mCharColors['W'] = CAST_S32(ThemeColorId::WHISPER_TAB);
111
216
    mCharColors['w'] = CAST_S32(ThemeColorId::WHISPER_TAB_OFFLINE);
112
216
    mCharColors['I'] = CAST_S32(ThemeColorId::IS);
113
216
    mCharColors['P'] = CAST_S32(ThemeColorId::PARTY_CHAT_TAB);
114
216
    mCharColors['U'] = CAST_S32(ThemeColorId::GUILD_CHAT_TAB);
115
216
    mCharColors['S'] = CAST_S32(ThemeColorId::SERVER);
116
216
    mCharColors['L'] = CAST_S32(ThemeColorId::LOGGER);
117
216
    mCharColors['<'] = CAST_S32(ThemeColorId::HYPERLINK);
118
216
    mCharColors['s'] = CAST_S32(ThemeColorId::SELFNICK);
119
216
    mCharColors['o'] = CAST_S32(ThemeColorId::OLDCHAT);
120
216
    mCharColors['a'] = CAST_S32(ThemeColorId::AWAYCHAT);
121
122
    // here need use outlined colors
123
432
    mCharColors['H' | 0x80]
124
216
        = CAST_S32(ThemeColorId::HIGHLIGHT_OUTLINE);
125
216
    mCharColors['C' | 0x80] = CAST_S32(ThemeColorId::CHAT_OUTLINE);
126
216
    mCharColors['G' | 0x80] = CAST_S32(ThemeColorId::GM_OUTLINE);
127
216
    mCharColors['g' | 0x80] = CAST_S32(ThemeColorId::GLOBAL_OUTLINE);
128
216
    mCharColors['Y' | 0x80] = CAST_S32(ThemeColorId::PLAYER_OUTLINE);
129
432
    mCharColors['W' | 0x80]
130
216
        = CAST_S32(ThemeColorId::WHISPER_TAB_OUTLINE);
131
432
    mCharColors['w' | 0x80]
132
216
        = CAST_S32(ThemeColorId::WHISPER_TAB_OFFLINE_OUTLINE);
133
216
    mCharColors['I' | 0x80] = CAST_S32(ThemeColorId::IS_OUTLINE);
134
432
    mCharColors['P' | 0x80]
135
216
        = CAST_S32(ThemeColorId::PARTY_CHAT_TAB_OUTLINE);
136
432
    mCharColors['U' | 0x80]
137
216
        = CAST_S32(ThemeColorId::GUILD_CHAT_TAB_OUTLINE);
138
216
    mCharColors['S' | 0x80] = CAST_S32(ThemeColorId::SERVER_OUTLINE);
139
216
    mCharColors['L' | 0x80] = CAST_S32(ThemeColorId::LOGGER_OUTLINE);
140
432
    mCharColors['<' | 0x80]
141
216
        = CAST_S32(ThemeColorId::HYPERLINK_OUTLINE);
142
216
    mCharColors['s' | 0x80] = CAST_S32(ThemeColorId::SELFNICK_OUTLINE);
143
216
    mCharColors['o' | 0x80] = CAST_S32(ThemeColorId::OLDCHAT_OUTLINE);
144
216
    mCharColors['a' | 0x80] = CAST_S32(ThemeColorId::AWAYCHAT_OUTLINE);
145
216
}
146
147
1218
Theme::~Theme()
148
{
149
406
    delete_all(mSkins);
150
812
    config.removeListener("guialpha", this);
151
    CHECKLISTENERS
152
406
    delete_all(mProgressColors);
153
406
}
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
2240
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
4480
    const SkinIterator skinIterator = mSkins.find(filename);
187
4480
    if (mSkins.end() != skinIterator)
188
    {
189
1402
        if (skinIterator->second != nullptr)
190
1402
            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
2218
void Theme::unload(Skin *const skin)
251
{
252
2218
    if (skin == nullptr)
253
        return;
254
2218
    skin->instances --;
255
2218
    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
2730
void Theme::updateAlpha()
282
{
283
8190
    FOR_EACH (SkinIterator, iter, mSkins)
284
    {
285
        Skin *const skin = iter->second;
286
        if (skin != nullptr)
287
            skin->updateAlpha(mMinimumOpacity);
288
    }
289
2730
}
290
291
2730
void Theme::optionChanged(const std::string &name A_UNUSED)
292
{
293
2730
    updateAlpha();
294
2730
}
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
216
bool Theme::tryThemePath(const std::string &themeName)
547
{
548
216
    if (!themeName.empty())
549
    {
550
216
        const std::string path = defaultThemePath + themeName;
551

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

864
                theme->loadColors("");
557
432
            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
216
void Theme::selectSkin()
599
{
600
216
    prepareThemePath();
601
432
    mScreenDensity = graphicsManager.getDensityString();
602
216
}
603
604
216
void Theme::prepareThemePath()
605
{
606
216
    initDefaultThemePath();
607
608
216
    mThemePath.clear();
609
216
    mThemeName.clear();
610
611
    // Try theme from settings
612

1080
    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
3359
std::string Theme::resolveThemePath(const std::string &path)
628
{
629
    // Need to strip off any dye info for the existence tests
630
3359
    const int pos = CAST_S32(path.find('|'));
631
6718
    std::string file;
632
3359
    if (pos > 0)
633
        file = path.substr(0, pos);
634
    else
635
        file = path;
636
637
    // File with path
638
3359
    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
10077
    file = pathJoin(getThemePath(), file);
647
648

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

7025
    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
163728
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




















































































































164186
    };
677
678
163728
    if (type.empty())
679
        return -1;
680
681
40577760
    for (int i = 0; i < CAST_S32(ThemeColorId::THEME_COLORS_END); i++)
682
    {
683
20362104
        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
96336
static Color readColor(const std::string &description)
698
{
699
96336
    const int size = static_cast<int>(description.length());
700

192672
    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
1252368
    for (int i = 1; i < 7; ++i)
709
    {
710
1156032
        signed const char c = description[i];
711
        int n;
712
713
578016
        if ('0' <= c && c <= '9')
714
        {
715
339120
            n = c - '0';
716
        }
717
238896
        else if ('A' <= c && c <= 'F')
718
        {
719
2592
            n = c - 'A' + 10;
720
        }
721
236304
        else if ('a' <= c && c <= 'f')
722
        {
723
236304
            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
578016
        v = (v << 4) | n;
733
    }
734
735
96336
    return Color(v);
736
}
737
738
96336
static GradientTypeT readColorGradient(const std::string &grad)
739
{
740
    static const std::string grads[] =
741
    {
742
        "STATIC",
743
        "PULSE",
744
        "SPECTRUM",
745
        "RAINBOW"
746




96346
    };
747
748
96336
    if (grad.empty())
749
        return GradientType::STATIC;
750
751
864
    for (int i = 0; i < 4; i++)
752
    {
753
864
        if (compareStrI(grad, grads[i]) != 0)
754
            return static_cast<GradientTypeT>(i);
755
    }
756
757
    return GradientType::STATIC;
758
}
759
760
2592
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








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

216
    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
216
    logger->log("Loading colors file: %s", file.c_str());
813
814
6912
    for_each_xml_child_node(paletteNode, root)
815
    {
816
6696
        if (xmlNameEqual(paletteNode, "progressbar"))
817
        {
818
12960
            const int type = readProgressType(XML::getProperty(
819
2592
                paletteNode, "id", ""));
820
2592
            if (type < 0)
821
                continue;
822
823
18144
            mProgressColors[type] = new DyePalette(XML::getProperty(
824

2592
                paletteNode, "color", ""), 6);
825
        }
826
4104
        else if (!xmlNameEqual(paletteNode, "palette"))
827
        {
828
            continue;
829
        }
830
831
3240
        const int paletteId = XML::getProperty(paletteNode, "id", 1);
832
3240
        if (paletteId < 0 || paletteId >= THEME_PALETTES)
833
            continue;
834
835
196560
        for_each_xml_child_node(node, paletteNode)
836
        {
837
193320
            if (xmlNameEqual(node, "color"))
838
            {
839
473040
                const std::string id = XML::getProperty(node, "id", "");
840
96336
                const int type = readColorType(id);
841
96336
                if (type < 0)
842
8640
                    continue;
843
844

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

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

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

134784
                    const int type2 = readColorType(id + "_OUTLINE");
858
67392
                    if (type2 < 0)
859
                        continue;
860
                    const int idx = paletteId
861
58752
                        * CAST_S32(ThemeColorId::THEME_COLORS_END);
862
176256
                    mColors[idx + type2] = mColors[idx + type];
863
                }
864
            }
865
        }
866
    }
867
216
    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
}