/****************************************************************************
** $Id$
**
** Implementation of QSettings class
**
** Created: 2000.06.26
**
** Copyright (C) 2000-2002 Trolltech AS.  All rights reserved.
**
** This file is part of the tools module of the Qt GUI Toolkit.
**
** This file may be distributed under the terms of the Q Public License
** as defined by Trolltech AS of Norway and appearing in the file
** LICENSE.QPL included in the packaging of this file.
**
** This file may be distributed and/or modified under the terms of the
** GNU General Public License version 2 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.
**
** Licensees holding valid Qt Enterprise Edition or Qt Professional Edition
** licenses may use this file in accordance with the Qt Commercial License
** Agreement provided with the Software.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
**   information about Qt Commercial License Agreements.
** See http://www.trolltech.com/qpl/ for QPL licensing information.
** See http://www.trolltech.com/gpl/ for GPL licensing information.
**
** Contact info@trolltech.com if any conditions of this licensing are
** not clear to you.
**
**********************************************************************/

#include "qplatformdefs.h"
#include "qsettings.h"

#ifndef QT_NO_SETTINGS

#include "qdir.h"
#include "qfile.h"
#include "qfileinfo.h"
#include "qmap.h"
#include "qtextstream.h"
#include "qcleanuphandler.h"
#include "qregexp.h"
#include <errno.h>

// for now
#define QSETTINGS_DEFAULT_PATH_SUFFIX "/etc/settings"

/*!
    \class QSettings
    \brief The QSettings class provides persistent platform-independent application settings.

    \ingroup io
    \ingroup misc
    \mainclass

    On Unix systems (including Mac OS X), QSettings uses text files to
    store settings. On Windows systems, QSettings uses the system
    registry.

    Each setting comprises an identifying key and the data associated
    with the key. A key is a Unicode string which consists of \e two
    or more subkeys. A subkey is a slash, '/', followed by one or more
    Unicode characters (excluding slashes, newlines, carriage returns
    and equals, '=', signs). The associated data, called the entry or
    value, may be a boolean, an integer, a double, a string or a list
    of strings. Entry strings may contain any Unicode characters.

    If you want to save and restore the entire desktop's settings,
    i.e. which applications are running, use QSettings to save the
    settings for each individual application and QSessionManager to
    save the desktop's session.

    Example settings:
    \code
    /MyCompany/MyApplication/background color
    /MyCompany/MyApplication/foreground color
    /MyCompany/MyApplication/geometry/x
    /MyCompany/MyApplication/geometry/y
    /MyCompany/MyApplication/geometry/width
    /MyCompany/MyApplication/geometry/height
    /MyCompany/MyApplication/recent files/1
    /MyCompany/MyApplication/recent files/2
    /MyCompany/MyApplication/recent files/3
    \endcode
    Each line above is a complete key, made up of subkeys.

    A typical usage pattern for application startup:
    \code
    QSettings settings;
    settings.insertSearchPath( QSettings::Windows, "/MyCompany" );
    // No search path needed for Unix; see notes further on.
    // Use default values if the keys don't exist
    QString bgColor = settings.readEntry( "/MyApplication/background color", "white" );
    int width = settings.readNumEntry( "/MyApplication/geometry/width", 640 );
    // ...
    \endcode

    A typical usage pattern for application exit or 'save preferences':
    \code
    QSettings settings;
    settings.insertSearchPath( QSettings::Windows, "/MyCompany" );
    // No search path needed for Unix; see notes further on.
    settings.writeEntry( "/MyApplication/background color", bgColor );
    settings.writeEntry( "/MyApplication/geometry/width", width );
    // ...
    \endcode

    You can get a list of entry-holding keys by calling entryList(),
    and a list of key-holding keys using subkeyList().

    \code
    QStringList keys = entryList( "/MyApplication" );
    // keys contains 'background color' and 'foreground color'.

    QStringList keys = entryList( "/MyApplication/recent files" );
    // keys contains '1', '2' and '3'.

    QStringList subkeys = subkeyList( "/MyApplication" );
    // subkeys contains 'geometry' and 'recent files'

    QStringList subkeys = subkeyList( "/MyApplication/recent files" );
    // subkeys is empty.
    \endcode

    If you wish to use a different search path call insertSearchPath()
    as often as necessary to add your preferred paths. Call
    removeSearchPath() to remove any unwanted paths.

    Since settings for Windows are stored in the registry some size
    limits apply:
    \list
    \i A subkey may not exceed 255 characters.
    \i An entry's value may not exceed 16,300 characters.
    \i All the values of a key (for example, all the 'recent files'
    subkeys values), may not exceed 65,535 characters.
    \endlist

    These limitations are not enforced on Unix.

    \section1 Notes for Unix Applications

    There is no universally accepted place for storing application
    settings under Unix. In the examples the settings file will be
    searched for in the following directories:
    \list 1
    \i $QT_INSTALL_PREFIX/etc/settings
    \i /opt/MyCompany/share/etc
    \i /opt/MyCompany/share/MyApplication/etc
    \i $HOME/.qt
    \endlist
    When reading settings the files are searched in the order shown
    above, with later settings overriding earlier settings. Files for
    which the user doesn't have read permission are ignored. When
    saving settings QSettings works in the order shown above, writing
    to the first settings file for which the user has write
    permission. ($QT_INSTALL_PREFIX is the directory where Qt was
    installed. This can be modified by using the configure script's
    -prefix argument)

    If you want to put the settings in a particular place in the
    filesystem you could do this:
    \code
    settings.insertSearchPath( QSettings::Unix, "/opt/MyCompany/share" );
    \endcode

    But in practice you may prefer not to use a search path for Unix.
    For example the following code:
    \code
    settings.writeEntry( "/MyApplication/geometry/width", width );
    \endcode
    will end up writing the "geometry/width" setting to the file
    \c{$HOME/.qt/myapplicationrc} (assuming that the application is
    being run by an ordinary user, i.e. not by root).

    For multiplatform applications you should ensure that the Windows
    size limitations are not exceeded.
*/

