From 63476c03f322bf5abcb460ca6c2f1b86d3dd858f Mon Sep 17 00:00:00 2001 From: Max Paperno Date: Fri, 15 Jun 2018 00:25:35 -0400 Subject: [PATCH] [Companion] Implement application settings import and export via GUI and CLI. --- companion/src/companion.cpp | 151 ++++++++++++++++++++++++++++++----- companion/src/constants.h | 4 + companion/src/helpers.cpp | 19 +++++ companion/src/helpers.h | 3 + companion/src/mainwindow.cpp | 71 ++++++++++++++++ companion/src/mainwindow.h | 4 + 6 files changed, 232 insertions(+), 20 deletions(-) diff --git a/companion/src/companion.cpp b/companion/src/companion.cpp index c983c088d..7a431b8f4 100644 --- a/companion/src/companion.cpp +++ b/companion/src/companion.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #if defined(JOYSTICKS) || defined(SIMU_AUDIO) #include @@ -36,6 +37,7 @@ #include "simulatorinterface.h" #include "storage.h" #include "translations.h" +#include "helpers.h" #ifdef __APPLE__ #include @@ -52,6 +54,84 @@ class MyProxyStyle : public QProxyStyle }; #endif +void importError() +{ + QMessageBox::critical(nullptr, CPN_STR_APP_NAME, QCoreApplication::translate("Companion", "The saved settings could not be imported, please try again or continue with current settings."), QMessageBox::Ok, 0); +} + +void checkSettingsImport(bool force = false) +{ + QString previousVersion; + bool found; + if (!(found = g.findPreviousVersionSettings(&previousVersion))) { + found = QDir(CPN_SETTINGS_BACKUP_DIR).entryList(QStringList({"*.ini"}), QDir::Files).size() > 0; + } + if (!found && !force) + return; + + const QString impFileBtn = QCoreApplication::translate("Companion", "Import from File"); + const QString impPrevBtn = QCoreApplication::translate("Companion", "Import from v%1").arg(previousVersion); + const QString impNoneBtn = QCoreApplication::translate("Companion", "Do not import"); + + QString msg; + if (previousVersion.isEmpty()) { + if (found) + msg = QCoreApplication::translate("Companion", "We have found possible Companion settings backup file(s).\nDo you want to import settings from a file?"); + else + msg = QCoreApplication::translate("Companion", "Import settings from a file, or start with current values."); + } + else { + msg = QCoreApplication::translate("Companion", "We have found existing settings for Companion version: %1.\nDo you want to import them?\n\n" \ + "If you have a settings backup file, you may import that instead.").arg(previousVersion); + } + + const int ret = QMessageBox::question(nullptr, CPN_STR_APP_NAME, msg, impNoneBtn, impFileBtn, (previousVersion.isEmpty() ? QString() : impPrevBtn), 0, 0); + if (!ret) + return; + + // Import from previous version + if (ret == 2) { + if (!g.importSettings(previousVersion)) { + // very unlikely, but just in case of unexpected error, restart the import + importError(); + checkSettingsImport(); + } + return; + } + + // Import from file + bool err = false; + QString impFile = CPN_SETTINGS_BACKUP_DIR; + impFile = QFileDialog::getOpenFileName(nullptr, QCoreApplication::translate("Companion", "Select %1:").arg(CPN_STR_APP_SETTINGS_FILES), impFile, CPN_STR_APP_SETTINGS_FILTER); + + if (!impFile.isEmpty() && QFileInfo(impFile).isReadable()) { + QSettings fromSettings(impFile, QSettings::IniFormat); + if (g.importSettings(&fromSettings)) { + return; + } + else { + importError(); + err = true; + } + } + // Restart the import prompt if user cancelled the file select dialog but we do have previous settings, or if we had a file import error. + if (err || !previousVersion.isEmpty()) + checkSettingsImport(); +} + +void printHelpText() +{ + printf(qPrintable(QString(APP_COMPANION % " v%s\n\n")), VERSION); + const char tmpl[] = " %s \t %s\n"; + printf(tmpl, "--export", QCoreApplication::translate("Companion", "Save application settings to file...").toUtf8().constData()); + printf(tmpl, "--import", QCoreApplication::translate("Companion", "Load application settings from file or previous version...").toUtf8().constData()); + printf(tmpl, "--defaults", QCoreApplication::translate("Companion", "Reset ALL application settings to default and remove radio profiles...").toUtf8().constData()); + printf(tmpl, "--quit ", QCoreApplication::translate("Companion", "Exit before settings initialization and application startup.").toUtf8().constData()); + printf(tmpl, "--version", QCoreApplication::translate("Companion", "Print version number and exit.").toUtf8().constData()); + printf(tmpl, "--help|-h", QCoreApplication::translate("Companion", "Print this help text.").toUtf8().constData()); + fflush(stdout); +} + int main(int argc, char *argv[]) { #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) @@ -65,6 +145,19 @@ int main(int argc, char *argv[]) app.setOrganizationDomain(COMPANY_DOMAIN); app.setAttribute(Qt::AA_DontShowIconsInMenus, false); + const QStringList args(QApplication::arguments()); + + if (args.contains("--help") || args.contains("-h") || args.contains("/?")) { + printHelpText(); + exit(0); + } + + if (args.contains("--version")) { + printf("%s\n", VERSION); + fflush(stdout); + exit(0); + } + Q_INIT_RESOURCE(companion); if (AppDebugMessageHandler::instance()) @@ -72,28 +165,45 @@ int main(int argc, char *argv[]) CustomDebug::setFilterRules(); - if (!g.hasCurrentSettings()) { - QString previousVersion; - if (g.findPreviousVersionSettings(&previousVersion)) { - QMessageBox msgBox; - msgBox.setText(QCoreApplication::translate("Companion", "We have found existing settings for Companion version: %1.\nDo you want to import them?").arg(previousVersion)); - msgBox.setIcon(QMessageBox::Information); - msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); - msgBox.setDefaultButton(QMessageBox::Yes); - int ret = msgBox.exec(); + // handle settings export/import/reset via CLI - if (ret == QMessageBox::Yes) - g.importSettings(previousVersion); + bool doExport = args.contains("--export"); + bool doImport = args.contains("--import"); + bool doReset = args.contains("--defaults"); + + if (doExport) + Helpers::exportAppSettings(); + + if (doReset) { + char ok; + printf("%s [Y/N] ", QCoreApplication::translate("Companion", "Reset ALL application settings to default values and remove radio profiles, are you sure?").toUtf8().constData()); + std::cin >> ok; + if (ok != 'Y' && ok != 'y') + exit(0); + if (!doExport) { + printf("%s [Y/N] ", QCoreApplication::translate("Companion", "Would you like to perform a backup first?").toUtf8().constData()); + std::cin >> ok; + if (ok == 'Y' || ok == 'y') + Helpers::exportAppSettings(); } + g.resetAllSettings(); + g.storeAllSettings(); + std::cout << QCoreApplication::translate("Companion", "Application settings were reset and saved.").toUtf8().constData() << std::endl; } - g.init(); - QStringList strl = QApplication::arguments(); - if (strl.contains("--version")) { - printf("%s\n", VERSION); - fflush(stdout); + if (doImport) + checkSettingsImport(true); + + // give option to quit before settings init + if (args.contains("--quit")) exit(0); - } + + // end CLI handling + + if (!doImport && g.isFirstUse()) + checkSettingsImport(false); + + g.init(); QFile dbgLog; if (AppDebugMessageHandler::instance() && g.appDebugLog() && !g.appLogsDir().isEmpty() && QDir().mkpath(g.appLogsDir())) { @@ -127,9 +237,10 @@ int main(int argc, char *argv[]) registerOpenTxFirmwares(); SimulatorLoader::registerSimulators(); - if (g.profile[g.id()].fwType().isEmpty()){ - g.profile[g.id()].fwType(Firmware::getDefaultVariant()->getId()); - g.profile[g.id()].fwName(""); + Profile & profile = g.currentProfile(); + if (profile.fwType().isEmpty()){ + profile.fwType(Firmware::getDefaultVariant()->getId()); + profile.fwName(""); } QString splashScreen; diff --git a/companion/src/constants.h b/companion/src/constants.h index a5a28fdb7..9e35a57cc 100644 --- a/companion/src/constants.h +++ b/companion/src/constants.h @@ -69,6 +69,10 @@ #define EEPE_EEPROM_FILE_HEADER "EEPE EEPROM FILE" #define EEPE_MODEL_FILE_HEADER "EEPE MODEL FILE" +#define CPN_STR_APP_SETTINGS QCoreApplication::translate("Companion", "Application Settings") +#define CPN_STR_APP_SETTINGS_FILES CPN_STR_APP_SETTINGS % " " % CPN_STR_FILES +#define CPN_STR_APP_SETTINGS_FILTER CPN_STR_APP_SETTINGS_FILES % " (*.ini)" + #define CPN_STR_SW_INDICATOR_UP QCoreApplication::translate("RawSwitch", "\xE2\x86\x91") // Switch up position indicator: Up arrow, or similar. #define CPN_STR_SW_INDICATOR_DN QCoreApplication::translate("RawSwitch", "\xE2\x86\x93") // Switch down position indicator: Down arrow, or similar. #define CPN_STR_SW_INDICATOR_NEUT QCoreApplication::translate("RawSwitch", "-") // Switch neutral (middle) position indicator. diff --git a/companion/src/helpers.cpp b/companion/src/helpers.cpp index b7c0fa9ee..bb6e7da4c 100644 --- a/companion/src/helpers.cpp +++ b/companion/src/helpers.cpp @@ -33,6 +33,7 @@ #include "simulatormainwindow.h" #include "storage/sdcard.h" +#include #include #include @@ -543,6 +544,24 @@ QStandardItemModel * Helpers::getRawSourceItemModel(const GeneralSettings * cons return itemModel; } +void Helpers::exportAppSettings(QWidget * dlgParent) +{ + static QString lastExpFile = CPN_SETTINGS_INI_PATH.arg(QDateTime::currentDateTime().toString("dd-MMM-yy")); + const QString expFile = QFileDialog::getSaveFileName(dlgParent, QCoreApplication::translate("Companion", "Select or create a file for exported Settings:"), lastExpFile, CPN_STR_APP_SETTINGS_FILTER); + if (expFile.isEmpty()) + return; + + lastExpFile = expFile; + QString resultMsg; + if (g.exportSettingsToFile(expFile, resultMsg)) { + QMessageBox::information(dlgParent, CPN_STR_APP_NAME, resultMsg); + return; + } + resultMsg.append("\n" % QCoreApplication::translate("Companion", "Press the 'Retry' button to choose another file.")); + if (QMessageBox::warning(dlgParent, CPN_STR_APP_NAME, resultMsg, QMessageBox::Cancel, QMessageBox::Retry) == QMessageBox::Retry) + exportAppSettings(dlgParent); +} + void startSimulation(QWidget * parent, RadioData & radioData, int modelIdx) { QString fwId = SimulatorLoader::findSimulatorByFirmwareName(getCurrentFirmware()->getId()); diff --git a/companion/src/helpers.h b/companion/src/helpers.h index db2ab7a18..e1e404847 100644 --- a/companion/src/helpers.h +++ b/companion/src/helpers.h @@ -143,6 +143,9 @@ namespace Helpers void populateFileComboBox(QComboBox * b, const QSet & set, const QString & current); void getFileComboBoxValue(QComboBox * b, char * dest, int length); + + void exportAppSettings(QWidget * dlgParent = nullptr); + } // namespace Helpers // TODO : move globals to Helpers namespace diff --git a/companion/src/mainwindow.cpp b/companion/src/mainwindow.cpp index a0bb46b77..dc854afcf 100644 --- a/companion/src/mainwindow.cpp +++ b/companion/src/mainwindow.cpp @@ -1387,6 +1387,9 @@ void MainWindow::retranslateUi(bool showMsg) trAct(copyProfileAct, tr("Copy Current Radio Profile"), tr("Duplicate current Radio Settings Profile")); trAct(deleteProfileAct, tr("Delete Current Radio Profile..."), tr("Delete the current Radio Settings Profile")); + trAct(exportSettingsAct, tr("Export Application Settings.."), tr("Save all the current %1 and Simulator settings (including radio profiles) to a file.").arg(CPN_STR_APP_NAME)); + trAct(importSettingsAct, tr("Import Application Settings.."), tr("Load %1 and Simulator settings from a prevously exported settings file.").arg(CPN_STR_APP_NAME)); + trAct(actTabbedWindows, tr("Tabbed Windows"), tr("Use tabs to arrange open windows.")); trAct(actTileWindows, tr("Tile Windows"), tr("Arrange open windows across all the available space.")); trAct(actCascadeWindows, tr("Cascade Windows"), tr("Arrange all open windows in a stack.")); @@ -1442,6 +1445,9 @@ void MainWindow::createActions() copyProfileAct = addAct("copy.png", SLOT(copyProfile())); deleteProfileAct = addAct("clear.png", SLOT(deleteCurrentProfile())); + exportSettingsAct = addAct("saveas.png", SLOT(exportSettings())); + importSettingsAct = addAct("open.png", SLOT(importSettings())); + actTabbedWindows = addAct("", SLOT(setTabbedWindows(bool)), 0, this, SIGNAL(triggered(bool))); actTileWindows = addAct("", SLOT(tileSubWindows()), 0, mdiArea); actCascadeWindows = addAct("", SLOT(cascadeSubWindows()), 0, mdiArea); @@ -1515,6 +1521,9 @@ void MainWindow::createMenus() settingsMenu->addAction(profilesMenuAct); settingsMenu->addAction(editSplashAct); settingsMenu->addAction(burnConfigAct); + settingsMenu->addSeparator(); + settingsMenu->addAction(exportSettingsAct); + settingsMenu->addAction(importSettingsAct); burnMenu = menuBar()->addMenu(""); burnMenu->addAction(writeEepromAct); @@ -1854,6 +1863,68 @@ void MainWindow::deleteCurrentProfile() deleteProfile(g.id()); } +void MainWindow::exportSettings() +{ + Helpers::exportAppSettings(); +} + +void MainWindow::importSettings() +{ + if (anyChildrenDirty()) { + QMessageBox::warning(this, CPN_STR_APP_NAME, tr("Please save or close all modified files before importing settings")); + return; + } + QString resultMsg = tr("" \ + "

