/**********************************************************************
 * LeechCraft - modular cross-platform feature rich internet client.
 * Copyright (C) 2006-2014  Georg Rudoy
 *
 * Boost Software License - Version 1.0 - August 17th, 2003
 *
 * Permission is hereby granted, free of charge, to any person or organization
 * obtaining a copy of the software and accompanying documentation covered by
 * this license (the "Software") to use, reproduce, display, distribute,
 * execute, and transmit the Software, and to prepare derivative works of the
 * Software, and to permit third-parties to whom the Software is furnished to
 * do so, all subject to the following:
 *
 * The copyright notices in the Software and this entire statement, including
 * the above license grant, this restriction and the following disclaimer,
 * must be included in all copies of the Software, in whole or in part, and
 * all derivative works of the Software, unless such copies or derivative
 * works are solely in the form of machine-executable object code generated by
 * a source language processor.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
 * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
 * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **********************************************************************/

#include "browserwidget.h"

#ifdef ENABLE_IDN
#include <idna.h>
#endif

#include <limits>
#include <cmath>
#include <boost/preprocessor/seq/size.hpp>
#include <boost/preprocessor/seq/elem.hpp>
#include <boost/preprocessor/repetition/repeat.hpp>
#include <QKeyEvent>
#include <QDesktopWidget>
#include <QtDebug>
#include <QToolBar>
#include <QToolButton>
#include <QBuffer>
#include <QDial>
#include <QMenu>
#include <QMovie>
#include <QWidgetAction>
#include <QPrinter>
#include <QPrintDialog>
#include <QTimer>
#include <QPrintPreviewDialog>
#include <QPainter>
#include <qwebframe.h>
#include <QFileDialog>
#include <QMessageBox>
#include <QDesktopServices>
#include <QTextCodec>
#include <QCursor>
#include <QDomDocument>
#include <QDomElement>
#include <qwebhistory.h>
#include <qwebelement.h>
#include <QWebInspector>
#include <QDataStream>
#include <QRegExp>
#include <QKeySequence>
#include <QLabel>
#include <QXmlStreamWriter>
#include <util/util.h>
#include <util/xpc/util.h>
#include <util/xpc/defaulthookproxy.h>
#include <util/xpc/notificationactionhandler.h>
#include <interfaces/core/icoreproxy.h>
#include <interfaces/core/iiconthememanager.h>
#include "core.h"
#include "historymodel.h"
#include "finddialog.h"
#include "screenshotsavedialog.h"
#include "xmlsettingsmanager.h"
#include "sourceviewer.h"
#include "passwordremember.h"
#include "reloadintervalselector.h"
#include "browserwidgetsettings.h"
#include "bookmarkswidget.h"
#include "historywidget.h"
#include "customwebview.h"
#include "urleditbuttonsmanager.h"
#include "webpagesslwatcher.h"

Q_DECLARE_METATYPE (QList<QObject*>);

namespace LeechCraft
{
namespace Poshuku
{
	using LeechCraft::ActionInfo;

	QObject *BrowserWidget::S_MultiTabsParent_ = 0;

