Merge remote-tracking branch 'tdesktop/master' into v2.1
This commit is contained in:
commit
499af3e481
17 changed files with 248 additions and 133 deletions
|
|
@ -23,8 +23,6 @@ TextStyle {
|
||||||
lineHeight: pixels;
|
lineHeight: pixels;
|
||||||
}
|
}
|
||||||
|
|
||||||
semibold: "Open Sans Semibold";
|
|
||||||
|
|
||||||
fsize: 13px;
|
fsize: 13px;
|
||||||
normalFont: font(fsize);
|
normalFont: font(fsize);
|
||||||
semiboldFont: font(fsize semibold);
|
semiboldFont: font(fsize semibold);
|
||||||
|
|
@ -271,6 +269,10 @@ inlineResultsMinWidth: 48px;
|
||||||
inlineDurationMargin: 3px;
|
inlineDurationMargin: 3px;
|
||||||
|
|
||||||
toastTextStyle: defaultTextStyle;
|
toastTextStyle: defaultTextStyle;
|
||||||
|
toastTextPalette: TextPalette(defaultTextPalette) {
|
||||||
|
linkFg: mediaviewTextLinkFg;
|
||||||
|
monoFg: mediaviewCaptionFg;
|
||||||
|
}
|
||||||
toastMaxWidth: 480px;
|
toastMaxWidth: 480px;
|
||||||
toastMinMargin: 13px;
|
toastMinMargin: 13px;
|
||||||
toastPadding: margins(19px, 13px, 19px, 12px);
|
toastPadding: margins(19px, 13px, 19px, 12px);
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,44 @@ void UrlClickHandler::Open(QString url, QVariant context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool UrlClickHandler::IsSuspicious(const QString &url) {
|
||||||
|
static const auto Check1 = QRegularExpression(
|
||||||
|
"^(https?://)?([^/#\\:]+)([/#\\:]|$)",
|
||||||
|
QRegularExpression::CaseInsensitiveOption);
|
||||||
|
const auto match1 = Check1.match(url);
|
||||||
|
if (!match1.hasMatch()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto domain = match1.capturedRef(2);
|
||||||
|
static const auto Check2 = QRegularExpression("^(.*)\\.[a-zA-Z]+$");
|
||||||
|
const auto match2 = Check2.match(domain);
|
||||||
|
if (!match2.hasMatch()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto part = match2.capturedRef(1);
|
||||||
|
static const auto Check3 = QRegularExpression("[^a-zA-Z0-9\\.\\-]");
|
||||||
|
return Check3.match(part).hasMatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QString UrlClickHandler::ShowEncoded(const QString &url) {
|
||||||
|
if (const auto u = QUrl(url); u.isValid()) {
|
||||||
|
return QString::fromUtf8(u.toEncoded());
|
||||||
|
}
|
||||||
|
static const auto Check1 = QRegularExpression(
|
||||||
|
"^(https?://)?([^/#\\:]+)([/#\\:]|$)",
|
||||||
|
QRegularExpression::CaseInsensitiveOption);
|
||||||
|
if (const auto match1 = Check1.match(url); match1.hasMatch()) {
|
||||||
|
const auto domain = match1.captured(1).append(match1.capturedRef(2));
|
||||||
|
if (const auto u = QUrl(domain); u.isValid()) {
|
||||||
|
return QString(
|
||||||
|
).append(QString::fromUtf8(u.toEncoded())
|
||||||
|
).append(url.midRef(match1.capturedEnd(2)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
auto UrlClickHandler::getTextEntity() const -> TextEntity {
|
auto UrlClickHandler::getTextEntity() const -> TextEntity {
|
||||||
const auto type = isEmail() ? EntityType::Email : EntityType::Url;
|
const auto type = isEmail() ? EntityType::Email : EntityType::Url;
|
||||||
return { type, _originalUrl };
|
return { type, _originalUrl };
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,8 @@ public:
|
||||||
const auto at = url.indexOf('@'), slash = url.indexOf('/');
|
const auto at = url.indexOf('@'), slash = url.indexOf('/');
|
||||||
return ((at > 0) && (slash < 0 || slash > at));
|
return ((at > 0) && (slash < 0 || slash > at));
|
||||||
}
|
}
|
||||||
|
[[nodiscard]] static bool IsSuspicious(const QString &url);
|
||||||
|
[[nodiscard]] static QString ShowEncoded(const QString &url);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
QString url() const override;
|
QString url() const override;
|
||||||
|
|
|
||||||
|
|
@ -12,16 +12,12 @@
|
||||||
#include "styles/palette.h"
|
#include "styles/palette.h"
|
||||||
|
|
||||||
#include <QtGui/QPainter>
|
#include <QtGui/QPainter>
|
||||||
#include <QtGui/QFontDatabase>
|
|
||||||
|
|
||||||
#include <rpl/event_stream.h>
|
#include <rpl/event_stream.h>
|
||||||
#include <rpl/variable.h>
|
#include <rpl/variable.h>
|
||||||
|
|
||||||
namespace style {
|
namespace style {
|
||||||
namespace internal {
|
namespace internal {
|
||||||
|
|
||||||
extern bool UseSystemFont;
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr auto kMinContrastAlpha = 64;
|
constexpr auto kMinContrastAlpha = 64;
|
||||||
|
|
@ -31,7 +27,6 @@ constexpr auto kContrastDeltaL = 64;
|
||||||
auto PaletteChanges = rpl::event_stream<>();
|
auto PaletteChanges = rpl::event_stream<>();
|
||||||
auto ShortAnimationRunning = rpl::variable<bool>(false);
|
auto ShortAnimationRunning = rpl::variable<bool>(false);
|
||||||
auto RunningShortAnimations = 0;
|
auto RunningShortAnimations = 0;
|
||||||
auto ResolvedMonospaceFont = style::font();
|
|
||||||
|
|
||||||
std::vector<internal::ModuleBase*> &StyleModules() {
|
std::vector<internal::ModuleBase*> &StyleModules() {
|
||||||
static auto result = std::vector<internal::ModuleBase*>();
|
static auto result = std::vector<internal::ModuleBase*>();
|
||||||
|
|
@ -44,37 +39,8 @@ void startModules(int scale) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ResolveMonospaceFont() {
|
|
||||||
auto family = QString();
|
|
||||||
const auto tryFont = [&](const QString &attempt) {
|
|
||||||
if (family.isEmpty()
|
|
||||||
&& !QFontInfo(QFont(attempt)).family().trimmed().compare(
|
|
||||||
attempt,
|
|
||||||
Qt::CaseInsensitive)) {
|
|
||||||
family = attempt;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (!CustomMonospaceFont.isEmpty()) {
|
|
||||||
tryFont(CustomMonospaceFont);
|
|
||||||
}
|
|
||||||
if (!UseSystemFont) {
|
|
||||||
tryFont("Consolas");
|
|
||||||
tryFont("Liberation Mono");
|
|
||||||
tryFont("Menlo");
|
|
||||||
tryFont("Courier");
|
|
||||||
}
|
|
||||||
if (family.isEmpty()) {
|
|
||||||
const auto type = QFontDatabase::FixedFont;
|
|
||||||
family = QFontDatabase::systemFont(type).family();
|
|
||||||
}
|
|
||||||
const auto size = st::normalFont->f.pixelSize();
|
|
||||||
ResolvedMonospaceFont = style::font(size, 0, family)->monospace();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
QString CustomMonospaceFont;
|
|
||||||
|
|
||||||
void registerModule(ModuleBase *module) {
|
void registerModule(ModuleBase *module) {
|
||||||
StyleModules().push_back(module);
|
StyleModules().push_back(module);
|
||||||
}
|
}
|
||||||
|
|
@ -96,7 +62,6 @@ void StopShortAnimation() {
|
||||||
void startManager(int scale) {
|
void startManager(int scale) {
|
||||||
internal::registerFontFamily("Open Sans");
|
internal::registerFontFamily("Open Sans");
|
||||||
internal::startModules(scale);
|
internal::startModules(scale);
|
||||||
internal::ResolveMonospaceFont();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void stopManager() {
|
void stopManager() {
|
||||||
|
|
@ -116,10 +81,6 @@ rpl::producer<bool> ShortAnimationPlaying() {
|
||||||
return internal::ShortAnimationRunning.value();
|
return internal::ShortAnimationRunning.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
const style::font &MonospaceFont() {
|
|
||||||
return internal::ResolvedMonospaceFont;
|
|
||||||
}
|
|
||||||
|
|
||||||
void colorizeImage(const QImage &src, QColor c, QImage *outResult, QRect srcRect, QPoint dstPoint) {
|
void colorizeImage(const QImage &src, QColor c, QImage *outResult, QRect srcRect, QPoint dstPoint) {
|
||||||
// In background_box ColorizePattern we use the fact that
|
// In background_box ColorizePattern we use the fact that
|
||||||
// colorizeImage takes only first byte of the mask, so it
|
// colorizeImage takes only first byte of the mask, so it
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,6 @@
|
||||||
namespace style {
|
namespace style {
|
||||||
namespace internal {
|
namespace internal {
|
||||||
|
|
||||||
extern QString CustomMonospaceFont;
|
|
||||||
|
|
||||||
// Objects of derived classes are created in global scope.
|
// Objects of derived classes are created in global scope.
|
||||||
// They call [un]registerModule() in [de|con]structor.
|
// They call [un]registerModule() in [de|con]structor.
|
||||||
class ModuleBase {
|
class ModuleBase {
|
||||||
|
|
@ -45,8 +43,6 @@ void NotifyPaletteChanged();
|
||||||
|
|
||||||
[[nodiscard]] rpl::producer<bool> ShortAnimationPlaying();
|
[[nodiscard]] rpl::producer<bool> ShortAnimationPlaying();
|
||||||
|
|
||||||
const style::font &MonospaceFont();
|
|
||||||
|
|
||||||
// *outResult must be r.width() x r.height(), ARGB32_Premultiplied.
|
// *outResult must be r.width() x r.height(), ARGB32_Premultiplied.
|
||||||
// QRect(0, 0, src.width(), src.height()) must contain r.
|
// QRect(0, 0, src.width(), src.height()) must contain r.
|
||||||
void colorizeImage(const QImage &src, QColor c, QImage *outResult, QRect srcRect = QRect(), QPoint dstPoint = QPoint(0, 0));
|
void colorizeImage(const QImage &src, QColor c, QImage *outResult, QRect srcRect = QRect(), QPoint dstPoint = QPoint(0, 0));
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
#include <QtCore/QVector>
|
#include <QtCore/QVector>
|
||||||
#include <QtGui/QFontInfo>
|
#include <QtGui/QFontInfo>
|
||||||
#include <QtGui/QFontDatabase>
|
#include <QtGui/QFontDatabase>
|
||||||
|
#include <QtWidgets/QApplication>
|
||||||
|
|
||||||
void style_InitFontsResource() {
|
void style_InitFontsResource() {
|
||||||
#ifndef DESKTOP_APP_USE_PACKAGED_FONTS
|
#ifndef DESKTOP_APP_USE_PACKAGED_FONTS
|
||||||
|
|
@ -77,11 +78,9 @@ bool ValidateFont(const QString &familyName, int flags = 0) {
|
||||||
const auto parsedFamily = ParseFamilyName(familyName);
|
const auto parsedFamily = ParseFamilyName(familyName);
|
||||||
|
|
||||||
QFont checkFont(parsedFamily);
|
QFont checkFont(parsedFamily);
|
||||||
checkFont.setPixelSize(13);
|
|
||||||
checkFont.setBold(flags & style::internal::FontBold);
|
checkFont.setBold(flags & style::internal::FontBold);
|
||||||
checkFont.setItalic(flags & style::internal::FontItalic);
|
checkFont.setItalic(flags & style::internal::FontItalic);
|
||||||
checkFont.setUnderline(flags & style::internal::FontUnderline);
|
checkFont.setUnderline(flags & style::internal::FontUnderline);
|
||||||
checkFont.setStyleStrategy(QFont::PreferQuality);
|
|
||||||
|
|
||||||
if (IsRealSemibold(familyName)) {
|
if (IsRealSemibold(familyName)) {
|
||||||
checkFont.setStyleName("Semibold");
|
checkFont.setStyleName("Semibold");
|
||||||
|
|
@ -126,6 +125,40 @@ bool LoadCustomFont(const QString &filePath, const QString &familyName, int flag
|
||||||
return ValidateFont(familyName, flags);
|
return ValidateFont(familyName, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString MonospaceFont() {
|
||||||
|
static const auto family = [&]() -> QString {
|
||||||
|
const auto tryFont = [&](const QString &attempt) {
|
||||||
|
const auto resolved = QFontInfo(QFont(attempt)).family();
|
||||||
|
return !resolved.trimmed().compare(attempt, Qt::CaseInsensitive);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tryFont(CustomMonospaceFont)) {
|
||||||
|
return CustomMonospaceFont;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef Q_OS_LINUX
|
||||||
|
if (!UseSystemFont) {
|
||||||
|
const auto kTryFirst = std::initializer_list<QString>{
|
||||||
|
"Consolas",
|
||||||
|
"Liberation Mono",
|
||||||
|
"Menlo",
|
||||||
|
"Courier"
|
||||||
|
};
|
||||||
|
for (const auto &family : kTryFirst) {
|
||||||
|
if (tryFont(family)) {
|
||||||
|
return family;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // !Q_OS_LINUX
|
||||||
|
|
||||||
|
const auto type = QFontDatabase::FixedFont;
|
||||||
|
return QFontDatabase::systemFont(type).family();
|
||||||
|
}();
|
||||||
|
|
||||||
|
return family;
|
||||||
|
}
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
FontTypeRegular = 0,
|
FontTypeRegular = 0,
|
||||||
FontTypeRegularItalic,
|
FontTypeRegularItalic,
|
||||||
|
|
@ -151,8 +184,8 @@ int32 FontTypeFlags[FontTypesCount] = {
|
||||||
FontItalic,
|
FontItalic,
|
||||||
FontBold,
|
FontBold,
|
||||||
FontBold | FontItalic,
|
FontBold | FontItalic,
|
||||||
0,
|
FontSemibold,
|
||||||
FontItalic,
|
FontSemibold | FontItalic,
|
||||||
};
|
};
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
QString FontTypeWindowsFallback[FontTypesCount] = {
|
QString FontTypeWindowsFallback[FontTypesCount] = {
|
||||||
|
|
@ -172,6 +205,7 @@ QString Overrides[FontTypesCount];
|
||||||
|
|
||||||
QString CustomMainFont;
|
QString CustomMainFont;
|
||||||
QString CustomSemiboldFont;
|
QString CustomSemiboldFont;
|
||||||
|
QString CustomMonospaceFont;
|
||||||
bool CustomSemiboldIsBold = false;
|
bool CustomSemiboldIsBold = false;
|
||||||
bool UseSystemFont = false;
|
bool UseSystemFont = false;
|
||||||
bool UseOriginalMetrics = false;
|
bool UseOriginalMetrics = false;
|
||||||
|
|
@ -216,6 +250,18 @@ void StartFonts() {
|
||||||
#endif // Q_OS_WIN
|
#endif // Q_OS_WIN
|
||||||
}
|
}
|
||||||
#endif // !DESKTOP_APP_USE_PACKAGED_FONTS
|
#endif // !DESKTOP_APP_USE_PACKAGED_FONTS
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
auto list = QStringList();
|
||||||
|
list.append("Microsoft YaHei");
|
||||||
|
list.append("Microsoft JhengHei UI");
|
||||||
|
list.append("Yu Gothic UI");
|
||||||
|
list.append("\xEB\xA7\x91\xEC\x9D\x80 \xEA\xB3\xA0\xEB\x94\x95");
|
||||||
|
for (const auto &name : FontTypeNames) {
|
||||||
|
QFont::insertSubstitutions(name, list);
|
||||||
|
}
|
||||||
|
#endif // Q_OS_WIN
|
||||||
|
|
||||||
#ifdef Q_OS_MAC
|
#ifdef Q_OS_MAC
|
||||||
auto list = QStringList();
|
auto list = QStringList();
|
||||||
list.append("STIXGeneral");
|
list.append("STIXGeneral");
|
||||||
|
|
@ -239,35 +285,41 @@ void StartFonts() {
|
||||||
Overrides[FontTypeSemiboldItalic] = CustomSemiboldFont;
|
Overrides[FontTypeSemiboldItalic] = CustomSemiboldFont;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto appFont = QApplication::font();
|
||||||
|
appFont.setStyleStrategy(QFont::PreferQuality);
|
||||||
|
QApplication::setFont(appFont);
|
||||||
|
|
||||||
if (integrationExists) {
|
if (integrationExists) {
|
||||||
Ui::Integration::Instance().startFontsEnd();
|
Ui::Integration::Instance().startFontsEnd();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString GetPossibleEmptyOverride(const QString &familyName, int32 flags) {
|
QString GetPossibleEmptyOverride(int32 flags) {
|
||||||
flags = flags & (FontBold | FontItalic);
|
flags = flags & (FontBold | FontSemibold | FontItalic);
|
||||||
if (familyName == qstr("Open Sans")) {
|
int32 flagsBold = flags & (FontBold | FontItalic);
|
||||||
if (flags == (FontBold | FontItalic)) {
|
int32 flagsSemibold = flags & (FontSemibold | FontItalic);
|
||||||
return Overrides[FontTypeBoldItalic];
|
if (flagsSemibold == (FontSemibold | FontItalic)) {
|
||||||
} else if (flags == FontBold) {
|
return Overrides[FontTypeSemiboldItalic];
|
||||||
return Overrides[FontTypeBold];
|
} else if (flagsSemibold == FontSemibold) {
|
||||||
} else if (flags == FontItalic) {
|
return Overrides[FontTypeSemibold];
|
||||||
return Overrides[FontTypeRegularItalic];
|
} else if (flagsBold == (FontBold | FontItalic)) {
|
||||||
} else if (flags == 0) {
|
return Overrides[FontTypeBoldItalic];
|
||||||
return Overrides[FontTypeRegular];
|
} else if (flagsBold == FontBold) {
|
||||||
}
|
return Overrides[FontTypeBold];
|
||||||
} else if (familyName == qstr("Open Sans Semibold")) {
|
} else if (flags == FontItalic) {
|
||||||
if (flags == FontItalic) {
|
return Overrides[FontTypeRegularItalic];
|
||||||
return Overrides[FontTypeSemiboldItalic];
|
} else if (flags == 0) {
|
||||||
} else if (flags == 0) {
|
return Overrides[FontTypeRegular];
|
||||||
return Overrides[FontTypeSemibold];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString GetFontOverride(const QString &familyName, int32 flags) {
|
QString GetFontOverride(int32 flags) {
|
||||||
const auto result = GetPossibleEmptyOverride(familyName, flags);
|
const auto familyName = (flags & FontSemibold)
|
||||||
|
? "Open Sans Semibold"
|
||||||
|
: "Open Sans";
|
||||||
|
|
||||||
|
const auto result = GetPossibleEmptyOverride(flags);
|
||||||
return result.isEmpty() ? familyName : result;
|
return result.isEmpty() ? familyName : result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -293,8 +345,8 @@ FontData::FontData(int size, uint32 flags, int family, Font *other)
|
||||||
, _size(size)
|
, _size(size)
|
||||||
, _flags(flags)
|
, _flags(flags)
|
||||||
, _family(family) {
|
, _family(family) {
|
||||||
const auto fontOverride = GetFontOverride(fontFamilies[family], flags);
|
const auto fontOverride = ParseFamilyName(GetFontOverride(flags));
|
||||||
const auto possibleEmptyOverride = GetPossibleEmptyOverride(fontFamilies[family], flags);
|
const auto overrideIsEmpty = GetPossibleEmptyOverride(flags).isEmpty();
|
||||||
|
|
||||||
if (other) {
|
if (other) {
|
||||||
memcpy(modified, other, sizeof(modified));
|
memcpy(modified, other, sizeof(modified));
|
||||||
|
|
@ -303,27 +355,29 @@ FontData::FontData(int size, uint32 flags, int family, Font *other)
|
||||||
}
|
}
|
||||||
modified[_flags] = Font(this);
|
modified[_flags] = Font(this);
|
||||||
|
|
||||||
if (!UseSystemFont || !possibleEmptyOverride.isEmpty() || (_flags & FontMonospace)) {
|
if (_flags & FontMonospace) {
|
||||||
|
f.setFamily(MonospaceFont());
|
||||||
|
} else if (!UseSystemFont || !overrideIsEmpty) {
|
||||||
f.setFamily(fontOverride);
|
f.setFamily(fontOverride);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_flags & FontSemibold) {
|
||||||
|
if (CustomSemiboldIsBold) {
|
||||||
|
f.setBold(true);
|
||||||
|
#ifdef DESKTOP_APP_USE_PACKAGED_FONTS
|
||||||
|
} else {
|
||||||
|
#else // DESKTOP_APP_USE_PACKAGED_FONTS
|
||||||
|
} else if (UseSystemFont) {
|
||||||
|
#endif // !DESKTOP_APP_USE_PACKAGED_FONTS
|
||||||
|
f.setWeight(QFont::DemiBold);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
f.setPixelSize(size);
|
f.setPixelSize(size);
|
||||||
if (_flags & FontBold) {
|
f.setBold(_flags & FontBold);
|
||||||
f.setBold(true);
|
|
||||||
} else if (fontFamilies[family] == "Open Sans Semibold" && CustomSemiboldIsBold) {
|
|
||||||
f.setBold(true);
|
|
||||||
} else if (fontFamilies[family] == "Open Sans Semibold" && UseSystemFont
|
|
||||||
&& possibleEmptyOverride.isEmpty()) {
|
|
||||||
f.setWeight(QFont::DemiBold);
|
|
||||||
#ifdef DESKTOP_APP_USE_PACKAGED_FONTS
|
|
||||||
} else if (fontFamilies[family] == "Open Sans Semibold") {
|
|
||||||
f.setWeight(QFont::DemiBold);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
f.setItalic(_flags & FontItalic);
|
f.setItalic(_flags & FontItalic);
|
||||||
f.setUnderline(_flags & FontUnderline);
|
f.setUnderline(_flags & FontUnderline);
|
||||||
f.setStrikeOut(_flags & FontStrikeOut);
|
f.setStrikeOut(_flags & FontStrikeOut);
|
||||||
f.setStyleStrategy(QFont::PreferQuality);
|
|
||||||
|
|
||||||
if (IsRealSemibold(fontOverride)) {
|
if (IsRealSemibold(fontOverride)) {
|
||||||
f.setStyleName("Semibold");
|
f.setStyleName("Semibold");
|
||||||
|
|
@ -365,6 +419,10 @@ Font FontData::strikeout(bool set) const {
|
||||||
return otherFlagsFont(FontStrikeOut, set);
|
return otherFlagsFont(FontStrikeOut, set);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Font FontData::semibold(bool set) const {
|
||||||
|
return otherFlagsFont(FontSemibold, set);
|
||||||
|
}
|
||||||
|
|
||||||
Font FontData::monospace(bool set) const {
|
Font FontData::monospace(bool set) const {
|
||||||
return otherFlagsFont(FontMonospace, set);
|
return otherFlagsFont(FontMonospace, set);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,13 @@ namespace internal {
|
||||||
|
|
||||||
extern QString CustomMainFont;
|
extern QString CustomMainFont;
|
||||||
extern QString CustomSemiboldFont;
|
extern QString CustomSemiboldFont;
|
||||||
|
extern QString CustomMonospaceFont;
|
||||||
extern bool CustomSemiboldIsBold;
|
extern bool CustomSemiboldIsBold;
|
||||||
extern bool UseSystemFont;
|
extern bool UseSystemFont;
|
||||||
extern bool UseOriginalMetrics;
|
extern bool UseOriginalMetrics;
|
||||||
|
|
||||||
void StartFonts();
|
void StartFonts();
|
||||||
[[nodiscard]] QString GetFontOverride(const QString &familyName, int32 flags = 0);
|
[[nodiscard]] QString GetFontOverride(int32 flags = 0);
|
||||||
|
|
||||||
void destroyFonts();
|
void destroyFonts();
|
||||||
int registerFontFamily(const QString &family);
|
int registerFontFamily(const QString &family);
|
||||||
|
|
@ -65,9 +66,10 @@ enum FontFlags {
|
||||||
FontItalic = 0x02,
|
FontItalic = 0x02,
|
||||||
FontUnderline = 0x04,
|
FontUnderline = 0x04,
|
||||||
FontStrikeOut = 0x08,
|
FontStrikeOut = 0x08,
|
||||||
FontMonospace = 0x10,
|
FontSemibold = 0x10,
|
||||||
|
FontMonospace = 0x20,
|
||||||
|
|
||||||
FontDifferentFlags = 0x20,
|
FontDifferentFlags = 0x40,
|
||||||
};
|
};
|
||||||
|
|
||||||
class FontData {
|
class FontData {
|
||||||
|
|
@ -90,6 +92,7 @@ public:
|
||||||
Font italic(bool set = true) const;
|
Font italic(bool set = true) const;
|
||||||
Font underline(bool set = true) const;
|
Font underline(bool set = true) const;
|
||||||
Font strikeout(bool set = true) const;
|
Font strikeout(bool set = true) const;
|
||||||
|
Font semibold(bool set = true) const;
|
||||||
Font monospace(bool set = true) const;
|
Font monospace(bool set = true) const;
|
||||||
|
|
||||||
int size() const;
|
int size() const;
|
||||||
|
|
|
||||||
|
|
@ -2014,24 +2014,18 @@ private:
|
||||||
}
|
}
|
||||||
auto result = f;
|
auto result = f;
|
||||||
if ((flags & TextBlockFPre) || (flags & TextBlockFCode)) {
|
if ((flags & TextBlockFPre) || (flags & TextBlockFCode)) {
|
||||||
result = style::MonospaceFont();
|
result = result->monospace();
|
||||||
if (result->size() != f->size()) {
|
|
||||||
result = style::font(f->size(), result->flags(), result->family());
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (flags & TextBlockFBold) {
|
if (flags & TextBlockFBold) {
|
||||||
result = result->bold();
|
result = result->bold();
|
||||||
} else if (flags & TextBlockFSemibold) {
|
} else if (flags & TextBlockFSemibold) {
|
||||||
result = st::semiboldFont;
|
result = result->semibold();
|
||||||
if (result->size() != f->size() || result->flags() != f->flags()) {
|
|
||||||
result = style::font(f->size(), f->flags(), result->family());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (flags & TextBlockFItalic) result = result->italic();
|
if (flags & TextBlockFItalic) result = result->italic();
|
||||||
if (flags & TextBlockFUnderline) result = result->underline();
|
if (flags & TextBlockFUnderline) result = result->underline();
|
||||||
if (flags & TextBlockFStrikeOut) result = result->strikeout();
|
if (flags & TextBlockFStrikeOut) result = result->strikeout();
|
||||||
if (flags & TextBlockFTilde) { // tilde fix in OpenSans
|
if (flags & TextBlockFTilde) { // tilde fix in OpenSans
|
||||||
result = st::semiboldFont;
|
result = result->semibold();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|
|
||||||
|
|
@ -322,24 +322,18 @@ TextBlock::TextBlock(const style::font &font, const QString &str, QFixed minResi
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((flags & TextBlockFPre) || (flags & TextBlockFCode)) {
|
if ((flags & TextBlockFPre) || (flags & TextBlockFCode)) {
|
||||||
blockFont = style::MonospaceFont();
|
blockFont = blockFont->monospace();
|
||||||
if (blockFont->size() != font->size()) {
|
|
||||||
blockFont = style::font(font->size(), blockFont->flags(), blockFont->family());
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (flags & TextBlockFBold) {
|
if (flags & TextBlockFBold) {
|
||||||
blockFont = blockFont->bold();
|
blockFont = blockFont->bold();
|
||||||
} else if (flags & TextBlockFSemibold) {
|
} else if (flags & TextBlockFSemibold) {
|
||||||
blockFont = st::semiboldFont;
|
blockFont = blockFont->semibold();
|
||||||
if (blockFont->size() != font->size() || blockFont->flags() != font->flags()) {
|
|
||||||
blockFont = style::font(font->size(), font->flags(), blockFont->family());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (flags & TextBlockFItalic) blockFont = blockFont->italic();
|
if (flags & TextBlockFItalic) blockFont = blockFont->italic();
|
||||||
if (flags & TextBlockFUnderline) blockFont = blockFont->underline();
|
if (flags & TextBlockFUnderline) blockFont = blockFont->underline();
|
||||||
if (flags & TextBlockFStrikeOut) blockFont = blockFont->strikeout();
|
if (flags & TextBlockFStrikeOut) blockFont = blockFont->strikeout();
|
||||||
if (flags & TextBlockFTilde) { // tilde fix in OpenSans
|
if (flags & TextBlockFTilde) { // tilde fix in OpenSans
|
||||||
blockFont = st::semiboldFont;
|
blockFont = blockFont->semibold();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1321,6 +1321,12 @@ QString SingleLine(const QString &text) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TextWithEntities SingleLine(const TextWithEntities &text) {
|
||||||
|
auto copy = text;
|
||||||
|
Trim(copy);
|
||||||
|
return { SingleLine(copy.text), std::move(copy.entities) };
|
||||||
|
}
|
||||||
|
|
||||||
QString RemoveAccents(const QString &text) {
|
QString RemoveAccents(const QString &text) {
|
||||||
auto result = text;
|
auto result = text;
|
||||||
auto copying = false;
|
auto copying = false;
|
||||||
|
|
|
||||||
|
|
@ -285,6 +285,7 @@ QString MarkdownPreBadAfter();
|
||||||
QString Clean(const QString &text);
|
QString Clean(const QString &text);
|
||||||
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);
|
||||||
QString RemoveAccents(const QString &text);
|
QString RemoveAccents(const QString &text);
|
||||||
QString RemoveEmoji(const QString &text);
|
QString RemoveEmoji(const QString &text);
|
||||||
QStringList PrepareSearchWords(const QString &query, const QRegularExpression *SplitterOverride = nullptr);
|
QStringList PrepareSearchWords(const QString &query, const QRegularExpression *SplitterOverride = nullptr);
|
||||||
|
|
|
||||||
|
|
@ -50,13 +50,13 @@ void Show(const Config &config) {
|
||||||
|
|
||||||
void Show(not_null<QWidget*> parent, const QString &text) {
|
void Show(not_null<QWidget*> parent, const QString &text) {
|
||||||
auto config = Config();
|
auto config = Config();
|
||||||
config.text = text;
|
config.text = { text };
|
||||||
Show(parent, config);
|
Show(parent, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Show(const QString &text) {
|
void Show(const QString &text) {
|
||||||
auto config = Config();
|
auto config = Config();
|
||||||
config.text = text;
|
config.text = { text };
|
||||||
Show(config);
|
Show(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "ui/effects/animations.h"
|
#include "ui/effects/animations.h"
|
||||||
|
#include "ui/text/text_entity.h"
|
||||||
|
#include "ui/click_handler.h"
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
namespace Toast {
|
namespace Toast {
|
||||||
|
|
@ -16,15 +18,19 @@ class Manager;
|
||||||
class Widget;
|
class Widget;
|
||||||
} // namespace internal
|
} // namespace internal
|
||||||
|
|
||||||
|
using ClickHandlerFilter = Fn<bool(const ClickHandlerPtr&, Qt::MouseButton)>;
|
||||||
|
|
||||||
inline constexpr auto kDefaultDuration = crl::time(1500);
|
inline constexpr auto kDefaultDuration = crl::time(1500);
|
||||||
struct Config {
|
struct Config {
|
||||||
QString text;
|
TextWithEntities text;
|
||||||
QMargins padding;
|
QMargins padding;
|
||||||
crl::time durationMs = kDefaultDuration;
|
crl::time durationMs = kDefaultDuration;
|
||||||
int minWidth = 0;
|
int minWidth = 0;
|
||||||
int maxWidth = 0;
|
int maxWidth = 0;
|
||||||
int maxLines = 16;
|
int maxLines = 16;
|
||||||
bool multiline = false;
|
bool multiline = false;
|
||||||
|
bool dark = false;
|
||||||
|
ClickHandlerFilter filter;
|
||||||
};
|
};
|
||||||
void SetDefaultParent(not_null<QWidget*> parent);
|
void SetDefaultParent(not_null<QWidget*> parent);
|
||||||
void Show(not_null<QWidget*> parent, const Config &config);
|
void Show(not_null<QWidget*> parent, const Config &config);
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@
|
||||||
#include "ui/image/image_prepare.h"
|
#include "ui/image/image_prepare.h"
|
||||||
#include "styles/palette.h"
|
#include "styles/palette.h"
|
||||||
|
|
||||||
|
#include <QtGui/QtEvents>
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
namespace Toast {
|
namespace Toast {
|
||||||
namespace internal {
|
namespace internal {
|
||||||
|
|
@ -17,24 +19,30 @@ Widget::Widget(QWidget *parent, const Config &config)
|
||||||
: TWidget(parent)
|
: TWidget(parent)
|
||||||
, _roundRect(ImageRoundRadius::Large, st::toastBg)
|
, _roundRect(ImageRoundRadius::Large, st::toastBg)
|
||||||
, _multiline(config.multiline)
|
, _multiline(config.multiline)
|
||||||
|
, _dark(config.dark)
|
||||||
, _maxWidth((config.maxWidth > 0) ? config.maxWidth : st::toastMaxWidth)
|
, _maxWidth((config.maxWidth > 0) ? config.maxWidth : st::toastMaxWidth)
|
||||||
, _padding((config.padding.left() > 0) ? config.padding : st::toastPadding)
|
, _padding((config.padding.left() > 0) ? config.padding : st::toastPadding)
|
||||||
, _maxTextWidth(widthWithoutPadding(_maxWidth))
|
, _maxTextWidth(widthWithoutPadding(_maxWidth))
|
||||||
, _maxTextHeight(
|
, _maxTextHeight(
|
||||||
st::toastTextStyle.font->height * (_multiline ? config.maxLines : 1))
|
st::toastTextStyle.font->height * (_multiline ? config.maxLines : 1))
|
||||||
, _text(_multiline ? widthWithoutPadding(config.minWidth) : QFIXED_MAX) {
|
, _text(_multiline ? widthWithoutPadding(config.minWidth) : QFIXED_MAX)
|
||||||
|
, _clickHandlerFilter(config.filter) {
|
||||||
const auto toastOptions = TextParseOptions{
|
const auto toastOptions = TextParseOptions{
|
||||||
TextParseMultiline,
|
TextParseMultiline,
|
||||||
_maxTextWidth,
|
_maxTextWidth,
|
||||||
_maxTextHeight,
|
_maxTextHeight,
|
||||||
Qt::LayoutDirectionAuto
|
Qt::LayoutDirectionAuto
|
||||||
};
|
};
|
||||||
_text.setText(
|
_text.setMarkedText(
|
||||||
st::toastTextStyle,
|
st::toastTextStyle,
|
||||||
_multiline ? config.text : TextUtilities::SingleLine(config.text),
|
_multiline ? config.text : TextUtilities::SingleLine(config.text),
|
||||||
toastOptions);
|
toastOptions);
|
||||||
|
|
||||||
setAttribute(Qt::WA_TransparentForMouseEvents);
|
if (_text.hasLinks()) {
|
||||||
|
setMouseTracking(true);
|
||||||
|
} else {
|
||||||
|
setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||||
|
}
|
||||||
|
|
||||||
onParentResized();
|
onParentResized();
|
||||||
show();
|
show();
|
||||||
|
|
@ -62,12 +70,65 @@ void Widget::paintEvent(QPaintEvent *e) {
|
||||||
|
|
||||||
p.setOpacity(_shownLevel);
|
p.setOpacity(_shownLevel);
|
||||||
_roundRect.paint(p, rect());
|
_roundRect.paint(p, rect());
|
||||||
|
if (_dark) {
|
||||||
|
_roundRect.paint(p, rect());
|
||||||
|
}
|
||||||
|
|
||||||
|
p.setTextPalette(st::toastTextPalette);
|
||||||
|
|
||||||
const auto lines = _maxTextHeight / st::toastTextStyle.font->height;
|
const auto lines = _maxTextHeight / st::toastTextStyle.font->height;
|
||||||
p.setPen(st::toastFg);
|
p.setPen(st::toastFg);
|
||||||
_text.drawElided(p, _padding.left(), _padding.top(), _textWidth + 1, lines);
|
_text.drawElided(p, _padding.left(), _padding.top(), _textWidth + 1, lines);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Widget::leaveEventHook(QEvent *e) {
|
||||||
|
if (!_text.hasLinks()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ClickHandler::getActive()) {
|
||||||
|
ClickHandler::setActive(nullptr);
|
||||||
|
setCursor(style::cur_default);
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Widget::mouseMoveEvent(QMouseEvent *e) {
|
||||||
|
if (!_text.hasLinks()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto point = e->pos() - QPoint(_padding.left(), _padding.top());
|
||||||
|
const auto lines = _maxTextHeight / st::toastTextStyle.font->height;
|
||||||
|
const auto state = _text.getStateElided(point, _textWidth + 1);
|
||||||
|
const auto was = ClickHandler::getActive();
|
||||||
|
if (was != state.link) {
|
||||||
|
ClickHandler::setActive(state.link);
|
||||||
|
if ((was != nullptr) != (state.link != nullptr)) {
|
||||||
|
setCursor(was ? style::cur_default : style::cur_pointer);
|
||||||
|
}
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Widget::mousePressEvent(QMouseEvent *e) {
|
||||||
|
if (!_text.hasLinks() || e->button() != Qt::LeftButton) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ClickHandler::pressed();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Widget::mouseReleaseEvent(QMouseEvent *e) {
|
||||||
|
if (!_text.hasLinks() || e->button() != Qt::LeftButton) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (const auto handler = ClickHandler::unpressed()) {
|
||||||
|
const auto button = e->button();
|
||||||
|
if (!_clickHandlerFilter
|
||||||
|
|| _clickHandlerFilter(handler, button)) {
|
||||||
|
ActivateClickHandler(this, handler, button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace internal
|
} // namespace internal
|
||||||
} // namespace Toast
|
} // namespace Toast
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,11 @@ public:
|
||||||
protected:
|
protected:
|
||||||
void paintEvent(QPaintEvent *e) override;
|
void paintEvent(QPaintEvent *e) override;
|
||||||
|
|
||||||
|
void leaveEventHook(QEvent *e) override;
|
||||||
|
void mouseMoveEvent(QMouseEvent *e) override;
|
||||||
|
void mousePressEvent(QMouseEvent *e) override;
|
||||||
|
void mouseReleaseEvent(QMouseEvent *e) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int widthWithoutPadding(int w) {
|
int widthWithoutPadding(int w) {
|
||||||
return w - _padding.left() - _padding.right();
|
return w - _padding.left() - _padding.right();
|
||||||
|
|
@ -36,6 +41,7 @@ private:
|
||||||
|
|
||||||
float64 _shownLevel = 0;
|
float64 _shownLevel = 0;
|
||||||
bool _multiline = false;
|
bool _multiline = false;
|
||||||
|
bool _dark = false;
|
||||||
int _maxWidth = 0;
|
int _maxWidth = 0;
|
||||||
QMargins _padding;
|
QMargins _padding;
|
||||||
|
|
||||||
|
|
@ -44,6 +50,8 @@ private:
|
||||||
int _textWidth = 0;
|
int _textWidth = 0;
|
||||||
Text::String _text;
|
Text::String _text;
|
||||||
|
|
||||||
|
ClickHandlerFilter _clickHandlerFilter;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace internal
|
} // namespace internal
|
||||||
|
|
|
||||||
|
|
@ -654,14 +654,6 @@ void RemoveDocumentTags(
|
||||||
cursor.mergeCharFormat(format);
|
cursor.mergeCharFormat(format);
|
||||||
}
|
}
|
||||||
|
|
||||||
style::font AdjustFont(
|
|
||||||
const style::font &font,
|
|
||||||
const style::font &original) {
|
|
||||||
return (font->size() != original->size())
|
|
||||||
? style::font(original->size(), font->flags(), font->family())
|
|
||||||
: font;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsValidMarkdownLink(const QString &link) {
|
bool IsValidMarkdownLink(const QString &link) {
|
||||||
return (link.indexOf('.') >= 0) || (link.indexOf(':') >= 0);
|
return (link.indexOf('.') >= 0) || (link.indexOf(':') >= 0);
|
||||||
}
|
}
|
||||||
|
|
@ -674,16 +666,8 @@ QTextCharFormat PrepareTagFormat(
|
||||||
result.setForeground(st::defaultTextPalette.linkFg);
|
result.setForeground(st::defaultTextPalette.linkFg);
|
||||||
result.setFont(st.font);
|
result.setFont(st.font);
|
||||||
} else if (tag == kTagBold) {
|
} else if (tag == kTagBold) {
|
||||||
auto semibold = st::semiboldFont;
|
|
||||||
if (semibold->size() != st.font->size()
|
|
||||||
|| semibold->flags() != st.font->flags()) {
|
|
||||||
semibold = style::font(
|
|
||||||
st.font->size(),
|
|
||||||
st.font->flags(),
|
|
||||||
semibold->family());
|
|
||||||
}
|
|
||||||
result.setForeground(st.textFg);
|
result.setForeground(st.textFg);
|
||||||
result.setFont(AdjustFont(st::semiboldFont, st.font));
|
result.setFont(st.font->semibold());
|
||||||
} else if (tag == kTagItalic) {
|
} else if (tag == kTagItalic) {
|
||||||
result.setForeground(st.textFg);
|
result.setForeground(st.textFg);
|
||||||
result.setFont(st.font->italic());
|
result.setFont(st.font->italic());
|
||||||
|
|
@ -695,7 +679,7 @@ QTextCharFormat PrepareTagFormat(
|
||||||
result.setFont(st.font->strikeout());
|
result.setFont(st.font->strikeout());
|
||||||
} else if (tag == kTagCode || tag == kTagPre) {
|
} else if (tag == kTagCode || tag == kTagPre) {
|
||||||
result.setForeground(st::defaultTextPalette.monoFg);
|
result.setForeground(st::defaultTextPalette.monoFg);
|
||||||
result.setFont(AdjustFont(style::MonospaceFont(), st.font));
|
result.setFont(st.font->monospace());
|
||||||
} else {
|
} else {
|
||||||
result.setForeground(st.textFg);
|
result.setForeground(st.textFg);
|
||||||
result.setFont(st.font);
|
result.setFont(st.font);
|
||||||
|
|
@ -1997,7 +1981,7 @@ void InputField::processFormatting(int insertPosition, int insertEnd) {
|
||||||
const auto tildeFormatting = (_st.font->f.pixelSize() * style::DevicePixelRatio() == 13)
|
const auto tildeFormatting = (_st.font->f.pixelSize() * style::DevicePixelRatio() == 13)
|
||||||
&& (_st.font->f.family() == qstr("DAOpenSansRegular"));
|
&& (_st.font->f.family() == qstr("DAOpenSansRegular"));
|
||||||
auto isTildeFragment = false;
|
auto isTildeFragment = false;
|
||||||
const auto tildeFixedFont = AdjustFont(st::semiboldFont, _st.font);
|
const auto tildeFixedFont = _st.font->semibold();
|
||||||
|
|
||||||
// First tag handling (the one we inserted text to).
|
// First tag handling (the one we inserted text to).
|
||||||
bool startTagFound = false;
|
bool startTagFound = false;
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,8 @@ int SideBarButton::resizeGetHeight(int newWidth) {
|
||||||
const auto text = std::min(
|
const auto text = std::min(
|
||||||
_text.countHeight(newWidth - _st.textSkip * 2),
|
_text.countHeight(newWidth - _st.textSkip * 2),
|
||||||
_st.style.font->height * kMaxLabelLines);
|
_st.style.font->height * kMaxLabelLines);
|
||||||
return result + text;
|
const auto add = text - _st.style.font->height;
|
||||||
|
return result + std::max(add, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SideBarButton::paintEvent(QPaintEvent *e) {
|
void SideBarButton::paintEvent(QPaintEvent *e) {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue