diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d5c68cde..208bddf33 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,6 +75,7 @@ option(BUILD_ESMTOOL "build ESM inspector" ON) option(BUILD_LAUNCHER "build Launcher" ON) option(BUILD_MWINIIMPORTER "build MWiniImporter" ON) option(BUILD_OPENCS "build OpenMW Construction Set" ON) +option(BUILD_WIZARD "build Installation Wizard" ON) option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF) option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest and GMock frameworks" OFF) option(BUILD_NIFTEST "build nif file tester" OFF) @@ -433,6 +434,9 @@ IF(NOT WIN32 AND NOT APPLE) IF(BUILD_NIFTEST) INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/niftest" DESTINATION "${BINDIR}" ) ENDIF(BUILD_NIFTEST) + IF(BUILD_WIZARD) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw-wizard" DESTINATION "${BINDIR}" ) + ENDIF(BUILD_WIZARD) if(BUILD_MYGUI_PLUGIN) INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Plugin_MyGUI_OpenMW_Resources.so" DESTINATION "${LIBDIR}" ) ENDIF(BUILD_MYGUI_PLUGIN) @@ -485,6 +489,9 @@ if(WIN32) INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/opencs.exe" DESTINATION ".") INSTALL(FILES "${OpenMW_BINARY_DIR}/opencs.ini" DESTINATION ".") ENDIF(BUILD_OPENCS) + IF(BUILD_WIZARD) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/openmw-wizard.exe" DESTINATION ".") + ENDIF(BUILD_WIZARD) if(BUILD_MYGUI_PLUGIN) INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/Plugin_MyGUI_OpenMW_Resources.dll" DESTINATION ".") ENDIF(BUILD_MYGUI_PLUGIN) @@ -505,6 +512,9 @@ if(WIN32) IF(BUILD_OPENCS) SET(CPACK_PACKAGE_EXECUTABLES "${CPACK_PACKAGE_EXECUTABLES};opencs;OpenMW Construction Set") ENDIF(BUILD_OPENCS) + IF(BUILD_WIZARD) + SET(CPACK_PACKAGE_EXECUTABLES "${CPACK_PACKAGE_EXECUTABLES};openmw-wizard;OpenMW Wizard") + ENDIF(BUILD_WIZARD) SET(CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '\$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Readme.lnk' '\$INSTDIR\\\\readme.txt'") SET(CPACK_NSIS_DELETE_ICONS_EXTRA " !insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP @@ -578,13 +588,6 @@ if (BUILD_ESMTOOL) endif() if (BUILD_LAUNCHER) - if(NOT WIN32) - find_package(LIBUNSHIELD REQUIRED) - if(NOT LIBUNSHIELD_FOUND) - message(SEND_ERROR "Failed to find libunshield") - endif(NOT LIBUNSHIELD_FOUND) - endif(NOT WIN32) - add_subdirectory( apps/launcher ) endif() @@ -596,6 +599,15 @@ if (BUILD_OPENCS) add_subdirectory (apps/opencs) endif() +if (BUILD_WIZARD) + find_package(LIBUNSHIELD REQUIRED) + if(NOT LIBUNSHIELD_FOUND) + message(FATAL_ERROR "Failed to find Unshield library") + endif(NOT LIBUNSHIELD_FOUND) + + add_subdirectory(apps/wizard) +endif() + # UnitTests if (BUILD_UNITTESTS) add_subdirectory( apps/openmw_test_suite ) @@ -698,6 +710,9 @@ if (WIN32) if (BUILD_ESMTOOL) set_target_properties(esmtool PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") endif (BUILD_ESMTOOL) + if (BUILD_WIZARD) + set_target_properties(openmw-wizard PROPERTIES COMPILE_FLAGS ${WARNINGS}) + endif (BUILD_WIZARD) if (BUILD_OPENCS) # QT triggers an informational warning that the object layout may differ when compiled with /vd2 set(OPENCS_WARNINGS "${WARNINGS} ${MT_BUILD} /wd4435") diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index 6be3f0e3e..ba330f70c 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -5,21 +5,16 @@ set(LAUNCHER maindialog.cpp playpage.cpp textslotmsgbox.cpp + settingspage.cpp - settings/gamesettings.cpp settings/graphicssettings.cpp - settings/launchersettings.cpp - utils/checkablemessagebox.cpp utils/profilescombobox.cpp utils/textinputdialog.cpp utils/lineedit.cpp ${CMAKE_SOURCE_DIR}/files/windows/launcher.rc ) -if(NOT WIN32) - LIST(APPEND LAUNCHER unshieldthread.cpp) -endif(NOT WIN32) set(LAUNCHER_HEADER datafilespage.hpp @@ -27,21 +22,14 @@ set(LAUNCHER_HEADER maindialog.hpp playpage.hpp textslotmsgbox.hpp + settingspage.hpp - settings/gamesettings.hpp settings/graphicssettings.hpp - settings/launchersettings.hpp - settings/settingsbase.hpp - utils/checkablemessagebox.hpp utils/profilescombobox.hpp utils/textinputdialog.hpp utils/lineedit.hpp ) -if(NOT WIN32) - LIST(APPEND LAUNCHER_HEADER unshieldthread.hpp) -endif(NOT WIN32) - # Headers that must be pre-processed set(LAUNCHER_HEADER_MOC @@ -50,25 +38,21 @@ set(LAUNCHER_HEADER_MOC maindialog.hpp playpage.hpp textslotmsgbox.hpp + settingspage.hpp utils/textinputdialog.hpp - utils/checkablemessagebox.hpp utils/profilescombobox.hpp utils/lineedit.hpp ) -if(NOT WIN32) - LIST(APPEND LAUNCHER_HEADER_MOC unshieldthread.hpp) -endif(NOT WIN32) - - set(LAUNCHER_UI ${CMAKE_SOURCE_DIR}/files/ui/datafilespage.ui ${CMAKE_SOURCE_DIR}/files/ui/graphicspage.ui ${CMAKE_SOURCE_DIR}/files/ui/mainwindow.ui ${CMAKE_SOURCE_DIR}/files/ui/playpage.ui ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui + ${CMAKE_SOURCE_DIR}/files/ui/settingspage.ui ) source_group(launcher FILES ${LAUNCHER} ${LAUNCHER_HEADER}) @@ -111,12 +95,6 @@ target_link_libraries(omwlauncher ${QT_LIBRARIES} components ) -if(NOT WIN32) - target_link_libraries(omwlauncher - ${LIBUNSHIELD_LIBRARY} - ) -endif(NOT WIN32) - if (BUILD_WITH_CODE_COVERAGE) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 362d7562c..17951f2e4 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -1,5 +1,7 @@ #include "datafilespage.hpp" +#include + #include #include #include @@ -9,18 +11,16 @@ #include #include - #include +#include + +#include +#include #include "utils/textinputdialog.hpp" #include "utils/profilescombobox.hpp" -#include "settings/gamesettings.hpp" -#include "settings/launchersettings.hpp" - -#include "components/contentselector/view/contentselector.hpp" - -Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gameSettings, LauncherSettings &launcherSettings, QWidget *parent) +Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings, Config::LauncherSettings &launcherSettings, QWidget *parent) : mCfgMgr(cfg) , mGameSettings(gameSettings) , mLauncherSettings(launcherSettings) @@ -30,20 +30,71 @@ Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSet setObjectName ("DataFilesPage"); mSelector = new ContentSelectorView::ContentSelector (ui.contentSelectorWidget); + mProfileDialog = new TextInputDialog(tr("New Profile"), tr("Profile name:"), this); + + connect(mProfileDialog->lineEdit(), SIGNAL(textChanged(QString)), + this, SLOT(updateOkButton(QString))); + buildView(); - setupDataFiles(); + loadSettings(); } -void Launcher::DataFilesPage::loadSettings() +void Launcher::DataFilesPage::buildView() +{ + ui.verticalLayout->insertWidget (0, mSelector->uiWidget()); + + //tool buttons + ui.newProfileButton->setToolTip ("Create a new profile"); + ui.deleteProfileButton->setToolTip ("Delete an existing profile"); + + //combo box + ui.profilesComboBox->addItem ("Default"); + ui.profilesComboBox->setPlaceholderText (QString("Select a profile...")); + ui.profilesComboBox->setCurrentIndex(ui.profilesComboBox->findText(QLatin1String("Default"))); + + // Add the actions to the toolbuttons + ui.newProfileButton->setDefaultAction (ui.newProfileAction); + ui.deleteProfileButton->setDefaultAction (ui.deleteProfileAction); + + //establish connections + connect (ui.profilesComboBox, SIGNAL (currentIndexChanged(int)), + this, SLOT (slotProfileChanged(int))); + + connect (ui.profilesComboBox, SIGNAL (profileRenamed(QString, QString)), + this, SLOT (slotProfileRenamed(QString, QString))); + + connect (ui.profilesComboBox, SIGNAL (signalProfileChanged(QString, QString)), + this, SLOT (slotProfileChangedByUser(QString, QString))); +} + +bool Launcher::DataFilesPage::loadSettings() { QStringList paths = mGameSettings.getDataDirs(); + + foreach (const QString &path, paths) + mSelector->addFiles(path); + + mDataLocal = mGameSettings.getDataLocal(); + + if (!mDataLocal.isEmpty()) + mSelector->addFiles(mDataLocal); + paths.insert (0, mDataLocal); PathIterator pathIterator (paths); - QString profileName = ui.profilesComboBox->currentText(); + QStringList profiles = mLauncherSettings.subKeys(QString("Profiles/")); + QString currentProfile = mLauncherSettings.getSettings().value("Profiles/currentprofile"); - QStringList files = mLauncherSettings.values(QString("Profiles/") + profileName, Qt::MatchExactly); + qDebug() << "current profile is: " << currentProfile; + foreach (const QString &item, profiles) + addProfile (item, false); + + // Hack: also add the current profile + if (!currentProfile.isEmpty()) + addProfile(currentProfile, true); + + QStringList files = mLauncherSettings.values(QString("Profiles/") + currentProfile + QString("/content"), Qt::MatchExactly); QStringList filepaths; foreach (const QString &file, files) @@ -55,6 +106,8 @@ void Launcher::DataFilesPage::loadSettings() } mSelector->setProfileContent (filepaths); + + return true; } void Launcher::DataFilesPage::saveSettings(const QString &profile) @@ -75,39 +128,12 @@ void Launcher::DataFilesPage::saveSettings(const QString &profile) mLauncherSettings.setValue(QString("Profiles/currentprofile"), ui.profilesComboBox->currentText()); foreach(const ContentSelectorModel::EsmFile *item, items) { - mLauncherSettings.setMultiValue(QString("Profiles/") + profileName, item->fileName()); + mLauncherSettings.setMultiValue(QString("Profiles/") + profileName + QString("/content"), item->fileName()); mGameSettings.setMultiValue(QString("content"), item->fileName()); } } -void Launcher::DataFilesPage::buildView() -{ - ui.verticalLayout->insertWidget (0, mSelector->uiWidget()); - - //tool buttons - ui.newProfileButton->setToolTip ("Create a new profile"); - ui.deleteProfileButton->setToolTip ("Delete an existing profile"); - - //combo box - ui.profilesComboBox->addItem ("Default"); - ui.profilesComboBox->setPlaceholderText (QString("Select a profile...")); - - // Add the actions to the toolbuttons - ui.newProfileButton->setDefaultAction (ui.newProfileAction); - ui.deleteProfileButton->setDefaultAction (ui.deleteProfileAction); - - //establish connections - connect (ui.profilesComboBox, SIGNAL (currentIndexChanged(int)), - this, SLOT (slotProfileChanged(int))); - - connect (ui.profilesComboBox, SIGNAL (profileRenamed(QString, QString)), - this, SLOT (slotProfileRenamed(QString, QString))); - - connect (ui.profilesComboBox, SIGNAL (signalProfileChanged(QString, QString)), - this, SLOT (slotProfileChangedByUser(QString, QString))); -} - void Launcher::DataFilesPage::removeProfile(const QString &profile) { mLauncherSettings.remove(QString("Profiles/") + profile); @@ -140,6 +166,9 @@ void Launcher::DataFilesPage::setProfile (const QString &previous, const QString if (previous == current) return; + if (previous.isEmpty()) + return; + if (!previous.isEmpty() && savePrevious) saveSettings (previous); @@ -180,53 +209,15 @@ void Launcher::DataFilesPage::slotProfileChanged(int index) setProfile (index, true); } -void Launcher::DataFilesPage::setupDataFiles() -{ - QStringList paths = mGameSettings.getDataDirs(); - - foreach (const QString &path, paths) - mSelector->addFiles(path); - - mDataLocal = mGameSettings.getDataLocal(); - - if (!mDataLocal.isEmpty()) - mSelector->addFiles(mDataLocal); - - QStringList profiles; - QString currentProfile = mLauncherSettings.getSettings().value("Profiles/currentprofile"); - - foreach (QString key, mLauncherSettings.getSettings().keys()) - { - if (key.contains("Profiles/")) - { - QString profile = key.mid (9); - if (profile != "currentprofile") - { - if (!profiles.contains(profile)) - profiles << profile; - } - } - } - - foreach (const QString &item, profiles) - addProfile (item, false); - - setProfile (ui.profilesComboBox->findText(currentProfile), false); - - loadSettings(); -} - void Launcher::DataFilesPage::on_newProfileAction_triggered() { - TextInputDialog newDialog (tr("New Profile"), tr("Profile name:"), this); - - if (newDialog.exec() != QDialog::Accepted) + if (!mProfileDialog->exec() == QDialog::Accepted) return; - QString profile = newDialog.getText(); + QString profile = mProfileDialog->lineEdit()->text(); if (profile.isEmpty()) - return; + return; saveSettings(); @@ -277,6 +268,19 @@ void Launcher::DataFilesPage::on_deleteProfileAction_triggered() checkForDefaultProfile(); } +void Launcher::DataFilesPage::updateOkButton(const QString &text) +{ + // We do this here because we need the profiles combobox text + if (text.isEmpty()) { + mProfileDialog->setOkButtonEnabled(false); + return; + } + + (ui.profilesComboBox->findText(text) == -1) + ? mProfileDialog->setOkButtonEnabled(true) + : mProfileDialog->setOkButtonEnabled(false); +} + void Launcher::DataFilesPage::checkForDefaultProfile() { //don't allow deleting "Default" profile diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index 37603a210..5beeb0e03 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -14,12 +14,12 @@ class QMenu; namespace Files { struct ConfigurationManager; } namespace ContentSelectorView { class ContentSelector; } +namespace Config { class GameSettings; + class LauncherSettings; } namespace Launcher { class TextInputDialog; - class GameSettings; - class LauncherSettings; class ProfilesComboBox; class DataFilesPage : public QWidget @@ -30,8 +30,8 @@ namespace Launcher Ui::DataFilesPage ui; public: - explicit DataFilesPage (Files::ConfigurationManager &cfg, GameSettings &gameSettings, - LauncherSettings &launcherSettings, QWidget *parent = 0); + explicit DataFilesPage (Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings, + Config::LauncherSettings &launcherSettings, QWidget *parent = 0); QAbstractItemModel* profilesModel() const; @@ -39,7 +39,7 @@ namespace Launcher //void writeConfig(QString profile = QString()); void saveSettings(const QString &profile = ""); - void loadSettings(); + bool loadSettings(); signals: void signalProfileChanged (int index); @@ -53,24 +53,25 @@ namespace Launcher void slotProfileRenamed(const QString &previous, const QString ¤t); void slotProfileDeleted(const QString &item); + void updateOkButton(const QString &text); + void on_newProfileAction_triggered(); void on_deleteProfileAction_triggered(); private: - QMenu *mContextMenu; + TextInputDialog *mProfileDialog; Files::ConfigurationManager &mCfgMgr; - GameSettings &mGameSettings; - LauncherSettings &mLauncherSettings; + Config::GameSettings &mGameSettings; + Config::LauncherSettings &mLauncherSettings; QString mDataLocal; void setPluginsCheckstates(Qt::CheckState state); void buildView(); - void setupDataFiles(); void setupConfig(); void readConfig(); void setProfile (int index, bool savePrevious); diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 1c6e69023..ec7f5a04d 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -137,6 +137,7 @@ bool Launcher::GraphicsPage::setupSDL() return false; } + screenComboBox->clear(); for (int i = 0; i < displays; i++) { screenComboBox->addItem(QString(tr("Screen ")) + QString::number(i + 1)); @@ -149,7 +150,7 @@ bool Launcher::GraphicsPage::loadSettings() { if (!setupSDL()) return false; - if (!setupOgre()) + if (!mOgre && !setupOgre()) return false; if (mGraphicsSettings.value(QString("Video/vsync")) == QLatin1String("true")) diff --git a/apps/launcher/main.cpp b/apps/launcher/main.cpp index fabf77d90..562f5c779 100644 --- a/apps/launcher/main.cpp +++ b/apps/launcher/main.cpp @@ -52,11 +52,14 @@ int main(int argc, char *argv[]) Launcher::MainDialog mainWin; - if (mainWin.setup()) { - mainWin.show(); - } else { + if (!mainWin.showFirstRunDialog()) return 0; - } + +// if (!mainWin.setup()) { +// return 0; +// } + + mainWin.show(); int returnValue = app.exec(); SDL_Quit(); diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 41d662d30..975958d7a 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -5,29 +5,24 @@ #include #include #include +#include #include #include #include #include #include #include -#include #include #include #include -#ifndef WIN32 - #include "unshieldthread.hpp" -#endif - -#include "textslotmsgbox.hpp" - -#include "utils/checkablemessagebox.hpp" - #include "playpage.hpp" #include "graphicspage.hpp" #include "datafilespage.hpp" +#include "settingspage.hpp" + +using namespace Process; Launcher::MainDialog::MainDialog(QWidget *parent) : mGameSettings(mCfgMgr), QMainWindow (parent) @@ -53,6 +48,15 @@ Launcher::MainDialog::MainDialog(QWidget *parent) setupUi(this); + mGameInvoker = new ProcessInvoker(); + mWizardInvoker = new ProcessInvoker(); + + connect(mWizardInvoker->getProcess(), SIGNAL(started()), + this, SLOT(wizardStarted())); + + connect(mWizardInvoker->getProcess(), SIGNAL(finished(int,QProcess::ExitStatus)), + this, SLOT(wizardFinished(int,QProcess::ExitStatus))); + iconWidget->setViewMode(QListView::IconMode); iconWidget->setWrapping(false); iconWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // Just to be sure @@ -79,13 +83,13 @@ Launcher::MainDialog::MainDialog(QWidget *parent) if (!revision.isEmpty() && !tag.isEmpty()) { if (revision == tag) { - versionLabel->setText(tr("OpenMW %0 release").arg(OPENMW_VERSION)); + versionLabel->setText(tr("OpenMW %1 release").arg(OPENMW_VERSION)); } else { - versionLabel->setText(tr("OpenMW development (%0)").arg(revision.left(10))); + versionLabel->setText(tr("OpenMW development (%1)").arg(revision.left(10))); } // Add the compile date and time - versionLabel->setToolTip(tr("Compiled on %0 %1").arg(QLocale(QLocale::C).toDate(QString(__DATE__).simplified(), + versionLabel->setToolTip(tr("Compiled on %1 %2").arg(QLocale(QLocale::C).toDate(QString(__DATE__).simplified(), QLatin1String("MMM d yyyy")).toString(Qt::SystemLocaleLongDate), QLocale(QLocale::C).toTime(QString(__TIME__).simplified(), QLatin1String("hh:mm:ss")).toString(Qt::SystemLocaleShortDate))); @@ -94,32 +98,41 @@ Launcher::MainDialog::MainDialog(QWidget *parent) createIcons(); } +Launcher::MainDialog::~MainDialog() +{ + delete mGameInvoker; + delete mWizardInvoker; +} + void Launcher::MainDialog::createIcons() { if (!QIcon::hasThemeIcon("document-new")) QIcon::setThemeName("tango"); - // We create a fallback icon because the default fallback doesn't work - QIcon graphicsIcon = QIcon(":/icons/tango/video-display.png"); - QListWidgetItem *playButton = new QListWidgetItem(iconWidget); playButton->setIcon(QIcon(":/images/openmw.png")); playButton->setText(tr("Play")); playButton->setTextAlignment(Qt::AlignCenter); playButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); - QListWidgetItem *graphicsButton = new QListWidgetItem(iconWidget); - graphicsButton->setIcon(QIcon::fromTheme("video-display", graphicsIcon)); - graphicsButton->setText(tr("Graphics")); - graphicsButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom | Qt::AlignAbsolute); - graphicsButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); - QListWidgetItem *dataFilesButton = new QListWidgetItem(iconWidget); dataFilesButton->setIcon(QIcon(":/images/openmw-plugin.png")); dataFilesButton->setText(tr("Data Files")); dataFilesButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom); dataFilesButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); + QListWidgetItem *graphicsButton = new QListWidgetItem(iconWidget); + graphicsButton->setIcon(QIcon::fromTheme("video-display")); + graphicsButton->setText(tr("Graphics")); + graphicsButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom | Qt::AlignAbsolute); + graphicsButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); + + QListWidgetItem *settingsButton = new QListWidgetItem(iconWidget); + settingsButton->setIcon(QIcon::fromTheme("preferences-system")); + settingsButton->setText(tr("Settings")); + settingsButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom); + settingsButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); + connect(iconWidget, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), this, SLOT(changePage(QListWidgetItem*,QListWidgetItem*))); @@ -129,8 +142,9 @@ void Launcher::MainDialog::createIcons() void Launcher::MainDialog::createPages() { mPlayPage = new PlayPage(this); - mGraphicsPage = new GraphicsPage(mCfgMgr, mGraphicsSettings, this); mDataFilesPage = new DataFilesPage(mCfgMgr, mGameSettings, mLauncherSettings, this); + mGraphicsPage = new GraphicsPage(mCfgMgr, mGraphicsSettings, this); + mSettingsPage = new SettingsPage(mCfgMgr, mGameSettings, mLauncherSettings, this); // Set the combobox of the play page to imitate the combobox on the datafilespage mPlayPage->setProfilesModel(mDataFilesPage->profilesModel()); @@ -138,8 +152,9 @@ void Launcher::MainDialog::createPages() // Add the pages to the stacked widget pagesWidget->addWidget(mPlayPage); - pagesWidget->addWidget(mGraphicsPage); pagesWidget->addWidget(mDataFilesPage); + pagesWidget->addWidget(mGraphicsPage); + pagesWidget->addWidget(mSettingsPage); // Select the first page iconWidget->setCurrentItem(iconWidget->item(0), QItemSelectionModel::Select); @@ -153,153 +168,63 @@ void Launcher::MainDialog::createPages() bool Launcher::MainDialog::showFirstRunDialog() { - QStringList iniPaths; + if (!setupLauncherSettings()) + return false; - foreach (const QString &path, mGameSettings.getDataDirs()) { - QDir dir(path); - dir.setPath(dir.canonicalPath()); // Resolve symlinks - - if (dir.exists(QString("Morrowind.ini"))) - iniPaths.append(dir.absoluteFilePath(QString("Morrowind.ini"))); - else - { - if (!dir.cdUp()) - continue; // Cannot move from Data Files - - if (dir.exists(QString("Morrowind.ini"))) - iniPaths.append(dir.absoluteFilePath(QString("Morrowind.ini"))); - } - } - - // Ask the user where the Morrowind.ini is - if (iniPaths.empty()) { + if (mLauncherSettings.value(QString("General/firstrun"), QString("true")) == QLatin1String("true")) + { QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error detecting Morrowind configuration")); - msgBox.setIcon(QMessageBox::Warning); - msgBox.setStandardButtons(QMessageBox::Cancel); - msgBox.setText(QObject::tr("
Could not find Morrowind.ini

\ - OpenMW needs to import settings from this file.

\ - Press \"Browse...\" to specify the location manually.
")); + msgBox.setWindowTitle(tr("First run")); + msgBox.setIcon(QMessageBox::Question); + msgBox.setStandardButtons(QMessageBox::NoButton); + msgBox.setText(tr("

Welcome to OpenMW!

\ +

It is recommended to run the Installation Wizard.

\ +

The Wizard will let you select an existing Morrowind installation, \ + or install Morrowind for OpenMW to use.

")); - QAbstractButton *dirSelectButton = - msgBox.addButton(QObject::tr("B&rowse..."), QMessageBox::ActionRole); + QAbstractButton *wizardButton = + msgBox.addButton(tr("Run &Installation Wizard"), QMessageBox::AcceptRole); // ActionRole doesn't work?! + QAbstractButton *skipButton = + msgBox.addButton(tr("Skip"), QMessageBox::RejectRole); + + Q_UNUSED(skipButton); // Surpress compiler unused warning msgBox.exec(); - QString iniFile; - if (msgBox.clickedButton() == dirSelectButton) { - iniFile = QFileDialog::getOpenFileName( - NULL, - QObject::tr("Select configuration file"), - QDir::currentPath(), - QString(tr("Morrowind configuration file (*.ini)"))); - } - - if (iniFile.isEmpty()) - return false; // Cancel was clicked; - - QFileInfo info(iniFile); - iniPaths.clear(); - iniPaths.append(info.absoluteFilePath()); - } - - CheckableMessageBox msgBox(this); - msgBox.setWindowTitle(tr("Morrowind installation detected")); - - QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxQuestion); - int size = QApplication::style()->pixelMetric(QStyle::PM_MessageBoxIconSize); - msgBox.setIconPixmap(icon.pixmap(size, size)); - - QAbstractButton *importerButton = - msgBox.addButton(tr("Import"), QDialogButtonBox::AcceptRole); // ActionRole doesn't work?! - QAbstractButton *skipButton = - msgBox.addButton(tr("Skip"), QDialogButtonBox::RejectRole); - - Q_UNUSED(skipButton); // Surpress compiler unused warning - - msgBox.setStandardButtons(QDialogButtonBox::NoButton); - msgBox.setText(tr("
An existing Morrowind configuration was detected
\ -
Would you like to import settings from Morrowind.ini?
\ -
Warning: In most cases OpenMW needs these settings to run properly
")); - msgBox.setCheckBoxText(tr("Include selected masters and plugins (creates a new profile)")); - msgBox.exec(); - - - if (msgBox.clickedButton() == importerButton) { - - if (iniPaths.count() > 1) { - // Multiple Morrowind.ini files found - bool ok; - QString path = QInputDialog::getItem(this, tr("Multiple configurations found"), - tr("
There are multiple Morrowind.ini files found.

