diff --git a/Telegram/Resources/lang.strings b/Telegram/Resources/lang.strings
index deb8fd1384..3518eaddf8 100644
--- a/Telegram/Resources/lang.strings
+++ b/Telegram/Resources/lang.strings
@@ -527,7 +527,8 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
 "lng_search_found_results" = "{count:No messages found|Found # message|Found # messages}";
 "lng_search_global_results" = "Global search results";
 
-"lng_mediaview_save_as" = "Save as..";
+"lng_media_save_progress" = "{ready} of {total} {mb}";
+"lng_mediaview_save_as" = "Save As..";
 "lng_mediaview_copy" = "Copy";
 "lng_mediaview_forward" = "Forward";
 "lng_mediaview_delete" = "Delete";
diff --git a/Telegram/Resources/style.txt b/Telegram/Resources/style.txt
index 31b97aa66b..9f34c5b78e 100644
--- a/Telegram/Resources/style.txt
+++ b/Telegram/Resources/style.txt
@@ -1623,15 +1623,25 @@ mvFadeDuration: 150;
 mvDocPadding: 18px;
 mvDocSize: size(340px, 116px);
 mvDocBg: white;
-mvDocNameTop: 5px;
+mvDocNameTop: 4px;
+mvDocNameFont: font(semibold 14px);
 mvDocNameColor: black;
-mvDocSizeTop: 30px;
+mvDocSizeTop: 29px;
 mvDocSizeColor: #808080;
+mvDocExtTop: 35px;
+mvDocExtFont: font(semibold 18px);
+mvDocExtColor: white;
+mvDocExtPadding: 10px;
 mvDocLinksTop: 57px;
 mvDocRed: sprite(0px, 400px, 80px, 80px);
 mvDocYellow: sprite(80px, 400px, 80px, 80px);
 mvDocGreen: sprite(160px, 400px, 80px, 80px);
 mvDocBlue: sprite(240px, 400px, 80px, 80px);
+mvDocLink: linkButton(btnDefLink) {
+	color: #4595d3;
+	overColor: #4595d3;
+	downColor: #4595d3;
+}
 
 mvDeltaFromLastAction: 5px;
 mvSwipeDistance: 80px;
@@ -1694,9 +1704,13 @@ photoLoaderAlphaMin: 0.1; // not less than that
 
 radialSize: size(50px, 50px);
 radialLine: 2px;
-radialDuration: 200;
-radialPeriod: 2000;
+radialDuration: 350;
+radialPeriod: 3000;
 radialBgOpacity: 0.4;
+radialDownload: sprite(346px, 0px, 50px, 50px);
+radialDownloadOpacity: 0.8;
+radialCancel: sprite(378px, 50px, 18px, 18px);
+radialCancelOpacity: 0.7;
 
 overviewLoader: size(34px, 14px);
 overviewLoaderPoint: size(4px, 4px);
diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp
index c7719bd45a..d89191e50e 100644
--- a/Telegram/SourceFiles/application.cpp
+++ b/Telegram/SourceFiles/application.cpp
@@ -675,6 +675,8 @@ void Application::startApp() {
 
 	DEBUG_LOG(("Application Info: starting app.."));
 
+	QMimeDatabase().mimeTypeForName(qsl("text/plain")); // create mime database
+
 	window->createWinId();
 	window->init();
 
diff --git a/Telegram/SourceFiles/art/sprite.png b/Telegram/SourceFiles/art/sprite.png
index 7e3eeca29a..86fb78fda7 100644
Binary files a/Telegram/SourceFiles/art/sprite.png and b/Telegram/SourceFiles/art/sprite.png differ
diff --git a/Telegram/SourceFiles/art/sprite_200x.png b/Telegram/SourceFiles/art/sprite_200x.png
index 820955566e..e2d45ef522 100644
Binary files a/Telegram/SourceFiles/art/sprite_200x.png and b/Telegram/SourceFiles/art/sprite_200x.png differ
diff --git a/Telegram/SourceFiles/boxes/abstractbox.cpp b/Telegram/SourceFiles/boxes/abstractbox.cpp
index 656d9c0b1d..9300efb8ec 100644
--- a/Telegram/SourceFiles/boxes/abstractbox.cpp
+++ b/Telegram/SourceFiles/boxes/abstractbox.cpp
@@ -106,8 +106,10 @@ void AbstractBox::setMaxHeight(int32 maxHeight) {
 
 void AbstractBox::resizeMaxHeight(int32 newWidth, int32 maxHeight) {
 	if (width() != newWidth || _maxHeight != maxHeight) {
+		QRect g(geometry());
 		_maxHeight = maxHeight;
 		resize(newWidth, countHeight());
+		if (parentWidget()) parentWidget()->update(geometry().united(g).marginsAdded(QMargins(st::boxShadow.pxWidth(), st::boxShadow.pxHeight(), st::boxShadow.pxWidth(), st::boxShadow.pxHeight())));
 	}
 }
 
diff --git a/Telegram/SourceFiles/gui/animation.cpp b/Telegram/SourceFiles/gui/animation.cpp
index 027d4105c8..40bd55078c 100644
--- a/Telegram/SourceFiles/gui/animation.cpp
+++ b/Telegram/SourceFiles/gui/animation.cpp
@@ -18,7 +18,9 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
 #include "stdafx.h"
 
 #include "animation.h"
-#include <QtCore/QTimer>
+
+#include "mainwidget.h"
+#include "window.h"
 
 namespace {
 	AnimationManager *manager = 0;
@@ -94,3 +96,113 @@ namespace anim {
 	}
 
 }
+
+bool AnimatedGif::animStep(float64 ms) {
+	int32 f = frame;
+	while (f < frames.size() && ms > delays[f]) {
+		++f;
+		if (f == frames.size() && frames.size() < framesCount) {
+			if (reader->read(&img)) {
+				int64 d = reader->nextImageDelay(), delay = delays[f - 1];
+				if (!d) d = 1;
+				delay += d;
+				frames.push_back(QPixmap::fromImage(img.size() == QSize(w, h) ? img : img.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly));
+				delays.push_back(delay);
+				for (int32 i = 0; i < frames.size(); ++i) {
+					if (!frames[i].isNull()) {
+						frames[i] = QPixmap();
+						break;
+					}
+				}
+			} else {
+				framesCount = frames.size();
+			}
+		}
+		if (f == frames.size()) {
+			if (!duration) {
+				duration = delays.isEmpty() ? 1 : delays.back();
+			}
+
+			f = 0;
+			for (int32 i = 0, s = delays.size() - 1; i <= s; ++i) {
+				delays[i] += duration;
+			}
+			if (frames[f].isNull()) {
+				QString fname = reader->fileName();
+				delete reader;
+				reader = new QImageReader(fname);
+			}
+		}
+		if (frames[f].isNull() && reader->read(&img)) {
+			frames[f] = QPixmap::fromImage(img.size() == QSize(w, h) ? img : img.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly);
+		}
+	}
+	if (frame != f) {
+		frame = f;
+		if (msg && App::main()) {
+			App::main()->msgUpdated(msg->history()->peer->id, msg);
+		} else {
+			emit updated();
+		}
+	}
+	return true;
+}
+
+void AnimatedGif::start(HistoryItem *row, const QString &file) {
+	stop();
+
+	reader = new QImageReader(file);
+	if (!reader->canRead() || !reader->supportsAnimation()) {
+		stop();
+		return;
+	}
+
+	QSize s = reader->size();
+	w = s.width();
+	h = s.height();
+	framesCount = reader->imageCount();
+	if (!w || !h || !framesCount) {
+		stop();
+		return;
+	}
+
+	frames.reserve(framesCount);
+	delays.reserve(framesCount);
+
+	int32 sizeLeft = MediaViewImageSizeLimit, delay = 0;
+	for (bool read = reader->read(&img); read; read = reader->read(&img)) {
+		sizeLeft -= w * h * 4;
+		frames.push_back(QPixmap::fromImage(img.size() == s ? img : img.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly));
+		int32 d = reader->nextImageDelay();
+		if (!d) d = 1;
+		delay += d;
+		delays.push_back(delay);
+		if (sizeLeft < 0) break;
+	}
+
+	msg = row;
+
+	anim::start(this);
+	if (msg) {
+		msg->initDimensions();
+		if (App::main()) App::main()->itemResized(msg, true);
+	}
+}
+
+void AnimatedGif::stop(bool onItemRemoved) {
+	if (isNull()) return;
+
+	delete reader;
+	reader = 0;
+	HistoryItem *row = msg;
+	msg = 0;
+	frames.clear();
+	delays.clear();
+	w = h = frame = framesCount = duration = 0;
+
+	anim::stop(this);
+	if (row && !onItemRemoved) {
+		row->initDimensions();
+		if (App::main()) App::main()->itemResized(row, true);
+	}
+}
diff --git a/Telegram/SourceFiles/gui/animation.h b/Telegram/SourceFiles/gui/animation.h
index 9d9556ba9b..e600f0d229 100644
--- a/Telegram/SourceFiles/gui/animation.h
+++ b/Telegram/SourceFiles/gui/animation.h
@@ -307,3 +307,39 @@ private:
 	bool iterating;
 
 };
+
+class HistoryItem;
+class AnimatedGif : public QObject, public Animated {
+	Q_OBJECT
+
+public:
+
+	AnimatedGif() : msg(0), reader(0), w(0), h(0), frame(0), framesCount(0), duration(0) {
+	}
+
+	bool animStep(float64 ms);
+
+	void start(HistoryItem *row, const QString &file);
+	void stop(bool onItemRemoved = false);
+
+	bool isNull() const {
+		return !reader;
+	}
+
+	~AnimatedGif() {
+		stop(true);
+	}
+
+signals:
+
+	void updated();
+
+public:
+
+	HistoryItem *msg;
+	QImage img;
+	QImageReader *reader;
+	QVector<QPixmap> frames;
+	QVector<int64> delays;
+	int32 w, h, frame, framesCount, duration;
+};
diff --git a/Telegram/SourceFiles/gui/style_core.h b/Telegram/SourceFiles/gui/style_core.h
index cfab2435e6..190b8b917e 100644
--- a/Telegram/SourceFiles/gui/style_core.h
+++ b/Telegram/SourceFiles/gui/style_core.h
@@ -26,6 +26,10 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
 #include <QtGui/QCursor>
 #include <QtGui/QFont>
 
+inline QRect rtlrect(int x, int y, int w, int h, int outerw) {
+	return rtl() ? QRect(outerw - x - w, y, w, h) : QRect(x, y, w, h);
+}
+
 namespace style {
 	
 	class FontData;
diff --git a/Telegram/SourceFiles/gui/text.cpp b/Telegram/SourceFiles/gui/text.cpp
index 9ac74f9cbf..8b26f7e5f1 100644
--- a/Telegram/SourceFiles/gui/text.cpp
+++ b/Telegram/SourceFiles/gui/text.cpp
@@ -887,7 +887,7 @@ public:
 		_align = align;
 
 		_parDirection = _t->_startDir;
-		if (_parDirection == Qt::LayoutDirectionAuto) _parDirection = langDir();
+		if (_parDirection == Qt::LayoutDirectionAuto) _parDirection = cLangDir();
 		if ((*_t->_blocks.cbegin())->type() != TextBlockNewline) {
 			initNextParagraph(_t->_blocks.cbegin());
 		}
@@ -926,7 +926,7 @@ public:
 				}
 
 				_parDirection = static_cast<NewlineBlock*>(b)->nextDirection();
-				if (_parDirection == Qt::LayoutDirectionAuto) _parDirection = langDir();
+				if (_parDirection == Qt::LayoutDirectionAuto) _parDirection = cLangDir();
 				initNextParagraph(i + 1);
 
 				longWordLine = true;
@@ -2613,7 +2613,7 @@ QString Text::original(uint16 selectedFrom, uint16 selectedTo, bool expandLinks)
 						result += r;
 					} else {
 						QUrl u(url);
-						if (r.size() > 3 && _text.midRef(lnkFrom, r.size() - 3) == (u.isValid() ? u.toDisplayString() : url).midRef(0, r.size() - 3)) { // same link
+						if (r.size() <= 3 || _text.midRef(lnkFrom, r.size() - 3) == (u.isValid() ? u.toDisplayString() : url).midRef(0, r.size() - 3)) { // same link
 							result += url;
 						} else {
 							result.append(r).append(qsl(" ( ")).append(url).append(qsl(" )"));
@@ -4090,7 +4090,7 @@ LinkRanges textParseLinks(const QString &text, int32 flags, bool rich) { // some
 	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;) {
+	for (int32 offset = 0, matchOffset = offset, mentionSkip = 0; offset < len;) {
 		if (nextCmd <= offset) {
 			for (nextCmd = offset; nextCmd < len; ++nextCmd) {
 				if (*(start + nextCmd) == TextCommand) {
@@ -4101,8 +4101,7 @@ LinkRanges textParseLinks(const QString &text, int32 flags, bool rich) { // some
 		QRegularExpressionMatch mDomain = _reDomain.match(text, matchOffset);
 		QRegularExpressionMatch mExplicitDomain = _reExplicitDomain.match(text, matchOffset);
 		QRegularExpressionMatch mHashtag = withHashtags ? _reHashtag.match(text, matchOffset) : QRegularExpressionMatch();
-		QRegularExpressionMatch mMention = withMentions ? _reMention.match(text, matchOffset) : QRegularExpressionMatch();
-		if (!mDomain.hasMatch() && !mExplicitDomain.hasMatch() && !mHashtag.hasMatch() && !mMention.hasMatch()) break;
+		QRegularExpressionMatch mMention = withMentions ? _reMention.match(text, qMax(mentionSkip, matchOffset)) : QRegularExpressionMatch();
 
 		LinkRange link;
 		int32 domainOffset = mDomain.hasMatch() ? mDomain.capturedStart() : INT_MAX,
@@ -4121,7 +4120,7 @@ LinkRanges textParseLinks(const QString &text, int32 flags, bool rich) { // some
 				--hashtagEnd;
 			}
 		}
-		if (mMention.hasMatch()) {
+		while (mMention.hasMatch()) {
 			if (!mMention.capturedRef(1).isEmpty()) {
 				++mentionOffset;
 			}
@@ -4129,10 +4128,21 @@ LinkRanges textParseLinks(const QString &text, int32 flags, bool rich) { // some
 				--mentionEnd;
 			}
 			if (!(start + mentionOffset + 1)->isLetter() || !(start + mentionEnd - 1)->isLetterOrNumber()) {
-				mentionOffset = mentionEnd = INT_MAX;
-				if (!mDomain.hasMatch() && !mExplicitDomain.hasMatch() && !mHashtag.hasMatch()) break;
+				mentionSkip = mentionEnd;
+				mMention = _reMention.match(text, qMax(mentionSkip, matchOffset));
+				if (mMention.hasMatch()) {
+					mentionOffset = mMention.capturedStart();
+					mentionEnd = mMention.capturedEnd();
+				} else {
+					mentionOffset = INT_MAX;
+					mentionEnd = INT_MAX;
+				}
+			} else {
+				break;
 			}
 		}
+		if (!mMention.hasMatch() && !mDomain.hasMatch() && !mExplicitDomain.hasMatch() && !mHashtag.hasMatch()) break;
+
 		if (explicitDomainOffset < domainOffset) {
 			domainOffset = explicitDomainOffset;
 			domainEnd = explicitDomainEnd;
diff --git a/Telegram/SourceFiles/gui/twidget.cpp b/Telegram/SourceFiles/gui/twidget.cpp
index 7690b3c1eb..7cbcf90d17 100644
--- a/Telegram/SourceFiles/gui/twidget.cpp
+++ b/Telegram/SourceFiles/gui/twidget.cpp
@@ -20,9 +20,6 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
 #include "application.h"
 
 namespace {
-	Qt::LayoutDirection _dir = Qt::LeftToRight;
-	bool _rtl = false;
-
 	void _sendResizeEvents(QWidget *target) {
 		QResizeEvent e(target->size(), QSize());
 		QApplication::sendEvent(target, &e);
@@ -37,19 +34,6 @@ namespace {
 	}
 }
 
-void rtl(bool is) {
-	_rtl = is;
-	_dir = _rtl ? Qt::RightToLeft : Qt::LeftToRight;
-}
-
-bool rtl() {
-	return _rtl;
-}
-
-Qt::LayoutDirection langDir() { // current lang dependent
-	return _dir;
-}
-
 QPixmap myGrab(QWidget *target, const QRect &rect) {
     if (!cRetina()) return target->grab(rect);
         
diff --git a/Telegram/SourceFiles/gui/twidget.h b/Telegram/SourceFiles/gui/twidget.h
index 5e458f9737..83e15d8dd3 100644
--- a/Telegram/SourceFiles/gui/twidget.h
+++ b/Telegram/SourceFiles/gui/twidget.h
@@ -17,14 +17,6 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
 */
 #pragma once
 
-void rtl(bool is);
-bool rtl();
-Qt::LayoutDirection langDir();
-
-inline QRect rtlrect(int x, int y, int w, int h, int outerw) {
-	return rtl() ? QRect(outerw - x - w, y, w, h) : QRect(x, y, w, h);
-}
-
 class Widget : public QWidget {
 public:
 
@@ -61,15 +53,36 @@ public:
 	void drawPixmapLeft(int x, int y, int outerw, const QPixmap &pix, const QRect &from) {
 		drawPixmap(QPoint(rtl() ? (outerw - x - (from.width() / pix.devicePixelRatio())) : x, y), pix, from);
 	}
+	void drawPixmapLeft(const QPoint &p, int outerw, const QPixmap &pix, const QRect &from) {
+		return drawPixmapLeft(p.x(), p.y(), outerw, pix, from);
+	}
 	void drawPixmapRight(int x, int y, int outerw, const QPixmap &pix, const QRect &from) {
 		drawPixmap(QPoint(rtl() ? x : (outerw - x - (from.width() / pix.devicePixelRatio())), y), pix, from);
 	}
-	void drawSpriteLeft(int x, int y, int outerw, const QRect &sprite) {
+	void drawPixmapRight(const QPoint &p, int outerw, const QPixmap &pix, const QRect &from) {
+		return drawPixmapRight(p.x(), p.y(), outerw, pix, from);
+	}
+	void drawSprite(int x, int y, const style::sprite &sprite) {
+		return drawPixmap(QPoint(x, y), App::sprite(), sprite);
+	}
+	void drawSprite(const QPoint &p, const style::sprite &sprite) {
+		return drawPixmap(p, App::sprite(), sprite);
+	}
+	void drawSpriteLeft(int x, int y, int outerw, const style::sprite &sprite) {
 		return drawPixmapLeft(x, y, outerw, App::sprite(), sprite);
 	}
-	void drawSpriteRight(int x, int y, int outerw, const QRect &sprite) {
+	void drawSpriteLeft(const QPoint &p, int outerw, const style::sprite &sprite) {
+		return drawPixmapLeft(p, outerw, App::sprite(), sprite);
+	}
+	void drawSpriteRight(int x, int y, int outerw, const style::sprite &sprite) {
 		return drawPixmapRight(x, y, outerw, App::sprite(), sprite);
 	}
+	void drawSpriteRight(const QPoint &p, int outerw, const style::sprite &sprite) {
+		return drawPixmapRight(p, outerw, App::sprite(), sprite);
+	}
+	void drawSpriteCenter(const QRect &in, const style::sprite &sprite) {
+		return drawPixmap(QPoint(in.x() + (in.width() - sprite.pxWidth()) / 2, in.y() + (in.height() - sprite.pxHeight()) / 2), App::sprite(), sprite);
+	}
 };
 
 class TWidget : public Widget {
diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp
index 246f03cda1..3c6d11b34d 100644
--- a/Telegram/SourceFiles/history.cpp
+++ b/Telegram/SourceFiles/history.cpp
@@ -81,7 +81,7 @@ namespace {
 	};
 
 	inline void _initTextOptions() {
-		_historySrvOptions.dir = _textNameOptions.dir = _textDlgOptions.dir = langDir();
+		_historySrvOptions.dir = _textNameOptions.dir = _textDlgOptions.dir = cLangDir();
 		_textDlgOptions.maxw = st::dlgMaxWidth * 2;
 		_webpageTitleOptions.maxw = st::msgMaxWidth - st::msgPadding.left() - st::msgPadding.right() - st::webPageLeft;
 		_webpageTitleOptions.maxh = st::webPageTitleFont->height * 2;
@@ -89,127 +89,6 @@ namespace {
 		_webpageDescriptionOptions.maxh = st::webPageDescriptionFont->height * 3;
 	}
 
-	class AnimatedGif : public Animated {
-	public:
-
-		AnimatedGif() : msg(0), reader(0), w(0), h(0), frame(0), framesCount(0), duration(0) {
-		}
-
-		bool animStep(float64 ms) {
-			int32 f = frame;
-			while (f < frames.size() && ms > delays[f]) {
-				++f;
-				if (f == frames.size() && frames.size() < framesCount) {
-					if (reader->read(&img)) {
-						int64 d = reader->nextImageDelay(), delay = delays[f - 1];
-						if (!d) d = 1;
-						delay += d;
-						frames.push_back(QPixmap::fromImage(img.size() == QSize(w, h) ? img : img.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly));
-						delays.push_back(delay);
-						for (int32 i = 0; i < frames.size(); ++i) {
-							if (!frames[i].isNull()) {
-								frames[i] = QPixmap();
-								break;
-							}
-						}
-					} else {
-						framesCount = frames.size();
-					}
-				}
-				if (f == frames.size()) {
-					if (!duration) {
-						duration = delays.isEmpty() ? 1 : delays.back();
-					}
-
-					f = 0;
-					for (int32 i = 0, s = delays.size() - 1; i <= s; ++i) {
-						delays[i] += duration;
-					}
-					if (frames[f].isNull()) {
-						QString fname = reader->fileName();
-						delete reader;
-						reader = new QImageReader(fname);
-					}
-				}
-				if (frames[f].isNull() && reader->read(&img)) {
-					frames[f] = QPixmap::fromImage(img.size() == QSize(w, h) ? img : img.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly);
-				}
-			}
-			if (frame != f) {
-				frame = f;
-				if (App::main()) App::main()->msgUpdated(msg->history()->peer->id, msg);
-			}
-			return true;
-		}
-
-		void start(HistoryItem *row, const QString &file) {
-			if (reader) {
-				stop();
-			}
-			reader = new QImageReader(file);
-			if (!reader->canRead() || !reader->supportsAnimation()) {
-				stop();
-				return;
-			}
-
-			QSize s = reader->size();
-			w = s.width();
-			h = s.height();
-			framesCount = reader->imageCount();
-			if (!w || !h || !framesCount) {
-				stop();
-				return;
-			}
-
-			frames.reserve(framesCount);
-			delays.reserve(framesCount);
-			
-			int32 sizeLeft = MediaViewImageSizeLimit, delay = 0;
-			for (bool read = reader->read(&img); read; read = reader->read(&img)) {
-				sizeLeft -= w * h * 4;
-				frames.push_back(QPixmap::fromImage(img.size() == s ? img : img.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly));
-				int32 d = reader->nextImageDelay();
-				if (!d) d = 1;
-				delay += d;
-				delays.push_back(delay);
-				if (sizeLeft < 0) break;
-			}
-
-			msg = row;
-
-			anim::start(this);
-			msg->initDimensions();
-			App::main()->itemResized(msg, true);
-		}
-
-		void stop(bool onItemRemoved = false) {
-			delete reader;
-			reader = 0;
-			HistoryItem *row = msg;
-			msg = 0;
-			frames.clear();
-			delays.clear();
-			w = h = frame = framesCount = duration = 0;
-
-			anim::stop(this);
-			if (row && !onItemRemoved) {
-				row->initDimensions();
-				if (App::main()) App::main()->itemResized(row, true);
-			}
-		}
-
-		~AnimatedGif() {
-			stop(true);
-		}
-
-		HistoryItem *msg;
-		QImage img;
-		QImageReader *reader;
-		QVector<QPixmap> frames;
-		QVector<int64> delays;
-		int32 w, h, frame, framesCount, duration;
-	};
-
 	AnimatedGif animated;
 
 	inline HistoryReply *toHistoryReply(HistoryItem *item) {
@@ -2508,6 +2387,7 @@ void HistoryDocument::initDimensions(const HistoryItem *parent) {
 			}
 		}
 	}
+	_height = _minh;
 }
 
 void HistoryDocument::draw(QPainter &p, const HistoryItem *parent, bool selected, int32 width) const {
@@ -3191,6 +3071,7 @@ void HistoryWebPage::initDimensions(const HistoryItem *parent) {
 	if (data->pendingTill) {
 		_maxw = st::webPageLeft + st::linkFont->m.width(lang((data->pendingTill < 0) ? lng_attach_failed : lng_profile_loading));
 		_minh = st::replyHeight;
+		_height = _minh;
 		return;
 	}
 	if (!_openl && !data->url.isEmpty()) _openl = TextLinkPtr(new TextLink(data->url));
@@ -3280,6 +3161,7 @@ void HistoryWebPage::initDimensions(const HistoryItem *parent) {
 		_duration = formatDurationText(data->duration);
 		_durationWidth = st::msgDateFont->m.width(_duration);
 	}
+	_height = _minh;
 }
 
 void HistoryWebPage::draw(QPainter &p, const HistoryItem *parent, bool selected, int32 width) const {
@@ -4054,6 +3936,7 @@ void HistoryImageLink::initDimensions(const HistoryItem *parent) {
 			_minh += st::msgPadding.top() + st::msgNameFont->height;
 		}
 	}
+	_height = _minh;
 }
 
 void HistoryImageLink::draw(QPainter &p, const HistoryItem *parent, bool selected, int32 width) const {
diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp
index f01f53f7d9..6e57dce461 100644
--- a/Telegram/SourceFiles/historywidget.cpp
+++ b/Telegram/SourceFiles/historywidget.cpp
@@ -842,9 +842,9 @@ void HistoryList::saveContextFile() {
     VideoLink *lnkVideo = dynamic_cast<VideoLink*>(_contextMenuLnk.data());
     AudioLink *lnkAudio = dynamic_cast<AudioLink*>(_contextMenuLnk.data());
     DocumentLink *lnkDocument = dynamic_cast<DocumentLink*>(_contextMenuLnk.data());
-	if (lnkVideo) VideoSaveLink(lnkVideo->video()).doSave(true);
-	if (lnkAudio) AudioSaveLink(lnkAudio->audio()).doSave(true);
-	if (lnkDocument) DocumentSaveLink(lnkDocument->document()).doSave(true);
+	if (lnkVideo) VideoSaveLink::doSave(lnkVideo->video(), true);
+	if (lnkAudio) AudioSaveLink::doSave(lnkAudio->audio(), true);
+	if (lnkDocument) DocumentSaveLink::doSave(lnkDocument->document(), true);
 }
 
 void HistoryList::copyContextText() {
diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp
index 37e3cd3e19..6cbd6eb395 100644
--- a/Telegram/SourceFiles/mainwidget.cpp
+++ b/Telegram/SourceFiles/mainwidget.cpp
@@ -1431,6 +1431,7 @@ void MainWidget::documentLoadProgress(mtpFileLoader *loader) {
 			msgUpdated(j.key()->history()->peer->id, j.key());
 		}
 	}
+	App::wnd()->documentUpdated(document);
 }
 
 void MainWidget::documentLoadFailed(mtpFileLoader *loader, bool started) {
diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp
index 6972183a7d..6995b47cd4 100644
--- a/Telegram/SourceFiles/mediaview.cpp
+++ b/Telegram/SourceFiles/mediaview.cpp
@@ -50,7 +50,10 @@ _width(0), _x(0), _y(0), _w(0), _h(0), _xStart(0), _yStart(0),
 _zoom(0), _zoomToScreen(0), _pressed(false), _dragging(0), _full(-1),
 _docNameWidth(0), _docSizeWidth(0),
 _docThumbx(0), _docThumby(0), _docThumbw(0),
-_docRadialFirst(0), _docRadialStart(0), _docRadialLast(0), a_docRadialStart(0, 1),
+_docRadialFirst(0), _docRadialStart(0), _docRadialLast(0), _docRadialOpacity(1), a_docRadialStart(0, 1),
+_docDownload(this, lang(lng_media_download), st::mvDocLink),
+_docSaveAs(this, lang(lng_mediaview_save_as), st::mvDocLink),
+_docCancel(this, lang(lng_cancel), st::mvDocLink),
 _history(0), _peer(0), _user(0), _from(0), _index(-1), _msgid(0),
 _loadRequest(0), _over(OverNone), _down(OverNone), _lastAction(-st::mvDeltaFromLastAction, -st::mvDeltaFromLastAction), _ignoringDropdown(false),
 _controlsState(ControlsShown), _controlsAnimStarted(0),
@@ -85,6 +88,10 @@ _saveMsgStarted(0), _saveMsgOpacity(0)
 	_touchTimer.setSingleShot(true);
 	connect(&_touchTimer, SIGNAL(timeout()), this, SLOT(onTouchTimer()));
 
+	connect(&_currentGif, SIGNAL(updated()), this, SLOT(onGifUpdated()));
+
+	_btns.push_back(_btnSaveCancel = _dropdown.addButton(new IconedButton(this, st::mvButton, lang(lng_cancel))));
+	connect(_btnSaveCancel, SIGNAL(clicked()), this, SLOT(onSaveCancel()));
 	_btns.push_back(_btnToMessage = _dropdown.addButton(new IconedButton(this, st::mvButton, lang(lng_context_to_msg))));
 	connect(_btnToMessage, SIGNAL(clicked()), this, SLOT(onToMessage()));
 	_btns.push_back(_btnShowInFolder = _dropdown.addButton(new IconedButton(this, st::mvButton, lang(cPlatform() == dbipMac ? lng_context_show_in_finder : lng_context_show_in_folder))));
@@ -96,7 +103,7 @@ _saveMsgStarted(0), _saveMsgOpacity(0)
 	_btns.push_back(_btnDelete = _dropdown.addButton(new IconedButton(this, st::mvButton, lang(lng_mediaview_delete))));
 	connect(_btnDelete, SIGNAL(clicked()), this, SLOT(onDelete()));
 	_btns.push_back(_btnSaveAs = _dropdown.addButton(new IconedButton(this, st::mvButton, lang(lng_mediaview_save_as))));
-	connect(_btnSaveAs, SIGNAL(clicked()), this, SLOT(onSave()));
+	connect(_btnSaveAs, SIGNAL(clicked()), this, SLOT(onSaveAs()));
 	_btns.push_back(_btnViewAll = _dropdown.addButton(new IconedButton(this, st::mvButton, lang(lng_mediaview_photos_all))));
 	connect(_btnViewAll, SIGNAL(clicked()), this, SLOT(onOverview()));
 
@@ -105,6 +112,10 @@ _saveMsgStarted(0), _saveMsgOpacity(0)
 
 	_controlsHideTimer.setSingleShot(true);
 	connect(&_controlsHideTimer, SIGNAL(timeout()), this, SLOT(onHideControls()));
+
+	connect(&_docDownload, SIGNAL(clicked()), this, SLOT(onDownload()));
+	connect(&_docSaveAs, SIGNAL(clicked()), this, SLOT(onSaveAs()));
+	connect(&_docCancel, SIGNAL(clicked()), this, SLOT(onSaveCancel()));
 }
 
 void MediaView::moveToScreen() {
@@ -149,6 +160,22 @@ void MediaView::mediaOverviewUpdated(PeerData *peer) {
 	}
 }
 
+void MediaView::documentUpdated(DocumentData *doc) {
+	if (_doc && _doc == doc && _current.isNull() && _currentGif.isNull()) {
+		if ((_doc->loader && _docCancel.isHidden()) || (!_doc->loader && !_docCancel.isHidden())) {
+			updateControls();
+		} else if (_doc->loader) {
+			updateDocSize();
+			update(_docRect);
+		}
+	}
+}
+
+void MediaView::onGifUpdated() {
+	_currentGif.frames[_currentGif.frame].setDevicePixelRatio(cRetinaFactor());
+	update(_x, _y, _w, _h);
+}
+
 void MediaView::changingMsgId(HistoryItem *row, MsgId newId) {
 	if (row->id == _msgid) {
 		_msgid = newId;
@@ -156,10 +183,68 @@ void MediaView::changingMsgId(HistoryItem *row, MsgId newId) {
 	mediaOverviewUpdated(row->history()->peer);
 }
 
+void MediaView::updateDocSize() {
+	if (!_doc || !_current.isNull() || !_currentGif.isNull()) return;
+
+	if (_doc->loader) {
+		quint64 ready = _doc->loader->currentOffset(), total = _doc->size;
+		QString readyStr, totalStr, mb;
+		if (total >= 1024 * 1024) { // more than 1 mb
+			qint64 readyTenthMb = (ready * 10 / (1024 * 1024)), totalTenthMb = (total * 10 / (1024 * 1024));
+			readyStr = QString::number(readyTenthMb / 10) + '.' + QString::number(readyTenthMb % 10);
+			totalStr = QString::number(totalTenthMb / 10) + '.' + QString::number(totalTenthMb % 10);
+			mb = qsl("MB");
+		} else {
+			qint64 readyKb = (ready / 1024), totalKb = (total / 1024);
+			readyStr = QString::number(readyKb);
+			totalStr = QString::number(totalKb);
+			mb = qsl("KB");
+		}
+		_docSize = lng_media_save_progress(lt_ready, readyStr, lt_total, totalStr, lt_mb, mb);
+	} else {
+		_docSize = formatSizeText(_doc->size);
+	}
+	_docSizeWidth = st::mvFont->m.width(_docSize);
+	int32 maxw = st::mvDocSize.width() - st::mvDocBlue.pxWidth() - st::mvDocPadding * 3;
+	if (_docSizeWidth > maxw) {
+		_docSize = st::mvFont->m.elidedText(_docSize, Qt::ElideRight, maxw);
+		_docSizeWidth = st::mvFont->m.width(_docSize);
+	}
+}
+
 void MediaView::updateControls() {
 	if (!_photo && !_doc) return;
 
-	_saveVisible = ((_photo && _photo->full->loaded()) || (_doc && !_doc->already(true).isEmpty()));
+	if (_doc && _current.isNull() && _currentGif.isNull()) {
+		if (_doc->loader) {
+			_docDownload.hide();
+			_docSaveAs.hide();
+			_docCancel.moveToLeft(_docRect.x() + 2 * st::mvDocPadding + st::mvDocBlue.pxWidth(), _docRect.y() + st::mvDocPadding + st::mvDocLinksTop, width());
+			_docCancel.show();
+			if (!_docRadialFirst) _docRadialFirst = _docRadialLast = _docRadialStart = getms();
+			if (!animating()) anim::start(this);
+		} else {
+			if (_doc->already(true).isEmpty()) {
+				_docDownload.moveToLeft(_docRect.x() + 2 * st::mvDocPadding + st::mvDocBlue.pxWidth(), _docRect.y() + st::mvDocPadding + st::mvDocLinksTop, width());
+				_docDownload.show();
+				_docSaveAs.moveToLeft(_docRect.x() + 2.5 * st::mvDocPadding + st::mvDocBlue.pxWidth() + _docDownload.width(), _docRect.y() + st::mvDocPadding + st::mvDocLinksTop, width());
+				_docSaveAs.show();
+				_docCancel.hide();
+			} else {
+				_docDownload.hide();
+				_docSaveAs.moveToLeft(_docRect.x() + 2 * st::mvDocPadding + st::mvDocBlue.pxWidth(), _docRect.y() + st::mvDocPadding + st::mvDocLinksTop, width());
+				_docSaveAs.show();
+				_docCancel.hide();
+			}
+		}
+		updateDocSize();
+	} else {
+		_docDownload.hide();
+		_docSaveAs.hide();
+		_docCancel.hide();
+	}
+
+	_saveVisible = ((_photo && _photo->full->loaded()) || (_doc && (!_doc->already(true).isEmpty() || (_current.isNull() && _currentGif.isNull()))));
 	_saveNav = rtlrect(width() - st::mvIconSize.width() * 2, height() - st::mvIconSize.height(), st::mvIconSize.width(), st::mvIconSize.height(), width());
 	_moreNav = rtlrect(width() - st::mvIconSize.width(), height() - st::mvIconSize.height(), st::mvIconSize.width(), st::mvIconSize.height(), width());
 
@@ -196,6 +281,7 @@ void MediaView::updateControls() {
 }
 
 void MediaView::updateDropdown() {
+	_btnSaveCancel->setVisible(_doc && _doc->loader);
 	_btnToMessage->setVisible(_msgid > 0);
 	_btnShowInFolder->setVisible(_doc && !_doc->already(true).isEmpty());
 	_btnSaveAs->setVisible(true);
@@ -221,6 +307,7 @@ bool MediaView::animStep(float64 msp) {
 		case OverHeader: update(_headerNav); break;
 		case OverClose: update(_closeNav); break;
 		case OverSave: update(_saveNav); break;
+		case OverIcon: update(_docIconRect); break;
 		case OverMore: update(_moreNav); break;
 		default: break;
 		}
@@ -246,28 +333,39 @@ bool MediaView::animStep(float64 msp) {
 		if (dt < 1) result = true;
 	}
 	if (_doc && _docRadialStart > 0) {
-		float64 prg = _doc->loader ? _doc->loader->currentProgress() : (_doc->status == FileFailed ? 0 : 1);
+		float64 prg = _doc->loader ? qMax(_doc->loader->currentProgress(), 0.0001) : (_doc->status == FileFailed ? 0 : (_doc->already().isEmpty() ? 0 : 1));
 		if (prg != a_docRadial.to()) {
 			a_docRadial.start(prg);
 			_docRadialStart = _docRadialLast;
 		}
 		_docRadialLast = ms;
 
-		float64 dt = float64(ms - _docRadialStart);
+		float64 dt = float64(ms - _docRadialStart), fulldt = float64(ms - _docRadialFirst);
+		_docRadialOpacity = qMin(fulldt / st::radialDuration, 1.);
 		if (_doc->loader) {
 			a_docRadial.update(1. - (st::radialDuration / (st::radialDuration + dt)), anim::linear);
 			result = true;
 		} else if (dt >= st::radialDuration) {
 			a_docRadial.update(1, anim::linear);
 			result = true;
-//			_docRadialStart = 0;
+			_docRadialFirst = _docRadialLast = _docRadialStart = 0;
+			a_docRadial = anim::fvalue(0, 0);
+			if (!_doc->already().isEmpty() && _doc->size < MediaViewImageSizeLimit) {
+				QString fname(_doc->already(true));
+				QImageReader reader(fname);
+				if (reader.canRead()) {
+					displayDocument(_doc, App::histItemById(_msgid));
+				}
+			}
 		} else {
-			a_docRadial.update(dt / st::radialDuration, anim::linear);
+			float64 r = dt / st::radialDuration;
+			a_docRadial.update(r, anim::linear);
 			result = true;
+			_docRadialOpacity *= 1 - r;
 		}
-		float64 fromstart = float64(ms - _docRadialFirst) / st::radialPeriod;
+		float64 fromstart = fulldt / st::radialPeriod;
 		a_docRadialStart.update(fromstart - qFloor(fromstart), anim::linear);
-		update(_docIcon);
+		update(_docIconRect);
 	}
 	return result || !_animations.isEmpty();
 }
@@ -285,7 +383,6 @@ void MediaView::close() {
 }
 
 void MediaView::activateControls() {
-	LOG(("ACTIVATING CONTROLS!!"));
 	_controlsHideTimer.start(int(st::mvWaitHide));
 	if (_controlsState == ControlsHiding || _controlsState == ControlsHidden) {
 		_controlsState = ControlsShowing;
@@ -306,13 +403,11 @@ void MediaView::onHideControls(bool force) {
 
 void MediaView::onDropdownHiding() {
 	setFocus();
-	LOG(("DROPDOWN HIDDEN"));
 	_ignoringDropdown = true;
 	_lastMouseMovePos = mapFromGlobal(QCursor::pos());
 	updateOver(_lastMouseMovePos);
 	_ignoringDropdown = false;
 	if (!_controlsHideTimer.isActive()) {
-		LOG((", STARTING CONTROLS HIDE"));
 		onHideControls(true);
 	}
 }
@@ -327,23 +422,43 @@ void MediaView::onToMessage() {
 	}
 }
 
-void MediaView::onSave() {
+void MediaView::onSaveAs() {
 	QString file;
 	if (_doc) {
 		QString cur = _doc->already(true);
 		if (cur.isEmpty()) {
-			_saveVisible = false;
-			update(_saveNav);
+			if (_current.isNull() && _currentGif.isNull()) {
+				DocumentSaveLink::doSave(_doc, true);
+				updateControls();
+			} else {
+				_saveVisible = false;
+				update(_saveNav);
+			}
+			updateOver(_lastMouseMovePos);
 			return;
 		}
 
+		QFileInfo alreadyInfo(cur);
+		QDir alreadyDir(alreadyInfo.dir());
+		QString name = alreadyInfo.fileName(), filter;
+		MimeType mimeType = mimeTypeForName(_doc->mime);
+		QStringList p = mimeType.globPatterns();
+		QString pattern = p.isEmpty() ? QString() : p.front();
+		if (name.isEmpty()) {
+			name = pattern.isEmpty() ? qsl(".unknown") : pattern.replace('*', QString());
+		}
+
+		if (pattern.isEmpty()) {
+			filter = qsl("All files (*.*)");
+		} else {
+			filter = mimeType.filterString() + qsl(";;All files (*.*)");
+		}
+
 		psBringToBack(this);
-		bool gotName = filedialogGetSaveFile(file, lang(lng_save_photo), qsl("JPEG Image (*.jpg);;All files (*.*)"), cur);
+		file = saveFileName(lang(lng_save_file), filter, qsl("doc"), name, true, alreadyDir);
 		psShowOverAll(this);
-		if (gotName) {
-			if (!file.isEmpty() && file != cur) {
-				QFile(cur).copy(file);
-			}
+		if (!file.isEmpty() && file != cur) {
+			QFile(cur).copy(file);
 		}
 	} else {
 		if (!_photo || !_photo->full->loaded()) return;
@@ -359,9 +474,22 @@ void MediaView::onSave() {
 	}
 }
 
+void MediaView::onDocClick() {
+	QString fname = _doc->already(true);
+	if (fname.isEmpty()) {
+		if (_doc->loader) {
+			onSaveCancel();
+		} else {
+			onDownload();
+		}
+	} else {
+		psOpenFile(fname);
+	}
+}
+
 void MediaView::onDownload() {
 	if (cAskDownloadPath()) {
-		return onSave();
+		return onSaveAs();
 	}
 
 	QString path;
@@ -376,8 +504,14 @@ void MediaView::onDownload() {
 	if (_doc) {
 		QString cur = _doc->already(true);
 		if (cur.isEmpty()) {
-			_saveVisible = false;
-			update(_saveNav);
+			if (_current.isNull() && _currentGif.isNull()) {
+				DocumentSaveLink::doSave(_doc);
+				updateControls();
+			} else {
+				_saveVisible = false;
+				update(_saveNav);
+			}
+			updateOver(_lastMouseMovePos);
 		} else {
 			if (!QDir().exists(path)) QDir().mkpath(path);
 			toName = filedialogNextFilename(_doc->name, cur, path);
@@ -405,6 +539,12 @@ void MediaView::onDownload() {
 	}
 }
 
+void MediaView::onSaveCancel() {
+	if (_doc && _doc->loader) {
+		_doc->loader->cancel();
+	}
+}
+
 void MediaView::onShowInFolder() {
 	if (!_doc) return;
 	QString already(_doc->already(true));
@@ -457,6 +597,8 @@ void MediaView::onCopy() {
 		_dropdown.hideStart();
 	}
 	if (_doc) {
+		if (_current.isNull()) return;
+
 		QApplication::clipboard()->setPixmap(_current);
 	} else {
 		if (!_photo || !_photo->full->loaded()) return;
@@ -579,6 +721,7 @@ void MediaView::displayPhoto(PhotoData *photo) {
 	MTP::clearLoaderPriorities();
 	_full = -1;
 	_current = QPixmap();
+	_currentGif.stop();
 	_down = OverNone;
 	_w = convertScale(photo->full->width());
 	_h = convertScale(photo->full->height());
@@ -621,12 +764,31 @@ void MediaView::displayPhoto(PhotoData *photo) {
 void MediaView::displayDocument(DocumentData *doc, HistoryItem *item) {
 	_doc = doc;
 
-	_docRadialFirst = _docRadialLast = _docRadialStart = _doc->loader ? getms() : 0;
-
 	QString already = _doc->already(true);
-	QPixmap pix = (_doc->sticker->isNull() || !_doc->sticker->loaded()) ? (already.isEmpty() ? QPixmap() : QPixmap::fromImage(App::readImage(already, 0, false), Qt::ColorOnly)) : _doc->sticker->pix();
-	_current = pix;
-	if (_current.isNull()) {
+	if (!_doc->sticker->isNull() && _doc->sticker->loaded()) {
+		_currentGif.stop();
+		_current = _doc->sticker->pix();
+	} else if (!already.isEmpty()) {
+		QImageReader reader(already);
+		if (reader.canRead()) {
+			if (reader.supportsAnimation() && reader.imageCount() > 1) {
+				_currentGif.start(0, already);
+				_current = QPixmap();
+			} else {
+				_currentGif.stop();
+				QPixmap pix = QPixmap::fromImage(App::readImage(already, 0, false), Qt::ColorOnly);
+				_current = pix;
+			}
+		} else {
+			_currentGif.stop();
+			_current = QPixmap();
+		}
+	} else {
+		_currentGif.stop();
+		_current = QPixmap();
+	}
+
+	if (_current.isNull() && _currentGif.isNull()) {
 		if (_doc->thumb->isNull()) {
 			style::sprite thumbs[] = { st::mvDocBlue, st::mvDocGreen, st::mvDocRed, st::mvDocYellow };
 			QString name = _doc->name.toLower(), mime = _doc->mime.toLower();
@@ -680,24 +842,38 @@ void MediaView::displayDocument(DocumentData *doc, HistoryItem *item) {
 		int32 maxw = st::mvDocSize.width() - st::mvDocBlue.pxWidth() - st::mvDocPadding * 3;
 
 		_docName = _doc->name.isEmpty() ? lang(_doc->type == StickerDocument ? lng_in_dlg_sticker : lng_mediaview_doc_image) : _doc->name;
-		_docNameWidth = st::mvThickFont->m.width(_docName);
+		int32 lastDot = _docName.lastIndexOf('.');
+		_docExt = (lastDot < 0 || lastDot + 2 > _docName.size()) ? _docName : _docName.mid(lastDot + 1);
+		_docNameWidth = st::mvDocNameFont->m.width(_docName);
 		if (_docNameWidth > maxw) {
-			_docName = st::mvThickFont->m.elidedText(_docName, Qt::ElideRight, maxw);
-			_docNameWidth = st::mvThickFont->m.width(_docName);
+			_docName = st::mvDocNameFont->m.elidedText(_docName, Qt::ElideMiddle, maxw);
+			_docNameWidth = st::mvDocNameFont->m.width(_docName);
 		}
 
-		_docSize = formatSizeText(_doc->size);
-		_docSizeWidth = st::mvFont->m.width(_docSize);
-		if (_docSizeWidth > maxw) {
-			_docSize = st::mvFont->m.elidedText(_docSize, Qt::ElideRight, maxw);
-			_docSizeWidth = st::mvFont->m.width(_docSize);
+		int32 extmaxw = (st::mvDocBlue.pxWidth() - st::mvDocExtPadding * 2);
+
+		_docExtWidth = st::mvDocExtFont->m.width(_docExt);
+		if (_docExtWidth > extmaxw) {
+			_docExt = st::mvDocNameFont->m.elidedText(_docExt, Qt::ElideMiddle, extmaxw);
+			_docExtWidth = st::mvDocNameFont->m.width(_docExt);
 		}
 
+		_docRadialFirst = _docRadialLast = _docRadialStart = 0;
+		
+		float64 prg = _doc->loader ? _doc->loader->currentProgress() : 0;
+		a_docRadial = anim::fvalue(prg, qMax(prg, 0.0001));
+		// _docSize is updated in updateControls()
+
 		_docRect = QRect((width() - st::mvDocSize.width()) / 2, (height() - st::mvDocSize.height()) / 2, st::mvDocSize.width(), st::mvDocSize.height());
-	} else {
+		_docIconRect = rtlrect(_docRect.x() + st::mvDocPadding, _docRect.y() + st::mvDocPadding, st::mvDocBlue.pxWidth(), st::mvDocBlue.pxHeight(), width());
+	} else if (!_current.isNull()) {
 		_current.setDevicePixelRatio(cRetinaFactor());
 		_w = _current.width() / cIntRetinaFactor();
 		_h = _current.height() / cIntRetinaFactor();
+	} else {
+		_currentGif.frames[_currentGif.frame].setDevicePixelRatio(cRetinaFactor());
+		_w = _currentGif.frames[_currentGif.frame].width() / cIntRetinaFactor();
+		_h = _currentGif.frames[_currentGif.frame].height() / cIntRetinaFactor();
 	}
 	if (isHidden()) {
 		moveToScreen();
@@ -776,21 +952,21 @@ void MediaView::paintEvent(QPaintEvent *e) {
 			if (cRetina()) _current.setDevicePixelRatio(cRetinaFactor());
 		}
 	}
-	if (_photo || !_current.isNull()) {
-		p.setOpacity(1);
-
+	p.setOpacity(1);
+	if (_photo || !_current.isNull() || !_currentGif.isNull()) {
 		QRect imgRect(_x, _y, _w, _h);
+		const QPixmap *toDraw = _currentGif.isNull() ? &_current : &_currentGif.frames[_currentGif.frame];
 		if (imgRect.intersects(r)) {
-			if (_current.hasAlpha() && (!_doc || _doc->sticker->isNull())) {
+			if (toDraw->hasAlpha() && (!_doc || _doc->sticker->isNull())) {
 				p.fillRect(imgRect, _transparentBrush);
 			}
 			if (_zoom) {
 				bool was = (p.renderHints() & QPainter::SmoothPixmapTransform);
 				if (!was) p.setRenderHint(QPainter::SmoothPixmapTransform, true);
-				p.drawPixmap(QRect(_x, _y, _w, _h), _current);
+				p.drawPixmap(QRect(_x, _y, _w, _h), *toDraw);
 				if (!was) p.setRenderHint(QPainter::SmoothPixmapTransform, false);
 			} else {
-				p.drawPixmap(_x, _y, _current);
+				p.drawPixmap(_x, _y, *toDraw);
 			}
 
 			uint64 ms = 0;
@@ -849,43 +1025,65 @@ void MediaView::paintEvent(QPaintEvent *e) {
 				}
 			}
 		}
-	} else {
+	} else if (_doc) {
 		if (_docRect.intersects(r)) {
 			p.fillRect(_docRect, st::mvDocBg->b);
-			QRect thumb = rtlrect(_docRect.x() + st::mvDocPadding, _docRect.y() + st::mvDocPadding, st::mvDocBlue.pxWidth(), st::mvDocBlue.pxHeight(), width());
-			if (_doc->thumb->isNull()) {
-				p.drawPixmap(thumb.topLeft(), App::sprite(), _docIcon);
-			} else {
-				int32 rf(cIntRetinaFactor());
-				p.drawPixmap(thumb.topLeft(), _doc->thumb->pix(_docThumbw), QRect(_docThumbx * rf, _docThumby * rf, st::mvDocBlue.pxWidth() * rf, st::mvDocBlue.pxHeight() * rf));
+			if (_docIconRect.intersects(r)) {
+				if (_doc->thumb->isNull()) {
+					p.drawPixmap(_docIconRect.topLeft(), App::sprite(), _docIcon);
+					if (!_doc->already().isEmpty() && (!_docRadialStart || _docRadialOpacity < 1)) {
+						p.setPen(st::mvDocExtColor->p);
+						p.setFont(st::mvDocExtFont->f);
+						p.drawText(_docIconRect.x() + (_docIconRect.width() - _docExtWidth) / 2, _docIconRect.y() + st::mvDocExtTop + st::mvDocExtFont->ascent, _docExt);
+					}
+				} else {
+					int32 rf(cIntRetinaFactor());
+					p.drawPixmap(_docIconRect.topLeft(), _doc->thumb->pix(_docThumbw), QRect(_docThumbx * rf, _docThumby * rf, st::mvDocBlue.pxWidth() * rf, st::mvDocBlue.pxHeight() * rf));
+				}
+
+				float64 o = overLevel(OverIcon);
+				if (_docRadialStart > 0) {
+					if (_doc->already().isEmpty() && _docRadialOpacity < 1) {
+						p.setOpacity((o * 1. + (1 - o) * st::radialDownloadOpacity) * (1 - _docRadialOpacity));
+						p.drawSpriteCenter(_docIconRect, st::radialDownload);
+					}
+
+					p.setRenderHint(QPainter::HighQualityAntialiasing);
+
+					QRect inner(QPoint(_docIconRect.x() + ((_docIconRect.width() - st::radialSize.width()) / 2), _docIconRect.y() + ((_docIconRect.height() - st::radialSize.height()) / 2)), st::radialSize);
+					p.setPen(Qt::NoPen);
+					p.setBrush(st::black->b);
+					p.setOpacity(_docRadialOpacity * st::radialBgOpacity);
+					p.drawEllipse(inner);
+
+					p.setOpacity((o * 1. + (1 - o) * st::radialCancelOpacity) * _docRadialOpacity);
+					p.drawSpriteCenter(_docIconRect, st::radialCancel);
+
+					QRect arc(inner.marginsRemoved(QMargins(st::radialLine, st::radialLine, st::radialLine, st::radialLine)));
+
+					p.setOpacity(_docRadialOpacity);
+					p.setPen(_docRadialPen);
+
+					int len = 16 + a_docRadial.current() * 5744;
+					p.drawArc(arc, 1440 - a_docRadialStart.current() * 5760 - len, len);
+
+					p.setOpacity(1);
+					p.setRenderHint(QPainter::HighQualityAntialiasing, false);
+				} else if (_doc->already().isEmpty()) {
+					p.setOpacity((o * 1. + (1 - o) * st::radialDownloadOpacity));
+					p.drawSpriteCenter(_docIconRect, st::radialDownload);
+				}
 			}
 
-			if (_doc && _docRadialStart > 0) {
-				p.setRenderHint(QPainter::HighQualityAntialiasing);
+			if (!_docIconRect.contains(r)) {
+				p.setPen(st::mvDocNameColor->p);
+				p.setFont(st::mvDocNameFont->f);
+				p.drawTextLeft(_docRect.x() + 2 * st::mvDocPadding + st::mvDocBlue.pxWidth(), _docRect.y() + st::mvDocPadding + st::mvDocNameTop, width(), _docName, _docNameWidth);
 
-				QRect inner(QPoint(thumb.x() + ((thumb.width() - st::radialSize.width()) / 2), thumb.y() + ((thumb.height() - st::radialSize.height()) / 2)), st::radialSize);
-				p.setPen(Qt::NoPen);
-				p.setBrush(st::black->b);
-				p.setOpacity(st::radialBgOpacity);
-				p.drawEllipse(inner);
-
-				QRect arc(inner.marginsRemoved(QMargins(st::radialLine / 2, st::radialLine / 2, st::radialLine / 2, st::radialLine / 2)));
-				p.setOpacity(1);
-
-				p.setPen(_docRadialPen);
-
-				p.drawArc(arc, a_docRadialStart.current() * 5600, 10 + a_docRadial.current() * 5490);
-
-				p.setRenderHint(QPainter::HighQualityAntialiasing, false);
+				p.setPen(st::mvDocSizeColor->p);
+				p.setFont(st::mvFont->f);
+				p.drawTextLeft(_docRect.x() + 2 * st::mvDocPadding + st::mvDocBlue.pxWidth(), _docRect.y() + st::mvDocPadding + st::mvDocSizeTop, width(), _docSize, _docSizeWidth);
 			}
-
-			p.setPen(st::mvDocNameColor->p);
-			p.setFont(st::mvThickFont->f);
-			p.drawTextLeft(_docRect.x() + 2 * st::mvDocPadding + st::mvDocBlue.pxWidth(), _docRect.y() + st::mvDocPadding + st::mvDocNameTop, width(), _docName, _docNameWidth);
-
-			p.setPen(st::mvDocSizeColor->p);
-			p.setFont(st::mvFont->f);
-			p.drawTextLeft(_docRect.x() + 2 * st::mvDocPadding + st::mvDocBlue.pxWidth(), _docRect.y() + st::mvDocPadding + st::mvDocSizeTop, width(), _docSize, _docSizeWidth);
 		}
 	}
 
@@ -1003,9 +1201,13 @@ void MediaView::keyPressEvent(QKeyEvent *e) {
 	if (!_menu && e->key() == Qt::Key_Escape) {
 		close();
 	} else if (e == QKeySequence::Save || e == QKeySequence::SaveAs) {
-		onSave();
+		onSaveAs();
 	} else if (e->key() == Qt::Key_Copy || (e->key() == Qt::Key_C && e->modifiers().testFlag(Qt::ControlModifier))) {
 		onCopy();
+	} else if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return || e->key() == Qt::Key_Space) {
+		if (_doc && !_doc->loader && _current.isNull() && _currentGif.isNull()) {
+			onDocClick();
+		}
 	} else if (e->key() == Qt::Key_Left) {
 		moveToNext(-1);
 	} else if (e->key() == Qt::Key_Right) {
@@ -1047,7 +1249,7 @@ void MediaView::keyPressEvent(QKeyEvent *e) {
 				newZoom = 0;
 			}
 			_x = -_width / 2;
-			_y = -((_current.height() / cIntRetinaFactor()) / 2);
+			_y = -(((_currentGif.isNull() ? _current.height() : _currentGif.frames[_currentGif.frame].height()) / cIntRetinaFactor()) / 2);
 			float64 z = (_zoom == ZoomToScreenLevel) ? _zoomToScreen : _zoom;
 			if (z >= 0) {
 				_x = qRound(_x * (z + 1));
@@ -1067,8 +1269,8 @@ void MediaView::keyPressEvent(QKeyEvent *e) {
 		}
 		if (_zoom != newZoom) {
 			float64 nx, ny, z = (_zoom == ZoomToScreenLevel) ? _zoomToScreen : _zoom;
-			_w = _current.width() / cIntRetinaFactor();
-			_h = _current.height() / cIntRetinaFactor();
+			_w = (_currentGif.isNull() ? _current.width() : _currentGif.frames[_currentGif.frame].width()) / cIntRetinaFactor();
+			_h = (_currentGif.isNull() ? _current.height() : _currentGif.frames[_currentGif.frame].height()) / cIntRetinaFactor();
 			if (z >= 0) {
 				nx = (_x - width() / 2.) / (z + 1);
 				ny = (_y - height() / 2.) / (z + 1);
@@ -1195,6 +1397,8 @@ void MediaView::mousePressEvent(QMouseEvent *e) {
 				_down = OverHeader;
 			} else if (_over == OverSave) {
 				_down = OverSave;
+			} else if (_over == OverIcon) {
+				_down = OverIcon;
 			} else if (_over == OverMore) {
 				_down = OverMore;
 			} else if (_over == OverClose) {
@@ -1227,7 +1431,6 @@ void MediaView::snapXY() {
 
 void MediaView::mouseMoveEvent(QMouseEvent *e) {
 	bool moved = (e->pos() != _lastMouseMovePos);
-	LOG(("MOUSE MOVE: WAS %1 %2 NOW %3 %4 MOVED: %5").arg(_lastMouseMovePos.x()).arg(_lastMouseMovePos.y()).arg(e->pos().x()).arg(e->pos().y()).arg(logBool(moved)));
 	_lastMouseMovePos = e->pos();
 
 	updateOver(e->pos());
@@ -1258,7 +1461,6 @@ void MediaView::mouseMoveEvent(QMouseEvent *e) {
 bool MediaView::updateOverState(OverState newState) {
 	bool result = true;
 	if (_over != newState) {
-		LOG(("UPDATING STATE TO %1, IGNORING: %2").arg(newState).arg(logBool(_ignoringDropdown)));
 		if (newState == OverMore && !_ignoringDropdown) {
 			QTimer::singleShot(0, this, SLOT(onDropdown()));
 		} else if (newState == OverNone) {
@@ -1272,6 +1474,8 @@ bool MediaView::updateOverState(OverState newState) {
 				update(_dateNav);
 			} else if (_over == OverSave) {
 				update(_saveNav);
+			} else if (_over == OverIcon) {
+				update(_docIconRect);
 			} else if (_over == OverHeader) {
 				update(_headerNav);
 			} else if (_over == OverClose) {
@@ -1349,6 +1553,10 @@ void MediaView::updateOver(const QPoint &pos) {
 		if (!updateOverState(OverSave)) {
 			update(_saveNav);
 		}
+	} else if (_doc && _current.isNull() && _currentGif.isNull() && _docIconRect.contains(pos)) {
+		if (!updateOverState(OverIcon)) {
+			update(_docIconRect);
+		}
 	} else if (_moreNav.contains(pos)) {
 		if (!updateOverState(OverMore)) {
 			update(_moreNav);
@@ -1379,6 +1587,8 @@ void MediaView::mouseReleaseEvent(QMouseEvent *e) {
 		onOverview();
 	} else if (_over == OverSave && _down == OverSave) {
 		onDownload();
+	} else if (_over == OverIcon && _down == OverIcon) {
+		onDocClick();
 	} else if (_over == OverMore && _down == OverMore) {
 		QTimer::singleShot(0, this, SLOT(onDropdown()));
 	} else if (_over == OverClose && _down == OverClose) {
@@ -1393,7 +1603,7 @@ void MediaView::mouseReleaseEvent(QMouseEvent *e) {
 			}
 			_dragging = 0;
 			setCursor(style::cur_default);
-		} else if ((e->pos() - _lastAction).manhattanLength() >= st::mvDeltaFromLastAction) {
+		} else if ((e->pos() - _lastAction).manhattanLength() >= st::mvDeltaFromLastAction && (!_doc || !_current.isNull() || !_currentGif.isNull() || !_docRect.contains(e->pos()))) {
 			close();
 		}
 		_pressed = false;
diff --git a/Telegram/SourceFiles/mediaview.h b/Telegram/SourceFiles/mediaview.h
index 79a02a72e5..103a490160 100644
--- a/Telegram/SourceFiles/mediaview.h
+++ b/Telegram/SourceFiles/mediaview.h
@@ -56,7 +56,9 @@ public:
 	}
 
 	void mediaOverviewUpdated(PeerData *peer);
+	void documentUpdated(DocumentData *doc);
 	void changingMsgId(HistoryItem *row, MsgId newId);
+	void updateDocSize();
 	void updateControls();
 	void updateDropdown();
 
@@ -66,6 +68,7 @@ public:
 	void close();
 
 	void activateControls();
+	void onDocClick();
 
 	~MediaView();
 
@@ -75,8 +78,9 @@ public slots:
 	void onDropdownHiding();
 
 	void onToMessage();
-	void onSave();
+	void onSaveAs();
 	void onDownload();
+	void onSaveCancel();
 	void onShowInFolder();
 	void onForward();
 	void onDelete();
@@ -91,6 +95,7 @@ public slots:
 	void onTouchTimer();
 
 	void updateImage();
+	void onGifUpdated();
 
 private:
 
@@ -125,16 +130,19 @@ private:
 	bool _pressed;
 	int32 _dragging;
 	QPixmap _current;
+	AnimatedGif _currentGif;
 	int32 _full; // -1 - thumb, 0 - medium, 1 - full
 
 	style::sprite _docIcon;
-	QString _docName, _docSize;
-	int32 _docNameWidth, _docSizeWidth;
-	QRect _docRect;
+	QString _docName, _docSize, _docExt;
+	int32 _docNameWidth, _docSizeWidth, _docExtWidth;
+	QRect _docRect, _docIconRect;
 	int32 _docThumbx, _docThumby, _docThumbw;
 	uint64 _docRadialFirst, _docRadialStart, _docRadialLast;
+	float64 _docRadialOpacity;
 	QPen _docRadialPen;
 	anim::fvalue a_docRadial, a_docRadialStart;
+	LinkButton _docDownload, _docSaveAs, _docCancel;
 
 	History *_history; // if conversation photos or files overview
 	PeerData *_peer;
@@ -158,6 +166,7 @@ private:
 		OverDate,
 		OverSave,
 		OverMore,
+		OverIcon,
 	};
 	OverState _over, _down;
 	QPoint _lastAction, _lastMouseMovePos;
@@ -176,7 +185,7 @@ private:
 
 	ContextMenu *_menu;
 	Dropdown _dropdown;
-	IconedButton *_btnToMessage, *_btnShowInFolder, *_btnSaveAs, *_btnCopy, *_btnForward, *_btnDelete, *_btnViewAll;
+	IconedButton *_btnSaveCancel, *_btnToMessage, *_btnShowInFolder, *_btnSaveAs, *_btnCopy, *_btnForward, *_btnDelete, *_btnViewAll;
 	QList<IconedButton*> _btns;
 
 	bool _receiveMouse;
diff --git a/Telegram/SourceFiles/overviewwidget.cpp b/Telegram/SourceFiles/overviewwidget.cpp
index 980fb4fb45..4d17ccba6d 100644
--- a/Telegram/SourceFiles/overviewwidget.cpp
+++ b/Telegram/SourceFiles/overviewwidget.cpp
@@ -1225,9 +1225,9 @@ void OverviewInner::saveContextFile() {
 	VideoLink *lnkVideo = dynamic_cast<VideoLink*>(_contextMenuLnk.data());
 	AudioLink *lnkAudio = dynamic_cast<AudioLink*>(_contextMenuLnk.data());
 	DocumentLink *lnkDocument = dynamic_cast<DocumentLink*>(_contextMenuLnk.data());
-	if (lnkVideo) VideoSaveLink(lnkVideo->video()).doSave(true);
-	if (lnkAudio) AudioSaveLink(lnkAudio->audio()).doSave(true);
-	if (lnkDocument) DocumentSaveLink(lnkDocument->document()).doSave(true);
+	if (lnkVideo) VideoSaveLink::doSave(lnkVideo->video(), true);
+	if (lnkAudio) AudioSaveLink::doSave(lnkAudio->audio(), true);
+	if (lnkDocument) DocumentSaveLink::doSave(lnkDocument->document(), true);
 }
 
 void OverviewInner::openContextFile() {
@@ -1355,6 +1355,7 @@ void OverviewInner::mediaOverviewUpdated() {
 				prevDate = date;
 			}
 			int32 w = _width - st::msgMargin.left() - st::msgMargin.right();
+			media->initDimensions(item);
 			y += media->countHeight(item, w) + st::msgMargin.top() + st::msgMargin.bottom(); // item height
 			if (_items.size() > in) {
 				_items[in].msgid = msgid;
diff --git a/Telegram/SourceFiles/settings.cpp b/Telegram/SourceFiles/settings.cpp
index a2821b219d..01b1b987d2 100644
--- a/Telegram/SourceFiles/settings.cpp
+++ b/Telegram/SourceFiles/settings.cpp
@@ -20,6 +20,9 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
 #include "settings.h"
 #include "lang.h"
 
+bool gRtl = false;
+Qt::LayoutDirection gLangDir = Qt::LeftToRight;
+
 mtpDcOptions gDcOptions;
 
 bool gTestMode = false;
diff --git a/Telegram/SourceFiles/settings.h b/Telegram/SourceFiles/settings.h
index 0cf7eb7d05..5898e73605 100644
--- a/Telegram/SourceFiles/settings.h
+++ b/Telegram/SourceFiles/settings.h
@@ -41,6 +41,12 @@ inline void cSet##Name(const Type &Name) { \
 	g##Name = Name; \
 }
 
+DeclareSetting(bool, Rtl);
+DeclareSetting(Qt::LayoutDirection, LangDir);
+inline bool rtl() {
+	return cRtl();
+}
+
 struct mtpDcOption {
 	mtpDcOption(int _id, const string &_host, const string &_ip, int _port) : id(_id), host(_host), ip(_ip), port(_port) {
 	}
diff --git a/Telegram/SourceFiles/stdafx.h b/Telegram/SourceFiles/stdafx.h
index ed30365cd5..bb7cc5f4ba 100644
--- a/Telegram/SourceFiles/stdafx.h
+++ b/Telegram/SourceFiles/stdafx.h
@@ -51,9 +51,8 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
 
 #include "mtproto/mtp.h"
 
-#include "gui/twidget.h"
-
 #include "gui/style_core.h"
+#include "gui/twidget.h"
 #include "gui/animation.h"
 #include "gui/flatinput.h"
 #include "gui/flattextarea.h"
diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp
index f797f9336a..cdcf7b5d46 100644
--- a/Telegram/SourceFiles/structs.cpp
+++ b/Telegram/SourceFiles/structs.cpp
@@ -230,7 +230,7 @@ void PhotoLink::onClick(Qt::MouseButton button) const {
 	}
 }
 
-QString saveFileName(const QString &title, const QString &filter, const QString &prefix, QString name, bool savingAs, const QDir &dir = QDir()) {
+QString saveFileName(const QString &title, const QString &filter, const QString &prefix, QString name, bool savingAs, const QDir &dir) {
 #ifdef Q_OS_WIN
 	name = name.replace(QRegularExpression(qsl("[\\\\\\/\\:\\*\\?\\\"\\<\\>\\|]")), qsl("_"));
 #elif defined Q_OS_MAC
@@ -307,8 +307,7 @@ void VideoOpenLink::onClick(Qt::MouseButton button) const {
 	}
 }
 
-void VideoSaveLink::doSave(bool forceSavingAs) const {
-	VideoData *data = video();
+void VideoSaveLink::doSave(VideoData *data, bool forceSavingAs) {
 	if (!data->user && !data->date) return;
 
 	QString already = data->already(true);
@@ -336,7 +335,7 @@ void VideoSaveLink::doSave(bool forceSavingAs) const {
 
 void VideoSaveLink::onClick(Qt::MouseButton button) const {
 	if (button != Qt::LeftButton) return;
-	doSave();
+	doSave(video());
 }
 
 void VideoCancelLink::onClick(Qt::MouseButton button) const {
@@ -399,8 +398,7 @@ void AudioOpenLink::onClick(Qt::MouseButton button) const {
 	}
 }
 
-void AudioSaveLink::doSave(bool forceSavingAs) const {
-	AudioData *data = audio();
+void AudioSaveLink::doSave(AudioData *data, bool forceSavingAs) {
 	if (!data->user && !data->date) return;
 
 	QString already = data->already(true);
@@ -429,7 +427,7 @@ void AudioSaveLink::doSave(bool forceSavingAs) const {
 
 void AudioSaveLink::onClick(Qt::MouseButton button) const {
 	if (button != Qt::LeftButton) return;
-	doSave();
+	doSave(audio());
 }
 
 void AudioCancelLink::onClick(Qt::MouseButton button) const {
@@ -505,8 +503,7 @@ void DocumentOpenLink::onClick(Qt::MouseButton button) const {
 	}
 }
 
-void DocumentSaveLink::doSave(bool forceSavingAs) const {
-	DocumentData *data = document();
+void DocumentSaveLink::doSave(DocumentData *data, bool forceSavingAs) {
 	if (!data->date) return;
 
 	QString already = data->already(true);
@@ -547,7 +544,7 @@ void DocumentSaveLink::doSave(bool forceSavingAs) const {
 
 void DocumentSaveLink::onClick(Qt::MouseButton button) const {
 	if (button != Qt::LeftButton) return;
-	doSave();
+	doSave(document());
 }
 
 void DocumentCancelLink::onClick(Qt::MouseButton button) const {
diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h
index 0b35db792f..8ba6657a94 100644
--- a/Telegram/SourceFiles/structs.h
+++ b/Telegram/SourceFiles/structs.h
@@ -291,7 +291,7 @@ class VideoSaveLink : public VideoLink {
 public:
 	VideoSaveLink(VideoData *video) : VideoLink(video) {
 	}
-	void doSave(bool forceSavingAs = false) const;
+	static void doSave(VideoData *video, bool forceSavingAs = false);
 	void onClick(Qt::MouseButton button) const;
 };
 
@@ -378,7 +378,7 @@ class AudioSaveLink : public AudioLink {
 public:
 	AudioSaveLink(AudioData *audio) : AudioLink(audio) {
 	}
-	void doSave(bool forceSavingAs = false) const;
+	static void doSave(AudioData *audio, bool forceSavingAs = false);
 	void onClick(Qt::MouseButton button) const;
 };
 
@@ -481,7 +481,7 @@ class DocumentSaveLink : public DocumentLink {
 public:
 	DocumentSaveLink(DocumentData *document) : DocumentLink(document) {
 	}
-	void doSave(bool forceSavingAs = false) const;
+	static void doSave(DocumentData *document, bool forceSavingAs = false);
 	void onClick(Qt::MouseButton button) const;
 };
 
@@ -528,6 +528,7 @@ struct WebPageData {
 	int32 pendingTill;
 };
 
+QString saveFileName(const QString &title, const QString &filter, const QString &prefix, QString name, bool savingAs, const QDir &dir = QDir());
 MsgId clientMsgId();
 
 struct MessageCursor {
diff --git a/Telegram/SourceFiles/window.cpp b/Telegram/SourceFiles/window.cpp
index 8c07243726..fb19b8ee1f 100644
--- a/Telegram/SourceFiles/window.cpp
+++ b/Telegram/SourceFiles/window.cpp
@@ -1667,6 +1667,11 @@ void Window::mediaOverviewUpdated(PeerData *peer) {
 	_mediaView->mediaOverviewUpdated(peer);
 }
 
+void Window::documentUpdated(DocumentData *doc) {
+	if (!_mediaView || _mediaView->isHidden()) return;
+	_mediaView->documentUpdated(doc);
+}
+
 void Window::changingMsgId(HistoryItem *row, MsgId newId) {
 	if (main) main->changingMsgId(row, newId);
 	if (!_mediaView || _mediaView->isHidden()) return;
diff --git a/Telegram/SourceFiles/window.h b/Telegram/SourceFiles/window.h
index 595379c441..091a97af4f 100644
--- a/Telegram/SourceFiles/window.h
+++ b/Telegram/SourceFiles/window.h
@@ -227,6 +227,7 @@ public:
 	void sendPaths();
 
 	void mediaOverviewUpdated(PeerData *peer);
+	void documentUpdated(DocumentData *doc);
 	void changingMsgId(HistoryItem *row, MsgId newId);
 
 	bool isActive(bool cached = true) const;