Imported Upstream version 0.26.0

This commit is contained in:
Bret Curtis 2013-10-17 16:37:22 +02:00
commit 9a2b6c69b6
1398 changed files with 212217 additions and 0 deletions

View file

@ -0,0 +1,19 @@
set(BSATOOL
bsatool.cpp
)
source_group(apps\\bsatool FILES ${BSATOOL})
# Main executable
add_executable(bsatool
${BSATOOL}
)
target_link_libraries(bsatool
${Boost_LIBRARIES}
components
)
if (BUILD_WITH_CODE_COVERAGE)
add_definitions (--coverage)
target_link_libraries(bsatool gcov)
endif()

288
apps/bsatool/bsatool.cpp Normal file
View file

@ -0,0 +1,288 @@
#include <iostream>
#include <vector>
#include <exception>
#include <boost/program_options.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <components/bsa/bsa_file.hpp>
#define BSATOOL_VERSION 1.1
// Create local aliases for brevity
namespace bpo = boost::program_options;
namespace bfs = boost::filesystem;
struct Arguments
{
std::string mode;
std::string filename;
std::string extractfile;
std::string outdir;
bool longformat;
bool fullpath;
};
void replaceAll(std::string& str, const std::string& needle, const std::string& substitute)
{
int pos = str.find(needle);
while(pos != -1)
{
str.replace(pos, needle.size(), substitute);
pos = str.find(needle);
}
}
bool parseOptions (int argc, char** argv, Arguments &info)
{
bpo::options_description desc("Inspect and extract files from Bethesda BSA archives\n\n"
"Usages:\n"
" bsatool list [-l] archivefile\n"
" List the files presents in the input archive.\n\n"
" bsatool extract [-f] archivefile [file_to_extract] [output_directory]\n"
" Extract a file from the input archive.\n\n"
" bsatool extractall archivefile [output_directory]\n"
" Extract all files from the input archive.\n\n"
"Allowed options");
desc.add_options()
("help,h", "print help message.")
("version,v", "print version information and quit.")
("long,l", "Include extra information in archive listing.")
("full-path,f", "Create diretory hierarchy on file extraction "
"(always true for extractall).")
;
// input-file is hidden and used as a positional argument
bpo::options_description hidden("Hidden Options");
hidden.add_options()
( "mode,m", bpo::value<std::string>(), "bsatool mode")
( "input-file,i", bpo::value< std::vector<std::string> >(), "input file")
;
bpo::positional_options_description p;
p.add("mode", 1).add("input-file", 3);
// there might be a better way to do this
bpo::options_description all;
all.add(desc).add(hidden);
bpo::variables_map variables;
try
{
bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv)
.options(all).positional(p).run();
bpo::store(valid_opts, variables);
}
catch(std::exception &e)
{
std::cout << "ERROR parsing arguments: " << e.what() << "\n\n"
<< desc << std::endl;
return false;
}
bpo::notify(variables);
if (variables.count ("help"))
{
std::cout << desc << std::endl;
return false;
}
if (variables.count ("version"))
{
std::cout << "BSATool version " << BSATOOL_VERSION << std::endl;
return false;
}
if (!variables.count("mode"))
{
std::cout << "ERROR: no mode specified!\n\n"
<< desc << std::endl;
return false;
}
info.mode = variables["mode"].as<std::string>();
if (!(info.mode == "list" || info.mode == "extract" || info.mode == "extractall"))
{
std::cout << std::endl << "ERROR: invalid mode \"" << info.mode << "\"\n\n"
<< desc << std::endl;
return false;
}
if (!variables.count("input-file"))
{
std::cout << "\nERROR: missing BSA archive\n\n"
<< desc << std::endl;
return false;
}
info.filename = variables["input-file"].as< std::vector<std::string> >()[0];
// Default output to the working directory
info.outdir = ".";
if (info.mode == "extract")
{
if (variables["input-file"].as< std::vector<std::string> >().size() < 2)
{
std::cout << "\nERROR: file to extract unspecified\n\n"
<< desc << std::endl;
return false;
}
if (variables["input-file"].as< std::vector<std::string> >().size() > 1)
info.extractfile = variables["input-file"].as< std::vector<std::string> >()[1];
if (variables["input-file"].as< std::vector<std::string> >().size() > 2)
info.outdir = variables["input-file"].as< std::vector<std::string> >()[2];
}
else if (variables["input-file"].as< std::vector<std::string> >().size() > 1)
info.outdir = variables["input-file"].as< std::vector<std::string> >()[1];
info.longformat = variables.count("long");
info.fullpath = variables.count("full-path");
return true;
}
int list(Bsa::BSAFile& bsa, Arguments& info);
int extract(Bsa::BSAFile& bsa, Arguments& info);
int extractAll(Bsa::BSAFile& bsa, Arguments& info);
int main(int argc, char** argv)
{
Arguments info;
if(!parseOptions (argc, argv, info))
return 1;
// Open file
Bsa::BSAFile bsa;
try
{
bsa.open(info.filename);
}
catch(std::exception &e)
{
std::cout << "ERROR reading BSA archive '" << info.filename
<< "'\nDetails:\n" << e.what() << std::endl;
return 2;
}
if (info.mode == "list")
return list(bsa, info);
else if (info.mode == "extract")
return extract(bsa, info);
else if (info.mode == "extractall")
return extractAll(bsa, info);
else
{
std::cout << "Unsupported mode. That is not supposed to happen." << std::endl;
return 1;
}
}
int list(Bsa::BSAFile& bsa, Arguments& info)
{
// List all files
const Bsa::BSAFile::FileList &files = bsa.getList();
for(int i=0; i<files.size(); i++)
{
if(info.longformat)
{
// Long format
std::cout << std::setw(50) << std::left << files[i].name;
std::cout << std::setw(8) << std::left << std::dec << files[i].fileSize;
std::cout << "@ 0x" << std::hex << files[i].offset << std::endl;
}
else
std::cout << files[i].name << std::endl;
}
return 0;
}
int extract(Bsa::BSAFile& bsa, Arguments& info)
{
std::string archivePath = info.extractfile;
replaceAll(archivePath, "/", "\\");
std::string extractPath = info.extractfile;
replaceAll(extractPath, "\\", "/");
if (!bsa.exists(archivePath.c_str()))
{
std::cout << "ERROR: file '" << archivePath << "' not found\n";
std::cout << "In archive: " << info.filename << std::endl;
return 3;
}
// Get the target path (the path the file will be extracted to)
bfs::path relPath (extractPath);
bfs::path outdir (info.outdir);
bfs::path target;
if (info.fullpath)
target = outdir / relPath;
else
target = outdir / relPath.filename();
// Create the directory hierarchy
bfs::create_directories(target.parent_path());
bfs::file_status s = bfs::status(target.parent_path());
if (!bfs::is_directory(s))
{
std::cout << "ERROR: " << target.parent_path() << " is not a directory." << std::endl;
return 3;
}
// Get a stream for the file to extract
Ogre::DataStreamPtr data = bsa.getFile(archivePath.c_str());
bfs::ofstream out(target, std::ios::binary);
// Write the file to disk
std::cout << "Extracting " << info.extractfile << " to " << target << std::endl;
out.write(data->getAsString().c_str(), data->size());
out.close();
return 0;
}
int extractAll(Bsa::BSAFile& bsa, Arguments& info)
{
// Get the list of files present in the archive
Bsa::BSAFile::FileList list = bsa.getList();
// Iter on the list
for(Bsa::BSAFile::FileList::iterator it = list.begin(); it != list.end(); ++it) {
const char* archivePath = it->name;
std::string extractPath (archivePath);
replaceAll(extractPath, "\\", "/");
// Get the target path (the path the file will be extracted to)
bfs::path target (info.outdir);
target /= extractPath;
// Create the directory hierarchy
bfs::create_directories(target.parent_path());
bfs::file_status s = bfs::status(target.parent_path());
if (!bfs::is_directory(s))
{
std::cout << "ERROR: " << target.parent_path() << " is not a directory." << std::endl;
return 3;
}
// Get a stream for the file to extract
// (inefficient because getFile iter on the list again)
Ogre::DataStreamPtr data = bsa.getFile(archivePath);
bfs::ofstream out(target, std::ios::binary);
// Write the file to disk
std::cout << "Extracting " << target << std::endl;
out.write(data->getAsString().c_str(), data->size());
out.close();
}
return 0;
}

3
apps/doc.hpp Normal file
View file

@ -0,0 +1,3 @@
// Note: This is not a regular source file.
/// \defgroup apps Applications

View file

@ -0,0 +1,23 @@
set(ESMTOOL
esmtool.cpp
labels.hpp
labels.cpp
record.hpp
record.cpp
)
source_group(apps\\esmtool FILES ${ESMTOOL})
# Main executable
add_executable(esmtool
${ESMTOOL}
)
target_link_libraries(esmtool
${Boost_LIBRARIES}
components
)
if (BUILD_WITH_CODE_COVERAGE)
add_definitions (--coverage)
target_link_libraries(esmtool gcov)
endif()

556
apps/esmtool/esmtool.cpp Normal file
View file

@ -0,0 +1,556 @@
#include <iostream>
#include <vector>
#include <deque>
#include <list>
#include <map>
#include <set>
#include <boost/program_options.hpp>
#include <components/esm/esmreader.hpp>
#include <components/esm/esmwriter.hpp>
#include <components/esm/records.hpp>
#include "record.hpp"
#define ESMTOOL_VERSION 1.2
// Create a local alias for brevity
namespace bpo = boost::program_options;
struct ESMData
{
std::string author;
std::string description;
int version;
std::vector<ESM::Header::MasterData> masters;
std::deque<EsmTool::RecordBase *> mRecords;
std::map<ESM::Cell *, std::deque<ESM::CellRef> > mCellRefs;
std::map<int, int> mRecordStats;
static const std::set<int> sLabeledRec;
};
static const int sLabeledRecIds[] = {
ESM::REC_GLOB, ESM::REC_CLAS, ESM::REC_FACT, ESM::REC_RACE, ESM::REC_SOUN,
ESM::REC_REGN, ESM::REC_BSGN, ESM::REC_LTEX, ESM::REC_STAT, ESM::REC_DOOR,
ESM::REC_MISC, ESM::REC_WEAP, ESM::REC_CONT, ESM::REC_SPEL, ESM::REC_CREA,
ESM::REC_BODY, ESM::REC_LIGH, ESM::REC_ENCH, ESM::REC_NPC_, ESM::REC_ARMO,
ESM::REC_CLOT, ESM::REC_REPA, ESM::REC_ACTI, ESM::REC_APPA, ESM::REC_LOCK,
ESM::REC_PROB, ESM::REC_INGR, ESM::REC_BOOK, ESM::REC_ALCH, ESM::REC_LEVI,
ESM::REC_LEVC, ESM::REC_SNDG, ESM::REC_CELL, ESM::REC_DIAL
};
const std::set<int> ESMData::sLabeledRec =
std::set<int>(sLabeledRecIds, sLabeledRecIds + 34);
// Based on the legacy struct
struct Arguments
{
unsigned int raw_given;
unsigned int quiet_given;
unsigned int loadcells_given;
bool plain_given;
std::string mode;
std::string encoding;
std::string filename;
std::string outname;
std::vector<std::string> types;
ESMData data;
ESM::ESMReader reader;
ESM::ESMWriter writer;
};
bool parseOptions (int argc, char** argv, Arguments &info)
{
bpo::options_description desc("Inspect and extract from Morrowind ES files (ESM, ESP, ESS)\nSyntax: esmtool [options] mode infile [outfile]\nAllowed modes:\n dump\t Dumps all readable data from the input file.\n clone\t Clones the input file to the output file.\n comp\t Compares the given files.\n\nAllowed options");
desc.add_options()
("help,h", "print help message.")
("version,v", "print version information and quit.")
("raw,r", "Show an unformatted list of all records and subrecords.")
// The intention is that this option would interact better
// with other modes including clone, dump, and raw.
("type,t", bpo::value< std::vector<std::string> >(),
"Show only records of this type (four character record code). May "
"be specified multiple times. Only affects dump mode.")
("plain,p", "Print contents of dialogs, books and scripts. "
"(skipped by default)"
"Only affects dump mode.")
("quiet,q", "Supress all record information. Useful for speed tests.")
("loadcells,C", "Browse through contents of all cells.")
( "encoding,e", bpo::value<std::string>(&(info.encoding))->
default_value("win1252"),
"Character encoding used in ESMTool:\n"
"\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages\n"
"\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n"
"\n\twin1252 - Western European (Latin) alphabet, used by default")
;
std::string finalText = "\nIf no option is given, the default action is to parse all records in the archive\nand display diagnostic information.";
// input-file is hidden and used as a positional argument
bpo::options_description hidden("Hidden Options");
hidden.add_options()
( "mode,m", bpo::value<std::string>(), "esmtool mode")
( "input-file,i", bpo::value< std::vector<std::string> >(), "input file")
;
bpo::positional_options_description p;
p.add("mode", 1).add("input-file", 2);
// there might be a better way to do this
bpo::options_description all;
all.add(desc).add(hidden);
bpo::variables_map variables;
try
{
bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv)
.options(all).positional(p).run();
bpo::store(valid_opts, variables);
}
catch(boost::program_options::unknown_option & x)
{
std::cerr << "ERROR: " << x.what() << std::endl;
return false;
}
catch(boost::program_options::invalid_command_line_syntax & x)
{
std::cerr << "ERROR: " << x.what() << std::endl;
return false;
}
bpo::notify(variables);
if (variables.count ("help"))
{
std::cout << desc << finalText << std::endl;
return false;
}
if (variables.count ("version"))
{
std::cout << "ESMTool version " << ESMTOOL_VERSION << std::endl;
return false;
}
if (!variables.count("mode"))
{
std::cout << "No mode specified!" << std::endl << std::endl
<< desc << finalText << std::endl;
return false;
}
if (variables.count("type") > 0)
info.types = variables["type"].as< std::vector<std::string> >();
info.mode = variables["mode"].as<std::string>();
if (!(info.mode == "dump" || info.mode == "clone" || info.mode == "comp"))
{
std::cout << std::endl << "ERROR: invalid mode \"" << info.mode << "\"" << std::endl << std::endl
<< desc << finalText << std::endl;
return false;
}
if ( !variables.count("input-file") )
{
std::cout << "\nERROR: missing ES file\n\n";
std::cout << desc << finalText << std::endl;
return false;
}
// handling gracefully the user adding multiple files
/* if (variables["input-file"].as< std::vector<std::string> >().size() > 1)
{
std::cout << "\nERROR: more than one ES file specified\n\n";
std::cout << desc << finalText << std::endl;
return false;
}*/
info.filename = variables["input-file"].as< std::vector<std::string> >()[0];
if (variables["input-file"].as< std::vector<std::string> >().size() > 1)
info.outname = variables["input-file"].as< std::vector<std::string> >()[1];
info.raw_given = variables.count ("raw");
info.quiet_given = variables.count ("quiet");
info.loadcells_given = variables.count ("loadcells");
info.plain_given = (variables.count("plain") > 0);
// Font encoding settings
info.encoding = variables["encoding"].as<std::string>();
if(info.encoding != "win1250" && info.encoding != "win1251" && info.encoding != "win1252")
{
std::cout << info.encoding << " is not a valid encoding option." << std::endl;
info.encoding = "win1252";
}
std::cout << ToUTF8::encodingUsingMessage(info.encoding) << std::endl;
return true;
}
void printRaw(ESM::ESMReader &esm);
void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info);
int load(Arguments& info);
int clone(Arguments& info);
int comp(Arguments& info);
int main(int argc, char**argv)
{
Arguments info;
if(!parseOptions (argc, argv, info))
return 1;
if (info.mode == "dump")
return load(info);
else if (info.mode == "clone")
return clone(info);
else if (info.mode == "comp")
return comp(info);
else
{
std::cout << "Invalid or no mode specified, dying horribly. Have a nice day." << std::endl;
return 1;
}
return 0;
}
void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info)
{
bool quiet = (info.quiet_given || info.mode == "clone");
bool save = (info.mode == "clone");
// Skip back to the beginning of the reference list
// FIXME: Changes to the references backend required to support multiple plugins have
// almost certainly broken this following line. I'll leave it as is for now, so that
// the compiler does not complain.
cell.restore(esm, 0);
// Loop through all the references
ESM::CellRef ref;
if(!quiet) std::cout << " References:\n";
while(cell.getNextRef(esm, ref))
{
if (save) {
info.data.mCellRefs[&cell].push_back(ref);
}
if(quiet) continue;
std::cout << " Refnum: " << ref.mRefnum << std::endl;
std::cout << " ID: '" << ref.mRefID << "'\n";
std::cout << " Owner: '" << ref.mOwner << "'\n";
std::cout << " Enchantment charge: '" << ref.mEnchantmentCharge << "'\n";
std::cout << " Uses/health: '" << ref.mCharge << "'\n";
std::cout << " Gold value: '" << ref.mGoldValue << "'\n";
std::cout << " Blocked: '" << static_cast<int>(ref.mReferenceBlocked) << "'" << std::endl;
}
}
void printRaw(ESM::ESMReader &esm)
{
while(esm.hasMoreRecs())
{
ESM::NAME n = esm.getRecName();
std::cout << "Record: " << n.toString() << std::endl;
esm.getRecHeader();
while(esm.hasMoreSubs())
{
uint64_t offs = esm.getOffset();
esm.getSubName();
esm.skipHSub();
n = esm.retSubName();
std::cout << " " << n.toString() << " - " << esm.getSubSize()
<< " bytes @ 0x" << std::hex << offs << "\n";
}
}
}
int load(Arguments& info)
{
ESM::ESMReader& esm = info.reader;
ToUTF8::Utf8Encoder encoder (ToUTF8::calculateEncoding(info.encoding));
esm.setEncoder(&encoder);
std::string filename = info.filename;
std::cout << "Loading file: " << filename << std::endl;
std::list<int> skipped;
try {
if(info.raw_given && info.mode == "dump")
{
std::cout << "RAW file listing:\n";
esm.openRaw(filename);
printRaw(esm);
return 0;
}
bool quiet = (info.quiet_given || info.mode == "clone");
bool loadCells = (info.loadcells_given || info.mode == "clone");
bool save = (info.mode == "clone");
esm.open(filename);
info.data.author = esm.getAuthor();
info.data.description = esm.getDesc();
info.data.masters = esm.getMasters();
if (!quiet)
{
std::cout << "Author: " << esm.getAuthor() << std::endl
<< "Description: " << esm.getDesc() << std::endl
<< "File format version: " << esm.getFVer() << std::endl;
std::vector<ESM::Header::MasterData> m = esm.getMasters();
if (!m.empty())
{
std::cout << "Masters:" << std::endl;
for(unsigned int i=0;i<m.size();i++)
std::cout << " " << m[i].name << ", " << m[i].size << " bytes" << std::endl;
}
}
// Loop through all records
while(esm.hasMoreRecs())
{
ESM::NAME n = esm.getRecName();
uint32_t flags;
esm.getRecHeader(flags);
// Is the user interested in this record type?
bool interested = true;
if (info.types.size() > 0)
{
std::vector<std::string>::iterator match;
match = std::find(info.types.begin(), info.types.end(),
n.toString());
if (match == info.types.end()) interested = false;
}
std::string id = esm.getHNOString("NAME");
if(!quiet && interested)
std::cout << "\nRecord: " << n.toString()
<< " '" << id << "'\n";
EsmTool::RecordBase *record = EsmTool::RecordBase::create(n);
if (record == 0) {
if (std::find(skipped.begin(), skipped.end(), n.val) == skipped.end())
{
std::cout << "Skipping " << n.toString() << " records." << std::endl;
skipped.push_back(n.val);
}
esm.skipRecord();
if (quiet) break;
std::cout << " Skipping\n";
} else {
if (record->getType().val == ESM::REC_GMST) {
// preset id for GameSetting record
record->cast<ESM::GameSetting>()->get().mId = id;
}
record->setId(id);
record->setFlags((int) flags);
record->setPrintPlain(info.plain_given);
record->load(esm);
if (!quiet && interested) record->print();
if (record->getType().val == ESM::REC_CELL && loadCells) {
loadCell(record->cast<ESM::Cell>()->get(), esm, info);
}
if (save) {
info.data.mRecords.push_back(record);
} else {
delete record;
}
++info.data.mRecordStats[n.val];
}
}
} catch(std::exception &e) {
std::cout << "\nERROR:\n\n " << e.what() << std::endl;
typedef std::deque<EsmTool::RecordBase *> RecStore;
RecStore &store = info.data.mRecords;
for (RecStore::iterator it = store.begin(); it != store.end(); ++it)
{
delete *it;
}
store.clear();
return 1;
}
return 0;
}
#include <iomanip>
int clone(Arguments& info)
{
if (info.outname.empty())
{
std::cout << "You need to specify an output name" << std::endl;
return 1;
}
if (load(info) != 0)
{
std::cout << "Failed to load, aborting." << std::endl;
return 1;
}
int recordCount = info.data.mRecords.size();
int digitCount = 1; // For a nicer output
if (recordCount > 9) ++digitCount;
if (recordCount > 99) ++digitCount;
if (recordCount > 999) ++digitCount;
if (recordCount > 9999) ++digitCount;
if (recordCount > 99999) ++digitCount;
if (recordCount > 999999) ++digitCount;
std::cout << "Loaded " << recordCount << " records:" << std::endl << std::endl;
ESM::NAME name;
int i = 0;
typedef std::map<int, int> Stats;
Stats &stats = info.data.mRecordStats;
for (Stats::iterator it = stats.begin(); it != stats.end(); ++it)
{
name.val = it->first;
float amount = it->second;
std::cout << std::setw(digitCount) << amount << " " << name.toString() << " ";
if (++i % 3 == 0)
std::cout << std::endl;
}
if (i % 3 != 0)
std::cout << std::endl;
std::cout << std::endl << "Saving records to: " << info.outname << "..." << std::endl;
ESM::ESMWriter& esm = info.writer;
ToUTF8::Utf8Encoder encoder (ToUTF8::calculateEncoding(info.encoding));
esm.setEncoder(&encoder);
esm.setAuthor(info.data.author);
esm.setDescription(info.data.description);
esm.setVersion(info.data.version);
esm.setRecordCount (recordCount);
for (std::vector<ESM::Header::MasterData>::iterator it = info.data.masters.begin(); it != info.data.masters.end(); ++it)
esm.addMaster(it->name, it->size);
std::fstream save(info.outname.c_str(), std::fstream::out | std::fstream::binary);
esm.save(save);
int saved = 0;
typedef std::deque<EsmTool::RecordBase *> Records;
Records &records = info.data.mRecords;
for (Records::iterator it = records.begin(); it != records.end() && i > 0; ++it)
{
EsmTool::RecordBase *record = *it;
name.val = record->getType().val;
esm.startRecord(name.toString(), record->getFlags());
// TODO wrap this with std::set
if (ESMData::sLabeledRec.count(name.val) > 0) {
esm.writeHNCString("NAME", record->getId());
} else {
esm.writeHNOString("NAME", record->getId());
}
record->save(esm);
if (name.val == ESM::REC_CELL) {
ESM::Cell *ptr = &record->cast<ESM::Cell>()->get();
if (!info.data.mCellRefs[ptr].empty()) {
typedef std::deque<ESM::CellRef> RefList;
RefList &refs = info.data.mCellRefs[ptr];
for (RefList::iterator it = refs.begin(); it != refs.end(); ++it)
{
it->save(esm);
}
}
}
esm.endRecord(name.toString());
saved++;
int perc = (saved / (float)recordCount)*100;
if (perc % 10 == 0)
{
std::cerr << "\r" << perc << "%";
}
}
std::cout << "\rDone!" << std::endl;
esm.close();
save.close();
return 0;
}
int comp(Arguments& info)
{
if (info.filename.empty() || info.outname.empty())
{
std::cout << "You need to specify two input files" << std::endl;
return 1;
}
Arguments fileOne;
Arguments fileTwo;
fileOne.raw_given = 0;
fileTwo.raw_given = 0;
fileOne.mode = "clone";
fileTwo.mode = "clone";
fileOne.encoding = info.encoding;
fileTwo.encoding = info.encoding;
fileOne.filename = info.filename;
fileTwo.filename = info.outname;
if (load(fileOne) != 0)
{
std::cout << "Failed to load " << info.filename << ", aborting comparison." << std::endl;
return 1;
}
if (load(fileTwo) != 0)
{
std::cout << "Failed to load " << info.outname << ", aborting comparison." << std::endl;
return 1;
}
if (fileOne.data.mRecords.size() != fileTwo.data.mRecords.size())
{
std::cout << "Not equal, different amount of records." << std::endl;
return 1;
}
return 0;
}

880
apps/esmtool/labels.cpp Normal file
View file

