Highlight timestamps in video captions.

This commit is contained in:
John Preston 2019-12-25 18:20:02 +03:00
parent e9620af6fb
commit 3e3e1d628c
10 changed files with 210 additions and 18 deletions

View File

@ -583,27 +583,36 @@ void Application::checkStartUrl() {
}
bool Application::openLocalUrl(const QString &url, QVariant context) {
auto urlTrimmed = url.trimmed();
if (urlTrimmed.size() > 8192) urlTrimmed = urlTrimmed.mid(0, 8192);
return openCustomUrl("tg://", LocalUrlHandlers(), url, context);
}
const auto protocol = qstr("tg://");
bool Application::openInternalUrl(const QString &url, QVariant context) {
return openCustomUrl("internal:", InternalUrlHandlers(), url, context);
}
bool Application::openCustomUrl(
const QString &protocol,
const std::vector<LocalUrlHandler> &handlers,
const QString &url,
const QVariant &context) {
const auto urlTrimmed = url.trimmed();
if (!urlTrimmed.startsWith(protocol, Qt::CaseInsensitive) || locked()) {
return false;
}
auto command = urlTrimmed.midRef(protocol.size());
const auto command = urlTrimmed.midRef(protocol.size(), 8192);
const auto session = activeAccount().sessionExists()
? &activeAccount().session()
: nullptr;
using namespace qthelp;
const auto options = RegExOption::CaseInsensitive;
for (const auto &[expression, handler] : LocalUrlHandlers()) {
for (const auto &[expression, handler] : handlers) {
const auto match = regex_match(expression, command, options);
if (match) {
return handler(session, match, context);
}
}
return false;
}
void Application::lockByPasscode() {

View File

@ -178,6 +178,7 @@ public:
QString createInternalLinkFull(const QString &query) const;
void checkStartUrl();
bool openLocalUrl(const QString &url, QVariant context);
bool openInternalUrl(const QString &url, QVariant context);
void forceLogOut(const TextWithEntities &explanation);
void checkLocalTime();
@ -241,6 +242,12 @@ private:
void clearPasscodeLock();
bool openCustomUrl(
const QString &protocol,
const std::vector<LocalUrlHandler> &handlers,
const QString &url,
const QVariant &context);
static Application *Instance;
struct InstanceSetter {
InstanceSetter(not_null<Application*> instance) {

View File

@ -43,7 +43,8 @@ void HiddenUrlClickHandler::Open(QString url, QVariant context) {
const auto open = [=] {
UrlClickHandler::Open(url, context);
};
if (url.startsWith(qstr("tg://"), Qt::CaseInsensitive)) {
if (url.startsWith(qstr("tg://"), Qt::CaseInsensitive)
|| url.startsWith(qstr("internal:"), Qt::CaseInsensitive)) {
open();
} else {
const auto parsedUrl = QUrl::fromUserInput(url);
@ -55,7 +56,9 @@ void HiddenUrlClickHandler::Open(QString url, QVariant context) {
: url;
Ui::show(
Box<ConfirmBox>(
tr::lng_open_this_link(tr::now) + qsl("\n\n") + displayUrl,
(tr::lng_open_this_link(tr::now)
+ qsl("\n\n")
+ displayUrl),
tr::lng_open_link(tr::now),
[=] { Ui::hideLayer(); open(); }),
Ui::LayerOption::KeepOther);

View File

@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "passport/passport_form_controller.h"
#include "window/window_session_controller.h"
#include "data/data_session.h"
#include "data/data_document.h"
#include "data/data_cloud_themes.h"
#include "data/data_channel.h"
#include "mainwindow.h"
@ -362,6 +363,38 @@ bool HandleUnknown(
return true;
}
bool OpenMediaTimestamp(
Main::Session *session,
const Match &match,
const QVariant &context) {
if (!session) {
return false;
}
const auto time = match->captured(2).toInt();
if (time < 0) {
return false;
}
const auto base = match->captured(1);
if (base.startsWith(qstr("doc"))) {
const auto parts = base.mid(3).split('_');
const auto documentId = parts.value(0).toULongLong();
const auto itemId = FullMsgId(
parts.value(1).toInt(),
parts.value(2).toInt());
const auto document = session->data().document(documentId);
session->settings().setMediaLastPlaybackPosition(
documentId,
time * crl::time(1000));
if (!document->isNull()) {
Core::App().showDocument(
document,
session->data().message(itemId));
}
return true;
}
return false;
}
} // namespace
const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
@ -421,7 +454,17 @@ const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
{
qsl("^([^\\?]+)(\\?|#|$)"),
HandleUnknown
}
},
};
return Result;
}
const std::vector<LocalUrlHandler> &InternalUrlHandlers() {
static auto Result = std::vector<LocalUrlHandler>{
{
qsl("^media_timestamp/?\\?base=([a-zA-Z0-9\\.\\_\\-]+)&t=(\\d+)(&|$)"),
OpenMediaTimestamp
},
};
return Result;
}

View File

@ -26,6 +26,7 @@ struct LocalUrlHandler {
};
[[nodiscard]] const std::vector<LocalUrlHandler> &LocalUrlHandlers();
[[nodiscard]] const std::vector<LocalUrlHandler> &InternalUrlHandlers();
[[nodiscard]] QString TryConvertUrlToLocal(QString url);

View File

@ -126,6 +126,9 @@ bool UiIntegration::handleUrlClick(
} else if (local.startsWith(qstr("tg://"), Qt::CaseInsensitive)) {
Core::App().openLocalUrl(local, context);
return true;
} else if (local.startsWith(qstr("internal:"), Qt::CaseInsensitive)) {
Core::App().openInternalUrl(local, context);
return true;
}
return false;

View File

@ -76,7 +76,7 @@ Gif::Gif(
setStatusSize(FileStatusSizeReady);
_caption = createCaption(realParent);
refreshCaption();
_data->loadThumbnail(realParent->fullId());
}
@ -1208,10 +1208,29 @@ bool Gif::isReadyForOpen() const {
}
void Gif::parentTextUpdated() {
_caption = (_parent->media() == this)
? createCaption(_parent->data())
: Ui::Text::String();
history()->owner().requestViewResize(_parent);
if (_parent->media() == this) {
refreshCaption();
history()->owner().requestViewResize(_parent);
}
}
void Gif::refreshParentId(not_null<HistoryItem*> realParent) {
if (_parent->media() == this) {
refreshCaption();
}
}
void Gif::refreshCaption() {
const auto timestampLinksDuration = _data->isVideoFile()
? _data->getDuration()
: 0;
const auto timestampLinkBase = timestampLinksDuration
? DocumentTimestampLinkBase(_data, _realParent->fullId())
: QString();
_caption = createCaption(
_parent->data(),
timestampLinksDuration,
timestampLinkBase);
}
int Gif::additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply, const HistoryMessageForwarded *forwarded) const {

View File

@ -104,6 +104,8 @@ public:
stopAnimation();
}
void refreshParentId(not_null<HistoryItem*> realParent) override;
private:
struct Streamed;
@ -111,6 +113,8 @@ private:
bool dataFinished() const override;
bool dataLoaded() const override;
void refreshCaption();
[[nodiscard]] bool autoplayEnabled() const;
void playAnimation(bool autoplay) override;

View File

@ -11,10 +11,96 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_element.h"
#include "history/view/history_view_cursor_state.h"
#include "storage/storage_shared_media.h"
#include "data/data_document.h"
#include "ui/text_options.h"
#include "styles/style_history.h"
namespace HistoryView {
namespace {
[[nodiscard]] TimeId TimeFromMatch(
const QStringRef &hours,
const QStringRef &minutes1,
const QStringRef &minutes2,
const QStringRef &seconds) {
auto ok1 = true;
auto ok2 = true;
auto ok3 = true;
const auto result = (hours.isEmpty() ? 0 : hours.toInt(&ok1)) * 3600
+ (minutes1 + minutes2).toInt(&ok2) * 60
+ seconds.toInt(&ok3);
return (ok1 && ok2 && ok3) ? result : -1;
}
[[nodiscard]] TextWithEntities AddTimestampLinks(
TextWithEntities text,
TimeId duration,
const QString &base) {
static const auto expression = QRegularExpression(
"(?<![^\\s])(?:(?:(\\d{1,2}):)?(\\d))?(\\d):(\\d\\d)(?![^\\s])");
const auto &string = text.text;
auto offset = 0;
while (true) {
const auto m = expression.match(string, offset);
if (!m.hasMatch()) {
break;
}
const auto from = m.capturedStart();
const auto till = from + m.capturedLength();
offset = till;
const auto time = TimeFromMatch(
m.capturedRef(1),
m.capturedRef(2),
m.capturedRef(3),
m.capturedRef(4));
if (time < 0 || time > duration) {
continue;
}
auto &entities = text.entities;
const auto i = ranges::lower_bound(
entities,
from,
std::less<>(),
&EntityInText::offset);
if (i != entities.end() && i->offset() < till) {
continue;
}
const auto intersects = [&](const EntityInText &entity) {
return entity.offset() + entity.length() > from;
};
auto j = std::make_reverse_iterator(i);
const auto e = std::make_reverse_iterator(entities.begin());
if (std::find_if(j, e, intersects) != e) {
continue;
}
entities.insert(
i,
EntityInText(
EntityType::CustomUrl,
from,
till - from,
("internal:media_timestamp?base="
+ base
+ "&t="
+ QString::number(time))));
}
return text;
}
} // namespace
QString DocumentTimestampLinkBase(
not_null<DocumentData*> document,
FullMsgId context) {
return QString(
"doc%1_%2_%3"
).arg(document->id).arg(context.channel).arg(context.msg);
}
Storage::SharedMediaTypesMask Media::sharedMediaTypes() const {
return {};
@ -32,7 +118,12 @@ QSize Media::countCurrentSize(int newWidth) {
return QSize(qMin(newWidth, maxWidth()), minHeight());
}
Ui::Text::String Media::createCaption(not_null<HistoryItem*> item) const {
Ui::Text::String Media::createCaption(
not_null<HistoryItem*> item,
TimeId timestampLinksDuration,
const QString &timestampLinkBase) const {
Expects(timestampLinksDuration >= 0);
if (item->emptyText()) {
return {};
}
@ -42,7 +133,12 @@ Ui::Text::String Media::createCaption(not_null<HistoryItem*> item) const {
auto result = Ui::Text::String(minResizeWidth);
result.setMarkedText(
st::messageTextStyle,
item->originalText(),
(timestampLinksDuration
? AddTimestampLinks(
item->originalText(),
timestampLinksDuration,
timestampLinkBase)
: item->originalText()),
Ui::ItemTextOptions(item));
if (const auto width = _parent->skipBlockWidth()) {
result.updateSkipBlock(width, _parent->skipBlockHeight());

View File

@ -40,6 +40,10 @@ enum class MediaInBubbleState {
Bottom,
};
[[nodiscard]] QString DocumentTimestampLinkBase(
not_null<DocumentData*> document,
FullMsgId context);
class Media : public Object {
public:
Media(not_null<Element*> parent) : _parent(parent) {
@ -237,8 +241,11 @@ public:
virtual ~Media() = default;
protected:
QSize countCurrentSize(int newWidth) override;
Ui::Text::String createCaption(not_null<HistoryItem*> item) const;
[[nodiscard]] QSize countCurrentSize(int newWidth) override;
[[nodiscard]] Ui::Text::String createCaption(
not_null<HistoryItem*> item,
TimeId timestampLinksDuration = 0,
const QString &timestampLinkBase = QString()) const;
virtual void playAnimation(bool autoplay) {
}