From 3e2485678d8848b50e83eb8199191544372899e0 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 5 Apr 2016 01:09:46 +0400 Subject: [PATCH] Moved inline bot result structs from structs.cpp/h module. Moved inline bot result layouts from layout.cpp/h module. Over status prepared for inline bot file result icon. Dependencies reduced, inline bots code moved to InlineBots namespace. Build in Xcode and QtCreator broken. --- Telegram/Resources/style.txt | 2 + Telegram/SourceFiles/app.cpp | 11 - Telegram/SourceFiles/app.h | 4 - Telegram/SourceFiles/basic_types.h | 50 + Telegram/SourceFiles/dropdown.cpp | 278 ++--- Telegram/SourceFiles/dropdown.h | 75 +- Telegram/SourceFiles/facades.cpp | 4 +- Telegram/SourceFiles/facades.h | 12 +- Telegram/SourceFiles/gui/flattextarea.cpp | 8 +- Telegram/SourceFiles/gui/flattextarea.h | 2 + Telegram/SourceFiles/gui/text.h | 8 + Telegram/SourceFiles/historywidget.cpp | 41 +- Telegram/SourceFiles/historywidget.h | 17 +- .../inline_bot_layout_internal.cpp | 1082 +++++++++++++++++ .../inline_bots/inline_bot_layout_internal.h | 325 +++++ .../inline_bots/inline_bot_layout_item.cpp | 96 ++ .../inline_bots/inline_bot_layout_item.h | 103 ++ .../inline_bots/inline_bot_result.cpp | 320 +++++ .../inline_bots/inline_bot_result.h | 115 ++ .../inline_bots/inline_bot_send_data.cpp | 146 +++ .../inline_bots/inline_bot_send_data.h | 233 ++++ Telegram/SourceFiles/layout.cpp | 1048 +--------------- Telegram/SourceFiles/layout.h | 409 +------ Telegram/SourceFiles/mainwidget.cpp | 8 +- Telegram/SourceFiles/mainwidget.h | 12 +- Telegram/SourceFiles/overviewwidget.cpp | 20 +- Telegram/SourceFiles/overviewwidget.h | 10 +- Telegram/SourceFiles/structs.cpp | 213 ---- Telegram/SourceFiles/structs.h | 269 ---- Telegram/Telegram.vcxproj | 8 + Telegram/Telegram.vcxproj.filters | 27 + 31 files changed, 2789 insertions(+), 2167 deletions(-) create mode 100644 Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp create mode 100644 Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h create mode 100644 Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp create mode 100644 Telegram/SourceFiles/inline_bots/inline_bot_layout_item.h create mode 100644 Telegram/SourceFiles/inline_bots/inline_bot_result.cpp create mode 100644 Telegram/SourceFiles/inline_bots/inline_bot_result.h create mode 100644 Telegram/SourceFiles/inline_bots/inline_bot_send_data.cpp create mode 100644 Telegram/SourceFiles/inline_bots/inline_bot_send_data.h diff --git a/Telegram/Resources/style.txt b/Telegram/Resources/style.txt index bc0ef6cb85..f1502116b2 100644 --- a/Telegram/Resources/style.txt +++ b/Telegram/Resources/style.txt @@ -2486,6 +2486,8 @@ inlineDescriptionFg: #8a8a8a; inlineRowMargin: 6px; inlineRowBorder: linksBorder; inlineRowBorderFg: linksBorderFg; +inlineRowFileNameTop: 2px; +inlineRowFileDescriptionTop: 23px; inlineResultsMinWidth: 64px; inlineDurationMargin: 3px; diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index 0ff2880f83..901fc71c88 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -105,9 +105,6 @@ namespace { typedef QHash LastPhotosMap; LastPhotosMap lastPhotosMap; - typedef QMap InlineResultLoaders; - InlineResultLoaders inlineResultLoaders; - style::color _msgServiceBg; style::color _msgServiceSelectBg; style::color _historyScrollBarColor; @@ -2376,14 +2373,6 @@ namespace { if (changeInMin) App::main()->updateMutedIn(changeInMin); } - void regInlineResultLoader(FileLoader *loader, InlineResult *result) { - ::inlineResultLoaders.insert(loader, result); - } - - void unregInlineResultLoader(FileLoader *loader) { - ::inlineResultLoaders.remove(loader); - } - void setProxySettings(QNetworkAccessManager &manager) { #ifndef TDESKTOP_DISABLE_NETWORK_PROXY manager.setProxy(getHttpProxySettings()); diff --git a/Telegram/SourceFiles/app.h b/Telegram/SourceFiles/app.h index 1c2ffd65eb..3b53eb0611 100644 --- a/Telegram/SourceFiles/app.h +++ b/Telegram/SourceFiles/app.h @@ -259,10 +259,6 @@ namespace App { void unregMuted(PeerData *peer); void updateMuted(); - void regInlineResultLoader(FileLoader *loader, InlineResult *result); - void unregInlineResultLoader(FileLoader *loader); - InlineResult *inlineResultFromLoader(FileLoader *loader); - void setProxySettings(QNetworkAccessManager &manager); #ifndef TDESKTOP_DISABLE_NETWORK_PROXY QNetworkProxy getHttpProxySettings(); diff --git a/Telegram/SourceFiles/basic_types.h b/Telegram/SourceFiles/basic_types.h index 67c39475c3..3d41159e04 100644 --- a/Telegram/SourceFiles/basic_types.h +++ b/Telegram/SourceFiles/basic_types.h @@ -782,6 +782,56 @@ inline QSharedPointer MakeShared(Args&&... args) { return QSharedPointer(new T(std_::forward(args)...)); } +// This pointer is used for global non-POD variables that are allocated +// on demand by createIfNull(lambda) and are never automatically freed. +template +class NeverFreedPointer { +public: + explicit NeverFreedPointer() { + } + NeverFreedPointer(const NeverFreedPointer &other) = delete; + NeverFreedPointer &operator=(const NeverFreedPointer &other) = delete; + + template + void createIfNull(U creator) { + if (isNull()) { + reset(creator()); + } + } + + T *data() const { + return _p; + } + T *release() { + return getPointerAndReset(_p); + } + void reset(T *p = nullptr) { + delete _p; + _p = p; + } + bool isNull() const { + return data() == nullptr; + } + + void clear() { + reset(); + } + T *operator->() const { + return data(); + } + T &operator*() const { + t_assert(!isNull()); + return *data(); + } + explicit operator bool() const { + return !isNull(); + } + +private: + T *_p = nullptr; + +}; + template inline void destroyImplementation(I *&ptr) { if (ptr) { diff --git a/Telegram/SourceFiles/dropdown.cpp b/Telegram/SourceFiles/dropdown.cpp index 78fe10ba3b..3133d18497 100644 --- a/Telegram/SourceFiles/dropdown.cpp +++ b/Telegram/SourceFiles/dropdown.cpp @@ -19,19 +19,18 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" - #include "dropdown.h" -#include "historywidget.h" - -#include "localstorage.h" -#include "lang.h" - -#include "window.h" -#include "apiwrap.h" -#include "mainwidget.h" #include "boxes/confirmbox.h" #include "boxes/stickersetbox.h" +#include "inline_bots/inline_bot_result.h" +#include "inline_bots/inline_bot_layout_item.h" +#include "historywidget.h" +#include "localstorage.h" +#include "lang.h" +#include "window.h" +#include "apiwrap.h" +#include "mainwidget.h" Dropdown::Dropdown(QWidget *parent, const style::dropdown &st) : TWidget(parent) , _ignore(false) @@ -457,6 +456,8 @@ void DragArea::step_appearance(float64 ms, bool timer) { if (timer) update(); } +namespace internal { + EmojiColorPicker::EmojiColorPicker() : TWidget() , _ignoreShow(false) , _a_selected(animation(this, &EmojiColorPicker::step_selected)) @@ -1272,6 +1273,12 @@ int32 StickerPanInner::countHeight(bool plain) { return qMax(minLastH, result) + st::stickerPanPadding; } +StickerPanInner::~StickerPanInner() { + clearInlineRows(true); + deleteUnusedGifLayouts(); + deleteUnusedInlineLayouts(); +} + QRect StickerPanInner::stickerRect(int tab, int sel) { int x = 0, y = 0; for (int i = 0; i < _sets.size(); ++i) { @@ -1311,7 +1318,7 @@ void StickerPanInner::paintInlineItems(Painter &p, const QRect &r) { p.drawText(QRect(0, 0, width(), (height() / 3) * 2 + st::normalFont->height), lang(lng_inline_bot_no_results), style::al_center); return; } - InlinePaintContext context(getms(), false, Ui::isLayerShown() || Ui::isMediaViewShown() || _previewShown, false); + InlineBots::Layout::PaintContext context(getms(), false, Ui::isLayerShown() || Ui::isMediaViewShown() || _previewShown, false); int32 top = st::emojiPanHeader; int32 fromx = rtl() ? (width() - r.x() - r.width()) : r.x(), tox = rtl() ? (width() - r.x()) : (r.x() + r.width()); @@ -1324,7 +1331,7 @@ void StickerPanInner::paintInlineItems(Painter &p, const QRect &r) { for (int32 col = 0, cols = inlineRow.items.size(); col < cols; ++col) { if (left >= tox) break; - const LayoutInlineItem *item = inlineRow.items.at(col); + const InlineItem *item = inlineRow.items.at(col); int32 w = item->width(); if (left + w > fromx) { p.translate(left, top); @@ -1441,7 +1448,7 @@ void StickerPanInner::mouseReleaseEvent(QMouseEvent *e) { if (_selected < 0 || _selected != pressed || (_showingInlineItems && !activated)) return; if (_showingInlineItems) { - if (!dynamic_cast(activated.data())) { + if (!dynamic_cast(activated.data())) { App::activateClickHandler(activated, e->button()); return; } @@ -1450,10 +1457,10 @@ void StickerPanInner::mouseReleaseEvent(QMouseEvent *e) { return; } - LayoutInlineItem *item = _inlineRows.at(row).items.at(col); + InlineItem *item = _inlineRows.at(row).items.at(col); PhotoData *photo = item->getPhoto(); DocumentData *document = item->getDocument(); - InlineResult *inlineResult = item->getInlineResult(); + InlineResult *inlineResult = item->getResult(); using Type = InlineResult::Type; auto getShownPhoto = [photo, inlineResult]() -> PhotoData* { if (photo) { @@ -1495,7 +1502,7 @@ void StickerPanInner::mouseReleaseEvent(QMouseEvent *e) { shownPhoto->medium->loadEvenCancelled(); } } else if (DocumentData *shownDocument = getShownDocument()) { - if (inlineResult->type == Type::Gif) { + if (!inlineResult || inlineResult->type == Type::Gif) { if (shownDocument->loaded()) { sendInlineItem(); } else if (shownDocument->loading()) { @@ -1640,17 +1647,28 @@ void StickerPanInner::clearSelection(bool fast) { void StickerPanInner::hideFinish(bool completely) { if (completely) { + auto itemForget = [](const InlineItem *item) { + if (DocumentData *document = item->getDocument()) { + document->forget(); + } + if (PhotoData *photo = item->getPhoto()) { + photo->forget(); + } + if (InlineResult *result = item->getResult()) { + if (DocumentData *document = result->document) { + document->forget(); + } + if (PhotoData *photo = result->photo) { + photo->forget(); + } + } + }; clearInlineRows(false); - for (GifLayouts::const_iterator i = _gifLayouts.cbegin(), e = _gifLayouts.cend(); i != e; ++i) { - i.value()->getDocument()->forget(); + for_const (InlineItem *item, _gifLayouts) { + itemForget(item); } - for (InlineLayouts::const_iterator i = _inlineLayouts.cbegin(), e = _inlineLayouts.cend(); i != e; ++i) { - if (i.value()->getInlineResult()->document) { - i.value()->getInlineResult()->document->forget(); - } - if (i.value()->getInlineResult()->photo) { - i.value()->getInlineResult()->photo->forget(); - } + for_const (InlineItem *item, _inlineLayouts) { + itemForget(item); } } if (_setGifCommand && _showingSavedGifs) { @@ -1686,7 +1704,7 @@ void StickerPanInner::refreshStickers() { } bool StickerPanInner::inlineRowsAddItem(DocumentData *savedGif, InlineResult *result, InlineRow &row, int32 &sumWidth) { - LayoutInlineItem *layout = nullptr; + InlineItem *layout = nullptr; if (savedGif) { layout = layoutPrepareSavedGif(savedGif, (_inlineRows.size() * MatrixRowShift) + row.items.size()); } else if (result) { @@ -1711,12 +1729,12 @@ bool StickerPanInner::inlineRowsAddItem(DocumentData *savedGif, InlineResult *re bool StickerPanInner::inlineRowFinalize(InlineRow &row, int32 &sumWidth, bool force) { if (row.items.isEmpty()) return false; - bool full = (row.items.size() >= SavedGifsMaxPerRow); + bool full = (row.items.size() >= InlineItemsMaxPerRow); bool big = (sumWidth >= st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft); if (full || big || force) { _inlineRows.push_back(layoutInlineRow(row, (full || big) ? sumWidth : 0)); row = InlineRow(); - row.items.reserve(SavedGifsMaxPerRow); + row.items.reserve(InlineItemsMaxPerRow); sumWidth = 0; return true; } @@ -1735,7 +1753,7 @@ void StickerPanInner::refreshSavedGifs() { } else { _inlineRows.reserve(saved.size()); InlineRow row; - row.items.reserve(SavedGifsMaxPerRow); + row.items.reserve(InlineItemsMaxPerRow); int32 sumWidth = 0; for (SavedGifs::const_iterator i = saved.cbegin(), e = saved.cend(); i != e; ++i) { inlineRowsAddItem(*i, 0, row, sumWidth); @@ -1779,40 +1797,33 @@ void StickerPanInner::clearInlineRows(bool resultsDeleted) { _inlineRows.clear(); } -LayoutInlineGif *StickerPanInner::layoutPrepareSavedGif(DocumentData *doc, int32 position) { - GifLayouts::const_iterator i = _gifLayouts.constFind(doc); +InlineItem *StickerPanInner::layoutPrepareSavedGif(DocumentData *doc, int32 position) { + auto i = _gifLayouts.constFind(doc); if (i == _gifLayouts.cend()) { - i = _gifLayouts.insert(doc, new LayoutInlineGif(doc, true)); - i.value()->initDimensions(); + if (auto layout = InlineItem::createLayoutGif(doc)) { + i = _gifLayouts.insert(doc, layout.release()); + i.value()->initDimensions(); + } else { + return nullptr; + } } - if (!i.value()->maxWidth()) return 0; + if (!i.value()->maxWidth()) return nullptr; i.value()->setPosition(position); return i.value(); } -LayoutInlineItem *StickerPanInner::layoutPrepareInlineResult(InlineResult *result, int32 position) { - InlineLayouts::const_iterator i = _inlineLayouts.constFind(result); +InlineItem *StickerPanInner::layoutPrepareInlineResult(InlineResult *result, int32 position) { + auto i = _inlineLayouts.constFind(result); if (i == _inlineLayouts.cend()) { - LayoutInlineItem *layout = nullptr; - using Type = InlineResult::Type; - switch (result->type) { - case Type::Photo: layout = new LayoutInlinePhoto(result); break; - case Type::Audio: - case Type::File: layout = new LayoutInlineFile(result); break; - case Type::Video: layout = new LayoutInlineVideo(result); break; - case Type::Sticker: layout = new LayoutInlineSticker(result); break; - case Type::Gif: layout = new LayoutInlineGif(result); break; - case Type::Article: - case Type::Contact: - case Type::Venue: layout = new LayoutInlineArticle(result, _inlineWithThumb); break; + if (auto layout = InlineItem::createLayout(result, _inlineWithThumb)) { + i = _inlineLayouts.insert(result, layout.release()); + i.value()->initDimensions(); + } else { + return nullptr; } - if (!layout) return nullptr; - - i = _inlineLayouts.insert(result, layout); - layout->initDimensions(); } - if (!i.value()->maxWidth()) return 0; + if (!i.value()->maxWidth()) return nullptr; i.value()->setPosition(position); return i.value(); @@ -1820,8 +1831,8 @@ LayoutInlineItem *StickerPanInner::layoutPrepareInlineResult(InlineResult *resul void StickerPanInner::deleteUnusedGifLayouts() { if (_inlineRows.isEmpty() || !_showingSavedGifs) { // delete all - for (GifLayouts::const_iterator i = _gifLayouts.cbegin(), e = _gifLayouts.cend(); i != e; ++i) { - delete i.value(); + for_const (const InlineItem *item, _gifLayouts) { + delete item; } _gifLayouts.clear(); } else { @@ -1838,8 +1849,8 @@ void StickerPanInner::deleteUnusedGifLayouts() { void StickerPanInner::deleteUnusedInlineLayouts() { if (_inlineRows.isEmpty() || _showingSavedGifs) { // delete all - for (InlineLayouts::const_iterator i = _inlineLayouts.cbegin(), e = _inlineLayouts.cend(); i != e; ++i) { - delete i.value(); + for_const (const InlineItem *item, _inlineLayouts) { + delete item; } _inlineLayouts.clear(); } else { @@ -1856,11 +1867,11 @@ void StickerPanInner::deleteUnusedInlineLayouts() { StickerPanInner::InlineRow &StickerPanInner::layoutInlineRow(InlineRow &row, int32 sumWidth) { int32 count = row.items.size(); - t_assert(count <= SavedGifsMaxPerRow); + t_assert(count <= InlineItemsMaxPerRow); // enumerate items in the order of growing maxWidth() // for that sort item indices by maxWidth() - int indices[SavedGifsMaxPerRow]; + int indices[InlineItemsMaxPerRow]; for (int i = 0; i < count; ++i) { indices[i] = i; } @@ -1972,7 +1983,7 @@ int32 StickerPanInner::refreshInlineRows(UserData *bot, const InlineResults &res if (count) { _inlineRows.reserve(count); InlineRow row; - row.items.reserve(SavedGifsMaxPerRow); + row.items.reserve(InlineItemsMaxPerRow); int32 sumWidth = 0; for (int32 i = from; i < count; ++i) { if (inlineRowsAddItem(0, results.at(i), row, sumWidth)) { @@ -1997,7 +2008,7 @@ int32 StickerPanInner::refreshInlineRows(UserData *bot, const InlineResults &res int32 StickerPanInner::validateExistingInlineRows(const InlineResults &results) { int32 count = results.size(), until = 0, untilrow = 0, untilcol = 0; for (; until < count;) { - if (untilrow >= _inlineRows.size() || _inlineRows.at(untilrow).items.at(untilcol)->getInlineResult() != results.at(until)) { + if (untilrow >= _inlineRows.size() || _inlineRows.at(untilrow).items.at(untilcol)->getResult() != results.at(until)) { break; } ++until; @@ -2044,20 +2055,8 @@ int32 StickerPanInner::validateExistingInlineRows(const InlineResults &results) if (_inlineRows.isEmpty()) { _inlineWithThumb = false; - auto hasThumbDisplay = [](InlineResult *inlineResult) -> bool { - if (!inlineResult->thumb->isNull()) { - return true; - } - if (inlineResult->type == InlineResult::Type::Contact) { - return true; - } - if (inlineResult->sendData->hasLocationCoords()) { - return true; - } - return false; - }; for (int32 i = until; i < count; ++i) { - if (hasThumbDisplay(results.at(i))) { + if (results.at(i)->hasThumbDisplay()) { _inlineWithThumb = true; break; } @@ -2066,7 +2065,7 @@ int32 StickerPanInner::validateExistingInlineRows(const InlineResults &results) return until; } -void StickerPanInner::ui_repaintInlineItem(const LayoutInlineItem *layout) { +void StickerPanInner::ui_repaintInlineItem(const InlineItem *layout) { uint64 ms = getms(); if (_lastScrolled + 100 <= ms) { update(); @@ -2075,7 +2074,7 @@ void StickerPanInner::ui_repaintInlineItem(const LayoutInlineItem *layout) { } } -bool StickerPanInner::ui_isInlineItemVisible(const LayoutInlineItem *layout) { +bool StickerPanInner::ui_isInlineItemVisible(const InlineItem *layout) { int32 position = layout->position(); if (!_showingInlineItems || position < 0) return false; @@ -2614,6 +2613,8 @@ void EmojiSwitchButton::paintEvent(QPaintEvent *e) { } } +} // namespace internal + EmojiPan::EmojiPan(QWidget *parent) : TWidget(parent) , _maxHeight(st::emojiPanMaxHeight) , _contentMaxHeight(st::emojiPanMaxHeight) @@ -2708,7 +2709,7 @@ EmojiPan::EmojiPan(QWidget *parent) : TWidget(parent) connect(&e_inner, SIGNAL(selected(EmojiPtr)), this, SIGNAL(emojiSelected(EmojiPtr))); connect(&s_inner, SIGNAL(selected(DocumentData*)), this, SIGNAL(stickerSelected(DocumentData*))); connect(&s_inner, SIGNAL(selected(PhotoData*)), this, SIGNAL(photoSelected(PhotoData*))); - connect(&s_inner, SIGNAL(selected(InlineResult*,UserData*)), this, SIGNAL(inlineResultSelected(InlineResult*,UserData*))); + connect(&s_inner, SIGNAL(selected(InlineBots::Result*,UserData*)), this, SIGNAL(inlineResultSelected(InlineBots::Result*,UserData*))); connect(&s_inner, SIGNAL(emptyInlineRows()), this, SLOT(onEmptyInlineRows())); @@ -2848,7 +2849,7 @@ void EmojiPan::paintEvent(QPaintEvent *e) { i += _iconsX.current() / int(st::rbEmoji.width); x -= _iconsX.current() % int(st::rbEmoji.width); for (int32 l = qMin(_icons.size(), i + 8 - skip); i < l; ++i) { - const StickerIcon &s(_icons.at(i)); + const internal::StickerIcon &s(_icons.at(i)); s.sticker->thumb->load(); QPixmap pix(s.sticker->thumb->pix(s.pixw, s.pixh)); @@ -3393,13 +3394,13 @@ void EmojiPan::stickersInstalled(uint64 setId) { showStart(); } -void EmojiPan::ui_repaintInlineItem(const LayoutInlineItem *layout) { +void EmojiPan::ui_repaintInlineItem(const InlineBots::Layout::ItemBase *layout) { if (_stickersShown && !isHidden()) { s_inner.ui_repaintInlineItem(layout); } } -bool EmojiPan::ui_isInlineItemVisible(const LayoutInlineItem *layout) { +bool EmojiPan::ui_isInlineItemVisible(const InlineBots::Layout::ItemBase *layout) { if (_stickersShown && !isHidden()) { return s_inner.ui_isInlineItemVisible(layout); } @@ -3414,9 +3415,9 @@ bool EmojiPan::ui_isInlineItemBeingChosen() { } void EmojiPan::notify_automaticLoadSettingsChangedGif() { - for (InlineCache::const_iterator i = _inlineCache.cbegin(), ei = _inlineCache.cend(); i != ei; ++i) { - for (InlineResults::const_iterator j = i.value()->results.cbegin(), ej = i.value()->results.cend(); j != ej; ++j) { - (*j)->automaticLoadSettingsChangedGif(); + for_const (const InlineCacheEntry *entry, _inlineCache) { + for_const (InlineBots::Result *l, entry->results) { + l->automaticLoadSettingsChangedGif(); } } } @@ -3475,7 +3476,7 @@ void EmojiPan::onTabChange() { e_inner.showEmojiPack(newTab); } -void EmojiPan::updatePanelsPositions(const QVector &panels, int32 st) { +void EmojiPan::updatePanelsPositions(const QVector &panels, int32 st) { for (int32 i = 0, l = panels.size(); i < l; ++i) { int32 y = panels.at(i)->wantedY() - st; if (y < 0) { @@ -3655,6 +3656,13 @@ bool EmojiPan::hideOnNoInlineResults() { return _inlineBot && _stickersShown && s_inner.inlineResultsShown() && (_shownFromInlineQuery || _inlineBot->username != cInlineGifBotUsername()); } +void EmojiPan::InlineCacheEntry::clearResults() { + for_const (const InlineBots::Result *result, results) { + delete result; + } + results.clear(); +} + void EmojiPan::inlineBotChanged() { if (!_inlineBot) return; @@ -3677,7 +3685,7 @@ void EmojiPan::inlineBotChanged() { Notify::inlineBotRequesting(false); } - +#include void EmojiPan::inlineResultsDone(const MTPmessages_BotResults &result) { _inlineRequestId = 0; Notify::inlineBotRequesting(false); @@ -3699,105 +3707,11 @@ void EmojiPan::inlineResultsDone(const MTPmessages_BotResults &result) { if (count) { it.value()->results.reserve(it.value()->results.size() + count); } - auto inlineResultTypes = InlineResult::getTypesMap(); - auto getInlineResultType = [&inlineResultTypes](const MTPBotInlineResult &inlineResult) -> InlineResult::Type { - QString type; - switch (inlineResult.type()) { - case mtpc_botInlineResult: type = qs(inlineResult.c_botInlineResult().vtype); break; - case mtpc_botInlineMediaResult: type = qs(inlineResult.c_botInlineMediaResult().vtype); break; - } - return inlineResultTypes.value(type, InlineResult::Type::Unknown); - }; for (int32 i = 0; i < count; ++i) { - InlineResult::Type type = getInlineResultType(v.at(i)); - if (type == InlineResult::Type::Unknown) continue; - - UniquePointer result = MakeUnique(queryId, type); - - const MTPBotInlineMessage *message = nullptr; - switch (v.at(i).type()) { - case mtpc_botInlineResult: { - const MTPDbotInlineResult &r(v.at(i).c_botInlineResult()); - result->id = qs(r.vid); - if (r.has_title()) result->title = qs(r.vtitle); - if (r.has_description()) result->description = qs(r.vdescription); - if (r.has_url()) result->url = qs(r.vurl); - if (r.has_thumb_url()) result->thumb_url = qs(r.vthumb_url); - if (r.has_content_type()) result->content_type = qs(r.vcontent_type); - if (r.has_content_url()) result->content_url = qs(r.vcontent_url); - if (r.has_w()) result->width = r.vw.v; - if (r.has_h()) result->height = r.vh.v; - if (r.has_duration()) result->duration = r.vduration.v; - if (!result->thumb_url.isEmpty() && (result->thumb_url.startsWith(qstr("http://"), Qt::CaseInsensitive) || result->thumb_url.startsWith(qstr("https://"), Qt::CaseInsensitive))) { - result->thumb = ImagePtr(result->thumb_url); - } - message = &r.vsend_message; - } break; - case mtpc_botInlineMediaResult: { - const MTPDbotInlineMediaResult &r(v.at(i).c_botInlineMediaResult()); - result->id = qs(r.vid); - if (r.has_title()) result->title = qs(r.vtitle); - if (r.has_description()) result->description = qs(r.vdescription); - if (r.has_photo()) result->photo = App::feedPhoto(r.vphoto); - if (r.has_document()) result->document = App::feedDocument(r.vdocument); - message = &r.vsend_message; - } break; + if (UniquePointer result = InlineBots::Result::create(queryId, v.at(i))) { + ++added; + it.value()->results.push_back(result.release()); } - bool badAttachment = (result->photo && !result->photo->access) || (result->document && !result->document->access); - - if (!message) { - continue; - } - switch (message->type()) { - case mtpc_botInlineMessageMediaAuto: { - const MTPDbotInlineMessageMediaAuto &r(message->c_botInlineMessageMediaAuto()); - if (result->type == InlineResult::Type::Photo) { - result->sendData.reset(new InlineResultSendPhoto(result->photo, result->content_url, qs(r.vcaption))); - } else { - result->sendData.reset(new InlineResultSendFile(result->document, result->content_url, qs(r.vcaption))); - } - } break; - - case mtpc_botInlineMessageText: { - const MTPDbotInlineMessageText &r(message->c_botInlineMessageText()); - EntitiesInText entities = r.has_entities() ? entitiesFromMTP(r.ventities.c_vector().v) : EntitiesInText(); - result->sendData.reset(new InlineResultSendText(qs(r.vmessage), entities, r.is_no_webpage())); - } break; - - case mtpc_botInlineMessageMediaGeo: { - const MTPDbotInlineMessageMediaGeo &r(message->c_botInlineMessageMediaGeo()); - if (r.vgeo.type() == mtpc_geoPoint) { - result->sendData.reset(new InlineResultSendGeo(r.vgeo.c_geoPoint())); - } else { - badAttachment = true; - } - } break; - - case mtpc_botInlineMessageMediaVenue: { - const MTPDbotInlineMessageMediaVenue &r(message->c_botInlineMessageMediaVenue()); - if (r.vgeo.type() == mtpc_geoPoint) { - result->sendData.reset(new InlineResultSendVenue(r.vgeo.c_geoPoint(), qs(r.vvenue_id), qs(r.vprovider), qs(r.vtitle), qs(r.vaddress))); - } else { - badAttachment = true; - } - } break; - - case mtpc_botInlineMessageMediaContact: { - const MTPDbotInlineMessageMediaContact &r(message->c_botInlineMessageMediaContact()); - result->sendData.reset(new InlineResultSendContact(qs(r.vfirst_name), qs(r.vlast_name), qs(r.vphone_number))); - } break; - - default: { - badAttachment = true; - } break; - } - - if (badAttachment || !result->sendData || !result->sendData->isValid()) { - continue; - } - - ++added; - it.value()->results.push_back(result.release()); } if (!added) { @@ -3885,7 +3799,7 @@ bool EmojiPan::refreshInlineRows(int32 *added) { _inlineNextOffset = i.value()->nextOffset; } if (clear) prepareShowHideCache(); - int32 result = s_inner.refreshInlineRows(_inlineBot, clear ? InlineResults() : i.value()->results, false); + int32 result = s_inner.refreshInlineRows(_inlineBot, clear ? internal::InlineResults() : i.value()->results, false); if (added) *added = result; return !clear; } diff --git a/Telegram/SourceFiles/dropdown.h b/Telegram/SourceFiles/dropdown.h index 86b1bf0a64..8eae0f42b7 100644 --- a/Telegram/SourceFiles/dropdown.h +++ b/Telegram/SourceFiles/dropdown.h @@ -157,8 +157,21 @@ private: }; -class EmojiPanel; -static const int EmojiColorsCount = 5; +namespace InlineBots { +namespace Layout { +class ItemBase; +} // namespace Layout +class Result; +} // namespace InlineBots + +namespace internal { + +static constexpr int InlineItemsMaxPerRow = 5; +static constexpr int EmojiColorsCount = 5; + +using InlineResult = InlineBots::Result; +using InlineResults = QList; +using InlineItem = InlineBots::Layout::ItemBase; class EmojiColorPicker : public TWidget { Q_OBJECT @@ -222,6 +235,7 @@ private: }; +class EmojiPanel; class EmojiPanInner : public TWidget { Q_OBJECT @@ -314,8 +328,6 @@ struct StickerIcon { int32 pixw, pixh; }; -static constexpr int SavedGifsMaxPerRow = 5; - class StickerPanInner : public TWidget { Q_OBJECT @@ -361,8 +373,8 @@ public: uint64 currentSet(int yOffset) const; - void ui_repaintInlineItem(const LayoutInlineItem *layout); - bool ui_isInlineItemVisible(const LayoutInlineItem *layout); + void ui_repaintInlineItem(const InlineItem *layout); + bool ui_isInlineItemVisible(const InlineItem *layout); bool ui_isInlineItemBeingChosen(); bool inlineResultsShown() const { @@ -370,11 +382,7 @@ public: } int32 countHeight(bool plain = false); - ~StickerPanInner() { - clearInlineRows(true); - deleteUnusedGifLayouts(); - deleteUnusedInlineLayouts(); - } + ~StickerPanInner(); public slots: @@ -387,7 +395,7 @@ signals: void selected(DocumentData *sticker); void selected(PhotoData *photo); - void selected(InlineResult *result, UserData *bot); + void selected(InlineBots::Result *result, UserData *bot); void removing(quint64 setId); @@ -441,7 +449,7 @@ private: QTimer _updateInlineItems; bool _inlineWithThumb; - typedef QVector InlineItems; + typedef QVector InlineItems; struct InlineRow { InlineRow() : height(0) { } @@ -452,13 +460,13 @@ private: InlineRows _inlineRows; void clearInlineRows(bool resultsDeleted); - typedef QMap GifLayouts; + using GifLayouts = QMap; GifLayouts _gifLayouts; - LayoutInlineGif *layoutPrepareSavedGif(DocumentData *doc, int32 position); + InlineItem *layoutPrepareSavedGif(DocumentData *doc, int32 position); - typedef QMap InlineLayouts; + using InlineLayouts = QMap; InlineLayouts _inlineLayouts; - LayoutInlineItem *layoutPrepareInlineResult(InlineResult *result, int32 position); + InlineItem *layoutPrepareInlineResult(InlineResult *result, int32 position); bool inlineRowsAddItem(DocumentData *savedGif, InlineResult *result, InlineRow &row, int32 &sumWidth); bool inlineRowFinalize(InlineRow &row, int32 &sumWidth, bool force = false); @@ -533,6 +541,8 @@ protected: }; +} // namespace internal + class EmojiPan : public TWidget, public RPCSender { Q_OBJECT @@ -581,8 +591,8 @@ public: ).contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size())); } - void ui_repaintInlineItem(const LayoutInlineItem *layout); - bool ui_isInlineItemVisible(const LayoutInlineItem *layout); + void ui_repaintInlineItem(const InlineBots::Layout::ItemBase *layout); + bool ui_isInlineItemVisible(const InlineBots::Layout::ItemBase *layout); bool ui_isInlineItemBeingChosen(); bool inlineResultsShown() const { @@ -623,7 +633,7 @@ signals: void emojiSelected(EmojiPtr emoji); void stickerSelected(DocumentData *sticker); void photoSelected(PhotoData *photo); - void inlineResultSelected(InlineResult *result, UserData *bot); + void inlineResultSelected(InlineBots::Result *result, UserData *bot); void updateStickers(); @@ -643,7 +653,7 @@ private: void updateIcons(); void prepareTab(int32 &left, int32 top, int32 _width, FlatRadiobutton &tab); - void updatePanelsPositions(const QVector &panels, int32 st); + void updatePanelsPositions(const QVector &panels, int32 st); void showAll(); void hideAll(); @@ -662,7 +672,7 @@ private: BoxShadow _shadow; FlatRadiobutton _recent, _people, _nature, _food, _activity, _travel, _objects, _symbols; - QList _icons; + QList _icons; QVector _iconHovers; int32 _iconOver, _iconSel, _iconDown; bool _iconsDragging; @@ -682,13 +692,13 @@ private: Animation _a_slide; ScrollArea e_scroll; - EmojiPanInner e_inner; - QVector e_panels; - EmojiSwitchButton e_switch; + internal::EmojiPanInner e_inner; + QVector e_panels; + internal::EmojiSwitchButton e_switch; ScrollArea s_scroll; - StickerPanInner s_inner; - QVector s_panels; - EmojiSwitchButton s_switch; + internal::StickerPanInner s_inner; + QVector s_panels; + internal::EmojiSwitchButton s_switch; uint64 _removingSetId; @@ -700,13 +710,8 @@ private: clearResults(); } QString nextOffset; - InlineResults results; - void clearResults() { - for (int32 i = 0, l = results.size(); i < l; ++i) { - delete results.at(i); - } - results.clear(); - } + internal::InlineResults results; + void clearResults(); }; typedef QMap InlineCache; InlineCache _inlineCache; diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index 08c373f382..1c5c1ebcca 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -175,12 +175,12 @@ namespace Ui { if (MainWidget *m = App::main()) m->ui_repaintHistoryItem(item); } - void repaintInlineItem(const LayoutInlineItem *layout) { + void repaintInlineItem(const InlineBots::Layout::ItemBase *layout) { if (!layout) return; if (MainWidget *m = App::main()) m->ui_repaintInlineItem(layout); } - bool isInlineItemVisible(const LayoutInlineItem *layout) { + bool isInlineItemVisible(const InlineBots::Layout::ItemBase *layout) { if (MainWidget *m = App::main()) return m->ui_isInlineItemVisible(layout); return false; } diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h index 943d263b9e..6d0651a4a8 100644 --- a/Telegram/SourceFiles/facades.h +++ b/Telegram/SourceFiles/facades.h @@ -43,6 +43,14 @@ namespace App { }; +namespace InlineBots { +namespace Layout { + +class ItemBase; + +} // namespace Layout +} // namespace InlineBots + namespace Ui { void showStickerPreview(DocumentData *sticker); @@ -55,8 +63,8 @@ namespace Ui { bool isInlineItemBeingChosen(); void repaintHistoryItem(const HistoryItem *item); - void repaintInlineItem(const LayoutInlineItem *layout); - bool isInlineItemVisible(const LayoutInlineItem *reader); + void repaintInlineItem(const InlineBots::Layout::ItemBase *layout); + bool isInlineItemVisible(const InlineBots::Layout::ItemBase *reader); void autoplayMediaInlineAsync(const FullMsgId &msgId); void showPeerHistory(const PeerId &peer, MsgId msgId, bool back = false); diff --git a/Telegram/SourceFiles/gui/flattextarea.cpp b/Telegram/SourceFiles/gui/flattextarea.cpp index 7fa7b36128..f406ddea3c 100644 --- a/Telegram/SourceFiles/gui/flattextarea.cpp +++ b/Telegram/SourceFiles/gui/flattextarea.cpp @@ -19,9 +19,9 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" -#include "gui/style.h" - #include "flattextarea.h" + +#include "gui/style.h" #include "window.h" FlatTextarea::FlatTextarea(QWidget *parent, const style::flatTextarea &st, const QString &pholder, const QString &v) : QTextEdit(parent) @@ -298,10 +298,10 @@ QString FlatTextarea::getInlineBotQuery(UserData *&inlineBot, QString &inlineBot inlineBot = 0; } } else { - inlineBot = InlineBotLookingUpData; + inlineBot = LookingUpInlineBot; } } - if (inlineBot == InlineBotLookingUpData) return QString(); + if (inlineBot == LookingUpInlineBot) return QString(); if (inlineBot && (!inlineBot->botInfo || inlineBot->botInfo->inlinePlaceholder.isEmpty())) { inlineBot = 0; diff --git a/Telegram/SourceFiles/gui/flattextarea.h b/Telegram/SourceFiles/gui/flattextarea.h index 6012b8af23..17f067a71f 100644 --- a/Telegram/SourceFiles/gui/flattextarea.h +++ b/Telegram/SourceFiles/gui/flattextarea.h @@ -25,6 +25,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "animation.h" class UserData; +static UserData * const LookingUpInlineBot = SharedMemoryLocation(); + class FlatTextarea : public QTextEdit { Q_OBJECT T_WIDGET diff --git a/Telegram/SourceFiles/gui/text.h b/Telegram/SourceFiles/gui/text.h index e2f93ff9d5..d2edb51940 100644 --- a/Telegram/SourceFiles/gui/text.h +++ b/Telegram/SourceFiles/gui/text.h @@ -555,6 +555,14 @@ private: }; +inline TextClickHandlerPtr clickHandlerFromUrl(const QString &url) { + int32 at = url.indexOf('@'), slash = url.indexOf('/'); + if ((at > 0) && (slash < 0 || slash > at)) { + return MakeShared(url); + } + return MakeShared(url); +} + struct LocationCoords { LocationCoords() : lat(0), lon(0) { } diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index ffeb7121ff..c740e96387 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -19,21 +19,21 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" -#include "gui/style.h" -#include "lang.h" - -#include "application.h" -#include "boxes/confirmbox.h" #include "historywidget.h" -#include "gui/filedialog.h" + +#include "boxes/confirmbox.h" #include "boxes/photosendbox.h" +#include "gui/filedialog.h" +#include "gui/style.h" +#include "inline_bots/inline_bot_result.h" +#include "lang.h" +#include "application.h" #include "mainwidget.h" #include "window.h" #include "passcodewidget.h" #include "window.h" #include "fileuploader.h" #include "audio.h" - #include "localstorage.h" // flick scroll taken from http://qt-project.org/doc/qt-4.8/demos-embedded-anomaly-src-flickcharm-cpp.html @@ -2733,7 +2733,7 @@ HistoryWidget::HistoryWidget(QWidget *parent) : TWidget(parent) connect(&_emojiPan, SIGNAL(emojiSelected(EmojiPtr)), &_field, SLOT(onEmojiInsert(EmojiPtr))); connect(&_emojiPan, SIGNAL(stickerSelected(DocumentData*)), this, SLOT(onStickerSend(DocumentData*))); connect(&_emojiPan, SIGNAL(photoSelected(PhotoData*)), this, SLOT(onPhotoSend(PhotoData*))); - connect(&_emojiPan, SIGNAL(inlineResultSelected(InlineResult*,UserData*)), this, SLOT(onInlineResultSend(InlineResult*,UserData*))); + connect(&_emojiPan, SIGNAL(inlineResultSelected(InlineBots::Result*,UserData*)), this, SLOT(onInlineResultSend(InlineBots::Result*,UserData*))); connect(&_emojiPan, SIGNAL(updateStickers()), this, SLOT(updateStickers())); connect(&_sendActionStopTimer, SIGNAL(timeout()), this, SLOT(onCancelSendAction())); connect(&_previewTimer, SIGNAL(timeout()), this, SLOT(onPreviewTimeout())); @@ -2861,12 +2861,12 @@ void HistoryWidget::updateInlineBotQuery() { MTP::cancel(_inlineBotResolveRequestId); _inlineBotResolveRequestId = 0; } - if (_inlineBot == InlineBotLookingUpData) { + if (_inlineBot == LookingUpInlineBot) { // Notify::inlineBotRequesting(true); _inlineBotResolveRequestId = MTP::send(MTPcontacts_ResolveUsername(MTP_string(_inlineBotUsername)), rpcDone(&HistoryWidget::inlineBotResolveDone), rpcFail(&HistoryWidget::inlineBotResolveFail, _inlineBotUsername)); return; } - } else if (_inlineBot == InlineBotLookingUpData) { + } else if (_inlineBot == LookingUpInlineBot) { return; } @@ -5748,7 +5748,7 @@ void HistoryWidget::updateFieldPlaceholder() { _field.setPlaceholder(lang(lng_edit_message_text)); _send.setText(lang(lng_settings_save)); } else { - if (_inlineBot && _inlineBot != InlineBotLookingUpData) { + if (_inlineBot && _inlineBot != LookingUpInlineBot) { _field.setPlaceholder(_inlineBot->botInfo->inlinePlaceholder.mid(1), _inlineBot->username.size() + 2); } else if (hasBroadcastToggle()) { _field.setPlaceholder(lang(_broadcast.checked() ? (_silent.checked() ? lng_broadcast_silent_ph : lng_broadcast_ph) : lng_comment_ph)); @@ -6172,11 +6172,11 @@ void HistoryWidget::onUpdateHistoryItems() { } } -void HistoryWidget::ui_repaintInlineItem(const LayoutInlineItem *layout) { +void HistoryWidget::ui_repaintInlineItem(const InlineBots::Layout::ItemBase *layout) { _emojiPan.ui_repaintInlineItem(layout); } -bool HistoryWidget::ui_isInlineItemVisible(const LayoutInlineItem *layout) { +bool HistoryWidget::ui_isInlineItemVisible(const InlineBots::Layout::ItemBase *layout) { return _emojiPan.ui_isInlineItemVisible(layout); } @@ -6729,7 +6729,7 @@ void HistoryWidget::onPhotoSend(PhotoData *photo) { sendExistingPhoto(photo, QString()); } -void HistoryWidget::onInlineResultSend(InlineResult *result, UserData *bot) { +void HistoryWidget::onInlineResultSend(InlineBots::Result *result, UserData *bot) { if (!_history || !result || !canSendMessages(_peer)) return; App::main()->readServerHistory(_history, false); @@ -6769,17 +6769,8 @@ void HistoryWidget::onInlineResultSend(InlineResult *result, UserData *bot) { MTPint messageDate = MTP_int(unixtime()); UserId messageViaBotId = bot ? peerToUser(bot->id) : 0; MsgId messageId = newId.msg; - if (DocumentData *document = result->sendData->getSentDocument()) { - _history->addNewDocument(messageId, flags, messageViaBotId, replyToId(), date(messageDate), messageFromId, document, result->sendData->getSentCaption()); - } else if (PhotoData *photo = result->sendData->getSentPhoto()) { - _history->addNewPhoto(messageId, flags, messageViaBotId, replyToId(), date(messageDate), messageFromId, photo, result->sendData->getSentCaption()); - } else { - InlineResultSendData::SentMTPMessageFields fields = result->sendData->getSentMessageFields(result); - if (!fields.entities.c_vector().v.isEmpty()) { - flags |= MTPDmessage::Flag::f_entities; - } - _history->addNewMessage(MTP_message(MTP_flags(flags), MTP_int(messageId), MTP_int(messageFromId), peerToMTP(_history->peer->id), MTPnullFwdHeader, MTP_int(messageViaBotId), MTP_int(replyToId()), messageDate, fields.text, fields.media, MTPnullMarkup, fields.entities, MTP_int(1), MTPint()), NewMessageUnread); - } + + result->addToHistory(_history, flags, messageId, messageFromId, messageDate, messageViaBotId, replyToId()); _history->sendRequestId = MTP::send(MTPmessages_SendInlineBotResult(MTP_flags(sendFlags), _peer->input, MTP_int(replyToId()), MTP_long(randomId), MTP_long(result->queryId), MTP_string(result->id)), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::sendMessageFail), 0, 0, _history->sendRequestId); App::main()->finishForwarding(_history, _broadcast.checked(), _silent.checked()); diff --git a/Telegram/SourceFiles/historywidget.h b/Telegram/SourceFiles/historywidget.h index 0d65ebe033..ef1b260bdf 100644 --- a/Telegram/SourceFiles/historywidget.h +++ b/Telegram/SourceFiles/historywidget.h @@ -473,6 +473,17 @@ enum TextUpdateEventsFlags { TextUpdateEventsSendTyping = 0x02, }; +namespace InlineBots { +namespace Layout { + +class ItemBase; + +} // namespace Layout + +class Result; + +} // namespace InlineBots + class HistoryWidget : public TWidget, public RPCSender { Q_OBJECT @@ -653,8 +664,8 @@ public: bool isItemVisible(HistoryItem *item); void ui_repaintHistoryItem(const HistoryItem *item); - void ui_repaintInlineItem(const LayoutInlineItem *gif); - bool ui_isInlineItemVisible(const LayoutInlineItem *layout); + void ui_repaintInlineItem(const InlineBots::Layout::ItemBase *gif); + bool ui_isInlineItemVisible(const InlineBots::Layout::ItemBase *layout); bool ui_isInlineItemBeingChosen(); PeerData *ui_getPeerForMouseAction(); @@ -745,7 +756,7 @@ public slots: void onFieldTabbed(); void onStickerSend(DocumentData *sticker); void onPhotoSend(PhotoData *photo); - void onInlineResultSend(InlineResult *result, UserData *bot); + void onInlineResultSend(InlineBots::Result *result, UserData *bot); void onWindowVisibleChanged(); diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp new file mode 100644 index 0000000000..dbf5e9c4c0 --- /dev/null +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp @@ -0,0 +1,1082 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "inline_bots/inline_bot_layout_internal.h" + +#include "inline_bots/inline_bot_result.h" +#include "localstorage.h" +#include "mainwidget.h" + +namespace InlineBots { +namespace Layout { +namespace internal { + +FileBase::FileBase(Result *result) : ItemBase(result) { +} + +FileBase::FileBase(DocumentData *document) : ItemBase(document) { +} + +DocumentData *FileBase::getShownDocument() const { + if (DocumentData *result = getDocument()) { + return result; + } else if (Result *result = getResult()) { + return result->document; + } + return nullptr; +} + +int FileBase::content_width() const { + if (DocumentData *document = getShownDocument()) { + if (document->dimensions.width() > 0) { + return document->dimensions.width(); + } + if (!document->thumb->isNull()) { + return convertScale(document->thumb->width()); + } + } else if (_result) { + return _result->width; + } + return 0; +} + +int FileBase::content_height() const { + if (DocumentData *document = getShownDocument()) { + if (document->dimensions.height() > 0) { + return document->dimensions.height(); + } + if (!document->thumb->isNull()) { + return convertScale(document->thumb->height()); + } + } else if (_result) { + return _result->height; + } + return 0; +} + +bool FileBase::content_loading() const { + if (DocumentData *document = getShownDocument()) { + return document->loading(); + } + return _result->loading(); +} + +bool FileBase::content_displayLoading() const { + if (DocumentData *document = getShownDocument()) { + return document->displayLoading(); + } + return _result->displayLoading(); +} + +bool FileBase::content_loaded() const { + if (DocumentData *document = getShownDocument()) { + return document->loaded(); + } + return _result->loaded(); +} + +float64 FileBase::content_progress() const { + if (DocumentData *document = getShownDocument()) { + return document->progress(); + } + return _result->progress(); +} + +void FileBase::content_automaticLoad() const { + if (DocumentData *document = getShownDocument()) { + document->automaticLoad(nullptr); + } else { + _result->automaticLoadGif(); + } +} + +void FileBase::content_forget() { + if (DocumentData *document = getShownDocument()) { + document->forget(); + } else { + _result->forget(); + } +} + +FileLocation FileBase::content_location() const { + if (DocumentData *document = getShownDocument()) { + return document->location(); + } + return FileLocation(); +} + +QByteArray FileBase::content_data() const { + if (DocumentData *document = getShownDocument()) { + return document->data(); + } + return _result->data(); +} + +ImagePtr FileBase::content_thumb() const { + if (DocumentData *document = getShownDocument()) { + if (!document->thumb->isNull()) { + return document->thumb; + } + } + if (_result->photo && !_result->photo->thumb->isNull()) { + return _result->photo->thumb; + } + return _result->thumb; +} + +int FileBase::content_duration() const { + if (_result->document) { + if (_result->document->duration() > 0) { + return _result->document->duration(); + } else if (SongData *song = _result->document->song()) { + if (song->duration) { + return song->duration; + } + } + } + return _result->duration; +} + +Gif::Gif(Result *result) : FileBase(result) { +} + +Gif::Gif(DocumentData *document, bool hasDeleteButton) : FileBase(document) +, _delete(hasDeleteButton ? new DeleteSavedGifClickHandler(document) : nullptr) { +} + +void Gif::initDimensions() { + int32 w = content_width(), h = content_height(); + if (w <= 0 || h <= 0) { + _maxw = 0; + } else { + w = w * st::inlineMediaHeight / h; + _maxw = qMax(w, int32(st::inlineResultsMinWidth)); + } + _minh = st::inlineMediaHeight + st::inlineResultsSkip; +} + +void Gif::setPosition(int32 position) { + ItemBase::setPosition(position); + if (_position < 0) { + if (gif()) delete _gif; + _gif = 0; + } +} + +void DeleteSavedGifClickHandler::onClickImpl() const { + int32 index = cSavedGifs().indexOf(_data); + if (index >= 0) { + cRefSavedGifs().remove(index); + Local::writeSavedGifs(); + + MTP::send(MTPmessages_SaveGif(MTP_inputDocument(MTP_long(_data->id), MTP_long(_data->access)), MTP_bool(true))); + } + if (App::main()) emit App::main()->savedGifsUpdated(); +} + +void Gif::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { + content_automaticLoad(); + + bool loaded = content_loaded(), loading = content_loading(), displayLoading = content_displayLoading(); + if (loaded && !gif() && _gif != BadClipReader) { + Gif *that = const_cast(this); + that->_gif = new ClipReader(content_location(), content_data(), func(that, &Gif::clipCallback)); + if (gif()) _gif->setAutoplay(); + } + + bool animating = (gif() && _gif->started()); + if (displayLoading) { + ensureAnimation(); + if (!_animation->radial.animating()) { + _animation->radial.start(content_progress()); + } + } + bool radial = isRadialAnimation(context->ms); + + int32 height = st::inlineMediaHeight; + QSize frame = countFrameSize(); + + QRect r(0, 0, _width, height); + if (animating) { + if (!_thumb.isNull()) _thumb = QPixmap(); + p.drawPixmap(r.topLeft(), _gif->current(frame.width(), frame.height(), _width, height, context->paused ? 0 : context->ms)); + } else { + prepareThumb(_width, height, frame); + if (_thumb.isNull()) { + p.fillRect(r, st::overviewPhotoBg); + } else { + p.drawPixmap(r.topLeft(), _thumb); + } + } + + if (radial || (!_gif && !loaded && !loading) || (_gif == BadClipReader)) { + float64 radialOpacity = (radial && loaded) ? _animation->radial.opacity() : 1; + if (_animation && _animation->_a_over.animating(context->ms)) { + float64 over = _animation->_a_over.current(); + p.setOpacity((st::msgDateImgBg->c.alphaF() * (1 - over)) + (st::msgDateImgBgOver->c.alphaF() * over)); + p.fillRect(r, st::black); + } else { + p.fillRect(r, (_state & StateFlag::Over) ? st::msgDateImgBgOver : st::msgDateImgBg); + } + p.setOpacity(radialOpacity * p.opacity()); + + p.setOpacity(radialOpacity); + style::sprite icon; + if (loaded && !radial) { + icon = st::msgFileInPlay; + } else if (radial || loading) { + icon = st::msgFileInCancel; + } else { + icon = st::msgFileInDownload; + } + QRect inner((_width - st::msgFileSize) / 2, (height - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize); + p.drawSpriteCenter(inner, icon); + 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, st::msgInBg); + } + } + + if (_delete && (_state & StateFlag::Over)) { + float64 deleteOver = _a_deleteOver.current(context->ms, (_state & StateFlag::DeleteOver) ? 1 : 0); + QPoint deletePos = QPoint(_width - st::stickerPanDelete.pxWidth(), 0); + p.setOpacity(deleteOver + (1 - deleteOver) * st::stickerPanDeleteOpacity); + p.drawSpriteLeft(deletePos, _width, st::stickerPanDelete); + p.setOpacity(1); + } +} + +void Gif::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const { + if (x >= 0 && x < _width && y >= 0 && y < st::inlineMediaHeight) { + if (_delete && (rtl() ? _width - x : x) >= _width - st::stickerPanDelete.pxWidth() && y < st::stickerPanDelete.pxHeight()) { + link = _delete; + } else { + link = _send; + } + } +} + +void Gif::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { + if (!p) return; + + if (_delete && p == _delete) { + bool wasactive = (_state & StateFlag::DeleteOver); + if (active != wasactive) { + float64 from = active ? 0 : 1, to = active ? 1 : 0; + EnsureAnimation(_a_deleteOver, from, func(this, &Gif::update)); + _a_deleteOver.start(to, st::stickersRowDuration); + if (active) { + _state |= StateFlag::DeleteOver; + } else { + _state &= ~StateFlag::DeleteOver; + } + } + } + if (p == _delete || p == _send) { + bool wasactive = (_state & StateFlag::Over); + if (active != wasactive) { + if (!content_loaded()) { + ensureAnimation(); + float64 from = active ? 0 : 1, to = active ? 1 : 0; + EnsureAnimation(_animation->_a_over, from, func(this, &Gif::update)); + _animation->_a_over.start(to, st::stickersRowDuration); + } + if (active) { + _state |= StateFlag::Over; + } else { + _state &= ~StateFlag::Over; + } + } + } + ItemBase::clickHandlerActiveChanged(p, active); +} + +QSize Gif::countFrameSize() const { + bool animating = (gif() && _gif->ready()); + int32 framew = animating ? _gif->width() : content_width(), frameh = animating ? _gif->height() : content_height(), height = st::inlineMediaHeight; + if (framew * height > frameh * _width) { + if (framew < st::maxStickerSize || frameh > height) { + if (frameh > height || (framew * height / frameh) <= st::maxStickerSize) { + framew = framew * height / frameh; + frameh = height; + } else { + frameh = int32(frameh * st::maxStickerSize) / framew; + framew = st::maxStickerSize; + } + } + } else { + if (frameh < st::maxStickerSize || framew > _width) { + if (framew > _width || (frameh * _width / framew) <= st::maxStickerSize) { + frameh = frameh * _width / framew; + framew = _width; + } else { + framew = int32(framew * st::maxStickerSize) / frameh; + frameh = st::maxStickerSize; + } + } + } + return QSize(framew, frameh); +} + +Gif::~Gif() { + if (gif()) deleteAndMark(_gif); + deleteAndMark(_animation); +} + +void Gif::prepareThumb(int32 width, int32 height, const QSize &frame) const { + if (DocumentData *document = getShownDocument()) { + if (!document->thumb->isNull()) { + if (document->thumb->loaded()) { + if (_thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) { + _thumb = document->thumb->pixNoCache(frame.width() * cIntRetinaFactor(), frame.height() * cIntRetinaFactor(), ImagePixSmooth, width, height); + } + } else { + document->thumb->load(); + } + } + } else { + if (!_result->thumb_url.isEmpty()) { + if (_result->thumb->loaded()) { + if (_thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) { + _thumb = _result->thumb->pixNoCache(frame.width() * cIntRetinaFactor(), frame.height() * cIntRetinaFactor(), ImagePixSmooth, width, height); + } + } else { + _result->thumb->load(); + } + } + } +} + +void Gif::ensureAnimation() const { + if (!_animation) { + _animation = new AnimationData(animation(const_cast(this), &Gif::step_radial)); + } +} + +bool Gif::isRadialAnimation(uint64 ms) const { + if (!_animation || !_animation->radial.animating()) return false; + + _animation->radial.step(ms); + return _animation && _animation->radial.animating(); +} + +void Gif::step_radial(uint64 ms, bool timer) { + if (timer) { + update(); + } else { + _animation->radial.update(content_progress(), !content_loading() || content_loaded(), ms); + if (!_animation->radial.animating() && content_loaded()) { + delete _animation; + _animation = 0; + } + } +} + +void Gif::clipCallback(ClipReaderNotification notification) { + switch (notification) { + case ClipReaderReinit: { + if (gif()) { + if (_gif->state() == ClipError) { + delete _gif; + _gif = BadClipReader; + content_forget(); + } else if (_gif->ready() && !_gif->started()) { + int32 height = st::inlineMediaHeight; + QSize frame = countFrameSize(); + _gif->start(frame.width(), frame.height(), _width, height, false); + } else if (_gif->paused() && !Ui::isInlineItemVisible(this)) { + delete _gif; + _gif = 0; + content_forget(); + } + } + + update(); + } break; + + case ClipReaderRepaint: { + if (gif() && !_gif->currentDisplayed()) { + update(); + } + } break; + } +} + +Sticker::Sticker(Result *result) : FileBase(result) { +} + +void Sticker::initDimensions() { + _maxw = st::stickerPanSize.width(); + _minh = st::stickerPanSize.height(); +} + +void Sticker::preload() const { + if (DocumentData *document = getShownDocument()) { + bool goodThumb = !document->thumb->isNull() && ((document->thumb->width() >= 128) || (document->thumb->height() >= 128)); + if (goodThumb) { + document->thumb->load(); + } else { + document->checkSticker(); + } + } else if (!_result->thumb->isNull()) { + _result->thumb->load(); + } +} + +void Sticker::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { + bool loaded = content_loaded(); + + float64 over = _a_over.isNull() ? (_active ? 1 : 0) : _a_over.current(); + if (over > 0) { + p.setOpacity(over); + App::roundRect(p, QRect(QPoint(0, 0), st::stickerPanSize), st::emojiPanHover, StickerHoverCorners); + p.setOpacity(1); + } + + prepareThumb(); + if (!_thumb.isNull()) { + int w = _thumb.width() / cIntRetinaFactor(), h = _thumb.height() / cIntRetinaFactor(); + QPoint pos = QPoint((st::stickerPanSize.width() - w) / 2, (st::stickerPanSize.height() - h) / 2); + p.drawPixmap(pos, _thumb); + } +} + +void Sticker::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const { + if (x >= 0 && x < _width && y >= 0 && y < st::inlineMediaHeight) { + link = _send; + } +} + +void Sticker::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { + if (!p) return; + + if (p == _send) { + if (active != _active) { + _active = active; + + float64 from = _active ? 0 : 1, to = _active ? 1 : 0; + EnsureAnimation(_a_over, from, func(this, &Sticker::update)); + _a_over.start(to, st::stickersRowDuration); + } + } + ItemBase::clickHandlerActiveChanged(p, active); +} + +QSize Sticker::getThumbSize() const { + int width = qMax(content_width(), 1), height = qMax(content_height(), 1); + float64 coefw = (st::stickerPanSize.width() - st::msgRadius * 2) / float64(width); + float64 coefh = (st::stickerPanSize.height() - st::msgRadius * 2) / float64(height); + float64 coef = qMin(qMin(coefw, coefh), 1.); + int w = qRound(coef * content_width()), h = qRound(coef * content_height()); + return QSize(qMax(w, 1), qMax(h, 1)); +} + +void Sticker::prepareThumb() const { + if (DocumentData *document = getShownDocument()) { + bool goodThumb = !document->thumb->isNull() && ((document->thumb->width() >= 128) || (document->thumb->height() >= 128)); + if (goodThumb) { + document->thumb->load(); + } else { + document->checkSticker(); + } + + ImagePtr sticker = goodThumb ? document->thumb : document->sticker()->img; + if (!_thumbLoaded && sticker->loaded()) { + QSize thumbSize = getThumbSize(); + _thumb = sticker->pix(thumbSize.width(), thumbSize.height()); + _thumbLoaded = true; + } + } else { + if (_result->thumb->loaded()) { + if (!_thumbLoaded) { + QSize thumbSize = getThumbSize(); + _thumb = _result->thumb->pix(thumbSize.width(), thumbSize.height()); + _thumbLoaded = true; + } + } else { + _result->thumb->load(); + } + } +} + +Photo::Photo(Result *result) : ItemBase(result) { +} + +void Photo::initDimensions() { + int32 w = content_width(), h = content_height(); + if (w <= 0 || h <= 0) { + _maxw = 0; + } else { + w = w * st::inlineMediaHeight / h; + _maxw = qMax(w, int32(st::inlineResultsMinWidth)); + } + _minh = st::inlineMediaHeight + st::inlineResultsSkip; +} + +void Photo::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { + bool loaded = content_loaded(); + + int32 height = st::inlineMediaHeight; + QSize frame = countFrameSize(); + + QRect r(0, 0, _width, height); + + prepareThumb(_width, height, frame); + if (_thumb.isNull()) { + p.fillRect(r, st::overviewPhotoBg); + } else { + p.drawPixmap(r.topLeft(), _thumb); + } +} + +void Photo::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const { + if (x >= 0 && x < _width && y >= 0 && y < st::inlineMediaHeight) { + link = _send; + } +} + +PhotoData *Photo::getShownPhoto() const { + if (PhotoData *result = getPhoto()) { + return result; + } else if (Result *result = getResult()) { + return result->photo; + } + return nullptr; +} + +QSize Photo::countFrameSize() const { + int32 framew = content_width(), frameh = content_height(), height = st::inlineMediaHeight; + if (framew * height > frameh * _width) { + if (framew < st::maxStickerSize || frameh > height) { + if (frameh > height || (framew * height / frameh) <= st::maxStickerSize) { + framew = framew * height / frameh; + frameh = height; + } else { + frameh = int32(frameh * st::maxStickerSize) / framew; + framew = st::maxStickerSize; + } + } + } else { + if (frameh < st::maxStickerSize || framew > _width) { + if (framew > _width || (frameh * _width / framew) <= st::maxStickerSize) { + frameh = frameh * _width / framew; + framew = _width; + } else { + framew = int32(framew * st::maxStickerSize) / frameh; + frameh = st::maxStickerSize; + } + } + } + return QSize(framew, frameh); +} + +void Photo::prepareThumb(int32 width, int32 height, const QSize &frame) const { + if (PhotoData *photo = getShownPhoto()) { + if (photo->medium->loaded()) { + if (!_thumbLoaded || _thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) { + _thumb = photo->medium->pixNoCache(frame.width() * cIntRetinaFactor(), frame.height() * cIntRetinaFactor(), ImagePixSmooth, width, height); + } + _thumbLoaded = true; + } else { + if (photo->thumb->loaded()) { + if (_thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) { + _thumb = photo->thumb->pixNoCache(frame.width() * cIntRetinaFactor(), frame.height() * cIntRetinaFactor(), ImagePixSmooth, width, height); + } + } + photo->medium->load(); + } + } else { + if (_result->thumb->loaded()) { + if (_thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) { + _thumb = _result->thumb->pixNoCache(frame.width() * cIntRetinaFactor(), frame.height() * cIntRetinaFactor(), ImagePixSmooth, width, height); + } + } else { + _result->thumb->load(); + } + } +} + +int Photo::content_width() const { + if (PhotoData *photo = getShownPhoto()) { + return photo->full->width(); + } else if (_result) { + return _result->width; + } + return 0; +} + +int Photo::content_height() const { + if (PhotoData *photo = getShownPhoto()) { + return photo->full->height(); + } else if (_result) { + return _result->height; + } + return 0; +} + +bool Photo::content_loaded() const { + if (PhotoData *photo = getShownPhoto()) { + return photo->loaded(); + } + return _result->loaded(); +} + +void Photo::content_forget() { + if (PhotoData *photo = getShownPhoto()) { + photo->forget(); + } else { + _result->forget(); + } +} + +Video::Video(Result *result) : FileBase(result) +, _title(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) +, _description(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) { + if (!result->content_url.isEmpty()) { + _link = clickHandlerFromUrl(result->content_url); + } + if (int duration = content_duration()) { + _duration = formatDurationText(duration); + } +} + +void Video::initDimensions() { + bool withThumb = !content_thumb()->isNull(); + + _maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft; + int32 textWidth = _maxw - (withThumb ? (st::inlineThumbSize + st::inlineThumbSkip) : 0); + TextParseOptions titleOpts = { 0, _maxw, 2 * st::semiboldFont->height, Qt::LayoutDirectionAuto }; + _title.setText(st::semiboldFont, textOneLine(_result->getLayoutTitle()), titleOpts); + int32 titleHeight = qMin(_title.countHeight(_maxw), 2 * st::semiboldFont->height); + + int32 descriptionLines = withThumb ? (titleHeight > st::semiboldFont->height ? 1 : 2) : 3; + + TextParseOptions descriptionOpts = { TextParseMultiline, _maxw, descriptionLines * st::normalFont->height, Qt::LayoutDirectionAuto }; + _description.setText(st::normalFont, _result->getLayoutDescription(), descriptionOpts); + int32 descriptionHeight = qMin(_description.countHeight(_maxw), descriptionLines * st::normalFont->height); + + _minh = st::inlineThumbSize; + _minh += st::inlineRowMargin * 2 + st::inlineRowBorder; +} + +void Video::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { + int32 left = st::inlineThumbSize + st::inlineThumbSkip; + + bool withThumb = !content_thumb()->isNull(); + if (withThumb) { + prepareThumb(st::inlineThumbSize, st::inlineThumbSize); + if (_thumb.isNull()) { + p.fillRect(rtlrect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize, _width), st::overviewPhotoBg); + } else { + p.drawPixmapLeft(0, st::inlineRowMargin, _width, _thumb); + } + } else { + p.fillRect(rtlrect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize, _width), st::black); + } + + if (!_duration.isEmpty()) { + int32 durationTop = st::inlineRowMargin + st::inlineThumbSize - st::normalFont->height - st::inlineDurationMargin; + p.fillRect(rtlrect(0, durationTop - st::inlineDurationMargin, st::inlineThumbSize, st::normalFont->height + 2 * st::inlineDurationMargin, _width), st::msgDateImgBg); + p.setPen(st::white); + p.setFont(st::normalFont); + p.drawTextRight(_width - st::inlineThumbSize + st::inlineDurationMargin, durationTop, _width, _duration); + } + + p.setPen(st::black); + _title.drawLeftElided(p, left, st::inlineRowMargin, _width - left, _width, 2); + int32 titleHeight = qMin(_title.countHeight(_width - left), st::semiboldFont->height * 2); + + p.setPen(st::inlineDescriptionFg); + int32 descriptionLines = withThumb ? (titleHeight > st::semiboldFont->height ? 1 : 2) : 3; + _description.drawLeftElided(p, left, st::inlineRowMargin + titleHeight, _width - left, _width, descriptionLines); + + if (!context->lastRow) { + p.fillRect(rtlrect(left, _height - st::inlineRowBorder, _width - left, st::inlineRowBorder, _width), st::inlineRowBorderFg); + } +} + +void Video::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const { + if (x >= 0 && x < st::inlineThumbSize && y >= st::inlineRowMargin && y < st::inlineRowMargin + st::inlineThumbSize) { + link = _link; + return; + } + if (x >= st::inlineThumbSize + st::inlineThumbSkip && x < _width && y >= 0 && y < _height) { + link = _send; + return; + } +} + +void Video::prepareThumb(int32 width, int32 height) const { + ImagePtr thumb = content_thumb(); + if (thumb->loaded()) { + if (_thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) { + int32 w = qMax(convertScale(thumb->width()), 1), h = qMax(convertScale(thumb->height()), 1); + if (w * height > h * width) { + if (height < h) { + w = w * height / h; + h = height; + } + } else { + if (width < w) { + h = h * width / w; + w = width; + } + } + _thumb = thumb->pixNoCache(w * cIntRetinaFactor(), h * cIntRetinaFactor(), ImagePixSmooth, width, height); + } + } else { + thumb->load(); + } +} + +void OpenFileClickHandler::onClickImpl() const { + _result->automaticLoadGif(); +} + +void CancelFileClickHandler::onClickImpl() const { + _result->cancelFile(); +} + +File::File(Result *result) : FileBase(result) +, _title(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::msgFileSize - st::inlineThumbSkip) +, _description(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::msgFileSize - st::inlineThumbSkip) +, _open(new OpenFileClickHandler(result)) +, _cancel(new CancelFileClickHandler(result)) { +} + +void File::initDimensions() { + _maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft; + int32 textWidth = _maxw - (st::msgFileSize + st::inlineThumbSkip); + TextParseOptions titleOpts = { 0, _maxw, 2 * st::semiboldFont->height, Qt::LayoutDirectionAuto }; + _title.setText(st::semiboldFont, textOneLine(_result->getLayoutTitle()), titleOpts); + int32 titleHeight = qMin(_title.countHeight(_maxw), 2 * st::semiboldFont->height); + + int32 descriptionLines = 1; + + TextParseOptions descriptionOpts = { TextParseMultiline, _maxw, descriptionLines * st::normalFont->height, Qt::LayoutDirectionAuto }; + _description.setText(st::normalFont, _result->getLayoutDescription(), descriptionOpts); + int32 descriptionHeight = qMin(_description.countHeight(_maxw), descriptionLines * st::normalFont->height); + + _minh = st::msgFileSize; + _minh += st::inlineRowMargin * 2 + st::inlineRowBorder; +} + +void File::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { + int32 left = st::msgFileSize + st::inlineThumbSkip; + + bool loaded = content_loaded(), displayLoading = content_displayLoading(); + if (displayLoading) { + ensureAnimation(); + if (!_animation->radial.animating()) { + _animation->radial.start(content_progress()); + } + } + bool showPause = false;// updateStatusText(parent); + bool radial = isRadialAnimation(context->ms); + + QRect iconCircle = rtlrect(0, st::inlineRowMargin, st::msgFileSize, st::msgFileSize, _width); + p.setPen(Qt::NoPen); + if (isThumbAnimation(context->ms)) { + float64 over = _animation->a_thumbOver.current(); + p.setBrush(style::interpolate(st::msgFileInBg, st::msgFileInBgOver, over)); + } else { + bool over = ClickHandler::showAsActive(content_loading() ? _cancel : _open); + p.setBrush((over ? st::msgFileInBgOver : st::msgFileInBg)); + } + + p.setRenderHint(QPainter::HighQualityAntialiasing); + p.drawEllipse(iconCircle); + p.setRenderHint(QPainter::HighQualityAntialiasing, false); + + style::sprite icon; + if (bool showPause = false) { + icon = st::msgFileInPause; + } else if (radial || content_loading()) { + icon = st::msgFileInCancel; + } else if (loaded) { + //if (_data->song() || _data->voice()) { + icon = st::msgFileInPlay; + //} else if (_data->isImage()) { + // icon = st::msgFileInImage; + //} else { + // icon = st::msgFileInFile; + //} + } else { + icon = st::msgFileInDownload; + } + p.drawSpriteCenter(iconCircle, icon); + + int titleTop = st::inlineRowMargin + st::inlineRowFileNameTop; + int descriptionTop = st::inlineRowMargin + st::inlineRowFileDescriptionTop; + + p.setPen(st::black); + _title.drawLeftElided(p, left, titleTop, _width - left, _width, 2); + int32 titleHeight = st::semiboldFont->height; + + p.setPen(st::inlineDescriptionFg); + int32 descriptionLines = 1; + _description.drawLeftElided(p, left, descriptionTop, _width - left, _width, descriptionLines); + + if (!context->lastRow) { + p.fillRect(rtlrect(left, _height - st::inlineRowBorder, _width - left, st::inlineRowBorder, _width), st::inlineRowBorderFg); + } +} + +void File::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const { + if (x >= 0 && x < st::msgFileSize && y >= st::inlineRowMargin && y < st::inlineRowMargin + st::msgFileSize) { + link = content_loading() ? _cancel : _open; + return; + } + if (x >= st::msgFileSize + st::inlineThumbSkip && x < _width && y >= 0 && y < _height) { + link = _send; + return; + } +} + +void File::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { + if (p == _open || p == _cancel) { + if (active && !content_loaded()) { + ensureAnimation(); + _animation->a_thumbOver.start(1); + _animation->_a_thumbOver.start(); + } else if (!active && _animation) { + _animation->a_thumbOver.start(0); + _animation->_a_thumbOver.start(); + } + } +} + +void File::step_thumbOver(float64 ms, bool timer) { + float64 dt = ms / st::msgFileOverDuration; + if (dt >= 1) { + _animation->a_thumbOver.finish(); + _animation->_a_thumbOver.stop(); + checkAnimationFinished(); + } else if (!timer) { + _animation->a_thumbOver.update(dt, anim::linear); + } + if (timer) { + Ui::repaintInlineItem(this); + } +} + +void File::step_radial(uint64 ms, bool timer) { + if (timer) { + Ui::repaintInlineItem(this); + } else { + _animation->radial.update(content_progress(), content_loaded(), ms); + if (!_animation->radial.animating()) { + checkAnimationFinished(); + } + } +} + +void File::ensureAnimation() const { + if (!_animation) { + _animation.reset(new AnimationData( + animation(const_cast(this), &File::step_thumbOver), + animation(const_cast(this), &File::step_radial))); + } +} + +void File::checkAnimationFinished() { + if (_animation && !_animation->_a_thumbOver.animating() && !_animation->radial.animating()) { + if (content_loaded()) { + _animation.clear(); + } + } +} + +Article::Article(Result *result, bool withThumb) : ItemBase(result) +, _withThumb(withThumb) +, _title(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) +, _description(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) { + if (!result->url.isEmpty()) { + _url = clickHandlerFromUrl(result->url); + } + if (!result->content_url.isEmpty()) { + _link = clickHandlerFromUrl(result->content_url); + } else { + LocationCoords location; + if (result->getLocationCoords(&location)) { + _link.reset(new LocationClickHandler(location)); + int32 w = st::inlineThumbSize, h = st::inlineThumbSize; + int32 zoom = 13, scale = 1; + if (cScale() == dbisTwo || cRetina()) { + scale = 2; + w /= 2; + h /= 2; + } + QString coords = qsl("%1,%2").arg(location.lat).arg(location.lon); + QString url = qsl("https://maps.googleapis.com/maps/api/staticmap?center=") + coords + qsl("&zoom=%1&size=%2x%3&maptype=roadmap&scale=%4&markers=color:red|size:big|").arg(zoom).arg(w).arg(h).arg(scale) + coords + qsl("&sensor=false"); + result->thumb = ImagePtr(url); + } + } + QVector parts = _result->url.splitRef('/'); + if (!parts.isEmpty()) { + QStringRef domain = parts.at(0); + if (parts.size() > 2 && domain.endsWith(':') && parts.at(1).isEmpty()) { // http:// and others + domain = parts.at(2); + } + + parts = domain.split('@').back().split('.'); + if (parts.size() > 1) { + _letter = parts.at(parts.size() - 2).at(0).toUpper(); + } + } + if (_letter.isEmpty() && !_result->title.isEmpty()) { + _letter = _result->title.at(0).toUpper(); + } +} + +void Article::initDimensions() { + _maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft; + int32 textWidth = _maxw - (_withThumb ? (st::inlineThumbSize + st::inlineThumbSkip) : 0); + TextParseOptions titleOpts = { 0, _maxw, 2 * st::semiboldFont->height, Qt::LayoutDirectionAuto }; + _title.setText(st::semiboldFont, textOneLine(_result->title), titleOpts); + int32 titleHeight = qMin(_title.countHeight(_maxw), 2 * st::semiboldFont->height); + + int32 descriptionLines = (_withThumb || _url) ? 2 : 3; + QString description = _result->getLayoutDescription(); + TextParseOptions descriptionOpts = { TextParseMultiline, _maxw, descriptionLines * st::normalFont->height, Qt::LayoutDirectionAuto }; + _description.setText(st::normalFont, description, descriptionOpts); + int32 descriptionHeight = qMin(_description.countHeight(_maxw), descriptionLines * st::normalFont->height); + + _minh = titleHeight + descriptionHeight; + if (_url) _minh += st::normalFont->height; + if (_withThumb) _minh = qMax(_minh, int32(st::inlineThumbSize)); + _minh += st::inlineRowMargin * 2 + st::inlineRowBorder; +} + +int32 Article::resizeGetHeight(int32 width) { + _width = qMin(width, _maxw); + if (_url) { + _urlText = _result->url; + _urlWidth = st::normalFont->width(_urlText); + if (_urlWidth > _width - st::inlineThumbSize - st::inlineThumbSkip) { + _urlText = st::normalFont->elided(_result->url, _width - st::inlineThumbSize - st::inlineThumbSkip); + _urlWidth = st::normalFont->width(_urlText); + } + } + _height = _minh; + return _height; +} + +void Article::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { + int32 left = st::emojiPanHeaderLeft - st::inlineResultsLeft; + if (_withThumb) { + left = st::inlineThumbSize + st::inlineThumbSkip; + prepareThumb(st::inlineThumbSize, st::inlineThumbSize); + QRect rthumb(rtlrect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize, _width)); + if (_thumb.isNull()) { + if (_result->thumb->isNull() && !_letter.isEmpty()) { + int32 index = (_letter.at(0).unicode() % 4); + style::color colors[] = { st::msgFileRedColor, st::msgFileYellowColor, st::msgFileGreenColor, st::msgFileBlueColor }; + + p.fillRect(rthumb, colors[index]); + if (!_letter.isEmpty()) { + p.setFont(st::linksLetterFont); + p.setPen(st::white); + p.drawText(rthumb, _letter, style::al_center); + } + } else { + p.fillRect(rthumb, st::overviewPhotoBg); + } + } else { + p.drawPixmapLeft(rthumb.topLeft(), _width, _thumb); + } + } + + p.setPen(st::black); + _title.drawLeftElided(p, left, st::inlineRowMargin, _width - left, _width, 2); + int32 titleHeight = qMin(_title.countHeight(_width - left), st::semiboldFont->height * 2); + + p.setPen(st::inlineDescriptionFg); + int32 descriptionLines = (_withThumb || _url) ? 2 : 3; + _description.drawLeftElided(p, left, st::inlineRowMargin + titleHeight, _width - left, _width, descriptionLines); + + if (_url) { + int32 descriptionHeight = qMin(_description.countHeight(_width - left), st::normalFont->height * descriptionLines); + p.drawTextLeft(left, st::inlineRowMargin + titleHeight + descriptionHeight, _width, _urlText, _urlWidth); + } + + if (!context->lastRow) { + p.fillRect(rtlrect(left, _height - st::inlineRowBorder, _width - left, st::inlineRowBorder, _width), st::inlineRowBorderFg); + } +} + +void Article::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const { + int32 left = _withThumb ? (st::inlineThumbSize + st::inlineThumbSkip) : 0; + if (x >= 0 && x < left - st::inlineThumbSkip && y >= st::inlineRowMargin && y < st::inlineRowMargin + st::inlineThumbSize) { + link = _link; + return; + } + if (x >= left && x < _width && y >= 0 && y < _height) { + if (_url) { + int32 left = st::inlineThumbSize + st::inlineThumbSkip; + int32 titleHeight = qMin(_title.countHeight(_width - left), st::semiboldFont->height * 2); + int32 descriptionLines = 2; + int32 descriptionHeight = qMin(_description.countHeight(_width - left), st::normalFont->height * descriptionLines); + if (rtlrect(left, st::inlineRowMargin + titleHeight + descriptionHeight, _urlWidth, st::normalFont->height, _width).contains(x, y)) { + link = _url; + return; + } + } + link = _send; + return; + } +} + +void Article::prepareThumb(int32 width, int32 height) const { + if (_result->thumb->isNull()) { + if (_result->type == Result::Type::Contact) { + if (_thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) { + _thumb = userDefPhoto(qHash(_result->id) % UserColorsCount)->pixCircled(width, height); + } + } + return; + } + + if (_result->thumb->loaded()) { + if (_thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) { + int32 w = qMax(convertScale(_result->thumb->width()), 1), h = qMax(convertScale(_result->thumb->height()), 1); + if (w * height > h * width) { + if (height < h) { + w = w * height / h; + h = height; + } + } else { + if (width < w) { + h = h * width / w; + w = width; + } + } + _thumb = _result->thumb->pixNoCache(w * cIntRetinaFactor(), h * cIntRetinaFactor(), ImagePixSmooth, width, height); + } + } else { + _result->thumb->load(); + } +} + +} // namespace internal +} // namespace Layout +} // namespace InlineBots diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h new file mode 100644 index 0000000000..74b5c6e486 --- /dev/null +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h @@ -0,0 +1,325 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "inline_bots/inline_bot_layout_item.h" +#include "gui/text.h" + +namespace InlineBots { +namespace Layout { +namespace internal { + +class FileBase : public ItemBase { +public: + FileBase(Result *result); + // for saved gif layouts + FileBase(DocumentData *doc); + +protected: + DocumentData *getShownDocument() const; + + int content_width() const; + int content_height() const; + bool content_loading() const; + bool content_displayLoading() const; + bool content_loaded() const; + float64 content_progress() const; + void content_automaticLoad() const; + void content_forget(); + FileLocation content_location() const; + QByteArray content_data() const; + ImagePtr content_thumb() const; + int content_duration() const; +}; + +class DeleteSavedGifClickHandler : public LeftButtonClickHandler { +public: + DeleteSavedGifClickHandler(DocumentData *data) : _data(data) { + } + +protected: + void onClickImpl() const override; + +private: + DocumentData *_data; + +}; + +class Gif : public FileBase { +public: + Gif(Result *result); + Gif(DocumentData *doc, bool hasDeleteButton); + + void setPosition(int32 position) override; + void initDimensions() override; + + bool isFullLine() const override { + return false; + } + bool hasRightSkip() const override { + return true; + } + + void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const override; + void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const override; + + // ClickHandlerHost interface + void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; + + ~Gif(); + +private: + + QSize countFrameSize() const; + + enum class StateFlag { + Over = 0x01, + DeleteOver = 0x02, + }; + Q_DECLARE_FLAGS(StateFlags, StateFlag); + StateFlags _state; + friend inline StateFlags operator~(StateFlag flag) { + return ~StateFlags(flag); + } + + ClipReader *_gif = nullptr; + ClickHandlerPtr _delete; + bool gif() const { + return (!_gif || _gif == BadClipReader) ? false : true; + } + mutable QPixmap _thumb; + void prepareThumb(int32 width, int32 height, const QSize &frame) const; + + void ensureAnimation() const; + bool isRadialAnimation(uint64 ms) const; + void step_radial(uint64 ms, bool timer); + + void clipCallback(ClipReaderNotification notification); + + struct AnimationData { + AnimationData(AnimationCreator creator) + : over(false) + , radial(creator) { + } + bool over; + FloatAnimation _a_over; + RadialAnimation radial; + }; + mutable AnimationData *_animation = nullptr; + mutable FloatAnimation _a_deleteOver; + +}; + +class Photo : public ItemBase { +public: + Photo(Result *result); + // Not used anywhere currently. + //LayoutInlinePhoto(PhotoData *photo); + + void initDimensions() override; + + bool isFullLine() const override { + return false; + } + bool hasRightSkip() const override { + return true; + } + + void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const override; + void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const override; + +private: + PhotoData *getShownPhoto() const; + + QSize countFrameSize() const; + + int content_width() const; + int content_height() const; + bool content_loaded() const; + void content_forget(); + + mutable QPixmap _thumb; + mutable bool _thumbLoaded = false; + void prepareThumb(int32 width, int32 height, const QSize &frame) const; + +}; + +class Sticker : public FileBase { +public: + Sticker(Result *result); + // Not used anywhere currently. + //LayoutInlineSticker(DocumentData *document); + + void initDimensions() override; + + bool isFullLine() const override { + return false; + } + bool hasRightSkip() const override { + return false; + } + void preload() const override; + + void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const override; + void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const override; + + // ClickHandlerHost interface + void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; + +private: + + QSize getThumbSize() const; + + mutable FloatAnimation _a_over; + mutable bool _active = false; + + mutable QPixmap _thumb; + mutable bool _thumbLoaded = false; + void prepareThumb() const; + +}; + +class Video : public FileBase { +public: + Video(Result *result); + + void initDimensions() override; + + void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const override; + void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const override; + +private: + + ClickHandlerPtr _link; + + mutable QPixmap _thumb; + Text _title, _description; + QString _duration; + int32 _durationWidth; + + void prepareThumb(int32 width, int32 height) const; + +}; + +class OpenFileClickHandler : public LeftButtonClickHandler { +public: + OpenFileClickHandler(Result *result) : _result(result) { + } + +protected: + void onClickImpl() const override; + +private: + Result *_result; + +}; + +class CancelFileClickHandler : public LeftButtonClickHandler { +public: + CancelFileClickHandler(Result *result) : _result(result) { + } + +protected: + void onClickImpl() const override; + +private: + Result *_result; + +}; + +class File : public FileBase { +public: + File(Result *result); + + void initDimensions() override; + + void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const override; + void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const override; + + // ClickHandlerHost interface + void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; + +private: + + Text _title, _description; + ClickHandlerPtr _open, _cancel; + + void step_thumbOver(float64 ms, bool timer); + void step_radial(uint64 ms, bool timer); + + void ensureAnimation() const; + void checkAnimationFinished(); + + bool isRadialAnimation(uint64 ms) const { + if (!_animation || !_animation->radial.animating()) return false; + + _animation->radial.step(ms); + return _animation && _animation->radial.animating(); + } + bool isThumbAnimation(uint64 ms) const { + if (!_animation || !_animation->_a_thumbOver.animating()) return false; + + _animation->_a_thumbOver.step(ms); + return _animation && _animation->_a_thumbOver.animating(); + } + + struct AnimationData { + AnimationData(AnimationCreator thumbOverCallbacks, AnimationCreator radialCallbacks) : a_thumbOver(0, 0) + , _a_thumbOver(thumbOverCallbacks) + , radial(radialCallbacks) { + } + anim::fvalue a_thumbOver; + Animation _a_thumbOver; + + RadialAnimation radial; + }; + mutable UniquePointer _animation; + + +}; + +class Article : public ItemBase { +public: + Article(Result *result, bool withThumb); + + void initDimensions() override; + int32 resizeGetHeight(int32 width) override; + + void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const override; + void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const override; + +private: + + ClickHandlerPtr _url, _link; + + bool _withThumb; + mutable QPixmap _thumb; + Text _title, _description; + QString _letter, _urlText; + int32 _urlWidth; + + void prepareThumb(int32 width, int32 height) const; + +}; + +} // namespace internal +} // namespace Layout +} // namespace InlineBots diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp new file mode 100644 index 0000000000..5c5049e7ae --- /dev/null +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp @@ -0,0 +1,96 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "inline_bots/inline_bot_layout_item.h" + +#include "inline_bots/inline_bot_result.h" +#include "inline_bots/inline_bot_layout_internal.h" +#include "localstorage.h" +#include "mainwidget.h" + +namespace InlineBots { +namespace Layout { + +void ItemBase::setPosition(int32 position) { + _position = position; +} + +int32 ItemBase::position() const { + return _position; +} + +Result *ItemBase::getResult() const { + return _result; +} + +DocumentData *ItemBase::getDocument() const { + return _doc; +} + +PhotoData *ItemBase::getPhoto() const { + return _photo; +} + +void ItemBase::preload() const { + if (_result) { + if (_result->photo) { + _result->photo->thumb->load(); + } else if (_result->document) { + _result->document->thumb->load(); + } else if (!_result->thumb->isNull()) { + _result->thumb->load(); + } + } else if (_doc) { + _doc->thumb->load(); + } else if (_photo) { + _photo->medium->load(); + } +} + +void ItemBase::update() { + if (_position >= 0) { + Ui::repaintInlineItem(this); + } +} + +UniquePointer ItemBase::createLayout(Result *result, bool forceThumb) { + using Type = Result::Type; + + switch (result->type) { + case Type::Photo: return MakeUnique(result); break; + case Type::Audio: + case Type::File: return MakeUnique(result); break; + case Type::Video: return MakeUnique(result); break; + case Type::Sticker: return MakeUnique(result); break; + case Type::Gif: return MakeUnique(result); break; + case Type::Article: + case Type::Contact: + case Type::Venue: return MakeUnique(result, forceThumb); break; + } + return UniquePointer(); +} + +UniquePointer ItemBase::createLayoutGif(DocumentData *document) { + return MakeUnique(document, true); +} + +} // namespace Layout +} // namespace InlineBots diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.h b/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.h new file mode 100644 index 0000000000..9f1896e74b --- /dev/null +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.h @@ -0,0 +1,103 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "layout.h" +#include "structs.h" +#include "gui/text.h" + +namespace InlineBots { +class Result; + +namespace Layout { + +class PaintContext : public PaintContextBase { +public: + PaintContext(uint64 ms, bool selecting, bool paused, bool lastRow) + : PaintContextBase(ms, selecting) + , paused(paused) + , lastRow(lastRow) { + } + bool paused, lastRow; +}; + +// this type used as a flag, we dynamic_cast<> to it +class SendClickHandler : public ClickHandler { +public: + void onClick(Qt::MouseButton) const override { + } +}; + +class ItemBase : public LayoutItemBase { +public: + + ItemBase(Result *result) : _result(result) { + } + ItemBase(DocumentData *doc) : _doc(doc) { + } + // Not used anywhere currently. + //ItemBase(PhotoData *photo) : _photo(photo) { + //} + + virtual void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const = 0; + + virtual void setPosition(int32 position); + int32 position() const; + + virtual bool isFullLine() const { + return true; + } + virtual bool hasRightSkip() const { + return false; + } + + Result *getResult() const; + DocumentData *getDocument() const; + PhotoData *getPhoto() const; + + virtual void preload() const; + + void update(); + + // ClickHandlerHost interface + void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override { + update(); + } + void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override { + update(); + } + + static UniquePointer createLayout(Result *result, bool forceThumb); + static UniquePointer createLayoutGif(DocumentData *document); + +protected: + Result *_result = nullptr; + DocumentData *_doc = nullptr; + PhotoData *_photo = nullptr; + + ClickHandlerPtr _send = ClickHandlerPtr{ new SendClickHandler() }; + + int _position = 0; // < 0 means removed from layout + +}; + +} // namespace Layout +} // namespace InlineBots diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp new file mode 100644 index 0000000000..a1c2118b0c --- /dev/null +++ b/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp @@ -0,0 +1,320 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "inline_bots/inline_bot_result.h" + +#include "inline_bots/inline_bot_layout_item.h" +#include "inline_bots/inline_bot_send_data.h" +#include "mtproto/file_download.h" +#include "mainwidget.h" + +namespace InlineBots { + +namespace { + +using ResultsByLoaderMap = QMap; +NeverFreedPointer ResultsByLoader; + +void regLoader(FileLoader *loader, Result *result) { + ResultsByLoader.createIfNull([]() -> ResultsByLoaderMap* { + return new ResultsByLoaderMap(); + }); + ResultsByLoader->insert(loader, result); +} + +void unregLoader(FileLoader *loader) { + if (!ResultsByLoader) { + return; + } + ResultsByLoader->remove(loader); +} + +} // namespace + +Result *getResultFromLoader(FileLoader *loader) { + if (!ResultsByLoader) { + return nullptr; + } + return ResultsByLoader->value(loader, nullptr); +} + +namespace { + +using StringToTypeMap = QMap; +NeverFreedPointer stringToTypeMap; + +} // namespace + +Result::Result(const Creator &creator) : queryId(creator.queryId), type(creator.type) { +} + +UniquePointer Result::create(uint64 queryId, const MTPBotInlineResult &mtpData) { + stringToTypeMap.createIfNull([]() -> StringToTypeMap* { + auto result = MakeUnique(); + result->insert(qsl("photo"), Result::Type::Photo); + result->insert(qsl("video"), Result::Type::Video); + result->insert(qsl("audio"), Result::Type::Audio); + result->insert(qsl("sticker"), Result::Type::Sticker); + result->insert(qsl("file"), Result::Type::File); + result->insert(qsl("gif"), Result::Type::Gif); + result->insert(qsl("article"), Result::Type::Article); + result->insert(qsl("contact"), Result::Type::Contact); + result->insert(qsl("venue"), Result::Type::Venue); + return result.release(); + }); + auto getInlineResultType = [](const MTPBotInlineResult &inlineResult) -> Type { + QString type; + switch (inlineResult.type()) { + case mtpc_botInlineResult: type = qs(inlineResult.c_botInlineResult().vtype); break; + case mtpc_botInlineMediaResult: type = qs(inlineResult.c_botInlineMediaResult().vtype); break; + } + return stringToTypeMap->value(type, Type::Unknown); + }; + Type type = getInlineResultType(mtpData); + if (type == Type::Unknown) { + return UniquePointer(); + } + + auto result = MakeUnique(Creator{ queryId, type }); + const MTPBotInlineMessage *message = nullptr; + switch (mtpData.type()) { + case mtpc_botInlineResult: { + const MTPDbotInlineResult &r(mtpData.c_botInlineResult()); + result->id = qs(r.vid); + if (r.has_title()) result->title = qs(r.vtitle); + if (r.has_description()) result->description = qs(r.vdescription); + if (r.has_url()) result->url = qs(r.vurl); + if (r.has_thumb_url()) result->thumb_url = qs(r.vthumb_url); + if (r.has_content_type()) result->content_type = qs(r.vcontent_type); + if (r.has_content_url()) result->content_url = qs(r.vcontent_url); + if (r.has_w()) result->width = r.vw.v; + if (r.has_h()) result->height = r.vh.v; + if (r.has_duration()) result->duration = r.vduration.v; + if (!result->thumb_url.isEmpty() && (result->thumb_url.startsWith(qstr("http://"), Qt::CaseInsensitive) || result->thumb_url.startsWith(qstr("https://"), Qt::CaseInsensitive))) { + result->thumb = ImagePtr(result->thumb_url); + } + message = &r.vsend_message; + } break; + case mtpc_botInlineMediaResult: { + const MTPDbotInlineMediaResult &r(mtpData.c_botInlineMediaResult()); + result->id = qs(r.vid); + if (r.has_title()) result->title = qs(r.vtitle); + if (r.has_description()) result->description = qs(r.vdescription); + if (r.has_photo()) result->photo = App::feedPhoto(r.vphoto); + if (r.has_document()) result->document = App::feedDocument(r.vdocument); + message = &r.vsend_message; + } break; + } + bool badAttachment = (result->photo && !result->photo->access) || (result->document && !result->document->access); + + if (!message) { + return UniquePointer(); + } + + switch (message->type()) { + case mtpc_botInlineMessageMediaAuto: { + const MTPDbotInlineMessageMediaAuto &r(message->c_botInlineMessageMediaAuto()); + if (result->type == Type::Photo) { + result->sendData.reset(new internal::SendPhoto(result->photo, result->content_url, qs(r.vcaption))); + } else { + result->sendData.reset(new internal::SendFile(result->document, result->content_url, qs(r.vcaption))); + } + } break; + + case mtpc_botInlineMessageText: { + const MTPDbotInlineMessageText &r(message->c_botInlineMessageText()); + EntitiesInText entities = r.has_entities() ? entitiesFromMTP(r.ventities.c_vector().v) : EntitiesInText(); + result->sendData.reset(new internal::SendText(qs(r.vmessage), entities, r.is_no_webpage())); + } break; + + case mtpc_botInlineMessageMediaGeo: { + const MTPDbotInlineMessageMediaGeo &r(message->c_botInlineMessageMediaGeo()); + if (r.vgeo.type() == mtpc_geoPoint) { + result->sendData.reset(new internal::SendGeo(r.vgeo.c_geoPoint())); + } else { + badAttachment = true; + } + } break; + + case mtpc_botInlineMessageMediaVenue: { + const MTPDbotInlineMessageMediaVenue &r(message->c_botInlineMessageMediaVenue()); + if (r.vgeo.type() == mtpc_geoPoint) { + result->sendData.reset(new internal::SendVenue(r.vgeo.c_geoPoint(), qs(r.vvenue_id), qs(r.vprovider), qs(r.vtitle), qs(r.vaddress))); + } else { + badAttachment = true; + } + } break; + + case mtpc_botInlineMessageMediaContact: { + const MTPDbotInlineMessageMediaContact &r(message->c_botInlineMessageMediaContact()); + result->sendData.reset(new internal::SendContact(qs(r.vfirst_name), qs(r.vlast_name), qs(r.vphone_number))); + } break; + + default: { + badAttachment = true; + } break; + } + + if (badAttachment || !result->sendData || !result->sendData->isValid()) { + return UniquePointer(); + } + return result; +} + +void Result::automaticLoadGif() { + if (loaded() || type != Type::Gif || (content_type != qstr("video/mp4") && content_type != "image/gif")) return; + + if (_loader != CancelledWebFileLoader) { + // if load at least anywhere + bool loadFromCloud = !(cAutoDownloadGif() & dbiadNoPrivate) || !(cAutoDownloadGif() & dbiadNoGroups); + saveFile(QString(), loadFromCloud ? LoadFromCloudOrLocal : LoadFromLocalOnly, true); + } +} + +void Result::automaticLoadSettingsChangedGif() { + if (loaded() || _loader != CancelledWebFileLoader) return; + _loader = 0; +} + +void Result::saveFile(const QString &toFile, LoadFromCloudSetting fromCloud, bool autoLoading) { + if (loaded()) { + return; + } + + if (_loader == CancelledWebFileLoader) _loader = 0; + if (_loader) { + if (!_loader->setFileName(toFile)) { + cancelFile(); + _loader = 0; + } + } + + if (_loader) { + if (fromCloud == LoadFromCloudOrLocal) _loader->permitLoadFromCloud(); + } else { + _loader = new webFileLoader(content_url, toFile, fromCloud, autoLoading); + regLoader(_loader, this); + + _loader->connect(_loader, SIGNAL(progress(FileLoader*)), App::main(), SLOT(inlineResultLoadProgress(FileLoader*))); + _loader->connect(_loader, SIGNAL(failed(FileLoader*, bool)), App::main(), SLOT(inlineResultLoadFailed(FileLoader*, bool))); + _loader->start(); + } +} + +void Result::cancelFile() { + if (!loading()) return; + + unregLoader(_loader); + + webFileLoader *l = _loader; + _loader = CancelledWebFileLoader; + if (l) { + l->cancel(); + l->deleteLater(); + l->stop(); + } +} + +QByteArray Result::data() const { + return _data; +} + +bool Result::loading() const { + return _loader && _loader != CancelledWebFileLoader; +} + +bool Result::loaded() const { + if (loading() && _loader->done()) { + unregLoader(_loader); + if (_loader->fileType() == mtpc_storage_fileUnknown) { + _loader->deleteLater(); + _loader->stop(); + _loader = CancelledWebFileLoader; + } else { + Result *that = const_cast(this); + that->_data = _loader->bytes(); + + _loader->deleteLater(); + _loader->stop(); + _loader = 0; + } + } + return !_data.isEmpty(); +} + +bool Result::displayLoading() const { + return loading() ? (!_loader->loadingLocal() || !_loader->autoLoading()) : false; +} + +void Result::forget() { + thumb->forget(); + _data.clear(); +} + +float64 Result::progress() const { + return loading() ? _loader->currentProgress() : (loaded() ? 1 : 0); return false; +} + +bool Result::hasThumbDisplay() const { + if (!thumb->isNull()) { + return true; + } + if (type == Type::Contact) { + return true; + } + if (sendData->hasLocationCoords()) { + return true; + } + return false; +}; + +void Result::addToHistory(History *history, MTPDmessage::Flags flags, MsgId msgId, UserId fromId, MTPint mtpDate, UserId viaBotId, MsgId replyToId) const { + if (DocumentData *document = sendData->getSentDocument()) { + history->addNewDocument(msgId, flags, viaBotId, replyToId, date(mtpDate), fromId, document, sendData->getSentCaption()); + } else if (PhotoData *photo = sendData->getSentPhoto()) { + history->addNewPhoto(msgId, flags, viaBotId, replyToId, date(mtpDate), fromId, photo, sendData->getSentCaption()); + } else { + internal::SendData::SentMTPMessageFields fields = sendData->getSentMessageFields(this); + if (!fields.entities.c_vector().v.isEmpty()) { + flags |= MTPDmessage::Flag::f_entities; + } + history->addNewMessage(MTP_message(MTP_flags(flags), MTP_int(msgId), MTP_int(fromId), peerToMTP(history->peer->id), MTPnullFwdHeader, MTP_int(viaBotId), MTP_int(replyToId), mtpDate, fields.text, fields.media, MTPnullMarkup, fields.entities, MTP_int(1), MTPint()), NewMessageUnread); + } + +} + +bool Result::getLocationCoords(LocationCoords *outLocation) const { + return sendData->getLocationCoords(outLocation); +} + +QString Result::getLayoutTitle() const { + return sendData->getLayoutTitle(this); +} + +QString Result::getLayoutDescription() const { + return sendData->getLayoutDescription(this); +} + +Result::~Result() { + cancelFile(); +} + +} // namespace InlineBots diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_result.h b/Telegram/SourceFiles/inline_bots/inline_bot_result.h new file mode 100644 index 0000000000..1c21aba489 --- /dev/null +++ b/Telegram/SourceFiles/inline_bots/inline_bot_result.h @@ -0,0 +1,115 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "basic_types.h" +#include "structs.h" +#include "mtproto/core_types.h" + +class FileLoader; + +namespace InlineBots { + +namespace Layout { +class ItemBase; +} // namespace Layout + +namespace internal { +class SendData; +} // namespace internal + +class Result { +private: + // See http://stackoverflow.com/a/8147326 + struct Creator; + +public: + enum class Type { + Unknown, + Photo, + Video, + Audio, + Sticker, + File, + Gif, + Article, + Contact, + Venue, + }; + + // Constructor is public only for MakeUnique<>() to work. + // You should use create() static method instead. + explicit Result(const Creator &creator); + static UniquePointer create(uint64 queryId, const MTPBotInlineResult &mtpData); + Result(const Result &other) = delete; + Result &operator=(const Result &other) = delete; + + uint64 queryId; + QString id; + Type type; + DocumentData *document = nullptr; + PhotoData *photo = nullptr; + QString title, description, url, thumb_url; + QString content_type, content_url; + int width = 0; + int height = 0; + int duration = 0; + + ImagePtr thumb; + + void automaticLoadGif(); + void automaticLoadSettingsChangedGif(); + void saveFile(const QString &toFile, LoadFromCloudSetting fromCloud, bool autoLoading); + void cancelFile(); + + QByteArray data() const; + bool loading() const; + bool loaded() const; + bool displayLoading() const; + void forget(); + float64 progress() const; + + bool hasThumbDisplay() const; + + void addToHistory(History *history, MTPDmessage::Flags flags, MsgId msgId, UserId fromId, MTPint mtpDate, UserId viaBotId, MsgId replyToId) const; + + // interface for Layout:: usage + bool getLocationCoords(LocationCoords *outLocation) const; + QString getLayoutTitle() const; + QString getLayoutDescription() const; + + ~Result(); + +private: + struct Creator { + uint64 queryId; + Type type; + }; + + UniquePointer sendData; + + QByteArray _data; + mutable webFileLoader *_loader = nullptr; + +}; +Result *getResultFromLoader(FileLoader *loader); + +} // namespace InlineBots diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_send_data.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_send_data.cpp new file mode 100644 index 0000000000..9de2824911 --- /dev/null +++ b/Telegram/SourceFiles/inline_bots/inline_bot_send_data.cpp @@ -0,0 +1,146 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "inline_bots/inline_bot_send_data.h" + +#include "inline_bots/inline_bot_result.h" +#include "localstorage.h" + +namespace InlineBots { +namespace internal { + +QString SendData::getLayoutTitle(const Result *owner) const { + return owner->title; +} + +QString SendData::getLayoutDescription(const Result *owner) const { + return owner->description; +} + +SendData::SentMTPMessageFields SendText::getSentMessageFields(const Result*) const { + SentMTPMessageFields result; + result.text = MTP_string(_message); + result.entities = linksToMTP(_entities); + return result; +} + +SendData::SentMTPMessageFields SendGeo::getSentMessageFields(const Result*) const { + SentMTPMessageFields result; + result.media = MTP_messageMediaGeo(MTP_geoPoint(MTP_double(_location.lon), MTP_double(_location.lat))); + return result; +} + +SendData::SentMTPMessageFields SendVenue::getSentMessageFields(const Result*) const { + SentMTPMessageFields result; + result.media = MTP_messageMediaVenue(MTP_geoPoint(MTP_double(_location.lon), MTP_double(_location.lat)), MTP_string(_title), MTP_string(_address), MTP_string(_provider), MTP_string(_venueId)); + return result; +} + +SendData::SentMTPMessageFields SendContact::getSentMessageFields(const Result*) const { + SentMTPMessageFields result; + result.media = MTP_messageMediaContact(MTP_string(_phoneNumber), MTP_string(_firstName), MTP_string(_lastName), MTP_int(0)); + return result; +} + +QString SendContact::getLayoutDescription(const Result *owner) const { + return App::formatPhone(_phoneNumber) + '\n' + owner->description; +} + +SendData::SentMTPMessageFields SendPhoto::getSentMessageFields(const Result *owner) const { + SentMTPMessageFields result; + + QImage fileThumb(owner->thumb->pix().toImage()); + + QVector photoSizes; + + QPixmap thumb = (fileThumb.width() > 100 || fileThumb.height() > 100) ? QPixmap::fromImage(fileThumb.scaled(100, 100, Qt::KeepAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly) : QPixmap::fromImage(fileThumb); + ImagePtr thumbPtr = ImagePtr(thumb, "JPG"); + photoSizes.push_back(MTP_photoSize(MTP_string("s"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(thumb.width()), MTP_int(thumb.height()), MTP_int(0))); + + QSize medium = resizeKeepAspect(owner->width, owner->height, 320, 320); + photoSizes.push_back(MTP_photoSize(MTP_string("m"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(medium.width()), MTP_int(medium.height()), MTP_int(0))); + + photoSizes.push_back(MTP_photoSize(MTP_string("x"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(owner->width), MTP_int(owner->height), MTP_int(0))); + + uint64 photoId = rand_value(); + PhotoData *ph = App::photoSet(photoId, 0, 0, unixtime(), thumbPtr, ImagePtr(medium.width(), medium.height()), ImagePtr(owner->width, owner->height)); + MTPPhoto photo = MTP_photo(MTP_long(photoId), MTP_long(0), MTP_int(ph->date), MTP_vector(photoSizes)); + + result.media = MTP_messageMediaPhoto(photo, MTP_string(_caption)); + + return result; +} + +SendData::SentMTPMessageFields SendFile::getSentMessageFields(const Result *owner) const { + SentMTPMessageFields result; + + MTPPhotoSize thumbSize; + QPixmap thumb; + int32 tw = owner->thumb->width(), th = owner->thumb->height(); + if (tw > 0 && th > 0 && tw < 20 * th && th < 20 * tw && owner->thumb->loaded()) { + if (tw > th) { + if (tw > 90) { + th = th * 90 / tw; + tw = 90; + } + } else if (th > 90) { + tw = tw * 90 / th; + th = 90; + } + thumbSize = MTP_photoSize(MTP_string(""), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(tw), MTP_int(th), MTP_int(0)); + thumb = owner->thumb->pixNoCache(tw, th, ImagePixSmooth); + } else { + tw = th = 0; + thumbSize = MTP_photoSizeEmpty(MTP_string("")); + } + uint64 docId = rand_value(); + QVector attributes; + + int duration = getSentDuration(owner); + QSize dimensions = getSentDimensions(owner); + using Type = Result::Type; + if (owner->type == Type::Gif) { + attributes.push_back(MTP_documentAttributeFilename(MTP_string((owner->content_type == qstr("video/mp4") ? "animation.gif.mp4" : "animation.gif")))); + attributes.push_back(MTP_documentAttributeAnimated()); + attributes.push_back(MTP_documentAttributeVideo(MTP_int(duration), MTP_int(dimensions.width()), MTP_int(dimensions.height()))); + } else if (owner->type == Type::Video) { + attributes.push_back(MTP_documentAttributeVideo(MTP_int(duration), MTP_int(dimensions.width()), MTP_int(dimensions.height()))); + } + MTPDocument document = MTP_document(MTP_long(docId), MTP_long(0), MTP_int(unixtime()), MTP_string(owner->content_type), MTP_int(owner->data().size()), thumbSize, MTP_int(MTP::maindc()), MTP_vector(attributes)); + if (tw > 0 && th > 0) { + App::feedDocument(document, thumb); + } + Local::writeStickerImage(mediaKey(DocumentFileLocation, MTP::maindc(), docId), owner->data()); + + result.media = MTP_messageMediaDocument(document, MTP_string(_caption)); + + return result; +} + +int SendFile::getSentDuration(const Result *owner) const { + return (_document && _document->duration()) ? _document->duration() : owner->duration; +} +QSize SendFile::getSentDimensions(const Result *owner) const { + return (!_document || _document->dimensions.isEmpty()) ? QSize(owner->width, owner->height) : _document->dimensions; +} + +} // namespace internal +} // namespace InlineBots diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_send_data.h b/Telegram/SourceFiles/inline_bots/inline_bot_send_data.h new file mode 100644 index 0000000000..7c70b0a848 --- /dev/null +++ b/Telegram/SourceFiles/inline_bots/inline_bot_send_data.h @@ -0,0 +1,233 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "basic_types.h" +#include "structs.h" +#include "mtproto/core_types.h" + +namespace InlineBots { + +class Result; + +namespace internal { + +// Abstract class describing the message that will be +// sent if the user chooses this inline bot result. +// For each type of message that can be sent there will be a subclass. +class SendData { +public: + SendData() = default; + SendData(const SendData &other) = delete; + SendData &operator=(const SendData &other) = delete; + virtual ~SendData() = default; + + virtual bool isValid() const = 0; + + virtual DocumentData *getSentDocument() const { + return nullptr; + } + virtual PhotoData *getSentPhoto() const { + return nullptr; + } + virtual QString getSentCaption() const { + return QString(); + } + struct SentMTPMessageFields { + MTPString text = MTP_string(""); + MTPVector entities = MTPnullEntities; + MTPMessageMedia media = MTP_messageMediaEmpty(); + }; + virtual SentMTPMessageFields getSentMessageFields(const Result *owner) const = 0; + + virtual bool hasLocationCoords() const { + return false; + } + virtual bool getLocationCoords(LocationCoords *outLocation) const { + return false; + } + virtual QString getLayoutTitle(const Result *owner) const; + virtual QString getLayoutDescription(const Result *owner) const; + +}; + +// Plain text message. +class SendText : public SendData { +public: + SendText(const QString &message, const EntitiesInText &entities, bool noWebPage) + : _message(message) + , _entities(entities) + , _noWebPage(noWebPage) { + } + + bool isValid() const override { + return !_message.isEmpty(); + } + + SentMTPMessageFields getSentMessageFields(const Result *owner) const override; + +private: + QString _message; + EntitiesInText _entities; + bool _noWebPage; + +}; + +// Message with geo location point media. +class SendGeo : public SendData { +public: + SendGeo(const MTPDgeoPoint &point) : _location(point) { + } + + bool isValid() const override { + return true; + } + + SentMTPMessageFields getSentMessageFields(const Result *owner) const override; + + bool hasLocationCoords() const override { + return true; + } + bool getLocationCoords(LocationCoords *outLocation) const override { + t_assert(outLocation != nullptr); + *outLocation = _location; + return true; + } + +private: + LocationCoords _location; + +}; + +// Message with venue media. +class SendVenue : public SendData { +public: + SendVenue(const MTPDgeoPoint &point, const QString &venueId, + const QString &provider, const QString &title, const QString &address) + : _location(point) + , _venueId(venueId) + , _provider(provider) + , _title(title) + , _address(address) { + } + + bool isValid() const override { + return true; + } + + SentMTPMessageFields getSentMessageFields(const Result *owner) const override; + + bool hasLocationCoords() const override { + return true; + } + bool getLocationCoords(LocationCoords *outLocation) const override { + t_assert(outLocation != nullptr); + *outLocation = _location; + return true; + } + +private: + LocationCoords _location; + QString _venueId, _provider, _title, _address; + +}; + +// Message with shared contact media. +class SendContact : public SendData { +public: + SendContact(const QString &firstName, const QString &lastName, const QString &phoneNumber) + : _firstName(firstName) + , _lastName(lastName) + , _phoneNumber(phoneNumber) { + } + + bool isValid() const override { + return (!_firstName.isEmpty() || !_lastName.isEmpty()) && !_phoneNumber.isEmpty(); + } + + SentMTPMessageFields getSentMessageFields(const Result *owner) const override; + + QString getLayoutDescription(const Result *owner) const override; + +private: + QString _firstName, _lastName, _phoneNumber; + +}; + +// Message with photo. +class SendPhoto : public SendData { +public: + SendPhoto(PhotoData *photo, const QString &url, const QString &caption) + : _photo(photo) + , _url(url) + , _caption(caption) { + } + + bool isValid() const override { + return _photo || !_url.isEmpty(); + } + + PhotoData *getSentPhoto() const override { + return _photo; + } + QString getSentCaption() const override { + return _caption; + } + SentMTPMessageFields getSentMessageFields(const Result *owner) const override; + +private: + PhotoData *_photo; + QString _url, _caption; + +}; + +// Message with file. +class SendFile : public SendData { +public: + SendFile(DocumentData *document, const QString &url, const QString &caption) + : _document(document) + , _url(url) + , _caption(caption) { + } + + bool isValid() const override { + return _document || !_url.isEmpty(); + } + + DocumentData *getSentDocument() const override { + return _document; + } + QString getSentCaption() const override { + return _caption; + } + SentMTPMessageFields getSentMessageFields(const Result *owner) const override; + +private: + DocumentData *_document; + QString _url, _caption; + + int getSentDuration(const Result *owner) const; + QSize getSentDimensions(const Result *owner) const; + +}; + +} // namespace internal +} // namespace InlineBots diff --git a/Telegram/SourceFiles/layout.cpp b/Telegram/SourceFiles/layout.cpp index f9b9b9295b..48ba91258b 100644 --- a/Telegram/SourceFiles/layout.cpp +++ b/Telegram/SourceFiles/layout.cpp @@ -217,12 +217,12 @@ RoundCorners documentCorners(int32 colorIndex) { return RoundCorners(DocBlueCorners + (colorIndex & 3)); } -void LayoutMediaItem::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { +void LayoutMediaItemBase::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { App::hoveredLinkItem(active ? _parent : nullptr); Ui::repaintHistoryItem(_parent); } -void LayoutMediaItem::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) { +void LayoutMediaItemBase::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) { App::pressedLinkItem(pressed ? _parent : nullptr); Ui::repaintHistoryItem(_parent); } @@ -232,11 +232,11 @@ void LayoutRadialProgressItem::clickHandlerActiveChanged(const ClickHandlerPtr & a_iconOver.start(active ? 1 : 0); _a_iconOver.start(); } - LayoutMediaItem::clickHandlerActiveChanged(p, active); + LayoutMediaItemBase::clickHandlerActiveChanged(p, active); } void LayoutRadialProgressItem::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) { - LayoutMediaItem::clickHandlerPressedChanged(p, pressed); + LayoutMediaItemBase::clickHandlerPressedChanged(p, pressed); } void LayoutRadialProgressItem::setLinks(ClickHandlerPtr &&openl, ClickHandlerPtr &&savel, ClickHandlerPtr &&cancell) { @@ -301,8 +301,8 @@ void LayoutAbstractFileItem::setStatusSize(int32 newSize, int32 fullSize, int32 } } -LayoutOverviewDate::LayoutOverviewDate(const QDate &date, bool month) : LayoutItem() -, _date(date) +LayoutOverviewDate::LayoutOverviewDate(const QDate &date, bool month) +: _date(date) , _text(month ? langMonthFull(date) : langDayOfMonthFull(date)) { AddComponents(OverviewItemInfo::Bit()); } @@ -312,7 +312,7 @@ void LayoutOverviewDate::initDimensions() { _minh = st::linksDateMargin.top() + st::normalFont->height + st::linksDateMargin.bottom() + st::linksBorder; } -void LayoutOverviewDate::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { +void LayoutOverviewDate::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContextOverview *context) const { if (clip.intersects(QRect(0, st::linksDateMargin.top(), _width, st::normalFont->height))) { p.setPen(st::linksDateColor); p.setFont(st::semiboldFont); @@ -320,7 +320,7 @@ void LayoutOverviewDate::paint(Painter &p, const QRect &clip, uint32 selection, } } -LayoutOverviewPhoto::LayoutOverviewPhoto(PhotoData *photo, HistoryItem *parent) : LayoutMediaItem(parent) +LayoutOverviewPhoto::LayoutOverviewPhoto(PhotoData *photo, HistoryItem *parent) : LayoutMediaItemBase(parent) , _data(photo) , _link(new PhotoOpenClickHandler(photo)) , _goodLoaded(false) { @@ -341,7 +341,7 @@ int32 LayoutOverviewPhoto::resizeGetHeight(int32 width) { return _height; } -void LayoutOverviewPhoto::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { +void LayoutOverviewPhoto::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContextOverview *context) const { bool good = _data->loaded(); if (!good) { _data->medium->automaticLoad(_parent); @@ -412,7 +412,7 @@ int32 LayoutOverviewVideo::resizeGetHeight(int32 width) { return _height; } -void LayoutOverviewVideo::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { +void LayoutOverviewVideo::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContextOverview *context) const { bool selected = (selection == FullSelection), thumbLoaded = _data->thumb->loaded(); _data->automaticLoad(_parent); @@ -579,7 +579,7 @@ void LayoutOverviewVoice::initDimensions() { _minh = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom() + st::lineWidth; } -void LayoutOverviewVoice::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { +void LayoutOverviewVoice::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContextOverview *context) const { bool selected = (selection == FullSelection); _data->automaticLoad(_parent); @@ -797,7 +797,7 @@ void LayoutOverviewDocument::initDimensions() { } } -void LayoutOverviewDocument::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { +void LayoutOverviewDocument::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContextOverview *context) const { bool selected = (selection == FullSelection); _data->automaticLoad(_parent); @@ -867,10 +867,8 @@ void LayoutOverviewDocument::paint(Painter &p, const QRect &clip, uint32 selecti statustop = st::linksBorder + st::overviewFileStatusTop; datetop = st::linksBorder + st::overviewFileDateTop; - const OverviewPaintContext *pcontext = context->toOverviewPaintContext(); - t_assert(pcontext != 0); QRect border(rtlrect(nameleft, 0, _width - nameleft, st::linksBorder, _width)); - if (!pcontext->isAfterDate && clip.intersects(border)) { + if (!context->isAfterDate && clip.intersects(border)) { p.fillRect(clip.intersected(border), st::linksBorderFg); } @@ -1070,17 +1068,7 @@ bool LayoutOverviewDocument::updateStatusText() const { return showPause; } -namespace { - TextClickHandlerPtr clickHandlerFromUrl(const QString &url) { - int32 at = url.indexOf('@'), slash = url.indexOf('/'); - if ((at > 0) && (slash < 0 || slash > at)) { - return MakeShared(url); - } - return MakeShared(url); - } -} // namespace - -LayoutOverviewLink::LayoutOverviewLink(HistoryMedia *media, HistoryItem *parent) : LayoutMediaItem(parent) { +LayoutOverviewLink::LayoutOverviewLink(HistoryMedia *media, HistoryItem *parent) : LayoutMediaItemBase(parent) { AddComponents(OverviewItemInfo::Bit()); QString text = _parent->originalText(); @@ -1221,7 +1209,7 @@ int32 LayoutOverviewLink::resizeGetHeight(int32 width) { return _height; } -void LayoutOverviewLink::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { +void LayoutOverviewLink::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContextOverview *context) const { int32 left = st::dlgPhotoSize + st::dlgPhotoPadding, top = st::linksMargin.top() + st::linksBorder, w = _width - left; if (clip.intersects(rtlrect(0, top, st::dlgPhotoSize, st::dlgPhotoSize, _width))) { if (_page && _page->photo) { @@ -1292,10 +1280,8 @@ void LayoutOverviewLink::paint(Painter &p, const QRect &clip, uint32 selection, top += st::normalFont->height; } - const OverviewPaintContext *pcontext = context->toOverviewPaintContext(); - t_assert(pcontext != 0); QRect border(rtlrect(left, 0, w, st::linksBorder, _width)); - if (!pcontext->isAfterDate && clip.intersects(border)) { + if (!context->isAfterDate && clip.intersects(border)) { p.fillRect(clip.intersected(border), st::linksBorderFg); } } @@ -1334,1005 +1320,3 @@ LayoutOverviewLink::Link::Link(const QString &url, const QString &text) , width(st::normalFont->width(text)) , lnk(clickHandlerFromUrl(url)) { } - -void LayoutInlineItem::setPosition(int32 position) { - _position = position; -} - -int32 LayoutInlineItem::position() const { - return _position; -} - -InlineResult *LayoutInlineItem::getInlineResult() const { - return _result; -} - -DocumentData *LayoutInlineItem::getDocument() const { - return _doc; -} - -PhotoData *LayoutInlineItem::getPhoto() const { - return _photo; -} - -void LayoutInlineItem::preload() const { - if (_result) { - if (_result->photo) { - _result->photo->thumb->load(); - } else if (_result->document) { - _result->document->thumb->load(); - } else if (!_result->thumb->isNull()) { - _result->thumb->load(); - } - } else if (_doc) { - _doc->thumb->load(); - } else if (_photo) { - _photo->medium->load(); - } -} - -void LayoutInlineItem::update() { - if (_position >= 0) { - Ui::repaintInlineItem(this); - } -} - -LayoutInlineAbstractFile::LayoutInlineAbstractFile(InlineResult *result) : LayoutInlineItem(result) { -} - -LayoutInlineAbstractFile::LayoutInlineAbstractFile(DocumentData *document) : LayoutInlineItem(document) { -} - -int LayoutInlineAbstractFile::content_width() const { - if (DocumentData *document = getShownDocument()) { - if (document->dimensions.width() > 0) { - return document->dimensions.width(); - } - if (!document->thumb->isNull()) { - return convertScale(document->thumb->width()); - } - } else if (_result) { - return _result->width; - } - return 0; -} - -int LayoutInlineAbstractFile::content_height() const { - if (DocumentData *document = getShownDocument()) { - if (document->dimensions.height() > 0) { - return document->dimensions.height(); - } - if (!document->thumb->isNull()) { - return convertScale(document->thumb->height()); - } - } else if (_result) { - return _result->height; - } - return 0; -} - -bool LayoutInlineAbstractFile::content_loading() const { - if (DocumentData *document = getShownDocument()) { - return document->loading(); - } - return _result->loading(); -} - -bool LayoutInlineAbstractFile::content_displayLoading() const { - if (DocumentData *document = getShownDocument()) { - return document->displayLoading(); - } - return _result->displayLoading(); -} - -bool LayoutInlineAbstractFile::content_loaded() const { - if (DocumentData *document = getShownDocument()) { - return document->loaded(); - } - return _result->loaded(); -} - -float64 LayoutInlineAbstractFile::content_progress() const { - if (DocumentData *document = getShownDocument()) { - return document->progress(); - } - return _result->progress(); -} - -void LayoutInlineAbstractFile::content_automaticLoad() const { - if (DocumentData *document = getShownDocument()) { - document->automaticLoad(nullptr); - } else { - _result->automaticLoadGif(); - } -} - -void LayoutInlineAbstractFile::content_forget() { - if (DocumentData *document = getShownDocument()) { - document->forget(); - } else { - _result->forget(); - } -} - -FileLocation LayoutInlineAbstractFile::content_location() const { - if (DocumentData *document = getShownDocument()) { - return document->location(); - } - return FileLocation(); -} - -QByteArray LayoutInlineAbstractFile::content_data() const { - if (DocumentData *document = getShownDocument()) { - return document->data(); - } - return _result->data(); -} - -ImagePtr LayoutInlineAbstractFile::content_thumb() const { - if (DocumentData *document = getShownDocument()) { - if (!document->thumb->isNull()) { - return document->thumb; - } - } - if (_result->photo && !_result->photo->thumb->isNull()) { - return _result->photo->thumb; - } - return _result->thumb; -} - -int LayoutInlineAbstractFile::content_duration() const { - if (_result->document) { - if (_result->document->duration() > 0) { - return _result->document->duration(); - } else if (SongData *song = _result->document->song()) { - if (song->duration) { - return song->duration; - } - } - } - return _result->duration; -} - -LayoutInlineGif::LayoutInlineGif(InlineResult *result) : LayoutInlineAbstractFile(result) { -} - -LayoutInlineGif::LayoutInlineGif(DocumentData *document, bool hasDeleteButton) : LayoutInlineAbstractFile(document) -, _delete(hasDeleteButton ? new DeleteSavedGifClickHandler(document) : nullptr) { -} - -void LayoutInlineGif::initDimensions() { - int32 w = content_width(), h = content_height(); - if (w <= 0 || h <= 0) { - _maxw = 0; - } else { - w = w * st::inlineMediaHeight / h; - _maxw = qMax(w, int32(st::inlineResultsMinWidth)); - } - _minh = st::inlineMediaHeight + st::inlineResultsSkip; -} - -void LayoutInlineGif::setPosition(int32 position) { - LayoutInlineItem::setPosition(position); - if (_position < 0) { - if (gif()) delete _gif; - _gif = 0; - } -} - -void DeleteSavedGifClickHandler::onClickImpl() const { - int32 index = cSavedGifs().indexOf(_data); - if (index >= 0) { - cRefSavedGifs().remove(index); - Local::writeSavedGifs(); - - MTP::send(MTPmessages_SaveGif(MTP_inputDocument(MTP_long(_data->id), MTP_long(_data->access)), MTP_bool(true))); - } - if (App::main()) emit App::main()->savedGifsUpdated(); -} - -void LayoutInlineGif::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { - content_automaticLoad(); - - bool loaded = content_loaded(), loading = content_loading(), displayLoading = content_displayLoading(); - if (loaded && !gif() && _gif != BadClipReader) { - LayoutInlineGif *that = const_cast(this); - that->_gif = new ClipReader(content_location(), content_data(), func(that, &LayoutInlineGif::clipCallback)); - if (gif()) _gif->setAutoplay(); - } - - bool animating = (gif() && _gif->started()); - if (displayLoading) { - ensureAnimation(); - if (!_animation->radial.animating()) { - _animation->radial.start(content_progress()); - } - } - bool radial = isRadialAnimation(context->ms); - - int32 height = st::inlineMediaHeight; - QSize frame = countFrameSize(); - - QRect r(0, 0, _width, height); - if (animating) { - if (!_thumb.isNull()) _thumb = QPixmap(); - const InlinePaintContext *ctx = context->toInlinePaintContext(); - t_assert(ctx); - p.drawPixmap(r.topLeft(), _gif->current(frame.width(), frame.height(), _width, height, ctx->paused ? 0 : context->ms)); - } else { - prepareThumb(_width, height, frame); - if (_thumb.isNull()) { - p.fillRect(r, st::overviewPhotoBg); - } else { - p.drawPixmap(r.topLeft(), _thumb); - } - } - - if (radial || (!_gif && !loaded && !loading) || (_gif == BadClipReader)) { - float64 radialOpacity = (radial && loaded) ? _animation->radial.opacity() : 1; - if (_animation && _animation->_a_over.animating(context->ms)) { - float64 over = _animation->_a_over.current(); - p.setOpacity((st::msgDateImgBg->c.alphaF() * (1 - over)) + (st::msgDateImgBgOver->c.alphaF() * over)); - p.fillRect(r, st::black); - } else { - p.fillRect(r, (_state & StateFlag::Over) ? st::msgDateImgBgOver : st::msgDateImgBg); - } - p.setOpacity(radialOpacity * p.opacity()); - - p.setOpacity(radialOpacity); - style::sprite icon; - if (loaded && !radial) { - icon = st::msgFileInPlay; - } else if (radial || loading) { - icon = st::msgFileInCancel; - } else { - icon = st::msgFileInDownload; - } - QRect inner((_width - st::msgFileSize) / 2, (height - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize); - p.drawSpriteCenter(inner, icon); - 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, st::msgInBg); - } - } - - if (_delete && (_state & StateFlag::Over)) { - float64 deleteOver = _a_deleteOver.current(context->ms, (_state & StateFlag::DeleteOver) ? 1 : 0); - QPoint deletePos = QPoint(_width - st::stickerPanDelete.pxWidth(), 0); - p.setOpacity(deleteOver + (1 - deleteOver) * st::stickerPanDeleteOpacity); - p.drawSpriteLeft(deletePos, _width, st::stickerPanDelete); - p.setOpacity(1); - } -} - -void LayoutInlineGif::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const { - if (x >= 0 && x < _width && y >= 0 && y < st::inlineMediaHeight) { - if (_delete && (rtl() ? _width - x : x) >= _width - st::stickerPanDelete.pxWidth() && y < st::stickerPanDelete.pxHeight()) { - link = _delete; - } else { - link = _send; - } - } -} - -void LayoutInlineGif::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { - if (!p) return; - - if (_delete && p == _delete) { - bool wasactive = (_state & StateFlag::DeleteOver); - if (active != wasactive) { - float64 from = active ? 0 : 1, to = active ? 1 : 0; - EnsureAnimation(_a_deleteOver, from, func(this, &LayoutInlineGif::update)); - _a_deleteOver.start(to, st::stickersRowDuration); - if (active) { - _state |= StateFlag::DeleteOver; - } else { - _state &= ~StateFlag::DeleteOver; - } - } - } - if (p == _delete || p == _send) { - bool wasactive = (_state & StateFlag::Over); - if (active != wasactive) { - if (!content_loaded()) { - ensureAnimation(); - float64 from = active ? 0 : 1, to = active ? 1 : 0; - EnsureAnimation(_animation->_a_over, from, func(this, &LayoutInlineGif::update)); - _animation->_a_over.start(to, st::stickersRowDuration); - } - if (active) { - _state |= StateFlag::Over; - } else { - _state &= ~StateFlag::Over; - } - } - } - LayoutInlineItem::clickHandlerActiveChanged(p, active); -} - -QSize LayoutInlineGif::countFrameSize() const { - bool animating = (gif() && _gif->ready()); - int32 framew = animating ? _gif->width() : content_width(), frameh = animating ? _gif->height() : content_height(), height = st::inlineMediaHeight; - if (framew * height > frameh * _width) { - if (framew < st::maxStickerSize || frameh > height) { - if (frameh > height || (framew * height / frameh) <= st::maxStickerSize) { - framew = framew * height / frameh; - frameh = height; - } else { - frameh = int32(frameh * st::maxStickerSize) / framew; - framew = st::maxStickerSize; - } - } - } else { - if (frameh < st::maxStickerSize || framew > _width) { - if (framew > _width || (frameh * _width / framew) <= st::maxStickerSize) { - frameh = frameh * _width / framew; - framew = _width; - } else { - framew = int32(framew * st::maxStickerSize) / frameh; - frameh = st::maxStickerSize; - } - } - } - return QSize(framew, frameh); -} - -LayoutInlineGif::~LayoutInlineGif() { - if (gif()) deleteAndMark(_gif); - deleteAndMark(_animation); -} - -void LayoutInlineGif::prepareThumb(int32 width, int32 height, const QSize &frame) const { - if (DocumentData *document = getShownDocument()) { - if (!document->thumb->isNull()) { - if (document->thumb->loaded()) { - if (_thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) { - _thumb = document->thumb->pixNoCache(frame.width() * cIntRetinaFactor(), frame.height() * cIntRetinaFactor(), ImagePixSmooth, width, height); - } - } else { - document->thumb->load(); - } - } - } else { - if (!_result->thumb_url.isEmpty()) { - if (_result->thumb->loaded()) { - if (_thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) { - _thumb = _result->thumb->pixNoCache(frame.width() * cIntRetinaFactor(), frame.height() * cIntRetinaFactor(), ImagePixSmooth, width, height); - } - } else { - _result->thumb->load(); - } - } - } -} - -void LayoutInlineGif::ensureAnimation() const { - if (!_animation) { - _animation = new AnimationData(animation(const_cast(this), &LayoutInlineGif::step_radial)); - } -} - -bool LayoutInlineGif::isRadialAnimation(uint64 ms) const { - if (!_animation || !_animation->radial.animating()) return false; - - _animation->radial.step(ms); - return _animation && _animation->radial.animating(); -} - -void LayoutInlineGif::step_radial(uint64 ms, bool timer) { - if (timer) { - update(); - } else { - _animation->radial.update(content_progress(), !content_loading() || content_loaded(), ms); - if (!_animation->radial.animating() && content_loaded()) { - delete _animation; - _animation = 0; - } - } -} - -void LayoutInlineGif::clipCallback(ClipReaderNotification notification) { - switch (notification) { - case ClipReaderReinit: { - if (gif()) { - if (_gif->state() == ClipError) { - delete _gif; - _gif = BadClipReader; - content_forget(); - } else if (_gif->ready() && !_gif->started()) { - int32 height = st::inlineMediaHeight; - QSize frame = countFrameSize(); - _gif->start(frame.width(), frame.height(), _width, height, false); - } else if (_gif->paused() && !Ui::isInlineItemVisible(this)) { - delete _gif; - _gif = 0; - content_forget(); - } - } - - update(); - } break; - - case ClipReaderRepaint: { - if (gif() && !_gif->currentDisplayed()) { - update(); - } - } break; - } -} - -LayoutInlineSticker::LayoutInlineSticker(InlineResult *result) : LayoutInlineAbstractFile(result) { -} - -void LayoutInlineSticker::initDimensions() { - _maxw = st::stickerPanSize.width(); - _minh = st::stickerPanSize.height(); -} - -void LayoutInlineSticker::preload() const { - if (DocumentData *document = getShownDocument()) { - bool goodThumb = !document->thumb->isNull() && ((document->thumb->width() >= 128) || (document->thumb->height() >= 128)); - if (goodThumb) { - document->thumb->load(); - } else { - document->checkSticker(); - } - } else if (!_result->thumb->isNull()) { - _result->thumb->load(); - } -} - -void LayoutInlineSticker::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { - bool loaded = content_loaded(); - - float64 over = _a_over.isNull() ? (_active ? 1 : 0) : _a_over.current(); - if (over > 0) { - p.setOpacity(over); - App::roundRect(p, QRect(QPoint(0, 0), st::stickerPanSize), st::emojiPanHover, StickerHoverCorners); - p.setOpacity(1); - } - - prepareThumb(); - if (!_thumb.isNull()) { - int w = _thumb.width() / cIntRetinaFactor(), h = _thumb.height() / cIntRetinaFactor(); - QPoint pos = QPoint((st::stickerPanSize.width() - w) / 2, (st::stickerPanSize.height() - h) / 2); - p.drawPixmap(pos, _thumb); - } -} - -void LayoutInlineSticker::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const { - if (x >= 0 && x < _width && y >= 0 && y < st::inlineMediaHeight) { - link = _send; - } -} - -void LayoutInlineSticker::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { - if (!p) return; - - if (p == _send) { - if (active != _active) { - _active = active; - - float64 from = _active ? 0 : 1, to = _active ? 1 : 0; - EnsureAnimation(_a_over, from, func(this, &LayoutInlineSticker::update)); - _a_over.start(to, st::stickersRowDuration); - } - } - LayoutInlineItem::clickHandlerActiveChanged(p, active); -} - -QSize LayoutInlineSticker::getThumbSize() const { - int width = qMax(content_width(), 1), height = qMax(content_height(), 1); - float64 coefw = (st::stickerPanSize.width() - st::msgRadius * 2) / float64(width); - float64 coefh = (st::stickerPanSize.height() - st::msgRadius * 2) / float64(height); - float64 coef = qMin(qMin(coefw, coefh), 1.); - int w = qRound(coef * content_width()), h = qRound(coef * content_height()); - return QSize(qMax(w, 1), qMax(h, 1)); -} - -void LayoutInlineSticker::prepareThumb() const { - if (DocumentData *document = getShownDocument()) { - bool goodThumb = !document->thumb->isNull() && ((document->thumb->width() >= 128) || (document->thumb->height() >= 128)); - if (goodThumb) { - document->thumb->load(); - } else { - document->checkSticker(); - } - - ImagePtr sticker = goodThumb ? document->thumb : document->sticker()->img; - if (!_thumbLoaded && sticker->loaded()) { - QSize thumbSize = getThumbSize(); - _thumb = sticker->pix(thumbSize.width(), thumbSize.height()); - _thumbLoaded = true; - } - } else { - if (_result->thumb->loaded()) { - if (!_thumbLoaded) { - QSize thumbSize = getThumbSize(); - _thumb = _result->thumb->pix(thumbSize.width(), thumbSize.height()); - _thumbLoaded = true; - } - } else { - _result->thumb->load(); - } - } -} - -LayoutInlinePhoto::LayoutInlinePhoto(InlineResult *result) : LayoutInlineItem(result) { -} - -void LayoutInlinePhoto::initDimensions() { - int32 w = content_width(), h = content_height(); - if (w <= 0 || h <= 0) { - _maxw = 0; - } else { - w = w * st::inlineMediaHeight / h; - _maxw = qMax(w, int32(st::inlineResultsMinWidth)); - } - _minh = st::inlineMediaHeight + st::inlineResultsSkip; -} - -void LayoutInlinePhoto::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { - bool loaded = content_loaded(); - - int32 height = st::inlineMediaHeight; - QSize frame = countFrameSize(); - - QRect r(0, 0, _width, height); - - prepareThumb(_width, height, frame); - if (_thumb.isNull()) { - p.fillRect(r, st::overviewPhotoBg); - } else { - p.drawPixmap(r.topLeft(), _thumb); - } -} - -void LayoutInlinePhoto::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const { - if (x >= 0 && x < _width && y >= 0 && y < st::inlineMediaHeight) { - link = _send; - } -} - -QSize LayoutInlinePhoto::countFrameSize() const { - int32 framew = content_width(), frameh = content_height(), height = st::inlineMediaHeight; - if (framew * height > frameh * _width) { - if (framew < st::maxStickerSize || frameh > height) { - if (frameh > height || (framew * height / frameh) <= st::maxStickerSize) { - framew = framew * height / frameh; - frameh = height; - } else { - frameh = int32(frameh * st::maxStickerSize) / framew; - framew = st::maxStickerSize; - } - } - } else { - if (frameh < st::maxStickerSize || framew > _width) { - if (framew > _width || (frameh * _width / framew) <= st::maxStickerSize) { - frameh = frameh * _width / framew; - framew = _width; - } else { - framew = int32(framew * st::maxStickerSize) / frameh; - frameh = st::maxStickerSize; - } - } - } - return QSize(framew, frameh); -} - -void LayoutInlinePhoto::prepareThumb(int32 width, int32 height, const QSize &frame) const { - if (PhotoData *photo = getShownPhoto()) { - if (photo->medium->loaded()) { - if (!_thumbLoaded || _thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) { - _thumb = photo->medium->pixNoCache(frame.width() * cIntRetinaFactor(), frame.height() * cIntRetinaFactor(), ImagePixSmooth, width, height); - } - _thumbLoaded = true; - } else { - if (photo->thumb->loaded()) { - if (_thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) { - _thumb = photo->thumb->pixNoCache(frame.width() * cIntRetinaFactor(), frame.height() * cIntRetinaFactor(), ImagePixSmooth, width, height); - } - } - photo->medium->load(); - } - } else { - if (_result->thumb->loaded()) { - if (_thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) { - _thumb = _result->thumb->pixNoCache(frame.width() * cIntRetinaFactor(), frame.height() * cIntRetinaFactor(), ImagePixSmooth, width, height); - } - } else { - _result->thumb->load(); - } - } -} - -int LayoutInlinePhoto::content_width() const { - if (PhotoData *photo = getShownPhoto()) { - return photo->full->width(); - } else if (_result) { - return _result->width; - } - return 0; -} - -int LayoutInlinePhoto::content_height() const { - if (PhotoData *photo = getShownPhoto()) { - return photo->full->height(); - } else if (_result) { - return _result->height; - } - return 0; -} - -bool LayoutInlinePhoto::content_loaded() const { - if (PhotoData *photo = getShownPhoto()) { - return photo->loaded(); - } - return _result->loaded(); -} - -void LayoutInlinePhoto::content_forget() { - if (PhotoData *photo = getShownPhoto()) { - photo->forget(); - } else { - _result->forget(); - } -} - -LayoutInlineVideo::LayoutInlineVideo(InlineResult *result) : LayoutInlineAbstractFile(result) -, _title(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) -, _description(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) { - if (!result->content_url.isEmpty()) { - _link = clickHandlerFromUrl(result->content_url); - } - if (int duration = content_duration()) { - _duration = formatDurationText(duration); - } -} - -void LayoutInlineVideo::initDimensions() { - bool withThumb = !content_thumb()->isNull(); - - _maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft; - int32 textWidth = _maxw - (withThumb ? (st::inlineThumbSize + st::inlineThumbSkip) : 0); - TextParseOptions titleOpts = { 0, _maxw, 2 * st::semiboldFont->height, Qt::LayoutDirectionAuto }; - _title.setText(st::semiboldFont, textOneLine(_result->sendData->getLayoutTitle(_result)), titleOpts); - int32 titleHeight = qMin(_title.countHeight(_maxw), 2 * st::semiboldFont->height); - - int32 descriptionLines = withThumb ? (titleHeight > st::semiboldFont->height ? 1 : 2) : 3; - - TextParseOptions descriptionOpts = { TextParseMultiline, _maxw, descriptionLines * st::normalFont->height, Qt::LayoutDirectionAuto }; - _description.setText(st::normalFont, _result->sendData->getLayoutDescription(_result), descriptionOpts); - int32 descriptionHeight = qMin(_description.countHeight(_maxw), descriptionLines * st::normalFont->height); - - _minh = st::inlineThumbSize; - _minh += st::inlineRowMargin * 2 + st::inlineRowBorder; -} - -void LayoutInlineVideo::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { - int32 left = st::inlineThumbSize + st::inlineThumbSkip; - - bool withThumb = !content_thumb()->isNull(); - if (withThumb) { - prepareThumb(st::inlineThumbSize, st::inlineThumbSize); - if (_thumb.isNull()) { - p.fillRect(rtlrect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize, _width), st::overviewPhotoBg); - } else { - p.drawPixmapLeft(0, st::inlineRowMargin, _width, _thumb); - } - } else { - p.fillRect(rtlrect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize, _width), st::black); - } - - if (!_duration.isEmpty()) { - int32 durationTop = st::inlineRowMargin + st::inlineThumbSize - st::normalFont->height - st::inlineDurationMargin; - p.fillRect(rtlrect(0, durationTop - st::inlineDurationMargin, st::inlineThumbSize, st::normalFont->height + 2 * st::inlineDurationMargin, _width), st::msgDateImgBg); - p.setPen(st::white); - p.setFont(st::normalFont); - p.drawTextRight(_width - st::inlineThumbSize + st::inlineDurationMargin, durationTop, _width, _duration); - } - - p.setPen(st::black); - _title.drawLeftElided(p, left, st::inlineRowMargin, _width - left, _width, 2); - int32 titleHeight = qMin(_title.countHeight(_width - left), st::semiboldFont->height * 2); - - p.setPen(st::inlineDescriptionFg); - int32 descriptionLines = withThumb ? (titleHeight > st::semiboldFont->height ? 1 : 2) : 3; - _description.drawLeftElided(p, left, st::inlineRowMargin + titleHeight, _width - left, _width, descriptionLines); - - const InlinePaintContext *ctx = context->toInlinePaintContext(); - t_assert(ctx != nullptr); - if (!ctx->lastRow) { - p.fillRect(rtlrect(left, _height - st::inlineRowBorder, _width - left, st::inlineRowBorder, _width), st::inlineRowBorderFg); - } -} - -void LayoutInlineVideo::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const { - if (x >= 0 && x < st::inlineThumbSize && y >= st::inlineRowMargin && y < st::inlineRowMargin + st::inlineThumbSize) { - link = _link; - return; - } - if (x >= st::inlineThumbSize + st::inlineThumbSkip && x < _width && y >= 0 && y < _height) { - link = _send; - return; - } -} - -void LayoutInlineVideo::prepareThumb(int32 width, int32 height) const { - ImagePtr thumb = content_thumb(); - if (thumb->loaded()) { - if (_thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) { - int32 w = qMax(convertScale(thumb->width()), 1), h = qMax(convertScale(thumb->height()), 1); - if (w * height > h * width) { - if (height < h) { - w = w * height / h; - h = height; - } - } else { - if (width < w) { - h = h * width / w; - w = width; - } - } - _thumb = thumb->pixNoCache(w * cIntRetinaFactor(), h * cIntRetinaFactor(), ImagePixSmooth, width, height); - } - } else { - thumb->load(); - } -} - -LayoutInlineFile::LayoutInlineFile(InlineResult *result) : LayoutInlineAbstractFile(result) -, _title(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::msgFileSize - st::inlineThumbSkip) -, _description(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::msgFileSize - st::inlineThumbSkip) { -} - -void LayoutInlineFile::initDimensions() { - _maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft; - int32 textWidth = _maxw - (st::msgFileSize + st::inlineThumbSkip); - TextParseOptions titleOpts = { 0, _maxw, 2 * st::semiboldFont->height, Qt::LayoutDirectionAuto }; - _title.setText(st::semiboldFont, textOneLine(_result->sendData->getLayoutTitle(_result)), titleOpts); - int32 titleHeight = qMin(_title.countHeight(_maxw), 2 * st::semiboldFont->height); - - int32 descriptionLines = 1; - - TextParseOptions descriptionOpts = { TextParseMultiline, _maxw, descriptionLines * st::normalFont->height, Qt::LayoutDirectionAuto }; - _description.setText(st::normalFont, _result->sendData->getLayoutDescription(_result), descriptionOpts); - int32 descriptionHeight = qMin(_description.countHeight(_maxw), descriptionLines * st::normalFont->height); - - _minh = st::msgFileSize; - _minh += st::inlineRowMargin * 2 + st::inlineRowBorder; -} - -void LayoutInlineFile::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { - int32 left = st::msgFileSize + st::inlineThumbSkip; - - QRect iconCircle = rtlrect(0, st::inlineRowMargin, st::msgFileSize, st::msgFileSize, _width); - p.setPen(Qt::NoPen); - //if (isThumbAnimation(ms)) { - // float64 over = _animation->a_thumbOver.current(); - // p.setBrush(style::interpolate(outbg ? st::msgFileOutBg : st::msgFileInBg, outbg ? st::msgFileOutBgOver : st::msgFileInBgOver, over)); - //} else { - bool over = ClickHandler::showAsActive(_send/*_data->loading() ? _cancell : _savel*/); - p.setBrush((over ? st::msgFileInBgOver : st::msgFileInBg)); - //} - - p.setRenderHint(QPainter::HighQualityAntialiasing); - p.drawEllipse(iconCircle); - p.setRenderHint(QPainter::HighQualityAntialiasing, false); - - style::sprite icon; - if (bool showPause = false) { - icon = st::msgFileInPause; - //} else if (radial || _data->loading()) { - // icon = st::msgFileInCancel; - //} else if (loaded) { - // if (_data->song() || _data->voice()) { - // icon = outbg ? (selected ? st::msgFileOutPlaySelected : st::msgFileOutPlay) : (selected ? st::msgFileInPlaySelected : st::msgFileInPlay); - // } else if (_data->isImage()) { - // icon = outbg ? (selected ? st::msgFileOutImageSelected : st::msgFileOutImage) : (selected ? st::msgFileInImageSelected : st::msgFileInImage); - // } else { - // icon = outbg ? (selected ? st::msgFileOutFileSelected : st::msgFileOutFile) : (selected ? st::msgFileInFileSelected : st::msgFileInFile); - // } - } else { - icon = st::msgFileInDownload; - } - p.drawSpriteCenter(iconCircle, icon); - - p.setPen(st::black); - _title.drawLeftElided(p, left, st::inlineRowMargin, _width - left, _width, 2); - int32 titleHeight = qMin(_title.countHeight(_width - left), st::semiboldFont->height * 2); - - p.setPen(st::inlineDescriptionFg); - int32 descriptionLines = 1; - _description.drawLeftElided(p, left, st::inlineRowMargin + titleHeight, _width - left, _width, descriptionLines); - - const InlinePaintContext *ctx = context->toInlinePaintContext(); - t_assert(ctx != nullptr); - if (!ctx->lastRow) { - p.fillRect(rtlrect(left, _height - st::inlineRowBorder, _width - left, st::inlineRowBorder, _width), st::inlineRowBorderFg); - } -} - -void LayoutInlineFile::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const { - if (x >= 0 && x < st::msgFileSize && y >= st::inlineRowMargin && y < st::inlineRowMargin + st::msgFileSize) { - return; - } - if (x >= st::msgFileSize + st::inlineThumbSkip && x < _width && y >= 0 && y < _height) { - link = _send; - return; - } -} - -LayoutInlineArticle::LayoutInlineArticle(InlineResult *result, bool withThumb) : LayoutInlineItem(result) -, _withThumb(withThumb) -, _title(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) -, _description(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) { - if (!result->url.isEmpty()) { - _url = clickHandlerFromUrl(result->url); - } - if (!result->content_url.isEmpty()) { - _link = clickHandlerFromUrl(result->content_url); - } else { - LocationCoords location; - if (result->sendData->getLocationCoords(&location)) { - _link.reset(new LocationClickHandler(location)); - int32 w = st::inlineThumbSize, h = st::inlineThumbSize; - int32 zoom = 13, scale = 1; - if (cScale() == dbisTwo || cRetina()) { - scale = 2; - w /= 2; - h /= 2; - } - QString coords = qsl("%1,%2").arg(location.lat).arg(location.lon); - QString url = qsl("https://maps.googleapis.com/maps/api/staticmap?center=") + coords + qsl("&zoom=%1&size=%2x%3&maptype=roadmap&scale=%4&markers=color:red|size:big|").arg(zoom).arg(w).arg(h).arg(scale) + coords + qsl("&sensor=false"); - result->thumb = ImagePtr(url); - } - } - QVector parts = _result->url.splitRef('/'); - if (!parts.isEmpty()) { - QStringRef domain = parts.at(0); - if (parts.size() > 2 && domain.endsWith(':') && parts.at(1).isEmpty()) { // http:// and others - domain = parts.at(2); - } - - parts = domain.split('@').back().split('.'); - if (parts.size() > 1) { - _letter = parts.at(parts.size() - 2).at(0).toUpper(); - } - } - if (_letter.isEmpty() && !_result->title.isEmpty()) { - _letter = _result->title.at(0).toUpper(); - } -} - -void LayoutInlineArticle::initDimensions() { - _maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft; - int32 textWidth = _maxw - (_withThumb ? (st::inlineThumbSize + st::inlineThumbSkip) : 0); - TextParseOptions titleOpts = { 0, _maxw, 2 * st::semiboldFont->height, Qt::LayoutDirectionAuto }; - _title.setText(st::semiboldFont, textOneLine(_result->title), titleOpts); - int32 titleHeight = qMin(_title.countHeight(_maxw), 2 * st::semiboldFont->height); - - int32 descriptionLines = (_withThumb || _url) ? 2 : 3; - QString description = _result->sendData->getLayoutDescription(_result); - TextParseOptions descriptionOpts = { TextParseMultiline, _maxw, descriptionLines * st::normalFont->height, Qt::LayoutDirectionAuto }; - _description.setText(st::normalFont, description, descriptionOpts); - int32 descriptionHeight = qMin(_description.countHeight(_maxw), descriptionLines * st::normalFont->height); - - _minh = titleHeight + descriptionHeight; - if (_url) _minh += st::normalFont->height; - if (_withThumb) _minh = qMax(_minh, int32(st::inlineThumbSize)); - _minh += st::inlineRowMargin * 2 + st::inlineRowBorder; -} - -int32 LayoutInlineArticle::resizeGetHeight(int32 width) { - _width = qMin(width, _maxw); - if (_url) { - _urlText = _result->url; - _urlWidth = st::normalFont->width(_urlText); - if (_urlWidth > _width - st::inlineThumbSize - st::inlineThumbSkip) { - _urlText = st::normalFont->elided(_result->url, _width - st::inlineThumbSize - st::inlineThumbSkip); - _urlWidth = st::normalFont->width(_urlText); - } - } - _height = _minh; - return _height; -} - -void LayoutInlineArticle::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { - int32 left = st::emojiPanHeaderLeft - st::inlineResultsLeft; - if (_withThumb) { - left = st::inlineThumbSize + st::inlineThumbSkip; - prepareThumb(st::inlineThumbSize, st::inlineThumbSize); - QRect rthumb(rtlrect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize, _width)); - if (_thumb.isNull()) { - if (_result->thumb->isNull() && !_letter.isEmpty()) { - int32 index = (_letter.at(0).unicode() % 4); - style::color colors[] = { st::msgFileRedColor, st::msgFileYellowColor, st::msgFileGreenColor, st::msgFileBlueColor }; - - p.fillRect(rthumb, colors[index]); - if (!_letter.isEmpty()) { - p.setFont(st::linksLetterFont); - p.setPen(st::white); - p.drawText(rthumb, _letter, style::al_center); - } - } else { - p.fillRect(rthumb, st::overviewPhotoBg); - } - } else { - p.drawPixmapLeft(rthumb.topLeft(), _width, _thumb); - } - } - - p.setPen(st::black); - _title.drawLeftElided(p, left, st::inlineRowMargin, _width - left, _width, 2); - int32 titleHeight = qMin(_title.countHeight(_width - left), st::semiboldFont->height * 2); - - p.setPen(st::inlineDescriptionFg); - int32 descriptionLines = (_withThumb || _url) ? 2 : 3; - _description.drawLeftElided(p, left, st::inlineRowMargin + titleHeight, _width - left, _width, descriptionLines); - - if (_url) { - int32 descriptionHeight = qMin(_description.countHeight(_width - left), st::normalFont->height * descriptionLines); - p.drawTextLeft(left, st::inlineRowMargin + titleHeight + descriptionHeight, _width, _urlText, _urlWidth); - } - - const InlinePaintContext *ctx = context->toInlinePaintContext(); - t_assert(ctx); - if (!ctx->lastRow) { - p.fillRect(rtlrect(left, _height - st::inlineRowBorder, _width - left, st::inlineRowBorder, _width), st::inlineRowBorderFg); - } -} - -void LayoutInlineArticle::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const { - int32 left = _withThumb ? (st::inlineThumbSize + st::inlineThumbSkip) : 0; - if (x >= 0 && x < left - st::inlineThumbSkip && y >= st::inlineRowMargin && y < st::inlineRowMargin + st::inlineThumbSize) { - link = _link; - return; - } - if (x >= left && x < _width && y >= 0 && y < _height) { - if (_url) { - int32 left = st::inlineThumbSize + st::inlineThumbSkip; - int32 titleHeight = qMin(_title.countHeight(_width - left), st::semiboldFont->height * 2); - int32 descriptionLines = 2; - int32 descriptionHeight = qMin(_description.countHeight(_width - left), st::normalFont->height * descriptionLines); - if (rtlrect(left, st::inlineRowMargin + titleHeight + descriptionHeight, _urlWidth, st::normalFont->height, _width).contains(x, y)) { - link = _url; - return; - } - } - link = _send; - return; - } -} - -void LayoutInlineArticle::prepareThumb(int32 width, int32 height) const { - if (_result->thumb->isNull()) { - if (_result->type == InlineResult::Type::Contact) { - if (_thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) { - _thumb = userDefPhoto(qHash(_result->id) % UserColorsCount)->pixCircled(width, height); - } - } - return; - } - - if (_result->thumb->loaded()) { - if (_thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) { - int32 w = qMax(convertScale(_result->thumb->width()), 1), h = qMax(convertScale(_result->thumb->height()), 1); - if (w * height > h * width) { - if (height < h) { - w = w * height / h; - h = height; - } - } else { - if (width < w) { - h = h * width / w; - w = width; - } - } - _thumb = _result->thumb->pixNoCache(w * cIntRetinaFactor(), h * cIntRetinaFactor(), ImagePixSmooth, width, height); - } - } else { - _result->thumb->load(); - } -} diff --git a/Telegram/SourceFiles/layout.h b/Telegram/SourceFiles/layout.h index 047478824a..4e2284960e 100644 --- a/Telegram/SourceFiles/layout.h +++ b/Telegram/SourceFiles/layout.h @@ -82,31 +82,22 @@ style::color documentSelectedColor(int32 colorIndex); style::sprite documentCorner(int32 colorIndex); RoundCorners documentCorners(int32 colorIndex); -class OverviewPaintContext; -class InlinePaintContext; -class PaintContext { +class PaintContextBase { public: - PaintContext(uint64 ms, bool selecting) : ms(ms), selecting(selecting) { + PaintContextBase(uint64 ms, bool selecting) : ms(ms), selecting(selecting) { } uint64 ms; bool selecting; - virtual const OverviewPaintContext *toOverviewPaintContext() const { - return nullptr; - } - virtual const InlinePaintContext *toInlinePaintContext() const { - return nullptr; - } - }; -class LayoutMediaItem; -class LayoutItem : public Composer, public ClickHandlerHost { +class LayoutMediaItemBase; +class LayoutItemBase : public Composer, public ClickHandlerHost { public: - LayoutItem() { + LayoutItemBase() { } - LayoutItem &operator=(const LayoutItem &) = delete; + LayoutItemBase &operator=(const LayoutItemBase &) = delete; int32 maxWidth() const { return _maxw; @@ -121,7 +112,6 @@ public: return _height; } - virtual void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const = 0; virtual void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const { link.clear(); cursor = HistoryDefaultCursorState; @@ -143,13 +133,34 @@ public: return (x >= 0 && y >= 0 && x < width() && y < height()); } - virtual ~LayoutItem() { + virtual ~LayoutItemBase() { } - virtual LayoutMediaItem *toLayoutMediaItem() { +protected: + int _width = 0; + int _height = 0; + int _maxw = 0; + int _minh = 0; + +}; + +class PaintContextOverview : public PaintContextBase { +public: + PaintContextOverview(uint64 ms, bool selecting) : PaintContextBase(ms, selecting), isAfterDate(false) { + } + bool isAfterDate; + +}; + +class LayoutOverviewItemBase : public LayoutItemBase { +public: + + virtual void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContextOverview *context) const = 0; + + virtual LayoutMediaItemBase *toLayoutMediaItem() { return nullptr; } - virtual const LayoutMediaItem *toLayoutMediaItem() const { + virtual const LayoutMediaItemBase *toLayoutMediaItem() const { return nullptr; } @@ -164,23 +175,17 @@ public: return item ? item->id : 0; } -protected: - int _width = 0; - int _height = 0; - int _maxw = 0; - int _minh = 0; - }; -class LayoutMediaItem : public LayoutItem { +class LayoutMediaItemBase : public LayoutOverviewItemBase { public: - LayoutMediaItem(HistoryItem *parent) : _parent(parent) { + LayoutMediaItemBase(HistoryItem *parent) : _parent(parent) { } - LayoutMediaItem *toLayoutMediaItem() override { + LayoutMediaItemBase *toLayoutMediaItem() override { return this; } - const LayoutMediaItem *toLayoutMediaItem() const override { + const LayoutMediaItemBase *toLayoutMediaItem() const override { return this; } HistoryItem *getItem() const override { @@ -195,9 +200,9 @@ protected: }; -class LayoutRadialProgressItem : public LayoutMediaItem { +class LayoutRadialProgressItem : public LayoutMediaItemBase { public: - LayoutRadialProgressItem(HistoryItem *parent) : LayoutMediaItem(parent) + LayoutRadialProgressItem(HistoryItem *parent) : LayoutMediaItemBase(parent) , _radial(0) , a_iconOver(0, 0) , _a_iconOver(animation(this, &LayoutRadialProgressItem::step_iconOver)) { @@ -269,27 +274,16 @@ protected: }; -class OverviewPaintContext : public PaintContext { -public: - OverviewPaintContext(uint64 ms, bool selecting) : PaintContext(ms, selecting), isAfterDate(false) { - } - const OverviewPaintContext *toOverviewPaintContext() const { - return this; - } - bool isAfterDate; - -}; - struct OverviewItemInfo : public BaseComponent { int top = 0; }; -class LayoutOverviewDate : public LayoutItem { +class LayoutOverviewDate : public LayoutOverviewItemBase { public: LayoutOverviewDate(const QDate &date, bool month); void initDimensions() override; - void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const override; + void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContextOverview *context) const override; private: QDate _date; @@ -297,13 +291,13 @@ private: }; -class LayoutOverviewPhoto : public LayoutMediaItem { +class LayoutOverviewPhoto : public LayoutMediaItemBase { public: LayoutOverviewPhoto(PhotoData *photo, HistoryItem *parent); void initDimensions() override; int32 resizeGetHeight(int32 width) override; - void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const override; + void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContextOverview *context) const override; void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const override; private: @@ -321,7 +315,7 @@ public: void initDimensions() override; int32 resizeGetHeight(int32 width) override; - void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const override; + void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContextOverview *context) const override; void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const override; protected: @@ -354,7 +348,7 @@ public: LayoutOverviewVoice(DocumentData *voice, HistoryItem *parent); void initDimensions() override; - void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const override; + void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContextOverview *context) const override; void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const override; protected: @@ -388,7 +382,7 @@ public: LayoutOverviewDocument(DocumentData *document, HistoryItem *parent); void initDimensions() override; - void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const override; + void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContextOverview *context) const override; void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const override; virtual DocumentData *getDocument() const { @@ -427,13 +421,13 @@ private: }; -class LayoutOverviewLink : public LayoutMediaItem { +class LayoutOverviewLink : public LayoutMediaItemBase { public: LayoutOverviewLink(HistoryMedia *media, HistoryItem *parent); void initDimensions() override; int32 resizeGetHeight(int32 width) override; - void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const override; + void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContextOverview *context) const override; void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const override; private: @@ -457,316 +451,3 @@ private: QVector _links; }; - -class InlinePaintContext : public PaintContext { -public: - InlinePaintContext(uint64 ms, bool selecting, bool paused, bool lastRow) - : PaintContext(ms, selecting) - , paused(paused) - , lastRow(lastRow) { - } - const InlinePaintContext *toInlinePaintContext() const override { - return this; - } - bool paused, lastRow; -}; - -// this type used as a flag, we dynamic_cast<> to it -class SendInlineItemClickHandler : public ClickHandler { -public: - void onClick(Qt::MouseButton) const override { - } -}; - -class LayoutInlineItem : public LayoutItem { -public: - - LayoutInlineItem(InlineResult *result) : _result(result) { - } - LayoutInlineItem(DocumentData *doc) : _doc(doc) { - } - // Not used anywhere currently. - //LayoutInlineItem(PhotoData *photo) : _photo(photo) { - //} - - virtual void setPosition(int32 position); - int32 position() const; - - virtual bool isFullLine() const { - return true; - } - virtual bool hasRightSkip() const { - return false; - } - - InlineResult *getInlineResult() const; - DocumentData *getDocument() const; - PhotoData *getPhoto() const; - - virtual void preload() const; - - void update(); - - // ClickHandlerHost interface - void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override { - update(); - } - void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override { - update(); - } - -protected: - InlineResult *_result = nullptr; - DocumentData *_doc = nullptr; - PhotoData *_photo = nullptr; - - ClickHandlerPtr _send = ClickHandlerPtr{ new SendInlineItemClickHandler() }; - - int _position = 0; // < 0 means removed from layout - -}; - -class LayoutInlineAbstractFile : public LayoutInlineItem { -public: - LayoutInlineAbstractFile(InlineResult *result); - // for saved gif layouts - LayoutInlineAbstractFile(DocumentData *doc); - -protected: - DocumentData *getShownDocument() const { - if (DocumentData *result = getDocument()) { - return result; - } else if (InlineResult *result = getInlineResult()) { - return result->document; - } - return nullptr; - } - - int content_width() const; - int content_height() const; - bool content_loading() const; - bool content_displayLoading() const; - bool content_loaded() const; - float64 content_progress() const; - void content_automaticLoad() const; - void content_forget(); - FileLocation content_location() const; - QByteArray content_data() const; - ImagePtr content_thumb() const; - int content_duration() const; -}; - -class DeleteSavedGifClickHandler : public LeftButtonClickHandler { -public: - DeleteSavedGifClickHandler(DocumentData *data) : _data(data) { - } - -protected: - void onClickImpl() const override; - -private: - DocumentData *_data; - -}; - -class LayoutInlineGif : public LayoutInlineAbstractFile { -public: - LayoutInlineGif(InlineResult *result); - LayoutInlineGif(DocumentData *doc, bool hasDeleteButton); - - void setPosition(int32 position) override; - void initDimensions() override; - - bool isFullLine() const override { - return false; - } - bool hasRightSkip() const override { - return true; - } - - void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const override; - void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const override; - - // ClickHandlerHost interface - void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; - - ~LayoutInlineGif(); - -private: - - QSize countFrameSize() const; - - enum class StateFlag { - Over = 0x01, - DeleteOver = 0x02, - }; - Q_DECLARE_FLAGS(StateFlags, StateFlag); - StateFlags _state; - friend inline StateFlags operator~(StateFlag flag) { - return ~StateFlags(flag); - } - - ClipReader *_gif = nullptr; - ClickHandlerPtr _delete; - bool gif() const { - return (!_gif || _gif == BadClipReader) ? false : true; - } - mutable QPixmap _thumb; - void prepareThumb(int32 width, int32 height, const QSize &frame) const; - - void ensureAnimation() const; - bool isRadialAnimation(uint64 ms) const; - void step_radial(uint64 ms, bool timer); - - void clipCallback(ClipReaderNotification notification); - - struct AnimationData { - AnimationData(AnimationCreator creator) - : over(false) - , radial(creator) { - } - bool over; - FloatAnimation _a_over; - RadialAnimation radial; - }; - mutable AnimationData *_animation = nullptr; - mutable FloatAnimation _a_deleteOver; - -}; - -class LayoutInlinePhoto : public LayoutInlineItem { -public: - LayoutInlinePhoto(InlineResult *result); - // Not used anywhere currently. - //LayoutInlinePhoto(PhotoData *photo); - - void initDimensions() override; - - bool isFullLine() const override { - return false; - } - bool hasRightSkip() const override { - return true; - } - - void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const override; - void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const override; - -private: - PhotoData *getShownPhoto() const { - if (PhotoData *result = getPhoto()) { - return result; - } else if (InlineResult *result = getInlineResult()) { - return result->photo; - } - return nullptr; - } - - QSize countFrameSize() const; - - int content_width() const; - int content_height() const; - bool content_loaded() const; - void content_forget(); - - mutable QPixmap _thumb; - mutable bool _thumbLoaded = false; - void prepareThumb(int32 width, int32 height, const QSize &frame) const; - -}; - -class LayoutInlineSticker : public LayoutInlineAbstractFile { -public: - LayoutInlineSticker(InlineResult *result); - // Not used anywhere currently. - //LayoutInlineSticker(DocumentData *document); - - void initDimensions() override; - - bool isFullLine() const override { - return false; - } - bool hasRightSkip() const override { - return false; - } - void preload() const override; - - void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const override; - void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const override; - - // ClickHandlerHost interface - void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; - -private: - - QSize getThumbSize() const; - - mutable FloatAnimation _a_over; - mutable bool _active = false; - - mutable QPixmap _thumb; - mutable bool _thumbLoaded = false; - void prepareThumb() const; - -}; - -class LayoutInlineVideo : public LayoutInlineAbstractFile { -public: - LayoutInlineVideo(InlineResult *result); - - void initDimensions() override; - - void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const override; - void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const override; - -private: - - ClickHandlerPtr _link; - - mutable QPixmap _thumb; - Text _title, _description; - QString _duration; - int32 _durationWidth; - - void prepareThumb(int32 width, int32 height) const; - -}; - -class LayoutInlineFile : public LayoutInlineAbstractFile { -public: - LayoutInlineFile(InlineResult *result); - - void initDimensions() override; - - void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const override; - void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const override; - -private: - - Text _title, _description; - -}; - -class LayoutInlineArticle : public LayoutInlineItem { -public: - LayoutInlineArticle(InlineResult *result, bool withThumb); - - void initDimensions() override; - int32 resizeGetHeight(int32 width) override; - - void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const override; - void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const override; - -private: - - ClickHandlerPtr _url, _link; - - bool _withThumb; - mutable QPixmap _thumb; - Text _title, _description; - QString _letter, _urlText; - int32 _urlWidth; - - void prepareThumb(int32 width, int32 height) const; - -}; diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 647ccf468e..5e0c5292e1 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -812,11 +812,11 @@ void MainWidget::ui_repaintHistoryItem(const HistoryItem *item) { if (overview) overview->ui_repaintHistoryItem(item); } -void MainWidget::ui_repaintInlineItem(const LayoutInlineItem *layout) { +void MainWidget::ui_repaintInlineItem(const InlineBots::Layout::ItemBase *layout) { history.ui_repaintInlineItem(layout); } -bool MainWidget::ui_isInlineItemVisible(const LayoutInlineItem *layout) { +bool MainWidget::ui_isInlineItemVisible(const InlineBots::Layout::ItemBase *layout) { return history.ui_isInlineItemVisible(layout); } @@ -1895,7 +1895,7 @@ void MainWidget::documentLoadRetry() { } void MainWidget::inlineResultLoadProgress(FileLoader *loader) { - //InlineResult *result = App::inlineResultFromLoader(loader); + //InlineBots::Result *result = InlineBots::resultFromLoader(loader); //if (!result) return; //result->loaded(); @@ -1904,7 +1904,7 @@ void MainWidget::inlineResultLoadProgress(FileLoader *loader) { } void MainWidget::inlineResultLoadFailed(FileLoader *loader, bool started) { - //InlineResult *result = App::inlineResultFromLoader(loader); + //InlineBots::Result *result = InlineBots::resultFromLoader(loader); //if (!result) return; //result->loaded(); diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index 5e10ac99c7..72d17c7650 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -197,6 +197,14 @@ enum NotifySettingStatus { NotifySettingSetNotify, }; +namespace InlineBots { +namespace Layout { + +class ItemBase; + +} // namespace Layout +} // namespace InlineBots + class MainWidget : public TWidget, public RPCSender { Q_OBJECT @@ -435,8 +443,8 @@ public: bool isItemVisible(HistoryItem *item); void ui_repaintHistoryItem(const HistoryItem *item); - void ui_repaintInlineItem(const LayoutInlineItem *layout); - bool ui_isInlineItemVisible(const LayoutInlineItem *layout); + void ui_repaintInlineItem(const InlineBots::Layout::ItemBase *layout); + bool ui_isInlineItemVisible(const InlineBots::Layout::ItemBase *layout); bool ui_isInlineItemBeingChosen(); void ui_showPeerHistory(quint64 peer, qint32 msgId, bool back); PeerData *ui_getPeerForMouseAction(); diff --git a/Telegram/SourceFiles/overviewwidget.cpp b/Telegram/SourceFiles/overviewwidget.cpp index 57aa800e63..37497449b5 100644 --- a/Telegram/SourceFiles/overviewwidget.cpp +++ b/Telegram/SourceFiles/overviewwidget.cpp @@ -809,7 +809,7 @@ void OverviewInner::paintEvent(QPaintEvent *e) { p.setClipRect(r); } uint64 ms = getms(); - OverviewPaintContext context(ms, _selMode); + PaintContextOverview context(ms, _selMode); if (_history->overview[_type].isEmpty() && (!_migrated || !_history->overviewLoaded(_type) || _migrated->overview[_type].isEmpty())) { QPoint dogPos((_width - st::msgDogImg.pxWidth()) / 2, ((height() - st::msgDogImg.pxHeight()) * 4) / 9); @@ -908,7 +908,7 @@ void OverviewInner::onUpdateSelected() { upon = false; } if (i >= 0) { - if (LayoutMediaItem *media = _items.at(i)->toLayoutMediaItem()) { + if (LayoutMediaItemBase *media = _items.at(i)->toLayoutMediaItem()) { item = media->getItem(); index = i; if (upon) { @@ -945,7 +945,7 @@ void OverviewInner::onUpdateSelected() { j = _reversed ? (l - i - 1) : i; } - if (LayoutMediaItem *media = _items.at(i)->toLayoutMediaItem()) { + if (LayoutMediaItemBase *media = _items.at(i)->toLayoutMediaItem()) { item = media->getItem(); index = i; media->getState(lnk, cursorState, m.x() - _rowsLeft, m.y() - _marginTop - top); @@ -1632,7 +1632,7 @@ void OverviewInner::mediaOverviewUpdated() { allGood = false; } HistoryItem *item = App::histItemById(itemChannel(msgid), itemMsgId(msgid)); - LayoutMediaItem *layout = layoutPrepare(item); + LayoutMediaItemBase *layout = layoutPrepare(item); if (!layout) continue; setLayoutItem(index, layout, 0); @@ -1678,7 +1678,7 @@ void OverviewInner::mediaOverviewUpdated() { allGood = false; } HistoryItem *item = App::histItemById(itemChannel(msgid), itemMsgId(msgid)); - LayoutMediaItem *layout = layoutPrepare(item); + LayoutMediaItemBase *layout = layoutPrepare(item); if (!layout) continue; if (withDates) { @@ -1840,8 +1840,8 @@ void OverviewInner::recountMargins() { } } -LayoutMediaItem *OverviewInner::layoutPrepare(HistoryItem *item) { - if (!item) return 0; +LayoutMediaItemBase *OverviewInner::layoutPrepare(HistoryItem *item) { + if (!item) return nullptr; LayoutItems::const_iterator i = _layoutItems.cend(); HistoryMedia *media = item->getMedia(); @@ -1879,10 +1879,10 @@ LayoutMediaItem *OverviewInner::layoutPrepare(HistoryItem *item) { i.value()->initDimensions(); } } - return (i == _layoutItems.cend()) ? 0 : i.value(); + return (i == _layoutItems.cend()) ? nullptr : i.value(); } -LayoutItem *OverviewInner::layoutPrepare(const QDate &date, bool month) { +LayoutOverviewItemBase *OverviewInner::layoutPrepare(const QDate &date, bool month) { int32 key = date.year() * 100 + date.month(); if (!month) key = key * 100 + date.day(); LayoutDates::const_iterator i = _layoutDates.constFind(key); @@ -1893,7 +1893,7 @@ LayoutItem *OverviewInner::layoutPrepare(const QDate &date, bool month) { return i.value(); } -int32 OverviewInner::setLayoutItem(int32 index, LayoutItem *item, int32 top) { +int32 OverviewInner::setLayoutItem(int32 index, LayoutOverviewItemBase *item, int32 top) { if (_items.size() > index) { _items[index] = item; } else { diff --git a/Telegram/SourceFiles/overviewwidget.h b/Telegram/SourceFiles/overviewwidget.h index 6cd7affbb9..94c226759b 100644 --- a/Telegram/SourceFiles/overviewwidget.h +++ b/Telegram/SourceFiles/overviewwidget.h @@ -152,15 +152,15 @@ private: int32 _rowsLeft, _rowWidth; - typedef QVector Items; + typedef QVector Items; Items _items; - typedef QMap LayoutItems; + typedef QMap LayoutItems; LayoutItems _layoutItems; typedef QMap LayoutDates; LayoutDates _layoutDates; - LayoutMediaItem *layoutPrepare(HistoryItem *item); - LayoutItem *layoutPrepare(const QDate &date, bool month); - int32 setLayoutItem(int32 index, LayoutItem *item, int32 top); + LayoutMediaItemBase *layoutPrepare(HistoryItem *item); + LayoutOverviewItemBase *layoutPrepare(const QDate &date, bool month); + int32 setLayoutItem(int32 index, LayoutOverviewItemBase *item, int32 top); FlatInput _search; IconedButton _cancelSearch; diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp index 9d403a63ed..e2ec85bd8e 100644 --- a/Telegram/SourceFiles/structs.cpp +++ b/Telegram/SourceFiles/structs.cpp @@ -1442,219 +1442,6 @@ WebPageData::WebPageData(const WebPageId &id, WebPageType type, const QString &u , pendingTill(pendingTill) { } -QString InlineResultSendData::getLayoutTitle(InlineResult *owner) const { - return owner->title; -} - -QString InlineResultSendData::getLayoutDescription(InlineResult *owner) const { - return owner->description; -} - -InlineResultSendData::SentMTPMessageFields InlineResultSendText::getSentMessageFields(InlineResult*) const { - SentMTPMessageFields result; - result.text = MTP_string(_message); - result.entities = linksToMTP(_entities); - return result; -} - -InlineResultSendData::SentMTPMessageFields InlineResultSendGeo::getSentMessageFields(InlineResult*) const { - SentMTPMessageFields result; - result.media = MTP_messageMediaGeo(MTP_geoPoint(MTP_double(_location.lon), MTP_double(_location.lat))); - return result; -} - -InlineResultSendData::SentMTPMessageFields InlineResultSendVenue::getSentMessageFields(InlineResult*) const { - SentMTPMessageFields result; - result.media = MTP_messageMediaVenue(MTP_geoPoint(MTP_double(_location.lon), MTP_double(_location.lat)), MTP_string(_title), MTP_string(_address), MTP_string(_provider), MTP_string(_venueId)); - return result; -} - -InlineResultSendData::SentMTPMessageFields InlineResultSendContact::getSentMessageFields(InlineResult*) const { - SentMTPMessageFields result; - result.media = MTP_messageMediaContact(MTP_string(_phoneNumber), MTP_string(_firstName), MTP_string(_lastName), MTP_int(0)); - return result; -} - -QString InlineResultSendContact::getLayoutDescription(InlineResult *owner) const { - return App::formatPhone(_phoneNumber) + '\n' + owner->description; -} - -InlineResultSendData::SentMTPMessageFields InlineResultSendPhoto::getSentMessageFields(InlineResult *owner) const { - SentMTPMessageFields result; - - QImage fileThumb(owner->thumb->pix().toImage()); - - QVector photoSizes; - - QPixmap thumb = (fileThumb.width() > 100 || fileThumb.height() > 100) ? QPixmap::fromImage(fileThumb.scaled(100, 100, Qt::KeepAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly) : QPixmap::fromImage(fileThumb); - ImagePtr thumbPtr = ImagePtr(thumb, "JPG"); - photoSizes.push_back(MTP_photoSize(MTP_string("s"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(thumb.width()), MTP_int(thumb.height()), MTP_int(0))); - - QSize medium = resizeKeepAspect(owner->width, owner->height, 320, 320); - photoSizes.push_back(MTP_photoSize(MTP_string("m"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(medium.width()), MTP_int(medium.height()), MTP_int(0))); - - photoSizes.push_back(MTP_photoSize(MTP_string("x"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(owner->width), MTP_int(owner->height), MTP_int(0))); - - uint64 photoId = rand_value(); - PhotoData *ph = App::photoSet(photoId, 0, 0, unixtime(), thumbPtr, ImagePtr(medium.width(), medium.height()), ImagePtr(owner->width, owner->height)); - MTPPhoto photo = MTP_photo(MTP_long(photoId), MTP_long(0), MTP_int(ph->date), MTP_vector(photoSizes)); - - result.media = MTP_messageMediaPhoto(photo, MTP_string(_caption)); - - return result; -} - -InlineResultSendData::SentMTPMessageFields InlineResultSendFile::getSentMessageFields(InlineResult *owner) const { - SentMTPMessageFields result; - - MTPPhotoSize thumbSize; - QPixmap thumb; - int32 tw = owner->thumb->width(), th = owner->thumb->height(); - if (tw > 0 && th > 0 && tw < 20 * th && th < 20 * tw && owner->thumb->loaded()) { - if (tw > th) { - if (tw > 90) { - th = th * 90 / tw; - tw = 90; - } - } else if (th > 90) { - tw = tw * 90 / th; - th = 90; - } - thumbSize = MTP_photoSize(MTP_string(""), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(tw), MTP_int(th), MTP_int(0)); - thumb = owner->thumb->pixNoCache(tw, th, ImagePixSmooth); - } else { - tw = th = 0; - thumbSize = MTP_photoSizeEmpty(MTP_string("")); - } - uint64 docId = rand_value(); - QVector attributes; - - int duration = getSentDuration(owner); - QSize dimensions = getSentDimensions(owner); - using Type = InlineResult::Type; - if (owner->type == Type::Gif) { - attributes.push_back(MTP_documentAttributeFilename(MTP_string((owner->content_type == qstr("video/mp4") ? "animation.gif.mp4" : "animation.gif")))); - attributes.push_back(MTP_documentAttributeAnimated()); - attributes.push_back(MTP_documentAttributeVideo(MTP_int(duration), MTP_int(dimensions.width()), MTP_int(dimensions.height()))); - } else if (owner->type == Type::Video) { - attributes.push_back(MTP_documentAttributeVideo(MTP_int(duration), MTP_int(dimensions.width()), MTP_int(dimensions.height()))); - } - MTPDocument document = MTP_document(MTP_long(docId), MTP_long(0), MTP_int(unixtime()), MTP_string(owner->content_type), MTP_int(owner->data().size()), thumbSize, MTP_int(MTP::maindc()), MTP_vector(attributes)); - if (tw > 0 && th > 0) { - App::feedDocument(document, thumb); - } - Local::writeStickerImage(mediaKey(DocumentFileLocation, MTP::maindc(), docId), owner->data()); - - result.media = MTP_messageMediaDocument(document, MTP_string(_caption)); - - return result; -} - -int InlineResultSendFile::getSentDuration(InlineResult *owner) const { - return (_document && _document->duration()) ? _document->duration() : owner->duration; -} -QSize InlineResultSendFile::getSentDimensions(InlineResult *owner) const { - return (!_document || _document->dimensions.isEmpty()) ? QSize(owner->width, owner->height) : _document->dimensions; -} - -void InlineResult::automaticLoadGif() { - if (loaded() || type != Type::Gif || (content_type != qstr("video/mp4") && content_type != "image/gif")) return; - - if (_loader != CancelledWebFileLoader) { - // if load at least anywhere - bool loadFromCloud = !(cAutoDownloadGif() & dbiadNoPrivate) || !(cAutoDownloadGif() & dbiadNoGroups); - saveFile(QString(), loadFromCloud ? LoadFromCloudOrLocal : LoadFromLocalOnly, true); - } -} - -void InlineResult::automaticLoadSettingsChangedGif() { - if (loaded() || _loader != CancelledWebFileLoader) return; - _loader = 0; -} - -void InlineResult::saveFile(const QString &toFile, LoadFromCloudSetting fromCloud, bool autoLoading) { - if (loaded()) { - return; - } - - if (_loader == CancelledWebFileLoader) _loader = 0; - if (_loader) { - if (!_loader->setFileName(toFile)) { - cancelFile(); - _loader = 0; - } - } - - if (_loader) { - if (fromCloud == LoadFromCloudOrLocal) _loader->permitLoadFromCloud(); - } else { - _loader = new webFileLoader(content_url, toFile, fromCloud, autoLoading); - App::regInlineResultLoader(_loader, this); - - _loader->connect(_loader, SIGNAL(progress(FileLoader*)), App::main(), SLOT(inlineResultLoadProgress(FileLoader*))); - _loader->connect(_loader, SIGNAL(failed(FileLoader*,bool)), App::main(), SLOT(inlineResultLoadFailed(FileLoader*,bool))); - _loader->start(); - } -} - -void InlineResult::cancelFile() { - if (!loading()) return; - - App::unregInlineResultLoader(_loader); - - webFileLoader *l = _loader; - _loader = CancelledWebFileLoader; - if (l) { - l->cancel(); - l->deleteLater(); - l->stop(); - } -} - -QByteArray InlineResult::data() const { - return _data; -} - -bool InlineResult::loading() const { - return _loader && _loader != CancelledWebFileLoader; -} - -bool InlineResult::loaded() const { - if (loading() && _loader->done()) { - App::unregInlineResultLoader(_loader); - if (_loader->fileType() == mtpc_storage_fileUnknown) { - _loader->deleteLater(); - _loader->stop(); - _loader = CancelledWebFileLoader; - } else { - InlineResult *that = const_cast(this); - that->_data = _loader->bytes(); - - _loader->deleteLater(); - _loader->stop(); - _loader = 0; - } - } - return !_data.isEmpty(); -} - -bool InlineResult::displayLoading() const { - return loading() ? (!_loader->loadingLocal() || !_loader->autoLoading()) : false; -} - -void InlineResult::forget() { - thumb->forget(); - _data.clear(); -} - -float64 InlineResult::progress() const { - return loading() ? _loader->currentProgress() : (loaded() ? 1 : 0); return false; -} - -InlineResult::~InlineResult() { - cancelFile(); -} - void PeerOpenClickHandler::onClickImpl() const { if (App::main()) { if (peer() && peer()->isChannel() && App::main()->historyPeer() != peer()) { diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h index 3106595407..b5706e24fc 100644 --- a/Telegram/SourceFiles/structs.h +++ b/Telegram/SourceFiles/structs.h @@ -452,7 +452,6 @@ private: QString _restrictionReason; }; -static UserData * const InlineBotLookingUpData = SharedMemoryLocation(); class ChatData : public PeerData { public: @@ -1246,274 +1245,6 @@ struct WebPageData { }; -class InlineResult; - -// Abstract class describing the message that will be -// sent if the user chooses this inline bot result. -// For each type of message that can be sent there will be a subclass. -class InlineResultSendData { -public: - InlineResultSendData() = default; - InlineResultSendData(const InlineResultSendData &other) = delete; - InlineResultSendData &operator=(const InlineResultSendData &other) = delete; - virtual ~InlineResultSendData() = default; - - virtual bool isValid() const = 0; - - virtual DocumentData *getSentDocument() const { - return nullptr; - } - virtual PhotoData *getSentPhoto() const { - return nullptr; - } - virtual QString getSentCaption() const { - return QString(); - } - struct SentMTPMessageFields { - MTPString text = MTP_string(""); - MTPVector entities = MTPnullEntities; - MTPMessageMedia media = MTP_messageMediaEmpty(); - }; - virtual SentMTPMessageFields getSentMessageFields(InlineResult *owner) const = 0; - - virtual bool hasLocationCoords() const { - return false; - } - virtual bool getLocationCoords(LocationCoords *location) const { - return false; - } - virtual QString getLayoutTitle(InlineResult *owner) const; - virtual QString getLayoutDescription(InlineResult *owner) const; - -}; - -// Plain text message. -class InlineResultSendText : public InlineResultSendData { -public: - InlineResultSendText(const QString &message, const EntitiesInText &entities, bool noWebPage) - : _message(message) - , _entities(entities) - , _noWebPage(noWebPage) { - } - - bool isValid() const override { - return !_message.isEmpty(); - } - - SentMTPMessageFields getSentMessageFields(InlineResult *owner) const override; - -private: - QString _message; - EntitiesInText _entities; - bool _noWebPage; - -}; - -// Message with geo location point media. -class InlineResultSendGeo : public InlineResultSendData { -public: - InlineResultSendGeo(const MTPDgeoPoint &point) : _location(point) { - } - - bool isValid() const override { - return true; - } - - SentMTPMessageFields getSentMessageFields(InlineResult *owner) const override; - - bool hasLocationCoords() const override { - return true; - } - bool getLocationCoords(LocationCoords *location) const override { - t_assert(location != nullptr); - *location = _location; - return true; - } - -private: - LocationCoords _location; - -}; - -// Message with venue media. -class InlineResultSendVenue : public InlineResultSendData { -public: - InlineResultSendVenue(const MTPDgeoPoint &point, const QString &venueId, - const QString &provider, const QString &title, const QString &address) - : _location(point) - , _venueId(venueId) - , _provider(provider) - , _title(title) - , _address(address) { - } - - bool isValid() const override { - return true; - } - - SentMTPMessageFields getSentMessageFields(InlineResult *owner) const override; - - bool getLocationCoords(LocationCoords *location) const override { - if (location) { - *location = _location; - } - return true; - } - -private: - LocationCoords _location; - QString _venueId, _provider, _title, _address; - -}; - -// Message with shared contact media. -class InlineResultSendContact : public InlineResultSendData { -public: - InlineResultSendContact(const QString &firstName, const QString &lastName, const QString &phoneNumber) - : _firstName(firstName) - , _lastName(lastName) - , _phoneNumber(phoneNumber) { - } - - bool isValid() const override { - return (!_firstName.isEmpty() || !_lastName.isEmpty()) && !_phoneNumber.isEmpty(); - } - - SentMTPMessageFields getSentMessageFields(InlineResult *owner) const override; - - QString getLayoutDescription(InlineResult *owner) const override; - -private: - QString _firstName, _lastName, _phoneNumber; - -}; - -// Message with photo. -class InlineResultSendPhoto : public InlineResultSendData { -public: - InlineResultSendPhoto(PhotoData *photo, const QString &url, const QString &caption) - : _photo(photo) - , _url(url) - , _caption(caption) { - } - - bool isValid() const override { - return _photo || !_url.isEmpty(); - } - - PhotoData *getSentPhoto() const override { - return _photo; - } - QString getSentCaption() const override { - return _caption; - } - SentMTPMessageFields getSentMessageFields(InlineResult *owner) const override; - -private: - PhotoData *_photo; - QString _url, _caption; - -}; - -// Message with file. -class InlineResultSendFile : public InlineResultSendData { -public: - InlineResultSendFile(DocumentData *document, const QString &url, const QString &caption) - : _document(document) - , _url(url) - , _caption(caption) { - } - - bool isValid() const override { - return _document || !_url.isEmpty(); - } - - DocumentData *getSentDocument() const override { - return _document; - } - QString getSentCaption() const override { - return _caption; - } - SentMTPMessageFields getSentMessageFields(InlineResult *owner) const override; - -private: - DocumentData *_document; - QString _url, _caption; - - int getSentDuration(InlineResult *owner) const; - QSize getSentDimensions(InlineResult *owner) const; - -}; - -class InlineResult { -public: - enum class Type { - Unknown, - Photo, - Video, - Audio, - Sticker, - File, - Gif, - Article, - Contact, - Venue, - }; - static QMap getTypesMap() { - QMap result; - result.insert(qsl("photo"), Type::Photo); - result.insert(qsl("video"), Type::Video); - result.insert(qsl("audio"), Type::Audio); - result.insert(qsl("sticker"), Type::Sticker); - result.insert(qsl("file"), Type::File); - result.insert(qsl("gif"), Type::Gif); - result.insert(qsl("article"), Type::Article); - result.insert(qsl("contact"), Type::Contact); - result.insert(qsl("venue"), Type::Venue); - return result; - } - - InlineResult(uint64 queryId, Type type) : queryId(queryId), type(type) { - } - InlineResult(const InlineResult &other) = delete; - InlineResult &operator=(const InlineResult &other) = delete; - - uint64 queryId; - QString id; - Type type; - DocumentData *document = nullptr; - PhotoData *photo = nullptr; - QString title, description, url, thumb_url; - QString content_type, content_url; - int width = 0; - int height = 0; - int duration = 0; - - UniquePointer sendData; - - ImagePtr thumb; - - void automaticLoadGif(); - void automaticLoadSettingsChangedGif(); - void saveFile(const QString &toFile, LoadFromCloudSetting fromCloud, bool autoLoading); - void cancelFile(); - - QByteArray data() const; - bool loading() const; - bool loaded() const; - bool displayLoading() const; - void forget(); - float64 progress() const; - - ~InlineResult(); - -private: - QByteArray _data; - mutable webFileLoader *_loader = nullptr; - -}; -typedef QList InlineResults; - QString saveFileName(const QString &title, const QString &filter, const QString &prefix, QString name, bool savingAs, const QDir &dir = QDir()); MsgId clientMsgId(); diff --git a/Telegram/Telegram.vcxproj b/Telegram/Telegram.vcxproj index 8e57d22786..ece76bf164 100644 --- a/Telegram/Telegram.vcxproj +++ b/Telegram/Telegram.vcxproj @@ -1075,6 +1075,10 @@ + + + + @@ -1204,6 +1208,10 @@ "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/basic_types.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\openssl\Release\include" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\..\..\Libraries\breakpad\src" "-I.\ThirdParty\minizip" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I.\..\..\Libraries\QtStatic\qtbase\include\QtCore\5.5.1\QtCore" "-I.\..\..\Libraries\QtStatic\qtbase\include\QtGui\5.5.1\QtGui" + + + + $(QTDIR)\bin\moc.exe;%(FullPath) diff --git a/Telegram/Telegram.vcxproj.filters b/Telegram/Telegram.vcxproj.filters index 5fb4039bad..8dac536460 100644 --- a/Telegram/Telegram.vcxproj.filters +++ b/Telegram/Telegram.vcxproj.filters @@ -52,6 +52,9 @@ {1abe710c-3c36-484c-b2a5-881c29a051c2} + + {da3d0334-a011-41dd-a8e0-9b701afacfb3} + @@ -972,6 +975,18 @@ Generated Files\Release + + inline_bots + + + inline_bots + + + inline_bots + + + inline_bots + @@ -1067,6 +1082,18 @@ gui + + inline_bots + + + inline_bots + + + inline_bots + + + inline_bots +