// Copyright 2005-2019 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.

#include "mumble_pch.hpp"

#include "OverlayConfig.h"

#include "Overlay.h"
#include "OverlayUserGroup.h"
#include "OverlayPositionableItem.h"
#include "OverlayText.h"
#include "User.h"
#include "Channel.h"
#include "Message.h"
#include "Database.h"
#include "NetworkConfig.h"
#include "ServerHandler.h"
#include "MainWindow.h"
#include "GlobalShortcut.h"
#include "PathListWidget.h"

// We define a global macro called 'g'. This can lead to issues when included code uses 'g' as a type or parameter name (like protobuf 3.7 does). As such, for now, we have to make this our last include.
#include "Global.h"

#ifdef Q_OS_WIN
#include "../../overlay/overlay_launchers.h"
#include "../../overlay/overlay_whitelist.h"
#include "../../overlay/overlay_blacklist.h"
#endif

static const int OVERLAYCONFIG_PATH_ROLE = Qt::UserRole;
static const int OVERLAYCONFIG_BUILTIN_ROLE = Qt::UserRole + 1;

// Hide overlay config for Mac OS X universal builds
#if !defined(USE_MAC_UNIVERSAL)
static ConfigWidget *OverlayConfigDialogNew(Settings &st) {
	return new OverlayConfig(st);
}

static ConfigRegistrar registrar(6000, OverlayConfigDialogNew);
#endif

void OverlayConfig::initDisplayFps() {
	// set up FPS preview
	qgsFpsPreview.clear();
	qgsFpsPreview.setBackgroundBrush(qgvFpsPreview->backgroundBrush());

	qgpiFpsDemo = new QGraphicsPixmapItem();
	refreshFpsDemo();

	qgsFpsPreview.addItem(qgpiFpsDemo);
	qgpiFpsDemo->show();

	qgvFpsPreview->setScene(&qgsFpsPreview);
	qgvFpsPreview->centerOn(qgpiFpsDemo);

	qgpiFpsLive = new OverlayPositionableItem(&s.os.qrfFps, true);
	qgpiFpsLive->setZValue(-2.0f);
	refreshFpsLive();
}

void OverlayConfig::initDisplayClock() {
	qgpiTimeLive = new OverlayPositionableItem(&s.os.qrfTime, true);
	qgpiTimeLive->setZValue(-2.0f);
	refreshTimeLive();
}

void OverlayConfig::initDisplay() {
	// set up overlay preview
	qgpiScreen = new QGraphicsPixmapItem();
	qgpiScreen->setPixmap(qpScreen);
	qgpiScreen->setOpacity(0.5f);
	qgpiScreen->setZValue(-10.0f);

	initDisplayFps();
	initDisplayClock();

	qgtiInstructions = new QGraphicsTextItem();
	qgtiInstructions->setHtml(QString::fromLatin1("<ul><li>%1</li><li>%2</li><li>%3</li></ul>").arg(
	                              tr("To move the users, drag the little red dot."),
	                              tr("To resize the users, mouse wheel over a user."),
	                              tr("For more options, right click a user.")
	                          ));
	qgtiInstructions->setOpacity(1.0f);
	qgtiInstructions->setZValue(-5.0f);
	qgtiInstructions->setDefaultTextColor(Qt::white);

	qgs.clear();
	qgs.setSceneRect(QRectF(0, 0, qgpiScreen->pixmap().width(), qgpiScreen->pixmap().height()));
	qgs.setBackgroundBrush(qgvView->backgroundBrush());

	qgs.addItem(qgpiScreen);
	qgpiScreen->show();

	qgs.addItem(qgpiFpsLive);
	qgpiFpsLive->show();

	qgs.addItem(qgpiTimeLive);
	qgpiTimeLive->show();

	oug = new OverlayUserGroup(&s.os);
	oug->bShowExamples = true;
	qgs.addItem(oug);
	oug->show();

	qgs.addItem(qgtiInstructions);
	qgtiInstructions->show();

	qgvView->setScene(&qgs);

	qgvView->installEventFilter(this);
}

void OverlayConfig::refreshFpsDemo() {
	bpFpsDemo = OverlayTextLine(QString::fromLatin1("%1").arg(42), s.os.qfFps).createPixmap(s.os.qcFps);
	qgpiFpsDemo->setPixmap(bpFpsDemo);
	qgvFpsPreview->centerOn(qgpiFpsDemo);
}

void OverlayConfig::refreshFpsLive() {
	if (s.os.bFps) {
		qgpiFpsLive->setPixmap(bpFpsDemo.scaled(bpFpsDemo.size() * fViewScale));
		qgpiFpsLive->setOffset((-bpFpsDemo.qpBasePoint + QPoint(0, bpFpsDemo.iAscent)) * fViewScale);
	} else {
		qgpiFpsLive->setPixmap(QPixmap());
	}
	qgpiFpsLive->setItemVisible(s.os.bFps);
}

void OverlayConfig::refreshTimeLive() {
	if (s.os.bTime) {
		bpTimeDemo = OverlayTextLine(QString::fromLatin1("%1").arg(QTime::currentTime().toString()), s.os.qfFps).createPixmap(s.os.qcFps);
		qgpiTimeLive->setPixmap(bpTimeDemo.scaled(bpTimeDemo.size() * fViewScale));
		qgpiTimeLive->setOffset((-bpTimeDemo.qpBasePoint + QPoint(0, bpTimeDemo.iAscent)) * fViewScale);
	} else {
		qgpiTimeLive->setPixmap(QPixmap());
	}
	qgpiTimeLive->setItemVisible(s.os.bTime);
}

OverlayConfig::OverlayConfig(Settings &st) :
		ConfigWidget(st),
		qgpiScreen(NULL),
		qgs(),
		qgsFpsPreview(),
		qgpiFpsDemo(NULL),
		oug(NULL),
		qgtiInstructions(NULL),
		fViewScale(1.0f) {
	setupUi(this);

	qlwPaths->setPathType(PathListWidget::FOLDER);

	qcbOverlayExclusionMode->insertItem(static_cast<int>(OverlaySettings::LauncherFilterExclusionMode), tr("Launcher Filter"));
	qcbOverlayExclusionMode->insertItem(static_cast<int>(OverlaySettings::WhitelistExclusionMode), tr("Whitelist"));
	qcbOverlayExclusionMode->insertItem(static_cast<int>(OverlaySettings::BlacklistExclusionMode), tr("Blacklist"));
	qcbOverlayExclusionMode->setCurrentIndex(static_cast<int>(OverlaySettings::LauncherFilterExclusionMode));

	if (! isInstalled()) {
		qswOverlayPage->setCurrentWidget(qwOverlayInstall);
	} else if (needsUpgrade()) {
		qswOverlayPage->setCurrentWidget(qwOverlayUpgrade);
	} else {
		qswOverlayPage->setCurrentWidget(qwOverlayConfig);
		qpbUninstall->setVisible(supportsInstallableOverlay());
	}

	// grab a desktop screenshot as background
	QRect dsg = QApplication::desktop()->screenGeometry();

#if QT_VERSION > 0x050000
	qpScreen = QGuiApplication::primaryScreen()->grabWindow(QApplication::desktop()->winId());
#else
	qpScreen = QPixmap::grabWindow(QApplication::desktop()->winId(), dsg.x(), dsg.y(), dsg.width(), dsg.height());
#endif
	if (qpScreen.size().isEmpty()) {
		qWarning() << __FUNCTION__ << "failed to grab desktop image, trying desktop widget...";

		qpScreen = QPixmap::grabWidget(QApplication::desktop(), dsg);

		if (qpScreen.size().isEmpty()) {
			qWarning() << __FUNCTION__ << "failed to grab desktop widget image, falling back.";

			QRect desktop_size = QApplication::desktop()->screenGeometry();
			qpScreen = QPixmap(desktop_size.width(), desktop_size.height());
			qpScreen.fill(Qt::darkGreen);
		}
	}

	initDisplay();

	// Attach the upgrade button to the install click handler. Currently, the
	// actions they perform are the same. The distinction is only there to inform
	// users as to what's actually going on.
	connect(qpbUpgrade, SIGNAL(clicked()), this, SLOT(on_qpbInstall_clicked()));
}

