mirror of https://github.com/OpenMW/openmw.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
469 lines
14 KiB
C++
469 lines
14 KiB
C++
#include "editor.hpp"
|
|
|
|
#include <QApplication>
|
|
#include <QLocalServer>
|
|
#include <QLocalSocket>
|
|
#include <QMessageBox>
|
|
|
|
#include <boost/program_options/options_description.hpp>
|
|
|
|
#include <components/debug/debugging.hpp>
|
|
#include <components/debug/debuglog.hpp>
|
|
#include <components/fallback/validate.hpp>
|
|
#include <components/files/conversion.hpp>
|
|
#include <components/files/qtconversion.hpp>
|
|
#include <components/misc/rng.hpp>
|
|
#include <components/misc/strings/conversion.hpp>
|
|
#include <components/nifosg/nifloader.hpp>
|
|
#include <components/settings/settings.hpp>
|
|
|
|
#include "model/doc/document.hpp"
|
|
|
|
#ifdef _WIN32
|
|
#include <components/windows.hpp>
|
|
#endif
|
|
|
|
using namespace Fallback;
|
|
|
|
CS::Editor::Editor(int argc, char** argv)
|
|
: mConfigVariables(readConfiguration())
|
|
, mSettingsState(mCfgMgr)
|
|
, mDocumentManager(mCfgMgr)
|
|
, mPid("")
|
|
, mLock()
|
|
, 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(mFsStrict, 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;
|
|
|
|
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("fs-strict", boost::program_options::value<bool>()->implicit_value(true)->default_value(false));
|
|
addOption("encoding", boost::program_options::value<std::string>()->default_value("win1252"));
|
|
addOption("resources",
|
|
boost::program_options::value<Files::MaybeQuotedPath>()->default_value(Files::MaybeQuotedPath(), "resources"));
|
|
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<FallbackMap>()->default_value(FallbackMap(), "")->multitoken()->composing(),
|
|
"fallback values");
|
|
addOption("script-blacklist",
|
|
boost::program_options::value<std::vector<std::string>>()
|
|
->default_value(std::vector<std::string>(), "")
|
|
->multitoken(),
|
|
"exclude specified script from the verifier (if the use of the blacklist is enabled)");
|
|
addOption("script-blacklist-use", boost::program_options::value<bool>()->implicit_value(true)->default_value(true),
|
|
"enable script blacklisting");
|
|
Files::ConfigurationManager::addCommonOptions(desc);
|
|
|
|
boost::program_options::notify(variables);
|
|
|
|
mCfgMgr.readConfiguration(variables, desc, false);
|
|
Settings::Manager::load(mCfgMgr, true);
|
|
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<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.
|
|
|
|
if (variables["script-blacklist-use"].as<bool>())
|
|
mDocumentManager.setBlacklistedScripts(variables["script-blacklist"].as<std::vector<std::string>>());
|
|
|
|
mFsStrict = variables["fs-strict"].as<bool>();
|
|
|
|
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(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);
|
|
}
|
|
|
|
dataDirs.insert(dataDirs.end(), dataLocal.begin(), dataLocal.end());
|
|
|
|
// iterate the data directories and add them to the file dialog for loading
|
|
mFileDialog.addFiles(dataDirs);
|
|
|
|
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
|
|
{
|
|
mPid = std::filesystem::temp_directory_path();
|
|
mPid /= "openmw-cs.pid";
|
|
bool pidExists = std::filesystem::exists(mPid);
|
|
|
|
mPidFile.open(mPid);
|
|
|
|
mLock = boost::interprocess::file_lock(mPid.c_str());
|
|
if (!mLock.try_lock())
|
|
{
|
|
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(QRegExp("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();
|
|
}
|