#include "FractoriumPch.h"
#include "QssDialog.h"
#include "ui_QssDialog.h"
#include "qcssscanner.h"

/// <summary>
/// The code in this file did not originate in Fractorium.
/// It was taken either in whole or in part from the source code
/// of Qt Creator. Their license applies.
/// </summary>

/// <summary>
/// Comparison for sorting object names.
/// Strings not starting with the letter 'Q' take precedence.
/// This has the effect of putting custom derived classes first before
/// all Q* classes.
/// </summary>
/// <param name="s1">The first string to compare</param>
/// <param name="s2">The second string to compare</param>
/// <returns>True if s1 < s2 with special rules for 'Q' taken into account.</returns>
bool CaseInsensitiveLessThanQ(const QString& s1, const QString& s2)
{
	if (s1.length() && s2.length())
	{
		if (s1[0] == 'Q' && s2[0] == 'Q')
			return s1.toLower() < s2.toLower();
		else if (s1[0] == 'Q')
			return false;
		else if (s2[0] == 'Q')
			return true;
		else
			return s1.toLower() < s2.toLower();
	}

	return false;
}

/// <summary>
/// Construct a QssDialog.
/// This manually constructs much of the menu GUI via code rather
/// than in the designer.
/// </summary>
/// <param name="parent">The main Fractorium window.</param>
QssDialog::QssDialog(Fractorium* parent) :
	QDialog(parent),
	ui(new Ui::QssDialog),
	m_Parent(parent),
	m_AddColorAction(new QAction(tr("Add Color"), this)),
	m_AddGeomAction(new QAction(tr("Add Geometry"), this)),
	m_AddBorderAction(new QAction(tr("Add Border"), this)),
	m_AddFontAction(new QAction(tr("Add Font..."), this)),
	m_AddStyleAction(new QAction(tr("Set Theme"), this))
{
	ui->setupUi(this);
	m_LastStyle = m_Parent->styleSheet();
	setWindowTitle("QSS Editor - default.qss");
	connect(ui->QssEdit, SIGNAL(textChanged()), this, SLOT(SlotTextChanged()));
	QToolBar* toolBar = new QToolBar(this);
	QMenu* colorActionMenu = new QMenu(this);
	QMenu* geomActionMenu = new QMenu(this);
	QMenu* borderActionMenu = new QMenu(this);
	QMenu* styleActionMenu = new QMenu(this);
	(m_ColorActionMapper = new QSignalMapper(this))->setMapping(m_AddColorAction, QString());
	(m_GeomActionMapper = new QSignalMapper(this))->setMapping(m_AddGeomAction, QString());
	(m_BorderActionMapper = new QSignalMapper(this))->setMapping(m_AddBorderAction, QString());
	(m_StyleActionMapper = new QSignalMapper(this))->setMapping(m_AddStyleAction, QString());
	connect(ui->QssLoadButton, SIGNAL(clicked()), this, SLOT(LoadButton_clicked()), Qt::QueuedConnection);
	connect(ui->QssSaveButton, SIGNAL(clicked()), this, SLOT(SaveButton_clicked()), Qt::QueuedConnection);
	connect(ui->QssBasicButton, SIGNAL(clicked()), this, SLOT(BasicButton_clicked()), Qt::QueuedConnection);
	connect(ui->QssMediumButton, SIGNAL(clicked()), this, SLOT(MediumButton_clicked()), Qt::QueuedConnection);
	connect(ui->QssAdvancedButton, SIGNAL(clicked()), this, SLOT(AdvancedButton_clicked()), Qt::QueuedConnection);
	connect(m_AddFontAction, SIGNAL(triggered()), this, SLOT(SlotAddFont()));
	QVector<QPair<QString, QString>> colorVec;
	colorVec.reserve(12);
	colorVec.push_back(QPair<QString, QString>("color", ""));
	colorVec.push_back(QPair<QString, QString>("background-color", ""));
	colorVec.push_back(QPair<QString, QString>("alternate-background-color", ""));
	colorVec.push_back(QPair<QString, QString>("border-color", ""));
	colorVec.push_back(QPair<QString, QString>("border-top-color", ""));
	colorVec.push_back(QPair<QString, QString>("border-right-color", ""));
	colorVec.push_back(QPair<QString, QString>("border-bottom-color", ""));
	colorVec.push_back(QPair<QString, QString>("border-left-color", ""));
	colorVec.push_back(QPair<QString, QString>("gridline-color", ""));
	colorVec.push_back(QPair<QString, QString>("selection-color", ""));
	colorVec.push_back(QPair<QString, QString>("selection-background-color", ""));

	for (auto& c : colorVec)
	{
		auto colorAction = colorActionMenu->addAction(c.first);
		m_ColorMap[c.first] = c.second;
		connect(colorAction, SIGNAL(triggered()), m_ColorActionMapper, SLOT(map()));
		m_ColorActionMapper->setMapping(colorAction, c.first);
	}

	QVector<QPair<QString, QString>> geomVec;
	geomVec.reserve(12);
	geomVec.push_back(QPair<QString, QString>("width", "100px"));
	geomVec.push_back(QPair<QString, QString>("height", "50px"));
	geomVec.push_back(QPair<QString, QString>("spacing", "10"));
	geomVec.push_back(QPair<QString, QString>("padding", "3px"));
	geomVec.push_back(QPair<QString, QString>("padding-top", "3px"));
	geomVec.push_back(QPair<QString, QString>("padding-right", "3px"));
	geomVec.push_back(QPair<QString, QString>("padding-bottom", "3px"));
	geomVec.push_back(QPair<QString, QString>("padding-left", "3px"));
	geomVec.push_back(QPair<QString, QString>("margin", "3px"));
	geomVec.push_back(QPair<QString, QString>("margin-top", "3px"));
	geomVec.push_back(QPair<QString, QString>("margin-right", "3px"));
	geomVec.push_back(QPair<QString, QString>("margin-bottom", "3px"));
	geomVec.push_back(QPair<QString, QString>("margin-left", "3px"));

	for (auto& g : geomVec)
	{
		auto geomAction = geomActionMenu->addAction(g.first);
		m_GeomMap[g.first] = g.second;
		connect(geomAction, SIGNAL(triggered()), m_GeomActionMapper, SLOT(map()));
		m_GeomActionMapper->setMapping(geomAction, g.first);
	}

	QVector<QPair<QString, QString>> borderVec;
	borderVec.reserve(8);
	borderVec.push_back(QPair<QString, QString>("border", "1px solid black"));
	borderVec.push_back(QPair<QString, QString>("border-top", "1px inset black"));
	borderVec.push_back(QPair<QString, QString>("border-right", "1px outset black"));
	borderVec.push_back(QPair<QString, QString>("border-bottom", "1px ridge black"));
	borderVec.push_back(QPair<QString, QString>("border-left", "1px groove black"));
	borderVec.push_back(QPair<QString, QString>("border-style", "double"));
	borderVec.push_back(QPair<QString, QString>("border-width", "1px"));
	borderVec.push_back(QPair<QString, QString>("border-radius", "10px"));

	for (auto& b : borderVec)
	{
		auto borderAction = borderActionMenu->addAction(b.first);
		m_BorderMap[b.first] = b.second;
		connect(borderAction, SIGNAL(triggered()), m_BorderActionMapper, SLOT(map()));
		m_BorderActionMapper->setMapping(borderAction, b.first);
	}

	auto styles = QStyleFactory::keys();

	for (auto& s : styles)
	{
		auto styleAction = styleActionMenu->addAction(s);
		m_StyleMap[s] = s;
		connect(styleAction, SIGNAL(triggered()), m_StyleActionMapper, SLOT(map()));
		m_StyleActionMapper->setMapping(styleAction, s);
	}

	connect(m_ColorActionMapper, SIGNAL(mapped(QString)), this, SLOT(SlotAddColor(QString)));
	connect(m_GeomActionMapper, SIGNAL(mapped(QString)), this, SLOT(SlotAddGeom(QString)));
	connect(m_BorderActionMapper, SIGNAL(mapped(QString)), this, SLOT(SlotAddBorder(QString)));
	connect(m_StyleActionMapper, SIGNAL(mapped(QString)), this, SLOT(SlotSetTheme(QString)));
	m_AddColorAction->setMenu(colorActionMenu);
	m_AddGeomAction->setMenu(geomActionMenu);
	m_AddBorderAction->setMenu(borderActionMenu);
	m_AddStyleAction->setMenu(styleActionMenu);
	toolBar->addAction(m_AddColorAction);
	toolBar->addAction(m_AddGeomAction);
	toolBar->addAction(m_AddBorderAction);
	toolBar->addAction(m_AddFontAction);
	toolBar->addAction(m_AddStyleAction);
	ui->verticalLayout->insertWidget(0, toolBar);
	ui->QssEdit->setFocus();
	m_ApplyTimer = new QTimer(this);
	m_ApplyTimer->setSingleShot(true);
	m_ApplyTimer->setInterval(1000);
	connect(m_ApplyTimer, SIGNAL(timeout()), this, SLOT(SlotApplyCss()));
}