@ -0,0 +1,880 @@
#include "labels.hpp"
#include <components/esm/loadbody.hpp>
#include <components/esm/loadcell.hpp>
#include <components/esm/loadcont.hpp>
#include <components/esm/loadcrea.hpp>
#include <components/esm/loadlevlist.hpp>
#include <components/esm/loadligh.hpp>
#include <components/esm/loadmgef.hpp>
#include <components/esm/loadnpc.hpp>
#include <components/esm/loadrace.hpp>
#include <components/esm/loadspel.hpp>
#include <components/esm/loadweap.hpp>
#include <components/esm/aipackage.hpp>
#include <iostream>
#include <boost/format.hpp>
std::string bodyPartLabel(int idx)
{
const char *bodyPartLabels[] = {
"Head",
"Hair",
"Neck",
"Cuirass",
"Groin",
"Skirt",
"Right Hand",
"Left Hand",
"Right Wrist",
"Left Wrist",
"Shield",
"Right Forearm",
"Left Forearm",
"Right Upperarm",
"Left Upperarm",
"Right Foot",
"Left Foot",
"Right Ankle",
"Left Ankle",
"Right Knee",
"Left Knee",
"Right Leg",
"Left Leg",
"Right Shoulder",
"Left Shoulder",
"Weapon",
"Tail"
};
if (idx >= 0 && idx <= 26)
return bodyPartLabels[idx];
else
return "Invalid";
}
std::string meshPartLabel(int idx)
{
const char *meshPartLabels[] = {
"Head",
"Hair",
"Neck",
"Chest",
"Groin",
"Hand",
"Wrist",
"Forearm",
"Upperarm",
"Foot",
"Ankle",
"Knee",
"Upper Leg",
"Clavicle",
"Tail"
};
if (idx >= 0 && idx <= ESM::BodyPart::MP_Tail)
return meshPartLabels[idx];
else
return "Invalid";
}
std::string meshTypeLabel(int idx)
{
const char *meshTypeLabels[] = {
"Skin",
"Clothing",
"Armor"
};
if (idx >= 0 && idx <= ESM::BodyPart::MT_Armor)
return meshTypeLabels[idx];
else
return "Invalid";
}
std::string clothingTypeLabel(int idx)
{
const char *clothingTypeLabels[] = {
"Pants",
"Shoes",
"Shirt",
"Belt",
"Robe",
"Right Glove",
"Left Glove",
"Skirt",
"Ring",
"Amulet"
};
if (idx >= 0 && idx <= 9)
return clothingTypeLabels[idx];
else
return "Invalid";
}
std::string armorTypeLabel(int idx)
{
const char *armorTypeLabels[] = {
"Helmet",
"Cuirass",
"Left Pauldron",
"Right Pauldron",
"Greaves",
"Boots",
"Left Gauntlet",
"Right Gauntlet",
"Shield",
"Left Bracer",
"Right Bracer"
};
if (idx >= 0 && idx <= 10)
return armorTypeLabels[idx];
else
return "Invalid";
}
std::string dialogTypeLabel(int idx)
{
const char *dialogTypeLabels[] = {
"Topic",
"Voice",
"Greeting",
"Persuasion",
"Journal"
};
if (idx >= 0 && idx <= 4)
return dialogTypeLabels[idx];
else if (idx == -1)
return "Deleted";
else
return "Invalid";
}
std::string questStatusLabel(int idx)
{
const char *questStatusLabels[] = {
"None",
"Name",
"Finished",
"Restart",
"Deleted"
};
if (idx >= 0 && idx <= 4)
return questStatusLabels[idx];
else
return "Invalid";
}
std::string creatureTypeLabel(int idx)
{
const char *creatureTypeLabels[] = {
"Creature",
"Daedra",
"Undead",
"Humanoid",
};
if (idx >= 0 && idx <= 3)
return creatureTypeLabels[idx];
else
return "Invalid";
}
std::string soundTypeLabel(int idx)
{
const char *soundTypeLabels[] = {
"Left Foot",
"Right Foot",
"Swim Left",
"Swim Right",
"Moan",
"Roar",
"Scream",
"Land"
};
if (idx >= 0 && idx <= 7)
return soundTypeLabels[idx];
else
return "Invalid";
}
std::string weaponTypeLabel(int idx)
{
const char *weaponTypeLabels[] = {
"Short Blade One Hand",
"Long Blade One Hand",
"Long Blade Two Hand",
"Blunt One Hand",
"Blunt Two Close",
"Blunt Two Wide",
"Spear Two Wide",
"Axe One Hand",
"Axe Two Hand",
"Marksman Bow",
"Marksman Crossbow",
"Marksman Thrown",
"Arrow",
"Bolt"
};
if (idx >= 0 && idx <= 13)
return weaponTypeLabels[idx];
else
return "Invalid";
}
std::string aiTypeLabel(int type)
{
if (type == ESM::AI_Wander) return "Wander";
else if (type == ESM::AI_Travel) return "Travel";
else if (type == ESM::AI_Follow) return "Follow";
else if (type == ESM::AI_Escort) return "Escort";
else if (type == ESM::AI_Activate) return "Activate";
else return "Invalid";
}
std::string magicEffectLabel(int idx)
{
const char* magicEffectLabels [] = {
"Water Breathing",
"Swift Swim",
"Water Walking",
"Shield",
"Fire Shield",
"Lightning Shield",
"Frost Shield",
"Burden",
"Feather",
"Jump",
"Levitate",
"SlowFall",
"Lock",
"Open",
"Fire Damage",
"Shock Damage",
"Frost Damage",
"Drain Attribute",
"Drain Health",
"Drain Magicka",
"Drain Fatigue",
"Drain Skill",
"Damage Attribute",
"Damage Health",
"Damage Magicka",
"Damage Fatigue",
"Damage Skill",
"Poison",
"Weakness to Fire",
"Weakness to Frost",
"Weakness to Shock",
"Weakness to Magicka",
"Weakness to Common Disease",
"Weakness to Blight Disease",
"Weakness to Corprus Disease",
"Weakness to Poison",
"Weakness to Normal Weapons",
"Disintegrate Weapon",
"Disintegrate Armor",
"Invisibility",
"Chameleon",
"Light",
"Sanctuary",
"Night Eye",
"Charm",
"Paralyze",
"Silence",
"Blind",
"Sound",
"Calm Humanoid",
"Calm Creature",
"Frenzy Humanoid",
"Frenzy Creature",
"Demoralize Humanoid",
"Demoralize Creature",
"Rally Humanoid",
"Rally Creature",
"Dispel",
"Soultrap",
"Telekinesis",
"Mark",
"Recall",
"Divine Intervention",
"Almsivi Intervention",
"Detect Animal",
"Detect Enchantment",
"Detect Key",
"Spell Absorption",
"Reflect",
"Cure Common Disease",
"Cure Blight Disease",
"Cure Corprus Disease",
"Cure Poison",
"Cure Paralyzation",
"Restore Attribute",
"Restore Health",
"Restore Magicka",
"Restore Fatigue",
"Restore Skill",
"Fortify Attribute",
"Fortify Health",
"Fortify Magicka",
"Fortify Fatigue",
"Fortify Skill",
"Fortify Maximum Magicka",
"Absorb Attribute",
"Absorb Health",
"Absorb Magicka",
"Absorb Fatigue",
"Absorb Skill",
"Resist Fire",
"Resist Frost",
"Resist Shock",
"Resist Magicka",
"Resist Common Disease",
"Resist Blight Disease",
"Resist Corprus Disease",
"Resist Poison",
"Resist Normal Weapons",
"Resist Paralysis",
"Remove Curse",
"Turn Undead",
"Summon Scamp",
"Summon Clannfear",
"Summon Daedroth",
"Summon Dremora",
"Summon Ancestral Ghost",
"Summon Skeletal Minion",
"Summon Bonewalker",
"Summon Greater Bonewalker",
"Summon Bonelord",
"Summon Winged Twilight",
"Summon Hunger",
"Summon Golden Saint",
"Summon Flame Atronach",
"Summon Frost Atronach",
"Summon Storm Atronach",
"Fortify Attack",
"Command Creature",
"Command Humanoid",
"Bound Dagger",
"Bound Longsword",
"Bound Mace",
"Bound Battle Axe",
"Bound Spear",
"Bound Longbow",
"EXTRA SPELL",
"Bound Cuirass",
"Bound Helm",
"Bound Boots",
"Bound Shield",
"Bound Gloves",
"Corprus",
"Vampirism",
"Summon Centurion Sphere",
"Sun Damage",
"Stunted Magicka",
"Summon Fabricant",
"sEffectSummonCreature01",
"sEffectSummonCreature02",
"sEffectSummonCreature03",
"sEffectSummonCreature04",
"sEffectSummonCreature05"
};
if (idx >= 0 && idx <= 143)
return magicEffectLabels[idx];
else
return "Invalid";
}
std::string attributeLabel(int idx)
{
const char* attributeLabels [] = {
"Strength",
"Intelligence",
"Willpower",
"Agility",
"Speed",
"Endurance",
"Personality",
"Luck"
};
if (idx >= 0 && idx <= 7)
return attributeLabels[idx];
else
return "Invalid";
}
std::string spellTypeLabel(int idx)
{
const char* spellTypeLabels [] = {
"Spells",
"Abilities",
"Blight Disease",
"Disease",
"Curse",
"Powers"
};
if (idx >= 0 && idx <= 5)
return spellTypeLabels[idx];
else
return "Invalid";
}
std::string specializationLabel(int idx)
{
const char* specializationLabels [] = {
"Combat",
"Magic",
"Stealth"
};
if (idx >= 0 && idx <= 2)
return specializationLabels[idx];
else
return "Invalid";
}
std::string skillLabel(int idx)
{
const char* skillLabels [] = {
"Block",
"Armorer",
"Medium Armor",
"Heavy Armor",
"Blunt Weapon",
"Long Blade",
"Axe",
"Spear",
"Athletics",
"Enchant",
"Destruction",
"Alteration",
"Illusion",
"Conjuration",
"Mysticism",
"Restoration",
"Alchemy",
"Unarmored",
"Security",
"Sneak",
"Acrobatics",
"Light Armor",
"Short Blade",
"Marksman",
"Mercantile",
"Speechcraft",
"Hand-to-hand"
};
if (idx >= 0 && idx <= 27)
return skillLabels[idx];
else
return "Invalid";
}
std::string apparatusTypeLabel(int idx)
{
const char* apparatusTypeLabels [] = {
"Mortar",
"Alembic",
"Calcinator",
"Retort",
};
if (idx >= 0 && idx <= 3)
return apparatusTypeLabels[idx];
else
return "Invalid";
}
std::string rangeTypeLabel(int idx)
{
const char* rangeTypeLabels [] = {
"Self",
"Touch",
"Target"
};
if (idx >= 0 && idx <= 3)
return rangeTypeLabels[idx];
else
return "Invalid";
}
std::string schoolLabel(int idx)
{
const char* schoolLabels [] = {
"Alteration",
"Conjuration",
"Destruction",
"Illusion",
"Mysticism",
"Restoration"
};
if (idx >= 0 && idx <= 5)
return schoolLabels[idx];
else
return "Invalid";
}
std::string enchantTypeLabel(int idx)
{
const char* enchantTypeLabels [] = {
"Cast Once",
"Cast When Strikes",
"Cast When Used",
"Constant Effect"
};
if (idx >= 0 && idx <= 3)
return enchantTypeLabels[idx];
else
return "Invalid";
}
std::string ruleFunction(int idx)
{
std::string ruleFunctions[] = {
"Reaction Low",
"Reaction High",
"Rank Requirement",
"NPC? Reputation",
"Health Percent",
"Player Reputation",
"NPC Level",
"Player Health Percent",
"Player Magicka",
"Player Fatigue",
"Player Attribute Strength",
"Player Skill Block",
"Player Skill Armorer",
"Player Skill Medium Armor",
"Player Skill Heavy Armor",
"Player Skill Blunt Weapon",
"Player Skill Long Blade",
"Player Skill Axe",
"Player Skill Spear",
"Player Skill Athletics",
"Player Skill Enchant",
"Player Skill Destruction",
"Player Skill Alteration",
"Player Skill Illusion",
"Player Skill Conjuration",
"Player Skill Mysticism",
"Player SKill Restoration",
"Player Skill Alchemy",
"Player Skill Unarmored",
"Player Skill Security",
"Player Skill Sneak",
"Player Skill Acrobatics",
"Player Skill Light Armor",
"Player Skill Short Blade",
"Player Skill Marksman",
"Player Skill Mercantile",
"Player Skill Speechcraft",
"Player Skill Hand to Hand",
"Player Gender",
"Player Expelled from Faction",
"Player Diseased (Common)",
"Player Diseased (Blight)",
"Player Clothing Modifier",
"Player Crime Level",
"Player Same Sex",
"Player Same Race",
"Player Same Faction",
"Faction Rank Difference",
"Player Detected",
"Alarmed",
"Choice Selected",
"Player Attribute Intelligence",
"Player Attribute Willpower",
"Player Attribute Agility",
"Player Attribute Speed",
"Player Attribute Endurance",
"Player Attribute Personality",
"Player Attribute Luck",
"Player Diseased (Corprus)",
"Weather",
"Player is a Vampire",
"Player Level",
"Attacked",
"NPC Talked to Player",
"Player Health",
"Creature Target",
"Friend Hit",
"Fight",
"Hello",
"Alarm",
"Flee",
"Should Attack",
"Werewolf"
};
if (idx >= 0 && idx <= 72)
return ruleFunctions[idx];
else
return "Invalid";
}
// The "unused flag bits" should probably be defined alongside the
// defined bits in the ESM component. The names of the flag bits are
// very inconsistent.
std::string bodyPartFlags(int flags)
{
std::string properties = "";
if (flags == 0) properties += "[None] ";
if (flags & ESM::BodyPart::BPF_Female) properties += "Female ";
if (flags & ESM::BodyPart::BPF_NotPlayable) properties += "NotPlayable ";
int unused = (0xFFFFFFFF ^
(ESM::BodyPart::BPF_Female|
ESM::BodyPart::BPF_NotPlayable));
if (flags & unused) properties += "Invalid ";
properties += str(boost::format("(0x%08X)") % flags);
return properties;
}
std::string cellFlags(int flags)
{
std::string properties = "";
if (flags == 0) properties += "[None] ";
if (flags & ESM::Cell::HasWater) properties += "HasWater ";
if (flags & ESM::Cell::Interior) properties += "Interior ";
if (flags & ESM::Cell::NoSleep) properties += "NoSleep ";
if (flags & ESM::Cell::QuasiEx) properties += "QuasiEx ";
// This used value is not in the ESM component.
if (flags & 0x00000040) properties += "Unknown ";
int unused = (0xFFFFFFFF ^
(ESM::Cell::HasWater|
ESM::Cell::Interior|
ESM::Cell::NoSleep|
ESM::Cell::QuasiEx|
0x00000040));
if (flags & unused) properties += "Invalid ";
properties += str(boost::format("(0x%08X)") % flags);
return properties;
}
std::string containerFlags(int flags)
{
std::string properties = "";
if (flags == 0) properties += "[None] ";
if (flags & ESM::Container::Unknown) properties += "Unknown ";
if (flags & ESM::Container::Organic) properties += "Organic ";
if (flags & ESM::Container::Respawn) properties += "Respawn ";
int unused = (0xFFFFFFFF ^
(ESM::Container::Unknown|
ESM::Container::Organic|
ESM::Container::Respawn));
if (flags & unused) properties += "Invalid ";
properties += str(boost::format("(0x%08X)") % flags);
return properties;
}
std::string creatureFlags(int flags)
{
std::string properties = "";
if (flags == 0) properties += "[None] ";
if (flags & ESM::Creature::None) properties += "All ";
if (flags & ESM::Creature::Walks) properties += "Walks ";
if (flags & ESM::Creature::Swims) properties += "Swims ";
if (flags & ESM::Creature::Flies) properties += "Flies ";
if (flags & ESM::Creature::Biped) properties += "Biped ";
if (flags & ESM::Creature::Respawn) properties += "Respawn ";
if (flags & ESM::Creature::Weapon) properties += "Weapon ";
if (flags & ESM::Creature::Skeleton) properties += "Skeleton ";
if (flags & ESM::Creature::Metal) properties += "Metal ";
if (flags & ESM::Creature::Essential) properties += "Essential ";
int unused = (0xFFFFFFFF ^
(ESM::Creature::None|
ESM::Creature::Walks|
ESM::Creature::Swims|
ESM::Creature::Flies|
ESM::Creature::Biped|
ESM::Creature::Respawn|
ESM::Creature::Weapon|
ESM::Creature::Skeleton|
ESM::Creature::Metal|
ESM::Creature::Essential));
if (flags & unused) properties += "Invalid ";
properties += str(boost::format("(0x%08X)") % flags);
return properties;
}
std::string landFlags(int flags)
{
std::string properties = "";
// The ESM component says that this first four bits are used, but
// only the first three bits are used as far as I can tell.
// There's also no enumeration of the bit in the ESM component.
if (flags == 0) properties += "[None] ";
if (flags & 0x00000001) properties += "Unknown1 ";
if (flags & 0x00000004) properties += "Unknown3 ";
if (flags & 0x00000002) properties += "Unknown2 ";
if (flags & 0xFFFFFFF8) properties += "Invalid ";
properties += str(boost::format("(0x%08X)") % flags);
return properties;
}
std::string leveledListFlags(int flags)
{
std::string properties = "";
if (flags == 0) properties += "[None] ";
if (flags & ESM::LeveledListBase::AllLevels) properties += "AllLevels ";
// This flag apparently not present on creature lists...
if (flags & ESM::LeveledListBase::Each) properties += "Each ";
int unused = (0xFFFFFFFF ^
(ESM::LeveledListBase::AllLevels|
ESM::LeveledListBase::Each));
if (flags & unused) properties += "Invalid ";
properties += str(boost::format("(0x%08X)") % flags);
return properties;
}
std::string lightFlags(int flags)
{
std::string properties = "";
if (flags == 0) properties += "[None] ";
if (flags & ESM::Light::Dynamic) properties += "Dynamic ";
if (flags & ESM::Light::Fire) properties += "Fire ";
if (flags & ESM::Light::Carry) properties += "Carry ";
if (flags & ESM::Light::Flicker) properties += "Flicker ";
if (flags & ESM::Light::FlickerSlow) properties += "FlickerSlow ";
if (flags & ESM::Light::Pulse) properties += "Pulse ";
if (flags & ESM::Light::PulseSlow) properties += "PulseSlow ";
if (flags & ESM::Light::Negative) properties += "Negative ";
if (flags & ESM::Light::OffDefault) properties += "OffDefault ";
int unused = (0xFFFFFFFF ^
(ESM::Light::Dynamic|
ESM::Light::Fire|
ESM::Light::Carry|
ESM::Light::Flicker|
ESM::Light::FlickerSlow|
ESM::Light::Pulse|
ESM::Light::PulseSlow|
ESM::Light::Negative|
ESM::Light::OffDefault));
if (flags & unused) properties += "Invalid ";
properties += str(boost::format("(0x%08X)") % flags);
return properties;
}
std::string magicEffectFlags(int flags)
{
std::string properties = "";
if (flags == 0) properties += "[None] ";
// Enchanting & SpellMaking occur on the same list of effects.
// "EXTRA SPELL" appears in the construction set under both the
// spell making and enchanting tabs as an allowed effect. Since
// most of the effects without this flags are defective in various
// ways, it's still very unclear what these flag bits are.
if (flags & ESM::MagicEffect::SpellMaking) properties += "SpellMaking ";
if (flags & ESM::MagicEffect::Enchanting) properties += "Enchanting ";
if (flags & 0x00000040) properties += "RangeNoSelf ";
if (flags & 0x00000080) properties += "RangeTouch ";
if (flags & 0x00000100) properties += "RangeTarget ";
if (flags & 0x00001000) properties += "Unknown2 ";
if (flags & 0x00000001) properties += "AffectSkill ";
if (flags & 0x00000002) properties += "AffectAttribute ";
if (flags & ESM::MagicEffect::NoDuration) properties += "NoDuration ";
if (flags & 0x00000008) properties += "NoMagnitude ";
if (flags & 0x00000010) properties += "Negative ";
if (flags & 0x00000020) properties += "Unknown1 ";
// ESM componet says 0x800 is negative, but none of the magic
// effects have this flags set.
if (flags & ESM::MagicEffect::Negative) properties += "Unused ";
// Since only Chameleon has this flag it could be anything
// that uniquely distinguishes Chameleon.
if (flags & 0x00002000) properties += "Chameleon ";
if (flags & 0x00004000) properties += "Bound ";
if (flags & 0x00008000) properties += "Summon ";
// Calm, Demoralize, Frenzy, Lock, Open, Rally, Soultrap, Turn Unded
if (flags & 0x00010000) properties += "Unknown3 ";
if (flags & 0x00020000) properties += "Absorb ";
if (flags & 0xFFFC0000) properties += "Invalid ";
properties += str(boost::format("(0x%08X)") % flags);
return properties;
}
std::string npcFlags(int flags)
{
std::string properties = "";
if (flags == 0) properties += "[None] ";
// Mythicmods and the ESM component differ. Mythicmods says
// 0x8=None and 0x10=AutoCalc, while our code defines 0x8 as
// AutoCalc. The former seems to be correct. All Bethesda
// records have bit 0x8 set. A suspiciously large portion of
// females have autocalc turned off.
if (flags & ESM::NPC::Autocalc) properties += "Unknown ";
if (flags & 0x00000010) properties += "Autocalc ";
if (flags & ESM::NPC::Female) properties += "Female ";
if (flags & ESM::NPC::Respawn) properties += "Respawn ";
if (flags & ESM::NPC::Essential) properties += "Essential ";
// These two flags do not appear on any NPCs and may have been
// confused with the flags for creatures.
if (flags & ESM::NPC::Skeleton) properties += "Skeleton ";
if (flags & ESM::NPC::Metal) properties += "Metal ";
// Whether corpses persist is a bit that is unaccounted for,
// however the only unknown bit occurs on ALL records, and
// relatively few NPCs have this bit set.
int unused = (0xFFFFFFFF ^
(ESM::NPC::Autocalc|
0x00000010|
ESM::NPC::Female|
ESM::NPC::Respawn|
ESM::NPC::Essential|
ESM::NPC::Skeleton|
ESM::NPC::Metal));
if (flags & unused) properties += "Invalid ";
properties += str(boost::format("(0x%08X)") % flags);
return properties;
}
std::string raceFlags(int flags)
{
std::string properties = "";
if (flags == 0) properties += "[None] ";
// All races have the playable flag set in Bethesda files.
if (flags & ESM::Race::Playable) properties += "Playable ";
if (flags & ESM::Race::Beast) properties += "Beast ";
int unused = (0xFFFFFFFF ^
(ESM::Race::Playable|
ESM::Race::Beast));
if (flags & unused) properties += "Invalid ";
properties += str(boost::format("(0x%08X)") % flags);
return properties;
}
std::string spellFlags(int flags)
{
std::string properties = "";
if (flags == 0) properties += "[None] ";
if (flags & ESM::Spell::F_Autocalc) properties += "Autocalc ";
if (flags & ESM::Spell::F_PCStart) properties += "PCStart ";
if (flags & ESM::Spell::F_Always) properties += "Always ";
int unused = (0xFFFFFFFF ^
(ESM::Spell::F_Autocalc|
ESM::Spell::F_PCStart|
ESM::Spell::F_Always));
if (flags & unused) properties += "Invalid ";
properties += str(boost::format("(0x%08X)") % flags);
return properties;
}
std::string weaponFlags(int flags)
{
std::string properties = "";
if (flags == 0) properties += "[None] ";
// The interpretation of the flags are still unclear to me.
// Apparently you can't be Silver without being Magical? Many of
// the "Magical" weapons don't have enchantments of any sort.
if (flags & ESM::Weapon::Magical) properties += "Magical ";
if (flags & ESM::Weapon::Silver) properties += "Silver ";
int unused = (0xFFFFFFFF ^
(ESM::Weapon::Magical|
ESM::Weapon::Silver));
if (flags & unused) properties += "Invalid ";
properties += str(boost::format("(0x%08X)") % flags);
return properties;
}

64
apps/esmtool/labels.hpp Normal file
View file

@ -0,0 +1,64 @@
#ifndef OPENMW_ESMTOOL_LABELS_H
#define OPENMW_ESMTOOL_LABELS_H
#include <string>
std::string bodyPartLabel(int idx);
std::string meshPartLabel(int idx);
std::string meshTypeLabel(int idx);
std::string clothingTypeLabel(int idx);
std::string armorTypeLabel(int idx);
std::string dialogTypeLabel(int idx);
std::string questStatusLabel(int idx);
std::string creatureTypeLabel(int idx);
std::string soundTypeLabel(int idx);
std::string weaponTypeLabel(int idx);
// This function's a bit different because the types are record types,
// not consecutive values.
std::string aiTypeLabel(int type);
// This one's also a bit different, because it enumerates dialog
// select rule functions, not types. Structurally, it still converts
// indexes to strings for display.
std::string ruleFunction(int idx);
// The labels below here can all be loaded from GMSTs, but are not
// currently because among other things, that requires loading the
// GMSTs before dumping any of the records.
// If the data format supported ordered lists of GMSTs (post 1.0), the
// lists could define the valid values, their localization strings,
// and the indexes for referencing the types in other records in the
// database. Then a single label function could work for all types.
std::string magicEffectLabel(int idx);
std::string attributeLabel(int idx);
std::string spellTypeLabel(int idx);
std::string specializationLabel(int idx);
std::string skillLabel(int idx);
std::string apparatusTypeLabel(int idx);
std::string rangeTypeLabel(int idx);
std::string schoolLabel(int idx);
std::string enchantTypeLabel(int idx);
// The are the flag functions that convert a bitmask into a list of
// human readble strings representing the set bits.
std::string bodyPartFlags(int flags);
std::string cellFlags(int flags);
std::string containerFlags(int flags);
std::string creatureFlags(int flags);
std::string landFlags(int flags);
std::string leveledListFlags(int flags);
std::string lightFlags(int flags);
std::string magicEffectFlags(int flags);
std::string npcFlags(int flags);
std::string raceFlags(int flags);
std::string spellFlags(int flags);
std::string weaponFlags(int flags);
// Missing flags functions:
// aiServicesFlags, possibly more
#endif

1300
apps/esmtool/record.cpp Normal file

File diff suppressed because it is too large Load diff

136
apps/esmtool/record.hpp Normal file
View file

@ -0,0 +1,136 @@
#ifndef OPENMW_ESMTOOL_RECORD_H
#define OPENMW_ESMTOOL_RECORD_H
#include <string>
#include <components/esm/records.hpp>
namespace ESM
{
class ESMReader;
class ESMWriter;
}
namespace EsmTool
{
template <class T> class Record;
class RecordBase
{
protected:
std::string mId;
int mFlags;
ESM::NAME mType;
bool mPrintPlain;
public:
RecordBase () { mPrintPlain = false; }
virtual ~RecordBase() {}
const std::string &getId() const {
return mId;
}
void setId(const std::string &id) {
mId = id;
}
int getFlags() const {
return mFlags;
}
void setFlags(int flags) {
mFlags = flags;
}
ESM::NAME getType() const {
return mType;
}
bool getPrintPlain() const {
return mPrintPlain;
}
void setPrintPlain(bool plain) {
mPrintPlain = plain;
}
virtual void load(ESM::ESMReader &esm) = 0;
virtual void save(ESM::ESMWriter &esm) = 0;
virtual void print() = 0;
static RecordBase *create(ESM::NAME type);
// just make it a bit shorter
template <class T>
Record<T> *cast() {
return static_cast<Record<T> *>(this);
}
};
template <class T>
class Record : public RecordBase
{
T mData;
public:
T &get() {
return mData;
}
void save(ESM::ESMWriter &esm) {
mData.save(esm);
}
void load(ESM::ESMReader &esm) {
mData.load(esm);
}
void print();
};
template<> void Record<ESM::Activator>::print();
template<> void Record<ESM::Potion>::print();
template<> void Record<ESM::Armor>::print();
template<> void Record<ESM::Apparatus>::print();
template<> void Record<ESM::BodyPart>::print();
template<> void Record<ESM::Book>::print();
template<> void Record<ESM::BirthSign>::print();
template<> void Record<ESM::Cell>::print();
template<> void Record<ESM::Class>::print();
template<> void Record<ESM::Clothing>::print();
template<> void Record<ESM::Container>::print();
template<> void Record<ESM::Creature>::print();
template<> void Record<ESM::Dialogue>::print();
template<> void Record<ESM::Door>::print();
template<> void Record<ESM::Enchantment>::print();
template<> void Record<ESM::Faction>::print();
template<> void Record<ESM::Global>::print();
template<> void Record<ESM::GameSetting>::print();
template<> void Record<ESM::DialInfo>::print();
template<> void Record<ESM::Ingredient>::print();
template<> void Record<ESM::Land>::print();
template<> void Record<ESM::CreatureLevList>::print();
template<> void Record<ESM::ItemLevList>::print();
template<> void Record<ESM::Light>::print();
template<> void Record<ESM::Lockpick>::print();
template<> void Record<ESM::Probe>::print();
template<> void Record<ESM::Repair>::print();
template<> void Record<ESM::LandTexture>::print();
template<> void Record<ESM::MagicEffect>::print();
template<> void Record<ESM::Miscellaneous>::print();
template<> void Record<ESM::NPC>::print();
template<> void Record<ESM::Pathgrid>::print();
template<> void Record<ESM::Race>::print();
template<> void Record<ESM::Region>::print();
template<> void Record<ESM::Script>::print();
template<> void Record<ESM::Skill>::print();
template<> void Record<ESM::SoundGenerator>::print();
template<> void Record<ESM::Sound>::print();
template<> void Record<ESM::Spell>::print();
template<> void Record<ESM::StartScript>::print();
template<> void Record<ESM::Static>::print();
template<> void Record<ESM::Weapon>::print();
}
#endif

View file

