Allow creating separate windows for peers.

This commit is contained in:
John Preston 2022-01-04 14:18:13 +03:00
parent f4f36d85b9
commit 20411be9bd
8 changed files with 128 additions and 39 deletions

View file

@ -1195,7 +1195,10 @@ base::unique_qptr<Ui::PopupMenu> Members::Controller::createRowContextMenu(
const auto admin = IsGroupCallAdmin(_peer, participantPeer); const auto admin = IsGroupCallAdmin(_peer, participantPeer);
const auto session = &_peer->session(); const auto session = &_peer->session();
const auto getCurrentWindow = [=]() -> Window::SessionController* { 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 (const auto controller = window->sessionController()) {
if (&controller->session() == session) { if (&controller->session() == session) {
return controller; return controller;
@ -1221,7 +1224,7 @@ base::unique_qptr<Ui::PopupMenu> Members::Controller::createRowContextMenu(
? st::groupCallPopupMenuWithVolume ? st::groupCallPopupMenuWithVolume
: st::groupCallPopupMenu)); : st::groupCallPopupMenu));
const auto weakMenu = Ui::MakeWeak(result.get()); 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 window = getWindow()) {
if (const auto menu = weakMenu.data()) { if (const auto menu = weakMenu.data()) {
menu->discardParentReActivate(); menu->discardParentReActivate();
@ -1236,12 +1239,12 @@ base::unique_qptr<Ui::PopupMenu> Members::Controller::createRowContextMenu(
} }
}; };
const auto showProfile = [=] { const auto showProfile = [=] {
performOnMainWindow([=](not_null<Window::SessionController*> window) { withActiveWindow([=](not_null<Window::SessionController*> window) {
window->showPeerInfo(participantPeer); window->showPeerInfo(participantPeer);
}); });
}; };
const auto showHistory = [=] { const auto showHistory = [=] {
performOnMainWindow([=](not_null<Window::SessionController*> window) { withActiveWindow([=](not_null<Window::SessionController*> window) {
window->showPeerHistory( window->showPeerHistory(
participantPeer, participantPeer,
Window::SectionShow::Way::Forward); Window::SectionShow::Way::Forward);

View file

@ -123,6 +123,7 @@ Application *Application::Instance = nullptr;
struct Application::Private { struct Application::Private {
base::Timer quitTimer; base::Timer quitTimer;
UiIntegration uiIntegration; UiIntegration uiIntegration;
Settings settings;
}; };
Application::Application(not_null<Launcher*> launcher) Application::Application(not_null<Launcher*> launcher)
@ -170,6 +171,7 @@ Application::~Application() {
// Depend on activeWindow() for now :( // Depend on activeWindow() for now :(
Shortcuts::Finish(); Shortcuts::Finish();
_separateWindows.clear();
_window = nullptr; _window = nullptr;
_mediaView = nullptr; _mediaView = nullptr;
_notifications->clearAllFast(); _notifications->clearAllFast();
@ -264,6 +266,7 @@ void Application::run() {
QMimeDatabase().mimeTypeForName(qsl("text/plain")); QMimeDatabase().mimeTypeForName(qsl("text/plain"));
_window = std::make_unique<Window::Controller>(); _window = std::make_unique<Window::Controller>();
_lastActiveWindow = _window.get();
_domain->activeChanges( _domain->activeChanges(
) | rpl::start_with_next([=](not_null<Main::Account*> account) { ) | rpl::start_with_next([=](not_null<Main::Account*> account) {
@ -371,8 +374,8 @@ void Application::startSettingsAndBackground() {
} }
void Application::checkSystemDarkMode() { void Application::checkSystemDarkMode() {
const auto maybeDarkMode = _settings.systemDarkMode(); const auto maybeDarkMode = settings().systemDarkMode();
const auto darkModeEnabled = _settings.systemDarkModeEnabled(); const auto darkModeEnabled = settings().systemDarkModeEnabled();
const auto needToSwitch = darkModeEnabled const auto needToSwitch = darkModeEnabled
&& maybeDarkMode && maybeDarkMode
&& (*maybeDarkMode != Window::Theme::IsNightMode()); && (*maybeDarkMode != Window::Theme::IsNightMode());
@ -384,11 +387,11 @@ void Application::checkSystemDarkMode() {
void Application::startSystemDarkModeViewer() { void Application::startSystemDarkModeViewer() {
if (Window::Theme::Background()->editingTheme()) { if (Window::Theme::Background()->editingTheme()) {
_settings.setSystemDarkModeEnabled(false); settings().setSystemDarkModeEnabled(false);
} }
rpl::merge( rpl::merge(
_settings.systemDarkModeChanges() | rpl::to_empty, settings().systemDarkModeChanges() | rpl::to_empty,
_settings.systemDarkModeEnabledChanges() | rpl::to_empty settings().systemDarkModeEnabledChanges() | rpl::to_empty
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
checkSystemDarkMode(); checkSystemDarkMode();
}, _lifetime); }, _lifetime);
@ -397,7 +400,7 @@ void Application::startSystemDarkModeViewer() {
auto Application::prepareEmojiSourceImages() auto Application::prepareEmojiSourceImages()
-> std::shared_ptr<Ui::Emoji::UniversalImages> { -> std::shared_ptr<Ui::Emoji::UniversalImages> {
const auto &images = Ui::Emoji::SourceImages(); const auto &images = Ui::Emoji::SourceImages();
if (_settings.largeEmoji()) { if (settings().largeEmoji()) {
return images; return images;
} }
Ui::Emoji::ClearSourceImages(images); Ui::Emoji::ClearSourceImages(images);
@ -471,6 +474,10 @@ bool Application::eventFilter(QObject *object, QEvent *e) {
return QObject::eventFilter(object, e); return QObject::eventFilter(object, e);
} }
Settings &Application::settings() {
return _private->settings;
}
void Application::saveSettingsDelayed(crl::time delay) { void Application::saveSettingsDelayed(crl::time delay) {
if (_saveSettingsTimer) { if (_saveSettingsTimer) {
_saveSettingsTimer->callOnce(delay); _saveSettingsTimer->callOnce(delay);
@ -508,18 +515,17 @@ void Application::constructFallbackProductionConfig(
void Application::setCurrentProxy( void Application::setCurrentProxy(
const MTP::ProxyData &proxy, const MTP::ProxyData &proxy,
MTP::ProxyData::Settings settings) { MTP::ProxyData::Settings settings) {
auto &my = _private->settings.proxy();
const auto current = [&] { const auto current = [&] {
return _settings.proxy().isEnabled() return my.isEnabled() ? my.selected() : MTP::ProxyData();
? _settings.proxy().selected()
: MTP::ProxyData();
}; };
const auto was = current(); const auto was = current();
_settings.proxy().setSelected(proxy); my.setSelected(proxy);
_settings.proxy().setSettings(settings); my.setSettings(settings);
const auto now = current(); const auto now = current();
refreshGlobalProxy(); refreshGlobalProxy();
_proxyChanges.fire({ was, now }); _proxyChanges.fire({ was, now });
_settings.proxy().connectionTypeChangesNotify(); my.connectionTypeChangesNotify();
} }
auto Application::proxyChanges() const -> rpl::producer<ProxyChange> { auto Application::proxyChanges() const -> rpl::producer<ProxyChange> {
@ -527,10 +533,10 @@ auto Application::proxyChanges() const -> rpl::producer<ProxyChange> {
} }
void Application::badMtprotoConfigurationError() { void Application::badMtprotoConfigurationError() {
if (_settings.proxy().isEnabled() && !_badProxyDisableBox) { if (settings().proxy().isEnabled() && !_badProxyDisableBox) {
const auto disableCallback = [=] { const auto disableCallback = [=] {
setCurrentProxy( setCurrentProxy(
_settings.proxy().selected(), settings().proxy().selected(),
MTP::ProxyData::Settings::System); MTP::ProxyData::Settings::System);
}; };
_badProxyDisableBox = Ui::show(Box<Ui::InformBox>( _badProxyDisableBox = Ui::show(Box<Ui::InformBox>(
@ -542,7 +548,7 @@ void Application::badMtprotoConfigurationError() {
void Application::startLocalStorage() { void Application::startLocalStorage() {
Local::start(); Local::start();
_saveSettingsTimer.emplace([=] { saveSettings(); }); _saveSettingsTimer.emplace([=] { saveSettings(); });
_settings.saveDelayedRequests() | rpl::start_with_next([=] { settings().saveDelayedRequests() | rpl::start_with_next([=] {
saveSettingsDelayed(); saveSettingsDelayed();
}, _lifetime); }, _lifetime);
} }
@ -550,12 +556,12 @@ void Application::startLocalStorage() {
void Application::startEmojiImageLoader() { void Application::startEmojiImageLoader() {
_emojiImageLoader.with([ _emojiImageLoader.with([
source = prepareEmojiSourceImages(), source = prepareEmojiSourceImages(),
large = _settings.largeEmoji() large = settings().largeEmoji()
](Stickers::EmojiImageLoader &loader) mutable { ](Stickers::EmojiImageLoader &loader) mutable {
loader.init(std::move(source), large); loader.init(std::move(source), large);
}); });
_settings.largeEmojiChanges( settings().largeEmojiChanges(
) | rpl::start_with_next([=](bool large) { ) | rpl::start_with_next([=](bool large) {
if (large) { if (large) {
_clearEmojiImageLoaderTimer.cancel(); _clearEmojiImageLoaderTimer.cancel();
@ -913,9 +919,11 @@ void Application::checkAutoLock(crl::time lastNonIdleTime) {
checkLocalTime(); checkLocalTime();
const auto now = crl::now(); const auto now = crl::now();
const auto shouldLockInMs = _settings.autoLock() * 1000LL; const auto shouldLockInMs = settings().autoLock() * 1000LL;
const auto checkTimeMs = now - lastNonIdleTime; const auto checkTimeMs = now - lastNonIdleTime;
if (checkTimeMs >= shouldLockInMs || (_shouldLockAt > 0 && now > _shouldLockAt + kAutoLockTimeoutLateMs)) { if (checkTimeMs >= shouldLockInMs
|| (_shouldLockAt > 0
&& now > _shouldLockAt + kAutoLockTimeoutLateMs)) {
_shouldLockAt = 0; _shouldLockAt = 0;
_autoLockTimer.cancel(); _autoLockTimer.cancel();
lockByPasscode(); lockByPasscode();
@ -961,10 +969,39 @@ void Application::saveCurrentDraftsToHistories() {
} }
} }
Window::Controller *Application::activeWindow() const { Window::Controller *Application::mainWindow() const {
return _window.get(); return _window.get();
} }
Window::Controller *Application::separateWindowForPeer(
not_null<PeerData*> peer) const {
for (const auto &[history, window] : _separateWindows) {
if (history->peer == peer) {
return window.get();
}
}
return nullptr;
}
Window::Controller *Application::ensureSeparateWindowForPeer(
not_null<PeerData*> peer) {
if (const auto existing = separateWindowForPeer(peer)) {
return existing;
}
const auto result = _separateWindows.emplace(
peer->owner().history(peer),
std::make_unique<Window::Controller>()).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() { bool Application::closeActiveWindow() {
if (hideMediaView()) { if (hideMediaView()) {
return true; return true;

View file

@ -133,7 +133,12 @@ public:
// Windows interface. // Windows interface.
bool hasActiveWindow(not_null<Main::Session*> session) const; bool hasActiveWindow(not_null<Main::Session*> session) const;
void saveCurrentDraftsToHistories(); void saveCurrentDraftsToHistories();
[[nodiscard]] Window::Controller *mainWindow() const;
[[nodiscard]] Window::Controller *activeWindow() const; [[nodiscard]] Window::Controller *activeWindow() const;
[[nodiscard]] Window::Controller *separateWindowForPeer(
not_null<PeerData*> peer) const;
Window::Controller *ensureSeparateWindowForPeer(
not_null<PeerData*> peer);
bool closeActiveWindow(); bool closeActiveWindow();
bool minimizeActiveWindow(); bool minimizeActiveWindow();
[[nodiscard]] QWidget *getFileDialogParent(); [[nodiscard]] QWidget *getFileDialogParent();
@ -147,9 +152,7 @@ public:
[[nodiscard]] QPoint getPointForCallPanelCenter() const; [[nodiscard]] QPoint getPointForCallPanelCenter() const;
void startSettingsAndBackground(); void startSettingsAndBackground();
[[nodiscard]] Settings &settings() { [[nodiscard]] Settings &settings();
return _settings;
}
void saveSettingsDelayed(crl::time delay = kDefaultSaveDelay); void saveSettingsDelayed(crl::time delay = kDefaultSaveDelay);
void saveSettings(); void saveSettings();
@ -337,6 +340,11 @@ private:
const std::unique_ptr<Export::Manager> _exportManager; const std::unique_ptr<Export::Manager> _exportManager;
const std::unique_ptr<Calls::Instance> _calls; const std::unique_ptr<Calls::Instance> _calls;
std::unique_ptr<Window::Controller> _window; std::unique_ptr<Window::Controller> _window;
base::flat_map<
not_null<History*>,
std::unique_ptr<Window::Controller>> _separateWindows;
Window::Controller *_lastActiveWindow = nullptr;
std::unique_ptr<Media::View::OverlayWidget> _mediaView; std::unique_ptr<Media::View::OverlayWidget> _mediaView;
const std::unique_ptr<Lang::Instance> _langpack; const std::unique_ptr<Lang::Instance> _langpack;
const std::unique_ptr<Lang::CloudManager> _langCloudManager; const std::unique_ptr<Lang::CloudManager> _langCloudManager;

View file

@ -1052,7 +1052,7 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) {
} }
if (anim::Disabled() if (anim::Disabled()
&& (!_pressed || !_pressed->entry()->isPinnedDialog(_filterId))) { && (!_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) { void InnerWidget::mouseReleaseEvent(QMouseEvent *e) {
mousePressReleased(e->globalPos(), e->button()); mousePressReleased(e->globalPos(), e->button(), e->modifiers());
} }
void InnerWidget::mousePressReleased( void InnerWidget::mousePressReleased(
QPoint globalPosition, QPoint globalPosition,
Qt::MouseButton button) { Qt::MouseButton button,
Qt::KeyboardModifiers modifiers) {
auto wasDragging = (_dragging != nullptr); auto wasDragging = (_dragging != nullptr);
if (wasDragging) { if (wasDragging) {
updateReorderIndexGetCount(); updateReorderIndexGetCount();
@ -1322,7 +1323,7 @@ void InnerWidget::mousePressReleased(
&& peerSearchPressed == _peerSearchSelected) && peerSearchPressed == _peerSearchSelected)
|| (searchedPressed >= 0 || (searchedPressed >= 0
&& searchedPressed == _searchedSelected)) { && searchedPressed == _searchedSelected)) {
chooseRow(); chooseRow(modifiers);
} }
} }
} }
@ -1758,7 +1759,7 @@ void InnerWidget::contextMenuEvent(QContextMenuEvent *e) {
_menuRow = row; _menuRow = row;
if (_pressButton != Qt::LeftButton) { if (_pressButton != Qt::LeftButton) {
mousePressReleased(e->globalPos(), _pressButton); mousePressReleased(e->globalPos(), _pressButton, e->modifiers());
} }
_menu = base::make_unique_q<Ui::PopupMenu>( _menu = base::make_unique_q<Ui::PopupMenu>(
@ -2689,13 +2690,21 @@ ChosenRow InnerWidget::computeChosenRow() const {
return ChosenRow(); return ChosenRow();
} }
bool InnerWidget::chooseRow() { bool InnerWidget::chooseRow(Qt::KeyboardModifiers modifiers) {
if (chooseCollapsedRow()) { if (chooseCollapsedRow()) {
return true; return true;
} else if (chooseHashtag()) { } else if (chooseHashtag()) {
return true; 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 (chosen.key) {
if (IsServerMsgId(chosen.message.fullId.msg)) { if (IsServerMsgId(chosen.message.fullId.msg)) {
session().local().saveRecentSearchHashtags(_filter); session().local().saveRecentSearchHashtags(_filter);

View file

@ -46,6 +46,7 @@ struct ChosenRow {
Key key; Key key;
Data::MessagePosition message; Data::MessagePosition message;
bool filteredRow = false; bool filteredRow = false;
bool newWindow = false;
}; };
enum class SearchRequestType { enum class SearchRequestType {
@ -95,7 +96,7 @@ public:
void refreshEmptyLabel(); void refreshEmptyLabel();
void resizeEmptyLabel(); void resizeEmptyLabel();
bool chooseRow(); bool chooseRow(Qt::KeyboardModifiers modifiers = {});
void scrollToEntry(const RowDescriptor &entry); void scrollToEntry(const RowDescriptor &entry);
@ -192,7 +193,10 @@ private:
void refreshDialogRow(RowDescriptor row); void refreshDialogRow(RowDescriptor row);
void clearMouseSelection(bool clearSelection = false); 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 clearIrrelevantState();
void selectByMouse(QPoint globalPosition); void selectByMouse(QPoint globalPosition);
void loadPeerPhotos(); void loadPeerPhotos();

View file

@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peer_list_box.h" #include "boxes/peer_list_box.h"
#include "boxes/peers/edit_participants_box.h" #include "boxes/peers/edit_participants_box.h"
#include "window/window_adaptive.h" #include "window/window_adaptive.h"
#include "window/window_controller.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "window/window_slide_animation.h" #include "window/window_slide_animation.h"
#include "window/window_connecting_widget.h" #include "window/window_connecting_widget.h"
@ -226,7 +227,11 @@ Widget::Widget(
const auto openSearchResult = !controller->selectingPeer() const auto openSearchResult = !controller->selectingPeer()
&& row.filteredRow; && row.filteredRow;
if (const auto history = row.key.history()) { 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, history->peer->id,
(controller->uniqueChatsInSearchResults() (controller->uniqueChatsInSearchResults()
? ShowAtUnreadMsgId ? ShowAtUnreadMsgId

View file

@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/themes/window_theme.h" #include "window/themes/window_theme.h"
#include "window/themes/window_theme_editor.h" #include "window/themes/window_theme_editor.h"
#include "ui/boxes/confirm_box.h" #include "ui/boxes/confirm_box.h"
#include "data/data_peer.h"
#include "mainwindow.h" #include "mainwindow.h"
#include "apiwrap.h" // ApiWrap::acceptTerms. #include "apiwrap.h" // ApiWrap::acceptTerms.
#include "facades.h" #include "facades.h"
@ -40,8 +41,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Window { namespace Window {
Controller::Controller() Controller::Controller() : Controller(CreateArgs{}) {
: _widget(this) }
Controller::Controller(not_null<PeerData*> singlePeer)
: Controller(CreateArgs{ singlePeer.get() }) {
}
Controller::Controller(CreateArgs &&args)
: _singlePeer(args.singlePeer)
, _widget(this)
, _adaptive(std::make_unique<Adaptive>()) , _adaptive(std::make_unique<Adaptive>())
, _isActiveTimer([=] { updateIsActive(); }) { , _isActiveTimer([=] { updateIsActive(); }) {
_widget.init(); _widget.init();
@ -54,6 +63,8 @@ Controller::~Controller() {
} }
void Controller::showAccount(not_null<Main::Account*> account) { void Controller::showAccount(not_null<Main::Account*> account) {
Expects(!_singlePeer || &_singlePeer->account() == account);
const auto prevSessionUniqueId = (_account && _account->sessionExists()) const auto prevSessionUniqueId = (_account && _account->sessionExists())
? _account->session().uniqueId() ? _account->session().uniqueId()
: 0; : 0;
@ -118,6 +129,10 @@ void Controller::showAccount(not_null<Main::Account*> account) {
}, _accountLifetime); }, _accountLifetime);
} }
PeerData *Controller::singlePeer() const {
return _singlePeer;
}
void Controller::checkLockByTerms() { void Controller::checkLockByTerms() {
const auto data = account().sessionExists() const auto data = account().sessionExists()
? account().session().termsLocked() ? account().session().termsLocked()

View file

@ -24,12 +24,14 @@ namespace Window {
class Controller final : public base::has_weak_ptr { class Controller final : public base::has_weak_ptr {
public: public:
Controller(); Controller();
explicit Controller(not_null<PeerData*> singlePeer);
~Controller(); ~Controller();
Controller(const Controller &other) = delete; Controller(const Controller &other) = delete;
Controller &operator=(const Controller &other) = delete; Controller &operator=(const Controller &other) = delete;
void showAccount(not_null<Main::Account*> account); void showAccount(not_null<Main::Account*> account);
[[nodiscard]] PeerData *singlePeer() const;
[[nodiscard]] not_null<::MainWindow*> widget() { [[nodiscard]] not_null<::MainWindow*> widget() {
return &_widget; return &_widget;
@ -100,6 +102,11 @@ public:
rpl::lifetime &lifetime(); rpl::lifetime &lifetime();
private: private:
struct CreateArgs {
PeerData *singlePeer = nullptr;
};
explicit Controller(CreateArgs &&args);
void showBox( void showBox(
object_ptr<Ui::BoxContent> content, object_ptr<Ui::BoxContent> content,
Ui::LayerOptions options, Ui::LayerOptions options,
@ -109,6 +116,7 @@ private:
void showTermsDecline(); void showTermsDecline();
void showTermsDelete(); void showTermsDelete();
PeerData *_singlePeer = nullptr;
Main::Account *_account = nullptr; Main::Account *_account = nullptr;
::MainWindow _widget; ::MainWindow _widget;
const std::unique_ptr<Adaptive> _adaptive; const std::unique_ptr<Adaptive> _adaptive;