%1 and Simulator settings can be imported (restored) from a previosly saved export (backup) file. " \ + "This will replace current settings with any settings found in the file.

" \ + "

An automatic backup of the current settings will be attempted. But if the current settings are useful then it is recommended that you make a manual backup first.

" \ + "

For best results when importing settings, close any other %1 windows you may have open, and make sure the standalone Simulator application is not running.

" \ + "

Do you wish to continue?

" \ + "").arg(CPN_STR_APP_NAME); + + int ret = QMessageBox::question(this, tr("Confirm Settings Import"), resultMsg); + if (ret != QMessageBox::Yes) + return; + + QString impFile = CPN_SETTINGS_BACKUP_DIR; + impFile = QFileDialog::getOpenFileName(this, tr("Select %1:").arg(CPN_STR_APP_SETTINGS_FILES), impFile, CPN_STR_APP_SETTINGS_FILTER); + if (impFile.isEmpty() || !QFileInfo(impFile).isReadable() || QFileInfo(impFile).isExecutable()) + return; + + // Try a backup first + QString expFile = CPN_SETTINGS_INI_PATH.arg(tr("backup") % " " % QDateTime::currentDateTime().toString("dd-MMM-yy HH-mm")); + if (!g.exportSettingsToFile(expFile, resultMsg)) { + resultMsg.append("\n" % tr("Press the 'Ignore' button to continue anyway.")); + if (QMessageBox::warning(this, CPN_STR_APP_NAME, resultMsg, QMessageBox::Cancel, QMessageBox::Ignore) == QMessageBox::Cancel) + return; + expFile.clear(); + } + const QString prevLoc = g.locale(); + + // Do the import + QSettings fromSettings(impFile, QSettings::IniFormat); + if (!g.importSettings(&fromSettings)) { + QMessageBox::critical(this, CPN_STR_APP_NAME, tr("The settings could not be imported."), QMessageBox::Ok, 0); + return; + } + resultMsg = tr("" \ + "

