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

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B

View File

@ -1023,6 +1023,22 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
"lng_confirm_phone_send" = "Send";
"lng_confirm_phone_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";

View File

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

View File

@ -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"

View File

@ -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"

View File

@ -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")) {

View File

@ -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"

View File

@ -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"

View File

@ -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);
}
}

View File

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

View File

@ -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));
}
}

View File

@ -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"

View File

@ -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) {
}

View File

@ -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));
}
}

View File

@ -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"

View File

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

View File

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

View File

@ -37,7 +37,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "history/history_media_types.h"
#include "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 &&copyCallback, 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();
}
};

View File

@ -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) {

View File

@ -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())) {

View File

@ -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<QByteArray> singleLineComments_;
// Where the last (currently read) token has started.
const char *tokenStart_ = nullptr;

View File

@ -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<QByteArray> CleanFile::singleLineComments() const {
return singleLineComments_;
}
LogStream CleanFile::logError(int code, int line) const {
return common::logError(code, filepath_, line);
}

View File

@ -38,6 +38,7 @@ public:
CleanFile &operator=(const CleanFile &other) = delete;
bool read();
QVector<QByteArray> 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<Comment> comments_;
QVector<QByteArray> singleLineComments_;
};

View File

@ -63,6 +63,10 @@ public:
return (end_ - pos_);
}
QVector<QByteArray> 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<LogStream>(file_.logError(code, line));

View File

@ -106,12 +106,13 @@ char hexFirstChar(char ch) {
return hexChar((*reinterpret_cast<uchar*>(&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<row> 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<structure::FullName> 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<row> data() {\n\
auto result = QList<row>();\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<structure::FullName> names;
module_.enumVariables([this, &names](const Variable &variable) -> bool {
names.push_back(variable.name);
return true;
});
bool result = module_.enumVariables([this, &names, &stream](const Variable &variable) -> bool {
auto name = variable.name.back();
if (variable.value.type().tag != structure::TypeTag::Color) {
return false;
}
auto color = variable.value.Color();
//color.red = uchar(rand() % 256);
//color.green = uchar(rand() % 256);
//color.blue = uchar(rand() % 256);
//auto fallbackIndex = -1;
auto fallbackIndex = paletteIndices_.value(colorFallbackName(variable.value), -1);
auto colorString = paletteColorValue(color);
if (fallbackIndex >= 0) {
auto fallbackVariable = module_.findVariableInModule(names[fallbackIndex - 1], module_);
if (!fallbackVariable || fallbackVariable->value.type().tag != structure::TypeTag::Color) {
return false;
}
auto fallbackName = fallbackVariable->name.back();
auto fallbackColor = fallbackVariable->value.Color();
if (colorString == paletteColorValue(fallbackColor)) {
stream << name << ": " << fallbackName << ";\n";
} else {
stream << name << ": #" << colorString << "; // " << fallbackName << ";\n";
}
} else {
stream << name << ": #" << colorString << ";\n";
}
return true;
});
if (!result) {
return result;
}
stream.flush();
QFile file(filepath);
if (file.open(QIODevice::ReadOnly)) {
if (file.readAll() == content) {
file.close();
return true;
}
file.close();
}
if (!file.open(QIODevice::WriteOnly)) {
return false;
}
if (file.write(content) != content.size()) {
return false;
}
return true;
}
} // namespace style
} // namespace codegen

View File

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

View File

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

View File

@ -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;
}

View File

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

View File

@ -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)) {

View File

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

View File

@ -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";

View File

@ -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"

View File

@ -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<InformBox>(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);

View File

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

View File

@ -26,7 +26,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "serialize/serialize_document.h"
#include "serialize/serialize_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()) {

View File

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

View File

@ -57,7 +57,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "media/player/media_player_instance.h"
#include "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"

View File

@ -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<Settings::Widget>());
}
void MainWindow::showSpecialLayer(object_ptr<LayerWidget> layer) {
if (_passcode) return;
if (!_layerBg) {
_layerBg.create(bodyWidget());
}
_layerBg->showSpecialLayer(Box<Settings::Widget>());
_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);

