2016-06-09 11:51:24 +00:00
|
|
|
/*
|
|
|
|
This file is part of Telegram Desktop,
|
2018-01-03 10:23:14 +00:00
|
|
|
the official desktop application for the Telegram messaging service.
|
2016-06-09 11:51:24 +00:00
|
|
|
|
2018-01-03 10:23:14 +00:00
|
|
|
For license and copyright information please follow this link:
|
|
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
2016-06-09 11:51:24 +00:00
|
|
|
*/
|
2018-01-09 17:08:31 +00:00
|
|
|
#include "history/view/history_view_service_message.h"
|
2016-06-09 11:51:24 +00:00
|
|
|
|
2018-01-13 12:45:11 +00:00
|
|
|
#include "history/history.h"
|
2017-06-22 15:11:41 +00:00
|
|
|
#include "history/history_service.h"
|
2019-08-02 13:21:09 +00:00
|
|
|
#include "history/view/media/history_view_media.h"
|
2018-01-13 12:45:11 +00:00
|
|
|
#include "history/history_item_components.h"
|
|
|
|
#include "history/view/history_view_cursor_state.h"
|
2016-06-09 11:51:24 +00:00
|
|
|
#include "data/data_abstract_structure.h"
|
2019-01-15 14:13:20 +00:00
|
|
|
#include "data/data_chat.h"
|
|
|
|
#include "data/data_channel.h"
|
2019-01-18 08:11:15 +00:00
|
|
|
#include "ui/text_options.h"
|
2016-06-09 11:51:24 +00:00
|
|
|
#include "mainwidget.h"
|
2018-01-14 16:02:25 +00:00
|
|
|
#include "layout.h"
|
2017-04-13 08:27:10 +00:00
|
|
|
#include "lang/lang_keys.h"
|
2019-01-18 08:11:15 +00:00
|
|
|
#include "styles/style_history.h"
|
2016-06-09 11:51:24 +00:00
|
|
|
|
2018-01-09 17:08:31 +00:00
|
|
|
namespace HistoryView {
|
2016-06-09 11:51:24 +00:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
enum CircleMask {
|
|
|
|
NormalMask = 0x00,
|
|
|
|
InvertedMask = 0x01,
|
|
|
|
};
|
|
|
|
enum CircleMaskMultiplier {
|
|
|
|
MaskMultiplier = 0x04,
|
|
|
|
};
|
|
|
|
enum CornerVerticalSide {
|
|
|
|
CornerTop = 0x00,
|
|
|
|
CornerBottom = 0x02,
|
|
|
|
};
|
|
|
|
enum CornerHorizontalSide {
|
|
|
|
CornerLeft = 0x00,
|
|
|
|
CornerRight = 0x01,
|
|
|
|
};
|
|
|
|
|
|
|
|
class ServiceMessageStyleData : public Data::AbstractStructure {
|
|
|
|
public:
|
|
|
|
// circle[CircleMask value]
|
|
|
|
QImage circle[2];
|
|
|
|
|
|
|
|
// corners[(CircleMask value) * MaskMultiplier | (CornerVerticalSide value) | (CornerHorizontalSide value)]
|
|
|
|
QPixmap corners[8];
|
|
|
|
};
|
|
|
|
Data::GlobalStructurePointer<ServiceMessageStyleData> serviceMessageStyle;
|
|
|
|
|
2016-08-14 19:56:26 +00:00
|
|
|
int historyServiceMsgRadius() {
|
|
|
|
static int HistoryServiceMsgRadius = ([]() {
|
|
|
|
auto minMsgHeight = (st::msgServiceFont->height + st::msgServicePadding.top() + st::msgServicePadding.bottom());
|
|
|
|
return minMsgHeight / 2;
|
|
|
|
})();
|
|
|
|
return HistoryServiceMsgRadius;
|
|
|
|
}
|
|
|
|
|
|
|
|
int historyServiceMsgInvertedRadius() {
|
|
|
|
static int HistoryServiceMsgInvertedRadius = ([]() {
|
|
|
|
auto minRowHeight = st::msgServiceFont->height;
|
|
|
|
return minRowHeight - historyServiceMsgRadius();
|
|
|
|
})();
|
|
|
|
return HistoryServiceMsgInvertedRadius;
|
|
|
|
}
|
|
|
|
|
|
|
|
int historyServiceMsgInvertedShrink() {
|
|
|
|
static int HistoryServiceMsgInvertedShrink = ([]() {
|
|
|
|
return (historyServiceMsgInvertedRadius() * 2) / 3;
|
|
|
|
})();
|
|
|
|
return HistoryServiceMsgInvertedShrink;
|
|
|
|
}
|
|
|
|
|
2016-06-09 11:51:24 +00:00
|
|
|
void createCircleMasks() {
|
|
|
|
serviceMessageStyle.createIfNull();
|
|
|
|
if (!serviceMessageStyle->circle[NormalMask].isNull()) return;
|
|
|
|
|
2016-08-14 19:56:26 +00:00
|
|
|
int size = historyServiceMsgRadius() * 2;
|
2016-06-09 11:51:24 +00:00
|
|
|
serviceMessageStyle->circle[NormalMask] = style::createCircleMask(size);
|
2016-08-14 19:56:26 +00:00
|
|
|
int sizeInverted = historyServiceMsgInvertedRadius() * 2;
|
2016-07-08 10:06:41 +00:00
|
|
|
serviceMessageStyle->circle[InvertedMask] = style::createInvertedCircleMask(sizeInverted);
|
2016-06-09 11:51:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QPixmap circleCorner(int corner) {
|
|
|
|
if (serviceMessageStyle->corners[corner].isNull()) {
|
2016-07-08 10:06:41 +00:00
|
|
|
int maskType = corner / MaskMultiplier;
|
2016-08-14 19:56:26 +00:00
|
|
|
int radius = (maskType == NormalMask ? historyServiceMsgRadius() : historyServiceMsgInvertedRadius());
|
2016-07-08 10:06:41 +00:00
|
|
|
int size = radius * cIntRetinaFactor();
|
2016-06-09 11:51:24 +00:00
|
|
|
|
|
|
|
int xoffset = 0, yoffset = 0;
|
|
|
|
if (corner & CornerRight) {
|
|
|
|
xoffset = size;
|
|
|
|
}
|
|
|
|
if (corner & CornerBottom) {
|
|
|
|
yoffset = size;
|
|
|
|
}
|
|
|
|
auto part = QRect(xoffset, yoffset, size, size);
|
2016-11-03 10:33:57 +00:00
|
|
|
auto result = style::colorizeImage(serviceMessageStyle->circle[maskType], st::msgServiceBg, part);
|
2016-06-09 11:51:24 +00:00
|
|
|
result.setDevicePixelRatio(cRetinaFactor());
|
2017-02-21 13:45:56 +00:00
|
|
|
serviceMessageStyle->corners[corner] = App::pixmapFromImageInPlace(std::move(result));
|
2016-06-09 11:51:24 +00:00
|
|
|
}
|
|
|
|
return serviceMessageStyle->corners[corner];
|
|
|
|
}
|
|
|
|
|
|
|
|
enum class SideStyle {
|
|
|
|
Rounded,
|
|
|
|
Plain,
|
|
|
|
Inverted,
|
|
|
|
};
|
|
|
|
|
|
|
|
// Returns amount of pixels already painted vertically (so you can skip them in the complex rect shape).
|
|
|
|
int paintBubbleSide(Painter &p, int x, int y, int width, SideStyle style, CornerVerticalSide side) {
|
|
|
|
if (style == SideStyle::Rounded) {
|
|
|
|
auto left = circleCorner((NormalMask * MaskMultiplier) | side | CornerLeft);
|
|
|
|
int leftWidth = left.width() / cIntRetinaFactor();
|
|
|
|
p.drawPixmap(x, y, left);
|
|
|
|
|
|
|
|
auto right = circleCorner((NormalMask * MaskMultiplier) | side | CornerRight);
|
|
|
|
int rightWidth = right.width() / cIntRetinaFactor();
|
|
|
|
p.drawPixmap(x + width - rightWidth, y, right);
|
|
|
|
|
|
|
|
int cornerHeight = left.height() / cIntRetinaFactor();
|
2016-11-03 10:33:57 +00:00
|
|
|
p.fillRect(x + leftWidth, y, width - leftWidth - rightWidth, cornerHeight, st::msgServiceBg);
|
2016-06-09 11:51:24 +00:00
|
|
|
return cornerHeight;
|
|
|
|
} else if (style == SideStyle::Inverted) {
|
|
|
|
// CornerLeft and CornerRight are inverted for SideStyle::Inverted sprites.
|
|
|
|
auto left = circleCorner((InvertedMask * MaskMultiplier) | side | CornerRight);
|
|
|
|
int leftWidth = left.width() / cIntRetinaFactor();
|
|
|
|
p.drawPixmap(x - leftWidth, y, left);
|
|
|
|
|
|
|
|
auto right = circleCorner((InvertedMask * MaskMultiplier) | side | CornerLeft);
|
|
|
|
p.drawPixmap(x + width, y, right);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-08-14 19:56:26 +00:00
|
|
|
void paintBubblePart(Painter &p, int x, int y, int width, int height, SideStyle topStyle, SideStyle bottomStyle, bool forceShrink = false) {
|
|
|
|
if (topStyle == SideStyle::Inverted || bottomStyle == SideStyle::Inverted || forceShrink) {
|
|
|
|
width -= historyServiceMsgInvertedShrink() * 2;
|
|
|
|
x += historyServiceMsgInvertedShrink();
|
2016-07-08 10:06:41 +00:00
|
|
|
}
|
|
|
|
|
2016-06-09 11:51:24 +00:00
|
|
|
if (int skip = paintBubbleSide(p, x, y, width, topStyle, CornerTop)) {
|
|
|
|
y += skip;
|
|
|
|
height -= skip;
|
|
|
|
}
|
2016-07-08 10:06:41 +00:00
|
|
|
int bottomSize = 0;
|
|
|
|
if (bottomStyle == SideStyle::Rounded) {
|
2016-08-14 19:56:26 +00:00
|
|
|
bottomSize = historyServiceMsgRadius();
|
2016-07-08 10:06:41 +00:00
|
|
|
} else if (bottomStyle == SideStyle::Inverted) {
|
2016-08-14 19:56:26 +00:00
|
|
|
bottomSize = historyServiceMsgInvertedRadius();
|
2016-07-08 10:06:41 +00:00
|
|
|
}
|
|
|
|
if (int skip = paintBubbleSide(p, x, y + height - bottomSize, width, bottomStyle, CornerBottom)) {
|
2016-06-09 11:51:24 +00:00
|
|
|
height -= skip;
|
|
|
|
}
|
|
|
|
|
2016-11-03 10:33:57 +00:00
|
|
|
p.fillRect(x, y, width, height, st::msgServiceBg);
|
2016-06-09 11:51:24 +00:00
|
|
|
}
|
|
|
|
|
2016-06-10 10:21:09 +00:00
|
|
|
void paintPreparedDate(Painter &p, const QString &dateText, int dateTextWidth, int y, int w) {
|
|
|
|
int left = st::msgServiceMargin.left();
|
|
|
|
int maxwidth = w;
|
2017-01-14 18:50:16 +00:00
|
|
|
if (Adaptive::ChatWide()) {
|
2017-05-17 12:38:42 +00:00
|
|
|
maxwidth = qMin(maxwidth, WideChatWidth());
|
2016-06-10 10:21:09 +00:00
|
|
|
}
|
|
|
|
w = maxwidth - st::msgServiceMargin.left() - st::msgServiceMargin.left();
|
|
|
|
|
|
|
|
left += (w - dateTextWidth - st::msgServicePadding.left() - st::msgServicePadding.right()) / 2;
|
|
|
|
int height = st::msgServicePadding.top() + st::msgServiceFont->height + st::msgServicePadding.bottom();
|
2016-07-08 10:06:41 +00:00
|
|
|
ServiceMessagePainter::paintBubble(p, left, y + st::msgServiceMargin.top(), dateTextWidth + st::msgServicePadding.left() + st::msgServicePadding.left(), height);
|
2016-06-10 10:21:09 +00:00
|
|
|
|
|
|
|
p.setFont(st::msgServiceFont);
|
2016-12-21 15:05:58 +00:00
|
|
|
p.setPen(st::msgServiceFg);
|
2016-06-10 10:21:09 +00:00
|
|
|
p.drawText(left + st::msgServicePadding.left(), y + st::msgServiceMargin.top() + st::msgServicePadding.top() + st::msgServiceFont->ascent, dateText);
|
|
|
|
}
|
|
|
|
|
2019-01-18 08:11:15 +00:00
|
|
|
bool NeedAboutGroup(not_null<History*> history) {
|
|
|
|
if (const auto chat = history->peer->asChat()) {
|
|
|
|
return chat->amCreator();
|
|
|
|
} else if (const auto channel = history->peer->asMegagroup()) {
|
|
|
|
return channel->amCreator();
|
2019-01-15 14:13:20 +00:00
|
|
|
}
|
2019-01-18 08:11:15 +00:00
|
|
|
return false;
|
2019-01-15 14:13:20 +00:00
|
|
|
}
|
|
|
|
|
2016-06-09 11:51:24 +00:00
|
|
|
} // namepsace
|
|
|
|
|
2017-05-17 12:38:42 +00:00
|
|
|
int WideChatWidth() {
|
|
|
|
return st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left();
|
|
|
|
}
|
|
|
|
|
2016-06-10 10:21:09 +00:00
|
|
|
void ServiceMessagePainter::paintDate(Painter &p, const QDateTime &date, int y, int w) {
|
|
|
|
auto dateText = langDayOfMonthFull(date.date());
|
|
|
|
auto dateTextWidth = st::msgServiceFont->width(dateText);
|
|
|
|
paintPreparedDate(p, dateText, dateTextWidth, y, w);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ServiceMessagePainter::paintDate(Painter &p, const QString &dateText, int dateTextWidth, int y, int w) {
|
|
|
|
paintPreparedDate(p, dateText, dateTextWidth, y, w);
|
|
|
|
}
|
|
|
|
|
2016-07-08 10:06:41 +00:00
|
|
|
void ServiceMessagePainter::paintBubble(Painter &p, int x, int y, int w, int h) {
|
|
|
|
createCircleMasks();
|
|
|
|
|
|
|
|
paintBubblePart(p, x, y, w, h, SideStyle::Rounded, SideStyle::Rounded);
|
|
|
|
}
|
|
|
|
|
2019-06-12 13:26:04 +00:00
|
|
|
void ServiceMessagePainter::paintComplexBubble(Painter &p, int left, int width, const Ui::Text::String &text, const QRect &textRect) {
|
2016-06-09 11:51:24 +00:00
|
|
|
createCircleMasks();
|
|
|
|
|
|
|
|
auto lineWidths = countLineWidths(text, textRect);
|
|
|
|
|
2016-08-14 19:56:26 +00:00
|
|
|
int y = st::msgServiceMargin.top(), previousRichWidth = 0;
|
|
|
|
bool previousShrink = false, forceShrink = false;
|
2016-06-09 11:51:24 +00:00
|
|
|
SideStyle topStyle = SideStyle::Rounded, bottomStyle;
|
|
|
|
for (int i = 0, count = lineWidths.size(); i < count; ++i) {
|
|
|
|
auto lineWidth = lineWidths.at(i);
|
|
|
|
if (i + 1 < count) {
|
|
|
|
auto nextLineWidth = lineWidths.at(i + 1);
|
|
|
|
if (nextLineWidth > lineWidth) {
|
|
|
|
bottomStyle = SideStyle::Inverted;
|
|
|
|
} else if (nextLineWidth < lineWidth) {
|
|
|
|
bottomStyle = SideStyle::Rounded;
|
|
|
|
} else {
|
|
|
|
bottomStyle = SideStyle::Plain;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
bottomStyle = SideStyle::Rounded;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto richWidth = lineWidth + st::msgServicePadding.left() + st::msgServicePadding.right();
|
|
|
|
auto richHeight = st::msgServiceFont->height;
|
|
|
|
if (topStyle == SideStyle::Rounded) {
|
|
|
|
richHeight += st::msgServicePadding.top();
|
|
|
|
} else if (topStyle == SideStyle::Inverted) {
|
|
|
|
richHeight -= st::msgServicePadding.bottom();
|
|
|
|
}
|
|
|
|
if (bottomStyle == SideStyle::Rounded) {
|
|
|
|
richHeight += st::msgServicePadding.bottom();
|
|
|
|
} else if (bottomStyle == SideStyle::Inverted) {
|
|
|
|
richHeight -= st::msgServicePadding.top();
|
|
|
|
}
|
2016-08-14 19:56:26 +00:00
|
|
|
forceShrink = previousShrink && (richWidth == previousRichWidth);
|
|
|
|
paintBubblePart(p, left + ((width - richWidth) / 2), y, richWidth, richHeight, topStyle, bottomStyle, forceShrink);
|
2016-06-09 11:51:24 +00:00
|
|
|
y += richHeight;
|
|
|
|
|
2016-08-14 19:56:26 +00:00
|
|
|
previousShrink = forceShrink || (topStyle == SideStyle::Inverted) || (bottomStyle == SideStyle::Inverted);
|
|
|
|
previousRichWidth = richWidth;
|
|
|
|
|
2016-06-09 11:51:24 +00:00
|
|
|
if (bottomStyle == SideStyle::Inverted) {
|
|
|
|
topStyle = SideStyle::Rounded;
|
|
|
|
} else if (bottomStyle == SideStyle::Rounded) {
|
|
|
|
topStyle = SideStyle::Inverted;
|
|
|
|
} else {
|
|
|
|
topStyle = SideStyle::Plain;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-12 13:26:04 +00:00
|
|
|
QVector<int> ServiceMessagePainter::countLineWidths(const Ui::Text::String &text, const QRect &textRect) {
|
2016-06-09 11:51:24 +00:00
|
|
|
int linesCount = qMax(textRect.height() / st::msgServiceFont->height, 1);
|
|
|
|
QVector<int> lineWidths;
|
|
|
|
lineWidths.reserve(linesCount);
|
|
|
|
text.countLineWidths(textRect.width(), &lineWidths);
|
|
|
|
|
2016-08-14 19:56:26 +00:00
|
|
|
int minDelta = 2 * (historyServiceMsgRadius() + historyServiceMsgInvertedRadius() - historyServiceMsgInvertedShrink());
|
2016-06-09 11:51:24 +00:00
|
|
|
for (int i = 0, count = lineWidths.size(); i < count; ++i) {
|
|
|
|
int width = qMax(lineWidths.at(i), 0);
|
|
|
|
if (i > 0) {
|
|
|
|
int widthBefore = lineWidths.at(i - 1);
|
|
|
|
if (width < widthBefore && width + minDelta > widthBefore) {
|
|
|
|
width = widthBefore;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (i + 1 < count) {
|
|
|
|
int widthAfter = lineWidths.at(i + 1);
|
|
|
|
if (width < widthAfter && width + minDelta > widthAfter) {
|
|
|
|
width = widthAfter;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (width > lineWidths.at(i)) {
|
|
|
|
lineWidths[i] = width;
|
|
|
|
if (i > 0) {
|
|
|
|
int widthBefore = lineWidths.at(i - 1);
|
|
|
|
if (widthBefore != width && widthBefore < width + minDelta && widthBefore + minDelta > width) {
|
|
|
|
i -= 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return lineWidths;
|
|
|
|
}
|
|
|
|
|
|
|
|
void serviceColorsUpdated() {
|
|
|
|
if (serviceMessageStyle) {
|
|
|
|
for (auto &corner : serviceMessageStyle->corners) {
|
|
|
|
corner = QPixmap();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-19 17:10:58 +00:00
|
|
|
Service::Service(
|
|
|
|
not_null<ElementDelegate*> delegate,
|
|
|
|
not_null<HistoryService*> data)
|
|
|
|
: Element(delegate, data) {
|
2018-01-13 12:45:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
not_null<HistoryService*> Service::message() const {
|
|
|
|
return static_cast<HistoryService*>(data().get());
|
|
|
|
}
|
|
|
|
|
|
|
|
QRect Service::countGeometry() const {
|
|
|
|
auto result = QRect(0, 0, width(), height());
|
|
|
|
if (Adaptive::ChatWide()) {
|
|
|
|
result.setWidth(qMin(result.width(), st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left()));
|
|
|
|
}
|
|
|
|
return result.marginsRemoved(st::msgServiceMargin);
|
|
|
|
}
|
|
|
|
|
|
|
|
QSize Service::performCountCurrentSize(int newWidth) {
|
2018-01-23 11:47:38 +00:00
|
|
|
auto newHeight = displayedDateHeight();
|
2018-01-21 19:21:08 +00:00
|
|
|
if (const auto bar = Get<UnreadBar>()) {
|
|
|
|
newHeight += bar->height();
|
2018-01-13 12:45:11 +00:00
|
|
|
}
|
|
|
|
|
2018-01-30 13:17:50 +00:00
|
|
|
if (isHidden()) {
|
|
|
|
return { newWidth, newHeight };
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto item = message();
|
|
|
|
const auto media = this->media();
|
|
|
|
|
2018-01-13 12:45:11 +00:00
|
|
|
if (item->_text.isEmpty()) {
|
|
|
|
item->_textHeight = 0;
|
2016-11-18 16:27:47 +00:00
|
|
|
} else {
|
2018-01-13 12:45:11 +00:00
|
|
|
auto contentWidth = newWidth;
|
|
|
|
if (Adaptive::ChatWide()) {
|
|
|
|
accumulate_min(contentWidth, st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left());
|
|
|
|
}
|
|
|
|
contentWidth -= st::msgServiceMargin.left() + st::msgServiceMargin.left(); // two small margins
|
|
|
|
if (contentWidth < st::msgServicePadding.left() + st::msgServicePadding.right() + 1) {
|
|
|
|
contentWidth = st::msgServicePadding.left() + st::msgServicePadding.right() + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto nwidth = qMax(contentWidth - st::msgServicePadding.left() - st::msgServicePadding.right(), 0);
|
|
|
|
if (nwidth != item->_textWidth) {
|
|
|
|
item->_textWidth = nwidth;
|
|
|
|
item->_textHeight = item->_text.countHeight(nwidth);
|
|
|
|
}
|
|
|
|
if (contentWidth >= maxWidth()) {
|
|
|
|
newHeight += minHeight();
|
|
|
|
} else {
|
|
|
|
newHeight += item->_textHeight;
|
|
|
|
}
|
|
|
|
newHeight += st::msgServicePadding.top() + st::msgServicePadding.bottom() + st::msgServiceMargin.top() + st::msgServiceMargin.bottom();
|
|
|
|
if (media) {
|
2018-01-29 17:13:24 +00:00
|
|
|
newHeight += st::msgServiceMargin.top() + media->resizeGetHeight(media->maxWidth());
|
2018-01-13 12:45:11 +00:00
|
|
|
}
|
2016-11-18 16:27:47 +00:00
|
|
|
}
|
2018-01-13 12:45:11 +00:00
|
|
|
|
|
|
|
return { newWidth, newHeight };
|
|
|
|
}
|
|
|
|
|
|
|
|
QSize Service::performCountOptimalSize() {
|
|
|
|
const auto item = message();
|
2018-01-14 16:02:25 +00:00
|
|
|
const auto media = this->media();
|
2018-01-13 12:45:11 +00:00
|
|
|
|
|
|
|
auto maxWidth = item->_text.maxWidth() + st::msgServicePadding.left() + st::msgServicePadding.right();
|
|
|
|
auto minHeight = item->_text.minHeight();
|
|
|
|
if (media) {
|
|
|
|
media->initDimensions();
|
|
|
|
}
|
|
|
|
return { maxWidth, minHeight };
|
|
|
|
}
|
|
|
|
|
2018-01-30 13:17:50 +00:00
|
|
|
bool Service::isHidden() const {
|
2019-04-15 11:54:03 +00:00
|
|
|
//if (context() == Context::Feed) { // #feed
|
|
|
|
// return true;
|
|
|
|
//}
|
2018-01-30 13:17:50 +00:00
|
|
|
return Element::isHidden();
|
|
|
|
}
|
|
|
|
|
2018-02-16 17:59:35 +00:00
|
|
|
int Service::marginTop() const {
|
|
|
|
return st::msgServiceMargin.top();
|
|
|
|
}
|
|
|
|
|
|
|
|
int Service::marginBottom() const {
|
|
|
|
return st::msgServiceMargin.bottom();
|
|
|
|
}
|
|
|
|
|
2018-01-13 12:45:11 +00:00
|
|
|
void Service::draw(
|
|
|
|
Painter &p,
|
|
|
|
QRect clip,
|
|
|
|
TextSelection selection,
|
2019-02-19 06:57:53 +00:00
|
|
|
crl::time ms) const {
|
2018-01-13 12:45:11 +00:00
|
|
|
const auto item = message();
|
|
|
|
auto g = countGeometry();
|
|
|
|
if (g.width() < 1) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto height = this->height() - st::msgServiceMargin.top() - st::msgServiceMargin.bottom();
|
|
|
|
auto dateh = 0;
|
|
|
|
auto unreadbarh = 0;
|
2018-01-23 11:47:38 +00:00
|
|
|
if (auto date = Get<DateBadge>()) {
|
2018-01-13 12:45:11 +00:00
|
|
|
dateh = date->height();
|
|
|
|
p.translate(0, dateh);
|
|
|
|
clip.translate(0, -dateh);
|
|
|
|
height -= dateh;
|
|
|
|
}
|
2018-01-21 19:21:08 +00:00
|
|
|
if (const auto bar = Get<UnreadBar>()) {
|
|
|
|
unreadbarh = bar->height();
|
2018-01-13 12:45:11 +00:00
|
|
|
if (clip.intersects(QRect(0, 0, width(), unreadbarh))) {
|
2018-01-21 19:21:08 +00:00
|
|
|
bar->paint(p, 0, width());
|
2018-01-13 12:45:11 +00:00
|
|
|
}
|
|
|
|
p.translate(0, unreadbarh);
|
|
|
|
clip.translate(0, -unreadbarh);
|
|
|
|
height -= unreadbarh;
|
|
|
|
}
|
|
|
|
|
2018-01-30 13:17:50 +00:00
|
|
|
if (isHidden()) {
|
|
|
|
if (auto skiph = dateh + unreadbarh) {
|
|
|
|
p.translate(0, -skiph);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-02-16 17:59:35 +00:00
|
|
|
paintHighlight(p, height);
|
2018-01-13 12:45:11 +00:00
|
|
|
|
|
|
|
p.setTextPalette(st::serviceTextPalette);
|
|
|
|
|
2018-01-14 16:02:25 +00:00
|
|
|
if (auto media = this->media()) {
|
2018-01-13 12:45:11 +00:00
|
|
|
height -= st::msgServiceMargin.top() + media->height();
|
|
|
|
auto left = st::msgServiceMargin.left() + (g.width() - media->maxWidth()) / 2, top = st::msgServiceMargin.top() + height + st::msgServiceMargin.top();
|
|
|
|
p.translate(left, top);
|
2018-01-14 16:02:25 +00:00
|
|
|
media->draw(p, clip.translated(-left, -top), TextSelection(), ms);
|
2018-01-13 12:45:11 +00:00
|
|
|
p.translate(-left, -top);
|
|
|
|
}
|
|
|
|
|
|
|
|
auto trect = QRect(g.left(), st::msgServiceMargin.top(), g.width(), height).marginsAdded(-st::msgServicePadding);
|
|
|
|
|
|
|
|
ServiceMessagePainter::paintComplexBubble(p, g.left(), g.width(), item->_text, trect);
|
|
|
|
|
|
|
|
p.setBrush(Qt::NoBrush);
|
|
|
|
p.setPen(st::msgServiceFg);
|
|
|
|
p.setFont(st::msgServiceFont);
|
|
|
|
item->_text.draw(p, trect.x(), trect.y(), trect.width(), Qt::AlignCenter, 0, -1, selection, false);
|
|
|
|
|
|
|
|
p.restoreTextPalette();
|
|
|
|
|
|
|
|
if (auto skiph = dateh + unreadbarh) {
|
|
|
|
p.translate(0, -skiph);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-27 13:59:24 +00:00
|
|
|
PointState Service::pointState(QPoint point) const {
|
2018-01-13 12:45:11 +00:00
|
|
|
const auto item = message();
|
2018-01-14 16:02:25 +00:00
|
|
|
const auto media = this->media();
|
2018-01-13 12:45:11 +00:00
|
|
|
|
|
|
|
auto g = countGeometry();
|
2018-01-30 13:17:50 +00:00
|
|
|
if (g.width() < 1 || isHidden()) {
|
2018-01-27 13:59:24 +00:00
|
|
|
return PointState::Outside;
|
2018-01-13 12:45:11 +00:00
|
|
|
}
|
|
|
|
|
2018-01-23 11:47:38 +00:00
|
|
|
if (const auto dateh = displayedDateHeight()) {
|
2018-01-13 12:45:11 +00:00
|
|
|
g.setTop(g.top() + dateh);
|
|
|
|
}
|
2018-01-21 19:21:08 +00:00
|
|
|
if (const auto bar = Get<UnreadBar>()) {
|
|
|
|
g.setTop(g.top() + bar->height());
|
2018-01-13 12:45:11 +00:00
|
|
|
}
|
|
|
|
if (media) {
|
|
|
|
g.setHeight(g.height() - (st::msgServiceMargin.top() + media->height()));
|
|
|
|
}
|
2018-01-27 13:59:24 +00:00
|
|
|
return g.contains(point) ? PointState::Inside : PointState::Outside;
|
2018-01-13 12:45:11 +00:00
|
|
|
}
|
|
|
|
|
2018-01-27 13:59:24 +00:00
|
|
|
TextState Service::textState(QPoint point, StateRequest request) const {
|
2018-01-13 12:45:11 +00:00
|
|
|
const auto item = message();
|
2018-01-14 16:02:25 +00:00
|
|
|
const auto media = this->media();
|
2018-01-13 12:45:11 +00:00
|
|
|
|
2018-01-27 13:59:24 +00:00
|
|
|
auto result = TextState(item);
|
2018-01-13 12:45:11 +00:00
|
|
|
|
|
|
|
auto g = countGeometry();
|
2018-01-30 13:17:50 +00:00
|
|
|
if (g.width() < 1 || isHidden()) {
|
2018-01-13 12:45:11 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2018-01-23 11:47:38 +00:00
|
|
|
if (const auto dateh = displayedDateHeight()) {
|
2018-01-13 12:45:11 +00:00
|
|
|
point.setY(point.y() - dateh);
|
|
|
|
g.setHeight(g.height() - dateh);
|
|
|
|
}
|
2018-01-21 19:21:08 +00:00
|
|
|
if (const auto bar = Get<UnreadBar>()) {
|
|
|
|
auto unreadbarh = bar->height();
|
2018-01-13 12:45:11 +00:00
|
|
|
point.setY(point.y() - unreadbarh);
|
|
|
|
g.setHeight(g.height() - unreadbarh);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (media) {
|
|
|
|
g.setHeight(g.height() - (st::msgServiceMargin.top() + media->height()));
|
|
|
|
}
|
|
|
|
auto trect = g.marginsAdded(-st::msgServicePadding);
|
|
|
|
if (trect.contains(point)) {
|
|
|
|
auto textRequest = request.forText();
|
|
|
|
textRequest.align = style::al_center;
|
2018-01-27 13:59:24 +00:00
|
|
|
result = TextState(item, item->_text.getState(
|
2018-01-13 12:45:11 +00:00
|
|
|
point - trect.topLeft(),
|
|
|
|
trect.width(),
|
|
|
|
textRequest));
|
|
|
|
if (auto gamescore = item->Get<HistoryServiceGameScore>()) {
|
2018-01-27 13:59:24 +00:00
|
|
|
if (!result.link
|
|
|
|
&& result.cursor == CursorState::Text
|
|
|
|
&& g.contains(point)) {
|
2018-01-13 12:45:11 +00:00
|
|
|
result.link = gamescore->lnk;
|
|
|
|
}
|
|
|
|
} else if (auto payment = item->Get<HistoryServicePayment>()) {
|
2018-01-27 13:59:24 +00:00
|
|
|
if (!result.link
|
|
|
|
&& result.cursor == CursorState::Text
|
|
|
|
&& g.contains(point)) {
|
2018-01-13 12:45:11 +00:00
|
|
|
result.link = payment->lnk;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (media) {
|
2018-01-27 13:59:24 +00:00
|
|
|
result = media->textState(point - QPoint(st::msgServiceMargin.left() + (g.width() - media->maxWidth()) / 2, st::msgServiceMargin.top() + g.height() + st::msgServiceMargin.top()), request);
|
2018-01-13 12:45:11 +00:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Service::updatePressed(QPoint point) {
|
2016-11-18 16:27:47 +00:00
|
|
|
}
|
|
|
|
|
2019-04-08 15:10:06 +00:00
|
|
|
TextForMimeData Service::selectedText(TextSelection selection) const {
|
|
|
|
return message()->_text.toTextForMimeData(selection);
|
2018-01-14 16:02:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
TextSelection Service::adjustSelection(
|
|
|
|
TextSelection selection,
|
|
|
|
TextSelectType type) const {
|
|
|
|
return message()->_text.adjustSelection(selection, type);
|
|
|
|
}
|
|
|
|
|
2019-01-18 08:11:15 +00:00
|
|
|
EmptyPainter::EmptyPainter(not_null<History*> history) : _history(history) {
|
|
|
|
if (NeedAboutGroup(_history)) {
|
|
|
|
fillAboutGroup();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void EmptyPainter::fillAboutGroup() {
|
|
|
|
const auto phrases = {
|
2019-06-19 15:09:03 +00:00
|
|
|
tr::lng_group_about1(tr::now),
|
|
|
|
tr::lng_group_about2(tr::now),
|
|
|
|
tr::lng_group_about3(tr::now),
|
|
|
|
tr::lng_group_about4(tr::now),
|
2019-01-18 08:11:15 +00:00
|
|
|
};
|
2019-06-12 13:26:04 +00:00
|
|
|
const auto setText = [](Ui::Text::String &text, const QString &content) {
|
2019-01-18 08:11:15 +00:00
|
|
|
text.setText(
|
|
|
|
st::serviceTextStyle,
|
|
|
|
content,
|
2019-01-22 07:50:21 +00:00
|
|
|
Ui::NameTextOptions());
|
2019-01-18 08:11:15 +00:00
|
|
|
};
|
2019-06-19 15:09:03 +00:00
|
|
|
setText(_header, tr::lng_group_about_header(tr::now));
|
|
|
|
setText(_text, tr::lng_group_about_text(tr::now));
|
2019-01-18 08:11:15 +00:00
|
|
|
for (const auto &text : phrases) {
|
|
|
|
_phrases.emplace_back(st::msgMinWidth);
|
|
|
|
setText(_phrases.back(), text);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void EmptyPainter::paint(Painter &p, int width, int height) {
|
|
|
|
if (_phrases.empty()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
constexpr auto kMaxTextLines = 3;
|
|
|
|
const auto maxPhraseWidth = ranges::max_element(
|
|
|
|
_phrases,
|
|
|
|
ranges::less(),
|
2019-06-12 13:26:04 +00:00
|
|
|
&Ui::Text::String::maxWidth
|
2019-01-18 08:11:15 +00:00
|
|
|
)->maxWidth();
|
|
|
|
|
|
|
|
const auto &font = st::serviceTextStyle.font;
|
|
|
|
const auto margin = st::msgMargin.left();
|
|
|
|
const auto maxBubbleWidth = width - 2 * st::historyGroupAboutMargin;
|
|
|
|
const auto padding = st::historyGroupAboutPadding;
|
|
|
|
const auto bubbleWidth = std::min(
|
|
|
|
maxBubbleWidth,
|
|
|
|
std::max({
|
|
|
|
maxPhraseWidth + st::historyGroupAboutBulletSkip,
|
|
|
|
_header.maxWidth(),
|
|
|
|
_text.maxWidth() }) + padding.left() + padding.right());
|
|
|
|
const auto innerWidth = bubbleWidth - padding.left() - padding.right();
|
2019-06-12 13:26:04 +00:00
|
|
|
const auto textHeight = [&](const Ui::Text::String &text) {
|
2019-01-18 08:11:15 +00:00
|
|
|
return std::min(
|
|
|
|
text.countHeight(innerWidth),
|
|
|
|
kMaxTextLines * font->height);
|
|
|
|
};
|
|
|
|
const auto bubbleHeight = padding.top()
|
|
|
|
+ textHeight(_header)
|
|
|
|
+ st::historyGroupAboutHeaderSkip
|
|
|
|
+ textHeight(_text)
|
|
|
|
+ st::historyGroupAboutTextSkip
|
|
|
|
+ ranges::accumulate(_phrases, 0, ranges::plus(), textHeight)
|
|
|
|
+ st::historyGroupAboutSkip * int(_phrases.size() - 1)
|
|
|
|
+ padding.bottom();
|
|
|
|
const auto bubbleLeft = (width - bubbleWidth) / 2;
|
2019-01-22 07:50:21 +00:00
|
|
|
const auto bubbleTop = (height - bubbleHeight) / 2;
|
2019-01-18 08:11:15 +00:00
|
|
|
|
|
|
|
ServiceMessagePainter::paintBubble(
|
|
|
|
p,
|
|
|
|
bubbleLeft,
|
|
|
|
bubbleTop,
|
|
|
|
bubbleWidth,
|
|
|
|
bubbleHeight);
|
|
|
|
|
|
|
|
p.setPen(st::msgServiceFg);
|
|
|
|
p.setBrush(st::msgServiceFg);
|
|
|
|
|
|
|
|
const auto left = bubbleLeft + padding.left();
|
|
|
|
auto top = bubbleTop + padding.top();
|
|
|
|
|
|
|
|
_header.drawElided(
|
|
|
|
p,
|
|
|
|
left,
|
|
|
|
top,
|
|
|
|
innerWidth,
|
|
|
|
kMaxTextLines,
|
|
|
|
style::al_top);
|
|
|
|
top += textHeight(_header) + st::historyGroupAboutHeaderSkip;
|
|
|
|
|
|
|
|
_text.drawElided(
|
|
|
|
p,
|
|
|
|
left,
|
|
|
|
top,
|
|
|
|
innerWidth,
|
|
|
|
kMaxTextLines);
|
|
|
|
top += textHeight(_text) + st::historyGroupAboutTextSkip;
|
|
|
|
|
|
|
|
for (const auto &text : _phrases) {
|
|
|
|
p.setPen(st::msgServiceFg);
|
|
|
|
text.drawElided(
|
|
|
|
p,
|
|
|
|
left + st::historyGroupAboutBulletSkip,
|
|
|
|
top,
|
|
|
|
innerWidth,
|
|
|
|
kMaxTextLines);
|
|
|
|
|
|
|
|
PainterHighQualityEnabler hq(p);
|
|
|
|
p.setPen(Qt::NoPen);
|
|
|
|
p.drawEllipse(
|
|
|
|
left,
|
|
|
|
top + (font->height - st::mediaUnreadSize) / 2,
|
|
|
|
st::mediaUnreadSize,
|
|
|
|
st::mediaUnreadSize);
|
|
|
|
top += textHeight(text) + st::historyGroupAboutSkip;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-09 17:08:31 +00:00
|
|
|
} // namespace HistoryView
|