New settings have been imported from:
%1.

" \ + "

%2 will now re-initialize.

" \ + "

Note that you may need to close and restart %2 before some settings like language and icon theme take effect.

" \ + ).arg(impFile).arg(CPN_STR_APP_NAME); + + if (!expFile.isEmpty()) + resultMsg.append(tr("

The previous settings were backed up to:
%1

").arg(expFile)); + resultMsg.append(""); + QMessageBox::information(this, CPN_STR_APP_NAME, resultMsg); + + g.init(); + initWindowOptions(); + if (prevLoc != g.locale()) + Translations::installTranslators(); + onCurrentProfileChanged(); +} + QString MainWindow::strippedName(const QString &fullFileName) { return QFileInfo(fullFileName).fileName(); diff --git a/companion/src/mainwindow.h b/companion/src/mainwindow.h index ee63bdf1a..e0136d6f9 100644 --- a/companion/src/mainwindow.h +++ b/companion/src/mainwindow.h @@ -130,6 +130,8 @@ class MainWindow : public QMainWindow void copyProfile(); void deleteProfile(const int pid); void deleteCurrentProfile(); + void exportSettings(); + void importSettings(); void autoClose(); void closeUpdatesWaitDialog(); @@ -221,6 +223,8 @@ class MainWindow : public QMainWindow QAction *createProfileAct; QAction *copyProfileAct; QAction *deleteProfileAct; + QAction *exportSettingsAct; + QAction *importSettingsAct; QAction *openDocURLAct; QAction *actTabbedWindows; QAction *actTileWindows;