@ -0,0 +1,139 @@
set(LAUNCHER
datafilespage.cpp
graphicspage.cpp
main.cpp
maindialog.cpp
playpage.cpp
textslotmsgbox.cpp
settings/gamesettings.cpp
settings/graphicssettings.cpp
settings/launchersettings.cpp
utils/checkablemessagebox.cpp
utils/textinputdialog.cpp
${CMAKE_SOURCE_DIR}/files/launcher/launcher.rc
)
if(NOT WIN32)
LIST(APPEND LAUNCHER unshieldthread.cpp)
endif(NOT WIN32)
set(LAUNCHER_HEADER
datafilespage.hpp
graphicspage.hpp
maindialog.hpp
playpage.hpp
unshieldthread.hpp
textslotmsgbox.hpp
settings/gamesettings.hpp
settings/graphicssettings.hpp
settings/launchersettings.hpp
settings/settingsbase.hpp
utils/checkablemessagebox.hpp
utils/textinputdialog.hpp
)
if(NOT WIN32)
LIST(APPEND LAUNCHER_HEADER unshieldthread.hpp)
endif(NOT WIN32)
# Headers that must be pre-processed
set(LAUNCHER_HEADER_MOC
datafilespage.hpp
graphicspage.hpp
maindialog.hpp
playpage.hpp
unshieldthread.hpp
textslotmsgbox.hpp
utils/checkablemessagebox.hpp
utils/textinputdialog.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
)
source_group(launcher FILES ${LAUNCHER} ${LAUNCHER_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/launcher/launcher.qrc)
QT4_WRAP_CPP(MOC_SRCS ${LAUNCHER_HEADER_MOC})
QT4_WRAP_UI(UI_HDRS ${LAUNCHER_UI})
include(${QT_USE_FILE})
include_directories(${CMAKE_CURRENT_BINARY_DIR})
if(NOT WIN32)
include_directories(${LIBUNSHIELD_INCLUDE})
endif(NOT WIN32)
# Main executable
IF(OGRE_STATIC)
IF(WIN32)
ADD_DEFINITIONS(-DENABLE_PLUGIN_Direct3D9 -DENABLE_PLUGIN_GL)
set(OGRE_STATIC_PLUGINS ${OGRE_RenderSystem_Direct3D9_LIBRARIES} ${OGRE_RenderSystem_GL_LIBRARIES})
ELSE(WIN32)
ADD_DEFINITIONS(-DENABLE_PLUGIN_GL)
set(OGRE_STATIC_PLUGINS ${OGRE_RenderSystem_GL_LIBRARIES})
ENDIF(WIN32)
ENDIF(OGRE_STATIC)
add_executable(omwlauncher
${GUI_TYPE}
${LAUNCHER}
${LAUNCHER_HEADER}
${RCC_SRCS}
${MOC_SRCS}
${UI_HDRS}
)
target_link_libraries(omwlauncher
${Boost_LIBRARIES}
${OGRE_LIBRARIES}
${OGRE_STATIC_PLUGINS}
${SDL2_LIBRARY}
${QT_LIBRARIES}
components
)
if(NOT WIN32)
target_link_libraries(omwlauncher
${LIBUNSHIELD_LIBRARY}
)
endif(NOT WIN32)
if(DPKG_PROGRAM)
INSTALL(TARGETS omwlauncher RUNTIME DESTINATION games COMPONENT omwlauncher)
endif()
if (BUILD_WITH_CODE_COVERAGE)
add_definitions (--coverage)
target_link_libraries(omwlauncher gcov)
endif()
# Workaround for binutil => 2.23 problem when linking, should be fixed eventually upstream
if (UNIX AND NOT APPLE)
target_link_libraries(omwlauncher dl Xt)
endif()

View file

@ -0,0 +1,551 @@
#include "datafilespage.hpp"
#include <QPushButton>
#include <QMessageBox>
#include <QCheckBox>
#include <QMenu>
#include <components/files/configurationmanager.hpp>
#include <components/fileorderlist/model/datafilesmodel.hpp>
#include <components/fileorderlist/model/pluginsproxymodel.hpp>
#include <components/fileorderlist/model/esm/esmfile.hpp>
#include <components/fileorderlist/utils/lineedit.hpp>
#include <components/fileorderlist/utils/naturalsort.hpp>
#include <components/fileorderlist/utils/profilescombobox.hpp>
#include "settings/gamesettings.hpp"
#include "settings/launchersettings.hpp"
#include "utils/textinputdialog.hpp"
DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gameSettings, LauncherSettings &launcherSettings, QWidget *parent)
: mCfgMgr(cfg)
, mGameSettings(gameSettings)
, mLauncherSettings(launcherSettings)
, QWidget(parent)
{
setupUi(this);
// Models
mDataFilesModel = new DataFilesModel(this);
mMastersProxyModel = new QSortFilterProxyModel();
mMastersProxyModel->setFilterRegExp(QString("^.*\\.esm"));
mMastersProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
mMastersProxyModel->setSourceModel(mDataFilesModel);
mPluginsProxyModel = new PluginsProxyModel();
mPluginsProxyModel->setFilterRegExp(QString("^.*\\.esp"));
mPluginsProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
mPluginsProxyModel->setSourceModel(mDataFilesModel);
mFilterProxyModel = new QSortFilterProxyModel();
mFilterProxyModel->setDynamicSortFilter(true);
mFilterProxyModel->setSourceModel(mPluginsProxyModel);
QCheckBox checkBox;
unsigned int height = checkBox.sizeHint().height() + 4;
mastersTable->setModel(mMastersProxyModel);
mastersTable->setObjectName("MastersTable");
mastersTable->setContextMenuPolicy(Qt::CustomContextMenu);
mastersTable->setSortingEnabled(false);
mastersTable->setSelectionBehavior(QAbstractItemView::SelectRows);
mastersTable->setSelectionMode(QAbstractItemView::ExtendedSelection);
mastersTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
mastersTable->setAlternatingRowColors(true);
mastersTable->horizontalHeader()->setStretchLastSection(true);
mastersTable->horizontalHeader()->hide();
// Set the row height to the size of the checkboxes
mastersTable->verticalHeader()->setDefaultSectionSize(height);
mastersTable->verticalHeader()->setResizeMode(QHeaderView::Fixed);
mastersTable->verticalHeader()->hide();
pluginsTable->setModel(mFilterProxyModel);
pluginsTable->setObjectName("PluginsTable");
pluginsTable->setContextMenuPolicy(Qt::CustomContextMenu);
pluginsTable->setSortingEnabled(false);
pluginsTable->setSelectionBehavior(QAbstractItemView::SelectRows);
pluginsTable->setSelectionMode(QAbstractItemView::ExtendedSelection);
pluginsTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
pluginsTable->setAlternatingRowColors(true);
pluginsTable->setVerticalScrollMode(QAbstractItemView::ScrollPerItem);
pluginsTable->horizontalHeader()->setStretchLastSection(true);
pluginsTable->horizontalHeader()->hide();
pluginsTable->verticalHeader()->setDefaultSectionSize(height);
pluginsTable->verticalHeader()->setResizeMode(QHeaderView::Fixed);
// Adjust the tableview widths inside the splitter
QList<int> sizeList;
sizeList << mLauncherSettings.value(QString("General/MastersTable/width"), QString("200")).toInt();
sizeList << mLauncherSettings.value(QString("General/PluginTable/width"), QString("340")).toInt();
splitter->setSizes(sizeList);
// Create a dialog for the new profile name input
mNewProfileDialog = new TextInputDialog(tr("New Profile"), tr("Profile name:"), this);
connect(profilesComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentIndexChanged(int)));
connect(mNewProfileDialog->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(updateOkButton(QString)));
connect(pluginsTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex)));
connect(mastersTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex)));
connect(pluginsTable, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint)));
connect(mastersTable, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint)));
connect(mDataFilesModel, SIGNAL(layoutChanged()), this, SLOT(updateViews()));
connect(filterLineEdit, SIGNAL(textChanged(QString)), this, SLOT(filterChanged(QString)));
connect(splitter, SIGNAL(splitterMoved(int,int)), this, SLOT(updateSplitter()));
createActions();
setupDataFiles();
}
void DataFilesPage::createActions()
{
// Add the actions to the toolbuttons
newProfileButton->setDefaultAction(newProfileAction);
deleteProfileButton->setDefaultAction(deleteProfileAction);
// Context menu actions
mContextMenu = new QMenu(this);
mContextMenu->addAction(checkAction);
mContextMenu->addAction(uncheckAction);
}
void DataFilesPage::setupDataFiles()
{
// Set the encoding to the one found in openmw.cfg or the default
mDataFilesModel->setEncoding(mGameSettings.value(QString("encoding"), QString("win1252")));
QStringList paths = mGameSettings.getDataDirs();
foreach (const QString &path, paths) {
mDataFilesModel->addFiles(path);
}
QString dataLocal = mGameSettings.getDataLocal();
if (!dataLocal.isEmpty())
mDataFilesModel->addFiles(dataLocal);
// Sort by date accessed for now
mDataFilesModel->sort(3);
QStringList profiles = mLauncherSettings.subKeys(QString("Profiles/"));
QString profile = mLauncherSettings.value(QString("Profiles/currentprofile"));
if (!profiles.isEmpty())
profilesComboBox->addItems(profiles);
// Add the current profile if empty
if (profilesComboBox->findText(profile) == -1 && !profile.isEmpty())
profilesComboBox->addItem(profile);
if (profilesComboBox->findText(QString("Default")) == -1)
profilesComboBox->addItem(QString("Default"));
if (profile.isEmpty() || profile == QLatin1String("Default")) {
deleteProfileAction->setEnabled(false);
profilesComboBox->setEditEnabled(false);
profilesComboBox->setCurrentIndex(profilesComboBox->findText(QString("Default")));
} else {
profilesComboBox->setEditEnabled(true);
profilesComboBox->setCurrentIndex(profilesComboBox->findText(profile));
}
// We do this here to prevent deletion of profiles when initializing the combobox
connect(profilesComboBox, SIGNAL(profileRenamed(QString,QString)), this, SLOT(profileRenamed(QString,QString)));
connect(profilesComboBox, SIGNAL(profileChanged(QString,QString)), this, SLOT(profileChanged(QString,QString)));
loadSettings();
}
void DataFilesPage::loadSettings()
{
QString profile = mLauncherSettings.value(QString("Profiles/currentprofile"));
if (profile.isEmpty())
return;
mDataFilesModel->uncheckAll();
QStringList masters = mLauncherSettings.values(QString("Profiles/") + profile + QString("/master"), Qt::MatchExactly);
QStringList plugins = mLauncherSettings.values(QString("Profiles/") + profile + QString("/plugin"), Qt::MatchExactly);
foreach (const QString &master, masters) {
QModelIndex index = mDataFilesModel->indexFromItem(mDataFilesModel->findItem(master));
if (index.isValid())
mDataFilesModel->setCheckState(index, Qt::Checked);
}
foreach (const QString &plugin, plugins) {
QModelIndex index = mDataFilesModel->indexFromItem(mDataFilesModel->findItem(plugin));
if (index.isValid())
mDataFilesModel->setCheckState(index, Qt::Checked);
}
}
void DataFilesPage::saveSettings()
{
if (mDataFilesModel->rowCount() < 1)
return;
QString profile = mLauncherSettings.value(QString("Profiles/currentprofile"));
if (profile.isEmpty()) {
profile = profilesComboBox->currentText();
mLauncherSettings.setValue(QString("Profiles/currentprofile"), profile);
}
mLauncherSettings.remove(QString("Profiles/") + profile + QString("/master"));
mLauncherSettings.remove(QString("Profiles/") + profile + QString("/plugin"));
mGameSettings.remove(QString("master"));
mGameSettings.remove(QString("plugin"));
QStringList items = mDataFilesModel->checkedItems();
foreach(const QString &item, items) {
if (item.endsWith(QString(".esm"), Qt::CaseInsensitive)) {
mLauncherSettings.setMultiValue(QString("Profiles/") + profile + QString("/master"), item);
mGameSettings.setMultiValue(QString("master"), item);
} else if (item.endsWith(QString(".esp"), Qt::CaseInsensitive)) {
mLauncherSettings.setMultiValue(QString("Profiles/") + profile + QString("/plugin"), item);
mGameSettings.setMultiValue(QString("plugin"), item);
}
}
}
void DataFilesPage::updateOkButton(const QString &text)
{
// We do this here because we need the profiles combobox text
if (text.isEmpty()) {
mNewProfileDialog->setOkButtonEnabled(false);
return;
}
(profilesComboBox->findText(text) == -1)
? mNewProfileDialog->setOkButtonEnabled(true)
: mNewProfileDialog->setOkButtonEnabled(false);
}
void DataFilesPage::updateSplitter()
{
// Sigh, update the saved splitter size in settings only when moved
// Since getting mSplitter->sizes() if page is hidden returns invalid values
QList<int> sizes = splitter->sizes();
mLauncherSettings.setValue(QString("General/MastersTable/width"), QString::number(sizes.at(0)));
mLauncherSettings.setValue(QString("General/PluginsTable/width"), QString::number(sizes.at(1)));
}
void DataFilesPage::updateViews()
{
// Ensure the columns are hidden because sort() re-enables them
mastersTable->setColumnHidden(1, true);
mastersTable->setColumnHidden(2, true);
mastersTable->setColumnHidden(3, true);
mastersTable->setColumnHidden(4, true);
mastersTable->setColumnHidden(5, true);
mastersTable->setColumnHidden(6, true);
mastersTable->setColumnHidden(7, true);
mastersTable->setColumnHidden(8, true);
pluginsTable->setColumnHidden(1, true);
pluginsTable->setColumnHidden(2, true);
pluginsTable->setColumnHidden(3, true);
pluginsTable->setColumnHidden(4, true);
pluginsTable->setColumnHidden(5, true);
pluginsTable->setColumnHidden(6, true);
pluginsTable->setColumnHidden(7, true);
pluginsTable->setColumnHidden(8, true);
}
void DataFilesPage::setProfilesComboBoxIndex(int index)
{
profilesComboBox->setCurrentIndex(index);
}
void DataFilesPage::slotCurrentIndexChanged(int index)
{
emit profileChanged(index);
}
QAbstractItemModel* DataFilesPage::profilesComboBoxModel()
{
return profilesComboBox->model();
}
int DataFilesPage::profilesComboBoxIndex()
{
return profilesComboBox->currentIndex();
}
void DataFilesPage::on_newProfileAction_triggered()
{
if (mNewProfileDialog->exec() == QDialog::Accepted) {
QString profile = mNewProfileDialog->lineEdit()->text();
profilesComboBox->addItem(profile);
profilesComboBox->setCurrentIndex(profilesComboBox->findText(profile));
}
}
void DataFilesPage::on_deleteProfileAction_triggered()
{
QString profile = profilesComboBox->currentText();
if (profile.isEmpty())
return;
QMessageBox msgBox(this);
msgBox.setWindowTitle(tr("Delete Profile"));
msgBox.setIcon(QMessageBox::Warning);
msgBox.setStandardButtons(QMessageBox::Cancel);
msgBox.setText(tr("Are you sure you want to delete <b>%0</b>?").arg(profile));
QAbstractButton *deleteButton =
msgBox.addButton(tr("Delete"), QMessageBox::ActionRole);
msgBox.exec();
if (msgBox.clickedButton() == deleteButton) {
mLauncherSettings.remove(QString("Profiles/") + profile + QString("/master"));
mLauncherSettings.remove(QString("Profiles/") + profile + QString("/plugin"));
// Remove the profile from the combobox
profilesComboBox->removeItem(profilesComboBox->findText(profile));
}
}
void DataFilesPage::on_checkAction_triggered()
{
if (pluginsTable->hasFocus())
setPluginsCheckstates(Qt::Checked);
if (mastersTable->hasFocus())
setMastersCheckstates(Qt::Checked);
}
void DataFilesPage::on_uncheckAction_triggered()
{
if (pluginsTable->hasFocus())
setPluginsCheckstates(Qt::Unchecked);
if (mastersTable->hasFocus())
setMastersCheckstates(Qt::Unchecked);
}
void DataFilesPage::setMastersCheckstates(Qt::CheckState state)
{
if (!mastersTable->selectionModel()->hasSelection()) {
return;
}
QModelIndexList indexes = mastersTable->selectionModel()->selectedIndexes();
foreach (const QModelIndex &index, indexes)
{
if (!index.isValid())
return;
QModelIndex sourceIndex = mMastersProxyModel->mapToSource(index);
if (!sourceIndex.isValid())
return;
mDataFilesModel->setCheckState(sourceIndex, state);
}
}
void DataFilesPage::setPluginsCheckstates(Qt::CheckState state)
{
if (!pluginsTable->selectionModel()->hasSelection()) {
return;
}
QModelIndexList indexes = pluginsTable->selectionModel()->selectedIndexes();
foreach (const QModelIndex &index, indexes)
{
if (!index.isValid())
return;
QModelIndex sourceIndex = mPluginsProxyModel->mapToSource(
mFilterProxyModel->mapToSource(index));
if (!sourceIndex.isValid())
return;
mDataFilesModel->setCheckState(sourceIndex, state);
}
}
void DataFilesPage::setCheckState(QModelIndex index)
{
if (!index.isValid())
return;
QObject *object = QObject::sender();
// Not a signal-slot call
if (!object)
return;
if (object->objectName() == QLatin1String("PluginsTable")) {
QModelIndex sourceIndex = mPluginsProxyModel->mapToSource(
mFilterProxyModel->mapToSource(index));
if (sourceIndex.isValid()) {
(mDataFilesModel->checkState(sourceIndex) == Qt::Checked)
? mDataFilesModel->setCheckState(sourceIndex, Qt::Unchecked)
: mDataFilesModel->setCheckState(sourceIndex, Qt::Checked);
}
}
if (object->objectName() == QLatin1String("MastersTable")) {
QModelIndex sourceIndex = mMastersProxyModel->mapToSource(index);
if (sourceIndex.isValid()) {
(mDataFilesModel->checkState(sourceIndex) == Qt::Checked)
? mDataFilesModel->setCheckState(sourceIndex, Qt::Unchecked)
: mDataFilesModel->setCheckState(sourceIndex, Qt::Checked);
}
}
return;
}
void DataFilesPage::filterChanged(const QString filter)
{
QRegExp regExp(filter, Qt::CaseInsensitive, QRegExp::FixedString);
mFilterProxyModel->setFilterRegExp(regExp);
}
void DataFilesPage::profileChanged(const QString &previous, const QString &current)
{
// Prevent the deletion of the default profile
if (current == QLatin1String("Default")) {
deleteProfileAction->setEnabled(false);
profilesComboBox->setEditEnabled(false);
} else {
deleteProfileAction->setEnabled(true);
profilesComboBox->setEditEnabled(true);
}
if (previous.isEmpty())
return;
if (profilesComboBox->findText(previous) == -1)
return; // Profile was deleted
// Store the previous profile
mLauncherSettings.setValue(QString("Profiles/currentprofile"), previous);
saveSettings();
mLauncherSettings.setValue(QString("Profiles/currentprofile"), current);
loadSettings();
}
void DataFilesPage::profileRenamed(const QString &previous, const QString &current)
{
if (previous.isEmpty())
return;
// Save the new profile name
mLauncherSettings.setValue(QString("Profiles/currentprofile"), current);
saveSettings();
// Remove the old one
mLauncherSettings.remove(QString("Profiles/") + previous + QString("/master"));
mLauncherSettings.remove(QString("Profiles/") + previous + QString("/plugin"));
// Remove the profile from the combobox
profilesComboBox->removeItem(profilesComboBox->findText(previous));
loadSettings();
}
void DataFilesPage::showContextMenu(const QPoint &point)
{
QObject *object = QObject::sender();
// Not a signal-slot call
if (!object)
return;
if (object->objectName() == QLatin1String("PluginsTable")) {
if (!pluginsTable->selectionModel()->hasSelection())
return;
QPoint globalPos = pluginsTable->mapToGlobal(point);
QModelIndexList indexes = pluginsTable->selectionModel()->selectedIndexes();
// Show the check/uncheck actions depending on the state of the selected items
uncheckAction->setEnabled(false);
checkAction->setEnabled(false);
foreach (const QModelIndex &index, indexes)
{
if (!index.isValid())
return;
QModelIndex sourceIndex = mPluginsProxyModel->mapToSource(
mFilterProxyModel->mapToSource(index));
if (!sourceIndex.isValid())
return;
(mDataFilesModel->checkState(sourceIndex) == Qt::Checked)
? uncheckAction->setEnabled(true)
: checkAction->setEnabled(true);
}
// Show menu
mContextMenu->exec(globalPos);
}
if (object->objectName() == QLatin1String("MastersTable")) {
if (!mastersTable->selectionModel()->hasSelection())
return;
QPoint globalPos = mastersTable->mapToGlobal(point);
QModelIndexList indexes = mastersTable->selectionModel()->selectedIndexes();
// Show the check/uncheck actions depending on the state of the selected items
uncheckAction->setEnabled(false);
checkAction->setEnabled(false);
foreach (const QModelIndex &index, indexes)
{
if (!index.isValid())
return;
QModelIndex sourceIndex = mMastersProxyModel->mapToSource(index);
if (!sourceIndex.isValid())
return;
(mDataFilesModel->checkState(sourceIndex) == Qt::Checked)
? uncheckAction->setEnabled(true)
: checkAction->setEnabled(true);
}
mContextMenu->exec(globalPos);
}
}

View file

@ -0,0 +1,88 @@
#ifndef DATAFILESPAGE_H
#define DATAFILESPAGE_H
#include <QWidget>
#include <QModelIndex>
#include "ui_datafilespage.h"
class QSortFilterProxyModel;
class QAbstractItemModel;
class QAction;
class QMenu;
class DataFilesModel;
class TextInputDialog;
class GameSettings;
class LauncherSettings;
class PluginsProxyModel;
namespace Files { struct ConfigurationManager; }
class DataFilesPage : public QWidget, private Ui::DataFilesPage
{
Q_OBJECT
public:
DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gameSettings, LauncherSettings &launcherSettings, QWidget *parent = 0);
QAbstractItemModel* profilesComboBoxModel();
int profilesComboBoxIndex();
void writeConfig(QString profile = QString());
void saveSettings();
signals:
void profileChanged(int index);
public slots:
void setCheckState(QModelIndex index);
void setProfilesComboBoxIndex(int index);
void filterChanged(const QString filter);
void showContextMenu(const QPoint &point);
void profileChanged(const QString &previous, const QString &current);
void profileRenamed(const QString &previous, const QString &current);
void updateOkButton(const QString &text);
void updateSplitter();
void updateViews();
// Action slots
void on_newProfileAction_triggered();
void on_deleteProfileAction_triggered();
void on_checkAction_triggered();
void on_uncheckAction_triggered();
private slots:
void slotCurrentIndexChanged(int index);
private:
DataFilesModel *mDataFilesModel;
PluginsProxyModel *mPluginsProxyModel;
QSortFilterProxyModel *mMastersProxyModel;
QSortFilterProxyModel *mFilterProxyModel;
QMenu *mContextMenu;
Files::ConfigurationManager &mCfgMgr;
GameSettings &mGameSettings;
LauncherSettings &mLauncherSettings;
TextInputDialog *mNewProfileDialog;
void setMastersCheckstates(Qt::CheckState state);
void setPluginsCheckstates(Qt::CheckState state);
void createActions();
void setupDataFiles();
void setupConfig();
void readConfig();
void loadSettings();
};
#endif

View file

@ -0,0 +1,385 @@
#include "graphicspage.hpp"
#include <QDesktopWidget>
#include <QMessageBox>
#include <QDir>
#ifdef __APPLE__
// 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
#include <SDL.h>
#include <cstdlib>
#include <boost/math/common_factor.hpp>
#include <components/files/configurationmanager.hpp>
#include <components/files/ogreplugin.hpp>
#include <components/fileorderlist/utils/naturalsort.hpp>
#include "settings/graphicssettings.hpp"
QString getAspect(int x, int y)
{
int gcd = boost::math::gcd (x, y);
int xaspect = x / gcd;
int yaspect = y / gcd;
// special case: 8 : 5 is usually referred to as 16:10
if (xaspect == 8 && yaspect == 5)
return QString("16:10");
return QString(QString::number(xaspect) + ":" + QString::number(yaspect));
}
GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, GraphicsSettings &graphicsSetting, QWidget *parent)
: mCfgMgr(cfg)
, mGraphicsSettings(graphicsSetting)
, QWidget(parent)
{
setupUi(this);
// Set the maximum res we can set in windowed mode
QRect res = getMaximumResolution();
customWidthSpinBox->setMaximum(res.width());
customHeightSpinBox->setMaximum(res.height());
connect(rendererComboBox, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(rendererChanged(const QString&)));
connect(fullScreenCheckBox, SIGNAL(stateChanged(int)), this, SLOT(slotFullScreenChanged(int)));
connect(standardRadioButton, SIGNAL(toggled(bool)), this, SLOT(slotStandardToggled(bool)));
connect(screenComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(screenChanged(int)));
}
bool GraphicsPage::setupOgre()
{
// Create a log manager so we can surpress debug text to stdout/stderr
Ogre::LogManager* logMgr = OGRE_NEW Ogre::LogManager;
logMgr->createLog((mCfgMgr.getLogPath().string() + "/launcherOgre.log"), true, false, false);
try
{
mOgre = new Ogre::Root("", "", "./launcherOgre.log");
}
catch(Ogre::Exception &ex)
{
QString ogreError = QString::fromStdString(ex.getFullDescription().c_str());
QMessageBox msgBox;
msgBox.setWindowTitle("Error creating Ogre::Root");
msgBox.setIcon(QMessageBox::Critical);
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setText(tr("<br><b>Failed to create the Ogre::Root object</b><br><br> \
Press \"Show Details...\" for more information.<br>"));
msgBox.setDetailedText(ogreError);
msgBox.exec();
qCritical("Error creating Ogre::Root, the error reported was:\n %s", qPrintable(ogreError));
return false;
}
std::string pluginDir;
const char* pluginEnv = getenv("OPENMW_OGRE_PLUGIN_DIR");
if (pluginEnv)
pluginDir = pluginEnv;
else
{
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
pluginDir = ".\\";
#endif
#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE
pluginDir = OGRE_PLUGIN_DIR;
#endif
#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX
pluginDir = OGRE_PLUGIN_DIR_REL;
#endif
}
QDir dir(QString::fromStdString(pluginDir));
pluginDir = dir.absolutePath().toStdString();
Files::loadOgrePlugin(pluginDir, "RenderSystem_GL", *mOgre);
Files::loadOgrePlugin(pluginDir, "RenderSystem_GL3Plus", *mOgre);
Files::loadOgrePlugin(pluginDir, "RenderSystem_Direct3D9", *mOgre);
#ifdef ENABLE_PLUGIN_GL
mGLPlugin = new Ogre::GLPlugin();
mOgre->installPlugin(mGLPlugin);
#endif
#ifdef ENABLE_PLUGIN_Direct3D9
mD3D9Plugin = new Ogre::D3D9Plugin();
mOgre->installPlugin(mD3D9Plugin);
#endif
// Get the available renderers and put them in the combobox
const Ogre::RenderSystemList &renderers = mOgre->getAvailableRenderers();
for (Ogre::RenderSystemList::const_iterator r = renderers.begin(); r != renderers.end(); ++r) {
mSelectedRenderSystem = *r;
rendererComboBox->addItem((*r)->getName().c_str());
}
QString openGLName = QString("OpenGL Rendering Subsystem");
QString direct3DName = QString("Direct3D9 Rendering Subsystem");
// Create separate rendersystems
mOpenGLRenderSystem = mOgre->getRenderSystemByName(openGLName.toStdString());
mDirect3DRenderSystem = mOgre->getRenderSystemByName(direct3DName.toStdString());
if (!mOpenGLRenderSystem && !mDirect3DRenderSystem) {
QMessageBox msgBox;
msgBox.setWindowTitle(tr("Error creating renderer"));
msgBox.setIcon(QMessageBox::Critical);
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setText(tr("<br><b>Could not select a valid render system</b><br><br> \
Please make sure the plugins.cfg file exists and contains a valid rendering plugin.<br>"));
msgBox.exec();
return false;
}
// Now fill the GUI elements
int index = rendererComboBox->findText(mGraphicsSettings.value(QString("Video/render system")));
if ( index != -1) {
rendererComboBox->setCurrentIndex(index);
} else {
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
rendererComboBox->setCurrentIndex(rendererComboBox->findText(direct3DName));
#else
rendererComboBox->setCurrentIndex(rendererComboBox->findText(openGLName));
#endif
}
antiAliasingComboBox->clear();
antiAliasingComboBox->addItems(getAvailableOptions(QString("FSAA"), mSelectedRenderSystem));
return true;
}
bool GraphicsPage::setupSDL()
{
int displays = SDL_GetNumVideoDisplays();
if (displays < 0)
{
QMessageBox msgBox;
msgBox.setWindowTitle(tr("Error receiving number of screens"));
msgBox.setIcon(QMessageBox::Critical);
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setText(tr("<br><b>SDL_GetNumDisplayModes failed:</b><br><br>") + QString::fromStdString(SDL_GetError()) + "<br>");
msgBox.exec();
return false;
}
for (int i = 0; i < displays; i++)
{
screenComboBox->addItem(QString(tr("Screen ")) + QString::number(i + 1));
}
return true;
}
bool GraphicsPage::loadSettings()
{
if (!setupSDL())
return false;
if (!setupOgre())
return false;
if (mGraphicsSettings.value(QString("Video/vsync")) == QLatin1String("true"))
vSyncCheckBox->setCheckState(Qt::Checked);
if (mGraphicsSettings.value(QString("Video/fullscreen")) == QLatin1String("true"))
fullScreenCheckBox->setCheckState(Qt::Checked);
int aaIndex = antiAliasingComboBox->findText(mGraphicsSettings.value(QString("Video/antialiasing")));
if (aaIndex != -1)
antiAliasingComboBox->setCurrentIndex(aaIndex);
QString width = mGraphicsSettings.value(QString("Video/resolution x"));
QString height = mGraphicsSettings.value(QString("Video/resolution y"));
QString resolution = width + QString(" x ") + height;
QString screen = mGraphicsSettings.value(QString("Video/screen"));
screenComboBox->setCurrentIndex(screen.toInt());
int resIndex = resolutionComboBox->findText(resolution, Qt::MatchStartsWith);
if (resIndex != -1) {
standardRadioButton->toggle();
resolutionComboBox->setCurrentIndex(resIndex);
} else {
customRadioButton->toggle();
customWidthSpinBox->setValue(width.toInt());
customHeightSpinBox->setValue(height.toInt());
}
return true;
}
void GraphicsPage::saveSettings()
{
vSyncCheckBox->checkState() ? mGraphicsSettings.setValue(QString("Video/vsync"), QString("true"))
: mGraphicsSettings.setValue(QString("Video/vsync"), QString("false"));
fullScreenCheckBox->checkState() ? mGraphicsSettings.setValue(QString("Video/fullscreen"), QString("true"))
: mGraphicsSettings.setValue(QString("Video/fullscreen"), QString("false"));
mGraphicsSettings.setValue(QString("Video/antialiasing"), antiAliasingComboBox->currentText());
mGraphicsSettings.setValue(QString("Video/render system"), rendererComboBox->currentText());
if (standardRadioButton->isChecked()) {
QRegExp resolutionRe(QString("(\\d+) x (\\d+).*"));
if (resolutionRe.exactMatch(resolutionComboBox->currentText().simplified())) {
mGraphicsSettings.setValue(QString("Video/resolution x"), resolutionRe.cap(1));
mGraphicsSettings.setValue(QString("Video/resolution y"), resolutionRe.cap(2));
}
} else {
mGraphicsSettings.setValue(QString("Video/resolution x"), QString::number(customWidthSpinBox->value()));
mGraphicsSettings.setValue(QString("Video/resolution y"), QString::number(customHeightSpinBox->value()));
}
mGraphicsSettings.setValue(QString("Video/screen"), QString::number(screenComboBox->currentIndex()));
}
QStringList GraphicsPage::getAvailableOptions(const QString &key, Ogre::RenderSystem *renderer)
{
QStringList result;
uint row = 0;
Ogre::ConfigOptionMap options = renderer->getConfigOptions();
for (Ogre::ConfigOptionMap::iterator i = options.begin (); i != options.end (); i++, row++)
{
Ogre::StringVector::iterator opt_it;
uint idx = 0;
for (opt_it = i->second.possibleValues.begin();
opt_it != i->second.possibleValues.end(); opt_it++, idx++)
{
if (strcmp (key.toStdString().c_str(), i->first.c_str()) == 0) {
result << ((key == "FSAA") ? QString("MSAA ") : QString("")) + QString::fromStdString((*opt_it).c_str()).simplified();
}
}
}
// Sort ascending
qSort(result.begin(), result.end(), naturalSortLessThanCI);
// Replace the zero option with Off
int index = result.indexOf("MSAA 0");
if (index != -1)
result.replace(index, tr("Off"));
return result;
}
QStringList GraphicsPage::getAvailableResolutions(int screen)
{
QStringList result;
SDL_DisplayMode mode;
int modeIndex, modes = SDL_GetNumDisplayModes(screen);
if (modes < 0)
{
QMessageBox msgBox;
msgBox.setWindowTitle(tr("Error receiving resolutions"));
msgBox.setIcon(QMessageBox::Critical);
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setText(tr("<br><b>SDL_GetNumDisplayModes failed:</b><br><br>") + QString::fromStdString(SDL_GetError()) + "<br>");
msgBox.exec();
return result;
}
for (modeIndex = 0; modeIndex < modes; modeIndex++)
{
if (SDL_GetDisplayMode(screen, modeIndex, &mode) < 0)
{
QMessageBox msgBox;
msgBox.setWindowTitle(tr("Error receiving resolutions"));
msgBox.setIcon(QMessageBox::Critical);
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setText(tr("<br><b>SDL_GetDisplayMode failed:</b><br><br>") + QString::fromStdString(SDL_GetError()) + "<br>");
msgBox.exec();
return result;
}
QString aspect = getAspect(mode.w, mode.h);
QString resolution = QString::number(mode.w) + QString(" x ") + QString::number(mode.h);
if (aspect == QLatin1String("16:9") || aspect == QLatin1String("16:10")) {
resolution.append(tr("\t(Wide ") + aspect + ")");
} else if (aspect == QLatin1String("4:3")) {
resolution.append(tr("\t(Standard 4:3)"));
}
result.append(resolution);
}
result.removeDuplicates();
return result;
}
QRect GraphicsPage::getMaximumResolution()
{
QRect max;
int screens = QApplication::desktop()->screenCount();
for (int i = 0; i < screens; ++i)
{
QRect res = QApplication::desktop()->screenGeometry(i);
if (res.width() > max.width())
max.setWidth(res.width());
if (res.height() > max.height())
max.setHeight(res.height());
}
return max;
}
void GraphicsPage::rendererChanged(const QString &renderer)
{
mSelectedRenderSystem = mOgre->getRenderSystemByName(renderer.toStdString());
antiAliasingComboBox->clear();
antiAliasingComboBox->addItems(getAvailableOptions(QString("FSAA"), mSelectedRenderSystem));
}
void GraphicsPage::screenChanged(int screen)
{
if (screen >= 0) {
resolutionComboBox->clear();
resolutionComboBox->addItems(getAvailableResolutions(screen));
}
}
void GraphicsPage::slotFullScreenChanged(int state)
{
if (state == Qt::Checked) {
standardRadioButton->toggle();
customRadioButton->setEnabled(false);
customWidthSpinBox->setEnabled(false);
customHeightSpinBox->setEnabled(false);
} else {
customRadioButton->setEnabled(true);
customWidthSpinBox->setEnabled(true);
customHeightSpinBox->setEnabled(true);
}
}
void GraphicsPage::slotStandardToggled(bool checked)
{
if (checked) {
resolutionComboBox->setEnabled(true);
customWidthSpinBox->setEnabled(false);
customHeightSpinBox->setEnabled(false);
} else {
resolutionComboBox->setEnabled(false);
customWidthSpinBox->setEnabled(true);
customHeightSpinBox->setEnabled(true);
}
}

View file

@ -0,0 +1,66 @@
#ifndef GRAPHICSPAGE_H
#define GRAPHICSPAGE_H
#include <QWidget>
#include <OgreRoot.h>
#include <OgreRenderSystem.h>
//#include <OgreConfigFile.h>
//#include <OgreConfigDialog.h>
// Static plugin headers
#ifdef ENABLE_PLUGIN_GL
# include "OgreGLPlugin.h"
#endif
#ifdef ENABLE_PLUGIN_Direct3D9
# include "OgreD3D9Plugin.h"
#endif
#include "ui_graphicspage.h"
class GraphicsSettings;
namespace Files { struct ConfigurationManager; }
class GraphicsPage : public QWidget, private Ui::GraphicsPage
{
Q_OBJECT
public:
GraphicsPage(Files::ConfigurationManager &cfg, GraphicsSettings &graphicsSettings, QWidget *parent = 0);
void saveSettings();
bool loadSettings();
public slots:
void rendererChanged(const QString &renderer);
void screenChanged(int screen);
private slots:
void slotFullScreenChanged(int state);
void slotStandardToggled(bool checked);
private:
Ogre::Root *mOgre;
Ogre::RenderSystem *mSelectedRenderSystem;
Ogre::RenderSystem *mOpenGLRenderSystem;
Ogre::RenderSystem *mDirect3DRenderSystem;
#ifdef ENABLE_PLUGIN_GL
Ogre::GLPlugin* mGLPlugin;
#endif
#ifdef ENABLE_PLUGIN_Direct3D9
Ogre::D3D9Plugin* mD3D9Plugin;
#endif
Files::ConfigurationManager &mCfgMgr;
GraphicsSettings &mGraphicsSettings;
QStringList getAvailableOptions(const QString &key, Ogre::RenderSystem *renderer);
QStringList getAvailableResolutions(int screen);
QRect getMaximumResolution();
bool setupOgre();
bool setupSDL();
};
#endif

64
apps/launcher/main.cpp Normal file
View file

@ -0,0 +1,64 @@
#include <QApplication>
#include <QTextCodec>
#include <QDir>
#include <QDebug>
#ifdef __APPLE__
// 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
#include <SDL.h>
#include "maindialog.hpp"
// SDL workaround
#include "graphicspage.hpp"
int main(int argc, char *argv[])
{
SDL_SetHint(SDL_HINT_RENDER_DRIVER, "software");
if (SDL_Init(SDL_INIT_VIDEO) != 0)
{
qDebug() << "SDL_Init failed: " << QString::fromStdString(SDL_GetError());
return 0;
}
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"));
MainDialog mainWin;
if (mainWin.setup()) {
mainWin.show();
} else {
return 0;
}
int returnValue = app.exec();
SDL_Quit();
return returnValue;
}

View file

