mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-06 15:13:57 +02:00
Closed beta 1000006001: Built in theme and color palette editor.
This commit is contained in:
parent
60f45ab9b3
commit
b842761ea3
95 changed files with 3870 additions and 477 deletions
|
@ -275,3 +275,5 @@ notifyFadeRight: icon {{ "fade_horizontal", notificationBg }};
|
||||||
|
|
||||||
stickerIconLeft: icon {{ "fade_horizontal-flip_horizontal", emojiPanCategories }};
|
stickerIconLeft: icon {{ "fade_horizontal-flip_horizontal", emojiPanCategories }};
|
||||||
stickerIconRight: icon {{ "fade_horizontal", emojiPanCategories }};
|
stickerIconRight: icon {{ "fade_horizontal", emojiPanCategories }};
|
||||||
|
|
||||||
|
transparentPlaceholderSize: 4px;
|
||||||
|
|
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_send" = "Send";
|
||||||
"lng_confirm_phone_enter_code" = "Please enter the code.";
|
"lng_confirm_phone_enter_code" = "Please enter the code.";
|
||||||
|
|
||||||
|
"lng_theme_editor_no_keys" = "No keys in the palette yet";
|
||||||
|
"lng_theme_editor_cant_change_theme" = "You can not apply themes while you edit a color palette. Please close the palette editor first.";
|
||||||
|
"lng_theme_editor_new_keys" = "Not in the palette yet";
|
||||||
|
"lng_theme_editor_background_image" = "Background image";
|
||||||
|
"lng_theme_editor_saved_to_jpg" = "Saved to JPEG, {size}";
|
||||||
|
"lng_theme_editor_read_from_jpg" = "JPEG image, {size}";
|
||||||
|
"lng_theme_editor_read_from_png" = "PNG image, {size}";
|
||||||
|
"lng_theme_editor_export" = "Export";
|
||||||
|
"lng_theme_editor_choose_image" = "Choose background image";
|
||||||
|
"lng_theme_editor_save_palette" = "Save palette file";
|
||||||
|
"lng_theme_editor_choose_name" = "Choose theme filename";
|
||||||
|
"lng_theme_editor_error" = "Editor encountered an error :( See 'log.txt' for details.";
|
||||||
|
"lng_theme_editor_done" = "Theme export was successfull!";
|
||||||
|
"lng_theme_editor_title" = "Edit color palette";
|
||||||
|
"lng_theme_editor_export_button" = "Export theme";
|
||||||
|
|
||||||
// Not used
|
// Not used
|
||||||
|
|
||||||
"lng_topbar_info" = "Info";
|
"lng_topbar_info" = "Info";
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<Identity Name="TelegramDesktop"
|
<Identity Name="TelegramDesktop"
|
||||||
ProcessorArchitecture="x64"
|
ProcessorArchitecture="x64"
|
||||||
Publisher="CN=Telegram Messenger LLP, O=Telegram Messenger LLP, L=London, C=GB"
|
Publisher="CN=Telegram Messenger LLP, O=Telegram Messenger LLP, L=London, C=GB"
|
||||||
Version="1.0.6.0" />
|
Version="1.0.6.1" />
|
||||||
<Properties>
|
<Properties>
|
||||||
<DisplayName>Telegram Desktop</DisplayName>
|
<DisplayName>Telegram Desktop</DisplayName>
|
||||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||||
|
@ -31,7 +31,7 @@
|
||||||
Square44x44Logo="Assets\logo44\logo44.png"
|
Square44x44Logo="Assets\logo44\logo44.png"
|
||||||
Description="Telegram Desktop official messenger" />
|
Description="Telegram Desktop official messenger" />
|
||||||
<Extensions>
|
<Extensions>
|
||||||
<uap:Extension Category="windows.protocol">
|
<uap:Extension Category="windows.protocol" Executable="Telegram.exe">
|
||||||
<uap:Protocol Name="tg" />
|
<uap:Protocol Name="tg" />
|
||||||
</uap:Extension>
|
</uap:Extension>
|
||||||
</Extensions>
|
</Extensions>
|
||||||
|
|
|
@ -34,8 +34,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||||
//
|
//
|
||||||
|
|
||||||
VS_VERSION_INFO VERSIONINFO
|
VS_VERSION_INFO VERSIONINFO
|
||||||
FILEVERSION 1,0,6,0
|
FILEVERSION 1,0,6,1
|
||||||
PRODUCTVERSION 1,0,6,0
|
PRODUCTVERSION 1,0,6,1
|
||||||
FILEFLAGSMASK 0x3fL
|
FILEFLAGSMASK 0x3fL
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
FILEFLAGS 0x1L
|
FILEFLAGS 0x1L
|
||||||
|
@ -51,10 +51,10 @@ BEGIN
|
||||||
BLOCK "040904b0"
|
BLOCK "040904b0"
|
||||||
BEGIN
|
BEGIN
|
||||||
VALUE "CompanyName", "Telegram Messenger LLP"
|
VALUE "CompanyName", "Telegram Messenger LLP"
|
||||||
VALUE "FileVersion", "1.0.6.0"
|
VALUE "FileVersion", "1.0.6.1"
|
||||||
VALUE "LegalCopyright", "Copyright (C) 2014-2017"
|
VALUE "LegalCopyright", "Copyright (C) 2014-2017"
|
||||||
VALUE "ProductName", "Telegram Desktop"
|
VALUE "ProductName", "Telegram Desktop"
|
||||||
VALUE "ProductVersion", "1.0.6.0"
|
VALUE "ProductVersion", "1.0.6.1"
|
||||||
END
|
END
|
||||||
END
|
END
|
||||||
BLOCK "VarFileInfo"
|
BLOCK "VarFileInfo"
|
||||||
|
|
|
@ -25,8 +25,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||||
//
|
//
|
||||||
|
|
||||||
VS_VERSION_INFO VERSIONINFO
|
VS_VERSION_INFO VERSIONINFO
|
||||||
FILEVERSION 1,0,6,0
|
FILEVERSION 1,0,6,1
|
||||||
PRODUCTVERSION 1,0,6,0
|
PRODUCTVERSION 1,0,6,1
|
||||||
FILEFLAGSMASK 0x3fL
|
FILEFLAGSMASK 0x3fL
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
FILEFLAGS 0x1L
|
FILEFLAGS 0x1L
|
||||||
|
@ -43,10 +43,10 @@ BEGIN
|
||||||
BEGIN
|
BEGIN
|
||||||
VALUE "CompanyName", "Telegram Messenger LLP"
|
VALUE "CompanyName", "Telegram Messenger LLP"
|
||||||
VALUE "FileDescription", "Telegram Updater"
|
VALUE "FileDescription", "Telegram Updater"
|
||||||
VALUE "FileVersion", "1.0.6.0"
|
VALUE "FileVersion", "1.0.6.1"
|
||||||
VALUE "LegalCopyright", "Copyright (C) 2014-2017"
|
VALUE "LegalCopyright", "Copyright (C) 2014-2017"
|
||||||
VALUE "ProductName", "Telegram Desktop"
|
VALUE "ProductName", "Telegram Desktop"
|
||||||
VALUE "ProductVersion", "1.0.6.0"
|
VALUE "ProductVersion", "1.0.6.1"
|
||||||
END
|
END
|
||||||
END
|
END
|
||||||
BLOCK "VarFileInfo"
|
BLOCK "VarFileInfo"
|
||||||
|
|
|
@ -30,7 +30,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
#include "historywidget.h"
|
#include "historywidget.h"
|
||||||
#include "localstorage.h"
|
#include "localstorage.h"
|
||||||
#include "boxes/confirmbox.h"
|
#include "boxes/confirmbox.h"
|
||||||
#include "window/window_theme.h"
|
#include "window/themes/window_theme.h"
|
||||||
|
|
||||||
ApiWrap::ApiWrap(QObject *parent) : QObject(parent)
|
ApiWrap::ApiWrap(QObject *parent) : QObject(parent)
|
||||||
, _messageDataResolveDelayed(new SingleDelayedCall(this, "resolveMessageDatas")) {
|
, _messageDataResolveDelayed(new SingleDelayedCall(this, "resolveMessageDatas")) {
|
||||||
|
|
|
@ -44,7 +44,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
#include "numbers.h"
|
#include "numbers.h"
|
||||||
#include "observer_peer.h"
|
#include "observer_peer.h"
|
||||||
#include "window/window_theme.h"
|
#include "window/themes/window_theme.h"
|
||||||
#include "window/notifications_manager.h"
|
#include "window/notifications_manager.h"
|
||||||
#include "platform/platform_notifications_manager.h"
|
#include "platform/platform_notifications_manager.h"
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
#include "autoupdater.h"
|
#include "autoupdater.h"
|
||||||
#include "core/observer.h"
|
#include "core/observer.h"
|
||||||
#include "observer_peer.h"
|
#include "observer_peer.h"
|
||||||
#include "window/window_theme.h"
|
#include "window/themes/window_theme.h"
|
||||||
#include "media/player/media_player_instance.h"
|
#include "media/player/media_player_instance.h"
|
||||||
#include "window/notifications_manager.h"
|
#include "window/notifications_manager.h"
|
||||||
#include "history/history_location_manager.h"
|
#include "history/history_location_manager.h"
|
||||||
|
|
|
@ -252,16 +252,14 @@ void AbstractBox::paintEvent(QPaintEvent *e) {
|
||||||
void AbstractBox::paintTitle(Painter &p, const QString &title, const QString &additional) {
|
void AbstractBox::paintTitle(Painter &p, const QString &title, const QString &additional) {
|
||||||
p.setFont(st::boxTitleFont);
|
p.setFont(st::boxTitleFont);
|
||||||
p.setPen(st::boxTitleFg);
|
p.setPen(st::boxTitleFg);
|
||||||
if (_layerType) {
|
auto titleWidth = st::boxTitleFont->width(title);
|
||||||
auto titleWidth = st::boxTitleFont->width(title);
|
auto titleLeft = _layerType ? st::boxLayerTitlePosition.x() : st::boxTitlePosition.x();
|
||||||
p.drawTextLeft(st::boxLayerTitlePosition.x(), st::boxLayerTitlePosition.y(), width(), title, titleWidth);
|
auto titleTop = _layerType ? st::boxLayerTitlePosition.y() : st::boxTitlePosition.y();
|
||||||
if (!additional.isEmpty()) {
|
p.drawTextLeft(titleLeft, titleTop, width(), title, titleWidth);
|
||||||
p.setFont(st::boxLayerTitleAdditionalFont);
|
if (!additional.isEmpty()) {
|
||||||
p.setPen(st::boxTitleAdditionalFg);
|
p.setFont(st::boxLayerTitleAdditionalFont);
|
||||||
p.drawTextLeft(st::boxLayerTitlePosition.x() + titleWidth + st::boxLayerTitleAdditionalSkip, st::boxLayerTitlePosition.y() + st::boxTitleFont->ascent - st::boxLayerTitleAdditionalFont->ascent, width(), additional);
|
p.setPen(st::boxTitleAdditionalFg);
|
||||||
}
|
p.drawTextLeft(titleLeft + titleWidth + st::boxLayerTitleAdditionalSkip, titleTop + st::boxTitleFont->ascent - st::boxLayerTitleAdditionalFont->ascent, width(), additional);
|
||||||
} else {
|
|
||||||
p.drawTextLeft(st::boxTitlePosition.x(), st::boxTitlePosition.y(), width(), title);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -246,12 +246,6 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename BoxType, typename ...Args>
|
|
||||||
inline object_ptr<BoxType> Box(Args&&... args) {
|
|
||||||
auto parent = static_cast<QWidget*>(nullptr);
|
|
||||||
return object_ptr<BoxType>(parent, std_::forward<Args>(args)...);
|
|
||||||
}
|
|
||||||
|
|
||||||
enum CreatingGroupType {
|
enum CreatingGroupType {
|
||||||
CreatingGroupNone,
|
CreatingGroupNone,
|
||||||
CreatingGroupGroup,
|
CreatingGroupGroup,
|
||||||
|
|
|
@ -542,10 +542,7 @@ void SetupChannelBox::mouseMoveEvent(QMouseEvent *e) {
|
||||||
void SetupChannelBox::mousePressEvent(QMouseEvent *e) {
|
void SetupChannelBox::mousePressEvent(QMouseEvent *e) {
|
||||||
if (_linkOver) {
|
if (_linkOver) {
|
||||||
Application::clipboard()->setText(_channel->inviteLink());
|
Application::clipboard()->setText(_channel->inviteLink());
|
||||||
|
Ui::Toast::Show(lang(lng_create_channel_link_copied));
|
||||||
Ui::Toast::Config toast;
|
|
||||||
toast.text = lang(lng_create_channel_link_copied);
|
|
||||||
Ui::Toast::Show(App::wnd(), toast);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
#include "lang.h"
|
#include "lang.h"
|
||||||
#include "mainwidget.h"
|
#include "mainwidget.h"
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "window/window_theme.h"
|
#include "window/themes/window_theme.h"
|
||||||
#include "styles/style_overview.h"
|
#include "styles/style_overview.h"
|
||||||
#include "styles/style_boxes.h"
|
#include "styles/style_boxes.h"
|
||||||
#include "ui/effects/round_checkbox.h"
|
#include "ui/effects/round_checkbox.h"
|
||||||
|
|
|
@ -530,3 +530,23 @@ usernameTextStyle: TextStyle(passcodeTextStyle) {
|
||||||
usernameDefaultFg: windowSubTextFg;
|
usernameDefaultFg: windowSubTextFg;
|
||||||
|
|
||||||
downloadPathSkip: 10px;
|
downloadPathSkip: 10px;
|
||||||
|
|
||||||
|
colorEditWidth: 390px;
|
||||||
|
colorEditSkip: 10px;
|
||||||
|
colorPickerSize: 256px;
|
||||||
|
colorPickerMarkRadius: 6px;
|
||||||
|
colorPickerMarkLine: 1px;
|
||||||
|
colorSliderSkip: 8px;
|
||||||
|
colorSliderArrowLeft: icon {{ "color_slider_arrow", sliderBgActive }};
|
||||||
|
colorSliderArrowRight: icon {{ "color_slider_arrow-flip_horizontal", sliderBgActive }};
|
||||||
|
colorSliderArrowTop: icon {{ "color_slider_arrow_vertical", sliderBgActive }};
|
||||||
|
colorSliderArrowBottom: icon {{ "color_slider_arrow_vertical-flip_vertical", sliderBgActive }};
|
||||||
|
colorSliderWidth: 19px;
|
||||||
|
colorSampleSize: size(60px, 34px);
|
||||||
|
colorFieldSkip: 13px;
|
||||||
|
colorValueInput: InputField(defaultInputField) {
|
||||||
|
textMargins: margins(16px, 3px, 0px, 2px);
|
||||||
|
heightMin: 27px;
|
||||||
|
}
|
||||||
|
colorResultInput: InputField(colorValueInput) {
|
||||||
|
}
|
||||||
|
|
|
@ -241,10 +241,7 @@ void MaxInviteBox::mousePressEvent(QMouseEvent *e) {
|
||||||
mouseMoveEvent(e);
|
mouseMoveEvent(e);
|
||||||
if (_linkOver) {
|
if (_linkOver) {
|
||||||
Application::clipboard()->setText(_link);
|
Application::clipboard()->setText(_link);
|
||||||
|
Ui::Toast::Show(lang(lng_create_channel_link_copied));
|
||||||
Ui::Toast::Config toast;
|
|
||||||
toast.text = lang(lng_create_channel_link_copied);
|
|
||||||
Ui::Toast::Show(App::wnd(), toast);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
#include "ui/effects/ripple_animation.h"
|
#include "ui/effects/ripple_animation.h"
|
||||||
#include "boxes/photocropbox.h"
|
#include "boxes/photocropbox.h"
|
||||||
#include "boxes/confirmbox.h"
|
#include "boxes/confirmbox.h"
|
||||||
#include "window/window_theme.h"
|
#include "window/themes/window_theme.h"
|
||||||
#include "observer_peer.h"
|
#include "observer_peer.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
|
|
||||||
|
|
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 "history/history_media_types.h"
|
||||||
#include "ui/widgets/buttons.h"
|
#include "ui/widgets/buttons.h"
|
||||||
#include "ui/widgets/scroll_area.h"
|
#include "ui/widgets/scroll_area.h"
|
||||||
#include "window/window_theme.h"
|
#include "window/themes/window_theme.h"
|
||||||
#include "boxes/contactsbox.h"
|
#include "boxes/contactsbox.h"
|
||||||
|
|
||||||
ShareBox::ShareBox(QWidget*, CopyCallback &©Callback, SubmitCallback &&submitCallback, FilterCallback &&filterCallback)
|
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));
|
QApplication::clipboard()->setText(CreateInternalLinkHttps(bot->username + qsl("?game=") + shortName));
|
||||||
|
|
||||||
Ui::Toast::Config toast;
|
Ui::Toast::Show(lang(lng_share_game_link_copied));
|
||||||
toast.text = lang(lng_share_game_link_copied);
|
|
||||||
Ui::Toast::Show(App::wnd(), toast);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -892,10 +890,7 @@ void shareGameScoreFromItem(HistoryItem *item) {
|
||||||
}
|
}
|
||||||
data->requests.remove(requestId);
|
data->requests.remove(requestId);
|
||||||
if (data->requests.empty()) {
|
if (data->requests.empty()) {
|
||||||
Ui::Toast::Config toast;
|
Ui::Toast::Show(lang(lng_share_done));
|
||||||
toast.text = lang(lng_share_done);
|
|
||||||
Ui::Toast::Show(App::wnd(), toast);
|
|
||||||
|
|
||||||
Ui::hideLayer();
|
Ui::hideLayer();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -161,10 +161,7 @@ void UsernameBox::onChanged() {
|
||||||
|
|
||||||
void UsernameBox::onLinkClick() {
|
void UsernameBox::onLinkClick() {
|
||||||
Application::clipboard()->setText(CreateInternalLinkHttps(getName()));
|
Application::clipboard()->setText(CreateInternalLinkHttps(getName()));
|
||||||
|
Ui::Toast::Show(lang(lng_username_copied));
|
||||||
Ui::Toast::Config toast;
|
|
||||||
toast.text = lang(lng_username_copied);
|
|
||||||
Ui::Toast::Show(App::wnd(), toast);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void UsernameBox::onUpdateDone(const MTPUser &user) {
|
void UsernameBox::onUpdateDone(const MTPUser &user) {
|
||||||
|
|
|
@ -52,8 +52,6 @@ Token invalidToken() {
|
||||||
return { Type::Invalid, QString(), ConstUtf8String(nullptr, 0), false };
|
return { Type::Invalid, QString(), ConstUtf8String(nullptr, 0), false };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
BasicTokenizedFile::BasicTokenizedFile(const QString &filepath) : reader_(filepath) {
|
BasicTokenizedFile::BasicTokenizedFile(const QString &filepath) : reader_(filepath) {
|
||||||
|
@ -152,6 +150,22 @@ Type BasicTokenizedFile::uniteLastTokens(Type type) {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString BasicTokenizedFile::getCurrentLineComment() {
|
||||||
|
if (lineNumber_ > singleLineComments_.size()) {
|
||||||
|
reader_.logError(kErrorInternal, lineNumber_) << "internal tokenizer error (line number larger than comments list size).";
|
||||||
|
failed_ = true;
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
auto commentBytes = singleLineComments_[lineNumber_ - 1].mid(2); // Skip "//"
|
||||||
|
CheckedUtf8String comment(commentBytes);
|
||||||
|
if (!comment.isValid()) {
|
||||||
|
reader_.logError(kErrorIncorrectUtf8String, lineNumber_) << "incorrect UTF-8 string in the comment.";
|
||||||
|
failed_ = true;
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
return comment.toString().trimmed();
|
||||||
|
}
|
||||||
|
|
||||||
Type BasicTokenizedFile::readNameOrNumber() {
|
Type BasicTokenizedFile::readNameOrNumber() {
|
||||||
while (!reader_.atEnd()) {
|
while (!reader_.atEnd()) {
|
||||||
if (!isDigitChar(reader_.currentChar())) {
|
if (!isDigitChar(reader_.currentChar())) {
|
||||||
|
|
|
@ -77,7 +77,11 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
bool read() {
|
bool read() {
|
||||||
return reader_.read();
|
if (reader_.read()) {
|
||||||
|
singleLineComments_ = reader_.singleLineComments();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
bool atEnd() const {
|
bool atEnd() const {
|
||||||
return reader_.atEnd();
|
return reader_.atEnd();
|
||||||
|
@ -90,6 +94,8 @@ public:
|
||||||
return failed_;
|
return failed_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString getCurrentLineComment();
|
||||||
|
|
||||||
// Log error to std::cerr with 'code' at the current position in file.
|
// Log error to std::cerr with 'code' at the current position in file.
|
||||||
LogStream logError(int code) const;
|
LogStream logError(int code) const;
|
||||||
LogStream logErrorUnexpectedToken() const;
|
LogStream logErrorUnexpectedToken() const;
|
||||||
|
@ -124,6 +130,7 @@ private:
|
||||||
int currentToken_ = 0;
|
int currentToken_ = 0;
|
||||||
int lineNumber_ = 1;
|
int lineNumber_ = 1;
|
||||||
bool failed_ = false;
|
bool failed_ = false;
|
||||||
|
QVector<QByteArray> singleLineComments_;
|
||||||
|
|
||||||
// Where the last (currently read) token has started.
|
// Where the last (currently read) token has started.
|
||||||
const char *tokenStart_ = nullptr;
|
const char *tokenStart_ = nullptr;
|
||||||
|
|
|
@ -86,10 +86,17 @@ bool CleanFile::read() {
|
||||||
offset = ch;
|
offset = ch;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
auto feedComment = [this, &offset, end](const char *ch) {
|
|
||||||
|
auto lineNumber = 0;
|
||||||
|
auto feedComment = [this, &offset, end, &lineNumber](const char *ch, bool save = false) {
|
||||||
if (ch > offset) {
|
if (ch > offset) {
|
||||||
// comments_.push_back({ content_.size(), QByteArray(offset, ch - offset) });
|
if (save) {
|
||||||
if (result_.isEmpty()) result_.reserve(end - offset - 2);
|
singleLineComments_.resize(lineNumber + 1);
|
||||||
|
singleLineComments_[lineNumber] = QByteArray(offset, ch - offset);
|
||||||
|
}
|
||||||
|
if (result_.isEmpty()) {
|
||||||
|
result_.reserve(end - offset - 2);
|
||||||
|
}
|
||||||
result_.append(' ');
|
result_.append(' ');
|
||||||
offset = ch;
|
offset = ch;
|
||||||
}
|
}
|
||||||
|
@ -105,6 +112,9 @@ bool CleanFile::read() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (insideString) {
|
if (insideString) {
|
||||||
|
if (currentChar == '\n') {
|
||||||
|
++lineNumber;
|
||||||
|
}
|
||||||
++ch;
|
++ch;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -114,12 +124,14 @@ bool CleanFile::read() {
|
||||||
insideComment = InsideComment::SingleLine;
|
insideComment = InsideComment::SingleLine;
|
||||||
ch += 2;
|
ch += 2;
|
||||||
} else if (insideComment == InsideComment::SingleLine && currentChar == '\r' && nextChar == '\n') {
|
} else if (insideComment == InsideComment::SingleLine && currentChar == '\r' && nextChar == '\n') {
|
||||||
feedComment(ch);
|
feedComment(ch, true);
|
||||||
ch += 2;
|
ch += 2;
|
||||||
|
++lineNumber;
|
||||||
insideComment = InsideComment::None;
|
insideComment = InsideComment::None;
|
||||||
} else if (insideComment == InsideComment::SingleLine && currentChar == '\n') {
|
} else if (insideComment == InsideComment::SingleLine && currentChar == '\n') {
|
||||||
feedComment(ch);
|
feedComment(ch, true);
|
||||||
++ch;
|
++ch;
|
||||||
|
++lineNumber;
|
||||||
insideComment = InsideComment::None;
|
insideComment = InsideComment::None;
|
||||||
} else if (insideComment == InsideComment::None && currentChar == '/' && nextChar == '*') {
|
} else if (insideComment == InsideComment::None && currentChar == '/' && nextChar == '*') {
|
||||||
feedContent(ch);
|
feedContent(ch);
|
||||||
|
@ -132,15 +144,21 @@ bool CleanFile::read() {
|
||||||
} else if (insideComment == InsideComment::MultiLine && currentChar == '\r' && nextChar == '\n') {
|
} else if (insideComment == InsideComment::MultiLine && currentChar == '\r' && nextChar == '\n') {
|
||||||
feedComment(ch);
|
feedComment(ch);
|
||||||
ch += 2;
|
ch += 2;
|
||||||
|
++lineNumber;
|
||||||
feedContent(ch);
|
feedContent(ch);
|
||||||
} else if (insideComment == InsideComment::MultiLine && currentChar == '\n') {
|
} else if (insideComment == InsideComment::MultiLine && currentChar == '\n') {
|
||||||
feedComment(ch);
|
feedComment(ch);
|
||||||
++ch;
|
++ch;
|
||||||
|
++lineNumber;
|
||||||
feedContent(ch);
|
feedContent(ch);
|
||||||
} else {
|
} else {
|
||||||
|
if (currentChar == '\n') {
|
||||||
|
++lineNumber;
|
||||||
|
}
|
||||||
++ch;
|
++ch;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
singleLineComments_.resize(lineNumber + 1);
|
||||||
|
|
||||||
if (insideComment == InsideComment::MultiLine) {
|
if (insideComment == InsideComment::MultiLine) {
|
||||||
common::logError(kErrorUnexpectedEndOfFile, filepath_);
|
common::logError(kErrorUnexpectedEndOfFile, filepath_);
|
||||||
|
@ -156,6 +174,10 @@ bool CleanFile::read() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QVector<QByteArray> CleanFile::singleLineComments() const {
|
||||||
|
return singleLineComments_;
|
||||||
|
}
|
||||||
|
|
||||||
LogStream CleanFile::logError(int code, int line) const {
|
LogStream CleanFile::logError(int code, int line) const {
|
||||||
return common::logError(code, filepath_, line);
|
return common::logError(code, filepath_, line);
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ public:
|
||||||
CleanFile &operator=(const CleanFile &other) = delete;
|
CleanFile &operator=(const CleanFile &other) = delete;
|
||||||
|
|
||||||
bool read();
|
bool read();
|
||||||
|
QVector<QByteArray> singleLineComments() const;
|
||||||
|
|
||||||
const char *data() const {
|
const char *data() const {
|
||||||
return result_.constData();
|
return result_.constData();
|
||||||
|
@ -55,11 +56,8 @@ private:
|
||||||
QString filepath_;
|
QString filepath_;
|
||||||
QByteArray content_, result_;
|
QByteArray content_, result_;
|
||||||
bool read_;
|
bool read_;
|
||||||
//struct Comment {
|
|
||||||
// int offset;
|
QVector<QByteArray> singleLineComments_;
|
||||||
// QByteArray content;
|
|
||||||
//};
|
|
||||||
//QVector<Comment> comments_;
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -63,6 +63,10 @@ public:
|
||||||
return (end_ - pos_);
|
return (end_ - pos_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QVector<QByteArray> singleLineComments() const {
|
||||||
|
return file_.singleLineComments();
|
||||||
|
}
|
||||||
|
|
||||||
// Log error to std::cerr with 'code' at line number 'line' in data().
|
// Log error to std::cerr with 'code' at line number 'line' in data().
|
||||||
LogStream logError(int code, int line) const {
|
LogStream logError(int code, int line) const {
|
||||||
return std::forward<LogStream>(file_.logError(code, line));
|
return std::forward<LogStream>(file_.logError(code, line));
|
||||||
|
|
|
@ -106,12 +106,13 @@ char hexFirstChar(char ch) {
|
||||||
return hexChar((*reinterpret_cast<uchar*>(&ch)) >> 4);
|
return hexChar((*reinterpret_cast<uchar*>(&ch)) >> 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString stringToEncodedString(const std::string &str) {
|
QString stringToEncodedString(const QString &str) {
|
||||||
QString result, lineBreak = "\\\n";
|
QString result, lineBreak = "\\\n";
|
||||||
result.reserve(str.size() * 8);
|
result.reserve(str.size() * 8);
|
||||||
bool writingHexEscapedCharacters = false, startOnNewLine = false;
|
bool writingHexEscapedCharacters = false, startOnNewLine = false;
|
||||||
int lastCutSize = 0;
|
int lastCutSize = 0;
|
||||||
for (uchar ch : str) {
|
auto utf = str.toUtf8();
|
||||||
|
for (auto ch : utf) {
|
||||||
if (result.size() - lastCutSize > 80) {
|
if (result.size() - lastCutSize > 80) {
|
||||||
startOnNewLine = true;
|
startOnNewLine = true;
|
||||||
result.append(lineBreak);
|
result.append(lineBreak);
|
||||||
|
@ -140,6 +141,10 @@ QString stringToEncodedString(const std::string &str) {
|
||||||
return '"' + (startOnNewLine ? lineBreak : QString()) + result + '"';
|
return '"' + (startOnNewLine ? lineBreak : QString()) + result + '"';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString stringToEncodedString(const std::string &str) {
|
||||||
|
return stringToEncodedString(QString::fromStdString(str));
|
||||||
|
}
|
||||||
|
|
||||||
QString stringToBinaryArray(const std::string &str) {
|
QString stringToBinaryArray(const std::string &str) {
|
||||||
QStringList rows, chars;
|
QStringList rows, chars;
|
||||||
chars.reserve(13);
|
chars.reserve(13);
|
||||||
|
@ -334,7 +339,7 @@ QString Generator::valueAssignmentCode(structure::Value value) const {
|
||||||
case Tag::Int: return QString("%1").arg(value.Int());
|
case Tag::Int: return QString("%1").arg(value.Int());
|
||||||
case Tag::Double: return QString("%1").arg(value.Double());
|
case Tag::Double: return QString("%1").arg(value.Double());
|
||||||
case Tag::Pixels: return pxValueName(value.Int());
|
case Tag::Pixels: return pxValueName(value.Int());
|
||||||
case Tag::String: return QString("qsl(%1)").arg(stringToEncodedString(value.String()));
|
case Tag::String: return QString("QString::fromUtf8(%1)").arg(stringToEncodedString(value.String()));
|
||||||
case Tag::Color: {
|
case Tag::Color: {
|
||||||
auto v(value.Color());
|
auto v(value.Color());
|
||||||
if (v.red == v.green && v.red == v.blue && v.red == 0 && v.alpha == 255) {
|
if (v.red == v.green && v.red == v.blue && v.red == 0 && v.alpha == 255) {
|
||||||
|
@ -441,8 +446,15 @@ public:\n\
|
||||||
\n\
|
\n\
|
||||||
QByteArray save() const;\n\
|
QByteArray save() const;\n\
|
||||||
bool load(const QByteArray &cache);\n\
|
bool load(const QByteArray &cache);\n\
|
||||||
bool setColor(QLatin1String name, uchar r, uchar g, uchar b, uchar a);\n\
|
\n\
|
||||||
bool setColor(QLatin1String name, QLatin1String from);\n\
|
enum class SetResult {\n\
|
||||||
|
Ok,\n\
|
||||||
|
KeyNotFound,\n\
|
||||||
|
ValueNotFound,\n\
|
||||||
|
Duplicate,\n\
|
||||||
|
};\n\
|
||||||
|
SetResult setColor(QLatin1String name, uchar r, uchar g, uchar b, uchar a);\n\
|
||||||
|
SetResult setColor(QLatin1String name, QLatin1String from);\n\
|
||||||
void reset() {\n\
|
void reset() {\n\
|
||||||
clear();\n\
|
clear();\n\
|
||||||
finalize();\n\
|
finalize();\n\
|
||||||
|
@ -564,12 +576,20 @@ namespace main_palette {\n\
|
||||||
\n\
|
\n\
|
||||||
QByteArray save();\n\
|
QByteArray save();\n\
|
||||||
bool load(const QByteArray &cache);\n\
|
bool load(const QByteArray &cache);\n\
|
||||||
bool setColor(QLatin1String name, uchar r, uchar g, uchar b, uchar a);\n\
|
palette::SetResult setColor(QLatin1String name, uchar r, uchar g, uchar b, uchar a);\n\
|
||||||
bool setColor(QLatin1String name, QLatin1String from);\n\
|
palette::SetResult setColor(QLatin1String name, QLatin1String from);\n\
|
||||||
void apply(const palette &other);\n\
|
void apply(const palette &other);\n\
|
||||||
void reset();\n\
|
void reset();\n\
|
||||||
int indexOfColor(color c);\n\
|
int indexOfColor(color c);\n\
|
||||||
\n\
|
\n\
|
||||||
|
struct row {\n\
|
||||||
|
\tQLatin1String name;\n\
|
||||||
|
\tQLatin1String value;\n\
|
||||||
|
\tQLatin1String fallback;\n\
|
||||||
|
\tQLatin1String description;\n\
|
||||||
|
};\n\
|
||||||
|
QList<row> data();\n\
|
||||||
|
\n\
|
||||||
} // namespace main_palette\n";
|
} // namespace main_palette\n";
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -739,10 +759,17 @@ void palette::finalize() {\n\
|
||||||
\n\
|
\n\
|
||||||
compute(0, -1, { 255, 255, 255, 0}); // special color\n";
|
compute(0, -1, { 255, 255, 255, 0}); // special color\n";
|
||||||
|
|
||||||
|
QList<structure::FullName> names;
|
||||||
|
module_.enumVariables([this, &names](const Variable &variable) -> bool {
|
||||||
|
names.push_back(variable.name);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
QString dataRows;
|
||||||
int indexInPalette = 1;
|
int indexInPalette = 1;
|
||||||
QByteArray checksumString;
|
QByteArray checksumString;
|
||||||
checksumString.append("&transparent:{ 255, 255, 255, 0 }");
|
checksumString.append("&transparent:{ 255, 255, 255, 0 }");
|
||||||
bool result = module_.enumVariables([this, &indexInPalette, &checksumString](const Variable &variable) -> bool {
|
auto result = module_.enumVariables([this, &indexInPalette, &checksumString, &dataRows, &names](const Variable &variable) -> bool {
|
||||||
auto name = variable.name.back();
|
auto name = variable.name.back();
|
||||||
auto index = indexInPalette++;
|
auto index = indexInPalette++;
|
||||||
paletteIndices_[name] = index;
|
paletteIndices_[name] = index;
|
||||||
|
@ -754,8 +781,27 @@ void palette::finalize() {\n\
|
||||||
auto assignment = QString("{ %1, %2, %3, %4 }").arg(color.red).arg(color.green).arg(color.blue).arg(color.alpha);
|
auto assignment = QString("{ %1, %2, %3, %4 }").arg(color.red).arg(color.green).arg(color.blue).arg(color.alpha);
|
||||||
source_->stream() << "\tcompute(" << index << ", " << fallbackIndex << ", " << assignment << ");\n";
|
source_->stream() << "\tcompute(" << index << ", " << fallbackIndex << ", " << assignment << ");\n";
|
||||||
checksumString.append('&' + name + ':' + assignment);
|
checksumString.append('&' + name + ':' + assignment);
|
||||||
|
|
||||||
|
auto isCopy = !variable.value.copyOf().isEmpty();
|
||||||
|
auto colorString = paletteColorValue(color);
|
||||||
|
auto fallbackName = QString();
|
||||||
|
if (fallbackIndex > 0) {
|
||||||
|
auto fallbackVariable = module_.findVariableInModule(names[fallbackIndex - 1], module_);
|
||||||
|
if (fallbackVariable && fallbackVariable->value.type().tag == structure::TypeTag::Color) {
|
||||||
|
fallbackName = fallbackVariable->name.back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto value = isCopy ? fallbackName : '#' + colorString;
|
||||||
|
if (value.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
dataRows.append("\tresult.push_back({ qstr(\"" + name + "\"), qstr(\"" + value + "\"), qstr(\"" + (isCopy ? QString() : fallbackName) + "\"), qstr(" + stringToEncodedString(variable.description.toStdString()) + ") });\n");
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
if (!result) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
auto count = indexInPalette;
|
auto count = indexInPalette;
|
||||||
auto checksum = hashCrc32(checksumString.constData(), checksumString.size());
|
auto checksum = hashCrc32(checksumString.constData(), checksumString.size());
|
||||||
|
|
||||||
|
@ -854,23 +900,25 @@ bool palette::load(const QByteArray &cache) {\n\
|
||||||
return true;\n\
|
return true;\n\
|
||||||
}\n\
|
}\n\
|
||||||
\n\
|
\n\
|
||||||
bool palette::setColor(QLatin1String name, uchar r, uchar g, uchar b, uchar a) {\n\
|
palette::SetResult palette::setColor(QLatin1String name, uchar r, uchar g, uchar b, uchar a) {\n\
|
||||||
auto index = getPaletteIndex(name);\n\
|
auto nameIndex = getPaletteIndex(name);\n\
|
||||||
if (index >= 0) {\n\
|
if (nameIndex < 0) return SetResult::KeyNotFound;\n\
|
||||||
setData(index, { r, g, b, a });\n\
|
auto duplicate = (_status[nameIndex] != Status::Initial);\n\
|
||||||
return true;\n\
|
\n\
|
||||||
}\n\
|
setData(nameIndex, { r, g, b, a });\n\
|
||||||
return false;\n\
|
return duplicate ? SetResult::Duplicate : SetResult::Ok;\n\
|
||||||
}\n\
|
}\n\
|
||||||
\n\
|
\n\
|
||||||
bool palette::setColor(QLatin1String name, QLatin1String from) {\n\
|
palette::SetResult palette::setColor(QLatin1String name, QLatin1String from) {\n\
|
||||||
auto nameIndex = getPaletteIndex(name);\n\
|
auto nameIndex = getPaletteIndex(name);\n\
|
||||||
|
if (nameIndex < 0) return SetResult::KeyNotFound;\n\
|
||||||
|
auto duplicate = (_status[nameIndex] != Status::Initial);\n\
|
||||||
|
\n\
|
||||||
auto fromIndex = getPaletteIndex(from);\n\
|
auto fromIndex = getPaletteIndex(from);\n\
|
||||||
if (nameIndex >= 0 && fromIndex >= 0 && _status[fromIndex] == Status::Loaded) {\n\
|
if (fromIndex < 0 || _status[fromIndex] != Status::Loaded) return SetResult::ValueNotFound;\n\
|
||||||
setData(nameIndex, *data(fromIndex));\n\
|
\n\
|
||||||
return true;\n\
|
setData(nameIndex, *data(fromIndex));\n\
|
||||||
}\n\
|
return duplicate ? SetResult::Duplicate : SetResult::Ok;\n\
|
||||||
return false;\n\
|
|
||||||
}\n\
|
}\n\
|
||||||
\n\
|
\n\
|
||||||
namespace main_palette {\n\
|
namespace main_palette {\n\
|
||||||
|
@ -887,11 +935,11 @@ bool load(const QByteArray &cache) {\n\
|
||||||
return false;\n\
|
return false;\n\
|
||||||
}\n\
|
}\n\
|
||||||
\n\
|
\n\
|
||||||
bool setColor(QLatin1String name, uchar r, uchar g, uchar b, uchar a) {\n\
|
palette::SetResult setColor(QLatin1String name, uchar r, uchar g, uchar b, uchar a) {\n\
|
||||||
return _palette.setColor(name, r, g, b, a);\n\
|
return _palette.setColor(name, r, g, b, a);\n\
|
||||||
}\n\
|
}\n\
|
||||||
\n\
|
\n\
|
||||||
bool setColor(QLatin1String name, QLatin1String from) {\n\
|
palette::SetResult setColor(QLatin1String name, QLatin1String from) {\n\
|
||||||
return _palette.setColor(name, from);\n\
|
return _palette.setColor(name, from);\n\
|
||||||
}\n\
|
}\n\
|
||||||
\n\
|
\n\
|
||||||
|
@ -909,6 +957,14 @@ int indexOfColor(color c) {\n\
|
||||||
return _palette.indexOfColor(c);\n\
|
return _palette.indexOfColor(c);\n\
|
||||||
}\n\
|
}\n\
|
||||||
\n\
|
\n\
|
||||||
|
QList<row> data() {\n\
|
||||||
|
auto result = QList<row>();\n\
|
||||||
|
result.reserve(" << count << ");\n\
|
||||||
|
\n\
|
||||||
|
" << dataRows << "\n\
|
||||||
|
return result;\n\
|
||||||
|
}\n\
|
||||||
|
\n\
|
||||||
} // namespace main_palette\n\
|
} // namespace main_palette\n\
|
||||||
\n";
|
\n";
|
||||||
|
|
||||||
|
@ -1208,91 +1264,5 @@ bool Generator::collectUniqueValues() {
|
||||||
return module_.enumVariables(collector);
|
return module_.enumVariables(collector);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Generator::writeSampleTheme(const QString &filepath) {
|
|
||||||
QByteArray content;
|
|
||||||
QTextStream stream(&content);
|
|
||||||
|
|
||||||
stream << "\
|
|
||||||
//\n\
|
|
||||||
// This is a sample Telegram Desktop theme file.\n\
|
|
||||||
// It was generated from the 'colors.palette' style file.\n\
|
|
||||||
//\n\
|
|
||||||
// To create a theme with a background image included you should\n\
|
|
||||||
// put two files in a .zip archive:\n\
|
|
||||||
//\n\
|
|
||||||
// First one is the color scheme like the one you're viewing\n\
|
|
||||||
// right now, this file should be named 'colors.tdesktop-theme'.\n\
|
|
||||||
//\n\
|
|
||||||
// Second one should be the background image and it can be named\n\
|
|
||||||
// 'background.jpg', 'background.png', 'tiled.jpg' or 'tiled.png'.\n\
|
|
||||||
// You should name it 'background' (if you'd like it not to be tiled),\n\
|
|
||||||
// or it can be named 'tiled' (if you'd like it to be tiled).\n\
|
|
||||||
//\n\
|
|
||||||
// After that you need to change the extension of your .zip archive\n\
|
|
||||||
// to 'tdesktop-theme', so you'll have:\n\
|
|
||||||
//\n\
|
|
||||||
// mytheme.tdesktop-theme\n\
|
|
||||||
// |-colors.tdesktop-theme\n\
|
|
||||||
// |-background.jpg (or tiled.jpg, background.png, tiled.png)\n\
|
|
||||||
//\n\n";
|
|
||||||
|
|
||||||
QList<structure::FullName> names;
|
|
||||||
module_.enumVariables([this, &names](const Variable &variable) -> bool {
|
|
||||||
names.push_back(variable.name);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
bool result = module_.enumVariables([this, &names, &stream](const Variable &variable) -> bool {
|
|
||||||
auto name = variable.name.back();
|
|
||||||
if (variable.value.type().tag != structure::TypeTag::Color) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
auto color = variable.value.Color();
|
|
||||||
//color.red = uchar(rand() % 256);
|
|
||||||
//color.green = uchar(rand() % 256);
|
|
||||||
//color.blue = uchar(rand() % 256);
|
|
||||||
//auto fallbackIndex = -1;
|
|
||||||
auto fallbackIndex = paletteIndices_.value(colorFallbackName(variable.value), -1);
|
|
||||||
auto colorString = paletteColorValue(color);
|
|
||||||
if (fallbackIndex >= 0) {
|
|
||||||
auto fallbackVariable = module_.findVariableInModule(names[fallbackIndex - 1], module_);
|
|
||||||
if (!fallbackVariable || fallbackVariable->value.type().tag != structure::TypeTag::Color) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
auto fallbackName = fallbackVariable->name.back();
|
|
||||||
auto fallbackColor = fallbackVariable->value.Color();
|
|
||||||
if (colorString == paletteColorValue(fallbackColor)) {
|
|
||||||
stream << name << ": " << fallbackName << ";\n";
|
|
||||||
} else {
|
|
||||||
stream << name << ": #" << colorString << "; // " << fallbackName << ";\n";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
stream << name << ": #" << colorString << ";\n";
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
if (!result) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
stream.flush();
|
|
||||||
|
|
||||||
QFile file(filepath);
|
|
||||||
if (file.open(QIODevice::ReadOnly)) {
|
|
||||||
if (file.readAll() == content) {
|
|
||||||
file.close();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
file.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!file.open(QIODevice::WriteOnly)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (file.write(content) != content.size()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace style
|
} // namespace style
|
||||||
} // namespace codegen
|
} // namespace codegen
|
||||||
|
|
|
@ -40,7 +40,6 @@ public:
|
||||||
|
|
||||||
bool writeHeader();
|
bool writeHeader();
|
||||||
bool writeSource();
|
bool writeSource();
|
||||||
bool writeSampleTheme(const QString &filepath);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString typeToString(structure::Type type) const;
|
QString typeToString(structure::Type type) const;
|
||||||
|
|
|
@ -261,6 +261,7 @@ structure::Variable ParsedFile::readVariable(const QString &name) {
|
||||||
}
|
}
|
||||||
if (value.type().tag != structure::TypeTag::Struct || !value.copyOf().empty()) {
|
if (value.type().tag != structure::TypeTag::Struct || !value.copyOf().empty()) {
|
||||||
assertNextToken(BasicType::Semicolon);
|
assertNextToken(BasicType::Semicolon);
|
||||||
|
result.description = file_.getCurrentLineComment();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -81,10 +81,6 @@ bool Processor::write(const structure::Module &module) const {
|
||||||
if (!generator.writeSource()) {
|
if (!generator.writeSource()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
auto themePath = srcFile.absoluteDir().absolutePath() + "/default.tdesktop-theme";
|
|
||||||
if (options_.isPalette && !generator.writeSampleTheme(themePath)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -198,6 +198,7 @@ private:
|
||||||
struct Variable {
|
struct Variable {
|
||||||
FullName name;
|
FullName name;
|
||||||
Value value;
|
Value value;
|
||||||
|
QString description;
|
||||||
|
|
||||||
explicit operator bool() const {
|
explicit operator bool() const {
|
||||||
return !name.isEmpty();
|
return !name.isEmpty();
|
||||||
|
|
|
@ -940,6 +940,7 @@ QStringList MimeType::globPatterns() const {
|
||||||
switch (_type) {
|
switch (_type) {
|
||||||
case Known::WebP: return QStringList(qsl("*.webp"));
|
case Known::WebP: return QStringList(qsl("*.webp"));
|
||||||
case Known::TDesktopTheme: return QStringList(qsl("*.tdesktop-theme"));
|
case Known::TDesktopTheme: return QStringList(qsl("*.tdesktop-theme"));
|
||||||
|
case Known::TDesktopPalette: return QStringList(qsl("*.tdesktop-palette"));
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
return _typeStruct.globPatterns();
|
return _typeStruct.globPatterns();
|
||||||
|
@ -948,6 +949,7 @@ QString MimeType::filterString() const {
|
||||||
switch (_type) {
|
switch (_type) {
|
||||||
case Known::WebP: return qsl("WebP image (*.webp)");
|
case Known::WebP: return qsl("WebP image (*.webp)");
|
||||||
case Known::TDesktopTheme: return qsl("Theme files (*.tdesktop-theme)");
|
case Known::TDesktopTheme: return qsl("Theme files (*.tdesktop-theme)");
|
||||||
|
case Known::TDesktopPalette: return qsl("Palette files (*.tdesktop-palette)");
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
return _typeStruct.filterString();
|
return _typeStruct.filterString();
|
||||||
|
@ -956,6 +958,7 @@ QString MimeType::name() const {
|
||||||
switch (_type) {
|
switch (_type) {
|
||||||
case Known::WebP: return qsl("image/webp");
|
case Known::WebP: return qsl("image/webp");
|
||||||
case Known::TDesktopTheme: return qsl("application/x-tdesktop-theme");
|
case Known::TDesktopTheme: return qsl("application/x-tdesktop-theme");
|
||||||
|
case Known::TDesktopPalette: return qsl("application/x-tdesktop-palette");
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
return _typeStruct.name();
|
return _typeStruct.name();
|
||||||
|
@ -966,17 +969,22 @@ MimeType mimeTypeForName(const QString &mime) {
|
||||||
return MimeType(MimeType::Known::WebP);
|
return MimeType(MimeType::Known::WebP);
|
||||||
} else if (mime == qsl("application/x-tdesktop-theme")) {
|
} else if (mime == qsl("application/x-tdesktop-theme")) {
|
||||||
return MimeType(MimeType::Known::TDesktopTheme);
|
return MimeType(MimeType::Known::TDesktopTheme);
|
||||||
|
} else if (mime == qsl("application/x-tdesktop-palette")) {
|
||||||
|
return MimeType(MimeType::Known::TDesktopPalette);
|
||||||
}
|
}
|
||||||
return MimeType(QMimeDatabase().mimeTypeForName(mime));
|
return MimeType(QMimeDatabase().mimeTypeForName(mime));
|
||||||
}
|
}
|
||||||
|
|
||||||
MimeType mimeTypeForFile(const QFileInfo &file) {
|
MimeType mimeTypeForFile(const QFileInfo &file) {
|
||||||
QString path = file.absoluteFilePath();
|
QString path = file.absoluteFilePath();
|
||||||
if (path.endsWith(qsl(".webp"), Qt::CaseInsensitive)) {
|
if (path.endsWith(qstr(".webp"), Qt::CaseInsensitive)) {
|
||||||
return MimeType(MimeType::Known::WebP);
|
return MimeType(MimeType::Known::WebP);
|
||||||
} else if (path.endsWith(qsl(".tdesktop-theme"), Qt::CaseInsensitive)) {
|
} else if (path.endsWith(qstr(".tdesktop-theme"), Qt::CaseInsensitive)) {
|
||||||
return MimeType(MimeType::Known::TDesktopTheme);
|
return MimeType(MimeType::Known::TDesktopTheme);
|
||||||
|
} else if (path.endsWith(qstr(".tdesktop-palette"), Qt::CaseInsensitive)) {
|
||||||
|
return MimeType(MimeType::Known::TDesktopPalette);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
QFile f(path);
|
QFile f(path);
|
||||||
if (f.open(QIODevice::ReadOnly)) {
|
if (f.open(QIODevice::ReadOnly)) {
|
||||||
|
|
|
@ -432,6 +432,7 @@ public:
|
||||||
enum class Known {
|
enum class Known {
|
||||||
Unknown,
|
Unknown,
|
||||||
TDesktopTheme,
|
TDesktopTheme,
|
||||||
|
TDesktopPalette,
|
||||||
WebP,
|
WebP,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
|
|
||||||
#include "core/utils.h"
|
#include "core/utils.h"
|
||||||
|
|
||||||
#define BETA_VERSION_MACRO (0ULL)
|
#define BETA_VERSION_MACRO (1000006001ULL)
|
||||||
|
|
||||||
constexpr int AppVersion = 1000006;
|
constexpr int AppVersion = 1000006;
|
||||||
constexpr str_const AppVersionStr = "1.0.6";
|
constexpr str_const AppVersionStr = "1.0.6";
|
||||||
|
|
|
@ -42,7 +42,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
#include "ui/widgets/dropdown_menu.h"
|
#include "ui/widgets/dropdown_menu.h"
|
||||||
#include "ui/widgets/input_fields.h"
|
#include "ui/widgets/input_fields.h"
|
||||||
#include "window/window_theme.h"
|
#include "window/themes/window_theme.h"
|
||||||
#include "autoupdater.h"
|
#include "autoupdater.h"
|
||||||
#include "observer_peer.h"
|
#include "observer_peer.h"
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
#include "localstorage.h"
|
#include "localstorage.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
#include "window/top_bar_widget.h"
|
#include "window/top_bar_widget.h"
|
||||||
#include "window/window_theme.h"
|
#include "window/themes/window_theme.h"
|
||||||
#include "observer_peer.h"
|
#include "observer_peer.h"
|
||||||
#include "core/qthelp_regex.h"
|
#include "core/qthelp_regex.h"
|
||||||
#include "ui/widgets/popup_menu.h"
|
#include "ui/widgets/popup_menu.h"
|
||||||
|
@ -5802,10 +5802,8 @@ void HistoryWidget::botCallbackDone(BotCallbackInfo info, const MTPmessages_BotC
|
||||||
if (answerData.has_message()) {
|
if (answerData.has_message()) {
|
||||||
if (answerData.is_alert()) {
|
if (answerData.is_alert()) {
|
||||||
Ui::show(Box<InformBox>(qs(answerData.vmessage)));
|
Ui::show(Box<InformBox>(qs(answerData.vmessage)));
|
||||||
} else if (App::wnd()) {
|
} else {
|
||||||
Ui::Toast::Config toast;
|
Ui::Toast::Show(qs(answerData.vmessage));
|
||||||
toast.text = qs(answerData.vmessage);
|
|
||||||
Ui::Toast::Show(App::wnd(), toast);
|
|
||||||
}
|
}
|
||||||
} else if (answerData.has_url()) {
|
} else if (answerData.has_url()) {
|
||||||
auto url = qs(answerData.vurl);
|
auto url = qs(answerData.vurl);
|
||||||
|
|
|
@ -204,3 +204,9 @@ private:
|
||||||
mutable QSize _cachedSize;
|
mutable QSize _cachedSize;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename BoxType, typename ...Args>
|
||||||
|
inline object_ptr<BoxType> Box(Args&&... args) {
|
||||||
|
auto parent = static_cast<QWidget*>(nullptr);
|
||||||
|
return object_ptr<BoxType>(parent, std_::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
#include "serialize/serialize_document.h"
|
#include "serialize/serialize_document.h"
|
||||||
#include "serialize/serialize_common.h"
|
#include "serialize/serialize_common.h"
|
||||||
#include "data/data_drafts.h"
|
#include "data/data_drafts.h"
|
||||||
#include "window/window_theme.h"
|
#include "window/themes/window_theme.h"
|
||||||
#include "observer_peer.h"
|
#include "observer_peer.h"
|
||||||
#include "mainwidget.h"
|
#include "mainwidget.h"
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
|
@ -601,6 +601,7 @@ bool _backgroundWasRead = false;
|
||||||
bool _backgroundCanWrite = true;
|
bool _backgroundCanWrite = true;
|
||||||
|
|
||||||
FileKey _themeKey = 0;
|
FileKey _themeKey = 0;
|
||||||
|
QString _themePaletteAbsolutePath;
|
||||||
|
|
||||||
bool _readingUserSettings = false;
|
bool _readingUserSettings = false;
|
||||||
FileKey _userSettingsKey = 0;
|
FileKey _userSettingsKey = 0;
|
||||||
|
@ -3717,6 +3718,9 @@ bool readThemeUsingKey(FileKey key) {
|
||||||
if (theme.stream.status() != QDataStream::Ok) {
|
if (theme.stream.status() != QDataStream::Ok) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_themePaletteAbsolutePath = Window::Theme::IsPaletteTestingPath(pathAbsolute) ? pathAbsolute : QString();
|
||||||
|
|
||||||
QFile file(pathRelative);
|
QFile file(pathRelative);
|
||||||
if (pathRelative.isEmpty() || !file.exists()) {
|
if (pathRelative.isEmpty() || !file.exists()) {
|
||||||
file.setFileName(pathAbsolute);
|
file.setFileName(pathAbsolute);
|
||||||
|
@ -3748,6 +3752,7 @@ bool readThemeUsingKey(FileKey key) {
|
||||||
|
|
||||||
void writeTheme(const QString &pathRelative, const QString &pathAbsolute, const QByteArray &content, const Window::Theme::Cached &cache) {
|
void writeTheme(const QString &pathRelative, const QString &pathAbsolute, const QByteArray &content, const Window::Theme::Cached &cache) {
|
||||||
if (content.isEmpty()) {
|
if (content.isEmpty()) {
|
||||||
|
_themePaletteAbsolutePath = QString();
|
||||||
if (_themeKey) {
|
if (_themeKey) {
|
||||||
clearKey(_themeKey);
|
clearKey(_themeKey);
|
||||||
_themeKey = 0;
|
_themeKey = 0;
|
||||||
|
@ -3755,6 +3760,8 @@ void writeTheme(const QString &pathRelative, const QString &pathAbsolute, const
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_themePaletteAbsolutePath = Window::Theme::IsPaletteTestingPath(pathAbsolute) ? pathAbsolute : QString();
|
||||||
if (!_themeKey) {
|
if (!_themeKey) {
|
||||||
_themeKey = genKey();
|
_themeKey = genKey();
|
||||||
writeSettings();
|
writeSettings();
|
||||||
|
@ -3787,6 +3794,29 @@ bool hasTheme() {
|
||||||
return (_themeKey != 0);
|
return (_themeKey != 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString themePaletteAbsolutePath() {
|
||||||
|
return _themePaletteAbsolutePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool copyThemeColorsToPalette(const QString &path) {
|
||||||
|
if (!_themeKey) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileReadDescriptor theme;
|
||||||
|
if (!readEncryptedFile(theme, _themeKey, FileOption::Safe, _settingsKey)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray themeContent;
|
||||||
|
theme.stream >> themeContent;
|
||||||
|
if (theme.stream.status() != QDataStream::Ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Window::Theme::CopyColorsToPalette(path, themeContent);
|
||||||
|
}
|
||||||
|
|
||||||
uint32 _peerSize(PeerData *peer) {
|
uint32 _peerSize(PeerData *peer) {
|
||||||
uint32 result = sizeof(quint64) + sizeof(quint64) + Serialize::storageImageLocationSize();
|
uint32 result = sizeof(quint64) + sizeof(quint64) + Serialize::storageImageLocationSize();
|
||||||
if (peer->isUser()) {
|
if (peer->isUser()) {
|
||||||
|
|
|
@ -153,6 +153,8 @@ bool readBackground();
|
||||||
void writeTheme(const QString &pathRelative, const QString &pathAbsolute, const QByteArray &content, const Window::Theme::Cached &cache);
|
void writeTheme(const QString &pathRelative, const QString &pathAbsolute, const QByteArray &content, const Window::Theme::Cached &cache);
|
||||||
void clearTheme();
|
void clearTheme();
|
||||||
bool hasTheme();
|
bool hasTheme();
|
||||||
|
QString themePaletteAbsolutePath();
|
||||||
|
bool copyThemeColorsToPalette(const QString &file);
|
||||||
|
|
||||||
void writeRecentHashtagsAndBots();
|
void writeRecentHashtagsAndBots();
|
||||||
void readRecentHashtagsAndBots();
|
void readRecentHashtagsAndBots();
|
||||||
|
|
|
@ -57,7 +57,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
#include "media/player/media_player_instance.h"
|
#include "media/player/media_player_instance.h"
|
||||||
#include "core/qthelp_regex.h"
|
#include "core/qthelp_regex.h"
|
||||||
#include "core/qthelp_url.h"
|
#include "core/qthelp_url.h"
|
||||||
#include "window/window_theme.h"
|
#include "window/themes/window_theme.h"
|
||||||
#include "window/player_wrap_widget.h"
|
#include "window/player_wrap_widget.h"
|
||||||
#include "styles/style_boxes.h"
|
#include "styles/style_boxes.h"
|
||||||
|
|
||||||
|
|
|
@ -47,9 +47,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
#include "settings/settings_widget.h"
|
#include "settings/settings_widget.h"
|
||||||
#include "platform/platform_notifications_manager.h"
|
#include "platform/platform_notifications_manager.h"
|
||||||
#include "window/notifications_manager.h"
|
#include "window/notifications_manager.h"
|
||||||
#include "window/window_theme.h"
|
#include "window/themes/window_theme.h"
|
||||||
#include "window/window_theme_warning.h"
|
#include "window/themes/window_theme_warning.h"
|
||||||
#include "window/window_main_menu.h"
|
#include "window/window_main_menu.h"
|
||||||
|
#include "core/task_queue.h"
|
||||||
|
|
||||||
ConnectingWidget::ConnectingWidget(QWidget *parent, const QString &text, const QString &reconnect) : TWidget(parent)
|
ConnectingWidget::ConnectingWidget(QWidget *parent, const QString &text, const QString &reconnect) : TWidget(parent)
|
||||||
, _reconnect(this, QString()) {
|
, _reconnect(this, QString()) {
|
||||||
|
@ -379,14 +380,18 @@ void MainWindow::setupMain(const MTPUser *self) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::showSettings() {
|
void MainWindow::showSettings() {
|
||||||
if (_passcode) return;
|
|
||||||
|
|
||||||
if (isHidden()) showFromTray();
|
if (isHidden()) showFromTray();
|
||||||
|
|
||||||
|
showSpecialLayer(Box<Settings::Widget>());
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::showSpecialLayer(object_ptr<LayerWidget> layer) {
|
||||||
|
if (_passcode) return;
|
||||||
|
|
||||||
if (!_layerBg) {
|
if (!_layerBg) {
|
||||||
_layerBg.create(bodyWidget());
|
_layerBg.create(bodyWidget());
|
||||||
}
|
}
|
||||||
_layerBg->showSpecialLayer(Box<Settings::Widget>());
|
_layerBg->showSpecialLayer(std_::move(layer));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::showMainMenu() {
|
void MainWindow::showMainMenu() {
|
||||||
|
@ -527,17 +532,37 @@ void MainWindow::hideConnecting() {
|
||||||
|
|
||||||
void MainWindow::themeUpdated(const Window::Theme::BackgroundUpdate &data) {
|
void MainWindow::themeUpdated(const Window::Theme::BackgroundUpdate &data) {
|
||||||
using Type = Window::Theme::BackgroundUpdate::Type;
|
using Type = Window::Theme::BackgroundUpdate::Type;
|
||||||
|
|
||||||
|
// We delay animating theme warning because we want all other
|
||||||
|
// subscribers to receive paltte changed notification before any
|
||||||
|
// animations (that include pixmap caches with old palette values).
|
||||||
if (data.type == Type::TestingTheme) {
|
if (data.type == Type::TestingTheme) {
|
||||||
if (!_testingThemeWarning) {
|
if (!_testingThemeWarning) {
|
||||||
_testingThemeWarning.create(bodyWidget());
|
_testingThemeWarning.create(bodyWidget());
|
||||||
|
_testingThemeWarning->hide();
|
||||||
_testingThemeWarning->setGeometry(rect());
|
_testingThemeWarning->setGeometry(rect());
|
||||||
_testingThemeWarning->setHiddenCallback([this] { _testingThemeWarning.destroyDelayed(); });
|
_testingThemeWarning->setHiddenCallback([this] { _testingThemeWarning.destroyDelayed(); });
|
||||||
}
|
}
|
||||||
_testingThemeWarning->showAnimated();
|
|
||||||
|
base::TaskQueue::Main().Put(base::lambda_guarded(this, [this] {
|
||||||
|
if (_testingThemeWarning) {
|
||||||
|
_testingThemeWarning->showAnimated();
|
||||||
|
}
|
||||||
|
}));
|
||||||
} else if (data.type == Type::RevertingTheme || data.type == Type::ApplyingTheme) {
|
} else if (data.type == Type::RevertingTheme || data.type == Type::ApplyingTheme) {
|
||||||
_testingThemeWarning->hideAnimated();
|
if (_testingThemeWarning) {
|
||||||
_testingThemeWarning = nullptr;
|
if (_testingThemeWarning->isHidden()) {
|
||||||
setInnerFocus();
|
_testingThemeWarning.destroy();
|
||||||
|
} else {
|
||||||
|
base::TaskQueue::Main().Put(base::lambda_guarded(this, [this] {
|
||||||
|
if (_testingThemeWarning) {
|
||||||
|
_testingThemeWarning->hideAnimated();
|
||||||
|
_testingThemeWarning = nullptr;
|
||||||
|
}
|
||||||
|
setInnerFocus();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -834,12 +859,9 @@ void MainWindow::closeEvent(QCloseEvent *e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::resizeEvent(QResizeEvent *e) {
|
|
||||||
Platform::MainWindow::resizeEvent(e);
|
|
||||||
updateControlsGeometry();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWindow::updateControlsGeometry() {
|
void MainWindow::updateControlsGeometry() {
|
||||||
|
Platform::MainWindow::updateControlsGeometry();
|
||||||
|
|
||||||
auto body = bodyWidget()->rect();
|
auto body = bodyWidget()->rect();
|
||||||
if (_passcode) _passcode->setGeometry(body);
|
if (_passcode) _passcode->setGeometry(body);
|
||||||
if (_main) _main->setGeometry(body);
|
if (_main) _main->setGeometry(body);
|
||||||
|
|
|
@ -145,6 +145,8 @@ public:
|
||||||
void showMainMenu();
|
void showMainMenu();
|
||||||
void updateTrayMenu(bool force = false) override;
|
void updateTrayMenu(bool force = false) override;
|
||||||
|
|
||||||
|
void showSpecialLayer(object_ptr<LayerWidget> layer);
|
||||||
|
|
||||||
void ui_showBox(object_ptr<BoxContent> box, ShowLayerOptions options);
|
void ui_showBox(object_ptr<BoxContent> box, ShowLayerOptions options);
|
||||||
void ui_hideSettingsAndLayer(ShowLayerOptions options);
|
void ui_hideSettingsAndLayer(ShowLayerOptions options);
|
||||||
bool ui_isLayerShown();
|
bool ui_isLayerShown();
|
||||||
|
@ -156,12 +158,13 @@ public:
|
||||||
protected:
|
protected:
|
||||||
bool eventFilter(QObject *o, QEvent *e) override;
|
bool eventFilter(QObject *o, QEvent *e) override;
|
||||||
void closeEvent(QCloseEvent *e) override;
|
void closeEvent(QCloseEvent *e) override;
|
||||||
void resizeEvent(QResizeEvent *e) override;
|
|
||||||
|
|
||||||
void initHook() override;
|
void initHook() override;
|
||||||
void updateIsActiveHook() override;
|
void updateIsActiveHook() override;
|
||||||
void clearWidgetsHook() override;
|
void clearWidgetsHook() override;
|
||||||
|
|
||||||
|
void updateControlsGeometry() override;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void checkAutoLock();
|
void checkAutoLock();
|
||||||
|
|
||||||
|
@ -205,8 +208,6 @@ private:
|
||||||
|
|
||||||
void themeUpdated(const Window::Theme::BackgroundUpdate &data);
|
void themeUpdated(const Window::Theme::BackgroundUpdate &data);
|
||||||
|
|
||||||
void updateControlsGeometry();
|
|
||||||
|
|
||||||
QPixmap grabInner();
|
QPixmap grabInner();
|
||||||
|
|
||||||
void placeSmallCounter(QImage &img, int size, int count, style::color bg, const QPoint &shift, style::color color) override;
|
void placeSmallCounter(QImage &img, int size, int count, style::color bg, const QPoint &shift, style::color color) override;
|
||||||
|
|
|
@ -111,8 +111,6 @@ mediaviewFileIconSize: 80px;
|
||||||
|
|
||||||
mediaviewFileLink: defaultLinkButton;
|
mediaviewFileLink: defaultLinkButton;
|
||||||
|
|
||||||
mediaviewTransparentSize: 4px;
|
|
||||||
|
|
||||||
mediaviewMenu: Menu(defaultMenu) {
|
mediaviewMenu: Menu(defaultMenu) {
|
||||||
itemBg: mediaviewMenuBg;
|
itemBg: mediaviewMenuBg;
|
||||||
itemBgOver: mediaviewMenuBgOver;
|
itemBgOver: mediaviewMenuBgOver;
|
||||||
|
|
|
@ -34,7 +34,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
#include "styles/style_history.h"
|
#include "styles/style_history.h"
|
||||||
#include "media/media_audio.h"
|
#include "media/media_audio.h"
|
||||||
#include "history/history_media_types.h"
|
#include "history/history_media_types.h"
|
||||||
#include "window/window_theme_preview.h"
|
#include "window/themes/window_theme_preview.h"
|
||||||
#include "core/task_queue.h"
|
#include "core/task_queue.h"
|
||||||
#include "observer_peer.h"
|
#include "observer_peer.h"
|
||||||
|
|
||||||
|
@ -69,6 +69,7 @@ bool typeHasMediaOverview(MediaOverviewType type) {
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
MediaView::MediaView(QWidget*) : TWidget(nullptr)
|
MediaView::MediaView(QWidget*) : TWidget(nullptr)
|
||||||
|
, _transparentBrush(style::transparentPlaceholderBrush())
|
||||||
, _animStarted(getms())
|
, _animStarted(getms())
|
||||||
, _docDownload(this, lang(lng_media_download), st::mediaviewFileLink)
|
, _docDownload(this, lang(lng_media_download), st::mediaviewFileLink)
|
||||||
, _docSaveAs(this, lang(lng_mediaview_save_as), st::mediaviewFileLink)
|
, _docSaveAs(this, lang(lng_mediaview_save_as), st::mediaviewFileLink)
|
||||||
|
@ -96,8 +97,6 @@ MediaView::MediaView(QWidget*) : TWidget(nullptr)
|
||||||
mediaOverviewUpdated(update);
|
mediaOverviewUpdated(update);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
generateTransparentBrush();
|
|
||||||
|
|
||||||
setWindowFlags(Qt::FramelessWindowHint | Qt::BypassWindowManagerHint | Qt::Tool | Qt::NoDropShadowWindowHint);
|
setWindowFlags(Qt::FramelessWindowHint | Qt::BypassWindowManagerHint | Qt::Tool | Qt::NoDropShadowWindowHint);
|
||||||
moveToScreen();
|
moveToScreen();
|
||||||
setAttribute(Qt::WA_NoSystemBackground, true);
|
setAttribute(Qt::WA_NoSystemBackground, true);
|
||||||
|
@ -1186,6 +1185,7 @@ void MediaView::displayPhoto(PhotoData *photo, HistoryItem *item) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void MediaView::destroyThemePreview() {
|
void MediaView::destroyThemePreview() {
|
||||||
|
_themePreviewId = 0;
|
||||||
_themePreviewShown = false;
|
_themePreviewShown = false;
|
||||||
_themePreview.reset();
|
_themePreview.reset();
|
||||||
_themeApply.destroy();
|
_themeApply.destroy();
|
||||||
|
@ -2767,19 +2767,6 @@ void MediaView::loadBack() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MediaView::generateTransparentBrush() {
|
|
||||||
auto size = st::mediaviewTransparentSize * cIntRetinaFactor();
|
|
||||||
auto transparent = QImage(2 * size, 2 * size, QImage::Format_ARGB32_Premultiplied);
|
|
||||||
transparent.fill(st::mediaviewTransparentBg->c);
|
|
||||||
{
|
|
||||||
Painter p(&transparent);
|
|
||||||
p.fillRect(rtlrect(0, size, size, size, 2 * size), st::mediaviewTransparentFg);
|
|
||||||
p.fillRect(rtlrect(size, 0, size, size, 2 * size), st::mediaviewTransparentFg);
|
|
||||||
}
|
|
||||||
transparent.setDevicePixelRatio(cRetinaFactor());
|
|
||||||
_transparentBrush = QBrush(transparent);
|
|
||||||
}
|
|
||||||
|
|
||||||
MediaView::LastChatPhoto MediaView::computeLastOverviewChatPhoto() {
|
MediaView::LastChatPhoto MediaView::computeLastOverviewChatPhoto() {
|
||||||
LastChatPhoto emptyResult = { nullptr, nullptr };
|
LastChatPhoto emptyResult = { nullptr, nullptr };
|
||||||
auto lastPhotoInOverview = [&emptyResult](auto history, auto list) -> LastChatPhoto {
|
auto lastPhotoInOverview = [&emptyResult](auto history, auto list) -> LastChatPhoto {
|
||||||
|
|
|
@ -166,8 +166,6 @@ private:
|
||||||
void findCurrent();
|
void findCurrent();
|
||||||
void loadBack();
|
void loadBack();
|
||||||
|
|
||||||
void generateTransparentBrush();
|
|
||||||
|
|
||||||
void updateCursor();
|
void updateCursor();
|
||||||
void setZoomLevel(int newZoom);
|
void setZoomLevel(int newZoom);
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
#include "ui/widgets/buttons.h"
|
#include "ui/widgets/buttons.h"
|
||||||
#include "ui/widgets/input_fields.h"
|
#include "ui/widgets/input_fields.h"
|
||||||
#include "window/top_bar_widget.h"
|
#include "window/top_bar_widget.h"
|
||||||
#include "window/window_theme.h"
|
#include "window/themes/window_theme.h"
|
||||||
#include "lang.h"
|
#include "lang.h"
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "mainwidget.h"
|
#include "mainwidget.h"
|
||||||
|
|
|
@ -30,7 +30,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
#include "lang.h"
|
#include "lang.h"
|
||||||
#include "localstorage.h"
|
#include "localstorage.h"
|
||||||
#include "ui/widgets/popup_menu.h"
|
#include "ui/widgets/popup_menu.h"
|
||||||
#include "window/window_theme.h"
|
#include "window/themes/window_theme.h"
|
||||||
|
|
||||||
#include <qpa/qplatformnativeinterface.h>
|
#include <qpa/qplatformnativeinterface.h>
|
||||||
|
|
||||||
|
|
|
@ -110,9 +110,7 @@ void InviteLinkWidget::refreshLink() {
|
||||||
}
|
}
|
||||||
|
|
||||||
QApplication::clipboard()->setText(link);
|
QApplication::clipboard()->setText(link);
|
||||||
Ui::Toast::Config toast;
|
Ui::Toast::Show(lang(lng_group_invite_copied));
|
||||||
toast.text = lang(lng_group_invite_copied);
|
|
||||||
Ui::Toast::Show(App::wnd(), toast);
|
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
#include "ui/widgets/buttons.h"
|
#include "ui/widgets/buttons.h"
|
||||||
#include "ui/effects/widget_slide_wrap.h"
|
#include "ui/effects/widget_slide_wrap.h"
|
||||||
#include "localstorage.h"
|
#include "localstorage.h"
|
||||||
#include "window/window_theme.h"
|
#include "window/themes/window_theme.h"
|
||||||
|
|
||||||
namespace Settings {
|
namespace Settings {
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
#include "ui/widgets/buttons.h"
|
#include "ui/widgets/buttons.h"
|
||||||
#include "localstorage.h"
|
#include "localstorage.h"
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "window/window_theme.h"
|
#include "window/themes/window_theme.h"
|
||||||
|
|
||||||
namespace Settings {
|
namespace Settings {
|
||||||
|
|
||||||
|
@ -240,7 +240,7 @@ void BackgroundWidget::needBackgroundUpdate(bool tile) {
|
||||||
|
|
||||||
void BackgroundWidget::onChooseFromFile() {
|
void BackgroundWidget::onChooseFromFile() {
|
||||||
auto imgExtensions = cImgExtensions();
|
auto imgExtensions = cImgExtensions();
|
||||||
auto filters = QStringList(qsl("Theme files (*.tdesktop-theme *") + imgExtensions.join(qsl(" *")) + qsl(")"));
|
auto filters = QStringList(qsl("Theme files (*.tdesktop-theme *.tdesktop-palette *") + imgExtensions.join(qsl(" *")) + qsl(")"));
|
||||||
filters.push_back(filedialogAllFilesFilter());
|
filters.push_back(filedialogAllFilesFilter());
|
||||||
|
|
||||||
_chooseFromFileQueryId = FileDialog::queryReadFile(lang(lng_choose_image), filters.join(qsl(";;")));
|
_chooseFromFileQueryId = FileDialog::queryReadFile(lang(lng_choose_image), filters.join(qsl(";;")));
|
||||||
|
@ -261,7 +261,8 @@ void BackgroundWidget::notifyFileQueryUpdated(const FileDialog::QueryUpdate &upd
|
||||||
}
|
}
|
||||||
|
|
||||||
auto filePath = update.filePaths.front();
|
auto filePath = update.filePaths.front();
|
||||||
if (filePath.endsWith(qstr(".tdesktop-theme"), Qt::CaseInsensitive)) {
|
if (filePath.endsWith(qstr(".tdesktop-theme"), Qt::CaseInsensitive)
|
||||||
|
|| filePath.endsWith(qstr(".tdesktop-palette"), Qt::CaseInsensitive)) {
|
||||||
Window::Theme::Apply(filePath);
|
Window::Theme::Apply(filePath);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
#include "styles/style_settings.h"
|
#include "styles/style_settings.h"
|
||||||
#include "styles/style_boxes.h"
|
#include "styles/style_boxes.h"
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "lang.h"
|
|
||||||
|
|
||||||
namespace Settings {
|
namespace Settings {
|
||||||
|
|
||||||
|
@ -32,6 +31,11 @@ FixedBar::FixedBar(QWidget *parent) : TWidget(parent) {
|
||||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FixedBar::setText(const QString &text) {
|
||||||
|
_text = text;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
int FixedBar::resizeGetHeight(int newWidth) {
|
int FixedBar::resizeGetHeight(int newWidth) {
|
||||||
return st::settingsFixedBarHeight - st::boxRadius;
|
return st::settingsFixedBarHeight - st::boxRadius;
|
||||||
}
|
}
|
||||||
|
@ -43,7 +47,7 @@ void FixedBar::paintEvent(QPaintEvent *e) {
|
||||||
|
|
||||||
p.setFont(st::settingsFixedBarFont);
|
p.setFont(st::settingsFixedBarFont);
|
||||||
p.setPen(st::windowFg);
|
p.setPen(st::windowFg);
|
||||||
p.drawTextLeft(st::settingsFixedBarTextPosition.x(), st::settingsFixedBarTextPosition.y() - st::boxRadius, width(), lang(lng_menu_settings));
|
p.drawTextLeft(st::settingsFixedBarTextPosition.x(), st::settingsFixedBarTextPosition.y() - st::boxRadius, width(), _text);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Settings
|
} // namespace Settings
|
||||||
|
|
|
@ -30,11 +30,16 @@ class FixedBar : public TWidget {
|
||||||
public:
|
public:
|
||||||
FixedBar(QWidget *parent);
|
FixedBar(QWidget *parent);
|
||||||
|
|
||||||
|
void setText(const QString &text);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void paintEvent(QPaintEvent *e) override;
|
void paintEvent(QPaintEvent *e) override;
|
||||||
|
|
||||||
int resizeGetHeight(int newWidth) override;
|
int resizeGetHeight(int newWidth) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString _text;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Settings
|
} // namespace Settings
|
||||||
|
|
|
@ -189,21 +189,21 @@ void GeneralWidget::refreshControls() {
|
||||||
|
|
||||||
if (cPlatform() == dbipWindows || cSupportTray()) {
|
if (cPlatform() == dbipWindows || cSupportTray()) {
|
||||||
addChildRow(_enableTrayIcon, marginSmall, lang(lng_settings_workmode_tray), SLOT(onEnableTrayIcon()), (cWorkMode() == dbiwmTrayOnly || cWorkMode() == dbiwmWindowAndTray));
|
addChildRow(_enableTrayIcon, marginSmall, lang(lng_settings_workmode_tray), SLOT(onEnableTrayIcon()), (cWorkMode() == dbiwmTrayOnly || cWorkMode() == dbiwmWindowAndTray));
|
||||||
#ifdef Q_OS_WIN
|
if (cPlatform() == dbipWindows) {
|
||||||
addChildRow(_enableTaskbarIcon, marginLarge, lang(lng_settings_workmode_window), SLOT(onEnableTaskbarIcon()), (cWorkMode() == dbiwmWindowOnly || cWorkMode() == dbiwmWindowAndTray));
|
addChildRow(_enableTaskbarIcon, marginLarge, lang(lng_settings_workmode_window), SLOT(onEnableTaskbarIcon()), (cWorkMode() == dbiwmWindowOnly || cWorkMode() == dbiwmWindowAndTray));
|
||||||
|
|
||||||
#ifndef OS_WIN_STORE
|
#ifndef OS_WIN_STORE
|
||||||
addChildRow(_autoStart, marginSmall, lang(lng_settings_auto_start), SLOT(onAutoStart()), cAutoStart());
|
addChildRow(_autoStart, marginSmall, lang(lng_settings_auto_start), SLOT(onAutoStart()), cAutoStart());
|
||||||
addChildRow(_startMinimized, marginLarge, slidedPadding, lang(lng_settings_start_min), SLOT(onStartMinimized()), (cStartMinimized() && !Global::LocalPasscode()));
|
addChildRow(_startMinimized, marginLarge, slidedPadding, lang(lng_settings_start_min), SLOT(onStartMinimized()), (cStartMinimized() && !Global::LocalPasscode()));
|
||||||
subscribe(Global::RefLocalPasscodeChanged(), [this] {
|
subscribe(Global::RefLocalPasscodeChanged(), [this] {
|
||||||
_startMinimized->entity()->setChecked(cStartMinimized() && !Global::LocalPasscode());
|
_startMinimized->entity()->setChecked(cStartMinimized() && !Global::LocalPasscode());
|
||||||
});
|
});
|
||||||
if (!cAutoStart()) {
|
if (!cAutoStart()) {
|
||||||
_startMinimized->hideFast();
|
_startMinimized->hideFast();
|
||||||
}
|
}
|
||||||
addChildRow(_addInSendTo, marginSmall, lang(lng_settings_add_sendto), SLOT(onAddInSendTo()), cSendToMenu());
|
addChildRow(_addInSendTo, marginSmall, lang(lng_settings_add_sendto), SLOT(onAddInSendTo()), cSendToMenu());
|
||||||
#endif // OS_WIN_STORE
|
#endif // OS_WIN_STORE
|
||||||
#endif // Q_OS_WIN
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -297,7 +297,7 @@ void GeneralWidget::updateWorkmode() {
|
||||||
Local::writeSettings();
|
Local::writeSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined Q_OS_WIN && !defined OS_WIN_STORE
|
#if !defined OS_WIN_STORE
|
||||||
void GeneralWidget::onAutoStart() {
|
void GeneralWidget::onAutoStart() {
|
||||||
cSetAutoStart(_autoStart->checked());
|
cSetAutoStart(_autoStart->checked());
|
||||||
if (cAutoStart()) {
|
if (cAutoStart()) {
|
||||||
|
@ -335,6 +335,6 @@ void GeneralWidget::onAddInSendTo() {
|
||||||
psSendToMenu(_addInSendTo->checked());
|
psSendToMenu(_addInSendTo->checked());
|
||||||
Local::writeSettings();
|
Local::writeSettings();
|
||||||
}
|
}
|
||||||
#endif // Q_OS_WIN && !OS_WIN_STORE
|
#endif // !OS_WIN_STORE
|
||||||
|
|
||||||
} // namespace Settings
|
} // namespace Settings
|
||||||
|
|
|
@ -94,11 +94,11 @@ private slots:
|
||||||
void onEnableTrayIcon();
|
void onEnableTrayIcon();
|
||||||
void onEnableTaskbarIcon();
|
void onEnableTaskbarIcon();
|
||||||
|
|
||||||
#if defined Q_OS_WIN && !defined OS_WIN_STORE
|
#ifndef OS_WIN_STORE
|
||||||
void onAutoStart();
|
void onAutoStart();
|
||||||
void onStartMinimized();
|
void onStartMinimized();
|
||||||
void onAddInSendTo();
|
void onAddInSendTo();
|
||||||
#endif // Q_OS_WIN && !OS_WIN_STORE
|
#endif // !OS_WIN_STORE
|
||||||
|
|
||||||
void onRestart();
|
void onRestart();
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
|
|
||||||
namespace Settings {
|
namespace Settings {
|
||||||
|
|
||||||
InnerWidget::InnerWidget(QWidget *parent) : TWidget(parent)
|
InnerWidget::InnerWidget(QWidget *parent) : LayerInner(parent)
|
||||||
, _self(App::self()) {
|
, _self(App::self()) {
|
||||||
refreshBlocks();
|
refreshBlocks();
|
||||||
subscribe(Global::RefSelfChanged(), [this]() { selfUpdated(); });
|
subscribe(Global::RefSelfChanged(), [this]() { selfUpdated(); });
|
||||||
|
|
|
@ -20,19 +20,21 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "settings/settings_layer.h"
|
||||||
|
|
||||||
namespace Settings {
|
namespace Settings {
|
||||||
|
|
||||||
class CoverWidget;
|
class CoverWidget;
|
||||||
class BlockWidget;
|
class BlockWidget;
|
||||||
|
|
||||||
class InnerWidget : public TWidget, private base::Subscriber {
|
class InnerWidget : public LayerInner, private base::Subscriber {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
InnerWidget(QWidget *parent);
|
InnerWidget(QWidget *parent);
|
||||||
|
|
||||||
// Count new height for width=newWidth and resize to it.
|
// Count new height for width=newWidth and resize to it.
|
||||||
void resizeToWidth(int newWidth, int contentLeft) {
|
void resizeToWidth(int newWidth, int contentLeft) override {
|
||||||
_contentLeft = contentLeft;
|
_contentLeft = contentLeft;
|
||||||
return TWidget::resizeToWidth(newWidth);
|
return TWidget::resizeToWidth(newWidth);
|
||||||
}
|
}
|
||||||
|
|
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 "mainwidget.h"
|
||||||
#include "localstorage.h"
|
#include "localstorage.h"
|
||||||
#include "boxes/confirmbox.h"
|
#include "boxes/confirmbox.h"
|
||||||
|
#include "lang.h"
|
||||||
#include "application.h"
|
#include "application.h"
|
||||||
|
#include "ui/filedialog.h"
|
||||||
|
#include "window/themes/window_theme.h"
|
||||||
|
#include "window/themes/window_theme_editor.h"
|
||||||
|
|
||||||
namespace Settings {
|
namespace Settings {
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -88,6 +92,23 @@ void fillCodes() {
|
||||||
main->getDifference();
|
main->getDifference();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Codes.insert(qsl("loadcolors"), []() {
|
||||||
|
FileDialog::askOpenPath("Open palette file", "Palette (*.tdesktop-palette)", [](const FileDialog::OpenResult &result) {
|
||||||
|
if (!result.paths.isEmpty()) {
|
||||||
|
Window::Theme::Apply(result.paths.front());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
Codes.insert(qsl("edittheme"), []() {
|
||||||
|
auto palettePath = Local::themePaletteAbsolutePath();
|
||||||
|
if (palettePath.isEmpty()) {
|
||||||
|
FileDialog::askWritePath(lang(lng_theme_editor_save_palette), "Palette (*.tdesktop-palette)", "colors.tdesktop-palette", [](const QString &path) {
|
||||||
|
Window::Theme::Editor::StartFromCurrentTheme(path);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Window::Theme::Editor::Start(palettePath);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void codesFeedString(const QString &text) {
|
void codesFeedString(const QString &text) {
|
||||||
|
@ -123,42 +144,28 @@ void codesFeedString(const QString &text) {
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Widget::Widget(QWidget *parent) : LayerWidget(parent)
|
Widget::Widget(QWidget *parent) {
|
||||||
, _scroll(this, st::settingsScroll)
|
setTitle(lang(lng_menu_settings));
|
||||||
, _fixedBar(this)
|
_inner = setInnerWidget(object_ptr<InnerWidget>(this));
|
||||||
, _fixedBarClose(this, st::settingsFixedBarClose)
|
setCloseClickHandler([]() {
|
||||||
, _fixedBarShadow(this, object_ptr<BoxLayerTitleShadow>(this)) {
|
|
||||||
_inner = _scroll->setOwnedWidget(object_ptr<InnerWidget>(this));
|
|
||||||
|
|
||||||
_fixedBar->moveToLeft(0, st::boxRadius);
|
|
||||||
_fixedBarClose->moveToRight(0, 0);
|
|
||||||
_fixedBarShadow->entity()->resize(width(), st::lineWidth);
|
|
||||||
_fixedBarShadow->moveToLeft(0, _fixedBar->y() + _fixedBar->height());
|
|
||||||
_fixedBarShadow->hideFast();
|
|
||||||
_scroll->moveToLeft(0, st::settingsFixedBarHeight);
|
|
||||||
|
|
||||||
connect(_scroll, SIGNAL(scrolled()), this, SLOT(onScroll()));
|
|
||||||
|
|
||||||
_fixedBarClose->setClickedCallback([]() {
|
|
||||||
Ui::hideSettingsAndLayer();
|
Ui::hideSettingsAndLayer();
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(_inner, SIGNAL(heightUpdated()), this, SLOT(onInnerHeightUpdated()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Widget::onScroll() {
|
void Widget::showFinished() {
|
||||||
if (_scroll->scrollTop() > 0) {
|
_inner->showFinished();
|
||||||
_fixedBarShadow->showAnimated();
|
}
|
||||||
} else {
|
|
||||||
_fixedBarShadow->hideAnimated();
|
void Widget::keyPressEvent(QKeyEvent *e) {
|
||||||
}
|
codesFeedString(e->text());
|
||||||
|
return LayerWidget::keyPressEvent(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Widget::parentResized() {
|
void Widget::parentResized() {
|
||||||
auto parentSize = parentWidget()->size();
|
auto parentSize = parentWidget()->size();
|
||||||
int windowWidth = parentSize.width();
|
auto windowWidth = parentSize.width();
|
||||||
int newWidth = st::settingsMaxWidth;
|
auto newWidth = st::settingsMaxWidth;
|
||||||
int newContentLeft = st::settingsMaxPadding;
|
auto newContentLeft = st::settingsMaxPadding;
|
||||||
if (windowWidth <= st::settingsMaxWidth) {
|
if (windowWidth <= st::settingsMaxWidth) {
|
||||||
newWidth = windowWidth;
|
newWidth = windowWidth;
|
||||||
newContentLeft = st::settingsMinPadding;
|
newContentLeft = st::settingsMinPadding;
|
||||||
|
@ -176,92 +183,27 @@ void Widget::parentResized() {
|
||||||
newContentLeft += ((newWidth - st::windowMinWidth) * (st::settingsMaxPadding - st::settingsMinPadding)) / (st::settingsMaxWidth - st::windowMinWidth);
|
newContentLeft += ((newWidth - st::windowMinWidth) * (st::settingsMaxPadding - st::settingsMinPadding)) / (st::settingsMaxWidth - st::windowMinWidth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
resizeToWidth(newWidth, newContentLeft);
|
||||||
// Widget height depends on InnerWidget height, so we
|
|
||||||
// resize it here, not in the resizeEvent() handler.
|
|
||||||
_inner->resizeToWidth(newWidth, newContentLeft);
|
|
||||||
|
|
||||||
resizeUsingInnerHeight(newWidth, newContentLeft);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Widget::onInnerHeightUpdated() {
|
void Widget::resizeUsingInnerHeight(int newWidth, int innerHeight) {
|
||||||
resizeUsingInnerHeight(width(), _contentLeft);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Widget::resizeUsingInnerHeight(int newWidth, int newContentLeft) {
|
|
||||||
if (!App::wnd()) return;
|
if (!App::wnd()) return;
|
||||||
|
|
||||||
auto parentSize = parentWidget()->size();
|
auto parentSize = parentWidget()->size();
|
||||||
int windowWidth = parentSize.width();
|
auto windowWidth = parentSize.width();
|
||||||
int windowHeight = parentSize.height();
|
auto windowHeight = parentSize.height();
|
||||||
int maxHeight = st::settingsFixedBarHeight + _inner->height();
|
auto maxHeight = st::settingsFixedBarHeight + innerHeight;
|
||||||
int newHeight = maxHeight + st::boxRadius;
|
auto newHeight = maxHeight + st::boxRadius;
|
||||||
if (newHeight > windowHeight || newWidth >= windowWidth) {
|
if (newHeight > windowHeight || newWidth >= windowWidth) {
|
||||||
newHeight = windowHeight;
|
newHeight = windowHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_contentLeft != newContentLeft) {
|
auto roundedCorners = newHeight < windowHeight;
|
||||||
_contentLeft = newContentLeft;
|
setRoundedCorners(roundedCorners);
|
||||||
}
|
setAttribute(Qt::WA_OpaquePaintEvent, !roundedCorners);
|
||||||
|
|
||||||
_roundedCorners = (newHeight < windowHeight);
|
|
||||||
setAttribute(Qt::WA_OpaquePaintEvent, !_roundedCorners);
|
|
||||||
|
|
||||||
setGeometry((windowWidth - newWidth) / 2, (windowHeight - newHeight) / 2, newWidth, newHeight);
|
setGeometry((windowWidth - newWidth) / 2, (windowHeight - newHeight) / 2, newWidth, newHeight);
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Widget::showFinished() {
|
|
||||||
_inner->showFinished();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Widget::paintEvent(QPaintEvent *e) {
|
|
||||||
Painter p(this);
|
|
||||||
auto clip = e->rect();
|
|
||||||
if (_roundedCorners) {
|
|
||||||
auto paintTopRounded = clip.intersects(QRect(0, 0, width(), st::boxRadius));
|
|
||||||
auto paintBottomRounded = clip.intersects(QRect(0, height() - st::boxRadius, width(), st::boxRadius));
|
|
||||||
if (paintTopRounded || paintBottomRounded) {
|
|
||||||
auto parts = qFlags(App::RectPart::None);
|
|
||||||
if (paintTopRounded) parts |= App::RectPart::TopFull;
|
|
||||||
if (paintBottomRounded) parts |= App::RectPart::BottomFull;
|
|
||||||
App::roundRect(p, rect(), st::boxBg, BoxCorners, nullptr, parts);
|
|
||||||
}
|
|
||||||
auto other = clip.intersected(QRect(0, st::boxRadius, width(), height() - 2 * st::boxRadius));
|
|
||||||
if (!other.isEmpty()) {
|
|
||||||
p.fillRect(other, st::boxBg);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
p.fillRect(e->rect(), st::boxBg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Widget::resizeEvent(QResizeEvent *e) {
|
|
||||||
LayerWidget::resizeEvent(e);
|
|
||||||
if (!width() || !height()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_fixedBar->resizeToWidth(width());
|
|
||||||
_fixedBar->moveToLeft(0, st::boxRadius);
|
|
||||||
_fixedBarClose->moveToRight(0, 0);
|
|
||||||
auto shadowTop = _fixedBar->y() + _fixedBar->height();
|
|
||||||
_fixedBarShadow->entity()->resize(width(), st::lineWidth);
|
|
||||||
_fixedBarShadow->moveToLeft(0, shadowTop);
|
|
||||||
|
|
||||||
auto scrollSize = QSize(width(), height() - shadowTop - (_roundedCorners ? st::boxRadius : 0));
|
|
||||||
if (_scroll->size() != scrollSize) {
|
|
||||||
_scroll->resize(scrollSize);
|
|
||||||
}
|
|
||||||
if (!_scroll->isHidden()) {
|
|
||||||
auto scrollTop = _scroll->scrollTop();
|
|
||||||
_inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Widget::keyPressEvent(QKeyEvent *e) {
|
|
||||||
codesFeedString(e->text());
|
|
||||||
return LayerWidget::keyPressEvent(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Settings
|
} // namespace Settings
|
||||||
|
|
|
@ -20,51 +20,30 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "layerwidget.h"
|
#include "settings/settings_layer.h"
|
||||||
|
|
||||||
class BoxLayerTitleShadow;
|
|
||||||
|
|
||||||
namespace Ui {
|
|
||||||
class ScrollArea;
|
|
||||||
class IconButton;
|
|
||||||
template <typename Widget>
|
|
||||||
class WidgetFadeWrap;
|
|
||||||
} // namespace Ui
|
|
||||||
|
|
||||||
namespace Settings {
|
namespace Settings {
|
||||||
|
|
||||||
class InnerWidget;
|
class InnerWidget;
|
||||||
class FixedBar;
|
|
||||||
|
|
||||||
class Widget : public LayerWidget {
|
class Widget : public Layer {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Widget(QWidget *parent);
|
Widget(QWidget*);
|
||||||
|
|
||||||
void parentResized() override;
|
|
||||||
void showFinished() override;
|
void showFinished() override;
|
||||||
|
void parentResized() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void paintEvent(QPaintEvent *e) override;
|
|
||||||
void resizeEvent(QResizeEvent *e) override;
|
|
||||||
void keyPressEvent(QKeyEvent *e) override;
|
void keyPressEvent(QKeyEvent *e) override;
|
||||||
|
|
||||||
private slots:
|
|
||||||
void onInnerHeightUpdated();
|
|
||||||
void onScroll();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void resizeUsingInnerHeight(int newWidth, int newContentLeft);
|
void resizeUsingInnerHeight(int newWidth, int innerHeight) override;
|
||||||
|
|
||||||
object_ptr<Ui::ScrollArea> _scroll;
|
|
||||||
QPointer<InnerWidget> _inner;
|
QPointer<InnerWidget> _inner;
|
||||||
object_ptr<FixedBar> _fixedBar;
|
|
||||||
object_ptr<Ui::IconButton> _fixedBarClose;
|
|
||||||
object_ptr<Ui::WidgetFadeWrap<BoxLayerTitleShadow>> _fixedBarShadow;
|
|
||||||
|
|
||||||
int _contentLeft = 0;
|
int _contentLeft = 0;
|
||||||
bool _roundedCorners = false;
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -86,7 +86,7 @@ void applyArchivedResult(const MTPDmessages_stickerSetInstallResultArchive &d) {
|
||||||
toast.text = lang(lng_stickers_packs_archived);
|
toast.text = lang(lng_stickers_packs_archived);
|
||||||
toast.maxWidth = st::stickersToastMaxWidth;
|
toast.maxWidth = st::stickersToastMaxWidth;
|
||||||
toast.padding = st::stickersToastPadding;
|
toast.padding = st::stickersToastPadding;
|
||||||
Ui::Toast::Show(App::wnd(), toast);
|
Ui::Toast::Show(toast);
|
||||||
// Ui::show(Box<StickersBox>(archived), KeepOtherLayers);
|
// Ui::show(Box<StickersBox>(archived), KeepOtherLayers);
|
||||||
|
|
||||||
emit App::main()->stickersUpdated();
|
emit App::main()->stickersUpdated();
|
||||||
|
|
|
@ -35,7 +35,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
#include "localstorage.h"
|
#include "localstorage.h"
|
||||||
#include "history/history_media_types.h"
|
#include "history/history_media_types.h"
|
||||||
#include "styles/style_history.h"
|
#include "styles/style_history.h"
|
||||||
#include "window/window_theme.h"
|
#include "window/themes/window_theme.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
|
|
@ -1169,7 +1169,7 @@ public:
|
||||||
return (type == AnimatedDocument) && !mime.compare(qstr("video/mp4"), Qt::CaseInsensitive);
|
return (type == AnimatedDocument) && !mime.compare(qstr("video/mp4"), Qt::CaseInsensitive);
|
||||||
}
|
}
|
||||||
bool isTheme() const {
|
bool isTheme() const {
|
||||||
return name.endsWith(qstr(".tdesktop-theme"), Qt::CaseInsensitive);
|
return name.endsWith(qstr(".tdesktop-theme"), Qt::CaseInsensitive) || name.endsWith(qstr(".tdesktop-palette"), Qt::CaseInsensitive);
|
||||||
}
|
}
|
||||||
bool isMusic() const {
|
bool isMusic() const {
|
||||||
if (auto s = song()) {
|
if (auto s = song()) {
|
||||||
|
|
|
@ -25,6 +25,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
#include "localstorage.h"
|
#include "localstorage.h"
|
||||||
#include "platform/platform_file_dialog.h"
|
#include "platform/platform_file_dialog.h"
|
||||||
|
|
||||||
|
#include "core/task_queue.h"
|
||||||
|
|
||||||
void filedialogInit() {
|
void filedialogInit() {
|
||||||
if (cDialogLastPath().isEmpty()) {
|
if (cDialogLastPath().isEmpty()) {
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
|
@ -384,4 +386,67 @@ base::Observable<QueryUpdate> &QueryDone() {
|
||||||
return QueryDoneObservable;
|
return QueryDoneObservable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void askOpenPath(const QString &caption, const QString &filter, base::lambda<void(const OpenResult &result)> &&callback, base::lambda<void()> &&failed) {
|
||||||
|
base::TaskQueue::Main().Put([caption, filter, callback = std_::move(callback), failed = std_::move(failed)] {
|
||||||
|
auto file = QString();
|
||||||
|
auto remoteContent = QByteArray();
|
||||||
|
if (filedialogGetOpenFile(file, remoteContent, caption, filter) && (!file.isEmpty() || !remoteContent.isEmpty())) {
|
||||||
|
if (callback) {
|
||||||
|
auto result = OpenResult();
|
||||||
|
if (!file.isEmpty()) {
|
||||||
|
result.paths.push_back(file);
|
||||||
|
}
|
||||||
|
result.remoteContent = remoteContent;
|
||||||
|
callback(result);
|
||||||
|
}
|
||||||
|
} else if (failed) {
|
||||||
|
failed();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void askOpenPaths(const QString &caption, const QString &filter, base::lambda<void(const OpenResult &result)> &&callback, base::lambda<void()> &&failed) {
|
||||||
|
base::TaskQueue::Main().Put([caption, filter, callback = std_::move(callback), failed = std_::move(failed)] {
|
||||||
|
auto files = QStringList();
|
||||||
|
auto remoteContent = QByteArray();
|
||||||
|
if (filedialogGetOpenFiles(files, remoteContent, caption, filter) && (!files.isEmpty() || !remoteContent.isEmpty())) {
|
||||||
|
if (callback) {
|
||||||
|
auto result = OpenResult();
|
||||||
|
result.paths = files;
|
||||||
|
result.remoteContent = remoteContent;
|
||||||
|
callback(result);
|
||||||
|
}
|
||||||
|
} else if (failed) {
|
||||||
|
failed();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void askWritePath(const QString &caption, const QString &filter, const QString &initialPath, base::lambda<void(const QString &result)> &&callback, base::lambda<void()> &&failed) {
|
||||||
|
base::TaskQueue::Main().Put([caption, filter, initialPath, callback = std_::move(callback), failed = std_::move(failed)] {
|
||||||
|
auto file = QString();
|
||||||
|
if (filedialogGetSaveFile(file, caption, filter, initialPath)) {
|
||||||
|
if (callback) {
|
||||||
|
callback(file);
|
||||||
|
}
|
||||||
|
} else if (failed) {
|
||||||
|
failed();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void askFolder(const QString &caption, base::lambda<void(const QString &result)> &&callback, base::lambda<void()> &&failed) {
|
||||||
|
base::TaskQueue::Main().Put([caption, callback = std_::move(callback), failed = std_::move(failed)] {
|
||||||
|
auto folder = QString();
|
||||||
|
if (filedialogGetDir(folder, caption) && !folder.isEmpty()) {
|
||||||
|
if (callback) {
|
||||||
|
callback(folder);
|
||||||
|
}
|
||||||
|
} else if (failed) {
|
||||||
|
failed();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace FileDialog
|
} // namespace FileDialog
|
||||||
|
|
|
@ -65,4 +65,13 @@ bool processQuery();
|
||||||
|
|
||||||
base::Observable<QueryUpdate> &QueryDone();
|
base::Observable<QueryUpdate> &QueryDone();
|
||||||
|
|
||||||
|
struct OpenResult {
|
||||||
|
QStringList paths;
|
||||||
|
QByteArray remoteContent;
|
||||||
|
};
|
||||||
|
void askOpenPath(const QString &caption, const QString &filter, base::lambda<void(const OpenResult &result)> &&callback, base::lambda<void()> &&failed = base::lambda<void()>());
|
||||||
|
void askOpenPaths(const QString &caption, const QString &filter, base::lambda<void(const OpenResult &result)> &&callback, base::lambda<void()> &&failed = base::lambda<void()>());
|
||||||
|
void askWritePath(const QString &caption, const QString &filter, const QString &initialPath, base::lambda<void(const QString &result)> &&callback, base::lambda<void()> &&failed = base::lambda<void()>());
|
||||||
|
void askFolder(const QString &caption, base::lambda<void(const QString &result)> &&callback, base::lambda<void()> &&failed = base::lambda<void()>());
|
||||||
|
|
||||||
} // namespace FileDialog
|
} // namespace FileDialog
|
||||||
|
|
|
@ -115,6 +115,20 @@ void colorizeImage(const QImage &src, QColor c, QImage *outResult, QRect srcRect
|
||||||
outResult->setDevicePixelRatio(src.devicePixelRatio());
|
outResult->setDevicePixelRatio(src.devicePixelRatio());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QBrush transparentPlaceholderBrush() {
|
||||||
|
auto size = st::transparentPlaceholderSize * cIntRetinaFactor();
|
||||||
|
auto transparent = QImage(2 * size, 2 * size, QImage::Format_ARGB32_Premultiplied);
|
||||||
|
transparent.fill(st::mediaviewTransparentBg->c);
|
||||||
|
{
|
||||||
|
Painter p(&transparent);
|
||||||
|
p.fillRect(rtlrect(0, size, size, size, 2 * size), st::mediaviewTransparentFg);
|
||||||
|
p.fillRect(rtlrect(size, 0, size, size, 2 * size), st::mediaviewTransparentFg);
|
||||||
|
}
|
||||||
|
transparent.setDevicePixelRatio(cRetinaFactor());
|
||||||
|
return QBrush(transparent);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
namespace internal {
|
namespace internal {
|
||||||
|
|
||||||
QImage createCircleMask(int size, QColor bg, QColor fg) {
|
QImage createCircleMask(int size, QColor bg, QColor fg) {
|
||||||
|
|
|
@ -78,6 +78,8 @@ inline QImage colorizeImage(const QImage &src, const color &c, QRect srcRect = Q
|
||||||
return colorizeImage(src, c->c, srcRect);
|
return colorizeImage(src, c->c, srcRect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QBrush transparentPlaceholderBrush();
|
||||||
|
|
||||||
namespace internal {
|
namespace internal {
|
||||||
|
|
||||||
QImage createCircleMask(int size, QColor bg, QColor fg);
|
QImage createCircleMask(int size, QColor bg, QColor fg);
|
||||||
|
|
|
@ -23,6 +23,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
|
|
||||||
#include "ui/toast/toast_manager.h"
|
#include "ui/toast/toast_manager.h"
|
||||||
#include "ui/toast/toast_widget.h"
|
#include "ui/toast/toast_widget.h"
|
||||||
|
#include "mainwindow.h"
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
namespace Toast {
|
namespace Toast {
|
||||||
|
@ -40,6 +41,18 @@ void Show(QWidget *parent, const Config &config) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Show(const Config &config) {
|
||||||
|
if (auto window = App::wnd()) {
|
||||||
|
Show(window->bodyWidget(), config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Show(const QString &text) {
|
||||||
|
Config toast;
|
||||||
|
toast.text = text;
|
||||||
|
Show(toast);
|
||||||
|
}
|
||||||
|
|
||||||
void Instance::opacityAnimationCallback() {
|
void Instance::opacityAnimationCallback() {
|
||||||
_widget->setShownLevel(_a_opacity.current(_hiding ? 0. : 1.));
|
_widget->setShownLevel(_a_opacity.current(_hiding ? 0. : 1.));
|
||||||
_widget->update();
|
_widget->update();
|
||||||
|
|
|
@ -36,6 +36,8 @@ struct Config {
|
||||||
QMargins padding;
|
QMargins padding;
|
||||||
};
|
};
|
||||||
void Show(QWidget *parent, const Config &config);
|
void Show(QWidget *parent, const Config &config);
|
||||||
|
void Show(const Config &config);
|
||||||
|
void Show(const QString &text);
|
||||||
|
|
||||||
class Instance {
|
class Instance {
|
||||||
struct Private {
|
struct Private {
|
||||||
|
|
|
@ -40,6 +40,10 @@ Manager::Manager(QWidget *parent) : QObject(parent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Manager *Manager::instance(QWidget *parent) {
|
Manager *Manager::instance(QWidget *parent) {
|
||||||
|
if (!parent) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
_managers.createIfNull();
|
_managers.createIfNull();
|
||||||
auto i = _managers->constFind(parent);
|
auto i = _managers->constFind(parent);
|
||||||
if (i == _managers->cend()) {
|
if (i == _managers->cend()) {
|
||||||
|
|
|
@ -632,14 +632,19 @@ CrossButton::CrossButton(QWidget *parent, const style::CrossButton &st) : Ripple
|
||||||
hide();
|
hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CrossButton::hideAnimated() {
|
|
||||||
startAnimation(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CrossButton::showAnimated() {
|
void CrossButton::showAnimated() {
|
||||||
startAnimation(true);
|
startAnimation(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CrossButton::showFast() {
|
||||||
|
showAnimated();
|
||||||
|
_a_show.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CrossButton::hideAnimated() {
|
||||||
|
startAnimation(false);
|
||||||
|
}
|
||||||
|
|
||||||
void CrossButton::hideFast() {
|
void CrossButton::hideFast() {
|
||||||
hideAnimated();
|
hideAnimated();
|
||||||
_a_show.finish();
|
_a_show.finish();
|
||||||
|
|
|
@ -213,6 +213,7 @@ public:
|
||||||
CrossButton(QWidget *parent, const style::CrossButton &st);
|
CrossButton(QWidget *parent, const style::CrossButton &st);
|
||||||
|
|
||||||
void showAnimated();
|
void showAnimated();
|
||||||
|
void showFast();
|
||||||
void hideAnimated();
|
void hideAnimated();
|
||||||
void hideFast();
|
void hideFast();
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
#include "ui/widgets/popup_menu.h"
|
#include "ui/widgets/popup_menu.h"
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "ui/countryinput.h"
|
#include "ui/countryinput.h"
|
||||||
#include "window/window_theme.h"
|
#include "window/themes/window_theme.h"
|
||||||
#include "lang.h"
|
#include "lang.h"
|
||||||
#include "numbers.h"
|
#include "numbers.h"
|
||||||
|
|
||||||
|
|
|
@ -586,7 +586,7 @@ attentionLeftOutlineButton: OutlineButton(defaultLeftOutlineButton) {
|
||||||
textBgOver: attentionButtonBgOver;
|
textBgOver: attentionButtonBgOver;
|
||||||
|
|
||||||
textFg: attentionButtonFg;
|
textFg: attentionButtonFg;
|
||||||
textFgOver: attentionButtonFg;
|
textFgOver: attentionButtonFgOver;
|
||||||
|
|
||||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||||
color: attentionButtonBgRipple;
|
color: attentionButtonBgRipple;
|
||||||
|
|
|
@ -24,7 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
#include "localstorage.h"
|
#include "localstorage.h"
|
||||||
#include "styles/style_window.h"
|
#include "styles/style_window.h"
|
||||||
#include "platform/platform_window_title.h"
|
#include "platform/platform_window_title.h"
|
||||||
#include "window/window_theme.h"
|
#include "window/themes/window_theme.h"
|
||||||
#include "mediaview.h"
|
#include "mediaview.h"
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
|
|
||||||
|
@ -248,11 +248,16 @@ void MainWindow::resizeEvent(QResizeEvent *e) {
|
||||||
|
|
||||||
void MainWindow::updateControlsGeometry() {
|
void MainWindow::updateControlsGeometry() {
|
||||||
auto bodyTop = 0;
|
auto bodyTop = 0;
|
||||||
|
auto bodyWidth = width();
|
||||||
if (_title && !_title->isHidden()) {
|
if (_title && !_title->isHidden()) {
|
||||||
_title->setGeometry(0, bodyTop, width(), _title->height());
|
_title->setGeometry(0, bodyTop, width(), _title->height());
|
||||||
bodyTop += _title->height();
|
bodyTop += _title->height();
|
||||||
}
|
}
|
||||||
_body->setGeometry(0, bodyTop, width(), height() - bodyTop);
|
if (_rightColumn) {
|
||||||
|
bodyWidth -= _rightColumn->width();
|
||||||
|
_rightColumn->setGeometry(bodyWidth, bodyTop, width() - bodyWidth, height() - bodyTop);
|
||||||
|
}
|
||||||
|
_body->setGeometry(0, bodyTop, bodyWidth, height() - bodyTop);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::updateUnreadCounter() {
|
void MainWindow::updateUnreadCounter() {
|
||||||
|
@ -276,7 +281,7 @@ void MainWindow::savePosition(Qt::WindowState state) {
|
||||||
auto r = geometry();
|
auto r = geometry();
|
||||||
curPos.x = r.x();
|
curPos.x = r.x();
|
||||||
curPos.y = r.y();
|
curPos.y = r.y();
|
||||||
curPos.w = r.width();
|
curPos.w = r.width() - (_rightColumn ? _rightColumn->width() : 0);
|
||||||
curPos.h = r.height();
|
curPos.h = r.height();
|
||||||
curPos.maximized = 0;
|
curPos.maximized = 0;
|
||||||
}
|
}
|
||||||
|
@ -317,6 +322,35 @@ bool MainWindow::minimizeToTray() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::showRightColumn(object_ptr<TWidget> widget) {
|
||||||
|
auto wasWidth = width();
|
||||||
|
auto wasRightWidth = _rightColumn ? _rightColumn->width() : 0;
|
||||||
|
_rightColumn = std_::move(widget);
|
||||||
|
if (_rightColumn) {
|
||||||
|
_rightColumn->setParent(this);
|
||||||
|
_rightColumn->show();
|
||||||
|
_rightColumn->setFocus();
|
||||||
|
} else if (App::wnd()) {
|
||||||
|
App::wnd()->setInnerFocus();
|
||||||
|
}
|
||||||
|
auto nowRightWidth = _rightColumn ? _rightColumn->width() : 0;
|
||||||
|
setMinimumWidth(st::windowMinWidth + nowRightWidth);
|
||||||
|
auto nowWidth = width();
|
||||||
|
|
||||||
|
if (!isMaximized()) {
|
||||||
|
auto desktop = QDesktopWidget().availableGeometry(this);
|
||||||
|
auto newWidth = qMin(wasWidth + nowRightWidth - wasRightWidth, desktop.width());
|
||||||
|
auto newLeft = qMin(x(), desktop.x() + desktop.width() - newWidth);
|
||||||
|
if (x() != newLeft || width() != newWidth) {
|
||||||
|
setGeometry(newLeft, y(), newWidth, height());
|
||||||
|
} else {
|
||||||
|
updateControlsGeometry();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
updateControlsGeometry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::documentUpdated(DocumentData *doc) {
|
void MainWindow::documentUpdated(DocumentData *doc) {
|
||||||
if (!_mediaView || _mediaView->isHidden()) return;
|
if (!_mediaView || _mediaView->isHidden()) return;
|
||||||
_mediaView->documentUpdated(doc);
|
_mediaView->documentUpdated(doc);
|
||||||
|
|
|
@ -68,6 +68,8 @@ public:
|
||||||
|
|
||||||
QWidget *filedialogParent();
|
QWidget *filedialogParent();
|
||||||
|
|
||||||
|
void showRightColumn(object_ptr<TWidget> widget);
|
||||||
|
|
||||||
virtual void updateTrayMenu(bool force = false) {
|
virtual void updateTrayMenu(bool force = false) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,6 +127,8 @@ protected:
|
||||||
virtual void showTrayTooltip() {
|
virtual void showTrayTooltip() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual void updateControlsGeometry();
|
||||||
|
|
||||||
// This one is overriden in Windows for historical reasons.
|
// This one is overriden in Windows for historical reasons.
|
||||||
virtual int32 screenNameChecksum(const QString &name) const;
|
virtual int32 screenNameChecksum(const QString &name) const;
|
||||||
|
|
||||||
|
@ -143,7 +147,6 @@ private slots:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void updatePalette();
|
void updatePalette();
|
||||||
void updateControlsGeometry();
|
|
||||||
void updateUnreadCounter();
|
void updateUnreadCounter();
|
||||||
void initSize();
|
void initSize();
|
||||||
|
|
||||||
|
@ -154,6 +157,7 @@ private:
|
||||||
|
|
||||||
object_ptr<TitleWidget> _title = { nullptr };
|
object_ptr<TitleWidget> _title = { nullptr };
|
||||||
object_ptr<TWidget> _body;
|
object_ptr<TWidget> _body;
|
||||||
|
object_ptr<TWidget> _rightColumn = { nullptr };
|
||||||
|
|
||||||
QString _titleText;
|
QString _titleText;
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
#include "ui/widgets/buttons.h"
|
#include "ui/widgets/buttons.h"
|
||||||
#include "ui/widgets/input_fields.h"
|
#include "ui/widgets/input_fields.h"
|
||||||
#include "dialogs/dialogs_layout.h"
|
#include "dialogs/dialogs_layout.h"
|
||||||
#include "window/window_theme.h"
|
#include "window/themes/window_theme.h"
|
||||||
#include "styles/style_dialogs.h"
|
#include "styles/style_dialogs.h"
|
||||||
#include "styles/style_boxes.h"
|
#include "styles/style_boxes.h"
|
||||||
#include "styles/style_window.h"
|
#include "styles/style_window.h"
|
||||||
|
|
|
@ -19,7 +19,7 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||||
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
*/
|
*/
|
||||||
#include "stdafx.h"
|
#include "stdafx.h"
|
||||||
#include "window/window_theme.h"
|
#include "window/themes/window_theme.h"
|
||||||
|
|
||||||
#include "mainwidget.h"
|
#include "mainwidget.h"
|
||||||
#include "localstorage.h"
|
#include "localstorage.h"
|
||||||
|
@ -27,6 +27,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
#include "core/zlib_help.h"
|
#include "core/zlib_help.h"
|
||||||
#include "styles/style_widgets.h"
|
#include "styles/style_widgets.h"
|
||||||
#include "styles/style_history.h"
|
#include "styles/style_history.h"
|
||||||
|
#include "boxes/backgroundbox.h"
|
||||||
|
|
||||||
namespace Window {
|
namespace Window {
|
||||||
namespace Theme {
|
namespace Theme {
|
||||||
|
@ -51,6 +52,13 @@ struct Data {
|
||||||
};
|
};
|
||||||
NeverFreedPointer<Data> instance;
|
NeverFreedPointer<Data> instance;
|
||||||
|
|
||||||
|
inline bool AreTestingTheme() {
|
||||||
|
if (instance) {
|
||||||
|
return !instance->applying.paletteForRevert.isEmpty();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
QByteArray readThemeContent(const QString &path) {
|
QByteArray readThemeContent(const QString &path) {
|
||||||
QFile file(path);
|
QFile file(path);
|
||||||
if (!file.exists()) {
|
if (!file.exists()) {
|
||||||
|
@ -63,7 +71,7 @@ QByteArray readThemeContent(const QString &path) {
|
||||||
return QByteArray();
|
return QByteArray();
|
||||||
}
|
}
|
||||||
if (!file.open(QIODevice::ReadOnly)) {
|
if (!file.open(QIODevice::ReadOnly)) {
|
||||||
LOG(("Theme Warning: could not open theme file: %1").arg(path));
|
LOG(("Theme Error: could not open theme file: %1").arg(path));
|
||||||
return QByteArray();
|
return QByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,7 +110,7 @@ bool readNameAndValue(const char *&from, const char *end, QLatin1String *outName
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (*from != ':') {
|
if (*from != ':') {
|
||||||
LOG(("Theme Error: Expected ':' between each name and value in the color scheme."));
|
LOG(("Theme Error: Expected ':' between each name and value in the color scheme (while reading key '%1')").arg(*outName));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!skipWhitespaces(++from, end)) {
|
if (!skipWhitespaces(++from, end)) {
|
||||||
|
@ -113,7 +121,7 @@ bool readNameAndValue(const char *&from, const char *end, QLatin1String *outName
|
||||||
if (*from == '#') ++from;
|
if (*from == '#') ++from;
|
||||||
|
|
||||||
if (readName(from, end).size() == 0) {
|
if (readName(from, end).size() == 0) {
|
||||||
LOG(("Theme Error: Expected a color value in #rrggbb or #rrggbbaa format in the color scheme."));
|
LOG(("Theme Error: Expected a color value in #rrggbb or #rrggbbaa format in the color scheme (while reading key '%1')").arg(*outName));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
*outValue = QLatin1String(valueStart, from - valueStart);
|
*outValue = QLatin1String(valueStart, from - valueStart);
|
||||||
|
@ -123,7 +131,7 @@ bool readNameAndValue(const char *&from, const char *end, QLatin1String *outName
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (*from != ';') {
|
if (*from != ';') {
|
||||||
LOG(("Theme Error: Expected ';' after each value in the color scheme."));
|
LOG(("Theme Error: Expected ';' after each value in the color scheme (while reading key '%1')").arg(*outName));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
++from;
|
++from;
|
||||||
|
@ -136,7 +144,7 @@ enum class SetResult {
|
||||||
NotFound,
|
NotFound,
|
||||||
};
|
};
|
||||||
SetResult setColorSchemeValue(QLatin1String name, QLatin1String value, Instance *out) {
|
SetResult setColorSchemeValue(QLatin1String name, QLatin1String value, Instance *out) {
|
||||||
auto found = false;
|
auto result = style::palette::SetResult::Ok;
|
||||||
auto size = value.size();
|
auto size = value.size();
|
||||||
auto data = value.data();
|
auto data = value.data();
|
||||||
if (data[0] == '#' && (size == 7 || size == 9)) {
|
if (data[0] == '#' && (size == 7 || size == 9)) {
|
||||||
|
@ -146,41 +154,39 @@ SetResult setColorSchemeValue(QLatin1String name, QLatin1String value, Instance
|
||||||
auto b = readHexUchar(data[5], data[6], error);
|
auto b = readHexUchar(data[5], data[6], error);
|
||||||
auto a = (size == 9) ? readHexUchar(data[7], data[8], error) : uchar(255);
|
auto a = (size == 9) ? readHexUchar(data[7], data[8], error) : uchar(255);
|
||||||
if (error) {
|
if (error) {
|
||||||
LOG(("Theme Error: Expected a color value in #rrggbb or #rrggbbaa format in the color scheme (while applying '%1: %2')").arg(QLatin1String(name)).arg(QLatin1String(value)));
|
LOG(("Theme Warning: Skipping value '%1: %2' (expected a color value in #rrggbb or #rrggbbaa or a previously defined key in the color scheme)").arg(name).arg(value));
|
||||||
return SetResult::Bad;
|
return SetResult::Ok;
|
||||||
} else if (out) {
|
} else if (out) {
|
||||||
found = out->palette.setColor(name, r, g, b, a);
|
result = out->palette.setColor(name, r, g, b, a);
|
||||||
} else {
|
} else {
|
||||||
found = style::main_palette::setColor(name, r, g, b, a);
|
result = style::main_palette::setColor(name, r, g, b, a);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (out) {
|
if (out) {
|
||||||
found = out->palette.setColor(name, value);
|
result = out->palette.setColor(name, value);
|
||||||
} else {
|
} else {
|
||||||
found = style::main_palette::setColor(name, value);
|
result = style::main_palette::setColor(name, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return found ? SetResult::Ok : SetResult::NotFound;
|
if (result == style::palette::SetResult::Ok) {
|
||||||
|
return SetResult::Ok;
|
||||||
|
} else if (result == style::palette::SetResult::KeyNotFound) {
|
||||||
|
return SetResult::NotFound;
|
||||||
|
} else if (result == style::palette::SetResult::ValueNotFound) {
|
||||||
|
LOG(("Theme Warning: Skipping value '%1: %2' (expected a color value in #rrggbb or #rrggbbaa or a previously defined key in the color scheme)").arg(name).arg(value));
|
||||||
|
return SetResult::Ok;
|
||||||
|
} else if (result == style::palette::SetResult::Duplicate) {
|
||||||
|
LOG(("Theme Warning: Color value appears more than once in the color scheme (while applying '%1: %2')").arg(name).arg(value));
|
||||||
|
return SetResult::Ok;
|
||||||
|
} else {
|
||||||
|
LOG(("Theme Error: Unexpected internal error."));
|
||||||
|
}
|
||||||
|
return SetResult::Bad;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool loadColorScheme(const QByteArray &content, Instance *out = nullptr) {
|
bool loadColorScheme(const QByteArray &content, Instance *out) {
|
||||||
if (content.size() > kThemeSchemeSizeLimit) {
|
auto unsupported = QMap<QLatin1String, QLatin1String>();
|
||||||
LOG(("Theme Error: color scheme file too large (should be less than 1 MB, got %2)").arg(content.size()));
|
return ReadPaletteValues(content, [&unsupported, out](QLatin1String name, QLatin1String value) {
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QMap<QLatin1String, QLatin1String> unsupported;
|
|
||||||
auto data = base::parse::stripComments(content);
|
|
||||||
auto from = data.constData(), end = from + data.size();
|
|
||||||
while (from != end) {
|
|
||||||
QLatin1String name(""), value("");
|
|
||||||
if (!readNameAndValue(from, end, &name, &value)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (name.size() == 0) { // End of content reached.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the named value in the already read unsupported list.
|
// Find the named value in the already read unsupported list.
|
||||||
value = unsupported.value(value, value);
|
value = unsupported.value(value, value);
|
||||||
|
|
||||||
|
@ -188,11 +194,10 @@ bool loadColorScheme(const QByteArray &content, Instance *out = nullptr) {
|
||||||
if (result == SetResult::Bad) {
|
if (result == SetResult::Bad) {
|
||||||
return false;
|
return false;
|
||||||
} else if (result == SetResult::NotFound) {
|
} else if (result == SetResult::NotFound) {
|
||||||
LOG(("Theme Warning: unexpected name or value in the color scheme (while applying '%1: %2')").arg(name).arg(value));
|
|
||||||
unsupported.insert(name, value);
|
unsupported.insert(name, value);
|
||||||
}
|
}
|
||||||
}
|
return true;
|
||||||
return true;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void applyBackground(QImage &&background, bool tiled, Instance *out) {
|
void applyBackground(QImage &&background, bool tiled, Instance *out) {
|
||||||
|
@ -276,8 +281,12 @@ bool loadTheme(const QByteArray &content, Cached &cache, Instance *out = nullptr
|
||||||
file.getGlobalInfo(&globalInfo);
|
file.getGlobalInfo(&globalInfo);
|
||||||
if (file.error() == UNZ_OK) {
|
if (file.error() == UNZ_OK) {
|
||||||
auto schemeContent = file.readFileContent("colors.tdesktop-theme", zlib::kCaseInsensitive, kThemeSchemeSizeLimit);
|
auto schemeContent = file.readFileContent("colors.tdesktop-theme", zlib::kCaseInsensitive, kThemeSchemeSizeLimit);
|
||||||
|
if (file.error() == UNZ_END_OF_LIST_OF_FILE) {
|
||||||
|
file.clearError();
|
||||||
|
schemeContent = file.readFileContent("colors.tdesktop-palette", zlib::kCaseInsensitive, kThemeSchemeSizeLimit);
|
||||||
|
}
|
||||||
if (file.error() != UNZ_OK) {
|
if (file.error() != UNZ_OK) {
|
||||||
LOG(("Theme Error: could not read 'colors.tdesktop-theme' in the theme file."));
|
LOG(("Theme Error: could not read 'colors.tdesktop-theme' or 'colors.tdesktop-palette' in the theme file."));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!loadColorScheme(schemeContent, out)) {
|
if (!loadColorScheme(schemeContent, out)) {
|
||||||
|
@ -330,13 +339,13 @@ QImage prepareBackgroundImage(QImage &&image) {
|
||||||
return std_::move(image);
|
return std_::move(image);
|
||||||
}
|
}
|
||||||
|
|
||||||
void initColor(style::color color, float64 hue, float64 saturation) {
|
void adjustColor(style::color color, float64 hue, float64 saturation) {
|
||||||
auto original = color->c;
|
auto original = color->c;
|
||||||
original.setHslF(hue, saturation, original.lightnessF(), original.alphaF());
|
original.setHslF(hue, saturation, original.lightnessF(), original.alphaF());
|
||||||
color.set(original.red(), original.green(), original.blue(), original.alpha());
|
color.set(original.red(), original.green(), original.blue(), original.alpha());
|
||||||
}
|
}
|
||||||
|
|
||||||
void initColorsFromBackground(const QImage &img) {
|
void adjustColorsUsingBackground(const QImage &img) {
|
||||||
t_assert(img.format() == QImage::Format_ARGB32_Premultiplied);
|
t_assert(img.format() == QImage::Format_ARGB32_Premultiplied);
|
||||||
|
|
||||||
uint64 components[3] = { 0 };
|
uint64 components[3] = { 0 };
|
||||||
|
@ -361,12 +370,12 @@ void initColorsFromBackground(const QImage &img) {
|
||||||
auto bgColor = QColor(components[0], components[1], components[2]);
|
auto bgColor = QColor(components[0], components[1], components[2]);
|
||||||
auto hue = bgColor.hslHueF();
|
auto hue = bgColor.hslHueF();
|
||||||
auto saturation = bgColor.hslSaturationF();
|
auto saturation = bgColor.hslSaturationF();
|
||||||
initColor(st::msgServiceBg, hue, saturation);
|
adjustColor(st::msgServiceBg, hue, saturation);
|
||||||
initColor(st::msgServiceBgSelected, hue, saturation);
|
adjustColor(st::msgServiceBgSelected, hue, saturation);
|
||||||
initColor(st::historyScroll.bg, hue, saturation);
|
adjustColor(st::historyScroll.bg, hue, saturation);
|
||||||
initColor(st::historyScroll.bgOver, hue, saturation);
|
adjustColor(st::historyScroll.bgOver, hue, saturation);
|
||||||
initColor(st::historyScroll.barBg, hue, saturation);
|
adjustColor(st::historyScroll.barBg, hue, saturation);
|
||||||
initColor(st::historyScroll.barBgOver, hue, saturation);
|
adjustColor(st::historyScroll.barBgOver, hue, saturation);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
@ -392,7 +401,9 @@ void ChatBackground::setImage(int32 id, QImage &&image) {
|
||||||
if (_id == kThemeBackground) {
|
if (_id == kThemeBackground) {
|
||||||
_tile = _themeTile;
|
_tile = _themeTile;
|
||||||
setPreparedImage(QImage(_themeImage));
|
setPreparedImage(QImage(_themeImage));
|
||||||
} else if (_id == internal::kTestingThemeBackground || _id == internal::kTestingDefaultBackground) {
|
} else if (_id == internal::kTestingThemeBackground
|
||||||
|
|| _id == internal::kTestingDefaultBackground
|
||||||
|
|| _id == internal::kTestingEditorBackground) {
|
||||||
if (_id == internal::kTestingDefaultBackground || image.isNull()) {
|
if (_id == internal::kTestingDefaultBackground || image.isNull()) {
|
||||||
image.load(qsl(":/gui/art/bg.jpg"));
|
image.load(qsl(":/gui/art/bg.jpg"));
|
||||||
_id = internal::kTestingDefaultBackground;
|
_id = internal::kTestingDefaultBackground;
|
||||||
|
@ -420,14 +431,35 @@ void ChatBackground::setImage(int32 id, QImage &&image) {
|
||||||
void ChatBackground::setPreparedImage(QImage &&image) {
|
void ChatBackground::setPreparedImage(QImage &&image) {
|
||||||
image = std_::move(image).convertToFormat(QImage::Format_ARGB32_Premultiplied);
|
image = std_::move(image).convertToFormat(QImage::Format_ARGB32_Premultiplied);
|
||||||
image.setDevicePixelRatio(cRetinaFactor());
|
image.setDevicePixelRatio(cRetinaFactor());
|
||||||
if (_id != kThemeBackground && _id != internal::kTestingThemeBackground) {
|
|
||||||
auto colorsFromSomeTheme = Local::hasTheme();
|
auto adjustColors = [this] {
|
||||||
if (instance && !instance->applying.paletteForRevert.isEmpty()) {
|
auto someCustomThemeApplied = [] {
|
||||||
colorsFromSomeTheme = !instance->applying.path.isEmpty();
|
if (AreTestingTheme()) {
|
||||||
}
|
return !instance->applying.path.isEmpty();
|
||||||
if (colorsFromSomeTheme || (_id != kDefaultBackground && _id != internal::kTestingDefaultBackground)) {
|
}
|
||||||
initColorsFromBackground(image);
|
return Local::hasTheme();
|
||||||
|
};
|
||||||
|
auto usingThemeBackground = [this] {
|
||||||
|
return (_id == kThemeBackground || _id == internal::kTestingThemeBackground);
|
||||||
|
};
|
||||||
|
auto usingDefaultBackground = [this] {
|
||||||
|
return (_id == kDefaultBackground || _id == internal::kTestingDefaultBackground);
|
||||||
|
};
|
||||||
|
auto testingPalette = [] {
|
||||||
|
if (AreTestingTheme()) {
|
||||||
|
return IsPaletteTestingPath(instance->applying.path);
|
||||||
|
}
|
||||||
|
return !Local::themePaletteAbsolutePath().isEmpty();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (someCustomThemeApplied()) {
|
||||||
|
return !usingThemeBackground() && !testingPalette();
|
||||||
}
|
}
|
||||||
|
return !usingDefaultBackground();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (adjustColors()) {
|
||||||
|
adjustColorsUsingBackground(image);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto width = image.width();
|
auto width = image.width();
|
||||||
|
@ -522,7 +554,13 @@ void ChatBackground::saveForRevert() {
|
||||||
|
|
||||||
void ChatBackground::setTestingTheme(Instance &&theme) {
|
void ChatBackground::setTestingTheme(Instance &&theme) {
|
||||||
style::main_palette::apply(theme.palette);
|
style::main_palette::apply(theme.palette);
|
||||||
if (!theme.background.isNull() || _id == kThemeBackground) {
|
if (AreTestingTheme() && IsPaletteTestingPath(instance->applying.path)) {
|
||||||
|
// Grab current background image if it is not already custom
|
||||||
|
if (_id != kCustomBackground) {
|
||||||
|
saveForRevert();
|
||||||
|
setImage(internal::kTestingEditorBackground, std_::move(_pixmap).toImage());
|
||||||
|
}
|
||||||
|
} else if (!theme.background.isNull() || _id == kThemeBackground) {
|
||||||
saveForRevert();
|
saveForRevert();
|
||||||
setImage(internal::kTestingThemeBackground, std_::move(theme.background));
|
setImage(internal::kTestingThemeBackground, std_::move(theme.background));
|
||||||
setTile(theme.tiled);
|
setTile(theme.tiled);
|
||||||
|
@ -547,7 +585,12 @@ void ChatBackground::setTestingDefaultTheme() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatBackground::keepApplied() {
|
void ChatBackground::keepApplied() {
|
||||||
if (_id == internal::kTestingThemeBackground) {
|
if (_id == internal::kTestingEditorBackground) {
|
||||||
|
_id = kCustomBackground;
|
||||||
|
_themeImage = QImage();
|
||||||
|
_themeTile = false;
|
||||||
|
writeNewBackgroundSettings();
|
||||||
|
} else if (_id == internal::kTestingThemeBackground) {
|
||||||
_id = kThemeBackground;
|
_id = kThemeBackground;
|
||||||
_themeImage = _pixmap.toImage();
|
_themeImage = _pixmap.toImage();
|
||||||
_themeTile = _tile;
|
_themeTile = _tile;
|
||||||
|
@ -565,11 +608,13 @@ void ChatBackground::writeNewBackgroundSettings() {
|
||||||
if (_tile != _tileForRevert) {
|
if (_tile != _tileForRevert) {
|
||||||
Local::writeUserSettings();
|
Local::writeUserSettings();
|
||||||
}
|
}
|
||||||
Local::writeBackground(_id, QImage());
|
Local::writeBackground(_id, (_id == kThemeBackground || _id == kDefaultBackground) ? QImage() : _pixmap.toImage());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatBackground::revert() {
|
void ChatBackground::revert() {
|
||||||
if (_id == internal::kTestingThemeBackground || _id == internal::kTestingDefaultBackground) {
|
if (_id == internal::kTestingThemeBackground
|
||||||
|
|| _id == internal::kTestingDefaultBackground
|
||||||
|
|| _id == internal::kTestingEditorBackground) {
|
||||||
setTile(_tileForRevert);
|
setTile(_tileForRevert);
|
||||||
setImage(_idForRevert, std_::move(_imageForRevert));
|
setImage(_idForRevert, std_::move(_imageForRevert));
|
||||||
} else {
|
} else {
|
||||||
|
@ -639,6 +684,27 @@ void ApplyDefault() {
|
||||||
Background()->setTestingDefaultTheme();
|
Background()->setTestingDefaultTheme();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ApplyEditedPalette(const QString &path, const QByteArray &content) {
|
||||||
|
Instance out;
|
||||||
|
if (!loadColorScheme(content, &out)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.cached.colors = out.palette.save();
|
||||||
|
out.cached.paletteChecksum = style::palette::Checksum();
|
||||||
|
out.cached.contentChecksum = hashCrc32(content.constData(), content.size());
|
||||||
|
|
||||||
|
instance.createIfNull();
|
||||||
|
instance->applying.path = path;
|
||||||
|
instance->applying.content = content;
|
||||||
|
instance->applying.cached = out.cached;
|
||||||
|
if (instance->applying.paletteForRevert.isEmpty()) {
|
||||||
|
instance->applying.paletteForRevert = style::main_palette::save();
|
||||||
|
}
|
||||||
|
Background()->setTestingTheme(std_::move(out));
|
||||||
|
KeepApplied();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void KeepApplied() {
|
void KeepApplied() {
|
||||||
if (!instance) {
|
if (!instance) {
|
||||||
return;
|
return;
|
||||||
|
@ -669,6 +735,13 @@ bool LoadFromFile(const QString &path, Instance *out, QByteArray *outContent) {
|
||||||
return loadTheme(*outContent, out->cached, out);
|
return loadTheme(*outContent, out->cached, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsPaletteTestingPath(const QString &path) {
|
||||||
|
if (path.endsWith(qstr(".tdesktop-palette"), Qt::CaseInsensitive)) {
|
||||||
|
return QFileInfo(path).exists();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void ComputeBackgroundRects(QRect wholeFill, QSize imageSize, QRect &to, QRect &from) {
|
void ComputeBackgroundRects(QRect wholeFill, QSize imageSize, QRect &to, QRect &from) {
|
||||||
if (uint64(imageSize.width()) * wholeFill.height() > uint64(imageSize.height()) * wholeFill.width()) {
|
if (uint64(imageSize.width()) * wholeFill.height() > uint64(imageSize.height()) * wholeFill.width()) {
|
||||||
float64 pxsize = wholeFill.height() / float64(imageSize.height());
|
float64 pxsize = wholeFill.height() / float64(imageSize.height());
|
||||||
|
@ -693,5 +766,61 @@ void ComputeBackgroundRects(QRect wholeFill, QSize imageSize, QRect &to, QRect &
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CopyColorsToPalette(const QString &path, const QByteArray &themeContent) {
|
||||||
|
auto paletteContent = themeContent;
|
||||||
|
|
||||||
|
zlib::FileToRead file(themeContent);
|
||||||
|
|
||||||
|
unz_global_info globalInfo = { 0 };
|
||||||
|
file.getGlobalInfo(&globalInfo);
|
||||||
|
if (file.error() == UNZ_OK) {
|
||||||
|
paletteContent = file.readFileContent("colors.tdesktop-theme", zlib::kCaseInsensitive, kThemeSchemeSizeLimit);
|
||||||
|
if (file.error() == UNZ_END_OF_LIST_OF_FILE) {
|
||||||
|
file.clearError();
|
||||||
|
paletteContent = file.readFileContent("colors.tdesktop-palette", zlib::kCaseInsensitive, kThemeSchemeSizeLimit);
|
||||||
|
}
|
||||||
|
if (file.error() != UNZ_OK) {
|
||||||
|
LOG(("Theme Error: could not read 'colors.tdesktop-theme' or 'colors.tdesktop-palette' in the theme file, while copying to '%1'.").arg(path));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QFile f(path);
|
||||||
|
if (!f.open(QIODevice::WriteOnly)) {
|
||||||
|
LOG(("Theme Error: could not open file for write '%1'").arg(path));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (f.write(paletteContent) != paletteContent.size()) {
|
||||||
|
LOG(("Theme Error: could not write palette to '%1'").arg(path));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ReadPaletteValues(const QByteArray &content, base::lambda<bool(QLatin1String name, QLatin1String value)> &&callback) {
|
||||||
|
if (content.size() > kThemeSchemeSizeLimit) {
|
||||||
|
LOG(("Theme Error: color scheme file too large (should be less than 1 MB, got %2)").arg(content.size()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto data = base::parse::stripComments(content);
|
||||||
|
auto from = data.constData(), end = from + data.size();
|
||||||
|
while (from != end) {
|
||||||
|
auto name = QLatin1String("");
|
||||||
|
auto value = QLatin1String("");
|
||||||
|
if (!readNameAndValue(from, end, &name, &value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (name.size() == 0) { // End of content reached.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!callback(name, value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Theme
|
} // namespace Theme
|
||||||
} // namespace Window
|
} // namespace Window
|
|
@ -27,6 +27,7 @@ namespace internal {
|
||||||
constexpr int32 kUninitializedBackground = -999;
|
constexpr int32 kUninitializedBackground = -999;
|
||||||
constexpr int32 kTestingThemeBackground = -666;
|
constexpr int32 kTestingThemeBackground = -666;
|
||||||
constexpr int32 kTestingDefaultBackground = -665;
|
constexpr int32 kTestingDefaultBackground = -665;
|
||||||
|
constexpr int32 kTestingEditorBackground = -664;
|
||||||
|
|
||||||
} // namespace internal
|
} // namespace internal
|
||||||
|
|
||||||
|
@ -62,10 +63,12 @@ struct Preview {
|
||||||
bool Apply(const QString &filepath);
|
bool Apply(const QString &filepath);
|
||||||
bool Apply(std_::unique_ptr<Preview> preview);
|
bool Apply(std_::unique_ptr<Preview> preview);
|
||||||
void ApplyDefault();
|
void ApplyDefault();
|
||||||
|
bool ApplyEditedPalette(const QString &path, const QByteArray &content);
|
||||||
void KeepApplied();
|
void KeepApplied();
|
||||||
void Revert();
|
void Revert();
|
||||||
|
|
||||||
bool LoadFromFile(const QString &file, Instance *out, QByteArray *outContent);
|
bool LoadFromFile(const QString &file, Instance *out, QByteArray *outContent);
|
||||||
|
bool IsPaletteTestingPath(const QString &path);
|
||||||
|
|
||||||
struct BackgroundUpdate {
|
struct BackgroundUpdate {
|
||||||
enum class Type {
|
enum class Type {
|
||||||
|
@ -136,5 +139,9 @@ ChatBackground *Background();
|
||||||
|
|
||||||
void ComputeBackgroundRects(QRect wholeFill, QSize imageSize, QRect &to, QRect &from);
|
void ComputeBackgroundRects(QRect wholeFill, QSize imageSize, QRect &to, QRect &from);
|
||||||
|
|
||||||
|
bool CopyColorsToPalette(const QString &path, const QByteArray &themeContent);
|
||||||
|
|
||||||
|
bool ReadPaletteValues(const QByteArray &content, base::lambda<bool(QLatin1String name, QLatin1String value)> &&callback);
|
||||||
|
|
||||||
} // namespace Theme
|
} // namespace Theme
|
||||||
} // namespace Window
|
} // namespace Window
|
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
|
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
*/
|
*/
|
||||||
#include "stdafx.h"
|
#include "stdafx.h"
|
||||||
#include "window/window_theme_preview.h"
|
#include "window/themes/window_theme_preview.h"
|
||||||
|
|
||||||
#include "window/window_theme.h"
|
#include "window/themes/window_theme.h"
|
||||||
#include "lang.h"
|
#include "lang.h"
|
||||||
#include "platform/platform_window_title.h"
|
#include "platform/platform_window_title.h"
|
||||||
#include "styles/style_widgets.h"
|
#include "styles/style_widgets.h"
|
|
@ -20,7 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "window/window_theme.h"
|
#include "window/themes/window_theme.h"
|
||||||
|
|
||||||
namespace Window {
|
namespace Window {
|
||||||
namespace Theme {
|
namespace Theme {
|
|
@ -19,12 +19,12 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||||
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
*/
|
*/
|
||||||
#include "stdafx.h"
|
#include "stdafx.h"
|
||||||
#include "window/window_theme_warning.h"
|
#include "window/themes/window_theme_warning.h"
|
||||||
|
|
||||||
#include "styles/style_boxes.h"
|
#include "styles/style_boxes.h"
|
||||||
#include "ui/widgets/buttons.h"
|
#include "ui/widgets/buttons.h"
|
||||||
#include "ui/widgets/shadow.h"
|
#include "ui/widgets/shadow.h"
|
||||||
#include "window/window_theme.h"
|
#include "window/themes/window_theme.h"
|
||||||
#include "lang.h"
|
#include "lang.h"
|
||||||
|
|
||||||
namespace Window {
|
namespace Window {
|
|
@ -267,6 +267,12 @@ topBarInfoButton: PeerAvatarButton {
|
||||||
}
|
}
|
||||||
topBarSlideDuration: 200;
|
topBarSlideDuration: 200;
|
||||||
|
|
||||||
|
themeEditorSampleSize: size(90px, 51px);
|
||||||
|
themeEditorMargin: margins(17px, 10px, 17px, 10px);
|
||||||
|
themeEditorDescriptionSkip: 10px;
|
||||||
|
themeEditorNameFont: font(15px semibold);
|
||||||
|
themeEditorCopyNameFont: font(fsize semibold);
|
||||||
|
|
||||||
// Mac specific
|
// Mac specific
|
||||||
|
|
||||||
macAccessoryWidth: 450.;
|
macAccessoryWidth: 450.;
|
||||||
|
|
|
@ -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
|
call :repl "Replace=("ProductVersion",) (\s*)"\d+.\d+.\d+.\d+"/$1$2 "%VersionMajor%.%VersionMinor%.%VersionPatch%.%VersionBeta%"" "Filename=%ResourcePath%" || goto :error
|
||||||
|
|
||||||
echo Patching appxmanifest.xml...
|
echo Patching appxmanifest.xml...
|
||||||
set "ResourcePath=%FullScriptPath%..\Resources\uwp\appxmanifest.xml"
|
set "ResourcePath=%FullScriptPath%..\Resources\uwp\AppX\AppxManifest.xml"
|
||||||
call :repl "Replace= (Version=)"\d+.\d+.\d+.\d+"/ $1"%VersionMajor%.%VersionMinor%.%VersionPatch%.%VersionBeta%"" "Filename=%ResourcePath%" || goto :error
|
call :repl "Replace= (Version=)"\d+.\d+.\d+.\d+"/ $1"%VersionMajor%.%VersionMinor%.%VersionPatch%.%VersionBeta%"" "Filename=%ResourcePath%" || goto :error
|
||||||
|
|
||||||
exit /b
|
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 "\(\"FileVersion\",\) \([ ]*\)\"[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\"" "\1\2 \"$VersionMajor.$VersionMinor.$VersionPatch.$VersionBeta\"" "$ResourcePath"
|
||||||
repl "\(\"ProductVersion\",\) \([ ]*\)\"[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\"" "\1\2 \"$VersionMajor.$VersionMinor.$VersionPatch.$VersionBeta\"" "$ResourcePath"
|
repl "\(\"ProductVersion\",\) \([ ]*\)\"[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\"" "\1\2 \"$VersionMajor.$VersionMinor.$VersionPatch.$VersionBeta\"" "$ResourcePath"
|
||||||
|
|
||||||
|
echo "Patching appxmanifest.xml..."
|
||||||
|
ResourcePath="$FullScriptPath/../Resources/uwp/AppX/AppxManifest.xml"
|
||||||
|
repl " \(Version=\)\"[0-9][0-9]*.[0-9][0-9]*.[0-9][0-9]*.[0-9][0-9]*\"" " \1\"$VersionMajor.$VersionMinor.$VersionPatch.$VersionBeta\"" "$ResourcePath"
|
||||||
|
|
|
@ -3,4 +3,4 @@ AppVersionStrMajor 1.0
|
||||||
AppVersionStrSmall 1.0.6
|
AppVersionStrSmall 1.0.6
|
||||||
AppVersionStr 1.0.6
|
AppVersionStr 1.0.6
|
||||||
AlphaChannel 0
|
AlphaChannel 0
|
||||||
BetaVersion 0
|
BetaVersion 1000006001
|
||||||
|
|
|
@ -4,10 +4,7 @@ set "FullScriptPath=%~dp0"
|
||||||
set "FullExecPath=%cd%"
|
set "FullExecPath=%cd%"
|
||||||
|
|
||||||
set "Command=%1"
|
set "Command=%1"
|
||||||
if "%Command%" == "module" (
|
if "%Command%" == "header" (
|
||||||
call :write_module %2
|
|
||||||
exit /b %errorlevel%
|
|
||||||
) else if "%Command%" == "header" (
|
|
||||||
call :write_header %2
|
call :write_header %2
|
||||||
exit /b %errorlevel%
|
exit /b %errorlevel%
|
||||||
) else if "%Command%" == "source" (
|
) else if "%Command%" == "source" (
|
||||||
|
@ -15,11 +12,8 @@ if "%Command%" == "module" (
|
||||||
exit /b %errorlevel%
|
exit /b %errorlevel%
|
||||||
)
|
)
|
||||||
|
|
||||||
cd gyp
|
call :write_module %Command%
|
||||||
call refresh.bat
|
exit /b %errorlevel%
|
||||||
cd ..
|
|
||||||
|
|
||||||
exit /b
|
|
||||||
|
|
||||||
:write_module
|
:write_module
|
||||||
(
|
(
|
||||||
|
@ -30,8 +24,8 @@ exit /b
|
||||||
exit /b 1
|
exit /b 1
|
||||||
)
|
)
|
||||||
echo Generating module !CommandPathUnix!..
|
echo Generating module !CommandPathUnix!..
|
||||||
call prepare.bat header !CommandPathUnix!
|
call create.bat header !CommandPathUnix!
|
||||||
call prepare.bat source !CommandPathUnix!
|
call create.bat source !CommandPathUnix!
|
||||||
exit /b
|
exit /b
|
||||||
)
|
)
|
||||||
|
|
|
@ -173,6 +173,8 @@
|
||||||
'<(src_loc)/boxes/contactsbox.h',
|
'<(src_loc)/boxes/contactsbox.h',
|
||||||
'<(src_loc)/boxes/downloadpathbox.cpp',
|
'<(src_loc)/boxes/downloadpathbox.cpp',
|
||||||
'<(src_loc)/boxes/downloadpathbox.h',
|
'<(src_loc)/boxes/downloadpathbox.h',
|
||||||
|
'<(src_loc)/boxes/editcolorbox.cpp',
|
||||||
|
'<(src_loc)/boxes/editcolorbox.h',
|
||||||
'<(src_loc)/boxes/emojibox.cpp',
|
'<(src_loc)/boxes/emojibox.cpp',
|
||||||
'<(src_loc)/boxes/emojibox.h',
|
'<(src_loc)/boxes/emojibox.h',
|
||||||
'<(src_loc)/boxes/languagebox.cpp',
|
'<(src_loc)/boxes/languagebox.cpp',
|
||||||
|
@ -457,6 +459,8 @@
|
||||||
'<(src_loc)/settings/settings_info_widget.h',
|
'<(src_loc)/settings/settings_info_widget.h',
|
||||||
'<(src_loc)/settings/settings_inner_widget.cpp',
|
'<(src_loc)/settings/settings_inner_widget.cpp',
|
||||||
'<(src_loc)/settings/settings_inner_widget.h',
|
'<(src_loc)/settings/settings_inner_widget.h',
|
||||||
|
'<(src_loc)/settings/settings_layer.cpp',
|
||||||
|
'<(src_loc)/settings/settings_layer.h',
|
||||||
'<(src_loc)/settings/settings_notifications_widget.cpp',
|
'<(src_loc)/settings/settings_notifications_widget.cpp',
|
||||||
'<(src_loc)/settings/settings_notifications_widget.h',
|
'<(src_loc)/settings/settings_notifications_widget.h',
|
||||||
'<(src_loc)/settings/settings_privacy_widget.cpp',
|
'<(src_loc)/settings/settings_privacy_widget.cpp',
|
||||||
|
@ -574,12 +578,16 @@
|
||||||
'<(src_loc)/window/top_bar_widget.h',
|
'<(src_loc)/window/top_bar_widget.h',
|
||||||
'<(src_loc)/window/window_main_menu.cpp',
|
'<(src_loc)/window/window_main_menu.cpp',
|
||||||
'<(src_loc)/window/window_main_menu.h',
|
'<(src_loc)/window/window_main_menu.h',
|
||||||
'<(src_loc)/window/window_theme.cpp',
|
'<(src_loc)/window/themes/window_theme.cpp',
|
||||||
'<(src_loc)/window/window_theme.h',
|
'<(src_loc)/window/themes/window_theme.h',
|
||||||
'<(src_loc)/window/window_theme_preview.cpp',
|
'<(src_loc)/window/themes/window_theme_editor.cpp',
|
||||||
'<(src_loc)/window/window_theme_preview.h',
|
'<(src_loc)/window/themes/window_theme_editor.h',
|
||||||
'<(src_loc)/window/window_theme_warning.cpp',
|
'<(src_loc)/window/themes/window_theme_editor_block.cpp',
|
||||||
'<(src_loc)/window/window_theme_warning.h',
|
'<(src_loc)/window/themes/window_theme_editor_block.h',
|
||||||
|
'<(src_loc)/window/themes/window_theme_preview.cpp',
|
||||||
|
'<(src_loc)/window/themes/window_theme_preview.h',
|
||||||
|
'<(src_loc)/window/themes/window_theme_warning.cpp',
|
||||||
|
'<(src_loc)/window/themes/window_theme_warning.h',
|
||||||
'<(src_loc)/window/window_title.h',
|
'<(src_loc)/window/window_title.h',
|
||||||
|
|
||||||
'<(sp_media_key_tap_loc)/SPMediaKeyTap.m',
|
'<(sp_media_key_tap_loc)/SPMediaKeyTap.m',
|
||||||
|
|
Loading…
Add table
Reference in a new issue