tdesktop/Telegram/SourceFiles/history/media/history_media_document.cpp

848 lines
31 KiB
C++

/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "history/media/history_media_document.h"
#include "lang/lang_keys.h"
#include "layout.h"
#include "storage/localstorage.h"
#include "media/audio/media_audio.h"
#include "media/player/media_player_instance.h"
#include "history/history_item_components.h"
#include "history/history.h"
#include "history/view/history_view_element.h"
#include "history/view/history_view_cursor_state.h"
#include "history/media/history_media_common.h"
#include "ui/image/image.h"
#include "data/data_session.h"
#include "data/data_document.h"
#include "data/data_media_types.h"
#include "styles/style_history.h"
namespace {
constexpr auto kAudioVoiceMsgUpdateView = crl::time(100);
using TextState = HistoryView::TextState;
} // namespace
HistoryDocument::HistoryDocument(
not_null<Element*> parent,
not_null<DocumentData*> document)
: HistoryFileMedia(parent, parent->data())
, _data(document) {
const auto item = parent->data();
auto caption = createCaption(item);
createComponents(!caption.isEmpty());
if (auto named = Get<HistoryDocumentNamed>()) {
fillNamedFromData(named);
}
setDocumentLinks(_data, item);
setStatusSize(FileStatusSizeReady);
if (auto captioned = Get<HistoryDocumentCaptioned>()) {
captioned->_caption = std::move(caption);
}
}
float64 HistoryDocument::dataProgress() const {
return _data->progress();
}
bool HistoryDocument::dataFinished() const {
return !_data->loading() && !_data->uploading();
}
bool HistoryDocument::dataLoaded() const {
return _data->loaded();
}
void HistoryDocument::createComponents(bool caption) {
uint64 mask = 0;
if (_data->isVoiceMessage()) {
mask |= HistoryDocumentVoice::Bit();
} else {
mask |= HistoryDocumentNamed::Bit();
if (const auto thumb = _data->thumbnail()) {
if (!_data->isSong()
&& thumb->width()
&& thumb->height()
&& !Data::IsExecutableName(_data->filename())) {
mask |= HistoryDocumentThumbed::Bit();
}
}
}
if (caption) {
mask |= HistoryDocumentCaptioned::Bit();
}
UpdateComponents(mask);
if (const auto thumbed = Get<HistoryDocumentThumbed>()) {
thumbed->_linksavel = std::make_shared<DocumentSaveClickHandler>(
_data,
_parent->data()->fullId());
thumbed->_linkopenwithl = std::make_shared<DocumentOpenWithClickHandler>(
_data,
_parent->data()->fullId());
thumbed->_linkcancell = std::make_shared<DocumentCancelClickHandler>(
_data,
_parent->data()->fullId());
}
if (const auto voice = Get<HistoryDocumentVoice>()) {
voice->_seekl = std::make_shared<VoiceSeekClickHandler>(
_data,
_parent->data()->fullId());
}
}
void HistoryDocument::fillNamedFromData(HistoryDocumentNamed *named) {
const auto nameString = named->_name = _data->composeNameString();
named->_namew = st::semiboldFont->width(nameString);
}
QSize HistoryDocument::countOptimalSize() {
const auto item = _parent->data();
auto captioned = Get<HistoryDocumentCaptioned>();
if (_parent->media() != this) {
if (captioned) {
RemoveComponents(HistoryDocumentCaptioned::Bit());
captioned = nullptr;
}
} else if (captioned && captioned->_caption.hasSkipBlock()) {
captioned->_caption.updateSkipBlock(
_parent->skipBlockWidth(),
_parent->skipBlockHeight());
}
auto thumbed = Get<HistoryDocumentThumbed>();
if (thumbed) {
_data->loadThumbnail(_realParent->fullId());
auto tw = ConvertScale(_data->thumbnail()->width());
auto th = ConvertScale(_data->thumbnail()->height());
if (tw > th) {
thumbed->_thumbw = (tw * st::msgFileThumbSize) / th;
} else {
thumbed->_thumbw = st::msgFileThumbSize;
}
}
auto maxWidth = st::msgFileMinWidth;
auto tleft = 0;
auto tright = 0;
if (thumbed) {
tleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right();
tright = st::msgFileThumbPadding.left();
accumulate_max(maxWidth, tleft + documentMaxStatusWidth(_data) + tright);
} else {
tleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right();
tright = st::msgFileThumbPadding.left();
auto unread = _data->isVoiceMessage() ? (st::mediaUnreadSkip + st::mediaUnreadSize) : 0;
accumulate_max(maxWidth, tleft + documentMaxStatusWidth(_data) + unread + _parent->skipBlockWidth() + st::msgPadding.right());
}
if (auto named = Get<HistoryDocumentNamed>()) {
accumulate_max(maxWidth, tleft + named->_namew + tright);
accumulate_min(maxWidth, st::msgMaxWidth);
}
auto minHeight = 0;
if (thumbed) {
minHeight = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom();
} else {
minHeight = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom();
}
if (!captioned && (item->Has<HistoryMessageSigned>()
|| item->Has<HistoryMessageViews>()
|| _parent->displayEditedBadge())) {
minHeight += st::msgDateFont->height - st::msgDateDelta.y();
}
if (!isBubbleTop()) {
minHeight -= st::msgFileTopMinus;
}
if (captioned) {
auto captionw = maxWidth
- st::msgPadding.left()
- st::msgPadding.right();
minHeight += captioned->_caption.countHeight(captionw);
if (isBubbleBottom()) {
minHeight += st::msgPadding.bottom();
}
}
return { maxWidth, minHeight };
}
QSize HistoryDocument::countCurrentSize(int newWidth) {
auto captioned = Get<HistoryDocumentCaptioned>();
if (!captioned) {
return HistoryFileMedia::countCurrentSize(newWidth);
}
accumulate_min(newWidth, maxWidth());
auto newHeight = 0;
if (Get<HistoryDocumentThumbed>()) {
newHeight = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom();
} else {
newHeight = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom();
}
if (!isBubbleTop()) {
newHeight -= st::msgFileTopMinus;
}
auto captionw = newWidth - st::msgPadding.left() - st::msgPadding.right();
newHeight += captioned->_caption.countHeight(captionw);
if (isBubbleBottom()) {
newHeight += st::msgPadding.bottom();
}
return { newWidth, newHeight };
}
void HistoryDocument::draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms) const {
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
const auto cornerDownload = downloadInCorner();
_data->automaticLoad(_realParent->fullId(), _parent->data());
bool loaded = _data->loaded(), displayLoading = _data->displayLoading();
bool selected = (selection == FullSelection);
int captionw = width() - st::msgPadding.left() - st::msgPadding.right();
auto outbg = _parent->hasOutLayout();
if (displayLoading) {
ensureAnimation();
if (!_animation->radial.animating()) {
_animation->radial.start(_data->progress());
}
}
const auto showPause = updateStatusText();
const auto radial = isRadialAnimation();
auto topMinus = isBubbleTop() ? 0 : st::msgFileTopMinus;
int nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0, bottom = 0;
if (auto thumbed = Get<HistoryDocumentThumbed>()) {
nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right();
nametop = st::msgFileThumbNameTop - topMinus;
nameright = st::msgFileThumbPadding.left();
statustop = st::msgFileThumbStatusTop - topMinus;
linktop = st::msgFileThumbLinkTop - topMinus;
bottom = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom() - topMinus;
auto inWebPage = (_parent->media() != this);
auto roundRadius = inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large;
QRect rthumb(rtlrect(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top() - topMinus, st::msgFileThumbSize, st::msgFileThumbSize, width()));
QPixmap thumb;
if (const auto normal = _data->thumbnail()) {
if (normal->loaded()) {
thumb = normal->pixSingle(_realParent->fullId(), thumbed->_thumbw, 0, st::msgFileThumbSize, st::msgFileThumbSize, roundRadius);
} else {
_data->loadThumbnail(_realParent->fullId());
if (const auto blurred = _data->thumbnailInline()) {
thumb = blurred->pixBlurredSingle(_realParent->fullId(), thumbed->_thumbw, 0, st::msgFileThumbSize, st::msgFileThumbSize, roundRadius);
}
}
}
p.drawPixmap(rthumb.topLeft(), thumb);
if (selected) {
auto overlayCorners = inWebPage ? SelectedOverlaySmallCorners : SelectedOverlayLargeCorners;
App::roundRect(p, rthumb, p.textPalette().selectOverlay, overlayCorners);
}
if (radial || (!loaded && !_data->loading())) {
float64 radialOpacity = (radial && loaded && !_data->uploading()) ? _animation->radial.opacity() : 1;
QRect inner(rthumb.x() + (rthumb.width() - st::msgFileSize) / 2, rthumb.y() + (rthumb.height() - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize);
p.setPen(Qt::NoPen);
if (selected) {
p.setBrush(st::msgDateImgBgSelected);
} else {
p.setBrush(st::msgDateImgBg);
}
p.setOpacity(radialOpacity * p.opacity());
{
PainterHighQualityEnabler hq(p);
p.drawEllipse(inner);
}
p.setOpacity(radialOpacity);
auto icon = ([radial, this, selected] {
if (radial || _data->loading()) {
return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel);
}
return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload);
})();
p.setOpacity((radial && loaded) ? _animation->radial.opacity() : 1);
icon->paintInCenter(p, inner);
if (radial) {
p.setOpacity(1);
QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine)));
_animation->radial.draw(p, rinner, st::msgFileRadialLine, selected ? st::historyFileThumbRadialFgSelected : st::historyFileThumbRadialFg);
}
}
if (_data->status != FileUploadFailed) {
const auto &lnk = (_data->loading() || _data->uploading())
? thumbed->_linkcancell
: _data->loaded()
? thumbed->_linkopenwithl
: thumbed->_linksavel;
bool over = ClickHandler::showAsActive(lnk);
p.setFont(over ? st::semiboldFont->underline() : st::semiboldFont);
p.setPen(outbg ? (selected ? st::msgFileThumbLinkOutFgSelected : st::msgFileThumbLinkOutFg) : (selected ? st::msgFileThumbLinkInFgSelected : st::msgFileThumbLinkInFg));
p.drawTextLeft(nameleft, linktop, width(), thumbed->_link, thumbed->_linkw);
}
} else {
nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right();
nametop = st::msgFileNameTop - topMinus;
nameright = st::msgFilePadding.left();
statustop = st::msgFileStatusTop - topMinus;
bottom = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom() - topMinus;
QRect inner(rtlrect(st::msgFilePadding.left(), st::msgFilePadding.top() - topMinus, st::msgFileSize, st::msgFileSize, width()));
p.setPen(Qt::NoPen);
if (selected) {
p.setBrush(outbg ? st::msgFileOutBgSelected : st::msgFileInBgSelected);
} else {
p.setBrush(outbg ? st::msgFileOutBg : st::msgFileInBg);
}
{
PainterHighQualityEnabler hq(p);
p.drawEllipse(inner);
}
const auto icon = [&] {
if (!cornerDownload && (_data->loading() || _data->uploading())) {
return &(outbg ? (selected ? st::historyFileOutCancelSelected : st::historyFileOutCancel) : (selected ? st::historyFileInCancelSelected : st::historyFileInCancel));
} else if (showPause) {
return &(outbg ? (selected ? st::historyFileOutPauseSelected : st::historyFileOutPause) : (selected ? st::historyFileInPauseSelected : st::historyFileInPause));
} else if (loaded || _data->canBePlayed()) {
if (_data->canBePlayed()) {
return &(outbg ? (selected ? st::historyFileOutPlaySelected : st::historyFileOutPlay) : (selected ? st::historyFileInPlaySelected : st::historyFileInPlay));
} else if (_data->isImage()) {
return &(outbg ? (selected ? st::historyFileOutImageSelected : st::historyFileOutImage) : (selected ? st::historyFileInImageSelected : st::historyFileInImage));
}
return &(outbg ? (selected ? st::historyFileOutDocumentSelected : st::historyFileOutDocument) : (selected ? st::historyFileInDocumentSelected : st::historyFileInDocument));
}
return &(outbg ? (selected ? st::historyFileOutDownloadSelected : st::historyFileOutDownload) : (selected ? st::historyFileInDownloadSelected : st::historyFileInDownload));
}();
icon->paintInCenter(p, inner);
if (radial && !cornerDownload) {
QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine)));
auto fg = outbg ? (selected ? st::historyFileOutRadialFgSelected : st::historyFileOutRadialFg) : (selected ? st::historyFileInRadialFgSelected : st::historyFileInRadialFg);
_animation->radial.draw(p, rinner, st::msgFileRadialLine, fg);
}
drawCornerDownload(p, selected);
}
auto namewidth = width() - nameleft - nameright;
auto statuswidth = namewidth;
auto voiceStatusOverride = QString();
if (const auto voice = Get<HistoryDocumentVoice>()) {
const VoiceWaveform *wf = nullptr;
uchar norm_value = 0;
if (const auto voiceData = _data->voice()) {
wf = &voiceData->waveform;
if (wf->isEmpty()) {
wf = nullptr;
if (loaded) {
Local::countVoiceWaveform(_data);
}
} else if (wf->at(0) < 0) {
wf = nullptr;
} else {
norm_value = voiceData->wavemax;
}
}
auto progress = ([voice] {
if (voice->seeking()) {
return voice->seekingCurrent();
} else if (voice->_playback) {
return voice->_playback->progress.current();
}
return 0.;
})();
if (voice->seeking()) {
voiceStatusOverride = formatPlayedText(qRound(progress * voice->_lastDurationMs) / 1000, voice->_lastDurationMs / 1000);
}
// rescale waveform by going in waveform.size * bar_count 1D grid
auto active = outbg ? (selected ? st::msgWaveformOutActiveSelected : st::msgWaveformOutActive) : (selected ? st::msgWaveformInActiveSelected : st::msgWaveformInActive);
auto inactive = outbg ? (selected ? st::msgWaveformOutInactiveSelected : st::msgWaveformOutInactive) : (selected ? st::msgWaveformInInactiveSelected : st::msgWaveformInInactive);
auto wf_size = wf ? wf->size() : Media::Player::kWaveformSamplesCount;
auto availw = namewidth + st::msgWaveformSkip;
auto activew = qRound(availw * progress);
if (!outbg
&& !voice->_playback
&& _parent->data()->hasUnreadMediaFlag()) {
activew = availw;
}
auto bar_count = qMin(availw / (st::msgWaveformBar + st::msgWaveformSkip), wf_size);
auto max_value = 0;
auto max_delta = st::msgWaveformMax - st::msgWaveformMin;
auto bottom = st::msgFilePadding.top() - topMinus + st::msgWaveformMax;
p.setPen(Qt::NoPen);
for (auto i = 0, bar_x = 0, sum_i = 0; i < wf_size; ++i) {
auto value = wf ? wf->at(i) : 0;
if (sum_i + bar_count >= wf_size) { // draw bar
sum_i = sum_i + bar_count - wf_size;
if (sum_i < (bar_count + 1) / 2) {
if (max_value < value) max_value = value;
}
auto bar_value = ((max_value * max_delta) + ((norm_value + 1) / 2)) / (norm_value + 1);
if (bar_x >= activew) {
p.fillRect(nameleft + bar_x, bottom - bar_value, st::msgWaveformBar, st::msgWaveformMin + bar_value, inactive);
} else if (bar_x + st::msgWaveformBar <= activew) {
p.fillRect(nameleft + bar_x, bottom - bar_value, st::msgWaveformBar, st::msgWaveformMin + bar_value, active);
} else {
p.fillRect(nameleft + bar_x, bottom - bar_value, activew - bar_x, st::msgWaveformMin + bar_value, active);
p.fillRect(nameleft + activew, bottom - bar_value, st::msgWaveformBar - (activew - bar_x), st::msgWaveformMin + bar_value, inactive);
}
bar_x += st::msgWaveformBar + st::msgWaveformSkip;
if (sum_i < (bar_count + 1) / 2) {
max_value = 0;
} else {
max_value = value;
}
} else {
if (max_value < value) max_value = value;
sum_i += bar_count;
}
}
} else if (auto named = Get<HistoryDocumentNamed>()) {
p.setFont(st::semiboldFont);
p.setPen(outbg ? (selected ? st::historyFileNameOutFgSelected : st::historyFileNameOutFg) : (selected ? st::historyFileNameInFgSelected : st::historyFileNameInFg));
if (namewidth < named->_namew) {
p.drawTextLeft(nameleft, nametop, width(), st::semiboldFont->elided(named->_name, namewidth, Qt::ElideMiddle));
} else {
p.drawTextLeft(nameleft, nametop, width(), named->_name, named->_namew);
}
}
auto statusText = voiceStatusOverride.isEmpty() ? _statusText : voiceStatusOverride;
auto status = outbg ? (selected ? st::mediaOutFgSelected : st::mediaOutFg) : (selected ? st::mediaInFgSelected : st::mediaInFg);
p.setFont(st::normalFont);
p.setPen(status);
p.drawTextLeft(nameleft, statustop, width(), statusText);
if (_parent->data()->hasUnreadMediaFlag()) {
auto w = st::normalFont->width(statusText);
if (w + st::mediaUnreadSkip + st::mediaUnreadSize <= statuswidth) {
p.setPen(Qt::NoPen);
p.setBrush(outbg ? (selected ? st::msgFileOutBgSelected : st::msgFileOutBg) : (selected ? st::msgFileInBgSelected : st::msgFileInBg));
{
PainterHighQualityEnabler hq(p);
p.drawEllipse(rtlrect(nameleft + w + st::mediaUnreadSkip, statustop + st::mediaUnreadTop, st::mediaUnreadSize, st::mediaUnreadSize, width()));
}
}
}
if (auto captioned = Get<HistoryDocumentCaptioned>()) {
p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg));
captioned->_caption.draw(p, st::msgPadding.left(), bottom, captionw, style::al_left, 0, -1, selection);
}
}
bool HistoryDocument::downloadInCorner() const {
return _data->isAudioFile()
&& _data->canBeStreamed()
&& !_data->inappPlaybackFailed()
&& IsServerMsgId(_parent->data()->id);
}
void HistoryDocument::drawCornerDownload(Painter &p, bool selected) const {
if (_data->loaded() || !downloadInCorner()) {
return;
}
auto outbg = _parent->hasOutLayout();
auto topMinus = isBubbleTop() ? 0 : st::msgFileTopMinus;
const auto shift = st::historyAudioDownloadShift;
const auto size = st::historyAudioDownloadSize;
const auto inner = rtlrect(st::msgFilePadding.left() + shift, st::msgFilePadding.top() - topMinus + shift, size, size, width());
auto pen = (selected
? (outbg ? st::msgOutBgSelected : st::msgInBgSelected)
: (outbg ? st::msgOutBg : st::msgInBg))->p;
pen.setWidth(st::lineWidth);
p.setPen(pen);
if (selected) {
p.setBrush(outbg ? st::msgFileOutBgSelected : st::msgFileInBgSelected);
} else {
p.setBrush(outbg ? st::msgFileOutBg : st::msgFileInBg);
}
{
PainterHighQualityEnabler hq(p);
p.drawEllipse(inner);
}
const auto icon = [&] {
if (_data->loading()) {
return &(outbg ? (selected ? st::historyAudioOutCancelSelected : st::historyAudioOutCancel) : (selected ? st::historyAudioInCancelSelected : st::historyAudioInCancel));
}
return &(outbg ? (selected ? st::historyAudioOutDownloadSelected : st::historyAudioOutDownload) : (selected ? st::historyAudioInDownloadSelected : st::historyAudioInDownload));
}();
icon->paintInCenter(p, inner);
if (_animation && _animation->radial.animating()) {
const auto rinner = inner.marginsRemoved(QMargins(st::historyAudioRadialLine, st::historyAudioRadialLine, st::historyAudioRadialLine, st::historyAudioRadialLine));
auto fg = outbg ? (selected ? st::historyFileOutRadialFgSelected : st::historyFileOutRadialFg) : (selected ? st::historyFileInRadialFgSelected : st::historyFileInRadialFg);
_animation->radial.draw(p, rinner, st::historyAudioRadialLine, fg);
}
}
TextState HistoryDocument::cornerDownloadTextState(
QPoint point,
StateRequest request) const {
auto result = TextState(_parent);
if (!downloadInCorner() || _data->loaded()) {
return result;
}
auto topMinus = isBubbleTop() ? 0 : st::msgFileTopMinus;
const auto shift = st::historyAudioDownloadShift;
const auto size = st::historyAudioDownloadSize;
const auto inner = rtlrect(st::msgFilePadding.left() + shift, st::msgFilePadding.top() - topMinus + shift, size, size, width());
if (inner.contains(point)) {
result.link = _data->loading() ? _cancell : _savel;
}
return result;
}
TextState HistoryDocument::textState(QPoint point, StateRequest request) const {
auto result = TextState(_parent);
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) {
return result;
}
bool loaded = _data->loaded();
bool showPause = updateStatusText();
auto nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0, bottom = 0;
auto topMinus = isBubbleTop() ? 0 : st::msgFileTopMinus;
if (const auto thumbed = Get<HistoryDocumentThumbed>()) {
nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right();
nameright = st::msgFileThumbPadding.left();
nametop = st::msgFileThumbNameTop - topMinus;
linktop = st::msgFileThumbLinkTop - topMinus;
bottom = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom() - topMinus;
QRect rthumb(rtlrect(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top() - topMinus, st::msgFileThumbSize, st::msgFileThumbSize, width()));
if ((_data->loading() || _data->uploading()) && rthumb.contains(point)) {
result.link = _cancell;
return result;
}
if (_data->status != FileUploadFailed) {
if (rtlrect(nameleft, linktop, thumbed->_linkw, st::semiboldFont->height, width()).contains(point)) {
result.link = (_data->loading() || _data->uploading())
? thumbed->_linkcancell
: _data->loaded()
? thumbed->_linkopenwithl
: thumbed->_linksavel;
return result;
}
}
} else {
nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right();
nameright = st::msgFilePadding.left();
nametop = st::msgFileNameTop - topMinus;
bottom = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom() - topMinus;
if (const auto state = cornerDownloadTextState(point, request); state.link) {
return state;
}
QRect inner(rtlrect(st::msgFilePadding.left(), st::msgFilePadding.top() - topMinus, st::msgFileSize, st::msgFileSize, width()));
if ((_data->loading() || _data->uploading()) && inner.contains(point) && !downloadInCorner()) {
result.link = _cancell;
return result;
}
}
if (const auto voice = Get<HistoryDocumentVoice>()) {
auto namewidth = width() - nameleft - nameright;
auto waveformbottom = st::msgFilePadding.top() - topMinus + st::msgWaveformMax + st::msgWaveformMin;
if (QRect(nameleft, nametop, namewidth, waveformbottom - nametop).contains(point)) {
const auto state = Media::Player::instance()->getState(AudioMsgId::Type::Voice);
if (state.id == AudioMsgId(_data, _parent->data()->fullId(), state.id.externalPlayId())
&& !Media::Player::IsStoppedOrStopping(state.state)) {
if (!voice->seeking()) {
voice->setSeekingStart((point.x() - nameleft) / float64(namewidth));
}
result.link = voice->_seekl;
return result;
}
}
}
auto painth = height();
if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
if (point.y() >= bottom) {
result = TextState(_parent, captioned->_caption.getState(
point - QPoint(st::msgPadding.left(), bottom),
width() - st::msgPadding.left() - st::msgPadding.right(),
request.forText()));
return result;
}
auto captionw = width() - st::msgPadding.left() - st::msgPadding.right();
painth -= captioned->_caption.countHeight(captionw);
if (isBubbleBottom()) {
painth -= st::msgPadding.bottom();
}
}
if (QRect(0, 0, width(), painth).contains(point)
&& (!_data->loading() || downloadInCorner())
&& !_data->uploading()
&& !_data->isNull()) {
if (loaded || _data->canBePlayed()) {
result.link = _openl;
} else {
result.link = _savel;
}
return result;
}
return result;
}
void HistoryDocument::updatePressed(QPoint point) {
if (auto voice = Get<HistoryDocumentVoice>()) {
if (voice->seeking()) {
auto nameleft = 0, nameright = 0;
if (auto thumbed = Get<HistoryDocumentThumbed>()) {
nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right();
nameright = st::msgFileThumbPadding.left();
} else {
nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right();
nameright = st::msgFilePadding.left();
}
voice->setSeekingCurrent(snap((point.x() - nameleft) / float64(width() - nameleft - nameright), 0., 1.));
history()->owner().requestViewRepaint(_parent);
}
}
}
TextSelection HistoryDocument::adjustSelection(
TextSelection selection,
TextSelectType type) const {
if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
return captioned->_caption.adjustSelection(selection, type);
}
return selection;
}
uint16 HistoryDocument::fullSelectionLength() const {
if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
return captioned->_caption.length();
}
return 0;
}
bool HistoryDocument::hasTextForCopy() const {
return Has<HistoryDocumentCaptioned>();
}
TextForMimeData HistoryDocument::selectedText(TextSelection selection) const {
if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
const auto &caption = captioned->_caption;
return captioned->_caption.toTextForMimeData(selection);
}
return TextForMimeData();
}
bool HistoryDocument::uploading() const {
return _data->uploading();
}
void HistoryDocument::setStatusSize(int newSize, qint64 realDuration) const {
auto duration = _data->isSong()
? _data->song()->duration
: (_data->isVoiceMessage()
? _data->voice()->duration
: -1);
HistoryFileMedia::setStatusSize(newSize, _data->size, duration, realDuration);
if (auto thumbed = Get<HistoryDocumentThumbed>()) {
if (_statusSize == FileStatusSizeReady) {
thumbed->_link = tr::lng_media_download(tr::now).toUpper();
} else if (_statusSize == FileStatusSizeLoaded) {
thumbed->_link = tr::lng_media_open_with(tr::now).toUpper();
} else if (_statusSize == FileStatusSizeFailed) {
thumbed->_link = tr::lng_media_download(tr::now).toUpper();
} else if (_statusSize >= 0) {
thumbed->_link = tr::lng_media_cancel(tr::now).toUpper();
} else {
thumbed->_link = tr::lng_media_open_with(tr::now).toUpper();
}
thumbed->_linkw = st::semiboldFont->width(thumbed->_link);
}
}
bool HistoryDocument::updateStatusText() const {
auto showPause = false;
auto statusSize = 0;
auto realDuration = 0;
if (_data->status == FileDownloadFailed || _data->status == FileUploadFailed) {
statusSize = FileStatusSizeFailed;
} else if (_data->uploading()) {
statusSize = _data->uploadingData->offset;
} else if (_data->loading()) {
statusSize = _data->loadOffset();
} else if (_data->loaded()) {
statusSize = FileStatusSizeLoaded;
} else {
statusSize = FileStatusSizeReady;
}
if (_data->isVoiceMessage()) {
const auto state = Media::Player::instance()->getState(AudioMsgId::Type::Voice);
if (state.id == AudioMsgId(_data, _parent->data()->fullId(), state.id.externalPlayId())
&& !Media::Player::IsStoppedOrStopping(state.state)) {
if (auto voice = Get<HistoryDocumentVoice>()) {
bool was = (voice->_playback != nullptr);
voice->ensurePlayback(this);
if (!was || state.position != voice->_playback->position) {
auto prg = state.length ? snap(float64(state.position) / state.length, 0., 1.) : 0.;
if (voice->_playback->position < state.position) {
voice->_playback->progress.start(prg);
} else {
voice->_playback->progress = anim::value(0., prg);
}
voice->_playback->position = state.position;
voice->_playback->progressAnimation.start();
}
voice->_lastDurationMs = static_cast<int>((state.length * 1000LL) / state.frequency); // Bad :(
}
statusSize = -1 - (state.position / state.frequency);
realDuration = (state.length / state.frequency);
showPause = Media::Player::ShowPauseIcon(state.state);
} else {
if (auto voice = Get<HistoryDocumentVoice>()) {
voice->checkPlaybackFinished();
}
}
if (!showPause && (state.id == AudioMsgId(_data, _parent->data()->fullId(), state.id.externalPlayId()))) {
showPause = Media::Player::instance()->isSeeking(AudioMsgId::Type::Voice);
}
} else if (_data->isAudioFile()) {
const auto state = Media::Player::instance()->getState(AudioMsgId::Type::Song);
if (state.id == AudioMsgId(_data, _parent->data()->fullId(), state.id.externalPlayId())
&& !Media::Player::IsStoppedOrStopping(state.state)) {
statusSize = -1 - (state.position / state.frequency);
realDuration = (state.length / state.frequency);
showPause = Media::Player::ShowPauseIcon(state.state);
} else {
}
if (!showPause && (state.id == AudioMsgId(_data, _parent->data()->fullId(), state.id.externalPlayId()))) {
showPause = Media::Player::instance()->isSeeking(AudioMsgId::Type::Song);
}
}
if (statusSize != _statusSize) {
setStatusSize(statusSize, realDuration);
}
return showPause;
}
QMargins HistoryDocument::bubbleMargins() const {
return Get<HistoryDocumentThumbed>() ? QMargins(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top(), st::msgFileThumbPadding.left(), st::msgFileThumbPadding.bottom()) : st::msgPadding;
}
bool HistoryDocument::hideForwardedFrom() const {
return _data->isSong();
}
bool HistoryDocument::voiceProgressAnimationCallback(crl::time now) {
if (anim::Disabled()) {
now += (2 * kAudioVoiceMsgUpdateView);
}
if (const auto voice = Get<HistoryDocumentVoice>()) {
if (voice->_playback) {
const auto dt = (now - voice->_playback->progressAnimation.started())
/ float64(2 * kAudioVoiceMsgUpdateView);
if (dt >= 1.) {
voice->_playback->progressAnimation.stop();
voice->_playback->progress.finish();
} else {
voice->_playback->progress.update(qMin(dt, 1.), anim::linear);
}
history()->owner().requestViewRepaint(_parent);
return (dt < 1.);
}
}
return false;
}
void HistoryDocument::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) {
if (auto voice = Get<HistoryDocumentVoice>()) {
if (pressed && p == voice->_seekl && !voice->seeking()) {
voice->startSeeking();
} else if (!pressed && voice->seeking()) {
const auto type = AudioMsgId::Type::Voice;
const auto state = Media::Player::instance()->getState(type);
if (state.id == AudioMsgId(_data, _parent->data()->fullId(), state.id.externalPlayId()) && state.length) {
const auto currentProgress = voice->seekingCurrent();
Media::Player::instance()->finishSeeking(
AudioMsgId::Type::Voice,
currentProgress);
voice->ensurePlayback(this);
voice->_playback->position = 0;
voice->_playback->progress = anim::value(currentProgress, currentProgress);
}
voice->stopSeeking();
}
}
HistoryFileMedia::clickHandlerPressedChanged(p, pressed);
}
void HistoryDocument::refreshParentId(not_null<HistoryItem*> realParent) {
HistoryFileMedia::refreshParentId(realParent);
const auto fullId = realParent->fullId();
if (auto thumbed = Get<HistoryDocumentThumbed>()) {
if (thumbed->_linksavel) {
thumbed->_linksavel->setMessageId(fullId);
thumbed->_linkcancell->setMessageId(fullId);
}
}
if (auto voice = Get<HistoryDocumentVoice>()) {
if (voice->_seekl) {
voice->_seekl->setMessageId(fullId);
}
}
}
void HistoryDocument::parentTextUpdated() {
auto caption = (_parent->media() == this)
? createCaption(_parent->data())
: Ui::Text::String();
if (!caption.isEmpty()) {
AddComponents(HistoryDocumentCaptioned::Bit());
auto captioned = Get<HistoryDocumentCaptioned>();
captioned->_caption = std::move(caption);
} else {
RemoveComponents(HistoryDocumentCaptioned::Bit());
}
history()->owner().requestViewResize(_parent);
}
TextWithEntities HistoryDocument::getCaption() const {
if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
return captioned->_caption.toTextWithEntities();
}
return TextWithEntities();
}