void OverlayConfig::updateOverlayExclusionModeState() {
	OverlaySettings::OverlayExclusionMode exclusionMode = static_cast<OverlaySettings::OverlayExclusionMode>(qcbOverlayExclusionMode->currentIndex());

	switch (exclusionMode) {
		case OverlaySettings::LauncherFilterExclusionMode:
			qwLaunchers->setHidden(false);
			qwWhitelist->setHidden(false);
			qwPaths->setHidden(false);
			qwBlacklist->setHidden(false);

			// Show all whitelist items (including built-in items)
			// in launcher filter mode.
			for (int i = 0; i < qlwWhitelist->count(); i++) {
				QListWidgetItem *item = qlwWhitelist->item(i);
				item->setHidden(false);
			}

			break;
		case OverlaySettings::WhitelistExclusionMode:
			qwLaunchers->setHidden(true);
			qwWhitelist->setHidden(false);
			qwPaths->setHidden(true);
			qwBlacklist->setHidden(true);

			// Hide the built-in items when in WhitelistExclusionMode.
			// They are only considered in launcher filter mode.
			for (int i = 0; i < qlwWhitelist->count(); i++) {
				QListWidgetItem *item = qlwWhitelist->item(i);
				bool isBuiltin = item->data(OVERLAYCONFIG_BUILTIN_ROLE).toBool();
				if (isBuiltin) {
					item->setHidden(true);
				} else {
					item->setHidden(false);
				}
			}

			break;
		case OverlaySettings::BlacklistExclusionMode:
			qwLaunchers->setHidden(true);
			qwWhitelist->setHidden(true);
			qwPaths->setHidden(true);
			qwBlacklist->setHidden(false);
			break;
	}
}

