1 /*
2 This file is part of Telegram Desktop,
3 the official desktop application for the Telegram messaging service.
4
5 For license and copyright information please follow this link:
6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
7 */
8 #include "mainwidget.h"
9
10 #include "api/api_updates.h"
11 #include "api/api_views.h"
12 #include "data/data_photo.h"
13 #include "data/data_document.h"
14 #include "data/data_document_media.h"
15 #include "data/data_document_resolver.h"
16 #include "data/data_wall_paper.h"
17 #include "data/data_web_page.h"
18 #include "data/data_game.h"
19 #include "data/data_peer_values.h"
20 #include "data/data_drafts.h"
21 #include "data/data_session.h"
22 #include "data/data_changes.h"
23 #include "data/data_media_types.h"
24 #include "data/data_folder.h"
25 #include "data/data_channel.h"
26 #include "data/data_chat.h"
27 #include "data/data_user.h"
28 #include "data/data_chat_filters.h"
29 #include "data/data_scheduled_messages.h"
30 #include "data/data_file_origin.h"
31 #include "data/data_histories.h"
32 #include "data/stickers/data_stickers.h"
33 #include "ui/chat/chat_theme.h"
34 #include "ui/special_buttons.h"
35 #include "ui/widgets/buttons.h"
36 #include "ui/widgets/shadow.h"
37 #include "ui/toasts/common_toasts.h"
38 #include "ui/widgets/dropdown_menu.h"
39 #include "ui/image/image.h"
40 #include "ui/focus_persister.h"
41 #include "ui/resize_area.h"
42 #include "ui/text/text_options.h"
43 #include "ui/emoji_config.h"
44 #include "ui/ui_utility.h"
45 #include "window/section_memento.h"
46 #include "window/section_widget.h"
47 #include "window/window_connecting_widget.h"
48 #include "window/window_top_bar_wrap.h"
49 #include "window/notifications_manager.h"
50 #include "window/window_slide_animation.h"
51 #include "window/window_session_controller.h"
52 #include "window/window_history_hider.h"
53 #include "window/window_controller.h"
54 #include "window/themes/window_theme.h"
55 #include "chat_helpers/tabbed_selector.h" // TabbedSelector::refreshStickers
56 #include "chat_helpers/message_field.h"
57 #include "info/info_memento.h"
58 #include "info/info_controller.h"
59 #include "apiwrap.h"
60 #include "dialogs/dialogs_widget.h"
61 #include "dialogs/dialogs_key.h"
62 #include "history/history.h"
63 #include "history/history_widget.h"
64 #include "history/history_message.h"
65 #include "history/view/media/history_view_media.h"
66 #include "history/view/history_view_service_message.h"
67 #include "history/view/history_view_element.h"
68 #include "lang/lang_keys.h"
69 #include "lang/lang_cloud_manager.h"
70 #include "boxes/add_contact_box.h"
71 #include "mainwindow.h"
72 #include "inline_bots/inline_bot_layout_item.h"
73 #include "ui/boxes/confirm_box.h"
74 #include "boxes/sticker_set_box.h"
75 #include "boxes/mute_settings_box.h"
76 #include "boxes/peer_list_controllers.h"
77 #include "boxes/download_path_box.h"
78 #include "boxes/connection_box.h"
79 #include "storage/storage_account.h"
80 #include "media/audio/media_audio.h"
81 #include "media/player/media_player_panel.h"
82 #include "media/player/media_player_widget.h"
83 #include "media/player/media_player_volume_controller.h"
84 #include "media/player/media_player_instance.h"
85 #include "media/player/media_player_float.h"
86 #include "base/qthelp_regex.h"
87 #include "base/qthelp_url.h"
88 #include "base/flat_set.h"
89 #include "mtproto/mtproto_dc_options.h"
90 #include "core/file_utilities.h"
91 #include "core/update_checker.h"
92 #include "core/shortcuts.h"
93 #include "core/application.h"
94 #include "core/changelogs.h"
95 #include "base/unixtime.h"
96 #include "calls/calls_call.h"
97 #include "calls/calls_instance.h"
98 #include "calls/calls_top_bar.h"
99 #include "calls/group/calls_group_call.h"
100 #include "export/export_settings.h"
101 #include "export/export_manager.h"
102 #include "export/view/export_view_top_bar.h"
103 #include "export/view/export_view_panel_controller.h"
104 #include "main/main_session.h"
105 #include "main/main_session_settings.h"
106 #include "main/main_account.h"
107 #include "support/support_helper.h"
108 #include "storage/storage_facade.h"
109 #include "storage/storage_shared_media.h"
110 #include "storage/storage_user_photos.h"
111 #include "facades.h"
112 #include "styles/style_dialogs.h"
113 #include "styles/style_chat.h"
114 #include "styles/style_boxes.h"
115
116 #include <QtCore/QCoreApplication>
117 #include <QtCore/QMimeData>
118
119 enum StackItemType {
120 HistoryStackItem,
121 SectionStackItem,
122 };
123
124 class StackItem {
125 public:
StackItem(PeerData * peer)126 StackItem(PeerData *peer) : _peer(peer) {
127 }
128
peer() const129 PeerData *peer() const {
130 return _peer;
131 }
132
133 void setThirdSectionMemento(
134 std::shared_ptr<Window::SectionMemento> memento);
takeThirdSectionMemento()135 std::shared_ptr<Window::SectionMemento> takeThirdSectionMemento() {
136 return std::move(_thirdSectionMemento);
137 }
138
setThirdSectionWeak(QPointer<Window::SectionWidget> section)139 void setThirdSectionWeak(QPointer<Window::SectionWidget> section) {
140 _thirdSectionWeak = section;
141 }
thirdSectionWeak() const142 QPointer<Window::SectionWidget> thirdSectionWeak() const {
143 return _thirdSectionWeak;
144 }
145
146 virtual StackItemType type() const = 0;
147 virtual ~StackItem() = default;
148
149 private:
150 PeerData *_peer = nullptr;
151 QPointer<Window::SectionWidget> _thirdSectionWeak;
152 std::shared_ptr<Window::SectionMemento> _thirdSectionMemento;
153
154 };
155
156 class StackItemHistory : public StackItem {
157 public:
StackItemHistory(not_null<History * > history,MsgId msgId,QList<MsgId> replyReturns)158 StackItemHistory(
159 not_null<History*> history,
160 MsgId msgId,
161 QList<MsgId> replyReturns)
162 : StackItem(history->peer)
163 , history(history)
164 , msgId(msgId)
165 , replyReturns(replyReturns) {
166 }
167
type() const168 StackItemType type() const override {
169 return HistoryStackItem;
170 }
171
172 not_null<History*> history;
173 MsgId msgId;
174 QList<MsgId> replyReturns;
175
176 };
177
178 class StackItemSection : public StackItem {
179 public:
180 StackItemSection(
181 std::shared_ptr<Window::SectionMemento> memento);
182
type() const183 StackItemType type() const override {
184 return SectionStackItem;
185 }
takeMemento()186 std::shared_ptr<Window::SectionMemento> takeMemento() {
187 return std::move(_memento);
188 }
189
190 private:
191 std::shared_ptr<Window::SectionMemento> _memento;
192
193 };
194
setThirdSectionMemento(std::shared_ptr<Window::SectionMemento> memento)195 void StackItem::setThirdSectionMemento(
196 std::shared_ptr<Window::SectionMemento> memento) {
197 _thirdSectionMemento = std::move(memento);
198 }
199
StackItemSection(std::shared_ptr<Window::SectionMemento> memento)200 StackItemSection::StackItemSection(
201 std::shared_ptr<Window::SectionMemento> memento)
202 : StackItem(nullptr)
203 , _memento(std::move(memento)) {
204 }
205
206 struct MainWidget::SettingBackground {
207 explicit SettingBackground(const Data::WallPaper &data);
208
209 Data::WallPaper data;
210 std::shared_ptr<Data::DocumentMedia> dataMedia;
211 base::binary_guard generating;
212 };
213
SettingBackground(const Data::WallPaper & data)214 MainWidget::SettingBackground::SettingBackground(
215 const Data::WallPaper &data)
216 : data(data) {
217 }
218
MainWidget(QWidget * parent,not_null<Window::SessionController * > controller)219 MainWidget::MainWidget(
220 QWidget *parent,
221 not_null<Window::SessionController*> controller)
222 : RpWidget(parent)
223 , _controller(controller)
224 , _dialogsWidth(st::columnMinimalWidthLeft)
225 , _thirdColumnWidth(st::columnMinimalWidthThird)
226 , _sideShadow(this)
227 , _dialogs(this, _controller)
228 , _history(this, _controller)
229 , _playerPlaylist(this, _controller)
230 , _changelogs(Core::Changelogs::Create(&controller->session())) {
231 setupConnectingWidget();
232
233 connect(_dialogs, SIGNAL(cancelled()), this, SLOT(dialogsCancelled()));
234
235 _history->cancelRequests(
236 ) | rpl::start_with_next([=] {
237 handleHistoryBack();
238 }, lifetime());
239
240 Core::App().calls().currentCallValue(
241 ) | rpl::start_with_next([=](Calls::Call *call) {
242 setCurrentCall(call);
243 }, lifetime());
244 Core::App().calls().currentGroupCallValue(
245 ) | rpl::start_with_next([=](Calls::GroupCall *call) {
246 setCurrentGroupCall(call);
247 }, lifetime());
248 if (_callTopBar) {
249 _callTopBar->finishAnimating();
250 }
251
252 Core::App().setDefaultFloatPlayerDelegate(floatPlayerDelegate());
253 Core::App().floatPlayerClosed(
254 ) | rpl::start_with_next([=](FullMsgId itemId) {
255 floatPlayerClosed(itemId);
256 }, lifetime());
257
258 Core::App().exportManager().currentView(
259 ) | rpl::start_with_next([=](Export::View::PanelController *view) {
260 setCurrentExportView(view);
261 }, lifetime());
262 if (_exportTopBar) {
263 _exportTopBar->finishAnimating();
264 }
265
266 Media::Player::instance()->updatedNotifier(
267 ) | rpl::start_with_next([=](const Media::Player::TrackState &state) {
268 handleAudioUpdate(state);
269 }, lifetime());
270 handleAudioUpdate(Media::Player::instance()->getState(AudioMsgId::Type::Song));
271 handleAudioUpdate(Media::Player::instance()->getState(AudioMsgId::Type::Voice));
272 if (_player) {
273 _player->finishAnimating();
274 }
275
276 subscribe(_controller->dialogsListFocused(), [this](bool) {
277 updateDialogsWidthAnimated();
278 });
279 subscribe(_controller->dialogsListDisplayForced(), [this](bool) {
280 updateDialogsWidthAnimated();
281 });
282 rpl::merge(
283 Core::App().settings().dialogsWidthRatioChanges() | rpl::to_empty,
284 Core::App().settings().thirdColumnWidthChanges() | rpl::to_empty
285 ) | rpl::start_with_next([=] {
286 updateControlsGeometry();
287 }, lifetime());
288
289 session().changes().historyUpdates(
290 Data::HistoryUpdate::Flag::MessageSent
291 | Data::HistoryUpdate::Flag::LocalDraftSet
292 ) | rpl::start_with_next([=](const Data::HistoryUpdate &update) {
293 const auto history = update.history;
294 if (update.flags & Data::HistoryUpdate::Flag::MessageSent) {
295 history->forgetScrollState();
296 if (const auto from = history->peer->migrateFrom()) {
297 auto &owner = history->owner();
298 if (const auto migrated = owner.historyLoaded(from)) {
299 migrated->forgetScrollState();
300 }
301 }
302 }
303 if (update.flags & Data::HistoryUpdate::Flag::LocalDraftSet) {
304 const auto opened = (_history->peer() == history->peer.get());
305 if (opened) {
306 _history->applyDraft();
307 } else {
308 Ui::showPeerHistory(history, ShowAtUnreadMsgId);
309 }
310 Ui::hideLayer();
311 }
312 }, lifetime());
313
314 // MSVC BUG + REGRESSION rpl::mappers::tuple :(
315 using namespace rpl::mappers;
316 _controller->activeChatValue(
317 ) | rpl::map([](Dialogs::Key key) {
318 const auto peer = key.peer();
319 auto canWrite = peer
320 ? Data::CanWriteValue(peer)
321 : rpl::single(false);
322 return std::move(
323 canWrite
324 ) | rpl::map([=](bool can) {
325 return std::make_tuple(key, can);
326 });
327 }) | rpl::flatten_latest(
328 ) | rpl::start_with_next([this](Dialogs::Key key, bool canWrite) {
329 updateThirdColumnToCurrentChat(key, canWrite);
330 }, lifetime());
331
332 QCoreApplication::instance()->installEventFilter(this);
333
334 subscribe(Media::Player::instance()->playerWidgetOver(), [this](bool over) {
335 if (over) {
336 if (_playerPlaylist->isHidden()) {
337 auto position = mapFromGlobal(QCursor::pos()).x();
338 auto bestPosition = _playerPlaylist->bestPositionFor(position);
339 if (rtl()) bestPosition = position + 2 * (position - bestPosition) - _playerPlaylist->width();
340 updateMediaPlaylistPosition(bestPosition);
341 }
342 _playerPlaylist->showFromOther();
343 } else {
344 _playerPlaylist->hideFromOther();
345 }
346 });
347 subscribe(Media::Player::instance()->tracksFinishedNotifier(), [this](AudioMsgId::Type type) {
348 if (type == AudioMsgId::Type::Voice) {
349 const auto songState = Media::Player::instance()->getState(AudioMsgId::Type::Song);
350 if (!songState.id || IsStoppedOrStopping(songState.state)) {
351 closeBothPlayers();
352 }
353 } else if (type == AudioMsgId::Type::Song) {
354 const auto songState = Media::Player::instance()->getState(AudioMsgId::Type::Song);
355 if (!songState.id) {
356 closeBothPlayers();
357 }
358 }
359 });
360
361 _controller->adaptive().changes(
362 ) | rpl::start_with_next([=] {
363 handleAdaptiveLayoutUpdate();
364 }, lifetime());
365
366 _dialogs->show();
367 if (isOneColumn()) {
368 _history->hide();
369 } else {
370 _history->show();
371 }
372
373 orderWidgets();
374
375 if (!Core::UpdaterDisabled()) {
376 Core::UpdateChecker checker;
377 checker.start();
378 }
379
380 cSetOtherOnline(0);
381
382 _history->start();
383 }
384
385 MainWidget::~MainWidget() = default;
386
session() const387 Main::Session &MainWidget::session() const {
388 return _controller->session();
389 }
390
controller() const391 not_null<Window::SessionController*> MainWidget::controller() const {
392 return _controller;
393 }
394
setupConnectingWidget()395 void MainWidget::setupConnectingWidget() {
396 using namespace rpl::mappers;
397 _connecting = std::make_unique<Window::ConnectionState>(
398 this,
399 &session().account(),
400 _controller->adaptive().oneColumnValue() | rpl::map(!_1));
401 }
402
floatPlayerDelegate()403 not_null<Media::Player::FloatDelegate*> MainWidget::floatPlayerDelegate() {
404 return static_cast<Media::Player::FloatDelegate*>(this);
405 }
406
floatPlayerWidget()407 not_null<Ui::RpWidget*> MainWidget::floatPlayerWidget() {
408 return this;
409 }
410
floatPlayerGetSection(Window::Column column)411 auto MainWidget::floatPlayerGetSection(Window::Column column)
412 -> not_null<Media::Player::FloatSectionDelegate*> {
413 if (isThreeColumn()) {
414 if (column == Window::Column::First) {
415 return _dialogs;
416 } else if (column == Window::Column::Second
417 || !_thirdSection) {
418 if (_mainSection) {
419 return _mainSection;
420 }
421 return _history;
422 }
423 return _thirdSection;
424 } else if (isNormalColumn()) {
425 if (column == Window::Column::First) {
426 return _dialogs;
427 } else if (_mainSection) {
428 return _mainSection;
429 }
430 return _history;
431 }
432 if (isOneColumn() && selectingPeer()) {
433 return _dialogs;
434 } else if (_mainSection) {
435 return _mainSection;
436 } else if (!isOneColumn() || _history->peer()) {
437 return _history;
438 }
439 return _dialogs;
440 }
441
floatPlayerEnumerateSections(Fn<void (not_null<Media::Player::FloatSectionDelegate * > widget,Window::Column widgetColumn)> callback)442 void MainWidget::floatPlayerEnumerateSections(Fn<void(
443 not_null<Media::Player::FloatSectionDelegate*> widget,
444 Window::Column widgetColumn)> callback) {
445 if (isThreeColumn()) {
446 callback(_dialogs, Window::Column::First);
447 if (_mainSection) {
448 callback(_mainSection, Window::Column::Second);
449 } else {
450 callback(_history, Window::Column::Second);
451 }
452 if (_thirdSection) {
453 callback(_thirdSection, Window::Column::Third);
454 }
455 } else if (isNormalColumn()) {
456 callback(_dialogs, Window::Column::First);
457 if (_mainSection) {
458 callback(_mainSection, Window::Column::Second);
459 } else {
460 callback(_history, Window::Column::Second);
461 }
462 } else {
463 if (isOneColumn() && selectingPeer()) {
464 callback(_dialogs, Window::Column::First);
465 } else if (_mainSection) {
466 callback(_mainSection, Window::Column::Second);
467 } else if (!isOneColumn() || _history->peer()) {
468 callback(_history, Window::Column::Second);
469 } else {
470 callback(_dialogs, Window::Column::First);
471 }
472 }
473 }
474
floatPlayerIsVisible(not_null<HistoryItem * > item)475 bool MainWidget::floatPlayerIsVisible(not_null<HistoryItem*> item) {
476 auto isVisible = false;
477 session().data().queryItemVisibility().notify({ item, &isVisible }, true);
478 return isVisible;
479 }
480
floatPlayerClosed(FullMsgId itemId)481 void MainWidget::floatPlayerClosed(FullMsgId itemId) {
482 if (_player) {
483 const auto voiceData = Media::Player::instance()->current(
484 AudioMsgId::Type::Voice);
485 if (voiceData.contextId() == itemId) {
486 stopAndClosePlayer();
487 }
488 }
489 }
490
floatPlayerDoubleClickEvent(not_null<const HistoryItem * > item)491 void MainWidget::floatPlayerDoubleClickEvent(
492 not_null<const HistoryItem*> item) {
493 _controller->showPeerHistoryAtItem(item);
494 }
495
setForwardDraft(PeerId peerId,Data::ForwardDraft && draft)496 bool MainWidget::setForwardDraft(PeerId peerId, Data::ForwardDraft &&draft) {
497 Expects(peerId != 0);
498
499 const auto peer = session().data().peer(peerId);
500 const auto error = GetErrorTextForSending(
501 peer,
502 session().data().idsToItems(draft.ids),
503 true);
504 if (!error.isEmpty()) {
505 Ui::show(Box<Ui::InformBox>(error), Ui::LayerOption::KeepOther);
506 return false;
507 }
508
509 peer->owner().history(peer)->setForwardDraft(std::move(draft));
510 _controller->showPeerHistory(
511 peer,
512 SectionShow::Way::Forward,
513 ShowAtUnreadMsgId);
514 _history->cancelReply();
515 return true;
516 }
517
shareUrl(PeerId peerId,const QString & url,const QString & text)518 bool MainWidget::shareUrl(
519 PeerId peerId,
520 const QString &url,
521 const QString &text) {
522 Expects(peerId != 0);
523
524 const auto peer = session().data().peer(peerId);
525 if (!peer->canWrite()) {
526 Ui::show(Box<Ui::InformBox>(tr::lng_share_cant(tr::now)));
527 return false;
528 }
529 TextWithTags textWithTags = {
530 url + '\n' + text,
531 TextWithTags::Tags()
532 };
533 MessageCursor cursor = {
534 int(url.size()) + 1,
535 int(url.size()) + 1 + int(text.size()),
536 QFIXED_MAX
537 };
538 auto history = peer->owner().history(peer);
539 history->setLocalDraft(std::make_unique<Data::Draft>(
540 textWithTags,
541 0,
542 cursor,
543 Data::PreviewState::Allowed));
544 history->clearLocalEditDraft();
545 history->session().changes().historyUpdated(
546 history,
547 Data::HistoryUpdate::Flag::LocalDraftSet);
548 return true;
549 }
550
inlineSwitchChosen(PeerId peerId,const QString & botAndQuery)551 bool MainWidget::inlineSwitchChosen(PeerId peerId, const QString &botAndQuery) {
552 Expects(peerId != 0);
553
554 const auto peer = session().data().peer(peerId);
555 if (!peer->canWrite()) {
556 Ui::show(Box<Ui::InformBox>(tr::lng_inline_switch_cant(tr::now)));
557 return false;
558 }
559 const auto h = peer->owner().history(peer);
560 TextWithTags textWithTags = { botAndQuery, TextWithTags::Tags() };
561 MessageCursor cursor = { int(botAndQuery.size()), int(botAndQuery.size()), QFIXED_MAX };
562 h->setLocalDraft(std::make_unique<Data::Draft>(
563 textWithTags,
564 0,
565 cursor,
566 Data::PreviewState::Allowed));
567 h->clearLocalEditDraft();
568 h->session().changes().historyUpdated(
569 h,
570 Data::HistoryUpdate::Flag::LocalDraftSet);
571 return true;
572 }
573
sendPaths(PeerId peerId)574 bool MainWidget::sendPaths(PeerId peerId) {
575 Expects(peerId != 0);
576
577 auto peer = session().data().peer(peerId);
578 if (!peer->canWrite()) {
579 Ui::show(Box<Ui::InformBox>(
580 tr::lng_forward_send_files_cant(tr::now)));
581 return false;
582 } else if (const auto error = Data::RestrictionError(
583 peer,
584 ChatRestriction::SendMedia)) {
585 Ui::show(Box<Ui::InformBox>(*error));
586 return false;
587 }
588 Ui::showPeerHistory(peer, ShowAtTheEndMsgId);
589 return _history->confirmSendingFiles(cSendPaths());
590 }
591
onFilesOrForwardDrop(const PeerId & peerId,const QMimeData * data)592 void MainWidget::onFilesOrForwardDrop(
593 const PeerId &peerId,
594 const QMimeData *data) {
595 Expects(peerId != 0);
596
597 if (data->hasFormat(qsl("application/x-td-forward"))) {
598 auto draft = Data::ForwardDraft{
599 .ids = session().data().takeMimeForwardIds(),
600 };
601 if (!setForwardDraft(peerId, std::move(draft))) {
602 // We've already released the mouse button, so the forwarding is cancelled.
603 if (_hider) {
604 _hider->startHide();
605 clearHider(_hider);
606 }
607 }
608 } else {
609 auto peer = session().data().peer(peerId);
610 if (!peer->canWrite()) {
611 Ui::show(Box<Ui::InformBox>(
612 tr::lng_forward_send_files_cant(tr::now)));
613 return;
614 }
615 Ui::showPeerHistory(peer, ShowAtTheEndMsgId);
616 _history->confirmSendingFiles(data);
617 }
618 }
619
notify_switchInlineBotButtonReceived(const QString & query,UserData * samePeerBot,MsgId samePeerReplyTo)620 bool MainWidget::notify_switchInlineBotButtonReceived(const QString &query, UserData *samePeerBot, MsgId samePeerReplyTo) {
621 return _history->notify_switchInlineBotButtonReceived(query, samePeerBot, samePeerReplyTo);
622 }
623
clearHider(not_null<Window::HistoryHider * > instance)624 void MainWidget::clearHider(not_null<Window::HistoryHider*> instance) {
625 if (_hider != instance) {
626 return;
627 }
628 _hider.release();
629 controller()->setSelectingPeer(false);
630
631 if (isOneColumn()) {
632 if (_mainSection || (_history->peer() && _history->peer()->id)) {
633 auto animationParams = ([=] {
634 if (_mainSection) {
635 return prepareMainSectionAnimation(_mainSection);
636 }
637 return prepareHistoryAnimation(_history->peer() ? _history->peer()->id : 0);
638 })();
639 _dialogs->hide();
640 if (_mainSection) {
641 _mainSection->showAnimated(Window::SlideDirection::FromRight, animationParams);
642 } else {
643 _history->showAnimated(Window::SlideDirection::FromRight, animationParams);
644 }
645 floatPlayerCheckVisibility();
646 } else {
647 _dialogs->updateForwardBar();
648 }
649 }
650 }
651
hiderLayer(base::unique_qptr<Window::HistoryHider> hider)652 void MainWidget::hiderLayer(base::unique_qptr<Window::HistoryHider> hider) {
653 if (controller()->window().locked()) {
654 return;
655 }
656
657 _hider = std::move(hider);
658 controller()->setSelectingPeer(true);
659
660 _dialogs->closeForwardBarRequests(
661 ) | rpl::start_with_next([=] {
662 _hider->startHide();
663 }, _hider->lifetime());
664
665 _hider->setParent(this);
666
667 _hider->hidden(
668 ) | rpl::start_with_next([=, instance = _hider.get()] {
669 clearHider(instance);
670 instance->hide();
671 instance->deleteLater();
672 }, _hider->lifetime());
673
674 _hider->confirmed(
675 ) | rpl::start_with_next([=] {
676 _dialogs->onCancelSearch();
677 }, _hider->lifetime());
678
679 if (isOneColumn()) {
680 dialogsToUp();
681
682 _hider->hide();
683 auto animationParams = prepareDialogsAnimation();
684
685 if (_mainSection) {
686 _mainSection->hide();
687 } else {
688 _history->hide();
689 }
690 if (_dialogs->isHidden()) {
691 _dialogs->show();
692 updateControlsGeometry();
693 _dialogs->showAnimated(Window::SlideDirection::FromLeft, animationParams);
694 }
695 } else {
696 _hider->show();
697 updateControlsGeometry();
698 _dialogs->setInnerFocus();
699 }
700 floatPlayerCheckVisibility();
701 }
702
showForwardLayer(Data::ForwardDraft && draft)703 void MainWidget::showForwardLayer(Data::ForwardDraft &&draft) {
704 auto callback = [=, draft = std::move(draft)](PeerId peer) mutable {
705 return setForwardDraft(peer, std::move(draft));
706 };
707 hiderLayer(base::make_unique_q<Window::HistoryHider>(
708 this,
709 tr::lng_forward_choose(tr::now),
710 std::move(callback),
711 _controller->adaptive().oneColumnValue()));
712 }
713
showSendPathsLayer()714 void MainWidget::showSendPathsLayer() {
715 hiderLayer(base::make_unique_q<Window::HistoryHider>(
716 this,
717 tr::lng_forward_choose(tr::now),
718 [=](PeerId peer) { return sendPaths(peer); },
719 _controller->adaptive().oneColumnValue()));
720 if (_hider) {
721 connect(_hider, &QObject::destroyed, [] {
722 cSetSendPaths(QStringList());
723 });
724 }
725 }
726
shareUrlLayer(const QString & url,const QString & text)727 void MainWidget::shareUrlLayer(const QString &url, const QString &text) {
728 // Don't allow to insert an inline bot query by share url link.
729 if (url.trimmed().startsWith('@')) {
730 return;
731 }
732 auto callback = [=](PeerId peer) {
733 return shareUrl(peer, url, text);
734 };
735 hiderLayer(base::make_unique_q<Window::HistoryHider>(
736 this,
737 tr::lng_forward_choose(tr::now),
738 std::move(callback),
739 _controller->adaptive().oneColumnValue()));
740 }
741
inlineSwitchLayer(const QString & botAndQuery)742 void MainWidget::inlineSwitchLayer(const QString &botAndQuery) {
743 auto callback = [=](PeerId peer) {
744 return inlineSwitchChosen(peer, botAndQuery);
745 };
746 hiderLayer(base::make_unique_q<Window::HistoryHider>(
747 this,
748 tr::lng_inline_switch_choose(tr::now),
749 std::move(callback),
750 _controller->adaptive().oneColumnValue()));
751 }
752
selectingPeer() const753 bool MainWidget::selectingPeer() const {
754 return _hider ? true : false;
755 }
756
sendBotCommand(Bot::SendCommandRequest request)757 void MainWidget::sendBotCommand(Bot::SendCommandRequest request) {
758 const auto type = _mainSection
759 ? _mainSection->sendBotCommand(request)
760 : Window::SectionActionResult::Fallback;
761 if (type == Window::SectionActionResult::Fallback) {
762 ui_showPeerHistory(
763 request.peer->id,
764 SectionShow::Way::ClearStack,
765 ShowAtTheEndMsgId);
766 _history->sendBotCommand(request);
767 }
768 }
769
hideSingleUseKeyboard(PeerData * peer,MsgId replyTo)770 void MainWidget::hideSingleUseKeyboard(PeerData *peer, MsgId replyTo) {
771 _history->hideSingleUseKeyboard(peer, replyTo);
772 }
773
insertBotCommand(const QString & cmd)774 bool MainWidget::insertBotCommand(const QString &cmd) {
775 return _history->insertBotCommand(cmd);
776 }
777
searchMessages(const QString & query,Dialogs::Key inChat)778 void MainWidget::searchMessages(const QString &query, Dialogs::Key inChat) {
779 _dialogs->searchMessages(query, inChat);
780 if (isOneColumn()) {
781 Ui::showChatsList(&session());
782 } else {
783 _dialogs->setInnerFocus();
784 }
785 }
786
handleAudioUpdate(const Media::Player::TrackState & state)787 void MainWidget::handleAudioUpdate(const Media::Player::TrackState &state) {
788 using State = Media::Player::State;
789 const auto document = state.id.audio();
790 if (!Media::Player::IsStoppedOrStopping(state.state)) {
791 createPlayer();
792 } else if (state.state == State::StoppedAtStart) {
793 closeBothPlayers();
794 }
795
796 if (const auto item = session().data().message(state.id.contextId())) {
797 session().data().requestItemRepaint(item);
798 }
799 if (document) {
800 if (const auto items = InlineBots::Layout::documentItems()) {
801 if (const auto i = items->find(document); i != items->end()) {
802 for (const auto &item : i->second) {
803 item->update();
804 }
805 }
806 }
807 }
808 }
809
closeBothPlayers()810 void MainWidget::closeBothPlayers() {
811 if (_player) {
812 _player->hide(anim::type::normal);
813 }
814 _playerVolume.destroyDelayed();
815
816 _playerPlaylist->hideIgnoringEnterEvents();
817 Media::Player::instance()->stop(AudioMsgId::Type::Voice);
818 Media::Player::instance()->stop(AudioMsgId::Type::Song);
819
820 Shortcuts::ToggleMediaShortcuts(false);
821 }
822
stopAndClosePlayer()823 void MainWidget::stopAndClosePlayer() {
824 if (_player) {
825 _player->entity()->stopAndClose();
826 }
827 }
828
createPlayer()829 void MainWidget::createPlayer() {
830 if (!_player) {
831 _player.create(
832 this,
833 object_ptr<Media::Player::Widget>(this, &session()),
834 _controller->adaptive().oneColumnValue());
835 rpl::merge(
836 _player->heightValue() | rpl::map_to(true),
837 _player->shownValue()
838 ) | rpl::start_with_next(
839 [this] { playerHeightUpdated(); },
840 _player->lifetime());
841 _player->entity()->setCloseCallback([=] { closeBothPlayers(); });
842 _player->entity()->setShowItemCallback([=](
843 not_null<const HistoryItem*> item) {
844 _controller->showPeerHistoryAtItem(item);
845 });
846 _playerVolume.create(this, _controller);
847 _player->entity()->volumeWidgetCreated(_playerVolume);
848 orderWidgets();
849 if (_a_show.animating()) {
850 _player->show(anim::type::instant);
851 _player->setVisible(false);
852 Shortcuts::ToggleMediaShortcuts(true);
853 } else {
854 _player->hide(anim::type::instant);
855 }
856 }
857 if (_player && !_player->toggled()) {
858 if (!_a_show.animating()) {
859 _player->show(anim::type::normal);
860 _playerHeight = _contentScrollAddToY = _player->contentHeight();
861 updateControlsGeometry();
862 Shortcuts::ToggleMediaShortcuts(true);
863 }
864 }
865 }
866
playerHeightUpdated()867 void MainWidget::playerHeightUpdated() {
868 if (!_player) {
869 // Player could be already "destroyDelayed", but still handle events.
870 return;
871 }
872 auto playerHeight = _player->contentHeight();
873 if (playerHeight != _playerHeight) {
874 _contentScrollAddToY += playerHeight - _playerHeight;
875 _playerHeight = playerHeight;
876 updateControlsGeometry();
877 }
878 if (!_playerHeight && _player->isHidden()) {
879 const auto state = Media::Player::instance()->getState(Media::Player::instance()->getActiveType());
880 if (!state.id || Media::Player::IsStoppedOrStopping(state.state)) {
881 _playerVolume.destroyDelayed();
882 _player.destroyDelayed();
883 }
884 }
885 }
886
setCurrentCall(Calls::Call * call)887 void MainWidget::setCurrentCall(Calls::Call *call) {
888 if (!call && _currentGroupCall) {
889 return;
890 }
891 _currentCallLifetime.destroy();
892 _currentCall = call;
893 if (_currentCall) {
894 _callTopBar.destroy();
895 _currentCall->stateValue(
896 ) | rpl::start_with_next([=](Calls::Call::State state) {
897 using State = Calls::Call::State;
898 if (state != State::Established) {
899 destroyCallTopBar();
900 } else if (!_callTopBar) {
901 createCallTopBar();
902 }
903 }, _currentCallLifetime);
904 } else {
905 destroyCallTopBar();
906 }
907 }
908
setCurrentGroupCall(Calls::GroupCall * call)909 void MainWidget::setCurrentGroupCall(Calls::GroupCall *call) {
910 if (!call && _currentCall) {
911 return;
912 }
913 _currentCallLifetime.destroy();
914 _currentGroupCall = call;
915 if (_currentGroupCall) {
916 _callTopBar.destroy();
917 _currentGroupCall->stateValue(
918 ) | rpl::start_with_next([=](Calls::GroupCall::State state) {
919 using State = Calls::GroupCall::State;
920 if (state != State::Creating
921 && state != State::Waiting
922 && state != State::Joining
923 && state != State::Joined
924 && state != State::Connecting) {
925 destroyCallTopBar();
926 } else if (!_callTopBar) {
927 createCallTopBar();
928 }
929 }, _currentCallLifetime);
930 } else {
931 destroyCallTopBar();
932 }
933 }
934
createCallTopBar()935 void MainWidget::createCallTopBar() {
936 Expects(_currentCall != nullptr || _currentGroupCall != nullptr);
937
938 _callTopBar.create(
939 this,
940 (_currentCall
941 ? object_ptr<Calls::TopBar>(this, _currentCall)
942 : object_ptr<Calls::TopBar>(this, _currentGroupCall)));
943 _callTopBar->entity()->initBlobsUnder(this, _callTopBar->geometryValue());
944 _callTopBar->heightValue(
945 ) | rpl::start_with_next([this](int value) {
946 callTopBarHeightUpdated(value);
947 }, lifetime());
948 orderWidgets();
949 if (_a_show.animating()) {
950 _callTopBar->show(anim::type::instant);
951 _callTopBar->setVisible(false);
952 } else {
953 _callTopBar->hide(anim::type::instant);
954 _callTopBar->show(anim::type::normal);
955 _callTopBarHeight = _contentScrollAddToY = _callTopBar->height();
956 updateControlsGeometry();
957 }
958 }
959
destroyCallTopBar()960 void MainWidget::destroyCallTopBar() {
961 if (_callTopBar) {
962 _callTopBar->hide(anim::type::normal);
963 }
964 }
965
callTopBarHeightUpdated(int callTopBarHeight)966 void MainWidget::callTopBarHeightUpdated(int callTopBarHeight) {
967 if (!callTopBarHeight && !_currentCall && !_currentGroupCall) {
968 _callTopBar.destroyDelayed();
969 }
970 if (callTopBarHeight != _callTopBarHeight) {
971 _contentScrollAddToY += callTopBarHeight - _callTopBarHeight;
972 _callTopBarHeight = callTopBarHeight;
973 updateControlsGeometry();
974 }
975 }
976
setCurrentExportView(Export::View::PanelController * view)977 void MainWidget::setCurrentExportView(Export::View::PanelController *view) {
978 _currentExportView = view;
979 if (_currentExportView) {
980 _currentExportView->progressState(
981 ) | rpl::start_with_next([=](Export::View::Content &&data) {
982 if (!data.rows.empty()
983 && data.rows[0].id == Export::View::Content::kDoneId) {
984 LOG(("Export Info: Destroy top bar by Done."));
985 destroyExportTopBar();
986 } else if (!_exportTopBar) {
987 LOG(("Export Info: Create top bar by State."));
988 createExportTopBar(std::move(data));
989 } else {
990 _exportTopBar->entity()->updateData(std::move(data));
991 }
992 }, _exportViewLifetime);
993 } else {
994 _exportViewLifetime.destroy();
995
996 LOG(("Export Info: Destroy top bar by controller removal."));
997 destroyExportTopBar();
998 }
999 }
1000
createExportTopBar(Export::View::Content && data)1001 void MainWidget::createExportTopBar(Export::View::Content &&data) {
1002 _exportTopBar.create(
1003 this,
1004 object_ptr<Export::View::TopBar>(this, std::move(data)),
1005 _controller->adaptive().oneColumnValue());
1006 _exportTopBar->entity()->clicks(
1007 ) | rpl::start_with_next([=] {
1008 if (_currentExportView) {
1009 _currentExportView->activatePanel();
1010 }
1011 }, _exportTopBar->lifetime());
1012 orderWidgets();
1013 if (_a_show.animating()) {
1014 _exportTopBar->show(anim::type::instant);
1015 _exportTopBar->setVisible(false);
1016 } else {
1017 _exportTopBar->hide(anim::type::instant);
1018 _exportTopBar->show(anim::type::normal);
1019 _exportTopBarHeight = _contentScrollAddToY = _exportTopBar->contentHeight();
1020 updateControlsGeometry();
1021 }
1022 rpl::merge(
1023 _exportTopBar->heightValue() | rpl::map_to(true),
1024 _exportTopBar->shownValue()
1025 ) | rpl::start_with_next([=] {
1026 exportTopBarHeightUpdated();
1027 }, _exportTopBar->lifetime());
1028 }
1029
destroyExportTopBar()1030 void MainWidget::destroyExportTopBar() {
1031 if (_exportTopBar) {
1032 _exportTopBar->hide(anim::type::normal);
1033 }
1034 }
1035
exportTopBarHeightUpdated()1036 void MainWidget::exportTopBarHeightUpdated() {
1037 if (!_exportTopBar) {
1038 // Player could be already "destroyDelayed", but still handle events.
1039 return;
1040 }
1041 const auto exportTopBarHeight = _exportTopBar->contentHeight();
1042 if (exportTopBarHeight != _exportTopBarHeight) {
1043 _contentScrollAddToY += exportTopBarHeight - _exportTopBarHeight;
1044 _exportTopBarHeight = exportTopBarHeight;
1045 updateControlsGeometry();
1046 }
1047 if (!_exportTopBarHeight && _exportTopBar->isHidden()) {
1048 _exportTopBar.destroyDelayed();
1049 }
1050 }
1051
inlineResultLoadProgress(FileLoader * loader)1052 void MainWidget::inlineResultLoadProgress(FileLoader *loader) {
1053 //InlineBots::Result *result = InlineBots::resultFromLoader(loader);
1054 //if (!result) return;
1055
1056 //result->loaded();
1057
1058 //Ui::repaintInlineItem();
1059 }
1060
inlineResultLoadFailed(FileLoader * loader,bool started)1061 void MainWidget::inlineResultLoadFailed(FileLoader *loader, bool started) {
1062 //InlineBots::Result *result = InlineBots::resultFromLoader(loader);
1063 //if (!result) return;
1064
1065 //result->loaded();
1066
1067 //Ui::repaintInlineItem();
1068 }
1069
sendMenuType() const1070 SendMenu::Type MainWidget::sendMenuType() const {
1071 return _history->sendMenuType();
1072 }
1073
sendExistingDocument(not_null<DocumentData * > document)1074 bool MainWidget::sendExistingDocument(not_null<DocumentData*> document) {
1075 return sendExistingDocument(document, Api::SendOptions());
1076 }
1077
sendExistingDocument(not_null<DocumentData * > document,Api::SendOptions options)1078 bool MainWidget::sendExistingDocument(
1079 not_null<DocumentData*> document,
1080 Api::SendOptions options) {
1081 return _history->sendExistingDocument(document, options);
1082 }
1083
dialogsCancelled()1084 void MainWidget::dialogsCancelled() {
1085 if (_hider) {
1086 _hider->startHide();
1087 clearHider(_hider);
1088 }
1089 _history->activate();
1090 }
1091
setChatBackground(const Data::WallPaper & background,QImage && image)1092 void MainWidget::setChatBackground(
1093 const Data::WallPaper &background,
1094 QImage &&image) {
1095 using namespace Window::Theme;
1096
1097 if (isReadyChatBackground(background, image)) {
1098 setReadyChatBackground(background, std::move(image));
1099 return;
1100 }
1101
1102 _background = std::make_unique<SettingBackground>(background);
1103 if (const auto document = _background->data.document()) {
1104 _background->dataMedia = document->createMediaView();
1105 _background->dataMedia->thumbnailWanted(
1106 _background->data.fileOrigin());
1107 }
1108 _background->data.loadDocument();
1109 checkChatBackground();
1110
1111 const auto tile = Data::IsLegacy1DefaultWallPaper(background);
1112 Window::Theme::Background()->downloadingStarted(tile);
1113 }
1114
isReadyChatBackground(const Data::WallPaper & background,const QImage & image) const1115 bool MainWidget::isReadyChatBackground(
1116 const Data::WallPaper &background,
1117 const QImage &image) const {
1118 return !image.isNull() || !background.document();
1119 }
1120
setReadyChatBackground(const Data::WallPaper & background,QImage && image)1121 void MainWidget::setReadyChatBackground(
1122 const Data::WallPaper &background,
1123 QImage &&image) {
1124 using namespace Window::Theme;
1125
1126 if (image.isNull()
1127 && !background.document()
1128 && background.localThumbnail()) {
1129 image = background.localThumbnail()->original();
1130 }
1131
1132 const auto resetToDefault = image.isNull()
1133 && !background.document()
1134 && background.backgroundColors().empty()
1135 && !Data::IsLegacy1DefaultWallPaper(background);
1136 const auto ready = resetToDefault
1137 ? Data::DefaultWallPaper()
1138 : background;
1139
1140 Background()->set(ready, std::move(image));
1141 const auto tile = Data::IsLegacy1DefaultWallPaper(ready);
1142 Background()->setTile(tile);
1143 Ui::ForceFullRepaint(this);
1144 }
1145
chatBackgroundLoading()1146 bool MainWidget::chatBackgroundLoading() {
1147 return (_background != nullptr);
1148 }
1149
chatBackgroundProgress() const1150 float64 MainWidget::chatBackgroundProgress() const {
1151 if (_background) {
1152 if (_background->generating) {
1153 return 1.;
1154 } else if (const auto document = _background->data.document()) {
1155 return _background->dataMedia->progress();
1156 }
1157 }
1158 return 1.;
1159 }
1160
checkChatBackground()1161 void MainWidget::checkChatBackground() {
1162 if (!_background || _background->generating) {
1163 return;
1164 }
1165 const auto &media = _background->dataMedia;
1166 Assert(media != nullptr);
1167 if (!media->loaded()) {
1168 return;
1169 }
1170
1171 const auto document = _background->data.document();
1172 Assert(document != nullptr);
1173
1174 const auto generateCallback = [=](QImage &&image) {
1175 const auto background = base::take(_background);
1176 const auto ready = image.isNull()
1177 ? Data::DefaultWallPaper()
1178 : background->data;
1179 setReadyChatBackground(ready, std::move(image));
1180 };
1181 _background->generating = Data::ReadBackgroundImageAsync(
1182 media.get(),
1183 Ui::PreprocessBackgroundImage,
1184 generateCallback);
1185 }
1186
newBackgroundThumb()1187 Image *MainWidget::newBackgroundThumb() {
1188 return !_background
1189 ? nullptr
1190 : _background->data.localThumbnail()
1191 ? _background->data.localThumbnail()
1192 : _background->dataMedia
1193 ? _background->dataMedia->thumbnail()
1194 : nullptr;
1195 }
1196
setInnerFocus()1197 void MainWidget::setInnerFocus() {
1198 if (_hider || !_history->peer()) {
1199 if (!_hider && _mainSection) {
1200 _mainSection->setInnerFocus();
1201 } else if (!_hider && _thirdSection) {
1202 _thirdSection->setInnerFocus();
1203 } else {
1204 _dialogs->setInnerFocus();
1205 }
1206 } else if (_mainSection) {
1207 _mainSection->setInnerFocus();
1208 } else if (_history->peer() || !_thirdSection) {
1209 _history->setInnerFocus();
1210 } else {
1211 _thirdSection->setInnerFocus();
1212 }
1213 }
1214
choosePeer(PeerId peerId,MsgId showAtMsgId)1215 void MainWidget::choosePeer(PeerId peerId, MsgId showAtMsgId) {
1216 if (selectingPeer()) {
1217 _hider->offerPeer(peerId);
1218 } else if (peerId) {
1219 Ui::showPeerHistory(session().data().peer(peerId), showAtMsgId);
1220 } else {
1221 Ui::showChatsList(&session());
1222 }
1223 }
1224
clearBotStartToken(PeerData * peer)1225 void MainWidget::clearBotStartToken(PeerData *peer) {
1226 if (peer && peer->isUser() && peer->asUser()->isBot()) {
1227 peer->asUser()->botInfo->startToken = QString();
1228 }
1229 }
1230
ctrlEnterSubmitUpdated()1231 void MainWidget::ctrlEnterSubmitUpdated() {
1232 _history->updateFieldSubmitSettings();
1233 }
1234
showChooseReportMessages(not_null<PeerData * > peer,Ui::ReportReason reason,Fn<void (MessageIdsList)> done)1235 void MainWidget::showChooseReportMessages(
1236 not_null<PeerData*> peer,
1237 Ui::ReportReason reason,
1238 Fn<void(MessageIdsList)> done) {
1239 _history->setChooseReportMessagesDetails(reason, std::move(done));
1240 ui_showPeerHistory(
1241 peer->id,
1242 SectionShow::Way::Forward,
1243 ShowForChooseMessagesMsgId);
1244 Ui::ShowMultilineToast({
1245 .text = { tr::lng_report_please_select_messages(tr::now) },
1246 });
1247 }
1248
clearChooseReportMessages()1249 void MainWidget::clearChooseReportMessages() {
1250 _history->setChooseReportMessagesDetails({}, nullptr);
1251 }
1252
toggleChooseChatTheme(not_null<PeerData * > peer)1253 void MainWidget::toggleChooseChatTheme(not_null<PeerData*> peer) {
1254 _history->toggleChooseChatTheme(peer);
1255 }
1256
ui_showPeerHistory(PeerId peerId,const SectionShow & params,MsgId showAtMsgId)1257 void MainWidget::ui_showPeerHistory(
1258 PeerId peerId,
1259 const SectionShow ¶ms,
1260 MsgId showAtMsgId) {
1261
1262 if (auto peer = session().data().peerLoaded(peerId)) {
1263 if (peer->migrateTo()) {
1264 peer = peer->migrateTo();
1265 peerId = peer->id;
1266 if (showAtMsgId > 0) showAtMsgId = -showAtMsgId;
1267 }
1268 const auto unavailable = peer->computeUnavailableReason();
1269 if (!unavailable.isEmpty()) {
1270 if (params.activation != anim::activation::background) {
1271 Ui::show(Box<Ui::InformBox>(unavailable));
1272 }
1273 return;
1274 }
1275 }
1276 if (IsServerMsgId(showAtMsgId)
1277 && _mainSection
1278 && _mainSection->showMessage(peerId, params, showAtMsgId)) {
1279 return;
1280 }
1281
1282 if (!(_history->peer() && _history->peer()->id == peerId)
1283 && preventsCloseSection(
1284 [=] { ui_showPeerHistory(peerId, params, showAtMsgId); },
1285 params)) {
1286 return;
1287 }
1288
1289 using OriginMessage = SectionShow::OriginMessage;
1290 if (const auto origin = std::get_if<OriginMessage>(¶ms.origin)) {
1291 if (const auto returnTo = session().data().message(origin->id)) {
1292 if (returnTo->history()->peer->id == peerId) {
1293 _history->pushReplyReturn(returnTo);
1294 }
1295 }
1296 }
1297
1298 _controller->dialogsListFocused().set(false, true);
1299 _a_dialogsWidth.stop();
1300
1301 using Way = SectionShow::Way;
1302 auto way = params.way;
1303 bool back = (way == Way::Backward || !peerId);
1304 bool foundInStack = !peerId;
1305 if (foundInStack || (way == Way::ClearStack)) {
1306 for (const auto &item : _stack) {
1307 clearBotStartToken(item->peer());
1308 }
1309 _stack.clear();
1310 } else {
1311 for (auto i = 0, s = int(_stack.size()); i < s; ++i) {
1312 if (_stack.at(i)->type() == HistoryStackItem && _stack.at(i)->peer()->id == peerId) {
1313 foundInStack = true;
1314 while (int(_stack.size()) > i + 1) {
1315 clearBotStartToken(_stack.back()->peer());
1316 _stack.pop_back();
1317 }
1318 _stack.pop_back();
1319 if (!back) {
1320 back = true;
1321 }
1322 break;
1323 }
1324 }
1325 if (const auto activeChat = _controller->activeChatCurrent()) {
1326 if (const auto peer = activeChat.peer()) {
1327 if (way == Way::Forward && peer->id == peerId) {
1328 way = _mainSection ? Way::Backward : Way::ClearStack;
1329 }
1330 }
1331 }
1332 }
1333
1334 const auto wasActivePeer = _controller->activeChatCurrent().peer();
1335 if (params.activation != anim::activation::background) {
1336 Ui::hideSettingsAndLayer();
1337 }
1338 if (_hider) {
1339 _hider->startHide();
1340 _hider.release();
1341 controller()->setSelectingPeer(false);
1342 }
1343
1344 auto animatedShow = [&] {
1345 if (_a_show.animating()
1346 || Core::App().passcodeLocked()
1347 || (params.animated == anim::type::instant)) {
1348 return false;
1349 }
1350 if (!peerId) {
1351 if (isOneColumn()) {
1352 return _dialogs->isHidden();
1353 } else {
1354 return false;
1355 }
1356 }
1357 if (_history->isHidden()) {
1358 if (!isOneColumn() && way == Way::ClearStack) {
1359 return false;
1360 }
1361 return (_mainSection != nullptr)
1362 || (isOneColumn() && !_dialogs->isHidden());
1363 }
1364 if (back || way == Way::Forward) {
1365 return true;
1366 }
1367 return false;
1368 };
1369
1370 auto animationParams = animatedShow() ? prepareHistoryAnimation(peerId) : Window::SectionSlideParams();
1371
1372 if (!back && (way != Way::ClearStack)) {
1373 // This may modify the current section, for example remove its contents.
1374 saveSectionInStack();
1375 }
1376
1377 if (_history->peer() && _history->peer()->id != peerId && way != Way::Forward) {
1378 clearBotStartToken(_history->peer());
1379 }
1380 _history->showHistory(peerId, showAtMsgId);
1381
1382 auto noPeer = !_history->peer();
1383 auto onlyDialogs = noPeer && isOneColumn();
1384 _mainSection.destroy();
1385
1386 updateControlsGeometry();
1387
1388 if (noPeer) {
1389 _controller->setActiveChatEntry(Dialogs::Key());
1390 }
1391
1392 if (onlyDialogs) {
1393 _history->hide();
1394 if (!_a_show.animating()) {
1395 if (animationParams) {
1396 auto direction = back ? Window::SlideDirection::FromLeft : Window::SlideDirection::FromRight;
1397 _dialogs->showAnimated(direction, animationParams);
1398 } else {
1399 _dialogs->showFast();
1400 }
1401 }
1402 } else {
1403 const auto nowActivePeer = _controller->activeChatCurrent().peer();
1404 if (nowActivePeer && nowActivePeer != wasActivePeer) {
1405 session().api().views().removeIncremented(nowActivePeer);
1406 }
1407 if (isOneColumn() && !_dialogs->isHidden()) {
1408 _dialogs->hide();
1409 }
1410 if (!_a_show.animating()) {
1411 if (!animationParams.oldContentCache.isNull()) {
1412 _history->showAnimated(
1413 back
1414 ? Window::SlideDirection::FromLeft
1415 : Window::SlideDirection::FromRight,
1416 animationParams);
1417 } else {
1418 _history->show();
1419 crl::on_main(this, [=] {
1420 _controller->widget()->setInnerFocus();
1421 });
1422 }
1423 }
1424 }
1425
1426 if (!_dialogs->isHidden()) {
1427 if (!back) {
1428 if (const auto history = _history->history()) {
1429 _dialogs->scrollToEntry(Dialogs::RowDescriptor(
1430 history,
1431 FullMsgId(history->channelId(), showAtMsgId)));
1432 }
1433 }
1434 _dialogs->update();
1435 }
1436
1437 floatPlayerCheckVisibility();
1438 }
1439
peer()1440 PeerData *MainWidget::peer() {
1441 return _history->peer();
1442 }
1443
saveSectionInStack()1444 void MainWidget::saveSectionInStack() {
1445 if (_mainSection) {
1446 if (auto memento = _mainSection->createMemento()) {
1447 _stack.push_back(std::make_unique<StackItemSection>(
1448 std::move(memento)));
1449 _stack.back()->setThirdSectionWeak(_thirdSection.data());
1450 }
1451 } else if (const auto history = _history->history()) {
1452 _stack.push_back(std::make_unique<StackItemHistory>(
1453 history,
1454 _history->msgId(),
1455 _history->replyReturns()));
1456 _stack.back()->setThirdSectionWeak(_thirdSection.data());
1457 }
1458 }
1459
showSection(std::shared_ptr<Window::SectionMemento> memento,const SectionShow & params)1460 void MainWidget::showSection(
1461 std::shared_ptr<Window::SectionMemento> memento,
1462 const SectionShow ¶ms) {
1463 if (_mainSection && _mainSection->showInternal(
1464 memento.get(),
1465 params)) {
1466 if (const auto entry = _mainSection->activeChat(); entry.key) {
1467 _controller->setActiveChatEntry(entry);
1468 }
1469 return;
1470 //
1471 // Now third section handles only its own showSection() requests.
1472 // General showSection() should show layer or main_section instead.
1473 //
1474 //} else if (_thirdSection && _thirdSection->showInternal(
1475 // &memento,
1476 // params)) {
1477 // return;
1478 }
1479
1480 if (preventsCloseSection(
1481 [=] { showSection(memento, params); },
1482 params)) {
1483 return;
1484 }
1485
1486 // If the window was not resized, but we've enabled
1487 // tabbedSelectorSectionEnabled or thirdSectionInfoEnabled
1488 // we need to update adaptive layout to Adaptive::ThirdColumn().
1489 updateColumnLayout();
1490
1491 showNewSection(std::move(memento), params);
1492 }
1493
updateColumnLayout()1494 void MainWidget::updateColumnLayout() {
1495 updateWindowAdaptiveLayout();
1496 }
1497
prepareThirdSectionAnimation(Window::SectionWidget * section)1498 Window::SectionSlideParams MainWidget::prepareThirdSectionAnimation(Window::SectionWidget *section) {
1499 Expects(_thirdSection != nullptr);
1500
1501 Window::SectionSlideParams result;
1502 result.withTopBarShadow = section->hasTopBarShadow();
1503 if (!_thirdSection->hasTopBarShadow()) {
1504 result.withTopBarShadow = false;
1505 }
1506 floatPlayerHideAll();
1507 result.oldContentCache = _thirdSection->grabForShowAnimation(result);
1508 floatPlayerShowVisible();
1509 return result;
1510 }
1511
prepareShowAnimation(bool willHaveTopBarShadow)1512 Window::SectionSlideParams MainWidget::prepareShowAnimation(
1513 bool willHaveTopBarShadow) {
1514 Window::SectionSlideParams result;
1515 result.withTopBarShadow = willHaveTopBarShadow;
1516 if (selectingPeer() && isOneColumn()) {
1517 result.withTopBarShadow = false;
1518 } else if (_mainSection) {
1519 if (!_mainSection->hasTopBarShadow()) {
1520 result.withTopBarShadow = false;
1521 }
1522 } else if (!_history->peer()) {
1523 result.withTopBarShadow = false;
1524 }
1525
1526 floatPlayerHideAll();
1527 if (_player) {
1528 _player->hideShadow();
1529 }
1530 auto playerVolumeVisible = _playerVolume && !_playerVolume->isHidden();
1531 if (playerVolumeVisible) {
1532 _playerVolume->hide();
1533 }
1534 auto playerPlaylistVisible = !_playerPlaylist->isHidden();
1535 if (playerPlaylistVisible) {
1536 _playerPlaylist->hide();
1537 }
1538
1539 auto sectionTop = getMainSectionTop();
1540 if (selectingPeer() && isOneColumn()) {
1541 result.oldContentCache = Ui::GrabWidget(this, QRect(
1542 0,
1543 sectionTop,
1544 _dialogsWidth,
1545 height() - sectionTop));
1546 } else if (_mainSection) {
1547 result.oldContentCache = _mainSection->grabForShowAnimation(result);
1548 } else if (!isOneColumn() || !_history->isHidden()) {
1549 result.oldContentCache = _history->grabForShowAnimation(result);
1550 } else {
1551 result.oldContentCache = Ui::GrabWidget(this, QRect(
1552 0,
1553 sectionTop,
1554 _dialogsWidth,
1555 height() - sectionTop));
1556 }
1557
1558 if (playerVolumeVisible) {
1559 _playerVolume->show();
1560 }
1561 if (playerPlaylistVisible) {
1562 _playerPlaylist->show();
1563 }
1564 if (_player) {
1565 _player->showShadow();
1566 }
1567 floatPlayerShowVisible();
1568
1569 return result;
1570 }
1571
prepareMainSectionAnimation(Window::SectionWidget * section)1572 Window::SectionSlideParams MainWidget::prepareMainSectionAnimation(Window::SectionWidget *section) {
1573 return prepareShowAnimation(section->hasTopBarShadow());
1574 }
1575
prepareHistoryAnimation(PeerId historyPeerId)1576 Window::SectionSlideParams MainWidget::prepareHistoryAnimation(PeerId historyPeerId) {
1577 return prepareShowAnimation(historyPeerId != 0);
1578 }
1579
prepareDialogsAnimation()1580 Window::SectionSlideParams MainWidget::prepareDialogsAnimation() {
1581 return prepareShowAnimation(false);
1582 }
1583
showNewSection(std::shared_ptr<Window::SectionMemento> memento,const SectionShow & params)1584 void MainWidget::showNewSection(
1585 std::shared_ptr<Window::SectionMemento> memento,
1586 const SectionShow ¶ms) {
1587 using Column = Window::Column;
1588
1589 auto saveInStack = (params.way == SectionShow::Way::Forward);
1590 const auto thirdSectionTop = getThirdSectionTop();
1591 const auto newThirdGeometry = QRect(
1592 width() - st::columnMinimalWidthThird,
1593 thirdSectionTop,
1594 st::columnMinimalWidthThird,
1595 height() - thirdSectionTop);
1596 auto newThirdSection = (isThreeColumn() && params.thirdColumn)
1597 ? memento->createWidget(
1598 this,
1599 _controller,
1600 Column::Third,
1601 newThirdGeometry)
1602 : nullptr;
1603 const auto layerRect = parentWidget()->rect();
1604 if (newThirdSection) {
1605 saveInStack = false;
1606 } else if (auto layer = memento->createLayer(_controller, layerRect)) {
1607 if (params.activation != anim::activation::background) {
1608 Ui::hideLayer(anim::type::instant);
1609 }
1610 _controller->showSpecialLayer(std::move(layer));
1611 return;
1612 }
1613
1614 if (params.activation != anim::activation::background) {
1615 Ui::hideSettingsAndLayer();
1616 }
1617
1618 _controller->dialogsListFocused().set(false, true);
1619 _a_dialogsWidth.stop();
1620
1621 auto mainSectionTop = getMainSectionTop();
1622 auto newMainGeometry = QRect(
1623 _history->x(),
1624 mainSectionTop,
1625 _history->width(),
1626 height() - mainSectionTop);
1627 auto newMainSection = newThirdSection
1628 ? nullptr
1629 : memento->createWidget(
1630 this,
1631 _controller,
1632 isOneColumn() ? Column::First : Column::Second,
1633 newMainGeometry);
1634 Assert(newMainSection || newThirdSection);
1635
1636 auto animatedShow = [&] {
1637 if (_a_show.animating()
1638 || Core::App().passcodeLocked()
1639 || (params.animated == anim::type::instant)
1640 || memento->instant()) {
1641 return false;
1642 }
1643 if (!isOneColumn() && params.way == SectionShow::Way::ClearStack) {
1644 return false;
1645 } else if (isOneColumn()
1646 || (newThirdSection && _thirdSection)
1647 || (newMainSection && isMainSectionShown())) {
1648 return true;
1649 }
1650 return false;
1651 }();
1652 auto animationParams = animatedShow
1653 ? (newThirdSection
1654 ? prepareThirdSectionAnimation(newThirdSection)
1655 : prepareMainSectionAnimation(newMainSection))
1656 : Window::SectionSlideParams();
1657
1658 setFocus(); // otherwise dialogs widget could be focused.
1659
1660 if (saveInStack) {
1661 // This may modify the current section, for example remove its contents.
1662 saveSectionInStack();
1663 }
1664 auto &settingSection = newThirdSection
1665 ? _thirdSection
1666 : _mainSection;
1667 if (newThirdSection) {
1668 _thirdSection = std::move(newThirdSection);
1669 if (!_thirdShadow) {
1670 _thirdShadow.create(this);
1671 _thirdShadow->show();
1672 orderWidgets();
1673 }
1674 updateControlsGeometry();
1675 } else {
1676 _mainSection = std::move(newMainSection);
1677 updateControlsGeometry();
1678 _history->finishAnimating();
1679 _history->showHistory(0, 0);
1680 _history->hide();
1681 if (isOneColumn()) _dialogs->hide();
1682 }
1683
1684 if (animationParams) {
1685 auto back = (params.way == SectionShow::Way::Backward);
1686 auto direction = (back || settingSection->forceAnimateBack())
1687 ? Window::SlideDirection::FromLeft
1688 : Window::SlideDirection::FromRight;
1689 if (isOneColumn()) {
1690 _controller->removeLayerBlackout();
1691 }
1692 settingSection->showAnimated(direction, animationParams);
1693 } else {
1694 settingSection->showFast();
1695 }
1696
1697 if (settingSection.data() == _mainSection.data()) {
1698 if (const auto entry = _mainSection->activeChat(); entry.key) {
1699 _controller->setActiveChatEntry(entry);
1700 }
1701 }
1702
1703 floatPlayerCheckVisibility();
1704 orderWidgets();
1705 }
1706
checkMainSectionToLayer()1707 void MainWidget::checkMainSectionToLayer() {
1708 if (!_mainSection) {
1709 return;
1710 }
1711 Ui::FocusPersister persister(this);
1712 if (auto layer = _mainSection->moveContentToLayer(rect())) {
1713 dropMainSection(_mainSection);
1714 _controller->showSpecialLayer(
1715 std::move(layer),
1716 anim::type::instant);
1717 }
1718 }
1719
dropMainSection(Window::SectionWidget * widget)1720 void MainWidget::dropMainSection(Window::SectionWidget *widget) {
1721 if (_mainSection != widget) {
1722 return;
1723 }
1724 _mainSection.destroy();
1725 _controller->showBackFromStack(
1726 SectionShow(
1727 anim::type::instant,
1728 anim::activation::background));
1729 }
1730
isMainSectionShown() const1731 bool MainWidget::isMainSectionShown() const {
1732 return _mainSection || _history->peer();
1733 }
1734
isThirdSectionShown() const1735 bool MainWidget::isThirdSectionShown() const {
1736 return _thirdSection != nullptr;
1737 }
1738
stackIsEmpty() const1739 bool MainWidget::stackIsEmpty() const {
1740 return _stack.empty();
1741 }
1742
preventsCloseSection(Fn<void ()> callback) const1743 bool MainWidget::preventsCloseSection(Fn<void()> callback) const {
1744 if (Core::App().passcodeLocked()) {
1745 return false;
1746 }
1747 auto copy = callback;
1748 return (_mainSection && _mainSection->preventsClose(std::move(copy)))
1749 || (_history && _history->preventsClose(std::move(callback)));
1750 }
1751
preventsCloseSection(Fn<void ()> callback,const SectionShow & params) const1752 bool MainWidget::preventsCloseSection(
1753 Fn<void()> callback,
1754 const SectionShow ¶ms) const {
1755 return params.thirdColumn
1756 ? false
1757 : preventsCloseSection(std::move(callback));
1758 }
1759
showBackFromStack(const SectionShow & params)1760 void MainWidget::showBackFromStack(
1761 const SectionShow ¶ms) {
1762
1763 if (preventsCloseSection([=] { showBackFromStack(params); }, params)) {
1764 return;
1765 }
1766
1767 if (selectingPeer()) {
1768 return;
1769 } else if (_stack.empty()) {
1770 _controller->clearSectionStack(params);
1771 crl::on_main(this, [=] {
1772 _controller->widget()->setInnerFocus();
1773 });
1774 return;
1775 }
1776 auto item = std::move(_stack.back());
1777 _stack.pop_back();
1778 if (auto currentHistoryPeer = _history->peer()) {
1779 clearBotStartToken(currentHistoryPeer);
1780 }
1781 _thirdSectionFromStack = item->takeThirdSectionMemento();
1782 if (item->type() == HistoryStackItem) {
1783 auto historyItem = static_cast<StackItemHistory*>(item.get());
1784 _controller->showPeerHistory(
1785 historyItem->peer()->id,
1786 params.withWay(SectionShow::Way::Backward),
1787 ShowAtUnreadMsgId);
1788 _history->setReplyReturns(historyItem->peer()->id, historyItem->replyReturns);
1789 } else if (item->type() == SectionStackItem) {
1790 auto sectionItem = static_cast<StackItemSection*>(item.get());
1791 showNewSection(
1792 sectionItem->takeMemento(),
1793 params.withWay(SectionShow::Way::Backward));
1794 }
1795 if (_thirdSectionFromStack && _thirdSection) {
1796 _controller->showSection(
1797 base::take(_thirdSectionFromStack),
1798 SectionShow(
1799 SectionShow::Way::ClearStack,
1800 anim::type::instant,
1801 anim::activation::background));
1802
1803 }
1804 }
1805
orderWidgets()1806 void MainWidget::orderWidgets() {
1807 _dialogs->raise();
1808 if (_player) {
1809 _player->raise();
1810 }
1811 if (_exportTopBar) {
1812 _exportTopBar->raise();
1813 }
1814 if (_callTopBar) {
1815 _callTopBar->raise();
1816 }
1817 if (_playerVolume) {
1818 _playerVolume->raise();
1819 }
1820 _sideShadow->raise();
1821 if (_thirdShadow) {
1822 _thirdShadow->raise();
1823 }
1824 if (_firstColumnResizeArea) {
1825 _firstColumnResizeArea->raise();
1826 }
1827 if (_thirdColumnResizeArea) {
1828 _thirdColumnResizeArea->raise();
1829 }
1830 _connecting->raise();
1831 _playerPlaylist->raise();
1832 floatPlayerRaiseAll();
1833 if (_hider) _hider->raise();
1834 }
1835
historyRect() const1836 QRect MainWidget::historyRect() const {
1837 QRect r(_history->historyRect());
1838 r.moveLeft(r.left() + _history->x());
1839 r.moveTop(r.top() + _history->y());
1840 return r;
1841 }
1842
grabForShowAnimation(const Window::SectionSlideParams & params)1843 QPixmap MainWidget::grabForShowAnimation(const Window::SectionSlideParams ¶ms) {
1844 QPixmap result;
1845 floatPlayerHideAll();
1846 if (_player) {
1847 _player->hideShadow();
1848 }
1849 auto playerVolumeVisible = _playerVolume && !_playerVolume->isHidden();
1850 if (playerVolumeVisible) {
1851 _playerVolume->hide();
1852 }
1853 auto playerPlaylistVisible = !_playerPlaylist->isHidden();
1854 if (playerPlaylistVisible) {
1855 _playerPlaylist->hide();
1856 }
1857
1858 auto sectionTop = getMainSectionTop();
1859 if (isOneColumn()) {
1860 result = Ui::GrabWidget(this, QRect(
1861 0,
1862 sectionTop,
1863 _dialogsWidth,
1864 height() - sectionTop));
1865 } else {
1866 _sideShadow->hide();
1867 if (_thirdShadow) {
1868 _thirdShadow->hide();
1869 }
1870 result = Ui::GrabWidget(this, QRect(
1871 _dialogsWidth,
1872 sectionTop,
1873 width() - _dialogsWidth,
1874 height() - sectionTop));
1875 _sideShadow->show();
1876 if (_thirdShadow) {
1877 _thirdShadow->show();
1878 }
1879 }
1880 if (playerVolumeVisible) {
1881 _playerVolume->show();
1882 }
1883 if (playerPlaylistVisible) {
1884 _playerPlaylist->show();
1885 }
1886 if (_player) {
1887 _player->showShadow();
1888 }
1889 floatPlayerShowVisible();
1890 return result;
1891 }
1892
windowShown()1893 void MainWidget::windowShown() {
1894 _history->windowShown();
1895 }
1896
dialogsToUp()1897 void MainWidget::dialogsToUp() {
1898 _dialogs->jumpToTop();
1899 }
1900
checkHistoryActivation()1901 void MainWidget::checkHistoryActivation() {
1902 _history->checkHistoryActivation();
1903 }
1904
showAnimated(const QPixmap & bgAnimCache,bool back)1905 void MainWidget::showAnimated(const QPixmap &bgAnimCache, bool back) {
1906 _showBack = back;
1907 (_showBack ? _cacheOver : _cacheUnder) = bgAnimCache;
1908
1909 _a_show.stop();
1910
1911 showAll();
1912 floatPlayerHideAll();
1913 (_showBack ? _cacheUnder : _cacheOver) = Ui::GrabWidget(this);
1914 hideAll();
1915 floatPlayerShowVisible();
1916
1917 _a_show.start(
1918 [this] { animationCallback(); },
1919 0.,
1920 1.,
1921 st::slideDuration,
1922 Window::SlideAnimation::transition());
1923
1924 show();
1925 }
1926
animationCallback()1927 void MainWidget::animationCallback() {
1928 update();
1929 if (!_a_show.animating()) {
1930 _cacheUnder = _cacheOver = QPixmap();
1931
1932 showAll();
1933 activate();
1934 }
1935 }
1936
paintEvent(QPaintEvent * e)1937 void MainWidget::paintEvent(QPaintEvent *e) {
1938 if (_background) {
1939 checkChatBackground();
1940 }
1941
1942 Painter p(this);
1943 auto progress = _a_show.value(1.);
1944 if (_a_show.animating()) {
1945 auto coordUnder = _showBack ? anim::interpolate(-st::slideShift, 0, progress) : anim::interpolate(0, -st::slideShift, progress);
1946 auto coordOver = _showBack ? anim::interpolate(0, width(), progress) : anim::interpolate(width(), 0, progress);
1947 auto shadow = _showBack ? (1. - progress) : progress;
1948 if (coordOver > 0) {
1949 p.drawPixmap(QRect(0, 0, coordOver, height()), _cacheUnder, QRect(-coordUnder * cRetinaFactor(), 0, coordOver * cRetinaFactor(), height() * cRetinaFactor()));
1950 p.setOpacity(shadow);
1951 p.fillRect(0, 0, coordOver, height(), st::slideFadeOutBg);
1952 p.setOpacity(1);
1953 }
1954 p.drawPixmap(coordOver, 0, _cacheOver);
1955 p.setOpacity(shadow);
1956 st::slideShadow.fill(p, QRect(coordOver - st::slideShadow.width(), 0, st::slideShadow.width(), height()));
1957 }
1958 }
1959
getMainSectionTop() const1960 int MainWidget::getMainSectionTop() const {
1961 return _callTopBarHeight + _exportTopBarHeight + _playerHeight;
1962 }
1963
getThirdSectionTop() const1964 int MainWidget::getThirdSectionTop() const {
1965 return 0;
1966 }
1967
hideAll()1968 void MainWidget::hideAll() {
1969 _dialogs->hide();
1970 _history->hide();
1971 if (_mainSection) {
1972 _mainSection->hide();
1973 }
1974 if (_thirdSection) {
1975 _thirdSection->hide();
1976 }
1977 _sideShadow->hide();
1978 if (_thirdShadow) {
1979 _thirdShadow->hide();
1980 }
1981 if (_player) {
1982 _player->setVisible(false);
1983 _playerHeight = 0;
1984 }
1985 if (_callTopBar) {
1986 _callTopBar->setVisible(false);
1987 _callTopBarHeight = 0;
1988 }
1989 }
1990
showAll()1991 void MainWidget::showAll() {
1992 if (cPasswordRecovered()) {
1993 cSetPasswordRecovered(false);
1994 Ui::show(Box<Ui::InformBox>(tr::lng_cloud_password_updated(tr::now)));
1995 }
1996 if (isOneColumn()) {
1997 _sideShadow->hide();
1998 if (_hider) {
1999 _hider->hide();
2000 }
2001 if (selectingPeer()) {
2002 _dialogs->showFast();
2003 _history->hide();
2004 if (_mainSection) _mainSection->hide();
2005 } else if (_mainSection) {
2006 _mainSection->show();
2007 } else if (_history->peer()) {
2008 _history->show();
2009 _history->updateControlsGeometry();
2010 } else {
2011 _dialogs->showFast();
2012 _history->hide();
2013 }
2014 if (!selectingPeer()) {
2015 if (_mainSection) {
2016 _dialogs->hide();
2017 } else if (isMainSectionShown()) {
2018 _dialogs->hide();
2019 }
2020 }
2021 } else {
2022 _sideShadow->show();
2023 if (_hider) {
2024 _hider->show();
2025 }
2026 _dialogs->showFast();
2027 if (_mainSection) {
2028 _mainSection->show();
2029 } else {
2030 _history->show();
2031 _history->updateControlsGeometry();
2032 }
2033 if (_thirdSection) {
2034 _thirdSection->show();
2035 }
2036 if (_thirdShadow) {
2037 _thirdShadow->show();
2038 }
2039 }
2040 if (_player) {
2041 _player->setVisible(true);
2042 _playerHeight = _player->contentHeight();
2043 }
2044 if (_callTopBar) {
2045 _callTopBar->setVisible(true);
2046
2047 // show() could've send pending resize event that would update
2048 // the height value and destroy the top bar if it was hiding.
2049 if (_callTopBar) {
2050 _callTopBarHeight = _callTopBar->height();
2051 }
2052 }
2053 updateControlsGeometry();
2054 floatPlayerCheckVisibility();
2055
2056 _controller->widget()->checkHistoryActivation();
2057 }
2058
resizeEvent(QResizeEvent * e)2059 void MainWidget::resizeEvent(QResizeEvent *e) {
2060 updateControlsGeometry();
2061 }
2062
updateControlsGeometry()2063 void MainWidget::updateControlsGeometry() {
2064 updateWindowAdaptiveLayout();
2065 if (Core::App().settings().dialogsWidthRatio() > 0) {
2066 _a_dialogsWidth.stop();
2067 }
2068 if (!_a_dialogsWidth.animating()) {
2069 _dialogs->stopWidthAnimation();
2070 }
2071 if (isThreeColumn()) {
2072 if (!_thirdSection
2073 && !_controller->takeThirdSectionFromLayer()) {
2074 auto params = Window::SectionShow(
2075 Window::SectionShow::Way::ClearStack,
2076 anim::type::instant,
2077 anim::activation::background);
2078 const auto active = _controller->activeChatCurrent();
2079 if (const auto peer = active.peer()) {
2080 if (Core::App().settings().tabbedSelectorSectionEnabled()) {
2081 if (_mainSection) {
2082 _mainSection->pushTabbedSelectorToThirdSection(peer, params);
2083 } else {
2084 _history->pushTabbedSelectorToThirdSection(peer, params);
2085 }
2086 } else if (Core::App().settings().thirdSectionInfoEnabled()) {
2087 _controller->showSection(
2088 Info::Memento::Default(peer),
2089 params.withThirdColumn());
2090 }
2091 }
2092 }
2093 } else {
2094 _thirdSection.destroy();
2095 _thirdShadow.destroy();
2096 }
2097 auto mainSectionTop = getMainSectionTop();
2098 auto dialogsWidth = qRound(_a_dialogsWidth.value(_dialogsWidth));
2099 if (isOneColumn()) {
2100 if (_callTopBar) {
2101 _callTopBar->resizeToWidth(dialogsWidth);
2102 _callTopBar->moveToLeft(0, 0);
2103 }
2104 if (_exportTopBar) {
2105 _exportTopBar->resizeToWidth(dialogsWidth);
2106 _exportTopBar->moveToLeft(0, _callTopBarHeight);
2107 }
2108 if (_player) {
2109 _player->resizeToWidth(dialogsWidth);
2110 _player->moveToLeft(0, _callTopBarHeight + _exportTopBarHeight);
2111 }
2112 auto mainSectionGeometry = QRect(
2113 0,
2114 mainSectionTop,
2115 dialogsWidth,
2116 height() - mainSectionTop);
2117 _dialogs->setGeometryWithTopMoved(mainSectionGeometry, _contentScrollAddToY);
2118 _history->setGeometryWithTopMoved(mainSectionGeometry, _contentScrollAddToY);
2119 if (_hider) _hider->setGeometry(0, 0, dialogsWidth, height());
2120 } else {
2121 auto thirdSectionWidth = _thirdSection ? _thirdColumnWidth : 0;
2122 if (_thirdSection) {
2123 auto thirdSectionTop = getThirdSectionTop();
2124 _thirdSection->setGeometry(
2125 width() - thirdSectionWidth,
2126 thirdSectionTop,
2127 thirdSectionWidth,
2128 height() - thirdSectionTop);
2129 }
2130 accumulate_min(dialogsWidth, width() - st::columnMinimalWidthMain);
2131 auto mainSectionWidth = width() - dialogsWidth - thirdSectionWidth;
2132
2133 _dialogs->setGeometryToLeft(0, 0, dialogsWidth, height());
2134 const auto shadowTop = _controller->window().verticalShadowTop();
2135 const auto shadowHeight = height() - shadowTop;
2136 _sideShadow->setGeometryToLeft(
2137 dialogsWidth,
2138 shadowTop,
2139 st::lineWidth,
2140 shadowHeight);
2141 if (_thirdShadow) {
2142 _thirdShadow->setGeometryToLeft(
2143 width() - thirdSectionWidth - st::lineWidth,
2144 shadowTop,
2145 st::lineWidth,
2146 shadowHeight);
2147 }
2148 if (_callTopBar) {
2149 _callTopBar->resizeToWidth(mainSectionWidth);
2150 _callTopBar->moveToLeft(dialogsWidth, 0);
2151 }
2152 if (_exportTopBar) {
2153 _exportTopBar->resizeToWidth(mainSectionWidth);
2154 _exportTopBar->moveToLeft(dialogsWidth, _callTopBarHeight);
2155 }
2156 if (_player) {
2157 _player->resizeToWidth(mainSectionWidth);
2158 _player->moveToLeft(
2159 dialogsWidth,
2160 _callTopBarHeight + _exportTopBarHeight);
2161 }
2162 _history->setGeometryWithTopMoved({ dialogsWidth, mainSectionTop, mainSectionWidth, height() - mainSectionTop }, _contentScrollAddToY);
2163 if (_hider) {
2164 _hider->setGeometryToLeft(dialogsWidth, 0, mainSectionWidth, height());
2165 }
2166 }
2167 if (_mainSection) {
2168 auto mainSectionGeometry = QRect(_history->x(), mainSectionTop, _history->width(), height() - mainSectionTop);
2169 _mainSection->setGeometryWithTopMoved(mainSectionGeometry, _contentScrollAddToY);
2170 }
2171 refreshResizeAreas();
2172 updateMediaPlayerPosition();
2173 updateMediaPlaylistPosition(_playerPlaylist->x());
2174 _contentScrollAddToY = 0;
2175
2176 floatPlayerUpdatePositions();
2177 }
2178
refreshResizeAreas()2179 void MainWidget::refreshResizeAreas() {
2180 if (!isOneColumn()) {
2181 ensureFirstColumnResizeAreaCreated();
2182 _firstColumnResizeArea->setGeometryToLeft(
2183 _history->x(),
2184 0,
2185 st::historyResizeWidth,
2186 height());
2187 } else if (_firstColumnResizeArea) {
2188 _firstColumnResizeArea.destroy();
2189 }
2190
2191 if (isThreeColumn() && _thirdSection) {
2192 ensureThirdColumnResizeAreaCreated();
2193 _thirdColumnResizeArea->setGeometryToLeft(
2194 _thirdSection->x(),
2195 0,
2196 st::historyResizeWidth,
2197 height());
2198 } else if (_thirdColumnResizeArea) {
2199 _thirdColumnResizeArea.destroy();
2200 }
2201 }
2202
2203 template <typename MoveCallback, typename FinishCallback>
createResizeArea(object_ptr<Ui::ResizeArea> & area,MoveCallback && moveCallback,FinishCallback && finishCallback)2204 void MainWidget::createResizeArea(
2205 object_ptr<Ui::ResizeArea> &area,
2206 MoveCallback &&moveCallback,
2207 FinishCallback &&finishCallback) {
2208 area.create(this);
2209 area->show();
2210 area->addMoveLeftCallback(
2211 std::forward<MoveCallback>(moveCallback));
2212 area->addMoveFinishedCallback(
2213 std::forward<FinishCallback>(finishCallback));
2214 orderWidgets();
2215 }
2216
ensureFirstColumnResizeAreaCreated()2217 void MainWidget::ensureFirstColumnResizeAreaCreated() {
2218 if (_firstColumnResizeArea) {
2219 return;
2220 }
2221 auto moveLeftCallback = [=](int globalLeft) {
2222 auto newWidth = globalLeft - mapToGlobal(QPoint(0, 0)).x();
2223 auto newRatio = (newWidth < st::columnMinimalWidthLeft / 2)
2224 ? 0.
2225 : float64(newWidth) / width();
2226 Core::App().settings().setDialogsWidthRatio(newRatio);
2227 };
2228 auto moveFinishedCallback = [=] {
2229 if (isOneColumn()) {
2230 return;
2231 }
2232 if (Core::App().settings().dialogsWidthRatio() > 0) {
2233 Core::App().settings().setDialogsWidthRatio(
2234 float64(_dialogsWidth) / width());
2235 }
2236 Core::App().saveSettingsDelayed();
2237 };
2238 createResizeArea(
2239 _firstColumnResizeArea,
2240 std::move(moveLeftCallback),
2241 std::move(moveFinishedCallback));
2242 }
2243
ensureThirdColumnResizeAreaCreated()2244 void MainWidget::ensureThirdColumnResizeAreaCreated() {
2245 if (_thirdColumnResizeArea) {
2246 return;
2247 }
2248 auto moveLeftCallback = [=](int globalLeft) {
2249 auto newWidth = mapToGlobal(QPoint(width(), 0)).x() - globalLeft;
2250 Core::App().settings().setThirdColumnWidth(newWidth);
2251 };
2252 auto moveFinishedCallback = [=] {
2253 if (!isThreeColumn() || !_thirdSection) {
2254 return;
2255 }
2256 Core::App().settings().setThirdColumnWidth(std::clamp(
2257 Core::App().settings().thirdColumnWidth(),
2258 st::columnMinimalWidthThird,
2259 st::columnMaximalWidthThird));
2260 Core::App().saveSettingsDelayed();
2261 };
2262 createResizeArea(
2263 _thirdColumnResizeArea,
2264 std::move(moveLeftCallback),
2265 std::move(moveFinishedCallback));
2266 }
2267
updateDialogsWidthAnimated()2268 void MainWidget::updateDialogsWidthAnimated() {
2269 if (Core::App().settings().dialogsWidthRatio() > 0) {
2270 return;
2271 }
2272 auto dialogsWidth = _dialogsWidth;
2273 updateWindowAdaptiveLayout();
2274 if (!Core::App().settings().dialogsWidthRatio()
2275 && (_dialogsWidth != dialogsWidth
2276 || _a_dialogsWidth.animating())) {
2277 _dialogs->startWidthAnimation();
2278 _a_dialogsWidth.start(
2279 [this] { updateControlsGeometry(); },
2280 dialogsWidth,
2281 _dialogsWidth,
2282 st::dialogsWidthDuration,
2283 anim::easeOutCirc);
2284 updateControlsGeometry();
2285 }
2286 }
2287
saveThirdSectionToStackBack() const2288 bool MainWidget::saveThirdSectionToStackBack() const {
2289 return !_stack.empty()
2290 && _thirdSection != nullptr
2291 && _stack.back()->thirdSectionWeak() == _thirdSection.data();
2292 }
2293
thirdSectionForCurrentMainSection(Dialogs::Key key)2294 auto MainWidget::thirdSectionForCurrentMainSection(
2295 Dialogs::Key key)
2296 -> std::shared_ptr<Window::SectionMemento> {
2297 if (_thirdSectionFromStack) {
2298 return std::move(_thirdSectionFromStack);
2299 } else if (const auto peer = key.peer()) {
2300 return std::make_shared<Info::Memento>(
2301 peer,
2302 Info::Memento::DefaultSection(peer));
2303 }
2304 Unexpected("Key in MainWidget::thirdSectionForCurrentMainSection().");
2305 }
2306
updateThirdColumnToCurrentChat(Dialogs::Key key,bool canWrite)2307 void MainWidget::updateThirdColumnToCurrentChat(
2308 Dialogs::Key key,
2309 bool canWrite) {
2310 auto saveOldThirdSection = [&] {
2311 if (saveThirdSectionToStackBack()) {
2312 _stack.back()->setThirdSectionMemento(
2313 _thirdSection->createMemento());
2314 _thirdSection.destroy();
2315 }
2316 };
2317 auto &settings = Core::App().settings();
2318 auto params = Window::SectionShow(
2319 Window::SectionShow::Way::ClearStack,
2320 anim::type::instant,
2321 anim::activation::background);
2322 auto switchInfoFast = [&] {
2323 saveOldThirdSection();
2324
2325 //
2326 // Like in _controller->showPeerInfo()
2327 //
2328 if (isThreeColumn()
2329 && !settings.thirdSectionInfoEnabled()) {
2330 settings.setThirdSectionInfoEnabled(true);
2331 Core::App().saveSettingsDelayed();
2332 }
2333
2334 _controller->showSection(
2335 thirdSectionForCurrentMainSection(key),
2336 params.withThirdColumn());
2337 };
2338 auto switchTabbedFast = [&](not_null<PeerData*> peer) {
2339 saveOldThirdSection();
2340 return _mainSection
2341 ? _mainSection->pushTabbedSelectorToThirdSection(peer, params)
2342 : _history->pushTabbedSelectorToThirdSection(peer, params);
2343 };
2344 if (isThreeColumn()
2345 && settings.tabbedSelectorSectionEnabled()
2346 && key) {
2347 if (!canWrite) {
2348 switchInfoFast();
2349 settings.setTabbedSelectorSectionEnabled(true);
2350 settings.setTabbedReplacedWithInfo(true);
2351 } else if (settings.tabbedReplacedWithInfo()
2352 && key.history()
2353 && switchTabbedFast(key.history()->peer)) {
2354 settings.setTabbedReplacedWithInfo(false);
2355 }
2356 } else {
2357 settings.setTabbedReplacedWithInfo(false);
2358 if (!key) {
2359 if (_thirdSection) {
2360 _thirdSection.destroy();
2361 _thirdShadow.destroy();
2362 updateControlsGeometry();
2363 }
2364 } else if (isThreeColumn()
2365 && settings.thirdSectionInfoEnabled()) {
2366 switchInfoFast();
2367 }
2368 }
2369 }
2370
updateMediaPlayerPosition()2371 void MainWidget::updateMediaPlayerPosition() {
2372 if (_player && _playerVolume) {
2373 auto relativePosition = _player->entity()->getPositionForVolumeWidget();
2374 auto playerMargins = _playerVolume->getMargin();
2375 _playerVolume->moveToLeft(_player->x() + relativePosition.x() - playerMargins.left(), _player->y() + relativePosition.y() - playerMargins.top());
2376 }
2377 }
2378
updateMediaPlaylistPosition(int x)2379 void MainWidget::updateMediaPlaylistPosition(int x) {
2380 if (_player) {
2381 auto playlistLeft = x;
2382 auto playlistWidth = _playerPlaylist->width();
2383 auto playlistTop = _player->y() + _player->height();
2384 auto rightEdge = width();
2385 if (playlistLeft + playlistWidth > rightEdge) {
2386 playlistLeft = rightEdge - playlistWidth;
2387 } else if (playlistLeft < 0) {
2388 playlistLeft = 0;
2389 }
2390 _playerPlaylist->move(playlistLeft, playlistTop);
2391 }
2392 }
2393
returnTabbedSelector()2394 void MainWidget::returnTabbedSelector() {
2395 if (!_mainSection || !_mainSection->returnTabbedSelector()) {
2396 _history->returnTabbedSelector();
2397 }
2398 }
2399
keyPressEvent(QKeyEvent * e)2400 void MainWidget::keyPressEvent(QKeyEvent *e) {
2401 }
2402
eventFilter(QObject * o,QEvent * e)2403 bool MainWidget::eventFilter(QObject *o, QEvent *e) {
2404 if (e->type() == QEvent::FocusIn) {
2405 if (const auto widget = qobject_cast<QWidget*>(o)) {
2406 if (_history == widget || _history->isAncestorOf(widget)
2407 || (_mainSection && (_mainSection == widget || _mainSection->isAncestorOf(widget)))
2408 || (_thirdSection && (_thirdSection == widget || _thirdSection->isAncestorOf(widget)))) {
2409 _controller->dialogsListFocused().set(false);
2410 } else if (_dialogs == widget || _dialogs->isAncestorOf(widget)) {
2411 _controller->dialogsListFocused().set(true);
2412 }
2413 }
2414 } else if (e->type() == QEvent::MouseButtonPress) {
2415 if (static_cast<QMouseEvent*>(e)->button() == Qt::BackButton) {
2416 if (!Core::App().hideMediaView()) {
2417 handleHistoryBack();
2418 }
2419 return true;
2420 }
2421 } else if (e->type() == QEvent::Wheel) {
2422 if (const auto result = floatPlayerFilterWheelEvent(o, e)) {
2423 return *result;
2424 }
2425 }
2426 return RpWidget::eventFilter(o, e);
2427 }
2428
handleAdaptiveLayoutUpdate()2429 void MainWidget::handleAdaptiveLayoutUpdate() {
2430 showAll();
2431 _sideShadow->setVisible(!isOneColumn());
2432 if (_player) {
2433 _player->updateAdaptiveLayout();
2434 }
2435 }
2436
handleHistoryBack()2437 void MainWidget::handleHistoryBack() {
2438 const auto historyFromFolder = _history->history()
2439 ? _history->history()->folder()
2440 : nullptr;
2441 const auto openedFolder = _controller->openedFolder().current();
2442 if (!openedFolder
2443 || historyFromFolder == openedFolder
2444 || _dialogs->isHidden()) {
2445 _controller->showBackFromStack();
2446 _dialogs->setInnerFocus();
2447 } else {
2448 _controller->closeFolder();
2449 }
2450 }
2451
updateWindowAdaptiveLayout()2452 void MainWidget::updateWindowAdaptiveLayout() {
2453 auto layout = _controller->computeColumnLayout();
2454 auto dialogsWidthRatio = Core::App().settings().dialogsWidthRatio();
2455
2456 // Check if we are in a single-column layout in a wide enough window
2457 // for the normal layout. If so, switch to the normal layout.
2458 if (layout.windowLayout == Window::Adaptive::WindowLayout::OneColumn) {
2459 auto chatWidth = layout.chatWidth;
2460 //if (session().settings().tabbedSelectorSectionEnabled()
2461 // && chatWidth >= _history->minimalWidthForTabbedSelectorSection()) {
2462 // chatWidth -= _history->tabbedSelectorSectionWidth();
2463 //}
2464 auto minimalNormalWidth = st::columnMinimalWidthLeft
2465 + st::columnMinimalWidthMain;
2466 if (chatWidth >= minimalNormalWidth) {
2467 // Switch layout back to normal in a wide enough window.
2468 layout.windowLayout = Window::Adaptive::WindowLayout::Normal;
2469 layout.dialogsWidth = st::columnMinimalWidthLeft;
2470 layout.chatWidth = layout.bodyWidth - layout.dialogsWidth;
2471 dialogsWidthRatio = float64(layout.dialogsWidth) / layout.bodyWidth;
2472 }
2473 }
2474
2475 // Check if we are going to create the third column and shrink the
2476 // dialogs widget to provide a wide enough chat history column.
2477 // Don't shrink the column on the first call, when window is inited.
2478 if (layout.windowLayout == Window::Adaptive::WindowLayout::ThreeColumn
2479 && _controller->widget()->positionInited()) {
2480 //auto chatWidth = layout.chatWidth;
2481 //if (_history->willSwitchToTabbedSelectorWithWidth(chatWidth)) {
2482 // auto thirdColumnWidth = _history->tabbedSelectorSectionWidth();
2483 // auto twoColumnsWidth = (layout.bodyWidth - thirdColumnWidth);
2484 // auto sameRatioChatWidth = twoColumnsWidth - qRound(dialogsWidthRatio * twoColumnsWidth);
2485 // auto desiredChatWidth = qMax(sameRatioChatWidth, HistoryView::WideChatWidth());
2486 // chatWidth -= thirdColumnWidth;
2487 // auto extendChatBy = desiredChatWidth - chatWidth;
2488 // accumulate_min(extendChatBy, layout.dialogsWidth - st::columnMinimalWidthLeft);
2489 // if (extendChatBy > 0) {
2490 // layout.dialogsWidth -= extendChatBy;
2491 // layout.chatWidth += extendChatBy;
2492 // dialogsWidthRatio = float64(layout.dialogsWidth) / layout.bodyWidth;
2493 // }
2494 //}
2495 }
2496
2497 Core::App().settings().setDialogsWidthRatio(dialogsWidthRatio);
2498
2499 auto useSmallColumnWidth = !isOneColumn()
2500 && !dialogsWidthRatio
2501 && !_controller->forceWideDialogs();
2502 _dialogsWidth = useSmallColumnWidth
2503 ? _controller->dialogsSmallColumnWidth()
2504 : layout.dialogsWidth;
2505 _thirdColumnWidth = layout.thirdWidth;
2506 _controller->adaptive().setWindowLayout(layout.windowLayout);
2507 }
2508
backgroundFromY() const2509 int MainWidget::backgroundFromY() const {
2510 return -getMainSectionTop();
2511 }
2512
searchInChat(Dialogs::Key chat)2513 void MainWidget::searchInChat(Dialogs::Key chat) {
2514 if (_controller->openedFolder().current()) {
2515 _controller->closeFolder();
2516 }
2517 _dialogs->searchInChat(chat);
2518 if (isOneColumn()) {
2519 Ui::showChatsList(&session());
2520 } else {
2521 _dialogs->setInnerFocus();
2522 }
2523 }
2524
contentOverlapped(const QRect & globalRect)2525 bool MainWidget::contentOverlapped(const QRect &globalRect) {
2526 return (_history->contentOverlapped(globalRect)
2527 || _playerPlaylist->overlaps(globalRect)
2528 || (_playerVolume && _playerVolume->overlaps(globalRect)));
2529 }
2530
activate()2531 void MainWidget::activate() {
2532 if (_a_show.animating()) {
2533 return;
2534 } else if (!_mainSection) {
2535 if (_hider) {
2536 _dialogs->setInnerFocus();
2537 } else if (!Ui::isLayerShown()) {
2538 if (!cSendPaths().isEmpty()) {
2539 const auto interpret = qstr("interpret://");
2540 const auto path = cSendPaths()[0];
2541 if (path.startsWith(interpret)) {
2542 cSetSendPaths(QStringList());
2543 const auto error = Support::InterpretSendPath(
2544 _controller,
2545 path.mid(interpret.size()));
2546 if (!error.isEmpty()) {
2547 Ui::show(Box<Ui::InformBox>(error));
2548 }
2549 } else {
2550 showSendPathsLayer();
2551 }
2552 } else if (_history->peer()) {
2553 _history->activate();
2554 } else {
2555 _dialogs->setInnerFocus();
2556 }
2557 }
2558 }
2559 _controller->widget()->fixOrder();
2560 }
2561
isActive() const2562 bool MainWidget::isActive() const {
2563 return isVisible()
2564 && !_a_show.animating()
2565 && !session().updates().isIdle();
2566 }
2567
doWeMarkAsRead() const2568 bool MainWidget::doWeMarkAsRead() const {
2569 return isActive() && !_mainSection;
2570 }
2571
dlgsWidth() const2572 int32 MainWidget::dlgsWidth() const {
2573 return _dialogs->width();
2574 }
2575
saveFieldToHistoryLocalDraft()2576 void MainWidget::saveFieldToHistoryLocalDraft() {
2577 _history->saveFieldToHistoryLocalDraft();
2578 }
2579
isOneColumn() const2580 bool MainWidget::isOneColumn() const {
2581 return _controller->adaptive().isOneColumn();
2582 }
2583
isNormalColumn() const2584 bool MainWidget::isNormalColumn() const {
2585 return _controller->adaptive().isNormal();
2586 }
2587
isThreeColumn() const2588 bool MainWidget::isThreeColumn() const {
2589 return _controller->adaptive().isThreeColumn();
2590 }
2591
2592 namespace App {
2593
main()2594 MainWidget *main() {
2595 if (const auto window = wnd()) {
2596 return window->sessionContent();
2597 }
2598 return nullptr;
2599 }
2600
2601 } // namespace App
2602