/// <summary>
/// Destructor that stops the apply timer and deletes the ui.
/// </summary>
QssDialog::~QssDialog()
{
	m_ApplyTimer->stop();
	delete ui;
}

/// <summary>
/// Thin wrapper around getting the text from the main text box as plain text.
/// </summary>
/// <returns>The plain text of the main text box</returns>
QString QssDialog::Text() const
{
	return ui->QssEdit->toPlainText();
}

/// <summary>
/// Thin wrapper around setting the text of the main text box.
/// </summary>
/// <param name="t">The text to set</param>
void QssDialog::SetText(const QString& t)
{
	ui->QssEdit->setText(t);
}

/// <summary>
/// Get the class names of all objects in the application.
/// This only makes one entry for each class type.
/// It will also optionally return the object names as well for advanced QSS editing.
/// </summary>
/// <param name="includeObjectNames">Whether to get the individual object names as well</param>
/// <returns>A list of all class names with optional entries for each individual object</returns>
QList<QString> QssDialog::GetClassNames(bool includeObjectNames)
{
	QSet<QString> classNames;
	QList<QList<QString>> dialogClassNames;
	auto widgetList = m_Parent->findChildren<QWidget*>();

	for (int i = 0; i < widgetList.size(); i++)
	{
		auto classAndName = QString(widgetList[i]->metaObject()->className());

		if (!includeObjectNames)
		{
			classNames.insert(classAndName);
		}
		else
		{
			auto dlg = qobject_cast<QDialog*>(widgetList[i]);

			if (dlg)//Dialogs only nest one level deep, so no need for generalized recursion.
			{
				QSet<QString> dlgSet;
				auto dlgWidgetList = dlg->findChildren<QWidget*>();//Find all children of the dialog.
				dlgSet.insert(classAndName);//Add the basic dialog class name, opening curly brace will be added later.
				classAndName += " ";

				for (int i = 0; i < dlgWidgetList.size(); i++)
				{
					auto dlgClassAndName = classAndName + QString(dlgWidgetList[i]->metaObject()->className());
					dlgSet.insert(dlgClassAndName);

					if (!dlgWidgetList[i]->objectName().isEmpty())//Add the class with object name for individual control customization.
					{
						dlgClassAndName += "#" + dlgWidgetList[i]->objectName();
						dlgSet.insert(dlgClassAndName);
					}
				}

				auto dlgList = dlgSet.toList();//Convert set to list and sort.
				qSort(dlgList.begin(), dlgList.end(), CaseInsensitiveLessThanQ);
				dialogClassNames.push_back(dlgList);//Add this to the full list after sorting at the end.
			}
			else if (GetAllParents<QDialog*>(widgetList[i]).empty())//Skip widgets on dialogs, they are added above.
			{
				classNames.insert(classAndName);//Add the basic class name.

				if (!widgetList[i]->objectName().isEmpty())//Add the class with object name for individual control customization.
				{
					classAndName += "#" + widgetList[i]->objectName();
					classNames.insert(classAndName);
				}
			}
		}
	}

	auto l = classNames.toList();
	qSort(l.begin(), l.end(), CaseInsensitiveLessThanQ);

	for (auto& d : dialogClassNames)
		l.append(d);

	return l;
}