@ -0,0 +1,854 @@
#include "maindialog.hpp"
#include <QFontDatabase>
#include <QInputDialog>
#include <QFileDialog>
#include <QCloseEvent>
#include <QTextCodec>
#include <QProcess>
#include <QFile>
#include <QDir>
#include <QDebug>
#ifndef WIN32
#include "unshieldthread.hpp"
#endif
#include "textslotmsgbox.hpp"
#include "utils/checkablemessagebox.hpp"
#include "playpage.hpp"
#include "graphicspage.hpp"
#include "datafilespage.hpp"
MainDialog::MainDialog()
: mGameSettings(mCfgMgr)
{
// Install the stylesheet font
QFile file;
QFontDatabase fontDatabase;
const QStringList fonts = fontDatabase.families();
// Check if the font is installed
if (!fonts.contains("EB Garamond")) {
QString font = QString::fromStdString(mCfgMgr.getGlobalDataPath().string()) + QString("resources/mygui/EBGaramond-Regular.ttf");
file.setFileName(font);
if (!file.exists()) {
font = QString::fromStdString(mCfgMgr.getLocalPath().string()) + QString("resources/mygui/EBGaramond-Regular.ttf");
}
fontDatabase.addApplicationFont(font);
}
setupUi(this);
iconWidget->setViewMode(QListView::IconMode);
iconWidget->setWrapping(false);
iconWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // Just to be sure
iconWidget->setIconSize(QSize(48, 48));
iconWidget->setMovement(QListView::Static);
iconWidget->setSpacing(4);
iconWidget->setCurrentRow(0);
iconWidget->setFlow(QListView::LeftToRight);
QPushButton *playButton = new QPushButton(tr("Play"));
buttonBox->addButton(playButton, QDialogButtonBox::AcceptRole);
connect(buttonBox, SIGNAL(rejected()), this, SLOT(close()));
connect(buttonBox, SIGNAL(accepted()), this, SLOT(play()));
// Remove what's this? button
setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint);
createIcons();
}
void 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);
connect(iconWidget,
SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)),
this, SLOT(changePage(QListWidgetItem*,QListWidgetItem*)));
}
void MainDialog::createPages()
{
mPlayPage = new PlayPage(this);
mGraphicsPage = new GraphicsPage(mCfgMgr, mGraphicsSettings, this);
mDataFilesPage = new DataFilesPage(mCfgMgr, mGameSettings, mLauncherSettings, this);
// Set the combobox of the play page to imitate the combobox on the datafilespage
mPlayPage->setProfilesComboBoxModel(mDataFilesPage->profilesComboBoxModel());
mPlayPage->setProfilesComboBoxIndex(mDataFilesPage->profilesComboBoxIndex());
// Add the pages to the stacked widget
pagesWidget->addWidget(mPlayPage);
pagesWidget->addWidget(mGraphicsPage);
pagesWidget->addWidget(mDataFilesPage);
// Select the first page
iconWidget->setCurrentItem(iconWidget->item(0), QItemSelectionModel::Select);
connect(mPlayPage, SIGNAL(playButtonClicked()), this, SLOT(play()));
connect(mPlayPage, SIGNAL(profileChanged(int)), mDataFilesPage, SLOT(setProfilesComboBoxIndex(int)));
connect(mDataFilesPage, SIGNAL(profileChanged(int)), mPlayPage, SLOT(setProfilesComboBoxIndex(int)));
}
bool MainDialog::showFirstRunDialog()
{
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")));
}
}
// Ask the user where the Morrowind.ini is
if (iniPaths.empty()) {
QMessageBox msgBox;
msgBox.setWindowTitle(tr("Error detecting Morrowind configuration"));
msgBox.setIcon(QMessageBox::Warning);
msgBox.setStandardButtons(QMessageBox::Cancel);
msgBox.setText(QObject::tr("<br><b>Could not find Morrowind.ini</b><br><br> \
OpenMW needs to import settings from this file.<br><br> \
Press \"Browse...\" to specify the location manually.<br>"));
QAbstractButton *dirSelectButton =
msgBox.addButton(QObject::tr("B&rowse..."), QMessageBox::ActionRole);
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("<br><b>An existing Morrowind configuration was detected</b><br> \
<br>Would you like to import settings from Morrowind.ini?<br> \
<br><b>Warning: In most cases OpenMW needs these settings to run properly</b><br>"));
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("<br><b>There are multiple Morrowind.ini files found.</b><br><br> \
Please select the one you wish to import from:"), iniPaths, 0, false, &ok);
if (ok && !path.isEmpty()) {
iniPaths.clear();
iniPaths.append(path);
} else {
// Cancel was clicked
return false;
}
}
// Create the file if it doesn't already exist, else the importer will fail
QString path = QString::fromStdString(mCfgMgr.getUserPath().string()) + 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("<br><b>Could not open or create %0 for writing</b><br><br> \
Please make sure you have the right permissions \
and try again.<br>").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/master"));
mLauncherSettings.remove(QString("Profiles/Imported/plugin"));
QStringList masters = mGameSettings.values(QString("master"));
QStringList plugins = mGameSettings.values(QString("plugin"));
foreach (const QString &master, masters) {
mLauncherSettings.setMultiValue(QString("Profiles/Imported/master"), master);
}
foreach (const QString &plugin, plugins) {
mLauncherSettings.setMultiValue(QString("Profiles/Imported/plugin"), plugin);
}
}
}
return true;
}
bool MainDialog::setup()
{
if (!setupLauncherSettings())
return false;
if (!setupGameSettings())
return false;
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;
}
// 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;
}
void MainDialog::changePage(QListWidgetItem *current, QListWidgetItem *previous)
{
if (!current)
current = previous;
pagesWidget->setCurrentIndex(iconWidget->row(current));
}
bool MainDialog::setupLauncherSettings()
{
mLauncherSettings.setMultiValueEnabled(true);
QString userPath = QString::fromStdString(mCfgMgr.getUserPath().string());
QStringList paths;
paths.append(QString("launcher.cfg"));
paths.append(userPath + QString("launcher.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(QObject::tr("<br><b>Could not open %0 for reading</b><br><br> \
Please make sure you have the right permissions \
and try again.<br>").arg(file.fileName()));
msgBox.exec();
return false;
}
QTextStream stream(&file);
stream.setCodec(QTextCodec::codecForName("UTF-8"));
mLauncherSettings.readFile(stream);
}
file.close();
}
return true;
}
#ifndef WIN32
bool expansions(UnshieldThread& cd)
{
if(cd.BloodmoonDone())
{
cd.Done();
return false;
}
QMessageBox expansionsBox;
expansionsBox.setText(QObject::tr("<br>Would you like to install expansions now ? (make sure you have the disc)<br> \
If you want to install both Bloodmoon and Tribunal, you have to install Tribunal first.<br>"));
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 MainDialog::setupGameSettings()
{
QString userPath = QString::fromStdString(mCfgMgr.getUserPath().string());
QString globalPath = QString::fromStdString(mCfgMgr.getGlobalPath().string());
QStringList paths;
paths.append(userPath + QString("openmw.cfg"));
paths.append(QString("openmw.cfg"));
paths.append(globalPath + QString("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(QObject::tr("<br><b>Could not open %0 for reading</b><br><br> \
Please make sure you have the right permissions \
and try again.<br>").arg(file.fileName()));
msgBox.exec();
return false;
}
QTextStream stream(&file);
stream.setCodec(QTextCodec::codecForName("UTF-8"));
mGameSettings.readFile(stream);
}
file.close();
}
QStringList dataDirs;
// Check if the paths actually contain data files
foreach (const QString path, mGameSettings.getDataDirs()) {
QDir dir(path);
QStringList filters;
filters << "*.esp" << "*.esm";
if (!dir.entryList(filters).isEmpty())
dataDirs.append(path);
}
if (dataDirs.isEmpty())
{
QMessageBox msgBox;
msgBox.setWindowTitle(tr("Error detecting Morrowind installation"));
msgBox.setIcon(QMessageBox::Warning);
msgBox.setStandardButtons(QMessageBox::Cancel);
msgBox.setText(QObject::tr("<br><b>Could not find the Data Files location</b><br><br> \
The directory containing the data files was not found.<br><br> \
Press \"Browse...\" to specify the location manually.<br>"));
QAbstractButton *dirSelectButton =
msgBox.addButton(QObject::tr("Browse to &Install..."), QMessageBox::ActionRole);
#ifndef WIN32
QAbstractButton *cdSelectButton =
msgBox.addButton(QObject::tr("Browse to &CD..."), QMessageBox::ActionRole);
#endif
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();
}
while(expansions(cd));
selectedFile = QString::fromStdString(cd.GetMWEsmPath());
}
#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;
}
bool MainDialog::setupGraphicsSettings()
{
mGraphicsSettings.setMultiValueEnabled(false);
QString userPath = QString::fromStdString(mCfgMgr.getUserPath().string());
QString globalPath = QString::fromStdString(mCfgMgr.getGlobalPath().string());
QFile localDefault(QString("settings-default.cfg"));
QFile globalDefault(globalPath + QString("settings-default.cfg"));
if (!localDefault.exists() && !globalDefault.exists()) {
QMessageBox msgBox;
msgBox.setWindowTitle(tr("Error reading OpenMW configuration file"));
msgBox.setIcon(QMessageBox::Critical);
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setText(QObject::tr("<br><b>Could not find settings-default.cfg</b><br><br> \
The problem may be due to an incomplete installation of OpenMW.<br> \
Reinstalling OpenMW may resolve the problem."));
msgBox.exec();
return false;
}
QStringList paths;
paths.append(globalPath + QString("settings-default.cfg"));
paths.append(QString("settings-default.cfg"));
paths.append(userPath + QString("settings.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(QObject::tr("<br><b>Could not open %0 for reading</b><br><br> \
Please make sure you have the right permissions \
and try again.<br>").arg(file.fileName()));
msgBox.exec();
return false;
}
QTextStream stream(&file);
stream.setCodec(QTextCodec::codecForName("UTF-8"));
mGraphicsSettings.readFile(stream);
}
file.close();
}
return true;
}
void MainDialog::loadSettings()
{
int width = mLauncherSettings.value(QString("General/MainWindow/width")).toInt();
int height = mLauncherSettings.value(QString("General/MainWindow/height")).toInt();
int posX = mLauncherSettings.value(QString("General/MainWindow/posx")).toInt();
int posY = mLauncherSettings.value(QString("General/MainWindow/posy")).toInt();
resize(width, height);
move(posX, posY);
}
void MainDialog::saveSettings()
{
QString width = QString::number(this->width());
QString height = QString::number(this->height());
mLauncherSettings.setValue(QString("General/MainWindow/width"), width);
mLauncherSettings.setValue(QString("General/MainWindow/height"), height);
QString posX = QString::number(this->pos().x());
QString posY = QString::number(this->pos().y());
mLauncherSettings.setValue(QString("General/MainWindow/posx"), posX);
mLauncherSettings.setValue(QString("General/MainWindow/posy"), posY);
mLauncherSettings.setValue(QString("General/firstrun"), QString("false"));
}
bool MainDialog::writeSettings()
{
// Now write all config files
saveSettings();
mGraphicsPage->saveSettings();
mDataFilesPage->saveSettings();
QString userPath = QString::fromStdString(mCfgMgr.getUserPath().string());
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("<br><b>Could not create %0</b><br><br> \
Please make sure you have the right permissions \
and try again.<br>").arg(userPath));
msgBox.exec();
return false;
}
}
// Game settings
QFile file(userPath + QString("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("<br><b>Could not open or create %0 for writing</b><br><br> \
Please make sure you have the right permissions \
and try again.<br>").arg(file.fileName()));
msgBox.exec();
return false;
}
QTextStream stream(&file);
stream.setCodec(QTextCodec::codecForName("UTF-8"));
mGameSettings.writeFile(stream);
file.close();
// Graphics settings
file.setFileName(userPath + QString("settings.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("<br><b>Could not open or create %0 for writing</b><br><br> \
Please make sure you have the right permissions \
and try again.<br>").arg(file.fileName()));
msgBox.exec();
return false;
}
stream.setDevice(&file);
stream.setCodec(QTextCodec::codecForName("UTF-8"));
mGraphicsSettings.writeFile(stream);
file.close();
// Launcher settings
file.setFileName(userPath + QString("launcher.cfg"));
if (!file.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate)) {
// File cannot be opened or created
QMessageBox msgBox;
msgBox.setWindowTitle(tr("Error writing Launcher configuration file"));
msgBox.setIcon(QMessageBox::Critical);
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setText(tr("<br><b>Could not open or create %0 for writing</b><br><br> \
Please make sure you have the right permissions \
and try again.<br>").arg(file.fileName()));
msgBox.exec();
return false;
}
stream.setDevice(&file);
stream.setCodec(QTextCodec::codecForName("UTF-8"));
mLauncherSettings.writeFile(stream);
file.close();
return true;
}
void MainDialog::closeEvent(QCloseEvent *event)
{
writeSettings();
event->accept();
}
void MainDialog::play()
{
if (!writeSettings()) {
qApp->quit();
return;
}
if(!mGameSettings.hasMaster()) {
QMessageBox msgBox;
msgBox.setWindowTitle(tr("No master file selected"));
msgBox.setIcon(QMessageBox::Warning);
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setText(tr("<br><b>You do not have any master files selected.</b><br><br> \
OpenMW will not start without a master file selected.<br>"));
msgBox.exec();
return;
}
// Launch the game detached
startProgram(QString("openmw"), true);
qApp->quit();
}
bool 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("<br><b>Could not find %1</b><br><br> \
The application is not found.<br> \
Please make sure OpenMW is installed correctly and try again.<br>").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("<br><b>Could not start %1</b><br><br> \
The application is not executable.<br> \
Please make sure you have the right permissions and try again.<br>").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("<br><b>Could not start %1</b><br><br> \
An error occurred while starting %1.<br><br> \
Press \"Show Details...\" for more information.<br>").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("<br><b>Could not start %1</b><br><br> \
An error occurred while starting %1.<br><br> \
Press \"Show Details...\" for more information.<br>").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("<br><b>Executable %1 returned an error</b><br><br> \
An error occurred while running %1.<br><br> \
Press \"Show Details...\" for more information.<br>").arg(info.fileName()));
msgBox.setDetailedText(error);
msgBox.exec();
return false;
}
}
return true;
}

View file

@ -0,0 +1,67 @@
#ifndef MAINDIALOG_H
#define MAINDIALOG_H
#include <QMainWindow>
#ifndef Q_MOC_RUN
#include <components/files/configurationmanager.hpp>
#endif
#include "settings/gamesettings.hpp"
#include "settings/graphicssettings.hpp"
#include "settings/launchersettings.hpp"
#include "ui_mainwindow.h"
class QListWidget;
class QListWidgetItem;
class QStackedWidget;
class QStringList;
class QStringListModel;
class QString;
class PlayPage;
class GraphicsPage;
class DataFilesPage;
class MainDialog : public QMainWindow, private Ui::MainWindow
{
Q_OBJECT
public:
MainDialog();
bool setup();
bool showFirstRunDialog();
public slots:
void changePage(QListWidgetItem *current, QListWidgetItem *previous);
void play();
private:
void createIcons();
void createPages();
bool setupLauncherSettings();
bool setupGameSettings();
bool setupGraphicsSettings();
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);
void closeEvent(QCloseEvent *event);
PlayPage *mPlayPage;
GraphicsPage *mGraphicsPage;
DataFilesPage *mDataFilesPage;
Files::ConfigurationManager mCfgMgr;
GameSettings mGameSettings;
GraphicsSettings mGraphicsSettings;
LauncherSettings mLauncherSettings;
};
#endif

View file

@ -0,0 +1,43 @@
#include "playpage.hpp"
#include <QListView>
#ifdef Q_OS_MAC
#include <QPlastiqueStyle>
#endif
PlayPage::PlayPage(QWidget *parent) : QWidget(parent)
{
setupUi(this);
// Hacks to get the stylesheet look properly
#ifdef Q_OS_MAC
QPlastiqueStyle *style = new QPlastiqueStyle;
profilesComboBox->setStyle(style);
#endif
profilesComboBox->setView(new QListView());
connect(profilesComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentIndexChanged(int)));
connect(playButton, SIGNAL(clicked()), this, SLOT(slotPlayClicked()));
}
void PlayPage::setProfilesComboBoxModel(QAbstractItemModel *model)
{
profilesComboBox->setModel(model);
}
void PlayPage::setProfilesComboBoxIndex(int index)
{
profilesComboBox->setCurrentIndex(index);
}
void PlayPage::slotCurrentIndexChanged(int index)
{
emit profileChanged(index);
}
void PlayPage::slotPlayClicked()
{
emit playButtonClicked();
}

View file

@ -0,0 +1,35 @@
#ifndef PLAYPAGE_H
#define PLAYPAGE_H
#include <QWidget>
#include "ui_playpage.h"
class QComboBox;
class QPushButton;
class QAbstractItemModel;
class PlayPage : public QWidget, private Ui::PlayPage
{
Q_OBJECT
public:
PlayPage(QWidget *parent = 0);
void setProfilesComboBoxModel(QAbstractItemModel *model);
signals:
void profileChanged(int index);
void playButtonClicked();
public slots:
void setProfilesComboBoxIndex(int index);
private slots:
void slotCurrentIndexChanged(int index);
void slotPlayClicked();
};
#endif

View file

@ -0,0 +1,175 @@
#include "gamesettings.hpp"
#include <QTextStream>
#include <QDir>
#include <QString>
#include <QRegExp>
#include <QMap>
#include <components/files/configurationmanager.hpp>
#include <boost/version.hpp>
/**
* Workaround for problems with whitespaces in paths in older versions of Boost library
*/
#if (BOOST_VERSION <= 104600)
namespace boost
{
template<>
inline boost::filesystem::path lexical_cast<boost::filesystem::path, std::string>(const std::string& arg)
{
return boost::filesystem::path(arg);
}
} /* namespace boost */
#endif /* (BOOST_VERSION <= 104600) */
GameSettings::GameSettings(Files::ConfigurationManager &cfg)
: mCfgMgr(cfg)
{
}
GameSettings::~GameSettings()
{
}
void 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;
foreach (const QString &path, paths) {
dataDirs.push_back(Files::PathContainer::value_type(path.toStdString()));
}
// Parse the data dirs to convert the tokenized paths
mCfgMgr.processPaths(dataDirs);
mDataDirs.clear();
for (Files::PathContainer::iterator it = dataDirs.begin(); it != dataDirs.end(); ++it) {
QString path = QString::fromStdString(it->string());
path.remove(QChar('\"'));
QDir dir(path);
if (dir.exists())
mDataDirs.append(path);
}
// Do the same for data-local
QString local = mSettings.value(QString("data-local"));
if (local.isEmpty())
return;
dataDirs.clear();
dataDirs.push_back(Files::PathContainer::value_type(local.toStdString()));
mCfgMgr.processPaths(dataDirs);
if (!dataDirs.empty()) {
QString path = QString::fromStdString(dataDirs.front().string());
path.remove(QChar('\"'));
QDir dir(path);
if (dir.exists())
mDataLocal = path;
}
}
QStringList GameSettings::values(const QString &key, const QStringList &defaultValues)
{
if (!mSettings.values(key).isEmpty())
return mSettings.values(key);
return defaultValues;
}
bool GameSettings::readFile(QTextStream &stream)
{
QMap<QString, QString> cache;
QRegExp keyRe("^([^=]+)\\s*=\\s*(.+)$");
while (!stream.atEnd()) {
QString line = stream.readLine();
if (line.isEmpty() || line.startsWith("#"))
continue;
if (keyRe.indexIn(line) != -1) {
QString key = keyRe.cap(1).trimmed();
QString value = keyRe.cap(2).trimmed();
// Don't remove existing data entries
if (key != QLatin1String("data"))
mSettings.remove(key);
QStringList values = cache.values(key);
values.append(mSettings.values(key));
if (!values.contains(value)) {
cache.insertMulti(key, value);
}
}
}
if (mSettings.isEmpty()) {
mSettings = cache; // This is the first time we read a file
validatePaths();
return true;
}
// Merge the changed keys with those which didn't
mSettings.unite(cache);
validatePaths();
return true;
}
bool GameSettings::writeFile(QTextStream &stream)
{
// Iterate in reverse order to preserve insertion order
QMapIterator<QString, QString> i(mSettings);
i.toBack();
while (i.hasPrevious()) {
i.previous();
if (i.key() == QLatin1String("master") || i.key() == QLatin1String("plugin"))
continue;
// Quote paths with spaces
if (i.key() == QLatin1String("data")
|| i.key() == QLatin1String("data-local")
|| i.key() == QLatin1String("resources"))
{
if (i.value().contains(QChar(' ')))
{
QString stripped = i.value();
stripped.remove(QChar('\"')); // Remove quotes
stream << i.key() << "=\"" << stripped << "\"\n";
continue;
}
}
stream << i.key() << "=" << i.value() << "\n";
}
QStringList masters = mSettings.values(QString("master"));
for (int i = masters.count(); i--;) {
stream << "master=" << masters.at(i) << "\n";
}
QStringList plugins = mSettings.values(QString("plugin"));
for (int i = plugins.count(); i--;) {
stream << "plugin=" << plugins.at(i) << "\n";
}
return true;
}

View file

@ -0,0 +1,62 @@
#ifndef GAMESETTINGS_HPP
#define GAMESETTINGS_HPP
#include <QTextStream>
#include <QStringList>
#include <QString>
#include <QMap>
#include <boost/filesystem/path.hpp>
namespace Files { typedef std::vector<boost::filesystem::path> PathContainer;
struct ConfigurationManager;}
class GameSettings
{
public:
GameSettings(Files::ConfigurationManager &cfg);
~GameSettings();
inline QString value(const QString &key, const QString &defaultValue = QString())
{
return mSettings.value(key).isEmpty() ? defaultValue : mSettings.value(key);
}
inline void setValue(const QString &key, const QString &value)
{
mSettings.insert(key, value);
}
inline void setMultiValue(const QString &key, const QString &value)
{
QStringList values = mSettings.values(key);
if (!values.contains(value))
mSettings.insertMulti(key, value);
}
inline void remove(const QString &key)
{
mSettings.remove(key);
}
inline QStringList getDataDirs() { return mDataDirs; }
inline void addDataDir(const QString &dir) { if(!dir.isEmpty()) mDataDirs.append(dir); }
inline QString getDataLocal() {return mDataLocal; }
inline bool hasMaster() { return mSettings.count(QString("master")) > 0; }
QStringList values(const QString &key, const QStringList &defaultValues = QStringList());
bool readFile(QTextStream &stream);
bool writeFile(QTextStream &stream);
private:
Files::ConfigurationManager &mCfgMgr;
void validatePaths();
QMap<QString, QString> mSettings;
QStringList mDataDirs;
QString mDataLocal;
};
#endif // GAMESETTINGS_HPP

View file

@ -0,0 +1,44 @@
#include "graphicssettings.hpp"
#include <QTextStream>
#include <QString>
#include <QRegExp>
#include <QMap>
GraphicsSettings::GraphicsSettings()
{
}
GraphicsSettings::~GraphicsSettings()
{
}
bool GraphicsSettings::writeFile(QTextStream &stream)
{
QString sectionPrefix;
QRegExp sectionRe("([^/]+)/(.+)$");
QMap<QString, QString> settings = SettingsBase::getSettings();
QMapIterator<QString, QString> i(settings);
while (i.hasNext()) {
i.next();
QString prefix;
QString key;
if (sectionRe.exactMatch(i.key())) {
prefix = sectionRe.cap(1);
key = sectionRe.cap(2);
}
if (sectionPrefix != prefix) {
sectionPrefix = prefix;
stream << "\n[" << prefix << "]\n";
}
stream << key << " = " << i.value() << "\n";
}
return true;
}

View file

@ -0,0 +1,16 @@
#ifndef GRAPHICSSETTINGS_HPP
#define GRAPHICSSETTINGS_HPP
#include "settingsbase.hpp"
class GraphicsSettings : public SettingsBase<QMap<QString, QString> >
{
public:
GraphicsSettings();
~GraphicsSettings();
bool writeFile(QTextStream &stream);
};
#endif // GRAPHICSSETTINGS_HPP

View file

@ -0,0 +1,101 @@
#include "launchersettings.hpp"
#include <QTextStream>
#include <QString>
#include <QRegExp>
#include <QMap>
LauncherSettings::LauncherSettings()
{
}
LauncherSettings::~LauncherSettings()
{
}
QStringList LauncherSettings::values(const QString &key, Qt::MatchFlags flags)
{
QMap<QString, QString> settings = SettingsBase::getSettings();
if (flags == Qt::MatchExactly)
return settings.values(key);
QStringList result;
if (flags == Qt::MatchStartsWith) {
QStringList keys = settings.keys();
foreach (const QString &currentKey, keys) {
if (currentKey.startsWith(key))
result.append(settings.value(currentKey));
}
}
return result;
}
QStringList LauncherSettings::subKeys(const QString &key)
{
QMap<QString, QString> settings = SettingsBase::getSettings();
QStringList keys = settings.uniqueKeys();
QRegExp keyRe("(.+)/");
QStringList result;
foreach (const QString &currentKey, keys) {
if (keyRe.indexIn(currentKey) != -1) {
QString prefixedKey = keyRe.cap(1);
if(prefixedKey.startsWith(key)) {
QString subKey = prefixedKey.remove(key);
if (!subKey.isEmpty())
result.append(subKey);
}
}
}
result.removeDuplicates();
return result;
}
bool LauncherSettings::writeFile(QTextStream &stream)
{
QString sectionPrefix;
QRegExp sectionRe("([^/]+)/(.+)$");
QMap<QString, QString> settings = SettingsBase::getSettings();
QMapIterator<QString, QString> i(settings);
i.toBack();
while (i.hasPrevious()) {
i.previous();
QString prefix;
QString key;
if (sectionRe.exactMatch(i.key())) {
prefix = sectionRe.cap(1);
key = sectionRe.cap(2);
}
// Get rid of legacy settings
if (key.contains(QChar('\\')))
continue;
if (key == QLatin1String("CurrentProfile"))
continue;
if (sectionPrefix != prefix) {
sectionPrefix = prefix;
stream << "\n[" << prefix << "]\n";
}
stream << key << "=" << i.value() << "\n";
}
return true;
}

View file

@ -0,0 +1,19 @@
#ifndef LAUNCHERSETTINGS_HPP
#define LAUNCHERSETTINGS_HPP
#include "settingsbase.hpp"
class LauncherSettings : public SettingsBase<QMap<QString, QString> >
{
public:
LauncherSettings();
~LauncherSettings();
QStringList subKeys(const QString &key);
QStringList values(const QString &key, Qt::MatchFlags flags = Qt::MatchExactly);
bool writeFile(QTextStream &stream);
};
#endif // LAUNCHERSETTINGS_HPP

View file

@ -0,0 +1,109 @@
#ifndef SETTINGSBASE_HPP
#define SETTINGSBASE_HPP
#include <QTextStream>
#include <QStringList>
#include <QString>
#include <QRegExp>
#include <QMap>
template <class Map>
class SettingsBase
{
public:
SettingsBase() { mMultiValue = false; }
~SettingsBase() {}
inline QString value(const QString &key, const QString &defaultValue = QString())
{
return mSettings.value(key).isEmpty() ? defaultValue : mSettings.value(key);
}
inline void setValue(const QString &key, const QString &value)
{
QStringList values = mSettings.values(key);
if (!values.contains(value))
mSettings.insert(key, value);
}
inline void setMultiValue(const QString &key, const QString &value)
{
QStringList values = mSettings.values(key);
if (!values.contains(value))
mSettings.insertMulti(key, value);
}
inline void setMultiValueEnabled(bool enable)
{
mMultiValue = enable;
}
inline void remove(const QString &key)
{
mSettings.remove(key);
}
Map getSettings() {return mSettings;}
bool readFile(QTextStream &stream)
{
mCache.clear();
QString sectionPrefix;
QRegExp sectionRe("^\\[([^]]+)\\]");
QRegExp keyRe("^([^=]+)\\s*=\\s*(.+)$");
while (!stream.atEnd()) {
QString line = stream.readLine();
if (line.isEmpty() || line.startsWith("#"))
continue;
if (sectionRe.exactMatch(line)) {
sectionPrefix = sectionRe.cap(1);
sectionPrefix.append("/");
continue;
}
if (keyRe.indexIn(line) != -1) {
QString key = keyRe.cap(1).trimmed();
QString value = keyRe.cap(2).trimmed();
if (!sectionPrefix.isEmpty())
key.prepend(sectionPrefix);
mSettings.remove(key);
QStringList values = mCache.values(key);
if (!values.contains(value)) {
if (mMultiValue) {
mCache.insertMulti(key, value);
} else {
mCache.insert(key, value);
}
}
}
}
if (mSettings.isEmpty()) {
mSettings = mCache; // This is the first time we read a file
return true;
}
// Merge the changed keys with those which didn't
mSettings.unite(mCache);
return true;
}
private:
Map mSettings;
Map mCache;
bool mMultiValue;
};
#endif // SETTINGSBASE_HPP

View file

@ -0,0 +1,6 @@
#include "textslotmsgbox.hpp"
void TextSlotMsgBox::setTextSlot(const QString& string)
{
setText(string);
}

View file

@ -0,0 +1,13 @@
#ifndef TEXT_SLOT_MSG_BOX
#define TEXT_SLOT_MSG_BOX
#include <QMessageBox>
class TextSlotMsgBox : public QMessageBox
{
Q_OBJECT
public slots:
void setTextSlot(const QString& string);
};
#endif

View file

@ -0,0 +1,487 @@
#include "unshieldthread.hpp"
#include <fstream>
#include <components/misc/stringops.hpp>
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)
{
std::ifstream strstream(path.c_str(), 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");
}
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;
std::string setting;
category = "General";
{
setting = "Werewolf FOV"; add_setting(category, setting, get_setting(category, setting, inx), ini);
}
category = "Moons";
{
setting = "Script Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
}
category = "Weather";
{
setting = "Snow Ripples"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Snow Ripple Radius"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Snow Ripples Per Flake"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Snow Ripple Scale"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Snow Ripple Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Snow Gravity Scale"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Snow High Kill"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Snow Low Kill"; add_setting(category, setting, get_setting(category, setting, inx), ini);
}
category = "Weather Blight";
{
setting = "Ambient Loop Sound ID"; add_setting(category, setting, get_setting(category, setting, inx), ini);
}
category = "Weather Snow";
{
setting = "Sky Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Sky Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Sky Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Sky Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Fog Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Fog Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Fog Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Fog Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Ambient Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Ambient Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Ambient Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Ambient Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Sun Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Sun Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Sun Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Sun Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Sun Disc Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Transition Delta"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Land Fog Day Depth"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Land Fog Night Depth"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Clouds Maximum Percent"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Wind Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Cloud Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Glare View"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Cloud Texture"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Ambient Loop Sound ID"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Snow Threshold"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Snow Diameter"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Snow Height Min"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Snow Height Max"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Snow Entrance Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Max Snowflakes"; add_setting(category, setting, get_setting(category, setting, inx), ini);
}
category = "Weather Blizzard";
{
setting = "Sky Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Sky Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Sky Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Sky Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Fog Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Fog Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Fog Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Fog Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Ambient Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Ambient Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Ambient Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Ambient Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Sun Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Sun Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Sun Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Sun Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Sun Disc Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Transition Delta"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Land Fog Day Depth"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Land Fog Night Depth"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Clouds Maximum Percent"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Wind Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Cloud Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Glare View"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Cloud Texture"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Ambient Loop Sound ID"; add_setting(category, setting, get_setting(category, setting, inx), ini);
setting = "Storm Threshold"; add_setting(category, setting, get_setting(category, setting, inx), ini);
}
}
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);
}
std::ofstream inistream(ini_path.c_str());
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::copy_file(dir->path(), to / dir->path().filename());
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::lowerCase(dir->path().filename().string()) == filename)
return dir->path();
}
}
else
{
for ( bfs::directory_iterator end, dir(in); dir != end; ++dir )
{
if(Misc::StringUtils::lowerCase(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::lowerCase(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);
}
}
bool UnshieldThread::SetMorrowindPath(const std::string& path)
{
mMorrowindPath = path;
return true;
}
bool UnshieldThread::SetTribunalPath(const std::string& path)
{
mTribunalPath = path;
return true;
}
bool UnshieldThread::SetBloodmoonPath(const std::string& path)
{
mBloodmoonPath = path;
return true;
}
void UnshieldThread::SetOutputPath(const std::string& path)
{
mOutputPath = path;
}
bool 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 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 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);
// 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);
// 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);
// 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 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 UnshieldThread::GetMWEsmPath()
{
return findFile(mOutputPath / "Data Files", "morrowind.esm").string();
}
bool UnshieldThread::TribunalDone()
{
return mTribunalDone;
}
bool UnshieldThread::BloodmoonDone()
{
return mBloodmoonDone;
}
void UnshieldThread::run()
{
extract();
emit close();
}
UnshieldThread::UnshieldThread()
{
mMorrowindDone = false;
mTribunalDone = false;
mBloodmoonDone = false;
}

View file

@ -0,0 +1,57 @@
#ifndef UNSHIELD_THREAD_H
#define UNSHIELD_THREAD_H
#include <QThread>
#include <boost/filesystem.hpp>
#include <libunshield.h>
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

View file

@ -0,0 +1,269 @@
/****************************************************************************
**
** 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 <QVariant>
#include <QPushButton>
#include <QAction>
#include <QApplication>
#include <QButtonGroup>
#include <QCheckBox>
#include <QDialog>
#include <QDialogButtonBox>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QLabel>
#include <QSpacerItem>
#include <QVBoxLayout>
/*!
\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.
*/
class CheckableMessageBoxPrivate
{
public:
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(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);
}
QLabel *pixmapLabel;
QLabel *messageLabel;
QCheckBox *checkBox;
QDialogButtonBox *buttonBox;
QAbstractButton *clickedButton;
};
CheckableMessageBox::CheckableMessageBox(QWidget *parent) :
QDialog(parent),
d(new 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*)));
}
CheckableMessageBox::~CheckableMessageBox()
{
delete d;
}
void CheckableMessageBox::slotClicked(QAbstractButton *b)
{
d->clickedButton = b;
}
QAbstractButton *CheckableMessageBox::clickedButton() const
{
return d->clickedButton;
}
QDialogButtonBox::StandardButton CheckableMessageBox::clickedStandardButton() const
{
if (d->clickedButton)
return d->buttonBox->standardButton(d->clickedButton);
return QDialogButtonBox::NoButton;
}
QString CheckableMessageBox::text() const
{
return d->messageLabel->text();
}
void CheckableMessageBox::setText(const QString &t)
{
d->messageLabel->setText(t);
}
QPixmap CheckableMessageBox::iconPixmap() const
{
if (const QPixmap *p = d->pixmapLabel->pixmap())
return QPixmap(*p);
return QPixmap();
}
void CheckableMessageBox::setIconPixmap(const QPixmap &p)
{
d->pixmapLabel->setPixmap(p);
d->pixmapLabel->setVisible(!p.isNull());
}
bool CheckableMessageBox::isChecked() const
{
return d->checkBox->isChecked();
}
void CheckableMessageBox::setChecked(bool s)
{
d->checkBox->setChecked(s);
}
QString CheckableMessageBox::checkBoxText() const
{
return d->checkBox->text();
}
void CheckableMessageBox::setCheckBoxText(const QString &t)
{
d->checkBox->setText(t);
}
bool CheckableMessageBox::isCheckBoxVisible() const
{
return d->checkBox->isVisible();
}
void CheckableMessageBox::setCheckBoxVisible(bool v)
{
d->checkBox->setVisible(v);
}
QDialogButtonBox::StandardButtons CheckableMessageBox::standardButtons() const
{
return d->buttonBox->standardButtons();
}
void CheckableMessageBox::setStandardButtons(QDialogButtonBox::StandardButtons s)
{
d->buttonBox->setStandardButtons(s);
}
QPushButton *CheckableMessageBox::button(QDialogButtonBox::StandardButton b) const
{
return d->buttonBox->button(b);
}
QPushButton *CheckableMessageBox::addButton(const QString &text, QDialogButtonBox::ButtonRole role)
{
return d->buttonBox->addButton(text, role);
}
QDialogButtonBox::StandardButton CheckableMessageBox::defaultButton() const
{
foreach (QAbstractButton *b, d->buttonBox->buttons())
if (QPushButton *pb = qobject_cast<QPushButton *>(b))
if (pb->isDefault())
return d->buttonBox->standardButton(pb);
return QDialogButtonBox::NoButton;
}
void CheckableMessageBox::setDefaultButton(QDialogButtonBox::StandardButton s)
{
if (QPushButton *b = d->buttonBox->button(s)) {
b->setDefault(true);
b->setFocus();
}
}
QDialogButtonBox::StandardButton
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 CheckableMessageBox::dialogButtonBoxToMessageBoxButton(QDialogButtonBox::StandardButton db)
{
return static_cast<QMessageBox::StandardButton>(int(db));
}

