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

Line Branch Exec Source
1
/*
2
 *  The ManaPlus Client
3
 *  Copyright (C) 2008  The Legend of Mazzeroth Development Team
4
 *  Copyright (C) 2009  Aethyra Development Team
5
 *  Copyright (C) 2009  The Mana World Development Team
6
 *  Copyright (C) 2009-2010  The Mana Developers
7
 *  Copyright (C) 2011-2019  The ManaPlus Developers
8
 *  Copyright (C) 2019-2021  Andrei Karas
9
 *
10
 *  This file is part of The ManaPlus Client.
11
 *
12
 *  This program is free software; you can redistribute it and/or modify
13
 *  it under the terms of the GNU General Public License as published by
14
 *  the Free Software Foundation; either version 2 of the License, or
15
 *  any later version.
16
 *
17
 *  This program is distributed in the hope that it will be useful,
18
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20
 *  GNU General Public License for more details.
21
 *
22
 *  You should have received a copy of the GNU General Public License
23
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
24
 */
25
26
#include "gui/theme.h"
27
28
#include "configuration.h"
29
#include "graphicsmanager.h"
30
31
#include "const/gui/theme.h"
32
33
#include "fs/virtfs/fs.h"
34
#include "fs/virtfs/list.h"
35
36
#include "gui/skin.h"
37
#include "gui/themeinfo.h"
38
39
#include "resources/imagerect.h"
40
41
#include "resources/dye/dyepalette.h"
42
43
#include "resources/image/image.h"
44
45
#include "resources/loaders/imageloader.h"
46
#include "resources/loaders/imagesetloader.h"
47
#include "resources/loaders/subimageloader.h"
48
#include "resources/loaders/subimagesetloader.h"
49
#include "resources/loaders/xmlloader.h"
50
51
#include "utils/dtor.h"
52
#include "utils/foreach.h"
53
54
#include "debug.h"
55
56
1
static std::string defaultThemePath;
57
58
1
std::string Theme::mThemePath;
59
1
std::string Theme::mThemeName;
60
1
std::string Theme::mScreenDensity;
61
62
Theme *theme = nullptr;
63
64
// Set the theme path...
65
454
static void initDefaultThemePath()
66
{
67
✓✗
2270
    defaultThemePath = branding.getStringValue("guiThemePath");
68
69
✓✗
908
    logger->log("defaultThemePath: " + defaultThemePath);
70
✓✗✗✓
✗✓
1816
    if (!defaultThemePath.empty() &&
71
✓✗✗✓
✗✗
1362
        VirtFs::isDirectory(defaultThemePath))
72
    {
73
        return;
74
    }
75
    defaultThemePath = "themes/";
76
}
77
78
227
Theme::Theme() :
79
    Palette(CAST_S32(ThemeColorId::THEME_COLORS_END) * THEME_PALETTES),
80
    mSkins(),
81
    mMinimumOpacity(-1.0F),
82
    mProgressColors(ProgressColors(CAST_SIZE(
83
✓✗
1135
                    ProgressColorId::THEME_PROG_END)))