/// <summary>
/// Determines whether the passed in stylesheet text is valid.
/// If the initial parse fails, a second attempt is made by wrapping the entire
/// text in curly braces.
/// </summary>
/// <param name="styleSheet">The stylesheet text to analyze.</param>
/// <returns>True if valid, else false.</returns>
bool QssDialog::IsStyleSheetValid(const QString& styleSheet)
{
	QCss::Parser parser(styleSheet);
	QCss::StyleSheet sheet;

	if (parser.parse(&sheet))
		return true;

	QString fullSheet = QStringLiteral("* { ");
	fullSheet += styleSheet;
	fullSheet += QLatin1Char('}');
	QCss::Parser parser2(fullSheet);
	return parser2.parse(&sheet);
}

/// <summary>
/// Save the current stylesheet text to default.qss.
/// Also save the selected theme to the settings.
/// Called when the user clicks ok.
/// Not called if cancelled or closed with the X.
/// </summary>
void QssDialog::accept()
{
	if (m_Theme)
		m_Parent->m_Settings->Theme(m_Theme->objectName());

	SaveAsDefault();
	QDialog::accept();
}

/// <summary>
/// Restore the stylesheet and theme to what it was when the dialog was opened.
/// Called when the user clicks cancel or closes with the X.
/// </summary>
void QssDialog::reject()
{
	if (!m_LastStyle.isEmpty())
		m_Parent->setStyleSheet(m_LastStyle);

	if (m_LastTheme)
	{
		m_Parent->setStyle(m_LastTheme);
		m_Parent->m_Settings->Theme(m_LastTheme->objectName());
	}

	m_Parent->m_Controller->FillVariationTreeWithCurrentXform();
	QDialog::reject();
}