View file

@ -0,0 +1,100 @@
/****************************************************************************
**
** 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 <QDialogButtonBox>
#include <QMessageBox>
#include <QDialog>
class CheckableMessageBoxPrivate;
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

View file

@ -0,0 +1,71 @@
#include "textinputdialog.hpp"
#include <QDialogButtonBox>
#include <QApplication>
#include <QPushButton>
#include <QVBoxLayout>
#include <QValidator>
#include <QLabel>
#include <components/fileorderlist/utils/lineedit.hpp>
TextInputDialog::TextInputDialog(const QString& title, const QString &text, QWidget *parent) :
QDialog(parent)
{
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
mButtonBox = new QDialogButtonBox(this);
mButtonBox->addButton(QDialogButtonBox::Ok);
mButtonBox->addButton(QDialogButtonBox::Cancel);
// Line edit
QValidator *validator = new QRegExpValidator(QRegExp("^[a-zA-Z0-9_]*$"), this); // Alpha-numeric + underscore
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);
dialogLayout->addWidget(mButtonBox);
// Messageboxes on mac have no title
#ifndef Q_OS_MAC
setWindowTitle(title);
#else
Q_UNUSED(title);
#endif
setOkButtonEnabled(false);
setModal(true);
connect(mButtonBox, SIGNAL(accepted()), this, SLOT(accept()));
connect(mButtonBox, SIGNAL(rejected()), this, SLOT(reject()));
}
int TextInputDialog::exec()
{
mLineEdit->clear();
mLineEdit->setFocus();
return QDialog::exec();
}
void TextInputDialog::setOkButtonEnabled(bool enabled)
{
QPushButton *okButton = mButtonBox->button(QDialogButtonBox::Ok);
okButton->setEnabled(enabled);
QPalette *palette = new QPalette();
palette->setColor(QPalette::Text,Qt::red);
if (enabled) {
mLineEdit->setPalette(QApplication::palette());
} else {
// Existing profile name, make the text red
mLineEdit->setPalette(*palette);
}
}

View file

@ -0,0 +1,28 @@
#ifndef TEXTINPUTDIALOG_HPP
#define TEXTINPUTDIALOG_HPP
#include <QDialog>
//#include "lineedit.hpp"
class QDialogButtonBox;
class LineEdit;
class TextInputDialog : public QDialog
{
Q_OBJECT
public:
explicit TextInputDialog(const QString& title, const QString &text, QWidget *parent = 0);
inline LineEdit *lineEdit() { return mLineEdit; }
void setOkButtonEnabled(bool enabled);
LineEdit *mLineEdit;
int exec();
private:
QDialogButtonBox *mButtonBox;
};
#endif // TEXTINPUTDIALOG_HPP

View file

@ -0,0 +1,29 @@
set(MWINIIMPORT
main.cpp
importer.cpp
)
set(MWINIIMPORT_HEADER
importer.hpp
)
source_group(launcher FILES ${MWINIIMPORT} ${MWINIIMPORT_HEADER})
add_executable(mwiniimport
${MWINIIMPORT}
)
target_link_libraries(mwiniimport
${Boost_LIBRARIES}
components
)
if (BUILD_WITH_CODE_COVERAGE)
add_definitions (--coverage)
target_link_libraries(mwiniimport gcov)
endif()
if(DPKG_PROGRAM)
INSTALL(TARGETS mwiniimport RUNTIME DESTINATION games COMPONENT mwiniimport)
endif()

View file

@ -0,0 +1,873 @@
#include "importer.hpp"
#include <boost/iostreams/device/file.hpp>
#include <boost/iostreams/stream.hpp>
#include <iostream>
#include <string>
#include <map>
#include <vector>
#include <algorithm>
#include <sstream>
#include <components/misc/stringops.hpp>
MwIniImporter::MwIniImporter()
: mVerbose(false)
{
const char *map[][2] =
{
{ "fps", "General:Show FPS" },
{ "nosound", "General:Disable Audio" },
{ 0, 0 }
};
const char *fallback[] = {
// light
"LightAttenuation:UseConstant",
"LightAttenuation:ConstantValue",
"LightAttenuation:UseLinear",
"LightAttenuation:LinearMethod",
"LightAttenuation:LinearValue",
"LightAttenuation:LinearRadiusMult",
"LightAttenuation:UseQuadratic",
"LightAttenuation:QuadraticMethod",
"LightAttenuation:QuadraticValue",
"LightAttenuation:QuadraticRadiusMult",
"LightAttenuation:OutQuadInLin",
// inventory
"Inventory:DirectionalDiffuseR",
"Inventory:DirectionalDiffuseG",
"Inventory:DirectionalDiffuseB",
"Inventory:DirectionalAmbientR",
"Inventory:DirectionalAmbientG",
"Inventory:DirectionalAmbientB",
"Inventory:DirectionalRotationX",
"Inventory:DirectionalRotationY",
"Inventory:UniformScaling",
// map
"Map:Travel Siltstrider Red",
"Map:Travel Siltstrider Green",
"Map:Travel Siltstrider Blue",
"Map:Travel Boat Red",
"Map:Travel Boat Green",
"Map:Travel Boat Blue",
"Map:Travel Magic Red",
"Map:Travel Magic Green",
"Map:Travel Magic Blue",
"Map:Show Travel Lines",
// water
"Water:Map Alpha",
"Water:World Alpha",
"Water:SurfaceTextureSize",
"Water:SurfaceTileCount",
"Water:SurfaceFPS",
"Water:SurfaceTexture",
"Water:SurfaceFrameCount",
"Water:TileTextureDivisor",
"Water:RippleTexture",
"Water:RippleFrameCount",
"Water:RippleLifetime",
"Water:MaxNumberRipples",
"Water:RippleScale",
"Water:RippleRotSpeed",
"Water:RippleAlphas",
"Water:PSWaterReflectTerrain",
"Water:PSWaterReflectUpdate",
"Water:NearWaterRadius",
"Water:NearWaterPoints",
"Water:NearWaterUnderwaterFreq",
"Water:NearWaterUnderwaterVolume",
"Water:NearWaterIndoorTolerance",
"Water:NearWaterOutdoorTolerance",
"Water:NearWaterIndoorID",
"Water:NearWaterOutdoorID",
"Water:UnderwaterSunriseFog",
"Water:UnderwaterDayFog",
"Water:UnderwaterSunsetFog",
"Water:UnderwaterNightFog",
"Water:UnderwaterIndoorFog",
"Water:UnderwaterColor",
"Water:UnderwaterColorWeight",
// pixelwater
"PixelWater:SurfaceFPS",
"PixelWater:TileCount",
"PixelWater:Resolution",
// fonts
"Fonts:Font 0",
"Fonts:Font 1",
"Fonts:Font 2",
// UI colors
"FontColor:color_normal",
"FontColor:color_normal_over",
"FontColor:color_normal_pressed",
"FontColor:color_active",
"FontColor:color_active_over",
"FontColor:color_active_pressed",
"FontColor:color_disabled",
"FontColor:color_disabled_over",
"FontColor:color_disabled_pressed",
"FontColor:color_link",
"FontColor:color_link_over",
"FontColor:color_link_pressed",
"FontColor:color_journal_link",
"FontColor:color_journal_link_over",
"FontColor:color_journal_link_pressed",
"FontColor:color_journal_topic",
"FontColor:color_journal_topic_over",
"FontColor:color_journal_topic_pressed",
"FontColor:color_answer",
"FontColor:color_answer_over",
"FontColor:color_answer_pressed",
"FontColor:color_header",
"FontColor:color_notify",
"FontColor:color_big_normal",
"FontColor:color_big_normal_over",
"FontColor:color_big_normal_pressed",
"FontColor:color_big_link",
"FontColor:color_big_link_over",
"FontColor:color_big_link_pressed",
"FontColor:color_big_answer",
"FontColor:color_big_answer_over",
"FontColor:color_big_answer_pressed",
"FontColor:color_big_header",
"FontColor:color_big_notify",
"FontColor:color_background",
"FontColor:color_focus",
"FontColor:color_health",
"FontColor:color_magic",
"FontColor:color_fatigue",
"FontColor:color_misc",
"FontColor:color_weapon_fill",
"FontColor:color_magic_fill",
"FontColor:color_positive",
"FontColor:color_negative",
"FontColor:color_count",
// level up messages
"Level Up:Level2",
"Level Up:Level3",
"Level Up:Level4",
"Level Up:Level5",
"Level Up:Level6",
"Level Up:Level7",
"Level Up:Level8",
"Level Up:Level9",
"Level Up:Level10",
"Level Up:Level11",
"Level Up:Level12",
"Level Up:Level13",
"Level Up:Level14",
"Level Up:Level15",
"Level Up:Level16",
"Level Up:Level17",
"Level Up:Level18",
"Level Up:Level19",
"Level Up:Level20",
"Level Up:Default",
// character creation multiple choice test
"Question 1:Question",
"Question 1:AnswerOne",
"Question 1:AnswerTwo",
"Question 1:AnswerThree",
"Question 1:Sound",
"Question 2:Question",
"Question 2:AnswerOne",
"Question 2:AnswerTwo",
"Question 2:AnswerThree",
"Question 2:Sound",
"Question 3:Question",
"Question 3:AnswerOne",
"Question 3:AnswerTwo",
"Question 3:AnswerThree",
"Question 3:Sound",
"Question 4:Question",
"Question 4:AnswerOne",
"Question 4:AnswerTwo",
"Question 4:AnswerThree",
"Question 4:Sound",
"Question 5:Question",
"Question 5:AnswerOne",
"Question 5:AnswerTwo",
"Question 5:AnswerThree",
"Question 5:Sound",
"Question 6:Question",
"Question 6:AnswerOne",
"Question 6:AnswerTwo",
"Question 6:AnswerThree",
"Question 6:Sound",
"Question 7:Question",
"Question 7:AnswerOne",
"Question 7:AnswerTwo",
"Question 7:AnswerThree",
"Question 7:Sound",
"Question 8:Question",
"Question 8:AnswerOne",
"Question 8:AnswerTwo",
"Question 8:AnswerThree",
"Question 8:Sound",
"Question 9:Question",
"Question 9:AnswerOne",
"Question 9:AnswerTwo",
"Question 9:AnswerThree",
"Question 9:Sound",
"Question 10:Question",
"Question 10:AnswerOne",
"Question 10:AnswerTwo",
"Question 10:AnswerThree",
"Question 10:Sound",
// blood textures and models
"Blood:Model 0",
"Blood:Model 1",
"Blood:Model 2",
"Blood:Texture 0",
"Blood:Texture 1",
"Blood:Texture 2",
"Blood:Texture Name 0",
"Blood:Texture Name 1",
"Blood:Texture Name 2",
// movies
"Movies:Company Logo",
"Movies:Morrowind Logo",
"Movies:New Game",
"Movies:Loading",
"Movies:Options Menu",
// weather related values
"Weather Thunderstorm:Thunder Sound ID 0",
"Weather Thunderstorm:Thunder Sound ID 1",
"Weather Thunderstorm:Thunder Sound ID 2",
"Weather Thunderstorm:Thunder Sound ID 3",
"Weather:Sunrise Time",
"Weather:Sunset Time",
"Weather:Sunrise Duration",
"Weather:Sunset Duration",
"Weather:Hours Between Weather Changes", // AKA weather update time
"Weather Thunderstorm:Thunder Frequency",
"Weather Thunderstorm:Thunder Threshold",
"Weather:EnvReduceColor",
"Weather:LerpCloseColor",
"Weather:BumpFadeColor",
"Weather:AlphaReduce",
"Weather:Minimum Time Between Environmental Sounds",
"Weather:Maximum Time Between Environmental Sounds",
"Weather:Sun Glare Fader Max",
"Weather:Sun Glare Fader Angle Max",
"Weather:Sun Glare Fader Color",
"Weather:Timescale Clouds",
"Weather:Precip Gravity",
"Weather:Rain Ripples",
"Weather:Rain Ripple Radius",
"Weather:Rain Ripples Per Drop",
"Weather:Rain Ripple Scale",
"Weather:Rain Ripple Speed",
"Weather:Fog Depth Change Speed",
"Weather:Sky Pre-Sunrise Time",
"Weather:Sky Post-Sunrise Time",
"Weather:Sky Pre-Sunset Time",
"Weather:Sky Post-Sunset Time",
"Weather:Ambient Pre-Sunrise Time",
"Weather:Ambient Post-Sunrise Time",
"Weather:Ambient Pre-Sunset Time",
"Weather:Ambient Post-Sunset Time",
"Weather:Fog Pre-Sunrise Time",
"Weather:Fog Post-Sunrise Time",
"Weather:Fog Pre-Sunset Time",
"Weather:Fog Post-Sunset Time",
"Weather:Sun Pre-Sunrise Time",
"Weather:Sun Post-Sunrise Time",
"Weather:Sun Pre-Sunset Time",
"Weather:Sun Post-Sunset Time",
"Weather:Stars Post-Sunset Start",
"Weather:Stars Pre-Sunrise Finish",
"Weather:Stars Fading Duration",
"Weather:Snow Ripples",
"Weather:Snow Ripple Radius",
"Weather:Snow Ripples Per Flake",
"Weather:Snow Ripple Scale",
"Weather:Snow Ripple Speed",
"Weather:Snow Gravity Scale",
"Weather:Snow High Kill",
"Weather:Snow Low Kill",
"Weather Clear:Cloud Texture",
"Weather Clear:Clouds Maximum Percent",
"Weather Clear:Transition Delta",
"Weather Clear:Sky Sunrise Color",
"Weather Clear:Sky Day Color",
"Weather Clear:Sky Sunset Color",
"Weather Clear:Sky Night Color",
"Weather Clear:Fog Sunrise Color",
"Weather Clear:Fog Day Color",
"Weather Clear:Fog Sunset Color",
"Weather Clear:Fog Night Color",
"Weather Clear:Ambient Sunrise Color",
"Weather Clear:Ambient Day Color",
"Weather Clear:Ambient Sunset Color",
"Weather Clear:Ambient Night Color",
"Weather Clear:Sun Sunrise Color",
"Weather Clear:Sun Day Color",
"Weather Clear:Sun Sunset Color",
"Weather Clear:Sun Night Color",
"Weather Clear:Sun Disc Sunset Color",
"Weather Clear:Land Fog Day Depth",
"Weather Clear:Land Fog Night Depth",
"Weather Clear:Wind Speed",
"Weather Clear:Cloud Speed",
"Weather Clear:Glare View",
"Weather Clear:Ambient Loop Sound ID",
"Weather Cloudy:Cloud Texture",
"Weather Cloudy:Clouds Maximum Percent",
"Weather Cloudy:Transition Delta",
"Weather Cloudy:Sky Sunrise Color",
"Weather Cloudy:Sky Day Color",
"Weather Cloudy:Sky Sunset Color",
"Weather Cloudy:Sky Night Color",
"Weather Cloudy:Fog Sunrise Color",
"Weather Cloudy:Fog Day Color",
"Weather Cloudy:Fog Sunset Color",
"Weather Cloudy:Fog Night Color",
"Weather Cloudy:Ambient Sunrise Color",
"Weather Cloudy:Ambient Day Color",
"Weather Cloudy:Ambient Sunset Color",
"Weather Cloudy:Ambient Night Color",
"Weather Cloudy:Sun Sunrise Color",
"Weather Cloudy:Sun Day Color",
"Weather Cloudy:Sun Sunset Color",
"Weather Cloudy:Sun Night Color",
"Weather Cloudy:Sun Disc Sunset Color",
"Weather Cloudy:Land Fog Day Depth",
"Weather Cloudy:Land Fog Night Depth",
"Weather Cloudy:Wind Speed",
"Weather Cloudy:Cloud Speed",
"Weather Cloudy:Glare View",
"Weather Cloudy:Ambient Loop Sound ID",
"Weather Foggy:Cloud Texture",
"Weather Foggy:Clouds Maximum Percent",
"Weather Foggy:Transition Delta",
"Weather Foggy:Sky Sunrise Color",
"Weather Foggy:Sky Day Color",
"Weather Foggy:Sky Sunset Color",
"Weather Foggy:Sky Night Color",
"Weather Foggy:Fog Sunrise Color",
"Weather Foggy:Fog Day Color",
"Weather Foggy:Fog Sunset Color",
"Weather Foggy:Fog Night Color",
"Weather Foggy:Ambient Sunrise Color",
"Weather Foggy:Ambient Day Color",
"Weather Foggy:Ambient Sunset Color",
"Weather Foggy:Ambient Night Color",
"Weather Foggy:Sun Sunrise Color",
"Weather Foggy:Sun Day Color",
"Weather Foggy:Sun Sunset Color",
"Weather Foggy:Sun Night Color",
"Weather Foggy:Sun Disc Sunset Color",
"Weather Foggy:Land Fog Day Depth",
"Weather Foggy:Land Fog Night Depth",
"Weather Foggy:Wind Speed",
"Weather Foggy:Cloud Speed",
"Weather Foggy:Glare View",
"Weather Foggy:Ambient Loop Sound ID",
"Weather Thunderstorm:Cloud Texture",
"Weather Thunderstorm:Clouds Maximum Percent",
"Weather Thunderstorm:Transition Delta",
"Weather Thunderstorm:Sky Sunrise Color",
"Weather Thunderstorm:Sky Day Color",
"Weather Thunderstorm:Sky Sunset Color",
"Weather Thunderstorm:Sky Night Color",
"Weather Thunderstorm:Fog Sunrise Color",
"Weather Thunderstorm:Fog Day Color",
"Weather Thunderstorm:Fog Sunset Color",
"Weather Thunderstorm:Fog Night Color",
"Weather Thunderstorm:Ambient Sunrise Color",
"Weather Thunderstorm:Ambient Day Color",
"Weather Thunderstorm:Ambient Sunset Color",
"Weather Thunderstorm:Ambient Night Color",
"Weather Thunderstorm:Sun Sunrise Color",
"Weather Thunderstorm:Sun Day Color",
"Weather Thunderstorm:Sun Sunset Color",
"Weather Thunderstorm:Sun Night Color",
"Weather Thunderstorm:Sun Disc Sunset Color",
"Weather Thunderstorm:Land Fog Day Depth",
"Weather Thunderstorm:Land Fog Night Depth",
"Weather Thunderstorm:Wind Speed",
"Weather Thunderstorm:Cloud Speed",
"Weather Thunderstorm:Glare View",
"Weather Thunderstorm:Rain Loop Sound ID",
"Weather Thunderstorm:Using Precip",
"Weather Thunderstorm:Rain Diameter",
"Weather Thunderstorm:Rain Height Min",
"Weather Thunderstorm:Rain Height Max",
"Weather Thunderstorm:Rain Threshold",
"Weather Thunderstorm:Max Raindrops",
"Weather Thunderstorm:Rain Entrance Speed",
"Weather Thunderstorm:Ambient Loop Sound ID",
"Weather Thunderstorm:Flash Decrement",
"Weather Rain:Cloud Texture",
"Weather Rain:Clouds Maximum Percent",
"Weather Rain:Transition Delta",
"Weather Rain:Sky Sunrise Color",
"Weather Rain:Sky Day Color",
"Weather Rain:Sky Sunset Color",
"Weather Rain:Sky Night Color",
"Weather Rain:Fog Sunrise Color",
"Weather Rain:Fog Day Color",
"Weather Rain:Fog Sunset Color",
"Weather Rain:Fog Night Color",
"Weather Rain:Ambient Sunrise Color",
"Weather Rain:Ambient Day Color",
"Weather Rain:Ambient Sunset Color",
"Weather Rain:Ambient Night Color",
"Weather Rain:Sun Sunrise Color",
"Weather Rain:Sun Day Color",
"Weather Rain:Sun Sunset Color",
"Weather Rain:Sun Night Color",
"Weather Rain:Sun Disc Sunset Color",
"Weather Rain:Land Fog Day Depth",
"Weather Rain:Land Fog Night Depth",
"Weather Rain:Wind Speed",
"Weather Rain:Cloud Speed",
"Weather Rain:Glare View",
"Weather Rain:Rain Loop Sound ID",
"Weather Rain:Using Precip",
"Weather Rain:Rain Diameter",
"Weather Rain:Rain Height Min",
"Weather Rain:Rain Height Max",
"Weather Rain:Rain Threshold",
"Weather Rain:Rain Entrance Speed",
"Weather Rain:Ambient Loop Sound ID",
"Weather Rain:Max Raindrops",
"Weather Overcast:Cloud Texture",
"Weather Overcast:Clouds Maximum Percent",
"Weather Overcast:Transition Delta",
"Weather Overcast:Sky Sunrise Color",
"Weather Overcast:Sky Day Color",
"Weather Overcast:Sky Sunset Color",
"Weather Overcast:Sky Night Color",
"Weather Overcast:Fog Sunrise Color",
"Weather Overcast:Fog Day Color",
"Weather Overcast:Fog Sunset Color",
"Weather Overcast:Fog Night Color",
"Weather Overcast:Ambient Sunrise Color",
"Weather Overcast:Ambient Day Color",
"Weather Overcast:Ambient Sunset Color",
"Weather Overcast:Ambient Night Color",
"Weather Overcast:Sun Sunrise Color",
"Weather Overcast:Sun Day Color",
"Weather Overcast:Sun Sunset Color",
"Weather Overcast:Sun Night Color",
"Weather Overcast:Sun Disc Sunset Color",
"Weather Overcast:Land Fog Day Depth",
"Weather Overcast:Land Fog Night Depth",
"Weather Overcast:Wind Speed",
"Weather Overcast:Cloud Speed",
"Weather Overcast:Glare View",
"Weather Overcast:Ambient Loop Sound ID",
"Weather Ashstorm:Cloud Texture",
"Weather Ashstorm:Clouds Maximum Percent",
"Weather Ashstorm:Transition Delta",
"Weather Ashstorm:Sky Sunrise Color",
"Weather Ashstorm:Sky Day Color",
"Weather Ashstorm:Sky Sunset Color",
"Weather Ashstorm:Sky Night Color",
"Weather Ashstorm:Fog Sunrise Color",
"Weather Ashstorm:Fog Day Color",
"Weather Ashstorm:Fog Sunset Color",
"Weather Ashstorm:Fog Night Color",
"Weather Ashstorm:Ambient Sunrise Color",
"Weather Ashstorm:Ambient Day Color",
"Weather Ashstorm:Ambient Sunset Color",
"Weather Ashstorm:Ambient Night Color",
"Weather Ashstorm:Sun Sunrise Color",
"Weather Ashstorm:Sun Day Color",
"Weather Ashstorm:Sun Sunset Color",
"Weather Ashstorm:Sun Night Color",
"Weather Ashstorm:Sun Disc Sunset Color",
"Weather Ashstorm:Land Fog Day Depth",
"Weather Ashstorm:Land Fog Night Depth",
"Weather Ashstorm:Wind Speed",
"Weather Ashstorm:Cloud Speed",
"Weather Ashstorm:Glare View",
"Weather Ashstorm:Ambient Loop Sound ID",
"Weather Ashstorm:Storm Threshold",
"Weather Blight:Cloud Texture",
"Weather Blight:Clouds Maximum Percent",
"Weather Blight:Transition Delta",
"Weather Blight:Sky Sunrise Color",
"Weather Blight:Sky Day Color",
"Weather Blight:Sky Sunset Color",
"Weather Blight:Sky Night Color",
"Weather Blight:Fog Sunrise Color",
"Weather Blight:Fog Day Color",
"Weather Blight:Fog Sunset Color",
"Weather Blight:Fog Night Color",
"Weather Blight:Ambient Sunrise Color",
"Weather Blight:Ambient Day Color",
"Weather Blight:Ambient Sunset Color",
"Weather Blight:Ambient Night Color",
"Weather Blight:Sun Sunrise Color",
"Weather Blight:Sun Day Color",
"Weather Blight:Sun Sunset Color",
"Weather Blight:Sun Night Color",
"Weather Blight:Sun Disc Sunset Color",
"Weather Blight:Land Fog Day Depth",
"Weather Blight:Land Fog Night Depth",
"Weather Blight:Wind Speed",
"Weather Blight:Cloud Speed",
"Weather Blight:Glare View",
"Weather Blight:Ambient Loop Sound ID",
"Weather Blight:Storm Threshold",
"Weather Blight:Disease Chance",
// for Bloodmoon
"Weather Snow:Cloud Texture",
"Weather Snow:Clouds Maximum Percent",
"Weather Snow:Transition Delta",
"Weather Snow:Sky Sunrise Color",
"Weather Snow:Sky Day Color",
"Weather Snow:Sky Sunset Color",
"Weather Snow:Sky Night Color",
"Weather Snow:Fog Sunrise Color",
"Weather Snow:Fog Day Color",
"Weather Snow:Fog Sunset Color",
"Weather Snow:Fog Night Color",
"Weather Snow:Ambient Sunrise Color",
"Weather Snow:Ambient Day Color",
"Weather Snow:Ambient Sunset Color",
"Weather Snow:Ambient Night Color",
"Weather Snow:Sun Sunrise Color",
"Weather Snow:Sun Day Color",
"Weather Snow:Sun Sunset Color",
"Weather Snow:Sun Night Color",
"Weather Snow:Sun Disc Sunset Color",
"Weather Snow:Land Fog Day Depth",
"Weather Snow:Land Fog Night Depth",
"Weather Snow:Wind Speed",
"Weather Snow:Cloud Speed",
"Weather Snow:Glare View",
"Weather Snow:Snow Diameter",
"Weather Snow:Snow Height Min",
"Weather Snow:Snow Height Max",
"Weather Snow:Snow Entrance Speed",
"Weather Snow:Max Snowflakes",
"Weather Snow:Ambient Loop Sound ID",
"Weather Snow:Snow Threshold",
// for Bloodmoon
"Weather Blizzard:Cloud Texture",
"Weather Blizzard:Clouds Maximum Percent",
"Weather Blizzard:Transition Delta",
"Weather Blizzard:Sky Sunrise Color",
"Weather Blizzard:Sky Day Color",
"Weather Blizzard:Sky Sunset Color",
"Weather Blizzard:Sky Night Color",
"Weather Blizzard:Fog Sunrise Color",
"Weather Blizzard:Fog Day Color",
"Weather Blizzard:Fog Sunset Color",
"Weather Blizzard:Fog Night Color",
"Weather Blizzard:Ambient Sunrise Color",
"Weather Blizzard:Ambient Day Color",
"Weather Blizzard:Ambient Sunset Color",
"Weather Blizzard:Ambient Night Color",
"Weather Blizzard:Sun Sunrise Color",
"Weather Blizzard:Sun Day Color",
"Weather Blizzard:Sun Sunset Color",
"Weather Blizzard:Sun Night Color",
"Weather Blizzard:Sun Disc Sunset Color",
"Weather Blizzard:Land Fog Day Depth",
"Weather Blizzard:Land Fog Night Depth",
"Weather Blizzard:Wind Speed",
"Weather Blizzard:Cloud Speed",
"Weather Blizzard:Glare View",
"Weather Blizzard:Ambient Loop Sound ID",
"Weather Blizzard:Storm Threshold",
// moons
"Moons:Secunda Size",
"Moons:Secunda Axis Offset",
"Moons:Secunda Speed",
"Moons:Secunda Daily Increment",
"Moons:Secunda Moon Shadow Early Fade Angle",
"Moons:Secunda Fade Start Angle",
"Moons:Secunda Fade End Angle",
"Moons:Secunda Fade In Start",
"Moons:Secunda Fade In Finish",
"Moons:Secunda Fade Out Start",
"Moons:Secunda Fade Out Finish",
"Moons:Masser Size",
"Moons:Masser Axis Offset",
"Moons:Masser Speed",
"Moons:Masser Daily Increment",
"Moons:Masser Moon Shadow Early Fade Angle",
"Moons:Masser Fade Start Angle",
"Moons:Masser Fade End Angle",
"Moons:Masser Fade In Start",
"Moons:Masser Fade In Finish",
"Moons:Masser Fade Out Start",
"Moons:Masser Fade Out Finish",
"Moons:Script Color",
0
};
for(int i=0; map[i][0]; i++) {
mMergeMap.insert(std::make_pair<std::string, std::string>(map[i][0], map[i][1]));
}
for(int i=0; fallback[i]; i++) {
mMergeFallback.push_back(fallback[i]);
}
}
void MwIniImporter::setVerbose(bool verbose) {
mVerbose = verbose;
}
std::string MwIniImporter::numberToString(int n) {
std::stringstream str;
str << n;
return str.str();
}
MwIniImporter::multistrmap MwIniImporter::loadIniFile(const std::string& filename) const {
std::cout << "load ini file: " << filename << std::endl;
std::string section("");
MwIniImporter::multistrmap map;
boost::iostreams::stream<boost::iostreams::file_source>file(filename.c_str());
ToUTF8::Utf8Encoder encoder(mEncoding);
std::string line;
while (std::getline(file, line)) {
line = encoder.getUtf8(line);
// unify Unix-style and Windows file ending
if (!(line.empty()) && (line[line.length()-1]) == '\r') {
line = line.substr(0, line.length()-1);
}
if(line[0] == '[') {
int pos = line.find(']');
if(pos < 2) {
std::cout << "Warning: ini file wrongly formatted (" << line << "). Line ignored." << std::endl;
continue;
}
section = line.substr(1, line.find(']')-1);
continue;
}
int comment_pos = line.find(";");
if(comment_pos > 0) {
line = line.substr(0,comment_pos);
}
if(line.empty()) {
continue;
}
int pos = line.find("=");
if(pos < 1) {
continue;
}
std::string key(section + ":" + line.substr(0,pos));
std::string value(line.substr(pos+1));
if(value.empty()) {
std::cout << "Warning: ignored empty value for key '" << key << "'." << std::endl;
continue;
}
multistrmap::iterator it;
if((it = map.find(key)) == map.end()) {
map.insert( std::make_pair (key, std::vector<std::string>() ) );
}
map[key].push_back(value);
}
return map;
}
MwIniImporter::multistrmap MwIniImporter::loadCfgFile(const std::string& filename) {
std::cout << "load cfg file: " << filename << std::endl;
MwIniImporter::multistrmap map;
boost::iostreams::stream<boost::iostreams::file_source>file(filename.c_str());
std::string line;
while (std::getline(file, line)) {
// we cant say comment by only looking at first char anymore
int comment_pos = line.find("#");
if(comment_pos > 0) {
line = line.substr(0,comment_pos);
}
if(line.empty()) {
continue;
}
int pos = line.find("=");
if(pos < 1) {
continue;
}
std::string key(line.substr(0,pos));
std::string value(line.substr(pos+1));
multistrmap::iterator it;
if((it = map.find(key)) == map.end()) {
map.insert( std::make_pair (key, std::vector<std::string>() ) );
}
map[key].push_back(value);
}
return map;
}
void MwIniImporter::merge(multistrmap &cfg, const multistrmap &ini) const {
multistrmap::const_iterator iniIt;
for(strmap::const_iterator it=mMergeMap.begin(); it!=mMergeMap.end(); ++it) {
if((iniIt = ini.find(it->second)) != ini.end()) {
for(std::vector<std::string>::const_iterator vc = iniIt->second.begin(); vc != iniIt->second.end(); ++vc) {
cfg.erase(it->first);
insertMultistrmap(cfg, it->first, *vc);
}
}
}
}
void MwIniImporter::mergeFallback(multistrmap &cfg, const multistrmap &ini) const {
cfg.erase("fallback");
multistrmap::const_iterator iniIt;
for(std::vector<std::string>::const_iterator it=mMergeFallback.begin(); it!=mMergeFallback.end(); ++it) {
if((iniIt = ini.find(*it)) != ini.end()) {
for(std::vector<std::string>::const_iterator vc = iniIt->second.begin(); vc != iniIt->second.end(); ++vc) {
std::string value(*it);
std::replace( value.begin(), value.end(), ' ', '_' );
std::replace( value.begin(), value.end(), ':', '_' );
value.append(",").append(vc->substr(0,vc->length()));
insertMultistrmap(cfg, "fallback", value);
}
}
}
}
void MwIniImporter::insertMultistrmap(multistrmap &cfg, const std::string& key, const std::string& value) {
const multistrmap::const_iterator it = cfg.find(key);
if(it == cfg.end()) {
cfg.insert(std::make_pair (key, std::vector<std::string>() ));
}
cfg[key].push_back(value);
}
void MwIniImporter::importArchives(multistrmap &cfg, const multistrmap &ini) const {
std::vector<std::string> archives;
std::string baseArchive("Archives:Archive ");
std::string archive;
// Search archives listed in ini file
multistrmap::const_iterator it = ini.begin();
for(int i=0; it != ini.end(); i++) {
archive = baseArchive;
archive.append(this->numberToString(i));
it = ini.find(archive);
if(it == ini.end()) {
break;
}
for(std::vector<std::string>::const_iterator entry = it->second.begin(); entry!=it->second.end(); ++entry) {
archives.push_back(*entry);
}
}
cfg.erase("fallback-archive");
cfg.insert( std::make_pair<std::string, std::vector<std::string> > ("fallback-archive", std::vector<std::string>()));
// Add Morrowind.bsa by default, since Vanilla loads this archive even if it
// does not appears in the ini file
cfg["fallback-archive"].push_back("Morrowind.bsa");
for(std::vector<std::string>::const_iterator it=archives.begin(); it!=archives.end(); ++it) {
cfg["fallback-archive"].push_back(*it);
}
}
void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini) const {
std::vector<std::string> esmFiles;
std::vector<std::string> espFiles;
std::string baseGameFile("Game Files:GameFile");
std::string gameFile("");
multistrmap::const_iterator it = ini.begin();
for(int i=0; it != ini.end(); i++) {
gameFile = baseGameFile;
gameFile.append(this->numberToString(i));
it = ini.find(gameFile);
if(it == ini.end()) {
break;
}
for(std::vector<std::string>::const_iterator entry = it->second.begin(); entry!=it->second.end(); ++entry) {
std::string filetype(entry->substr(entry->length()-3));
Misc::StringUtils::toLower(filetype);
if(filetype.compare("esm") == 0) {
esmFiles.push_back(*entry);
}
else if(filetype.compare("esp") == 0) {
espFiles.push_back(*entry);
}
}
gameFile = "";
}
cfg.erase("master");
cfg.insert( std::make_pair<std::string, std::vector<std::string> > ("master", std::vector<std::string>() ) );
for(std::vector<std::string>::const_iterator it=esmFiles.begin(); it!=esmFiles.end(); ++it) {
cfg["master"].push_back(*it);
}
cfg.erase("plugin");
cfg.insert( std::make_pair<std::string, std::vector<std::string> > ("plugin", std::vector<std::string>() ) );
for(std::vector<std::string>::const_iterator it=espFiles.begin(); it!=espFiles.end(); ++it) {
cfg["plugin"].push_back(*it);
}
}
void MwIniImporter::writeToFile(boost::iostreams::stream<boost::iostreams::file_sink> &out, const multistrmap &cfg) {
for(multistrmap::const_iterator it=cfg.begin(); it != cfg.end(); ++it) {
for(std::vector<std::string>::const_iterator entry=it->second.begin(); entry != it->second.end(); ++entry) {
out << (it->first) << "=" << (*entry) << std::endl;
}
}
}
void MwIniImporter::setInputEncoding(const ToUTF8::FromType &encoding)
{
mEncoding = encoding;
}

View file

@ -0,0 +1,39 @@
#ifndef MWINIIMPORTER_IMPORTER
#define MWINIIMPORTER_IMPORTER 1
#include <boost/iostreams/device/file.hpp>
#include <boost/iostreams/stream.hpp>
#include <string>
#include <map>
#include <vector>
#include <exception>
#include <components/to_utf8/to_utf8.hpp>
class MwIniImporter {
public:
typedef std::map<std::string, std::string> strmap;
typedef std::map<std::string, std::vector<std::string> > multistrmap;
MwIniImporter();
void setInputEncoding(const ToUTF8::FromType& encoding);
void setVerbose(bool verbose);
multistrmap loadIniFile(const std::string& filename) const;
static multistrmap loadCfgFile(const std::string& filename);
void merge(multistrmap &cfg, const multistrmap &ini) const;
void mergeFallback(multistrmap &cfg, const multistrmap &ini) const;
void importGameFiles(multistrmap &cfg, const multistrmap &ini) const;
void importArchives(multistrmap &cfg, const multistrmap &ini) const;
static void writeToFile(boost::iostreams::stream<boost::iostreams::file_sink> &out, const multistrmap &cfg);
private:
static void insertMultistrmap(multistrmap &cfg, const std::string& key, const std::string& value);
static std::string numberToString(int n);
bool mVerbose;
strmap mMergeMap;
std::vector<std::string> mMergeFallback;
ToUTF8::FromType mEncoding;
};
#endif

101
apps/mwiniimporter/main.cpp Normal file
View file

@ -0,0 +1,101 @@
#include "importer.hpp"
#include <iostream>
#include <string>
#include <boost/program_options.hpp>
#include <boost/filesystem.hpp>
namespace bpo = boost::program_options;
int main(int argc, char *argv[]) {
bpo::options_description desc("Syntax: mwiniimporter <options> inifile configfile\nAllowed options");
bpo::positional_options_description p_desc;
desc.add_options()
("help,h", "produce help message")
("verbose,v", "verbose output")
("ini,i", bpo::value<std::string>(), "morrowind.ini file")
("cfg,c", bpo::value<std::string>(), "openmw.cfg file")
("output,o", bpo::value<std::string>()->default_value(""), "openmw.cfg file")
("game-files,g", "import esm and esp files")
("no-archives,A", "disable bsa archives import")
("encoding,e", bpo::value<std::string>()-> default_value("win1252"),
"Character encoding used in OpenMW game messages:\n"
"\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages\n"
"\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n"
"\n\twin1252 - Western European (Latin) alphabet, used by default")
;
p_desc.add("ini", 1).add("cfg", 1);
bpo::variables_map vm;
try
{
bpo::parsed_options parsed = bpo::command_line_parser(argc, argv)
.options(desc)
.positional(p_desc)
.run();
bpo::store(parsed, vm);
}
catch(boost::program_options::unknown_option & x)
{
std::cerr << "ERROR: " << x.what() << std::endl;
return false;
}
catch(boost::program_options::invalid_command_line_syntax & x)
{
std::cerr << "ERROR: " << x.what() << std::endl;
return false;
}
if(vm.count("help") || !vm.count("ini") || !vm.count("cfg")) {
std::cout << desc;
return 0;
}
bpo::notify(vm);
std::string iniFile = vm["ini"].as<std::string>();
std::string cfgFile = vm["cfg"].as<std::string>();
// if no output is given, write back to cfg file
std::string outputFile(vm["output"].as<std::string>());
if(vm["output"].defaulted()) {
outputFile = vm["cfg"].as<std::string>();
}
if(!boost::filesystem::exists(iniFile)) {
std::cerr << "ini file does not exist" << std::endl;
return -3;
}
if(!boost::filesystem::exists(cfgFile))
std::cerr << "cfg file does not exist" << std::endl;
MwIniImporter importer;
importer.setVerbose(vm.count("verbose"));
// Font encoding settings
std::string encoding(vm["encoding"].as<std::string>());
importer.setInputEncoding(ToUTF8::calculateEncoding(encoding));
MwIniImporter::multistrmap ini = importer.loadIniFile(iniFile);
MwIniImporter::multistrmap cfg = importer.loadCfgFile(cfgFile);
importer.merge(cfg, ini);
importer.mergeFallback(cfg, ini);
if(vm.count("game-files")) {
importer.importGameFiles(cfg, ini);
}
if(!vm.count("no-archives")) {
importer.importArchives(cfg, ini);
}
std::cout << "write to: " << outputFile << std::endl;
boost::iostreams::stream<boost::iostreams::file_sink> file(outputFile);
importer.writeToFile(file, cfg);
return 0;
}

158
apps/opencs/CMakeLists.txt Normal file
View file

@ -0,0 +1,158 @@
set (OPENCS_SRC main.cpp)
opencs_units (. editor)
set (CMAKE_BUILD_TYPE DEBUG)
opencs_units (model/doc
document
)
opencs_units_noqt (model/doc
documentmanager
)
opencs_hdrs_noqt (model/doc
state
)
opencs_units (model/world
idtable idtableproxymodel regionmap
)
opencs_units_noqt (model/world
universalid data record commands columnbase scriptcontext cell refidcollection
refidadapter refiddata refidadapterimp ref collectionbase refcollection columns
)
opencs_hdrs_noqt (model/world
columnimp idcollection collection
)
opencs_units (model/tools
tools operation reportmodel
)
opencs_units_noqt (model/tools
stage verifier mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck
birthsigncheck spellcheck
)
opencs_units (view/doc
viewmanager view operations operation subview startup filedialog
)
opencs_units_noqt (view/doc
subviewfactory
)
opencs_hdrs_noqt (view/doc
subviewfactoryimp
)
opencs_units (view/world
table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator
cellcreator referenceablecreator referencecreator
)
opencs_units_noqt (view/world
dialoguesubview subviews
enumdelegate vartypedelegate recordstatusdelegate refidtypedelegate datadisplaydelegate
scripthighlighter idvalidator
)
opencs_units (view/tools
reportsubview
)
opencs_units_noqt (view/tools
subviews
)
opencs_units (view/settings
abstractblock
proxyblock
abstractwidget
usersettingsdialog
datadisplayformatpage
windowpage
)
opencs_units_noqt (view/settings
abstractpage
blankpage
groupblock
customblock
groupbox
itemblock
settingwidget
toggleblock
support
)
opencs_units (model/settings
usersettings
settingcontainer
)
opencs_units_noqt (model/settings
support
settingsitem
)
opencs_units_noqt (model/filter
node unarynode narynode leafnode booleannode parser andnode ornode notnode textnode valuenode
)
opencs_hdrs_noqt (model/filter
filter
)
opencs_units (view/filter
filtercreator filterbox recordfilterbox editwidget
)
set (OPENCS_US
)
set (OPENCS_RES ../../files/opencs/resources.qrc
../../files/launcher/launcher.qrc
)
set (OPENCS_UI ../../files/ui/datafilespage.ui
)
source_group (opencs FILES ${OPENCS_SRC} ${OPENCS_HDR})
if(WIN32)
set(QT_USE_QTMAIN TRUE)
endif(WIN32)
find_package(Qt4 COMPONENTS QtCore QtGui QtNetwork QtXml QtXmlPatterns REQUIRED)
include(${QT_USE_FILE})
qt4_wrap_ui(OPENCS_UI_HDR ${OPENCS_UI})
qt4_wrap_cpp(OPENCS_MOC_SRC ${OPENCS_HDR_QT})
qt4_add_resources(OPENCS_RES_SRC ${OPENCS_RES})
include_directories(${CMAKE_CURRENT_BINARY_DIR})
add_executable(opencs
${OPENCS_SRC}
${OPENCS_UI_HDR}
${OPENCS_MOC_SRC}
${OPENCS_RES_SRC}
)
target_link_libraries(opencs
${Boost_LIBRARIES}
${QT_LIBRARIES}
components
)

157
apps/opencs/editor.cpp Normal file
View file

@ -0,0 +1,157 @@
#include "editor.hpp"
#include <QApplication>
#include <QLocalServer>
#include <QLocalSocket>
#include "model/doc/document.hpp"
#include "model/world/data.hpp"
CS::Editor::Editor() : mViewManager (mDocumentManager)
{
mIpcServerName = "org.openmw.OpenCS";
connect (&mViewManager, SIGNAL (newDocumentRequest ()), this, SLOT (createDocument ()));
connect (&mViewManager, SIGNAL (loadDocumentRequest ()), this, SLOT (loadDocument ()));
connect (&mStartup, SIGNAL (createDocument()), this, SLOT (createDocument ()));
connect (&mStartup, SIGNAL (loadDocument()), this, SLOT (loadDocument ()));
connect (&mFileDialog, SIGNAL(openFiles()), this, SLOT(openFiles()));
connect (&mFileDialog, SIGNAL(createNewFile()), this, SLOT(createNewFile()));
setupDataFiles();
}
void CS::Editor::setupDataFiles()
{
boost::program_options::variables_map variables;
boost::program_options::options_description desc;
desc.add_options()
("data", boost::program_options::value<Files::PathContainer>()->default_value(Files::PathContainer(), "data")->multitoken())
("data-local", boost::program_options::value<std::string>()->default_value(""))
("fs-strict", boost::program_options::value<bool>()->implicit_value(true)->default_value(false))
("encoding", boost::program_options::value<std::string>()->default_value("win1252"));
boost::program_options::notify(variables);
mCfgMgr.readConfiguration(variables, desc);
Files::PathContainer mDataDirs, mDataLocal;
if (!variables["data"].empty()) {
mDataDirs = Files::PathContainer(variables["data"].as<Files::PathContainer>());
}
std::string local = variables["data-local"].as<std::string>();
if (!local.empty()) {
mDataLocal.push_back(Files::PathContainer::value_type(local));
}
mCfgMgr.processPaths(mDataDirs);
mCfgMgr.processPaths(mDataLocal);
// Set the charset for reading the esm/esp files
QString encoding = QString::fromStdString(variables["encoding"].as<std::string>());
mFileDialog.setEncoding(encoding);
Files::PathContainer dataDirs;
dataDirs.insert(dataDirs.end(), mDataDirs.begin(), mDataDirs.end());
dataDirs.insert(dataDirs.end(), mDataLocal.begin(), mDataLocal.end());
for (Files::PathContainer::const_iterator iter = dataDirs.begin(); iter != dataDirs.end(); ++iter)
{
QString path = QString::fromStdString(iter->string());
mFileDialog.addFiles(path);
}
//load the settings into the userSettings instance.
const QString settingFileName = "opencs.cfg";
CSMSettings::UserSettings::instance().loadSettings(settingFileName);
}
void CS::Editor::createDocument()
{
mStartup.hide();
mFileDialog.newFile();
}
void CS::Editor::loadDocument()
{
mStartup.hide();
mFileDialog.openFile();
}
void CS::Editor::openFiles()
{
std::vector<boost::filesystem::path> files;
QStringList paths = mFileDialog.checkedItemsPaths();
foreach (const QString &path, paths) {
files.push_back(path.toStdString());
}
CSMDoc::Document *document = mDocumentManager.addDocument(files, false);
mViewManager.addView (document);
mFileDialog.hide();
}
void CS::Editor::createNewFile()
{
std::vector<boost::filesystem::path> files;
QStringList paths = mFileDialog.checkedItemsPaths();
foreach (const QString &path, paths) {
files.push_back(path.toStdString());
}
files.push_back(mFileDialog.fileName().toStdString());
CSMDoc::Document *document = mDocumentManager.addDocument (files, true);
mViewManager.addView (document);
mFileDialog.hide();
}
void CS::Editor::showStartup()
{
if(mStartup.isHidden())
mStartup.show();
mStartup.raise();
mStartup.activateWindow();
}
bool CS::Editor::makeIPCServer()
{
mServer = new QLocalServer(this);
if(mServer->listen(mIpcServerName))
{
connect(mServer, SIGNAL(newConnection()), this, SLOT(showStartup()));
return true;
}
mServer->close();
return false;
}
void CS::Editor::connectToIPCServer()
{
mClientSocket = new QLocalSocket(this);
mClientSocket->connectToServer(mIpcServerName);
mClientSocket->close();
}
int CS::Editor::run()
{
mStartup.show();
QApplication::setQuitOnLastWindowClosed (true);
return QApplication::exec();
}

66
apps/opencs/editor.hpp Normal file
View file

@ -0,0 +1,66 @@
#ifndef CS_EDITOR_H
#define CS_EDITOR_H
#include <QObject>
#include <QString>
#include <QLocalServer>
#include <QLocalSocket>
#ifndef Q_MOC_RUN
#include <components/files/configurationmanager.hpp>
#endif
#include "model/doc/documentmanager.hpp"
#include "view/doc/viewmanager.hpp"
#include "view/doc/startup.hpp"
#include "view/doc/filedialog.hpp"
#include "model/settings/usersettings.hpp"
namespace CS
{
class Editor : public QObject
{
Q_OBJECT
CSMSettings::UserSettings mUserSettings;
CSMDoc::DocumentManager mDocumentManager;
CSVDoc::ViewManager mViewManager;
CSVDoc::StartupDialogue mStartup;
FileDialog mFileDialog;
Files::ConfigurationManager mCfgMgr;
void setupDataFiles();
// not implemented
Editor (const Editor&);
Editor& operator= (const Editor&);
public:
Editor();
bool makeIPCServer();
void connectToIPCServer();
int run();
///< \return error status
private slots:
void createDocument();
void loadDocument();
void openFiles();
void createNewFile();
void showStartup();
private:
QString mIpcServerName;
QLocalServer *mServer;
QLocalSocket *mClientSocket;
};
}
#endif

49
apps/opencs/main.cpp Normal file
View file

@ -0,0 +1,49 @@
#include "editor.hpp"
#include <exception>
#include <iostream>
#include <QApplication>
#include <QIcon>
class Application : public QApplication
{
private:
bool notify (QObject *receiver, QEvent *event)
{
try
{
return QApplication::notify (receiver, event);
}
catch (const std::exception& exception)
{
std::cerr << "An exception has been caught: " << exception.what() << std::endl;
}
return false;
}
public:
Application (int& argc, char *argv[]) : QApplication (argc, argv) {}
};
int main(int argc, char *argv[])
{
Q_INIT_RESOURCE (resources);
Application mApplication (argc, argv);
mApplication.setWindowIcon (QIcon (":./opencs.png"));
CS::Editor editor;
if(!editor.makeIPCServer())
{
editor.connectToIPCServer();
return 0;
}
return editor.run();
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,111 @@
#ifndef CSM_DOC_DOCUMENT_H
#define CSM_DOC_DOCUMENT_H
#include <string>
#include <boost/filesystem/path.hpp>
#include <QUndoStack>
#include <QObject>
#include <QTimer>
#include "../world/data.hpp"
#include "../tools/tools.hpp"
#include "state.hpp"
class QAbstractItemModel;
namespace ESM
{
struct GameSetting;
struct Global;
}
namespace CSMDoc
{
class Document : public QObject
{
Q_OBJECT
private:
std::string mName; ///< \todo replace name with ESX list
CSMWorld::Data mData;
CSMTools::Tools mTools;
// It is important that the undo stack is declared last, because on desctruction it fires a signal, that is connected to a slot, that is
// using other member variables. Unfortunately this connection is cut only in the QObject destructor, which is way too late.
QUndoStack mUndoStack;
int mSaveCount; ///< dummy implementation -> remove when proper save is implemented.
QTimer mSaveTimer; ///< dummy implementation -> remove when proper save is implemented.
// not implemented
Document (const Document&);
Document& operator= (const Document&);
void load (const std::vector<boost::filesystem::path>::const_iterator& begin,
const std::vector<boost::filesystem::path>::const_iterator& end, bool lastAsModified);
///< \param lastAsModified Store the last file in Modified instead of merging it into Base.
void createBase();
void addGmsts();
void addOptionalGmsts();
void addOptionalGlobals();
void addOptionalGmst (const ESM::GameSetting& gmst);
void addOptionalGlobal (const ESM::Global& global);
public:
Document (const std::vector<boost::filesystem::path>& files, bool new_);
~Document();
QUndoStack& getUndoStack();
int getState() const;
const std::string& getName() const;
///< \todo replace with ESX list
void save();
CSMWorld::UniversalId verify();
void abortOperation (int type);
const CSMWorld::Data& getData() const;
CSMWorld::Data& getData();
CSMTools::ReportModel *getReport (const CSMWorld::UniversalId& id);
///< The ownership of the returned report is not transferred.
signals:
void stateChanged (int state, CSMDoc::Document *document);
void progress (int current, int max, int type, int threads, CSMDoc::Document *document);
private slots:
void modificationStateChanged (bool clean);
void operationDone (int type);
void saving();
///< dummy implementation -> remove when proper save is implemented.
public slots:
void progress (int current, int max, int type);
};
}
#endif

View file

@ -0,0 +1,38 @@
#include "documentmanager.hpp"
#include <algorithm>
#include <stdexcept>
#include "document.hpp"
CSMDoc::DocumentManager::DocumentManager() {}
CSMDoc::DocumentManager::~DocumentManager()
{
for (std::vector<Document *>::iterator iter (mDocuments.begin()); iter!=mDocuments.end(); ++iter)
delete *iter;
}
CSMDoc::Document *CSMDoc::DocumentManager::addDocument (const std::vector<boost::filesystem::path>& files,
bool new_)
{
Document *document = new Document (files, new_);
mDocuments.push_back (document);
return document;
}
bool CSMDoc::DocumentManager::removeDocument (Document *document)
{
std::vector<Document *>::iterator iter = std::find (mDocuments.begin(), mDocuments.end(), document);
if (iter==mDocuments.end())
throw std::runtime_error ("removing invalid document");
mDocuments.erase (iter);
delete document;
return mDocuments.empty();
}

View file

@ -0,0 +1,37 @@
#ifndef CSM_DOC_DOCUMENTMGR_H
#define CSM_DOC_DOCUMENTMGR_H
#include <vector>
#include <string>
#include <boost/filesystem/path.hpp>
namespace CSMDoc
{
class Document;
class DocumentManager
{
std::vector<Document *> mDocuments;
DocumentManager (const DocumentManager&);
DocumentManager& operator= (const DocumentManager&);
public:
DocumentManager();
~DocumentManager();
Document *addDocument (const std::vector<boost::filesystem::path>& files, bool new_);
///< The ownership of the returned document is not transferred to the caller.
///
/// \param new_ Do not load the last content file in \a files and instead create in an
/// appropriate way.
bool removeDocument (Document *document);
///< \return last document removed?
};
}
#endif

View file

@ -0,0 +1,19 @@
#ifndef CSM_DOC_STATE_H
#define CSM_DOC_STATE_H
namespace CSMDoc
{
enum State
{
State_Modified = 1,
State_Locked = 2,
State_Operation = 4,
State_Saving = 8,
State_Verifying = 16,
State_Compiling = 32, // not implemented yet
State_Searching = 64 // not implemented yet
};
}
#endif

View file

@ -0,0 +1,20 @@
#include "andnode.hpp"
#include <sstream>
CSMFilter::AndNode::AndNode (const std::vector<boost::shared_ptr<Node> >& nodes)
: NAryNode (nodes, "and")
{}
bool CSMFilter::AndNode::test (const CSMWorld::IdTable& table, int row,
const std::map<int, int>& columns) const
{
int size = getSize();
for (int i=0; i<size; ++i)
if (!(*this)[i].test (table, row, columns))
return false;
return true;
}

View file

@ -0,0 +1,23 @@
#ifndef CSM_FILTER_ANDNODE_H
#define CSM_FILTER_ANDNODE_H
#include "narynode.hpp"
namespace CSMFilter
{
class AndNode : public NAryNode
{
bool mAnd;
public:
AndNode (const std::vector<boost::shared_ptr<Node> >& nodes);
virtual bool test (const CSMWorld::IdTable& table, int row,
const std::map<int, int>& columns) const;
///< \return Can the specified table row pass through to filter?
/// \param columns column ID to column index mapping
};
}
#endif

View file

@ -0,0 +1,15 @@
#include "booleannode.hpp"
CSMFilter::BooleanNode::BooleanNode (bool true_) : mTrue (true_) {}
bool CSMFilter::BooleanNode::test (const CSMWorld::IdTable& table, int row,
const std::map<int, int>& columns) const
{
return mTrue;
}
std::string CSMFilter::BooleanNode::toString (bool numericColumns) const
{
return mTrue ? "true" : "false";
}

View file

@ -0,0 +1,29 @@
#ifndef CSM_FILTER_BOOLEANNODE_H
#define CSM_FILTER_BOOLEANNODE_H
#include "leafnode.hpp"
namespace CSMFilter
{
class BooleanNode : public LeafNode
{
bool mTrue;
public:
BooleanNode (bool true_);
virtual bool test (const CSMWorld::IdTable& table, int row,
const std::map<int, int>& columns) const;
///< \return Can the specified table row pass through to filter?
/// \param columns column ID to column index mapping
virtual std::string toString (bool numericColumns) const;
///< Return a string that represents this node.
///
/// \param numericColumns Use numeric IDs instead of string to represent columns.
};
}
#endif

View file

@ -0,0 +1,25 @@
#ifndef CSM_FILTER_FILTER_H
#define CSM_FILTER_FILTER_H
#include <vector>
#include <string>
#include <components/esm/filter.hpp>
namespace CSMFilter
{
/// \brief Wrapper for Filter record
struct Filter : public ESM::Filter
{
enum Scope
{
Scope_Project = 0, // per project
Scope_Session = 1, // exists only for one editing session; not saved
Scope_Content = 2 // embedded in the edited content file
};
Scope mScope;
};
}
#endif

View file

@ -0,0 +1,8 @@
#include "leafnode.hpp"
std::vector<int> CSMFilter::LeafNode::getReferencedColumns() const
{
return std::vector<int>();
}

View file

@ -0,0 +1,20 @@
#ifndef CSM_FILTER_LEAFNODE_H
#define CSM_FILTER_LEAFNODE_H
#include <memory>
#include "node.hpp"
namespace CSMFilter
{
class LeafNode : public Node
{
public:
virtual std::vector<int> getReferencedColumns() const;
///< Return a list of the IDs of the columns referenced by this node. The column mapping
/// passed into test as columns must contain all columns listed here.
};
}
#endif

View file

@ -0,0 +1,60 @@
#include "narynode.hpp"
#include <sstream>
CSMFilter::NAryNode::NAryNode (const std::vector<boost::shared_ptr<Node> >& nodes,
const std::string& name)
: mNodes (nodes), mName (name)
{}
int CSMFilter::NAryNode::getSize() const
{
return mNodes.size();
}
const CSMFilter::Node& CSMFilter::NAryNode::operator[] (int index) const
{
return *mNodes.at (index);
}
std::vector<int> CSMFilter::NAryNode::getReferencedColumns() const
{
std::vector<int> columns;
for (std::vector<boost::shared_ptr<Node> >::const_iterator iter (mNodes.begin());
iter!=mNodes.end(); ++iter)
{
std::vector<int> columns2 = (*iter)->getReferencedColumns();
columns.insert (columns.end(), columns2.begin(), columns2.end());
}
return columns;
}
std::string CSMFilter::NAryNode::toString (bool numericColumns) const
{
std::ostringstream stream;
stream << mName << " (";
bool first = true;
int size = getSize();
for (int i=0; i<size; ++i)
{
if (first)
first = false;
else
stream << ", ";
stream << (*this)[i].toString (numericColumns);
}
stream << ")";
return stream.str();
}

View file

@ -0,0 +1,37 @@
#ifndef CSM_FILTER_NARYNODE_H
#define CSM_FILTER_NARYNODE_H
#include <vector>
#include <string>
#include <boost/shared_ptr.hpp>
#include "node.hpp"
namespace CSMFilter
{
class NAryNode : public Node
{
std::vector<boost::shared_ptr<Node> > mNodes;
std::string mName;
public:
NAryNode (const std::vector<boost::shared_ptr<Node> >& nodes, const std::string& name);
int getSize() const;
const Node& operator[] (int index) const;
virtual std::vector<int> getReferencedColumns() const;
///< Return a list of the IDs of the columns referenced by this node. The column mapping
/// passed into test as columns must contain all columns listed here.
virtual std::string toString (bool numericColumns) const;
///< Return a string that represents this node.
///
/// \param numericColumns Use numeric IDs instead of string to represent columns.
};
}
#endif

View file

@ -0,0 +1,6 @@
#include "node.hpp"
CSMFilter::Node::Node() {}
CSMFilter::Node::~Node() {}

View file

@ -0,0 +1,53 @@
#ifndef CSM_FILTER_NODE_H
#define CSM_FILTER_NODE_H
#include <string>
#include <map>
#include <vector>
#include <boost/shared_ptr.hpp>
#include <QMetaType>
namespace CSMWorld
{
class IdTable;
}
namespace CSMFilter
{
/// \brief Root class for the filter node hierarchy
///
/// \note When the function documentation for this class mentions "this node", this should be
/// interpreted as "the node and all its children".
class Node
{
// not implemented
Node (const Node&);
Node& operator= (const Node&);
public:
Node();
virtual ~Node();
virtual bool test (const CSMWorld::IdTable& table, int row,
const std::map<int, int>& columns) const = 0;
///< \return Can the specified table row pass through to filter?
/// \param columns column ID to column index mapping
virtual std::vector<int> getReferencedColumns() const = 0;
///< Return a list of the IDs of the columns referenced by this node. The column mapping
/// passed into test as columns must contain all columns listed here.
virtual std::string toString (bool numericColumns) const = 0;
///< Return a string that represents this node.
///
/// \param numericColumns Use numeric IDs instead of string to represent columns.
};
}
Q_DECLARE_METATYPE (boost::shared_ptr<CSMFilter::Node>)
#endif

View file

@ -0,0 +1,10 @@
#include "notnode.hpp"
CSMFilter::NotNode::NotNode (boost::shared_ptr<Node> child) : UnaryNode (child, "not") {}
bool CSMFilter::NotNode::test (const CSMWorld::IdTable& table, int row,
const std::map<int, int>& columns) const
{
return !getChild().test (table, row, columns);
}

View file

@ -0,0 +1,21 @@
#ifndef CSM_FILTER_NOTNODE_H
#define CSM_FILTER_NOTNODE_H
#include "unarynode.hpp"
namespace CSMFilter
{
class NotNode : public UnaryNode
{
public:
NotNode (boost::shared_ptr<Node> child);
virtual bool test (const CSMWorld::IdTable& table, int row,
const std::map<int, int>& columns) const;
///< \return Can the specified table row pass through to filter?
/// \param columns column ID to column index mapping
};
}
#endif

View file

@ -0,0 +1,20 @@
#include "ornode.hpp"
#include <sstream>
CSMFilter::OrNode::OrNode (const std::vector<boost::shared_ptr<Node> >& nodes)
: NAryNode (nodes, "or")
{}
bool CSMFilter::OrNode::test (const CSMWorld::IdTable& table, int row,
const std::map<int, int>& columns) const
{
int size = getSize();
for (int i=0; i<size; ++i)
if ((*this)[i].test (table, row, columns))
return true;
return false;
}

View file

@ -0,0 +1,23 @@
#ifndef CSM_FILTER_ORNODE_H
#define CSM_FILTER_ORNODE_H
#include "narynode.hpp"
namespace CSMFilter
{
class OrNode : public NAryNode
{
bool mAnd;
public:
OrNode (const std::vector<boost::shared_ptr<Node> >& nodes);
virtual bool test (const CSMWorld::IdTable& table, int row,
const std::map<int, int>& columns) const;
///< \return Can the specified table row pass through to filter?
/// \param columns column ID to column index mapping
};
}
#endif

View file

@ -0,0 +1,595 @@
#include "parser.hpp"
#include <cctype>
#include <stdexcept>
#include <sstream>
#include <components/misc/stringops.hpp>
#include "../world/columns.hpp"
#include "../world/data.hpp"
#include "../world/idcollection.hpp"
#include "booleannode.hpp"
#include "ornode.hpp"
#include "andnode.hpp"
#include "notnode.hpp"
#include "textnode.hpp"
#include "valuenode.hpp"
namespace CSMFilter
{
struct Token
{
enum Type
{
Type_EOS,
Type_None,
Type_String,
Type_Number,
Type_Open,
Type_Close,
Type_OpenSquare,
Type_CloseSquare,
Type_Comma,
Type_OneShot,
Type_Keyword_True, ///< \attention Keyword enums must be arranged continuously.
Type_Keyword_False,
Type_Keyword_And,
Type_Keyword_Or,
Type_Keyword_Not,
Type_Keyword_Text,
Type_Keyword_Value
};
Type mType;
std::string mString;
double mNumber;
Token (Type type = Type_None);
Token (const std::string& string);
Token (double number);
operator bool() const;
};
Token::Token (Type type) : mType (type) {}
Token::Token (const std::string& string) : mType (Type_String), mString (string) {}
Token::Token (double number) : mType (Type_Number), mNumber (number) {}
Token::operator bool() const
{
return mType!=Type_None;
}
bool operator== (const Token& left, const Token& right)
{
if (left.mType!=right.mType)
return false;
switch (left.mType)
{
case Token::Type_String: return left.mString==right.mString;
case Token::Type_Number: return left.mNumber==right.mNumber;
default: return true;
}
}
}
CSMFilter::Token CSMFilter::Parser::getStringToken()
{
std::string string;
int size = static_cast<int> (mInput.size());
for (; mIndex<size; ++mIndex)
{
char c = mInput[mIndex];
if (std::isalpha (c) || c==':' || c=='_' || (!string.empty() && std::isdigit (c)) || c=='"' ||
(!string.empty() && string[0]=='"'))
string += c;
else
break;
if (c=='"' && string.size()>1)
{
++mIndex;
break;
}
};
if (!string.empty())
{
if (string[0]=='"' && (string[string.size()-1]!='"' || string.size()<2) )
{
error();
return Token (Token::Type_None);
}
if (string[0]!='"' && string[string.size()-1]=='"')
{
error();
return Token (Token::Type_None);
}
if (string[0]=='"')
string = string.substr (1, string.size()-2);
}
return checkKeywords (string);
}
CSMFilter::Token CSMFilter::Parser::getNumberToken()
{
std::string string;
int size = static_cast<int> (mInput.size());
bool hasDecimalPoint = false;
bool hasDigit = false;
for (; mIndex<size; ++mIndex)
{
char c = mInput[mIndex];
if (std::isdigit (c))
{
string += c;
hasDigit = true;
}
else if (c=='.' && !hasDecimalPoint)
{
string += c;
hasDecimalPoint = true;
}
else if (string.empty() && c=='-')
string += c;
else
break;
}
if (!hasDigit)
{
error();
return Token (Token::Type_None);
}
float value;
std::istringstream stream (string.c_str());
stream >> value;
return value;
}
CSMFilter::Token CSMFilter::Parser::checkKeywords (const Token& token)
{
static const char *sKeywords[] =
{
"true", "false",
"and", "or", "not",
"string", "value",
0
};
std::string string = Misc::StringUtils::lowerCase (token.mString);
for (int i=0; sKeywords[i]; ++i)
if (sKeywords[i]==string || (string.size()==1 && sKeywords[i][0]==string[0]))
return Token (static_cast<Token::Type> (i+Token::Type_Keyword_True));
return token;
}
CSMFilter::Token CSMFilter::Parser::getNextToken()
{
int size = static_cast<int> (mInput.size());
char c = 0;
for (; mIndex<size; ++mIndex)
{
c = mInput[mIndex];
if (c!=' ')
break;
}
if (mIndex>=size)
return Token (Token::Type_EOS);
switch (c)
{
case '(': ++mIndex; return Token (Token::Type_Open);
case ')': ++mIndex; return Token (Token::Type_Close);
case '[': ++mIndex; return Token (Token::Type_OpenSquare);
case ']': ++mIndex; return Token (Token::Type_CloseSquare);
case ',': ++mIndex; return Token (Token::Type_Comma);
case '!': ++mIndex; return Token (Token::Type_OneShot);
}
if (c=='"' || c=='_' || std::isalpha (c) || c==':')
return getStringToken();
if (c=='-' || c=='.' || std::isdigit (c))
return getNumberToken();
error();
return Token (Token::Type_None);
}
boost::shared_ptr<CSMFilter::Node> CSMFilter::Parser::parseImp (bool allowEmpty, bool ignoreOneShot)
{
if (Token token = getNextToken())
{
if (token==Token (Token::Type_OneShot))
token = getNextToken();
if (token)
switch (token.mType)
{
case Token::Type_Keyword_True:
return boost::shared_ptr<CSMFilter::Node> (new BooleanNode (true));
case Token::Type_Keyword_False:
return boost::shared_ptr<CSMFilter::Node> (new BooleanNode (false));
case Token::Type_Keyword_And:
case Token::Type_Keyword_Or:
return parseNAry (token);
case Token::Type_Keyword_Not:
{
boost::shared_ptr<CSMFilter::Node> node = parseImp();
if (mError)
return boost::shared_ptr<Node>();
return boost::shared_ptr<CSMFilter::Node> (new NotNode (node));
}
case Token::Type_Keyword_Text:
return parseText();
case Token::Type_Keyword_Value:
return parseValue();
case Token::Type_EOS:
if (!allowEmpty)
error();
return boost::shared_ptr<Node>();
default:
error();
}
}
return boost::shared_ptr<Node>();
}
boost::shared_ptr<CSMFilter::Node> CSMFilter::Parser::parseNAry (const Token& keyword)
{
std::vector<boost::shared_ptr<Node> > nodes;
Token token = getNextToken();
if (token.mType!=Token::Type_Open)
{
error();
return boost::shared_ptr<Node>();
}
for (;;)
{
boost::shared_ptr<Node> node = parseImp();
if (mError)
return boost::shared_ptr<Node>();
nodes.push_back (node);
Token token = getNextToken();
if (!token || (token.mType!=Token::Type_Close && token.mType!=Token::Type_Comma))
{
error();
return boost::shared_ptr<Node>();
}
if (token.mType==Token::Type_Close)
break;
}
if (nodes.empty())
{
error();
return boost::shared_ptr<Node>();
}
switch (keyword.mType)
{
case Token::Type_Keyword_And: return boost::shared_ptr<CSMFilter::Node> (new AndNode (nodes));
case Token::Type_Keyword_Or: return boost::shared_ptr<CSMFilter::Node> (new OrNode (nodes));
default: error(); return boost::shared_ptr<Node>();
}
}
boost::shared_ptr<CSMFilter::Node> CSMFilter::Parser::parseText()
{
Token token = getNextToken();
if (token.mType!=Token::Type_Open)
{
error();
return boost::shared_ptr<Node>();
}
token = getNextToken();
if (!token)
return boost::shared_ptr<Node>();
// parse column ID
int columnId = -1;
if (token.mType==Token::Type_Number)
{
if (static_cast<int> (token.mNumber)==token.mNumber)
columnId = static_cast<int> (token.mNumber);
}
else if (token.mType==Token::Type_String)
{
columnId = CSMWorld::Columns::getId (token.mString);
}
if (columnId<0)
{
error();
return boost::shared_ptr<Node>();
}
token = getNextToken();
if (token.mType!=Token::Type_Comma)
{
error();
return boost::shared_ptr<Node>();
}
// parse text pattern
token = getNextToken();
if (token.mType!=Token::Type_String)
{
error();
return boost::shared_ptr<Node>();
}
std::string text = token.mString;
token = getNextToken();
if (token.mType!=Token::Type_Close)
{
error();
return boost::shared_ptr<Node>();
}
return boost::shared_ptr<Node> (new TextNode (columnId, text));
}
boost::shared_ptr<CSMFilter::Node> CSMFilter::Parser::parseValue()
{
Token token = getNextToken();
if (token.mType!=Token::Type_Open)
{
error();
return boost::shared_ptr<Node>();
}
token = getNextToken();
if (!token)
return boost::shared_ptr<Node>();
// parse column ID
int columnId = -1;
if (token.mType==Token::Type_Number)
{
if (static_cast<int> (token.mNumber)==token.mNumber)
columnId = static_cast<int> (token.mNumber);
}
else if (token.mType==Token::Type_String)
{
columnId = CSMWorld::Columns::getId (token.mString);
}
if (columnId<0)
{
error();
return boost::shared_ptr<Node>();
}
token = getNextToken();
if (token.mType!=Token::Type_Comma)
{
error();
return boost::shared_ptr<Node>();
}
// parse value
double lower = 0;
double upper = 0;
bool min = false;
bool max = false;
token = getNextToken();
if (token.mType==Token::Type_Number)
{
// single value
min = max = true;
lower = upper = token.mNumber;
}
else
{
// interval
if (token.mType==Token::Type_OpenSquare)
min = true;
else if (token.mType!=Token::Type_CloseSquare && token.mType!=Token::Type_Open)
{
error();
return boost::shared_ptr<Node>();
}
token = getNextToken();
if (token.mType!=Token::Type_Number)
{
error();
return boost::shared_ptr<Node>();
}
lower = token.mNumber;
token = getNextToken();
if (token.mType!=Token::Type_Comma)
{
error();
return boost::shared_ptr<Node>();
}
token = getNextToken();
if (token.mType!=Token::Type_Number)
{
error();
return boost::shared_ptr<Node>();
}
upper = token.mNumber;
token = getNextToken();
if (token.mType==Token::Type_CloseSquare)
max = true;
else if (token.mType!=Token::Type_OpenSquare && token.mType!=Token::Type_Close)
{
error();
return boost::shared_ptr<Node>();
}
}
token = getNextToken();
if (token.mType!=Token::Type_Close)
{
error();
return boost::shared_ptr<Node>();
}
return boost::shared_ptr<Node> (new ValueNode (columnId, lower, upper, min, max));
}
void CSMFilter::Parser::error()
{
mError = true;
}
CSMFilter::Parser::Parser (const CSMWorld::Data& data)
: mIndex (0), mError (false), mData (data) {}
bool CSMFilter::Parser::parse (const std::string& filter, bool allowPredefined)
{
// reset
mFilter.reset();
mError = false;
mInput = filter;
mIndex = 0;
Token token;
if (allowPredefined)
token = getNextToken();
if (!allowPredefined || token==Token (Token::Type_OneShot))
{
boost::shared_ptr<Node> node = parseImp (true, token!=Token (Token::Type_OneShot));
if (mError)
return false;
if (getNextToken()!=Token (Token::Type_EOS))
{
error();
return false;
}
if (node)
mFilter = node;
else
{
// Empty filter string equals to filter "true".
mFilter.reset (new BooleanNode (true));
}
return true;
}
else if (token.mType==Token::Type_String && allowPredefined)
{
if (getNextToken()!=Token (Token::Type_EOS))
{
error();
return false;
}
int index = mData.getFilters().searchId (token.mString);
if (index==-1)
{
error();
return false;
}
const CSMWorld::Record<CSMFilter::Filter>& record = mData.getFilters().getRecord (index);
if (record.isDeleted())
{
error();
return false;
}
return parse (record.get().mFilter, false);
}
else
{
error();
return false;
}
}
boost::shared_ptr<CSMFilter::Node> CSMFilter::Parser::getFilter() const
{
if (mError)
throw std::logic_error ("No filter available");
return mFilter;
}

View file

@ -0,0 +1,59 @@
#ifndef CSM_FILTER_PARSER_H
#define CSM_FILTER_PARSER_H
#include <boost/shared_ptr.hpp>
#include "node.hpp"
namespace CSMWorld
{
class Data;
}
namespace CSMFilter
{
struct Token;
class Parser
{
boost::shared_ptr<Node> mFilter;
std::string mInput;
int mIndex;
bool mError;
const CSMWorld::Data& mData;
Token getStringToken();
Token getNumberToken();
Token getNextToken();
Token checkKeywords (const Token& token);
///< Turn string token into keyword token, if possible.
boost::shared_ptr<Node> parseImp (bool allowEmpty = false, bool ignoreOneShot = false);
///< Will return a null-pointer, if there is nothing more to parse.
boost::shared_ptr<Node> parseNAry (const Token& keyword);
boost::shared_ptr<Node> parseText();
boost::shared_ptr<Node> parseValue();
void error();
public:
Parser (const CSMWorld::Data& data);
bool parse (const std::string& filter, bool allowPredefined = true);
///< Discards any previous calls to parse
///
/// \return Success?
boost::shared_ptr<Node> getFilter() const;
///< Throws an exception if the last call to parse did not return true.
};
}
#endif

View file

@ -0,0 +1,62 @@
#include "textnode.hpp"
#include <sstream>
#include <stdexcept>
#include <QRegExp>
#include "../world/columns.hpp"
#include "../world/idtable.hpp"
CSMFilter::TextNode::TextNode (int columnId, const std::string& text)
: mColumnId (columnId), mText (text)
{}
bool CSMFilter::TextNode::test (const CSMWorld::IdTable& table, int row,
const std::map<int, int>& columns) const
{
const std::map<int, int>::const_iterator iter = columns.find (mColumnId);
if (iter==columns.end())
throw std::logic_error ("invalid column in text node test");
if (iter->second==-1)
return true;
QModelIndex index = table.index (row, iter->second);
QVariant data = table.data (index);
if (data.type()!=QVariant::String)
return false;
/// \todo make pattern syntax configurable
QRegExp regExp (QString::fromUtf8 (mText.c_str()), Qt::CaseInsensitive);
return regExp.exactMatch (data.toString());
}
std::vector<int> CSMFilter::TextNode::getReferencedColumns() const
{
return std::vector<int> (1, mColumnId);
}
std::string CSMFilter::TextNode::toString (bool numericColumns) const
{
std::ostringstream stream;
stream << "text (";
if (numericColumns)
stream << mColumnId;
else
stream
<< "\""
<< CSMWorld::Columns::getName (static_cast<CSMWorld::Columns::ColumnId> (mColumnId))
<< "\"";
stream << ", \"" << mText << "\")";
return stream.str();
}

View file

@ -0,0 +1,33 @@
#ifndef CSM_FILTER_TEXTNODE_H
#define CSM_FILTER_TEXTNODE_H
#include "leafnode.hpp"
namespace CSMFilter
{
class TextNode : public LeafNode
{
int mColumnId;
std::string mText;
public:
TextNode (int columnId, const std::string& text);
virtual bool test (const CSMWorld::IdTable& table, int row,
const std::map<int, int>& columns) const;
///< \return Can the specified table row pass through to filter?
/// \param columns column ID to column index mapping
virtual std::vector<int> getReferencedColumns() const;
///< Return a list of the IDs of the columns referenced by this node. The column mapping
/// passed into test as columns must contain all columns listed here.
virtual std::string toString (bool numericColumns) const;
///< Return a string that represents this node.
///
/// \param numericColumns Use numeric IDs instead of string to represent columns.
};
}
#endif

View file

@ -0,0 +1,26 @@
#include "unarynode.hpp"
CSMFilter::UnaryNode::UnaryNode (boost::shared_ptr<Node> child, const std::string& name)
: mChild (child), mName (name)
{}
const CSMFilter::Node& CSMFilter::UnaryNode::getChild() const
{
return *mChild;
}
CSMFilter::Node& CSMFilter::UnaryNode::getChild()
{
return *mChild;
}
std::vector<int> CSMFilter::UnaryNode::getReferencedColumns() const
{
return mChild->getReferencedColumns();
}
std::string CSMFilter::UnaryNode::toString (bool numericColumns) const
{
return mName + " " + mChild->toString (numericColumns);
}

View file

@ -0,0 +1,34 @@
#ifndef CSM_FILTER_UNARYNODE_H
#define CSM_FILTER_UNARYNODE_H
#include <boost/shared_ptr.hpp>
#include "node.hpp"
namespace CSMFilter
{
class UnaryNode : public Node
{
boost::shared_ptr<Node> mChild;
std::string mName;
public:
UnaryNode (boost::shared_ptr<Node> child, const std::string& name);
const Node& getChild() const;
Node& getChild();
virtual std::vector<int> getReferencedColumns() const;
///< Return a list of the IDs of the columns referenced by this node. The column mapping
/// passed into test as columns must contain all columns listed here.
virtual std::string toString (bool numericColumns) const;
///< Return a string that represents this node.
///
/// \param numericColumns Use numeric IDs instead of string to represent columns.
};
}
#endif

View file

@ -0,0 +1,71 @@
#include "valuenode.hpp"
#include <sstream>
#include <stdexcept>
#include "../world/columns.hpp"
#include "../world/idtable.hpp"
CSMFilter::ValueNode::ValueNode (int columnId,
double lower, double upper, bool min, bool max)
: mColumnId (columnId), mLower (lower), mUpper (upper), mMin (min), mMax (max)
{}
bool CSMFilter::ValueNode::test (const CSMWorld::IdTable& table, int row,
const std::map<int, int>& columns) const
{
const std::map<int, int>::const_iterator iter = columns.find (mColumnId);
if (iter==columns.end())
throw std::logic_error ("invalid column in test value test");
if (iter->second==-1)
return true;
QModelIndex index = table.index (row, iter->second);
QVariant data = table.data (index);
if (data.type()!=QVariant::Double && data.type()!=QVariant::Bool && data.type()!=QVariant::Int &&
data.type()!=QVariant::UInt)
return false;
double value = data.toDouble();
if (mLower==mUpper && mMin && mMax)
return value==mLower;
return (mMin ? value>=mLower : value>mLower) && (mMax ? value<=mUpper : value<mUpper);
}
std::vector<int> CSMFilter::ValueNode::getReferencedColumns() const
{
return std::vector<int> (1, mColumnId);
}
std::string CSMFilter::ValueNode::toString (bool numericColumns) const
{
std::ostringstream stream;
stream << "value (";
if (numericColumns)
stream << mColumnId;
else
stream
<< "\""
<< CSMWorld::Columns::getName (static_cast<CSMWorld::Columns::ColumnId> (mColumnId))
<< "\"";
stream << ", \"";
if (mLower==mUpper && mMin && mMax)
stream << mLower;
else
stream << (mMin ? "[" : "(") << mLower << ", " << mUpper << (mMax ? "]" : ")");
stream << ")";
return stream.str();
}

View file

@ -0,0 +1,37 @@
#ifndef CSM_FILTER_VALUENODE_H
#define CSM_FILTER_VALUENODE_H
#include "leafnode.hpp"
namespace CSMFilter
{
class ValueNode : public LeafNode
{
int mColumnId;
std::string mText;
double mLower;
double mUpper;
bool mMin;
bool mMax;
public:
ValueNode (int columnId, double lower, double upper, bool min, bool max);
virtual bool test (const CSMWorld::IdTable& table, int row,
const std::map<int, int>& columns) const;
///< \return Can the specified table row pass through to filter?
/// \param columns column ID to column index mapping
virtual std::vector<int> getReferencedColumns() const;
///< Return a list of the IDs of the columns referenced by this node. The column mapping
/// passed into test as columns must contain all columns listed here.
virtual std::string toString (bool numericColumns) const;
///< Return a string that represents this node.
///
/// \param numericColumns Use numeric IDs instead of string to represent columns.
};
}
#endif

View file

@ -0,0 +1,82 @@
#include "settingcontainer.hpp"
#include <QStringList>
CSMSettings::SettingContainer::SettingContainer(QObject *parent) :
QObject(parent), mValue (0), mValues (0)
{
}
CSMSettings::SettingContainer::SettingContainer(const QString &value, QObject *parent) :
QObject(parent), mValue (new QString (value)), mValues (0)
{
}
void CSMSettings::SettingContainer::insert (const QString &value)
{
if (mValue)
{
mValues = new QStringList;
mValues->push_back (*mValue);
mValues->push_back (value);
delete mValue;
mValue = 0;
}
else
{
delete mValue;
mValue = new QString (value);
}
}
void CSMSettings::SettingContainer::update (const QString &value, int index)
{
if (isEmpty())
mValue = new QString(value);
else if (mValue)
*mValue = value;
else if (mValues)
mValues->replace(index, value);
}
QString CSMSettings::SettingContainer::getValue (int index) const
{
QString retVal("");
//if mValue is valid, it's a single-value property.
//ignore the index and return the value
if (mValue)
retVal = *mValue;
//otherwise, if it's a multivalued property
//return the appropriate value at the index
else if (mValues)
{
if (index == -1)
retVal = mValues->at(0);
else if (index < mValues->size())
retVal = mValues->at(index);
}
return retVal;
}
int CSMSettings::SettingContainer::count () const
{
int retVal = 0;
if (!isEmpty())
{
if (mValues)
retVal = mValues->size();
else
retVal = 1;
}
return retVal;
}

View file

@ -0,0 +1,47 @@
#ifndef SETTINGCONTAINER_HPP
#define SETTINGCONTAINER_HPP
#include <QObject>
class QStringList;
namespace CSMSettings
{
class SettingContainer : public QObject
{
Q_OBJECT
QString *mValue;
QStringList *mValues;
public:
explicit SettingContainer (QObject *parent = 0);
explicit SettingContainer (const QString &value, QObject *parent = 0);
/// add a value to the container
/// multiple values supported
void insert (const QString &value);
/// update an existing value
/// index specifies multiple values
void update (const QString &value, int index = 0);
/// return value at specified index
QString getValue (int index = -1) const;
/// retrieve list of all values
inline QStringList *getValues() const { return mValues; }
/// return size of list
int count() const;
/// test for empty container
/// useful for default-constructed containers returned by QMap when invalid key is passed
inline bool isEmpty() const { return (!mValue && !mValues); }
inline bool isMultiValue() const { return (mValues); }
};
}
#endif // SETTINGCONTAINER_HPP

View file

@ -0,0 +1,104 @@
#include "settingsitem.hpp"
#include <QStringList>
bool CSMSettings::SettingsItem::updateItem (const QStringList *values)
{
QStringList::ConstIterator it = values->begin();
//if the item is not multivalued,
//save the last value passed in the container
if (!mIsMultiValue)
{
it = values->end();
it--;
}
bool isValid = true;
QString value ("");
for (; it != values->end(); ++it)
{
value = *it;
isValid = validate(value);
//skip only the invalid values
if (!isValid)
continue;
insert(value);
}
return isValid;
}
bool CSMSettings::SettingsItem::updateItem (const QString &value)
{
//takes a value or a SettingsContainer and updates itself accordingly
//after validating the data against it's own definition
QString newValue = value;
if (!validate (newValue))
newValue = mDefaultValue;
bool success = (getValue() != newValue);
if (success)
{
if (mIsMultiValue)
insert (newValue);
else
update (newValue);
}
return success;
}
bool CSMSettings::SettingsItem::updateItem(int valueListIndex)
{
bool success = false;
if (mValueList)
{
if (mValueList->size() > valueListIndex)
success = updateItem (mValueList->at(valueListIndex));
}
return success;
}
bool CSMSettings::SettingsItem::validate (const QString &value)
{
//if there is no value list or value pair, there is no validation to do
bool isValid = !(!mValueList->isEmpty() || mValuePair);
if (!isValid && !mValueList->isEmpty())
{
for (QStringList::Iterator it = mValueList->begin(); it != mValueList->end(); ++it)
// foreach (QString listItem, *mValueList)
{
isValid = (value == *it);
if (isValid)
break;
}
}
else if (!isValid && mValuePair)
{
int numVal = value.toInt();
isValid = (numVal > mValuePair->left.toInt() && numVal < mValuePair->right.toInt());
}
return isValid;
}
void CSMSettings::SettingsItem::setDefaultValue (const QString &value)
{
mDefaultValue = value;
update (value);
}
QString CSMSettings::SettingsItem::getDefaultValue() const
{
return mDefaultValue;
}

View file

@ -0,0 +1,63 @@
#ifndef SETTINGSITEM_HPP
#define SETTINGSITEM_HPP
#include <QObject>
#include "support.hpp"
#include "settingcontainer.hpp"
namespace CSMSettings
{
/// Represents a setting including metadata
/// (valid values, ranges, defaults, and multivalue status
class SettingsItem : public SettingContainer
{
QStringPair *mValuePair;
QStringList *mValueList;
bool mIsMultiValue;
QString mDefaultValue;
public:
explicit SettingsItem(QString name, bool isMultiValue,
const QString& defaultValue, QObject *parent = 0)
: SettingContainer(defaultValue, parent),
mIsMultiValue (isMultiValue), mValueList (0),
mValuePair (0), mDefaultValue (defaultValue)
{
QObject::setObjectName(name);
}
/// updateItem overloads for updating setting value
/// provided a list of values (multi-valued),
/// a specific value
/// or an index value corresponding to the mValueList
bool updateItem (const QStringList *values);
bool updateItem (const QString &value);
bool updateItem (int valueListIndex);
/// retrieve list of valid values for setting
inline QStringList *getValueList() { return mValueList; }
/// write list of valid values for setting
inline void setValueList (QStringList *valueList) { mValueList = valueList; }
/// valuePair used for spin boxes (max / min)
inline QStringPair *getValuePair() { return mValuePair; }
/// set value range (spinbox / integer use)
inline void setValuePair (QStringPair valuePair) { mValuePair = new QStringPair(valuePair); }
inline bool isMultivalue () { return mIsMultiValue; }
void setDefaultValue (const QString &value);
QString getDefaultValue () const;
private:
/// Verifies that the supplied value is one of the following:
/// 1. Within the limits of the value pair (min / max)
/// 2. One of the values indicated in the value list
bool validate (const QString &value);
};
}
#endif // SETTINGSITEM_HPP

View file

@ -0,0 +1 @@
#include "support.hpp"

View file

@ -0,0 +1,39 @@
#ifndef MODEL_SUPPORT_HPP
#define MODEL_SUPPORT_HPP
#include <QObject>
#include <QStringList>
class QLayout;
class QWidget;
class QListWidgetItem;
namespace CSMSettings
{
class SettingContainer;
typedef QList<SettingContainer *> SettingList;
typedef QMap<QString, SettingContainer *> SettingMap;
typedef QMap<QString, SettingMap *> SectionMap;
struct QStringPair
{
QStringPair(): left (""), right ("")
{}
QStringPair (const QString &leftValue, const QString &rightValue)
: left (leftValue), right(rightValue)
{}
QStringPair (const QStringPair &pair)
: left (pair.left), right (pair.right)
{}
QString left;
QString right;
bool isEmpty() const
{ return (left.isEmpty() && right.isEmpty()); }
};
}
#endif // MODEL_SUPPORT_HPP

View file

@ -0,0 +1,355 @@
#include "usersettings.hpp"
#include <QTextStream>
#include <QDir>
#include <QString>
#include <QRegExp>
#include <QMap>
#include <QMessageBox>
#include <QTextCodec>
#include <QFile>
#include <components/files/configurationmanager.hpp>
#include "settingcontainer.hpp"
#include <boost/version.hpp>
/**
* Workaround for problems with whitespaces in paths in older versions of Boost library
*/
#if (BOOST_VERSION <= 104600)
namespace boost
{
template<>
inline boost::filesystem::path lexical_cast<boost::filesystem::path, std::string>(const std::string& arg)
{
return boost::filesystem::path(arg);
}
} /* namespace boost */
#endif /* (BOOST_VERSION <= 104600) */
CSMSettings::UserSettings *CSMSettings::UserSettings::mUserSettingsInstance = 0;
CSMSettings::UserSettings::UserSettings()
{
assert(!mUserSettingsInstance);
mUserSettingsInstance = this;
mReadWriteMessage = QObject::tr("<br><b>Could not open or create file for writing</b><br><br> \
Please make sure you have the right permissions and try again.<br>");
mReadOnlyMessage = QObject::tr("<br><b>Could not open file for reading</b><br><br> \
Please make sure you have the right permissions and try again.<br>");
buildEditorSettingDefaults();
}
void CSMSettings::UserSettings::buildEditorSettingDefaults()
{
SettingContainer *windowHeight = new SettingContainer("768", this);
SettingContainer *windowWidth = new SettingContainer("1024", this);
SettingContainer *rsDelegate = new SettingContainer("Icon and Text", this);
SettingContainer *refIdTypeDelegate = new SettingContainer("Icon and Text", this);
windowHeight->setObjectName ("Height");
windowWidth->setObjectName ("Width");
rsDelegate->setObjectName ("Record Status Display");
refIdTypeDelegate->setObjectName ("Referenceable ID Type Display");
SettingMap *displayFormatMap = new SettingMap;
SettingMap *windowSizeMap = new SettingMap;
displayFormatMap->insert (rsDelegate->objectName(), rsDelegate );
displayFormatMap->insert (refIdTypeDelegate->objectName(), refIdTypeDelegate);
windowSizeMap->insert (windowWidth->objectName(), windowWidth );
windowSizeMap->insert (windowHeight->objectName(), windowHeight );
mEditorSettingDefaults.insert ("Display Format", displayFormatMap);
mEditorSettingDefaults.insert ("Window Size", windowSizeMap);
}
CSMSettings::UserSettings::~UserSettings()
{
mUserSettingsInstance = 0;
}
QTextStream *CSMSettings::UserSettings::openFileStream (const QString &filePath, bool isReadOnly) const
{
QIODevice::OpenMode openFlags = QIODevice::Text;
if (isReadOnly)
openFlags = QIODevice::ReadOnly | openFlags;
else
openFlags = QIODevice::ReadWrite | QIODevice::Truncate | openFlags;
QFile *file = new QFile(filePath);
QTextStream *stream = 0;
if (file->open(openFlags))
{
stream = new QTextStream(file);
stream->setCodec(QTextCodec::codecForName("UTF-8"));
}
return stream;
}
bool CSMSettings::UserSettings::writeSettings(QMap<QString, CSMSettings::SettingList *> &settings)
{
QTextStream *stream = openFileStream(mUserFilePath);
bool success = (stream);
if (success)
{
QList<QString> keyList = settings.keys();
foreach (QString key, keyList)
{
SettingList *sectionSettings = settings[key];
*stream << "[" << key << "]" << '\n';
foreach (SettingContainer *item, *sectionSettings)
*stream << item->objectName() << " = " << item->getValue() << '\n';
}
stream->device()->close();
delete stream;
stream = 0;
}
else
{
displayFileErrorMessage(mReadWriteMessage, false);
}
return (success);
}
const CSMSettings::SectionMap &CSMSettings::UserSettings::getSectionMap() const
{
return mSectionSettings;
}
const CSMSettings::SettingMap *CSMSettings::UserSettings::getSettings(const QString &sectionName) const
{
return getValidSettings(sectionName);
}
bool CSMSettings::UserSettings::loadFromFile(const QString &filePath)
{
if (filePath.isEmpty())
return false;
SectionMap loadedSettings;
QTextStream *stream = openFileStream (filePath, true);
bool success = (stream);
if (success)
{
//looks for a square bracket, "'\\["
//that has one or more "not nothing" in it, "([^]]+)"
//and is closed with a square bracket, "\\]"
QRegExp sectionRe("^\\[([^]]+)\\]");
//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("^([^=]+)\\s*=\\s*(.+)$");
CSMSettings::SettingMap *settings = 0;
QString section = "none";
while (!stream->atEnd())
{
QString line = stream->readLine().simplified();
if (line.isEmpty() || line.startsWith("#"))
continue;
//if a section is found, push it onto a new QStringList
//and push the QStringList onto
if (sectionRe.exactMatch(line))
{
//add the previous section's settings to the member map
if (settings)
loadedSettings.insert(section, settings);
//save new section and create a new list
section = sectionRe.cap(1);
settings = new SettingMap;
continue;
}
if (keyRe.indexIn(line) != -1)
{
SettingContainer *sc = new SettingContainer (keyRe.cap(2).simplified());
sc->setObjectName(keyRe.cap(1).simplified());
(*settings)[keyRe.cap(1).simplified()] = sc;
}
}
loadedSettings.insert(section, settings);
stream->device()->close();
delete stream;
stream = 0;
}
mergeMap (loadedSettings);
return success;
}
void CSMSettings::UserSettings::mergeMap (const CSMSettings::SectionMap &sectionSettings)
{
foreach (QString key, sectionSettings.uniqueKeys())
{
// insert entire section if it does not already exist in the loaded files
if (mSectionSettings.find(key) == mSectionSettings.end())
mSectionSettings.insert(key, sectionSettings.value(key));
else
{
SettingMap *passedSettings = sectionSettings.value(key);
SettingMap *settings = mSectionSettings.value(key);
foreach (QString key2, passedSettings->uniqueKeys())
{
//insert section settings individially if they do not already exist
if (settings->find(key2) == settings->end())
settings->insert(key2, passedSettings->value(key2));
else
{
settings->value(key2)->update(passedSettings->value(key2)->getValue());
}
}
}
}
}
void CSMSettings::UserSettings::loadSettings (const QString &fileName)
{
mSectionSettings.clear();
//global
QString globalFilePath = QString::fromStdString(mCfgMgr.getGlobalPath().string()) + fileName;
bool globalOk = loadFromFile(globalFilePath);
//local
QString localFilePath = QString::fromStdString(mCfgMgr.getLocalPath().string()) + fileName;
bool localOk = loadFromFile(localFilePath);
//user
mUserFilePath = QString::fromStdString(mCfgMgr.getUserPath().string()) + fileName;
loadFromFile(mUserFilePath);
if (!(localOk || globalOk))
{
QString message = QObject::tr("<br><b>Could not open user settings files for reading</b><br><br> \
Global and local settings files could not be read.\
You may have incorrect file permissions or the OpenCS installation may be corrupted.<br>");
message += QObject::tr("<br>Global filepath: ") + globalFilePath;
message += QObject::tr("<br>Local filepath: ") + localFilePath;
displayFileErrorMessage ( message, true);
}
}
void CSMSettings::UserSettings::updateSettings (const QString &sectionName, const QString &settingName)
{
SettingMap *settings = getValidSettings(sectionName);
if (!settings)
return;
if (settingName.isEmpty())
{
foreach (const SettingContainer *setting, *settings)
emit signalUpdateEditorSetting (setting->objectName(), setting->getValue());
}
else
{
if (settings->find(settingName) != settings->end())
{
const SettingContainer *setting = settings->value(settingName);
emit signalUpdateEditorSetting (setting->objectName(), setting->getValue());
}
}
}
QString CSMSettings::UserSettings::getSetting (const QString &section, const QString &setting) const
{
SettingMap *settings = getValidSettings(section);
QString retVal = "";
if (settings->find(setting) != settings->end())
retVal = settings->value(setting)->getValue();
return retVal;
}
CSMSettings::UserSettings& CSMSettings::UserSettings::instance()
{
assert(mUserSettingsInstance);
return *mUserSettingsInstance;
}
void CSMSettings::UserSettings::displayFileErrorMessage(const QString &message, bool isReadOnly)
{
// File cannot be opened or created
QMessageBox msgBox;
msgBox.setWindowTitle(QObject::tr("OpenCS configuration file I/O error"));
msgBox.setIcon(QMessageBox::Critical);
msgBox.setStandardButtons(QMessageBox::Ok);
if (!isReadOnly)
msgBox.setText (mReadWriteMessage + message);
else
msgBox.setText (message);
msgBox.exec();
}
CSMSettings::SettingMap *
CSMSettings::UserSettings::getValidSettings (const QString &sectionName) const
{
SettingMap *settings = 0;
//copy the default values for the entire section if it's not found
if (mSectionSettings.find(sectionName) == mSectionSettings.end())
{
if (mEditorSettingDefaults.find(sectionName) != mEditorSettingDefaults.end())
settings = mEditorSettingDefaults.value (sectionName);
}
//otherwise, iterate the section's settings, looking for missing values and replacing them with defaults.
else
{
SettingMap *loadedSettings = mSectionSettings[sectionName];
SettingMap *defaultSettings = mEditorSettingDefaults[sectionName];
foreach (QString key, defaultSettings->uniqueKeys())
{
//write the default value to the loaded settings
if (loadedSettings->find((key))==loadedSettings->end())
loadedSettings->insert(key, defaultSettings->value(key));
}
settings = mSectionSettings.value (sectionName);
}
return settings;
}

