diff --git a/Telegram/Resources/basic.style b/Telegram/Resources/basic.style
index 85a9e6edf..dfe5849ae 100644
--- a/Telegram/Resources/basic.style
+++ b/Telegram/Resources/basic.style
@@ -275,3 +275,5 @@ notifyFadeRight: icon {{ "fade_horizontal", notificationBg }};
stickerIconLeft: icon {{ "fade_horizontal-flip_horizontal", emojiPanCategories }};
stickerIconRight: icon {{ "fade_horizontal", emojiPanCategories }};
+
+transparentPlaceholderSize: 4px;
diff --git a/Telegram/Resources/icons/color_slider_arrow.png b/Telegram/Resources/icons/color_slider_arrow.png
new file mode 100644
index 000000000..f874974a3
Binary files /dev/null and b/Telegram/Resources/icons/color_slider_arrow.png differ
diff --git a/Telegram/Resources/icons/color_slider_arrow@2x.png b/Telegram/Resources/icons/color_slider_arrow@2x.png
new file mode 100644
index 000000000..cf8a23dc7
Binary files /dev/null and b/Telegram/Resources/icons/color_slider_arrow@2x.png differ
diff --git a/Telegram/Resources/icons/color_slider_arrow_vertical.png b/Telegram/Resources/icons/color_slider_arrow_vertical.png
new file mode 100644
index 000000000..1de79ef08
Binary files /dev/null and b/Telegram/Resources/icons/color_slider_arrow_vertical.png differ
diff --git a/Telegram/Resources/icons/color_slider_arrow_vertical@2x.png b/Telegram/Resources/icons/color_slider_arrow_vertical@2x.png
new file mode 100644
index 000000000..001b70d18
Binary files /dev/null and b/Telegram/Resources/icons/color_slider_arrow_vertical@2x.png differ
diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index 3ff8cd8e2..e4b6beffb 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -1023,6 +1023,22 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
"lng_confirm_phone_send" = "Send";
"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
"lng_topbar_info" = "Info";
diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml
index 85f202906..6d3aafc24 100644
--- a/Telegram/Resources/uwp/AppX/AppxManifest.xml
+++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml
@@ -6,7 +6,7 @@
+ Version="1.0.6.1" />
Telegram Desktop
Telegram Messenger LLP
@@ -31,7 +31,7 @@
Square44x44Logo="Assets\logo44\logo44.png"
Description="Telegram Desktop official messenger" />
-
+
diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc
index 4bbd03c92..92b3a800f 100644
--- a/Telegram/Resources/winrc/Telegram.rc
+++ b/Telegram/Resources/winrc/Telegram.rc
@@ -34,8 +34,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
- FILEVERSION 1,0,6,0
- PRODUCTVERSION 1,0,6,0
+ FILEVERSION 1,0,6,1
+ PRODUCTVERSION 1,0,6,1
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -51,10 +51,10 @@ BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", "Telegram Messenger LLP"
- VALUE "FileVersion", "1.0.6.0"
+ VALUE "FileVersion", "1.0.6.1"
VALUE "LegalCopyright", "Copyright (C) 2014-2017"
VALUE "ProductName", "Telegram Desktop"
- VALUE "ProductVersion", "1.0.6.0"
+ VALUE "ProductVersion", "1.0.6.1"
END
END
BLOCK "VarFileInfo"
diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc
index 70ad75da7..9ac2f0990 100644
--- a/Telegram/Resources/winrc/Updater.rc
+++ b/Telegram/Resources/winrc/Updater.rc
@@ -25,8 +25,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
- FILEVERSION 1,0,6,0
- PRODUCTVERSION 1,0,6,0
+ FILEVERSION 1,0,6,1
+ PRODUCTVERSION 1,0,6,1
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -43,10 +43,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram Messenger LLP"
VALUE "FileDescription", "Telegram Updater"
- VALUE "FileVersion", "1.0.6.0"
+ VALUE "FileVersion", "1.0.6.1"
VALUE "LegalCopyright", "Copyright (C) 2014-2017"
VALUE "ProductName", "Telegram Desktop"
- VALUE "ProductVersion", "1.0.6.0"
+ VALUE "ProductVersion", "1.0.6.1"
END
END
BLOCK "VarFileInfo"
diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp
index 19facf748..cf291c359 100644
--- a/Telegram/SourceFiles/apiwrap.cpp
+++ b/Telegram/SourceFiles/apiwrap.cpp
@@ -30,7 +30,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "historywidget.h"
#include "localstorage.h"
#include "boxes/confirmbox.h"
-#include "window/window_theme.h"
+#include "window/themes/window_theme.h"
ApiWrap::ApiWrap(QObject *parent) : QObject(parent)
, _messageDataResolveDelayed(new SingleDelayedCall(this, "resolveMessageDatas")) {
diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp
index a4b6afa91..b88a506f0 100644
--- a/Telegram/SourceFiles/app.cpp
+++ b/Telegram/SourceFiles/app.cpp
@@ -44,7 +44,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "apiwrap.h"
#include "numbers.h"
#include "observer_peer.h"
-#include "window/window_theme.h"
+#include "window/themes/window_theme.h"
#include "window/notifications_manager.h"
#include "platform/platform_notifications_manager.h"
diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp
index 6e207326c..fca7dd303 100644
--- a/Telegram/SourceFiles/application.cpp
+++ b/Telegram/SourceFiles/application.cpp
@@ -34,7 +34,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "autoupdater.h"
#include "core/observer.h"
#include "observer_peer.h"
-#include "window/window_theme.h"
+#include "window/themes/window_theme.h"
#include "media/player/media_player_instance.h"
#include "window/notifications_manager.h"
#include "history/history_location_manager.h"
diff --git a/Telegram/SourceFiles/boxes/abstractbox.cpp b/Telegram/SourceFiles/boxes/abstractbox.cpp
index f62aa0502..4b07004bb 100644
--- a/Telegram/SourceFiles/boxes/abstractbox.cpp
+++ b/Telegram/SourceFiles/boxes/abstractbox.cpp
@@ -252,16 +252,14 @@ void AbstractBox::paintEvent(QPaintEvent *e) {
void AbstractBox::paintTitle(Painter &p, const QString &title, const QString &additional) {
p.setFont(st::boxTitleFont);
p.setPen(st::boxTitleFg);
- if (_layerType) {
- auto titleWidth = st::boxTitleFont->width(title);
- p.drawTextLeft(st::boxLayerTitlePosition.x(), st::boxLayerTitlePosition.y(), width(), title, titleWidth);
- if (!additional.isEmpty()) {
- p.setFont(st::boxLayerTitleAdditionalFont);
- p.setPen(st::boxTitleAdditionalFg);
- p.drawTextLeft(st::boxLayerTitlePosition.x() + titleWidth + st::boxLayerTitleAdditionalSkip, st::boxLayerTitlePosition.y() + st::boxTitleFont->ascent - st::boxLayerTitleAdditionalFont->ascent, width(), additional);
- }
- } else {
- p.drawTextLeft(st::boxTitlePosition.x(), st::boxTitlePosition.y(), width(), title);
+ auto titleWidth = st::boxTitleFont->width(title);
+ auto titleLeft = _layerType ? st::boxLayerTitlePosition.x() : st::boxTitlePosition.x();
+ auto titleTop = _layerType ? st::boxLayerTitlePosition.y() : st::boxTitlePosition.y();
+ p.drawTextLeft(titleLeft, titleTop, width(), title, titleWidth);
+ if (!additional.isEmpty()) {
+ p.setFont(st::boxLayerTitleAdditionalFont);
+ p.setPen(st::boxTitleAdditionalFg);
+ p.drawTextLeft(titleLeft + titleWidth + st::boxLayerTitleAdditionalSkip, titleTop + st::boxTitleFont->ascent - st::boxLayerTitleAdditionalFont->ascent, width(), additional);
}
}
diff --git a/Telegram/SourceFiles/boxes/abstractbox.h b/Telegram/SourceFiles/boxes/abstractbox.h
index 6c3b681ba..0fc187186 100644
--- a/Telegram/SourceFiles/boxes/abstractbox.h
+++ b/Telegram/SourceFiles/boxes/abstractbox.h
@@ -246,12 +246,6 @@ private:
};
-template
-inline object_ptr Box(Args&&... args) {
- auto parent = static_cast(nullptr);
- return object_ptr(parent, std_::forward(args)...);
-}
-
enum CreatingGroupType {
CreatingGroupNone,
CreatingGroupGroup,
diff --git a/Telegram/SourceFiles/boxes/addcontactbox.cpp b/Telegram/SourceFiles/boxes/addcontactbox.cpp
index 7e1468736..e04685cce 100644
--- a/Telegram/SourceFiles/boxes/addcontactbox.cpp
+++ b/Telegram/SourceFiles/boxes/addcontactbox.cpp
@@ -542,10 +542,7 @@ void SetupChannelBox::mouseMoveEvent(QMouseEvent *e) {
void SetupChannelBox::mousePressEvent(QMouseEvent *e) {
if (_linkOver) {
Application::clipboard()->setText(_channel->inviteLink());
-
- Ui::Toast::Config toast;
- toast.text = lang(lng_create_channel_link_copied);
- Ui::Toast::Show(App::wnd(), toast);
+ Ui::Toast::Show(lang(lng_create_channel_link_copied));
}
}
diff --git a/Telegram/SourceFiles/boxes/backgroundbox.cpp b/Telegram/SourceFiles/boxes/backgroundbox.cpp
index f60637249..ba93c0f90 100644
--- a/Telegram/SourceFiles/boxes/backgroundbox.cpp
+++ b/Telegram/SourceFiles/boxes/backgroundbox.cpp
@@ -24,7 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "lang.h"
#include "mainwidget.h"
#include "mainwindow.h"
-#include "window/window_theme.h"
+#include "window/themes/window_theme.h"
#include "styles/style_overview.h"
#include "styles/style_boxes.h"
#include "ui/effects/round_checkbox.h"
diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style
index 96806ca15..877b33a11 100644
--- a/Telegram/SourceFiles/boxes/boxes.style
+++ b/Telegram/SourceFiles/boxes/boxes.style
@@ -530,3 +530,23 @@ usernameTextStyle: TextStyle(passcodeTextStyle) {
usernameDefaultFg: windowSubTextFg;
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) {
+}
diff --git a/Telegram/SourceFiles/boxes/confirmbox.cpp b/Telegram/SourceFiles/boxes/confirmbox.cpp
index 12605935f..f8b28d47a 100644
--- a/Telegram/SourceFiles/boxes/confirmbox.cpp
+++ b/Telegram/SourceFiles/boxes/confirmbox.cpp
@@ -241,10 +241,7 @@ void MaxInviteBox::mousePressEvent(QMouseEvent *e) {
mouseMoveEvent(e);
if (_linkOver) {
Application::clipboard()->setText(_link);
-
- Ui::Toast::Config toast;
- toast.text = lang(lng_create_channel_link_copied);
- Ui::Toast::Show(App::wnd(), toast);
+ Ui::Toast::Show(lang(lng_create_channel_link_copied));
}
}
diff --git a/Telegram/SourceFiles/boxes/contactsbox.cpp b/Telegram/SourceFiles/boxes/contactsbox.cpp
index 4ba5449c0..812bc5eae 100644
--- a/Telegram/SourceFiles/boxes/contactsbox.cpp
+++ b/Telegram/SourceFiles/boxes/contactsbox.cpp
@@ -40,7 +40,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "ui/effects/ripple_animation.h"
#include "boxes/photocropbox.h"
#include "boxes/confirmbox.h"
-#include "window/window_theme.h"
+#include "window/themes/window_theme.h"
#include "observer_peer.h"
#include "apiwrap.h"
diff --git a/Telegram/SourceFiles/boxes/editcolorbox.cpp b/Telegram/SourceFiles/boxes/editcolorbox.cpp
new file mode 100644
index 000000000..c44992b5b
--- /dev/null
+++ b/Telegram/SourceFiles/boxes/editcolorbox.cpp
@@ -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 &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 _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(_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 &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 _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(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();
+ 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();
+}
\ No newline at end of file
diff --git a/Telegram/SourceFiles/boxes/editcolorbox.h b/Telegram/SourceFiles/boxes/editcolorbox.h
new file mode 100644
index 000000000..42e92512a
--- /dev/null
+++ b/Telegram/SourceFiles/boxes/editcolorbox.h
@@ -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 &&callback) {
+ _saveCallback = std_::move(callback);
+ }
+
+ void setCancelCallback(base::lambda &&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;
+ object_ptr _hueSlider;
+ object_ptr _opacitySlider;
+
+ object_ptr _hueField;
+ object_ptr _saturationField;
+ object_ptr _brightnessField;
+ object_ptr _redField;
+ object_ptr _greenField;
+ object_ptr _blueField;
+ object_ptr _result;
+
+ QBrush _transparent;
+ QColor _current;
+ QColor _new;
+
+ QRect _currentRect;
+ QRect _newRect;
+
+ base::lambda _saveCallback;
+ base::lambda _cancelCallback;
+
+};
diff --git a/Telegram/SourceFiles/boxes/sharebox.cpp b/Telegram/SourceFiles/boxes/sharebox.cpp
index 06ba468f6..2ba88b3c4 100644
--- a/Telegram/SourceFiles/boxes/sharebox.cpp
+++ b/Telegram/SourceFiles/boxes/sharebox.cpp
@@ -37,7 +37,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "history/history_media_types.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/scroll_area.h"
-#include "window/window_theme.h"
+#include "window/themes/window_theme.h"
#include "boxes/contactsbox.h"
ShareBox::ShareBox(QWidget*, CopyCallback &©Callback, SubmitCallback &&submitCallback, FilterCallback &&filterCallback)
@@ -872,9 +872,7 @@ void shareGameScoreFromItem(HistoryItem *item) {
QApplication::clipboard()->setText(CreateInternalLinkHttps(bot->username + qsl("?game=") + shortName));
- Ui::Toast::Config toast;
- toast.text = lang(lng_share_game_link_copied);
- Ui::Toast::Show(App::wnd(), toast);
+ Ui::Toast::Show(lang(lng_share_game_link_copied));
}
}
}
@@ -892,10 +890,7 @@ void shareGameScoreFromItem(HistoryItem *item) {
}
data->requests.remove(requestId);
if (data->requests.empty()) {
- Ui::Toast::Config toast;
- toast.text = lang(lng_share_done);
- Ui::Toast::Show(App::wnd(), toast);
-
+ Ui::Toast::Show(lang(lng_share_done));
Ui::hideLayer();
}
};
diff --git a/Telegram/SourceFiles/boxes/usernamebox.cpp b/Telegram/SourceFiles/boxes/usernamebox.cpp
index 4853b29c3..1e4da6329 100644
--- a/Telegram/SourceFiles/boxes/usernamebox.cpp
+++ b/Telegram/SourceFiles/boxes/usernamebox.cpp
@@ -161,10 +161,7 @@ void UsernameBox::onChanged() {
void UsernameBox::onLinkClick() {
Application::clipboard()->setText(CreateInternalLinkHttps(getName()));
-
- Ui::Toast::Config toast;
- toast.text = lang(lng_username_copied);
- Ui::Toast::Show(App::wnd(), toast);
+ Ui::Toast::Show(lang(lng_username_copied));
}
void UsernameBox::onUpdateDone(const MTPUser &user) {
diff --git a/Telegram/SourceFiles/codegen/common/basic_tokenized_file.cpp b/Telegram/SourceFiles/codegen/common/basic_tokenized_file.cpp
index a84cb99a5..03b8320e8 100644
--- a/Telegram/SourceFiles/codegen/common/basic_tokenized_file.cpp
+++ b/Telegram/SourceFiles/codegen/common/basic_tokenized_file.cpp
@@ -52,8 +52,6 @@ Token invalidToken() {
return { Type::Invalid, QString(), ConstUtf8String(nullptr, 0), false };
}
-
-
} // namespace
BasicTokenizedFile::BasicTokenizedFile(const QString &filepath) : reader_(filepath) {
@@ -152,6 +150,22 @@ Type BasicTokenizedFile::uniteLastTokens(Type 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() {
while (!reader_.atEnd()) {
if (!isDigitChar(reader_.currentChar())) {
diff --git a/Telegram/SourceFiles/codegen/common/basic_tokenized_file.h b/Telegram/SourceFiles/codegen/common/basic_tokenized_file.h
index 5dd201e31..fca8c5992 100644
--- a/Telegram/SourceFiles/codegen/common/basic_tokenized_file.h
+++ b/Telegram/SourceFiles/codegen/common/basic_tokenized_file.h
@@ -77,7 +77,11 @@ public:
};
bool read() {
- return reader_.read();
+ if (reader_.read()) {
+ singleLineComments_ = reader_.singleLineComments();
+ return true;
+ }
+ return false;
}
bool atEnd() const {
return reader_.atEnd();
@@ -90,6 +94,8 @@ public:
return failed_;
}
+ QString getCurrentLineComment();
+
// Log error to std::cerr with 'code' at the current position in file.
LogStream logError(int code) const;
LogStream logErrorUnexpectedToken() const;
@@ -124,6 +130,7 @@ private:
int currentToken_ = 0;
int lineNumber_ = 1;
bool failed_ = false;
+ QVector singleLineComments_;
// Where the last (currently read) token has started.
const char *tokenStart_ = nullptr;
diff --git a/Telegram/SourceFiles/codegen/common/clean_file.cpp b/Telegram/SourceFiles/codegen/common/clean_file.cpp
index 414512f2e..b93a3880e 100644
--- a/Telegram/SourceFiles/codegen/common/clean_file.cpp
+++ b/Telegram/SourceFiles/codegen/common/clean_file.cpp
@@ -86,10 +86,17 @@ bool CleanFile::read() {
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) {
-// comments_.push_back({ content_.size(), QByteArray(offset, ch - offset) });
- if (result_.isEmpty()) result_.reserve(end - offset - 2);
+ if (save) {
+ singleLineComments_.resize(lineNumber + 1);
+ singleLineComments_[lineNumber] = QByteArray(offset, ch - offset);
+ }
+ if (result_.isEmpty()) {
+ result_.reserve(end - offset - 2);
+ }
result_.append(' ');
offset = ch;
}
@@ -105,6 +112,9 @@ bool CleanFile::read() {
}
}
if (insideString) {
+ if (currentChar == '\n') {
+ ++lineNumber;
+ }
++ch;
continue;
}
@@ -114,12 +124,14 @@ bool CleanFile::read() {
insideComment = InsideComment::SingleLine;
ch += 2;
} else if (insideComment == InsideComment::SingleLine && currentChar == '\r' && nextChar == '\n') {
- feedComment(ch);
+ feedComment(ch, true);
ch += 2;
+ ++lineNumber;
insideComment = InsideComment::None;
} else if (insideComment == InsideComment::SingleLine && currentChar == '\n') {
- feedComment(ch);
+ feedComment(ch, true);
++ch;
+ ++lineNumber;
insideComment = InsideComment::None;
} else if (insideComment == InsideComment::None && currentChar == '/' && nextChar == '*') {
feedContent(ch);
@@ -132,15 +144,21 @@ bool CleanFile::read() {
} else if (insideComment == InsideComment::MultiLine && currentChar == '\r' && nextChar == '\n') {
feedComment(ch);
ch += 2;
+ ++lineNumber;
feedContent(ch);
} else if (insideComment == InsideComment::MultiLine && currentChar == '\n') {
feedComment(ch);
++ch;
+ ++lineNumber;
feedContent(ch);
} else {
+ if (currentChar == '\n') {
+ ++lineNumber;
+ }
++ch;
}
}
+ singleLineComments_.resize(lineNumber + 1);
if (insideComment == InsideComment::MultiLine) {
common::logError(kErrorUnexpectedEndOfFile, filepath_);
@@ -156,6 +174,10 @@ bool CleanFile::read() {
return true;
}
+QVector CleanFile::singleLineComments() const {
+ return singleLineComments_;
+}
+
LogStream CleanFile::logError(int code, int line) const {
return common::logError(code, filepath_, line);
}
diff --git a/Telegram/SourceFiles/codegen/common/clean_file.h b/Telegram/SourceFiles/codegen/common/clean_file.h
index 4afd8dd8f..51f13dcb3 100644
--- a/Telegram/SourceFiles/codegen/common/clean_file.h
+++ b/Telegram/SourceFiles/codegen/common/clean_file.h
@@ -38,6 +38,7 @@ public:
CleanFile &operator=(const CleanFile &other) = delete;
bool read();
+ QVector singleLineComments() const;
const char *data() const {
return result_.constData();
@@ -55,11 +56,8 @@ private:
QString filepath_;
QByteArray content_, result_;
bool read_;
- //struct Comment {
- // int offset;
- // QByteArray content;
- //};
- //QVector comments_;
+
+ QVector singleLineComments_;
};
diff --git a/Telegram/SourceFiles/codegen/common/clean_file_reader.h b/Telegram/SourceFiles/codegen/common/clean_file_reader.h
index add48c038..f43a5e43c 100644
--- a/Telegram/SourceFiles/codegen/common/clean_file_reader.h
+++ b/Telegram/SourceFiles/codegen/common/clean_file_reader.h
@@ -63,6 +63,10 @@ public:
return (end_ - pos_);
}
+ QVector singleLineComments() const {
+ return file_.singleLineComments();
+ }
+
// Log error to std::cerr with 'code' at line number 'line' in data().
LogStream logError(int code, int line) const {
return std::forward(file_.logError(code, line));
diff --git a/Telegram/SourceFiles/codegen/style/generator.cpp b/Telegram/SourceFiles/codegen/style/generator.cpp
index 1ed229b76..60799f10a 100644
--- a/Telegram/SourceFiles/codegen/style/generator.cpp
+++ b/Telegram/SourceFiles/codegen/style/generator.cpp
@@ -106,12 +106,13 @@ char hexFirstChar(char ch) {
return hexChar((*reinterpret_cast(&ch)) >> 4);
}
-QString stringToEncodedString(const std::string &str) {
+QString stringToEncodedString(const QString &str) {
QString result, lineBreak = "\\\n";
result.reserve(str.size() * 8);
bool writingHexEscapedCharacters = false, startOnNewLine = false;
int lastCutSize = 0;
- for (uchar ch : str) {
+ auto utf = str.toUtf8();
+ for (auto ch : utf) {
if (result.size() - lastCutSize > 80) {
startOnNewLine = true;
result.append(lineBreak);
@@ -140,6 +141,10 @@ QString stringToEncodedString(const std::string &str) {
return '"' + (startOnNewLine ? lineBreak : QString()) + result + '"';
}
+QString stringToEncodedString(const std::string &str) {
+ return stringToEncodedString(QString::fromStdString(str));
+}
+
QString stringToBinaryArray(const std::string &str) {
QStringList rows, chars;
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::Double: return QString("%1").arg(value.Double());
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: {
auto v(value.Color());
if (v.red == v.green && v.red == v.blue && v.red == 0 && v.alpha == 255) {
@@ -441,8 +446,15 @@ public:\n\
\n\
QByteArray save() const;\n\
bool load(const QByteArray &cache);\n\
- bool setColor(QLatin1String name, uchar r, uchar g, uchar b, uchar a);\n\
- bool setColor(QLatin1String name, QLatin1String from);\n\
+\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\
clear();\n\
finalize();\n\
@@ -564,12 +576,20 @@ namespace main_palette {\n\
\n\
QByteArray save();\n\
bool load(const QByteArray &cache);\n\
-bool setColor(QLatin1String name, uchar r, uchar g, uchar b, uchar a);\n\
-bool setColor(QLatin1String name, QLatin1String from);\n\
+palette::SetResult setColor(QLatin1String name, uchar r, uchar g, uchar b, uchar a);\n\
+palette::SetResult setColor(QLatin1String name, QLatin1String from);\n\
void apply(const palette &other);\n\
void reset();\n\
int indexOfColor(color c);\n\
\n\
+struct row {\n\
+\tQLatin1String name;\n\
+\tQLatin1String value;\n\
+\tQLatin1String fallback;\n\
+\tQLatin1String description;\n\
+};\n\
+QList data();\n\
+\n\
} // namespace main_palette\n";
return true;
@@ -739,10 +759,17 @@ void palette::finalize() {\n\
\n\
compute(0, -1, { 255, 255, 255, 0}); // special color\n";
+ QList names;
+ module_.enumVariables([this, &names](const Variable &variable) -> bool {
+ names.push_back(variable.name);
+ return true;
+ });
+
+ QString dataRows;
int indexInPalette = 1;
QByteArray checksumString;
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 index = indexInPalette++;
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);
source_->stream() << "\tcompute(" << index << ", " << fallbackIndex << ", " << assignment << ");\n";
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;
});
+ if (!result) {
+ return false;
+ }
auto count = indexInPalette;
auto checksum = hashCrc32(checksumString.constData(), checksumString.size());
@@ -854,23 +900,25 @@ bool palette::load(const QByteArray &cache) {\n\
return true;\n\
}\n\
\n\
-bool palette::setColor(QLatin1String name, uchar r, uchar g, uchar b, uchar a) {\n\
- auto index = getPaletteIndex(name);\n\
- if (index >= 0) {\n\
- setData(index, { r, g, b, a });\n\
- return true;\n\
- }\n\
- return false;\n\
+palette::SetResult palette::setColor(QLatin1String name, uchar r, uchar g, uchar b, uchar a) {\n\
+ auto nameIndex = getPaletteIndex(name);\n\
+ if (nameIndex < 0) return SetResult::KeyNotFound;\n\
+ auto duplicate = (_status[nameIndex] != Status::Initial);\n\
+\n\
+ setData(nameIndex, { r, g, b, a });\n\
+ return duplicate ? SetResult::Duplicate : SetResult::Ok;\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\
+ if (nameIndex < 0) return SetResult::KeyNotFound;\n\
+ auto duplicate = (_status[nameIndex] != Status::Initial);\n\
+\n\
auto fromIndex = getPaletteIndex(from);\n\
- if (nameIndex >= 0 && fromIndex >= 0 && _status[fromIndex] == Status::Loaded) {\n\
- setData(nameIndex, *data(fromIndex));\n\
- return true;\n\
- }\n\
- return false;\n\
+ if (fromIndex < 0 || _status[fromIndex] != Status::Loaded) return SetResult::ValueNotFound;\n\
+\n\
+ setData(nameIndex, *data(fromIndex));\n\
+ return duplicate ? SetResult::Duplicate : SetResult::Ok;\n\
}\n\
\n\
namespace main_palette {\n\
@@ -887,11 +935,11 @@ bool load(const QByteArray &cache) {\n\
return false;\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\
}\n\
\n\
-bool setColor(QLatin1String name, QLatin1String from) {\n\
+palette::SetResult setColor(QLatin1String name, QLatin1String from) {\n\
return _palette.setColor(name, from);\n\
}\n\
\n\
@@ -909,6 +957,14 @@ int indexOfColor(color c) {\n\
return _palette.indexOfColor(c);\n\
}\n\
\n\
+QList data() {\n\
+ auto result = QList();\n\
+ result.reserve(" << count << ");\n\
+\n\
+" << dataRows << "\n\
+ return result;\n\
+}\n\
+\n\
} // namespace main_palette\n\
\n";
@@ -1208,91 +1264,5 @@ bool Generator::collectUniqueValues() {
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 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 codegen
diff --git a/Telegram/SourceFiles/codegen/style/generator.h b/Telegram/SourceFiles/codegen/style/generator.h
index 3d80fdb30..fae531120 100644
--- a/Telegram/SourceFiles/codegen/style/generator.h
+++ b/Telegram/SourceFiles/codegen/style/generator.h
@@ -40,7 +40,6 @@ public:
bool writeHeader();
bool writeSource();
- bool writeSampleTheme(const QString &filepath);
private:
QString typeToString(structure::Type type) const;
diff --git a/Telegram/SourceFiles/codegen/style/parsed_file.cpp b/Telegram/SourceFiles/codegen/style/parsed_file.cpp
index 5b6cec8e5..726fb5a80 100644
--- a/Telegram/SourceFiles/codegen/style/parsed_file.cpp
+++ b/Telegram/SourceFiles/codegen/style/parsed_file.cpp
@@ -261,6 +261,7 @@ structure::Variable ParsedFile::readVariable(const QString &name) {
}
if (value.type().tag != structure::TypeTag::Struct || !value.copyOf().empty()) {
assertNextToken(BasicType::Semicolon);
+ result.description = file_.getCurrentLineComment();
}
}
return result;
diff --git a/Telegram/SourceFiles/codegen/style/processor.cpp b/Telegram/SourceFiles/codegen/style/processor.cpp
index 3cd25227f..683ec0000 100644
--- a/Telegram/SourceFiles/codegen/style/processor.cpp
+++ b/Telegram/SourceFiles/codegen/style/processor.cpp
@@ -81,10 +81,6 @@ bool Processor::write(const structure::Module &module) const {
if (!generator.writeSource()) {
return false;
}
- auto themePath = srcFile.absoluteDir().absolutePath() + "/default.tdesktop-theme";
- if (options_.isPalette && !generator.writeSampleTheme(themePath)) {
- return false;
- }
return true;
}
diff --git a/Telegram/SourceFiles/codegen/style/structure_types.h b/Telegram/SourceFiles/codegen/style/structure_types.h
index a77812b30..8de7b8499 100644
--- a/Telegram/SourceFiles/codegen/style/structure_types.h
+++ b/Telegram/SourceFiles/codegen/style/structure_types.h
@@ -198,6 +198,7 @@ private:
struct Variable {
FullName name;
Value value;
+ QString description;
explicit operator bool() const {
return !name.isEmpty();
diff --git a/Telegram/SourceFiles/core/utils.cpp b/Telegram/SourceFiles/core/utils.cpp
index 177549744..5448e3caa 100644
--- a/Telegram/SourceFiles/core/utils.cpp
+++ b/Telegram/SourceFiles/core/utils.cpp
@@ -940,6 +940,7 @@ QStringList MimeType::globPatterns() const {
switch (_type) {
case Known::WebP: return QStringList(qsl("*.webp"));
case Known::TDesktopTheme: return QStringList(qsl("*.tdesktop-theme"));
+ case Known::TDesktopPalette: return QStringList(qsl("*.tdesktop-palette"));
default: break;
}
return _typeStruct.globPatterns();
@@ -948,6 +949,7 @@ QString MimeType::filterString() const {
switch (_type) {
case Known::WebP: return qsl("WebP image (*.webp)");
case Known::TDesktopTheme: return qsl("Theme files (*.tdesktop-theme)");
+ case Known::TDesktopPalette: return qsl("Palette files (*.tdesktop-palette)");
default: break;
}
return _typeStruct.filterString();
@@ -956,6 +958,7 @@ QString MimeType::name() const {
switch (_type) {
case Known::WebP: return qsl("image/webp");
case Known::TDesktopTheme: return qsl("application/x-tdesktop-theme");
+ case Known::TDesktopPalette: return qsl("application/x-tdesktop-palette");
default: break;
}
return _typeStruct.name();
@@ -966,17 +969,22 @@ MimeType mimeTypeForName(const QString &mime) {
return MimeType(MimeType::Known::WebP);
} else if (mime == qsl("application/x-tdesktop-theme")) {
return MimeType(MimeType::Known::TDesktopTheme);
+ } else if (mime == qsl("application/x-tdesktop-palette")) {
+ return MimeType(MimeType::Known::TDesktopPalette);
}
return MimeType(QMimeDatabase().mimeTypeForName(mime));
}
MimeType mimeTypeForFile(const QFileInfo &file) {
QString path = file.absoluteFilePath();
- if (path.endsWith(qsl(".webp"), Qt::CaseInsensitive)) {
+ if (path.endsWith(qstr(".webp"), Qt::CaseInsensitive)) {
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);
+ } else if (path.endsWith(qstr(".tdesktop-palette"), Qt::CaseInsensitive)) {
+ return MimeType(MimeType::Known::TDesktopPalette);
}
+
{
QFile f(path);
if (f.open(QIODevice::ReadOnly)) {
diff --git a/Telegram/SourceFiles/core/utils.h b/Telegram/SourceFiles/core/utils.h
index c74aa3737..cc6d444a5 100644
--- a/Telegram/SourceFiles/core/utils.h
+++ b/Telegram/SourceFiles/core/utils.h
@@ -432,6 +432,7 @@ public:
enum class Known {
Unknown,
TDesktopTheme,
+ TDesktopPalette,
WebP,
};
diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h
index 1d0b95ebf..24f553790 100644
--- a/Telegram/SourceFiles/core/version.h
+++ b/Telegram/SourceFiles/core/version.h
@@ -22,7 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "core/utils.h"
-#define BETA_VERSION_MACRO (0ULL)
+#define BETA_VERSION_MACRO (1000006001ULL)
constexpr int AppVersion = 1000006;
constexpr str_const AppVersionStr = "1.0.6";
diff --git a/Telegram/SourceFiles/dialogswidget.cpp b/Telegram/SourceFiles/dialogswidget.cpp
index ab9e58534..d2a965369 100644
--- a/Telegram/SourceFiles/dialogswidget.cpp
+++ b/Telegram/SourceFiles/dialogswidget.cpp
@@ -42,7 +42,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "apiwrap.h"
#include "ui/widgets/dropdown_menu.h"
#include "ui/widgets/input_fields.h"
-#include "window/window_theme.h"
+#include "window/themes/window_theme.h"
#include "autoupdater.h"
#include "observer_peer.h"
diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp
index c28d64b79..4384f19db 100644
--- a/Telegram/SourceFiles/historywidget.cpp
+++ b/Telegram/SourceFiles/historywidget.cpp
@@ -56,7 +56,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "localstorage.h"
#include "apiwrap.h"
#include "window/top_bar_widget.h"
-#include "window/window_theme.h"
+#include "window/themes/window_theme.h"
#include "observer_peer.h"
#include "core/qthelp_regex.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.is_alert()) {
Ui::show(Box(qs(answerData.vmessage)));
- } else if (App::wnd()) {
- Ui::Toast::Config toast;
- toast.text = qs(answerData.vmessage);
- Ui::Toast::Show(App::wnd(), toast);
+ } else {
+ Ui::Toast::Show(qs(answerData.vmessage));
}
} else if (answerData.has_url()) {
auto url = qs(answerData.vurl);
diff --git a/Telegram/SourceFiles/layerwidget.h b/Telegram/SourceFiles/layerwidget.h
index 00a235dc2..7b784f27f 100644
--- a/Telegram/SourceFiles/layerwidget.h
+++ b/Telegram/SourceFiles/layerwidget.h
@@ -204,3 +204,9 @@ private:
mutable QSize _cachedSize;
};
+
+template
+inline object_ptr Box(Args&&... args) {
+ auto parent = static_cast(nullptr);
+ return object_ptr(parent, std_::forward(args)...);
+}
diff --git a/Telegram/SourceFiles/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp
index 928ffed1d..88be5f06d 100644
--- a/Telegram/SourceFiles/localstorage.cpp
+++ b/Telegram/SourceFiles/localstorage.cpp
@@ -26,7 +26,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "serialize/serialize_document.h"
#include "serialize/serialize_common.h"
#include "data/data_drafts.h"
-#include "window/window_theme.h"
+#include "window/themes/window_theme.h"
#include "observer_peer.h"
#include "mainwidget.h"
#include "mainwindow.h"
@@ -601,6 +601,7 @@ bool _backgroundWasRead = false;
bool _backgroundCanWrite = true;
FileKey _themeKey = 0;
+QString _themePaletteAbsolutePath;
bool _readingUserSettings = false;
FileKey _userSettingsKey = 0;
@@ -3717,6 +3718,9 @@ bool readThemeUsingKey(FileKey key) {
if (theme.stream.status() != QDataStream::Ok) {
return false;
}
+
+ _themePaletteAbsolutePath = Window::Theme::IsPaletteTestingPath(pathAbsolute) ? pathAbsolute : QString();
+
QFile file(pathRelative);
if (pathRelative.isEmpty() || !file.exists()) {
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) {
if (content.isEmpty()) {
+ _themePaletteAbsolutePath = QString();
if (_themeKey) {
clearKey(_themeKey);
_themeKey = 0;
@@ -3755,6 +3760,8 @@ void writeTheme(const QString &pathRelative, const QString &pathAbsolute, const
}
return;
}
+
+ _themePaletteAbsolutePath = Window::Theme::IsPaletteTestingPath(pathAbsolute) ? pathAbsolute : QString();
if (!_themeKey) {
_themeKey = genKey();
writeSettings();
@@ -3787,6 +3794,29 @@ bool hasTheme() {
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 result = sizeof(quint64) + sizeof(quint64) + Serialize::storageImageLocationSize();
if (peer->isUser()) {
diff --git a/Telegram/SourceFiles/localstorage.h b/Telegram/SourceFiles/localstorage.h
index 3c8d48415..9a13bcfba 100644
--- a/Telegram/SourceFiles/localstorage.h
+++ b/Telegram/SourceFiles/localstorage.h
@@ -153,6 +153,8 @@ bool readBackground();
void writeTheme(const QString &pathRelative, const QString &pathAbsolute, const QByteArray &content, const Window::Theme::Cached &cache);
void clearTheme();
bool hasTheme();
+QString themePaletteAbsolutePath();
+bool copyThemeColorsToPalette(const QString &file);
void writeRecentHashtagsAndBots();
void readRecentHashtagsAndBots();
diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp
index a506810eb..7a23b2b21 100644
--- a/Telegram/SourceFiles/mainwidget.cpp
+++ b/Telegram/SourceFiles/mainwidget.cpp
@@ -57,7 +57,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "media/player/media_player_instance.h"
#include "core/qthelp_regex.h"
#include "core/qthelp_url.h"
-#include "window/window_theme.h"
+#include "window/themes/window_theme.h"
#include "window/player_wrap_widget.h"
#include "styles/style_boxes.h"
diff --git a/Telegram/SourceFiles/mainwindow.cpp b/Telegram/SourceFiles/mainwindow.cpp
index deb38beed..764d2d36c 100644
--- a/Telegram/SourceFiles/mainwindow.cpp
+++ b/Telegram/SourceFiles/mainwindow.cpp
@@ -47,9 +47,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "settings/settings_widget.h"
#include "platform/platform_notifications_manager.h"
#include "window/notifications_manager.h"
-#include "window/window_theme.h"
-#include "window/window_theme_warning.h"
+#include "window/themes/window_theme.h"
+#include "window/themes/window_theme_warning.h"
#include "window/window_main_menu.h"
+#include "core/task_queue.h"
ConnectingWidget::ConnectingWidget(QWidget *parent, const QString &text, const QString &reconnect) : TWidget(parent)
, _reconnect(this, QString()) {
@@ -379,14 +380,18 @@ void MainWindow::setupMain(const MTPUser *self) {
}
void MainWindow::showSettings() {
- if (_passcode) return;
-
if (isHidden()) showFromTray();
+ showSpecialLayer(Box());
+}
+
+void MainWindow::showSpecialLayer(object_ptr layer) {
+ if (_passcode) return;
+
if (!_layerBg) {
_layerBg.create(bodyWidget());
}
- _layerBg->showSpecialLayer(Box());
+ _layerBg->showSpecialLayer(std_::move(layer));
}
void MainWindow::showMainMenu() {
@@ -527,17 +532,37 @@ void MainWindow::hideConnecting() {
void MainWindow::themeUpdated(const Window::Theme::BackgroundUpdate &data) {
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 (!_testingThemeWarning) {
_testingThemeWarning.create(bodyWidget());
+ _testingThemeWarning->hide();
_testingThemeWarning->setGeometry(rect());
_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) {
- _testingThemeWarning->hideAnimated();
- _testingThemeWarning = nullptr;
- setInnerFocus();
+ if (_testingThemeWarning) {
+ if (_testingThemeWarning->isHidden()) {
+ _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() {
+ Platform::MainWindow::updateControlsGeometry();
+
auto body = bodyWidget()->rect();
if (_passcode) _passcode->setGeometry(body);
if (_main) _main->setGeometry(body);
diff --git a/Telegram/SourceFiles/mainwindow.h b/Telegram/SourceFiles/mainwindow.h
index 4b8ea93ad..2841a87f3 100644
--- a/Telegram/SourceFiles/mainwindow.h
+++ b/Telegram/SourceFiles/mainwindow.h
@@ -145,6 +145,8 @@ public:
void showMainMenu();
void updateTrayMenu(bool force = false) override;
+ void showSpecialLayer(object_ptr layer);
+
void ui_showBox(object_ptr box, ShowLayerOptions options);
void ui_hideSettingsAndLayer(ShowLayerOptions options);
bool ui_isLayerShown();
@@ -156,12 +158,13 @@ public:
protected:
bool eventFilter(QObject *o, QEvent *e) override;
void closeEvent(QCloseEvent *e) override;
- void resizeEvent(QResizeEvent *e) override;
void initHook() override;
void updateIsActiveHook() override;
void clearWidgetsHook() override;
+ void updateControlsGeometry() override;
+
public slots:
void checkAutoLock();
@@ -205,8 +208,6 @@ private:
void themeUpdated(const Window::Theme::BackgroundUpdate &data);
- void updateControlsGeometry();
-
QPixmap grabInner();
void placeSmallCounter(QImage &img, int size, int count, style::color bg, const QPoint &shift, style::color color) override;
diff --git a/Telegram/SourceFiles/media/view/mediaview.style b/Telegram/SourceFiles/media/view/mediaview.style
index 34d568eb3..491e3fd88 100644
--- a/Telegram/SourceFiles/media/view/mediaview.style
+++ b/Telegram/SourceFiles/media/view/mediaview.style
@@ -111,8 +111,6 @@ mediaviewFileIconSize: 80px;
mediaviewFileLink: defaultLinkButton;
-mediaviewTransparentSize: 4px;
-
mediaviewMenu: Menu(defaultMenu) {
itemBg: mediaviewMenuBg;
itemBgOver: mediaviewMenuBgOver;
diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp
index ce1f22b47..fc0be7cd3 100644
--- a/Telegram/SourceFiles/mediaview.cpp
+++ b/Telegram/SourceFiles/mediaview.cpp
@@ -34,7 +34,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "styles/style_history.h"
#include "media/media_audio.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 "observer_peer.h"
@@ -69,6 +69,7 @@ bool typeHasMediaOverview(MediaOverviewType type) {
} // namespace
MediaView::MediaView(QWidget*) : TWidget(nullptr)
+, _transparentBrush(style::transparentPlaceholderBrush())
, _animStarted(getms())
, _docDownload(this, lang(lng_media_download), st::mediaviewFileLink)
, _docSaveAs(this, lang(lng_mediaview_save_as), st::mediaviewFileLink)
@@ -96,8 +97,6 @@ MediaView::MediaView(QWidget*) : TWidget(nullptr)
mediaOverviewUpdated(update);
}));
- generateTransparentBrush();
-
setWindowFlags(Qt::FramelessWindowHint | Qt::BypassWindowManagerHint | Qt::Tool | Qt::NoDropShadowWindowHint);
moveToScreen();
setAttribute(Qt::WA_NoSystemBackground, true);
@@ -1186,6 +1185,7 @@ void MediaView::displayPhoto(PhotoData *photo, HistoryItem *item) {
}
void MediaView::destroyThemePreview() {
+ _themePreviewId = 0;
_themePreviewShown = false;
_themePreview.reset();
_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() {
LastChatPhoto emptyResult = { nullptr, nullptr };
auto lastPhotoInOverview = [&emptyResult](auto history, auto list) -> LastChatPhoto {
diff --git a/Telegram/SourceFiles/mediaview.h b/Telegram/SourceFiles/mediaview.h
index 4700d0e8e..167c5d81a 100644
--- a/Telegram/SourceFiles/mediaview.h
+++ b/Telegram/SourceFiles/mediaview.h
@@ -166,8 +166,6 @@ private:
void findCurrent();
void loadBack();
- void generateTransparentBrush();
-
void updateCursor();
void setZoomLevel(int newZoom);
diff --git a/Telegram/SourceFiles/overviewwidget.cpp b/Telegram/SourceFiles/overviewwidget.cpp
index ea7327b85..1963d7c9e 100644
--- a/Telegram/SourceFiles/overviewwidget.cpp
+++ b/Telegram/SourceFiles/overviewwidget.cpp
@@ -33,7 +33,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "ui/widgets/buttons.h"
#include "ui/widgets/input_fields.h"
#include "window/top_bar_widget.h"
-#include "window/window_theme.h"
+#include "window/themes/window_theme.h"
#include "lang.h"
#include "mainwindow.h"
#include "mainwidget.h"
diff --git a/Telegram/SourceFiles/platform/win/main_window_win.cpp b/Telegram/SourceFiles/platform/win/main_window_win.cpp
index 3524c9574..85a18d75d 100644
--- a/Telegram/SourceFiles/platform/win/main_window_win.cpp
+++ b/Telegram/SourceFiles/platform/win/main_window_win.cpp
@@ -30,7 +30,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "lang.h"
#include "localstorage.h"
#include "ui/widgets/popup_menu.h"
-#include "window/window_theme.h"
+#include "window/themes/window_theme.h"
#include
diff --git a/Telegram/SourceFiles/profile/profile_block_invite_link.cpp b/Telegram/SourceFiles/profile/profile_block_invite_link.cpp
index 02ce6137b..5cdaa255a 100644
--- a/Telegram/SourceFiles/profile/profile_block_invite_link.cpp
+++ b/Telegram/SourceFiles/profile/profile_block_invite_link.cpp
@@ -110,9 +110,7 @@ void InviteLinkWidget::refreshLink() {
}
QApplication::clipboard()->setText(link);
- Ui::Toast::Config toast;
- toast.text = lang(lng_group_invite_copied);
- Ui::Toast::Show(App::wnd(), toast);
+ Ui::Toast::Show(lang(lng_group_invite_copied));
return false;
});
}
diff --git a/Telegram/SourceFiles/settings/settings_advanced_widget.cpp b/Telegram/SourceFiles/settings/settings_advanced_widget.cpp
index 23b6dcc72..740bf2a86 100644
--- a/Telegram/SourceFiles/settings/settings_advanced_widget.cpp
+++ b/Telegram/SourceFiles/settings/settings_advanced_widget.cpp
@@ -31,7 +31,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "ui/widgets/buttons.h"
#include "ui/effects/widget_slide_wrap.h"
#include "localstorage.h"
-#include "window/window_theme.h"
+#include "window/themes/window_theme.h"
namespace Settings {
diff --git a/Telegram/SourceFiles/settings/settings_background_widget.cpp b/Telegram/SourceFiles/settings/settings_background_widget.cpp
index a0e606d35..8cfdfaa55 100644
--- a/Telegram/SourceFiles/settings/settings_background_widget.cpp
+++ b/Telegram/SourceFiles/settings/settings_background_widget.cpp
@@ -30,7 +30,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "ui/widgets/buttons.h"
#include "localstorage.h"
#include "mainwindow.h"
-#include "window/window_theme.h"
+#include "window/themes/window_theme.h"
namespace Settings {
@@ -240,7 +240,7 @@ void BackgroundWidget::needBackgroundUpdate(bool tile) {
void BackgroundWidget::onChooseFromFile() {
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());
_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();
- 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);
return;
}
diff --git a/Telegram/SourceFiles/settings/settings_fixed_bar.cpp b/Telegram/SourceFiles/settings/settings_fixed_bar.cpp
index 16fa6ec8d..a4d860bfe 100644
--- a/Telegram/SourceFiles/settings/settings_fixed_bar.cpp
+++ b/Telegram/SourceFiles/settings/settings_fixed_bar.cpp
@@ -24,7 +24,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "styles/style_settings.h"
#include "styles/style_boxes.h"
#include "mainwindow.h"
-#include "lang.h"
namespace Settings {
@@ -32,6 +31,11 @@ FixedBar::FixedBar(QWidget *parent) : TWidget(parent) {
setAttribute(Qt::WA_OpaquePaintEvent);
}
+void FixedBar::setText(const QString &text) {
+ _text = text;
+ update();
+}
+
int FixedBar::resizeGetHeight(int newWidth) {
return st::settingsFixedBarHeight - st::boxRadius;
}
@@ -43,7 +47,7 @@ void FixedBar::paintEvent(QPaintEvent *e) {
p.setFont(st::settingsFixedBarFont);
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
diff --git a/Telegram/SourceFiles/settings/settings_fixed_bar.h b/Telegram/SourceFiles/settings/settings_fixed_bar.h
index 170b76144..89dabeb03 100644
--- a/Telegram/SourceFiles/settings/settings_fixed_bar.h
+++ b/Telegram/SourceFiles/settings/settings_fixed_bar.h
@@ -30,11 +30,16 @@ class FixedBar : public TWidget {
public:
FixedBar(QWidget *parent);
+ void setText(const QString &text);
+
protected:
void paintEvent(QPaintEvent *e) override;
int resizeGetHeight(int newWidth) override;
+private:
+ QString _text;
+
};
} // namespace Settings
diff --git a/Telegram/SourceFiles/settings/settings_general_widget.cpp b/Telegram/SourceFiles/settings/settings_general_widget.cpp
index 5614d42bb..f947910b1 100644
--- a/Telegram/SourceFiles/settings/settings_general_widget.cpp
+++ b/Telegram/SourceFiles/settings/settings_general_widget.cpp
@@ -189,21 +189,21 @@ void GeneralWidget::refreshControls() {
if (cPlatform() == dbipWindows || cSupportTray()) {
addChildRow(_enableTrayIcon, marginSmall, lang(lng_settings_workmode_tray), SLOT(onEnableTrayIcon()), (cWorkMode() == dbiwmTrayOnly || cWorkMode() == dbiwmWindowAndTray));
-#ifdef Q_OS_WIN
- addChildRow(_enableTaskbarIcon, marginLarge, lang(lng_settings_workmode_window), SLOT(onEnableTaskbarIcon()), (cWorkMode() == dbiwmWindowOnly || cWorkMode() == dbiwmWindowAndTray));
+ if (cPlatform() == dbipWindows) {
+ addChildRow(_enableTaskbarIcon, marginLarge, lang(lng_settings_workmode_window), SLOT(onEnableTaskbarIcon()), (cWorkMode() == dbiwmWindowOnly || cWorkMode() == dbiwmWindowAndTray));
#ifndef OS_WIN_STORE
- 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()));
- subscribe(Global::RefLocalPasscodeChanged(), [this] {
- _startMinimized->entity()->setChecked(cStartMinimized() && !Global::LocalPasscode());
- });
- if (!cAutoStart()) {
- _startMinimized->hideFast();
- }
- addChildRow(_addInSendTo, marginSmall, lang(lng_settings_add_sendto), SLOT(onAddInSendTo()), cSendToMenu());
+ 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()));
+ subscribe(Global::RefLocalPasscodeChanged(), [this] {
+ _startMinimized->entity()->setChecked(cStartMinimized() && !Global::LocalPasscode());
+ });
+ if (!cAutoStart()) {
+ _startMinimized->hideFast();
+ }
+ addChildRow(_addInSendTo, marginSmall, lang(lng_settings_add_sendto), SLOT(onAddInSendTo()), cSendToMenu());
#endif // OS_WIN_STORE
-#endif // Q_OS_WIN
+ }
}
}
@@ -297,7 +297,7 @@ void GeneralWidget::updateWorkmode() {
Local::writeSettings();
}
-#if defined Q_OS_WIN && !defined OS_WIN_STORE
+#if !defined OS_WIN_STORE
void GeneralWidget::onAutoStart() {
cSetAutoStart(_autoStart->checked());
if (cAutoStart()) {
@@ -335,6 +335,6 @@ void GeneralWidget::onAddInSendTo() {
psSendToMenu(_addInSendTo->checked());
Local::writeSettings();
}
-#endif // Q_OS_WIN && !OS_WIN_STORE
+#endif // !OS_WIN_STORE
} // namespace Settings
diff --git a/Telegram/SourceFiles/settings/settings_general_widget.h b/Telegram/SourceFiles/settings/settings_general_widget.h
index 7b9579724..833b3857c 100644
--- a/Telegram/SourceFiles/settings/settings_general_widget.h
+++ b/Telegram/SourceFiles/settings/settings_general_widget.h
@@ -94,11 +94,11 @@ private slots:
void onEnableTrayIcon();
void onEnableTaskbarIcon();
-#if defined Q_OS_WIN && !defined OS_WIN_STORE
+#ifndef OS_WIN_STORE
void onAutoStart();
void onStartMinimized();
void onAddInSendTo();
-#endif // Q_OS_WIN && !OS_WIN_STORE
+#endif // !OS_WIN_STORE
void onRestart();
diff --git a/Telegram/SourceFiles/settings/settings_inner_widget.cpp b/Telegram/SourceFiles/settings/settings_inner_widget.cpp
index aa8b93988..d2c972eeb 100644
--- a/Telegram/SourceFiles/settings/settings_inner_widget.cpp
+++ b/Telegram/SourceFiles/settings/settings_inner_widget.cpp
@@ -35,7 +35,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
namespace Settings {
-InnerWidget::InnerWidget(QWidget *parent) : TWidget(parent)
+InnerWidget::InnerWidget(QWidget *parent) : LayerInner(parent)
, _self(App::self()) {
refreshBlocks();
subscribe(Global::RefSelfChanged(), [this]() { selfUpdated(); });
diff --git a/Telegram/SourceFiles/settings/settings_inner_widget.h b/Telegram/SourceFiles/settings/settings_inner_widget.h
index 5bf68e830..ecc7f5fa1 100644
--- a/Telegram/SourceFiles/settings/settings_inner_widget.h
+++ b/Telegram/SourceFiles/settings/settings_inner_widget.h
@@ -20,19 +20,21 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
+#include "settings/settings_layer.h"
+
namespace Settings {
class CoverWidget;
class BlockWidget;
-class InnerWidget : public TWidget, private base::Subscriber {
+class InnerWidget : public LayerInner, private base::Subscriber {
Q_OBJECT
public:
InnerWidget(QWidget *parent);
// 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;
return TWidget::resizeToWidth(newWidth);
}
diff --git a/Telegram/SourceFiles/settings/settings_layer.cpp b/Telegram/SourceFiles/settings/settings_layer.cpp
new file mode 100644
index 000000000..6cbd2cba3
--- /dev/null
+++ b/Telegram/SourceFiles/settings/settings_layer.cpp
@@ -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(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 &&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 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
diff --git a/Telegram/SourceFiles/settings/settings_layer.h b/Telegram/SourceFiles/settings/settings_layer.h
new file mode 100644
index 000000000..3f097b2a5
--- /dev/null
+++ b/Telegram/SourceFiles/settings/settings_layer.h
@@ -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
+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 &&callback);
+ void resizeToWidth(int newWidth, int newContentLeft);
+
+protected:
+ void paintEvent(QPaintEvent *e) override;
+ void resizeEvent(QResizeEvent *e) override;
+
+ template
+ QPointer setInnerWidget(object_ptr widget) {
+ auto result = QPointer(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 widget);
+
+ virtual void resizeUsingInnerHeight(int newWidth, int innerHeight) {
+ resize(newWidth, height());
+ }
+
+ object_ptr _scroll;
+ QPointer _inner;
+ object_ptr _fixedBar;
+ object_ptr _fixedBarClose;
+ object_ptr> _fixedBarShadow;
+
+ bool _roundedCorners = false;
+
+};
+
+} // namespace Settings
diff --git a/Telegram/SourceFiles/settings/settings_widget.cpp b/Telegram/SourceFiles/settings/settings_widget.cpp
index 6db50f88e..b6f2aa5e7 100644
--- a/Telegram/SourceFiles/settings/settings_widget.cpp
+++ b/Telegram/SourceFiles/settings/settings_widget.cpp
@@ -33,7 +33,11 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "mainwidget.h"
#include "localstorage.h"
#include "boxes/confirmbox.h"
+#include "lang.h"
#include "application.h"
+#include "ui/filedialog.h"
+#include "window/themes/window_theme.h"
+#include "window/themes/window_theme_editor.h"
namespace Settings {
namespace {
@@ -88,6 +92,23 @@ void fillCodes() {
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) {
@@ -123,42 +144,28 @@ void codesFeedString(const QString &text) {
} // namespace
-Widget::Widget(QWidget *parent) : LayerWidget(parent)
-, _scroll(this, st::settingsScroll)
-, _fixedBar(this)
-, _fixedBarClose(this, st::settingsFixedBarClose)
-, _fixedBarShadow(this, object_ptr(this)) {
- _inner = _scroll->setOwnedWidget(object_ptr(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([]() {
+Widget::Widget(QWidget *parent) {
+ setTitle(lang(lng_menu_settings));
+ _inner = setInnerWidget(object_ptr(this));
+ setCloseClickHandler([]() {
Ui::hideSettingsAndLayer();
});
-
- connect(_inner, SIGNAL(heightUpdated()), this, SLOT(onInnerHeightUpdated()));
}
-void Widget::onScroll() {
- if (_scroll->scrollTop() > 0) {
- _fixedBarShadow->showAnimated();
- } else {
- _fixedBarShadow->hideAnimated();
- }
+void Widget::showFinished() {
+ _inner->showFinished();
+}
+
+void Widget::keyPressEvent(QKeyEvent *e) {
+ codesFeedString(e->text());
+ return LayerWidget::keyPressEvent(e);
}
void Widget::parentResized() {
auto parentSize = parentWidget()->size();
- int windowWidth = parentSize.width();
- int newWidth = st::settingsMaxWidth;
- int newContentLeft = st::settingsMaxPadding;
+ auto windowWidth = parentSize.width();
+ auto newWidth = st::settingsMaxWidth;
+ auto newContentLeft = st::settingsMaxPadding;
if (windowWidth <= st::settingsMaxWidth) {
newWidth = windowWidth;
newContentLeft = st::settingsMinPadding;
@@ -176,92 +183,27 @@ void Widget::parentResized() {
newContentLeft += ((newWidth - st::windowMinWidth) * (st::settingsMaxPadding - st::settingsMinPadding)) / (st::settingsMaxWidth - st::windowMinWidth);
}
}
-
- // Widget height depends on InnerWidget height, so we
- // resize it here, not in the resizeEvent() handler.
- _inner->resizeToWidth(newWidth, newContentLeft);
-
- resizeUsingInnerHeight(newWidth, newContentLeft);
+ resizeToWidth(newWidth, newContentLeft);
}
-void Widget::onInnerHeightUpdated() {
- resizeUsingInnerHeight(width(), _contentLeft);
-}
-
-void Widget::resizeUsingInnerHeight(int newWidth, int newContentLeft) {
+void Widget::resizeUsingInnerHeight(int newWidth, int innerHeight) {
if (!App::wnd()) return;
auto parentSize = parentWidget()->size();
- int windowWidth = parentSize.width();
- int windowHeight = parentSize.height();
- int maxHeight = st::settingsFixedBarHeight + _inner->height();
- int newHeight = maxHeight + st::boxRadius;
+ auto windowWidth = parentSize.width();
+ auto windowHeight = parentSize.height();
+ auto maxHeight = st::settingsFixedBarHeight + innerHeight;
+ auto newHeight = maxHeight + st::boxRadius;
if (newHeight > windowHeight || newWidth >= windowWidth) {
newHeight = windowHeight;
}
- if (_contentLeft != newContentLeft) {
- _contentLeft = newContentLeft;
- }
-
- _roundedCorners = (newHeight < windowHeight);
- setAttribute(Qt::WA_OpaquePaintEvent, !_roundedCorners);
+ auto roundedCorners = newHeight < windowHeight;
+ setRoundedCorners(roundedCorners);
+ setAttribute(Qt::WA_OpaquePaintEvent, !roundedCorners);
setGeometry((windowWidth - newWidth) / 2, (windowHeight - newHeight) / 2, newWidth, newHeight);
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
diff --git a/Telegram/SourceFiles/settings/settings_widget.h b/Telegram/SourceFiles/settings/settings_widget.h
index 0abebdcae..6a5c760ea 100644
--- a/Telegram/SourceFiles/settings/settings_widget.h
+++ b/Telegram/SourceFiles/settings/settings_widget.h
@@ -20,51 +20,30 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
-#include "layerwidget.h"
-
-class BoxLayerTitleShadow;
-
-namespace Ui {
-class ScrollArea;
-class IconButton;
-template
-class WidgetFadeWrap;
-} // namespace Ui
+#include "settings/settings_layer.h"
namespace Settings {
class InnerWidget;
-class FixedBar;
-class Widget : public LayerWidget {
+class Widget : public Layer {
Q_OBJECT
public:
- Widget(QWidget *parent);
+ Widget(QWidget*);
- void parentResized() override;
void showFinished() override;
+ void parentResized() override;
protected:
- void paintEvent(QPaintEvent *e) override;
- void resizeEvent(QResizeEvent *e) override;
void keyPressEvent(QKeyEvent *e) override;
-private slots:
- void onInnerHeightUpdated();
- void onScroll();
-
private:
- void resizeUsingInnerHeight(int newWidth, int newContentLeft);
+ void resizeUsingInnerHeight(int newWidth, int innerHeight) override;
- object_ptr _scroll;
QPointer _inner;
- object_ptr _fixedBar;
- object_ptr _fixedBarClose;
- object_ptr> _fixedBarShadow;
int _contentLeft = 0;
- bool _roundedCorners = false;
};
diff --git a/Telegram/SourceFiles/stickers/stickers.cpp b/Telegram/SourceFiles/stickers/stickers.cpp
index ea9b57614..0bfff3c22 100644
--- a/Telegram/SourceFiles/stickers/stickers.cpp
+++ b/Telegram/SourceFiles/stickers/stickers.cpp
@@ -86,7 +86,7 @@ void applyArchivedResult(const MTPDmessages_stickerSetInstallResultArchive &d) {
toast.text = lang(lng_stickers_packs_archived);
toast.maxWidth = st::stickersToastMaxWidth;
toast.padding = st::stickersToastPadding;
- Ui::Toast::Show(App::wnd(), toast);
+ Ui::Toast::Show(toast);
// Ui::show(Box(archived), KeepOtherLayers);
emit App::main()->stickersUpdated();
diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp
index 277b3b259..d22275395 100644
--- a/Telegram/SourceFiles/structs.cpp
+++ b/Telegram/SourceFiles/structs.cpp
@@ -35,7 +35,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "localstorage.h"
#include "history/history_media_types.h"
#include "styles/style_history.h"
-#include "window/window_theme.h"
+#include "window/themes/window_theme.h"
namespace {
diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h
index 445141376..d918320f5 100644
--- a/Telegram/SourceFiles/structs.h
+++ b/Telegram/SourceFiles/structs.h
@@ -1169,7 +1169,7 @@ public:
return (type == AnimatedDocument) && !mime.compare(qstr("video/mp4"), Qt::CaseInsensitive);
}
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 {
if (auto s = song()) {
diff --git a/Telegram/SourceFiles/ui/filedialog.cpp b/Telegram/SourceFiles/ui/filedialog.cpp
index e85b6a89d..324a9ff8b 100644
--- a/Telegram/SourceFiles/ui/filedialog.cpp
+++ b/Telegram/SourceFiles/ui/filedialog.cpp
@@ -25,6 +25,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "localstorage.h"
#include "platform/platform_file_dialog.h"
+#include "core/task_queue.h"
+
void filedialogInit() {
if (cDialogLastPath().isEmpty()) {
#ifdef Q_OS_WIN
@@ -384,4 +386,67 @@ base::Observable &QueryDone() {
return QueryDoneObservable;
}
+void askOpenPath(const QString &caption, const QString &filter, base::lambda &&callback, base::lambda &&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 &&callback, base::lambda &&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 &&callback, base::lambda &&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 &&callback, base::lambda &&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
diff --git a/Telegram/SourceFiles/ui/filedialog.h b/Telegram/SourceFiles/ui/filedialog.h
index 1479b9fc7..045266cb7 100644
--- a/Telegram/SourceFiles/ui/filedialog.h
+++ b/Telegram/SourceFiles/ui/filedialog.h
@@ -65,4 +65,13 @@ bool processQuery();
base::Observable &QueryDone();
+struct OpenResult {
+ QStringList paths;
+ QByteArray remoteContent;
+};
+void askOpenPath(const QString &caption, const QString &filter, base::lambda &&callback, base::lambda &&failed = base::lambda());
+void askOpenPaths(const QString &caption, const QString &filter, base::lambda &&callback, base::lambda &&failed = base::lambda());
+void askWritePath(const QString &caption, const QString &filter, const QString &initialPath, base::lambda &&callback, base::lambda &&failed = base::lambda());
+void askFolder(const QString &caption, base::lambda &&callback, base::lambda &&failed = base::lambda());
+
} // namespace FileDialog
diff --git a/Telegram/SourceFiles/ui/style/style_core.cpp b/Telegram/SourceFiles/ui/style/style_core.cpp
index 59b69ef6e..4bb663eb8 100644
--- a/Telegram/SourceFiles/ui/style/style_core.cpp
+++ b/Telegram/SourceFiles/ui/style/style_core.cpp
@@ -115,6 +115,20 @@ void colorizeImage(const QImage &src, QColor c, QImage *outResult, QRect srcRect
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 {
QImage createCircleMask(int size, QColor bg, QColor fg) {
diff --git a/Telegram/SourceFiles/ui/style/style_core.h b/Telegram/SourceFiles/ui/style/style_core.h
index 73520210c..dc1c4144e 100644
--- a/Telegram/SourceFiles/ui/style/style_core.h
+++ b/Telegram/SourceFiles/ui/style/style_core.h
@@ -78,6 +78,8 @@ inline QImage colorizeImage(const QImage &src, const color &c, QRect srcRect = Q
return colorizeImage(src, c->c, srcRect);
}
+QBrush transparentPlaceholderBrush();
+
namespace internal {
QImage createCircleMask(int size, QColor bg, QColor fg);
diff --git a/Telegram/SourceFiles/ui/toast/toast.cpp b/Telegram/SourceFiles/ui/toast/toast.cpp
index 3dcb66b4a..393a4dc7c 100644
--- a/Telegram/SourceFiles/ui/toast/toast.cpp
+++ b/Telegram/SourceFiles/ui/toast/toast.cpp
@@ -23,6 +23,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "ui/toast/toast_manager.h"
#include "ui/toast/toast_widget.h"
+#include "mainwindow.h"
namespace Ui {
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() {
_widget->setShownLevel(_a_opacity.current(_hiding ? 0. : 1.));
_widget->update();
diff --git a/Telegram/SourceFiles/ui/toast/toast.h b/Telegram/SourceFiles/ui/toast/toast.h
index 729b1d2ae..4a389515c 100644
--- a/Telegram/SourceFiles/ui/toast/toast.h
+++ b/Telegram/SourceFiles/ui/toast/toast.h
@@ -36,6 +36,8 @@ struct Config {
QMargins padding;
};
void Show(QWidget *parent, const Config &config);
+void Show(const Config &config);
+void Show(const QString &text);
class Instance {
struct Private {
diff --git a/Telegram/SourceFiles/ui/toast/toast_manager.cpp b/Telegram/SourceFiles/ui/toast/toast_manager.cpp
index cfd42b047..60440c411 100644
--- a/Telegram/SourceFiles/ui/toast/toast_manager.cpp
+++ b/Telegram/SourceFiles/ui/toast/toast_manager.cpp
@@ -40,6 +40,10 @@ Manager::Manager(QWidget *parent) : QObject(parent) {
}
Manager *Manager::instance(QWidget *parent) {
+ if (!parent) {
+ return nullptr;
+ }
+
_managers.createIfNull();
auto i = _managers->constFind(parent);
if (i == _managers->cend()) {
diff --git a/Telegram/SourceFiles/ui/widgets/buttons.cpp b/Telegram/SourceFiles/ui/widgets/buttons.cpp
index d90d12b99..97f95fc08 100644
--- a/Telegram/SourceFiles/ui/widgets/buttons.cpp
+++ b/Telegram/SourceFiles/ui/widgets/buttons.cpp
@@ -632,14 +632,19 @@ CrossButton::CrossButton(QWidget *parent, const style::CrossButton &st) : Ripple
hide();
}
-void CrossButton::hideAnimated() {
- startAnimation(false);
-}
-
void CrossButton::showAnimated() {
startAnimation(true);
}
+void CrossButton::showFast() {
+ showAnimated();
+ _a_show.finish();
+}
+
+void CrossButton::hideAnimated() {
+ startAnimation(false);
+}
+
void CrossButton::hideFast() {
hideAnimated();
_a_show.finish();
diff --git a/Telegram/SourceFiles/ui/widgets/buttons.h b/Telegram/SourceFiles/ui/widgets/buttons.h
index 9e9b1fc39..45b156cba 100644
--- a/Telegram/SourceFiles/ui/widgets/buttons.h
+++ b/Telegram/SourceFiles/ui/widgets/buttons.h
@@ -213,6 +213,7 @@ public:
CrossButton(QWidget *parent, const style::CrossButton &st);
void showAnimated();
+ void showFast();
void hideAnimated();
void hideFast();
diff --git a/Telegram/SourceFiles/ui/widgets/input_fields.cpp b/Telegram/SourceFiles/ui/widgets/input_fields.cpp
index ffdda652d..d7be4cb81 100644
--- a/Telegram/SourceFiles/ui/widgets/input_fields.cpp
+++ b/Telegram/SourceFiles/ui/widgets/input_fields.cpp
@@ -24,7 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "ui/widgets/popup_menu.h"
#include "mainwindow.h"
#include "ui/countryinput.h"
-#include "window/window_theme.h"
+#include "window/themes/window_theme.h"
#include "lang.h"
#include "numbers.h"
diff --git a/Telegram/SourceFiles/ui/widgets/widgets.style b/Telegram/SourceFiles/ui/widgets/widgets.style
index dec96a5cc..5606f1dbc 100644
--- a/Telegram/SourceFiles/ui/widgets/widgets.style
+++ b/Telegram/SourceFiles/ui/widgets/widgets.style
@@ -586,7 +586,7 @@ attentionLeftOutlineButton: OutlineButton(defaultLeftOutlineButton) {
textBgOver: attentionButtonBgOver;
textFg: attentionButtonFg;
- textFgOver: attentionButtonFg;
+ textFgOver: attentionButtonFgOver;
ripple: RippleAnimation(defaultRippleAnimation) {
color: attentionButtonBgRipple;
diff --git a/Telegram/SourceFiles/window/main_window.cpp b/Telegram/SourceFiles/window/main_window.cpp
index 11a64eed3..c6b5cd43c 100644
--- a/Telegram/SourceFiles/window/main_window.cpp
+++ b/Telegram/SourceFiles/window/main_window.cpp
@@ -24,7 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "localstorage.h"
#include "styles/style_window.h"
#include "platform/platform_window_title.h"
-#include "window/window_theme.h"
+#include "window/themes/window_theme.h"
#include "mediaview.h"
#include "mainwindow.h"
@@ -248,11 +248,16 @@ void MainWindow::resizeEvent(QResizeEvent *e) {
void MainWindow::updateControlsGeometry() {
auto bodyTop = 0;
+ auto bodyWidth = width();
if (_title && !_title->isHidden()) {
_title->setGeometry(0, bodyTop, width(), _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() {
@@ -276,7 +281,7 @@ void MainWindow::savePosition(Qt::WindowState state) {
auto r = geometry();
curPos.x = r.x();
curPos.y = r.y();
- curPos.w = r.width();
+ curPos.w = r.width() - (_rightColumn ? _rightColumn->width() : 0);
curPos.h = r.height();
curPos.maximized = 0;
}
@@ -317,6 +322,35 @@ bool MainWindow::minimizeToTray() {
return true;
}
+void MainWindow::showRightColumn(object_ptr 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) {
if (!_mediaView || _mediaView->isHidden()) return;
_mediaView->documentUpdated(doc);
diff --git a/Telegram/SourceFiles/window/main_window.h b/Telegram/SourceFiles/window/main_window.h
index 70c196121..cf7c19456 100644
--- a/Telegram/SourceFiles/window/main_window.h
+++ b/Telegram/SourceFiles/window/main_window.h
@@ -68,6 +68,8 @@ public:
QWidget *filedialogParent();
+ void showRightColumn(object_ptr widget);
+
virtual void updateTrayMenu(bool force = false) {
}
@@ -125,6 +127,8 @@ protected:
virtual void showTrayTooltip() {
}
+ virtual void updateControlsGeometry();
+
// This one is overriden in Windows for historical reasons.
virtual int32 screenNameChecksum(const QString &name) const;
@@ -143,7 +147,6 @@ private slots:
private:
void updatePalette();
- void updateControlsGeometry();
void updateUnreadCounter();
void initSize();
@@ -154,6 +157,7 @@ private:
object_ptr _title = { nullptr };
object_ptr _body;
+ object_ptr _rightColumn = { nullptr };
QString _titleText;
diff --git a/Telegram/SourceFiles/window/notifications_manager_default.cpp b/Telegram/SourceFiles/window/notifications_manager_default.cpp
index bbcefdaad..b1f7ea681 100644
--- a/Telegram/SourceFiles/window/notifications_manager_default.cpp
+++ b/Telegram/SourceFiles/window/notifications_manager_default.cpp
@@ -28,7 +28,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "ui/widgets/buttons.h"
#include "ui/widgets/input_fields.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_boxes.h"
#include "styles/style_window.h"
diff --git a/Telegram/SourceFiles/window/window_theme.cpp b/Telegram/SourceFiles/window/themes/window_theme.cpp
similarity index 75%
rename from Telegram/SourceFiles/window/window_theme.cpp
rename to Telegram/SourceFiles/window/themes/window_theme.cpp
index ef8e04f30..95bc5cb43 100644
--- a/Telegram/SourceFiles/window/window_theme.cpp
+++ b/Telegram/SourceFiles/window/themes/window_theme.cpp
@@ -19,7 +19,7 @@ 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/window_theme.h"
+#include "window/themes/window_theme.h"
#include "mainwidget.h"
#include "localstorage.h"
@@ -27,6 +27,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "core/zlib_help.h"
#include "styles/style_widgets.h"
#include "styles/style_history.h"
+#include "boxes/backgroundbox.h"
namespace Window {
namespace Theme {
@@ -51,6 +52,13 @@ struct Data {
};
NeverFreedPointer instance;
+inline bool AreTestingTheme() {
+ if (instance) {
+ return !instance->applying.paletteForRevert.isEmpty();
+ }
+ return false;
+};
+
QByteArray readThemeContent(const QString &path) {
QFile file(path);
if (!file.exists()) {
@@ -63,7 +71,7 @@ QByteArray readThemeContent(const QString &path) {
return QByteArray();
}
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();
}
@@ -102,7 +110,7 @@ bool readNameAndValue(const char *&from, const char *end, QLatin1String *outName
return false;
}
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;
}
if (!skipWhitespaces(++from, end)) {
@@ -113,7 +121,7 @@ bool readNameAndValue(const char *&from, const char *end, QLatin1String *outName
if (*from == '#') ++from;
if (readName(from, end).size() == 0) {
- LOG(("Theme Error: Expected a color value in #rrggbb or #rrggbbaa format in the color scheme."));
+ LOG(("Theme Error: Expected a color value in #rrggbb or #rrggbbaa format in the color scheme (while reading key '%1')").arg(*outName));
return false;
}
*outValue = QLatin1String(valueStart, from - valueStart);
@@ -123,7 +131,7 @@ bool readNameAndValue(const char *&from, const char *end, QLatin1String *outName
return false;
}
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;
}
++from;
@@ -136,7 +144,7 @@ enum class SetResult {
NotFound,
};
SetResult setColorSchemeValue(QLatin1String name, QLatin1String value, Instance *out) {
- auto found = false;
+ auto result = style::palette::SetResult::Ok;
auto size = value.size();
auto data = value.data();
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 a = (size == 9) ? readHexUchar(data[7], data[8], error) : uchar(255);
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)));
- return SetResult::Bad;
+ LOG(("Theme Warning: Skipping value '%1: %2' (expected a color value in #rrggbb or #rrggbbaa or a previously defined key in the color scheme)").arg(name).arg(value));
+ return SetResult::Ok;
} else if (out) {
- found = out->palette.setColor(name, r, g, b, a);
+ result = out->palette.setColor(name, r, g, b, a);
} else {
- found = style::main_palette::setColor(name, r, g, b, a);
+ result = style::main_palette::setColor(name, r, g, b, a);
}
} else {
if (out) {
- found = out->palette.setColor(name, value);
+ result = out->palette.setColor(name, value);
} 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) {
- 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;
- }
-
- QMap 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;
- }
-
+bool loadColorScheme(const QByteArray &content, Instance *out) {
+ auto unsupported = QMap();
+ return ReadPaletteValues(content, [&unsupported, out](QLatin1String name, QLatin1String value) {
// Find the named value in the already read unsupported list.
value = unsupported.value(value, value);
@@ -188,11 +194,10 @@ bool loadColorScheme(const QByteArray &content, Instance *out = nullptr) {
if (result == SetResult::Bad) {
return false;
} 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);
}
- }
- return true;
+ return true;
+ });
}
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);
if (file.error() == UNZ_OK) {
auto schemeContent = file.readFileContent("colors.tdesktop-theme", zlib::kCaseInsensitive, kThemeSchemeSizeLimit);
+ if (file.error() == UNZ_END_OF_LIST_OF_FILE) {
+ file.clearError();
+ schemeContent = file.readFileContent("colors.tdesktop-palette", zlib::kCaseInsensitive, kThemeSchemeSizeLimit);
+ }
if (file.error() != UNZ_OK) {
- LOG(("Theme Error: could not read 'colors.tdesktop-theme' in the theme file."));
+ LOG(("Theme Error: could not read 'colors.tdesktop-theme' or 'colors.tdesktop-palette' in the theme file."));
return false;
}
if (!loadColorScheme(schemeContent, out)) {
@@ -330,13 +339,13 @@ QImage prepareBackgroundImage(QImage &&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;
original.setHslF(hue, saturation, original.lightnessF(), original.alphaF());
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);
uint64 components[3] = { 0 };
@@ -361,12 +370,12 @@ void initColorsFromBackground(const QImage &img) {
auto bgColor = QColor(components[0], components[1], components[2]);
auto hue = bgColor.hslHueF();
auto saturation = bgColor.hslSaturationF();
- initColor(st::msgServiceBg, hue, saturation);
- initColor(st::msgServiceBgSelected, hue, saturation);
- initColor(st::historyScroll.bg, hue, saturation);
- initColor(st::historyScroll.bgOver, hue, saturation);
- initColor(st::historyScroll.barBg, hue, saturation);
- initColor(st::historyScroll.barBgOver, hue, saturation);
+ adjustColor(st::msgServiceBg, hue, saturation);
+ adjustColor(st::msgServiceBgSelected, hue, saturation);
+ adjustColor(st::historyScroll.bg, hue, saturation);
+ adjustColor(st::historyScroll.bgOver, hue, saturation);
+ adjustColor(st::historyScroll.barBg, hue, saturation);
+ adjustColor(st::historyScroll.barBgOver, hue, saturation);
}
} // namespace
@@ -392,7 +401,9 @@ void ChatBackground::setImage(int32 id, QImage &&image) {
if (_id == kThemeBackground) {
_tile = _themeTile;
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()) {
image.load(qsl(":/gui/art/bg.jpg"));
_id = internal::kTestingDefaultBackground;
@@ -420,14 +431,35 @@ void ChatBackground::setImage(int32 id, QImage &&image) {
void ChatBackground::setPreparedImage(QImage &&image) {
image = std_::move(image).convertToFormat(QImage::Format_ARGB32_Premultiplied);
image.setDevicePixelRatio(cRetinaFactor());
- if (_id != kThemeBackground && _id != internal::kTestingThemeBackground) {
- auto colorsFromSomeTheme = Local::hasTheme();
- if (instance && !instance->applying.paletteForRevert.isEmpty()) {
- colorsFromSomeTheme = !instance->applying.path.isEmpty();
- }
- if (colorsFromSomeTheme || (_id != kDefaultBackground && _id != internal::kTestingDefaultBackground)) {
- initColorsFromBackground(image);
+
+ auto adjustColors = [this] {
+ auto someCustomThemeApplied = [] {
+ if (AreTestingTheme()) {
+ return !instance->applying.path.isEmpty();
+ }
+ 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();
@@ -522,7 +554,13 @@ void ChatBackground::saveForRevert() {
void ChatBackground::setTestingTheme(Instance &&theme) {
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();
setImage(internal::kTestingThemeBackground, std_::move(theme.background));
setTile(theme.tiled);
@@ -547,7 +585,12 @@ void ChatBackground::setTestingDefaultTheme() {
}
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;
_themeImage = _pixmap.toImage();
_themeTile = _tile;
@@ -565,11 +608,13 @@ void ChatBackground::writeNewBackgroundSettings() {
if (_tile != _tileForRevert) {
Local::writeUserSettings();
}
- Local::writeBackground(_id, QImage());
+ Local::writeBackground(_id, (_id == kThemeBackground || _id == kDefaultBackground) ? QImage() : _pixmap.toImage());
}
void ChatBackground::revert() {
- if (_id == internal::kTestingThemeBackground || _id == internal::kTestingDefaultBackground) {
+ if (_id == internal::kTestingThemeBackground
+ || _id == internal::kTestingDefaultBackground
+ || _id == internal::kTestingEditorBackground) {
setTile(_tileForRevert);
setImage(_idForRevert, std_::move(_imageForRevert));
} else {
@@ -639,6 +684,27 @@ void ApplyDefault() {
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() {
if (!instance) {
return;
@@ -669,6 +735,13 @@ bool LoadFromFile(const QString &path, Instance *out, QByteArray *outContent) {
return loadTheme(*outContent, out->cached, out);
}
+bool IsPaletteTestingPath(const QString &path) {
+ if (path.endsWith(qstr(".tdesktop-palette"), Qt::CaseInsensitive)) {
+ return QFileInfo(path).exists();
+ }
+ return false;
+}
+
void ComputeBackgroundRects(QRect wholeFill, QSize imageSize, QRect &to, QRect &from) {
if (uint64(imageSize.width()) * wholeFill.height() > uint64(imageSize.height()) * wholeFill.width()) {
float64 pxsize = wholeFill.height() / float64(imageSize.height());
@@ -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 &&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 Window
diff --git a/Telegram/SourceFiles/window/window_theme.h b/Telegram/SourceFiles/window/themes/window_theme.h
similarity index 90%
rename from Telegram/SourceFiles/window/window_theme.h
rename to Telegram/SourceFiles/window/themes/window_theme.h
index 1e7ad3b73..8c6c717c2 100644
--- a/Telegram/SourceFiles/window/window_theme.h
+++ b/Telegram/SourceFiles/window/themes/window_theme.h
@@ -27,6 +27,7 @@ namespace internal {
constexpr int32 kUninitializedBackground = -999;
constexpr int32 kTestingThemeBackground = -666;
constexpr int32 kTestingDefaultBackground = -665;
+constexpr int32 kTestingEditorBackground = -664;
} // namespace internal
@@ -62,10 +63,12 @@ struct Preview {
bool Apply(const QString &filepath);
bool Apply(std_::unique_ptr preview);
void ApplyDefault();
+bool ApplyEditedPalette(const QString &path, const QByteArray &content);
void KeepApplied();
void Revert();
bool LoadFromFile(const QString &file, Instance *out, QByteArray *outContent);
+bool IsPaletteTestingPath(const QString &path);
struct BackgroundUpdate {
enum class Type {
@@ -136,5 +139,9 @@ ChatBackground *Background();
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 &&callback);
+
} // namespace Theme
} // namespace Window
diff --git a/Telegram/SourceFiles/window/themes/window_theme_editor.cpp b/Telegram/SourceFiles/window/themes/window_theme_editor.cpp
new file mode 100644
index 000000000..51e2e490b
--- /dev/null
+++ b/Telegram/SourceFiles/window/themes/window_theme_editor.cpp
@@ -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();
+ 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 &&callback) {
+ _errorCallback = std_::move(callback);
+ }
+ void setFocusCallback(base::lambda &&callback) {
+ _focusCallback = std_::move(callback);
+ }
+ void setScrollCallback(base::lambda &&callback) {
+ _scrollCallback = std_::move(callback);
+ }
+
+ void prepare();
+
+ base::lambda 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 ©Of, QColor value);
+
+ EditorBlock::Context _context;
+
+ QString _path;
+ QByteArray _paletteContent;
+ base::lambda _errorCallback;
+ base::lambda _focusCallback;
+ base::lambda _scrollCallback;
+
+ object_ptr _existingRows;
+ object_ptr _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 _chooseFromFile;
+ object_ptr _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(lang(lng_theme_editor_cant_change_theme)));
+ });
+ }
+ });
+}
+
+void Editor::Inner::prepare() {
+ if (!readData()) {
+ error();
+ }
+}
+
+base::lambda 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(_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 ©Of, 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(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(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(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(this, path));
+
+ _export->setClickedCallback(_inner->exportCallback());
+
+ _inner->setErrorCallback([this] {
+ Ui::show(Box(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(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(path));
+ }
+}
+
+void Editor::closeEditor() {
+ if (auto window = App::wnd()) {
+ window->showRightColumn(nullptr);
+ }
+}
+
+} // namespace Theme
+} // namespace Window
diff --git a/Telegram/SourceFiles/window/themes/window_theme_editor.h b/Telegram/SourceFiles/window/themes/window_theme_editor.h
new file mode 100644
index 000000000..8a27ab660
--- /dev/null
+++ b/Telegram/SourceFiles/window/themes/window_theme_editor.h
@@ -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 _scroll;
+ class Inner;
+ QPointer _inner;
+ object_ptr _close;
+ object_ptr _select;
+ object_ptr _leftShadow;
+ object_ptr _topShadow;
+ object_ptr _export;
+
+};
+
+} // namespace Theme
+} // namespace Window
diff --git a/Telegram/SourceFiles/window/themes/window_theme_editor_block.cpp b/Telegram/SourceFiles/window/themes/window_theme_editor_block.cpp
new file mode 100644
index 000000000..7c1cf701d
--- /dev/null
+++ b/Telegram/SourceFiles/window/themes/window_theme_editor_block.cpp
@@ -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 ©Of, QColor value);
+
+ QString name() const {
+ return _name;
+ }
+
+ void setCopyOf(const QString ©Of) {
+ _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 &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 &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 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 _searchWords;
+ OrderedSet _searchStartChars;
+
+ int _top = 0;
+ int _height = 0;
+
+ mutable std_::unique_ptr _ripple;
+
+};
+
+EditorBlock::Row::Row(const QString &name, const QString ©Of, 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 ©OfExisting) {
+ 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 ©Of) {
+ 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(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();
+ 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
+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
+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
+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
+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(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 ©Of, 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
diff --git a/Telegram/SourceFiles/window/themes/window_theme_editor_block.h b/Telegram/SourceFiles/window/themes/window_theme_editor_block.h
new file mode 100644
index 000000000..5730e1ac8
--- /dev/null
+++ b/Telegram/SourceFiles/window/themes/window_theme_editor_block.h
@@ -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 box;
+ QString name;
+ QString possibleCopyOf;
+
+ base::Observable updated;
+ base::Observable resized;
+
+ struct AppendData {
+ QString name;
+ QString possibleCopyOf;
+ QColor value;
+ QString description;
+ };
+ base::Observable appended;
+
+ struct ChangeData {
+ QStringList names;
+ QColor value;
+ };
+ base::Observable changed;
+
+ struct EditionData {
+ QString name;
+ QString copyOf;
+ QColor value;
+ };
+ base::Observable pending;
+
+ struct ScrollData {
+ Type type;
+ int position;
+ int height;
+ };
+ base::Observable 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 ©OfExisting = QString());
+ bool feedCopy(const QString &name, const QString ©Of);
+ 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 ©Of, QColor value);
+ void removeRow(const QString &name, bool removeCopyReferences = true);
+
+ void addToSearch(const Row &row);
+ void removeFromSearch(const Row &row);
+
+ template
+ void enumerateRows(Callback callback);
+
+ template
+ void enumerateRows(Callback callback) const;
+
+ template
+ void enumerateRowsFrom(int top, Callback callback);
+
+ template
+ 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 _data;
+ QMap _indices;
+
+ QString _searchQuery;
+ QVector _searchResults;
+ QMap> _searchIndex;
+
+ int _selected = -1;
+ int _pressed = -1;
+ int _editing = -1;
+
+ QPoint _lastGlobalPos;
+ bool _mouseSelection = false;
+
+ QBrush _transparent;
+
+};
+
+} // namespace Theme
+} // namespace Window
diff --git a/Telegram/SourceFiles/window/window_theme_preview.cpp b/Telegram/SourceFiles/window/themes/window_theme_preview.cpp
similarity index 99%
rename from Telegram/SourceFiles/window/window_theme_preview.cpp
rename to Telegram/SourceFiles/window/themes/window_theme_preview.cpp
index 82c49ac8a..abcaaa17c 100644
--- a/Telegram/SourceFiles/window/window_theme_preview.cpp
+++ b/Telegram/SourceFiles/window/themes/window_theme_preview.cpp
@@ -19,9 +19,9 @@ 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/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 "platform/platform_window_title.h"
#include "styles/style_widgets.h"
diff --git a/Telegram/SourceFiles/window/window_theme_preview.h b/Telegram/SourceFiles/window/themes/window_theme_preview.h
similarity index 96%
rename from Telegram/SourceFiles/window/window_theme_preview.h
rename to Telegram/SourceFiles/window/themes/window_theme_preview.h
index 5b93d2149..9366ac763 100644
--- a/Telegram/SourceFiles/window/window_theme_preview.h
+++ b/Telegram/SourceFiles/window/themes/window_theme_preview.h
@@ -20,7 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
-#include "window/window_theme.h"
+#include "window/themes/window_theme.h"
namespace Window {
namespace Theme {
diff --git a/Telegram/SourceFiles/window/window_theme_warning.cpp b/Telegram/SourceFiles/window/themes/window_theme_warning.cpp
similarity index 97%
rename from Telegram/SourceFiles/window/window_theme_warning.cpp
rename to Telegram/SourceFiles/window/themes/window_theme_warning.cpp
index e689216c8..19fab5e79 100644
--- a/Telegram/SourceFiles/window/window_theme_warning.cpp
+++ b/Telegram/SourceFiles/window/themes/window_theme_warning.cpp
@@ -19,12 +19,12 @@ 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/window_theme_warning.h"
+#include "window/themes/window_theme_warning.h"
#include "styles/style_boxes.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/shadow.h"
-#include "window/window_theme.h"
+#include "window/themes/window_theme.h"
#include "lang.h"
namespace Window {
diff --git a/Telegram/SourceFiles/window/window_theme_warning.h b/Telegram/SourceFiles/window/themes/window_theme_warning.h
similarity index 100%
rename from Telegram/SourceFiles/window/window_theme_warning.h
rename to Telegram/SourceFiles/window/themes/window_theme_warning.h
diff --git a/Telegram/SourceFiles/window/window.style b/Telegram/SourceFiles/window/window.style
index 4fdc48e5f..425957345 100644
--- a/Telegram/SourceFiles/window/window.style
+++ b/Telegram/SourceFiles/window/window.style
@@ -267,6 +267,12 @@ topBarInfoButton: PeerAvatarButton {
}
topBarSlideDuration: 200;
+themeEditorSampleSize: size(90px, 51px);
+themeEditorMargin: margins(17px, 10px, 17px, 10px);
+themeEditorDescriptionSkip: 10px;
+themeEditorNameFont: font(15px semibold);
+themeEditorCopyNameFont: font(fsize semibold);
+
// Mac specific
macAccessoryWidth: 450.;
diff --git a/Telegram/build/set_version.bat b/Telegram/build/set_version.bat
index 35f4d4f10..01f2f2564 100644
--- a/Telegram/build/set_version.bat
+++ b/Telegram/build/set_version.bat
@@ -104,7 +104,7 @@ call :repl "Replace=("FileVersion",) (\s*)"\d+.\d+.\d+.\d+"/
call :repl "Replace=("ProductVersion",) (\s*)"\d+.\d+.\d+.\d+"/$1$2 "%VersionMajor%.%VersionMinor%.%VersionPatch%.%VersionBeta%"" "Filename=%ResourcePath%" || goto :error
echo Patching appxmanifest.xml...
-set "ResourcePath=%FullScriptPath%..\Resources\uwp\appxmanifest.xml"
+set "ResourcePath=%FullScriptPath%..\Resources\uwp\AppX\AppxManifest.xml"
call :repl "Replace= (Version=)"\d+.\d+.\d+.\d+"/ $1"%VersionMajor%.%VersionMinor%.%VersionPatch%.%VersionBeta%"" "Filename=%ResourcePath%" || goto :error
exit /b
diff --git a/Telegram/build/set_version.sh b/Telegram/build/set_version.sh
index ba667a757..1580682af 100755
--- a/Telegram/build/set_version.sh
+++ b/Telegram/build/set_version.sh
@@ -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 "\(\"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"
diff --git a/Telegram/build/version b/Telegram/build/version
index cdafbd7aa..b930f657b 100644
--- a/Telegram/build/version
+++ b/Telegram/build/version
@@ -3,4 +3,4 @@ AppVersionStrMajor 1.0
AppVersionStrSmall 1.0.6
AppVersionStr 1.0.6
AlphaChannel 0
-BetaVersion 0
+BetaVersion 1000006001
diff --git a/Telegram/prepare.bat b/Telegram/create.bat
similarity index 92%
rename from Telegram/prepare.bat
rename to Telegram/create.bat
index 950f24863..363748109 100644
--- a/Telegram/prepare.bat
+++ b/Telegram/create.bat
@@ -4,10 +4,7 @@ set "FullScriptPath=%~dp0"
set "FullExecPath=%cd%"
set "Command=%1"
-if "%Command%" == "module" (
- call :write_module %2
- exit /b %errorlevel%
-) else if "%Command%" == "header" (
+if "%Command%" == "header" (
call :write_header %2
exit /b %errorlevel%
) else if "%Command%" == "source" (
@@ -15,11 +12,8 @@ if "%Command%" == "module" (
exit /b %errorlevel%
)
-cd gyp
-call refresh.bat
-cd ..
-
-exit /b
+call :write_module %Command%
+exit /b %errorlevel%
:write_module
(
@@ -30,8 +24,8 @@ exit /b
exit /b 1
)
echo Generating module !CommandPathUnix!..
- call prepare.bat header !CommandPathUnix!
- call prepare.bat source !CommandPathUnix!
+ call create.bat header !CommandPathUnix!
+ call create.bat source !CommandPathUnix!
exit /b
)
diff --git a/Telegram/gyp/Telegram.gyp b/Telegram/gyp/Telegram.gyp
index a1e632ae9..45ab8cd27 100644
--- a/Telegram/gyp/Telegram.gyp
+++ b/Telegram/gyp/Telegram.gyp
@@ -173,6 +173,8 @@
'<(src_loc)/boxes/contactsbox.h',
'<(src_loc)/boxes/downloadpathbox.cpp',
'<(src_loc)/boxes/downloadpathbox.h',
+ '<(src_loc)/boxes/editcolorbox.cpp',
+ '<(src_loc)/boxes/editcolorbox.h',
'<(src_loc)/boxes/emojibox.cpp',
'<(src_loc)/boxes/emojibox.h',
'<(src_loc)/boxes/languagebox.cpp',
@@ -457,6 +459,8 @@
'<(src_loc)/settings/settings_info_widget.h',
'<(src_loc)/settings/settings_inner_widget.cpp',
'<(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.h',
'<(src_loc)/settings/settings_privacy_widget.cpp',
@@ -574,12 +578,16 @@
'<(src_loc)/window/top_bar_widget.h',
'<(src_loc)/window/window_main_menu.cpp',
'<(src_loc)/window/window_main_menu.h',
- '<(src_loc)/window/window_theme.cpp',
- '<(src_loc)/window/window_theme.h',
- '<(src_loc)/window/window_theme_preview.cpp',
- '<(src_loc)/window/window_theme_preview.h',
- '<(src_loc)/window/window_theme_warning.cpp',
- '<(src_loc)/window/window_theme_warning.h',
+ '<(src_loc)/window/themes/window_theme.cpp',
+ '<(src_loc)/window/themes/window_theme.h',
+ '<(src_loc)/window/themes/window_theme_editor.cpp',
+ '<(src_loc)/window/themes/window_theme_editor.h',
+ '<(src_loc)/window/themes/window_theme_editor_block.cpp',
+ '<(src_loc)/window/themes/window_theme_editor_block.h',
+ '<(src_loc)/window/themes/window_theme_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',
'<(sp_media_key_tap_loc)/SPMediaKeyTap.m',