Files
AlexeyZavar 0825ea0160 Merge tag 'v6.7.8' into dev
# Conflicts:
#	.github/workflows/mac_packaged.yml
#	Telegram/Resources/winrc/Telegram.rc
#	Telegram/Resources/winrc/Updater.rc
#	Telegram/SourceFiles/api/api_chat_invite.h
#	Telegram/SourceFiles/boxes/language_box.cpp
#	Telegram/SourceFiles/boxes/send_files_box.h
#	Telegram/SourceFiles/boxes/sticker_set_box.cpp
#	Telegram/SourceFiles/core/version.h
#	Telegram/SourceFiles/data/data_types.h
#	Telegram/SourceFiles/history/history_item.cpp
#	Telegram/SourceFiles/history/history_widget.cpp
#	Telegram/SourceFiles/history/view/history_view_chat_section.cpp
#	Telegram/SourceFiles/history/view/history_view_message.cpp
#	Telegram/SourceFiles/history/view/history_view_translate_bar.cpp
#	Telegram/SourceFiles/history/view/history_view_translate_tracker.cpp
#	Telegram/SourceFiles/platform/mac/main_window_mac.mm
#	Telegram/SourceFiles/settings/sections/settings_main.cpp
#	Telegram/lib_ui
#	snap/snapcraft.yaml
2026-04-24 20:47:17 +03:00

