From aebe171f55f93fe5375f63b1b1027e39a2d38e3e Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Tue, 7 Oct 2014 21:57:57 +0400
Subject: [PATCH] 0.6.2 version, context menus fixed, image documents view in
 overlay added

---
 Telegram/PrepareLinux.sh                    |   4 +-
 Telegram/PrepareLinux32.sh                  |   4 +-
 Telegram/PrepareMac.sh                      |   4 +-
 Telegram/Resources/lang.txt                 |   4 +
 Telegram/Resources/style.txt                |   2 +
 Telegram/Setup.iss                          |   6 +-
 Telegram/SignWinSetup.bat                   |   2 +-
 Telegram/SourceFiles/config.h               |   7 +-
 Telegram/SourceFiles/dialogswidget.cpp      |   3 +-
 Telegram/SourceFiles/gui/contextmenu.cpp    |   4 +-
 Telegram/SourceFiles/gui/emoji_config.h     |  16 +-
 Telegram/SourceFiles/gui/text.cpp           | 363 +++++++++--------
 Telegram/SourceFiles/gui/text.h             |  22 +-
 Telegram/SourceFiles/history.cpp            |  44 ++-
 Telegram/SourceFiles/history.h              |  18 +-
 Telegram/SourceFiles/historywidget.cpp      |   4 +-
 Telegram/SourceFiles/localimageloader.cpp   |   1 +
 Telegram/SourceFiles/mainwidget.cpp         |  16 +-
 Telegram/SourceFiles/mediaview.cpp          | 410 +++++++++++++++-----
 Telegram/SourceFiles/mediaview.h            |  12 +-
 Telegram/SourceFiles/supporttl.cpp          |   4 +-
 Telegram/SourceFiles/supporttl.h            |   2 +-
 Telegram/SourceFiles/window.cpp             |   7 +
 Telegram/SourceFiles/window.h               |   1 +
 Telegram/Telegram.plist                     |   2 +-
 Telegram/Telegram.rc                        | Bin 5542 -> 5542 bytes
 Telegram/Telegram.xcodeproj/project.pbxproj |  12 +-
 27 files changed, 648 insertions(+), 326 deletions(-)

diff --git a/Telegram/PrepareLinux.sh b/Telegram/PrepareLinux.sh
index 8db6e1487..11ee7c41b 100755
--- a/Telegram/PrepareLinux.sh
+++ b/Telegram/PrepareLinux.sh
@@ -1,5 +1,5 @@
-AppVersionStr=0.6.1
-AppVersion=6001
+AppVersionStr=0.6.2
+AppVersion=6002
 
 if [ -d "./../Linux/Release/deploy/$AppVersionStr" ]; then
   echo "Deploy folder for version $AppVersionStr already exists!"
diff --git a/Telegram/PrepareLinux32.sh b/Telegram/PrepareLinux32.sh
index 9b15a215a..1901f4041 100755
--- a/Telegram/PrepareLinux32.sh
+++ b/Telegram/PrepareLinux32.sh
@@ -1,5 +1,5 @@
-AppVersionStr=0.6.1
-AppVersion=6001
+AppVersionStr=0.6.2
+AppVersion=6002
 
 if [ -d "./../Linux/Release/deploy/$AppVersionStr" ]; then
   echo "Deploy folder for version $AppVersionStr already exists!"
diff --git a/Telegram/PrepareMac.sh b/Telegram/PrepareMac.sh
index 037507202..0520b7d0f 100755
--- a/Telegram/PrepareMac.sh
+++ b/Telegram/PrepareMac.sh
@@ -1,5 +1,5 @@
-AppVersionStr=0.6.1
-AppVersion=6001
+AppVersionStr=0.6.2
+AppVersion=6002
 
 if [ -d "./../Mac/Release/deploy/$AppVersionStr" ]; then
   echo "Deploy folder for version $AppVersionStr already exists!"
diff --git a/Telegram/Resources/lang.txt b/Telegram/Resources/lang.txt
index 7f8252e1b..e20f1dbca 100644
--- a/Telegram/Resources/lang.txt
+++ b/Telegram/Resources/lang.txt
@@ -357,6 +357,9 @@ lng_context_open_audio: "Open Audio";
 lng_context_save_audio: "Save Audio As...";
 lng_context_open_document: "Open File";
 lng_context_save_document: "Save File As...";
+lng_context_forward_file: "Forward File";
+lng_context_delete_file: "Delete File";
+lng_context_close_file: "Close File";
 lng_context_copy_text: "Copy Message Text";
 lng_context_to_msg: "Go To Message";
 lng_context_forward_msg: "Forward Message";
@@ -436,6 +439,7 @@ lng_mediaview_single_photo: "Single Photo";
 lng_mediaview_group_photo: "Group Photo";
 lng_mediaview_profile_photo: "Profile Photo";
 lng_mediaview_n_of_count: "{n} of {count}";
+lng_mediaview_doc_image: "Document";
 
 // Mac specific
 
diff --git a/Telegram/Resources/style.txt b/Telegram/Resources/style.txt
index 7f31687bb..326e6560f 100644
--- a/Telegram/Resources/style.txt
+++ b/Telegram/Resources/style.txt
@@ -754,6 +754,7 @@ msgFont: font(fsize);
 msgNameFont: font(fsize semibold);
 msgServiceFont: font(fsize semibold);
 msgServiceNameFont: font(fsize semibold);
+msgServicePhotoWidth: 100px;
 msgDateFont: font(13px);
 msgMinWidth: 190px;
 msgPhotoSize: 30px;
@@ -1466,6 +1467,7 @@ medviewNavBarWidth: 120px;
 medviewTopSkip: 66px;
 medviewBottomSkip: 66px;
 medviewMainWidth: 600px;
+medviewControlsBgOpacity: 0.5;
 medviewLightOpacity: 0.7;
 medviewDarkOpacity: 0.8;
 medviewLightNav: 0.5;
diff --git a/Telegram/Setup.iss b/Telegram/Setup.iss
index 8f860e4c1..1175cca78 100644
--- a/Telegram/Setup.iss
+++ b/Telegram/Setup.iss
@@ -3,9 +3,9 @@
 
 #define MyAppShortName "Telegram"
 #define MyAppName "Telegram Desktop"
-#define MyAppVersion "0.6.1"
-#define MyAppVersionZero "0.6.1"
-#define MyAppFullVersion "0.6.1.0"
+#define MyAppVersion "0.6.2"
+#define MyAppVersionZero "0.6.2"
+#define MyAppFullVersion "0.6.2.0"
 #define MyAppPublisher "Telegram Messenger LLP"
 #define MyAppURL "https://tdesktop.com"
 #define MyAppExeName "Telegram.exe"
diff --git a/Telegram/SignWinSetup.bat b/Telegram/SignWinSetup.bat
index c7944ac55..052489e9e 100644
--- a/Telegram/SignWinSetup.bat
+++ b/Telegram/SignWinSetup.bat
@@ -1,3 +1,3 @@
 cd ..\Win32\Deploy
-call ..\..\..\TelegramPrivate\Sign.bat tsetup.0.6.1.exe
+call ..\..\..\TelegramPrivate\Sign.bat tsetup.0.6.2.exe
 cd ..\..\Telegram
diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h
index a5d249583..5ce1c37c1 100644
--- a/Telegram/SourceFiles/config.h
+++ b/Telegram/SourceFiles/config.h
@@ -17,8 +17,8 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
 */
 #pragma once
 
-static const int32 AppVersion = 6001;
-static const wchar_t *AppVersionStr = L"0.6.1";
+static const int32 AppVersion = 6002;
+static const wchar_t *AppVersionStr = L"0.6.2";
 
 static const wchar_t *AppNameOld = L"Telegram Win (Unofficial)";
 static const wchar_t *AppName = L"Telegram Desktop";
@@ -79,6 +79,9 @@ enum {
 	AudioVoiceMsgChannels = 2, // stereo
 	AudioVoiceMsgBufferSize = 1024 * 1024, // 1 Mb buffers
 	AudioVoiceMsgInMemory = 1024 * 1024, // 1 Mb audio is hold in memory and auto loaded
+
+	MediaViewImageSizeLimit = 10 * 1024 * 1024, // show up to 10mb jpg/png docs in mediaview
+	MaxZoomLevel = 7, // x8
 };
 
 #ifdef Q_OS_WIN