/*!
    \enum QSettings::System

    \value Mac Macintosh execution environments (internal)
    \value Unix Mac OS X, Unix, Linux and Unix-like execution environments
    \value Windows Windows execution environments
*/


/*
  Locks the file specified by name. The lockfile is created as a
  hidden file in the same directory as the target file, with .lock
  appended to the name. For example, "/etc/settings/onerc" uses a
  lockfile named "/etc/settings/.onerc.lock". The type argument
  controls the type of the lock, it can be either F_RDLCK for a read
  lock, or F_WRLCK for a write lock.

  A file descriptor for the lock file is returned, and should be
  closed with closelock() when the lock is no longer needed.
 */
static int openlock( const QString &name, int type )
{
    QFileInfo info( name );
    // lockfile should be hidden, and never removed
    QString lockfile = info.dirPath() + "/." + info.fileName() + ".lock";

    // open the lockfile
    int fd = open( QFile::encodeName( lockfile ),
		   O_RDWR | O_CREAT, S_IRUSR | S_IWUSR );

    if ( fd < 0 ) {
 	// failed to open the lock file, most likely because of permissions
	return fd;
    }

    struct flock fl;
    fl.l_type = type;
    fl.l_whence = SEEK_SET;
    fl.l_start = 0;
    fl.l_len = 0;
    if ( fcntl( fd, F_SETLKW, &fl ) == -1 ) {
	// the lock failed, so we should fail silently, so that people
	// using filesystems that do not support locking don't see
	// numerous warnings about a failed lock
	close( fd );
	fd = -1;
    }

    return fd;
}

/*
  Closes the lock file specified by fd. fd is the file descriptor
  returned by the openlock() function.
*/
static void closelock( int fd )
{
    if ( fd < 0 ) {
	// the lock file is not open
	return;
    }

    struct flock fl;
    fl.l_type = F_UNLCK;
    fl.l_whence = SEEK_SET;
    fl.l_start = 0;
    fl.l_len = 0;
    // ignore the return value, so that the unlock fails silently
    (void) fcntl( fd, F_SETLKW, &fl );

    close( fd );
}


// QSettingsGroup is a map of key/value pairs
class QSettingsGroup : public QMap<QString,QString>
{
public:
    QSettingsGroup();

    bool modified;
};


QSettingsGroup::QSettingsGroup()
    : modified(FALSE)
{
}


// QSettingsHeading is a map of heading/group pairs
class QSettingsHeading : public QMap<QString,QSettingsGroup>
{
public:
    QSettingsHeading::Iterator git;
    void read(const QString &);
    void parseLine(QTextStream &);
};


void QSettingsHeading::read(const QString &filename)
{
    if (! QFileInfo(filename).exists())
	return;

    int lockfd = openlock( filename, F_RDLCK );

    QFile file(filename);
    if (! file.open(IO_ReadOnly)) {
	qWarning("QSettings: failed to open file '%s'", filename.latin1());
	return;
    }

    git = end();

    QTextStream stream(&file);
    stream.setEncoding(QTextStream::UnicodeUTF8);
    while (! stream.atEnd())
	parseLine(stream);

    git = end();

    file.close();

    closelock( lockfd );
}