void OverlayConfig::load(const Settings &r) {
	s.os = r.os;

	loadCheckBox(qcbEnable, s.os.bEnable);
	qcbShowFps->setChecked(s.os.bFps);
	qcbShowTime->setChecked(s.os.bTime);
	qgpFps->setEnabled(s.os.bEnable);

	qcbOverlayExclusionMode->setCurrentIndex(static_cast<int>(s.os.oemOverlayExcludeMode));

	// Launchers
	{
		qlwLaunchers->clear();

		QStringList builtinLaunchers;
#ifdef Q_OS_WIN
		int i = 0;
		while (overlayLaunchers[i]) {
			QString str = QLatin1String(overlayLaunchers[i]);
			builtinLaunchers << str;
			++i;
		}
#endif
		foreach (QString str, builtinLaunchers) {
			OverlayAppInfo oai = OverlayAppInfo::applicationInfoForId(str);
			QListWidgetItem *qlwiApplication = new QListWidgetItem(oai.qiIcon, oai.qsDisplayName, qlwLaunchers);
			qlwiApplication->setData(OVERLAYCONFIG_PATH_ROLE, QVariant(str));
			qlwiApplication->setData(OVERLAYCONFIG_BUILTIN_ROLE, QVariant(true));

			// Use italic for 'default' items.
			QFont font = qlwiApplication->font();
			font.setItalic(true);
			qlwiApplication->setFont(font);

			qlwiApplication->setFlags(qlwiApplication->flags() | Qt::ItemIsUserCheckable);

			if (s.os.qslLaunchersExclude.contains(str)) {
				qlwiApplication->setCheckState(Qt::Unchecked);
			} else {
				qlwiApplication->setCheckState(Qt::Checked);
			}
		}

		foreach(QString str, s.os.qslLaunchers) {
			if (builtinLaunchers.contains(str)) {
				continue;
			}
			OverlayAppInfo oai = OverlayAppInfo::applicationInfoForId(str);
			QListWidgetItem *qlwiApplication = new QListWidgetItem(oai.qiIcon, oai.qsDisplayName, qlwLaunchers);
			qlwiApplication->setData(OVERLAYCONFIG_PATH_ROLE, QVariant(str));
			qlwiApplication->setData(OVERLAYCONFIG_BUILTIN_ROLE, QVariant(false));
		}
	}

	// Whitelist
	{
		qlwWhitelist->clear();

		QStringList builtinWhitelist;
#ifdef Q_OS_WIN
		int i = 0;
		while (overlayWhitelist[i]) {
			QString str = QLatin1String(overlayWhitelist[i]);
			builtinWhitelist << str;
			++i;
		}
#endif
		foreach (QString str, builtinWhitelist) {
			OverlayAppInfo oai = OverlayAppInfo::applicationInfoForId(str);
			QListWidgetItem *qlwiApplication = new QListWidgetItem(oai.qiIcon, oai.qsDisplayName, qlwWhitelist);
			qlwiApplication->setData(OVERLAYCONFIG_PATH_ROLE, QVariant(str));
			qlwiApplication->setData(OVERLAYCONFIG_BUILTIN_ROLE, QVariant(true));

			// Use italic for 'default' items.
			QFont font = qlwiApplication->font();
			font.setItalic(true);
			qlwiApplication->setFont(font);

			qlwiApplication->setFlags(qlwiApplication->flags() | Qt::ItemIsUserCheckable);

			if (s.os.qslWhitelistExclude.contains(str)) {
				qlwiApplication->setCheckState(Qt::Unchecked);
			} else {
				qlwiApplication->setCheckState(Qt::Checked);
			}
		}

		foreach(QString str, s.os.qslWhitelist) {
			if (builtinWhitelist.contains(str)) {
				continue;
			}
			OverlayAppInfo oai = OverlayAppInfo::applicationInfoForId(str);
			QListWidgetItem *qlwiApplication = new QListWidgetItem(oai.qiIcon, oai.qsDisplayName, qlwWhitelist);
			qlwiApplication->setData(OVERLAYCONFIG_PATH_ROLE, QVariant(str));
			qlwiApplication->setData(OVERLAYCONFIG_BUILTIN_ROLE, QVariant(false));
		}
	}

	// Paths
	{
		qlwPaths->clear();

		foreach(QString str, s.os.qslPaths) {
			QListWidgetItem *qlwiApplication = new QListWidgetItem(QIcon(), str, qlwPaths);
			qlwiApplication->setData(OVERLAYCONFIG_PATH_ROLE, QVariant(str));
			qlwiApplication->setData(OVERLAYCONFIG_BUILTIN_ROLE, QVariant(false));
		}
	}

	// Blacklist
	{
		qlwBlacklist->clear();

		QStringList builtinBlacklist;
#ifdef Q_OS_WIN
		int i = 0;
		while (overlayBlacklist[i]) {
			QString str = QLatin1String(overlayBlacklist[i]);
			builtinBlacklist << str;
			++i;
		}
#endif
		foreach (QString str, builtinBlacklist) {
			OverlayAppInfo oai = OverlayAppInfo::applicationInfoForId(str);
			QListWidgetItem *qlwiApplication = new QListWidgetItem(oai.qiIcon, oai.qsDisplayName, qlwBlacklist);
			qlwiApplication->setData(OVERLAYCONFIG_PATH_ROLE, QVariant(str));
			qlwiApplication->setData(OVERLAYCONFIG_BUILTIN_ROLE, QVariant(true));

			// Use italic for 'default' items.
			QFont font = qlwiApplication->font();
			font.setItalic(true);
			qlwiApplication->setFont(font);

			qlwiApplication->setFlags(qlwiApplication->flags() | Qt::ItemIsUserCheckable);

			if (s.os.qslBlacklistExclude.contains(str)) {
				qlwiApplication->setCheckState(Qt::Unchecked);
			} else {
				qlwiApplication->setCheckState(Qt::Checked);
			}
		}

		foreach(QString str, s.os.qslBlacklist) {
			if (builtinBlacklist.contains(str)) {
				continue;
			}
			OverlayAppInfo oai = OverlayAppInfo::applicationInfoForId(str);
			QListWidgetItem *qlwiApplication = new QListWidgetItem(oai.qiIcon, oai.qsDisplayName, qlwBlacklist);
			qlwiApplication->setData(OVERLAYCONFIG_PATH_ROLE, QVariant(str));
			qlwiApplication->setData(OVERLAYCONFIG_BUILTIN_ROLE, QVariant(false));
		}
	}

	initDisplay();
	resizeScene(true);
	update();
}

QString OverlayConfig::title() const {
	return tr("Overlay");
}

QIcon OverlayConfig::icon() const {
	return QIcon(QLatin1String("skin:config_osd.png"));
}

