GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/gui/theme.cpp Lines: 392 465 84.3 %
Date: 2018-06-18 21:15:20 Branches: 651 1362 47.8 %

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
2
static std::string defaultThemePath;
56
57
2
std::string Theme::mThemePath;
58
2
std::string Theme::mThemeName;
59
2
std::string Theme::mScreenDensity;
60
61
Theme *theme = nullptr;
62
63
// Set the theme path...
64
904
static void initDefaultThemePath()
65
{
66

4520
    defaultThemePath = branding.getStringValue("guiThemePath");
67
68
1808
    logger->log("defaultThemePath: " + defaultThemePath);
69

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

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

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

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

1678
        if ((skin == nullptr) && filename2 != "window.xml")
200

8
            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
1676
    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
1676
    mSkins[filename] = skin;
246
1676
    return skin;
247
}
248
249
4372
void Theme::unload(Skin *const skin)
250
{
251
4372
    if (skin == nullptr)
252
        return;
253
4372
    skin->instances --;
254
4372
    if (skin->instances == 0)
255
    {
256
3328
        SkinIterator it = mSkins.begin();
257
3328
        const SkinIterator it_end = mSkins.end();
258
6664
        while (it != it_end)
259
        {
260
6664
            if (it->second == skin)
261
            {
262
1664
                mSkins.erase(it);
263
                break;
264
            }
265
            ++ it;
266
        }
267
1664
        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
5746
void Theme::updateAlpha()
281
{
282
17238
    FOR_EACH (SkinIterator, iter, mSkins)
283
    {
284
        Skin *const skin = iter->second;
285
        if (skin != nullptr)
286
            skin->updateAlpha(mMinimumOpacity);
287
    }
288
5746
}
289
290
5746
void Theme::optionChanged(const std::string &name A_UNUSED)
291
{
292
5746
    updateAlpha();
293
5746
}
294
295
156
struct SkinParameter final
296
{
297
    A_DEFAULT_COPY(SkinParameter)
298
    int index;
299
    std::string name;
300
};
301
302
6
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


















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


18
};
348
349
3352
struct SkinHelper final
350
{
351
1676
    SkinHelper() :
352
        partType(),
353
        xPos(),
354
        yPos(),
355
        width(),
356
        height(),
357
        rect(),
358
        node(),
359
3352
        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
10242
    bool loadList(const SkinParameter *const params,
375
                  const size_t size) A_NONNULL(2)
376
    {
377
206256
        for (size_t f = 0; f < size; f ++)
378
        {
379
205856
            const SkinParameter &param = params[f];
380
215698
            if (partType == param.name)
381
            {
382
9842
                rect->grid[param.index] = Loader::getSubImage(
383
                    image,
384
                    xPos, yPos,
385
                    width, height);
386
9842
                return true;
387
            }
388
        }
389
        return false;
390
    }
391
};
392
393
1856
Skin *Theme::readSkin(const std::string &filename, const bool full)
394
{
395
1856
    if (filename.empty())
396
        return nullptr;
397
398
1844
    const std::string path = resolveThemePath(filename);
399

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

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

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

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

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

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

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

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

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

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

12320
                        partNode, "name", "");
477
2464
                    if (name == "padding")
478
                    {
479
932
                        padding = XML::getProperty(partNode, "value", 3);
480
                    }
481
1532
                    else if (name == "titlePadding")
482
                    {
483
2
                        titlePadding = XML::getProperty(partNode, "value", 4);
484
                    }
485
1530
                    else if (name == "closePadding")
486
                    {
487
                        closePadding = XML::getProperty(partNode, "value", 3);
488
                    }
489
1530
                    else if (name == "stickySpacing")
490
                    {
491
82
                        stickySpacing = XML::getProperty(partNode, "value", 3);
492
                    }
493
1448
                    else if (name == "stickyPadding")
494
                    {
495
                        stickyPadding = XML::getProperty(partNode, "value", 3);
496
                    }
497
1448
                    else if (name == "titlebarHeight")
498
                    {
499
114
                        titlebarHeight = XML::getProperty(
500
                            partNode, "value", 0);
501
                    }
502
1334
                    else if (name == "titlebarHeightRelative")
503
                    {
504
                        titlebarHeightRelative = XML::getProperty(
505
                            partNode, "value", 0);
506
                    }
507
1334
                    else if (name == "resizePadding")
508
                    {
509
                        resizePadding = XML::getProperty(
510
                            partNode, "value", 2);
511
                    }
512
                    else
513
                    {
514

1334
                        (*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
1676
    if (dBorders != nullptr)
528
1676
        dBorders->decRef();
529
530

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

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

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

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

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

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

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

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

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

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

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

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


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

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


2260
    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
6856
std::string Theme::resolveThemePath(const std::string &path)
627
{
628
    // Need to strip off any dye info for the existence tests
629
6856
    const int pos = CAST_S32(path.find('|'));
630
13712
    std::string file;
631
6856
    if (pos > 0)
632
        file = path.substr(0, pos);
633
    else
634
        file = path;
635
636
    // File with path
637
6856
    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
20568
    file = pathJoin(getThemePath(), file);
646
647
6856
    if (VirtFs::exists(file))
648
7852
        return pathJoin(getThemePath(), path);
649
650
    // Backup
651

14650
    return pathJoin(branding.getStringValue("guiPath"), path);
652
}
653
654
1688
Image *Theme::getImageFromTheme(const std::string &path)
655
{
656
3376
    return Loader::getImage(resolveThemePath(path));
657
}
658
659
304
ImageSet *Theme::getImageSetFromTheme(const std::string &path,
660
                                      const int w, const int h)
661
{
662
608
    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
342616
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




















































































































343532
    };
676
677
342616
    if (type.empty())
678
        return -1;
679
680
84912720
    for (int i = 0; i < CAST_S32(ThemeColorId::THEME_COLORS_END); i++)
681
    {
682
42609588
        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
201592
static Color readColor(const std::string &description)
697
{
698
201592
    const int size = static_cast<int>(description.length());
699

403184
    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
2620696
    for (int i = 1; i < 7; ++i)
708
    {
709
2419104
        signed const char c = description[i];
710
        int n;
711
712
1209552
        if ('0' <= c && c <= '9')
713
        {
714
709640
            n = c - '0';
715
        }
716
499912
        else if ('A' <= c && c <= 'F')
717
        {
718
5424
            n = c - 'A' + 10;
719
        }
720
494488
        else if ('a' <= c && c <= 'f')
721
        {
722
494488
            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
1209552
        v = (v << 4) | n;
732
    }
733
734
201592
    return Color(v);
735
}
736
737
201592
static GradientTypeT readColorGradient(const std::string &grad)
738
{
739
    static const std::string grads[] =
740
    {
741
        "STATIC",
742
        "PULSE",
743
        "SPECTRUM",
744
        "RAINBOW"
745




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








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

452
    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
904
    logger->log("Loading colors file: %s", file.c_str());
812
813
14464
    for_each_xml_child_node(paletteNode, root)
814
    {
815
14012
        if (xmlNameEqual(paletteNode, "progressbar"))
816
        {
817

27120
            const int type = readProgressType(XML::getProperty(
818
5424
                paletteNode, "id", ""));
819
5424
            if (type < 0)
820
                continue;
821
822

37968
            mProgressColors[type] = new DyePalette(XML::getProperty(
823

5424
                paletteNode, "color", ""), 6);
824
        }
825
8588
        else if (!xmlNameEqual(paletteNode, "palette"))
826
        {
827
7232
            continue;
828
        }
829
830
6780
        const int paletteId = XML::getProperty(paletteNode, "id", 1);
831
6780
        if (paletteId < 0 || paletteId >= THEME_PALETTES)
832
            continue;
833
834
411320
        for_each_xml_child_node(node, paletteNode)
835
        {
836
404540
            if (xmlNameEqual(node, "color"))
837
            {
838

989880
                const std::string id = XML::getProperty(node, "id", "");
839
201592
                const int type = readColorType(id);
840
201592
                if (type < 0)
841
18080
                    continue;
842
843

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

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

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

282048
                    const int type2 = readColorType(id + "_OUTLINE");
857
141024
                    if (type2 < 0)
858
18080
                        continue;
859
122944
                    const int idx = paletteId
860
                        * CAST_S32(ThemeColorId::THEME_COLORS_END);
861
368832
                    mColors[idx + type2] = mColors[idx + type];
862
                }
863
            }
864
        }
865
    }
866
452
    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
420
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
840
        Theme::getThemePath());
892
420
    if (skin != nullptr)
893
    {
894

420
        loadGrid();
895
420
        unload(skin);
896
    }
897
420
}
898
899
86
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
172
        Theme::getThemePath());
909
86
    if (skin != nullptr)
910

86
        loadGrid();
911
86
    return skin;
912
}
913
914
524
void Theme::unloadRect(const ImageRect &rect,
915
                       const int start,
916
                       const int end)
917
{
918
5240
    for (int f = start; f <= end; f ++)
919
    {
920
4716
        if (rect.grid[f] != nullptr)
921
2572
            rect.grid[f]->decRef();
922
    }
923
524
}
924
925
118
Image *Theme::getImageFromThemeXml(const std::string &name,
926
                                   const std::string &name2)
927
{
928
118
    if (theme == nullptr)
929
        return nullptr;
930
931
    Skin *const skin = theme->load(name,
932
        name2,
933
        false,
934
236
        Theme::getThemePath());
935
118
    if (skin != nullptr)
936
    {
937
118
        const ImageRect &rect = skin->getBorder();
938
118
        if (rect.grid[0] != nullptr)
939
        {
940
106
            Image *const image = rect.grid[0];
941
106
            image->incRef();
942
106
            theme->unload(skin);
943
106
            return image;
944
        }
945
12
        theme->unload(skin);
946
    }
947
    return nullptr;
948
}
949
950
2
ImageSet *Theme::getImageSetFromThemeXml(const std::string &name,
951
                                         const std::string &name2,
952
                                         const int w, const int h)
953
{
954
2
    if (theme == nullptr)
955
        return nullptr;
956
957
    Skin *const skin = theme->load(name,
958
        name2,
959
        false,
960
4
        Theme::getThemePath());
961
2
    if (skin != nullptr)
962
    {
963
2
        const ImageRect &rect = skin->getBorder();
964
2
        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
2
        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
8
ThemeInfo *Theme::loadInfo(const std::string &themeName)
1006
{
1007
16
    std::string path;
1008
8
    if (themeName.empty())
1009
    {
1010
        path = "graphics/gui/info.xml";
1011
    }
1012
    else
1013
    {
1014

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

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

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

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

16
            readValue(name)
1041

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

16
            readValue(copyright)
1043

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

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

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

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

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

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

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

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

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

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

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

16
            readFloatValue(guiAlpha)
1065

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

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

6
}