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 &params,
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>(&params.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 &params) {
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 &params) {
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 &params) 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 &params) {
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 &params) {
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