\ - Please select the one you wish to import from:"), iniPaths, 0, false, &ok); - if (ok && !path.isEmpty()) { - iniPaths.clear(); - iniPaths.append(path); + if (msgBox.clickedButton() == wizardButton) + { + if (!mWizardInvoker->startProcess(QLatin1String("openmw-wizard"), false)) { + return false; } else { - // Cancel was clicked - return false; + return true; } } - - // Create the file if it doesn't already exist, else the importer will fail - QString path = QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str()) + QString("openmw.cfg"); - QFile file(path); - - if (!file.exists()) { - if (!file.open(QIODevice::ReadWrite)) { - // File cannot be created - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error writing OpenMW configuration file")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not open or create %0 for writing

\ - Please make sure you have the right permissions \ - and try again.
").arg(file.fileName())); - msgBox.exec(); - return false; - } - - file.close(); - } - - // Construct the arguments to run the importer - QStringList arguments; - - if (msgBox.isChecked()) - arguments.append(QString("--game-files")); - - arguments.append(QString("--encoding")); - arguments.append(mGameSettings.value(QString("encoding"), QString("win1252"))); - arguments.append(QString("--ini")); - arguments.append(iniPaths.first()); - arguments.append(QString("--cfg")); - arguments.append(path); - - if (!startProgram(QString("mwiniimport"), arguments, false)) - return false; - - // Re-read the game settings - if (!setupGameSettings()) - return false; - - // Add a new profile - if (msgBox.isChecked()) { - mLauncherSettings.setValue(QString("Profiles/currentprofile"), QString("Imported")); - mLauncherSettings.remove(QString("Profiles/Imported/content")); - - QStringList contents = mGameSettings.values(QString("content")); - foreach (const QString &content, contents) { - mLauncherSettings.setMultiValue(QString("Profiles/Imported/content"), content); - } - } - } + return setup(); +} + +bool Launcher::MainDialog::setup() +{ + if (!setupGameSettings()) + return false; + + if (!setupGraphicsSettings()) + return false; + + // Now create the pages as they need the settings + createPages(); + + // Call this so we can exit on Ogre/SDL errors before mainwindow is shown + if (!mGraphicsPage->loadSettings()) + return false; + + loadSettings(); + return true; } -bool Launcher::MainDialog::setup() +bool Launcher::MainDialog::reloadSettings() { if (!setupLauncherSettings()) return false; @@ -310,21 +235,15 @@ bool Launcher::MainDialog::setup() if (!setupGraphicsSettings()) return false; - // Check if we need to show the importer - if (mLauncherSettings.value(QString("General/firstrun"), QString("true")) == QLatin1String("true")) - { - if (!showFirstRunDialog()) - return false; - } + if (!mSettingsPage->loadSettings()) + return false; - // Now create the pages as they need the settings - createPages(); + if (!mDataFilesPage->loadSettings()) + return false; - // Call this so we can exit on Ogre/SDL errors before mainwindow is shown if (!mGraphicsPage->loadSettings()) return false; - loadSettings(); return true; } @@ -334,24 +253,24 @@ void Launcher::MainDialog::changePage(QListWidgetItem *current, QListWidgetItem current = previous; int currentIndex = iconWidget->row(current); - int previousIndex = iconWidget->row(previous); +// int previousIndex = iconWidget->row(previous); pagesWidget->setCurrentIndex(currentIndex); - DataFilesPage *previousPage = dynamic_cast(pagesWidget->widget(previousIndex)); - DataFilesPage *currentPage = dynamic_cast(pagesWidget->widget(currentIndex)); + // DataFilesPage *previousPage = dynamic_cast(pagesWidget->widget(previousIndex)); + // DataFilesPage *currentPage = dynamic_cast(pagesWidget->widget(currentIndex)); - //special call to update/save data files page list view when it's displayed/hidden. - if (previousPage) - { - if (previousPage->objectName() == "DataFilesPage") - previousPage->saveSettings(); - } - else if (currentPage) - { - if (currentPage->objectName() == "DataFilesPage") - currentPage->loadSettings(); - } + // //special call to update/save data files page list view when it's displayed/hidden. + // if (previousPage) + // { + // if (previousPage->objectName() == "DataFilesPage") + // previousPage->saveSettings(); + // } + // else if (currentPage) + // { + // if (currentPage->objectName() == "DataFilesPage") + // currentPage->loadSettings(); + // } } bool Launcher::MainDialog::setupLauncherSettings() @@ -373,10 +292,10 @@ bool Launcher::MainDialog::setupLauncherSettings() msgBox.setWindowTitle(tr("Error opening OpenMW configuration file")); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(QObject::tr("
Could not open %0 for reading

\ - Please make sure you have the right permissions \ - and try again.
").arg(file.fileName())); - msgBox.exec(); + msgBox.setText(tr("
Could not open %0 for reading

\ + Please make sure you have the right permissions \ + and try again.
").arg(file.fileName())); + msgBox.exec(); return false; } QTextStream stream(&file); @@ -390,78 +309,6 @@ bool Launcher::MainDialog::setupLauncherSettings() return true; } -#ifndef WIN32 -bool Launcher::expansions(Launcher::UnshieldThread& cd) -{ - if(cd.BloodmoonDone()) - { - cd.Done(); - return false; - } - - QMessageBox expansionsBox; - expansionsBox.setText(QObject::tr("
Would you like to install expansions now ? (make sure you have the disc)
\ - If you want to install both Bloodmoon and Tribunal, you have to install Tribunal first.
")); - - QAbstractButton* tribunalButton = NULL; - if(!cd.TribunalDone()) - tribunalButton = expansionsBox.addButton(QObject::tr("&Tribunal"), QMessageBox::ActionRole); - - QAbstractButton* bloodmoonButton = expansionsBox.addButton(QObject::tr("&Bloodmoon"), QMessageBox::ActionRole); - QAbstractButton* noneButton = expansionsBox.addButton(QObject::tr("&None"), QMessageBox::ActionRole); - - expansionsBox.exec(); - - if(expansionsBox.clickedButton() == noneButton) - { - cd.Done(); - return false; - } - else if(expansionsBox.clickedButton() == tribunalButton) - { - - TextSlotMsgBox cdbox; - cdbox.setStandardButtons(QMessageBox::Cancel); - - QObject::connect(&cd,SIGNAL(signalGUI(const QString&)), &cdbox, SLOT(setTextSlot(const QString&))); - QObject::connect(&cd,SIGNAL(close()), &cdbox, SLOT(reject())); - - cd.SetTribunalPath( - QFileDialog::getOpenFileName( - NULL, - QObject::tr("Select data1.hdr from Tribunal Installation CD (Tribunal/data1.hdr on GOTY CDs)"), - QDir::currentPath(), - QString(QObject::tr("Installshield hdr file (*.hdr)"))).toUtf8().constData()); - - cd.start(); - cdbox.exec(); - } - else if(expansionsBox.clickedButton() == bloodmoonButton) - { - - TextSlotMsgBox cdbox; - cdbox.setStandardButtons(QMessageBox::Cancel); - - QObject::connect(&cd,SIGNAL(signalGUI(const QString&)), &cdbox, SLOT(setTextSlot(const QString&))); - QObject::connect(&cd,SIGNAL(close()), &cdbox, SLOT(reject())); - - cd.SetBloodmoonPath( - QFileDialog::getOpenFileName( - NULL, - QObject::tr("Select data1.hdr from Bloodmoon Installation CD (Bloodmoon/data1.hdr on GOTY CDs)"), - QDir::currentPath(), - QString(QObject::tr("Installshield hdr file (*.hdr)"))).toUtf8().constData()); - - cd.start(); - cdbox.exec(); - } - - - - return true; -} -#endif // WIN32 - bool Launcher::MainDialog::setupGameSettings() { QString userPath = QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str()); @@ -480,7 +327,7 @@ bool Launcher::MainDialog::setupGameSettings() msgBox.setWindowTitle(tr("Error opening OpenMW configuration file")); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(QObject::tr("
Could not open %0 for reading

