Closed beta 1000006001: Built in theme and color palette editor.

This commit is contained in:
John Preston 2017-02-03 23:07:26 +03:00
parent 60f45ab9b3
commit b842761ea3
95 changed files with 3870 additions and 477 deletions

View file

@ -275,3 +275,5 @@ notifyFadeRight: icon {{ "fade_horizontal", notificationBg }};
stickerIconLeft: icon {{ "fade_horizontal-flip_horizontal", emojiPanCategories }}; stickerIconLeft: icon {{ "fade_horizontal-flip_horizontal", emojiPanCategories }};
stickerIconRight: icon {{ "fade_horizontal", emojiPanCategories }}; stickerIconRight: icon {{ "fade_horizontal", emojiPanCategories }};
transparentPlaceholderSize: 4px;

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B

View file

@ -1023,6 +1023,22 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
"lng_confirm_phone_send" = "Send"; "lng_confirm_phone_send" = "Send";
"lng_confirm_phone_enter_code" = "Please enter the code."; "lng_confirm_phone_enter_code" = "Please enter the code.";
"lng_theme_editor_no_keys" = "No keys in the palette yet";
"lng_theme_editor_cant_change_theme" = "You can not apply themes while you edit a color palette. Please close the palette editor first.";
"lng_theme_editor_new_keys" = "Not in the palette yet";
"lng_theme_editor_background_image" = "Background image";
"lng_theme_editor_saved_to_jpg" = "Saved to JPEG, {size}";
"lng_theme_editor_read_from_jpg" = "JPEG image, {size}";
"lng_theme_editor_read_from_png" = "PNG image, {size}";
"lng_theme_editor_export" = "Export";
"lng_theme_editor_choose_image" = "Choose background image";
"lng_theme_editor_save_palette" = "Save palette file";
"lng_theme_editor_choose_name" = "Choose theme filename";
"lng_theme_editor_error" = "Editor encountered an error :( See 'log.txt' for details.";
"lng_theme_editor_done" = "Theme export was successfull!";
"lng_theme_editor_title" = "Edit color palette";
"lng_theme_editor_export_button" = "Export theme";
// Not used // Not used
"lng_topbar_info" = "Info"; "lng_topbar_info" = "Info";

View file

@ -6,7 +6,7 @@
<Identity Name="TelegramDesktop" <Identity Name="TelegramDesktop"
ProcessorArchitecture="x64" ProcessorArchitecture="x64"
Publisher="CN=Telegram Messenger LLP, O=Telegram Messenger LLP, L=London, C=GB" Publisher="CN=Telegram Messenger LLP, O=Telegram Messenger LLP, L=London, C=GB"
Version="1.0.6.0" /> Version="1.0.6.1" />
<Properties> <Properties>
<DisplayName>Telegram Desktop</DisplayName> <DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName> <PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
@ -31,7 +31,7 @@
Square44x44Logo="Assets\logo44\logo44.png" Square44x44Logo="Assets\logo44\logo44.png"
Description="Telegram Desktop official messenger" /> Description="Telegram Desktop official messenger" />
<Extensions> <Extensions>
<uap:Extension Category="windows.protocol"> <uap:Extension Category="windows.protocol" Executable="Telegram.exe">
<uap:Protocol Name="tg" /> <uap:Protocol Name="tg" />
</uap:Extension> </uap:Extension>
</Extensions> </Extensions>

View file

@ -34,8 +34,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,0,6,0 FILEVERSION 1,0,6,1
PRODUCTVERSION 1,0,6,0 PRODUCTVERSION 1,0,6,1
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
FILEFLAGS 0x1L FILEFLAGS 0x1L
@ -51,10 +51,10 @@ BEGIN
BLOCK "040904b0" BLOCK "040904b0"
BEGIN BEGIN
VALUE "CompanyName", "Telegram Messenger LLP" VALUE "CompanyName", "Telegram Messenger LLP"
VALUE "FileVersion", "1.0.6.0" VALUE "FileVersion", "1.0.6.1"
VALUE "LegalCopyright", "Copyright (C) 2014-2017" VALUE "LegalCopyright", "Copyright (C) 2014-2017"
VALUE "ProductName", "Telegram Desktop" VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "1.0.6.0" VALUE "ProductVersion", "1.0.6.1"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View file

@ -25,8 +25,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,0,6,0 FILEVERSION 1,0,6,1
PRODUCTVERSION 1,0,6,0 PRODUCTVERSION 1,0,6,1
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
FILEFLAGS 0x1L FILEFLAGS 0x1L
@ -43,10 +43,10 @@ BEGIN
BEGIN BEGIN
VALUE "CompanyName", "Telegram Messenger LLP" VALUE "CompanyName", "Telegram Messenger LLP"
VALUE "FileDescription", "Telegram Updater" VALUE "FileDescription", "Telegram Updater"
VALUE "FileVersion", "1.0.6.0" VALUE "FileVersion", "1.0.6.1"
VALUE "LegalCopyright", "Copyright (C) 2014-2017" VALUE "LegalCopyright", "Copyright (C) 2014-2017"
VALUE "ProductName", "Telegram Desktop" VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "1.0.6.0" VALUE "ProductVersion", "1.0.6.1"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View file

@ -30,7 +30,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "historywidget.h" #include "historywidget.h"
#include "localstorage.h" #include "localstorage.h"
#include "boxes/confirmbox.h" #include "boxes/confirmbox.h"
#include "window/window_theme.h" #include "window/themes/window_theme.h"
ApiWrap::ApiWrap(QObject *parent) : QObject(parent) ApiWrap::ApiWrap(QObject *parent) : QObject(parent)
, _messageDataResolveDelayed(new SingleDelayedCall(this, "resolveMessageDatas")) { , _messageDataResolveDelayed(new SingleDelayedCall(this, "resolveMessageDatas")) {

View file

@ -44,7 +44,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "apiwrap.h" #include "apiwrap.h"
#include "numbers.h" #include "numbers.h"
#include "observer_peer.h" #include "observer_peer.h"
#include "window/window_theme.h" #include "window/themes/window_theme.h"
#include "window/notifications_manager.h" #include "window/notifications_manager.h"
#include "platform/platform_notifications_manager.h" #include "platform/platform_notifications_manager.h"

View file

@ -34,7 +34,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "autoupdater.h" #include "autoupdater.h"
#include "core/observer.h" #include "core/observer.h"
#include "observer_peer.h" #include "observer_peer.h"
#include "window/window_theme.h" #include "window/themes/window_theme.h"
#include "media/player/media_player_instance.h" #include "media/player/media_player_instance.h"
#include "window/notifications_manager.h" #include "window/notifications_manager.h"
#include "history/history_location_manager.h" #include "history/history_location_manager.h"

View file

@ -252,16 +252,14 @@ void AbstractBox::paintEvent(QPaintEvent *e) {
void AbstractBox::paintTitle(Painter &p, const QString &title, const QString &additional) { void AbstractBox::paintTitle(Painter &p, const QString &title, const QString &additional) {
p.setFont(st::boxTitleFont); p.setFont(st::boxTitleFont);
p.setPen(st::boxTitleFg); p.setPen(st::boxTitleFg);
if (_layerType) { auto titleWidth = st::boxTitleFont->width(title);
auto titleWidth = st::boxTitleFont->width(title); auto titleLeft = _layerType ? st::boxLayerTitlePosition.x() : st::boxTitlePosition.x();
p.drawTextLeft(st::boxLayerTitlePosition.x(), st::boxLayerTitlePosition.y(), width(), title, titleWidth); auto titleTop = _layerType ? st::boxLayerTitlePosition.y() : st::boxTitlePosition.y();
if (!additional.isEmpty()) { p.drawTextLeft(titleLeft, titleTop, width(), title, titleWidth);
p.setFont(st::boxLayerTitleAdditionalFont); if (!additional.isEmpty()) {
p.setPen(st::boxTitleAdditionalFg); p.setFont(st::boxLayerTitleAdditionalFont);
p.drawTextLeft(st::boxLayerTitlePosition.x() + titleWidth + st::boxLayerTitleAdditionalSkip, st::boxLayerTitlePosition.y() + st::boxTitleFont->ascent - st::boxLayerTitleAdditionalFont->ascent, width(), additional); p.setPen(st::boxTitleAdditionalFg);
} p.drawTextLeft(titleLeft + titleWidth + st::boxLayerTitleAdditionalSkip, titleTop + st::boxTitleFont->ascent - st::boxLayerTitleAdditionalFont->ascent, width(), additional);
} else {
p.drawTextLeft(st::boxTitlePosition.x(), st::boxTitlePosition.y(), width(), title);
} }
} }

View file

@ -246,12 +246,6 @@ private:
}; };
template <typename BoxType, typename ...Args>
inline object_ptr<BoxType> Box(Args&&... args) {
auto parent = static_cast<QWidget*>(nullptr);
return object_ptr<BoxType>(parent, std_::forward<Args>(args)...);
}
enum CreatingGroupType { enum CreatingGroupType {
CreatingGroupNone, CreatingGroupNone,
CreatingGroupGroup, CreatingGroupGroup,

View file

@ -542,10 +542,7 @@ void SetupChannelBox::mouseMoveEvent(QMouseEvent *e) {
void SetupChannelBox::mousePressEvent(QMouseEvent *e) { void SetupChannelBox::mousePressEvent(QMouseEvent *e) {
if (_linkOver) { if (_linkOver) {
Application::clipboard()->setText(_channel->inviteLink()); Application::clipboard()->setText(_channel->inviteLink());
Ui::Toast::Show(lang(lng_create_channel_link_copied));
Ui::Toast::Config toast;
toast.text = lang(lng_create_channel_link_copied);
Ui::Toast::Show(App::wnd(), toast);
} }
} }

View file

@ -24,7 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "lang.h" #include "lang.h"
#include "mainwidget.h" #include "mainwidget.h"
#include "mainwindow.h" #include "mainwindow.h"
#include "window/window_theme.h" #include "window/themes/window_theme.h"
#include "styles/style_overview.h" #include "styles/style_overview.h"
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
#include "ui/effects/round_checkbox.h" #include "ui/effects/round_checkbox.h"

View file

@ -530,3 +530,23 @@ usernameTextStyle: TextStyle(passcodeTextStyle) {
usernameDefaultFg: windowSubTextFg; usernameDefaultFg: windowSubTextFg;
downloadPathSkip: 10px; downloadPathSkip: 10px;
colorEditWidth: 390px;
colorEditSkip: 10px;
colorPickerSize: 256px;
colorPickerMarkRadius: 6px;
colorPickerMarkLine: 1px;
colorSliderSkip: 8px;
colorSliderArrowLeft: icon {{ "color_slider_arrow", sliderBgActive }};
colorSliderArrowRight: icon {{ "color_slider_arrow-flip_horizontal", sliderBgActive }};
colorSliderArrowTop: icon {{ "color_slider_arrow_vertical", sliderBgActive }};
colorSliderArrowBottom: icon {{ "color_slider_arrow_vertical-flip_vertical", sliderBgActive }};
colorSliderWidth: 19px;
colorSampleSize: size(60px, 34px);
colorFieldSkip: 13px;
colorValueInput: InputField(defaultInputField) {
textMargins: margins(16px, 3px, 0px, 2px);
heightMin: 27px;
}
colorResultInput: InputField(colorValueInput) {
}

View file

@ -241,10 +241,7 @@ void MaxInviteBox::mousePressEvent(QMouseEvent *e) {
mouseMoveEvent(e); mouseMoveEvent(e);
if (_linkOver) { if (_linkOver) {
Application::clipboard()->setText(_link); Application::clipboard()->setText(_link);
Ui::Toast::Show(lang(lng_create_channel_link_copied));
Ui::Toast::Config toast;
toast.text = lang(lng_create_channel_link_copied);
Ui::Toast::Show(App::wnd(), toast);
} }
} }

View file

@ -40,7 +40,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "ui/effects/ripple_animation.h" #include "ui/effects/ripple_animation.h"
#include "boxes/photocropbox.h" #include "boxes/photocropbox.h"
#include "boxes/confirmbox.h" #include "boxes/confirmbox.h"
#include "window/window_theme.h" #include "window/themes/window_theme.h"
#include "observer_peer.h" #include "observer_peer.h"
#include "apiwrap.h" #include "apiwrap.h"

View file

@ -0,0 +1,908 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "stdafx.h"
#include "boxes/editcolorbox.h"
#include "lang.h"
#include "styles/style_boxes.h"
#include "ui/widgets/shadow.h"
#include "styles/style_mediaview.h"
#include "ui/widgets/input_fields.h"
class EditColorBox::Picker : public TWidget {
public:
Picker(QWidget *parent, QColor color);
float64 valueX() const {
return _x;
}
float64 valueY() const {
return _y;
}
base::Observable<void> &changed() {
return _changed;
}
void setHSV(int hue, int saturation, int brightness);
void setRGB(int red, int green, int blue);
protected:
void paintEvent(QPaintEvent *e);
void mousePressEvent(QMouseEvent *e);
void mouseMoveEvent(QMouseEvent *e);
void mouseReleaseEvent(QMouseEvent *e);
private:
void setFromColor(QColor color);
QCursor generateCursor();
void preparePalette();
void updateCurrentPoint(QPoint localPosition);
QColor _topleft;
QColor _topright;
QColor _bottomleft;
QColor _bottomright;
QImage _palette;
bool _paletteInvalidated = false;
float64 _x = 0.;
float64 _y = 0.;
bool _choosing = false;
base::Observable<void> _changed;
};
QCursor EditColorBox::Picker::generateCursor() {
auto diameter = convertScale(16);
auto line = convertScale(1);
auto size = ((diameter + 2 * line) >= 32) ? 64 : 32;
auto cursor = QImage(QSize(size, size) * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
cursor.setDevicePixelRatio(cRetinaFactor());
cursor.fill(Qt::transparent);
{
Painter p(&cursor);
PainterHighQualityEnabler hq(p);
p.setBrush(Qt::NoBrush);
auto pen = QPen(Qt::white);
pen.setWidth(3 * line);
p.setPen(pen);
p.drawEllipse((size - diameter) / 2, (size - diameter) / 2, diameter, diameter);
pen = QPen(Qt::black);
pen.setWidth(line);
p.setPen(pen);
p.drawEllipse((size - diameter) / 2, (size - diameter) / 2, diameter, diameter);
}
return QCursor(QPixmap::fromImage(cursor));
}
EditColorBox::Picker::Picker(QWidget *parent, QColor color) : TWidget(parent) {
setCursor(generateCursor());
auto size = QSize(st::colorPickerSize, st::colorPickerSize);
resize(size);
_palette = QImage(size * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
setFromColor(color);
}
void EditColorBox::Picker::paintEvent(QPaintEvent *e) {
Painter p(this);
preparePalette();
p.drawImage(0, 0, _palette);
auto left = anim::color(_topleft, _bottomleft, _y);
auto right = anim::color(_topright, _bottomright, _y);
auto color = anim::color(left, right, _x);
auto lightness = 0.2989 * color.redF() + 0.5870 * color.greenF() + 0.1140 * color.blueF();
auto pen = QPen((lightness > 0.6) ? QColor(0, 0, 0) : QColor(255, 255, 255));
pen.setWidth(st::colorPickerMarkLine);
p.setPen(pen);
p.setBrush(Qt::NoBrush);
auto x = anim::interpolate(0, width() - 1, _x);
auto y = anim::interpolate(0, height() - 1, _y);
PainterHighQualityEnabler hq(p);
p.drawEllipse(QRect(x - st::colorPickerMarkRadius, y - st::colorPickerMarkRadius, 2 * st::colorPickerMarkRadius, 2 * st::colorPickerMarkRadius));
}
void EditColorBox::Picker::mousePressEvent(QMouseEvent *e) {
_choosing = true;
updateCurrentPoint(e->pos());
}
void EditColorBox::Picker::mouseMoveEvent(QMouseEvent *e) {
if (_choosing) {
updateCurrentPoint(e->pos());
}
}
void EditColorBox::Picker::mouseReleaseEvent(QMouseEvent *e) {
_choosing = false;
}
void EditColorBox::Picker::preparePalette() {
if (!_paletteInvalidated) return;
_paletteInvalidated = false;
auto size = _palette.width();
auto ints = reinterpret_cast<uint32*>(_palette.bits());
auto intsAddPerLine = (_palette.bytesPerLine() - size * sizeof(uint32)) / sizeof(uint32);
constexpr auto Large = 1024 * 1024;
constexpr auto LargeBit = 20; // n / Large == (n >> LargeBit)
auto part = Large / size;
auto topleft = anim::shifted(_topleft);
auto topright = anim::shifted(_topright);
auto bottomleft = anim::shifted(_bottomleft);
auto bottomright = anim::shifted(_bottomright);
auto y_accumulated = 0;
for (auto y = 0; y != size; ++y, y_accumulated += part) {
auto y_ratio = y_accumulated >> (LargeBit - 8); // (y_accumulated * 256) / Large;
// 0 <= y_accumulated < Large
// 0 <= y_ratio < 256
auto top_ratio = 255 - y_ratio;
auto bottom_ratio = y_ratio;
auto left = anim::reshifted(bottomleft * bottom_ratio + topleft * top_ratio);
auto right = anim::reshifted(bottomright * bottom_ratio + topright * top_ratio);
auto x_accumulated = 0;
for (auto x = 0; x != size; ++x, x_accumulated += part) {
auto x_ratio = x_accumulated >> (LargeBit - 8); // (x_accumulated * 256) / Large;
// 0 <= x_accumulated < Large
// 0 <= x_ratio < 256
auto left_ratio = 255 - x_ratio;
auto right_ratio = x_ratio;
*ints++ = anim::unshifted(left * left_ratio + right * right_ratio);
}
ints += intsAddPerLine;
}
}
void EditColorBox::Picker::updateCurrentPoint(QPoint localPosition) {
auto x = snap(localPosition.x(), 0, width()) / float64(width());
auto y = snap(localPosition.y(), 0, height()) / float64(height());
if (_x != x || _y != y) {
_x = x;
_y = y;
update();
_changed.notify();
}
}
void EditColorBox::Picker::setHSV(int hue, int saturation, int brightness) {
_topleft = QColor(255, 255, 255);
_topright.setHsv(qMax(0, hue), 255, 255);
_topright = _topright.toRgb();
_bottomleft = _bottomright = QColor(0, 0, 0);
_paletteInvalidated = true;
update();
_x = snap(saturation / 255., 0., 1.);
_y = 1. - snap(brightness / 255., 0., 1.);
}
void EditColorBox::Picker::setRGB(int red, int green, int blue) {
setFromColor(QColor(red, green, blue));
}
void EditColorBox::Picker::setFromColor(QColor color) {
setHSV(color.hsvHue(), color.hsvSaturation(), color.value());
}
class EditColorBox::Slider : public TWidget {
public:
enum class Direction {
Horizontal,
Vertical,
};
enum class Type {
Hue,
Opacity,
};
Slider(QWidget *parent, Direction direction, Type type, QColor color);
base::Observable<void> &changed() {
return _changed;
}
float64 value() const {
return _value;
}
void setValue(float64 value) {
_value = snap(value, 0., 1.);
update();
}
void setHSV(int hue, int saturation, int brightness);
void setRGB(int red, int green, int blue);
void setAlpha(int alpha);
protected:
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
private:
float64 valueFromColor(QColor color) const;
float64 valueFromHue(int hue) const;
bool isHorizontal() const {
return (_direction == Direction::Horizontal);
}
void colorUpdated();
void prepareMinSize();
void generatePixmap();
void updatePixmapFromMask();
void updateCurrentPoint(QPoint localPosition);
Direction _direction = Direction::Horizontal;
Type _type = Type::Hue;
QColor _color;
float64 _value = 0;
QImage _mask;
QPixmap _pixmap;
QBrush _transparent;
bool _choosing = false;
base::Observable<void> _changed;
};
EditColorBox::Slider::Slider(QWidget *parent, Direction direction, Type type, QColor color) : TWidget(parent)
, _direction(direction)
, _type(type)
, _color(color.red(), color.green(), color.blue())
, _value(valueFromColor(color))
, _transparent((_type == Type::Hue) ? QBrush() : style::transparentPlaceholderBrush()) {
prepareMinSize();
}
void EditColorBox::Slider::prepareMinSize() {
auto minSize = st::colorSliderSkip + st::colorSliderWidth + st::colorSliderSkip;
resize(minSize, minSize);
}
void EditColorBox::Slider::paintEvent(QPaintEvent *e) {
Painter p(this);
auto to = rect().marginsRemoved(QMargins(st::colorSliderSkip, st::colorSliderSkip, st::colorSliderSkip, st::colorSliderSkip));
Ui::Shadow::paint(p, to, width(), st::defaultRoundShadow);
if (_type == Type::Opacity) {
p.fillRect(to, _transparent);
}
p.drawPixmap(to, _pixmap, _pixmap.rect());
if (isHorizontal()) {
auto x = st::colorSliderSkip + qRound(_value * to.width());
st::colorSliderArrowTop.paint(p, x - st::colorSliderArrowTop.width() / 2, 0, width());
st::colorSliderArrowBottom.paint(p, x - st::colorSliderArrowBottom.width() / 2, height() - st::colorSliderArrowBottom.height(), width());
} else {
auto y = st::colorSliderSkip + qRound(_value * to.height());
st::colorSliderArrowLeft.paint(p, 0, y - st::colorSliderArrowLeft.height() / 2, width());
st::colorSliderArrowRight.paint(p, width() - st::colorSliderArrowRight.width(), y - st::colorSliderArrowRight.height() / 2, width());
}
}
void EditColorBox::Slider::resizeEvent(QResizeEvent *e) {
generatePixmap();
update();
}
void EditColorBox::Slider::mousePressEvent(QMouseEvent *e) {
_choosing = true;
updateCurrentPoint(e->pos());
}
void EditColorBox::Slider::mouseMoveEvent(QMouseEvent *e) {
if (_choosing) {
updateCurrentPoint(e->pos());
}
}
void EditColorBox::Slider::mouseReleaseEvent(QMouseEvent *e) {
_choosing = false;
}
void EditColorBox::Slider::generatePixmap() {
auto size = (isHorizontal() ? width() : height()) * cIntRetinaFactor();
auto image = QImage(size, cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
image.setDevicePixelRatio(cRetinaFactor());
auto ints = reinterpret_cast<uint32*>(image.bits());
auto intsPerLine = image.bytesPerLine() / sizeof(uint32);
auto intsPerLineAdded = intsPerLine - size;
constexpr auto Large = 1024 * 1024;
constexpr auto LargeBit = 20; // n / Large == (n >> LargeBit)
auto part = Large / size;
if (_type == Type::Hue) {
QColor color;
for (auto x = 0; x != size; ++x) {
color.setHsv(x * 360 / size, 255, 255);
auto value = anim::getPremultiplied(color.toRgb());
for (auto y = 0; y != cIntRetinaFactor(); ++y) {
ints[y * intsPerLine] = value;
}
++ints;
}
if (!isHorizontal()) {
image = std_::move(image).transformed(QTransform(0, -1, 1, 0, 0, 0));
}
_pixmap = App::pixmapFromImageInPlace(std_::move(image));
} else {
auto color = anim::shifted(QColor(255, 255, 255, 255));
auto transparent = anim::shifted(QColor(255, 255, 255, 0));
for (auto y = 0; y != cIntRetinaFactor(); ++y) {
auto x_accumulated = 0;
for (auto x = 0; x != size; ++x, x_accumulated += part) {
auto x_ratio = x_accumulated >> (LargeBit - 8);
// 0 <= x_accumulated < Large
// 0 <= x_ratio < 256
*ints++ = anim::unshifted(color * x_ratio + transparent * (255 - x_ratio));
}
ints += intsPerLineAdded;
}
if (!isHorizontal()) {
image = std_::move(image).transformed(QTransform(0, -1, 1, 0, 0, 0));
}
_mask = std_::move(image);
updatePixmapFromMask();
}
}
void EditColorBox::Slider::setHSV(int hue, int saturation, int brightness) {
if (_type == Type::Hue) {
// hue == 360 converts to 0 if done in general way
_value = valueFromHue(hue);
update();
} else {
_color.setHsv(hue, saturation, brightness);
colorUpdated();
}
}
void EditColorBox::Slider::setRGB(int red, int green, int blue) {
_color.setRgb(red, green, blue);
colorUpdated();
}
void EditColorBox::Slider::colorUpdated() {
if (_type == Type::Hue) {
_value = valueFromColor(_color);
} else if (!_mask.isNull()) {
updatePixmapFromMask();
}
update();
}
float64 EditColorBox::Slider::valueFromColor(QColor color) const {
return (_type == Type::Hue) ? valueFromHue(color.hsvHue()) : color.alphaF();
}
float64 EditColorBox::Slider::valueFromHue(int hue) const {
return (1. - snap(hue, 0, 360) / 360.);
}
void EditColorBox::Slider::setAlpha(int alpha) {
if (_type == Type::Opacity) {
_value = snap(alpha, 0, 255) / 255.;
update();
}
}
void EditColorBox::Slider::updatePixmapFromMask() {
_pixmap = App::pixmapFromImageInPlace(style::colorizeImage(_mask, _color));
}
void EditColorBox::Slider::updateCurrentPoint(QPoint localPosition) {
auto coord = (isHorizontal() ? localPosition.x() : localPosition.y()) - st::colorSliderSkip;
auto maximum = (isHorizontal() ? width() : height()) - 2 * st::colorSliderSkip;
auto value = snap(coord, 0, maximum) / float64(maximum);
if (_value != value) {
_value = value;
update();
_changed.notify();
}
}
class EditColorBox::Field : public Ui::MaskedInputField {
public:
Field(QWidget *parent, const style::InputField &st, const QString &placeholder, int limit, const QString &units = QString());
int value() const {
return getLastText().toInt();
}
void setTextWithFocus(const QString &text) {
setText(text);
if (hasFocus()) {
selectAll();
}
}
protected:
void correctValue(const QString &was, int wasCursor, QString &now, int &nowCursor) override;
void paintAdditionalPlaceholder(Painter &p, TimeMs ms) override;
void wheelEvent(QWheelEvent *e) override;
void keyPressEvent(QKeyEvent *e) override;
private:
void changeValue(int delta);
QString _placeholder, _units;
int _limit = 0;
int _digitLimit = 1;
int _wheelDelta = 0;
};
EditColorBox::Field::Field(QWidget *parent, const style::InputField &st, const QString &placeholder, int limit, const QString &units) : Ui::MaskedInputField(parent, st)
, _placeholder(placeholder)
, _units(units)
, _limit(limit)
, _digitLimit(QString::number(_limit).size()) {
}
void EditColorBox::Field::correctValue(const QString &was, int wasCursor, QString &now, int &nowCursor) {
QString newText;
int oldPos(nowCursor), newPos(-1), oldLen(now.length());
newText.reserve(oldLen);
for (int i = 0; i < oldLen; ++i) {
if (i == oldPos) {
newPos = newText.length();
}
QChar ch(now[i]);
if (ch.isDigit()) {
newText += ch;
}
if (newText.size() >= _digitLimit) {
break;
}
}
if (newPos < 0 || newPos > newText.size()) {
newPos = newText.size();
}
if (newText.toInt() > _limit) {
newText = QString::number(_limit);
newPos = newText.size();
}
if (newText != now) {
now = newText;
setText(now);
startPlaceholderAnimation();
nowCursor = -1;
}
if (newPos != nowCursor) {
nowCursor = newPos;
setCursorPosition(nowCursor);
}
}
void EditColorBox::Field::paintAdditionalPlaceholder(Painter &p, TimeMs ms) {
p.setFont(_st.font);
p.setPen(_st.placeholderFg);
auto inner = QRect(_st.textMargins.right(), _st.textMargins.top(), width() - 2 * _st.textMargins.right(), height() - _st.textMargins.top() - _st.textMargins.bottom());
p.drawText(inner, _placeholder, style::al_topleft);
if (!_units.isEmpty()) {
p.drawText(inner, _units, style::al_topright);
}
}
void EditColorBox::Field::wheelEvent(QWheelEvent *e) {
if (!hasFocus()) {
return;
}
auto deltaX = e->angleDelta().x(), deltaY = e->angleDelta().y();
if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) {
deltaY *= -1;
} else {
deltaX *= -1;
}
_wheelDelta += (qAbs(deltaX) > qAbs(deltaY)) ? deltaX : deltaY;
constexpr auto step = 5;
if (auto delta = _wheelDelta / step) {
_wheelDelta -= delta * step;
changeValue(delta);
}
}
void EditColorBox::Field::changeValue(int delta) {
auto currentValue = value();
auto newValue = snap(currentValue + delta, 0, _limit);
if (newValue != currentValue) {
setText(QString::number(newValue));
setFocus();
selectAll();
emit changed();
}
}
void EditColorBox::Field::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Up) {
changeValue(1);
} else if (e->key() == Qt::Key_Down) {
changeValue(-1);
} else {
MaskedInputField::keyPressEvent(e);
}
}
class EditColorBox::ResultField : public Ui::MaskedInputField {
public:
ResultField(QWidget *parent, const style::InputField &st);
void setTextWithFocus(const QString &text) {
setText(text);
if (hasFocus()) {
selectAll();
}
}
protected:
void correctValue(const QString &was, int wasCursor, QString &now, int &nowCursor) override;
void paintAdditionalPlaceholder(Painter &p, TimeMs ms) override;
};
EditColorBox::ResultField::ResultField(QWidget *parent, const style::InputField &st) : Ui::MaskedInputField(parent, st) {
}
void EditColorBox::ResultField::correctValue(const QString &was, int wasCursor, QString &now, int &nowCursor) {
QString newText;
int oldPos(nowCursor), newPos(-1), oldLen(now.length());
newText.reserve(oldLen);
for (int i = 0; i < oldLen; ++i) {
if (i == oldPos) {
newPos = newText.length();
}
QChar ch(now[i]);
auto code = ch.unicode();
if ((code >= '0' && code <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')) {
newText += ch;
}
if (newText.size() >= 8) {
break;
}
}
if (newPos < 0 || newPos > newText.size()) {
newPos = newText.size();
}
if (newText != now) {
now = newText;
setText(now);
startPlaceholderAnimation();
nowCursor = -1;
}
if (newPos != nowCursor) {
nowCursor = newPos;
setCursorPosition(nowCursor);
}
}
void EditColorBox::ResultField::paintAdditionalPlaceholder(Painter &p, TimeMs ms) {
p.setFont(_st.font);
p.setPen(_st.placeholderFg);
p.drawText(QRect(_st.textMargins.right(), _st.textMargins.top(), width(), height() - _st.textMargins.top() - _st.textMargins.bottom()), "#", style::al_topleft);
}
EditColorBox::EditColorBox(QWidget*, const QString &title, QColor current) : BoxContent()
, _title(title)
, _picker(this, current)
, _hueSlider(this, Slider::Direction::Vertical, Slider::Type::Hue, current)
, _opacitySlider(this, Slider::Direction::Horizontal, Slider::Type::Opacity, current)
, _hueField(this, st::colorValueInput, "H", 360, QString() + QChar(176)) // degree character
, _saturationField(this, st::colorValueInput, "S", 100, "%")
, _brightnessField(this, st::colorValueInput, "B", 100, "%")
, _redField(this, st::colorValueInput, "R", 255)
, _greenField(this, st::colorValueInput, "G", 255)
, _blueField(this, st::colorValueInput, "B", 255)
, _result(this, st::colorResultInput)
, _transparent(style::transparentPlaceholderBrush())
, _current(current)
, _new(current) {
}
void EditColorBox::prepare() {
setTitle(_title);
connect(_hueField, SIGNAL(changed()), this, SLOT(onFieldChanged()));
connect(_saturationField, SIGNAL(changed()), this, SLOT(onFieldChanged()));
connect(_brightnessField, SIGNAL(changed()), this, SLOT(onFieldChanged()));
connect(_redField, SIGNAL(changed()), this, SLOT(onFieldChanged()));
connect(_greenField, SIGNAL(changed()), this, SLOT(onFieldChanged()));
connect(_blueField, SIGNAL(changed()), this, SLOT(onFieldChanged()));
connect(_result, SIGNAL(changed()), this, SLOT(onFieldChanged()));
connect(_hueField, SIGNAL(submitted(bool)), this, SLOT(onFieldSubmitted()));
connect(_saturationField, SIGNAL(submitted(bool)), this, SLOT(onFieldSubmitted()));
connect(_brightnessField, SIGNAL(submitted(bool)), this, SLOT(onFieldSubmitted()));
connect(_redField, SIGNAL(submitted(bool)), this, SLOT(onFieldSubmitted()));
connect(_greenField, SIGNAL(submitted(bool)), this, SLOT(onFieldSubmitted()));
connect(_blueField, SIGNAL(submitted(bool)), this, SLOT(onFieldSubmitted()));
connect(_result, SIGNAL(submitted(bool)), this, SLOT(onFieldSubmitted()));
addButton(lang(lng_settings_save), [this] { saveColor(); });
addButton(lang(lng_cancel), [this] { closeBox(); });
auto height = st::colorEditSkip + st::colorPickerSize + st::colorEditSkip + st::colorSliderWidth + st::colorEditSkip;
setDimensions(st::colorEditWidth, height);
subscribe(_picker->changed(), [this] { updateFromControls(); });
subscribe(_hueSlider->changed(), [this] { updateFromControls(); });
subscribe(_opacitySlider->changed(), [this] { updateFromControls(); });
updateFromControls();
}
void EditColorBox::setInnerFocus() {
_result->setFocus();
_result->selectAll();
}
void EditColorBox::onFieldChanged() {
auto emitter = sender();
auto checkHSVSender = [this, emitter](QObject *field) {
if (emitter == field) {
updateFromHSVFields();
}
};
auto checkRGBSender = [this, emitter](QObject *field) {
if (emitter == field) {
updateFromRGBFields();
}
};
checkHSVSender(_hueField);
checkHSVSender(_saturationField);
checkHSVSender(_brightnessField);
checkRGBSender(_redField);
checkRGBSender(_greenField);
checkRGBSender(_blueField);
if (emitter == _result) {
updateFromResultField();
}
}
void EditColorBox::onFieldSubmitted() {
Ui::MaskedInputField *fields[] = {
_hueField,
_saturationField,
_brightnessField,
_redField,
_greenField,
_blueField,
_result
};
for (auto i = 0, count = int(base::array_size(fields)); i + 1 != count; ++i) {
if (fields[i]->hasFocus()) {
fields[i + 1]->setFocus();
fields[i + 1]->selectAll();
return;
}
}
if (_result->hasFocus()) {
saveColor();
}
}
void EditColorBox::saveColor() {
_cancelCallback = base::lambda<void()>();
if (_saveCallback) {
_saveCallback(_new.toRgb());
}
closeBox();
}
void EditColorBox::updateHSVFields() {
auto hue = qRound((1. - _hueSlider->value()) * 360);
auto saturation = qRound(_picker->valueX() * 255);
auto brightness = qRound((1. - _picker->valueY()) * 255);
auto alpha = qRound(_opacitySlider->value() * 255);
_hueField->setTextWithFocus(QString::number(hue));
_saturationField->setTextWithFocus(QString::number(percentFromByte(saturation)));
_brightnessField->setTextWithFocus(QString::number(percentFromByte(brightness)));
}
void EditColorBox::updateRGBFields() {
_redField->setTextWithFocus(QString::number(_new.red()));
_greenField->setTextWithFocus(QString::number(_new.green()));
_blueField->setTextWithFocus(QString::number(_new.blue()));
}
void EditColorBox::updateResultField() {
auto text = QString();
auto addHex = [&text](int value) {
if (value >= 0 && value <= 9) {
text.append('0' + value);
} else if (value >= 10 && value <= 15) {
text.append('a' + (value - 10));
}
};
auto addValue = [addHex](int value) {
addHex(value / 16);
addHex(value % 16);
};
addValue(_new.red());
addValue(_new.green());
addValue(_new.blue());
if (_new.alpha() != 255) {
addValue(_new.alpha());
}
_result->setTextWithFocus(text);
}
void EditColorBox::resizeEvent(QResizeEvent *e) {
auto fullwidth = _picker->width() + 2 * (st::colorEditSkip - st::colorSliderSkip) + _hueSlider->width() + st::colorSampleSize.width();
auto left = (width() - fullwidth) / 2;
_picker->moveToLeft(left, st::colorEditSkip);
_hueSlider->setGeometryToLeft(_picker->x() + _picker->width() + st::colorEditSkip - st::colorSliderSkip, st::colorEditSkip - st::colorSliderSkip, _hueSlider->width(), st::colorPickerSize + 2 * st::colorSliderSkip);
_opacitySlider->setGeometryToLeft(_picker->x() - st::colorSliderSkip, _picker->y() + _picker->height() + st::colorEditSkip - st::colorSliderSkip, _picker->width() + 2 * st::colorSliderSkip, _opacitySlider->height());
auto fieldLeft = _hueSlider->x() + _hueSlider->width() - st::colorSliderSkip + st::colorEditSkip;
auto fieldWidth = st::colorSampleSize.width();
auto fieldHeight = _hueField->height();
_newRect = QRect(fieldLeft, st::colorEditSkip, fieldWidth, st::colorSampleSize.height());
_currentRect = _newRect.translated(0, st::colorSampleSize.height());
_hueField->setGeometryToLeft(fieldLeft, _currentRect.y() + _currentRect.height() + st::colorFieldSkip, fieldWidth, fieldHeight);
_saturationField->setGeometryToLeft(fieldLeft, _hueField->y() + _hueField->height(), fieldWidth, fieldHeight);
_brightnessField->setGeometryToLeft(fieldLeft, _saturationField->y() + _saturationField->height(), fieldWidth, fieldHeight);
_redField->setGeometryToLeft(fieldLeft, _brightnessField->y() + _brightnessField->height() + st::colorFieldSkip, fieldWidth, fieldHeight);
_greenField->setGeometryToLeft(fieldLeft, _redField->y() + _redField->height(), fieldWidth, fieldHeight);
_blueField->setGeometryToLeft(fieldLeft, _greenField->y() + _greenField->height(), fieldWidth, fieldHeight);
_result->setGeometryToLeft(fieldLeft - (st::colorEditSkip + st::colorSliderWidth), _opacitySlider->y() + _opacitySlider->height() - st::colorSliderSkip - _result->height(), fieldWidth + (st::colorEditSkip + st::colorSliderWidth), fieldHeight);
}
void EditColorBox::paintEvent(QPaintEvent *e) {
BoxContent::paintEvent(e);
Painter p(this);
Ui::Shadow::paint(p, _picker->geometry(), width(), st::defaultRoundShadow);
Ui::Shadow::paint(p, QRect(_newRect.x(), _newRect.y(), _newRect.width(), _newRect.height() + _currentRect.height()), width(), st::defaultRoundShadow);
if (_new.alphaF() < 1.) {
p.fillRect(myrtlrect(_newRect), _transparent);
}
p.fillRect(myrtlrect(_newRect), _new);
if (_current.alphaF() < 1.) {
p.fillRect(myrtlrect(_currentRect), _transparent);
}
p.fillRect(myrtlrect(_currentRect), _current);
}
void EditColorBox::mousePressEvent(QMouseEvent *e) {
if (myrtlrect(_currentRect).contains(e->pos())) {
updateFromColor(_current);
}
}
void EditColorBox::updateFromColor(QColor color) {
_new = color;
updateControlsFromColor();
updateRGBFields();
updateHSVFields();
updateResultField();
update();
}
void EditColorBox::updateFromControls() {
auto hue = qRound((1. - _hueSlider->value()) * 360);
auto saturation = qRound(_picker->valueX() * 255);
auto brightness = qRound((1. - _picker->valueY()) * 255);
auto alpha = qRound(_opacitySlider->value() * 255);
setHSV(hue, saturation, brightness, alpha);
updateHSVFields();
updateControlsFromHSV(hue, saturation, brightness);
}
void EditColorBox::updateFromHSVFields() {
auto hue = _hueField->value();
auto saturation = percentToByte(_saturationField->value());
auto brightness = percentToByte(_brightnessField->value());
auto alpha = qRound(_opacitySlider->value() * 255);
setHSV(hue, saturation, brightness, alpha);
updateControlsFromHSV(hue, saturation, brightness);
}
void EditColorBox::updateFromRGBFields() {
auto red = _redField->value();
auto blue = _blueField->value();
auto green = _greenField->value();
auto alpha = qRound(_opacitySlider->value() * 255);
setRGB(red, green, blue, alpha);
updateResultField();
}
void EditColorBox::updateFromResultField() {
auto text = _result->getLastText();
if (text.size() != 6 && text.size() != 8) {
return;
}
auto fromHex = [](QChar hex) {
auto code = hex.unicode();
if (code >= 'A' && code <= 'F') {
return (code - 'A' + 10);
} else if (code >= 'a' && code <= 'f') {
return (code - 'a' + 10);
}
return code - '0';
};
auto fromChars = [fromHex](QChar a, QChar b) {
return fromHex(a) * 0x10 + fromHex(b);
};
auto red = fromChars(text[0], text[1]);
auto green = fromChars(text[2], text[3]);
auto blue = fromChars(text[4], text[5]);
auto alpha = (text.size() == 8) ? fromChars(text[6], text[7]) : 255;
setRGB(red, green, blue, alpha);
updateRGBFields();
}
void EditColorBox::updateControlsFromHSV(int hue, int saturation, int brightness) {
_picker->setHSV(hue, saturation, brightness);
_hueSlider->setHSV(hue, saturation, brightness);
_opacitySlider->setHSV(hue, saturation, brightness);
}
void EditColorBox::updateControlsFromColor() {
auto red = _new.red();
auto green = _new.green();
auto blue = _new.blue();
auto alpha = _new.alpha();
_picker->setRGB(red, green, blue);
_hueSlider->setRGB(red, green, blue);
_opacitySlider->setRGB(red, green, blue);
_opacitySlider->setAlpha(alpha);
}
void EditColorBox::setHSV(int hue, int saturation, int value, int alpha) {
_new.setHsv(hue, saturation, value, alpha);
updateRGBFields();
updateResultField();
update();
}
void EditColorBox::setRGB(int red, int green, int blue, int alpha) {
_new.setRgb(red, green, blue, alpha);
updateControlsFromColor();
updateHSVFields();
update();
}

View file

@ -0,0 +1,115 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "boxes/abstractbox.h"
class EditColorBox : public BoxContent {
Q_OBJECT
public:
EditColorBox(QWidget*, const QString &title, QColor current = QColor(255, 255, 255));
void setSaveCallback(base::lambda<void(QColor)> &&callback) {
_saveCallback = std_::move(callback);
}
void setCancelCallback(base::lambda<void()> &&callback) {
_cancelCallback = std_::move(callback);
}
void showColor(QColor color) {
updateFromColor(color);
}
void closeHook() override {
if (_cancelCallback) {
_cancelCallback();
}
}
protected:
void prepare() override;
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void setInnerFocus() override;
private slots:
void onFieldChanged();
void onFieldSubmitted();
private:
void saveColor();
void updateFromColor(QColor color);
void updateControlsFromColor();
void updateControlsFromHSV(int hue, int saturation, int brightness);
void updateHSVFields();
void updateRGBFields();
void updateResultField();
void updateFromControls();
void updateFromHSVFields();
void updateFromRGBFields();
void updateFromResultField();
void setHSV(int hue, int saturation, int brightness, int alpha);
void setRGB(int red, int green, int blue, int alpha);
int percentFromByte(int byte) {
return snap(qRound(byte * 100 / 255.), 0, 100);
}
int percentToByte(int percent) {
return snap(qRound(percent * 255 / 100.), 0, 255);
}
class Picker;
class Slider;
class Field;
class ResultField;
QString _title;
object_ptr<Picker> _picker;
object_ptr<Slider> _hueSlider;
object_ptr<Slider> _opacitySlider;
object_ptr<Field> _hueField;
object_ptr<Field> _saturationField;
object_ptr<Field> _brightnessField;
object_ptr<Field> _redField;
object_ptr<Field> _greenField;
object_ptr<Field> _blueField;
object_ptr<ResultField> _result;
QBrush _transparent;
QColor _current;
QColor _new;
QRect _currentRect;
QRect _newRect;
base::lambda<void(QColor)> _saveCallback;
base::lambda<void()> _cancelCallback;
};

View file

@ -37,7 +37,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "history/history_media_types.h" #include "history/history_media_types.h"
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/widgets/scroll_area.h" #include "ui/widgets/scroll_area.h"
#include "window/window_theme.h" #include "window/themes/window_theme.h"
#include "boxes/contactsbox.h" #include "boxes/contactsbox.h"
ShareBox::ShareBox(QWidget*, CopyCallback &&copyCallback, SubmitCallback &&submitCallback, FilterCallback &&filterCallback) ShareBox::ShareBox(QWidget*, CopyCallback &&copyCallback, SubmitCallback &&submitCallback, FilterCallback &&filterCallback)
@ -872,9 +872,7 @@ void shareGameScoreFromItem(HistoryItem *item) {
QApplication::clipboard()->setText(CreateInternalLinkHttps(bot->username + qsl("?game=") + shortName)); QApplication::clipboard()->setText(CreateInternalLinkHttps(bot->username + qsl("?game=") + shortName));
Ui::Toast::Config toast; Ui::Toast::Show(lang(lng_share_game_link_copied));
toast.text = lang(lng_share_game_link_copied);
Ui::Toast::Show(App::wnd(), toast);
} }
} }
} }
@ -892,10 +890,7 @@ void shareGameScoreFromItem(HistoryItem *item) {
} }
data->requests.remove(requestId); data->requests.remove(requestId);
if (data->requests.empty()) { if (data->requests.empty()) {
Ui::Toast::Config toast; Ui::Toast::Show(lang(lng_share_done));
toast.text = lang(lng_share_done);
Ui::Toast::Show(App::wnd(), toast);
Ui::hideLayer(); Ui::hideLayer();
} }
}; };

