diff --git a/Telegram/Resources/style.txt b/Telegram/Resources/style.txt index 9a70915d5..155785981 100644 --- a/Telegram/Resources/style.txt +++ b/Telegram/Resources/style.txt @@ -1307,6 +1307,13 @@ btnAttachEmoji: iconedButton(btnAttachDocument) { width: 33px; } +emojiCircle: size(19px, 19px); +emojiCirclePeriod: 1500; +emojiCircleDuration: 500; +emojiCircleTop: 13px; +emojiCircleLine: 2px; +emojiCircleFg: #b9b9b9; +emojiCirclePart: 3.5; btnBotKbShow: iconedButton(btnAttachEmoji) { icon: sprite(375px, 74px, 21px, 21px); iconPos: point(6px, 12px); diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp index 1e79a4ce8..9313e0b85 100644 --- a/Telegram/SourceFiles/application.cpp +++ b/Telegram/SourceFiles/application.cpp @@ -548,7 +548,7 @@ void Application::stopUpdate() { void Application::startUpdateCheck(bool forceWait) { updateCheckTimer.stop(); if (updateRequestId || updateThread || updateReply || !cAutoUpdate()) return; - + int32 constDelay = cBetaVersion() ? 600 : UpdateDelayConstPart, randDelay = cBetaVersion() ? 300 : UpdateDelayRandPart; int32 updateInSecs = cLastUpdateCheck() + constDelay + int32(MTP::nonce() % randDelay) - unixtime(); bool sendRequest = (updateInSecs <= 0 || updateInSecs > (constDelay + randDelay)); @@ -684,7 +684,7 @@ void Application::socketError(QLocalSocket::LocalSocketError e) { socket.close(); psCheckLocalSocket(serverName); - + if (!server.listen(serverName)) { DEBUG_LOG(("Application Error: failed to start listening to %1 server, error %2").arg(serverName).arg(int(server.serverError()))); return App::quit(); @@ -705,10 +705,11 @@ void Application::checkMapVersion() { if (Local::oldMapVersion() < AppVersion) { if (Local::oldMapVersion()) { QString versionFeatures; - if (cDevVersion() && Local::oldMapVersion() < 9014) { - versionFeatures = QString::fromUtf8("\xe2\x80\x94 Sticker management: manually rearrange your sticker packs, pack order is now synced across all your devices\n\xe2\x80\x94 Click and hold on a sticker to preview it before sending\n\xe2\x80\x94 New context menu for chats in chats list\n\xe2\x80\x94 Support for all existing emoji");// .replace('@', qsl("@") + QChar(0x200D)); + if (cDevVersion() && Local::oldMapVersion() < 9016) { +// versionFeatures = QString::fromUtf8("\xe2\x80\x94 Sticker management: manually rearrange your sticker packs, pack order is now synced across all your devices\n\xe2\x80\x94 Click and hold on a sticker to preview it before sending\n\xe2\x80\x94 New context menu for chats in chats list\n\xe2\x80\x94 Support for all existing emoji");// .replace('@', qsl("@") + QChar(0x200D)); + versionFeatures = lng_new_version_text(lt_gifs_link, qsl("https://telegram.org/blog/gif-revolution"), lt_bots_link, qsl("https://telegram.org/blog/inline-bots")).trimmed(); } else if (Local::oldMapVersion() < 9015) { -// versionFeatures = lang(lng_new_version_text).trimmed(); + versionFeatures = lng_new_version_text(lt_gifs_link, qsl("https://telegram.org/blog/gif-revolution"), lt_bots_link, qsl("https://telegram.org/blog/inline-bots")).trimmed(); } else { versionFeatures = lang(lng_new_version_minor).trimmed(); } @@ -769,7 +770,7 @@ void Application::startApp() { } QNetworkProxyFactory::setUseSystemConfiguration(true); - + if (state != Local::ReadMapPassNeeded) { checkMapVersion(); } @@ -912,7 +913,7 @@ Application::~Application() { cSetChatDogImage(0); style::stopManager(); - + delete _translator; } diff --git a/Telegram/SourceFiles/art/sprite_200x.png b/Telegram/SourceFiles/art/sprite_200x.png index 50dfed9c3..16b454f05 100644 Binary files a/Telegram/SourceFiles/art/sprite_200x.png and b/Telegram/SourceFiles/art/sprite_200x.png differ diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h index 4926981de..2875cabd1 100644 --- a/Telegram/SourceFiles/config.h +++ b/Telegram/SourceFiles/config.h @@ -23,7 +23,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org static const int32 AppVersion = 9015; static const wchar_t *AppVersionStr = L"0.9.15"; static const bool DevVersion = false; -#define BETA_VERSION (9015005ULL) // just comment this line to build public version +#define BETA_VERSION (9015006ULL) // just comment this line to build public version static const wchar_t *AppNameOld = L"Telegram Win (Unofficial)"; static const wchar_t *AppName = L"Telegram Desktop"; @@ -87,6 +87,7 @@ enum { AverageGifSize = 320 * 240, WaitBeforeGifPause = 200, // wait 200ms for gif draw before pausing it InlineBotRequestDelay = 400, // wait 400ms before context bot realtime request + RecentInlineBotsLimit = 10, AVBlockSize = 4096, // 4Kb for ffmpeg blocksize diff --git a/Telegram/SourceFiles/dialogswidget.cpp b/Telegram/SourceFiles/dialogswidget.cpp index d46b65a4f..ef5247c88 100644 --- a/Telegram/SourceFiles/dialogswidget.cpp +++ b/Telegram/SourceFiles/dialogswidget.cpp @@ -163,7 +163,7 @@ void DialogsInner::paintRegion(Painter &p, const QRegion ®ion, bool paintingO PeerData *act = App::main()->activePeer(); MsgId actId = App::main()->activeMsgId(); for (; from < to; ++from) { - bool active = ((_filterResults[from]->history->peer == act) || (_filterResults[from]->history->peer->migrateTo() && _filterResults[from]->history->peer->migrateTo() == act)) && !actId; + bool active = ((_filterResults[from]->history->peer == act) || (_filterResults[from]->history->peer->migrateTo() && _filterResults[from]->history->peer->migrateTo() == act)) && !actId; bool selected = (from == _filteredSel) || (_filterResults[from]->history->peer == _menuPeer); _filterResults[from]->paint(p, w, active, selected, paintingOther); p.translate(0, st::dlgHeight); @@ -872,7 +872,7 @@ void DialogsInner::onHashtagFilterUpdate(QStringRef newFilter) { } _hashtagFilter = newFilter.toString(); if (cRecentSearchHashtags().isEmpty() && cRecentWriteHashtags().isEmpty()) { - Local::readRecentHashtags(); + Local::readRecentHashtagsAndBots(); } const RecentHashtagPack &recent(cRecentSearchHashtags()); _hashtagResults.clear(); @@ -1385,7 +1385,7 @@ void DialogsInner::loadPeerPhotos(int32 yFrom) { if (from < _filterResults.size()) { int32 to = (yTo / int32(st::dlgHeight)) + 1, w = width(); if (to > _filterResults.size()) to = _filterResults.size(); - + for (; from < to; ++from) { _filterResults[from]->history->peer->photo->load(); } @@ -1434,7 +1434,7 @@ bool DialogsInner::choosePeer() { } } cSetRecentSearchHashtags(recent); - Local::writeRecentHashtags(); + Local::writeRecentHashtagsAndBots(); emit refreshHashtags(); selByMouse = true; @@ -1487,7 +1487,7 @@ void DialogsInner::saveRecentHashtags(const QString &text) { } } if (!found && cRecentWriteHashtags().isEmpty() && cRecentSearchHashtags().isEmpty()) { - Local::readRecentHashtags(); + Local::readRecentHashtagsAndBots(); recent = cRecentSearchHashtags(); } found = true; @@ -1495,7 +1495,7 @@ void DialogsInner::saveRecentHashtags(const QString &text) { } if (found) { cSetRecentSearchHashtags(recent); - Local::writeRecentHashtags(); + Local::writeRecentHashtagsAndBots(); } } diff --git a/Telegram/SourceFiles/dropdown.cpp b/Telegram/SourceFiles/dropdown.cpp index 015a815c1..85dfa1047 100644 --- a/Telegram/SourceFiles/dropdown.cpp +++ b/Telegram/SourceFiles/dropdown.cpp @@ -1303,7 +1303,7 @@ void StickerPanInner::paintEvent(QPaintEvent *e) { } void StickerPanInner::paintInlineItems(Painter &p, const QRect &r) { - InlinePaintContext context(getms(), false, _previewShown, false); + InlinePaintContext 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()); @@ -3560,10 +3560,13 @@ void EmojiPan::inlineBotChanged() { } _inlineCache.clear(); s_inner.inlineBotChanged(); + + Notify::inlineBotRequesting(false); } void EmojiPan::inlineResultsDone(const MTPmessages_BotResults &result) { _inlineRequestId = 0; + Notify::inlineBotRequesting(false); InlineCache::iterator it = _inlineCache.find(_inlineQuery); @@ -3659,6 +3662,8 @@ void EmojiPan::inlineResultsDone(const MTPmessages_BotResults &result) { bool EmojiPan::inlineResultsFail(const RPCError &error) { if (mtpIsFlood(error)) return false; + + Notify::inlineBotRequesting(false); _inlineRequestId = 0; return true; } @@ -3674,6 +3679,7 @@ void EmojiPan::queryInlineBot(UserData *bot, QString query) { if (_inlineRequestId) { MTP::cancel(_inlineRequestId); _inlineRequestId = 0; + Notify::inlineBotRequesting(false); } if (_inlineCache.contains(query)) { _inlineRequestTimer.stop(); @@ -3696,6 +3702,7 @@ void EmojiPan::onInlineRequest() { nextOffset = i.value()->nextOffset; if (nextOffset.isEmpty()) return; } + Notify::inlineBotRequesting(true); _inlineRequestId = MTP::send(MTPmessages_GetInlineBotResults(_inlineBot->inputUser, MTP_string(_inlineQuery), MTP_string(nextOffset)), rpcDone(&EmojiPan::inlineResultsDone), rpcFail(&EmojiPan::inlineResultsFail)); } @@ -3759,7 +3766,7 @@ void MentionsInner::paintEvent(QPaintEvent *e) { if (selected) { p.fillRect(0, i * st::mentionHeight, width(), st::mentionHeight, st::mentionBgOver->b); int skip = (st::mentionHeight - st::notifyClose.icon.pxHeight()) / 2; - if (!_hrows->isEmpty()) p.drawPixmap(QPoint(width() - st::notifyClose.icon.pxWidth() - skip, i * st::mentionHeight + skip), App::sprite(), st::notifyClose.icon); + if (!_hrows->isEmpty() || (!_mrows->isEmpty() && i < _recentInlineBotsInRows)) p.drawPixmap(QPoint(width() - st::notifyClose.icon.pxWidth() - skip, i * st::mentionHeight + skip), App::sprite(), st::notifyClose.icon); } p.setPen(st::black->p); if (!_mrows->isEmpty()) { @@ -3895,6 +3902,10 @@ bool MentionsInner::select() { return false; } +void MentionsInner::setRecentInlineBotsInRows(int32 bots) { + _recentInlineBotsInRows = bots; +} + QString MentionsInner::getSelected() const { int32 maxSel = (_mrows->isEmpty() ? (_hrows->isEmpty() ? _brows->size() : _hrows->size()) : _mrows->size()); if (_sel >= 0 && _sel < maxSel) { @@ -3923,20 +3934,32 @@ void MentionsInner::mousePressEvent(QMouseEvent *e) { _mouseSel = true; onUpdateSelected(true); if (e->button() == Qt::LeftButton) { - if (_overDelete && _sel >= 0 && _sel < _hrows->size()) { + if (_overDelete && _sel >= 0 && _sel < (_mrows->isEmpty() ? _hrows->size() : _recentInlineBotsInRows)) { _mousePos = mapToGlobal(e->pos()); - - QString toRemove = _hrows->at(_sel); - RecentHashtagPack recent(cRecentWriteHashtags()); - for (RecentHashtagPack::iterator i = recent.begin(); i != recent.cend();) { - if (i->first == toRemove) { - i = recent.erase(i); - } else { - ++i; + bool removed = false; + if (_mrows->isEmpty()) { + QString toRemove = _hrows->at(_sel); + RecentHashtagPack &recent(cRefRecentWriteHashtags()); + for (RecentHashtagPack::iterator i = recent.begin(); i != recent.cend();) { + if (i->first == toRemove) { + i = recent.erase(i); + removed = true; + } else { + ++i; + } + } + } else { + UserData *toRemove = _mrows->at(_sel); + RecentInlineBots &recent(cRefRecentInlineBots()); + int32 index = recent.indexOf(toRemove); + if (index >= 0) { + recent.remove(index); + removed = true; } } - cSetRecentWriteHashtags(recent); - Local::writeRecentHashtags(); + if (removed) { + Local::writeRecentHashtagsAndBots(); + } _parent->updateFiltered(); _mouseSel = true; @@ -3973,8 +3996,8 @@ void MentionsInner::onUpdateSelected(bool force) { if ((!force && !rect().contains(mouse)) || !_mouseSel) return; int w = width(), mouseY = mouse.y(); - _overDelete = _mrows->isEmpty() && (mouse.x() >= w - st::mentionHeight); int32 sel = mouseY / int32(st::mentionHeight), maxSel = _mrows->isEmpty() ? (_hrows->isEmpty() ? _brows->size() : _hrows->size()) : _mrows->size(); + _overDelete = (!_hrows->isEmpty() || (!_mrows->isEmpty() && sel < _recentInlineBotsInRows)) ? (mouse.x() >= w - st::mentionHeight) : false; if (sel < 0 || sel >= maxSel) { sel = -1; } @@ -4036,15 +4059,23 @@ void MentionsDropdown::paintEvent(QPaintEvent *e) { } -void MentionsDropdown::showFiltered(PeerData *peer, QString start) { +void MentionsDropdown::showFiltered(PeerData *peer, QString query, bool start) { + if (query.isEmpty() || (peer->isUser() && query.at(0) == '@' && (!start || cRecentInlineBots().isEmpty()))) { + if (!isHidden()) { + hideStart(); + } + return; + } + _chat = peer->asChat(); _user = peer->asUser(); _channel = peer->asChannel(); - start = start.toLower(); - bool toDown = (_filter != start); + query = query.toLower(); + bool toDown = (_filter != query); if (toDown) { - _filter = start; + _filter = query; } + _addInlineBots = start; updateFiltered(toDown); } @@ -4056,13 +4087,34 @@ bool MentionsDropdown::clearFilteredBotCommands() { } void MentionsDropdown::updateFiltered(bool toDown) { - int32 now = unixtime(); - MentionRows rows; + int32 now = unixtime(), recentInlineBots = 0; + MentionRows mrows; HashtagRows hrows; BotCommandRows brows; + if (_filter.at(0) == '@') { + if (_chat) { + mrows.reserve((_addInlineBots ? cRecentInlineBots().size() : 0) + (_chat->participants.isEmpty() ? _chat->lastAuthors.size() : _chat->participants.size())); + } else if (_channel && _channel->isMegagroup()) { + if (_channel->mgInfo->lastParticipants.isEmpty() || _channel->lastParticipantsCountOutdated()) { + } else { + mrows.reserve((_addInlineBots ? cRecentInlineBots().size() : 0) + _channel->mgInfo->lastParticipants.size()); + } + } else if (_addInlineBots) { + mrows.reserve(cRecentInlineBots().size()); + } + if (_addInlineBots) { + for (RecentInlineBots::const_iterator i = cRecentInlineBots().cbegin(), e = cRecentInlineBots().cend(); i != e; ++i) { + UserData *user = *i; + if (user->username.isEmpty()) continue; + if (_filter.size() > 1 && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue; + mrows.push_back(user); + ++recentInlineBots; + } + } + } if (_filter.at(0) == '@' && _chat) { QMultiMap ordered; - rows.reserve(_chat->participants.isEmpty() ? _chat->lastAuthors.size() : _chat->participants.size()); + mrows.reserve(mrows.size() + (_chat->participants.isEmpty() ? _chat->lastAuthors.size() : _chat->participants.size())); if (_chat->noParticipantInfo()) { if (App::api()) App::api()->requestFullPeer(_chat); } else if (!_chat->participants.isEmpty()) { @@ -4077,7 +4129,7 @@ void MentionsDropdown::updateFiltered(bool toDown) { UserData *user = *i; if (user->username.isEmpty()) continue; if (_filter.size() > 1 && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue; - rows.push_back(user); + mrows.push_back(user); if (!ordered.isEmpty()) { ordered.remove(App::onlineForSort(user, now), user); } @@ -4085,7 +4137,7 @@ void MentionsDropdown::updateFiltered(bool toDown) { if (!ordered.isEmpty()) { for (QMultiMap::const_iterator i = ordered.cend(), b = ordered.cbegin(); i != b;) { --i; - rows.push_back(i.value()); + mrows.push_back(i.value()); } } } else if (_filter.at(0) == '@' && _channel && _channel->isMegagroup()) { @@ -4093,12 +4145,12 @@ void MentionsDropdown::updateFiltered(bool toDown) { if (_channel->mgInfo->lastParticipants.isEmpty() || _channel->lastParticipantsCountOutdated()) { if (App::api()) App::api()->requestLastParticipants(_channel); } else { - rows.reserve(_channel->mgInfo->lastParticipants.size()); + mrows.reserve(mrows.size() + _channel->mgInfo->lastParticipants.size()); for (MegagroupInfo::LastParticipants::const_iterator i = _channel->mgInfo->lastParticipants.cbegin(), e = _channel->mgInfo->lastParticipants.cend(); i != e; ++i) { UserData *user = *i; if (user->username.isEmpty()) continue; if (_filter.size() > 1 && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue; - rows.push_back(user); + mrows.push_back(user); } } } else if (_filter.at(0) == '#') { @@ -4177,7 +4229,8 @@ void MentionsDropdown::updateFiltered(bool toDown) { } } } - rowsUpdated(rows, hrows, brows, toDown); + rowsUpdated(mrows, hrows, brows, toDown); + _inner.setRecentInlineBotsInRows(recentInlineBots); } void MentionsDropdown::rowsUpdated(const MentionRows &mrows, const HashtagRows &hrows, const BotCommandRows &brows, bool toDown) { diff --git a/Telegram/SourceFiles/dropdown.h b/Telegram/SourceFiles/dropdown.h index b86a1d6f2..75ce7c634 100644 --- a/Telegram/SourceFiles/dropdown.h +++ b/Telegram/SourceFiles/dropdown.h @@ -736,6 +736,8 @@ public: bool moveSel(int direction); bool select(); + void setRecentInlineBotsInRows(int32 bots); + QString getSelected() const; signals: @@ -756,6 +758,7 @@ private: MentionRows *_mrows; HashtagRows *_hrows; BotCommandRows *_brows; + int32 _recentInlineBotsInRows; int32 _sel; bool _mouseSel; QPoint _mousePos; @@ -775,7 +778,7 @@ public: void fastHide(); bool clearFilteredBotCommands(); - void showFiltered(PeerData *peer, QString start); + void showFiltered(PeerData *peer, QString query, bool start); void updateFiltered(bool toDown = false); void setBoundings(QRect boundings); @@ -820,7 +823,7 @@ private: HashtagRows _hrows; BotCommandRows _brows; - void rowsUpdated(const MentionRows &rows, const HashtagRows &hrows, const BotCommandRows &brows, bool toDown); + void rowsUpdated(const MentionRows &mrows, const HashtagRows &hrows, const BotCommandRows &brows, bool toDown); ScrollArea _scroll; MentionsInner _inner; @@ -830,6 +833,7 @@ private: ChannelData *_channel; QString _filter; QRect _boundings; + bool _addInlineBots; int32 _width, _height; bool _hiding; diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index 0fb84b15b..09b132740 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -129,7 +129,7 @@ namespace Ui { void showPeerHistoryAsync(const PeerId &peer, MsgId msgId) { if (MainWidget *m = App::main()) { - QMetaObject::invokeMethod(m, SLOT(ui_showPeerHistoryAsync(quint64,qint32)), Qt::QueuedConnection, Q_ARG(quint64, peer), Q_ARG(qint32, msgId)); + QMetaObject::invokeMethod(m, "ui_showPeerHistoryAsync", Qt::QueuedConnection, Q_ARG(quint64, peer), Q_ARG(qint32, msgId)); } } @@ -149,6 +149,10 @@ namespace Notify { if (MainWidget *m = App::main()) m->notify_botCommandsChanged(user); } + void inlineBotRequesting(bool requesting) { + if (MainWidget *m = App::main()) m->notify_inlineBotRequesting(requesting); + } + void migrateUpdated(PeerData *peer) { if (MainWidget *m = App::main()) m->notify_migrateUpdated(peer); } diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h index 21db659b6..ed7ee718c 100644 --- a/Telegram/SourceFiles/facades.h +++ b/Telegram/SourceFiles/facades.h @@ -80,6 +80,8 @@ namespace Notify { void userIsContactChanged(UserData *user, bool fromThisApp = false); void botCommandsChanged(UserData *user); + void inlineBotRequesting(bool requesting); + void migrateUpdated(PeerData *peer); void clipStopperHidden(ClipStopperType type); diff --git a/Telegram/SourceFiles/gui/animation.cpp b/Telegram/SourceFiles/gui/animation.cpp index 79c64b8d3..7d43fb68e 100644 --- a/Telegram/SourceFiles/gui/animation.cpp +++ b/Telegram/SourceFiles/gui/animation.cpp @@ -184,7 +184,7 @@ void AnimationManager::clipCallback(ClipReader *reader, qint32 threadIndex, qint ClipReader::callback(reader, threadIndex, ClipReaderNotification(notification)); } -QPixmap _prepareFrame(const ClipFrameRequest &request, const QImage &original, QImage &cache, bool hasAlpha) { +QPixmap _prepareFrame(const ClipFrameRequest &request, const QImage &original, bool hasAlpha, QImage &cache) { bool badSize = (original.width() != request.framew) || (original.height() != request.frameh); bool needOuter = (request.outerw != request.framew) || (request.outerh != request.frameh); if (badSize || needOuter || hasAlpha || request.rounded) { @@ -258,14 +258,16 @@ ClipReader::Frame *ClipReader::frameToShow() const { // 0 means not ready return _frames + (((step + 1) / 2) % 3); } -ClipReader::Frame *ClipReader::frameToWrite() const { // 0 means not ready - int32 step = _step.loadAcquire(); - if (step == FirstFrameNotReadStep) { - return _frames; - } else if (step == WaitingForRequestStep) { +ClipReader::Frame *ClipReader::frameToWrite(int32 *index) const { // 0 means not ready + int32 step = _step.loadAcquire(), i = 0; + if (step == WaitingForRequestStep) { + if (index) *index = 0; return 0; + } else if (step != FirstFrameNotReadStep) { + i = (((step + 3) / 2) % 3); } - return _frames + (((step + 3) / 2) % 3); + if (index) *index = i; + return _frames + i; } ClipReader::Frame *ClipReader::frameToRequestOther(bool check) const { @@ -342,10 +344,10 @@ QPixmap ClipReader::current(int32 framew, int32 frameh, int32 outerw, int32 oute frame->request.frameh = frameh * factor; frame->request.outerw = outerw * factor; frame->request.outerh = outerh * factor; - frame->pix = QPixmap(); - QImage cache; - frame->pix = _prepareFrame(frame->request, frame->original, cache, true); + QImage cacheForResize; + frame->pix = QPixmap(); + frame->pix = _prepareFrame(frame->request, frame->original, true, cacheForResize); Frame *other = frameToRequestOther(true); if (other) other->request = frame->request; @@ -830,8 +832,7 @@ public: , _location(_data.isEmpty() ? new FileLocation(location) : 0) , _accessed(false) , _implementation(0) - , _currentHasAlpha(true) - , _nextHasAlpha(true) + , _frame(_frames) , _width(0) , _height(0) , _previousMs(0) @@ -850,12 +851,12 @@ public: if (!_implementation && !init()) { return error(); } - if (_currentOriginal.isNull()) { - if (!_implementation->readNextFrame(_currentOriginal, _currentHasAlpha, QSize())) { + if (_frame->original.isNull()) { + if (!_implementation->readNextFrame(_frame->original, _frame->alpha, QSize())) { return error(); } - _width = _currentOriginal.width(); - _height = _currentOriginal.height(); + _width = _frame->original.width(); + _height = _frame->original.height(); return ClipProcessReinit; } return ClipProcessWait; @@ -868,12 +869,12 @@ public: return start(ms); } - if (_current.isNull()) { // first frame read, but not yet prepared - _currentOriginal.setDevicePixelRatio(_request.factor); + if (_frame->pix.isNull()) { // first frame read, but not yet prepared + _frame->original.setDevicePixelRatio(_request.factor); _previousMs = _currentMs; _currentMs = ms; - _current = _prepareFrame(_request, _currentOriginal, _currentCache, _currentHasAlpha); + _frame->pix = _prepareFrame(_request, _frame->original, _frame->alpha, _frame->cache); if (!prepareNextFrame()) { return error(); @@ -906,20 +907,16 @@ public: void swapBuffers(uint64 ms = 0) { _previousMs = _currentMs; _currentMs = qMax(ms, _nextUpdateMs); - qSwap(_currentOriginal, _nextOriginal); - qSwap(_current, _next); - qSwap(_currentCache, _nextCache); - qSwap(_currentHasAlpha, _nextHasAlpha); } bool prepareNextFrame() { - if (!_implementation->readNextFrame(_nextOriginal, _nextHasAlpha, QSize(_request.framew, _request.frameh))) { + if (!_implementation->readNextFrame(_frame->original, _frame->alpha, QSize(_request.framew, _request.frameh))) { return false; } _nextUpdateMs = _currentMs + nextFrameDelay(); - _nextOriginal.setDevicePixelRatio(_request.factor); - _next = QPixmap(); - _next = _prepareFrame(_request, _nextOriginal, _nextCache, _nextHasAlpha); + _frame->original.setDevicePixelRatio(_request.factor); + _frame->pix = QPixmap(); + _frame->pix = _prepareFrame(_request, _frame->original, _frame->alpha, _frame->cache); return true; } @@ -979,9 +976,16 @@ private: ClipReaderImplementation *_implementation; ClipFrameRequest _request; - QPixmap _current, _next; - QImage _currentOriginal, _nextOriginal, _currentCache, _nextCache; - bool _currentHasAlpha, _nextHasAlpha; + struct Frame { + Frame() : alpha(true) { + } + QPixmap pix; + QImage original, cache; + bool alpha; + }; + Frame _frames[3]; + Frame *_frame; + int32 _width, _height; uint64 _previousMs, _currentMs, _nextUpdateMs; @@ -1016,34 +1020,58 @@ void ClipReadManager::start(ClipReader *reader) { } void ClipReadManager::update(ClipReader *reader) { - QMutexLocker lock(&_readerPointersMutex); - _readerPointers.insert(reader, reader->_private); + QReadLocker lock(&_readerPointersMutex); + ReaderPointers::const_iterator i = _readerPointers.constFind(reader); + if (i == _readerPointers.cend()) { + lock.unlock(); + + QWriteLocker lock(&_readerPointersMutex); + _readerPointers.insert(reader, MutableAtomicInt(1)); + } else { + i->v.storeRelease(1); + } emit processDelayed(); } void ClipReadManager::stop(ClipReader *reader) { - QMutexLocker lock(&_readerPointersMutex); + if (!carries(reader)) return; + + QWriteLocker lock(&_readerPointersMutex); _readerPointers.remove(reader); emit processDelayed(); } bool ClipReadManager::carries(ClipReader *reader) const { - QMutexLocker lock(&_readerPointersMutex); + QReadLocker lock(&_readerPointersMutex); return _readerPointers.contains(reader); } -bool ClipReadManager::handleProcessResult(ClipReaderPrivate *reader, ClipProcessResult result, uint64 ms) { - QMutexLocker lock(&_readerPointersMutex); +ClipReadManager::ReaderPointers::iterator ClipReadManager::unsafeFindReaderPointer(ClipReaderPrivate *reader) { ReaderPointers::iterator it = _readerPointers.find(reader->_interface); - if (it != _readerPointers.cend() && it.key()->_private != reader) { - it = _readerPointers.end(); // it is a new reader which was realloced in the same address - } + + // could be a new reader which was realloced in the same address + return (it == _readerPointers.cend() || it.key()->_private == reader) ? it : _readerPointers.end(); +} + +ClipReadManager::ReaderPointers::const_iterator ClipReadManager::constUnsafeFindReaderPointer(ClipReaderPrivate *reader) const { + ReaderPointers::const_iterator it = _readerPointers.constFind(reader->_interface); + + // could be a new reader which was realloced in the same address + return (it == _readerPointers.cend() || it.key()->_private == reader) ? it : _readerPointers.cend(); +} + +bool ClipReadManager::handleProcessResult(ClipReaderPrivate *reader, ClipProcessResult result, uint64 ms) { + QReadLocker lock(&_readerPointersMutex); + ReaderPointers::const_iterator it = constUnsafeFindReaderPointer(reader); if (result == ClipProcessError) { if (it != _readerPointers.cend()) { it.key()->error(); emit callback(it.key(), it.key()->threadIndex(), ClipReaderReinit); - _readerPointers.erase(it); + lock.unlock(); + QWriteLocker lock(&_readerPointersMutex); + ReaderPointers::iterator i = unsafeFindReaderPointer(reader); + if (i != _readerPointers.cend()) _readerPointers.erase(i); } return false; } @@ -1066,9 +1094,9 @@ bool ClipReadManager::handleProcessResult(ClipReaderPrivate *reader, ClipProcess if (result == ClipProcessReinit || result == ClipProcessRepaint || result == ClipProcessStarted) { ClipReader::Frame *frame = it.key()->frameToWrite(); t_assert(frame != 0); - frame->pix = QPixmap(); - frame->pix = reader->_current; - frame->original = reader->_currentOriginal; + frame->clear(); + frame->pix = reader->_frame->pix; + frame->original = reader->_frame->original; frame->displayed = false; it.key()->moveToNextWrite(); if (result == ClipProcessReinit) { @@ -1082,7 +1110,7 @@ bool ClipReadManager::handleProcessResult(ClipReaderPrivate *reader, ClipProcess ClipReadManager::ResultHandleState ClipReadManager::handleResult(ClipReaderPrivate *reader, ClipProcessResult result, uint64 ms) { if (!handleProcessResult(reader, result, ms)) { - _loadLevel.fetchAndAddRelaxed(-1 * (reader->_currentOriginal.isNull() ? AverageGifSize : reader->_width * reader->_height)); + _loadLevel.fetchAndAddRelaxed(-1 * (reader->_frame->original.isNull() ? AverageGifSize : reader->_width * reader->_height)); delete reader; return ResultHandleRemove; } @@ -1110,27 +1138,37 @@ void ClipReadManager::process() { uint64 ms = getms(), minms = ms + 86400 * 1000ULL; { - QMutexLocker lock(&_readerPointersMutex); - for (ReaderPointers::iterator i = _readerPointers.begin(), e = _readerPointers.end(); i != e; ++i) { - if (i.value()) { - Readers::iterator it = _readers.find(i.value()); - if (it == _readers.cend()) { - _readers.insert(i.value(), 0); + QReadLocker lock(&_readerPointersMutex); + for (ReaderPointers::iterator it = _readerPointers.begin(), e = _readerPointers.end(); it != e; ++it) { + if (it->v.loadAcquire()) { + Readers::iterator i = _readers.find(it.key()->_private); + if (i == _readers.cend()) { + _readers.insert(it.key()->_private, 0); } else { - it.value() = ms; - if (it.key()->_paused && !i.key()->_paused.loadAcquire()) { - it.key()->_paused = false; + i.value() = ms; + if (i.key()->_paused && !it.key()->_paused.loadAcquire()) { + i.key()->_paused = false; } } - ClipReader::Frame *frame = i.key()->frameToWrite(); - if (frame) i.value()->_request = frame->request; - i.value() = 0; + ClipReader::Frame *frame = it.key()->frameToWrite(); + if (frame) it.key()->_private->_request = frame->request; + it->v.storeRelease(0); } } } for (Readers::iterator i = _readers.begin(), e = _readers.end(); i != e;) { if (i.value() <= ms) { + { + QReadLocker lock(&_readerPointersMutex); + ReaderPointers::const_iterator it = constUnsafeFindReaderPointer(i.key()); + if (it != _readerPointers.cend()) { + int32 index = 0; + ClipReader::Frame *frame = it.key()->frameToWrite(&index); + if (frame) frame->clear(); + i.key()->_frame = i.key()->_frames + index; + } + } ClipProcessResult result = i.key()->process(ms); ResultHandleState state = handleResult(i.key(), result, ms); @@ -1167,13 +1205,13 @@ void ClipReadManager::finish() { } void ClipReadManager::clear() { - QMutexLocker lock(&_readerPointersMutex); - for (ReaderPointers::iterator i = _readerPointers.begin(), e = _readerPointers.end(); i != e; ++i) { - if (i.value()) { - i.key()->_private = 0; - } - } - _readerPointers.clear(); + { + QWriteLocker lock(&_readerPointersMutex); + for (ReaderPointers::iterator it = _readerPointers.begin(), e = _readerPointers.end(); it != e; ++it) { + it.key()->_private = 0; + } + _readerPointers.clear(); + } for (Readers::iterator i = _readers.begin(), e = _readers.end(); i != e; ++i) { delete i.key(); @@ -1195,12 +1233,12 @@ MTPDocumentAttribute clipReadAnimatedAttributes(const QString &fname, const QByt if (reader->readNextFrame(cover, hasAlpha, QSize())) { if (cover.width() > 0 && cover.height() > 0 && cover.width() < cover.height() * 10 && cover.height() < cover.width() * 10) { if (hasAlpha) { - QImage cache; + QImage cacheForResize; ClipFrameRequest request; request.framew = request.outerw = cover.width(); request.frameh = request.outerh = cover.height(); request.factor = 1; - cover = _prepareFrame(request, cover, cache, hasAlpha).toImage(); + cover = _prepareFrame(request, cover, hasAlpha, cacheForResize).toImage(); } int32 duration = reader->duration(); delete reader; diff --git a/Telegram/SourceFiles/gui/animation.h b/Telegram/SourceFiles/gui/animation.h index 4ae1fa8e1..ef932df53 100644 --- a/Telegram/SourceFiles/gui/animation.h +++ b/Telegram/SourceFiles/gui/animation.h @@ -563,6 +563,10 @@ private: struct Frame { Frame() : displayed(false), when(0) { } + void clear() { + pix = QPixmap(); + original = QImage(); + } QPixmap pix; QImage original; ClipFrameRequest request; @@ -571,7 +575,7 @@ private: }; mutable Frame _frames[3]; Frame *frameToShow() const; // 0 means not ready - Frame *frameToWrite() const; // 0 means not ready + Frame *frameToWrite(int32 *index = 0) const; // 0 means not ready Frame *frameToRequestOther(bool check) const; void moveToNextShow() const; void moveToNextWrite() const; @@ -629,9 +633,17 @@ private: void clear(); QAtomicInt _loadLevel; - typedef QMap ReaderPointers; + struct MutableAtomicInt { + MutableAtomicInt(int value) : v(value) { + } + mutable QAtomicInt v; + }; + typedef QMap ReaderPointers; ReaderPointers _readerPointers; - mutable QMutex _readerPointersMutex; + mutable QReadWriteLock _readerPointersMutex; + + ReaderPointers::const_iterator constUnsafeFindReaderPointer(ClipReaderPrivate *reader) const; + ReaderPointers::iterator unsafeFindReaderPointer(ClipReaderPrivate *reader); bool handleProcessResult(ClipReaderPrivate *reader, ClipProcessResult result, uint64 ms); diff --git a/Telegram/SourceFiles/gui/flatbutton.cpp b/Telegram/SourceFiles/gui/flatbutton.cpp index e02c47fa2..e41f13b35 100644 --- a/Telegram/SourceFiles/gui/flatbutton.cpp +++ b/Telegram/SourceFiles/gui/flatbutton.cpp @@ -289,6 +289,59 @@ void MaskedButton::paintEvent(QPaintEvent *e) { } } +EmojiButton::EmojiButton(QWidget *parent, const style::iconedButton &st) : IconedButton(parent, st) +, _loading(false) +, _a_loading(animation(this, &EmojiButton::step_loading)) { +} + +void EmojiButton::paintEvent(QPaintEvent *e) { + QPainter p(this); + + uint64 ms = getms(); + float64 loading = a_loading.current(ms, _loading ? 1 : 0); + p.setOpacity(_opacity * (1 - loading)); + + p.fillRect(e->rect(), a_bg.current()); + + p.setOpacity(a_opacity.current() * _opacity * (1 - loading)); + + const QRect &i((_state & StateDown) ? _st.downIcon : _st.icon); + if (i.width()) { + const QPoint &t((_state & StateDown) ? _st.downIconPos : _st.iconPos); + p.drawPixmap(t, App::sprite(), i); + } + + QRect inner(QPoint((width() - st::emojiCircle.width()) / 2, st::emojiCircleTop), st::emojiCircle); + int32 full = 5760; + int32 start = qRound(full * float64(ms % uint64(st::emojiCirclePeriod)) / st::emojiCirclePeriod), part = qRound(full / st::emojiCirclePart); + + p.setBrush(Qt::NoBrush); + p.setRenderHint(QPainter::HighQualityAntialiasing); + + p.setPen(QPen(st::emojiCircleFg->c, st::emojiCircleLine)); + p.setOpacity(a_opacity.current() * _opacity); + p.drawEllipse(inner); + + p.setPen(QPen(st::white->c, st::emojiCircleLine)); + p.setOpacity(loading); + p.drawArc(inner, (full - start) % full, part); + + p.setRenderHint(QPainter::HighQualityAntialiasing, false); +} + +void EmojiButton::setLoading(bool loading) { + if (_loading != loading) { + EnsureAnimation(a_loading, _loading ? 1. : 0., func(this, &EmojiButton::update)); + a_loading.start(loading ? 1. : 0., st::emojiCircleDuration); + _loading = loading; + if (_loading) { + _a_loading.start(); + } else { + _a_loading.stop(); + } + } +} + BoxButton::BoxButton(QWidget *parent, const QString &text, const style::BoxButton &st) : Button(parent) , _text(text.toUpper()) , _fullText(text.toUpper()) diff --git a/Telegram/SourceFiles/gui/flatbutton.h b/Telegram/SourceFiles/gui/flatbutton.h index 9f542949e..f07b0c4a2 100644 --- a/Telegram/SourceFiles/gui/flatbutton.h +++ b/Telegram/SourceFiles/gui/flatbutton.h @@ -106,7 +106,7 @@ public: void setText(const QString &text); QString getText() const; - + public slots: void onStateChange(int oldState, ButtonStateChangeSource source); @@ -136,6 +136,28 @@ public: }; +class EmojiButton : public IconedButton { + Q_OBJECT + +public: + EmojiButton(QWidget *parent, const style::iconedButton &st); + + void paintEvent(QPaintEvent *e); + void setLoading(bool loading); + +private: + bool _loading; + FloatAnimation a_loading; + Animation _a_loading; + + void step_loading(uint64 ms, bool timer) { + if (timer) { + update(); + } + } + +}; + class BoxButton : public Button { Q_OBJECT diff --git a/Telegram/SourceFiles/gui/flattextarea.cpp b/Telegram/SourceFiles/gui/flattextarea.cpp index ae09ce169..4b88c0110 100644 --- a/Telegram/SourceFiles/gui/flattextarea.cpp +++ b/Telegram/SourceFiles/gui/flattextarea.cpp @@ -270,7 +270,9 @@ EmojiPtr FlatTextarea::getSingleEmoji() const { return 0; } -void FlatTextarea::getMentionHashtagBotCommandStart(QString &start, UserData *&inlineBot, QString &inlineBotUsername) const { +QString FlatTextarea::getMentionHashtagBotCommandPart(bool &start, UserData *&inlineBot, QString &inlineBotUsername) const { + start = false; + // check inline bot query const QString &text(getLastText()); int32 inlineUsernameStart = 1, inlineUsernameLength = 0, size = text.size(); @@ -301,13 +303,12 @@ void FlatTextarea::getMentionHashtagBotCommandStart(QString &start, UserData *&i inlineBot = InlineBotLookingUpData; } } - if (inlineBot == InlineBotLookingUpData) return; + if (inlineBot == InlineBotLookingUpData) return QString(); if (inlineBot && (!inlineBot->botInfo || inlineBot->botInfo->inlinePlaceholder.isEmpty())) { inlineBot = 0; } else { - start = text.mid(inlineUsernameStart + inlineUsernameLength + 1); - return; + return text.mid(inlineUsernameStart + inlineUsernameLength + 1); } } else { inlineUsernameLength = 0; @@ -319,7 +320,7 @@ void FlatTextarea::getMentionHashtagBotCommandStart(QString &start, UserData *&i } int32 pos = textCursor().position(); - if (textCursor().anchor() != pos) return; + if (textCursor().anchor() != pos) return QString(); // check mention / hashtag / bot command QTextDocument *doc(document()); @@ -339,29 +340,33 @@ void FlatTextarea::getMentionHashtagBotCommandStart(QString &start, UserData *&i for (int i = pos - p; i > 0; --i) { if (t.at(i - 1) == '@') { if ((pos - p - i < 1 || t.at(i).isLetter()) && (i < 2 || !(t.at(i - 2).isLetterOrNumber() || t.at(i - 2) == '_'))) { - start = t.mid(i - 1, pos - p - i + 1); + start = (i == 1) && (p == 0); + return t.mid(i - 1, pos - p - i + 1); } else if ((pos - p - i < 1 || t.at(i).isLetter()) && i > 2 && (t.at(i - 2).isLetterOrNumber() || t.at(i - 2) == '_') && !mentionInCommand) { mentionInCommand = true; --i; continue; } - return; + return QString(); } else if (t.at(i - 1) == '#') { if (i < 2 || !(t.at(i - 2).isLetterOrNumber() || t.at(i - 2) == '_')) { - start = t.mid(i - 1, pos - p - i + 1); + start = (i == 1) && (p == 0); + return t.mid(i - 1, pos - p - i + 1); } - return; + return QString(); } else if (t.at(i - 1) == '/') { if (i < 2) { - start = t.mid(i - 1, pos - p - i + 1); + start = (i == 1) && (p == 0); + return t.mid(i - 1, pos - p - i + 1); } - return; + return QString(); } if (pos - p - i > 127 || (!mentionInCommand && (pos - p - i > 63))) break; if (!t.at(i - 1).isLetterOrNumber() && t.at(i - 1) != '_') break; } - return; + break; } + return QString(); } void FlatTextarea::onMentionHashtagOrBotCommandInsert(QString str) { diff --git a/Telegram/SourceFiles/gui/flattextarea.h b/Telegram/SourceFiles/gui/flattextarea.h index 8020a8d95..c7e74efde 100644 --- a/Telegram/SourceFiles/gui/flattextarea.h +++ b/Telegram/SourceFiles/gui/flattextarea.h @@ -64,7 +64,7 @@ public: QSize minimumSizeHint() const; EmojiPtr getSingleEmoji() const; - void getMentionHashtagBotCommandStart(QString &start, UserData *&contextBot, QString &contextBotUsername) const; + QString getMentionHashtagBotCommandPart(bool &start, UserData *&contextBot, QString &contextBotUsername) const; void removeSingleEmoji(); bool hasText() const; diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 18da2d243..a373a9413 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -3028,6 +3028,10 @@ void HistoryWidget::notify_botCommandsChanged(UserData *user) { } } +void HistoryWidget::notify_inlineBotRequesting(bool requesting) { + _attachEmoji.setLoading(requesting); +} + void HistoryWidget::notify_userIsBotChanged(UserData *user) { if (_peer && _peer == user) { _list->notifyIsBotChanged(); @@ -4971,6 +4975,7 @@ bool HistoryWidget::hasBroadcastToggle() const { } void HistoryWidget::inlineBotResolveDone(const MTPcontacts_ResolvedPeer &result) { + Notify::inlineBotRequesting(false); _inlineBotUsername = QString(); if (result.type() == mtpc_contacts_resolvedPeer) { const MTPDcontacts_resolvedPeer &d(result.c_contacts_resolvedPeer()); @@ -4982,6 +4987,8 @@ void HistoryWidget::inlineBotResolveDone(const MTPcontacts_ResolvedPeer &result) bool HistoryWidget::inlineBotResolveFail(QString name, const RPCError &error) { if (mtpIsFlood(error)) return false; + + Notify::inlineBotRequesting(false); if (name == _inlineBotUsername) { _inlineBot = 0; onCheckMentionDropdown(); @@ -5331,14 +5338,17 @@ void HistoryWidget::onCheckMentionDropdown() { if (!_history || _a_show.animating()) return; UserData *bot = _inlineBot; - QString start, inlineBotUsername(_inlineBotUsername); - _field.getMentionHashtagBotCommandStart(start, _inlineBot, _inlineBotUsername); + bool start = false; + QString inlineBotUsername(_inlineBotUsername); + QString query = _field.getMentionHashtagBotCommandPart(start, _inlineBot, _inlineBotUsername); if (inlineBotUsername != _inlineBotUsername) { if (_inlineBotResolveRequestId) { + Notify::inlineBotRequesting(false); MTP::cancel(_inlineBotResolveRequestId); _inlineBotResolveRequestId = 0; } if (_inlineBot == InlineBotLookingUpData) { + Notify::inlineBotRequesting(true); _inlineBotResolveRequestId = MTP::send(MTPcontacts_ResolveUsername(MTP_string(_inlineBotUsername)), rpcDone(&HistoryWidget::inlineBotResolveDone), rpcFail(&HistoryWidget::inlineBotResolveFail, _inlineBotUsername)); return; } @@ -5350,10 +5360,10 @@ void HistoryWidget::onCheckMentionDropdown() { if (_inlineBot != bot) { updateFieldPlaceholder(); } - if (_inlineBot->username == (cTestMode() ? qstr("contextbot") : qstr("gif")) && start.isEmpty()) { + if (_inlineBot->username == (cTestMode() ? qstr("contextbot") : qstr("gif")) && query.isEmpty()) { _emojiPan.clearInlineBot(); } else { - _emojiPan.queryInlineBot(_inlineBot, start); + _emojiPan.queryInlineBot(_inlineBot, query); } if (!_attachMention.isHidden()) { _attachMention.hideStart(); @@ -5364,16 +5374,12 @@ void HistoryWidget::onCheckMentionDropdown() { _field.finishPlaceholder(); } _emojiPan.clearInlineBot(); - if (!start.isEmpty()) { - if (start.at(0) == '#' && cRecentWriteHashtags().isEmpty() && cRecentSearchHashtags().isEmpty()) Local::readRecentHashtags(); - if (start.at(0) == '@' && _peer->isUser()) return; - if (start.at(0) == '/' && _peer->isUser() && !_peer->asUser()->botInfo) return; - _attachMention.showFiltered(_peer, start); - } else { - if (!_attachMention.isHidden()) { - _attachMention.hideStart(); - } + if (!query.isEmpty()) { + if (query.at(0) == '#' && cRecentWriteHashtags().isEmpty() && cRecentSearchHashtags().isEmpty()) Local::readRecentHashtagsAndBots(); + if (query.at(0) == '@' && cRecentInlineBots().isEmpty()) Local::readRecentHashtagsAndBots(); + if (query.at(0) == '/' && _peer->isUser() && !_peer->asUser()->botInfo) return; } + _attachMention.showFiltered(_peer, query, start); } } @@ -6419,6 +6425,18 @@ void HistoryWidget::onInlineResultSend(InlineResult *result, UserData *bot) { _saveDraftStart = getms(); onDraftSave(); + RecentInlineBots &bots(cRefRecentInlineBots()); + int32 index = bots.indexOf(bot); + if (index) { + if (index > 0) { + bots.removeAt(index); + } else if (bots.size() >= RecentInlineBotsLimit) { + bots.resize(RecentInlineBotsLimit - 1); + } + bots.push_front(bot); + Local::writeRecentHashtagsAndBots(); + } + onCheckMentionDropdown(); if (!_attachType.isHidden()) _attachType.hideStart(); diff --git a/Telegram/SourceFiles/historywidget.h b/Telegram/SourceFiles/historywidget.h index f3f7acb31..a7a3e6d02 100644 --- a/Telegram/SourceFiles/historywidget.h +++ b/Telegram/SourceFiles/historywidget.h @@ -568,6 +568,7 @@ public: void notify_historyItemLayoutChanged(const HistoryItem *item); void notify_automaticLoadSettingsChangedGif(); void notify_botCommandsChanged(UserData *user); + void notify_inlineBotRequesting(bool requesting); void notify_userIsBotChanged(UserData *user); void notify_migrateUpdated(PeerData *peer); void notify_clipStopperHidden(ClipStopperType type); @@ -784,7 +785,9 @@ private: FlatButton _send, _unblock, _botStart, _joinChannel, _muteUnmute; mtpRequestId _unblockRequest, _reportSpamRequest; - IconedButton _attachDocument, _attachPhoto, _attachEmoji, _kbShow, _kbHide, _cmdStart; + IconedButton _attachDocument, _attachPhoto; + EmojiButton _attachEmoji; + IconedButton _kbShow, _kbHide, _cmdStart; FlatCheckbox _broadcast; bool _cmdStartShown; MessageField _field; diff --git a/Telegram/SourceFiles/layout.cpp b/Telegram/SourceFiles/layout.cpp index a127836ca..e1ac81379 100644 --- a/Telegram/SourceFiles/layout.cpp +++ b/Telegram/SourceFiles/layout.cpp @@ -1529,13 +1529,14 @@ LayoutInlineGif::~LayoutInlineGif() { } void LayoutInlineGif::prepareThumb(int32 width, int32 height, const QSize &frame) const { - if (_doc && !_doc->thumb->isNull()) { - if (_doc->thumb->loaded()) { + DocumentData *doc = _doc ? _doc : (_result ? _result->doc : 0); + if (doc && !doc->thumb->isNull()) { + if (doc->thumb->loaded()) { if (_thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) { - _thumb = _doc->thumb->pixNoCache(frame.width() * cIntRetinaFactor(), frame.height() * cIntRetinaFactor(), true, false, false, width, height); + _thumb = doc->thumb->pixNoCache(frame.width() * cIntRetinaFactor(), frame.height() * cIntRetinaFactor(), true, false, false, width, height); } } else { - _doc->thumb->load(); + doc->thumb->load(); } } else if (_result && !_result->thumb_url.isEmpty()) { if (_result->thumb->loaded()) { @@ -1746,19 +1747,20 @@ QSize LayoutInlinePhoto::countFrameSize() const { } void LayoutInlinePhoto::prepareThumb(int32 width, int32 height, const QSize &frame) const { - if (_photo) { - if (_photo->medium->loaded()) { + PhotoData *photo = _photo ? _photo : (_result ? _result->photo : 0); + if (photo) { + if (photo->medium->loaded()) { if (!_thumbLoaded || _thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) { - _thumb = _photo->medium->pixNoCache(frame.width() * cIntRetinaFactor(), frame.height() * cIntRetinaFactor(), true, false, false, width, height); + _thumb = photo->medium->pixNoCache(frame.width() * cIntRetinaFactor(), frame.height() * cIntRetinaFactor(), true, false, false, width, height); } _thumbLoaded = true; } else { - if (_photo->thumb->loaded()) { + if (photo->thumb->loaded()) { if (_thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) { - _thumb = _photo->thumb->pixNoCache(frame.width() * cIntRetinaFactor(), frame.height() * cIntRetinaFactor(), true, false, false, width, height); + _thumb = photo->thumb->pixNoCache(frame.width() * cIntRetinaFactor(), frame.height() * cIntRetinaFactor(), true, false, false, width, height); } } - _photo->medium->load(); + photo->medium->load(); } } else { if (_result->thumb->loaded()) { diff --git a/Telegram/SourceFiles/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp index de6800d5b..2e34afc7f 100644 --- a/Telegram/SourceFiles/localstorage.cpp +++ b/Telegram/SourceFiles/localstorage.cpp @@ -538,22 +538,22 @@ namespace { FileKey _dataNameKey = 0; enum { // Local Storage Keys - lskUserMap = 0x00, - lskDraft = 0x01, // data: PeerId peer - lskDraftPosition = 0x02, // data: PeerId peer - lskImages = 0x03, // data: StorageKey location - lskLocations = 0x04, // no data - lskStickerImages = 0x05, // data: StorageKey location - lskAudios = 0x06, // data: StorageKey location - lskRecentStickersOld = 0x07, // no data - lskBackground = 0x08, // no data - lskUserSettings = 0x09, // no data - lskRecentHashtags = 0x0a, // no data - lskStickers = 0x0b, // no data - lskSavedPeers = 0x0c, // no data - lskReportSpamStatuses = 0x0d, // no data - lskSavedGifsOld = 0x0e, - lskSavedGifs = 0x0f, + lskUserMap = 0x00, + lskDraft = 0x01, // data: PeerId peer + lskDraftPosition = 0x02, // data: PeerId peer + lskImages = 0x03, // data: StorageKey location + lskLocations = 0x04, // no data + lskStickerImages = 0x05, // data: StorageKey location + lskAudios = 0x06, // data: StorageKey location + lskRecentStickersOld = 0x07, // no data + lskBackground = 0x08, // no data + lskUserSettings = 0x09, // no data + lskRecentHashtagsAndBots = 0x0a, // no data + lskStickers = 0x0b, // no data + lskSavedPeers = 0x0c, // no data + lskReportSpamStatuses = 0x0d, // no data + lskSavedGifsOld = 0x0e, // no data + lskSavedGifs = 0x0f, // no data }; typedef QMap DraftsMap; @@ -581,8 +581,8 @@ namespace { bool _backgroundWasRead = false; FileKey _userSettingsKey = 0; - FileKey _recentHashtagsKey = 0; - bool _recentHashtagsWereRead = false; + FileKey _recentHashtagsAndBotsKey = 0; + bool _recentHashtagsAndBotsWereRead = false; FileKey _savedPeersKey = 0; @@ -726,18 +726,20 @@ namespace { _fileLocationAliases.insert(MediaKey(kfirst, ksecond), MediaKey(vfirst, vsecond)); } - _storageWebFilesSize = 0; - _webFilesMap.clear(); + if (!locations.stream.atEnd()) { + _storageWebFilesSize = 0; + _webFilesMap.clear(); - quint32 webLocationsCount; - locations.stream >> webLocationsCount; - for (quint32 i = 0; i < webLocationsCount; ++i) { - QString url; - quint64 key; - qint32 size; - locations.stream >> url >> key >> size; - _webFilesMap.insert(url, FileDesc(key, size)); - _storageWebFilesSize += size; + quint32 webLocationsCount; + locations.stream >> webLocationsCount; + for (quint32 i = 0; i < webLocationsCount; ++i) { + QString url; + quint64 key; + qint32 size; + locations.stream >> url >> key >> size; + _webFilesMap.insert(url, FileDesc(key, size)); + _storageWebFilesSize += size; + } } } } @@ -1667,7 +1669,7 @@ namespace { qint64 storageImagesSize = 0, storageStickersSize = 0, storageAudiosSize = 0; quint64 locationsKey = 0, reportSpamStatusesKey = 0; quint64 recentStickersKeyOld = 0, stickersKey = 0, savedGifsKey = 0; - quint64 backgroundKey = 0, userSettingsKey = 0, recentHashtagsKey = 0, savedPeersKey = 0; + quint64 backgroundKey = 0, userSettingsKey = 0, recentHashtagsAndBotsKey = 0, savedPeersKey = 0; while (!map.stream.atEnd()) { quint32 keyType; map.stream >> keyType; @@ -1744,8 +1746,8 @@ namespace { case lskUserSettings: { map.stream >> userSettingsKey; } break; - case lskRecentHashtags: { - map.stream >> recentHashtagsKey; + case lskRecentHashtagsAndBots: { + map.stream >> recentHashtagsAndBotsKey; } break; case lskStickers: { map.stream >> stickersKey; @@ -1788,7 +1790,7 @@ namespace { _savedPeersKey = savedPeersKey; _backgroundKey = backgroundKey; _userSettingsKey = userSettingsKey; - _recentHashtagsKey = recentHashtagsKey; + _recentHashtagsAndBotsKey = recentHashtagsAndBotsKey; _oldMapVersion = mapData.version; if (_oldMapVersion < AppVersion) { _mapChanged = true; @@ -1861,7 +1863,7 @@ namespace { if (_savedPeersKey) mapSize += sizeof(quint32) + sizeof(quint64); if (_backgroundKey) mapSize += sizeof(quint32) + sizeof(quint64); if (_userSettingsKey) mapSize += sizeof(quint32) + sizeof(quint64); - if (_recentHashtagsKey) mapSize += sizeof(quint32) + sizeof(quint64); + if (_recentHashtagsAndBotsKey) mapSize += sizeof(quint32) + sizeof(quint64); EncryptedDescriptor mapData(mapSize); if (!_draftsMap.isEmpty()) { mapData.stream << quint32(lskDraft) << quint32(_draftsMap.size()); @@ -1917,8 +1919,8 @@ namespace { if (_userSettingsKey) { mapData.stream << quint32(lskUserSettings) << quint64(_userSettingsKey); } - if (_recentHashtagsKey) { - mapData.stream << quint32(lskRecentHashtags) << quint64(_recentHashtagsKey); + if (_recentHashtagsAndBotsKey) { + mapData.stream << quint32(lskRecentHashtagsAndBots) << quint64(_recentHashtagsAndBotsKey); } map.writeEncrypted(mapData); @@ -2187,7 +2189,7 @@ namespace Local { _storageWebFilesSize = 0; _locationsKey = _reportSpamStatusesKey = 0; _recentStickersKeyOld = _stickersKey = _savedGifsKey = 0; - _backgroundKey = _userSettingsKey = _recentHashtagsKey = _savedPeersKey = 0; + _backgroundKey = _userSettingsKey = _recentHashtagsAndBotsKey = _savedPeersKey = 0; _oldMapVersion = _oldSettingsVersion = 0; _mapChanged = true; _writeMap(WriteMapNow); @@ -3258,89 +3260,6 @@ namespace Local { return false; } - void writeRecentHashtags() { - if (!_working()) return; - - const RecentHashtagPack &write(cRecentWriteHashtags()), &search(cRecentSearchHashtags()); - if (write.isEmpty() && search.isEmpty()) readRecentHashtags(); - if (write.isEmpty() && search.isEmpty()) { - if (_recentHashtagsKey) { - clearKey(_recentHashtagsKey); - _recentHashtagsKey = 0; - _mapChanged = true; - } - _writeMap(); - } else { - if (!_recentHashtagsKey) { - _recentHashtagsKey = genKey(); - _mapChanged = true; - _writeMap(WriteMapFast); - } - quint32 size = sizeof(quint32) * 2, writeCnt = 0, searchCnt = 0; - for (RecentHashtagPack::const_iterator i = write.cbegin(); i != write.cend(); ++i) { - if (!i->first.isEmpty()) { - size += _stringSize(i->first) + sizeof(quint16); - ++writeCnt; - } - } - for (RecentHashtagPack::const_iterator i = search.cbegin(); i != search.cend(); ++i) { - if (!i->first.isEmpty()) { - size += _stringSize(i->first) + sizeof(quint16); - ++searchCnt; - } - } - EncryptedDescriptor data(size); - data.stream << quint32(writeCnt) << quint32(searchCnt); - for (RecentHashtagPack::const_iterator i = write.cbegin(); i != write.cend(); ++i) { - if (!i->first.isEmpty()) data.stream << i->first << quint16(i->second); - } - for (RecentHashtagPack::const_iterator i = search.cbegin(); i != search.cend(); ++i) { - if (!i->first.isEmpty()) data.stream << i->first << quint16(i->second); - } - FileWriteDescriptor file(_recentHashtagsKey); - file.writeEncrypted(data); - } - } - - void readRecentHashtags() { - if (_recentHashtagsWereRead) return; - _recentHashtagsWereRead = true; - - if (!_recentHashtagsKey) return; - - FileReadDescriptor hashtags; - if (!readEncryptedFile(hashtags, _recentHashtagsKey)) { - clearKey(_recentHashtagsKey); - _recentHashtagsKey = 0; - _writeMap(); - return; - } - - quint32 writeCount = 0, searchCount = 0; - hashtags.stream >> writeCount >> searchCount; - - QString tag; - quint16 count; - - RecentHashtagPack write, search; - if (writeCount) { - write.reserve(writeCount); - for (uint32 i = 0; i < writeCount; ++i) { - hashtags.stream >> tag >> count; - write.push_back(qMakePair(tag.trimmed(), count)); - } - } - if (searchCount) { - search.reserve(searchCount); - for (uint32 i = 0; i < searchCount; ++i) { - hashtags.stream >> tag >> count; - search.push_back(qMakePair(tag.trimmed(), count)); - } - } - cSetRecentWriteHashtags(write); - cSetRecentSearchHashtags(search); - } - uint32 _peerSize(PeerData *peer) { uint32 result = sizeof(quint64) + sizeof(quint64) + _storageImageLocationSize(); if (peer->isUser()) { @@ -3370,7 +3289,7 @@ namespace Local { return result; } - void _writePeer(QDataStream &stream, PeerData *peer) { + void _writePeer(QDataStream &stream, PeerData *peer, int32 fileVersion = AppVersion) { stream << quint64(peer->id) << quint64(peer->photoId); _writeStorageImageLocation(stream, peer->photoLoc); if (peer->isUser()) { @@ -3380,6 +3299,9 @@ namespace Local { if (AppVersion >= 9012) { stream << qint32(user->flags); } + if (AppVersion >= 9016 || fileVersion >= 9016) { + stream << (user->botInfo ? user->botInfo->inlinePlaceholder : QString()); + } stream << qint32(user->onlineTill) << qint32(user->contact) << qint32(user->botInfo ? user->botInfo->version : -1); } else if (peer->isChat()) { ChatData *chat = peer->asChat(); @@ -3396,47 +3318,60 @@ namespace Local { } } - PeerData *_readPeer(FileReadDescriptor &from) { + PeerData *_readPeer(FileReadDescriptor &from, int32 fileVersion = 0) { PeerData *result = 0; quint64 peerId = 0, photoId = 0; from.stream >> peerId >> photoId; StorageImageLocation photoLoc(_readStorageImageLocation(from)); - result = App::peer(peerId); - result->loaded = true; + result = App::peerLoaded(peerId); + bool wasLoaded = (result && result->loaded); + + if (!wasLoaded) { + result = App::peer(peerId); + result->loaded = true; + } if (result->isUser()) { UserData *user = result->asUser(); - QString first, last, phone, username; + QString first, last, phone, username, inlinePlaceholder; quint64 access; qint32 flags = 0, onlineTill, contact, botInfoVersion; from.stream >> first >> last >> phone >> username >> access; if (from.version >= 9012) { from.stream >> flags; } + if (from.version >= 9016 || fileVersion >= 9016) { + from.stream >> inlinePlaceholder; + } from.stream >> onlineTill >> contact >> botInfoVersion; bool showPhone = !isServiceUser(user->id) && (peerToUser(user->id) != MTP::authedId()) && (contact <= 0); QString pname = (showPhone && !phone.isEmpty()) ? App::formatPhone(phone) : QString(); - user->setName(first, last, pname, username); + if (!wasLoaded) { + user->setName(first, last, pname, username); - user->access = access; - user->flags = flags; - user->onlineTill = onlineTill; - user->contact = contact; - user->setBotInfoVersion(botInfoVersion); + user->access = access; + user->flags = flags; + user->onlineTill = onlineTill; + user->contact = contact; + user->setBotInfoVersion(botInfoVersion); + if (!inlinePlaceholder.isEmpty() && user->botInfo) { + user->botInfo->inlinePlaceholder = inlinePlaceholder; + } - if (peerToUser(user->id) == MTP::authedId()) { - user->input = MTP_inputPeerSelf(); - user->inputUser = MTP_inputUserSelf(); - } else { - user->input = MTP_inputPeerUser(MTP_int(peerToUser(user->id)), MTP_long((user->access == UserNoAccess) ? 0 : user->access)); - user->inputUser = MTP_inputUser(MTP_int(peerToUser(user->id)), MTP_long((user->access == UserNoAccess) ? 0 : user->access)); + if (peerToUser(user->id) == MTP::authedId()) { + user->input = MTP_inputPeerSelf(); + user->inputUser = MTP_inputUserSelf(); + } else { + user->input = MTP_inputPeerUser(MTP_int(peerToUser(user->id)), MTP_long((user->access == UserNoAccess) ? 0 : user->access)); + user->inputUser = MTP_inputUser(MTP_int(peerToUser(user->id)), MTP_long((user->access == UserNoAccess) ? 0 : user->access)); + } + + user->photo = photoLoc.isNull() ? ImagePtr(userDefPhoto(user->colorIndex)) : ImagePtr(photoLoc); } - - user->photo = photoLoc.isNull() ? ImagePtr(userDefPhoto(user->colorIndex)) : ImagePtr(photoLoc); } else if (result->isChat()) { ChatData *chat = result->asChat(); @@ -3450,19 +3385,21 @@ namespace Local { // flagsData was haveLeft flags = (flagsData == 1 ? MTPDchat::flag_left : 0); } - chat->updateName(name, QString(), QString()); - chat->count = count; - chat->date = date; - chat->version = version; - chat->creator = creator; - chat->isForbidden = (forbidden == 1); - chat->flags = flags; - chat->invitationUrl = invitationUrl; + if (!wasLoaded) { + chat->updateName(name, QString(), QString()); + chat->count = count; + chat->date = date; + chat->version = version; + chat->creator = creator; + chat->isForbidden = (forbidden == 1); + chat->flags = flags; + chat->invitationUrl = invitationUrl; - chat->input = MTP_inputPeerChat(MTP_int(peerToChat(chat->id))); - chat->inputChat = MTP_int(peerToChat(chat->id)); + chat->input = MTP_inputPeerChat(MTP_int(peerToChat(chat->id))); + chat->inputChat = MTP_int(peerToChat(chat->id)); - chat->photo = photoLoc.isNull() ? ImagePtr(chatDefPhoto(chat->colorIndex)) : ImagePtr(photoLoc); + chat->photo = photoLoc.isNull() ? ImagePtr(chatDefPhoto(chat->colorIndex)) : ImagePtr(photoLoc); + } } else if (result->isChannel()) { ChannelData *channel = result->asChannel(); @@ -3471,24 +3408,135 @@ namespace Local { qint32 date, version, adminned, forbidden, flags; from.stream >> name >> access >> date >> version >> forbidden >> flags >> invitationUrl; - channel->updateName(name, QString(), QString()); - channel->access = access; - channel->date = date; - channel->version = version; - channel->isForbidden = (forbidden == 1); - channel->flags = flags; - channel->invitationUrl = invitationUrl; + if (!wasLoaded) { + channel->updateName(name, QString(), QString()); + channel->access = access; + channel->date = date; + channel->version = version; + channel->isForbidden = (forbidden == 1); + channel->flags = flags; + channel->invitationUrl = invitationUrl; - channel->input = MTP_inputPeerChannel(MTP_int(peerToChannel(channel->id)), MTP_long(access)); - channel->inputChannel = MTP_inputChannel(MTP_int(peerToChannel(channel->id)), MTP_long(access)); + channel->input = MTP_inputPeerChannel(MTP_int(peerToChannel(channel->id)), MTP_long(access)); + channel->inputChannel = MTP_inputChannel(MTP_int(peerToChannel(channel->id)), MTP_long(access)); - channel->photo = photoLoc.isNull() ? ImagePtr((channel->isMegagroup() ? chatDefPhoto(channel->colorIndex) : channelDefPhoto(channel->colorIndex))) : ImagePtr(photoLoc); + channel->photo = photoLoc.isNull() ? ImagePtr((channel->isMegagroup() ? chatDefPhoto(channel->colorIndex) : channelDefPhoto(channel->colorIndex))) : ImagePtr(photoLoc); + } + } + if (!wasLoaded) { + App::markPeerUpdated(result); + emit App::main()->peerPhotoChanged(result); } - App::markPeerUpdated(result); - emit App::main()->peerPhotoChanged(result); return result; } + void writeRecentHashtagsAndBots() { + if (!_working()) return; + + const RecentHashtagPack &write(cRecentWriteHashtags()), &search(cRecentSearchHashtags()); + const RecentInlineBots &bots(cRecentInlineBots()); + if (write.isEmpty() && search.isEmpty() && bots.isEmpty()) readRecentHashtagsAndBots(); + if (write.isEmpty() && search.isEmpty() && bots.isEmpty()) { + if (_recentHashtagsAndBotsKey) { + clearKey(_recentHashtagsAndBotsKey); + _recentHashtagsAndBotsKey = 0; + _mapChanged = true; + } + _writeMap(); + } else { + if (!_recentHashtagsAndBotsKey) { + _recentHashtagsAndBotsKey = genKey(); + _mapChanged = true; + _writeMap(WriteMapFast); + } + quint32 size = sizeof(quint32) * 3, writeCnt = 0, searchCnt = 0, botsCnt = cRecentInlineBots().size(); + for (RecentHashtagPack::const_iterator i = write.cbegin(), e = write.cend(); i != e; ++i) { + if (!i->first.isEmpty()) { + size += _stringSize(i->first) + sizeof(quint16); + ++writeCnt; + } + } + for (RecentHashtagPack::const_iterator i = search.cbegin(), e = search.cend(); i != e; ++i) { + if (!i->first.isEmpty()) { + size += _stringSize(i->first) + sizeof(quint16); + ++searchCnt; + } + } + for (RecentInlineBots::const_iterator i = bots.cbegin(), e = bots.cend(); i != e; ++i) { + size += _peerSize(*i); + } + + EncryptedDescriptor data(size); + data.stream << quint32(writeCnt) << quint32(searchCnt); + for (RecentHashtagPack::const_iterator i = write.cbegin(), e = write.cend(); i != e; ++i) { + if (!i->first.isEmpty()) data.stream << i->first << quint16(i->second); + } + for (RecentHashtagPack::const_iterator i = search.cbegin(), e = search.cend(); i != e; ++i) { + if (!i->first.isEmpty()) data.stream << i->first << quint16(i->second); + } + data.stream << quint32(botsCnt); + for (RecentInlineBots::const_iterator i = bots.cbegin(), e = bots.cend(); i != e; ++i) { + _writePeer(data.stream, *i, 9016); + } + FileWriteDescriptor file(_recentHashtagsAndBotsKey); + file.writeEncrypted(data); + } + } + + void readRecentHashtagsAndBots() { + if (_recentHashtagsAndBotsWereRead) return; + _recentHashtagsAndBotsWereRead = true; + + if (!_recentHashtagsAndBotsKey) return; + + FileReadDescriptor hashtags; + if (!readEncryptedFile(hashtags, _recentHashtagsAndBotsKey)) { + clearKey(_recentHashtagsAndBotsKey); + _recentHashtagsAndBotsKey = 0; + _writeMap(); + return; + } + + quint32 writeCount = 0, searchCount = 0, botsCount = 0; + hashtags.stream >> writeCount >> searchCount; + + QString tag; + quint16 count; + + RecentHashtagPack write, search; + RecentInlineBots bots; + if (writeCount) { + write.reserve(writeCount); + for (uint32 i = 0; i < writeCount; ++i) { + hashtags.stream >> tag >> count; + write.push_back(qMakePair(tag.trimmed(), count)); + } + } + if (searchCount) { + search.reserve(searchCount); + for (uint32 i = 0; i < searchCount; ++i) { + hashtags.stream >> tag >> count; + search.push_back(qMakePair(tag.trimmed(), count)); + } + } + cSetRecentWriteHashtags(write); + cSetRecentSearchHashtags(search); + + if (!hashtags.stream.atEnd()) { + hashtags.stream >> botsCount; + if (botsCount) { + bots.reserve(botsCount); + for (uint32 i = 0; i < botsCount; ++i) { + PeerData *peer = _readPeer(hashtags, 9016); + if (peer && peer->isUser() && peer->asUser()->botInfo && !peer->asUser()->botInfo->inlinePlaceholder.isEmpty() && !peer->asUser()->username.isEmpty()) { + bots.push_back(peer->asUser()); + } + } + } + cSetRecentInlineBots(bots); + } + } + void writeSavedPeers() { if (!_working()) return; @@ -3594,6 +3642,7 @@ namespace Local { struct ClearManagerData { QThread *thread; StorageMap images, stickers, audios; + WebFilesMap webFiles; QMutex mutex; QList tasks; bool working; @@ -3650,8 +3699,8 @@ namespace Local { _stickersKey = 0; _mapChanged = true; } - if (_recentHashtagsKey) { - _recentHashtagsKey = 0; + if (_recentHashtagsAndBotsKey) { + _recentHashtagsAndBotsKey = 0; _mapChanged = true; } if (_savedPeersKey) { @@ -3693,6 +3742,22 @@ namespace Local { _storageStickersSize = 0; _mapChanged = true; } + if (data->webFiles.isEmpty()) { + data->webFiles = _webFilesMap; + } else { + for (WebFilesMap::const_iterator i = _webFilesMap.cbegin(), e = _webFilesMap.cend(); i != e; ++i) { + QString k = i.key(); + while (data->webFiles.constFind(k) != data->webFiles.cend()) { + k += '#'; + } + data->webFiles.insert(k, i.value()); + } + } + if (!_webFilesMap.isEmpty()) { + _webFilesMap.clear(); + _storageWebFilesSize = 0; + _writeLocations(); + } if (data->audios.isEmpty()) { data->audios = _audiosMap; } else { @@ -3745,6 +3810,7 @@ namespace Local { int task = 0; bool result = false; StorageMap images, stickers, audios; + WebFilesMap webFiles; { QMutexLocker lock(&data->mutex); if (data->tasks.isEmpty()) { @@ -3755,6 +3821,7 @@ namespace Local { images = data->images; stickers = data->stickers; audios = data->audios; + webFiles = data->webFiles; } switch (task) { case ClearManagerAll: { @@ -3786,6 +3853,9 @@ namespace Local { for (StorageMap::const_iterator i = audios.cbegin(), e = audios.cend(); i != e; ++i) { clearKey(i.value().first, UserPath); } + for (WebFilesMap::const_iterator i = webFiles.cbegin(), e = webFiles.cend(); i != e; ++i) { + clearKey(i.value().first, UserPath); + } result = true; break; } diff --git a/Telegram/SourceFiles/localstorage.h b/Telegram/SourceFiles/localstorage.h index aa4f51432..6bf8e7c9d 100644 --- a/Telegram/SourceFiles/localstorage.h +++ b/Telegram/SourceFiles/localstorage.h @@ -157,8 +157,8 @@ namespace Local { void writeBackground(int32 id, const QImage &img); bool readBackground(); - void writeRecentHashtags(); - void readRecentHashtags(); + void writeRecentHashtagsAndBots(); + void readRecentHashtagsAndBots(); void addSavedPeer(PeerData *peer, const QDateTime &position); void removeSavedPeer(PeerData *peer); diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 8624a27d3..e73ed08df 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -759,6 +759,10 @@ void MainWidget::notify_botCommandsChanged(UserData *bot) { history.notify_botCommandsChanged(bot); } +void MainWidget::notify_inlineBotRequesting(bool requesting) { + history.notify_inlineBotRequesting(requesting); +} + void MainWidget::notify_userIsBotChanged(UserData *bot) { history.notify_userIsBotChanged(bot); } @@ -1345,7 +1349,7 @@ void MainWidget::saveRecentHashtags(const QString &text) { } } if (!found && cRecentWriteHashtags().isEmpty() && cRecentSearchHashtags().isEmpty()) { - Local::readRecentHashtags(); + Local::readRecentHashtagsAndBots(); recent = cRecentWriteHashtags(); } found = true; @@ -1353,7 +1357,7 @@ void MainWidget::saveRecentHashtags(const QString &text) { } if (found) { cSetRecentWriteHashtags(recent); - Local::writeRecentHashtags(); + Local::writeRecentHashtagsAndBots(); } } diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index 03c30ccda..3d8c6efa3 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -416,6 +416,7 @@ public: void ui_showPeerHistory(quint64 peer, qint32 msgId, bool back); void notify_botCommandsChanged(UserData *bot); + void notify_inlineBotRequesting(bool requesting); void notify_userIsBotChanged(UserData *bot); void notify_userIsContactChanged(UserData *user, bool fromThisApp); void notify_migrateUpdated(PeerData *peer); diff --git a/Telegram/SourceFiles/settings.cpp b/Telegram/SourceFiles/settings.cpp index 6fa99541b..8b2700c4e 100644 --- a/Telegram/SourceFiles/settings.cpp +++ b/Telegram/SourceFiles/settings.cpp @@ -121,6 +121,8 @@ int32 gSavedGifsLimit = 100; RecentHashtagPack gRecentWriteHashtags, gRecentSearchHashtags; +RecentInlineBots gRecentInlineBots; + bool gPasswordRecovered = false; int32 gPasscodeBadTries = 0; uint64 gPasscodeLastTry = 0; diff --git a/Telegram/SourceFiles/settings.h b/Telegram/SourceFiles/settings.h index a5aac46f1..25b34f573 100644 --- a/Telegram/SourceFiles/settings.h +++ b/Telegram/SourceFiles/settings.h @@ -230,9 +230,13 @@ DeclareSetting(bool, ShowingSavedGifs); DeclareSetting(int32, SavedGifsLimit); typedef QList > RecentHashtagPack; -DeclareSetting(RecentHashtagPack, RecentWriteHashtags); +DeclareRefSetting(RecentHashtagPack, RecentWriteHashtags); DeclareSetting(RecentHashtagPack, RecentSearchHashtags); +class UserData; +typedef QVector RecentInlineBots; +DeclareRefSetting(RecentInlineBots, RecentInlineBots); + DeclareSetting(bool, PasswordRecovered); DeclareSetting(int32, PasscodeBadTries); diff --git a/Telegram/SourceFiles/settingswidget.cpp b/Telegram/SourceFiles/settingswidget.cpp index 6668bfc4e..ea3cfe7e8 100644 --- a/Telegram/SourceFiles/settingswidget.cpp +++ b/Telegram/SourceFiles/settingswidget.cpp @@ -462,7 +462,7 @@ void SettingsInner::paintEvent(QPaintEvent *e) { top += st::setHeaderSkip; #ifndef TDESKTOP_DISABLE_AUTOUPDATE - top += _autoUpdate.height(); + top += _autoUpdate.height(); QString textToDraw; if (cAutoUpdate()) { switch (_updatingState) { @@ -485,7 +485,7 @@ void SettingsInner::paintEvent(QPaintEvent *e) { if (cPlatform() == dbipWindows) { top += _workmodeTray.height() + st::setLittleSkip; top += _workmodeWindow.height() + st::setSectionSkip; - + top += _autoStart.height() + st::setLittleSkip; top += _startMinimized.height() + st::setSectionSkip; @@ -500,12 +500,12 @@ void SettingsInner::paintEvent(QPaintEvent *e) { p.drawText(_left + st::setHeaderLeft, top + st::setHeaderTop + st::setHeaderFont->ascent, lang(lng_settings_scale_label)); top += st::setHeaderSkip; top += _dpiAutoScale.height() + st::setLittleSkip; - + top += _dpiSlider.height() + st::dpiFont4->height; int32 sLeft = _dpiSlider.x() + _dpiWidth1 / 2, sWidth = _dpiSlider.width(); float64 sStep = (sWidth - _dpiWidth1 / 2 - _dpiWidth4 / 2) / float64(dbisScaleCount - 2); p.setFont(st::dpiFont1->f); - + p.setPen((scaleIs(dbisOne) ? st::dpiActive : st::dpiInactive)->p); p.drawText(sLeft + qRound(0 * sStep) - _dpiWidth1 / 2, top - (st::dpiFont4->height - st::dpiFont1->height) / 2 - st::dpiFont1->descent, scaleLabel(dbisOne)); p.setFont(st::dpiFont2->f); @@ -519,7 +519,7 @@ void SettingsInner::paintEvent(QPaintEvent *e) { p.drawText(sLeft + qRound(3 * sStep) - _dpiWidth4 / 2, top - (st::dpiFont4->height - st::dpiFont4->height) / 2 - st::dpiFont4->descent, scaleLabel(dbisTwo)); p.setFont(st::linkFont->f); } - + if (self()) { // chat options p.setFont(st::setHeaderFont->f); @@ -575,7 +575,7 @@ void SettingsInner::paintEvent(QPaintEvent *e) { top += st::setHeaderSkip; - int32 cntImages = Local::hasImages() + Local::hasStickers(), cntAudios = Local::hasAudios(); + int32 cntImages = Local::hasImages() + Local::hasStickers() + Local::hasWebFiles(), cntAudios = Local::hasAudios(); if (cntImages > 0 && cntAudios > 0) { if (_localStorageHeight != 2) { cntAudios = 0; @@ -587,7 +587,7 @@ void SettingsInner::paintEvent(QPaintEvent *e) { } } if (cntImages > 0) { - QString cnt = lng_settings_images_cached(lt_count, cntImages, lt_size, formatSizeText(Local::storageImagesSize() + Local::storageStickersSize())); + QString cnt = lng_settings_images_cached(lt_count, cntImages, lt_size, formatSizeText(Local::storageImagesSize() + Local::storageStickersSize() + Local::storageWebFilesSize())); p.drawText(_left + st::setHeaderLeft, top + st::linkFont->ascent, cnt); } if (_localStorageHeight == 2) top += _localStorageClear.height() + st::setLittleSkip; @@ -644,7 +644,7 @@ void SettingsInner::paintEvent(QPaintEvent *e) { p.setPen(st::setHeaderColor->p); p.drawText(_left + st::setHeaderLeft, top + st::setHeaderTop + st::setHeaderFont->ascent, lang(lng_settings_section_advanced)); top += st::setHeaderSkip; - + p.setFont(st::linkFont->f); p.setPen(st::black->p); if (self()) { @@ -702,7 +702,7 @@ void SettingsInner::resizeEvent(QResizeEvent *e) { if (cPlatform() == dbipWindows) { _workmodeTray.move(_left, top); top += _workmodeTray.height() + st::setLittleSkip; _workmodeWindow.move(_left, top); top += _workmodeWindow.height() + st::setSectionSkip; - + _autoStart.move(_left, top); top += _autoStart.height() + st::setLittleSkip; _startMinimized.move(_left, top); top += _startMinimized.height() + st::setSectionSkip; @@ -715,7 +715,7 @@ void SettingsInner::resizeEvent(QResizeEvent *e) { _dpiAutoScale.move(_left, top); top += _dpiAutoScale.height() + st::setLittleSkip; _dpiSlider.move(_left, top); top += _dpiSlider.height() + st::dpiFont4->height; } - + // chat options if (self()) { top += st::setHeaderSkip; @@ -739,7 +739,7 @@ void SettingsInner::resizeEvent(QResizeEvent *e) { // local storage _localStorageClear.move(_left + st::setWidth - _localStorageClear.width(), top + st::setHeaderTop + st::setHeaderFont->ascent - st::linkFont->ascent); top += st::setHeaderSkip; - if ((Local::hasImages() || Local::hasStickers()) && Local::hasAudios()) { + if ((Local::hasImages() || Local::hasStickers() || Local::hasWebFiles()) && Local::hasAudios()) { _localStorageHeight = 2; top += _localStorageClear.height() + st::setLittleSkip; } else { @@ -1031,7 +1031,7 @@ void SettingsInner::showAll() { _workmodeTray.hide(); } _workmodeWindow.hide(); - + _autoStart.hide(); _startMinimized.hide(); @@ -1163,7 +1163,7 @@ void SettingsInner::onUpdatePhotoCancel() { void SettingsInner::onUpdatePhoto() { saveError(); - QStringList imgExtensions(cImgExtensions()); + QStringList imgExtensions(cImgExtensions()); QString filter(qsl("Image files (*") + imgExtensions.join(qsl(" *")) + qsl(");;All files (*.*)")); QImage img; diff --git a/Telegram/SourceFiles/window.cpp b/Telegram/SourceFiles/window.cpp index 035116826..9a35a4ef6 100644 --- a/Telegram/SourceFiles/window.cpp +++ b/Telegram/SourceFiles/window.cpp @@ -453,9 +453,9 @@ void Window::firstShow() { trayIconMenu = new QMenu(this); trayIconMenu->setFont(QFont("Tahoma")); #endif - QString notificationItem = lang(cDesktopNotify() + QString notificationItem = lang(cDesktopNotify() ? lng_disable_notifications_from_tray : lng_enable_notifications_from_tray); - + if (cPlatform() == dbipWindows || cPlatform() == dbipMac || cPlatform() == dbipMacOld) { trayIconMenu->addAction(lang(lng_minimize_to_tray), this, SLOT(minimizeToTray()))->setEnabled(true); trayIconMenu->addAction(notificationItem, this, SLOT(toggleDisplayNotifyFromTray()))->setEnabled(true); @@ -940,7 +940,7 @@ void Window::paintEvent(QPaintEvent *e) { HitTestType Window::hitTest(const QPoint &p) const { int x(p.x()), y(p.y()), w(width()), h(height()); - + const int32 raw = psResizeRowWidth(); if (!windowState().testFlag(Qt::WindowMaximized)) { if (y < raw) { @@ -1019,7 +1019,7 @@ void Window::mouseMoveEvent(QMouseEvent *e) { if (dragging) { if (windowState().testFlag(Qt::WindowMaximized)) { setWindowState(windowState() & ~Qt::WindowMaximized); - + dragStart = e->globalPos() - frameGeometry().topLeft(); } else { move(e->globalPos() - dragStart); @@ -1260,7 +1260,7 @@ Window::TempDirState Window::localStorageState() { if (_clearManager && _clearManager->hasTask(Local::ClearManagerStorage)) { return TempDirRemoving; } - return (Local::hasImages() || Local::hasStickers() || Local::hasAudios()) ? TempDirExists : TempDirEmpty; + return (Local::hasImages() || Local::hasStickers() || Local::hasWebFiles() || Local::hasAudios()) ? TempDirExists : TempDirEmpty; } void Window::tempDirDelete(int task) { diff --git a/Telegram/Telegram.rc b/Telegram/Telegram.rc index af8a70d02..630f3982e 100644 --- a/Telegram/Telegram.rc +++ b/Telegram/Telegram.rc @@ -34,8 +34,8 @@ IDI_ICON1 ICON "SourceFiles\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,9,15,5 - PRODUCTVERSION 0,9,15,5 + FILEVERSION 0,9,15,6 + PRODUCTVERSION 0,9,15,6 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -51,10 +51,10 @@ BEGIN BLOCK "040904b0" BEGIN VALUE "CompanyName", "Telegram Messenger LLP" - VALUE "FileVersion", "0.9.15.5" + VALUE "FileVersion", "0.9.15.6" VALUE "LegalCopyright", "Copyright (C) 2013" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "0.9.15.5" + VALUE "ProductVersion", "0.9.15.6" END END BLOCK "VarFileInfo" diff --git a/Telegram/Version b/Telegram/Version index 06a61db83..2143be02e 100644 --- a/Telegram/Version +++ b/Telegram/Version @@ -3,4 +3,4 @@ AppVersionStrMajor 0.9 AppVersionStrSmall 0.9.15 AppVersionStr 0.9.15 DevChannel 0 -BetaVersion 9015005 +BetaVersion 9015006