View file

@ -0,0 +1,94 @@
#ifndef USERSETTINGS_HPP
#define USERSETTINGS_HPP
#include <QTextStream>
#include <QStringList>
#include <QString>
#include <QMap>
#include <boost/filesystem/path.hpp>
#include "support.hpp"
#ifndef Q_MOC_RUN
#include <components/files/configurationmanager.hpp>
#endif
namespace Files { typedef std::vector<boost::filesystem::path> PathContainer;
struct ConfigurationManager;}
class QFile;
namespace CSMSettings {
struct UserSettings: public QObject
{
Q_OBJECT
SectionMap mSectionSettings;
SectionMap mEditorSettingDefaults;
static UserSettings *mUserSettingsInstance;
QString mUserFilePath;
Files::ConfigurationManager mCfgMgr;
QString mReadOnlyMessage;
QString mReadWriteMessage;
public:
/// Singleton implementation
static UserSettings& instance();
UserSettings();
~UserSettings();
UserSettings (UserSettings const &); //not implemented
void operator= (UserSettings const &); //not implemented
/// Writes settings to the last loaded settings file
bool writeSettings(QMap<QString, SettingList *> &sections);
/// Called from editor to trigger signal to update the specified setting.
/// If no setting name is specified, all settings found in the specified section are updated.
void updateSettings (const QString &sectionName, const QString &settingName = "");
/// Retrieves the settings file at all three levels (global, local and user).
/// \todo Multi-valued settings are not fully implemented. Setting values
/// \todo loaded in later files will always overwrite previously loaded values.
void loadSettings (const QString &fileName);
/// Returns the entire map of settings across all sections
const SectionMap &getSectionMap () const;
const SettingMap *getSettings (const QString &sectionName) const;
/// Retrieves the value as a QString of the specified setting in the specified section
QString getSetting(const QString &section, const QString &setting) const;
private:
/// Opens a QTextStream from the provided path as read-only or read-write.
QTextStream *openFileStream (const QString &filePath, bool isReadOnly = false) const;
/// Parses a setting file specified in filePath from the provided text stream.
bool loadFromFile (const QString &filePath = "");
/// merge the passed map into mSectionSettings
void mergeMap (const SectionMap &);
void displayFileErrorMessage(const QString &message, bool isReadOnly);
void buildEditorSettingDefaults();
SettingMap *getValidSettings (const QString &sectionName) const;
signals:
void signalUpdateEditorSetting (const QString &settingName, const QString &settingValue);
};
}
#endif // USERSETTINGS_HPP