\ + msgBox.setText(tr("
Could not open %0 for reading

\ Please make sure you have the right permissions \ and try again.
").arg(file.fileName())); msgBox.exec(); @@ -508,7 +355,7 @@ bool Launcher::MainDialog::setupGameSettings() msgBox.setWindowTitle(tr("Error opening OpenMW configuration file")); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(QObject::tr("
Could not open %0 for reading

\ + msgBox.setText(tr("
Could not open %0 for reading

\ Please make sure you have the right permissions \ and try again.
").arg(file.fileName())); msgBox.exec(); @@ -540,72 +387,22 @@ bool Launcher::MainDialog::setupGameSettings() msgBox.setWindowTitle(tr("Error detecting Morrowind installation")); msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::Cancel); - msgBox.setText(QObject::tr("
Could not find the Data Files location

\ - The directory containing the data files was not found.

\ - Press \"Browse...\" to specify the location manually.
")); + msgBox.setText(tr("
Could not find the Data Files location

\ + The directory containing the data files was not found.")); - QAbstractButton *dirSelectButton = - msgBox.addButton(QObject::tr("Browse to &Install..."), QMessageBox::ActionRole); + QAbstractButton *wizardButton = + msgBox.addButton(tr("Run &Installation Wizard..."), QMessageBox::ActionRole); - #ifndef WIN32 - QAbstractButton *cdSelectButton = - msgBox.addButton(QObject::tr("Browse to &CD..."), QMessageBox::ActionRole); - #endif + msgBox.exec(); - - msgBox.exec(); - - QString selectedFile; - if (msgBox.clickedButton() == dirSelectButton) { - selectedFile = QFileDialog::getOpenFileName( - NULL, - QObject::tr("Select master file"), - QDir::currentPath(), - QString(tr("Morrowind master file (*.esm)"))); - } - #ifndef WIN32 - else if(msgBox.clickedButton() == cdSelectButton) { - UnshieldThread cd; - - { - TextSlotMsgBox cdbox; - cdbox.setStandardButtons(QMessageBox::Cancel); - - QObject::connect(&cd,SIGNAL(signalGUI(const QString&)), &cdbox, SLOT(setTextSlot(const QString&))); - QObject::connect(&cd,SIGNAL(close()), &cdbox, SLOT(reject())); - - cd.SetMorrowindPath( - QFileDialog::getOpenFileName( - NULL, - QObject::tr("Select data1.hdr from Morrowind Installation CD"), - QDir::currentPath(), - QString(tr("Installshield hdr file (*.hdr)"))).toUtf8().constData()); - - cd.SetOutputPath( - QFileDialog::getExistingDirectory( - NULL, - QObject::tr("Select where to extract files to"), - QDir::currentPath(), - QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks).toUtf8().constData()); - - cd.start(); - cdbox.exec(); + if (msgBox.clickedButton() == wizardButton) + { + if (!mWizardInvoker->startProcess(QLatin1String("openmw-wizard"), false)) { + return false; + } else { + return true; } - - while(expansions(cd)); - - selectedFile = QString::fromUtf8(cd.GetMWEsmPath().c_str()); } - #endif // WIN32 - - if (selectedFile.isEmpty()) - return false; // Cancel was clicked; - - QFileInfo info(selectedFile); - - // Add the new dir to the settings file and to the data dir container - mGameSettings.setMultiValue(QString("data"), info.absolutePath()); - mGameSettings.addDataDir(info.absolutePath()); } return true; @@ -626,7 +423,7 @@ bool Launcher::MainDialog::setupGraphicsSettings() msgBox.setWindowTitle(tr("Error reading OpenMW configuration file")); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(QObject::tr("
Could not find settings-default.cfg

\ + msgBox.setText(tr("
Could not find settings-default.cfg

\ The problem may be due to an incomplete installation of OpenMW.
\ Reinstalling OpenMW may resolve the problem.")); msgBox.exec(); @@ -648,7 +445,7 @@ bool Launcher::MainDialog::setupGraphicsSettings() msgBox.setWindowTitle(tr("Error opening OpenMW configuration file")); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(QObject::tr("
Could not open %0 for reading

\ + msgBox.setText(tr("
Could not open %0 for reading

\ Please make sure you have the right permissions \ and try again.
").arg(file.fileName())); msgBox.exec(); @@ -699,8 +496,9 @@ bool Launcher::MainDialog::writeSettings() { // Now write all config files saveSettings(); - mGraphicsPage->saveSettings(); mDataFilesPage->saveSettings(); + mGraphicsPage->saveSettings(); + mSettingsPage->saveSettings(); QString userPath = QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str()); QDir dir(userPath); @@ -714,8 +512,8 @@ bool Launcher::MainDialog::writeSettings() msgBox.setText(tr("
Could not create %0

\ Please make sure you have the right permissions \ and try again.
").arg(userPath)); - msgBox.exec(); - return false; + msgBox.exec(); + return false; } } @@ -731,8 +529,8 @@ bool Launcher::MainDialog::writeSettings() msgBox.setText(tr("
Could not open or create %0 for writing

\ Please make sure you have the right permissions \ and try again.
").arg(file.fileName())); - msgBox.exec(); - return false; + msgBox.exec(); + return false; } QTextStream stream(&file); @@ -753,8 +551,8 @@ bool Launcher::MainDialog::writeSettings() msgBox.setText(tr("
Could not open or create %0 for writing

\ Please make sure you have the right permissions \ and try again.
").arg(file.fileName())); - msgBox.exec(); - return false; + msgBox.exec(); + return false; } stream.setDevice(&file); @@ -775,8 +573,8 @@ bool Launcher::MainDialog::writeSettings() msgBox.setText(tr("
Could not open or create %0 for writing

\ Please make sure you have the right permissions \ and try again.
").arg(file.fileName())); - msgBox.exec(); - return false; + msgBox.exec(); + return false; } stream.setDevice(&file); @@ -794,122 +592,41 @@ void Launcher::MainDialog::closeEvent(QCloseEvent *event) event->accept(); } +void Launcher::MainDialog::wizardStarted() +{ + hide(); +} + +void Launcher::MainDialog::wizardFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + if (exitCode != 0 || exitStatus == QProcess::CrashExit) + return qApp->quit(); + + // HACK: Ensure the pages are created, else segfault + setup(); + + if (reloadSettings()) + show(); +} + void Launcher::MainDialog::play() { - if (!writeSettings()) { - qApp->quit(); + if (!writeSettings()) + return qApp->quit(); + + if (!mGameSettings.hasMaster()) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("No game file selected")); + msgBox.setIcon(QMessageBox::Warning); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("
You do not have a game file selected.

\ + OpenMW will not start without a game file selected.
")); + msgBox.exec(); return; } - if(!mGameSettings.hasMaster()) { - QMessageBox msgBox; - msgBox.setWindowTitle(tr("No game file selected")); - msgBox.setIcon(QMessageBox::Warning); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
You do not have a game file selected.

\ - OpenMW will not start without a game file selected.
")); - msgBox.exec(); - return; - } - // Launch the game detached - startProgram(QString("openmw"), true); - qApp->quit(); -} - -bool Launcher::MainDialog::startProgram(const QString &name, const QStringList &arguments, bool detached) -{ - QString path = name; -#ifdef Q_OS_WIN - path.append(QString(".exe")); -#elif defined(Q_OS_MAC) - QDir dir(QCoreApplication::applicationDirPath()); - path = dir.absoluteFilePath(name); -#else - path.prepend(QString("./")); -#endif - - QFile file(path); - - QProcess process; - QFileInfo info(file); - - if (!file.exists()) { - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error starting executable")); - msgBox.setIcon(QMessageBox::Warning); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not find %1

\ - The application is not found.
\ - Please make sure OpenMW is installed correctly and try again.
").arg(info.fileName())); - msgBox.exec(); - - return false; - } - - if (!info.isExecutable()) { - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error starting executable")); - msgBox.setIcon(QMessageBox::Warning); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not start %1

\ - The application is not executable.
\ - Please make sure you have the right permissions and try again.
").arg(info.fileName())); - msgBox.exec(); - - return false; - } - - // Start the executable - if (detached) { - if (!process.startDetached(path, arguments)) { - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error starting executable")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not start %1

\ - An error occurred while starting %1.

\ - Press \"Show Details...\" for more information.
").arg(info.fileName())); - msgBox.setDetailedText(process.errorString()); - msgBox.exec(); - - return false; - } - } else { - process.start(path, arguments); - if (!process.waitForFinished()) { - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error starting executable")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not start %1

\ - An error occurred while starting %1.

\ - Press \"Show Details...\" for more information.
").arg(info.fileName())); - msgBox.setDetailedText(process.errorString()); - msgBox.exec(); - - return false; - } - - if (process.exitCode() != 0 || process.exitStatus() == QProcess::CrashExit) { - QString error(process.readAllStandardError()); - error.append(tr("\nArguments:\n")); - error.append(arguments.join(" ")); - - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error running executable")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Executable %1 returned an error

\ - An error occurred while running %1.

\ - Press \"Show Details...\" for more information.
").arg(info.fileName())); - msgBox.setDetailedText(error); - msgBox.exec(); - - return false; - } - } - - return true; - + + if (mGameInvoker->startProcess(QLatin1String("openmw"), true)) + return qApp->quit(); } diff --git a/apps/launcher/maindialog.hpp b/apps/launcher/maindialog.hpp index 5b8e4908e..0708f7002 100644 --- a/apps/launcher/maindialog.hpp +++ b/apps/launcher/maindialog.hpp @@ -2,16 +2,26 @@ #define MAINDIALOG_H #include +#include + #ifndef Q_MOC_RUN #include #endif -#include "settings/gamesettings.hpp" + +#include + +#include +#include + #include "settings/graphicssettings.hpp" -#include "settings/launchersettings.hpp" #include "ui_mainwindow.h" class QListWidgetItem; +class QStackedWidget; +class QStringList; +class QStringListModel; +class QString; namespace Launcher { @@ -19,6 +29,7 @@ namespace Launcher class GraphicsPage; class DataFilesPage; class UnshieldThread; + class SettingsPage; #ifndef WIN32 bool expansions(Launcher::UnshieldThread& cd); @@ -30,13 +41,22 @@ namespace Launcher public: explicit MainDialog(QWidget *parent = 0); + ~MainDialog(); + bool setup(); bool showFirstRunDialog(); + bool reloadSettings(); + bool writeSettings(); + public slots: void changePage(QListWidgetItem *current, QListWidgetItem *previous); void play(); + private slots: + void wizardStarted(); + void wizardFinished(int exitCode, QProcess::ExitStatus exitStatus); + private: void createIcons(); void createPages(); @@ -47,7 +67,6 @@ namespace Launcher void loadSettings(); void saveSettings(); - bool writeSettings(); inline bool startProgram(const QString &name, bool detached = false) { return startProgram(name, QStringList(), detached); } bool startProgram(const QString &name, const QStringList &arguments, bool detached = false); @@ -57,12 +76,16 @@ namespace Launcher PlayPage *mPlayPage; GraphicsPage *mGraphicsPage; DataFilesPage *mDataFilesPage; + SettingsPage *mSettingsPage; + + Process::ProcessInvoker *mGameInvoker; + Process::ProcessInvoker *mWizardInvoker; Files::ConfigurationManager mCfgMgr; - GameSettings mGameSettings; + Config::GameSettings mGameSettings; GraphicsSettings mGraphicsSettings; - LauncherSettings mLauncherSettings; + Config::LauncherSettings mLauncherSettings; }; } diff --git a/apps/launcher/settings/graphicssettings.hpp b/apps/launcher/settings/graphicssettings.hpp index 6f7c13547..a52e0aa84 100644 --- a/apps/launcher/settings/graphicssettings.hpp +++ b/apps/launcher/settings/graphicssettings.hpp @@ -1,11 +1,11 @@ #ifndef GRAPHICSSETTINGS_HPP #define GRAPHICSSETTINGS_HPP -#include "settingsbase.hpp" +#include namespace Launcher { - class GraphicsSettings : public SettingsBase > + class GraphicsSettings : public Config::SettingsBase > { public: GraphicsSettings(); diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp new file mode 100644 index 000000000..5422e7957 --- /dev/null +++ b/apps/launcher/settingspage.cpp @@ -0,0 +1,269 @@ +#include "settingspage.hpp" + +#include +#include +#include +#include + +#include + +#include +#include + +#include "utils/textinputdialog.hpp" + +using namespace Process; + +Launcher::SettingsPage::SettingsPage(Files::ConfigurationManager &cfg, + Config::GameSettings &gameSettings, + Config::LauncherSettings &launcherSettings, MainDialog *parent) + : mCfgMgr(cfg) + , mGameSettings(gameSettings) + , mLauncherSettings(launcherSettings) + , QWidget(parent) + , mMain(parent) +{ + setupUi(this); + + QStringList languages; + languages << QLatin1String("English") + << QLatin1String("French") + << QLatin1String("German") + << QLatin1String("Italian") + << QLatin1String("Polish") + << QLatin1String("Russian") + << QLatin1String("Spanish"); + + languageComboBox->addItems(languages); + + mWizardInvoker = new ProcessInvoker(); + mImporterInvoker = new ProcessInvoker(); + + connect(mWizardInvoker->getProcess(), SIGNAL(started()), + this, SLOT(wizardStarted())); + + connect(mWizardInvoker->getProcess(), SIGNAL(finished(int,QProcess::ExitStatus)), + this, SLOT(wizardFinished(int,QProcess::ExitStatus))); + + connect(mImporterInvoker->getProcess(), SIGNAL(started()), + this, SLOT(importerStarted())); + + connect(mImporterInvoker->getProcess(), SIGNAL(finished(int,QProcess::ExitStatus)), + this, SLOT(importerFinished(int,QProcess::ExitStatus))); + + mProfileDialog = new TextInputDialog(tr("New Profile"), tr("Profile name:"), this); + + connect(mProfileDialog->lineEdit(), SIGNAL(textChanged(QString)), + this, SLOT(updateOkButton(QString))); + + // Detect Morrowind configuration files + QStringList iniPaths; + + foreach (const QString &path, mGameSettings.getDataDirs()) { + QDir dir(path); + dir.setPath(dir.canonicalPath()); // Resolve symlinks + + if (dir.exists(QString("Morrowind.ini"))) + iniPaths.append(dir.absoluteFilePath(QString("Morrowind.ini"))); + else + { + if (!dir.cdUp()) + continue; // Cannot move from Data Files + + if (dir.exists(QString("Morrowind.ini"))) + iniPaths.append(dir.absoluteFilePath(QString("Morrowind.ini"))); + } + } + + if (!iniPaths.isEmpty()) { + settingsComboBox->addItems(iniPaths); + importerButton->setEnabled(true); + } else { + importerButton->setEnabled(false); + } + + loadSettings(); +} + +Launcher::SettingsPage::~SettingsPage() +{ + delete mWizardInvoker; + delete mImporterInvoker; +} + +void Launcher::SettingsPage::on_wizardButton_clicked() +{ + saveSettings(); + + if (!mWizardInvoker->startProcess(QLatin1String("openmw-wizard"), false)) + return; +} + +void Launcher::SettingsPage::on_importerButton_clicked() +{ + saveSettings(); + + // Create the file if it doesn't already exist, else the importer will fail + QString path(QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str())); + path.append(QLatin1String("openmw.cfg")); + QFile file(path); + + if (!file.exists()) { + if (!file.open(QIODevice::ReadWrite)) { + // File cannot be created + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error writing OpenMW configuration file")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("

Could not open or create %1 for writing

\ +

Please make sure you have the right permissions \ + and try again.

").arg(file.fileName())); + msgBox.exec(); + return; + } + + file.close(); + } + + // Construct the arguments to run the importer + QStringList arguments; + + if (addonsCheckBox->isChecked()) + arguments.append(QString("--game-files")); + + arguments.append(QString("--encoding")); + arguments.append(mGameSettings.value(QString("encoding"), QString("win1252"))); + arguments.append(QString("--ini")); + arguments.append(settingsComboBox->currentText()); + arguments.append(QString("--cfg")); + arguments.append(path); + + qDebug() << "arguments " << arguments; + + if (!mImporterInvoker->startProcess(QLatin1String("mwiniimport"), arguments, false)) + return; +} + +void Launcher::SettingsPage::on_browseButton_clicked() +{ + QString iniFile = QFileDialog::getOpenFileName( + this, + QObject::tr("Select configuration file"), + QDir::currentPath(), + QString(tr("Morrowind configuration file (*.ini)"))); + + + if (iniFile.isEmpty()) + return; + + QFileInfo info(iniFile); + + if (!info.exists() || !info.isReadable()) + return; + + const QString path(QDir::toNativeSeparators(info.absoluteFilePath())); + + if (settingsComboBox->findText(path) == -1) { + settingsComboBox->addItem(path); + settingsComboBox->setCurrentIndex(settingsComboBox->findText(path)); + importerButton->setEnabled(true); + } +} + +void Launcher::SettingsPage::wizardStarted() +{ + mMain->hide(); // Hide the launcher + + wizardButton->setEnabled(false); +} + +void Launcher::SettingsPage::wizardFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + if (exitCode != 0 || exitStatus == QProcess::CrashExit) + return qApp->quit(); + + mMain->reloadSettings(); + wizardButton->setEnabled(true); + + mMain->show(); // Show the launcher again +} + +void Launcher::SettingsPage::importerStarted() +{ + importerButton->setEnabled(false); +} + +void Launcher::SettingsPage::importerFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + if (exitCode != 0 || exitStatus == QProcess::CrashExit) + return; + + // Re-read the settings in their current state + mMain->reloadSettings(); + + // Import selected data files from openmw.cfg + if (addonsCheckBox->isChecked()) + { + if (mProfileDialog->exec() == QDialog::Accepted) + { + const QString profile(mProfileDialog->lineEdit()->text()); + const QStringList files(mGameSettings.values(QLatin1String("content"))); + + qDebug() << "Profile " << profile << files; + + // Doesn't quite work right now + mLauncherSettings.setValue(QLatin1String("Profiles/currentprofile"), profile); + + foreach (const QString &file, files) { + mLauncherSettings.setMultiValue(QLatin1String("Profiles/") + profile + QLatin1String("/content"), file); + } + + mGameSettings.remove(QLatin1String("content")); + } + } + + mMain->reloadSettings(); + importerButton->setEnabled(true); +} + +void Launcher::SettingsPage::updateOkButton(const QString &text) +{ + // We do this here because we need to access the profiles + if (text.isEmpty()) { + mProfileDialog->setOkButtonEnabled(false); + return; + } + + const QStringList profiles(mLauncherSettings.subKeys(QString("Profiles/"))); + + (profiles.contains(text)) + ? mProfileDialog->setOkButtonEnabled(false) + : mProfileDialog->setOkButtonEnabled(true); +} + +void Launcher::SettingsPage::saveSettings() +{ + QString language(languageComboBox->currentText()); + + mLauncherSettings.setValue(QLatin1String("Settings/language"), language); + + if (language == QLatin1String("Polish")) { + mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1250")); + } else if (language == QLatin1String("Russian")) { + mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1251")); + } else { + mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1252")); + } +} + +bool Launcher::SettingsPage::loadSettings() +{ + QString language(mLauncherSettings.value(QLatin1String("Settings/language"))); + + int index = languageComboBox->findText(language); + + if (index != -1) + languageComboBox->setCurrentIndex(index); + + return true; +} diff --git a/apps/launcher/settingspage.hpp b/apps/launcher/settingspage.hpp new file mode 100644 index 000000000..124c80600 --- /dev/null +++ b/apps/launcher/settingspage.hpp @@ -0,0 +1,64 @@ +#ifndef SETTINGSPAGE_HPP +#define SETTINGSPAGE_HPP + +#include +#include + +#include + +#include "ui_settingspage.h" + +#include "maindialog.hpp" + +namespace Files { struct ConfigurationManager; } +namespace Config { class GameSettings; + class LauncherSettings; } + +namespace Launcher +{ + class TextInputDialog; + + class SettingsPage : public QWidget, private Ui::SettingsPage + { + Q_OBJECT + + public: + SettingsPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings, + Config::LauncherSettings &launcherSettings, MainDialog *parent = 0); + ~SettingsPage(); + + void saveSettings(); + bool loadSettings(); + + private slots: + + void on_wizardButton_clicked(); + void on_importerButton_clicked(); + void on_browseButton_clicked(); + + void wizardStarted(); + void wizardFinished(int exitCode, QProcess::ExitStatus exitStatus); + + void importerStarted(); + void importerFinished(int exitCode, QProcess::ExitStatus exitStatus); + + void updateOkButton(const QString &text); + + private: + + Process::ProcessInvoker *mWizardInvoker; + Process::ProcessInvoker *mImporterInvoker; + + Files::ConfigurationManager &mCfgMgr; + + Config::GameSettings &mGameSettings; + Config::LauncherSettings &mLauncherSettings; + + MainDialog *mMain; + TextInputDialog *mProfileDialog; + + + }; +} + +#endif // SETTINGSPAGE_HPP diff --git a/apps/launcher/unshieldthread.cpp b/apps/launcher/unshieldthread.cpp deleted file mode 100644 index 3d0f2e5da..000000000 --- a/apps/launcher/unshieldthread.cpp +++ /dev/null @@ -1,521 +0,0 @@ -#include "unshieldthread.hpp" - -#include -#include - -namespace bfs = boost::filesystem; - -namespace -{ - static bool make_sure_directory_exists(bfs::path directory) - { - - if(!bfs::exists(directory)) - { - bfs::create_directories(directory); - } - - return bfs::exists(directory); - } - - void fill_path(bfs::path& path, const std::string& name) - { - size_t start = 0; - - size_t i; - for(i = 0; i < name.length(); i++) - { - switch(name[i]) - { - case '\\': - path /= name.substr(start, i-start); - start = i+1; - break; - } - } - - path /= name.substr(start, i-start); - } - - std::string get_setting(const std::string& category, const std::string& setting, const std::string& inx) - { - size_t start = inx.find(category); - start = inx.find(setting, start) + setting.length() + 3; - - size_t end = inx.find("!", start); - - return inx.substr(start, end-start); - } - - std::string read_to_string(const bfs::path& path) - { - bfs::ifstream strstream(path, std::ios::in | std::ios::binary); - std::string str; - - strstream.seekg(0, std::ios::end); - str.resize(strstream.tellg()); - strstream.seekg(0, std::ios::beg); - strstream.read(&str[0], str.size()); - strstream.close(); - - return str; - } - - void add_setting(const std::string& category, const std::string& setting, const std::string& val, std::string& ini) - { - size_t loc; - loc = ini.find("[" + category + "]"); - - // If category is not found, create it - if(loc == std::string::npos) - { - loc = ini.size() + 2; - ini += ("\r\n[" + category + "]\r\n"); - } - - loc += category.length() +2 +2; - ini.insert(loc, setting + "=" + val + "\r\n"); - } - - #define FIX(setting) add_setting(category, setting, get_setting(category, setting, inx), ini) - - void bloodmoon_fix_ini(std::string& ini, const bfs::path inxPath) - { - std::string inx = read_to_string(inxPath); - - // Remove this one setting (the only one actually changed by bloodmoon, as opposed to just adding new ones) - size_t start = ini.find("[Weather Blight]"); - start = ini.find("Ambient Loop Sound ID", start); - size_t end = ini.find("\r\n", start) +2; - ini.erase(start, end-start); - - std::string category; - - category = "General"; - { - FIX("Werewolf FOV"); - } - category = "Moons"; - { - FIX("Script Color"); - } - category = "Weather"; - { - FIX("Snow Ripples"); - FIX("Snow Ripple Radius"); - FIX("Snow Ripples Per Flake"); - FIX("Snow Ripple Scale"); - FIX("Snow Ripple Speed"); - FIX("Snow Gravity Scale"); - FIX("Snow High Kill"); - FIX("Snow Low Kill"); - } - category = "Weather Blight"; - { - FIX("Ambient Loop Sound ID"); - } - category = "Weather Snow"; - { - FIX("Sky Sunrise Color"); - FIX("Sky Day Color"); - FIX("Sky Sunset Color"); - FIX("Sky Night Color"); - FIX("Fog Sunrise Color"); - FIX("Fog Day Color"); - FIX("Fog Sunset Color"); - FIX("Fog Night Color"); - FIX("Ambient Sunrise Color"); - FIX("Ambient Day Color"); - FIX("Ambient Sunset Color"); - FIX("Ambient Night Color"); - FIX("Sun Sunrise Color"); - FIX("Sun Day Color"); - FIX("Sun Sunset Color"); - FIX("Sun Night Color"); - FIX("Sun Disc Sunset Color"); - FIX("Transition Delta"); - FIX("Land Fog Day Depth"); - FIX("Land Fog Night Depth"); - FIX("Clouds Maximum Percent"); - FIX("Wind Speed"); - FIX("Cloud Speed"); - FIX("Glare View"); - FIX("Cloud Texture"); - FIX("Ambient Loop Sound ID"); - FIX("Snow Threshold"); - FIX("Snow Diameter"); - FIX("Snow Height Min"); - FIX("Snow Height Max"); - FIX("Snow Entrance Speed"); - FIX("Max Snowflakes"); - } - category = "Weather Blizzard"; - { - FIX("Sky Sunrise Color"); - FIX("Sky Day Color"); - FIX("Sky Sunset Color"); - FIX("Sky Night Color"); - FIX("Fog Sunrise Color"); - FIX("Fog Day Color"); - FIX("Fog Sunset Color"); - FIX("Fog Night Color"); - FIX("Ambient Sunrise Color"); - FIX("Ambient Day Color"); - FIX("Ambient Sunset Color"); - FIX("Ambient Night Color"); - FIX("Sun Sunrise Color"); - FIX("Sun Day Color"); - FIX("Sun Sunset Color"); - FIX("Sun Night Color"); - FIX("Sun Disc Sunset Color"); - FIX("Transition Delta"); - FIX("Land Fog Day Depth"); - FIX("Land Fog Night Depth"); - FIX("Clouds Maximum Percent"); - FIX("Wind Speed"); - FIX("Cloud Speed"); - FIX("Glare View"); - FIX("Cloud Texture"); - FIX("Ambient Loop Sound ID"); - FIX("Storm Threshold"); - } - } - - - void fix_ini(const bfs::path& output_dir, bfs::path cdPath, bool tribunal, bool bloodmoon) - { - bfs::path ini_path = output_dir; - ini_path /= "Morrowind.ini"; - - std::string ini = read_to_string(ini_path.string()); - - if(tribunal) - { - add_setting("Game Files", "GameFile1", "Tribunal.esm", ini); - add_setting("Archives", "Archive 0", "Tribunal.bsa", ini); - } - if(bloodmoon) - { - bloodmoon_fix_ini(ini, cdPath / "setup.inx"); - add_setting("Game Files", "GameFile2", "Bloodmoon.esm", ini); - add_setting("Archives", "Archive 1", "Bloodmoon.bsa", ini); - } - - bfs::ofstream inistream((ini_path)); - inistream << ini; - inistream.close(); - } - - void installToPath(const bfs::path& from, const bfs::path& to, bool copy = false) - { - make_sure_directory_exists(to); - - for ( bfs::directory_iterator end, dir(from); dir != end; ++dir ) - { - if(bfs::is_directory(dir->path())) - installToPath(dir->path(), to / dir->path().filename(), copy); - else - { - if(copy) - { - bfs::path dest = to / dir->path().filename(); - if(bfs::exists(dest)) - bfs::remove_all(dest); - bfs::copy_file(dir->path(), dest); - } - else - bfs::rename(dir->path(), to / dir->path().filename()); - } - } - } - - bfs::path findFile(const bfs::path& in, std::string filename, bool recursive = true) - { - if(recursive) - { - for ( bfs::recursive_directory_iterator end, dir(in); dir != end; ++dir ) - { - if(Misc::StringUtils::ciEqual(dir->path().filename().string(), filename)) - return dir->path(); - } - } - else - { - for ( bfs::directory_iterator end, dir(in); dir != end; ++dir ) - { - if(Misc::StringUtils::ciEqual(dir->path().filename().string(), filename)) - return dir->path(); - } - } - - return ""; - } - - bool contains(const bfs::path& in, std::string filename) - { - for(bfs::directory_iterator end, dir(in); dir != end; ++dir) - { - if(Misc::StringUtils::ciEqual(dir->path().filename().string(), filename)) - return true; - } - - return false; - } - - time_t getTime(const char* time) - { - struct tm tms; - memset(&tms, 0, sizeof(struct tm)); - strptime(time, "%d %B %Y", &tms); - return mktime(&tms); - } - - // Some cds have cab files which have the Data Files subfolders outside the Data Files folder - void install_dfiles_outside(const bfs::path& from, const bfs::path& dFiles) - { - bfs::path fonts = findFile(from, "fonts", false); - if(fonts.string() != "") - installToPath(fonts, dFiles / "Fonts"); - - bfs::path music = findFile(from, "music", false); - if(music.string() != "") - installToPath(music, dFiles / "Music"); - - bfs::path sound = findFile(from, "sound", false); - if(sound.string() != "") - installToPath(sound, dFiles / "Sound"); - - bfs::path splash = findFile(from, "splash", false); - if(splash.string() != "") - installToPath(splash, dFiles / "Splash"); - } - -} - -bool Launcher::UnshieldThread::SetMorrowindPath(const std::string& path) -{ - mMorrowindPath = path; - return true; -} - -bool Launcher::UnshieldThread::SetTribunalPath(const std::string& path) -{ - mTribunalPath = path; - return true; -} - -bool Launcher::UnshieldThread::SetBloodmoonPath(const std::string& path) -{ - mBloodmoonPath = path; - return true; -} - -void Launcher::UnshieldThread::SetOutputPath(const std::string& path) -{ - mOutputPath = path; -} - -bool Launcher::UnshieldThread::extract_file(Unshield* unshield, bfs::path output_dir, const char* prefix, int index) -{ - bool success; - bfs::path dirname; - bfs::path filename; - int directory = unshield_file_directory(unshield, index); - - dirname = output_dir; - - if (prefix && prefix[0]) - dirname /= prefix; - - if (directory >= 0) - { - const char* tmp = unshield_directory_name(unshield, directory); - if (tmp && tmp[0]) - fill_path(dirname, tmp); - } - - make_sure_directory_exists(dirname); - - filename = dirname; - filename /= unshield_file_name(unshield, index); - - emit signalGUI(QString("Extracting: ") + QString(filename.c_str())); - - success = unshield_file_save(unshield, index, filename.c_str()); - - if (!success) - bfs::remove(filename); - - return success; -} - -void Launcher::UnshieldThread::extract_cab(const bfs::path& cab, const bfs::path& output_dir, bool extract_ini) -{ - Unshield * unshield; - unshield = unshield_open(cab.c_str()); - - int i; - for (i = 0; i < unshield_file_group_count(unshield); i++) - { - UnshieldFileGroup* file_group = unshield_file_group_get(unshield, i); - - for (size_t j = file_group->first_file; j <= file_group->last_file; j++) - { - if (unshield_file_is_valid(unshield, j)) - extract_file(unshield, output_dir, file_group->name, j); - } - } - unshield_close(unshield); -} - - -bool Launcher::UnshieldThread::extract() -{ - bfs::path outputDataFilesDir = mOutputPath; - outputDataFilesDir /= "Data Files"; - bfs::path extractPath = mOutputPath; - extractPath /= "extract-temp"; - - if(!mMorrowindDone && mMorrowindPath.string().length() > 0) - { - mMorrowindDone = true; - - bfs::path mwExtractPath = extractPath / "morrowind"; - extract_cab(mMorrowindPath, mwExtractPath, true); - - bfs::path dFilesDir = findFile(mwExtractPath, "morrowind.esm").parent_path(); - - installToPath(dFilesDir, outputDataFilesDir); - - install_dfiles_outside(mwExtractPath, outputDataFilesDir); - - // Videos are often kept uncompressed on the cd - bfs::path videosPath = findFile(mMorrowindPath.parent_path(), "video", false); - if(videosPath.string() != "") - { - emit signalGUI(QString("Installing Videos...")); - installToPath(videosPath, outputDataFilesDir / "Video", true); - } - - bfs::path cdDFiles = findFile(mMorrowindPath.parent_path(), "data files", false); - if(cdDFiles.string() != "") - { - emit signalGUI(QString("Installing Uncompressed Data files from CD...")); - installToPath(cdDFiles, outputDataFilesDir, true); - } - - - bfs::rename(findFile(mwExtractPath, "morrowind.ini"), outputDataFilesDir / "Morrowind.ini"); - - mTribunalDone = contains(outputDataFilesDir, "tribunal.esm"); - mBloodmoonDone = contains(outputDataFilesDir, "bloodmoon.esm"); - - } - - else if(!mTribunalDone && mTribunalPath.string().length() > 0) - { - mTribunalDone = true; - - bfs::path tbExtractPath = extractPath / "tribunal"; - extract_cab(mTribunalPath, tbExtractPath, true); - - bfs::path dFilesDir = findFile(tbExtractPath, "tribunal.esm").parent_path(); - - installToPath(dFilesDir, outputDataFilesDir); - - install_dfiles_outside(tbExtractPath, outputDataFilesDir); - - // Mt GOTY CD has Sounds in a seperate folder from the rest of the data files - bfs::path soundsPath = findFile(tbExtractPath, "sounds", false); - if(soundsPath.string() != "") - installToPath(soundsPath, outputDataFilesDir / "Sounds"); - - bfs::path cdDFiles = findFile(mTribunalPath.parent_path(), "data files", false); - if(cdDFiles.string() != "") - { - emit signalGUI(QString("Installing Uncompressed Data files from CD...")); - installToPath(cdDFiles, outputDataFilesDir, true); - } - - mBloodmoonDone = contains(outputDataFilesDir, "bloodmoon.esm"); - - fix_ini(outputDataFilesDir, bfs::path(mTribunalPath).parent_path(), mTribunalDone, mBloodmoonDone); - } - - else if(!mBloodmoonDone && mBloodmoonPath.string().length() > 0) - { - mBloodmoonDone = true; - - bfs::path bmExtractPath = extractPath / "bloodmoon"; - extract_cab(mBloodmoonPath, bmExtractPath, true); - - bfs::path dFilesDir = findFile(bmExtractPath, "bloodmoon.esm").parent_path(); - - installToPath(dFilesDir, outputDataFilesDir); - - install_dfiles_outside(bmExtractPath, outputDataFilesDir); - - // My GOTY CD contains a folder within cab files called Tribunal patch, - // which contains Tribunal.esm - bfs::path tbPatchPath = findFile(bmExtractPath, "tribunal.esm"); - if(tbPatchPath.string() != "") - bfs::rename(tbPatchPath, outputDataFilesDir / "Tribunal.esm"); - - bfs::path cdDFiles = findFile(mBloodmoonPath.parent_path(), "data files", false); - if(cdDFiles.string() != "") - { - emit signalGUI(QString("Installing Uncompressed Data files from CD...")); - installToPath(cdDFiles, outputDataFilesDir, true); - } - - fix_ini(outputDataFilesDir, bfs::path(mBloodmoonPath).parent_path(), false, mBloodmoonDone); - } - - - return true; -} - -void Launcher::UnshieldThread::Done() -{ - // Get rid of unnecessary files - bfs::remove_all(mOutputPath / "extract-temp"); - - // Set modified time to release dates, to preserve load order - if(mMorrowindDone) - bfs::last_write_time(findFile(mOutputPath, "morrowind.esm"), getTime("1 May 2002")); - - if(mTribunalDone) - bfs::last_write_time(findFile(mOutputPath, "tribunal.esm"), getTime("6 November 2002")); - - if(mBloodmoonDone) - bfs::last_write_time(findFile(mOutputPath, "bloodmoon.esm"), getTime("3 June 2003")); -} - -std::string Launcher::UnshieldThread::GetMWEsmPath() -{ - return findFile(mOutputPath / "Data Files", "morrowind.esm").string(); -} - -bool Launcher::UnshieldThread::TribunalDone() -{ - return mTribunalDone; -} - -bool Launcher::UnshieldThread::BloodmoonDone() -{ - return mBloodmoonDone; -} - -void Launcher::UnshieldThread::run() -{ - extract(); - emit close(); -} - -Launcher::UnshieldThread::UnshieldThread() -{ - unshield_set_log_level(0); - mMorrowindDone = false; - mTribunalDone = false; - mBloodmoonDone = false; -} diff --git a/apps/launcher/unshieldthread.hpp b/apps/launcher/unshieldthread.hpp deleted file mode 100644 index de6a32b44..000000000 --- a/apps/launcher/unshieldthread.hpp +++ /dev/null @@ -1,58 +0,0 @@ -#ifndef UNSHIELD_THREAD_H -#define UNSHIELD_THREAD_H - -#include - -#include - -#include - -namespace Launcher -{ - class UnshieldThread : public QThread - { - Q_OBJECT - - public: - bool SetMorrowindPath(const std::string& path); - bool SetTribunalPath(const std::string& path); - bool SetBloodmoonPath(const std::string& path); - - void SetOutputPath(const std::string& path); - - bool extract(); - - bool TribunalDone(); - bool BloodmoonDone(); - - void Done(); - - std::string GetMWEsmPath(); - - UnshieldThread(); - - private: - - void extract_cab(const boost::filesystem::path& cab, const boost::filesystem::path& output_dir, bool extract_ini = false); - bool extract_file(Unshield* unshield, boost::filesystem::path output_dir, const char* prefix, int index); - - boost::filesystem::path mMorrowindPath; - boost::filesystem::path mTribunalPath; - boost::filesystem::path mBloodmoonPath; - - bool mMorrowindDone; - bool mTribunalDone; - bool mBloodmoonDone; - - boost::filesystem::path mOutputPath; - - - protected: - virtual void run(); - - signals: - void signalGUI(QString); - void close(); - }; -} -#endif diff --git a/apps/launcher/utils/checkablemessagebox.cpp b/apps/launcher/utils/checkablemessagebox.cpp deleted file mode 100644 index 2f775af57..000000000 --- a/apps/launcher/utils/checkablemessagebox.cpp +++ /dev/null @@ -1,258 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of Qt Creator. -** -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -****************************************************************************/ - -#include "checkablemessagebox.hpp" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -/*! - \class Utils::CheckableMessageBox - - \brief A messagebox suitable for questions with a - "Do not ask me again" checkbox. - - Emulates the QMessageBox API with - static conveniences. The message label can open external URLs. -*/ -Launcher::CheckableMessageBoxPrivate::CheckableMessageBoxPrivate(QDialog *q) - : clickedButton(0) -{ - QSizePolicy sizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); - - pixmapLabel = new QLabel(q); - sizePolicy.setHorizontalStretch(0); - sizePolicy.setVerticalStretch(0); - sizePolicy.setHeightForWidth(pixmapLabel->sizePolicy().hasHeightForWidth()); - pixmapLabel->setSizePolicy(sizePolicy); - pixmapLabel->setVisible(false); - - QSpacerItem *pixmapSpacer = - new QSpacerItem(0, 5, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding); - - messageLabel = new QLabel(q); - messageLabel->setMinimumSize(QSize(300, 0)); - messageLabel->setWordWrap(true); - messageLabel->setOpenExternalLinks(true); - messageLabel->setTextInteractionFlags(Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse); - - QSpacerItem *checkBoxRightSpacer = - new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum); - QSpacerItem *buttonSpacer = - new QSpacerItem(0, 1, QSizePolicy::Minimum, QSizePolicy::Minimum); - - checkBox = new QCheckBox(q); - checkBox->setText(Launcher::CheckableMessageBox::tr("Do not ask again")); - - buttonBox = new QDialogButtonBox(q); - buttonBox->setOrientation(Qt::Horizontal); - buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); - - QVBoxLayout *verticalLayout = new QVBoxLayout(); - verticalLayout->addWidget(pixmapLabel); - verticalLayout->addItem(pixmapSpacer); - - QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); - horizontalLayout_2->addLayout(verticalLayout); - horizontalLayout_2->addWidget(messageLabel); - - QHBoxLayout *horizontalLayout = new QHBoxLayout(); - horizontalLayout->addWidget(checkBox); - horizontalLayout->addItem(checkBoxRightSpacer); - - QVBoxLayout *verticalLayout_2 = new QVBoxLayout(q); - verticalLayout_2->addLayout(horizontalLayout_2); - verticalLayout_2->addLayout(horizontalLayout); - verticalLayout_2->addItem(buttonSpacer); - verticalLayout_2->addWidget(buttonBox); -} - -Launcher::CheckableMessageBox::CheckableMessageBox(QWidget *parent) : - QDialog(parent), - d(new Launcher::CheckableMessageBoxPrivate(this)) -{ - setModal(true); - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); - connect(d->buttonBox, SIGNAL(accepted()), SLOT(accept())); - connect(d->buttonBox, SIGNAL(rejected()), SLOT(reject())); - connect(d->buttonBox, SIGNAL(clicked(QAbstractButton*)), - SLOT(slotClicked(QAbstractButton*))); -} - -Launcher::CheckableMessageBox::~CheckableMessageBox() -{ - delete d; -} - -void Launcher::CheckableMessageBox::slotClicked(QAbstractButton *b) -{ - d->clickedButton = b; -} - -QAbstractButton *Launcher::CheckableMessageBox::clickedButton() const -{ - return d->clickedButton; -} - -QDialogButtonBox::StandardButton Launcher::CheckableMessageBox::clickedStandardButton() const -{ - if (d->clickedButton) - return d->buttonBox->standardButton(d->clickedButton); - return QDialogButtonBox::NoButton; -} - -QString Launcher::CheckableMessageBox::text() const -{ - return d->messageLabel->text(); -} - -void Launcher::CheckableMessageBox::setText(const QString &t) -{ - d->messageLabel->setText(t); -} - -QPixmap Launcher::CheckableMessageBox::iconPixmap() const -{ - if (const QPixmap *p = d->pixmapLabel->pixmap()) - return QPixmap(*p); - return QPixmap(); -} - -void Launcher::CheckableMessageBox::setIconPixmap(const QPixmap &p) -{ - d->pixmapLabel->setPixmap(p); - d->pixmapLabel->setVisible(!p.isNull()); -} - -bool Launcher::CheckableMessageBox::isChecked() const -{ - return d->checkBox->isChecked(); -} - -void Launcher::CheckableMessageBox::setChecked(bool s) -{ - d->checkBox->setChecked(s); -} - -QString Launcher::CheckableMessageBox::checkBoxText() const -{ - return d->checkBox->text(); -} - -void Launcher::CheckableMessageBox::setCheckBoxText(const QString &t) -{ - d->checkBox->setText(t); -} - -bool Launcher::CheckableMessageBox::isCheckBoxVisible() const -{ - return d->checkBox->isVisible(); -} - -void Launcher::CheckableMessageBox::setCheckBoxVisible(bool v) -{ - d->checkBox->setVisible(v); -} - -QDialogButtonBox::StandardButtons Launcher::CheckableMessageBox::standardButtons() const -{ - return d->buttonBox->standardButtons(); -} - -void Launcher::CheckableMessageBox::setStandardButtons(QDialogButtonBox::StandardButtons s) -{ - d->buttonBox->setStandardButtons(s); -} - -QPushButton *Launcher::CheckableMessageBox::button(QDialogButtonBox::StandardButton b) const -{ - return d->buttonBox->button(b); -} - -QPushButton *Launcher::CheckableMessageBox::addButton(const QString &text, QDialogButtonBox::ButtonRole role) -{ - return d->buttonBox->addButton(text, role); -} - -QDialogButtonBox::StandardButton Launcher::CheckableMessageBox::defaultButton() const -{ - foreach (QAbstractButton *b, d->buttonBox->buttons()) - if (QPushButton *pb = qobject_cast(b)) - if (pb->isDefault()) - return d->buttonBox->standardButton(pb); - return QDialogButtonBox::NoButton; -} - -void Launcher::CheckableMessageBox::setDefaultButton(QDialogButtonBox::StandardButton s) -{ - if (QPushButton *b = d->buttonBox->button(s)) { - b->setDefault(true); - b->setFocus(); - } -} - -QDialogButtonBox::StandardButton -Launcher::CheckableMessageBox::question(QWidget *parent, - const QString &title, - const QString &question, - const QString &checkBoxText, - bool *checkBoxSetting, - QDialogButtonBox::StandardButtons buttons, - QDialogButtonBox::StandardButton defaultButton) -{ - CheckableMessageBox mb(parent); - mb.setWindowTitle(title); - mb.setIconPixmap(QMessageBox::standardIcon(QMessageBox::Question)); - mb.setText(question); - mb.setCheckBoxText(checkBoxText); - mb.setChecked(*checkBoxSetting); - mb.setStandardButtons(buttons); - mb.setDefaultButton(defaultButton); - mb.exec(); - *checkBoxSetting = mb.isChecked(); - return mb.clickedStandardButton(); -} - -QMessageBox::StandardButton Launcher::CheckableMessageBox::dialogButtonBoxToMessageBoxButton(QDialogButtonBox::StandardButton db) -{ - return static_cast(int(db)); -} diff --git a/apps/launcher/utils/checkablemessagebox.hpp b/apps/launcher/utils/checkablemessagebox.hpp deleted file mode 100644 index 09a501b9c..000000000 --- a/apps/launcher/utils/checkablemessagebox.hpp +++ /dev/null @@ -1,116 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of Qt Creator. -** -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -****************************************************************************/ - -#ifndef CHECKABLEMESSAGEBOX_HPP -#define CHECKABLEMESSAGEBOX_HPP - -#include -#include -#include - -class QCheckBox; - -namespace Launcher -{ - class CheckableMessageBoxPrivate - { - public: - - QLabel *pixmapLabel; - QLabel *messageLabel; - QCheckBox *checkBox; - QDialogButtonBox *buttonBox; - QAbstractButton *clickedButton; - - public: - CheckableMessageBoxPrivate(QDialog *q); - }; - - class CheckableMessageBox : public QDialog - { - Q_OBJECT - Q_PROPERTY(QString text READ text WRITE setText) - Q_PROPERTY(QPixmap iconPixmap READ iconPixmap WRITE setIconPixmap) - Q_PROPERTY(bool isChecked READ isChecked WRITE setChecked) - Q_PROPERTY(QString checkBoxText READ checkBoxText WRITE setCheckBoxText) - Q_PROPERTY(QDialogButtonBox::StandardButtons buttons READ standardButtons WRITE setStandardButtons) - Q_PROPERTY(QDialogButtonBox::StandardButton defaultButton READ defaultButton WRITE setDefaultButton) - - public: - explicit CheckableMessageBox(QWidget *parent); - virtual ~CheckableMessageBox(); - - static QDialogButtonBox::StandardButton - question(QWidget *parent, - const QString &title, - const QString &question, - const QString &checkBoxText, - bool *checkBoxSetting, - QDialogButtonBox::StandardButtons buttons = QDialogButtonBox::Yes|QDialogButtonBox::No, - QDialogButtonBox::StandardButton defaultButton = QDialogButtonBox::No); - - QString text() const; - void setText(const QString &); - - bool isChecked() const; - void setChecked(bool s); - - QString checkBoxText() const; - void setCheckBoxText(const QString &); - - bool isCheckBoxVisible() const; - void setCheckBoxVisible(bool); - - QDialogButtonBox::StandardButtons standardButtons() const; - void setStandardButtons(QDialogButtonBox::StandardButtons s); - QPushButton *button(QDialogButtonBox::StandardButton b) const; - QPushButton *addButton(const QString &text, QDialogButtonBox::ButtonRole role); - - QDialogButtonBox::StandardButton defaultButton() const; - void setDefaultButton(QDialogButtonBox::StandardButton s); - - // See static QMessageBox::standardPixmap() - QPixmap iconPixmap() const; - void setIconPixmap (const QPixmap &p); - - // Query the result - QAbstractButton *clickedButton() const; - QDialogButtonBox::StandardButton clickedStandardButton() const; - - // Conversion convenience - static QMessageBox::StandardButton dialogButtonBoxToMessageBoxButton(QDialogButtonBox::StandardButton); - - private slots: - void slotClicked(QAbstractButton *b); - - private: - CheckableMessageBoxPrivate *d; - }; -} -#endif // CHECKABLEMESSAGEBOX_HPP diff --git a/apps/launcher/utils/textinputdialog.cpp b/apps/launcher/utils/textinputdialog.cpp index 76cbe32d0..b47fdb6e6 100644 --- a/apps/launcher/utils/textinputdialog.cpp +++ b/apps/launcher/utils/textinputdialog.cpp @@ -16,15 +16,15 @@ Launcher::TextInputDialog::TextInputDialog(const QString& title, const QString & mButtonBox->addButton(QDialogButtonBox::Cancel); mButtonBox->button(QDialogButtonBox::Ok)->setEnabled (false); + QLabel *label = new QLabel(this); + label->setText(text); + // Line edit QValidator *validator = new QRegExpValidator(QRegExp("^[a-zA-Z0-9_]*$"), this); // Alpha-numeric + underscore - mLineEdit = new DialogLineEdit(this); + mLineEdit = new LineEdit(this); mLineEdit->setValidator(validator); mLineEdit->setCompleter(0); - QLabel *label = new QLabel(this); - label->setText(text); - QVBoxLayout *dialogLayout = new QVBoxLayout(this); dialogLayout->addWidget(label); dialogLayout->addWidget(mLineEdit); @@ -41,8 +41,10 @@ Launcher::TextInputDialog::TextInputDialog(const QString& title, const QString & connect(mButtonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(mButtonBox, SIGNAL(rejected()), this, SLOT(reject())); - connect(mLineEdit, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateOkButton(QString))); +} +Launcher::TextInputDialog::~TextInputDialog() +{ } int Launcher::TextInputDialog::exec() @@ -52,36 +54,18 @@ int Launcher::TextInputDialog::exec() return QDialog::exec(); } -QString Launcher::TextInputDialog::getText() const +void Launcher::TextInputDialog::setOkButtonEnabled(bool enabled) { - return mLineEdit->text(); -} + QPushButton *okButton = mButtonBox->button(QDialogButtonBox::Ok); + okButton->setEnabled(enabled); -void Launcher::TextInputDialog::slotUpdateOkButton(QString text) -{ - bool enabled = !(text.isEmpty()); - mButtonBox->button(QDialogButtonBox::Ok)->setEnabled(enabled); + QPalette *palette = new QPalette(); + palette->setColor(QPalette::Text, Qt::red); - if (enabled) + if (enabled) { mLineEdit->setPalette(QApplication::palette()); - else - { + } else { // Existing profile name, make the text red - QPalette *palette = new QPalette(); - palette->setColor(QPalette::Text,Qt::red); mLineEdit->setPalette(*palette); } } - -Launcher::TextInputDialog::DialogLineEdit::DialogLineEdit (QWidget *parent) : - LineEdit (parent) -{ - int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); - - setObjectName(QString("LineEdit")); - setStyleSheet(QString("LineEdit { padding-right: %1px; } ").arg(mClearButton->sizeHint().width() + frameWidth + 1)); - QSize msz = minimumSizeHint(); - setMinimumSize(qMax(msz.width(), mClearButton->sizeHint().height() + frameWidth * 2 + 2), - qMax(msz.height(), mClearButton->sizeHint().height() + frameWidth * 2 + 2)); - -} diff --git a/apps/launcher/utils/textinputdialog.hpp b/apps/launcher/utils/textinputdialog.hpp index bb01778be..9eb9c717d 100644 --- a/apps/launcher/utils/textinputdialog.hpp +++ b/apps/launcher/utils/textinputdialog.hpp @@ -13,26 +13,20 @@ namespace Launcher { Q_OBJECT - class DialogLineEdit : public LineEdit - { - public: - explicit DialogLineEdit (QWidget *parent = 0); - }; - - DialogLineEdit *mLineEdit; - QDialogButtonBox *mButtonBox; - public: explicit TextInputDialog(const QString& title, const QString &text, QWidget *parent = 0); - ~TextInputDialog () {} + ~TextInputDialog (); - QString getText() const; + inline LineEdit *lineEdit() { return mLineEdit; } + void setOkButtonEnabled(bool enabled); int exec(); - private slots: - void slotUpdateOkButton(QString text); + private: + + QDialogButtonBox *mButtonBox; + LineEdit *mLineEdit; }; } diff --git a/apps/wizard/CMakeLists.txt b/apps/wizard/CMakeLists.txt new file mode 100644 index 000000000..f2defd031 --- /dev/null +++ b/apps/wizard/CMakeLists.txt @@ -0,0 +1,116 @@ +set(WIZARD + componentselectionpage.cpp + conclusionpage.cpp + existinginstallationpage.cpp + importpage.cpp + inisettings.cpp + installationpage.cpp + installationtargetpage.cpp + intropage.cpp + languageselectionpage.cpp + main.cpp + mainwizard.cpp + methodselectionpage.cpp + + unshield/unshieldworker.cpp + + utils/componentlistwidget.cpp +) + +set(WIZARD_HEADER + componentselectionpage.hpp + conclusionpage.hpp + existinginstallationpage.hpp + importpage.hpp + inisettings.hpp + installationpage.hpp + installationtargetpage.hpp + intropage.hpp + languageselectionpage.hpp + mainwizard.hpp + methodselectionpage.hpp + + unshield/unshieldworker.hpp + + utils/componentlistwidget.hpp +) + +# Headers that must be pre-processed +set(WIZARD_HEADER_MOC + componentselectionpage.hpp + conclusionpage.hpp + existinginstallationpage.hpp + importpage.hpp + installationpage.hpp + installationtargetpage.hpp + intropage.hpp + languageselectionpage.hpp + mainwizard.hpp + methodselectionpage.hpp + + unshield/unshieldworker.hpp + + utils/componentlistwidget.hpp +) + +set(WIZARD_UI + ${CMAKE_SOURCE_DIR}/files/ui/wizard/componentselectionpage.ui + ${CMAKE_SOURCE_DIR}/files/ui/wizard/conclusionpage.ui + ${CMAKE_SOURCE_DIR}/files/ui/wizard/existinginstallationpage.ui + ${CMAKE_SOURCE_DIR}/files/ui/wizard/importpage.ui + ${CMAKE_SOURCE_DIR}/files/ui/wizard/installationpage.ui + ${CMAKE_SOURCE_DIR}/files/ui/wizard/installationtargetpage.ui + ${CMAKE_SOURCE_DIR}/files/ui/wizard/intropage.ui + ${CMAKE_SOURCE_DIR}/files/ui/wizard/languageselectionpage.ui + ${CMAKE_SOURCE_DIR}/files/ui/wizard/methodselectionpage.ui +) + +source_group(wizard FILES ${WIZARD} ${WIZARD_HEADER}) + +find_package(Qt4 REQUIRED) +set(QT_USE_QTGUI 1) + +# Set some platform specific settings +if(WIN32) + set(GUI_TYPE WIN32) + set(QT_USE_QTMAIN TRUE) +endif(WIN32) + +QT4_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/wizard/wizard.qrc) +QT4_WRAP_CPP(MOC_SRCS ${WIZARD_HEADER_MOC}) +QT4_WRAP_UI(UI_HDRS ${WIZARD_UI}) + + +include(${QT_USE_FILE}) +include_directories(${CMAKE_CURRENT_BINARY_DIR} ${LIBUNSHIELD_INCLUDE}) + +add_executable(openmw-wizard + ${GUI_TYPE} + ${WIZARD} + ${WIZARD_HEADER} + ${RCC_SRCS} + ${MOC_SRCS} + ${UI_HDRS} +) + +target_link_libraries(openmw-wizard + ${Boost_LIBRARIES} + ${QT_LIBRARIES} + ${LIBUNSHIELD_LIBRARY} + components +) + +if(DPKG_PROGRAM) + INSTALL(TARGETS openmw-wizard RUNTIME DESTINATION games COMPONENT openmw-wizard) +endif() + +if (BUILD_WITH_CODE_COVERAGE) + add_definitions (--coverage) + target_link_libraries(openmw-wizard gcov) +endif() + +# Workaround for binutil => 2.23 problem when linking, should be fixed eventually upstream +if (UNIX AND NOT APPLE) +target_link_libraries(openmw-wizard dl Xt) +endif() + diff --git a/apps/wizard/componentselectionpage.cpp b/apps/wizard/componentselectionpage.cpp new file mode 100644 index 000000000..d372f677d --- /dev/null +++ b/apps/wizard/componentselectionpage.cpp @@ -0,0 +1,164 @@ +#include "componentselectionpage.hpp" + +#include +#include +#include +#include + +#include "mainwizard.hpp" + +Wizard::ComponentSelectionPage::ComponentSelectionPage(QWidget *parent) : + QWizardPage(parent) +{ + mWizard = qobject_cast(parent); + + setupUi(this); + + setCommitPage(true); + setButtonText(QWizard::CommitButton, tr("&Install")); + + registerField(QLatin1String("installation.components"), componentsList); + + connect(componentsList, SIGNAL(itemChanged(QListWidgetItem *)), + this, SLOT(updateButton(QListWidgetItem *))); + +} + +void Wizard::ComponentSelectionPage::updateButton(QListWidgetItem *item) +{ + if (field(QLatin1String("installation.new")).toBool() == true) + return; // Morrowind is always checked here + + bool unchecked = true; + + for (int i =0; i < componentsList->count(); ++i) { + QListWidgetItem *item = componentsList->item(i); + + if (!item) + continue; + + if (item->checkState() == Qt::Checked) { + unchecked = false; + } + } + + if (unchecked) { + setCommitPage(false); + setButtonText(QWizard::NextButton, tr("&Skip")); + } else { + setCommitPage(true); + } +} + +void Wizard::ComponentSelectionPage::initializePage() +{ + componentsList->clear(); + + QString path(field(QLatin1String("installation.path")).toString()); + + QListWidgetItem *morrowindItem = new QListWidgetItem(QLatin1String("Morrowind")); + QListWidgetItem *tribunalItem = new QListWidgetItem(QLatin1String("Tribunal")); + QListWidgetItem *bloodmoonItem = new QListWidgetItem(QLatin1String("Bloodmoon")); + + if (field(QLatin1String("installation.new")).toBool() == true) + { + morrowindItem->setFlags(morrowindItem->flags() & !Qt::ItemIsEnabled & Qt::ItemIsUserCheckable); + morrowindItem->setData(Qt::CheckStateRole, Qt::Checked); + componentsList->addItem(morrowindItem); + + tribunalItem->setFlags(tribunalItem->flags() | Qt::ItemIsUserCheckable); + tribunalItem->setData(Qt::CheckStateRole, Qt::Checked); + componentsList->addItem(tribunalItem); + + bloodmoonItem->setFlags(bloodmoonItem->flags() | Qt::ItemIsUserCheckable); + bloodmoonItem->setData(Qt::CheckStateRole, Qt::Checked); + componentsList->addItem(bloodmoonItem); + } else { + + if (mWizard->mInstallations[path].hasMorrowind) { + morrowindItem->setText(tr("Morrowind\t\t(installed)")); + morrowindItem->setFlags(morrowindItem->flags() & !Qt::ItemIsEnabled & Qt::ItemIsUserCheckable); + morrowindItem->setData(Qt::CheckStateRole, Qt::Unchecked); + } else { + morrowindItem->setText(tr("Morrowind")); + morrowindItem->setData(Qt::CheckStateRole, Qt::Checked); + } + + componentsList->addItem(morrowindItem); + + if (mWizard->mInstallations[path].hasTribunal) { + tribunalItem->setText(tr("Tribunal\t\t(installed)")); + tribunalItem->setFlags(tribunalItem->flags() & !Qt::ItemIsEnabled & Qt::ItemIsUserCheckable); + tribunalItem->setData(Qt::CheckStateRole, Qt::Unchecked); + } else { + tribunalItem->setText(tr("Tribunal")); + tribunalItem->setData(Qt::CheckStateRole, Qt::Checked); + } + + componentsList->addItem(tribunalItem); + + if (mWizard->mInstallations[path].hasBloodmoon) { + bloodmoonItem->setText(tr("Bloodmoon\t\t(installed)")); + bloodmoonItem->setFlags(bloodmoonItem->flags() & !Qt::ItemIsEnabled & Qt::ItemIsUserCheckable); + bloodmoonItem->setData(Qt::CheckStateRole, Qt::Unchecked); + } else { + bloodmoonItem->setText(tr("Bloodmoon")); + bloodmoonItem->setData(Qt::CheckStateRole, Qt::Checked); + } + + componentsList->addItem(bloodmoonItem); + } +} + +bool Wizard::ComponentSelectionPage::validatePage() +{ + QStringList components(field(QLatin1String("installation.components")).toStringList()); + QString path(field(QLatin1String("installation.path")).toString()); + +// qDebug() << components << path << mWizard->mInstallations[path]; + + if (field(QLatin1String("installation.new")).toBool() == false) { + if (components.contains(QLatin1String("Tribunal")) && !components.contains(QLatin1String("Bloodmoon"))) + { + if (mWizard->mInstallations[path].hasBloodmoon) + { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("About to install Tribunal after Bloodmoon")); + msgBox.setIcon(QMessageBox::Information); + msgBox.setStandardButtons(QMessageBox::Cancel); + msgBox.setText(tr("