void OverlayConfig::save() const {
	s.os.bEnable = qcbEnable->isChecked();
	s.os.bFps = qcbShowFps->isChecked();
	s.os.bTime = qcbShowTime->isChecked();

	// Directly save overlay config

	s.os.oemOverlayExcludeMode = static_cast<OverlaySettings::OverlayExclusionMode>(qcbOverlayExclusionMode->currentIndex());

	// Launchers
	{
		s.os.qslLaunchers.clear();
		s.os.qslLaunchersExclude.clear();

		for (int i = 0; i < qlwLaunchers->count(); i++) {
			bool isBuiltin = (qlwLaunchers->item(i)->data(OVERLAYCONFIG_BUILTIN_ROLE).toBool());

			bool isChecked = false;
			if (isBuiltin) {
				isChecked = qlwLaunchers->item(i)->checkState() == Qt::Checked;
			}

			QString str = qlwLaunchers->item(i)->data(OVERLAYCONFIG_PATH_ROLE).toString();

			if (isBuiltin) {
				if (!isChecked) {
					s.os.qslLaunchersExclude << str;
				}
			} else {
				s.os.qslLaunchers << str;
			}
		}
	}

	// Whitelist
	{
		s.os.qslWhitelist.clear();
		s.os.qslWhitelistExclude.clear();

		for (int i = 0; i < qlwWhitelist->count(); i++) {
			bool isBuiltin = (qlwWhitelist->item(i)->data(OVERLAYCONFIG_BUILTIN_ROLE).toBool());

			bool isChecked = false;
			if (isBuiltin) {
				isChecked = qlwWhitelist->item(i)->checkState() == Qt::Checked;
			}

			QString str = qlwWhitelist->item(i)->data(OVERLAYCONFIG_PATH_ROLE).toString();

			if (isBuiltin) {
				if (!isChecked) {
					s.os.qslWhitelistExclude << str;
				}
			} else {
				s.os.qslWhitelist << str;
			}
		}
	}

	// Paths
	{
		s.os.qslPaths.clear();
		s.os.qslPathsExclude.clear();

		for (int i = 0; i < qlwPaths->count(); i++) {
			bool isBuiltin = (qlwPaths->item(i)->data(OVERLAYCONFIG_BUILTIN_ROLE).toBool());

			bool isChecked = false;
			if (isBuiltin) {
				isChecked = qlwPaths->item(i)->checkState() == Qt::Checked;
			}

			QString str = qlwPaths->item(i)->data(OVERLAYCONFIG_PATH_ROLE).toString();

			if (isBuiltin) {
				if (!isChecked) {
					s.os.qslPathsExclude << str;
				}
			} else {
				s.os.qslPaths << str;
			}
		}
	}

	// Blacklist
	{
		s.os.qslBlacklist.clear();
		s.os.qslBlacklistExclude.clear();

		for (int i = 0; i < qlwBlacklist->count(); i++) {
			bool isBuiltin = (qlwBlacklist->item(i)->data(OVERLAYCONFIG_BUILTIN_ROLE).toBool());

			bool isChecked = false;
			if (isBuiltin) {
				isChecked = qlwBlacklist->item(i)->checkState() == Qt::Checked;
			}

			QString str = qlwBlacklist->item(i)->data(OVERLAYCONFIG_PATH_ROLE).toString();

			if (isBuiltin) {
				if (!isChecked) {
					s.os.qslBlacklistExclude << str;
				}
			} else {
				s.os.qslBlacklist << str;
			}
		}
	}

	g.qs->beginGroup(QLatin1String("overlay"));
	s.os.save();
	g.qs->endGroup();
#ifdef Q_OS_WIN
	// On MS windows force sync so the registry is updated.
	g.qs->sync();
#endif
}

void OverlayConfig::accept() const {
	g.o->forceSettings();
	g.o->setActive(s.os.bEnable);
}

bool OverlayConfig::eventFilter(QObject *obj, QEvent *evt) {
	if (evt->type() == QEvent::Resize)
		QMetaObject::invokeMethod(this, "resizeScene", Qt::QueuedConnection);
	return ConfigWidget::eventFilter(obj, evt);
}