84
{
85
✓✗
227
    initDefaultThemePath();
86
87
✓✗✓✗
908
    config.addListener("guialpha", this);
88
89
227
    mColors[CAST_SIZE(ThemeColorId::HIGHLIGHT)].ch = 'H';
90
227
    mColors[CAST_SIZE(ThemeColorId::CHAT)].ch = 'C';
91
227
    mColors[CAST_SIZE(ThemeColorId::GM)].ch = 'G';
92
227
    mColors[CAST_SIZE(ThemeColorId::GLOBAL)].ch = 'g';
93
227
    mColors[CAST_SIZE(ThemeColorId::PLAYER)].ch = 'Y';
94
227
    mColors[CAST_SIZE(ThemeColorId::WHISPER_TAB)].ch = 'W';
95
227
    mColors[CAST_SIZE(ThemeColorId::WHISPER_TAB_OFFLINE)].ch = 'w';
96
227
    mColors[CAST_SIZE(ThemeColorId::IS)].ch = 'I';
97
227
    mColors[CAST_SIZE(ThemeColorId::PARTY_CHAT_TAB)].ch = 'P';
98
227
    mColors[CAST_SIZE(ThemeColorId::GUILD_CHAT_TAB)].ch = 'U';
99
227
    mColors[CAST_SIZE(ThemeColorId::SERVER)].ch = 'S';
100
227
    mColors[CAST_SIZE(ThemeColorId::LOGGER)].ch = 'L';
101
227
    mColors[CAST_SIZE(ThemeColorId::HYPERLINK)].ch = '<';
102
227
    mColors[CAST_SIZE(ThemeColorId::SELFNICK)].ch = 's';
103
227
    mColors[CAST_SIZE(ThemeColorId::OLDCHAT)].ch = 'o';
104
227
    mColors[CAST_SIZE(ThemeColorId::AWAYCHAT)].ch = 'a';
105
✓✗
227
    mCharColors['H'] = CAST_S32(ThemeColorId::HIGHLIGHT);
106
✓✗
227
    mCharColors['C'] = CAST_S32(ThemeColorId::CHAT);
107
✓✗
227
    mCharColors['G'] = CAST_S32(ThemeColorId::GM);
108
✓✗
227
    mCharColors['g'] = CAST_S32(ThemeColorId::GLOBAL);
109
✓✗
227
    mCharColors['Y'] = CAST_S32(ThemeColorId::PLAYER);
110
✓✗
227
    mCharColors['W'] = CAST_S32(ThemeColorId::WHISPER_TAB);
111
✓✗
227
    mCharColors['w'] = CAST_S32(ThemeColorId::WHISPER_TAB_OFFLINE);
112
✓✗
227
    mCharColors['I'] = CAST_S32(ThemeColorId::IS);
113
✓✗
227
    mCharColors['P'] = CAST_S32(ThemeColorId::PARTY_CHAT_TAB);
114
✓✗
227
    mCharColors['U'] = CAST_S32(ThemeColorId::GUILD_CHAT_TAB);
115
✓✗
227
    mCharColors['S'] = CAST_S32(ThemeColorId::SERVER);
116
✓✗
227
    mCharColors['L'] = CAST_S32(ThemeColorId::LOGGER);
117
✓✗
227
    mCharColors['<'] = CAST_S32(ThemeColorId::HYPERLINK);
118
✓✗
227
    mCharColors['s'] = CAST_S32(ThemeColorId::SELFNICK);
119
✓✗
227
    mCharColors['o'] = CAST_S32(ThemeColorId::OLDCHAT);
120
✓✗
227
    mCharColors['a'] = CAST_S32(ThemeColorId::AWAYCHAT);
121
122
    // here need use outlined colors
123
✓✗
454
    mCharColors['H' | 0x80]
124
227
        = CAST_S32(ThemeColorId::HIGHLIGHT_OUTLINE);
125
✓✗
227
    mCharColors['C' | 0x80] = CAST_S32(ThemeColorId::CHAT_OUTLINE);
126
✓✗
227
    mCharColors['G' | 0x80] = CAST_S32(ThemeColorId::GM_OUTLINE);
127
✓✗
227
    mCharColors['g' | 0x80] = CAST_S32(ThemeColorId::GLOBAL_OUTLINE);
128
✓✗
227
    mCharColors['Y' | 0x80] = CAST_S32(ThemeColorId::PLAYER_OUTLINE);
129
✓✗
454
    mCharColors['W' | 0x80]
130
227
        = CAST_S32(ThemeColorId::WHISPER_TAB_OUTLINE);
131
✓✗
454
    mCharColors['w' | 0x80]
132
227
        = CAST_S32(ThemeColorId::WHISPER_TAB_OFFLINE_OUTLINE);
133
✓✗
227
    mCharColors['I' | 0x80] = CAST_S32(ThemeColorId::IS_OUTLINE);
134
✓✗
454
    mCharColors['P' | 0x80]
135
227
        = CAST_S32(ThemeColorId::PARTY_CHAT_TAB_OUTLINE);
136
✓✗
454
    mCharColors['U' | 0x80]
137
227
        = CAST_S32(ThemeColorId::GUILD_CHAT_TAB_OUTLINE);
138
✓✗
227
    mCharColors['S' | 0x80] = CAST_S32(ThemeColorId::SERVER_OUTLINE);
139
✓✗
227
    mCharColors['L' | 0x80] = CAST_S32(ThemeColorId::LOGGER_OUTLINE);
140
✓✗
454
    mCharColors['<' | 0x80]
141
227
        = CAST_S32(ThemeColorId::HYPERLINK_OUTLINE);
142
✓✗
227
    mCharColors['s' | 0x80] = CAST_S32(ThemeColorId::SELFNICK_OUTLINE);
143
✓✗
227
    mCharColors['o' | 0x80] = CAST_S32(ThemeColorId::OLDCHAT_OUTLINE);
144
✓✗
227
    mCharColors['a' | 0x80] = CAST_S32(ThemeColorId::AWAYCHAT_OUTLINE);
145
227
}
146
147
1284
Theme::~Theme()
148
{
149
428
    delete_all(mSkins);
150
856
    config.removeListener("guialpha", this);
151
    CHECKLISTENERS
152
428
    delete_all(mProgressColors);
153
428
}
154
155
24
Color Theme::getProgressColor(const ProgressColorIdT type,
156
                              const float progress)
157
{
158
24
    int color[3] = {0, 0, 0};
159
160
✓✗
24
    if (theme != nullptr)
161
    {
162
        const DyePalette *const dye
163
48
            = theme->mProgressColors[CAST_SIZE(type)];
164
165
✓✗
24
        if (dye != nullptr)
166
        {
167
24
            dye->getColor(progress, color);
168
        }
169
        else
170
        {
171
            logger->log("color not found: "
172
                + toString(CAST_S32(type)));
173
        }
174
    }
175
176
24
    return Color(color[0], color[1], color[2], 255U);
177
}
178
179
2232
Skin *Theme::load(const std::string &filename,
180
                  const std::string &filename2,
181
                  const bool full,
182
                  const std::string &restrict defaultPath)
