From e39b95175bfa65a0b75b56c597bc629a2ad30a81 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Thu, 22 Jun 2017 00:38:31 +0300
Subject: [PATCH] Handle channel event log mouse events.

---
 Telegram/SourceFiles/auth_session.h           |   8 +
 Telegram/SourceFiles/boxes/confirm_box.cpp    |   5 +-
 .../SourceFiles/chat_helpers/bot_keyboard.cpp |   8 +-
 .../SourceFiles/chat_helpers/bot_keyboard.h   |   2 +-
 .../chat_helpers/gifs_list_widget.cpp         |   2 +-
 Telegram/SourceFiles/facades.cpp              |   4 +-
 Telegram/SourceFiles/facades.h                |   2 +-
 Telegram/SourceFiles/history.cpp              |   4 +-
 .../history/history_admin_log_inner.cpp       | 567 +++++++++++++++++-
 .../history/history_admin_log_inner.h         |  80 ++-
 .../history/history_admin_log_item.cpp        |  28 +-
 .../history/history_admin_log_item.h          |  15 +-
 .../history/history_admin_log_section.cpp     |   2 +-
 .../history/history_inner_widget.cpp          | 365 ++++++-----
 .../history/history_inner_widget.h            |  44 +-
 Telegram/SourceFiles/history/history_item.cpp |   6 +-
 Telegram/SourceFiles/history/history_item.h   |  16 +-
 Telegram/SourceFiles/history/history_media.h  |   8 +-
 .../history/history_media_types.cpp           | 184 +++---
 .../SourceFiles/history/history_media_types.h |  24 +-
 .../SourceFiles/history/history_message.cpp   | 101 ++--
 .../SourceFiles/history/history_message.h     |  24 +-
 Telegram/SourceFiles/historywidget.cpp        |  18 +-
 Telegram/SourceFiles/historywidget.h          |   6 +-
 .../inline_bot_layout_internal.cpp            |  59 +-
 .../inline_bots/inline_bot_layout_internal.h  |  16 +-
 .../inline_bots/inline_results_widget.cpp     |   2 +-
 Telegram/SourceFiles/layout.h                 |  20 +-
 Telegram/SourceFiles/mainwidget.cpp           |  28 +-
 Telegram/SourceFiles/mainwidget.h             |   4 +-
 Telegram/SourceFiles/mainwindow.cpp           |  44 +-
 Telegram/SourceFiles/mainwindow.h             |  15 -
 .../media/player/media_player_float.h         |   2 +-
 .../media/player/media_player_list.cpp        |   4 +-
 .../media/player/media_player_list.h          |   2 +-
 .../media/player/media_player_panel.cpp       |   2 +-
 .../media/player/media_player_panel.h         |   2 +-
 Telegram/SourceFiles/mediaview.cpp            |   5 +-
 .../SourceFiles/overview/overview_layout.cpp  |  40 +-
 .../SourceFiles/overview/overview_layout.h    |  12 +-
 Telegram/SourceFiles/overviewwidget.cpp       |  50 +-
 Telegram/SourceFiles/overviewwidget.h         |   7 +-
 .../platform/win/windows_event_filter.cpp     |   2 +-
 Telegram/SourceFiles/ui/text/text.cpp         |  24 +-
 Telegram/SourceFiles/ui/text/text.h           |  12 +-
 Telegram/SourceFiles/ui/twidget.cpp           |   3 +-
 Telegram/SourceFiles/ui/widgets/labels.cpp    |   8 +-
 Telegram/SourceFiles/window/main_window.cpp   |  46 +-
 Telegram/SourceFiles/window/main_window.h     |  30 +-
 .../SourceFiles/window/window_controller.h    |   4 -
 50 files changed, 1298 insertions(+), 668 deletions(-)

diff --git a/Telegram/SourceFiles/auth_session.h b/Telegram/SourceFiles/auth_session.h
index bd45b01e8..1599a66f3 100644
--- a/Telegram/SourceFiles/auth_session.h
+++ b/Telegram/SourceFiles/auth_session.h
@@ -57,6 +57,12 @@ public:
 	base::Observable<void> &savedGifsUpdated() {
 		return _savedGifsUpdated;
 	}