/// <summary>
/// Shows the event.
/// </summary>
/// <param name="e">The e.</param>
void QssDialog::showEvent(QShowEvent* e)
{
	if (m_Parent)
	{
		m_LastStyle = m_Parent->styleSheet();
		m_LastTheme = m_Parent->m_Theme;//The style() member cannot be relied upon, it is *not* the same object passed to setStyle();
		SetText(m_LastStyle);
	}

	QDialog::showEvent(e);
}

/// <summary>
/// Start the timer which will analyze and apply the current stylesheet text.
/// Each successive keystroke will reset the timer if it has not timed out yet.
/// This is only called when the dialog is visible because it seems to be spurriously
/// called on startup.
/// Called when the user changes the text in main text box.
/// </summary>
void QssDialog::SlotTextChanged()
{
	if (isVisible())//Sometimes this fires even though the window is not shown yet.
		m_ApplyTimer->start();
}

/// <summary>
///	Add a color string to the stylesheet text.
/// Called when the user clicks the add color menu.
/// </summary>
/// <param name="s">The color string selector to add</param>
void QssDialog::SlotAddColor(const QString& s)
{
	const QColor color = QColorDialog::getColor(0xffffffff, this, QString(), QColorDialog::ShowAlphaChannel);

	if (!color.isValid())
		return;

	QString colorStr;

	if (color.alpha() == 255)
	{
		colorStr = QString(QStringLiteral("rgb(%1, %2, %3)")).arg(
					   color.red()).arg(color.green()).arg(color.blue());
	}
	else
	{
		colorStr = QString(QStringLiteral("rgba(%1, %2, %3, %4)")).arg(
					   color.red()).arg(color.green()).arg(color.blue()).arg(color.alpha());
	}

	InsertCssProperty(s, colorStr);
}

/// <summary>
/// Adds a geometry string to the stylesheet text.
/// </summary>
/// <param name="s">The geometry string to add</param>
void QssDialog::SlotAddGeom(const QString& s)
{
	auto val = m_GeomMap[s];
	InsertCssProperty(s, val);
}

/// <summary>
/// Adds a border string to the stylesheet text.
/// </summary>
/// <param name="s">The border string to add</param>
void QssDialog::SlotAddBorder(const QString& s)
{
	auto val = m_BorderMap[s];
	InsertCssProperty(s, val);
}

/// <summary>
/// Set the theme to the user selection.
/// Called when the user selects an item on the theme combo box.
/// </summary>
/// <param name="s">The s.</param>
void QssDialog::SlotSetTheme(const QString& s)
{
	if (auto theme = QStyleFactory::create(s))
	{
		m_Theme = theme;
		m_Parent->setStyle(m_Theme);
	}
}

