/* 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 { using TextState = HistoryView::TextState; } // namespace HistoryDocument::HistoryDocument( not_null parent, not_null document) : HistoryFileMedia(parent, parent->data()) , _data(document) { const auto item = parent->data(); auto caption = createCaption(item); createComponents(!caption.isEmpty()); if (auto named = Get()) { fillNamedFromData(named); } setDocumentLinks(_data, item); setStatusSize(FileStatusSizeReady); if (auto captioned = Get()) { 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 (auto thumbed = Get()) { thumbed->_linksavel = std::make_shared( _data, _parent->data()->fullId()); thumbed->_linkcancell = std::make_shared( _data, _parent->data()->fullId()); } if (auto voice = Get()) { voice->_seekl = std::make_shared( _data, _parent->data()->fullId()); } } void HistoryDocument::fillNamedFromData(HistoryDocumentNamed *named) { auto nameString = named->_name = _data->composeNameString(); named->_namew = st::semiboldFont->width(nameString); } QSize HistoryDocument::countOptimalSize() { const auto item = _parent->data(); auto captioned = Get(); 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(); 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()) { 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() || item->Has() || _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(); if (!captioned) { return HistoryFileMedia::countCurrentSize(newWidth); } accumulate_min(newWidth, maxWidth()); auto newHeight = 0; if (Get()) { 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; _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()); } } bool showPause = updateStatusText(); bool radial = isRadialAnimation(ms); auto topMinus = isBubbleTop() ? 0 : st::msgFileTopMinus; int nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0, bottom = 0; if (auto thumbed = Get()) { 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 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 : 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); } if (radial) { 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); } auto icon = [&] { if (_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); } auto namewidth = width() - nameleft - nameright; auto statuswidth = namewidth; auto voiceStatusOverride = QString(); if (const auto voice = Get()) { 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->a_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()) { 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()) { 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); } } 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()) { 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 : 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; QRect inner(rtlrect(st::msgFilePadding.left(), st::msgFilePadding.top() - topMinus, st::msgFileSize, st::msgFileSize, width())); if ((_data->loading() || _data->uploading()) && inner.contains(point)) { result.link = _cancell; return result; } } if (const auto voice = Get()) { 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()) { 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() && !_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()) { if (voice->seeking()) { auto nameleft = 0, nameright = 0; if (auto thumbed = Get()) { 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()) { return captioned->_caption.adjustSelection(selection, type); } return selection; } uint16 HistoryDocument::fullSelectionLength() const { if (const auto captioned = Get()) { return captioned->_caption.length(); } return 0; } bool HistoryDocument::hasTextForCopy() const { return Has(); } TextWithEntities HistoryDocument::selectedText(TextSelection selection) const { if (const auto captioned = Get()) { const auto &caption = captioned->_caption; return caption.originalTextWithEntities(selection, ExpandLinksAll); } return TextWithEntities(); } 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()) { if (_statusSize == FileStatusSizeReady) { thumbed->_link = lang(lng_media_download).toUpper(); } else if (_statusSize == FileStatusSizeLoaded) { thumbed->_link = lang(lng_media_open_with).toUpper(); } else if (_statusSize == FileStatusSizeFailed) { thumbed->_link = lang(lng_media_download).toUpper(); } else if (_statusSize >= 0) { thumbed->_link = lang(lng_media_cancel).toUpper(); } else { thumbed->_link = lang(lng_media_open_with).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()) { 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->a_progress.start(prg); } else { voice->_playback->a_progress = anim::value(0., prg); } voice->_playback->_position = state.position; voice->_playback->_a_progress.start(); } voice->_lastDurationMs = static_cast((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()) { 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() ? QMargins(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top(), st::msgFileThumbPadding.left(), st::msgFileThumbPadding.bottom()) : st::msgPadding; } bool HistoryDocument::hideForwardedFrom() const { return _data->isSong(); } void HistoryDocument::step_voiceProgress(float64 ms, bool timer) { if (anim::Disabled()) { ms += (2 * AudioVoiceMsgUpdateView); } if (auto voice = Get()) { if (voice->_playback) { float64 dt = ms / (2 * AudioVoiceMsgUpdateView); if (dt >= 1) { voice->_playback->_a_progress.stop(); voice->_playback->a_progress.finish(); } else { voice->_playback->a_progress.update(qMin(dt, 1.), anim::linear); } if (timer) { history()->owner().requestViewRepaint(_parent); } } } } void HistoryDocument::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) { if (auto voice = Get()) { 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->a_progress = anim::value(currentProgress, currentProgress); } voice->stopSeeking(); } } HistoryFileMedia::clickHandlerPressedChanged(p, pressed); } void HistoryDocument::refreshParentId(not_null realParent) { HistoryFileMedia::refreshParentId(realParent); const auto fullId = realParent->fullId(); if (auto thumbed = Get()) { if (thumbed->_linksavel) { thumbed->_linksavel->setMessageId(fullId); thumbed->_linkcancell->setMessageId(fullId); } } if (auto voice = Get()) { if (voice->_seekl) { voice->_seekl->setMessageId(fullId); } } } void HistoryDocument::parentTextUpdated() { auto caption = (_parent->media() == this) ? createCaption(_parent->data()) : Text(); if (!caption.isEmpty()) { AddComponents(HistoryDocumentCaptioned::Bit()); auto captioned = Get(); captioned->_caption = std::move(caption); } else { RemoveComponents(HistoryDocumentCaptioned::Bit()); } history()->owner().requestViewResize(_parent); } TextWithEntities HistoryDocument::getCaption() const { if (const auto captioned = Get()) { return captioned->_caption.originalTextWithEntities(); } return TextWithEntities(); }