	BrowserWidget::BrowserWidget (QWidget *parent)
	: QWidget (parent)
	, ReloadTimer_ (new QTimer (this))
	, HtmlMode_ (false)
	, Own_ (true)
	{
		Ui_.setupUi (this);

		LinkTextItem_ = new QLabel (Ui_.WebFrame_);
		LinkTextItem_->setAutoFillBackground (true);
		LinkTextItem_->setTextFormat (Qt::TextFormat::PlainText);
		LinkTextItem_->setMargin (4);
		LinkTextItem_->raise ();
		LinkTextItem_->hide ();

		Core::Instance ().GetPluginManager ()->RegisterHookable (this);
		Ui_.Sidebar_->AddPage (tr ("Bookmarks"), new BookmarksWidget);
		Ui_.Sidebar_->AddPage (tr ("History"), new HistoryWidget);
		Ui_.Splitter_->setSizes ({ 0, 1000 });

		WebView_ = new CustomWebView;
		Ui_.WebFrame_->layout ()->addWidget (WebView_);
		WebView_->setSizePolicy (QSizePolicy::Expanding, QSizePolicy::Expanding);

		auto sslWatcher = new WebPageSslWatcher (WebView_->page ());
		connect (WebView_,
				SIGNAL (navigateRequested (QUrl)),
				sslWatcher,
				SLOT (resetStats ()));

		WebInspector_ = new QWebInspector;
		WebInspector_->setPage (WebView_->page ());

		WebView_->SetBrowserWidget (this);
		connect (WebView_,
				SIGNAL (urlChanged (QUrl)),
				this,
				SIGNAL (tabRecoverDataChanged ()));
		connect (WebView_,
				SIGNAL (zoomChanged ()),
				this,
				SIGNAL (tabRecoverDataChanged ()));

		connect (ReloadTimer_,
				SIGNAL (timeout ()),
				WebView_,
				SLOT (reload ()));

		Cut_ = WebView_->pageAction (QWebPage::Cut);
		Cut_->setShortcutContext (Qt::WindowShortcut);
		Cut_->setProperty ("ActionIcon", "edit-cut");
		Copy_ = WebView_->pageAction (QWebPage::Copy);
		Copy_->setShortcutContext (Qt::WindowShortcut);
		Copy_->setProperty ("ActionIcon", "edit-copy");
		Paste_ = WebView_->pageAction (QWebPage::Paste);
		Paste_->setProperty ("ActionIcon", "edit-paste");

		ToolBar_ = new QToolBar (this);
		ToolBar_->setWindowTitle ("Poshuku");

		Back_ = WebView_->pageAction (QWebPage::Back);
		Back_->setParent (this);
		Back_->setProperty ("ActionIcon", "go-previous");

		BackMenu_ = new QMenu ();

		Forward_ = WebView_->pageAction (QWebPage::Forward);
		Forward_->setParent (this);
		Forward_->setProperty ("ActionIcon", "go-next");

		ForwardMenu_ = new QMenu ();

		Reload_ = WebView_->pageAction (QWebPage::Reload);
		Reload_->setProperty ("ActionIcon", "view-refresh");
		Reload_->setIcon (Core::Instance ()
				.GetProxy ()->GetIconThemeManager ()->GetIcon ("view-refresh"));

		Stop_ = WebView_->pageAction (QWebPage::Stop);
		Stop_->setProperty ("ActionIcon", "process-stop");
		Stop_->setIcon (Core::Instance ()
				.GetProxy ()->GetIconThemeManager ()->GetIcon ("process-stop"));

		ReloadStop_ = new QAction (this);
		handleLoadProgress (0);

		ReloadPeriodically_ = new QAction (tr ("Reload periodically"), this);
		ReloadPeriodically_->setCheckable (true);
		ReloadPeriodically_->setProperty ("ActionIcon", "view-refresh-periodically");

		NotifyWhenFinished_ = new QAction (tr ("Notify when finished loading"), this);
		NotifyWhenFinished_->setCheckable (true);
		NotifyWhenFinished_->setProperty ("ActionIcon", "preferences-desktop-notification");
		NotifyWhenFinished_->setChecked (XmlSettingsManager::Instance ()->
				property ("NotifyFinishedByDefault").toBool ());

		Add2Favorites_ = new QAction (tr ("Bookmark..."), this);
		Add2Favorites_->setProperty ("ActionIcon", "bookmark-new");
		Add2Favorites_->setEnabled (false);

		new UrlEditButtonsManager (WebView_,
				Ui_.URLFrame_->GetEditAsProgressLine (),
				sslWatcher,
				Add2Favorites_);

		Find_ = new QAction (tr ("Find..."),
				this);
		Find_->setProperty ("ActionIcon", "edit-find");
		Find_->setEnabled (false);

		FindNext_ = new QAction (tr ("Find next"), this);
		FindNext_->setEnabled (false);

		FindPrevious_ = new QAction (tr ("Find previous"), this);
		FindPrevious_->setEnabled (false);

		Print_ = new QAction (tr ("Print..."),
				this);
		Print_->setProperty ("ActionIcon", "document-print");
		Print_->setEnabled (false);

		PrintPreview_ = new QAction (tr ("Print with preview..."),
				this);
		PrintPreview_->setProperty ("ActionIcon", "document-print-preview");
		PrintPreview_->setEnabled (false);

		ScreenSave_ = new QAction (tr ("Take page's screenshot..."),
				this);
		ScreenSave_->setProperty ("ActionIcon", "camera-photo");
		ScreenSave_->setEnabled (false);

		ViewSources_ = new QAction (tr ("View sources..."),
				this);
		ViewSources_->setProperty ("ActionIcon", "applications-development-web");
		ViewSources_->setEnabled (false);

		SavePage_ = new QAction (tr ("Save page..."),
				this);
		SavePage_->setProperty ("ActionIcon", "download");
		SavePage_->setEnabled (false);
		SavePage_->setShortcut (QKeySequence (tr ("Ctrl+S")));

		ZoomIn_ = new QAction (tr ("Zoom in"),
				this);
		ZoomIn_->setProperty ("ActionIcon", "zoom-in");

		ZoomOut_ = new QAction (tr ("Zoom out"),
				this);
		ZoomOut_->setProperty ("ActionIcon", "zoom-out");

		ZoomReset_ = new QAction (tr ("Reset zoom"),
				this);
		ZoomReset_->setProperty ("ActionIcon", "zoom-original");

		HistoryAction_ = new QAction (tr ("Open history"),
				this);
		HistoryAction_->setCheckable (true);
		HistoryAction_->setShortcut (QKeySequence (tr ("Ctrl+H")));
		HistoryAction_->setProperty ("ActionIcon", "view-history");

		BookmarksAction_ = new QAction (tr ("Open bookmarks"),
				this);
		BookmarksAction_->setCheckable (true);
		BookmarksAction_->setShortcut (QKeySequence (tr ("Ctrl+B")));
		BookmarksAction_->setProperty ("ActionIcon", "bookmarks-organize");

		auto backButton = new QToolButton ();
		backButton->setMenu (BackMenu_);
		backButton->setDefaultAction (Back_);
		backButton->setPopupMode (QToolButton::MenuButtonPopup);
		ToolBar_->addWidget (backButton);

		auto fwdButton = new QToolButton ();
		fwdButton->setMenu (ForwardMenu_);
		fwdButton->setDefaultAction (Forward_);
		fwdButton->setPopupMode (QToolButton::MenuButtonPopup);
		ToolBar_->addWidget (fwdButton);

		ToolBar_->addAction (ReloadStop_);

		Util::DefaultHookProxy_ptr proxy (new Util::DefaultHookProxy ());
		QMenu *moreMenu = new QMenu (this);
		emit hookMoreMenuFillBegin (proxy, moreMenu, WebView_, this);
		if (!proxy->IsCancelled ())
		{
			const QString tools = "Poshuku";
			WindowMenus_ [tools] << Find_;
			WindowMenus_ [tools] << FindNext_;
			WindowMenus_ [tools] << FindPrevious_;
			WindowMenus_ [tools] << Util::CreateSeparator (this);
			WindowMenus_ [tools] << Add2Favorites_;
			WindowMenus_ [tools] << HistoryAction_;
			WindowMenus_ [tools] << BookmarksAction_;
			WindowMenus_ [tools] << Util::CreateSeparator (this);
			WindowMenus_ [tools] << ReloadPeriodically_;
			WindowMenus_ [tools] << NotifyWhenFinished_;
			WindowMenus_ [tools] << Util::CreateSeparator (this);
			WindowMenus_ [tools] << Print_;
			WindowMenus_ [tools] << PrintPreview_;
			WindowMenus_ [tools] << ScreenSave_;
			WindowMenus_ [tools] << Util::CreateSeparator (this);
			WindowMenus_ [tools] << ViewSources_;
			WindowMenus_ [tools] << SavePage_;
			WindowMenus_ [tools] << Util::CreateSeparator (this);

			const QString view = "view";
			WindowMenus_ [view] << ZoomIn_;
			WindowMenus_ [view] << ZoomOut_;
			WindowMenus_ [view] << ZoomReset_;
			WindowMenus_ [view] << Util::CreateSeparator (this);
		}
		proxy.reset (new Util::DefaultHookProxy ());
		emit hookMoreMenuFillEnd (proxy, moreMenu, WebView_, this);

		if (moreMenu->actions ().size ())
		{
			const QString tools = "Poshuku";
			WindowMenus_ [tools] << moreMenu->actions ();
			WindowMenus_ [tools] << Util::CreateSeparator (this);
		}

		ChangeEncoding_ = moreMenu->addMenu (tr ("Change encoding"));
		connect (ChangeEncoding_,
				SIGNAL (aboutToShow ()),
				this,
				SLOT (handleChangeEncodingAboutToShow ()));
		connect (ChangeEncoding_,
				SIGNAL (triggered (QAction*)),
				this,
				SLOT (handleChangeEncodingTriggered (QAction*)));

		QWidgetAction *addressBar = new QWidgetAction (this);
		addressBar->setDefaultWidget (Ui_.URLFrame_);
		ToolBar_->addAction (addressBar);

		static_cast<QVBoxLayout*> (layout ())->insertWidget (0, ToolBar_);

		connect (ReloadPeriodically_,
				SIGNAL (triggered ()),
				this,
				SLOT (handleReloadPeriodically ()));
		connect (NotifyWhenFinished_,
				SIGNAL (triggered ()),
				this,
				SIGNAL (tabRecoverDataChanged ()));
		connect (WebView_,
				SIGNAL (addToFavorites (const QString&, const QString&)),
				this,
				SIGNAL (addToFavorites (const QString&, const QString&)));
		connect (Add2Favorites_,
				SIGNAL (triggered ()),
				this,
				SLOT (handleAdd2Favorites ()));
		connect (Print_,
				SIGNAL (triggered ()),
				this,
				SLOT (handlePrinting ()));
		connect (PrintPreview_,
				SIGNAL (triggered ()),
				this,
				SLOT (handlePrintingWithPreview ()));
		connect (Find_,
				SIGNAL (triggered ()),
				this,
				SLOT (handleFind ()));
		connect (ScreenSave_,
				SIGNAL (triggered ()),
				this,
				SLOT (handleScreenSave ()));
		connect (ViewSources_,
				SIGNAL (triggered ()),
				this,
				SLOT (handleViewSources ()));
		connect (SavePage_,
				SIGNAL (triggered ()),
				this,
				SLOT (handleSavePage ()));
		connect (ZoomIn_,
				SIGNAL (triggered ()),
				WebView_,
				SLOT (zoomIn ()));
		connect (ZoomOut_,
				SIGNAL (triggered ()),
				WebView_,
				SLOT (zoomOut ()));
		connect (ZoomReset_,
				SIGNAL (triggered ()),
				WebView_,
				SLOT (zoomReset ()));

		connect (Ui_.URLFrame_,
				SIGNAL (load (const QString&)),
				this,
				SLOT (handleURLFrameLoad (const QString&)));

		connect (WebView_,
				SIGNAL (titleChanged (const QString&)),
				this,
				SLOT (updateTitle (const QString&)));
		connect (WebView_,
				SIGNAL (titleChanged (const QString&)),
				this,
				SLOT (updateLogicalPath ()));
		connect (WebView_,
				SIGNAL (urlChanged (const QString&)),
				this,
				SLOT (handleUrlChanged (const QString&)));
		connect (WebView_,
				SIGNAL (urlChanged (const QString&)),
				this,
				SLOT (updateLogicalPath ()));
		connect (WebView_,
				SIGNAL (loadProgress (int)),
				this,
				SLOT (handleLoadProgress (int)));
		connect (WebView_,
				SIGNAL (loadProgress (int)),
				this,
				SLOT (handleIconChanged ()));
		connect (WebView_,
				SIGNAL (iconChanged ()),
				this,
				SLOT (handleIconChanged ()));
		connect (WebView_,
				SIGNAL (statusBarMessage (const QString&)),
				this,
				SLOT (handleStatusBarMessage (const QString&)),
				Qt::QueuedConnection);
		connect (WebView_,
				SIGNAL (gotEntity (const LeechCraft::Entity&)),
				this,
				SIGNAL (gotEntity (const LeechCraft::Entity&)));
		connect (WebView_,
				SIGNAL (delegateEntity (const LeechCraft::Entity&, int*, QObject**)),
				this,
				SIGNAL (delegateEntity (const LeechCraft::Entity&, int*, QObject**)));
		connect (WebView_,
				SIGNAL (couldHandle (const LeechCraft::Entity&, bool*)),
				this,
				SIGNAL (couldHandle (const LeechCraft::Entity&, bool*)));
		connect (WebView_->page (),
				SIGNAL (linkHovered (const QString&,
						const QString&,
						const QString&)),
				this,
				SLOT (handleStatusBarMessage (const QString&)),
				Qt::QueuedConnection);
		connect (WebView_,
				SIGNAL (loadFinished (bool)),
				this,
				SLOT (setScrollPosition ()));
		connect (WebView_,
				SIGNAL (loadFinished (bool)),
				this,
				SLOT (pageFocus ()));
		connect (WebView_,
				SIGNAL (loadFinished (bool)),
				this,
				SLOT (notifyLoadFinished (bool)));
		connect (WebView_,
				SIGNAL (loadFinished (bool)),
				this,
				SLOT (handleIconChanged ()));
		connect (WebView_,
				SIGNAL (loadFinished (bool)),
				this,
				SLOT (checkLoadedDocument ()));
		connect (WebView_,
				SIGNAL (loadStarted ()),
				this,
				SLOT (enableActions ()));
		connect (WebView_,
				SIGNAL (loadStarted ()),
				this,
				SLOT (updateNavHistory ()));
		connect (WebView_,
				SIGNAL (printRequested (QWebFrame*)),
				this,
				SLOT (handleViewPrint (QWebFrame*)));
		connect (WebView_,
				SIGNAL (closeRequested ()),
				this,
				SIGNAL (needToClose ()));
		connect (WebView_->page (),
				SIGNAL (couldHandle (const LeechCraft::Entity&, bool*)),
				this,
				SIGNAL (couldHandle (const LeechCraft::Entity&, bool*)));

		connect (HistoryAction_,
				SIGNAL (triggered (bool)),
				this,
				SLOT (handleShortcutHistory ()));

		connect (BookmarksAction_,
				SIGNAL (triggered (bool)),
				this,
				SLOT (handleShortcutBookmarks ()));

		QTimer::singleShot (100,
				this,
				SLOT (focusLineEdit ()));

		FindDialog_ = new FindDialog (WebView_);
		FindDialog_->hide ();
		connect (FindNext_,
				SIGNAL (triggered ()),
				FindDialog_,
				SLOT (findNext ()));
		connect (FindPrevious_,
				SIGNAL (triggered ()),
				FindDialog_,
				SLOT (findPrevious ()));

		RememberDialog_ = new PasswordRemember (WebView_);
		RememberDialog_->hide ();

		connect (WebView_,
				SIGNAL (storeFormData (const PageFormsData_t&)),
				RememberDialog_,
				SLOT (add (const PageFormsData_t&)));

		updateLogicalPath ();
	}