/// <summary>
/// Add a font string.
/// Called when the user clicks the add font menu button.
/// </summary>
void QssDialog::SlotAddFont()
{
	bool ok;
	auto font = QFontDialog::getFont(&ok, this);

	if (ok)
	{
		QString fontStr;

		if (font.weight() != QFont::Normal)
		{
			fontStr += QString::number(font.weight());
			fontStr += QLatin1Char(' ');
		}

		switch (font.style())
		{
			case QFont::StyleItalic:
				fontStr += QStringLiteral("italic ");
				break;

			case QFont::StyleOblique:
				fontStr += QStringLiteral("oblique ");
				break;

			default:
				break;
		}

		fontStr += QString::number(font.pointSize());
		fontStr += QStringLiteral("pt \"");
		fontStr += font.family();
		fontStr += QLatin1Char('"');
		InsertCssProperty(QStringLiteral("font"), fontStr);
	}
}

/// <summary>
/// Check if the current stylesheet is valid and apply it if so.
/// Also indicate via label whether it was valid.
/// </summary>
void QssDialog::SlotApplyCss()
{
	auto label = ui->QssValidityLabel;
	auto style = Text();
	const bool valid = IsStyleSheetValid(style);
	ui->QssButtonBox->button(QDialogButtonBox::Ok)->setEnabled(valid);

	if (valid)
	{
		label->setText(tr("Valid Style Sheet"));
		label->setStyleSheet(QStringLiteral("color: green"));
		m_Parent->setStyleSheet(style);
		m_Parent->m_Controller->FillVariationTreeWithCurrentXform();
	}
	else
	{
		label->setText(tr("Invalid Style Sheet"));
		label->setStyleSheet(QStringLiteral("color: red"));
	}
}

/// <summary>
/// Load a stylesheet from disk.
/// Called when the user clicks the load button.
/// </summary>
void QssDialog::LoadButton_clicked()
{
	string s;
	auto f = OpenFile();

	if (!f.isEmpty() && ReadFile(f.toStdString().c_str(), s) && !s.empty())
		SetText(QString::fromStdString(s));

	setWindowTitle("QSS Editor - " + f);
}

/// <summary>
/// Save the stylesheet to disk.
/// Called when the user clicks the save button.
/// The user cannot save to default.qss, as it's a special placeholder.
/// When they exit the dialog by clicking OK, the currently displayed stylesheet
/// will be saved to default.qss.
/// </summary>
void QssDialog::SaveButton_clicked()
{
	auto path = SaveFile();

	if (path.toLower().endsWith("default.qss"))
	{
		QMessageBox::critical(this, "File save error", "Stylesheet cannot be saved to default.qss. Save it to a different file name, then exit the dialog by clicking OK which will set it as the default.");
		return;
	}

	if (!path.isEmpty())
	{
		ofstream of(path.toStdString());
		string s = Text().toStdString();

		if (of.is_open())
			of << s;
		else
			QMessageBox::critical(this, "File save error", "Failed to save " + path + ", style will not be set as default");
	}
}

/// <summary>
/// Save the stylesheet to the default.qss on disk.
/// This will be loaded the next time Fractorium runs.
/// Called when the user clicks ok.
/// </summary>
void QssDialog::SaveAsDefault()
{
	auto path = m_Parent->m_SettingsPath + "/default.qss";
	ofstream of(path.toStdString());
	auto s = Text().toStdString();

	if (of.is_open())
		of << s;
	else
		QMessageBox::critical(this, "File save error", "Failed to save " + path + ", style will not be set as default");
}

/// <summary>
/// Fill the main text box with the most basic style.
/// Called when the Basic button is clicked.
/// </summary>
void QssDialog::BasicButton_clicked()
{
	SetText(BaseStyle());
	setWindowTitle("QSS Editor");
}

/// <summary>
/// Fill the main text box with a medium specificity style.
/// This will expose all control types in the application.
/// Called when the Medium button is clicked.
/// </summary>
void QssDialog::MediumButton_clicked()
{
	QString str = BaseStyle();
	auto names = GetClassNames(false);

	for (auto& it : names)
		str += it + QString("\n{\n\t\n}\n\n");

	SetText(str);
	setWindowTitle("QSS Editor");
}

/// <summary>
/// Fill the main text box with the most advanced style.
/// This will expose all control types in the application as well as their named instances.
/// Called when the Advanced button is clicked.
/// </summary>
void QssDialog::AdvancedButton_clicked()
{
	QString str = BaseStyle();
	auto names = GetClassNames(true);

	for (auto& it : names)
		str += it + QString("\n{\n\t\n}\n\n");

	SetText(str);
	setWindowTitle("QSS Editor");
}