183
{
184
    // Check if this skin was already loaded
185
186
4464
    const SkinIterator skinIterator = mSkins.find(filename);
187
✓✓
4464
    if (mSkins.end() != skinIterator)
188
    {
189
✓✗
1394
        if (skinIterator->second != nullptr)
190
1394
            skinIterator->second->instances++;
191
        return skinIterator->second;
192
    }
193
194
838
    Skin *skin = nullptr;
195
✓✗
838
    if (mScreenDensity.empty())
196
    {   // if no density detected
197
838
        skin = readSkin(filename, full);
198
✓✓✓✗
✗✓✓✓
1016
        if ((skin == nullptr) && !filename2.empty() && filename2 != filename)
199
89
            skin = readSkin(filename2, full);
200
✓✓✗✓
✓✓
839
        if ((skin == nullptr) && filename2 != "window.xml")
201
✓✗
4
            skin = readSkin("window.xml", full);
202
    }
203
    else
204
    {   // first use correct density images
205
        const std::string endStr("_" + mScreenDensity + ".xml");
206
        std::string name = filename;
207
        if (findCutLast(name, ".xml"))
208
            skin = readSkin(name + endStr, full);
209
        if (skin == nullptr)
210
            skin = readSkin(filename, full);
211
        if ((skin == nullptr) && !filename2.empty() && filename2 != filename)
212
        {
213
            name = filename2;
214
            if (findCutLast(name, ".xml"))
215
                skin = readSkin(name + endStr, full);
216
            if (skin == nullptr)
217
                skin = readSkin(filename2, full);
218
        }
219
        if ((skin == nullptr) && filename2 != "window.xml")
220
        {
221
            skin = readSkin("window" + endStr, full);
222
            if (skin == nullptr)
223
                skin = readSkin("window.xml", full);
224
        }
225
    }
226
227
✗✓
838
    if (skin == nullptr)
228
    {
229
        // Try falling back on the defaultPath if this makes sense
230
        if (filename != defaultPath)
231
        {
232
            logger->log("Error loading skin '%s', falling back on default.",
233
                        filename.c_str());
234
235
            skin = readSkin(defaultPath, full);
236
        }
237
238
        if (skin == nullptr)
239
        {
240
            logger->log(strprintf("Error: Loading default skin '%s' failed. "
241
                                  "Make sure the skin file is valid.",
242
                                  defaultPath.c_str()));
243
        }
244
    }
245
246
838
    mSkins[filename] = skin;
247
838
    return skin;
248
}
249
250
2210
void Theme::unload(Skin *const skin)
251
{
252
✓✗
2210
    if (skin == nullptr)
253
        return;
254
2210
    skin->instances --;
255
✓✓
2210
    if (skin->instances == 0)
256
    {
257
1664
        SkinIterator it = mSkins.begin();
258
1664
        const SkinIterator it_end = mSkins.end();
259
✓✗
3332
        while (it != it_end)
260
        {
261
✓✓
3332
            if (it->second == skin)
262
            {
263
832
                mSkins.erase(it);
264
                break;
265
            }
266
            ++ it;
267
        }
268
832
        delete skin;
269
    }
270
}
271
272
void Theme::setMinimumOpacity(const float minimumOpacity)
273
{
274
    if (minimumOpacity > 1.0F)
275
        return;
276
277
    mMinimumOpacity = minimumOpacity;
278
    updateAlpha();
279
}
280
281
2873
void Theme::updateAlpha()
282
{
283
✗✓
8619
    FOR_EACH (SkinIterator, iter, mSkins)
284
    {
285
        Skin *const skin = iter->second;
286
        if (skin != nullptr)
287
            skin->updateAlpha(mMinimumOpacity);
288
    }
289
2873
}
290
291
2873
void Theme::optionChanged(const std::string &name A_UNUSED)
292
{
293
2873
    updateAlpha();
294
2873
}
295
296
78
struct SkinParameter final
297
{
298
    A_DEFAULT_COPY(SkinParameter)
299
    int index;
300
    std::string name;
301
};
302
303
✓✓
3
static const SkinParameter skinParam[] =
304
{
305
    {0, "top-left-corner"},
306
    {0, "standart"},
307
    {0, "up"},
308
    {0, "hstart"},
309
    {0, "in"},
310
    {0, "normal"},
311
    {1, "top-edge"},
312
    {1, "highlighted"},
313
    {1, "down"},
314
    {1, "hmiddle"},
315
    {1, "in-highlighted"},
316
    {1, "checked"},
317
    {2, "top-right-corner"},
318
    {2, "pressed"},
319
    {2, "left"},
320
    {2, "hend"},
321
    {2, "out"},
322
    {2, "disabled"},
323
    {3, "left-edge"},
324
    {3, "disabled"},
325
    {3, "right"},
326
    {3, "hgrip"},
327
    {3, "out-highlighted"},
328
    {3, "disabled-checked"},
329
    {4, "bg-quad"},
330
    {4, "vstart"},
331
    {4, "normal-highlighted"},
332
    {5, "right-edge"},
333
    {5, "vmiddle"},
334
    {5, "checked-highlighted"},
335
    {6, "bottom-left-corner"},
336
    {6, "vend"},
337
    {7, "bottom-edge"},
338
    {7, "vgrip"},
339
    {8, "bottom-right-corner"},
340
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✗✗
71
};
341
342
✓✓
3
static const SkinParameter imageParam[] =
343
{
344
    {0, "closeImage"},
345
    {1, "closeImageHighlighted"},
346
    {2, "stickyImageUp"},
347
    {3, "stickyImageDown"},
348
✓✗✓✗
✓✗✓✗
✗✗
9
};
349
350
1676
struct SkinHelper final
351
{
352
838
    SkinHelper() :
353
        partType(),
354
        xPos(),
355
        yPos(),
356
        width(),
357
        height(),
358
        rect(),
359
        node(),
360
1676
        image()
361
    {
362
    }
363
364
    A_DELETE_COPY(SkinHelper)
365
366
    std::string partType;
367
    int xPos;
368
    int yPos;
369
    int width;
370
    int height;
371
    ImageRect *rect;
372
    XmlNodePtr *node;
373
    Image *image;
374
375
5121
    bool loadList(const SkinParameter *const params,
376
                  const size_t size) A_NONNULL(2)
377
    {
378
✓✓
103128
        for (size_t f = 0; f < size; f ++)
379
        {
380
102928
            const SkinParameter &param = params[f];
381
✓✓
102928
            if (partType == param.name)
382
            {
383
4921
                rect->grid[param.index] = Loader::getSubImage(
384
                    image,
385
                    xPos, yPos,
386
                    width, height);
387
4921
                return true;
388
            }
389
        }
390
        return false;
391
    }
392
};
393
394
928
Skin *Theme::readSkin(const std::string &filename, const bool full)
395
{
396
✓✓
928
    if (filename.empty())
397
        return nullptr;
398
399
922
    const std::string path = resolveThemePath(filename);
400
✓✗✓✓
1844
    if (!VirtFs::exists(path))
401
        return nullptr;
402
    XML::Document *const doc = Loader::getXml(path,
403
        UseVirtFs_true,
404
✓✗
838
        SkipError_true);
405
✓✗
838
    if (doc == nullptr)
406
        return nullptr;
407
✓✗
838
    XmlNodeConstPtr rootNode = doc->rootNode();
408
✓✗✓✗
✗✓✗✓
838
    if ((rootNode == nullptr) || !xmlNameEqual(rootNode, "skinset"))
409
    {
410
        doc->decRef();
411
        return nullptr;
412
    }
413
414
✓✗✓✗
3352
    const std::string skinSetImage = XML::getProperty(rootNode, "image", "");
415
416
✗✓
838
    if (skinSetImage.empty())
417
    {
418
        logger->log1("Theme::readSkin(): Skinset does not define an image!");
419
        doc->decRef();
420
        return nullptr;
421
    }
422
423
✓✗
838
    Image *const dBorders = Theme::getImageFromTheme(skinSetImage);
424
✓✗
1676
    ImageRect *const border = new ImageRect;
425
✓✗
1676
    ImageRect *const images = new ImageRect;
426
838
    int padding = 3;
427
838
    int titlePadding = 4;
428
838
    int titlebarHeight = 0;
429
838
    int titlebarHeightRelative = 0;
430
838
    int closePadding = 3;
431
838
    int stickySpacing = 3;
432
838
    int stickyPadding = 3;
433
838
    int resizePadding = 2;
434
✓✗
1676
    StringIntMap *const mOptions = new StringIntMap;
435
436
    // iterate <widget>'s
437
✓✓
3352
    for_each_xml_child_node(widgetNode, rootNode)
438
    {
439
✓✗✓✓
2514
        if (!xmlNameEqual(widgetNode, "widget"))
440
1676
            continue;
441
442
        const std::string widgetType =
443
✓✗✓✗
4190
                XML::getProperty(widgetNode, "type", "unknown");
444
✓✗
838
        if (widgetType == "Window")
445
        {
446
1676
            SkinHelper helper;
447
✓✗
838
            const int globalXPos = XML::getProperty(widgetNode, "xpos", 0);
448
✓✗
838
            const int globalYPos = XML::getProperty(widgetNode, "ypos", 0);
449
✓✓
18348
            for_each_xml_child_node(partNode, widgetNode)
450
            {
451
✓✗✓✓
17510
                if (xmlNameEqual(partNode, "part"))
452
                {
453
✓✗✓✗
27090
                    helper.partType = XML::getProperty(
454
5418
                        partNode, "type", "unknown");
455
✓✗
10836
                    helper.xPos = XML::getProperty(
456
5418
                        partNode, "xpos", 0) + globalXPos;
457
✓✗
10836
                    helper.yPos = XML::getProperty(
458
5418
                        partNode, "ypos", 0) + globalYPos;
459
✓✗
5418
                    helper.width = XML::getProperty(partNode, "width", 0);
460
✓✗
5418
                    helper.height = XML::getProperty(partNode, "height", 0);
461
✓✓✓✗
5418
                    if ((helper.width == 0) || (helper.height == 0))
462
                        continue;
463
4921
                    helper.image = dBorders;
464
465
4921
                    helper.rect = border;
466
✓✗✓✓
4921
                    if (!helper.loadList(skinParam,
467
                        sizeof(skinParam) / sizeof(SkinParameter)))
468
                    {
469
200
                        helper.rect = images;
470
                        helper.loadList(imageParam,
471
✓✗
200
                            sizeof(imageParam) / sizeof(SkinParameter));
472
                    }
473
                }
474
✓✓✓✗
✓✓✓✓
12092
                else if (full && xmlNameEqual(partNode, "option"))
475
                {
476
                    const std::string name = XML::getProperty(
477
✓✗✓✗
6160
                        partNode, "name", "");
478
✓✓
1232
                    if (name == "padding")
479
                    {
480
✓✗
466
                        padding = XML::getProperty(partNode, "value", 3);
481
                    }
482
✓✓
766
                    else if (name == "titlePadding")
483
                    {
484
✓✗
1
                        titlePadding = XML::getProperty(partNode, "value", 4);
485
                    }
486
✗✓
765
                    else if (name == "closePadding")
487
                    {
488
                        closePadding = XML::getProperty(partNode, "value", 3);
489
                    }
490
✓✓
765
                    else if (name == "stickySpacing")
491
                    {
492
✓✗
41
                        stickySpacing = XML::getProperty(partNode, "value", 3);
493
                    }
494
✗✓
724
                    else if (name == "stickyPadding")
495
                    {
496
                        stickyPadding = XML::getProperty(partNode, "value", 3);
497
                    }
498
✓✓
724
                    else if (name == "titlebarHeight")
499
                    {
500
                        titlebarHeight = XML::getProperty(
501
✓✗
57
                            partNode, "value", 0);
502
                    }
503
✗✓
667
                    else if (name == "titlebarHeightRelative")
504
                    {
505
                        titlebarHeightRelative = XML::getProperty(
506
                            partNode, "value", 0);
507
                    }
508
✗✓
667
                    else if (name == "resizePadding")
509
                    {
510
                        resizePadding = XML::getProperty(
511
                            partNode, "value", 2);
512
                    }
513
                    else
514
                    {
515
✓✗✓✗
667
                        (*mOptions)[name] = XML::getProperty(
516
                            partNode, "value", 0);
517
                    }
518
                }
519
            }
520
        }
521
        else
522
        {
523
            logger->log("Theme::readSkin(): Unknown widget type '%s'",
524
                        widgetType.c_str());
525
        }
526
    }
527
528
✓✗
838
    if (dBorders != nullptr)
529
✓✗
838
        dBorders->decRef();
530
531
✓✗✓✗
3352
    (*mOptions)["closePadding"] = closePadding;
532
✓✗✓✗
3352
    (*mOptions)["stickyPadding"] = stickyPadding;
533
✓✗✓✗
3352
    (*mOptions)["stickySpacing"] = stickySpacing;
534
✓✗✓✗
3352
    (*mOptions)["titlebarHeight"] = titlebarHeight;
535
✓✗✓✗
3352
    (*mOptions)["titlebarHeightRelative"] = titlebarHeightRelative;
536
✓✗✓✗
3352
    (*mOptions)["resizePadding"] = resizePadding;
537
538
    Skin *const skin = new Skin(border, images, filename, "", padding,
539
✓✗✓✗
✓✗
3352
        titlePadding, mOptions);
540
838
    delete images;
541
✓✗
838
    skin->updateAlpha(mMinimumOpacity);
542
✓✗
838
    doc->decRef();
543
    return skin;
544
}
545
546
227
bool Theme::tryThemePath(const std::string &themeName)
547
{
548
✓✗
227
    if (!themeName.empty())
549
    {
550
227
        const std::string path = defaultThemePath + themeName;
551
✓✗✓✗
454
        if (VirtFs::exists(path))
552
        {
553
227
            mThemePath = path;
554
227
            mThemeName = themeName;
555
✓✗
227
            if (theme != nullptr)
556
✓✗✓✗
908
                theme->loadColors("");
557
454
            return true;
558
        }
559
    }
560
561
    return false;
562
}
563
564
2
void Theme::fillSkinsList(StringVect &list)
565
{
566
✓✗✓✗
10
    VirtFs::getDirs(branding.getStringValue("guiThemePath"), list);
567
2
    std::sort(list.begin(), list.end());
568
2
}
569
570
2
void Theme::fillFontsList(StringVect &list)
571
{
572
2
    VirtFs::permitLinks(true);
573
✓✗✓✗
10
    VirtFs::getFiles(branding.getStringValue("fontsPath"), list);
574
4
    std::sort(list.begin(), list.end());
575
2
    VirtFs::permitLinks(false);
576
2
}
577
578
2
void Theme::fillSoundsList(StringVect &list)
579
{
580
    VirtFs::List *const skins = VirtFs::enumerateFiles(
581
✓✗✓✗
10
        branding.getStringValue("systemsounds"));
582
583
✓✓
58
    FOR_EACH (StringVectCIter, i, skins->names)
584
    {
585
✓✗✓✗
✓✗
276
        if (!VirtFs::isDirectory((branding.getStringValue(
586
✓✗
138
            "systemsounds") + *i)))
587
        {
588
138
            std::string str = *i;
589
✓✗✓✗
✓✓
184
            if (findCutLast(str, ".ogg"))
590
✓✗
38
                list.push_back(str);
591
        }
592
    }
593
594
2
    VirtFs::freeList(skins);
595
4
    std::sort(list.begin(), list.end());
596
2
}
597
598
227
void Theme::selectSkin()
599
{
600
227
    prepareThemePath();
601
454
    mScreenDensity = graphicsManager.getDensityString();
602
227
}
603
604
227
void Theme::prepareThemePath()
605
{
606
227
    initDefaultThemePath();
607
608
227
    mThemePath.clear();
609
227
    mThemeName.clear();
610
611
    // Try theme from settings
612
✓✗✓✗
✗✓
1135
    if (tryThemePath(config.getStringValue("theme")))
613
        return;
614
615
    // Try theme from branding
616
    if (tryThemePath(branding.getStringValue("theme")))
617
        return;
618
619
    if (mThemePath.empty())
620
        mThemePath = "graphics/gui";
621
622
    theme->loadColors(mThemePath);
623
624
    logger->log("Selected Theme: " + mThemePath);
625
}
626
627
3436
std::string Theme::resolveThemePath(const std::string &path)
628
{
629
    // Need to strip off any dye info for the existence tests
630
3436
    const int pos = CAST_S32(path.find('|'));
631
6872
    std::string file;
632
✗✓
3436
    if (pos > 0)
633
        file = path.substr(0, pos);
634
    else
635
        file = path;
636
637
    // File with path
638
✗✓
3436
    if (file.find('/') != std::string::npos)
639
    {
640
        // Might be a valid path already
641
        if (VirtFs::exists(file))
642
            return path;
643
    }
644
645
    // Try the theme
646
✓✗
10308
    file = pathJoin(getThemePath(), file);
647
648
✓✗✓✓
6872
    if (VirtFs::exists(file))
649
✓✗
3930
        return pathJoin(getThemePath(), path);
650
651
    // Backup
652
✓✗✓✗
✓✗
7355
    return pathJoin(branding.getStringValue("guiPath"), path);
653
}
654
655
844
Image *Theme::getImageFromTheme(const std::string &path)
656
{
657
✓✗
1688
    return Loader::getImage(resolveThemePath(path));
658
}
659
660
153
ImageSet *Theme::getImageSetFromTheme(const std::string &path,
661
                                      const int w, const int h)
