mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-01-20 22:41:11 +00:00
Closed beta 1000006001: Built in theme and color palette editor.
This commit is contained in:
parent
60f45ab9b3
commit
b842761ea3
@ -275,3 +275,5 @@ notifyFadeRight: icon {{ "fade_horizontal", notificationBg }};
|
||||
|
||||
stickerIconLeft: icon {{ "fade_horizontal-flip_horizontal", emojiPanCategories }};
|
||||
stickerIconRight: icon {{ "fade_horizontal", emojiPanCategories }};
|
||||
|
||||
transparentPlaceholderSize: 4px;
|
||||
|
BIN
Telegram/Resources/icons/color_slider_arrow.png
Normal file
BIN
Telegram/Resources/icons/color_slider_arrow.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 152 B |
BIN
Telegram/Resources/icons/color_slider_arrow@2x.png
Normal file
BIN
Telegram/Resources/icons/color_slider_arrow@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 193 B |
BIN
Telegram/Resources/icons/color_slider_arrow_vertical.png
Normal file
BIN
Telegram/Resources/icons/color_slider_arrow_vertical.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 175 B |
BIN
Telegram/Resources/icons/color_slider_arrow_vertical@2x.png
Normal file
BIN
Telegram/Resources/icons/color_slider_arrow_vertical@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 236 B |
@ -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";
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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")) {
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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) {
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
|
||||
|
908
Telegram/SourceFiles/boxes/editcolorbox.cpp
Normal file
908
Telegram/SourceFiles/boxes/editcolorbox.cpp
Normal 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();
|
||||
}
|
115
Telegram/SourceFiles/boxes/editcolorbox.h
Normal file
115
Telegram/SourceFiles/boxes/editcolorbox.h
Normal 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;
|
||||
|
||||
};
|
@ -37,7 +37,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "history/history_media_types.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "window/window_theme.h"
|
||||
#include "window/themes/window_theme.h"
|
||||
#include "boxes/contactsbox.h"
|
||||
|
||||
ShareBox::ShareBox(QWidget*, CopyCallback &©Callback, SubmitCallback &&submitCallback, FilterCallback &&filterCallback)
|
||||
@ -872,9 +872,7 @@ void shareGameScoreFromItem(HistoryItem *item) {
|
||||
|
||||
QApplication::clipboard()->setText(CreateInternalLinkHttps(bot->username + qsl("?game=") + shortName));
|
||||
|
||||
Ui::Toast::Config toast;
|
||||
toast.text = lang(lng_share_game_link_copied);
|
||||
Ui::Toast::Show(App::wnd(), toast);
|
||||
Ui::Toast::Show(lang(lng_share_game_link_copied));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -892,10 +890,7 @@ void shareGameScoreFromItem(HistoryItem *item) {
|
||||
}
|
||||
data->requests.remove(requestId);
|
||||
if (data->requests.empty()) {
|
||||
Ui::Toast::Config toast;
|
||||
toast.text = lang(lng_share_done);
|
||||
Ui::Toast::Show(App::wnd(), toast);
|
||||
|
||||
Ui::Toast::Show(lang(lng_share_done));
|
||||
Ui::hideLayer();
|
||||
}
|
||||
};
|
||||
|
@ -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) {
|
||||
|
@ -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())) {
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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_;
|
||||
|
||||
};
|
||||
|
||||
|
@ -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));
|
||||
|
@ -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
|
||||
|
@ -40,7 +40,6 @@ public:
|
||||
|
||||
bool writeHeader();
|
||||
bool writeSource();
|
||||
bool writeSampleTheme(const QString &filepath);
|
||||
|
||||
private:
|
||||
QString typeToString(structure::Type type) const;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -198,6 +198,7 @@ private:
|
||||
struct Variable {
|
||||
FullName name;
|
||||
Value value;
|
||||
QString description;
|
||||
|
||||
explicit operator bool() const {
|
||||
return !name.isEmpty();
|
||||
|
@ -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)) {
|
||||
|
@ -432,6 +432,7 @@ public:
|
||||
enum class Known {
|
||||
Unknown,
|
||||
TDesktopTheme,
|
||||
TDesktopPalette,
|
||||
WebP,
|
||||
};
|
||||
|
||||
|
@ -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";
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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)...);
|
||||
}
|
||||
|
@ -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()) {
|
||||
|
@ -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();
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -111,8 +111,6 @@ mediaviewFileIconSize: 80px;
|
||||
|
||||
mediaviewFileLink: defaultLinkButton;
|
||||
|
||||
mediaviewTransparentSize: 4px;
|
||||
|
||||
mediaviewMenu: Menu(defaultMenu) {
|
||||
itemBg: mediaviewMenuBg;
|
||||
itemBgOver: mediaviewMenuBgOver;
|
||||
|
@ -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 {
|
||||
|
@ -166,8 +166,6 @@ private:
|
||||
void findCurrent();
|
||||
void loadBack();
|
||||
|
||||
void generateTransparentBrush();
|
||||
|
||||
void updateCursor();
|
||||
void setZoomLevel(int newZoom);
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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(); });
|
||||
|
@ -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);
|
||||
}
|
||||
|
134
Telegram/SourceFiles/settings/settings_layer.cpp
Normal file
134
Telegram/SourceFiles/settings/settings_layer.cpp
Normal 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
|
94
Telegram/SourceFiles/settings/settings_layer.h
Normal file
94
Telegram/SourceFiles/settings/settings_layer.h
Normal 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
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
};
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -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()) {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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 {
|
||||
|
@ -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()) {
|
||||
|
@ -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();
|
||||
|
@ -213,6 +213,7 @@ public:
|
||||
CrossButton(QWidget *parent, const style::CrossButton &st);
|
||||
|
||||
void showAnimated();
|
||||
void showFast();
|
||||
void hideAnimated();
|
||||
void hideFast();
|
||||
|
||||
|
@ -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"
|
||||
|
||||
|
@ -586,7 +586,7 @@ attentionLeftOutlineButton: OutlineButton(defaultLeftOutlineButton) {
|
||||
textBgOver: attentionButtonBgOver;
|
||||
|
||||
textFg: attentionButtonFg;
|
||||
textFgOver: attentionButtonFg;
|
||||
textFgOver: attentionButtonFgOver;
|
||||
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: attentionButtonBgRipple;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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
|
@ -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
|
817
Telegram/SourceFiles/window/themes/window_theme_editor.cpp
Normal file
817
Telegram/SourceFiles/window/themes/window_theme_editor.cpp
Normal 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 ©Of, 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 ©Of, QColor value) {
|
||||
auto plainName = name.toLatin1();
|
||||
auto plainValue = (copyOf.isEmpty() ? colorString(value) : copyOf).toLatin1();
|
||||
auto newContent = replaceValueInContent(_paletteContent, plainName, plainValue);
|
||||
if (newContent == "error") {
|
||||
LOG(("Theme Error: could not replace '%1: %2' in content").arg(name).arg(copyOf.isEmpty() ? colorString(value) : copyOf));
|
||||
error();
|
||||
return;
|
||||
}
|
||||
if (newContent.isEmpty()) {
|
||||
auto newline = (_paletteContent.indexOf("\r\n") >= 0 ? "\r\n" : "\n");
|
||||
auto addedline = (_paletteContent.endsWith('\n') ? "" : newline);
|
||||
newContent = _paletteContent + addedline + plainName + ": " + plainValue + ";" + newline;
|
||||
}
|
||||
QFile f(_path);
|
||||
if (!f.open(QIODevice::WriteOnly)) {
|
||||
LOG(("Theme Error: could not open '%1' for writing a palette update.").arg(_path));
|
||||
error();
|
||||
return;
|
||||
}
|
||||
if (f.write(newContent) != newContent.size()) {
|
||||
LOG(("Theme Error: could not write all content to '%1' while writing a palette update.").arg(_path));
|
||||
error();
|
||||
return;
|
||||
}
|
||||
f.close();
|
||||
|
||||
_applyingUpdate = true;
|
||||
if (!ApplyEditedPalette(_path, newContent)) {
|
||||
LOG(("Theme Error: could not apply newly composed content :("));
|
||||
error();
|
||||
return;
|
||||
}
|
||||
_applyingUpdate = false;
|
||||
|
||||
_paletteContent = newContent;
|
||||
}
|
||||
|
||||
void writeDefaultPalette(const QString &path) {
|
||||
QFile f(path);
|
||||
if (!f.open(QIODevice::WriteOnly)) {
|
||||
LOG(("Theme Error: could not open '%1' for writing.").arg(path));
|
||||
return;
|
||||
}
|
||||
|
||||
QTextStream stream(&f);
|
||||
stream.setCodec("UTF-8");
|
||||
|
||||
auto rows = style::main_palette::data();
|
||||
for_const (auto &row, rows) {
|
||||
stream << bytesToUtf8(row.name) << ": " << bytesToUtf8(row.value) << "; // " << bytesToUtf8(row.description).replace('\n', ' ').replace('\r', ' ') << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
ThemeExportBox::ThemeExportBox(QWidget*, const QByteArray &paletteContent, const QImage &background, const QByteArray &backgroundContent, bool tileBackground) : BoxContent()
|
||||
, _paletteContent(paletteContent)
|
||||
, _background(background)
|
||||
, _backgroundContent(backgroundContent)
|
||||
, _chooseFromFile(this, lang(lng_settings_bg_from_file), st::boxLinkButton)
|
||||
, _tileBackground(this, lang(lng_settings_bg_tile), tileBackground, st::defaultBoxCheckbox) {
|
||||
_imageText = lng_theme_editor_saved_to_jpg(lt_size, formatSizeText(_backgroundContent.size()));
|
||||
_chooseFromFile->setClickedCallback([this] { chooseBackgroundFromFile(); });
|
||||
}
|
||||
|
||||
void ThemeExportBox::prepare() {
|
||||
setTitle(lang(lng_theme_editor_background_image));
|
||||
|
||||
addButton(lang(lng_theme_editor_export), [this] { exportTheme(); });
|
||||
addButton(lang(lng_cancel), [this] { closeBox(); });
|
||||
|
||||
auto height = st::settingsSmallSkip + st::settingsBackgroundSize + st::settingsSmallSkip + _tileBackground->height();
|
||||
|
||||
setDimensions(st::boxWideWidth, height);
|
||||
|
||||
updateThumbnail();
|
||||
}
|
||||
|
||||
void ThemeExportBox::paintEvent(QPaintEvent *e) {
|
||||
BoxContent::paintEvent(e);
|
||||
|
||||
Painter p(this);
|
||||
|
||||
auto linkLeft = st::boxPadding.left() + st::settingsBackgroundSize + st::settingsSmallSkip;
|
||||
|
||||
p.setPen(st::boxTextFg);
|
||||
p.setFont(st::boxTextFont);
|
||||
p.drawTextLeft(linkLeft, st::settingsSmallSkip, width(), _imageText);
|
||||
|
||||
p.drawPixmapLeft(st::boxPadding.left(), st::settingsSmallSkip, width(), _thumbnail);
|
||||
}
|
||||
|
||||
void ThemeExportBox::resizeEvent(QResizeEvent *e) {
|
||||
auto linkLeft = st::boxPadding.left() + st::settingsBackgroundSize + st::settingsSmallSkip;
|
||||
_chooseFromFile->moveToLeft(linkLeft, st::settingsSmallSkip + st::boxTextFont->height + st::settingsSmallSkip);
|
||||
_tileBackground->moveToLeft(st::boxPadding.left(), st::settingsSmallSkip + st::settingsBackgroundSize + 2 * st::settingsSmallSkip);
|
||||
}
|
||||
|
||||
void ThemeExportBox::updateThumbnail() {
|
||||
int32 size = st::settingsBackgroundSize * cIntRetinaFactor();
|
||||
QImage back(size, size, QImage::Format_ARGB32_Premultiplied);
|
||||
back.setDevicePixelRatio(cRetinaFactor());
|
||||
{
|
||||
Painter p(&back);
|
||||
PainterHighQualityEnabler hq(p);
|
||||
|
||||
auto &pix = _background;
|
||||
int sx = (pix.width() > pix.height()) ? ((pix.width() - pix.height()) / 2) : 0;
|
||||
int sy = (pix.height() > pix.width()) ? ((pix.height() - pix.width()) / 2) : 0;
|
||||
int s = (pix.width() > pix.height()) ? pix.height() : pix.width();
|
||||
p.drawImage(QRect(0, 0, st::settingsBackgroundSize, st::settingsBackgroundSize), pix, QRect(sx, sy, s, s));
|
||||
}
|
||||
Images::prepareRound(back, ImageRoundRadius::Small);
|
||||
_thumbnail = App::pixmapFromImageInPlace(std_::move(back));
|
||||
_thumbnail.setDevicePixelRatio(cRetinaFactor());
|
||||
update();
|
||||
}
|
||||
|
||||
void ThemeExportBox::chooseBackgroundFromFile() {
|
||||
FileDialog::askOpenPath(lang(lng_theme_editor_choose_image), "Image files (*.jpeg *.jpg *.png)", base::lambda_guarded(this, [this](const FileDialog::OpenResult &result) {
|
||||
auto content = result.remoteContent;
|
||||
if (!result.paths.isEmpty()) {
|
||||
QFile f(result.paths.front());
|
||||
if (f.open(QIODevice::ReadOnly)) {
|
||||
content = f.readAll();
|
||||
f.close();
|
||||
}
|
||||
}
|
||||
if (!content.isEmpty()) {
|
||||
auto format = QByteArray();
|
||||
auto image = App::readImage(content, &format);
|
||||
if (!image.isNull() && (format == "jpeg" || format == "jpg" || format == "png")) {
|
||||
_background = image;
|
||||
_backgroundContent = content;
|
||||
_isPng = (format == "png");
|
||||
_imageText = (_isPng ? lng_theme_editor_read_from_png : lng_theme_editor_read_from_jpg)(lt_size, formatSizeText(_backgroundContent.size()));
|
||||
_tileBackground->setChecked(false);
|
||||
updateThumbnail();
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
void ThemeExportBox::exportTheme() {
|
||||
App::CallDelayed(st::defaultRippleAnimation.hideDuration, this, [this] {
|
||||
auto caption = lang(lng_theme_editor_choose_name);
|
||||
auto filter = "Themes (*.tdesktop-theme)";
|
||||
auto name = "awesome.tdesktop-theme";
|
||||
FileDialog::askWritePath(caption, filter, name, base::lambda_guarded(this, [this](const QString &path) {
|
||||
zlib::FileToWrite zip;
|
||||
|
||||
zip_fileinfo zfi = { { 0, 0, 0, 0, 0, 0 }, 0, 0, 0 };
|
||||
auto background = std::string(_tileBackground->checked() ? "tiled" : "background") + (_isPng ? ".png" : ".jpg");
|
||||
zip.openNewFile(background.c_str(), &zfi, nullptr, 0, nullptr, 0, nullptr, Z_DEFLATED, Z_DEFAULT_COMPRESSION);
|
||||
zip.writeInFile(_backgroundContent.constData(), _backgroundContent.size());
|
||||
zip.closeFile();
|
||||
auto scheme = "colors.tdesktop-theme";
|
||||
zip.openNewFile(scheme, &zfi, nullptr, 0, nullptr, 0, nullptr, Z_DEFLATED, Z_DEFAULT_COMPRESSION);
|
||||
zip.writeInFile(_paletteContent.constData(), _paletteContent.size());
|
||||
zip.closeFile();
|
||||
zip.close();
|
||||
|
||||
if (zip.error() != ZIP_OK) {
|
||||
LOG(("Theme Error: could not export zip-ed theme, status: %1").arg(zip.error()));
|
||||
Ui::show(Box<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
|
66
Telegram/SourceFiles/window/themes/window_theme_editor.h
Normal file
66
Telegram/SourceFiles/window/themes/window_theme_editor.h
Normal 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
|
778
Telegram/SourceFiles/window/themes/window_theme_editor_block.cpp
Normal file
778
Telegram/SourceFiles/window/themes/window_theme_editor_block.cpp
Normal 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 ©Of, QColor value);
|
||||
|
||||
QString name() const {
|
||||
return _name;
|
||||
}
|
||||
|
||||
void setCopyOf(const QString ©Of) {
|
||||
_copyOf = copyOf;
|
||||
fillSearchIndex();
|
||||
}
|
||||
QString copyOf() const {
|
||||
return _copyOf;
|
||||
}
|
||||
|
||||
void setValue(QColor value);
|
||||
const QColor &value() const {
|
||||
return _value;
|
||||
}
|
||||
|
||||
QString description() const {
|
||||
return _description.originalText();
|
||||
}
|
||||
const Text &descriptionText() const {
|
||||
return _description;
|
||||
}
|
||||
void setDescription(const QString &description) {
|
||||
_description.setText(st::defaultTextStyle, description);
|
||||
fillSearchIndex();
|
||||
}
|
||||
|
||||
const OrderedSet<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 ©Of, QColor value)
|
||||
: _name(name)
|
||||
, _copyOf(copyOf) {
|
||||
setValue(value);
|
||||
}
|
||||
|
||||
void EditorBlock::Row::setValue(QColor value) {
|
||||
_value = value;
|
||||
fillValueString();
|
||||
fillSearchIndex();
|
||||
}
|
||||
|
||||
void EditorBlock::Row::fillValueString() {
|
||||
auto addHex = [this](int code) {
|
||||
if (code >= 0 && code < 10) {
|
||||
_valueString.append('0' + code);
|
||||
} else if (code >= 10 && code < 16) {
|
||||
_valueString.append('a' + (code - 10));
|
||||
}
|
||||
};
|
||||
auto addCode = [this, addHex](int code) {
|
||||
addHex(code / 16);
|
||||
addHex(code % 16);
|
||||
};
|
||||
_valueString.resize(0);
|
||||
_valueString.reserve(9);
|
||||
_valueString.append('#');
|
||||
addCode(_value.red());
|
||||
addCode(_value.green());
|
||||
addCode(_value.blue());
|
||||
if (_value.alpha() != 255) {
|
||||
addCode(_value.alpha());
|
||||
}
|
||||
}
|
||||
|
||||
void EditorBlock::Row::fillSearchIndex() {
|
||||
_searchWords.clear();
|
||||
_searchStartChars.clear();
|
||||
auto toIndex = _name + ' ' + _copyOf + ' ' + textAccentFold(_description.originalText()) + ' ' + _valueString;
|
||||
auto words = toIndex.toLower().split(SearchSplitter, QString::SkipEmptyParts);
|
||||
for_const (auto &word, words) {
|
||||
_searchWords.insert(word);
|
||||
_searchStartChars.insert(word[0]);
|
||||
}
|
||||
}
|
||||
|
||||
EditorBlock::EditorBlock(QWidget *parent, Type type, Context *context) : TWidget(parent)
|
||||
, _type(type)
|
||||
, _context(context)
|
||||
, _transparent(style::transparentPlaceholderBrush()) {
|
||||
setMouseTracking(true);
|
||||
subscribe(_context->updated, [this] {
|
||||
if (_mouseSelection) {
|
||||
_lastGlobalPos = QCursor::pos();
|
||||
updateSelected(mapFromGlobal(_lastGlobalPos));
|
||||
}
|
||||
update();
|
||||
});
|
||||
if (_type == Type::Existing) {
|
||||
subscribe(_context->appended, [this](const Context::AppendData &added) {
|
||||
auto name = added.name;
|
||||
auto value = added.value;
|
||||
feed(name, value);
|
||||
feedDescription(name, added.description);
|
||||
|
||||
auto row = findRow(name);
|
||||
t_assert(row != nullptr);
|
||||
auto possibleCopyOf = added.possibleCopyOf;
|
||||
auto copyOf = checkCopyOf(findRowIndex(row), possibleCopyOf) ? possibleCopyOf : QString();
|
||||
removeFromSearch(*row);
|
||||
row->setCopyOf(copyOf);
|
||||
addToSearch(*row);
|
||||
|
||||
_context->changed.notify({ QStringList(name), value }, true);
|
||||
_context->resized.notify();
|
||||
_context->pending.notify({ name, copyOf, value }, true);
|
||||
});
|
||||
} else {
|
||||
subscribe(_context->changed, [this](const Context::ChangeData &data) {
|
||||
checkCopiesChanged(0, data.names, data.value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void EditorBlock::feed(const QString &name, QColor value, const QString ©OfExisting) {
|
||||
if (findRow(name)) {
|
||||
// Remove the existing row and mark all its copies as unique keys.
|
||||
LOG(("Theme Warning: Color value '%1' appears more than once in the color scheme.").arg(name));
|
||||
removeRow(name);
|
||||
}
|
||||
addRow(name, copyOfExisting, value);
|
||||
}
|
||||
|
||||
bool EditorBlock::feedCopy(const QString &name, const QString ©Of) {
|
||||
if (auto row = findRow(copyOf)) {
|
||||
if (findRow(name)) {
|
||||
// Remove the existing row and mark all its copies as unique keys.
|
||||
LOG(("Theme Warning: Color value '%1' appears more than once in the color scheme.").arg(name));
|
||||
removeRow(name);
|
||||
|
||||
// row was invalidated by removeRow() call.
|
||||
row = findRow(copyOf);
|
||||
}
|
||||
addRow(name, copyOf, row->value());
|
||||
} else {
|
||||
LOG(("Theme Warning: Skipping value '%1: %2' (expected a color value in #rrggbb or #rrggbbaa or a previously defined key in the color scheme)").arg(name).arg(copyOf));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void EditorBlock::removeRow(const QString &name, bool removeCopyReferences) {
|
||||
auto it = _indices.find(name);
|
||||
t_assert(it != _indices.cend());
|
||||
|
||||
auto index = it.value();
|
||||
for (auto i = index + 1, count = _data.size(); i != count; ++i) {
|
||||
auto &row = _data[i];
|
||||
removeFromSearch(row);
|
||||
_indices[row.name()] = i - 1;
|
||||
if (removeCopyReferences && row.copyOf() == name) {
|
||||
row.setCopyOf(QString());
|
||||
}
|
||||
}
|
||||
_data.erase(_data.begin() + index);
|
||||
_indices.erase(it);
|
||||
for (auto i = index, count = _data.size(); i != count; ++i) {
|
||||
addToSearch(_data[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorBlock::addToSearch(const Row &row) {
|
||||
auto query = _searchQuery;
|
||||
if (!query.isEmpty()) resetSearch();
|
||||
|
||||
auto index = findRowIndex(&row);
|
||||
for_const (auto ch, row.searchStartChars()) {
|
||||
_searchIndex[ch].insert(index);
|
||||
}
|
||||
|
||||
if (!query.isEmpty()) searchByQuery(query);
|
||||
}
|
||||
|
||||
void EditorBlock::removeFromSearch(const Row &row) {
|
||||
auto query = _searchQuery;
|
||||
if (!query.isEmpty()) resetSearch();
|
||||
|
||||
auto index = findRowIndex(&row);
|
||||
for_const (auto ch, row.searchStartChars()) {
|
||||
auto it = _searchIndex.find(ch);
|
||||
if (it != _searchIndex.cend()) {
|
||||
it->remove(index);
|
||||
if (it->isEmpty()) {
|
||||
_searchIndex.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!query.isEmpty()) searchByQuery(query);
|
||||
}
|
||||
|
||||
void EditorBlock::filterRows(const QString &query) {
|
||||
searchByQuery(query);
|
||||
}
|
||||
|
||||
void EditorBlock::chooseRow() {
|
||||
if (_selected < 0) {
|
||||
return;
|
||||
}
|
||||
activateRow(rowAtIndex(_selected));
|
||||
}
|
||||
|
||||
void EditorBlock::activateRow(const Row &row) {
|
||||
if (_context->box) {
|
||||
if (_type == Type::Existing) {
|
||||
_context->possibleCopyOf = row.name();
|
||||
_context->box->showColor(row.value());
|
||||
}
|
||||
} else {
|
||||
_editing = findRowIndex(&row);
|
||||
if (auto box = Ui::show(Box<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 ©Of, QColor value) {
|
||||
_data.push_back({ name, copyOf, value });
|
||||
_indices.insert(name, _data.size() - 1);
|
||||
addToSearch(_data.back());
|
||||
}
|
||||
|
||||
EditorBlock::Row &EditorBlock::rowAtIndex(int index) {
|
||||
if (isSearch()) {
|
||||
return _data[_searchResults[index]];
|
||||
}
|
||||
return _data[index];
|
||||
}
|
||||
|
||||
int EditorBlock::findRowIndex(const QString &name) const {
|
||||
return _indices.value(name, -1);;
|
||||
}
|
||||
|
||||
EditorBlock::Row *EditorBlock::findRow(const QString &name) {
|
||||
auto index = findRowIndex(name);
|
||||
return (index >= 0) ? &_data[index] : nullptr;
|
||||
}
|
||||
|
||||
int EditorBlock::findRowIndex(const Row *row) {
|
||||
return row ? (row - &_data[0]) : -1;
|
||||
}
|
||||
|
||||
} // namespace Theme
|
||||
} // namespace Window
|
171
Telegram/SourceFiles/window/themes/window_theme_editor_block.h
Normal file
171
Telegram/SourceFiles/window/themes/window_theme_editor_block.h
Normal 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 ©OfExisting = QString());
|
||||
bool feedCopy(const QString &name, const QString ©Of);
|
||||
const QColor *find(const QString &name);
|
||||
|
||||
bool feedDescription(const QString &name, const QString &description);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
int resizeGetHeight(int newWidth) override;
|
||||
|
||||
void mousePressEvent(QMouseEvent *e) override;
|
||||
void mouseReleaseEvent(QMouseEvent *e) override;
|
||||
void mouseMoveEvent(QMouseEvent *e) override;
|
||||
void leaveEvent(QEvent *e) override;
|
||||
|
||||
private:
|
||||
class Row;
|
||||
|
||||
void addRow(const QString &name, const QString ©Of, QColor value);
|
||||
void removeRow(const QString &name, bool removeCopyReferences = true);
|
||||
|
||||
void addToSearch(const Row &row);
|
||||
void removeFromSearch(const Row &row);
|
||||
|
||||
template <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
|
@ -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"
|
@ -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 {
|
@ -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 {
|
@ -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.;
|
||||
|
@ -104,7 +104,7 @@ call :repl "Replace=("FileVersion",) (\s*)"\d+.\d+.\d+.\d+"/
|
||||
call :repl "Replace=("ProductVersion",) (\s*)"\d+.\d+.\d+.\d+"/$1$2 "%VersionMajor%.%VersionMinor%.%VersionPatch%.%VersionBeta%"" "Filename=%ResourcePath%" || goto :error
|
||||
|
||||
echo Patching appxmanifest.xml...
|
||||
set "ResourcePath=%FullScriptPath%..\Resources\uwp\appxmanifest.xml"
|
||||
set "ResourcePath=%FullScriptPath%..\Resources\uwp\AppX\AppxManifest.xml"
|
||||
call :repl "Replace= (Version=)"\d+.\d+.\d+.\d+"/ $1"%VersionMajor%.%VersionMinor%.%VersionPatch%.%VersionBeta%"" "Filename=%ResourcePath%" || goto :error
|
||||
|
||||
exit /b
|
||||
|
@ -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"
|
||||
|
@ -3,4 +3,4 @@ AppVersionStrMajor 1.0
|
||||
AppVersionStrSmall 1.0.6
|
||||
AppVersionStr 1.0.6
|
||||
AlphaChannel 0
|
||||
BetaVersion 0
|
||||
BetaVersion 1000006001
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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',
|
||||
|
Loading…
Reference in New Issue
Block a user