void OverlayConfig::resizeScene(bool force) {
	QSize sz = qgvView->viewport()->size();

	int ph = qgpiScreen->pixmap().height();
	int pw = qgpiScreen->pixmap().width();
	if (!force && ((ph == sz.height() && pw <= sz.width()) || (ph <= sz.height() && pw == sz.width()))) {
		return;
	}

	qgpiScreen->setPixmap(qpScreen.scaled(sz, Qt::KeepAspectRatio, Qt::SmoothTransformation));
	qgs.setSceneRect(QRectF(0, 0, qgpiScreen->pixmap().width(), qgpiScreen->pixmap().height()));

	fViewScale = static_cast<float>(qgpiScreen->pixmap().height()) / static_cast<float>(qpScreen.height());
	refreshFpsLive();
	refreshTimeLive();

	QFont f = qgtiInstructions->font();
	f.setPointSizeF(qgs.sceneRect().height() / 20.0f);
	qgtiInstructions->setFont(f);

	qgtiInstructions->setPos(qgs.sceneRect().width() / 20.0f, qgs.sceneRect().height() / 10.0f);
	qgtiInstructions->setTextWidth(qgs.sceneRect().width() * 18.0f / 20.0f);

	qgvView->fitInView(qgs.sceneRect(), Qt::KeepAspectRatio);
	oug->updateLayout();
	oug->updateUsers();

	qgpiFpsLive->updateRender();
	qgpiTimeLive->updateRender();
}

void OverlayConfig::on_qlwLaunchers_itemSelectionChanged() {
	QList<QListWidgetItem *> items = qlwLaunchers->selectedItems();
	if (items.isEmpty()) {
		qpbLaunchersRemove->setEnabled(false);
		return;
	}

	QListWidgetItem *item = items.at(0);
	bool isBuiltin = item->data(OVERLAYCONFIG_BUILTIN_ROLE).toBool();

	if (isBuiltin) {
		qpbLaunchersRemove->setEnabled(false);
	} else {
		qpbLaunchersRemove->setEnabled(true);
	}
}

void OverlayConfig::on_qcbOverlayExclusionMode_currentIndexChanged(int) {
	updateOverlayExclusionModeState();
}

void OverlayConfig::on_qpbLaunchersAdd_clicked() {
#if defined(Q_OS_WIN)
	QString file = QFileDialog::getOpenFileName(this, tr("Choose executable"), QString(), QLatin1String("*.exe"));
#elif defined(Q_OS_MAC)
	QString file = QFileDialog::getOpenFileName(this, tr("Choose application"), QString(), QLatin1String("*.app"));
#else
	QString file = QString();
#endif

	if (! file.isEmpty()) {
		QString qsAppIdentifier = OverlayAppInfo::applicationIdentifierForPath(file);
		QStringList qslIdentifiers;
		for (int i = 0; i < qlwLaunchers->count(); i++)
			qslIdentifiers << qlwLaunchers->item(i)->data(Qt::UserRole).toString();
		if (! qslIdentifiers.contains(qsAppIdentifier)) {
			OverlayAppInfo oai = OverlayAppInfo::applicationInfoForId(qsAppIdentifier);
			QListWidgetItem *qlwiApplication = new QListWidgetItem(oai.qiIcon, oai.qsDisplayName, qlwLaunchers);
			qlwiApplication->setData(Qt::UserRole, QVariant(qsAppIdentifier));
			qlwLaunchers->setCurrentItem(qlwiApplication);
		}
	}
}

void OverlayConfig::on_qpbLaunchersRemove_clicked() {
	int row = qlwLaunchers->currentRow();
	if (row != -1)
		delete qlwLaunchers->takeItem(row);
}

void OverlayConfig::on_qlwWhitelist_itemSelectionChanged() {
	QList<QListWidgetItem *> items = qlwWhitelist->selectedItems();
	if (items.isEmpty()) {
		qpbWhitelistRemove->setEnabled(false);
		return;
	}

	QListWidgetItem *item = items.at(0);
	bool isBuiltin = item->data(OVERLAYCONFIG_BUILTIN_ROLE).toBool();

	if (isBuiltin) {
		qpbWhitelistRemove->setEnabled(false);
	} else {
		qpbWhitelistRemove->setEnabled(true);
	}
}

void OverlayConfig::addWhitelistPath(const QString &path) {
	QString qsAppIdentifier = OverlayAppInfo::applicationIdentifierForPath(path);
	QListWidget *sel = qlwWhitelist;
	QStringList qslIdentifiers;
	for (int i = 0; i < sel->count(); i++)
		qslIdentifiers << sel->item(i)->data(Qt::UserRole).toString();
	if (! qslIdentifiers.contains(qsAppIdentifier)) {
		OverlayAppInfo oai = OverlayAppInfo::applicationInfoForId(qsAppIdentifier);
		QListWidgetItem *qlwiApplication = new QListWidgetItem(oai.qiIcon, oai.qsDisplayName, sel);
		qlwiApplication->setData(Qt::UserRole, QVariant(qsAppIdentifier));
		sel->setCurrentItem(qlwiApplication);
	}
}