/// <summary>
/// Insert a CSS property.
/// This is called whenever the user inserts a value via the menus.
/// </summary>
/// <param name="name">The name of the property to insert</param>
/// <param name="value">The value of the property to insert</param>
void QssDialog::InsertCssProperty(const QString& name, const QString& value)
{
	auto editor = ui->QssEdit;
	auto cursor = editor->textCursor();

	if (!name.isEmpty())
	{
		cursor.beginEditBlock();
		cursor.removeSelectedText();
		cursor.movePosition(QTextCursor::EndOfLine);
		//Simple check to see if we're in a selector scope.
		const QTextDocument* doc = editor->document();
		const QTextCursor closing = doc->find(QStringLiteral("}"), cursor, QTextDocument::FindBackward);
		const QTextCursor opening = doc->find(QStringLiteral("{"), cursor, QTextDocument::FindBackward);
		const bool inSelector = !opening.isNull() && (closing.isNull() ||
								closing.position() < opening.position());
		QString insertion;

		//Reasonable attempt at positioning things correctly. This can and often is wrong, but is sufficient for our purposes.
		if (editor->textCursor().block().length() != 1 && !editor->textCursor().block().text().isEmpty())
			insertion += QLatin1Char('\n');

		if (inSelector && editor->textCursor().block().text() != "\t")
			insertion += QLatin1Char('\t');

		insertion += name;
		insertion += QStringLiteral(": ");
		insertion += value;
		insertion += QLatin1Char(';');
		cursor.insertText(insertion);
		cursor.endEditBlock();
	}
	else
	{
		cursor.insertText(value);
	}
}

/// <summary>
/// Initial file dialog creation.
/// This will perform lazy instantiation since it takes a long time.
/// </summary>
void QssDialog::SetupFileDialog()
{
#ifndef __APPLE__

	if (!m_FileDialog)
	{
		m_FileDialog = new QFileDialog(this);
		m_FileDialog->setViewMode(QFileDialog::List);
		m_FileDialog->setDirectory(m_Parent->m_SettingsPath);
		m_FileDialog->setOption(QFileDialog::DontUseNativeDialog, true);
		m_FileDialog->setSidebarUrls(dynamic_cast<Fractorium*>(parent())->Urls());
	}

#endif
}

/// <summary>
/// Present a file open dialog and retun the file selected.
/// </summary>
/// <returns>The file selected if any, else empty string.</returns>
QString QssDialog::OpenFile()
{
#ifndef __APPLE__
	QStringList filenames;
	SetupFileDialog();
	m_FileDialog->setFileMode(QFileDialog::ExistingFile);
	m_FileDialog->setAcceptMode(QFileDialog::AcceptOpen);
	m_FileDialog->setNameFilter("Qss (*.qss)");
	m_FileDialog->setWindowTitle("Open Stylesheet");
	m_FileDialog->selectNameFilter("*.qss");

	if (m_FileDialog->exec() == QDialog::Accepted)
		filenames = m_FileDialog->selectedFiles();

	return !filenames.empty() ? filenames[0] : "";
#else
	auto filename = QFileDialog::getOpenFileName(this, tr("Open Stylesheet"), m_Parent->m_SettingsPath, tr("Qss (*.qss)"));
	return filename.size() > 0 ? filename : "";
#endif
}

/// <summary>
/// Present a file save dialog and retun the file selected.
/// </summary>
/// <returns>The file selected for saving if any, else empty string.</returns>
QString QssDialog::SaveFile()
{
#ifndef __APPLE__
	QStringList filenames;
	SetupFileDialog();
	m_FileDialog->setFileMode(QFileDialog::AnyFile);
	m_FileDialog->setAcceptMode(QFileDialog::AcceptSave);
	m_FileDialog->setNameFilter("Qss (*.qss)");
	m_FileDialog->setWindowTitle("Save Stylesheet");
	m_FileDialog->selectNameFilter("*.qss");

	if (m_FileDialog->exec() == QDialog::Accepted)
		filenames = m_FileDialog->selectedFiles();

	return !filenames.empty() ? filenames[0] : "";
#else
	auto filename = QFileDialog::getSaveFileName(this, tr("Save Stylesheet"), m_Parent->m_SettingsPath, tr("Qss (*.qss)"));
	return filename.size() > 0 ? filename : "";
#endif
}