	BrowserWidget::~BrowserWidget ()
	{
		WebInspector_->hide ();
		if (Own_)
			Core::Instance ().Unregister (this);

		delete ToolBar_;
	}

	void BrowserWidget::SetParentMultiTabs (QObject *parent)
	{
		S_MultiTabsParent_ = parent;
	}

	void BrowserWidget::Deown ()
	{
		Own_ = false;
	}

	void BrowserWidget::FinalizeInit ()
	{
		IShortcutProxy *proxy = Core::Instance ().GetShortcutProxy ();
		QObject *object = Core::Instance ().parent ();

		Cut_->setShortcuts (proxy->GetShortcuts (object, "BrowserCut_"));
		Copy_->setShortcuts (proxy->GetShortcuts (object, "BrowserCopy_"));
		Paste_->setShortcuts (proxy->GetShortcuts (object, "BrowserPaste_"));
		Back_->setShortcuts (proxy->GetShortcuts (object, "BrowserBack_"));
		Forward_->setShortcuts (proxy->GetShortcuts (object, "BrowserForward_"));
		Reload_->setShortcuts (proxy->GetShortcuts (object, "BrowserReload_"));
		Stop_->setShortcuts (proxy->GetShortcuts (object, "BrowserStop_"));
		Add2Favorites_->setShortcuts (proxy->GetShortcuts (object, "BrowserAdd2Favorites_"));
		Find_->setShortcuts (proxy->GetShortcuts (object, "BrowserFind_"));
		FindNext_->setShortcuts (proxy->GetShortcuts (object, "BrowserFindNext_"));
		FindPrevious_->setShortcuts (proxy->GetShortcuts (object, "BrowserFindPrevious_"));
		Print_->setShortcuts (proxy->GetShortcuts (object, "BrowserPrint_"));
		PrintPreview_->setShortcuts (proxy->GetShortcuts (object, "BrowserPrintPreview_"));
		ScreenSave_->setShortcuts (proxy->GetShortcuts (object, "BrowserScreenSave_"));
		ViewSources_->setShortcuts (proxy->GetShortcuts (object, "BrowserViewSources_"));
		ZoomIn_->setShortcuts (proxy->GetShortcuts (object, "BrowserZoomIn_"));
		ZoomOut_->setShortcuts (proxy->GetShortcuts (object, "BrowserZoomOut_"));
		ZoomReset_->setShortcuts (proxy->GetShortcuts (object, "BrowserZoomReset_"));

		if (Own_)
		{
			IHookProxy_ptr proxy { new Util::DefaultHookProxy };
			emit hookBrowserWidgetInitialized (proxy, WebView_, this);
		}
	}