You are about to install Tribunal

\ +

Bloodmoon is already installed on your computer.

\ +

However, it is recommended that you install Tribunal before Bloodmoon.

\ +

Would you like to re-install Bloodmoon?

")); + + QAbstractButton *reinstallButton = msgBox.addButton(tr("Re-install &Bloodmoon"), QMessageBox::ActionRole); + msgBox.exec(); + + + if (msgBox.clickedButton() == reinstallButton) { + // Force reinstallation + mWizard->mInstallations[path].hasBloodmoon = false; + QList items = componentsList->findItems(QLatin1String("Bloodmoon"), Qt::MatchStartsWith); + + foreach (QListWidgetItem *item, items) { + item->setText(QLatin1String("Bloodmoon")); + item->setCheckState(Qt::Checked); + } + + return true; + } + } + } + } + + return true; +} + +int Wizard::ComponentSelectionPage::nextId() const +{ + if (isCommitPage()) { + return MainWizard::Page_Installation; + } else { + return MainWizard::Page_Import; + } +} diff --git a/apps/wizard/componentselectionpage.hpp b/apps/wizard/componentselectionpage.hpp new file mode 100644 index 000000000..ca4347108 --- /dev/null +++ b/apps/wizard/componentselectionpage.hpp @@ -0,0 +1,34 @@ +#ifndef COMPONENTSELECTIONPAGE_HPP +#define COMPONENTSELECTIONPAGE_HPP + +#include + +#include "ui_componentselectionpage.h" + +namespace Wizard +{ + class MainWizard; + + class ComponentSelectionPage : public QWizardPage, private Ui::ComponentSelectionPage + { + Q_OBJECT + public: + ComponentSelectionPage(QWidget *parent); + + int nextId() const; + virtual bool validatePage(); + + private slots: + void updateButton(QListWidgetItem *item); + + private: + MainWizard *mWizard; + + protected: + void initializePage(); + + }; + +} + +#endif // COMPONENTSELECTIONPAGE_HPP diff --git a/apps/wizard/conclusionpage.cpp b/apps/wizard/conclusionpage.cpp new file mode 100644 index 000000000..87154732a --- /dev/null +++ b/apps/wizard/conclusionpage.cpp @@ -0,0 +1,54 @@ +#include "conclusionpage.hpp" + +#include + +#include "mainwizard.hpp" + +Wizard::ConclusionPage::ConclusionPage(QWidget *parent) : + QWizardPage(parent) +{ + mWizard = qobject_cast(parent); + + setupUi(this); + setPixmap(QWizard::WatermarkPixmap, QPixmap(QLatin1String(":/images/intropage-background.png"))); +} + +void Wizard::ConclusionPage::initializePage() +{ + // Write the path to openmw.cfg + if (field(QLatin1String("installation.new")).toBool() == true) { + QString path(field(QLatin1String("installation.path")).toString()); + mWizard->addInstallation(path); + } + + if (!mWizard->mError) + { + if ((field(QLatin1String("installation.new")).toBool() == true) + || (field(QLatin1String("installation.import-settings")).toBool() == true)) + { + qDebug() << "IMPORT SETTINGS"; + mWizard->runSettingsImporter(); + } + } + + if (!mWizard->mError) + { + if (field(QLatin1String("installation.new")).toBool() == true) + { + textLabel->setText(tr("

The OpenMW Wizard successfully installed Morrowind on your computer.

\ +

Click Finish to close the Wizard.

")); + } else { + textLabel->setText(tr("

The OpenMW Wizard successfully modified your existing Morrowind installation.

\ +

Click Finish to close the Wizard.

")); + } + } else { + textLabel->setText(tr("

The OpenMW Wizard failed to install Morrowind on your computer.

\ +

Please report any bugs you might have encountered to our \ + bug tracker.
Make sure to include the installation log.


