diff --git a/Telegram/Prepare.sh b/Telegram/Prepare.sh index 4cdb3145d35732..ffd792406d509c 100755 --- a/Telegram/Prepare.sh +++ b/Telegram/Prepare.sh @@ -1,5 +1,5 @@ -AppVersionStr=0.5.7 -AppVersion=5007 +AppVersionStr=0.5.8 +AppVersion=5008 if [ -d "./../Mac/Release/deploy/$AppVersionStr" ]; then echo "Deploy folder for version $AppVersionStr already exists!" diff --git a/Telegram/PrepareUbuntu.sh b/Telegram/PrepareUbuntu.sh index 7a74be67005559..4c513ba28f4d73 100755 --- a/Telegram/PrepareUbuntu.sh +++ b/Telegram/PrepareUbuntu.sh @@ -1,5 +1,5 @@ -AppVersionStr=0.5.7 -AppVersion=5007 +AppVersionStr=0.5.8 +AppVersion=5008 if [ -d "./../Linux/Release/deploy/$AppVersionStr" ]; then echo "Deploy folder for version $AppVersionStr already exists!" diff --git a/Telegram/Resources/lang.txt b/Telegram/Resources/lang.txt index 3855a32315ff8b..960bf1426d55f1 100644 --- a/Telegram/Resources/lang.txt +++ b/Telegram/Resources/lang.txt @@ -308,6 +308,8 @@ lng_context_open_link: "Open Link"; lng_context_copy_link: "Copy Link"; lng_context_open_email: "Write to this address"; lng_context_copy_email: "Copy email address"; +lng_context_open_hashtag: "Search by hashtag"; +lng_context_copy_hashtag: "Copy hashtag"; lng_context_open_image: "Open Image"; lng_context_save_image: "Save Image As..."; lng_context_forward_image: "Forward Image"; diff --git a/Telegram/Setup.iss b/Telegram/Setup.iss index 4752b1d57480c3..d2b584d91d530f 100644 --- a/Telegram/Setup.iss +++ b/Telegram/Setup.iss @@ -3,9 +3,9 @@ #define MyAppShortName "Telegram" #define MyAppName "Telegram Win (Unofficial)" -#define MyAppVersion "0.5.7" -#define MyAppVersionZero "0.5.7" -#define MyAppFullVersion "0.5.7.0" +#define MyAppVersion "0.5.8" +#define MyAppVersionZero "0.5.8" +#define MyAppFullVersion "0.5.8.0" #define MyAppPublisher "Telegram (Unofficial)" #define MyAppURL "https://tdesktop.com" #define MyAppExeName "Telegram.exe" diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index ef21d548aeaacc..bea52c95130ebf 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -1955,4 +1955,10 @@ namespace App { } } + void searchByHashtag(const QString &tag) { + if (App::main()) { + App::main()->searchMessages(tag); + } + } + } diff --git a/Telegram/SourceFiles/app.h b/Telegram/SourceFiles/app.h index 8bb4636ffbaf65..c60a1fd186c952 100644 --- a/Telegram/SourceFiles/app.h +++ b/Telegram/SourceFiles/app.h @@ -187,4 +187,6 @@ namespace App { void setProxySettings(QNetworkAccessManager &manager); void setProxySettings(QTcpSocket &socket); + void searchByHashtag(const QString &tag); + }; diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h index 646a271d58fe2f..bfa84d2c7b4cf2 100644 --- a/Telegram/SourceFiles/config.h +++ b/Telegram/SourceFiles/config.h @@ -17,8 +17,8 @@ Copyright (c) 2014 John Preston, https://tdesktop.com */ #pragma once -static const int32 AppVersion = 5007; -static const wchar_t *AppVersionStr = L"0.5.7"; +static const int32 AppVersion = 5008; +static const wchar_t *AppVersionStr = L"0.5.8"; #ifdef Q_OS_WIN static const wchar_t *AppName = L"Telegram Win (Unofficial)"; #else diff --git a/Telegram/SourceFiles/dialogswidget.cpp b/Telegram/SourceFiles/dialogswidget.cpp index 30a3d7854fc9dd..b85a3842810064 100644 --- a/Telegram/SourceFiles/dialogswidget.cpp +++ b/Telegram/SourceFiles/dialogswidget.cpp @@ -1118,6 +1118,16 @@ void DialogsWidget::onNeedSearchMessages() { } } +void DialogsWidget::searchMessages(const QString &query) { + if (_filter.text() != query) { + _filter.setText(query); + _filter.updatePlaceholder(); + onFilterUpdate(); + _searchTimer.stop(); + onSearchMessages(); + } +} + void DialogsWidget::onSearchMore(MsgId minMsgId) { if (!_searchRequest && !_searchFull) { _searchRequest = MTP::send(MTPmessages_Search(MTP_inputPeerEmpty(), MTP_string(_searchQuery), MTP_inputMessagesFilterEmpty(), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(minMsgId), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, !minMsgId), rpcFail(&DialogsWidget::searchFailed)); diff --git a/Telegram/SourceFiles/dialogswidget.h b/Telegram/SourceFiles/dialogswidget.h index 2e78c200c7862e..5355c44de15213 100644 --- a/Telegram/SourceFiles/dialogswidget.h +++ b/Telegram/SourceFiles/dialogswidget.h @@ -170,6 +170,7 @@ class DialogsWidget : public QWidget, public Animated, public RPCSender { void enableShadow(bool enable = true); + void searchMessages(const QString &query); void onSearchMore(MsgId minMsgId); void clearFiltered(); diff --git a/Telegram/SourceFiles/gui/text.cpp b/Telegram/SourceFiles/gui/text.cpp index edabf19c8c1d10..cd1058ac4787d2 100644 --- a/Telegram/SourceFiles/gui/text.cpp +++ b/Telegram/SourceFiles/gui/text.cpp @@ -19,6 +19,7 @@ Copyright (c) 2014 John Preston, https://tdesktop.com #include "text.h" #include "lang.h" +#include "app.h" #include @@ -111,8 +112,9 @@ namespace { } const QRegularExpression reDomain(QString::fromUtf8("(?|'\"\\[\\]\\{\\}`\\~\\!\\%\\^\\*\\(\\)\\-\\+=\\x10])#[A-Za-z_\\.0-9]{4,20}([\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\%\\^\\*\\(\\)\\-\\+=\\x10]|$)")); QSet validProtocols, validTopDomains; void initLinkSets(); @@ -298,7 +300,7 @@ class TextParser { return Qt::LayoutDirectionAuto; } - void prepareLinks() { // support emails! + void prepareLinks() { // support emails and hashtags! if (validProtocols.empty()) { initLinkSets(); } @@ -313,73 +315,98 @@ class TextParser { } } QRegularExpressionMatch mDomain = reDomain.match(src, offset); - if (!mDomain.hasMatch()) break; + QRegularExpressionMatch mHashtag = reHashtag.match(src, offset); + if (!mDomain.hasMatch() && !mHashtag.hasMatch()) break; - int32 domainOffset = mDomain.capturedStart(), domainEnd = mDomain.capturedEnd(); - if (domainOffset > nextCmd) { - const QChar *after = skipCommand(srcData + nextCmd, srcData + len); - if (after > srcData + nextCmd && domainOffset < (after - srcData)) { - nextCmd = offset = after - srcData; - continue; + LinkRange link; + int32 domainOffset = mDomain.hasMatch() ? mDomain.capturedStart() : INT_MAX, + domainEnd = mDomain.hasMatch() ? mDomain.capturedEnd() : INT_MAX, + hashtagOffset = mHashtag.hasMatch() ? mHashtag.capturedStart() : INT_MAX, + hashtagEnd = mHashtag.hasMatch() ? mHashtag.capturedEnd() : INT_MAX; + if (mHashtag.hasMatch()) { + if (!mHashtag.capturedRef(1).isEmpty()) { + ++hashtagOffset; + } + if (!mHashtag.capturedRef(2).isEmpty()) { + --hashtagEnd; } } + if (hashtagOffset < domainOffset) { + if (hashtagOffset > nextCmd) { + const QChar *after = skipCommand(srcData + nextCmd, srcData + len); + if (after > srcData + nextCmd && hashtagOffset < (after - srcData)) { + nextCmd = offset = after - srcData; + continue; + } + } + + link.from = start + hashtagOffset; + link.len = start + hashtagEnd - link.from; + } else { + if (domainOffset > nextCmd) { + const QChar *after = skipCommand(srcData + nextCmd, srcData + len); + if (after > srcData + nextCmd && domainOffset < (after - srcData)) { + nextCmd = offset = after - srcData; + continue; + } + } - QString protocol = mDomain.captured(1).toLower(); - QString topDomain = mDomain.captured(3).toLower(); - - bool isProtocolValid = protocol.isEmpty() || validProtocols.contains(hashCrc32(protocol.constData(), protocol.size() * sizeof(QChar))); - bool isTopDomainValid = validTopDomains.contains(hashCrc32(topDomain.constData(), topDomain.size() * sizeof(QChar))); + QString protocol = mDomain.captured(1).toLower(); + QString topDomain = mDomain.captured(3).toLower(); - if (!isProtocolValid || !isTopDomainValid) { - offset = domainEnd; - continue; - } + bool isProtocolValid = protocol.isEmpty() || validProtocols.contains(hashCrc32(protocol.constData(), protocol.size() * sizeof(QChar))); + bool isTopDomainValid = validTopDomains.contains(hashCrc32(topDomain.constData(), topDomain.size() * sizeof(QChar))); - LinkRange link; - if (protocol.isEmpty() && domainOffset > offset + 1 && *(start + domainOffset - 1) == QChar('@')) { - QString forMailName = src.mid(offset, domainOffset - offset - 1); - QRegularExpressionMatch mMailName = reMailName.match(forMailName); - if (mMailName.hasMatch()) { - int32 mailOffset = offset + mMailName.capturedStart(); - if (mailOffset < offset) { - mailOffset = offset; - } - link.from = start + mailOffset; - link.len = domainEnd - mailOffset; + if (!isProtocolValid || !isTopDomainValid) { + offset = domainEnd; + continue; } - } - if (!link.from || !link.len) { - link.from = start + domainOffset; - - QStack parenth; - const QChar *p = start + mDomain.capturedEnd(); - for (; p < end; ++p) { - QChar ch(*p); - if (chIsLinkEnd(ch)) break; // link finished - if (chIsAlmostLinkEnd(ch)) { - const QChar *endTest = p + 1; - while (endTest < end && chIsAlmostLinkEnd(*endTest)) { - ++endTest; - } - if (endTest >= end || chIsLinkEnd(*endTest)) { - break; // link finished at p + + if (protocol.isEmpty() && domainOffset > offset + 1 && *(start + domainOffset - 1) == QChar('@')) { + QString forMailName = src.mid(offset, domainOffset - offset - 1); + QRegularExpressionMatch mMailName = reMailName.match(forMailName); + if (mMailName.hasMatch()) { + int32 mailOffset = offset + mMailName.capturedStart(); + if (mailOffset < offset) { + mailOffset = offset; } - p = endTest; - ch = *p; + link.from = start + mailOffset; + link.len = domainEnd - mailOffset; } - if (ch == '(' || ch == '[' || ch == '{' || ch == '<') { - parenth.push(p); - } else if (ch == ')' || ch == ']' || ch == '}' || ch == '>') { - if (parenth.isEmpty()) break; - const QChar *q = parenth.pop(), open(*q); - if ((ch == ')' && open != '(') || (ch == ']' && open != '[') || (ch == '}' && open != '{') || (ch == '>' && open != '<')) { - p = q; - break; + } + if (!link.from || !link.len) { + link.from = start + domainOffset; + + QStack parenth; + const QChar *p = start + mDomain.capturedEnd(); + for (; p < end; ++p) { + QChar ch(*p); + if (chIsLinkEnd(ch)) break; // link finished + if (chIsAlmostLinkEnd(ch)) { + const QChar *endTest = p + 1; + while (endTest < end && chIsAlmostLinkEnd(*endTest)) { + ++endTest; + } + if (endTest >= end || chIsLinkEnd(*endTest)) { + break; // link finished at p + } + p = endTest; + ch = *p; + } + if (ch == '(' || ch == '[' || ch == '{' || ch == '<') { + parenth.push(p); + } else if (ch == ')' || ch == ']' || ch == '}' || ch == '>') { + if (parenth.isEmpty()) break; + const QChar *q = parenth.pop(), open(*q); + if ((ch == ')' && open != '(') || (ch == ']' && open != '[') || (ch == '}' && open != '{') || (ch == '>' && open != '<')) { + p = q; + break; + } } } - } - link.len = p - link.from; + link.len = p - link.from; + } } lnkRanges.push_back(link); @@ -421,9 +448,12 @@ class TextParser { } void getLinkData(const QString &original, QString &result, int32 &fullDisplayed) { - if (reMailStart.match(original).hasMatch()) { + if (!original.isEmpty() && original.at(0) == '#') { + result = original; + fullDisplayed = -2; // hashtag + } else if (reMailStart.match(original).hasMatch()) { result = original; - fullDisplayed = -1; + fullDisplayed = -1; // email } else { QUrl url(original), good(url.isValid() ? url.toEncoded() : ""); QString readable = good.isValid() ? good.toDisplayString() : original; @@ -725,7 +755,9 @@ class TextParser { _t->_links.resize(lnkIndex); const TextLinkData &data(links[lnkIndex - maxLnkIndex - 1]); TextLinkPtr lnk; - if (data.fullDisplayed < 0) { // email + if (data.fullDisplayed < -1) { // hashtag + lnk = TextLinkPtr(new HashtagLink(data.url)); + } else if (data.fullDisplayed < 0) { // email lnk = TextLinkPtr(new EmailLink(data.url)); } else { lnk = TextLinkPtr(new TextLink(data.url, data.fullDisplayed > 0)); @@ -755,7 +787,7 @@ class TextParser { TextLinkData(const QString &url = QString(), int32 fullDisplayed = 1) : url(url), fullDisplayed(fullDisplayed) { } QString url; - int32 fullDisplayed; // < 0 - email + int32 fullDisplayed; // -2 - hashtag, -1 - email }; typedef QVector TextLinks; TextLinks links; @@ -874,6 +906,12 @@ namespace { } +void HashtagLink::onClick(Qt::MouseButton button) const { + if (button == Qt::LeftButton || button == Qt::MiddleButton) { + App::searchByHashtag(_tag); + } +} + class TextPainter { public: diff --git a/Telegram/SourceFiles/gui/text.h b/Telegram/SourceFiles/gui/text.h index 7f804c5c961240..d0d1c92f96745a 100644 --- a/Telegram/SourceFiles/gui/text.h +++ b/Telegram/SourceFiles/gui/text.h @@ -288,6 +288,32 @@ class EmailLink : public ITextLink { }; +class HashtagLink : public ITextLink { +public: + + HashtagLink(const QString &tag) : _tag(tag) { + } + + const QString &text() const { + return _tag; + } + + void onClick(Qt::MouseButton button) const; + + const QString &readable() const { + return _tag; + } + + QString encoded() const { + return _tag; + } + +private: + + QString _tag; + +}; + static const QChar TextCommand(0x0010); enum TextCommands { TextCommandBold = 0x01, diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index 2ca012a1ebdc03..092f245fdaaaaf 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -1472,6 +1472,7 @@ void History::clear(bool leaveItems) { setMsgCount(0); if (!leaveItems) { setUnreadCount(0); + last = 0; } height = 0; oldLoaded = false; diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 92736460608bc3..59561d859e7b1e 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -630,6 +630,13 @@ void HistoryList::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { } _menu->addAction(lang(lng_context_open_email), this, SLOT(openContextUrl()))->setEnabled(true); _menu->addAction(lang(lng_context_copy_email), this, SLOT(copyContextUrl()))->setEnabled(true); + } else if (_contextMenuLnk && dynamic_cast(_contextMenuLnk.data())) { + _menu = new QMenu(historyWidget); + if (isUponSelected > 0) { + _menu->addAction(lang(lng_context_copy_selected), this, SLOT(copySelectedText()))->setEnabled(true); + } + _menu->addAction(lang(lng_context_open_hashtag), this, SLOT(openContextUrl()))->setEnabled(true); + _menu->addAction(lang(lng_context_copy_hashtag), this, SLOT(copyContextUrl()))->setEnabled(true); } else { PhotoLink *lnkPhoto = dynamic_cast(_contextMenuLnk.data()); VideoLink *lnkVideo = dynamic_cast(_contextMenuLnk.data()); diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index ce9b11f2ed928a..e889f294eb496a 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -536,6 +536,10 @@ void MainWidget::stopAnimActive() { history.stopAnimActive(); } +void MainWidget::searchMessages(const QString &query) { + dialogs.searchMessages(query); +} + void MainWidget::partWasRead(PeerData *peer, const MTPmessages_AffectedHistory &result) { const MTPDmessages_affectedHistory &d(result.c_messages_affectedHistory()); App::main()->updUpdated(d.vpts.v, 0, 0, d.vseq.v); diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index 0bf767186a20e2..d6a3dde9a31054 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -197,6 +197,8 @@ class MainWidget : public QWidget, public Animated, public RPCSender { uint64 animActiveTime() const; void stopAnimActive(); + void searchMessages(const QString &query); + ~MainWidget(); signals: diff --git a/Telegram/Telegram.plist b/Telegram/Telegram.plist index 7362a0523abdbe..3ff43131358d67 100644 --- a/Telegram/Telegram.plist +++ b/Telegram/Telegram.plist @@ -11,7 +11,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.5.7 + 0.5.8 CFBundleSignature ???? NOTE diff --git a/Telegram/Telegram.rc b/Telegram/Telegram.rc index d894e039811aa9..8e0c995cea3e8b 100644 Binary files a/Telegram/Telegram.rc and b/Telegram/Telegram.rc differ diff --git a/Telegram/Telegram.xcodeproj/project.pbxproj b/Telegram/Telegram.xcodeproj/project.pbxproj index 3cd1750ee7a56d..54848de0ecb7c2 100644 --- a/Telegram/Telegram.xcodeproj/project.pbxproj +++ b/Telegram/Telegram.xcodeproj/project.pbxproj @@ -1453,7 +1453,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 0.5.1; + CURRENT_PROJECT_VERSION = 0.5.8; DEBUG_INFORMATION_FORMAT = dwarf; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; GCC_OPTIMIZATION_LEVEL = 0; @@ -1471,7 +1471,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; COPY_PHASE_STRIP = YES; - CURRENT_PROJECT_VERSION = 0.5.1; + CURRENT_PROJECT_VERSION = 0.5.8; GCC_GENERATE_DEBUGGING_SYMBOLS = NO; GCC_OPTIMIZATION_LEVEL = fast; GCC_PREFIX_HEADER = ./SourceFiles/stdafx.h; @@ -1495,9 +1495,9 @@ CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = YES; - CURRENT_PROJECT_VERSION = 0.5.7; + CURRENT_PROJECT_VERSION = 0.5.8; DYLIB_COMPATIBILITY_VERSION = 0.5; - DYLIB_CURRENT_VERSION = 0.5.7; + DYLIB_CURRENT_VERSION = 0.5.8; FRAMEWORK_SEARCH_PATHS = ""; GCC_GENERATE_DEBUGGING_SYMBOLS = NO; GCC_OPTIMIZATION_LEVEL = fast; @@ -1620,10 +1620,10 @@ CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 0.5.7; + CURRENT_PROJECT_VERSION = 0.5.8; DEBUG_INFORMATION_FORMAT = dwarf; DYLIB_COMPATIBILITY_VERSION = 0.5; - DYLIB_CURRENT_VERSION = 0.5.7; + DYLIB_CURRENT_VERSION = 0.5.8; FRAMEWORK_SEARCH_PATHS = ""; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; GCC_OPTIMIZATION_LEVEL = 0;