	CustomWebView* BrowserWidget::GetView () const
	{
		return WebView_;
	}

	QLineEdit* BrowserWidget::GetURLEdit () const
	{
		return Ui_.URLFrame_->GetEdit ();
	}

	BrowserWidgetSettings BrowserWidget::GetWidgetSettings () const
	{
		QByteArray ba;
		QDataStream out (&ba, QIODevice::WriteOnly);
		out << *WebView_->page ()->history ();
		return
		{
			WebView_->zoomFactor (),
			NotifyWhenFinished_->isChecked (),
			ReloadPeriodically_->isChecked () ?
				QTime (0, 0, 0).addMSecs (ReloadTimer_->interval ()) :
				QTime (0, 0, 0),
			ba,
			WebView_->page ()->mainFrame ()->scrollPosition (),
			WebView_->settings ()->defaultTextEncoding ()
		};
	}

	void BrowserWidget::SetWidgetSettings (const BrowserWidgetSettings& settings)
	{
		if (std::fabs (settings.ZoomFactor_ - 1) >
				std::numeric_limits<decltype (settings.ZoomFactor_)>::epsilon ())
			WebView_->setZoomFactor (settings.ZoomFactor_);

		NotifyWhenFinished_->setChecked (settings.NotifyWhenFinished_);
		QTime interval = settings.ReloadInterval_;
		QTime null (0, 0, 0);
		int msecs = null.msecsTo (interval);
		if (msecs >= 1000)
		{
			ReloadPeriodically_->setChecked (true);
			SetActualReloadInterval (interval);
		}

		if (settings.WebHistorySerialized_.size ())
		{
			QDataStream str (settings.WebHistorySerialized_);
			str >> *WebView_->page ()->history ();
		}

		if (!settings.ScrollPosition_.isNull ())
			SetOnLoadScrollPoint (settings.ScrollPosition_);

		WebView_->settings ()->setDefaultTextEncoding (settings.DefaultEncoding_);
	}

	void BrowserWidget::SetURL (const QUrl& thurl)
	{
		QUrl url = thurl;
		Util::DefaultHookProxy_ptr proxy (new Util::DefaultHookProxy);
		emit hookSetURL (proxy, this, url);
		if (proxy->IsCancelled ())
			return;

		proxy->FillValue ("url", url);

		if (!url.isEmpty () && url.isValid ())
		{
			HtmlMode_ = false;
			WebView_->Load (url);
		}
	}

	void BrowserWidget::Load (const QString& url)
	{
		SetURL (Core::Instance ().MakeURL (url));
	}

	void BrowserWidget::SetHtml (const QString& html, const QUrl& base)
	{
		Ui_.URLFrame_->GetEdit ()->clear ();
		HtmlMode_ = true;
		WebView_->setHtml (html, base);

		if (!Own_)
		{
			auto links = WebView_->page ()->mainFrame ()->findAllElements ("a");
			for (int i = 0; i < links.count (); ++i)
			{
				auto elem = links.at (i);
				if (elem.attribute ("target") == "_blank")
					continue;

				elem.setAttribute ("target", "_blank");
			}
		}
	}

	void BrowserWidget::SetNavBarVisible (bool visible)
	{
		ToolBar_->setVisible (visible);
	}

	void BrowserWidget::SetEverythingElseVisible (bool visible)
	{
		if (!visible)
			Ui_.Sidebar_->hide ();
		Ui_.Splitter_->handle (1)->setVisible (visible);
	}

	QWidget* BrowserWidget::Widget ()
	{
		return this;
	}

#define _LC_MERGE(a) "Browser"#a

#define _LC_SINGLE(a) \
		name2act [_LC_MERGE(a)] = a;

#define _LC_TRAVERSER(z,i,array) \
		_LC_SINGLE (BOOST_PP_SEQ_ELEM(i, array))

#define _LC_EXPANDER(Names) \
		BOOST_PP_REPEAT (BOOST_PP_SEQ_SIZE (Names), _LC_TRAVERSER, Names)
	void BrowserWidget::SetShortcut (const QString& name, const QKeySequences_t& sequences)
	{
		QMap<QString, QAction*> name2act;
		_LC_EXPANDER ((Add2Favorites_)
				(Find_)
				(FindNext_)
				(FindPrevious_)
				(Print_)
				(PrintPreview_)
				(ScreenSave_)
				(ViewSources_)
				(ZoomIn_)
				(ZoomOut_)
				(ZoomReset_)
				(Cut_)
				(Copy_)
				(Paste_)
				(Back_)
				(Forward_)
				(Reload_)
				(Stop_));
		if (name2act.contains (name))
			name2act [name]->setShortcuts (sequences);
	}

#define _L(a,b) result ["Browser"#a] = ActionInfo (a->text (), \
			b, a->icon ())
	QMap<QString, ActionInfo> BrowserWidget::GetActionInfo () const
	{
		QMap<QString, ActionInfo> result;
		_L (Add2Favorites_, tr ("Ctrl+D"));
		_L (Find_, tr ("Ctrl+F"));
		_L (FindNext_, tr ("F3"));
		_L (FindPrevious_, tr ("Shift+F3"));
		_L (Print_, tr ("Ctrl+P"));
		_L (PrintPreview_, tr ("Ctrl+Shift+P"));
		_L (ScreenSave_, Qt::Key_F12);
		_L (ViewSources_, tr ("Ctrl+Shift+V"));
		_L (ZoomIn_, Qt::CTRL + Qt::Key_Plus);
		_L (ZoomOut_, Qt::CTRL + Qt::Key_Minus);
		_L (ZoomReset_, tr ("Ctrl+0"));
		_L (Cut_, tr ("Ctrl+X"));
		_L (Copy_, tr ("Ctrl+C"));
		_L (Paste_, tr ("Ctrl+V"));
		_L (Back_, Qt::ALT + Qt::Key_Left);
		_L (Forward_, Qt::ALT + Qt::Key_Right);
		_L (Reload_, Qt::Key_F5);
		_L (Stop_, Qt::Key_Escape);
		return result;
	}

	void BrowserWidget::Remove ()
	{
		Util::DefaultHookProxy_ptr proxy (new Util::DefaultHookProxy);
		emit hookTabRemoveRequested (proxy, this);
		if (proxy->IsCancelled ())
			return;

		emit needToClose ();
	}

