diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp index be0499359..7f4cfbfe2 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp @@ -1195,7 +1195,10 @@ base::unique_qptr Members::Controller::createRowContextMenu( const auto admin = IsGroupCallAdmin(_peer, participantPeer); const auto session = &_peer->session(); const auto getCurrentWindow = [=]() -> Window::SessionController* { - if (const auto window = Core::App().activeWindow()) { + if (const auto window = Core::App().separateWindowForPeer( + participantPeer)) { + return window->sessionController(); + } else if (const auto window = Core::App().activeWindow()) { if (const auto controller = window->sessionController()) { if (&controller->session() == session) { return controller; @@ -1221,7 +1224,7 @@ base::unique_qptr Members::Controller::createRowContextMenu( ? st::groupCallPopupMenuWithVolume : st::groupCallPopupMenu)); const auto weakMenu = Ui::MakeWeak(result.get()); - const auto performOnMainWindow = [=](auto callback) { + const auto withActiveWindow = [=](auto callback) { if (const auto window = getWindow()) { if (const auto menu = weakMenu.data()) { menu->discardParentReActivate(); @@ -1236,12 +1239,12 @@ base::unique_qptr Members::Controller::createRowContextMenu( } }; const auto showProfile = [=] { - performOnMainWindow([=](not_null window) { + withActiveWindow([=](not_null window) { window->showPeerInfo(participantPeer); }); }; const auto showHistory = [=] { - performOnMainWindow([=](not_null window) { + withActiveWindow([=](not_null window) { window->showPeerHistory( participantPeer, Window::SectionShow::Way::Forward); diff --git a/Telegram/SourceFiles/core/application.cpp b/Telegram/SourceFiles/core/application.cpp index 225452dc3..b1e2278b9 100644 --- a/Telegram/SourceFiles/core/application.cpp +++ b/Telegram/SourceFiles/core/application.cpp @@ -123,6 +123,7 @@ Application *Application::Instance = nullptr; struct Application::Private { base::Timer quitTimer; UiIntegration uiIntegration; + Settings settings; }; Application::Application(not_null launcher) @@ -170,6 +171,7 @@ Application::~Application() { // Depend on activeWindow() for now :( Shortcuts::Finish(); + _separateWindows.clear(); _window = nullptr; _mediaView = nullptr; _notifications->clearAllFast(); @@ -264,6 +266,7 @@ void Application::run() { QMimeDatabase().mimeTypeForName(qsl("text/plain")); _window = std::make_unique(); + _lastActiveWindow = _window.get(); _domain->activeChanges( ) | rpl::start_with_next([=](not_null account) { @@ -371,8 +374,8 @@ void Application::startSettingsAndBackground() { } void Application::checkSystemDarkMode() { - const auto maybeDarkMode = _settings.systemDarkMode(); - const auto darkModeEnabled = _settings.systemDarkModeEnabled(); + const auto maybeDarkMode = settings().systemDarkMode(); + const auto darkModeEnabled = settings().systemDarkModeEnabled(); const auto needToSwitch = darkModeEnabled && maybeDarkMode && (*maybeDarkMode != Window::Theme::IsNightMode()); @@ -384,11 +387,11 @@ void Application::checkSystemDarkMode() { void Application::startSystemDarkModeViewer() { if (Window::Theme::Background()->editingTheme()) { - _settings.setSystemDarkModeEnabled(false); + settings().setSystemDarkModeEnabled(false); } rpl::merge( - _settings.systemDarkModeChanges() | rpl::to_empty, - _settings.systemDarkModeEnabledChanges() | rpl::to_empty + settings().systemDarkModeChanges() | rpl::to_empty, + settings().systemDarkModeEnabledChanges() | rpl::to_empty ) | rpl::start_with_next([=] { checkSystemDarkMode(); }, _lifetime); @@ -397,7 +400,7 @@ void Application::startSystemDarkModeViewer() { auto Application::prepareEmojiSourceImages() -> std::shared_ptr { const auto &images = Ui::Emoji::SourceImages(); - if (_settings.largeEmoji()) { + if (settings().largeEmoji()) { return images; } Ui::Emoji::ClearSourceImages(images); @@ -471,6 +474,10 @@ bool Application::eventFilter(QObject *object, QEvent *e) { return QObject::eventFilter(object, e); } +Settings &Application::settings() { + return _private->settings; +} + void Application::saveSettingsDelayed(crl::time delay) { if (_saveSettingsTimer) { _saveSettingsTimer->callOnce(delay); @@ -508,18 +515,17 @@ void Application::constructFallbackProductionConfig( void Application::setCurrentProxy( const MTP::ProxyData &proxy, MTP::ProxyData::Settings settings) { + auto &my = _private->settings.proxy(); const auto current = [&] { - return _settings.proxy().isEnabled() - ? _settings.proxy().selected() - : MTP::ProxyData(); + return my.isEnabled() ? my.selected() : MTP::ProxyData(); }; const auto was = current(); - _settings.proxy().setSelected(proxy); - _settings.proxy().setSettings(settings); + my.setSelected(proxy); + my.setSettings(settings); const auto now = current(); refreshGlobalProxy(); _proxyChanges.fire({ was, now }); - _settings.proxy().connectionTypeChangesNotify(); + my.connectionTypeChangesNotify(); } auto Application::proxyChanges() const -> rpl::producer { @@ -527,10 +533,10 @@ auto Application::proxyChanges() const -> rpl::producer { } void Application::badMtprotoConfigurationError() { - if (_settings.proxy().isEnabled() && !_badProxyDisableBox) { + if (settings().proxy().isEnabled() && !_badProxyDisableBox) { const auto disableCallback = [=] { setCurrentProxy( - _settings.proxy().selected(), + settings().proxy().selected(), MTP::ProxyData::Settings::System); }; _badProxyDisableBox = Ui::show(Box( @@ -542,7 +548,7 @@ void Application::badMtprotoConfigurationError() { void Application::startLocalStorage() { Local::start(); _saveSettingsTimer.emplace([=] { saveSettings(); }); - _settings.saveDelayedRequests() | rpl::start_with_next([=] { + settings().saveDelayedRequests() | rpl::start_with_next([=] { saveSettingsDelayed(); }, _lifetime); } @@ -550,12 +556,12 @@ void Application::startLocalStorage() { void Application::startEmojiImageLoader() { _emojiImageLoader.with([ source = prepareEmojiSourceImages(), - large = _settings.largeEmoji() + large = settings().largeEmoji() ](Stickers::EmojiImageLoader &loader) mutable { loader.init(std::move(source), large); }); - _settings.largeEmojiChanges( + settings().largeEmojiChanges( ) | rpl::start_with_next([=](bool large) { if (large) { _clearEmojiImageLoaderTimer.cancel(); @@ -913,9 +919,11 @@ void Application::checkAutoLock(crl::time lastNonIdleTime) { checkLocalTime(); const auto now = crl::now(); - const auto shouldLockInMs = _settings.autoLock() * 1000LL; + const auto shouldLockInMs = settings().autoLock() * 1000LL; const auto checkTimeMs = now - lastNonIdleTime; - if (checkTimeMs >= shouldLockInMs || (_shouldLockAt > 0 && now > _shouldLockAt + kAutoLockTimeoutLateMs)) { + if (checkTimeMs >= shouldLockInMs + || (_shouldLockAt > 0 + && now > _shouldLockAt + kAutoLockTimeoutLateMs)) { _shouldLockAt = 0; _autoLockTimer.cancel(); lockByPasscode(); @@ -961,10 +969,39 @@ void Application::saveCurrentDraftsToHistories() { } } -Window::Controller *Application::activeWindow() const { +Window::Controller *Application::mainWindow() const { return _window.get(); } +Window::Controller *Application::separateWindowForPeer( + not_null peer) const { + for (const auto &[history, window] : _separateWindows) { + if (history->peer == peer) { + return window.get(); + } + } + return nullptr; +} + +Window::Controller *Application::ensureSeparateWindowForPeer( + not_null peer) { + if (const auto existing = separateWindowForPeer(peer)) { + return existing; + } + const auto result = _separateWindows.emplace( + peer->owner().history(peer), + std::make_unique()).first->second.get(); + result->showAccount(&peer->account()); + result->sessionController()->showPeerHistory(peer); + result->widget()->show(); + result->finishFirstShow(); + return result; +} + +Window::Controller *Application::activeWindow() const { + return _lastActiveWindow; +} + bool Application::closeActiveWindow() { if (hideMediaView()) { return true; diff --git a/Telegram/SourceFiles/core/application.h b/Telegram/SourceFiles/core/application.h index 219579b27..da8f1cf96 100644 --- a/Telegram/SourceFiles/core/application.h +++ b/Telegram/SourceFiles/core/application.h @@ -133,7 +133,12 @@ public: // Windows interface. bool hasActiveWindow(not_null session) const; void saveCurrentDraftsToHistories(); + [[nodiscard]] Window::Controller *mainWindow() const; [[nodiscard]] Window::Controller *activeWindow() const; + [[nodiscard]] Window::Controller *separateWindowForPeer( + not_null peer) const; + Window::Controller *ensureSeparateWindowForPeer( + not_null peer); bool closeActiveWindow(); bool minimizeActiveWindow(); [[nodiscard]] QWidget *getFileDialogParent(); @@ -147,9 +152,7 @@ public: [[nodiscard]] QPoint getPointForCallPanelCenter() const; void startSettingsAndBackground(); - [[nodiscard]] Settings &settings() { - return _settings; - } + [[nodiscard]] Settings &settings(); void saveSettingsDelayed(crl::time delay = kDefaultSaveDelay); void saveSettings(); @@ -337,6 +340,11 @@ private: const std::unique_ptr _exportManager; const std::unique_ptr _calls; std::unique_ptr _window; + base::flat_map< + not_null, + std::unique_ptr> _separateWindows; + Window::Controller *_lastActiveWindow = nullptr; + std::unique_ptr _mediaView; const std::unique_ptr _langpack; const std::unique_ptr _langCloudManager; diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 21096322f..1d1275aa1 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -1052,7 +1052,7 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) { } if (anim::Disabled() && (!_pressed || !_pressed->entry()->isPinnedDialog(_filterId))) { - mousePressReleased(e->globalPos(), e->button()); + mousePressReleased(e->globalPos(), e->button(), e->modifiers()); } } @@ -1274,12 +1274,13 @@ bool InnerWidget::pinnedShiftAnimationCallback(crl::time now) { } void InnerWidget::mouseReleaseEvent(QMouseEvent *e) { - mousePressReleased(e->globalPos(), e->button()); + mousePressReleased(e->globalPos(), e->button(), e->modifiers()); } void InnerWidget::mousePressReleased( QPoint globalPosition, - Qt::MouseButton button) { + Qt::MouseButton button, + Qt::KeyboardModifiers modifiers) { auto wasDragging = (_dragging != nullptr); if (wasDragging) { updateReorderIndexGetCount(); @@ -1322,7 +1323,7 @@ void InnerWidget::mousePressReleased( && peerSearchPressed == _peerSearchSelected) || (searchedPressed >= 0 && searchedPressed == _searchedSelected)) { - chooseRow(); + chooseRow(modifiers); } } } @@ -1758,7 +1759,7 @@ void InnerWidget::contextMenuEvent(QContextMenuEvent *e) { _menuRow = row; if (_pressButton != Qt::LeftButton) { - mousePressReleased(e->globalPos(), _pressButton); + mousePressReleased(e->globalPos(), _pressButton, e->modifiers()); } _menu = base::make_unique_q( @@ -2689,13 +2690,21 @@ ChosenRow InnerWidget::computeChosenRow() const { return ChosenRow(); } -bool InnerWidget::chooseRow() { +bool InnerWidget::chooseRow(Qt::KeyboardModifiers modifiers) { if (chooseCollapsedRow()) { return true; } else if (chooseHashtag()) { return true; } - const auto chosen = computeChosenRow(); + const auto modifyChosenRow = []( + ChosenRow row, + Qt::KeyboardModifiers modifiers) { +#ifdef _DEBUG + row.newWindow = (modifiers & Qt::ControlModifier); +#endif + return row; + }; + const auto chosen = modifyChosenRow(computeChosenRow(), modifiers); if (chosen.key) { if (IsServerMsgId(chosen.message.fullId.msg)) { session().local().saveRecentSearchHashtags(_filter); diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index bb8d34601..3fb61d03c 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -46,6 +46,7 @@ struct ChosenRow { Key key; Data::MessagePosition message; bool filteredRow = false; + bool newWindow = false; }; enum class SearchRequestType { @@ -95,7 +96,7 @@ public: void refreshEmptyLabel(); void resizeEmptyLabel(); - bool chooseRow(); + bool chooseRow(Qt::KeyboardModifiers modifiers = {}); void scrollToEntry(const RowDescriptor &entry); @@ -192,7 +193,10 @@ private: void refreshDialogRow(RowDescriptor row); void clearMouseSelection(bool clearSelection = false); - void mousePressReleased(QPoint globalPosition, Qt::MouseButton button); + void mousePressReleased( + QPoint globalPosition, + Qt::MouseButton button, + Qt::KeyboardModifiers modifiers); void clearIrrelevantState(); void selectByMouse(QPoint globalPosition); void loadPeerPhotos(); diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 8535fad4d..bb4ba1cb9 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/peer_list_box.h" #include "boxes/peers/edit_participants_box.h" #include "window/window_adaptive.h" +#include "window/window_controller.h" #include "window/window_session_controller.h" #include "window/window_slide_animation.h" #include "window/window_connecting_widget.h" @@ -226,7 +227,11 @@ Widget::Widget( const auto openSearchResult = !controller->selectingPeer() && row.filteredRow; if (const auto history = row.key.history()) { - controller->content()->choosePeer( + const auto window = row.newWindow + ? Core::App().ensureSeparateWindowForPeer( + history->peer)->sessionController() + : controller.get(); + window->content()->choosePeer( history->peer->id, (controller->uniqueChatsInSearchResults() ? ShowAtUnreadMsgId diff --git a/Telegram/SourceFiles/window/window_controller.cpp b/Telegram/SourceFiles/window/window_controller.cpp index 69c9fe997..f69b3ac7e 100644 --- a/Telegram/SourceFiles/window/window_controller.cpp +++ b/Telegram/SourceFiles/window/window_controller.cpp @@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/themes/window_theme.h" #include "window/themes/window_theme_editor.h" #include "ui/boxes/confirm_box.h" +#include "data/data_peer.h" #include "mainwindow.h" #include "apiwrap.h" // ApiWrap::acceptTerms. #include "facades.h" @@ -40,8 +41,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Window { -Controller::Controller() -: _widget(this) +Controller::Controller() : Controller(CreateArgs{}) { +} + +Controller::Controller(not_null singlePeer) +: Controller(CreateArgs{ singlePeer.get() }) { +} + +Controller::Controller(CreateArgs &&args) +: _singlePeer(args.singlePeer) +, _widget(this) , _adaptive(std::make_unique()) , _isActiveTimer([=] { updateIsActive(); }) { _widget.init(); @@ -54,6 +63,8 @@ Controller::~Controller() { } void Controller::showAccount(not_null account) { + Expects(!_singlePeer || &_singlePeer->account() == account); + const auto prevSessionUniqueId = (_account && _account->sessionExists()) ? _account->session().uniqueId() : 0; @@ -118,6 +129,10 @@ void Controller::showAccount(not_null account) { }, _accountLifetime); } +PeerData *Controller::singlePeer() const { + return _singlePeer; +} + void Controller::checkLockByTerms() { const auto data = account().sessionExists() ? account().session().termsLocked() diff --git a/Telegram/SourceFiles/window/window_controller.h b/Telegram/SourceFiles/window/window_controller.h index f58dc8e77..ce83903bc 100644 --- a/Telegram/SourceFiles/window/window_controller.h +++ b/Telegram/SourceFiles/window/window_controller.h @@ -24,12 +24,14 @@ namespace Window { class Controller final : public base::has_weak_ptr { public: Controller(); + explicit Controller(not_null singlePeer); ~Controller(); Controller(const Controller &other) = delete; Controller &operator=(const Controller &other) = delete; void showAccount(not_null account); + [[nodiscard]] PeerData *singlePeer() const; [[nodiscard]] not_null<::MainWindow*> widget() { return &_widget; @@ -100,6 +102,11 @@ public: rpl::lifetime &lifetime(); private: + struct CreateArgs { + PeerData *singlePeer = nullptr; + }; + explicit Controller(CreateArgs &&args); + void showBox( object_ptr content, Ui::LayerOptions options, @@ -109,6 +116,7 @@ private: void showTermsDecline(); void showTermsDelete(); + PeerData *_singlePeer = nullptr; Main::Account *_account = nullptr; ::MainWindow _widget; const std::unique_ptr _adaptive;