void QSettingsHeading::parseLine(QTextStream &stream)
{
    QString line = stream.readLine();
    if (line.isEmpty())
	// empty line... we'll allow it
	return;

    if (line[0] == QChar('#'))
	// commented line
	return;

    if (line[0] == QChar('[')) {
	QString gname = line;

	gname = gname.remove(0, 1);
	if (gname[(int)gname.length() - 1] == QChar(']'))
	    gname = gname.remove(gname.length() - 1, 1);

	git = find(gname);
	if (git == end())
	    git = replace(gname, QSettingsGroup());
    } else {
	if (git == end()) {
	    qWarning("QSettings: line '%s' out of group", line.latin1());

	    return;
	}

	int i = line.find('=');
       	if (i == -1) {
	    qWarning("QSettings: malformed line '%s' in group '%s'",
		     line.latin1(), git.key().latin1());
	    return;
	} else {
	    QString key, value;
	    key = line.left(i);
	    value = "";
	    bool esc=TRUE;
	    i++;
	    while (esc) {
		esc = FALSE;
		for ( ; i < (int)line.length(); i++ ) {
		    if ( esc ) {
			if ( line[i] == 'n' )
			    value.append('\n'); // escaped newline
			else if ( line[i] == '0' )
			    value = QString::null; // escaped empty string
			else
			    value.append(line[i]);
			esc = FALSE;
		    } else if ( line[i] == '\\' )
			esc = TRUE;
		    else
			value.append(line[i]);
		}
		if ( esc ) {
		    // Backwards-compatiblity...
		    // still escaped at EOL - manually escaped "newline"
		    if (stream.atEnd()) {
			qWarning("QSettings: reached end of file, expected continued line");
			break;
		    }
		    value.append('\n');
		    line = stream.readLine();
		    i = 0;
		}
	    }

	    (*git).insert(key, value);
	}
    }
}


class QSettingsPrivate
{
public:
    QSettingsPrivate();

    QSettingsGroup readGroup();
    void removeGroup(const QString &);
    void writeGroup(const QString &, const QString &);
    QDateTime modificationTime();

    QStringList searchPaths;
    QMap<QString,QSettingsHeading> headings;
    QString group;
    QString heading;
    bool modified;
};


QSettingsPrivate::QSettingsPrivate()
    : modified(FALSE)
{
    QDir dir(QDir::homeDirPath() + "/.qt/");
    if (! dir.exists()) {
	if (! dir.mkdir(dir.path()))
	    qWarning("QSettings: error creating %s", dir.path().latin1());
    }

#ifndef QT_INSTALL_PREFIX
#  define QT_INSTALL_PREFIX "/usr/local/qt"
#endif // QT_INSTALL_PREFIX

    QString defpath(QT_INSTALL_PREFIX);
    defpath += QSETTINGS_DEFAULT_PATH_SUFFIX;
    searchPaths.append(defpath);
    searchPaths.append(dir.path());
}


QSettingsGroup QSettingsPrivate::readGroup()
{
    QSettingsHeading hd;
    QSettingsGroup grp;

    QMap<QString,QSettingsHeading>::Iterator headingsit = headings.find(heading);
    if (headingsit != headings.end())
	hd = *headingsit;

    QSettingsHeading::Iterator grpit = hd.find(group);
    if (grpit == hd.end()) {
	QStringList::Iterator it = searchPaths.begin();
	while (it != searchPaths.end()) {
	    QString filebase = heading.lower().replace(QRegExp("\\s+"), "_");
	    QString fn((*it++) + "/" + filebase + "rc");
	    if (! hd.contains(fn + "cached")) {
		hd.read(fn);
		hd.insert(fn + "cached", QSettingsGroup());
	    }
	}

	headings.replace(heading, hd);

	grpit = hd.find(group);
	if (grpit != hd.end())
	    grp = *grpit;
    } else if (hd.count() != 0)
	grp = *grpit;

    return grp;
}


void QSettingsPrivate::removeGroup(const QString &key)
{
    QSettingsHeading hd;
    QSettingsGroup grp;
    bool found = FALSE;

    QMap<QString,QSettingsHeading>::Iterator headingsit = headings.find(heading);
    if (headingsit != headings.end())
	hd = *headingsit;

    QSettingsHeading::Iterator grpit = hd.find(group);
    if (grpit == hd.end()) {
	QStringList::Iterator it = searchPaths.begin();
	while (it != searchPaths.end()) {
	    QString filebase = heading.lower().replace(QRegExp("\\s+"), "_");
	    QString fn((*it++) + "/" + filebase + "rc");
	    if (! hd.contains(fn + "cached")) {
		hd.read(fn);
		hd.insert(fn + "cached", QSettingsGroup());
	    }
	}

	headings.replace(heading, hd);

	grpit = hd.find(group);
	if (grpit != hd.end()) {
	    found = TRUE;
	    grp = *grpit;
	}
    } else if (hd.count() != 0) {
	found = TRUE;
	grp = *grpit;
    }

    if (found) {
	grp.remove(key);

	if (grp.count() > 0)
	    hd.replace(group, grp);
	else
	    hd.remove(group);

	if (hd.count() > 0)
	    headings.replace(heading, hd);
	else
	    headings.remove(heading);

	modified = TRUE;
    }
}


