diff --git a/.gitignore b/.gitignore index a380dad..0bc8bfa 100644 --- a/.gitignore +++ b/.gitignore @@ -22,7 +22,7 @@ Makefile MinetestMapperGui *.o -moc_mainwindow.cpp -qrc_minetestmappergui.cpp -ui_mainwindow.h +moc_*.cpp +qrc_*.cpp +ui_*.h diff --git a/MinetestMapperGui.pro b/MinetestMapperGui.pro index 90f0ad9..4c9d3de 100644 --- a/MinetestMapperGui.pro +++ b/MinetestMapperGui.pro @@ -16,14 +16,17 @@ TEMPLATE = app SOURCES += main.cpp\ mainwindow.cpp \ colorlineedit.cpp \ - geometrywidget.cpp + geometrywidget.cpp \ + configdialog.cpp HEADERS += mainwindow.h \ colorlineedit.h \ - geometrywidget.h + geometrywidget.h \ + configdialog.h FORMS += mainwindow.ui \ - geometrywidget.ui + geometrywidget.ui \ + configdialog.ui RESOURCES += \ minetestmappergui.qrc diff --git a/configdialog.cpp b/configdialog.cpp new file mode 100644 index 0000000..c2121b5 --- /dev/null +++ b/configdialog.cpp @@ -0,0 +1,281 @@ +#include +#include +#include +#include +#include +#include + +#include "configdialog.h" +#include "ui_configdialog.h" +#include "mainwindow.h" + +const QString ConfigSettings::defaultMapperExecutableName("minetestmapper"); +QStringList ConfigSettings::predefinedMapperLocations; + +QString ConfigSettings::versionUnknown("(unknown)"); +QString ConfigSettings::versionError("(error)"); +QString ConfigSettings::optionsVersionKey("(version)"); +QString ConfigSettings::optionsTreeKey("(github-tree)"); +QString ConfigSettings::optionsTreeMinetest("minetest"); +QString ConfigSettings::optionsTreeRogier5("Rogier5"); + +ConfigSettings::InitStatics::InitStatics() +{ + #ifndef Q_OS_WIN + ConfigSettings::predefinedMapperLocations << "/bin/" + defaultMapperExecutableName; + ConfigSettings::predefinedMapperLocations << "/usr/bin/" + defaultMapperExecutableName; + ConfigSettings::predefinedMapperLocations << "/usr/local/bin/" + defaultMapperExecutableName; + #endif +} + + +ConfigDialog::ConfigDialog(const ConfigSettings &settings, MainWindow *app, QWidget *parent) : + QDialog(parent), + ui(new Ui::ConfigDialog), + settings(settings), + application(app) +{ + ui->setupUi(this); + executableList = ConfigSettings::getMapperExecutables(); + QString standardExecutable; + if (!executableList.empty()) + standardExecutable = executableList.at(0); + if (standardExecutable != "") + executableList.push_front(tr("---Automatic--- (Currently: %1)").arg(standardExecutable)); + else + executableList.push_front(tr("---Automatic--- (Currently: NONE - Minetestmapper Not Found)")); + ui->path_Minetestmapper->addItems(executableList); + ui->path_Minetestmapper->setCurrentIndex(0); + if (settings.mapperPath != "") { + ui->path_Minetestmapper->setCurrentText(settings.mapperPath); + if (ui->path_Minetestmapper->currentText() != settings.mapperPath) { + // Unfortunately, adding it means that the saved path can be chosen again, even if it + // does not exist. Can't avoid that, as we still want it to be the *current* value... + executableList.push_back(settings.mapperPath); + ui->path_Minetestmapper->addItem(settings.mapperPath); + ui->path_Minetestmapper->setCurrentText(settings.mapperPath); + // TODO: make the combobox editable (also solves the problem above) + } + } +} + +ConfigDialog::~ConfigDialog() +{ + delete ui; +} + +void ConfigDialog::on_browseMapper_clicked() +{ + #ifdef Q_OS_WIN + QString fileType = tr("Executable (*.exe)"); + #else + QString fileType; + #endif + + bool selectionSeemsOK; + QString fileName; + if (ui->path_Minetestmapper->currentIndex() != 0) { + fileName = ui->path_Minetestmapper->currentText(); + } + else if (ui->path_Minetestmapper->count() > 0) { + fileName = ui->path_Minetestmapper->itemText(1); + } + else { + #ifdef Q_OS_WIN + fileName = QCoreApplication::applicationDirPath(); + #else + fileName = QDir::currentPath(); + #endif + } + do { + fileName = QFileDialog::getOpenFileName(this, tr("Select Mapper Location"), + fileName, + fileType); + if (fileName != "" && !(QFile(fileName).permissions() & QFileDevice::ExeUser)) { + selectionSeemsOK = false; + QMessageBox::critical(this, tr("Invalid minetestmapper executable"), + tr("ERROR: The selected file is not executable")); + } + else { + // TODO: verify if it is minetestmapper (?) -> invoke with --help ? + selectionSeemsOK = true; + } + } while (!selectionSeemsOK); + + if(fileName != "") { + settings.mapperPath = fileName; + ui->path_Minetestmapper->setCurrentText(fileName); + if (ui->path_Minetestmapper->currentText() != fileName) { + executableList.push_back(fileName); + ui->path_Minetestmapper->addItem(fileName); + ui->path_Minetestmapper->setCurrentText(fileName); + } + } +} + +QString ConfigSettings::getMapperVersion(const QString &mapperBinary, QWidget *parent) +{ + QProcess mapperProcess(parent); + mapperProcess.setProgram(mapperBinary); + mapperProcess.setArguments(QStringList("--version")); + mapperProcess.start(); + if (!mapperProcess.waitForStarted(1000)) { + mapperProcess.terminate(); + return ConfigSettings::versionError; + } + if (!mapperProcess.waitForFinished(1000)) { + mapperProcess.terminate(); + if (!mapperProcess.waitForFinished(1000)) { + mapperProcess.kill(); + } + return ConfigSettings::versionError; + } + + QByteArray dataRaw; + QString data; + dataRaw = mapperProcess.readAllStandardError(); + data = QString(dataRaw).trimmed(); + if (data.contains("unrecognized option")) { + return ConfigSettings::versionUnknown; + } + dataRaw = mapperProcess.readAllStandardOutput(); + data = QString(dataRaw).trimmed(); + if (!data.contains("Version-ID:")) { + return ConfigSettings::versionUnknown; + } + return data.replace(QRegularExpression(".*Version-ID: *"),""); +} + + +// Also store the deduced github source tree and the version in the list +QMap ConfigSettings::getMapperOptions(const QString &mapperBinary, QWidget *parent) +{ + QRegularExpression optionRegex("^(|-[a-zA-Z].*)(--[a-zA-Z0-9][a-zA-Z0-9-_]+)( *(.*[^ \t\r\n]))?[ \t\r\n]*$"); + QMap options; + QProcess mapperProcess(parent); + mapperProcess.setProgram(mapperBinary); + mapperProcess.setArguments(QStringList("--help")); + mapperProcess.start(); + if (!mapperProcess.waitForStarted(1000)) { + mapperProcess.terminate(); + return options; + } + if (!mapperProcess.waitForFinished(1000)) { + mapperProcess.terminate(); + if (!mapperProcess.waitForFinished(1000)) { + mapperProcess.kill(); + } + return options; + } + + QByteArray helpTextRaw = mapperProcess.readAllStandardOutput(); + QStringList helpText = QString(helpTextRaw).trimmed().split("\n"); + for (int i = 0; i < helpText.count(); i++) { + QString helpLine = helpText[i].trimmed(); + if (helpLine.contains("Color format: '#000000'")) { + options[ConfigSettings::optionsTreeKey] = ConfigSettings::optionsTreeMinetest; + } + else if (helpLine.contains("X and Y coordinate formats:")) { + options[ConfigSettings::optionsTreeKey] = ConfigSettings::optionsTreeRogier5; + } + QString option = helpLine; + option.replace(optionRegex, "\\2"); + if (option[1] != '-' || option.contains(' ')) continue; + QString arguments = helpLine; + arguments.replace(optionRegex, "\\4"); + if (arguments == helpLine) arguments = ""; + options[option] = arguments; + } + options[ConfigSettings::optionsVersionKey] = getMapperVersion(mapperBinary, parent); + return options; +} + +QString ConfigSettings::getDefaultMapperExecutable(void) +{ + QStringList locations = getMapperExecutables(); + if (locations.empty()) return ""; + else return locations.at(0); +} + +void ConfigSettings::addMapperExecutablesToList(QStringList &existingList, QStringList &pathList) +{ + QString prevLocation; + for (; ! pathList.empty(); pathList.pop_front()) { + QString location = QStandardPaths::findExecutable(ConfigSettings::defaultMapperExecutableName, pathList); + if (location != "" && prevLocation != location) { + prevLocation = location; + int count = existingList.count(); + int i; + for (i = 0; i < count; i++) { + if (existingList[i] == location) + break; + } + if (i >= count) + existingList << location; + } + } +} + +QStringList ConfigSettings::getMapperExecutables(void) +{ + QStringList mapperLocations; + QStringList moreLocations = ConfigSettings::predefinedMapperLocations; + + // On windows, check installation directory of minetestmappergui, & give precedence over anything else + #ifdef Q_OS_WIN + QString appDir = QCoreApplication::applicationDirPath(); + QString mapperFileName; + + mapperFileName = QStandardPaths::findExecutable(ConfigSettings::defaultMapperExecutableName, QStringList(appDir)); + if (mapperFileName != "") + mapperLocations << mapperFileName; + + mapperFileName = QStandardPaths::findExecutable(ConfigSettings::defaultMapperExecutableName, QStringList(appDir + '/' + ConfigSettings::defaultMapperExecutableName)); + if (mapperFileName != "") + mapperLocations << mapperFileName; + + mapperFileName = QStandardPaths::findExecutable(ConfigSettings::defaultMapperExecutableName, QStringList(appDir + "/mapper")); + if (mapperFileName != "") + mapperLocations << mapperFileName; + #endif + + // Find all minetestmappers in PATH. Unfortunately, it seems that QT can't do this directly... + QStringList path = QProcessEnvironment::systemEnvironment().value("PATH").split(":"); + addMapperExecutablesToList(mapperLocations, path); + + // Find all minetestmappers in QStandardPaths::ApplicationsLocation, add if not already added + QStringList appLocations = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation); + addMapperExecutablesToList(mapperLocations, appLocations); + + // Add locations from moreLocations that were not already added + addMapperExecutablesToList(mapperLocations, moreLocations); + + return mapperLocations; +} + + +void ConfigDialog::on_path_Minetestmapper_currentIndexChanged(int index) +{ + if (index == 0) + settings.mapperPath = ""; + else + settings.mapperPath = ui->path_Minetestmapper->currentText(); +} + + + +void ConfigDialog::on_CancelButton_clicked() +{ + application->closeConfigDialog(); +} + +void ConfigDialog::on_ApplyButton_clicked() +{ + application->updateConfigSettings(settings); +} + +void ConfigDialog::on_OKButton_clicked() +{ + on_ApplyButton_clicked(); + on_CancelButton_clicked(); +} diff --git a/configdialog.h b/configdialog.h new file mode 100644 index 0000000..32427dd --- /dev/null +++ b/configdialog.h @@ -0,0 +1,67 @@ +#ifndef CONFIGDIALOG_H +#define CONFIGDIALOG_H + +#include +#include +#include + + +namespace Ui { +class ConfigDialog; +} +class MainWindow; + +struct ConfigSettings +{ + QString mapperPath; + + static QString versionUnknown; + static QString versionError; + static QString optionsVersionKey; + static QString optionsTreeKey; + static QString optionsTreeMinetest; + static QString optionsTreeRogier5; + + static QStringList getMapperExecutables(void); + static QString getDefaultMapperExecutable(void); + static const QString defaultMapperExecutableName; + static QStringList predefinedMapperLocations; + static QString getMapperVersion(const QString &mapperBinary, QWidget *parent = NULL); + static QMap getMapperOptions(const QString &mapperBinary, QWidget *parent = NULL); + +private: + struct InitStatics { InitStatics(void); }; + static const InitStatics initStatics; + friend struct InitStatics; + + static void addMapperExecutablesToList(QStringList &existingList, QStringList &pathList); +}; + +class ConfigDialog : public QDialog +{ + Q_OBJECT + +public: + explicit ConfigDialog(const ConfigSettings &settings, MainWindow *app, QWidget *parent = 0); + ~ConfigDialog(); + +private: + Ui::ConfigDialog *ui; + QStringList executableList; + ConfigSettings settings; + MainWindow *application; + +protected slots: + +private slots: + + void on_browseMapper_clicked(); + + void on_path_Minetestmapper_currentIndexChanged(int index); + + void on_CancelButton_clicked(); + void on_ApplyButton_clicked(); + void on_OKButton_clicked(); +}; + +#endif // CONFIGDIALOG_H diff --git a/configdialog.ui b/configdialog.ui new file mode 100644 index 0000000..c2cf779 --- /dev/null +++ b/configdialog.ui @@ -0,0 +1,101 @@ + + + ConfigDialog + + + + 0 + 0 + 483 + 66 + + + + Dialog + + + + + + + 0 + 0 + + + + Browse + + + + :/open:/open + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Cancel + + + + + + + Apply + + + + + + + OK + + + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + Mapper Location: + + + + + + + + + + diff --git a/mainwindow.cpp b/mainwindow.cpp index 1314319..f9bfd5f 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -9,6 +9,8 @@ #include #include +#include "configdialog.h" + #if defined(Q_OS_WIN) static const QString qSettingsOrganisation("addi"); static const QString qSettingsApplicationPrefix("Minetestmapper_"); @@ -45,7 +47,8 @@ InitStatics::InitStatics(void) MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), - ui(new Ui::MainWindow) + ui(new Ui::MainWindow), + configDialog(NULL) { #ifndef Q_OS_WIN if (!migrateSettingsProfiles()) @@ -191,11 +194,41 @@ void MainWindow::changeEvent(QEvent* event) MainWindow::~MainWindow() { + if (configDialog) { + delete configDialog; + configDialog = NULL; + } delete ui; } void MainWindow::on_button_generate_clicked() { + + QFile mapperBinary; + if (currentSettings.mapperPath == "") + mapperBinary.setFileName(ConfigSettings::getDefaultMapperExecutable()); + else + mapperBinary.setFileName(currentSettings.mapperPath); + if (!mapperBinary.exists()) { + if (currentSettings.mapperPath == "") + QMessageBox::critical(this, tr("Minetestmapper not found"), + tr("ERROR: No minetestmapper executable could not be found