")); + } +} + +int Wizard::ConclusionPage::nextId() const +{ + return -1; +} diff --git a/apps/wizard/conclusionpage.hpp b/apps/wizard/conclusionpage.hpp new file mode 100644 index 000000000..0e9abed72 --- /dev/null +++ b/apps/wizard/conclusionpage.hpp @@ -0,0 +1,30 @@ +#ifndef CONCLUSIONPAGE_HPP +#define CONCLUSIONPAGE_HPP + +#include + +#include "ui_conclusionpage.h" + +namespace Wizard +{ + class MainWizard; + + class ConclusionPage : public QWizardPage, private Ui::ConclusionPage + { + Q_OBJECT + public: + ConclusionPage(QWidget *parent); + + int nextId() const; + + private: + MainWizard *mWizard; + + protected: + void initializePage(); + + }; + +} + +#endif // CONCLUSIONPAGE_HPP diff --git a/apps/wizard/existinginstallationpage.cpp b/apps/wizard/existinginstallationpage.cpp new file mode 100644 index 000000000..83ea20f5a --- /dev/null +++ b/apps/wizard/existinginstallationpage.cpp @@ -0,0 +1,158 @@ +#include "existinginstallationpage.hpp" + +#include +#include +#include +#include +#include + +#include "mainwizard.hpp" + +Wizard::ExistingInstallationPage::ExistingInstallationPage(QWidget *parent) : + QWizardPage(parent) +{ + mWizard = qobject_cast(parent); + + setupUi(this); + + // Add a placeholder item to the list of installations + QListWidgetItem *emptyItem = new QListWidgetItem(tr("No existing installations detected")); + emptyItem->setFlags(Qt::NoItemFlags); + + installationsList->insertItem(0, emptyItem); + +} + +void Wizard::ExistingInstallationPage::initializePage() +{ + // Add the available installation paths + QStringList paths(mWizard->mInstallations.keys()); + + // Hide the default item if there are installations to choose from + if (paths.isEmpty()) { + installationsList->item(0)->setHidden(false); + } else { + installationsList->item(0)->setHidden(true); + } + + foreach (const QString &path, paths) { + QListWidgetItem *item = new QListWidgetItem(path); + + if (installationsList->findItems(path, Qt::MatchExactly).isEmpty()) + installationsList->addItem(item); + } + + connect(installationsList, SIGNAL(currentTextChanged(QString)), + this, SLOT(textChanged(QString))); + + connect(installationsList,SIGNAL(itemSelectionChanged()), + this, SIGNAL(completeChanged())); +} + +bool Wizard::ExistingInstallationPage::validatePage() +{ + // See if Morrowind.ini is detected, if not, ask the user + // It can be missing entirely + // Or failed to be detected due to the target being a symlink + + QString path(field(QLatin1String("installation.path")).toString()); + QFile file(mWizard->mInstallations[path].iniPath); + + if (!file.exists()) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error detecting Morrowind configuration")); + msgBox.setIcon(QMessageBox::Warning); + msgBox.setStandardButtons(QMessageBox::Cancel); + msgBox.setText(QObject::tr("
Could not find Morrowind.ini

\ + The Wizard needs to update settings in this file.

\ + Press \"Browse...\" to specify the location manually.
")); + + QAbstractButton *browseButton = + msgBox.addButton(QObject::tr("B&rowse..."), QMessageBox::ActionRole); + + msgBox.exec(); + + QString iniFile; + if (msgBox.clickedButton() == browseButton) { + iniFile = QFileDialog::getOpenFileName( + this, + QObject::tr("Select configuration file"), + QDir::currentPath(), + QString(tr("Morrowind configuration file (*.ini)"))); + } + + if (iniFile.isEmpty()) { + return false; // Cancel was clicked; + } + + // A proper Morrowind.ini was selected, set it + QFileInfo info(iniFile); + mWizard->mInstallations[path].iniPath = info.absoluteFilePath(); + } + + return true; +} + +void Wizard::ExistingInstallationPage::on_browseButton_clicked() +{ + QString selectedFile = QFileDialog::getOpenFileName( + this, + tr("Select master file"), + QDir::currentPath(), + QString(tr("Morrowind master file (*.esm)")), + NULL, + QFileDialog::DontResolveSymlinks); + + if (selectedFile.isEmpty()) + return; + + QFileInfo info(selectedFile); + + if (!info.exists()) + return; + + if (!mWizard->findFiles(QLatin1String("Morrowind"), info.absolutePath())) + return; // No valid Morrowind installation found + + QString path(QDir::toNativeSeparators(info.absolutePath())); + QList items = installationsList->findItems(path, Qt::MatchExactly); + + if (items.isEmpty()) { + // Path is not yet in the list, add it + mWizard->addInstallation(path); + + // Hide the default item + installationsList->item(0)->setHidden(true); + + QListWidgetItem *item = new QListWidgetItem(path); + installationsList->addItem(item); + installationsList->setCurrentItem(item); // Select it too + } else { + installationsList->setCurrentItem(items.first()); + } + + // Update the button + emit completeChanged(); +} + +void Wizard::ExistingInstallationPage::textChanged(const QString &text) +{ + // Set the installation path manually, as registerField doesn't work + // Because it doesn't accept two widgets operating on a single field + if (!text.isEmpty()) + mWizard->setField(QLatin1String("installation.path"), text); +} + +bool Wizard::ExistingInstallationPage::isComplete() const +{ + if (installationsList->selectionModel()->hasSelection()) { + return true; + } else { + return false; + } +} + +int Wizard::ExistingInstallationPage::nextId() const +{ + return MainWizard::Page_LanguageSelection; +} diff --git a/apps/wizard/existinginstallationpage.hpp b/apps/wizard/existinginstallationpage.hpp new file mode 100644 index 000000000..601295464 --- /dev/null +++ b/apps/wizard/existinginstallationpage.hpp @@ -0,0 +1,37 @@ +#ifndef EXISTINGINSTALLATIONPAGE_HPP +#define EXISTINGINSTALLATIONPAGE_HPP + +#include + +#include "ui_existinginstallationpage.h" + +namespace Wizard +{ + class MainWizard; + + class ExistingInstallationPage : public QWizardPage, private Ui::ExistingInstallationPage + { + Q_OBJECT + public: + ExistingInstallationPage(QWidget *parent); + + int nextId() const; + virtual bool isComplete() const; + virtual bool validatePage(); + + private slots: + void on_browseButton_clicked(); + void textChanged(const QString &text); + + + private: + MainWizard *mWizard; + + protected: + void initializePage(); + + }; + +} + +#endif // EXISTINGINSTALLATIONPAGE_HPP diff --git a/apps/wizard/importpage.cpp b/apps/wizard/importpage.cpp new file mode 100644 index 000000000..71c5544e6 --- /dev/null +++ b/apps/wizard/importpage.cpp @@ -0,0 +1,19 @@ +#include "importpage.hpp" + +#include "mainwizard.hpp" + +Wizard::ImportPage::ImportPage(QWidget *parent) : + QWizardPage(parent) +{ + mWizard = qobject_cast(parent); + + setupUi(this); + + registerField(QLatin1String("installation.import-settings"), importCheckBox); + registerField(QLatin1String("installation.import-addons"), addonsCheckBox); +} + +int Wizard::ImportPage::nextId() const +{ + return MainWizard::Page_Conclusion; +} diff --git a/apps/wizard/importpage.hpp b/apps/wizard/importpage.hpp new file mode 100644 index 000000000..386cd59af --- /dev/null +++ b/apps/wizard/importpage.hpp @@ -0,0 +1,27 @@ +#ifndef IMPORTPAGE_HPP +#define IMPORTPAGE_HPP + +#include + +#include "ui_importpage.h" + +namespace Wizard +{ + class MainWizard; + + class ImportPage : public QWizardPage, private Ui::ImportPage + { + Q_OBJECT + public: + ImportPage(QWidget *parent); + + int nextId() const; + + private: + MainWizard *mWizard; + + }; + +} + +#endif // IMPORTPAGE_HPP diff --git a/apps/wizard/inisettings.cpp b/apps/wizard/inisettings.cpp new file mode 100644 index 000000000..3711ba066 --- /dev/null +++ b/apps/wizard/inisettings.cpp @@ -0,0 +1,219 @@ +#include "inisettings.hpp" + +#include + +#include +#include +#include +#include +#include +#include + +Wizard::IniSettings::IniSettings() +{ +} + +Wizard::IniSettings::~IniSettings() +{ +} + +QStringList Wizard::IniSettings::findKeys(const QString &text) +{ + QStringList result; + + foreach (const QString &key, mSettings.keys()) { + + if (key.startsWith(text)) + result << key; + + } + + return result; +} + +bool Wizard::IniSettings::readFile(QTextStream &stream) +{ + // Look for a square bracket, "'\\[" + // that has one or more "not nothing" in it, "([^]]+)" + // and is closed with a square bracket, "\\]" + QRegExp sectionRe(QLatin1String("^\\[([^]]+)\\]")); + + // Find any character(s) that is/are not equal sign(s), "[^=]+" + // followed by an optional whitespace, an equal sign, and another optional whitespace, "\\s*=\\s*" + // and one or more periods, "(.+)" + QRegExp keyRe(QLatin1String("^([^=]+)\\s*=\\s*(.+)$")); + + QString currentSection; + + while (!stream.atEnd()) + { + const QString line(stream.readLine()); + + if (line.isEmpty() || line.startsWith(QLatin1Char(';'))) + continue; + + if (sectionRe.exactMatch(line)) + { + currentSection = sectionRe.cap(1); + } + else if (keyRe.indexIn(line) != -1) + { + QString key = keyRe.cap(1).trimmed(); + QString value = keyRe.cap(2).trimmed(); + + // Append the section, but only if there is one + if (!currentSection.isEmpty()) + key = currentSection + QLatin1Char('/') + key; + + mSettings[key] = QVariant(value); + } + } + + return true; +} + +bool Wizard::IniSettings::writeFile(const QString &path, QTextStream &stream) +{ + // Look for a square bracket, "'\\[" + // that has one or more "not nothing" in it, "([^]]+)" + // and is closed with a square bracket, "\\]" + QRegExp sectionRe(QLatin1String("^\\[([^]]+)\\]")); + + // Find any character(s) that is/are not equal sign(s), "[^=]+" + // followed by an optional whitespace, an equal sign, and another optional whitespace, "\\s*=\\s*" + // and one or more periods, "(.+)" + QRegExp keyRe(QLatin1String("^([^=]+)\\s*=\\s*(.+)$")); + + const QStringList keys(mSettings.keys()); + + QString currentSection; + QString buffer; + + while (!stream.atEnd()) { + + const QString line(stream.readLine()); + + if (line.isEmpty() || line.startsWith(QLatin1Char(';'))) { + buffer.append(line + QLatin1String("\n")); + continue; + } + + if (sectionRe.exactMatch(line)) { + buffer.append(line + QLatin1String("\n")); + currentSection = sectionRe.cap(1); + } else if (keyRe.indexIn(line) != -1) { + QString key(keyRe.cap(1).trimmed()); + QString lookupKey(key); + + // Append the section, but only if there is one + if (!currentSection.isEmpty()) + lookupKey = currentSection + QLatin1Char('/') + key; + + buffer.append(key + QLatin1Char('=') + mSettings[lookupKey].toString() + QLatin1String("\n")); + mSettings.remove(lookupKey); + } + } + + // Add the new settings to the buffer + QHashIterator i(mSettings); + while (i.hasNext()) { + i.next(); + + QStringList fullKey(i.key().split(QLatin1Char('/'))); + QString section(fullKey.at(0)); + section.prepend(QLatin1Char('[')); + section.append(QLatin1Char(']')); + QString key(fullKey.at(1)); + + int index = buffer.lastIndexOf(section); + if (index != -1) { + // Look for the next section + index = buffer.indexOf(QLatin1Char('['), index + 1); + + if (index == -1 ) { + // We are at the last section, append it to the bottom of the file + buffer.append(QString("\n%1=%2").arg(key, i.value().toString())); + mSettings.remove(i.key()); + continue; + } else { + // Not at last section, add the key at the index + buffer.insert(index - 1, QString("\n%1=%2").arg(key, i.value().toString())); + mSettings.remove(i.key()); + } + + } else { + // Add the section to the end of the file, because it's not found + buffer.append(QString("\n%1\n").arg(section)); + i.previous(); + } + } + + // Now we reopen the file, this time we write + QFile file(path); + + if (file.open(QIODevice::ReadWrite | QIODevice::Truncate | QIODevice::Text)) { + QTextStream in(&file); + in.setCodec(stream.codec()); + + // Write the updated buffer to an empty file + in << buffer; + file.flush(); + file.close(); + } else { + return false; + } + + return true; +} + +bool Wizard::IniSettings::parseInx(const QString &path) +{ + QFile file(path); + + if (file.open(QIODevice::ReadOnly)) + { + + const QByteArray data(file.readAll()); + const QByteArray pattern("\x21\x00\x1A\x01\x04\x00\x04\x97\xFF\x06", 10); + + int i = 0; + while ((i = data.indexOf(pattern, i)) != -1) { + + int next = data.indexOf(pattern, i + 1); + if (next == -1) + break; + + QByteArray array(data.mid(i, (next - i))); + + // Skip some invalid entries + if (array.contains("\x04\x96\xFF")) { + ++i; + continue; + } + + // Remove the pattern from the beginning + array.remove(0, 12); + + int index = array.indexOf("\x06"); + const QString section(array.left(index)); + + // Figure how many characters to read for the key + int lenght = array.indexOf("\x06", section.length() + 3) - (section.length() + 3); + const QString key(array.mid(section.length() + 3, lenght)); + + QString value(array.mid(section.length() + key.length() + 6)); + + // Add the value + setValue(section + QLatin1Char('/') + key, QVariant(value)); + + ++i; + } + + file.close(); + } else { + qDebug() << "Failed to open INX file: " << path; + return false; + } + + return true; +} diff --git a/apps/wizard/inisettings.hpp b/apps/wizard/inisettings.hpp new file mode 100644 index 000000000..d425a9b1b --- /dev/null +++ b/apps/wizard/inisettings.hpp @@ -0,0 +1,56 @@ +#ifndef INISETTINGS_HPP +#define INISETTINGS_HPP + +#include +#include + +class QTextStream; + +namespace Wizard +{ + + typedef QHash SettingsMap; + + class IniSettings + { + public: + explicit IniSettings(); + ~IniSettings(); + + inline QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const + { + return mSettings.value(key, defaultValue); + } + + inline QList values() const + { + return mSettings.values(); + } + + inline void setValue(const QString &key, const QVariant &value) + { + mSettings.insert(key, value); + } + + inline void remove(const QString &key) + { + mSettings.remove(key); + } + + QStringList findKeys(const QString &text); + + bool readFile(QTextStream &stream); + bool writeFile(const QString &path, QTextStream &stream); + + bool parseInx(const QString &path); + + private: + + int getLastNewline(const QString &buffer, int from); + + SettingsMap mSettings; + }; + +} + +#endif // INISETTINGS_HPP diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp new file mode 100644 index 000000000..09e577317 --- /dev/null +++ b/apps/wizard/installationpage.cpp @@ -0,0 +1,244 @@ +#include "installationpage.hpp" + +#include +#include +#include +#include +#include + +#include "mainwizard.hpp" +#include "inisettings.hpp" + +Wizard::InstallationPage::InstallationPage(QWidget *parent) : + QWizardPage(parent) +{ + mWizard = qobject_cast(parent); + + setupUi(this); + + mFinished = false; + + mThread = new QThread(); + mUnshield = new UnshieldWorker(); + mUnshield->moveToThread(mThread); + + connect(mThread, SIGNAL(started()), + mUnshield, SLOT(extract())); + + connect(mUnshield, SIGNAL(finished()), + mThread, SLOT(quit())); + + connect(mUnshield, SIGNAL(finished()), + mUnshield, SLOT(deleteLater())); + + connect(mUnshield, SIGNAL(finished()), + mThread, SLOT(deleteLater()));; + + connect(mUnshield, SIGNAL(finished()), + this, SLOT(installationFinished()), Qt::QueuedConnection); + + connect(mUnshield, SIGNAL(error(QString, QString)), + this, SLOT(installationError(QString, QString)), Qt::QueuedConnection); + + connect(mUnshield, SIGNAL(textChanged(QString)), + installProgressLabel, SLOT(setText(QString)), Qt::QueuedConnection); + + connect(mUnshield, SIGNAL(textChanged(QString)), + logTextEdit, SLOT(appendPlainText(QString)), Qt::QueuedConnection); + + connect(mUnshield, SIGNAL(textChanged(QString)), + mWizard, SLOT(addLogText(QString)), Qt::QueuedConnection); + + connect(mUnshield, SIGNAL(progressChanged(int)), + installProgressBar, SLOT(setValue(int)), Qt::QueuedConnection); + + connect(mUnshield, SIGNAL(requestFileDialog(Wizard::Component)), + this, SLOT(showFileDialog(Wizard::Component)), Qt::QueuedConnection); +} + +Wizard::InstallationPage::~InstallationPage() +{ + if (mThread->isRunning()) { + mUnshield->stopWorker(); + mThread->wait(); + } + + delete mUnshield; + delete mThread; +} + +void Wizard::InstallationPage::initializePage() +{ + QString path(field(QLatin1String("installation.path")).toString()); + QStringList components(field(QLatin1String("installation.components")).toStringList()); + + logTextEdit->appendPlainText(QString("Installing to %1").arg(path)); + logTextEdit->appendPlainText(QString("Installing %1.").arg(components.join(", "))); + + installProgressBar->setMinimum(0); + + // Set the progressbar maximum to a multiple of 100 + // That way installing all three components would yield 300% + // When one component is done the bar will be filled by 33% + + if (field(QLatin1String("installation.new")).toBool() == true) { + installProgressBar->setMaximum((components.count() * 100)); + } else { + if (components.contains(QLatin1String("Tribunal")) + && !mWizard->mInstallations[path].hasTribunal) + installProgressBar->setMaximum(100); + + if (components.contains(QLatin1String("Bloodmoon")) + && !mWizard->mInstallations[path].hasBloodmoon) + installProgressBar->setMaximum(installProgressBar->maximum() + 100); + } + + startInstallation(); +} + +void Wizard::InstallationPage::startInstallation() +{ + QStringList components(field(QLatin1String("installation.components")).toStringList()); + QString path(field(QLatin1String("installation.path")).toString()); + + if (field(QLatin1String("installation.new")).toBool() == true) + { + // Always install Morrowind + mUnshield->setInstallComponent(Wizard::Component_Morrowind, true); + + if (components.contains(QLatin1String("Tribunal"))) + mUnshield->setInstallComponent(Wizard::Component_Tribunal, true); + + if (components.contains(QLatin1String("Bloodmoon"))) + mUnshield->setInstallComponent(Wizard::Component_Bloodmoon, true); + } else { + // Morrowind should already be installed + mUnshield->setInstallComponent(Wizard::Component_Morrowind, false); + + if (components.contains(QLatin1String("Tribunal")) + && !mWizard->mInstallations[path].hasTribunal) + mUnshield->setInstallComponent(Wizard::Component_Tribunal, true); + + if (components.contains(QLatin1String("Bloodmoon")) + && !mWizard->mInstallations[path].hasBloodmoon) + mUnshield->setInstallComponent(Wizard::Component_Bloodmoon, true); + + // Set the location of the Morrowind.ini to update + mUnshield->setIniPath(mWizard->mInstallations[path].iniPath); + mUnshield->setupSettings(); + } + + // Set the installation target path + mUnshield->setPath(path); + + // Set the right codec to use for Morrowind.ini + QString language(field(QLatin1String("installation.language")).toString()); + + if (language == QLatin1String("Polish")) { + mUnshield->setIniCodec(QTextCodec::codecForName("windows-1250")); + } else if (language == QLatin1String("Russian")) { + mUnshield->setIniCodec(QTextCodec::codecForName("windows-1251")); + } else { + mUnshield->setIniCodec(QTextCodec::codecForName("windows-1252")); + } + + mThread->start(); +} + +void Wizard::InstallationPage::showFileDialog(Wizard::Component component) +{ + QString name; + switch (component) { + + case Wizard::Component_Morrowind: + name = QLatin1String("Morrowind"); + break; + case Wizard::Component_Tribunal: + name = QLatin1String("Tribunal"); + break; + case Wizard::Component_Bloodmoon: + name = QLatin1String("Bloodmoon"); + break; + } + + QString path = QFileDialog::getExistingDirectory(this, + tr("Select %1 installation media").arg(name), + QDir::rootPath()); + + if (path.isEmpty()) { + logTextEdit->appendHtml(tr("


\ + Error: The installation was aborted by the user

")); + + mWizard->addLogText(QLatin1String("Error: The installation was aborted by the user")); + mWizard->mError = true; + + emit completeChanged(); + return; + } + + mUnshield->setDiskPath(path); +} + +void Wizard::InstallationPage::installationFinished() +{ + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Installation finished")); + msgBox.setIcon(QMessageBox::Information); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("Installation completed sucessfully!")); + + msgBox.exec(); + + mFinished = true; + emit completeChanged(); +} + +void Wizard::InstallationPage::installationError(const QString &text, const QString &details) +{ + installProgressLabel->setText(tr("Installation failed!")); + + logTextEdit->appendHtml(tr("


\ + Error: %1

").arg(text)); + logTextEdit->appendHtml(tr("

\ + %1

").arg(details)); + + mWizard->addLogText(QLatin1String("Error: ") + text); + mWizard->addLogText(details); + + mWizard->mError = true; + QMessageBox msgBox; + msgBox.setWindowTitle(tr("An error occurred")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("

The Wizard has encountered an error

\ +

The error reported was:

%1

\ +

Press "Show Details..." for more information.

").arg(text)); + + msgBox.setDetailedText(details); + msgBox.exec(); + + + emit completeChanged(); +} + +bool Wizard::InstallationPage::isComplete() const +{ + if (!mWizard->mError) { + return mFinished; + } else { + return true; + } +} + +int Wizard::InstallationPage::nextId() const +{ + if (field(QLatin1String("installation.new")).toBool() == true) { + return MainWizard::Page_Conclusion; + } else { + if (!mWizard->mError) { + return MainWizard::Page_Import; + } else { + return MainWizard::Page_Conclusion; + } + } +} diff --git a/apps/wizard/installationpage.hpp b/apps/wizard/installationpage.hpp new file mode 100644 index 000000000..822cd21cd --- /dev/null +++ b/apps/wizard/installationpage.hpp @@ -0,0 +1,50 @@ +#ifndef INSTALLATIONPAGE_HPP +#define INSTALLATIONPAGE_HPP + +#include + +#include "unshield/unshieldworker.hpp" +#include "ui_installationpage.h" +#include "inisettings.hpp" + +class QThread; + +namespace Wizard +{ + class MainWizard; + class IniSettings; + class UnshieldWorker; + + class InstallationPage : public QWizardPage, private Ui::InstallationPage + { + Q_OBJECT + public: + InstallationPage(QWidget *parent); + ~InstallationPage(); + + int nextId() const; + virtual bool isComplete() const; + + private: + MainWizard *mWizard; + bool mFinished; + + QThread* mThread; + UnshieldWorker *mUnshield; + + void startInstallation(); + + private slots: + void showFileDialog(Wizard::Component component); + + void installationFinished(); + void installationError(const QString &text, const QString &details); + + protected: + void initializePage(); + + }; + +} + +#endif // INSTALLATIONPAGE_HPP diff --git a/apps/wizard/installationtargetpage.cpp b/apps/wizard/installationtargetpage.cpp new file mode 100644 index 000000000..3a179a2cb --- /dev/null +++ b/apps/wizard/installationtargetpage.cpp @@ -0,0 +1,101 @@ +#include "installationtargetpage.hpp" + +#include +#include +#include + +#include "mainwizard.hpp" + +Wizard::InstallationTargetPage::InstallationTargetPage(QWidget *parent, const Files::ConfigurationManager &cfg) : + QWizardPage(parent), + mCfgMgr(cfg) +{ + mWizard = qobject_cast(parent); + + setupUi(this); + + registerField(QLatin1String("installation.path*"), targetLineEdit); +} + +void Wizard::InstallationTargetPage::initializePage() +{ + QString path(QFile::decodeName(mCfgMgr.getUserDataPath().string().c_str())); + path.append(QDir::separator() + QLatin1String("data")); + + QDir dir(path); + targetLineEdit->setText(QDir::toNativeSeparators(dir.absolutePath())); +} + +bool Wizard::InstallationTargetPage::validatePage() +{ + QString path(field(QLatin1String("installation.path")).toString()); + + qDebug() << "Validating path: " << path; + + if (!QFile::exists(path)) { + QDir dir; + + if (!dir.mkpath(path)) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error creating destination")); + msgBox.setIcon(QMessageBox::Warning); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("

Could not create the destination directory

\ +

Please make sure you have the right permissions \ + and try again, or specify a different location.

")); + msgBox.exec(); + return false; + } + } + + QFileInfo info(path); + + if (!info.isWritable()) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Insufficient permissions")); + msgBox.setIcon(QMessageBox::Warning); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("

Could not write to the destination directory

\ +

Please make sure you have the right permissions \ + and try again, or specify a different location.

")); + msgBox.exec(); + return false; + } + + if (mWizard->findFiles(QLatin1String("Morrowind"), path)) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Destination not empty")); + msgBox.setIcon(QMessageBox::Warning); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("

The destination directory is not empty

\ +

An existing Morrowind installation is present in the specified location.

\ +

Please specify a different location, or go back and select the location as an existing installation.

")); + msgBox.exec(); + return false; + } + + return true; +} + +void Wizard::InstallationTargetPage::on_browseButton_clicked() +{ + QString selectedPath = QFileDialog::getExistingDirectory( + this, + tr("Select where to install Morrowind"), + QDir::homePath(), + QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); + + qDebug() << selectedPath; + QFileInfo info(selectedPath); + if (!info.exists()) + return; + + if (info.isWritable()) + targetLineEdit->setText(info.absoluteFilePath()); + +} + +int Wizard::InstallationTargetPage::nextId() const +{ + return MainWizard::Page_LanguageSelection; +} diff --git a/apps/wizard/installationtargetpage.hpp b/apps/wizard/installationtargetpage.hpp new file mode 100644 index 000000000..ca3b505b7 --- /dev/null +++ b/apps/wizard/installationtargetpage.hpp @@ -0,0 +1,40 @@ +#ifndef INSTALLATIONTARGETPAGE_HPP +#define INSTALLATIONTARGETPAGE_HPP + +#include + +#include "ui_installationtargetpage.h" + +namespace Files +{ + struct ConfigurationManager; +} + +namespace Wizard +{ + class MainWizard; + + class InstallationTargetPage : public QWizardPage, private Ui::InstallationTargetPage + { + Q_OBJECT + public: + InstallationTargetPage(QWidget *parent, const Files::ConfigurationManager &cfg); + + int nextId() const; + virtual bool validatePage(); + + private slots: + void on_browseButton_clicked(); + + private: + MainWizard *mWizard; + const Files::ConfigurationManager &mCfgMgr; + + protected: + void initializePage(); + + }; + +} + +#endif // INSTALLATIONTARGETPAGE_HPP diff --git a/apps/wizard/intropage.cpp b/apps/wizard/intropage.cpp new file mode 100644 index 000000000..0a98ae5f3 --- /dev/null +++ b/apps/wizard/intropage.cpp @@ -0,0 +1,17 @@ +#include "intropage.hpp" + +#include "mainwizard.hpp" + +Wizard::IntroPage::IntroPage(QWidget *parent) : + QWizardPage(parent) +{ + mWizard = qobject_cast(parent); + + setupUi(this); + setPixmap(QWizard::WatermarkPixmap, QPixmap(QLatin1String(":/images/intropage-background.png"))); +} + +int Wizard::IntroPage::nextId() const +{ + return MainWizard::Page_MethodSelection; +} diff --git a/apps/wizard/intropage.hpp b/apps/wizard/intropage.hpp new file mode 100644 index 000000000..4ad9b4111 --- /dev/null +++ b/apps/wizard/intropage.hpp @@ -0,0 +1,26 @@ +#ifndef INTROPAGE_HPP +#define INTROPAGE_HPP + +#include + +#include "ui_intropage.h" + +namespace Wizard +{ + class MainWizard; + + class IntroPage : public QWizardPage, private Ui::IntroPage + { + Q_OBJECT + public: + IntroPage(QWidget *parent); + + int nextId() const; + + private: + MainWizard *mWizard; + }; + +} + +#endif // INTROPAGE_HPP diff --git a/apps/wizard/languageselectionpage.cpp b/apps/wizard/languageselectionpage.cpp new file mode 100644 index 000000000..0d5132f5b --- /dev/null +++ b/apps/wizard/languageselectionpage.cpp @@ -0,0 +1,51 @@ +#include "languageselectionpage.hpp" + +#include "mainwizard.hpp" + +#include + +Wizard::LanguageSelectionPage::LanguageSelectionPage(QWidget *parent) : + QWizardPage(parent) +{ + mWizard = qobject_cast(parent); + + setupUi(this); + + registerField(QLatin1String("installation.language"), languageComboBox); +} + +void Wizard::LanguageSelectionPage::initializePage() +{ + QStringList languages; + languages << QLatin1String("English") + << QLatin1String("French") + << QLatin1String("German") + << QLatin1String("Italian") + << QLatin1String("Polish") + << QLatin1String("Russian") + << QLatin1String("Spanish"); + + languageComboBox->addItems(languages); +} + +int Wizard::LanguageSelectionPage::nextId() const +{ + if (field(QLatin1String("installation.new")).toBool() == true) { + return MainWizard::Page_ComponentSelection; + } else { + QString path(field(QLatin1String("installation.path")).toString()); + + if (path.isEmpty()) + return MainWizard::Page_ComponentSelection; + + // Check if we have to install something + if (mWizard->mInstallations[path].hasMorrowind == true && + mWizard->mInstallations[path].hasTribunal == true && + mWizard->mInstallations[path].hasBloodmoon == true) + { + return MainWizard::Page_Import; + } else { + return MainWizard::Page_ComponentSelection; + } + } +} diff --git a/apps/wizard/languageselectionpage.hpp b/apps/wizard/languageselectionpage.hpp new file mode 100644 index 000000000..abc3edb98 --- /dev/null +++ b/apps/wizard/languageselectionpage.hpp @@ -0,0 +1,28 @@ +#ifndef LANGUAGESELECTIONPAGE_HPP +#define LANGUAGESELECTIONPAGE_HPP + +#include + +#include "ui_languageselectionpage.h" + +namespace Wizard +{ + class MainWizard; + + class LanguageSelectionPage : public QWizardPage, private Ui::LanguageSelectionPage + { + Q_OBJECT + public: + LanguageSelectionPage(QWidget *parent); + + int nextId() const; + + private: + MainWizard *mWizard; + + protected: + void initializePage(); + }; +} + +#endif // LANGUAGESELECTIONPAGE_HPP diff --git a/apps/wizard/main.cpp b/apps/wizard/main.cpp new file mode 100644 index 000000000..e6a94118a --- /dev/null +++ b/apps/wizard/main.cpp @@ -0,0 +1,48 @@ +#include +#include +#include +#include + +#include "mainwizard.hpp" + +#ifdef MAC_OS_X_VERSION_MIN_REQUIRED +#undef MAC_OS_X_VERSION_MIN_REQUIRED +// We need to do this because of Qt: https://bugreports.qt-project.org/browse/QTBUG-22154 +#define MAC_OS_X_VERSION_MIN_REQUIRED __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ +#endif // MAC_OS_X_VERSION_MIN_REQUIRED + +int main(int argc, char *argv[]) +{ + + QApplication app(argc, argv); + + // Now we make sure the current dir is set to application path + QDir dir(QCoreApplication::applicationDirPath()); + + #ifdef Q_OS_MAC + if (dir.dirName() == "MacOS") { + dir.cdUp(); + dir.cdUp(); + dir.cdUp(); + } + + // force Qt to load only LOCAL plugins, don't touch system Qt installation + QDir pluginsPath(QCoreApplication::applicationDirPath()); + pluginsPath.cdUp(); + pluginsPath.cd("Plugins"); + + QStringList libraryPaths; + libraryPaths << pluginsPath.path() << QCoreApplication::applicationDirPath(); + app.setLibraryPaths(libraryPaths); + #endif + + QDir::setCurrent(dir.absolutePath()); + + // Support non-latin characters + QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8")); + + Wizard::MainWizard wizard; + + wizard.show(); + return app.exec(); +} diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp new file mode 100644 index 000000000..d539793ec --- /dev/null +++ b/apps/wizard/mainwizard.cpp @@ -0,0 +1,457 @@ +#include "mainwizard.hpp" + +#include + +#include +#include +#include +#include +#include +#include + +#include "intropage.hpp" +#include "methodselectionpage.hpp" +#include "languageselectionpage.hpp" +#include "existinginstallationpage.hpp" +#include "installationtargetpage.hpp" +#include "componentselectionpage.hpp" +#include "installationpage.hpp" +#include "importpage.hpp" +#include "conclusionpage.hpp" + +using namespace Process; + +Wizard::MainWizard::MainWizard(QWidget *parent) : + mGameSettings(mCfgMgr), + QWizard(parent), + mError(false), + mInstallations() +{ +#ifndef Q_OS_MAC + setWizardStyle(QWizard::ModernStyle); +#else + setWizardStyle(QWizard::ClassicStyle); +#endif + + setWindowTitle(tr("OpenMW Wizard")); + setWindowIcon(QIcon(QLatin1String(":/images/openmw-wizard.png"))); + setMinimumWidth(550); + + // Set the property for comboboxes to the text instead of index + setDefaultProperty("QComboBox", "currentText", "currentIndexChanged"); + + setDefaultProperty("ComponentListWidget", "mCheckedItems", "checkedItemsChanged"); + + mImporterInvoker = new ProcessInvoker(); + + connect(mImporterInvoker->getProcess(), SIGNAL(started()), + this, SLOT(importerStarted())); + + connect(mImporterInvoker->getProcess(), SIGNAL(finished(int,QProcess::ExitStatus)), + this, SLOT(importerFinished(int,QProcess::ExitStatus))); + + mLogError = tr("

