mirror of
https://github.com/libretro/scummvm.git
synced 2025-02-10 21:03:31 +00:00
added config manager common code
This commit is contained in:
parent
c9a93b00d9
commit
95e4d9f14c
375
common/config-file.cpp
Normal file
375
common/config-file.cpp
Normal file
@ -0,0 +1,375 @@
|
||||
/* Residual - Virtual machine to run LucasArts' 3D adventure games
|
||||
*
|
||||
* Residual is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the AUTHORS
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* $URL$
|
||||
* $Id$
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/config-file.h"
|
||||
#include "common/file.h"
|
||||
#include "common/savefile.h"
|
||||
#include "common/util.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
#include "engine/backend/driver.h"
|
||||
|
||||
#define MAXLINELEN 256
|
||||
|
||||
namespace Common {
|
||||
|
||||
/**
|
||||
* Check whether the given string is a valid section or key name.
|
||||
* For that, it must only consist of letters, numbers, dashes and
|
||||
* underscores. In particular, white space and "#", "=", "[", "]"
|
||||
* are not valid!
|
||||
*/
|
||||
bool ConfigFile::isValidName(const Common::String &name) {
|
||||
const char *p = name.c_str();
|
||||
while (*p && (isalnum(*p) || *p == '-' || *p == '_' || *p == '.'))
|
||||
p++;
|
||||
return *p == 0;
|
||||
}
|
||||
|
||||
ConfigFile::ConfigFile() {
|
||||
}
|
||||
|
||||
ConfigFile::~ConfigFile() {
|
||||
}
|
||||
|
||||
void ConfigFile::clear() {
|
||||
_sections.clear();
|
||||
}
|
||||
|
||||
bool ConfigFile::loadFromFile(const String &filename) {
|
||||
File file;
|
||||
if (file.open(filename, File::kFileReadMode))
|
||||
return loadFromStream(file);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ConfigFile::loadFromSaveFile(const char *filename) {
|
||||
SaveFileManager *saveFileMan = g_driver->getSavefileManager();
|
||||
SeekableReadStream *loadFile;
|
||||
|
||||
if (!(loadFile = saveFileMan->openForLoading(filename)))
|
||||
return false;
|
||||
|
||||
bool status = loadFromStream(*loadFile);
|
||||
delete loadFile;
|
||||
return status;
|
||||
}
|
||||
|
||||
bool ConfigFile::loadFromStream(SeekableReadStream &stream) {
|
||||
char buf[MAXLINELEN];
|
||||
Section section;
|
||||
KeyValue kv;
|
||||
String comment;
|
||||
int lineno = 0;
|
||||
|
||||
// TODO: Detect if a section occurs multiple times (or likewise, if
|
||||
// a key occurs multiple times inside one section).
|
||||
|
||||
while (!stream.eos()) {
|
||||
lineno++;
|
||||
if (!stream.readLine(buf, MAXLINELEN))
|
||||
break;
|
||||
|
||||
if (buf[0] == '#') {
|
||||
// Accumulate comments here. Once we encounter either the start
|
||||
// of a new section, or a key-value-pair, we associate the value
|
||||
// of the 'comment' variable with that entity.
|
||||
comment += buf;
|
||||
comment += "\n";
|
||||
} else if (buf[0] == '(') {
|
||||
// HACK: The following is a hack added by Kirben to support the
|
||||
// "map.ini" used in the HE SCUMM game "SPY Fox in Hold the Mustard".
|
||||
//
|
||||
// It would be nice if this hack could be restricted to that game,
|
||||
// but the current design of this class doesn't allow to do that
|
||||
// in a nice fashion (a "isMustard" parameter is *not* a nice
|
||||
// solution).
|
||||
comment += buf;
|
||||
comment += "\n";
|
||||
} else if (buf[0] == '[') {
|
||||
// It's a new section which begins here.
|
||||
char *p = buf + 1;
|
||||
// Get the section name, and check whether it's valid (that
|
||||
// is, verify that it only consists of alphanumerics,
|
||||
// dashes and underscores).
|
||||
while (*p && (isalnum(*p) || *p == '-' || *p == '_'))
|
||||
p++;
|
||||
|
||||
if (*p == '\0')
|
||||
error("ConfigFile::loadFromStream: missing ] in line %d", lineno);
|
||||
else if (*p != ']')
|
||||
error("ConfigFile::loadFromStream: Invalid character '%c' occured in section name in line %d", *p, lineno);
|
||||
|
||||
*p = 0;
|
||||
|
||||
// Previous section is finished now, store it.
|
||||
if (!section.name.empty())
|
||||
_sections.push_back(section);
|
||||
|
||||
section.name = buf + 1;
|
||||
section.keys.clear();
|
||||
section.comment = comment;
|
||||
comment.clear();
|
||||
|
||||
assert(isValidName(section.name));
|
||||
} else {
|
||||
// Skip leading & trailing whitespaces
|
||||
char *t = rtrim(ltrim(buf));
|
||||
|
||||
// Skip empty lines
|
||||
if (*t == 0)
|
||||
continue;
|
||||
|
||||
// If no section has been set, this config file is invalid!
|
||||
if (section.name.empty()) {
|
||||
error("ConfigFile::loadFromStream: Key/value pair found outside a section in line %d", lineno);
|
||||
}
|
||||
|
||||
// Split string at '=' into 'key' and 'value'.
|
||||
char *p = strchr(t, '=');
|
||||
if (!p)
|
||||
error("ConfigFile::loadFromStream: Junk found in line line %d: '%s'", lineno, t);
|
||||
*p = 0;
|
||||
|
||||
kv.key = rtrim(t);
|
||||
kv.value = ltrim(p + 1);
|
||||
kv.comment = comment;
|
||||
comment.clear();
|
||||
|
||||
assert(isValidName(kv.key));
|
||||
|
||||
section.keys.push_back(kv);
|
||||
}
|
||||
}
|
||||
|
||||
// Save last section
|
||||
if (!section.name.empty())
|
||||
_sections.push_back(section);
|
||||
|
||||
return (!stream.ioFailed() || stream.eos());
|
||||
}
|
||||
|
||||
bool ConfigFile::saveToFile(const String &filename) {
|
||||
File file;
|
||||
if (file.open(filename, File::kFileWriteMode))
|
||||
return saveToStream(file);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ConfigFile::saveToSaveFile(const char *filename) {
|
||||
SaveFileManager *saveFileMan = g_driver->getSavefileManager();
|
||||
WriteStream *saveFile;
|
||||
|
||||
if (!(saveFile = saveFileMan->openForSaving(filename)))
|
||||
return false;
|
||||
|
||||
bool status = saveToStream(*saveFile);
|
||||
delete saveFile;
|
||||
return status;
|
||||
}
|
||||
|
||||
bool ConfigFile::saveToStream(WriteStream &stream) {
|
||||
for (List<Section>::iterator i = _sections.begin(); i != _sections.end(); ++i) {
|
||||
// Write out the section comment, if any
|
||||
if (! i->comment.empty()) {
|
||||
stream.writeString(i->comment);
|
||||
}
|
||||
|
||||
// Write out the section name
|
||||
stream.writeByte('[');
|
||||
stream.writeString(i->name);
|
||||
stream.writeByte(']');
|
||||
stream.writeByte('\n');
|
||||
|
||||
// Write out the key/value pairs
|
||||
for (List<KeyValue>::iterator kv = i->keys.begin(); kv != i->keys.end(); ++kv) {
|
||||
// Write out the comment, if any
|
||||
if (! kv->comment.empty()) {
|
||||
stream.writeString(kv->comment);
|
||||
}
|
||||
// Write out the key/value pair
|
||||
stream.writeString(kv->key);
|
||||
stream.writeByte('=');
|
||||
stream.writeString(kv->value);
|
||||
stream.writeByte('\n');
|
||||
}
|
||||
}
|
||||
|
||||
stream.flush();
|
||||
return !stream.ioFailed();
|
||||
}
|
||||
|
||||
|
||||
void ConfigFile::removeSection(const String §ion) {
|
||||
assert(isValidName(section));
|
||||
for (List<Section>::iterator i = _sections.begin(); i != _sections.end(); ++i) {
|
||||
if (!strcasecmp(section.c_str(), i->name.c_str())) {
|
||||
_sections.erase(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ConfigFile::hasSection(const String §ion) const {
|
||||
assert(isValidName(section));
|
||||
const Section *s = getSection(section);
|
||||
return s != 0;
|
||||
}
|
||||
|
||||
void ConfigFile::renameSection(const String &oldName, const String &newName) {
|
||||
assert(isValidName(oldName));
|
||||
assert(isValidName(newName));
|
||||
|
||||
//Section *os = getSection(oldName);
|
||||
Section *ns = getSection(newName);
|
||||
if (ns) {
|
||||
ns->name = newName;
|
||||
}
|
||||
// TODO: Check here whether there already is a section with the
|
||||
// new name. Not sure how to cope with that case, we could:
|
||||
// - simply remove the existing "newName" section
|
||||
// - error out
|
||||
// - merge the two sections "oldName" and "newName"
|
||||
}
|
||||
|
||||
|
||||
bool ConfigFile::hasKey(const String &key, const String §ion) const {
|
||||
assert(isValidName(key));
|
||||
assert(isValidName(section));
|
||||
|
||||
const Section *s = getSection(section);
|
||||
if (!s)
|
||||
return false;
|
||||
return s->hasKey(key);
|
||||
}
|
||||
|
||||
void ConfigFile::removeKey(const String &key, const String §ion) {
|
||||
assert(isValidName(key));
|
||||
assert(isValidName(section));
|
||||
|
||||
Section *s = getSection(section);
|
||||
if (s)
|
||||
s->removeKey(key);
|
||||
}
|
||||
|
||||
bool ConfigFile::getKey(const String &key, const String §ion, String &value) const {
|
||||
assert(isValidName(key));
|
||||
assert(isValidName(section));
|
||||
|
||||
const Section *s = getSection(section);
|
||||
if (!s)
|
||||
return false;
|
||||
const KeyValue *kv = s->getKey(key);
|
||||
if (!kv)
|
||||
return false;
|
||||
value = kv->value;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConfigFile::setKey(const String &key, const String §ion, const String &value) {
|
||||
assert(isValidName(key));
|
||||
assert(isValidName(section));
|
||||
// TODO: Verify that value is valid, too. In particular, it shouldn't
|
||||
// contain CR or LF...
|
||||
|
||||
Section *s = getSection(section);
|
||||
if (!s) {
|
||||
KeyValue newKV;
|
||||
newKV.key = key;
|
||||
newKV.value = value;
|
||||
|
||||
Section newSection;
|
||||
newSection.name = section;
|
||||
newSection.keys.push_back(newKV);
|
||||
|
||||
_sections.push_back(newSection);
|
||||
} else {
|
||||
s->setKey(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
const ConfigFile::SectionKeyList ConfigFile::getKeys(const String §ion) const {
|
||||
const Section *s = getSection(section);
|
||||
|
||||
return s->getKeys();
|
||||
}
|
||||
|
||||
ConfigFile::Section *ConfigFile::getSection(const String §ion) {
|
||||
for (List<Section>::iterator i = _sections.begin(); i != _sections.end(); ++i) {
|
||||
if (!strcasecmp(section.c_str(), i->name.c_str())) {
|
||||
return &(*i);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const ConfigFile::Section *ConfigFile::getSection(const String §ion) const {
|
||||
for (List<Section>::const_iterator i = _sections.begin(); i != _sections.end(); ++i) {
|
||||
if (!strcasecmp(section.c_str(), i->name.c_str())) {
|
||||
return &(*i);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool ConfigFile::Section::hasKey(const String &key) const {
|
||||
return getKey(key) != 0;
|
||||
}
|
||||
|
||||
const ConfigFile::KeyValue* ConfigFile::Section::getKey(const String &key) const {
|
||||
for (List<KeyValue>::const_iterator i = keys.begin(); i != keys.end(); ++i) {
|
||||
if (!strcasecmp(key.c_str(), i->key.c_str())) {
|
||||
return &(*i);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ConfigFile::Section::setKey(const String &key, const String &value) {
|
||||
for (List<KeyValue>::iterator i = keys.begin(); i != keys.end(); ++i) {
|
||||
if (!strcasecmp(key.c_str(), i->key.c_str())) {
|
||||
i->value = value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
KeyValue newKV;
|
||||
newKV.key = key;
|
||||
newKV.value = value;
|
||||
keys.push_back(newKV);
|
||||
}
|
||||
|
||||
void ConfigFile::Section::removeKey(const String &key) {
|
||||
for (List<KeyValue>::iterator i = keys.begin(); i != keys.end(); ++i) {
|
||||
if (!strcasecmp(key.c_str(), i->key.c_str())) {
|
||||
keys.erase(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Common
|
143
common/config-file.h
Normal file
143
common/config-file.h
Normal file
@ -0,0 +1,143 @@
|
||||
/* Residual - Virtual machine to run LucasArts' 3D adventure games
|
||||
*
|
||||
* Residual is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the AUTHORS
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* $URL$
|
||||
* $Id$
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef COMMON_CONFIG_FILE_H
|
||||
#define COMMON_CONFIG_FILE_H
|
||||
|
||||
#include "common/config-manager.h"
|
||||
#include "common/list.h"
|
||||
#include "common/str.h"
|
||||
#include "common/stream.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
/**
|
||||
* This class allows reading/writing INI style config files.
|
||||
* It is used by the ConfigManager for storage, but can also
|
||||
* be used by other code if it needs to read/write custom INI
|
||||
* files.
|
||||
*
|
||||
* Lines starting with a '#' are ignored (i.e. treated as comments).
|
||||
* Some effort is made to preserve comments, though.
|
||||
*
|
||||
* This class makes no attempts to provide fast access to key/value pairs.
|
||||
* In particular, it stores all sections and k/v pairs in lists, not
|
||||
* in dictionaries/maps. This makes it very easy to read/write the data
|
||||
* from/to files, but of course is not appropriate for fast access.
|
||||
* The main reason is that this class is indeed geared toward doing precisely
|
||||
* that!
|
||||
* If you need fast access to the game config, use higher level APIs, like the
|
||||
* one provided by ConfigManager.
|
||||
*/
|
||||
class ConfigFile {
|
||||
public:
|
||||
typedef HashMap<String, bool, IgnoreCase_Hash, IgnoreCase_EqualTo> StringSet;
|
||||
|
||||
struct KeyValue {
|
||||
String key;
|
||||
String value;
|
||||
String comment;
|
||||
};
|
||||
|
||||
typedef List<KeyValue> SectionKeyList;
|
||||
|
||||
/** A section in a config file. I.e. corresponds to something like this:
|
||||
* [mySection]
|
||||
* key=value
|
||||
*
|
||||
* Comments are also stored, to keep users happy who like editing their
|
||||
* config files manually.
|
||||
*/
|
||||
struct Section {
|
||||
String name;
|
||||
List<KeyValue> keys;
|
||||
String comment;
|
||||
|
||||
bool hasKey(const String &key) const;
|
||||
const KeyValue* getKey(const String &key) const;
|
||||
void setKey(const String &key, const String &value);
|
||||
void removeKey(const String &key);
|
||||
const SectionKeyList getKeys() const { return keys; }
|
||||
};
|
||||
|
||||
typedef List<Section> SectionList;
|
||||
|
||||
public:
|
||||
ConfigFile();
|
||||
~ConfigFile();
|
||||
|
||||
// TODO: Maybe add a copy constructor etc.?
|
||||
|
||||
/**
|
||||
* Check whether the given string is a valid section or key name.
|
||||
* For that, it must only consist of letters, numbers, dashes and
|
||||
* underscores. In particular, white space and "#", "=", "[", "]"
|
||||
* are not valid!
|
||||
*/
|
||||
static bool isValidName(const Common::String &name);
|
||||
|
||||
/** Reset everything stored in this config file. */
|
||||
void clear();
|
||||
|
||||
bool loadFromFile(const String &filename);
|
||||
bool loadFromSaveFile(const char *filename);
|
||||
bool loadFromStream(SeekableReadStream &stream);
|
||||
bool saveToFile(const String &filename);
|
||||
bool saveToSaveFile(const char *filename);
|
||||
bool saveToStream(WriteStream &stream);
|
||||
|
||||
bool hasSection(const String §ion) const;
|
||||
void removeSection(const String §ion);
|
||||
void renameSection(const String &oldName, const String &newName);
|
||||
|
||||
bool hasKey(const String &key, const String §ion) const;
|
||||
bool getKey(const String &key, const String §ion, String &value) const;
|
||||
void setKey(const String &key, const String §ion, const String &value);
|
||||
void removeKey(const String &key, const String §ion);
|
||||
|
||||
const SectionList getSections() const { return _sections; }
|
||||
const SectionKeyList getKeys(const String §ion) const;
|
||||
|
||||
void listSections(StringSet &set);
|
||||
void listKeyValues(StringMap &kv);
|
||||
|
||||
private:
|
||||
SectionList _sections;
|
||||
|
||||
Section *getSection(const String §ion);
|
||||
const Section *getSection(const String §ion) const;
|
||||
};
|
||||
|
||||
/*
|
||||
- ConfigMan owns a config file
|
||||
- allow direct access to that config file (for the launcher)
|
||||
- simplify and unify the regular ConfigMan API in exchange
|
||||
|
||||
|
||||
*/
|
||||
|
||||
} // End of namespace Common
|
||||
|
||||
#endif
|
696
common/config-manager.cpp
Normal file
696
common/config-manager.cpp
Normal file
@ -0,0 +1,696 @@
|
||||
/* Residual - Virtual machine to run LucasArts' 3D adventure games
|
||||
*
|
||||
* Residual is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the AUTHORS
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* $URL$
|
||||
* $Id$
|
||||
*
|
||||
*/
|
||||
|
||||
#if defined(WIN32)
|
||||
#include <windows.h>
|
||||
// winnt.h defines ARRAYSIZE, but we want our own one...
|
||||
#undef ARRAYSIZE
|
||||
#endif
|
||||
|
||||
#include "common/config-manager.h"
|
||||
#include "common/file.h"
|
||||
#include "common/util.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
DECLARE_SINGLETON(Common::ConfigManager);
|
||||
|
||||
#ifdef __PLAYSTATION2__
|
||||
#include "backends/platform/ps2/systemps2.h"
|
||||
#endif
|
||||
|
||||
#ifdef IPHONE
|
||||
#include "backends/platform/iphone/osys_iphone.h"
|
||||
#endif
|
||||
|
||||
#if defined(UNIX)
|
||||
#ifdef MACOSX
|
||||
#define DEFAULT_CONFIG_FILE "Library/Preferences/Resdial Preferences"
|
||||
#else
|
||||
#define DEFAULT_CONFIG_FILE ".residualrc"
|
||||
#endif
|
||||
#else
|
||||
#define DEFAULT_CONFIG_FILE "residual.ini"
|
||||
#endif
|
||||
|
||||
#define MAXLINELEN 256
|
||||
|
||||
static bool isValidDomainName(const Common::String &domName) {
|
||||
const char *p = domName.c_str();
|
||||
while (*p && (isalnum(*p) || *p == '-' || *p == '_'))
|
||||
p++;
|
||||
return *p == 0;
|
||||
}
|
||||
|
||||
namespace Common {
|
||||
|
||||
#if !(defined(PALMOS_ARM) || defined(PALMOS_DEBUG) || defined(__GP32__))
|
||||
|
||||
const String ConfigManager::kApplicationDomain("residual");
|
||||
const String ConfigManager::kTransientDomain("__TRANSIENT");
|
||||
|
||||
#else
|
||||
|
||||
const char *ConfigManager::kApplicationDomain = "residual";
|
||||
const char *ConfigManager::kTransientDomain = "__TRANSIENT";
|
||||
|
||||
#endif
|
||||
|
||||
#pragma mark -
|
||||
|
||||
|
||||
ConfigManager::ConfigManager()
|
||||
: _activeDomain(0) {
|
||||
}
|
||||
|
||||
|
||||
void ConfigManager::loadDefaultConfigFile() {
|
||||
char configFile[MAXPATHLEN];
|
||||
// GP2X is Linux based but Home dir can be read only so do not use it and put the config in the executable dir.
|
||||
// On the iPhone, the home dir of the user when you launch the app from the Springboard, is /. Which we don't want.
|
||||
#if defined(UNIX) && !defined(GP2X) && !defined(IPHONE)
|
||||
const char *home = getenv("HOME");
|
||||
if (home != NULL && strlen(home) < MAXPATHLEN)
|
||||
snprintf(configFile, MAXPATHLEN, "%s/%s", home, DEFAULT_CONFIG_FILE);
|
||||
else
|
||||
strcpy(configFile, DEFAULT_CONFIG_FILE);
|
||||
#else
|
||||
#if defined (WIN32) && !defined(_WIN32_WCE) && !defined(__SYMBIAN32__)
|
||||
OSVERSIONINFO win32OsVersion;
|
||||
ZeroMemory(&win32OsVersion, sizeof(OSVERSIONINFO));
|
||||
win32OsVersion.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
|
||||
GetVersionEx(&win32OsVersion);
|
||||
// Check for non-9X version of Windows.
|
||||
if (win32OsVersion.dwPlatformId != VER_PLATFORM_WIN32_WINDOWS) {
|
||||
// Use the Application Data directory of the user profile.
|
||||
if (win32OsVersion.dwMajorVersion >= 5) {
|
||||
if (!GetEnvironmentVariable("APPDATA", configFile, sizeof(configFile)))
|
||||
error("Unable to access application data directory");
|
||||
} else {
|
||||
if (!GetEnvironmentVariable("USERPROFILE", configFile, sizeof(configFile)))
|
||||
error("Unable to access user profile directory");
|
||||
|
||||
strcat(configFile, "\\Application Data");
|
||||
CreateDirectory(configFile, NULL);
|
||||
}
|
||||
|
||||
strcat(configFile, "\\Residual");
|
||||
CreateDirectory(configFile, NULL);
|
||||
strcat(configFile, "\\" DEFAULT_CONFIG_FILE);
|
||||
} else {
|
||||
// Check windows directory
|
||||
GetWindowsDirectory(configFile, MAXPATHLEN);
|
||||
strcat(configFile, "\\" DEFAULT_CONFIG_FILE);
|
||||
}
|
||||
|
||||
#elif defined(PALMOS_MODE)
|
||||
strcpy(configFile,"/PALM/Programs/ScummVM/" DEFAULT_CONFIG_FILE);
|
||||
#elif defined(IPHONE)
|
||||
strcpy(configFile, OSystem_IPHONE::getConfigPath());
|
||||
#elif defined(__PLAYSTATION2__)
|
||||
((OSystem_PS2*)g_system)->makeConfigPath(configFile);
|
||||
#elif defined(__PSP__)
|
||||
strcpy(configFile, "ms0:/" DEFAULT_CONFIG_FILE);
|
||||
#elif defined (__SYMBIAN32__)
|
||||
strcpy(configFile, Symbian::GetExecutablePath());
|
||||
strcat(configFile, DEFAULT_CONFIG_FILE);
|
||||
#else
|
||||
strcpy(configFile, DEFAULT_CONFIG_FILE);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
loadConfigFile(configFile);
|
||||
flushToDisk();
|
||||
}
|
||||
|
||||
void ConfigManager::loadConfigFile(const String &filename) {
|
||||
_appDomain.clear();
|
||||
_gameDomains.clear();
|
||||
_transientDomain.clear();
|
||||
|
||||
_filename = filename;
|
||||
_domainSaveOrder.clear();
|
||||
loadFile(_filename);
|
||||
printf("Using configuration file: %s\n", _filename.c_str());
|
||||
}
|
||||
|
||||
void ConfigManager::loadFile(const String &filename) {
|
||||
File cfg_file;
|
||||
|
||||
if (!cfg_file.open(filename)) {
|
||||
printf("Creating configuration file: %s\n", filename.c_str());
|
||||
} else {
|
||||
String domain;
|
||||
String comment;
|
||||
int lineno = 0;
|
||||
|
||||
// TODO: Detect if a domain occurs multiple times (or likewise, if
|
||||
// a key occurs multiple times inside one domain).
|
||||
|
||||
while (!cfg_file.eof() && !cfg_file.ioFailed()) {
|
||||
lineno++;
|
||||
|
||||
// Read a line
|
||||
String line;
|
||||
while (line.lastChar() != '\n') {
|
||||
char buf[MAXLINELEN];
|
||||
if (!cfg_file.readLine_NEW(buf, MAXLINELEN))
|
||||
break;
|
||||
line += buf;
|
||||
}
|
||||
|
||||
if (line.size() == 0) {
|
||||
// Do nothing
|
||||
} else if (line[0] == '#') {
|
||||
// Accumulate comments here. Once we encounter either the start
|
||||
// of a new domain, or a key-value-pair, we associate the value
|
||||
// of the 'comment' variable with that entity.
|
||||
comment += line;
|
||||
} else if (line[0] == '[') {
|
||||
// It's a new domain which begins here.
|
||||
const char *p = line.c_str() + 1;
|
||||
// Get the domain name, and check whether it's valid (that
|
||||
// is, verify that it only consists of alphanumerics,
|
||||
// dashes and underscores).
|
||||
while (*p && (isalnum(*p) || *p == '-' || *p == '_'))
|
||||
p++;
|
||||
|
||||
switch (*p) {
|
||||
case '\0':
|
||||
error("Config file buggy: missing ] in line %d", lineno);
|
||||
break;
|
||||
case ']':
|
||||
domain = String(line.c_str() + 1, p - (line.c_str() + 1));
|
||||
//domain = String(line.c_str() + 1, p); // TODO: Pending Common::String changes
|
||||
break;
|
||||
default:
|
||||
error("Config file buggy: Invalid character '%c' occured in domain name in line %d", *p, lineno);
|
||||
}
|
||||
|
||||
// Store domain comment
|
||||
if (domain == kApplicationDomain) {
|
||||
_appDomain.setDomainComment(comment);
|
||||
} else {
|
||||
_gameDomains[domain].setDomainComment(comment);
|
||||
}
|
||||
comment.clear();
|
||||
|
||||
_domainSaveOrder.push_back(domain);
|
||||
} else {
|
||||
// This line should be a line with a 'key=value' pair, or an empty one.
|
||||
|
||||
// Skip leading whitespaces
|
||||
const char *t = line.c_str();
|
||||
while (isspace(*t))
|
||||
t++;
|
||||
|
||||
// Skip empty lines / lines with only whitespace
|
||||
if (*t == 0)
|
||||
continue;
|
||||
|
||||
// If no domain has been set, this config file is invalid!
|
||||
if (domain.empty()) {
|
||||
error("Config file buggy: Key/value pair found outside a domain in line %d", lineno);
|
||||
}
|
||||
|
||||
// Split string at '=' into 'key' and 'value'. First, find the "=" delimeter.
|
||||
const char *p = strchr(t, '=');
|
||||
if (!p)
|
||||
error("Config file buggy: Junk found in line line %d: '%s'", lineno, t);
|
||||
|
||||
// Trim spaces before the '=' to obtain the key
|
||||
const char *p2 = p;
|
||||
while (p2 > t && isspace(*(p2-1)))
|
||||
p2--;
|
||||
String key(t, p2 - t);
|
||||
|
||||
// Skip spaces after the '='
|
||||
t = p + 1;
|
||||
while (isspace(*t))
|
||||
t++;
|
||||
|
||||
// Trim trailing spaces
|
||||
p2 = t + strlen(t);
|
||||
while (p2 > t && isspace(*(p2-1)))
|
||||
p2--;
|
||||
|
||||
String value(t, p2 - t);
|
||||
|
||||
// Finally, store the key/value pair in the active domain
|
||||
set(key, value, domain);
|
||||
|
||||
// Store comment
|
||||
if (domain == kApplicationDomain) {
|
||||
_appDomain.setKVComment(key, comment);
|
||||
} else {
|
||||
_gameDomains[domain].setKVComment(key, comment);
|
||||
}
|
||||
comment.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigManager::flushToDisk() {
|
||||
#ifndef __DC__
|
||||
File cfg_file;
|
||||
|
||||
// TODO
|
||||
// if (!willwrite)
|
||||
// return;
|
||||
|
||||
if (!cfg_file.open(_filename, File::kFileWriteMode)) {
|
||||
warning("Unable to write configuration file: %s", _filename.c_str());
|
||||
} else {
|
||||
// First write the domains in _domainSaveOrder, in that order.
|
||||
// Note: It's possible for _domainSaveOrder to list domains which
|
||||
// are not present anymore.
|
||||
StringList::const_iterator i;
|
||||
for (i = _domainSaveOrder.begin(); i != _domainSaveOrder.end(); ++i) {
|
||||
if (kApplicationDomain == *i) {
|
||||
writeDomain(cfg_file, *i, _appDomain);
|
||||
} else if (_gameDomains.contains(*i)) {
|
||||
writeDomain(cfg_file, *i, _gameDomains[*i]);
|
||||
}
|
||||
}
|
||||
|
||||
DomainMap::const_iterator d;
|
||||
|
||||
|
||||
// Now write the domains which haven't been written yet
|
||||
if (find(_domainSaveOrder.begin(), _domainSaveOrder.end(), kApplicationDomain) == _domainSaveOrder.end())
|
||||
writeDomain(cfg_file, kApplicationDomain, _appDomain);
|
||||
for (d = _gameDomains.begin(); d != _gameDomains.end(); ++d) {
|
||||
if (find(_domainSaveOrder.begin(), _domainSaveOrder.end(), d->_key) == _domainSaveOrder.end())
|
||||
writeDomain(cfg_file, d->_key, d->_value);
|
||||
}
|
||||
}
|
||||
#endif // !__DC__
|
||||
}
|
||||
|
||||
void ConfigManager::writeDomain(WriteStream &stream, const String &name, const Domain &domain) {
|
||||
if (domain.empty())
|
||||
return; // Don't bother writing empty domains.
|
||||
|
||||
String comment;
|
||||
|
||||
// Write domain comment (if any)
|
||||
comment = domain.getDomainComment();
|
||||
if (!comment.empty())
|
||||
stream.writeString(comment);
|
||||
|
||||
// Write domain start
|
||||
stream.writeByte('[');
|
||||
stream.writeString(name);
|
||||
stream.writeByte(']');
|
||||
stream.writeByte('\n');
|
||||
|
||||
// Write all key/value pairs in this domain, including comments
|
||||
Domain::const_iterator x;
|
||||
for (x = domain.begin(); x != domain.end(); ++x) {
|
||||
if (!x->_value.empty()) {
|
||||
// Write comment (if any)
|
||||
if (domain.hasKVComment(x->_key)) {
|
||||
comment = domain.getKVComment(x->_key);
|
||||
stream.writeString(comment);
|
||||
}
|
||||
// Write the key/value pair
|
||||
stream.writeString(x->_key);
|
||||
stream.writeByte('=');
|
||||
stream.writeString(x->_value);
|
||||
stream.writeByte('\n');
|
||||
}
|
||||
}
|
||||
stream.writeByte('\n');
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
|
||||
|
||||
const ConfigManager::Domain *ConfigManager::getDomain(const String &domName) const {
|
||||
assert(!domName.empty());
|
||||
assert(isValidDomainName(domName));
|
||||
|
||||
if (domName == kTransientDomain)
|
||||
return &_transientDomain;
|
||||
if (domName == kApplicationDomain)
|
||||
return &_appDomain;
|
||||
if (_gameDomains.contains(domName))
|
||||
return &_gameDomains[domName];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
ConfigManager::Domain *ConfigManager::getDomain(const String &domName) {
|
||||
assert(!domName.empty());
|
||||
assert(isValidDomainName(domName));
|
||||
|
||||
if (domName == kTransientDomain)
|
||||
return &_transientDomain;
|
||||
if (domName == kApplicationDomain)
|
||||
return &_appDomain;
|
||||
if (_gameDomains.contains(domName))
|
||||
return &_gameDomains[domName];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
|
||||
|
||||
bool ConfigManager::hasKey(const String &key) const {
|
||||
// Search the domains in the following order:
|
||||
// 1) the transient domain,
|
||||
// 2) the active game domain (if any),
|
||||
// 3) the application domain.
|
||||
// The defaults domain is explicitly *not* checked.
|
||||
|
||||
if (_transientDomain.contains(key))
|
||||
return true;
|
||||
|
||||
if (_activeDomain && _activeDomain->contains(key))
|
||||
return true;
|
||||
|
||||
if (_appDomain.contains(key))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ConfigManager::hasKey(const String &key, const String &domName) const {
|
||||
// FIXME: For now we continue to allow empty domName to indicate
|
||||
// "use 'default' domain". This is mainly needed for the SCUMM ConfigDialog
|
||||
// and should be removed ASAP.
|
||||
if (domName.empty())
|
||||
return hasKey(key);
|
||||
|
||||
const Domain *domain = getDomain(domName);
|
||||
|
||||
if (!domain)
|
||||
return false;
|
||||
return domain->contains(key);
|
||||
}
|
||||
|
||||
void ConfigManager::removeKey(const String &key, const String &domName) {
|
||||
Domain *domain = getDomain(domName);
|
||||
|
||||
if (!domain)
|
||||
error("ConfigManager::removeKey(%s, %s) called on non-existent domain",
|
||||
key.c_str(), domName.c_str());
|
||||
|
||||
domain->erase(key);
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
|
||||
|
||||
const String & ConfigManager::get(const String &key) const {
|
||||
if (_transientDomain.contains(key))
|
||||
return _transientDomain[key];
|
||||
else if (_activeDomain && _activeDomain->contains(key))
|
||||
return (*_activeDomain)[key];
|
||||
else if (_appDomain.contains(key))
|
||||
return _appDomain[key];
|
||||
else if (_defaultsDomain.contains(key))
|
||||
return _defaultsDomain[key];
|
||||
|
||||
#if !(defined(PALMOS_ARM) || defined(PALMOS_DEBUG) || defined(__GP32__))
|
||||
return String::emptyString;
|
||||
#else
|
||||
return ConfMan._emptyString;
|
||||
#endif
|
||||
}
|
||||
|
||||
const String & ConfigManager::get(const String &key, const String &domName) const {
|
||||
// FIXME: For now we continue to allow empty domName to indicate
|
||||
// "use 'default' domain". This is mainly needed for the SCUMM ConfigDialog
|
||||
// and should be removed ASAP.
|
||||
if (domName.empty())
|
||||
return get(key);
|
||||
|
||||
const Domain *domain = getDomain(domName);
|
||||
|
||||
if (!domain)
|
||||
error("ConfigManager::get(%s,%s) called on non-existent domain",
|
||||
key.c_str(), domName.c_str());
|
||||
|
||||
if (domain->contains(key))
|
||||
return (*domain)[key];
|
||||
|
||||
return _defaultsDomain.get(key);
|
||||
|
||||
if (!domain->contains(key)) {
|
||||
#if 1
|
||||
#if !(defined(PALMOS_ARM) || defined(PALMOS_DEBUG) || defined(__GP32__))
|
||||
return String::emptyString;
|
||||
#else
|
||||
return ConfMan._emptyString;
|
||||
#endif
|
||||
#else
|
||||
error("ConfigManager::get(%s,%s) called on non-existent key",
|
||||
key.c_str(), domName.c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
return (*domain)[key];
|
||||
}
|
||||
|
||||
int ConfigManager::getInt(const String &key, const String &domName) const {
|
||||
String value(get(key, domName));
|
||||
char *errpos;
|
||||
|
||||
// For now, be tolerant against missing config keys. Strictly spoken, it is
|
||||
// a bug in the calling code to retrieve an int for a key which isn't even
|
||||
// present... and a default value of 0 seems rather arbitrary.
|
||||
if (value.empty())
|
||||
return 0;
|
||||
|
||||
// We use the special value '0' for the base passed to strtol. Doing that
|
||||
// makes it possible to enter hex values as "0x1234", but also decimal
|
||||
// values ("123") are still valid.
|
||||
int ivalue = (int)strtol(value.c_str(), &errpos, 0);
|
||||
if (value.c_str() == errpos)
|
||||
error("ConfigManager::getInt(%s,%s): '%s' is not a valid integer",
|
||||
key.c_str(), domName.c_str(), errpos);
|
||||
|
||||
return ivalue;
|
||||
}
|
||||
|
||||
bool ConfigManager::getBool(const String &key, const String &domName) const {
|
||||
String value(get(key, domName));
|
||||
|
||||
if ((value == "true") || (value == "yes") || (value == "1"))
|
||||
return true;
|
||||
if ((value == "false") || (value == "no") || (value == "0"))
|
||||
return false;
|
||||
|
||||
error("ConfigManager::getBool(%s,%s): '%s' is not a valid bool",
|
||||
key.c_str(), domName.c_str(), value.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
|
||||
|
||||
void ConfigManager::set(const String &key, const String &value) {
|
||||
// Remove the transient domain value, if any.
|
||||
_transientDomain.erase(key);
|
||||
|
||||
// Write the new key/value pair into the active domain, resp. into
|
||||
// the application domain if no game domain is active.
|
||||
if (_activeDomain)
|
||||
(*_activeDomain)[key] = value;
|
||||
else
|
||||
_appDomain[key] = value;
|
||||
}
|
||||
|
||||
void ConfigManager::set(const String &key, const String &value, const String &domName) {
|
||||
// FIXME: For now we continue to allow empty domName to indicate
|
||||
// "use 'default' domain". This is mainly needed for the SCUMM ConfigDialog
|
||||
// and should be removed ASAP.
|
||||
if (domName.empty()) {
|
||||
set(key, value);
|
||||
return;
|
||||
}
|
||||
|
||||
Domain *domain = getDomain(domName);
|
||||
|
||||
if (!domain)
|
||||
error("ConfigManager::set(%s,%s,%s) called on non-existent domain",
|
||||
key.c_str(), value.c_str(), domName.c_str());
|
||||
|
||||
(*domain)[key] = value;
|
||||
|
||||
// TODO/FIXME: We used to erase the given key from the transient domain
|
||||
// here. Do we still want to do that?
|
||||
// It was probably there to simplify the options dialogs code:
|
||||
// Imagine you are editing the current options (via the SCUMM ConfigDialog,
|
||||
// for example). If you edit the game domain for that, but a matching
|
||||
// entry in the transient domain is present, than your changes may not take
|
||||
// effect. So you want to remove the key from the transient domain before
|
||||
// adding it to the active domain.
|
||||
// But doing this here seems rather evil... need to comb the options dialog
|
||||
// code to find out if it's still necessary, and if that's the case, how
|
||||
// to replace it in a clean fashion...
|
||||
/*
|
||||
if (domName == kTransientDomain)
|
||||
_transientDomain[key] = value;
|
||||
else {
|
||||
if (domName == kApplicationDomain) {
|
||||
_appDomain[key] = value;
|
||||
if (_activeDomainName.empty() || !_gameDomains[_activeDomainName].contains(key))
|
||||
_transientDomain.erase(key);
|
||||
} else {
|
||||
_gameDomains[domName][key] = value;
|
||||
if (domName == _activeDomainName)
|
||||
_transientDomain.erase(key);
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
void ConfigManager::setInt(const String &key, int value, const String &domName) {
|
||||
char tmp[128];
|
||||
snprintf(tmp, sizeof(tmp), "%i", value);
|
||||
set(key, String(tmp), domName);
|
||||
}
|
||||
|
||||
void ConfigManager::setBool(const String &key, bool value, const String &domName) {
|
||||
set(key, String(value ? "true" : "false"), domName);
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
|
||||
|
||||
void ConfigManager::registerDefault(const String &key, const String &value) {
|
||||
_defaultsDomain[key] = value;
|
||||
}
|
||||
|
||||
void ConfigManager::registerDefault(const String &key, const char *value) {
|
||||
registerDefault(key, String(value));
|
||||
}
|
||||
|
||||
void ConfigManager::registerDefault(const String &key, int value) {
|
||||
char tmp[128];
|
||||
snprintf(tmp, sizeof(tmp), "%i", value);
|
||||
registerDefault(key, tmp);
|
||||
}
|
||||
|
||||
void ConfigManager::registerDefault(const String &key, bool value) {
|
||||
registerDefault(key, value ? "true" : "false");
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
|
||||
|
||||
void ConfigManager::setActiveDomain(const String &domName) {
|
||||
if (domName.empty()) {
|
||||
_activeDomain = 0;
|
||||
} else {
|
||||
assert(isValidDomainName(domName));
|
||||
_activeDomain = & _gameDomains[domName];
|
||||
}
|
||||
_activeDomainName = domName;
|
||||
}
|
||||
|
||||
void ConfigManager::addGameDomain(const String &domName) {
|
||||
assert(!domName.empty());
|
||||
assert(isValidDomainName(domName));
|
||||
|
||||
// TODO: Do we want to generate an error/warning if a domain with
|
||||
// the given name already exists?
|
||||
|
||||
_gameDomains[domName];
|
||||
}
|
||||
|
||||
void ConfigManager::removeGameDomain(const String &domName) {
|
||||
assert(!domName.empty());
|
||||
assert(isValidDomainName(domName));
|
||||
_gameDomains.erase(domName);
|
||||
}
|
||||
|
||||
void ConfigManager::renameGameDomain(const String &oldName, const String &newName) {
|
||||
if (oldName == newName)
|
||||
return;
|
||||
|
||||
assert(!oldName.empty());
|
||||
assert(!newName.empty());
|
||||
assert(isValidDomainName(oldName));
|
||||
assert(isValidDomainName(newName));
|
||||
|
||||
// _gameDomains[newName].merge(_gameDomains[oldName]);
|
||||
Domain &oldDom = _gameDomains[oldName];
|
||||
Domain &newDom = _gameDomains[newName];
|
||||
Domain::const_iterator iter;
|
||||
for (iter = oldDom.begin(); iter != oldDom.end(); ++iter)
|
||||
newDom[iter->_key] = iter->_value;
|
||||
|
||||
_gameDomains.erase(oldName);
|
||||
}
|
||||
|
||||
bool ConfigManager::hasGameDomain(const String &domName) const {
|
||||
assert(!domName.empty());
|
||||
return isValidDomainName(domName) && _gameDomains.contains(domName);
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
|
||||
|
||||
const String &ConfigManager::Domain::get(const String &key) const {
|
||||
const_iterator iter(find(key));
|
||||
if (iter != end())
|
||||
return iter->_value;
|
||||
|
||||
#if !(defined(PALMOS_ARM) || defined(PALMOS_DEBUG) || defined(__GP32__))
|
||||
return String::emptyString;
|
||||
#else
|
||||
return ConfMan._emptyString;
|
||||
#endif
|
||||
}
|
||||
|
||||
void ConfigManager::Domain::setDomainComment(const String &comment) {
|
||||
_domainComment = comment;
|
||||
}
|
||||
const String &ConfigManager::Domain::getDomainComment() const {
|
||||
return _domainComment;
|
||||
}
|
||||
|
||||
void ConfigManager::Domain::setKVComment(const String &key, const String &comment) {
|
||||
_keyValueComments[key] = comment;
|
||||
}
|
||||
const String &ConfigManager::Domain::getKVComment(const String &key) const {
|
||||
return _keyValueComments[key];
|
||||
}
|
||||
bool ConfigManager::Domain::hasKVComment(const String &key) const {
|
||||
return _keyValueComments.contains(key);
|
||||
}
|
||||
|
||||
} // End of namespace Common
|
179
common/config-manager.h
Normal file
179
common/config-manager.h
Normal file
@ -0,0 +1,179 @@
|
||||
/* Residual - Virtual machine to run LucasArts' 3D adventure games
|
||||
*
|
||||
* Residual is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the AUTHORS
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* $URL$
|
||||
* $Id$
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef COMMON_CONFIG_MANAGER_H
|
||||
#define COMMON_CONFIG_MANAGER_H
|
||||
|
||||
#include "common/array.h"
|
||||
#include "common/hashmap.h"
|
||||
#include "common/singleton.h"
|
||||
#include "common/str.h"
|
||||
#include "common/hash-str.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
class WriteStream;
|
||||
|
||||
|
||||
/**
|
||||
* The (singleton) configuration manager, used to query & set configuration
|
||||
* values using string keys.
|
||||
*
|
||||
* @todo Implement the callback based notification system (outlined below)
|
||||
* which sends out notifications to interested parties whenever the value
|
||||
* of some specific (or any) configuration key changes.
|
||||
*/
|
||||
class ConfigManager : public Singleton<ConfigManager> {
|
||||
|
||||
public:
|
||||
|
||||
class Domain : public StringMap {
|
||||
private:
|
||||
StringMap _keyValueComments;
|
||||
String _domainComment;
|
||||
|
||||
public:
|
||||
const String &get(const String &key) const;
|
||||
|
||||
void setDomainComment(const String &comment);
|
||||
const String &getDomainComment() const;
|
||||
|
||||
void setKVComment(const String &key, const String &comment);
|
||||
const String &getKVComment(const String &key) const;
|
||||
bool hasKVComment(const String &key) const;
|
||||
};
|
||||
|
||||
typedef HashMap<String, Domain, IgnoreCase_Hash, IgnoreCase_EqualTo> DomainMap;
|
||||
|
||||
#if !(defined(PALMOS_ARM) || defined(PALMOS_DEBUG) || defined(__GP32__))
|
||||
/** The name of the application domain (normally 'scummvm'). */
|
||||
static const String kApplicationDomain;
|
||||
|
||||
/** The transient (pseudo) domain. */
|
||||
static const String kTransientDomain;
|
||||
#else
|
||||
static const char *kApplicationDomain;
|
||||
static const char *kTransientDomain;
|
||||
|
||||
const String _emptyString;
|
||||
#endif
|
||||
|
||||
void loadDefaultConfigFile();
|
||||
void loadConfigFile(const String &filename);
|
||||
|
||||
/**
|
||||
* Retrieve the config domain with the given name.
|
||||
* @param domName the name of the domain to retrieve
|
||||
* @return pointer to the domain, or 0 if the domain doesn't exist.
|
||||
*/
|
||||
Domain * getDomain(const String &domName);
|
||||
const Domain * getDomain(const String &domName) const;
|
||||
|
||||
|
||||
//
|
||||
// Generic access methods: No domain specified, use the values from the
|
||||
// various domains in the order of their priority.
|
||||
//
|
||||
|
||||
bool hasKey(const String &key) const;
|
||||
const String & get(const String &key) const;
|
||||
void set(const String &key, const String &value);
|
||||
|
||||
#if 1
|
||||
//
|
||||
// Domain specific access methods: Acces *one specific* domain and modify it.
|
||||
// TODO: I'd like to get rid of most of those if possible, or at least reduce
|
||||
// their usage, by using getDomain as often as possible. For example in the
|
||||
// options dialog code...
|
||||
//
|
||||
|
||||
bool hasKey(const String &key, const String &domName) const;
|
||||
const String & get(const String &key, const String &domName) const;
|
||||
void set(const String &key, const String &value, const String &domName);
|
||||
|
||||
void removeKey(const String &key, const String &domName);
|
||||
#endif
|
||||
|
||||
//
|
||||
// Some additional convenience accessors.
|
||||
//
|
||||
int getInt(const String &key, const String &domName = String::emptyString) const;
|
||||
bool getBool(const String &key, const String &domName = String::emptyString) const;
|
||||
void setInt(const String &key, int value, const String &domName = String::emptyString);
|
||||
void setBool(const String &key, bool value, const String &domName = String::emptyString);
|
||||
|
||||
|
||||
void registerDefault(const String &key, const String &value);
|
||||
void registerDefault(const String &key, const char *value);
|
||||
void registerDefault(const String &key, int value);
|
||||
void registerDefault(const String &key, bool value);
|
||||
|
||||
void flushToDisk();
|
||||
|
||||
void setActiveDomain(const String &domName);
|
||||
Domain * getActiveDomain() { return _activeDomain; }
|
||||
const Domain * getActiveDomain() const { return _activeDomain; }
|
||||
const String & getActiveDomainName() const { return _activeDomainName; }
|
||||
|
||||
void addGameDomain(const String &domName);
|
||||
void removeGameDomain(const String &domName);
|
||||
void renameGameDomain(const String &oldName, const String &newName);
|
||||
bool hasGameDomain(const String &domName) const;
|
||||
const DomainMap & getGameDomains() const { return _gameDomains; }
|
||||
|
||||
/*
|
||||
TODO: Callback/change notification system
|
||||
typedef void (*ConfigCallback)(const ConstString &key, void *refCon);
|
||||
|
||||
void registerCallback(ConfigCallback cfgc, void *refCon, const ConstString &key = String::emptyString)
|
||||
void unregisterCallback(ConfigCallback cfgc, const ConstString &key = String::emptyString)
|
||||
*/
|
||||
|
||||
private:
|
||||
friend class Singleton<SingletonBaseType>;
|
||||
ConfigManager();
|
||||
|
||||
void loadFile(const String &filename);
|
||||
void writeDomain(WriteStream &stream, const String &name, const Domain &domain);
|
||||
|
||||
Domain _transientDomain;
|
||||
DomainMap _gameDomains;
|
||||
Domain _appDomain;
|
||||
Domain _defaultsDomain;
|
||||
|
||||
StringList _domainSaveOrder;
|
||||
|
||||
String _activeDomainName;
|
||||
Domain * _activeDomain;
|
||||
|
||||
String _filename;
|
||||
};
|
||||
|
||||
} // End of namespace Common
|
||||
|
||||
/** Shortcut for accessing the configuration manager. */
|
||||
#define ConfMan Common::ConfigManager::instance()
|
||||
|
||||
#endif
|
@ -115,6 +115,7 @@ extern "C" int residual_main(int argc, char *argv[]);
|
||||
#elif defined(_WIN32_WCE)
|
||||
|
||||
#define strcasecmp stricmp
|
||||
#define snprintf _snprintf
|
||||
|
||||
#define SYSTEM_LITTLE_ENDIAN
|
||||
#define SYSTEM_NEED_ALIGNMENT
|
||||
@ -129,6 +130,7 @@ extern "C" int residual_main(int argc, char *argv[]);
|
||||
#elif defined(_MSC_VER)
|
||||
|
||||
#define strcasecmp stricmp
|
||||
#define snprintf _snprintf
|
||||
|
||||
#define SYSTEM_LITTLE_ENDIAN
|
||||
|
||||
|
@ -117,6 +117,18 @@
|
||||
<File
|
||||
RelativePath="..\..\common\array.h">
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\common\config-file.cpp">
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\common\config-file.h">
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\common\config-manager.cpp">
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\common\config-manager.h">
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\common\debug.cpp">
|
||||
</File>
|
||||
|
@ -131,6 +131,18 @@
|
||||
<File
|
||||
RelativePath="..\..\common\array.h">
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\common\config-file.cpp">
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\common\config-file.h">
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\common\config-manager.cpp">
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\common\config-manager.h">
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\common\debug.cpp">
|
||||
</File>
|
||||
|
@ -195,6 +195,22 @@
|
||||
RelativePath="..\..\common\array.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\common\config-file.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\common\config-file.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\common\config-manager.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\common\config-manager.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\common\debug.cpp"
|
||||
>
|
||||
|
@ -200,6 +200,22 @@
|
||||
RelativePath="..\..\common\array.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\common\config-file.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\common\config-file.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\common\config-manager.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\common\config-manager.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\common\debug.cpp"
|
||||
>
|
||||
|
Loading…
x
Reference in New Issue
Block a user