void OverlayConfig::on_qpbWhitelistAdd_clicked() {
#if defined(Q_OS_WIN)
	QString file = QFileDialog::getOpenFileName(this, tr("Choose executable"), QString(), QLatin1String("*.exe"));
#elif defined(Q_OS_MAC)
	QString file = QFileDialog::getOpenFileName(this, tr("Choose application"), QString(), QLatin1String("*.app"));
#else
	QString file = QString();
#endif

	if (! file.isEmpty()) {
		addWhitelistPath(file);
	}
}

void OverlayConfig::on_qpbWhitelistRemove_clicked() {
	QListWidget *sel = qlwWhitelist;
	int row = sel->currentRow();
	if (row != -1)
		delete sel->takeItem(row);
}

void OverlayConfig::on_qlwPaths_itemSelectionChanged() {
	QList<QListWidgetItem *> items = qlwPaths->selectedItems();
	if (items.isEmpty()) {
		qpbPathsRemove->setEnabled(false);
		return;
	}

	QListWidgetItem *item = items.at(0);
	bool isBuiltin = item->data(OVERLAYCONFIG_BUILTIN_ROLE).toBool();

	if (isBuiltin) {
		qpbPathsRemove->setEnabled(false);
	} else {
		qpbPathsRemove->setEnabled(true);
	}
}

void OverlayConfig::on_qpbPathsAdd_clicked() {
	QString existingPath = QFileDialog::getExistingDirectory(this, tr("Choose path"));
	QString dir = QDir::toNativeSeparators(existingPath);

	QListWidget *sel = qlwPaths;
	QStringList qslIdentifiers;
	for (int i = 0; i < sel->count(); i++)
		qslIdentifiers << sel->item(i)->data(Qt::UserRole).toString();
	if (! dir.isEmpty() && ! qslIdentifiers.contains(dir)) {
		QListWidgetItem *qlwiPath = new QListWidgetItem(QIcon(), dir, sel);
		qlwiPath->setData(Qt::UserRole, QVariant(dir));
		sel->setCurrentItem(qlwiPath);
	}
}

void OverlayConfig::on_qpbPathsRemove_clicked() {
	int row = qlwPaths->currentRow();
	if (row != -1)
		delete qlwPaths->takeItem(row);
}

void OverlayConfig::on_qlwBlacklist_itemSelectionChanged() {
	QList<QListWidgetItem *> items = qlwBlacklist->selectedItems();
	if (items.isEmpty()) {
		qpbBlacklistRemove->setEnabled(false);
		return;
	}

	QListWidgetItem *item = items.at(0);
	bool isBuiltin = item->data(OVERLAYCONFIG_BUILTIN_ROLE).toBool();

	if (isBuiltin) {
		qpbBlacklistRemove->setEnabled(false);
	} else {
		qpbBlacklistRemove->setEnabled(true);
	}
}

void OverlayConfig::on_qpbBlacklistAdd_clicked() {
#if defined(Q_OS_WIN)
	QString file = QFileDialog::getOpenFileName(this, tr("Choose executable"), QString(), QLatin1String("*.exe"));
#elif defined(Q_OS_MAC)
	QString file = QFileDialog::getOpenFileName(this, tr("Choose application"), QString(), QLatin1String("*.app"));
#else
	QString file = QString();
#endif

	if (! file.isEmpty()) {
		QString qsAppIdentifier = OverlayAppInfo::applicationIdentifierForPath(file);
		QListWidget *sel = qlwBlacklist;
		QStringList qslIdentifiers;
		for (int i = 0; i < sel->count(); i++)
			qslIdentifiers << sel->item(i)->data(Qt::UserRole).toString();
		if (! qslIdentifiers.contains(qsAppIdentifier)) {
			OverlayAppInfo oai = OverlayAppInfo::applicationInfoForId(qsAppIdentifier);
			QListWidgetItem *qlwiApplication = new QListWidgetItem(oai.qiIcon, oai.qsDisplayName, sel);
			qlwiApplication->setData(Qt::UserRole, QVariant(qsAppIdentifier));
			sel->setCurrentItem(qlwiApplication);
		}
	}
}

