tdesktop/Telegram/SourceFiles/window/themes/window_theme.cpp

1598 lines
46 KiB
C++
Raw Normal View History

/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "window/themes/window_theme.h"
#include "window/themes/window_theme_preview.h"
#include "mainwidget.h"
2019-01-28 13:59:49 +00:00
#include "auth_session.h"
#include "core/application.h"
#include "storage/serialize_common.h"
#include "data/data_document.h"
#include "data/data_session.h"
2017-03-04 10:23:56 +00:00
#include "storage/localstorage.h"
2017-04-06 14:38:10 +00:00
#include "base/parse_helper.h"
#include "base/zlib_help.h"
2019-01-28 13:59:49 +00:00
#include "ui/image/image.h"
#include "boxes/background_box.h"
#include "styles/style_widgets.h"
#include "styles/style_history.h"
2019-01-28 13:59:49 +00:00
namespace Data {
namespace {
constexpr auto FromLegacyBackgroundId(int32 legacyId) -> WallPaperId {
return uint64(0xFFFFFFFF00000000ULL) | uint64(uint32(legacyId));
}
constexpr auto kUninitializedBackground = FromLegacyBackgroundId(-999);
constexpr auto kTestingThemeBackground = FromLegacyBackgroundId(-666);
constexpr auto kTestingDefaultBackground = FromLegacyBackgroundId(-665);
constexpr auto kTestingEditorBackground = FromLegacyBackgroundId(-664);
constexpr auto kThemeBackground = FromLegacyBackgroundId(-2);
constexpr auto kCustomBackground = FromLegacyBackgroundId(-1);
constexpr auto kLegacy1DefaultBackground = FromLegacyBackgroundId(0);
constexpr auto kDefaultBackground = FromLegacyBackgroundId(105);
[[nodiscard]] bool ValidateFlags(MTPDwallPaper::Flags flags) {
using Flag = MTPDwallPaper::Flag;
const auto all = Flag(0)
| Flag::f_creator
| Flag::f_default
| Flag::f_pattern
| Flag::f_settings;
return !(flags & ~all);
}
[[nodiscard]] bool ValidateFlags(MTPDwallPaperSettings::Flags flags) {
using Flag = MTPDwallPaperSettings::Flag;
const auto all = Flag(0)
| Flag::f_background_color
| Flag::f_blur
| Flag::f_intensity
| Flag::f_motion;
return !(flags & ~all);
}
quint32 SerializeMaybeColor(std::optional<QColor> color) {
return color
? ((quint32(std::clamp(color->red(), 0, 255)) << 16)
| (quint32(std::clamp(color->green(), 0, 255)) << 8)
| quint32(std::clamp(color->blue(), 0, 255)))
: quint32(-1);
}
std::optional<QColor> MaybeColorFromSerialized(quint32 serialized) {
return (serialized == quint32(-1))
? std::nullopt
: std::make_optional(QColor(
int((serialized >> 16) & 0xFFU),
int((serialized >> 8) & 0xFFU),
int(serialized & 0xFFU)));
}
2019-01-29 07:29:38 +00:00
std::optional<QColor> ColorFromString(const QString &string) {
if (string.size() != 6) {
return {};
} else if (ranges::find_if(string, [](QChar ch) {
return (ch < 'a' || ch > 'f')
&& (ch < 'A' || ch > 'F')
&& (ch < '0' || ch > '9');
}) != string.end()) {
return {};
}
const auto component = [](const QString &text, int index) {
const auto decimal = [](QChar hex) {
const auto code = hex.unicode();
return (code >= '0' && code <= '9')
? int(code - '0')
: (code >= 'a' && code <= 'f')
? int(code - 'a' + 0x0a)
: int(code - 'A' + 0x0a);
};
index *= 2;
return decimal(text[index]) * 0x10 + decimal(text[index + 1]);
};
return QColor(
component(string, 0),
component(string, 1),
component(string, 2),
255);
}
QImage PreparePatternImage(QImage image, QColor bg, QColor fg, int intensity) {
if (image.format() != QImage::Format_ARGB32_Premultiplied) {
image = std::move(image).convertToFormat(
QImage::Format_ARGB32_Premultiplied);
}
// Similar to ColorizePattern.
// But here we set bg to all 'alpha=0' pixels and fg to opaque ones.
const auto width = image.width();
const auto height = image.height();
const auto alpha = anim::interpolate(
0,
255,
fg.alphaF() * std::clamp(intensity / 100., 0., 1.));
if (!alpha) {
image.fill(bg);
return image;
2019-01-29 07:29:38 +00:00
}
fg.setAlpha(255);
const auto patternBg = anim::shifted(bg);
const auto patternFg = anim::shifted(fg);
const auto resultBytesPerPixel = (image.depth() >> 3);
constexpr auto resultIntsPerPixel = 1;
const auto resultIntsPerLine = (image.bytesPerLine() >> 2);
const auto resultIntsAdded = resultIntsPerLine - width * resultIntsPerPixel;
auto resultInts = reinterpret_cast<uint32*>(image.bits());
Assert(resultIntsAdded >= 0);
Assert(image.depth() == static_cast<int>((resultIntsPerPixel * sizeof(uint32)) << 3));
Assert(image.bytesPerLine() == (resultIntsPerLine << 2));
const auto maskBytesPerPixel = (image.depth() >> 3);
const auto maskBytesPerLine = image.bytesPerLine();
const auto maskBytesAdded = maskBytesPerLine - width * maskBytesPerPixel;
// We want to read the last byte of four available.
// This is the difference with style::colorizeImage.
auto maskBytes = image.constBits() + (maskBytesPerPixel - 1);
Assert(maskBytesAdded >= 0);
Assert(image.depth() == (maskBytesPerPixel << 3));
for (auto y = 0; y != height; ++y) {
for (auto x = 0; x != width; ++x) {
const auto maskOpacity = static_cast<anim::ShiftedMultiplier>(
*maskBytes) + 1;
const auto fgOpacity = (maskOpacity * alpha) >> 8;
const auto bgOpacity = 256 - fgOpacity;
*resultInts = anim::unshifted(
patternBg * bgOpacity + patternFg * fgOpacity);
maskBytes += maskBytesPerPixel;
resultInts += resultIntsPerPixel;
}
maskBytes += maskBytesAdded;
resultInts += resultIntsAdded;
}
return image;
2019-01-29 07:29:38 +00:00
}
2019-01-28 13:59:49 +00:00
} // namespace
WallPaper::WallPaper(WallPaperId id) : _id(id) {
}
void WallPaper::setLocalImageAsThumbnail(not_null<Image*> image) {
Expects(IsDefaultWallPaper(*this)
|| IsLegacy1DefaultWallPaper(*this)
|| IsCustomWallPaper(*this));
Expects(_thumbnail == nullptr);
_thumbnail = image;
}
WallPaperId WallPaper::id() const {
return _id;
}
std::optional<QColor> WallPaper::backgroundColor() const {
return _backgroundColor;
}
DocumentData *WallPaper::document() const {
return _document;
}
Image *WallPaper::thumbnail() const {
return _thumbnail;
}
2019-01-29 07:29:38 +00:00
bool WallPaper::isPattern() const {
return _flags & MTPDwallPaper::Flag::f_pattern;
}
bool WallPaper::isDefault() const {
return _flags & MTPDwallPaper::Flag::f_default;
}
bool WallPaper::isCreator() const {
return _flags & MTPDwallPaper::Flag::f_creator;
}
int WallPaper::patternIntensity() const {
return _intensity;
}
2019-01-28 13:59:49 +00:00
bool WallPaper::hasShareUrl() const {
return !_slug.isEmpty();
}
QString WallPaper::shareUrl() const {
return hasShareUrl()
? Core::App().createInternalLinkFull("bg/" + _slug)
: QString();
}
void WallPaper::loadThumbnail() const {
if (_thumbnail) {
_thumbnail->load(fileOrigin());
}
}
void WallPaper::loadDocument() const {
if (_document) {
_document->save(fileOrigin(), QString());
}
}
FileOrigin WallPaper::fileOrigin() const {
return FileOriginWallpaper(_id, _accessHash);
}
2019-01-29 07:29:38 +00:00
WallPaper WallPaper::withUrlParams(
const QMap<QString, QString> &params) const {
using Flag = MTPDwallPaperSettings::Flag;
auto result = *this;
result._settings = Flag(0);
result._backgroundColor = ColorFromString(_slug);
result._intensity = kDefaultIntensity;
if (auto mode = params.value("mode"); !mode.isEmpty()) {
const auto list = mode.replace('+', ' ').split(' ');
for (const auto &change : list) {
if (change == qstr("blur")) {
result._settings |= Flag::f_blur;
} else if (change == qstr("motion")) {
result._settings |= Flag::f_motion;
}
}
}
if (const auto color = ColorFromString(params.value("bg_color"))) {
result._backgroundColor = color;
}
if (const auto string = params.value("intensity"); !string.isEmpty()) {
auto ok = false;
const auto intensity = string.toInt(&ok);
if (ok && base::in_range(intensity, 0, 100)) {
result._intensity = intensity;
}
}
return result;
}
2019-01-28 13:59:49 +00:00
std::optional<WallPaper> WallPaper::Create(const MTPWallPaper &data) {
return data.match([](const MTPDwallPaper &data) {
return Create(data);
});
}
std::optional<WallPaper> WallPaper::Create(const MTPDwallPaper &data) {
2019-01-29 07:29:38 +00:00
using Flag = MTPDwallPaper::Flag;
2019-01-28 13:59:49 +00:00
const auto document = Auth().data().processDocument(
data.vdocument);
if (!document->checkWallPaperProperties()) {
return std::nullopt;
}
auto result = WallPaper(data.vid.v);
result._accessHash = data.vaccess_hash.v;
result._flags = data.vflags.v;
result._slug = qs(data.vslug);
result._document = document;
result._thumbnail = document->thumbnail();
if (data.has_settings()) {
2019-01-29 07:29:38 +00:00
const auto isPattern = ((result._flags & Flag::f_pattern) != 0);
2019-01-28 13:59:49 +00:00
data.vsettings.match([&](const MTPDwallPaperSettings &data) {
2019-01-29 07:29:38 +00:00
using Flag = MTPDwallPaperSettings::Flag;
2019-01-28 13:59:49 +00:00
result._settings = data.vflags.v;
2019-01-29 07:29:38 +00:00
if (isPattern && data.has_background_color()) {
2019-01-28 13:59:49 +00:00
result._backgroundColor = MaybeColorFromSerialized(
data.vbackground_color.v);
2019-01-29 07:29:38 +00:00
} else {
result._settings &= ~Flag::f_background_color;
2019-01-28 13:59:49 +00:00
}
2019-01-29 07:29:38 +00:00
if (isPattern && data.has_intensity()) {
2019-01-28 13:59:49 +00:00
result._intensity = data.vintensity.v;
2019-01-29 07:29:38 +00:00
} else {
result._settings &= ~Flag::f_intensity;
2019-01-28 13:59:49 +00:00
}
});
}
return result;
}
QByteArray WallPaper::serialize() const {
auto size = sizeof(quint64) // _id
+ sizeof(quint64) // _accessHash
+ sizeof(qint32) // _flags
+ Serialize::stringSize(_slug)
+ sizeof(qint32) // _settings
+ sizeof(quint32) // _backgroundColor
+ sizeof(qint32); // _intensity
auto result = QByteArray();
result.reserve(size);
{
auto stream = QDataStream(&result, QIODevice::WriteOnly);
stream.setVersion(QDataStream::Qt_5_1);
stream
<< quint64(_id)
<< quint64(_accessHash)
<< qint32(_flags)
<< _slug
<< qint32(_settings)
<< SerializeMaybeColor(_backgroundColor)
<< qint32(_intensity);
}
return result;
}
std::optional<WallPaper> WallPaper::FromSerialized(
const QByteArray &serialized) {
if (serialized.isEmpty()) {
return std::nullopt;
}
auto id = quint64();
auto accessHash = quint64();
auto flags = qint32();
auto slug = QString();
auto settings = qint32();
auto backgroundColor = quint32();
auto intensity = qint32();
auto stream = QDataStream(serialized);
stream.setVersion(QDataStream::Qt_5_1);
stream
>> id
>> accessHash
>> flags
>> slug
>> settings
>> backgroundColor
2019-01-29 07:29:38 +00:00
>> intensity;
2019-01-28 13:59:49 +00:00
if (stream.status() != QDataStream::Ok) {
return std::nullopt;
} else if (intensity < 0 || intensity > 100) {
return std::nullopt;
}
auto result = WallPaper(id);
result._accessHash = accessHash;
result._flags = MTPDwallPaper::Flags::from_raw(flags);
result._slug = slug;
result._settings = MTPDwallPaperSettings::Flags::from_raw(settings);
result._backgroundColor = MaybeColorFromSerialized(backgroundColor);
result._intensity = intensity;
if (!ValidateFlags(result._flags) || !ValidateFlags(result._settings)) {
return std::nullopt;
}
return result;
}
std::optional<WallPaper> WallPaper::FromLegacySerialized(
quint64 id,
quint64 accessHash,
quint32 flags,
QString slug) {
auto result = WallPaper(id);
result._accessHash = accessHash;
result._flags = MTPDwallPaper::Flags::from_raw(flags);
result._slug = slug;
2019-01-29 07:29:38 +00:00
result._backgroundColor = ColorFromString(slug);
2019-01-28 13:59:49 +00:00
if (!ValidateFlags(result._flags)) {
return std::nullopt;
}
return result;
}
std::optional<WallPaper> WallPaper::FromLegacyId(qint32 legacyId) {
auto result = WallPaper(FromLegacyBackgroundId(legacyId));
if (!IsCustomWallPaper(result)) {
result._flags = MTPDwallPaper::Flag::f_default;
}
return result;
}
std::optional<WallPaper> WallPaper::FromColorSlug(const QString &slug) {
2019-01-29 07:29:38 +00:00
if (const auto color = ColorFromString(slug)) {
2019-01-28 13:59:49 +00:00
auto result = CustomWallPaper();
result._slug = slug;
result._backgroundColor = color;
return result;
}
return std::nullopt;
}
WallPaper ThemeWallPaper() {
return WallPaper(kThemeBackground);
}
bool IsThemeWallPaper(const WallPaper &paper) {
return (paper.id() == kThemeBackground);
}
WallPaper CustomWallPaper() {
return WallPaper(kCustomBackground);
}
bool IsCustomWallPaper(const WallPaper &paper) {
return (paper.id() == kCustomBackground);
}
WallPaper Legacy1DefaultWallPaper() {
return WallPaper(kLegacy1DefaultBackground);
}
bool IsLegacy1DefaultWallPaper(const WallPaper &paper) {
return (paper.id() == kLegacy1DefaultBackground);
}
WallPaper DefaultWallPaper() {
return WallPaper(kDefaultBackground);
}
bool IsDefaultWallPaper(const WallPaper &paper) {
return (paper.id() == kDefaultBackground);
}
namespace details {
WallPaper UninitializedWallPaper() {
return WallPaper(kUninitializedBackground);
}
bool IsUninitializedWallPaper(const WallPaper &paper) {
return (paper.id() == kUninitializedBackground);
}
WallPaper TestingThemeWallPaper() {
return WallPaper(kTestingThemeBackground);
}
bool IsTestingThemeWallPaper(const WallPaper &paper) {
return (paper.id() == kTestingThemeBackground);
}
WallPaper TestingDefaultWallPaper() {
return WallPaper(kTestingDefaultBackground);
}
bool IsTestingDefaultWallPaper(const WallPaper &paper) {
return (paper.id() == kTestingDefaultBackground);
}
WallPaper TestingEditorWallPaper() {
return WallPaper(kTestingEditorBackground);
}
bool IsTestingEditorWallPaper(const WallPaper &paper) {
return (paper.id() == kTestingEditorBackground);
}
} // namespace details
} // namespace Data
namespace Window {
namespace Theme {
namespace {
constexpr auto kThemeFileSizeLimit = 5 * 1024 * 1024;
constexpr auto kThemeBackgroundSizeLimit = 4 * 1024 * 1024;
constexpr auto kBackgroundSizeLimit = 25 * 1024 * 1024;
constexpr auto kThemeSchemeSizeLimit = 1024 * 1024;
constexpr auto kNightThemeFile = str_const(":/gui/night.tdesktop-theme");
2019-01-16 17:26:26 +00:00
struct Applying {
QString pathRelative;
QString pathAbsolute;
QByteArray content;
QByteArray paletteForRevert;
Cached cached;
Fn<void()> overrideKeep;
};
2019-01-16 17:26:26 +00:00
NeverFreedPointer<ChatBackground> GlobalBackground;
Applying GlobalApplying;
inline bool AreTestingTheme() {
2019-01-16 17:26:26 +00:00
return !GlobalApplying.paletteForRevert.isEmpty();
};
QByteArray readThemeContent(const QString &path) {
QFile file(path);
if (!file.exists()) {
2017-02-02 16:29:36 +00:00
LOG(("Theme Error: theme file not found: %1").arg(path));
return QByteArray();
}
if (file.size() > kThemeFileSizeLimit) {
2017-02-02 16:29:36 +00:00
LOG(("Theme Error: theme file too large: %1 (should be less than 5 MB, got %2)").arg(path).arg(file.size()));
return QByteArray();
}
if (!file.open(QIODevice::ReadOnly)) {
LOG(("Theme Error: could not open theme file: %1").arg(path));
return QByteArray();
}
return file.readAll();
}
inline uchar readHexUchar(char code, bool &error) {
if (code >= '0' && code <= '9') {
return ((code - '0') & 0xFF);
} else if (code >= 'a' && code <= 'f') {
return ((code + 10 - 'a') & 0xFF);
} else if (code >= 'A' && code <= 'F') {
return ((code + 10 - 'A') & 0xFF);
}
error = true;
return 0xFF;
}
inline uchar readHexUchar(char char1, char char2, bool &error) {
return ((readHexUchar(char1, error) & 0x0F) << 4) | (readHexUchar(char2, error) & 0x0F);
}
bool readNameAndValue(const char *&from, const char *end, QLatin1String *outName, QLatin1String *outValue) {
using base::parse::skipWhitespaces;
using base::parse::readName;
if (!skipWhitespaces(from, end)) return true;
*outName = readName(from, end);
if (outName->size() == 0) {
2017-02-02 16:29:36 +00:00
LOG(("Theme Error: Could not read name in the color scheme."));
return false;
}
if (!skipWhitespaces(from, end)) {
2017-02-02 16:29:36 +00:00
LOG(("Theme Error: Unexpected end of the color scheme."));
return false;
}
if (*from != ':') {
LOG(("Theme Error: Expected ':' between each name and value in the color scheme (while reading key '%1')").arg(*outName));
return false;
}
if (!skipWhitespaces(++from, end)) {
2017-02-02 16:29:36 +00:00
LOG(("Theme Error: Unexpected end of the color scheme."));
return false;
}
auto valueStart = from;
if (*from == '#') ++from;
if (readName(from, end).size() == 0) {
LOG(("Theme Error: Expected a color value in #rrggbb or #rrggbbaa format in the color scheme (while reading key '%1')").arg(*outName));
return false;
}
*outValue = QLatin1String(valueStart, from - valueStart);
if (!skipWhitespaces(from, end)) {
2017-02-02 16:29:36 +00:00
LOG(("Theme Error: Unexpected end of the color scheme."));
return false;
}
if (*from != ';') {
LOG(("Theme Error: Expected ';' after each value in the color scheme (while reading key '%1')").arg(*outName));
return false;
}
++from;
return true;
}
enum class SetResult {
Ok,
Bad,
NotFound,
};
SetResult setColorSchemeValue(QLatin1String name, QLatin1String value, Instance *out) {
auto result = style::palette::SetResult::Ok;
auto size = value.size();
auto data = value.data();
if (data[0] == '#' && (size == 7 || size == 9)) {
auto error = false;
auto r = readHexUchar(data[1], data[2], error);
auto g = readHexUchar(data[3], data[4], error);
auto b = readHexUchar(data[5], data[6], error);
auto a = (size == 9) ? readHexUchar(data[7], data[8], error) : uchar(255);
if (error) {
LOG(("Theme Warning: Skipping value '%1: %2' (expected a color value in #rrggbb or #rrggbbaa or a previously defined key in the color scheme)").arg(name).arg(value));
return SetResult::Ok;
} else if (out) {
result = out->palette.setColor(name, r, g, b, a);
} else {
result = style::main_palette::setColor(name, r, g, b, a);
}
} else {
if (out) {
result = out->palette.setColor(name, value);
} else {
result = style::main_palette::setColor(name, value);
}
}
if (result == style::palette::SetResult::Ok) {
return SetResult::Ok;
} else if (result == style::palette::SetResult::KeyNotFound) {
return SetResult::NotFound;
} else if (result == style::palette::SetResult::ValueNotFound) {
LOG(("Theme Warning: Skipping value '%1: %2' (expected a color value in #rrggbb or #rrggbbaa or a previously defined key in the color scheme)").arg(name).arg(value));
return SetResult::Ok;
} else if (result == style::palette::SetResult::Duplicate) {
LOG(("Theme Warning: Color value appears more than once in the color scheme (while applying '%1: %2')").arg(name).arg(value));
return SetResult::Ok;
} else {
LOG(("Theme Error: Unexpected internal error."));
}
return SetResult::Bad;
}
bool loadColorScheme(const QByteArray &content, Instance *out) {
auto unsupported = QMap<QLatin1String, QLatin1String>();
return ReadPaletteValues(content, [&unsupported, out](QLatin1String name, QLatin1String value) {
// Find the named value in the already read unsupported list.
value = unsupported.value(value, value);
auto result = setColorSchemeValue(name, value, out);
if (result == SetResult::Bad) {
return false;
} else if (result == SetResult::NotFound) {
unsupported.insert(name, value);
}
return true;
});
}
void applyBackground(QImage &&background, bool tiled, Instance *out) {
if (out) {
out->background = std::move(background);
out->tiled = tiled;
} else {
Background()->setThemeData(std::move(background), tiled);
}
}
bool loadThemeFromCache(const QByteArray &content, const Cached &cache) {
2016-10-31 12:29:26 +00:00
if (cache.paletteChecksum != style::palette::Checksum()) {
return false;
}
if (cache.contentChecksum != hashCrc32(content.constData(), content.size())) {
return false;
}
QImage background;
if (!cache.background.isEmpty()) {
QDataStream stream(cache.background);
QImageReader reader(stream.device());
#ifndef OS_MAC_OLD
reader.setAutoTransform(true);
#endif // OS_MAC_OLD
if (!reader.read(&background) || background.isNull()) {
return false;
}
}
if (!style::main_palette::load(cache.colors)) {
return false;
}
2018-07-21 13:54:00 +00:00
Background()->saveAdjustableColors();
if (!background.isNull()) {
applyBackground(std::move(background), cache.tiled, nullptr);
}
return true;
}
enum class LoadResult {
Loaded,
Failed,
NotFound,
};
LoadResult loadBackgroundFromFile(zlib::FileToRead &file, const char *filename, QByteArray *outBackground) {
*outBackground = file.readFileContent(filename, zlib::kCaseInsensitive, kThemeBackgroundSizeLimit);
if (file.error() == UNZ_OK) {
return LoadResult::Loaded;
} else if (file.error() == UNZ_END_OF_LIST_OF_FILE) {
file.clearError();
return LoadResult::NotFound;
}
2017-02-02 16:29:36 +00:00
LOG(("Theme Error: could not read '%1' in the theme file.").arg(filename));
return LoadResult::Failed;
}
bool loadBackground(zlib::FileToRead &file, QByteArray *outBackground, bool *outTiled) {
auto result = loadBackgroundFromFile(file, "background.jpg", outBackground);
if (result != LoadResult::NotFound) return (result == LoadResult::Loaded);
result = loadBackgroundFromFile(file, "background.png", outBackground);
if (result != LoadResult::NotFound) return (result == LoadResult::Loaded);
*outTiled = true;
result = loadBackgroundFromFile(file, "tiled.jpg", outBackground);
if (result != LoadResult::NotFound) return (result == LoadResult::Loaded);
result = loadBackgroundFromFile(file, "tiled.png", outBackground);
if (result != LoadResult::NotFound) return (result == LoadResult::Loaded);
return true;
}
bool loadTheme(const QByteArray &content, Cached &cache, Instance *out = nullptr) {
cache = Cached();
zlib::FileToRead file(content);
unz_global_info globalInfo = { 0 };
file.getGlobalInfo(&globalInfo);
if (file.error() == UNZ_OK) {
auto schemeContent = file.readFileContent("colors.tdesktop-theme", zlib::kCaseInsensitive, kThemeSchemeSizeLimit);
if (file.error() == UNZ_END_OF_LIST_OF_FILE) {
file.clearError();
schemeContent = file.readFileContent("colors.tdesktop-palette", zlib::kCaseInsensitive, kThemeSchemeSizeLimit);
}
if (file.error() != UNZ_OK) {
LOG(("Theme Error: could not read 'colors.tdesktop-theme' or 'colors.tdesktop-palette' in the theme file."));
return false;
}
if (!loadColorScheme(schemeContent, out)) {
return false;
}
2018-07-21 13:54:00 +00:00
Background()->saveAdjustableColors();
auto backgroundTiled = false;
auto backgroundContent = QByteArray();
if (!loadBackground(file, &backgroundContent, &backgroundTiled)) {
return false;
}
if (!backgroundContent.isEmpty()) {
auto check = QBuffer(&backgroundContent);
auto reader = QImageReader(&check);
const auto size = reader.size();
if (size.isEmpty()
|| (size.width() * size.height() > kBackgroundSizeLimit)) {
LOG(("Theme Error: bad background image size in the theme file."));
return false;
}
auto background = App::readImage(backgroundContent);
if (background.isNull()) {
2017-02-02 16:29:36 +00:00
LOG(("Theme Error: could not read background image in the theme file."));
return false;
}
auto buffer = QBuffer(&cache.background);
if (!background.save(&buffer, "BMP")) {
2017-02-02 16:29:36 +00:00
LOG(("Theme Error: could not write background image as a BMP to cache."));
return false;
}
cache.tiled = backgroundTiled;
applyBackground(std::move(background), cache.tiled, out);
}
} else {
// Looks like it is not a .zip theme.
if (!loadColorScheme(content, out)) {
return false;
}
2018-07-21 13:54:00 +00:00
Background()->saveAdjustableColors();
}
if (out) {
cache.colors = out->palette.save();
} else {
cache.colors = style::main_palette::save();
}
2016-10-31 12:29:26 +00:00
cache.paletteChecksum = style::palette::Checksum();
cache.contentChecksum = hashCrc32(content.constData(), content.size());
return true;
}
QImage prepareBackgroundImage(QImage &&image) {
if (image.format() != QImage::Format_ARGB32_Premultiplied) {
image = std::move(image).convertToFormat(
QImage::Format_ARGB32_Premultiplied);
}
image.setDevicePixelRatio(cRetinaFactor());
return std::move(image);
}
void adjustColor(style::color color, float64 hue, float64 saturation) {
auto original = color->c;
original.setHslF(hue, saturation, original.lightnessF(), original.alphaF());
color.set(original.red(), original.green(), original.blue(), original.alpha());
}
2018-08-02 12:47:50 +00:00
void WriteAppliedTheme() {
auto saved = Saved();
2019-01-16 17:26:26 +00:00
saved.pathRelative = GlobalApplying.pathRelative;
saved.pathAbsolute = GlobalApplying.pathAbsolute;
saved.content = std::move(GlobalApplying.content);
saved.cache = std::move(GlobalApplying.cached);
2018-08-02 12:47:50 +00:00
Local::writeTheme(saved);
}
void ClearApplying() {
2019-01-16 17:26:26 +00:00
GlobalApplying = Applying();
2018-08-02 12:47:50 +00:00
}
} // namespace
2018-07-21 13:54:00 +00:00
ChatBackground::AdjustableColor::AdjustableColor(style::color data)
: item(data)
, original(data->c) {
}
ChatBackground::ChatBackground() : _adjustableColors({
st::msgServiceBg,
st::msgServiceBgSelected,
st::historyScrollBg,
st::historyScrollBgOver,
st::historyScrollBarBg,
st::historyScrollBarBgOver }) {
saveAdjustableColors();
}
void ChatBackground::setThemeData(QImage &&themeImage, bool themeTile) {
_themeImage = prepareBackgroundImage(std::move(themeImage));
_themeTile = themeTile;
}
void ChatBackground::start() {
2019-01-28 13:59:49 +00:00
if (Data::details::IsUninitializedWallPaper(_paper)) {
if (!Local::readBackground()) {
2019-01-28 13:59:49 +00:00
setImage(Data::ThemeWallPaper());
}
}
}
2019-01-16 17:26:26 +00:00
void ChatBackground::setImage(
const Data::WallPaper &paper,
QImage &&image) {
2019-01-29 07:29:38 +00:00
if (image.format() != QImage::Format_ARGB32_Premultiplied) {
image = std::move(image).convertToFormat(
QImage::Format_ARGB32_Premultiplied);
}
2019-01-28 13:59:49 +00:00
const auto needResetAdjustable = Data::IsDefaultWallPaper(paper)
&& !Data::IsDefaultWallPaper(_paper)
&& !nightMode()
&& _themeAbsolutePath.isEmpty();
2019-01-28 13:59:49 +00:00
if (Data::IsThemeWallPaper(paper) && _themeImage.isNull()) {
setPaper(Data::DefaultWallPaper());
2019-01-16 17:26:26 +00:00
} else {
setPaper(paper);
2019-01-16 17:26:26 +00:00
if (needResetAdjustable) {
// If we had a default color theme with non-default background,
// and we switch to default background we must somehow switch from
// adjusted service colors to default (non-adjusted) service colors.
// The only way to do that right now is through full palette reset.
restoreAdjustableColors();
}
}
2019-01-28 13:59:49 +00:00
if (Data::IsThemeWallPaper(_paper)) {
(nightMode() ? _tileNightValue : _tileDayValue) = _themeTile;
setPreparedImage(QImage(_themeImage));
2019-01-28 13:59:49 +00:00
} else if (Data::details::IsTestingThemeWallPaper(_paper)
|| Data::details::IsTestingDefaultWallPaper(_paper)
|| Data::details::IsTestingEditorWallPaper(_paper)) {
if (Data::details::IsTestingDefaultWallPaper(_paper) || image.isNull()) {
image.load(qsl(":/gui/art/bg.jpg"));
2019-01-28 13:59:49 +00:00
setPaper(Data::details::TestingDefaultWallPaper());
}
setPreparedImage(prepareBackgroundImage(std::move(image)));
} else {
2019-01-28 13:59:49 +00:00
if (Data::IsLegacy1DefaultWallPaper(_paper)) {
image.load(qsl(":/gui/art/bg_initial.jpg"));
const auto scale = cScale() * cIntRetinaFactor();
if (scale != 100) {
image = image.scaledToWidth(
ConvertScale(image.width(), scale),
Qt::SmoothTransformation);
}
2019-01-28 13:59:49 +00:00
} else if (Data::IsDefaultWallPaper(_paper)
2019-01-29 07:29:38 +00:00
|| (!_paper.backgroundColor() && image.isNull())) {
2019-01-28 13:59:49 +00:00
setPaper(Data::DefaultWallPaper());
image.load(qsl(":/gui/art/bg.jpg"));
}
2019-01-16 17:26:26 +00:00
Local::writeBackground(
_paper,
2019-01-28 13:59:49 +00:00
((Data::IsDefaultWallPaper(_paper)
|| Data::IsLegacy1DefaultWallPaper(_paper))
2019-01-16 17:26:26 +00:00
? QImage()
: image));
2019-01-29 07:29:38 +00:00
if (const auto fill = _paper.backgroundColor()) {
if (_paper.isPattern() && !image.isNull()) {
setPreparedImage(Data::PreparePatternImage(
std::move(image),
*fill,
PatternColor(*fill),
_paper.patternIntensity()));
} else {
_pixmap = QPixmap();
_pixmapForTiled = QPixmap();
if (adjustPaletteRequired()) {
adjustPaletteUsingColor(*fill);
}
}
} else {
setPreparedImage(prepareBackgroundImage(std::move(image)));
}
}
2019-01-29 07:29:38 +00:00
Assert((!_pixmap.isNull() && !_pixmapForTiled.isNull())
|| colorForFill());
notify(BackgroundUpdate(BackgroundUpdate::Type::New, tile()));
2018-07-21 13:54:00 +00:00
if (needResetAdjustable) {
notify(BackgroundUpdate(BackgroundUpdate::Type::TestingTheme, tile()), true);
notify(BackgroundUpdate(BackgroundUpdate::Type::ApplyingTheme, tile()), true);
}
}
void ChatBackground::setPreparedImage(QImage &&image) {
Expects(image.format() == QImage::Format_ARGB32_Premultiplied);
image.setDevicePixelRatio(cRetinaFactor());
if (adjustPaletteRequired()) {
2018-07-21 13:54:00 +00:00
adjustPaletteUsingBackground(image);
}
auto width = image.width();
auto height = image.height();
Assert(width > 0 && height > 0);
auto isSmallForTiled = (width < kMinimumTiledSize || height < kMinimumTiledSize);
if (isSmallForTiled) {
auto repeatTimesX = qCeil(kMinimumTiledSize / float64(width));
auto repeatTimesY = qCeil(kMinimumTiledSize / float64(height));
auto imageForTiled = QImage(width * repeatTimesX, height * repeatTimesY, QImage::Format_ARGB32_Premultiplied);
imageForTiled.setDevicePixelRatio(image.devicePixelRatio());
auto imageForTiledBytes = imageForTiled.bits();
auto bytesInLine = width * sizeof(uint32);
for (auto timesY = 0; timesY != repeatTimesY; ++timesY) {
auto imageBytes = image.constBits();
for (auto y = 0; y != height; ++y) {
for (auto timesX = 0; timesX != repeatTimesX; ++timesX) {
memcpy(imageForTiledBytes, imageBytes, bytesInLine);
imageForTiledBytes += bytesInLine;
}
imageBytes += image.bytesPerLine();
imageForTiledBytes += imageForTiled.bytesPerLine() - (repeatTimesX * bytesInLine);
}
}
_pixmapForTiled = App::pixmapFromImageInPlace(std::move(imageForTiled));
}
_pixmap = App::pixmapFromImageInPlace(std::move(image));
if (!isSmallForTiled) {
_pixmapForTiled = _pixmap;
}
}
void ChatBackground::setPaper(const Data::WallPaper &paper) {
_paper = paper;
}
bool ChatBackground::adjustPaletteRequired() {
const auto usingThemeBackground = [&] {
2019-01-28 13:59:49 +00:00
return Data::IsThemeWallPaper(_paper)
|| Data::details::IsTestingThemeWallPaper(_paper);
};
const auto usingDefaultBackground = [&] {
2019-01-28 13:59:49 +00:00
return Data::IsDefaultWallPaper(_paper)
|| Data::details::IsTestingDefaultWallPaper(_paper);
};
const auto testingPalette = [&] {
const auto path = AreTestingTheme()
? GlobalApplying.pathAbsolute
: _themeAbsolutePath;
return IsPaletteTestingPath(path);
};
if (testingPalette()) {
return false;
} else if (isNonDefaultThemeOrBackground() || nightMode()) {
return !usingThemeBackground();
}
return !usingDefaultBackground();
}
2018-07-21 13:54:00 +00:00
void ChatBackground::adjustPaletteUsingBackground(const QImage &img) {
Assert(img.format() == QImage::Format_ARGB32_Premultiplied);
uint64 components[3] = { 0 };
uint64 componentsScroll[3] = { 0 };
auto w = img.width();
auto h = img.height();
auto size = w * h;
if (auto pix = img.constBits()) {
for (auto i = 0, l = size * 4; i != l; i += 4) {
components[2] += pix[i + 0];
components[1] += pix[i + 1];
components[0] += pix[i + 2];
}
}
if (size) {
for (auto i = 0; i != 3; ++i) {
components[i] /= size;
}
}
adjustPaletteUsingColor(
QColor(components[0], components[1], components[2]));
}
void ChatBackground::adjustPaletteUsingColor(QColor color) {
2019-01-29 07:29:38 +00:00
const auto hue = color.hslHueF();
const auto saturation = color.hslSaturationF();
2018-07-21 13:54:00 +00:00
for (const auto &color : _adjustableColors) {
adjustColor(color.item, hue, saturation);
}
}
2019-01-29 07:29:38 +00:00
std::optional<QColor> ChatBackground::colorForFill() const {
return _pixmap.isNull() ? _paper.backgroundColor() : std::nullopt;
}
QImage ChatBackground::createCurrentImage() const {
2019-01-29 07:29:38 +00:00
if (const auto fill = colorForFill()) {
auto result = QImage(
kMinimumTiledSize,
kMinimumTiledSize,
QImage::Format_ARGB32_Premultiplied);
result.fill(*fill);
return result;
}
2019-01-29 07:29:38 +00:00
return pixmap().toImage(); // #TODO patterns
}
bool ChatBackground::tile() const {
return nightMode() ? _tileNightValue : _tileDayValue;
}
bool ChatBackground::tileDay() const {
2019-01-28 13:59:49 +00:00
if (Data::details::IsTestingThemeWallPaper(_paper) ||
Data::details::IsTestingDefaultWallPaper(_paper)) {
if (!nightMode()) {
return _tileForRevert;
}
}
return _tileDayValue;
}
bool ChatBackground::tileNight() const {
2019-01-28 13:59:49 +00:00
if (Data::details::IsTestingThemeWallPaper(_paper) ||
Data::details::IsTestingDefaultWallPaper(_paper)) {
if (nightMode()) {
return _tileForRevert;
}
}
return _tileNightValue;
}
void ChatBackground::ensureStarted() {
2019-01-29 07:29:38 +00:00
if (_pixmap.isNull() && !_paper.backgroundColor()) {
// We should start first, otherwise the default call
// to start() will reset this value to _themeTile.
start();
}
}
void ChatBackground::setTile(bool tile) {
ensureStarted();
const auto old = this->tile();
if (nightMode()) {
setTileNightValue(tile);
} else {
setTileDayValue(tile);
}
if (this->tile() != old) {
2019-01-28 13:59:49 +00:00
if (!Data::details::IsTestingThemeWallPaper(_paper)
&& !Data::details::IsTestingDefaultWallPaper(_paper)) {
Local::writeUserSettings();
}
notify(BackgroundUpdate(BackgroundUpdate::Type::Changed, tile));
}
}
void ChatBackground::setTileDayValue(bool tile) {
ensureStarted();
_tileDayValue = tile;
}
void ChatBackground::setTileNightValue(bool tile) {
ensureStarted();
_tileNightValue = tile;
}
void ChatBackground::setThemeAbsolutePath(const QString &path) {
_themeAbsolutePath = path;
}
QString ChatBackground::themeAbsolutePath() const {
return _themeAbsolutePath;
}
void ChatBackground::reset() {
2019-01-28 13:59:49 +00:00
if (Data::details::IsTestingThemeWallPaper(_paper)
|| Data::details::IsTestingDefaultWallPaper(_paper)) {
if (_themeImage.isNull()) {
2019-01-28 13:59:49 +00:00
_paperForRevert = Data::DefaultWallPaper();
_imageForRevert = QImage();
_tileForRevert = false;
} else {
2019-01-28 13:59:49 +00:00
_paperForRevert = Data::ThemeWallPaper();
_imageForRevert = _themeImage;
_tileForRevert = _themeTile;
}
} else {
2019-01-28 13:59:49 +00:00
setImage(Data::ThemeWallPaper());
2018-07-21 13:54:00 +00:00
restoreAdjustableColors();
notify(BackgroundUpdate(BackgroundUpdate::Type::TestingTheme, tile()), true);
notify(BackgroundUpdate(BackgroundUpdate::Type::ApplyingTheme, tile()), true);
}
}
void ChatBackground::saveForRevert() {
ensureStarted();
2019-01-28 13:59:49 +00:00
if (!Data::details::IsTestingThemeWallPaper(_paper)
&& !Data::details::IsTestingDefaultWallPaper(_paper)) {
2019-01-16 17:26:26 +00:00
_paperForRevert = _paper;
_imageForRevert = std::move(_pixmap).toImage();
_tileForRevert = tile();
}
}
2018-07-21 13:54:00 +00:00
void ChatBackground::saveAdjustableColors() {
for (auto &color : _adjustableColors) {
color.original = color.item->c;
}
}
void ChatBackground::restoreAdjustableColors() {
for (const auto &color : _adjustableColors) {
const auto value = color.original;
color.item.set(value.red(), value.green(), value.blue(), value.alpha());
}
}
void ChatBackground::setTestingTheme(Instance &&theme) {
style::main_palette::apply(theme.palette);
2018-07-21 13:54:00 +00:00
saveAdjustableColors();
auto switchToThemeBackground = !theme.background.isNull()
2019-01-28 13:59:49 +00:00
|| Data::IsThemeWallPaper(_paper)
|| (Data::IsDefaultWallPaper(_paper)
&& !nightMode()
&& _themeAbsolutePath.isEmpty());
2019-01-16 17:26:26 +00:00
if (AreTestingTheme() && IsPaletteTestingPath(GlobalApplying.pathAbsolute)) {
// Grab current background image if it is not already custom
2019-01-28 13:59:49 +00:00
if (!Data::IsCustomWallPaper(_paper)) {
saveForRevert();
2019-01-28 13:59:49 +00:00
setImage(
Data::details::TestingEditorWallPaper(),
std::move(_pixmap).toImage());
}
} else if (switchToThemeBackground) {
saveForRevert();
2019-01-28 13:59:49 +00:00
setImage(
Data::details::TestingThemeWallPaper(),
std::move(theme.background));
setTile(theme.tiled);
} else {
// Apply current background image so that service bg colors are recounted.
2019-01-29 07:29:38 +00:00
// #TODO patterns
2019-01-16 17:26:26 +00:00
setImage(_paper, std::move(_pixmap).toImage());
}
notify(BackgroundUpdate(BackgroundUpdate::Type::TestingTheme, tile()), true);
}
void ChatBackground::setTestingDefaultTheme() {
style::main_palette::reset();
2018-07-21 13:54:00 +00:00
saveAdjustableColors();
saveForRevert();
2019-01-28 13:59:49 +00:00
setImage(Data::details::TestingDefaultWallPaper());
setTile(false);
notify(BackgroundUpdate(BackgroundUpdate::Type::TestingTheme, tile()), true);
}
void ChatBackground::keepApplied(const QString &path, bool write) {
setThemeAbsolutePath(path);
2019-01-28 13:59:49 +00:00
if (Data::details::IsTestingEditorWallPaper(_paper)) {
setPaper(Data::CustomWallPaper());
_themeImage = QImage();
_themeTile = false;
if (write) {
writeNewBackgroundSettings();
}
2019-01-28 13:59:49 +00:00
} else if (Data::details::IsTestingThemeWallPaper(_paper)) {
setPaper(Data::ThemeWallPaper());
_themeImage = prepareBackgroundImage(_pixmap.toImage());
_themeTile = tile();
if (write) {
writeNewBackgroundSettings();
}
2019-01-28 13:59:49 +00:00
} else if (Data::details::IsTestingDefaultWallPaper(_paper)) {
setPaper(Data::DefaultWallPaper());
_themeImage = QImage();
_themeTile = false;
if (write) {
writeNewBackgroundSettings();
}
}
notify(BackgroundUpdate(BackgroundUpdate::Type::ApplyingTheme, tile()), true);
}
2018-07-21 13:54:00 +00:00
bool ChatBackground::isNonDefaultThemeOrBackground() {
start();
return nightMode()
? (_themeAbsolutePath != NightThemePath()
2019-01-28 13:59:49 +00:00
|| !Data::IsThemeWallPaper(_paper))
: (!_themeAbsolutePath.isEmpty()
2019-01-28 13:59:49 +00:00
|| !Data::IsDefaultWallPaper(_paper));
}
bool ChatBackground::isNonDefaultBackground() {
start();
return _themeAbsolutePath.isEmpty()
2019-01-28 13:59:49 +00:00
? !Data::IsDefaultWallPaper(_paper)
: !Data::IsThemeWallPaper(_paper);
}
void ChatBackground::writeNewBackgroundSettings() {
if (tile() != _tileForRevert) {
Local::writeUserSettings();
}
Local::writeBackground(
2019-01-16 17:26:26 +00:00
_paper,
2019-01-28 13:59:49 +00:00
((Data::IsThemeWallPaper(_paper)
|| Data::IsDefaultWallPaper(_paper))
? QImage()
2019-01-29 07:29:38 +00:00
: _pixmap.toImage())); // #TODO patterns
}
void ChatBackground::revert() {
2019-01-28 13:59:49 +00:00
if (Data::details::IsTestingThemeWallPaper(_paper)
|| Data::details::IsTestingDefaultWallPaper(_paper)
|| Data::details::IsTestingEditorWallPaper(_paper)) {
setTile(_tileForRevert);
2019-01-16 17:26:26 +00:00
setImage(_paperForRevert, std::move(_imageForRevert));
} else {
// Apply current background image so that service bg colors are recounted.
2019-01-29 07:29:38 +00:00
// #TODO patterns
2019-01-16 17:26:26 +00:00
setImage(_paper, std::move(_pixmap).toImage());
}
notify(BackgroundUpdate(BackgroundUpdate::Type::RevertingTheme, tile()), true);
}
void ChatBackground::setNightModeValue(bool nightMode) {
_nightMode = nightMode;
}
bool ChatBackground::nightMode() const {
return _nightMode;
}
void ChatBackground::toggleNightMode(std::optional<QString> themePath) {
const auto settingDefault = themePath.has_value();
const auto oldNightMode = _nightMode;
const auto newNightMode = !_nightMode;
_nightMode = newNightMode;
auto read = settingDefault ? Saved() : Local::readThemeAfterSwitch();
auto path = read.pathAbsolute;
_nightMode = oldNightMode;
auto oldTileValue = (_nightMode ? _tileNightValue : _tileDayValue);
2018-08-02 12:47:50 +00:00
const auto alreadyOnDisk = [&] {
if (read.content.isEmpty()) {
return false;
}
auto preview = std::make_unique<Preview>();
preview->pathAbsolute = std::move(read.pathAbsolute);
preview->pathRelative = std::move(read.pathRelative);
preview->content = std::move(read.content);
preview->instance.cached = std::move(read.cache);
const auto loaded = loadTheme(
preview->content,
preview->instance.cached,
&preview->instance);
if (!loaded) {
return false;
}
Apply(std::move(preview));
return true;
}();
2018-08-02 12:47:50 +00:00
if (!alreadyOnDisk) {
path = themePath
? *themePath
: (newNightMode ? NightThemePath() : QString());
ApplyDefaultWithPath(path);
}
// Theme editor could have already reverted the testing of this toggle.
if (AreTestingTheme()) {
2019-01-16 17:26:26 +00:00
GlobalApplying.overrideKeep = [=] {
_nightMode = newNightMode;
2018-07-24 19:49:37 +00:00
// Restore the value, it was set inside theme testing.
(oldNightMode ? _tileNightValue : _tileDayValue) = oldTileValue;
if (!alreadyOnDisk) {
// First-time switch to default night mode should write it.
WriteAppliedTheme();
}
ClearApplying();
keepApplied(path, settingDefault);
if (tile() != _tileForRevert) {
Local::writeUserSettings();
}
Local::writeSettings();
if (!settingDefault && !Local::readBackground()) {
2019-01-28 13:59:49 +00:00
setImage(Data::ThemeWallPaper());
}
};
}
}
ChatBackground *Background() {
2019-01-16 17:26:26 +00:00
GlobalBackground.createIfNull();
return GlobalBackground.data();
}
bool Load(Saved &&saved) {
if (saved.content.size() < 4) {
LOG(("Theme Error: Could not load theme from '%1' (%2)"
).arg(saved.pathRelative
).arg(saved.pathAbsolute));
return false;
}
2019-01-16 17:26:26 +00:00
GlobalBackground.createIfNull();
if (loadThemeFromCache(saved.content, saved.cache)) {
Background()->setThemeAbsolutePath(saved.pathAbsolute);
return true;
}
if (!loadTheme(saved.content, saved.cache)) {
return false;
}
Local::writeTheme(saved);
Background()->setThemeAbsolutePath(saved.pathAbsolute);
return true;
}
void Unload() {
2019-01-16 17:26:26 +00:00
GlobalBackground.clear();
GlobalApplying = Applying();
}
bool Apply(const QString &filepath) {
if (auto preview = PreviewFromFile(filepath)) {
return Apply(std::move(preview));
}
return false;
}
bool Apply(std::unique_ptr<Preview> preview) {
2019-01-16 17:26:26 +00:00
GlobalApplying.pathRelative = std::move(preview->pathRelative);
GlobalApplying.pathAbsolute = std::move(preview->pathAbsolute);
GlobalApplying.content = std::move(preview->content);
GlobalApplying.cached = std::move(preview->instance.cached);
if (GlobalApplying.paletteForRevert.isEmpty()) {
GlobalApplying.paletteForRevert = style::main_palette::save();
}
Background()->setTestingTheme(std::move(preview->instance));
return true;
}
void ApplyDefaultWithPath(const QString &themePath) {
if (!themePath.isEmpty()) {
if (auto preview = PreviewFromFile(themePath)) {
Apply(std::move(preview));
}
} else {
2019-01-16 17:26:26 +00:00
GlobalApplying.pathRelative = QString();
GlobalApplying.pathAbsolute = QString();
GlobalApplying.content = QByteArray();
GlobalApplying.cached = Cached();
if (GlobalApplying.paletteForRevert.isEmpty()) {
GlobalApplying.paletteForRevert = style::main_palette::save();
}
Background()->setTestingDefaultTheme();
}
}
bool ApplyEditedPalette(const QString &path, const QByteArray &content) {
Instance out;
if (!loadColorScheme(content, &out)) {
return false;
}
out.cached.colors = out.palette.save();
out.cached.paletteChecksum = style::palette::Checksum();
out.cached.contentChecksum = hashCrc32(content.constData(), content.size());
2019-01-16 17:26:26 +00:00
GlobalApplying.pathRelative = path.isEmpty()
? QString()
: QDir().relativeFilePath(path);
2019-01-16 17:26:26 +00:00
GlobalApplying.pathAbsolute = path.isEmpty()
? QString()
: QFileInfo(path).absoluteFilePath();
2019-01-16 17:26:26 +00:00
GlobalApplying.content = content;
GlobalApplying.cached = out.cached;
if (GlobalApplying.paletteForRevert.isEmpty()) {
GlobalApplying.paletteForRevert = style::main_palette::save();
}
Background()->setTestingTheme(std::move(out));
KeepApplied();
return true;
}
void KeepApplied() {
if (!AreTestingTheme()) {
return;
2019-01-16 17:26:26 +00:00
} else if (GlobalApplying.overrideKeep) {
2018-09-26 13:06:30 +00:00
// This callback will be destroyed while running.
// And it won't be able to safely access captures after that.
// So we save it on stack for the time while it is running.
2019-01-16 17:26:26 +00:00
const auto onstack = base::take(GlobalApplying.overrideKeep);
onstack();
return;
}
2019-01-16 17:26:26 +00:00
const auto path = GlobalApplying.pathAbsolute;
2018-08-02 12:47:50 +00:00
WriteAppliedTheme();
ClearApplying();
Background()->keepApplied(path, true);
}
void Revert() {
if (!AreTestingTheme()) {
return;
}
2019-01-16 17:26:26 +00:00
style::main_palette::load(GlobalApplying.paletteForRevert);
2018-07-21 13:54:00 +00:00
Background()->saveAdjustableColors();
2018-08-02 12:47:50 +00:00
ClearApplying();
Background()->revert();
}
QString NightThemePath() {
return str_const_toString(kNightThemeFile);
}
bool IsNonDefaultBackground() {
return Background()->isNonDefaultBackground();
}
bool IsNightMode() {
2019-01-16 17:26:26 +00:00
return GlobalBackground ? Background()->nightMode() : false;
}
void SetNightModeValue(bool nightMode) {
2019-01-16 17:26:26 +00:00
if (GlobalBackground || nightMode) {
Background()->setNightModeValue(nightMode);
}
}
void ToggleNightMode() {
Background()->toggleNightMode(std::nullopt);
}
void ToggleNightMode(const QString &path) {
Background()->toggleNightMode(path);
}
bool LoadFromFile(const QString &path, Instance *out, QByteArray *outContent) {
*outContent = readThemeContent(path);
if (outContent->size() < 4) {
2017-02-02 16:29:36 +00:00
LOG(("Theme Error: Could not load theme from %1").arg(path));
return false;
}
return loadTheme(*outContent, out->cached, out);
}
bool IsPaletteTestingPath(const QString &path) {
if (path.endsWith(qstr(".tdesktop-palette"), Qt::CaseInsensitive)) {
return QFileInfo(path).exists();
}
return false;
}
void ComputeBackgroundRects(QRect wholeFill, QSize imageSize, QRect &to, QRect &from) {
if (uint64(imageSize.width()) * wholeFill.height() > uint64(imageSize.height()) * wholeFill.width()) {
float64 pxsize = wholeFill.height() / float64(imageSize.height());
int takewidth = qCeil(wholeFill.width() / pxsize);
if (takewidth > imageSize.width()) {
takewidth = imageSize.width();
} else if ((imageSize.width() % 2) != (takewidth % 2)) {
++takewidth;
}
to = QRect(int((wholeFill.width() - takewidth * pxsize) / 2.), 0, qCeil(takewidth * pxsize), wholeFill.height());
from = QRect((imageSize.width() - takewidth) / 2, 0, takewidth, imageSize.height());
} else {
float64 pxsize = wholeFill.width() / float64(imageSize.width());
int takeheight = qCeil(wholeFill.height() / pxsize);
if (takeheight > imageSize.height()) {
takeheight = imageSize.height();
} else if ((imageSize.height() % 2) != (takeheight % 2)) {
++takeheight;
}
to = QRect(0, int((wholeFill.height() - takeheight * pxsize) / 2.), wholeFill.width(), qCeil(takeheight * pxsize));
from = QRect(0, (imageSize.height() - takeheight) / 2, imageSize.width(), takeheight);
}
}
bool CopyColorsToPalette(const QString &path, const QByteArray &themeContent) {
auto paletteContent = themeContent;
zlib::FileToRead file(themeContent);
unz_global_info globalInfo = { 0 };
file.getGlobalInfo(&globalInfo);
if (file.error() == UNZ_OK) {
paletteContent = file.readFileContent("colors.tdesktop-theme", zlib::kCaseInsensitive, kThemeSchemeSizeLimit);
if (file.error() == UNZ_END_OF_LIST_OF_FILE) {
file.clearError();
paletteContent = file.readFileContent("colors.tdesktop-palette", zlib::kCaseInsensitive, kThemeSchemeSizeLimit);
}
if (file.error() != UNZ_OK) {
LOG(("Theme Error: could not read 'colors.tdesktop-theme' or 'colors.tdesktop-palette' in the theme file, while copying to '%1'.").arg(path));
return false;
}
}
QFile f(path);
if (!f.open(QIODevice::WriteOnly)) {
LOG(("Theme Error: could not open file for write '%1'").arg(path));
return false;
}
if (f.write(paletteContent) != paletteContent.size()) {
LOG(("Theme Error: could not write palette to '%1'").arg(path));
return false;
}
return true;
}
bool ReadPaletteValues(const QByteArray &content, Fn<bool(QLatin1String name, QLatin1String value)> callback) {
if (content.size() > kThemeSchemeSizeLimit) {
LOG(("Theme Error: color scheme file too large (should be less than 1 MB, got %2)").arg(content.size()));
return false;
}
auto data = base::parse::stripComments(content);
auto from = data.constData(), end = from + data.size();
while (from != end) {
auto name = QLatin1String("");
auto value = QLatin1String("");
if (!readNameAndValue(from, end, &name, &value)) {
return false;
}
if (name.size() == 0) { // End of content reached.
return true;
}
if (!callback(name, value)) {
return false;
}
}
return true;
}
2019-01-29 07:29:38 +00:00
QColor PatternColor(QColor background) {
const auto hue = background.hueF();
const auto saturation = background.saturationF();
const auto value = background.valueF();
return QColor::fromHsvF(
hue,
std::min(1.0, saturation + 0.05 + 0.1 * (1. - saturation)),
(value > 0.5
? std::max(0., value * 0.65)
: std::max(0., std::min(1., 1. - value * 0.65))),
0.4
).toRgb();
}
} // namespace Theme
} // namespace Window