| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010 |
- /*
- 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_editor_box.h"
- #include "window/themes/window_theme.h"
- #include "window/themes/window_theme_editor.h"
- #include "window/themes/window_theme_preview.h"
- #include "window/themes/window_themes_generate_name.h"
- #include "window/window_controller.h"
- #include "ui/boxes/confirm_box.h"
- #include "ui/text/text_utilities.h"
- #include "ui/widgets/fields/input_field.h"
- #include "ui/widgets/checkbox.h"
- #include "ui/widgets/buttons.h"
- #include "ui/widgets/labels.h"
- #include "ui/image/image_prepare.h"
- #include "ui/toast/toast.h"
- #include "ui/text/format_values.h"
- #include "ui/style/style_palette_colorizer.h"
- #include "ui/widgets/fields/special_fields.h"
- #include "ui/painter.h"
- #include "ui/ui_utility.h"
- #include "main/main_account.h"
- #include "main/main_session.h"
- #include "storage/localstorage.h"
- #include "core/file_utilities.h"
- #include "core/application.h"
- #include "lang/lang_keys.h"
- #include "base/event_filter.h"
- #include "base/base_file_utilities.h"
- #include "base/zlib_help.h"
- #include "base/unixtime.h"
- #include "base/random.h"
- #include "data/data_session.h"
- #include "data/data_document.h"
- #include "data/data_cloud_themes.h"
- #include "storage/file_upload.h"
- #include "mainwindow.h"
- #include "apiwrap.h"
- #include "styles/style_widgets.h"
- #include "styles/style_window.h"
- #include "styles/style_layers.h"
- #include "styles/style_boxes.h"
- #include <QtCore/QBuffer>
- namespace Window {
- namespace Theme {
- namespace {
- constexpr auto kRandomSlugSize = 16;
- constexpr auto kMinSlugSize = 5;
- constexpr auto kMaxSlugSize = 64;
- enum class SaveErrorType {
- Other,
- Name,
- Link,
- };
- class BackgroundSelector : public Ui::RpWidget {
- public:
- BackgroundSelector(
- QWidget *parent,
- const QImage &background,
- const ParsedTheme &parsed);
- [[nodiscard]] ParsedTheme result() const;
- [[nodiscard]] QImage image() const;
- int resizeGetHeight(int newWidth) override;
- protected:
- void paintEvent(QPaintEvent *e) override;
- private:
- void updateThumbnail();
- void chooseBackgroundFromFile();
- object_ptr<Ui::LinkButton> _chooseFromFile;
- object_ptr<Ui::Checkbox> _tileBackground;
- QImage _background;
- ParsedTheme _parsed;
- QString _imageText;
- int _thumbnailSize = 0;
- QPixmap _thumbnail;
- };
- template <size_t Size>
- QByteArray qba(const char(&string)[Size]) {
- return QByteArray::fromRawData(string, Size - 1);
- }
- QByteArray qba(QLatin1String string) {
- return QByteArray::fromRawData(string.data(), string.size());
- }
- BackgroundSelector::BackgroundSelector(
- QWidget *parent,
- const QImage &background,
- const ParsedTheme &parsed)
- : RpWidget(parent)
- , _chooseFromFile(
- this,
- tr::lng_settings_bg_from_file(tr::now),
- st::boxLinkButton)
- , _tileBackground(
- this,
- tr::lng_settings_bg_tile(tr::now),
- parsed.tiled,
- st::defaultBoxCheckbox)
- , _background(background)
- , _parsed(parsed) {
- _imageText = tr::lng_theme_editor_saved_to_jpg(
- tr::now,
- lt_size,
- Ui::FormatSizeText(_parsed.background.size()));
- _chooseFromFile->setClickedCallback([=] { chooseBackgroundFromFile(); });
- _thumbnailSize = st::boxTextFont->height
- + st::themesSmallSkip
- + _chooseFromFile->heightNoMargins()
- + st::themesSmallSkip
- + _tileBackground->heightNoMargins();
- resize(width(), _thumbnailSize + st::themesSmallSkip);
- updateThumbnail();
- }
- void BackgroundSelector::paintEvent(QPaintEvent *e) {
- Painter p(this);
- const auto left = _thumbnailSize + st::themesSmallSkip;
- p.setPen(st::boxTextFg);
- p.setFont(st::boxTextFont);
- p.drawTextLeft(left, 0, width(), _imageText);
- p.drawPixmapLeft(0, 0, width(), _thumbnail);
- }
- int BackgroundSelector::resizeGetHeight(int newWidth) {
- const auto left = _thumbnailSize + st::themesSmallSkip;
- _chooseFromFile->moveToLeft(left, st::boxTextFont->height + st::themesSmallSkip);
- _tileBackground->moveToLeft(left, st::boxTextFont->height + st::themesSmallSkip + _chooseFromFile->height() + st::themesSmallSkip);
- return height();
- }
- void BackgroundSelector::updateThumbnail() {
- const auto size = _thumbnailSize;
- auto back = QImage(
- QSize(size, size) * style::DevicePixelRatio(),
- QImage::Format_ARGB32_Premultiplied);
- back.setDevicePixelRatio(style::DevicePixelRatio());
- {
- Painter p(&back);
- PainterHighQualityEnabler hq(p);
- auto &pix = _background;
- int sx = (pix.width() > pix.height()) ? ((pix.width() - pix.height()) / 2) : 0;
- int sy = (pix.height() > pix.width()) ? ((pix.height() - pix.width()) / 2) : 0;
- int s = (pix.width() > pix.height()) ? pix.height() : pix.width();
- p.drawImage(QRect(0, 0, size, size), pix, QRect(sx, sy, s, s));
- }
- _thumbnail = Ui::PixmapFromImage(
- Images::Round(std::move(back), ImageRoundRadius::Small));
- _thumbnail.setDevicePixelRatio(style::DevicePixelRatio());
- update();
- }
- void BackgroundSelector::chooseBackgroundFromFile() {
- const auto callback = [=](const FileDialog::OpenResult &result) {
- auto content = result.remoteContent;
- if (!result.paths.isEmpty()) {
- QFile f(result.paths.front());
- if (f.open(QIODevice::ReadOnly)) {
- content = f.readAll();
- f.close();
- }
- }
- if (!content.isEmpty()) {
- auto read = Images::Read({ .content = content });
- if (!read.image.isNull()
- && (read.format == "jpeg"
- || read.format == "jpg"
- || read.format == "png")) {
- _background = std::move(read.image);
- _parsed.background = content;
- _parsed.isPng = (read.format == "png");
- const auto phrase = _parsed.isPng
- ? tr::lng_theme_editor_read_from_png
- : tr::lng_theme_editor_read_from_jpg;
- _imageText = phrase(
- tr::now,
- lt_size,
- Ui::FormatSizeText(_parsed.background.size()));
- _tileBackground->setChecked(false);
- updateThumbnail();
- }
- }
- };
- FileDialog::GetOpenPath(
- this,
- tr::lng_theme_editor_choose_image(tr::now),
- "Image files (*.jpeg *.jpg *.png)",
- crl::guard(this, callback));
- }
- ParsedTheme BackgroundSelector::result() const {
- auto result = _parsed;
- result.tiled = _tileBackground->checked();
- return result;
- }
- QImage BackgroundSelector::image() const {
- return _background;
- }
- bool PaletteChanged(
- const QByteArray &editorPalette,
- const QByteArray &originalPalette,
- const Data::CloudTheme &cloud) {
- return originalPalette.isEmpty()
- || (editorPalette != WriteCloudToText(cloud) + originalPalette);
- }
- void ImportFromFile(
- not_null<Main::Session*> session,
- not_null<QWidget*> parent) {
- auto filters = QStringList(
- u"Theme files (*.tdesktop-theme *.tdesktop-palette)"_q);
- filters.push_back(FileDialog::AllFilesFilter());
- const auto callback = crl::guard(session, [=](
- const FileDialog::OpenResult &result) {
- const auto path = result.paths.isEmpty()
- ? QString()
- : result.paths.front();
- if (!path.isEmpty()) {
- Window::Theme::Apply(path);
- }
- });
- FileDialog::GetOpenPath(
- parent.get(),
- tr::lng_theme_editor_menu_import(tr::now),
- filters.join(u";;"_q),
- crl::guard(parent, callback));
- }
- // They're duplicated in window_theme.cpp:ChatBackground::ChatBackground.
- [[nodiscard]] QByteArray ReplaceAdjustableColors(QByteArray data) {
- const auto &themeObject = Background()->themeObject();
- const auto &paper = Background()->paper();
- const auto usingDefaultTheme = themeObject.pathAbsolute.isEmpty();
- const auto usingThemeBackground = usingDefaultTheme
- ? Data::IsDefaultWallPaper(paper)
- : Data::IsThemeWallPaper(paper);
- if (usingThemeBackground) {
- return data;
- }
- const auto adjustables = base::flat_map<QByteArray, style::color>{
- { qba(qstr("msgServiceBg")), st::msgServiceBg },
- { qba(qstr("msgServiceBgSelected")), st::msgServiceBgSelected },
- { qba(qstr("historyScrollBg")), st::historyScrollBg },
- { qba(qstr("historyScrollBgOver")), st::historyScrollBgOver },
- { qba(qstr("historyScrollBarBg")), st::historyScrollBarBg },
- { qba(qstr("historyScrollBarBgOver")), st::historyScrollBarBgOver }
- };
- for (const auto &[name, color] : adjustables) {
- data = ReplaceValueInPaletteContent(
- data,
- name,
- ColorHexString(color->c));
- if (data == "error") {
- LOG(("Theme Error: could not adjust '%1: %2' in content").arg(
- QString::fromLatin1(name),
- QString::fromLatin1(ColorHexString(color->c))));
- return QByteArray();
- }
- }
- return data;
- }
- QByteArray GenerateDefaultPalette() {
- auto result = QByteArray();
- const auto rows = style::main_palette::data();
- for (const auto &row : std::as_const(rows)) {
- result.append(qba(row.name)
- ).append(": "
- ).append(qba(row.value)
- ).append("; // "
- ).append(
- qba(
- row.description
- ).replace(
- '\n',
- ' '
- ).replace(
- '\r',
- ' ')
- ).append('\n');
- }
- return result;
- }
- bool CopyColorsToPalette(
- const QString &path,
- const QByteArray &palette,
- const Data::CloudTheme &cloud) {
- QFile f(path);
- if (!f.open(QIODevice::WriteOnly)) {
- LOG(("Theme Error: could not open '%1' for writing.").arg(path));
- return false;
- }
- const auto prefix = WriteCloudToText(cloud);
- if (f.write(prefix) != prefix.size()
- || f.write(palette) != palette.size()) {
- LOG(("Theme Error: could not write palette to '%1'").arg(path));
- return false;
- }
- return true;
- }
- [[nodiscard]] QByteArray PackTheme(const ParsedTheme &parsed) {
- zlib::FileToWrite zip;
- zip_fileinfo zfi = { { 0, 0, 0, 0, 0, 0 }, 0, 0, 0 };
- const auto back = std::string(parsed.tiled ? "tiled" : "background")
- + (parsed.isPng ? ".png" : ".jpg");
- zip.openNewFile(
- back.c_str(),
- &zfi,
- nullptr,
- 0,
- nullptr,
- 0,
- nullptr,
- Z_DEFLATED,
- Z_DEFAULT_COMPRESSION);
- zip.writeInFile(
- parsed.background.constData(),
- parsed.background.size());
- zip.closeFile();
- const auto scheme = "colors.tdesktop-theme";
- zip.openNewFile(
- scheme,
- &zfi,
- nullptr,
- 0,
- nullptr,
- 0,
- nullptr,
- Z_DEFLATED,
- Z_DEFAULT_COMPRESSION);
- zip.writeInFile(parsed.palette.constData(), parsed.palette.size());
- zip.closeFile();
- zip.close();
- if (zip.error() != ZIP_OK) {
- LOG(("Theme Error: could not export zip-ed theme, status: %1"
- ).arg(zip.error()));
- return QByteArray();
- }
- return zip.result();
- }
- [[nodiscard]] bool IsGoodSlug(const QString &slug) {
- if (slug.size() < kMinSlugSize || slug.size() > kMaxSlugSize) {
- return false;
- }
- return ranges::none_of(slug, [](QChar ch) {
- return (ch < 'A' || ch > 'Z')
- && (ch < 'a' || ch > 'z')
- && (ch < '0' || ch > '9')
- && (ch != '_');
- });
- }
- std::shared_ptr<FilePrepareResult> PrepareThemeMedia(
- MTP::DcId dcId,
- const QString &name,
- const QByteArray &content) {
- PreparedPhotoThumbs thumbnails;
- QVector<MTPPhotoSize> sizes;
- auto thumbnail = GeneratePreview(content, QString()).scaled(
- 320,
- 320,
- Qt::KeepAspectRatio,
- Qt::SmoothTransformation);
- auto thumbnailBytes = QByteArray();
- {
- QBuffer buffer(&thumbnailBytes);
- thumbnail.save(&buffer, "JPG", 87);
- }
- sizes.push_back(MTP_photoSize(
- MTP_string("s"),
- MTP_int(thumbnail.width()),
- MTP_int(thumbnail.height()), MTP_int(0)));
- const auto id = base::RandomValue<DocumentId>();
- const auto filename = base::FileNameFromUserString(name)
- + u".tdesktop-theme"_q;
- auto attributes = QVector<MTPDocumentAttribute>(
- 1,
- MTP_documentAttributeFilename(MTP_string(filename)));
- auto result = MakePreparedFile({
- .id = id,
- .type = SendMediaType::ThemeFile,
- });
- result->filename = filename;
- result->content = content;
- result->filesize = content.size();
- result->thumb = thumbnail;
- result->thumbname = "thumb.jpg";
- result->setThumbData(thumbnailBytes);
- result->document = MTP_document(
- MTP_flags(0),
- MTP_long(id),
- MTP_long(0),
- MTP_bytes(),
- MTP_int(base::unixtime::now()),
- MTP_string("application/x-tgtheme-tdesktop"),
- MTP_long(content.size()),
- MTP_vector<MTPPhotoSize>(sizes),
- MTPVector<MTPVideoSize>(),
- MTP_int(dcId),
- MTP_vector<MTPDocumentAttribute>(attributes));
- return result;
- }
- Fn<void()> SavePreparedTheme(
- not_null<Window::Controller*> window,
- const ParsedTheme &parsed,
- const QImage &background,
- const QByteArray &originalContent,
- const ParsedTheme &originalParsed,
- const Data::CloudTheme &fields,
- Fn<void()> done,
- Fn<void(SaveErrorType,QString)> fail) {
- Expects(window->account().sessionExists());
- using Storage::UploadedMedia;
- struct State {
- FullMsgId id;
- bool generating = false;
- mtpRequestId requestId = 0;
- QByteArray themeContent;
- QString filename;
- rpl::lifetime lifetime;
- };
- const auto session = &window->account().session();
- const auto api = &session->api();
- const auto state = std::make_shared<State>();
- state->id = FullMsgId(
- session->userPeerId(),
- session->data().nextLocalMessageId());
- const auto creating = !fields.id
- || (fields.createdBy != session->userId());
- const auto changed = (parsed.background != originalParsed.background)
- || (parsed.tiled != originalParsed.tiled)
- || PaletteChanged(parsed.palette, originalParsed.palette, fields);
- const auto finish = [=](const MTPTheme &result) {
- Background()->clearEditingTheme(ClearEditing::KeepChanges);
- done();
- const auto cloud = result.match([&](const MTPDtheme &data) {
- const auto result = Data::CloudTheme::Parse(session, data);
- session->data().cloudThemes().savedFromEditor(result);
- return result;
- });
- if (cloud.documentId && !state->themeContent.isEmpty()) {
- const auto document = session->data().document(cloud.documentId);
- document->setDataAndCache(state->themeContent);
- }
- KeepFromEditor(
- originalContent,
- originalParsed,
- cloud,
- state->themeContent,
- parsed,
- background);
- };
- const auto createTheme = [=](const MTPDocument &data) {
- const auto document = session->data().processDocument(data);
- state->requestId = api->request(MTPaccount_CreateTheme(
- MTP_flags(MTPaccount_CreateTheme::Flag::f_document),
- MTP_string(fields.slug),
- MTP_string(fields.title),
- document->mtpInput(),
- MTPVector<MTPInputThemeSettings>()
- )).done([=](const MTPTheme &result) {
- finish(result);
- }).fail([=](const MTP::Error &error) {
- fail(SaveErrorType::Other, error.type());
- }).send();
- };
- const auto updateTheme = [=](const MTPDocument &data) {
- using Flag = MTPaccount_UpdateTheme::Flag;
- const auto document = session->data().processDocument(data);
- const auto flags = Flag::f_title
- | Flag::f_slug
- | (data.type() == mtpc_documentEmpty
- ? Flag(0)
- : Flag::f_document);
- state->requestId = api->request(MTPaccount_UpdateTheme(
- MTP_flags(flags),
- MTP_string(Data::CloudThemes::Format()),
- MTP_inputTheme(MTP_long(fields.id), MTP_long(fields.accessHash)),
- MTP_string(fields.slug),
- MTP_string(fields.title),
- document->mtpInput(),
- MTPVector<MTPInputThemeSettings>()
- )).done([=](const MTPTheme &result) {
- finish(result);
- }).fail([=](const MTP::Error &error) {
- fail(SaveErrorType::Other, error.type());
- }).send();
- };
- const auto uploadTheme = [=](const UploadedMedia &data) {
- state->requestId = api->request(MTPaccount_UploadTheme(
- MTP_flags(MTPaccount_UploadTheme::Flag::f_thumb),
- data.info.file,
- *data.info.thumb,
- MTP_string(state->filename),
- MTP_string("application/x-tgtheme-tdesktop")
- )).done([=](const MTPDocument &result) {
- if (creating) {
- createTheme(result);
- } else {
- updateTheme(result);
- }
- }).fail([=](const MTP::Error &error) {
- fail(SaveErrorType::Other, error.type());
- }).send();
- };
- const auto uploadFile = [=](const QByteArray &theme) {
- const auto media = PrepareThemeMedia(
- session->mainDcId(),
- fields.title,
- theme);
- state->filename = media->filename;
- state->themeContent = theme;
- session->uploader().documentReady(
- ) | rpl::filter([=](const UploadedMedia &data) {
- return (data.fullId == state->id) && data.info.thumb.has_value();
- }) | rpl::start_with_next([=](const UploadedMedia &data) {
- uploadTheme(data);
- }, state->lifetime);
- session->uploader().upload(state->id, media);
- };
- const auto save = [=] {
- if (!creating && !changed) {
- updateTheme(MTP_documentEmpty(MTP_long(fields.documentId)));
- return;
- }
- state->generating = true;
- crl::async([=] {
- crl::on_main([=, ready = PackTheme(parsed)]{
- if (!state->generating) {
- return;
- }
- state->generating = false;
- uploadFile(ready);
- });
- });
- };
- const auto checkFields = [=] {
- state->requestId = api->request(MTPaccount_CreateTheme(
- MTP_flags(MTPaccount_CreateTheme::Flag::f_document),
- MTP_string(fields.slug),
- MTP_string(fields.title),
- MTP_inputDocumentEmpty(),
- MTPVector<MTPInputThemeSettings>()
- )).done([=](const MTPTheme &result) {
- save();
- }).fail([=](const MTP::Error &error) {
- if (error.type() == u"THEME_FILE_INVALID"_q) {
- save();
- } else {
- fail(SaveErrorType::Other, error.type());
- }
- }).send();
- };
- if (creating) {
- checkFields();
- } else {
- save();
- }
- return [=] {
- state->generating = false;
- api->request(base::take(state->requestId)).cancel();
- session->uploader().cancel(state->id);
- state->lifetime.destroy();
- };
- }
- } // namespace
- bool PaletteChanged(
- const QByteArray &editorPalette,
- const Data::CloudTheme &cloud) {
- auto object = Local::ReadThemeContent();
- const auto real = object.content.isEmpty()
- ? GenerateDefaultPalette()
- : ParseTheme(object, true).palette;
- return PaletteChanged(editorPalette, real, cloud);
- }
- void StartEditor(
- not_null<Window::Controller*> window,
- const Data::CloudTheme &cloud) {
- const auto path = EditingPalettePath();
- auto object = Local::ReadThemeContent();
- const auto palette = object.content.isEmpty()
- ? GenerateDefaultPalette()
- : ParseTheme(object, true).palette;
- if (palette.isEmpty() || !CopyColorsToPalette(path, palette, cloud)) {
- window->show(Ui::MakeInformBox(tr::lng_theme_editor_error()));
- return;
- }
- if (Core::App().settings().systemDarkModeEnabled()) {
- Core::App().settings().setSystemDarkModeEnabled(false);
- Core::App().saveSettingsDelayed();
- }
- Background()->setEditingTheme(cloud);
- window->showRightColumn(Box<Editor>(window, cloud));
- }
- void CreateBox(
- not_null<Ui::GenericBox*> box,
- not_null<Window::Controller*> window) {
- CreateForExistingBox(box, window, Data::CloudTheme());
- }
- void CreateForExistingBox(
- not_null<Ui::GenericBox*> box,
- not_null<Window::Controller*> window,
- const Data::CloudTheme &cloud) {
- const auto amCreator = window->account().sessionExists()
- && (window->account().session().userId() == cloud.createdBy);
- box->setTitle(amCreator
- ? (rpl::single(cloud.title) | Ui::Text::ToWithEntities())
- : tr::lng_theme_editor_create_title(Ui::Text::WithEntities));
- box->addRow(object_ptr<Ui::FlatLabel>(
- box,
- (amCreator
- ? tr::lng_theme_editor_attach_description
- : tr::lng_theme_editor_create_description)(),
- st::boxDividerLabel));
- box->addRow(
- object_ptr<Ui::SettingsButton>(
- box,
- tr::lng_theme_editor_import_existing(),
- st::createThemeImportButton),
- style::margins(
- 0,
- st::boxRowPadding.left(),
- 0,
- 0)
- )->addClickHandler([=] {
- ImportFromFile(&window->account().session(), box);
- });
- const auto done = [=] {
- box->closeBox();
- StartEditor(window, cloud);
- };
- base::install_event_filter(box, box, [=](not_null<QEvent*> event) {
- if (event->type() == QEvent::KeyPress) {
- const auto key = static_cast<QKeyEvent*>(event.get())->key();
- if (key == Qt::Key_Enter || key == Qt::Key_Return) {
- done();
- return base::EventFilterResult::Cancel;
- }
- }
- return base::EventFilterResult::Continue;
- });
- box->addButton(tr::lng_theme_editor_create(), done);
- box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
- }
- void SaveTheme(
- not_null<Window::Controller*> window,
- const Data::CloudTheme &cloud,
- const QByteArray &palette,
- Fn<void()> unlock) {
- Expects(window->account().sessionExists());
- using Data::CloudTheme;
- const auto save = [=](const CloudTheme &fields) {
- unlock();
- window->show(Box(SaveThemeBox, window, fields, palette));
- };
- if (cloud.id) {
- window->account().session().api().request(MTPaccount_GetTheme(
- MTP_string(Data::CloudThemes::Format()),
- MTP_inputTheme(MTP_long(cloud.id), MTP_long(cloud.accessHash))
- )).done([=](const MTPTheme &result) {
- result.match([&](const MTPDtheme &data) {
- save(CloudTheme::Parse(&window->account().session(), data));
- });
- }).fail([=] {
- save(CloudTheme());
- }).send();
- } else {
- save(CloudTheme());
- }
- }
- struct CollectedData {
- QByteArray originalContent;
- ParsedTheme originalParsed;
- ParsedTheme parsed;
- QImage background;
- QColor accent;
- };
- [[nodiscard]] CollectedData CollectData(const QByteArray &palette) {
- const auto original = Local::ReadThemeContent();
- const auto originalContent = original.content;
- // We don't need default palette here, because in case of it we are
- // not interested if the palette was changed, we'll save it anyway.
- const auto originalParsed = originalContent.isEmpty()
- ? ParsedTheme() // GenerateDefaultPalette()
- : ParseTheme(original);
- const auto background = Background()->createCurrentImage();
- const auto changed = !Data::IsThemeWallPaper(Background()->paper())
- || originalParsed.background.isEmpty()
- || ColorizerForTheme(original.pathAbsolute);
- auto parsed = ParsedTheme();
- parsed.palette = StripCloudTextFields(palette);
- parsed.isPng = false;
- if (changed) {
- QBuffer buffer(&parsed.background);
- background.save(&buffer, "JPG", 87);
- } else {
- // Use existing background serialization.
- parsed.background = originalParsed.background;
- parsed.isPng = originalParsed.isPng;
- }
- const auto accent = st::windowActiveTextFg->c;
- return { originalContent, originalParsed, parsed, background, accent };
- }
- QByteArray CollectForExport(const QByteArray &palette) {
- return PackTheme(CollectData(palette).parsed);
- }
- void SaveThemeBox(
- not_null<Ui::GenericBox*> box,
- not_null<Window::Controller*> window,
- const Data::CloudTheme &cloud,
- const QByteArray &palette) {
- Expects(window->account().sessionExists());
- const auto collected = CollectData(palette);
- const auto title = cloud.title.isEmpty()
- ? GenerateName(collected.accent)
- : cloud.title;
- box->setTitle(tr::lng_theme_editor_save_title(Ui::Text::WithEntities));
- const auto name = box->addRow(object_ptr<Ui::InputField>(
- box,
- st::defaultInputField,
- tr::lng_theme_editor_name(),
- title));
- const auto linkWrap = box->addRow(
- object_ptr<Ui::RpWidget>(box),
- style::margins(
- st::boxRowPadding.left(),
- st::themesSmallSkip,
- st::boxRowPadding.right(),
- st::boxRowPadding.bottom()));
- const auto link = Ui::CreateChild<Ui::UsernameInput>(
- linkWrap,
- st::createThemeLink,
- rpl::single(u"link"_q),
- cloud.slug.isEmpty() ? GenerateSlug() : cloud.slug,
- window->account().session().createInternalLink(QString()));
- linkWrap->widthValue(
- ) | rpl::start_with_next([=](int width) {
- link->resize(width, link->height());
- link->moveToLeft(0, 0, width);
- }, link->lifetime());
- link->heightValue(
- ) | rpl::start_with_next([=](int height) {
- linkWrap->resize(linkWrap->width(), height);
- }, link->lifetime());
- link->setLinkPlaceholder(
- window->account().session().createInternalLink(u"addtheme/"_q));
- link->setPlaceholderHidden(false);
- link->setMaxLength(kMaxSlugSize);
- box->addRow(
- object_ptr<Ui::FlatLabel>(
- box,
- tr::lng_theme_editor_link_about(),
- st::boxDividerLabel),
- style::margins(
- st::boxRowPadding.left(),
- st::themesSmallSkip,
- st::boxRowPadding.right(),
- st::boxRowPadding.bottom()));
- box->addRow(
- object_ptr<Ui::FlatLabel>(
- box,
- tr::lng_theme_editor_background_image(),
- st::defaultSubsectionTitle),
- st::defaultSubsectionTitlePadding);
- const auto back = box->addRow(
- object_ptr<BackgroundSelector>(
- box,
- collected.background,
- collected.parsed),
- style::margins(
- st::boxRowPadding.left(),
- st::themesSmallSkip,
- st::boxRowPadding.right(),
- st::boxRowPadding.bottom()));
- box->setFocusCallback([=] { name->setFocusFast(); });
- box->setWidth(st::boxWideWidth);
- const auto saving = box->lifetime().make_state<bool>();
- const auto cancel = std::make_shared<Fn<void()>>(nullptr);
- box->lifetime().add([=] { if (*cancel) (*cancel)(); });
- const auto save = [=] {
- if (*saving) {
- return;
- }
- const auto done = crl::guard(box, [=] {
- box->closeBox();
- window->showRightColumn(nullptr);
- });
- const auto fail = crl::guard(box, [=](
- SaveErrorType type,
- const QString &error) {
- *saving = false;
- box->showLoading(false);
- if (error == u"THEME_TITLE_INVALID"_q) {
- type = SaveErrorType::Name;
- } else if (error == u"THEME_SLUG_INVALID"_q) {
- type = SaveErrorType::Link;
- } else if (error == u"THEME_SLUG_OCCUPIED"_q) {
- box->showToast(
- tr::lng_create_channel_link_occupied(tr::now));
- type = SaveErrorType::Link;
- } else if (!error.isEmpty()) {
- box->showToast(error);
- }
- if (type == SaveErrorType::Name) {
- name->showError();
- } else if (type == SaveErrorType::Link) {
- link->showError();
- }
- });
- auto fields = cloud;
- fields.title = name->getLastText().trimmed();
- fields.slug = link->getLastText().trimmed();
- if (fields.title.isEmpty()) {
- fail(SaveErrorType::Name, QString());
- return;
- } else if (!IsGoodSlug(fields.slug)) {
- fail(SaveErrorType::Link, QString());
- return;
- }
- *saving = true;
- box->showLoading(true);
- *cancel = SavePreparedTheme(
- window,
- back->result(),
- back->image(),
- collected.originalContent,
- collected.originalParsed,
- fields,
- done,
- fail);
- };
- box->addButton(tr::lng_settings_save(), save);
- box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
- }
- ParsedTheme ParseTheme(
- const Object &theme,
- bool onlyPalette,
- bool parseCurrent) {
- auto raw = ParsedTheme();
- raw.palette = theme.content;
- const auto result = [&] {
- if (const auto colorizer = ColorizerForTheme(theme.pathAbsolute)) {
- raw.palette = Editor::ColorizeInContent(
- std::move(raw.palette),
- colorizer);
- }
- if (parseCurrent) {
- raw.palette = ReplaceAdjustableColors(std::move(raw.palette));
- }
- return raw;
- };
- zlib::FileToRead file(theme.content);
- unz_global_info globalInfo = { 0 };
- file.getGlobalInfo(&globalInfo);
- if (file.error() != UNZ_OK) {
- return result();
- }
- raw.palette = file.readFileContent("colors.tdesktop-theme", zlib::kCaseInsensitive, kThemeSchemeSizeLimit);
- if (file.error() == UNZ_END_OF_LIST_OF_FILE) {
- file.clearError();
- raw.palette = 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 ParsedTheme();
- } else if (onlyPalette) {
- return result();
- }
- const auto fromFile = [&](const char *filename) {
- raw.background = file.readFileContent(filename, zlib::kCaseInsensitive, kThemeBackgroundSizeLimit);
- if (file.error() == UNZ_OK) {
- return true;
- } else if (file.error() == UNZ_END_OF_LIST_OF_FILE) {
- file.clearError();
- return true;
- }
- LOG(("Theme Error: could not read '%1' in the theme file.").arg(filename));
- return false;
- };
- if (!fromFile("background.jpg") || !raw.background.isEmpty()) {
- return raw.background.isEmpty() ? ParsedTheme() : result();
- }
- raw.isPng = true;
- if (!fromFile("background.png") || !raw.background.isEmpty()) {
- return raw.background.isEmpty() ? ParsedTheme() : result();
- }
- raw.tiled = true;
- if (!fromFile("tiled.png") || !raw.background.isEmpty()) {
- return raw.background.isEmpty() ? ParsedTheme() : result();
- }
- raw.isPng = false;
- if (!fromFile("background.jpg") || !raw.background.isEmpty()) {
- return raw.background.isEmpty() ? ParsedTheme() : result();
- }
- return result();
- }
- [[nodiscard]] QString GenerateSlug() {
- const auto letters = uint8('Z' + 1 - 'A');
- const auto digits = uint8('9' + 1 - '0');
- const auto firstValues = uint8(2 * letters);
- const auto values = uint8(2 * letters + digits);
- auto result = QString();
- result.reserve(kRandomSlugSize);
- for (auto i = 0; i != kRandomSlugSize; ++i) {
- const auto value = i
- ? (base::RandomValue<uint8>() % values)
- : (base::RandomValue<uint8>() % firstValues);
- if (value < letters) {
- result.append(char('A' + value));
- } else if (value < 2 * letters) {
- result.append(char('a' + (value - letters)));
- } else {
- result.append(char('0' + (value - 2 * letters)));
- }
- }
- return result;
- }
- } // namespace Theme
- } // namespace Window
|