662
{
663
✓✗
306
    return Loader::getImageSet(resolveThemePath(path), w, h);
664
}
665
666
#define themeEnumStart(name) #name,
667
#define themeEnum(name) #name,
668
#define themeEnumEnd(name)
669
670
172066
static int readColorType(const std::string &type)
671
{
672
    static const std::string colors[CAST_SIZE(
673
        ThemeColorId::THEME_COLORS_END)] =
674
    {
675
#include "gui/themecolortype.inc"
676
✓✓✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✗✗✓✓
172524
    };
677
678
✓✗
172066
    if (type.empty())
679
        return -1;
680
681
✓✓
42644220
    for (int i = 0; i < CAST_S32(ThemeColorId::THEME_COLORS_END); i++)
682
    {
683
✓✓
21399063
        if (compareStrI(type, colors[i]) == 0)
684
            return i;
685
    }
686
687
    return -1;
688
}
689
690
THEMECOLORTYPE_VOID
691
692
#undef themeEnumStart
693
#undef themeEnum
694
#undef themeEnumEnd
695
#undef THEMECOLORTYPE_VOID
696
697
101242
static Color readColor(const std::string &description)
698
{
699
101242
    const int size = static_cast<int>(description.length());
700
✓✗✗✓
✓✗
202484
    if (size < 7 || description[0] != '#')
701
    {
702
        logger->log("Error, invalid theme color palette: %s",
703
                    description.c_str());
704
        return Palette::BLACK;
705
    }
706
707
    unsigned int v = 0;
708
✓✓
1316146
    for (int i = 1; i < 7; ++i)
709
    {
710
1214904
        signed const char c = description[i];
711
        int n;
712
713
✓✓
607452
        if ('0' <= c && c <= '9')
714
        {
715
356390
            n = c - '0';
716
        }
717
✓✓
251062
        else if ('A' <= c && c <= 'F')
718
        {
719
2724
            n = c - 'A' + 10;
720
        }
721
✓✗
248338
        else if ('a' <= c && c <= 'f')
722
        {
723
248338
            n = c - 'a' + 10;
724
        }
725
        else
726
        {
727
            logger->log("Error, invalid theme color palette: %s",
728
                        description.c_str());
729
            return Palette::BLACK;
730
        }
731
732
607452
        v = (v << 4) | n;
733
    }
734
735
101242
    return Color(v);
736
}
737
738
101242
static GradientTypeT readColorGradient(const std::string &grad)
739
{
740
    static const std::string grads[] =
741
    {
742
        "STATIC",
743
        "PULSE",
744
        "SPECTRUM",
745
        "RAINBOW"
746
✓✓✓✗
✓✗✓✗
✓✗✓✗
✗✗✓✓
101252
    };
747
748
✓✓
101242
    if (grad.empty())
749
        return GradientType::STATIC;
750
751
✓✗
908
    for (int i = 0; i < 4; i++)
752
    {
753
✗✓
908
        if (compareStrI(grad, grads[i]) != 0)
754
            return static_cast<GradientTypeT>(i);
755
    }
756
757
    return GradientType::STATIC;
758
}
759
760
2724
static int readProgressType(const std::string &type)
761
{
762
    static const std::string colors[CAST_SIZE(
763
        ProgressColorId::THEME_PROG_END)] =
764
    {
765
        "HP",
766
        "HP_POISON",
767
        "MP",
768
        "NO_MP",
769
        "EXP",
770
        "INVY_SLOTS",
771
        "WEIGHT",
772
        "JOB",
773
        "UPDATE",
774
        "MONEY",
775
        "ARROWS",
776
        "STATUS"
777
✓✓✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✓✗✓✗
✗✗✓✓
2750
    };
778
779
✓✗
2724
    if (type.empty())
780
        return -1;
781
782
✓✗
32688
    for (int i = 0; i < CAST_S32(ProgressColorId::THEME_PROG_END); i++)
783
    {
784
✓✓
17706
        if (compareStrI(type, colors[i]) == 0)
785
            return i;
786
    }
787
788
    return -1;
789
}
790
791
227
void Theme::loadColors(std::string file)
792
{
793
✓✗
227
    if (file.empty())
794
        file = "colors.xml";
795
    else
796
        file = pathJoin(file, "colors.xml");
797
798
454
    XML::Document *const doc = Loader::getXml(resolveThemePath(file),
799
        UseVirtFs_true,
800
✓✗
227
        SkipError_false);
801
✓✗
227
    if (doc == nullptr)
802
        return;
803
227
    XmlNodeConstPtrConst root = doc->rootNode();
804
805
✓✗✗✓
✗✓
227
    if ((root == nullptr) || !xmlNameEqual(root, "colors"))
806
    {
807
        logger->log("Error loading colors file: %s", file.c_str());
808
        doc->decRef();
809
        return;
810
    }
811
812
227
    logger->log("Loading colors file: %s", file.c_str());
813
814
✓✓
7264
    for_each_xml_child_node(paletteNode, root)
815
    {
816
✓✓
7037
        if (xmlNameEqual(paletteNode, "progressbar"))
817
        {
818
✓✗
13620
            const int type = readProgressType(XML::getProperty(
819
✓✗
2724
                paletteNode, "id", ""));
820
✓✗
2724
            if (type < 0)
821
                continue;
822
823
✓✗
19068
            mProgressColors[type] = new DyePalette(XML::getProperty(
824
✓✗✓✗
2724
                paletteNode, "color", ""), 6);
825
        }
826
✓✓
4313
        else if (!xmlNameEqual(paletteNode, "palette"))
827
        {
828
            continue;
829
        }
830
831
3405
        const int paletteId = XML::getProperty(paletteNode, "id", 1);
832
✓✗
3405
        if (paletteId < 0 || paletteId >= THEME_PALETTES)
833
            continue;
834
835
✓✓
206570
        for_each_xml_child_node(node, paletteNode)
836
        {
837
✓✓
203165
            if (xmlNameEqual(node, "color"))
838
            {
839
✓✗
497130
                const std::string id = XML::getProperty(node, "id", "");
840
✓✗
101242
                const int type = readColorType(id);
841
✓✗
101242
                if (type < 0)
842
9080
                    continue;
843
844
✓✗✓✗
497130
                const std::string temp = XML::getProperty(node, "color", "");
845
✓✗
101242
                if (temp.empty())
846
                    continue;
847
848
✓✗
101242
                const Color color = readColor(temp);
849
                const GradientTypeT grad = readColorGradient(
850
✓✗✓✗
✓✗
506210
                    XML::getProperty(node, "effect", ""));
851
101242
                mColors[paletteId * CAST_SIZE(
852
202484
                    ThemeColorId::THEME_COLORS_END) + type].set(
853
101242
                    type, color, grad, 10);
854
855
✓✗✓✗
✓✓
404968
                if (!findLast(id, "_OUTLINE"))
856
                {
857
✓✗✓✗
141648
                    const int type2 = readColorType(id + "_OUTLINE");
858
✓✓
70824
                    if (type2 < 0)
859
                        continue;
860
                    const int idx = paletteId
861
61744
                        * CAST_S32(ThemeColorId::THEME_COLORS_END);
862
✓✗
185232
                    mColors[idx + type2] = mColors[idx + type];
863
                }
864
            }
865
        }
866
    }
867
227
    doc->decRef();
868
}
869
870
#define loadGrid() \
871
    { \
872
        const ImageRect &rect = skin->getBorder(); \
873
        for (int f = start; f <= end; f ++) \
874
        { \
875
            if (rect.grid[f]) \
876
            { \
877
                image.grid[f] = rect.grid[f]; \
878
                image.grid[f]->incRef(); \
879
            } \
880
        } \
881
    }