diff --git a/Telegram/SourceFiles/dialogswidget.cpp b/Telegram/SourceFiles/dialogswidget.cpp
index 51efd8bbf..9bde7816a 100644
--- a/Telegram/SourceFiles/dialogswidget.cpp
+++ b/Telegram/SourceFiles/dialogswidget.cpp
@@ -334,7 +334,7 @@ void DialogsListWidget::onPeerPhotoChanged(PeerData *peer) {
 }
 
 void DialogsListWidget::onFilterUpdate(QString newFilter, bool force) {
-	newFilter = textAccentFold(newFilter.trimmed().toLower());
+	newFilter = textSearchKey(newFilter);
 	if (newFilter != filter || force) {
 		QStringList f;
 		if (!newFilter.isEmpty()) {
@@ -466,6 +466,7 @@ void DialogsListWidget::onItemRemoved(HistoryItem *item) {
 	for (int i = 0; i < searchResults.size();) {
 		if (searchResults[i]->_item == item) {
 			searchResults.remove(i);
+			if (searchedCount > 0) --searchedCount;
 		} else {
 			++i;
 		}
diff --git a/Telegram/SourceFiles/gui/contextmenu.cpp b/Telegram/SourceFiles/gui/contextmenu.cpp
index bebf47243..0a6bafe2c 100644
--- a/Telegram/SourceFiles/gui/contextmenu.cpp
+++ b/Telegram/SourceFiles/gui/contextmenu.cpp
@@ -244,8 +244,8 @@ void ContextMenu::popup(const QPoint &p) {
 	if (w.y() + height() - st::dropdownPadding.bottom() > r.y() + r.height()) {
 		w.setY(p.y() - height() + st::dropdownPadding.bottom());
 	}
-	if (w.y() < 0) {
-		w.setY(0);
+	if (w.y() < r.y()) {
+		w.setY(r.y());
 	}
 	move(w);
 	showStart();
diff --git a/Telegram/SourceFiles/gui/emoji_config.h b/Telegram/SourceFiles/gui/emoji_config.h
index f8f1839c7..cbb61ca60 100644
--- a/Telegram/SourceFiles/gui/emoji_config.h
+++ b/Telegram/SourceFiles/gui/emoji_config.h
@@ -17,6 +17,8 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
 */
 #pragma once
 
+#include "gui/text.h"
+
 void initEmoji();
 EmojiPtr getEmoji(uint32 code);
 
@@ -38,7 +40,9 @@ inline bool emojiEdge(const QChar *ch) {
 
 inline QString replaceEmojis(const QString &text) {
 	QString result;
-	const QChar *emojiEnd = text.unicode(), *e = text.cend();
+	LinkRanges lnkRanges = textParseLinks(text);
+	int32 currentLink = 0, lnkCount = lnkRanges.size();
+	const QChar *emojiStart = text.unicode(), *emojiEnd = emojiStart, *e = text.cend();
 	bool canFindEmoji = true, consumePrevious = false;
 	for (const QChar *ch = emojiEnd; ch != e;) {
 		uint32 emojiCode = 0;
@@ -46,7 +50,15 @@ inline QString replaceEmojis(const QString &text) {
 		if (canFindEmoji) {
 			findEmoji(ch, e, newEmojiEnd, emojiCode);
 		}
-		if (emojiCode) {
+		
+		while (currentLink < lnkCount && ch >= lnkRanges[currentLink].from + lnkRanges[currentLink].len) {
+			++currentLink;
+		}
+		if (emojiCode &&
+		    (ch == emojiStart || !ch->isLetterOrNumber() || !(ch - 1)->isLetterOrNumber()) &&
+		    (newEmojiEnd == e || !newEmojiEnd->isLetterOrNumber() || newEmojiEnd == emojiStart || !(newEmojiEnd - 1)->isLetterOrNumber()) &&
+			(currentLink >= lnkCount || (ch < lnkRanges[currentLink].from && newEmojiEnd <= lnkRanges[currentLink].from) || (ch >= lnkRanges[currentLink].from + lnkRanges[currentLink].len && newEmojiEnd > lnkRanges[currentLink].from + lnkRanges[currentLink].len))
+		) {
 //			if (newEmojiEnd < e && newEmojiEnd->unicode() == ' ') ++newEmojiEnd;
 			if (result.isEmpty()) result.reserve(text.size());
 			if (ch > emojiEnd + (consumePrevious ? 1 : 0)) {
diff --git a/Telegram/SourceFiles/gui/text.cpp b/Telegram/SourceFiles/gui/text.cpp
index 852a959a7..e1a2f1c5b 100644
--- a/Telegram/SourceFiles/gui/text.cpp
+++ b/Telegram/SourceFiles/gui/text.cpp
@@ -265,14 +265,52 @@ QString textcmdStopColor() {
 	return result.append(TextCommand).append(QChar(TextCommandNoColor)).append(TextCommand);
 }
 
-class TextParser {
-	struct LinkRange {
-		LinkRange() : from(0), len(0) {
-		}
-		const QChar *from;
-		int32 len;
-	};
+const QChar *skipCommand(const QChar *from, const QChar *end, bool canLink = true) {
+	const QChar *result = from + 1;
+	if (*from != TextCommand || result >= end) return from;
 
+	ushort cmd = result->unicode();
+	++result;
+	if (result >= end) return from;
+
+	switch (cmd) {
+	case TextCommandBold:
+	case TextCommandNoBold:
+	case TextCommandItalic:
+	case TextCommandNoItalic:
+	case TextCommandUnderline:
+	case TextCommandNoUnderline:
+	case TextCommandNoColor:
+		break;
+
+	case TextCommandLinkIndex:
+		if (result->unicode() > 0x7FFF) return from;
+		++result;
+		break;
+
+	case TextCommandLinkText: {
+		ushort len = result->unicode();
+		if (len >= 4096 || !canLink) return from;
+		result += len + 1;
+	} break;
+
+	case TextCommandColor: {
+		const QChar *e = result + 4;
+		if (e >= end) return from;
+
+		for (; result < e; ++result) {
+			if (result->unicode() >= 256) return from;
+		}
+	} break;
+
+	case TextCommandSkipBlock:
+		result += 2;
+		break;
+	}
+	return (result < end && *result == TextCommand) ? (result + 1) : from;
+}
+
+class TextParser {
 public:
 	
 	static Qt::LayoutDirection stringDirection(const QString &str, int32 from, int32 to) {
@@ -301,133 +339,6 @@ public:
 		return Qt::LayoutDirectionAuto;
 	}
 
-	void prepareLinks() { // support emails and hashtags!
-		if (validProtocols.empty()) {
-			initLinkSets();
-		}
-		int32 len = src.size(), nextCmd = rich ? 0 : len;
-		const QChar *srcData = src.unicode();
-		for (int32 offset = 0; offset < len; ) {
-			if (nextCmd <= offset) {
-				for (nextCmd = offset; nextCmd < len; ++nextCmd) {
-					if (*(srcData + nextCmd) == TextCommand) {
-						break;
-					}
-				}
-			}
-			QRegularExpressionMatch mDomain = reDomain.match(src, offset);
-			QRegularExpressionMatch mExplicitDomain = reExplicitDomain.match(src, offset);
-			QRegularExpressionMatch mHashtag = reHashtag.match(src, offset);
-			if (!mDomain.hasMatch() && !mExplicitDomain.hasMatch() && !mHashtag.hasMatch()) break;
-
-			LinkRange link;
-			int32 domainOffset = mDomain.hasMatch() ? mDomain.capturedStart() : INT_MAX,
-			      domainEnd = mDomain.hasMatch() ? mDomain.capturedEnd() : INT_MAX,
-				  explicitDomainOffset = mExplicitDomain.hasMatch() ? mExplicitDomain.capturedStart() : INT_MAX,
-				  explicitDomainEnd = mExplicitDomain.hasMatch() ? mExplicitDomain.capturedEnd() : INT_MAX,
-			      hashtagOffset = mHashtag.hasMatch() ? mHashtag.capturedStart() : INT_MAX,
-			      hashtagEnd = mHashtag.hasMatch() ? mHashtag.capturedEnd() : INT_MAX;
-			if (mHashtag.hasMatch()) {
-				if (!mHashtag.capturedRef(1).isEmpty()) {
-					++hashtagOffset;
-				}
-				if (!mHashtag.capturedRef(2).isEmpty()) {
-					--hashtagEnd;
-				}
-			}
-			if (explicitDomainOffset < domainOffset) {
-				domainOffset = explicitDomainOffset;
-				domainEnd = explicitDomainEnd;
-				mDomain = mExplicitDomain;
-			}
-			if (hashtagOffset < domainOffset) {
-				if (hashtagOffset > nextCmd) {
-					const QChar *after = skipCommand(srcData + nextCmd, srcData + len);
-					if (after > srcData + nextCmd && hashtagOffset < (after - srcData)) {
-						nextCmd = offset = after - srcData;
-						continue;
-					}
-				}
-
-				link.from = start + hashtagOffset;
-				link.len = start + hashtagEnd - link.from;
-			} else {
-				if (domainOffset > nextCmd) {
-					const QChar *after = skipCommand(srcData + nextCmd, srcData + len);
-					if (after > srcData + nextCmd && domainOffset < (after - srcData)) {
-						nextCmd = offset = after - srcData;
-						continue;
-					}
-				}
-
-				QString protocol = mDomain.captured(1).toLower();
-				QString topDomain = mDomain.captured(3).toLower();
-
-				bool isProtocolValid = protocol.isEmpty() || validProtocols.contains(hashCrc32(protocol.constData(), protocol.size() * sizeof(QChar)));
-				bool isTopDomainValid = !protocol.isEmpty() || validTopDomains.contains(hashCrc32(topDomain.constData(), topDomain.size() * sizeof(QChar)));
-
-				if (!isProtocolValid || !isTopDomainValid) {
-					offset = domainEnd;
-					continue;
-				}
-
-				if (protocol.isEmpty() && domainOffset > offset + 1 && *(start + domainOffset - 1) == QChar('@')) {
-					QString forMailName = src.mid(offset, domainOffset - offset - 1);
-					QRegularExpressionMatch mMailName = reMailName.match(forMailName);
-					if (mMailName.hasMatch()) {
-						int32 mailOffset = offset + mMailName.capturedStart();
-						if (mailOffset < offset) {
-							mailOffset = offset;
-						}
-						link.from = start + mailOffset;
-						link.len = domainEnd - mailOffset;
-					}
-				}
-				if (!link.from || !link.len) {
-					link.from = start + domainOffset;
-
-					QStack<const QChar*> parenth;
-					const QChar *domainEnd = start + mDomain.capturedEnd(), *p = domainEnd;
-					for (; p < end; ++p) {
-						QChar ch(*p);
-						if (chIsLinkEnd(ch)) break; // link finished
-						if (chIsAlmostLinkEnd(ch)) {
-							const QChar *endTest = p + 1;
-							while (endTest < end && chIsAlmostLinkEnd(*endTest)) {
-								++endTest;
-							}
-							if (endTest >= end || chIsLinkEnd(*endTest)) {
-								break; // link finished at p
-							}
-							p = endTest;
-							ch = *p;
-						}
-						if (ch == '(' || ch == '[' || ch == '{' || ch == '<') {
-							parenth.push(p);
-						} else if (ch == ')' || ch == ']' || ch == '}' || ch == '>') {
-							if (parenth.isEmpty()) break;
-							const QChar *q = parenth.pop(), open(*q);
-							if ((ch == ')' && open != '(') || (ch == ']' && open != '[') || (ch == '}' && open != '{') || (ch == '>' && open != '<')) {
-								p = q;
-								break;
-							}
-						}
-					}
-					if (p > domainEnd) { // check, that domain ended
-						if (domainEnd->unicode() != '/') {
-							offset = domainEnd - start;
-							continue;
-						}
-					}
-					link.len = p - link.from;
-				}
-			}
-			lnkRanges.push_back(link);
-
-			offset = (link.from - start) + link.len;
-		}
-	}
-
 	void blockCreated() {
 		sumWidth += _t->_blocks.back()->f_width();
 		if (sumWidth.floor().toInt() > stopAfterWidth) {
@@ -500,53 +411,8 @@ public:
 		return true;
 	}
 	
-	const QChar *skipCommand(const QChar *from, const QChar *end) {
-		const QChar *result = from + 1;
-		if (*from != TextCommand || result >= end) return from;
-
-		ushort cmd = result->unicode();
-		++result;
-		if (result >= end) return from;
-
-		switch (cmd) {
-		case TextCommandBold:
-		case TextCommandNoBold:
-		case TextCommandItalic:
-		case TextCommandNoItalic:
-		case TextCommandUnderline:
-		case TextCommandNoUnderline:
-		case TextCommandNoColor:
-		break;
-
-		case TextCommandLinkIndex:
-			if (result->unicode() > 0x7FFF) return from;
-			++result;
-		break;
-
-		case TextCommandLinkText: {
-			ushort len = result->unicode();
-			if (len >= 4096 || links.size() >= 0x7FFF) return from;
-			result += len + 1;
-		} break;
-
-		case TextCommandColor: {
-			const QChar *e = result + 4;
-			if (e >= end) return from;
-			
-			for (; result < e; ++result) {
-				if (result->unicode() >= 256) return from;
-			}
-		} break;
-
-		case TextCommandSkipBlock:
-			result += 2;
-		break;
-		}
-		return (result < end && *result == TextCommand) ? (result + 1) : from;
-	}
-
 	bool readCommand() {
-		const QChar *afterCmd = skipCommand(ptr, end);
+		const QChar *afterCmd = skipCommand(ptr, end, links.size() < 0x7FFF);
 		if (afterCmd == ptr) {
 			return false;
 		}
@@ -724,7 +590,7 @@ public:
 		end = start + src.size();
 
 		if (options.flags & TextParseLinks) {
-			prepareLinks();
+			lnkRanges = textParseLinks(src, rich);
 		}
 
 		while (start != end && chIsTrimmed(*start, rich)) {
@@ -793,7 +659,6 @@ private:
 	const QChar *start, *end, *ptr;
 	bool rich, multiline;
 
-	typedef QVector<LinkRange> LinkRanges;
 	LinkRanges lnkRanges;
 	const LinkRange *waitingLink, *linksEnd;
 
@@ -4102,3 +3967,137 @@ QString textAccentFold(const QString &text) {
 	}
 	return (i < result.size()) ? result.mid(0, i) : result;
 }
+
+QString textSearchKey(const QString &text) {
+	return textAccentFold(text.trimmed().toLower());
+}
+
+LinkRanges textParseLinks(const QString &text, bool rich) {
+	LinkRanges lnkRanges;
+
+	if (validProtocols.empty()) {
+		initLinkSets();
+	}
+	int32 len = text.size(), nextCmd = rich ? 0 : len;
+	const QChar *start = text.unicode(), *end = start + text.size();
+	for (int32 offset = 0, matchOffset = offset; offset < len;) {
+		if (nextCmd <= offset) {
+			for (nextCmd = offset; nextCmd < len; ++nextCmd) {
+				if (*(start + nextCmd) == TextCommand) {
+					break;
+				}
+			}
+		}
+		QRegularExpressionMatch mDomain = reDomain.match(text, matchOffset);
+		QRegularExpressionMatch mExplicitDomain = reExplicitDomain.match(text, matchOffset);
+		QRegularExpressionMatch mHashtag = reHashtag.match(text, matchOffset);
+		if (!mDomain.hasMatch() && !mExplicitDomain.hasMatch() && !mHashtag.hasMatch()) break;
+
+		LinkRange link;
+		int32 domainOffset = mDomain.hasMatch() ? mDomain.capturedStart() : INT_MAX,
+			domainEnd = mDomain.hasMatch() ? mDomain.capturedEnd() : INT_MAX,
+			explicitDomainOffset = mExplicitDomain.hasMatch() ? mExplicitDomain.capturedStart() : INT_MAX,
+			explicitDomainEnd = mExplicitDomain.hasMatch() ? mExplicitDomain.capturedEnd() : INT_MAX,
+			hashtagOffset = mHashtag.hasMatch() ? mHashtag.capturedStart() : INT_MAX,
+			hashtagEnd = mHashtag.hasMatch() ? mHashtag.capturedEnd() : INT_MAX;
+		if (mHashtag.hasMatch()) {
+			if (!mHashtag.capturedRef(1).isEmpty()) {
+				++hashtagOffset;
+			}
+			if (!mHashtag.capturedRef(2).isEmpty()) {
+				--hashtagEnd;
+			}
+		}
+		if (explicitDomainOffset < domainOffset) {
+			domainOffset = explicitDomainOffset;
+			domainEnd = explicitDomainEnd;
+			mDomain = mExplicitDomain;
+		}
+		if (hashtagOffset < domainOffset) {
+			if (hashtagOffset > nextCmd) {
+				const QChar *after = skipCommand(start + nextCmd, start + len);
+				if (after > start + nextCmd && hashtagOffset < (after - start)) {
+					nextCmd = offset = matchOffset = after - start;
+					continue;
+				}
+			}
+
+			link.from = start + hashtagOffset;
+			link.len = start + hashtagEnd - link.from;
+		} else {
+			if (domainOffset > nextCmd) {
+				const QChar *after = skipCommand(start + nextCmd, start + len);
+				if (after > start + nextCmd && domainOffset < (after - start)) {
+					nextCmd = offset = matchOffset = after - start;
+					continue;
+				}
+			}
+
+			QString protocol = mDomain.captured(1).toLower();
+			QString topDomain = mDomain.captured(3).toLower();
+
+			bool isProtocolValid = protocol.isEmpty() || validProtocols.contains(hashCrc32(protocol.constData(), protocol.size() * sizeof(QChar)));
+			bool isTopDomainValid = !protocol.isEmpty() || validTopDomains.contains(hashCrc32(topDomain.constData(), topDomain.size() * sizeof(QChar)));
+
+			if (protocol.isEmpty() && domainOffset > offset + 1 && *(start + domainOffset - 1) == QChar('@')) {
+				QString forMailName = text.mid(offset, domainOffset - offset - 1);
+				QRegularExpressionMatch mMailName = reMailName.match(forMailName);
+				if (mMailName.hasMatch()) {
+					int32 mailOffset = offset + mMailName.capturedStart();
+					if (mailOffset < offset) {
+						mailOffset = offset;
+					}
+					link.from = start + mailOffset;
+					link.len = domainEnd - mailOffset;
+				}
+			}
+			if (!link.from || !link.len) {
+				if (!isProtocolValid || !isTopDomainValid) {
+					matchOffset = domainEnd;
+					continue;
+				}
+				link.from = start + domainOffset;
+
+				QStack<const QChar*> parenth;
+				const QChar *domainEnd = start + mDomain.capturedEnd(), *p = domainEnd;
+				for (; p < end; ++p) {
+					QChar ch(*p);
+					if (chIsLinkEnd(ch)) break; // link finished
+					if (chIsAlmostLinkEnd(ch)) {
+						const QChar *endTest = p + 1;
+						while (endTest < end && chIsAlmostLinkEnd(*endTest)) {
+							++endTest;
+						}
+						if (endTest >= end || chIsLinkEnd(*endTest)) {
+							break; // link finished at p
+						}
+						p = endTest;
+						ch = *p;
+					}
+					if (ch == '(' || ch == '[' || ch == '{' || ch == '<') {
+						parenth.push(p);
+					} else if (ch == ')' || ch == ']' || ch == '}' || ch == '>') {
+						if (parenth.isEmpty()) break;
+						const QChar *q = parenth.pop(), open(*q);
+						if ((ch == ')' && open != '(') || (ch == ']' && open != '[') || (ch == '}' && open != '{') || (ch == '>' && open != '<')) {
+							p = q;
+							break;
+						}
+					}
+				}
+				if (p > domainEnd) { // check, that domain ended
+					if (domainEnd->unicode() != '/') {
+						matchOffset = domainEnd - start;
+						continue;
+					}
+				}
+				link.len = p - link.from;
+			}
+		}
+		lnkRanges.push_back(link);
+
+		offset = matchOffset = (link.from - start) + link.len;
+	}
+
+	return lnkRanges;
+}
diff --git a/Telegram/SourceFiles/gui/text.h b/Telegram/SourceFiles/gui/text.h
index a404f8e2a..a566b2605 100644
--- a/Telegram/SourceFiles/gui/text.h
+++ b/Telegram/SourceFiles/gui/text.h
@@ -17,6 +17,22 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
 */
 #pragma once
 
+// text preprocess
+QString textClean(const QString &text);
+QString textRichPrepare(const QString &text);
+QString textOneLine(const QString &text, bool trim = true, bool rich = false);
+QString textAccentFold(const QString &text);
+QString textSearchKey(const QString &text);
+
+struct LinkRange {
+	LinkRange() : from(0), len(0) {
+	}
+	const QChar *from;
+	int32 len;
+};
+typedef QVector<LinkRange> LinkRanges;
+LinkRanges textParseLinks(const QString &text, bool rich = false);
+
 #include "gui/emoji_config.h"
 
 #include "../../../QtStatic/qtbase/src/gui/text/qfontengine_p.h"
@@ -438,12 +454,6 @@ inline void textstyleRestore() {
 	textstyleSet(0);
 }
 
-// text preprocess
-QString textClean(const QString &text);
-QString textRichPrepare(const QString &text);
-QString textOneLine(const QString &text, bool trim = true, bool rich = false);
-QString textAccentFold(const QString &text);
-
 // textlnk
 void textlnkOver(const TextLinkPtr &lnk);
 const TextLinkPtr &textlnkOver();
diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp
index fc470dc51..fe00b4f29 100644
--- a/Telegram/SourceFiles/history.cpp
+++ b/Telegram/SourceFiles/history.cpp
@@ -297,6 +297,7 @@ void VideoOpenLink::onClick(Qt::MouseButton button) const {
 	QString filename = saveFileName(lang(lng_save_video), qsl("MOV Video (*.mov);;All files (*.*)"), qsl("video"), qsl(".mov"), false);
 	if (!filename.isEmpty()) {
 		data->openOnSave = 1;
+		data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->id : 0;
 		data->save(filename);
 	}
 }
@@ -316,7 +317,10 @@ void VideoSaveLink::doSave(bool forceSavingAs) const {
 		QString filename = saveFileName(lang(lng_save_video), qsl("MOV Video (*.mov);;All files (*.*)"), qsl("video"), name, forceSavingAs, alreadyDir);
 		if (!filename.isEmpty()) {
 			if (forceSavingAs) data->cancel();
-			if (!already.isEmpty()) data->openOnSave = -1;
+			if (!already.isEmpty()) {
+				data->openOnSave = -1;
+				data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->id : 0;
+			}
 			data->save(filename);
 		}
 	}
@@ -369,6 +373,7 @@ void AudioOpenLink::onClick(Qt::MouseButton button) const {
 	QString filename = saveFileName(lang(lng_save_audio), qsl("OGG Opus Audio (*.ogg);;All files (*.*)"), qsl("audio"), qsl(".ogg"), false);
 	if (!filename.isEmpty()) {
 		data->openOnSave = 1;
+		data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->id : 0;
 		data->save(filename);
 	}
 }
@@ -388,7 +393,10 @@ void AudioSaveLink::doSave(bool forceSavingAs) const {
 		QString filename = saveFileName(lang(lng_save_audio), qsl("OGG Opus Audio (*.ogg);;All files (*.*)"), qsl("audio"), name, forceSavingAs, alreadyDir);
 		if (!filename.isEmpty()) {
 			if (forceSavingAs) data->cancel();
-			if (!already.isEmpty()) data->openOnSave = -1;
+			if (!already.isEmpty()) {
+				data->openOnSave = -1;
+				data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->id : 0;
+			}
 			data->save(filename);
 		}
 	}
@@ -420,7 +428,21 @@ void DocumentOpenLink::onClick(Qt::MouseButton button) const {
 
 	QString already = data->already(true);
 	if (!already.isEmpty()) {
-        psOpenFile(already);
+		bool showInMediaView = false;
+		if (data->size < MediaViewImageSizeLimit) {
+			QMimeType mime = QMimeDatabase().mimeTypeForName(data->mime);
+			QString name = mime.name().toLower(), fname = already.toLower();
+			if (name == qsl("image/jpeg") || name == qsl("image/jpg") || name == qsl("image/png")) {
+				showInMediaView = true;
+			} else if (fname.endsWith(qsl(".jpeg")) || fname.endsWith(qsl(".jpg")) || fname.endsWith(qsl(".png"))) {
+				showInMediaView = name.isEmpty();
+			}
+		}
+		if (showInMediaView) {
+			App::wnd()->showDocument(data, App::hoveredLinkItem());
+		} else {
+			psOpenFile(already);
+		}
 		return;
 	}
 	
@@ -443,6 +465,7 @@ void DocumentOpenLink::onClick(Qt::MouseButton button) const {
 	QString filename = saveFileName(lang(lng_save_document), filter, qsl("doc"), name, false);
 	if (!filename.isEmpty()) {
 		data->openOnSave = 1;
+		data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->id : 0;
 		data->save(filename);
 	}
 }
@@ -475,7 +498,10 @@ void DocumentSaveLink::doSave(bool forceSavingAs) const {
 		QString filename = saveFileName(lang(lng_save_document), filter, qsl("doc"), name, forceSavingAs, alreadyDir);
 		if (!filename.isEmpty()) {
 			if (forceSavingAs) data->cancel();
-			if (!already.isEmpty()) data->openOnSave = -1;
+			if (!already.isEmpty()) {
+				data->openOnSave = -1;
+				data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->id : 0;
+			}
 			data->save(filename);
 		}
 	}
@@ -2644,7 +2670,7 @@ int32 HistoryDocument::resize(int32 width) {
 }
 
 const QString HistoryDocument::inDialogsText() const {
-	return lang(lng_in_dlg_document);
+	return data->name.isEmpty() ? lang(lng_in_dlg_document) : data->name;
 }
 
 bool HistoryDocument::hasPoint(int32 x, int32 y, int32 width) const {
@@ -3237,12 +3263,14 @@ void HistoryMessage::drawInDialog(QPainter &p, const QRect &r, bool act, const H
 	if (cacheFor != this) {
 		cacheFor = this;
 		QString msg(_media ? _media->inDialogsText() : _text.original(0, 0xFFFF, false));
-		TextCustomTagsMap custom;
 		if (_history->peer->chat || out()) {
+			TextCustomTagsMap custom;
 			custom.insert(QChar('c'), qMakePair(textcmdStartLink(1), textcmdStopLink()));
 			msg = lang(lng_message_with_from).replace(qsl("{from}"), textRichPrepare((_from == App::self()) ? lang(lng_from_you) : _from->firstName)).replace(qsl("{message}"), textRichPrepare(msg));
+			cache.setRichText(st::dlgHistFont, msg, _textDlgOptions, custom);
+		} else {
+			cache.setText(st::dlgHistFont, msg, _textDlgOptions);
 		}
-		cache.setRichText(st::dlgHistFont, msg, _textDlgOptions, custom);
 	}
 	if (r.width()) {
 		textstyleSet(&(act ? st::dlgActiveTextStyle : st::dlgTextStyle));
@@ -3488,7 +3516,7 @@ QString HistoryServiceMsg::messageByAction(const MTPmessageAction &action, TextL
 	case mtpc_messageActionChatEditPhoto: {
 		const MTPDmessageActionChatEditPhoto &d(action.c_messageActionChatEditPhoto());
 		if (d.vphoto.type() == mtpc_photo) {
-			_media = new HistoryPhoto(history()->peer, d.vphoto.c_photo(), 100);
+			_media = new HistoryPhoto(history()->peer, d.vphoto.c_photo(), st::msgServicePhotoWidth);
 		}
 		return lang(lng_action_changed_photo);
 	} break;
diff --git a/Telegram/SourceFiles/history.h b/Telegram/SourceFiles/history.h
index 2b7e55d49..1c6f9af8a 100644
--- a/Telegram/SourceFiles/history.h
+++ b/Telegram/SourceFiles/history.h
@@ -231,7 +231,7 @@ enum FileStatus {
 
 struct VideoData {
 	VideoData(const VideoId &id, const uint64 &access = 0, int32 user = 0, int32 date = 0, int32 duration = 0, int32 w = 0, int32 h = 0, const ImagePtr &thumb = ImagePtr(), int32 dc = 0, int32 size = 0) :
-		id(id), access(access), user(user), date(date), duration(duration), w(w), h(h), thumb(thumb), dc(dc), size(size), status(FileReady), uploadOffset(0), fileType(0), openOnSave(0), loader(0) {
+		id(id), access(access), user(user), date(date), duration(duration), w(w), h(h), thumb(thumb), dc(dc), size(size), status(FileReady), uploadOffset(0), fileType(0), openOnSave(0), openOnSaveMsgId(0), loader(0) {
 		memset(md5, 0, sizeof(md5));
 	}
 	void forget() {
@@ -251,7 +251,7 @@ struct VideoData {
 		fileName = QString();
 		modDate = QDateTime();
 		if (!beforeDownload) {
-			openOnSave = 0;
+			openOnSave = openOnSaveMsgId = 0;
 		}
 	}
 
@@ -292,7 +292,7 @@ struct VideoData {
 	int32 uploadOffset;
 
 	mtpTypeId fileType;
-	int32 openOnSave;
+	int32 openOnSave, openOnSaveMsgId;
 	mtpFileLoader *loader;
 	QString fileName;
 	QDateTime modDate;
@@ -335,7 +335,7 @@ public:
 
 struct AudioData {
 	AudioData(const AudioId &id, const uint64 &access = 0, int32 user = 0, int32 date = 0, int32 duration = 0, int32 dc = 0, int32 size = 0) : 
-		id(id), access(access), user(user), date(date), duration(duration), dc(dc), size(size), status(FileReady), uploadOffset(0), openOnSave(0), loader(0) {
+		id(id), access(access), user(user), date(date), duration(duration), dc(dc), size(size), status(FileReady), uploadOffset(0), openOnSave(0), openOnSaveMsgId(0), loader(0) {
 		memset(md5, 0, sizeof(md5));
 	}
 	void forget() {
@@ -354,7 +354,7 @@ struct AudioData {
 		fileName = QString();
 		modDate = QDateTime();
 		if (!beforeDownload) {
-			openOnSave = 0;
+			openOnSave = openOnSaveMsgId = 0;
 		}
 	}
 
@@ -393,7 +393,7 @@ struct AudioData {
 	FileStatus status;
 	int32 uploadOffset;
 
-	int32 openOnSave;
+	int32 openOnSave, openOnSaveMsgId;
 	mtpFileLoader *loader;
 	QString fileName;
 	QDateTime modDate;
@@ -437,7 +437,7 @@ public:
 
 struct DocumentData {
 	DocumentData(const DocumentId &id, const uint64 &access = 0, int32 user = 0, int32 date = 0, const QString &name = QString(), const QString &mime = QString(), const ImagePtr &thumb = ImagePtr(), int32 dc = 0, int32 size = 0) :
-		id(id), access(access), user(user), date(date), name(name), mime(mime), thumb(thumb), dc(dc), size(size), status(FileReady), uploadOffset(0), openOnSave(0), loader(0) {
+		id(id), access(access), user(user), date(date), name(name), mime(mime), thumb(thumb), dc(dc), size(size), status(FileReady), uploadOffset(0), openOnSave(0), openOnSaveMsgId(0), loader(0) {
 		memset(md5, 0, sizeof(md5));
 	}
 	void forget() {
@@ -457,7 +457,7 @@ struct DocumentData {
 		fileName = QString();
 		modDate = QDateTime();
 		if (!beforeDownload) {
-			openOnSave = 0;
+			openOnSave = openOnSaveMsgId = 0;
 		}
 	}
 
@@ -496,7 +496,7 @@ struct DocumentData {
 	FileStatus status;
 	int32 uploadOffset;
 
-	int32 openOnSave;
+	int32 openOnSave, openOnSaveMsgId;
 	mtpFileLoader *loader;
 	QString fileName;
 	QDateTime modDate;
diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp
index bea129b30..5fef45fc0 100644
--- a/Telegram/SourceFiles/historywidget.cpp
+++ b/Telegram/SourceFiles/historywidget.cpp
@@ -3067,7 +3067,7 @@ void HistoryWidget::keyPressEvent(QKeyEvent *e) {
 }
 
 void HistoryWidget::onFieldTabbed() {
-	QString v = _field.getText(), t = supportTemplate(v.trimmed());
+	QString v = _field.getText(), t = supportTemplate(v);
 	if (!t.isEmpty()) {
 		bool isImg = t.startsWith(qsl("img:")), isFile = t.startsWith(qsl("file:")), isContact = t.startsWith(qsl("contact:"));
 		if (isImg || isFile) {
@@ -3098,7 +3098,7 @@ void HistoryWidget::onFieldTabbed() {
 			if (data.size() > 1) {
 				_field.setPlainText(text);
 
-				QString phone = data.at(0).trimmed(), fname = data.at(1).trimmed(), lname = (data.size() > 2) ? data.at(2).trimmed() : QString();
+				QString phone = data.at(0).trimmed(), fname = data.at(1).trimmed(), lname = (data.size() > 2) ? static_cast<QStringList>(data.mid(2)).join(QChar(' ')).trimmed() : QString();
 				shareContactConfirmation(phone, fname, lname, !text.isEmpty());
 			}
 		} else {
diff --git a/Telegram/SourceFiles/localimageloader.cpp b/Telegram/SourceFiles/localimageloader.cpp
index cd3c92c12..07c6b73f9 100644
--- a/Telegram/SourceFiles/localimageloader.cpp
+++ b/Telegram/SourceFiles/localimageloader.cpp
@@ -103,6 +103,7 @@ void LocalImageLoaderPrivate::prepareImages() {
 		if (type == ToPrepareDocument) {
 			filename = filedialogDefaultName(qsl("image"), qsl(".png"), QString(), true);
 			QMimeType mimeType = QMimeDatabase().mimeTypeForName("image/png");
+			mime = mimeType.name();
 			data = QByteArray();
 			{
 				QBuffer b(&data);
diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp
index 67b32f953..ac648b70e 100644
--- a/Telegram/SourceFiles/mainwidget.cpp
+++ b/Telegram/SourceFiles/mainwidget.cpp
@@ -907,7 +907,21 @@ void MainWidget::documentLoadProgress(mtpFileLoader *loader) {
 			document->finish();
 			QString already = document->already();
 			if (!already.isEmpty() && document->openOnSave) {
-				psOpenFile(already, document->openOnSave < 0);
+				bool showInMediaView = false;
+				if (document->openOnSave > 0 && document->size < MediaViewImageSizeLimit) {
+					QMimeType mime = QMimeDatabase().mimeTypeForName(document->mime);
+					QString name = mime.name().toLower(), fname = already.toLower();;
+					if (name == qsl("image/jpeg") || name == qsl("image/png")) {
+						showInMediaView = true;
+					} else if (fname.endsWith(qsl(".jpeg")) || fname.endsWith(qsl(".jpg")) || fname.endsWith(qsl(".png"))) {
+						showInMediaView = name.isEmpty();
+					}
+				}
+				if (showInMediaView) {
+					App::wnd()->showDocument(document, App::histItemById(document->openOnSaveMsgId));
+				} else {
+					psOpenFile(already, document->openOnSave < 0);
+				}
 			}
 		}
 	}
diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp
index 324b4b8b9..8044a31fd 100644
--- a/Telegram/SourceFiles/mediaview.cpp
+++ b/Telegram/SourceFiles/mediaview.cpp
@@ -25,7 +25,8 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
 #include "gui/filedialog.h"
 
 MediaView::MediaView() : TWidget(App::wnd()),
-_photo(0), _leftNavVisible(false), _rightNavVisible(false), _animStarted(getms()), _maxWidth(0), _maxHeight(0), _x(0), _y(0), _w(0), _full(-1),
+_photo(0), _doc(0), _leftNavVisible(false), _rightNavVisible(false), _animStarted(getms()), _maxWidth(0), _maxHeight(0), _width(0),
+_x(0), _y(0), _w(0), _h(0), _xStart(0), _yStart(0), _zoom(0), _pressed(false), _dragging(0), _full(-1),
 _history(0), _peer(0), _user(0), _from(0), _index(-1), _msgid(0), _loadRequest(0), _over(OverNone), _down(OverNone), _lastAction(-st::medviewDeltaFromLastAction, -st::medviewDeltaFromLastAction),
 _close(this, lang(lng_mediaview_close), st::medviewButton),
 _save(this, lang(lng_mediaview_save), st::medviewButton),
@@ -66,6 +67,11 @@ void MediaView::moveToScreen() {
 	_maxHeight = _avail.height() - st::medviewTopSkip - st::medviewBottomSkip;
 	_leftNav = QRect(0, 0, st::medviewNavBarWidth, height());
 	_rightNav = QRect(width() - st::medviewNavBarWidth, 0, st::medviewNavBarWidth, height());
+
+	int32 w = st::medviewMainWidth + (st::medviewTopSkip - _save.height()), l = _avail.x() + (_avail.width() - w) / 2;
+	_topActions = QRect(l, _avail.y(), w, st::medviewTopSkip);
+	_bottomActions = QRect(l, _avail.y() + _avail.height() - st::medviewBottomSkip, w, st::medviewBottomSkip);
+
 	_close.move(_avail.x() + (_avail.width() + st::medviewMainWidth) / 2 - _close.width(), _avail.y() + (st::medviewTopSkip - _close.height()) / 2);
 	_save.move(_avail.x() + (_avail.width() - st::medviewMainWidth) / 2, _avail.y() + (st::medviewTopSkip - _save.height()) / 2);
 	_delete.move(_avail.x() + (_avail.width() + st::medviewMainWidth) / 2 - _delete.width(), _avail.y() + _avail.height() - (st::medviewTopSkip + _delete.height()) / 2);
@@ -73,6 +79,7 @@ void MediaView::moveToScreen() {
 }
 
 void MediaView::mediaOverviewUpdated(PeerData *peer) {
+	if (!_photo) return;
 	if (_history && _history->peer == peer) {
 		_index = -1;
 		for (int i = 0, l = _history->_overview[OverviewPhotos].size(); i < l; ++i) {
@@ -104,10 +111,10 @@ void MediaView::changingMsgId(HistoryItem *row, MsgId newId) {
 }
 
 void MediaView::updateControls() {
-	if (!_photo) return;
+	if (!_photo && !_doc) return;
 
 	_close.show();
-	if (_photo->full->loaded()) {
+	if (_photo && _photo->full->loaded() || _doc && !_doc->already(true).isEmpty()) {
 		_save.show();
 	} else {
 		_save.hide();
@@ -122,13 +129,13 @@ void MediaView::updateControls() {
 		_delete.show();
 	} else {
 		_forward.hide();
-		if ((App::self() && _photo && App::self()->photoId == _photo->id) || (_photo->chat && _photo->chat->photoId == _photo->id)) {
+		if (_photo && ((App::self() && App::self()->photoId == _photo->id) || (_photo->chat && _photo->chat->photoId == _photo->id))) {
 			_delete.show();
 		} else {
 			_delete.hide();
 		}
 	}
-	QDateTime d(date(_photo->date)), dNow(date(unixtime()));
+	QDateTime d(date(_photo ? _photo->date : _doc->date)), dNow(date(unixtime()));
 	if (d.date() == dNow.date()) {
 		_dateText = lang(lng_status_lastseen_today).replace(qsl("{time}"), d.time().toString(qsl("hh:mm")));
 	} else if (d.date().addDays(1) == dNow.date()) {
@@ -143,8 +150,8 @@ void MediaView::updateControls() {
 	_nameNav = QRect(_forward.x() + _forward.width() + (maxWidth - nameWidth) / 2, _forward.y() + st::medviewNameTop, nameWidth, st::msgNameFont->height);
 	_dateNav = QRect(_forward.x() + _forward.width() + (maxWidth - dateWidth) / 2, _forward.y() + st::medviewDateTop, dateWidth, st::medviewDateFont->height);
 	updateHeader();
-	_leftNavVisible = (_index > 0 || (_index == 0 && _history && _history->_overview[OverviewPhotos].size() < _history->_overviewCount[OverviewPhotos]));
-	_rightNavVisible = (_index >= 0 && (
+	_leftNavVisible = _photo && (_index > 0 || (_index == 0 && _history && _history->_overview[OverviewPhotos].size() < _history->_overviewCount[OverviewPhotos]));
+	_rightNavVisible = _photo && (_index >= 0 && (
 		(_history && _index + 1 < _history->_overview[OverviewPhotos].size()) ||
 		(_user && (_index + 1 < _user->photos.size() || _index + 1 < _user->photosCount))));
 	updateOver(mapFromGlobal(QCursor::pos()));
@@ -183,16 +190,34 @@ void MediaView::onClose() {
 }
 
 void MediaView::onSave() {
-	if (!_photo || !_photo->full->loaded()) return;
+	if (_doc) {
+		QString cur = _doc->already(true), file;
+		if (cur.isEmpty()) {
+			_save.hide();
+			return;
+		}
+		if (filedialogGetSaveFile(file, lang(lng_save_photo), qsl("JPEG Image (*.jpg);;All files (*.*)"), cur)) {
+			if (!file.isEmpty() && file != cur) {
+				QFile(cur).copy(file);
+			}
+		}
+	} else {
+		if (!_photo || !_photo->full->loaded()) return;
 
-	QString file;
-	if (filedialogGetSaveFile(file, lang(lng_save_photo), qsl("JPEG Image (*.jpg);;All files (*.*)"), filedialogDefaultName(qsl("photo"), qsl(".jpg")))) {
-		if (!file.isEmpty()) {
-			_photo->full->pix().toImage().save(file, "JPG");
+		QString file;
+		if (filedialogGetSaveFile(file, lang(lng_save_photo), qsl("JPEG Image (*.jpg);;All files (*.*)"), filedialogDefaultName(qsl("photo"), qsl(".jpg")))) {
+			if (!file.isEmpty()) {
+				_photo->full->pix().toImage().save(file, "JPG");
+			}
 		}
 	}
 }
 
+void MediaView::onShowInFolder() {
+	QString already(_doc->already(true));
+	if (!already.isEmpty()) psShowInFolder(already);
+}
+
 void MediaView::onForward() {
 	HistoryItem *item = App::histItemById(_msgid);
 	if (!_msgid || !item) return;
@@ -224,9 +249,13 @@ void MediaView::onDelete() {
 }
 
 void MediaView::onCopy() {
-	if (!_photo || !_photo->full->loaded()) return;
+	if (_doc) {
+		QApplication::clipboard()->setPixmap(_current);
+	} else {
+		if (!_photo || !_photo->full->loaded()) return;
 
-	QApplication::clipboard()->setPixmap(_photo->full->pix());
+		QApplication::clipboard()->setPixmap(_photo->full->pix());
+	}
 }
 
 void MediaView::showPhoto(PhotoData *photo, HistoryItem *context) {
@@ -236,6 +265,9 @@ void MediaView::showPhoto(PhotoData *photo, HistoryItem *context) {
 
 	_loadRequest = 0;
 	_over = OverNone;
+	_pressed = false;
+	_dragging = 0;
+	setCursor(style::cur_default);
 	if (!_animations.isEmpty()) {
 		_animations.clear();
 		anim::stop(this);
@@ -297,33 +329,78 @@ void MediaView::showPhoto(PhotoData *photo, PeerData *context) {
 	preloadPhotos(0);
 }
 
+void MediaView::showDocument(DocumentData *doc, HistoryItem *context) {
+	_photo = 0;
+	_history = context ? context->history() : 0;
+	_peer = 0;
+	_user = 0;
+	_zoom = 0;
+	_msgid = context ? context->id : 0;
+	_index = -1;
+	_loadRequest = 0;
+	_over = OverNone;
+	_pressed = false;
+	_dragging = 0;
+	setCursor(style::cur_default);
+	if (!_animations.isEmpty()) {
+		_animations.clear();
+		anim::stop(this);
+	}
+	if (!_animOpacities.isEmpty()) _animOpacities.clear();
+	setCursor(style::cur_default);
+
+	QString name = doc->already();
+	_current = name.isEmpty() ? QPixmap() : QPixmap(name);
+	_current.setDevicePixelRatio(cRetinaFactor());
+	_doc = doc;
+	_down = OverNone;
+	if (isHidden()) {
+		moveToScreen();
+	}
+	_w = _current.width() / cIntRetinaFactor();
+	_h = _current.height() / cIntRetinaFactor();
+	_x = _avail.x() + (_avail.width() - _w) / 2;
+	_y = _avail.y() + (_avail.height() - _h) / 2;
+	_width = _w;
+	_from = App::user(_doc->user);
+	_full = 1;
+	updateControls();
+	if (isHidden()) {
+		psUpdateOverlayed(this);
+		show();
+	}
+}
+
 void MediaView::showPhoto(PhotoData *photo) {
 	_photo = photo;
+	_doc = 0;
+	_zoom = 0;
 	MTP::clearLoaderPriorities();
 	_photo->full->load();
 	_full = -1;
 	_current = QPixmap();
-	_w = photo->full->width();
 	_down = OverNone;
-	int h = photo->full->height();
+	_w = photo->full->width();
+	_h = photo->full->height();
 	switch (cScale()) {
-	case dbisOneAndQuarter: _w = qRound(float64(_w) * 1.25 - 0.01); h = qRound(float64(h) * 1.25 - 0.01); break;
-	case dbisOneAndHalf: _w = qRound(float64(_w) * 1.5 - 0.01); h = qRound(float64(h) * 1.5 - 0.01); break;
-	case dbisTwo: _w *= 2; h *= 2; break;
+	case dbisOneAndQuarter: _w = qRound(float64(_w) * 1.25 - 0.01); _h = qRound(float64(_h) * 1.25 - 0.01); break;
+	case dbisOneAndHalf: _w = qRound(float64(_w) * 1.5 - 0.01); _h = qRound(float64(_h) * 1.5 - 0.01); break;
+	case dbisTwo: _w *= 2; _h *= 2; break;
 	}
 	if (isHidden()) {
 		moveToScreen();
 	}
 	if (_w > _maxWidth) {
-		h = qRound(h * _maxWidth / float64(_w));
+		_h = qRound(_h * _maxWidth / float64(_w));
 		_w = _maxWidth;
 	}
-	if (h > _maxHeight) {
-		_w = qRound(_w * _maxHeight / float64(h));
-		h = _maxHeight;
+	if (_h > _maxHeight) {
+		_w = qRound(_w * _maxHeight / float64(_h));
+		_h = _maxHeight;
 	}
 	_x = _avail.x() + (_avail.width() - _w) / 2;
-	_y = _avail.y() + (_avail.height() - h) / 2;
+	_y = _avail.y() + (_avail.height() - _h) / 2;
+	_width = _w;
 	_from = App::user(_photo->user);
 	updateControls();
 	if (isHidden()) {
@@ -369,24 +446,80 @@ void MediaView::paintEvent(QPaintEvent *e) {
 	}
 
 	p.setCompositionMode(m);
-
-	// header
 	p.setOpacity(1);
-	p.setPen(st::medviewHeaderColor->p);
-	p.setFont(st::medviewHeaderFont->f);
-	QRect r_header(_save.x() + _save.width(), _save.y(), _close.x() - _save.x() - _save.width(), _save.height());
-	if (r_header.intersects(r)) p.drawText(r_header, _header, style::al_center);
 
-	// name
-	p.setPen(nameDateColor(overLevel(OverName)));
-	if (_over == OverName) _from->nameText.replaceFont(st::msgNameFont->underline());
-	if (_nameNav.intersects(r)) _from->nameText.drawElided(p, _nameNav.left(), _nameNav.top(), _nameNav.width());
-	if (_over == OverName) _from->nameText.replaceFont(st::msgNameFont);
+	// photo
+	if (_photo) {
+		if (_full <= 0 && _photo->full->loaded()) {
+			_current = _photo->full->pixNoCache(_width * cIntRetinaFactor(), 0, true);
+			if (cRetina()) _current.setDevicePixelRatio(cRetinaFactor());
+			_full = 1;
+		} else if (_full < 0 && _photo->medium->loaded()) {
+			_current = _photo->medium->pixBlurredNoCache(_width * cIntRetinaFactor());
+			if (cRetina()) _current.setDevicePixelRatio(cRetinaFactor());
+			_full = 0;
+		} else if (_current.isNull() && _photo->thumb->loaded()) {
+			_current = _photo->thumb->pixBlurredNoCache(_width * cIntRetinaFactor());
+			if (cRetina()) _current.setDevicePixelRatio(cRetinaFactor());
+		}
+	}
+	if (_photo || !_current.isNull()) {
+		QRect imgRect(_x, _y, _w, _h);
+		if (imgRect.intersects(r)) {
+			if (_zoom) {
+				bool was = (p.renderHints() & QPainter::SmoothPixmapTransform);
+				if (!was) p.setRenderHint(QPainter::SmoothPixmapTransform);
+				p.drawPixmap(QRect(_x, _y, _w, _h), _current);
+				if (!was) p.setRenderHint(QPainter::SmoothPixmapTransform, false);
+			} else {
+				p.drawPixmap(_x, _y, _current);
+			}
+			if (imgRect.intersects(_topActions)) {
+				p.setOpacity(st::medviewControlsBgOpacity);
+				p.fillRect(imgRect.intersected(_topActions), st::black->b);
+				p.setOpacity(1);
+			}
+			if (imgRect.intersects(_bottomActions)) {
+				p.setOpacity(st::medviewControlsBgOpacity);
+				p.fillRect(imgRect.intersected(_bottomActions), st::black->b);
+				p.setOpacity(1);
+			}
+			if (_leftNavVisible && imgRect.intersects(_leftNav)) {
+				float64 o = overLevel(OverLeftNav);
+				p.setOpacity(o * st::medviewDarkOpacity + (1 - o) * st::medviewControlsBgOpacity);
+				p.fillRect(imgRect.intersected(_leftNav), st::black->b);
+				p.setOpacity(1);
+			}
+			if (_rightNavVisible && imgRect.intersects(_rightNav)) {
+				float64 o = overLevel(OverRightNav);
+				p.setOpacity(o * st::medviewDarkOpacity + (1 - o) * st::medviewControlsBgOpacity);
+				p.fillRect(imgRect.intersected(_rightNav), st::black->b);
+				p.setOpacity(1);
+			}
+			if (_full < 1) {
+				uint64 dt = getms() - _animStarted;
+				int32 cnt = int32(st::photoLoaderCnt), period = int32(st::photoLoaderPeriod), t = dt % period, delta = int32(st::photoLoaderDelta);
+
+				int32 x = _avail.x() + (_avail.width() - st::mediaviewLoader.width()) / 2, y = _avail.y() + (_avail.height() - st::mediaviewLoader.height()) / 2;
+				p.fillRect(x, y, st::mediaviewLoader.width(), st::mediaviewLoader.height(), st::photoLoaderBg->b);
+				x += (st::mediaviewLoader.width() - cnt * st::mediaviewLoaderPoint.width() - (cnt - 1) * st::mediaviewLoaderSkip) / 2;
+				y += (st::mediaviewLoader.height() - st::mediaviewLoaderPoint.height()) / 2;
+				QColor c(st::white->c);
+				QBrush b(c);
+				for (int32 i = 0; i < cnt; ++i) {
+					t -= delta;
+					while (t < 0) t += period;
+
+					float64 alpha = (t >= st::photoLoaderDuration1 + st::photoLoaderDuration2) ? 0 : ((t > st::photoLoaderDuration1 ? ((st::photoLoaderDuration1 + st::photoLoaderDuration2 - t) / st::photoLoaderDuration2) : (t / st::photoLoaderDuration1)));
+					c.setAlphaF(st::photoLoaderAlphaMin + alpha * (1 - st::photoLoaderAlphaMin));
+					b.setColor(c);
+					p.fillRect(x + i * (st::mediaviewLoaderPoint.width() + st::mediaviewLoaderSkip), y, st::mediaviewLoaderPoint.width(), st::mediaviewLoaderPoint.height(), b);
+				}
+				QTimer::singleShot(AnimationTimerDelta, this, SLOT(updateImage()));
+			}
+		}
+	}
 
-	// date
-	p.setPen(nameDateColor(overLevel(OverDate)));
-	p.setFont((_over == OverDate ? st::medviewDateFont->underline() : st::medviewDateFont)->f);
-	if (_dateNav.intersects(r)) p.drawText(_dateNav.left(), _dateNav.top() + st::medviewDateFont->ascent, _dateText);
 
 	// left nav bar
 	if (_leftNavVisible) {
@@ -407,46 +540,24 @@ void MediaView::paintEvent(QPaintEvent *e) {
 			p.drawPixmap(p_right, App::sprite(), st::medviewRight);
 		}
 	}
-
-	// photo
 	p.setOpacity(1);
-	if (_full <= 0 && _photo->full->loaded()) {
-		_current = _photo->full->pixNoCache(_w * cIntRetinaFactor(), 0, true);
-		if (cRetina()) _current.setDevicePixelRatio(cRetinaFactor());
-		_full = 1;
-	} else if (_full < 0 && _photo->medium->loaded()) {
-		_current = _photo->medium->pixBlurredNoCache(_w * cIntRetinaFactor());
-		if (cRetina()) _current.setDevicePixelRatio(cRetinaFactor());
-		_full = 0;
-	} else if (_current.isNull() && _photo->thumb->loaded()) {
-		_current = _photo->thumb->pixBlurredNoCache(_w * cIntRetinaFactor());
-		if (cRetina()) _current.setDevicePixelRatio(cRetinaFactor());
-	}
-	int32 h = _current.height() / cIntRetinaFactor();
-	if (QRect(_x, _y, _w, h).intersects(r)) {
-		p.drawPixmap(_x, _y, _current);
-		if (_full < 1) {
-			uint64 dt = getms() - _animStarted;
-			int32 cnt = int32(st::photoLoaderCnt), period = int32(st::photoLoaderPeriod), t = dt % period, delta = int32(st::photoLoaderDelta);
 
-			int32 x = _x + (_w - st::mediaviewLoader.width()) / 2, y = _y + (h - st::mediaviewLoader.height()) / 2;
-			p.fillRect(x, y, st::mediaviewLoader.width(), st::mediaviewLoader.height(), st::photoLoaderBg->b);
-			x += (st::mediaviewLoader.width() - cnt * st::mediaviewLoaderPoint.width() - (cnt - 1) * st::mediaviewLoaderSkip) / 2;
-			y += (st::mediaviewLoader.height() - st::mediaviewLoaderPoint.height()) / 2;
-			QColor c(st::white->c);
-			QBrush b(c);
-			for (int32 i = 0; i < cnt; ++i) {
-				t -= delta;
-				while (t < 0) t += period;
+	// header
+	p.setPen(st::medviewHeaderColor->p);
+	p.setFont(st::medviewHeaderFont->f);
+	QRect r_header(_save.x() + _save.width(), _save.y(), _close.x() - _save.x() - _save.width(), _save.height());
+	if (r_header.intersects(r)) p.drawText(r_header, _header, style::al_center);
 
-				float64 alpha = (t >= st::photoLoaderDuration1 + st::photoLoaderDuration2) ? 0 : ((t > st::photoLoaderDuration1 ? ((st::photoLoaderDuration1 + st::photoLoaderDuration2 - t) / st::photoLoaderDuration2) : (t / st::photoLoaderDuration1)));
-				c.setAlphaF(st::photoLoaderAlphaMin + alpha * (1 - st::photoLoaderAlphaMin));
-				b.setColor(c);
-				p.fillRect(x + i * (st::mediaviewLoaderPoint.width() + st::mediaviewLoaderSkip), y, st::mediaviewLoaderPoint.width(), st::mediaviewLoaderPoint.height(), b);
-			}
-			QTimer::singleShot(AnimationTimerDelta, this, SLOT(updateImage()));
-		}
-	}
+	// name
+	p.setPen(nameDateColor(overLevel(OverName)));
+	if (_over == OverName) _from->nameText.replaceFont(st::msgNameFont->underline());
+	if (_nameNav.intersects(r)) _from->nameText.drawElided(p, _nameNav.left(), _nameNav.top(), _nameNav.width());
+	if (_over == OverName) _from->nameText.replaceFont(st::msgNameFont);
+
+	// date
+	p.setPen(nameDateColor(overLevel(OverDate)));
+	p.setFont((_over == OverDate ? st::medviewDateFont->underline() : st::medviewDateFont)->f);
+	if (_dateNav.intersects(r)) p.drawText(_dateNav.left(), _dateNav.top() + st::medviewDateFont->ascent, _dateText);
 }
 
 void MediaView::keyPressEvent(QKeyEvent *e) {
@@ -460,11 +571,62 @@ void MediaView::keyPressEvent(QKeyEvent *e) {
 		moveToPhoto(-1);
 	} else if (e->key() == Qt::Key_Right) {
 		moveToPhoto(1);
+	} else if (e->modifiers().testFlag(Qt::ControlModifier) && (e->key() == Qt::Key_Plus || e->key() == Qt::Key_Equal || e->key() == Qt::Key_Minus || e->key() == Qt::Key_Underscore || e->key() == Qt::Key_0)) {
+		int32 newZoom = _zoom;
+		if (e->key() == Qt::Key_Plus || e->key() == Qt::Key_Equal) {
+			if (newZoom < MaxZoomLevel) ++newZoom;
+		} else if (e->key() == Qt::Key_Minus || e->key() == Qt::Key_Underscore) {
+			if (newZoom > -MaxZoomLevel) --newZoom;
+		} else {
+			newZoom = 0;
+			_x = -_width / 2;
+			_y = -(_current.height() / cIntRetinaFactor()) / 2;
+			if (_zoom >= 0) {
+				_x *= _zoom + 1;
+				_y *= _zoom + 1;
+			} else {
+				_x /= -_zoom + 1;
+				_y /= -_zoom + 1;
+			}
+			_x += _avail.width() / 2;
+			_y += _avail.height() / 2;
+			update();
+		}
+		while (newZoom < 0 && (-newZoom + 1) > _w || (-newZoom + 1) > _h) {
+			++newZoom;
+		}
+		if (_zoom != newZoom) {
+			float64 nx, ny;
+			_w = _current.width() / cIntRetinaFactor();
+			_h = _current.height() / cIntRetinaFactor();
+			if (_zoom >= 0) {
+				nx = (_x - _avail.width() / 2.) / float64(_zoom + 1);
+				ny = (_y - _avail.height() / 2.) / float64(_zoom + 1);
+			} else {
+				nx = (_x - _avail.width() / 2.) * float64(-_zoom + 1);
+				ny = (_y - _avail.height() / 2.) * float64(-_zoom + 1);
+			}
+			_zoom = newZoom;
+			if (_zoom > 0) {
+				_w *= _zoom + 1;
+				_h *= _zoom + 1;
+				_x = int32(nx * (_zoom + 1) + _avail.width() / 2.);
+				_y = int32(ny * (_zoom + 1) + _avail.height() / 2.);
+			} else {
+				_w /= (-_zoom + 1);
+				_h /= (-_zoom + 1);
+				_x = int32(nx / (-_zoom + 1) + _avail.width() / 2.);
+				_y = int32(ny / (-_zoom + 1) + _avail.height() / 2.);
+			}
+			snapXY();
+
+			update();
+		}
 	}
 }
 
 void MediaView::moveToPhoto(int32 delta) {
-	if (_index < 0) return;
+	if (_index < 0 || !_photo) return;
 
 	int32 newIndex = _index + delta;
 	if (_history) {
@@ -495,7 +657,7 @@ void MediaView::moveToPhoto(int32 delta) {
 }
 
 void MediaView::preloadPhotos(int32 delta) {
-	if (_index < 0) return;
+	if (_index < 0 || !_photo) return;
 
 	int32 from = _index + (delta ? delta : -1), to = _index + (delta ? delta * MediaOverviewPreloadCount : 1), forget = _index - delta * 2;
 	if (from > to) qSwap(from, to);
@@ -551,22 +713,53 @@ void MediaView::mousePressEvent(QMouseEvent *e) {
 			_down = OverName;
 		} else if (_over == OverDate) {
 			_down = OverDate;
-		} else {
-			int32 w = st::medviewMainWidth + (st::medviewTopSkip - _save.height()), l = _avail.x() + (_avail.width() - w) / 2;
-			if (!QRect(l, _avail.y(), w, st::medviewTopSkip).contains(e->pos()) && !QRect(l, _avail.y() + _avail.height() - st::medviewBottomSkip, w, st::medviewBottomSkip).contains(e->pos())) {
-				if ((e->pos() - _lastAction).manhattanLength() >= st::medviewDeltaFromLastAction) {
-					onClose();
-				}
-			}
+		} else if (!_topActions.contains(e->pos()) && !_bottomActions.contains(e->pos())) {
+			_pressed = true;
+			_dragging = 0;
+			setCursor(style::cur_default);
+			_mStart = e->pos();
+			_xStart = _x;
+			_yStart = _y;
 		}
 	}
 }
 
+void MediaView::snapXY() {
+	int32 xmin = _avail.x() + _avail.width() - _w - st::medviewNavBarWidth, xmax = _avail.x() + st::medviewNavBarWidth;
+	int32 ymin = _avail.y() + _avail.height() - _h - st::medviewTopSkip, ymax = _avail.y() + st::medviewTopSkip;
+	if (xmin > _avail.x() + ((_avail.width() - _w) / 2)) xmin = _avail.x() + ((_avail.width() - _w) / 2);
+	if (xmax < _avail.x() + ((_avail.width() - _w) / 2)) xmax = _avail.x() + ((_avail.width() - _w) / 2);
+	if (ymin > _avail.y() + ((_avail.height() - _h) / 2)) ymin = _avail.y() + ((_avail.height() - _h) / 2);
+	if (ymax < _avail.y() + ((_avail.height() - _h) / 2)) ymax = _avail.y() + ((_avail.height() - _h) / 2);
+	if (_x < xmin) _x = xmin;
+	if (_x > xmax) _x = xmax;
+	if (_y < ymin) _y = ymin;
+	if (_y > ymax) _y = ymax;
+}
+
 void MediaView::mouseMoveEvent(QMouseEvent *e) {
 	updateOver(e->pos());
 	if (_lastAction.x() >= 0 && (e->pos() - _lastAction).manhattanLength() >= st::medviewDeltaFromLastAction) {
 		_lastAction = QPoint(-st::medviewDeltaFromLastAction, -st::medviewDeltaFromLastAction);
 	}
+	if (_pressed) {
+		if (!_dragging && (e->pos() - _mStart).manhattanLength() >= QApplication::startDragDistance()) {
+			_dragging = QRect(_x, _y, _w, _h).contains(_mStart) ? 1 : -1;
+			if (_dragging > 0) {
+				if (_w > _avail.width() - 2 * st::medviewNavBarWidth || _h > _avail.height() - 2 * st::medviewTopSkip) {
+					setCursor(style::cur_sizeall);
+				} else {
+					setCursor(style::cur_default);
+				}
+			}
+		}
+		if (_dragging > 0) {
+			_x = _xStart + (e->pos() - _mStart).x();
+			_y = _yStart + (e->pos() - _mStart).y();
+			snapXY();
+			update();
+		}
+	}
 }
 
 bool MediaView::updateOverState(OverState newState) {
@@ -604,6 +797,8 @@ bool MediaView::updateOverState(OverState newState) {
 }
 
 void MediaView::updateOver(const QPoint &pos) {
+	if (_pressed || _dragging) return;
+
 	if (_leftNavVisible && _leftNav.contains(pos)) {
 		if (!updateOverState(OverLeftNav)) {
 			update(_leftNav);
@@ -649,13 +844,26 @@ void MediaView::mouseReleaseEvent(QMouseEvent *e) {
 				if (App::main()) App::main()->showPeer(item->history()->peer->id, _msgid, false, true);
 			}
 		}
+	} else if (_pressed) {
+		if (_dragging) {
+			if (_dragging > 0) {
+				_x = _xStart + (e->pos() - _mStart).x();
+				_y = _yStart + (e->pos() - _mStart).y();
+				snapXY();
+				update();
+			}
+			_dragging = 0;
+			setCursor(style::cur_default);
+		} else if ((e->pos() - _lastAction).manhattanLength() >= st::medviewDeltaFromLastAction) {
+			onClose();
+		}
+		_pressed = false;
 	}
 	_down = OverNone;
 }
 
 void MediaView::contextMenuEvent(QContextMenuEvent *e) {
-	if (_photo && _photo->full->loaded() && (e->reason() != QContextMenuEvent::Mouse || QRect(_x, _y, _w, _current.height() / cIntRetinaFactor()).contains(e->pos()))) {
-		
+	if (_photo && _photo->full->loaded() && (e->reason() != QContextMenuEvent::Mouse || QRect(_x, _y, _w, _h).contains(e->pos()))) {
 		if (_menu) {
 			_menu->deleteLater();
 			_menu = 0;
@@ -674,6 +882,25 @@ void MediaView::contextMenuEvent(QContextMenuEvent *e) {
 		connect(_menu, SIGNAL(destroyed(QObject*)), this, SLOT(onMenuDestroy(QObject*)));
 		_menu->popup(e->globalPos());
 		e->accept();
+	} else if (_doc && (e->reason() != QContextMenuEvent::Mouse || QRect(_x, _y, _w, _h).contains(e->pos()))) {
+		if (_menu) {
+			_menu->deleteLater();
+			_menu = 0;
+		}
+		_menu = new ContextMenu(this);
+		if (!_doc->already(true).isEmpty()) {
+			_menu->addAction(lang(cPlatform() == dbipMac ? lng_context_show_in_finder : lng_context_show_in_folder), this, SLOT(onShowInFolder()))->setEnabled(true);
+		}
+		_menu->addAction(lang(lng_context_save_document), this, SLOT(onSave()))->setEnabled(true);
+		_menu->addAction(lang(lng_context_close_file), this, SLOT(onClose()))->setEnabled(true);
+		if (_msgid) {
+			_menu->addAction(lang(lng_context_forward_file), this, SLOT(onForward()))->setEnabled(true);
+			_menu->addAction(lang(lng_context_delete_file), this, SLOT(onDelete()))->setEnabled(true);
+		}
+		_menu->deleteOnHide();
+		connect(_menu, SIGNAL(destroyed(QObject*)), this, SLOT(onMenuDestroy(QObject*)));
+		_menu->popup(e->globalPos());
+		e->accept();
 	}
 }
 
@@ -779,11 +1006,11 @@ void MediaView::onTouchTimer() {
 void MediaView::updateImage() {
 	if (_current.isNull()) return;
 
-	update(_x, _y, _w, _current.height() / cIntRetinaFactor());
+	update(_x, _y, _w, _h);
 }
 
 void MediaView::loadPhotosBack() {
-	if (_loadRequest || _index < 0) return;
+	if (_loadRequest || _index < 0 || !_photo) return;
 
 	if (_history && _history->_overviewCount[OverviewPhotos] != 0) {
 		if (App::main()) App::main()->loadMediaBack(_history->peer, OverviewPhotos);
@@ -830,6 +1057,11 @@ void MediaView::userPhotosLoaded(UserData *u, const MTPphotos_Photos &photos, mt
 }
 
 void MediaView::updateHeader() {
+	if (!_photo) {
+		_header = lang(lng_mediaview_doc_image);
+		return;
+	}
+
 	int32 index = _index, count = 0;
 	if (_history) {
 		count = _history->_overviewCount[OverviewPhotos] ? _history->_overviewCount[OverviewPhotos] : _history->_overview[OverviewPhotos].size();
diff --git a/Telegram/SourceFiles/mediaview.h b/Telegram/SourceFiles/mediaview.h
index 427ff896e..8e4c81317 100644
--- a/Telegram/SourceFiles/mediaview.h
+++ b/Telegram/SourceFiles/mediaview.h
@@ -41,6 +41,7 @@ public:
 
 	void showPhoto(PhotoData *photo, HistoryItem *context);
 	void showPhoto(PhotoData *photo, PeerData *context);
+	void showDocument(DocumentData *doc, HistoryItem *context);
 	void moveToScreen();
 	void moveToPhoto(int32 delta);
 	void preloadPhotos(int32 delta);
@@ -57,6 +58,7 @@ public slots:
 
 	void onClose();
 	void onSave();
+	void onShowInFolder();
 	void onForward();
 	void onDelete();
 	void onCopy();
@@ -77,16 +79,22 @@ private:
 	void userPhotosLoaded(UserData *u, const MTPphotos_Photos &photos, mtpRequestId req);
 
 	void updateHeader();
+	void snapXY();
 
 	QTimer _timer;
 	PhotoData *_photo;
-	QRect _avail, _leftNav, _rightNav, _nameNav, _dateNav;
+	DocumentData *_doc;
+	QRect _avail, _leftNav, _rightNav, _nameNav, _dateNav, _topActions, _bottomActions;
 	bool _leftNavVisible, _rightNavVisible;
 	QString _dateText;
 
 	uint64 _animStarted;
 
-	int32 _maxWidth, _maxHeight, _x, _y, _w;
+	int32 _maxWidth, _maxHeight, _width, _x, _y, _w, _h, _xStart, _yStart;
+	int32 _zoom; // < 0 - out, 0 - none, > 0 - in
+	QPoint _mStart;
+	bool _pressed;
+	int32 _dragging;
 	QPixmap _current;
 	int32 _full; // -1 - thumb, 0 - medium, 1 - full
 
diff --git a/Telegram/SourceFiles/supporttl.cpp b/Telegram/SourceFiles/supporttl.cpp
index 42802739c..651c36d88 100644
--- a/Telegram/SourceFiles/supporttl.cpp
+++ b/Telegram/SourceFiles/supporttl.cpp
@@ -28,7 +28,7 @@ namespace {
 				value = value.mid(0, value.size() - 1);
 			}
 			for (QStringList::const_iterator i = keys.cbegin(), e = keys.cend(); i != e; ++i) {
-				_supportTemplates[*i] = value;
+				_supportTemplates[textSearchKey(*i)] = value;
 			}
 		}
 		value = QString();
@@ -105,7 +105,7 @@ void readSupportTemplates() {
 }
 
 const QString &supportTemplate(const QString &key) {
-	SupportTemplates::const_iterator i = _supportTemplates.constFind(key);
+	SupportTemplates::const_iterator i = _supportTemplates.constFind(textSearchKey(key));
 	if (i != _supportTemplates.cend()) {
 		return *i;
 	}
diff --git a/Telegram/SourceFiles/supporttl.h b/Telegram/SourceFiles/supporttl.h
index c0dd86f3b..970f21f6a 100644
--- a/Telegram/SourceFiles/supporttl.h
+++ b/Telegram/SourceFiles/supporttl.h
@@ -18,4 +18,4 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
 #pragma once
 
 void readSupportTemplates();
-const QString &supportTemplate(const QString &word);
+const QString &supportTemplate(const QString &key);
diff --git a/Telegram/SourceFiles/window.cpp b/Telegram/SourceFiles/window.cpp
index 268dfec53..8e4a25833 100644
--- a/Telegram/SourceFiles/window.cpp
+++ b/Telegram/SourceFiles/window.cpp
@@ -588,6 +588,13 @@ void Window::showPhoto(PhotoData *photo, PeerData *peer) {
 	_mediaView->setFocus();
 }
 
+void Window::showDocument(DocumentData *doc, HistoryItem *item) {
+	layerHidden();
+	_mediaView->showDocument(doc, item);
+	_mediaView->activateWindow();
+	_mediaView->setFocus();
+}
+
 void Window::showLayer(LayeredWidget *w) {
 	layerHidden();
 	layerBG = new BackgroundWidget(this, w);
diff --git a/Telegram/SourceFiles/window.h b/Telegram/SourceFiles/window.h
index 12b7ed42c..92bb07491 100644
--- a/Telegram/SourceFiles/window.h
+++ b/Telegram/SourceFiles/window.h
@@ -177,6 +177,7 @@ public:
 	void showPhoto(const PhotoLink *lnk, HistoryItem *item = 0);
 	void showPhoto(PhotoData *photo, HistoryItem *item);
 	void showPhoto(PhotoData *photo, PeerData *item);
+	void showDocument(DocumentData *doc, HistoryItem *item);
 	void showLayer(LayeredWidget *w);
 	void replaceLayer(LayeredWidget *w);
 	void hideLayer();
diff --git a/Telegram/Telegram.plist b/Telegram/Telegram.plist
index 11202a800..652da986c 100644
--- a/Telegram/Telegram.plist
+++ b/Telegram/Telegram.plist
@@ -11,7 +11,7 @@
 	<key>CFBundlePackageType</key>
 	<string>APPL</string>
 	<key>CFBundleShortVersionString</key>
-	<string>0.6.1</string>
+	<string>0.6.2</string>
 	<key>CFBundleSignature</key>
 	<string>????</string>
 	<key>NOTE</key>
diff --git a/Telegram/Telegram.rc b/Telegram/Telegram.rc
index 270685eef6932627732728c4e14dd4618abfa9f3..b19b8f96099a6241bd5ff7a99c6e58802d61b8be 100644
GIT binary patch
delta 42
vcmZ3cy-a(94mYFGWL<7UMx)K~-2F^I>OHSDBT(!;uk_>yexc0~{C;cz3SJBk

delta 42
vcmZ3cy-a(94mYFWWL<7UM#IhV-2F^I>OHSDBT(!;uk_>yexc0~{C;cz3H=NU

diff --git a/Telegram/Telegram.xcodeproj/project.pbxproj b/Telegram/Telegram.xcodeproj/project.pbxproj
index 85e5e35ac..bfb2bd2f3 100644
--- a/Telegram/Telegram.xcodeproj/project.pbxproj
+++ b/Telegram/Telegram.xcodeproj/project.pbxproj
@@ -1497,7 +1497,7 @@
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				COPY_PHASE_STRIP = NO;
-				CURRENT_PROJECT_VERSION = 0.6.1;
+				CURRENT_PROJECT_VERSION = 0.6.2;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
 				GCC_OPTIMIZATION_LEVEL = 0;
@@ -1515,7 +1515,7 @@
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				COPY_PHASE_STRIP = YES;
-				CURRENT_PROJECT_VERSION = 0.6.1;
+				CURRENT_PROJECT_VERSION = 0.6.2;
 				GCC_GENERATE_DEBUGGING_SYMBOLS = NO;
 				GCC_OPTIMIZATION_LEVEL = fast;
 				GCC_PREFIX_HEADER = ./SourceFiles/stdafx.h;
@@ -1541,10 +1541,10 @@
 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
 				CODE_SIGN_IDENTITY = "";
 				COPY_PHASE_STRIP = NO;
-				CURRENT_PROJECT_VERSION = 0.6.1;
+				CURRENT_PROJECT_VERSION = 0.6.2;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DYLIB_COMPATIBILITY_VERSION = 0.6;
-				DYLIB_CURRENT_VERSION = 0.6.1;
+				DYLIB_CURRENT_VERSION = 0.6.2;
 				ENABLE_STRICT_OBJC_MSGSEND = YES;
 				FRAMEWORK_SEARCH_PATHS = "";
 				GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
@@ -1683,10 +1683,10 @@
 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
 				CODE_SIGN_IDENTITY = "";
 				COPY_PHASE_STRIP = NO;
-				CURRENT_PROJECT_VERSION = 0.6.1;
+				CURRENT_PROJECT_VERSION = 0.6.2;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DYLIB_COMPATIBILITY_VERSION = 0.6;
-				DYLIB_CURRENT_VERSION = 0.6.1;
+				DYLIB_CURRENT_VERSION = 0.6.2;
 				ENABLE_STRICT_OBJC_MSGSEND = YES;
 				FRAMEWORK_SEARCH_PATHS = "";
 				GCC_GENERATE_DEBUGGING_SYMBOLS = YES;