void QSettingsPrivate::writeGroup(const QString &key, const QString &value)
{
    QSettingsHeading hd;
    QSettingsGroup grp;

    QMap<QString,QSettingsHeading>::Iterator headingsit = headings.find(heading);
    if (headingsit != headings.end())
	hd = *headingsit;

    QSettingsHeading::Iterator grpit = hd.find(group);
    if (grpit == hd.end()) {
	QStringList::Iterator it = searchPaths.begin();
	while (it != searchPaths.end()) {
	    QString filebase = heading.lower().replace(QRegExp("\\s+"), "_");
	    QString fn((*it++) + "/" + filebase + "rc");
	    if (! hd.contains(fn + "cached")) {
		hd.read(fn);
		hd.insert(fn + "cached", QSettingsGroup());
	    }
	}

	headings.replace(heading, hd);

	grpit = hd.find(group);
	if (grpit != hd.end())
	    grp = *grpit;
    } else if (hd.count() != 0)
	grp = *grpit;

    grp.modified = TRUE;
    grp.replace(key, value);
    hd.replace(group, grp);
    headings.replace(heading, hd);

    modified = TRUE;
}


QDateTime QSettingsPrivate::modificationTime()
{
    QSettingsHeading hd = headings[heading];
    QSettingsGroup grp = hd[group];

    QDateTime datetime;

    QStringList::Iterator it = searchPaths.begin();
    while (it != searchPaths.end()) {
	QFileInfo fi((*it++) + "/" + heading + "rc");
	if (fi.exists() && fi.lastModified() > datetime)
	    datetime = fi.lastModified();
    }

    return datetime;
}


/*!
    Inserts \a path into the settings search path. The semantics of \a
    path depends on the system \a s.

    When \a s is \e Windows and the execution environment is \e not
    Windows the function does nothing. Similarly when \a s is \e Unix
    and the execution environment is \e not Unix the function does
    nothing.

    When \a s is \e Windows, and the execution environment is Windows,
    the search path list will be used as the first subfolder of the
    "Software" folder in the registry.

    When reading settings the folders are searched forwards from the
    first folder (listed below) to the last, returning the first
    settings found, and ignoring any folders for which the user doesn't 
    have read permission.
    \list 1
    \i HKEY_CURRENT_USER/Software/MyCompany/MyApplication
    \i HKEY_LOCAL_MACHINE/Software/MyCompany/MyApplication
    \i HKEY_CURRENT_USER/Software/MyApplication
    \i HKEY_LOCAL_MACHINE/Software/MyApplication
    \endlist

    \code
    QSettings settings;
    settings.insertSearchPath( QSettings::Windows, "/MyCompany" );
    settings.writeEntry( "/MyApplication/Tip of the day", TRUE );
    \endcode
    The code above will write the subkey "Tip of the day" into the \e
    first of the registry folders listed below that is found and for
    which the user has write permission.
    \list 1
    \i HKEY_LOCAL_MACHINE/Software/MyCompany/MyApplication
    \i HKEY_CURRENT_USER/Software/MyCompany/MyApplication
    \i HKEY_LOCAL_MACHINE/Software/MyApplication
    \i HKEY_CURRENT_USER/Software/MyApplication
    \endlist
    If a setting is found in the HKEY_CURRENT_USER space, this setting
    is overwritten independently of write permissions in the 
    HKEY_LOCAL_MACHINE space.

    When \a s is \e Unix, and the execution environment is Unix, the
    search path list will be used when trying to determine a suitable
    filename for reading and writing settings files. By default, there
    are two entries in the search path:

    \list 1
    \i \c $QTDIR/etc - where \c $QTDIR is the directory where Qt was installed.
    \i \c $HOME/.qt/ - where \c $HOME is the user's home directory.
    \endlist

    All insertions into the search path will go before $HOME/.qt/.
    For example:
    \code
    QSettings settings;
    settings.insertSearchPath( QSettings::Unix, "/opt/MyCompany/share/etc" );
    settings.insertSearchPath( QSettings::Unix, "/opt/MyCompany/share/MyApplication/etc" );
    // ...
    \endcode
    Will result in a search path of:
    \list 1
    \i $QTDIR/etc
    \i /opt/MyCompany/share/etc
    \i /opt/MyCompany/share/MyApplication/etc
    \i $HOME/.qt
    \endlist
    When reading settings the files are searched in the order shown
    above, with later settings overriding earlier settings. Files for
    which the user doesn't have read permission are ignored. When
    saving settings QSettings works in the order as shown above,
    writing to the first settings file for which the user has write
    permission. ($QTDIR is the directory where Qt was installed.)

    Settings under Unix are stored in files whose names are based on
    the first subkey of the key (not including the search path). The
    algorithm for creating names is essentially: lowercase the first
    subkey, replace spaces with underscores and add 'rc', e.g.
    \c{/MyCompany/MyApplication/background color} will be stored
    in \c{myapplicationrc} (assuming that \c{/MyCompany} is
    part of the search path).

    \sa removeSearchPath()

*/
void QSettings::insertSearchPath( System s, const QString &path)
{
    if ( s != Unix ) {
#ifdef Q_OS_MAC
	if(s != Mac) //mac is respected on the mac as well
#endif
	    return;
    }

    QStringList::Iterator it = d->searchPaths.find(d->searchPaths.last());
    if (it != d->searchPaths.end()) {
	d->searchPaths.insert(it, path);
    }
}