View file

@ -0,0 +1,39 @@
#include "birthsigncheck.hpp"
#include <sstream>
#include <map>
#include <components/esm/loadbsgn.hpp>
#include "../world/universalid.hpp"
CSMTools::BirthsignCheckStage::BirthsignCheckStage (const CSMWorld::IdCollection<ESM::BirthSign>& birthsigns)
: mBirthsigns (birthsigns)
{}
int CSMTools::BirthsignCheckStage::setup()
{
return mBirthsigns.getSize();
}
void CSMTools::BirthsignCheckStage::perform (int stage, std::vector<std::string>& messages)
{
const ESM::BirthSign& birthsign = mBirthsigns.getRecord (stage).get();
CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Birthsign, birthsign.mId);
// test for empty name, description and texture
if (birthsign.mName.empty())
messages.push_back (id.toString() + "|" + birthsign.mId + " has an empty name");
if (birthsign.mDescription.empty())
messages.push_back (id.toString() + "|" + birthsign.mId + " has an empty description");
if (birthsign.mTexture.empty())
messages.push_back (id.toString() + "|" + birthsign.mId + " is missing a texture");
/// \todo test if the texture exists
/// \todo check data members that can't be edited in the table view
}

View file

@ -0,0 +1,29 @@
#ifndef CSM_TOOLS_BIRTHSIGNCHECK_H
#define CSM_TOOLS_BIRTHSIGNCHECK_H
#include <components/esm/loadbsgn.hpp>
#include "../world/idcollection.hpp"
#include "stage.hpp"
namespace CSMTools
{
/// \brief VerifyStage: make sure that birthsign records are internally consistent
class BirthsignCheckStage : public Stage
{
const CSMWorld::IdCollection<ESM::BirthSign>& mBirthsigns;
public:
BirthsignCheckStage (const CSMWorld::IdCollection<ESM::BirthSign>& birthsigns);
virtual int setup();
///< \return number of steps
virtual void perform (int stage, std::vector<std::string>& messages);
///< Messages resulting from this tage will be appended to \a messages.
};
}
#endif