882
883
210
void Theme::loadRect(ImageRect &image,
884
                     const std::string &name,
885
                     const std::string &name2,
886
                     const int start,
887
                     const int end)
888
{
889
    Skin *const skin = load(name,
890
        name2,
891
        false,
892
✓✗
420
        Theme::getThemePath());
893
✓✗
210
    if (skin != nullptr)
894
    {
895
✓✓✓✓
210
        loadGrid()
896
210
        unload(skin);
897
    }
898
210
}
899
900
43
Skin *Theme::loadSkinRect(ImageRect &image,
901
                          const std::string &name,
902
                          const std::string &name2,
903
                          const int start,
904
                          const int end)
905
{
906
    Skin *const skin = load(name,
907
        name2,
908
        true,
909
✓✗
86
        Theme::getThemePath());
910
✓✗
43
    if (skin != nullptr)
911
✓✓✓✗
43
        loadGrid()
912
43
    return skin;
913
}
914
915
262
void Theme::unloadRect(const ImageRect &rect,
916
                       const int start,
917
                       const int end)
918
{
919
✓✓
2620
    for (int f = start; f <= end; f ++)
920
    {
921
✓✓
2358
        if (rect.grid[f] != nullptr)
922
1286
            rect.grid[f]->decRef();
923
    }
924
262
}
925
926
59
Image *Theme::getImageFromThemeXml(const std::string &name,
927
                                   const std::string &name2)