	QToolBar* BrowserWidget::GetToolBar () const
	{
		return Own_ ? ToolBar_ : 0;
	}

	namespace
	{
		void Append (QList<QAction*>& result, const QList<QObject*>& objs)
		{
			Q_FOREACH (QObject *obj, objs)
			{
				QAction *act = qobject_cast<QAction*> (obj);
				if (!act)
				{
					qWarning () << Q_FUNC_INFO
							<< "unable to cast"
							<< obj
							<< "from plugins to QAction*";
					continue;
				}
				result << act;
			}
		}
	}

	QList<QAction*> BrowserWidget::GetTabBarContextMenuActions () const
	{
		QList<QObject*> plugResult;

		Util::DefaultHookProxy_ptr proxy (new Util::DefaultHookProxy);
		emit hookTabBarContextMenuActions (proxy, this);
		proxy->FillValue ("actions", plugResult);

		QList<QAction*> result;
		Append (result, plugResult);

		if (!proxy->IsCancelled ())
			result << Reload_
				<< NotifyWhenFinished_
				<< Add2Favorites_
				<< Print_
				<< Back_;

		plugResult.clear ();
		proxy->FillValue ("endActions", plugResult);
		Append (result, plugResult);

		return result;
	}

	QMap<QString, QList<QAction*>> BrowserWidget::GetWindowMenus () const
	{
		return WindowMenus_;
	}

	QObject* BrowserWidget::ParentMultiTabs ()
	{
		return S_MultiTabsParent_;
	}

	TabClassInfo BrowserWidget::GetTabClassInfo () const
	{
		return Core::Instance ().GetTabClass ();
	}

	void BrowserWidget::FillMimeData (QMimeData *data)
	{
		const auto& url = WebView_->url ();
		if (!url.isEmpty () && url.isValid ())
			data->setUrls ({ url });

		QImage image (WebView_->size (), QImage::Format_ARGB32);
		WebView_->render (&image);
		data->setImageData (image);
	}

	void BrowserWidget::HandleDragEnter (QDragMoveEvent *event)
	{
		if (event->mimeData ()->hasUrls ())
			event->acceptProposedAction ();
	}

	void BrowserWidget::HandleDrop (QDropEvent *event)
	{
		const auto& urls = event->mimeData ()->urls ();
		if (!urls.isEmpty ())
			SetURL (urls.first ());
		event->acceptProposedAction ();
	}

	void BrowserWidget::SetTabRecoverData (const QByteArray& data)
	{
		QUrl url;
		BrowserWidgetSettings settings;

		QDataStream str (data);
		str >> url
			>> settings;

		SetURL (url);
		SetWidgetSettings (settings);
	}

	QByteArray BrowserWidget::GetTabRecoverData () const
	{
		QByteArray result;
		QDataStream str (&result, QIODevice::WriteOnly);
		str << WebView_->url ();
		str << GetWidgetSettings ();
		return result;
	}

	QString BrowserWidget::GetTabRecoverName () const
	{
		return QString ("%1 (%2)")
				.arg (WebView_->title ())
				.arg (WebView_->url ().toString ());
	}

	QIcon BrowserWidget::GetTabRecoverIcon () const
	{
		return WebView_->icon ();
	}

	void BrowserWidget::SetOnLoadScrollPoint (const QPoint& sp)
	{
		OnLoadPos_ = sp;
	}

	void BrowserWidget::PrintImpl (bool preview, QWebFrame *frame)
	{
		Util::DefaultHookProxy_ptr proxy (new Util::DefaultHookProxy);
		emit hookPrint (proxy, this, preview, frame);
		if (proxy->IsCancelled ())
			return;

		proxy->FillValue ("preview", preview);

		QPrinter printer;
		if (preview)
		{
			QPrintPreviewDialog prevDialog (&printer, this);
			connect (&prevDialog,
					SIGNAL (paintRequested (QPrinter*)),
					frame,
					SLOT (print (QPrinter*)));

			if (prevDialog.exec () != QDialog::Accepted)
				return;
		}
		else
		{
			QPrintDialog dialog (&printer, this);
			dialog.setWindowTitle (tr ("Print web page"));

			if (dialog.exec () != QDialog::Accepted)
				return;

			frame->print (&printer);
		}
	}

	void BrowserWidget::SetActualReloadInterval (const QTime& value)
	{
		QTime null (0, 0, 0);
		int msecs = null.msecsTo (value);
		QString tip = tr ("Reloading once in %1")
			.arg (value.toString ());
		ReloadPeriodically_->setStatusTip (tip);
		ReloadPeriodically_->setToolTip (tip);
		ReloadTimer_->start (msecs);
	}

	void BrowserWidget::handleIconChanged ()
	{
		Util::DefaultHookProxy_ptr proxy (new Util::DefaultHookProxy);
		emit hookIconChanged (proxy, WebView_->page (), this);
		if (proxy->IsCancelled ())
			return;

		QIcon icon = WebView_->icon ();
		if (icon.isNull ())
			icon = Core::Instance ().GetIcon (WebView_->url ());

		Ui_.URLFrame_->SetFavicon (icon);

		emit iconChanged (icon);
	}

	void BrowserWidget::handleStatusBarMessage (const QString& thmsg)
	{
		QString msg = thmsg;
		Util::DefaultHookProxy_ptr proxy (new Util::DefaultHookProxy);
		emit hookStatusBarMessage (proxy, this, msg);
		if (proxy->IsCancelled ())
			return;

		proxy->FillValue ("message", msg);

		if (msg.isEmpty ())
		{
			LinkTextItem_->hide ();
			return;
		}

		const auto& metrics = LinkTextItem_->fontMetrics ();
		msg = metrics.elidedText (msg, Qt::ElideMiddle, WebView_->rect ().width () * 5 / 11);
		const auto margin = LinkTextItem_->margin ();
		LinkTextItem_->setFixedSize (metrics.width (msg) + 2 * margin, metrics.height () + 2 * margin);
		LinkTextItem_->setText (msg);

		const auto& localCursorPos = WebView_->mapFromGlobal (QCursor::pos ());

		const int textHeight = metrics.boundingRect (msg).height ();
		const qreal y = WebView_->rect ().height () - textHeight - 7;
		const qreal x = QRect (QPoint (0, y), LinkTextItem_->size ()).contains (localCursorPos) ?
				WebView_->rect ().width () - LinkTextItem_->width () + margin :
				margin;
		LinkTextItem_->move (x, y);
		LinkTextItem_->show ();
		LinkTextItem_->raise ();
	}

	void BrowserWidget::handleURLFrameLoad (const QString& text)
	{
		Util::DefaultHookProxy_ptr proxy (new Util::DefaultHookProxy);
		emit hookURLEditReturnPressed (proxy, this);
		if (proxy->IsCancelled ())
			return;

		Load (text);
	}

	void BrowserWidget::handleReloadPeriodically ()
	{
		if (ReloadPeriodically_->isChecked ())
		{
			std::auto_ptr<ReloadIntervalSelector> sel (new ReloadIntervalSelector (this));
			if (sel->exec () != QDialog::Accepted)
			{
				ReloadPeriodically_->setChecked (false);
				ReloadPeriodically_->setStatusTip (QString ());
				ReloadPeriodically_->setToolTip (QString ());
				ReloadTimer_->stop ();
				return;
			}

			QTime value = sel->GetInterval ();
			QTime null (0, 0, 0);
			int msecs = null.msecsTo (value);
			if (msecs < 1000)
			{
				ReloadPeriodically_->setChecked (false);
				ReloadPeriodically_->setStatusTip (QString ());
				ReloadPeriodically_->setToolTip (QString ());
				ReloadTimer_->stop ();
				return;
			}

			SetActualReloadInterval (value);
		}
		else if (ReloadTimer_->isActive ())
		{
			ReloadPeriodically_->setStatusTip (QString ());
			ReloadPeriodically_->setToolTip (QString ());
			ReloadTimer_->stop ();
		}

		emit tabRecoverDataChanged ();
	}

	void BrowserWidget::handleAdd2Favorites ()
	{
		const QString& url = WebView_->url ().toString ();

		if (Core::Instance ().IsUrlInFavourites (url))
			Core::Instance ().GetFavoritesModel ()->removeItem (url);
		else
			emit addToFavorites (WebView_->title (), url);
	}

	void BrowserWidget::handleFind ()
	{
		QAction *act = qobject_cast<QAction*> (sender ());
		if (act)
			FindDialog_->SetText (act->data ().toString ());
		FindDialog_->show ();
		FindDialog_->setFocus ();
	}

	void BrowserWidget::handleViewPrint (QWebFrame *frame)
	{
		PrintImpl (false, frame);
	}

	void BrowserWidget::handlePrinting ()
	{
		PrintImpl (false, WebView_->page ()->mainFrame ());
	}

	void BrowserWidget::handlePrintingWithPreview ()
	{
		PrintImpl (true, WebView_->page ()->mainFrame ());
	}

	void BrowserWidget::handleScreenSave ()
	{
		QSize contentsSize = WebView_->page ()->mainFrame ()->contentsSize ();
		QSize oldSize = WebView_->page ()->viewportSize ();
		QRegion clip (0, 0, contentsSize.width (), contentsSize.height ());

		QPixmap image (contentsSize);
		QPainter painter (&image);
		WebView_->page ()->setViewportSize (contentsSize);
		WebView_->page ()->mainFrame ()->render (&painter, clip);
		WebView_->page ()->setViewportSize (oldSize);

		ScreenShotSaveDialog dia (image, this);
		dia.exec ();
	}

	void BrowserWidget::handleViewSources ()
	{
		QString html = WebView_->page ()->mainFrame ()->toHtml ();

		Entity e = Util::MakeEntity (html,
				QString (),
				FromUserInitiated,
				"x-leechcraft/plain-text-document");
		e.Additional_ ["Language"] = "HTML";
		e.Additional_ ["IsTemporaryDocument"] = true;
		bool ch = false;
		emit couldHandle (e, &ch);
		if (ch)
		{
			emit gotEntity (e);
			return;
		}

		SourceViewer *viewer = new SourceViewer (this);
		viewer->setAttribute (Qt::WA_DeleteOnClose);
		viewer->SetHtml (html);
		viewer->show ();
	}

	void BrowserWidget::handleSavePage ()
	{
		Entity e = Util::MakeEntity (WebView_->url (),
				QString (),
				FromUserInitiated);
		e.Additional_ ["AllowedSemantics"] = QStringList ("fetch") << "save";
		emit gotEntity (e);
	}

	void BrowserWidget::focusLineEdit ()
	{
		QLineEdit *edit = Ui_.URLFrame_->GetEdit ();
		edit->setFocus (Qt::OtherFocusReason);
		edit->selectAll ();
	}

	QWebView* BrowserWidget::getWebView () const
	{
		return WebView_;
	}

	QLineEdit* BrowserWidget::getAddressBar () const
	{
		return Ui_.URLFrame_->GetEdit ();
	}

	QWidget* BrowserWidget::getSideBar () const
	{
		return Ui_.Sidebar_;
	}

	void BrowserWidget::enableActions ()
	{
		Add2Favorites_->setEnabled (true);
		Find_->setEnabled (true);
		FindNext_->setEnabled (true);
		FindPrevious_->setEnabled (true);
		Print_->setEnabled (true);
		PrintPreview_->setEnabled (true);
		ScreenSave_->setEnabled (true);
		ViewSources_->setEnabled (true);
		SavePage_->setEnabled (true);
	}

	void BrowserWidget::updateTitle (const QString& title)
	{
		if (!title.isEmpty ())
		{
			emit titleChanged (title);
			return;
		}

		const auto& name = QFileInfo (WebView_->url ().path ()).fileName ();
		emit titleChanged (name);
	}

	const int MaxHistoryItems = 10;

	void BrowserWidget::updateNavHistory ()
	{
		auto history = WebView_->history ();

		BackMenu_->clear ();
		auto items = history->backItems (MaxHistoryItems);
		for (int i = items.size () - 1; i >= 0; --i)
		{
			const auto& item = items.at (i);
			if (!item.isValid ())
				continue;
			auto act = BackMenu_->addAction (Core::Instance ().GetIcon (item.url ()),
					item.title ());
			act->setToolTip (item.url ().toString ());
			act->setData (i);

			connect (act,
					SIGNAL (triggered ()),
					this,
					SLOT (handleBackHistoryAction ()));
		}

		ForwardMenu_->clear ();
		items = history->forwardItems (MaxHistoryItems);
		for (int i = 0; i < items.size (); ++i)
		{
			const auto& item = items.at (i);
			if (!item.isValid ())
				continue;
			auto act = ForwardMenu_->addAction (Core::Instance ().GetIcon (item.url ()),
					item.title ());
			act->setToolTip (item.url ().toString ());
			act->setData (i);

			connect (act,
					SIGNAL (triggered ()),
					this,
					SLOT (handleForwardHistoryAction ()));
		}
	}

	void BrowserWidget::handleBackHistoryAction ()
	{
		auto idx = qobject_cast<QAction*> (sender ())->data ().toInt ();

		auto history = WebView_->history ();
		const auto& items = history->backItems (MaxHistoryItems);
		if (idx < 0 || idx >= items.size ())
			return;

		const auto& item = items.at (idx);
		if (!item.isValid ())
			return;

		history->goToItem (item);
	}

	void BrowserWidget::handleForwardHistoryAction ()
	{
		auto idx = qobject_cast<QAction*> (sender ())->data ().toInt ();

		auto history = WebView_->history ();
		const auto& items = history->forwardItems (MaxHistoryItems);
		if (idx < 0 || idx >= items.size ())
			return;

		const auto& item = items.at (idx);
		if (!item.isValid ())
			return;

		history->goToItem (item);
	}

	namespace
	{
		class HtmlWriter
		{
			QXmlStreamWriter& W_;
		public:
			HtmlWriter (QXmlStreamWriter& w)
			: W_ (w)
			{
			}

			void WriteColored (const QString& color, const QString& text, int pad)
			{
				const auto& padStr = pad ?
						("; margin-left: " + QString::number (pad) + "em;") :
						QString ();

				W_.writeStartElement ("span");
				W_.writeAttribute ("style", "color:" + color + padStr);
				W_.writeCharacters (text);
				W_.writeEndElement ();
			}

			void ToHtml (const QDomDocument& doc)
			{
				const auto& doctype = doc.doctype ();
				if (!doctype.name ().isEmpty ())
				{
					W_.writeStartElement ("div");
					W_.writeAttribute ("style", "color: #964B00; margin-left: 1em;");
					W_.writeCharacters ("<!DOCTYPE ");

					WriteColored ("#000000", doctype.name (), 0);
					W_.writeCharacters (" ");
					WriteColored ("#881280", '"' + doctype.publicId () + '"', 0);
					W_.writeEmptyElement ("br");
					WriteColored ("#881280", '"' + doctype.systemId () + '"', 2);

					W_.writeCharacters (">");

					W_.writeEndElement ();
				}

				ToHtmlChildren (doc, [] {});
			}

			void ToHtml (const QDomElement& elem)
			{
				W_.writeStartElement ("div");
				W_.writeAttribute ("style", "color: #881280; margin-left: 1em;");
				W_.writeCharacters ("<" + elem.tagName ());

				const auto& attrs = elem.attributes ();
				for (int i = 0; i < attrs.size (); ++i)
				{
					const auto& attrNode = attrs.item (i).toAttr ();

					WriteColored ("#994500", " " + attrNode.name (), 0);
					WriteColored ("#881280", "=\"", 0);
					WriteColored ("#1A1AA6", attrNode.value (), 0);
					WriteColored ("#881280", "\"", 0);
				}

				bool hasChildren = false;
				auto childrenize = [this, &hasChildren] () -> void
				{
					if (hasChildren)
						return;

					hasChildren = true;
					W_.writeCharacters (">");
					W_.writeEmptyElement ("br");
				};

				ToHtmlChildren (elem, childrenize);

				W_.writeCharacters (hasChildren ? ("</" + elem.tagName () + ">") : " />");
				W_.writeEndElement ();
			}

			template<typename ChildrenizeCbType>
			void ToHtmlChildren (const QDomNode& elem, const ChildrenizeCbType& childrenize)
			{
				auto child = elem.firstChild ();
				while (!child.isNull ())
				{
					switch (child.nodeType ())
					{
					case QDomDocument::TextNode:
						childrenize ();
						WriteColored ("#000000", child.toText ().data (), 1);
						W_.writeEmptyElement ("br");
						break;
					case QDomDocument::CDATASectionNode:
						childrenize ();
						WriteColored ("#000000", "<![CDATA[ " + child.toCDATASection ().data () + " ]]>", 1);
						W_.writeEmptyElement ("br");
						break;
					case QDomDocument::CommentNode:
						childrenize ();
						WriteColored ("#00BB00", "<!--" + child.toComment ().data () + "-->", 1);
						W_.writeEmptyElement ("br");
						break;
					case QDomNode::ElementNode:
						childrenize ();
						ToHtml (child.toElement ());
						break;
					case QDomNode::ProcessingInstructionNode:
					{
						childrenize ();
						const auto& instr = child.toProcessingInstruction ();
						W_.writeStartElement ("div");
						W_.writeAttribute ("style", "color: #007700; margin-left: 1em;");
						W_.writeCharacters ("<?" + instr.target () + " ");
						WriteColored ("#994500", instr.data (), 0);
						W_.writeCharacters ("?>");
						W_.writeEndElement ();
						break;
					}
					case QDomNode::DocumentTypeNode:
					case QDomNode::EntityNode:
					case QDomNode::EntityReferenceNode:
					case QDomNode::AttributeNode:
					case QDomNode::CharacterDataNode:
					case QDomNode::BaseNode:
					case QDomNode::DocumentFragmentNode:
					case QDomNode::DocumentNode:
					case QDomNode::NotationNode:
						qWarning () << "unexpected node type"
								<< child.nodeType ();
						break;
					}
					child = child.nextSibling ();
				}
			}
		};
	}

	void BrowserWidget::checkLoadedDocument ()
	{
		const auto& html = WebView_->page ()->mainFrame ()->toHtml ();
		QDomDocument doc;
		if (!doc.setContent (html))
			return;

		const auto& rootTagName = doc.documentElement ().tagName ().toLower ();
		if (rootTagName == "html" || rootTagName == "xhtml" || rootTagName == "svg")
			return;

		LeechCraft::Entity e;
		e.Entity_ = WebView_->url ();
		e.Mime_ = "text/xml";
		e.Parameters_ = LeechCraft::FromUserInitiated |
			LeechCraft::OnlyHandle;
		e.Additional_ ["IgnorePlugins"] = QStringList ("org.LeechCraft.Poshuku");
		e.Additional_ ["URLData"] = html;
		emit gotEntity (e);

		QString formatted;
		QXmlStreamWriter w (&formatted);
		w.writeStartDocument ();
			w.writeStartElement ("html");
				w.writeStartElement ("head");
					w.writeTextElement ("title", Qt::escape (WebView_->url ().toString ()));
				w.writeEndElement ();
				w.writeStartElement ("body");
					w.writeAttribute ("style", "font-family:monospace;");
					HtmlWriter { w }.ToHtml (doc);
				w.writeEndElement ();
			w.writeEndElement ();
		w.writeEndDocument ();

		GetView ()->setHtml (formatted, WebView_->url ());
	}

	void BrowserWidget::setScrollPosition ()
	{
		if (!OnLoadPos_.isNull ())
		{
			GetView ()->page ()->mainFrame ()->setScrollPosition (OnLoadPos_);
			OnLoadPos_ = QPoint ();
		}
	}

	void BrowserWidget::pageFocus ()
	{
		if (!HtmlMode_ && isVisible ())
			WebView_->setFocus ();
	}

	void BrowserWidget::handleLoadProgress (int p)
	{
		Util::DefaultHookProxy_ptr proxy (new Util::DefaultHookProxy);
		emit hookLoadProgress (proxy, WebView_->page (), this, p);
		if (proxy->IsCancelled ())
			return;

		proxy->FillValue ("progress", p);

		QString title = WebView_->title ();
		if (title.isEmpty ())
			title = QFileInfo (WebView_->url ().path ()).fileName ();

		if (p > 0 && p < 100)
			title.prepend (QString ("[%1%] ").arg (p));
		emit titleChanged (title);

		QAction *o = 0;
		QAction *n = 0;
		QString actionIcon;
		if (p < 100 && p > 0)
		{
			o = Reload_;
			n = Stop_;
			actionIcon = "process-stop";
		}
		else
		{
			o = Stop_;
			n = Reload_;
			actionIcon = "view-refresh";
		}
		disconnect (ReloadStop_,
				SIGNAL (triggered ()),
				o,
				SLOT (trigger ()));
		ReloadStop_->setIcon (n->icon ());
		ReloadStop_->setShortcut (n->shortcut ());
		ReloadStop_->setText (n->text ());
		ReloadStop_->setProperty ("ActionIcon", actionIcon);
		connect (ReloadStop_,
				SIGNAL (triggered ()),
				n,
				SLOT (trigger ()));
	}

