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 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<Ui::PopupMenu> 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<Ui::PopupMenu> Members::Controller::createRowContextMenu(
}
};
const auto showProfile = [=] {
performOnMainWindow([=](not_null<Window::SessionController*> window) {
withActiveWindow([=](not_null<Window::SessionController*> window) {
window->showPeerInfo(participantPeer);
});
};
const auto showHistory = [=] {
performOnMainWindow([=](not_null<Window::SessionController*> window) {
withActiveWindow([=](not_null<Window::SessionController*> window) {
window->showPeerHistory(
participantPeer,
Window::SectionShow::Way::Forward);

View file

@ -123,6 +123,7 @@ Application *Application::Instance = nullptr;
struct Application::Private {
base::Timer quitTimer;
UiIntegration uiIntegration;
Settings settings;
};
Application::Application(not_null<Launcher*> 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<Window::Controller>();
_lastActiveWindow = _window.get();
_domain->activeChanges(
) | rpl::start_with_next([=](not_null<Main::Account*> 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<Ui::Emoji::UniversalImages> {
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<ProxyChange> {
@ -527,10 +533,10 @@ auto Application::proxyChanges() const -> rpl::producer<ProxyChange> {
}
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<Ui::InformBox>(
@ -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<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() {
if (hideMediaView()) {
return true;

View file

@ -133,7 +133,12 @@ public:
// Windows interface.
bool hasActiveWindow(not_null<Main::Session*> session) const;
void saveCurrentDraftsToHistories();
[[nodiscard]] Window::Controller *mainWindow() 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 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<Export::Manager> _exportManager;
const std::unique_ptr<Calls::Instance> _calls;
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;
const std::unique_ptr<Lang::Instance> _langpack;
const std::unique_ptr<Lang::CloudManager> _langCloudManager;

View file

@ -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<Ui::PopupMenu>(
@ -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);

View file

@ -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();

View file

@ -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

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_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<PeerData*> singlePeer)
: Controller(CreateArgs{ singlePeer.get() }) {
}
Controller::Controller(CreateArgs &&args)
: _singlePeer(args.singlePeer)
, _widget(this)
, _adaptive(std::make_unique<Adaptive>())
, _isActiveTimer([=] { updateIsActive(); }) {
_widget.init();
@ -54,6 +63,8 @@ Controller::~Controller() {
}
void Controller::showAccount(not_null<Main::Account*> 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<Main::Account*> account) {
}, _accountLifetime);
}
PeerData *Controller::singlePeer() const {
return _singlePeer;
}
void Controller::checkLockByTerms() {
const auto data = account().sessionExists()
? account().session().termsLocked()

View file

@ -24,12 +24,14 @@ namespace Window {
class Controller final : public base::has_weak_ptr {
public:
Controller();
explicit Controller(not_null<PeerData*> singlePeer);
~Controller();
Controller(const Controller &other) = delete;
Controller &operator=(const Controller &other) = delete;
void showAccount(not_null<Main::Account*> 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<Ui::BoxContent> 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> _adaptive;