Updated lib_ui sources to TDesktop version 3.4.3
This commit is contained in:
commit
039cbd656d
24 changed files with 867 additions and 178 deletions
|
|
@ -230,6 +230,8 @@ PRIVATE
|
||||||
ui/rect_part.h
|
ui/rect_part.h
|
||||||
ui/round_rect.cpp
|
ui/round_rect.cpp
|
||||||
ui/round_rect.h
|
ui/round_rect.h
|
||||||
|
ui/spoiler_click_handler.cpp
|
||||||
|
ui/spoiler_click_handler.h
|
||||||
ui/rp_widget.cpp
|
ui/rp_widget.cpp
|
||||||
ui/rp_widget.h
|
ui/rp_widget.h
|
||||||
ui/ui_utility.cpp
|
ui/ui_utility.cpp
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,9 @@ TextPalette {
|
||||||
selectMonoFg: color;
|
selectMonoFg: color;
|
||||||
selectOverlay: color;
|
selectOverlay: color;
|
||||||
linkAlwaysActive: int;
|
linkAlwaysActive: int;
|
||||||
|
spoilerBg: color;
|
||||||
|
spoilerActiveBg: color;
|
||||||
|
spoilerActiveFg: color;
|
||||||
}
|
}
|
||||||
|
|
||||||
TextStyle {
|
TextStyle {
|
||||||
|
|
@ -44,6 +47,9 @@ defaultTextPalette: TextPalette {
|
||||||
selectLinkFg: historyLinkInFgSelected;
|
selectLinkFg: historyLinkInFgSelected;
|
||||||
selectMonoFg: msgInMonoFgSelected;
|
selectMonoFg: msgInMonoFgSelected;
|
||||||
selectOverlay: msgSelectOverlay;
|
selectOverlay: msgSelectOverlay;
|
||||||
|
spoilerBg: msgInDateFg;
|
||||||
|
spoilerActiveBg: msgInDateFg;
|
||||||
|
spoilerActiveFg: msgInBg;
|
||||||
}
|
}
|
||||||
defaultTextStyle: TextStyle {
|
defaultTextStyle: TextStyle {
|
||||||
font: normalFont;
|
font: normalFont;
|
||||||
|
|
@ -139,6 +145,9 @@ inTextPalette: TextPalette(defaultTextPalette) {
|
||||||
selectLinkFg: historyLinkInFgSelected;
|
selectLinkFg: historyLinkInFgSelected;
|
||||||
selectMonoFg: msgInMonoFgSelected;
|
selectMonoFg: msgInMonoFgSelected;
|
||||||
selectOverlay: msgSelectOverlay;
|
selectOverlay: msgSelectOverlay;
|
||||||
|
spoilerBg: msgInDateFg;
|
||||||
|
spoilerActiveBg: msgInDateFg;
|
||||||
|
spoilerActiveFg: msgInBg;
|
||||||
}
|
}
|
||||||
inTextPaletteSelected: TextPalette(inTextPalette) {
|
inTextPaletteSelected: TextPalette(inTextPalette) {
|
||||||
linkFg: historyLinkInFgSelected;
|
linkFg: historyLinkInFgSelected;
|
||||||
|
|
@ -152,6 +161,9 @@ outTextPalette: TextPalette(defaultTextPalette) {
|
||||||
selectLinkFg: historyLinkOutFgSelected;
|
selectLinkFg: historyLinkOutFgSelected;
|
||||||
selectMonoFg: msgOutMonoFgSelected;
|
selectMonoFg: msgOutMonoFgSelected;
|
||||||
selectOverlay: msgSelectOverlay;
|
selectOverlay: msgSelectOverlay;
|
||||||
|
spoilerBg: msgOutDateFg;
|
||||||
|
spoilerActiveBg: msgOutDateFg;
|
||||||
|
spoilerActiveFg: msgOutBg;
|
||||||
}
|
}
|
||||||
outTextPaletteSelected: TextPalette(outTextPalette) {
|
outTextPaletteSelected: TextPalette(outTextPalette) {
|
||||||
linkFg: historyLinkOutFgSelected;
|
linkFg: historyLinkOutFgSelected;
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ menuBgOver: windowBgOver; // default popup menu item background with mouse over
|
||||||
menuBgRipple: windowBgRipple; // default popup menu item ripple effect
|
menuBgRipple: windowBgRipple; // default popup menu item ripple effect
|
||||||
menuIconFg: #a8a8a8; // default popup menu item icon (like main menu)
|
menuIconFg: #a8a8a8; // default popup menu item icon (like main menu)
|
||||||
menuIconFgOver: #999999; // default popup menu item icon with mouse over
|
menuIconFgOver: #999999; // default popup menu item icon with mouse over
|
||||||
menuSubmenuArrowFg: #373737; // default popup menu submenu arrow icon (like in message field context menu in case of RTL system language)
|
menuSubmenuArrowFg: #666B72; // default popup menu submenu arrow icon (like in message field context menu in case of RTL system language)
|
||||||
menuFgDisabled: #cccccc; // default popup menu item disabled text (like unavailable items in message field context menu)
|
menuFgDisabled: #cccccc; // default popup menu item disabled text (like unavailable items in message field context menu)
|
||||||
menuSeparatorFg: #f1f1f1; // default popup menu separator (like in message field context menu)
|
menuSeparatorFg: #f1f1f1; // default popup menu separator (like in message field context menu)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -144,4 +144,8 @@ QString Integration::phraseFormattingMonospace() {
|
||||||
return "Monospace";
|
return "Monospace";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString Integration::phraseFormattingSpoiler() {
|
||||||
|
return "Spoiler";
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,7 @@ public:
|
||||||
[[nodiscard]] virtual QString phraseFormattingUnderline();
|
[[nodiscard]] virtual QString phraseFormattingUnderline();
|
||||||
[[nodiscard]] virtual QString phraseFormattingStrikeOut();
|
[[nodiscard]] virtual QString phraseFormattingStrikeOut();
|
||||||
[[nodiscard]] virtual QString phraseFormattingMonospace();
|
[[nodiscard]] virtual QString phraseFormattingMonospace();
|
||||||
|
[[nodiscard]] virtual QString phraseFormattingSpoiler();
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,8 @@
|
||||||
#include "ui/platform/linux/ui_utility_linux.h"
|
#include "ui/platform/linux/ui_utility_linux.h"
|
||||||
|
|
||||||
#include "base/platform/base_platform_info.h"
|
#include "base/platform/base_platform_info.h"
|
||||||
#include "base/debug_log.h"
|
|
||||||
#include "ui/platform/linux/ui_linux_wayland_integration.h"
|
#include "ui/platform/linux/ui_linux_wayland_integration.h"
|
||||||
#include "base/const_string.h"
|
#include "base/const_string.h"
|
||||||
#include "base/flat_set.h"
|
|
||||||
|
|
||||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||||
#include "base/platform/linux/base_linux_glibmm_helper.h"
|
#include "base/platform/linux/base_linux_glibmm_helper.h"
|
||||||
|
|
@ -23,10 +21,8 @@
|
||||||
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
|
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
|
||||||
|
|
||||||
#include <QtCore/QPoint>
|
#include <QtCore/QPoint>
|
||||||
#include <QtGui/QScreen>
|
|
||||||
#include <QtGui/QWindow>
|
#include <QtGui/QWindow>
|
||||||
#include <QtWidgets/QApplication>
|
#include <QtWidgets/QApplication>
|
||||||
#include <qpa/qplatformnativeinterface.h>
|
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
namespace Platform {
|
namespace Platform {
|
||||||
|
|
@ -132,7 +128,7 @@ std::optional<uint> XCBCurrentWorkspace() {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto root = base::Platform::XCB::GetRootWindowFromQt();
|
const auto root = base::Platform::XCB::GetRootWindow(connection);
|
||||||
if (!root.has_value()) {
|
if (!root.has_value()) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
@ -220,7 +216,7 @@ std::optional<bool> XCBIsOverlapped(
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto root = base::Platform::XCB::GetRootWindowFromQt();
|
const auto root = base::Platform::XCB::GetRootWindow(connection);
|
||||||
if (!root.has_value()) {
|
if (!root.has_value()) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
@ -345,7 +341,7 @@ bool ShowXCBWindowMenu(QWindow *window) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto root = base::Platform::XCB::GetRootWindowFromQt();
|
const auto root = base::Platform::XCB::GetRootWindow(connection);
|
||||||
if (!root.has_value()) {
|
if (!root.has_value()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -431,23 +427,36 @@ bool TranslucentWindowsSupported(QPoint globalPosition) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
|
||||||
if (::Platform::IsX11()) {
|
if (::Platform::IsX11()) {
|
||||||
if (const auto native = QGuiApplication::platformNativeInterface()) {
|
const auto connection = base::Platform::XCB::GetConnectionFromQt();
|
||||||
if (const auto screen = QGuiApplication::screenAt(globalPosition)) {
|
if (!connection) {
|
||||||
if (native->nativeResourceForScreen(QByteArray("compositingEnabled"), screen)) {
|
return false;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
const auto index = QGuiApplication::screens().indexOf(screen);
|
|
||||||
static auto WarnedAbout = base::flat_set<int>();
|
const auto atom = base::Platform::XCB::GetAtom(
|
||||||
if (!WarnedAbout.contains(index)) {
|
connection,
|
||||||
WarnedAbout.emplace(index);
|
"_NET_WM_CM_S0");
|
||||||
LOG(("WARNING: Compositing is disabled for screen index %1 (for position %2,%3)").arg(index).arg(globalPosition.x()).arg(globalPosition.y()));
|
|
||||||
}
|
if (!atom) {
|
||||||
} else {
|
return false;
|
||||||
LOG(("WARNING: Could not get screen for position %1,%2").arg(globalPosition.x()).arg(globalPosition.y()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto cookie = xcb_get_selection_owner(connection, *atom);
|
||||||
|
|
||||||
|
const auto result = base::Platform::XCB::MakeReplyPointer(
|
||||||
|
xcb_get_selection_owner_reply(
|
||||||
|
connection,
|
||||||
|
cookie,
|
||||||
|
nullptr));
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return result->owner;
|
||||||
}
|
}
|
||||||
|
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -740,16 +740,7 @@ HWND GetWindowHandle(not_null<QWidget*> widget) {
|
||||||
}
|
}
|
||||||
|
|
||||||
HWND GetWindowHandle(not_null<QWindow*> window) {
|
HWND GetWindowHandle(not_null<QWindow*> window) {
|
||||||
if (!window->winId()) {
|
return reinterpret_cast<HWND>(window->winId());
|
||||||
window->create();
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto native = QGuiApplication::platformNativeInterface();
|
|
||||||
Assert(native != nullptr);
|
|
||||||
|
|
||||||
return static_cast<HWND>(native->nativeResourceForWindow(
|
|
||||||
QByteArrayLiteral("handle"),
|
|
||||||
window));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SendWMPaintForce(not_null<QWidget*> widget) {
|
void SendWMPaintForce(not_null<QWidget*> widget) {
|
||||||
|
|
|
||||||
40
ui/spoiler_click_handler.cpp
Normal file
40
ui/spoiler_click_handler.cpp
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
// This file is part of Desktop App Toolkit,
|
||||||
|
// a set of libraries for developing nice desktop applications.
|
||||||
|
//
|
||||||
|
// For license and copyright information please follow this link:
|
||||||
|
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||||
|
//
|
||||||
|
#include "ui/spoiler_click_handler.h"
|
||||||
|
|
||||||
|
#include "ui/effects/animation_value.h"
|
||||||
|
#include "ui/text/text_entity.h"
|
||||||
|
|
||||||
|
ClickHandler::TextEntity SpoilerClickHandler::getTextEntity() const {
|
||||||
|
return { EntityType::Spoiler };
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpoilerClickHandler::onClick(ClickContext context) const {
|
||||||
|
if (!_shown) {
|
||||||
|
const auto nonconst = const_cast<SpoilerClickHandler*>(this);
|
||||||
|
nonconst->_shown = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SpoilerClickHandler::shown() const {
|
||||||
|
return _shown;
|
||||||
|
}
|
||||||
|
|
||||||
|
crl::time SpoilerClickHandler::startMs() const {
|
||||||
|
return _startMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpoilerClickHandler::setStartMs(crl::time value) {
|
||||||
|
if (anim::Disabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_startMs = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpoilerClickHandler::setShown(bool value) {
|
||||||
|
_shown = value;
|
||||||
|
}
|
||||||
30
ui/spoiler_click_handler.h
Normal file
30
ui/spoiler_click_handler.h
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
// This file is part of Desktop App Toolkit,
|
||||||
|
// a set of libraries for developing nice desktop applications.
|
||||||
|
//
|
||||||
|
// For license and copyright information please follow this link:
|
||||||
|
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||||
|
//
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui/click_handler.h"
|
||||||
|
|
||||||
|
enum class EntityType : uchar;
|
||||||
|
|
||||||
|
class SpoilerClickHandler : public ClickHandler {
|
||||||
|
public:
|
||||||
|
SpoilerClickHandler() = default;
|
||||||
|
|
||||||
|
TextEntity getTextEntity() const override;
|
||||||
|
|
||||||
|
void onClick(ClickContext context) const override;
|
||||||
|
|
||||||
|
[[nodiscard]] bool shown() const;
|
||||||
|
void setShown(bool value);
|
||||||
|
[[nodiscard]] crl::time startMs() const;
|
||||||
|
void setStartMs(crl::time value);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool _shown = false;
|
||||||
|
crl::time _startMs = 0;
|
||||||
|
|
||||||
|
};
|
||||||
437
ui/text/text.cpp
437
ui/text/text.cpp
|
|
@ -11,6 +11,9 @@
|
||||||
#include "ui/text/text_isolated_emoji.h"
|
#include "ui/text/text_isolated_emoji.h"
|
||||||
#include "ui/emoji_config.h"
|
#include "ui/emoji_config.h"
|
||||||
#include "ui/integration.h"
|
#include "ui/integration.h"
|
||||||
|
#include "ui/round_rect.h"
|
||||||
|
#include "ui/image/image_prepare.h"
|
||||||
|
#include "ui/spoiler_click_handler.h"
|
||||||
#include "base/platform/base_platform_info.h"
|
#include "base/platform/base_platform_info.h"
|
||||||
#include "base/qt_adapters.h"
|
#include "base/qt_adapters.h"
|
||||||
|
|
||||||
|
|
@ -25,6 +28,7 @@ namespace {
|
||||||
|
|
||||||
constexpr auto kStringLinkIndexShift = uint16(0x8000);
|
constexpr auto kStringLinkIndexShift = uint16(0x8000);
|
||||||
constexpr auto kMaxDiacAfterSymbol = 2;
|
constexpr auto kMaxDiacAfterSymbol = 2;
|
||||||
|
constexpr auto kSelectedSpoilerOpacity = 0.5;
|
||||||
|
|
||||||
Qt::LayoutDirection StringDirection(
|
Qt::LayoutDirection StringDirection(
|
||||||
const QString &str,
|
const QString &str,
|
||||||
|
|
@ -195,6 +199,18 @@ QString textcmdStopSemibold() {
|
||||||
return result.append(TextCommand).append(QChar(TextCommandNoSemibold)).append(TextCommand);
|
return result.append(TextCommand).append(QChar(TextCommandNoSemibold)).append(TextCommand);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString textcmdStartSpoiler() {
|
||||||
|
QString result;
|
||||||
|
result.reserve(3);
|
||||||
|
return result.append(TextCommand).append(QChar(TextCommandSpoiler)).append(TextCommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString textcmdStopSpoiler() {
|
||||||
|
QString result;
|
||||||
|
result.reserve(3);
|
||||||
|
return result.append(TextCommand).append(QChar(TextCommandNoSpoiler)).append(TextCommand);
|
||||||
|
}
|
||||||
|
|
||||||
const QChar *textSkipCommand(const QChar *from, const QChar *end, bool canLink) {
|
const QChar *textSkipCommand(const QChar *from, const QChar *end, bool canLink) {
|
||||||
const QChar *result = from + 1;
|
const QChar *result = from + 1;
|
||||||
if (*from != TextCommand || result >= end) return from;
|
if (*from != TextCommand || result >= end) return from;
|
||||||
|
|
@ -212,6 +228,8 @@ const QChar *textSkipCommand(const QChar *from, const QChar *end, bool canLink)
|
||||||
case TextCommandNoItalic:
|
case TextCommandNoItalic:
|
||||||
case TextCommandUnderline:
|
case TextCommandUnderline:
|
||||||
case TextCommandNoUnderline:
|
case TextCommandNoUnderline:
|
||||||
|
case TextCommandSpoiler:
|
||||||
|
case TextCommandNoSpoiler:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TextCommandLinkIndex:
|
case TextCommandLinkIndex:
|
||||||
|
|
@ -273,13 +291,20 @@ private:
|
||||||
class StartedEntity {
|
class StartedEntity {
|
||||||
public:
|
public:
|
||||||
explicit StartedEntity(TextBlockFlags flags);
|
explicit StartedEntity(TextBlockFlags flags);
|
||||||
explicit StartedEntity(uint16 lnkIndex);
|
explicit StartedEntity(uint16 index, bool isLnk = true);
|
||||||
|
|
||||||
std::optional<TextBlockFlags> flags() const;
|
std::optional<TextBlockFlags> flags() const;
|
||||||
std::optional<uint16> lnkIndex() const;
|
std::optional<uint16> lnkIndex() const;
|
||||||
|
std::optional<uint16> spoilerIndex() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int _value = 0;
|
enum class Type {
|
||||||
|
Flags,
|
||||||
|
Link,
|
||||||
|
Spoiler,
|
||||||
|
};
|
||||||
|
const int _value = 0;
|
||||||
|
const Type _type;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -334,15 +359,18 @@ private:
|
||||||
const bool _checkTilde = false; // do we need a special text block for tilde symbol
|
const bool _checkTilde = false; // do we need a special text block for tilde symbol
|
||||||
|
|
||||||
std::vector<EntityLinkData> _links;
|
std::vector<EntityLinkData> _links;
|
||||||
|
std::vector<EntityLinkData> _spoilers;
|
||||||
base::flat_map<
|
base::flat_map<
|
||||||
const QChar*,
|
const QChar*,
|
||||||
std::vector<StartedEntity>> _startedEntities;
|
std::vector<StartedEntity>> _startedEntities;
|
||||||
|
|
||||||
uint16 _maxLnkIndex = 0;
|
uint16 _maxLnkIndex = 0;
|
||||||
|
uint16 _maxSpoilerIndex = 0;
|
||||||
|
|
||||||
// current state
|
// current state
|
||||||
int32 _flags = 0;
|
int32 _flags = 0;
|
||||||
uint16 _lnkIndex = 0;
|
uint16 _lnkIndex = 0;
|
||||||
|
uint16 _spoilerIndex = 0;
|
||||||
EmojiPtr _emoji = nullptr; // current emoji, if current word is an emoji, or zero
|
EmojiPtr _emoji = nullptr; // current emoji, if current word is an emoji, or zero
|
||||||
int32 _blockStart = 0; // offset in result, from which current parsed block is started
|
int32 _blockStart = 0; // offset in result, from which current parsed block is started
|
||||||
int32 _diacs = 0; // diac chars skipped without good char
|
int32 _diacs = 0; // diac chars skipped without good char
|
||||||
|
|
@ -357,23 +385,36 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Parser::StartedEntity::StartedEntity(TextBlockFlags flags) : _value(flags) {
|
Parser::StartedEntity::StartedEntity(TextBlockFlags flags)
|
||||||
|
: _value(flags)
|
||||||
|
, _type(Type::Flags) {
|
||||||
Expects(_value >= 0 && _value < int(kStringLinkIndexShift));
|
Expects(_value >= 0 && _value < int(kStringLinkIndexShift));
|
||||||
}
|
}
|
||||||
|
|
||||||
Parser::StartedEntity::StartedEntity(uint16 lnkIndex) : _value(lnkIndex) {
|
Parser::StartedEntity::StartedEntity(uint16 index, bool isLnk)
|
||||||
Expects(_value >= kStringLinkIndexShift);
|
: _value(index)
|
||||||
|
, _type(isLnk ? Type::Link : Type::Spoiler) {
|
||||||
|
Expects((_type == Type::Link)
|
||||||
|
? (_value >= kStringLinkIndexShift)
|
||||||
|
: (_value < kStringLinkIndexShift));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<TextBlockFlags> Parser::StartedEntity::flags() const {
|
std::optional<TextBlockFlags> Parser::StartedEntity::flags() const {
|
||||||
if (_value < int(kStringLinkIndexShift)) {
|
if (_value < int(kStringLinkIndexShift) && (_type == Type::Flags)) {
|
||||||
return TextBlockFlags(_value);
|
return TextBlockFlags(_value);
|
||||||
}
|
}
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<uint16> Parser::StartedEntity::lnkIndex() const {
|
std::optional<uint16> Parser::StartedEntity::lnkIndex() const {
|
||||||
if (_value >= int(kStringLinkIndexShift)) {
|
if (_value >= int(kStringLinkIndexShift) && (_type == Type::Link)) {
|
||||||
|
return uint16(_value);
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<uint16> Parser::StartedEntity::spoilerIndex() const {
|
||||||
|
if (_value < int(kStringLinkIndexShift) && (_type == Type::Spoiler)) {
|
||||||
return uint16(_value);
|
return uint16(_value);
|
||||||
}
|
}
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
|
@ -437,6 +478,9 @@ void Parser::createBlock(int32 skipBack) {
|
||||||
if (_lnkIndex < kStringLinkIndexShift && _lnkIndex > _maxLnkIndex) {
|
if (_lnkIndex < kStringLinkIndexShift && _lnkIndex > _maxLnkIndex) {
|
||||||
_maxLnkIndex = _lnkIndex;
|
_maxLnkIndex = _lnkIndex;
|
||||||
}
|
}
|
||||||
|
if (_spoilerIndex > _maxSpoilerIndex) {
|
||||||
|
_maxSpoilerIndex = _spoilerIndex;
|
||||||
|
}
|
||||||
|
|
||||||
int32 len = int32(_t->_text.size()) + skipBack - _blockStart;
|
int32 len = int32(_t->_text.size()) + skipBack - _blockStart;
|
||||||
if (len > 0) {
|
if (len > 0) {
|
||||||
|
|
@ -450,13 +494,13 @@ void Parser::createBlock(int32 skipBack) {
|
||||||
}
|
}
|
||||||
_lastSkipped = false;
|
_lastSkipped = false;
|
||||||
if (_emoji) {
|
if (_emoji) {
|
||||||
_t->_blocks.push_back(Block::Emoji(_t->_st->font, _t->_text, _blockStart, len, _flags, _lnkIndex, _emoji));
|
_t->_blocks.push_back(Block::Emoji(_t->_st->font, _t->_text, _blockStart, len, _flags, _lnkIndex, _spoilerIndex, _emoji));
|
||||||
_emoji = nullptr;
|
_emoji = nullptr;
|
||||||
_lastSkipped = true;
|
_lastSkipped = true;
|
||||||
} else if (newline) {
|
} else if (newline) {
|
||||||
_t->_blocks.push_back(Block::Newline(_t->_st->font, _t->_text, _blockStart, len, _flags, _lnkIndex));
|
_t->_blocks.push_back(Block::Newline(_t->_st->font, _t->_text, _blockStart, len, _flags, _lnkIndex, _spoilerIndex));
|
||||||
} else {
|
} else {
|
||||||
_t->_blocks.push_back(Block::Text(_t->_st->font, _t->_text, _t->_minResizeWidth, _blockStart, len, _flags, _lnkIndex));
|
_t->_blocks.push_back(Block::Text(_t->_st->font, _t->_text, _t->_minResizeWidth, _blockStart, len, _flags, _lnkIndex, _spoilerIndex));
|
||||||
}
|
}
|
||||||
_blockStart += len;
|
_blockStart += len;
|
||||||
blockCreated();
|
blockCreated();
|
||||||
|
|
@ -466,7 +510,7 @@ void Parser::createBlock(int32 skipBack) {
|
||||||
void Parser::createSkipBlock(int32 w, int32 h) {
|
void Parser::createSkipBlock(int32 w, int32 h) {
|
||||||
createBlock();
|
createBlock();
|
||||||
_t->_text.push_back('_');
|
_t->_text.push_back('_');
|
||||||
_t->_blocks.push_back(Block::Skip(_t->_st->font, _t->_text, _blockStart++, w, h, _lnkIndex));
|
_t->_blocks.push_back(Block::Skip(_t->_st->font, _t->_text, _blockStart++, w, h, _lnkIndex, _spoilerIndex));
|
||||||
blockCreated();
|
blockCreated();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -509,6 +553,11 @@ void Parser::finishEntities() {
|
||||||
createBlock();
|
createBlock();
|
||||||
_lnkIndex = 0;
|
_lnkIndex = 0;
|
||||||
}
|
}
|
||||||
|
} else if (const auto spoilerIndex = list.back().spoilerIndex()) {
|
||||||
|
if (_spoilerIndex == *spoilerIndex && (_spoilerIndex != 0)) {
|
||||||
|
createBlock();
|
||||||
|
_spoilerIndex = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
list.pop_back();
|
list.pop_back();
|
||||||
}
|
}
|
||||||
|
|
@ -594,6 +643,16 @@ bool Parser::checkEntities() {
|
||||||
_flags |= flags;
|
_flags |= flags;
|
||||||
_startedEntities[entityEnd].emplace_back(flags);
|
_startedEntities[entityEnd].emplace_back(flags);
|
||||||
}
|
}
|
||||||
|
} else if (entityType == EntityType::Spoiler) {
|
||||||
|
createBlock();
|
||||||
|
|
||||||
|
_spoilers.push_back(EntityLinkData{
|
||||||
|
.data = QString::number(_spoilers.size() + 1),
|
||||||
|
.type = entityType,
|
||||||
|
});
|
||||||
|
_spoilerIndex = _spoilers.size();
|
||||||
|
|
||||||
|
_startedEntities[entityEnd].emplace_back(_spoilerIndex, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
++_waitingEntity;
|
++_waitingEntity;
|
||||||
|
|
@ -738,6 +797,24 @@ bool Parser::readCommand() {
|
||||||
_lnkIndex = kStringLinkIndexShift + _links.size();
|
_lnkIndex = kStringLinkIndexShift + _links.size();
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
|
case TextCommandSpoiler: {
|
||||||
|
if (!_spoilerIndex) {
|
||||||
|
createBlock();
|
||||||
|
_spoilers.push_back(EntityLinkData{
|
||||||
|
.data = QString::number(_spoilers.size() + 1),
|
||||||
|
.type = EntityType::Spoiler,
|
||||||
|
});
|
||||||
|
_spoilerIndex = _spoilers.size();
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case TextCommandNoSpoiler:
|
||||||
|
if (_spoilerIndex == _spoilers.size()) {
|
||||||
|
createBlock();
|
||||||
|
_spoilerIndex = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case TextCommandSkipBlock:
|
case TextCommandSkipBlock:
|
||||||
createSkipBlock(_ptr->unicode(), (_ptr + 1)->unicode());
|
createSkipBlock(_ptr->unicode(), (_ptr + 1)->unicode());
|
||||||
break;
|
break;
|
||||||
|
|
@ -920,7 +997,16 @@ void Parser::checkForElidedSkipBlock() {
|
||||||
|
|
||||||
void Parser::finalize(const TextParseOptions &options) {
|
void Parser::finalize(const TextParseOptions &options) {
|
||||||
_t->_links.resize(_maxLnkIndex);
|
_t->_links.resize(_maxLnkIndex);
|
||||||
|
_t->_spoilers.resize(_maxSpoilerIndex);
|
||||||
for (auto &block : _t->_blocks) {
|
for (auto &block : _t->_blocks) {
|
||||||
|
const auto spoilerIndex = block->spoilerIndex();
|
||||||
|
if (spoilerIndex) {
|
||||||
|
_t->_spoilers.resize(spoilerIndex);
|
||||||
|
const auto handler = (options.flags & TextParseLinks)
|
||||||
|
? std::make_shared<SpoilerClickHandler>()
|
||||||
|
: nullptr;
|
||||||
|
_t->setSpoiler(spoilerIndex, std::move(handler));
|
||||||
|
}
|
||||||
const auto shiftedIndex = block->lnkIndex();
|
const auto shiftedIndex = block->lnkIndex();
|
||||||
if (shiftedIndex <= kStringLinkIndexShift) {
|
if (shiftedIndex <= kStringLinkIndexShift) {
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -941,6 +1027,7 @@ void Parser::finalize(const TextParseOptions &options) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_t->_links.squeeze();
|
_t->_links.squeeze();
|
||||||
|
_t->_spoilers.squeeze();
|
||||||
_t->_blocks.squeeze();
|
_t->_blocks.squeeze();
|
||||||
_t->_text.squeeze();
|
_t->_text.squeeze();
|
||||||
}
|
}
|
||||||
|
|
@ -1593,8 +1680,14 @@ private:
|
||||||
TextBlockType _type = currentBlock->type();
|
TextBlockType _type = currentBlock->type();
|
||||||
if (!_p && _lookupX >= x && _lookupX < x + si.width) { // _lookupRequest
|
if (!_p && _lookupX >= x && _lookupX < x + si.width) { // _lookupRequest
|
||||||
if (_lookupLink) {
|
if (_lookupLink) {
|
||||||
if (currentBlock->lnkIndex() && _lookupY >= _y + _yDelta && _lookupY < _y + _yDelta + _fontHeight) {
|
if (_lookupY >= _y + _yDelta && _lookupY < _y + _yDelta + _fontHeight) {
|
||||||
_lookupResult.link = _t->_links.at(currentBlock->lnkIndex() - 1);
|
const auto spoilerLink = _t->spoilerLink(currentBlock->spoilerIndex());
|
||||||
|
const auto resultLink = (spoilerLink || !currentBlock->lnkIndex())
|
||||||
|
? spoilerLink
|
||||||
|
: _t->_links.at(currentBlock->lnkIndex() - 1);
|
||||||
|
if (resultLink) {
|
||||||
|
_lookupResult.link = resultLink;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (_type != TextBlockTSkip) {
|
if (_type != TextBlockTSkip) {
|
||||||
|
|
@ -1649,29 +1742,65 @@ private:
|
||||||
if (rtl) {
|
if (rtl) {
|
||||||
glyphX += spacesWidth;
|
glyphX += spacesWidth;
|
||||||
}
|
}
|
||||||
|
struct {
|
||||||
|
QFixed from;
|
||||||
|
QFixed to;
|
||||||
|
} fillSelect;
|
||||||
|
struct {
|
||||||
|
QFixed from;
|
||||||
|
QFixed width;
|
||||||
|
} fillSpoiler;
|
||||||
if (_localFrom + si.position < _selection.to) {
|
if (_localFrom + si.position < _selection.to) {
|
||||||
auto chFrom = _str + currentBlock->from();
|
auto chFrom = _str + currentBlock->from();
|
||||||
auto chTo = chFrom + ((nextBlock ? nextBlock->from() : _t->_text.size()) - currentBlock->from());
|
auto chTo = chFrom + ((nextBlock ? nextBlock->from() : _t->_text.size()) - currentBlock->from());
|
||||||
if (_localFrom + si.position >= _selection.from) { // could be without space
|
if (_localFrom + si.position >= _selection.from) { // could be without space
|
||||||
if (chTo == chFrom || (chTo - 1)->unicode() != QChar::Space || _selection.to >= (chTo - _str)) {
|
if (chTo == chFrom || (chTo - 1)->unicode() != QChar::Space || _selection.to >= (chTo - _str)) {
|
||||||
fillSelectRange(x, x + si.width);
|
fillSelect = { x, x + si.width };
|
||||||
} else { // or with space
|
} else { // or with space
|
||||||
fillSelectRange(glyphX, glyphX + currentBlock->f_width());
|
fillSelect = { glyphX, glyphX + currentBlock->f_width() };
|
||||||
}
|
}
|
||||||
} else if (chTo > chFrom && (chTo - 1)->unicode() == QChar::Space && (chTo - 1 - _str) >= _selection.from) {
|
} else if (chTo > chFrom && (chTo - 1)->unicode() == QChar::Space && (chTo - 1 - _str) >= _selection.from) {
|
||||||
if (rtl) { // rtl space only
|
if (rtl) { // rtl space only
|
||||||
fillSelectRange(x, glyphX);
|
fillSelect = { x, glyphX };
|
||||||
} else { // ltr space only
|
} else { // ltr space only
|
||||||
fillSelectRange(x + currentBlock->f_width(), x + si.width);
|
fillSelect = { x + currentBlock->f_width(), x + si.width };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const auto hasSpoiler = _background.color &&
|
||||||
|
(_background.inFront || _background.startMs);
|
||||||
|
if (hasSpoiler) {
|
||||||
|
fillSpoiler = { x, si.width };
|
||||||
|
}
|
||||||
|
const auto spoilerOpacity = hasSpoiler
|
||||||
|
? fillSpoilerOpacity()
|
||||||
|
: 0.;
|
||||||
|
const auto hasSelect = fillSelect.to != QFixed();
|
||||||
|
if (hasSelect) {
|
||||||
|
fillSelectRange(fillSelect.from, fillSelect.to);
|
||||||
|
}
|
||||||
|
const auto opacity = _p->opacity();
|
||||||
|
if (spoilerOpacity < 1.) {
|
||||||
|
if (hasSpoiler) {
|
||||||
|
_p->setOpacity(opacity * (1. - spoilerOpacity));
|
||||||
|
}
|
||||||
Emoji::Draw(
|
Emoji::Draw(
|
||||||
*_p,
|
*_p,
|
||||||
static_cast<const EmojiBlock*>(currentBlock)->_emoji,
|
static_cast<const EmojiBlock*>(currentBlock)->_emoji,
|
||||||
Emoji::GetSizeNormal(),
|
Emoji::GetSizeNormal(),
|
||||||
(glyphX + st::emojiPadding).toInt(),
|
(glyphX + st::emojiPadding).toInt(),
|
||||||
_y + _yDelta + emojiY);
|
_y + _yDelta + emojiY);
|
||||||
|
}
|
||||||
|
if (hasSpoiler) {
|
||||||
|
_p->setOpacity(opacity * spoilerOpacity);
|
||||||
|
fillSpoilerRange(
|
||||||
|
fillSpoiler.from,
|
||||||
|
fillSpoiler.width,
|
||||||
|
blockIndex,
|
||||||
|
currentBlock->from(),
|
||||||
|
(nextBlock ? nextBlock->from() : _t->_text.size()));
|
||||||
|
_p->setOpacity(opacity);
|
||||||
|
}
|
||||||
// } else if (_p && currentBlock->type() == TextBlockSkip) { // debug
|
// } else if (_p && currentBlock->type() == TextBlockSkip) { // debug
|
||||||
// _p->fillRect(QRect(x.toInt(), _y, currentBlock->width(), static_cast<SkipBlock*>(currentBlock)->height()), QColor(0, 0, 0, 32));
|
// _p->fillRect(QRect(x.toInt(), _y, currentBlock->width(), static_cast<SkipBlock*>(currentBlock)->height()), QColor(0, 0, 0, 32));
|
||||||
}
|
}
|
||||||
|
|
@ -1699,8 +1828,14 @@ private:
|
||||||
|
|
||||||
if (!_p && _lookupX >= x && _lookupX < x + itemWidth) { // _lookupRequest
|
if (!_p && _lookupX >= x && _lookupX < x + itemWidth) { // _lookupRequest
|
||||||
if (_lookupLink) {
|
if (_lookupLink) {
|
||||||
if (currentBlock->lnkIndex() && _lookupY >= _y + _yDelta && _lookupY < _y + _yDelta + _fontHeight) {
|
if (_lookupY >= _y + _yDelta && _lookupY < _y + _yDelta + _fontHeight) {
|
||||||
_lookupResult.link = _t->_links.at(currentBlock->lnkIndex() - 1);
|
const auto spoilerLink = _t->spoilerLink(currentBlock->spoilerIndex());
|
||||||
|
const auto resultLink = (spoilerLink || !currentBlock->lnkIndex())
|
||||||
|
? spoilerLink
|
||||||
|
: _t->_links.at(currentBlock->lnkIndex() - 1);
|
||||||
|
if (resultLink) {
|
||||||
|
_lookupResult.link = resultLink;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_lookupResult.uponSymbol = true;
|
_lookupResult.uponSymbol = true;
|
||||||
|
|
@ -1801,6 +1936,15 @@ private:
|
||||||
selectedRect = QRect(selX.toInt(), _y + _yDelta, (selX + selWidth).toInt() - selX.toInt(), _fontHeight);
|
selectedRect = QRect(selX.toInt(), _y + _yDelta, (selX + selWidth).toInt() - selX.toInt(), _fontHeight);
|
||||||
fillSelectRange(selX, selX + selWidth);
|
fillSelectRange(selX, selX + selWidth);
|
||||||
}
|
}
|
||||||
|
const auto hasSpoiler = (_background.inFront || _background.startMs);
|
||||||
|
const auto spoilerOpacity = hasSpoiler
|
||||||
|
? fillSpoilerOpacity()
|
||||||
|
: 0.;
|
||||||
|
const auto opacity = _p->opacity();
|
||||||
|
if (spoilerOpacity < 1.) {
|
||||||
|
if (hasSpoiler) {
|
||||||
|
_p->setOpacity(opacity * (1. - spoilerOpacity));
|
||||||
|
}
|
||||||
if (Q_UNLIKELY(hasSelected)) {
|
if (Q_UNLIKELY(hasSelected)) {
|
||||||
if (Q_UNLIKELY(hasNotSelected)) {
|
if (Q_UNLIKELY(hasNotSelected)) {
|
||||||
auto clippingEnabled = _p->hasClipping();
|
auto clippingEnabled = _p->hasClipping();
|
||||||
|
|
@ -1827,6 +1971,18 @@ private:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasSpoiler) {
|
||||||
|
_p->setOpacity(opacity * spoilerOpacity);
|
||||||
|
fillSpoilerRange(
|
||||||
|
x,
|
||||||
|
itemWidth,
|
||||||
|
blockIndex,
|
||||||
|
_localFrom + itemStart,
|
||||||
|
_localFrom + itemEnd);
|
||||||
|
_p->setOpacity(opacity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
x += itemWidth;
|
x += itemWidth;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -1837,6 +1993,91 @@ private:
|
||||||
_p->fillRect(left, _y + _yDelta, width, _fontHeight, _textPalette->selectBg);
|
_p->fillRect(left, _y + _yDelta, width, _fontHeight, _textPalette->selectBg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float fillSpoilerOpacity() {
|
||||||
|
if (!_background.startMs) {
|
||||||
|
return 1.;
|
||||||
|
}
|
||||||
|
const auto progress = float64(crl::now() - _background.startMs)
|
||||||
|
/ st::fadeWrapDuration;
|
||||||
|
if ((progress > 1.) && _background.spoilerIndex) {
|
||||||
|
const auto link = _t->_spoilers.at(_background.spoilerIndex - 1);
|
||||||
|
if (link) {
|
||||||
|
link->setStartMs(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (1. - std::min(progress, 1.));
|
||||||
|
}
|
||||||
|
|
||||||
|
void fillSpoilerRange(
|
||||||
|
QFixed x,
|
||||||
|
QFixed width,
|
||||||
|
int currentBlockIndex,
|
||||||
|
int positionFrom,
|
||||||
|
int positionTill) {
|
||||||
|
if (!_background.color) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto elideOffset = (_indexOfElidedBlock == currentBlockIndex)
|
||||||
|
? (_elideRemoveFromEnd + _f->elidew)
|
||||||
|
: 0;
|
||||||
|
const auto &cache = _background.inFront
|
||||||
|
? _t->_spoilerCache
|
||||||
|
: _t->_spoilerShownCache;
|
||||||
|
const auto cornerWidth = cache.corners[0].width()
|
||||||
|
/ style::DevicePixelRatio();
|
||||||
|
const auto useWidth = (x + width).toInt() - x.toInt();
|
||||||
|
const auto rect = QRect(
|
||||||
|
x.toInt(),
|
||||||
|
_y + _yDelta,
|
||||||
|
std::max(useWidth - elideOffset, cornerWidth * 2),
|
||||||
|
_fontHeight);
|
||||||
|
if (!rect.isValid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto parts = [&] {
|
||||||
|
const auto blockIndex = currentBlockIndex - 1;
|
||||||
|
const auto block = _t->_blocks[blockIndex].get();
|
||||||
|
const auto nextBlock = (blockIndex + 1 < _t->_blocks.size())
|
||||||
|
? _t->_blocks[blockIndex + 1].get()
|
||||||
|
: nullptr;
|
||||||
|
const auto blockEnd = nextBlock ? nextBlock->from() : _t->_text.size();
|
||||||
|
const auto now = block->spoilerIndex();
|
||||||
|
const auto was = (positionFrom > block->from())
|
||||||
|
? now
|
||||||
|
: (blockIndex > 0)
|
||||||
|
? _t->_blocks[blockIndex - 1]->spoilerIndex()
|
||||||
|
: 0;
|
||||||
|
const auto will = (positionTill < blockEnd)
|
||||||
|
? now
|
||||||
|
: nextBlock
|
||||||
|
? nextBlock->spoilerIndex()
|
||||||
|
: 0;
|
||||||
|
return RectPart::None
|
||||||
|
| ((now != was) ? (RectPart::FullLeft) : RectPart::None)
|
||||||
|
| ((now != will) ? (RectPart::FullRight) : RectPart::None);
|
||||||
|
}();
|
||||||
|
|
||||||
|
if (parts != RectPart::None) {
|
||||||
|
DrawRoundedRect(
|
||||||
|
*_p,
|
||||||
|
rect,
|
||||||
|
*_background.color,
|
||||||
|
cache.corners,
|
||||||
|
parts);
|
||||||
|
}
|
||||||
|
const auto hasLeft = (parts & RectPart::Left) != 0;
|
||||||
|
const auto hasRight = (parts & RectPart::Right) != 0;
|
||||||
|
_p->fillRect(
|
||||||
|
rect.left() + (hasLeft ? cornerWidth : 0),
|
||||||
|
rect.top(),
|
||||||
|
rect.width()
|
||||||
|
- (hasRight ? cornerWidth : 0)
|
||||||
|
- (hasLeft ? cornerWidth : 0),
|
||||||
|
rect.height(),
|
||||||
|
*_background.color);
|
||||||
|
}
|
||||||
|
|
||||||
void elideSaveBlock(int32 blockIndex, const AbstractBlock *&_endBlock, int32 elideStart, int32 elideWidth) {
|
void elideSaveBlock(int32 blockIndex, const AbstractBlock *&_endBlock, int32 elideStart, int32 elideWidth) {
|
||||||
if (_elideSavedBlock) {
|
if (_elideSavedBlock) {
|
||||||
restoreAfterElided();
|
restoreAfterElided();
|
||||||
|
|
@ -1845,7 +2086,7 @@ private:
|
||||||
_elideSavedIndex = blockIndex;
|
_elideSavedIndex = blockIndex;
|
||||||
auto mutableText = const_cast<String*>(_t);
|
auto mutableText = const_cast<String*>(_t);
|
||||||
_elideSavedBlock = std::move(mutableText->_blocks[blockIndex]);
|
_elideSavedBlock = std::move(mutableText->_blocks[blockIndex]);
|
||||||
mutableText->_blocks[blockIndex] = Block::Text(_t->_st->font, _t->_text, QFIXED_MAX, elideStart, 0, (*_elideSavedBlock)->flags(), (*_elideSavedBlock)->lnkIndex());
|
mutableText->_blocks[blockIndex] = Block::Text(_t->_st->font, _t->_text, QFIXED_MAX, elideStart, 0, (*_elideSavedBlock)->flags(), (*_elideSavedBlock)->lnkIndex(), (*_elideSavedBlock)->spoilerIndex());
|
||||||
_blocksSize = blockIndex + 1;
|
_blocksSize = blockIndex + 1;
|
||||||
_endBlock = (blockIndex + 1 < _t->_blocks.size() ? _t->_blocks[blockIndex + 1].get() : nullptr);
|
_endBlock = (blockIndex + 1 < _t->_blocks.size() ? _t->_blocks[blockIndex + 1].get() : nullptr);
|
||||||
}
|
}
|
||||||
|
|
@ -1861,8 +2102,6 @@ private:
|
||||||
}
|
}
|
||||||
|
|
||||||
void prepareElidedLine(QString &lineText, int32 lineStart, int32 &lineLength, const AbstractBlock *&_endBlock, int repeat = 0) {
|
void prepareElidedLine(QString &lineText, int32 lineStart, int32 &lineLength, const AbstractBlock *&_endBlock, int repeat = 0) {
|
||||||
static const auto _Elide = QString::fromLatin1("...");
|
|
||||||
|
|
||||||
_f = _t->_st->font;
|
_f = _t->_st->font;
|
||||||
QStackTextEngine engine(lineText, _f->f);
|
QStackTextEngine engine(lineText, _f->f);
|
||||||
engine.option.setTextDirection(_parDirection);
|
engine.option.setTextDirection(_parDirection);
|
||||||
|
|
@ -1899,10 +2138,11 @@ private:
|
||||||
}
|
}
|
||||||
if (_type == TextBlockTEmoji || _type == TextBlockTSkip || _type == TextBlockTNewline) {
|
if (_type == TextBlockTEmoji || _type == TextBlockTSkip || _type == TextBlockTNewline) {
|
||||||
if (_wLeft < si.width) {
|
if (_wLeft < si.width) {
|
||||||
lineText = lineText.mid(0, currentBlock->from() - _localFrom) + _Elide;
|
lineText = lineText.mid(0, currentBlock->from() - _localFrom) + kQEllipsis;
|
||||||
lineLength = currentBlock->from() + _Elide.size() - _lineStart;
|
lineLength = currentBlock->from() + kQEllipsis.size() - _lineStart;
|
||||||
_selection.to = qMin(_selection.to, currentBlock->from());
|
_selection.to = qMin(_selection.to, currentBlock->from());
|
||||||
setElideBidi(currentBlock->from(), _Elide.size());
|
_indexOfElidedBlock = blockIndex + (nextBlock ? 1 : 0);
|
||||||
|
setElideBidi(currentBlock->from(), kQEllipsis.size());
|
||||||
elideSaveBlock(blockIndex - 1, _endBlock, currentBlock->from(), elideWidth);
|
elideSaveBlock(blockIndex - 1, _endBlock, currentBlock->from(), elideWidth);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1931,10 +2171,11 @@ private:
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lineText.size() <= pos || repeat > 3) {
|
if (lineText.size() <= pos || repeat > 3) {
|
||||||
lineText += _Elide;
|
lineText += kQEllipsis;
|
||||||
lineLength = _localFrom + pos + _Elide.size() - _lineStart;
|
lineLength = _localFrom + pos + kQEllipsis.size() - _lineStart;
|
||||||
_selection.to = qMin(_selection.to, uint16(_localFrom + pos));
|
_selection.to = qMin(_selection.to, uint16(_localFrom + pos));
|
||||||
setElideBidi(_localFrom + pos, _Elide.size());
|
_indexOfElidedBlock = blockIndex + (nextBlock ? 1 : 0);
|
||||||
|
setElideBidi(_localFrom + pos, kQEllipsis.size());
|
||||||
_blocksSize = blockIndex;
|
_blocksSize = blockIndex;
|
||||||
_endBlock = nextBlock;
|
_endBlock = nextBlock;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1954,10 +2195,11 @@ private:
|
||||||
|
|
||||||
int32 elideStart = _localFrom + lineText.size();
|
int32 elideStart = _localFrom + lineText.size();
|
||||||
_selection.to = qMin(_selection.to, uint16(elideStart));
|
_selection.to = qMin(_selection.to, uint16(elideStart));
|
||||||
setElideBidi(elideStart, _Elide.size());
|
_indexOfElidedBlock = blockIndex + (nextBlock ? 1 : 0);
|
||||||
|
setElideBidi(elideStart, kQEllipsis.size());
|
||||||
|
|
||||||
lineText += _Elide;
|
lineText += kQEllipsis;
|
||||||
lineLength += _Elide.size();
|
lineLength += kQEllipsis.size();
|
||||||
|
|
||||||
if (!repeat) {
|
if (!repeat) {
|
||||||
for (; blockIndex < _blocksSize && _t->_blocks[blockIndex].get() != _endBlock && _t->_blocks[blockIndex]->from() < elideStart; ++blockIndex) {
|
for (; blockIndex < _blocksSize && _t->_blocks[blockIndex].get() != _endBlock && _t->_blocks[blockIndex]->from() < elideStart; ++blockIndex) {
|
||||||
|
|
@ -2613,6 +2855,33 @@ private:
|
||||||
void applyBlockProperties(const AbstractBlock *block) {
|
void applyBlockProperties(const AbstractBlock *block) {
|
||||||
eSetFont(block);
|
eSetFont(block);
|
||||||
if (_p) {
|
if (_p) {
|
||||||
|
if (block->spoilerIndex()) {
|
||||||
|
const auto handler
|
||||||
|
= _t->_spoilers.at(block->spoilerIndex() - 1);
|
||||||
|
const auto inBack = (handler && handler->shown());
|
||||||
|
_background.inFront = !inBack;
|
||||||
|
_background.color = inBack
|
||||||
|
? &_textPalette->spoilerActiveBg
|
||||||
|
: &_textPalette->spoilerBg;
|
||||||
|
_background.startMs = handler ? handler->startMs() : 0;
|
||||||
|
_background.spoilerIndex = block->spoilerIndex();
|
||||||
|
|
||||||
|
const auto &cache = _background.inFront
|
||||||
|
? _t->_spoilerCache
|
||||||
|
: _t->_spoilerShownCache;
|
||||||
|
if (cache.color != (*_background.color)->c) {
|
||||||
|
auto mutableText = const_cast<String*>(_t);
|
||||||
|
auto &mutableCache = _background.inFront
|
||||||
|
? mutableText->_spoilerCache
|
||||||
|
: mutableText->_spoilerShownCache;
|
||||||
|
mutableCache.corners = Images::PrepareCorners(
|
||||||
|
ImageRoundRadius::Small,
|
||||||
|
*_background.color);
|
||||||
|
mutableCache.color = (*_background.color)->c;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_background = {};
|
||||||
|
}
|
||||||
if (block->lnkIndex()) {
|
if (block->lnkIndex()) {
|
||||||
_currentPen = &_textPalette->linkFg->p;
|
_currentPen = &_textPalette->linkFg->p;
|
||||||
_currentPenSelected = &_textPalette->selectLinkFg->p;
|
_currentPenSelected = &_textPalette->selectLinkFg->p;
|
||||||
|
|
@ -2637,6 +2906,12 @@ private:
|
||||||
QPen _originalPenSelected;
|
QPen _originalPenSelected;
|
||||||
const QPen *_currentPen = nullptr;
|
const QPen *_currentPen = nullptr;
|
||||||
const QPen *_currentPenSelected = nullptr;
|
const QPen *_currentPenSelected = nullptr;
|
||||||
|
struct {
|
||||||
|
const style::color *color = nullptr;
|
||||||
|
bool inFront = false;
|
||||||
|
crl::time startMs = 0;
|
||||||
|
uint16 spoilerIndex = 0;
|
||||||
|
} _background;
|
||||||
int _yFrom = 0;
|
int _yFrom = 0;
|
||||||
int _yTo = 0;
|
int _yTo = 0;
|
||||||
int _yToElide = 0;
|
int _yToElide = 0;
|
||||||
|
|
@ -2644,6 +2919,8 @@ private:
|
||||||
bool _fullWidthSelection = true;
|
bool _fullWidthSelection = true;
|
||||||
const QChar *_str = nullptr;
|
const QChar *_str = nullptr;
|
||||||
|
|
||||||
|
int _indexOfElidedBlock = -1; // For spoilers.
|
||||||
|
|
||||||
// current paragraph data
|
// current paragraph data
|
||||||
String::TextBlocks::const_iterator _parStartBlock;
|
String::TextBlocks::const_iterator _parStartBlock;
|
||||||
Qt::LayoutDirection _parDirection;
|
Qt::LayoutDirection _parDirection;
|
||||||
|
|
@ -2867,10 +3144,32 @@ void String::setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk) {
|
||||||
_links[lnkIndex - 1] = lnk;
|
_links[lnkIndex - 1] = lnk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void String::setSpoiler(
|
||||||
|
uint16 lnkIndex,
|
||||||
|
const std::shared_ptr<SpoilerClickHandler> &lnk) {
|
||||||
|
if (!lnkIndex || lnkIndex > _spoilers.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_spoilers[lnkIndex - 1] = lnk;
|
||||||
|
}
|
||||||
|
|
||||||
|
void String::setSpoilerShown(uint16 lnkIndex, bool shown) {
|
||||||
|
if (!lnkIndex
|
||||||
|
|| (lnkIndex > _spoilers.size())
|
||||||
|
|| !_spoilers[lnkIndex - 1]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_spoilers[lnkIndex - 1]->setShown(shown);
|
||||||
|
}
|
||||||
|
|
||||||
bool String::hasLinks() const {
|
bool String::hasLinks() const {
|
||||||
return !_links.isEmpty();
|
return !_links.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int String::spoilersCount() const {
|
||||||
|
return _spoilers.size();
|
||||||
|
}
|
||||||
|
|
||||||
bool String::hasSkipBlock() const {
|
bool String::hasSkipBlock() const {
|
||||||
return _blocks.empty() ? false : _blocks.back()->type() == TextBlockTSkip;
|
return _blocks.empty() ? false : _blocks.back()->type() == TextBlockTSkip;
|
||||||
}
|
}
|
||||||
|
|
@ -2891,6 +3190,7 @@ bool String::updateSkipBlock(int width, int height) {
|
||||||
_text.size() - 1,
|
_text.size() - 1,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
|
0,
|
||||||
0));
|
0));
|
||||||
recountNaturalSize(false);
|
recountNaturalSize(false);
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -3153,9 +3453,14 @@ void String::enumerateText(TextSelection selection, AppendPartCallback appendPar
|
||||||
|
|
||||||
int lnkIndex = 0;
|
int lnkIndex = 0;
|
||||||
uint16 lnkFrom = 0;
|
uint16 lnkFrom = 0;
|
||||||
|
|
||||||
|
int spoilerIndex = 0;
|
||||||
|
uint16 spoilerFrom = 0;
|
||||||
|
|
||||||
int32 flags = 0;
|
int32 flags = 0;
|
||||||
for (auto i = _blocks.cbegin(), e = _blocks.cend(); true; ++i) {
|
for (auto i = _blocks.cbegin(), e = _blocks.cend(); true; ++i) {
|
||||||
int blockLnkIndex = (i == e) ? 0 : (*i)->lnkIndex();
|
int blockLnkIndex = (i == e) ? 0 : (*i)->lnkIndex();
|
||||||
|
int blockSpoilerIndex = (i == e) ? 0 : (*i)->spoilerIndex();
|
||||||
uint16 blockFrom = (i == e) ? _text.size() : (*i)->from();
|
uint16 blockFrom = (i == e) ? _text.size() : (*i)->from();
|
||||||
int32 blockFlags = (i == e) ? 0 : (*i)->flags();
|
int32 blockFlags = (i == e) ? 0 : (*i)->flags();
|
||||||
|
|
||||||
|
|
@ -3168,18 +3473,43 @@ void String::enumerateText(TextSelection selection, AppendPartCallback appendPar
|
||||||
auto rangeTo = qMin(selection.to, blockFrom);
|
auto rangeTo = qMin(selection.to, blockFrom);
|
||||||
if (rangeTo > rangeFrom) { // handle click handler
|
if (rangeTo > rangeFrom) { // handle click handler
|
||||||
const auto r = base::StringViewMid(_text, rangeFrom, rangeTo - rangeFrom);
|
const auto r = base::StringViewMid(_text, rangeFrom, rangeTo - rangeFrom);
|
||||||
if (lnkFrom != rangeFrom || blockFrom != rangeTo) {
|
|
||||||
// Ignore links that are partially copied.
|
// Ignore links that are partially copied.
|
||||||
clickHandlerFinishCallback(r, nullptr);
|
const auto handler = (lnkFrom != rangeFrom || blockFrom != rangeTo)
|
||||||
} else {
|
? nullptr
|
||||||
clickHandlerFinishCallback(r, _links.at(lnkIndex - 1));
|
: _links.at(lnkIndex - 1);
|
||||||
}
|
const auto type = handler
|
||||||
|
? handler->getTextEntity().type
|
||||||
|
: EntityType::Invalid;
|
||||||
|
clickHandlerFinishCallback(r, handler, type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lnkIndex = blockLnkIndex;
|
lnkIndex = blockLnkIndex;
|
||||||
if (lnkIndex) {
|
if (lnkIndex) {
|
||||||
lnkFrom = blockFrom;
|
lnkFrom = blockFrom;
|
||||||
clickHandlerStartCallback();
|
const auto handler = _links.at(lnkIndex - 1);
|
||||||
|
clickHandlerStartCallback(handler
|
||||||
|
? handler->getTextEntity().type
|
||||||
|
: EntityType::Invalid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (blockSpoilerIndex != spoilerIndex) {
|
||||||
|
if (spoilerIndex) {
|
||||||
|
auto rangeFrom = qMax(selection.from, spoilerFrom);
|
||||||
|
auto rangeTo = qMin(selection.to, blockFrom);
|
||||||
|
if (rangeTo > rangeFrom) { // handle click handler
|
||||||
|
const auto r = base::StringViewMid(_text, rangeFrom, rangeTo - rangeFrom);
|
||||||
|
// Ignore links that are partially copied.
|
||||||
|
const auto handler = (spoilerFrom != rangeFrom || blockFrom != rangeTo)
|
||||||
|
? nullptr
|
||||||
|
: _spoilers.at(spoilerIndex - 1);
|
||||||
|
const auto type = EntityType::Spoiler;
|
||||||
|
clickHandlerFinishCallback(r, handler, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spoilerIndex = blockSpoilerIndex;
|
||||||
|
if (spoilerIndex) {
|
||||||
|
spoilerFrom = blockFrom;
|
||||||
|
clickHandlerStartCallback(EntityType::Spoiler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3241,6 +3571,7 @@ TextForMimeData String::toText(
|
||||||
result.rich.entities.insert(i, std::move(entity));
|
result.rich.entities.insert(i, std::move(entity));
|
||||||
};
|
};
|
||||||
auto linkStart = 0;
|
auto linkStart = 0;
|
||||||
|
auto spoilerStart = 0;
|
||||||
auto markdownTrackers = composeEntities
|
auto markdownTrackers = composeEntities
|
||||||
? std::vector<MarkdownTagTracker>{
|
? std::vector<MarkdownTagTracker>{
|
||||||
{ TextBlockFItalic, EntityType::Italic },
|
{ TextBlockFItalic, EntityType::Italic },
|
||||||
|
|
@ -3249,7 +3580,7 @@ TextForMimeData String::toText(
|
||||||
{ TextBlockFUnderline, EntityType::Underline },
|
{ TextBlockFUnderline, EntityType::Underline },
|
||||||
{ TextBlockFStrikeOut, EntityType::StrikeOut },
|
{ TextBlockFStrikeOut, EntityType::StrikeOut },
|
||||||
{ TextBlockFCode, EntityType::Code }, // #TODO entities
|
{ TextBlockFCode, EntityType::Code }, // #TODO entities
|
||||||
{ TextBlockFPre, EntityType::Pre }
|
{ TextBlockFPre, EntityType::Pre },
|
||||||
} : std::vector<MarkdownTagTracker>();
|
} : std::vector<MarkdownTagTracker>();
|
||||||
const auto flagsChangeCallback = [&](int32 oldFlags, int32 newFlags) {
|
const auto flagsChangeCallback = [&](int32 oldFlags, int32 newFlags) {
|
||||||
if (!composeEntities) {
|
if (!composeEntities) {
|
||||||
|
|
@ -3267,12 +3598,26 @@ TextForMimeData String::toText(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const auto clickHandlerStartCallback = [&] {
|
const auto clickHandlerStartCallback = [&](EntityType type) {
|
||||||
|
if (type == EntityType::Spoiler) {
|
||||||
|
spoilerStart = result.rich.text.size();
|
||||||
|
} else {
|
||||||
linkStart = result.rich.text.size();
|
linkStart = result.rich.text.size();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
const auto clickHandlerFinishCallback = [&](
|
const auto clickHandlerFinishCallback = [&](
|
||||||
QStringView inText,
|
QStringView inText,
|
||||||
const ClickHandlerPtr &handler) {
|
const ClickHandlerPtr &handler,
|
||||||
|
EntityType type) {
|
||||||
|
if (type == EntityType::Spoiler) {
|
||||||
|
insertEntity({
|
||||||
|
type,
|
||||||
|
spoilerStart,
|
||||||
|
int(result.rich.text.size() - spoilerStart),
|
||||||
|
QString(),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!handler || (!composeExpanded && !composeEntities)) {
|
if (!handler || (!composeExpanded && !composeEntities)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -3344,7 +3689,8 @@ IsolatedEmoji String::toIsolatedEmoji() const {
|
||||||
auto result = IsolatedEmoji();
|
auto result = IsolatedEmoji();
|
||||||
const auto skip = (_blocks.empty()
|
const auto skip = (_blocks.empty()
|
||||||
|| _blocks.back()->type() != TextBlockTSkip) ? 0 : 1;
|
|| _blocks.back()->type() != TextBlockTSkip) ? 0 : 1;
|
||||||
if (_blocks.size() > kIsolatedEmojiLimit + skip) {
|
if ((_blocks.size() > kIsolatedEmojiLimit + skip)
|
||||||
|
|| !_spoilers.empty()) {
|
||||||
return IsolatedEmoji();
|
return IsolatedEmoji();
|
||||||
}
|
}
|
||||||
auto index = 0;
|
auto index = 0;
|
||||||
|
|
@ -3369,10 +3715,19 @@ void String::clear() {
|
||||||
void String::clearFields() {
|
void String::clearFields() {
|
||||||
_blocks.clear();
|
_blocks.clear();
|
||||||
_links.clear();
|
_links.clear();
|
||||||
|
_spoilers.clear();
|
||||||
_maxWidth = _minHeight = 0;
|
_maxWidth = _minHeight = 0;
|
||||||
_startDir = Qt::LayoutDirectionAuto;
|
_startDir = Qt::LayoutDirectionAuto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ClickHandlerPtr String::spoilerLink(uint16 spoilerIndex) const {
|
||||||
|
if (spoilerIndex) {
|
||||||
|
const auto &handler = _spoilers.at(spoilerIndex - 1);
|
||||||
|
return (handler && !handler->shown()) ? handler : nullptr;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
bool IsWordSeparator(QChar ch) {
|
bool IsWordSeparator(QChar ch) {
|
||||||
switch (ch.unicode()) {
|
switch (ch.unicode()) {
|
||||||
case QChar::Space:
|
case QChar::Space:
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,12 @@
|
||||||
#include <private/qfixed_p.h>
|
#include <private/qfixed_p.h>
|
||||||
#include <any>
|
#include <any>
|
||||||
|
|
||||||
|
class SpoilerClickHandler;
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
static const auto kQEllipsis = QStringLiteral("...");
|
||||||
|
} // namespace Ui
|
||||||
|
|
||||||
static const QChar TextCommand(0x0010);
|
static const QChar TextCommand(0x0010);
|
||||||
enum TextCommands {
|
enum TextCommands {
|
||||||
TextCommandBold = 0x01,
|
TextCommandBold = 0x01,
|
||||||
|
|
@ -30,6 +36,8 @@ enum TextCommands {
|
||||||
TextCommandLinkIndex = 0x0B, // 0 - NoLink
|
TextCommandLinkIndex = 0x0B, // 0 - NoLink
|
||||||
TextCommandLinkText = 0x0C,
|
TextCommandLinkText = 0x0C,
|
||||||
TextCommandSkipBlock = 0x0D,
|
TextCommandSkipBlock = 0x0D,
|
||||||
|
TextCommandSpoiler = 0x0E,
|
||||||
|
TextCommandNoSpoiler = 0x0F,
|
||||||
|
|
||||||
TextCommandLangTag = 0x20,
|
TextCommandLangTag = 0x20,
|
||||||
};
|
};
|
||||||
|
|
@ -131,6 +139,11 @@ public:
|
||||||
|
|
||||||
void setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk);
|
void setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk);
|
||||||
bool hasLinks() const;
|
bool hasLinks() const;
|
||||||
|
void setSpoiler(
|
||||||
|
uint16 lnkIndex,
|
||||||
|
const std::shared_ptr<SpoilerClickHandler> &lnk);
|
||||||
|
void setSpoilerShown(uint16 lnkIndex, bool shown);
|
||||||
|
int spoilersCount() const;
|
||||||
|
|
||||||
bool hasSkipBlock() const;
|
bool hasSkipBlock() const;
|
||||||
bool updateSkipBlock(int width, int height);
|
bool updateSkipBlock(int width, int height);
|
||||||
|
|
@ -205,6 +218,8 @@ private:
|
||||||
// it is also called from move constructor / assignment operator
|
// it is also called from move constructor / assignment operator
|
||||||
void clearFields();
|
void clearFields();
|
||||||
|
|
||||||
|
ClickHandlerPtr spoilerLink(uint16 spoilerIndex) const;
|
||||||
|
|
||||||
TextForMimeData toText(
|
TextForMimeData toText(
|
||||||
TextSelection selection,
|
TextSelection selection,
|
||||||
bool composeExpanded,
|
bool composeExpanded,
|
||||||
|
|
@ -220,8 +235,15 @@ private:
|
||||||
TextBlocks _blocks;
|
TextBlocks _blocks;
|
||||||
TextLinks _links;
|
TextLinks _links;
|
||||||
|
|
||||||
|
QVector<std::shared_ptr<SpoilerClickHandler>> _spoilers;
|
||||||
|
|
||||||
Qt::LayoutDirection _startDir = Qt::LayoutDirectionAuto;
|
Qt::LayoutDirection _startDir = Qt::LayoutDirectionAuto;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
std::array<QImage, 4> corners;
|
||||||
|
QColor color;
|
||||||
|
} _spoilerCache, _spoilerShownCache;
|
||||||
|
|
||||||
friend class Parser;
|
friend class Parser;
|
||||||
friend class Renderer;
|
friend class Renderer;
|
||||||
|
|
||||||
|
|
@ -264,4 +286,8 @@ QString textcmdLink(ushort lnkIndex, const QString &text);
|
||||||
QString textcmdLink(const QString &url, const QString &text);
|
QString textcmdLink(const QString &url, const QString &text);
|
||||||
QString textcmdStartSemibold();
|
QString textcmdStartSemibold();
|
||||||
QString textcmdStopSemibold();
|
QString textcmdStopSemibold();
|
||||||
|
|
||||||
|
QString textcmdStartSpoiler();
|
||||||
|
QString textcmdStopSpoiler();
|
||||||
|
|
||||||
const QChar *textSkipCommand(const QChar *from, const QChar *end, bool canLink = true);
|
const QChar *textSkipCommand(const QChar *from, const QChar *end, bool canLink = true);
|
||||||
|
|
|
||||||
|
|
@ -340,6 +340,63 @@ bool BlockParser::isLineBreak(
|
||||||
return lineBreak;
|
return lineBreak;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AbstractBlock::AbstractBlock(
|
||||||
|
const style::font &font,
|
||||||
|
const QString &str,
|
||||||
|
uint16 from,
|
||||||
|
uint16 length,
|
||||||
|
uchar flags,
|
||||||
|
uint16 lnkIndex,
|
||||||
|
uint16 spoilerIndex)
|
||||||
|
: _from(from)
|
||||||
|
, _flags((flags & 0xFF) | ((lnkIndex & 0xFFFF) << 12))
|
||||||
|
, _spoilerIndex(spoilerIndex) {
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16 AbstractBlock::from() const {
|
||||||
|
return _from;
|
||||||
|
}
|
||||||
|
|
||||||
|
int AbstractBlock::width() const {
|
||||||
|
return _width.toInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
int AbstractBlock::rpadding() const {
|
||||||
|
return _rpadding.toInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
QFixed AbstractBlock::f_width() const {
|
||||||
|
return _width;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFixed AbstractBlock::f_rpadding() const {
|
||||||
|
return _rpadding;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16 AbstractBlock::lnkIndex() const {
|
||||||
|
return (_flags >> 12) & 0xFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AbstractBlock::setLnkIndex(uint16 lnkIndex) {
|
||||||
|
_flags = (_flags & ~(0xFFFF << 12)) | (lnkIndex << 12);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16 AbstractBlock::spoilerIndex() const {
|
||||||
|
return _spoilerIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AbstractBlock::setSpoilerIndex(uint16 spoilerIndex) {
|
||||||
|
_spoilerIndex = spoilerIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextBlockType AbstractBlock::type() const {
|
||||||
|
return TextBlockType((_flags >> 8) & 0x0F);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 AbstractBlock::flags() const {
|
||||||
|
return (_flags & 0xFFF);
|
||||||
|
}
|
||||||
|
|
||||||
QFixed AbstractBlock::f_rbearing() const {
|
QFixed AbstractBlock::f_rbearing() const {
|
||||||
return (type() == TextBlockTText)
|
return (type() == TextBlockTText)
|
||||||
? static_cast<const TextBlock*>(this)->real_f_rbearing()
|
? static_cast<const TextBlock*>(this)->real_f_rbearing()
|
||||||
|
|
@ -353,8 +410,9 @@ TextBlock::TextBlock(
|
||||||
uint16 from,
|
uint16 from,
|
||||||
uint16 length,
|
uint16 length,
|
||||||
uchar flags,
|
uchar flags,
|
||||||
uint16 lnkIndex)
|
uint16 lnkIndex,
|
||||||
: AbstractBlock(font, str, from, length, flags, lnkIndex) {
|
uint16 spoilerIndex)
|
||||||
|
: AbstractBlock(font, str, from, length, flags, lnkIndex, spoilerIndex) {
|
||||||
_flags |= ((TextBlockTText & 0x0F) << 8);
|
_flags |= ((TextBlockTText & 0x0F) << 8);
|
||||||
if (length) {
|
if (length) {
|
||||||
style::font blockFont = font;
|
style::font blockFont = font;
|
||||||
|
|
@ -395,8 +453,9 @@ EmojiBlock::EmojiBlock(
|
||||||
uint16 length,
|
uint16 length,
|
||||||
uchar flags,
|
uchar flags,
|
||||||
uint16 lnkIndex,
|
uint16 lnkIndex,
|
||||||
|
uint16 spoilerIndex,
|
||||||
EmojiPtr emoji)
|
EmojiPtr emoji)
|
||||||
: AbstractBlock(font, str, from, length, flags, lnkIndex)
|
: AbstractBlock(font, str, from, length, flags, lnkIndex, spoilerIndex)
|
||||||
, _emoji(emoji) {
|
, _emoji(emoji) {
|
||||||
_flags |= ((TextBlockTEmoji & 0x0F) << 8);
|
_flags |= ((TextBlockTEmoji & 0x0F) << 8);
|
||||||
_width = int(st::emojiSize + 2 * st::emojiPadding);
|
_width = int(st::emojiSize + 2 * st::emojiPadding);
|
||||||
|
|
|
||||||
|
|
@ -34,38 +34,23 @@ enum TextBlockFlags {
|
||||||
|
|
||||||
class AbstractBlock {
|
class AbstractBlock {
|
||||||
public:
|
public:
|
||||||
uint16 from() const {
|
uint16 from() const;
|
||||||
return _from;
|
int width() const;
|
||||||
}
|
int rpadding() const;
|
||||||
int width() const {
|
QFixed f_width() const;
|
||||||
return _width.toInt();
|
QFixed f_rpadding() const;
|
||||||
}
|
|
||||||
int rpadding() const {
|
|
||||||
return _rpadding.toInt();
|
|
||||||
}
|
|
||||||
QFixed f_width() const {
|
|
||||||
return _width;
|
|
||||||
}
|
|
||||||
QFixed f_rpadding() const {
|
|
||||||
return _rpadding;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should be virtual, but optimized throught type() call.
|
// Should be virtual, but optimized throught type() call.
|
||||||
QFixed f_rbearing() const;
|
QFixed f_rbearing() const;
|
||||||
|
|
||||||
uint16 lnkIndex() const {
|
uint16 lnkIndex() const;
|
||||||
return (_flags >> 12) & 0xFFFF;
|
void setLnkIndex(uint16 lnkIndex);
|
||||||
}
|
|
||||||
void setLnkIndex(uint16 lnkIndex) {
|
|
||||||
_flags = (_flags & ~(0xFFFF << 12)) | (lnkIndex << 12);
|
|
||||||
}
|
|
||||||
|
|
||||||
TextBlockType type() const {
|
uint16 spoilerIndex() const;
|
||||||
return TextBlockType((_flags >> 8) & 0x0F);
|
void setSpoilerIndex(uint16 spoilerIndex);
|
||||||
}
|
|
||||||
int32 flags() const {
|
TextBlockType type() const;
|
||||||
return (_flags & 0xFF);
|
int32 flags() const;
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
AbstractBlock(
|
AbstractBlock(
|
||||||
|
|
@ -74,15 +59,15 @@ protected:
|
||||||
uint16 from,
|
uint16 from,
|
||||||
uint16 length,
|
uint16 length,
|
||||||
uchar flags,
|
uchar flags,
|
||||||
uint16 lnkIndex)
|
uint16 lnkIndex,
|
||||||
: _from(from)
|
uint16 spoilerIndex);
|
||||||
, _flags((flags & 0xFF) | ((lnkIndex & 0xFFFF) << 12)) {
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16 _from = 0;
|
uint16 _from = 0;
|
||||||
|
|
||||||
uint32 _flags = 0; // 4 bits empty, 16 bits lnkIndex, 4 bits type, 8 bits flags
|
uint32 _flags = 0; // 4 bits empty, 16 bits lnkIndex, 4 bits type, 8 bits flags
|
||||||
|
|
||||||
|
uint16 _spoilerIndex = 0;
|
||||||
|
|
||||||
QFixed _width = 0;
|
QFixed _width = 0;
|
||||||
|
|
||||||
// Right padding: spaces after the last content of the block (like a word).
|
// Right padding: spaces after the last content of the block (like a word).
|
||||||
|
|
@ -102,8 +87,9 @@ public:
|
||||||
uint16 from,
|
uint16 from,
|
||||||
uint16 length,
|
uint16 length,
|
||||||
uchar flags,
|
uchar flags,
|
||||||
uint16 lnkIndex)
|
uint16 lnkIndex,
|
||||||
: AbstractBlock(font, str, from, length, flags, lnkIndex) {
|
uint16 spoilerIndex)
|
||||||
|
: AbstractBlock(font, str, from, length, flags, lnkIndex, spoilerIndex) {
|
||||||
_flags |= ((TextBlockTNewline & 0x0F) << 8);
|
_flags |= ((TextBlockTNewline & 0x0F) << 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -163,7 +149,8 @@ public:
|
||||||
uint16 from,
|
uint16 from,
|
||||||
uint16 length,
|
uint16 length,
|
||||||
uchar flags,
|
uchar flags,
|
||||||
uint16 lnkIndex);
|
uint16 lnkIndex,
|
||||||
|
uint16 spoilerIndex);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QFixed real_f_rbearing() const {
|
QFixed real_f_rbearing() const {
|
||||||
|
|
@ -189,6 +176,7 @@ public:
|
||||||
uint16 length,
|
uint16 length,
|
||||||
uchar flags,
|
uchar flags,
|
||||||
uint16 lnkIndex,
|
uint16 lnkIndex,
|
||||||
|
uint16 spoilerIndex,
|
||||||
EmojiPtr emoji);
|
EmojiPtr emoji);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
@ -208,8 +196,9 @@ public:
|
||||||
uint16 from,
|
uint16 from,
|
||||||
int32 w,
|
int32 w,
|
||||||
int32 h,
|
int32 h,
|
||||||
uint16 lnkIndex)
|
uint16 lnkIndex,
|
||||||
: AbstractBlock(font, str, from, 1, 0, lnkIndex)
|
uint16 spoilerIndex)
|
||||||
|
: AbstractBlock(font, str, from, 1, 0, lnkIndex, spoilerIndex)
|
||||||
, _height(h) {
|
, _height(h) {
|
||||||
_flags |= ((TextBlockTSkip & 0x0F) << 8);
|
_flags |= ((TextBlockTSkip & 0x0F) << 8);
|
||||||
_width = w;
|
_width = w;
|
||||||
|
|
@ -325,8 +314,9 @@ public:
|
||||||
uint16 from,
|
uint16 from,
|
||||||
uint16 length,
|
uint16 length,
|
||||||
uchar flags,
|
uchar flags,
|
||||||
uint16 lnkIndex) {
|
uint16 lnkIndex,
|
||||||
return New<NewlineBlock>(font, str, from, length, flags, lnkIndex);
|
uint16 spoilerIndex) {
|
||||||
|
return New<NewlineBlock>(font, str, from, length, flags, lnkIndex, spoilerIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] static Block Text(
|
[[nodiscard]] static Block Text(
|
||||||
|
|
@ -336,7 +326,8 @@ public:
|
||||||
uint16 from,
|
uint16 from,
|
||||||
uint16 length,
|
uint16 length,
|
||||||
uchar flags,
|
uchar flags,
|
||||||
uint16 lnkIndex) {
|
uint16 lnkIndex,
|
||||||
|
uint16 spoilerIndex) {
|
||||||
return New<TextBlock>(
|
return New<TextBlock>(
|
||||||
font,
|
font,
|
||||||
str,
|
str,
|
||||||
|
|
@ -344,7 +335,8 @@ public:
|
||||||
from,
|
from,
|
||||||
length,
|
length,
|
||||||
flags,
|
flags,
|
||||||
lnkIndex);
|
lnkIndex,
|
||||||
|
spoilerIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] static Block Emoji(
|
[[nodiscard]] static Block Emoji(
|
||||||
|
|
@ -354,6 +346,7 @@ public:
|
||||||
uint16 length,
|
uint16 length,
|
||||||
uchar flags,
|
uchar flags,
|
||||||
uint16 lnkIndex,
|
uint16 lnkIndex,
|
||||||
|
uint16 spoilerIndex,
|
||||||
EmojiPtr emoji) {
|
EmojiPtr emoji) {
|
||||||
return New<EmojiBlock>(
|
return New<EmojiBlock>(
|
||||||
font,
|
font,
|
||||||
|
|
@ -362,6 +355,7 @@ public:
|
||||||
length,
|
length,
|
||||||
flags,
|
flags,
|
||||||
lnkIndex,
|
lnkIndex,
|
||||||
|
spoilerIndex,
|
||||||
emoji);
|
emoji);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -371,8 +365,9 @@ public:
|
||||||
uint16 from,
|
uint16 from,
|
||||||
int32 w,
|
int32 w,
|
||||||
int32 h,
|
int32 h,
|
||||||
uint16 lnkIndex) {
|
uint16 lnkIndex,
|
||||||
return New<SkipBlock>(font, str, from, w, h, lnkIndex);
|
uint16 spoilerIndex) {
|
||||||
|
return New<SkipBlock>(font, str, from, w, h, lnkIndex, spoilerIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename FinalBlock>
|
template <typename FinalBlock>
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@
|
||||||
namespace TextUtilities {
|
namespace TextUtilities {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto kTagSeparator = '$';
|
||||||
|
|
||||||
using namespace Ui::Text;
|
using namespace Ui::Text;
|
||||||
|
|
||||||
QString ExpressionMailNameAtEnd() {
|
QString ExpressionMailNameAtEnd() {
|
||||||
|
|
@ -68,6 +70,10 @@ QString SeparatorsMono() {
|
||||||
return Separators(QString::fromUtf8("*~/"));
|
return Separators(QString::fromUtf8("*~/"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString SeparatorsSpoiler() {
|
||||||
|
return Separators(QString::fromUtf8("|*~/"));
|
||||||
|
}
|
||||||
|
|
||||||
QString ExpressionHashtag() {
|
QString ExpressionHashtag() {
|
||||||
return QString::fromUtf8("(^|[") + ExpressionSeparators(QString::fromUtf8("`\\*/")) + QString::fromUtf8("])#[\\w]{2,64}([\\W]|$)");
|
return QString::fromUtf8("(^|[") + ExpressionSeparators(QString::fromUtf8("`\\*/")) + QString::fromUtf8("])#[\\w]{2,64}([\\W]|$)");
|
||||||
}
|
}
|
||||||
|
|
@ -1269,6 +1275,14 @@ QString MarkdownPreBadAfter() {
|
||||||
return QString::fromLatin1("`");
|
return QString::fromLatin1("`");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString MarkdownSpoilerGoodBefore() {
|
||||||
|
return SeparatorsSpoiler();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString MarkdownSpoilerBadAfter() {
|
||||||
|
return QString::fromLatin1("|");
|
||||||
|
}
|
||||||
|
|
||||||
bool IsValidProtocol(const QString &protocol) {
|
bool IsValidProtocol(const QString &protocol) {
|
||||||
static const auto list = CreateValidProtocols();
|
static const auto list = CreateValidProtocols();
|
||||||
return list.contains(base::crc32(protocol.constData(), protocol.size() * sizeof(QChar)));
|
return list.contains(base::crc32(protocol.constData(), protocol.size() * sizeof(QChar)));
|
||||||
|
|
@ -1279,9 +1293,17 @@ bool IsValidTopDomain(const QString &protocol) {
|
||||||
return list.contains(base::crc32(protocol.constData(), protocol.size() * sizeof(QChar)));
|
return list.contains(base::crc32(protocol.constData(), protocol.size() * sizeof(QChar)));
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Clean(const QString &text) {
|
QString Clean(const QString &text, bool keepSpoilers) {
|
||||||
auto result = text;
|
auto result = text;
|
||||||
for (auto s = text.unicode(), ch = s, e = text.unicode() + text.size(); ch != e; ++ch) {
|
for (auto s = text.unicode(), ch = s, e = text.unicode() + text.size(); ch != e; ++ch) {
|
||||||
|
if (keepSpoilers && (*ch == TextCommand)) {
|
||||||
|
if ((*(ch + 1) == TextCommandSpoiler)
|
||||||
|
|| (*(ch - 1) == TextCommandSpoiler)
|
||||||
|
|| (*(ch + 1) == TextCommandNoSpoiler)
|
||||||
|
|| (*(ch - 1) == TextCommandNoSpoiler)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (*ch == TextCommand) {
|
if (*ch == TextCommand) {
|
||||||
result[int(ch - s)] = QChar::Space;
|
result[int(ch - s)] = QChar::Space;
|
||||||
}
|
}
|
||||||
|
|
@ -2041,17 +2063,21 @@ QString JoinTag(const QList<QStringView> &list) {
|
||||||
result.append(list.front());
|
result.append(list.front());
|
||||||
for (auto i = 1, count = int(list.size()); i != count; ++i) {
|
for (auto i = 1, count = int(list.size()); i != count; ++i) {
|
||||||
if (!IsSeparateTag(list[i])) {
|
if (!IsSeparateTag(list[i])) {
|
||||||
result.append('|').append(list[i]);
|
result.append(kTagSeparator).append(list[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QList<QStringView> SplitTags(const QString &tag) {
|
||||||
|
return QStringView(tag).split(kTagSeparator);
|
||||||
|
}
|
||||||
|
|
||||||
QString TagWithRemoved(const QString &tag, const QString &removed) {
|
QString TagWithRemoved(const QString &tag, const QString &removed) {
|
||||||
if (tag == removed) {
|
if (tag == removed) {
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
auto list = QStringView(tag).split('|');
|
auto list = SplitTags(tag);
|
||||||
list.erase(ranges::remove(list, QStringView(removed)), list.end());
|
list.erase(ranges::remove(list, QStringView(removed)), list.end());
|
||||||
return JoinTag(list);
|
return JoinTag(list);
|
||||||
}
|
}
|
||||||
|
|
@ -2060,7 +2086,7 @@ QString TagWithAdded(const QString &tag, const QString &added) {
|
||||||
if (tag.isEmpty() || tag == added) {
|
if (tag.isEmpty() || tag == added) {
|
||||||
return added;
|
return added;
|
||||||
}
|
}
|
||||||
auto list = QStringView(tag).split('|');
|
auto list = SplitTags(tag);
|
||||||
const auto ref = QStringView(added);
|
const auto ref = QStringView(added);
|
||||||
if (list.contains(ref)) {
|
if (list.contains(ref)) {
|
||||||
return tag;
|
return tag;
|
||||||
|
|
@ -2081,6 +2107,7 @@ EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags) {
|
||||||
EntityType::Italic,
|
EntityType::Italic,
|
||||||
EntityType::Underline,
|
EntityType::Underline,
|
||||||
EntityType::StrikeOut,
|
EntityType::StrikeOut,
|
||||||
|
EntityType::Spoiler,
|
||||||
EntityType::Code,
|
EntityType::Code,
|
||||||
EntityType::Pre,
|
EntityType::Pre,
|
||||||
};
|
};
|
||||||
|
|
@ -2173,7 +2200,7 @@ EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags) {
|
||||||
};
|
};
|
||||||
const auto stateForTag = [&](const QString &tag) {
|
const auto stateForTag = [&](const QString &tag) {
|
||||||
auto result = State();
|
auto result = State();
|
||||||
const auto list = QStringView(tag).split('|');
|
const auto list = SplitTags(tag);
|
||||||
for (const auto &single : list) {
|
for (const auto &single : list) {
|
||||||
if (single == Ui::InputField::kTagBold) {
|
if (single == Ui::InputField::kTagBold) {
|
||||||
result.set(EntityType::Bold);
|
result.set(EntityType::Bold);
|
||||||
|
|
@ -2187,6 +2214,8 @@ EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags) {
|
||||||
result.set(EntityType::Code);
|
result.set(EntityType::Code);
|
||||||
} else if (single == Ui::InputField::kTagPre) {
|
} else if (single == Ui::InputField::kTagPre) {
|
||||||
result.set(EntityType::Pre);
|
result.set(EntityType::Pre);
|
||||||
|
} else if (single == Ui::InputField::kTagSpoiler) {
|
||||||
|
result.set(EntityType::Spoiler);
|
||||||
} else {
|
} else {
|
||||||
result.link = single.toString();
|
result.link = single.toString();
|
||||||
}
|
}
|
||||||
|
|
@ -2275,6 +2304,7 @@ TextWithTags::Tags ConvertEntitiesToTextTags(
|
||||||
break;
|
break;
|
||||||
case EntityType::Code: push(Ui::InputField::kTagCode); break; // #TODO entities
|
case EntityType::Code: push(Ui::InputField::kTagCode); break; // #TODO entities
|
||||||
case EntityType::Pre: push(Ui::InputField::kTagPre); break;
|
case EntityType::Pre: push(Ui::InputField::kTagPre); break;
|
||||||
|
case EntityType::Spoiler: push(Ui::InputField::kTagSpoiler); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!toRemove.empty()) {
|
if (!toRemove.empty()) {
|
||||||
|
|
@ -2302,6 +2332,36 @@ void SetClipboardText(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString TextWithSpoilerCommands(const TextWithEntities &textWithEntities) {
|
||||||
|
auto text = textWithEntities.text;
|
||||||
|
auto offset = 0;
|
||||||
|
const auto start = textcmdStartSpoiler();
|
||||||
|
const auto stop = textcmdStopSpoiler();
|
||||||
|
for (const auto &e : textWithEntities.entities) {
|
||||||
|
if (e.type() == EntityType::Spoiler) {
|
||||||
|
text.insert(e.offset() + offset, start);
|
||||||
|
offset += start.size();
|
||||||
|
text.insert(e.offset() + e.length() + offset, stop);
|
||||||
|
offset += stop.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString CutTextWithCommands(
|
||||||
|
QString text,
|
||||||
|
int length,
|
||||||
|
const QString &start,
|
||||||
|
const QString &stop) {
|
||||||
|
text = text.mid(0, length);
|
||||||
|
const auto lastStart = text.lastIndexOf(start);
|
||||||
|
const auto lastStop = text.lastIndexOf(stop);
|
||||||
|
const auto additional = ((lastStart == -1) || (lastStart < lastStop))
|
||||||
|
? QString()
|
||||||
|
: stop;
|
||||||
|
return text + additional + Ui::kQEllipsis;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace TextUtilities
|
} // namespace TextUtilities
|
||||||
|
|
||||||
EntityInText::EntityInText(
|
EntityInText::EntityInText(
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ enum class EntityType : uchar {
|
||||||
StrikeOut,
|
StrikeOut,
|
||||||
Code, // inline
|
Code, // inline
|
||||||
Pre, // block
|
Pre, // block
|
||||||
|
Spoiler,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class EntityLinkShown : uchar {
|
enum class EntityLinkShown : uchar {
|
||||||
|
|
@ -293,9 +294,11 @@ QString MarkdownCodeGoodBefore();
|
||||||
QString MarkdownCodeBadAfter();
|
QString MarkdownCodeBadAfter();
|
||||||
QString MarkdownPreGoodBefore();
|
QString MarkdownPreGoodBefore();
|
||||||
QString MarkdownPreBadAfter();
|
QString MarkdownPreBadAfter();
|
||||||
|
QString MarkdownSpoilerGoodBefore();
|
||||||
|
QString MarkdownSpoilerBadAfter();
|
||||||
|
|
||||||
// Text preprocess.
|
// Text preprocess.
|
||||||
QString Clean(const QString &text);
|
QString Clean(const QString &text, bool keepSpoilers = false);
|
||||||
QString EscapeForRichParsing(const QString &text);
|
QString EscapeForRichParsing(const QString &text);
|
||||||
QString SingleLine(const QString &text);
|
QString SingleLine(const QString &text);
|
||||||
TextWithEntities SingleLine(const TextWithEntities &text);
|
TextWithEntities SingleLine(const TextWithEntities &text);
|
||||||
|
|
@ -366,6 +369,7 @@ inline const auto kMentionTagStart = qstr("mention://user.");
|
||||||
[[nodiscard]] bool IsMentionLink(QStringView link);
|
[[nodiscard]] bool IsMentionLink(QStringView link);
|
||||||
[[nodiscard]] bool IsSeparateTag(QStringView tag);
|
[[nodiscard]] bool IsSeparateTag(QStringView tag);
|
||||||
[[nodiscard]] QString JoinTag(const QList<QStringView> &list);
|
[[nodiscard]] QString JoinTag(const QList<QStringView> &list);
|
||||||
|
[[nodiscard]] QList<QStringView> SplitTags(const QString &tag);
|
||||||
[[nodiscard]] QString TagWithRemoved(
|
[[nodiscard]] QString TagWithRemoved(
|
||||||
const QString &tag,
|
const QString &tag,
|
||||||
const QString &removed);
|
const QString &removed);
|
||||||
|
|
@ -380,4 +384,12 @@ void SetClipboardText(
|
||||||
const TextForMimeData &text,
|
const TextForMimeData &text,
|
||||||
QClipboard::Mode mode = QClipboard::Clipboard);
|
QClipboard::Mode mode = QClipboard::Clipboard);
|
||||||
|
|
||||||
|
[[nodiscard]] QString TextWithSpoilerCommands(
|
||||||
|
const TextWithEntities &textWithEntities);
|
||||||
|
[[nodiscard]] QString CutTextWithCommands(
|
||||||
|
QString text,
|
||||||
|
int length,
|
||||||
|
const QString &start,
|
||||||
|
const QString &stop);
|
||||||
|
|
||||||
} // namespace TextUtilities
|
} // namespace TextUtilities
|
||||||
|
|
|
||||||
|
|
@ -380,7 +380,22 @@ Checkbox::Checkbox(
|
||||||
const style::Check &checkSt)
|
const style::Check &checkSt)
|
||||||
: Checkbox(
|
: Checkbox(
|
||||||
parent,
|
parent,
|
||||||
text,
|
rpl::single(text) | rpl::map(TextWithEntities::Simple),
|
||||||
|
st,
|
||||||
|
std::make_unique<CheckView>(
|
||||||
|
checkSt,
|
||||||
|
checked)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Checkbox::Checkbox(
|
||||||
|
QWidget *parent,
|
||||||
|
const TextWithEntities &text,
|
||||||
|
bool checked,
|
||||||
|
const style::Checkbox &st,
|
||||||
|
const style::Check &checkSt)
|
||||||
|
: Checkbox(
|
||||||
|
parent,
|
||||||
|
rpl::single(text),
|
||||||
st,
|
st,
|
||||||
std::make_unique<CheckView>(
|
std::make_unique<CheckView>(
|
||||||
checkSt,
|
checkSt,
|
||||||
|
|
@ -395,7 +410,7 @@ Checkbox::Checkbox(
|
||||||
const style::Toggle &toggleSt)
|
const style::Toggle &toggleSt)
|
||||||
: Checkbox(
|
: Checkbox(
|
||||||
parent,
|
parent,
|
||||||
rpl::single(text),
|
rpl::single(text) | rpl::map(TextWithEntities::Simple),
|
||||||
st,
|
st,
|
||||||
std::make_unique<ToggleView>(
|
std::make_unique<ToggleView>(
|
||||||
toggleSt,
|
toggleSt,
|
||||||
|
|
@ -410,7 +425,7 @@ Checkbox::Checkbox(
|
||||||
const style::Check &checkSt)
|
const style::Check &checkSt)
|
||||||
: Checkbox(
|
: Checkbox(
|
||||||
parent,
|
parent,
|
||||||
std::move(text),
|
std::move(text) | rpl::map(TextWithEntities::Simple),
|
||||||
st,
|
st,
|
||||||
std::make_unique<CheckView>(
|
std::make_unique<CheckView>(
|
||||||
checkSt,
|
checkSt,
|
||||||
|
|
@ -425,7 +440,7 @@ Checkbox::Checkbox(
|
||||||
const style::Toggle &toggleSt)
|
const style::Toggle &toggleSt)
|
||||||
: Checkbox(
|
: Checkbox(
|
||||||
parent,
|
parent,
|
||||||
std::move(text),
|
std::move(text) | rpl::map(TextWithEntities::Simple),
|
||||||
st,
|
st,
|
||||||
std::make_unique<ToggleView>(
|
std::make_unique<ToggleView>(
|
||||||
toggleSt,
|
toggleSt,
|
||||||
|
|
@ -439,14 +454,14 @@ Checkbox::Checkbox(
|
||||||
std::unique_ptr<AbstractCheckView> check)
|
std::unique_ptr<AbstractCheckView> check)
|
||||||
: Checkbox(
|
: Checkbox(
|
||||||
parent,
|
parent,
|
||||||
rpl::single(text),
|
rpl::single(text) | rpl::map(TextWithEntities::Simple),
|
||||||
st,
|
st,
|
||||||
std::move(check)) {
|
std::move(check)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Checkbox::Checkbox(
|
Checkbox::Checkbox(
|
||||||
QWidget *parent,
|
QWidget *parent,
|
||||||
rpl::producer<QString> &&text,
|
rpl::producer<TextWithEntities> &&text,
|
||||||
const style::Checkbox &st,
|
const style::Checkbox &st,
|
||||||
std::unique_ptr<AbstractCheckView> check)
|
std::unique_ptr<AbstractCheckView> check)
|
||||||
: RippleButton(parent, st.ripple)
|
: RippleButton(parent, st.ripple)
|
||||||
|
|
@ -462,8 +477,17 @@ Checkbox::Checkbox(
|
||||||
setCursor(style::cur_pointer);
|
setCursor(style::cur_pointer);
|
||||||
std::move(
|
std::move(
|
||||||
text
|
text
|
||||||
) | rpl::start_with_next([=](QString &&value) {
|
) | rpl::start_with_next([=](TextWithEntities &&value) {
|
||||||
setText(std::move(value));
|
if (value.entities.empty()) {
|
||||||
|
setText(base::take(value.text));
|
||||||
|
} else {
|
||||||
|
_text.setMarkedText(
|
||||||
|
_st.style,
|
||||||
|
std::move(value),
|
||||||
|
_checkboxRichOptions);
|
||||||
|
resizeToText();
|
||||||
|
update();
|
||||||
|
}
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -140,6 +140,12 @@ public:
|
||||||
bool checked = false,
|
bool checked = false,
|
||||||
const style::Checkbox &st = st::defaultCheckbox,
|
const style::Checkbox &st = st::defaultCheckbox,
|
||||||
const style::Check &checkSt = st::defaultCheck);
|
const style::Check &checkSt = st::defaultCheck);
|
||||||
|
Checkbox(
|
||||||
|
QWidget *parent,
|
||||||
|
const TextWithEntities &text,
|
||||||
|
bool checked = false,
|
||||||
|
const style::Checkbox &st = st::defaultCheckbox,
|
||||||
|
const style::Check &checkSt = st::defaultCheck);
|
||||||
Checkbox(
|
Checkbox(
|
||||||
QWidget *parent,
|
QWidget *parent,
|
||||||
const QString &text,
|
const QString &text,
|
||||||
|
|
@ -165,7 +171,7 @@ public:
|
||||||
std::unique_ptr<AbstractCheckView> check);
|
std::unique_ptr<AbstractCheckView> check);
|
||||||
Checkbox(
|
Checkbox(
|
||||||
QWidget *parent,
|
QWidget *parent,
|
||||||
rpl::producer<QString> &&text,
|
rpl::producer<TextWithEntities> &&text,
|
||||||
const style::Checkbox &st,
|
const style::Checkbox &st,
|
||||||
std::unique_ptr<AbstractCheckView> check);
|
std::unique_ptr<AbstractCheckView> check);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ const auto &kTagUnderline = InputField::kTagUnderline;
|
||||||
const auto &kTagStrikeOut = InputField::kTagStrikeOut;
|
const auto &kTagStrikeOut = InputField::kTagStrikeOut;
|
||||||
const auto &kTagCode = InputField::kTagCode;
|
const auto &kTagCode = InputField::kTagCode;
|
||||||
const auto &kTagPre = InputField::kTagPre;
|
const auto &kTagPre = InputField::kTagPre;
|
||||||
|
const auto &kTagSpoiler = InputField::kTagSpoiler;
|
||||||
const auto kTagCheckLinkMeta = QString("^:/:/:^");
|
const auto kTagCheckLinkMeta = QString("^:/:/:^");
|
||||||
const auto kNewlineChars = QString("\r\n")
|
const auto kNewlineChars = QString("\r\n")
|
||||||
+ QChar(0xfdd0) // QTextBeginningOfFrame
|
+ QChar(0xfdd0) // QTextBeginningOfFrame
|
||||||
|
|
@ -129,7 +130,7 @@ bool IsNewline(QChar ch) {
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
auto found = false;
|
auto found = false;
|
||||||
for (const auto &single : QStringView(existing.id).split('|')) {
|
for (const auto &single : TextUtilities::SplitTags(existing.id)) {
|
||||||
const auto normalized = (single == QStringView(kTagPre))
|
const auto normalized = (single == QStringView(kTagPre))
|
||||||
? QStringView(kTagCode)
|
? QStringView(kTagCode)
|
||||||
: single;
|
: single;
|
||||||
|
|
@ -234,6 +235,7 @@ constexpr auto kTagItalicIndex = 1;
|
||||||
constexpr auto kTagStrikeOutIndex = 2;
|
constexpr auto kTagStrikeOutIndex = 2;
|
||||||
constexpr auto kTagCodeIndex = 3;
|
constexpr auto kTagCodeIndex = 3;
|
||||||
constexpr auto kTagPreIndex = 4;
|
constexpr auto kTagPreIndex = 4;
|
||||||
|
constexpr auto kTagSpoilerIndex = 5;
|
||||||
constexpr auto kInvalidPosition = std::numeric_limits<int>::max() / 2;
|
constexpr auto kInvalidPosition = std::numeric_limits<int>::max() / 2;
|
||||||
|
|
||||||
class TagSearchItem {
|
class TagSearchItem {
|
||||||
|
|
@ -377,6 +379,13 @@ const std::vector<TagStartExpression> &TagStartExpressions() {
|
||||||
TextUtilities::MarkdownPreBadAfter(),
|
TextUtilities::MarkdownPreBadAfter(),
|
||||||
TextUtilities::MarkdownPreGoodBefore()
|
TextUtilities::MarkdownPreGoodBefore()
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
kTagSpoiler,
|
||||||
|
TextUtilities::MarkdownSpoilerGoodBefore(),
|
||||||
|
TextUtilities::MarkdownSpoilerBadAfter(),
|
||||||
|
TextUtilities::MarkdownSpoilerBadAfter(),
|
||||||
|
TextUtilities::MarkdownSpoilerGoodBefore()
|
||||||
|
},
|
||||||
};
|
};
|
||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
|
|
@ -389,6 +398,7 @@ const std::map<QString, int> &TagIndices() {
|
||||||
{ kTagStrikeOut, kTagStrikeOutIndex },
|
{ kTagStrikeOut, kTagStrikeOutIndex },
|
||||||
{ kTagCode, kTagCodeIndex },
|
{ kTagCode, kTagCodeIndex },
|
||||||
{ kTagPre, kTagPreIndex },
|
{ kTagPre, kTagPreIndex },
|
||||||
|
{ kTagSpoiler, kTagSpoilerIndex },
|
||||||
};
|
};
|
||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
|
|
@ -706,6 +716,7 @@ QTextCharFormat PrepareTagFormat(
|
||||||
auto result = QTextCharFormat();
|
auto result = QTextCharFormat();
|
||||||
auto font = st.font;
|
auto font = st.font;
|
||||||
auto color = std::optional<style::color>();
|
auto color = std::optional<style::color>();
|
||||||
|
auto bg = std::optional<style::color>();
|
||||||
const auto applyOne = [&](QStringView tag) {
|
const auto applyOne = [&](QStringView tag) {
|
||||||
if (IsValidMarkdownLink(tag)) {
|
if (IsValidMarkdownLink(tag)) {
|
||||||
color = st::defaultTextPalette.linkFg;
|
color = st::defaultTextPalette.linkFg;
|
||||||
|
|
@ -720,14 +731,19 @@ QTextCharFormat PrepareTagFormat(
|
||||||
} else if (tag == kTagCode || tag == kTagPre) {
|
} else if (tag == kTagCode || tag == kTagPre) {
|
||||||
color = st::defaultTextPalette.monoFg;
|
color = st::defaultTextPalette.monoFg;
|
||||||
font = font->monospace();
|
font = font->monospace();
|
||||||
|
} else if (tag == kTagSpoiler) {
|
||||||
|
bg = st::defaultTextPalette.spoilerActiveBg;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
for (const auto &tag : QStringView(tag).split('|')) {
|
for (const auto &tag : TextUtilities::SplitTags(tag)) {
|
||||||
applyOne(tag);
|
applyOne(tag);
|
||||||
}
|
}
|
||||||
result.setFont(font);
|
result.setFont(font);
|
||||||
result.setForeground(color.value_or(st.textFg));
|
result.setForeground(color.value_or(st.textFg));
|
||||||
result.setProperty(kTagProperty, tag);
|
result.setProperty(kTagProperty, tag);
|
||||||
|
if (bg) {
|
||||||
|
result.setBackground(*bg);
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -850,6 +866,7 @@ const QString InputField::kTagUnderline = QStringLiteral("^^");
|
||||||
const QString InputField::kTagStrikeOut = QStringLiteral("~~");
|
const QString InputField::kTagStrikeOut = QStringLiteral("~~");
|
||||||
const QString InputField::kTagCode = QStringLiteral("`");
|
const QString InputField::kTagCode = QStringLiteral("`");
|
||||||
const QString InputField::kTagPre = QStringLiteral("```");
|
const QString InputField::kTagPre = QStringLiteral("```");
|
||||||
|
const QString InputField::kTagSpoiler = QStringLiteral("||");
|
||||||
|
|
||||||
class InputField::Inner final : public QTextEdit {
|
class InputField::Inner final : public QTextEdit {
|
||||||
public:
|
public:
|
||||||
|
|
@ -1185,8 +1202,9 @@ void FlatInput::refreshPlaceholder(const QString &text) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void FlatInput::contextMenuEvent(QContextMenuEvent *e) {
|
void FlatInput::contextMenuEvent(QContextMenuEvent *e) {
|
||||||
if (auto menu = createStandardContextMenu()) {
|
if (const auto menu = createStandardContextMenu()) {
|
||||||
(new PopupMenu(this, menu))->popup(e->globalPos());
|
_contextMenu = base::make_unique_q<PopupMenu>(this, menu);
|
||||||
|
_contextMenu->popup(e->globalPos());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2869,6 +2887,8 @@ bool InputField::handleMarkdownKey(QKeyEvent *e) {
|
||||||
toggleSelectionMarkdown(kTagStrikeOut);
|
toggleSelectionMarkdown(kTagStrikeOut);
|
||||||
} else if (matches(kMonospaceSequence)) {
|
} else if (matches(kMonospaceSequence)) {
|
||||||
toggleSelectionMarkdown(kTagCode);
|
toggleSelectionMarkdown(kTagCode);
|
||||||
|
} else if (matches(kSpoilerSequence)) {
|
||||||
|
toggleSelectionMarkdown(kTagSpoiler);
|
||||||
} else if (matches(kClearFormatSequence)) {
|
} else if (matches(kClearFormatSequence)) {
|
||||||
clearSelectionMarkdown();
|
clearSelectionMarkdown();
|
||||||
} else if (matches(kEditLinkSequence) && _editLinkCallback) {
|
} else if (matches(kEditLinkSequence) && _editLinkCallback) {
|
||||||
|
|
@ -2953,8 +2973,8 @@ auto InputField::selectionEditLinkData(EditLinkSelection selection) const
|
||||||
};
|
};
|
||||||
const auto stateTagHasLink = [&](const State &state) {
|
const auto stateTagHasLink = [&](const State &state) {
|
||||||
const auto tag = stateTag(state);
|
const auto tag = stateTag(state);
|
||||||
return (tag == link) || QStringView(tag).split('|').contains(
|
return (tag == link)
|
||||||
QStringView(link));
|
|| TextUtilities::SplitTags(tag).contains(QStringView(link));
|
||||||
};
|
};
|
||||||
const auto stateStart = [&](const State &state) {
|
const auto stateStart = [&](const State &state) {
|
||||||
return state.i.fragment().position();
|
return state.i.fragment().position();
|
||||||
|
|
@ -3162,7 +3182,7 @@ void InputField::commitInstantReplacement(
|
||||||
const auto currentTag = cursor.charFormat().property(
|
const auto currentTag = cursor.charFormat().property(
|
||||||
kTagProperty
|
kTagProperty
|
||||||
).toString();
|
).toString();
|
||||||
const auto currentTags = QStringView(currentTag).split('|');
|
const auto currentTags = TextUtilities::SplitTags(currentTag);
|
||||||
if (currentTags.contains(QStringView(kTagPre))
|
if (currentTags.contains(QStringView(kTagPre))
|
||||||
|| currentTags.contains(QStringView(kTagCode))) {
|
|| currentTags.contains(QStringView(kTagCode))) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -3628,6 +3648,7 @@ void InputField::addMarkdownActions(
|
||||||
addtag(integration.phraseFormattingUnderline(), QKeySequence::Underline, kTagUnderline);
|
addtag(integration.phraseFormattingUnderline(), QKeySequence::Underline, kTagUnderline);
|
||||||
addtag(integration.phraseFormattingStrikeOut(), kStrikeOutSequence, kTagStrikeOut);
|
addtag(integration.phraseFormattingStrikeOut(), kStrikeOutSequence, kTagStrikeOut);
|
||||||
addtag(integration.phraseFormattingMonospace(), kMonospaceSequence, kTagCode);
|
addtag(integration.phraseFormattingMonospace(), kMonospaceSequence, kTagCode);
|
||||||
|
addtag(integration.phraseFormattingSpoiler(), kSpoilerSequence, kTagSpoiler);
|
||||||
|
|
||||||
if (_editLinkCallback) {
|
if (_editLinkCallback) {
|
||||||
submenu->addSeparator();
|
submenu->addSeparator();
|
||||||
|
|
@ -4075,7 +4096,8 @@ void MaskedInputField::setPlaceholder(rpl::producer<QString> placeholder) {
|
||||||
|
|
||||||
void MaskedInputField::contextMenuEvent(QContextMenuEvent *e) {
|
void MaskedInputField::contextMenuEvent(QContextMenuEvent *e) {
|
||||||
if (const auto menu = createStandardContextMenu()) {
|
if (const auto menu = createStandardContextMenu()) {
|
||||||
(new PopupMenu(this, menu))->popup(e->globalPos());
|
_contextMenu = base::make_unique_q<PopupMenu>(this, menu);
|
||||||
|
_contextMenu->popup(e->globalPos());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ const auto kClearFormatSequence = QKeySequence("ctrl+shift+n");
|
||||||
const auto kStrikeOutSequence = QKeySequence("ctrl+shift+x");
|
const auto kStrikeOutSequence = QKeySequence("ctrl+shift+x");
|
||||||
const auto kMonospaceSequence = QKeySequence("ctrl+shift+m");
|
const auto kMonospaceSequence = QKeySequence("ctrl+shift+m");
|
||||||
const auto kEditLinkSequence = QKeySequence("ctrl+k");
|
const auto kEditLinkSequence = QKeySequence("ctrl+k");
|
||||||
|
const auto kSpoilerSequence = QKeySequence("ctrl+shift+p");
|
||||||
|
|
||||||
class PopupMenu;
|
class PopupMenu;
|
||||||
|
|
||||||
|
|
@ -140,6 +141,9 @@ private:
|
||||||
QTimer _touchTimer;
|
QTimer _touchTimer;
|
||||||
bool _touchPress, _touchRightButton, _touchMove;
|
bool _touchPress, _touchRightButton, _touchMove;
|
||||||
QPoint _touchStart;
|
QPoint _touchStart;
|
||||||
|
|
||||||
|
base::unique_qptr<PopupMenu> _contextMenu;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class InputField : public RpWidget {
|
class InputField : public RpWidget {
|
||||||
|
|
@ -171,6 +175,7 @@ public:
|
||||||
static const QString kTagStrikeOut;
|
static const QString kTagStrikeOut;
|
||||||
static const QString kTagCode;
|
static const QString kTagCode;
|
||||||
static const QString kTagPre;
|
static const QString kTagPre;
|
||||||
|
static const QString kTagSpoiler;
|
||||||
|
|
||||||
InputField(
|
InputField(
|
||||||
QWidget *parent,
|
QWidget *parent,
|
||||||
|
|
@ -700,6 +705,9 @@ private:
|
||||||
bool _touchRightButton = false;
|
bool _touchRightButton = false;
|
||||||
bool _touchMove = false;
|
bool _touchMove = false;
|
||||||
QPoint _touchStart;
|
QPoint _touchStart;
|
||||||
|
|
||||||
|
base::unique_qptr<PopupMenu> _contextMenu;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class PasswordInput : public MaskedInputField {
|
class PasswordInput : public MaskedInputField {
|
||||||
|
|
|
||||||
|
|
@ -68,10 +68,12 @@ not_null<QAction*> Menu::addAction(
|
||||||
|
|
||||||
not_null<QAction*> Menu::addAction(
|
not_null<QAction*> Menu::addAction(
|
||||||
const QString &text,
|
const QString &text,
|
||||||
std::unique_ptr<QMenu> submenu) {
|
std::unique_ptr<QMenu> submenu,
|
||||||
|
const style::icon *icon,
|
||||||
|
const style::icon *iconOver) {
|
||||||
const auto action = new QAction(text, this);
|
const auto action = new QAction(text, this);
|
||||||
action->setMenu(submenu.release());
|
action->setMenu(submenu.release());
|
||||||
return addAction(action, nullptr, nullptr);
|
return addAction(action, icon, iconOver);
|
||||||
}
|
}
|
||||||
|
|
||||||
not_null<QAction*> Menu::addAction(
|
not_null<QAction*> Menu::addAction(
|
||||||
|
|
@ -365,10 +367,21 @@ void Menu::mouseReleaseEvent(QMouseEvent *e) {
|
||||||
|
|
||||||
void Menu::handleMousePress(QPoint globalPosition) {
|
void Menu::handleMousePress(QPoint globalPosition) {
|
||||||
handleMouseMove(globalPosition);
|
handleMouseMove(globalPosition);
|
||||||
|
const auto margins = style::margins(0, _st.skip, 0, _st.skip);
|
||||||
|
const auto inner = rect().marginsRemoved(margins);
|
||||||
|
const auto localPosition = mapFromGlobal(globalPosition);
|
||||||
|
const auto pressed = (inner.contains(localPosition)
|
||||||
|
&& _lastSelectedByMouse)
|
||||||
|
? findSelectedAction()
|
||||||
|
: nullptr;
|
||||||
|
if (pressed) {
|
||||||
|
pressed->setClicked();
|
||||||
|
} else {
|
||||||
if (_mousePressDelegate) {
|
if (_mousePressDelegate) {
|
||||||
_mousePressDelegate(globalPosition);
|
_mousePressDelegate(globalPosition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Menu::handleMouseRelease(QPoint globalPosition) {
|
void Menu::handleMouseRelease(QPoint globalPosition) {
|
||||||
if (!rect().contains(mapFromGlobal(globalPosition))
|
if (!rect().contains(mapFromGlobal(globalPosition))
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,9 @@ public:
|
||||||
const style::icon *iconOver = nullptr);
|
const style::icon *iconOver = nullptr);
|
||||||
not_null<QAction*> addAction(
|
not_null<QAction*> addAction(
|
||||||
const QString &text,
|
const QString &text,
|
||||||
std::unique_ptr<QMenu> submenu);
|
std::unique_ptr<QMenu> submenu,
|
||||||
|
const style::icon *icon = nullptr,
|
||||||
|
const style::icon *iconOver = nullptr);
|
||||||
not_null<QAction*> addSeparator();
|
not_null<QAction*> addSeparator();
|
||||||
void clearActions();
|
void clearActions();
|
||||||
void finishAnimating();
|
void finishAnimating();
|
||||||
|
|
|
||||||
|
|
@ -357,14 +357,24 @@ not_null<QAction*> PopupMenu::addAction(
|
||||||
return _menu->addAction(std::move(widget));
|
return _menu->addAction(std::move(widget));
|
||||||
}
|
}
|
||||||
|
|
||||||
not_null<QAction*> PopupMenu::addAction(const QString &text, Fn<void()> callback, const style::icon *icon, const style::icon *iconOver) {
|
not_null<QAction*> PopupMenu::addAction(
|
||||||
|
const QString &text,
|
||||||
|
Fn<void()> callback,
|
||||||
|
const style::icon *icon,
|
||||||
|
const style::icon *iconOver) {
|
||||||
return _menu->addAction(text, std::move(callback), icon, iconOver);
|
return _menu->addAction(text, std::move(callback), icon, iconOver);
|
||||||
}
|
}
|
||||||
|
|
||||||
not_null<QAction*> PopupMenu::addAction(
|
not_null<QAction*> PopupMenu::addAction(
|
||||||
const QString &text,
|
const QString &text,
|
||||||
std::unique_ptr<PopupMenu> submenu) {
|
std::unique_ptr<PopupMenu> submenu,
|
||||||
const auto action = _menu->addAction(text, std::make_unique<QMenu>());
|
const style::icon *icon,
|
||||||
|
const style::icon *iconOver) {
|
||||||
|
const auto action = _menu->addAction(
|
||||||
|
text,
|
||||||
|
std::make_unique<QMenu>(),
|
||||||
|
icon,
|
||||||
|
iconOver);
|
||||||
const auto saved = _submenus.emplace(
|
const auto saved = _submenus.emplace(
|
||||||
action,
|
action,
|
||||||
base::unique_qptr<PopupMenu>(submenu.release())
|
base::unique_qptr<PopupMenu>(submenu.release())
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,16 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
not_null<QAction*> addAction(base::unique_qptr<Menu::ItemBase> widget);
|
not_null<QAction*> addAction(base::unique_qptr<Menu::ItemBase> widget);
|
||||||
not_null<QAction*> addAction(const QString &text, Fn<void()> callback, const style::icon *icon = nullptr, const style::icon *iconOver = nullptr);
|
not_null<QAction*> addAction(
|
||||||
not_null<QAction*> addAction(const QString &text, std::unique_ptr<PopupMenu> submenu);
|
const QString &text,
|
||||||
|
Fn<void()> callback,
|
||||||
|
const style::icon *icon = nullptr,
|
||||||
|
const style::icon *iconOver = nullptr);
|
||||||
|
not_null<QAction*> addAction(
|
||||||
|
const QString &text,
|
||||||
|
std::unique_ptr<PopupMenu> submenu,
|
||||||
|
const style::icon *icon = nullptr,
|
||||||
|
const style::icon *iconOver = nullptr);
|
||||||
not_null<QAction*> addSeparator();
|
not_null<QAction*> addSeparator();
|
||||||
void clearActions();
|
void clearActions();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -857,7 +857,7 @@ defaultMenu: Menu {
|
||||||
|
|
||||||
arrow: defaultMenuArrow;
|
arrow: defaultMenuArrow;
|
||||||
|
|
||||||
widthMin: 180px;
|
widthMin: 140px;
|
||||||
widthMax: 300px;
|
widthMax: 300px;
|
||||||
|
|
||||||
ripple: defaultRippleAnimation;
|
ripple: defaultRippleAnimation;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue