<commit_before>#include "zoomwindow.h"
#include "ui_zoomwindow.h"
#include <QApplication>
#include <QDesktopWidget>
#include <QMessageBox>
#include <QShortcut>
#include <QMenu>
#include <QFileDialog>
#include <QScrollBar>
#include "ui/QAffiche.h"
#include "models/filename.h"
#include "models/site.h"
#include "models/page.h"
#include "models/profile.h"
#include "reverse-search/reverse-search-loader.h"
#include "settings/optionswindow.h"
#include "threads/image-loader.h"
#include "threads/image-loader-queue.h"
#include "detailswindow.h"
#include "mainwindow.h"
#include "helpers.h"
#include "functions.h"


zoomWindow::zoomWindow(QList<QSharedPointer<Image> > images, QSharedPointer<Image> image, Site *site, QMap<QString,Site*> *sites, Profile *profile, mainWindow *parent)
	: QWidget(Q_NULLPTR, Qt::Window), m_parent(parent), m_profile(profile), m_favorites(profile->getFavorites()), m_viewItLater(profile->getKeptForLater()), m_ignore(profile->getIgnored()), m_settings(profile->getSettings()), ui(new Ui::zoomWindow), m_site(site), timeout(300), m_loaded(false), m_loadedImage(false), m_loadedDetails(false), m_displayImage(QPixmap()), m_displayMovie(nullptr), m_finished(false), m_size(0), m_sites(sites), m_source(), m_fullScreen(nullptr), m_images(images), m_isFullscreen(false), m_isSlideshowRunning(false), m_imagePath("")
{
	setAttribute(Qt::WA_DeleteOnClose);
	ui->setupUi(this);

	m_mustSave = 0;

	restoreGeometry(m_settings->value("Zoom/geometry").toByteArray());
	ui->buttonPlus->setChecked(m_settings->value("Zoom/plus", false).toBool());
	ui->progressBarDownload->hide();

	QShortcut *escape = new QShortcut(QKeySequence(Qt::Key_Escape), this);
		connect(escape, &QShortcut::activated, this, &zoomWindow::close);
	QShortcut *save = new QShortcut(QKeySequence::Save, this);
		connect(save, SIGNAL(activated()), this, SLOT(saveImage()));
	QShortcut *saveAs = new QShortcut(QKeySequence::SaveAs, this);
		connect(saveAs, &QShortcut::activated, this, &zoomWindow::saveImageAs);
	QShortcut *arrowNext = new QShortcut(QKeySequence(Qt::Key_Right), this);
		connect(arrowNext, &QShortcut::activated, this, &zoomWindow::next);
	QShortcut *arrowPrevious = new QShortcut(QKeySequence(Qt::Key_Left), this);
		connect(arrowPrevious, &QShortcut::activated, this, &zoomWindow::previous);
	QShortcut *copyImageFile = new QShortcut(QKeySequence::Copy, this);
		connect(copyImageFile, &QShortcut::activated, this, &zoomWindow::copyImageDataToClipboard);
	QShortcut *toggleFullscreen = new QShortcut(QKeySequence::FullScreen, this);
		connect(toggleFullscreen, &QShortcut::activated, this, &zoomWindow::toggleFullScreen);

	m_labelTagsLeft = new QAffiche(QVariant(), 0, QColor(), this);
		m_labelTagsLeft->setContextMenuPolicy(Qt::CustomContextMenu);
		m_labelTagsLeft->setTextInteractionFlags(Qt::TextBrowserInteraction);
		connect(m_labelTagsLeft, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextMenu(QPoint)));
		connect(m_labelTagsLeft, SIGNAL(linkActivated(QString)), this, SLOT(openUrl(QString)));
		connect(m_labelTagsLeft, SIGNAL(linkHovered(QString)), this, SLOT(linkHovered(QString)));
		connect(m_labelTagsLeft, SIGNAL(middleClicked()), this, SLOT(openInNewTab()));
		ui->scrollAreaWidgetContents->layout()->addWidget(m_labelTagsLeft);

	m_labelTagsTop = new QAffiche(QVariant(), 0, QColor(), this);
		m_labelTagsTop->setWordWrap(true);
		m_labelTagsTop->setContextMenuPolicy(Qt::CustomContextMenu);
		m_labelTagsTop->setTextInteractionFlags(Qt::TextBrowserInteraction);
		connect(m_labelTagsTop, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextMenu(QPoint)));
		connect(m_labelTagsTop, SIGNAL(linkActivated(QString)), this, SLOT(openUrl(QString)));
		connect(m_labelTagsTop, SIGNAL(linkHovered(QString)), this, SLOT(linkHovered(QString)));
		connect(m_labelTagsTop, SIGNAL(middleClicked()), this, SLOT(openInNewTab()));
		ui->verticalLayout->insertWidget(0, m_labelTagsTop, 0);

	// Automatically re-color if some settings change
	connect(m_profile, &Profile::favoritesChanged, this, &zoomWindow::colore);
	connect(m_profile, &Profile::keptForLaterChanged, this, &zoomWindow::colore);
	connect(m_profile, &Profile::ignoredChanged, this, &zoomWindow::colore);

	m_stackedWidget = new QStackedWidget(this);
		ui->verticalLayout->insertWidget(1, m_stackedWidget, 1);
	m_labelImage = new QAffiche(QVariant(), 0, QColor(), this);
		m_labelImage->setSizePolicy(QSizePolicy(QSizePolicy::Ignored,QSizePolicy::Ignored));
		connect(m_labelImage, SIGNAL(doubleClicked()), this, SLOT(openFile()));
		m_stackedWidget->addWidget(m_labelImage);

	connect(ui->buttonDetails, SIGNAL(clicked()), this, SLOT(showDetails()));
	connect(ui->buttonPlus, &QPushButton::toggled, this, &zoomWindow::updateButtonPlus);

	m_labelImage->setContextMenuPolicy(Qt::CustomContextMenu);
	connect(m_labelImage, &QAffiche::customContextMenuRequested, this, &zoomWindow::imageContextMenu);

	m_slideshow.setSingleShot(true);
	connect(&m_slideshow, &QTimer::timeout, this, &zoomWindow::next);

	// Overlay progressbar and image
	ui->overlayLayout->removeWidget(ui->progressBarDownload);
	ui->overlayLayout->addWidget(ui->progressBarDownload, 0, 0, Qt::AlignBottom);
	ui->progressBarDownload->raise();

	// Threads
	m_imageLoaderThread.setObjectName("Image loader thread");
	m_imageLoader = new ImageLoader();
	m_imageLoader->moveToThread(&m_imageLoaderThread);
	m_imageLoaderQueueThread.setObjectName("Image loader queue thread");
	m_imageLoaderQueue = new ImageLoaderQueue(m_imageLoader);
	m_imageLoaderQueue->moveToThread(&m_imageLoaderQueueThread);
	connect(&m_imageLoaderThread, &QThread::finished, m_imageLoader, &QObject::deleteLater);
	connect(&m_imageLoaderQueueThread, &QThread::finished, m_imageLoaderQueue, &QObject::deleteLater);
	connect(this, &zoomWindow::loadImage, m_imageLoaderQueue, &ImageLoaderQueue::load);
	connect(this, &zoomWindow::clearLoadQueue, m_imageLoaderQueue, &ImageLoaderQueue::clear);
	connect(m_imageLoaderQueue, &ImageLoaderQueue::finished, this, &zoomWindow::display);
	m_imageLoaderQueueThread.start();
	m_imageLoaderThread.start();

	// Background color
	QString bg = m_settings->value("imageBackgroundColor", "").toString();
	if (!bg.isEmpty())
	{
		setStyleSheet("#zoomWindow { background-color:" + bg + "; }");
		m_labelTagsLeft->setStyleSheet("background-color:" + bg);
		m_labelTagsTop->setStyleSheet("background-color:" + bg);
	}

	load(image);
}
void zoomWindow::go()
{
	ui->labelPools->hide();
	bool whitelisted = false;
	if (!m_settings->value("whitelistedtags").toString().isEmpty())
	{
		QStringList whitelist = m_settings->value("whitelistedtags").toString().split(" ");
		for (Tag t : m_image->tags())
		{
			if (whitelist.contains(t.text()))
			{
				whitelisted = true;
				break;
			}
		}
	}
	if (m_settings->value("autodownload", false).toBool() || (whitelisted && m_settings->value("whitelist_download", "image").toString() == "image"))
	{ saveImage(); }

	m_url = m_image->getDisplayableUrl().toString();

	QTimer *timer = new QTimer(this);
		connect(timer, SIGNAL(timeout()), this, SLOT(update()));
		timer->setSingleShot(true);
		m_resizeTimer = timer;

	QString u = m_site->value("Urls/Html/Post");
		u.replace("{id}", QString::number(m_image->id()));

	QString pos = m_settings->value("tagsposition", "top").toString();
	if (pos == "auto")
	{
		if (!m_image->size().isEmpty())
		{
			if (float(m_image->width())/float(m_image->height()) >= 4./3.)
			{ pos = "top"; }
			else
			{ pos = "left"; }
		}
		else
		{ pos = "top"; }
	}

	if (pos == "top")
	{
		ui->widgetLeft->hide();
		m_labelTagsTop->setText(m_image->stylishedTags(m_profile).join(" "));
		m_labelTagsTop->show();
	}
	else
	{
		m_labelTagsTop->hide();
		m_labelTagsLeft->setText(m_image->stylishedTags(m_profile).join("<br/>"));
		m_labelTagsLeft->setMinimumWidth(m_labelTagsLeft->sizeHint().width() + ui->scrollArea->verticalScrollBar()->sizeHint().width());
		m_labelTagsLeft->show();
		ui->widgetLeft->show();
	}

	m_detailsWindow = new detailsWindow(m_profile, this);

	// Load image details (exact tags & co)
	connect(m_image.data(), &Image::finishedLoadingTags, this, &zoomWindow::replyFinishedDetails, Qt::UniqueConnection);
	m_image->loadDetails();

	ReverseSearchLoader loader(m_settings);
	m_reverseSearchEngines = loader.getAllReverseSearchEngines();

	if (!m_isFullscreen)
		activateWindow();
}

/**
 * Destructor of the zoomWindow class
 */
zoomWindow::~zoomWindow()
{
	if (m_displayMovie != nullptr)
		m_displayMovie->deleteLater();

	m_labelTagsTop->deleteLater();
	m_labelTagsLeft->deleteLater();
	m_detailsWindow->deleteLater();

	// Quit threads
	m_imageLoaderQueueThread.quit();
	m_imageLoaderThread.wait(1000);
	m_imageLoaderThread.quit();
	m_imageLoaderThread.wait(1000);

	delete ui;
}

void zoomWindow::imageContextMenu()
{
	QMenu *menu = new QMenu(this);

	// Copy actions
	menu->addAction(QIcon(":/images/icons/copy.png"), tr("Copy file"), this, SLOT(copyImageFileToClipboard()));
	menu->addAction(QIcon(":/images/icons/document-binary.png"), tr("Copy data"), this, SLOT(copyImageDataToClipboard()));
	menu->addSeparator();

	// Reverse search actions
	m_reverseSearchSignalMapper = new QSignalMapper(this);
	connect(m_reverseSearchSignalMapper, SIGNAL(mapped(int)), this, SLOT(reverseImageSearch(int)));
	for (int i = 0; i < m_reverseSearchEngines.count(); ++i)
	{
		ReverseSearchEngine engine = m_reverseSearchEngines[i];
		QAction *subMenuAct = menu->addAction(engine.icon(), engine.name());
		connect(subMenuAct, SIGNAL(triggered()), m_reverseSearchSignalMapper, SLOT(map()));
		m_reverseSearchSignalMapper->setMapping(subMenuAct, i);
	}

	menu->exec(QCursor::pos());
}
void zoomWindow::reverseImageSearch(int i)
{
	if (m_reverseSearchEngines.count() < i)
		return;

	m_reverseSearchEngines[i].searchByUrl(m_image->fileUrl());
}
void zoomWindow::copyImageFileToClipboard()
{
	QString path = m_imagePath;
	if (path.isEmpty() || !QFile::exists(path))
	{
		QMap<QString, Image::SaveResult> files = m_image->save(m_settings->value("Save/filename").toString(), m_profile->tempPath(), false);
		path = files.firstKey();
	}

	QMimeData* mimeData = new QMimeData();
	mimeData->setUrls({ QUrl::fromLocalFile(path) });
	QApplication::clipboard()->setMimeData(mimeData);
}
void zoomWindow::copyImageDataToClipboard()
{
	QApplication::clipboard()->setPixmap(m_displayImage);
}

void zoomWindow::showDetails()
{
	m_detailsWindow->setImage(m_image);
	m_detailsWindow->show();
}

void zoomWindow::openUrl(QString url)
{ emit linkClicked(url); }
void zoomWindow::openPool(QString url)
{
	if (url.startsWith("pool:"))
	{ emit poolClicked(url.right(url.length() - 5).toInt(), m_image->site()); }
	else
	{
		Page *p = new Page(m_profile, m_sites->value(m_image->site()), m_sites->values(), QStringList() << "id:"+url, 1, 1, QStringList(), false, this);
		connect(p, SIGNAL(finishedLoading(Page*)), this, SLOT(openPoolId(Page*)));
		p->load();
	}
}
void zoomWindow::openPoolId(Page *p)
{
	if (p->images().size() < 1)
	{
		p->deleteLater();
		return;
	}

	m_image = p->images().at(0);
	timeout = 300;
	m_loaded = false;
	m_loadedDetails = false;
	m_loadedImage = false;
	m_finished = false;
	m_size = 0;
	m_labelImage->hide();
	ui->verticalLayout->removeWidget(m_labelImage);

	go();
}

void zoomWindow::openSaveDir(bool fav)
{
	// If the file was already saved, we focus on it
	if (!m_imagePath.isEmpty())
	{
		showInGraphicalShell(m_imagePath);
	}
	else
	{
		QString path = m_settings->value("Save/path"+QString(fav ? "_favorites" : "")).toString().replace("\\", "/"), fn = m_settings->value("Save/filename"+QString(fav ? "_favorites" : "")).toString();

		if (path.right(1) == "/")
		{ path = path.left(path.length()-1); }
		path = QDir::toNativeSeparators(path);

		QStringList files = m_image->path(fn, path);
		QString file = files.empty() ? "" : files.at(0);
		QString pth = file.section(QDir::toNativeSeparators("/"), 0, -2);
		QString url = path+QDir::toNativeSeparators("/")+pth;

		QDir dir(url);
		if (dir.exists())
		{ showInGraphicalShell(url); }
		else
		{
			int reply = QMessageBox::question(this, tr("Folder does not exist"), tr("The save folder does not exist yet. Create it?"), QMessageBox::Yes | QMessageBox::No);
			if (reply == QMessageBox::Yes)
			{
				QDir dir(path);
				if (!dir.mkpath(pth))
				{ error(this, tr("Error creating folder.\r\n%1").arg(url)); }
				showInGraphicalShell(url);
			}
		}
	}
}
void zoomWindow::openSaveDirFav()
{ openSaveDir(true); }

void zoomWindow::linkHovered(QString url)
{ this->link = url; }
void zoomWindow::contextMenu(QPoint)
{
	QMenu *menu = new QMenu(this);
	if (!this->link.isEmpty())
	{
		// Favorites
		if (m_favorites.contains(Favorite(link)))
		{
			menu->addAction(QIcon(":/images/icons/remove.png"), tr("Remove from favorites"), this, SLOT(unfavorite()));
			menu->addAction(QIcon(":/images/icons/save.png"), tr("Choose as image"), this, SLOT(setfavorite()));
		}
		else
		{ menu->addAction(QIcon(":/images/icons/add.png"), tr("Add to favorites"), this, SLOT(favorite())); }

		// Keep for later
		if (m_viewItLater.contains(link, Qt::CaseInsensitive))
		{ menu->addAction(QIcon(":/images/icons/remove.png"), tr("Don't keep for later"), this, SLOT(unviewitlater())); }
		else
		{ menu->addAction(QIcon(":/images/icons/add.png"), tr("Keep for later"), this, SLOT(viewitlater())); }

		// Blacklist
		QStringList blacklistedTags = m_settings->value("blacklistedtags").toString().split(' ');
		if (blacklistedTags.contains(link))
		{ menu->addAction(QIcon(":/images/icons/eye-plus.png"), tr("Don't blacklist"), this, SLOT(unblacklist())); }
		else
		{ menu->addAction(QIcon(":/images/icons/eye-minus.png"), tr("Blacklist"), this, SLOT(blacklist())); }

		// Ignore
		if (m_ignore.contains(link, Qt::CaseInsensitive))
		{ menu->addAction(QIcon(":/images/icons/eye-plus.png"), tr("Don't ignore"), this, SLOT(unignore())); }
		else
		{ menu->addAction(QIcon(":/images/icons/eye-minus.png"), tr("Ignore"), this, SLOT(ignore())); }
		menu->addSeparator();

		// Copy
		menu->addAction(QIcon(":/images/icons/copy.png"), tr("Copy tag"), this, SLOT(copyTagToClipboard()));
		menu->addAction(QIcon(":/images/icons/copy.png"), tr("Copy all tags"), this, SLOT(copyAllTagsToClipboard()));
		menu->addSeparator();

		// Tabs
		menu->addAction(QIcon(":/images/icons/tab-plus.png"), tr("Open in a new tab"), this, SLOT(openInNewTab()));
		menu->addAction(QIcon(":/images/icons/window.png"), tr("Open in new a window"), this, SLOT(openInNewWindow()));
		menu->addAction(QIcon(":/images/icons/browser.png"), tr("Open in browser"), this, SLOT(openInBrowser()));
	}
	menu->exec(QCursor::pos());
}

void zoomWindow::openInNewTab()
{
	m_parent->addTab(link);
}
void zoomWindow::openInNewWindow()
{
	QProcess myProcess;
	myProcess.startDetached(qApp->arguments().at(0), QStringList(link));
}
void zoomWindow::openInBrowser()
{
	QDesktopServices::openUrl(m_image->pageUrl());
}
void zoomWindow::copyTagToClipboard()
{
	QApplication::clipboard()->setText(this->link);
}
void zoomWindow::copyAllTagsToClipboard()
{
	QStringList tags;
	for (Tag tag : m_image->tags())
		tags.append(tag.text());

	QApplication::clipboard()->setText(tags.join(' '));
}

void zoomWindow::favorite()
{
	Favorite fav(link);
	if (m_loadedImage)
		fav.setImage(m_displayImage);

	m_profile->addFavorite(fav);
}
void zoomWindow::setfavorite()
{
	if (!m_loadedImage)
		return;

	Favorite fav(link);
	int pos = m_favorites.indexOf(fav);
	if (pos >= 0)
	{
		m_favorites[pos].setImage(m_displayImage);
	}
	else
	{
		fav.setImage(m_displayImage);
		m_favorites.append(fav);
	}

	m_profile->emitFavorite();
}
void zoomWindow::unfavorite()
{
	m_profile->removeFavorite(Favorite(link));
}

void zoomWindow::viewitlater()
{
	m_profile->addKeptForLater(link);
}
void zoomWindow::unviewitlater()
{
	m_profile->removeKeptForLater(link);
}

void zoomWindow::ignore()
{
	m_profile->addIgnored(link);
}
void zoomWindow::unignore()
{
	m_profile->removeIgnored(link);
}

void zoomWindow::blacklist()
{
	QString blacklistedTags = m_settings->value("blacklistedtags").toString();
	blacklistedTags += " " + link;
	m_settings->setValue("blacklistedtags", blacklistedTags);
	colore();
}
void zoomWindow::unblacklist()
{
	QStringList blacklistedTags = m_settings->value("blacklistedtags").toString().split(' ');
	blacklistedTags.removeAll(link);
	m_settings->setValue("blacklistedtags", blacklistedTags.join(' '));
	colore();
}

void zoomWindow::load()
{
	log(QString("Loading image from <a href=\"%1\">%1</a>").arg(m_url));

	m_source.clear();

	ui->progressBarDownload->setMaximum(100);
	ui->progressBarDownload->setValue(0);
	ui->progressBarDownload->show();

	connect(m_image.data(), &Image::downloadProgressImage, this, &zoomWindow::downloadProgress);
	connect(m_image.data(), &Image::finishedImage, this, &zoomWindow::replyFinishedZoom);

	m_imageTime.start();
	m_image->loadImage();
}

