diff --git a/Telegram/MetaStyle.xcodeproj/project.pbxproj b/Telegram/MetaStyle.xcodeproj/project.pbxproj index 62abc68a2..6d7546ca2 100644 --- a/Telegram/MetaStyle.xcodeproj/project.pbxproj +++ b/Telegram/MetaStyle.xcodeproj/project.pbxproj @@ -636,7 +636,7 @@ "/usr/local/Qt-5.5.1/plugins/platforms", "/usr/local/Qt-5.5.1/plugins/imageformats", ); - MACOSX_DEPLOYMENT_TARGET = 10.7; + MACOSX_DEPLOYMENT_TARGET = 10.8; OBJROOT = ./../Mac/ReleaseIntermediateStyle; OTHER_CFLAGS = ( "-pipe", @@ -727,7 +727,7 @@ "/usr/local/Qt-5.5.1/plugins/platforms", "/usr/local/Qt-5.5.1/plugins/imageformats", ); - MACOSX_DEPLOYMENT_TARGET = 10.7; + MACOSX_DEPLOYMENT_TARGET = 10.8; OBJROOT = ./../Mac/DebugIntermediateStyle; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = ( diff --git a/Telegram/SourceFiles/_other/genstyles.cpp b/Telegram/SourceFiles/_other/genstyles.cpp index 8513ece9b..1a16cdd62 100644 --- a/Telegram/SourceFiles/_other/genstyles.cpp +++ b/Telegram/SourceFiles/_other/genstyles.cpp @@ -383,7 +383,7 @@ to link the code of portions of this program with the OpenSSL library.\n\ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE\n\ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org\n\ */\n"; - tout << "#pragma once\n\n#include \"ui/style.h\"\n\nnamespace style {\n"; + tout << "#pragma once\n\n#include \"ui/style_core.h\"\n\nnamespace style {\n"; for (int i = 0, l = byIndex.size(); i < l; ++i) { ClassData &cls(byIndex[i]); classes.insert(cls.name, cls); @@ -513,7 +513,7 @@ typedef QPair ScalarData; typedef QPair Scalar; typedef QMap Fields; typedef QPair ObjectData; -typedef QPair Object; +typedef QPair Object; typedef QVector Objects; typedef QVector Scalars; @@ -636,7 +636,7 @@ ScalarValue prepareNumber(int variant, const string &token, const char *&text, c ScalarValue prepareColorRGB(int variant, const string &name, const char *&text, const char *end) { StyleGenTokenType type; string token; - + readStyleGenToken(text, end, type, token); if (type != stConsStart) throw Exception(QString("Unexpected token %1 while reading rgb() cons!").arg(type)); @@ -675,7 +675,7 @@ ScalarValue prepareColorRGB(int variant, const string &name, const char *&text, ScalarValue prepareColorRGBA(int variant, const string &name, const char *&text, const char *end) { StyleGenTokenType type; string token; - + readStyleGenToken(text, end, type, token); if (type != stConsStart) throw Exception(QString("Unexpected token %1 while reading rgba() cons!").arg(type)); @@ -721,7 +721,7 @@ ScalarValue prepareColorRGBA(int variant, const string &name, const char *&text, ScalarValue prepareRect(int variant, const char *&text, const char *end) { StyleGenTokenType type; string token; - + readStyleGenToken(text, end, type, token); if (type != stConsStart) throw Exception(QString("Unexpected token %1 while reading rect() cons!").arg(type)); @@ -859,7 +859,7 @@ ScalarValue prepareSprite(int variant, const char *&text, const char *end) { ScalarValue preparePoint(int variant, const char *&text, const char *end) { StyleGenTokenType type; string token; - + readStyleGenToken(text, end, type, token); if (type != stConsStart) throw Exception(QString("Unexpected token %1 while reading point() cons!").arg(type)); @@ -892,7 +892,7 @@ ScalarValue preparePoint(int variant, const char *&text, const char *end) { ScalarValue prepareSize(int variant, const char *&text, const char *end) { StyleGenTokenType type; string token; - + readStyleGenToken(text, end, type, token); if (type != stConsStart) throw Exception(QString("Unexpected token %1 while reading size() cons!").arg(type)); @@ -925,7 +925,7 @@ ScalarValue prepareSize(int variant, const char *&text, const char *end) { ScalarValue prepareTransition(int variant, const char *&text, const char *end) { StyleGenTokenType type; string token; - + readStyleGenToken(text, end, type, token); if (type != stConsStart) throw Exception(QString("Unexpected token %1 while reading transition() cons!").arg(type)); @@ -942,7 +942,7 @@ ScalarValue prepareTransition(int variant, const char *&text, const char *end) { ScalarValue prepareCursor(int variant, const char *&text, const char *end) { StyleGenTokenType type; string token; - + readStyleGenToken(text, end, type, token); if (type != stConsStart) throw Exception(QString("Unexpected token %1 while reading cursor() cons!").arg(type)); @@ -959,7 +959,7 @@ ScalarValue prepareCursor(int variant, const char *&text, const char *end) { ScalarValue prepareAlign(int variant, const char *&text, const char *end) { StyleGenTokenType type; string token; - + readStyleGenToken(text, end, type, token); if (type != stConsStart) throw Exception(QString("Unexpected token %1 while reading align() cons!").arg(type)); @@ -976,7 +976,7 @@ ScalarValue prepareAlign(int variant, const char *&text, const char *end) { ScalarValue prepareMargins(int variant, const char *&text, const char *end) { StyleGenTokenType type; string token; - + readStyleGenToken(text, end, type, token); if (type != stConsStart) throw Exception(QString("Unexpected token %1 while reading margins() cons!").arg(type)); @@ -1049,7 +1049,7 @@ QMap fonts; ScalarValue prepareFont(int variant, const string &name, const char *&text, const char *end) { StyleGenTokenType type; string token; - + ScalarValue sizeScalar, familyScalar; string size, family; @@ -1534,7 +1534,7 @@ to link the code of portions of this program with the OpenSSL library.\n\ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE\n\ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org\n\ */\n"; - tout << "#pragma once\n\n#include \"ui/style.h\"\n\nnamespace st {\n"; + tout << "#pragma once\n\n#include \"ui/style_core.h\"\n\nnamespace st {\n"; tcpp << "\ /*\n\ Created from \'/Resources/style.txt\' by \'/MetaStyle\' project\n\ @@ -1594,7 +1594,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org\n\ tcpp << "\tColorDatas _colorsMap;\n"; tcpp << "\tint _spriteWidth = " << spriteWidths[0] << ";\n\n"; tcpp << "\tvoid startManager() {\n"; - + tcpp << "\n\t\tif (cRetina()) {\n"; tcpp << "\t\t\tcSetRealScale(dbisOne);\n"; tcpp << "\t\t\t_spriteWidth = " << spriteWidths[variantsCount - 1] << ";\n\n"; diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 83596d4e7..62adaec33 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -23,7 +23,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "lang.h" #include "application.h" -#include "window.h" +#include "mainwindow.h" #include "mainwidget.h" #include "apiwrap.h" diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index 6d11b479b..ec8db9052 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -31,7 +31,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include #endif #include "localstorage.h" - +#include "apiwrap.h" #include "numbers.h" namespace { @@ -146,23 +146,29 @@ namespace App { return AppClass::app(); } - Window *wnd() { + MainWindow *wnd() { return AppClass::wnd(); } MainWidget *main() { - Window *w(wnd()); - return w ? w->mainWidget() : 0; + if (auto w = wnd()) { + return w->mainWidget(); + } + return nullptr; } SettingsWidget *settings() { - Window *w(wnd()); - return w ? w->settingsWidget() : 0; + if (auto w = wnd()) { + return w->settingsWidget(); + } + return nullptr; } bool passcoded() { - Window *w(wnd()); - return w ? w->passcodeWidget() : 0; + if (auto w = wnd()) { + return w->passcodeWidget(); + } + return false; } FileUploader *uploader() { @@ -181,7 +187,7 @@ namespace { if (audioPlayer()) { audioPlayer()->stopAndClear(); } - if (Window *w = wnd()) { + if (auto w = wnd()) { w->tempDirDelete(Local::ClearManagerAll); w->notifyClearFast(); w->setupIntro(true); @@ -196,7 +202,7 @@ namespace { globalNotifyChatsPtr = UnknownNotifySettings; if (App::uploader()) App::uploader()->clear(); clearStorageImages(); - if (Window *w = wnd()) { + if (auto w = wnd()) { w->getTitle()->updateBackButton(); w->updateTitleStatus(); w->getTitle()->resizeEvent(0); @@ -1762,22 +1768,22 @@ namespace { void historyItemDetached(HistoryItem *item) { if (::hoveredItem == item) { - hoveredItem(0); + hoveredItem(nullptr); } if (::pressedItem == item) { - pressedItem(0); + pressedItem(nullptr); } if (::hoveredLinkItem == item) { - hoveredLinkItem(0); + hoveredLinkItem(nullptr); } if (::pressedLinkItem == item) { - pressedLinkItem(0); + pressedLinkItem(nullptr); } if (::contextItem == item) { - contextItem(0); + contextItem(nullptr); } if (::mousedItem == item) { - mousedItem(0); + mousedItem(nullptr); } if (App::wnd()) { App::wnd()->notifyItemRemoved(item); diff --git a/Telegram/SourceFiles/app.h b/Telegram/SourceFiles/app.h index 3b53eb061..4e437b71b 100644 --- a/Telegram/SourceFiles/app.h +++ b/Telegram/SourceFiles/app.h @@ -20,10 +20,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "basic_types.h" +#include "core/basic_types.h" class AppClass; -class Window; +class MainWindow; class MainWidget; class SettingsWidget; class ApiWrap; @@ -48,7 +48,7 @@ class LayeredWidget; namespace App { AppClass *app(); - Window *wnd(); + MainWindow *wnd(); MainWidget *main(); SettingsWidget *settings(); bool passcoded(); diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp index a011db893..67cfd5ed0 100644 --- a/Telegram/SourceFiles/application.cpp +++ b/Telegram/SourceFiles/application.cpp @@ -20,20 +20,16 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" #include "application.h" + #include "ui/style.h" - #include "shortcuts.h" - #include "pspecific.h" #include "fileuploader.h" #include "mainwidget.h" - #include "lang.h" #include "boxes/confirmbox.h" #include "langloaderplain.h" - #include "localstorage.h" - #include "autoupdater.h" namespace { @@ -90,14 +86,7 @@ namespace { AppClass *AppObject = 0; -Application::Application(int &argc, char **argv) : QApplication(argc, argv) -, _secondInstance(false) -#ifndef TDESKTOP_DISABLE_AUTOUPDATE -, _updateReply(0) -, _updateThread(0) -, _updateChecker(0) -#endif -{ +Application::Application(int &argc, char **argv) : QApplication(argc, argv) { QByteArray d(QFile::encodeName(QDir(cWorkingDir()).absolutePath())); char h[33] = { 0 }; hashMd5Hex(d.constData(), d.size(), h); @@ -718,7 +707,7 @@ AppClass::AppClass() : QObject() QMimeDatabase().mimeTypeForName(qsl("text/plain")); // create mime database - _window = new Window(); + _window = new MainWindow(); _window->createWinId(); _window->init(); @@ -905,6 +894,12 @@ void AppClass::call_handleHistoryUpdate() { Notify::handlePendingHistoryUpdate(); } +void AppClass::call_handleUnreadCounterUpdate() { + if (auto w = App::wnd()) { + w->updateUnreadCounter(); + } +} + void AppClass::killDownloadSessions() { uint64 ms = getms(), left = MTPAckSendWaiting + MTPKillFileSessionTimeout; for (QMap::iterator i = killDownloadSessionTimes.begin(); i != killDownloadSessionTimes.end(); ) { @@ -1035,8 +1030,8 @@ void AppClass::checkMapVersion() { if (Local::oldMapVersion()) { QString versionFeatures; if ((cDevVersion() || cBetaVersion()) && Local::oldMapVersion() < 9041) { -// versionFeatures = QString::fromUtf8("\xe2\x80\x94 Design improvements\n\xe2\x80\x94 Bug fixes and other minor improvements"); - versionFeatures = langNewVersionText(); + versionFeatures = QString::fromUtf8("\xe2\x80\x94 Select and copy text in photo / video captions and web page previews\n\xe2\x80\x94 Media player shortcuts are enabled only when player is opened"); +// versionFeatures = langNewVersionText(); } else if (Local::oldMapVersion() < 9041) { versionFeatures = langNewVersionText(); } else { @@ -1053,7 +1048,7 @@ void AppClass::checkMapVersion() { AppClass::~AppClass() { Shortcuts::finish(); - if (Window *w = _window) { + if (auto w = _window) { _window = 0; delete w; } @@ -1086,7 +1081,7 @@ AppClass *AppClass::app() { return AppObject; } -Window *AppClass::wnd() { +MainWindow *AppClass::wnd() { return AppObject ? AppObject->_window : 0; } diff --git a/Telegram/SourceFiles/application.h b/Telegram/SourceFiles/application.h index ae51569e8..f894102fe 100644 --- a/Telegram/SourceFiles/application.h +++ b/Telegram/SourceFiles/application.h @@ -20,7 +20,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "window.h" +#include "mainwindow.h" #include "pspecific.h" class UpdateChecker; @@ -56,7 +56,7 @@ private: QLocalServer _localServer; QLocalSocket _localSocket; LocalClients _localClients; - bool _secondInstance; + bool _secondInstance = false; void singleInstanceChecked(); @@ -98,10 +98,10 @@ public slots: private: SingleTimer _updateCheckTimer; - QNetworkReply *_updateReply; + QNetworkReply *_updateReply = nullptr; QNetworkAccessManager _updateManager; - QThread *_updateThread; - UpdateChecker *_updateChecker; + QThread *_updateThread = nullptr; + UpdateChecker *_updateChecker = nullptr; #endif }; @@ -153,7 +153,7 @@ public: ~AppClass(); static AppClass *app(); - static Window *wnd(); + static MainWindow *wnd(); static MainWidget *main(); FileUploader *uploader(); @@ -202,6 +202,7 @@ public slots: void onAppStateChanged(Qt::ApplicationState state); void call_handleHistoryUpdate(); + void call_handleUnreadCounterUpdate(); private: @@ -212,7 +213,7 @@ private: uint64 _lastActionTime; - Window *_window; + MainWindow *_window; FileUploader *_uploader; Translator *_translator; diff --git a/Telegram/SourceFiles/audio.h b/Telegram/SourceFiles/audio.h index 4eb75c88d..17bbf8cf0 100644 --- a/Telegram/SourceFiles/audio.h +++ b/Telegram/SourceFiles/audio.h @@ -20,7 +20,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "basic_types.h" +#include "core/basic_types.h" void audioInit(); bool audioWorks(); diff --git a/Telegram/SourceFiles/boxes/aboutbox.cpp b/Telegram/SourceFiles/boxes/aboutbox.cpp index da41b067e..083d6b650 100644 --- a/Telegram/SourceFiles/boxes/aboutbox.cpp +++ b/Telegram/SourceFiles/boxes/aboutbox.cpp @@ -23,7 +23,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "aboutbox.h" #include "mainwidget.h" -#include "window.h" +#include "mainwindow.h" #include "autoupdater.h" #include "boxes/confirmbox.h" diff --git a/Telegram/SourceFiles/boxes/abstractbox.cpp b/Telegram/SourceFiles/boxes/abstractbox.cpp index e9d1783ad..f721c6b57 100644 --- a/Telegram/SourceFiles/boxes/abstractbox.cpp +++ b/Telegram/SourceFiles/boxes/abstractbox.cpp @@ -25,7 +25,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "abstractbox.h" #include "mainwidget.h" -#include "window.h" +#include "mainwindow.h" void BlueTitleShadow::paintEvent(QPaintEvent *e) { Painter p(this); diff --git a/Telegram/SourceFiles/boxes/addcontactbox.cpp b/Telegram/SourceFiles/boxes/addcontactbox.cpp index 7e58787bc..444742a81 100644 --- a/Telegram/SourceFiles/boxes/addcontactbox.cpp +++ b/Telegram/SourceFiles/boxes/addcontactbox.cpp @@ -28,7 +28,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "photocropbox.h" #include "ui/filedialog.h" #include "mainwidget.h" -#include "window.h" +#include "mainwindow.h" +#include "apiwrap.h" AddContactBox::AddContactBox(QString fname, QString lname, QString phone) : AbstractBox(st::boxWidth) , _user(0) diff --git a/Telegram/SourceFiles/boxes/autolockbox.cpp b/Telegram/SourceFiles/boxes/autolockbox.cpp index 3af3b2615..a755697ca 100644 --- a/Telegram/SourceFiles/boxes/autolockbox.cpp +++ b/Telegram/SourceFiles/boxes/autolockbox.cpp @@ -26,7 +26,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "autolockbox.h" #include "confirmbox.h" #include "mainwidget.h" -#include "window.h" +#include "mainwindow.h" AutoLockBox::AutoLockBox() : _close(this, lang(lng_box_ok), st::defaultBoxButton) { diff --git a/Telegram/SourceFiles/boxes/backgroundbox.cpp b/Telegram/SourceFiles/boxes/backgroundbox.cpp index edb6a2bd9..ddbdf4a39 100644 --- a/Telegram/SourceFiles/boxes/backgroundbox.cpp +++ b/Telegram/SourceFiles/boxes/backgroundbox.cpp @@ -23,7 +23,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "backgroundbox.h" #include "mainwidget.h" -#include "window.h" +#include "mainwindow.h" #include "settingswidget.h" BackgroundInner::BackgroundInner() : diff --git a/Telegram/SourceFiles/boxes/confirmbox.cpp b/Telegram/SourceFiles/boxes/confirmbox.cpp index 2f350119b..fcd6b5e1d 100644 --- a/Telegram/SourceFiles/boxes/confirmbox.cpp +++ b/Telegram/SourceFiles/boxes/confirmbox.cpp @@ -23,9 +23,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "confirmbox.h" #include "mainwidget.h" -#include "window.h" - +#include "mainwindow.h" +#include "apiwrap.h" #include "application.h" +#include "core/click_handler_types.h" TextParseOptions _confirmBoxTextOptions = { TextParseLinks | TextParseMultiline | TextParseRichText, // flags @@ -118,10 +119,10 @@ void ConfirmBox::updateHover() { QPoint m(mapFromGlobal(_lastMousePos)); textstyleSet(&st::boxTextStyle); - ClickHandlerPtr handler = _text.linkLeft(m.x() - st::boxPadding.left(), m.y() - st::boxPadding.top(), _textWidth, width(), style::al_left); + auto state = _text.getStateLeft(m.x() - st::boxPadding.left(), m.y() - st::boxPadding.top(), _textWidth, width()); textstyleRestore(); - ClickHandler::setActive(handler, this); + ClickHandler::setActive(state.link, this); } void ConfirmBox::closePressed() { diff --git a/Telegram/SourceFiles/boxes/connectionbox.cpp b/Telegram/SourceFiles/boxes/connectionbox.cpp index c9d65c3bb..8f2fc96b0 100644 --- a/Telegram/SourceFiles/boxes/connectionbox.cpp +++ b/Telegram/SourceFiles/boxes/connectionbox.cpp @@ -25,7 +25,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "connectionbox.h" #include "mainwidget.h" -#include "window.h" +#include "mainwindow.h" ConnectionBox::ConnectionBox() : AbstractBox(st::boxWidth) , _hostInput(this, st::connectionHostInputField, lang(lng_connection_host_ph), cConnectionProxy().host) diff --git a/Telegram/SourceFiles/boxes/contactsbox.cpp b/Telegram/SourceFiles/boxes/contactsbox.cpp index da166de86..92540758a 100644 --- a/Telegram/SourceFiles/boxes/contactsbox.cpp +++ b/Telegram/SourceFiles/boxes/contactsbox.cpp @@ -26,11 +26,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "boxes/addcontactbox.h" #include "boxes/contactsbox.h" #include "mainwidget.h" -#include "window.h" +#include "mainwindow.h" #include "application.h" #include "ui/filedialog.h" #include "boxes/photocropbox.h" #include "boxes/confirmbox.h" +#include "apiwrap.h" QString cantInviteError() { return lng_cant_invite_not_contact(lt_more_info, textcmdLink(qsl("https://telegram.me/spambot"), lang(lng_cant_more_info))); @@ -956,7 +957,6 @@ void ContactsInner::peopleReceived(const QString &query, const QVector if (p->asUser()->botInfo->cantJoinGroups) continue; } if (_channel) { - if (_channel->isMegagroup() && _membersFilter == MembersFilterAdmins) continue; if (!_channel->isMegagroup() && _membersFilter != MembersFilterAdmins) continue; } } diff --git a/Telegram/SourceFiles/boxes/emojibox.cpp b/Telegram/SourceFiles/boxes/emojibox.cpp index 9bd637bed..9aca04bc0 100644 --- a/Telegram/SourceFiles/boxes/emojibox.cpp +++ b/Telegram/SourceFiles/boxes/emojibox.cpp @@ -23,7 +23,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "emojibox.h" #include "mainwidget.h" -#include "window.h" +#include "mainwindow.h" namespace { // copied from genemoji.cpp diff --git a/Telegram/SourceFiles/boxes/languagebox.cpp b/Telegram/SourceFiles/boxes/languagebox.cpp index 9400077e6..8c7fe47ed 100644 --- a/Telegram/SourceFiles/boxes/languagebox.cpp +++ b/Telegram/SourceFiles/boxes/languagebox.cpp @@ -26,7 +26,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "languagebox.h" #include "confirmbox.h" #include "mainwidget.h" -#include "window.h" +#include "mainwindow.h" #include "langloaderplain.h" diff --git a/Telegram/SourceFiles/boxes/passcodebox.cpp b/Telegram/SourceFiles/boxes/passcodebox.cpp index f444a960a..8468e86ff 100644 --- a/Telegram/SourceFiles/boxes/passcodebox.cpp +++ b/Telegram/SourceFiles/boxes/passcodebox.cpp @@ -23,7 +23,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "passcodebox.h" #include "confirmbox.h" -#include "window.h" +#include "mainwindow.h" #include "localstorage.h" diff --git a/Telegram/SourceFiles/boxes/sessionsbox.cpp b/Telegram/SourceFiles/boxes/sessionsbox.cpp index 7bb3c4847..06d84ced1 100644 --- a/Telegram/SourceFiles/boxes/sessionsbox.cpp +++ b/Telegram/SourceFiles/boxes/sessionsbox.cpp @@ -25,7 +25,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "sessionsbox.h" #include "mainwidget.h" -#include "window.h" +#include "mainwindow.h" #include "countries.h" #include "confirmbox.h" diff --git a/Telegram/SourceFiles/boxes/stickersetbox.cpp b/Telegram/SourceFiles/boxes/stickersetbox.cpp index 2756b15bc..814958521 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.cpp +++ b/Telegram/SourceFiles/boxes/stickersetbox.cpp @@ -23,10 +23,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "stickersetbox.h" #include "mainwidget.h" -#include "window.h" +#include "mainwindow.h" #include "settingswidget.h" #include "boxes/confirmbox.h" - +#include "apiwrap.h" #include "localstorage.h" StickerSetInner::StickerSetInner(const MTPInputStickerSet &set) : TWidget() diff --git a/Telegram/SourceFiles/boxes/usernamebox.cpp b/Telegram/SourceFiles/boxes/usernamebox.cpp index 57c940570..7d7bb157f 100644 --- a/Telegram/SourceFiles/boxes/usernamebox.cpp +++ b/Telegram/SourceFiles/boxes/usernamebox.cpp @@ -24,7 +24,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "application.h" #include "usernamebox.h" #include "mainwidget.h" -#include "window.h" +#include "mainwindow.h" UsernameBox::UsernameBox() : AbstractBox(st::boxWidth), _save(this, lang(lng_settings_save), st::defaultBoxButton), diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h index 948dd8380..db8d48770 100644 --- a/Telegram/SourceFiles/config.h +++ b/Telegram/SourceFiles/config.h @@ -20,9 +20,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -static const int32 AppVersion = 9042; -static const wchar_t *AppVersionStr = L"0.9.42"; -static const bool DevVersion = false; +static const int32 AppVersion = 9043; +static const wchar_t *AppVersionStr = L"0.9.43"; +static const bool DevVersion = true; //#define BETA_VERSION (9040128ULL) // just comment this line to build public version static const wchar_t *AppNameOld = L"Telegram Win (Unofficial)"; diff --git a/Telegram/SourceFiles/basic_types.cpp b/Telegram/SourceFiles/core/basic_types.cpp similarity index 100% rename from Telegram/SourceFiles/basic_types.cpp rename to Telegram/SourceFiles/core/basic_types.cpp diff --git a/Telegram/SourceFiles/basic_types.h b/Telegram/SourceFiles/core/basic_types.h similarity index 100% rename from Telegram/SourceFiles/basic_types.h rename to Telegram/SourceFiles/core/basic_types.h diff --git a/Telegram/SourceFiles/core/click_handler.cpp b/Telegram/SourceFiles/core/click_handler.cpp new file mode 100644 index 000000000..09809de14 --- /dev/null +++ b/Telegram/SourceFiles/core/click_handler.cpp @@ -0,0 +1,63 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "core/click_handler.h" + +ClickHandlerHost::~ClickHandlerHost() { + ClickHandler::hostDestroyed(this); +} + +NeverFreedPointer ClickHandler::_active; +NeverFreedPointer ClickHandler::_pressed; +ClickHandlerHost *ClickHandler::_activeHost = nullptr; +ClickHandlerHost *ClickHandler::_pressedHost = nullptr; + +bool ClickHandler::setActive(const ClickHandlerPtr &p, ClickHandlerHost *host) { + if ((_active && (*_active == p)) || (!_active && !p)) { + return false; + } + + // emit clickHandlerActiveChanged only when there is no + // other pressed click handler currently, if there is + // this method will be called when it is unpressed + if (_active && *_active) { + bool emitClickHandlerActiveChanged = (!_pressed || !*_pressed || *_pressed == *_active); + ClickHandlerPtr wasactive = *_active; + (*_active).clear(); + if (_activeHost) { + if (emitClickHandlerActiveChanged) { + _activeHost->clickHandlerActiveChanged(wasactive, false); + } + _activeHost = nullptr; + } + } + if (p) { + _active.makeIfNull(); + *_active = p; + if ((_activeHost = host)) { + bool emitClickHandlerActiveChanged = (!_pressed || !*_pressed || *_pressed == *_active); + if (emitClickHandlerActiveChanged) { + _activeHost->clickHandlerActiveChanged(*_active, true); + } + } + } + return true; +} diff --git a/Telegram/SourceFiles/core/click_handler.h b/Telegram/SourceFiles/core/click_handler.h new file mode 100644 index 000000000..8c997e6b9 --- /dev/null +++ b/Telegram/SourceFiles/core/click_handler.h @@ -0,0 +1,158 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +class ClickHandler; +using ClickHandlerPtr = QSharedPointer; + +class ClickHandlerHost { +protected: + + virtual void clickHandlerActiveChanged(const ClickHandlerPtr &action, bool active) { + } + virtual void clickHandlerPressedChanged(const ClickHandlerPtr &action, bool pressed) { + } + virtual ~ClickHandlerHost() = 0; + friend class ClickHandler; + +}; + +class ClickHandler { +public: + + virtual void onClick(Qt::MouseButton) const = 0; + + virtual QString tooltip() const { + return QString(); + } + virtual void copyToClipboard() const { + } + virtual QString copyToClipboardContextItem() const { + return QString(); + } + virtual QString text() const { + return QString(); + } + virtual QString dragText() const { + return text(); + } + + virtual ~ClickHandler() { + } + + // this method should be called on mouse over a click handler + // it returns true if something was changed or false otherwise + static bool setActive(const ClickHandlerPtr &p, ClickHandlerHost *host = nullptr); + + // this method should be called when mouse leaves the host + // it returns true if something was changed or false otherwise + static bool clearActive(ClickHandlerHost *host = nullptr) { + if (host && _activeHost != host) { + return false; + } + return setActive(ClickHandlerPtr(), host); + } + + // this method should be called on mouse pressed + static void pressed() { + unpressed(); + if (!_active || !*_active) { + return; + } + _pressed.makeIfNull(); + *_pressed = *_active; + if ((_pressedHost = _activeHost)) { + _pressedHost->clickHandlerPressedChanged(*_pressed, true); + } + } + + // this method should be called on mouse released + // the activated click handler is returned + static ClickHandlerPtr unpressed() { + if (_pressed && *_pressed) { + bool activated = (_active && *_active == *_pressed); + ClickHandlerPtr waspressed = *_pressed; + (*_pressed).clear(); + if (_pressedHost) { + _pressedHost->clickHandlerPressedChanged(waspressed, false); + _pressedHost = nullptr; + } + + if (activated) { + return *_active; + } else if (_active && *_active && _activeHost) { + // emit clickHandlerActiveChanged for current active + // click handler, which we didn't emit while we has + // a pressed click handler + _activeHost->clickHandlerActiveChanged(*_active, true); + } + } + return ClickHandlerPtr(); + } + + static ClickHandlerPtr getActive() { + return _active ? *_active : ClickHandlerPtr(); + } + static ClickHandlerPtr getPressed() { + return _pressed ? *_pressed : ClickHandlerPtr(); + } + + static bool showAsActive(const ClickHandlerPtr &p) { + if (!p || !_active || p != *_active) { + return false; + } + return !_pressed || !*_pressed || (p == *_pressed); + } + static bool showAsPressed(const ClickHandlerPtr &p) { + if (!p || !_active || p != *_active) { + return false; + } + return _pressed && (p == *_pressed); + } + static void hostDestroyed(ClickHandlerHost *host) { + if (_activeHost == host) { + _activeHost = nullptr; + } + if (_pressedHost == host) { + _pressedHost = nullptr; + } + } + +private: + + static NeverFreedPointer _active; + static NeverFreedPointer _pressed; + static ClickHandlerHost *_activeHost; + static ClickHandlerHost *_pressedHost; + +}; + +class LeftButtonClickHandler : public ClickHandler { +public: + void onClick(Qt::MouseButton button) const override final { + if (button != Qt::LeftButton) return; + onClickImpl(); + } + +protected: + virtual void onClickImpl() const = 0; + +}; diff --git a/Telegram/SourceFiles/core/click_handler_types.cpp b/Telegram/SourceFiles/core/click_handler_types.cpp new file mode 100644 index 000000000..5bbf2df64 --- /dev/null +++ b/Telegram/SourceFiles/core/click_handler_types.cpp @@ -0,0 +1,139 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "core/click_handler_types.h" + +#include "lang.h" +#include "pspecific.h" +#include "boxes/confirmbox.h" + +QString UrlClickHandler::copyToClipboardContextItem() const { + return lang(isEmail() ? lng_context_copy_email : lng_context_copy_link); +} + +namespace { + +QString tryConvertUrlToLocal(const QString &url) { + QRegularExpressionMatch telegramMeUser = QRegularExpression(qsl("^https?://telegram\\.me/([a-zA-Z0-9\\.\\_]+)(/?\\?|/?$|/(\\d+)/?(?:\\?|$))"), QRegularExpression::CaseInsensitiveOption).match(url); + QRegularExpressionMatch telegramMeGroup = QRegularExpression(qsl("^https?://telegram\\.me/joinchat/([a-zA-Z0-9\\.\\_\\-]+)(\\?|$)"), QRegularExpression::CaseInsensitiveOption).match(url); + QRegularExpressionMatch telegramMeStickers = QRegularExpression(qsl("^https?://telegram\\.me/addstickers/([a-zA-Z0-9\\.\\_]+)(\\?|$)"), QRegularExpression::CaseInsensitiveOption).match(url); + QRegularExpressionMatch telegramMeShareUrl = QRegularExpression(qsl("^https?://telegram\\.me/share/url\\?(.+)$"), QRegularExpression::CaseInsensitiveOption).match(url); + if (telegramMeGroup.hasMatch()) { + return qsl("tg://join?invite=") + myUrlEncode(telegramMeGroup.captured(1)); + } else if (telegramMeStickers.hasMatch()) { + return qsl("tg://addstickers?set=") + myUrlEncode(telegramMeStickers.captured(1)); + } else if (telegramMeShareUrl.hasMatch()) { + return qsl("tg://msg_url?") + telegramMeShareUrl.captured(1); + } else if (telegramMeUser.hasMatch()) { + QString params = url.mid(telegramMeUser.captured(0).size()), postParam; + if (QRegularExpression(qsl("^/\\d+/?(?:\\?|$)")).match(telegramMeUser.captured(2)).hasMatch()) { + postParam = qsl("&post=") + telegramMeUser.captured(3); + } + return qsl("tg://resolve/?domain=") + myUrlEncode(telegramMeUser.captured(1)) + postParam + (params.isEmpty() ? QString() : '&' + params); + } + return url; +} + +} // namespace + +void UrlClickHandler::doOpen(QString url) { + PopupTooltip::Hide(); + + if (isEmail(url)) { + QUrl u(qstr("mailto:") + url); + if (!QDesktopServices::openUrl(u)) { + psOpenFile(u.toString(QUrl::FullyEncoded), true); + } + return; + } + + url = tryConvertUrlToLocal(url); + + if (url.startsWith(qstr("tg://"), Qt::CaseInsensitive)) { + App::openLocalUrl(url); + } else { + QDesktopServices::openUrl(url); + } +} + +void HiddenUrlClickHandler::onClick(Qt::MouseButton button) const { + QString u = url(); + + u = tryConvertUrlToLocal(u); + + if (u.startsWith(qstr("tg://"))) { + App::openLocalUrl(u); + } else { + Ui::showLayer(new ConfirmLinkBox(u)); + } +} + +QString LocationClickHandler::copyToClipboardContextItem() const { + return lang(lng_context_copy_link); +} + +void LocationClickHandler::onClick(Qt::MouseButton button) const { + if (!psLaunchMaps(_coords)) { + QDesktopServices::openUrl(_text); + } +} + +void LocationClickHandler::setup() { + QString latlon(qsl("%1,%2").arg(_coords.lat).arg(_coords.lon)); + _text = qsl("https://maps.google.com/maps?q=") + latlon + qsl("&ll=") + latlon + qsl("&z=16"); +} + +QString MentionClickHandler::copyToClipboardContextItem() const { + return lang(lng_context_copy_mention); +} + +void MentionClickHandler::onClick(Qt::MouseButton button) const { + if (button == Qt::LeftButton || button == Qt::MiddleButton) { + App::openPeerByName(_tag.mid(1), ShowAtProfileMsgId); + } +} + +QString HashtagClickHandler::copyToClipboardContextItem() const { + return lang(lng_context_copy_hashtag); +} + +void HashtagClickHandler::onClick(Qt::MouseButton button) const { + if (button == Qt::LeftButton || button == Qt::MiddleButton) { + App::searchByHashtag(_tag, Ui::getPeerForMouseAction()); + } +} + +void BotCommandClickHandler::onClick(Qt::MouseButton button) const { + if (button == Qt::LeftButton || button == Qt::MiddleButton) { + if (PeerData *peer = Ui::getPeerForMouseAction()) { + UserData *bot = peer->isUser() ? peer->asUser() : nullptr; + if (auto item = App::hoveredLinkItem()) { + if (!bot) { + bot = item->fromOriginal()->asUser(); // may return nullptr + } + } + Ui::showPeerHistory(peer, ShowAtTheEndMsgId); + App::sendBotCommand(peer, bot, _cmd); + } else { + App::insertBotCommand(_cmd); + } + } +} diff --git a/Telegram/SourceFiles/core/click_handler_types.h b/Telegram/SourceFiles/core/click_handler_types.h new file mode 100644 index 000000000..9ca66ad85 --- /dev/null +++ b/Telegram/SourceFiles/core/click_handler_types.h @@ -0,0 +1,181 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "core/click_handler.h" + +class TextClickHandler : public ClickHandler { +public: + + TextClickHandler(bool fullDisplayed = true) : _fullDisplayed(fullDisplayed) { + } + + void copyToClipboard() const override { + QString u = url(); + if (!u.isEmpty()) { + QApplication::clipboard()->setText(u); + } + } + + QString tooltip() const override { + return _fullDisplayed ? QString() : readable(); + } + + void setFullDisplayed(bool full) { + _fullDisplayed = full; + } + +protected: + virtual QString url() const = 0; + virtual QString readable() const { + return url(); + } + + bool _fullDisplayed; + +}; + +class UrlClickHandler : public TextClickHandler { +public: + UrlClickHandler(const QString &url, bool fullDisplayed = true) : TextClickHandler(fullDisplayed), _url(url) { + if (isEmail()) { + _readable = _url; + } else { + QUrl u(_url), good(u.isValid() ? u.toEncoded() : QString()); + _readable = good.isValid() ? good.toDisplayString() : _url; + } + } + QString copyToClipboardContextItem() const override; + + QString text() const override { + return _url; + } + QString dragText() const override { + return url(); + } + + static void doOpen(QString url); + void onClick(Qt::MouseButton button) const override { + if (button == Qt::LeftButton || button == Qt::MiddleButton) { + doOpen(url()); + } + } + +protected: + QString url() const override { + if (isEmail()) { + return _url; + } + + QUrl u(_url), good(u.isValid() ? u.toEncoded() : QString()); + QString result(good.isValid() ? QString::fromUtf8(good.toEncoded()) : _url); + + if (!QRegularExpression(qsl("^[a-zA-Z]+:")).match(result).hasMatch()) { // no protocol + return qsl("http://") + result; + } + return result; + } + QString readable() const override { + return _readable; + } + +private: + static bool isEmail(const QString &url) { + int at = url.indexOf('@'), slash = url.indexOf('/'); + return ((at > 0) && (slash < 0 || slash > at)); + } + bool isEmail() const { + return isEmail(_url); + } + + QString _url, _readable; + +}; +typedef QSharedPointer TextClickHandlerPtr; + +class HiddenUrlClickHandler : public UrlClickHandler { +public: + HiddenUrlClickHandler(QString url) : UrlClickHandler(url, false) { + } + void onClick(Qt::MouseButton button) const override; + +}; + +class MentionClickHandler : public TextClickHandler { +public: + MentionClickHandler(const QString &tag) : _tag(tag) { + } + QString copyToClipboardContextItem() const override; + + QString text() const override { + return _tag; + } + void onClick(Qt::MouseButton button) const override; + +protected: + QString url() const override { + return _tag; + } + +private: + QString _tag; + +}; + +class HashtagClickHandler : public TextClickHandler { +public: + HashtagClickHandler(const QString &tag) : _tag(tag) { + } + QString copyToClipboardContextItem() const override; + + QString text() const override { + return _tag; + } + void onClick(Qt::MouseButton button) const override; + +protected: + QString url() const override { + return _tag; + } + +private: + QString _tag; + +}; + +class BotCommandClickHandler : public TextClickHandler { +public: + BotCommandClickHandler(const QString &cmd) : _cmd(cmd) { + } + QString text() const override { + return _cmd; + } + void onClick(Qt::MouseButton button) const override; + +protected: + QString url() const override { + return _cmd; + } + +private: + QString _cmd; + +}; diff --git a/Telegram/SourceFiles/dialogs/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/dialogs_layout.cpp index d9c5fbc22..081463267 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_layout.cpp @@ -93,7 +93,7 @@ void paintRow(Painter &p, History *history, HistoryItem *item, int w, bool activ p.drawSprite(QPoint(rectForName.left() + rectForName.width() + st::dlgCheckLeft, rectForName.top() + st::dlgCheckTop), *check); } - paintItemCallback(nameleft, namewidth); + paintItemCallback(nameleft, namewidth, item); } if (history->peer->isUser() && history->peer->isVerified()) { @@ -132,10 +132,10 @@ QImage colorizeCircleHalf(int size, int half, int xoffset, style::color color) { int a = color->c.alpha() + 1; int fg_r = color->c.red() * a, fg_g = color->c.green() * a, fg_b = color->c.blue() * a, fg_a = 255 * a; - QImage result(size, size, QImage::Format_ARGB32_Premultiplied); + QImage result(half, size, QImage::Format_ARGB32_Premultiplied); uchar *bits = result.bits(), *maskbits = unreadBadgeStyle->circle.bits(); int bpl = result.bytesPerLine(), maskbpl = unreadBadgeStyle->circle.bytesPerLine(); - for (int x = 0; x < size; ++x) { + for (int x = 0; x < half; ++x) { for (int y = 0; y < size; ++y) { int s = y * bpl + (x * 4); int o = maskbits[y * maskbpl + x + xoffset] + 1; @@ -194,7 +194,7 @@ void paintUnreadCount(Painter &p, const QString &text, int top, int w, bool acti void RowPainter::paint(Painter &p, const Row *row, int w, bool active, bool selected, bool onlyBackground) { auto history = row->history(); auto item = history->lastMsg; - paintRow(p, history, item, w, active, selected, onlyBackground, [&p, w, active, history, item](int nameleft, int namewidth) { + paintRow(p, history, item, w, active, selected, onlyBackground, [&p, w, active, history](int nameleft, int namewidth, HistoryItem *item) { int32 unread = history->unreadCount(); if (history->peer->migrateFrom()) { if (History *h = App::historyLoaded(history->peer->migrateFrom()->id)) { @@ -219,7 +219,7 @@ void RowPainter::paint(Painter &p, const Row *row, int w, bool active, bool sele void RowPainter::paint(Painter &p, const FakeRow *row, int w, bool active, bool selected, bool onlyBackground) { auto item = row->item(); auto history = item->history(); - paintRow(p, history, item, w, active, selected, onlyBackground, [&p, row, active, item](int nameleft, int namewidth) { + paintRow(p, history, item, w, active, selected, onlyBackground, [&p, row, active](int nameleft, int namewidth, HistoryItem *item) { int lastWidth = namewidth, texttop = st::dlgPaddingVer + st::dlgFont->height + st::dlgSep; item->drawInDialog(p, QRect(nameleft, texttop, lastWidth, st::dlgFont->height), active, row->_cacheFor, row->_cache); }); diff --git a/Telegram/SourceFiles/dialogs/dialogs_row.h b/Telegram/SourceFiles/dialogs/dialogs_row.h index 88dd3ca6f..45180f4ff 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_row.h +++ b/Telegram/SourceFiles/dialogs/dialogs_row.h @@ -20,7 +20,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "ui/text.h" +#include "ui/text/text.h" class History; class HistoryItem; diff --git a/Telegram/SourceFiles/dialogswidget.cpp b/Telegram/SourceFiles/dialogswidget.cpp index ee1dd621a..f6447d0a4 100644 --- a/Telegram/SourceFiles/dialogswidget.cpp +++ b/Telegram/SourceFiles/dialogswidget.cpp @@ -26,13 +26,14 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "ui/style.h" #include "lang.h" #include "application.h" -#include "window.h" +#include "mainwindow.h" #include "dialogswidget.h" #include "mainwidget.h" #include "boxes/addcontactbox.h" #include "boxes/contactsbox.h" #include "boxes/confirmbox.h" #include "localstorage.h" +#include "apiwrap.h" DialogsInner::DialogsInner(QWidget *parent, MainWidget *main) : SplittedWidget(parent) , dialogs(std_::make_unique(Dialogs::SortMode::Date)) @@ -1027,7 +1028,7 @@ void DialogsInner::dialogsReceived(const QVector &added) { } } - if (App::wnd()) App::wnd()->updateCounter(); + Notify::unreadCounterUpdated(); if (!_sel && !shownDialogs()->isEmpty()) { _sel = *shownDialogs()->cbegin(); _importantSwitchSel = false; @@ -1935,7 +1936,7 @@ void DialogsWidget::unreadCountsReceived(const QVector &dialogs) { if (History *h = App::historyLoaded(peerFromMTP(d.vpeer))) { App::main()->applyNotifySetting(MTP_notifyPeer(d.vpeer), d.vnotify_settings, h); if (d.vunread_count.v >= h->unreadCount()) { - h->setUnreadCount(d.vunread_count.v, false); + h->setUnreadCount(d.vunread_count.v); h->inboxReadBefore = d.vread_inbox_max_id.v + 1; } } @@ -1953,14 +1954,13 @@ void DialogsWidget::unreadCountsReceived(const QVector &dialogs) { App::main()->applyNotifySetting(MTP_notifyPeer(d.vpeer), d.vnotify_settings, h); int32 unreadCount = h->isMegagroup() ? d.vunread_count.v : d.vunread_important_count.v; if (unreadCount >= h->unreadCount()) { - h->setUnreadCount(unreadCount, false); + h->setUnreadCount(unreadCount); h->inboxReadBefore = d.vread_inbox_max_id.v + 1; } } } break; } } - if (App::wnd()) App::wnd()->updateCounter(); } void DialogsWidget::dialogsReceived(const MTPmessages_Dialogs &dialogs, mtpRequestId req) { diff --git a/Telegram/SourceFiles/dropdown.cpp b/Telegram/SourceFiles/dropdown.cpp index 8fce3e32e..676ff0025 100644 --- a/Telegram/SourceFiles/dropdown.cpp +++ b/Telegram/SourceFiles/dropdown.cpp @@ -28,7 +28,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "historywidget.h" #include "localstorage.h" #include "lang.h" -#include "window.h" +#include "mainwindow.h" #include "apiwrap.h" #include "mainwidget.h" @@ -1349,7 +1349,7 @@ void StickerPanInner::paintInlineItems(Painter &p, const QRect &r) { int w = item->width(); if (left + w > fromx) { p.translate(left, top); - item->paint(p, r.translated(-left, -top), 0, &context); + item->paint(p, r.translated(-left, -top), &context); p.translate(-left, -top); } left += w; diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index fd74f4b6a..4932f1887 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -20,12 +20,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" -#include "window.h" +#include "mainwindow.h" #include "mainwidget.h" #include "application.h" - +#include "core/click_handler_types.h" #include "boxes/confirmbox.h" - #include "layerwidget.h" #include "lang.h" @@ -34,12 +33,16 @@ Q_DECLARE_METATYPE(Qt::MouseButton); namespace App { -void sendBotCommand(PeerData *peer, const QString &cmd, MsgId replyTo) { - if (MainWidget *m = main()) m->sendBotCommand(peer, cmd, replyTo); +void sendBotCommand(PeerData *peer, UserData *bot, const QString &cmd, MsgId replyTo) { + if (auto m = main()) { + m->sendBotCommand(peer, bot, cmd, replyTo); + } } bool insertBotCommand(const QString &cmd, bool specialGif) { - if (MainWidget *m = main()) return m->insertBotCommand(cmd, specialGif); + if (auto m = main()) { + return m->insertBotCommand(cmd, specialGif); + } return false; } @@ -60,7 +63,7 @@ void activateBotCommand(const HistoryItem *msg, int row, int col) { // Copy string before passing it to the sending method // because the original button can be destroyed inside. MsgId replyTo = (msg->id > 0) ? msg->id : 0; - sendBotCommand(msg->history()->peer, QString(button->text), replyTo); + sendBotCommand(msg->history()->peer, msg->fromOriginal()->asUser(), QString(button->text), replyTo); } break; case HistoryMessageReplyMarkup::Button::Callback: { @@ -136,13 +139,13 @@ void removeDialog(History *history) { } void showSettings() { - if (Window *w = wnd()) { + if (auto w = wnd()) { w->showSettings(); } } void activateClickHandler(ClickHandlerPtr handler, Qt::MouseButton button) { - if (Window *w = wnd()) { + if (auto w = wnd()) { qRegisterMetaType(); qRegisterMetaType(); QMetaObject::invokeMethod(w, "app_activateClickHandler", Qt::QueuedConnection, Q_ARG(ClickHandlerPtr, handler), Q_ARG(Qt::MouseButton, button)); @@ -150,7 +153,7 @@ void activateClickHandler(ClickHandlerPtr handler, Qt::MouseButton button) { } void logOutDelayed() { - if (Window *w = App::wnd()) { + if (auto w = App::wnd()) { QMetaObject::invokeMethod(w, "onLogoutSure", Qt::QueuedConnection); } } @@ -160,25 +163,25 @@ void logOutDelayed() { namespace Ui { void showMediaPreview(DocumentData *document) { - if (Window *w = App::wnd()) { + if (auto w = App::wnd()) { w->ui_showMediaPreview(document); } } void showMediaPreview(PhotoData *photo) { - if (Window *w = App::wnd()) { + if (auto w = App::wnd()) { w->ui_showMediaPreview(photo); } } void hideMediaPreview() { - if (Window *w = App::wnd()) { + if (auto w = App::wnd()) { w->ui_hideMediaPreview(); } } void showLayer(LayeredWidget *box, ShowLayerOptions options) { - if (Window *w = App::wnd()) { + if (auto w = App::wnd()) { w->ui_showLayer(box, options); } else { delete box; @@ -186,16 +189,16 @@ void showLayer(LayeredWidget *box, ShowLayerOptions options) { } void hideLayer(bool fast) { - if (Window *w = App::wnd()) w->ui_showLayer(0, ShowLayerOptions(CloseOtherLayers) | (fast ? ForceFastShowLayer : AnimatedShowLayer)); + if (auto w = App::wnd()) w->ui_showLayer(0, ShowLayerOptions(CloseOtherLayers) | (fast ? ForceFastShowLayer : AnimatedShowLayer)); } bool isLayerShown() { - if (Window *w = App::wnd()) return w->ui_isLayerShown(); + if (auto w = App::wnd()) return w->ui_isLayerShown(); return false; } bool isMediaViewShown() { - if (Window *w = App::wnd()) return w->ui_isMediaViewShown(); + if (auto w = App::wnd()) return w->ui_isMediaViewShown(); return false; } @@ -236,7 +239,7 @@ void showPeerHistoryAsync(const PeerId &peer, MsgId msgId) { } PeerData *getPeerForMouseAction() { - if (Window *w = App::wnd()) { + if (auto w = App::wnd()) { return w->ui_getPeerForMouseAction(); } return nullptr; @@ -244,7 +247,7 @@ PeerData *getPeerForMouseAction() { bool hideWindowNoQuit() { if (!App::quitting()) { - if (Window *w = App::wnd()) { + if (auto w = App::wnd()) { if (cWorkMode() == dbiwmTrayOnly || cWorkMode() == dbiwmWindowAndTray) { return w->minimizeToTray(); } else if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) { @@ -327,6 +330,10 @@ void handlePendingHistoryUpdate() { Global::RefPendingRepaintItems().clear(); } +void unreadCounterUpdated() { + Global::RefHandleUnreadCounterUpdate().call(); +} + } // namespace Notify #define DefineReadOnlyVar(Namespace, Type, Name) const Type &Name() { \ @@ -480,6 +487,7 @@ namespace internal { struct Data { uint64 LaunchId = 0; SingleDelayedCall HandleHistoryUpdate = { App::app(), "call_handleHistoryUpdate" }; + SingleDelayedCall HandleUnreadCounterUpdate = { App::app(), "call_handleUnreadCounterUpdate" }; Adaptive::Layout AdaptiveLayout = Adaptive::NormalLayout; bool AdaptiveForWide = true; @@ -542,6 +550,7 @@ void finish() { DefineReadOnlyVar(Global, uint64, LaunchId); DefineRefVar(Global, SingleDelayedCall, HandleHistoryUpdate); +DefineRefVar(Global, SingleDelayedCall, HandleUnreadCounterUpdate); DefineVar(Global, Adaptive::Layout, AdaptiveLayout); DefineVar(Global, bool, AdaptiveForWide); diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h index a8bf6fbb1..412c0d65a 100644 --- a/Telegram/SourceFiles/facades.h +++ b/Telegram/SourceFiles/facades.h @@ -24,7 +24,7 @@ class LayeredWidget; namespace App { -void sendBotCommand(PeerData *peer, const QString &cmd, MsgId replyTo = 0); +void sendBotCommand(PeerData *peer, UserData *bot, const QString &cmd, MsgId replyTo = 0); bool insertBotCommand(const QString &cmd, bool specialGif = false); void activateBotCommand(const HistoryItem *msg, int row, int col); void searchByHashtag(const QString &tag, PeerData *inPeer); @@ -116,6 +116,7 @@ void historyMuteUpdated(History *history); // handle pending resize() / paint() on history items void handlePendingHistoryUpdate(); +void unreadCounterUpdated(); } // namespace Notify @@ -184,6 +185,7 @@ void finish(); DeclareReadOnlyVar(uint64, LaunchId); DeclareRefVar(SingleDelayedCall, HandleHistoryUpdate); +DeclareRefVar(SingleDelayedCall, HandleUnreadCounterUpdate); DeclareVar(Adaptive::Layout, AdaptiveLayout); DeclareVar(bool, AdaptiveForWide); diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index 4a0890ccc..7970cfbe2 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -21,18 +21,22 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "stdafx.h" #include "history.h" +#include "core/click_handler_types.h" #include "dialogs/dialogs_indexed_list.h" #include "ui/style.h" #include "lang.h" #include "mainwidget.h" #include "application.h" #include "fileuploader.h" -#include "window.h" +#include "mainwindow.h" #include "ui/filedialog.h" #include "boxes/addcontactbox.h" #include "boxes/confirmbox.h" #include "audio.h" #include "localstorage.h" +#include "apiwrap.h" +#include "window/top_bar_widget.h" +#include "playerwidget.h" namespace { @@ -321,9 +325,7 @@ void ChannelHistory::insertCollapseItem(MsgId wasMinId) { HistoryItem *item = block->items.at(itemIndex); if (insertAfter || item->id > wasMinId || (item->id == wasMinId && !item->isImportant())) { _collapseMessage = HistoryCollapse::create((History*)this, wasMinId, item->date); - if (!addNewInTheMiddle(_collapseMessage, blockIndex, itemIndex)) { - _collapseMessage = 0; - } + addNewInTheMiddle(_collapseMessage, blockIndex, itemIndex); return; } else if (item->id == wasMinId && item->isImportant()) { insertAfter = true; @@ -659,16 +661,19 @@ void ChannelHistory::switchMode() { checkJoinedMessage(); } -void ChannelHistory::cleared() { - _collapseMessage = 0; - _joinedMessage = 0; +void ChannelHistory::cleared(bool leaveItems) { + _collapseMessage = nullptr; + _joinedMessage = nullptr; + if (!leaveItems) { + _otherList.clear(); + } } HistoryGroup *ChannelHistory::findGroup(MsgId msgId) const { // find message group using binary search if (!_onlyImportant) return findGroupInOther(msgId); HistoryBlock *block = findGroupBlock(msgId); - if (!block) return 0; + if (!block) return nullptr; int32 itemIndex = 0; if (block->items.size() > 1) for (int32 minItem = 0, maxItem = block->items.size();;) { @@ -694,10 +699,14 @@ HistoryGroup *ChannelHistory::findGroup(MsgId msgId) const { // find message gro } } - HistoryItem *item = block->items.at(itemIndex); - if (item->type() != HistoryItemGroup) return 0; - HistoryGroup *result = static_cast(item); - return (result->minId() < msgId && result->maxId() > msgId) ? result : 0; + auto item = block->items.at(itemIndex); + if (item->type() == HistoryItemGroup) { + auto result = static_cast(item); + if (result->minId() < msgId && result->maxId() > msgId) { + return result; + } + } + return nullptr; } HistoryBlock *ChannelHistory::findGroupBlock(MsgId msgId) const { // find block with message group using binary search @@ -834,7 +843,7 @@ History *Histories::findOrInsert(const PeerId &peerId, int32 unreadCount, int32 Map::const_iterator i = map.constFind(peerId); if (i == map.cend()) { i = map.insert(peerId, peerIsChannel(peerId) ? static_cast(new ChannelHistory(peerId)) : (new History(peerId))); - i.value()->setUnreadCount(unreadCount, false); + i.value()->setUnreadCount(unreadCount); i.value()->inboxReadBefore = maxInboxRead + 1; } return i.value(); @@ -842,18 +851,17 @@ History *Histories::findOrInsert(const PeerId &peerId, int32 unreadCount, int32 void Histories::clear() { App::historyClearMsgs(); - for (Map::const_iterator i = map.cbegin(), e = map.cend(); i != e; ++i) { - delete i.value(); + + Map temp; + std::swap(temp, map); + for_const (auto history, temp) { + delete history; } - Global::RefPendingRepaintItems().clear(); _unreadFull = _unreadMuted = 0; - if (App::wnd()) { - App::wnd()->updateCounter(); - } + Notify::unreadCounterUpdated(); App::historyClearItems(); typing.clear(); - map.clear(); } void Histories::regSendAction(History *history, UserData *user, const MTPSendMessageAction &action) { @@ -1738,7 +1746,7 @@ HistoryItem *History::lastImportantMessage() const { return nullptr; } -void History::setUnreadCount(int newUnreadCount, bool psUpdate) { +void History::setUnreadCount(int newUnreadCount) { if (_unreadCount != newUnreadCount) { if (newUnreadCount == 1) { if (loadedAtBottom()) showFrom = lastImportantMessage(); @@ -1749,8 +1757,8 @@ void History::setUnreadCount(int newUnreadCount, bool psUpdate) { } if (inChatList(Dialogs::Mode::All)) { App::histories().unreadIncrement(newUnreadCount - _unreadCount, mute()); - if (psUpdate && (!mute() || cIncludeMuted()) && App::wnd()) { - App::wnd()->updateCounter(); + if (!mute() || cIncludeMuted()) { + Notify::unreadCounterUpdated(); } } _unreadCount = newUnreadCount; @@ -1776,7 +1784,7 @@ void History::setUnreadCount(int newUnreadCount, bool psUpdate) { if (inChatList(Dialogs::Mode::All)) { if (_unreadCount) { App::histories().unreadMuteChanged(_unreadCount, newMute); - if (App::wnd()) App::wnd()->updateCounter(); + Notify::unreadCounterUpdated(); } Notify::historyMuteUpdated(this); } @@ -2175,6 +2183,15 @@ void History::clear(bool leaveItems) { } if (!leaveItems) { setLastMessage(nullptr); + notifies.clear(); + auto &pending = Global::RefPendingRepaintItems(); + for (auto i = pending.begin(); i != pending.end();) { + if ((*i)->history() == this) { + i = pending.erase(i); + } else { + ++i; + } + } } for (int32 i = 0; i < OverviewCount; ++i) { if (!overview[i].isEmpty() || !overviewIds[i].isEmpty()) { @@ -2195,6 +2212,10 @@ void History::clear(bool leaveItems) { lastKeyboardInited = false; } else { setUnreadCount(0); + if (peer->isMegagroup()) { + peer->asChannel()->mgInfo->pinnedMsgId = 0; + } + clearLastKeyboard(); } setPendingResize(); @@ -2205,7 +2226,7 @@ void History::clear(bool leaveItems) { peer->asChat()->lastAuthors.clear(); peer->asChat()->markupSenders.clear(); } else if (isChannel()) { - asChannelHistory()->cleared(); + asChannelHistory()->cleared(leaveItems); if (isMegagroup()) { peer->asChannel()->mgInfo->markupSenders.clear(); } @@ -2247,7 +2268,7 @@ Dialogs::Row *History::addToChatList(Dialogs::Mode list, Dialogs::IndexedList *i chatListLinks(list) = indexed->addToEnd(this); if (list == Dialogs::Mode::All && unreadCount()) { App::histories().unreadIncrement(unreadCount(), mute()); - if (App::wnd()) App::wnd()->updateCounter(); + Notify::unreadCounterUpdated(); } } return mainChatListLink(list); @@ -2260,7 +2281,7 @@ void History::removeFromChatList(Dialogs::Mode list, Dialogs::IndexedList *index chatListLinks(list).clear(); if (list == Dialogs::Mode::All && unreadCount()) { App::histories().unreadIncrement(-unreadCount(), mute()); - if (App::wnd()) App::wnd()->updateCounter(); + Notify::unreadCounterUpdated(); } } } @@ -2622,10 +2643,10 @@ void ReplyKeyboard::resize(int width, int height) { } bool ReplyKeyboard::isEnoughSpace(int width, const style::botKeyboardButton &st) const { - for_const (const ButtonRow &row, _rows) { + for_const (const auto &row, _rows) { int s = row.size(); int widthLeft = width - ((s - 1) * st.margin + s * 2 * st.padding); - for_const (const Button &button, row) { + for_const (const auto &button, row) { widthLeft -= qMax(button.text.maxWidth(), 1); if (widthLeft < 0) { if (row.size() > 3) { @@ -2644,18 +2665,15 @@ void ReplyKeyboard::setStyle(StylePtr &&st) { } int ReplyKeyboard::naturalWidth() const { - int result = 0; + auto result = 0; + for_const (const auto &row, _rows) { + auto rowMaxButtonWidth = 0; + for_const (const auto &button, row) { + accumulate_max(rowMaxButtonWidth, qMax(button.text.maxWidth(), 1) + _st->minButtonWidth(button.type)); + } - auto markup = _item->Get(); - for_const (const ButtonRow &row, _rows) { - int rowSize = row.size(); - int rowWidth = (rowSize - 1) * _st->buttonSkip(); - for_const (const Button &button, row) { - rowWidth += qMax(button.text.maxWidth(), 1) + _st->minButtonWidth(button.type); - } - if (rowWidth > result) { - result = rowWidth; - } + auto rowSize = row.size(); + accumulate_max(result, rowSize * rowMaxButtonWidth + (rowSize - 1) * _st->buttonSkip()); } return result; } @@ -2683,10 +2701,9 @@ void ReplyKeyboard::paint(Painter &p, const QRect &clip) const { } } -void ReplyKeyboard::getState(ClickHandlerPtr &lnk, int x, int y) const { +ClickHandlerPtr ReplyKeyboard::getState(int x, int y) const { t_assert(_width > 0); - lnk.clear(); for_const (const ButtonRow &row, _rows) { for_const (const Button &button, row) { QRect rect(button.rect); @@ -2695,11 +2712,11 @@ void ReplyKeyboard::getState(ClickHandlerPtr &lnk, int x, int y) const { if (rect.x() + rect.width() > _width) break; if (rect.contains(x, y)) { - lnk = button.link; - return; + return button.link; } } } + return ClickHandlerPtr(); } void ReplyKeyboard::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { @@ -2941,6 +2958,24 @@ void HistoryMediaPtr::reset(HistoryMedia *p) { } } +namespace internal { + +TextSelection unshiftSelection(TextSelection selection, const Text &byText) { + if (selection == FullSelection) { + return selection; + } + return ::unshiftSelection(selection, byText); +} + +TextSelection shiftSelection(TextSelection selection, const Text &byText) { + if (selection == FullSelection) { + return selection; + } + return ::shiftSelection(selection, byText); +} + +} // namespace internal + HistoryItem::HistoryItem(History *history, MsgId msgId, MTPDmessage::Flags flags, QDateTime msgDate, int32 from) : HistoryElem() , y(0) , id(msgId) @@ -2976,6 +3011,7 @@ void HistoryItem::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pres } void HistoryItem::destroy() { + // All this must be done for all items manually in History::clear(false)! bool wasAtBottom = history()->loadedAtBottom(); _history->removeNotification(this); detach(); @@ -3239,29 +3275,43 @@ void RadialAnimation::draw(Painter &p, const QRect &inner, int32 thickness, cons } namespace { - int32 documentMaxStatusWidth(DocumentData *document) { - int32 result = st::normalFont->width(formatDownloadText(document->size, document->size)); - if (SongData *song = document->song()) { - result = qMax(result, st::normalFont->width(formatPlayedText(song->duration, song->duration))); - result = qMax(result, st::normalFont->width(formatDurationAndSizeText(song->duration, document->size))); - } else if (VoiceData *voice = document->voice()) { - result = qMax(result, st::normalFont->width(formatPlayedText(voice->duration, voice->duration))); - result = qMax(result, st::normalFont->width(formatDurationAndSizeText(voice->duration, document->size))); - } else if (document->isVideo()) { - result = qMax(result, st::normalFont->width(formatDurationAndSizeText(document->duration(), document->size))); - } else { - result = qMax(result, st::normalFont->width(formatSizeText(document->size))); - } - return result; - } - int32 gifMaxStatusWidth(DocumentData *document) { - int32 result = st::normalFont->width(formatDownloadText(document->size, document->size)); - result = qMax(result, st::normalFont->width(formatGifAndSizeText(document->size))); - return result; +int32 documentMaxStatusWidth(DocumentData *document) { + int32 result = st::normalFont->width(formatDownloadText(document->size, document->size)); + if (SongData *song = document->song()) { + result = qMax(result, st::normalFont->width(formatPlayedText(song->duration, song->duration))); + result = qMax(result, st::normalFont->width(formatDurationAndSizeText(song->duration, document->size))); + } else if (VoiceData *voice = document->voice()) { + result = qMax(result, st::normalFont->width(formatPlayedText(voice->duration, voice->duration))); + result = qMax(result, st::normalFont->width(formatDurationAndSizeText(voice->duration, document->size))); + } else if (document->isVideo()) { + result = qMax(result, st::normalFont->width(formatDurationAndSizeText(document->duration(), document->size))); + } else { + result = qMax(result, st::normalFont->width(formatSizeText(document->size))); } + return result; } +int32 gifMaxStatusWidth(DocumentData *document) { + int32 result = st::normalFont->width(formatDownloadText(document->size, document->size)); + result = qMax(result, st::normalFont->width(formatGifAndSizeText(document->size))); + return result; +} + +QString captionedSelectedText(const QString &attachType, const Text &caption, TextSelection selection) { + if (selection != FullSelection) { + return caption.original(selection, Text::ExpandLinksAll); + } + QString result; + result.reserve(5 + attachType.size() + caption.length()); + result.append(qstr("[ ")).append(attachType).append(qstr(" ]")); + if (!caption.isEmpty()) { + result.append(qstr("\n")).append(caption.original(AllTextSelection)); + } + return result; +} +} // namespace + void HistoryFileMedia::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { if (p == _savel || p == _cancell) { if (active && !dataLoaded()) { @@ -3460,10 +3510,11 @@ int HistoryPhoto::resizeGetHeight(int width) { return _height; } -void HistoryPhoto::draw(Painter &p, const QRect &r, bool selected, uint64 ms) const { +void HistoryPhoto::draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const { if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; _data->automaticLoad(_parent); + bool selected = (selection == FullSelection); bool loaded = _data->loaded(), displayLoading = _data->displayLoading(); bool notChild = (_parent->getMedia() == this); @@ -3557,12 +3608,14 @@ void HistoryPhoto::draw(Painter &p, const QRect &r, bool selected, uint64 ms) co } } else { p.setPen(st::black); - _caption.draw(p, st::msgPadding.left(), skipy + height + st::mediaPadding.bottom() + st::mediaCaptionSkip, captionw); + _caption.draw(p, st::msgPadding.left(), skipy + height + st::mediaPadding.bottom() + st::mediaCaptionSkip, captionw, style::al_left, 0, -1, selection); } } -void HistoryPhoto::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const { - if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; +HistoryTextState HistoryPhoto::getState(int x, int y, HistoryStateRequest request) const { + HistoryTextState result; + + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result; int skipx = 0, skipy = 0, width = _width, height = _height; bool bubble = _parent->hasBubble(); @@ -3573,10 +3626,8 @@ void HistoryPhoto::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int int captionw = width - st::msgPadding.left() - st::msgPadding.right(); height -= _caption.countHeight(captionw) + st::msgPadding.bottom(); if (x >= st::msgPadding.left() && y >= height && x < st::msgPadding.left() + captionw && y < _height) { - bool inText = false; - _caption.getState(lnk, inText, x - st::msgPadding.left(), y - height, captionw); - state = inText ? HistoryInTextCursorState : HistoryDefaultCursorState; - return; + result = _caption.getState(x - st::msgPadding.left(), y - height, captionw, request.forText()); + return result; } height -= st::mediaCaptionSkip; } @@ -3585,26 +3636,27 @@ void HistoryPhoto::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int } if (x >= skipx && y >= skipy && x < skipx + width && y < skipy + height) { if (_data->uploading()) { - lnk = _cancell; + result.link = _cancell; } else if (_data->loaded()) { - lnk = _openl; + result.link = _openl; } else if (_data->loading()) { DelayedStorageImage *delayed = _data->full->toDelayedStorageImage(); if (!delayed || !delayed->location().isNull()) { - lnk = _cancell; + result.link = _cancell; } } else { - lnk = _savel; + result.link = _savel; } if (_caption.isEmpty() && _parent->getMedia() == this) { int32 fullRight = skipx + width, fullBottom = skipy + height; bool inDate = _parent->pointInTime(fullRight, fullBottom, x, y, InfoDisplayOverImage); if (inDate) { - state = HistoryInDateCursorState; + result.cursor = HistoryInDateCursorState; } } - return; + return result; } + return result; } void HistoryPhoto::updateSentMedia(const MTPMessageMedia &media) { @@ -3676,12 +3728,12 @@ void HistoryPhoto::detachFromParent() { App::unregPhotoItem(_data, _parent); } -const QString HistoryPhoto::inDialogsText() const { - return _caption.isEmpty() ? lang(lng_in_dlg_photo) : _caption.original(0, 0xFFFF, Text::ExpandLinksNone); +QString HistoryPhoto::inDialogsText() const { + return _caption.isEmpty() ? lang(lng_in_dlg_photo) : _caption.original(AllTextSelection, Text::ExpandLinksNone); } -const QString HistoryPhoto::inHistoryText() const { - return qsl("[ ") + lang(lng_in_dlg_photo) + (_caption.isEmpty() ? QString() : (qsl(", ") + _caption.original(0, 0xFFFF, Text::ExpandLinksAll))) + qsl(" ]"); +QString HistoryPhoto::selectedText(TextSelection selection) const { + return captionedSelectedText(lang(lng_in_dlg_photo), _caption, selection); } ImagePtr HistoryPhoto::replyPreview() { @@ -3784,11 +3836,12 @@ int HistoryVideo::resizeGetHeight(int width) { return _height; } -void HistoryVideo::draw(Painter &p, const QRect &r, bool selected, uint64 ms) const { +void HistoryVideo::draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const { if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; _data->automaticLoad(_parent); bool loaded = _data->loaded(), displayLoading = _data->displayLoading(); + bool selected = (selection == FullSelection); int skipx = 0, skipy = 0, width = _width, height = _height; bool bubble = _parent->hasBubble(); @@ -3875,12 +3928,14 @@ void HistoryVideo::draw(Painter &p, const QRect &r, bool selected, uint64 ms) co } } else { p.setPen(st::black); - _caption.draw(p, st::msgPadding.left(), skipy + height + st::mediaPadding.bottom() + st::mediaCaptionSkip, captionw); + _caption.draw(p, st::msgPadding.left(), skipy + height + st::mediaPadding.bottom() + st::mediaCaptionSkip, captionw, style::al_left, 0, -1, selection); } } -void HistoryVideo::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const { - if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; +HistoryTextState HistoryVideo::getState(int x, int y, HistoryStateRequest request) const { + HistoryTextState result; + + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result; bool loaded = _data->loaded(); @@ -3894,9 +3949,7 @@ void HistoryVideo::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int int32 captionw = width - st::msgPadding.left() - st::msgPadding.right(); height -= _caption.countHeight(captionw) + st::msgPadding.bottom(); if (x >= st::msgPadding.left() && y >= height && x < st::msgPadding.left() + captionw && y < _height) { - bool inText = false; - _caption.getState(lnk, inText, x - st::msgPadding.left(), y - height, captionw); - state = inText ? HistoryInTextCursorState : HistoryDefaultCursorState; + result = _caption.getState(x - st::msgPadding.left(), y - height, captionw, request.forText()); } height -= st::mediaCaptionSkip; } @@ -3904,28 +3957,29 @@ void HistoryVideo::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int height -= skipy + st::mediaPadding.bottom(); } if (x >= skipx && y >= skipy && x < skipx + width && y < skipy + height) { - lnk = loaded ? _openl : (_data->loading() ? _cancell : _savel); + result.link = loaded ? _openl : (_data->loading() ? _cancell : _savel); if (_caption.isEmpty() && _parent->getMedia() == this) { int32 fullRight = skipx + width, fullBottom = skipy + height; bool inDate = _parent->pointInTime(fullRight, fullBottom, x, y, InfoDisplayOverImage); if (inDate) { - state = HistoryInDateCursorState; + result.cursor = HistoryInDateCursorState; } } - return; + return result; } + return result; } void HistoryVideo::setStatusSize(int32 newSize) const { HistoryFileMedia::setStatusSize(newSize, _data->size, _data->duration(), 0); } -const QString HistoryVideo::inDialogsText() const { - return _caption.isEmpty() ? lang(lng_in_dlg_video) : _caption.original(0, 0xFFFF, Text::ExpandLinksNone); +QString HistoryVideo::inDialogsText() const { + return _caption.isEmpty() ? lang(lng_in_dlg_video) : _caption.original(AllTextSelection, Text::ExpandLinksNone); } -const QString HistoryVideo::inHistoryText() const { - return qsl("[ ") + lang(lng_in_dlg_video) + (_caption.isEmpty() ? QString() : (qsl(", ") + _caption.original(0, 0xFFFF, Text::ExpandLinksAll))) + qsl(" ]"); +QString HistoryVideo::selectedText(TextSelection selection) const { + return captionedSelectedText(lang(lng_in_dlg_video), _caption, selection); } void HistoryVideo::updateStatusText() const { @@ -4122,11 +4176,12 @@ int HistoryDocument::resizeGetHeight(int width) { return _height; } -void HistoryDocument::draw(Painter &p, const QRect &r, bool selected, uint64 ms) const { +void HistoryDocument::draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const { if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; _data->automaticLoad(_parent); bool loaded = _data->loaded(), displayLoading = _data->displayLoading(); + bool selected = (selection == FullSelection); int captionw = _width - st::msgPadding.left() - st::msgPadding.right(); @@ -4338,12 +4393,14 @@ void HistoryDocument::draw(Painter &p, const QRect &r, bool selected, uint64 ms) if (auto captioned = Get()) { p.setPen(st::black); - captioned->_caption.draw(p, st::msgPadding.left(), bottom, captionw); + captioned->_caption.draw(p, st::msgPadding.left(), bottom, captionw, style::al_left, 0, -1, selection); } } -void HistoryDocument::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const { - if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; +HistoryTextState HistoryDocument::getState(int x, int y, HistoryStateRequest request) const { + HistoryTextState result; + + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result; bool out = _parent->out(), isPost = _parent->isPost(), outbg = out && !isPost; bool loaded = _data->loaded(); @@ -4359,14 +4416,14 @@ void HistoryDocument::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, QRect rthumb(rtlrect(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top(), st::msgFileThumbSize, st::msgFileThumbSize, _width)); if ((_data->loading() || _data->uploading() || !loaded) && rthumb.contains(x, y)) { - lnk = (_data->loading() || _data->uploading()) ? _cancell : _savel; - return; + result.link = (_data->loading() || _data->uploading()) ? _cancell : _savel; + return result; } if (_data->status != FileUploadFailed) { if (rtlrect(nameleft, linktop, thumbed->_linkw, st::semiboldFont->height, _width).contains(x, y)) { - lnk = (_data->loading() || _data->uploading()) ? thumbed->_linkcancell : thumbed->_linksavel; - return; + result.link = (_data->loading() || _data->uploading()) ? thumbed->_linkcancell : thumbed->_linksavel; + return result; } } } else { @@ -4374,28 +4431,27 @@ void HistoryDocument::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, QRect inner(rtlrect(st::msgFilePadding.left(), st::msgFilePadding.top(), st::msgFileSize, st::msgFileSize, _width)); if ((_data->loading() || _data->uploading() || !loaded) && inner.contains(x, y)) { - lnk = (_data->loading() || _data->uploading()) ? _cancell : _savel; - return; + result.link = (_data->loading() || _data->uploading()) ? _cancell : _savel; + return result; } } int32 height = _height; if (auto captioned = Get()) { if (y >= bottom) { - bool inText = false; - captioned->_caption.getState(lnk, inText, x - st::msgPadding.left(), y - bottom, _width - st::msgPadding.left() - st::msgPadding.right()); - state = inText ? HistoryInTextCursorState : HistoryDefaultCursorState; - return; + result = captioned->_caption.getState(x - st::msgPadding.left(), y - bottom, _width - st::msgPadding.left() - st::msgPadding.right(), request.forText()); + return result; } height -= captioned->_caption.countHeight(_width - st::msgPadding.left() - st::msgPadding.right()) + st::msgPadding.bottom(); } if (x >= 0 && y >= 0 && x < _width && y < height && !_data->loading() && !_data->uploading() && _data->isValid()) { - lnk = _openl; - return; + result.link = _openl; + return result; } + return result; } -const QString HistoryDocument::inDialogsText() const { +QString HistoryDocument::inDialogsText() const { QString result; if (Has()) { result = lang(lng_in_dlg_audio); @@ -4410,32 +4466,30 @@ const QString HistoryDocument::inDialogsText() const { } if (auto captioned = Get()) { if (!captioned->_caption.isEmpty()) { - result.append(' ').append(captioned->_caption.original(0, 0xFFFF, Text::ExpandLinksNone)); + result.append(' ').append(captioned->_caption.original(AllTextSelection, Text::ExpandLinksNone)); } } return result; } -const QString HistoryDocument::inHistoryText() const { - QString result; +QString HistoryDocument::selectedText(TextSelection selection) const { + const Text emptyCaption; + const Text *caption = &emptyCaption; + if (auto captioned = Get()) { + caption = &captioned->_caption; + } + QString attachType = lang(lng_in_dlg_file); if (Has()) { - result = lang(lng_in_dlg_audio); + attachType = lang(lng_in_dlg_audio); } else if (_data->song()) { - result = lang(lng_in_dlg_audio_file); - } else { - result = lang(lng_in_dlg_file); + attachType = lang(lng_in_dlg_audio_file); } if (auto named = Get()) { if (!named->_name.isEmpty()) { - result.append(qsl(" : ")).append(named->_name); + attachType.append(qstr(" : ")).append(named->_name); } } - if (auto captioned = Get()) { - if (!captioned->_caption.isEmpty()) { - result.append(qsl(", ")).append(captioned->_caption.original(0, 0xFFFF, Text::ExpandLinksAll)); - } - } - return qsl("[ ") + result.append(qsl(" ]")); + return captionedSelectedText(attachType, *caption, selection); } void HistoryDocument::setStatusSize(int32 newSize, qint64 realDuration) const { @@ -4715,11 +4769,13 @@ int HistoryGif::resizeGetHeight(int width) { return _height; } -void HistoryGif::draw(Painter &p, const QRect &r, bool selected, uint64 ms) const { +void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const { if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; _data->automaticLoad(_parent); bool loaded = _data->loaded(), displayLoading = (_parent->id < 0) || _data->displayLoading(); + bool selected = (selection == FullSelection); + if (loaded && !gif() && _gif != BadClipReader && cAutoPlayGif()) { Ui::autoplayMediaInlineAsync(_parent->fullId()); } @@ -4820,15 +4876,17 @@ void HistoryGif::draw(Painter &p, const QRect &r, bool selected, uint64 ms) cons if (!_caption.isEmpty()) { p.setPen(st::black); - _caption.draw(p, st::msgPadding.left(), skipy + height + st::mediaPadding.bottom() + st::mediaCaptionSkip, captionw); + _caption.draw(p, st::msgPadding.left(), skipy + height + st::mediaPadding.bottom() + st::mediaCaptionSkip, captionw, style::al_left, 0, -1, selection); } else if (_parent->getMedia() == this && (_data->uploading() || App::hoveredItem() == _parent)) { int32 fullRight = skipx + width, fullBottom = skipy + height; _parent->drawInfo(p, fullRight, fullBottom, 2 * skipx + width, selected, InfoDisplayOverImage); } } -void HistoryGif::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const { - if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; +HistoryTextState HistoryGif::getState(int x, int y, HistoryStateRequest request) const { + HistoryTextState result; + + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result; int32 skipx = 0, skipy = 0, width = _width, height = _height; bool bubble = _parent->hasBubble(); @@ -4839,10 +4897,8 @@ void HistoryGif::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x int32 captionw = width - st::msgPadding.left() - st::msgPadding.right(); height -= _caption.countHeight(captionw) + st::msgPadding.bottom(); if (x >= st::msgPadding.left() && y >= height && x < st::msgPadding.left() + captionw && y < _height) { - bool inText = false; - _caption.getState(lnk, inText, x - st::msgPadding.left(), y - height, captionw); - state = inText ? HistoryInTextCursorState : HistoryDefaultCursorState; - return; + result = _caption.getState(x - st::msgPadding.left(), y - height, captionw, request.forText()); + return result; } height -= st::mediaCaptionSkip; } @@ -4851,27 +4907,28 @@ void HistoryGif::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x } if (x >= skipx && y >= skipy && x < skipx + width && y < skipy + height) { if (_data->uploading()) { - lnk = _cancell; + result.link = _cancell; } else if (!gif() || !cAutoPlayGif()) { - lnk = _data->loaded() ? _openl : (_data->loading() ? _cancell : _savel); + result.link = _data->loaded() ? _openl : (_data->loading() ? _cancell : _savel); } if (_parent->getMedia() == this) { int32 fullRight = skipx + width, fullBottom = skipy + height; bool inDate = _parent->pointInTime(fullRight, fullBottom, x, y, InfoDisplayOverImage); if (inDate) { - state = HistoryInDateCursorState; + result.cursor = HistoryInDateCursorState; } } - return; + return result; } + return result; } -const QString HistoryGif::inDialogsText() const { - return qsl("GIF") + (_caption.isEmpty() ? QString() : (' ' + _caption.original(0, 0xFFFF, Text::ExpandLinksNone))); +QString HistoryGif::inDialogsText() const { + return qsl("GIF") + (_caption.isEmpty() ? QString() : (' ' + _caption.original(AllTextSelection, Text::ExpandLinksNone))); } -const QString HistoryGif::inHistoryText() const { - return qsl("[ GIF ") + (_caption.isEmpty() ? QString() : (_caption.original(0, 0xFFFF, Text::ExpandLinksAll) + ' ')) + qsl(" ]"); +QString HistoryGif::selectedText(TextSelection selection) const { + return captionedSelectedText(qsl("GIF"), _caption, selection); } void HistoryGif::setStatusSize(int32 newSize) const { @@ -5044,11 +5101,12 @@ int HistorySticker::resizeGetHeight(int width) { // return new height return _height; } -void HistorySticker::draw(Painter &p, const QRect &r, bool selected, uint64 ms) const { +void HistorySticker::draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const { if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; _data->checkSticker(); bool loaded = _data->loaded(); + bool selected = (selection == FullSelection); bool out = _parent->out(), isPost = _parent->isPost(), childmedia = (_parent->getMedia() != this); @@ -5116,8 +5174,9 @@ void HistorySticker::draw(Painter &p, const QRect &r, bool selected, uint64 ms) } } -void HistorySticker::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const { - if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; +HistoryTextState HistorySticker::getState(int x, int y, HistoryStateRequest request) const { + HistoryTextState result; + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result; bool out = _parent->out(), isPost = _parent->isPost(), childmedia = (_parent->getMedia() != this); @@ -5152,8 +5211,8 @@ void HistorySticker::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, i if (via) { int viah = st::msgReplyPadding.top() + st::msgServiceNameFont->height + (reply ? 0 : st::msgReplyPadding.bottom()); if (x >= rectx && y >= recty && x < rectx + rectw && y < recty + viah) { - lnk = via->_lnk; - return; + result.link = via->_lnk; + return result; } int skip = st::msgServiceNameFont->height + (reply ? 2 * st::msgReplyPadding.top() : 0); recty += skip; @@ -5161,30 +5220,34 @@ void HistorySticker::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, i } if (reply) { if (x >= rectx && y >= recty && x < rectx + rectw && y < recty + recth) { - lnk = reply->replyToLink(); - return; + result.link = reply->replyToLink(); + return result; } } } if (_parent->getMedia() == this) { bool inDate = _parent->pointInTime(usex + usew, _height, x, y, InfoDisplayOverImage); if (inDate) { - state = HistoryInDateCursorState; + result.cursor = HistoryInDateCursorState; } } int pixLeft = usex + (usew - _pixw) / 2, pixTop = (_minh - _pixh) / 2; if (x >= pixLeft && x < pixLeft + _pixw && y >= pixTop && y < pixTop + _pixh) { - lnk = _packLink; - return; + result.link = _packLink; + return result; } + return result; } -const QString HistorySticker::inDialogsText() const { +QString HistorySticker::inDialogsText() const { return _emoji.isEmpty() ? lang(lng_in_dlg_sticker) : lng_in_dlg_sticker_emoji(lt_emoji, _emoji); } -const QString HistorySticker::inHistoryText() const { +QString HistorySticker::selectedText(TextSelection selection) const { + if (selection != FullSelection) { + return QString(); + } return qsl("[ ") + inDialogsText() + qsl(" ]"); } @@ -5288,11 +5351,12 @@ void HistoryContact::initDimensions() { _height = _minh; } -void HistoryContact::draw(Painter &p, const QRect &r, bool selected, uint64 ms) const { +void HistoryContact::draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const { if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; int32 skipx = 0, skipy = 0, width = _width, height = _height; bool out = _parent->out(), isPost = _parent->isPost(), outbg = out && !isPost; + bool selected = (selection == FullSelection); if (width >= _maxw) { width = _maxw; @@ -5341,7 +5405,8 @@ void HistoryContact::draw(Painter &p, const QRect &r, bool selected, uint64 ms) p.drawTextLeft(nameleft, statustop, width, _phone); } -void HistoryContact::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const { +HistoryTextState HistoryContact::getState(int x, int y, HistoryStateRequest request) const { + HistoryTextState result; bool out = _parent->out(), isPost = _parent->isPost(), outbg = out && !isPost; int32 nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0; @@ -5349,22 +5414,26 @@ void HistoryContact::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, i nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right(); linktop = st::msgFileThumbLinkTop; if (rtlrect(nameleft, linktop, _linkw, st::semiboldFont->height, _width).contains(x, y)) { - lnk = _linkl; - return; + result.link = _linkl; + return result; } } if (x >= 0 && y >= 0 && x < _width && y < _height && _contact) { - lnk = _contact->openLink(); - return; + result.link = _contact->openLink(); + return result; } + return result; } -const QString HistoryContact::inDialogsText() const { +QString HistoryContact::inDialogsText() const { return lang(lng_in_dlg_contact); } -const QString HistoryContact::inHistoryText() const { - return qsl("[ ") + lang(lng_in_dlg_contact) + qsl(" : ") + _name.original() + qsl(", ") + _phone + qsl(" ]"); +QString HistoryContact::selectedText(TextSelection selection) const { + if (selection != FullSelection) { + return QString(); + } + return qsl("[ ") + lang(lng_in_dlg_contact) + qsl(" ]\n") + _name.original() + '\n' + _phone; } void HistoryContact::attachToParent() { @@ -5666,11 +5735,12 @@ int HistoryWebPage::resizeGetHeight(int width) { return _height; } -void HistoryWebPage::draw(Painter &p, const QRect &r, bool selected, uint64 ms) const { +void HistoryWebPage::draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const { if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; int32 skipx = 0, skipy = 0, width = _width, height = _height; bool out = _parent->out(), isPost = _parent->isPost(), outbg = out && !isPost; + bool selected = (selection == FullSelection); style::color barfg = (selected ? (outbg ? st::msgOutReplyBarSelColor : st::msgInReplyBarSelColor) : (outbg ? st::msgOutReplyBarColor : st::msgInReplyBarColor)); style::color semibold = (selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg)); @@ -5722,7 +5792,7 @@ void HistoryWebPage::draw(Painter &p, const QRect &r, bool selected, uint64 ms) if (_title.hasSkipBlock()) { endskip = _parent->skipBlockWidth(); } - _title.drawLeftElided(p, lshift, tshift, width, _width, _titleLines, style::al_left, 0, -1, endskip); + _title.drawLeftElided(p, lshift, tshift, width, _width, _titleLines, style::al_left, 0, -1, endskip, false, selection); tshift += _titleLines * _lineHeight; } if (_descriptionLines) { @@ -5731,7 +5801,7 @@ void HistoryWebPage::draw(Painter &p, const QRect &r, bool selected, uint64 ms) if (_description.hasSkipBlock()) { endskip = _parent->skipBlockWidth(); } - _description.drawLeftElided(p, lshift, tshift, width, _width, _descriptionLines, style::al_left, 0, -1, endskip); + _description.drawLeftElided(p, lshift, tshift, width, _width, _descriptionLines, style::al_left, 0, -1, endskip, false, toDescriptionSelection(selection)); tshift += _descriptionLines * _lineHeight; } if (_attach) { @@ -5743,7 +5813,8 @@ void HistoryWebPage::draw(Painter &p, const QRect &r, bool selected, uint64 ms) p.save(); p.translate(attachLeft, attachTop); - _attach->draw(p, r.translated(-attachLeft, -attachTop), selected, ms); + auto attachSelection = selected ? FullSelection : TextSelection{ 0, 0 }; + _attach->draw(p, r.translated(-attachLeft, -attachTop), attachSelection, ms); int32 pixwidth = _attach->currentWidth(), pixheight = _attach->height(); if (_data->type == WebPageVideo) { @@ -5770,8 +5841,10 @@ void HistoryWebPage::draw(Painter &p, const QRect &r, bool selected, uint64 ms) } } -void HistoryWebPage::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const { - if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; +HistoryTextState HistoryWebPage::getState(int x, int y, HistoryStateRequest request) const { + HistoryTextState result; + + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result; int32 skipx = 0, skipy = 0, width = _width, height = _height; int32 lshift = st::msgPadding.left() + st::webPageLeft, rshift = st::msgPadding.right(), bshift = st::msgPadding.bottom(); @@ -5781,48 +5854,74 @@ void HistoryWebPage::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, i bshift += st::msgDateFont->height; } + bool inThumb = false; if (_asArticle) { int32 pw = qMax(_pixw, int16(_lineHeight)); if (rtlrect(lshift + width - pw, 0, pw, _pixh, _width).contains(x, y)) { - lnk = _openl; - return; + inThumb = true; } width -= pw + st::webPagePhotoDelta; } - int32 tshift = 0; + int tshift = 0, symbolAdd = 0; if (_siteNameWidth) { tshift += _lineHeight; } if (_titleLines) { + if (y >= tshift && y < tshift + _titleLines * _lineHeight) { + Text::StateRequestElided titleRequest = request.forText(); + titleRequest.lines = _titleLines; + result = _title.getStateElidedLeft(x - lshift, y - tshift, width, _width, titleRequest); + } else if (y >= tshift + _titleLines * _lineHeight) { + symbolAdd += _title.length(); + } tshift += _titleLines * _lineHeight; } if (_descriptionLines) { if (y >= tshift && y < tshift + _descriptionLines * _lineHeight) { - bool inText = false; - _description.getStateLeft(lnk, inText, x - lshift, y - tshift, width, _width); - state = inText ? HistoryInTextCursorState : HistoryDefaultCursorState; - return; + Text::StateRequestElided descriptionRequest = request.forText(); + descriptionRequest.lines = _descriptionLines; + result = _description.getStateElidedLeft(x - lshift, y - tshift, width, _width, descriptionRequest); + } else if (y >= tshift + _descriptionLines * _lineHeight) { + symbolAdd += _description.length(); } tshift += _descriptionLines * _lineHeight; } - if (_attach) { + if (inThumb) { + result.link = _openl; + } else if (_attach) { if (tshift) tshift += st::webPagePhotoSkip; if (x >= lshift && x < lshift + width && y >= tshift && y < _height - st::msgPadding.bottom()) { int32 attachLeft = lshift - bubble.left(), attachTop = tshift - bubble.top(); if (rtl()) attachLeft = _width - attachLeft - _attach->currentWidth(); - _attach->getState(lnk, state, x - attachLeft, y - attachTop); - if (lnk && !_data->document && _data->photo) { + result = _attach->getState(x - attachLeft, y - attachTop, request); + + if (result.link && !_data->document && _data->photo) { if (_data->type == WebPageProfile || _data->type == WebPageVideo) { - lnk = _openl; + result.link = _openl; } else if (_data->type == WebPagePhoto || _data->siteName == qstr("Twitter") || _data->siteName == qstr("Facebook")) { // leave photo link } else { - lnk = _openl; + result.link = _openl; } } } } + + result.symbol += symbolAdd; + return result; +} + +TextSelection HistoryWebPage::adjustSelection(TextSelection selection, TextSelectType type) const { + if (!_descriptionLines || selection.to <= _title.length()) { + return _title.adjustSelection(selection, type); + } + auto descriptionSelection = _description.adjustSelection(toDescriptionSelection(selection), type); + if (selection.from >= _title.length()) { + return fromDescriptionSelection(descriptionSelection); + } + auto titleSelection = _title.adjustSelection(selection, type); + return { titleSelection.from, fromDescriptionSelection(descriptionSelection).to }; } void HistoryWebPage::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { @@ -5847,12 +5946,22 @@ void HistoryWebPage::detachFromParent() { if (_attach) _attach->detachFromParent(); } -const QString HistoryWebPage::inDialogsText() const { +QString HistoryWebPage::inDialogsText() const { return QString(); } -const QString HistoryWebPage::inHistoryText() const { - return QString(); +QString HistoryWebPage::selectedText(TextSelection selection) const { + if (selection == FullSelection) { + return QString(); + } + auto titleResult = _title.original(selection, Text::ExpandLinksAll); + auto descriptionResult = _description.original(toDescriptionSelection(selection), Text::ExpandLinksAll); + if (titleResult.isEmpty()) { + return descriptionResult; + } else if (descriptionResult.isEmpty()) { + return titleResult; + } + return titleResult + '\n' + descriptionResult; } ImagePtr HistoryWebPage::replyPreview() { @@ -6148,11 +6257,12 @@ int HistoryLocation::resizeGetHeight(int width) { return _height; } -void HistoryLocation::draw(Painter &p, const QRect &r, bool selected, uint64 ms) const { +void HistoryLocation::draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const { if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; int32 skipx = 0, skipy = 0, width = _width, height = _height; bool bubble = _parent->hasBubble(); bool out = _parent->out(), isPost = _parent->isPost(), outbg = out && !isPost; + bool selected = (selection == FullSelection); if (bubble) { skipx = st::mediaPadding.left(); @@ -6169,11 +6279,11 @@ void HistoryLocation::draw(Painter &p, const QRect &r, bool selected, uint64 ms) p.setPen(st::black); if (!_title.isEmpty()) { - _title.drawLeftElided(p, skipx + st::msgPadding.left(), skipy, textw, _width, 2); + _title.drawLeftElided(p, skipx + st::msgPadding.left(), skipy, textw, _width, 2, style::al_left, 0, -1, 0, false, selection); skipy += qMin(_title.countHeight(textw), 2 * st::webPageTitleFont->height); } if (!_description.isEmpty()) { - _description.drawLeftElided(p, skipx + st::msgPadding.left(), skipy, textw, _width, 3); + _description.drawLeftElided(p, skipx + st::msgPadding.left(), skipy, textw, _width, 3, style::al_left, 0, -1, 0, false, toDescriptionSelection(selection)); skipy += qMin(_description.countHeight(textw), 3 * st::webPageDescriptionFont->height); } if (!_title.isEmpty() || !_description.isEmpty()) { @@ -6212,8 +6322,11 @@ void HistoryLocation::draw(Painter &p, const QRect &r, bool selected, uint64 ms) } } -void HistoryLocation::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const { - if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; +HistoryTextState HistoryLocation::getState(int x, int y, HistoryStateRequest request) const { + HistoryTextState result; + auto symbolAdd = 0; + + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result; int32 skipx = 0, skipy = 0, width = _width, height = _height; bool bubble = _parent->hasBubble(); @@ -6231,10 +6344,23 @@ void HistoryLocation::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int32 textw = _width - st::msgPadding.left() - st::msgPadding.right(); if (!_title.isEmpty()) { - skipy += qMin(_title.countHeight(textw), 2 * st::webPageTitleFont->height); + auto titleh = qMin(_title.countHeight(textw), 2 * st::webPageTitleFont->height); + if (y >= skipy && y < skipy + titleh) { + result = _title.getStateLeft(x - skipx - st::msgPadding.left(), y - skipy, textw, _width, request.forText()); + return result; + } else if (y >= skipy + titleh) { + symbolAdd += _title.length(); + } + skipy += titleh; } if (!_description.isEmpty()) { - skipy += qMin(_description.countHeight(textw), 3 * st::webPageDescriptionFont->height); + auto descriptionh = qMin(_description.countHeight(textw), 3 * st::webPageDescriptionFont->height); + if (y >= skipy && y < skipy + descriptionh) { + result = _description.getStateLeft(x - skipx - st::msgPadding.left(), y - skipy, textw, _width, request.forText()); + } else if (y >= skipy + descriptionh) { + symbolAdd += _description.length(); + } + skipy += descriptionh; } if (!_title.isEmpty() || !_description.isEmpty()) { skipy += st::webPagePhotoSkip; @@ -6242,24 +6368,49 @@ void HistoryLocation::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, height -= skipy + st::mediaPadding.bottom(); } if (x >= skipx && y >= skipy && x < skipx + width && y < skipy + height && _data) { - lnk = _link; + result.link = _link; int32 fullRight = skipx + width, fullBottom = _height - (skipx ? st::mediaPadding.bottom() : 0); bool inDate = _parent->pointInTime(fullRight, fullBottom, x, y, InfoDisplayOverImage); if (inDate) { - state = HistoryInDateCursorState; + result.cursor = HistoryInDateCursorState; } - - return; } + result.symbol += symbolAdd; + return result; } -const QString HistoryLocation::inDialogsText() const { - return lang(lng_maps_point); +TextSelection HistoryLocation::adjustSelection(TextSelection selection, TextSelectType type) const { + if (_description.isEmpty() || selection.to <= _title.length()) { + return _title.adjustSelection(selection, type); + } + auto descriptionSelection = _description.adjustSelection(toDescriptionSelection(selection), type); + if (selection.from >= _title.length()) { + return fromDescriptionSelection(descriptionSelection); + } + auto titleSelection = _title.adjustSelection(selection, type); + return { titleSelection.from, fromDescriptionSelection(descriptionSelection).to }; } -const QString HistoryLocation::inHistoryText() const { - return qsl("[ ") + lang(lng_maps_point) + qsl(" : ") + _link->text() + qsl(" ]"); +QString HistoryLocation::inDialogsText() const { + return _title.isEmpty() ? lang(lng_maps_point) : _title.original(AllTextSelection); +} + +QString HistoryLocation::selectedText(TextSelection selection) const { + if (selection == FullSelection) { + auto result = qsl("[ ") + lang(lng_maps_point) + qsl(" ]\n"); + auto info = selectedText(AllTextSelection); + if (!info.isEmpty()) result.append(info).append('\n'); + return result + _link->text(); + } + auto titleResult = _title.original(selection); + auto descriptionResult = _description.original(toDescriptionSelection(selection)); + if (titleResult.isEmpty()) { + return descriptionResult; + } else if (descriptionResult.isEmpty()) { + return titleResult; + } + return titleResult + '\n' + descriptionResult; } int32 HistoryLocation::fullWidth() const { @@ -6968,19 +7119,26 @@ void HistoryMessage::eraseFromOverview() { } } -QString HistoryMessage::selectedText(uint32 selection) const { - QString result; - if (_media && selection == FullSelection) { - QString text = _text.original(0, 0xFFFF, Text::ExpandLinksAll), mediaText = _media->inHistoryText(); - result = text.isEmpty() ? mediaText : (mediaText.isEmpty() ? text : (text + ' ' + mediaText)); +QString HistoryMessage::selectedText(TextSelection selection) const { + QString result, textResult, mediaResult; + if (selection == FullSelection) { + textResult = _text.original(AllTextSelection, Text::ExpandLinksAll); } else { - uint16 selectedFrom = (selection == FullSelection) ? 0 : ((selection >> 16) & 0xFFFF); - uint16 selectedTo = (selection == FullSelection) ? 0xFFFF : (selection & 0xFFFF); - result = _text.original(selectedFrom, selectedTo, Text::ExpandLinksAll); + textResult = _text.original(selection, Text::ExpandLinksAll); + } + if (_media) { + mediaResult = _media->selectedText(toMediaSelection(selection)); + } + if (textResult.isEmpty()) { + result = mediaResult; + } else if (mediaResult.isEmpty()) { + result = textResult; + } else { + result = textResult + qstr("\n\n") + mediaResult; } if (auto fwd = Get()) { if (selection == FullSelection) { - QString fwdinfo = fwd->_text.original(0, 0xFFFF, Text::ExpandLinksAll), wrapped; + QString fwdinfo = fwd->_text.original(AllTextSelection, Text::ExpandLinksAll), wrapped; wrapped.reserve(fwdinfo.size() + 4 + result.size()); wrapped.append('[').append(fwdinfo).append(qsl("]\n")).append(result); result = wrapped; @@ -6998,7 +7156,7 @@ QString HistoryMessage::selectedText(uint32 selection) const { } QString HistoryMessage::inDialogsText() const { - return emptyText() ? (_media ? _media->inDialogsText() : QString()) : _text.original(0, 0xFFFF, Text::ExpandLinksNone); + return emptyText() ? (_media ? _media->inDialogsText() : QString()) : _text.original(AllTextSelection, Text::ExpandLinksNone); } HistoryMedia *HistoryMessage::getMedia() const { @@ -7220,7 +7378,7 @@ void HistoryMessage::setId(MsgId newId) { } } -void HistoryMessage::draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const { +void HistoryMessage::draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const { bool outbg = out() && !isPost(), bubble = drawBubble(), selected = (selection == FullSelection); int left = 0, width = 0, height = _height; @@ -7313,14 +7471,12 @@ void HistoryMessage::draw(Painter &p, const QRect &r, uint32 selection, uint64 m p.setPen(st::msgColor); p.setFont(st::msgFont); - uint16 selectedFrom = selected ? 0 : ((selection >> 16) & 0xFFFF); - uint16 selectedTo = selected ? 0 : (selection & 0xFFFF); - _text.draw(p, trect.x(), trect.y(), trect.width(), style::al_left, 0, -1, selectedFrom, selectedTo); + _text.draw(p, trect.x(), trect.y(), trect.width(), style::al_left, 0, -1, selection); if (_media && _media->isDisplayed()) { int32 top = height - marginBottom() - _media->height(); p.translate(left, top); - _media->draw(p, r.translated(-left, -top), selected, ms); + _media->draw(p, r.translated(-left, -top), toMediaSelection(selection), ms); p.translate(-left, -top); if (!_media->customInfoLayout()) { HistoryMessage::drawInfo(p, r.x() + r.width(), r.y() + r.height(), 2 * r.x() + r.width(), selected, InfoDisplayDefault); @@ -7331,7 +7487,7 @@ void HistoryMessage::draw(Painter &p, const QRect &r, uint32 selection, uint64 m } else { int32 top = marginTop(); p.translate(left, top); - _media->draw(p, r.translated(-left, -top), selected, ms); + _media->draw(p, r.translated(-left, -top), toMediaSelection(selection), ms); p.translate(-left, -top); } @@ -7533,22 +7689,18 @@ bool HistoryMessage::pointInTime(int32 right, int32 bottom, int x, int y, InfoDi return QRect(dateX, dateY, HistoryMessage::timeWidth(), st::msgDateFont->height).contains(x, y); } -void HistoryMessage::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const { - lnk.clear(); - state = HistoryDefaultCursorState; +HistoryTextState HistoryMessage::getState(int x, int y, HistoryStateRequest request) const { + HistoryTextState result; int left = 0, width = 0, height = _height; countPositionAndSize(left, width); - if (width < 1) return; + if (width < 1) return result; - if (const ReplyKeyboard *keyboard = inlineReplyKeyboard()) { + auto keyboard = inlineReplyKeyboard(); + if (keyboard) { int h = st::msgBotKbButton.margin + keyboard->naturalHeight(); height -= h; - int top = height + st::msgBotKbButton.margin - marginBottom(); - if (x >= left && x < left + width && y >= top && y < _height - marginBottom()) { - return keyboard->getState(lnk, x - left, y - top); - } } if (drawBubble()) { @@ -7562,12 +7714,12 @@ void HistoryMessage::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, i if (displayFromName()) { if (y >= trect.top() && y < trect.top() + st::msgNameFont->height) { if (x >= trect.left() && x < trect.left() + trect.width() && x < trect.left() + author()->nameText.maxWidth()) { - lnk = author()->openLink(); - return; + result.link = author()->openLink(); + return result; } if (via && !fwd && x >= trect.left() + author()->nameText.maxWidth() + st::msgServiceFont->spacew && x < trect.left() + author()->nameText.maxWidth() + st::msgServiceFont->spacew + via->_width) { - lnk = via->_lnk; - return; + result.link = via->_lnk; + return result; } } trect.setTop(trect.top() + st::msgNameFont->height); @@ -7575,22 +7727,29 @@ void HistoryMessage::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, i if (displayForwardedFrom()) { int32 fwdheight = ((fwd->_text.maxWidth() > trect.width()) ? 2 : 1) * st::semiboldFont->height; if (y >= trect.top() && y < trect.top() + fwdheight) { - bool inText = false; bool breakEverywhere = (fwd->_text.countHeight(trect.width()) > 2 * st::semiboldFont->height); - textstyleSet(&st::inFwdTextStyle); - fwd->_text.getState(lnk, inText, x - trect.left(), y - trect.top(), trect.width(), style::al_left, breakEverywhere); - textstyleRestore(); + auto textRequest = request.forText(); if (breakEverywhere) { - state = HistoryInForwardedCursorState; + textRequest.flags |= Text::StateRequest::Flag::BreakEverywhere; } - return; + textstyleSet(&st::inFwdTextStyle); + result = fwd->_text.getState(x - trect.left(), y - trect.top(), trect.width(), textRequest); + textstyleRestore(); + result.symbol = 0; + result.afterSymbol = false; + if (breakEverywhere) { + result.cursor = HistoryInForwardedCursorState; + } else { + result.cursor = HistoryDefaultCursorState; + } + return result; } trect.setTop(trect.top() + fwdheight); } if (via && !displayFromName() && !displayForwardedFrom()) { if (x >= trect.left() && y >= trect.top() && y < trect.top() + st::msgNameFont->height && x < trect.left() + via->_width) { - lnk = via->_lnk; - return; + result.link = via->_lnk; + return result; } trect.setTop(trect.top() + st::msgNameFont->height); } @@ -7598,84 +7757,59 @@ void HistoryMessage::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, i int32 h = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); if (y >= trect.top() && y < trect.top() + h) { if (reply->replyToMsg && y >= trect.top() + st::msgReplyPadding.top() && y < trect.top() + st::msgReplyPadding.top() + st::msgReplyBarSize.height() && x >= trect.left() && x < trect.left() + trect.width()) { - lnk = reply->replyToLink(); + result.link = reply->replyToLink(); } - return; + return result; } trect.setTop(trect.top() + h); } - bool inDate = false; - - if (_media && _media->isDisplayed()) { - if (!_media->customInfoLayout()) { - inDate = HistoryMessage::pointInTime(r.x() + r.width(), r.y() + r.height(), x, y, InfoDisplayDefault); - } - if (y >= r.bottom() - _media->height() && y < r.bottom()) { - _media->getState(lnk, state, x - r.left(), y - (r.bottom() - _media->height())); - if (inDate) state = HistoryInDateCursorState; - return; - } - trect.setBottom(trect.bottom() - _media->height()); - } else { + bool inDate = false, mediaDisplayed = _media && _media->isDisplayed(); + if (!mediaDisplayed || !_media->customInfoLayout()) { inDate = HistoryMessage::pointInTime(r.x() + r.width(), r.y() + r.height(), x, y, InfoDisplayDefault); } - textstyleSet(&((out() && !isPost()) ? st::outTextStyle : st::inTextStyle)); - bool inText = false; - _text.getState(lnk, inText, x - trect.x(), y - trect.y(), trect.width()); - textstyleRestore(); - + if (mediaDisplayed) { + trect.setBottom(trect.bottom() - _media->height()); + if (y >= r.bottom() - _media->height()) { + result = _media->getState(x - r.left(), y - (r.bottom() - _media->height()), request); + result.symbol += _text.length(); + } + } + if (!mediaDisplayed || (y < r.bottom() - _media->height())) { + textstyleSet(&((out() && !isPost()) ? st::outTextStyle : st::inTextStyle)); + result = _text.getState(x - trect.x(), y - trect.y(), trect.width(), request.forText()); + textstyleRestore(); + } if (inDate) { - state = HistoryInDateCursorState; - } else if (inText) { - state = HistoryInTextCursorState; - } else { - state = HistoryDefaultCursorState; + result.cursor = HistoryInDateCursorState; } } else { - _media->getState(lnk, state, x - left, y - marginTop()); + result = _media->getState(x - left, y - marginTop(), request); + result.symbol += _text.length(); } + + if (keyboard) { + int top = height + st::msgBotKbButton.margin - marginBottom(); + if (x >= left && x < left + width && y >= top && y < _height - marginBottom()) { + result.link = keyboard->getState(x - left, y - top); + return result; + } + } + + return result; } -void HistoryMessage::getSymbol(uint16 &symbol, bool &after, bool &upon, int x, int y) const { - symbol = 0; - after = false; - upon = false; - - if (drawBubble()) { - int left = 0, width = 0, height = _height; - countPositionAndSize(left, width); - if (width < 1) return; - - auto fwd = Get(); - auto via = Get(); - auto reply = Get(); - - int top = marginTop(); - QRect r(left, top, width, height - top - marginBottom()); - QRect trect(r.marginsAdded(-st::msgPadding)); - if (displayFromName()) { - trect.setTop(trect.top() + st::msgNameFont->height); - } else if (via && !fwd) { - trect.setTop(trect.top() + st::msgNameFont->height); - } - if (displayForwardedFrom()) { - int32 fwdheight = ((fwd->_text.maxWidth() > trect.width()) ? 2 : 1) * st::semiboldFont->height; - trect.setTop(trect.top() + fwdheight); - } - if (reply) { - int32 h = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); - trect.setTop(trect.top() + h); - } - if (_media && _media->isDisplayed()) { - trect.setBottom(trect.bottom() - _media->height()); - } - - textstyleSet(&((out() && !isPost()) ? st::outTextStyle : st::inTextStyle)); - _text.getSymbol(symbol, after, upon, x - trect.x(), y - trect.y(), trect.width()); - textstyleRestore(); +TextSelection HistoryMessage::adjustSelection(TextSelection selection, TextSelectType type) const { + if (!_media || selection.to <= _text.length()) { + return _text.adjustSelection(selection, type); } + auto mediaSelection = _media->adjustSelection(toMediaSelection(selection), type); + if (selection.from >= _text.length()) { + return fromMediaSelection(mediaSelection); + } + auto textSelection = _text.adjustSelection(selection, type); + return { textSelection.from, fromMediaSelection(mediaSelection).to }; } void HistoryMessage::drawInDialog(Painter &p, const QRect &r, bool act, const HistoryItem *&cacheFor, Text &cache) const { @@ -8015,14 +8149,12 @@ void HistoryService::countPositionAndSize(int32 &left, int32 &width) const { width = maxwidth - st::msgServiceMargin.left() - st::msgServiceMargin.left(); } -QString HistoryService::selectedText(uint32 selection) const { - uint16 selectedFrom = (selection == FullSelection) ? 0 : (selection >> 16) & 0xFFFF; - uint16 selectedTo = (selection == FullSelection) ? 0xFFFF : (selection & 0xFFFF); - return _text.original(selectedFrom, selectedTo); +QString HistoryService::selectedText(TextSelection selection) const { + return _text.original((selection == FullSelection) ? AllTextSelection : selection); } QString HistoryService::inDialogsText() const { - return _text.original(0, 0xFFFF, Text::ExpandLinksNone); + return _text.original(AllTextSelection, Text::ExpandLinksNone); } QString HistoryService::inReplyText() const { @@ -8037,7 +8169,7 @@ void HistoryService::setServiceText(const QString &text) { initDimensions(); } -void HistoryService::draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const { +void HistoryService::draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const { int left = 0, width = 0, height = _height - st::msgServiceMargin.top() - st::msgServiceMargin.bottom(); // two small margins countPositionAndSize(left, width); if (width < 1) return; @@ -8081,7 +8213,7 @@ void HistoryService::draw(Painter &p, const QRect &r, uint32 selection, uint64 m height -= st::msgServiceMargin.top() + _media->height(); int32 left = st::msgServiceMargin.left() + (width - _media->maxWidth()) / 2, top = st::msgServiceMargin.top() + height + st::msgServiceMargin.top(); p.translate(left, top); - _media->draw(p, r.translated(-left, -top), selection == FullSelection, ms); + _media->draw(p, r.translated(-left, -top), toMediaSelection(selection), ms); p.translate(-left, -top); } @@ -8098,9 +8230,7 @@ void HistoryService::draw(Painter &p, const QRect &r, uint32 selection, uint64 m p.setBrush(Qt::NoBrush); p.setPen(st::msgServiceColor); p.setFont(st::msgServiceFont); - uint16 selectedFrom = (selection == FullSelection) ? 0 : (selection >> 16) & 0xFFFF; - uint16 selectedTo = (selection == FullSelection) ? 0 : selection & 0xFFFF; - _text.draw(p, trect.x(), trect.y(), trect.width(), Qt::AlignCenter, 0, -1, selectedFrom, selectedTo); + _text.draw(p, trect.x(), trect.y(), trect.width(), Qt::AlignCenter, 0, -1, selection); textstyleRestore(); @@ -8162,13 +8292,12 @@ bool HistoryService::hasPoint(int x, int y) const { return QRect(left, st::msgServiceMargin.top(), width, height).contains(x, y); } -void HistoryService::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const { - lnk.clear(); - state = HistoryDefaultCursorState; +HistoryTextState HistoryService::getState(int x, int y, HistoryStateRequest request) const { + HistoryTextState result; int left = 0, width = 0, height = _height - st::msgServiceMargin.top() - st::msgServiceMargin.bottom(); // two small margins countPositionAndSize(left, width); - if (width < 1) return; + if (width < 1) return result; if (int dateh = displayedDateHeight()) { y -= dateh; @@ -8186,41 +8315,14 @@ void HistoryService::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, i QRect trect(QRect(left, st::msgServiceMargin.top(), width, height).marginsAdded(-st::msgServicePadding)); if (trect.contains(x, y)) { textstyleSet(&st::serviceTextStyle); - bool inText = false; - _text.getState(lnk, inText, x - trect.x(), y - trect.y(), trect.width(), Qt::AlignCenter); + auto textRequest = request.forText(); + textRequest.align = style::al_center; + result = _text.getState(x - trect.x(), y - trect.y(), trect.width(), textRequest); textstyleRestore(); - state = inText ? HistoryInTextCursorState : HistoryDefaultCursorState; } else if (_media) { - _media->getState(lnk, state, x - st::msgServiceMargin.left() - (width - _media->maxWidth()) / 2, y - st::msgServiceMargin.top() - height - st::msgServiceMargin.top()); + result = _media->getState(x - st::msgServiceMargin.left() - (width - _media->maxWidth()) / 2, y - st::msgServiceMargin.top() - height - st::msgServiceMargin.top(), request); } -} - -void HistoryService::getSymbol(uint16 &symbol, bool &after, bool &upon, int x, int y) const { - symbol = 0; - after = false; - upon = false; - - int left = 0, width = 0, height = _height - st::msgServiceMargin.top() - st::msgServiceMargin.bottom(); // two small margins - countPositionAndSize(left, width); - if (width < 1) return; - - if (int dateh = displayedDateHeight()) { - y -= dateh; - height -= dateh; - } - if (auto unreadbar = Get()) { - int unreadbarh = unreadbar->height(); - y -= unreadbarh; - height -= unreadbarh; - } - - if (_media) { - height -= st::msgServiceMargin.top() + _media->height(); - } - QRect trect(QRect(left, st::msgServiceMargin.top(), width, height).marginsAdded(-st::msgServicePadding)); - textstyleSet(&st::serviceTextStyle); - _text.getSymbol(symbol, after, upon, x - trect.x(), y - trect.y(), trect.width(), Qt::AlignCenter); - textstyleRestore(); + return result; } void HistoryService::drawInDialog(Painter &p, const QRect &r, bool act, const HistoryItem *&cacheFor, Text &cache) const { @@ -8268,13 +8370,12 @@ HistoryGroup::HistoryGroup(History *history, HistoryItem *newItem, const QDateTi , _lnk(new CommentsClickHandler(this)) { } -void HistoryGroup::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const { - lnk.clear(); - state = HistoryDefaultCursorState; +HistoryTextState HistoryGroup::getState(int x, int y, HistoryStateRequest request) const { + HistoryTextState result; int32 left = 0, width = 0, height = _height - st::msgServiceMargin.top() - st::msgServiceMargin.bottom(); // two small margins countPositionAndSize(left, width); - if (width < 1) return; + if (width < 1) return result; QRect trect(QRect(left, st::msgServiceMargin.top(), width, height).marginsAdded(-st::msgServicePadding)); if (width > _maxw) { @@ -8282,8 +8383,9 @@ void HistoryGroup::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int width = _maxw; } if (QRect(left, st::msgServiceMargin.top(), width, height).contains(x, y)) { - lnk = _lnk; + result.link = _lnk; } + return result; } void HistoryGroup::uniteWith(MsgId minId, MsgId maxId, int32 count) { @@ -8335,12 +8437,11 @@ HistoryCollapse::HistoryCollapse(History *history, MsgId wasMinId, const QDateTi , _wasMinId(wasMinId) { } -void HistoryCollapse::draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const { +void HistoryCollapse::draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const { } -void HistoryCollapse::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const { - lnk.clear(); - state = HistoryDefaultCursorState; +HistoryTextState HistoryCollapse::getState(int x, int y, HistoryStateRequest request) const { + return HistoryTextState(); } HistoryJoined::HistoryJoined(History *history, const QDateTime &inviteDate, UserData *inviter, MTPDmessage::Flags flags) diff --git a/Telegram/SourceFiles/history.h b/Telegram/SourceFiles/history.h index ce0d8aca7..81314ee64 100644 --- a/Telegram/SourceFiles/history.h +++ b/Telegram/SourceFiles/history.h @@ -255,7 +255,7 @@ public: int unreadCount() const { return _unreadCount; } - void setUnreadCount(int newUnreadCount, bool psUpdate = true); + void setUnreadCount(int newUnreadCount); bool mute() const { return _mute; } @@ -642,7 +642,7 @@ private: HistoryItem *findPrevItem(HistoryItem *item) const; void switchMode(); - void cleared(); + void cleared(bool leaveItems); bool _onlyImportant; @@ -729,7 +729,7 @@ protected: }; -class HistoryMessage; // dynamic_cast optimize +class HistoryMessage; enum HistoryCursorState { HistoryDefaultCursorState, @@ -738,6 +738,36 @@ enum HistoryCursorState { HistoryInForwardedCursorState, }; +struct HistoryTextState { + HistoryTextState() = default; + HistoryTextState(const Text::StateResult &state) + : cursor(state.uponSymbol ? HistoryInTextCursorState : HistoryDefaultCursorState) + , link(state.link) + , afterSymbol(state.afterSymbol) + , symbol(state.symbol) { + } + HistoryTextState &operator=(const Text::StateResult &state) { + cursor = state.uponSymbol ? HistoryInTextCursorState : HistoryDefaultCursorState; + link = state.link; + afterSymbol = state.afterSymbol; + symbol = state.symbol; + return *this; + } + HistoryCursorState cursor = HistoryDefaultCursorState; + ClickHandlerPtr link; + bool afterSymbol = false; + uint16 symbol = 0; +}; + +struct HistoryStateRequest { + Text::StateRequest::Flags flags = Text::StateRequest::Flag::LookupLink; + Text::StateRequest forText() const { + Text::StateRequest result; + result.flags = flags; + return result; + } +}; + enum InfoDisplayType { InfoDisplayDefault, InfoDisplayOverImage, @@ -936,7 +966,7 @@ public: int naturalHeight() const; void paint(Painter &p, const QRect &clip) const; - void getState(ClickHandlerPtr &lnk, int x, int y) const; + ClickHandlerPtr getState(int x, int y) const; void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active); void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed); @@ -1052,6 +1082,14 @@ private: }; + +namespace internal { + +TextSelection unshiftSelection(TextSelection selection, const Text &byText); +TextSelection shiftSelection(TextSelection selection, const Text &byText); + +} // namespace internal + class HistoryItem : public HistoryElem, public Composer, public ClickHandlerHost { public: @@ -1068,7 +1106,7 @@ public: } return resizeGetHeight_(width); } - virtual void draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const = 0; + virtual void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const = 0; virtual void dependencyItemRemoved(HistoryItem *dependency) { } @@ -1216,17 +1254,11 @@ public: virtual bool hasPoint(int x, int y) const { return false; } - virtual void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const { - lnk.clear(); - state = HistoryDefaultCursorState; - } - virtual void getSymbol(uint16 &symbol, bool &after, bool &upon, int x, int y) const { // from text - upon = hasPoint(x, y); - symbol = upon ? 0xFFFF : 0; - after = false; - } - virtual uint32 adjustSelection(uint16 from, uint16 to, TextSelectType type) const { - return (from << 16) | to; + + virtual HistoryTextState getState(int x, int y, HistoryStateRequest request) const = 0; + + virtual TextSelection adjustSelection(TextSelection selection, TextSelectType type) const { + return selection; } // ClickHandlerHost interface @@ -1251,7 +1283,7 @@ public: } virtual void previousItemChanged(); - virtual QString selectedText(uint32 selection) const { + virtual QString selectedText(TextSelection selection) const { return qsl("[-]"); } virtual QString inDialogsText() const { @@ -1514,6 +1546,13 @@ protected: return const_cast(static_cast(this)->inlineReplyKeyboard()); } + TextSelection toMediaSelection(TextSelection selection) const { + return internal::unshiftSelection(selection, _text); + } + TextSelection fromMediaSelection(TextSelection selection) const { + return internal::shiftSelection(selection, _text); + } + Text _text = { int(st::msgMinWidth) }; int32 _textWidth, _textHeight; @@ -1609,8 +1648,8 @@ public: HistoryMedia &operator=(const HistoryMedia &other) = delete; virtual HistoryMediaType type() const = 0; - virtual const QString inDialogsText() const = 0; - virtual const QString inHistoryText() const = 0; + virtual QString inDialogsText() const = 0; + virtual QString selectedText(TextSelection selection) const = 0; bool hasPoint(int x, int y) const { return (x >= 0 && y >= 0 && x < _width && y < _height); @@ -1624,8 +1663,8 @@ public: _width = qMin(width, _maxw); return _height; } - virtual void draw(Painter &p, const QRect &r, bool selected, uint64 ms) const = 0; - virtual void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const = 0; + virtual void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const = 0; + virtual HistoryTextState getState(int x, int y, HistoryStateRequest request) const = 0; // if we are in selecting items mode perhaps we want to // toggle selection instead of activating the pressed link @@ -1636,6 +1675,10 @@ public: return false; } + virtual TextSelection adjustSelection(TextSelection selection, TextSelectType type) const { + return selection; + } + // if we press and drag this link should we drag the item virtual bool dragItemByHandler(const ClickHandlerPtr &p) const = 0; @@ -1829,11 +1872,15 @@ public: void initDimensions() override; int resizeGetHeight(int width) override; - void draw(Painter &p, const QRect &r, bool selected, uint64 ms) const override; - void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const override; + void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; + HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; - const QString inDialogsText() const override; - const QString inHistoryText() const override; + TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override { + return _caption.adjustSelection(selection, type); + } + + QString inDialogsText() const override; + QString selectedText(TextSelection selection) const override; PhotoData *photo() const { return _data; @@ -1902,11 +1949,15 @@ public: void initDimensions() override; int resizeGetHeight(int width) override; - void draw(Painter &p, const QRect &r, bool selected, uint64 ms) const override; - void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const override; + void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; + HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; - const QString inDialogsText() const override; - const QString inHistoryText() const override; + TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override { + return _caption.adjustSelection(selection, type); + } + + QString inDialogsText() const override; + QString selectedText(TextSelection selection) const override; DocumentData *getDocument() override { return _data; @@ -2015,11 +2066,18 @@ public: void initDimensions() override; int resizeGetHeight(int width) override; - void draw(Painter &p, const QRect &r, bool selected, uint64 ms) const override; - void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const override; + void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; + HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; - const QString inDialogsText() const override; - const QString inHistoryText() const override; + TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override { + if (auto captioned = Get()) { + return captioned->_caption.adjustSelection(selection, type); + } + return selection; + } + + QString inDialogsText() const override; + QString selectedText(TextSelection selection) const override; bool uploading() const override { return _data->uploading(); @@ -2095,11 +2153,15 @@ public: void initDimensions() override; int resizeGetHeight(int width) override; - void draw(Painter &p, const QRect &r, bool selected, uint64 ms) const override; - void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const override; + void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; + HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; - const QString inDialogsText() const override; - const QString inHistoryText() const override; + TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override { + return _caption.adjustSelection(selection, type); + } + + QString inDialogsText() const override; + QString selectedText(TextSelection selection) const override; bool uploading() const override { return _data->uploading(); @@ -2183,8 +2245,8 @@ public: void initDimensions() override; int resizeGetHeight(int width) override; - void draw(Painter &p, const QRect &r, bool selected, uint64 ms) const override; - void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const override; + void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; + HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { return true; @@ -2196,8 +2258,8 @@ public: return true; } - const QString inDialogsText() const override; - const QString inHistoryText() const override; + QString inDialogsText() const override; + QString selectedText(TextSelection selection) const override; DocumentData *getDocument() override { return _data; @@ -2255,8 +2317,8 @@ public: void initDimensions() override; - void draw(Painter &p, const QRect &r, bool selected, uint64 ms) const override; - void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const override; + void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; + HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { return true; @@ -2265,8 +2327,8 @@ public: return true; } - const QString inDialogsText() const override; - const QString inHistoryText() const override; + QString inDialogsText() const override; + QString selectedText(TextSelection selection) const override; void attachToParent() override; void detachFromParent() override; @@ -2318,8 +2380,10 @@ public: void initDimensions() override; int resizeGetHeight(int width) override; - void draw(Painter &p, const QRect &r, bool selected, uint64 ms) const override; - void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const override; + void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; + HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; + + TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override; bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { return _attach && _attach->toggleSelectionByHandlerClick(p); @@ -2328,8 +2392,8 @@ public: return _attach && _attach->dragItemByHandler(p); } - const QString inDialogsText() const override; - const QString inHistoryText() const override; + QString inDialogsText() const override; + QString selectedText(TextSelection selection) const override; void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; @@ -2376,6 +2440,13 @@ public: ~HistoryWebPage(); private: + TextSelection toDescriptionSelection(TextSelection selection) const { + return internal::unshiftSelection(selection, _title); + } + TextSelection fromDescriptionSelection(TextSelection selection) const { + return internal::shiftSelection(selection, _title); + } + WebPageData *_data; ClickHandlerPtr _openl; HistoryMedia *_attach; @@ -2396,17 +2467,8 @@ void initImageLinkManager(); void reinitImageLinkManager(); void deinitImageLinkManager(); -struct LocationData { - LocationData(const LocationCoords &coords) : coords(coords), loading(false) { - } - - LocationCoords coords; - ImagePtr thumb; - bool loading; - - void load(); -}; - +struct LocationCoords; +struct LocationData; class LocationManager : public QObject { Q_OBJECT public: @@ -2449,8 +2511,10 @@ public: void initDimensions() override; int resizeGetHeight(int32 width) override; - void draw(Painter &p, const QRect &r, bool selected, uint64 ms) const override; - void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const override; + void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; + HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; + + TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override; bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { return p == _link; @@ -2459,8 +2523,8 @@ public: return p == _link; } - const QString inDialogsText() const override; - const QString inHistoryText() const override; + QString inDialogsText() const override; + QString selectedText(TextSelection selection) const override; bool needsBubble() const override { if (!_title.isEmpty() || !_description.isEmpty()) { @@ -2476,6 +2540,13 @@ public: } private: + TextSelection toDescriptionSelection(TextSelection selection) const { + return internal::unshiftSelection(selection, _title); + } + TextSelection fromDescriptionSelection(TextSelection selection) const { + return internal::shiftSelection(selection, _title); + } + LocationData *_data; Text _title, _description; ClickHandlerPtr _link; @@ -2547,7 +2618,7 @@ public: void drawInfo(Painter &p, int32 right, int32 bottom, int32 width, bool selected, InfoDisplayType type) const override; void setViewsCount(int32 count) override; void setId(MsgId newId) override; - void draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const override; + void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; void dependencyItemRemoved(HistoryItem *dependency) override; @@ -2556,12 +2627,9 @@ public: bool hasPoint(int x, int y) const override; bool pointInTime(int32 right, int32 bottom, int x, int y, InfoDisplayType type) const override; - void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const override; + HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; - void getSymbol(uint16 &symbol, bool &after, bool &upon, int x, int y) const override; - uint32 adjustSelection(uint16 from, uint16 to, TextSelectType type) const override { - return _text.adjustSelection(from, to, type); - } + TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override; // ClickHandlerHost interface void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override { @@ -2569,7 +2637,7 @@ public: HistoryItem::clickHandlerActiveChanged(p, active); } void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override { - if (_media) _media->clickHandlerActiveChanged(p, pressed); + if (_media) _media->clickHandlerPressedChanged(p, pressed); HistoryItem::clickHandlerPressedChanged(p, pressed); } @@ -2582,7 +2650,7 @@ public: int32 addToOverview(AddToOverviewMethod method) override; void eraseFromOverview(); - QString selectedText(uint32 selection) const override; + QString selectedText(TextSelection selection) const override; QString inDialogsText() const override; HistoryMedia *getMedia() const override; void setText(const QString &text, const EntitiesInText &entities) override; @@ -2772,12 +2840,12 @@ public: void countPositionAndSize(int32 &left, int32 &width) const; - void draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const override; + void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; bool hasPoint(int x, int y) const override; - void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const override; - void getSymbol(uint16 &symbol, bool &after, bool &upon, int x, int y) const override; - uint32 adjustSelection(uint16 from, uint16 to, TextSelectType type) const override { - return _text.adjustSelection(from, to, type); + HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; + + TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override { + return _text.adjustSelection(selection, type); } void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override { @@ -2798,7 +2866,7 @@ public: bool serviceMsg() const override { return true; } - QString selectedText(uint32 selection) const override; + QString selectedText(TextSelection selection) const override; QString inDialogsText() const override; QString inReplyText() const override; @@ -2833,16 +2901,12 @@ public: return _create(history, newItem, date); } - void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const; - void getSymbol(uint16 &symbol, bool &after, bool &upon, int x, int y) const { - symbol = 0xFFFF; - after = false; - upon = false; - } - QString selectedText(uint32 selection) const { + HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; + + QString selectedText(TextSelection selection) const override { return QString(); } - HistoryItemType type() const { + HistoryItemType type() const override { return HistoryItemGroup; } void uniteWith(MsgId minId, MsgId maxId, int32 count); @@ -2886,17 +2950,13 @@ public: return _create(history, wasMinId, date); } - void draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const; - void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const; - void getSymbol(uint16 &symbol, bool &after, bool &upon, int x, int y) const { - symbol = 0xFFFF; - after = false; - upon = false; - } - QString selectedText(uint32 selection) const { + void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; + HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; + + QString selectedText(TextSelection selection) const override { return QString(); } - HistoryItemType type() const { + HistoryItemType type() const override { return HistoryItemCollapse; } MsgId wasMinId() const { diff --git a/Telegram/SourceFiles/history/history_common.h b/Telegram/SourceFiles/history/history_common.h new file mode 100644 index 000000000..f5e807d5b --- /dev/null +++ b/Telegram/SourceFiles/history/history_common.h @@ -0,0 +1,28 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +enum DragState { + DragStateNone = 0x00, + DragStateFiles = 0x01, + DragStatePhotoFiles = 0x02, + DragStateImage = 0x03, +}; diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index bebe9f51b..59c6ae8ec 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -30,12 +30,15 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "lang.h" #include "application.h" #include "mainwidget.h" -#include "window.h" +#include "mainwindow.h" #include "passcodewidget.h" -#include "window.h" +#include "mainwindow.h" #include "fileuploader.h" #include "audio.h" #include "localstorage.h" +#include "apiwrap.h" +#include "window/top_bar_widget.h" +#include "playerwidget.h" // flick scroll taken from http://qt-project.org/doc/qt-4.8/demos-embedded-anomaly-src-flickcharm-cpp.html @@ -249,11 +252,13 @@ void HistoryInner::paintEvent(QPaintEvent *e) { p.save(); p.translate(0, y); if (r.y() < y + item->height()) while (y < drawToY) { - uint32 sel = 0; + TextSelection sel; if (y >= selfromy && y < seltoy) { - sel = (_dragSelecting && !item->serviceMsg() && item->id > 0) ? FullSelection : 0; + if (_dragSelecting && !item->serviceMsg() && item->id > 0) { + sel = FullSelection; + } } else if (hasSel) { - SelectedItems::const_iterator i = _selected.constFind(item); + auto i = _selected.constFind(item); if (i != selEnd) { sel = i.value(); } @@ -294,11 +299,13 @@ void HistoryInner::paintEvent(QPaintEvent *e) { while (y < drawToY) { int32 h = item->height(); if (historyRect.y() < y + h && hdrawtop < y + h) { - uint32 sel = 0; + TextSelection sel; if (y >= selfromy && y < seltoy) { - sel = (_dragSelecting && !item->serviceMsg() && item->id > 0) ? FullSelection : 0; + if (_dragSelecting && !item->serviceMsg() && item->id > 0) { + sel = FullSelection; + } } else if (hasSel) { - SelectedItems::const_iterator i = _selected.constFind(item); + auto i = _selected.constFind(item); if (i != selEnd) { sel = i.value(); } @@ -585,19 +592,20 @@ void HistoryInner::dragActionStart(const QPoint &screenPos, Qt::MouseButton butt } } if (_dragAction == NoDrag && _dragItem) { - bool afterDragSymbol, uponSymbol; - uint16 symbol; + HistoryTextState dragState; if (_trippleClickTimer.isActive() && (screenPos - _trippleClickPoint).manhattanLength() < QApplication::startDragDistance()) { - _dragItem->getSymbol(symbol, afterDragSymbol, uponSymbol, _dragStartPos.x(), _dragStartPos.y()); - if (uponSymbol) { - uint32 selStatus = (symbol << 16) | symbol; + HistoryStateRequest request; + request.flags = Text::StateRequest::Flag::LookupSymbol; + dragState = _dragItem->getState(_dragStartPos.x(), _dragStartPos.y(), request); + if (dragState.cursor == HistoryInTextCursorState) { + TextSelection selStatus = { dragState.symbol, dragState.symbol }; if (selStatus != FullSelection && (_selected.isEmpty() || _selected.cbegin().value() != FullSelection)) { if (!_selected.isEmpty()) { repaintItem(_selected.cbegin().key()); _selected.clear(); } _selected.insert(_dragItem, selStatus); - _dragSymbol = symbol; + _dragSymbol = dragState.symbol; _dragAction = Selecting; _dragSelType = TextSelectParagraphs; dragActionUpdate(_dragPos); @@ -605,12 +613,14 @@ void HistoryInner::dragActionStart(const QPoint &screenPos, Qt::MouseButton butt } } } else if (App::pressedItem()) { - _dragItem->getSymbol(symbol, afterDragSymbol, uponSymbol, _dragStartPos.x(), _dragStartPos.y()); + HistoryStateRequest request; + request.flags = Text::StateRequest::Flag::LookupSymbol; + dragState = _dragItem->getState(_dragStartPos.x(), _dragStartPos.y(), request); } if (_dragSelType != TextSelectParagraphs) { if (App::pressedItem()) { - _dragSymbol = symbol; - bool uponSelected = uponSymbol; + _dragSymbol = dragState.symbol; + bool uponSelected = (dragState.cursor == HistoryInTextCursorState); if (uponSelected) { if (_selected.isEmpty() || _selected.cbegin().value() == FullSelection || @@ -618,7 +628,7 @@ void HistoryInner::dragActionStart(const QPoint &screenPos, Qt::MouseButton butt ) { uponSelected = false; } else { - uint16 selFrom = (_selected.cbegin().value() >> 16) & 0xFFFF, selTo = _selected.cbegin().value() & 0xFFFF; + uint16 selFrom = _selected.cbegin().value().from, selTo = _selected.cbegin().value().to; if (_dragSymbol < selFrom || _dragSymbol >= selTo) { uponSelected = false; } @@ -630,8 +640,8 @@ void HistoryInner::dragActionStart(const QPoint &screenPos, Qt::MouseButton butt if (dynamic_cast(App::pressedItem()->getMedia()) || _dragCursorState == HistoryInDateCursorState) { _dragAction = PrepareDrag; // start sticker drag or by-date drag } else { - if (afterDragSymbol) ++_dragSymbol; - uint32 selStatus = (_dragSymbol << 16) | _dragSymbol; + if (dragState.afterSymbol) ++_dragSymbol; + TextSelection selStatus = { _dragSymbol, _dragSymbol }; if (selStatus != FullSelection && (_selected.isEmpty() || _selected.cbegin().value() != FullSelection)) { if (!_selected.isEmpty()) { repaintItem(_selected.cbegin().key()); @@ -672,12 +682,13 @@ void HistoryInner::onDragExec() { bool uponSelected = false; if (_dragItem) { - bool afterDragSymbol; - uint16 symbol; if (!_selected.isEmpty() && _selected.cbegin().value() == FullSelection) { uponSelected = _selected.contains(_dragItem); } else { - _dragItem->getSymbol(symbol, afterDragSymbol, uponSelected, _dragStartPos.x(), _dragStartPos.y()); + HistoryStateRequest request; + request.flags |= Text::StateRequest::Flag::LookupSymbol; + auto dragState = _dragItem->getState(_dragStartPos.x(), _dragStartPos.y(), request); + uponSelected = (dragState.cursor == HistoryInTextCursorState); if (uponSelected) { if (_selected.isEmpty() || _selected.cbegin().value() == FullSelection || @@ -685,8 +696,8 @@ void HistoryInner::onDragExec() { ) { uponSelected = false; } else { - uint16 selFrom = (_selected.cbegin().value() >> 16) & 0xFFFF, selTo = _selected.cbegin().value() & 0xFFFF; - if (symbol < selFrom || symbol >= selTo) { + uint16 selFrom = _selected.cbegin().value().from, selTo = _selected.cbegin().value().to; + if (dragState.symbol < selFrom || dragState.symbol >= selTo) { uponSelected = false; } } @@ -838,8 +849,8 @@ void HistoryInner::dragActionFinish(const QPoint &screenPos, Qt::MouseButton but applyDragSelection(); _dragSelFrom = _dragSelTo = 0; } else if (!_selected.isEmpty() && !_dragWasInactive) { - uint32 sel = _selected.cbegin().value(); - if (sel != FullSelection && (sel & 0xFFFF) == ((sel >> 16) & 0xFFFF)) { + auto sel = _selected.cbegin().value(); + if (sel != FullSelection && sel.from == sel.to) { _selected.clear(); if (App::wnd()) App::wnd()->setInnerFocus(); } @@ -864,15 +875,15 @@ void HistoryInner::mouseDoubleClickEvent(QMouseEvent *e) { dragActionStart(e->globalPos(), e->button()); if (((_dragAction == Selecting && !_selected.isEmpty() && _selected.cbegin().value() != FullSelection) || (_dragAction == NoDrag && (_selected.isEmpty() || _selected.cbegin().value() != FullSelection))) && _dragSelType == TextSelectLetters && _dragItem) { - bool afterDragSymbol, uponSelected; - uint16 symbol; - _dragItem->getSymbol(symbol, afterDragSymbol, uponSelected, _dragStartPos.x(), _dragStartPos.y()); - if (uponSelected) { - _dragSymbol = symbol; + HistoryStateRequest request; + request.flags |= Text::StateRequest::Flag::LookupSymbol; + auto dragState = _dragItem->getState(_dragStartPos.x(), _dragStartPos.y(), request); + if (dragState.cursor == HistoryInTextCursorState) { + _dragSymbol = dragState.symbol; _dragSelType = TextSelectWords; if (_dragAction == NoDrag) { _dragAction = Selecting; - uint32 selStatus = (symbol << 16) | symbol; + TextSelection selStatus = { dragState.symbol, dragState.symbol }; if (!_selected.isEmpty()) { repaintItem(_selected.cbegin().key()); _selected.clear(); @@ -912,13 +923,14 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { isUponSelected = -2; } } else { - uint16 symbol, selFrom = (_selected.cbegin().value() >> 16) & 0xFFFF, selTo = _selected.cbegin().value() & 0xFFFF; + uint16 selFrom = _selected.cbegin().value().from, selTo = _selected.cbegin().value().to; hasSelected = (selTo > selFrom) ? 1 : 0; if (App::mousedItem() && App::mousedItem() == App::hoveredItem()) { QPoint mousePos(mapMouseToItem(mapFromGlobal(_dragPos), App::mousedItem())); - bool afterDragSymbol, uponSymbol; - App::mousedItem()->getSymbol(symbol, afterDragSymbol, uponSymbol, mousePos.x(), mousePos.y()); - if (uponSymbol && symbol >= selFrom && symbol < selTo) { + HistoryStateRequest request; + request.flags |= Text::StateRequest::Flag::LookupSymbol; + auto dragState = App::mousedItem()->getState(mousePos.x(), mousePos.y(), request); + if (dragState.cursor == HistoryInTextCursorState && dragState.symbol >= selFrom && dragState.symbol < selTo) { isUponSelected = 1; } } @@ -1054,7 +1066,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { } } QString contextMenuText = item->selectedText(FullSelection); - if (!contextMenuText.isEmpty() && (!msg || !msg->getMedia() || (msg->getMedia()->type() != MediaTypeSticker && msg->getMedia()->type() != MediaTypeGif))) { + if (!contextMenuText.isEmpty() && msg && !msg->getMedia()) { _menu->addAction(lang(lng_context_copy_text), this, SLOT(copyContextText()))->setEnabled(true); } } @@ -1489,7 +1501,7 @@ void HistoryInner::updateSize() { void HistoryInner::enterEvent(QEvent *e) { dragActionUpdate(QCursor::pos()); - return QWidget::enterEvent(e); +// return QWidget::enterEvent(e); } void HistoryInner::leaveEvent(QEvent *e) { @@ -1522,6 +1534,7 @@ void HistoryInner::adjustCurrent(int32 y) const { } void HistoryInner::adjustCurrent(int32 y, History *history) const { + t_assert(!history->isEmpty()); _curHistory = history; if (_curBlock >= history->blocks.size()) { _curBlock = history->blocks.size() - 1; @@ -1681,57 +1694,13 @@ void HistoryInner::onUpdateSelected() { dragActionCancel(); } - ClickHandlerPtr lnk; + HistoryTextState dragState; ClickHandlerHost *lnkhost = nullptr; - HistoryCursorState cursorState = HistoryDefaultCursorState; + bool selectingText = (item == _dragItem && item == App::hoveredItem() && !_selected.isEmpty() && _selected.cbegin().value() != FullSelection); if (point.y() < _historyOffset) { if (_botAbout && !_botAbout->info->text.isEmpty() && _botAbout->height > 0) { - bool inText = false; - _botAbout->info->text.getState(lnk, inText, point.x() - _botAbout->rect.left() - st::msgPadding.left(), point.y() - _botAbout->rect.top() - st::msgPadding.top() - st::botDescSkip - st::msgNameFont->height, _botAbout->width); + dragState = _botAbout->info->text.getState(point.x() - _botAbout->rect.left() - st::msgPadding.left(), point.y() - _botAbout->rect.top() - st::msgPadding.top() - st::botDescSkip - st::msgNameFont->height, _botAbout->width); lnkhost = _botAbout.get(); - cursorState = inText ? HistoryInTextCursorState : HistoryDefaultCursorState; - } - } else if (item) { - item->getState(lnk, cursorState, m.x(), m.y()); - lnkhost = item; - if (!lnk && m.x() >= st::msgMargin.left() && m.x() < st::msgMargin.left() + st::msgPhotoSize) { - if (HistoryMessage *msg = item->toHistoryMessage()) { - if (msg->hasFromPhoto()) { - enumerateUserpics([&lnk, &lnkhost, &point](HistoryMessage *message, int userpicTop) -> bool { - // stop enumeration if the userpic is above our point - if (userpicTop + st::msgPhotoSize <= point.y()) { - return false; - } - - // stop enumeration if we've found a userpic under the cursor - if (point.y() >= userpicTop && point.y() < userpicTop + st::msgPhotoSize) { - lnk = message->from()->openLink(); - lnkhost = message; - return false; - } - return true; - }); - } - } - } - } - bool lnkChanged = ClickHandler::setActive(lnk, lnkhost); - if (lnkChanged || cursorState != _dragCursorState) { - PopupTooltip::Hide(); - } - if (lnk || cursorState == HistoryInDateCursorState || cursorState == HistoryInForwardedCursorState) { - PopupTooltip::Show(1000, this); - } - - Qt::CursorShape cur = style::cur_default; - if (_dragAction == NoDrag) { - _dragCursorState = cursorState; - if (lnk) { - cur = style::cur_pointer; - } else if (_dragCursorState == HistoryInTextCursorState && (_selected.isEmpty() || _selected.cbegin().value() != FullSelection)) { - cur = style::cur_text; - } else if (_dragCursorState == HistoryInDateCursorState) { -// cur = style::cur_cross; } } else if (item) { if (item != _dragItem || (m - _dragStartPos).manhattanLength() >= QApplication::startDragDistance()) { @@ -1742,19 +1711,68 @@ void HistoryInner::onUpdateSelected() { _dragAction = Selecting; } } + + HistoryStateRequest request; if (_dragAction == Selecting) { - bool canSelectMany = (_history != 0); - if (item == _dragItem && item == App::hoveredItem() && !_selected.isEmpty() && _selected.cbegin().value() != FullSelection) { - bool afterSymbol, uponSymbol; - uint16 second; - _dragItem->getSymbol(second, afterSymbol, uponSymbol, m.x(), m.y()); - if (afterSymbol && _dragSelType == TextSelectLetters) ++second; - uint32 selState = _dragItem->adjustSelection(qMin(second, _dragSymbol), qMax(second, _dragSymbol), _dragSelType); + request.flags |= Text::StateRequest::Flag::LookupSymbol; + } else { + selectingText = false; + } + dragState = item->getState(m.x(), m.y(), request); + lnkhost = item; + if (!dragState.link && m.x() >= st::msgMargin.left() && m.x() < st::msgMargin.left() + st::msgPhotoSize) { + if (HistoryMessage *msg = item->toHistoryMessage()) { + if (msg->hasFromPhoto()) { + enumerateUserpics([&dragState, &lnkhost, &point](HistoryMessage *message, int userpicTop) -> bool { + // stop enumeration if the userpic is above our point + if (userpicTop + st::msgPhotoSize <= point.y()) { + return false; + } + + // stop enumeration if we've found a userpic under the cursor + if (point.y() >= userpicTop && point.y() < userpicTop + st::msgPhotoSize) { + dragState.link = message->from()->openLink(); + lnkhost = message; + return false; + } + return true; + }); + } + } + } + } + bool lnkChanged = ClickHandler::setActive(dragState.link, lnkhost); + if (lnkChanged || dragState.cursor != _dragCursorState) { + PopupTooltip::Hide(); + } + if (dragState.link || dragState.cursor == HistoryInDateCursorState || dragState.cursor == HistoryInForwardedCursorState) { + PopupTooltip::Show(1000, this); + } + + Qt::CursorShape cur = style::cur_default; + if (_dragAction == NoDrag) { + _dragCursorState = dragState.cursor; + if (dragState.link) { + cur = style::cur_pointer; + } else if (_dragCursorState == HistoryInTextCursorState && (_selected.isEmpty() || _selected.cbegin().value() != FullSelection)) { + cur = style::cur_text; + } else if (_dragCursorState == HistoryInDateCursorState) { +// cur = style::cur_cross; + } + } else if (item) { + if (_dragAction == Selecting) { + bool canSelectMany = (_history != nullptr); + if (selectingText) { + uint16 second = dragState.symbol; + if (dragState.afterSymbol && _dragSelType == TextSelectLetters) { + ++second; + } + auto selState = _dragItem->adjustSelection({ qMin(second, _dragSymbol), qMax(second, _dragSymbol) }, _dragSelType); if (_selected[_dragItem] != selState) { _selected[_dragItem] = selState; repaintItem(_dragItem); } - if (!_wasSelectedText && (selState == FullSelection || (selState & 0xFFFF) != ((selState >> 16) & 0xFFFF))) { + if (!_wasSelectedText && (selState == FullSelection || selState.from != selState.to)) { _wasSelectedText = true; setFocus(); } @@ -2005,7 +2023,7 @@ QString HistoryInner::tooltipText() const { } else if (_dragCursorState == HistoryInForwardedCursorState && _dragAction == NoDrag) { if (App::hoveredItem()) { if (HistoryMessageForwarded *fwd = App::hoveredItem()->Get()) { - return fwd->_text.original(0, 0xFFFF, Text::ExpandLinksNone); + return fwd->_text.original(AllTextSelection, Text::ExpandLinksNone); } } } else if (ClickHandlerPtr lnk = ClickHandler::getActive()) { @@ -2242,8 +2260,7 @@ void BotKeyboard::enterEvent(QEvent *e) { } void BotKeyboard::leaveEvent(QEvent *e) { - _lastMousePos = QPoint(-1, -1); - updateSelected(); + clearSelection(); } void BotKeyboard::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { @@ -2257,44 +2274,43 @@ void BotKeyboard::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pres } bool BotKeyboard::updateMarkup(HistoryItem *to, bool force) { - if (to && to->definesReplyKeyboard()) { - if (_wasForMsgId == FullMsgId(to->channelId(), to->id) && !force) { - return false; + if (!to || !to->definesReplyKeyboard()) { + if (_wasForMsgId.msg) { + _maximizeSize = _singleUse = _forceReply = false; + _wasForMsgId = FullMsgId(); + _impl = nullptr; + return true; } - - _wasForMsgId = FullMsgId(to->channelId(), to->id); - clearSelection(); - - auto markupFlags = to->replyKeyboardFlags(); - _forceReply = markupFlags & MTPDreplyKeyboardMarkup_ClientFlag::f_force_reply; - _maximizeSize = !(markupFlags & MTPDreplyKeyboardMarkup::Flag::f_resize); - _singleUse = _forceReply || (markupFlags & MTPDreplyKeyboardMarkup::Flag::f_single_use); - - _impl = nullptr; - if (auto markup = to->Get()) { - if (!markup->rows.isEmpty()) { - _impl.reset(new ReplyKeyboard(to, std_::make_unique