/*!
    Removes all occurrences of \a path (using exact matching) from the
    settings search path for system \a s. Note that the default search
    paths cannot be removed.

    \sa insertSearchPath()
*/
void QSettings::removeSearchPath( System s, const QString &path)
{
    if ( s != Unix ) {
#ifdef Q_OS_MAC
	if(s != Mac) //mac is respected on the mac as well
#endif
	    return;
    }

    if (path == d->searchPaths.first() || path == d->searchPaths.last())
	return;

    d->searchPaths.remove(path);
}


/*!
    Creates a settings object.
*/
QSettings::QSettings()
{
    d = new QSettingsPrivate;
    Q_CHECK_PTR(d);
}

/*!
    Destroys the settings object. All modifications made to the
    settings will automatically be saved.
*/
QSettings::~QSettings()
{
    sync();

    delete d;
}


/*! \internal
  Writes all modifications to the settings to disk. If any errors are
  encountered, this function returns FALSE, otherwise it will return TRUE.
*/
bool QSettings::sync()
{
    if (! d->modified)
	// fake success
	return TRUE;

    bool success = TRUE;
    QMap<QString,QSettingsHeading>::Iterator it = d->headings.begin();

    while (it != d->headings.end()) {
	// determine filename
	QSettingsHeading hd(*it);
	QSettingsHeading::Iterator hdit = hd.begin();
	QFile file;

	QStringList::Iterator pit = d->searchPaths.begin();
	while (pit != d->searchPaths.end()) {
	    QString filebase = it.key().lower().replace(QRegExp("\\s+"), "_");
	    QFileInfo di(*pit);
	    QFileInfo fi((*pit++) + "/" + filebase + "rc");

	    if ((fi.exists() && fi.isFile() && fi.isWritable()) ||
		(! fi.exists() && di.isDir() && di.isWritable())) {
		file.setName(fi.filePath());
		break;
	    }
	}

	it++;

	if (file.name().isNull() || file.name().isEmpty()) {

#ifdef QT_CHECK_STATE
	    qWarning("QSettings::sync: filename is null/empty");
#endif // QT_CHECK_STATE

	    success = FALSE;
	    continue;
	}

	int lockfd = openlock( file.name(), F_WRLCK );

	if (! file.open(IO_WriteOnly)) {

#ifdef QT_CHECK_STATE
	    qWarning("QSettings::sync: failed to open '%s' for writing",
		     file.name().latin1());
#endif // QT_CHECK_STATE

	    success = FALSE;
	    continue;
	}

	// spew to file
	QTextStream stream(&file);
	stream.setEncoding(QTextStream::UnicodeUTF8);

	while (hdit != hd.end()) {
	    if ((*hdit).count() > 0) {
		stream << "[" << hdit.key() << "]" << endl;

		QSettingsGroup grp(*hdit);
		QSettingsGroup::Iterator grpit = grp.begin();

		while (grpit != grp.end()) {
		    QString v = grpit.data();
		    if ( v.isNull() ) {
			v = "\\0"; // escape null string
		    } else {
			v.replace(QRegExp("\\\\"), "\\\\"); // escape backslash
			v.replace(QRegExp("\n"), "\\n"); // escape newlines
		    }

		    stream << grpit.key() << "=" << v << endl;
		    grpit++;
		}

		stream << endl;
	    }

	    hdit++;
	}

	if (file.status() != IO_Ok) {

#ifdef QT_CHECK_STATE
	    qWarning("QSettings::sync: error at end of write");
#endif // QT_CHECK_STATE

	    success = FALSE;
	}

	file.close();

	closelock( lockfd );
    }

    d->modified = FALSE;

    return success;
}


/*!
    Reads the entry specified by \a key, and returns a bool, or the
    default value, \a def, if the entry couldn't be read. If \a ok is
    not 0: \a *ok is set to TRUE if the key was read, otherwise \a *ok
    is set to FALSE.

    \sa readEntry(), readNumEntry(), readDoubleEntry(), writeEntry(), removeEntry()
*/
bool QSettings::readBoolEntry(const QString &key, bool def, bool *ok )
{
    QString value = readEntry( key, ( def ? "true" : "false" ), ok );

    if (value.lower() == "true")
	return TRUE;
    else if (value.lower() == "false")
	return FALSE;
    else if (value == "1")
	return TRUE;
    else if (value == "0")
	return FALSE;

    if (! value.isEmpty())
	qWarning("QSettings::readBoolEntry: '%s' is not 'true' or 'false'",
		 value.latin1());
    if ( ok )
	*ok = FALSE;
    return def;
}