" + "Please configure one (Edit->Preferences)")); + else + QMessageBox::critical(this, tr("Minetestmapper not found"), + tr("ERROR: Configured minetestmapper executable (%1) could not be found

" + "Please configure one (Edit->Preferences)").arg(currentSettings.mapperPath)); + return; + } else if (!(mapperBinary.permissions() & QFileDevice::ExeUser)) { + QMessageBox::critical(this, tr("Minetestmapper not executable"), + tr("ERROR: The configured minetestmapper (%1) is not executable" + "Please configure a valid minetestmapper executable (Edit->Preferences)") + .arg(mapperBinary.fileName())); + return; + } + + qDebug() << QString("Minetestmapper version: ") + ConfigSettings::getMapperVersion(mapperBinary.fileName(), this); + QDir worldPath = QDir(ui->path_World->text()); if(!worldPath.exists()||worldPath.path()=="."||worldPath.path()=="/"){ QMessageBox::critical(this, tr("no input world selected"), @@ -372,9 +405,10 @@ void MainWindow::on_button_generate_clicked() } myProcess = new QProcess(this); + #ifdef Q_OS_WIN myProcess->setWorkingDirectory(appDir); - QString program = appDir+"/mapper/minetestmapper"; - myProcess->setProgram(program); + #endif + myProcess->setProgram(mapperBinary.fileName()); qDebug()<setArguments(arguments); @@ -672,6 +706,10 @@ void MainWindow::writeProfile(QString profile) //todo: check the current profile settings.beginGroup("Mapper"); + //'currentSettings' + settings.setValue("path_minetestmapper", currentSettings.mapperPath); + + //tab1 General settings.setValue("path_OutputImage", ui->path_OutputImage->text()); settings.setValue("path_World", ui->path_World->text()); settings.setValue("backend",ui->backend->currentIndex()); @@ -744,6 +782,9 @@ void MainWindow::readProfile(QString profile) { QSettings settings(QSettings::IniFormat,QSettings::UserScope,qSettingsOrganisation, qSettingsApplicationPrefix+"profile_"+profile); settings.beginGroup("Mapper"); + //'currentSettings' + currentSettings.mapperPath = settings.value("path_minetestmapper").toString(); + //tab1 Genral ui->path_World->setText(settings.value("path_World",QDir::homePath()).toString()); ui->path_OutputImage->setText(settings.value("path_OutputImage",QDir::homePath().append("/map.png")).toString()); @@ -965,3 +1006,35 @@ void MainWindow::on_tileorigin_clicked() { ui->tiles_map->setText(tr("map origin (top left)")); } + +void MainWindow::on_actionPreferences_triggered() +{ + if (!configDialog) { + configDialog = new ConfigDialog(currentSettings, this, this); + configDialog->show(); + } else { + configDialog->show(); + configDialog->activateWindow(); + } +} + +void MainWindow::closeConfigDialog(void) +{ + if (configDialog) { + delete configDialog; + configDialog = NULL; + } +} + +void MainWindow::updateConfigSettings(const ConfigSettings &newSettings) +{ + if (newSettings.mapperPath != currentSettings.mapperPath) { + // Update all 'auto' paths (colors files, ...) ??? + // (currently there are none, but in the future, + // colors files could be searched for relative to the minetestmapper path. + // If the mapper path changes, the paths of the possible colors files + // may change as well + } + currentSettings = newSettings; +} + diff --git a/mainwindow.h b/mainwindow.h index c151b92..52a9bdd 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -17,6 +17,7 @@ #include #endif +#include "configdialog.h" namespace Ui { class MainWindow; @@ -30,6 +31,10 @@ public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); + // Interaction with config dialog + void closeConfigDialog(void); + void updateConfigSettings(const ConfigSettings &newSettings); + protected: void closeEvent(QCloseEvent* event); // this event is called, when a new translator is loaded or the system language is changed @@ -93,6 +98,8 @@ private slots: void on_tileorigin_clicked(); + void on_actionPreferences_triggered(); + private: Ui::MainWindow *ui; QProgressBar *progressBar; @@ -103,6 +110,9 @@ private: QWinTaskbarButton *taskbarButton; QWinTaskbarProgress *taskbarProgress; #endif + ConfigSettings currentSettings; + ConfigDialog *configDialog; + void finishUiInitialisation(void); // loads a language by the given language shortcur (e.g. de, en) diff --git a/mainwindow.ui b/mainwindow.ui index bbf7aea..7327535 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -2279,6 +2279,7 @@ + @@ -2564,6 +2565,11 @@ p, li { white-space: pre-wrap; } Open map after creation with the default png viewer + + + &Preferences + +