diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index afa0c3956..9ef676421 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1602,6 +1602,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_theme_editor_save_palette" = "Save palette file"; "lng_theme_editor_choose_name" = "Save theme file"; "lng_theme_editor_error" = "The editor encountered an error :( See 'log.txt' for details."; +"lng_theme_editor_sure_close" = "Are you sure you want to close the editor? Your changes won't be saved."; "lng_theme_editor_done" = "Theme exported successfully!"; "lng_theme_editor_title" = "Edit color palette"; "lng_theme_editor_export_button" = "Export theme"; diff --git a/Telegram/SourceFiles/core/application.cpp b/Telegram/SourceFiles/core/application.cpp index 25cd1eb05..a852c8b31 100644 --- a/Telegram/SourceFiles/core/application.cpp +++ b/Telegram/SourceFiles/core/application.cpp @@ -143,7 +143,7 @@ Application::~Application() { stopWebLoadManager(); App::deinitMedia(); - Window::Theme::Unload(); + Window::Theme::Uninitialize(); Media::Player::finish(_audio.get()); style::stopManager(); diff --git a/Telegram/SourceFiles/settings/settings_chat.cpp b/Telegram/SourceFiles/settings/settings_chat.cpp index 95a5af6e6..2d1a0ff5f 100644 --- a/Telegram/SourceFiles/settings/settings_chat.cpp +++ b/Telegram/SourceFiles/settings/settings_chat.cpp @@ -999,6 +999,9 @@ void SetupDefaultThemes(not_null container) { const auto chosen = [] { const auto &object = Window::Theme::Background()->themeObject(); + if (object.cloud.id) { + return Type(-1); + } for (const auto &scheme : kSchemesList) { if (object.pathAbsolute == scheme.path) { return scheme.type; diff --git a/Telegram/SourceFiles/storage/localstorage.cpp b/Telegram/SourceFiles/storage/localstorage.cpp index f4129f51f..72d482ea4 100644 --- a/Telegram/SourceFiles/storage/localstorage.cpp +++ b/Telegram/SourceFiles/storage/localstorage.cpp @@ -674,7 +674,7 @@ FileKey _themeKeyDay = 0; FileKey _themeKeyNight = 0; // Theme key legacy may be read in start() with settings. -// But it should be moved to keyDay or keyNight inside loadTheme() +// But it should be moved to keyDay or keyNight inside InitialLoadTheme() // and never used after. FileKey _themeKeyLegacy = 0; @@ -2554,7 +2554,7 @@ void finish() { } } -void loadTheme(); +void InitialLoadTheme(); void readLangPack(); void start() { @@ -2612,7 +2612,7 @@ void start() { _oldSettingsVersion = settingsData.version; _settingsSalt = salt; - loadTheme(); + InitialLoadTheme(); readLangPack(); applyReadContext(std::move(context)); @@ -4259,12 +4259,12 @@ Window::Theme::Saved readThemeUsingKey(FileKey key) { return result; } -QString loadThemeUsingKey(FileKey key) { +std::optional InitialLoadThemeUsingKey(FileKey key) { auto read = readThemeUsingKey(key); const auto result = read.object.pathAbsolute; if (read.object.content.isEmpty() - || !Window::Theme::Load(std::move(read))) { - return QString(); + || !Window::Theme::Initialize(std::move(read))) { + return std::nullopt; } return result; } @@ -4333,7 +4333,7 @@ void clearTheme() { writeTheme(Window::Theme::Saved()); } -void loadTheme() { +void InitialLoadTheme() { const auto key = (_themeKeyLegacy != 0) ? _themeKeyLegacy : (Window::Theme::IsNightMode() @@ -4341,9 +4341,9 @@ void loadTheme() { : _themeKeyDay); if (!key) { return; - } else if (const auto path = loadThemeUsingKey(key); !path.isEmpty()) { + } else if (const auto path = InitialLoadThemeUsingKey(key)) { if (_themeKeyLegacy) { - Window::Theme::SetNightModeValue(path + Window::Theme::SetNightModeValue(*path == Window::Theme::NightThemePath()); (Window::Theme::IsNightMode() ? _themeKeyNight diff --git a/Telegram/SourceFiles/window/themes/window_theme.cpp b/Telegram/SourceFiles/window/themes/window_theme.cpp index 83673e7af..831d648ac 100644 --- a/Telegram/SourceFiles/window/themes/window_theme.cpp +++ b/Telegram/SourceFiles/window/themes/window_theme.cpp @@ -233,37 +233,6 @@ void applyBackground(QImage &&background, bool tiled, Instance *out) { } } -bool loadThemeFromCache(const QByteArray &content, const Cached &cache) { - 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; - } - Background()->saveAdjustableColors(); - if (!background.isNull()) { - applyBackground(std::move(background), cache.tiled, nullptr); - } - - return true; -} - enum class LoadResult { Loaded, Failed, @@ -300,17 +269,24 @@ bool loadBackground(zlib::FileToRead &file, QByteArray *outBackground, bool *out bool loadTheme( const QByteArray &content, + const std::optional &editedPalette, Cached &cache, const Colorizer &colorizer, Instance *out = nullptr) { cache = Cached(); zlib::FileToRead file(content); + const auto emptyColorizer = Colorizer(); + const auto &applyColorizer = editedPalette ? emptyColorizer : colorizer; + 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) { + auto schemeContent = editedPalette.value_or(QByteArray()); + if (schemeContent.isEmpty()) { + schemeContent = file.readFileContent("colors.tdesktop-theme", zlib::kCaseInsensitive, kThemeSchemeSizeLimit); + } + if (schemeContent.isEmpty()) { file.clearError(); schemeContent = file.readFileContent("colors.tdesktop-palette", zlib::kCaseInsensitive, kThemeSchemeSizeLimit); } @@ -318,7 +294,7 @@ bool loadTheme( LOG(("Theme Error: could not read 'colors.tdesktop-theme' or 'colors.tdesktop-palette' in the theme file.")); return false; } - if (!loadColorScheme(schemeContent, colorizer, out)) { + if (!loadColorScheme(schemeContent, applyColorizer, out)) { return false; } Background()->saveAdjustableColors(); @@ -357,7 +333,7 @@ bool loadTheme( } } else { // Looks like it is not a .zip theme. - if (!loadColorScheme(content, colorizer, out)) { + if (!loadColorScheme(editedPalette.value_or(content), applyColorizer, out)) { return false; } Background()->saveAdjustableColors(); @@ -373,6 +349,74 @@ bool loadTheme( return true; } +bool InitializeFromCache( + const QByteArray &content, + const Cached &cache) { + 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; + } + Background()->saveAdjustableColors(); + if (!background.isNull()) { + applyBackground(std::move(background), cache.tiled, nullptr); + } + + return true; +} + +[[nodiscard]] std::optional ReadEditingPalette() { + auto file = QFile(EditingPalettePath()); + return file.open(QIODevice::ReadOnly) + ? std::make_optional(file.readAll()) + : std::nullopt; +} + +bool InitializeFromSaved(Saved &&saved) { + if (saved.object.content.size() < 4) { + LOG(("Theme Error: Could not load theme from '%1' (%2)" + ).arg(saved.object.pathRelative + ).arg(saved.object.pathAbsolute)); + return false; + } + + const auto editing = ReadEditingPalette(); + GlobalBackground.createIfNull(); + if (!editing && InitializeFromCache(saved.object.content, saved.cache)) { + return true; + } + + const auto colorizer = editing + ? Colorizer() + : ColorizerForTheme(saved.object.pathAbsolute); + if (!loadTheme(saved.object.content, editing, saved.cache, colorizer)) { + return false; + } + if (editing) { + Background()->setIsEditingTheme(true); + } else { + Local::writeTheme(saved); + } + return true; +} + QImage validateBackgroundImage(QImage image) { if (image.format() != QImage::Format_ARGB32_Premultiplied) { image = std::move(image).convertToFormat( @@ -701,10 +745,18 @@ bool ChatBackground::adjustPaletteRequired() { } bool ChatBackground::isEditingTheme() const { - const auto &object = AreTestingTheme() - ? GlobalApplying.data.object - : _themeObject; - return IsEditingTheme(object.pathAbsolute); + return _editingTheme; +} + +void ChatBackground::setIsEditingTheme(bool editing) { + if (_editingTheme == editing) { + return; + } + _editingTheme = editing; + if (!_editingTheme) { + reapplyWithNightMode(std::nullopt, _nightMode); + KeepApplied(); + } } void ChatBackground::adjustPaletteUsingBackground(const QImage &image) { @@ -971,12 +1023,14 @@ bool ChatBackground::nightMode() const { return _nightMode; } -void ChatBackground::toggleNightMode(std::optional themePath) { - const auto settingDefault = themePath.has_value(); +void ChatBackground::reapplyWithNightMode( + std::optional themePath, + bool newNightMode) { + const auto settingExactTheme = themePath.has_value(); + const auto nightModeChanged = (newNightMode != _nightMode); const auto oldNightMode = _nightMode; - const auto newNightMode = !_nightMode; _nightMode = newNightMode; - auto read = settingDefault ? Saved() : Local::readThemeAfterSwitch(); + auto read = settingExactTheme ? Saved() : Local::readThemeAfterSwitch(); auto path = read.object.pathAbsolute; _nightMode = oldNightMode; @@ -990,6 +1044,7 @@ void ChatBackground::toggleNightMode(std::optional themePath) { preview->instance.cached = std::move(read.cache); const auto loaded = loadTheme( preview->object.content, + std::nullopt, preview->instance.cached, ColorizerForTheme(path), &preview->instance); @@ -1009,10 +1064,12 @@ void ChatBackground::toggleNightMode(std::optional themePath) { // Theme editor could have already reverted the testing of this toggle. if (AreTestingTheme()) { GlobalApplying.overrideKeep = [=] { - _nightMode = newNightMode; + if (nightModeChanged) { + _nightMode = newNightMode; - // Restore the value, it was set inside theme testing. - (oldNightMode ? _tileNightValue : _tileDayValue) = oldTileValue; + // Restore the value, it was set inside theme testing. + (oldNightMode ? _tileNightValue : _tileDayValue) = oldTileValue; + } const auto saved = std::move(GlobalApplying.data); if (!alreadyOnDisk) { @@ -1020,47 +1077,38 @@ void ChatBackground::toggleNightMode(std::optional themePath) { Local::writeTheme(saved); } ClearApplying(); - keepApplied(saved.object, settingDefault); + keepApplied(saved.object, settingExactTheme); if (tile() != _tileForRevert) { Local::writeUserSettings(); } - Local::writeSettings(); - if (!settingDefault && !Local::readBackground()) { + if (nightModeChanged) { + Local::writeSettings(); + } + if (!settingExactTheme && !Local::readBackground()) { set(Data::ThemeWallPaper()); } }; } } +void ChatBackground::toggleNightMode(std::optional themePath) { + reapplyWithNightMode(themePath, !_nightMode); +} + ChatBackground *Background() { GlobalBackground.createIfNull(); return GlobalBackground.data(); } -bool Load(Saved &&saved) { - if (saved.object.content.size() < 4) { - LOG(("Theme Error: Could not load theme from '%1' (%2)" - ).arg(saved.object.pathRelative - ).arg(saved.object.pathAbsolute)); - return false; - } - - GlobalBackground.createIfNull(); - if (loadThemeFromCache(saved.object.content, saved.cache)) { +bool Initialize(Saved &&saved) { + if (InitializeFromSaved(std::move(saved))) { Background()->setThemeObject(saved.object); return true; } - - const auto colorizer = ColorizerForTheme(saved.object.pathAbsolute); - if (!loadTheme(saved.object.content, saved.cache, colorizer)) { - return false; - } - Local::writeTheme(saved); - Background()->setThemeObject(saved.object); - return true; + return false; } -void Unload() { +void Uninitialize() { GlobalBackground.clear(); GlobalApplying = Applying(); } @@ -1098,30 +1146,13 @@ void ApplyDefaultWithPath(const QString &themePath) { } } -bool ApplyEditedPalette(const QString &path, const QByteArray &content) { - Instance out; +bool ApplyEditedPalette(const QByteArray &content) { + auto out = Instance(); if (!loadColorScheme(content, Colorizer(), &out)) { return false; } - out.cached.colors = out.palette.save(); - out.cached.paletteChecksum = style::palette::Checksum(); - out.cached.contentChecksum = hashCrc32( - content.constData(), - content.size()); - - GlobalApplying.data.object.pathRelative = path.isEmpty() - ? QString() - : QDir().relativeFilePath(path); - GlobalApplying.data.object.pathAbsolute = path.isEmpty() - ? QString() - : QFileInfo(path).absoluteFilePath(); - GlobalApplying.data.object.content = content; - GlobalApplying.data.cache = out.cached; - if (GlobalApplying.paletteForRevert.isEmpty()) { - GlobalApplying.paletteForRevert = style::main_palette::save(); - } - Background()->setTestingTheme(std::move(out)); - KeepApplied(); + style::main_palette::apply(out.palette); + Background()->notify(BackgroundUpdate(BackgroundUpdate::Type::ApplyingEdit, Background()->tile()), true); return true; } @@ -1188,7 +1219,7 @@ bool LoadFromContent( return false; } - return loadTheme(content, out->cached, colorizer, out); + return loadTheme(content, std::nullopt, out->cached, colorizer, out); } bool LoadFromFile( @@ -1207,6 +1238,10 @@ QString EditingPalettePath() { return cWorkingDir() + "tdata/editing-theme.tdesktop-palette"; } +void ClearEditingPalette() { + QFile(EditingPalettePath()).remove(); +} + QColor CountAverageColor(const QImage &image) { Expects(image.format() == QImage::Format_ARGB32_Premultiplied); diff --git a/Telegram/SourceFiles/window/themes/window_theme.h b/Telegram/SourceFiles/window/themes/window_theme.h index 0042a128b..3d55e7e65 100644 --- a/Telegram/SourceFiles/window/themes/window_theme.h +++ b/Telegram/SourceFiles/window/themes/window_theme.h @@ -36,8 +36,8 @@ struct Saved { Object object; Cached cache; }; -bool Load(Saved &&saved); -void Unload(); +bool Initialize(Saved &&saved); +void Uninitialize(); struct Instance { style::palette palette; @@ -57,7 +57,7 @@ bool Apply( const Data::CloudTheme &cloud = Data::CloudTheme()); bool Apply(std::unique_ptr preview); void ApplyDefaultWithPath(const QString &themePath); -bool ApplyEditedPalette(const QString &path, const QByteArray &content); +bool ApplyEditedPalette(const QByteArray &content); void KeepApplied(); QString NightThemePath(); [[nodiscard]] bool IsNightMode(); @@ -68,6 +68,7 @@ void ToggleNightMode(const QString &themePath); void Revert(); [[nodiscard]] QString EditingPalettePath(); +void ClearEditingPalette(); bool LoadFromFile( const QString &file, @@ -86,12 +87,15 @@ struct BackgroundUpdate { TestingTheme, RevertingTheme, ApplyingTheme, + ApplyingEdit, }; BackgroundUpdate(Type type, bool tiled) : type(type), tiled(tiled) { } [[nodiscard]] bool paletteChanged() const { - return (type == Type::TestingTheme || type == Type::RevertingTheme); + return (type == Type::TestingTheme) + || (type == Type::RevertingTheme) + || (type == Type::ApplyingEdit); } Type type; bool tiled; @@ -115,6 +119,7 @@ public: void setThemeObject(const Object &object); [[nodiscard]] const Object &themeObject() const; [[nodiscard]] bool isEditingTheme() const; + void setIsEditingTheme(bool editing); void reset(); void setTestingTheme(Instance &&theme); @@ -164,6 +169,9 @@ private: void setNightModeValue(bool nightMode); [[nodiscard]] bool nightMode() const; void toggleNightMode(std::optional themePath); + void reapplyWithNightMode( + std::optional themePath, + bool newNightMode); void keepApplied(const Object &object, bool write); [[nodiscard]] bool isNonDefaultThemeOrBackground(); [[nodiscard]] bool isNonDefaultBackground(); @@ -191,6 +199,7 @@ private: Object _themeObject; QImage _themeImage; bool _themeTile = false; + bool _editingTheme = false; Data::WallPaper _paperForRevert = Data::details::UninitializedWallPaper(); diff --git a/Telegram/SourceFiles/window/themes/window_theme_editor.cpp b/Telegram/SourceFiles/window/themes/window_theme_editor.cpp index 73e6c8dcb..bd8aa0483 100644 --- a/Telegram/SourceFiles/window/themes/window_theme_editor.cpp +++ b/Telegram/SourceFiles/window/themes/window_theme_editor.cpp @@ -587,7 +587,7 @@ void Editor::Inner::applyEditing(const QString &name, const QString ©Of, QCo f.close(); _applyingUpdate = true; - if (!ApplyEditedPalette(_path, newContent)) { + if (!ApplyEditedPalette(newContent)) { LOG(("Theme Error: could not apply newly composed content :(")); error(); return; @@ -652,7 +652,20 @@ Editor::Editor(QWidget*, not_null window) _inner->setScrollCallback([this](int top, int bottom) { _scroll->scrollToY(top, bottom); }); - _close->setClickedCallback([this] { closeEditor(); }); + _close->setClickedCallback([=] { + const auto box = std::make_shared>(); + const auto close = crl::guard(this, [=] { + ClearEditingPalette(); + closeEditor(); + if (*box) { + (*box)->closeBox(); + } + }); + *box = _window->show(Box( + tr::lng_theme_editor_sure_close(tr::now), + tr::lng_close(tr::now), + close)); + }); _close->show(anim::type::instant); _select->resizeToWidth(st::windowMinWidth); @@ -759,6 +772,7 @@ void Editor::paintEvent(QPaintEvent *e) { void Editor::closeEditor() { if (const auto window = App::wnd()) { window->showRightColumn(nullptr); + Background()->setIsEditingTheme(false); } } diff --git a/Telegram/SourceFiles/window/themes/window_theme_editor_box.cpp b/Telegram/SourceFiles/window/themes/window_theme_editor_box.cpp index f6a421fab..a113adab7 100644 --- a/Telegram/SourceFiles/window/themes/window_theme_editor_box.cpp +++ b/Telegram/SourceFiles/window/themes/window_theme_editor_box.cpp @@ -233,11 +233,11 @@ QString BytesToUTF8(QLatin1String string) { return QString::fromUtf8(string.data(), string.size()); } -void WriteDefaultPalette(const QString &path) { +bool WriteDefaultPalette(const QString &path) { QFile f(path); if (!f.open(QIODevice::WriteOnly)) { LOG(("Theme Error: could not open '%1' for writing.").arg(path)); - return; + return false; } QTextStream stream(&f); @@ -260,6 +260,7 @@ void WriteDefaultPalette(const QString &path) { ' ') << "\n"; } + return true; } [[nodiscard]] QString GenerateSlug() { @@ -489,14 +490,12 @@ void StartEditor( not_null window, const Data::CloudTheme &cloud) { const auto path = EditingPalettePath(); - if (!Local::copyThemeColorsToPalette(path)) { - WriteDefaultPalette(path); - } - if (!Apply(path, cloud)) { + if (!Local::copyThemeColorsToPalette(path) + && !WriteDefaultPalette(path)) { window->show(Box(tr::lng_theme_editor_error(tr::now))); return; } - KeepApplied(); + Background()->setIsEditingTheme(true); window->showRightColumn(Box(window)); } diff --git a/Telegram/SourceFiles/window/themes/window_theme_preview.cpp b/Telegram/SourceFiles/window/themes/window_theme_preview.cpp index 9a72d1ef0..33ad9ff91 100644 --- a/Telegram/SourceFiles/window/themes/window_theme_preview.cpp +++ b/Telegram/SourceFiles/window/themes/window_theme_preview.cpp @@ -926,6 +926,7 @@ std::unique_ptr PreviewFromFile( return nullptr; } } else { + object.content = bytes; if (!LoadFromContent(bytes, &result->instance)) { return nullptr; }