Could not open %1 for writing

\ +

Please make sure you have the right permissions \ + and try again.

"); + + setupLog(); + setupGameSettings(); + setupLauncherSettings(); + setupInstallations(); + setupPages(); +} + +Wizard::MainWizard::~MainWizard() +{ + delete mImporterInvoker; +} + +void Wizard::MainWizard::setupLog() +{ + QString logPath(QString::fromUtf8(mCfgMgr.getLogPath().string().c_str())); + logPath.append(QLatin1String("wizard.log")); + + QFile file(logPath); + + if (!file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error opening Wizard log file")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(mLogError.arg(file.fileName())); + msgBox.exec(); + return qApp->quit(); + } + + addLogText(QString("Started OpenMW Wizard on %1").arg(QDateTime::currentDateTime().toString())); + + qDebug() << logPath; +} + +void Wizard::MainWizard::addLogText(const QString &text) +{ + QString logPath(QString::fromUtf8(mCfgMgr.getLogPath().string().c_str())); + logPath.append(QLatin1String("wizard.log")); + + QFile file(logPath); + + if (!file.open(QIODevice::ReadWrite | QIODevice::Text)) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error opening Wizard log file")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(mLogError.arg(file.fileName())); + msgBox.exec(); + return qApp->quit(); + } + + if (!file.isSequential()) + file.seek(file.size()); + + QTextStream out(&file); + + if (!text.isEmpty()) + out << text << endl; + +// file.close(); +} + +void Wizard::MainWizard::setupGameSettings() +{ + QString userPath(QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str())); + QString globalPath(QString::fromUtf8(mCfgMgr.getGlobalPath().string().c_str())); + QString message(tr("

Could not open %1 for reading

\ +

Please make sure you have the right permissions \ + and try again.

")); + + // Load the user config file first, separately + // So we can write it properly, uncontaminated + QString path(userPath + QLatin1String("openmw.cfg")); + QFile file(path); + + qDebug() << "Loading config file:" << qPrintable(path); + + if (file.exists()) { + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error opening OpenMW configuration file")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(message.arg(file.fileName())); + msgBox.exec(); + return qApp->quit(); + } + QTextStream stream(&file); + stream.setCodec(QTextCodec::codecForName("UTF-8")); + + mGameSettings.readUserFile(stream); + } + + // Now the rest + QStringList paths; + paths.append(userPath + QLatin1String("openmw.cfg")); + paths.append(QLatin1String("openmw.cfg")); + paths.append(globalPath + QLatin1String("openmw.cfg")); + + foreach (const QString &path, paths) { + qDebug() << "Loading config file:" << qPrintable(path); + + QFile file(path); + if (file.exists()) { + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error opening OpenMW configuration file")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(message.arg(file.fileName())); + + return qApp->quit(); + } + QTextStream stream(&file); + stream.setCodec(QTextCodec::codecForName("UTF-8")); + + mGameSettings.readFile(stream); + } + file.close(); + } +} + +void Wizard::MainWizard::setupLauncherSettings() +{ + QString path(QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str())); + path.append(QLatin1String("launcher.cfg")); + + QString message(tr("

Could not open %1 for reading

\ +

Please make sure you have the right permissions \ + and try again.