View File

@ -145,6 +145,8 @@ public:
void showMainMenu();
void updateTrayMenu(bool force = false) override;
void showSpecialLayer(object_ptr<LayerWidget> layer);
void ui_showBox(object_ptr<BoxContent> 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;

View File

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

View File

@ -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 {

View File

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

View File

@ -33,7 +33,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "ui/widgets/buttons.h"
#include "ui/widgets/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"

View File

@ -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 <qpa/qplatformnativeinterface.h>

View File

@ -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;
});
}

View File

@ -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 {

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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();

View File

@ -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(); });

View File

@ -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);
}

View File

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

View File

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

View File

@ -33,7 +33,11 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "mainwidget.h"
#include "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<BoxLayerTitleShadow>(this)) {
_inner = _scroll->setOwnedWidget(object_ptr<InnerWidget>(this));
_fixedBar->moveToLeft(0, st::boxRadius);
_fixedBarClose->moveToRight(0, 0);
_fixedBarShadow->entity()->resize(width(), st::lineWidth);
_fixedBarShadow->moveToLeft(0, _fixedBar->y() + _fixedBar->height());
_fixedBarShadow->hideFast();
_scroll->moveToLeft(0, st::settingsFixedBarHeight);
connect(_scroll, SIGNAL(scrolled()), this, SLOT(onScroll()));
_fixedBarClose->setClickedCallback([]() {
Widget::Widget(QWidget *parent) {
setTitle(lang(lng_menu_settings));
_inner = setInnerWidget(object_ptr<InnerWidget>(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

View File

@ -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 <typename Widget>
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<Ui::ScrollArea> _scroll;
QPointer<InnerWidget> _inner;
object_ptr<FixedBar> _fixedBar;
object_ptr<Ui::IconButton> _fixedBarClose;
object_ptr<Ui::WidgetFadeWrap<BoxLayerTitleShadow>> _fixedBarShadow;
int _contentLeft = 0;
bool _roundedCorners = false;
};

View File

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

View File

@ -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 {

View File

@ -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()) {

View File

@ -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<QueryUpdate> &QueryDone() {
return QueryDoneObservable;
}
void askOpenPath(const QString &caption, const QString &filter, base::lambda<void(const OpenResult &result)> &&callback, base::lambda<void()> &&failed) {
base::TaskQueue::Main().Put([caption, filter, callback = std_::move(callback), failed = std_::move(failed)] {
auto file = QString();
auto remoteContent = QByteArray();
if (filedialogGetOpenFile(file, remoteContent, caption, filter) && (!file.isEmpty() || !remoteContent.isEmpty())) {
if (callback) {
auto result = OpenResult();
if (!file.isEmpty()) {
result.paths.push_back(file);
}
result.remoteContent = remoteContent;
callback(result);
}
} else if (failed) {
failed();
}
});
}
void askOpenPaths(const QString &caption, const QString &filter, base::lambda<void(const OpenResult &result)> &&callback, base::lambda<void()> &&failed) {
base::TaskQueue::Main().Put([caption, filter, callback = std_::move(callback), failed = std_::move(failed)] {
auto files = QStringList();
auto remoteContent = QByteArray();
if (filedialogGetOpenFiles(files, remoteContent, caption, filter) && (!files.isEmpty() || !remoteContent.isEmpty())) {
if (callback) {
auto result = OpenResult();
result.paths = files;
result.remoteContent = remoteContent;
callback(result);
}
} else if (failed) {
failed();
}
});
}
void askWritePath(const QString &caption, const QString &filter, const QString &initialPath, base::lambda<void(const QString &result)> &&callback, base::lambda<void()> &&failed) {
base::TaskQueue::Main().Put([caption, filter, initialPath, callback = std_::move(callback), failed = std_::move(failed)] {
auto file = QString();
if (filedialogGetSaveFile(file, caption, filter, initialPath)) {
if (callback) {
callback(file);
}
} else if (failed) {
failed();
}
});
}
void askFolder(const QString &caption, base::lambda<void(const QString &result)> &&callback, base::lambda<void()> &&failed) {
base::TaskQueue::Main().Put([caption, callback = std_::move(callback), failed = std_::move(failed)] {
auto folder = QString();
if (filedialogGetDir(folder, caption) && !folder.isEmpty()) {
if (callback) {
callback(folder);
}
} else if (failed) {
failed();
}
});
}
} // namespace FileDialog

View File

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

View File

@ -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) {

View File

@ -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);

View File

@ -23,6 +23,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "ui/toast/toast_manager.h"
#include "ui/toast/toast_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();

View File

@ -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 {

View File

@ -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()) {

View File

@ -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();

View File

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

View File

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

View File

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

View File

@ -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<TWidget> widget) {
auto wasWidth = width();
auto wasRightWidth = _rightColumn ? _rightColumn->width() : 0;
_rightColumn = std_::move(widget);
if (_rightColumn) {
_rightColumn->setParent(this);
_rightColumn->show();
_rightColumn->setFocus();
} else if (App::wnd()) {
App::wnd()->setInnerFocus();
}
auto nowRightWidth = _rightColumn ? _rightColumn->width() : 0;
setMinimumWidth(st::windowMinWidth + nowRightWidth);
auto nowWidth = width();
if (!isMaximized()) {
auto desktop = QDesktopWidget().availableGeometry(this);
auto newWidth = qMin(wasWidth + nowRightWidth - wasRightWidth, desktop.width());
auto newLeft = qMin(x(), desktop.x() + desktop.width() - newWidth);
if (x() != newLeft || width() != newWidth) {
setGeometry(newLeft, y(), newWidth, height());
} else {
updateControlsGeometry();
}
} else {
updateControlsGeometry();
}
}
void MainWindow::documentUpdated(DocumentData *doc) {
if (!_mediaView || _mediaView->isHidden()) return;
_mediaView->documentUpdated(doc);

View File

@ -68,6 +68,8 @@ public:
QWidget *filedialogParent();
void showRightColumn(object_ptr<TWidget> 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<TitleWidget> _title = { nullptr };
object_ptr<TWidget> _body;
object_ptr<TWidget> _rightColumn = { nullptr };
QString _titleText;

View File

@ -28,7 +28,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "ui/widgets/buttons.h"
#include "ui/widgets/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"

View File

@ -19,7 +19,7 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#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<Data> 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<QLatin1String, QLatin1String> unsupported;
auto data = base::parse::stripComments(content);
auto from = data.constData(), end = from + data.size();
while (from != end) {
QLatin1String name(""), value("");
if (!readNameAndValue(from, end, &name, &value)) {
return false;
}
if (name.size() == 0) { // End of content reached.
return true;
}
bool loadColorScheme(const QByteArray &content, Instance *out) {
auto unsupported = QMap<QLatin1String, QLatin1String>();
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<bool(QLatin1String name, QLatin1String value)> &&callback) {
if (content.size() > kThemeSchemeSizeLimit) {
LOG(("Theme Error: color scheme file too large (should be less than 1 MB, got %2)").arg(content.size()));
return false;
}
auto data = base::parse::stripComments(content);
auto from = data.constData(), end = from + data.size();
while (from != end) {
auto name = QLatin1String("");
auto value = QLatin1String("");
if (!readNameAndValue(from, end, &name, &value)) {
return false;
}
if (name.size() == 0) { // End of content reached.
return true;
}
if (!callback(name, value)) {
return false;
}
}
return true;
}
} // namespace Theme
} // namespace Window

View File

@ -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> 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<bool(QLatin1String name, QLatin1String value)> &&callback);
} // namespace Theme
} // namespace Window

View File

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

View File

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

View File

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

View File

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

View File

@ -19,9 +19,9 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#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"

View File

@ -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 {

View File

@ -19,12 +19,12 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#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 {

View File

@ -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.;

View File

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

View File

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

View File

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

View File

@ -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
)

View File

@ -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',