/*!
    Reads the entry specified by \a key, and returns a double, or the
    default value, \a def, if the entry couldn't be read. If \a ok is
    not 0: \a *ok is set to TRUE if the key was read, otherwise \a *ok
    is set to FALSE.

    \sa readEntry(), readNumEntry(), readBoolEntry(), writeEntry(),
    removeEntry()
*/
double QSettings::readDoubleEntry(const QString &key, double def, bool *ok )
{
    QString value = readEntry( key, QString::number(def), ok );
    bool conv_ok;
    double retval = value.toDouble( &conv_ok );
    if ( conv_ok )
	return retval;
    if ( ! value.isEmpty() )
	qWarning( "QSettings::readDoubleEntry: '%s' is not a number",
		  value.latin1() );
    if ( ok )
	*ok = FALSE;
    return def;
}


/*!
    Reads the entry specified by \a key, and returns an integer, or
    the default value, \a def, if the entry couldn't be read. If \a ok
    is not 0: \a *ok is set to TRUE if the key was read, otherwise \a *ok
    is set to FALSE.

    \sa readEntry(), readDoubleEntry(), readBoolEntry(), writeEntry(),
    removeEntry()
*/
int QSettings::readNumEntry(const QString &key, int def, bool *ok )
{
    QString value = readEntry( key, QString::number( def ), ok );
    bool conv_ok;
    int retval = value.toInt( &conv_ok );
    if ( conv_ok )
	return retval;
    if ( ! value.isEmpty() )
	qWarning( "QSettings::readNumEntry: '%s' is not a number",
		  value.latin1() );
    if ( ok )
	*ok = FALSE;
    return def;
}


/*!
    Reads the entry specified by \a key, and returns a QString, or the
    default value, \a def, if the entry couldn't be read. If \a ok is
    not 0: \a *ok is set to TRUE if the key was read, otherwise \a *ok
    is set to FALSE.

    \sa readListEntry(), readNumEntry(), readDoubleEntry(), readBoolEntry(), writeEntry(), removeEntry()
*/
QString QSettings::readEntry(const QString &key, const QString &def, bool *ok )
{
    if ( ok ) // no, everything is not ok
	*ok = FALSE;

    if (key.isNull() || key.isEmpty()) {
#ifdef QT_CHECK_STATE
	qWarning("QSettings::readEntry: invalid null/empty key.");
#endif // QT_CHECK_STATE
	return def;
    }

    QString realkey;

    if (key[0] == '/') {
	// parse our key
	QStringList list(QStringList::split('/', key));

	if (list.count() < 2) {
#ifdef QT_CHECK_STATE
	    qWarning("QSettings::readEntry: invalid key '%s'", key.latin1());
#endif // QT_CHECK_STATE
	    if ( ok )
		*ok = FALSE;
	    return def;
	}

	if (list.count() == 2) {
	    d->heading = list[0];
	    d->group = "General";
	    realkey = list[1];
	} else {
	    d->heading = list[0];
	    d->group = list[1];

	    // remove the group from the list
	    list.remove(list.at(1));
	    // remove the heading from the list
	    list.remove(list.at(0));

	    realkey = list.join("/");
	}
    } else
    	realkey = key;

    QSettingsGroup grp = d->readGroup();
    QString retval = grp[realkey];
    if ( retval.isNull() )
	retval = def;
    else if ( ok ) // everything is ok
	*ok = TRUE;
    return retval;
}


#if !defined(Q_NO_BOOL_TYPE)
/*!
    Writes the boolean entry \a value into key \a key. The \a key is
    created if it doesn't exist. Any previous value is overwritten by
    \a value.

    If an error occurs the settings are left unchanged and FALSE is
    returned; otherwise returns TRUE.

  \sa readListEntry(), readNumEntry(), readDoubleEntry(), readBoolEntry(), removeEntry()
*/
bool QSettings::writeEntry(const QString &key, bool value)
{
    QString s(value ? "true" : "false");
    return writeEntry(key, s);
}
#endif


/*!
    \overload

    Writes the double entry \a value into key \a key. The \a key is
    created if it doesn't exist. Any previous value is overwritten by
    \a value.

    If an error occurs the settings are left unchanged and FALSE is
    returned; otherwise returns TRUE.

  \sa readListEntry(), readNumEntry(), readDoubleEntry(), readBoolEntry(), removeEntry()
*/
bool QSettings::writeEntry(const QString &key, double value)
{
    QString s(QString::number(value));
    return writeEntry(key, s);
}