")); + + + QFile file(path); + + qDebug() << "Loading config file:" << qPrintable(path); + + if (file.exists()) { + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error opening OpenMW configuration file")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(message.arg(file.fileName())); + msgBox.exec(); + return qApp->quit(); + } + QTextStream stream(&file); + stream.setCodec(QTextCodec::codecForName("UTF-8")); + + mLauncherSettings.readFile(stream); + } + + file.close(); + +} + +void Wizard::MainWizard::setupInstallations() +{ + // Check if the paths actually contain a Morrowind installation + foreach (const QString path, mGameSettings.getDataDirs()) { + + if (findFiles(QLatin1String("Morrowind"), path)) + addInstallation(path); + } +} + +void Wizard::MainWizard::runSettingsImporter() +{ + QString path(field(QLatin1String("installation.path")).toString()); + + // Create the file if it doesn't already exist, else the importer will fail + QString userPath(QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str())); + QFile file(userPath + QLatin1String("openmw.cfg")); + + if (!file.exists()) { + if (!file.open(QIODevice::ReadWrite)) { + // File cannot be created + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error writing OpenMW configuration file")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("

Could not open or create %1 for writing

\ +

Please make sure you have the right permissions \ + and try again.

").arg(file.fileName())); + msgBox.exec(); + return qApp->quit(); + } + + file.close(); + } + + // Construct the arguments to run the importer + QStringList arguments; + + // Import plugin selection? + if (field(QLatin1String("installation.new")).toBool() == true + || field(QLatin1String("installation.import-addons")).toBool() == true) + arguments.append(QLatin1String("--game-files")); + + arguments.append(QLatin1String("--encoding")); + + // Set encoding + QString language(field(QLatin1String("installation.language")).toString()); + + if (language == QLatin1String("Polish")) { + arguments.append(QLatin1String("win1250")); + } else if (language == QLatin1String("Russian")) { + arguments.append(QLatin1String("win1251")); + } else { + arguments.append(QLatin1String("win1252")); + } + + // Now the paths + arguments.append(QLatin1String("--ini")); + + if (field(QLatin1String("installation.new")).toBool() == true) { + arguments.append(path + QDir::separator() + QLatin1String("Morrowind.ini")); + } else { + arguments.append(mInstallations[path].iniPath); + } + + arguments.append(QLatin1String("--cfg")); + arguments.append(userPath + QLatin1String("openmw.cfg")); + + if (!mImporterInvoker->startProcess(QLatin1String("mwiniimport"), arguments, false)) + return qApp->quit(); +} + +void Wizard::MainWizard::addInstallation(const QString &path) +{ + qDebug() << "add installation in: " << path; + Installation install;// = new Installation(); + + install.hasMorrowind = findFiles(QLatin1String("Morrowind"), path); + install.hasTribunal = findFiles(QLatin1String("Tribunal"), path); + install.hasBloodmoon = findFiles(QLatin1String("Bloodmoon"), path); + + // Try to autodetect the Morrowind.ini location + QDir dir(path); + QFile file(dir.filePath("Morrowind.ini")); + + // Try the parent directory + // In normal Morrowind installations that's where Morrowind.ini is + if (!file.exists()) { + dir.cdUp(); + file.setFileName(dir.filePath(QLatin1String("Morrowind.ini"))); + } + + if (file.exists()) + install.iniPath = file.fileName(); + + mInstallations.insert(QDir::toNativeSeparators(path), install); + + // Add it to the openmw.cfg too + if (!mGameSettings.getDataDirs().contains(path)) { + mGameSettings.setMultiValue(QLatin1String("data"), path); + mGameSettings.addDataDir(path); + } +} + +void Wizard::MainWizard::setupPages() +{ + setPage(Page_Intro, new IntroPage(this)); + setPage(Page_MethodSelection, new MethodSelectionPage(this)); + setPage(Page_LanguageSelection, new LanguageSelectionPage(this)); + setPage(Page_ExistingInstallation, new ExistingInstallationPage(this)); + setPage(Page_InstallationTarget, new InstallationTargetPage(this, mCfgMgr)); + setPage(Page_ComponentSelection, new ComponentSelectionPage(this)); + setPage(Page_Installation, new InstallationPage(this)); + setPage(Page_Import, new ImportPage(this)); + setPage(Page_Conclusion, new ConclusionPage(this)); + setStartId(Page_Intro); + +} + +void Wizard::MainWizard::importerStarted() +{ +} + +void Wizard::MainWizard::importerFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + if (exitCode != 0 || exitStatus == QProcess::CrashExit) + return; + + // Re-read the settings + setupGameSettings(); +} + +void Wizard::MainWizard::accept() +{ + writeSettings(); + QWizard::accept(); +} + +void Wizard::MainWizard::reject() +{ + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Quit Wizard")); + msgBox.setIcon(QMessageBox::Question); + msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + msgBox.setText(tr("Are you sure you want to exit the Wizard?")); + + if (msgBox.exec() == QMessageBox::Yes) { + QWizard::reject(); + } +} + +void Wizard::MainWizard::writeSettings() +{ + // Write the encoding and language settings + QString language(field(QLatin1String("installation.language")).toString()); + mLauncherSettings.setValue(QLatin1String("Settings/language"), language); + + if (language == QLatin1String("Polish")) { + mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1250")); + } else if (language == QLatin1String("Russian")) { + mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1251")); + } else { + mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1252")); + } + + // Write the installation path so that openmw can find them + QString path(field(QLatin1String("installation.path")).toString()); + + // Make sure the installation path is the last data= entry + mGameSettings.removeDataDir(path); + mGameSettings.addDataDir(path); + + QString userPath(QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str())); + QDir dir(userPath); + + if (!dir.exists()) { + if (!dir.mkpath(userPath)) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error creating OpenMW configuration directory")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("

Could not create %1

\ +

Please make sure you have the right permissions \ + and try again.

").arg(userPath)); + msgBox.exec(); + return qApp->quit(); + } + } + + // Game settings + QFile file(userPath + QLatin1String("openmw.cfg")); + + if (!file.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate)) { + // File cannot be opened or created + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error writing OpenMW configuration file")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("

Could not open %1 for writing

\ +

Please make sure you have the right permissions \ + and try again.

").arg(file.fileName())); + msgBox.exec(); + return qApp->quit(); + } + + QTextStream stream(&file); + stream.setCodec(QTextCodec::codecForName("UTF-8")); + + mGameSettings.writeFile(stream); + file.close(); + + // Launcher settings + file.setFileName(userPath + QLatin1String("launcher.cfg")); + + if (!file.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate)) { + // File cannot be opened or created + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error writing OpenMW configuration file")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("

Could not open %1 for writing

\ +

Please make sure you have the right permissions \ + and try again.

").arg(file.fileName())); + msgBox.exec(); + return qApp->quit(); + } + + stream.setDevice(&file); + stream.setCodec(QTextCodec::codecForName("UTF-8")); + + mLauncherSettings.writeFile(stream); + file.close(); +} + +bool Wizard::MainWizard::findFiles(const QString &name, const QString &path) +{ + QDir dir(path); + + if (!dir.exists()) + return false; + + // TODO: add MIME handling to make sure the files are real + return (dir.entryList().contains(name + QLatin1String(".esm"), Qt::CaseInsensitive) + && dir.entryList().contains(name + QLatin1String(".bsa"), Qt::CaseInsensitive)); +} diff --git a/apps/wizard/mainwizard.hpp b/apps/wizard/mainwizard.hpp new file mode 100644 index 000000000..c22f20c25 --- /dev/null +++ b/apps/wizard/mainwizard.hpp @@ -0,0 +1,88 @@ +#ifndef MAINWIZARD_HPP +#define MAINWIZARD_HPP + +#include +#include +#include + +#include + +#ifndef Q_MOC_RUN +#include +#endif +#include +#include + +namespace Wizard +{ + class MainWizard : public QWizard + { + Q_OBJECT + + public: + struct Installation { + bool hasMorrowind; + bool hasTribunal; + bool hasBloodmoon; + + QString iniPath; + }; + + enum { + Page_Intro, + Page_MethodSelection, + Page_LanguageSelection, + Page_ExistingInstallation, + Page_InstallationTarget, + Page_ComponentSelection, + Page_Installation, + Page_Import, + Page_Conclusion + }; + + MainWizard(QWidget *parent = 0); + ~MainWizard(); + + bool findFiles(const QString &name, const QString &path); + void addInstallation(const QString &path); + void runSettingsImporter(); + + QMap mInstallations; + + Files::ConfigurationManager mCfgMgr; + + Process::ProcessInvoker *mImporterInvoker; + + bool mError; + + public slots: + void addLogText(const QString &text); + + private: + + void setupLog(); + void setupGameSettings(); + void setupLauncherSettings(); + void setupInstallations(); + void setupPages(); + + void writeSettings(); + + Config::GameSettings mGameSettings; + Config::LauncherSettings mLauncherSettings; + + QString mLogError; + + private slots: + + void importerStarted(); + void importerFinished(int exitCode, QProcess::ExitStatus exitStatus); + + void accept(); + void reject(); + + }; + +} + +#endif // MAINWIZARD_HPP diff --git a/apps/wizard/methodselectionpage.cpp b/apps/wizard/methodselectionpage.cpp new file mode 100644 index 000000000..d7c64f3b0 --- /dev/null +++ b/apps/wizard/methodselectionpage.cpp @@ -0,0 +1,22 @@ +#include "methodselectionpage.hpp" +#include +#include "mainwizard.hpp" + +Wizard::MethodSelectionPage::MethodSelectionPage(QWidget *parent) : + QWizardPage(parent) +{ + mWizard = qobject_cast(parent); + + setupUi(this); + + registerField(QLatin1String("installation.new"), newLocationRadioButton); +} + +int Wizard::MethodSelectionPage::nextId() const +{ + if (field(QLatin1String("installation.new")).toBool() == true) { + return MainWizard::Page_InstallationTarget; + } else { + return MainWizard::Page_ExistingInstallation; + } +} diff --git a/apps/wizard/methodselectionpage.hpp b/apps/wizard/methodselectionpage.hpp new file mode 100644 index 000000000..60941c651 --- /dev/null +++ b/apps/wizard/methodselectionpage.hpp @@ -0,0 +1,27 @@ +#ifndef METHODSELECTIONPAGE_HPP +#define METHODSELECTIONPAGE_HPP + +#include + +#include "ui_methodselectionpage.h" + +namespace Wizard +{ + class MainWizard; + + class MethodSelectionPage : public QWizardPage, private Ui::MethodSelectionPage + { + Q_OBJECT + public: + MethodSelectionPage(QWidget *parent); + + int nextId() const; + + private: + MainWizard *mWizard; + + }; + +} + +#endif // METHODSELECTIONPAGE_HPP diff --git a/apps/wizard/unshield/unshieldworker.cpp b/apps/wizard/unshield/unshieldworker.cpp new file mode 100644 index 000000000..5fa043347 --- /dev/null +++ b/apps/wizard/unshield/unshieldworker.cpp @@ -0,0 +1,923 @@ +#include "unshieldworker.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +Wizard::UnshieldWorker::UnshieldWorker(QObject *parent) : + QObject(parent), + mIniSettings() +{ + unshield_set_log_level(0); + + mPath = QString(); + mIniPath = QString(); + mDiskPath = QString(); + + // Default to Latin encoding + mIniCodec = QTextCodec::codecForName("windows-1252"); + + mInstallMorrowind = false; + mInstallTribunal = false; + mInstallBloodmoon = false; + + mMorrowindDone = false; + mTribunalDone = false; + mBloodmoonDone = false; + + mStopped = false; + + qRegisterMetaType("Wizard::Component"); +} + +Wizard::UnshieldWorker::~UnshieldWorker() +{ +} + +void Wizard::UnshieldWorker::stopWorker() +{ + mMutex.lock(); + mStopped = true; + mMutex.unlock(); +} + +void Wizard::UnshieldWorker::setInstallComponent(Wizard::Component component, bool install) +{ + QWriteLocker writeLock(&mLock); + switch (component) { + + case Wizard::Component_Morrowind: + mInstallMorrowind = install; + break; + case Wizard::Component_Tribunal: + mInstallTribunal = install; + break; + case Wizard::Component_Bloodmoon: + mInstallBloodmoon = install; + break; + } +} + +bool Wizard::UnshieldWorker::getInstallComponent(Component component) +{ + QReadLocker readLock(&mLock); + switch (component) { + + case Wizard::Component_Morrowind: + return mInstallMorrowind; + case Wizard::Component_Tribunal: + return mInstallTribunal; + case Wizard::Component_Bloodmoon: + return mInstallBloodmoon; + } + + return false; +} + +void Wizard::UnshieldWorker::setComponentDone(Component component, bool done) +{ + QWriteLocker writeLock(&mLock); + switch (component) { + + case Wizard::Component_Morrowind: + mMorrowindDone = done; + break; + case Wizard::Component_Tribunal: + mTribunalDone = done; + break; + case Wizard::Component_Bloodmoon: + mBloodmoonDone = done; + break; + } +} + +bool Wizard::UnshieldWorker::getComponentDone(Component component) +{ + QReadLocker readLock(&mLock); + switch (component) + { + + case Wizard::Component_Morrowind: + return mMorrowindDone; + case Wizard::Component_Tribunal: + return mTribunalDone; + case Wizard::Component_Bloodmoon: + return mBloodmoonDone; + } + + return false; +} + +void Wizard::UnshieldWorker::setPath(const QString &path) +{ + QWriteLocker writeLock(&mLock); + mPath = path; +} + +void Wizard::UnshieldWorker::setIniPath(const QString &path) +{ + QWriteLocker writeLock(&mLock); + mIniPath = path; +} + +void Wizard::UnshieldWorker::setDiskPath(const QString &path) +{ + QWriteLocker writeLock(&mLock); + mDiskPath = path; + mWait.wakeAll(); +} + +QString Wizard::UnshieldWorker::getPath() +{ + QReadLocker readLock(&mLock); + return mPath; +} + +QString Wizard::UnshieldWorker::getIniPath() +{ + QReadLocker readLock(&mLock); + return mIniPath; +} + +QString Wizard::UnshieldWorker::getDiskPath() +{ + QReadLocker readLock(&mLock); + return mDiskPath; +} + + +void Wizard::UnshieldWorker::setIniCodec(QTextCodec *codec) +{ + QWriteLocker writeLock(&mLock); + mIniCodec = codec; +} + +bool Wizard::UnshieldWorker::setupSettings() +{ + // Create Morrowind.ini settings map + if (getIniPath().isEmpty()) + return false; + + QFile file(getIniPath()); + + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + emit error(tr("Failed to open Morrowind configuration file!"), + tr("Opening %1 failed: %2.").arg(getIniPath(), file.errorString())); + return false; + } + + QTextStream stream(&file); + stream.setCodec(mIniCodec); + + mIniSettings.readFile(stream); + + return true; +} + +bool Wizard::UnshieldWorker::writeSettings() +{ + if (getIniPath().isEmpty()) + return false; + + QFile file(getIniPath()); + + if (!file.open(QIODevice::ReadWrite | QIODevice::Text)) { + emit error(tr("Failed to open Morrowind configuration file!"), + tr("Opening %1 failed: %2.").arg(getIniPath(), file.errorString())); + return false; + } + + QTextStream stream(&file); + stream.setCodec(mIniCodec); + + if (!mIniSettings.writeFile(getIniPath(), stream)) { + emit error(tr("Failed to write Morrowind configuration file!"), + tr("Writing to %1 failed: %2.").arg(getIniPath(), file.errorString())); + return false; + } + + return true; +} + +bool Wizard::UnshieldWorker::removeDirectory(const QString &dirName) +{ + bool result = false; + QDir dir(dirName); + + if (dir.exists(dirName)) + { + QFileInfoList list(dir.entryInfoList(QDir::NoDotAndDotDot | + QDir::System | QDir::Hidden | + QDir::AllDirs | QDir::Files, QDir::DirsFirst)); + foreach(QFileInfo info, list) { + if (info.isDir()) { + result = removeDirectory(info.absoluteFilePath()); + } else { + result = QFile::remove(info.absoluteFilePath()); + } + + if (!result) + return result; + } + + result = dir.rmdir(dirName); + } + return result; +} + +bool Wizard::UnshieldWorker::copyFile(const QString &source, const QString &destination, bool keepSource) +{ + QDir dir; + QFile file; + + QFileInfo info(destination); + + if (info.exists()) { + if (!dir.remove(info.absoluteFilePath())) + return false; + } + + if (file.copy(source, destination)) { + if (!keepSource) { + if (!file.remove(source)) + return false; + } else { + return true; + } + } else { + return false; + } + + return true; +} + +bool Wizard::UnshieldWorker::copyDirectory(const QString &source, const QString &destination, bool keepSource) +{ + QDir sourceDir(source); + QDir destDir(destination); + bool result = true; + + if (!destDir.exists()) { + if (!sourceDir.mkpath(destination)) + return false; + } + + destDir.refresh(); + + if (!destDir.exists()) + return false; + + QFileInfoList list(sourceDir.entryInfoList(QDir::NoDotAndDotDot | + QDir::System | QDir::Hidden | + QDir::AllDirs | QDir::Files, QDir::DirsFirst)); + + foreach (const QFileInfo &info, list) { + QString relativePath(info.absoluteFilePath()); + relativePath.remove(source); + + QString destinationPath(destDir.absolutePath() + relativePath); + + if (info.isSymLink()) + continue; + + if (info.isDir()) { + result = copyDirectory(info.absoluteFilePath(), destinationPath); + } else { + result = copyFile(info.absoluteFilePath(), destinationPath); + } + } + + if (!keepSource) + return result && removeDirectory(sourceDir.absolutePath()); + + return result; +} + +bool Wizard::UnshieldWorker::installFile(const QString &fileName, const QString &path, Qt::MatchFlags flags, bool keepSource) +{ + return installFiles(fileName, path, flags, true, keepSource); +} + +bool Wizard::UnshieldWorker::installFiles(const QString &fileName, const QString &path, Qt::MatchFlags flags, bool keepSource, bool single) +{ + QDir dir(path); + + if (!dir.exists()) + return false; + + QStringList files(findFiles(fileName, path, flags)); + + foreach (const QString &file, files) { + QFileInfo info(file); + emit textChanged(tr("Installing: %1").arg(info.fileName())); + + if (single) { + return copyFile(info.absoluteFilePath(), getPath() + QDir::separator() + info.fileName(), keepSource); + } else { + if (!copyFile(info.absoluteFilePath(), getPath() + QDir::separator() + info.fileName(), keepSource)) + return false; + } + } + + return true; +} + +bool Wizard::UnshieldWorker::installDirectories(const QString &dirName, const QString &path, bool recursive, bool keepSource) +{ + QDir dir(path); + + if (!dir.exists()) + return false; + + QStringList directories(findDirectories(dirName, path, recursive)); + + foreach (const QString &dir, directories) { + QFileInfo info(dir); + emit textChanged(tr("Installing: %1 directory").arg(info.fileName())); + if (!copyDirectory(info.absoluteFilePath(), getPath() + QDir::separator() + info.fileName(), keepSource)) + return false; + } + + return true; +} + +void Wizard::UnshieldWorker::extract() +{ + if (getInstallComponent(Wizard::Component_Morrowind)) + { + if (!getComponentDone(Wizard::Component_Morrowind)) + if (!setupComponent(Wizard::Component_Morrowind)) + return; + } + + if (getInstallComponent(Wizard::Component_Tribunal)) + { + if (!getComponentDone(Wizard::Component_Tribunal)) + if (!setupComponent(Wizard::Component_Tribunal)) + return; + } + + if (getInstallComponent(Wizard::Component_Bloodmoon)) + { + if (!getComponentDone(Wizard::Component_Bloodmoon)) + if (!setupComponent(Wizard::Component_Bloodmoon)) + return; + } + + // Update Morrowind configuration + if (getInstallComponent(Wizard::Component_Tribunal)) + { + mIniSettings.setValue(QLatin1String("Archives/Archive 0"), QVariant(QString("Tribunal.bsa"))); + mIniSettings.setValue(QLatin1String("Game Files/GameFile1"), QVariant(QString("Tribunal.esm"))); + } + + if (getInstallComponent(Wizard::Component_Bloodmoon)) + { + mIniSettings.setValue(QLatin1String("Archives/Archive 0"), QVariant(QString("Bloodmoon.bsa"))); + mIniSettings.setValue(QLatin1String("Game Files/GameFile1"), QVariant(QString("Bloodmoon.esm"))); + } + + if (getInstallComponent(Wizard::Component_Tribunal) && + getInstallComponent(Wizard::Component_Bloodmoon)) + { + mIniSettings.setValue(QLatin1String("Archives/Archive 0"), QVariant(QString("Tribunal.bsa"))); + mIniSettings.setValue(QLatin1String("Archives/Archive 1"), QVariant(QString("Bloodmoon.bsa"))); + mIniSettings.setValue(QLatin1String("Game Files/GameFile1"), QVariant(QString("Tribunal.esm"))); + mIniSettings.setValue(QLatin1String("Game Files/GameFile2"), QVariant(QString("Bloodmoon.esm"))); + } + + // Write the settings to the Morrowind config file + if (!writeSettings()) + return; + + // Remove the temporary directory + removeDirectory(getPath() + QDir::separator() + QLatin1String("extract-temp")); + + // Fill the progress bar + int total = 0; + + if (getInstallComponent(Wizard::Component_Morrowind)) + total = 100; + + if (getInstallComponent(Wizard::Component_Tribunal)) + total = total + 100; + + if (getInstallComponent(Wizard::Component_Bloodmoon)) + total = total + 100; + + emit textChanged(tr("Installation finished!")); + emit progressChanged(total); + emit finished(); +} + +bool Wizard::UnshieldWorker::setupComponent(Component component) +{ + QString name; + switch (component) { + + case Wizard::Component_Morrowind: + name = QLatin1String("Morrowind"); + break; + case Wizard::Component_Tribunal: + name = QLatin1String("Tribunal"); + break; + case Wizard::Component_Bloodmoon: + name = QLatin1String("Bloodmoon"); + break; + } + + if (name.isEmpty()) { + emit error(tr("Component parameter is invalid!"), tr("An invalid component parameter was supplied.")); + return false; + } + + bool found = false; + QString cabFile; + QDir disk; + + // Keep showing the file dialog until we find the necessary install files + while (!found) { + if (getDiskPath().isEmpty()) { + QReadLocker readLock(&mLock); + emit requestFileDialog(component); + mWait.wait(&mLock); + disk.setPath(getDiskPath()); + } else { + disk.setPath(getDiskPath()); + } + + QStringList list(findFiles(QLatin1String("data1.hdr"), disk.absolutePath())); + + foreach (const QString &file, list) { + + qDebug() << "current archive: " << file; + + if (component == Wizard::Component_Morrowind) + { + bool morrowindFound = findInCab(QLatin1String("Morrowind.bsa"), file); + bool tribunalFound = findInCab(QLatin1String("Tribunal.bsa"), file); + bool bloodmoonFound = findInCab(QLatin1String("Bloodmoon.bsa"), file); + + if (morrowindFound) { + // Check if we have correct archive, other archives have Morrowind.bsa too + if ((tribunalFound && bloodmoonFound) + || (!tribunalFound && !bloodmoonFound)) { + cabFile = file; + found = true; // We have a GoTY disk or a Morrowind-only disk + } + } + } else { + + if (findInCab(name + QLatin1String(".bsa"), file)) { + cabFile = file; + found = true; + } + } + + } + + if (!found) { + QReadLocker readLock(&mLock); + emit requestFileDialog(component); + mWait.wait(&mLock); + } + } + + if (installComponent(component, cabFile)) { + setComponentDone(component, true); + return true; + } else { + return false; + } + + return true; +} + +bool Wizard::UnshieldWorker::installComponent(Component component, const QString &path) +{ + QString name; + switch (component) { + + case Wizard::Component_Morrowind: + name = QLatin1String("Morrowind"); + break; + case Wizard::Component_Tribunal: + name = QLatin1String("Tribunal"); + break; + case Wizard::Component_Bloodmoon: + name = QLatin1String("Bloodmoon"); + break; + } + + if (name.isEmpty()) { + emit error(tr("Component parameter is invalid!"), tr("An invalid component parameter was supplied.")); + return false; + } + + emit textChanged(tr("Installing %1").arg(name)); + + QFileInfo info(path); + + if (!info.exists()) { + emit error(tr("Installation media path not set!"), tr("The source path for %1 was not set.").arg(name)); + return false; + } + + // Create temporary extract directory + // TODO: Use QTemporaryDir in Qt 5.0 + QString tempPath(getPath() + QDir::separator() + QLatin1String("extract-temp")); + QDir temp; + + // Make sure the temporary folder is empty + removeDirectory(tempPath); + + if (!temp.mkpath(tempPath)) { + emit error(tr("Cannot create temporary directory!"), tr("Failed to create %1.").arg(tempPath)); + return false; + } + + temp.setPath(tempPath); + + if (!temp.mkdir(name)) { + emit error(tr("Cannot create temporary directory!"), tr("Failed to create %1.").arg(temp.absoluteFilePath(name))); + return false; + } + + if (!temp.cd(name)) { + emit error(tr("Cannot move into temporary directory!"), tr("Failed to move into %1.").arg(temp.absoluteFilePath(name))); + return false; + } + + // Extract the installation files + if (!extractCab(info.absoluteFilePath(), temp.absolutePath())) + return false; + + // Move the files from the temporary path to the destination folder + emit textChanged(tr("Moving installation files")); + + // Install extracted directories + QStringList directories; + directories << QLatin1String("BookArt") + << QLatin1String("Fonts") + << QLatin1String("Icons") + << QLatin1String("Meshes") + << QLatin1String("Music") + << QLatin1String("Sound") + << QLatin1String("Splash") + << QLatin1String("Textures") + << QLatin1String("Video"); + + foreach (const QString &dir, directories) { + if (!installDirectories(dir, temp.absolutePath())) { + emit error(tr("Could not install directory!"), + tr("Installing %1 to %2 failed.").arg(dir, temp.absolutePath())); + return false; + } + } + + // Install directories from disk + foreach (const QString &dir, directories) { + if (!installDirectories(dir, info.absolutePath(), false, true)) { + emit error(tr("Could not install directory!"), + tr("Installing %1 to %2 failed.").arg(dir, info.absolutePath())); + return false; + } + + } + + // Install translation files + QStringList extensions; + extensions << QLatin1String(".cel") + << QLatin1String(".top") + << QLatin1String(".mrk"); + + foreach (const QString &extension, extensions) { + if (!installFiles(extension, info.absolutePath(), Qt::MatchEndsWith)) { + emit error(tr("Could not install translation file!"), + tr("Failed to install *%1 files.").arg(extension)); + return false; + } + } + + if (component == Wizard::Component_Morrowind) + { + QStringList files; + files << QLatin1String("Morrowind.esm") + << QLatin1String("Morrowind.bsa"); + + foreach (const QString &file, files) { + if (!installFile(file, temp.absolutePath())) { + emit error(tr("Could not install Morrowind data file!"), + tr("Failed to install %1.").arg(file)); + return false; + } + } + + // Copy Morrowind configuration file + if (!installFile(QLatin1String("Morrowind.ini"), temp.absolutePath())) { + emit error(tr("Could not install Morrowind configuration file!"), + tr("Failed to install %1.").arg(QLatin1String("Morrowind.ini"))); + return false; + } + + // Setup Morrowind configuration + setIniPath(getPath() + QDir::separator() + QLatin1String("Morrowind.ini")); + + if (!setupSettings()) + return false; + } + + if (component == Wizard::Component_Tribunal) + { + QFileInfo sounds(temp.absoluteFilePath(QLatin1String("Sounds"))); + QString dest(getPath() + QDir::separator() + QLatin1String("Sound")); + + if (sounds.exists()) { + emit textChanged(tr("Installing: Sound directory")); + if (!copyDirectory(sounds.absoluteFilePath(), dest)) { + emit error(tr("Could not install directory!"), + tr("Installing %1 to %2 failed.").arg(sounds.absoluteFilePath(), dest)); + return false; + } + + } + + QStringList files; + files << QLatin1String("Tribunal.esm") + << QLatin1String("Tribunal.bsa"); + + foreach (const QString &file, files) { + if (!installFile(file, temp.absolutePath())) { + emit error(tr("Could not find Tribunal data file!"), + tr("Failed to find %1.").arg(file)); + return false; + } + } + } + + if (component == Wizard::Component_Bloodmoon) + { + QFileInfo original(getPath() + QDir::separator() + QLatin1String("Tribunal.esm")); + + if (original.exists()) { + if (!installFile(QLatin1String("Tribunal.esm"), temp.absolutePath())) { + emit error(tr("Could not find Tribunal patch file!"), + tr("Failed to find %1.").arg(QLatin1String("Tribunal.esm"))); + return false; + } + } + + QStringList files; + files << QLatin1String("Bloodmoon.esm") + << QLatin1String("Bloodmoon.bsa"); + + foreach (const QString &file, files) { + if (!installFile(file, temp.absolutePath())) { + emit error(tr("Could not find Bloodmoon data file!"), + tr("Failed to find %1.").arg(file)); + return false; + } + } + + // Load Morrowind configuration settings from the setup script + QStringList list(findFiles(QLatin1String("setup.inx"), getDiskPath())); + + emit textChanged(tr("Updating Morrowind configuration file")); + + foreach (const QString &inx, list) { + mIniSettings.parseInx(inx); + } + } + + // Finally, install Data Files directories from temp and disk + QStringList datafiles(findDirectories(QLatin1String("Data Files"), temp.absolutePath())); + datafiles.append(findDirectories(QLatin1String("Data Files"), info.absolutePath())); + + foreach (const QString &dir, datafiles) { + QFileInfo info(dir); + emit textChanged(tr("Installing: %1 directory").arg(info.fileName())); + + if (!copyDirectory(info.absoluteFilePath(), getPath())) { + emit error(tr("Could not install directory!"), + tr("Installing %1 to %2 failed.").arg(info.absoluteFilePath(), getPath())); + return false; + } + } + + emit textChanged(tr("%1 installation finished!").arg(name)); + return true; + +} + +bool Wizard::UnshieldWorker::extractFile(Unshield *unshield, const QString &destination, const QString &prefix, int index, int counter) +{ + bool success = false; + QString path(destination); + path.append(QDir::separator()); + + int directory = unshield_file_directory(unshield, index); + + if (!prefix.isEmpty()) + path.append(prefix + QDir::separator()); + + if (directory >= 0) + path.append(QString::fromUtf8(unshield_directory_name(unshield, directory)) + QDir::separator()); + + // Ensure the path has the right separators + path.replace(QLatin1Char('\\'), QDir::separator()); + path = QDir::toNativeSeparators(path); + + // Ensure the target path exists + QDir dir; + dir.mkpath(path); + + QString fileName(path); + fileName.append(QString::fromUtf8(unshield_file_name(unshield, index))); + + // Calculate the percentage done + int progress = (((float) counter / (float) unshield_file_count(unshield)) * 100); + + if (getComponentDone(Wizard::Component_Morrowind)) + progress = progress + 100; + + if (getComponentDone(Wizard::Component_Tribunal)) + progress = progress + 100; + + emit textChanged(tr("Extracting: %1").arg(QString::fromUtf8(unshield_file_name(unshield, index)))); + emit progressChanged(progress); + + QByteArray array(fileName.toUtf8()); + success = unshield_file_save(unshield, index, array.constData()); + + if (!success) { + qDebug() << "error"; + dir.remove(fileName); + } + + return success; +} + +bool Wizard::UnshieldWorker::extractCab(const QString &cabFile, const QString &destination) +{ + bool success = false; + + QByteArray array(cabFile.toUtf8()); + + Unshield *unshield; + unshield = unshield_open(array.constData()); + + if (!unshield) { + emit error(tr("Failed to open InstallShield Cabinet File."), tr("Opening %1 failed.").arg(cabFile)); + unshield_close(unshield); + return false; + } + + int counter = 0; + + for (int i=0; ifirst_file; j<=group->last_file; ++j) + { + if (mStopped) { + qDebug() << "We're asked to stop!"; + + unshield_close(unshield); + return true; + } + + if (unshield_file_is_valid(unshield, j)) { + success = extractFile(unshield, destination, group->name, j, counter); + + if (!success) { + QString name(QString::fromUtf8(unshield_file_name(unshield, j))); + + emit error(tr("Failed to extract %1.").arg(name), + tr("Complete path: %1").arg(destination + QDir::separator() + name)); + + unshield_close(unshield); + return false; + } + + ++counter; + } + } + } + + unshield_close(unshield); + return success; +} + +QString Wizard::UnshieldWorker::findFile(const QString &fileName, const QString &path) +{ + return findFiles(fileName, path).first(); +} + +QStringList Wizard::UnshieldWorker::findFiles(const QString &fileName, const QString &path, int depth, bool recursive, + bool directories, Qt::MatchFlags flags) +{ + static const int MAXIMUM_DEPTH = 10; + + if (depth >= MAXIMUM_DEPTH) { + qWarning("Maximum directory depth limit reached."); + return QStringList(); + } + + QStringList result; + QDir dir(path); + + // Prevent parsing over the complete filesystem + if (dir == QDir::rootPath()) + return QStringList(); + + if (!dir.exists()) + return QStringList(); + + QFileInfoList list(dir.entryInfoList(QDir::NoDotAndDotDot | + QDir::AllDirs | QDir::Files, QDir::DirsFirst)); + foreach(QFileInfo info, list) { + if (info.isSymLink()) + continue; + + if (info.isDir()) { + if (directories) + { + if (info.fileName() == fileName) { + result.append(info.absoluteFilePath()); + } else { + if (recursive) + result.append(findFiles(fileName, info.absoluteFilePath(), depth + 1, recursive, true)); + } + } else { + if (recursive) + result.append(findFiles(fileName, info.absoluteFilePath(), depth + 1)); + } + } else { + if (directories) + break; + + switch (flags) { + case Qt::MatchExactly: + if (info.fileName() == fileName) + result.append(info.absoluteFilePath()); + break; + case Qt::MatchEndsWith: + if (info.fileName().endsWith(fileName)) + result.append(info.absoluteFilePath()); + break; + } + } + } + + return result; +} + +QStringList Wizard::UnshieldWorker::findDirectories(const QString &dirName, const QString &path, bool recursive) +{ + return findFiles(dirName, path, 0, true, true); +} + +bool Wizard::UnshieldWorker::findInCab(const QString &fileName, const QString &cabFile) +{ + QByteArray array(cabFile.toUtf8()); + + Unshield *unshield; + unshield = unshield_open(array.constData()); + + if (!unshield) { + emit error(tr("Failed to open InstallShield Cabinet File."), tr("Opening %1 failed.").arg(cabFile)); + unshield_close(unshield); + return false; + } + + for (int i=0; ifirst_file; j<=group->last_file; ++j) + { + + if (unshield_file_is_valid(unshield, j)) { + QString current(QString::fromUtf8(unshield_file_name(unshield, j))); + if (current.toLower() == fileName.toLower()) { + unshield_close(unshield); + return true; // File is found! + } + } + } + } + + unshield_close(unshield); + return false; +} diff --git a/apps/wizard/unshield/unshieldworker.hpp b/apps/wizard/unshield/unshieldworker.hpp new file mode 100644 index 000000000..5ea7b04ae --- /dev/null +++ b/apps/wizard/unshield/unshieldworker.hpp @@ -0,0 +1,127 @@ +#ifndef UNSHIELDWORKER_HPP +#define UNSHIELDWORKER_HPP + +#include +#include +#include +#include +#include +#include + +#include + +#include "../inisettings.hpp" + + +namespace Wizard +{ + enum Component { + Component_Morrowind, + Component_Tribunal, + Component_Bloodmoon + }; + + class UnshieldWorker : public QObject + { + Q_OBJECT + Q_ENUMS(Wizard::Component) + + public: + UnshieldWorker(QObject *parent = 0); + ~UnshieldWorker(); + + void stopWorker(); + + void setInstallComponent(Wizard::Component component, bool install); + + void setDiskPath(const QString &path); + + void setPath(const QString &path); + void setIniPath(const QString &path); + + QString getPath(); + QString getIniPath(); + + void setIniCodec(QTextCodec *codec); + + bool setupSettings(); + + private: + + bool writeSettings(); + + bool getInstallComponent(Component component); + + QString getDiskPath(); + + void setComponentDone(Component component, bool done = true); + bool getComponentDone(Component component); + + bool removeDirectory(const QString &dirName); + + bool copyFile(const QString &source, const QString &destination, bool keepSource = true); + bool copyDirectory(const QString &source, const QString &destination, bool keepSource = true); + + bool extractCab(const QString &cabFile, const QString &destination); + bool extractFile(Unshield *unshield, const QString &destination, const QString &prefix, int index, int counter); + + bool findInCab(const QString &fileName, const QString &cabFile); + + QString findFile(const QString &fileName, const QString &path); + + QStringList findFiles(const QString &fileName, const QString &path, int depth = 0, bool recursive = true, + bool directories = false, Qt::MatchFlags flags = Qt::MatchExactly); + + QStringList findDirectories(const QString &dirName, const QString &path, bool recursive = true); + + bool installFile(const QString &fileName, const QString &path, Qt::MatchFlags flags = Qt::MatchExactly, + bool keepSource = false); + + bool installFiles(const QString &fileName, const QString &path, Qt::MatchFlags flags = Qt::MatchExactly, + bool keepSource = false, bool single = false); + + bool installDirectories(const QString &dirName, const QString &path, + bool recursive = true, bool keepSource = false); + + bool installComponent(Component component, const QString &path); + bool setupComponent(Component component); + + bool mInstallMorrowind; + bool mInstallTribunal; + bool mInstallBloodmoon; + + bool mMorrowindDone; + bool mTribunalDone; + bool mBloodmoonDone; + + bool mStopped; + + QString mPath; + QString mIniPath; + QString mDiskPath; + + IniSettings mIniSettings; + + QTextCodec *mIniCodec; + + QWaitCondition mWait; + QMutex mMutex; + + QReadWriteLock mLock; + + public slots: + void extract(); + + signals: + void finished(); + void requestFileDialog(Wizard::Component component); + + void textChanged(const QString &text); + + void error(const QString &text, const QString &details); + void progressChanged(int progress); + + }; +} + +#endif // UNSHIELDWORKER_HPP diff --git a/apps/wizard/utils/componentlistwidget.cpp b/apps/wizard/utils/componentlistwidget.cpp new file mode 100644 index 000000000..6a5d019b5 --- /dev/null +++ b/apps/wizard/utils/componentlistwidget.cpp @@ -0,0 +1,48 @@ +#include "componentlistwidget.hpp" + +#include +#include + +ComponentListWidget::ComponentListWidget(QWidget *parent) : + QListWidget(parent) +{ + mCheckedItems = QStringList(); + + connect(this, SIGNAL(itemChanged(QListWidgetItem *)), + this, SLOT(updateCheckedItems(QListWidgetItem *))); + + connect(model(), SIGNAL(rowsInserted(QModelIndex, int, int)), + this, SLOT(updateCheckedItems(QModelIndex, int, int))); +} + +QStringList ComponentListWidget::checkedItems() +{ + mCheckedItems.removeDuplicates(); + return mCheckedItems; +} + +void ComponentListWidget::updateCheckedItems(const QModelIndex &index, int start, int end) +{ + updateCheckedItems(item(start)); +} + +void ComponentListWidget::updateCheckedItems(QListWidgetItem *item) +{ + if (!item) + return; + + QString text = item->text(); + + if (item->checkState() == Qt::Checked) { + if (!mCheckedItems.contains(text)) + mCheckedItems.append(text); + } else { + if (mCheckedItems.contains(text)) + mCheckedItems.removeAll(text); + } + + mCheckedItems.removeDuplicates(); + + emit checkedItemsChanged(mCheckedItems); + +} diff --git a/apps/wizard/utils/componentlistwidget.hpp b/apps/wizard/utils/componentlistwidget.hpp new file mode 100644 index 000000000..23965f8a6 --- /dev/null +++ b/apps/wizard/utils/componentlistwidget.hpp @@ -0,0 +1,26 @@ +#ifndef COMPONENTLISTWIDGET_HPP +#define COMPONENTLISTWIDGET_HPP + +#include + +class ComponentListWidget : public QListWidget +{ + Q_OBJECT + + Q_PROPERTY(QStringList mCheckedItems READ checkedItems) + +public: + ComponentListWidget(QWidget *parent = 0); + + QStringList mCheckedItems; + QStringList checkedItems(); + +signals: + void checkedItemsChanged(const QStringList &items); + +private slots: + void updateCheckedItems(QListWidgetItem *item); + void updateCheckedItems(const QModelIndex &index, int start, int end); +}; + +#endif // COMPONENTLISTWIDGET_HPP diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 9bb0a557c..6a1db371d 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -113,11 +113,20 @@ set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui find_package(Qt4 COMPONENTS QtCore QtGui) if(QT_QTGUI_LIBRARY AND QT_QTCORE_LIBRARY) - add_component_qt_dir (contentselector + add_component_qt_dir (contentselector model/modelitem model/esmfile model/naturalsort model/contentmodel view/combobox view/contentselector - ) + ) + add_component_qt_dir (config + gamesettings + launchersettings + settingsbase + ) + + add_component_qt_dir (process + processinvoker + ) include(${QT_USE_FILE}) QT4_WRAP_UI(ESM_UI_HDR ${ESM_UI}) diff --git a/apps/launcher/settings/gamesettings.cpp b/components/config/gamesettings.cpp similarity index 86% rename from apps/launcher/settings/gamesettings.cpp rename to components/config/gamesettings.cpp index 2f3dd9a45..1e7f716e2 100644 --- a/apps/launcher/settings/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -27,20 +27,17 @@ namespace boost #endif /* (BOOST_VERSION <= 104600) */ -Launcher::GameSettings::GameSettings(Files::ConfigurationManager &cfg) +Config::GameSettings::GameSettings(Files::ConfigurationManager &cfg) : mCfgMgr(cfg) { } -Launcher::GameSettings::~GameSettings() +Config::GameSettings::~GameSettings() { } -void Launcher::GameSettings::validatePaths() +void Config::GameSettings::validatePaths() { - if (mSettings.isEmpty() || !mDataDirs.isEmpty()) - return; // Don't re-validate paths if they are already parsed - QStringList paths = mSettings.values(QString("data")); Files::PathContainer dataDirs; @@ -84,24 +81,24 @@ void Launcher::GameSettings::validatePaths() } } -QStringList Launcher::GameSettings::values(const QString &key, const QStringList &defaultValues) +QStringList Config::GameSettings::values(const QString &key, const QStringList &defaultValues) { if (!mSettings.values(key).isEmpty()) return mSettings.values(key); return defaultValues; } -bool Launcher::GameSettings::readFile(QTextStream &stream) +bool Config::GameSettings::readFile(QTextStream &stream) { return readFile(stream, mSettings); } -bool Launcher::GameSettings::readUserFile(QTextStream &stream) +bool Config::GameSettings::readUserFile(QTextStream &stream) { return readFile(stream, mUserSettings); } -bool Launcher::GameSettings::readFile(QTextStream &stream, QMap &settings) +bool Config::GameSettings::readFile(QTextStream &stream, QMap &settings) { QMap cache; QRegExp keyRe("^([^=]+)\\s*=\\s*(.+)$"); @@ -143,8 +140,7 @@ bool Launcher::GameSettings::readFile(QTextStream &stream, QMap i(mUserSettings); @@ -183,7 +179,7 @@ bool Launcher::GameSettings::writeFile(QTextStream &stream) return true; } -bool Launcher::GameSettings::hasMaster() +bool Config::GameSettings::hasMaster() { bool result = false; QStringList content = mSettings.values(QString("content")); diff --git a/apps/launcher/settings/gamesettings.hpp b/components/config/gamesettings.hpp similarity index 94% rename from apps/launcher/settings/gamesettings.hpp rename to components/config/gamesettings.hpp index df8215074..c4a6ead79 100644 --- a/apps/launcher/settings/gamesettings.hpp +++ b/components/config/gamesettings.hpp @@ -14,7 +14,7 @@ namespace Files struct ConfigurationManager; } -namespace Launcher +namespace Config { class GameSettings { @@ -52,6 +52,8 @@ namespace Launcher } inline QStringList getDataDirs() { return mDataDirs; } + + inline void removeDataDir(const QString &dir) { if(!dir.isEmpty()) mDataDirs.removeAll(dir); } inline void addDataDir(const QString &dir) { if(!dir.isEmpty()) mDataDirs.append(dir); } inline QString getDataLocal() {return mDataLocal; } diff --git a/apps/launcher/settings/launchersettings.cpp b/components/config/launchersettings.cpp similarity index 81% rename from apps/launcher/settings/launchersettings.cpp rename to components/config/launchersettings.cpp index 705453555..c014579dc 100644 --- a/apps/launcher/settings/launchersettings.cpp +++ b/components/config/launchersettings.cpp @@ -7,15 +7,15 @@ #include -Launcher::LauncherSettings::LauncherSettings() +Config::LauncherSettings::LauncherSettings() { } -Launcher::LauncherSettings::~LauncherSettings() +Config::LauncherSettings::~LauncherSettings() { } -QStringList Launcher::LauncherSettings::values(const QString &key, Qt::MatchFlags flags) +QStringList Config::LauncherSettings::values(const QString &key, Qt::MatchFlags flags) { QMap settings = SettingsBase::getSettings(); @@ -36,19 +36,25 @@ QStringList Launcher::LauncherSettings::values(const QString &key, Qt::MatchFlag return result; } -QStringList Launcher::LauncherSettings::subKeys(const QString &key) +QStringList Config::LauncherSettings::subKeys(const QString &key) { QMap settings = SettingsBase::getSettings(); QStringList keys = settings.uniqueKeys(); + qDebug() << keys; + QRegExp keyRe("(.+)/"); QStringList result; foreach (const QString ¤tKey, keys) { - if (keyRe.indexIn(currentKey) != -1) { + + if (keyRe.indexIn(currentKey) != -1) + { QString prefixedKey = keyRe.cap(1); - if(prefixedKey.startsWith(key)) { + + if(prefixedKey.startsWith(key)) + { QString subKey = prefixedKey.remove(key); if (!subKey.isEmpty()) result.append(subKey); @@ -60,7 +66,7 @@ QStringList Launcher::LauncherSettings::subKeys(const QString &key) return result; } -bool Launcher::LauncherSettings::writeFile(QTextStream &stream) +bool Config::LauncherSettings::writeFile(QTextStream &stream) { QString sectionPrefix; QRegExp sectionRe("([^/]+)/(.+)$"); diff --git a/apps/launcher/settings/launchersettings.hpp b/components/config/launchersettings.hpp similarity index 96% rename from apps/launcher/settings/launchersettings.hpp rename to components/config/launchersettings.hpp index 8acc389a9..042823ca9 100644 --- a/apps/launcher/settings/launchersettings.hpp +++ b/components/config/launchersettings.hpp @@ -3,7 +3,7 @@ #include "settingsbase.hpp" -namespace Launcher +namespace Config { class LauncherSettings : public SettingsBase > { diff --git a/apps/launcher/settings/settingsbase.hpp b/components/config/settingsbase.hpp similarity index 99% rename from apps/launcher/settings/settingsbase.hpp rename to components/config/settingsbase.hpp index 3a1cf8e30..92ca34cdf 100644 --- a/apps/launcher/settings/settingsbase.hpp +++ b/components/config/settingsbase.hpp @@ -7,7 +7,7 @@ #include #include -namespace Launcher +namespace Config { template class SettingsBase diff --git a/components/ogreinit/ogreinit.cpp b/components/ogreinit/ogreinit.cpp index 40712e282..574b67f19 100644 --- a/components/ogreinit/ogreinit.cpp +++ b/components/ogreinit/ogreinit.cpp @@ -94,6 +94,8 @@ namespace OgreInit Ogre::Root* OgreInit::init(const std::string &logPath) { + if (mRoot) + throw std::runtime_error("OgreInit was already initialised"); #ifndef ANDROID // Set up logging first diff --git a/components/process/processinvoker.cpp b/components/process/processinvoker.cpp new file mode 100644 index 000000000..cc842fd61 --- /dev/null +++ b/components/process/processinvoker.cpp @@ -0,0 +1,185 @@ +#include "processinvoker.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +Process::ProcessInvoker::ProcessInvoker() +{ + mProcess = new QProcess(this); + + connect(mProcess, SIGNAL(error(QProcess::ProcessError)), + this, SLOT(processError(QProcess::ProcessError))); + + connect(mProcess, SIGNAL(finished(int,QProcess::ExitStatus)), + this, SLOT(processFinished(int,QProcess::ExitStatus))); + + + mName = QString(); + mArguments = QStringList(); +} + +Process::ProcessInvoker::~ProcessInvoker() +{ +} + +//void Process::ProcessInvoker::setProcessName(const QString &name) +//{ +// mName = name; +//} + +//void Process::ProcessInvoker::setProcessArguments(const QStringList &arguments) +//{ +// mArguments = arguments; +//} + +QProcess* Process::ProcessInvoker::getProcess() +{ + return mProcess; +} + +//QString Process::ProcessInvoker::getProcessName() +//{ +// return mName; +//} + +//QStringList Process::ProcessInvoker::getProcessArguments() +//{ +// return mArguments; +//} + +bool Process::ProcessInvoker::startProcess(const QString &name, const QStringList &arguments, bool detached) +{ +// mProcess = new QProcess(this); + mName = name; + mArguments = arguments; + + QString path(name); +#ifdef Q_OS_WIN + path.append(QLatin1String(".exe")); +#elif defined(Q_OS_MAC) + QDir dir(QCoreApplication::applicationDirPath()); + path = dir.absoluteFilePath(name); +#else + path.prepend(QLatin1String("./")); +#endif + + QFileInfo info(path); + + if (!info.exists()) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error starting executable")); + msgBox.setIcon(QMessageBox::Warning); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("

Could not find %1

\ +

The application is not found.

\ +

Please make sure OpenMW is installed correctly and try again.

").arg(info.fileName())); + msgBox.exec(); + return false; + } + + if (!info.isExecutable()) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error starting executable")); + msgBox.setIcon(QMessageBox::Warning); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("

Could not start %1

\ +

The application is not executable.

\ +

Please make sure you have the right permissions and try again.

").arg(info.fileName())); + msgBox.exec(); + return false; + } + + // Start the executable + if (detached) { + if (!mProcess->startDetached(path, arguments)) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error starting executable")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("

Could not start %1

\ +

An error occurred while starting %1.

\ +

Press \"Show Details...\" for more information.

").arg(info.fileName())); + msgBox.setDetailedText(mProcess->errorString()); + msgBox.exec(); + return false; + } + } else { + mProcess->start(path, arguments); + + /* + if (!mProcess->waitForFinished()) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error starting executable")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("

Could not start %1

\ +

An error occurred while starting %1.

\ +

Press \"Show Details...\" for more information.

").arg(info.fileName())); + msgBox.setDetailedText(mProcess->errorString()); + msgBox.exec(); + + return false; + } + + if (mProcess->exitCode() != 0 || mProcess->exitStatus() == QProcess::CrashExit) { + QString error(mProcess->readAllStandardError()); + error.append(tr("\nArguments:\n")); + error.append(arguments.join(" ")); + + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error running executable")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("

Executable %1 returned an error

\ +

An error occurred while running %1.

\ +

Press \"Show Details...\" for more information.

").arg(info.fileName())); + msgBox.setDetailedText(error); + msgBox.exec(); + + return false; + } + */ + } + + return true; + +} + +void Process::ProcessInvoker::processError(QProcess::ProcessError error) +{ + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error running executable")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("

Executable %1 returned an error

\ +

An error occurred while running %1.

\ +

Press \"Show Details...\" for more information.

").arg(mName)); + msgBox.setDetailedText(mProcess->errorString()); + msgBox.exec(); + +} + +void Process::ProcessInvoker::processFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + if (exitCode != 0 || exitStatus == QProcess::CrashExit) { + QString error(mProcess->readAllStandardError()); + error.append(tr("\nArguments:\n")); + error.append(mArguments.join(" ")); + + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error running executable")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("

Executable %1 returned an error

\ +

An error occurred while running %1.

\ +

Press \"Show Details...\" for more information.

").arg(mName)); + msgBox.setDetailedText(error); + msgBox.exec(); + } +} diff --git a/components/process/processinvoker.hpp b/components/process/processinvoker.hpp new file mode 100644 index 000000000..8fff6658c --- /dev/null +++ b/components/process/processinvoker.hpp @@ -0,0 +1,43 @@ +#ifndef PROCESSINVOKER_HPP +#define PROCESSINVOKER_HPP + +#include +#include +#include + +namespace Process +{ + class ProcessInvoker : public QObject + { + Q_OBJECT + + public: + + ProcessInvoker(); + ~ProcessInvoker(); + +// void setProcessName(const QString &name); +// void setProcessArguments(const QStringList &arguments); + + QProcess* getProcess(); +// QString getProcessName(); +// QStringList getProcessArguments(); + +// inline bool startProcess(bool detached = false) { return startProcess(mName, mArguments, detached); } + inline bool startProcess(const QString &name, bool detached = false) { return startProcess(name, QStringList(), detached); } + bool startProcess(const QString &name, const QStringList &arguments, bool detached = false); + + private: + QProcess *mProcess; + + QString mName; + QStringList mArguments; + + private slots: + void processError(QProcess::ProcessError error); + void processFinished(int exitCode, QProcess::ExitStatus exitStatus); + + }; +} + +#endif // PROCESSINVOKER_HPP diff --git a/files/launcher/icons/tango/document-new.png b/files/launcher/icons/tango/16x16/document-new.png similarity index 100% rename from files/launcher/icons/tango/document-new.png rename to files/launcher/icons/tango/16x16/document-new.png diff --git a/files/launcher/icons/tango/edit-copy.png b/files/launcher/icons/tango/16x16/edit-copy.png similarity index 100% rename from files/launcher/icons/tango/edit-copy.png rename to files/launcher/icons/tango/16x16/edit-copy.png diff --git a/files/launcher/icons/tango/edit-delete.png b/files/launcher/icons/tango/16x16/edit-delete.png similarity index 100% rename from files/launcher/icons/tango/edit-delete.png rename to files/launcher/icons/tango/16x16/edit-delete.png diff --git a/files/launcher/icons/tango/go-bottom.png b/files/launcher/icons/tango/16x16/go-bottom.png similarity index 100% rename from files/launcher/icons/tango/go-bottom.png rename to files/launcher/icons/tango/16x16/go-bottom.png diff --git a/files/launcher/icons/tango/go-down.png b/files/launcher/icons/tango/16x16/go-down.png similarity index 100% rename from files/launcher/icons/tango/go-down.png rename to files/launcher/icons/tango/16x16/go-down.png diff --git a/files/launcher/icons/tango/go-top.png b/files/launcher/icons/tango/16x16/go-top.png similarity index 100% rename from files/launcher/icons/tango/go-top.png rename to files/launcher/icons/tango/16x16/go-top.png diff --git a/files/launcher/icons/tango/go-up.png b/files/launcher/icons/tango/16x16/go-up.png similarity index 100% rename from files/launcher/icons/tango/go-up.png rename to files/launcher/icons/tango/16x16/go-up.png diff --git a/files/launcher/icons/tango/48x48/preferences-system.png b/files/launcher/icons/tango/48x48/preferences-system.png new file mode 100644 index 000000000..a1620e991 Binary files /dev/null and b/files/launcher/icons/tango/48x48/preferences-system.png differ diff --git a/files/launcher/icons/tango/video-display.png b/files/launcher/icons/tango/48x48/video-display.png similarity index 100% rename from files/launcher/icons/tango/video-display.png rename to files/launcher/icons/tango/48x48/video-display.png diff --git a/files/launcher/icons/tango/index.theme b/files/launcher/icons/tango/index.theme index 1f54489eb..8ec560f85 100644 --- a/files/launcher/icons/tango/index.theme +++ b/files/launcher/icons/tango/index.theme @@ -2,7 +2,10 @@ Name=Tango Comment=Tango Theme Inherits=default -Directories=16x16 +Directories=16x16,48x48 [16x16] -Size=16 \ No newline at end of file +Size=16 + +[48x48] +Size=48 \ No newline at end of file diff --git a/files/launcher/launcher.qrc b/files/launcher/launcher.qrc index 19b1c5a6f..4f55fccd5 100644 --- a/files/launcher/launcher.qrc +++ b/files/launcher/launcher.qrc @@ -9,13 +9,14 @@ icons/tango/index.theme - icons/tango/video-display.png - icons/tango/document-new.png - icons/tango/edit-copy.png - icons/tango/edit-delete.png - icons/tango/go-bottom.png - icons/tango/go-down.png - icons/tango/go-top.png - icons/tango/go-up.png + icons/tango/48x48/video-display.png + icons/tango/48x48/preferences-system.png + icons/tango/16x16/document-new.png + icons/tango/16x16/edit-copy.png + icons/tango/16x16/edit-delete.png + icons/tango/16x16/go-bottom.png + icons/tango/16x16/go-down.png + icons/tango/16x16/go-top.png + icons/tango/16x16/go-up.png diff --git a/files/ui/mainwindow.ui b/files/ui/mainwindow.ui index 5f2be05a2..a9e493926 100644 --- a/files/ui/mainwindow.ui +++ b/files/ui/mainwindow.ui @@ -6,13 +6,13 @@ 0 0 - 575 - 535 + 635 + 575 - 575 + 635 535 diff --git a/files/ui/settingspage.ui b/files/ui/settingspage.ui new file mode 100644 index 000000000..6c873ea92 --- /dev/null +++ b/files/ui/settingspage.ui @@ -0,0 +1,156 @@ + + + SettingsPage + + + + 0 + 0 + 514 + 397 + + + + Form + + + + + + General + + + + + + <html><head/><body><p>The language of the original Morrowind installation files (used for the character encoding)</p></body></html> + + + Morrowind content language: + + + + + + + + 250 + 0 + + + + + + + + + + + Morrowind Installation Wizard + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Run &Installation Wizard + + + + + + + + + + Morrowind Settings Importer + + + + + + + + File to import settings from: + + + + + + + + + + Browse... + + + + + + + + + Import add-on and plugin selection (creates a new Content List) + + + true + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Run &Settings Importer + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/files/ui/wizard/componentselectionpage.ui b/files/ui/wizard/componentselectionpage.ui new file mode 100644 index 000000000..026fcdff6 --- /dev/null +++ b/files/ui/wizard/componentselectionpage.ui @@ -0,0 +1,67 @@ + + + ComponentSelectionPage + + + + 0 + 0 + 448 + 387 + + + + WizardPage + + + Select Components + + + Which components should be installed? + + + + + + <html><head/><body><p>Select which official Morrowind expansions should be installed. For best results, it is recommended to have both expansions installed.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to install expansions later by re-running this Wizard.<br/></p></body></html> + + + true + + + + + + + Selected components: + + + + + + + + + + Qt::Vertical + + + + 20 + 81 + + + + + + + + + ComponentListWidget + QListWidget +
apps/wizard/utils/componentlistwidget.hpp
+
+
+ + +
diff --git a/files/ui/wizard/conclusionpage.ui b/files/ui/wizard/conclusionpage.ui new file mode 100644 index 000000000..eb6828b38 --- /dev/null +++ b/files/ui/wizard/conclusionpage.ui @@ -0,0 +1,40 @@ + + + ConclusionPage + + + + 0 + 0 + 398 + 298 + + + + WizardPage + + + Completing the OpenMW Wizard + + + + + + Placeholder + + + Qt::RichText + + + true + + + true + + + + + + + + diff --git a/files/ui/wizard/existinginstallationpage.ui b/files/ui/wizard/existinginstallationpage.ui new file mode 100644 index 000000000..921b2eb2a --- /dev/null +++ b/files/ui/wizard/existinginstallationpage.ui @@ -0,0 +1,79 @@ + + + ExistingInstallationPage + + + + 0 + 0 + 394 + 294 + + + + WizardPage + + + Select Existing Installation + + + Select an existing installation for OpenMW to use or modify. + + + + + + Detected installations: + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Browse... + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/files/ui/wizard/importpage.ui b/files/ui/wizard/importpage.ui new file mode 100644 index 000000000..9fd4ea500 --- /dev/null +++ b/files/ui/wizard/importpage.ui @@ -0,0 +1,70 @@ + + + ImportPage + + + + 0 + 0 + 508 + 322 + + + + WizardPage + + + Import Settings + + + Import settings from the Morrowind installation. + + + + + + <html><head/><body><p>OpenMW needs to import settings from the Morrowind configuration file in order to function properly.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to import settings later by re-running this Wizard.</p><p/></body></html> + + + true + + + + + + + Import settings from Morrowind.ini + + + true + + + + + + + Import add-on and plugin selection + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/files/ui/wizard/installationpage.ui b/files/ui/wizard/installationpage.ui new file mode 100644 index 000000000..6877f1e58 --- /dev/null +++ b/files/ui/wizard/installationpage.ui @@ -0,0 +1,51 @@ + + + InstallationPage + + + + 0 + 0 + 514 + 419 + + + + WizardPage + + + Installing + + + Please wait while Morrowind is installed on your computer. + + + + + + + + + + + + + 0 + + + + + + + false + + + true + + + + + + + + diff --git a/files/ui/wizard/installationtargetpage.ui b/files/ui/wizard/installationtargetpage.ui new file mode 100644 index 000000000..c87214d8d --- /dev/null +++ b/files/ui/wizard/installationtargetpage.ui @@ -0,0 +1,89 @@ + + + InstallationTargetPage + + + + 0 + 0 + 400 + 300 + + + + WizardPage + + + Select Installation Destination + + + Where should Morrowind be installed? + + + + + + + + + 0 + 0 + + + + <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> + + + Qt::RichText + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + Morrowind will be installed to the following location. + + + true + + + + + + + + + + + + + + Browse... + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + diff --git a/files/ui/wizard/intropage.ui b/files/ui/wizard/intropage.ui new file mode 100644 index 000000000..117d2098e --- /dev/null +++ b/files/ui/wizard/intropage.ui @@ -0,0 +1,34 @@ + + + IntroPage + + + + 0 + 0 + 472 + 368 + + + + WizardPage + + + Welcome to the OpenMW Wizard + + + + + + This Wizard will help you install Morrowind and its add-ons for OpenMW to use. + + + true + + + + + + + + diff --git a/files/ui/wizard/languageselectionpage.ui b/files/ui/wizard/languageselectionpage.ui new file mode 100644 index 000000000..fccd2aa42 --- /dev/null +++ b/files/ui/wizard/languageselectionpage.ui @@ -0,0 +1,113 @@ + + + LanguageSelectionPage + + + + 0 + 0 + 396 + 296 + + + + WizardPage + + + Select Morrowind Language + + + What is the language of the Morrowind installation? + + + + + + + + + 0 + 0 + + + + <html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> + + + Qt::RichText + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + Select the language of the Morrowind installation. + + + true + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 250 + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 230 + + + + + + + + + diff --git a/files/ui/wizard/methodselectionpage.ui b/files/ui/wizard/methodselectionpage.ui new file mode 100644 index 000000000..531d093af --- /dev/null +++ b/files/ui/wizard/methodselectionpage.ui @@ -0,0 +1,154 @@ + + + MethodSelectionPage + + + + 0 + 0 + 396 + 296 + + + + WizardPage + + + Select Installation Method + + + Select how OpenMW should get the required Morrowind installation files. + + + + + + font-weight:bold; + + + Install Morrowind to a new location + + + true + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Maximum + + + + 20 + 20 + + + + + + + + + 0 + 0 + + + + <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> + + + Qt::RichText + + + false + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + newLocationRadioButton + + + + + + + Install Morrowind from a retail disk to a new location for OpenMW to use. + + + true + + + + + + + + + font-weight:bold + + + Select an existing Morrowind installation + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Maximum + + + + 20 + 20 + + + + + + + + + 0 + 0 + + + + <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> + + + Qt::RichText + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + Select an existing Morrowind installation for OpenMW to use. + + + true + + + + + + + + + + diff --git a/files/wizard/icons/tango/48x48/folder.png b/files/wizard/icons/tango/48x48/folder.png new file mode 100644 index 000000000..e93d7cf8f Binary files /dev/null and b/files/wizard/icons/tango/48x48/folder.png differ diff --git a/files/wizard/icons/tango/48x48/preferences-desktop-locale.png b/files/wizard/icons/tango/48x48/preferences-desktop-locale.png new file mode 100644 index 000000000..f56497bd2 Binary files /dev/null and b/files/wizard/icons/tango/48x48/preferences-desktop-locale.png differ diff --git a/files/wizard/icons/tango/48x48/system-installer.png b/files/wizard/icons/tango/48x48/system-installer.png new file mode 100644 index 000000000..dbd08f7e2 Binary files /dev/null and b/files/wizard/icons/tango/48x48/system-installer.png differ diff --git a/files/wizard/icons/tango/index.theme b/files/wizard/icons/tango/index.theme new file mode 100644 index 000000000..3ffebebc9 --- /dev/null +++ b/files/wizard/icons/tango/index.theme @@ -0,0 +1,8 @@ +[Icon Theme] +Name=Tango +Comment=Tango Theme +Inherits=default +Directories=48x48 + +[48x48] +Size=48 \ No newline at end of file diff --git a/files/wizard/images/intropage-background.png b/files/wizard/images/intropage-background.png new file mode 100644 index 000000000..0ce13804b Binary files /dev/null and b/files/wizard/images/intropage-background.png differ diff --git a/files/wizard/images/openmw-wizard.png b/files/wizard/images/openmw-wizard.png new file mode 100644 index 000000000..cde473d14 Binary files /dev/null and b/files/wizard/images/openmw-wizard.png differ diff --git a/files/wizard/wizard.qrc b/files/wizard/wizard.qrc new file mode 100644 index 000000000..99623e985 --- /dev/null +++ b/files/wizard/wizard.qrc @@ -0,0 +1,12 @@ + + + icons/tango/48x48/preferences-desktop-locale.png + icons/tango/index.theme + icons/tango/48x48/folder.png + icons/tango/48x48/system-installer.png + + + images/intropage-background.png + images/openmw-wizard.png + +