#define PERCENT 0.05f
#define TIME 500
void zoomWindow::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
	 if (m_image->isVideo() || m_url.section('.', -1).toLower() == "gif")
		 return;

	ui->progressBarDownload->setMaximum(bytesTotal);
	ui->progressBarDownload->setValue(bytesReceived);

	if (m_imageTime.elapsed() > TIME || (bytesTotal > 0 && bytesReceived / bytesTotal > PERCENT))
	{
		m_imageTime.restart();
		emit loadImage(m_image->data());
	}
}
void zoomWindow::display(const QPixmap &pix, int size)
{
	if (!pix.size().isEmpty() && size >= m_size)
	{
		m_size = size;
		m_displayImage = pix;
		update(!m_finished);

		if (!pix.size().isEmpty() && m_image->size().isEmpty())
		{
			m_image->setSize(pix.size());
			updateWindowTitle();
		}

		if (m_isFullscreen && m_fullScreen != nullptr && m_fullScreen->isVisible())
		{ m_fullScreen->setImage(m_displayImage.scaled(QApplication::desktop()->screenGeometry().size(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); }
	}
}

void zoomWindow::replyFinishedDetails()
{
	m_loadedDetails = true;
	colore();

	// Show pool information
	if (!m_image->pools().isEmpty())
	{
		QStringList pools = QStringList();
		for (const Pool &p : m_image->pools())
		{ pools.append((p.previous() != 0 ? "<a href=\""+QString::number(p.previous())+"\">&lt;</a> " : "")+"<a href=\"pool:"+QString::number(p.id())+"\">"+p.name()+"</a>"+(p.next() != 0 ? " <a href=\""+QString::number(p.next())+"\">&gt;</a>" : "")); }
		ui->labelPools->setText(pools.join("<br />"));
		ui->labelPools->show();
	}

	QString path1 = m_settings->value("Save/path").toString().replace("\\", "/");
	QStringList pth1s = m_image->path(m_settings->value("Save/filename").toString(), path1, 0, true, false, true, true, true);
	QString source1;
	bool file1notexists = false;
	for (QString pth1 : pth1s)
	{
		QFile file(pth1);
		if (file.exists())
			source1 = file.fileName();
		else
			file1notexists = true;
	}

	QString path2 = m_settings->value("Save/path_favorites").toString().replace("\\", "/");
	QStringList pth2s = m_image->path(m_settings->value("Save/filename_favorites").toString(), path2, 0, true, false, true, true, true);
	QString source2;
	bool file2notexists = false;
	for (QString pth2 : pth2s)
	{
		QFile file(pth2);
		if (file.exists())
			source2 = file.fileName();
		else
			file2notexists = true;
	}

	QString md5Exists = m_profile->md5Exists(m_image->md5());

	// If the file already exists, we directly display it
	if (!md5Exists.isEmpty() || !file1notexists || !file2notexists)
	{
		if (!file1notexists)
		{
			ui->buttonSave->setText(tr("Delete"));
			ui->buttonSaveNQuit->setText(tr("Close"));
		}
		if (!file2notexists)
		{
			ui->buttonSaveFav->setText(tr("Delete (fav)"));
			ui->buttonSaveNQuitFav->setText(tr("Close (fav)"));
		}
		m_source = !md5Exists.isEmpty() ? md5Exists : (!file1notexists ? source1 : source2);
		m_imagePath = m_source;
		log(QString("Image loaded from the file <a href=\"file:///%1\">%1</a>").arg(m_source));

		// Fix extension when it should be guessed
		QString fext = m_source.section('.', -1);
		m_url = m_url.section('.', 0, -2) + "." + fext;
		m_image->setFileExtension(fext);
		m_finished = true;

		draw();
	}

	// If the file does not exist, we have to load it
	else
	{
		if (m_url.isEmpty())
		{ m_url = m_image->url(); }
		load();
	}

	updateWindowTitle();
}
void zoomWindow::colore()
{
	QStringList t = m_image->stylishedTags(m_profile);
	tags = t.join(' ');
	if (ui->widgetLeft->isHidden())
	{ m_labelTagsTop->setText(tags); }
	else
	{ m_labelTagsLeft->setText(t.join("<br/>")); }
	m_detailsWindow->setTags(tags);
}

void zoomWindow::replyFinishedZoom(QNetworkReply::NetworkError err, QString errorString)
{
	log(QString("Image received from <a href=\"%1\">%1</a>").arg(m_url));

	ui->progressBarDownload->hide();
	m_finished = true;

	if (err == QNetworkReply::NoError)
	{
		m_url = m_image->url();
		m_loadedImage = true;

		updateWindowTitle();
		pendingUpdate();
		draw();
	}
	else if (err == QNetworkReply::ContentNotFoundError)
	{ log("Image not found."); }
	else if (err != QNetworkReply::OperationCanceledError)
	{ error(this, tr("An unexpected error occured loading the image (%1 - %2).\r\n%3").arg(err).arg(errorString).arg(m_image->url())); }
}

void zoomWindow::pendingUpdate()
{
	// If we don't want to save, nothing to do
	if (m_mustSave == 0)
		return;

	// If the image is not even loaded, we cannot save it
	if (!m_loadedImage)
		return;

	bool fav = (m_mustSave == 3 || m_mustSave == 4);
	Filename fn(m_settings->value("Save/path" + QString(fav ? "_favorites" : "")).toString());

	// If the image is loaded but we need their tags and we don't have them, we wait
	if (!m_loadedDetails && fn.needExactTags(m_site))
		return;

	m_loaded = true;

	switch (m_mustSave)
	{
		case 1:
			saveImageNow();
			break;

		case 2:
			if (!saveImageNow().isEmpty())
				close();
			break;

		case 3:
			saveImageNow(true);
			break;

		case 4:
			if (!saveImageNow(true).isEmpty())
				close();
			break;

		case 5:
			openFile(true);
			break;
	}

	m_mustSave = 0;
}

void zoomWindow::draw()
{
	// Videos don't get drawn
	if (m_image->isVideo())
		return;

	QString fn = m_url.section('/', -1).toLower();
	QString ext = fn.section('.', -1).toLower();

	// We need a filename to display animations, so we get it if we're not already loading from a file
	QString filename;
	if (!m_source.isEmpty())
	{ filename = m_source; }
	else if (ext == "gif")
	{
		filename = QDir::temp().absoluteFilePath("grabber-" + fn);
		QFile f(filename);
		if (f.open(QFile::WriteOnly))
		{
			f.write(m_image->data());
			f.close();
		}
	}

	// GIF (using QLabel support for QMovie)
	if (ext == "gif")
	{
		m_displayMovie = new QMovie(filename, QByteArray(), this);
		m_labelImage->setMovie(m_displayMovie);
		m_stackedWidget->setCurrentWidget(m_labelImage);
		m_displayMovie->start();

		m_displayImage = QPixmap();

		if (m_isFullscreen && m_fullScreen != nullptr && m_fullScreen->isVisible())
		{ m_fullScreen->setMovie(m_displayMovie); }
		return;
	}

	// Images
	if (!m_source.isEmpty())
	{
		m_displayImage = QPixmap();
		m_displayImage.load(m_source);

		m_loadedImage = true;
		pendingUpdate();
		update();

		if (m_isFullscreen && m_fullScreen != nullptr && m_fullScreen->isVisible())
		{ m_fullScreen->setImage(m_displayImage.scaled(QApplication::desktop()->screenGeometry().size(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); }
	}
	else
	{
		emit loadImage(m_image->data());
	}
}



/**
 * Updates the image label to use the current image.
 * @param onlysize true to update the image quickly
 */
void zoomWindow::update(bool onlysize, bool force)
{
	// Update image alignment
	QString ext = m_url.section('.', -1).toLower();
	QString type;
	if (m_image->isVideo())
	{ type = "imagePositionVideo"; }
	else if (ext == "gif")
	{ type = "imagePositionAnimation"; }
	else
	{ type = "imagePositionImage"; }
	m_labelImage->setAlignment(getAlignments(type));

	// Only used for images
	if (m_displayImage.isNull())
		return;

	bool needScaling = (m_displayImage.width() > m_labelImage->width() || m_displayImage.height() > m_labelImage->height());
	if (needScaling && (onlysize || m_loadedImage || force))
	{
		Qt::TransformationMode mode = onlysize ? Qt::FastTransformation : Qt::SmoothTransformation;
		m_labelImage->setImage(m_displayImage.scaled(m_labelImage->width(), m_labelImage->height(), Qt::KeepAspectRatio, mode));
	}
	else if (m_loadedImage || force)
	{
		m_labelImage->setImage(m_displayImage);
	}

	m_stackedWidget->setCurrentWidget(m_labelImage);
}

Qt::Alignment zoomWindow::getAlignments(QString type)
{
	QString vertical = m_settings->value(type + "V", "center").toString();
	QString horizontal = m_settings->value(type + "H", "left").toString();

	Qt::Alignment vAlign = vertical == "top" ? Qt::AlignTop : (vertical == "bottom" ? Qt::AlignBottom : Qt::AlignVCenter);
	Qt::Alignment hAlign = horizontal == "left" ? Qt::AlignLeft : (horizontal == "right" ? Qt::AlignRight : Qt::AlignHCenter);

	return vAlign | hAlign;
}

void zoomWindow::saveNQuit()
{
	if (!m_imagePath.isEmpty())
	{
		close();
		return;
	}

	ui->buttonSaveNQuit->setText(tr("Saving..."));
	m_mustSave = 2;
	pendingUpdate();
}
void zoomWindow::saveNQuitFav()
{

	if (!m_imagePath.isEmpty())
	{
		close();
		return;
	}

	ui->buttonSaveNQuitFav->setText(tr("Saving..."));
	m_mustSave = 4;
	pendingUpdate();
}

void zoomWindow::saveImage(bool fav)
{
	if (fav)
	{
		ui->buttonSaveFav->setText(tr("Saving... (fav)"));
		m_mustSave = 3;
	}
	else
	{
		ui->buttonSave->setText(tr("Saving..."));
		m_mustSave = 1;
	}
	pendingUpdate();
}
void zoomWindow::saveImageFav()
{ saveImage(true); }
QStringList zoomWindow::saveImageNow(bool fav)
{
	QString pth = m_settings->value("Save/path"+QString(fav ? "_favorites" : "")).toString().replace("\\", "/");
	if (pth.right(1) == "/")
	{ pth = pth.left(pth.length()-1); }

	if (pth.isEmpty() || m_settings->value("Save/filename").toString().isEmpty())
	{
		int reply;
		if (pth.isEmpty())
		{ reply = QMessageBox::question(this, tr("Error"), tr("You did not specified a save folder! Do you want to open the options window?"), QMessageBox::Yes | QMessageBox::No); }
		else
		{ reply = QMessageBox::question(this, tr("Error"), tr("You did not specified a save format! Do you want to open the options window?"), QMessageBox::Yes | QMessageBox::No); }
		if (reply == QMessageBox::Yes)
		{
			optionsWindow *options = new optionsWindow(m_profile, parentWidget());
			//options->onglets->setCurrentIndex(3);
			options->setWindowModality(Qt::ApplicationModal);
			options->show();
			connect(options, SIGNAL(closed()), this, SLOT(saveImage()));
		}
		return QStringList();
	}

	QStringList paths;
	QMap<QString, Image::SaveResult> results = m_image->save(m_settings->value("Save/filename"+QString(fav ? "_favorites" : "")).toString(), pth);
	auto it = results.begin();
	while (it != results.end())
	{
		Image::SaveResult res = it.value();
		paths.append(it.key());
		m_imagePath = it.key();

		QPushButton *button = fav ? ui->buttonSaveFav : ui->buttonSave;
		QPushButton *saveQuit = fav ? ui->buttonSaveNQuitFav : ui->buttonSaveNQuit;
		switch (res)
		{
			case Image::SaveResult::Error:
				error(this, tr("Error saving image."));
				return QStringList();
				break;

			case Image::SaveResult::Saved:
				button->setText(fav ? tr("Delete (fav)") : tr("Delete"));
				break;

			case Image::SaveResult::Copied:
				button->setText(fav ? tr("Copied! (fav)") : tr("Copied!"));
				break;

			case Image::SaveResult::Moved:
				button->setText(fav ? tr("Moved! (fav)") : tr("Moved!"));
				break;

			case Image::SaveResult::Ignored:
				button->setText(fav ? tr("Ignored! (fav)") : tr("Ignored!"));
				m_imagePath = m_profile->md5Exists(m_image->md5());
				break;

			case Image::SaveResult::AlreadyExists:
				QFile::remove(it.key());
				m_imagePath = "";
				button->setText(fav ? tr("Save (fav)") : tr("Save"));
				break;
		}
		saveQuit->setText(res == Image::SaveResult::AlreadyExists
						  ? (fav ? tr("Save and close (fav)") : tr("Save and close"))
						  : (fav ? tr("Close (fav)") : tr("Close")));

		++it;
	}

	if (m_mustSave == 2 || m_mustSave == 4)
		close();

	m_mustSave = 0;
	return paths;
}

QString zoomWindow::saveImageAs()
{
	QString filename = m_image->fileUrl().toString().section('/', -1);
	QString lastDir = m_settings->value("Zoom/lastDir", "").toString();<commit_msg>Keep image data when deleting it if not loaded (fix #868)<commit_after>#include "zoomwindow.h"
#include "ui_zoomwindow.h"
#include <QApplication>
#include <QDesktopWidget>
#include <QMessageBox>
#include <QShortcut>
#include <QMenu>
#include <QFileDialog>
#include <QScrollBar>
#include "ui/QAffiche.h"
#include "models/filename.h"
#include "models/site.h"
#include "models/page.h"
#include "models/profile.h"
#include "reverse-search/reverse-search-loader.h"
#include "settings/optionswindow.h"
#include "threads/image-loader.h"
#include "threads/image-loader-queue.h"
#include "detailswindow.h"
#include "mainwindow.h"
#include "helpers.h"
#include "functions.h"


zoomWindow::zoomWindow(QList<QSharedPointer<Image> > images, QSharedPointer<Image> image, Site *site, QMap<QString,Site*> *sites, Profile *profile, mainWindow *parent)
	: QWidget(Q_NULLPTR, Qt::Window), m_parent(parent), m_profile(profile), m_favorites(profile->getFavorites()), m_viewItLater(profile->getKeptForLater()), m_ignore(profile->getIgnored()), m_settings(profile->getSettings()), ui(new Ui::zoomWindow), m_site(site), timeout(300), m_loaded(false), m_loadedImage(false), m_loadedDetails(false), m_displayImage(QPixmap()), m_displayMovie(nullptr), m_finished(false), m_size(0), m_sites(sites), m_source(), m_fullScreen(nullptr), m_images(images), m_isFullscreen(false), m_isSlideshowRunning(false), m_imagePath("")
{
	setAttribute(Qt::WA_DeleteOnClose);
	ui->setupUi(this);

	m_mustSave = 0;

	restoreGeometry(m_settings->value("Zoom/geometry").toByteArray());
	ui->buttonPlus->setChecked(m_settings->value("Zoom/plus", false).toBool());
	ui->progressBarDownload->hide();

	QShortcut *escape = new QShortcut(QKeySequence(Qt::Key_Escape), this);
		connect(escape, &QShortcut::activated, this, &zoomWindow::close);
	QShortcut *save = new QShortcut(QKeySequence::Save, this);
		connect(save, SIGNAL(activated()), this, SLOT(saveImage()));
	QShortcut *saveAs = new QShortcut(QKeySequence::SaveAs, this);
		connect(saveAs, &QShortcut::activated, this, &zoomWindow::saveImageAs);
	QShortcut *arrowNext = new QShortcut(QKeySequence(Qt::Key_Right), this);
		connect(arrowNext, &QShortcut::activated, this, &zoomWindow::next);
	QShortcut *arrowPrevious = new QShortcut(QKeySequence(Qt::Key_Left), this);
		connect(arrowPrevious, &QShortcut::activated, this, &zoomWindow::previous);
	QShortcut *copyImageFile = new QShortcut(QKeySequence::Copy, this);
		connect(copyImageFile, &QShortcut::activated, this, &zoomWindow::copyImageDataToClipboard);
	QShortcut *toggleFullscreen = new QShortcut(QKeySequence::FullScreen, this);
		connect(toggleFullscreen, &QShortcut::activated, this, &zoomWindow::toggleFullScreen);

	m_labelTagsLeft = new QAffiche(QVariant(), 0, QColor(), this);
		m_labelTagsLeft->setContextMenuPolicy(Qt::CustomContextMenu);
		m_labelTagsLeft->setTextInteractionFlags(Qt::TextBrowserInteraction);
		connect(m_labelTagsLeft, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextMenu(QPoint)));
		connect(m_labelTagsLeft, SIGNAL(linkActivated(QString)), this, SLOT(openUrl(QString)));
		connect(m_labelTagsLeft, SIGNAL(linkHovered(QString)), this, SLOT(linkHovered(QString)));
		connect(m_labelTagsLeft, SIGNAL(middleClicked()), this, SLOT(openInNewTab()));
		ui->scrollAreaWidgetContents->layout()->addWidget(m_labelTagsLeft);

	m_labelTagsTop = new QAffiche(QVariant(), 0, QColor(), this);
		m_labelTagsTop->setWordWrap(true);
		m_labelTagsTop->setContextMenuPolicy(Qt::CustomContextMenu);
		m_labelTagsTop->setTextInteractionFlags(Qt::TextBrowserInteraction);
		connect(m_labelTagsTop, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextMenu(QPoint)));
		connect(m_labelTagsTop, SIGNAL(linkActivated(QString)), this, SLOT(openUrl(QString)));
		connect(m_labelTagsTop, SIGNAL(linkHovered(QString)), this, SLOT(linkHovered(QString)));
		connect(m_labelTagsTop, SIGNAL(middleClicked()), this, SLOT(openInNewTab()));
		ui->verticalLayout->insertWidget(0, m_labelTagsTop, 0);

	// Automatically re-color if some settings change
	connect(m_profile, &Profile::favoritesChanged, this, &zoomWindow::colore);
	connect(m_profile, &Profile::keptForLaterChanged, this, &zoomWindow::colore);
	connect(m_profile, &Profile::ignoredChanged, this, &zoomWindow::colore);

	m_stackedWidget = new QStackedWidget(this);
		ui->verticalLayout->insertWidget(1, m_stackedWidget, 1);
	m_labelImage = new QAffiche(QVariant(), 0, QColor(), this);
		m_labelImage->setSizePolicy(QSizePolicy(QSizePolicy::Ignored,QSizePolicy::Ignored));
		connect(m_labelImage, SIGNAL(doubleClicked()), this, SLOT(openFile()));
		m_stackedWidget->addWidget(m_labelImage);

	connect(ui->buttonDetails, SIGNAL(clicked()), this, SLOT(showDetails()));
	connect(ui->buttonPlus, &QPushButton::toggled, this, &zoomWindow::updateButtonPlus);

	m_labelImage->setContextMenuPolicy(Qt::CustomContextMenu);
	connect(m_labelImage, &QAffiche::customContextMenuRequested, this, &zoomWindow::imageContextMenu);

	m_slideshow.setSingleShot(true);
	connect(&m_slideshow, &QTimer::timeout, this, &zoomWindow::next);

	// Overlay progressbar and image
	ui->overlayLayout->removeWidget(ui->progressBarDownload);
	ui->overlayLayout->addWidget(ui->progressBarDownload, 0, 0, Qt::AlignBottom);
	ui->progressBarDownload->raise();

	// Threads
	m_imageLoaderThread.setObjectName("Image loader thread");
	m_imageLoader = new ImageLoader();
	m_imageLoader->moveToThread(&m_imageLoaderThread);
	m_imageLoaderQueueThread.setObjectName("Image loader queue thread");
	m_imageLoaderQueue = new ImageLoaderQueue(m_imageLoader);
	m_imageLoaderQueue->moveToThread(&m_imageLoaderQueueThread);
	connect(&m_imageLoaderThread, &QThread::finished, m_imageLoader, &QObject::deleteLater);
	connect(&m_imageLoaderQueueThread, &QThread::finished, m_imageLoaderQueue, &QObject::deleteLater);
	connect(this, &zoomWindow::loadImage, m_imageLoaderQueue, &ImageLoaderQueue::load);
	connect(this, &zoomWindow::clearLoadQueue, m_imageLoaderQueue, &ImageLoaderQueue::clear);
	connect(m_imageLoaderQueue, &ImageLoaderQueue::finished, this, &zoomWindow::display);
	m_imageLoaderQueueThread.start();
	m_imageLoaderThread.start();

	// Background color
	QString bg = m_settings->value("imageBackgroundColor", "").toString();
	if (!bg.isEmpty())
	{
		setStyleSheet("#zoomWindow { background-color:" + bg + "; }");
		m_labelTagsLeft->setStyleSheet("background-color:" + bg);
		m_labelTagsTop->setStyleSheet("background-color:" + bg);
	}

	load(image);
}
void zoomWindow::go()
{
	ui->labelPools->hide();
	bool whitelisted = false;
	if (!m_settings->value("whitelistedtags").toString().isEmpty())
	{
		QStringList whitelist = m_settings->value("whitelistedtags").toString().split(" ");
		for (Tag t : m_image->tags())
		{
			if (whitelist.contains(t.text()))
			{
				whitelisted = true;
				break;
			}
		}
	}
	if (m_settings->value("autodownload", false).toBool() || (whitelisted && m_settings->value("whitelist_download", "image").toString() == "image"))
	{ saveImage(); }

	m_url = m_image->getDisplayableUrl().toString();

	QTimer *timer = new QTimer(this);
		connect(timer, SIGNAL(timeout()), this, SLOT(update()));
		timer->setSingleShot(true);
		m_resizeTimer = timer;

	QString u = m_site->value("Urls/Html/Post");
		u.replace("{id}", QString::number(m_image->id()));

	QString pos = m_settings->value("tagsposition", "top").toString();
	if (pos == "auto")
	{
		if (!m_image->size().isEmpty())
		{
			if (float(m_image->width())/float(m_image->height()) >= 4./3.)
			{ pos = "top"; }
			else
			{ pos = "left"; }
		}
		else
		{ pos = "top"; }
	}

	if (pos == "top")
	{
		ui->widgetLeft->hide();
		m_labelTagsTop->setText(m_image->stylishedTags(m_profile).join(" "));
		m_labelTagsTop->show();
	}
	else
	{
		m_labelTagsTop->hide();
		m_labelTagsLeft->setText(m_image->stylishedTags(m_profile).join("<br/>"));
		m_labelTagsLeft->setMinimumWidth(m_labelTagsLeft->sizeHint().width() + ui->scrollArea->verticalScrollBar()->sizeHint().width());
		m_labelTagsLeft->show();
		ui->widgetLeft->show();
	}

	m_detailsWindow = new detailsWindow(m_profile, this);

	// Load image details (exact tags & co)
	connect(m_image.data(), &Image::finishedLoadingTags, this, &zoomWindow::replyFinishedDetails, Qt::UniqueConnection);
	m_image->loadDetails();

	ReverseSearchLoader loader(m_settings);
	m_reverseSearchEngines = loader.getAllReverseSearchEngines();

	if (!m_isFullscreen)
		activateWindow();
}

/**
 * Destructor of the zoomWindow class
 */
zoomWindow::~zoomWindow()
{
	if (m_displayMovie != nullptr)
		m_displayMovie->deleteLater();

	m_labelTagsTop->deleteLater();
	m_labelTagsLeft->deleteLater();
	m_detailsWindow->deleteLater();

	// Quit threads
	m_imageLoaderQueueThread.quit();
	m_imageLoaderThread.wait(1000);
	m_imageLoaderThread.quit();
	m_imageLoaderThread.wait(1000);

	delete ui;
}

void zoomWindow::imageContextMenu()
{
	QMenu *menu = new QMenu(this);

	// Copy actions
	menu->addAction(QIcon(":/images/icons/copy.png"), tr("Copy file"), this, SLOT(copyImageFileToClipboard()));
	menu->addAction(QIcon(":/images/icons/document-binary.png"), tr("Copy data"), this, SLOT(copyImageDataToClipboard()));
	menu->addSeparator();

	// Reverse search actions
	m_reverseSearchSignalMapper = new QSignalMapper(this);
	connect(m_reverseSearchSignalMapper, SIGNAL(mapped(int)), this, SLOT(reverseImageSearch(int)));
	for (int i = 0; i < m_reverseSearchEngines.count(); ++i)
	{
		ReverseSearchEngine engine = m_reverseSearchEngines[i];
		QAction *subMenuAct = menu->addAction(engine.icon(), engine.name());
		connect(subMenuAct, SIGNAL(triggered()), m_reverseSearchSignalMapper, SLOT(map()));
		m_reverseSearchSignalMapper->setMapping(subMenuAct, i);
	}

	menu->exec(QCursor::pos());
}
void zoomWindow::reverseImageSearch(int i)
{
	if (m_reverseSearchEngines.count() < i)
		return;

	m_reverseSearchEngines[i].searchByUrl(m_image->fileUrl());
}
void zoomWindow::copyImageFileToClipboard()
{
	QString path = m_imagePath;
	if (path.isEmpty() || !QFile::exists(path))
	{
		QMap<QString, Image::SaveResult> files = m_image->save(m_settings->value("Save/filename").toString(), m_profile->tempPath(), false);
		path = files.firstKey();
	}

	QMimeData* mimeData = new QMimeData();
	mimeData->setUrls({ QUrl::fromLocalFile(path) });
	QApplication::clipboard()->setMimeData(mimeData);
}
void zoomWindow::copyImageDataToClipboard()
{
	QApplication::clipboard()->setPixmap(m_displayImage);
}

void zoomWindow::showDetails()
{
	m_detailsWindow->setImage(m_image);
	m_detailsWindow->show();
}

void zoomWindow::openUrl(QString url)
{ emit linkClicked(url); }
void zoomWindow::openPool(QString url)
{
	if (url.startsWith("pool:"))
	{ emit poolClicked(url.right(url.length() - 5).toInt(), m_image->site()); }
	else
	{
		Page *p = new Page(m_profile, m_sites->value(m_image->site()), m_sites->values(), QStringList() << "id:"+url, 1, 1, QStringList(), false, this);
		connect(p, SIGNAL(finishedLoading(Page*)), this, SLOT(openPoolId(Page*)));
		p->load();
	}
}
void zoomWindow::openPoolId(Page *p)
{
	if (p->images().size() < 1)
	{
		p->deleteLater();
		return;
	}

	m_image = p->images().at(0);
	timeout = 300;
	m_loaded = false;
	m_loadedDetails = false;
	m_loadedImage = false;
	m_finished = false;
	m_size = 0;
	m_labelImage->hide();
	ui->verticalLayout->removeWidget(m_labelImage);

	go();
}

void zoomWindow::openSaveDir(bool fav)
{
	// If the file was already saved, we focus on it
	if (!m_imagePath.isEmpty())
	{
		showInGraphicalShell(m_imagePath);
	}
	else
	{
		QString path = m_settings->value("Save/path"+QString(fav ? "_favorites" : "")).toString().replace("\\", "/"), fn = m_settings->value("Save/filename"+QString(fav ? "_favorites" : "")).toString();

		if (path.right(1) == "/")
		{ path = path.left(path.length()-1); }
		path = QDir::toNativeSeparators(path);

		QStringList files = m_image->path(fn, path);
		QString file = files.empty() ? "" : files.at(0);
		QString pth = file.section(QDir::toNativeSeparators("/"), 0, -2);
		QString url = path+QDir::toNativeSeparators("/")+pth;

		QDir dir(url);
		if (dir.exists())
		{ showInGraphicalShell(url); }
		else
		{
			int reply = QMessageBox::question(this, tr("Folder does not exist"), tr("The save folder does not exist yet. Create it?"), QMessageBox::Yes | QMessageBox::No);
			if (reply == QMessageBox::Yes)
			{
				QDir dir(path);
				if (!dir.mkpath(pth))
				{ error(this, tr("Error creating folder.\r\n%1").arg(url)); }
				showInGraphicalShell(url);
			}
		}
	}
}
void zoomWindow::openSaveDirFav()
{ openSaveDir(true); }

void zoomWindow::linkHovered(QString url)
{ this->link = url; }
void zoomWindow::contextMenu(QPoint)
{
	QMenu *menu = new QMenu(this);
	if (!this->link.isEmpty())
	{
		// Favorites
		if (m_favorites.contains(Favorite(link)))
		{
			menu->addAction(QIcon(":/images/icons/remove.png"), tr("Remove from favorites"), this, SLOT(unfavorite()));
			menu->addAction(QIcon(":/images/icons/save.png"), tr("Choose as image"), this, SLOT(setfavorite()));
		}
		else
		{ menu->addAction(QIcon(":/images/icons/add.png"), tr("Add to favorites"), this, SLOT(favorite())); }

		// Keep for later
		if (m_viewItLater.contains(link, Qt::CaseInsensitive))
		{ menu->addAction(QIcon(":/images/icons/remove.png"), tr("Don't keep for later"), this, SLOT(unviewitlater())); }
		else
		{ menu->addAction(QIcon(":/images/icons/add.png"), tr("Keep for later"), this, SLOT(viewitlater())); }

		// Blacklist
		QStringList blacklistedTags = m_settings->value("blacklistedtags").toString().split(' ');
		if (blacklistedTags.contains(link))
		{ menu->addAction(QIcon(":/images/icons/eye-plus.png"), tr("Don't blacklist"), this, SLOT(unblacklist())); }
		else
		{ menu->addAction(QIcon(":/images/icons/eye-minus.png"), tr("Blacklist"), this, SLOT(blacklist())); }

		// Ignore
		if (m_ignore.contains(link, Qt::CaseInsensitive))
		{ menu->addAction(QIcon(":/images/icons/eye-plus.png"), tr("Don't ignore"), this, SLOT(unignore())); }
		else
		{ menu->addAction(QIcon(":/images/icons/eye-minus.png"), tr("Ignore"), this, SLOT(ignore())); }
		menu->addSeparator();

		// Copy
		menu->addAction(QIcon(":/images/icons/copy.png"), tr("Copy tag"), this, SLOT(copyTagToClipboard()));
		menu->addAction(QIcon(":/images/icons/copy.png"), tr("Copy all tags"), this, SLOT(copyAllTagsToClipboard()));
		menu->addSeparator();

		// Tabs
		menu->addAction(QIcon(":/images/icons/tab-plus.png"), tr("Open in a new tab"), this, SLOT(openInNewTab()));
		menu->addAction(QIcon(":/images/icons/window.png"), tr("Open in new a window"), this, SLOT(openInNewWindow()));
		menu->addAction(QIcon(":/images/icons/browser.png"), tr("Open in browser"), this, SLOT(openInBrowser()));
	}
	menu->exec(QCursor::pos());
}

void zoomWindow::openInNewTab()
{
	m_parent->addTab(link);
}
void zoomWindow::openInNewWindow()
{
	QProcess myProcess;
	myProcess.startDetached(qApp->arguments().at(0), QStringList(link));
}
void zoomWindow::openInBrowser()
{
	QDesktopServices::openUrl(m_image->pageUrl());
}
void zoomWindow::copyTagToClipboard()
{
	QApplication::clipboard()->setText(this->link);
}
void zoomWindow::copyAllTagsToClipboard()
{
	QStringList tags;
	for (Tag tag : m_image->tags())
		tags.append(tag.text());

	QApplication::clipboard()->setText(tags.join(' '));
}

void zoomWindow::favorite()
{
	Favorite fav(link);
	if (m_loadedImage)
		fav.setImage(m_displayImage);

	m_profile->addFavorite(fav);
}
void zoomWindow::setfavorite()
{
	if (!m_loadedImage)
		return;

	Favorite fav(link);
	int pos = m_favorites.indexOf(fav);
	if (pos >= 0)
	{
		m_favorites[pos].setImage(m_displayImage);
	}
	else
	{
		fav.setImage(m_displayImage);
		m_favorites.append(fav);
	}

	m_profile->emitFavorite();
}
void zoomWindow::unfavorite()
{
	m_profile->removeFavorite(Favorite(link));
}

void zoomWindow::viewitlater()
{
	m_profile->addKeptForLater(link);
}
void zoomWindow::unviewitlater()
{
	m_profile->removeKeptForLater(link);
}

void zoomWindow::ignore()
{
	m_profile->addIgnored(link);
}
void zoomWindow::unignore()
{
	m_profile->removeIgnored(link);
}

void zoomWindow::blacklist()
{
	QString blacklistedTags = m_settings->value("blacklistedtags").toString();
	blacklistedTags += " " + link;
	m_settings->setValue("blacklistedtags", blacklistedTags);
	colore();
}
void zoomWindow::unblacklist()
{
	QStringList blacklistedTags = m_settings->value("blacklistedtags").toString().split(' ');
	blacklistedTags.removeAll(link);
	m_settings->setValue("blacklistedtags", blacklistedTags.join(' '));
	colore();
}

void zoomWindow::load()
{
	log(QString("Loading image from <a href=\"%1\">%1</a>").arg(m_url));

	m_source.clear();

	ui->progressBarDownload->setMaximum(100);
	ui->progressBarDownload->setValue(0);
	ui->progressBarDownload->show();

	connect(m_image.data(), &Image::downloadProgressImage, this, &zoomWindow::downloadProgress);
	connect(m_image.data(), &Image::finishedImage, this, &zoomWindow::replyFinishedZoom);

	m_imageTime.start();
	m_image->loadImage();
}

#define PERCENT 0.05f
#define TIME 500
void zoomWindow::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
	 if (m_image->isVideo() || m_url.section('.', -1).toLower() == "gif")
		 return;

	ui->progressBarDownload->setMaximum(bytesTotal);
	ui->progressBarDownload->setValue(bytesReceived);

	if (m_imageTime.elapsed() > TIME || (bytesTotal > 0 && bytesReceived / bytesTotal > PERCENT))
	{
		m_imageTime.restart();
		emit loadImage(m_image->data());
	}
}
void zoomWindow::display(const QPixmap &pix, int size)
{
	if (!pix.size().isEmpty() && size >= m_size)
	{
		m_size = size;
		m_displayImage = pix;
		update(!m_finished);

		if (!pix.size().isEmpty() && m_image->size().isEmpty())
		{
			m_image->setSize(pix.size());
			updateWindowTitle();
		}

		if (m_isFullscreen && m_fullScreen != nullptr && m_fullScreen->isVisible())
		{ m_fullScreen->setImage(m_displayImage.scaled(QApplication::desktop()->screenGeometry().size(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); }
	}
}

void zoomWindow::replyFinishedDetails()
{
	m_loadedDetails = true;
	colore();

	// Show pool information
	if (!m_image->pools().isEmpty())
	{
		QStringList pools = QStringList();
		for (const Pool &p : m_image->pools())
		{ pools.append((p.previous() != 0 ? "<a href=\""+QString::number(p.previous())+"\">&lt;</a> " : "")+"<a href=\"pool:"+QString::number(p.id())+"\">"+p.name()+"</a>"+(p.next() != 0 ? " <a href=\""+QString::number(p.next())+"\">&gt;</a>" : "")); }
		ui->labelPools->setText(pools.join("<br />"));
		ui->labelPools->show();
	}

	QString path1 = m_settings->value("Save/path").toString().replace("\\", "/");
	QStringList pth1s = m_image->path(m_settings->value("Save/filename").toString(), path1, 0, true, false, true, true, true);
	QString source1;
	bool file1notexists = false;
	for (QString pth1 : pth1s)
	{
		QFile file(pth1);
		if (file.exists())
			source1 = file.fileName();
		else
			file1notexists = true;
	}

	QString path2 = m_settings->value("Save/path_favorites").toString().replace("\\", "/");
	QStringList pth2s = m_image->path(m_settings->value("Save/filename_favorites").toString(), path2, 0, true, false, true, true, true);
	QString source2;
	bool file2notexists = false;
	for (QString pth2 : pth2s)
	{
		QFile file(pth2);
		if (file.exists())
			source2 = file.fileName();
		else
			file2notexists = true;
	}

	QString md5Exists = m_profile->md5Exists(m_image->md5());

	// If the file already exists, we directly display it
	if (!md5Exists.isEmpty() || !file1notexists || !file2notexists)
	{
		if (!file1notexists)
		{
			ui->buttonSave->setText(tr("Delete"));
			ui->buttonSaveNQuit->setText(tr("Close"));
		}
		if (!file2notexists)
		{
			ui->buttonSaveFav->setText(tr("Delete (fav)"));
			ui->buttonSaveNQuitFav->setText(tr("Close (fav)"));
		}
		m_source = !md5Exists.isEmpty() ? md5Exists : (!file1notexists ? source1 : source2);
		m_imagePath = m_source;
		log(QString("Image loaded from the file <a href=\"file:///%1\">%1</a>").arg(m_source));

		// Fix extension when it should be guessed
		QString fext = m_source.section('.', -1);
		m_url = m_url.section('.', 0, -2) + "." + fext;
		m_image->setFileExtension(fext);
		m_finished = true;

		draw();
	}

	// If the file does not exist, we have to load it
	else
	{
		if (m_url.isEmpty())
		{ m_url = m_image->url(); }
		load();
	}

	updateWindowTitle();
}
void zoomWindow::colore()
{
	QStringList t = m_image->stylishedTags(m_profile);
	tags = t.join(' ');
	if (ui->widgetLeft->isHidden())
	{ m_labelTagsTop->setText(tags); }
	else
	{ m_labelTagsLeft->setText(t.join("<br/>")); }
	m_detailsWindow->setTags(tags);
}

void zoomWindow::replyFinishedZoom(QNetworkReply::NetworkError err, QString errorString)
{
	log(QString("Image received from <a href=\"%1\">%1</a>").arg(m_url));

	ui->progressBarDownload->hide();
	m_finished = true;

	if (err == QNetworkReply::NoError)
	{
		m_url = m_image->url();
		m_loadedImage = true;

		updateWindowTitle();
		pendingUpdate();
		draw();
	}
	else if (err == QNetworkReply::ContentNotFoundError)
	{ log("Image not found."); }
	else if (err != QNetworkReply::OperationCanceledError)
	{ error(this, tr("An unexpected error occured loading the image (%1 - %2).\r\n%3").arg(err).arg(errorString).arg(m_image->url())); }
}

void zoomWindow::pendingUpdate()
{
	// If we don't want to save, nothing to do
	if (m_mustSave == 0)
		return;

	// If the image is not even loaded, we cannot save it
	if (!m_loadedImage)
		return;

	bool fav = (m_mustSave == 3 || m_mustSave == 4);
	Filename fn(m_settings->value("Save/path" + QString(fav ? "_favorites" : "")).toString());

	// If the image is loaded but we need their tags and we don't have them, we wait
	if (!m_loadedDetails && fn.needExactTags(m_site))
		return;

	m_loaded = true;

	switch (m_mustSave)
	{
		case 1:
			saveImageNow();
			break;

		case 2:
			if (!saveImageNow().isEmpty())
				close();
			break;

		case 3:
			saveImageNow(true);
			break;

		case 4:
			if (!saveImageNow(true).isEmpty())
				close();
			break;

		case 5:
			openFile(true);
			break;
	}

	m_mustSave = 0;
}

void zoomWindow::draw()
{
	// Videos don't get drawn
	if (m_image->isVideo())
		return;

	QString fn = m_url.section('/', -1).toLower();
	QString ext = fn.section('.', -1).toLower();

	// We need a filename to display animations, so we get it if we're not already loading from a file
	QString filename;
	if (!m_source.isEmpty())
	{ filename = m_source; }
	else if (ext == "gif")
	{
		filename = QDir::temp().absoluteFilePath("grabber-" + fn);
		QFile f(filename);
		if (f.open(QFile::WriteOnly))
		{
			f.write(m_image->data());
			f.close();
		}
	}

	// GIF (using QLabel support for QMovie)
	if (ext == "gif")
	{
		m_displayMovie = new QMovie(filename, QByteArray(), this);
		m_labelImage->setMovie(m_displayMovie);
		m_stackedWidget->setCurrentWidget(m_labelImage);
		m_displayMovie->start();

		m_displayImage = QPixmap();

		if (m_isFullscreen && m_fullScreen != nullptr && m_fullScreen->isVisible())
		{ m_fullScreen->setMovie(m_displayMovie); }
		return;
	}

	// Images
	if (!m_source.isEmpty())
	{
		m_displayImage = QPixmap();
		m_displayImage.load(m_source);

		m_loadedImage = true;
		pendingUpdate();
		update();

		if (m_isFullscreen && m_fullScreen != nullptr && m_fullScreen->isVisible())
		{ m_fullScreen->setImage(m_displayImage.scaled(QApplication::desktop()->screenGeometry().size(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); }
	}
	else
	{
		emit loadImage(m_image->data());
	}
}



/**
 * Updates the image label to use the current image.
 * @param onlysize true to update the image quickly
 */
void zoomWindow::update(bool onlysize, bool force)
{
	// Update image alignment
	QString ext = m_url.section('.', -1).toLower();
	QString type;
	if (m_image->isVideo())
	{ type = "imagePositionVideo"; }
	else if (ext == "gif")
	{ type = "imagePositionAnimation"; }
	else
	{ type = "imagePositionImage"; }
	m_labelImage->setAlignment(getAlignments(type));

	// Only used for images
	if (m_displayImage.isNull())
		return;

	bool needScaling = (m_displayImage.width() > m_labelImage->width() || m_displayImage.height() > m_labelImage->height());
	if (needScaling && (onlysize || m_loadedImage || force))
	{
		Qt::TransformationMode mode = onlysize ? Qt::FastTransformation : Qt::SmoothTransformation;
		m_labelImage->setImage(m_displayImage.scaled(m_labelImage->width(), m_labelImage->height(), Qt::KeepAspectRatio, mode));
	}
	else if (m_loadedImage || force)
	{
		m_labelImage->setImage(m_displayImage);
	}

	m_stackedWidget->setCurrentWidget(m_labelImage);
}

Qt::Alignment zoomWindow::getAlignments(QString type)
{
	QString vertical = m_settings->value(type + "V", "center").toString();
	QString horizontal = m_settings->value(type + "H", "left").toString();

	Qt::Alignment vAlign = vertical == "top" ? Qt::AlignTop : (vertical == "bottom" ? Qt::AlignBottom : Qt::AlignVCenter);
	Qt::Alignment hAlign = horizontal == "left" ? Qt::AlignLeft : (horizontal == "right" ? Qt::AlignRight : Qt::AlignHCenter);

	return vAlign | hAlign;
}

void zoomWindow::saveNQuit()
{
	if (!m_imagePath.isEmpty())
	{
		close();
		return;
	}

	ui->buttonSaveNQuit->setText(tr("Saving..."));
	m_mustSave = 2;
	pendingUpdate();
}
void zoomWindow::saveNQuitFav()
{

	if (!m_imagePath.isEmpty())
	{
		close();
		return;
	}

	ui->buttonSaveNQuitFav->setText(tr("Saving..."));
	m_mustSave = 4;
	pendingUpdate();
}

void zoomWindow::saveImage(bool fav)
{
	if (fav)
	{
		ui->buttonSaveFav->setText(tr("Saving... (fav)"));
		m_mustSave = 3;
	}
	else
	{
		ui->buttonSave->setText(tr("Saving..."));
		m_mustSave = 1;
	}
	pendingUpdate();
}
void zoomWindow::saveImageFav()
{ saveImage(true); }
QStringList zoomWindow::saveImageNow(bool fav)
{
	QString pth = m_settings->value("Save/path"+QString(fav ? "_favorites" : "")).toString().replace("\\", "/");
	if (pth.right(1) == "/")
	{ pth = pth.left(pth.length()-1); }

	if (pth.isEmpty() || m_settings->value("Save/filename").toString().isEmpty())
	{
		int reply;
		if (pth.isEmpty())
		{ reply = QMessageBox::question(this, tr("Error"), tr("You did not specified a save folder! Do you want to open the options window?"), QMessageBox::Yes | QMessageBox::No); }
		else
		{ reply = QMessageBox::question(this, tr("Error"), tr("You did not specified a save format! Do you want to open the options window?"), QMessageBox::Yes | QMessageBox::No); }
		if (reply == QMessageBox::Yes)
		{
			optionsWindow *options = new optionsWindow(m_profile, parentWidget());
			//options->onglets->setCurrentIndex(3);
			options->setWindowModality(Qt::ApplicationModal);
			options->show();
			connect(options, SIGNAL(closed()), this, SLOT(saveImage()));
		}
		return QStringList();
	}

	QStringList paths;
	QMap<QString, Image::SaveResult> results = m_image->save(m_settings->value("Save/filename"+QString(fav ? "_favorites" : "")).toString(), pth);
	auto it = results.begin();
	while (it != results.end())
	{
		Image::SaveResult res = it.value();
		paths.append(it.key());
		m_imagePath = it.key();

		QPushButton *button = fav ? ui->buttonSaveFav : ui->buttonSave;
		QPushButton *saveQuit = fav ? ui->buttonSaveNQuitFav : ui->buttonSaveNQuit;
		switch (res)
		{
			case Image::SaveResult::Error:
				error(this, tr("Error saving image."));
				return QStringList();
				break;

			case Image::SaveResult::Saved:
				button->setText(fav ? tr("Delete (fav)") : tr("Delete"));
				break;

			case Image::SaveResult::Copied:
				button->setText(fav ? tr("Copied! (fav)") : tr("Copied!"));
				break;

			case Image::SaveResult::Moved:
				button->setText(fav ? tr("Moved! (fav)") : tr("Moved!"));
				break;

			case Image::SaveResult::Ignored:
				button->setText(fav ? tr("Ignored! (fav)") : tr("Ignored!"));
				m_imagePath = m_profile->md5Exists(m_image->md5());
				break;

			case Image::SaveResult::AlreadyExists:
				QFile f(it.key());
				if (m_image->data().isEmpty() && f.open(QFile::ReadOnly))
				{ m_image->setData(f.readAll()); }
				f.remove();
				m_imagePath = "";
				button->setText(fav ? tr("Save (fav)") : tr("Save"));
				break;
		}
		saveQuit->setText(res == Image::SaveResult::AlreadyExists
						  ? (fav ? tr("Save and close (fav)") : tr("Save and close"))
						  : (fav ? tr("Close (fav)") : tr("Close")));

		++it;
	}

	if (m_mustSave == 2 || m_mustSave == 4)
		close();

	m_mustSave = 0;
	return paths;
}

QString zoomWindow::saveImageAs()
{
	QString filename = m_image->fileUrl().toString().section('/', -1);
	QString lastDir = m_settings->value("Zoom/lastDir", "").toString();
{'fraction_non_alphanumeric': 0.17941919861427788, 'fraction_numerical': 0.0047192321968291, 'mean_word_length': 5.751551312649164, 'pattern_counts': {'":': 32, '<': 69, '<?xml version=': 0, '>': 575, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 30, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6117636', 'n_tokens_mistral': 21031, 'n_tokens_neox': 19659, 'n_words': 3475}