/*!
    \overload

    Writes the integer entry \a value into key \a key. The \a key is
    created if it doesn't exist. Any previous value is overwritten by
    \a value.

    If an error occurs the settings are left unchanged and FALSE is
    returned; otherwise returns TRUE.

  \sa readListEntry(), readNumEntry(), readDoubleEntry(), readBoolEntry(), removeEntry()
*/
bool QSettings::writeEntry(const QString &key, int value)
{
    QString s(QString::number(value));
    return writeEntry(key, s);
}


/*!
    \internal

  Writes the entry specified by \a key with the string-literal \a value,
  replacing any previous setting. If \a value is zero-length or null, the
  entry is replaced by an empty setting.

  \e NOTE: This function is provided because some compilers use the
  writeEntry (const QString &, bool) overload for this code:
  writeEntry ("/foo/bar", "baz")

  If an error occurs, this functions returns FALSE and the object is left
  unchanged.

  \sa readEntry(), removeEntry()
*/
bool QSettings::writeEntry(const QString &key, const char *value)
{
    return writeEntry(key, QString(value));
}


/*!
    \overload

    Writes the string entry \a value into key \a key. The \a key is
    created if it doesn't exist. Any previous value is overwritten by
    \a value. If \a value is an empty string or a null string the
    key's value will be an empty string.

    If an error occurs the settings are left unchanged and FALSE is
    returned; otherwise returns TRUE.

  \sa readListEntry(), readNumEntry(), readDoubleEntry(), readBoolEntry(), removeEntry()
*/
bool QSettings::writeEntry(const QString &key, const QString &value)
{
    // NOTE: we *do* allow value to be a null/empty string

    if (key.isNull() || key.isEmpty()) {
#ifdef QT_CHECK_STATE
	qWarning("QSettings::writeEntry: invalid null/empty key.");
#endif // QT_CHECK_STATE

	return FALSE;
    }

    QString realkey;

    if (key[0] == '/') {
	// parse our key
	QStringList list(QStringList::split('/', key));

	if (list.count() < 2) {
#ifdef QT_CHECK_STATE
	    qWarning("QSettings::writeEntry: invalid key '%s'", key.latin1());
#endif // QT_CHECK_STATE

	    return FALSE;
	}

	if (list.count() == 2) {
	    d->heading = list[0];
	    d->group = "General";
	    realkey = list[1];
	} else {
	    d->heading = list[0];
	    d->group = list[1];

	    // remove the group from the list
	    list.remove(list.at(1));
	    // remove the heading from the list
	    list.remove(list.at(0));

	    realkey = list.join("/");
	}
    } else
	realkey = key;

    d->writeGroup(realkey, value);
    return TRUE;
}


/*!
    Removes the entry specified by \a key.

    Returns FALSE if the entry existed and could not be removed;
    otherwise returns TRUE.

    \sa readEntry(), writeEntry()
*/
bool QSettings::removeEntry(const QString &key)
{

    if (key.isNull() || key.isEmpty()) {
#ifdef QT_CHECK_STATE
	qWarning("QSettings::removeEntry: invalid null/empty key.");
#endif // QT_CHECK_STATE

	return FALSE;
    }

    QString realkey;

    if (key[0] == '/') {
	// parse our key
	QStringList list(QStringList::split('/', key));

	if (list.count() < 2) {
#ifdef QT_CHECK_STATE
	    qWarning("QSettings::removeEntry: invalid key '%s'", key.latin1());
#endif // QT_CHECK_STATE

	    return FALSE;
	}

	if (list.count() == 2) {
	    d->heading = list[0];
	    d->group = "General";
	    realkey = list[1];
	} else {
	    d->heading = list[0];
	    d->group = list[1];

	    // remove the group from the list
	    list.remove(list.at(1));
	    // remove the heading from the list
	    list.remove(list.at(0));

	    realkey = list.join("/");
	}
    } else
	realkey = key;

    d->removeGroup(realkey);
    return TRUE;
}