+	base::Observable<gsl::not_null<History*>> &historyCleared() {
+		return _historyCleared;
+	}
+	base::Observable<gsl::not_null<const HistoryItem*>> &repaintLogEntry() {
+		return _repaintLogEntry;
+	}
 
 	void copyFrom(const AuthSessionData &other) {
 		_variables = other._variables;
@@ -131,6 +137,8 @@ private:
 	base::Variable<bool> _allChatsLoaded = { false };
 	base::Observable<void> _moreChatsLoaded;
 	base::Observable<void> _savedGifsUpdated;
+	base::Observable<gsl::not_null<History*>> _historyCleared;
+	base::Observable<gsl::not_null<const HistoryItem*>> _repaintLogEntry;
 	Variables _variables;
 	TimeMs _lastTimeVideoPlayedAt = 0;
 
diff --git a/Telegram/SourceFiles/boxes/confirm_box.cpp b/Telegram/SourceFiles/boxes/confirm_box.cpp
index 47ab08844..897b0c80e 100644
--- a/Telegram/SourceFiles/boxes/confirm_box.cpp
+++ b/Telegram/SourceFiles/boxes/confirm_box.cpp
@@ -186,9 +186,8 @@ void ConfirmBox::updateLink() {
 }
 
 void ConfirmBox::updateHover() {
-	QPoint m(mapFromGlobal(_lastMousePos));
-
-	auto state = _text.getStateLeft(m.x() - st::boxPadding.left(), m.y() - st::boxPadding.top(), _textWidth, width());
+	auto m = mapFromGlobal(_lastMousePos);
+	auto state = _text.getStateLeft(m - QPoint(st::boxPadding.left(), st::boxPadding.top()), _textWidth, width());
 
 	ClickHandler::setActive(state.link, this);
 }
diff --git a/Telegram/SourceFiles/chat_helpers/bot_keyboard.cpp b/Telegram/SourceFiles/chat_helpers/bot_keyboard.cpp
index ecc813e34..a64c20042 100644
--- a/Telegram/SourceFiles/chat_helpers/bot_keyboard.cpp
+++ b/Telegram/SourceFiles/chat_helpers/bot_keyboard.cpp
@@ -52,7 +52,7 @@ const style::TextStyle &BotKeyboard::Style::textStyle() const {
 	return st::botKbStyle;
 }
 
-void BotKeyboard::Style::repaint(const HistoryItem *item) const {
+void BotKeyboard::Style::repaint(gsl::not_null<const HistoryItem*> item) const {
 	_parent->update();
 }
 
@@ -237,10 +237,10 @@ void BotKeyboard::updateSelected() {
 
 	if (!_impl) return;
 
-	QPoint p(mapFromGlobal(_lastMousePos));
-	int x = rtl() ? st::botKbScroll.width : _st->margin;
+	auto p = mapFromGlobal(_lastMousePos);
+	auto x = rtl() ? st::botKbScroll.width : _st->margin;
 
-	auto link = _impl->getState(p.x() - x, p.y() - _st->margin);
+	auto link = _impl->getState(p - QPoint(x, _st->margin));
 	if (ClickHandler::setActive(link, this)) {
 		Ui::Tooltip::Hide();
 		setCursor(link ? style::cur_pointer : style::cur_default);
diff --git a/Telegram/SourceFiles/chat_helpers/bot_keyboard.h b/Telegram/SourceFiles/chat_helpers/bot_keyboard.h
index 9fefc56ae..519dd2323 100644
--- a/Telegram/SourceFiles/chat_helpers/bot_keyboard.h
+++ b/Telegram/SourceFiles/chat_helpers/bot_keyboard.h
@@ -92,7 +92,7 @@ private:
 
 		void startPaint(Painter &p) const override;
 		const style::TextStyle &textStyle() const override;
-		void repaint(const HistoryItem *item) const override;
+		void repaint(gsl::not_null<const HistoryItem*> item) const override;
 
 	protected:
 		void paintButtonBg(Painter &p, const QRect &rect, float64 howMuchOver) const override;
diff --git a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp
index 8d180bf48..f36d52d61 100644
--- a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp
+++ b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp
@@ -900,7 +900,7 @@ void GifsListWidget::updateSelected() {
 		}
 		if (col < inlineItems.size()) {
 			sel = row * MatrixRowShift + col;
-			inlineItems.at(col)->getState(lnk, cursor, sx, sy);
+			inlineItems.at(col)->getState(lnk, cursor, QPoint(sx, sy));
 			lnkhost = inlineItems.at(col);
 		} else {
 			row = col = -1;
diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp
index fb6364e31..ca17f1085 100644
--- a/Telegram/SourceFiles/facades.cpp
+++ b/Telegram/SourceFiles/facades.cpp
@@ -253,7 +253,7 @@ bool isMediaViewShown() {
 	return false;
 }
 
-void repaintHistoryItem(const HistoryItem *item) {
+void repaintHistoryItem(gsl::not_null<const HistoryItem*> item) {
 	if (auto main = App::main()) {
 		main->ui_repaintHistoryItem(item);
 	}
@@ -379,7 +379,7 @@ void handlePendingHistoryUpdate() {
 			if (auto media = item->getMedia()) {
 				if (auto reader = media->getClipReader()) {
 					if (!reader->started() && reader->mode() == Media::Clip::Reader::Mode::Video) {
-						item->history()->resizeGetHeight(item->history()->width);
+						item->resizeGetHeight(item->width());
 					}
 				}
 			}
diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h
index c6796a7f0..60d9be41e 100644
--- a/Telegram/SourceFiles/facades.h
+++ b/Telegram/SourceFiles/facades.h
@@ -107,7 +107,7 @@ void hideSettingsAndLayer(bool fast = false);
 bool isLayerShown();
 bool isMediaViewShown();
 
-void repaintHistoryItem(const HistoryItem *item);
+void repaintHistoryItem(gsl::not_null<const HistoryItem*> item);
 void autoplayMediaInlineAsync(const FullMsgId &msgId);
 
 void showPeerProfile(const PeerId &peer);
diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp
index 4091204c7..0e408e160 100644
--- a/Telegram/SourceFiles/history.cpp
+++ b/Telegram/SourceFiles/history.cpp
@@ -2060,7 +2060,9 @@ void History::clear(bool leaveItems) {
 			peer->asChannel()->mgInfo->markupSenders.clear();
 		}
 	}
-	if (leaveItems && App::main()) App::main()->historyCleared(this);
+	if (leaveItems) {
+		AuthSession::Current().data().historyCleared().notify(this, true);
+	}
 }
 
 void History::clearBlocks(bool leaveItems) {
diff --git a/Telegram/SourceFiles/history/history_admin_log_inner.cpp b/Telegram/SourceFiles/history/history_admin_log_inner.cpp
index 427f15649..2fa9c13f6 100644
--- a/Telegram/SourceFiles/history/history_admin_log_inner.cpp
+++ b/Telegram/SourceFiles/history/history_admin_log_inner.cpp
@@ -20,21 +20,37 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 */
 #include "history/history_admin_log_inner.h"
 
+#include "styles/style_history.h"
+#include "history/history_media_types.h"
 #include "history/history_admin_log_item.h"
 #include "history/history_admin_log_section.h"
+#include "mainwindow.h"
+#include "window/window_controller.h"
+#include "auth_session.h"
+#include "lang/lang_keys.h"
 
 namespace AdminLog {
 namespace {
 
-constexpr int kEventsPerPage = 3;
+constexpr auto kScrollDateHideTimeout = 1000;
+constexpr auto kEventsPerPage = 10;
 
 } // namespace
 
-InnerWidget::InnerWidget(QWidget *parent, gsl::not_null<ChannelData*> channel, base::lambda<void(int top)> scrollTo) : TWidget(parent)
+InnerWidget::InnerWidget(QWidget *parent, gsl::not_null<Window::Controller*> controller, gsl::not_null<ChannelData*> channel, base::lambda<void(int top)> scrollTo) : TWidget(parent)
+, _controller(controller)
 , _channel(channel)
 , _history(App::history(channel))
-, _scrollTo(std::move(scrollTo)) {
+, _scrollTo(std::move(scrollTo))
+, _scrollDateCheck([this] { scrollDateCheck(); }) {
 	setMouseTracking(true);
+	_scrollDateHideTimer.setCallback([this] { scrollDateHideByTimer(); });
+	subscribe(AuthSession::Current().data().repaintLogEntry(), [this](gsl::not_null<const HistoryItem*> historyItem) {
+		auto it = _itemsByHistoryItems.find(historyItem);
+		if (it != _itemsByHistoryItems.cend()) {
+			repaintItem(it->second);
+		}
+	});
 }
 
 void InnerWidget::setVisibleTopBottom(int visibleTop, int visibleBottom) {
@@ -59,6 +75,59 @@ void InnerWidget::updateVisibleTopItem() {
 	}
 }
 
+bool InnerWidget::displayScrollDate() const {
+	return (_visibleTop <= height() - 2 * (_visibleBottom - _visibleTop));
+}
+
+void InnerWidget::scrollDateCheck() {
+	if (!_visibleTopItem) {
+		_scrollDateLastItem = nullptr;
+		_scrollDateLastItemTop = 0;
+		scrollDateHide();
+	} else if (_visibleTopItem != _scrollDateLastItem || _visibleTopFromItem != _scrollDateLastItemTop) {
+		// Show scroll date only if it is not the initial onScroll() event (with empty _scrollDateLastItem).
+		if (_scrollDateLastItem && !_scrollDateShown) {
+			toggleScrollDateShown();
+		}
+		_scrollDateLastItem = _visibleTopItem;
+		_scrollDateLastItemTop = _visibleTopFromItem;
+		_scrollDateHideTimer.callOnce(kScrollDateHideTimeout);
+	}
+}
+
+void InnerWidget::scrollDateHideByTimer() {
+	_scrollDateHideTimer.cancel();
+	if (!_scrollDateLink || ClickHandler::getPressed() != _scrollDateLink) {
+		scrollDateHide();
+	}
+}
+
+void InnerWidget::scrollDateHide() {
+	if (_scrollDateShown) {
+		toggleScrollDateShown();
+	}
+}
+
+void InnerWidget::keepScrollDateForNow() {
+	if (!_scrollDateShown && _scrollDateLastItem && _scrollDateOpacity.animating()) {
+		toggleScrollDateShown();
+	}
+	_scrollDateHideTimer.callOnce(kScrollDateHideTimeout);
+}
+
+void InnerWidget::toggleScrollDateShown() {
+	_scrollDateShown = !_scrollDateShown;
+	auto from = _scrollDateShown ? 0. : 1.;
+	auto to = _scrollDateShown ? 1. : 0.;
+	_scrollDateOpacity.start([this] { repaintScrollDateCallback(); }, from, to, st::historyDateFadeDuration);
+}
+
+void InnerWidget::repaintScrollDateCallback() {
+	auto updateTop = _visibleTop;
+	auto updateHeight = st::msgServiceMargin.top() + st::msgServicePadding.top() + st::msgServiceFont->height + st::msgServicePadding.bottom();
+	update(0, updateTop, width(), updateHeight);
+}
+
 void InnerWidget::checkPreloadMore() {
 	if (_visibleTop + PreloadHeightsCount * (_visibleBottom - _visibleTop) > height()) {
 		preloadMore(Direction::Down);
@@ -73,6 +142,26 @@ void InnerWidget::applyFilter(MTPDchannelAdminLogEventsFilter::Flags flags, cons
 	_filterAdmins = admins;
 }
 
+QString InnerWidget::tooltipText() const {
+	if (_mouseCursorState == HistoryInDateCursorState && _mouseAction == MouseAction::None) {
+		if (_itemOver) {
+			auto dateText = _itemOver->date().toString(QLocale::system().dateTimeFormat(QLocale::LongFormat));
+			return dateText;
+		}
+	} else if (_mouseCursorState == HistoryInForwardedCursorState && _mouseAction == MouseAction::None) {
+		if (_itemOver) {
+			auto forwarded = _itemOver->getForwardedInfoText();
+		}
+	} else if (auto lnk = ClickHandler::getActive()) {
+		return lnk->tooltip();
+	}
+	return QString();
+}
+
+QPoint InnerWidget::tooltipPos() const {
+	return _mousePosition;
+}
+
 void InnerWidget::saveState(gsl::not_null<SectionMemento*> memento) const {
 	//if (auto count = _items.size()) {
 	//	QList<gsl::not_null<PeerData*>> groups;
@@ -162,7 +251,7 @@ void InnerWidget::itemsAdded(Direction direction) {
 
 void InnerWidget::updateSize() {
 	TWidget::resizeToWidth(width());
-	auto newVisibleTop = (_visibleTopItem ? _visibleTopItem->top() : 0) + _visibleTopFromItem;
+	auto newVisibleTop = _visibleTopItem ? (itemTop(_visibleTopItem) + _visibleTopFromItem) : ScrollMax;
 	_scrollTo(newVisibleTop);
 	updateVisibleTopItem();
 	checkPreloadMore();
@@ -176,10 +265,16 @@ int InnerWidget::resizeGetHeight(int newWidth) {
 		item->setTop(newHeight);
 		newHeight += item->resizeGetHeight(newWidth);
 	}
-	return qMax(newHeight, _minHeight);
+	_itemsHeight = newHeight;
+	_itemsTop = (_minHeight > _itemsHeight + st::historyPaddingBottom) ? (_minHeight - _itemsHeight - st::historyPaddingBottom) : 0;
+	return _itemsTop + _itemsHeight + st::historyPaddingBottom;
 }
 
 void InnerWidget::paintEvent(QPaintEvent *e) {
+	if (Ui::skipPaintEvent(this, e)) {
+		return;
+	}
+
 	Painter p(this);
 
 	auto ms = getms();
@@ -189,14 +284,14 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
 		paintEmpty(p);
 	} else {
 		auto start = std::rbegin(_items), end = std::rend(_items);
-		auto from = std::upper_bound(start, end, clip.top(), [](int top, auto &elem) {
-			return top <= elem->top() + elem->height();
+		auto from = std::upper_bound(start, end, clip.top(), [this](int top, auto &elem) {
+			return top <= itemTop(elem.get()) + elem->height();
 		});
-		auto to = std::lower_bound(start, end, clip.top() + clip.height(), [](auto &elem, int bottom) {
-			return elem->top() < bottom;
+		auto to = std::lower_bound(start, end, clip.top() + clip.height(), [this](auto &elem, int bottom) {
+			return itemTop(elem.get()) < bottom;
 		});
 		if (from != end) {
-			auto top = (*from)->top();
+			auto top = itemTop(from->get());
 			p.translate(0, top);
 			for (auto i = from; i != to; ++i) {
 				(*i)->draw(p, clip.translated(0, -top), TextSelection(), ms);
@@ -226,12 +321,464 @@ void InnerWidget::keyPressEvent(QKeyEvent *e) {
 }
 
 void InnerWidget::mousePressEvent(QMouseEvent *e) {
+	if (_menu) {
+		e->accept();
+		return; // ignore mouse press, that was hiding context menu
+	}
+	mouseActionStart(e->globalPos(), e->button());
 }
 
 void InnerWidget::mouseMoveEvent(QMouseEvent *e) {
+	static auto lastGlobalPosition = e->globalPos();
+	auto reallyMoved = (lastGlobalPosition != e->globalPos());
+	auto buttonsPressed = (e->buttons() & (Qt::LeftButton | Qt::MiddleButton));
+	if (!buttonsPressed && _mouseAction != MouseAction::None) {
+		mouseReleaseEvent(e);
+	}
+	if (reallyMoved) {
+		lastGlobalPosition = e->globalPos();
+		if (!buttonsPressed || (_scrollDateLink && ClickHandler::getPressed() == _scrollDateLink)) {
+			keepScrollDateForNow();
+		}
+	}
+	mouseActionUpdate(e->globalPos());
 }
 
 void InnerWidget::mouseReleaseEvent(QMouseEvent *e) {
+	mouseActionFinish(e->globalPos(), e->button());
+	if (!rect().contains(e->pos())) {
+		leaveEvent(e);
+	}
+}
+
+void InnerWidget::enterEventHook(QEvent *e) {
+	mouseActionUpdate(QCursor::pos());
+	return TWidget::enterEventHook(e);
+}
+
+void InnerWidget::leaveEventHook(QEvent *e) {
+	repaintItem(base::take(_itemOver));
+	ClickHandler::clearActive();
+	Ui::Tooltip::Hide();
+	if (!ClickHandler::getPressed() && _cursor != style::cur_default) {
+		_cursor = style::cur_default;
+		setCursor(_cursor);
+	}
+	return TWidget::leaveEventHook(e);
+}
+
+void InnerWidget::mouseActionStart(const QPoint &screenPos, Qt::MouseButton button) {
+	mouseActionUpdate(screenPos);
+	if (button != Qt::LeftButton) return;
+
+	ClickHandler::pressed();
+	if (_itemPressed != _itemOver) {
+		repaintItem(_itemPressed);
+		_itemPressed = _itemOver;
+		repaintItem(_itemPressed);
+	}
+
+	_mouseAction = MouseAction::None;
+	_mouseActionItem = _itemNearest;
+	_dragStartPosition = mapPointToItem(mapFromGlobal(screenPos), _mouseActionItem);
+	_pressWasInactive = _controller->window()->wasInactivePress();
+	if (_pressWasInactive) _controller->window()->setInactivePress(false);
+
+	if (ClickHandler::getPressed()) {
+		_mouseAction = MouseAction::PrepareDrag;
+	}
+	if (_mouseAction == MouseAction::None && _mouseActionItem) {
+		HistoryTextState dragState;
+		if (_trippleClickTimer.isActive() && (screenPos - _trippleClickPoint).manhattanLength() < QApplication::startDragDistance()) {
+			HistoryStateRequest request;
+			request.flags = Text::StateRequest::Flag::LookupSymbol;
+			dragState = _mouseActionItem->getState(_dragStartPosition, request).data;
+			if (dragState.cursor == HistoryInTextCursorState) {
+				auto selection = TextSelection { dragState.symbol, dragState.symbol };
+				repaintItem(std::exchange(_selectedItem, _mouseActionItem));
+				_selectedText = selection;
+				_mouseTextSymbol = dragState.symbol;
+				_mouseAction = MouseAction::Selecting;
+				_mouseSelectType = TextSelectType::Paragraphs;
+				mouseActionUpdate(_mousePosition);
+				_trippleClickTimer.callOnce(QApplication::doubleClickInterval());
+			}
+		} else if (_itemPressed) {
+			HistoryStateRequest request;
+			request.flags = Text::StateRequest::Flag::LookupSymbol;
+			dragState = _mouseActionItem->getState(_dragStartPosition, request).data;
+		}
+		if (_mouseSelectType != TextSelectType::Paragraphs) {
+			if (_itemPressed) {
+				_mouseTextSymbol = dragState.symbol;
+				auto uponSelected = (dragState.cursor == HistoryInTextCursorState);
+				if (uponSelected) {
+					if (!_selectedItem || _selectedItem != _mouseActionItem) {
+						uponSelected = false;
+					} else if (_mouseTextSymbol < _selectedText.from || _mouseTextSymbol >= _selectedText.to) {
+						uponSelected = false;
+					}
+				}
+				if (uponSelected) {
+					_mouseAction = MouseAction::PrepareDrag; // start text drag
+				} else if (!_pressWasInactive) {
+					//if (dynamic_cast<HistorySticker*>(_itemPressed->getMedia())) {
+					//	_mouseAction = MouseAction::PrepareDrag; // start sticker drag or by-date drag
+					//} else { // TODO
+						if (dragState.afterSymbol) ++_mouseTextSymbol;
+						auto selection = TextSelection { _mouseTextSymbol, _mouseTextSymbol };
+						repaintItem(std::exchange(_selectedItem, _mouseActionItem));
+						_selectedText = selection;
+						_mouseAction = MouseAction::Selecting;
+						repaintItem(_mouseActionItem);
+					//} // TODO
+				}
+			}
+		}
+	}
+
+	if (!_mouseActionItem) {
+		_mouseAction = MouseAction::None;
+	} else if (_mouseAction == MouseAction::None) {
+		_mouseActionItem = nullptr;
+	}
+}
+
+void InnerWidget::mouseActionUpdate(const QPoint &screenPos) {
+	_mousePosition = screenPos;
+	updateSelected();
+}
+
+void InnerWidget::mouseActionCancel() {
+	_mouseActionItem = nullptr;
+	_mouseAction = MouseAction::None;
+	_dragStartPosition = QPoint(0, 0);
+	_wasSelectedText = false;
+	//_widget->noSelectingScroll(); // TODO
+}
+
+void InnerWidget::mouseActionFinish(const QPoint &screenPos, Qt::MouseButton button) {
+	mouseActionUpdate(screenPos);
+
+	ClickHandlerPtr activated = ClickHandler::unpressed();
+	if (_mouseAction == MouseAction::Dragging) {
+		activated.clear();
+	}
+	repaintItem(base::take(_itemPressed));
+
+	_wasSelectedText = false;
+
+	if (activated) {
+		mouseActionCancel();
+		App::activateClickHandler(activated, button);
+		return;
+	}
+	if (_mouseAction == MouseAction::PrepareDrag && !_pressWasInactive && button != Qt::RightButton) {
+		repaintItem(base::take(_selectedItem));
+	} else if (_mouseAction == MouseAction::Selecting) {
+		if (_selectedItem && !_pressWasInactive) {
+			if (_selectedText.from == _selectedText.to) {
+				_selectedItem = nullptr;
+				App::wnd()->setInnerFocus();
+			}
+		}
+	}
+	_mouseAction = MouseAction::None;
+	_mouseActionItem = nullptr;
+	_mouseSelectType = TextSelectType::Letters;
+	//_widget->noSelectingScroll(); // TODO
+
+#if defined Q_OS_LINUX32 || defined Q_OS_LINUX64
+	if (_selectedItem && _selectedText.from != _selectedText.to) {
+		setToClipboard(_selectedItem->selectedText(_selectedText), QClipboard::Selection);
+	}
+#endif // Q_OS_LINUX32 || Q_OS_LINUX64
+}
+
+void InnerWidget::updateSelected() {
+	auto mousePosition = mapFromGlobal(_mousePosition);
+	auto point = QPoint(snap(mousePosition.x(), 0, width()), snap(mousePosition.y(), _visibleTop, _visibleBottom));
+
+	auto itemPoint = QPoint();
+	auto start = std::rbegin(_items), end = std::rend(_items);
+	auto from = (point.y() >= _itemsTop && point.y() < _itemsTop + _itemsHeight) ? std::upper_bound(start, end, point.y(), [this](int top, auto &elem) {
+		return top <= itemTop(elem.get()) + elem->height();
+	}) : end;
+	if (from != end) {
+		_itemNearest = from->get();
+		itemPoint = mapPointToItem(point, _itemNearest);
+		if (_itemNearest->hasPoint(itemPoint)) {
+			if (_itemOver != _itemNearest) {
+				repaintItem(std::exchange(_itemOver, _itemNearest));
+				repaintItem(_itemNearest);
+			}
+		} else {
+			repaintItem(base::take(_itemOver));
+		}
+	} else {
+		_itemNearest = nullptr;
+	}
+
+	Item::TextState dragState;
+	ClickHandlerHost *lnkhost = nullptr;
+	auto selectingText = (_itemNearest == _mouseActionItem && _itemNearest == _itemOver && _selectedItem);
+	if (_itemNearest) {
+		if (_itemNearest != _mouseActionItem || (itemPoint - _dragStartPosition).manhattanLength() >= QApplication::startDragDistance()) {
+			if (_mouseAction == MouseAction::PrepareDrag) {
+				_mouseAction = MouseAction::Dragging;
+				InvokeQueued(this, [this] { performDrag(); });
+			}
+		}
+
+		auto dateHeight = st::msgServicePadding.bottom() + st::msgServiceFont->height + st::msgServicePadding.top();
+		auto scrollDateOpacity = _scrollDateOpacity.current(_scrollDateShown ? 1. : 0.);
+		//enumerateDates([this, &dragState, &lnkhost, &point, scrollDateOpacity, dateHeight/*, lastDate, showFloatingBefore*/](HistoryItem *item, int itemtop, int dateTop) {
+		//	// stop enumeration if the date is above our point
+		//	if (dateTop + dateHeight <= point.y()) {
+		//		return false;
+		//	}
+
+		//	bool displayDate = item->displayDate();
+		//	bool dateInPlace = displayDate;
+		//	if (dateInPlace) {
+		//		int correctDateTop = itemtop + st::msgServiceMargin.top();
+		//		dateInPlace = (dateTop < correctDateTop + dateHeight);
+		//	}
+
+		//	// stop enumeration if we've found a date under the cursor
+		//	if (dateTop <= point.y()) {
+		//		auto opacity = (dateInPlace/* || noFloatingDate*/) ? 1. : scrollDateOpacity;
+		//		if (opacity > 0.) {
+		//			auto dateWidth = 0;
+		//			if (auto date = item->Get<HistoryMessageDate>()) {
+		//				dateWidth = date->_width;
+		//			} else {
+		//				dateWidth = st::msgServiceFont->width(langDayOfMonthFull(item->date.date()));
+		//			}
+		//			dateWidth += st::msgServicePadding.left() + st::msgServicePadding.right();
+		//			auto dateLeft = st::msgServiceMargin.left();
+		//			auto maxwidth = item->history()->width;
+		//			if (Adaptive::ChatWide()) {
+		//				maxwidth = qMin(maxwidth, int32(st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left()));
+		//			}
+		//			auto widthForDate = maxwidth - st::msgServiceMargin.left() - st::msgServiceMargin.left();
+
+		//			dateLeft += (widthForDate - dateWidth) / 2;
+
+		//			if (point.x() >= dateLeft && point.x() < dateLeft + dateWidth) {
+		//				if (!_scrollDateLink) {
+		//					_scrollDateLink = MakeShared<DateClickHandler>(item->history()->peer, item->date.date());
+		//				} else {
+		//					static_cast<DateClickHandler*>(_scrollDateLink.data())->setDate(item->date.date());
+		//				}
+		//				dragState.link = _scrollDateLink;
+		//				lnkhost = item;
+		//			}
+		//		}
+		//		return false;
+		//	}
+		//	return true;
+		//}); // TODO
+		if (!dragState.data.link) {
+			HistoryStateRequest request;
+			if (_mouseAction == MouseAction::Selecting) {
+				request.flags |= Text::StateRequest::Flag::LookupSymbol;
+			} else {
+				selectingText = false;
+			}
+			dragState = _itemNearest->getState(itemPoint, request);
+			lnkhost = dragState.handler;
+			if (!dragState.data.link && itemPoint.x() >= st::historyPhotoLeft && itemPoint.x() < st::historyPhotoLeft + st::msgPhotoSize) {
+				//if (auto msg = item->toHistoryMessage()) {
+				//	if (msg->hasFromPhoto()) {
+				//		enumerateUserpics([&dragState, &lnkhost, &point](HistoryMessage *message, int userpicTop) -> bool {
+				//			// stop enumeration if the userpic is below our point
+				//			if (userpicTop > point.y()) {
+				//				return false;
+				//			}
+
+				//			// stop enumeration if we've found a userpic under the cursor
+				//			if (point.y() >= userpicTop && point.y() < userpicTop + st::msgPhotoSize) {
+				//				dragState.link = message->from()->openLink();
+				//				lnkhost = message;
+				//				return false;
+				//			}
+				//			return true;
+				//		});
+				//	}
+				//} // TODO
+			}
+		}
+	}
+	auto lnkChanged = ClickHandler::setActive(dragState.data.link, lnkhost);
+	if (lnkChanged || dragState.data.cursor != _mouseCursorState) {
+		Ui::Tooltip::Hide();
+	}
+	if (dragState.data.link || dragState.data.cursor == HistoryInDateCursorState || dragState.data.cursor == HistoryInForwardedCursorState) {
+		Ui::Tooltip::Show(1000, this);
+	}
+
+	auto cursor = style::cur_default;
+	if (_mouseAction == MouseAction::None) {
+		_mouseCursorState = dragState.data.cursor;
+		if (dragState.data.link) {
+			cursor = style::cur_pointer;
+		} else if (_mouseCursorState == HistoryInTextCursorState) {
+			cursor = style::cur_text;
+		} else if (_mouseCursorState == HistoryInDateCursorState) {
+//			cursor = style::cur_cross;
+		}
+	} else if (_itemNearest) {
+		if (_mouseAction == MouseAction::Selecting) {
+			if (selectingText) {
+				auto second = dragState.data.symbol;
+				if (dragState.data.afterSymbol && _mouseSelectType == TextSelectType::Letters) {
+					++second;
+				}
+				auto selection = _mouseActionItem->adjustSelection({ qMin(second, _mouseTextSymbol), qMax(second, _mouseTextSymbol) }, _mouseSelectType);
+				if (_selectedText != selection) {
+					_selectedText = selection;
+					repaintItem(_mouseActionItem);
+				}
+				if (!_wasSelectedText && (selection.from != selection.to)) {
+					_wasSelectedText = true;
+					setFocus();
+				}
+			}
+		} else if (_mouseAction == MouseAction::Dragging) {
+		}
+
+		if (ClickHandler::getPressed()) {
+			cursor = style::cur_pointer;
+		} else if (_mouseAction == MouseAction::Selecting && _selectedItem) {
+			cursor = style::cur_text;
+		}
+	}
+
+	// Voice message seek support.
+	if (_itemPressed) {
+		auto adjustedPoint = mapPointToItem(point, _itemPressed);
+		_itemPressed->updatePressed(adjustedPoint);
+	}
+
+	//if (_mouseAction == MouseAction::Selecting) {
+	//	_widget->checkSelectingScroll(mousePos);
+	//} else {
+	//	_widget->noSelectingScroll();
+	//} // TODO
+
+	if (_mouseAction == MouseAction::None && (lnkChanged || cursor != _cursor)) {
+		setCursor(_cursor = cursor);
+	}
+}
+
+void InnerWidget::performDrag() {
+	if (_mouseAction != MouseAction::Dragging) return;
+
+	auto uponSelected = false;
+	//if (_mouseActionItem) {
+	//	if (!_selected.isEmpty() && _selected.cbegin().value() == FullSelection) {
+	//		uponSelected = _selected.contains(_mouseActionItem);
+	//	} else {
+	//		HistoryStateRequest request;
+	//		request.flags |= Text::StateRequest::Flag::LookupSymbol;
+	//		auto dragState = _mouseActionItem->getState(_dragStartPosition.x(), _dragStartPosition.y(), request);
+	//		uponSelected = (dragState.cursor == HistoryInTextCursorState);
+	//		if (uponSelected) {
+	//			if (_selected.isEmpty() ||
+	//				_selected.cbegin().value() == FullSelection ||
+	//				_selected.cbegin().key() != _mouseActionItem
+	//				) {
+	//				uponSelected = false;
+	//			} else {
+	//				uint16 selFrom = _selected.cbegin().value().from, selTo = _selected.cbegin().value().to;
+	//				if (dragState.symbol < selFrom || dragState.symbol >= selTo) {
+	//					uponSelected = false;
+	//				}
+	//			}
+	//		}
+	//	}
+	//}
+	//auto pressedHandler = ClickHandler::getPressed();
+
+	//if (dynamic_cast<VoiceSeekClickHandler*>(pressedHandler.data())) {
+	//	return;
+	//}
+
+	//TextWithEntities sel;
+	//QList<QUrl> urls;
+	//if (uponSelected) {
+	//	sel = getSelectedText();
+	//} else if (pressedHandler) {
+	//	sel = { pressedHandler->dragText(), EntitiesInText() };
+	//	//if (!sel.isEmpty() && sel.at(0) != '/' && sel.at(0) != '@' && sel.at(0) != '#') {
+	//	//	urls.push_back(QUrl::fromEncoded(sel.toUtf8())); // Google Chrome crashes in Mac OS X O_o
+	//	//}
+	//}
+	//if (auto mimeData = mimeDataFromTextWithEntities(sel)) {
+	//	updateDragSelection(0, 0, false);
+	//	_widget->noSelectingScroll();
+
+	//	if (!urls.isEmpty()) mimeData->setUrls(urls);
+	//	if (uponSelected && !Adaptive::OneColumn()) {
+	//		auto selectedState = getSelectionState();
+	//		if (selectedState.count > 0 && selectedState.count == selectedState.canForwardCount) {
+	//			mimeData->setData(qsl("application/x-td-forward-selected"), "1");
+	//		}
+	//	}
+	//	_controller->window()->launchDrag(std::move(mimeData));
+	//	return;
+	//} else {
+	//	auto forwardMimeType = QString();
+	//	auto pressedMedia = static_cast<HistoryMedia*>(nullptr);
+	//	if (auto pressedItem = App::pressedItem()) {
+	//		pressedMedia = pressedItem->getMedia();
+	//		if (_mouseCursorState == HistoryInDateCursorState || (pressedMedia && pressedMedia->dragItem())) {
+	//			forwardMimeType = qsl("application/x-td-forward-pressed");
+	//		}
+	//	}
+	//	if (auto pressedLnkItem = App::pressedLinkItem()) {
+	//		if ((pressedMedia = pressedLnkItem->getMedia())) {
+	//			if (forwardMimeType.isEmpty() && pressedMedia->dragItemByHandler(pressedHandler)) {
+	//				forwardMimeType = qsl("application/x-td-forward-pressed-link");
+	//			}
+	//		}
+	//	}
+	//	if (!forwardMimeType.isEmpty()) {
+	//		auto mimeData = std::make_unique<QMimeData>();
+	//		mimeData->setData(forwardMimeType, "1");
+	//		if (auto document = (pressedMedia ? pressedMedia->getDocument() : nullptr)) {
+	//			auto filepath = document->filepath(DocumentData::FilePathResolveChecked);
+	//			if (!filepath.isEmpty()) {
+	//				QList<QUrl> urls;
+	//				urls.push_back(QUrl::fromLocalFile(filepath));
+	//				mimeData->setUrls(urls);
+	//			}
+	//		}
+
+	//		// This call enters event loop and can destroy any QObject.
+	//		_controller->window()->launchDrag(std::move(mimeData));
+	//		return;
+	//	}
+	//} // TODO
+}
+
+int InnerWidget::itemTop(gsl::not_null<Item*> item) const {
+	return _itemsTop + item->top();
+}
+
+void InnerWidget::repaintItem(Item *item) {
+	if (!item) {
+		return;
+	}
+	update(0, itemTop(item), width(), item->height());
+}
+
+QPoint InnerWidget::mapPointToItem(QPoint point, Item *item) const {
+	if (!item) {
+		return QPoint();
+	}
+	return point - QPoint(0, itemTop(item));
 }
 
 InnerWidget::~InnerWidget() = default;
diff --git a/Telegram/SourceFiles/history/history_admin_log_inner.h b/Telegram/SourceFiles/history/history_admin_log_inner.h
index 8e7df385d..d93b76d1d 100644
--- a/Telegram/SourceFiles/history/history_admin_log_inner.h
+++ b/Telegram/SourceFiles/history/history_admin_log_inner.h
@@ -20,7 +20,17 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 */
 #pragma once
 
+#include "ui/widgets/tooltip.h"
 #include "mtproto/sender.h"
+#include "base/timer.h"
+
+namespace Ui {
+class PopupMenu;
+} // namespace Ui
+
+namespace Window {
+class Controller;
+} // namespace Window
 
 namespace AdminLog {
 
@@ -47,9 +57,9 @@ private:
 
 };
 
-class InnerWidget final : public TWidget, private MTP::Sender {
+class InnerWidget final : public TWidget, public Ui::AbstractTooltipShower, private MTP::Sender, private base::Subscriber {
 public:
-	InnerWidget(QWidget *parent, gsl::not_null<ChannelData*> channel, base::lambda<void(int top)> scrollTo);
+	InnerWidget(QWidget *parent, gsl::not_null<Window::Controller*> controller, gsl::not_null<ChannelData*> channel, base::lambda<void(int top)> scrollTo);
 
 	gsl::not_null<ChannelData*> channel() const {
 		return _channel;
@@ -72,6 +82,10 @@ public:
 	// Empty "flags" means all events. Empty "admins" means all admins.
 	void applyFilter(MTPDchannelAdminLogEventsFilter::Flags flags, const std::vector<gsl::not_null<UserData*>> &admins);
 
+	// AbstractTooltipShower interface
+	QString tooltipText() const override;
+	QPoint tooltipPos() const override;
+
 	~InnerWidget();
 
 protected:
@@ -80,6 +94,8 @@ protected:
 	void mousePressEvent(QMouseEvent *e) override;
 	void mouseMoveEvent(QMouseEvent *e) override;
 	void mouseReleaseEvent(QMouseEvent *e) override;
+	void enterEventHook(QEvent *e) override;
+	void leaveEventHook(QEvent *e) override;
 
 	// Resizes content and counts natural widget height for the desired width.
 	int resizeGetHeight(int newWidth) override;
@@ -89,6 +105,23 @@ private:
 		Up,
 		Down,
 	};
+	enum class MouseAction {
+		None,
+		PrepareDrag,
+		Dragging,
+		Selecting,
+	};
+
+	void mouseActionStart(const QPoint &screenPos, Qt::MouseButton button);
+	void mouseActionUpdate(const QPoint &screenPos);
+	void mouseActionFinish(const QPoint &screenPos, Qt::MouseButton button);
+	void mouseActionCancel();
+	void updateSelected();
+	void performDrag();
+	int itemTop(gsl::not_null<Item*> item) const;
+	void repaintItem(Item *item);
+	QPoint mapPointToItem(QPoint point, Item *item) const;
+
 	void checkPreloadMore();
 	void updateVisibleTopItem();
 	void preloadMore(Direction direction);
@@ -96,11 +129,23 @@ private:
 	void updateSize();
 	void paintEmpty(Painter &p);
 
+	void toggleScrollDateShown();
+	void repaintScrollDateCallback();
+	bool displayScrollDate() const;
+	void scrollDateHide();
+	void keepScrollDateForNow();
+	void scrollDateCheck();
+	void scrollDateHideByTimer();
+
+	gsl::not_null<Window::Controller*> _controller;
 	gsl::not_null<ChannelData*> _channel;
 	gsl::not_null<History*> _history;
 	base::lambda<void()> _cancelledCallback;
 	base::lambda<void(int top)> _scrollTo;
 	std::vector<std::unique_ptr<Item>> _items;
+	std::map<gsl::not_null<HistoryItem*>, gsl::not_null<Item*>, std::less<>> _itemsByHistoryItems;
+	int _itemsTop = 0;
+	int _itemsHeight = 0;
 
 	LocalIdManager _idManager;
 	int _minHeight = 0;
@@ -109,6 +154,14 @@ private:
 	Item *_visibleTopItem = nullptr;
 	int _visibleTopFromItem = 0;
 
+	bool _scrollDateShown = false;
+	Animation _scrollDateOpacity;
+	SingleQueuedInvokation _scrollDateCheck;
+	base::Timer _scrollDateHideTimer;
+	Item *_scrollDateLastItem = nullptr;
+	int _scrollDateLastItemTop = 0;
+	ClickHandlerPtr _scrollDateLink;
+
 	// Up - max, Down - min.
 	uint64 _maxId = 0;
 	uint64 _minId = 0;
@@ -117,6 +170,29 @@ private:
 	bool _upLoaded = false;
 	bool _downLoaded = true;
 
+	MouseAction _mouseAction = MouseAction::None;
+	TextSelectType _mouseSelectType = TextSelectType::Letters;
+	QPoint _dragStartPosition;
+	QPoint _mousePosition;
+	Item *_mouseActionItem = nullptr;
+	HistoryCursorState _mouseCursorState = HistoryDefaultCursorState;
+	uint16 _mouseTextSymbol = 0;
+	bool _pressWasInactive = false;
+
+	Item *_itemNearest = nullptr;
+	Item *_itemOver = nullptr;
+	Item *_itemPressed = nullptr;
+	Item *_selectedItem = nullptr;
+	TextSelection _selectedText;
+	bool _wasSelectedText = false; // was some text selected in current drag action
+	Qt::CursorShape _cursor = style::cur_default;
+
+	// context menu
+	Ui::PopupMenu *_menu = nullptr;
+
+	QPoint _trippleClickPoint;
+	base::Timer _trippleClickTimer;
+
 	MTPDchannelAdminLogEventsFilter::Flags _filterFlags = 0;
 	std::vector<gsl::not_null<UserData*>> _filterAdmins;
 
diff --git a/Telegram/SourceFiles/history/history_admin_log_item.cpp b/Telegram/SourceFiles/history/history_admin_log_item.cpp
index 209940ab5..01a7fec65 100644
--- a/Telegram/SourceFiles/history/history_admin_log_item.cpp
+++ b/Telegram/SourceFiles/history/history_admin_log_item.cpp
@@ -235,6 +235,7 @@ TextWithEntities GenerateParticipantChangeText(gsl::not_null<ChannelData*> chann
 
 Item::Item(gsl::not_null<History*> history, LocalIdManager &idManager, const MTPDchannelAdminLogEvent &event)
 : _id(event.vid.v)
+, _date(::date(event.vdate))
 , _history(history)
 , _from(App::user(event.vuser_id.v)) {
 	auto &action = event.vaction;
@@ -439,23 +440,42 @@ bool Item::hasPoint(QPoint point) const {
 	for (auto part : _parts) {
 		auto height = part->height();
 		if (point.y() >= top && point.y() < top + height) {
-			return part->hasPoint(point.x(), point.y() - top);
+			return part->hasPoint(point - QPoint(0, top));
 		}
 		top += height;
 	}
 	return false;
 }
 
-HistoryTextState Item::getState(QPoint point, HistoryStateRequest request) const {
+Item::TextState Item::getState(QPoint point, HistoryStateRequest request) const {
 	auto top = 0;
 	for (auto part : _parts) {
 		auto height = part->height();
 		if (point.y() >= top && point.y() < top + height) {
-			return part->getState(point.x(), point.y() - top, request);
+			auto result = TextState();
+			result.data = part->getState(point - QPoint(0, top), request);
+			result.handler = part;
+			return result;
 		}
 		top += height;
 	}
-	return HistoryTextState();
+	return Item::TextState();
+}
+
+TextSelection Item::adjustSelection(TextSelection selection, TextSelectType type) const {
+	return selection;
+}
+
+void Item::updatePressed(QPoint point) {
+}
+
+QString Item::getForwardedInfoText() const {
+	for (auto part : _parts) {
+		if (auto forwarded = part->Get<HistoryMessageForwarded>()) {
+			return forwarded->_text.originalText(AllTextSelection, ExpandLinksNone);
+		}
+	}
+	return QString();
 }
 
 Item::~Item() {
diff --git a/Telegram/SourceFiles/history/history_admin_log_item.h b/Telegram/SourceFiles/history/history_admin_log_item.h
index d2a1de4c4..58a069bfc 100644
--- a/Telegram/SourceFiles/history/history_admin_log_item.h
+++ b/Telegram/SourceFiles/history/history_admin_log_item.h
@@ -26,11 +26,19 @@ namespace AdminLog {
 
 class Item {
 public:
+	struct TextState {
+		HistoryTextState data;
+		ClickHandlerHost *handler = nullptr;
+	};
+
 	Item(gsl::not_null<History*> history, LocalIdManager &idManager, const MTPDchannelAdminLogEvent &event);
 
 	uint64 id() const {
 		return _id;
 	}
+	QDateTime date() const {
+		return _date;
+	}
 	int top() const {
 		return _top;
 	}
@@ -44,7 +52,11 @@ public:
 	int resizeGetHeight(int newWidth);
 	void draw(Painter &p, QRect clip, TextSelection selection, TimeMs ms);
 	bool hasPoint(QPoint point) const;
-	HistoryTextState getState(QPoint point, HistoryStateRequest request) const;
+	TextState getState(QPoint point, HistoryStateRequest request) const;
+	TextSelection adjustSelection(TextSelection selection, TextSelectType type) const;
+	void updatePressed(QPoint point);
+
+	QString getForwardedInfoText() const;
 
 	~Item();
 
@@ -55,6 +67,7 @@ private:
 	void addPart(HistoryItem *item);
 
 	uint64 _id = 0;
+	QDateTime _date;
 	gsl::not_null<History*> _history;
 	gsl::not_null<UserData*> _from;
 	std::vector<HistoryItem*> _parts;
diff --git a/Telegram/SourceFiles/history/history_admin_log_section.cpp b/Telegram/SourceFiles/history/history_admin_log_section.cpp
index 0de33c386..fed64fd84 100644
--- a/Telegram/SourceFiles/history/history_admin_log_section.cpp
+++ b/Telegram/SourceFiles/history/history_admin_log_section.cpp
@@ -139,7 +139,7 @@ Widget::Widget(QWidget *parent, gsl::not_null<Window::Controller*> controller, g
 	updateAdaptiveLayout();
 	subscribe(Adaptive::Changed(), [this]() { updateAdaptiveLayout(); });
 
-	_inner = _scroll->setOwnedWidget(object_ptr<InnerWidget>(this, channel, [this](int top) { _scroll->scrollToY(top); }));
+	_inner = _scroll->setOwnedWidget(object_ptr<InnerWidget>(this, controller, channel, [this](int top) { _scroll->scrollToY(top); }));
 	_scroll->move(0, _fixedBar->height());
 	_scroll->show();
 
diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp
index 9b66fa7d1..311e7249f 100644
--- a/Telegram/SourceFiles/history/history_inner_widget.cpp
+++ b/Telegram/SourceFiles/history/history_inner_widget.cpp
@@ -57,12 +57,12 @@ private:
 
 };
 
-QMimeData *mimeDataFromTextWithEntities(const TextWithEntities &forClipboard) {
+std::unique_ptr<QMimeData> mimeDataFromTextWithEntities(const TextWithEntities &forClipboard) {
 	if (forClipboard.text.isEmpty()) {
 		return nullptr;
 	}
 
-	auto result = new QMimeData();
+	auto result = std::make_unique<QMimeData>();
 	result->setText(forClipboard.text);
 	auto tags = ConvertEntitiesToTextTags(forClipboard.entities);
 	if (!tags.isEmpty()) {
@@ -107,6 +107,14 @@ HistoryInner::HistoryInner(HistoryWidget *historyWidget, gsl::not_null<Window::C
 			update();
 		}
 	});
+	subscribe(_controller->window()->dragFinished(), [this] {
+		mouseActionUpdate(QCursor::pos());
+	});
+	subscribe(AuthSession::Current().data().historyCleared(), [this](gsl::not_null<History*> history) {
+		if (_history == history) {
+			mouseActionCancel();
+		}
+	});
 }
 
 void HistoryInner::messagesReceived(PeerData *peer, const QVector<MTPMessage> &messages) {
@@ -363,7 +371,7 @@ void HistoryInner::enumerateDates(Method method) {
 }
 
 void HistoryInner::paintEvent(QPaintEvent *e) {
-	if (!App::main() || (App::wnd() && App::wnd()->contentOverlapped(this, e))) {
+	if (Ui::skipPaintEvent(this, e)) {
 		return;
 	}
 	if (hasPendingResizedItems()) {
@@ -371,17 +379,13 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
 	}
 
 	Painter p(this);
-	QRect r(e->rect());
-	bool trivial = (rect() == r);
-	if (!trivial) {
-		p.setClipRect(r);
-	}
+	auto clip = e->rect();
 	auto ms = getms();
 
 	bool historyDisplayedEmpty = (_history->isDisplayedEmpty() && (!_migrated || _migrated->isDisplayedEmpty()));
 	bool noHistoryDisplayed = _firstLoading || historyDisplayedEmpty;
 	if (!_firstLoading && _botAbout && !_botAbout->info->text.isEmpty() && _botAbout->height > 0) {
-		if (r.y() < _botAbout->rect.y() + _botAbout->rect.height() && r.y() + r.height() > _botAbout->rect.y()) {
+		if (clip.y() < _botAbout->rect.y() + _botAbout->rect.height() && clip.y() + clip.height() > _botAbout->rect.y()) {
 			p.setTextPalette(st::inTextPalette);
 			App::roundRect(p, _botAbout->rect, st::msgInBg, MessageInCorners, &st::msgInShadow);
 
@@ -398,14 +402,15 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
 		HistoryLayout::paintEmpty(p, width(), height());
 	}
 	if (!noHistoryDisplayed) {
-		adjustCurrent(r.top());
+		adjustCurrent(clip.top());
 
-		SelectedItems::const_iterator selEnd = _selected.cend();
-		bool hasSel = !_selected.isEmpty();
+		auto selEnd = _selected.cend();
+		auto hasSel = !_selected.isEmpty();
 
-		int32 drawToY = r.y() + r.height();
+		auto drawToY = clip.y() + clip.height();
 
-		int32 selfromy = itemTop(_dragSelFrom), seltoy = itemTop(_dragSelTo);
+		auto selfromy = itemTop(_dragSelFrom);
+		auto seltoy = itemTop(_dragSelTo);
 		if (selfromy < 0 || seltoy < 0) {
 			selfromy = seltoy = -1;
 		} else {
@@ -424,7 +429,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
 			auto y = mtop + block->y() + item->y();
 			p.save();
 			p.translate(0, y);
-			if (r.y() < y + item->height()) while (y < drawToY) {
+			if (clip.y() < y + item->height()) while (y < drawToY) {
 				TextSelection sel;
 				if (y >= selfromy && y < seltoy) {
 					if (_dragSelecting && !item->serviceMsg() && item->id > 0) {
@@ -436,7 +441,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
 						sel = i.value();
 					}
 				}
-				item->draw(p, r.translated(0, -y), sel, ms);
+				item->draw(p, clip.translated(0, -y), sel, ms);
 
 				if (item->hasViews()) {
 					App::main()->scheduleViewIncrement(item);
@@ -465,7 +470,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
 			auto iItem = (_curHistory == _history ? _curItem : 0);
 			auto item = block->items[iItem];
 
-			auto historyRect = r.intersected(QRect(0, hdrawtop, width(), r.top() + r.height()));
+			auto historyRect = clip.intersected(QRect(0, hdrawtop, width(), clip.top() + clip.height()));
 			auto y = htop + block->y() + item->y();
 			p.save();
 			p.translate(0, y);
@@ -507,14 +512,14 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
 		}
 
 		if (mtop >= 0 || htop >= 0) {
-			enumerateUserpics([&p, &r](HistoryMessage *message, int userpicTop) {
+			enumerateUserpics([&p, &clip](HistoryMessage *message, int userpicTop) {
 				// stop the enumeration if the userpic is below the painted rect
-				if (userpicTop >= r.top() + r.height()) {
+				if (userpicTop >= clip.top() + clip.height()) {
 					return false;
 				}
 
 				// paint the userpic if it intersects the painted rect
-				if (userpicTop + st::msgPhotoSize > r.top()) {
+				if (userpicTop + st::msgPhotoSize > clip.top()) {
 					message->from()->paintUserpicLeft(p, st::historyPhotoLeft, userpicTop, message->history()->width, st::msgPhotoSize);
 				}
 				return true;
@@ -530,9 +535,9 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
 			//int showFloatingBefore = height() - 2 * (_visibleAreaBottom - _visibleAreaTop) - dateHeight;
 
 			auto scrollDateOpacity = _scrollDateOpacity.current(ms, _scrollDateShown ? 1. : 0.);
-			enumerateDates([&p, &r, scrollDateOpacity, dateHeight/*, lastDate, showFloatingBefore*/](HistoryItem *item, int itemtop, int dateTop) {
+			enumerateDates([&p, &clip, scrollDateOpacity, dateHeight/*, lastDate, showFloatingBefore*/](HistoryItem *item, int itemtop, int dateTop) {
 				// stop the enumeration if the date is above the painted rect
-				if (dateTop + dateHeight <= r.top()) {
+				if (dateTop + dateHeight <= clip.top()) {
 					return false;
 				}
 
@@ -550,7 +555,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
 				//}
 
 				// paint the date if it intersects the painted rect
-				if (dateTop < r.top() + r.height()) {
+				if (dateTop < clip.top() + clip.height()) {
 					auto opacity = (dateInPlace/* || noFloatingDate*/) ? 1. : scrollDateOpacity;
 					if (opacity > 0.) {
 						p.setOpacity(opacity);
@@ -660,7 +665,7 @@ void HistoryInner::touchEvent(QTouchEvent *e) {
 		_touchSelectTimer.stop();
 		_touchScroll = _touchSelect = false;
 		_touchScrollState = Ui::TouchScrollState::Manual;
-		dragActionCancel();
+		mouseActionCancel();
 		return;
 	}
 
@@ -696,7 +701,7 @@ void HistoryInner::touchEvent(QTouchEvent *e) {
 	case QEvent::TouchUpdate:
 	if (!_touchInProgress) return;
 	if (_touchSelect) {
-		dragActionUpdate(_touchPos);
+		mouseActionUpdate(_touchPos);
 	} else if (!_touchScroll && (_touchPos - _touchStart).manhattanLength() >= QApplication::startDragDistance()) {
 		_touchSelectTimer.stop();
 		_touchScroll = true;
@@ -719,7 +724,7 @@ void HistoryInner::touchEvent(QTouchEvent *e) {
 	if (!_touchInProgress) return;
 	_touchInProgress = false;
 	if (_touchSelect) {
-		dragActionFinish(_touchPos, Qt::RightButton);
+		mouseActionFinish(_touchPos, Qt::RightButton);
 		QContextMenuEvent contextMenu(QContextMenuEvent::Mouse, mapFromGlobal(_touchPos), _touchPos);
 		showContextMenu(&contextMenu, true);
 		_touchScroll = false;
@@ -738,9 +743,9 @@ void HistoryInner::touchEvent(QTouchEvent *e) {
 			_touchWaitingAcceleration = false;
 			_touchPrevPosValid = false;
 		}
-	} else { // one short tap -- like mouse click
-		dragActionStart(_touchPos);
-		dragActionFinish(_touchPos);
+	} else { // One short tap is like left mouse click.
+		mouseActionStart(_touchPos, Qt::LeftButton);
+		mouseActionFinish(_touchPos, Qt::LeftButton);
 	}
 	_touchSelectTimer.stop();
 	_touchSelect = false;
@@ -752,7 +757,7 @@ void HistoryInner::mouseMoveEvent(QMouseEvent *e) {
 	static auto lastGlobalPosition = e->globalPos();
 	auto reallyMoved = (lastGlobalPosition != e->globalPos());
 	auto buttonsPressed = (e->buttons() & (Qt::LeftButton | Qt::MiddleButton));
-	if (!buttonsPressed && _dragAction != NoDrag) {
+	if (!buttonsPressed && _mouseAction != MouseAction::None) {
 		mouseReleaseEvent(e);
 	}
 	if (reallyMoved) {
@@ -761,11 +766,11 @@ void HistoryInner::mouseMoveEvent(QMouseEvent *e) {
 			keepScrollDateForNow();
 		}
 	}
-	dragActionUpdate(e->globalPos());
+	mouseActionUpdate(e->globalPos());
 }
 
-void HistoryInner::dragActionUpdate(const QPoint &screenPos) {
-	_dragPos = screenPos;
+void HistoryInner::mouseActionUpdate(const QPoint &screenPos) {
+	_mousePosition = screenPos;
 	onUpdateSelected();
 }
 
@@ -788,11 +793,11 @@ void HistoryInner::mousePressEvent(QMouseEvent *e) {
 		e->accept();
 		return; // ignore mouse press, that was hiding context menu
 	}
-	dragActionStart(e->globalPos(), e->button());
+	mouseActionStart(e->globalPos(), e->button());
 }
 
-void HistoryInner::dragActionStart(const QPoint &screenPos, Qt::MouseButton button) {
-	dragActionUpdate(screenPos);
+void HistoryInner::mouseActionStart(const QPoint &screenPos, Qt::MouseButton button) {
+	mouseActionUpdate(screenPos);
 	if (button != Qt::LeftButton) return;
 
 	ClickHandler::pressed();
@@ -802,29 +807,29 @@ void HistoryInner::dragActionStart(const QPoint &screenPos, Qt::MouseButton butt
 		repaintItem(App::pressedItem());
 	}
 
-	_dragAction = NoDrag;
-	_dragItem = App::mousedItem();
-	_dragStartPos = mapMouseToItem(mapFromGlobal(screenPos), _dragItem);
-	_dragWasInactive = App::wnd()->inactivePress();
-	if (_dragWasInactive) App::wnd()->inactivePress(false);
+	_mouseAction = MouseAction::None;
+	_mouseActionItem = App::mousedItem();
+	_dragStartPosition = mapMouseToItem(mapFromGlobal(screenPos), _mouseActionItem);
+	_pressWasInactive = _controller->window()->wasInactivePress();
+	if (_pressWasInactive) _controller->window()->setInactivePress(false);
 
 	if (ClickHandler::getPressed()) {
-		_dragAction = PrepareDrag;
+		_mouseAction = MouseAction::PrepareDrag;
 	} else if (!_selected.isEmpty()) {
 		if (_selected.cbegin().value() == FullSelection) {
-			if (_selected.constFind(_dragItem) != _selected.cend() && App::hoveredItem()) {
-				_dragAction = PrepareDrag; // start items drag
-			} else if (!_dragWasInactive) {
-				_dragAction = PrepareSelect; // start items select
+			if (_selected.constFind(_mouseActionItem) != _selected.cend() && App::hoveredItem()) {
+				_mouseAction = MouseAction::PrepareDrag; // start items drag
+			} else if (!_pressWasInactive) {
+				_mouseAction = MouseAction::PrepareSelect; // start items select
 			}
 		}
 	}
-	if (_dragAction == NoDrag && _dragItem) {
+	if (_mouseAction == MouseAction::None && _mouseActionItem) {
 		HistoryTextState dragState;
 		if (_trippleClickTimer.isActive() && (screenPos - _trippleClickPoint).manhattanLength() < QApplication::startDragDistance()) {
 			HistoryStateRequest request;
 			request.flags = Text::StateRequest::Flag::LookupSymbol;
-			dragState = _dragItem->getState(_dragStartPos.x(), _dragStartPos.y(), request);
+			dragState = _mouseActionItem->getState(_dragStartPosition, request);
 			if (dragState.cursor == HistoryInTextCursorState) {
 				TextSelection selStatus = { dragState.symbol, dragState.symbol };
 				if (selStatus != FullSelection && (_selected.isEmpty() || _selected.cbegin().value() != FullSelection)) {
@@ -832,95 +837,95 @@ void HistoryInner::dragActionStart(const QPoint &screenPos, Qt::MouseButton butt
 						repaintItem(_selected.cbegin().key());
 						_selected.clear();
 					}
-					_selected.insert(_dragItem, selStatus);
-					_dragSymbol = dragState.symbol;
-					_dragAction = Selecting;
-					_dragSelType = TextSelectType::Paragraphs;
-					dragActionUpdate(_dragPos);
+					_selected.insert(_mouseActionItem, selStatus);
+					_mouseTextSymbol = dragState.symbol;
+					_mouseAction = MouseAction::Selecting;
+					_mouseSelectType = TextSelectType::Paragraphs;
+					mouseActionUpdate(_mousePosition);
 					_trippleClickTimer.start(QApplication::doubleClickInterval());
 				}
 			}
 		} else if (App::pressedItem()) {
 			HistoryStateRequest request;
 			request.flags = Text::StateRequest::Flag::LookupSymbol;
-			dragState = _dragItem->getState(_dragStartPos.x(), _dragStartPos.y(), request);
+			dragState = _mouseActionItem->getState(_dragStartPosition, request);
 		}
-		if (_dragSelType != TextSelectType::Paragraphs) {
+		if (_mouseSelectType != TextSelectType::Paragraphs) {
 			if (App::pressedItem()) {
-				_dragSymbol = dragState.symbol;
+				_mouseTextSymbol = dragState.symbol;
 				bool uponSelected = (dragState.cursor == HistoryInTextCursorState);
 				if (uponSelected) {
 					if (_selected.isEmpty() ||
 						_selected.cbegin().value() == FullSelection ||
-						_selected.cbegin().key() != _dragItem
+						_selected.cbegin().key() != _mouseActionItem
 						) {
 						uponSelected = false;
 					} else {
 						uint16 selFrom = _selected.cbegin().value().from, selTo = _selected.cbegin().value().to;
-						if (_dragSymbol < selFrom || _dragSymbol >= selTo) {
+						if (_mouseTextSymbol < selFrom || _mouseTextSymbol >= selTo) {
 							uponSelected = false;
 						}
 					}
 				}
 				if (uponSelected) {
-					_dragAction = PrepareDrag; // start text drag
-				} else if (!_dragWasInactive) {
-					if (dynamic_cast<HistorySticker*>(App::pressedItem()->getMedia()) || _dragCursorState == HistoryInDateCursorState) {
-						_dragAction = PrepareDrag; // start sticker drag or by-date drag
+					_mouseAction = MouseAction::PrepareDrag; // start text drag
+				} else if (!_pressWasInactive) {
+					if (dynamic_cast<HistorySticker*>(App::pressedItem()->getMedia()) || _mouseCursorState == HistoryInDateCursorState) {
+						_mouseAction = MouseAction::PrepareDrag; // start sticker drag or by-date drag
 					} else {
-						if (dragState.afterSymbol) ++_dragSymbol;
-						TextSelection selStatus = { _dragSymbol, _dragSymbol };
+						if (dragState.afterSymbol) ++_mouseTextSymbol;
+						TextSelection selStatus = { _mouseTextSymbol, _mouseTextSymbol };
 						if (selStatus != FullSelection && (_selected.isEmpty() || _selected.cbegin().value() != FullSelection)) {
 							if (!_selected.isEmpty()) {
 								repaintItem(_selected.cbegin().key());
 								_selected.clear();
 							}
-							_selected.insert(_dragItem, selStatus);
-							_dragAction = Selecting;
-							repaintItem(_dragItem);
+							_selected.insert(_mouseActionItem, selStatus);
+							_mouseAction = MouseAction::Selecting;
+							repaintItem(_mouseActionItem);
 						} else {
-							_dragAction = PrepareSelect;
+							_mouseAction = MouseAction::PrepareSelect;
 						}
 					}
 				}
-			} else if (!_dragWasInactive) {
-				_dragAction = PrepareSelect; // start items select
+			} else if (!_pressWasInactive) {
+				_mouseAction = MouseAction::PrepareSelect; // start items select
 			}
 		}
 	}
 
-	if (!_dragItem) {
-		_dragAction = NoDrag;
-	} else if (_dragAction == NoDrag) {
-		_dragItem = nullptr;
+	if (!_mouseActionItem) {
+		_mouseAction = MouseAction::None;
+	} else if (_mouseAction == MouseAction::None) {
+		_mouseActionItem = nullptr;
 	}
 }
 
-void HistoryInner::dragActionCancel() {
-	_dragItem = 0;
-	_dragAction = NoDrag;
-	_dragStartPos = QPoint(0, 0);
-	_dragSelFrom = _dragSelTo = 0;
+void HistoryInner::mouseActionCancel() {
+	_mouseActionItem = nullptr;
+	_mouseAction = MouseAction::None;
+	_dragStartPosition = QPoint(0, 0);
+	_dragSelFrom = _dragSelTo = nullptr;
 	_wasSelectedText = false;
 	_widget->noSelectingScroll();
 }
 
-void HistoryInner::onDragExec() {
-	if (_dragAction != Dragging) return;
+void HistoryInner::performDrag() {
+	if (_mouseAction != MouseAction::Dragging) return;
 
 	bool uponSelected = false;
-	if (_dragItem) {
+	if (_mouseActionItem) {
 		if (!_selected.isEmpty() && _selected.cbegin().value() == FullSelection) {
-			uponSelected = _selected.contains(_dragItem);
+			uponSelected = _selected.contains(_mouseActionItem);
 		} else {
 			HistoryStateRequest request;
 			request.flags |= Text::StateRequest::Flag::LookupSymbol;
-			auto dragState = _dragItem->getState(_dragStartPos.x(), _dragStartPos.y(), request);
+			auto dragState = _mouseActionItem->getState(_dragStartPosition, request);
 			uponSelected = (dragState.cursor == HistoryInTextCursorState);
 			if (uponSelected) {
 				if (_selected.isEmpty() ||
 					_selected.cbegin().value() == FullSelection ||
-					_selected.cbegin().key() != _dragItem
+					_selected.cbegin().key() != _mouseActionItem
 					) {
 					uponSelected = false;
 				} else {
@@ -952,7 +957,6 @@ void HistoryInner::onDragExec() {
 		updateDragSelection(0, 0, false);
 		_widget->noSelectingScroll();
 
-		auto drag = std::make_unique<QDrag>(App::wnd());
 		if (!urls.isEmpty()) mimeData->setUrls(urls);
 		if (uponSelected && !Adaptive::OneColumn()) {
 			auto selectedState = getSelectionState();
@@ -960,19 +964,14 @@ void HistoryInner::onDragExec() {
 				mimeData->setData(qsl("application/x-td-forward-selected"), "1");
 			}
 		}
-		drag->setMimeData(mimeData);
-		drag->exec(Qt::CopyAction);
-
-		// We don't receive mouseReleaseEvent when drag is finished.
-		ClickHandler::unpressed();
-		if (App::main()) App::main()->updateAfterDrag();
+		_controller->window()->launchDrag(std::move(mimeData));
 		return;
 	} else {
 		auto forwardMimeType = QString();
 		auto pressedMedia = static_cast<HistoryMedia*>(nullptr);
 		if (auto pressedItem = App::pressedItem()) {
 			pressedMedia = pressedItem->getMedia();
-			if (_dragCursorState == HistoryInDateCursorState || (pressedMedia && pressedMedia->dragItem())) {
+			if (_mouseCursorState == HistoryInDateCursorState || (pressedMedia && pressedMedia->dragItem())) {
 				forwardMimeType = qsl("application/x-td-forward-pressed");
 			}
 		}
@@ -984,9 +983,7 @@ void HistoryInner::onDragExec() {
 			}
 		}
 		if (!forwardMimeType.isEmpty()) {
-			auto drag = std::make_unique<QDrag>(App::wnd());
 			auto mimeData = std::make_unique<QMimeData>();
-
 			mimeData->setData(forwardMimeType, "1");
 			if (auto document = (pressedMedia ? pressedMedia->getDocument() : nullptr)) {
 				auto filepath = document->filepath(DocumentData::FilePathResolveChecked);
@@ -997,12 +994,8 @@ void HistoryInner::onDragExec() {
 				}
 			}
 
-			drag->setMimeData(mimeData.release());
-			drag->exec(Qt::CopyAction);
-
-			// We don't receive mouseReleaseEvent when drag is finished.
-			ClickHandler::unpressed();
-			if (App::main()) App::main()->updateAfterDrag();
+			// This call enters event loop and can destroy any QObject.
+			_controller->window()->launchDrag(std::move(mimeData));
 			return;
 		}
 	}
@@ -1022,8 +1015,8 @@ void HistoryInner::itemRemoved(HistoryItem *item) {
 		_widget->updateTopBarSelection();
 	}
 
-	if (_dragItem == item) {
-		dragActionCancel();
+	if (_mouseActionItem == item) {
+		mouseActionCancel();
 	}
 
 	if (_dragSelFrom == item || _dragSelTo == item) {
@@ -1034,16 +1027,16 @@ void HistoryInner::itemRemoved(HistoryItem *item) {
 	onUpdateSelected();
 }
 
-void HistoryInner::dragActionFinish(const QPoint &screenPos, Qt::MouseButton button) {
-	dragActionUpdate(screenPos);
+void HistoryInner::mouseActionFinish(const QPoint &screenPos, Qt::MouseButton button) {
+	mouseActionUpdate(screenPos);
 
 	ClickHandlerPtr activated = ClickHandler::unpressed();
-	if (_dragAction == Dragging) {
+	if (_mouseAction == MouseAction::Dragging) {
 		activated.clear();
-	} else if (HistoryItem *pressed = App::pressedLinkItem()) {
+	} else if (auto pressed = App::pressedLinkItem()) {
 		// if we are in selecting items mode perhaps we want to
 		// toggle selection instead of activating the pressed link
-		if (_dragAction == PrepareDrag && !_dragWasInactive && !_selected.isEmpty() && _selected.cbegin().value() == FullSelection && button != Qt::RightButton) {
+		if (_mouseAction == MouseAction::PrepareDrag && !_pressWasInactive && !_selected.isEmpty() && _selected.cbegin().value() == FullSelection && button != Qt::RightButton) {
 			if (HistoryMedia *media = pressed->getMedia()) {
 				if (media->toggleSelectionByHandlerClick(activated)) {
 					activated.clear();
@@ -1059,52 +1052,52 @@ void HistoryInner::dragActionFinish(const QPoint &screenPos, Qt::MouseButton but
 	_wasSelectedText = false;
 
 	if (activated) {
-		dragActionCancel();
+		mouseActionCancel();
 		App::activateClickHandler(activated, button);
 		return;
 	}
-	if (_dragAction == PrepareSelect && !_dragWasInactive && !_selected.isEmpty() && _selected.cbegin().value() == FullSelection) {
-		SelectedItems::iterator i = _selected.find(_dragItem);
-		if (i == _selected.cend() && !_dragItem->serviceMsg() && _dragItem->id > 0) {
+	if (_mouseAction == MouseAction::PrepareSelect && !_pressWasInactive && !_selected.isEmpty() && _selected.cbegin().value() == FullSelection) {
+		SelectedItems::iterator i = _selected.find(_mouseActionItem);
+		if (i == _selected.cend() && !_mouseActionItem->serviceMsg() && _mouseActionItem->id > 0) {
 			if (_selected.size() < MaxSelectedItems) {
 				if (!_selected.isEmpty() && _selected.cbegin().value() != FullSelection) {
 					_selected.clear();
 				}
-				_selected.insert(_dragItem, FullSelection);
+				_selected.insert(_mouseActionItem, FullSelection);
 			}
 		} else {
 			_selected.erase(i);
 		}
-		repaintItem(_dragItem);
-	} else if (_dragAction == PrepareDrag && !_dragWasInactive && button != Qt::RightButton) {
-		SelectedItems::iterator i = _selected.find(_dragItem);
+		repaintItem(_mouseActionItem);
+	} else if (_mouseAction == MouseAction::PrepareDrag && !_pressWasInactive && button != Qt::RightButton) {
+		SelectedItems::iterator i = _selected.find(_mouseActionItem);
 		if (i != _selected.cend() && i.value() == FullSelection) {
 			_selected.erase(i);
-			repaintItem(_dragItem);
-		} else if (i == _selected.cend() && !_dragItem->serviceMsg() && _dragItem->id > 0 && !_selected.isEmpty() && _selected.cbegin().value() == FullSelection) {
+			repaintItem(_mouseActionItem);
+		} else if (i == _selected.cend() && !_mouseActionItem->serviceMsg() && _mouseActionItem->id > 0 && !_selected.isEmpty() && _selected.cbegin().value() == FullSelection) {
 			if (_selected.size() < MaxSelectedItems) {
-				_selected.insert(_dragItem, FullSelection);
-				repaintItem(_dragItem);
+				_selected.insert(_mouseActionItem, FullSelection);
+				repaintItem(_mouseActionItem);
 			}
 		} else {
 			_selected.clear();
 			update();
 		}
-	} else if (_dragAction == Selecting) {
+	} else if (_mouseAction == MouseAction::Selecting) {
 		if (_dragSelFrom && _dragSelTo) {
 			applyDragSelection();
 			_dragSelFrom = _dragSelTo = 0;
-		} else if (!_selected.isEmpty() && !_dragWasInactive) {
+		} else if (!_selected.isEmpty() && !_pressWasInactive) {
 			auto sel = _selected.cbegin().value();
 			if (sel != FullSelection && sel.from == sel.to) {
 				_selected.clear();
-				if (App::wnd()) App::wnd()->setInnerFocus();
+				App::wnd()->setInnerFocus();
 			}
 		}
 	}
-	_dragAction = NoDrag;
-	_dragItem = 0;
-	_dragSelType = TextSelectType::Letters;
+	_mouseAction = MouseAction::None;
+	_mouseActionItem = nullptr;
+	_mouseSelectType = TextSelectType::Letters;
 	_widget->noSelectingScroll();
 	_widget->updateTopBarSelection();
 
@@ -1116,7 +1109,7 @@ void HistoryInner::dragActionFinish(const QPoint &screenPos, Qt::MouseButton but
 }
 
 void HistoryInner::mouseReleaseEvent(QMouseEvent *e) {
-	dragActionFinish(e->globalPos(), e->button());
+	mouseActionFinish(e->globalPos(), e->button());
 	if (!rect().contains(e->pos())) {
 		leaveEvent(e);
 	}
@@ -1125,22 +1118,22 @@ void HistoryInner::mouseReleaseEvent(QMouseEvent *e) {
 void HistoryInner::mouseDoubleClickEvent(QMouseEvent *e) {
 	if (!_history) return;
 
-	dragActionStart(e->globalPos(), e->button());
-	if (((_dragAction == Selecting && !_selected.isEmpty() && _selected.cbegin().value() != FullSelection) || (_dragAction == NoDrag && (_selected.isEmpty() || _selected.cbegin().value() != FullSelection))) && _dragSelType == TextSelectType::Letters && _dragItem) {
+	mouseActionStart(e->globalPos(), e->button());
+	if (((_mouseAction == MouseAction::Selecting && !_selected.isEmpty() && _selected.cbegin().value() != FullSelection) || (_mouseAction == MouseAction::None && (_selected.isEmpty() || _selected.cbegin().value() != FullSelection))) && _mouseSelectType == TextSelectType::Letters && _mouseActionItem) {
 		HistoryStateRequest request;
 		request.flags |= Text::StateRequest::Flag::LookupSymbol;
-		auto dragState = _dragItem->getState(_dragStartPos.x(), _dragStartPos.y(), request);
+		auto dragState = _mouseActionItem->getState(_dragStartPosition, request);
 		if (dragState.cursor == HistoryInTextCursorState) {
-			_dragSymbol = dragState.symbol;
-			_dragSelType = TextSelectType::Words;
-			if (_dragAction == NoDrag) {
-				_dragAction = Selecting;
+			_mouseTextSymbol = dragState.symbol;
+			_mouseSelectType = TextSelectType::Words;
+			if (_mouseAction == MouseAction::None) {
+				_mouseAction = MouseAction::Selecting;
 				TextSelection selStatus = { dragState.symbol, dragState.symbol };
 				if (!_selected.isEmpty()) {
 					repaintItem(_selected.cbegin().key());
 					_selected.clear();
 				}
-				_selected.insert(_dragItem, selStatus);
+				_selected.insert(_mouseActionItem, selStatus);
 			}
 			mouseMoveEvent(e);
 
@@ -1160,7 +1153,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
 		_menu = 0;
 	}
 	if (e->reason() == QContextMenuEvent::Mouse) {
-		dragActionUpdate(e->globalPos());
+		mouseActionUpdate(e->globalPos());
 	}
 
 	auto selectedState = getSelectionState();
@@ -1181,10 +1174,10 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
 			uint16 selFrom = _selected.cbegin().value().from, selTo = _selected.cbegin().value().to;
 			hasSelected = (selTo > selFrom) ? 1 : 0;
 			if (App::mousedItem() && App::mousedItem() == App::hoveredItem()) {
-				QPoint mousePos(mapMouseToItem(mapFromGlobal(_dragPos), App::mousedItem()));
+				QPoint mousePos(mapMouseToItem(mapFromGlobal(_mousePosition), App::mousedItem()));
 				HistoryStateRequest request;
 				request.flags |= Text::StateRequest::Flag::LookupSymbol;
-				auto dragState = App::mousedItem()->getState(mousePos.x(), mousePos.y(), request);
+				auto dragState = App::mousedItem()->getState(mousePos, request);
 				if (dragState.cursor == HistoryInTextCursorState && dragState.symbol >= selFrom && dragState.symbol < selTo) {
 					isUponSelected = 1;
 				}
@@ -1472,7 +1465,7 @@ void HistoryInner::openContextGif() {
 	if (auto item = App::contextItem()) {
 		if (auto media = item->getMedia()) {
 			if (auto document = media->getDocument()) {
-				App::wnd()->showDocument(document, item);
+				_controller->window()->showDocument(document, item);
 			}
 		}
 	}
@@ -1499,7 +1492,7 @@ void HistoryInner::copyContextText() {
 
 void HistoryInner::setToClipboard(const TextWithEntities &forClipboard, QClipboard::Mode mode) {
 	if (auto data = mimeDataFromTextWithEntities(forClipboard)) {
-		QApplication::clipboard()->setMimeData(data, mode);
+		QApplication::clipboard()->setMimeData(data.release(), mode);
 	}
 }
 
@@ -1510,7 +1503,7 @@ void HistoryInner::resizeEvent(QResizeEvent *e) {
 TextWithEntities HistoryInner::getSelectedText() const {
 	SelectedItems sel = _selected;
 
-	if (_dragAction == Selecting && _dragSelFrom && _dragSelTo) {
+	if (_mouseAction == MouseAction::Selecting && _dragSelFrom && _dragSelTo) {
 		applyDragSelection(&sel);
 	}
 
@@ -1813,14 +1806,14 @@ void HistoryInner::updateSize() {
 	if (width() != _scroll->width() || height() != newHeight) {
 		resize(_scroll->width(), newHeight);
 
-		dragActionUpdate(QCursor::pos());
+		mouseActionUpdate(QCursor::pos());
 	} else {
 		update();
 	}
 }
 
 void HistoryInner::enterEventHook(QEvent *e) {
-	dragActionUpdate(QCursor::pos());
+	mouseActionUpdate(QCursor::pos());
 	return TWidget::enterEventHook(e);
 }
 
@@ -1840,7 +1833,7 @@ void HistoryInner::leaveEventHook(QEvent *e) {
 
 HistoryInner::~HistoryInner() {
 	delete _menu;
-	_dragAction = NoDrag;
+	_mouseAction = MouseAction::None;
 }
 
 bool HistoryInner::focusNextPrevChild(bool next) {
@@ -1992,7 +1985,7 @@ void HistoryInner::selectItem(HistoryItem *item) {
 
 void HistoryInner::onTouchSelect() {
 	_touchSelect = true;
-	dragActionStart(_touchPos);
+	mouseActionStart(_touchPos, Qt::LeftButton);
 }
 
 void HistoryInner::onUpdateSelected() {
@@ -2000,7 +1993,7 @@ void HistoryInner::onUpdateSelected() {
 		return;
 	}
 
-	auto mousePos = mapFromGlobal(_dragPos);
+	auto mousePos = mapFromGlobal(_mousePosition);
 	auto point = _widget->clampMousePosition(mousePos);
 
 	HistoryBlock *block = 0;
@@ -2014,7 +2007,7 @@ void HistoryInner::onUpdateSelected() {
 
 		App::mousedItem(item);
 		m = mapMouseToItem(point, item);
-		if (item->hasPoint(m.x(), m.y())) {
+		if (item->hasPoint(m)) {
 			if (App::hoveredItem() != item) {
 				repaintItem(App::hoveredItem());
 				App::hoveredItem(item);
@@ -2025,25 +2018,25 @@ void HistoryInner::onUpdateSelected() {
 			App::hoveredItem(0);
 		}
 	}
-	if (_dragItem && _dragItem->detached()) {
-		dragActionCancel();
+	if (_mouseActionItem && _mouseActionItem->detached()) {
+		mouseActionCancel();
 	}
 
 	HistoryTextState dragState;
 	ClickHandlerHost *lnkhost = nullptr;
-	bool selectingText = (item == _dragItem && item == App::hoveredItem() && !_selected.isEmpty() && _selected.cbegin().value() != FullSelection);
+	bool selectingText = (item == _mouseActionItem && item == App::hoveredItem() && !_selected.isEmpty() && _selected.cbegin().value() != FullSelection);
 	if (point.y() < _historyPaddingTop) {
 		if (_botAbout && !_botAbout->info->text.isEmpty() && _botAbout->height > 0) {
-			dragState = _botAbout->info->text.getState(point.x() - _botAbout->rect.left() - st::msgPadding.left(), point.y() - _botAbout->rect.top() - st::msgPadding.top() - st::botDescSkip - st::msgNameFont->height, _botAbout->width);
+			dragState = _botAbout->info->text.getState(point - _botAbout->rect.topLeft() - QPoint(st::msgPadding.left(), st::msgPadding.top() + st::botDescSkip + st::msgNameFont->height), _botAbout->width);
 			lnkhost = _botAbout.get();
 		}
 	} else if (item) {
-		if (item != _dragItem || (m - _dragStartPos).manhattanLength() >= QApplication::startDragDistance()) {
-			if (_dragAction == PrepareDrag) {
-				_dragAction = Dragging;
-				QTimer::singleShot(1, this, SLOT(onDragExec()));
-			} else if (_dragAction == PrepareSelect) {
-				_dragAction = Selecting;
+		if (item != _mouseActionItem || (m - _dragStartPosition).manhattanLength() >= QApplication::startDragDistance()) {
+			if (_mouseAction == MouseAction::PrepareDrag) {
+				_mouseAction = MouseAction::Dragging;
+				InvokeQueued(this, [this] { performDrag(); });
+			} else if (_mouseAction == MouseAction::PrepareSelect) {
+				_mouseAction = MouseAction::Selecting;
 			}
 		}
 
@@ -2098,12 +2091,12 @@ void HistoryInner::onUpdateSelected() {
 		});
 		if (!dragState.link) {
 			HistoryStateRequest request;
-			if (_dragAction == Selecting) {
+			if (_mouseAction == MouseAction::Selecting) {
 				request.flags |= Text::StateRequest::Flag::LookupSymbol;
 			} else {
 				selectingText = false;
 			}
-			dragState = item->getState(m.x(), m.y(), request);
+			dragState = item->getState(m, request);
 			lnkhost = item;
 			if (!dragState.link && m.x() >= st::historyPhotoLeft && m.x() < st::historyPhotoLeft + st::msgPhotoSize) {
 				if (auto msg = item->toHistoryMessage()) {
@@ -2128,7 +2121,7 @@ void HistoryInner::onUpdateSelected() {
 		}
 	}
 	auto lnkChanged = ClickHandler::setActive(dragState.link, lnkhost);
-	if (lnkChanged || dragState.cursor != _dragCursorState) {
+	if (lnkChanged || dragState.cursor != _mouseCursorState) {
 		Ui::Tooltip::Hide();
 	}
 	if (dragState.link || dragState.cursor == HistoryInDateCursorState || dragState.cursor == HistoryInForwardedCursorState) {
@@ -2136,27 +2129,27 @@ void HistoryInner::onUpdateSelected() {
 	}
 
 	Qt::CursorShape cur = style::cur_default;
-	if (_dragAction == NoDrag) {
-		_dragCursorState = dragState.cursor;
+	if (_mouseAction == MouseAction::None) {
+		_mouseCursorState = dragState.cursor;
 		if (dragState.link) {
 			cur = style::cur_pointer;
-		} else if (_dragCursorState == HistoryInTextCursorState && (_selected.isEmpty() || _selected.cbegin().value() != FullSelection)) {
+		} else if (_mouseCursorState == HistoryInTextCursorState && (_selected.isEmpty() || _selected.cbegin().value() != FullSelection)) {
 			cur = style::cur_text;
-		} else if (_dragCursorState == HistoryInDateCursorState) {
+		} else if (_mouseCursorState == HistoryInDateCursorState) {
 			//			cur = style::cur_cross;
 		}
 	} else if (item) {
-		if (_dragAction == Selecting) {
+		if (_mouseAction == MouseAction::Selecting) {
 			auto canSelectMany = (_history != nullptr);
 			if (selectingText) {
 				uint16 second = dragState.symbol;
-				if (dragState.afterSymbol && _dragSelType == TextSelectType::Letters) {
+				if (dragState.afterSymbol && _mouseSelectType == TextSelectType::Letters) {
 					++second;
 				}
-				auto selState = _dragItem->adjustSelection({ qMin(second, _dragSymbol), qMax(second, _dragSymbol) }, _dragSelType);
-				if (_selected[_dragItem] != selState) {
-					_selected[_dragItem] = selState;
-					repaintItem(_dragItem);
+				auto selState = _mouseActionItem->adjustSelection({ qMin(second, _mouseTextSymbol), qMax(second, _mouseTextSymbol) }, _mouseSelectType);
+				if (_selected[_mouseActionItem] != selState) {
+					_selected[_mouseActionItem] = selState;
+					repaintItem(_mouseActionItem);
 				}
 				if (!_wasSelectedText && (selState == FullSelection || selState.from != selState.to)) {
 					_wasSelectedText = true;
@@ -2164,20 +2157,20 @@ void HistoryInner::onUpdateSelected() {
 				}
 				updateDragSelection(0, 0, false);
 			} else if (canSelectMany) {
-				auto selectingDown = (itemTop(_dragItem) < itemTop(item)) || (_dragItem == item && _dragStartPos.y() < m.y());
-				auto dragSelFrom = _dragItem, dragSelTo = item;
-				if (!dragSelFrom->hasPoint(_dragStartPos.x(), _dragStartPos.y())) { // maybe exclude dragSelFrom
+				auto selectingDown = (itemTop(_mouseActionItem) < itemTop(item)) || (_mouseActionItem == item && _dragStartPosition.y() < m.y());
+				auto dragSelFrom = _mouseActionItem, dragSelTo = item;
+				if (!dragSelFrom->hasPoint(_dragStartPosition)) { // maybe exclude dragSelFrom
 					if (selectingDown) {
-						if (_dragStartPos.y() >= dragSelFrom->height() - dragSelFrom->marginBottom() || ((item == dragSelFrom) && (m.y() < _dragStartPos.y() + QApplication::startDragDistance() || m.y() < dragSelFrom->marginTop()))) {
+						if (_dragStartPosition.y() >= dragSelFrom->height() - dragSelFrom->marginBottom() || ((item == dragSelFrom) && (m.y() < _dragStartPosition.y() + QApplication::startDragDistance() || m.y() < dragSelFrom->marginTop()))) {
 							dragSelFrom = (dragSelFrom == dragSelTo) ? 0 : nextItem(dragSelFrom);
 						}
 					} else {
-						if (_dragStartPos.y() < dragSelFrom->marginTop() || ((item == dragSelFrom) && (m.y() >= _dragStartPos.y() - QApplication::startDragDistance() || m.y() >= dragSelFrom->height() - dragSelFrom->marginBottom()))) {
+						if (_dragStartPosition.y() < dragSelFrom->marginTop() || ((item == dragSelFrom) && (m.y() >= _dragStartPosition.y() - QApplication::startDragDistance() || m.y() >= dragSelFrom->height() - dragSelFrom->marginBottom()))) {
 							dragSelFrom = (dragSelFrom == dragSelTo) ? 0 : prevItem(dragSelFrom);
 						}
 					}
 				}
-				if (_dragItem != item) { // maybe exclude dragSelTo
+				if (_mouseActionItem != item) { // maybe exclude dragSelTo
 					if (selectingDown) {
 						if (m.y() < dragSelTo->marginTop()) {
 							dragSelTo = (dragSelFrom == dragSelTo) ? 0 : prevItem(dragSelTo);
@@ -2199,12 +2192,12 @@ void HistoryInner::onUpdateSelected() {
 				}
 				updateDragSelection(dragSelFrom, dragSelTo, dragSelecting);
 			}
-		} else if (_dragAction == Dragging) {
+		} else if (_mouseAction == MouseAction::Dragging) {
 		}
 
 		if (ClickHandler::getPressed()) {
 			cur = style::cur_pointer;
-		} else if (_dragAction == Selecting && !_selected.isEmpty() && _selected.cbegin().value() != FullSelection) {
+		} else if (_mouseAction == MouseAction::Selecting && !_selected.isEmpty() && _selected.cbegin().value() != FullSelection) {
 			if (!_dragSelFrom || !_dragSelTo) {
 				cur = style::cur_text;
 			}
@@ -2216,19 +2209,19 @@ void HistoryInner::onUpdateSelected() {
 		if (!pressedItem->detached()) {
 			if (pressedItem->history() == _history || pressedItem->history() == _migrated) {
 				auto adjustedPoint = mapMouseToItem(point, pressedItem);
-				pressedItem->updatePressed(adjustedPoint.x(), adjustedPoint.y());
+				pressedItem->updatePressed(adjustedPoint);
 			}
 		}
 	}
 
-	if (_dragAction == Selecting) {
+	if (_mouseAction == MouseAction::Selecting) {
 		_widget->checkSelectingScroll(mousePos);
 	} else {
 		updateDragSelection(0, 0, false);
 		_widget->noSelectingScroll();
 	}
 
-	if (_dragAction == NoDrag && (lnkChanged || cur != _cursor)) {
+	if (_mouseAction == MouseAction::None && (lnkChanged || cur != _cursor)) {
 		setCursor(_cursor = cur);
 	}
 }
@@ -2414,7 +2407,7 @@ void HistoryInner::applyDragSelection(SelectedItems *toItems) const {
 }
 
 QString HistoryInner::tooltipText() const {
-	if (_dragCursorState == HistoryInDateCursorState && _dragAction == NoDrag) {
+	if (_mouseCursorState == HistoryInDateCursorState && _mouseAction == MouseAction::None) {
 		if (App::hoveredItem()) {
 			auto dateText = App::hoveredItem()->date.toString(QLocale::system().dateTimeFormat(QLocale::LongFormat));
 			if (auto edited = App::hoveredItem()->Get<HistoryMessageEdited>()) {
@@ -2425,7 +2418,7 @@ QString HistoryInner::tooltipText() const {
 			}
 			return dateText;
 		}
-	} else if (_dragCursorState == HistoryInForwardedCursorState && _dragAction == NoDrag) {
+	} else if (_mouseCursorState == HistoryInForwardedCursorState && _mouseAction == MouseAction::None) {
 		if (App::hoveredItem()) {
 			if (auto forwarded = App::hoveredItem()->Get<HistoryMessageForwarded>()) {
 				return forwarded->_text.originalText(AllTextSelection, ExpandLinksNone);
@@ -2438,14 +2431,14 @@ QString HistoryInner::tooltipText() const {
 }
 
 QPoint HistoryInner::tooltipPos() const {
-	return _dragPos;
+	return _mousePosition;
 }
 
 void HistoryInner::onParentGeometryChanged() {
 	auto mousePos = QCursor::pos();
 	auto mouseOver = _widget->rect().contains(_widget->mapFromGlobal(mousePos));
-	auto needToUpdate = (_dragAction != NoDrag || _touchScroll || mouseOver);
+	auto needToUpdate = (_mouseAction != MouseAction::None || _touchScroll || mouseOver);
 	if (needToUpdate) {
-		dragActionUpdate(mousePos);
+		mouseActionUpdate(mousePos);
 	}
 }
diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h
index 6d329e8d8..1374d0ffb 100644
--- a/Telegram/SourceFiles/history/history_inner_widget.h
+++ b/Telegram/SourceFiles/history/history_inner_widget.h
@@ -44,11 +44,6 @@ public:
 
 	TextWithEntities getSelectedText() const;
 
-	void dragActionStart(const QPoint &screenPos, Qt::MouseButton button = Qt::LeftButton);
-	void dragActionUpdate(const QPoint &screenPos);
-	void dragActionFinish(const QPoint &screenPos, Qt::MouseButton button = Qt::LeftButton);
-	void dragActionCancel();
-
 	void touchScrollUpdated(const QPoint &screenPos);
 	QPoint mapMouseToItem(QPoint p, HistoryItem *item);
 
@@ -125,13 +120,26 @@ public slots:
 	void onMenuDestroy(QObject *obj);
 	void onTouchSelect();
 	void onTouchScrollTimer();
-	void onDragExec();
 
 private slots:
 	void onScrollDateCheck();
 	void onScrollDateHideByTimer();
 
 private:
+	enum class MouseAction {
+		None,
+		PrepareDrag,
+		Dragging,
+		PrepareSelect,
+		Selecting,
+	};
+
+	void mouseActionStart(const QPoint &screenPos, Qt::MouseButton button);
+	void mouseActionUpdate(const QPoint &screenPos);
+	void mouseActionFinish(const QPoint &screenPos, Qt::MouseButton button);
+	void mouseActionCancel();
+	void performDrag();
+
 	void showContextMenu(QContextMenuEvent *e, bool showFromTouch = false);
 
 	void itemRemoved(HistoryItem *item);
@@ -207,20 +215,14 @@ private:
 		return (_history && _history->hasPendingResizedItems()) || (_migrated && _migrated->hasPendingResizedItems());
 	}
 
-	enum DragAction {
-		NoDrag = 0x00,
-		PrepareDrag = 0x01,
-		Dragging = 0x02,
-		PrepareSelect = 0x03,
-		Selecting = 0x04,
-	};
-	DragAction _dragAction = NoDrag;
-	TextSelectType _dragSelType = TextSelectType::Letters;
-	QPoint _dragStartPos, _dragPos;
-	HistoryItem *_dragItem = nullptr;
-	HistoryCursorState _dragCursorState = HistoryDefaultCursorState;
-	uint16 _dragSymbol = 0;
-	bool _dragWasInactive = false;
+	MouseAction _mouseAction = MouseAction::None;
+	TextSelectType _mouseSelectType = TextSelectType::Letters;
+	QPoint _dragStartPosition;
+	QPoint _mousePosition;
+	HistoryItem *_mouseActionItem = nullptr;
+	HistoryCursorState _mouseCursorState = HistoryDefaultCursorState;
+	uint16 _mouseTextSymbol = 0;
+	bool _pressWasInactive = false;
 
 	QPoint _trippleClickPoint;
 	QTimer _trippleClickTimer;
@@ -232,7 +234,7 @@ private:
 	bool _dragSelecting = false;
 	bool _wasSelectedText = false; // was some text selected in current drag action
 
-								   // scroll by touch support (at least Windows Surface tablets)
+	// scroll by touch support (at least Windows Surface tablets)
 	bool _touchScroll = false;
 	bool _touchSelect = false;
 	bool _touchInProgress = false;
diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp
index a8408f37c..f1292a114 100644
--- a/Telegram/SourceFiles/history/history_item.cpp
+++ b/Telegram/SourceFiles/history/history_item.cpp
@@ -244,7 +244,7 @@ void ReplyKeyboard::paint(Painter &p, int outerWidth, const QRect &clip, TimeMs
 	}
 }
 
-ClickHandlerPtr ReplyKeyboard::getState(int x, int y) const {
+ClickHandlerPtr ReplyKeyboard::getState(QPoint point) const {
 	t_assert(_width > 0);
 
 	for_const (auto &row, _rows) {
@@ -254,8 +254,8 @@ ClickHandlerPtr ReplyKeyboard::getState(int x, int y) const {
 			// just ignore the buttons that didn't layout well
 			if (rect.x() + rect.width() > _width) break;
 
-			if (rect.contains(x, y)) {
-				_savedCoords = QPoint(x, y);
+			if (rect.contains(point)) {
+				_savedCoords = point;
 				return button.link;
 			}
 		}
diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h
index fa548c9a5..21c9947e7 100644
--- a/Telegram/SourceFiles/history/history_item.h
+++ b/Telegram/SourceFiles/history/history_item.h
@@ -298,7 +298,7 @@ public:
 		int buttonHeight() const;
 		virtual int buttonRadius() const = 0;
 
-		virtual void repaint(const HistoryItem *item) const = 0;
+		virtual void repaint(gsl::not_null<const HistoryItem*> item) const = 0;
 		virtual ~Style() {
 		}
 
@@ -330,7 +330,7 @@ public:
 	int naturalHeight() const;
 
 	void paint(Painter &p, int outerWidth, const QRect &clip, TimeMs ms) const;
-	ClickHandlerPtr getState(int x, int y) const;
+	ClickHandlerPtr getState(QPoint point) const;
 
 	void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active);
 	void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed);
@@ -629,12 +629,12 @@ public:
 	virtual bool needCheck() const {
 		return out() || (id < 0 && history()->peer->isSelf());
 	}
-	virtual bool hasPoint(int x, int y) const {
+	virtual bool hasPoint(QPoint point) const {
 		return false;
 	}
 
-	virtual HistoryTextState getState(int x, int y, HistoryStateRequest request) const = 0;
-	virtual void updatePressed(int x, int y) {
+	virtual HistoryTextState getState(QPoint point, HistoryStateRequest request) const = 0;
+	virtual void updatePressed(QPoint point) {
 	}
 
 	virtual TextSelection adjustSelection(TextSelection selection, TextSelectType type) const {
@@ -745,14 +745,14 @@ public:
 	virtual int timeWidth() const {
 		return 0;
 	}
-	virtual bool pointInTime(int32 right, int32 bottom, int x, int y, InfoDisplayType type) const {
+	virtual bool pointInTime(int right, int bottom, QPoint point, InfoDisplayType type) const {
 		return false;
 	}
 
-	int32 skipBlockWidth() const {
+	int skipBlockWidth() const {
 		return st::msgDateSpace + infoWidth() - st::msgDateDelta.x();
 	}
-	int32 skipBlockHeight() const {
+	int skipBlockHeight() const {
 		return st::msgDateFont->height - st::msgDateDelta.y();
 	}
 	QString skipBlock() const {
diff --git a/Telegram/SourceFiles/history/history_media.h b/Telegram/SourceFiles/history/history_media.h
index 0875371d8..1a7bb8e99 100644
--- a/Telegram/SourceFiles/history/history_media.h
+++ b/Telegram/SourceFiles/history/history_media.h
@@ -46,8 +46,8 @@ public:
 	}
 	virtual TextWithEntities selectedText(TextSelection selection) const = 0;
 
-	bool hasPoint(int x, int y) const {
-		return (x >= 0 && y >= 0 && x < _width && y < _height);
+	bool hasPoint(QPoint point) const {
+		return QRect(0, 0, _width, _height).contains(point);
 	}
 
 	virtual bool isDisplayed() const {
@@ -65,8 +65,8 @@ public:
 		return _height;
 	}
 	virtual void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const = 0;
-	virtual HistoryTextState getState(int x, int y, HistoryStateRequest request) const = 0;
-	virtual void updatePressed(int x, int y) {
+	virtual HistoryTextState getState(QPoint point, HistoryStateRequest request) const = 0;
+	virtual void updatePressed(QPoint point) {
 	}
 
 	virtual int32 addToOverview(AddToOverviewMethod method) {
diff --git a/Telegram/SourceFiles/history/history_media_types.cpp b/Telegram/SourceFiles/history/history_media_types.cpp
index 0db124ef3..eeb13d241 100644
--- a/Telegram/SourceFiles/history/history_media_types.cpp
+++ b/Telegram/SourceFiles/history/history_media_types.cpp
@@ -462,7 +462,7 @@ void HistoryPhoto::draw(Painter &p, const QRect &r, TextSelection selection, Tim
 	}
 }
 
-HistoryTextState HistoryPhoto::getState(int x, int y, HistoryStateRequest request) const {
+HistoryTextState HistoryPhoto::getState(QPoint point, HistoryStateRequest request) const {
 	HistoryTextState result;
 
 	if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result;
@@ -478,8 +478,8 @@ HistoryTextState HistoryPhoto::getState(int x, int y, HistoryStateRequest reques
 			if (isBubbleBottom()) {
 				height -= st::msgPadding.bottom();
 			}
-			if (x >= st::msgPadding.left() && y >= height && x < st::msgPadding.left() + captionw && y < _height) {
-				result = _caption.getState(x - st::msgPadding.left(), y - height, captionw, request.forText());
+			if (QRect(st::msgPadding.left(), height, captionw, _height - height).contains(point)) {
+				result = _caption.getState(point - QPoint(st::msgPadding.left(), height), captionw, request.forText());
 				return result;
 			}
 			height -= st::mediaCaptionSkip;
@@ -487,13 +487,13 @@ HistoryTextState HistoryPhoto::getState(int x, int y, HistoryStateRequest reques
 		width -= st::mediaPadding.left() + st::mediaPadding.right();
 		height -= skipy + st::mediaPadding.bottom();
 	}
-	if (x >= skipx && y >= skipy && x < skipx + width && y < skipy + height) {
+	if (QRect(skipx, skipy, width, height).contains(point)) {
 		if (_data->uploading()) {
 			result.link = _cancell;
 		} else if (_data->loaded()) {
 			result.link = _openl;
 		} else if (_data->loading()) {
-			DelayedStorageImage *delayed = _data->full->toDelayedStorageImage();
+			auto delayed = _data->full->toDelayedStorageImage();
 			if (!delayed || !delayed->location().isNull()) {
 				result.link = _cancell;
 			}
@@ -501,9 +501,9 @@ HistoryTextState HistoryPhoto::getState(int x, int y, HistoryStateRequest reques
 			result.link = _savel;
 		}
 		if (_caption.isEmpty() && _parent->getMedia() == this) {
-			int32 fullRight = skipx + width, fullBottom = skipy + height;
-			bool inDate = _parent->pointInTime(fullRight, fullBottom, x, y, InfoDisplayOverImage);
-			if (inDate) {
+			auto fullRight = skipx + width;
+			auto fullBottom = skipy + height;
+			if (_parent->pointInTime(fullRight, fullBottom, point, InfoDisplayOverImage)) {
 				result.cursor = HistoryInDateCursorState;
 			}
 		}
@@ -826,7 +826,7 @@ void HistoryVideo::draw(Painter &p, const QRect &r, TextSelection selection, Tim
 	}
 }
 
-HistoryTextState HistoryVideo::getState(int x, int y, HistoryStateRequest request) const {
+HistoryTextState HistoryVideo::getState(QPoint point, HistoryStateRequest request) const {
 	HistoryTextState result;
 
 	if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result;
@@ -845,24 +845,24 @@ HistoryTextState HistoryVideo::getState(int x, int y, HistoryStateRequest reques
 			if (isBubbleBottom()) {
 				height -= st::msgPadding.bottom();
 			}
-			if (x >= st::msgPadding.left() && y >= height && x < st::msgPadding.left() + captionw && y < _height) {
-				result = _caption.getState(x - st::msgPadding.left(), y - height, captionw, request.forText());
+			if (QRect(st::msgPadding.left(), height, captionw, _height - height).contains(point)) {
+				result = _caption.getState(point - QPoint(st::msgPadding.left(), height), captionw, request.forText());
 			}
 			height -= st::mediaCaptionSkip;
 		}
 		width -= st::mediaPadding.left() + st::mediaPadding.right();
 		height -= skipy + st::mediaPadding.bottom();
 	}
-	if (x >= skipx && y >= skipy && x < skipx + width && y < skipy + height) {
+	if (QRect(skipx, skipy, width, height).contains(point)) {
 		if (_data->uploading()) {
 			result.link = _cancell;
 		} else {
 			result.link = loaded ? _openl : (_data->loading() ? _cancell : _savel);
 		}
 		if (_caption.isEmpty() && _parent->getMedia() == this) {
-			int32 fullRight = skipx + width, fullBottom = skipy + height;
-			bool inDate = _parent->pointInTime(fullRight, fullBottom, x, y, InfoDisplayOverImage);
-			if (inDate) {
+			auto fullRight = skipx + width;
+			auto fullBottom = skipy + height;
+			if (_parent->pointInTime(fullRight, fullBottom, point, InfoDisplayOverImage)) {
 				result.cursor = HistoryInDateCursorState;
 			}
 		}
@@ -1380,7 +1380,7 @@ void HistoryDocument::draw(Painter &p, const QRect &r, TextSelection selection,
 	}
 }
 
-HistoryTextState HistoryDocument::getState(int x, int y, HistoryStateRequest request) const {
+HistoryTextState HistoryDocument::getState(QPoint point, HistoryStateRequest request) const {
 	HistoryTextState result;
 
 	if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result;
@@ -1401,13 +1401,13 @@ HistoryTextState HistoryDocument::getState(int x, int y, HistoryStateRequest req
 
 		QRect rthumb(rtlrect(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top() - topMinus, st::msgFileThumbSize, st::msgFileThumbSize, _width));
 
-		if ((_data->loading() || _data->uploading() || !loaded) && rthumb.contains(x, y)) {
+		if ((_data->loading() || _data->uploading() || !loaded) && rthumb.contains(point)) {
 			result.link = (_data->loading() || _data->uploading()) ? _cancell : _savel;
 			return result;
 		}
 
 		if (_data->status != FileUploadFailed) {
-			if (rtlrect(nameleft, linktop, thumbed->_linkw, st::semiboldFont->height, _width).contains(x, y)) {
+			if (rtlrect(nameleft, linktop, thumbed->_linkw, st::semiboldFont->height, _width).contains(point)) {
 				result.link = (_data->loading() || _data->uploading()) ? thumbed->_linkcancell : thumbed->_linksavel;
 				return result;
 			}
@@ -1419,7 +1419,7 @@ HistoryTextState HistoryDocument::getState(int x, int y, HistoryStateRequest req
 		bottom = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom() - topMinus;
 
 		QRect inner(rtlrect(st::msgFilePadding.left(), st::msgFilePadding.top() - topMinus, st::msgFileSize, st::msgFileSize, _width));
-		if ((_data->loading() || _data->uploading() || !loaded) && inner.contains(x, y)) {
+		if ((_data->loading() || _data->uploading() || !loaded) && inner.contains(point)) {
 			result.link = (_data->loading() || _data->uploading()) ? _cancell : _savel;
 			return result;
 		}
@@ -1428,11 +1428,11 @@ HistoryTextState HistoryDocument::getState(int x, int y, HistoryStateRequest req
 	if (auto voice = Get<HistoryDocumentVoice>()) {
 		auto namewidth = _width - nameleft - nameright;
 		auto waveformbottom = st::msgFilePadding.top() - topMinus + st::msgWaveformMax + st::msgWaveformMin;
-		if (x >= nameleft && x < nameleft + namewidth && y >= nametop && y < waveformbottom) {
+		if (QRect(nameleft, nametop, namewidth, waveformbottom - nametop).contains(point)) {
 			auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice);
 			if (state.id == AudioMsgId(_data, _parent->fullId()) && !Media::Player::IsStoppedOrStopping(state.state)) {
 				if (!voice->seeking()) {
-					voice->setSeekingStart((x - nameleft) / float64(namewidth));
+					voice->setSeekingStart((point.x() - nameleft) / float64(namewidth));
 				}
 				result.link = voice->_seekl;
 				return result;
@@ -1440,10 +1440,10 @@ HistoryTextState HistoryDocument::getState(int x, int y, HistoryStateRequest req
 		}
 	}
 
-	int32 height = _height;
+	auto height = _height;
 	if (auto captioned = Get<HistoryDocumentCaptioned>()) {
-		if (y >= bottom) {
-			result = captioned->_caption.getState(x - st::msgPadding.left(), y - bottom, _width - st::msgPadding.left() - st::msgPadding.right(), request.forText());
+		if (point.y() >= bottom) {
+			result = captioned->_caption.getState(point - QPoint(st::msgPadding.left(), bottom), _width - st::msgPadding.left() - st::msgPadding.right(), request.forText());
 			return result;
 		}
 		auto captionw = _width - st::msgPadding.left() - st::msgPadding.right();
@@ -1452,14 +1452,14 @@ HistoryTextState HistoryDocument::getState(int x, int y, HistoryStateRequest req
 			height -= st::msgPadding.bottom();
 		}
 	}
-	if (x >= 0 && y >= 0 && x < _width && y < height && !_data->loading() && !_data->uploading() && _data->isValid()) {
+	if (QRect(0, 0, _width, height).contains(point) && !_data->loading() && !_data->uploading() && _data->isValid()) {
 		result.link = _openl;
 		return result;
 	}
 	return result;
 }
 
-void HistoryDocument::updatePressed(int x, int y) {
+void HistoryDocument::updatePressed(QPoint point) {
 	if (auto voice = Get<HistoryDocumentVoice>()) {
 		if (voice->seeking()) {
 			auto nameleft = 0, nameright = 0;
@@ -1470,7 +1470,7 @@ void HistoryDocument::updatePressed(int x, int y) {
 				nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right();
 				nameright = st::msgFilePadding.left();
 			}
-			voice->setSeekingCurrent(snap((x - nameleft) / float64(_width - nameleft - nameright), 0., 1.));
+			voice->setSeekingCurrent(snap((point.x() - nameleft) / float64(_width - nameleft - nameright), 0., 1.));
 			Ui::repaintHistoryItem(_parent);
 		}
 	}
@@ -2157,7 +2157,7 @@ void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, TimeM
 	}
 }
 
-HistoryTextState HistoryGif::getState(int x, int y, HistoryStateRequest request) const {
+HistoryTextState HistoryGif::getState(QPoint point, HistoryStateRequest request) const {
 	HistoryTextState result;
 
 	if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result;
@@ -2173,8 +2173,8 @@ HistoryTextState HistoryGif::getState(int x, int y, HistoryStateRequest request)
 			if (isBubbleBottom()) {
 				height -= st::msgPadding.bottom();
 			}
-			if (x >= st::msgPadding.left() && y >= height && x < st::msgPadding.left() + captionw && y < _height) {
-				result = _caption.getState(x - st::msgPadding.left(), y - height, captionw, request.forText());
+			if (QRect(st::msgPadding.left(), height, captionw, _height - height).contains(point)) {
+				result = _caption.getState(point - QPoint(st::msgPadding.left(), height), captionw, request.forText());
 				return result;
 			}
 			height -= st::mediaCaptionSkip;
@@ -2217,13 +2217,13 @@ HistoryTextState HistoryGif::getState(int x, int y, HistoryStateRequest request)
 		if (rtl()) rectx = _width - rectx - rectw;
 
 		if (forwarded) {
-			if (x >= rectx && y >= recty && x < rectx + rectw && y < recty + st::msgReplyPadding.top() + forwardedHeight) {
+			if (QRect(rectx, recty, rectw, st::msgReplyPadding.top() + forwardedHeight).contains(point)) {
 				auto breakEverywhere = (forwardedHeightReal > forwardedHeight);
 				auto textRequest = request.forText();
 				if (breakEverywhere) {
 					textRequest.flags |= Text::StateRequest::Flag::BreakEverywhere;
 				}
-				result = forwarded->_text.getState(x - rectx - st::msgReplyPadding.left(), y - recty - st::msgReplyPadding.top(), innerw, textRequest);
+				result = forwarded->_text.getState(point - QPoint(rectx + st::msgReplyPadding.left(), recty + st::msgReplyPadding.top()), innerw, textRequest);
 				result.symbol = 0;
 				result.afterSymbol = false;
 				if (breakEverywhere) {
@@ -2236,23 +2236,23 @@ HistoryTextState HistoryGif::getState(int x, int y, HistoryStateRequest request)
 			recty += forwardedHeight;
 			recth -= forwardedHeight;
 		} else if (via) {
-			int viah = st::msgReplyPadding.top() + st::msgServiceNameFont->height + (reply ? 0 : st::msgReplyPadding.bottom());
-			if (x >= rectx && y >= recty && x < rectx + rectw && y < recty + viah) {
+			auto viah = st::msgReplyPadding.top() + st::msgServiceNameFont->height + (reply ? 0 : st::msgReplyPadding.bottom());
+			if (QRect(rectx, recty, rectw, viah).contains(point)) {
 				result.link = via->_lnk;
 				return result;
 			}
-			int skip = st::msgServiceNameFont->height + (reply ? 2 * st::msgReplyPadding.top() : 0);
+			auto skip = st::msgServiceNameFont->height + (reply ? 2 * st::msgReplyPadding.top() : 0);
 			recty += skip;
 			recth -= skip;
 		}
 		if (reply) {
-			if (x >= rectx && y >= recty && x < rectx + rectw && y < recty + recth) {
+			if (QRect(rectx, recty, rectw, recth).contains(point)) {
 				result.link = reply->replyToLink();
 				return result;
 			}
 		}
 	}
-	if (x >= usex + skipx && y >= skipy && x < usex + skipx + usew && y < skipy + height) {
+	if (QRect(usex + skipx, skipy, usew, height).contains(point)) {
 		if (_data->uploading()) {
 			result.link = _cancell;
 		} else if (!_gif || !cAutoPlayGif() || _data->isRoundVideo()) {
@@ -2261,9 +2261,9 @@ HistoryTextState HistoryGif::getState(int x, int y, HistoryStateRequest request)
 			result.link = _openInMediaviewLink;
 		}
 		if (!isChildMedia) {
-			int32 fullRight = usex + skipx + usew, fullBottom = skipy + height;
-			bool inDate = _parent->pointInTime(fullRight, fullBottom, x, y, InfoDisplayOverImage);
-			if (inDate) {
+			auto fullRight = usex + skipx + usew;
+			auto fullBottom = skipy + height;
+			if (_parent->pointInTime(fullRight, fullBottom, point, InfoDisplayOverImage)) {
 				result.cursor = HistoryInDateCursorState;
 			}
 		}
@@ -2630,7 +2630,7 @@ void HistorySticker::draw(Painter &p, const QRect &r, TextSelection selection, T
 	}
 }
 
-HistoryTextState HistorySticker::getState(int x, int y, HistoryStateRequest request) const {
+HistoryTextState HistorySticker::getState(QPoint point, HistoryStateRequest request) const {
 	HistoryTextState result;
 	if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result;
 
@@ -2666,7 +2666,7 @@ HistoryTextState HistorySticker::getState(int x, int y, HistoryStateRequest requ
 
 		if (via) {
 			int viah = st::msgReplyPadding.top() + st::msgServiceNameFont->height + (reply ? 0 : st::msgReplyPadding.bottom());
-			if (x >= rectx && y >= recty && x < rectx + rectw && y < recty + viah) {
+			if (QRect(rectx, recty, rectw, viah).contains(point)) {
 				result.link = via->_lnk;
 				return result;
 			}
@@ -2675,21 +2675,21 @@ HistoryTextState HistorySticker::getState(int x, int y, HistoryStateRequest requ
 			recth -= skip;
 		}
 		if (reply) {
-			if (x >= rectx && y >= recty && x < rectx + rectw && y < recty + recth) {
+			if (QRect(rectx, recty, rectw, recth).contains(point)) {
 				result.link = reply->replyToLink();
 				return result;
 			}
 		}
 	}
 	if (_parent->getMedia() == this) {
-		bool inDate = _parent->pointInTime(usex + usew, _height, x, y, InfoDisplayOverImage);
-		if (inDate) {
+		if (_parent->pointInTime(usex + usew, _height, point, InfoDisplayOverImage)) {
 			result.cursor = HistoryInDateCursorState;
 		}
 	}
 
-	int pixLeft = usex + (usew - _pixw) / 2, pixTop = (_minh - _pixh) / 2;
-	if (x >= pixLeft && x < pixLeft + _pixw && y >= pixTop && y < pixTop + _pixh) {
+	auto pixLeft = usex + (usew - _pixw) / 2;
+	auto pixTop = (_minh - _pixh) / 2;
+	if (QRect(pixLeft, pixTop, _pixw, _pixh).contains(point)) {
 		result.link = _packLink;
 		return result;
 	}
@@ -2885,7 +2885,7 @@ void HistoryContact::draw(Painter &p, const QRect &r, TextSelection selection, T
 	p.drawTextLeft(nameleft, statustop, width, _phone);
 }
 
-HistoryTextState HistoryContact::getState(int x, int y, HistoryStateRequest request) const {
+HistoryTextState HistoryContact::getState(QPoint point, HistoryStateRequest request) const {
 	HistoryTextState result;
 	bool out = _parent->out(), isPost = _parent->isPost(), outbg = out && !isPost;
 
@@ -2894,12 +2894,12 @@ HistoryTextState HistoryContact::getState(int x, int y, HistoryStateRequest requ
 	if (_userId) {
 		nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right();
 		linktop = st::msgFileThumbLinkTop - topMinus;
-		if (rtlrect(nameleft, linktop, _linkw, st::semiboldFont->height, _width).contains(x, y)) {
+		if (rtlrect(nameleft, linktop, _linkw, st::semiboldFont->height, _width).contains(point)) {
 			result.link = _linkl;
 			return result;
 		}
 	}
-	if (x >= 0 && y >= 0 && x < _width && y < _height && _contact) {
+	if (QRect(0, 0, _width, _height).contains(point) && _contact) {
 		result.link = _contact->openLink();
 		return result;
 	}
@@ -3034,9 +3034,9 @@ void HistoryCall::draw(Painter &p, const QRect &r, TextSelection selection, Time
 	icon.paint(p, width - st::historyCallIconPosition.x() - icon.width(), st::historyCallIconPosition.y() - topMinus, width);
 }
 
-HistoryTextState HistoryCall::getState(int x, int y, HistoryStateRequest request) const {
+HistoryTextState HistoryCall::getState(QPoint point, HistoryStateRequest request) const {
 	HistoryTextState result;
-	if (x >= 0 && y >= 0 && x < _width && y < _height) {
+	if (QRect(0, 0, _width, _height).contains(point)) {
 		result.link = _link;
 		return result;
 	}
@@ -3459,7 +3459,7 @@ void HistoryWebPage::draw(Painter &p, const QRect &r, TextSelection selection, T
 	}
 }
 
-HistoryTextState HistoryWebPage::getState(int x, int y, HistoryStateRequest request) const {
+HistoryTextState HistoryWebPage::getState(QPoint point, HistoryStateRequest request) const {
 	HistoryTextState result;
 
 	if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result;
@@ -3478,7 +3478,7 @@ HistoryTextState HistoryWebPage::getState(int x, int y, HistoryStateRequest requ
 	auto inThumb = false;
 	if (_asArticle) {
 		int32 pw = qMax(_pixw, int16(lineHeight));
-		if (rtlrect(padding.left() + width - pw, 0, pw, _pixh, _width).contains(x, y)) {
+		if (rtlrect(padding.left() + width - pw, 0, pw, _pixh, _width).contains(point)) {
 			inThumb = true;
 		}
 		width -= pw + st::webPagePhotoDelta;
@@ -3488,21 +3488,21 @@ HistoryTextState HistoryWebPage::getState(int x, int y, HistoryStateRequest requ
 		tshift += lineHeight;
 	}
 	if (_titleLines) {
-		if (y >= tshift && y < tshift + _titleLines * lineHeight) {
+		if (point.y() >= tshift && point.y() < tshift + _titleLines * lineHeight) {
 			Text::StateRequestElided titleRequest = request.forText();
 			titleRequest.lines = _titleLines;
-			result = _title.getStateElidedLeft(x - padding.left(), y - tshift, width, _width, titleRequest);
-		} else if (y >= tshift + _titleLines * lineHeight) {
+			result = _title.getStateElidedLeft(point - QPoint(padding.left(), tshift), width, _width, titleRequest);
+		} else if (point.y() >= tshift + _titleLines * lineHeight) {
 			symbolAdd += _title.length();
 		}
 		tshift += _titleLines * lineHeight;
 	}
 	if (_descriptionLines) {
-		if (y >= tshift && y < tshift + _descriptionLines * lineHeight) {
+		if (point.y() >= tshift && point.y() < tshift + _descriptionLines * lineHeight) {
 			Text::StateRequestElided descriptionRequest = request.forText();
 			descriptionRequest.lines = _descriptionLines;
-			result = _description.getStateElidedLeft(x - padding.left(), y - tshift, width, _width, descriptionRequest);
-		} else if (y >= tshift + _descriptionLines * lineHeight) {
+			result = _description.getStateElidedLeft(point - QPoint(padding.left(), tshift), width, _width, descriptionRequest);
+		} else if (point.y() >= tshift + _descriptionLines * lineHeight) {
 			symbolAdd += _description.length();
 		}
 		tshift += _descriptionLines * lineHeight;
@@ -3513,11 +3513,11 @@ HistoryTextState HistoryWebPage::getState(int x, int y, HistoryStateRequest requ
 		auto attachAtTop = !_siteNameWidth && !_titleLines && !_descriptionLines;
 		if (!attachAtTop) tshift += st::mediaInBubbleSkip;
 
-		if (x >= padding.left() && x < padding.left() + width && y >= tshift && y < _height - bshift) {
+		if (QRect(padding.left(), tshift, width, _height - tshift - bshift).contains(point)) {
 			auto attachLeft = padding.left() - bubble.left();
 			auto attachTop = tshift - bubble.top();
 			if (rtl()) attachLeft = _width - attachLeft - _attach->currentWidth();
-			result = _attach->getState(x - attachLeft, y - attachTop, request);
+			result = _attach->getState(point - QPoint(attachLeft, attachTop), request);
 
 			if (result.link && !_data->document && _data->photo && _attach->isReadyForOpen()) {
 				if (_data->type == WebPageProfile || _data->type == WebPageVideo) {
@@ -3831,7 +3831,7 @@ void HistoryGame::draw(Painter &p, const QRect &r, TextSelection selection, Time
 	}
 }
 
-HistoryTextState HistoryGame::getState(int x, int y, HistoryStateRequest request) const {
+HistoryTextState HistoryGame::getState(QPoint point, HistoryStateRequest request) const {
 	HistoryTextState result;
 
 	if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result;
@@ -3850,21 +3850,21 @@ HistoryTextState HistoryGame::getState(int x, int y, HistoryStateRequest request
 	auto symbolAdd = 0;
 	auto lineHeight = unitedLineHeight();
 	if (_titleLines) {
-		if (y >= tshift && y < tshift + _titleLines * lineHeight) {
+		if (point.y() >= tshift && point.y() < tshift + _titleLines * lineHeight) {
 			Text::StateRequestElided titleRequest = request.forText();
 			titleRequest.lines = _titleLines;
-			result = _title.getStateElidedLeft(x - padding.left(), y - tshift, width, _width, titleRequest);
-		} else if (y >= tshift + _titleLines * lineHeight) {
+			result = _title.getStateElidedLeft(point - QPoint(padding.left(), tshift), width, _width, titleRequest);
+		} else if (point.y() >= tshift + _titleLines * lineHeight) {
 			symbolAdd += _title.length();
 		}
 		tshift += _titleLines * lineHeight;
 	}
 	if (_descriptionLines) {
-		if (y >= tshift && y < tshift + _descriptionLines * lineHeight) {
+		if (point.y() >= tshift && point.y() < tshift + _descriptionLines * lineHeight) {
 			Text::StateRequestElided descriptionRequest = request.forText();
 			descriptionRequest.lines = _descriptionLines;
-			result = _description.getStateElidedLeft(x - padding.left(), y - tshift, width, _width, descriptionRequest);
-		} else if (y >= tshift + _descriptionLines * lineHeight) {
+			result = _description.getStateElidedLeft(point - QPoint(padding.left(), tshift), width, _width, descriptionRequest);
+		} else if (point.y() >= tshift + _descriptionLines * lineHeight) {
 			symbolAdd += _description.length();
 		}
 		tshift += _descriptionLines * lineHeight;
@@ -3879,11 +3879,11 @@ HistoryTextState HistoryGame::getState(int x, int y, HistoryStateRequest request
 		auto attachTop = tshift - bubble.top();
 		if (rtl()) attachLeft = _width - attachLeft - _attach->currentWidth();
 
-		if (x >= attachLeft && x < attachLeft + _attach->currentWidth() && y >= tshift && y < _height - bshift) {
+		if (QRect(attachLeft, tshift, _attach->currentWidth(), _height - tshift - bshift).contains(point)) {
 			if (_attach->isReadyForOpen()) {
 				result.link = _openl;
 			} else {
-				result = _attach->getState(x - attachLeft, y - attachTop, request);
+				result = _attach->getState(point - QPoint(attachLeft, attachTop), request);
 			}
 		}
 	}
@@ -4254,7 +4254,7 @@ void HistoryInvoice::draw(Painter &p, const QRect &r, TextSelection selection, T
 	}
 }
 
-HistoryTextState HistoryInvoice::getState(int x, int y, HistoryStateRequest request) const {
+HistoryTextState HistoryInvoice::getState(QPoint point, HistoryStateRequest request) const {
 	HistoryTextState result;
 
 	if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result;
@@ -4272,19 +4272,19 @@ HistoryTextState HistoryInvoice::getState(int x, int y, HistoryStateRequest requ
 	auto lineHeight = unitedLineHeight();
 	auto symbolAdd = 0;
 	if (_titleHeight) {
-		if (y >= tshift && y < tshift + _titleHeight) {
+		if (point.y() >= tshift && point.y() < tshift + _titleHeight) {
 			Text::StateRequestElided titleRequest = request.forText();
 			titleRequest.lines = _titleHeight / lineHeight;
-			result = _title.getStateElidedLeft(x - padding.left(), y - tshift, width, _width, titleRequest);
-		} else if (y >= tshift + _titleHeight) {
+			result = _title.getStateElidedLeft(point - QPoint(padding.left(), tshift), width, _width, titleRequest);
+		} else if (point.y() >= tshift + _titleHeight) {
 			symbolAdd += _title.length();
 		}
 		tshift += _titleHeight;
 	}
 	if (_descriptionHeight) {
-		if (y >= tshift && y < tshift + _descriptionHeight) {
-			result = _description.getStateLeft(x - padding.left(), y - tshift, width, _width, request.forText());
-		} else if (y >= tshift + _descriptionHeight) {
+		if (point.y() >= tshift && point.y() < tshift + _descriptionHeight) {
+			result = _description.getStateLeft(point - QPoint(padding.left(), tshift), width, _width, request.forText());
+		} else if (point.y() >= tshift + _descriptionHeight) {
 			symbolAdd += _description.length();
 		}
 		tshift += _descriptionHeight;
@@ -4297,8 +4297,8 @@ HistoryTextState HistoryInvoice::getState(int x, int y, HistoryStateRequest requ
 		auto attachTop = tshift - bubble.top();
 		if (rtl()) attachLeft = _width - attachLeft - _attach->currentWidth();
 
-		if (x >= attachLeft && x < attachLeft + _attach->currentWidth() && y >= tshift && y < _height - bshift) {
-			result = _attach->getState(x - attachLeft, y - attachTop, request);
+		if (QRect(attachLeft, tshift, _attach->currentWidth(), _height - tshift - bshift).contains(point)) {
+			result = _attach->getState(point - QPoint(attachLeft, attachTop), request);
 		}
 	}
 
@@ -4534,7 +4534,7 @@ void HistoryLocation::draw(Painter &p, const QRect &r, TextSelection selection,
 	}
 }
 
-HistoryTextState HistoryLocation::getState(int x, int y, HistoryStateRequest request) const {
+HistoryTextState HistoryLocation::getState(QPoint point, HistoryStateRequest request) const {
 	HistoryTextState result;
 	auto symbolAdd = 0;
 
@@ -4553,23 +4553,23 @@ HistoryTextState HistoryLocation::getState(int x, int y, HistoryStateRequest req
 		}
 
 		width -= st::mediaPadding.left() + st::mediaPadding.right();
-		int32 textw = _width - st::msgPadding.left() - st::msgPadding.right();
+		auto textw = _width - st::msgPadding.left() - st::msgPadding.right();
 
 		if (!_title.isEmpty()) {
 			auto titleh = qMin(_title.countHeight(textw), 2 * st::webPageTitleFont->height);
-			if (y >= skipy && y < skipy + titleh) {
-				result = _title.getStateLeft(x - skipx - st::msgPadding.left(), y - skipy, textw, _width, request.forText());
+			if (point.y() >= skipy && point.y() < skipy + titleh) {
+				result = _title.getStateLeft(point - QPoint(skipx + st::msgPadding.left(), skipy), textw, _width, request.forText());
 				return result;
-			} else if (y >= skipy + titleh) {
+			} else if (point.y() >= skipy + titleh) {
 				symbolAdd += _title.length();
 			}
 			skipy += titleh;
 		}
 		if (!_description.isEmpty()) {
 			auto descriptionh = qMin(_description.countHeight(textw), 3 * st::webPageDescriptionFont->height);
-			if (y >= skipy && y < skipy + descriptionh) {
-				result = _description.getStateLeft(x - skipx - st::msgPadding.left(), y - skipy, textw, _width, request.forText());
-			} else if (y >= skipy + descriptionh) {
+			if (point.y() >= skipy && point.y() < skipy + descriptionh) {
+				result = _description.getStateLeft(point - QPoint(skipx + st::msgPadding.left(), skipy), textw, _width, request.forText());
+			} else if (point.y() >= skipy + descriptionh) {
 				symbolAdd += _description.length();
 			}
 			skipy += descriptionh;
@@ -4579,12 +4579,12 @@ HistoryTextState HistoryLocation::getState(int x, int y, HistoryStateRequest req
 		}
 		height -= skipy + st::mediaPadding.bottom();
 	}
-	if (x >= skipx && y >= skipy && x < skipx + width && y < skipy + height && _data) {
+	if (QRect(skipx, skipy, width, height).contains(point) && _data) {
 		result.link = _link;
 
-		int32 fullRight = skipx + width, fullBottom = _height - (skipx ? st::mediaPadding.bottom() : 0);
-		bool inDate = _parent->pointInTime(fullRight, fullBottom, x, y, InfoDisplayOverImage);
-		if (inDate) {
+		auto fullRight = skipx + width;
+		auto fullBottom = _height - (skipx ? st::mediaPadding.bottom() : 0);
+		if (_parent->pointInTime(fullRight, fullBottom, point, InfoDisplayOverImage)) {
 			result.cursor = HistoryInDateCursorState;
 		}
 	}
diff --git a/Telegram/SourceFiles/history/history_media_types.h b/Telegram/SourceFiles/history/history_media_types.h
index dea68a9b4..b2626e7f8 100644
--- a/Telegram/SourceFiles/history/history_media_types.h
+++ b/Telegram/SourceFiles/history/history_media_types.h
@@ -133,7 +133,7 @@ public:
 	int resizeGetHeight(int width) override;
 
 	void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override;
-	HistoryTextState getState(int x, int y, HistoryStateRequest request) const override;
+	HistoryTextState getState(QPoint point, HistoryStateRequest request) const override;
 
 	TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override {
 		return _caption.adjustSelection(selection, type);
@@ -226,7 +226,7 @@ public:
 	int resizeGetHeight(int width) override;
 
 	void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override;
-	HistoryTextState getState(int x, int y, HistoryStateRequest request) const override;
+	HistoryTextState getState(QPoint point, HistoryStateRequest request) const override;
 
 	TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override {
 		return _caption.adjustSelection(selection, type);
@@ -384,8 +384,8 @@ public:
 	int resizeGetHeight(int width) override;
 
 	void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override;
-	HistoryTextState getState(int x, int y, HistoryStateRequest request) const override;
-	void updatePressed(int x, int y) override;
+	HistoryTextState getState(QPoint point, HistoryStateRequest request) const override;
+	void updatePressed(QPoint point) override;
 
 	TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override {
 		if (auto captioned = Get<HistoryDocumentCaptioned>()) {
@@ -491,7 +491,7 @@ public:
 	int resizeGetHeight(int width) override;
 
 	void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override;
-	HistoryTextState getState(int x, int y, HistoryStateRequest request) const override;
+	HistoryTextState getState(QPoint point, HistoryStateRequest request) const override;
 
 	TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override {
 		return _caption.adjustSelection(selection, type);
@@ -614,7 +614,7 @@ public:
 	int resizeGetHeight(int width) override;
 
 	void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override;
-	HistoryTextState getState(int x, int y, HistoryStateRequest request) const override;
+	HistoryTextState getState(QPoint point, HistoryStateRequest request) const override;
 
 	bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override {
 		return true;
@@ -682,7 +682,7 @@ public:
 	void initDimensions() override;
 
 	void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override;
-	HistoryTextState getState(int x, int y, HistoryStateRequest request) const override;
+	HistoryTextState getState(QPoint point, HistoryStateRequest request) const override;
 
 	bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override {
 		return true;
@@ -744,7 +744,7 @@ public:
 	void initDimensions() override;
 
 	void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override;
-	HistoryTextState getState(int x, int y, HistoryStateRequest request) const override;
+	HistoryTextState getState(QPoint point, HistoryStateRequest request) const override;
 
 	bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override {
 		return true;
@@ -801,7 +801,7 @@ public:
 	int resizeGetHeight(int width) override;
 
 	void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override;
-	HistoryTextState getState(int x, int y, HistoryStateRequest request) const override;
+	HistoryTextState getState(QPoint point, HistoryStateRequest request) const override;
 
 	TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override;
 	bool hasTextForCopy() const override {
@@ -900,7 +900,7 @@ public:
 	int resizeGetHeight(int width) override;
 
 	void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override;
-	HistoryTextState getState(int x, int y, HistoryStateRequest request) const override;
+	HistoryTextState getState(QPoint point, HistoryStateRequest request) const override;
 
 	TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override;
 	bool isAboveMessage() const override {
@@ -1008,7 +1008,7 @@ public:
 	static QString fillAmountAndCurrency(int amount, const QString &currency);
 
 	void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override;
-	HistoryTextState getState(int x, int y, HistoryStateRequest request) const override;
+	HistoryTextState getState(QPoint point, HistoryStateRequest request) const override;
 
 	TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override;
 	bool hasTextForCopy() const override {
@@ -1091,7 +1091,7 @@ public:
 	int resizeGetHeight(int32 width) override;
 
 	void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override;
-	HistoryTextState getState(int x, int y, HistoryStateRequest request) const override;
+	HistoryTextState getState(QPoint point, HistoryStateRequest request) const override;
 
 	TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override;
 	bool hasTextForCopy() const override {
diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp
index 628bb1f71..2c279b365 100644
--- a/Telegram/SourceFiles/history/history_message.cpp
+++ b/Telegram/SourceFiles/history/history_message.cpp
@@ -350,7 +350,7 @@ const style::TextStyle &HistoryMessage::KeyboardStyle::textStyle() const {
 	return st::serviceTextStyle;
 }
 
-void HistoryMessage::KeyboardStyle::repaint(const HistoryItem *item) const {
+void HistoryMessage::KeyboardStyle::repaint(gsl::not_null<const HistoryItem*> item) const {
 	Ui::repaintHistoryItem(item);
 }
 
@@ -1640,23 +1640,24 @@ int HistoryMessage::performResizeGetHeight() {
 	return _height;
 }
 
-bool HistoryMessage::hasPoint(int x, int y) const {
+bool HistoryMessage::hasPoint(QPoint point) const {
 	auto g = countGeometry();
 	if (g.width() < 1) {
 		return false;
 	}
 
 	if (drawBubble()) {
-		return g.contains(x, y);
+		return g.contains(point);
 	} else if (_media) {
-		return _media->hasPoint(x - g.left(), y - g.top());
+		return _media->hasPoint(point - g.topLeft());
 	} else {
 		return false;
 	}
 }
 
-bool HistoryMessage::pointInTime(int32 right, int32 bottom, int x, int y, InfoDisplayType type) const {
-	int32 infoRight = right, infoBottom = bottom;
+bool HistoryMessage::pointInTime(int right, int bottom, QPoint point, InfoDisplayType type) const {
+	auto infoRight = right;
+	auto infoBottom = bottom;
 	switch (type) {
 	case InfoDisplayDefault:
 		infoRight -= st::msgPadding.right() - st::msgDateDelta.x();
@@ -1667,12 +1668,12 @@ bool HistoryMessage::pointInTime(int32 right, int32 bottom, int x, int y, InfoDi
 		infoBottom -= st::msgDateImgDelta + st::msgDateImgPadding.y();
 	break;
 	}
-	int32 dateX = infoRight - HistoryMessage::infoWidth() + HistoryMessage::timeLeft();
-	int32 dateY = infoBottom - st::msgDateFont->height;
-	return QRect(dateX, dateY, HistoryMessage::timeWidth(), st::msgDateFont->height).contains(x, y);
+	auto dateX = infoRight - HistoryMessage::infoWidth() + HistoryMessage::timeLeft();
+	auto dateY = infoBottom - st::msgDateFont->height;
+	return QRect(dateX, dateY, HistoryMessage::timeWidth(), st::msgDateFont->height).contains(point);
 }
 
-HistoryTextState HistoryMessage::getState(int x, int y, HistoryStateRequest request) const {
+HistoryTextState HistoryMessage::getState(QPoint point, HistoryStateRequest request) const {
 	HistoryTextState result;
 
 	auto g = countGeometry();
@@ -1693,10 +1694,10 @@ HistoryTextState HistoryMessage::getState(int x, int y, HistoryStateRequest requ
 		if (mediaDisplayed && _media->isBubbleTop()) {
 			trect.setY(trect.y() - st::msgPadding.top());
 		} else {
-			if (getStateFromName(x, y, trect, &result)) return result;
-			if (getStateForwardedInfo(x, y, trect, &result, request)) return result;
-			if (getStateReplyInfo(x, y, trect, &result)) return result;
-			if (getStateViaBotIdInfo(x, y, trect, &result)) return result;
+			if (getStateFromName(point, trect, &result)) return result;
+			if (getStateForwardedInfo(point, trect, &result, request)) return result;
+			if (getStateReplyInfo(point, trect, &result)) return result;
+			if (getStateViaBotIdInfo(point, trect, &result)) return result;
 		}
 		if (mediaDisplayed && _media->isBubbleBottom()) {
 			trect.setHeight(trect.height() + st::msgPadding.bottom());
@@ -1709,34 +1710,34 @@ HistoryTextState HistoryMessage::getState(int x, int y, HistoryStateRequest requ
 			auto mediaLeft = trect.x() - st::msgPadding.left();
 			auto mediaTop = mediaAboveText ? trect.y() : (trect.y() + trect.height() - mediaHeight);
 
-			if (y >= mediaTop && y < mediaTop + mediaHeight) {
-				result = _media->getState(x - mediaLeft, y - mediaTop, request);
+			if (point.y() >= mediaTop && point.y() < mediaTop + mediaHeight) {
+				result = _media->getState(point - QPoint(mediaLeft, mediaTop), request);
 				result.symbol += _text.length();
 			} else {
 				if (mediaAboveText) {
 					trect.setY(trect.y() + mediaHeight);
 				}
-				getStateText(x, y, trect, &result, request);
+				getStateText(point, trect, &result, request);
 			}
 
 			needDateCheck = !_media->customInfoLayout();
 		} else {
-			getStateText(x, y, trect, &result, request);
+			getStateText(point, trect, &result, request);
 		}
 		if (needDateCheck) {
-			if (HistoryMessage::pointInTime(g.left() + g.width(), g.top() + g.height(), x, y, InfoDisplayDefault)) {
+			if (HistoryMessage::pointInTime(g.left() + g.width(), g.top() + g.height(), point, InfoDisplayDefault)) {
 				result.cursor = HistoryInDateCursorState;
 			}
 		}
 	} else if (_media) {
-		result = _media->getState(x - g.left(), y - g.top(), request);
+		result = _media->getState(point - g.topLeft(), request);
 		result.symbol += _text.length();
 	}
 
 	if (keyboard) {
 		auto keyboardTop = g.top() + g.height() + st::msgBotKbButton.margin;
-		if (QRect(g.left(), keyboardTop, g.width(), keyboardHeight).contains(x, y)) {
-			result.link = keyboard->getState(x - g.left(), y - keyboardTop);
+		if (QRect(g.left(), keyboardTop, g.width(), keyboardHeight).contains(point)) {
+			result.link = keyboard->getState(point - g.topLeft());
 			return result;
 		}
 	}
@@ -1745,7 +1746,7 @@ HistoryTextState HistoryMessage::getState(int x, int y, HistoryStateRequest requ
 }
 
 // Forward to _media.
-void HistoryMessage::updatePressed(int x, int y) {
+void HistoryMessage::updatePressed(QPoint point) {
 	if (!_media) return;
 
 	auto g = countGeometry();
@@ -1788,23 +1789,23 @@ void HistoryMessage::updatePressed(int x, int y) {
 			auto mediaHeight = _media->height();
 			auto mediaLeft = trect.x() - st::msgPadding.left();
 			auto mediaTop = mediaAboveText ? trect.y() : (trect.y() + trect.height() - mediaHeight);
-			_media->updatePressed(x - mediaLeft, y - mediaTop);
+			_media->updatePressed(point - QPoint(mediaLeft, mediaTop));
 		}
 	} else {
-		_media->updatePressed(x - g.left(), y - g.top());
+		_media->updatePressed(point - g.topLeft());
 	}
 }
 
-bool HistoryMessage::getStateFromName(int x, int y, QRect &trect, HistoryTextState *outResult) const {
+bool HistoryMessage::getStateFromName(QPoint point, QRect &trect, HistoryTextState *outResult) const {
 	if (displayFromName()) {
-		if (y >= trect.top() && y < trect.top() + st::msgNameFont->height) {
-			if (x >= trect.left() && x < trect.left() + trect.width() && x < trect.left() + author()->nameText.maxWidth()) {
+		if (point.y() >= trect.top() && point.y() < trect.top() + st::msgNameFont->height) {
+			if (point.x() >= trect.left() && point.x() < trect.left() + trect.width() && point.x() < trect.left() + author()->nameText.maxWidth()) {
 				outResult->link = author()->openLink();
 				return true;
 			}
 			auto forwarded = Get<HistoryMessageForwarded>();
 			auto via = Get<HistoryMessageVia>();
-			if (via && !forwarded && x >= trect.left() + author()->nameText.maxWidth() + st::msgServiceFont->spacew && x < trect.left() + author()->nameText.maxWidth() + st::msgServiceFont->spacew + via->_width) {
+			if (via && !forwarded && point.x() >= trect.left() + author()->nameText.maxWidth() + st::msgServiceFont->spacew && point.x() < trect.left() + author()->nameText.maxWidth() + st::msgServiceFont->spacew + via->_width) {
 				outResult->link = via->_lnk;
 				return true;
 			}
@@ -1814,17 +1815,17 @@ bool HistoryMessage::getStateFromName(int x, int y, QRect &trect, HistoryTextSta
 	return false;
 }
 
-bool HistoryMessage::getStateForwardedInfo(int x, int y, QRect &trect, HistoryTextState *outResult, const HistoryStateRequest &request) const {
+bool HistoryMessage::getStateForwardedInfo(QPoint point, QRect &trect, HistoryTextState *outResult, const HistoryStateRequest &request) const {
 	if (displayForwardedFrom()) {
 		auto forwarded = Get<HistoryMessageForwarded>();
 		auto fwdheight = ((forwarded->_text.maxWidth() > trect.width()) ? 2 : 1) * st::semiboldFont->height;
-		if (y >= trect.top() && y < trect.top() + fwdheight) {
+		if (point.y() >= trect.top() && point.y() < trect.top() + fwdheight) {
 			auto breakEverywhere = (forwarded->_text.countHeight(trect.width()) > 2 * st::semiboldFont->height);
 			auto textRequest = request.forText();
 			if (breakEverywhere) {
 				textRequest.flags |= Text::StateRequest::Flag::BreakEverywhere;
 			}
-			*outResult = forwarded->_text.getState(x - trect.left(), y - trect.top(), trect.width(), textRequest);
+			*outResult = forwarded->_text.getState(point - trect.topLeft(), trect.width(), textRequest);
 			outResult->symbol = 0;
 			outResult->afterSymbol = false;
 			if (breakEverywhere) {
@@ -1839,11 +1840,11 @@ bool HistoryMessage::getStateForwardedInfo(int x, int y, QRect &trect, HistoryTe
 	return false;
 }
 
-bool HistoryMessage::getStateReplyInfo(int x, int y, QRect &trect, HistoryTextState *outResult) const {
+bool HistoryMessage::getStateReplyInfo(QPoint point, QRect &trect, HistoryTextState *outResult) const {
 	if (auto reply = Get<HistoryMessageReply>()) {
 		int32 h = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom();
-		if (y >= trect.top() && y < trect.top() + h) {
-			if (reply->replyToMsg && y >= trect.top() + st::msgReplyPadding.top() && y < trect.top() + st::msgReplyPadding.top() + st::msgReplyBarSize.height() && x >= trect.left() && x < trect.left() + trect.width()) {
+		if (point.y() >= trect.top() && point.y() < trect.top() + h) {
+			if (reply->replyToMsg && QRect(trect.x(), trect.y() + st::msgReplyPadding.top(), trect.width(), st::msgReplyBarSize.height()).contains(point)) {
 				outResult->link = reply->replyToLink();
 			}
 			return true;
@@ -1853,10 +1854,10 @@ bool HistoryMessage::getStateReplyInfo(int x, int y, QRect &trect, HistoryTextSt
 	return false;
 }
 
-bool HistoryMessage::getStateViaBotIdInfo(int x, int y, QRect &trect, HistoryTextState *outResult) const {
+bool HistoryMessage::getStateViaBotIdInfo(QPoint point, QRect &trect, HistoryTextState *outResult) const {
 	if (!displayFromName() && !Has<HistoryMessageForwarded>()) {
 		if (auto via = Get<HistoryMessageVia>()) {
-			if (x >= trect.left() && y >= trect.top() && y < trect.top() + st::msgNameFont->height && x < trect.left() + via->_width) {
+			if (QRect(trect.x(), trect.y(), via->_width, st::msgNameFont->height).contains(point)) {
 				outResult->link = via->_lnk;
 				return true;
 			}
@@ -1866,9 +1867,9 @@ bool HistoryMessage::getStateViaBotIdInfo(int x, int y, QRect &trect, HistoryTex
 	return false;
 }
 
-bool HistoryMessage::getStateText(int x, int y, QRect &trect, HistoryTextState *outResult, const HistoryStateRequest &request) const {
-	if (trect.contains(x, y)) {
-		*outResult = _text.getState(x - trect.x(), y - trect.y(), trect.width(), request.forText());
+bool HistoryMessage::getStateText(QPoint point, QRect &trect, HistoryTextState *outResult, const HistoryStateRequest &request) const {
+	if (trect.contains(point)) {
+		*outResult = _text.getState(point - trect.topLeft(), trect.width(), request.forText());
 		return true;
 	}
 	return false;
@@ -2370,7 +2371,7 @@ int HistoryService::resizeContentGetHeight() {
 	return _height;
 }
 
-bool HistoryService::hasPoint(int x, int y) const {
+bool HistoryService::hasPoint(QPoint point) const {
 	auto g = countGeometry();
 	if (g.width() < 1) {
 		return false;
@@ -2385,10 +2386,10 @@ bool HistoryService::hasPoint(int x, int y) const {
 	if (_media) {
 		g.setHeight(g.height() - (st::msgServiceMargin.top() + _media->height()));
 	}
-	return g.contains(x, y);
+	return g.contains(point);
 }
 
-HistoryTextState HistoryService::getState(int x, int y, HistoryStateRequest request) const {
+HistoryTextState HistoryService::getState(QPoint point, HistoryStateRequest request) const {
 	HistoryTextState result;
 
 	auto g = countGeometry();
@@ -2397,12 +2398,12 @@ HistoryTextState HistoryService::getState(int x, int y, HistoryStateRequest requ
 	}
 
 	if (auto dateh = displayedDateHeight()) {
-		y -= dateh;
+		point.setY(point.y() - dateh);
 		g.setHeight(g.height() - dateh);
 	}
 	if (auto unreadbar = Get<HistoryMessageUnreadBar>()) {
 		auto unreadbarh = unreadbar->height();
-		y -= unreadbarh;
+		point.setY(point.y() - unreadbarh);
 		g.setHeight(g.height() - unreadbarh);
 	}
 
@@ -2410,21 +2411,21 @@ HistoryTextState HistoryService::getState(int x, int y, HistoryStateRequest requ
 		g.setHeight(g.height() - (st::msgServiceMargin.top() + _media->height()));
 	}
 	auto trect = g.marginsAdded(-st::msgServicePadding);
-	if (trect.contains(x, y)) {
+	if (trect.contains(point)) {
 		auto textRequest = request.forText();
 		textRequest.align = style::al_center;
-		result = _text.getState(x - trect.x(), y - trect.y(), trect.width(), textRequest);
+		result = _text.getState(point - trect.topLeft(), trect.width(), textRequest);
 		if (auto gamescore = Get<HistoryServiceGameScore>()) {
-			if (!result.link && result.cursor == HistoryInTextCursorState && g.contains(x, y)) {
+			if (!result.link && result.cursor == HistoryInTextCursorState && g.contains(point)) {
 				result.link = gamescore->lnk;
 			}
 		} else if (auto payment = Get<HistoryServicePayment>()) {
-			if (!result.link && result.cursor == HistoryInTextCursorState && g.contains(x, y)) {
+			if (!result.link && result.cursor == HistoryInTextCursorState && g.contains(point)) {
 				result.link = payment->lnk;
 			}
 		}
 	} else if (_media) {
-		result = _media->getState(x - st::msgServiceMargin.left() - (g.width() - _media->maxWidth()) / 2, y - st::msgServiceMargin.top() - g.height() - st::msgServiceMargin.top(), request);
+		result = _media->getState(point - QPoint(st::msgServiceMargin.left() + (g.width() - _media->maxWidth()) / 2, st::msgServiceMargin.top() + g.height() + st::msgServiceMargin.top()), request);
 	}
 	return result;
 }
diff --git a/Telegram/SourceFiles/history/history_message.h b/Telegram/SourceFiles/history/history_message.h
index ce76692cc..27fa5c233 100644
--- a/Telegram/SourceFiles/history/history_message.h
+++ b/Telegram/SourceFiles/history/history_message.h
@@ -75,11 +75,11 @@ public:
 
 	void dependencyItemRemoved(HistoryItem *dependency) override;
 
-	bool hasPoint(int x, int y) const override;
-	bool pointInTime(int32 right, int32 bottom, int x, int y, InfoDisplayType type) const override;
+	bool hasPoint(QPoint point) const override;
+	bool pointInTime(int right, int bottom, QPoint point, InfoDisplayType type) const override;
 
-	HistoryTextState getState(int x, int y, HistoryStateRequest request) const override;
-	void updatePressed(int x, int y) override;
+	HistoryTextState getState(QPoint point, HistoryStateRequest request) const override;
+	void updatePressed(QPoint point) override;
 
 	TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override;
 
@@ -184,11 +184,11 @@ private:
 	void paintViaBotIdInfo(Painter &p, QRect &trect, bool selected) const;
 	void paintText(Painter &p, QRect &trect, TextSelection selection) const;
 
-	bool getStateFromName(int x, int y, QRect &trect, HistoryTextState *outResult) const;
-	bool getStateForwardedInfo(int x, int y, QRect &trect, HistoryTextState *outResult, const HistoryStateRequest &request) const;
-	bool getStateReplyInfo(int x, int y, QRect &trect, HistoryTextState *outResult) const;
-	bool getStateViaBotIdInfo(int x, int y, QRect &trect, HistoryTextState *outResult) const;
-	bool getStateText(int x, int y, QRect &trect, HistoryTextState *outResult, const HistoryStateRequest &request) const;
+	bool getStateFromName(QPoint point, QRect &trect, HistoryTextState *outResult) const;
+	bool getStateForwardedInfo(QPoint point, QRect &trect, HistoryTextState *outResult, const HistoryStateRequest &request) const;
+	bool getStateReplyInfo(QPoint point, QRect &trect, HistoryTextState *outResult) const;
+	bool getStateViaBotIdInfo(QPoint point, QRect &trect, HistoryTextState *outResult) const;
+	bool getStateText(QPoint point, QRect &trect, HistoryTextState *outResult, const HistoryStateRequest &request) const;
 
 	void setMedia(const MTPMessageMedia *media);
 	void setReplyMarkup(const MTPReplyMarkup *markup);
@@ -223,7 +223,7 @@ private:
 
 		void startPaint(Painter &p) const override;
 		const style::TextStyle &textStyle() const override;
-		void repaint(const HistoryItem *item) const override;
+		void repaint(gsl::not_null<const HistoryItem*> item) const override;
 
 	protected:
 		void paintButtonBg(Painter &p, const QRect &rect, float64 howMuchOver) const override;
@@ -300,8 +300,8 @@ public:
 	QRect countGeometry() const;
 
 	void draw(Painter &p, QRect clip, TextSelection selection, TimeMs ms) const override;
-	bool hasPoint(int x, int y) const override;
-	HistoryTextState getState(int x, int y, HistoryStateRequest request) const override;
+	bool hasPoint(QPoint point) const override;
+	HistoryTextState getState(QPoint point, HistoryStateRequest request) const override;
 
 	TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override {
 		return _text.adjustSelection(selection, type);
diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp
index ef8f79891..cd98a5e66 100644
--- a/Telegram/SourceFiles/historywidget.cpp
+++ b/Telegram/SourceFiles/historywidget.cpp
@@ -665,7 +665,7 @@ HistoryWidget::HistoryWidget(QWidget *parent, gsl::not_null<Window::Controller*>
 			onPreviewCheck();
 		}
 	}));
-	subscribe(controller->widgetGrabbed(), [this] {
+	subscribe(controller->window()->widgetGrabbed(), [this] {
 		// Qt bug workaround: QWidget::render() for an arbitrary widget calls
 		// sendPendingMoveAndResizeEvents(true, true) for the whole window,
 		// which does something like:
@@ -2047,10 +2047,6 @@ void HistoryWidget::clearAllLoadRequests() {
 	_preloadRequest = _preloadDownRequest = _firstLoadRequest = 0;
 }
 
-void HistoryWidget::updateAfterDrag() {
-	if (_list) _list->dragActionUpdate(QCursor::pos());
-}
-
 void HistoryWidget::updateFieldSubmitSettings() {
 	auto settings = Ui::FlatTextarea::SubmitSettings::Enter;
 	if (_isInlineBot) {
@@ -2434,12 +2430,6 @@ void HistoryWidget::unreadCountChanged(History *history) {
 	}
 }
 
-void HistoryWidget::historyCleared(History *history) {
-	if (history == _history) {
-		_list->dragActionCancel();
-	}
-}
-
 bool HistoryWidget::messagesFailed(const RPCError &error, mtpRequestId requestId) {
 	if (MTP::isDefaultHandledError(error)) return false;
 
@@ -3452,7 +3442,7 @@ void HistoryWidget::hideSingleUseKeyboard(PeerData *peer, MsgId replyTo) {
 	}
 }
 
-void HistoryWidget::app_sendBotCallback(const HistoryMessageReplyMarkup::Button *button, const HistoryItem *msg, int row, int col) {
+void HistoryWidget::app_sendBotCallback(const HistoryMessageReplyMarkup::Button *button, gsl::not_null<const HistoryItem*> msg, int row, int col) {
 	if (msg->id < 0 || _peer != msg->history()->peer) {
 		return;
 	}
@@ -3520,7 +3510,7 @@ void HistoryWidget::botCallbackDone(BotCallbackInfo info, const MTPmessages_BotC
 
 bool HistoryWidget::botCallbackFail(BotCallbackInfo info, const RPCError &error, mtpRequestId req) {
 	// show error?
-	if (HistoryItem *item = App::histItemById(info.msgId)) {
+	if (auto item = App::histItemById(info.msgId)) {
 		if (auto markup = item->Get<HistoryMessageReplyMarkup>()) {
 			if (info.row < markup->rows.size() && info.col < markup->rows.at(info.row).size()) {
 				if (markup->rows.at(info.row).at(info.col).requestId == req) {
@@ -4803,7 +4793,7 @@ bool HistoryWidget::isItemVisible(HistoryItem *item) {
 	return true;
 }
 
-void HistoryWidget::ui_repaintHistoryItem(const HistoryItem *item) {
+void HistoryWidget::ui_repaintHistoryItem(gsl::not_null<const HistoryItem*> item) {
 	if (_peer && _list && (item->history() == _history || (_migrated && item->history() == _migrated))) {
 		auto ms = getms();
 		if (_lastScrolled + kSkipRepaintWhileScrollMs <= ms) {
diff --git a/Telegram/SourceFiles/historywidget.h b/Telegram/SourceFiles/historywidget.h
index f293db3e5..d5bdc8851 100644
--- a/Telegram/SourceFiles/historywidget.h
+++ b/Telegram/SourceFiles/historywidget.h
@@ -199,7 +199,6 @@ public:
 	void newUnreadMsg(History *history, HistoryItem *item);
 	void historyToDown(History *history);
 	void historyWasRead(ReadServerHistoryChecks checks);
-	void historyCleared(History *history);
 	void unreadCountChanged(History *history);
 
 	QRect historyRect() const;
@@ -315,7 +314,6 @@ public:
 	void updateHistoryDownPosition();
 	void updateHistoryDownVisibility();
 
-	void updateAfterDrag();
 	void updateFieldSubmitSettings();
 
 	void setInnerFocus();
@@ -345,9 +343,9 @@ public:
 	bool wheelEventFromFloatPlayer(QEvent *e, Window::Column myColumn, Window::Column playerColumn) override;
 	QRect rectForFloatPlayer(Window::Column myColumn, Window::Column playerColumn) override;
 
-	void app_sendBotCallback(const HistoryMessageReplyMarkup::Button *button, const HistoryItem *msg, int row, int col);
+	void app_sendBotCallback(const HistoryMessageReplyMarkup::Button *button, gsl::not_null<const HistoryItem*> msg, int row, int col);
 
-	void ui_repaintHistoryItem(const HistoryItem *item);
+	void ui_repaintHistoryItem(gsl::not_null<const HistoryItem*> item);
 	PeerData *ui_getPeerForMouseAction();
 
 	void notify_historyItemLayoutChanged(const HistoryItem *item);
diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp
index 33c258ff8..68da37939 100644
--- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp
+++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp
@@ -209,9 +209,9 @@ void Gif::paint(Painter &p, const QRect &clip, const PaintContext *context) cons
 	}
 }
 
-void Gif::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const {
-	if (x >= 0 && x < _width && y >= 0 && y < st::inlineMediaHeight) {
-		if (_delete && (rtl() ? _width - x : x) >= _width - st::stickerPanDelete.width() && y < st::stickerPanDelete.height()) {
+void Gif::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, QPoint point) const {
+	if (QRect(0, 0, _width, st::inlineMediaHeight).contains(point)) {
+		if (_delete && rtlpoint(point, _width).x() >= _width - st::stickerPanDelete.width() && point.y() < st::stickerPanDelete.height()) {
 			link = _delete;
 		} else {
 			link = _send;
@@ -400,8 +400,8 @@ void Sticker::paint(Painter &p, const QRect &clip, const PaintContext *context)
 	}
 }
 
-void Sticker::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const {
-	if (x >= 0 && x < _width && y >= 0 && y < st::inlineMediaHeight) {
+void Sticker::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, QPoint point) const {
+	if (QRect(0, 0, _width, st::inlineMediaHeight).contains(point)) {
 		link = _send;
 	}
 }
@@ -487,8 +487,8 @@ void Photo::paint(Painter &p, const QRect &clip, const PaintContext *context) co
 	}
 }
 
-void Photo::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const {
-	if (x >= 0 && x < _width && y >= 0 && y < st::inlineMediaHeight) {
+void Photo::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, QPoint point) const {
+	if (QRect(0, 0, _width, st::inlineMediaHeight).contains(point)) {
 		link = _send;
 	}
 }
@@ -629,12 +629,12 @@ void Video::paint(Painter &p, const QRect &clip, const PaintContext *context) co
 	}
 }
 
-void Video::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const {
-	if (x >= 0 && x < st::inlineThumbSize && y >= st::inlineRowMargin && y < st::inlineRowMargin + st::inlineThumbSize) {
+void Video::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, QPoint point) const {
+	if (QRect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize).contains(point)) {
 		link = _link;
 		return;
 	}
-	if (x >= st::inlineThumbSize + st::inlineThumbSkip && x < _width && y >= 0 && y < _height) {
+	if (QRect(st::inlineThumbSize + st::inlineThumbSkip, 0, _width - st::inlineThumbSize - st::inlineThumbSkip, _height).contains(point)) {
 		link = _send;
 		return;
 	}
@@ -769,12 +769,13 @@ void File::paint(Painter &p, const QRect &clip, const PaintContext *context) con
 	}
 }
 
-void File::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const {
-	if (x >= 0 && x < st::msgFileSize && y >= st::inlineRowMargin && y < st::inlineRowMargin + st::msgFileSize) {
+void File::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, QPoint point) const {
+	if (QRect(0, st::inlineRowMargin, st::msgFileSize, st::msgFileSize).contains(point)) {
 		link = getShownDocument()->loading() ? _cancel : _open;
 		return;
 	}
-	if (x >= st::msgFileSize + st::inlineThumbSkip && x < _width && y >= 0 && y < _height) {
+	auto left = st::msgFileSize + st::inlineThumbSkip;
+	if (QRect(left, 0, _width - left, _height).contains(point)) {
 		link = _send;
 		return;
 	}
@@ -928,12 +929,12 @@ void Contact::paint(Painter &p, const QRect &clip, const PaintContext *context)
 	}
 }
 
-void Contact::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const {
-	int left = (st::msgFileSize + st::inlineThumbSkip);
-	if (x >= 0 && x < left - st::inlineThumbSkip && y >= st::inlineRowMargin && y < st::inlineRowMargin + st::inlineThumbSize) {
+void Contact::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, QPoint point) const {
+	if (QRect(0, st::inlineRowMargin, st::msgFileSize, st::inlineThumbSize).contains(point)) {
 		return;
 	}
-	if (x >= left && x < _width && y >= 0 && y < _height) {
+	auto left = (st::msgFileSize + st::inlineThumbSkip);
+	if (QRect(left, 0, _width - left, _height).contains(point)) {
 		link = _send;
 		return;
 	}
@@ -1064,19 +1065,19 @@ void Article::paint(Painter &p, const QRect &clip, const PaintContext *context)
 	}
 }
 
-void Article::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const {
-	int left = _withThumb ? (st::inlineThumbSize + st::inlineThumbSkip) : 0;
-	if (x >= 0 && x < left - st::inlineThumbSkip && y >= st::inlineRowMargin && y < st::inlineRowMargin + st::inlineThumbSize) {
+void Article::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, QPoint point) const {
+	if (_withThumb && QRect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize).contains(point)) {
 		link = _link;
 		return;
 	}
-	if (x >= left && x < _width && y >= 0 && y < _height) {
+	auto left = _withThumb ? (st::inlineThumbSize + st::inlineThumbSkip) : 0;
+	if (QRect(left, 0, _width - left, _height).contains(point)) {
 		if (_url) {
-			int32 left = st::inlineThumbSize + st::inlineThumbSkip;
-			int32 titleHeight = qMin(_title.countHeight(_width - left), st::semiboldFont->height * 2);
-			int32 descriptionLines = 2;
-			int32 descriptionHeight = qMin(_description.countHeight(_width - left), st::normalFont->height * descriptionLines);
-			if (rtlrect(left, st::inlineRowMargin + titleHeight + descriptionHeight, _urlWidth, st::normalFont->height, _width).contains(x, y)) {
+			auto left = st::inlineThumbSize + st::inlineThumbSkip;
+			auto titleHeight = qMin(_title.countHeight(_width - left), st::semiboldFont->height * 2);
+			auto descriptionLines = 2;
+			auto descriptionHeight = qMin(_description.countHeight(_width - left), st::normalFont->height * descriptionLines);
+			if (rtlrect(left, st::inlineRowMargin + titleHeight + descriptionHeight, _urlWidth, st::normalFont->height, _width).contains(point)) {
 				link = _url;
 				return;
 			}
@@ -1248,13 +1249,13 @@ void Game::paint(Painter &p, const QRect &clip, const PaintContext *context) con
 	}
 }
 
-void Game::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const {
+void Game::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, QPoint point) const {
 	int left = st::inlineThumbSize + st::inlineThumbSkip;
-	if (x >= 0 && x < left - st::inlineThumbSkip && y >= st::inlineRowMargin && y < st::inlineRowMargin + st::inlineThumbSize) {
+	if (QRect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize).contains(point)) {
 		link = _send;
 		return;
 	}
-	if (x >= left && x < _width && y >= 0 && y < _height) {
+	if (QRect(left, 0, _width - left, _height).contains(point)) {
 		link = _send;
 		return;
 	}
diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h
index 7685fdb2e..6c10c67b9 100644
--- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h
+++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h
@@ -72,7 +72,7 @@ public:
 	}
 
 	void paint(Painter &p, const QRect &clip, const PaintContext *context) const override;
-	void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const override;
+	void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, QPoint point) const override;
 
 	// ClickHandlerHost interface
 	void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
@@ -131,7 +131,7 @@ public:
 	}
 
 	void paint(Painter &p, const QRect &clip, const PaintContext *context) const override;
-	void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const override;
+	void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, QPoint point) const override;
 
 private:
 	PhotoData *getShownPhoto() const;
@@ -161,7 +161,7 @@ public:
 	void preload() const override;
 
 	void paint(Painter &p, const QRect &clip, const PaintContext *context) const override;
-	void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const override;
+	void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, QPoint point) const override;
 
 	// ClickHandlerHost interface
 	void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
@@ -185,7 +185,7 @@ public:
 	void initDimensions() override;
 
 	void paint(Painter &p, const QRect &clip, const PaintContext *context) const override;
-	void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const override;
+	void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, QPoint point) const override;
 
 private:
 	ClickHandlerPtr _link;
@@ -232,7 +232,7 @@ public:
 	void initDimensions() override;
 
 	void paint(Painter &p, const QRect &clip, const PaintContext *context) const override;
-	void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const override;
+	void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, QPoint point) const override;
 
 	// ClickHandlerHost interface
 	void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
@@ -295,7 +295,7 @@ public:
 	int resizeGetHeight(int width) override;
 
 	void paint(Painter &p, const QRect &clip, const PaintContext *context) const override;
-	void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const override;
+	void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, QPoint point) const override;
 
 private:
 	mutable QPixmap _thumb;
@@ -313,7 +313,7 @@ public:
 	int resizeGetHeight(int width) override;
 
 	void paint(Painter &p, const QRect &clip, const PaintContext *context) const override;
-	void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const override;
+	void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, QPoint point) const override;
 
 private:
 	ClickHandlerPtr _url, _link;
@@ -336,7 +336,7 @@ public:
 	void initDimensions() override;
 
 	void paint(Painter &p, const QRect &clip, const PaintContext *context) const override;
-	void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const override;
+	void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, QPoint point) const override;
 
 private:
 	void countFrameSize();
diff --git a/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp b/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp
index b3d182d0c..1e9fcd842 100644
--- a/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp
+++ b/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp
@@ -640,7 +640,7 @@ void Inner::updateSelected() {
 		}
 		if (col < inlineItems.size()) {
 			sel = row * MatrixRowShift + col;
-			inlineItems.at(col)->getState(lnk, cursor, sx, sy);
+			inlineItems.at(col)->getState(lnk, cursor, QPoint(sx, sy));
 			lnkhost = inlineItems.at(col);
 		} else {
 			row = col = -1;
diff --git a/Telegram/SourceFiles/layout.h b/Telegram/SourceFiles/layout.h
index 9633a42b6..8cd340ac2 100644
--- a/Telegram/SourceFiles/layout.h
+++ b/Telegram/SourceFiles/layout.h
@@ -108,38 +108,38 @@ public:
 	LayoutItemBase(const LayoutItemBase &other) = delete;
 	LayoutItemBase &operator=(const LayoutItemBase &other) = delete;
 
-	int32 maxWidth() const {
+	int maxWidth() const {
 		return _maxw;
 	}
-	int32 minHeight() const {
+	int minHeight() const {
 		return _minh;
 	}
 	virtual void initDimensions() = 0;
-	virtual int32 resizeGetHeight(int32 width) {
+	virtual int resizeGetHeight(int width) {
 		_width = qMin(width, _maxw);
 		_height = _minh;
 		return _height;
 	}
 
-	virtual void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const {
+	virtual void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, QPoint point) const {
 		link.clear();
 		cursor = HistoryDefaultCursorState;
 	}
-	virtual void getSymbol(uint16 &symbol, bool &after, bool &upon, int x, int y) const { // from text
-		upon = hasPoint(x, y);
+	virtual void getSymbol(uint16 &symbol, bool &after, bool &upon, QPoint point) const { // from text
+		upon = hasPoint(point);
 		symbol = upon ? 0xFFFF : 0;
 		after = false;
 	}
 
-	int32 width() const {
+	int width() const {
 		return _width;
 	}
-	int32 height() const {
+	int height() const {
 		return _height;
 	}
 
-	bool hasPoint(int x, int y) const {
-		return (x >= 0 && y >= 0 && x < width() && y < height());
+	bool hasPoint(QPoint point) const {
+		return QRect(0, 0, width(), height()).contains(point);
 	}
 
 	virtual ~LayoutItemBase() {
diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp
index c1acd5c78..693adcfe6 100644
--- a/Telegram/SourceFiles/mainwidget.cpp
+++ b/Telegram/SourceFiles/mainwidget.cpp
@@ -872,13 +872,17 @@ void MainWidget::notify_migrateUpdated(PeerData *peer) {
 	_history->notify_migrateUpdated(peer);
 }
 
-void MainWidget::ui_repaintHistoryItem(const HistoryItem *item) {
-	_history->ui_repaintHistoryItem(item);
-	if (item->history()->lastMsg == item) {
-		item->history()->updateChatListEntry();
+void MainWidget::ui_repaintHistoryItem(gsl::not_null<const HistoryItem*> item) {
+	if (item->isLogEntry()) {
+		AuthSession::Current().data().repaintLogEntry().notify(item, true);
+	} else {
+		_history->ui_repaintHistoryItem(item);
+		if (item->history()->lastMsg == item) {
+			item->history()->updateChatListEntry();
+		}
+		_playerPlaylist->ui_repaintHistoryItem(item);
+		_playerPanel->ui_repaintHistoryItem(item);
 	}
-	_playerPlaylist->ui_repaintHistoryItem(item);
-	_playerPanel->ui_repaintHistoryItem(item);
 	if (_overview) _overview->ui_repaintHistoryItem(item);
 	if (auto last = currentFloatPlayer()) {
 		last->widget->ui_repaintHistoryItem(item);
@@ -2471,14 +2475,6 @@ void MainWidget::clearBotStartToken(PeerData *peer) {
 	}
 }
 
-void MainWidget::updateAfterDrag() {
-	if (_overview) {
-		_overview->updateAfterDrag();
-	} else {
-		_history->updateAfterDrag();
-	}
-}
-
 void MainWidget::ctrlEnterSubmitUpdated() {
 	_history->updateFieldSubmitSettings();
 }
@@ -3197,10 +3193,6 @@ void MainWidget::markActiveHistoryAsRead() {
 	_history->historyWasRead(ReadServerHistoryChecks::OnlyIfUnread);
 }
 
-void MainWidget::historyCleared(History *history) {
-	_history->historyCleared(history);
-}
-
 void MainWidget::showAnimated(const QPixmap &bgAnimCache, bool back) {
 	_showBack = back;
 	(_showBack ? _cacheOver : _cacheUnder) = bgAnimCache;
diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h
index 39b260c27..a2364f9af 100644
--- a/Telegram/SourceFiles/mainwidget.h
+++ b/Telegram/SourceFiles/mainwidget.h
@@ -203,7 +203,6 @@ public:
 	void dialogsToUp();
 	void newUnreadMsg(History *history, HistoryItem *item);
 	void markActiveHistoryAsRead();
-	void historyCleared(History *history);
 
 	void peerBefore(const PeerData *inPeer, MsgId inMsg, PeerData *&outPeer, MsgId &outMsg);
 	void peerAfter(const PeerData *inPeer, MsgId inMsg, PeerData *&outPeer, MsgId &outMsg);
@@ -366,7 +365,6 @@ public:
 	void ptsWaiterStartTimerFor(ChannelData *channel, int32 ms); // ms <= 0 - stop timer
 	void feedUpdates(const MTPUpdates &updates, uint64 randomId = 0);
 	void feedUpdate(const MTPUpdate &update);
-	void updateAfterDrag();
 
 	void ctrlEnterSubmitUpdated();
 	void setInnerFocus();
@@ -386,7 +384,7 @@ public:
 
 	void app_sendBotCallback(const HistoryMessageReplyMarkup::Button *button, const HistoryItem *msg, int row, int col);
 
-	void ui_repaintHistoryItem(const HistoryItem *item);
+	void ui_repaintHistoryItem(gsl::not_null<const HistoryItem*> item);
 	void ui_showPeerHistory(quint64 peer, qint32 msgId, Ui::ShowWay way);
 	PeerData *ui_getPeerForMouseAction();
 
diff --git a/Telegram/SourceFiles/mainwindow.cpp b/Telegram/SourceFiles/mainwindow.cpp
index 719369f8b..4d78769ba 100644
--- a/Telegram/SourceFiles/mainwindow.cpp
+++ b/Telegram/SourceFiles/mainwindow.cpp
@@ -131,46 +131,16 @@ MainWindow::MainWindow() {
 
 	setLocale(QLocale(QLocale::English, QLocale::UnitedStates));
 
-	_inactiveTimer.setSingleShot(true);
-	connect(&_inactiveTimer, SIGNAL(timeout()), this, SLOT(onInactiveTimer()));
-
 	subscribe(Global::RefSelfChanged(), [this] { updateGlobalMenu(); });
 	subscribe(Window::Theme::Background(), [this](const Window::Theme::BackgroundUpdate &data) {
 		themeUpdated(data);
 	});
-	subscribe(Messenger::Instance().authSessionChanged(), [this] { checkAuthSession(); });
 	subscribe(Messenger::Instance().passcodedChanged(), [this] { updateGlobalMenu(); });
-	checkAuthSession();
 
 	setAttribute(Qt::WA_NoSystemBackground);
 	setAttribute(Qt::WA_OpaquePaintEvent);
 }
 
-void MainWindow::checkAuthSession() {
-	if (AuthSession::Exists()) {
-		_controller = std::make_unique<Window::Controller>(this);
-	} else {
-		_controller = nullptr;
-	}
-}
-
-void MainWindow::inactivePress(bool inactive) {
-	_inactivePress = inactive;
-	if (_inactivePress) {
-		_inactiveTimer.start(200);
-	} else {
-		_inactiveTimer.stop();
-	}
-}
-
-bool MainWindow::inactivePress() const {
-	return _inactivePress;
-}
-
-void MainWindow::onInactiveTimer() {
-	inactivePress(false);
-}
-
 void MainWindow::initHook() {
 	Platform::MainWindow::initHook();
 
@@ -368,9 +338,9 @@ void MainWindow::showMainMenu() {
 
 void MainWindow::ensureLayerCreated() {
 	if (!_layerBg) {
-		_layerBg.create(bodyWidget(), _controller.get());
-		if (_controller) {
-			_controller->enableGifPauseReason(Window::GifPauseReason::Layer);
+		_layerBg.create(bodyWidget(), controller());
+		if (controller()) {
+			controller()->enableGifPauseReason(Window::GifPauseReason::Layer);
 		}
 	}
 }
@@ -378,8 +348,8 @@ void MainWindow::ensureLayerCreated() {
 void MainWindow::destroyLayerDelayed() {
 	if (_layerBg) {
 		_layerBg.destroyDelayed();
-		if (_controller) {
-			_controller->disableGifPauseReason(Window::GifPauseReason::Layer);
+		if (controller()) {
+			controller()->disableGifPauseReason(Window::GifPauseReason::Layer);
 		}
 	}
 }
@@ -756,8 +726,8 @@ void MainWindow::noIntro(Intro::Widget *was) {
 void MainWindow::noLayerStack(LayerStackWidget *was) {
 	if (was == _layerBg) {
 		_layerBg = nullptr;
-		if (_controller) {
-			_controller->disableGifPauseReason(Window::GifPauseReason::Layer);
+		if (controller()) {
+			controller()->disableGifPauseReason(Window::GifPauseReason::Layer);
 		}
 	}
 }
diff --git a/Telegram/SourceFiles/mainwindow.h b/Telegram/SourceFiles/mainwindow.h
index bce2168fc..af2634288 100644
--- a/Telegram/SourceFiles/mainwindow.h
+++ b/Telegram/SourceFiles/mainwindow.h
@@ -42,7 +42,6 @@ namespace Theme {
 struct BackgroundUpdate;
 class WarningWidget;
 } // namespace Theme
-class Controller;
 } // namespace Window
 
 namespace Ui {
@@ -78,15 +77,8 @@ public:
 	MainWindow();
 	~MainWindow();
 
-	Window::Controller *controller() const {
-		return _controller.get();
-	}
-
 	void firstShow();
 
-	void inactivePress(bool inactive);
-	bool inactivePress() const;
-
 	void setupPasscode();
 	void clearPasscode();
 	void setupIntro();
@@ -169,8 +161,6 @@ public slots:
 	void toggleTray(QSystemTrayIcon::ActivationReason reason = QSystemTrayIcon::Unknown);
 	void toggleDisplayNotifyFromTray();
 
-	void onInactiveTimer();
-
 	void onClearFinished(int task, void *manager);
 	void onClearFailed(int task, void *manager);
 
@@ -187,7 +177,6 @@ signals:
 	void checkNewAuthorization();
 
 private:
-	void checkAuthSession();
 	void showConnecting(const QString &text, const QString &reconnect = QString());
 	void hideConnecting();
 
@@ -211,7 +200,6 @@ private:
 	QList<DelayedServiceMsg> _delayedServiceMsgs;
 	mtpRequestId _serviceHistoryRequest = 0;
 
-	std::unique_ptr<Window::Controller> _controller;
 	object_ptr<PasscodeWidget> _passcode = { nullptr };
 	object_ptr<Intro::Widget> _intro = { nullptr };
 	object_ptr<MainWidget> _main = { nullptr };
@@ -223,9 +211,6 @@ private:
 
 	Local::ClearManager *_clearManager = nullptr;
 
-	bool _inactivePress = false;
-	QTimer _inactiveTimer;
-
 };
 
 class PreLaunchWindow : public TWidget {
diff --git a/Telegram/SourceFiles/media/player/media_player_float.h b/Telegram/SourceFiles/media/player/media_player_float.h
index c69a5e6f8..98f5ddcbe 100644
--- a/Telegram/SourceFiles/media/player/media_player_float.h
+++ b/Telegram/SourceFiles/media/player/media_player_float.h
@@ -56,7 +56,7 @@ public:
 			finishDrag(false);
 		}
 	}
-	void ui_repaintHistoryItem(const HistoryItem *item) {
+	void ui_repaintHistoryItem(gsl::not_null<const HistoryItem*> item) {
 		if (item == _item) {
 			repaintItem();
 		}
diff --git a/Telegram/SourceFiles/media/player/media_player_list.cpp b/Telegram/SourceFiles/media/player/media_player_list.cpp
index 106a1525a..2d87d3190 100644
--- a/Telegram/SourceFiles/media/player/media_player_list.cpp
+++ b/Telegram/SourceFiles/media/player/media_player_list.cpp
@@ -95,7 +95,7 @@ void ListWidget::mouseMoveEvent(QMouseEvent *e) {
 			if (y <= m.y()) {
 				if (auto media = layout->toMediaItem()) {
 					item = media->getItem();
-					media->getState(lnk, cursorState, m.x(), m.y() - y);
+					media->getState(lnk, cursorState, m - QPoint(0, y));
 					lnkhost = media;
 				}
 			}
@@ -117,7 +117,7 @@ void ListWidget::mouseMoveEvent(QMouseEvent *e) {
 	}
 }
 
-void ListWidget::ui_repaintHistoryItem(const HistoryItem *item) {
+void ListWidget::ui_repaintHistoryItem(gsl::not_null<const HistoryItem*> item) {
 	repaintItem(item);
 }
 
diff --git a/Telegram/SourceFiles/media/player/media_player_list.h b/Telegram/SourceFiles/media/player/media_player_list.h
index e8d0a5d15..1f7193b8d 100644
--- a/Telegram/SourceFiles/media/player/media_player_list.h
+++ b/Telegram/SourceFiles/media/player/media_player_list.h
@@ -33,7 +33,7 @@ class ListWidget : public TWidget, private base::Subscriber {
 public:
 	ListWidget(QWidget *parent);
 
-	void ui_repaintHistoryItem(const HistoryItem *item);
+	void ui_repaintHistoryItem(gsl::not_null<const HistoryItem*> item);
 
 	QRect getCurrentTrackGeometry() const;
 
diff --git a/Telegram/SourceFiles/media/player/media_player_panel.cpp b/Telegram/SourceFiles/media/player/media_player_panel.cpp
index eedb05453..a166294fb 100644
--- a/Telegram/SourceFiles/media/player/media_player_panel.cpp
+++ b/Telegram/SourceFiles/media/player/media_player_panel.cpp
@@ -96,7 +96,7 @@ void Panel::updateControlsGeometry() {
 	}
 }
 
-void Panel::ui_repaintHistoryItem(const HistoryItem *item) {
+void Panel::ui_repaintHistoryItem(gsl::not_null<const HistoryItem*> item) {
 	if (auto list = static_cast<ListWidget*>(_scroll->widget())) {
 		list->ui_repaintHistoryItem(item);
 	}
diff --git a/Telegram/SourceFiles/media/player/media_player_panel.h b/Telegram/SourceFiles/media/player/media_player_panel.h
index a7c42927c..902aa9f75 100644
--- a/Telegram/SourceFiles/media/player/media_player_panel.h
+++ b/Telegram/SourceFiles/media/player/media_player_panel.h
@@ -52,7 +52,7 @@ public:
 	void setPinCallback(ButtonCallback &&callback);
 	void setCloseCallback(ButtonCallback &&callback);
 
-	void ui_repaintHistoryItem(const HistoryItem *item);
+	void ui_repaintHistoryItem(gsl::not_null<const HistoryItem*> item);
 
 	int bestPositionFor(int left) const;
 
diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp
index 5c15a087f..25c45e8ed 100644
--- a/Telegram/SourceFiles/mediaview.cpp
+++ b/Telegram/SourceFiles/mediaview.cpp
@@ -2458,13 +2458,12 @@ bool MediaView::updateOverState(OverState newState) {
 void MediaView::updateOver(QPoint pos) {
 	ClickHandlerPtr lnk;
 	ClickHandlerHost *lnkhost = nullptr;
-
 	if (_saveMsgStarted && _saveMsg.contains(pos)) {
-		auto textState = _saveMsgText.getState(pos.x() - _saveMsg.x() - st::mediaviewSaveMsgPadding.left(), pos.y() - _saveMsg.y() - st::mediaviewSaveMsgPadding.top(), _saveMsg.width() - st::mediaviewSaveMsgPadding.left() - st::mediaviewSaveMsgPadding.right());
+		auto textState = _saveMsgText.getState(pos - _saveMsg.topLeft() - QPoint(st::mediaviewSaveMsgPadding.left(), st::mediaviewSaveMsgPadding.top()), _saveMsg.width() - st::mediaviewSaveMsgPadding.left() - st::mediaviewSaveMsgPadding.right());
 		lnk = textState.link;
 		lnkhost = this;
 	} else if (_captionRect.contains(pos)) {
-		auto textState = _caption.getState(pos.x() - _captionRect.x(), pos.y() - _captionRect.y(), _captionRect.width());
+		auto textState = _caption.getState(pos - _captionRect.topLeft(), _captionRect.width());
 		lnk = textState.link;
 		lnkhost = this;
 	}
diff --git a/Telegram/SourceFiles/overview/overview_layout.cpp b/Telegram/SourceFiles/overview/overview_layout.cpp
index f4b4cfc7b..04bfb019d 100644
--- a/Telegram/SourceFiles/overview/overview_layout.cpp
+++ b/Telegram/SourceFiles/overview/overview_layout.cpp
@@ -282,8 +282,8 @@ void Photo::ensureCheckboxCreated() {
 	});
 }
 
-void Photo::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const {
-	if (hasPoint(x, y)) {
+void Photo::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, QPoint point) const {
+	if (hasPoint(point)) {
 		link = _link;
 	}
 }
@@ -462,10 +462,10 @@ void Video::invalidateCache() {
 	}
 }
 
-void Video::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const {
+void Video::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, QPoint point) const {
 	bool loaded = _data->loaded();
 
-	if (hasPoint(x, y)) {
+	if (hasPoint(point)) {
 		link = loaded ? _openl : (_data->loading() ? _cancell : _savel);
 	}
 }
@@ -614,7 +614,7 @@ void Voice::paint(Painter &p, const QRect &clip, TextSelection selection, const
 	}
 }
 
-void Voice::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const {
+void Voice::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, QPoint point) const {
 	bool loaded = _data->loaded();
 
 	int32 nameleft = 0, nametop = 0, nameright = 0, statustop = 0, datetop = 0;
@@ -625,18 +625,18 @@ void Voice::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, i
 	statustop = _st.songStatusTop;
 
 	auto inner = rtlrect(_st.songPadding.left(), _st.songPadding.top(), _st.songThumbSize, _st.songThumbSize, _width);
-	if (inner.contains(x, y)) {
+	if (inner.contains(point)) {
 		link = loaded ? _openl : ((_data->loading() || _data->status == FileUploading) ? _cancell : _openl);
 		return;
 	}
-	if (rtlrect(nameleft, statustop, _width - nameleft - nameright, st::normalFont->height, _width).contains(x, y)) {
+	if (rtlrect(nameleft, statustop, _width - nameleft - nameright, st::normalFont->height, _width).contains(point)) {
 		if (_status.size() == FileStatusSizeLoaded || _status.size() == FileStatusSizeReady) {
-			auto textState = _details.getStateLeft(x - nameleft, y - statustop, _width, _width);
+			auto textState = _details.getStateLeft(point - QPoint(nameleft, statustop), _width, _width);
 			link = textState.link;
 			cursor = textState.uponSymbol ? HistoryInTextCursorState : HistoryDefaultCursorState;
 		}
 	}
-	if (hasPoint(x, y) && !link && !_data->loading()) {
+	if (hasPoint(point) && !link && !_data->loading()) {
 		link = _namel;
 		return;
 	}
@@ -883,7 +883,7 @@ void Document::paint(Painter &p, const QRect &clip, TextSelection selection, con
 	}
 }
 
-void Document::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const {
+void Document::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, QPoint point) const {
 	bool loaded = _data->loaded() || Local::willStickerImageLoad(_data->mediaKey());
 
 	int32 nameleft = 0, nametop = 0, nameright = 0, statustop = 0, datetop = 0;
@@ -896,11 +896,11 @@ void Document::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x
 		statustop = _st.songStatusTop;
 
 		auto inner = rtlrect(_st.songPadding.left(), _st.songPadding.top(), _st.songThumbSize, _st.songThumbSize, _width);
-		if (inner.contains(x, y)) {
+		if (inner.contains(point)) {
 			link = loaded ? _openl : ((_data->loading() || _data->status == FileUploading) ? _cancell : _openl);
 			return;
 		}
-		if (hasPoint(x, y) && !_data->loading()) {
+		if (hasPoint(point) && !_data->loading()) {
 			link = _namel;
 			return;
 		}
@@ -912,23 +912,23 @@ void Document::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x
 
 		auto rthumb = rtlrect(0, st::linksBorder + _st.filePadding.top(), _st.fileThumbSize, _st.fileThumbSize, _width);
 
-		if (rthumb.contains(x, y)) {
+		if (rthumb.contains(point)) {
 			link = loaded ? _openl : ((_data->loading() || _data->status == FileUploading) ? _cancell : _savel);
 			return;
 		}
 
 		if (_data->status != FileUploadFailed) {
-			if (rtlrect(nameleft, datetop, _datew, st::normalFont->height, _width).contains(x, y)) {
+			if (rtlrect(nameleft, datetop, _datew, st::normalFont->height, _width).contains(point)) {
 				link = _msgl;
 				return;
 			}
 		}
 		if (!_data->loading() && _data->isValid()) {
-			if (loaded && rtlrect(0, st::linksBorder, nameleft, _height - st::linksBorder, _width).contains(x, y)) {
+			if (loaded && rtlrect(0, st::linksBorder, nameleft, _height - st::linksBorder, _width).contains(point)) {
 				link = _namel;
 				return;
 			}
-			if (rtlrect(nameleft, nametop, qMin(_width - nameleft - nameright, _name.maxWidth()), st::semiboldFont->height, _width).contains(x, y)) {
+			if (rtlrect(nameleft, nametop, qMin(_width - nameleft - nameright, _name.maxWidth()), st::semiboldFont->height, _width).contains(point)) {
 				link = _namel;
 				return;
 			}
@@ -1203,9 +1203,9 @@ void Link::paint(Painter &p, const QRect &clip, TextSelection selection, const P
 	}
 }
 
-void Link::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const {
+void Link::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, QPoint point) const {
 	int32 left = st::linksPhotoSize + st::linksPhotoPadding, top = st::linksMargin.top() + st::linksBorder, w = _width - left;
-	if (rtlrect(0, top, st::linksPhotoSize, st::linksPhotoSize, _width).contains(x, y)) {
+	if (rtlrect(0, top, st::linksPhotoSize, st::linksPhotoSize, _width).contains(point)) {
 		link = _photol;
 		return;
 	}
@@ -1214,7 +1214,7 @@ void Link::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, in
 		top += (st::linksPhotoSize - st::semiboldFont->height - st::normalFont->height) / 2;
 	}
 	if (!_title.isEmpty()) {
-		if (rtlrect(left, top, qMin(w, _titlew), st::semiboldFont->height, _width).contains(x, y)) {
+		if (rtlrect(left, top, qMin(w, _titlew), st::semiboldFont->height, _width).contains(point)) {
 			link = _photol;
 			return;
 		}
@@ -1224,7 +1224,7 @@ void Link::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, in
 		top += qMin(st::normalFont->height * 3, _text.countHeight(w));
 	}
 	for (int32 i = 0, l = _links.size(); i < l; ++i) {
-		if (rtlrect(left, top, qMin(w, _links.at(i).width), st::normalFont->height, _width).contains(x, y)) {
+		if (rtlrect(left, top, qMin(w, _links.at(i).width), st::normalFont->height, _width).contains(point)) {
 			link = _links.at(i).lnk;
 			return;
 		}
diff --git a/Telegram/SourceFiles/overview/overview_layout.h b/Telegram/SourceFiles/overview/overview_layout.h
index e6a7750fe..c3b28f4ad 100644
--- a/Telegram/SourceFiles/overview/overview_layout.h
+++ b/Telegram/SourceFiles/overview/overview_layout.h
@@ -55,7 +55,7 @@ public:
 		return nullptr;
 	}
 	MsgId msgId() const {
-		const HistoryItem *item = getItem();
+		auto item = getItem();
 		return item ? item->id : 0;
 	}
 
@@ -184,7 +184,7 @@ public:
 	void initDimensions() override;
 	int32 resizeGetHeight(int32 width) override;
 	void paint(Painter &p, const QRect &clip, TextSelection selection, const PaintContext *context) override;
-	void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const override;
+	void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, QPoint point) const override;
 
 	void clickHandlerActiveChanged(const ClickHandlerPtr &action, bool active) override;
 	void clickHandlerPressedChanged(const ClickHandlerPtr &action, bool pressed) override;
@@ -211,7 +211,7 @@ public:
 	void initDimensions() override;
 	int32 resizeGetHeight(int32 width) override;
 	void paint(Painter &p, const QRect &clip, TextSelection selection, const PaintContext *context) override;
-	void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const override;
+	void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, QPoint point) const override;
 
 	void clickHandlerActiveChanged(const ClickHandlerPtr &action, bool active) override;
 	void clickHandlerPressedChanged(const ClickHandlerPtr &action, bool pressed) override;
@@ -254,7 +254,7 @@ public:
 
 	void initDimensions() override;
 	void paint(Painter &p, const QRect &clip, TextSelection selection, const PaintContext *context) override;
-	void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const override;
+	void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, QPoint point) const override;
 
 protected:
 	float64 dataProgress() const override {
@@ -291,7 +291,7 @@ public:
 
 	void initDimensions() override;
 	void paint(Painter &p, const QRect &clip, TextSelection selection, const PaintContext *context) override;
-	void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const override;
+	void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, QPoint point) const override;
 
 	virtual DocumentData *getDocument() const override {
 		return _data;
@@ -340,7 +340,7 @@ public:
 	void initDimensions() override;
 	int32 resizeGetHeight(int32 width) override;
 	void paint(Painter &p, const QRect &clip, TextSelection selection, const PaintContext *context) override;
-	void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const override;
+	void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, QPoint point) const override;
 
 private:
 	ClickHandlerPtr _photol;
diff --git a/Telegram/SourceFiles/overviewwidget.cpp b/Telegram/SourceFiles/overviewwidget.cpp
index f28371865..f716d32d8 100644
--- a/Telegram/SourceFiles/overviewwidget.cpp
+++ b/Telegram/SourceFiles/overviewwidget.cpp
@@ -92,6 +92,9 @@ OverviewInner::OverviewInner(OverviewWidget *overview, Ui::ScrollArea *scroll, P
 			invalidateCache();
 		}
 	});
+	subscribe(App::wnd()->dragFinished(), [this] {
+		dragActionUpdate(QCursor::pos());
+	});
 
 	if (_type == OverviewLinks || _type == OverviewFiles) {
 		_search->show();
@@ -469,8 +472,8 @@ void OverviewInner::dragActionStart(const QPoint &screenPos, Qt::MouseButton but
 	_dragItem = _mousedItem;
 	_dragItemIndex = _mousedItemIndex;
 	_dragStartPos = mapMouseToItem(mapFromGlobal(screenPos), _dragItem, _dragItemIndex);
-	_dragWasInactive = App::wnd()->inactivePress();
-	if (_dragWasInactive) App::wnd()->inactivePress(false);
+	_dragWasInactive = App::wnd()->wasInactivePress();
+	if (_dragWasInactive) App::wnd()->setInactivePress(false);
 	if (ClickHandler::getPressed() && _selected.isEmpty()) {
 		_dragAction = PrepareDrag;
 	} else if (!_selected.isEmpty()) {
@@ -564,7 +567,7 @@ void OverviewInner::dragActionFinish(const QPoint &screenPos, Qt::MouseButton bu
 	_overview->updateTopBarSelection();
 }
 
-void OverviewInner::onDragExec() {
+void OverviewInner::performDrag() {
 	if (_dragAction != Dragging) return;
 
 	bool uponSelected = false;
@@ -596,25 +599,20 @@ void OverviewInner::onDragExec() {
 		updateDragSelection(0, -1, 0, -1, false);
 		_overview->noSelectingScroll();
 
-		QDrag *drag = new QDrag(App::wnd());
-		QMimeData *mimeData = new QMimeData;
-
+		auto mimeData = std::make_unique<QMimeData>();
 		if (!sel.isEmpty()) mimeData->setText(sel);
 		if (!urls.isEmpty()) mimeData->setUrls(urls);
 		if (forwardSelected) {
 			mimeData->setData(qsl("application/x-td-forward-selected"), "1");
 		}
-		drag->setMimeData(mimeData);
-		drag->exec(Qt::CopyAction);
 
-		// We don't receive mouseReleaseEvent when drag is finished.
-		ClickHandler::unpressed();
-		if (App::main()) App::main()->updateAfterDrag();
+		// This call enters event loop and can destroy any QObject.
+		App::wnd()->launchDrag(std::move(mimeData));
 		return;
 	} else {
 		QString forwardMimeType;
 		HistoryMedia *pressedMedia = nullptr;
-		if (HistoryItem *pressedLnkItem = App::pressedLinkItem()) {
+		if (auto pressedLnkItem = App::pressedLinkItem()) {
 			if ((pressedMedia = pressedLnkItem->getMedia())) {
 				if (forwardMimeType.isEmpty() && pressedMedia->dragItemByHandler(pressedHandler)) {
 					forwardMimeType = qsl("application/x-td-forward-pressed-link");
@@ -622,12 +620,10 @@ void OverviewInner::onDragExec() {
 			}
 		}
 		if (!forwardMimeType.isEmpty()) {
-			QDrag *drag = new QDrag(App::wnd());
-			QMimeData *mimeData = new QMimeData;
-
+			auto mimeData = std::make_unique<QMimeData>();
 			mimeData->setData(qsl("application/x-td-forward-pressed-link"), "1");
-			if (DocumentData *document = (pressedMedia ? pressedMedia->getDocument() : nullptr)) {
-				QString filepath = document->filepath(DocumentData::FilePathResolveChecked);
+			if (auto document = (pressedMedia ? pressedMedia->getDocument() : nullptr)) {
+				auto filepath = document->filepath(DocumentData::FilePathResolveChecked);
 				if (!filepath.isEmpty()) {
 					QList<QUrl> urls;
 					urls.push_back(QUrl::fromLocalFile(filepath));
@@ -635,12 +631,8 @@ void OverviewInner::onDragExec() {
 				}
 			}
 
-			drag->setMimeData(mimeData);
-			drag->exec(Qt::CopyAction);
-
-			// We don't receive mouseReleaseEvent when drag is finished.
-			ClickHandler::unpressed();
-			if (App::main()) App::main()->updateAfterDrag();
+			// This call enters event loop and can destroy any QObject.
+			App::wnd()->launchDrag(std::move(mimeData));
 			return;
 		}
 	}
@@ -904,7 +896,7 @@ void OverviewInner::onUpdateSelected() {
 				item = media->getItem();
 				index = i;
 				if (upon) {
-					media->getState(lnk, cursorState, m.x() - col * w - st::overviewPhotoSkip, m.y() - _marginTop - row * vsize - st::overviewPhotoSkip);
+					media->getState(lnk, cursorState, m - QPoint(col * w + st::overviewPhotoSkip, _marginTop + row * vsize + st::overviewPhotoSkip));
 					lnkhost = media;
 				}
 			}
@@ -940,7 +932,7 @@ void OverviewInner::onUpdateSelected() {
 				if (auto media = _items.at(i)->toMediaItem()) {
 					item = media->getItem();
 					index = i;
-					media->getState(lnk, cursorState, m.x() - _rowsLeft, m.y() - _marginTop - top);
+					media->getState(lnk, cursorState, m - QPoint(_rowsLeft, _marginTop + top));
 					lnkhost = media;
 				}
 				break;
@@ -989,7 +981,7 @@ void OverviewInner::onUpdateSelected() {
 		if (_mousedItem != _dragItem || (m - _dragStartPos).manhattanLength() >= QApplication::startDragDistance()) {
 			if (_dragAction == PrepareDrag) {
 				_dragAction = Dragging;
-				QTimer::singleShot(1, this, SLOT(onDragExec()));
+				InvokeQueued(this, [this] { performDrag(); });
 			} else if (_dragAction == PrepareSelect) {
 				_dragAction = Selecting;
 			}
@@ -2267,7 +2259,7 @@ void OverviewWidget::grabFinish() {
 	_topShadow->show();
 }
 
-void OverviewWidget::ui_repaintHistoryItem(const HistoryItem *item) {
+void OverviewWidget::ui_repaintHistoryItem(gsl::not_null<const HistoryItem*> item) {
 	if (peer() == item->history()->peer || migratePeer() == item->history()->peer) {
 		_inner->repaintItem(item);
 	}
@@ -2283,10 +2275,6 @@ SelectedItemSet OverviewWidget::getSelectedItems() const {
 	return _inner->getSelectedItems();
 }
 
-void OverviewWidget::updateAfterDrag() {
-	_inner->dragActionUpdate(QCursor::pos());
-}
-
 OverviewWidget::~OverviewWidget() {
 	onClearSelected();
 	updateTopBarSelection();
diff --git a/Telegram/SourceFiles/overviewwidget.h b/Telegram/SourceFiles/overviewwidget.h
index f28671ce0..2f8091f1f 100644
--- a/Telegram/SourceFiles/overviewwidget.h
+++ b/Telegram/SourceFiles/overviewwidget.h
@@ -132,8 +132,6 @@ public slots:
 	void onTouchSelect();
 	void onTouchScrollTimer();
 
-	void onDragExec();
-
 	bool onSearchMessages(bool searchCache = false);
 	void onNeedSearchMessages();
 
@@ -142,6 +140,7 @@ private:
 	void invalidateCache();
 	void resizeItems();
 	void resizeAndRepositionItems();
+	void performDrag();
 
 	void itemRemoved(HistoryItem *item);
 	MsgId complexMsgId(const HistoryItem *item) const;
@@ -333,8 +332,6 @@ public:
 
 	SelectedItemSet getSelectedItems() const;
 
-	void updateAfterDrag();
-
 	void grabStart() override {
 		_inGrab = true;
 		resizeEvent(0);
@@ -355,7 +352,7 @@ public:
 	bool wheelEventFromFloatPlayer(QEvent *e, Window::Column myColumn, Window::Column playerColumn) override;
 	QRect rectForFloatPlayer(Window::Column myColumn, Window::Column playerColumn) override;
 
-	void ui_repaintHistoryItem(const HistoryItem *item);
+	void ui_repaintHistoryItem(gsl::not_null<const HistoryItem*> item);
 
 	void notify_historyItemLayoutChanged(const HistoryItem *item);
 
diff --git a/Telegram/SourceFiles/platform/win/windows_event_filter.cpp b/Telegram/SourceFiles/platform/win/windows_event_filter.cpp
index 400f67e18..3d2566240 100644
--- a/Telegram/SourceFiles/platform/win/windows_event_filter.cpp
+++ b/Telegram/SourceFiles/platform/win/windows_event_filter.cpp
@@ -93,7 +93,7 @@ bool EventFilter::mainWindowEvent(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lPa
 
 	case WM_ACTIVATE: {
 		if (LOWORD(wParam) == WA_CLICKACTIVE) {
-			App::wnd()->inactivePress(true);
+			App::wnd()->setInactivePress(true);
 		}
 		if (LOWORD(wParam) != WA_INACTIVE) {
 			App::wnd()->shadowsActivate();
diff --git a/Telegram/SourceFiles/ui/text/text.cpp b/Telegram/SourceFiles/ui/text/text.cpp
index 22b75f1cd..044f87161 100644
--- a/Telegram/SourceFiles/ui/text/text.cpp
+++ b/Telegram/SourceFiles/ui/text/text.cpp
@@ -1020,11 +1020,11 @@ public:
 		draw(left, top, w, align, yFrom, yTo, selection);
 	}
 
-	Text::StateResult getState(int x, int y, int w, Text::StateRequest request) {
-		if (!_t->isNull() && y >= 0) {
+	Text::StateResult getState(QPoint point, int w, Text::StateRequest request) {
+		if (!_t->isNull() && point.y() >= 0) {
 			_lookupRequest = request;
-			_lookupX = x;
-			_lookupY = y;
+			_lookupX = point.x();
+			_lookupY = point.y();
 
 			_breakEverywhere = (_lookupRequest.flags & Text::StateRequest::Flag::BreakEverywhere);
 			_lookupSymbol = (_lookupRequest.flags & Text::StateRequest::Flag::LookupSymbol);
@@ -1036,11 +1036,11 @@ public:
 		return _lookupResult;
 	}
 
-	Text::StateResult getStateElided(int x, int y, int w, Text::StateRequestElided request) {
-		if (!_t->isNull() && y >= 0 && request.lines > 0) {
+	Text::StateResult getStateElided(QPoint point, int w, Text::StateRequestElided request) {
+		if (!_t->isNull() && point.y() >= 0 && request.lines > 0) {
 			_lookupRequest = request;
-			_lookupX = x;
-			_lookupY = y;
+			_lookupX = point.x();
+			_lookupY = point.y();
 
 			_breakEverywhere = (_lookupRequest.flags & Text::StateRequest::Flag::BreakEverywhere);
 			_lookupSymbol = (_lookupRequest.flags & Text::StateRequest::Flag::LookupSymbol);
@@ -2838,14 +2838,14 @@ void Text::drawElided(Painter &painter, int32 left, int32 top, int32 w, int32 li
 	p.drawElided(left, top, w, align, lines, yFrom, yTo, removeFromEnd, breakEverywhere, selection);
 }
 
-Text::StateResult Text::getState(int x, int y, int width, StateRequest request) const {
+Text::StateResult Text::getState(QPoint point, int width, StateRequest request) const {
 	TextPainter p(0, this);
-	return p.getState(x, y, width, request);
+	return p.getState(point, width, request);
 }
 
-Text::StateResult Text::getStateElided(int x, int y, int width, StateRequestElided request) const {
+Text::StateResult Text::getStateElided(QPoint point, int width, StateRequestElided request) const {
 	TextPainter p(0, this);
-	return p.getStateElided(x, y, width, request);
+	return p.getStateElided(point, width, request);
 }
 
 TextSelection Text::adjustSelection(TextSelection selection, TextSelectType selectType) const {
diff --git a/Telegram/SourceFiles/ui/text/text.h b/Telegram/SourceFiles/ui/text/text.h
index 17e21d2ea..bbaa9d894 100644
--- a/Telegram/SourceFiles/ui/text/text.h
+++ b/Telegram/SourceFiles/ui/text/text.h
@@ -146,9 +146,9 @@ public:
 		bool afterSymbol = false;
 		uint16 symbol = 0;
 	};
-	StateResult getState(int x, int y, int width, StateRequest request = StateRequest()) const;
-	StateResult getStateLeft(int x, int y, int width, int outerw, StateRequest request = StateRequest()) const {
-		return getState(rtl() ? (outerw - x - width) : x, y, width, request);
+	StateResult getState(QPoint point, int width, StateRequest request = StateRequest()) const;
+	StateResult getStateLeft(QPoint point, int width, int outerw, StateRequest request = StateRequest()) const {
+		return getState(rtlpoint(point, outerw), width, request);
 	}
 	struct StateRequestElided : public StateRequest {
 		StateRequestElided() {
@@ -158,9 +158,9 @@ public:
 		int lines = 1;
 		int removeFromEnd = 0;
     };
-	StateResult getStateElided(int x, int y, int width, StateRequestElided request = StateRequestElided()) const;
-	StateResult getStateElidedLeft(int x, int y, int width, int outerw, StateRequestElided request = StateRequestElided()) const {
-		return getStateElided(rtl() ? (outerw - x - width) : x, y, width, request);
+	StateResult getStateElided(QPoint point, int width, StateRequestElided request = StateRequestElided()) const;
+	StateResult getStateElidedLeft(QPoint point, int width, int outerw, StateRequestElided request = StateRequestElided()) const {
+		return getStateElided(rtlpoint(point, outerw), width, request);
 	}
 
 	TextSelection adjustSelection(TextSelection selection, TextSelectType selectType) const;
diff --git a/Telegram/SourceFiles/ui/twidget.cpp b/Telegram/SourceFiles/ui/twidget.cpp
index 6d22f1d2f..e9848ef25 100644
--- a/Telegram/SourceFiles/ui/twidget.cpp
+++ b/Telegram/SourceFiles/ui/twidget.cpp
@@ -22,7 +22,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 
 #include "application.h"
 #include "mainwindow.h"
-#include "window/window_controller.h"
 
 namespace Fonts {
 namespace {
@@ -156,7 +155,7 @@ QPixmap myGrab(TWidget *target, QRect rect, QColor bg) {
 		result.fill(bg);
 	}
 
-	App::wnd()->controller()->widgetGrabbed().notify(true);
+	App::wnd()->widgetGrabbed().notify(true);
 
 	target->grabStart();
 	target->render(&result, QPoint(0, 0), rect, QWidget::DrawChildren | QWidget::IgnoreMask);
diff --git a/Telegram/SourceFiles/ui/widgets/labels.cpp b/Telegram/SourceFiles/ui/widgets/labels.cpp
index 28287ecdc..d97acdb7c 100644
--- a/Telegram/SourceFiles/ui/widgets/labels.cpp
+++ b/Telegram/SourceFiles/ui/widgets/labels.cpp
@@ -262,8 +262,8 @@ Text::StateResult FlatLabel::dragActionStart(const QPoint &p, Qt::MouseButton bu
 
 	ClickHandler::pressed();
 	_dragAction = NoDrag;
-	_dragWasInactive = App::wnd()->inactivePress();
-	if (_dragWasInactive) App::wnd()->inactivePress(false);
+	_dragWasInactive = App::wnd()->wasInactivePress();
+	if (_dragWasInactive) App::wnd()->setInactivePress(false);
 
 	if (ClickHandler::getPressed()) {
 		_dragStartPosition = mapFromGlobal(_lastMousePos);
@@ -750,9 +750,9 @@ Text::StateResult FlatLabel::getTextState(const QPoint &m) const {
 		if (_breakEverywhere) {
 			request.flags |= Text::StateRequest::Flag::BreakEverywhere;
 		}
-		state = _text.getStateElided(m.x() - _st.margin.left(), m.y() - _st.margin.top(), textWidth, request);
+		state = _text.getStateElided(m - QPoint(_st.margin.left(), _st.margin.top()), textWidth, request);
 	} else {
-		state = _text.getState(m.x() - _st.margin.left(), m.y() - _st.margin.top(), textWidth, request);
+		state = _text.getState(m - QPoint(_st.margin.left(), _st.margin.top()), textWidth, request);
 	}
 
 	return state;
diff --git a/Telegram/SourceFiles/window/main_window.cpp b/Telegram/SourceFiles/window/main_window.cpp
index 598794812..0756ef6cd 100644
--- a/Telegram/SourceFiles/window/main_window.cpp
+++ b/Telegram/SourceFiles/window/main_window.cpp
@@ -24,12 +24,15 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 #include "styles/style_window.h"
 #include "platform/platform_window_title.h"
 #include "window/themes/window_theme.h"
+#include "window/window_controller.h"
 #include "mediaview.h"
 #include "messenger.h"
 #include "mainwindow.h"
 
 namespace Window {
 
+constexpr auto kInactivePressTimeout = 200;
+
 QImage LoadLogo() {
 	return QImage(qsl(":/gui/art/logo_256.png"));
 }
@@ -58,8 +61,7 @@ MainWindow::MainWindow() : QWidget()
 , _positionUpdatedTimer(this)
 , _body(this)
 , _icon(CreateIcon())
-, _titleText(qsl("Telegram"))
-, _isActiveTimer(this) {
+, _titleText(qsl("Telegram")) {
 	subscribe(Theme::Background(), [this](const Theme::BackgroundUpdate &data) {
 		if (data.paletteChanged()) {
 			if (_title) {
@@ -70,9 +72,11 @@ MainWindow::MainWindow() : QWidget()
 	});
 	subscribe(Global::RefUnreadCounterUpdate(), [this] { updateUnreadCounter(); });
 	subscribe(Global::RefWorkMode(), [this](DBIWorkMode mode) { workmodeUpdated(mode); });
+	subscribe(Messenger::Instance().authSessionChanged(), [this] { checkAuthSession(); });
+	checkAuthSession();
 
-	_isActiveTimer->setSingleShot(true);
-	connect(_isActiveTimer, SIGNAL(timeout()), this, SLOT(updateIsActiveByTimer()));
+	_isActiveTimer.setCallback([this] { updateIsActive(0); });
+	_inactivePressTimer.setCallback([this] { setInactivePress(false); });
 }
 
 bool MainWindow::hideNoQuit() {
@@ -140,8 +144,8 @@ bool MainWindow::ui_isMediaViewShown() {
 }
 
 void MainWindow::updateIsActive(int timeout) {
-	if (timeout) {
-		return _isActiveTimer->start(timeout);
+	if (timeout > 0) {
+		return _isActiveTimer.callOnce(timeout);
 	}
 	_isActive = computeIsActive();
 	updateIsActiveHook();
@@ -433,6 +437,36 @@ PeerData *MainWindow::ui_getPeerForMouseAction() {
 	return nullptr;
 }
 
+void MainWindow::launchDrag(std::unique_ptr<QMimeData> data) {
+	auto weak = QPointer<MainWindow>(this);
+	auto drag = std::make_unique<QDrag>(App::wnd());
+	drag->setMimeData(data.release());
+	drag->exec(Qt::CopyAction);
+
+	// We don't receive mouseReleaseEvent when drag is finished.
+	ClickHandler::unpressed();
+	if (weak) {
+		weak->dragFinished().notify();
+	}
+}
+
+void MainWindow::checkAuthSession() {
+	if (AuthSession::Exists()) {
+		_controller = std::make_unique<Window::Controller>(this);
+	} else {
+		_controller = nullptr;
+	}
+}
+
+void MainWindow::setInactivePress(bool inactive) {
+	_wasInactivePress = inactive;
+	if (_wasInactivePress) {
+		_inactivePressTimer.callOnce(kInactivePressTimeout);
+	} else {
+		_inactivePressTimer.cancel();
+	}
+}
+
 MainWindow::~MainWindow() = default;
 
 } // namespace Window
diff --git a/Telegram/SourceFiles/window/main_window.h b/Telegram/SourceFiles/window/main_window.h
index 5f539d57f..d8625ea04 100644
--- a/Telegram/SourceFiles/window/main_window.h
+++ b/Telegram/SourceFiles/window/main_window.h
@@ -21,11 +21,13 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 #pragma once
 
 #include "window/window_title.h"
+#include "base/timer.h"
 
 class MediaView;
 
 namespace Window {
 
+class Controller;
 class TitleWidget;
 
 QImage LoadLogo();
@@ -38,6 +40,14 @@ class MainWindow : public QWidget, protected base::Subscriber {
 public:
 	MainWindow();
 
+	Window::Controller *controller() const {
+		return _controller.get();
+	}
+	void setInactivePress(bool inactive);
+	bool wasInactivePress() const {
+		return _wasInactivePress;
+	}
+
 	bool hideNoQuit();
 	void hideMediaview();
 
@@ -90,6 +100,14 @@ public:
 	}
 	virtual PeerData *ui_getPeerForMouseAction();
 
+	void launchDrag(std::unique_ptr<QMimeData> data);
+	base::Observable<void> &dragFinished() {
+		return _dragFinished;
+	}
+	base::Observable<void> &widgetGrabbed() {
+		return _widgetGrabbed;
+	}
+
 public slots:
 	bool minimizeToTray();
 	void updateGlobalMenu() {
@@ -154,11 +172,9 @@ private slots:
 		savePosition();
 	}
 	void onReActivate();
-	void updateIsActiveByTimer() {
-		updateIsActive(0);
-	}
 
 private:
+	void checkAuthSession();
 	void updatePalette();
 	void updateUnreadCounter();
 	void initSize();
@@ -168,6 +184,7 @@ private:
 	object_ptr<QTimer> _positionUpdatedTimer;
 	bool _positionInited = false;
 
+	std::unique_ptr<Window::Controller> _controller;
 	object_ptr<TitleWidget> _title = { nullptr };
 	object_ptr<TWidget> _body;
 	object_ptr<TWidget> _rightColumn = { nullptr };
@@ -175,11 +192,16 @@ private:
 	QIcon _icon;
 	QString _titleText;
 
-	object_ptr<QTimer> _isActiveTimer;
 	bool _isActive = false;
+	base::Timer _isActiveTimer;
+	bool _wasInactivePress = false;
+	base::Timer _inactivePressTimer;
 
 	object_ptr<MediaView> _mediaView = { nullptr };
 
+	base::Observable<void> _dragFinished;
+	base::Observable<void> _widgetGrabbed;
+
 };
 
 } // namespace Window
diff --git a/Telegram/SourceFiles/window/window_controller.h b/Telegram/SourceFiles/window/window_controller.h
index b4ff1460f..4fb289e44 100644
--- a/Telegram/SourceFiles/window/window_controller.h
+++ b/Telegram/SourceFiles/window/window_controller.h
@@ -68,9 +68,6 @@ public:
 	base::Observable<void> &floatPlayerAreaUpdated() {
 		return _floatPlayerAreaUpdated;
 	}
-	base::Observable<void> &widgetGrabbed() {
-		return _widgetGrabbed;
-	}
 
 	struct ColumnLayout {
 		int bodyWidth;
@@ -111,7 +108,6 @@ private:
 	GifPauseReasons _gifPauseReasons = { 0 };
 	base::Observable<void> _gifPauseLevelChanged;
 	base::Observable<void> _floatPlayerAreaUpdated;
-	base::Observable<void> _widgetGrabbed;
 
 	base::Variable<float64> _dialogsWidthRatio = { kDefaultDialogsWidthRatio };
 	base::Variable<bool> _dialogsListFocused = { false };