View file

@ -161,10 +161,7 @@ void UsernameBox::onChanged() {
void UsernameBox::onLinkClick() { void UsernameBox::onLinkClick() {
Application::clipboard()->setText(CreateInternalLinkHttps(getName())); Application::clipboard()->setText(CreateInternalLinkHttps(getName()));
Ui::Toast::Show(lang(lng_username_copied));
Ui::Toast::Config toast;
toast.text = lang(lng_username_copied);
Ui::Toast::Show(App::wnd(), toast);
} }
void UsernameBox::onUpdateDone(const MTPUser &user) { void UsernameBox::onUpdateDone(const MTPUser &user) {

View file

@ -52,8 +52,6 @@ Token invalidToken() {
return { Type::Invalid, QString(), ConstUtf8String(nullptr, 0), false }; return { Type::Invalid, QString(), ConstUtf8String(nullptr, 0), false };
} }
} // namespace } // namespace
BasicTokenizedFile::BasicTokenizedFile(const QString &filepath) : reader_(filepath) { BasicTokenizedFile::BasicTokenizedFile(const QString &filepath) : reader_(filepath) {
@ -152,6 +150,22 @@ Type BasicTokenizedFile::uniteLastTokens(Type type) {
return type; return type;
} }
QString BasicTokenizedFile::getCurrentLineComment() {
if (lineNumber_ > singleLineComments_.size()) {
reader_.logError(kErrorInternal, lineNumber_) << "internal tokenizer error (line number larger than comments list size).";
failed_ = true;
return QString();
}
auto commentBytes = singleLineComments_[lineNumber_ - 1].mid(2); // Skip "//"
CheckedUtf8String comment(commentBytes);
if (!comment.isValid()) {
reader_.logError(kErrorIncorrectUtf8String, lineNumber_) << "incorrect UTF-8 string in the comment.";
failed_ = true;
return QString();
}
return comment.toString().trimmed();
}
Type BasicTokenizedFile::readNameOrNumber() { Type BasicTokenizedFile::readNameOrNumber() {
while (!reader_.atEnd()) { while (!reader_.atEnd()) {
if (!isDigitChar(reader_.currentChar())) { if (!isDigitChar(reader_.currentChar())) {

View file

@ -77,7 +77,11 @@ public:
}; };
bool read() { bool read() {
return reader_.read(); if (reader_.read()) {
singleLineComments_ = reader_.singleLineComments();
return true;
}
return false;
} }
bool atEnd() const { bool atEnd() const {
return reader_.atEnd(); return reader_.atEnd();
@ -90,6 +94,8 @@ public:
return failed_; return failed_;
} }
QString getCurrentLineComment();
// Log error to std::cerr with 'code' at the current position in file. // Log error to std::cerr with 'code' at the current position in file.
LogStream logError(int code) const; LogStream logError(int code) const;
LogStream logErrorUnexpectedToken() const; LogStream logErrorUnexpectedToken() const;
@ -124,6 +130,7 @@ private:
int currentToken_ = 0; int currentToken_ = 0;
int lineNumber_ = 1; int lineNumber_ = 1;
bool failed_ = false; bool failed_ = false;
QVector<QByteArray> singleLineComments_;
// Where the last (currently read) token has started. // Where the last (currently read) token has started.
const char *tokenStart_ = nullptr; const char *tokenStart_ = nullptr;

View file

@ -86,10 +86,17 @@ bool CleanFile::read() {
offset = ch; offset = ch;
} }
}; };
auto feedComment = [this, &offset, end](const char *ch) {
auto lineNumber = 0;
auto feedComment = [this, &offset, end, &lineNumber](const char *ch, bool save = false) {
if (ch > offset) { if (ch > offset) {
// comments_.push_back({ content_.size(), QByteArray(offset, ch - offset) }); if (save) {
if (result_.isEmpty()) result_.reserve(end - offset - 2); singleLineComments_.resize(lineNumber + 1);
singleLineComments_[lineNumber] = QByteArray(offset, ch - offset);
}
if (result_.isEmpty()) {
result_.reserve(end - offset - 2);
}
result_.append(' '); result_.append(' ');
offset = ch; offset = ch;
} }
@ -105,6 +112,9 @@ bool CleanFile::read() {
} }
} }
if (insideString) { if (insideString) {
if (currentChar == '\n') {
++lineNumber;
}
++ch; ++ch;
continue; continue;
} }
@ -114,12 +124,14 @@ bool CleanFile::read() {
insideComment = InsideComment::SingleLine; insideComment = InsideComment::SingleLine;
ch += 2; ch += 2;
} else if (insideComment == InsideComment::SingleLine && currentChar == '\r' && nextChar == '\n') { } else if (insideComment == InsideComment::SingleLine && currentChar == '\r' && nextChar == '\n') {
feedComment(ch); feedComment(ch, true);
ch += 2; ch += 2;
++lineNumber;
insideComment = InsideComment::None; insideComment = InsideComment::None;
} else if (insideComment == InsideComment::SingleLine && currentChar == '\n') { } else if (insideComment == InsideComment::SingleLine && currentChar == '\n') {
feedComment(ch); feedComment(ch, true);
++ch; ++ch;
++lineNumber;
insideComment = InsideComment::None; insideComment = InsideComment::None;
} else if (insideComment == InsideComment::None && currentChar == '/' && nextChar == '*') { } else if (insideComment == InsideComment::None && currentChar == '/' && nextChar == '*') {
feedContent(ch); feedContent(ch);
@ -132,15 +144,21 @@ bool CleanFile::read() {
} else if (insideComment == InsideComment::MultiLine && currentChar == '\r' && nextChar == '\n') { } else if (insideComment == InsideComment::MultiLine && currentChar == '\r' && nextChar == '\n') {
feedComment(ch); feedComment(ch);
ch += 2; ch += 2;
++lineNumber;
feedContent(ch); feedContent(ch);
} else if (insideComment == InsideComment::MultiLine && currentChar == '\n') { } else if (insideComment == InsideComment::MultiLine && currentChar == '\n') {
feedComment(ch); feedComment(ch);
++ch; ++ch;
++lineNumber;
feedContent(ch); feedContent(ch);
} else { } else {
if (currentChar == '\n') {
++lineNumber;
}
++ch; ++ch;
} }
} }
singleLineComments_.resize(lineNumber + 1);
if (insideComment == InsideComment::MultiLine) { if (insideComment == InsideComment::MultiLine) {
common::logError(kErrorUnexpectedEndOfFile, filepath_); common::logError(kErrorUnexpectedEndOfFile, filepath_);
@ -156,6 +174,10 @@ bool CleanFile::read() {
return true; return true;
} }
QVector<QByteArray> CleanFile::singleLineComments() const {
return singleLineComments_;
}
LogStream CleanFile::logError(int code, int line) const { LogStream CleanFile::logError(int code, int line) const {
return common::logError(code, filepath_, line); return common::logError(code, filepath_, line);
} }

View file

@ -38,6 +38,7 @@ public:
CleanFile &operator=(const CleanFile &other) = delete; CleanFile &operator=(const CleanFile &other) = delete;
bool read(); bool read();
QVector<QByteArray> singleLineComments() const;
const char *data() const { const char *data() const {
return result_.constData(); return result_.constData();
@ -55,11 +56,8 @@ private:
QString filepath_; QString filepath_;
QByteArray content_, result_; QByteArray content_, result_;
bool read_; bool read_;
//struct Comment {
// int offset; QVector<QByteArray> singleLineComments_;
// QByteArray content;
//};
//QVector<Comment> comments_;
}; };

View file

@ -63,6 +63,10 @@ public:
return (end_ - pos_); return (end_ - pos_);
} }
QVector<QByteArray> singleLineComments() const {
return file_.singleLineComments();
}
// Log error to std::cerr with 'code' at line number 'line' in data(). // Log error to std::cerr with 'code' at line number 'line' in data().
LogStream logError(int code, int line) const { LogStream logError(int code, int line) const {
return std::forward<LogStream>(file_.logError(code, line)); return std::forward<LogStream>(file_.logError(code, line));

View file

@ -106,12 +106,13 @@ char hexFirstChar(char ch) {
return hexChar((*reinterpret_cast<uchar*>(&ch)) >> 4); return hexChar((*reinterpret_cast<uchar*>(&ch)) >> 4);
} }
QString stringToEncodedString(const std::string &str) { QString stringToEncodedString(const QString &str) {
QString result, lineBreak = "\\\n"; QString result, lineBreak = "\\\n";
result.reserve(str.size() * 8); result.reserve(str.size() * 8);
bool writingHexEscapedCharacters = false, startOnNewLine = false; bool writingHexEscapedCharacters = false, startOnNewLine = false;
int lastCutSize = 0; int lastCutSize = 0;
for (uchar ch : str) { auto utf = str.toUtf8();
for (auto ch : utf) {
if (result.size() - lastCutSize > 80) { if (result.size() - lastCutSize > 80) {
startOnNewLine = true; startOnNewLine = true;
result.append(lineBreak); result.append(lineBreak);
@ -140,6 +141,10 @@ QString stringToEncodedString(const std::string &str) {
return '"' + (startOnNewLine ? lineBreak : QString()) + result + '"'; return '"' + (startOnNewLine ? lineBreak : QString()) + result + '"';
} }
QString stringToEncodedString(const std::string &str) {
return stringToEncodedString(QString::fromStdString(str));
}
QString stringToBinaryArray(const std::string &str) { QString stringToBinaryArray(const std::string &str) {
QStringList rows, chars; QStringList rows, chars;
chars.reserve(13); chars.reserve(13);
@ -334,7 +339,7 @@ QString Generator::valueAssignmentCode(structure::Value value) const {
case Tag::Int: return QString("%1").arg(value.Int()); case Tag::Int: return QString("%1").arg(value.Int());
case Tag::Double: return QString("%1").arg(value.Double()); case Tag::Double: return QString("%1").arg(value.Double());
case Tag::Pixels: return pxValueName(value.Int()); case Tag::Pixels: return pxValueName(value.Int());
case Tag::String: return QString("qsl(%1)").arg(stringToEncodedString(value.String())); case Tag::String: return QString("QString::fromUtf8(%1)").arg(stringToEncodedString(value.String()));
case Tag::Color: { case Tag::Color: {
auto v(value.Color()); auto v(value.Color());
if (v.red == v.green && v.red == v.blue && v.red == 0 && v.alpha == 255) { if (v.red == v.green && v.red == v.blue && v.red == 0 && v.alpha == 255) {
@ -441,8 +446,15 @@ public:\n\
\n\ \n\
QByteArray save() const;\n\ QByteArray save() const;\n\
bool load(const QByteArray &cache);\n\ bool load(const QByteArray &cache);\n\
bool setColor(QLatin1String name, uchar r, uchar g, uchar b, uchar a);\n\ \n\
bool setColor(QLatin1String name, QLatin1String from);\n\ enum class SetResult {\n\
Ok,\n\
KeyNotFound,\n\
ValueNotFound,\n\
Duplicate,\n\
};\n\
SetResult setColor(QLatin1String name, uchar r, uchar g, uchar b, uchar a);\n\
SetResult setColor(QLatin1String name, QLatin1String from);\n\
void reset() {\n\ void reset() {\n\
clear();\n\ clear();\n\
finalize();\n\ finalize();\n\
@ -564,12 +576,20 @@ namespace main_palette {\n\
\n\ \n\
QByteArray save();\n\ QByteArray save();\n\
bool load(const QByteArray &cache);\n\ bool load(const QByteArray &cache);\n\
bool setColor(QLatin1String name, uchar r, uchar g, uchar b, uchar a);\n\ palette::SetResult setColor(QLatin1String name, uchar r, uchar g, uchar b, uchar a);\n\
bool setColor(QLatin1String name, QLatin1String from);\n\ palette::SetResult setColor(QLatin1String name, QLatin1String from);\n\
void apply(const palette &other);\n\ void apply(const palette &other);\n\
void reset();\n\ void reset();\n\
int indexOfColor(color c);\n\ int indexOfColor(color c);\n\
\n\ \n\
struct row {\n\
\tQLatin1String name;\n\
\tQLatin1String value;\n\
\tQLatin1String fallback;\n\
\tQLatin1String description;\n\
};\n\
QList<row> data();\n\
\n\
} // namespace main_palette\n"; } // namespace main_palette\n";
return true; return true;
@ -739,10 +759,17 @@ void palette::finalize() {\n\
\n\ \n\
compute(0, -1, { 255, 255, 255, 0}); // special color\n"; compute(0, -1, { 255, 255, 255, 0}); // special color\n";
QList<structure::FullName> names;
module_.enumVariables([this, &names](const Variable &variable) -> bool {
names.push_back(variable.name);
return true;
});
QString dataRows;
int indexInPalette = 1; int indexInPalette = 1;
QByteArray checksumString; QByteArray checksumString;
checksumString.append("&transparent:{ 255, 255, 255, 0 }"); checksumString.append("&transparent:{ 255, 255, 255, 0 }");
bool result = module_.enumVariables([this, &indexInPalette, &checksumString](const Variable &variable) -> bool { auto result = module_.enumVariables([this, &indexInPalette, &checksumString, &dataRows, &names](const Variable &variable) -> bool {
auto name = variable.name.back(); auto name = variable.name.back();
auto index = indexInPalette++; auto index = indexInPalette++;
paletteIndices_[name] = index; paletteIndices_[name] = index;
@ -754,8 +781,27 @@ void palette::finalize() {\n\
auto assignment = QString("{ %1, %2, %3, %4 }").arg(color.red).arg(color.green).arg(color.blue).arg(color.alpha); auto assignment = QString("{ %1, %2, %3, %4 }").arg(color.red).arg(color.green).arg(color.blue).arg(color.alpha);
source_->stream() << "\tcompute(" << index << ", " << fallbackIndex << ", " << assignment << ");\n"; source_->stream() << "\tcompute(" << index << ", " << fallbackIndex << ", " << assignment << ");\n";
checksumString.append('&' + name + ':' + assignment); checksumString.append('&' + name + ':' + assignment);
auto isCopy = !variable.value.copyOf().isEmpty();
auto colorString = paletteColorValue(color);
auto fallbackName = QString();
if (fallbackIndex > 0) {
auto fallbackVariable = module_.findVariableInModule(names[fallbackIndex - 1], module_);
if (fallbackVariable && fallbackVariable->value.type().tag == structure::TypeTag::Color) {
fallbackName = fallbackVariable->name.back();
}
}
auto value = isCopy ? fallbackName : '#' + colorString;
if (value.isEmpty()) {
return false;
}
dataRows.append("\tresult.push_back({ qstr(\"" + name + "\"), qstr(\"" + value + "\"), qstr(\"" + (isCopy ? QString() : fallbackName) + "\"), qstr(" + stringToEncodedString(variable.description.toStdString()) + ") });\n");
return true; return true;
}); });
if (!result) {
return false;
}
auto count = indexInPalette; auto count = indexInPalette;
auto checksum = hashCrc32(checksumString.constData(), checksumString.size()); auto checksum = hashCrc32(checksumString.constData(), checksumString.size());
@ -854,23 +900,25 @@ bool palette::load(const QByteArray &cache) {\n\
return true;\n\ return true;\n\
}\n\ }\n\
\n\ \n\
bool palette::setColor(QLatin1String name, uchar r, uchar g, uchar b, uchar a) {\n\ palette::SetResult palette::setColor(QLatin1String name, uchar r, uchar g, uchar b, uchar a) {\n\
auto index = getPaletteIndex(name);\n\ auto nameIndex = getPaletteIndex(name);\n\
if (index >= 0) {\n\ if (nameIndex < 0) return SetResult::KeyNotFound;\n\
setData(index, { r, g, b, a });\n\ auto duplicate = (_status[nameIndex] != Status::Initial);\n\
return true;\n\ \n\
}\n\ setData(nameIndex, { r, g, b, a });\n\
return false;\n\ return duplicate ? SetResult::Duplicate : SetResult::Ok;\n\
}\n\ }\n\
\n\ \n\
bool palette::setColor(QLatin1String name, QLatin1String from) {\n\ palette::SetResult palette::setColor(QLatin1String name, QLatin1String from) {\n\
auto nameIndex = getPaletteIndex(name);\n\ auto nameIndex = getPaletteIndex(name);\n\
if (nameIndex < 0) return SetResult::KeyNotFound;\n\
auto duplicate = (_status[nameIndex] != Status::Initial);\n\
\n\
auto fromIndex = getPaletteIndex(from);\n\ auto fromIndex = getPaletteIndex(from);\n\
if (nameIndex >= 0 && fromIndex >= 0 && _status[fromIndex] == Status::Loaded) {\n\ if (fromIndex < 0 || _status[fromIndex] != Status::Loaded) return SetResult::ValueNotFound;\n\
setData(nameIndex, *data(fromIndex));\n\ \n\
return true;\n\ setData(nameIndex, *data(fromIndex));\n\
}\n\ return duplicate ? SetResult::Duplicate : SetResult::Ok;\n\
return false;\n\
}\n\ }\n\
\n\ \n\
namespace main_palette {\n\ namespace main_palette {\n\
@ -887,11 +935,11 @@ bool load(const QByteArray &cache) {\n\
return false;\n\ return false;\n\
}\n\ }\n\
\n\ \n\
bool setColor(QLatin1String name, uchar r, uchar g, uchar b, uchar a) {\n\ palette::SetResult setColor(QLatin1String name, uchar r, uchar g, uchar b, uchar a) {\n\
return _palette.setColor(name, r, g, b, a);\n\ return _palette.setColor(name, r, g, b, a);\n\
}\n\ }\n\
\n\ \n\
bool setColor(QLatin1String name, QLatin1String from) {\n\ palette::SetResult setColor(QLatin1String name, QLatin1String from) {\n\
return _palette.setColor(name, from);\n\ return _palette.setColor(name, from);\n\
}\n\ }\n\
\n\ \n\
@ -909,6 +957,14 @@ int indexOfColor(color c) {\n\
return _palette.indexOfColor(c);\n\ return _palette.indexOfColor(c);\n\
}\n\ }\n\
\n\ \n\
QList<row> data() {\n\
auto result = QList<row>();\n\
result.reserve(" << count << ");\n\
\n\
" << dataRows << "\n\
return result;\n\
}\n\
\n\
} // namespace main_palette\n\ } // namespace main_palette\n\
\n"; \n";
@ -1208,91 +1264,5 @@ bool Generator::collectUniqueValues() {
return module_.enumVariables(collector); return module_.enumVariables(collector);
} }
bool Generator::writeSampleTheme(const QString &filepath) {
QByteArray content;
QTextStream stream(&content);
stream << "\
//\n\
// This is a sample Telegram Desktop theme file.\n\
// It was generated from the 'colors.palette' style file.\n\
//\n\
// To create a theme with a background image included you should\n\
// put two files in a .zip archive:\n\
//\n\
// First one is the color scheme like the one you're viewing\n\
// right now, this file should be named 'colors.tdesktop-theme'.\n\
//\n\
// Second one should be the background image and it can be named\n\
// 'background.jpg', 'background.png', 'tiled.jpg' or 'tiled.png'.\n\
// You should name it 'background' (if you'd like it not to be tiled),\n\
// or it can be named 'tiled' (if you'd like it to be tiled).\n\
//\n\
// After that you need to change the extension of your .zip archive\n\
// to 'tdesktop-theme', so you'll have:\n\
//\n\
// mytheme.tdesktop-theme\n\
// |-colors.tdesktop-theme\n\
// |-background.jpg (or tiled.jpg, background.png, tiled.png)\n\
//\n\n";
QList<structure::FullName> names;
module_.enumVariables([this, &names](const Variable &variable) -> bool {
names.push_back(variable.name);
return true;
});
bool result = module_.enumVariables([this, &names, &stream](const Variable &variable) -> bool {
auto name = variable.name.back();
if (variable.value.type().tag != structure::TypeTag::Color) {
return false;
}
auto color = variable.value.Color();
//color.red = uchar(rand() % 256);
//color.green = uchar(rand() % 256);
//color.blue = uchar(rand() % 256);
//auto fallbackIndex = -1;
auto fallbackIndex = paletteIndices_.value(colorFallbackName(variable.value), -1);
auto colorString = paletteColorValue(color);
if (fallbackIndex >= 0) {
auto fallbackVariable = module_.findVariableInModule(names[fallbackIndex - 1], module_);
if (!fallbackVariable || fallbackVariable->value.type().tag != structure::TypeTag::Color) {
return false;
}
auto fallbackName = fallbackVariable->name.back();
auto fallbackColor = fallbackVariable->value.Color();
if (colorString == paletteColorValue(fallbackColor)) {
stream << name << ": " << fallbackName << ";\n";
} else {
stream << name << ": #" << colorString << "; // " << fallbackName << ";\n";
}
} else {
stream << name << ": #" << colorString << ";\n";
}
return true;
});
if (!result) {
return result;
}
stream.flush();
QFile file(filepath);
if (file.open(QIODevice::ReadOnly)) {
if (file.readAll() == content) {
file.close();
return true;
}
file.close();
}
if (!file.open(QIODevice::WriteOnly)) {
return false;
}
if (file.write(content) != content.size()) {
return false;
}
return true;
}
} // namespace style } // namespace style
} // namespace codegen } // namespace codegen

View file

@ -40,7 +40,6 @@ public:
bool writeHeader(); bool writeHeader();
bool writeSource(); bool writeSource();
bool writeSampleTheme(const QString &filepath);
private: private:
QString typeToString(structure::Type type) const; QString typeToString(structure::Type type) const;

View file

@ -261,6 +261,7 @@ structure::Variable ParsedFile::readVariable(const QString &name) {
} }
if (value.type().tag != structure::TypeTag::Struct || !value.copyOf().empty()) { if (value.type().tag != structure::TypeTag::Struct || !value.copyOf().empty()) {
assertNextToken(BasicType::Semicolon); assertNextToken(BasicType::Semicolon);
result.description = file_.getCurrentLineComment();
} }
} }
return result; return result;

View file

@ -81,10 +81,6 @@ bool Processor::write(const structure::Module &module) const {
if (!generator.writeSource()) { if (!generator.writeSource()) {
return false; return false;
} }
auto themePath = srcFile.absoluteDir().absolutePath() + "/default.tdesktop-theme";
if (options_.isPalette && !generator.writeSampleTheme(themePath)) {
return false;
}
return true; return true;
} }

View file

@ -198,6 +198,7 @@ private:
struct Variable { struct Variable {
FullName name; FullName name;
Value value; Value value;
QString description;
explicit operator bool() const { explicit operator bool() const {
return !name.isEmpty(); return !name.isEmpty();

View file

@ -940,6 +940,7 @@ QStringList MimeType::globPatterns() const {
switch (_type) { switch (_type) {
case Known::WebP: return QStringList(qsl("*.webp")); case Known::WebP: return QStringList(qsl("*.webp"));
case Known::TDesktopTheme: return QStringList(qsl("*.tdesktop-theme")); case Known::TDesktopTheme: return QStringList(qsl("*.tdesktop-theme"));
case Known::TDesktopPalette: return QStringList(qsl("*.tdesktop-palette"));
default: break; default: break;
} }
return _typeStruct.globPatterns(); return _typeStruct.globPatterns();
@ -948,6 +949,7 @@ QString MimeType::filterString() const {
switch (_type) { switch (_type) {
case Known::WebP: return qsl("WebP image (*.webp)"); case Known::WebP: return qsl("WebP image (*.webp)");
case Known::TDesktopTheme: return qsl("Theme files (*.tdesktop-theme)"); case Known::TDesktopTheme: return qsl("Theme files (*.tdesktop-theme)");
case Known::TDesktopPalette: return qsl("Palette files (*.tdesktop-palette)");
default: break; default: break;
} }
return _typeStruct.filterString(); return _typeStruct.filterString();
@ -956,6 +958,7 @@ QString MimeType::name() const {
switch (_type) { switch (_type) {
case Known::WebP: return qsl("image/webp"); case Known::WebP: return qsl("image/webp");
case Known::TDesktopTheme: return qsl("application/x-tdesktop-theme"); case Known::TDesktopTheme: return qsl("application/x-tdesktop-theme");
case Known::TDesktopPalette: return qsl("application/x-tdesktop-palette");
default: break; default: break;
} }
return _typeStruct.name(); return _typeStruct.name();
@ -966,17 +969,22 @@ MimeType mimeTypeForName(const QString &mime) {
return MimeType(MimeType::Known::WebP); return MimeType(MimeType::Known::WebP);
} else if (mime == qsl("application/x-tdesktop-theme")) { } else if (mime == qsl("application/x-tdesktop-theme")) {
return MimeType(MimeType::Known::TDesktopTheme); return MimeType(MimeType::Known::TDesktopTheme);
} else if (mime == qsl("application/x-tdesktop-palette")) {
return MimeType(MimeType::Known::TDesktopPalette);
} }
return MimeType(QMimeDatabase().mimeTypeForName(mime)); return MimeType(QMimeDatabase().mimeTypeForName(mime));
} }
MimeType mimeTypeForFile(const QFileInfo &file) { MimeType mimeTypeForFile(const QFileInfo &file) {
QString path = file.absoluteFilePath(); QString path = file.absoluteFilePath();
if (path.endsWith(qsl(".webp"), Qt::CaseInsensitive)) { if (path.endsWith(qstr(".webp"), Qt::CaseInsensitive)) {
return MimeType(MimeType::Known::WebP); return MimeType(MimeType::Known::WebP);
} else if (path.endsWith(qsl(".tdesktop-theme"), Qt::CaseInsensitive)) { } else if (path.endsWith(qstr(".tdesktop-theme"), Qt::CaseInsensitive)) {
return MimeType(MimeType::Known::TDesktopTheme); return MimeType(MimeType::Known::TDesktopTheme);
} else if (path.endsWith(qstr(".tdesktop-palette"), Qt::CaseInsensitive)) {
return MimeType(MimeType::Known::TDesktopPalette);
} }
{ {
QFile f(path); QFile f(path);
if (f.open(QIODevice::ReadOnly)) { if (f.open(QIODevice::ReadOnly)) {

View file

@ -432,6 +432,7 @@ public:
enum class Known { enum class Known {
Unknown, Unknown,
TDesktopTheme, TDesktopTheme,
TDesktopPalette,
WebP, WebP,
}; };

View file

@ -22,7 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "core/utils.h" #include "core/utils.h"
#define BETA_VERSION_MACRO (0ULL) #define BETA_VERSION_MACRO (1000006001ULL)
constexpr int AppVersion = 1000006; constexpr int AppVersion = 1000006;
constexpr str_const AppVersionStr = "1.0.6"; constexpr str_const AppVersionStr = "1.0.6";

View file

@ -42,7 +42,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "apiwrap.h" #include "apiwrap.h"
#include "ui/widgets/dropdown_menu.h" #include "ui/widgets/dropdown_menu.h"
#include "ui/widgets/input_fields.h" #include "ui/widgets/input_fields.h"
#include "window/window_theme.h" #include "window/themes/window_theme.h"
#include "autoupdater.h" #include "autoupdater.h"
#include "observer_peer.h" #include "observer_peer.h"

View file

@ -56,7 +56,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "localstorage.h" #include "localstorage.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "window/top_bar_widget.h" #include "window/top_bar_widget.h"
#include "window/window_theme.h" #include "window/themes/window_theme.h"
#include "observer_peer.h" #include "observer_peer.h"
#include "core/qthelp_regex.h" #include "core/qthelp_regex.h"
#include "ui/widgets/popup_menu.h" #include "ui/widgets/popup_menu.h"
@ -5802,10 +5802,8 @@ void HistoryWidget::botCallbackDone(BotCallbackInfo info, const MTPmessages_BotC
if (answerData.has_message()) { if (answerData.has_message()) {
if (answerData.is_alert()) { if (answerData.is_alert()) {
Ui::show(Box<InformBox>(qs(answerData.vmessage))); Ui::show(Box<InformBox>(qs(answerData.vmessage)));
} else if (App::wnd()) { } else {
Ui::Toast::Config toast; Ui::Toast::Show(qs(answerData.vmessage));
toast.text = qs(answerData.vmessage);
Ui::Toast::Show(App::wnd(), toast);
} }
} else if (answerData.has_url()) { } else if (answerData.has_url()) {
auto url = qs(answerData.vurl); auto url = qs(answerData.vurl);

View file

@ -204,3 +204,9 @@ private:
mutable QSize _cachedSize; mutable QSize _cachedSize;
}; };
template <typename BoxType, typename ...Args>
inline object_ptr<BoxType> Box(Args&&... args) {
auto parent = static_cast<QWidget*>(nullptr);
return object_ptr<BoxType>(parent, std_::forward<Args>(args)...);
}

View file

@ -26,7 +26,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "serialize/serialize_document.h" #include "serialize/serialize_document.h"
#include "serialize/serialize_common.h" #include "serialize/serialize_common.h"
#include "data/data_drafts.h" #include "data/data_drafts.h"
#include "window/window_theme.h" #include "window/themes/window_theme.h"
#include "observer_peer.h" #include "observer_peer.h"
#include "mainwidget.h" #include "mainwidget.h"
#include "mainwindow.h" #include "mainwindow.h"
@ -601,6 +601,7 @@ bool _backgroundWasRead = false;
bool _backgroundCanWrite = true; bool _backgroundCanWrite = true;
FileKey _themeKey = 0; FileKey _themeKey = 0;
QString _themePaletteAbsolutePath;
bool _readingUserSettings = false; bool _readingUserSettings = false;
FileKey _userSettingsKey = 0; FileKey _userSettingsKey = 0;
@ -3717,6 +3718,9 @@ bool readThemeUsingKey(FileKey key) {
if (theme.stream.status() != QDataStream::Ok) { if (theme.stream.status() != QDataStream::Ok) {
return false; return false;
} }
_themePaletteAbsolutePath = Window::Theme::IsPaletteTestingPath(pathAbsolute) ? pathAbsolute : QString();
QFile file(pathRelative); QFile file(pathRelative);
if (pathRelative.isEmpty() || !file.exists()) { if (pathRelative.isEmpty() || !file.exists()) {
file.setFileName(pathAbsolute); file.setFileName(pathAbsolute);
@ -3748,6 +3752,7 @@ bool readThemeUsingKey(FileKey key) {
void writeTheme(const QString &pathRelative, const QString &pathAbsolute, const QByteArray &content, const Window::Theme::Cached &cache) { void writeTheme(const QString &pathRelative, const QString &pathAbsolute, const QByteArray &content, const Window::Theme::Cached &cache) {
if (content.isEmpty()) { if (content.isEmpty()) {
_themePaletteAbsolutePath = QString();
if (_themeKey) { if (_themeKey) {
clearKey(_themeKey); clearKey(_themeKey);
_themeKey = 0; _themeKey = 0;
@ -3755,6 +3760,8 @@ void writeTheme(const QString &pathRelative, const QString &pathAbsolute, const
} }
return; return;
} }
_themePaletteAbsolutePath = Window::Theme::IsPaletteTestingPath(pathAbsolute) ? pathAbsolute : QString();
if (!_themeKey) { if (!_themeKey) {
_themeKey = genKey(); _themeKey = genKey();
writeSettings(); writeSettings();
@ -3787,6 +3794,29 @@ bool hasTheme() {
return (_themeKey != 0); return (_themeKey != 0);
} }
QString themePaletteAbsolutePath() {
return _themePaletteAbsolutePath;
}
bool copyThemeColorsToPalette(const QString &path) {
if (!_themeKey) {
return false;
}
FileReadDescriptor theme;
if (!readEncryptedFile(theme, _themeKey, FileOption::Safe, _settingsKey)) {
return false;
}
QByteArray themeContent;
theme.stream >> themeContent;
if (theme.stream.status() != QDataStream::Ok) {
return false;
}
return Window::Theme::CopyColorsToPalette(path, themeContent);
}
uint32 _peerSize(PeerData *peer) { uint32 _peerSize(PeerData *peer) {
uint32 result = sizeof(quint64) + sizeof(quint64) + Serialize::storageImageLocationSize(); uint32 result = sizeof(quint64) + sizeof(quint64) + Serialize::storageImageLocationSize();
if (peer->isUser()) { if (peer->isUser()) {

View file

@ -153,6 +153,8 @@ bool readBackground();
void writeTheme(const QString &pathRelative, const QString &pathAbsolute, const QByteArray &content, const Window::Theme::Cached &cache); void writeTheme(const QString &pathRelative, const QString &pathAbsolute, const QByteArray &content, const Window::Theme::Cached &cache);
void clearTheme(); void clearTheme();
bool hasTheme(); bool hasTheme();
QString themePaletteAbsolutePath();
bool copyThemeColorsToPalette(const QString &file);
void writeRecentHashtagsAndBots(); void writeRecentHashtagsAndBots();
void readRecentHashtagsAndBots(); void readRecentHashtagsAndBots();

View file

@ -57,7 +57,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "media/player/media_player_instance.h" #include "media/player/media_player_instance.h"
#include "core/qthelp_regex.h" #include "core/qthelp_regex.h"
#include "core/qthelp_url.h" #include "core/qthelp_url.h"
#include "window/window_theme.h" #include "window/themes/window_theme.h"
#include "window/player_wrap_widget.h" #include "window/player_wrap_widget.h"
#include "styles/style_boxes.h" #include "styles/style_boxes.h"

View file

@ -47,9 +47,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "settings/settings_widget.h" #include "settings/settings_widget.h"
#include "platform/platform_notifications_manager.h" #include "platform/platform_notifications_manager.h"
#include "window/notifications_manager.h" #include "window/notifications_manager.h"
#include "window/window_theme.h" #include "window/themes/window_theme.h"
#include "window/window_theme_warning.h" #include "window/themes/window_theme_warning.h"
#include "window/window_main_menu.h" #include "window/window_main_menu.h"
#include "core/task_queue.h"
ConnectingWidget::ConnectingWidget(QWidget *parent, const QString &text, const QString &reconnect) : TWidget(parent) ConnectingWidget::ConnectingWidget(QWidget *parent, const QString &text, const QString &reconnect) : TWidget(parent)
, _reconnect(this, QString()) { , _reconnect(this, QString()) {
@ -379,14 +380,18 @@ void MainWindow::setupMain(const MTPUser *self) {
} }
void MainWindow::showSettings() { void MainWindow::showSettings() {
if (_passcode) return;
if (isHidden()) showFromTray(); if (isHidden()) showFromTray();
showSpecialLayer(Box<Settings::Widget>());
}
void MainWindow::showSpecialLayer(object_ptr<LayerWidget> layer) {
if (_passcode) return;
if (!_layerBg) { if (!_layerBg) {
_layerBg.create(bodyWidget()); _layerBg.create(bodyWidget());
} }
_layerBg->showSpecialLayer(Box<Settings::Widget>()); _layerBg->showSpecialLayer(std_::move(layer));
} }
void MainWindow::showMainMenu() { void MainWindow::showMainMenu() {
@ -527,17 +532,37 @@ void MainWindow::hideConnecting() {
void MainWindow::themeUpdated(const Window::Theme::BackgroundUpdate &data) { void MainWindow::themeUpdated(const Window::Theme::BackgroundUpdate &data) {
using Type = Window::Theme::BackgroundUpdate::Type; using Type = Window::Theme::BackgroundUpdate::Type;
// We delay animating theme warning because we want all other
// subscribers to receive paltte changed notification before any
// animations (that include pixmap caches with old palette values).
if (data.type == Type::TestingTheme) { if (data.type == Type::TestingTheme) {
if (!_testingThemeWarning) { if (!_testingThemeWarning) {
_testingThemeWarning.create(bodyWidget()); _testingThemeWarning.create(bodyWidget());
_testingThemeWarning->hide();
_testingThemeWarning->setGeometry(rect()); _testingThemeWarning->setGeometry(rect());
_testingThemeWarning->setHiddenCallback([this] { _testingThemeWarning.destroyDelayed(); }); _testingThemeWarning->setHiddenCallback([this] { _testingThemeWarning.destroyDelayed(); });
} }
_testingThemeWarning->showAnimated();
base::TaskQueue::Main().Put(base::lambda_guarded(this, [this] {
if (_testingThemeWarning) {
_testingThemeWarning->showAnimated();
}
}));
} else if (data.type == Type::RevertingTheme || data.type == Type::ApplyingTheme) { } else if (data.type == Type::RevertingTheme || data.type == Type::ApplyingTheme) {
_testingThemeWarning->hideAnimated(); if (_testingThemeWarning) {
_testingThemeWarning = nullptr; if (_testingThemeWarning->isHidden()) {
setInnerFocus(); _testingThemeWarning.destroy();
} else {
base::TaskQueue::Main().Put(base::lambda_guarded(this, [this] {
if (_testingThemeWarning) {
_testingThemeWarning->hideAnimated();
_testingThemeWarning = nullptr;
}
setInnerFocus();
}));
}
}
} }
} }
@ -834,12 +859,9 @@ void MainWindow::closeEvent(QCloseEvent *e) {
} }
} }
void MainWindow::resizeEvent(QResizeEvent *e) {
Platform::MainWindow::resizeEvent(e);
updateControlsGeometry();
}
void MainWindow::updateControlsGeometry() { void MainWindow::updateControlsGeometry() {
Platform::MainWindow::updateControlsGeometry();
auto body = bodyWidget()->rect(); auto body = bodyWidget()->rect();
if (_passcode) _passcode->setGeometry(body); if (_passcode) _passcode->setGeometry(body);
if (_main) _main->setGeometry(body); if (_main) _main->setGeometry(body);

View file

@ -145,6 +145,8 @@ public:
void showMainMenu(); void showMainMenu();
void updateTrayMenu(bool force = false) override; void updateTrayMenu(bool force = false) override;
void showSpecialLayer(object_ptr<LayerWidget> layer);
void ui_showBox(object_ptr<BoxContent> box, ShowLayerOptions options); void ui_showBox(object_ptr<BoxContent> box, ShowLayerOptions options);
void ui_hideSettingsAndLayer(ShowLayerOptions options); void ui_hideSettingsAndLayer(ShowLayerOptions options);
bool ui_isLayerShown(); bool ui_isLayerShown();
@ -156,12 +158,13 @@ public:
protected: protected:
bool eventFilter(QObject *o, QEvent *e) override; bool eventFilter(QObject *o, QEvent *e) override;
void closeEvent(QCloseEvent *e) override; void closeEvent(QCloseEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
void initHook() override; void initHook() override;
void updateIsActiveHook() override; void updateIsActiveHook() override;
void clearWidgetsHook() override; void clearWidgetsHook() override;
void updateControlsGeometry() override;
public slots: public slots:
void checkAutoLock(); void checkAutoLock();
@ -205,8 +208,6 @@ private:
void themeUpdated(const Window::Theme::BackgroundUpdate &data); void themeUpdated(const Window::Theme::BackgroundUpdate &data);
void updateControlsGeometry();
QPixmap grabInner(); QPixmap grabInner();
void placeSmallCounter(QImage &img, int size, int count, style::color bg, const QPoint &shift, style::color color) override; void placeSmallCounter(QImage &img, int size, int count, style::color bg, const QPoint &shift, style::color color) override;

View file

@ -111,8 +111,6 @@ mediaviewFileIconSize: 80px;
mediaviewFileLink: defaultLinkButton; mediaviewFileLink: defaultLinkButton;
mediaviewTransparentSize: 4px;
mediaviewMenu: Menu(defaultMenu) { mediaviewMenu: Menu(defaultMenu) {
itemBg: mediaviewMenuBg; itemBg: mediaviewMenuBg;
itemBgOver: mediaviewMenuBgOver; itemBgOver: mediaviewMenuBgOver;

View file

@ -34,7 +34,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "styles/style_history.h" #include "styles/style_history.h"
#include "media/media_audio.h" #include "media/media_audio.h"
#include "history/history_media_types.h" #include "history/history_media_types.h"
#include "window/window_theme_preview.h" #include "window/themes/window_theme_preview.h"
#include "core/task_queue.h" #include "core/task_queue.h"
#include "observer_peer.h" #include "observer_peer.h"
@ -69,6 +69,7 @@ bool typeHasMediaOverview(MediaOverviewType type) {
} // namespace } // namespace
MediaView::MediaView(QWidget*) : TWidget(nullptr) MediaView::MediaView(QWidget*) : TWidget(nullptr)
, _transparentBrush(style::transparentPlaceholderBrush())
, _animStarted(getms()) , _animStarted(getms())
, _docDownload(this, lang(lng_media_download), st::mediaviewFileLink) , _docDownload(this, lang(lng_media_download), st::mediaviewFileLink)
, _docSaveAs(this, lang(lng_mediaview_save_as), st::mediaviewFileLink) , _docSaveAs(this, lang(lng_mediaview_save_as), st::mediaviewFileLink)
@ -96,8 +97,6 @@ MediaView::MediaView(QWidget*) : TWidget(nullptr)
mediaOverviewUpdated(update); mediaOverviewUpdated(update);
})); }));
generateTransparentBrush();
setWindowFlags(Qt::FramelessWindowHint | Qt::BypassWindowManagerHint | Qt::Tool | Qt::NoDropShadowWindowHint); setWindowFlags(Qt::FramelessWindowHint | Qt::BypassWindowManagerHint | Qt::Tool | Qt::NoDropShadowWindowHint);
moveToScreen(); moveToScreen();
setAttribute(Qt::WA_NoSystemBackground, true); setAttribute(Qt::WA_NoSystemBackground, true);
@ -1186,6 +1185,7 @@ void MediaView::displayPhoto(PhotoData *photo, HistoryItem *item) {
} }
void MediaView::destroyThemePreview() { void MediaView::destroyThemePreview() {
_themePreviewId = 0;
_themePreviewShown = false; _themePreviewShown = false;
_themePreview.reset(); _themePreview.reset();
_themeApply.destroy(); _themeApply.destroy();
@ -2767,19 +2767,6 @@ void MediaView::loadBack() {
} }
} }
void MediaView::generateTransparentBrush() {
auto size = st::mediaviewTransparentSize * cIntRetinaFactor();
auto transparent = QImage(2 * size, 2 * size, QImage::Format_ARGB32_Premultiplied);
transparent.fill(st::mediaviewTransparentBg->c);
{
Painter p(&transparent);
p.fillRect(rtlrect(0, size, size, size, 2 * size), st::mediaviewTransparentFg);
p.fillRect(rtlrect(size, 0, size, size, 2 * size), st::mediaviewTransparentFg);
}
transparent.setDevicePixelRatio(cRetinaFactor());
_transparentBrush = QBrush(transparent);
}
MediaView::LastChatPhoto MediaView::computeLastOverviewChatPhoto() { MediaView::LastChatPhoto MediaView::computeLastOverviewChatPhoto() {
LastChatPhoto emptyResult = { nullptr, nullptr }; LastChatPhoto emptyResult = { nullptr, nullptr };
auto lastPhotoInOverview = [&emptyResult](auto history, auto list) -> LastChatPhoto { auto lastPhotoInOverview = [&emptyResult](auto history, auto list) -> LastChatPhoto {

View file

@ -166,8 +166,6 @@ private:
void findCurrent(); void findCurrent();
void loadBack(); void loadBack();
void generateTransparentBrush();
void updateCursor(); void updateCursor();
void setZoomLevel(int newZoom); void setZoomLevel(int newZoom);

View file

@ -33,7 +33,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/widgets/input_fields.h" #include "ui/widgets/input_fields.h"
#include "window/top_bar_widget.h" #include "window/top_bar_widget.h"
#include "window/window_theme.h" #include "window/themes/window_theme.h"
#include "lang.h" #include "lang.h"
#include "mainwindow.h" #include "mainwindow.h"
#include "mainwidget.h" #include "mainwidget.h"

View file

@ -30,7 +30,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "lang.h" #include "lang.h"
#include "localstorage.h" #include "localstorage.h"
#include "ui/widgets/popup_menu.h" #include "ui/widgets/popup_menu.h"
#include "window/window_theme.h" #include "window/themes/window_theme.h"
#include <qpa/qplatformnativeinterface.h> #include <qpa/qplatformnativeinterface.h>

View file

@ -110,9 +110,7 @@ void InviteLinkWidget::refreshLink() {
} }
QApplication::clipboard()->setText(link); QApplication::clipboard()->setText(link);
Ui::Toast::Config toast; Ui::Toast::Show(lang(lng_group_invite_copied));
toast.text = lang(lng_group_invite_copied);
Ui::Toast::Show(App::wnd(), toast);
return false; return false;
}); });
} }

View file

@ -31,7 +31,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/effects/widget_slide_wrap.h" #include "ui/effects/widget_slide_wrap.h"
#include "localstorage.h" #include "localstorage.h"
#include "window/window_theme.h" #include "window/themes/window_theme.h"
namespace Settings { namespace Settings {

View file

@ -30,7 +30,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "localstorage.h" #include "localstorage.h"
#include "mainwindow.h" #include "mainwindow.h"
#include "window/window_theme.h" #include "window/themes/window_theme.h"
namespace Settings { namespace Settings {
@ -240,7 +240,7 @@ void BackgroundWidget::needBackgroundUpdate(bool tile) {
void BackgroundWidget::onChooseFromFile() { void BackgroundWidget::onChooseFromFile() {
auto imgExtensions = cImgExtensions(); auto imgExtensions = cImgExtensions();
auto filters = QStringList(qsl("Theme files (*.tdesktop-theme *") + imgExtensions.join(qsl(" *")) + qsl(")")); auto filters = QStringList(qsl("Theme files (*.tdesktop-theme *.tdesktop-palette *") + imgExtensions.join(qsl(" *")) + qsl(")"));
filters.push_back(filedialogAllFilesFilter()); filters.push_back(filedialogAllFilesFilter());
_chooseFromFileQueryId = FileDialog::queryReadFile(lang(lng_choose_image), filters.join(qsl(";;"))); _chooseFromFileQueryId = FileDialog::queryReadFile(lang(lng_choose_image), filters.join(qsl(";;")));
@ -261,7 +261,8 @@ void BackgroundWidget::notifyFileQueryUpdated(const FileDialog::QueryUpdate &upd
} }
auto filePath = update.filePaths.front(); auto filePath = update.filePaths.front();
if (filePath.endsWith(qstr(".tdesktop-theme"), Qt::CaseInsensitive)) { if (filePath.endsWith(qstr(".tdesktop-theme"), Qt::CaseInsensitive)
|| filePath.endsWith(qstr(".tdesktop-palette"), Qt::CaseInsensitive)) {
Window::Theme::Apply(filePath); Window::Theme::Apply(filePath);
return; return;
} }

View file

@ -24,7 +24,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "styles/style_settings.h" #include "styles/style_settings.h"
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
#include "mainwindow.h" #include "mainwindow.h"
#include "lang.h"
namespace Settings { namespace Settings {
@ -32,6 +31,11 @@ FixedBar::FixedBar(QWidget *parent) : TWidget(parent) {
setAttribute(Qt::WA_OpaquePaintEvent); setAttribute(Qt::WA_OpaquePaintEvent);
} }
void FixedBar::setText(const QString &text) {
_text = text;
update();
}
int FixedBar::resizeGetHeight(int newWidth) { int FixedBar::resizeGetHeight(int newWidth) {
return st::settingsFixedBarHeight - st::boxRadius; return st::settingsFixedBarHeight - st::boxRadius;
} }
@ -43,7 +47,7 @@ void FixedBar::paintEvent(QPaintEvent *e) {
p.setFont(st::settingsFixedBarFont); p.setFont(st::settingsFixedBarFont);
p.setPen(st::windowFg); p.setPen(st::windowFg);
p.drawTextLeft(st::settingsFixedBarTextPosition.x(), st::settingsFixedBarTextPosition.y() - st::boxRadius, width(), lang(lng_menu_settings)); p.drawTextLeft(st::settingsFixedBarTextPosition.x(), st::settingsFixedBarTextPosition.y() - st::boxRadius, width(), _text);
} }
} // namespace Settings } // namespace Settings

View file

@ -30,11 +30,16 @@ class FixedBar : public TWidget {
public: public:
FixedBar(QWidget *parent); FixedBar(QWidget *parent);
void setText(const QString &text);
protected: protected:
void paintEvent(QPaintEvent *e) override; void paintEvent(QPaintEvent *e) override;
int resizeGetHeight(int newWidth) override; int resizeGetHeight(int newWidth) override;
private:
QString _text;
}; };
} // namespace Settings } // namespace Settings

View file

@ -189,21 +189,21 @@ void GeneralWidget::refreshControls() {
if (cPlatform() == dbipWindows || cSupportTray()) { if (cPlatform() == dbipWindows || cSupportTray()) {
addChildRow(_enableTrayIcon, marginSmall, lang(lng_settings_workmode_tray), SLOT(onEnableTrayIcon()), (cWorkMode() == dbiwmTrayOnly || cWorkMode() == dbiwmWindowAndTray)); addChildRow(_enableTrayIcon, marginSmall, lang(lng_settings_workmode_tray), SLOT(onEnableTrayIcon()), (cWorkMode() == dbiwmTrayOnly || cWorkMode() == dbiwmWindowAndTray));
#ifdef Q_OS_WIN if (cPlatform() == dbipWindows) {
addChildRow(_enableTaskbarIcon, marginLarge, lang(lng_settings_workmode_window), SLOT(onEnableTaskbarIcon()), (cWorkMode() == dbiwmWindowOnly || cWorkMode() == dbiwmWindowAndTray)); addChildRow(_enableTaskbarIcon, marginLarge, lang(lng_settings_workmode_window), SLOT(onEnableTaskbarIcon()), (cWorkMode() == dbiwmWindowOnly || cWorkMode() == dbiwmWindowAndTray));
#ifndef OS_WIN_STORE #ifndef OS_WIN_STORE
addChildRow(_autoStart, marginSmall, lang(lng_settings_auto_start), SLOT(onAutoStart()), cAutoStart()); addChildRow(_autoStart, marginSmall, lang(lng_settings_auto_start), SLOT(onAutoStart()), cAutoStart());
addChildRow(_startMinimized, marginLarge, slidedPadding, lang(lng_settings_start_min), SLOT(onStartMinimized()), (cStartMinimized() && !Global::LocalPasscode())); addChildRow(_startMinimized, marginLarge, slidedPadding, lang(lng_settings_start_min), SLOT(onStartMinimized()), (cStartMinimized() && !Global::LocalPasscode()));
subscribe(Global::RefLocalPasscodeChanged(), [this] { subscribe(Global::RefLocalPasscodeChanged(), [this] {
_startMinimized->entity()->setChecked(cStartMinimized() && !Global::LocalPasscode()); _startMinimized->entity()->setChecked(cStartMinimized() && !Global::LocalPasscode());
}); });
if (!cAutoStart()) { if (!cAutoStart()) {
_startMinimized->hideFast(); _startMinimized->hideFast();
} }
addChildRow(_addInSendTo, marginSmall, lang(lng_settings_add_sendto), SLOT(onAddInSendTo()), cSendToMenu()); addChildRow(_addInSendTo, marginSmall, lang(lng_settings_add_sendto), SLOT(onAddInSendTo()), cSendToMenu());
#endif // OS_WIN_STORE #endif // OS_WIN_STORE
#endif // Q_OS_WIN }
} }
} }
@ -297,7 +297,7 @@ void GeneralWidget::updateWorkmode() {
Local::writeSettings(); Local::writeSettings();
} }
#if defined Q_OS_WIN && !defined OS_WIN_STORE #if !defined OS_WIN_STORE
void GeneralWidget::onAutoStart() { void GeneralWidget::onAutoStart() {
cSetAutoStart(_autoStart->checked()); cSetAutoStart(_autoStart->checked());
if (cAutoStart()) { if (cAutoStart()) {
@ -335,6 +335,6 @@ void GeneralWidget::onAddInSendTo() {
psSendToMenu(_addInSendTo->checked()); psSendToMenu(_addInSendTo->checked());
Local::writeSettings(); Local::writeSettings();
} }
#endif // Q_OS_WIN && !OS_WIN_STORE #endif // !OS_WIN_STORE
} // namespace Settings } // namespace Settings

View file

@ -94,11 +94,11 @@ private slots:
void onEnableTrayIcon(); void onEnableTrayIcon();
void onEnableTaskbarIcon(); void onEnableTaskbarIcon();
#if defined Q_OS_WIN && !defined OS_WIN_STORE #ifndef OS_WIN_STORE
void onAutoStart(); void onAutoStart();
void onStartMinimized(); void onStartMinimized();
void onAddInSendTo(); void onAddInSendTo();
#endif // Q_OS_WIN && !OS_WIN_STORE #endif // !OS_WIN_STORE
void onRestart(); void onRestart();

View file

@ -35,7 +35,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
namespace Settings { namespace Settings {
InnerWidget::InnerWidget(QWidget *parent) : TWidget(parent) InnerWidget::InnerWidget(QWidget *parent) : LayerInner(parent)
, _self(App::self()) { , _self(App::self()) {
refreshBlocks(); refreshBlocks();
subscribe(Global::RefSelfChanged(), [this]() { selfUpdated(); }); subscribe(Global::RefSelfChanged(), [this]() { selfUpdated(); });

View file

@ -20,19 +20,21 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/ */
#pragma once #pragma once
#include "settings/settings_layer.h"
namespace Settings { namespace Settings {
class CoverWidget; class CoverWidget;
class BlockWidget; class BlockWidget;
class InnerWidget : public TWidget, private base::Subscriber { class InnerWidget : public LayerInner, private base::Subscriber {
Q_OBJECT Q_OBJECT
public: public:
InnerWidget(QWidget *parent); InnerWidget(QWidget *parent);
// Count new height for width=newWidth and resize to it. // Count new height for width=newWidth and resize to it.
void resizeToWidth(int newWidth, int contentLeft) { void resizeToWidth(int newWidth, int contentLeft) override {
_contentLeft = contentLeft; _contentLeft = contentLeft;
return TWidget::resizeToWidth(newWidth); return TWidget::resizeToWidth(newWidth);
} }

View file

@ -0,0 +1,134 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "stdafx.h"
#include "settings/settings_layer.h"
#include "settings/settings_inner_widget.h"
#include "settings/settings_fixed_bar.h"
#include "styles/style_settings.h"
#include "styles/style_window.h"
#include "styles/style_boxes.h"
#include "ui/effects/widget_fade_wrap.h"
#include "ui/widgets/scroll_area.h"
#include "ui/widgets/buttons.h"
#include "mainwindow.h"
#include "mainwidget.h"
#include "localstorage.h"
#include "boxes/confirmbox.h"
#include "application.h"
#include "ui/filedialog.h"
#include "window/themes/window_theme.h"
namespace Settings {
Layer::Layer()
: _scroll(this, st::settingsScroll)
, _fixedBar(this)
, _fixedBarClose(this, st::settingsFixedBarClose)
, _fixedBarShadow(this, object_ptr<BoxLayerTitleShadow>(this)) {
_fixedBar->moveToLeft(0, st::boxRadius);
_fixedBarClose->moveToRight(0, 0);
_fixedBarShadow->entity()->resize(width(), st::lineWidth);
_fixedBarShadow->moveToLeft(0, _fixedBar->y() + _fixedBar->height());
_fixedBarShadow->hideFast();
_scroll->moveToLeft(0, st::settingsFixedBarHeight);
connect(_scroll, SIGNAL(scrolled()), this, SLOT(onScroll()));
}
void Layer::setCloseClickHandler(base::lambda<void()> &&callback) {
_fixedBarClose->setClickedCallback(std_::move(callback));
}
void Layer::onScroll() {
if (_scroll->scrollTop() > 0) {
_fixedBarShadow->showAnimated();
} else {
_fixedBarShadow->hideAnimated();
}
}
void Layer::resizeToWidth(int newWidth, int newContentLeft) {
// Widget height depends on InnerWidget height, so we
// resize it here, not in the resizeEvent() handler.
_inner->resizeToWidth(newWidth, newContentLeft);
resizeUsingInnerHeight(newWidth, _inner->height());
}
void Layer::onInnerHeightUpdated() {
resizeUsingInnerHeight(width(), _inner->height());
}
void Layer::doSetInnerWidget(object_ptr<LayerInner> widget) {
_inner = _scroll->setOwnedWidget(std_::move(widget));
connect(_inner, SIGNAL(heightUpdated()), this, SLOT(onInnerHeightUpdated()));
}
void Layer::paintEvent(QPaintEvent *e) {
Painter p(this);
auto clip = e->rect();
if (_roundedCorners) {
auto paintTopRounded = clip.intersects(QRect(0, 0, width(), st::boxRadius));
auto paintBottomRounded = clip.intersects(QRect(0, height() - st::boxRadius, width(), st::boxRadius));
if (paintTopRounded || paintBottomRounded) {
auto parts = qFlags(App::RectPart::None);
if (paintTopRounded) parts |= App::RectPart::TopFull;
if (paintBottomRounded) parts |= App::RectPart::BottomFull;
App::roundRect(p, rect(), st::boxBg, BoxCorners, nullptr, parts);
}
auto other = clip.intersected(QRect(0, st::boxRadius, width(), height() - 2 * st::boxRadius));
if (!other.isEmpty()) {
p.fillRect(other, st::boxBg);
}
} else {
p.fillRect(e->rect(), st::boxBg);
}
}
void Layer::resizeEvent(QResizeEvent *e) {
LayerWidget::resizeEvent(e);
if (!width() || !height()) {
return;
}
_fixedBar->resizeToWidth(width());
_fixedBar->moveToLeft(0, st::boxRadius);
_fixedBarClose->moveToRight(0, 0);
auto shadowTop = _fixedBar->y() + _fixedBar->height();
_fixedBarShadow->entity()->resize(width(), st::lineWidth);
_fixedBarShadow->moveToLeft(0, shadowTop);
auto scrollSize = QSize(width(), height() - shadowTop - (_roundedCorners ? st::boxRadius : 0));
if (_scroll->size() != scrollSize) {
_scroll->resize(scrollSize);
}
if (!_scroll->isHidden()) {
auto scrollTop = _scroll->scrollTop();
_inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height());
}
}
void Layer::setTitle(const QString &title) {
_fixedBar->setText(title);
}
} // namespace Settings

View file

@ -0,0 +1,94 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "layerwidget.h"
class BoxLayerTitleShadow;
namespace Ui {
class ScrollArea;
class IconButton;
template <typename Widget>
class WidgetFadeWrap;
} // namespace Ui
namespace Settings {
class FixedBar;
class LayerInner : public TWidget {
public:
LayerInner(QWidget *parent) : TWidget(parent) {
}
virtual void resizeToWidth(int newWidth, int contentLeft) {
TWidget::resizeToWidth(newWidth);
}
};
class Layer : public LayerWidget {
Q_OBJECT
public:
Layer();
void setCloseClickHandler(base::lambda<void()> &&callback);
void resizeToWidth(int newWidth, int newContentLeft);
protected:
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
template <typename Widget>
QPointer<Widget> setInnerWidget(object_ptr<Widget> widget) {
auto result = QPointer<Widget>(widget);
doSetInnerWidget(std_::move(widget));
return result;
}
void setTitle(const QString &title);
void setRoundedCorners(bool roundedCorners) {
_roundedCorners = roundedCorners;
}
private slots:
void onInnerHeightUpdated();
void onScroll();
private:
void doSetInnerWidget(object_ptr<LayerInner> widget);
virtual void resizeUsingInnerHeight(int newWidth, int innerHeight) {
resize(newWidth, height());
}
object_ptr<Ui::ScrollArea> _scroll;
QPointer<LayerInner> _inner;
object_ptr<FixedBar> _fixedBar;
object_ptr<Ui::IconButton> _fixedBarClose;
object_ptr<Ui::WidgetFadeWrap<BoxLayerTitleShadow>> _fixedBarShadow;
bool _roundedCorners = false;
};
} // namespace Settings

View file

@ -33,7 +33,11 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "mainwidget.h" #include "mainwidget.h"
#include "localstorage.h" #include "localstorage.h"
#include "boxes/confirmbox.h" #include "boxes/confirmbox.h"
#include "lang.h"
#include "application.h" #include "application.h"
#include "ui/filedialog.h"
#include "window/themes/window_theme.h"
#include "window/themes/window_theme_editor.h"
namespace Settings { namespace Settings {
namespace { namespace {
@ -88,6 +92,23 @@ void fillCodes() {
main->getDifference(); main->getDifference();
} }
}); });
Codes.insert(qsl("loadcolors"), []() {
FileDialog::askOpenPath("Open palette file", "Palette (*.tdesktop-palette)", [](const FileDialog::OpenResult &result) {
if (!result.paths.isEmpty()) {
Window::Theme::Apply(result.paths.front());
}
});
});
Codes.insert(qsl("edittheme"), []() {
auto palettePath = Local::themePaletteAbsolutePath();
if (palettePath.isEmpty()) {
FileDialog::askWritePath(lang(lng_theme_editor_save_palette), "Palette (*.tdesktop-palette)", "colors.tdesktop-palette", [](const QString &path) {
Window::Theme::Editor::StartFromCurrentTheme(path);
});
} else {
Window::Theme::Editor::Start(palettePath);
}
});
} }
void codesFeedString(const QString &text) { void codesFeedString(const QString &text) {
@ -123,42 +144,28 @@ void codesFeedString(const QString &text) {
} // namespace } // namespace
Widget::Widget(QWidget *parent) : LayerWidget(parent) Widget::Widget(QWidget *parent) {
, _scroll(this, st::settingsScroll) setTitle(lang(lng_menu_settings));
, _fixedBar(this) _inner = setInnerWidget(object_ptr<InnerWidget>(this));
, _fixedBarClose(this, st::settingsFixedBarClose) setCloseClickHandler([]() {
, _fixedBarShadow(this, object_ptr<BoxLayerTitleShadow>(this)) {
_inner = _scroll->setOwnedWidget(object_ptr<InnerWidget>(this));
_fixedBar->moveToLeft(0, st::boxRadius);
_fixedBarClose->moveToRight(0, 0);
_fixedBarShadow->entity()->resize(width(), st::lineWidth);
_fixedBarShadow->moveToLeft(0, _fixedBar->y() + _fixedBar->height());
_fixedBarShadow->hideFast();
_scroll->moveToLeft(0, st::settingsFixedBarHeight);
connect(_scroll, SIGNAL(scrolled()), this, SLOT(onScroll()));
_fixedBarClose->setClickedCallback([]() {
Ui::hideSettingsAndLayer(); Ui::hideSettingsAndLayer();
}); });
connect(_inner, SIGNAL(heightUpdated()), this, SLOT(onInnerHeightUpdated()));
} }
void Widget::onScroll() { void Widget::showFinished() {
if (_scroll->scrollTop() > 0) { _inner->showFinished();
_fixedBarShadow->showAnimated(); }
} else {
_fixedBarShadow->hideAnimated(); void Widget::keyPressEvent(QKeyEvent *e) {
} codesFeedString(e->text());
return LayerWidget::keyPressEvent(e);
} }
void Widget::parentResized() { void Widget::parentResized() {
auto parentSize = parentWidget()->size(); auto parentSize = parentWidget()->size();
int windowWidth = parentSize.width(); auto windowWidth = parentSize.width();
int newWidth = st::settingsMaxWidth; auto newWidth = st::settingsMaxWidth;
int newContentLeft = st::settingsMaxPadding; auto newContentLeft = st::settingsMaxPadding;
if (windowWidth <= st::settingsMaxWidth) { if (windowWidth <= st::settingsMaxWidth) {
newWidth = windowWidth; newWidth = windowWidth;
newContentLeft = st::settingsMinPadding; newContentLeft = st::settingsMinPadding;
@ -176,92 +183,27 @@ void Widget::parentResized() {
newContentLeft += ((newWidth - st::windowMinWidth) * (st::settingsMaxPadding - st::settingsMinPadding)) / (st::settingsMaxWidth - st::windowMinWidth); newContentLeft += ((newWidth - st::windowMinWidth) * (st::settingsMaxPadding - st::settingsMinPadding)) / (st::settingsMaxWidth - st::windowMinWidth);
} }
} }
resizeToWidth(newWidth, newContentLeft);
// Widget height depends on InnerWidget height, so we
// resize it here, not in the resizeEvent() handler.
_inner->resizeToWidth(newWidth, newContentLeft);
resizeUsingInnerHeight(newWidth, newContentLeft);
} }
void Widget::onInnerHeightUpdated() { void Widget::resizeUsingInnerHeight(int newWidth, int innerHeight) {
resizeUsingInnerHeight(width(), _contentLeft);
}
void Widget::resizeUsingInnerHeight(int newWidth, int newContentLeft) {
if (!App::wnd()) return; if (!App::wnd()) return;
auto parentSize = parentWidget()->size(); auto parentSize = parentWidget()->size();
int windowWidth = parentSize.width(); auto windowWidth = parentSize.width();
int windowHeight = parentSize.height(); auto windowHeight = parentSize.height();
int maxHeight = st::settingsFixedBarHeight + _inner->height(); auto maxHeight = st::settingsFixedBarHeight + innerHeight;
int newHeight = maxHeight + st::boxRadius; auto newHeight = maxHeight + st::boxRadius;
if (newHeight > windowHeight || newWidth >= windowWidth) { if (newHeight > windowHeight || newWidth >= windowWidth) {
newHeight = windowHeight; newHeight = windowHeight;
} }
if (_contentLeft != newContentLeft) { auto roundedCorners = newHeight < windowHeight;
_contentLeft = newContentLeft; setRoundedCorners(roundedCorners);
} setAttribute(Qt::WA_OpaquePaintEvent, !roundedCorners);
_roundedCorners = (newHeight < windowHeight);
setAttribute(Qt::WA_OpaquePaintEvent, !_roundedCorners);
setGeometry((windowWidth - newWidth) / 2, (windowHeight - newHeight) / 2, newWidth, newHeight); setGeometry((windowWidth - newWidth) / 2, (windowHeight - newHeight) / 2, newWidth, newHeight);
update(); update();
} }
void Widget::showFinished() {
_inner->showFinished();
}
void Widget::paintEvent(QPaintEvent *e) {
Painter p(this);
auto clip = e->rect();
if (_roundedCorners) {
auto paintTopRounded = clip.intersects(QRect(0, 0, width(), st::boxRadius));
auto paintBottomRounded = clip.intersects(QRect(0, height() - st::boxRadius, width(), st::boxRadius));
if (paintTopRounded || paintBottomRounded) {
auto parts = qFlags(App::RectPart::None);
if (paintTopRounded) parts |= App::RectPart::TopFull;
if (paintBottomRounded) parts |= App::RectPart::BottomFull;
App::roundRect(p, rect(), st::boxBg, BoxCorners, nullptr, parts);
}
auto other = clip.intersected(QRect(0, st::boxRadius, width(), height() - 2 * st::boxRadius));
if (!other.isEmpty()) {
p.fillRect(other, st::boxBg);
}
} else {
p.fillRect(e->rect(), st::boxBg);
}
}
void Widget::resizeEvent(QResizeEvent *e) {
LayerWidget::resizeEvent(e);
if (!width() || !height()) {
return;
}
_fixedBar->resizeToWidth(width());
_fixedBar->moveToLeft(0, st::boxRadius);
_fixedBarClose->moveToRight(0, 0);
auto shadowTop = _fixedBar->y() + _fixedBar->height();
_fixedBarShadow->entity()->resize(width(), st::lineWidth);
_fixedBarShadow->moveToLeft(0, shadowTop);
auto scrollSize = QSize(width(), height() - shadowTop - (_roundedCorners ? st::boxRadius : 0));
if (_scroll->size() != scrollSize) {
_scroll->resize(scrollSize);
}
if (!_scroll->isHidden()) {
auto scrollTop = _scroll->scrollTop();
_inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height());
}
}
void Widget::keyPressEvent(QKeyEvent *e) {
codesFeedString(e->text());
return LayerWidget::keyPressEvent(e);
}
} // namespace Settings } // namespace Settings

View file

@ -20,51 +20,30 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/ */
#pragma once #pragma once
#include "layerwidget.h" #include "settings/settings_layer.h"
class BoxLayerTitleShadow;
namespace Ui {
class ScrollArea;
class IconButton;
template <typename Widget>
class WidgetFadeWrap;
} // namespace Ui
namespace Settings { namespace Settings {
class InnerWidget; class InnerWidget;
class FixedBar;
class Widget : public LayerWidget { class Widget : public Layer {
Q_OBJECT Q_OBJECT
public: public:
Widget(QWidget *parent); Widget(QWidget*);
void parentResized() override;
void showFinished() override; void showFinished() override;
void parentResized() override;
protected: protected:
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
void keyPressEvent(QKeyEvent *e) override; void keyPressEvent(QKeyEvent *e) override;
private slots:
void onInnerHeightUpdated();
void onScroll();
private: private:
void resizeUsingInnerHeight(int newWidth, int newContentLeft); void resizeUsingInnerHeight(int newWidth, int innerHeight) override;
object_ptr<Ui::ScrollArea> _scroll;
QPointer<InnerWidget> _inner; QPointer<InnerWidget> _inner;
object_ptr<FixedBar> _fixedBar;
object_ptr<Ui::IconButton> _fixedBarClose;
object_ptr<Ui::WidgetFadeWrap<BoxLayerTitleShadow>> _fixedBarShadow;
int _contentLeft = 0; int _contentLeft = 0;
bool _roundedCorners = false;
}; };

View file

@ -86,7 +86,7 @@ void applyArchivedResult(const MTPDmessages_stickerSetInstallResultArchive &d) {
toast.text = lang(lng_stickers_packs_archived); toast.text = lang(lng_stickers_packs_archived);
toast.maxWidth = st::stickersToastMaxWidth; toast.maxWidth = st::stickersToastMaxWidth;
toast.padding = st::stickersToastPadding; toast.padding = st::stickersToastPadding;
Ui::Toast::Show(App::wnd(), toast); Ui::Toast::Show(toast);
// Ui::show(Box<StickersBox>(archived), KeepOtherLayers); // Ui::show(Box<StickersBox>(archived), KeepOtherLayers);
emit App::main()->stickersUpdated(); emit App::main()->stickersUpdated();

View file

@ -35,7 +35,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "localstorage.h" #include "localstorage.h"
#include "history/history_media_types.h" #include "history/history_media_types.h"
#include "styles/style_history.h" #include "styles/style_history.h"
#include "window/window_theme.h" #include "window/themes/window_theme.h"
namespace { namespace {

View file

@ -1169,7 +1169,7 @@ public:
return (type == AnimatedDocument) && !mime.compare(qstr("video/mp4"), Qt::CaseInsensitive); return (type == AnimatedDocument) && !mime.compare(qstr("video/mp4"), Qt::CaseInsensitive);
} }
bool isTheme() const { bool isTheme() const {
return name.endsWith(qstr(".tdesktop-theme"), Qt::CaseInsensitive); return name.endsWith(qstr(".tdesktop-theme"), Qt::CaseInsensitive) || name.endsWith(qstr(".tdesktop-palette"), Qt::CaseInsensitive);
} }
bool isMusic() const { bool isMusic() const {
if (auto s = song()) { if (auto s = song()) {

View file

@ -25,6 +25,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "localstorage.h" #include "localstorage.h"
#include "platform/platform_file_dialog.h" #include "platform/platform_file_dialog.h"
#include "core/task_queue.h"
void filedialogInit() { void filedialogInit() {
if (cDialogLastPath().isEmpty()) { if (cDialogLastPath().isEmpty()) {
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
@ -384,4 +386,67 @@ base::Observable<QueryUpdate> &QueryDone() {
return QueryDoneObservable; return QueryDoneObservable;
} }
void askOpenPath(const QString &caption, const QString &filter, base::lambda<void(const OpenResult &result)> &&callback, base::lambda<void()> &&failed) {
base::TaskQueue::Main().Put([caption, filter, callback = std_::move(callback), failed = std_::move(failed)] {
auto file = QString();
auto remoteContent = QByteArray();
if (filedialogGetOpenFile(file, remoteContent, caption, filter) && (!file.isEmpty() || !remoteContent.isEmpty())) {
if (callback) {
auto result = OpenResult();
if (!file.isEmpty()) {
result.paths.push_back(file);
}
result.remoteContent = remoteContent;
callback(result);
}
} else if (failed) {
failed();
}
});
}
void askOpenPaths(const QString &caption, const QString &filter, base::lambda<void(const OpenResult &result)> &&callback, base::lambda<void()> &&failed) {
base::TaskQueue::Main().Put([caption, filter, callback = std_::move(callback), failed = std_::move(failed)] {
auto files = QStringList();
auto remoteContent = QByteArray();
if (filedialogGetOpenFiles(files, remoteContent, caption, filter) && (!files.isEmpty() || !remoteContent.isEmpty())) {
if (callback) {
auto result = OpenResult();
result.paths = files;
result.remoteContent = remoteContent;
callback(result);
}
} else if (failed) {
failed();
}
});
}
void askWritePath(const QString &caption, const QString &filter, const QString &initialPath, base::lambda<void(const QString &result)> &&callback, base::lambda<void()> &&failed) {
base::TaskQueue::Main().Put([caption, filter, initialPath, callback = std_::move(callback), failed = std_::move(failed)] {
auto file = QString();
if (filedialogGetSaveFile(file, caption, filter, initialPath)) {
if (callback) {
callback(file);
}
} else if (failed) {
failed();
}
});
}
void askFolder(const QString &caption, base::lambda<void(const QString &result)> &&callback, base::lambda<void()> &&failed) {
base::TaskQueue::Main().Put([caption, callback = std_::move(callback), failed = std_::move(failed)] {
auto folder = QString();
if (filedialogGetDir(folder, caption) && !folder.isEmpty()) {
if (callback) {
callback(folder);
}
} else if (failed) {
failed();
}
});
}
} // namespace FileDialog } // namespace FileDialog

View file

@ -65,4 +65,13 @@ bool processQuery();
base::Observable<QueryUpdate> &QueryDone(); base::Observable<QueryUpdate> &QueryDone();
struct OpenResult {
QStringList paths;
QByteArray remoteContent;
};
void askOpenPath(const QString &caption, const QString &filter, base::lambda<void(const OpenResult &result)> &&callback, base::lambda<void()> &&failed = base::lambda<void()>());
void askOpenPaths(const QString &caption, const QString &filter, base::lambda<void(const OpenResult &result)> &&callback, base::lambda<void()> &&failed = base::lambda<void()>());
void askWritePath(const QString &caption, const QString &filter, const QString &initialPath, base::lambda<void(const QString &result)> &&callback, base::lambda<void()> &&failed = base::lambda<void()>());
void askFolder(const QString &caption, base::lambda<void(const QString &result)> &&callback, base::lambda<void()> &&failed = base::lambda<void()>());
} // namespace FileDialog } // namespace FileDialog

View file

@ -115,6 +115,20 @@ void colorizeImage(const QImage &src, QColor c, QImage *outResult, QRect srcRect
outResult->setDevicePixelRatio(src.devicePixelRatio()); outResult->setDevicePixelRatio(src.devicePixelRatio());
} }
QBrush transparentPlaceholderBrush() {
auto size = st::transparentPlaceholderSize * cIntRetinaFactor();
auto transparent = QImage(2 * size, 2 * size, QImage::Format_ARGB32_Premultiplied);
transparent.fill(st::mediaviewTransparentBg->c);
{
Painter p(&transparent);
p.fillRect(rtlrect(0, size, size, size, 2 * size), st::mediaviewTransparentFg);
p.fillRect(rtlrect(size, 0, size, size, 2 * size), st::mediaviewTransparentFg);
}
transparent.setDevicePixelRatio(cRetinaFactor());
return QBrush(transparent);
}
namespace internal { namespace internal {
QImage createCircleMask(int size, QColor bg, QColor fg) { QImage createCircleMask(int size, QColor bg, QColor fg) {

View file

@ -78,6 +78,8 @@ inline QImage colorizeImage(const QImage &src, const color &c, QRect srcRect = Q
return colorizeImage(src, c->c, srcRect); return colorizeImage(src, c->c, srcRect);
} }
QBrush transparentPlaceholderBrush();
namespace internal { namespace internal {
QImage createCircleMask(int size, QColor bg, QColor fg); QImage createCircleMask(int size, QColor bg, QColor fg);

View file

@ -23,6 +23,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "ui/toast/toast_manager.h" #include "ui/toast/toast_manager.h"
#include "ui/toast/toast_widget.h" #include "ui/toast/toast_widget.h"
#include "mainwindow.h"
namespace Ui { namespace Ui {
namespace Toast { namespace Toast {
@ -40,6 +41,18 @@ void Show(QWidget *parent, const Config &config) {
} }
} }
void Show(const Config &config) {
if (auto window = App::wnd()) {
Show(window->bodyWidget(), config);
}
}
void Show(const QString &text) {
Config toast;
toast.text = text;
Show(toast);
}
void Instance::opacityAnimationCallback() { void Instance::opacityAnimationCallback() {
_widget->setShownLevel(_a_opacity.current(_hiding ? 0. : 1.)); _widget->setShownLevel(_a_opacity.current(_hiding ? 0. : 1.));
_widget->update(); _widget->update();

View file

@ -36,6 +36,8 @@ struct Config {
QMargins padding; QMargins padding;
}; };
void Show(QWidget *parent, const Config &config); void Show(QWidget *parent, const Config &config);
void Show(const Config &config);
void Show(const QString &text);
class Instance { class Instance {
struct Private { struct Private {

View file

@ -40,6 +40,10 @@ Manager::Manager(QWidget *parent) : QObject(parent) {
} }
Manager *Manager::instance(QWidget *parent) { Manager *Manager::instance(QWidget *parent) {
if (!parent) {
return nullptr;
}
_managers.createIfNull(); _managers.createIfNull();
auto i = _managers->constFind(parent); auto i = _managers->constFind(parent);
if (i == _managers->cend()) { if (i == _managers->cend()) {

View file

@ -632,14 +632,19 @@ CrossButton::CrossButton(QWidget *parent, const style::CrossButton &st) : Ripple
hide(); hide();
} }
void CrossButton::hideAnimated() {
startAnimation(false);
}
void CrossButton::showAnimated() { void CrossButton::showAnimated() {
startAnimation(true); startAnimation(true);
} }
void CrossButton::showFast() {
showAnimated();
_a_show.finish();
}
void CrossButton::hideAnimated() {
startAnimation(false);
}
void CrossButton::hideFast() { void CrossButton::hideFast() {
hideAnimated(); hideAnimated();
_a_show.finish(); _a_show.finish();

View file

@ -213,6 +213,7 @@ public:
CrossButton(QWidget *parent, const style::CrossButton &st); CrossButton(QWidget *parent, const style::CrossButton &st);
void showAnimated(); void showAnimated();
void showFast();
void hideAnimated(); void hideAnimated();
void hideFast(); void hideFast();

View file

@ -24,7 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "ui/widgets/popup_menu.h" #include "ui/widgets/popup_menu.h"
#include "mainwindow.h" #include "mainwindow.h"
#include "ui/countryinput.h" #include "ui/countryinput.h"
#include "window/window_theme.h" #include "window/themes/window_theme.h"
#include "lang.h" #include "lang.h"
#include "numbers.h" #include "numbers.h"

View file

@ -586,7 +586,7 @@ attentionLeftOutlineButton: OutlineButton(defaultLeftOutlineButton) {
textBgOver: attentionButtonBgOver; textBgOver: attentionButtonBgOver;
textFg: attentionButtonFg; textFg: attentionButtonFg;
textFgOver: attentionButtonFg; textFgOver: attentionButtonFgOver;
ripple: RippleAnimation(defaultRippleAnimation) { ripple: RippleAnimation(defaultRippleAnimation) {
color: attentionButtonBgRipple; color: attentionButtonBgRipple;

View file

@ -24,7 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "localstorage.h" #include "localstorage.h"
#include "styles/style_window.h" #include "styles/style_window.h"
#include "platform/platform_window_title.h" #include "platform/platform_window_title.h"
#include "window/window_theme.h" #include "window/themes/window_theme.h"
#include "mediaview.h" #include "mediaview.h"
#include "mainwindow.h" #include "mainwindow.h"
@ -248,11 +248,16 @@ void MainWindow::resizeEvent(QResizeEvent *e) {
void MainWindow::updateControlsGeometry() { void MainWindow::updateControlsGeometry() {
auto bodyTop = 0; auto bodyTop = 0;
auto bodyWidth = width();
if (_title && !_title->isHidden()) { if (_title && !_title->isHidden()) {
_title->setGeometry(0, bodyTop, width(), _title->height()); _title->setGeometry(0, bodyTop, width(), _title->height());
bodyTop += _title->height(); bodyTop += _title->height();
} }
_body->setGeometry(0, bodyTop, width(), height() - bodyTop); if (_rightColumn) {
bodyWidth -= _rightColumn->width();
_rightColumn->setGeometry(bodyWidth, bodyTop, width() - bodyWidth, height() - bodyTop);
}
_body->setGeometry(0, bodyTop, bodyWidth, height() - bodyTop);
} }
void MainWindow::updateUnreadCounter() { void MainWindow::updateUnreadCounter() {
@ -276,7 +281,7 @@ void MainWindow::savePosition(Qt::WindowState state) {
auto r = geometry(); auto r = geometry();
curPos.x = r.x(); curPos.x = r.x();
curPos.y = r.y(); curPos.y = r.y();
curPos.w = r.width(); curPos.w = r.width() - (_rightColumn ? _rightColumn->width() : 0);
curPos.h = r.height(); curPos.h = r.height();
curPos.maximized = 0; curPos.maximized = 0;
} }
@ -317,6 +322,35 @@ bool MainWindow::minimizeToTray() {
return true; return true;
} }
void MainWindow::showRightColumn(object_ptr<TWidget> widget) {
auto wasWidth = width();
auto wasRightWidth = _rightColumn ? _rightColumn->width() : 0;
_rightColumn = std_::move(widget);
if (_rightColumn) {
_rightColumn->setParent(this);
_rightColumn->show();
_rightColumn->setFocus();
} else if (App::wnd()) {
App::wnd()->setInnerFocus();
}
auto nowRightWidth = _rightColumn ? _rightColumn->width() : 0;
setMinimumWidth(st::windowMinWidth + nowRightWidth);
auto nowWidth = width();
if (!isMaximized()) {
auto desktop = QDesktopWidget().availableGeometry(this);
auto newWidth = qMin(wasWidth + nowRightWidth - wasRightWidth, desktop.width());
auto newLeft = qMin(x(), desktop.x() + desktop.width() - newWidth);
if (x() != newLeft || width() != newWidth) {
setGeometry(newLeft, y(), newWidth, height());
} else {
updateControlsGeometry();
}
} else {
updateControlsGeometry();
}
}
void MainWindow::documentUpdated(DocumentData *doc) { void MainWindow::documentUpdated(DocumentData *doc) {
if (!_mediaView || _mediaView->isHidden()) return; if (!_mediaView || _mediaView->isHidden()) return;
_mediaView->documentUpdated(doc); _mediaView->documentUpdated(doc);

View file

@ -68,6 +68,8 @@ public:
QWidget *filedialogParent(); QWidget *filedialogParent();
void showRightColumn(object_ptr<TWidget> widget);
virtual void updateTrayMenu(bool force = false) { virtual void updateTrayMenu(bool force = false) {
} }
@ -125,6 +127,8 @@ protected:
virtual void showTrayTooltip() { virtual void showTrayTooltip() {
} }
virtual void updateControlsGeometry();
// This one is overriden in Windows for historical reasons. // This one is overriden in Windows for historical reasons.
virtual int32 screenNameChecksum(const QString &name) const; virtual int32 screenNameChecksum(const QString &name) const;
@ -143,7 +147,6 @@ private slots:
private: private:
void updatePalette(); void updatePalette();
void updateControlsGeometry();
void updateUnreadCounter(); void updateUnreadCounter();
void initSize(); void initSize();
@ -154,6 +157,7 @@ private:
object_ptr<TitleWidget> _title = { nullptr }; object_ptr<TitleWidget> _title = { nullptr };
object_ptr<TWidget> _body; object_ptr<TWidget> _body;
object_ptr<TWidget> _rightColumn = { nullptr };
QString _titleText; QString _titleText;

View file

@ -28,7 +28,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/widgets/input_fields.h" #include "ui/widgets/input_fields.h"
#include "dialogs/dialogs_layout.h" #include "dialogs/dialogs_layout.h"
#include "window/window_theme.h" #include "window/themes/window_theme.h"
#include "styles/style_dialogs.h" #include "styles/style_dialogs.h"
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
#include "styles/style_window.h" #include "styles/style_window.h"

View file

@ -19,7 +19,7 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/ */
#include "stdafx.h" #include "stdafx.h"
#include "window/window_theme.h" #include "window/themes/window_theme.h"
#include "mainwidget.h" #include "mainwidget.h"
#include "localstorage.h" #include "localstorage.h"
@ -27,6 +27,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "core/zlib_help.h" #include "core/zlib_help.h"
#include "styles/style_widgets.h" #include "styles/style_widgets.h"
#include "styles/style_history.h" #include "styles/style_history.h"
#include "boxes/backgroundbox.h"
namespace Window { namespace Window {
namespace Theme { namespace Theme {
@ -51,6 +52,13 @@ struct Data {
}; };
NeverFreedPointer<Data> instance; NeverFreedPointer<Data> instance;
inline bool AreTestingTheme() {
if (instance) {
return !instance->applying.paletteForRevert.isEmpty();
}
return false;
};
QByteArray readThemeContent(const QString &path) { QByteArray readThemeContent(const QString &path) {
QFile file(path); QFile file(path);
if (!file.exists()) { if (!file.exists()) {
@ -63,7 +71,7 @@ QByteArray readThemeContent(const QString &path) {
return QByteArray(); return QByteArray();
} }
if (!file.open(QIODevice::ReadOnly)) { if (!file.open(QIODevice::ReadOnly)) {
LOG(("Theme Warning: could not open theme file: %1").arg(path)); LOG(("Theme Error: could not open theme file: %1").arg(path));
return QByteArray(); return QByteArray();
} }
@ -102,7 +110,7 @@ bool readNameAndValue(const char *&from, const char *end, QLatin1String *outName
return false; return false;
} }
if (*from != ':') { if (*from != ':') {
LOG(("Theme Error: Expected ':' between each name and value in the color scheme.")); LOG(("Theme Error: Expected ':' between each name and value in the color scheme (while reading key '%1')").arg(*outName));
return false; return false;
} }
if (!skipWhitespaces(++from, end)) { if (!skipWhitespaces(++from, end)) {
@ -113,7 +121,7 @@ bool readNameAndValue(const char *&from, const char *end, QLatin1String *outName
if (*from == '#') ++from; if (*from == '#') ++from;
if (readName(from, end).size() == 0) { if (readName(from, end).size() == 0) {
LOG(("Theme Error: Expected a color value in #rrggbb or #rrggbbaa format in the color scheme.")); LOG(("Theme Error: Expected a color value in #rrggbb or #rrggbbaa format in the color scheme (while reading key '%1')").arg(*outName));
return false; return false;
} }
*outValue = QLatin1String(valueStart, from - valueStart); *outValue = QLatin1String(valueStart, from - valueStart);
@ -123,7 +131,7 @@ bool readNameAndValue(const char *&from, const char *end, QLatin1String *outName
return false; return false;
} }
if (*from != ';') { if (*from != ';') {
LOG(("Theme Error: Expected ';' after each value in the color scheme.")); LOG(("Theme Error: Expected ';' after each value in the color scheme (while reading key '%1')").arg(*outName));
return false; return false;
} }
++from; ++from;
@ -136,7 +144,7 @@ enum class SetResult {
NotFound, NotFound,
}; };
SetResult setColorSchemeValue(QLatin1String name, QLatin1String value, Instance *out) { SetResult setColorSchemeValue(QLatin1String name, QLatin1String value, Instance *out) {
auto found = false; auto result = style::palette::SetResult::Ok;
auto size = value.size(); auto size = value.size();
auto data = value.data(); auto data = value.data();
if (data[0] == '#' && (size == 7 || size == 9)) { if (data[0] == '#' && (size == 7 || size == 9)) {
@ -146,41 +154,39 @@ SetResult setColorSchemeValue(QLatin1String name, QLatin1String value, Instance
auto b = readHexUchar(data[5], data[6], error); auto b = readHexUchar(data[5], data[6], error);
auto a = (size == 9) ? readHexUchar(data[7], data[8], error) : uchar(255); auto a = (size == 9) ? readHexUchar(data[7], data[8], error) : uchar(255);
if (error) { if (error) {
LOG(("Theme Error: Expected a color value in #rrggbb or #rrggbbaa format in the color scheme (while applying '%1: %2')").arg(QLatin1String(name)).arg(QLatin1String(value))); LOG(("Theme Warning: Skipping value '%1: %2' (expected a color value in #rrggbb or #rrggbbaa or a previously defined key in the color scheme)").arg(name).arg(value));
return SetResult::Bad; return SetResult::Ok;
} else if (out) { } else if (out) {
found = out->palette.setColor(name, r, g, b, a); result = out->palette.setColor(name, r, g, b, a);
} else { } else {
found = style::main_palette::setColor(name, r, g, b, a); result = style::main_palette::setColor(name, r, g, b, a);
} }
} else { } else {
if (out) { if (out) {
found = out->palette.setColor(name, value); result = out->palette.setColor(name, value);
} else { } else {
found = style::main_palette::setColor(name, value); result = style::main_palette::setColor(name, value);
} }
} }
return found ? SetResult::Ok : SetResult::NotFound; if (result == style::palette::SetResult::Ok) {
return SetResult::Ok;
} else if (result == style::palette::SetResult::KeyNotFound) {
return SetResult::NotFound;
} else if (result == style::palette::SetResult::ValueNotFound) {
LOG(("Theme Warning: Skipping value '%1: %2' (expected a color value in #rrggbb or #rrggbbaa or a previously defined key in the color scheme)").arg(name).arg(value));
return SetResult::Ok;
} else if (result == style::palette::SetResult::Duplicate) {
LOG(("Theme Warning: Color value appears more than once in the color scheme (while applying '%1: %2')").arg(name).arg(value));
return SetResult::Ok;
} else {
LOG(("Theme Error: Unexpected internal error."));
}
return SetResult::Bad;
} }
bool loadColorScheme(const QByteArray &content, Instance *out = nullptr) { bool loadColorScheme(const QByteArray &content, Instance *out) {
if (content.size() > kThemeSchemeSizeLimit) { auto unsupported = QMap<QLatin1String, QLatin1String>();
LOG(("Theme Error: color scheme file too large (should be less than 1 MB, got %2)").arg(content.size())); return ReadPaletteValues(content, [&unsupported, out](QLatin1String name, QLatin1String value) {
return false;
}
QMap<QLatin1String, QLatin1String> unsupported;
auto data = base::parse::stripComments(content);
auto from = data.constData(), end = from + data.size();
while (from != end) {
QLatin1String name(""), value("");
if (!readNameAndValue(from, end, &name, &value)) {
return false;
}
if (name.size() == 0) { // End of content reached.
return true;
}
// Find the named value in the already read unsupported list. // Find the named value in the already read unsupported list.
value = unsupported.value(value, value); value = unsupported.value(value, value);
@ -188,11 +194,10 @@ bool loadColorScheme(const QByteArray &content, Instance *out = nullptr) {
if (result == SetResult::Bad) { if (result == SetResult::Bad) {
return false; return false;
} else if (result == SetResult::NotFound) { } else if (result == SetResult::NotFound) {
LOG(("Theme Warning: unexpected name or value in the color scheme (while applying '%1: %2')").arg(name).arg(value));
unsupported.insert(name, value); unsupported.insert(name, value);
} }
} return true;
return true; });
} }
void applyBackground(QImage &&background, bool tiled, Instance *out) { void applyBackground(QImage &&background, bool tiled, Instance *out) {
@ -276,8 +281,12 @@ bool loadTheme(const QByteArray &content, Cached &cache, Instance *out = nullptr
file.getGlobalInfo(&globalInfo); file.getGlobalInfo(&globalInfo);
if (file.error() == UNZ_OK) { if (file.error() == UNZ_OK) {
auto schemeContent = file.readFileContent("colors.tdesktop-theme", zlib::kCaseInsensitive, kThemeSchemeSizeLimit); auto schemeContent = file.readFileContent("colors.tdesktop-theme", zlib::kCaseInsensitive, kThemeSchemeSizeLimit);
if (file.error() == UNZ_END_OF_LIST_OF_FILE) {
file.clearError();
schemeContent = file.readFileContent("colors.tdesktop-palette", zlib::kCaseInsensitive, kThemeSchemeSizeLimit);
}
if (file.error() != UNZ_OK) { if (file.error() != UNZ_OK) {
LOG(("Theme Error: could not read 'colors.tdesktop-theme' in the theme file.")); LOG(("Theme Error: could not read 'colors.tdesktop-theme' or 'colors.tdesktop-palette' in the theme file."));
return false; return false;
} }
if (!loadColorScheme(schemeContent, out)) { if (!loadColorScheme(schemeContent, out)) {
@ -330,13 +339,13 @@ QImage prepareBackgroundImage(QImage &&image) {
return std_::move(image); return std_::move(image);
} }
void initColor(style::color color, float64 hue, float64 saturation) { void adjustColor(style::color color, float64 hue, float64 saturation) {
auto original = color->c; auto original = color->c;
original.setHslF(hue, saturation, original.lightnessF(), original.alphaF()); original.setHslF(hue, saturation, original.lightnessF(), original.alphaF());
color.set(original.red(), original.green(), original.blue(), original.alpha()); color.set(original.red(), original.green(), original.blue(), original.alpha());
} }
void initColorsFromBackground(const QImage &img) { void adjustColorsUsingBackground(const QImage &img) {
t_assert(img.format() == QImage::Format_ARGB32_Premultiplied); t_assert(img.format() == QImage::Format_ARGB32_Premultiplied);
uint64 components[3] = { 0 }; uint64 components[3] = { 0 };
@ -361,12 +370,12 @@ void initColorsFromBackground(const QImage &img) {
auto bgColor = QColor(components[0], components[1], components[2]); auto bgColor = QColor(components[0], components[1], components[2]);
auto hue = bgColor.hslHueF(); auto hue = bgColor.hslHueF();
auto saturation = bgColor.hslSaturationF(); auto saturation = bgColor.hslSaturationF();
initColor(st::msgServiceBg, hue, saturation); adjustColor(st::msgServiceBg, hue, saturation);
initColor(st::msgServiceBgSelected, hue, saturation); adjustColor(st::msgServiceBgSelected, hue, saturation);
initColor(st::historyScroll.bg, hue, saturation); adjustColor(st::historyScroll.bg, hue, saturation);
initColor(st::historyScroll.bgOver, hue, saturation); adjustColor(st::historyScroll.bgOver, hue, saturation);
initColor(st::historyScroll.barBg, hue, saturation); adjustColor(st::historyScroll.barBg, hue, saturation);
initColor(st::historyScroll.barBgOver, hue, saturation); adjustColor(st::historyScroll.barBgOver, hue, saturation);
} }
} // namespace } // namespace
@ -392,7 +401,9 @@ void ChatBackground::setImage(int32 id, QImage &&image) {
if (_id == kThemeBackground) { if (_id == kThemeBackground) {
_tile = _themeTile; _tile = _themeTile;
setPreparedImage(QImage(_themeImage)); setPreparedImage(QImage(_themeImage));
} else if (_id == internal::kTestingThemeBackground || _id == internal::kTestingDefaultBackground) { } else if (_id == internal::kTestingThemeBackground
|| _id == internal::kTestingDefaultBackground
|| _id == internal::kTestingEditorBackground) {
if (_id == internal::kTestingDefaultBackground || image.isNull()) { if (_id == internal::kTestingDefaultBackground || image.isNull()) {
image.load(qsl(":/gui/art/bg.jpg")); image.load(qsl(":/gui/art/bg.jpg"));
_id = internal::kTestingDefaultBackground; _id = internal::kTestingDefaultBackground;
@ -420,14 +431,35 @@ void ChatBackground::setImage(int32 id, QImage &&image) {
void ChatBackground::setPreparedImage(QImage &&image) { void ChatBackground::setPreparedImage(QImage &&image) {
image = std_::move(image).convertToFormat(QImage::Format_ARGB32_Premultiplied); image = std_::move(image).convertToFormat(QImage::Format_ARGB32_Premultiplied);
image.setDevicePixelRatio(cRetinaFactor()); image.setDevicePixelRatio(cRetinaFactor());
if (_id != kThemeBackground && _id != internal::kTestingThemeBackground) {
auto colorsFromSomeTheme = Local::hasTheme(); auto adjustColors = [this] {
if (instance && !instance->applying.paletteForRevert.isEmpty()) { auto someCustomThemeApplied = [] {
colorsFromSomeTheme = !instance->applying.path.isEmpty(); if (AreTestingTheme()) {
} return !instance->applying.path.isEmpty();
if (colorsFromSomeTheme || (_id != kDefaultBackground && _id != internal::kTestingDefaultBackground)) { }
initColorsFromBackground(image); return Local::hasTheme();
};
auto usingThemeBackground = [this] {
return (_id == kThemeBackground || _id == internal::kTestingThemeBackground);
};
auto usingDefaultBackground = [this] {
return (_id == kDefaultBackground || _id == internal::kTestingDefaultBackground);
};
auto testingPalette = [] {
if (AreTestingTheme()) {
return IsPaletteTestingPath(instance->applying.path);
}
return !Local::themePaletteAbsolutePath().isEmpty();
};
if (someCustomThemeApplied()) {
return !usingThemeBackground() && !testingPalette();
} }
return !usingDefaultBackground();
};
if (adjustColors()) {
adjustColorsUsingBackground(image);
} }
auto width = image.width(); auto width = image.width();
@ -522,7 +554,13 @@ void ChatBackground::saveForRevert() {
void ChatBackground::setTestingTheme(Instance &&theme) { void ChatBackground::setTestingTheme(Instance &&theme) {
style::main_palette::apply(theme.palette); style::main_palette::apply(theme.palette);
if (!theme.background.isNull() || _id == kThemeBackground) { if (AreTestingTheme() && IsPaletteTestingPath(instance->applying.path)) {
// Grab current background image if it is not already custom
if (_id != kCustomBackground) {
saveForRevert();
setImage(internal::kTestingEditorBackground, std_::move(_pixmap).toImage());
}
} else if (!theme.background.isNull() || _id == kThemeBackground) {
saveForRevert(); saveForRevert();
setImage(internal::kTestingThemeBackground, std_::move(theme.background)); setImage(internal::kTestingThemeBackground, std_::move(theme.background));
setTile(theme.tiled); setTile(theme.tiled);
@ -547,7 +585,12 @@ void ChatBackground::setTestingDefaultTheme() {
} }
void ChatBackground::keepApplied() { void ChatBackground::keepApplied() {
if (_id == internal::kTestingThemeBackground) { if (_id == internal::kTestingEditorBackground) {
_id = kCustomBackground;
_themeImage = QImage();
_themeTile = false;
writeNewBackgroundSettings();
} else if (_id == internal::kTestingThemeBackground) {
_id = kThemeBackground; _id = kThemeBackground;
_themeImage = _pixmap.toImage(); _themeImage = _pixmap.toImage();
_themeTile = _tile; _themeTile = _tile;
@ -565,11 +608,13 @@ void ChatBackground::writeNewBackgroundSettings() {
if (_tile != _tileForRevert) { if (_tile != _tileForRevert) {
Local::writeUserSettings(); Local::writeUserSettings();
} }
Local::writeBackground(_id, QImage()); Local::writeBackground(_id, (_id == kThemeBackground || _id == kDefaultBackground) ? QImage() : _pixmap.toImage());
} }
void ChatBackground::revert() { void ChatBackground::revert() {
if (_id == internal::kTestingThemeBackground || _id == internal::kTestingDefaultBackground) { if (_id == internal::kTestingThemeBackground
|| _id == internal::kTestingDefaultBackground
|| _id == internal::kTestingEditorBackground) {
setTile(_tileForRevert); setTile(_tileForRevert);
setImage(_idForRevert, std_::move(_imageForRevert)); setImage(_idForRevert, std_::move(_imageForRevert));
} else { } else {
@ -639,6 +684,27 @@ void ApplyDefault() {
Background()->setTestingDefaultTheme(); Background()->setTestingDefaultTheme();
} }
bool ApplyEditedPalette(const QString &path, const QByteArray &content) {
Instance out;
if (!loadColorScheme(content, &out)) {
return false;
}
out.cached.colors = out.palette.save();
out.cached.paletteChecksum = style::palette::Checksum();
out.cached.contentChecksum = hashCrc32(content.constData(), content.size());
instance.createIfNull();
instance->applying.path = path;
instance->applying.content = content;
instance->applying.cached = out.cached;
if (instance->applying.paletteForRevert.isEmpty()) {
instance->applying.paletteForRevert = style::main_palette::save();
}
Background()->setTestingTheme(std_::move(out));
KeepApplied();
return true;
}
void KeepApplied() { void KeepApplied() {
if (!instance) { if (!instance) {
return; return;
@ -669,6 +735,13 @@ bool LoadFromFile(const QString &path, Instance *out, QByteArray *outContent) {
return loadTheme(*outContent, out->cached, out); return loadTheme(*outContent, out->cached, out);
} }
bool IsPaletteTestingPath(const QString &path) {
if (path.endsWith(qstr(".tdesktop-palette"), Qt::CaseInsensitive)) {
return QFileInfo(path).exists();
}
return false;
}
void ComputeBackgroundRects(QRect wholeFill, QSize imageSize, QRect &to, QRect &from) { void ComputeBackgroundRects(QRect wholeFill, QSize imageSize, QRect &to, QRect &from) {
if (uint64(imageSize.width()) * wholeFill.height() > uint64(imageSize.height()) * wholeFill.width()) { if (uint64(imageSize.width()) * wholeFill.height() > uint64(imageSize.height()) * wholeFill.width()) {
float64 pxsize = wholeFill.height() / float64(imageSize.height()); float64 pxsize = wholeFill.height() / float64(imageSize.height());
@ -693,5 +766,61 @@ void ComputeBackgroundRects(QRect wholeFill, QSize imageSize, QRect &to, QRect &
} }
} }
bool CopyColorsToPalette(const QString &path, const QByteArray &themeContent) {
auto paletteContent = themeContent;
zlib::FileToRead file(themeContent);
unz_global_info globalInfo = { 0 };
file.getGlobalInfo(&globalInfo);
if (file.error() == UNZ_OK) {
paletteContent = file.readFileContent("colors.tdesktop-theme", zlib::kCaseInsensitive, kThemeSchemeSizeLimit);
if (file.error() == UNZ_END_OF_LIST_OF_FILE) {
file.clearError();
paletteContent = file.readFileContent("colors.tdesktop-palette", zlib::kCaseInsensitive, kThemeSchemeSizeLimit);
}
if (file.error() != UNZ_OK) {
LOG(("Theme Error: could not read 'colors.tdesktop-theme' or 'colors.tdesktop-palette' in the theme file, while copying to '%1'.").arg(path));
return false;
}
}
QFile f(path);
if (!f.open(QIODevice::WriteOnly)) {
LOG(("Theme Error: could not open file for write '%1'").arg(path));
return false;
}
if (f.write(paletteContent) != paletteContent.size()) {
LOG(("Theme Error: could not write palette to '%1'").arg(path));
return false;
}
return true;
}
bool ReadPaletteValues(const QByteArray &content, base::lambda<bool(QLatin1String name, QLatin1String value)> &&callback) {
if (content.size() > kThemeSchemeSizeLimit) {
LOG(("Theme Error: color scheme file too large (should be less than 1 MB, got %2)").arg(content.size()));
return false;
}
auto data = base::parse::stripComments(content);
auto from = data.constData(), end = from + data.size();
while (from != end) {
auto name = QLatin1String("");
auto value = QLatin1String("");
if (!readNameAndValue(from, end, &name, &value)) {
return false;
}
if (name.size() == 0) { // End of content reached.
return true;
}
if (!callback(name, value)) {
return false;
}
}
return true;
}
} // namespace Theme } // namespace Theme
} // namespace Window } // namespace Window

View file

@ -27,6 +27,7 @@ namespace internal {
constexpr int32 kUninitializedBackground = -999; constexpr int32 kUninitializedBackground = -999;
constexpr int32 kTestingThemeBackground = -666; constexpr int32 kTestingThemeBackground = -666;
constexpr int32 kTestingDefaultBackground = -665; constexpr int32 kTestingDefaultBackground = -665;
constexpr int32 kTestingEditorBackground = -664;
} // namespace internal } // namespace internal
@ -62,10 +63,12 @@ struct Preview {
bool Apply(const QString &filepath); bool Apply(const QString &filepath);
bool Apply(std_::unique_ptr<Preview> preview); bool Apply(std_::unique_ptr<Preview> preview);
void ApplyDefault(); void ApplyDefault();
bool ApplyEditedPalette(const QString &path, const QByteArray &content);
void KeepApplied(); void KeepApplied();
void Revert(); void Revert();
bool LoadFromFile(const QString &file, Instance *out, QByteArray *outContent); bool LoadFromFile(const QString &file, Instance *out, QByteArray *outContent);
bool IsPaletteTestingPath(const QString &path);
struct BackgroundUpdate { struct BackgroundUpdate {
enum class Type { enum class Type {
@ -136,5 +139,9 @@ ChatBackground *Background();
void ComputeBackgroundRects(QRect wholeFill, QSize imageSize, QRect &to, QRect &from); void ComputeBackgroundRects(QRect wholeFill, QSize imageSize, QRect &to, QRect &from);
bool CopyColorsToPalette(const QString &path, const QByteArray &themeContent);
bool ReadPaletteValues(const QByteArray &content, base::lambda<bool(QLatin1String name, QLatin1String value)> &&callback);
} // namespace Theme } // namespace Theme
} // namespace Window } // namespace Window

View file

@ -0,0 +1,817 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "stdafx.h"
#include "window/themes/window_theme_editor.h"
#include "window/themes/window_theme.h"
#include "window/themes/window_theme_editor_block.h"
#include "mainwindow.h"
#include "localstorage.h"
#include "boxes/confirmbox.h"
#include "styles/style_window.h"
#include "styles/style_settings.h"
#include "styles/style_dialogs.h"
#include "styles/style_boxes.h"
#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 "core/parse_helper.h"
#include "core/task_queue.h"
#include "core/zlib_help.h"
#include "ui/toast/toast.h"
#include "ui/filedialog.h"
#include "boxes/editcolorbox.h"
#include "lang.h"
namespace Window {
namespace Theme {
namespace {
struct ReadColorResult {
ReadColorResult(QColor color, bool error = false) : color(color), error(error) {
}
QColor color;
bool error = false;
};
ReadColorResult colorError(const QString &name) {
return { QColor(), true };
}
ReadColorResult readColor(const QString &name, const char *data, int size) {
if (size != 6 && size != 8) {
return colorError(name);
}
auto readHex = [](char ch) {
if (ch >= '0' && ch <= '9') {
return (ch - '0');
} else if (ch >= 'a' && ch <= 'f') {
return (ch - 'a' + 10);
} else if (ch >= 'A' && ch <= 'F') {
return (ch - 'A' + 10);
}
return -1;
};
auto readValue = [readHex](const char *data) {
auto high = readHex(data[0]);
auto low = readHex(data[1]);
return (high >= 0 && low >= 0) ? (high * 0x10 + low) : -1;
};
auto r = readValue(data);
auto g = readValue(data + 2);
auto b = readValue(data + 4);
auto a = (size == 8) ? readValue(data + 6) : 255;
if (r < 0 || g < 0 || b < 0 || a < 0) {
return colorError(name);
}
return { QColor(r, g, b, a) };
}
bool skipComment(const char *&data, const char *end) {
if (data == end) return false;
if (*data == '/' && data + 1 != end) {
if (*(data + 1) == '/') {
data += 2;
while (data != end && *data != '\n') {
++data;
}
return true;
} else if (*(data + 1) == '*') {
data += 2;
while (true) {
while (data != end && *data != '*') {
++data;
}
if (data != end) ++data;
if (data == end || *data == '/') {
break;
}
}
return true;
}
}
return false;
}
void skipWhitespacesAndComments(const char *&data, const char *end) {
while (data != end) {
if (!base::parse::skipWhitespaces(data, end)) return;
if (!skipComment(data, end)) return;
}
}
QLatin1String readValue(const char *&data, const char *end) {
auto start = data;
if (data != end && *data == '#') {
++data;
}
base::parse::readName(data, end);
return QLatin1String(start, data - start);
}
bool isValidColorValue(QLatin1String value) {
auto isValidHexChar = [](char ch) {
return (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F') || (ch >= 'a' && ch <= 'f');
};
auto data = value.data();
auto size = value.size();
if ((size != 7 && size != 9) || data[0] != '#') {
return false;
}
for (auto i = 1; i != size; ++i) {
if (!isValidHexChar(data[i])) {
return false;
}
}
return true;
}
QByteArray replaceValueInContent(const QByteArray &content, const QByteArray &name, const QByteArray &value) {
auto validNames = OrderedSet<QLatin1String>();
auto start = content.constBegin(), data = start, end = data + content.size();
auto lastValidValueStart = end, lastValidValueEnd = end;
while (data != end) {
skipWhitespacesAndComments(data, end);
if (data == end) break;
auto foundName = base::parse::readName(data, end);
skipWhitespacesAndComments(data, end);
if (data == end || *data != ':') {
return "error";
}
++data;
skipWhitespacesAndComments(data, end);
auto valueStart = data;
auto value = readValue(data, end);
auto valueEnd = data;
if (value.size() == 0) {
return "error";
}
auto validValue = validNames.contains(value) || isValidColorValue(value);
if (validValue) {
validNames.insert(foundName);
if (foundName == name) {
lastValidValueStart = valueStart;
lastValidValueEnd = valueEnd;
}
}
skipWhitespacesAndComments(data, end);
if (data == end || *data != ';') {
return "error";
}
++data;
}
if (lastValidValueStart != end) {
auto result = QByteArray();
result.reserve((lastValidValueStart - start) + value.size() + (end - lastValidValueEnd));
result.append(start, lastValidValueStart - start);
result.append(value);
if (end - lastValidValueEnd > 0) result.append(lastValidValueEnd, end - lastValidValueEnd);
return result;
}
return QByteArray();
}
QString bytesToUtf8(QLatin1String bytes) {
return QString::fromUtf8(bytes.data(), bytes.size());
}
} // namespace
class Editor::Inner : public TWidget, private base::Subscriber {
public:
Inner(QWidget *parent, const QString &path);
void setErrorCallback(base::lambda<void()> &&callback) {
_errorCallback = std_::move(callback);
}
void setFocusCallback(base::lambda<void()> &&callback) {
_focusCallback = std_::move(callback);
}
void setScrollCallback(base::lambda<void(int top, int bottom)> &&callback) {
_scrollCallback = std_::move(callback);
}
void prepare();
base::lambda<void()> exportCallback();
void filterRows(const QString &query);
void chooseRow();
void selectSkip(int direction);
void selectSkipPage(int delta, int direction);
~Inner() {
if (_context.box) _context.box->closeBox();
}
protected:
void paintEvent(QPaintEvent *e) override;
int resizeGetHeight(int newWidth) override;
private:
bool readData();
bool readExistingRows();
bool feedExistingRow(const QString &name, QLatin1String value);
void error() {
if (_errorCallback) {
_errorCallback();
}
}
void applyEditing(const QString &name, const QString &copyOf, QColor value);
EditorBlock::Context _context;
QString _path;
QByteArray _paletteContent;
base::lambda<void()> _errorCallback;
base::lambda<void()> _focusCallback;
base::lambda<void(int top, int bottom)> _scrollCallback;
object_ptr<EditorBlock> _existingRows;
object_ptr<EditorBlock> _newRows;
bool _applyingUpdate = false;
};
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;
};
Editor::Inner::Inner(QWidget *parent, const QString &path) : TWidget(parent)
, _path(path)
, _existingRows(this, EditorBlock::Type::Existing, &_context)
, _newRows(this, EditorBlock::Type::New, &_context) {
resize(st::windowMinWidth, st::windowMinHeight);
subscribe(_context.resized, [this] {
resizeToWidth(width());
});
subscribe(_context.pending, [this](const EditorBlock::Context::EditionData &data) {
applyEditing(data.name, data.copyOf, data.value);
});
subscribe(_context.updated, [this] {
if (_context.name.isEmpty() && _focusCallback) {
_focusCallback();
}
});
subscribe(_context.scroll, [this](const EditorBlock::Context::ScrollData &data) {
if (_scrollCallback) {
auto top = (data.type == EditorBlock::Type::Existing ? _existingRows : _newRows)->y();
top += data.position;
_scrollCallback(top, top + data.height);
}
});
subscribe(Background(), [this](const BackgroundUpdate &update) {
if (_applyingUpdate) return;
if (update.type == BackgroundUpdate::Type::TestingTheme) {
Revert();
App::CallDelayed(st::slideDuration, this, [] {
Ui::show(Box<InformBox>(lang(lng_theme_editor_cant_change_theme)));
});
}
});
}
void Editor::Inner::prepare() {
if (!readData()) {
error();
}
}
base::lambda<void()> Editor::Inner::exportCallback() {
return App::LambdaDelayed(st::defaultRippleAnimation.hideDuration, this, [this] {
auto background = Background()->pixmap().toImage();
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) {
_existingRows->filterRows(query);
_newRows->filterRows(query);
}
void Editor::Inner::chooseRow() {
if (!_existingRows->hasSelected() && !_newRows->hasSelected()) {
selectSkip(1);
}
if (_existingRows->hasSelected()) {
_existingRows->chooseRow();
} else if (_newRows->hasSelected()) {
_newRows->chooseRow();
}
}
// Block::selectSkip(-1) removes the selection if it can't select anything
// Block::selectSkip(1) leaves the selection if it can't select anything
void Editor::Inner::selectSkip(int direction) {
if (direction > 0) {
if (_newRows->hasSelected()) {
_existingRows->clearSelected();
_newRows->selectSkip(direction);
} else if (_existingRows->hasSelected()) {
if (!_existingRows->selectSkip(direction)) {
if (_newRows->selectSkip(direction)) {
_existingRows->clearSelected();
}
}
} else {
if (!_existingRows->selectSkip(direction)) {
_newRows->selectSkip(direction);
}
}
} else {
if (_existingRows->hasSelected()) {
_newRows->clearSelected();
_existingRows->selectSkip(direction);
} else if (_newRows->hasSelected()) {
if (!_newRows->selectSkip(direction)) {
_existingRows->selectSkip(direction);
}
}
}
}
void Editor::Inner::selectSkipPage(int delta, int direction) {
auto defaultRowHeight = st::themeEditorMargin.top()
+ st::themeEditorSampleSize.height()
+ st::themeEditorDescriptionSkip
+ st::defaultTextStyle.font->height
+ st::themeEditorMargin.bottom();
for (auto i = 0, count = ceilclamp(delta, defaultRowHeight, 1, delta); i != count; ++i) {
selectSkip(direction);
}
}
void Editor::Inner::paintEvent(QPaintEvent *e) {
Painter p(this);
p.setFont(st::settingsFixedBarFont);
p.setPen(st::windowFg);
if (!_newRows->isHidden()) {
p.drawTextLeft(st::themeEditorMargin.left(), _existingRows->y() + _existingRows->height() + st::settingsFixedBarTextPosition.y(), width(), lang(lng_theme_editor_new_keys));
}
}
int Editor::Inner::resizeGetHeight(int newWidth) {
auto rowsWidth = newWidth;
_existingRows->resizeToWidth(rowsWidth);
_newRows->resizeToWidth(rowsWidth);
_existingRows->moveToLeft(0, 0);
_newRows->moveToLeft(0, _existingRows->height() + st::settingsFixedBarHeight);
auto lowest = (_newRows->isHidden() ? _existingRows : _newRows).data();
return lowest->y() + lowest->height();
}
bool Editor::Inner::readData() {
if (!readExistingRows()) {
return false;
}
auto rows = style::main_palette::data();
for_const (auto &row, rows) {
auto name = bytesToUtf8(row.name);
auto description = bytesToUtf8(row.description);
if (!_existingRows->feedDescription(name, description)) {
if (row.value.data()[0] == '#') {
auto result = readColor(name, row.value.data() + 1, row.value.size() - 1);
t_assert(!result.error);
_newRows->feed(name, result.color);
//if (!_newRows->feedFallbackName(name, str_const_toString(row.fallback))) {
// t_assert(!"Row for fallback not found");
//}
} else {
auto copyOf = bytesToUtf8(row.value);
if (auto result = _existingRows->find(copyOf)) {
_newRows->feed(name, *result, copyOf);
} else if (!_newRows->feedCopy(name, copyOf)) {
t_assert(!"Copy of unknown value in the default palette");
}
t_assert(row.fallback.size() == 0);
}
if (!_newRows->feedDescription(name, description)) {
t_assert(!"Row for description not found");
}
}
}
return true;
}
bool Editor::Inner::readExistingRows() {
QFile f(_path);
if (!f.open(QIODevice::ReadOnly)) {
LOG(("Theme Error: could not open color palette file '%1'").arg(_path));
return false;
}
_paletteContent = f.readAll();
if (f.error() != QFileDevice::NoError) {
LOG(("Theme Error: could not read content from palette file '%1'").arg(_path));
return false;
}
f.close();
return ReadPaletteValues(_paletteContent, [this](QLatin1String name, QLatin1String value) {
return feedExistingRow(name, value);
});
}
bool Editor::Inner::feedExistingRow(const QString &name, QLatin1String value) {
auto data = value.data();
auto size = value.size();
if (data[0] != '#') {
return _existingRows->feedCopy(name, QString(value));
}
auto result = readColor(name, data + 1, size - 1);
if (result.error) {
LOG(("Theme Warning: Skipping value '%1: %2' (expected a color value in #rrggbb or #rrggbbaa or a previously defined key in the color scheme)").arg(name).arg(value));
} else {
_existingRows->feed(name, result.color);
}
return true;
}
QString colorString(QColor color) {
auto result = QString();
result.reserve(9);
result.append('#');
auto addHex = [&result](int code) {
if (code >= 0 && code < 10) {
result.append('0' + code);
} else if (code >= 10 && code < 16) {
result.append('a' + (code - 10));
}
};
auto addValue = [addHex](int code) {
addHex(code / 16);
addHex(code % 16);
};
addValue(color.red());
addValue(color.green());
addValue(color.blue());
if (color.alpha() != 255) {
addValue(color.alpha());
}
return result;
}
void Editor::Inner::applyEditing(const QString &name, const QString &copyOf, QColor value) {
auto plainName = name.toLatin1();
auto plainValue = (copyOf.isEmpty() ? colorString(value) : copyOf).toLatin1();
auto newContent = replaceValueInContent(_paletteContent, plainName, plainValue);
if (newContent == "error") {
LOG(("Theme Error: could not replace '%1: %2' in content").arg(name).arg(copyOf.isEmpty() ? colorString(value) : copyOf));
error();
return;
}
if (newContent.isEmpty()) {
auto newline = (_paletteContent.indexOf("\r\n") >= 0 ? "\r\n" : "\n");
auto addedline = (_paletteContent.endsWith('\n') ? "" : newline);
newContent = _paletteContent + addedline + plainName + ": " + plainValue + ";" + newline;
}
QFile f(_path);
if (!f.open(QIODevice::WriteOnly)) {
LOG(("Theme Error: could not open '%1' for writing a palette update.").arg(_path));
error();
return;
}
if (f.write(newContent) != newContent.size()) {
LOG(("Theme Error: could not write all content to '%1' while writing a palette update.").arg(_path));
error();
return;
}
f.close();
_applyingUpdate = true;
if (!ApplyEditedPalette(_path, newContent)) {
LOG(("Theme Error: could not apply newly composed content :("));
error();
return;
}
_applyingUpdate = false;
_paletteContent = newContent;
}
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, rows) {
stream << bytesToUtf8(row.name) << ": " << bytesToUtf8(row.value) << "; // " << bytesToUtf8(row.description).replace('\n', ' ').replace('\r', ' ') << "\n";
}
}
ThemeExportBox::ThemeExportBox(QWidget*, const QByteArray &paletteContent, const QImage &background, const QByteArray &backgroundContent, bool tileBackground) : BoxContent()
, _paletteContent(paletteContent)
, _background(background)
, _backgroundContent(backgroundContent)
, _chooseFromFile(this, lang(lng_settings_bg_from_file), st::boxLinkButton)
, _tileBackground(this, lang(lng_settings_bg_tile), tileBackground, st::defaultBoxCheckbox) {
_imageText = lng_theme_editor_saved_to_jpg(lt_size, formatSizeText(_backgroundContent.size()));
_chooseFromFile->setClickedCallback([this] { chooseBackgroundFromFile(); });
}
void ThemeExportBox::prepare() {
setTitle(lang(lng_theme_editor_background_image));
addButton(lang(lng_theme_editor_export), [this] { exportTheme(); });
addButton(lang(lng_cancel), [this] { closeBox(); });
auto height = st::settingsSmallSkip + st::settingsBackgroundSize + st::settingsSmallSkip + _tileBackground->height();
setDimensions(st::boxWideWidth, height);
updateThumbnail();
}
void ThemeExportBox::paintEvent(QPaintEvent *e) {
BoxContent::paintEvent(e);
Painter p(this);
auto linkLeft = st::boxPadding.left() + st::settingsBackgroundSize + st::settingsSmallSkip;
p.setPen(st::boxTextFg);
p.setFont(st::boxTextFont);
p.drawTextLeft(linkLeft, st::settingsSmallSkip, width(), _imageText);
p.drawPixmapLeft(st::boxPadding.left(), st::settingsSmallSkip, width(), _thumbnail);
}
void ThemeExportBox::resizeEvent(QResizeEvent *e) {
auto linkLeft = st::boxPadding.left() + st::settingsBackgroundSize + st::settingsSmallSkip;
_chooseFromFile->moveToLeft(linkLeft, st::settingsSmallSkip + st::boxTextFont->height + st::settingsSmallSkip);
_tileBackground->moveToLeft(st::boxPadding.left(), st::settingsSmallSkip + st::settingsBackgroundSize + 2 * st::settingsSmallSkip);
}
void ThemeExportBox::updateThumbnail() {
int32 size = st::settingsBackgroundSize * 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::settingsBackgroundSize, st::settingsBackgroundSize), 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::askOpenPath(lang(lng_theme_editor_choose_image), "Image files (*.jpeg *.jpg *.png)", base::lambda_guarded(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");
_imageText = (_isPng ? lng_theme_editor_read_from_png : lng_theme_editor_read_from_jpg)(lt_size, formatSizeText(_backgroundContent.size()));
_tileBackground->setChecked(false);
updateThumbnail();
}
}
}));
}
void ThemeExportBox::exportTheme() {
App::CallDelayed(st::defaultRippleAnimation.hideDuration, this, [this] {
auto caption = lang(lng_theme_editor_choose_name);
auto filter = "Themes (*.tdesktop-theme)";
auto name = "awesome.tdesktop-theme";
FileDialog::askWritePath(caption, filter, name, base::lambda_guarded(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>(lang(lng_theme_editor_error)));
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>(lang(lng_theme_editor_error)));
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>(lang(lng_theme_editor_error)));
return;
}
Ui::hideLayer();
Ui::Toast::Show(lang(lng_theme_editor_done));
}));
});
}
Editor::Editor(QWidget*, const QString &path)
: _scroll(this, st::settingsScroll)
, _close(this, st::contactsMultiSelect.fieldCancel)
, _select(this, st::contactsMultiSelect, lang(lng_country_ph))
, _leftShadow(this)
, _topShadow(this)
, _export(this, lang(lng_theme_editor_export_button).toUpper(), st::dialogsUpdateButton) {
_inner = _scroll->setOwnedWidget(object_ptr<Inner>(this, path));
_export->setClickedCallback(_inner->exportCallback());
_inner->setErrorCallback([this] {
Ui::show(Box<InformBox>(lang(lng_theme_editor_error)));
// This could be from inner->_context observable notification.
// We should not destroy it while iterating in subscribers.
base::TaskQueue::Main().Put(base::lambda_guarded(this, [this] { closeEditor(); }));
});
_inner->setFocusCallback([this] {
App::CallDelayed(2 * st::boxDuration, this, [this] { _select->setInnerFocus(); });
});
_inner->setScrollCallback([this](int top, int bottom) {
_scroll->scrollToY(top, bottom);
});
_close->setClickedCallback([this] { closeEditor(); });
_close->showFast();
_select->resizeToWidth(st::windowMinWidth);
_select->setQueryChangedCallback([this](const QString &query) { _inner->filterRows(query); _scroll->scrollToY(0); });
_select->setSubmittedCallback([this](bool) { _inner->chooseRow(); });
_inner->prepare();
resizeToWidth(st::windowMinWidth);
}
void Editor::StartFromCurrentTheme(const QString &path) {
if (!Local::copyThemeColorsToPalette(path)) {
writeDefaultPalette(path);
}
if (!Apply(path)) {
Ui::show(Box<InformBox>(lang(lng_theme_editor_error)));
return;
}
KeepApplied();
Start(path);
}
void Editor::resizeEvent(QResizeEvent *e) {
_export->resizeToWidth(width());
_close->moveToRight(0, 0);
_select->resizeToWidth(width());
_select->moveToLeft(0, _close->height());
auto shadowTop = _select->y() + _select->height();
_topShadow->resize(width() - st::lineWidth, st::lineWidth);
_topShadow->moveToLeft(st::lineWidth, shadowTop);
_leftShadow->resize(st::lineWidth, height());
_leftShadow->moveToLeft(0, 0);
auto scrollSize = QSize(width(), height() - shadowTop - _export->height());
if (_scroll->size() != scrollSize) {
_scroll->resize(scrollSize);
}
_inner->resizeToWidth(width());
_scroll->moveToLeft(0, shadowTop);
if (!_scroll->isHidden()) {
auto scrollTop = _scroll->scrollTop();
_inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height());
}
_export->moveToLeft(0, _scroll->y() + _scroll->height());
}
void Editor::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Escape) {
if (!_select->getQuery().isEmpty()) {
_select->clearQuery();
} else if (auto window = App::wnd()) {
window->setInnerFocus();
}
} else if (e->key() == Qt::Key_Down) {
_inner->selectSkip(1);
} else if (e->key() == Qt::Key_Up) {
_inner->selectSkip(-1);
} else if (e->key() == Qt::Key_PageDown) {
_inner->selectSkipPage(_scroll->height(), 1);
} else if (e->key() == Qt::Key_PageUp) {
_inner->selectSkipPage(_scroll->height(), -1);
}
}
void Editor::focusInEvent(QFocusEvent *e) {
_select->setInnerFocus();
}
void Editor::paintEvent(QPaintEvent *e) {
Painter p(this);
p.fillRect(e->rect(), st::dialogsBg);
p.setFont(st::settingsFixedBarFont);
p.setPen(st::windowFg);
p.drawTextLeft(st::themeEditorMargin.left(), st::themeEditorMargin.top(), width(), lang(lng_theme_editor_title));
}
void Editor::Start(const QString &path) {
if (auto window = App::wnd()) {
window->showRightColumn(Box<Editor>(path));
}
}
void Editor::closeEditor() {
if (auto window = App::wnd()) {
window->showRightColumn(nullptr);
}
}
} // namespace Theme
} // namespace Window

View file

@ -0,0 +1,66 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
class BoxLayerTitleShadow;
namespace Ui {
class FlatButton;
class ScrollArea;
class CrossButton;
class MultiSelect;
} // namespace Ui
namespace Window {
namespace Theme {
class Editor : public TWidget {
Q_OBJECT
public:
Editor(QWidget*, const QString &path);
static void StartFromCurrentTheme(const QString &path);
static void Start(const QString &path);
protected:
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
void keyPressEvent(QKeyEvent *e) override;
void focusInEvent(QFocusEvent *e) override;
private:
void closeEditor();
object_ptr<Ui::ScrollArea> _scroll;
class Inner;
QPointer<Inner> _inner;
object_ptr<Ui::CrossButton> _close;
object_ptr<Ui::MultiSelect> _select;
object_ptr<BoxLayerTitleShadow> _leftShadow;
object_ptr<BoxLayerTitleShadow> _topShadow;
object_ptr<Ui::FlatButton> _export;
};
} // namespace Theme
} // namespace Window

View file

@ -0,0 +1,778 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "stdafx.h"
#include "window/themes/window_theme_editor_block.h"
#include "styles/style_window.h"
#include "ui/effects/ripple_animation.h"
#include "boxes/editcolorbox.h"
#include "lang.h"
namespace Window {
namespace Theme {
namespace {
auto SearchSplitter = QRegularExpression(qsl("[\\@\\s\\-\\+\\(\\)\\[\\]\\{\\}\\<\\>\\,\\.\\:\\!\\_\\;\\\"\\'\\x0\\#]"));
} // namespace
class EditorBlock::Row {
public:
Row(const QString &name, const QString &copyOf, QColor value);
QString name() const {
return _name;
}
void setCopyOf(const QString &copyOf) {
_copyOf = copyOf;
fillSearchIndex();
}
QString copyOf() const {
return _copyOf;
}
void setValue(QColor value);
const QColor &value() const {
return _value;
}
QString description() const {
return _description.originalText();
}
const Text &descriptionText() const {
return _description;
}
void setDescription(const QString &description) {
_description.setText(st::defaultTextStyle, description);
fillSearchIndex();
}
const OrderedSet<QString> &searchWords() const {
return _searchWords;
}
bool searchWordsContain(const QString &needle) const {
for_const (auto &word, _searchWords) {
if (word.startsWith(needle)) {
return true;
}
}
return false;
}
const OrderedSet<QChar> &searchStartChars() const {
return _searchStartChars;
}
void setTop(int top) {
_top = top;
}
int top() const {
return _top;
}
void setHeight(int height) {
_height = height;
}
int height() const {
return _height;
}
Ui::RippleAnimation *ripple() const {
return _ripple.get();
}
Ui::RippleAnimation *setRipple(std_::unique_ptr<Ui::RippleAnimation> ripple) const {
_ripple = std_::move(ripple);
return _ripple.get();
}
void resetRipple() const {
_ripple = nullptr;
}
private:
void fillValueString();
void fillSearchIndex();
QString _name;
QString _copyOf;
QColor _value;
QString _valueString;
Text _description = { st::windowMinWidth / 2 };
OrderedSet<QString> _searchWords;
OrderedSet<QChar> _searchStartChars;
int _top = 0;
int _height = 0;
mutable std_::unique_ptr<Ui::RippleAnimation> _ripple;
};
EditorBlock::Row::Row(const QString &name, const QString &copyOf, QColor value)
: _name(name)
, _copyOf(copyOf) {
setValue(value);
}
void EditorBlock::Row::setValue(QColor value) {
_value = value;
fillValueString();
fillSearchIndex();
}
void EditorBlock::Row::fillValueString() {
auto addHex = [this](int code) {
if (code >= 0 && code < 10) {
_valueString.append('0' + code);
} else if (code >= 10 && code < 16) {
_valueString.append('a' + (code - 10));
}
};
auto addCode = [this, addHex](int code) {
addHex(code / 16);
addHex(code % 16);
};
_valueString.resize(0);
_valueString.reserve(9);
_valueString.append('#');
addCode(_value.red());
addCode(_value.green());
addCode(_value.blue());
if (_value.alpha() != 255) {
addCode(_value.alpha());
}
}
void EditorBlock::Row::fillSearchIndex() {
_searchWords.clear();
_searchStartChars.clear();
auto toIndex = _name + ' ' + _copyOf + ' ' + textAccentFold(_description.originalText()) + ' ' + _valueString;
auto words = toIndex.toLower().split(SearchSplitter, QString::SkipEmptyParts);
for_const (auto &word, words) {
_searchWords.insert(word);
_searchStartChars.insert(word[0]);
}
}
EditorBlock::EditorBlock(QWidget *parent, Type type, Context *context) : TWidget(parent)
, _type(type)
, _context(context)
, _transparent(style::transparentPlaceholderBrush()) {
setMouseTracking(true);
subscribe(_context->updated, [this] {
if (_mouseSelection) {
_lastGlobalPos = QCursor::pos();
updateSelected(mapFromGlobal(_lastGlobalPos));
}
update();
});
if (_type == Type::Existing) {
subscribe(_context->appended, [this](const Context::AppendData &added) {
auto name = added.name;
auto value = added.value;
feed(name, value);
feedDescription(name, added.description);
auto row = findRow(name);
t_assert(row != nullptr);
auto possibleCopyOf = added.possibleCopyOf;
auto copyOf = checkCopyOf(findRowIndex(row), possibleCopyOf) ? possibleCopyOf : QString();
removeFromSearch(*row);
row->setCopyOf(copyOf);
addToSearch(*row);
_context->changed.notify({ QStringList(name), value }, true);
_context->resized.notify();
_context->pending.notify({ name, copyOf, value }, true);
});
} else {
subscribe(_context->changed, [this](const Context::ChangeData &data) {
checkCopiesChanged(0, data.names, data.value);
});
}
}
void EditorBlock::feed(const QString &name, QColor value, const QString &copyOfExisting) {
if (findRow(name)) {
// Remove the existing row and mark all its copies as unique keys.
LOG(("Theme Warning: Color value '%1' appears more than once in the color scheme.").arg(name));
removeRow(name);
}
addRow(name, copyOfExisting, value);
}
bool EditorBlock::feedCopy(const QString &name, const QString &copyOf) {
if (auto row = findRow(copyOf)) {
if (findRow(name)) {
// Remove the existing row and mark all its copies as unique keys.
LOG(("Theme Warning: Color value '%1' appears more than once in the color scheme.").arg(name));
removeRow(name);
// row was invalidated by removeRow() call.
row = findRow(copyOf);
}
addRow(name, copyOf, row->value());
} else {
LOG(("Theme Warning: Skipping value '%1: %2' (expected a color value in #rrggbb or #rrggbbaa or a previously defined key in the color scheme)").arg(name).arg(copyOf));
}
return true;
}
void EditorBlock::removeRow(const QString &name, bool removeCopyReferences) {
auto it = _indices.find(name);
t_assert(it != _indices.cend());
auto index = it.value();
for (auto i = index + 1, count = _data.size(); i != count; ++i) {
auto &row = _data[i];
removeFromSearch(row);
_indices[row.name()] = i - 1;
if (removeCopyReferences && row.copyOf() == name) {
row.setCopyOf(QString());
}
}
_data.erase(_data.begin() + index);
_indices.erase(it);
for (auto i = index, count = _data.size(); i != count; ++i) {
addToSearch(_data[i]);
}
}
void EditorBlock::addToSearch(const Row &row) {
auto query = _searchQuery;
if (!query.isEmpty()) resetSearch();
auto index = findRowIndex(&row);
for_const (auto ch, row.searchStartChars()) {
_searchIndex[ch].insert(index);
}
if (!query.isEmpty()) searchByQuery(query);
}
void EditorBlock::removeFromSearch(const Row &row) {
auto query = _searchQuery;
if (!query.isEmpty()) resetSearch();
auto index = findRowIndex(&row);
for_const (auto ch, row.searchStartChars()) {
auto it = _searchIndex.find(ch);
if (it != _searchIndex.cend()) {
it->remove(index);
if (it->isEmpty()) {
_searchIndex.erase(it);
}
}
}
if (!query.isEmpty()) searchByQuery(query);
}
void EditorBlock::filterRows(const QString &query) {
searchByQuery(query);
}
void EditorBlock::chooseRow() {
if (_selected < 0) {
return;
}
activateRow(rowAtIndex(_selected));
}
void EditorBlock::activateRow(const Row &row) {
if (_context->box) {
if (_type == Type::Existing) {
_context->possibleCopyOf = row.name();
_context->box->showColor(row.value());
}
} else {
_editing = findRowIndex(&row);
if (auto box = Ui::show(Box<EditColorBox>(row.name(), row.value()))) {
box->setSaveCallback(base::lambda_guarded(this, [this](QColor value) {
saveEditing(value);
}));
box->setCancelCallback(base::lambda_guarded(this, [this] {
cancelEditing();
}));
_context->box = box;
_context->name = row.name();
_context->updated.notify();
}
}
}
bool EditorBlock::selectSkip(int direction) {
_mouseSelection = false;
auto maxSelected = (isSearch() ? _searchResults.size() : _data.size()) - 1;
auto newSelected = _selected + direction;
if (newSelected < -1 || newSelected > maxSelected) {
newSelected = maxSelected;
}
if (auto changed = (newSelected != _selected)) {
setSelected(newSelected);
scrollToSelected();
return (newSelected >= 0);
}
return false;
}
void EditorBlock::scrollToSelected() {
if (_selected >= 0) {
Context::ScrollData update;
update.type = _type;
update.position = rowAtIndex(_selected).top();
update.height = rowAtIndex(_selected).height();
_context->scroll.notify(update, true);
}
}
void EditorBlock::searchByQuery(QString query) {
auto searchWords = QStringList();
if (!query.isEmpty()) {
searchWords = textAccentFold(query.trimmed().toLower()).split(SearchSplitter, QString::SkipEmptyParts);
query = searchWords.join(' ');
}
if (_searchQuery != query) {
setSelected(-1);
setPressed(-1);
_searchQuery = query;
_searchResults.clear();
auto toFilter = OrderedSet<int>();
for_const (auto &word, searchWords) {
if (word.isEmpty()) continue;
auto testToFilter = _searchIndex.value(word[0]);
if (testToFilter.isEmpty()) {
toFilter.clear();
break;
} else if (toFilter.isEmpty() || testToFilter.size() < toFilter.size()) {
toFilter = testToFilter;
}
}
if (!toFilter.isEmpty()) {
auto allWordsFound = [&searchWords](const Row &row) {
for_const (auto &word, searchWords) {
if (!row.searchWordsContain(word)) {
return false;
}
}
return true;
};
for_const (auto index, toFilter) {
if (allWordsFound(_data[index])) {
_searchResults.push_back(index);
}
}
}
_context->resized.notify(true);
}
}
const QColor *EditorBlock::find(const QString &name) {
if (auto row = findRow(name)) {
return &row->value();
}
return nullptr;
}
bool EditorBlock::feedDescription(const QString &name, const QString &description) {
if (auto row = findRow(name)) {
removeFromSearch(*row);
row->setDescription(description);
addToSearch(*row);
return true;
}
return false;
}
template <typename Callback>
void EditorBlock::enumerateRows(Callback callback) {
if (isSearch()) {
for_const (auto index, _searchResults) {
if (!callback(_data[index])) {
break;
}
}
} else {
for_const (auto &row, _data) {
if (!callback(row)) {
break;
}
}
}
}
template <typename Callback>
void EditorBlock::enumerateRows(Callback callback) const {
if (isSearch()) {
for_const (auto index, _searchResults) {
if (!callback(_data[index])) {
break;
}
}
} else {
for_const (auto &row, _data) {
if (!callback(row)) {
break;
}
}
}
}
template <typename Callback>
void EditorBlock::enumerateRowsFrom(int top, Callback callback) {
auto started = false;
auto index = 0;
enumerateRows([top, callback, &started, &index](Row &row) {
if (!started) {
if (row.top() + row.height() <= top) {
++index;
return true;
}
started = true;
}
return callback(index++, row);
});
}
template <typename Callback>
void EditorBlock::enumerateRowsFrom(int top, Callback callback) const {
auto started = false;
enumerateRows([top, callback, &started](const Row &row) {
if (!started) {
if (row.top() + row.height() <= top) {
return true;
}
started = true;
}
return callback(row);
});
}
int EditorBlock::resizeGetHeight(int newWidth) {
auto result = 0;
auto descriptionWidth = newWidth - st::themeEditorMargin.left() - st::themeEditorMargin.right();
enumerateRows([this, &result, descriptionWidth](Row &row) {
row.setTop(result);
auto height = row.height();
if (!height) {
height = st::themeEditorMargin.top() + st::themeEditorSampleSize.height();
if (!row.descriptionText().isEmpty()) {
height += st::themeEditorDescriptionSkip + row.descriptionText().countHeight(descriptionWidth);
}
height += st::themeEditorMargin.bottom();
row.setHeight(height);
}
result += row.height();
return true;
});
if (_type == Type::New) {
setHidden(!result);
}
if (_type == Type::Existing && !result && !isSearch()) {
return st::noContactsHeight;
}
return result;
}
void EditorBlock::mousePressEvent(QMouseEvent *e) {
updateSelected(e->pos());
setPressed(_selected);
}
void EditorBlock::mouseReleaseEvent(QMouseEvent *e) {
auto pressed = _pressed;
setPressed(-1);
if (pressed == _selected) {
if (_context->box) {
chooseRow();
} else if (_selected >= 0) {
App::CallDelayed(st::defaultRippleAnimation.hideDuration, this, [this, index = findRowIndex(&rowAtIndex(_selected))] {
if (index >= 0 && index < _data.size()) {
activateRow(_data[index]);
}
});
}
}
}
void EditorBlock::saveEditing(QColor value) {
if (_editing < 0) {
return;
}
auto &row = _data[_editing];
auto name = row.name();
if (_type == Type::New) {
auto removing = base::take(_editing, -1);
setSelected(-1);
setPressed(-1);
auto possibleCopyOf = _context->possibleCopyOf.isEmpty() ? row.copyOf() : _context->possibleCopyOf;
auto color = value;
auto description = row.description();
removeRow(name, false);
_context->appended.notify({ name, possibleCopyOf, color, description }, true);
} else if (_type == Type::Existing) {
removeFromSearch(row);
auto valueChanged = (row.value() != value);
if (valueChanged) {
row.setValue(value);
}
auto possibleCopyOf = _context->possibleCopyOf.isEmpty() ? row.copyOf() : _context->possibleCopyOf;
auto copyOf = checkCopyOf(_editing, possibleCopyOf) ? possibleCopyOf : QString();
auto copyOfChanged = (row.copyOf() != copyOf);
if (copyOfChanged) {
row.setCopyOf(copyOf);
}
addToSearch(row);
if (valueChanged || copyOfChanged) {
checkCopiesChanged(_editing + 1, QStringList(name), value);
_context->pending.notify({ name, copyOf, value }, true);
}
}
cancelEditing();
}
void EditorBlock::checkCopiesChanged(int startIndex, QStringList names, QColor value) {
for (auto i = startIndex, count = _data.size(); i != count; ++i) {
auto &checkIfIsCopy = _data[i];
if (names.contains(checkIfIsCopy.copyOf())) {
removeFromSearch(checkIfIsCopy);
checkIfIsCopy.setValue(value);
names.push_back(checkIfIsCopy.name());
addToSearch(checkIfIsCopy);
}
}
if (_type == Type::Existing) {
_context->changed.notify({ names, value }, true);
}
}
void EditorBlock::cancelEditing() {
if (_editing >= 0) {
updateRow(_data[_editing]);
}
_editing = -1;
if (auto box = base::take(_context->box)) {
box->closeBox();
}
_context->possibleCopyOf = QString();
if (!_context->name.isEmpty()) {
_context->name = QString();
_context->updated.notify();
}
}
bool EditorBlock::checkCopyOf(int index, const QString &possibleCopyOf) {
auto copyOfIndex = findRowIndex(possibleCopyOf);
return (copyOfIndex >= 0
&& index > copyOfIndex
&& _data[copyOfIndex].value().toRgb() == _data[index].value().toRgb());
}
void EditorBlock::mouseMoveEvent(QMouseEvent *e) {
if (_lastGlobalPos != e->globalPos() || _mouseSelection) {
_lastGlobalPos = e->globalPos();
updateSelected(e->pos());
}
}
void EditorBlock::updateSelected(QPoint localPosition) {
_mouseSelection = true;
auto top = localPosition.y();
auto underMouseIndex = -1;
enumerateRowsFrom(top, [&underMouseIndex, top](int index, const Row &row) {
if (row.top() <= top) {
underMouseIndex = index;
}
return false;
});
setSelected(underMouseIndex);
}
void EditorBlock::leaveEvent(QEvent *e) {
_mouseSelection = false;
setSelected(-1);
}
void EditorBlock::paintEvent(QPaintEvent *e) {
Painter p(this);
auto clip = e->rect();
if (_data.isEmpty()) {
p.fillRect(clip, st::dialogsBg);
p.setFont(st::noContactsFont);
p.setPen(st::noContactsColor);
p.drawText(QRect(0, 0, width(), st::noContactsHeight), lang(lng_theme_editor_no_keys));
}
auto ms = getms();
auto cliptop = clip.y();
auto clipbottom = cliptop + clip.height();
enumerateRowsFrom(cliptop, [this, &p, clipbottom, ms](int index, const Row &row) {
if (row.top() >= clipbottom) {
return false;
}
paintRow(p, index, row, ms);
return true;
});
}
void EditorBlock::paintRow(Painter &p, int index, const Row &row, TimeMs ms) {
auto rowTop = row.top() + st::themeEditorMargin.top();
auto rect = QRect(0, row.top(), width(), row.height());
auto selected = (_pressed >= 0) ? (index == _pressed) : (index == _selected);
auto active = (findRowIndex(&row) == _editing);
p.fillRect(rect, active ? st::dialogsBgActive : selected ? st::dialogsBgOver : st::dialogsBg);
if (auto ripple = row.ripple()) {
ripple->paint(p, 0, row.top(), width(), ms, &(active ? st::activeButtonBgRipple : st::windowBgRipple)->c);
if (ripple->empty()) {
row.resetRipple();
}
}
auto sample = QRect(width() - st::themeEditorMargin.right() - st::themeEditorSampleSize.width(), rowTop, st::themeEditorSampleSize.width(), st::themeEditorSampleSize.height());
Ui::Shadow::paint(p, sample, width(), st::defaultRoundShadow);
if (row.value().alpha() != 255) {
p.fillRect(myrtlrect(sample), _transparent);
}
p.fillRect(myrtlrect(sample), row.value());
auto rowWidth = width() - st::themeEditorMargin.left() - st::themeEditorMargin.right();
auto nameWidth = rowWidth - st::themeEditorSampleSize.width() - st::themeEditorDescriptionSkip;
p.setFont(st::themeEditorNameFont);
p.setPen(active ? st::dialogsNameFgActive : selected ? st::dialogsNameFgOver : st::dialogsNameFg);
p.drawTextLeft(st::themeEditorMargin.left(), rowTop, width(), st::themeEditorNameFont->elided(row.name(), nameWidth));
if (!row.copyOf().isEmpty()) {
auto copyTop = rowTop + st::themeEditorNameFont->height;
p.setFont(st::themeEditorCopyNameFont);
p.drawTextLeft(st::themeEditorMargin.left(), copyTop, width(), st::themeEditorCopyNameFont->elided("= " + row.copyOf(), nameWidth));
}
if (!row.descriptionText().isEmpty()) {
auto descriptionTop = rowTop + st::themeEditorSampleSize.height() + st::themeEditorDescriptionSkip;
p.setPen(active ? st::dialogsTextFgActive : selected ? st::dialogsTextFgOver : st::dialogsTextFg);
row.descriptionText().drawLeft(p, st::themeEditorMargin.left(), descriptionTop, rowWidth, width());
}
if (isEditing() && !active && (_type == Type::New || (_editing >= 0 && findRowIndex(&row) >= _editing))) {
p.fillRect(rect, st::layerBg);
}
}
void EditorBlock::setSelected(int selected) {
if (isEditing()) {
if (_type == Type::New) {
selected = -1;
} else if (_editing >= 0 && selected >= 0 && findRowIndex(&rowAtIndex(selected)) >= _editing) {
selected = -1;
}
}
if (_selected != selected) {
if (_selected >= 0) updateRow(rowAtIndex(_selected));
_selected = selected;
if (_selected >= 0) updateRow(rowAtIndex(_selected));
setCursor((_selected >= 0) ? style::cur_pointer : style::cur_default);
}
}
void EditorBlock::setPressed(int pressed) {
if (_pressed != pressed) {
if (_pressed >= 0) {
updateRow(rowAtIndex(_pressed));
stopLastRipple(_pressed);
}
_pressed = pressed;
if (_pressed >= 0) {
addRowRipple(_pressed);
updateRow(rowAtIndex(_pressed));
}
}
}
void EditorBlock::addRowRipple(int index) {
auto &row = rowAtIndex(index);
auto ripple = row.ripple();
if (!ripple) {
auto mask = Ui::RippleAnimation::rectMask(QSize(width(), row.height()));
ripple = row.setRipple(std_::make_unique<Ui::RippleAnimation>(st::defaultRippleAnimation, std_::move(mask), [this, index] {
updateRow(rowAtIndex(index));
}));
}
auto origin = mapFromGlobal(QCursor::pos()) - QPoint(0, row.top());
ripple->add(origin);
}
void EditorBlock::stopLastRipple(int index) {
auto &row = rowAtIndex(index);
if (row.ripple()) {
row.ripple()->lastStop();
}
}
void EditorBlock::updateRow(const Row &row) {
update(0, row.top(), width(), row.height());
}
void EditorBlock::addRow(const QString &name, const QString &copyOf, QColor value) {
_data.push_back({ name, copyOf, value });
_indices.insert(name, _data.size() - 1);
addToSearch(_data.back());
}
EditorBlock::Row &EditorBlock::rowAtIndex(int index) {
if (isSearch()) {
return _data[_searchResults[index]];
}
return _data[index];
}
int EditorBlock::findRowIndex(const QString &name) const {
return _indices.value(name, -1);;
}
EditorBlock::Row *EditorBlock::findRow(const QString &name) {
auto index = findRowIndex(name);
return (index >= 0) ? &_data[index] : nullptr;
}
int EditorBlock::findRowIndex(const Row *row) {
return row ? (row - &_data[0]) : -1;
}
} // namespace Theme
} // namespace Window

View file

@ -0,0 +1,171 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
class EditColorBox;
namespace Window {
namespace Theme {
class EditorBlock : public TWidget, private base::Subscriber {
public:
enum class Type {
Existing,
New,
};
struct Context {
QPointer<EditColorBox> box;
QString name;
QString possibleCopyOf;
base::Observable<void> updated;
base::Observable<void> resized;
struct AppendData {
QString name;
QString possibleCopyOf;
QColor value;
QString description;
};
base::Observable<AppendData> appended;
struct ChangeData {
QStringList names;
QColor value;
};
base::Observable<ChangeData> changed;
struct EditionData {
QString name;
QString copyOf;
QColor value;
};
base::Observable<EditionData> pending;
struct ScrollData {
Type type;
int position;
int height;
};
base::Observable<ScrollData> scroll;
};
EditorBlock(QWidget *parent, Type type, Context *context);
void filterRows(const QString &query);
void chooseRow();
bool hasSelected() const {
return (_selected >= 0);
}
void clearSelected() {
setSelected(-1);
}
bool selectSkip(int direction);
void feed(const QString &name, QColor value, const QString &copyOfExisting = QString());
bool feedCopy(const QString &name, const QString &copyOf);
const QColor *find(const QString &name);
bool feedDescription(const QString &name, const QString &description);
protected:
void paintEvent(QPaintEvent *e) override;
int resizeGetHeight(int newWidth) override;
void mousePressEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void leaveEvent(QEvent *e) override;
private:
class Row;
void addRow(const QString &name, const QString &copyOf, QColor value);
void removeRow(const QString &name, bool removeCopyReferences = true);
void addToSearch(const Row &row);
void removeFromSearch(const Row &row);
template <typename Callback>
void enumerateRows(Callback callback);
template <typename Callback>
void enumerateRows(Callback callback) const;
template <typename Callback>
void enumerateRowsFrom(int top, Callback callback);
template <typename Callback>
void enumerateRowsFrom(int top, Callback callback) const;
Row &rowAtIndex(int index);
int findRowIndex(const QString &name) const;
Row *findRow(const QString &name);
int findRowIndex(const Row *row);
void updateRow(const Row &row);
void paintRow(Painter &p, int index, const Row &row, TimeMs ms);
void updateSelected(QPoint localPosition);
void setSelected(int selected);
void setPressed(int pressed);
void addRowRipple(int index);
void stopLastRipple(int index);
void scrollToSelected();
bool isEditing() const {
return !_context->name.isEmpty();
}
void saveEditing(QColor value);
void cancelEditing();
bool checkCopyOf(int index, const QString &possibleCopyOf);
void checkCopiesChanged(int startIndex, QStringList names, QColor value);
void activateRow(const Row &row);
bool isSearch() const {
return !_searchQuery.isEmpty();
}
void searchByQuery(QString query);
void resetSearch() {
searchByQuery(QString());
}
Type _type = Type::Existing;
Context *_context = nullptr;
std_::vector_of_moveable<Row> _data;
QMap<QString, int> _indices;
QString _searchQuery;
QVector<int> _searchResults;
QMap<QChar, OrderedSet<int>> _searchIndex;
int _selected = -1;
int _pressed = -1;
int _editing = -1;
QPoint _lastGlobalPos;
bool _mouseSelection = false;
QBrush _transparent;
};
} // namespace Theme
} // namespace Window

View file

@ -19,9 +19,9 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/ */
#include "stdafx.h" #include "stdafx.h"
#include "window/window_theme_preview.h" #include "window/themes/window_theme_preview.h"
#include "window/window_theme.h" #include "window/themes/window_theme.h"
#include "lang.h" #include "lang.h"
#include "platform/platform_window_title.h" #include "platform/platform_window_title.h"
#include "styles/style_widgets.h" #include "styles/style_widgets.h"

View file

@ -20,7 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/ */
#pragma once #pragma once
#include "window/window_theme.h" #include "window/themes/window_theme.h"
namespace Window { namespace Window {
namespace Theme { namespace Theme {

View file

@ -19,12 +19,12 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/ */
#include "stdafx.h" #include "stdafx.h"
#include "window/window_theme_warning.h" #include "window/themes/window_theme_warning.h"
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/widgets/shadow.h" #include "ui/widgets/shadow.h"
#include "window/window_theme.h" #include "window/themes/window_theme.h"
#include "lang.h" #include "lang.h"
namespace Window { namespace Window {

View file

@ -267,6 +267,12 @@ topBarInfoButton: PeerAvatarButton {
} }
topBarSlideDuration: 200; topBarSlideDuration: 200;
themeEditorSampleSize: size(90px, 51px);
themeEditorMargin: margins(17px, 10px, 17px, 10px);
themeEditorDescriptionSkip: 10px;
themeEditorNameFont: font(15px semibold);
themeEditorCopyNameFont: font(fsize semibold);
// Mac specific // Mac specific
macAccessoryWidth: 450.; macAccessoryWidth: 450.;

View file

@ -104,7 +104,7 @@ call :repl "Replace=(&quot;FileVersion&quot;,) (\s*)&quot;\d+.\d+.\d+.\d+&quot;/
call :repl "Replace=(&quot;ProductVersion&quot;,) (\s*)&quot;\d+.\d+.\d+.\d+&quot;/$1$2 &quot;%VersionMajor%.%VersionMinor%.%VersionPatch%.%VersionBeta%&quot;" "Filename=%ResourcePath%" || goto :error call :repl "Replace=(&quot;ProductVersion&quot;,) (\s*)&quot;\d+.\d+.\d+.\d+&quot;/$1$2 &quot;%VersionMajor%.%VersionMinor%.%VersionPatch%.%VersionBeta%&quot;" "Filename=%ResourcePath%" || goto :error
echo Patching appxmanifest.xml... echo Patching appxmanifest.xml...
set "ResourcePath=%FullScriptPath%..\Resources\uwp\appxmanifest.xml" set "ResourcePath=%FullScriptPath%..\Resources\uwp\AppX\AppxManifest.xml"
call :repl "Replace= (Version=)&quot;\d+.\d+.\d+.\d+&quot;/ $1&quot;%VersionMajor%.%VersionMinor%.%VersionPatch%.%VersionBeta%&quot;" "Filename=%ResourcePath%" || goto :error call :repl "Replace= (Version=)&quot;\d+.\d+.\d+.\d+&quot;/ $1&quot;%VersionMajor%.%VersionMinor%.%VersionPatch%.%VersionBeta%&quot;" "Filename=%ResourcePath%" || goto :error
exit /b exit /b

View file

@ -120,3 +120,6 @@ repl "\(PRODUCTVERSION\) \([ ]*\)[0-9][0-9]*,[0-9][0-9]*,[0-9][0-9]*,[0-9][0-9]*
repl "\(\"FileVersion\",\) \([ ]*\)\"[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\"" "\1\2 \"$VersionMajor.$VersionMinor.$VersionPatch.$VersionBeta\"" "$ResourcePath" repl "\(\"FileVersion\",\) \([ ]*\)\"[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\"" "\1\2 \"$VersionMajor.$VersionMinor.$VersionPatch.$VersionBeta\"" "$ResourcePath"
repl "\(\"ProductVersion\",\) \([ ]*\)\"[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\"" "\1\2 \"$VersionMajor.$VersionMinor.$VersionPatch.$VersionBeta\"" "$ResourcePath" repl "\(\"ProductVersion\",\) \([ ]*\)\"[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\"" "\1\2 \"$VersionMajor.$VersionMinor.$VersionPatch.$VersionBeta\"" "$ResourcePath"
echo "Patching appxmanifest.xml..."
ResourcePath="$FullScriptPath/../Resources/uwp/AppX/AppxManifest.xml"
repl " \(Version=\)\"[0-9][0-9]*.[0-9][0-9]*.[0-9][0-9]*.[0-9][0-9]*\"" " \1\"$VersionMajor.$VersionMinor.$VersionPatch.$VersionBeta\"" "$ResourcePath"

View file

@ -3,4 +3,4 @@ AppVersionStrMajor 1.0
AppVersionStrSmall 1.0.6 AppVersionStrSmall 1.0.6
AppVersionStr 1.0.6 AppVersionStr 1.0.6
AlphaChannel 0 AlphaChannel 0
BetaVersion 0 BetaVersion 1000006001

View file

@ -4,10 +4,7 @@ set "FullScriptPath=%~dp0"
set "FullExecPath=%cd%" set "FullExecPath=%cd%"
set "Command=%1" set "Command=%1"
if "%Command%" == "module" ( if "%Command%" == "header" (
call :write_module %2
exit /b %errorlevel%
) else if "%Command%" == "header" (
call :write_header %2 call :write_header %2
exit /b %errorlevel% exit /b %errorlevel%
) else if "%Command%" == "source" ( ) else if "%Command%" == "source" (
@ -15,11 +12,8 @@ if "%Command%" == "module" (
exit /b %errorlevel% exit /b %errorlevel%
) )
cd gyp call :write_module %Command%
call refresh.bat exit /b %errorlevel%
cd ..
exit /b
:write_module :write_module
( (
@ -30,8 +24,8 @@ exit /b
exit /b 1 exit /b 1
) )
echo Generating module !CommandPathUnix!.. echo Generating module !CommandPathUnix!..
call prepare.bat header !CommandPathUnix! call create.bat header !CommandPathUnix!
call prepare.bat source !CommandPathUnix! call create.bat source !CommandPathUnix!
exit /b exit /b
) )

View file

@ -173,6 +173,8 @@
'<(src_loc)/boxes/contactsbox.h', '<(src_loc)/boxes/contactsbox.h',
'<(src_loc)/boxes/downloadpathbox.cpp', '<(src_loc)/boxes/downloadpathbox.cpp',
'<(src_loc)/boxes/downloadpathbox.h', '<(src_loc)/boxes/downloadpathbox.h',
'<(src_loc)/boxes/editcolorbox.cpp',
'<(src_loc)/boxes/editcolorbox.h',
'<(src_loc)/boxes/emojibox.cpp', '<(src_loc)/boxes/emojibox.cpp',
'<(src_loc)/boxes/emojibox.h', '<(src_loc)/boxes/emojibox.h',
'<(src_loc)/boxes/languagebox.cpp', '<(src_loc)/boxes/languagebox.cpp',
@ -457,6 +459,8 @@
'<(src_loc)/settings/settings_info_widget.h', '<(src_loc)/settings/settings_info_widget.h',
'<(src_loc)/settings/settings_inner_widget.cpp', '<(src_loc)/settings/settings_inner_widget.cpp',
'<(src_loc)/settings/settings_inner_widget.h', '<(src_loc)/settings/settings_inner_widget.h',
'<(src_loc)/settings/settings_layer.cpp',
'<(src_loc)/settings/settings_layer.h',
'<(src_loc)/settings/settings_notifications_widget.cpp', '<(src_loc)/settings/settings_notifications_widget.cpp',
'<(src_loc)/settings/settings_notifications_widget.h', '<(src_loc)/settings/settings_notifications_widget.h',
'<(src_loc)/settings/settings_privacy_widget.cpp', '<(src_loc)/settings/settings_privacy_widget.cpp',
@ -574,12 +578,16 @@
'<(src_loc)/window/top_bar_widget.h', '<(src_loc)/window/top_bar_widget.h',
'<(src_loc)/window/window_main_menu.cpp', '<(src_loc)/window/window_main_menu.cpp',
'<(src_loc)/window/window_main_menu.h', '<(src_loc)/window/window_main_menu.h',
'<(src_loc)/window/window_theme.cpp', '<(src_loc)/window/themes/window_theme.cpp',
'<(src_loc)/window/window_theme.h', '<(src_loc)/window/themes/window_theme.h',
'<(src_loc)/window/window_theme_preview.cpp', '<(src_loc)/window/themes/window_theme_editor.cpp',
'<(src_loc)/window/window_theme_preview.h', '<(src_loc)/window/themes/window_theme_editor.h',
'<(src_loc)/window/window_theme_warning.cpp', '<(src_loc)/window/themes/window_theme_editor_block.cpp',
'<(src_loc)/window/window_theme_warning.h', '<(src_loc)/window/themes/window_theme_editor_block.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',
'<(src_loc)/window/themes/window_theme_warning.h',
'<(src_loc)/window/window_title.h', '<(src_loc)/window/window_title.h',
'<(sp_media_key_tap_loc)/SPMediaKeyTap.m', '<(sp_media_key_tap_loc)/SPMediaKeyTap.m',