void OverlayConfig::on_qpbBlacklistRemove_clicked() {
	QListWidget *sel = qlwBlacklist;
	int row = sel->currentRow();
	if (row != -1)
		delete sel->takeItem(row);
}

void OverlayConfig::on_qcbEnable_stateChanged(int state) {
	qgpFps->setEnabled(state == Qt::Checked);
}

void OverlayConfig::on_qpbInstall_clicked() {
	qpbInstall->setEnabled(false);

	if (installFiles()) {
		qswOverlayPage->setCurrentWidget(qwOverlayConfig);
	}

	qpbInstall->setEnabled(true);
}

void OverlayConfig::on_qpbUninstall_clicked() {

	qpbUninstall->setEnabled(false);

	if (uninstallFiles()) {
		qswOverlayPage->setCurrentWidget(qwOverlayInstall);
	}

	qpbUninstall->setEnabled(true);
}

void OverlayConfig::on_qcbShowFps_stateChanged(int state) {
	Q_UNUSED(state);
	s.os.bFps = qcbShowFps->isChecked();
	refreshFpsLive();
}

void OverlayConfig::on_qcbShowTime_stateChanged(int state) {
	Q_UNUSED(state);
	s.os.bTime = qcbShowTime->isChecked();
	refreshTimeLive();
}

void OverlayConfig::on_qpbFpsFont_clicked() {
	bool ok;
	QFont new_font = QFontDialog::getFont(&ok, s.os.qfFps);

	if (ok) {
		s.os.qfFps = new_font;

		refreshFpsDemo();
		refreshFpsLive();
		refreshTimeLive();
	}
}

void OverlayConfig::on_qpbFpsColor_clicked() {
	QColor color = QColorDialog::getColor(s.os.qcFps);

	if (color.isValid()) {
		s.os.qcFps = color;

		refreshFpsDemo();
		refreshFpsLive();
		refreshTimeLive();
	}
}

void OverlayConfig::on_qpbLoadPreset_clicked() {
	QString fn = QFileDialog::getOpenFileName(this,
	             tr("Load Overlay Presets"),
	             QDir::homePath(),
	             tr("Mumble overlay presets (*.mumblelay)"));

	if (fn.isEmpty()) {
		return;
	}

	QSettings qs(fn, QSettings::IniFormat);
	OverlaySettings load_preset;

	qs.beginGroup(QLatin1String("overlay"));
	load_preset.load(&qs);
	qs.endGroup();

#ifdef Q_OS_WIN
	load_preset.qslLaunchers = s.os.qslLaunchers;
	load_preset.qslLaunchersExclude = s.os.qslLaunchersExclude;

	load_preset.qslWhitelist = s.os.qslWhitelist;
	load_preset.qslWhitelistExclude = s.os.qslWhitelistExclude;

	load_preset.qslPaths = s.os.qslPaths;
	load_preset.qslPathsExclude = s.os.qslPathsExclude;

	load_preset.qslBlacklist = s.os.qslBlacklist;
	load_preset.qslBlacklistExclude = s.os.qslBlacklistExclude;
#endif
	load_preset.bEnable = s.os.bEnable;
	s.os = load_preset;

	load(s);
}

void OverlayConfig::on_qpbSavePreset_clicked() {
	QString fn = QFileDialog::getSaveFileName(this,
	             tr("Save Overlay Presets"),
	             QDir::homePath(),
	             tr("Mumble overlay presets (*.mumblelay)"));

	if (fn.isEmpty()) {
		return;
	}

	QSettings qs(fn, QSettings::IniFormat);

	if (!qs.isWritable()) {
		qWarning() << __FUNCTION__ << "preset save file" << fn << "is not writable!";
		return;
	}

	qs.beginGroup(QLatin1String("overlay"));
	s.os.save(&qs);
	qs.remove(QLatin1String("enable"));
	qs.remove(QLatin1String("usewhitelist"));
	qs.remove(QLatin1String("blacklist"));
	qs.remove(QLatin1String("whitelist"));
	qs.remove(QLatin1String("enablelauncherfilter"));
	qs.remove(QLatin1String("launchers"));
	qs.endGroup();
}