/*!
    Returns a list of the keys which contain entries under \a key.
    Does \e not return any keys that contain keys.

    Example settings:
    \code
    /MyCompany/MyApplication/background color
    /MyCompany/MyApplication/foreground color
    /MyCompany/MyApplication/geometry/x
    /MyCompany/MyApplication/geometry/y
    /MyCompany/MyApplication/geometry/width
    /MyCompany/MyApplication/geometry/height
    \endcode
    \code
    QStringList keys = entryList( "/MyCompany/MyApplication" );
    \endcode
    \c keys contains 'background color' and 'foreground color'. It does
    not contain 'geometry' because that key contains keys not entries.

    To access the geometry values either use subkeyList() to read the
    keys and then read each entry, or simply read each entry directly
    by specifying its full key, e.g.
    "/MyCompany/MyApplication/geometry/y".

    \sa subkeyList()
*/
QStringList QSettings::entryList(const QString &key) const
{

    if (key.isNull() || key.isEmpty()) {
#ifdef QT_CHECK_STATE
	qWarning("QSettings::listEntries: invalid null/empty key.");
#endif // QT_CHECK_STATE

	return QStringList();
    }

    QString realkey;
    if (key[0] == '/') {
	// parse our key
	QStringList list(QStringList::split('/', key));

	if (list.count() < 1) {
#ifdef QT_CHECK_STATE
	    qWarning("QSettings::listEntries: invalid key '%s'", key.latin1());
#endif // QT_CHECK_STATE

	    return QStringList();
	}

	if (list.count() == 1) {
	    d->heading = list[0];
	    d->group = "General";
	} else {
	    d->heading = list[0];
	    d->group = list[1];

	    // remove the group from the list
	    list.remove(list.at(1));
	    // remove the heading from the list
	    list.remove(list.at(0));

	    realkey = list.join("/");
	}
    } else
	realkey = key;

    QSettingsGroup grp = d->readGroup();
    QSettingsGroup::Iterator it = grp.begin();
    QStringList ret;
    QString itkey;
    while (it != grp.end()) {
	itkey = it.key();
	it++;

	if ( realkey.length() > 0 ) {
	    if ( itkey.left( realkey.length() ) != realkey )
		continue;
	    else
		itkey.remove( 0, realkey.length() + 1 );
	}

	if ( itkey.find( '/' ) != -1 )
	    continue;

	ret << itkey;
    }

    return ret;
}


/*!
    Returns a list of the keys which contain keys under \a key. Does
    \e not return any keys that contain entries.

    Example settings:
    \code
    /MyCompany/MyApplication/background color
    /MyCompany/MyApplication/foreground color
    /MyCompany/MyApplication/geometry/x
    /MyCompany/MyApplication/geometry/y
    /MyCompany/MyApplication/geometry/width
    /MyCompany/MyApplication/geometry/height
    /MyCompany/MyApplication/recent files/1
    /MyCompany/MyApplication/recent files/2
    /MyCompany/MyApplication/recent files/3
    \endcode
    \code
    QStringList keys = subkeyList( "/MyCompany/MyApplication" );
    \endcode
    \c keys contains 'geometry' and 'recent files'. It does not
    contain 'background color' or 'foreground color' because they are
    keys which contain entries not keys. To get a list of keys that
    have values rather than subkeys use entryList().

    \sa entryList()
*/
QStringList QSettings::subkeyList(const QString &key) const
{

    if (key.isNull() || key.isEmpty()) {
#ifdef QT_CHECK_STATE
	qWarning("QSettings::listSubkeys: invalid null/empty key.");
#endif // QT_CHECK_STATE

	return QStringList();
    }

    QString realkey;
    if (key[0] == '/') {
	// parse our key
	QStringList list(QStringList::split('/', key));

	if (list.count() < 1) {
#ifdef QT_CHECK_STATE
	    qWarning("QSettings::listSubkeys: invalid key '%s'", key.latin1());
#endif // QT_CHECK_STATE

	    return QStringList();
	}

	if (list.count() == 1) {
	    d->heading = list[0];
	    d->group = "General";
	} else {
	    d->heading = list[0];
	    d->group = list[1];

	    // remove the group from the list
	    list.remove(list.at(1));
	    // remove the heading from the list
	    list.remove(list.at(0));

	    realkey = list.join("/");
	}
    } else
	realkey = key;

    QSettingsGroup grp = d->readGroup();
    QSettingsGroup::Iterator it = grp.begin();
    QStringList ret;
    QString itkey;
    while (it != grp.end()) {
	itkey = it.key();
	it++;

	if ( realkey.length() > 0 ) {
	    if ( itkey.left( realkey.length() ) != realkey )
		continue;
	    else
		itkey.remove( 0, realkey.length() + 1 );
	}

	int slash = itkey.find( '/' );
	if ( slash == -1 )
	    continue;
	itkey.truncate( slash );

	if ( ! ret.contains( itkey ) )
	    ret << itkey;
    }

    return ret;
}


/*!
    \internal

  This function returns the time of last modification for \a key.
*/
QDateTime QSettings::lastModficationTime(const QString &key)
{

    if (key.isNull() || key.isEmpty()) {
#ifdef QT_CHECK_STATE
	qWarning("QSettings::lastModficationTime: invalid null/empty key.");
#endif // QT_CHECK_STATE

	return QDateTime();
    }

    if (key[0] == '/') {
	// parse our key
	QStringList list(QStringList::split('/', key));

	if (list.count() < 2) {
#ifdef QT_CHECK_STATE
	    qWarning("QSettings::lastModficationTime: invalid key '%s'", key.latin1());
#endif // QT_CHECK_STATE

	    return QDateTime();
	}

	if (list.count() == 2) {
	    d->heading = list[0];
	    d->group = "General";
	} else {
	    d->heading = list[0];
	    d->group = list[1];
	}
    }

    return d->modificationTime();
}

#endif //QT_NO_SETTINGS