928
{
929
✓✗
59
    if (theme == nullptr)
930
        return nullptr;
931
932
59
    Skin *const skin = theme->load(name,
933
        name2,
934
        false,
935
✓✗
118
        Theme::getThemePath());
936
✓✗
59
    if (skin != nullptr)
937
    {
938
59
        const ImageRect &rect = skin->getBorder();
939
✓✓
59
        if (rect.grid[0] != nullptr)
940
        {
941
53
            Image *const image = rect.grid[0];
942
53
            image->incRef();
943
53
            theme->unload(skin);
944
53
            return image;
945
        }
946
6
        theme->unload(skin);
947
    }
948
    return nullptr;
949
}
950
951
1
ImageSet *Theme::getImageSetFromThemeXml(const std::string &name,
952
                                         const std::string &name2,
953
                                         const int w, const int h)
954
{
955
✓✗
1
    if (theme == nullptr)
956
        return nullptr;
957
958
1
    Skin *const skin = theme->load(name,
959
        name2,
960
        false,
961
✓✗
2
        Theme::getThemePath());
962
✓✗
1
    if (skin != nullptr)
963
    {
964
1
        const ImageRect &rect = skin->getBorder();
965
✗✓
1
        if (rect.grid[0] != nullptr)
966
        {
967
            Image *const image = rect.grid[0];
968
            const SDL_Rect &rect2 = image->mBounds;
969
            if ((rect2.w != 0U) && (rect2.h != 0U))
970
            {
971
                ImageSet *const imageSet = Loader::getSubImageSet(
972
                    image, w, h);
973
                theme->unload(skin);
974
                return imageSet;
975
            }
976
        }
977
1
        theme->unload(skin);
978
    }
979
    return nullptr;
980
}
981
982
#define readValue(name) \
983
        { \
984
            tmpData = reinterpret_cast<XmlChar*>( \
985
                XmlNodeGetContent(infoNode)); \
986
            info->name = tmpData; \
987
            XmlFree(tmpData); \
988
        }