3000 lines
80 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "mainwidget.h"
#include "api/api_updates.h"
#include "api/api_views.h"
#include "data/components/scheduled_messages.h"
#include "data/data_document_media.h"
#include "data/data_document_resolver.h"
#include "data/data_forum_topic.h"
#include "data/data_web_page.h"
#include "data/data_game.h"
#include "data/data_peer_values.h"
#include "data/data_saved_sublist.h"
#include "data/data_session.h"
#include "data/data_changes.h"
#include "data/data_folder.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_user.h"
#include "data/data_chat_filters.h"
#include "data/data_file_origin.h"
#include "data/data_histories.h"
#include "data/stickers/data_stickers.h"
#include "ui/chat/chat_theme.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/shadow.h"
#include "ui/widgets/dropdown_menu.h"
#include "ui/focus_persister.h"
#include "ui/resize_area.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "ui/ui_utility.h"
#include "window/window_connecting_widget.h"
#include "window/window_top_bar_wrap.h"
#include "window/notifications_manager.h"
#include "window/window_separate_id.h"
#include "window/window_slide_animation.h"
#include "window/window_history_hider.h"
#include "window/window_controller.h"
#include "window/window_peer_menu.h"
#include "window/window_session_controller_link_info.h"
#include "window/themes/window_theme.h"
#include "chat_helpers/bot_command.h"
#include "chat_helpers/tabbed_selector.h" // TabbedSelector::refreshStickers
#include "chat_helpers/message_field.h"
#include "info/info_memento.h"
#include "apiwrap.h"
#include "dialogs/dialogs_widget.h"
#include "history/history_widget.h"
#include "history/history_item_helpers.h" // GetErrorForSending.
#include "history/view/media/history_view_media.h"
#include "history/view/history_view_chat_section.h"
#include "history/view/history_view_service_message.h"
#include "lang/lang_keys.h"
#include "lang/lang_cloud_manager.h"
#include "inline_bots/inline_bot_layout_item.h"
#include "ui/boxes/confirm_box.h"
#include "boxes/peer_list_controllers.h"
#include "storage/storage_account.h"
#include "main/main_domain.h"
#include "media/audio/media_audio.h"
#include "media/player/media_player_panel.h"
#include "media/player/media_player_widget.h"
#include "media/player/media_player_dropdown.h"
#include "media/player/media_player_instance.h"
#include "base/qthelp_regex.h"
#include "base/options.h"
#include "mtproto/mtproto_dc_options.h"
#include "core/update_checker.h"
#include "core/shortcuts.h"
#include "core/application.h"
#include "core/changelogs.h"
#include "core/mime_type.h"
#include "calls/calls_call.h"
#include "calls/calls_instance.h"
#include "calls/calls_top_bar.h"
#include "calls/group/calls_group_call.h"
#include "export/export_settings.h"
#include "export/export_manager.h"
#include "export/view/export_view_top_bar.h"
#include "export/view/export_view_panel_controller.h"
#include "main/main_session.h"
#include "main/main_session_settings.h"
#include "main/main_app_config.h"
#include "settings/sections/settings_premium.h"
#include "support/support_helper.h"
#include "storage/storage_user_photos.h"
#include "styles/style_dialogs.h"
#include "styles/style_chat.h"
#include "styles/style_window.h"
#include <QtCore/QCoreApplication>
#include <QtCore/QMimeData>
// AyuGram includes
#include "ayu/features/forward/ayu_forward.h"
namespace {
void ClearBotStartToken(PeerData *peer) {
if (peer && peer->isUser() && peer->asUser()->isBot()) {
peer->asUser()->botInfo->startToken = QString();
}
}
base::options::toggle ForceComposeSearchOneColumn({
.id = kForceComposeSearchOneColumn,
.name = "Force embedded search in chats",
.description = "Force in one-column mode the embedded search in chats.",
});
} // namespace
const char kForceComposeSearchOneColumn[] = "force-compose-search-one-column";
enum StackItemType {
HistoryStackItem,
SectionStackItem,
};
class StackItem {
public:
explicit StackItem(PeerData *peer) : _peer(peer) {
}
[[nodiscard]] PeerData *peer() const {
return _peer;
}
void setThirdSectionMemento(
std::shared_ptr<Window::SectionMemento> memento);
[[nodiscard]] auto takeThirdSectionMemento()
-> std::shared_ptr<Window::SectionMemento> {
return std::move(_thirdSectionMemento);
}
void setThirdSectionWeak(QPointer<Window::SectionWidget> section) {
_thirdSectionWeak = section;
}
[[nodiscard]] QPointer<Window::SectionWidget> thirdSectionWeak() const {
return _thirdSectionWeak;
}
[[nodiscard]] rpl::lifetime &lifetime() {
return _lifetime;
}
[[nodiscard]] virtual StackItemType type() const = 0;
[[nodiscard]] rpl::producer<> removeRequests() const {
return rpl::merge(
_thirdSectionRemoveRequests.events(),
sectionRemoveRequests());
}
virtual ~StackItem() = default;
private:
[[nodiscard]] virtual rpl::producer<> sectionRemoveRequests() const = 0;
PeerData *_peer = nullptr;
QPointer<Window::SectionWidget> _thirdSectionWeak;
std::shared_ptr<Window::SectionMemento> _thirdSectionMemento;
rpl::event_stream<> _thirdSectionRemoveRequests;
rpl::lifetime _lifetime;
};
class StackItemHistory final : public StackItem {
public:
StackItemHistory(
not_null<History*> history,
MsgId msgId,
QVector<FullMsgId> replyReturns)
: StackItem(history->peer)
, history(history)
, msgId(msgId)
, replyReturns(replyReturns) {
}
StackItemType type() const override {
return HistoryStackItem;
}
not_null<History*> history;
MsgId msgId;
QVector<FullMsgId> replyReturns;
private:
rpl::producer<> sectionRemoveRequests() const override {
return rpl::never<>();
}
};
class StackItemSection : public StackItem {
public:
StackItemSection(
std::shared_ptr<Window::SectionMemento> memento);
StackItemType type() const override {
return SectionStackItem;
}
std::shared_ptr<Window::SectionMemento> takeMemento() {
return std::move(_memento);
}
private:
rpl::producer<> sectionRemoveRequests() const override;
std::shared_ptr<Window::SectionMemento> _memento;
};
void StackItem::setThirdSectionMemento(
std::shared_ptr<Window::SectionMemento> memento) {
_thirdSectionMemento = std::move(memento);
if (const auto memento = _thirdSectionMemento.get()) {
memento->removeRequests(
) | rpl::start_to_stream(_thirdSectionRemoveRequests, _lifetime);
}
}
StackItemSection::StackItemSection(
std::shared_ptr<Window::SectionMemento> memento)
: StackItem(nullptr)
, _memento(std::move(memento)) {
}
rpl::producer<> StackItemSection::sectionRemoveRequests() const {
if (const auto topic = _memento->topicForRemoveRequests()) {
return rpl::merge(_memento->removeRequests(), topic->destroyed());
} else if (const auto sublist = _memento->sublistForRemoveRequests()) {
return rpl::merge(_memento->removeRequests(), sublist->destroyed());
}
return _memento->removeRequests();
}
struct MainWidget::SettingBackground {
explicit SettingBackground(const Data::WallPaper &data);
Data::WallPaper data;
std::shared_ptr<Data::DocumentMedia> dataMedia;
base::binary_guard generating;
};
MainWidget::SettingBackground::SettingBackground(
const Data::WallPaper &data)
: data(data) {
}
MainWidget::MainWidget(
QWidget *parent,
not_null<Window::SessionController*> controller)
: RpWidget(parent)
, _controller(controller)
, _dialogsWidth(st::columnMinimalWidthLeft)
, _thirdColumnWidth(st::columnMinimalWidthThird)
, _dialogs(windowId().hasChatsList()
? base::make_unique_q<Dialogs::Widget>(
this,
_controller,
Dialogs::Widget::Layout::Main)
: nullptr)
, _history(std::in_place, this, _controller)
, _sideShadow(_dialogs
? base::make_unique_q<Ui::PlainShadow>(this)
: nullptr)
, _playerPlaylist(this, _controller)
, _changelogs(Core::Changelogs::Create(&controller->session())) {
if (_dialogs) {
setupConnectingWidget();
}
_history->cancelRequests(
) | rpl::on_next([=] {
handleHistoryBack();
}, lifetime());
Core::App().calls().currentCallValue(
) | rpl::on_next([=](Calls::Call *call) {
setCurrentCall(call);
}, lifetime());
Core::App().calls().currentGroupCallValue(
) | rpl::on_next([=](Calls::GroupCall *call) {
setCurrentGroupCall(call);
}, lifetime());
if (_callTopBar) {
_callTopBar->finishAnimating();
}
controller->window().setDefaultFloatPlayerDelegate(
floatPlayerDelegate());
Core::App().floatPlayerClosed(
) | rpl::on_next([=](FullMsgId itemId) {
floatPlayerClosed(itemId);
}, lifetime());
Core::App().exportManager().currentView(
) | rpl::on_next([=](Export::View::PanelController *view) {
setCurrentExportView(view);
}, lifetime());
if (_exportTopBar) {
_exportTopBar->finishAnimating();
}
Media::Player::instance()->closePlayerRequests(
) | rpl::on_next([=] {
closeBothPlayers();
}, lifetime());
Media::Player::instance()->updatedNotifier(
) | rpl::on_next([=](const Media::Player::TrackState &state) {
handleAudioUpdate(state);
}, lifetime());
handleAudioUpdate(Media::Player::instance()->getState(AudioMsgId::Type::Song));
handleAudioUpdate(Media::Player::instance()->getState(AudioMsgId::Type::Voice));
if (_player) {
_player->finishAnimating();
}
_controller->chatsForceDisplayWideChanges(
) | rpl::on_next([=] {
crl::on_main(this, [=] {
updateDialogsWidthAnimated();
});
}, lifetime());
const auto filter = [=](bool mainSectionShown) {
return rpl::filter([=] {
return (_controller->mainSectionShown() == mainSectionShown);
});
};
rpl::merge(
Core::App().settings().dialogsWithChatWidthRatioChanges(
) | filter(true) | rpl::to_empty,
Core::App().settings().dialogsNoChatWidthRatioChanges(
) | filter(false) | rpl::to_empty,
Core::App().settings().thirdColumnWidthChanges() | rpl::to_empty
) | rpl::on_next([=] {
updateControlsGeometry();
}, lifetime());
session().changes().historyUpdates(
Data::HistoryUpdate::Flag::MessageSent
) | rpl::on_next([=](const Data::HistoryUpdate &update) {
const auto history = update.history;
history->forgetScrollState();
if (const auto from = history->peer->migrateFrom()) {
auto &owner = history->owner();
if (const auto migrated = owner.historyLoaded(from)) {
migrated->forgetScrollState();
}
}
}, lifetime());
session().changes().entryUpdates(
Data::EntryUpdate::Flag::LocalDraftSet
) | rpl::on_next([=](const Data::EntryUpdate &update) {
auto params = Window::SectionShow();
params.reapplyLocalDraft = true;
controller->showThread(
update.entry->asThread(),
ShowAtUnreadMsgId,
params);
controller->hideLayer();
}, lifetime());
// MSVC BUG + REGRESSION rpl::mappers::tuple :(
using namespace rpl::mappers;
_controller->activeChatValue(
) | rpl::map([](Dialogs::Key key) {
const auto peer = key.peer();
const auto topic = key.topic();
auto canWrite = topic
? Data::CanSendAnyOfValue(
topic,
Data::TabbedPanelSendRestrictions())
: peer
? Data::CanSendAnyOfValue(
peer, Data::TabbedPanelSendRestrictions())
: rpl::single(false);
return std::move(
canWrite
) | rpl::map([=](bool can) {
return std::make_tuple(key, can);
});
}) | rpl::flatten_latest(
) | rpl::on_next([this](Dialogs::Key key, bool canWrite) {
updateThirdColumnToCurrentChat(key, canWrite);
}, lifetime());
QCoreApplication::instance()->installEventFilter(this);
Media::Player::instance()->tracksFinished(
) | rpl::on_next([=](AudioMsgId::Type type) {
if (type == AudioMsgId::Type::Voice) {
const auto songState = Media::Player::instance()->getState(
AudioMsgId::Type::Song);
if (!songState.id || IsStoppedOrStopping(songState.state)) {
Media::Player::instance()->stopAndClose();
}
} else if (type == AudioMsgId::Type::Song) {
const auto songState = Media::Player::instance()->getState(
AudioMsgId::Type::Song);
if (!songState.id) {
Media::Player::instance()->stopAndClose();
}
}
}, lifetime());
_controller->adaptive().changes(
) | rpl::on_next([=] {
handleAdaptiveLayoutUpdate();
}, lifetime());
if (_dialogs) {
_dialogs->show();
}
if (_dialogs && isOneColumn()) {
_history->hide();
} else {
_history->show();
}
orderWidgets();
if (!Core::UpdaterDisabled()) {
Core::UpdateChecker checker;
checker.start();
}
cSetOtherOnline(0);
session().data().stickers().notifySavedGifsUpdated();
}
MainWidget::~MainWidget() {
if (_controller->activeChatCurrent()) {
session().api().saveCurrentDraftToCloud();
}
}
Main::Session &MainWidget::session() const {
return _controller->session();
}
not_null<Window::SessionController*> MainWidget::controller() const {
return _controller;
}
void MainWidget::setupConnectingWidget() {
using namespace rpl::mappers;
_connecting = std::make_unique<Window::ConnectionState>(
this,
&session().account(),
_controller->adaptive().oneColumnValue() | rpl::map(!_1));
_controller->connectingBottomSkipValue(
) | rpl::on_next([=](int skip) {
_connecting->setBottomSkip(skip);
}, lifetime());
}
not_null<Media::Player::FloatDelegate*> MainWidget::floatPlayerDelegate() {
return static_cast<Media::Player::FloatDelegate*>(this);
}
not_null<Ui::RpWidget*> MainWidget::floatPlayerWidget() {
return this;
}
void MainWidget::floatPlayerToggleGifsPaused(bool paused) {
constexpr auto kReason = Window::GifPauseReason::RoundPlaying;
if (paused) {
_controller->enableGifPauseReason(kReason);
} else {
_controller->disableGifPauseReason(kReason);
}
}
auto MainWidget::floatPlayerGetSection(Window::Column column)
-> not_null<Media::Player::FloatSectionDelegate*> {
if (isThreeColumn()) {
if (_dialogs && column == Window::Column::First) {
return _dialogs;
} else if (column == Window::Column::Second
|| !_dialogs
|| !_thirdSection) {
if (_mainSection) {
return _mainSection;
}
return _history;
}
return _thirdSection;
} else if (isNormalColumn()) {
if (_dialogs && column == Window::Column::First) {
return _dialogs;
} else if (_mainSection) {
return _mainSection;
}
return _history;
} else if (_mainSection) {
return _mainSection;
} else if (!isOneColumn() || _history->peer()) {
return _history;
}
Assert(_dialogs != nullptr);
return _dialogs;
}
void MainWidget::floatPlayerEnumerateSections(Fn<void(
not_null<Media::Player::FloatSectionDelegate*> widget,
Window::Column widgetColumn)> callback) {
if (isThreeColumn()) {
if (_dialogs) {
callback(_dialogs, Window::Column::First);
}
if (_mainSection) {
callback(_mainSection, Window::Column::Second);
} else {
callback(_history, Window::Column::Second);
}
if (_thirdSection) {
callback(_thirdSection, Window::Column::Third);
}
} else if (isNormalColumn()) {
if (_dialogs) {
callback(_dialogs, Window::Column::First);
}
if (_mainSection) {
callback(_mainSection, Window::Column::Second);
} else {
callback(_history, Window::Column::Second);
}
} else {
if (_mainSection) {
callback(_mainSection, Window::Column::Second);
} else if (!isOneColumn() || _history->peer()) {
callback(_history, Window::Column::Second);
} else {
Assert(_dialogs != nullptr);
callback(_dialogs, Window::Column::First);
}
}
}
bool MainWidget::floatPlayerIsVisible(not_null<HistoryItem*> item) {
return session().data().queryItemVisibility(item);
}
void MainWidget::floatPlayerClosed(FullMsgId itemId) {
if (_player) {
const auto voiceData = Media::Player::instance()->current(
AudioMsgId::Type::Voice);
if (voiceData.contextId() == itemId) {
stopAndClosePlayer();
}
}
}
void MainWidget::floatPlayerDoubleClickEvent(
not_null<const HistoryItem*> item) {
_controller->showMessage(item);
}
bool MainWidget::setForwardDraft(
not_null<Data::Thread*> thread,
Data::ForwardDraft &&draft) {
const auto history = thread->owningHistory();
const auto items = session().data().idsToItems(draft.ids);
const auto topicRootId = thread->topicRootId();
const auto monoforumPeerId = thread->monoforumPeerId();
const auto error = GetErrorForSending(
history->peer,
{
.topicRootId = topicRootId,
.forward = &items,
.ignoreSlowmodeCountdown = true,
});
// allow opening chat that
// already have some forward task
if (error && !AyuForward::isForwarding(history->peer->id)) {
Data::ShowSendErrorToast(_controller, history->peer, error);
return false;
}
history->setForwardDraft(topicRootId, monoforumPeerId, std::move(draft));
_controller->showThread(
thread,
ShowAtUnreadMsgId,
SectionShow::Way::Forward);
return true;
}
bool MainWidget::shareUrl(
not_null<Data::Thread*> thread,
const QString &url,
const QString &text) const {
if (!Data::CanSendTexts(thread)) {
_controller->show(Ui::MakeInformBox(tr::lng_share_cant()));
return false;
}
const auto textWithTags = TextWithTags{
url + '\n' + text,
TextWithTags::Tags()
};
const auto cursor = MessageCursor{
int(url.size()) + 1,
int(url.size()) + 1 + int(text.size()),
Ui::kQFixedMax
};
const auto history = thread->owningHistory();
const auto topicRootId = thread->topicRootId();
const auto monoforumPeerId = thread->monoforumPeerId();
history->setLocalDraft(std::make_unique<Data::Draft>(
textWithTags,
FullReplyTo{
.topicRootId = topicRootId,
.monoforumPeerId = monoforumPeerId,
},
SuggestOptions(),
cursor,
Data::WebPageDraft()));
history->clearLocalEditDraft(topicRootId, monoforumPeerId);
history->session().changes().entryUpdated(
thread,
Data::EntryUpdate::Flag::LocalDraftSet);
return true;
}
bool MainWidget::sendPaths(
not_null<Data::Thread*> thread,
const QStringList &paths) {
if (!Data::CanSendAnyOf(thread, Data::FilesSendRestrictions())) {
_controller->showToast(
tr::lng_forward_send_files_cant(tr::now));
return false;
} else if (const auto error = Data::AnyFileRestrictionError(
thread->peer())) {
Data::ShowSendErrorToast(controller(), thread->peer(), error);
return false;
} else {
_controller->showThread(
thread,
ShowAtTheEndMsgId,
Window::SectionShow::Way::ClearStack);
}
return (_controller->activeChatCurrent().thread() == thread)
&& (_mainSection
? _mainSection->confirmSendingFiles(paths)
: _history->confirmSendingFiles(paths));
}
bool MainWidget::filesOrForwardDrop(
not_null<Data::Thread*> thread,
not_null<const QMimeData*> data,
bool forumResolved) {
if (!forumResolved) {
if (const auto forum = thread->asForum()) {
Window::ShowDropMediaBox(
_controller,
Core::ShareMimeMediaData(data),
forum);
if (_hider) {
_hider->startHide();
clearHider(_hider);
}
return true;
} else if (const auto history = thread->asHistory()
; history && history->peer->monoforum()) {
Window::ShowDropMediaBox(
_controller,
Core::ShareMimeMediaData(data),
history->peer->monoforum());
if (_hider) {
_hider->startHide();
clearHider(_hider);
}
return true;
}
}
if (data->hasFormat(u"application/x-td-forward"_q)) {
auto draft = Data::ForwardDraft{
.ids = session().data().takeMimeForwardIds(),
};
if (setForwardDraft(thread, std::move(draft))) {
return true;
}
// We've already released the mouse button,
// so the forwarding is cancelled.
if (_hider) {
_hider->startHide();
clearHider(_hider);
}
return false;
} else if (!Data::CanSendAnyOf(thread, Data::FilesSendRestrictions())) {
_controller->showToast(
tr::lng_forward_send_files_cant(tr::now));
return false;
} else if (const auto error = Data::AnyFileRestrictionError(
thread->peer())) {
Data::ShowSendErrorToast(_controller, thread->peer(), error);
return false;
} else {
_controller->showThread(
thread,
ShowAtTheEndMsgId,
Window::SectionShow::Way::ClearStack);
if (_controller->activeChatCurrent().thread() != thread) {
return false;
}
(_mainSection
? _mainSection->confirmSendingFiles(data)
: _history->confirmSendingFiles(data));
return true;
}
}
bool MainWidget::notify_switchInlineBotButtonReceived(const QString &query, UserData *samePeerBot, MsgId samePeerReplyTo) {
return _history->notify_switchInlineBotButtonReceived(query, samePeerBot, samePeerReplyTo);
}
void MainWidget::clearHider(not_null<Window::HistoryHider*> instance) {
if (_hider != instance) {
return;
}
_hider.release();
}
void MainWidget::hiderLayer(base::unique_qptr<Window::HistoryHider> hider) {
if (!_dialogs || _controller->window().locked()) {
return;
}
_hider = std::move(hider);
_hider->setParent(this);
_hider->hidden(
) | rpl::on_next([=, instance = _hider.get()] {
clearHider(instance);
instance->hide();
instance->deleteLater();
}, _hider->lifetime());
_hider->show();
updateControlsGeometry();
_dialogs->setInnerFocus();
floatPlayerCheckVisibility();
}
void MainWidget::showDragForwardInfo() {
hiderLayer(base::make_unique_q<Window::HistoryHider>(
this,
tr::lng_forward_choose(tr::now)));
}
void MainWidget::hideDragForwardInfo() {
if (_hider) {
_hider->startHide();
_hider.release();
}
}
void MainWidget::sendBotCommand(Bot::SendCommandRequest request) {
const auto type = _mainSection
? _mainSection->sendBotCommand(request)
: Window::SectionActionResult::Fallback;
if (type == Window::SectionActionResult::Fallback) {
_controller->showPeerHistory(
request.peer,
SectionShow::Way::Forward,
ShowAtTheEndMsgId);
_history->sendBotCommand(request);
}
}
void MainWidget::hideSingleUseKeyboard(FullMsgId replyToId) {
_history->hideSingleUseKeyboard(replyToId);
}
void MainWidget::searchMessages(
const QString &query,
Dialogs::Key inChat,
PeerData *searchFrom) {
const auto complex = Data::HashtagWithUsernameFromQuery(query);
if (!complex.username.isEmpty()) {
_controller->showPeerByLink(Window::PeerByLinkInfo{
.usernameOrId = complex.username,
.text = complex.hashtag,
.resolveType = Window::ResolveType::HashtagSearch,
});
return;
}
auto tags = Data::SearchTagsFromQuery(query);
if (_dialogs
&& (!ForceComposeSearchOneColumn.value() || !isOneColumn())) {
auto state = Dialogs::SearchState{
.inChat = ((tags.empty() || inChat.sublist())
? inChat
: session().data().history(session().user())),
.fromPeer = inChat ? searchFrom : nullptr,
.tags = tags,
.query = tags.empty() ? query : QString(),
};
state.tab = state.defaultTabForMe();
_dialogs->searchMessages(std::move(state));
if (isOneColumn()) {
_controller->clearSectionStack();
} else {
_dialogs->setInnerFocus();
}
} else {
if (const auto sublist = inChat.sublist()) {
using namespace HistoryView;
controller()->showSection(
std::make_shared<ChatMemento>(ChatViewId{
.history = sublist->owningHistory(),
.sublist = sublist,
}));
} else if (!tags.empty()) {
inChat = controller()->session().data().history(
controller()->session().user());
}
if ((!_mainSection
|| !_mainSection->searchInChatEmbedded(query, inChat, searchFrom))
&& !_history->searchInChatEmbedded(query, inChat, searchFrom)) {
const auto account = not_null(&session().account());
if (const auto window = Core::App().windowFor(account)) {
if (const auto controller = window->sessionController()) {
controller->widget()->activate();
controller->content()->searchMessages(
query,
inChat,
searchFrom);
}
}
}
}
}
void MainWidget::handleAudioUpdate(const Media::Player::TrackState &state) {
using State = Media::Player::State;
const auto document = state.id.audio();
const auto item = session().data().message(state.id.contextId());
if (!Media::Player::IsStoppedOrStopping(state.state)) {
const auto ttlSeconds = item
&& item->media()
&& item->media()->ttlSeconds();
if (!ttlSeconds) {
createPlayer();
}
} else if (state.state == State::StoppedAtStart) {
Media::Player::instance()->stopAndClose();
}
if (item) {
session().data().requestItemRepaint(item);
}
if (document) {
if (const auto items = InlineBots::Layout::documentItems()) {
if (const auto i = items->find(document); i != items->end()) {
for (const auto &item : i->second) {
item->update();
}
}
}
}
}
void MainWidget::closeBothPlayers() {
if (_player) {
_player->hide(anim::type::normal);
}
_playerPlaylist->hideIgnoringEnterEvents();
}
void MainWidget::stopAndClosePlayer() {
if (_player) {
_player->entity()->stopAndClose();
}
}
void MainWidget::createPlayer() {
if (!_player) {
_player.create(
this,
object_ptr<Media::Player::Widget>(this, this, _controller),
_controller->adaptive().oneColumnValue());
rpl::merge(
_player->heightValue() | rpl::map_to(true),
_player->shownValue()
) | rpl::on_next(
[this] { playerHeightUpdated(); },
_player->lifetime());
_player->entity()->setCloseCallback([=] {
Media::Player::instance()->stopAndClose();
});
_player->entity()->setShowItemCallback([=](
not_null<const HistoryItem*> item) {
const auto peer = item->history()->peer;
if (const auto window = Core::App().windowFor(peer)) {
if (const auto controller = window->sessionController()) {
controller->showMessage(item);
return;
}
}
_controller->showMessage(item);
});
_player->entity()->togglePlaylistRequests(
) | rpl::on_next([=](bool shown) {
if (!shown) {
_playerPlaylist->hideFromOther();
return;
} else if (_playerPlaylist->isHidden()) {
auto position = mapFromGlobal(QCursor::pos()).x();
auto bestPosition = _playerPlaylist->bestPositionFor(position);
if (rtl()) bestPosition = position + 2 * (position - bestPosition) - _playerPlaylist->width();
updateMediaPlaylistPosition(bestPosition);
}
_playerPlaylist->showFromOther();
}, _player->lifetime());
orderWidgets();
if (_showAnimation) {
_player->show(anim::type::instant);
_player->setVisible(false);
Shortcuts::ToggleMediaShortcuts(true);
} else {
_player->hide(anim::type::instant);
}
}
if (_player && !_player->toggled()) {
if (!_showAnimation) {
_player->show(anim::type::normal);
_playerHeight = _contentScrollAddToY = _player->contentHeight();
updateControlsGeometry();
Shortcuts::ToggleMediaShortcuts(true);
}
}
}
void MainWidget::playerHeightUpdated() {
if (!_player) {
// Player could be already "destroyDelayed", but still handle events.
return;
}
auto playerHeight = _player->contentHeight();
if (playerHeight != _playerHeight) {
_contentScrollAddToY += playerHeight - _playerHeight;
_playerHeight = playerHeight;
updateControlsGeometry();
}
if (!_playerHeight && _player->isHidden()) {
const auto state = Media::Player::instance()->getState(Media::Player::instance()->getActiveType());
if (!state.id || Media::Player::IsStoppedOrStopping(state.state)) {
_player.destroyDelayed();
}
}
}
void MainWidget::setCurrentCall(Calls::Call *call) {
if (!call && _currentGroupCall) {
return;
}
_currentCallLifetime.destroy();
_currentCall = call;
if (call) {
_callTopBar.destroy();
call->stateValue(
) | rpl::on_next([=](Calls::Call::State state) {
using State = Calls::Call::State;
if (state != State::Established) {
destroyCallTopBar();
} else if (!_callTopBar) {
createCallTopBar(call, nullptr);
}
}, _currentCallLifetime);
} else {
destroyCallTopBar();
}
}
void MainWidget::setCurrentGroupCall(Calls::GroupCall *call) {
if (!call && _currentCall) {
return;
}
_currentCallLifetime.destroy();
_currentGroupCall = call;
if (call) {
_callTopBar.destroy();
_currentGroupCall->stateValue(
) | rpl::on_next([=](Calls::GroupCall::State state) {
using State = Calls::GroupCall::State;
if (state != State::Creating
&& state != State::Waiting
&& state != State::Joining
&& state != State::Joined
&& state != State::Connecting) {
destroyCallTopBar();
} else if (!_callTopBar) {
createCallTopBar(nullptr, call);
}
}, _currentCallLifetime);
} else {
destroyCallTopBar();
}
}
void MainWidget::createCallTopBar(
Calls::Call *call,
Calls::GroupCall *group) {
Expects(call || group);
const auto show = controller()->uiShow();
_callTopBar.create(
this,
(call
? object_ptr<Calls::TopBar>(this, call, show)
: object_ptr<Calls::TopBar>(this, group, show)));
_callTopBar->entity()->initBlobsUnder(this, _callTopBar->geometryValue());
_callTopBar->heightValue(
) | rpl::on_next([this](int value) {
callTopBarHeightUpdated(value);
}, lifetime());
orderWidgets();
if (_showAnimation) {
_callTopBar->show(anim::type::instant);
_callTopBar->setVisible(false);
} else {
_callTopBar->hide(anim::type::instant);
_callTopBar->show(anim::type::normal);
_callTopBarHeight = _contentScrollAddToY = _callTopBar->height();
updateControlsGeometry();
}
}
void MainWidget::destroyCallTopBar() {
if (_callTopBar) {
_callTopBar->hide(anim::type::normal);
}
}
void MainWidget::callTopBarHeightUpdated(int callTopBarHeight) {
if (!callTopBarHeight && !_currentCall && !_currentGroupCall) {
_callTopBar.destroyDelayed();
}
if (callTopBarHeight != _callTopBarHeight) {
_contentScrollAddToY += callTopBarHeight - _callTopBarHeight;
_callTopBarHeight = callTopBarHeight;
updateControlsGeometry();
}
}
void MainWidget::setCurrentExportView(Export::View::PanelController *view) {
_currentExportView = view;
if (_currentExportView) {
_currentExportView->progressState(
) | rpl::on_next([=](Export::View::Content &&data) {
if (!data.rows.empty()
&& data.rows[0].id == Export::View::Content::kDoneId) {
LOG(("Export Info: Destroy top bar by Done."));
destroyExportTopBar();
} else if (!_exportTopBar) {
LOG(("Export Info: Create top bar by State."));
createExportTopBar(std::move(data));
} else {
_exportTopBar->entity()->updateData(std::move(data));
}
}, _exportViewLifetime);
} else {
_exportViewLifetime.destroy();
LOG(("Export Info: Destroy top bar by controller removal."));
destroyExportTopBar();
}
}
void MainWidget::createExportTopBar(Export::View::Content &&data) {
_exportTopBar.create(
this,
object_ptr<Export::View::TopBar>(this, std::move(data)),
_controller->adaptive().oneColumnValue());
_exportTopBar->entity()->clicks(
) | rpl::on_next([=] {
if (_currentExportView) {
_currentExportView->activatePanel();
}
}, _exportTopBar->lifetime());
orderWidgets();
if (_showAnimation) {
_exportTopBar->show(anim::type::instant);
_exportTopBar->setVisible(false);
} else {
_exportTopBar->hide(anim::type::instant);
_exportTopBar->show(anim::type::normal);
_exportTopBarHeight = _contentScrollAddToY = _exportTopBar->contentHeight();
updateControlsGeometry();
}
rpl::merge(
_exportTopBar->heightValue() | rpl::map_to(true),
_exportTopBar->shownValue()
) | rpl::on_next([=] {
exportTopBarHeightUpdated();
}, _exportTopBar->lifetime());
}
void MainWidget::destroyExportTopBar() {
if (_exportTopBar) {
_exportTopBar->hide(anim::type::normal);
}
}
void MainWidget::exportTopBarHeightUpdated() {
if (!_exportTopBar) {
// Player could be already "destroyDelayed", but still handle events.
return;
}
const auto exportTopBarHeight = _exportTopBar->contentHeight();
if (exportTopBarHeight != _exportTopBarHeight) {
_contentScrollAddToY += exportTopBarHeight - _exportTopBarHeight;
_exportTopBarHeight = exportTopBarHeight;
updateControlsGeometry();
}
if (!_exportTopBarHeight && _exportTopBar->isHidden()) {
_exportTopBar.destroyDelayed();
}
}
SendMenu::Details MainWidget::sendMenuDetails() const {
return _history->sendMenuDetails();
}
void MainWidget::dialogsCancelled() {
if (_hider) {
_hider->startHide();
clearHider(_hider);
}
_history->activate();
}
void MainWidget::toggleFiltersMenu(bool value) const {
if (_dialogs) {
_dialogs->toggleFiltersMenu(value);
}
}
void MainWidget::setChatBackground(
const Data::WallPaper &background,
QImage &&image) {
using namespace Window::Theme;
if (isReadyChatBackground(background, image)) {
setReadyChatBackground(background, std::move(image));
return;
}
_background = std::make_unique<SettingBackground>(background);
if (const auto document = _background->data.document()) {
_background->dataMedia = document->createMediaView();
_background->dataMedia->thumbnailWanted(
_background->data.fileOrigin());
}
_background->data.loadDocument();
checkChatBackground();
const auto tile = Data::IsLegacy1DefaultWallPaper(background);
Window::Theme::Background()->downloadingStarted(tile);
}
bool MainWidget::isReadyChatBackground(
const Data::WallPaper &background,
const QImage &image) const {
return !image.isNull() || !background.document();
}
void MainWidget::setReadyChatBackground(
const Data::WallPaper &background,
QImage &&image) {
using namespace Window::Theme;
if (image.isNull()
&& !background.document()
&& background.localThumbnail()) {
image = background.localThumbnail()->original();
}
const auto resetToDefault = image.isNull()
&& !background.document()
&& background.backgroundColors().empty()
&& !Data::IsLegacy1DefaultWallPaper(background);
const auto ready = resetToDefault
? Data::DefaultWallPaper()
: background;
Background()->set(ready, std::move(image));
const auto tile = Data::IsLegacy1DefaultWallPaper(ready);
Background()->setTile(tile);
Ui::ForceFullRepaint(this);
}
bool MainWidget::chatBackgroundLoading() {
return (_background != nullptr);
}
float64 MainWidget::chatBackgroundProgress() const {
if (_background) {
if (_background->generating) {
return 1.;
} else if (_background->data.document()) {
return _background->dataMedia->progress();
}
}
return 1.;
}
void MainWidget::checkChatBackground() {
if (!_background || _background->generating) {
return;
}
const auto &media = _background->dataMedia;
Assert(media != nullptr);
if (!media->loaded()) {
return;
}
const auto document = _background->data.document();
Assert(document != nullptr);
const auto generateCallback = [=](QImage &&image) {
const auto background = base::take(_background);
const auto ready = image.isNull()
? Data::DefaultWallPaper()
: background->data;
setReadyChatBackground(ready, std::move(image));
};
_background->generating = Data::ReadBackgroundImageAsync(
media.get(),
Ui::PreprocessBackgroundImage,
generateCallback);
}
Image *MainWidget::newBackgroundThumb() {
return !_background
? nullptr
: _background->data.localThumbnail()
? _background->data.localThumbnail()
: _background->dataMedia
? _background->dataMedia->thumbnail()
: nullptr;
}
void MainWidget::setInnerFocus() {
const auto setTo = [&](auto &&widget) {
if (widget->isHidden()) {
// If we try setting focus inside a hidden widget, we may
// end up focusing search field in dialogs on window activation.
setFocus();
} else {
widget->setInnerFocus();
}
};
if (_dialogs && _dialogs->searchHasFocus()) {
setTo(_dialogs);
} else if (_hider || !_history->peer()) {
if (!_hider && _mainSection) {
setTo(_mainSection);
} else if (!_hider && _thirdSection) {
setTo(_thirdSection);
} else if (_dialogs) {
setTo(_dialogs);
} else {
// Maybe we're just closing a child window, content is destroyed.
_history->setFocus();
}
} else if (_mainSection) {
setTo(_mainSection);
} else {
setTo(_history);
}
}
void MainWidget::showChooseReportMessages(
not_null<PeerData*> peer,
Data::ReportInput reportInput,
Fn<void(std::vector<MsgId>)> done) {
_history->setChooseReportMessagesDetails(reportInput, std::move(done));
_controller->showPeerHistory(
peer,
SectionShow::Way::Forward,
ShowForChooseMessagesMsgId);
controller()->showToast(tr::lng_report_please_select_messages(tr::now));
}
void MainWidget::clearChooseReportMessages() {
_history->setChooseReportMessagesDetails({}, nullptr);
}
void MainWidget::toggleChooseChatTheme(
not_null<PeerData*> peer,
std::optional<bool> show) {
_history->toggleChooseChatTheme(peer, show);
}
bool MainWidget::showHistoryInDifferentWindow(
PeerId peerId,
const SectionShow &params,
MsgId showAtMsgId) {
if (!peerId) {
// In case we don't have dialogs, we can't clear section stack.
return !_dialogs;
}
const auto peer = session().data().peer(peerId);
if (const auto separateChat = _controller->windowId().chat()) {
if (const auto history = separateChat->asHistory()) {
if (history->peer == peer) {
return false;
}
}
}
const auto window = Core::App().windowForShowingHistory(peer);
if (window == &_controller->window()) {
return false;
} else if (window) {
window->sessionController()->showPeerHistory(
peerId,
params,
showAtMsgId);
window->activate();
return true;
} else if (windowId().hasChatsList()) {
return false;
}
const auto account = not_null(&session().account());
auto primary = Core::App().separateWindowFor(account);
if (!primary) {
Core::App().domain().activate(account);
primary = Core::App().separateWindowFor(account);
}
if (primary && &primary->account() == account) {
primary->sessionController()->showPeerHistory(
peerId,
params,
showAtMsgId);
primary->activate();
}
return true;
}
void MainWidget::showHistory(
PeerId peerId,
const SectionShow &params,
MsgId showAtMsgId) {
if (peerId && _controller->window().locked()) {
if (params.activation != anim::activation::background) {
_controller->window().activate();
}
return;
} else if (auto peer = session().data().peerLoaded(peerId)) {
if (peer->migrateTo()) {
peer = peer->migrateTo();
peerId = peer->id;
if (showAtMsgId > 0) {
showAtMsgId = -showAtMsgId;
}
}
const auto unavailable = peer->computeUnavailableReason();
if (!unavailable.isEmpty()) {
if (!isPrimary()) {
_controller->window().close();
} else if (params.activation != anim::activation::background) {
_controller->show(Ui::MakeInformBox(unavailable));
_controller->window().activate();
}
return;
}
}
if ((IsServerMsgId(showAtMsgId) || Data::IsScheduledMsgId(showAtMsgId))
&& _mainSection
&& _mainSection->showMessage(peerId, params, showAtMsgId)) {
session().data().hideShownSpoilers();
if (params.activation != anim::activation::background) {
_controller->window().activate();
}
return;
} else if (showHistoryInDifferentWindow(peerId, params, showAtMsgId)) {
return;
}
if (peerId && params.activation != anim::activation::background) {
Core::App().hideMediaView();
_controller->window().activate();
}
const auto alreadyThatPeer = _history->peer()
&& (_history->peer()->id == peerId);
if (!alreadyThatPeer
&& preventsCloseSection(
[=] { showHistory(peerId, params, showAtMsgId); },
params)) {
return;
}
using OriginMessage = SectionShow::OriginMessage;
if (const auto origin = std::get_if<OriginMessage>(&params.origin)) {
if (const auto returnTo = session().data().message(origin->id)) {
if (returnTo->history()->peer->id == peerId) {
_history->pushReplyReturn(returnTo);
}
}
}
_a_dialogsWidth.stop();
using Way = SectionShow::Way;
auto way = params.way;
bool back = (way == Way::Backward || !peerId);
bool foundInStack = !peerId;
if (foundInStack || (way == Way::ClearStack)) {
for (const auto &item : _stack) {
ClearBotStartToken(item->peer());
}
_stack.clear();
} else {
if (!params.allowDuplicateInStack) {
for (auto i = 0, s = int(_stack.size()); i < s; ++i) {
if (_stack.at(i)->type() == HistoryStackItem
&& _stack.at(i)->peer()->id == peerId) {
foundInStack = true;
while (int(_stack.size()) > i + 1) {
ClearBotStartToken(_stack.back()->peer());
_stack.pop_back();
}
_stack.pop_back();
if (!back) {
back = true;
}
break;
}
}
}
if (const auto activeChat = _controller->activeChatCurrent()) {
if (const auto peer = activeChat.peer()) {
if (way == Way::Forward && peer->id == peerId) {
way = _mainSection ? Way::Backward : Way::ClearStack;
}
}
}
}
const auto wasActivePeer = _controller->activeChatCurrent().peer();
if (params.activation != anim::activation::background) {
_controller->window().hideSettingsAndLayer();
}
auto animatedShow = [&] {
if (_showAnimation
|| Core::App().passcodeLocked()
|| (params.animated == anim::type::instant)) {
return false;
}
if (!peerId) {
if (isOneColumn()) {
return _dialogs && _dialogs->isHidden();
} else {
return false;
}
}
if (_history->isHidden()) {
if (!isOneColumn() && way == Way::ClearStack) {
return false;
}
return (_mainSection != nullptr)
|| (isOneColumn() && _dialogs && !_dialogs->isHidden());
}
if (back || way == Way::Forward) {
return true;
}
return false;
};
auto animationParams = animatedShow()
? prepareHistoryAnimation(peerId)
: Window::SectionSlideParams();
if (!back && (way != Way::ClearStack)) {
// This may modify the current section, for example remove its contents.
saveSectionInStack(params);
}
if (_history->peer()
&& _history->peer()->id != peerId
&& way != Way::Forward) {
ClearBotStartToken(_history->peer());
}
_history->showHistory(peerId, showAtMsgId, params);
if (alreadyThatPeer && params.reapplyLocalDraft) {
_history->applyDraft(HistoryWidget::FieldHistoryAction::NewEntry);
}
auto noPeer = !_history->peer();
auto onlyDialogs = noPeer && isOneColumn();
_mainSection.destroy();
updateMainSectionShown();
updateControlsGeometry();
if (noPeer) {
_controller->setActiveChatEntry(Dialogs::Key());
_controller->setChatStyleTheme(_controller->defaultChatTheme());
}
if (onlyDialogs) {
Assert(_dialogs != nullptr);
_history->hide();
if (!_showAnimation) {
if (animationParams) {
auto direction = back ? Window::SlideDirection::FromLeft : Window::SlideDirection::FromRight;
_dialogs->showAnimated(direction, animationParams);
} else {
_dialogs->showFast();
}
}
} else {
const auto nowActivePeer = _controller->activeChatCurrent().peer();
if (nowActivePeer && nowActivePeer != wasActivePeer) {
session().api().views().removeIncremented(nowActivePeer);
}
if (isOneColumn() && _dialogs && !_dialogs->isHidden()) {
_dialogs->hide();
}
if (!_showAnimation) {
if (!animationParams.oldContentCache.isNull()) {
_history->showAnimated(
back
? Window::SlideDirection::FromLeft
: Window::SlideDirection::FromRight,
animationParams);
} else {
_history->showFast();
crl::on_main(this, [=] {
_controller->widget()->setInnerFocus();
});
}
}
}
if (_dialogs && !_dialogs->isHidden()) {
if (!back) {
if (const auto history = _history->history()) {
_dialogs->scrollToEntry(Dialogs::RowDescriptor(
history,
FullMsgId(history->peer->id, showAtMsgId)));
}
}
_dialogs->update();
}
floatPlayerCheckVisibility();
controller()->dropSubsectionTabs();
}
bool MainWidget::handleDrawToReplyRequest(Data::DrawToReplyRequest request) {
if (_mainSection) {
using namespace HistoryView;
if (const auto с = dynamic_cast<ChatWidget*>(_mainSection.data())) {
return с->handleDrawToReplyRequest(std::move(request));
}
return false;
}
return _history->handleDrawToReplyRequest(std::move(request));
}
void MainWidget::showMessage(
not_null<const HistoryItem*> item,
const SectionShow &params) {
const auto peerId = item->history()->peer->id;
const auto itemId = item->id;
if (!v::is_null(params.origin)) {
if (_mainSection) {
if (_mainSection->showMessage(peerId, params, itemId)) {
if (params.activation != anim::activation::background) {
_controller->window().activate();
_controller->window().hideSettingsAndLayer();
}
return;
}
} else if (_history->peer() == item->history()->peer) {
// showHistory may be redirected to different window,
// so we don't call activate() on current controller's window.
showHistory(peerId, params, itemId);
return;
}
}
if (const auto topic = item->topic()) {
_controller->showTopic(topic, item->id, params);
if (params.activation != anim::activation::background) {
_controller->window().activate();
}
} else if (const auto sublist = item->savedSublist()
; sublist && sublist->parentChat()) {
_controller->showSublist(sublist, item->id, params);
if (params.activation != anim::activation::background) {
_controller->window().activate();
}
} else {
// showPeerHistory may be redirected to different window,
// so we don't call activate() on current controller's window.
_controller->showPeerHistory(
item->history(),
params,
item->id);
}
}
void MainWidget::showForum(
not_null<Data::Forum*> forum,
const SectionShow &params) {
Expects(_dialogs != nullptr);
_dialogs->showForum(forum, params);
if (params.activation != anim::activation::background) {
_controller->window().hideSettingsAndLayer();
}
}
PeerData *MainWidget::peer() const {
return _history->peer();
}
Ui::ChatTheme *MainWidget::customChatTheme() const {
return _history->customChatTheme();
}
bool MainWidget::saveSectionInStack(
const SectionShow &params,
Window::SectionWidget *newMainSection) {
if (_mainSection) {
if (auto memento = _mainSection->createMemento()) {
if (params.dropSameFromStack
&& newMainSection
&& newMainSection->sameTypeAs(memento.get())) {
// When choosing saved sublist we want to save the original
// "Saved Messages" in the stack, but don't save every
// sublist in a new stack entry when clicking them through.
return false;
}
_stack.push_back(std::make_unique<StackItemSection>(
std::move(memento)));
} else {
return false;
}
} else if (const auto history = _history->history()) {
_stack.push_back(std::make_unique<StackItemHistory>(
history,
_history->msgId(),
_history->replyReturns()));
} else {
// We pretend that we "saved" the chats list state in stack,
// so that we do animate a transition from chats list to a section.
return true;
}
const auto raw = _stack.back().get();
raw->setThirdSectionWeak(_thirdSection.data());
raw->removeRequests(
) | rpl::on_next([=] {
for (auto i = begin(_stack); i != end(_stack); ++i) {
if (i->get() == raw) {
_stack.erase(i);
return;
}
}
}, raw->lifetime());
return true;
}
void MainWidget::showSection(
std::shared_ptr<Window::SectionMemento> memento,
const SectionShow &params) {
if (_mainSection && _mainSection->showInternal(
memento.get(),
params)) {
if (params.activation != anim::activation::background) {
_controller->window().hideSettingsAndLayer();
}
if (const auto entry = _mainSection->activeChat(); entry.key) {
_controller->setActiveChatEntry(entry);
}
return;
//
// Now third section handles only its own showSection() requests.
// General showSection() should show layer or main_section instead.
//
//} else if (_thirdSection && _thirdSection->showInternal(
// &memento,
// params)) {
// return;
}
if (preventsCloseSection(
[=] { showSection(memento, params); },
params)) {
return;
}
// If the window was not resized, but we've enabled
// tabbedSelectorSectionEnabled or thirdSectionInfoEnabled
// we need to update adaptive layout to Adaptive::ThirdColumn().
updateColumnLayout();
showNewSection(std::move(memento), params);
}
void MainWidget::updateColumnLayout() {
updateWindowAdaptiveLayout();
}
Window::SectionSlideParams MainWidget::prepareThirdSectionAnimation(Window::SectionWidget *section) {
Expects(_thirdSection != nullptr);
Window::SectionSlideParams result;
result.withTopBarShadow = section->hasTopBarShadow();
if (!_thirdSection->hasTopBarShadow()) {
result.withTopBarShadow = false;
}
floatPlayerHideAll();
result.oldContentCache = _thirdSection->grabForShowAnimation(result);
floatPlayerShowVisible();
return result;
}
Window::SectionSlideParams MainWidget::prepareShowAnimation(
bool willHaveTopBarShadow) {
Window::SectionSlideParams result;
result.withTopBarShadow = willHaveTopBarShadow;
if (_mainSection) {
if (!_mainSection->hasTopBarShadow()) {
result.withTopBarShadow = false;
}
} else if (!_history->peer()) {
result.withTopBarShadow = false;
}
floatPlayerHideAll();
if (_player) {
_player->entity()->hideShadowAndDropdowns();
}
const auto playerPlaylistVisible = !_playerPlaylist->isHidden();
if (playerPlaylistVisible) {
_playerPlaylist->hide();
}
const auto hiderVisible = (_hider && !_hider->isHidden());
if (hiderVisible) {
_hider->hide();
}
auto sectionTop = getMainSectionTop();
if (_mainSection) {
result.oldContentCache = _mainSection->grabForShowAnimation(result);
} else if (!isOneColumn() || !_history->isHidden()) {
result.oldContentCache = _history->grabForShowAnimation(result);
} else {
result.oldContentCache = Ui::GrabWidget(this, QRect(
0,
sectionTop,
_dialogsWidth,
height() - sectionTop));
}
if (_hider && hiderVisible) {
_hider->show();
}
if (playerPlaylistVisible) {
_playerPlaylist->show();
}
if (_player) {
_player->entity()->showShadowAndDropdowns();
}
floatPlayerShowVisible();
return result;
}
Window::SectionSlideParams MainWidget::prepareMainSectionAnimation(Window::SectionWidget *section) {
return prepareShowAnimation(section->hasTopBarShadow());
}
Window::SectionSlideParams MainWidget::prepareHistoryAnimation(PeerId historyPeerId) {
return prepareShowAnimation(historyPeerId != 0);
}
Window::SectionSlideParams MainWidget::prepareDialogsAnimation() {
return prepareShowAnimation(false);
}
void MainWidget::showNewSection(
std::shared_ptr<Window::SectionMemento> memento,
const SectionShow &params) {
using Column = Window::Column;
if (_controller->window().locked()) {
return;
}
auto saveInStack = (params.way == SectionShow::Way::Forward);
const auto thirdSectionTop = getThirdSectionTop();
const auto newThirdGeometry = QRect(
width() - st::columnMinimalWidthThird,
thirdSectionTop,
st::columnMinimalWidthThird,
height() - thirdSectionTop);
auto newThirdSection = (isThreeColumn() && params.thirdColumn)
? memento->createWidget(
this,
_controller,
Column::Third,
newThirdGeometry)
: nullptr;
const auto layerRect = parentWidget()->rect();
if (newThirdSection) {
saveInStack = false;
} else if (auto layer = memento->createLayer(_controller, layerRect)) {
if (params.activation != anim::activation::background) {
_controller->hideLayer(anim::type::instant);
}
_controller->showSpecialLayer(std::move(layer));
return;
}
if (params.activation != anim::activation::background) {
_controller->window().hideSettingsAndLayer();
}
_a_dialogsWidth.stop();
auto mainSectionTop = getMainSectionTop();
auto newMainGeometry = QRect(
_history->x(),
mainSectionTop,
_history->width(),
height() - mainSectionTop);
auto newMainSection = newThirdSection
? nullptr
: memento->createWidget(
this,
_controller,
isOneColumn() ? Column::First : Column::Second,
newMainGeometry);
Assert(newMainSection || newThirdSection);
auto animatedShow = [&] {
if (_showAnimation
|| Core::App().passcodeLocked()
|| (params.animated == anim::type::instant)
|| memento->instant()) {
return false;
}
if (!isOneColumn() && params.way == SectionShow::Way::ClearStack) {
return false;
} else if (isOneColumn()
|| (newThirdSection && _thirdSection)
|| (newMainSection && isMainSectionShown())) {
return true;
}
return false;
}();
auto animationParams = animatedShow
? (newThirdSection
? prepareThirdSectionAnimation(newThirdSection)
: prepareMainSectionAnimation(newMainSection))
: Window::SectionSlideParams();
setFocus(); // otherwise dialogs widget could be focused.
if (saveInStack) {
// This may modify the current section, for example remove its contents.
if (!saveSectionInStack(params, newMainSection)) {
saveInStack = false;
animatedShow = false;
animationParams = Window::SectionSlideParams();
}
}
auto &settingSection = newThirdSection
? _thirdSection
: _mainSection;
if (newThirdSection) {
_thirdSection = std::move(newThirdSection);
_thirdSection->removeRequests(
) | rpl::on_next([=] {
destroyThirdSection();
_thirdShadow.destroy();
updateControlsGeometry();
}, _thirdSection->lifetime());
if (!_thirdShadow) {
_thirdShadow.create(this);
_thirdShadow->show();
orderWidgets();
}
updateControlsGeometry();
} else {
_mainSection = std::move(newMainSection);
_history->finishAnimating();
_history->showHistory(PeerId(), MsgId());
if (const auto entry = _mainSection->activeChat(); entry.key) {
_controller->setActiveChatEntry(entry);
}
updateMainSectionShown();
// Depends on SessionController::activeChatEntry
// for tabbed selector showing in the third column.
updateControlsGeometry();
_history->hide();
if (isOneColumn() && _dialogs) {
_dialogs->hide();
}
}
if (animationParams) {
auto back = (params.way == SectionShow::Way::Backward);
auto direction = (back || settingSection->forceAnimateBack())
? Window::SlideDirection::FromLeft
: Window::SlideDirection::FromRight;
if (isOneColumn()) {
_controller->removeLayerBlackout();
}
settingSection->showAnimated(direction, animationParams);
} else {
settingSection->showFast();
}
floatPlayerCheckVisibility();
orderWidgets();
}
void MainWidget::checkMainSectionToLayer() {
if (!_mainSection) {
return;
}
Ui::FocusPersister persister(this);
if (auto layer = _mainSection->moveContentToLayer(rect())) {
_mainSection.destroy();
_controller->showBackFromStack(
SectionShow(
anim::type::instant,
anim::activation::background));
_controller->showSpecialLayer(
std::move(layer),
anim::type::instant);
}
updateMainSectionShown();
}
Window::SeparateId MainWidget::windowId() const {
return _controller->windowId();
}
bool MainWidget::isPrimary() const {
return _controller->isPrimary();
}
bool MainWidget::isMainSectionShown() const {
return _mainSection || _history->peer();
}
bool MainWidget::isThirdSectionShown() const {
return _thirdSection != nullptr;
}
Dialogs::RowDescriptor MainWidget::resolveChatNext(
Dialogs::RowDescriptor from) const {
return _dialogs ? _dialogs->resolveChatNext(from) : Dialogs::RowDescriptor();
}
Dialogs::RowDescriptor MainWidget::resolveChatPrevious(
Dialogs::RowDescriptor from) const {
return _dialogs ? _dialogs->resolveChatPrevious(from) : Dialogs::RowDescriptor();
}
bool MainWidget::stackIsEmpty() const {
return _stack.empty();
}
bool MainWidget::preventsCloseSection(Fn<void()> callback) const {
if (Core::App().passcodeLocked()) {
return false;
}
auto copy = callback;
return (_mainSection && _mainSection->preventsClose(std::move(copy)))
|| (_history && _history->preventsClose(std::move(callback)));
}
bool MainWidget::preventsCloseSection(
Fn<void()> callback,
const SectionShow &params) const {
return !params.thirdColumn
&& (params.activation != anim::activation::background)
&& preventsCloseSection(std::move(callback));
}
void MainWidget::showNonPremiumLimitToast(bool download) {
const auto parent = _mainSection
? ((QWidget*)_mainSection.data())
: (_dialogs && _history->isHidden())
? ((QWidget*)_dialogs.get())
: ((QWidget*)_history.get());
const auto link = download
? tr::lng_limit_download_subscribe_link(tr::now)
: tr::lng_limit_upload_subscribe_link(tr::now);
const auto better = session().appConfig().get<double>(download
? u"upload_premium_speedup_download"_q
: u"upload_premium_speedup_upload"_q, 10.);
const auto percent = int(base::SafeRound(better * 100.));
if (percent <= 100) {
return;
}
const auto increase = ((percent % 100) || percent <= 400)
? (download
? tr::lng_limit_download_increase_speed
: tr::lng_limit_upload_increase_speed)(
tr::now,
lt_percent,
TextWithEntities{ QString::number(percent - 100) },
tr::rich)
: (download
? tr::lng_limit_download_increase_times
: tr::lng_limit_upload_increase_times)(
tr::now,
lt_count,
percent / 100,
tr::rich);
auto text = (download
? tr::lng_limit_download_subscribe
: tr::lng_limit_upload_subscribe)(
tr::now,
lt_link,
tr::link(tr::bold(link)),
lt_increase,
TextWithEntities{ increase },
tr::rich);
auto filter = [=](ClickHandlerPtr handler, Qt::MouseButton button) {
Settings::ShowPremium(
controller(),
download ? u"download_limit"_q : u"upload_limit"_q);
return false;
};
Ui::Toast::Show(parent, {
.title = (download
? tr::lng_limit_download_title
: tr::lng_limit_upload_title)(tr::now),
.text = std::move(text),
.filter = std::move(filter),
.attach = RectPart::Top,
.duration = 5 * crl::time(1000),
});
}
bool MainWidget::showBackFromStack(const SectionShow &params) {
if (preventsCloseSection([=] { showBackFromStack(params); }, params)) {
return false;
}
if (_stack.empty()) {
if (_dialogs) {
_controller->clearSectionStack(params);
}
crl::on_main(this, [=] {
_controller->widget()->setInnerFocus();
});
return (_dialogs != nullptr);
}
session().api().saveCurrentDraftToCloud();
auto item = std::move(_stack.back());
_stack.pop_back();
if (const auto currentHistoryPeer = _history->peer()) {
ClearBotStartToken(currentHistoryPeer);
}
_thirdSectionFromStack = item->takeThirdSectionMemento();
if (item->type() == HistoryStackItem) {
auto historyItem = static_cast<StackItemHistory*>(item.get());
_controller->showPeerHistory(
historyItem->peer()->id,
params.withWay(SectionShow::Way::Backward),
ShowAtUnreadMsgId);
_history->setReplyReturns(
historyItem->peer()->id,
std::move(historyItem->replyReturns));
} else if (item->type() == SectionStackItem) {
auto sectionItem = static_cast<StackItemSection*>(item.get());
showNewSection(
sectionItem->takeMemento(),
params.withWay(SectionShow::Way::Backward));
}
if (_thirdSectionFromStack && _thirdSection) {
_controller->showSection(
base::take(_thirdSectionFromStack),
SectionShow(
SectionShow::Way::ClearStack,
anim::type::instant,
anim::activation::background));
}
return true;
}
void MainWidget::orderWidgets() {
if (_dialogs) {
_dialogs->raiseWithTooltip();
}
if (_player) {
_player->raise();
}
if (_exportTopBar) {
_exportTopBar->raise();
}
if (_callTopBar) {
_callTopBar->raise();
}
if (_sideShadow) {
_sideShadow->raise();
}
if (_thirdShadow) {
_thirdShadow->raise();
}
if (_firstColumnResizeArea) {
_firstColumnResizeArea->raise();
}
if (_thirdColumnResizeArea) {
_thirdColumnResizeArea->raise();
}
if (_connecting) {
_connecting->raise();
}
floatPlayerRaiseAll();
_playerPlaylist->raise();
if (_player) {
_player->entity()->raiseDropdowns();
}
if (_hider) _hider->raise();
}
QPixmap MainWidget::grabForShowAnimation(const Window::SectionSlideParams &params) {
QPixmap result;
floatPlayerHideAll();
if (_player) {
_player->entity()->hideShadowAndDropdowns();
}
const auto playerPlaylistVisible = !_playerPlaylist->isHidden();
if (playerPlaylistVisible) {
_playerPlaylist->hide();
}
const auto hiderVisible = (_hider && !_hider->isHidden());
if (hiderVisible) {
_hider->hide();
}
auto sectionTop = getMainSectionTop();
if (isOneColumn()) {
result = Ui::GrabWidget(this, QRect(
0,
sectionTop,
width(),
height() - sectionTop));
} else {
if (_sideShadow) {
_sideShadow->hide();
}
if (_thirdShadow) {
_thirdShadow->hide();
}
result = Ui::GrabWidget(this, QRect(
_dialogsWidth,
sectionTop,
width() - _dialogsWidth,
height() - sectionTop));
if (_sideShadow) {
_sideShadow->show();
}
if (_thirdShadow) {
_thirdShadow->show();
}
}
if (_hider && hiderVisible) {
_hider->show();
}
if (playerPlaylistVisible) {
_playerPlaylist->show();
}
if (_player) {
_player->entity()->showShadowAndDropdowns();
}
floatPlayerShowVisible();
return result;
}
void MainWidget::windowShown() {
_history->windowShown();
}
void MainWidget::dialogsToUp() {
if (_dialogs) {
_dialogs->jumpToTop();
}
}
void MainWidget::checkActivation() {
_history->checkActivation();
if (_mainSection) {
_mainSection->checkActivation();
}
}
void MainWidget::showAnimated(QPixmap oldContentCache, bool back) {
_showAnimation = nullptr;
showAll();
floatPlayerHideAll();
auto newContentCache = Ui::GrabWidget(this);
hideAll();
floatPlayerShowVisible();
_showAnimation = std::make_unique<Window::SlideAnimation>();
_showAnimation->setDirection(back
? Window::SlideDirection::FromLeft
: Window::SlideDirection::FromRight);
_showAnimation->setRepaintCallback([=] { update(); });
_showAnimation->setFinishedCallback([=] { showFinished(); });
_showAnimation->setPixmaps(oldContentCache, newContentCache);
_showAnimation->start();
show();
}
void MainWidget::showFinished() {
_showAnimation = nullptr;
showAll();
activate();
}
void MainWidget::paintEvent(QPaintEvent *e) {
if (_background) {
checkChatBackground();
}
if (_showAnimation) {
auto p = QPainter(this);
_showAnimation->paintContents(p);
}
}
int MainWidget::getMainSectionTop() const {
return _callTopBarHeight + _exportTopBarHeight + _playerHeight;
}
int MainWidget::getThirdSectionTop() const {
return 0;
}
void MainWidget::hideAll() {
if (_dialogs) {
_dialogs->hide();
}
_history->hide();
if (_mainSection) {
_mainSection->hide();
}
if (_thirdSection) {
_thirdSection->hide();
}
if (_sideShadow) {
_sideShadow->hide();
}
if (_thirdShadow) {
_thirdShadow->hide();
}
if (_player) {
_player->setVisible(false);
_playerHeight = 0;
}
if (_callTopBar) {
_callTopBar->setVisible(false);
_callTopBarHeight = 0;
}
}
void MainWidget::showAll() {
if (cPasswordRecovered()) {
cSetPasswordRecovered(false);
_controller->show(Ui::MakeInformBox(
tr::lng_cloud_password_updated()));
}
if (isOneColumn()) {
if (_sideShadow) {
_sideShadow->hide();
}
if (_hider) {
_hider->hide();
}
if (_mainSection) {
_mainSection->show();
} else if (_history->peer()) {
_history->show();
_history->updateControlsGeometry();
} else {
Assert(_dialogs != nullptr);
_dialogs->showFast();
_history->hide();
}
if (_dialogs && isMainSectionShown()) {
_dialogs->hide();
}
} else {
if (_sideShadow) {
_sideShadow->show();
}
if (_hider) {
_hider->show();
}
if (_dialogs) {
_dialogs->showFast();
}
if (_mainSection) {
_mainSection->show();
} else {
_history->show();
_history->updateControlsGeometry();
}
if (_thirdSection) {
_thirdSection->show();
}
if (_thirdShadow) {
_thirdShadow->show();
}
}
if (_player) {
_player->setVisible(true);
_playerHeight = _player->contentHeight();
}
if (_callTopBar) {
_callTopBar->setVisible(true);
// show() could've send pending resize event that would update
// the height value and destroy the top bar if it was hiding.
if (_callTopBar) {
_callTopBarHeight = _callTopBar->height();
}
}
updateControlsGeometry();
floatPlayerCheckVisibility();
_controller->widget()->checkActivation();
}
void MainWidget::resizeEvent(QResizeEvent *e) {
updateControlsGeometry();
}
void MainWidget::updateMainSectionShown() {
_controller->setMainSectionShown(_mainSection || _history->peer());
}
void MainWidget::updateControlsGeometry() {
if (!width()) {
return;
}
updateWindowAdaptiveLayout();
if (_dialogs) {
const auto nochat = !_controller->mainSectionShown();
if (Core::App().settings().dialogsWidthRatio(nochat) > 0) {
_a_dialogsWidth.stop();
}
if (!_a_dialogsWidth.animating()) {
_dialogs->stopWidthAnimation();
}
}
if (isThreeColumn()) {
if (!_thirdSection
&& !_controller->takeThirdSectionFromLayer()) {
auto params = Window::SectionShow(
Window::SectionShow::Way::ClearStack,
anim::type::instant,
anim::activation::background);
const auto active = _controller->activeChatCurrent();
if (const auto thread = active.thread()) {
if (Core::App().settings().tabbedSelectorSectionEnabled()) {
if (_mainSection) {
_mainSection->pushTabbedSelectorToThirdSection(
thread,
params);
} else {
_history->pushTabbedSelectorToThirdSection(
thread,
params);
}
} else if (Core::App().settings().thirdSectionInfoEnabled()) {
_controller->showSection(
(thread->asTopic()
? std::make_shared<Info::Memento>(
thread->asTopic())
: thread->asSublist()
? std::make_shared<Info::Memento>(
thread->asSublist())
: Info::Memento::Default(
thread->asHistory()->peer)),
params.withThirdColumn());
}
}
}
} else {
destroyThirdSection();
_thirdShadow.destroy();
}
const auto mainSectionTop = getMainSectionTop();
auto dialogsWidth = _dialogs
? qRound(_a_dialogsWidth.value(_dialogsWidth))
: isOneColumn()
? width()
: 0;
if (isOneColumn()) {
if (_callTopBar) {
_callTopBar->resizeToWidth(dialogsWidth);
_callTopBar->moveToLeft(0, 0);
}
if (_exportTopBar) {
_exportTopBar->resizeToWidth(dialogsWidth);
_exportTopBar->moveToLeft(0, _callTopBarHeight);
}
if (_player) {
_player->resizeToWidth(dialogsWidth);
_player->moveToLeft(0, _callTopBarHeight + _exportTopBarHeight);
}
const auto mainSectionGeometry = QRect(
0,
mainSectionTop,
dialogsWidth,
height() - mainSectionTop);
if (_dialogs) {
_dialogs->setGeometryWithTopMoved(
mainSectionGeometry,
_contentScrollAddToY);
}
_history->setGeometryWithTopMoved(
mainSectionGeometry,
_contentScrollAddToY);
if (_hider) _hider->setGeometry(0, 0, dialogsWidth, height());
} else {
auto thirdSectionWidth = _thirdSection ? _thirdColumnWidth : 0;
if (_thirdSection) {
auto thirdSectionTop = getThirdSectionTop();
_thirdSection->setGeometry(
width() - thirdSectionWidth,
thirdSectionTop,
thirdSectionWidth,
height() - thirdSectionTop);
}
const auto shadowTop = _controller->window().verticalShadowTop();
const auto shadowHeight = height() - shadowTop;
if (_dialogs) {
accumulate_min(
dialogsWidth,
width() - st::columnMinimalWidthMain);
_dialogs->setGeometryToLeft(0, 0, dialogsWidth, height());
}
if (_sideShadow) {
_sideShadow->setGeometryToLeft(
dialogsWidth,
shadowTop,
st::lineWidth,
shadowHeight);
}
if (_thirdShadow) {
_thirdShadow->setGeometryToLeft(
width() - thirdSectionWidth - st::lineWidth,
shadowTop,
st::lineWidth,
shadowHeight);
}
const auto mainSectionWidth = width()
- dialogsWidth
- thirdSectionWidth;
if (_callTopBar) {
_callTopBar->resizeToWidth(mainSectionWidth);
_callTopBar->moveToLeft(dialogsWidth, 0);
}
if (_exportTopBar) {
_exportTopBar->resizeToWidth(mainSectionWidth);
_exportTopBar->moveToLeft(dialogsWidth, _callTopBarHeight);
}
if (_player) {
_player->resizeToWidth(mainSectionWidth);
_player->moveToLeft(
dialogsWidth,
_callTopBarHeight + _exportTopBarHeight);
}
_history->setGeometryWithTopMoved(QRect(
dialogsWidth,
mainSectionTop,
mainSectionWidth,
height() - mainSectionTop
), _contentScrollAddToY);
if (_hider) {
_hider->setGeometryToLeft(
dialogsWidth,
0,
mainSectionWidth,
height());
}
}
if (_mainSection) {
const auto mainSectionGeometry = QRect(
_history->x(),
mainSectionTop,
_history->width(),
height() - mainSectionTop);
_mainSection->setGeometryWithTopMoved(
mainSectionGeometry,
_contentScrollAddToY);
}
refreshResizeAreas();
if (_player) {
_player->entity()->updateDropdownsGeometry();
}
updateMediaPlaylistPosition(_playerPlaylist->x());
_contentScrollAddToY = 0;
floatPlayerUpdatePositions();
}
void MainWidget::destroyThirdSection() {
if (const auto strong = _thirdSection.data()) {
if (Ui::InFocusChain(strong)) {
setFocus();
}
}
_thirdSection.destroy();
}
void MainWidget::refreshResizeAreas() {
if (!isOneColumn() && _dialogs) {
ensureFirstColumnResizeAreaCreated();
_firstColumnResizeArea->setGeometryToLeft(
_history->x(),
0,
st::historyResizeWidth,
height());
} else if (_firstColumnResizeArea) {
_firstColumnResizeArea.destroy();
}
if (isThreeColumn() && _thirdSection) {
ensureThirdColumnResizeAreaCreated();
_thirdColumnResizeArea->setGeometryToLeft(
_thirdSection->x(),
0,
st::historyResizeWidth,
height());
} else if (_thirdColumnResizeArea) {
_thirdColumnResizeArea.destroy();
}
}
template <typename MoveCallback, typename FinishCallback>
void MainWidget::createResizeArea(
object_ptr<Ui::ResizeArea> &area,
MoveCallback &&moveCallback,
FinishCallback &&finishCallback) {
area.create(this);
area->show();
area->addMoveLeftCallback(
std::forward<MoveCallback>(moveCallback));
area->addMoveFinishedCallback(
std::forward<FinishCallback>(finishCallback));
orderWidgets();
}
void MainWidget::ensureFirstColumnResizeAreaCreated() {
Expects(_dialogs != nullptr);
if (_firstColumnResizeArea) {
return;
}
auto moveLeftCallback = [=](int globalLeft) {
const auto newWidth = globalLeft - mapToGlobal(QPoint(0, 0)).x();
const auto newRatio = (newWidth < st::columnMinimalWidthLeft / 2)
? 0.
: float64(newWidth) / width();
const auto nochat = !_controller->mainSectionShown();
Core::App().settings().updateDialogsWidthRatio(newRatio, nochat);
};
auto moveFinishedCallback = [=] {
if (isOneColumn()) {
return;
}
const auto nochat = !_controller->mainSectionShown();
if (Core::App().settings().dialogsWidthRatio(nochat) > 0) {
Core::App().settings().updateDialogsWidthRatio(
float64(_dialogsWidth) / width(),
nochat);
}
Core::App().saveSettingsDelayed();
};
createResizeArea(
_firstColumnResizeArea,
std::move(moveLeftCallback),
std::move(moveFinishedCallback));
}
void MainWidget::ensureThirdColumnResizeAreaCreated() {
if (_thirdColumnResizeArea) {
return;
}
auto moveLeftCallback = [=](int globalLeft) {
auto newWidth = mapToGlobal(QPoint(width(), 0)).x() - globalLeft;
Core::App().settings().setThirdColumnWidth(newWidth);
};
auto moveFinishedCallback = [=] {
if (!isThreeColumn() || !_thirdSection) {
return;
}
Core::App().settings().setThirdColumnWidth(std::clamp(
Core::App().settings().thirdColumnWidth(),
st::columnMinimalWidthThird,
st::columnMaximalWidthThird));
Core::App().saveSettingsDelayed();
};
createResizeArea(
_thirdColumnResizeArea,
std::move(moveLeftCallback),
std::move(moveFinishedCallback));
}
void MainWidget::updateDialogsWidthAnimated() {
const auto nochat = !_controller->mainSectionShown();
if (!_dialogs || Core::App().settings().dialogsWidthRatio(nochat) > 0) {
return;
}
auto dialogsWidth = _dialogsWidth;
updateWindowAdaptiveLayout();
if (Core::App().settings().dialogsWidthRatio(nochat) == 0.
&& (_dialogsWidth != dialogsWidth
|| _a_dialogsWidth.animating())) {
_dialogs->startWidthAnimation();
_a_dialogsWidth.start(
[this] { updateControlsGeometry(); },
dialogsWidth,
_dialogsWidth,
st::dialogsWidthDuration,
anim::easeOutCirc);
updateControlsGeometry();
}
}
bool MainWidget::saveThirdSectionToStackBack() const {
return !_stack.empty()
&& _thirdSection != nullptr
&& _stack.back()->thirdSectionWeak() == _thirdSection.data();
}
auto MainWidget::thirdSectionForCurrentMainSection(
Dialogs::Key key)
-> std::shared_ptr<Window::SectionMemento> {
if (_thirdSectionFromStack) {
return std::move(_thirdSectionFromStack);
} else if (const auto topic = key.topic()) {
return std::make_shared<Info::Memento>(topic);
} else if (const auto sublist = key.sublist()
; sublist && sublist->parentChat()) {
return std::make_shared<Info::Memento>(sublist);
} else if (const auto peer = key.peer()) {
return std::make_shared<Info::Memento>(
peer,
Info::Memento::DefaultSection(peer));
} else if (const auto sublist = key.sublist()) {
return std::make_shared<Info::Memento>(
sublist->owningHistory()->peer,
Info::Memento::DefaultSection(sublist->owningHistory()->peer));
}
Unexpected("Key in MainWidget::thirdSectionForCurrentMainSection().");
}
void MainWidget::updateThirdColumnToCurrentChat(
Dialogs::Key key,
bool canWrite) {
auto saveOldThirdSection = [&] {
if (saveThirdSectionToStackBack()) {
_stack.back()->setThirdSectionMemento(
_thirdSection->createMemento());
destroyThirdSection();
}
};
auto &settings = Core::App().settings();
auto params = Window::SectionShow(
Window::SectionShow::Way::ClearStack,
anim::type::instant,
anim::activation::background);
auto switchInfoFast = [&] {
saveOldThirdSection();
//
// Like in _controller->showPeerInfo()
//
if (isThreeColumn()
&& !settings.thirdSectionInfoEnabled()) {
settings.setThirdSectionInfoEnabled(true);
Core::App().saveSettingsDelayed();
}
_controller->showSection(
thirdSectionForCurrentMainSection(key),
params.withThirdColumn());
};
auto switchTabbedFast = [&](not_null<Data::Thread*> thread) {
saveOldThirdSection();
return _mainSection
? _mainSection->pushTabbedSelectorToThirdSection(thread, params)
: _history->pushTabbedSelectorToThirdSection(thread, params);
};
if (isThreeColumn()
&& settings.tabbedSelectorSectionEnabled()
&& key) {
if (!canWrite) {
switchInfoFast();
settings.setTabbedSelectorSectionEnabled(true);
settings.setTabbedReplacedWithInfo(true);
} else if (settings.tabbedReplacedWithInfo()
&& key.thread()
&& switchTabbedFast(key.thread())) {
settings.setTabbedReplacedWithInfo(false);
}
} else {
settings.setTabbedReplacedWithInfo(false);
if (!key) {
if (_thirdSection) {
destroyThirdSection();
_thirdShadow.destroy();
updateControlsGeometry();
}
} else if (isThreeColumn()
&& settings.thirdSectionInfoEnabled()) {
switchInfoFast();
}
}
}
void MainWidget::updateMediaPlaylistPosition(int x) {
if (_player) {
auto playlistLeft = x;
auto playlistWidth = _playerPlaylist->width();
auto playlistTop = _player->y() + _player->height();
auto rightEdge = width();
if (playlistLeft + playlistWidth > rightEdge) {
playlistLeft = rightEdge - playlistWidth;
} else if (playlistLeft < 0) {
playlistLeft = 0;
}
_playerPlaylist->move(playlistLeft, playlistTop);
}
}
void MainWidget::returnTabbedSelector() {
if (!_mainSection || !_mainSection->returnTabbedSelector()) {
_history->returnTabbedSelector();
}
}
bool MainWidget::relevantForDialogsFocus(not_null<QWidget*> widget) const {
if (!_dialogs || widget->window() != window()) {
return false;
}
while (true) {
if (widget.get() == this) {
return true;
}
const auto parent = widget->parentWidget();
if (!parent) {
return false;
}
widget = parent;
}
Unexpected("Should never be here.");
}
bool MainWidget::eventFilter(QObject *o, QEvent *e) {
const auto widget = o->isWidgetType()
? static_cast<QWidget*>(o)
: nullptr;
if (e->type() == QEvent::FocusIn) {
if (widget && relevantForDialogsFocus(widget)) {
_dialogs->updateHasFocus(widget);
} else if (widget == window()) {
crl::on_main(this, [=] {
_controller->widget()->setInnerFocus();
});
}
} else if (e->type() == QEvent::MouseButtonPress) {
if (widget && (widget->window() == window())) {
const auto event = static_cast<QMouseEvent*>(e);
if (event->button() == Qt::BackButton) {
if (!Core::App().hideMediaView()
&& (!_dialogs || !_dialogs->cancelSearchByMouseBack())) {
handleHistoryBack();
}
return true;
}
}
} else if (e->type() == QEvent::Wheel) {
if (widget && (widget->window() == window())) {
if (const auto result = floatPlayerFilterWheelEvent(o, e)) {
return *result;
}
}
}
return RpWidget::eventFilter(o, e);
}
void MainWidget::handleAdaptiveLayoutUpdate() {
showAll();
if (_sideShadow) {
_sideShadow->setVisible(!isOneColumn());
}
if (_player) {
_player->updateAdaptiveLayout();
}
}
void MainWidget::handleHistoryBack() {
const auto openedFolder = _controller->openedFolder().current();
const auto openedForum = _controller->shownForum().current();
const auto rootPeer = !_stack.empty()
? _stack.front()->peer()
: _history->peer()
? _history->peer()
: _mainSection
? _mainSection->activeChat().key.peer()
: nullptr;
const auto rootHistory = rootPeer
? rootPeer->owner().historyLoaded(rootPeer)
: nullptr;
const auto rootFolder = rootHistory ? rootHistory->folder() : nullptr;
if (openedForum
&& _stack.empty()
&& (!rootPeer || rootPeer->forum() != openedForum)) {
_controller->closeForum();
} else if (!openedFolder
|| (rootFolder == openedFolder)
|| (!_dialogs || _dialogs->isHidden())) {
_controller->showBackFromStack();
if (_dialogs) {
_dialogs->setInnerFocus();
}
} else {
_controller->closeFolder();
}
}
void MainWidget::updateWindowAdaptiveLayout() {
const auto nochat = !_controller->mainSectionShown();
auto layout = _controller->computeColumnLayout();
auto dialogsWidthRatio = Core::App().settings().dialogsWidthRatio(nochat);
// Check if we are in a single-column layout in a wide enough window
// for the normal layout. If so, switch to the normal layout.
if (layout.windowLayout == Window::Adaptive::WindowLayout::OneColumn) {
auto chatWidth = layout.chatWidth;
//if (session().settings().tabbedSelectorSectionEnabled()
// && chatWidth >= _history->minimalWidthForTabbedSelectorSection()) {
// chatWidth -= _history->tabbedSelectorSectionWidth();
//}
auto minimalNormalWidth = st::columnMinimalWidthLeft
+ st::columnMinimalWidthMain;
if (chatWidth >= minimalNormalWidth) {
// Switch layout back to normal in a wide enough window.
layout.windowLayout = Window::Adaptive::WindowLayout::Normal;
layout.dialogsWidth = st::columnMinimalWidthLeft;
layout.chatWidth = layout.bodyWidth - layout.dialogsWidth;
dialogsWidthRatio = float64(layout.dialogsWidth) / layout.bodyWidth;
}
}
// Check if we are going to create the third column and shrink the
// dialogs widget to provide a wide enough chat history column.
// Don't shrink the column on the first call, when window is inited.
if (layout.windowLayout == Window::Adaptive::WindowLayout::ThreeColumn
&& _controller->widget()->positionInited()) {
//auto chatWidth = layout.chatWidth;
//if (_history->willSwitchToTabbedSelectorWithWidth(chatWidth)) {
// auto thirdColumnWidth = _history->tabbedSelectorSectionWidth();
// auto twoColumnsWidth = (layout.bodyWidth - thirdColumnWidth);
// auto sameRatioChatWidth = twoColumnsWidth - qRound(dialogsWidthRatio * twoColumnsWidth);
// auto desiredChatWidth = qMax(sameRatioChatWidth, HistoryView::WideChatWidth());
// chatWidth -= thirdColumnWidth;
// auto extendChatBy = desiredChatWidth - chatWidth;
// accumulate_min(extendChatBy, layout.dialogsWidth - st::columnMinimalWidthLeft);
// if (extendChatBy > 0) {
// layout.dialogsWidth -= extendChatBy;
// layout.chatWidth += extendChatBy;
// dialogsWidthRatio = float64(layout.dialogsWidth) / layout.bodyWidth;
// }
//}
}
Core::App().settings().updateDialogsWidthRatio(dialogsWidthRatio, nochat);
auto useSmallColumnWidth = !isOneColumn()
&& !dialogsWidthRatio
&& !_controller->chatsForceDisplayWide();
_dialogsWidth = !_dialogs
? 0
: useSmallColumnWidth
? _controller->dialogsSmallColumnWidth()
: layout.dialogsWidth;
_thirdColumnWidth = layout.thirdWidth;
_controller->adaptive().setWindowLayout(layout.windowLayout);
}
int MainWidget::backgroundFromY() const {
return -getMainSectionTop();
}
bool MainWidget::contentOverlapped(const QRect &globalRect) {
return _history->contentOverlapped(globalRect)
/*|| _playerPlaylist->overlaps(globalRect)*/;
}
void MainWidget::activate() {
if (_showAnimation) {
return;
}
const auto urls = base::take(cRefStartUrls());
const auto interprets = urls | ranges::views::filter([](const QUrl &url) {
return url.scheme() == u"interpret"_q;
}) | ranges::views::transform([](const QUrl &url) {
return url.path();
}) | ranges::to<QStringList>;
const auto paths = urls | ranges::views::filter(
&QUrl::isLocalFile
) | ranges::views::transform(
&QUrl::toLocalFile
) | ranges::to<QStringList>;
if (!interprets.isEmpty() || !paths.isEmpty()) {
if (!interprets.isEmpty()) {
for (const auto &interpret : interprets) {
const auto error = Support::InterpretSendPath(
_controller,
interpret);
if (!error.isEmpty()) {
_controller->show(Ui::MakeInformBox(error));
}
}
}
if (!paths.isEmpty()) {
const auto chosen = [=](not_null<Data::Thread*> thread) {
return sendPaths(thread, paths);
};
Window::ShowChooseRecipientBox(_controller, chosen);
}
} else if (_mainSection) {
_mainSection->setInnerFocus();
} else if (_hider) {
Assert(_dialogs != nullptr);
_dialogs->setInnerFocus();
} else if (!_controller->isLayerShown()) {
if (_history->peer()) {
_history->activate();
} else {
Assert(_dialogs != nullptr);
_dialogs->setInnerFocus();
}
}
_controller->widget()->fixOrder();
}
bool MainWidget::animatingShow() const {
return _showAnimation != nullptr;
}
bool MainWidget::isOneColumn() const {
return _controller->adaptive().isOneColumn();
}
bool MainWidget::isNormalColumn() const {
return _controller->adaptive().isNormal();
}
bool MainWidget::isThreeColumn() const {
return _controller->adaptive().isThreeColumn();
}