Something went wrong. Try again.
OpenMW is an open implementation of the Morrowind engine
Something went wrong. Try again.
C++
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484#include "editor.hpp"
#include <QApplication>#include <QFileInfo>#include <QLocalServer>#include <QLocalSocket>#include <QMessageBox>#include <QRegularExpression>
#include <boost/program_options.hpp>
#include <exception>
#include <apps/opencs/model/doc/document.hpp>#include <apps/opencs/model/doc/documentmanager.hpp>#include <apps/opencs/view/doc/adjusterwidget.hpp>#include <apps/opencs/view/doc/filedialog.hpp>#include <apps/opencs/view/doc/newgame.hpp>#include <apps/opencs/view/doc/startup.hpp>#include <apps/opencs/view/prefs/dialogue.hpp>#include <apps/opencs/view/tools/merge.hpp>
#ifdef _WIN32#include <components/misc/windows.hpp>#endif
#include <components/debug/debugging.hpp>#include <components/debug/debuglog.hpp>#include <components/esm3/esmreader.hpp>#include <components/esm3/loadtes3.hpp>#include <components/fallback/fallback.hpp>#include <components/fallback/validate.hpp>#include <components/files/qtconversion.hpp>#include <components/misc/rng.hpp>#include <components/nifosg/nifloader.hpp>#include <components/settings/settings.hpp>#include <components/toutf8/toutf8.hpp>
#include "view/doc/viewmanager.hpp"
CS::Editor::Editor(int argc, char** argv) : mConfigVariables(readConfiguration()) , mSettingsState(mCfgMgr) , mDocumentManager(mCfgMgr) , mPid(std::filesystem::temp_directory_path() / "openmw-cs.pid") , mLockFile(QFileInfo(Files::pathToQString(mPid)).absoluteFilePath() + ".lock") , mMerge(mDocumentManager) , mIpcServerName("org.openmw.OpenCS") , mServer(nullptr) , mClientSocket(nullptr){ std::pair<Files::PathContainer, std::vector<std::string>> config = readConfig();
mViewManager = new CSVDoc::ViewManager(mDocumentManager); if (argc > 1) { mFileToLoad = argv[1]; mDataDirs = config.first; }
NifOsg::Loader::setShowMarkers(true);
mDocumentManager.setFileData(config.first, config.second);
mNewGame.setLocalData(mLocal); mFileDialog.setLocalData(mLocal); mMerge.setLocalData(mLocal);
connect(&mDocumentManager, &CSMDoc::DocumentManager::documentAdded, this, &Editor::documentAdded); connect( &mDocumentManager, &CSMDoc::DocumentManager::documentAboutToBeRemoved, this, &Editor::documentAboutToBeRemoved); connect(&mDocumentManager, &CSMDoc::DocumentManager::lastDocumentDeleted, this, &Editor::lastDocumentDeleted);
connect(mViewManager, &CSVDoc::ViewManager::newGameRequest, this, &Editor::createGame); connect(mViewManager, &CSVDoc::ViewManager::newAddonRequest, this, &Editor::createAddon); connect(mViewManager, &CSVDoc::ViewManager::loadDocumentRequest, this, &Editor::loadDocument); connect(mViewManager, &CSVDoc::ViewManager::editSettingsRequest, this, &Editor::showSettings); connect(mViewManager, &CSVDoc::ViewManager::mergeDocument, this, &Editor::mergeDocument);
connect(&mStartup, &CSVDoc::StartupDialogue::createGame, this, &Editor::createGame); connect(&mStartup, &CSVDoc::StartupDialogue::createAddon, this, &Editor::createAddon); connect(&mStartup, &CSVDoc::StartupDialogue::loadDocument, this, &Editor::loadDocument); connect(&mStartup, &CSVDoc::StartupDialogue::editConfig, this, &Editor::showSettings);
connect(&mFileDialog, &CSVDoc::FileDialog::signalOpenFiles, this, [this](const std::filesystem::path& savePath) { this->openFiles(savePath); }); connect(&mFileDialog, &CSVDoc::FileDialog::signalCreateNewFile, this, &Editor::createNewFile); connect(&mFileDialog, &CSVDoc::FileDialog::rejected, this, &Editor::cancelFileDialog);
connect(&mNewGame, &CSVDoc::NewGameDialogue::createRequest, this, &Editor::createNewGame); connect(&mNewGame, &CSVDoc::NewGameDialogue::cancelCreateGame, this, &Editor::cancelCreateGame);}
CS::Editor::~Editor(){ delete mViewManager;
mLockFile.unlock(); mPidFile.close();
if (mServer && std::filesystem::exists(mPid)) std::filesystem::remove(mPid);}
boost::program_options::variables_map CS::Editor::readConfiguration(){ boost::program_options::variables_map variables; boost::program_options::options_description desc("Syntax: openmw-cs <options>\nAllowed options");
auto addOption = desc.add_options(); addOption("data", boost::program_options::value<Files::MaybeQuotedPathContainer>() ->default_value(Files::MaybeQuotedPathContainer(), "data") ->multitoken() ->composing()); addOption("data-local", boost::program_options::value<Files::MaybeQuotedPathContainer::value_type>()->default_value( Files::MaybeQuotedPathContainer::value_type(), "")); addOption("encoding", boost::program_options::value<std::string>()->default_value("win1252")); addOption("fallback-archive", boost::program_options::value<std::vector<std::string>>() ->default_value(std::vector<std::string>(), "fallback-archive") ->multitoken()); addOption("fallback", boost::program_options::value<Fallback::FallbackMap>() ->default_value(Fallback::FallbackMap(), "") ->multitoken() ->composing(), "fallback values"); addOption("content", boost::program_options::value<std::vector<std::string>>() ->default_value(std::vector<std::string>(), "") ->multitoken() ->composing()); Files::ConfigurationManager::addCommonOptions(desc);
boost::program_options::notify(variables);
mCfgMgr.readConfiguration(variables, desc, false); Settings::Manager::load(mCfgMgr, true); Debug::setupLogging(mCfgMgr.getLogPath(), "OpenMW-CS");
return variables;}
std::pair<Files::PathContainer, std::vector<std::string>> CS::Editor::readConfig(bool quiet){ boost::program_options::variables_map& variables = mConfigVariables;
Fallback::Map::init(variables["fallback"].as<Fallback::FallbackMap>().mMap);
mEncodingName = variables["encoding"].as<std::string>(); mDocumentManager.setEncoding(ToUTF8::calculateEncoding(mEncodingName)); mFileDialog.setEncoding(QString::fromUtf8(mEncodingName.c_str()));
mDocumentManager.setResourceDir(mResources = variables["resources"] .as<Files::MaybeQuotedPath>() .u8string()); // This call to u8string is redundant, but required // to build on MSVC 14.26 due to implementation bugs.
Files::PathContainer dataDirs, dataLocal; if (!variables["data"].empty()) { dataDirs = asPathContainer(variables["data"].as<Files::MaybeQuotedPathContainer>()); }
Files::PathContainer::value_type local(variables["data-local"] .as<Files::MaybeQuotedPathContainer::value_type>() .u8string()); // This call to u8string is redundant, but required to // build on MSVC 14.26 due to implementation bugs. if (!local.empty()) { std::filesystem::create_directories(local); dataLocal.push_back(std::move(local)); } mCfgMgr.filterOutNonExistingPaths(dataDirs); mCfgMgr.filterOutNonExistingPaths(dataLocal);
if (!dataLocal.empty()) mLocal = dataLocal[0]; else { QMessageBox messageBox; messageBox.setWindowTitle(tr("No local data path available")); messageBox.setIcon(QMessageBox::Critical); messageBox.setStandardButtons(QMessageBox::Ok); messageBox.setText( tr("<br><b>OpenCS is unable to access the local data directory. This may indicate a faulty configuration " "or a broken install.</b>")); messageBox.exec();
QApplication::exit(1); }
if (!dataLocal.empty()) dataDirs.insert(dataDirs.begin(), dataLocal.begin(), dataLocal.end());
dataDirs.insert(dataDirs.begin(), mResources / "vfs");
mFileDialog.addFiles(dataDirs);
if (!variables["content"].empty()) { QStringList contentOrder; for (const auto& c : variables["content"].as<std::vector<std::string>>()) contentOrder.append(QString::fromStdString(c));
mFileDialog.setContentList(contentOrder, true); }
return std::make_pair(dataDirs, variables["fallback-archive"].as<std::vector<std::string>>());}
void CS::Editor::createGame(){ mStartup.hide();
if (mNewGame.isHidden()) mNewGame.show();
mNewGame.raise(); mNewGame.activateWindow();}
void CS::Editor::cancelCreateGame(){ if (!mDocumentManager.isEmpty()) return;
mNewGame.hide();
if (mStartup.isHidden()) mStartup.show();
mStartup.raise(); mStartup.activateWindow();}
void CS::Editor::createAddon(){ mStartup.hide();
mFileDialog.clearFiles(); readConfig(/*quiet*/ true);
mFileDialog.showDialog(CSVDoc::ContentAction_New);}
void CS::Editor::cancelFileDialog(){ if (!mDocumentManager.isEmpty()) return;
mFileDialog.hide();
if (mStartup.isHidden()) mStartup.show();
mStartup.raise(); mStartup.activateWindow();}
void CS::Editor::loadDocument(){ mStartup.hide();
mFileDialog.clearFiles(); readConfig(/*quiet*/ true);
mFileDialog.showDialog(CSVDoc::ContentAction_Edit);}
void CS::Editor::openFiles( const std::filesystem::path& savePath, const std::vector<std::filesystem::path>& discoveredFiles){ std::vector<std::filesystem::path> files;
if (discoveredFiles.empty()) { for (const QString& path : mFileDialog.selectedFilePaths()) { files.emplace_back(Files::pathFromQString(path)); } } else { files = discoveredFiles; }
mDocumentManager.addDocument(files, savePath, false);
mFileDialog.hide();}
void CS::Editor::createNewFile(const std::filesystem::path& savePath){ std::vector<std::filesystem::path> files;
for (const QString& path : mFileDialog.selectedFilePaths()) { files.emplace_back(Files::pathFromQString(path)); }
files.push_back(savePath);
mDocumentManager.addDocument(files, savePath, true);
mFileDialog.hide();}
void CS::Editor::createNewGame(const std::filesystem::path& file){ std::vector<std::filesystem::path> files;
files.push_back(file);
mDocumentManager.addDocument(files, file, true);
mNewGame.hide();}
void CS::Editor::showStartup(){ if (mStartup.isHidden()) mStartup.show(); mStartup.raise(); mStartup.activateWindow();}
void CS::Editor::showSettings(){ if (mSettings.isHidden()) mSettings.show();
mSettings.move(QCursor::pos()); mSettings.raise(); mSettings.activateWindow();}
bool CS::Editor::makeIPCServer(){ try { bool pidExists = std::filesystem::exists(mPid);
mPidFile.open(mPid);
if (!mLockFile.tryLock()) { Log(Debug::Error) << "Error: OpenMW-CS is already running."; return false; }
#ifdef _WIN32 mPidFile << GetCurrentProcessId() << std::endl;#else mPidFile << getpid() << std::endl;#endif
mServer = new QLocalServer(this);
if (pidExists) { // hack to get the temp directory path mServer->listen("dummy"); QString fullPath = mServer->fullServerName(); mServer->close(); fullPath.remove(QRegularExpression("dummy$")); fullPath += mIpcServerName; const auto path = Files::pathFromQString(fullPath); if (exists(path)) { // TODO: compare pid of the current process with that in the file Log(Debug::Info) << "Detected unclean shutdown."; // delete the stale file if (remove(path)) Log(Debug::Error) << "Error: can not remove stale connection file."; } } }
catch (const std::exception& e) { Log(Debug::Error) << "Error: " << e.what(); return false; }
if (mServer->listen(mIpcServerName)) { connect(mServer, &QLocalServer::newConnection, this, &Editor::showStartup); return true; }
mServer->close(); mServer = nullptr; return false;}
void CS::Editor::connectToIPCServer(){ mClientSocket = new QLocalSocket(this); mClientSocket->connectToServer(mIpcServerName); mClientSocket->close();}
int CS::Editor::run(){ if (mLocal.empty()) return 1;
Misc::Rng::init();
QApplication::setQuitOnLastWindowClosed(true);
if (mFileToLoad.empty()) { mStartup.show(); } else { ESM::ESMReader fileReader; ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(mEncodingName)); fileReader.setEncoder(&encoder); fileReader.open(mFileToLoad);
std::vector<std::filesystem::path> discoveredFiles;
for (const auto& item : fileReader.getGameFiles()) { for (const auto& path : mDataDirs) { if (auto masterPath = path / item.name; std::filesystem::exists(masterPath)) { discoveredFiles.emplace_back(std::move(masterPath)); break; } } } discoveredFiles.push_back(mFileToLoad);
const auto extension = Files::pathToQString(mFileToLoad.extension()).toLower(); if (extension == ".esm") { mFileToLoad.replace_extension(".omwgame"); mDocumentManager.addDocument(discoveredFiles, mFileToLoad, false); } else if (extension == ".esp") { mFileToLoad.replace_extension(".omwaddon"); mDocumentManager.addDocument(discoveredFiles, mFileToLoad, false); } else { openFiles(mFileToLoad, discoveredFiles); } }
return QApplication::exec();}
void CS::Editor::documentAdded(CSMDoc::Document* document){ mViewManager->addView(document);}
void CS::Editor::documentAboutToBeRemoved(CSMDoc::Document* document){ if (mMerge.getDocument() == document) mMerge.cancel();}
void CS::Editor::lastDocumentDeleted(){ QApplication::quit();}
void CS::Editor::mergeDocument(CSMDoc::Document* document){ mMerge.configure(document); mMerge.show(); mMerge.raise(); mMerge.activateWindow();}