mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-03-25 04:38:23 +00:00
Upload saved theme to the cloud.
This commit is contained in:
parent
4b045a602c
commit
229bc56cc8
@ -1603,6 +1603,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_theme_editor_done" = "Theme exported successfully!";
|
||||
"lng_theme_editor_title" = "Edit color palette";
|
||||
"lng_theme_editor_export_button" = "Export theme";
|
||||
"lng_theme_editor_save_button" = "Save theme";
|
||||
|
||||
"lng_theme_editor_create_title" = "Create theme";
|
||||
"lng_theme_editor_name" = "Theme name";
|
||||
|
@ -27,10 +27,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/image/image.h"
|
||||
#include "ui/image/image_source.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "window/themes/window_theme_editor.h"
|
||||
#include "window/themes/window_theme.h"
|
||||
#include "window/themes/window_themes_embedded.h"
|
||||
#include "window/themes/window_theme_create_box.h"
|
||||
#include "window/themes/window_theme_editor_box.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "info/profile/info_profile_button.h"
|
||||
#include "storage/localstorage.h"
|
||||
@ -43,6 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "support/support_templates.h"
|
||||
#include "main/main_session.h"
|
||||
#include "mainwidget.h"
|
||||
#include "mainwindow.h"
|
||||
#include "styles/style_settings.h"
|
||||
#include "styles/style_boxes.h"
|
||||
|
||||
@ -1264,7 +1264,9 @@ void SetupThemeOptions(
|
||||
&st::settingsIconThemes,
|
||||
st::settingsChatIconLeft
|
||||
)->addClickHandler([=] {
|
||||
Ui::show(Box(Window::Theme::CreateBox, &controller->session()));
|
||||
Ui::show(Box(
|
||||
Window::Theme::CreateBox,
|
||||
&controller->window()->controller()));
|
||||
});
|
||||
|
||||
AddSkip(container);
|
||||
|
@ -76,7 +76,7 @@ struct Uploader::File {
|
||||
Uploader::File::File(const SendMediaReady &media) : media(media) {
|
||||
partsCount = media.parts.size();
|
||||
if (type() == SendMediaType::File
|
||||
|| type() == SendMediaType::WallPaper
|
||||
|| type() == SendMediaType::ThemeFile
|
||||
|| type() == SendMediaType::Audio) {
|
||||
setDocSize(media.file.isEmpty()
|
||||
? media.data.size()
|
||||
@ -92,7 +92,7 @@ Uploader::File::File(const std::shared_ptr<FileLoadResult> &file)
|
||||
? file->fileparts.size()
|
||||
: file->thumbparts.size();
|
||||
if (type() == SendMediaType::File
|
||||
|| type() == SendMediaType::WallPaper
|
||||
|| type() == SendMediaType::ThemeFile
|
||||
|| type() == SendMediaType::Audio) {
|
||||
setDocSize(file->filesize);
|
||||
} else {
|
||||
@ -154,7 +154,7 @@ void Uploader::uploadMedia(
|
||||
if (media.type == SendMediaType::Photo) {
|
||||
Auth().data().processPhoto(media.photo, media.photoThumbs);
|
||||
} else if (media.type == SendMediaType::File
|
||||
|| media.type == SendMediaType::WallPaper
|
||||
|| media.type == SendMediaType::ThemeFile
|
||||
|| media.type == SendMediaType::Audio) {
|
||||
const auto document = media.photoThumbs.empty()
|
||||
? Auth().data().processDocument(media.document)
|
||||
@ -163,7 +163,7 @@ void Uploader::uploadMedia(
|
||||
base::duplicate(media.photoThumbs.front().second));
|
||||
if (!media.data.isEmpty()) {
|
||||
document->setData(media.data);
|
||||
if (media.type == SendMediaType::WallPaper) {
|
||||
if (media.type == SendMediaType::ThemeFile) {
|
||||
document->checkWallPaperProperties();
|
||||
}
|
||||
if (document->saveToCache()
|
||||
@ -193,7 +193,7 @@ void Uploader::upload(
|
||||
photo->uploadingData = std::make_unique<Data::UploadState>(
|
||||
file->partssize);
|
||||
} else if (file->type == SendMediaType::File
|
||||
|| file->type == SendMediaType::WallPaper
|
||||
|| file->type == SendMediaType::ThemeFile
|
||||
|| file->type == SendMediaType::Audio) {
|
||||
const auto document = file->thumb.isNull()
|
||||
? Auth().data().processDocument(file->document)
|
||||
@ -207,7 +207,7 @@ void Uploader::upload(
|
||||
std::move(file->goodThumbnailBytes));
|
||||
if (!file->content.isEmpty()) {
|
||||
document->setData(file->content);
|
||||
if (file->type == SendMediaType::WallPaper) {
|
||||
if (file->type == SendMediaType::ThemeFile) {
|
||||
document->checkWallPaperProperties();
|
||||
}
|
||||
if (document->saveToCache()
|
||||
@ -233,7 +233,7 @@ void Uploader::currentFailed() {
|
||||
if (j->second.type() == SendMediaType::Photo) {
|
||||
_photoFailed.fire_copy(j->first);
|
||||
} else if (j->second.type() == SendMediaType::File
|
||||
|| j->second.type() == SendMediaType::WallPaper
|
||||
|| j->second.type() == SendMediaType::ThemeFile
|
||||
|| j->second.type() == SendMediaType::Audio) {
|
||||
const auto document = Auth().data().document(j->second.id());
|
||||
if (document->uploading()) {
|
||||
@ -335,7 +335,7 @@ void Uploader::sendNext() {
|
||||
MTP_bytes(md5));
|
||||
_photoReady.fire({ uploadingId, options, file, edit });
|
||||
} else if (uploadingData.type() == SendMediaType::File
|
||||
|| uploadingData.type() == SendMediaType::WallPaper
|
||||
|| uploadingData.type() == SendMediaType::ThemeFile
|
||||
|| uploadingData.type() == SendMediaType::Audio) {
|
||||
QByteArray docMd5(32, Qt::Uninitialized);
|
||||
hashMd5Hex(uploadingData.md5Hash.result(), docMd5.data());
|
||||
@ -412,7 +412,7 @@ void Uploader::sendNext() {
|
||||
* uploadingData.docPartSize;
|
||||
toSend = content.mid(offset, uploadingData.docPartSize);
|
||||
if ((uploadingData.type() == SendMediaType::File
|
||||
|| uploadingData.type() == SendMediaType::WallPaper
|
||||
|| uploadingData.type() == SendMediaType::ThemeFile
|
||||
|| uploadingData.type() == SendMediaType::Audio)
|
||||
&& uploadingData.docSentParts <= kUseBigFilesFrom) {
|
||||
uploadingData.md5Hash.feed(toSend.constData(), toSend.size());
|
||||
@ -554,7 +554,7 @@ void Uploader::partLoaded(const MTPBool &result, mtpRequestId requestId) {
|
||||
}
|
||||
_photoProgress.fire_copy(fullId);
|
||||
} else if (file.type() == SendMediaType::File
|
||||
|| file.type() == SendMediaType::WallPaper
|
||||
|| file.type() == SendMediaType::ThemeFile
|
||||
|| file.type() == SendMediaType::Audio) {
|
||||
const auto document = Auth().data().document(file.id());
|
||||
if (document->uploading()) {
|
||||
|
@ -243,68 +243,6 @@ SendMediaReady PreparePeerPhoto(PeerId peerId, QImage &&image) {
|
||||
0);
|
||||
}
|
||||
|
||||
SendMediaReady PrepareWallPaper(const QImage &image) {
|
||||
PreparedPhotoThumbs thumbnails;
|
||||
QVector<MTPPhotoSize> sizes;
|
||||
|
||||
QByteArray jpeg;
|
||||
QBuffer jpegBuffer(&jpeg);
|
||||
image.save(&jpegBuffer, "JPG", 87);
|
||||
|
||||
const auto scaled = [&](int size) {
|
||||
return image.scaled(
|
||||
size,
|
||||
size,
|
||||
Qt::KeepAspectRatio,
|
||||
Qt::SmoothTransformation);
|
||||
};
|
||||
const auto push = [&](const char *type, QImage &&image) {
|
||||
sizes.push_back(MTP_photoSize(
|
||||
MTP_string(type),
|
||||
MTP_fileLocationToBeDeprecated(MTP_long(0), MTP_int(0)),
|
||||
MTP_int(image.width()),
|
||||
MTP_int(image.height()), MTP_int(0)));
|
||||
thumbnails.emplace(type[0], std::move(image));
|
||||
};
|
||||
push("s", scaled(320));
|
||||
|
||||
const auto filename = qsl("wallpaper.jpg");
|
||||
auto attributes = QVector<MTPDocumentAttribute>(
|
||||
1,
|
||||
MTP_documentAttributeFilename(MTP_string(filename)));
|
||||
attributes.push_back(MTP_documentAttributeImageSize(
|
||||
MTP_int(image.width()),
|
||||
MTP_int(image.height())));
|
||||
const auto id = rand_value<DocumentId>();
|
||||
const auto document = MTP_document(
|
||||
MTP_flags(0),
|
||||
MTP_long(id),
|
||||
MTP_long(0),
|
||||
MTP_bytes(),
|
||||
MTP_int(base::unixtime::now()),
|
||||
MTP_string("image/jpeg"),
|
||||
MTP_int(jpeg.size()),
|
||||
MTP_vector<MTPPhotoSize>(sizes),
|
||||
MTP_int(MTP::maindc()),
|
||||
MTP_vector<MTPDocumentAttribute>(attributes));
|
||||
|
||||
return SendMediaReady(
|
||||
SendMediaType::WallPaper,
|
||||
QString(), // filepath
|
||||
filename,
|
||||
jpeg.size(),
|
||||
jpeg,
|
||||
id,
|
||||
0,
|
||||
QString(),
|
||||
PeerId(),
|
||||
MTP_photoEmpty(MTP_long(0)),
|
||||
thumbnails,
|
||||
document,
|
||||
QByteArray(),
|
||||
0);
|
||||
}
|
||||
|
||||
TaskQueue::TaskQueue(crl::time stopTimeoutMs) {
|
||||
if (stopTimeoutMs > 0) {
|
||||
_stopTimer = new QTimer(this);
|
||||
|
@ -21,7 +21,7 @@ enum class SendMediaType {
|
||||
Photo,
|
||||
Audio,
|
||||
File,
|
||||
WallPaper,
|
||||
ThemeFile,
|
||||
Secure,
|
||||
};
|
||||
|
||||
@ -85,7 +85,6 @@ struct SendMediaReady {
|
||||
};
|
||||
|
||||
SendMediaReady PreparePeerPhoto(PeerId peerId, QImage &&image);
|
||||
SendMediaReady PrepareWallPaper(const QImage &image);
|
||||
|
||||
using TaskId = void*; // no interface, just id
|
||||
|
||||
|
@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "storage/file_upload.h"
|
||||
#include "base/parse_helper.h"
|
||||
#include "base/zlib_help.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "data/data_session.h"
|
||||
#include "main/main_account.h" // Account::sessionValue.
|
||||
#include "ui/image/image.h"
|
||||
@ -395,6 +396,68 @@ void ClearApplying() {
|
||||
GlobalApplying = Applying();
|
||||
}
|
||||
|
||||
SendMediaReady PrepareWallPaper(const QImage &image) {
|
||||
PreparedPhotoThumbs thumbnails;
|
||||
QVector<MTPPhotoSize> sizes;
|
||||
|
||||
QByteArray jpeg;
|
||||
QBuffer jpegBuffer(&jpeg);
|
||||
image.save(&jpegBuffer, "JPG", 87);
|
||||
|
||||
const auto scaled = [&](int size) {
|
||||
return image.scaled(
|
||||
size,
|
||||
size,
|
||||
Qt::KeepAspectRatio,
|
||||
Qt::SmoothTransformation);
|
||||
};
|
||||
const auto push = [&](const char *type, QImage &&image) {
|
||||
sizes.push_back(MTP_photoSize(
|
||||
MTP_string(type),
|
||||
MTP_fileLocationToBeDeprecated(MTP_long(0), MTP_int(0)),
|
||||
MTP_int(image.width()),
|
||||
MTP_int(image.height()), MTP_int(0)));
|
||||
thumbnails.emplace(type[0], std::move(image));
|
||||
};
|
||||
push("s", scaled(320));
|
||||
|
||||
const auto filename = qsl("wallpaper.jpg");
|
||||
auto attributes = QVector<MTPDocumentAttribute>(
|
||||
1,
|
||||
MTP_documentAttributeFilename(MTP_string(filename)));
|
||||
attributes.push_back(MTP_documentAttributeImageSize(
|
||||
MTP_int(image.width()),
|
||||
MTP_int(image.height())));
|
||||
const auto id = rand_value<DocumentId>();
|
||||
const auto document = MTP_document(
|
||||
MTP_flags(0),
|
||||
MTP_long(id),
|
||||
MTP_long(0),
|
||||
MTP_bytes(),
|
||||
MTP_int(base::unixtime::now()),
|
||||
MTP_string("image/jpeg"),
|
||||
MTP_int(jpeg.size()),
|
||||
MTP_vector<MTPPhotoSize>(sizes),
|
||||
MTP_int(MTP::maindc()),
|
||||
MTP_vector<MTPDocumentAttribute>(attributes));
|
||||
|
||||
return SendMediaReady(
|
||||
SendMediaType::ThemeFile,
|
||||
QString(), // filepath
|
||||
filename,
|
||||
jpeg.size(),
|
||||
jpeg,
|
||||
id,
|
||||
0,
|
||||
QString(),
|
||||
PeerId(),
|
||||
MTP_photoEmpty(MTP_long(0)),
|
||||
thumbnails,
|
||||
document,
|
||||
QByteArray(),
|
||||
0);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ChatBackground::AdjustableColor::AdjustableColor(style::color data)
|
||||
|
@ -1,148 +0,0 @@
|
||||
/*
|
||||
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_create_box.h"
|
||||
|
||||
#include "window/themes/window_theme.h"
|
||||
#include "window/themes/window_theme_editor.h"
|
||||
#include "boxes/generic_box.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/widgets/input_fields.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "info/profile/info_profile_button.h"
|
||||
#include "main/main_session.h"
|
||||
#include "core/file_utilities.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "mainwindow.h"
|
||||
#include "styles/style_widgets.h"
|
||||
#include "styles/style_window.h"
|
||||
#include "styles/style_boxes.h"
|
||||
|
||||
namespace Window {
|
||||
namespace Theme {
|
||||
namespace {
|
||||
|
||||
void ImportFromFile(
|
||||
not_null<Main::Session*> session,
|
||||
not_null<QWidget*> parent) {
|
||||
const auto &imgExtensions = cImgExtensions();
|
||||
auto filters = QStringList(
|
||||
qsl("Theme files (*.tdesktop-theme *.tdesktop-palette)"));
|
||||
filters.push_back(FileDialog::AllFilesFilter());
|
||||
const auto callback = crl::guard(session, [=](
|
||||
const FileDialog::OpenResult &result) {
|
||||
if (result.paths.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Window::Theme::Apply(result.paths.front());
|
||||
});
|
||||
FileDialog::GetOpenPath(
|
||||
parent.get(),
|
||||
tr::lng_choose_image(tr::now),
|
||||
filters.join(qsl(";;")),
|
||||
crl::guard(parent, callback));
|
||||
}
|
||||
|
||||
QString BytesToUTF8(QLatin1String string) {
|
||||
return QString::fromUtf8(string.data(), string.size());
|
||||
}
|
||||
|
||||
void WriteDefaultPalette(const QString &path) {
|
||||
QFile f(path);
|
||||
if (!f.open(QIODevice::WriteOnly)) {
|
||||
LOG(("Theme Error: could not open '%1' for writing.").arg(path));
|
||||
return;
|
||||
}
|
||||
|
||||
QTextStream stream(&f);
|
||||
stream.setCodec("UTF-8");
|
||||
|
||||
auto rows = style::main_palette::data();
|
||||
for (const auto &row : std::as_const(rows)) {
|
||||
stream
|
||||
<< BytesToUTF8(row.name)
|
||||
<< ": "
|
||||
<< BytesToUTF8(row.value)
|
||||
<< "; // "
|
||||
<< BytesToUTF8(
|
||||
row.description
|
||||
).replace(
|
||||
'\n',
|
||||
' '
|
||||
).replace(
|
||||
'\r',
|
||||
' ')
|
||||
<< "\n";
|
||||
}
|
||||
}
|
||||
|
||||
void StartEditor(
|
||||
not_null<Main::Session*> session,
|
||||
const QString &title) {
|
||||
const auto path = EditingPalettePath();
|
||||
if (!Local::copyThemeColorsToPalette(path)) {
|
||||
WriteDefaultPalette(path);
|
||||
}
|
||||
if (!Apply(path)) {
|
||||
Ui::show(Box<InformBox>(tr::lng_theme_editor_error(tr::now)));
|
||||
return;
|
||||
}
|
||||
KeepApplied();
|
||||
if (const auto window = App::wnd()) {
|
||||
window->showRightColumn(Box<Editor>());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void CreateBox(not_null<GenericBox*> box, not_null<Main::Session*> session) {
|
||||
box->setTitle(tr::lng_theme_editor_create_title(Ui::Text::WithEntities));
|
||||
|
||||
const auto name = box->addRow(object_ptr<Ui::InputField>(
|
||||
box,
|
||||
st::defaultInputField,
|
||||
tr::lng_theme_editor_name()));
|
||||
|
||||
box->addRow(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
tr::lng_theme_editor_create_description(),
|
||||
st::boxDividerLabel),
|
||||
style::margins(
|
||||
st::boxRowPadding.left(),
|
||||
st::boxRowPadding.left(),
|
||||
st::boxRowPadding.right(),
|
||||
st::boxRowPadding.right()));
|
||||
|
||||
box->addRow(
|
||||
object_ptr<Info::Profile::Button>(
|
||||
box,
|
||||
tr::lng_theme_editor_import_existing() | Ui::Text::ToUpper(),
|
||||
st::createThemeImportButton),
|
||||
style::margins()
|
||||
)->addClickHandler([=] {
|
||||
ImportFromFile(session, box);
|
||||
});
|
||||
|
||||
box->setFocusCallback([=] { name->setFocusFast(); });
|
||||
|
||||
box->addButton(tr::lng_box_done(), [=] {
|
||||
const auto title = name->getLastText().trimmed();
|
||||
if (title.isEmpty()) {
|
||||
name->showError();
|
||||
return;
|
||||
}
|
||||
box->closeBox();
|
||||
StartEditor(session, title);
|
||||
});
|
||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||
}
|
||||
|
||||
} // namespace Theme
|
||||
} // namespace Window
|
@ -9,9 +9,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "window/themes/window_theme.h"
|
||||
#include "window/themes/window_theme_editor_block.h"
|
||||
#include "window/themes/window_theme_editor_box.h"
|
||||
#include "window/themes/window_themes_embedded.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "main/main_account.h"
|
||||
#include "mainwindow.h"
|
||||
#include "layout.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "styles/style_window.h"
|
||||
@ -20,9 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/widgets/shadow.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/multi_select.h"
|
||||
#include "ui/image/image_prepare.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "base/parse_helper.h"
|
||||
#include "base/zlib_help.h"
|
||||
@ -247,8 +247,9 @@ public:
|
||||
}
|
||||
|
||||
void prepare();
|
||||
|
||||
Fn<void()> exportCallback();
|
||||
[[nodiscard]] QByteArray paletteContent() const {
|
||||
return _paletteContent;
|
||||
}
|
||||
|
||||
void filterRows(const QString &query);
|
||||
void chooseRow();
|
||||
@ -293,34 +294,6 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class ThemeExportBox : public BoxContent {
|
||||
public:
|
||||
ThemeExportBox(QWidget*, const QByteArray &paletteContent, const QImage &background, const QByteArray &backgroundContent, bool tileBackground);
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
||||
private:
|
||||
void updateThumbnail();
|
||||
void chooseBackgroundFromFile();
|
||||
void exportTheme();
|
||||
|
||||
QByteArray _paletteContent;
|
||||
|
||||
QImage _background;
|
||||
QByteArray _backgroundContent;
|
||||
bool _isPng = false;
|
||||
QString _imageText;
|
||||
QPixmap _thumbnail;
|
||||
|
||||
object_ptr<Ui::LinkButton> _chooseFromFile;
|
||||
object_ptr<Ui::Checkbox> _tileBackground;
|
||||
|
||||
};
|
||||
|
||||
bool CopyColorsToPalette(
|
||||
const QString &destination,
|
||||
const QString &themePath,
|
||||
@ -402,19 +375,6 @@ void Editor::Inner::prepare() {
|
||||
}
|
||||
}
|
||||
|
||||
Fn<void()> Editor::Inner::exportCallback() {
|
||||
return App::LambdaDelayed(st::defaultRippleAnimation.hideDuration, this, [=] {
|
||||
auto background = Background()->createCurrentImage();
|
||||
auto backgroundContent = QByteArray();
|
||||
auto tiled = Background()->tile();
|
||||
{
|
||||
QBuffer buffer(&backgroundContent);
|
||||
background.save(&buffer, "JPG", 87);
|
||||
}
|
||||
Ui::show(Box<ThemeExportBox>(_paletteContent, background, backgroundContent, tiled));
|
||||
});
|
||||
}
|
||||
|
||||
void Editor::Inner::filterRows(const QString &query) {
|
||||
if (query == ":sort-for-accent") {
|
||||
sortByAccentDistance();
|
||||
@ -637,150 +597,45 @@ void Editor::Inner::applyEditing(const QString &name, const QString ©Of, QCo
|
||||
_paletteContent = newContent;
|
||||
}
|
||||
|
||||
ThemeExportBox::ThemeExportBox(QWidget*, const QByteArray &paletteContent, const QImage &background, const QByteArray &backgroundContent, bool tileBackground) : BoxContent()
|
||||
, _paletteContent(paletteContent)
|
||||
, _background(background)
|
||||
, _backgroundContent(backgroundContent)
|
||||
, _chooseFromFile(this, tr::lng_settings_bg_from_file(tr::now), st::boxLinkButton)
|
||||
, _tileBackground(this, tr::lng_settings_bg_tile(tr::now), tileBackground, st::defaultBoxCheckbox) {
|
||||
_imageText = tr::lng_theme_editor_saved_to_jpg(tr::now, lt_size, formatSizeText(_backgroundContent.size()));
|
||||
_chooseFromFile->setClickedCallback([this] { chooseBackgroundFromFile(); });
|
||||
}
|
||||
//void ThemeExportBox::exportTheme() {
|
||||
// App::CallDelayed(st::defaultRippleAnimation.hideDuration, this, [this] {
|
||||
// auto caption = tr::lng_theme_editor_choose_name(tr::now);
|
||||
// auto filter = "Themes (*.tdesktop-theme)";
|
||||
// auto name = "awesome.tdesktop-theme";
|
||||
// FileDialog::GetWritePath(this, caption, filter, name, crl::guard(this, [this](const QString &path) {
|
||||
// QFile f(path);
|
||||
// if (!f.open(QIODevice::WriteOnly)) {
|
||||
// LOG(("Theme Error: could not open zip-ed theme file '%1' for writing").arg(path));
|
||||
// Ui::show(Box<InformBox>(tr::lng_theme_editor_error(tr::now)));
|
||||
// return;
|
||||
// }
|
||||
// if (f.write(result) != result.size()) {
|
||||
// LOG(("Theme Error: could not write zip-ed theme to file '%1'").arg(path));
|
||||
// Ui::show(Box<InformBox>(tr::lng_theme_editor_error(tr::now)));
|
||||
// return;
|
||||
// }
|
||||
// Ui::hideLayer();
|
||||
// Ui::Toast::Show(tr::lng_theme_editor_done(tr::now));
|
||||
// }));
|
||||
// });
|
||||
//}
|
||||
|
||||
void ThemeExportBox::prepare() {
|
||||
setTitle(tr::lng_theme_editor_background_image());
|
||||
|
||||
addButton(tr::lng_theme_editor_export(), [this] { exportTheme(); });
|
||||
addButton(tr::lng_cancel(), [this] { closeBox(); });
|
||||
|
||||
auto height = st::themesSmallSkip + st::themesBackgroundSize + st::themesSmallSkip + _tileBackground->height();
|
||||
|
||||
setDimensions(st::boxWideWidth, height);
|
||||
|
||||
updateThumbnail();
|
||||
}
|
||||
|
||||
void ThemeExportBox::paintEvent(QPaintEvent *e) {
|
||||
BoxContent::paintEvent(e);
|
||||
|
||||
Painter p(this);
|
||||
|
||||
auto linkLeft = st::boxPadding.left() + st::themesBackgroundSize + st::themesSmallSkip;
|
||||
|
||||
p.setPen(st::boxTextFg);
|
||||
p.setFont(st::boxTextFont);
|
||||
p.drawTextLeft(linkLeft, st::themesSmallSkip, width(), _imageText);
|
||||
|
||||
p.drawPixmapLeft(st::boxPadding.left(), st::themesSmallSkip, width(), _thumbnail);
|
||||
}
|
||||
|
||||
void ThemeExportBox::resizeEvent(QResizeEvent *e) {
|
||||
auto linkLeft = st::boxPadding.left() + st::themesBackgroundSize + st::themesSmallSkip;
|
||||
_chooseFromFile->moveToLeft(linkLeft, st::themesSmallSkip + st::boxTextFont->height + st::themesSmallSkip);
|
||||
_tileBackground->moveToLeft(st::boxPadding.left(), st::themesSmallSkip + st::themesBackgroundSize + 2 * st::themesSmallSkip);
|
||||
}
|
||||
|
||||
void ThemeExportBox::updateThumbnail() {
|
||||
int32 size = st::themesBackgroundSize * cIntRetinaFactor();
|
||||
QImage back(size, size, QImage::Format_ARGB32_Premultiplied);
|
||||
back.setDevicePixelRatio(cRetinaFactor());
|
||||
{
|
||||
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, st::themesBackgroundSize, st::themesBackgroundSize), pix, QRect(sx, sy, s, s));
|
||||
}
|
||||
Images::prepareRound(back, ImageRoundRadius::Small);
|
||||
_thumbnail = App::pixmapFromImageInPlace(std::move(back));
|
||||
_thumbnail.setDevicePixelRatio(cRetinaFactor());
|
||||
update();
|
||||
}
|
||||
|
||||
void ThemeExportBox::chooseBackgroundFromFile() {
|
||||
FileDialog::GetOpenPath(this, tr::lng_theme_editor_choose_image(tr::now), "Image files (*.jpeg *.jpg *.png)", crl::guard(this, [this](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 format = QByteArray();
|
||||
auto image = App::readImage(content, &format);
|
||||
if (!image.isNull() && (format == "jpeg" || format == "jpg" || format == "png")) {
|
||||
_background = image;
|
||||
_backgroundContent = content;
|
||||
_isPng = (format == "png");
|
||||
auto sizeText = formatSizeText(_backgroundContent.size());
|
||||
_imageText = _isPng ? tr::lng_theme_editor_read_from_png(tr::now, lt_size, sizeText) : tr::lng_theme_editor_read_from_jpg(tr::now, lt_size, sizeText);
|
||||
_tileBackground->setChecked(false);
|
||||
updateThumbnail();
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
void ThemeExportBox::exportTheme() {
|
||||
App::CallDelayed(st::defaultRippleAnimation.hideDuration, this, [this] {
|
||||
auto caption = tr::lng_theme_editor_choose_name(tr::now);
|
||||
auto filter = "Themes (*.tdesktop-theme)";
|
||||
auto name = "awesome.tdesktop-theme";
|
||||
FileDialog::GetWritePath(this, caption, filter, name, crl::guard(this, [this](const QString &path) {
|
||||
zlib::FileToWrite zip;
|
||||
|
||||
zip_fileinfo zfi = { { 0, 0, 0, 0, 0, 0 }, 0, 0, 0 };
|
||||
auto background = std::string(_tileBackground->checked() ? "tiled" : "background") + (_isPng ? ".png" : ".jpg");
|
||||
zip.openNewFile(background.c_str(), &zfi, nullptr, 0, nullptr, 0, nullptr, Z_DEFLATED, Z_DEFAULT_COMPRESSION);
|
||||
zip.writeInFile(_backgroundContent.constData(), _backgroundContent.size());
|
||||
zip.closeFile();
|
||||
auto scheme = "colors.tdesktop-theme";
|
||||
zip.openNewFile(scheme, &zfi, nullptr, 0, nullptr, 0, nullptr, Z_DEFLATED, Z_DEFAULT_COMPRESSION);
|
||||
zip.writeInFile(_paletteContent.constData(), _paletteContent.size());
|
||||
zip.closeFile();
|
||||
zip.close();
|
||||
|
||||
if (zip.error() != ZIP_OK) {
|
||||
LOG(("Theme Error: could not export zip-ed theme, status: %1").arg(zip.error()));
|
||||
Ui::show(Box<InformBox>(tr::lng_theme_editor_error(tr::now)));
|
||||
return;
|
||||
}
|
||||
auto result = zip.result();
|
||||
|
||||
QFile f(path);
|
||||
if (!f.open(QIODevice::WriteOnly)) {
|
||||
LOG(("Theme Error: could not open zip-ed theme file '%1' for writing").arg(path));
|
||||
Ui::show(Box<InformBox>(tr::lng_theme_editor_error(tr::now)));
|
||||
return;
|
||||
}
|
||||
if (f.write(result) != result.size()) {
|
||||
LOG(("Theme Error: could not write zip-ed theme to file '%1'").arg(path));
|
||||
Ui::show(Box<InformBox>(tr::lng_theme_editor_error(tr::now)));
|
||||
return;
|
||||
}
|
||||
Ui::hideLayer();
|
||||
Ui::Toast::Show(tr::lng_theme_editor_done(tr::now));
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
Editor::Editor(QWidget*)
|
||||
: _scroll(this, st::themesScroll)
|
||||
Editor::Editor(QWidget*, not_null<Window::Controller*> window)
|
||||
: _window(window)
|
||||
, _scroll(this, st::themesScroll)
|
||||
, _close(this, st::contactsMultiSelect.fieldCancel)
|
||||
, _select(this, st::contactsMultiSelect, tr::lng_country_ph())
|
||||
, _leftShadow(this)
|
||||
, _topShadow(this)
|
||||
, _export(this, tr::lng_theme_editor_export_button(tr::now).toUpper(), st::dialogsUpdateButton) {
|
||||
, _save(this, tr::lng_theme_editor_save_button(tr::now).toUpper(), st::dialogsUpdateButton) {
|
||||
const auto path = EditingPalettePath();
|
||||
|
||||
_inner = _scroll->setOwnedWidget(object_ptr<Inner>(this, path));
|
||||
|
||||
_export->setClickedCallback(_inner->exportCallback());
|
||||
_save->setClickedCallback(App::LambdaDelayed(
|
||||
st::defaultRippleAnimation.hideDuration,
|
||||
this,
|
||||
[=] { save(); }));
|
||||
|
||||
_inner->setErrorCallback([this] {
|
||||
Ui::show(Box<InformBox>(tr::lng_theme_editor_error(tr::now)));
|
||||
@ -808,8 +663,16 @@ Editor::Editor(QWidget*)
|
||||
resizeToWidth(st::windowMinWidth);
|
||||
}
|
||||
|
||||
void Editor::save() {
|
||||
if (!_window->account().sessionExists()) {
|
||||
//_window->show(Box<InformBox>())
|
||||
return;
|
||||
}
|
||||
Ui::show(Box(SaveThemeBox, _window, _inner->paletteContent()));
|
||||
}
|
||||
|
||||
void Editor::resizeEvent(QResizeEvent *e) {
|
||||
_export->resizeToWidth(width());
|
||||
_save->resizeToWidth(width());
|
||||
_close->moveToRight(0, 0);
|
||||
|
||||
_select->resizeToWidth(width());
|
||||
@ -821,7 +684,7 @@ void Editor::resizeEvent(QResizeEvent *e) {
|
||||
_topShadow->moveToLeft(st::lineWidth, shadowTop);
|
||||
_leftShadow->resize(st::lineWidth, height());
|
||||
_leftShadow->moveToLeft(0, 0);
|
||||
auto scrollSize = QSize(width(), height() - shadowTop - _export->height());
|
||||
auto scrollSize = QSize(width(), height() - shadowTop - _save->height());
|
||||
if (_scroll->size() != scrollSize) {
|
||||
_scroll->resize(scrollSize);
|
||||
}
|
||||
@ -831,7 +694,7 @@ void Editor::resizeEvent(QResizeEvent *e) {
|
||||
auto scrollTop = _scroll->scrollTop();
|
||||
_inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height());
|
||||
}
|
||||
_export->moveToLeft(0, _scroll->y() + _scroll->height());
|
||||
_save->moveToLeft(0, _scroll->y() + _scroll->height());
|
||||
}
|
||||
|
||||
void Editor::keyPressEvent(QKeyEvent *e) {
|
||||
@ -894,7 +757,7 @@ void Editor::paintEvent(QPaintEvent *e) {
|
||||
//}
|
||||
|
||||
void Editor::closeEditor() {
|
||||
if (auto window = App::wnd()) {
|
||||
if (const auto window = App::wnd()) {
|
||||
window->showRightColumn(nullptr);
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,9 @@ class PlainShadow;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Window {
|
||||
|
||||
class Controller;
|
||||
|
||||
namespace Theme {
|
||||
|
||||
bool CopyColorsToPalette(
|
||||
@ -25,7 +28,7 @@ bool CopyColorsToPalette(
|
||||
|
||||
class Editor : public TWidget {
|
||||
public:
|
||||
explicit Editor(QWidget*);
|
||||
Editor(QWidget*, not_null<Window::Controller*> window);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
@ -35,8 +38,10 @@ protected:
|
||||
void focusInEvent(QFocusEvent *e) override;
|
||||
|
||||
private:
|
||||
void save();
|
||||
void closeEditor();
|
||||
|
||||
not_null<Window::Controller*> _window;
|
||||
object_ptr<Ui::ScrollArea> _scroll;
|
||||
class Inner;
|
||||
QPointer<Inner> _inner;
|
||||
@ -44,7 +49,7 @@ private:
|
||||
object_ptr<Ui::MultiSelect> _select;
|
||||
object_ptr<Ui::PlainShadow> _leftShadow;
|
||||
object_ptr<Ui::PlainShadow> _topShadow;
|
||||
object_ptr<Ui::FlatButton> _export;
|
||||
object_ptr<Ui::FlatButton> _save;
|
||||
|
||||
};
|
||||
|
||||
|
664
Telegram/SourceFiles/window/themes/window_theme_editor_box.cpp
Normal file
664
Telegram/SourceFiles/window/themes/window_theme_editor_box.cpp
Normal file
@ -0,0 +1,664 @@
|
||||
/*
|
||||
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/window_controller.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/widgets/input_fields.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/image/image_prepare.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "info/profile/info_profile_button.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/zlib_help.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_document.h"
|
||||
#include "storage/file_upload.h"
|
||||
#include "mainwindow.h"
|
||||
#include "layout.h"
|
||||
#include "apiwrap.h"
|
||||
#include "styles/style_widgets.h"
|
||||
#include "styles/style_window.h"
|
||||
#include "styles/style_settings.h"
|
||||
#include "styles/style_boxes.h"
|
||||
|
||||
namespace Window {
|
||||
namespace Theme {
|
||||
namespace {
|
||||
|
||||
constexpr auto kRandomSlugSize = 16;
|
||||
constexpr auto kMinSlugSize = 5;
|
||||
constexpr auto kMaxSlugSize = 64;
|
||||
|
||||
enum class SaveErrorType {
|
||||
Other,
|
||||
Name,
|
||||
Link,
|
||||
};
|
||||
|
||||
struct PreparedBackground {
|
||||
QByteArray content;
|
||||
bool tile = false;
|
||||
bool isPng = false;
|
||||
};
|
||||
|
||||
class BackgroundSelector : public Ui::RpWidget {
|
||||
public:
|
||||
BackgroundSelector(
|
||||
QWidget *parent,
|
||||
const QImage &background,
|
||||
const PreparedBackground &data);
|
||||
|
||||
[[nodiscard]] PreparedBackground result() 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;
|
||||
QByteArray _backgroundContent;
|
||||
bool _isPng = false;
|
||||
QString _imageText;
|
||||
QPixmap _thumbnail;
|
||||
|
||||
};
|
||||
|
||||
BackgroundSelector::BackgroundSelector(
|
||||
QWidget *parent,
|
||||
const QImage &background,
|
||||
const PreparedBackground &data)
|
||||
: RpWidget(parent)
|
||||
, _chooseFromFile(
|
||||
this,
|
||||
tr::lng_settings_bg_from_file(tr::now),
|
||||
st::boxLinkButton)
|
||||
, _tileBackground(
|
||||
this,
|
||||
tr::lng_settings_bg_tile(tr::now),
|
||||
data.tile,
|
||||
st::defaultBoxCheckbox)
|
||||
, _background(background)
|
||||
, _backgroundContent(data.content) {
|
||||
_imageText = tr::lng_theme_editor_saved_to_jpg(
|
||||
tr::now,
|
||||
lt_size,
|
||||
formatSizeText(_backgroundContent.size()));
|
||||
_chooseFromFile->setClickedCallback([=] { chooseBackgroundFromFile(); });
|
||||
|
||||
const auto height = st::boxTextFont->height
|
||||
+ st::themesSmallSkip
|
||||
+ _chooseFromFile->heightNoMargins()
|
||||
+ st::themesSmallSkip
|
||||
+ _tileBackground->heightNoMargins();
|
||||
resize(width(), height);
|
||||
|
||||
updateThumbnail();
|
||||
}
|
||||
|
||||
void BackgroundSelector::paintEvent(QPaintEvent *e) {
|
||||
Painter p(this);
|
||||
|
||||
const auto left = height() + 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 = height() + 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 = height();
|
||||
auto back = QImage(
|
||||
QSize(size, size) * cIntRetinaFactor(),
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
back.setDevicePixelRatio(cRetinaFactor());
|
||||
{
|
||||
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));
|
||||
}
|
||||
Images::prepareRound(back, ImageRoundRadius::Small);
|
||||
_thumbnail = App::pixmapFromImageInPlace(std::move(back));
|
||||
_thumbnail.setDevicePixelRatio(cRetinaFactor());
|
||||
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 format = QByteArray();
|
||||
auto image = App::readImage(content, &format);
|
||||
if (!image.isNull()
|
||||
&& (format == "jpeg"
|
||||
|| format == "jpg"
|
||||
|| format == "png")) {
|
||||
_background = image;
|
||||
_backgroundContent = content;
|
||||
_isPng = (format == "png");
|
||||
const auto phrase = _isPng
|
||||
? tr::lng_theme_editor_read_from_png
|
||||
: tr::lng_theme_editor_read_from_jpg;
|
||||
_imageText = phrase(
|
||||
tr::now,
|
||||
lt_size,
|
||||
formatSizeText(_backgroundContent.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));
|
||||
}
|
||||
|
||||
PreparedBackground BackgroundSelector::result() const {
|
||||
return {
|
||||
_backgroundContent,
|
||||
_tileBackground->checked(),
|
||||
_isPng,
|
||||
};
|
||||
}
|
||||
|
||||
void ImportFromFile(
|
||||
not_null<Main::Session*> session,
|
||||
not_null<QWidget*> parent) {
|
||||
const auto &imgExtensions = cImgExtensions();
|
||||
auto filters = QStringList(
|
||||
qsl("Theme files (*.tdesktop-theme *.tdesktop-palette)"));
|
||||
filters.push_back(FileDialog::AllFilesFilter());
|
||||
const auto callback = crl::guard(session, [=](
|
||||
const FileDialog::OpenResult &result) {
|
||||
if (result.paths.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Window::Theme::Apply(result.paths.front());
|
||||
});
|
||||
FileDialog::GetOpenPath(
|
||||
parent.get(),
|
||||
tr::lng_choose_image(tr::now),
|
||||
filters.join(qsl(";;")),
|
||||
crl::guard(parent, callback));
|
||||
}
|
||||
|
||||
QString BytesToUTF8(QLatin1String string) {
|
||||
return QString::fromUtf8(string.data(), string.size());
|
||||
}
|
||||
|
||||
void WriteDefaultPalette(const QString &path) {
|
||||
QFile f(path);
|
||||
if (!f.open(QIODevice::WriteOnly)) {
|
||||
LOG(("Theme Error: could not open '%1' for writing.").arg(path));
|
||||
return;
|
||||
}
|
||||
|
||||
QTextStream stream(&f);
|
||||
stream.setCodec("UTF-8");
|
||||
|
||||
auto rows = style::main_palette::data();
|
||||
for (const auto &row : std::as_const(rows)) {
|
||||
stream
|
||||
<< BytesToUTF8(row.name)
|
||||
<< ": "
|
||||
<< BytesToUTF8(row.value)
|
||||
<< "; // "
|
||||
<< BytesToUTF8(
|
||||
row.description
|
||||
).replace(
|
||||
'\n',
|
||||
' '
|
||||
).replace(
|
||||
'\r',
|
||||
' ')
|
||||
<< "\n";
|
||||
}
|
||||
}
|
||||
|
||||
void StartEditor(
|
||||
not_null<Window::Controller*> window,
|
||||
const QString &title) {
|
||||
const auto path = EditingPalettePath();
|
||||
if (!Local::copyThemeColorsToPalette(path)) {
|
||||
WriteDefaultPalette(path);
|
||||
}
|
||||
if (!Apply(path)) {
|
||||
window->show(Box<InformBox>(tr::lng_theme_editor_error(tr::now)));
|
||||
return;
|
||||
}
|
||||
KeepApplied();
|
||||
window->showRightColumn(Box<Editor>(window));
|
||||
}
|
||||
|
||||
[[nodiscard]] QString GenerateSlug() {
|
||||
const auto letters = uint8('Z' + 1 - 'A');
|
||||
const auto digits = uint8('9' + 1 - '0');
|
||||
const auto values = uint8(2 * letters + digits);
|
||||
|
||||
auto result = QString();
|
||||
result.reserve(kRandomSlugSize);
|
||||
for (auto i = 0; i != kRandomSlugSize; ++i) {
|
||||
const auto value = rand_value<uint8>() % values;
|
||||
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;
|
||||
}
|
||||
|
||||
[[nodiscard]] QByteArray PrepareTheme(
|
||||
const QByteArray &palette,
|
||||
const PreparedBackground &background) {
|
||||
zlib::FileToWrite zip;
|
||||
|
||||
zip_fileinfo zfi = { { 0, 0, 0, 0, 0, 0 }, 0, 0, 0 };
|
||||
const auto back = std::string(background.tile ? "tiled" : "background")
|
||||
+ (background.isPng ? ".png" : ".jpg");
|
||||
zip.openNewFile(
|
||||
back.c_str(),
|
||||
&zfi,
|
||||
nullptr,
|
||||
0,
|
||||
nullptr,
|
||||
0,
|
||||
nullptr,
|
||||
Z_DEFLATED,
|
||||
Z_DEFAULT_COMPRESSION);
|
||||
zip.writeInFile(
|
||||
background.content.constData(),
|
||||
background.content.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(palette.constData(), 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;
|
||||
}
|
||||
const auto i = ranges::find_if(slug, [](QChar ch) {
|
||||
return (ch < 'A' || ch > 'Z')
|
||||
&& (ch < 'a' || ch > 'z')
|
||||
&& (ch < '0' || ch > '9')
|
||||
&& (ch != '_');
|
||||
});
|
||||
return (i == slug.end());
|
||||
}
|
||||
|
||||
SendMediaReady PrepareThemeMedia(
|
||||
const QString &name,
|
||||
const QByteArray &content) {
|
||||
PreparedPhotoThumbs thumbnails;
|
||||
QVector<MTPPhotoSize> sizes;
|
||||
|
||||
//const auto push = [&](const char *type, QImage &&image) {
|
||||
// sizes.push_back(MTP_photoSize(
|
||||
// MTP_string(type),
|
||||
// MTP_fileLocationToBeDeprecated(MTP_long(0), MTP_int(0)),
|
||||
// MTP_int(image.width()),
|
||||
// MTP_int(image.height()), MTP_int(0)));
|
||||
// thumbnails.emplace(type[0], std::move(image));
|
||||
//};
|
||||
//push("s", scaled(320));
|
||||
|
||||
const auto filename = QString(name).replace(' ', '_')
|
||||
+ qsl(".tdesktop-theme"); // #TODO themes
|
||||
auto attributes = QVector<MTPDocumentAttribute>(
|
||||
1,
|
||||
MTP_documentAttributeFilename(MTP_string(filename)));
|
||||
const auto id = rand_value<DocumentId>();
|
||||
const auto 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_int(content.size()),
|
||||
MTP_vector<MTPPhotoSize>(sizes),
|
||||
MTP_int(MTP::maindc()),
|
||||
MTP_vector<MTPDocumentAttribute>(attributes));
|
||||
|
||||
return SendMediaReady(
|
||||
SendMediaType::ThemeFile,
|
||||
QString(), // filepath
|
||||
filename,
|
||||
content.size(),
|
||||
content,
|
||||
id,
|
||||
0,
|
||||
QString(),
|
||||
PeerId(),
|
||||
MTP_photoEmpty(MTP_long(0)),
|
||||
thumbnails,
|
||||
document,
|
||||
QByteArray(),
|
||||
0);
|
||||
}
|
||||
|
||||
Fn<void()> SaveTheme(
|
||||
not_null<Window::Controller*> window,
|
||||
const QByteArray &palette,
|
||||
const PreparedBackground &background,
|
||||
const QString &name,
|
||||
const QString &link,
|
||||
Fn<void()> done,
|
||||
Fn<void(SaveErrorType,QString)> fail) {
|
||||
using Storage::UploadedDocument;
|
||||
struct State {
|
||||
FullMsgId id;
|
||||
bool generating = false;
|
||||
mtpRequestId requestId = 0;
|
||||
rpl::lifetime lifetime;
|
||||
};
|
||||
|
||||
if (name.isEmpty()) {
|
||||
fail(SaveErrorType::Name, {});
|
||||
return nullptr;
|
||||
} else if (!IsGoodSlug(link)) {
|
||||
fail(SaveErrorType::Link, {});
|
||||
return nullptr;
|
||||
}
|
||||
const auto session = &window->account().session();
|
||||
const auto api = &session->api();
|
||||
const auto state = std::make_shared<State>();
|
||||
state->id = FullMsgId(
|
||||
0,
|
||||
session->data().nextLocalMessageId());
|
||||
|
||||
const auto createTheme = [=](const MTPDocument &data) {
|
||||
const auto document = session->data().processDocument(data);
|
||||
state->requestId = api->request(MTPaccount_CreateTheme(
|
||||
MTP_string(link),
|
||||
MTP_string(name),
|
||||
document->mtpInput()
|
||||
)).done([=](const MTPTheme &result) {
|
||||
done();
|
||||
}).fail([=](const RPCError &error) {
|
||||
fail(SaveErrorType::Other, error.type());
|
||||
}).send();
|
||||
};
|
||||
|
||||
const auto uploadTheme = [=](const UploadedDocument &data) {
|
||||
state->requestId = api->request(MTPaccount_UploadTheme(
|
||||
MTP_flags(0),
|
||||
data.file,
|
||||
MTPInputFile(), // thumb
|
||||
MTP_string(name + ".tdesktop-theme"), // #TODO themes
|
||||
MTP_string("application/x-tgtheme-tdesktop")
|
||||
)).done([=](const MTPDocument &result) {
|
||||
createTheme(result);
|
||||
}).fail([=](const RPCError &error) {
|
||||
fail(SaveErrorType::Other, error.type());
|
||||
}).send();
|
||||
};
|
||||
|
||||
const auto uploadFile = [=](const QByteArray &theme) {
|
||||
session->uploader().documentReady(
|
||||
) | rpl::filter([=](const UploadedDocument &data) {
|
||||
return data.fullId == state->id;
|
||||
}) | rpl::start_with_next([=](const UploadedDocument &data) {
|
||||
uploadTheme(data);
|
||||
}, state->lifetime);
|
||||
|
||||
session->uploader().uploadMedia(
|
||||
state->id,
|
||||
PrepareThemeMedia(name, theme));
|
||||
};
|
||||
|
||||
state->generating = true;
|
||||
crl::async([=] {
|
||||
crl::on_main([=, ready = PrepareTheme(palette, background)]{
|
||||
if (!state->generating) {
|
||||
return;
|
||||
}
|
||||
state->generating = false;
|
||||
uploadFile(ready);
|
||||
});
|
||||
});
|
||||
|
||||
return [=] {
|
||||
if (state->generating) {
|
||||
state->generating = false;
|
||||
} else {
|
||||
api->request(base::take(state->requestId)).cancel();
|
||||
session->uploader().cancel(state->id);
|
||||
state->lifetime.destroy();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void CreateBox(
|
||||
not_null<GenericBox*> box,
|
||||
not_null<Window::Controller*> window) {
|
||||
Expects(window->account().sessionExists());
|
||||
|
||||
box->setTitle(tr::lng_theme_editor_create_title(Ui::Text::WithEntities));
|
||||
|
||||
const auto name = box->addRow(object_ptr<Ui::InputField>(
|
||||
box,
|
||||
st::defaultInputField,
|
||||
tr::lng_theme_editor_name()));
|
||||
|
||||
box->addRow(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
tr::lng_theme_editor_create_description(),
|
||||
st::boxDividerLabel),
|
||||
style::margins(
|
||||
st::boxRowPadding.left(),
|
||||
st::boxRowPadding.left(),
|
||||
st::boxRowPadding.right(),
|
||||
st::boxRowPadding.right()));
|
||||
|
||||
box->addRow(
|
||||
object_ptr<Info::Profile::Button>(
|
||||
box,
|
||||
tr::lng_theme_editor_import_existing() | Ui::Text::ToUpper(),
|
||||
st::createThemeImportButton),
|
||||
style::margins()
|
||||
)->addClickHandler([=] {
|
||||
ImportFromFile(&window->account().session(), box);
|
||||
});
|
||||
|
||||
box->setFocusCallback([=] { name->setFocusFast(); });
|
||||
|
||||
box->addButton(tr::lng_box_done(), [=] {
|
||||
const auto title = name->getLastText().trimmed();
|
||||
if (title.isEmpty()) {
|
||||
name->showError();
|
||||
return;
|
||||
}
|
||||
box->closeBox();
|
||||
StartEditor(window, title);
|
||||
});
|
||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||
}
|
||||
|
||||
void SaveThemeBox(
|
||||
not_null<GenericBox*> box,
|
||||
not_null<Window::Controller*> window,
|
||||
const QByteArray &palette) {
|
||||
Expects(window->account().sessionExists());
|
||||
|
||||
const auto background = Background()->createCurrentImage();
|
||||
auto backgroundContent = QByteArray();
|
||||
const auto tiled = Background()->tile();
|
||||
{
|
||||
QBuffer buffer(&backgroundContent);
|
||||
background.save(&buffer, "JPG", 87);
|
||||
}
|
||||
|
||||
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()));
|
||||
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(qsl("link")),
|
||||
GenerateSlug(),
|
||||
true);
|
||||
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(
|
||||
Core::App().createInternalLink(qsl("addtheme/")));
|
||||
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::settingsSubsectionTitle),
|
||||
st::settingsSubsectionTitlePadding);
|
||||
const auto back = box->addRow(
|
||||
object_ptr<BackgroundSelector>(
|
||||
box,
|
||||
background,
|
||||
PreparedBackground{ backgroundContent, tiled }),
|
||||
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)(); });
|
||||
box->addButton(tr::lng_settings_save(), [=] {
|
||||
if (*saving) {
|
||||
return;
|
||||
}
|
||||
*saving = true;
|
||||
const auto done = crl::guard(box, [=] {
|
||||
box->closeBox();
|
||||
window->showRightColumn(nullptr);
|
||||
});
|
||||
const auto fail = crl::guard(box, [=](
|
||||
SaveErrorType type,
|
||||
const QString &text) {
|
||||
if (!text.isEmpty()) {
|
||||
Ui::Toast::Show(text);
|
||||
}
|
||||
if (type == SaveErrorType::Name) {
|
||||
name->showError();
|
||||
} else if (type == SaveErrorType::Link) {
|
||||
link->showError();
|
||||
}
|
||||
});
|
||||
*cancel = SaveTheme(
|
||||
window,
|
||||
palette,
|
||||
back->result(),
|
||||
name->getLastText().trimmed(),
|
||||
link->getLastText().trimmed(),
|
||||
done,
|
||||
fail);
|
||||
});
|
||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||
}
|
||||
|
||||
} // namespace Theme
|
||||
} // namespace Window
|
@ -7,16 +7,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
class GenericBox;
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
#include "boxes/generic_box.h"
|
||||
|
||||
namespace Window {
|
||||
|
||||
class Controller;
|
||||
|
||||
namespace Theme {
|
||||
|
||||
void CreateBox(not_null<GenericBox*> box, not_null<Main::Session*> session);
|
||||
void CreateBox(
|
||||
not_null<GenericBox*> box,
|
||||
not_null<Window::Controller*> window);
|
||||
void SaveThemeBox(
|
||||
not_null<GenericBox*> box,
|
||||
not_null<Window::Controller*> window,
|
||||
const QByteArray &palette);
|
||||
|
||||
} // namespace Theme
|
||||
} // namespace Window
|
@ -309,6 +309,21 @@ createThemeImportButton: InfoProfileButton {
|
||||
|
||||
ripple: defaultRippleAnimation;
|
||||
}
|
||||
createThemeLink: InputField(defaultInputField) {
|
||||
textMargins: margins(0px, 7px, 0px, 0px);
|
||||
textBg: boxBg;
|
||||
|
||||
placeholderFg: placeholderFg;
|
||||
placeholderFgActive: placeholderFgActive;
|
||||
placeholderFgError: placeholderFgActive;
|
||||
placeholderMargins: margins(0px, 0px, 0px, 0px);
|
||||
placeholderScale: 0.;
|
||||
placeholderFont: boxTextFont;
|
||||
|
||||
heightMin: 34px;
|
||||
|
||||
font: boxTextFont;
|
||||
}
|
||||
|
||||
// Mac specific
|
||||
|
||||
|
@ -43,7 +43,7 @@ void Controller::firstShow() {
|
||||
|
||||
void Controller::checkThemeEditor() {
|
||||
if (Window::Theme::Background()->isEditingTheme()) {
|
||||
_widget.showRightColumn(Box<Window::Theme::Editor>());
|
||||
showRightColumn(Box<Window::Theme::Editor>(this));
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,6 +74,10 @@ void Controller::showBox(
|
||||
_widget.ui_showBox(std::move(content), options, animated);
|
||||
}
|
||||
|
||||
void Controller::showRightColumn(object_ptr<TWidget> widget) {
|
||||
_widget.showRightColumn(std::move(widget));
|
||||
}
|
||||
|
||||
void Controller::activate() {
|
||||
_widget.activate();
|
||||
}
|
||||
|
@ -52,6 +52,8 @@ public:
|
||||
return result;
|
||||
}
|
||||
|
||||
void showRightColumn(object_ptr<TWidget> widget);
|
||||
|
||||
void activate();
|
||||
void reActivate();
|
||||
void updateIsActive(int timeout);
|
||||
|
@ -889,12 +889,12 @@
|
||||
<(src_loc)/window/window_top_bar_wrap.h
|
||||
<(src_loc)/window/themes/window_theme.cpp
|
||||
<(src_loc)/window/themes/window_theme.h
|
||||
<(src_loc)/window/themes/window_theme_create_box.cpp
|
||||
<(src_loc)/window/themes/window_theme_create_box.h
|
||||
<(src_loc)/window/themes/window_theme_editor.cpp
|
||||
<(src_loc)/window/themes/window_theme_editor.h
|
||||
<(src_loc)/window/themes/window_theme_editor_block.cpp
|
||||
<(src_loc)/window/themes/window_theme_editor_block.h
|
||||
<(src_loc)/window/themes/window_theme_editor_box.cpp
|
||||
<(src_loc)/window/themes/window_theme_editor_box.h
|
||||
<(src_loc)/window/themes/window_theme_preview.cpp
|
||||
<(src_loc)/window/themes/window_theme_preview.h
|
||||
<(src_loc)/window/themes/window_theme_warning.cpp
|
||||
|
Loading…
Reference in New Issue
Block a user