1998-07-29 08:03:42 -04:00
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
// Name: fileconf.h
|
1998-08-02 19:06:42 -04:00
|
|
|
|
// Purpose: wxFileConfig derivation of wxConfigBase
|
1998-07-29 08:03:42 -04:00
|
|
|
|
// Author: Vadim Zeitlin
|
|
|
|
|
// Modified by:
|
|
|
|
|
// Created: 07.04.98 (adapted from appconf.cpp)
|
|
|
|
|
// RCS-ID: $Id$
|
|
|
|
|
// Copyright: (c) 1997 Karsten Ball<6C>der & Vadim Zeitlin
|
|
|
|
|
// Ballueder@usa.net <zeitlin@dptmaths.ens-cachan.fr>
|
|
|
|
|
// Licence: wxWindows license
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
1998-05-26 11:07:48 -04:00
|
|
|
|
|
|
|
|
|
#ifndef _FILECONF_H
|
|
|
|
|
#define _FILECONF_H
|
|
|
|
|
|
1998-07-03 12:36:10 -04:00
|
|
|
|
#ifdef __GNUG__
|
|
|
|
|
#pragma interface "fileconf.h"
|
|
|
|
|
#endif
|
|
|
|
|
|
1998-09-01 06:26:14 -04:00
|
|
|
|
#include "wx/defs.h"
|
|
|
|
|
|
1999-06-15 16:21:59 -04:00
|
|
|
|
#if wxUSE_CONFIG
|
1999-01-15 19:13:58 -05:00
|
|
|
|
|
1999-06-15 16:21:59 -04:00
|
|
|
|
#include "wx/textfile.h"
|
|
|
|
|
#include "wx/string.h"
|
1998-06-02 09:23:24 -04:00
|
|
|
|
|
1998-05-26 11:07:48 -04:00
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
// wxFileConfig
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
/*
|
1998-07-07 09:53:19 -04:00
|
|
|
|
wxFileConfig derives from base Config and implements file based config class,
|
1998-05-26 11:07:48 -04:00
|
|
|
|
i.e. it uses ASCII disk files to store the information. These files are
|
1998-07-07 09:53:19 -04:00
|
|
|
|
alternatively called INI, .conf or .rc in the documentation. They are
|
1998-05-26 11:07:48 -04:00
|
|
|
|
organized in groups or sections, which can nest (i.e. a group contains
|
|
|
|
|
subgroups, which contain their own subgroups &c). Each group has some
|
1998-07-07 09:53:19 -04:00
|
|
|
|
number of entries, which are "key = value" pairs. More precisely, the format
|
1998-05-26 11:07:48 -04:00
|
|
|
|
is:
|
|
|
|
|
|
|
|
|
|
# comments are allowed after either ';' or '#' (Win/UNIX standard)
|
|
|
|
|
|
|
|
|
|
# blank lines (as above) are ignored
|
|
|
|
|
|
|
|
|
|
# global entries are members of special (no name) top group
|
|
|
|
|
written_for = Windows
|
|
|
|
|
platform = Linux
|
|
|
|
|
|
|
|
|
|
# the start of the group 'Foo'
|
|
|
|
|
[Foo] # may put comments like this also
|
|
|
|
|
# following 3 lines are entries
|
|
|
|
|
key = value
|
|
|
|
|
another_key = " strings with spaces in the beginning should be quoted, \
|
|
|
|
|
otherwise the spaces are lost"
|
|
|
|
|
last_key = but you don't have to put " normally (nor quote them, like here)
|
|
|
|
|
|
|
|
|
|
# subgroup of the group 'Foo'
|
|
|
|
|
# (order is not important, only the name is: separator is '/', as in paths)
|
|
|
|
|
[Foo/Bar]
|
|
|
|
|
# entries prefixed with "!" are immutable, i.e. can't be changed if they are
|
|
|
|
|
# set in the system-wide config file
|
|
|
|
|
!special_key = value
|
|
|
|
|
bar_entry = whatever
|
|
|
|
|
|
|
|
|
|
[Foo/Bar/Fubar] # depth is (theoretically :-) unlimited
|
|
|
|
|
# may have the same name as key in another section
|
|
|
|
|
bar_entry = whatever not
|
|
|
|
|
|
|
|
|
|
You have {read/write/delete}Entry functions (guess what they do) and also
|
|
|
|
|
setCurrentPath to select current group. enum{Subgroups/Entries} allow you
|
|
|
|
|
to get all entries in the config file (in the current group). Finally,
|
|
|
|
|
flush() writes immediately all changed entries to disk (otherwise it would
|
|
|
|
|
be done automatically in dtor)
|
|
|
|
|
|
|
|
|
|
wxFileConfig manages not less than 2 config files for each program: global
|
|
|
|
|
and local (or system and user if you prefer). Entries are read from both of
|
|
|
|
|
them and the local entries override the global ones unless the latter is
|
|
|
|
|
immutable (prefixed with '!') in which case a warning message is generated
|
|
|
|
|
and local value is ignored. Of course, the changes are always written to local
|
|
|
|
|
file only.
|
|
|
|
|
|
1998-07-29 08:03:42 -04:00
|
|
|
|
The names of these files can be specified in a number of ways. First of all,
|
|
|
|
|
you can use the standard convention: using the ctor which takes 'strAppName'
|
|
|
|
|
parameter will probably be sufficient for 90% of cases. If, for whatever
|
|
|
|
|
reason you wish to use the files with some other names, you can always use the
|
|
|
|
|
second ctor.
|
|
|
|
|
|
|
|
|
|
wxFileConfig also may automatically expand the values of environment variables
|
|
|
|
|
in the entries it reads: for example, if you have an entry
|
|
|
|
|
score_file = $HOME/.score
|
|
|
|
|
a call to Read(&str, "score_file") will return a complete path to .score file
|
|
|
|
|
unless the expansion was previousle disabled with SetExpandEnvVars(FALSE) call
|
|
|
|
|
(it's on by default, the current status can be retrieved with
|
|
|
|
|
IsExpandingEnvVars function).
|
1998-05-26 11:07:48 -04:00
|
|
|
|
*/
|
1999-01-20 14:54:17 -05:00
|
|
|
|
class wxFileConfig;
|
|
|
|
|
class ConfigGroup;
|
|
|
|
|
class ConfigEntry;
|
1999-01-15 19:13:58 -05:00
|
|
|
|
|
1999-01-20 14:54:17 -05:00
|
|
|
|
// we store all lines of the local config file as a linked list in memory
|
|
|
|
|
class LineList
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
void SetNext(LineList *pNext) { m_pNext = pNext; }
|
|
|
|
|
void SetPrev(LineList *pPrev) { m_pPrev = pPrev; }
|
1998-05-26 11:07:48 -04:00
|
|
|
|
|
1999-01-20 14:54:17 -05:00
|
|
|
|
// ctor
|
|
|
|
|
LineList(const wxString& str, LineList *pNext = (LineList *) NULL) : m_strLine(str)
|
|
|
|
|
{ SetNext(pNext); SetPrev((LineList *) NULL); }
|
1999-01-15 19:13:58 -05:00
|
|
|
|
|
1999-01-20 14:54:17 -05:00
|
|
|
|
//
|
|
|
|
|
LineList *Next() const { return m_pNext; }
|
|
|
|
|
LineList *Prev() const { return m_pPrev; }
|
1999-01-15 19:13:58 -05:00
|
|
|
|
|
1999-01-20 14:54:17 -05:00
|
|
|
|
//
|
|
|
|
|
void SetText(const wxString& str) { m_strLine = str; }
|
|
|
|
|
const wxString& Text() const { return m_strLine; }
|
1999-01-15 19:13:58 -05:00
|
|
|
|
|
1999-01-20 14:54:17 -05:00
|
|
|
|
private:
|
|
|
|
|
wxString m_strLine; // line contents
|
|
|
|
|
LineList *m_pNext, // next node
|
|
|
|
|
*m_pPrev; // previous one
|
|
|
|
|
};
|
1999-01-15 19:13:58 -05:00
|
|
|
|
|
|
|
|
|
class wxFileConfig : public wxConfigBase
|
1998-05-26 11:07:48 -04:00
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
// construct the "standard" full name for global (system-wide) and
|
|
|
|
|
// local (user-specific) config files from the base file name.
|
|
|
|
|
//
|
|
|
|
|
// the following are the filenames returned by this functions:
|
|
|
|
|
// global local
|
|
|
|
|
// Unix /etc/file.ext ~/.file
|
|
|
|
|
// Win %windir%\file.ext %USERPROFILE%\file.ext
|
|
|
|
|
//
|
|
|
|
|
// where file is the basename of szFile, ext is it's extension
|
|
|
|
|
// or .conf (Unix) or .ini (Win) if it has none
|
1999-04-12 18:20:19 -04:00
|
|
|
|
static wxString GetGlobalFileName(const wxChar *szFile);
|
|
|
|
|
static wxString GetLocalFileName(const wxChar *szFile);
|
1998-05-26 11:07:48 -04:00
|
|
|
|
|
|
|
|
|
// ctor & dtor
|
1999-04-07 09:01:50 -04:00
|
|
|
|
// New constructor: one size fits all. Specify wxCONFIG_USE_LOCAL_FILE or
|
|
|
|
|
// wxCONFIG_USE_GLOBAL_FILE to say which files should be used.
|
|
|
|
|
wxFileConfig(const wxString& appName,
|
1999-04-12 18:20:19 -04:00
|
|
|
|
const wxString& vendorName = _T(""),
|
|
|
|
|
const wxString& localFilename = _T(""),
|
|
|
|
|
const wxString& globalFilename = _T(""),
|
1998-09-03 08:03:22 -04:00
|
|
|
|
long style = wxCONFIG_USE_LOCAL_FILE);
|
1998-08-27 17:06:02 -04:00
|
|
|
|
|
1998-05-26 11:07:48 -04:00
|
|
|
|
// dtor will save unsaved data
|
|
|
|
|
virtual ~wxFileConfig();
|
|
|
|
|
|
|
|
|
|
// implement inherited pure virtual functions
|
|
|
|
|
virtual void SetPath(const wxString& strPath);
|
|
|
|
|
virtual const wxString& GetPath() const { return m_strPath; }
|
|
|
|
|
|
1998-08-02 19:06:42 -04:00
|
|
|
|
virtual bool GetFirstGroup(wxString& str, long& lIndex) const;
|
|
|
|
|
virtual bool GetNextGroup (wxString& str, long& lIndex) const;
|
|
|
|
|
virtual bool GetFirstEntry(wxString& str, long& lIndex) const;
|
|
|
|
|
virtual bool GetNextEntry (wxString& str, long& lIndex) const;
|
1998-05-26 11:07:48 -04:00
|
|
|
|
|
1998-08-18 11:36:12 -04:00
|
|
|
|
virtual size_t GetNumberOfEntries(bool bRecursive = FALSE) const;
|
|
|
|
|
virtual size_t GetNumberOfGroups(bool bRecursive = FALSE) const;
|
1998-07-18 17:47:45 -04:00
|
|
|
|
|
1998-07-07 09:53:19 -04:00
|
|
|
|
virtual bool HasGroup(const wxString& strName) const;
|
|
|
|
|
virtual bool HasEntry(const wxString& strName) const;
|
|
|
|
|
|
1998-08-27 17:06:02 -04:00
|
|
|
|
virtual bool Read(const wxString& key, wxString *pStr) const;
|
|
|
|
|
virtual bool Read(const wxString& key, wxString *pStr, const wxString& defValue) const;
|
|
|
|
|
virtual bool Read(const wxString& key, long *pl) const;
|
|
|
|
|
|
|
|
|
|
// The following are necessary to satisfy the compiler
|
|
|
|
|
wxString Read(const wxString& key, const wxString& defVal) const
|
1999-01-20 14:54:17 -05:00
|
|
|
|
{ return wxConfigBase::Read(key, defVal); }
|
1998-08-27 17:06:02 -04:00
|
|
|
|
bool Read(const wxString& key, long *pl, long defVal) const
|
1999-01-20 14:54:17 -05:00
|
|
|
|
{ return wxConfigBase::Read(key, pl, defVal); }
|
1998-08-27 17:06:02 -04:00
|
|
|
|
long Read(const wxString& key, long defVal) const
|
1999-01-20 14:54:17 -05:00
|
|
|
|
{ return wxConfigBase::Read(key, defVal); }
|
1998-10-02 08:50:01 -04:00
|
|
|
|
bool Read(const wxString& key, int *pi, int defVal) const
|
|
|
|
|
{ return wxConfigBase::Read(key, pi, defVal); }
|
|
|
|
|
bool Read(const wxString& key, int *pi) const
|
|
|
|
|
{ return wxConfigBase::Read(key, pi); }
|
1998-08-27 17:06:02 -04:00
|
|
|
|
bool Read(const wxString& key, double* val) const
|
1999-01-20 14:54:17 -05:00
|
|
|
|
{ return wxConfigBase::Read(key, val); }
|
1998-08-27 17:06:02 -04:00
|
|
|
|
bool Read(const wxString& key, double* val, double defVal) const
|
1999-01-20 14:54:17 -05:00
|
|
|
|
{ return wxConfigBase::Read(key, val, defVal); }
|
1999-01-26 06:51:12 -05:00
|
|
|
|
bool Read(const wxString& key, bool* val) const
|
|
|
|
|
{ return wxConfigBase::Read(key, val); }
|
|
|
|
|
bool Read(const wxString& key, bool* val, bool defVal) const
|
|
|
|
|
{ return wxConfigBase::Read(key, val, defVal); }
|
1998-08-27 17:06:02 -04:00
|
|
|
|
|
|
|
|
|
virtual bool Write(const wxString& key, const wxString& szValue);
|
|
|
|
|
virtual bool Write(const wxString& key, long lValue);
|
1999-01-26 06:51:12 -05:00
|
|
|
|
bool Write(const wxString& key, double value)
|
|
|
|
|
{ return wxConfigBase::Write(key, value); }
|
|
|
|
|
bool Write(const wxString& key, bool value)
|
|
|
|
|
{ return wxConfigBase::Write(key, value); }
|
1998-08-27 17:06:02 -04:00
|
|
|
|
|
1998-05-26 11:07:48 -04:00
|
|
|
|
virtual bool Flush(bool bCurrentOnly = FALSE);
|
|
|
|
|
|
1999-01-20 14:54:17 -05:00
|
|
|
|
virtual bool RenameEntry(const wxString& oldName, const wxString& newName);
|
|
|
|
|
virtual bool RenameGroup(const wxString& oldName, const wxString& newName);
|
|
|
|
|
|
1998-08-27 17:06:02 -04:00
|
|
|
|
virtual bool DeleteEntry(const wxString& key, bool bGroupIfEmptyAlso);
|
|
|
|
|
virtual bool DeleteGroup(const wxString& szKey);
|
1998-05-26 11:07:48 -04:00
|
|
|
|
virtual bool DeleteAll();
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
// functions to work with this list
|
|
|
|
|
LineList *LineListAppend(const wxString& str);
|
1998-07-07 09:53:19 -04:00
|
|
|
|
LineList *LineListInsert(const wxString& str,
|
|
|
|
|
LineList *pLine); // NULL => Prepend()
|
|
|
|
|
void LineListRemove(LineList *pLine);
|
1998-05-26 11:07:48 -04:00
|
|
|
|
bool LineListIsEmpty();
|
|
|
|
|
|
|
|
|
|
private:
|
1999-01-20 14:54:17 -05:00
|
|
|
|
// GetXXXFileName helpers: return ('/' terminated) directory names
|
1998-07-29 08:03:42 -04:00
|
|
|
|
static wxString GetGlobalDir();
|
|
|
|
|
static wxString GetLocalDir();
|
|
|
|
|
|
|
|
|
|
// common part of all ctors (assumes that m_str{Local|Global}File are already
|
|
|
|
|
// initialized
|
1998-05-26 11:07:48 -04:00
|
|
|
|
void Init();
|
|
|
|
|
|
1998-07-29 08:03:42 -04:00
|
|
|
|
// common part of from dtor and DeleteAll
|
|
|
|
|
void CleanUp();
|
|
|
|
|
|
1998-05-26 11:07:48 -04:00
|
|
|
|
// parse the whole file
|
|
|
|
|
void Parse(wxTextFile& file, bool bLocal);
|
|
|
|
|
|
|
|
|
|
// the same as SetPath("/")
|
|
|
|
|
void SetRootPath();
|
|
|
|
|
|
|
|
|
|
// member variables
|
|
|
|
|
// ----------------
|
|
|
|
|
LineList *m_linesHead, // head of the linked list
|
|
|
|
|
*m_linesTail; // tail
|
|
|
|
|
|
|
|
|
|
wxString m_strLocalFile, // local file name passed to ctor
|
|
|
|
|
m_strGlobalFile; // global
|
|
|
|
|
wxString m_strPath; // current path (not '/' terminated)
|
|
|
|
|
|
|
|
|
|
ConfigGroup *m_pRootGroup, // the top (unnamed) group
|
|
|
|
|
*m_pCurrentGroup; // the current group
|
|
|
|
|
|
|
|
|
|
public:
|
1998-07-16 13:30:39 -04:00
|
|
|
|
WX_DEFINE_SORTED_ARRAY(ConfigEntry *, ArrayEntries);
|
|
|
|
|
WX_DEFINE_SORTED_ARRAY(ConfigGroup *, ArrayGroups);
|
1999-01-20 14:54:17 -05:00
|
|
|
|
};
|
1998-05-26 11:07:48 -04:00
|
|
|
|
|
1999-01-20 14:54:17 -05:00
|
|
|
|
class ConfigEntry
|
|
|
|
|
{
|
|
|
|
|
private:
|
|
|
|
|
ConfigGroup *m_pParent; // group that contains us
|
|
|
|
|
wxString m_strName, // entry name
|
|
|
|
|
m_strValue; // value
|
|
|
|
|
bool m_bDirty, // changed since last read?
|
|
|
|
|
m_bImmutable; // can be overriden locally?
|
|
|
|
|
int m_nLine; // used if m_pLine == NULL only
|
|
|
|
|
LineList *m_pLine; // pointer to our line in the linked list
|
|
|
|
|
// or NULL if it was found in global file
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
ConfigEntry(ConfigGroup *pParent, const wxString& strName, int nLine);
|
|
|
|
|
|
|
|
|
|
// simple accessors
|
|
|
|
|
const wxString& Name() const { return m_strName; }
|
|
|
|
|
const wxString& Value() const { return m_strValue; }
|
|
|
|
|
ConfigGroup *Group() const { return m_pParent; }
|
|
|
|
|
bool IsDirty() const { return m_bDirty; }
|
|
|
|
|
bool IsImmutable() const { return m_bImmutable; }
|
|
|
|
|
bool IsLocal() const { return m_pLine != 0; }
|
|
|
|
|
int Line() const { return m_nLine; }
|
|
|
|
|
LineList *GetLine() const { return m_pLine; }
|
|
|
|
|
|
|
|
|
|
// modify entry attributes
|
|
|
|
|
void SetValue(const wxString& strValue, bool bUser = TRUE);
|
|
|
|
|
void SetDirty();
|
|
|
|
|
void SetLine(LineList *pLine);
|
1999-01-15 19:13:58 -05:00
|
|
|
|
};
|
|
|
|
|
|
1999-01-20 14:54:17 -05:00
|
|
|
|
class ConfigGroup
|
|
|
|
|
{
|
|
|
|
|
private:
|
|
|
|
|
wxFileConfig *m_pConfig; // config object we belong to
|
|
|
|
|
ConfigGroup *m_pParent; // parent group (NULL for root group)
|
|
|
|
|
wxFileConfig::ArrayEntries m_aEntries; // entries in this group
|
|
|
|
|
wxFileConfig::ArrayGroups m_aSubgroups; // subgroups
|
|
|
|
|
wxString m_strName; // group's name
|
|
|
|
|
bool m_bDirty; // if FALSE => all subgroups are not dirty
|
|
|
|
|
LineList *m_pLine; // pointer to our line in the linked list
|
|
|
|
|
ConfigEntry *m_pLastEntry; // last entry/subgroup of this group in the
|
|
|
|
|
ConfigGroup *m_pLastGroup; // local file (we insert new ones after it)
|
|
|
|
|
|
|
|
|
|
// DeleteSubgroupByName helper
|
|
|
|
|
bool DeleteSubgroup(ConfigGroup *pGroup);
|
1998-05-26 11:07:48 -04:00
|
|
|
|
|
1999-01-20 14:54:17 -05:00
|
|
|
|
public:
|
|
|
|
|
// ctor
|
|
|
|
|
ConfigGroup(ConfigGroup *pParent, const wxString& strName, wxFileConfig *);
|
|
|
|
|
|
|
|
|
|
// dtor deletes all entries and subgroups also
|
|
|
|
|
~ConfigGroup();
|
|
|
|
|
|
|
|
|
|
// simple accessors
|
|
|
|
|
const wxString& Name() const { return m_strName; }
|
|
|
|
|
ConfigGroup *Parent() const { return m_pParent; }
|
|
|
|
|
wxFileConfig *Config() const { return m_pConfig; }
|
|
|
|
|
bool IsDirty() const { return m_bDirty; }
|
|
|
|
|
|
|
|
|
|
const wxFileConfig::ArrayEntries& Entries() const { return m_aEntries; }
|
|
|
|
|
const wxFileConfig::ArrayGroups& Groups() const { return m_aSubgroups; }
|
|
|
|
|
bool IsEmpty() const { return Entries().IsEmpty() && Groups().IsEmpty(); }
|
1999-01-15 19:13:58 -05:00
|
|
|
|
|
1999-01-20 14:54:17 -05:00
|
|
|
|
// find entry/subgroup (NULL if not found)
|
1999-04-12 18:20:19 -04:00
|
|
|
|
ConfigGroup *FindSubgroup(const wxChar *szName) const;
|
|
|
|
|
ConfigEntry *FindEntry (const wxChar *szName) const;
|
1999-01-15 19:13:58 -05:00
|
|
|
|
|
1999-01-20 14:54:17 -05:00
|
|
|
|
// delete entry/subgroup, return FALSE if doesn't exist
|
1999-04-12 18:20:19 -04:00
|
|
|
|
bool DeleteSubgroupByName(const wxChar *szName);
|
|
|
|
|
bool DeleteEntry(const wxChar *szName);
|
1999-01-15 19:13:58 -05:00
|
|
|
|
|
1999-01-20 14:54:17 -05:00
|
|
|
|
// create new entry/subgroup returning pointer to newly created element
|
|
|
|
|
ConfigGroup *AddSubgroup(const wxString& strName);
|
|
|
|
|
ConfigEntry *AddEntry (const wxString& strName, int nLine = wxNOT_FOUND);
|
1999-01-15 19:13:58 -05:00
|
|
|
|
|
1999-01-20 14:54:17 -05:00
|
|
|
|
// will also recursively set parent's dirty flag
|
|
|
|
|
void SetDirty();
|
|
|
|
|
void SetLine(LineList *pLine);
|
1999-01-04 08:52:06 -05:00
|
|
|
|
|
1999-01-20 14:54:17 -05:00
|
|
|
|
// rename: no checks are done to ensure that the name is unique!
|
|
|
|
|
void Rename(const wxString& newName);
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
wxString GetFullName() const;
|
|
|
|
|
|
|
|
|
|
// get the last line belonging to an entry/subgroup of this group
|
|
|
|
|
LineList *GetGroupLine(); // line which contains [group]
|
|
|
|
|
LineList *GetLastEntryLine(); // after which our subgroups start
|
|
|
|
|
LineList *GetLastGroupLine(); // after which the next group starts
|
|
|
|
|
|
|
|
|
|
// called by entries/subgroups when they're created/deleted
|
|
|
|
|
void SetLastEntry(ConfigEntry *pEntry) { m_pLastEntry = pEntry; }
|
|
|
|
|
void SetLastGroup(ConfigGroup *pGroup) { m_pLastGroup = pGroup; }
|
|
|
|
|
};
|
|
|
|
|
|
1999-06-15 16:21:59 -04:00
|
|
|
|
#endif
|
|
|
|
|
// wxUSE_CONFIG
|
|
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
//_FILECONF_H
|
1998-05-26 11:07:48 -04:00
|
|
|
|
|