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

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

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

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


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

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


















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


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

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


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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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




















































































































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

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




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








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

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

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

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

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

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

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

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

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

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

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


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

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

8
            readValue(name)
1041

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

8
            readValue(copyright)
1043

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

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

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

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

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

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

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

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

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

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

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

8
            readFloatValue(guiAlpha)
1065

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

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

3
}