View file

@ -0,0 +1,72 @@
#include "classcheck.hpp"
#include <sstream>
#include <map>
#include <components/esm/loadclas.hpp>
#include <components/esm/loadskil.hpp>
#include "../world/universalid.hpp"
CSMTools::ClassCheckStage::ClassCheckStage (const CSMWorld::IdCollection<ESM::Class>& classes)
: mClasses (classes)
{}
int CSMTools::ClassCheckStage::setup()
{
return mClasses.getSize();
}
void CSMTools::ClassCheckStage::perform (int stage, std::vector<std::string>& messages)
{
const ESM::Class& class_= mClasses.getRecord (stage).get();
CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Class, class_.mId);
// test for empty name and description
if (class_.mName.empty())
messages.push_back (id.toString() + "|" + class_.mId + " has an empty name");
if (class_.mDescription.empty())
messages.push_back (id.toString() + "|" + class_.mId + " has an empty description");
// test for invalid attributes
for (int i=0; i<2; ++i)
if (class_.mData.mAttribute[i]==-1)
{
std::ostringstream stream;
stream << id.toString() << "|Attribute #" << i << " of " << class_.mId << " is not set";
messages.push_back (stream.str());
}
if (class_.mData.mAttribute[0]==class_.mData.mAttribute[1] && class_.mData.mAttribute[0]!=-1)
{
std::ostringstream stream;
stream << id.toString() << "|Class lists same attribute twice";
messages.push_back (stream.str());
}
// test for non-unique skill
std::map<int, int> skills; // ID, number of occurrences
for (int i=0; i<5; ++i)
for (int i2=0; i2<2; ++i2)
++skills[class_.mData.mSkills[i][i2]];
for (std::map<int, int>::const_iterator iter (skills.begin()); iter!=skills.end(); ++iter)
if (iter->second>1)
{
std::ostringstream stream;
stream
<< id.toString() << "|"
<< ESM::Skill::indexToId (iter->first) << " is listed more than once";
messages.push_back (stream.str());
}
}

View file

@ -0,0 +1,29 @@
#ifndef CSM_TOOLS_CLASSCHECK_H
#define CSM_TOOLS_CLASSCHECK_H
#include <components/esm/loadclas.hpp>
#include "../world/idcollection.hpp"
#include "stage.hpp"
namespace CSMTools
{
/// \brief VerifyStage: make sure that class records are internally consistent
class ClassCheckStage : public Stage
{
const CSMWorld::IdCollection<ESM::Class>& mClasses;
public:
ClassCheckStage (const CSMWorld::IdCollection<ESM::Class>& classes);
virtual int setup();
///< \return number of steps
virtual void perform (int stage, std::vector<std::string>& messages);
///< Messages resulting from this tage will be appended to \a messages.
};
}
#endif

View file

@ -0,0 +1,61 @@
#include "factioncheck.hpp"
#include <sstream>
#include <map>
#include <components/esm/loadfact.hpp>
#include <components/esm/loadskil.hpp>
#include "../world/universalid.hpp"
CSMTools::FactionCheckStage::FactionCheckStage (const CSMWorld::IdCollection<ESM::Faction>& factions)
: mFactions (factions)
{}
int CSMTools::FactionCheckStage::setup()
{
return mFactions.getSize();
}
void CSMTools::FactionCheckStage::perform (int stage, std::vector<std::string>& messages)
{
const ESM::Faction& faction = mFactions.getRecord (stage).get();
CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Faction, faction.mId);
// test for empty name
if (faction.mName.empty())
messages.push_back (id.toString() + "|" + faction.mId + " has an empty name");
// test for invalid attributes
if (faction.mData.mAttribute[0]==faction.mData.mAttribute[1] && faction.mData.mAttribute[0]!=-1)
{
std::ostringstream stream;
stream << id.toString() << "|Faction lists same attribute twice";
messages.push_back (stream.str());
}
// test for non-unique skill
std::map<int, int> skills; // ID, number of occurrences
for (int i=0; i<6; ++i)
if (faction.mData.mSkills[i]!=-1)
++skills[faction.mData.mSkills[i]];
for (std::map<int, int>::const_iterator iter (skills.begin()); iter!=skills.end(); ++iter)
if (iter->second>1)
{
std::ostringstream stream;
stream
<< id.toString() << "|"
<< ESM::Skill::indexToId (iter->first) << " is listed more than once";
messages.push_back (stream.str());
}
/// \todo check data members that can't be edited in the table view
}

View file

@ -0,0 +1,29 @@
#ifndef CSM_TOOLS_FACTIONCHECK_H
#define CSM_TOOLS_FACTIONCHECK_H
#include <components/esm/loadfact.hpp>
#include "../world/idcollection.hpp"
#include "stage.hpp"
namespace CSMTools
{
/// \brief VerifyStage: make sure that faction records are internally consistent
class FactionCheckStage : public Stage
{
const CSMWorld::IdCollection<ESM::Faction>& mFactions;
public:
FactionCheckStage (const CSMWorld::IdCollection<ESM::Faction>& factions);
virtual int setup();
///< \return number of steps
virtual void perform (int stage, std::vector<std::string>& messages);
///< Messages resulting from this tage will be appended to \a messages.
};
}
#endif

View file

@ -0,0 +1,23 @@
#include "mandatoryid.hpp"
#include "../world/collectionbase.hpp"
#include "../world/record.hpp"
CSMTools::MandatoryIdStage::MandatoryIdStage (const CSMWorld::CollectionBase& idCollection,
const CSMWorld::UniversalId& collectionId, const std::vector<std::string>& ids)
: mIdCollection (idCollection), mCollectionId (collectionId), mIds (ids)
{}
int CSMTools::MandatoryIdStage::setup()
{
return mIds.size();
}
void CSMTools::MandatoryIdStage::perform (int stage, std::vector<std::string>& messages)
{
if (mIdCollection.searchId (mIds.at (stage))==-1 ||
mIdCollection.getRecord (mIds.at (stage)).isDeleted())
messages.push_back (mCollectionId.toString() + "|Missing mandatory record: " + mIds.at (stage));
}

View file

@ -0,0 +1,38 @@
#ifndef CSM_TOOLS_MANDATORYID_H
#define CSM_TOOLS_MANDATORYID_H
#include <string>
#include <vector>
#include "../world/universalid.hpp"
#include "stage.hpp"
namespace CSMWorld
{
class CollectionBase;
}
namespace CSMTools
{
/// \brief Verify stage: make sure that records with specific IDs exist.
class MandatoryIdStage : public Stage
{
const CSMWorld::CollectionBase& mIdCollection;
CSMWorld::UniversalId mCollectionId;
std::vector<std::string> mIds;
public:
MandatoryIdStage (const CSMWorld::CollectionBase& idCollection, const CSMWorld::UniversalId& collectionId,
const std::vector<std::string>& ids);
virtual int setup();
///< \return number of steps
virtual void perform (int stage, std::vector<std::string>& messages);
///< Messages resulting from this tage will be appended to \a messages.
};
}
#endif

View file

@ -0,0 +1,84 @@
#include "operation.hpp"
#include <string>
#include <vector>
#include <QTimer>
#include "../doc/state.hpp"
#include "stage.hpp"
void CSMTools::Operation::prepareStages()
{
mCurrentStage = mStages.begin();
mCurrentStep = 0;
mCurrentStepTotal = 0;
mTotalSteps = 0;
for (std::vector<std::pair<Stage *, int> >::iterator iter (mStages.begin()); iter!=mStages.end(); ++iter)
{
iter->second = iter->first->setup();
mTotalSteps += iter->second;
}
}
CSMTools::Operation::Operation (int type) : mType (type) {}
CSMTools::Operation::~Operation()
{
for (std::vector<std::pair<Stage *, int> >::iterator iter (mStages.begin()); iter!=mStages.end(); ++iter)
delete iter->first;
}
void CSMTools::Operation::run()
{
prepareStages();
QTimer timer;
timer.connect (&timer, SIGNAL (timeout()), this, SLOT (verify()));
timer.start (0);
exec();
}
void CSMTools::Operation::appendStage (Stage *stage)
{
mStages.push_back (std::make_pair (stage, 0));
}
void CSMTools::Operation::abort()
{
exit();
}
void CSMTools::Operation::verify()
{
std::vector<std::string> messages;
while (mCurrentStage!=mStages.end())
{
if (mCurrentStep>=mCurrentStage->second)
{
mCurrentStep = 0;
++mCurrentStage;
}
else
{
mCurrentStage->first->perform (mCurrentStep++, messages);
++mCurrentStepTotal;
break;
}
}
emit progress (mCurrentStepTotal, mTotalSteps ? mTotalSteps : 1, mType);
for (std::vector<std::string>::const_iterator iter (messages.begin()); iter!=messages.end(); ++iter)
emit reportMessage (iter->c_str(), mType);
if (mCurrentStage==mStages.end())
exit();
}

View file

@ -0,0 +1,54 @@
#ifndef CSM_TOOLS_OPERATION_H
#define CSM_TOOLS_OPERATION_H
#include <vector>
#include <QThread>
namespace CSMTools
{
class Stage;
class Operation : public QThread
{
Q_OBJECT
int mType;
std::vector<std::pair<Stage *, int> > mStages; // stage, number of steps
std::vector<std::pair<Stage *, int> >::iterator mCurrentStage;
int mCurrentStep;
int mCurrentStepTotal;
int mTotalSteps;
void prepareStages();
public:
Operation (int type);
virtual ~Operation();
virtual void run();
void appendStage (Stage *stage);
///< The ownership of \a stage is transferred to *this.
///
/// \attention Do no call this function while this Operation is running.
signals:
void progress (int current, int max, int type);
void reportMessage (const QString& message, int type);
public slots:
void abort();
private slots:
void verify();
};
}
#endif

View file

@ -0,0 +1,68 @@
#include "racecheck.hpp"
#include <sstream>
#include <components/esm/loadrace.hpp>
#include "../world/universalid.hpp"
void CSMTools::RaceCheckStage::performPerRecord (int stage, std::vector<std::string>& messages)
{
const ESM::Race& race = mRaces.getRecord (stage).get();
CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Race, race.mId);
// test for empty name and description
if (race.mName.empty())
messages.push_back (id.toString() + "|" + race.mId + " has an empty name");
if (race.mDescription.empty())
messages.push_back (id.toString() + "|" + race.mId + " has an empty description");
// test for positive height
if (race.mData.mHeight.mMale<=0)
messages.push_back (id.toString() + "|male " + race.mId + " has non-positive height");
if (race.mData.mHeight.mFemale<=0)
messages.push_back (id.toString() + "|female " + race.mId + " has non-positive height");
// test for non-negative weight
if (race.mData.mWeight.mMale<0)
messages.push_back (id.toString() + "|male " + race.mId + " has negative weight");
if (race.mData.mWeight.mFemale<0)
messages.push_back (id.toString() + "|female " + race.mId + " has negative weight");
// remember playable flag
if (race.mData.mFlags & 0x1)
mPlayable = true;
/// \todo check data members that can't be edited in the table view
}
void CSMTools::RaceCheckStage::performFinal (std::vector<std::string>& messages)
{
CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Races);
if (!mPlayable)
messages.push_back (id.toString() + "|No playable race");
}
CSMTools::RaceCheckStage::RaceCheckStage (const CSMWorld::IdCollection<ESM::Race>& races)
: mRaces (races), mPlayable (false)
{}
int CSMTools::RaceCheckStage::setup()
{
mPlayable = false;
return mRaces.getSize()+1;
}
void CSMTools::RaceCheckStage::perform (int stage, std::vector<std::string>& messages)
{
if (stage==mRaces.getSize())
performFinal (messages);
else
performPerRecord (stage, messages);
}

View file

@ -0,0 +1,34 @@
#ifndef CSM_TOOLS_RACECHECK_H
#define CSM_TOOLS_RACECHECK_H
#include <components/esm/loadrace.hpp>
#include "../world/idcollection.hpp"
#include "stage.hpp"
namespace CSMTools
{
/// \brief VerifyStage: make sure that race records are internally consistent
class RaceCheckStage : public Stage
{
const CSMWorld::IdCollection<ESM::Race>& mRaces;
bool mPlayable;
void performPerRecord (int stage, std::vector<std::string>& messages);
void performFinal (std::vector<std::string>& messages);
public:
RaceCheckStage (const CSMWorld::IdCollection<ESM::Race>& races);
virtual int setup();
///< \return number of steps
virtual void perform (int stage, std::vector<std::string>& messages);
///< Messages resulting from this tage will be appended to \a messages.
};
}
#endif

View file

@ -0,0 +1,33 @@
#include "regioncheck.hpp"
#include <sstream>
#include <map>
#include <components/esm/loadregn.hpp>
#include "../world/universalid.hpp"
CSMTools::RegionCheckStage::RegionCheckStage (const CSMWorld::IdCollection<ESM::Region>& regions)
: mRegions (regions)
{}
int CSMTools::RegionCheckStage::setup()
{
return mRegions.getSize();
}
void CSMTools::RegionCheckStage::perform (int stage, std::vector<std::string>& messages)
{
const ESM::Region& region = mRegions.getRecord (stage).get();
CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Region, region.mId);
// test for empty name
if (region.mName.empty())
messages.push_back (id.toString() + "|" + region.mId + " has an empty name");
/// \todo test that the ID in mSleeplist exists
/// \todo check data members that can't be edited in the table view
}

View file

@ -0,0 +1,29 @@
#ifndef CSM_TOOLS_REGIONCHECK_H
#define CSM_TOOLS_REGIONCHECK_H
#include <components/esm/loadregn.hpp>
#include "../world/idcollection.hpp"
#include "stage.hpp"
namespace CSMTools
{
/// \brief VerifyStage: make sure that region records are internally consistent
class RegionCheckStage : public Stage
{
const CSMWorld::IdCollection<ESM::Region>& mRegions;
public:
RegionCheckStage (const CSMWorld::IdCollection<ESM::Region>& regions);
virtual int setup();
///< \return number of steps
virtual void perform (int stage, std::vector<std::string>& messages);
///< Messages resulting from this tage will be appended to \a messages.
};
}
#endif

View file

@ -0,0 +1,71 @@
#include "reportmodel.hpp"
#include <stdexcept>
int CSMTools::ReportModel::rowCount (const QModelIndex & parent) const
{
if (parent.isValid())
return 0;
return mRows.size();
}
int CSMTools::ReportModel::columnCount (const QModelIndex & parent) const
{
if (parent.isValid())
return 0;
return 2;
}
QVariant CSMTools::ReportModel::data (const QModelIndex & index, int role) const
{
if (role!=Qt::DisplayRole)
return QVariant();
if (index.column()==0)
return static_cast<int> (mRows.at (index.row()).first.getType());
else
return mRows.at (index.row()).second.c_str();
}
QVariant CSMTools::ReportModel::headerData (int section, Qt::Orientation orientation, int role) const
{
if (role!=Qt::DisplayRole)
return QVariant();
if (orientation==Qt::Vertical)
return QVariant();
return tr (section==0 ? "Type" : "Description");
}
bool CSMTools::ReportModel::removeRows (int row, int count, const QModelIndex& parent)
{
if (parent.isValid())
return false;
mRows.erase (mRows.begin()+row, mRows.begin()+row+count);
return true;
}
void CSMTools::ReportModel::add (const std::string& row)
{
std::string::size_type index = row.find ('|');
if (index==std::string::npos)
throw std::logic_error ("invalid report message");
beginInsertRows (QModelIndex(), mRows.size(), mRows.size());
mRows.push_back (std::make_pair (row.substr (0, index), row.substr (index+1)));
endInsertRows();
}
const CSMWorld::UniversalId& CSMTools::ReportModel::getUniversalId (int row) const
{
return mRows.at (row).first;
}

View file

@ -0,0 +1,37 @@
#ifndef CSM_TOOLS_REPORTMODEL_H
#define CSM_TOOLS_REPORTMODEL_H
#include <vector>
#include <string>
#include <QAbstractTableModel>
#include "../world/universalid.hpp"
namespace CSMTools
{
class ReportModel : public QAbstractTableModel
{
Q_OBJECT
std::vector<std::pair<CSMWorld::UniversalId, std::string> > mRows;
public:
virtual int rowCount (const QModelIndex & parent = QModelIndex()) const;
virtual int columnCount (const QModelIndex & parent = QModelIndex()) const;
virtual QVariant data (const QModelIndex & index, int role = Qt::DisplayRole) const;
virtual QVariant headerData (int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
virtual bool removeRows (int row, int count, const QModelIndex& parent = QModelIndex());
void add (const std::string& row);
const CSMWorld::UniversalId& getUniversalId (int row) const;
};
}
#endif

View file

@ -0,0 +1,37 @@
#include "skillcheck.hpp"
#include <sstream>
#include <components/esm/loadskil.hpp>
#include "../world/universalid.hpp"
CSMTools::SkillCheckStage::SkillCheckStage (const CSMWorld::IdCollection<ESM::Skill>& skills)
: mSkills (skills)
{}
int CSMTools::SkillCheckStage::setup()
{
return mSkills.getSize();
}
void CSMTools::SkillCheckStage::perform (int stage, std::vector<std::string>& messages)
{
const ESM::Skill& skill = mSkills.getRecord (stage).get();
CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Skill, skill.mId);
for (int i=0; i<4; ++i)
if (skill.mData.mUseValue[i]<0)
{
std::ostringstream stream;
stream << id.toString() << "|Use value #" << i << " of " << skill.mId << " is negative";
messages.push_back (stream.str());
}
if (skill.mDescription.empty())
messages.push_back (id.toString() + "|" + skill.mId + " has an empty description");
}

View file

@ -0,0 +1,29 @@
#ifndef CSM_TOOLS_SKILLCHECK_H
#define CSM_TOOLS_SKILLCHECK_H
#include <components/esm/loadskil.hpp>
#include "../world/idcollection.hpp"
#include "stage.hpp"
namespace CSMTools
{
/// \brief VerifyStage: make sure that skill records are internally consistent
class SkillCheckStage : public Stage
{
const CSMWorld::IdCollection<ESM::Skill>& mSkills;
public:
SkillCheckStage (const CSMWorld::IdCollection<ESM::Skill>& skills);
virtual int setup();
///< \return number of steps
virtual void perform (int stage, std::vector<std::string>& messages);
///< Messages resulting from this tage will be appended to \a messages.
};
}
#endif

View file

@ -0,0 +1,29 @@
#include "soundcheck.hpp"
#include <sstream>
#include <components/esm/loadskil.hpp>
#include "../world/universalid.hpp"
CSMTools::SoundCheckStage::SoundCheckStage (const CSMWorld::IdCollection<ESM::Sound>& sounds)
: mSounds (sounds)
{}
int CSMTools::SoundCheckStage::setup()
{
return mSounds.getSize();
}
void CSMTools::SoundCheckStage::perform (int stage, std::vector<std::string>& messages)
{
const ESM::Sound& sound = mSounds.getRecord (stage).get();
CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Sound, sound.mId);
if (sound.mData.mMinRange>sound.mData.mMaxRange)
messages.push_back (id.toString() + "|Maximum range larger than minimum range");
/// \todo check, if the sound file exists
}

View file

@ -0,0 +1,29 @@
#ifndef CSM_TOOLS_SOUNDCHECK_H
#define CSM_TOOLS_SOUNDCHECK_H
#include <components/esm/loadsoun.hpp>
#include "../world/idcollection.hpp"
#include "stage.hpp"
namespace CSMTools
{
/// \brief VerifyStage: make sure that sound records are internally consistent
class SoundCheckStage : public Stage
{
const CSMWorld::IdCollection<ESM::Sound>& mSounds;
public:
SoundCheckStage (const CSMWorld::IdCollection<ESM::Sound>& sounds);
virtual int setup();
///< \return number of steps
virtual void perform (int stage, std::vector<std::string>& messages);
///< Messages resulting from this tage will be appended to \a messages.
};
}
#endif

View file

@ -0,0 +1,35 @@
#include "spellcheck.hpp"
#include <sstream>
#include <map>
#include <components/esm/loadspel.hpp>
#include "../world/universalid.hpp"
CSMTools::SpellCheckStage::SpellCheckStage (const CSMWorld::IdCollection<ESM::Spell>& spells)
: mSpells (spells)
{}
int CSMTools::SpellCheckStage::setup()
{
return mSpells.getSize();
}
void CSMTools::SpellCheckStage::perform (int stage, std::vector<std::string>& messages)
{
const ESM::Spell& spell = mSpells.getRecord (stage).get();
CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Spell, spell.mId);
// test for empty name and description
if (spell.mName.empty())
messages.push_back (id.toString() + "|" + spell.mId + " has an empty name");
// test for invalid cost values
if (spell.mData.mCost<0)
messages.push_back (id.toString() + "|" + spell.mId + " has a negative spell costs");
/// \todo check data members that can't be edited in the table view
}

View file

@ -0,0 +1,29 @@
#ifndef CSM_TOOLS_SPELLCHECK_H
#define CSM_TOOLS_SPELLCHECK_H
#include <components/esm/loadspel.hpp>
#include "../world/idcollection.hpp"
#include "stage.hpp"
namespace CSMTools
{
/// \brief VerifyStage: make sure that spell records are internally consistent
class SpellCheckStage : public Stage
{
const CSMWorld::IdCollection<ESM::Spell>& mSpells;
public:
SpellCheckStage (const CSMWorld::IdCollection<ESM::Spell>& spells);
virtual int setup();
///< \return number of steps
virtual void perform (int stage, std::vector<std::string>& messages);
///< Messages resulting from this tage will be appended to \a messages.
};
}
#endif

Some files were not shown because too many files have changed in this diff Show more