	void BrowserWidget::notifyLoadFinished (bool ok)
	{
		Util::DefaultHookProxy_ptr proxy (new Util::DefaultHookProxy);
		emit hookNotifyLoadFinished (proxy,
				WebView_,
				this,
				ok,
				NotifyWhenFinished_->isChecked (),
				Own_,
				HtmlMode_);

		proxy->FillValue ("ok", ok);

		if (!NotifyWhenFinished_->isChecked () ||
				!Own_ ||
				HtmlMode_ ||
				isVisible ())
			return;

		QString h = WebView_->title ();
		if (h.isEmpty ())
			h = WebView_->url ().toString ();
		if (h.isEmpty ())
			return;

		QString text;
		Priority prio = PInfo_;

		if (ok)
			text = tr ("Page load finished: %1")
				.arg (Qt::escape (WebView_->title ()));
		else
		{
			text = tr ("Page load failed: %1")
				.arg (Qt::escape (WebView_->title ()));
			prio = PWarning_;
		}

		Entity e = Util::MakeNotification ("Poshuku", text, prio);
		Util::NotificationActionHandler *nh = new Util::NotificationActionHandler (e, this);
		nh->AddFunction (tr ("Open"), [this] () { emit raiseTab (this); });
		nh->AddDependentObject (this);
		emit gotEntity (e);
	}

	void BrowserWidget::handleChangeEncodingAboutToShow ()
	{
		ChangeEncoding_->clear ();

		QStringList codecs;
		QList<int> mibs = QTextCodec::availableMibs ();
		QMap<QString, int> name2mib;
		Q_FOREACH (int mib, mibs)
		{
			QString name = QTextCodec::codecForMib (mib)->name ();
			codecs << name;
			name2mib [name] = mib;
		}
		codecs.sort ();

		QString defaultEncoding = WebView_->
			settings ()->defaultTextEncoding ();
		const int currentCodec = codecs.indexOf (defaultEncoding);

		QAction *def = ChangeEncoding_->addAction (tr ("Default"));
		def->setData (-1);
		def->setCheckable (true);
		if (currentCodec == -1)
			def->setChecked (true);
		ChangeEncoding_->addSeparator ();

		for (int i = 0; i < codecs.count (); ++i)
		{
			QAction *cdc = ChangeEncoding_->addAction (codecs.at (i));
			cdc->setData (name2mib [codecs.at (i)]);
			cdc->setCheckable (true);
			if (currentCodec == i)
				cdc->setChecked (true);
		}
	}

	void BrowserWidget::handleChangeEncodingTriggered (QAction *action)
	{
		if (!action)
		{
			qWarning () << Q_FUNC_INFO
				<< "action is null";
			return;
		}

		int mib = action->data ().toInt ();
		QString encoding;
		if (mib >= 0)
			encoding = QTextCodec::codecForMib (mib)->name ();
		WebView_->settings ()->setDefaultTextEncoding (encoding);
		Reload_->trigger ();
	}

	void BrowserWidget::updateLogicalPath ()
	{
		Util::DefaultHookProxy_ptr proxy (new Util::DefaultHookProxy);
		emit hookUpdateLogicalPath (proxy, this);
		if (proxy->IsCancelled ())
			return;

		static QStringList skip;

		skip << "org.ru"
			<< "net.ru";

		QUrl url = WebView_->url ();
		QString title = WebView_->title ();
		if (title.isEmpty ())
			title = tr ("No title");
		QString host = url.host ();
		host.remove ("www.");
		QStringList path;
		path << (host.isEmpty () ? QString ("Poshuku") : host);
		path << title;

		QStringList domains = host.split ('.', QString::SkipEmptyParts);
		while (domains.size () > 2)
		{
			domains.takeFirst ();
			QString joined = domains.join (".");
			if (skip.contains (joined))
				continue;
			path.prepend (joined);
		}

		setProperty ("WidgetLogicalPath", path);
	}

	void BrowserWidget::handleUrlChanged (const QString& value)
	{
		QString userText = value;
#ifdef ENABLE_IDN
		if (userText.contains ("xn--"))
		{
			QRegExp rx ("(?://|\\.)xn--(.+)(?:\\.|/)");
			rx.setMinimal (true);
			int pos = 0;
			QStringList caps;

			while ((pos = rx.indexIn (userText, pos)) != -1)
			{
				caps << rx.cap (1);
				pos += rx.matchedLength () - 4;
			}

			Q_FOREACH (QString str, caps)
			{
				str.prepend ("xn--");
				char *output = 0;
				idna_to_unicode_8z8z (str.toUtf8 ().constData (),
						&output, IDNA_ALLOW_UNASSIGNED);
				QString newStr = QString::fromUtf8 (output);
				userText.replace (str, newStr);
			}
		}
#endif

		if (userText.isEmpty ())
			return;

		Ui_.URLFrame_->GetEdit ()->setText (userText);
		Ui_.URLFrame_->GetEdit ()->repaint ();

		emit urlChanged (value);
	}

	void BrowserWidget::handleShortcutHistory ()
	{
		if (!HistoryAction_->isChecked ())
			HistoryAction_->setChecked (false);
		else
		{
			HistoryAction_->setChecked (true);
			BookmarksAction_->setChecked (false);
		}

		SetSplitterSizes (1);
	}

	void BrowserWidget::handleShortcutBookmarks ()
	{
		if (!BookmarksAction_->isChecked ())
			BookmarksAction_->setChecked (false);
		else
		{
			HistoryAction_->setChecked (false);
			BookmarksAction_->setChecked (true);
		}

		SetSplitterSizes (0);
	}

	void BrowserWidget::loadURL (const QUrl& url)
	{
		WebView_->Load (url);
	}

	void BrowserWidget::SetSplitterSizes (int currentIndex)
	{
		int splitterSize = XmlSettingsManager::Instance ()->
				Property ("HistoryBoormarksPanelSize", 250).toInt ();
		int wSize = WebView_->size ().width ();

		if (!Ui_.Splitter_->sizes ().at (0))
		{
			Ui_.Splitter_->setSizes (QList<int> () << splitterSize << wSize - splitterSize);
			Ui_.Sidebar_->GetMainTabBar ()->setCurrentIndex (currentIndex);
		}
		else if (Ui_.Sidebar_->GetMainTabBar ()->currentIndex () != currentIndex)
			Ui_.Sidebar_->GetMainTabBar ()->setCurrentIndex (currentIndex);
		else
		{
			XmlSettingsManager::Instance ()->
				setProperty ("HistoryBoormarksPanelSize", Ui_.Splitter_->sizes ().at (0));
			Ui_.Splitter_->setSizes (QList<int> () <<  0 << wSize);
		}
	}
}
}