989
990
#define readIntValue(name) \
991
        { \
992
            tmpData = reinterpret_cast<XmlChar*>( \
993
                XmlNodeGetContent(infoNode)); \
994
            info->name = atoi(tmpData); \
995
            XmlFree(tmpData); \
996
        }
997
998
#define readFloatValue(name) \
999
        { \
1000
            tmpData = reinterpret_cast<XmlChar*>( \
1001
                XmlNodeGetContent(infoNode)); \
1002
            info->name = static_cast<float>(atof(tmpData)); \
1003
            XmlFree(tmpData); \
1004
        }
1005
1006
4
ThemeInfo *Theme::loadInfo(const std::string &themeName)
1007
{
1008
8
    std::string path;
1009
✗✓
4
    if (themeName.empty())
1010
    {
1011
        path = "graphics/gui/info.xml";
1012
    }
1013
    else
1014
    {
1015
✓✗✓✗
28
        path = pathJoin(defaultThemePath,
1016
            themeName,
1017
4
            "info.xml");
1018
    }
1019
✓✗✓✗
8
    logger->log("loading: " + path);
1020
    XML::Document *const doc = Loader::getXml(path,
1021
        UseVirtFs_true,
1022
✓✗
4
        SkipError_false);
1023
✓✗
4
    if (doc == nullptr)
1024
        return nullptr;
1025
✓✗
4
    XmlNodeConstPtrConst rootNode = doc->rootNode();
1026
1027
✓✗✓✗
✗✓✗✓
4
    if ((rootNode == nullptr) || !xmlNameEqual(rootNode, "info"))
1028
    {
1029
        doc->decRef();
1030
        return nullptr;
1031
    }
1032
1033
✓✗
4
    ThemeInfo *const info = new ThemeInfo;
1034
1035
✓✗
4
    const std::string fontSize2("fontSize_" + mScreenDensity);
1036
✓✗
8
    const std::string npcfontSize2("npcfontSize_" + mScreenDensity);
1037
4
    XmlChar *tmpData = nullptr;
1038
✓✓
32
    for_each_xml_child_node(infoNode, rootNode)
1039
    {
1040
✓✗✓✓
28
        if (xmlNameEqual(infoNode, "name"))
1041
✓✗✓✗
8
            readValue(name)
1042
✓✗✓✓
24
        else if (xmlNameEqual(infoNode, "copyright"))
1043
✓✗✓✗
8
            readValue(copyright)
1044
✓✗✗✓
20
        else if (xmlNameEqual(infoNode, "font"))
1045
            readValue(font)
1046
✓✗✗✓
20
        else if (xmlNameEqual(infoNode, "boldFont"))
1047
            readValue(boldFont)
1048
✓✗✗✓
20
        else if (xmlNameEqual(infoNode, "particleFont"))
1049
            readValue(particleFont)
1050
✓✗✗✓
20
        else if (xmlNameEqual(infoNode, "helpFont"))
1051
            readValue(helpFont)
1052
✓✗✗✓
20
        else if (xmlNameEqual(infoNode, "secureFont"))
1053
            readValue(secureFont)
1054
✓✗✗✓
20
        else if (xmlNameEqual(infoNode, "npcFont"))
1055
            readValue(npcFont)
1056
✓✗✗✓
20
        else if (xmlNameEqual(infoNode, "japanFont"))
1057
            readValue(japanFont)
1058
✓✗✗✓
20
        else if (xmlNameEqual(infoNode, "chinaFont"))
1059
            readValue(chinaFont)
1060
✓✗✗✓
20
        else if (xmlNameEqual(infoNode, "fontSize"))
1061
            readIntValue(fontSize)
1062
✓✗✗✓
20
        else if (xmlNameEqual(infoNode, "npcfontSize"))
1063
            readIntValue(npcfontSize)
1064
✓✗✓✓
20
        else if (xmlNameEqual(infoNode, "guialpha"))
1065
✓✗✓✗
8
            readFloatValue(guiAlpha)
1066
✓✗✗✓
16
        else if (xmlNameEqual(infoNode, fontSize2.c_str()))
1067
            readIntValue(fontSize)
1068
✓✗✗✓
16
        else if (xmlNameEqual(infoNode, npcfontSize2.c_str()))
1069
            readIntValue(npcfontSize)
1070
    }
1071
✓✗
4
    doc->decRef();
1072
4
    return info;
1073
}
1074
1075
4
ThemeColorIdT Theme::getIdByChar(const signed char c, bool &valid) const
1076
{
1077
8
    const CharColors::const_iterator it = mCharColors.find(c);
1078
✓✓
8
    if (it != mCharColors.end())
1079
    {
1080
2
        valid = true;
1081
2
        return static_cast<ThemeColorIdT>((*it).second);
1082
    }
1083
1084
2
    valid = false;
1085
2
    return ThemeColorId::BROWSERBOX;
1086
✓✗✓✗
3
}