tdesktop/Telegram/SourceFiles/history/history_media_grouped.cpp

410 lines
12 KiB
C++
Raw Normal View History

2017-12-13 18:10:48 +00:00
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
2017-12-13 18:10:48 +00:00
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
2017-12-13 18:10:48 +00:00
*/
#include "history/history_media_grouped.h"
#include "history/history_item_components.h"
2017-12-13 18:10:48 +00:00
#include "history/history_media_types.h"
#include "history/history_message.h"
#include "history/view/history_view_element.h"
#include "data/data_media_types.h"
2017-12-13 18:10:48 +00:00
#include "storage/storage_shared_media.h"
#include "lang/lang_keys.h"
#include "ui/grouped_layout.h"
#include "ui/text_options.h"
2017-12-13 18:10:48 +00:00
#include "styles/style_history.h"
#include "layout.h"
2017-12-13 18:10:48 +00:00
HistoryGroupedMedia::Part::Part(not_null<HistoryItem*> item)
: item(item) {
2017-12-13 18:10:48 +00:00
}
HistoryGroupedMedia::HistoryGroupedMedia(
not_null<Element*> parent,
const std::vector<not_null<HistoryItem*>> &items)
: HistoryMedia(parent)
, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) {
const auto result = applyGroup(items);
2017-12-13 18:10:48 +00:00
Ensures(result);
}
QSize HistoryGroupedMedia::countOptimalSize() {
if (_caption.hasSkipBlock()) {
_caption.updateSkipBlock(
_parent->skipBlockWidth(),
_parent->skipBlockHeight());
}
2017-12-13 18:10:48 +00:00
std::vector<QSize> sizes;
sizes.reserve(_parts.size());
for (const auto &part : _parts) {
const auto &media = part.content;
2017-12-13 18:10:48 +00:00
media->initDimensions();
sizes.push_back(media->sizeForGrouping());
}
2017-12-19 16:57:42 +00:00
const auto layout = Ui::LayoutMediaGroup(
2017-12-13 18:10:48 +00:00
sizes,
st::historyGroupWidthMax,
st::historyGroupWidthMin,
st::historyGroupSkip);
Assert(layout.size() == _parts.size());
2017-12-13 18:10:48 +00:00
auto maxWidth = 0;
auto minHeight = 0;
2017-12-13 18:10:48 +00:00
for (auto i = 0, count = int(layout.size()); i != count; ++i) {
const auto &item = layout[i];
accumulate_max(maxWidth, item.geometry.x() + item.geometry.width());
accumulate_max(minHeight, item.geometry.y() + item.geometry.height());
_parts[i].initialGeometry = item.geometry;
_parts[i].sides = item.sides;
2017-12-13 18:10:48 +00:00
}
if (!_caption.isEmpty()) {
auto captionw = maxWidth - st::msgPadding.left() - st::msgPadding.right();
minHeight += st::mediaCaptionSkip + _caption.countHeight(captionw);
if (isBubbleBottom()) {
minHeight += st::msgPadding.bottom();
}
}
return { maxWidth, minHeight };
2017-12-13 18:10:48 +00:00
}
QSize HistoryGroupedMedia::countCurrentSize(int newWidth) {
accumulate_min(newWidth, maxWidth());
auto newHeight = 0;
if (newWidth < st::historyGroupWidthMin) {
return { newWidth, newHeight };
2017-12-13 18:10:48 +00:00
}
const auto initialSpacing = st::historyGroupSkip;
const auto factor = newWidth / float64(maxWidth());
2017-12-13 18:10:48 +00:00
const auto scale = [&](int value) {
return int(std::round(value * factor));
};
const auto spacing = scale(initialSpacing);
for (auto &part : _parts) {
const auto sides = part.sides;
const auto initialGeometry = part.initialGeometry;
2017-12-13 18:10:48 +00:00
const auto needRightSkip = !(sides & RectPart::Right);
const auto needBottomSkip = !(sides & RectPart::Bottom);
const auto initialLeft = initialGeometry.x();
const auto initialTop = initialGeometry.y();
const auto initialRight = initialLeft
+ initialGeometry.width()
+ (needRightSkip ? initialSpacing : 0);
const auto initialBottom = initialTop
+ initialGeometry.height()
+ (needBottomSkip ? initialSpacing : 0);
const auto left = scale(initialLeft);
const auto top = scale(initialTop);
const auto width = scale(initialRight)
- left
- (needRightSkip ? spacing : 0);
const auto height = scale(initialBottom)
- top
- (needBottomSkip ? spacing : 0);
part.geometry = QRect(left, top, width, height);
2017-12-13 18:10:48 +00:00
accumulate_max(newHeight, top + height);
2017-12-13 18:10:48 +00:00
}
if (!_caption.isEmpty()) {
const auto captionw = newWidth - st::msgPadding.left() - st::msgPadding.right();
newHeight += st::mediaCaptionSkip + _caption.countHeight(captionw);
if (isBubbleBottom()) {
newHeight += st::msgPadding.bottom();
}
}
return { newWidth, newHeight };
2017-12-13 18:10:48 +00:00
}
void HistoryGroupedMedia::refreshParentId(
not_null<HistoryItem*> realParent) {
for (const auto &part : _parts) {
part.content->refreshParentId(part.item);
}
}
2017-12-13 18:10:48 +00:00
void HistoryGroupedMedia::draw(
Painter &p,
const QRect &clip,
TextSelection selection,
TimeMs ms) const {
for (auto i = 0, count = int(_parts.size()); i != count; ++i) {
const auto &part = _parts[i];
const auto partSelection = (selection == FullSelection)
? FullSelection
: IsGroupItemSelection(selection, i)
? FullSelection
: TextSelection();
auto corners = Ui::GetCornersFromSides(part.sides);
2017-12-13 18:10:48 +00:00
if (!isBubbleTop()) {
corners &= ~(RectPart::TopLeft | RectPart::TopRight);
}
if (!isBubbleBottom() || !_caption.isEmpty()) {
corners &= ~(RectPart::BottomLeft | RectPart::BottomRight);
}
part.content->drawGrouped(
2017-12-13 18:10:48 +00:00
p,
clip,
partSelection,
2017-12-13 18:10:48 +00:00
ms,
part.geometry,
2017-12-13 18:10:48 +00:00
corners,
&part.cacheKey,
&part.cache);
2017-12-13 18:10:48 +00:00
}
// date
const auto selected = (selection == FullSelection);
if (!_caption.isEmpty()) {
const auto captionw = width() - st::msgPadding.left() - st::msgPadding.right();
const auto outbg = _parent->hasOutLayout();
const auto captiony = height()
- (isBubbleBottom() ? st::msgPadding.bottom() : 0)
- _caption.countHeight(captionw);
p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg));
_caption.draw(p, st::msgPadding.left(), captiony, captionw, style::al_left, 0, -1, selection);
} else if (_parent->media() == this) {
auto fullRight = width();
auto fullBottom = height();
if (needInfoDisplay()) {
_parent->drawInfo(p, fullRight, fullBottom, width(), selected, InfoDisplayOverImage);
}
if (!_parent->hasBubble() && _parent->displayRightAction()) {
auto fastShareLeft = (fullRight + st::historyFastShareLeft);
auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize);
_parent->drawRightAction(p, fastShareLeft, fastShareTop, width());
}
}
2017-12-13 18:10:48 +00:00
}
HistoryTextState HistoryGroupedMedia::getPartState(
2017-12-13 18:10:48 +00:00
QPoint point,
HistoryStateRequest request) const {
for (const auto &part : _parts) {
if (part.geometry.contains(point)) {
auto result = part.content->getStateGrouped(
part.geometry,
2017-12-13 18:10:48 +00:00
point,
request);
result.itemId = part.item->fullId();
return result;
}
}
return HistoryTextState(_parent->data());
2017-12-16 07:20:04 +00:00
}
HistoryTextState HistoryGroupedMedia::getState(
QPoint point,
HistoryStateRequest request) const {
auto result = getPartState(point, request);
2017-12-16 07:20:04 +00:00
if (!result.link && !_caption.isEmpty()) {
const auto captionw = width() - st::msgPadding.left() - st::msgPadding.right();
const auto captiony = height()
- (isBubbleBottom() ? st::msgPadding.bottom() : 0)
- _caption.countHeight(captionw);
if (QRect(st::msgPadding.left(), captiony, captionw, height() - captiony).contains(point)) {
return HistoryTextState(_parent->data(), _caption.getState(
point - QPoint(st::msgPadding.left(), captiony),
captionw,
request.forText()));
}
} else if (_parent->media() == this) {
auto fullRight = width();
auto fullBottom = height();
if (_parent->pointInTime(fullRight, fullBottom, point, InfoDisplayOverImage)) {
result.cursor = HistoryInDateCursorState;
}
if (!_parent->hasBubble() && _parent->displayRightAction()) {
auto fastShareLeft = (fullRight + st::historyFastShareLeft);
auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize);
if (QRect(fastShareLeft, fastShareTop, st::historyFastShareSize, st::historyFastShareSize).contains(point)) {
result.link = _parent->rightActionLink();
}
2017-12-13 18:10:48 +00:00
}
}
2017-12-16 07:20:04 +00:00
return result;
2017-12-13 18:10:48 +00:00
}
bool HistoryGroupedMedia::toggleSelectionByHandlerClick(
const ClickHandlerPtr &p) const {
for (const auto &part : _parts) {
if (part.content->toggleSelectionByHandlerClick(p)) {
2017-12-13 18:10:48 +00:00
return true;
}
}
return false;
}
bool HistoryGroupedMedia::dragItemByHandler(const ClickHandlerPtr &p) const {
for (const auto &part : _parts) {
if (part.content->dragItemByHandler(p)) {
2017-12-13 18:10:48 +00:00
return true;
}
}
return false;
}
TextSelection HistoryGroupedMedia::adjustSelection(
TextSelection selection,
TextSelectType type) const {
return _caption.adjustSelection(selection, type);
}
TextWithEntities HistoryGroupedMedia::selectedText(
TextSelection selection) const {
if (!IsSubGroupSelection(selection)) {
return WithCaptionSelectedText(
lang(lng_in_dlg_album),
_caption,
selection);
} else if (IsGroupItemSelection(selection, int(_parts.size()) - 1)) {
return main()->selectedText(FullSelection);
}
return TextWithEntities();
2017-12-13 18:10:48 +00:00
}
void HistoryGroupedMedia::clickHandlerActiveChanged(
const ClickHandlerPtr &p,
bool active) {
for (const auto &part : _parts) {
part.content->clickHandlerActiveChanged(p, active);
2017-12-13 18:10:48 +00:00
}
}
void HistoryGroupedMedia::clickHandlerPressedChanged(
const ClickHandlerPtr &p,
bool pressed) {
for (const auto &part : _parts) {
part.content->clickHandlerPressedChanged(p, pressed);
if (pressed && part.content->dragItemByHandler(p)) {
// #TODO drag by item from album
// App::pressedLinkItem(part.view);
2017-12-13 18:10:48 +00:00
}
}
}
std::unique_ptr<HistoryMedia> HistoryGroupedMedia::takeLastFromGroup() {
return std::move(_parts.back().content);
2017-12-13 18:10:48 +00:00
}
bool HistoryGroupedMedia::applyGroup(
const std::vector<not_null<HistoryItem*>> &items) {
if (items.empty()) {
2017-12-13 18:10:48 +00:00
return false;
}
if (validateGroupParts(items)) {
2017-12-13 18:10:48 +00:00
return true;
}
const auto pushElement = [&](not_null<HistoryItem*> item) {
const auto media = item->media();
Assert(media != nullptr && media->canBeGrouped());
_parts.push_back(Part(item));
_parts.back().content = media->createView(_parent, item);
};
ranges::for_each(items, pushElement);
2017-12-13 18:10:48 +00:00
return true;
}
bool HistoryGroupedMedia::validateGroupParts(
const std::vector<not_null<HistoryItem*>> &items) const {
if (_parts.size() != items.size()) {
2017-12-13 18:10:48 +00:00
return false;
}
for (auto i = 0, count = int(items.size()); i != count; ++i) {
if (_parts[i].item != items[i]) {
2017-12-13 18:10:48 +00:00
return false;
}
}
return true;
}
not_null<HistoryMedia*> HistoryGroupedMedia::main() const {
Expects(!_parts.empty());
2017-12-13 18:10:48 +00:00
return _parts.back().content.get();
2017-12-13 18:10:48 +00:00
}
bool HistoryGroupedMedia::hasReplyPreview() const {
return main()->hasReplyPreview();
}
ImagePtr HistoryGroupedMedia::replyPreview() {
return main()->replyPreview();
}
TextWithEntities HistoryGroupedMedia::getCaption() const {
return main()->getCaption();
}
2017-12-13 18:10:48 +00:00
Storage::SharedMediaTypesMask HistoryGroupedMedia::sharedMediaTypes() const {
return main()->sharedMediaTypes();
}
2017-12-19 16:57:42 +00:00
PhotoData *HistoryGroupedMedia::getPhoto() const {
return main()->getPhoto();
}
DocumentData *HistoryGroupedMedia::getDocument() const {
return main()->getDocument();
}
HistoryMessageEdited *HistoryGroupedMedia::displayedEditBadge() const {
if (!_caption.isEmpty()) {
return _parts.front().item->Get<HistoryMessageEdited>();
}
return nullptr;
}
2017-12-13 18:10:48 +00:00
void HistoryGroupedMedia::updateNeedBubbleState() {
2018-01-18 13:59:22 +00:00
const auto hasCaption = [&] {
if (_parts.front().item->emptyText()) {
return false;
}
for (auto i = 1, count = int(_parts.size()); i != count; ++i) {
2018-01-18 13:59:22 +00:00
if (!_parts[i].item->emptyText()) {
return false;
2017-12-13 18:10:48 +00:00
}
}
2018-01-18 13:59:22 +00:00
return true;
2017-12-13 18:10:48 +00:00
}();
2018-01-18 13:59:22 +00:00
if (hasCaption) {
_caption = createCaption(_parts.front().item);
2018-01-18 13:59:22 +00:00
}
2017-12-13 18:10:48 +00:00
_needBubble = computeNeedBubble();
}
bool HistoryGroupedMedia::needsBubble() const {
return _needBubble;
}
bool HistoryGroupedMedia::computeNeedBubble() const {
if (!_caption.isEmpty()) {
return true;
}
if (const auto item = _parent->data()) {
if (item->viaBot()
|| item->Has<HistoryMessageReply>()
|| _parent->displayForwardedFrom()
|| _parent->displayFromName()
) {
2017-12-16 13:09:53 +00:00
return true;
2017-12-13 18:10:48 +00:00
}
}
return false;
}
bool HistoryGroupedMedia::needInfoDisplay() const {
return (_parent->data()->id < 0 || _parent->isUnderCursor());
}