diff options
Diffstat (limited to 'kab')
-rw-r--r-- | kab/ChangeLog | 94 | ||||
-rw-r--r-- | kab/Makefile.am | 15 | ||||
-rw-r--r-- | kab/NEWS | 25 | ||||
-rw-r--r-- | kab/README | 38 | ||||
-rw-r--r-- | kab/README.FORMAT | 85 | ||||
-rw-r--r-- | kab/README.KABAPI | 25 | ||||
-rw-r--r-- | kab/TODO | 29 | ||||
-rw-r--r-- | kab/addressbook.cc | 2040 | ||||
-rw-r--r-- | kab/addressbook.h | 734 | ||||
-rw-r--r-- | kab/kabapi.cc | 220 | ||||
-rw-r--r-- | kab/kabapi.h | 220 | ||||
-rw-r--r-- | kab/qconfigDB.cc | 2547 | ||||
-rw-r--r-- | kab/qconfigDB.h | 779 | ||||
-rw-r--r-- | kab/template.config | 38 | ||||
-rw-r--r-- | kab/template.kab | 12 |
15 files changed, 6901 insertions, 0 deletions
diff --git a/kab/ChangeLog b/kab/ChangeLog new file mode 100644 index 000000000..d3e36fcc1 --- /dev/null +++ b/kab/ChangeLog @@ -0,0 +1,94 @@ +Tue Dec 22 22:56:58 1998 Mirko Boehm <mirko@kde.org> + + * changed the debugging aids to be more portable + removed all macros with variable args like ID(a...) + that are only usable with GNU C++ + + * debugged the KabAPI + +1998-12-08 Alex Zepeda <garbanzo@hooked.net> + + * businesscard.h (class BusinessCard): Hopefully remove + trigraph. + + * datepickerdialog.cc (DateLabel): Removed default value + in the implementation. + + * keyvaluemap.h: Removed a default value for a function + which should be in a .cpp file. Oy. + +1998-10-10 Mirko Boehm <mirko@kde.org> + + The files + AssertDialog.h + AssertDialog.cpp + AssertDialogData.h + AssertDialogData.cpp + are new. The AssertDialog pops up when an assertion is caught. The user + may ignore the error, kill the application or mail a note to the app + maintainer. + The files + SearchDialogData.h + SearchDialogData.cpp + have been removed. The new SearchDialog uses the DialogBase class. + The files + SearchDialog.h + SearchDialog.cpp + contain completely different code than before (the new dialog). + The files + SearchDialogMainWidget.h + SearchDialogMainWidget.cpp + SearchDialogMainWidgetData.h + SearchDialogMainWidgetData.cpp + contain the new widget (created with qtarch). + +1998-10-16 Mirko Boehm <mirko@kde.org> + The files + StringListSelectAndReorderSet.h + StringListSelectAndReorderSet.cpp + StringListSelectSetDialog.h + StringListSelectSetDialog.cpp + have been changed to use the new dialog base class. + The files + StringListSelectAndReorderSetData.h + StringListSelectAndReorderSetData.cpp + StringListSelectSetDialogData.h + StringListSelectSetDialogData.cpp + are obsolete and have been removed. + The files + dialog_background.jpg + has been added. + +1998-10-24 Mirko Boehm <mirko@kde.org> + I debugged the appearance of the EditEntry-dialog. It resizes now + automatically and more accurately. + The about dialog now contains a KURLLabel that opens an email composer + when the user clicks on it. + +1998-11-02 Mirko Boehm <mirko@kde.org> + I missed to store the contents of the "country" field, this bug is fixed + now. + Since I reworked the former StringListDialog, all files regarding the + old one have changed. These are removed: + StringListDialog.dlg + StringListDialog.h + StringListDialog.cpp + StringListDialogData.h + StringListDialogData.cpp, + these are added, they contain the new main widget for the DialogBase + deriven dialog class: + StringListEditWidget.dlg + StringListEditWidget.h + StringListEditWidget.cpp + StringListEditWidgetData.h + StringListEditWidgetData.cpp, + and these contin the dialog itselfes, they are also new: + StringListEditDialog.h + StringListEditDialog.cpp. + Writing dialogs becomes more and more easy using the DialogBase class. + +1998-11-22 Mirko Boehm <mirko@kde.org> + The displayed email addresses and URLs are interactive now. + + + diff --git a/kab/Makefile.am b/kab/Makefile.am new file mode 100644 index 000000000..301c5d952 --- /dev/null +++ b/kab/Makefile.am @@ -0,0 +1,15 @@ +INCLUDES= $(all_includes) + +noinst_LTLIBRARIES = libkab.la +KABVERSION= 4.0.0 +KABPATCH= 0 + +libkab_la_SOURCES = \ + kabapi.cc \ + addressbook.cc \ + qconfigDB.cc + +libkab_la_LDFLAGS = $(KDE_MT_LDFLAGS) -no-undefined +libkab_la_LIBADD = ../kdeui/libkdeui.la + +libkab_la_METASOURCES = AUTO diff --git a/kab/NEWS b/kab/NEWS new file mode 100644 index 000000000..e0c13d4a7 --- /dev/null +++ b/kab/NEWS @@ -0,0 +1,25 @@ +October 10, 1998: + I started writing a dialog base class that in future all dialogs + will be derived from. It is suited with already-connected standard + buttons (OK, Apply, Cancel) and some nice visual features (outer + and inner frame, a common background pixmap for the frames) and + with ready-to-use geometry management. Hopefully providing cute + dialogs will became easier now. + The new dialogs that use this common base class have a main widget + they manage inside their inner frame. I usually create this widgets + with qtarch. + And I started to write the NEWS file :-) +October 24, 1998: + You may click on the email address in the about dialog to send me an + email now. +November 4, 1998: + Did you ever wonder that the "Edit email address" dialog was nearly + unusable? If you did, you should have a look at it. It has been + improved to be more intuitive. + The bug that prevented you from storing something in the country + fields has been removed now. + The dialog for editing the entry is now resized more properly. +November 22, 1998: + You may now click on the URL or email address that is displayed in + the business card, both are interactive now. + diff --git a/kab/README b/kab/README new file mode 100644 index 000000000..56514215b --- /dev/null +++ b/kab/README @@ -0,0 +1,38 @@ +Note: libkab is now obsolete. Please use libkabc in kdelibs/kabc. libkab is +no longer installed. It's only needed for the conversion tool converting +libkab data to libkabc data. + +Cornelius Schumacher, February 2002. + + + +The KDE addressbook +=================== + +This directory contains the sources for the kab API provided by "libkab.so". +kab is the KDE addressbook, users usually know it by one of the applications +using the kab API, kab itselfes. But kab intends to do more, it tries to be +a central database to store personal information for as much KDE +applications as possible. + +To compile this subdirectory, your C++ compiler will be stressed a little. +You need either a recent version of gcc (2.7.2 or later) or egcs (1.0.2 or +later), or another C++ compiler that supports the following things: + +* error free (!) creation of template instances, +* a STL implementation that is similar to either the one provided by gcc +2.7 or the one provided by egcs/pgcc or gcc >=2.8 (HP or SGI based). + +Please note that egcs / pgcc 1.0.2 creates errors in the binary when you +compile the sources using optimization level 6 (-O6). Stick to -O2, and +it should work. Note: This seems to be solved since gcc 2.95. + +Since this directory provides only the kab library, you will have to +install the kdeutils package, that contains the sources for kab's binary, +also. + + Mirko Boehm, March 2000. + +PS: The situation described above seems to be solved in current +distributions. All up-to-date compilers do quite well on the sources. + Mirko Boehm, Mai 2001. diff --git a/kab/README.FORMAT b/kab/README.FORMAT new file mode 100644 index 000000000..7cce2f402 --- /dev/null +++ b/kab/README.FORMAT @@ -0,0 +1,85 @@ +The KDE addressbook +=================== + +The format that kab uses is plain ASCII, written by an QConfigDB object. + +In general, the QConfigDB class provides an easy way to read and +write hierarchical structured files storing data of basic C++ data +types. The files may be edited manually if the syntax is not +violated. + +kab handles address book entries. Each entry is meant to represent a +person. Thus, for each entry, it is possible to store more than one +address (like home or business addresses). + +An addressbook database file contains two sections, one to store the +users entries, called "entries", and one to store the configuration, +called "config". Every subsection of the "entries" section is one +entry, containing +- a section "addresses", where in turn each subsection is one address +related to the person +- a number of key-value pairs storing data of the person. + +An example entry could look like this: + + [15] + # subsections: + [addresses] + # subsections: + [1] + # key-value-pairs: + address="..." + country="" + deliverylabel="" + headline="street address" + org="" + orgsubunit="" + orgunit="" + position="" + state="" + town="" + zip="" + [END 1] + [2] + # key-value-pairs: + address="..." + country="" + deliverylabel="" + headline="business address" + org="" + orgsubunit="" + orgunit="" + position="" + state="" + town="" + zip="" + [END 2] + [END addresses] + # key-value-pairs: + URLs="http://www.kde.org\e" + birthday="0, 0, 0" + comment="" + custom="" + emails="kde-devel@kde.org\ekde-announce@lists.netcentral.net\e" + firstname="" + fn="KDE Mailing-Listen" + keywords="" + lastname="" + middlename="" + nameprefix="" + rank="" + talk="" + telephone="" + title="" + user1="" + user2="" + user3="" + user4="" + [END 15] + +To get a overview of the meanings of the fields, just have a look at +addressbook.h. If you plan to access kab databases, you should use the +kab API that is defined in kabapi.h and link libkabs.so to your +application. (read README.KABAPI). + + Mirko Boehm, Mai 2001. diff --git a/kab/README.KABAPI b/kab/README.KABAPI new file mode 100644 index 000000000..278d787d2 --- /dev/null +++ b/kab/README.KABAPI @@ -0,0 +1,25 @@ +The KDE addressbook +=================== + +The kab API is a binary interface for application developers that want to +use the users addressbook in their own applications. To use it, you have +to link the binary of your program against "libkab.so", a DLL that exports +kab's functions, and to include the header file "kabapi.h" in the KDE +include directory into your sources. + +The documentation of the kab API is contained in the header file +(kabapi.h), you can extract it by parsing the file with kdoc. The kab +binary source directory in the kdeutils package contains a sample program +that tries to explain some basic functions of the API. It is important +that you report any bugs you find in the API or in the rest of kab to + + mirko@kde.org + +Since it is used in different other applications, and I cannot imagine all +the possible situations that might occur. Read the file README for some +details about the compiler environment you need to get this sources +compiled. Most of the possible problems should by now already be solved +automatically by the automake script, but you need, for example, a C++ +compiler that handles templates properly. + + Mirko Boehm, September 1998. diff --git a/kab/TODO b/kab/TODO new file mode 100644 index 000000000..fd0595d3c --- /dev/null +++ b/kab/TODO @@ -0,0 +1,29 @@ +The KDE addressbook +================================================================================ +If you want kab to be extended, please send a mail to me. Also +contact me if you want to comment some of the features mentioned +below. Send the mail to mirko@kde.org +Thanks. +PS: I am still looking for volunteers who try writing the import filters. + Most often I do not have the different other addressbook programs myselfes. +PS1: This TODO list deals with tasks to add to the KDE addressbook +library. If you want the application to be extended, read kdeutils/kab/TODO. +-------------------------------------------------------------------------------- +° [done] change mail addresses to an unlimited list with selection before + sending an email +° [done] allow user to add custom fields +° [partly implemented] a searchable keyword field +° automatic menus for kmail and ktalk -> a matter of the applications +° [done for HTML in KDE 1.1] add export methods +° [done, even more flexible] + enhance the telephone numbers section, using home/work/..., + fixed/mobile, and so on +° [done] some functions in the main display, like clickable parts + (email/talk addresses, possibly telephony support?), +° [done] IMPORTANT: Possibility to use different files and, most important, + common addressbooks for different people. +° add "Nick name" field (after KDE 2.0) +° improve speed of database indexing +-------------------------------------------------------------------------------- + Mirko Boehm, June 2000. + diff --git a/kab/addressbook.cc b/kab/addressbook.cc new file mode 100644 index 000000000..8db8fdb26 --- /dev/null +++ b/kab/addressbook.cc @@ -0,0 +1,2040 @@ +/* -*- C++ -*- + This file implements the basic personal information management class + used in the KDE addressbook. + + the KDE addressbook + + $ Author: Mirko Boehm $ + $ Copyright: (C) 1996-2001, Mirko Boehm $ + $ Contact: mirko@kde.org + http://www.kde.org $ + $ License: GPL with the following explicit clarification: + This code may be linked against any version of the Qt toolkit + from Troll Tech, Norway. $ + + $Id$ +*/ + +#include "addressbook.h" +#include "qconfigDB.h" + +#include <qfileinfo.h> +#include <qstringlist.h> + +#include <kapplication.h> +#include <kglobal.h> +#include <kstandarddirs.h> +#include <klocale.h> +#include <kdebug.h> +#include <kmessagebox.h> + +extern "C" { +#include <sys/stat.h> +#include <sys/types.h> +#include <fcntl.h> +#include <unistd.h> +} + +// ----- some defines: +#ifdef KAB_KDEBUG_AREA +#undef KAB_KDEBUG_AREA +#endif + +#define KAB_KDEBUG_AREA 800 + +#ifdef STD_USERFILENAME +#undef STD_USERFILENAME +#endif +#define STD_USERFILENAME "kab/addressbook.kab" + +#ifdef STD_CONFIGFILENAME +#undef STD_CONFIGFILENAME +#endif +#define STD_CONFIGFILENAME "kab/kab.config" + +#ifdef ENTRY_SECTION +#undef ENTRY_SECTION +#endif +#define ENTRY_SECTION "entries" + +// the name of the file-local configuration section +#ifdef LOCAL_CONFIG_SECTION +#undef LOCAL_CONFIG_SECTION +#endif +#define LOCAL_CONFIG_SECTION "config" + +// the name of the subsection for each entry +#ifdef ADDRESS_SUBSECTION +#undef ADDRESS_SUBSECTION +#endif +#define ADDRESS_SUBSECTION "addresses" + +#ifdef KAB_TEMPLATEFILE +#undef KAB_TEMPLATEFILE +#endif +#define KAB_TEMPLATEFILE "kab/template.kab" + +#ifdef KAB_CONFIGTEMPLATE +#undef KAB_CONFIGTEMPLATE +#endif +#define KAB_CONFIGTEMPLATE "kab/template.config" + +#ifdef KAB_CATEGORY_KEY +#undef KAB_CATEGORY_KEY +#endif +#define KAB_CATEGORY_KEY "categories" + +const char* AddressBook::Entry::Address::Fields[]= { + "headline", "position", + "org", "orgunit", "orgsubunit", + "deliverylabel", "address", "zip", "town", "country", "state" }; +const int AddressBook::Entry::Address::NoOfFields +=sizeof(AddressBook::Entry::Address::Fields) +/sizeof(AddressBook::Entry::Address::Fields[0]); + +const char* AddressBook::Entry::Fields[]= { + "title", "rank", "fn", "nameprefix", "firstname", "middlename", "lastname", + "birthday", "comment", "talk", "emails", "keywords", "telephone", + "urls", "user1", "user2", "user3", "user4", "custom", "categories" }; +const int AddressBook::Entry::NoOfFields +=sizeof(AddressBook::Entry::Fields)/sizeof(AddressBook::Entry::Fields[0]); + +struct QStringLess + : public binary_function<const QString&, const QString&, bool> +{ + /** The function operator, inline. */ + bool operator()(const QString& x, const QString& y) const + { + return x < y; // make one Qt operator fit exactly + } +}; + +// ----- the derived map class: +class StringKabKeyMap : public map<QString, KabKey, QStringLess> +{ /* Same as map, but a class for compilation reasons. This way we do not need + * to include the QStringLess class into the addressbook header file. */ +}; + +// ----- another derived map class: +class KeyNameMap : public map<const char*, QString, less<const char*> > +{ // same thing +}; + +KeyNameMap* AddressBook::Entry::fields; +KeyNameMap* AddressBook::Entry::Address::fields; + +bool +KabKey::operator == (const KabKey& key) const +{ + // ########################################################################### + return key.getKey()==getKey(); + // ########################################################################### +} + +void +KabKey::setKey(const QCString& text) +{ + // ########################################################################### + key=text; + // ########################################################################### +} + +QCString +KabKey::getKey() const +{ + // ########################################################################### + return key; + // ########################################################################### +} + + + +AddressBook::Entry::Address::Address() +{ +} + +bool AddressBook::Entry::Address::nameOfField(const char* key, QString& value) +{ + KeyNameMap::iterator pos; + // ----- + if(fields==0) + { // this is executed exactly one time per application instance, + // as fields is static + int counter=0; + fields=new KeyNameMap; + Q_CHECK_PTR(fields); + if(!fields->insert + (map<const char*, QString, less<const char*> >::value_type + (Fields[counter++], i18n("Headline"))).second + || + !fields->insert + (map<const char*, QString, less<const char*> >::value_type + (Fields[counter++], i18n("Position"))).second + || + !fields->insert + (map<const char*, QString, less<const char*> >::value_type + (Fields[counter++], i18n("Organization"))).second + || + !fields->insert + (map<const char*, QString, less<const char*> >::value_type + (Fields[counter++], i18n("Department"))).second + || + !fields->insert + (map<const char*, QString, less<const char*> >::value_type + (Fields[counter++], i18n("Sub-Department"))).second + || + !fields->insert + (map<const char*, QString, less<const char*> >::value_type + (Fields[counter++], i18n("Delivery Label"))).second + || + !fields->insert + (map<const char*, QString, less<const char*> >::value_type + (Fields[counter++], i18n("street/postal","Address"))).second + || + !fields->insert + (map<const char*, QString, less<const char*> >::value_type + (Fields[counter++], i18n("Zipcode"))).second + || + !fields->insert + (map<const char*, QString, less<const char*> >::value_type + (Fields[counter++], i18n("City"))).second + || + !fields->insert + (map<const char*, QString, less<const char*> >::value_type + (Fields[counter++], i18n("Country"))).second + || + !fields->insert + (map<const char*, QString, less<const char*> >::value_type + (Fields[counter++], i18n("As in addresses", "State"))).second) + { + kdDebug(KAB_KDEBUG_AREA) + << "AddressBook::Entry::Address::nameOfField (while " + << " creating field-name map): TYPO, correct this." + << endl; + } else { + kdDebug(KAB_KDEBUG_AREA) + << "AddressBook::Entry::Address::nameOfField: " + << "inserted field names." << endl; + } +#if ! defined NDEBUG + QString name; + kdDebug(KAB_KDEBUG_AREA) + << "AddressBook::Entry::Address::nameOfField:" << endl + << "Created key-fieldname-map. Defined fields are:" + << endl; + for(counter=0; counter<AddressBook::Entry::Address::NoOfFields; + ++counter) + { + pos=fields->find(Fields[counter]); + if(pos==fields->end()) + { + kdDebug(KAB_KDEBUG_AREA) << " UNDEFINED" << endl; + } else { + kdDebug(KAB_KDEBUG_AREA) + << " " << Fields[counter] << " (" + << (*pos).second.utf8() << ")" << endl; + } + } +#endif + } + // ----- now finally do the lookup: + pos=fields->find(key); + if(pos==fields->end()) + { + return false; + } else { + value=(*pos).second; + return true; + } +} + +bool AddressBook::Entry::nameOfField(const char* key, QString& value) +{ + KeyNameMap::iterator pos; + // ----- + if(fields==0) + { // this is executed exactly one time per application instance, + // as fields is static + int counter=0; + fields=new KeyNameMap; + Q_CHECK_PTR(fields); + if(!fields->insert + (map<const char*, QString, less<const char*> >::value_type + (Fields[counter++], i18n("person","Title"))).second + || + !fields->insert + (map<const char*, QString, less<const char*> >::value_type + (Fields[counter++], i18n("Rank"))).second + || + !fields->insert + (map<const char*, QString, less<const char*> >::value_type + (Fields[counter++], i18n("Formatted Name"))).second + || + !fields->insert + (map<const char*, QString, less<const char*> >::value_type + (Fields[counter++], i18n("Name Prefix"))).second + || + !fields->insert + (map<const char*, QString, less<const char*> >::value_type + (Fields[counter++], i18n("First Name"))).second + || + !fields->insert + (map<const char*, QString, less<const char*> >::value_type + (Fields[counter++], i18n("Middle Name"))).second + || + !fields->insert + (map<const char*, QString, less<const char*> >::value_type + (Fields[counter++], i18n("Last Name"))).second + || + !fields->insert + (map<const char*, QString, less<const char*> >::value_type + (Fields[counter++], i18n("Birthday"))).second + || + !fields->insert + (map<const char*, QString, less<const char*> >::value_type + (Fields[counter++], i18n("Comment"))).second + || + !fields->insert + (map<const char*, QString, less<const char*> >::value_type + (Fields[counter++], i18n("Talk Addresses"))).second + || + !fields->insert + (map<const char*, QString, less<const char*> >::value_type + (Fields[counter++], i18n("Email Addresses"))).second + || + !fields->insert + (map<const char*, QString, less<const char*> >::value_type + (Fields[counter++], i18n("Keywords"))).second + || + !fields->insert + (map<const char*, QString, less<const char*> >::value_type + (Fields[counter++], i18n("Telephone Number"))).second + || + !fields->insert + (map<const char*, QString, less<const char*> >::value_type + (Fields[counter++], i18n("URLs"))).second + || + !fields->insert + (map<const char*, QString, less<const char*> >::value_type + (Fields[counter++], i18n("User Field 1"))).second + || + !fields->insert + (map<const char*, QString, less<const char*> >::value_type + (Fields[counter++], i18n("User Field 2"))).second + || + !fields->insert + (map<const char*, QString, less<const char*> >::value_type + (Fields[counter++], i18n("User Field 3"))).second + || + !fields->insert + (map<const char*, QString, less<const char*> >::value_type + (Fields[counter++], i18n("User Field 4"))).second + || + !fields->insert + (map<const char*, QString, less<const char*> >::value_type + (Fields[counter++], i18n("Custom"))).second + || + !fields->insert + (map<const char*, QString, less<const char*> >::value_type + (Fields[counter++], i18n("Categories"))).second) + { + kdDebug(KAB_KDEBUG_AREA) + << "AddressBook::Entry::Address::nameOfField (while " + << " creating field-name map): TYPO, correct this." << endl; + } else { + kdDebug(KAB_KDEBUG_AREA) + << "AddressBook::Entry::Address::nameOfField: " + << "inserted field names." << endl; + } +#if ! defined NDEBUG + QString name; + kdDebug(KAB_KDEBUG_AREA) + << "AddressBook::Entry::nameOfField:" << endl + << "Created key-fieldname-map. Defined fields are:" << endl; + for(counter=0; counter<AddressBook::Entry::Address::NoOfFields; + ++counter) + { + pos=fields->find(Fields[counter]); + if(pos==fields->end()) + { + kdDebug(KAB_KDEBUG_AREA) << " UNDEFINED" << endl; + } else { + kdDebug(KAB_KDEBUG_AREA) + << " " << Fields[counter] << " (" + << (*pos).second.utf8() << ")" << endl; + } + } +#endif + } + // ----- now finally do the lookup: + pos=fields->find(key); + if(pos==fields->end()) + { + return false; + } else { + value=(*pos).second; + return true; + } +} + +AddressBook::ErrorCode +AddressBook::Entry::getAddress(int index, Address& address) const +{ + // ########################################################################### + list<Address>::const_iterator pos; + // ----- + if(index>=0 && (unsigned)index<addresses.size()) + { + pos=addresses.begin(); + advance(pos, index); + address=*pos; + return AddressBook::NoError; + } else { + return AddressBook::OutOfRange; + } + // ########################################################################### +} + +int AddressBook::Entry::noOfAddresses() const +{ + return addresses.size(); +} + +AddressBook::AddressBook(QWidget* parent, const char* name, bool loadit) + : QFrame(parent, name), + config(new QConfigDB(this)), + data(new QConfigDB(this)), + entries(new StringKabKeyMap), + state(NoFile) +{ + register bool GUARD; GUARD=true; + // ########################################################################### + QString dir, filename; + bool createBackup=true; + KeyValueMap *keys; + // ----- do memory checks (do not rely on exception handling): + if(config==0 || data==0 || entries==0) + { + KMessageBox::error(this, + i18n("Cannot initialize local variables."), + i18n("Out of Memory")); + kapp->quit(); // It is critical, but will possibly never happen. + } + connect(data, SIGNAL(fileChanged()), SLOT(dataFileChanged())); + connect(data, SIGNAL(changed(QConfigDB*)), + SLOT(reloaded(QConfigDB*))); + connect(config, SIGNAL(fileChanged()), SLOT(configFileChanged())); + // ----- set style: + + filename = locate( "data", STD_CONFIGFILENAME); + if (filename.isEmpty()) + { + filename = locateLocal( "data", STD_CONFIGFILENAME ); + // config does not exist yet + if(createConfigFile()!=NoError) + { + KMessageBox::sorry(this, + i18n("Your local kab configuration file " + "\"%1\" " + "could not be created. kab will probably not " + "work correctly without it.\n" + "Make sure you have not removed write permission " + "from your local KDE directory (usually ~/.kde).").arg(filename)); + state=PermDenied; + } + } + loadConfigFile(); + // ----- now get some configuration settings: + if(config->get("config", keys)) + { + keys->get("CreateBackupOnStartup", createBackup); + } + // ----- check and possibly create user standard file: + filename = locate( "data", STD_USERFILENAME ); + + if(filename.isEmpty()) // if it does not exist + { + filename = locateLocal( "data", STD_USERFILENAME); + if(createNew(filename)!=NoError) // ...and we cannot create it + { + KMessageBox::sorry(this, + i18n("Your standard kab database file " + "\"%1\" " + "could not be created. kab will probably not " + "work correctly without it.\n" + "Make sure you have not removed write permission " + "from your local KDE directory (usually ~/.kde).").arg(filename)); + state=PermDenied; + } else { + KMessageBox::information + (this, + i18n("kab has created your standard addressbook in\n\"%1\"") + .arg(filename)); + } + } + // ----- load the user standard file: + if(loadit) + { + if(load(filename)!=NoError) + { // ----- the standard file could not be loaded + state=PermDenied; + } else { + if(createBackup) + { + // ----- create the backup file: + QString temp=data->fileName(); + if(data->setFileName(temp+".backup", false, false)) + { + if(!data->save()) + { + KMessageBox::information + (this, + i18n("Cannot create backup file (permission denied)."), + i18n("File Error")); + } + } else { + KMessageBox::error + (this, + i18n("Cannot open backup file for " + "writing (permission denied)."), + i18n("File Error")); + } + // ----- reset the filename: + if(!data->setFileName(temp, true, true)) + { + KMessageBox::error + (this, + i18n("Critical error:\n" + "Permissions changed in local directory!"), + i18n("File Error")); + closeFile(false); + state=PermDenied; + } else { + state=NoError; + } + } + } + } + // ----- + data->watch(true); + // ########################################################################### +} + +AddressBook::~AddressBook() +{ + // ########################################################################### + delete data; + delete config; + delete entries; + // ########################################################################### +} + +QConfigDB* AddressBook::getConfig() +{ + // ########################################################################### + return config; + // ########################################################################### +} + +AddressBook::ErrorCode AddressBook::getState() +{ + // ########################################################################### + return state; + // ########################################################################### +} + +AddressBook::ErrorCode AddressBook::load(const QString& filename) +{ + // ----- Remark: Close the file if it could not be loaded! + // ########################################################################### + ErrorCode rc=NoError; + QFileInfo newfile, oldfile; + // ----- + QString fname = (filename.isEmpty()) ? data->fileName() : filename ; + if(fname.isEmpty()) // there was never a filename set: + { + state=NoFile; + return NoFile; + } + // ----- + newfile.setFile(fname); + oldfile.setFile(data->fileName()); + if(isSameFile(fname, data->fileName())) + { // ----- possibly deleted file: + if(data->load()) + { + emit(setStatus(i18n("File reloaded."))); + state=NoError; + } else { + switch + (KMessageBox::questionYesNo + (this, + i18n("The currently loaded file " + "\"%1\" " + "cannot be reloaded. kab may close or save it.\n" + "Save it if you accidentally deleted your data file.\n" + "Close it if you intended to do so.\n" + "Your file will be closed by default.") + .arg(oldfile.absFilePath()), + i18n("File Error"), + KStdGuiItem::close(), KStdGuiItem::save())) + { + case KMessageBox::No: // save + if(!data->save(i18n("(Safety copy on file error)").ascii(), true)) + { + KMessageBox::information(this, + i18n("Cannot save the file; will close it now."), + i18n("File Error")); + closeFile(false); + state=NoFile; + rc=PermDenied; + } else { + state=NoError; + rc=NoError; + } + break; // no error if we could save the file + default: // close + closeFile(false); + state=NoFile; + rc=NoSuchFile; + break; + } + } + } else { // ----- set new filename + if(data->setFileName(fname, true, true)) + { + if(data->load()) + { + emit(changed()); + emit(setStatus(i18n("File opened."))); + state=NoError; + } else { + KMessageBox::information(this, + i18n("Could not load the file."), + i18n("File Error")); + closeFile(false); + emit(setStatus(i18n("No such file."))); + rc=NoSuchFile; + } + } else { + if(KMessageBox::questionYesNo + (this, + i18n("The file \"%1\" cannot be found. " + "Create a new one?").arg(fname), + i18n("No Such File"), + i18n("Create"), KStdGuiItem::cancel())==KMessageBox::Yes) + { + if(createNew(fname)==NoError) + { + emit(setStatus(i18n("New file."))); + } else { // ----- do not close here, stick with the old file: + emit(setStatus(i18n("Canceled."))); + } + } + } + } + // ----- + if(rc==NoError) + { + data->watch(true); + updateMirrorMap(); + } + // ----- + return rc; + // ########################################################################### +} + +AddressBook::ErrorCode +AddressBook::getListOfNames(QStringList* strings, bool reverse, bool initials) +{ + register bool GUARD; GUARD=false; + // ########################################################################### + kdDebug(GUARD, KAB_KDEBUG_AREA) << "AddressBook::getListOfNames: called.\n"; + StringKabKeyMap::iterator pos; + QString desc; + ErrorCode rc=NoError; + ErrorCode temp; + // ----- erase the list contents: + strings->clear(); + // ----- ...and fill it: + for(pos=entries->begin(); pos!=entries->end(); ++pos) + { + temp=literalName((*pos).second, desc, reverse, initials); + if(temp!=AddressBook::NoError) + { + desc=i18n("(Internal error in kab)"); + rc=InternError; + } + if(desc.isEmpty()) + { + desc=i18n("(empty entry)"); + } + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "AddressBook::getListOfNames: adding " << desc << endl; + strings->append(desc); + } + // ----- any problems? + kdDebug(GUARD, KAB_KDEBUG_AREA) + << "AddressBook::getListOfNames: done, " + << strings->count() + << " entries.\n"; + return rc; + // ########################################################################### +} + +AddressBook::ErrorCode +AddressBook::literalName(const KabKey& key, QString& text, bool rev, bool init) +{ + // ########################################################################### + Entry entry; + ErrorCode rc; + // ----- get the entry: + rc=getEntry(key, entry); + if(rc!=NoError) + { + return rc; + } + // ----- + return literalName(entry, text, rev, init); + // ########################################################################### +} + +AddressBook::ErrorCode +AddressBook::literalName(const Entry& entry, QString& text, bool rev, bool init) +{ + register bool GUARD; GUARD=false; + // ########################################################################### + kdDebug(GUARD, KAB_KDEBUG_AREA) << "AddressBook::literalName: called.\n"; + QString firstname, middlename, lastname, nameprefix; + // ----- is the formatted name set? + if(!entry.fn.isEmpty()) + { + text=entry.fn; + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "AddressBook::literalName: done (fn).\n"; + return NoError; + } + // ----- prepare the strings: + firstname=entry.firstname.simplifyWhiteSpace(); + middlename=entry.middlename.simplifyWhiteSpace(); + lastname=entry.lastname.simplifyWhiteSpace(); + nameprefix=entry.nameprefix.simplifyWhiteSpace(); + // ----- create the initials: + if(init) + { + if(!firstname.isEmpty()) firstname=firstname.mid(0, 1)+'.'; + if(!middlename.isEmpty()) middlename=middlename.mid(0, 1)+'.'; + // if(!lastname.isEmpty()) lastname=lastname.mid(0, 1)+'.'; + } + // ----- assemble the string: + text=""; + if(rev) + { // name, firstname - add. name - name prefix + if(!lastname.isEmpty()) + { + text=lastname; + } + if(!firstname.isEmpty() || !middlename.isEmpty() || !nameprefix.isEmpty()) + { + text+=','; + } + if(!firstname.isEmpty()) + { + if(!text.isEmpty()) + { + text+=' '; + } + text+=firstname; + } + if(!middlename.isEmpty()) + { + if(!text.isEmpty()) + { + text+=' '; + } + text+=middlename; + } + if(!nameprefix.isEmpty()) + { + if(!text.isEmpty()) + { + text+=' '; + } + text+=nameprefix; + } + } else { + // firstname - add. name - name prefix - name + text=firstname; + if(!middlename.isEmpty()) + { + if(!text.isEmpty()) + { + text+=' '; + } + text+=middlename; + } + if(!nameprefix.isEmpty()) + { + if(!text.isEmpty()) + { + text+=' '; + } + text+=nameprefix; + } + if(!lastname.isEmpty()) + { + if(!text.isEmpty()) + { + text+=' '; + } + text+=lastname; + } + } + // ----- + kdDebug(GUARD, KAB_KDEBUG_AREA) << "AddressBook::literalName: done: " + << text << ".\n"; + return NoError; + // ########################################################################### +} + +unsigned int +AddressBook::noOfEntries() +{ + // ########################################################################### + return entries->size(); + // ########################################################################### +} + +void +AddressBook::dataFileChanged() +{ + // ########################################################################### + data->watch(false); // will be restarted after successful load + load(); + // ########################################################################### +} + +void +AddressBook::configFileChanged() +{ + register bool GUARD; GUARD=true; + // ########################################################################### + if(!config->load()) + { + KMessageBox::error(this, + i18n("Cannot reload configuration file!"), + i18n("File Error")); + } else { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "AddressBook::configFileChanged: " + "config file reloaded.\n"; + emit(setStatus(i18n("Configuration file reloaded."))); + } + // ########################################################################### +} + +void +AddressBook::reloaded(QConfigDB* db) +{ + register bool GUARD; GUARD=false; + // ########################################################################### + if(db==data) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "AddressBook::reloaded: file has been " + "reloaded.\n"; + updateMirrorMap(); // WORK_TO_DO: what's up with the return value? + changed(); + } + // ########################################################################### +} + +AddressBook::ErrorCode +AddressBook::save(const QString& filename, bool force) +{ + // ########################################################################### + if(filename.isEmpty()) + { + if(data->save(0, force)) + { + emit(setStatus(i18n("File saved."))); + return NoError; + } else { + return PermDenied; + } + } else { + if(data->setFileName(filename, false, false)) + { + if(data->save(0, true)) + { + emit(newFile(filename)); + return NoError; + } else { + return PermDenied; + } + } else { + return PermDenied; + } + } + // ########################################################################### +} + +bool +AddressBook::isSameFile(const QString& a, const QString& b) +{ + // ########################################################################### + QFileInfo filea(a), fileb(b); + // ----- + return filea.absFilePath()==fileb.absFilePath(); + // ########################################################################### +} + +AddressBook::ErrorCode +AddressBook::closeFile(bool saveit) +{ + // ########################################################################### + if(saveit) + { + if(save()!=NoError) + { + emit(setStatus(i18n("Permission denied."))); + return PermDenied; + } + } + data->clear(); + // data->reset(); WORK_TO_DO: File name is not reset by now. + emit(setStatus(i18n("File closed."))); + return NoError; + // ########################################################################### +} + +AddressBook::ErrorCode +AddressBook::getEntry(const KabKey& key, Entry& entry) +{ + // ########################################################################### + Section *section; + // ----- + if(getEntry(key, section)==NoError) + { + return makeEntryFromSection(section, entry); + } else { + return NoSuchEntry; + } + // ########################################################################### +} + +AddressBook::ErrorCode +AddressBook::getEntry(const KabKey& key, Section*& section) +{ + register bool GUARD; GUARD=false; + // ########################################################################### + kdDebug(GUARD, KAB_KDEBUG_AREA) << "AddressBook::getEntry: searching entry " + "with key " << key.getKey().data() << endl; + StringKabKeyMap::iterator pos; + // ----- + for(pos=entries->begin(); pos!=entries->end(); ++pos) + { + if((*pos).second==key) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "AddressBook::getEntry: key exists." << endl; + break; + } + } + if(pos==entries->end()) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "AddressBook::getEntry: no such entry.\n"; + return NoSuchEntry; + } else { + if(data->get((QCString)ENTRY_SECTION+'/'+key.getKey(), section)) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "AddressBook::getEntry: done." << endl; + return NoError; + } else { + return InternError; + } + } + // ########################################################################### +} + +AddressBook::ErrorCode +AddressBook::getEntries(list<Entry>& thelist) +{ + // ########################################################################### + StringKabKeyMap::iterator pos; + Entry entry; + ErrorCode rc; + // ----- + kdDebug(!thelist.empty(), KAB_KDEBUG_AREA) + << "AddressBook::getEntries: warning - non-empty value list!" << endl; + thelist.erase(thelist.begin(), thelist.end()); + for(pos=entries->begin(); pos!=entries->end(); ++pos) + { + rc=getEntry((*pos).second, entry); + if(rc==NoError) + { + thelist.push_back(entry); + } else { + return InternError; + } + } + // ----- + return NoError; + // ########################################################################### +} + +AddressBook::ErrorCode +AddressBook::getKey(int index, KabKey& key) +{ + // ########################################################################### + StringKabKeyMap::iterator pos; + // ----- + if((unsigned)index<entries->size()) + { + pos=entries->begin(); + advance(pos, index); + key=(*pos).second; + return NoError; + } else { + return NoSuchEntry; + } + // ########################################################################### +} + +AddressBook::ErrorCode +AddressBook::getIndex(const KabKey& key, int& index) +{ + register bool GUARD; GUARD=true; + // ########################################################################### + StringKabKeyMap::iterator pos; + // ----- + index=0; + for(pos=entries->begin(); pos!=entries->end(); ++pos) + { + // kdDebug(KAB_KDEBUG_AREA) << (*pos).second.getKey().data() << " <--> " << + // key.getKey().data() << endl; + if((*pos).second==key) break; + ++index; + } + kdDebug(pos==entries->end(), KAB_KDEBUG_AREA) << + "AddressBook::getIndex: no such key." << endl; + if(pos==entries->end()) + { + return NoSuchEntry; + } else { + return NoError; + } + // ########################################################################### +} + +Section* +AddressBook::entrySection() +{ + // ########################################################################### + Section* section; + // ----- + if(!data->get(ENTRY_SECTION, section)) + { + return 0; + } else { + return section; + } + // ########################################################################### +} + +AddressBook::ErrorCode +AddressBook::add(const Entry& entry, KabKey& key, bool update) +{ + bool GUARD; GUARD=true; + kdDebug(GUARD, KAB_KDEBUG_AREA) << "AddressBook::add: called." << endl; + // ########################################################################### + Section* theEntries=entrySection(); + Section* newEntry; + KabKey nextKey; + ErrorCode locked; + ErrorCode rc; + // ----- + if(theEntries==0) + { + kdDebug(KAB_KDEBUG_AREA) << "AddressBook::add: no entries section." + << endl; + return NoFile; + } + newEntry=new Section; + if(newEntry==0) + { + KMessageBox::error(this, + i18n("Cannot initialize local variables."), + i18n("Out of Memory")); + kapp->quit(); // It is critical, but will possibly never happen. + return InternError; // shut the compiler up... + } + // ----- lock the file: + locked=lock(); + switch(locked) + { + case PermDenied: + kdDebug(GUARD, KAB_KDEBUG_AREA) + << "AddressBook::add: permission denied." << endl; + return PermDenied; // cannot get r/w mode + case Locked: + kdDebug(GUARD, KAB_KDEBUG_AREA) + << "AddressBook::add: db is already in r/w mode." << endl; + break; + case NoError: + kdDebug(GUARD, KAB_KDEBUG_AREA) + << "AddressBook::add: got writing permissions." << endl; + break; + default: + kdDebug(GUARD, KAB_KDEBUG_AREA) + << "AddressBook::add: unknown response, exiting." << endl; + return InternError; + } + // ----- + if(makeSectionFromEntry(entry, *newEntry)==NoError) + { + nextKey=nextAvailEntryKey(); + if(!theEntries->add(nextKey.getKey(), newEntry)) + { + kdDebug(KAB_KDEBUG_AREA) + << "AddressBook::add: Cannot insert section.\n"; + rc=InternError; + } else { + key=nextKey; + emit(changed()); + rc=NoError; + } + if(update) updateMirrorMap(); + } else { + rc=InternError; + } + if(locked!=Locked) + { // ----- unlock the file here: + kdDebug(GUARD, KAB_KDEBUG_AREA) + << "AddressBook::add: dropped writing permissions." << endl; + locked=unlock(); + } + // ----- + kdDebug(GUARD, KAB_KDEBUG_AREA) << "AddressBook::add: done." << endl; + if(locked!=NoError) + { + return locked; + } + if(rc!=NoError) + { + return rc; + } + return NoError; + // ########################################################################### +} + +AddressBook::ErrorCode +AddressBook::change(const KabKey& key, const Entry& entry) +{ + // ########################################################################### + Section* theEntries=entrySection(); + Section* oldEntry; + ErrorCode locked; + ErrorCode rc; + // ----- + if(theEntries==0) + { + return NoFile; + } + // ----- lock the file: + locked=lock(); + if(locked==PermDenied) + { + return PermDenied; // cannot get r/w mode + } + // ----- + if(!theEntries->find(key.getKey(), oldEntry)) + { + rc=NoSuchEntry; + } else { + oldEntry->clear(); + rc=makeSectionFromEntry(entry, *oldEntry); + emit(changed()); + } + // ----- + if(locked!=PermDenied) + { // ----- unlock the file here: + locked=unlock(); + } + if(locked==NoError) + { + return rc; + } else { + return locked; + } + // ########################################################################### +} + +AddressBook::ErrorCode +AddressBook::remove(const KabKey& key) +{ + // ########################################################################### + Section *theEntries=entrySection(); + ErrorCode locked; + ErrorCode rc; + // ----- + if(theEntries==0) + { + return NoFile; + } + // ----- lock the file: + locked=lock(); + if(locked==PermDenied) + { + return PermDenied; // cannot get r/w mode + } + // ----- + if(theEntries->remove(key.getKey())) + { + rc=NoError; + emit(changed()); + } else { + rc=NoSuchEntry; + } + // ----- + if(locked!=PermDenied) + { // ----- unlock the file here: + locked=unlock(); + } + if(locked==NoError) + { + return rc; + } else { + return locked; + } + // ########################################################################### +} + +AddressBook::ErrorCode +AddressBook::lock() +{ + // ########################################################################### + if(!data->isRO()) return Locked; + if(data->setFileName(data->fileName(), false, false)) + { + return NoError; + } else { + KMessageBox::information(this, + i18n("The file you wanted to change could not be locked.\n" + "It is probably in use by another application or read-only."), + i18n("File Error")); + return PermDenied; + } + // ########################################################################### +} + +AddressBook::ErrorCode +AddressBook::unlock() +{ + // ########################################################################### + if(data->isRO()) return PermDenied; + if(data->setFileName(data->fileName(), true, true)) + { + return NoError; + } else { + return InternError; + } + // ########################################################################### +} + +KabKey +AddressBook::nextAvailEntryKey() +{ + // ########################################################################### + int max=0; + int temp; + Section::StringSectionMap::iterator pos; + Section *section=entrySection(); + KabKey key; + QCString dummy; + bool good=true; + // ----- + if(section!=0) + { + for(pos=section->sectionsBegin(); pos!=section->sectionsEnd(); ++pos) + { + temp=0; + temp=(*pos).first.toInt(&good); + if(!good) + { + kdDebug(KAB_KDEBUG_AREA) + << "AddressBook::nextAvailEntryKey: non-integer entry " + << endl; + } + if(temp>max) + { + max=temp; + } + } + } + // ----- + dummy.setNum(++max); + key.setKey(dummy); + // CHECK(key.getKey().toInt(&good)==max); + return key; + // ########################################################################### +} + +AddressBook::ErrorCode +AddressBook::updateMirrorMap() +{ + register bool GUARD; GUARD=false; + // ########################################################################### + kdDebug(GUARD, KAB_KDEBUG_AREA) + << "AddressBook::updateMirrorMap: updating mirror map.\n"; + QString key; + Entry entry; + ErrorCode ec; + KabKey kk; + Section *section=entrySection(); + Section::StringSectionMap::iterator pos; + // ----- + entries->erase(entries->begin(), entries->end()); + if(section==0) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "AddressBook::updateMirrorMap: done, " + "no file loaded." << endl; + return NoError; + } + for(pos=section->sectionsBegin(); pos!=section->sectionsEnd(); ++pos) + { + if(makeEntryFromSection((*pos).second, entry)!=NoError) + { + // return InternError; // it is saver to continue without a key + } + key=""; + ec=literalName(entry, key, true, false); + if(key.isEmpty() || ec!=NoError) + { + key=i18n("(empty entry)"); + } + key+=(*pos).first; // append the section name to make the key unique + kk.setKey((*pos).first); + entries->insert(StringKabKeyMap::value_type(key, kk)); + } + // ----- + kdDebug(GUARD, KAB_KDEBUG_AREA) << "AddressBook::updateMirrorMap: done." + << endl; + return NoError; + // ########################################################################### +} + +AddressBook::ErrorCode +AddressBook::makeEntryFromSection(Section* section, Entry& entry) +{ + // ########################################################################### + Section *addresses; + Section *addressSection; + Section::StringSectionMap::iterator pos; + KeyValueMap *keys; + Entry temp; + Entry::Address address; + Entry::Address addressDummy; + int count; + // ----- create the aggregats: + const QCString StringKeys[]= { + "title", + "rank", + "fn", + "nameprefix", + "firstname", + "middlename", + "lastname", + "comment", + "user1", + "user2", + "user3", + "user4" + }; + QString* StringValues[]= { + &temp.title, + &temp.rank, + &temp.fn, + &temp.nameprefix, + &temp.firstname, + &temp.middlename, + &temp.lastname, + &temp.comment, + &temp.user1, + &temp.user2, + &temp.user3, + &temp.user4 + }; + const int StringKeySize=sizeof(StringKeys)/sizeof(StringKeys[0]); + const QCString StringListKeys[]= { + "talk", + "emails", + "keywords", + "telephone", + "URLs", + "custom", + "categories" + }; + QStringList* StringListValues[]= { + &temp.talk, + &temp.emails, + &temp.keywords, + &temp.telephone, + &temp.URLs, + &temp.custom, + &temp.categories + }; + const int StringListKeySize=sizeof(StringListKeys)/sizeof(StringListKeys[0]); + // ----- first parse "addresses" subsection: + if(!section->find(ADDRESS_SUBSECTION, addresses)) + { + return InternError; // no subsection called "addresses" + } + for(pos=addresses->sectionsBegin(); pos!=addresses->sectionsEnd(); ++pos) + { + if(!addresses->find((*pos).first, addressSection)) + { + return InternError; // no section we have an iterator for? + } + keys=addressSection->getKeys(); + address=addressDummy; // clean it up + if(makeAddressFromMap(keys, address)==AddressBook::NoError) + { + // ----- add the address to the list of addresses: + temp.addresses.push_back(address); + } else { + kdDebug(KAB_KDEBUG_AREA) + << "AddressBook::makeEntryFromSection: cannot find all fields " + << "in an address subsection." << endl; + } + } + // ----- now parse all other fields directly: + keys=section->getKeys(); + for(count=0; count<StringKeySize; ++count) + { + if(!keys->get(StringKeys[count], *StringValues[count])) + { + /* Spits out lots of warnings: + kdDebug(KAB_KDEBUG_AREA) + << "AddressBook::makeEntryFromSection: error: could not get " + << "value for key " << (const char*)StringKeys[count] + << "." << endl; + */ + } + } + for(count=0; count<StringListKeySize; ++count) + { + if(!keys->get(StringListKeys[count], *StringListValues[count])) + { + /* Spits out lots of warnings: + kdDebug(KAB_KDEBUG_AREA) + << "AddressBook::makeEntryFromSection: error: could not get " + << "value for key " << (const char*)StringListKeys[count] + << "." << endl; + */ + } + } + // ----- finally get the birthday: + keys->get("birthday", temp.birthday); // this may return false (no date) + // ----- + entry=temp; + return NoError; + // ########################################################################### +} + +AddressBook::ErrorCode +AddressBook::makeAddressFromMap(KeyValueMap* keys, Entry::Address& address) +{ + // ########################################################################### + const QCString Keys[]= { + "headline", + "position", + "org", + "orgunit", + "orgsubunit", + // "role", + "deliverylabel", + "address", + "zip", + "town", + "country", + "state" + }; + QString* strings[]= { + &address.headline, + &address.position, + &address.org, + &address.orgUnit, + &address.orgSubUnit, + // &address.role, + &address.deliveryLabel, + &address.address, + &address.zip, + &address.town, + &address.country, + &address.state + }; + const int Size=sizeof(Keys)/sizeof(Keys[0]); + int count; + // ----- + for(count=0; count<Size; ++count) + { + keys->get(Keys[count], *strings[count]); + } + return NoError; + // ########################################################################### +} + +AddressBook::ErrorCode +AddressBook::makeSectionFromEntry(const Entry& entry, Section& section) +{ + // ########################################################################### + list<Entry::Address>::const_iterator addPos; + Section *addresses=0; + Section *address=0; + QCString key; // used for creating address subsection keys + int count=0; // counts the addresses + KeyValueMap *keys; + // ----- prepare the section object: + section.clear(); + // ----- first create "addresses" subsection: + if(!section.add(ADDRESS_SUBSECTION)) + { + kdDebug(KAB_KDEBUG_AREA) + << "AddressBook::makeSectionFromEntry: cannot create " << "subsection." + << " " << endl; + return InternError; + } + if(!section.find(ADDRESS_SUBSECTION, addresses)) + { + kdDebug(KAB_KDEBUG_AREA) + << "AddressBook::makeSectionFromEntry: cannot get new section." << endl; + return InternError; + } + // ----- now insert addresses: + for(addPos=entry.addresses.begin(); addPos!=entry.addresses.end(); ++addPos) + { + ++count; + key.setNum(count); + if(!addresses->add(key)) + { + kdDebug(KAB_KDEBUG_AREA) + << "AddressBook::makeSectionFromEntry: cannot create address " << endl; + return InternError; + } + if(!addresses->find(key, address)) + { + kdDebug(KAB_KDEBUG_AREA) + << "AddressBook::makeSectionFromEntry: cannot get new " << endl; + return InternError; + } + keys=address->getKeys(); + // ----- now insert keys into address: + if(!keys->insert("headline", (*addPos).headline) || + !keys->insert("position", (*addPos).position) || + !keys->insert("org", (*addPos).org) || + !keys->insert("orgunit", (*addPos).orgUnit) || + !keys->insert("orgsubunit", (*addPos).orgSubUnit) || + // !keys->insert("role", (*addPos).role) || + !keys->insert("deliverylabel", (*addPos).deliveryLabel) || + !keys->insert("address", (*addPos).address) || + !keys->insert("zip", (*addPos).zip) || + !keys->insert("town", (*addPos).town) || + !keys->insert("country", (*addPos).country) || + !keys->insert("state", (*addPos).state)) + { + kdDebug(KAB_KDEBUG_AREA) + << "AddressBook::makeSectionFromEntry: cannot completely " + << "insert this address." << endl; + return InternError; + } + } + // ----- now add the other fields: + keys=section.getKeys(); + if(!keys->insert("title", entry.title) || + !keys->insert("rank", entry.rank) || + !keys->insert("fn", entry.fn) || + !keys->insert("nameprefix", entry.nameprefix) || + !keys->insert("firstname", entry.firstname) || + !keys->insert("middlename", entry.middlename) || + !keys->insert("lastname", entry.lastname) || + !keys->insert("birthday", entry.birthday) || + !keys->insert("comment", entry.comment) || + !keys->insert("talk", entry.talk) || + !keys->insert("emails", entry.emails) || + !keys->insert("keywords", entry.keywords) || + !keys->insert("telephone", entry.telephone) || + !keys->insert("URLs", entry.URLs) || + !keys->insert("user1", entry.user1) || + !keys->insert("user2", entry.user2) || + !keys->insert("user3", entry.user3) || + !keys->insert("user4", entry.user4) || + !keys->insert("custom", entry.custom) || + !keys->insert("categories", entry.categories)) + { + kdDebug(KAB_KDEBUG_AREA) + << "AddressBook::makeEntryFromSection: cannot insert " + << "all fields of the entry." << endl; + return InternError; + } + // ----- + return NoError; + // ########################################################################### +} + +AddressBook::ErrorCode +AddressBook::createNew(const QString& filename) +{ + // ########################################################################### + const QString KabTemplateFile=locate("data", "kab/template.kab"); + kdDebug(KAB_KDEBUG_AREA) + << "AddressBook::createNew: template file is \"" + << (const char*)KabTemplateFile.utf8() << "\"." << endl; + QConfigDB db; + // ----- + if(KabTemplateFile.isEmpty() + || !db.setFileName(KabTemplateFile, true, true)) + { + KMessageBox::error(this, + i18n("Cannot find kab's template file.\n" + "You cannot create new files."), + i18n("File Error")); + return InternError; + } + if(!db.load()) + { + KMessageBox::error(this, + i18n("Cannot read kab's template file.\n" + "You cannot create new files."), + i18n("Format Error")); + + return InternError; + } + if(!db.setFileName(filename, false, false)) + { + KMessageBox::error(this, + i18n("Cannot create the file\n\"") + +filename+"\"\n"+ + i18n("Could not create the new file."), + i18n("File Error")); + return PermDenied; + } + if(!db.save()) + { + KMessageBox::error(this, + i18n("Cannot save the file\n\"") + +filename+"\"\n"+ + i18n("Could not create the new file."), + i18n("File Error")); + return InternError; + } + // ----- + return NoError; + // ########################################################################### +} + +AddressBook::ErrorCode +AddressBook::createConfigFile() +{ + // ########################################################################### + const QString ConfigTemplateFile=locate("data", "kab/template.config"); + kdDebug(KAB_KDEBUG_AREA) + << "AddressBook::createConfigFile: config template file is \"" + << (const char*)ConfigTemplateFile.utf8() << "\"." << endl; + const QString filename= locateLocal( "data", STD_CONFIGFILENAME); + QConfigDB db; + // ----- + if(ConfigTemplateFile.isEmpty() + || !db.setFileName(ConfigTemplateFile, true, true)) + { + KMessageBox::error(this, + i18n("Cannot find kab's configuration template file.\n" + "kab cannot be configured."), + i18n("File Error")); + + return InternError; + } + if(!db.load()) + { + KMessageBox::error(this, + i18n("Cannot read kab's configuration template file.\n" + "kab cannot be configured."), + i18n("File Error")); + return InternError; + } + if(!db.setFileName(filename, false, false)) + { + KMessageBox::error(this, + i18n("Cannot create the file\n\"") + +filename+"\"\n"+ + i18n("Could not create the new configuration file."), + i18n("File Error")); + return PermDenied; + } + if(!db.save()) + { + KMessageBox::error(this, + i18n("Cannot save the file\n\"") + +filename+"\"\n"+ + i18n("Could not create the new configuration file."), + i18n("File Error")); + return InternError; + } + // ----- + return NoError; + // ########################################################################### +} + +AddressBook::ErrorCode +AddressBook::loadConfigFile() +{ + // ########################################################################### + QString file = locateLocal( "data", STD_CONFIGFILENAME); + if(config->setFileName(file, true, true)) + { + if(config->load()) + { + return NoError; + } else { + KMessageBox::information(this, + i18n("Cannot load kab's local configuration file.\n" + "There may be a formatting error.\n" + "kab cannot be configured."), + i18n("File Error")); + return InternError; + } + } else { + KMessageBox::information(this, + i18n("Cannot find kab's local configuration file.\n" + "kab cannot be configured."), + i18n("File Error")); + return NoSuchFile; + } + // ########################################################################### +} + +AddressBook::ErrorCode +AddressBook::makeVCardFromEntry(const Entry&, const QString&) +{ + // ########################################################################### + return NotImplemented; + // ########################################################################### +} + +AddressBook::ErrorCode +AddressBook::makeEntryFromVCard(const QString&, Entry&) +{ + // ########################################################################### + return NotImplemented; + // ########################################################################### +} + +QString +AddressBook::getStandardFileName() +{ + // ########################################################################### + return locateLocal( "data", STD_USERFILENAME); + // ########################################################################### +} + +QString AddressBook::phoneType(AddressBook::Telephone phone) +{ + switch(phone) + { + case Fixed: return i18n("fixed"); break; + case Mobile: return i18n("mobile"); break; + case Fax: return i18n("fax"); break; + case Modem: return i18n("modem"); break; + default: return i18n("general"); + } +} + +void AddressBook::externalChange() +{ + updateMirrorMap(); +} + +Section* AddressBook::configurationSection() +{ + Section *section; + if(data!=0) + { + if(data->get(LOCAL_CONFIG_SECTION, section)) + { + return section; + } else { + return 0; + } + } else { + return 0; + } +} + +AddressBook::ErrorCode AddressBook::Entry::get(const char* fieldname, QVariant& field) +{ + // "title", "rank", "fn", "nameprefix", "firstname", "middlename", "lastname", + // "birthday", "comment", "talk", "emails", "keywords", "telephone", + // "urls", "user1", "user2", "user3", "user4", "custom" + int dummy=0; + // ----- + if(fieldname==Fields[dummy++]) + { // the title + field=title; + return NoError; + } + if(fieldname==Fields[dummy++]) + { // the rank + field=rank; + return NoError; + } + if(fieldname==Fields[dummy++]) + { // the formatted name + field=fn; + return NoError; + } + if(fieldname==Fields[dummy++]) + { // the nameprefix + field=nameprefix; + return NoError; + } + if(fieldname==Fields[dummy++]) + { // the firstname + field=firstname; + return NoError; + } + if(fieldname==Fields[dummy++]) + { // the middle name + field=middlename; + return NoError; + } + if(fieldname==Fields[dummy++]) + { // the last name + field=lastname; + return NoError; + } + if(fieldname==Fields[dummy++]) + { // the birthday + field=birthday.toString(); + return NoError; + } + if(fieldname==Fields[dummy++]) + { // the comment + field=comment; + return NoError; + } + if(fieldname==Fields[dummy++]) + { // the talk addresses + field=talk; + return NoError; + } + if(fieldname==Fields[dummy++]) + { // the email addresses + field=emails; + return NoError; + } + if(fieldname==Fields[dummy++]) + { // the keywords + field=keywords; + return NoError; + } + if(fieldname==Fields[dummy++]) + { // the telephones + field=telephone; + return NoError; + } + if(fieldname==Fields[dummy++]) + { // the urls + field=URLs; + return NoError; + } + if(fieldname==Fields[dummy++]) + { // the user field 1 + field=user1; + return NoError; + } + if(fieldname==Fields[dummy++]) + { // the user field 2 + field=user2; + return NoError; + } + if(fieldname==Fields[dummy++]) + { // the user field 3 + field=user3; + return NoError; + } + if(fieldname==Fields[dummy++]) + { // the user field 4 + field=user4; + return NoError; + } + if(fieldname==Fields[dummy++]) + { // the custom fields (app specific) + field=custom; + return NoError; + } + // ----- we did not find that field: + return NoSuchField; +} + +AddressBook::ErrorCode AddressBook::Entry::Address::get(const char* fieldname, + QVariant& field) +{ + // "headline", "position", + // "org", "orgunit", "orgsubunit", + // "deliverylabel", "address", "zip", "town", "country", "state" + int dummy=0; + // ----- + if(fieldname==Fields[dummy++]) + { // the headline + field=headline; + return NoError; + } + if(fieldname==Fields[dummy++]) + { // the position + field=position; + return NoError; + } + if(fieldname==Fields[dummy++]) + { // the organization + field=org; + return NoError; + } + if(fieldname==Fields[dummy++]) + { // the organizational unit + field=orgUnit; + return NoError; + } + if(fieldname==Fields[dummy++]) + { // the organizational subunit + field=orgSubUnit; + return NoError; + } + if(fieldname==Fields[dummy++]) + { // the delivery label + field=deliveryLabel; + return NoError; + } + if(fieldname==Fields[dummy++]) + { // the address + field=address; + return NoError; + } + if(fieldname==Fields[dummy++]) + { // the zip code + field=zip; + return NoError; + } + if(fieldname==Fields[dummy++]) + { // the town + field=town; + return NoError; + } + if(fieldname==Fields[dummy++]) + { // the country + field=country; + return NoError; + } + if(fieldname==Fields[dummy++]) + { // the state + field=state; + return NoError; + } + // ----- we did not find that field: + return NoSuchField; +} + +Section* AddressBook::categoriesSection() +{ + const QString Predefines[]= { + i18n("Business"), + i18n("Private"), + i18n("Dates") }; + size_t Size=sizeof(Predefines)/sizeof(Predefines[0]); + Section* section; + Section* categories; + KeyValueMap *keys; + // ----- + if(data->get(KAB_CATEGORY_KEY, section)) + { + // it exists, go ahead + return section; + } else { + kdDebug(KAB_KDEBUG_AREA) + << "AddressBook::categoriesSection: creating categories structure." << endl; + // it does not exist - create it + if(!data->createSection(KAB_CATEGORY_KEY)) + { + kdDebug(KAB_KDEBUG_AREA) + << "AddressBook::categoriesSection: error creating categories section." + << endl; + return 0; + } + data->get(KAB_CATEGORY_KEY, section); + // add the predefined categories: + categories=new Section; + keys=categories->getKeys(); + for(size_t count=0; count<Size; ++count) + { + QStringList values; + values.append(Predefines[count]); + keys->insert(QCString().setNum(count), values); + } + section->add(KAB_CATEGORY_KEY, categories); + keys=section->getKeys(); + keys->insert("NextAvailableCatKey", (long)Size); + } + save(); + if(data->get(KAB_CATEGORY_KEY, section)) + return section; + return 0; // might not happen +} + +AddressBook::ErrorCode AddressBook::categories(CategoriesMap& cat) +{ // WORK_TO_DO: use a permanent cached map and update on changed() + kdDebug(KAB_KDEBUG_AREA, !cat.isEmpty()) + << "AddressBook::categories: warning - categories map is supposed to be empty!" + << endl; + Section *section; + Section *categories; + KeyValueMap* keys; + int key; + bool rc; + QStringList values; + StringStringMap::iterator pos; + // ----- query categories section: + section=categoriesSection(); + Q_CHECK_PTR(section); + // ----- + if(!section->find(KAB_CATEGORY_KEY, categories)) + { + kdDebug(KAB_KDEBUG_AREA) + << "AddressBook::categories: error in database structure." + << endl; + return InternError; + } + // ----- everything is set up, create the categories map: + // use an iterator to walk over all elements of categories key-value-map: + keys=categories->getKeys(); + for(pos=keys->begin(); pos!=keys->end(); ++pos) + { + if(!keys->get((*pos).first, values)) + { + kdDebug(KAB_KDEBUG_AREA) + << "AddressBook::categories: internal error querying categories." + << endl; + } else { + key=(*pos).first.toInt(&rc); + if(rc) + { + cat.insert(key, values[0]); + } else { + kdDebug(KAB_KDEBUG_AREA) + << "AddressBook::categories: error - non-integer category key - ignored." + << endl; + } + } + } + return NoError; +} + +#include "addressbook.moc" diff --git a/kab/addressbook.h b/kab/addressbook.h new file mode 100644 index 000000000..ccd2d1bc4 --- /dev/null +++ b/kab/addressbook.h @@ -0,0 +1,734 @@ +/* -*- C++ -*- + This file declares the basic personal information management class + used in the KDE addressbook. + + the KDE addressbook + + $ Author: Mirko Boehm $ + $ Copyright: (C) 1996-2001, Mirko Boehm $ + $ Contact: mirko@kde.org + http://www.kde.org $ + $ License: GPL with the following explicit clarification: + This code may be linked against any version of the Qt toolkit + from Troll Tech, Norway. $ + + $Id$ +*/ + +#ifndef ADDRESSBOOK_H +#define ADDRESSBOOK_H + +class KeyValueMap; +class QConfigDB; +class Section; +class StringKabKeyMap; /* The type of the mirror map. */ +class QStringList; +/* Used to implement field lookup accoording to + keys. */ +class KeyNameMap; + +#include <list> +#include <qframe.h> +#include <qdatetime.h> +#include <qstring.h> +#include <qsize.h> +#include <qvariant.h> +#include <qmap.h> + +/** + * The class KabKey is used to select entries out of the database file. + * In future, keys might become more complex. + */ +class KabKey +{ +public: + /** + * The comparison operator + */ + bool operator==(const KabKey&) const; + /** + * Get the key as a QCString + */ + QCString getKey() const; + /** + * Set this key + */ + void setKey(const QCString&); +protected: + /** + * The key of the in this database + */ + QCString key; + + class KabKeyPrivate; + KabKeyPrivate *d; +}; + +class CategoriesMap : public QMap<int, QString> +{ +}; + +// ----------------------------------------------------------------------------- +// this will be incremented when kab's file format changes significantly: +#if defined KAB_FILE_FORMAT +#undef KAB_FILE_FORMAT +#endif +/* + 0: all formats before the email list was implemented + 1: format enhanced for unlimited number of email addresses + 2: format enhanced by more address fields + 10: format of kab 2 + 11: added categories +*/ +#define KAB_FILE_FORMAT 11 + +// ----------------------------------------------------------------------------- +// this defines will contain the program version used for different purposes: +#ifdef KAB_VERSION +#undef KAB_VERSION +#endif +#ifdef KAB_MINOR +#undef KAB_MINOR +#endif +#ifdef KAB_PATCH +#undef KAB_PATCH +#endif +#ifdef KAB_STATE +#undef KAB_STATE +#endif +#define KAB_VERSION 2 +#define KAB_MINOR 2 +#define KAB_PATCH 0 +#define KAB_STATE "final" + +// ----------------------------------------------------------------------------- +/** The class AddressBook implements the base class for the KDE addressbook. + * \par Overview + * It + * is used by the KabAPI to make the interface to kab files available to + * application programmers. <BR> + * Unlike in the first kab version, the configuration file and the data file are + * different objects of QConfigDB. This way, the data file is no more limited + * to the one in the users KDE directory, multiple files may be used. Different + * instances of the program may use different data files. Read-only addressbook + * files are possible. <BR> + * Only one configuration file per user is used, it is <BR> + * <TT> ~/.kde/share/apps/kab/kab.config </TT> <BR> + * A standard user file will automatically be created as <BR> + * <TT> ~/.kde/share/apps/kab/addressbook.kab </TT> <BR> + * File changes are watched by the program, so every instance will automatically + * update its database on a change of the opened file. + * + * \par The KDE addressbook database system + * kab manages entries in address databases based on a key system where the + * program assigns keys to added entries. These keys are not reused in one file, + * so API users can rely on a key to be unique and identifying until the entry + * is deleted by the user (this is a change to kab 1 that reused freed entry + * keys). Of course, in different files a key might be used twice. <BR> + * The keys are objects of the type KabKey and define the section in the + * addressbook database where the entry is stored (see QConfigDB + * reference). Keys invalidate on file changes, so keep track of the + * signal ::changed. <BR> + * kab watches file changes. If the opened file changes on disk, it is + * automatically reloaded and ::changed() is emitted. + * + * \par The users standard personal information database + * kab assumes that it is able to read and write the users standard database. + * This way, the kab application itselfes and applications using the KabAPI can + * rely on the possibility to add entries to this database (from a browser, for + * example). Usually, this file is opened automatically by the constructor. + * If - for what reason ever - the file cannot be either created or read, kab + * will still start up, but no database operation will work until the user + * opened a file. In this case, the method ::getState will return + * ::PermDenied. In general it is a good idea to check the return value of the + * ::getState method before using KabAPI operations. + * + * \par The mirror map + * The entries are stored in the QConfigDB object ::data which represents the + * currently opened file. In every file there is a section with the name + * <TT> entries </TT> that contains a subsection for every entry. The name of + * the subsection is the key of the entry. <BR> + * When retrieving the sections, they are ordered alphabetically by their keys. + * This is not what users expect, since the keys show the insertion order of + * the entries, not more and not less. Additionally the displaying order should + * be configurable. <BR> + * That is why kab uses a STL map to map its entry keys to user + * (at least programmer...) defined descriptors. Usually, the descriptors are + * created as a combination of the entry data, and then displayed in aphabetical + * order in the selector combobox. This map is called the mirror map throughout + * the documentation. It is created or updated every time the database changes. + * Thus the way to find a special entry is: <OL> + * <LI> the user selects an item in the selector combo box, returning its + * index, </LI> + * <LI> the index is used to find the key of the entry in the mirror map, </LI> + * <LI> and finally the entry is retrieved by its key from the database. </LI> + * </OL> + * To modify the sorting order, the way to create the entry descriptors in the + * mirror map nedds to be changed. + * + * \par The view + * If you display an AddressBook object (that is a derived QFrame), + * it may show an entry + * of the database that you might select. The entry you hand over to the method + * ::displayEntry does not need to be contained in the currently loaded file. + * This way you may integrate views of + * the users addressbook database in your own application as a simple widget + * type. To allow the user to + * navigate through the database, you might want to show kab's own toolbar in + * your mainwindow (or whereever). (The toolbar is not implemented by now). <BR> + * Some parts of the AddressBook widget are \e interactive, that means they are + * displayed as transparent KURLLabels that react when the user clicks on it. + * These interactive parts have to be enabled by calling setInteractiveMode(). + */ +class AddressBook : public QFrame +{ + // ############################################################################ + Q_OBJECT + // ---------------------------------------------------------------------------- +public: + /** + * The return values of some AddressBook member functions are #ErrorCode + * values. + */ + enum ErrorCode { + NoError, /** No error, the operation did not fail. */ + PermDenied, /**< Access permissions for the operation are not available. */ + Locked, /**< An writing operation on a locked file was requested. */ + Rejected, /**< The requested operation has been rejected by the user. */ + NoSuchEntry, /**< An entry has been referenced using a unknown key. */ + NoEntry, /**< You tried to retrieve an entry but there is none. */ + NoFile, /**< No file has been loaded by now. */ + NoSuchFile, /**< A filename could not be found on the filesystem. */ + InternError, /**< A error in kab's internal logic occurred. */ + OutOfRange, /**< An index value was out of the allowed range. */ + NoSuchField, /**< You queried a field that does not exist. */ + NotImplemented /**< The requested operation is not implemented. */ + }; + /** + * Some predefined telephone types. More are possible, but these are + * provided and thus, for example, translated. + */ + enum Telephone { + NoTelephone, + Fixed, + Mobile, + Fax, + Modem, + User1, + User2, + User3, + NoOfTelephoneTypes + }; + /** Each entry in a loaded database has its own ::Entry object. + * + * \par The structure of the address database + * As you might have read, kab uses the QConfigDB class to manage its + * data files. This class is intended to handle hierarchical structures. + * Thus, kab is able to create human readable but still deep and complex + * data files. This paragraph describes the overall structure of the + * files, the next two deal with special parts of it. <BR> + * First of all, kab II data files (that usually end with \c .kab, while in + * kab 1 the fixed file name was \c addressbook.database) have two main + * sections (see the documentation of the QConfigDB and Section classes), + * one is called \c config, it contains different file specific + * configuration settings like the last displayed entry, and one section + * called \c entries that in turn contains a subsection for each entry in + * the database file. The keys of this subsections are the literal strings + * that are used in the KabKey class in the member KabKey::key. Each entry + * subsection has some key-value-pairs described below and another + * subsection "addresses" with one or more addresses in it. See the + * following example for a kab II data file (without any key-value-pairs): + * <BR> <PRE> + * [config] + * [END] + * [entries] + * [1] (the first entry with literal key "1") + * [addresses] + * [1] (the first address, addresses are enumerated) + * [END] + * [2] (the second address) + * [END] + * ... (more addresses may follow) + * [END] + * [END] + * [2] (the second entry) + * [addresses] + * [1] + * [END] + * [END] + * [END] + * ... (more entries may follow) + * [END] </PRE> <BR> + * + * \par The fields an entry contains + * An entry contains all settings that are expected to be unique for all + * addresses directly as key-value-pairs. Everything that is part of a + * specific address of this person is part of an object of the member list + * \c addresses referenced in the next paragraph. <BR> + * The keys defined directly in the entry sections are: <DL> + * <DT>title<DT><DD> The title of that person. </DD> + * <DT>rank<DT><DD>A possible military rank of that person. </DD> + * <DT>fn<DT><DD>The formatted name. If it is not empty, it replaces the + * standard combination of the other name fields in the address + * display. </DD> + * <DT>nameprefix<DT><DD>A possible name prefix. </DD> + * <DT>firstname<DT><DD>The first name. </DD> + * <DT>middlename<DT><DD>The middle name. </DD> + * <DT>lastname<DT><DD>The last name. </DD> + * <DT>birthday<DT><DD>The birthday (a QDate). </DD> + * <DT>comment<DT><DD>A free form comment. </DD> + * <DT>talk<DT><DD>The talk addresses (a string list). </DD> + * <DT>emails<DT><DD>The email addresses (a string list). </DD> + * <DT>keywords<DT><DD>A list of free-form keywords. </DD> + * <DT>telephone<DT><DD>A list of telephone numbers in a special format. </DD> + * <DT>URLs<DT><DD>A list of internet addresses. </DD> + * <DT>user_1<DT><DD>The first user-declared data field. </DD> + * <DT>user_2<DT><DD>The second user-declared data field. </DD> + * <DT>user_3<DT><DD>The third user-declared data field. </DD> + * <DT>user_4<DT><DD>The fourth user-declared data field. </DD> + * </DL> + * See the next section for a description of the addresses subsections. + * + * \par The fields of the addresses subsections + * The section for each entry contains a subsection \c addresses with + * in turn a subsection for each address. The addresses are enumerated + * in the order they are inserted, their keys are the numbers of + * inserting converted to a string. <BR> + * The keys defined in an address subsection are: <DL> + * <DT>headline</DT><DD> A headline shown for the address. </DD> + * <DT>position</DT><DD> The position of the person. </DD> + * <DT>org</DT><DD> The organization. </DD> + * <DT>orgunit</DT><DD> The organizational unit. </DD> + * <DT>orgsubunit</DT><DD> The organizational subunit. </DD> + * <DT>role</DT><DD> The role of the person. </DD> + * <DT>deliverylabel</DT><DD> A label for delivering to this address. </DD> + * <DT>address</DT><DD> The street, house no., flat etc line. </DD> + * <DT>zip</DT><DD> A zip or postal code. </DD> + * <DT>town</DT><DD> The town the person lives in in this address. </DD> + * <DT>country</DT><DD> The country for federal states. </DD> + * <DT>state</DT><DD> The state for federal states. </DD> + * </DL> + * + * \par The local configuration section + * For each kab II database file there are some settings that apply + * only to the file itselfes, not to all kab databases the user works + * with. These settings are called the local configuration. The settings + * are stored in the \c config section of the local file. The following + * keys are declared in this section: <DL> + * <DT>user_1</DT><DD>The \e name of the first user-declared field. </DD> + * <DT>user_2</DT><DD>The \e name of the second user-declared field. </DD> + * <DT>user_3</DT><DD>The \e name of the third user-declared field. </DD> + * <DT>user_4</DT><DD>The \e name of the fourth user-declared field. </DD> + * </DL> + * More fields will surely follow. + **/ + class Entry { + public: + // types: + /** Since an entry may have different addresses, we need a type for them. + * Multiple addresses are used to distinguish between addresses at home + * and work, for example. */ + class Address { + public: + /** A constructor. */ + Address(); + // ----- This aggregates are used to access the fields by + // keywords. We use char* here to be able to initialize the keys + // in code as statics without initializing Qt etc. : + /** An aggregat containing the keys of all declared fields: + */ + static const char* Fields[]; + /** The number of elements in Fields. + */ + static const int NoOfFields; + /** Query the literal, translated name of the field given by its + key. + @return false if key is not defined */ + static bool nameOfField(const char* key, QString& value); + /** Get a field by its field name. Field names are defined in + @see Fields. Since there are different file types a field + may be represented with, a QVariant is returned. */ + ErrorCode get(const char* key, QVariant&); + // ----- the following members represent the fields: + /** The headline for this address. */ + QString headline; + /** The position of the person at this address. */ + QString position; + /** The organization of the person at this address. */ + QString org; + /** The org unit of the person at this address. */ + QString orgUnit; + /** The org subunit of the person at this address. */ + QString orgSubUnit; + /** The description for delivering. */ + QString deliveryLabel; + /** Street, with house number. */ + QString address; + /** Zip or postal code. */ + QString zip; + /** The town. */ + QString town; + /** The country for federal states. */ + QString country; + /** The state for federal states. */ + QString state; + protected: + static KeyNameMap *fields; + }; + /** Contains one or more Address objects. */ + std::list<AddressBook::Entry::Address> addresses; + // ----- This aggregates are used to access the fields by + // keywords. We use char* here to be able to initialize the keys + // in code as statics without initializing Qt etc. : + /** An aggregat containing the keys of all declared fields: + */ + static const char* Fields[]; + /** The number of elements in Fields. + */ + static const int NoOfFields; + // methods: + /** Use this method to retrieve the address at the given \a index. + * The method is provided for convenience. The address data is + * returned in \a address. */ + AddressBook::ErrorCode getAddress(int index, Address& address) const; + /** Returns the number of addresses of this entry. */ + int noOfAddresses() const; + /** Query the literal, translated name of the field given by its + key. + @return false if key is not defined */ + static bool nameOfField(const char* key, QString& value); + /** Get a field by its field name. Field names are defined in + @see Fields. Since there are different file types a field + may be represented with, a QVariant is returned. */ + ErrorCode get(const char* key, QVariant&); + // members: + // this parts are assumed to be unique for every entry: + QString title; /**< The title of the person. */ + QString rank; /**< The rank of the person. */ + QString fn; /**< The formatted name of the person. */ + QString nameprefix; /**< A possibly name prefix for that person. */ + QString firstname; /**< The first name of the person. */ + QString middlename; /**< The middle name of the person. */ + QString lastname; /**< The last name of the person. */ + QDate birthday; /**< The birthday of this person. */ + QString comment; /**< The comment. */ + QStringList talk; /**< The talk addresses. */ + QStringList emails; /**< The email addresses. */ + QStringList keywords; /**< The user defined keywords for searching. */ + /** + * Telephon numbers and types. This list contains combinations of telephone + * numbers and the types of the phones, in this order. See enum + * Telephone above. + */ + QStringList telephone; + QStringList URLs; /**< The home or related web pages of this person. */ + QString user1; /**< The first user-declared field. */ + QString user2; /**< The second user-declared field. */ + QString user3; /**< The third user-declared field. */ + QString user4; /**< The fourth user-declared field. */ + QStringList custom; + QStringList categories; /**< The categories this entry is assigned to. */ + protected: + static KeyNameMap *fields; + }; + /** + * The constructor. If \e load is true, the user standard file will + * automatically be loaded into the object. + */ + AddressBook(QWidget* parent=0, const char* name=0, bool load=true); + ~AddressBook(); /**< The destructor. */ + /** + * Get the internal state of the object. + * If no problem occurred, it returns ::NoError. + * If the standard or the latest opened file could not be loaded, + * it returns ::PermDenied + */ + ErrorCode getState(); + /** + * Load the file with the given path. An empty file name reloads the + * currently opened file. + */ + ErrorCode load(const QString& filename=QString::null); + /** + * Save the file to the given path and file name. An empty file name saves + * to the file where the database has been read from. + * If force is true, the method will switch to r/w mode for saving and + * back. + */ + ErrorCode save(const QString& filename=QString::null, bool force=false); + /** + * Close this file. + * ::closeFile assures sure that the ::data object is reset no matter of the + * state of the assigned file. + * If \a save is true, it will not close the file if it could not be + * saved. + */ + ErrorCode closeFile(bool saveit=true); + /** + * Retrieve an entry from the database by its key. + */ + ErrorCode getEntry(const KabKey& key, Entry&); + /** + * Retrieve the Section of the entry directly, returning a section object. + */ + ErrorCode getEntry(const KabKey& key, Section*&); + /** + * Get all entries in displaying order. This method might be slow (O(n)). + */ + ErrorCode getEntries(std::list<Entry>&); + /** + * Add an ::Entry, \a return the new key for further operations. + * If update is false, the mirror map will not be affected, if it is true, + * the mirror map gets updated, too. + */ + ErrorCode add(const Entry&, KabKey& key, bool update=true); + /** + * Set the entry with the given key to the new contents. Be aware of + * #PermDenied for read-only databases or file sharing conflicts. You cannot + * change entries in a database for which you do not have write access. + */ + ErrorCode change(const KabKey& key, const Entry&); + /** + * Remove the entry with the given key. Returns #NoSuchEntry if there is no + * entry with this key, #PermDenied for read only databases. + */ + ErrorCode remove(const KabKey& key); + /** + * Returns the number of entries in the loaded database. + */ + unsigned int noOfEntries(); + /** + * This method returns the literal name for the entry, + * containing either the formatted name (if given) or a + * combination of the first, additional and last name. + * The name is returned in \a text. + * If \a reverse is false, the text looks like + * firstname (add. name) last name, + * if it is true, + + last name, first name (add. name). + * If \a initials is true, the text contains initials only: + * f. a. name [with reverse==false] or + * name, f. a. [with reverse==true]. + * If there is no entry with this key, the method returns ::NoSuchEntry. + */ + ErrorCode literalName(const KabKey& key, QString& text, + bool reverse=false, bool initials=false); + /** + * This is an overloaded method that differs only in the arguments it takes. + */ + ErrorCode literalName(const Entry& entry, QString& text, + bool reverse=false, bool initials=false); + /** + * Get the key of the item in the selector with the given index. + */ + ErrorCode getKey(int index, KabKey&); + /** + * Get the index of this key in the selector. This is the reverse + * functionality to getKey(). + */ + ErrorCode getIndex(const KabKey&, int&); + /** + * Fill the string list with name lines. If your application shows a combobox + * containing an overview over the currently loaded KabAPI database, then + * call this method when receiving the signal ::changed and display the list + * in the combo. + */ + ErrorCode getListOfNames(QStringList*, bool reverse=true, bool initials=true); + /** + * Hand over the configuration database. Careful! + */ + QConfigDB* getConfig(); + /** + * This method returns the QConfigDB section where the configuration of the + * currently opened file is stored. It might be used to retrieve or to modify + * these settings. The file-specific settings are saved along with + * the open file. + * Do not confuse the configuration section of the opened file with + * the configuration of the program. Each file might have its own + * local configuration for some settings where it makes sense. + * @ return Null if no file has been opened. + */ + Section *configurationSection(); + /** + * This method opens a dialog for configuring the file-specific settings + * for the loaded file. The database is automatically saved if the user + * accepts the changes. + */ + // ErrorCode configureFile(); + /** + * Creates a new database with the given file name. If the filename is + * empty, it creates the users standard data file. The method does not load + * the new database. + */ + ErrorCode createNew(const QString& filename=QString::null); + /** + * Creates the local configuration file. The filename is fixed to + * \c kab.config, it will be created in the local kab directory + * (\c $HOME/.kde/share/apps/kab). Adapt the global configuration template + * file (\c $KDEDIR/share/apps/kab/template.config) for unusual site-specific + * settings. + * The method does not load the new config file. + */ + ErrorCode createConfigFile(); + ErrorCode loadConfigFile(); /**< Load the local configuration file. */ + // ErrorCode configureKab(); /**< Open the configuration dialog for the KabAPI. */ + // QSize sizeHint(); /**< The preferred (minimal) size of the view. */ // ni + /** + * This method parses a vCard and creates an Entry object from it. + */ + ErrorCode makeEntryFromVCard(const QString& card, Entry&); + /** + * This method creates a vCard string from an entry. + */ + ErrorCode makeVCardFromEntry(const Entry& entry, const QString& card); + /** + * Returns the complete path to the user standard file. An empty path + * indicates an error, but this should not happen. It is NOT ensured + * that the file exists. + */ + QString getStandardFileName(); + /** + * Call this to get a telephone type translated to the locale. + */ + static QString phoneType(AddressBook::Telephone); + /** + * Query the entry categories defined for this address + * book. Categories may differ between addressbooks. + */ + ErrorCode categories(CategoriesMap& categories); + /** + * Modify the categories for this addressbook. The map given will replace the + * previoulsy stored one. + */ + ErrorCode setCategories(const CategoriesMap& categories); + /** + * Query the real name of a category by its index. + */ + ErrorCode category(int index, QString&); + /** + * Query the category section. This is the "raw" storage of the defined + * categories. It is always defined (or will be created if you have an old + * file that does not have categories). + * @see Section + */ + Section* categoriesSection(); + // ---------------------------------------------------------------------------- + +#ifdef KDE_NO_COMPAT +private: +#endif + QString getStandardFilename() { return getStandardFileName(); }; + +protected: + QConfigDB *config; /**< The configuration database. */ + QConfigDB *data; /**< The currently open data files. */ + StringKabKeyMap *entries; /**< The mirror map. */ + ErrorCode state; /**< The internal state of the object. */ + /** + * Get the next available entry key for this file. For internal use only. + */ + KabKey nextAvailEntryKey(); + /** + * Returns true if both pathes point to the same file. + * The method resolves relative file names to find this out. + */ + bool isSameFile(const QString& a, const QString& b); + /** + * Parse the section and copy its contents into \a entry. + * The method expects a subsection called \e addresses that contains a + * number of subsections each containing data for one Entry::Address object. + * All other fields are copied directly into the members of \a entry. + */ + ErrorCode makeEntryFromSection(Section*, Entry&); // nicht beendet + /** + * For internal use only. This parses one address subsection and puts its + * contents in the Address object. + */ + ErrorCode makeAddressFromMap(KeyValueMap*, Entry::Address&); + /** + * Create a section from the entries settings. + */ + ErrorCode makeSectionFromEntry(const Entry&, Section&); // nicht beendet + /** + * Update the mirror map after changes of the database. + */ + ErrorCode updateMirrorMap(); + /** + * Get the entry section of the file. Maybe a NULL pointer if no file is + * opened. + */ + Section* entrySection(); + /** + * Lock the file for changing. + * Since all database files are opened read-only, they must be locked before + * the files contents are changed. After changing the file must be saved and + * unlocked. Returns ::PermDenied if the file could not be locked, ::NoError + * if it was not locked and is now, and ::Locked if the file is already + * locked. + * @see unlock + * @see QConfigDB::setFileName + */ + ErrorCode lock(); + /** + * Unlock the file after changes. Returns ::NoError if the file was locked + * and could be unlocked, ::PermDenied if the file was not locked and + * possibly ::InternError if anything fails. + * @see ::lock + * @see QConfigDB::setFileName + */ + ErrorCode unlock(); + /** + * Set the background image. Kab will store a deep copy of the image. + * If the image is a null image nothing will be displayed. + */ + // void setBackground(const QImage&); + /** + * Enable or disable the background image. + */ + // void setBackgroundEnabled(bool state); + /** + * Retrieve wether the background image is enabled or not. + */ + // bool getBackgroundEnabled(); + /** + * Set if the URL labels are interactive. + */ + // void setInteractiveMode(bool state); + /** + * Get if the URL labels are interactive. + */ + // bool getInteractiveMode(); +protected slots: + /** + * Called when ::data has been cleared or reloaded. + */ + void reloaded(QConfigDB*); + /** + * Called when the \e file assigned to ::data has changed on disk. + */ + void dataFileChanged(); + /** + * Called when the \e file assigned to ::config has changed on disk. + */ + void configFileChanged(); + // ---------------------------------------------------------------------------- +public slots: + /** + * This slot is called when an external object changed the database through + * the kabapi. + */ + void externalChange(); + // ---------------------------------------------------------------------------- +signals: + void changed(); /**< The entries have changed, update the selector. */ + void setStatus(const QString&); /**< This is kab radio with the news... */ + void newFile(const QString&); /**< Notifies changes of the file name. */ + // ############################################################################ + +private: + class AddressBookPrivate; + AddressBookPrivate *d; +}; + +#endif // ADDRESSBOOK_H + diff --git a/kab/kabapi.cc b/kab/kabapi.cc new file mode 100644 index 000000000..0f5d76222 --- /dev/null +++ b/kab/kabapi.cc @@ -0,0 +1,220 @@ +/* -*- C++ -*- + This file implements the application programming interface + for using kab's addressbook files within other programs. + Parse it with kdoc to get the API documentation. + + the KDE addressbook + + $ Author: Mirko Boehm $ + $ Copyright: (C) 1996-2001, Mirko Boehm $ + $ Contact: mirko@kde.org + http://www.kde.org $ + $ License: GPL with the following explicit clarification: + This code may be linked against any version of the Qt toolkit + from Troll Tech, Norway. $ + + $Id$ +*/ + +#include "kabapi.h" +#include <klistbox.h> +#include <kmessagebox.h> +#include <klocale.h> +#include <kdebug.h> + + +#include "kabapi.moc" + +#ifdef KAB_KDEBUG_AREA +#undef KAB_KDEBUG_AREA +#endif + +#define KAB_KDEBUG_AREA 800 + +using namespace std; + +KabAPI::KabAPI(QWidget* parent, const char* name) + : KDialogBase(parent, name), + book(0), + listbox(new KListBox(this)), + selection(-1) +{ + Q_CHECK_PTR(listbox); + setMainWidget(listbox); + showButtonApply(false); + enableButtonSeparator(true); + connect(listbox, SIGNAL(highlighted(int)), SLOT(entrySelected(int))); + connect(listbox, SIGNAL(doubleClicked ( QListBoxItem * )),SLOT(slotDoubleClicked ( QListBoxItem * ))); +} + + +void KabAPI::slotDoubleClicked ( QListBoxItem * ) +{ + accept(); +} + +int KabAPI::exec() +{ + QStringList names; + // ----- + if(book==0) + { + kdDebug(KAB_KDEBUG_AREA) + << "KabAPI::exec: you have to call init before using the API." + << endl; + return -1; + } else { + if(book->getListOfNames(&names, true, false)==AddressBook::NoError) + { + listbox->clear(); + listbox->insertStringList(names); + if(names.count()>0) + { + listbox->setCurrentItem(0); + } + listbox->setMinimumSize(listbox->sizeHint()); + adjustSize(); + resize(minimumSize()); + return KDialogBase::exec(); + } else { + kdDebug(KAB_KDEBUG_AREA) << "KabAPI::exec: error creating interface." + << endl; + return -1; + } + } +} + +AddressBook::ErrorCode KabAPI::init() +{ + // ############################################################################ + book=new AddressBook(0, "KABAPI::book", true); //change parent from "this" to "0" //dsweet + if(book->getState()==AddressBook::NoError) + { + connect(book, SIGNAL(setStatus(const QString&)), + SLOT(setStatusSlot(const QString&))); + return AddressBook::NoError; + } else { + return AddressBook::InternError; + } + // ############################################################################ +} + +AddressBook::ErrorCode KabAPI::getEntry(AddressBook::Entry& entry, KabKey& key) +{ + // ############################################################################ + if(book->noOfEntries()==0) + { + return AddressBook::NoEntry; + } + if(selection>=0) + { + if(book->getKey(selection, key)==AddressBook::NoError) + { + if(book->getEntry(key, entry)==AddressBook::NoError) + { + return AddressBook::NoError; + } else { + return AddressBook::InternError; // this may not happen + } + } else { + return AddressBook::NoEntry; + } + } else { + return AddressBook::InternError; + } + // ############################################################################ +} + +AddressBook::ErrorCode KabAPI::add(const AddressBook::Entry& entry, KabKey& key, + bool update) +{ + // ############################################################################ + if(book->add(entry, key, update)!=AddressBook::NoError) + { + KMessageBox::sorry(this, i18n("Your new entry could not be added.")); + return AddressBook::InternError; + } else { + return AddressBook::NoError; + } + // ############################################################################ +} + +AddressBook::ErrorCode KabAPI::remove(const KabKey& key) +{ + Q_CHECK_PTR(book); + // ############################################################################ + if(book->AddressBook::remove(key)==AddressBook::NoError) + { + return AddressBook::NoError; + } else { + return AddressBook::NoEntry; + } + // ############################################################################ +} + +AddressBook::ErrorCode KabAPI::getEntryByName(const QString&, + list<AddressBook::Entry>&, const int) +{ + // ############################################################################ + return AddressBook::NotImplemented; + // ############################################################################ +} + +AddressBook::ErrorCode KabAPI::getEntryByName(const AddressBook::Entry&, + list<AddressBook::Entry>&, const int) +{ + // ############################################################################ + return AddressBook::NotImplemented; + // ############################################################################ +} + +AddressBook::ErrorCode KabAPI::getEntries(list<AddressBook::Entry>& entries) +{ + kdDebug(KAB_KDEBUG_AREA) << "KabAPI::getEntries: called." << endl; + // ############################################################################ + if(book->noOfEntries()==0) + { // ----- database is valid, but empty: + kdDebug(KAB_KDEBUG_AREA) << "KabAPI::getEntries: no entries." << endl; + return AddressBook::NoEntry; + } + if(book->getEntries(entries)!=AddressBook::NoError) + { + kdDebug(KAB_KDEBUG_AREA) << "KabAPI::getEntries: intern error." << endl; + return AddressBook::InternError; + } else { + kdDebug(KAB_KDEBUG_AREA) << "KabAPI::getEntries: done." << endl; + return AddressBook::NoError; + } + // ############################################################################ +} + +AddressBook* KabAPI::addressbook() +{ + // ############################################################################ + return book; + // ############################################################################ +} + +AddressBook::ErrorCode KabAPI::save(bool force) +{ + // ############################################################################ + if(book->save("", force)!=AddressBook::NoError) + { + return AddressBook::PermDenied; + } else { + return AddressBook::NoError; + } + // ############################################################################ +} + +void KabAPI::entrySelected(int index) +{ + kdDebug(KAB_KDEBUG_AREA) << "KabAPI::entrySelected: entry " << index + <<" selected." << endl; + selection=index; +} + +void KabAPI::setStatusSlot(const QString& text) +{ + emit(setStatus(text)); +} diff --git a/kab/kabapi.h b/kab/kabapi.h new file mode 100644 index 000000000..2c790ab1a --- /dev/null +++ b/kab/kabapi.h @@ -0,0 +1,220 @@ +/* -*- C++ -*- + Dialog widget using the addressbook, + provided for inclusion in other programs. + + the KDE addressbook + + $ Author: Mirko Boehm $ + $ Copyright: (C) 1996-2001, Mirko Boehm $ + $ Contact: mirko@kde.org + http://www.kde.org $ + $ License: GPL with the following explicit clarification: + This code may be linked against any version of the Qt toolkit + from Troll Tech, Norway. $ + + $Id$ +*/ +#ifndef KABAPI_H +#define KABAPI_H + +#include "addressbook.h" +#include <kdialogbase.h> + +class QPushButton; +class QFrame; +class KListBox; + +/** + * The class KabAPI provides a public interface to access the + * users address database created using kab. The complete + * functionality regarding database operations provided by kab is + * available using an object of this class. + * + * The class is derived from the class KDialogBase, thus objects + * can be used as a KDE dialog where the user may select a person + * out of the entries in his personal database. + * The following code may be used to let the user select an address: + * \code + * KabAPI kabapi(this); + * if(dialog.init()!=KabAPI::NoError) + * { + * ... error handling + * } + * AddressBook::Entry entry; + * if(kabapi.exec()) + * { + * if(!kabapi.getEntry(entry)) + * { + * // ... the database is empty + * } else { + * // ... use the entry + * } + * } + * ... + * \endcode + * Some methods declared here return keys of entries. The keys are of the + * datatype KabKey. Every key + * is (of course) unique and identifying. If you store it, you can access + * the entry it represents with it. Be careful that the entry may have been + * deleted by another program instance meanwhile! + * <tt>Please be careful to test for the return code NotImplemented as + * long the kab API is not completely finished.</tt> + * @short The class KabAPI defines the API to access user address databases. + * @author Mirko Boehm <mirko@kde.org> + * @version $Id$ + * @see AddressBook #KDialogBase + */ + +class KabAPI : public KDialogBase +{ + // ############################################################################ + Q_OBJECT + // ---------------------------------------------------------------------------- +public: + /** + * The constructor creates a KabAPI object, but it does not load the + * database itselfes, as you could not query if this could be done + * without failures. Thus you have to call init before you can + * use the database. + * @param parent The QWidget pointer to the parent widget. + * @param name The widgets name (used for debugging) + */ + KabAPI(QWidget* parent=0, const char* name=0); + /** + * You must call init before accessing the database. init opens the + * database file (usually $HOME/.kde/share/apps/kab/addressbook.database) + * and loads its contents. + * @return NoError if all succeeded or a valid ErrorCode. + * @see AddressBook::ErrorCode + */ + AddressBook::ErrorCode init(); + /** + * Get the addressbook object of this API. This is probably the most powerful + * method in the KabAPI since it allows you to access the database backend + * itselfes. + * If the API has not been initialized (using #init) before, zero is returned. + * @see init + */ + AddressBook* addressbook(); + /** + * Save the database to the file. + * This method is used to force the database to save its contents. + * If force is true, the method will try to get writing permissions to + * the file if the database is opened readonly. After finishing saving, + * the r/o state is reset. This allows easier file sharing, since by default, + * all files are opened readonly aand closed after all read accesses. + */ + AddressBook::ErrorCode save(bool force=false); + /** + * The method getEntry returns the selected entry. + * @return NoError if all succeeded or a valid ErrorCode. + * @see AddressBook::ErrorCode + * @param entry Reference to an AddressBook::Entry -object. + * @param key Reference to a KabKey where the key of the entry is stored. + */ + AddressBook::ErrorCode getEntry(AddressBook::Entry& entry, KabKey& key); + /** + * Using the method getEntries, the caller will get a copy of all entries + * in the database. This might seem unneeded, but the address database can be + * used by multiple instances of the kab API at the same time, so that, + * if the programmer wants, for example, print a letter header for all + * persons, the database might change during the operation. That is why + * she can retrieve the whole database in one operation. + * It is required that the referenced list is empty. + * Note that the function returns NoEntry if the database is empty. + * @see AddressBook::ErrorCode + * @short Retrieves all entries out of the database. + * @param entries Reference to a list of entries. + * @return NoError or a valid error code. + */ + AddressBook::ErrorCode getEntries(std::list<AddressBook::Entry>& entries); + /** + * The method requires that the database is not opened readonly. + * @short Adds an entry to the users default database. + * @return NoError if all succeeded or a valid ErrorCode, especially PermDenied. + * @param entry Reference to the entry to be added. + * @param key Reference to a KabKey where the key of the new entry is stored. + * @param update Whether to update the mirror map or not. + * Note: The functionality to edit an entry herein has been removed. + */ + AddressBook::ErrorCode add(const AddressBook::Entry& entry, KabKey& key, + bool update=true); + /** + * If the preferences of kab say to query before deleting, the user has + * to click "yes" on a message box that appeares. + * If called for a read only database, the method will return + * PermDenied. + * @short Deletes an entry in the database by its key. + * @param key The key of the entry to delete. + * @return NoEntry if there is no entry with this key or another ErrorCode. + */ + AddressBook::ErrorCode remove(const KabKey& key); + /** + * Use getEntryByName to find entries that look like the name given. + * The name might be incomplete or diffuse. + * @short This method delivers the closest matches to the given name. + * @param name The name, containing "." for abbreviations. + * @param entries Reference to a list of entries where matches are stored. + * @param max Maximum number of returned entries. + * @return NoError if an entry is found or NoEntry. + */ + AddressBook::ErrorCode getEntryByName(const QString& name, + std::list<AddressBook::Entry>& entries, + const int max=5); + /** + * This method also searches for close matches to the pattern, + * but it compares the whole entry given. This way you can search for, + * for example, nearly similar email addresses. Empty parts of the + * entry are not considered as criteria. + * @short This method delivers the closest matches to the given entry. + * @param pattern The pattern, containing "." for abbreviations. + * @param entries Reference to a list of entries where matches are stored. + * @param max Maximum number of returned entries. + * @return NoError if an entry is found or NoEntry. + */ + AddressBook::ErrorCode getEntryByName(const AddressBook::Entry& pattern, + std::list<AddressBook::Entry>& entries, + const int max=5); + /** + * Execute this dialog. This overloads QDialog::exec to fill the list box + * before showing. + */ + int exec(); + // ---------------------------------------------------------------------------- +protected: + /** + * This is our backend to the users address database. + */ + AddressBook* book; + /** + * This displays the overview over the addresses in the dialog. + */ + KListBox* listbox; + /** + * The index of the selected entry. This value is only valid after the + * KabAPI dialog has been executed and accepted by the user. + */ + int selection; +protected slots: + /** + * Capture selections in the dialog (listbox). + */ + void entrySelected(int); + /** + * Capture status messages from book. + */ + void setStatusSlot(const QString&); + void slotDoubleClicked ( QListBoxItem * ); + signals: + /** + * Send status messages. + */ + void setStatus(const QString&); + // ############################################################################ +private: + class KAbAPIPrivate; + KAbAPIPrivate *d; +}; + +#endif // KABAPI_H + diff --git a/kab/qconfigDB.cc b/kab/qconfigDB.cc new file mode 100644 index 000000000..d6a92261f --- /dev/null +++ b/kab/qconfigDB.cc @@ -0,0 +1,2547 @@ +/* the Configuration Database library, Version II + + the KDE addressbook + + $ Author: Mirko Boehm $ + $ Copyright: (C) 1996-2001, Mirko Boehm $ + $ Contact: mirko@kde.org + http://www.kde.org $ + $ License: GPL with the following explicit clarification: + This code may be linked against any version of the Qt toolkit + from Troll Tech, Norway. $ + + $Id$ +*/ + +#include "qconfigDB.h" +// #include "debug.h" + +extern "C" { +#include <sys/types.h> +#include <sys/stat.h> +#include <signal.h> +#include <unistd.h> +#include <ctype.h> +} + +// #include <qstring.h> +#include <qtextstream.h> +#include <qfile.h> +#include <qtimer.h> +#include <qdatetime.h> +#include <qfileinfo.h> + +#include "qconfigDB.moc" +#include <kdebug.h> + +#ifdef KAB_KDEBUG_AREA +#undef KAB_KDEBUG_AREA +#endif + +#define KAB_KDEBUG_AREA 800 + +static bool isComment(QCString line) +{ + // ############################################################################ + line=line.stripWhiteSpace(); + if(line.isEmpty()) + { + return false; // line is empty but not a comment + } else { + return(line[0]=='#'); + } + // ############################################################################ +} + +static void tokenize(list<QCString>& res, const QCString& text, char tr, bool strict=false) +{ + register bool GUARD; GUARD=false; + // ############################################################################ + kdDebug(GUARD, KAB_KDEBUG_AREA) << "tokenize: called." << endl; + int eins=0, zwei=0; + QCString teil; + // ----- + kdDebug(GUARD, KAB_KDEBUG_AREA) << "tokenize: partening -->%" << text.data() << "<--." << endl; + res.erase(res.begin(), res.end()); + // ----- + if(text.isEmpty()) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "tokenize: text is an empty string, done." << endl; + return; + } + while(zwei!=-1) + { + teil=""; + zwei=text.find(tr, eins); + if(zwei!=-1) + { + teil=text.mid(eins, zwei-eins); + res.push_back(teil); + } else { // last element + if(!strict) // nur wenn dazwischen Zeichen sind + { + teil=text.mid(eins, text.length()-eins); + res.push_back(teil); + } + } + eins=zwei+1; + // if((unsigned)eins>=text.length()) break; + } + kdDebug(GUARD, KAB_KDEBUG_AREA) << "tokenize: partened in " + << res.size() << " parts.\n"; + // ----- + kdDebug(GUARD, KAB_KDEBUG_AREA) << "tokenize: done." << endl; + // ############################################################################ +} + +// QCString AuthorEmailAddress; // assign your email address to this string + +static QCString ReadLineFromStream(QTextStream& stream) +{ + register bool GUARD; GUARD=false; + // ############################################################################ + kdDebug(GUARD, KAB_KDEBUG_AREA) << "ReadLineFromStream:: reading line." << endl; + QCString line; + // ----- + while(!stream.eof()) + { + line=stream.readLine().ascii(); + if(!line.isEmpty()) + { + if(isComment(line)) + { + line=""; + continue; + } + } + break; + } + // ----- + kdDebug(GUARD, KAB_KDEBUG_AREA) << "ReadLineFromStream:: line \"" << line.data() << "\" read.\n"; + return line; + // ############################################################################ +} + +// class implementations: + +list<QString> QConfigDB::LockFiles; // the lockfiles created by this session + +KeyValueMap::KeyValueMap() + : data(new StringStringMap) +{ + // ########################################################################### + // ########################################################################### +} + +KeyValueMap::KeyValueMap(const KeyValueMap& orig) + : data(new StringStringMap(*orig.data)) +{ + // ########################################################################### + // ########################################################################### +} + +KeyValueMap::~KeyValueMap() +{ + // ########################################################################### + delete data; + // ########################################################################### +} + +bool KeyValueMap::invariant() +{ + return true; +} + +StringStringMap::iterator KeyValueMap::begin() +{ + return data->begin(); +} + +StringStringMap::iterator KeyValueMap::end() +{ + return data->end(); +} + +unsigned int +KeyValueMap::size() const +{ + // ########################################################################### + return data->size(); + // ########################################################################### +} + +void +KeyValueMap::clear() +{ + register bool GUARD; GUARD=false; + // ########################################################################### + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "KeyValueMap::clear: erasing map contents ... " << endl; + // ----- + if(!data->empty()) // erase fails on empty containers! + { + data->erase(data->begin(), data->end()); + } + // ----- + kdDebug(GUARD, KAB_KDEBUG_AREA) << "done." << endl; + // ########################################################################### +} + +bool +KeyValueMap::fill(const QString& filename, bool force, bool relax) +{ + register bool GUARD; GUARD=false; + // ########################################################################### + QFile file(filename); + QCString line; + // ----- + if(file.open(IO_ReadOnly)) + { + QTextStream stream(&file); + // We read/write utf8 strings, so we don't want that QTextStream uses local8bit + // Latin1 means : no conversion, when giving char*s to a QTextStream. (DF) + stream.setEncoding(QTextStream::Latin1); + // ----- + while(!stream.eof()) + { + line=stream.readLine().ascii(); + if(!line.isEmpty() /* && !stream.eof() */ && !isComment(line)) + { + if(!insertLine(line, force, relax, false)) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "KeyValueMap::fill: could not insert line " + << line << ".\n"; // ignore this case further + } + } + } + file.close(); + // ----- + return true; + } else { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "KeyValueMap::fill: cannot open file " << + filename << endl; + return false; + } + // ########################################################################### +} + +bool +KeyValueMap::save(const QString& filename, bool force) +{ + register bool GUARD; GUARD=false; + // ########################################################################### + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "KeyValueMap::save: saving data to -->" << + filename << "<--.\n"; + StringStringMap::iterator pos; + QFile file(filename); + // ----- open file, regarding existence: + if(!force) + { + if(file.exists()) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "KeyValueMap::save: file exists but may not." << endl; + return false; + } + } + if(file.open(IO_WriteOnly)) + { + QTextStream stream(&file); + stream.setEncoding(QTextStream::Latin1); // no conversion + stream << "# saved by KeyValueMap object ($Revision$)" << endl; + for(pos=data->begin(); pos!=data->end(); ++pos) + { // values do not get coded here + stream << (*pos).first << '=' << (*pos).second << endl; + } + file.close(); + } else { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "KeyValueMap::save: could not open file -->%s<-- for saving." << + filename.utf8() << endl; + return false; + } + // ########################################################################### + return true; +} + +bool +KeyValueMap::save(QTextStream& file, int count) +{ + register bool GUARD; GUARD=false; + // ########################################################################### + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "KeyValueMap::save: saving data to given output stream." << endl; + StringStringMap::iterator pos; + bool ret=true; + char* prefix=new char[count+1]; + memset(prefix, ' ', count); + prefix[count]=0; + // ----- + for(pos=data->begin(); pos!=data->end(); ++pos) + { + file << prefix << (*pos).first << '=' << (*pos).second << endl; + } + delete [] prefix; + // ----- + return ret; + // ########################################################################### +} + + +bool +KeyValueMap::erase(const QCString& key) +{ + // ########################################################################### + bool rc=(data->erase(key)>0); + return rc; + // ########################################################################### +} + + +bool +KeyValueMap::empty() +{ + // ########################################################################### + return data->empty(); + // ########################################################################### +} + +bool +KeyValueMap::parseComplexString +(const QCString& orig, + int index, // first char to parse + QCString& result, // string without leading and trailing ".." + int& noOfChars) // no of chars that represented the + const // complex string in the original +{ + register bool GUARD; GUARD=false; + // ########################################################################### + int first; + QCString temp(2*orig.length()); + QCString mod; + int count=1; + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "KeyValueMap::parseComplexString: parsing the string -->" + << orig << "<--.\n"; + // ----- + if(orig.isEmpty()) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "KeyValueMap::parseComplexString: string is empty.\n" + " " + "This is no valid complex string." << endl; + return false; + } + // ----- prepare the string: + temp=orig.mid(index, orig.length()-index); // remove everything before index + mod=temp.stripWhiteSpace(); // remove leading and trailing white spaces + // ----- test some conditions: + if(mod.length()<2) + { + kdDebug() << "KeyValueMap::parseComplexString: no pair of brackets " << endl; + return false; + } + if(mod[0]!='"') + { + kdDebug() << "KeyValueMap::parseComplexString: no opening bracket." << endl; + return false; + } + // ----- now parse it: + first=1; // first character after opening bracket + temp=""; + for(;;) + { + if(mod[first]=='\\') + { // handle special characters + ++first; + kdDebug(GUARD, KAB_KDEBUG_AREA).form("KeyValueMap::parseComplexString: found " + "a special character \"%c\".", mod[first]) << endl; + if((unsigned)first==mod.length()) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "KeyValueMap::parseComplexString: " + "string lacks the closing \".\n " + " This is no valid " + "complex string." << endl; + return false; + } + switch(mod[first]) + { + case 't': temp+='\t'; break; + case 'n': temp+='\n'; break; + case '"': temp+='"'; break; + case 'e': temp+="\\e"; break; + case '\\': temp+='\\'; break; + default: + // WORK_TO_DO: implement octal coding here! + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::parseComplexString: " + "invalid control character.\n " + " This is no valid complex string." << endl; + return false; + } + count+=2; // this took 2 characters + ++first; + } else { // it is a character + ++count; + if(mod[first]=='"') // end of coded string? + { + break; + } + temp+=mod[first]; + ++first; + } + if((unsigned)first>=mod.length()) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::parseComplexString: " + "string lacks the closing \".\n " + " This is no valid complex string.\n"; + return false; + } + } + // ----- + kdDebug(GUARD, KAB_KDEBUG_AREA).form( + "KeyValueMap::parseComplexString: finished parsing, no errors, " + "%i characters, %i in string.", count, temp.length()) << endl; + noOfChars=count; + result=temp; + // ########################################################################### + return true; +} + +QCString +KeyValueMap::makeComplexString(const QCString& orig) +{ + register bool GUARD; GUARD=false; + // ########################################################################### + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "KeyValueMap::makeComplexString: coding the string\n -->" + << orig << + "<--\n into a complex string.\n"; + QCString temp(2*orig.length()); + unsigned int count; + // ----- + temp+='"'; // opening bracket + for(count=0; count<orig.length(); count++) + { + switch(orig[count]) + { // catch all special characters: + case '"': + kdDebug(GUARD, KAB_KDEBUG_AREA).form("KeyValueMap::makeComplexString: " + "found the special char \"%c\".", orig[count]) << endl; + temp+='\\'; + temp+='"'; + break; + case '\n': + kdDebug(GUARD, KAB_KDEBUG_AREA).form("KeyValueMap::makeComplexString: " + "found the special char \"%c\".", orig[count]) << endl; + temp+='\\'; + temp+='n'; + break; + case '\t': + kdDebug(GUARD, KAB_KDEBUG_AREA).form("KeyValueMap::makeComplexString: " + "found the special char \"%c\".", orig[count]) << endl; + temp+='\\'; + temp+='t'; + break; + case '\\': + kdDebug(GUARD, KAB_KDEBUG_AREA).form("KeyValueMap::makeComplexString: " + "found the special char \"%c\".", orig[count]) << endl; + temp+='\\'; + temp+='\\'; + break; + default: temp+=orig[count]; + } + } + temp+='"'; // closing bracket + // ----- + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "KeyValueMap::makeComplexString: result is\n -->" + <<temp<<"<--.\n"; + return temp; + // ########################################################################### +} + +bool +KeyValueMap::getRaw(const QCString& key, QCString& value) const +{ + register bool GUARD; GUARD=false; + // ########################################################################### + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "KeyValueMap::getRaw: trying to get raw value for key \"" << key << "\" ...\n"; + StringStringMap::iterator pos=data->find(key); + // ----- + if(pos==data->end()) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "not in KeyValueMap." << endl; + return false; + } else { + value=(*pos).second; + kdDebug(GUARD, KAB_KDEBUG_AREA) << "in KeyValueMap, value is " + << value << endl; + return true; + } + // ########################################################################### +} + +bool +KeyValueMap::insertRaw(const QCString& key, const QCString& value, bool force) +{ + register bool GUARD; GUARD=false; + // ########################################################################### + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "KeyValueMap::insertRaw: inserting uncoded value " + << value << + " for key " << key << endl; + int n=0; + // ----- + if(key.isEmpty()) // empty KEYS are errors: + { + kdDebug() << "KeyValueMap::insertRaw: tried to insert empty key." << endl; + return false; + } + if(force) // entry will be replaced + { + n=data->erase(key); + } + if(data->insert(StringStringMap::value_type(key, value)).second) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::insertRaw: success" + << (n==0 ? "" : " (forced)") << endl; + return true; + } else { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::insertRaw: failed, " + "key already in KeyValueMap." << endl; + return false; + } + // ########################################################################### +} + + +// ----------------------------------------------------------------------------- +// HUGE SEPARATOR BETWEEN INTERNAL LOGIC AND EXTENDABLE PAIRS OF GET- AND INSERT +// -METHODS. +// EXTENDABLE MEANS: OTHER DATATYPES CAN BE ADDED HERE. +// ----------------------------------------------------------------------------- + +/* The following functions are the pairs of insert-get-methods for different + * data types. See keyvaluemap.h for the declarations. */ + +// ascii strings: + +bool +KeyValueMap::insert(const QCString& key, const QCString& value, bool force) +{ + register bool GUARD; GUARD=false; + // ########################################################################### + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "KeyValueMap::insert: inserting value\n -->" + << value << + "<-- \"" + " for key\n -->" + << key << + "<--.\n"; + return insertRaw(key, makeComplexString(value), force); + // ########################################################################### +} + +/* Attention: + * This is another insert function that partens lines like "key=value"! + * It is used for reading files and command line parameters easily. + */ + +bool +KeyValueMap::insertLine(QCString line, bool force, bool relax, bool encode) +{ + register bool GUARD; GUARD=false; + // ########################################################################### + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "KeyValueMap::insertLine: inserting line -->"<<line<<"<--.\n"; + int index; + QCString key; + QCString value; + // ----- is the line empty or does it contain only whitespaces? + uint len = line.length(); + for(index=0; isspace(line[index]) && (unsigned)index<len; ++index); + if(line.isEmpty() || (unsigned)index==len) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "KeyValueMap::insertLine: line is empty." << endl; + return false; + } + // ----- + index=line.find('='); + if(index==-1) // not found + { + kdDebug() << "KeyValueMap::insertLine: no \"=\" found in \""<<line<<"\".\n"; + return false; + } + // ----- + key=line.mid(0, index); // copy from start to '=' + value=line.mid(index+1, line.length()-1-index); // copy the rest + // ----- only alphanumerical characters are allowed in the keys: + for(index=key.length()-1; index>-1; /* nothing */) + { + if(!(isalnum(key[index]) || ispunct(key[index]))) + { + key=key.remove(index, 1); // WORK_TO_DO: optimize this (very slow)! + } + --index; + } + // ----- now insert it if key is still valid: + if(!key.isEmpty() && (relax==true ? 1 : !value.isEmpty() ) ) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::insertLine: done." << endl; + if(encode) + { // the usual way: + return insert(key, value, force); + } else { // while loading from a already coded file: + return insertRaw(key, value, force); + } + } else { + kdDebug() << "KeyValueMap::insertLine: key " << (relax ? "" : "or value ") << " is empty." << endl; + return false; + } + // ########################################################################### +} + +bool +KeyValueMap::get(const QCString& key, QCString& value) const +{ + register bool GUARD; GUARD=false; + // ########################################################################### + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::get[string]: " + "trying to get value for key \"" << key << "\" ... " << endl; + QCString raw; + QCString temp; + // ----- + if(!getRaw(key, raw)) + { + return false; // key does not exist + } else { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::get[string]: checking " + "wether this is a complex string." << endl; + { + int count; + if(parseComplexString(raw, 0, temp, count)) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "KeyValueMap::get[string]: complex string found." << endl; + value=temp; + } else { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "KeyValueMap::get[string]: this is no complex string." << endl; + // disabled this strong check: + // CHECK(false); // kill debug version + return false; + } + } + // ^^^^^^ + return true; + } + // ########################################################################### +} + +// (^^^ ascii strings) +// UNICODE strings: + +bool +KeyValueMap::insert(const QCString& key, const QString& value, bool force) +{ + register bool GUARD; GUARD=false; + // ########################################################################### + QCString v; + // ----- + v=value.utf8(); + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::insert[QString]: trying to " + "insert \"" << (!value.isNull() ? "true" : "false") + << "\" for key\n -->" + << v + << "<--.\n"; + return insert(key, v, force); + // ########################################################################### +} + +bool +KeyValueMap::get(const QCString& key, QString& value) const +{ + register bool GUARD; GUARD=false; + // ########################################################################### + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::get[QString]: trying to get " + "a QString value for key " << key << endl; + QCString v; + // ----- get string representation: + if(!get(key, v)) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::get[QString]: key " + << key << " not in KeyValueMap.\n"; + return false; + } + // ----- find its state: + value=QString::fromUtf8(v); // is there a better way? + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::get[QString]: success, value" + " (in UTF8) is " << v << endl; + return true; + // ########################################################################### +} + +// (^^^ UNICODE strings) +// bool: + +bool +KeyValueMap::insert(const QCString& key, const bool& value, bool force) +{ + register bool GUARD; GUARD=false; + // ########################################################################### + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::insert[bool]: trying to " + "insert \"" + << (value==true ? "true" : "false") + <<"\" for key\n -->" + << key << "<--.\n"; + return insert(key, value==true ? "true" : "false", force); + // ########################################################################### +} + + +bool +KeyValueMap::get(const QCString& key, bool& value) const +{ + register bool GUARD; GUARD=false; + // ########################################################################### + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::get[bool]: trying to get " + "BOOL value for key " << key << endl; + QCString v; + // ----- get string representation: + if(!get(key, v)) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::get[bool]: key " + << key << " not in KeyValueMap."; + return false; + } + // ----- find its state: + v=v.stripWhiteSpace(); + if(v=="true") + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::get[bool]: success, " + "value is true." << endl; + value=true; + return true; + } + if(v=="false") + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::get[bool]: success, " + "value is false." << endl; + value=false; + return true; + } + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "KeyValueMap::get[bool]: failure, unknown value." << endl; + // ----- + return false; + // ########################################################################### +} + +// (^^^ bool) +// long: + +bool +KeyValueMap::insert(const QCString& key, const long& value, bool force) +{ + register bool GUARD; GUARD=false; + // ########################################################################### + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::insert[int]: trying to " + "insert value \""<<value << "\" for key\n -->"<<key<<"<--.\n"; + QCString temp; + // ----- + temp.setNum(value); + return insert(key, temp, force); + // ########################################################################### +} + +bool +KeyValueMap::get(const QCString& key, long& value) const +{ + register bool GUARD; GUARD=false; + // ########################################################################### + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::get[int]: trying to get " + "INTEGER value for key " << key << endl; + QCString v; + bool ok; + long temp; + // ----- + if(!get(key, v)) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::get[int]: key " + << key <<" not in KeyValueMap.\n"; + return false; + } + // ----- + temp=v.toLong(&ok); + if(ok) + { + value=temp; + return true; + } else { + return false; + } + // ########################################################################### +} + +// (^^^ long) +// long int lists: + +bool +KeyValueMap::insert(const QCString& key, const list<long>& values, bool force) +{ + register bool GUARD; GUARD=false; + // ########################################################################### + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::insert[long int list]: " + "trying to insert long int list into map." << endl; + QCString temp; + QCString value; + list<long>::const_iterator pos; + // ----- + for(pos=values.begin(); pos!=values.end(); ++pos) + { + temp.setNum(*pos); + value=value+temp+", "; + } + if(!value.isEmpty()) + { // remove last comma and space: + value.remove(value.length()-2, 2); + } + // ----- + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::insert[long int list]: " + "constructed string value is " << value << endl; + return insert(key, value, force); + // ########################################################################### +} + +bool +KeyValueMap::get(const QCString& key, list<long>& values) const +{ + register bool GUARD; GUARD=false; + // ########################################################################### + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::get[long int list]: trying " + "to decode int list for key " << key << endl; + kdDebug(!values.empty(), KAB_KDEBUG_AREA) << "KeyValueMap::get[long int list]" + ": attention - list should be empty but is not.\n"; + QCString value; + list<QCString> tokens; + list<QCString>::iterator pos; + long temp; + bool ok; + // ----- + if(!get(key, value)) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "KeyValueMap::get[long int list]: no such key." << endl; + return false; + } + tokenize(tokens, value, ','); + if(tokens.empty()) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "KeyValueMap::get[long int list]: no tokens." << endl; + return false; + } + // ----- + for(pos=tokens.begin(); pos!=tokens.end(); ++pos) + { + temp=(*pos).toLong(&ok); + if(ok) + { + values.push_back(temp); + } else { + kdDebug() << "KeyValueMap::get[long int list]: conversion error for " << endl; + } + } + // ----- + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::get[long int list]: done." << endl; + // ########################################################################### + return true; +} + +// (^^^ long int lists) +// int lists: + +bool +KeyValueMap::insert(const QCString& key, const list<int>& values, bool force) +{ + register bool GUARD; GUARD=false; + // ########################################################################### + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::insert[int list]: trying to " + "insert int list into map." << endl; + QCString temp; + QCString value; + list<int>::const_iterator pos; + // ----- + for(pos=values.begin(); pos!=values.end(); ++pos) + { + temp.setNum(*pos); + value=value+temp+", "; + } + if(!value.isEmpty()) + { // remove last comma and space: + value.remove(value.length()-2, 2); + } + // ----- + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::insert[int list]: " + "constructed string value is " << value << endl; + return insert(key, value, force); + // ########################################################################### +} + +bool +KeyValueMap::get(const QCString& key, list<int>& values) const +{ + register bool GUARD; GUARD=false; + // ########################################################################### + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::get[int list]: trying to " + "decode int list for key " << key << endl; + kdDebug(!values.empty(), KAB_KDEBUG_AREA) << "KeyValueMap::get[int list]: " + "attention - list should be empty but is not.\n"; + QCString value; + list<QCString> tokens; + list<QCString>::iterator pos; + int temp; + bool ok; + // ----- + if(!get(key, value)) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "KeyValueMap::get[int list]: no such key." << endl; + return false; + } + tokenize(tokens, value, ','); + if(tokens.empty()) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "KeyValueMap::get[int list]: no tokens." << endl; + return false; + } + // ----- + for(pos=tokens.begin(); pos!=tokens.end(); ++pos) + { + temp=(*pos).toInt(&ok); + if(ok) + { + values.push_back(temp); + } else { + kdDebug() << "KeyValueMap::get[int list]: conversion error for " << *pos << endl; + } + } + // ----- + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::get[long int list]: done." << endl; + // ########################################################################### + return true; +} + +// (^^^ int lists) +// doubles: + +bool +KeyValueMap::insert(const QCString& key, const double& value, bool force) +{ + register bool GUARD; GUARD=false; + // ########################################################################### + kdDebug(GUARD, KAB_KDEBUG_AREA).form("KeyValueMap::insert[double]: trying to " + "insert value \"%f\" for key\n -->", value) << key << "<--.\n"; + QCString temp; + // ----- + temp.setNum(value); + return insert(key, temp, force); + // ########################################################################### +} + +bool +KeyValueMap::get(const QCString& key, double& value) const +{ + register bool GUARD; GUARD=false; + // ########################################################################### + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::get[double]: trying to get " + "FLOAT value for key " << key << endl; + QCString v; + bool ok; + double temp; + // ----- + if(!get(key, v)) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::get[int]: key " + <<key<<" not in " + "KeyValueMap." << endl; + return false; + } + // ----- + temp=v.toDouble(&ok); + if(ok) + { + value=temp; + return true; + } else { + return false; + } + // ########################################################################### +} + +// (^^^ doubles) +// lists of strings: + +bool +KeyValueMap::get(const QCString& key, list<QCString>& values) const +{ + register bool GUARD; GUARD=false; + kdDebug(!values.empty(), KAB_KDEBUG_AREA) << "KeyValueMap::get[string list]: " + "attention!\n \"values\" list reference is not " + "empty!" << endl; + // ########################################################################### + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::get[string list]: trying to " + "decode string list for key " << key << endl; + QCString raw, part, value; + int first=1, second=1, i; + // ----- get the string value as a whole: + if(!getRaw(key, raw)) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::get[list<string>]: key " + << key << " not in KeyValueMap." << endl; + return false; + } + // ----- + for(;;) + { // ----- parten the string down into a list, find special characters: + second=first; + for(;;) + { + second=raw.find('\\', second); + // ----- this may never be the last and also not the second last + // character in a complex string: + if(second!=-1) + { // ----- check for string end: + // we use "\e" as token for the string-delimiter + if(raw[second+1]=='e' // the right character + && raw[second-1]!='\\') // not escaped + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::get" + "[list<string>]: found string end at pos " << + second << endl; + break; + } else { + ++second; + } + } else { + break; + } + } + if(second!=-1) + { + // ----- now second points to the end of the substring: + part="\""+raw.mid(first, second-first)+"\""; + // ----- insert decoded value into the list: + if(parseComplexString(part, 0, value, i)) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::get" + "[list<string>]: found item " << value << endl; + values.push_back(value); + } else { + kdDebug() << "KeyValueMap::get[list<string>]: parse error." << endl; + return false; + } + if((unsigned)second<raw.length()-3) + { // ----- there may be another string + first=second+2; + } else { // ----- we are completely finished + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "KeyValueMap::get[list<string>]: list end found." << endl; + break; + } + } else { // ----- finished: + break; + } + } + // ----- + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::get[list<string>]: done." << endl; + return true; + // ########################################################################### +} + +bool +KeyValueMap::insert(const QCString& key, const list<QCString>& values, bool force) +{ + register bool GUARD; GUARD=false; + // ########################################################################### + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::insert[string list]: " + "coding string list." << endl; + QCString value="\""; + QCString temp; + list<QCString>::const_iterator pos; + // ----- create coded string list: + for(pos=values.begin(); + pos!=values.end(); + pos++) + { // create strings like "abc\efgh\eijk": + temp=makeComplexString(*pos); + temp.remove(0, 1); // remove the leading "\"" + temp.remove(temp.length()-1, 1); // the trailing "\"" + value+=temp; + value+="\\e"; + } + value+="\""; // finish the string + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::insert[string list]: result " + "of coding is " << value << endl; + // ----- insert it without coding: + return insertRaw(key, value, force); + // ########################################################################### +} + +// (^^^ lists of strings) +// QStrList-s: + +bool +KeyValueMap::get(const QCString& key, QStrList& values) const +{ + register bool GUARD; GUARD=false; + kdDebug(!values.isEmpty(), KAB_KDEBUG_AREA) << "KeyValueMap::get[QStrList]: " + "attention!\n \"values\" list reference is not " + "empty!" << endl; + // ########################################################################### + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::get[QStrList]: trying to " + "decode string list for key " << key << endl; + QCString raw, part, value; + int first=1, second=1, i; + // ----- get the string value as a whole: + if(!getRaw(key, raw)) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::get[QStrList]: key " + << key <<" not in KeyValueMap." << endl; + return false; + } + // ----- + for(;;) + { // ----- parten the string down into a list, find special characters: + second=first; + for(;;) + { + second=raw.find('\\', second); + // ----- this may never be the last and also not the second last + // character in a complex string: + if(second!=-1) + { // ----- check for string end: + // we use "\e" as token for the string-delimiter + if(raw[second+1]=='e' // the right character + && raw[second-1]!='\\') // not escaped + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::get[QStrList]:" + " found string end at pos %i." << second << endl; + break; + } else { + ++second; + } + } else { + break; + } + } + if(second!=-1) + { + // ----- now second points to the end of the substring: + part="\""+raw.mid(first, second-first)+"\""; + // ----- insert decoded value into the list: + if(parseComplexString(part, 0, value, i)) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::get[QStrList]: " + "found item " << value << endl; + values.append(value); + } else { + kdDebug() << "KeyValueMap::get[QStrList]: parse error." << endl; + return false; + } + if((unsigned)second<raw.length()-3) + { // ----- there may be another string + first=second+2; + } else { // ----- we are completely finished + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::get[QStrList]: " + "list end found." << endl; + break; + } + } else { // ----- finished: + break; + } + } + // ----- + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::get[QStrList]: done." << endl; + return true; + // ########################################################################### +} + +bool +KeyValueMap::insert(const QCString& key, const QStrList& values, bool force) +{ + register bool GUARD; GUARD=false; + // ########################################################################### + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "KeyValueMap::insert[QStrList]: coding string list." << endl; + QCString value="\""; + QCString temp; + unsigned int count; + // ----- create coded string list: + for(count=0; count<values.count(); ++count) + { // create strings like "abc\efgh\eijk": + temp=makeComplexString(((QStrList)values).at(count)); + temp.remove(0, 1); // remove the leading "\"" + temp.remove(temp.length()-1, 1); // the trailing "\"" + value+=temp; + value+="\\e"; + } + value+="\""; // finish the string + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "KeyValueMap::insert[QStrList]: result of coding is %s." << + value << endl; + // ----- insert it without coding: + return insertRaw(key, value, force); + // ########################################################################### +} + +// (^^^ QStrList-s) +// QStringList-s: + +bool +KeyValueMap::get(const QCString& key, QStringList& values) const +{ + register bool GUARD; GUARD=false; + kdDebug(!values.isEmpty(), KAB_KDEBUG_AREA) << "KeyValueMap::get" + "[QStringList]: attention!\n \"values\" list reference" + " is not empty!" << endl; + // ########################################################################### + /* The values are stored in a utf8-coded set of QCStrings. + This list is retrieved and converted back to Unicode strings. */ + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::get[QStringList]: trying to " + "decode QStringList for key " << key << endl; + QStrList temp; + unsigned int count; + // ----- get the plain C strings: + if(!get(key, temp)) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::get[QStringList]: key " + << key << + " not in KeyValueMap." << endl; + return false; + } + // ----- do the conversion: + for(count=0; count<temp.count(); ++count) + { + values.append(QString::fromUtf8(temp.at(count))); + } + // ----- + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::get[QStringList]: done." << endl; + return true; + // ########################################################################### +} + +bool +KeyValueMap::insert(const QCString& key, const QStringList& values, bool force) +{ + register bool GUARD; GUARD=false; + // ########################################################################### + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "KeyValueMap::insert[QStringList]: coding QStringList." << endl; + // The method simply creates a list of utf8-coded strings and inserts it. + QStrList utf8strings; + unsigned int count; + // ----- create QCString list: + for(count=0; count<values.count(); ++count) + { + utf8strings.append((*values.at(count)).utf8()); + } + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::insert[QStringList]: done." << endl; + return insert(key, utf8strings, force); + // ########################################################################### +} + +// (^^^ QStringList-s) +// lists of doubles: + +bool +KeyValueMap::insert(const QCString& key, const list<double>& values, bool force) +{ + register bool GUARD; GUARD=false; + // ########################################################################### + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::insert[double list]: trying " + "to insert double list into map." << endl; + QCString buffer; + // QCString value(30*values.size()); // not usable with Qt 2 + QCString value; // WORK_TO_DO: how to reserve enough space to avoid growing? + list<double>::const_iterator pos; + // ----- + for(pos=values.begin(); pos!=values.end(); ++pos) + { + buffer.setNum(*pos); + value=value+buffer+", "; + } + if(!value.isEmpty()) + { // remove last comma and space: + value.remove(value.length()-2, 2); + } + // ----- + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::insert[double list]: " + "constructed string value is " << value << endl; + return insert(key, value, force); + // ########################################################################### +} + +bool +KeyValueMap::get(const QCString& key, list<double>& values) const +{ + register bool GUARD; GUARD=false; + // ########################################################################### + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::get[double list]: trying to " + "decode double list for key " << key << endl; + kdDebug(!values.empty(), KAB_KDEBUG_AREA) << "KeyValueMap::get[double list]: " + "attention - list should be empty but is not." << endl; + QCString value; + list<QCString> tokens; + list<QCString>::iterator pos; + double temp; + bool ok; + // ----- + if(!get(key, value)) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "KeyValueMap::get[double list]: no such key." << endl; + return false; + } + // ----- + tokenize(tokens, value, ','); + for(pos=tokens.begin(); pos!=tokens.end(); ++pos) + { + temp=(*pos).toDouble(&ok); + if(ok) + { + values.push_back(temp); + } else { + kdDebug() << "KeyValueMap::get[double list]: conversion error for " + << *pos << endl; + } + } + // ----- + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::get[int list]: done." << endl; + // ########################################################################### + return true; +} + +// (^^^ lists of doubles) +// QDates: + +bool +KeyValueMap::insert(const QCString& key, const QDate& value, bool force) +{ + register bool GUARD; GUARD=false; + // ########################################################################### + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::insert[QDate]: trying to " + "insert QDate into map." << endl; + list<long> values; + // ----- + if(!value.isValid()) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::insert[QDate]: invalid " + "date, inserting a null date." << endl; + for(int i=0; i<3; ++i) values.push_back(0); + } else { + values.push_back(value.year()); + values.push_back(value.month()); + values.push_back(value.day()); + } + // ----- + return insert(key, values, force); + // ########################################################################### +} + +bool +KeyValueMap::get(const QCString& key, QDate& date) const +{ + register bool GUARD; GUARD=false; + // ########################################################################### + kdDebug(GUARD, KAB_KDEBUG_AREA) << "KeyValueMap::get[QDate]: trying to decode" + " QDate for key " << key << endl; + list<long> values; + long y, m, d; + QDate temp; + // ----- + if(!get(key, values)) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "KeyValueMap::get[QDate]: no such key." << endl; + return false; + } + if(values.size()!=3) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "KeyValueMap::get[QDate]: more or less than 3 values." << endl; + return false; + } + y=values.front(); values.pop_front(); + m=values.front(); values.pop_front(); + d=values.front(); + // ----- + if(y!=0 || m!=0 || d!=0) temp.setYMD(y, m, d); // avoid QDate messages + if(!temp.isValid() && !temp.isNull()) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "KeyValueMap::get[QDate]: no valid date." << endl; + return false; + } else { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "KeyValueMap::get[QDate]: done." << endl; + date=temp; + return true; + } + // ########################################################################### +} + +// (^^^ QDates) +// Section class: + +const int Section::indent_width=2; + +Section::Section() +{ + // ########################################################################### + // ########################################################################### +} + +Section::Section(const KeyValueMap& contents) +{ + // ########################################################################### + keys=contents; + // ########################################################################### +} + +bool +Section::add(const QCString& name) +{ + register bool GUARD; GUARD=false; + // ########################################################################### + kdDebug(GUARD, KAB_KDEBUG_AREA) << "Section::add: adding section \"" + <<name<<"\" to " + "this section ..." << endl; + Section* section; + bool rc; + // ----- + if(name.isEmpty()) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "Section::add: empty key." << endl; + return false; + } + section=new Section; // create an empty section + if(section==0) + { + kdDebug() << "Section::add: out of memory." << endl; + return false; + } + rc=add(name, section); + if(!rc) + { + kdDebug(GUARD && !rc, KAB_KDEBUG_AREA) << " failed.\n"; + delete section; + return false; + } else { + kdDebug(GUARD && rc, KAB_KDEBUG_AREA) << " success.\n"; + return true; + } + // ########################################################################### +} + +bool +Section::add(const QCString& name, Section* section) +{ + register bool GUARD; GUARD=false; + // ########################################################################### + if(sections.insert(StringSectionMap::value_type(name, section)).second) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "Section::add: added section "<<name<<" successfully.\n"; + return true; + } else { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "Section::add: failed to add section " + << name << ", section already exists.\n"; + return false; + } + // ########################################################################### +} + +bool +Section::find(const QCString& name, StringSectionMap::iterator& result) +{ + register bool GUARD; GUARD=false; + // ########################################################################### + kdDebug(GUARD, KAB_KDEBUG_AREA) << "Section::find: trying to get section " + "\""<<name<<"\" ... \n"; + StringSectionMap::iterator pos; + // ----- + pos=sections.find(name); + if(pos==sections.end()) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "failed, no such section." << endl; + return false; + } else { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "success." << endl; + result=pos; + return true; + } + // ########################################################################### +} + +bool +Section::remove(const QCString& name) +{ + // ########################################################################### + StringSectionMap::iterator pos; + // ----- + if(!find(name, pos)) + { + return false; // no such section + } else { + sections.erase(pos); + return true; + } + // ########################################################################### +} + +bool +Section::find(const QCString& name, Section*& section) +{ + // ########################################################################### + StringSectionMap::iterator pos; + // ----- + if(!find(name, pos)) + { + return false; + } else { + section=(*pos).second; + return true; + } + // ########################################################################### +} + +KeyValueMap* +Section::getKeys() +{ + // ########################################################################### + return &keys; + // ########################################################################### +} + +void +Section::insertIndentSpace(QTextStream& file, int level) +{ + // ########################################################################### + int i, j; + // ----- + for(i=0; i<level; i++) + { + for(j=0; j<indent_width; j++) + file << ' '; + } + // ########################################################################### +} + +bool +Section::save(QTextStream& stream, int level) +{ + register bool GUARD; GUARD=false; + // ########################################################################### + StringSectionMap::iterator pos; + // ----- + if(!sections.empty()) + { // ----- insert a comment: + insertIndentSpace(stream, level); + stream << "# subsections:" << endl; + } + for(pos=sections.begin(); pos!=sections.end(); ++pos) + { + insertIndentSpace(stream, level); + stream << '[' << (*pos).first << ']' << endl; + if(!(*pos).second->save(stream, level+1)) + { + kdDebug() << "Section::save: error saving child section \"" << (*pos).first.data() << "\"." << endl; + return false; + } else { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "Section::save: saved section \"" + << (*pos).first + << "\".\n"; + } + insertIndentSpace(stream, level); + stream << "[END " << (*pos).first << ']' << endl; + } + if(!keys.empty()) + { + insertIndentSpace(stream, level); + stream << "# key-value-pairs:" << endl; + if(!keys.save(stream, level*indent_width)) + { + kdDebug() << "Section::save: error saving key-value-pairs." << endl; + return false; + } + } + // ----- + return true; + // ########################################################################### +} + +bool +Section::readSection(QTextStream& file, bool finish) +{ + register bool GUARD; GUARD=false; + // ########################################################################### + kdDebug(GUARD, KAB_KDEBUG_AREA) << "Section::readSection: reading section." << endl; + QCString line; + QCString name; + Section* temp; + // ----- + for(;;) + { + line=""; + line=ReadLineFromStream(file); + if(isEndOfSection(line)) + { // ----- eof does not matter: + return true; + } else { // ----- verify it: + if(file.eof()) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "Section::readSection: EOF, line is \""<<line<<"\".\n"; + if(!line.isEmpty()) + { + if(!keys.insertLine(line, false, true, false)) + { + kdWarning() << "Attention: unable to parse key-value-pair " + << endl << "\t\"" << line << "\"," << endl + << "ignoring and continuing (maybe duplicate " + << "declaration of the key)." + << endl; + } + } + if(finish==true) + { + kdDebug() << "Section::readSection: missing end of section." << endl; + return false; + } else { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "Section::readSection: EOF (no error)." << endl; + return true; + } + } + } + if(isBeginOfSection(line)) + { + name=nameOfSection(line); + add(name); + find(name, temp); + if(!temp->readSection(file)) + { + kdDebug() << "Section::readSection: unable to read " + "subsection \"" << name << "\".\n"; + return false; + } + } else { // ----- it has to be a key-value-pair: + if(!keys.insertLine(line, false, true, false)) + { + kdWarning() << "Attention: unable to parse key-value-pair " << endl + << "\t\"" << line << "\"," << endl + << "ignoring and continuing (maybe duplicate declaration of" + << " the key)." + << endl; + } + } + } + // ########################################################################### +} + +bool +Section::isBeginOfSection(QCString line) +{ + register bool GUARD; GUARD=false; + // ########################################################################### + line=line.simplifyWhiteSpace(); + if(line.isEmpty() || line.length()<2) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "Section::isBeginOfSection: too short " + "or empty line." << endl; + return false; + } + if(line[0]!='[' || line[line.length()-1]!=']') + { + return false; + } + // ----- + if(line.contains("END")) + { + return false; + } else { + return true; + } + // ########################################################################### +} + +bool +Section::isEndOfSection(QCString line) +{ + register bool GUARD; GUARD=false; + // ########################################################################### + kdDebug(GUARD, KAB_KDEBUG_AREA) << "Section::isEndOfSection: is " + << line <<" the end of" + " a section?" << endl; + int first=1, second; + QCString temp; + // ----- + line=line.simplifyWhiteSpace(); + if(line.isEmpty() || line.length()<2) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "Section::isBeginOfSection: too short " + "or empty line." << endl; + return false; + } + if(line[0]!='[' || line[line.length()-1]!=']') + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "Section::isBeginOfSection: does not match." << endl; + return false; + } + // ----- find the word inside the brackets: + for(first=1; line[first]==' '; ++first); // find first non-whitespace character + for(second=first; line[second]!=' ' && line[second]!=']'; ++second); + temp=line.mid(first, second-first); + if(temp=="END") + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "Section::isBeginOfSection: yes, it is." << endl; + return true; + } else { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "Section::isBeginOfSection: no, it is not." << endl; + return false; + } + // ########################################################################### +} + +QCString +Section::nameOfSection(const QCString& line) +{ + register bool GUARD; GUARD=false; + // ########################################################################### + int first=1, second; + QCString temp; + // ----- + temp=line.simplifyWhiteSpace(); + if(temp.isEmpty() || temp.length()<=2) + { // empty section names are not allowed + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "Section::isBeginOfSection: too short or empty line." << endl; + return ""; + } + if(temp[0]!='[' || temp[temp.length()-1]!=']') + { + return ""; + } + // ----- find the word inside the brackets: + for(first=1; temp[first]==' '; ++first); // find first non-whitespace character + for(second=first; temp[second]!=' ' && temp[second]!=']'; ++second); + temp=temp.mid(first, second-first); + if(temp=="END") + { + return ""; + } else { + return temp; + } + // ########################################################################### +} + +bool +Section::clear() +{ + // ########################################################################### + StringSectionMap::iterator pos; + // ----- + for(pos=sections.begin(); pos!=sections.end(); ++pos) + { + if(!(*pos).second->clear()) return false; + delete(*pos).second; + } + // sections.clear(); // seems to be not implemented + sections.erase(sections.begin(), sections.end()); + keys.clear(); + // ----- + return true; + // ########################################################################### +} + +bool +Section::empty() +{ + // ########################################################################### + return keys.empty() && sections.empty(); + // ########################################################################### +} + +Section::StringSectionMap::iterator +Section::sectionsBegin() +{ + // ########################################################################### + return sections.begin(); + // ########################################################################### +} + +Section::StringSectionMap::iterator +Section::sectionsEnd() +{ + // ########################################################################### + return sections.end(); + // ########################################################################### +} + +unsigned int +Section::noOfSections() +{ + // ########################################################################### + return sections.size(); + // ########################################################################### +} + +QConfigDB::QConfigDB(QWidget* parent, const char* name) + : QWidget(parent, name), + timer(0), + readonly(true), + locked(false), + mtime(new QDateTime) +{ + // ########################################################################### + hide(); + // ########################################################################### +} + + +QConfigDB::~QConfigDB() +{ + // ############################################################################ + // disconnect(); + // ----- + if(timer!=0) + { + delete timer; timer=0; + } + if(!clear()) // this will emit changed() a last time + { + kdDebug() << "QConfigDB destructor: cannot remove me." << endl; + } + if(locked) + { + unlock(); + } + // ############################################################################ +} + +bool QConfigDB::invariant() +{ + return true; +} + +bool +QConfigDB::get(const list<QCString>& key, KeyValueMap*& map) +{ + register bool GUARD; GUARD=false; + // ############################################################################ + kdDebug(GUARD, KAB_KDEBUG_AREA) << "QConfigDB::get: trying to get keys ... " << endl; + Section* section=⊤ + list<QCString>::const_iterator pos; + // ----- + if(key.empty()) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "ConfigDB::get: path is empty, returning toplevel section." << endl; + map=top.getKeys(); + return true; + } + for(pos=key.begin(); pos!=key.end(); ++pos) + { + if(!section->find(*pos, section)) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "failed,\n at least the element \"" + << *pos << + "\" of " + "the key-list is not declared." << endl; + return false; + } + } + // ----- + map=section->getKeys(); + kdDebug(GUARD, KAB_KDEBUG_AREA) << "success." << endl; + return true; + // ############################################################################ +} + +KeyValueMap* +QConfigDB::get() +{ + // ############################################################################ + return top.getKeys(); + // ############################################################################ +} + +bool +QConfigDB::createSection(const list<QCString>& key) +{ + // ############################################################################ + Section* section=⊤ + unsigned int index; + list<QCString>::const_iterator pos; + Section* thenewone; + bool rc; + // ----- + pos=key.begin(); + for(index=0; index<key.size()-1; index++) + { + if(!section->find(*pos, section)) + { // this section is not declared + Section* temp=new Section; // WORK_TO_DO: memory hole? + if(section->add(*pos, temp)) + { + section=temp; + } else { + delete temp; + return false; + } + } + ++pos; + } + // pos now points to the last element of key, + // section to the parent of the section that will be inserted + thenewone=new Section; + rc=section->add(*pos, thenewone); + return rc; // missing error report! WORK_TO_DO + // ############################################################################ +} + +bool +QConfigDB::clear() +{ + // ############################################################################ + bool rc=top.clear(); + emit(changed(this)); + return rc; + // ############################################################################ +} + +bool +QConfigDB::empty() +{ + // ############################################################################ + return top.empty(); + // ############################################################################ +} + +bool +QConfigDB::createSection(const QCString& desc) +{ + // ############################################################################ + return createSection(stringToKeylist(desc)); + // ############################################################################ +} + +bool +QConfigDB::get(const QCString& key, KeyValueMap*& map) +{ + // ############################################################################ + return get(stringToKeylist(key), map); + // ############################################################################ +} + +list<QCString> +QConfigDB::stringToKeylist(const QCString& desc) +{ + register bool GUARD; GUARD=false; + kdDebug(GUARD, KAB_KDEBUG_AREA) << "QConfigDB::stringToKeylist: parsing path " << desc << endl; + // ############################################################################ + list<QCString> key; + int first=0, second; + QCString temp; + // ----- + if(desc.isEmpty()) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "QConfigDB::stringToKeylist: path is empty." << endl; + return key; + } + for(;;) + { + second=desc.find('/', first); + if(second==-1) + { + if((unsigned)first<desc.length()+1) + { + temp=desc.mid(first, desc.length()-first); + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "QConfigDB::stringToKeylist: found last part " + << temp << endl; + key.push_back(temp); + } + break; + } + temp=desc.mid(first, second-first); + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "QConfigDB::stringToKeylist: found part " << temp << endl; + key.push_back(temp); + first=second+1; + } + // ----- + kdDebug(GUARD, KAB_KDEBUG_AREA) << "QConfigDB::stringToKeylist: done." << endl; + return key; + // ############################################################################ +} + + +bool +QConfigDB::get(const QCString& key, Section*& section) +{ + // ############################################################################ + return get(stringToKeylist(key), section); + // ############################################################################ +} + +bool +QConfigDB::get(const list<QCString>& key, Section*& section) +{ + register bool GUARD; GUARD=false; + // ############################################################################ + kdDebug(GUARD, KAB_KDEBUG_AREA) << "QConfigDB::get: searching section ... " << endl; + Section* temp=⊤ + list<QCString>::const_iterator pos; + // ----- + for(pos=key.begin(); pos!=key.end(); ++pos) + { + if(!temp->find(*pos, temp)) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "failure, no such section."; + return false; + } + } + // ----- + section=temp; + kdDebug(GUARD, KAB_KDEBUG_AREA) << "success, section found." << endl; + return true; + // ############################################################################ +} + +bool +QConfigDB::isRO() +{ + // ############################################################################ + return readonly; + // ############################################################################ +} + +int +QConfigDB::IsLocked(const QString& file) +{ + register bool GUARD; GUARD=false; + // ############################################################################ + QString lockfile=file+".lock"; + int pid=-1; + // ----- + if(access(QFile::encodeName(lockfile), F_OK)==0) + { + QFile f(lockfile); + // ----- + kdDebug(GUARD, KAB_KDEBUG_AREA) << "QConfigDB::IsLocked: the file\n" + << file << + "\nhas a lockfile.\n"; + if(f.open(IO_ReadOnly)) + { + QTextStream stream(&f); + stream.setEncoding(QTextStream::Latin1); // no conversion + // ----- + stream >> pid; + if(pid==-1) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "QConfigDB::IsLocked: the file " + "does not contain the ID\n of the process that " + "created it." << endl; + return -1; + } + f.close(); + } else { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "QConfigDB::IsLocked: cannot open the lockfile." << endl; + return -1; + } + return pid; + } else { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "QConfigDB::IsLocked: the file\n" + << file << "\nhas no lockfile.\n"; + return 0; + } + // ############################################################################ +} + +bool +QConfigDB::lock() +{ + register bool GUARD; GUARD=false; + // ############################################################################ + if(locked) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "QConfigDB::lock (current file): file " + "is already locked by this object." << endl; + return false; + } + if(lock(filename)) + { + locked=true; + return true; + } else { + return false; + } + // ############################################################################ +} + +bool +QConfigDB::lock(const QString& file) +{ + register bool GUARD; GUARD=false; + // ############################################################################ + kdDebug(GUARD, KAB_KDEBUG_AREA) << "QConfigDB::lock: locking the file " + << file << endl; + QString lockfile=file+".lock"; + QFile f(lockfile); + // ----- + if(access(QFile::encodeName(lockfile), F_OK)==0) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "QConfigDB::lock: the file is locked by" + " another process." << endl; + return false; + } else { + if(f.open(IO_WriteOnly)) + { + QTextStream stream(&f); + stream.setEncoding(QTextStream::Latin1); // no conversion + // ----- + stream << getpid() << endl; + f.close(); + } else { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "QConfigDB::lock: unable to create" + " lockfile." << endl; + return false; + } + } + // ----- + LockFiles.push_back(lockfile); + return true; + // ############################################################################ +} + +bool +QConfigDB::unlock() +{ + register bool GUARD; GUARD=false; + // ############################################################################ + kdDebug(GUARD, KAB_KDEBUG_AREA) << "QConfigDB::unlock: unlocking the file " + << filename << endl; + QString lockfile=filename+".lock"; + list<QString>::iterator pos; + // ----- + if(!locked) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "QConfigDB::unlock: this app did not " + "lock the file!" << endl; + return false; + } + if(access(QFile::encodeName(lockfile), F_OK | W_OK)==0) + { + if(::remove(QFile::encodeName(lockfile))==0) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "QConfigDB::unlock: lockfile deleted." << endl; + for(pos=LockFiles.begin(); pos!=LockFiles.end(); ++pos) + { + if((*pos)==lockfile) break; + } + if(pos!=LockFiles.end()) + { + LockFiles.erase(pos); --pos; + } else { + kdDebug() << "QConfigDB::unlock: file not mentioned in lockfile" << endl; + } + locked=false; + return true; + } else { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "QConfigDB::unlock: unable to " + "delete lockfile.n" << endl; + return false; + } + } else { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "QConfigDB::unlock: the file is not" + " locked or permission has been denied." << endl; + return false; + } + // ############################################################################ +} + +void +QConfigDB::CleanLockFiles(int) +{ + register bool GUARD; GUARD=false; + // ############################################################################ + list<QString>::iterator pos; + // ----- + kdDebug(GUARD, KAB_KDEBUG_AREA).form("QConfigDB::CleanLockFiles: removing %i " + "remaining lockfiles.", LockFiles.size()) << endl; + for(pos=LockFiles.begin(); pos!=LockFiles.end(); ++pos) + { + if(::remove(QFile::encodeName(*pos))==0) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + " " << *pos << " removed.\n"; + LockFiles.erase(pos); --pos; + } else { + kdDebug() << " could not remove " << *pos << endl; + } + } + // ----- + kdDebug(GUARD, KAB_KDEBUG_AREA) << "QConfigDB::CleanLockFiles: done." << endl; + // ############################################################################ +} + +void +QConfigDB::watch(bool state) +{ + // ############################################################################ + if(state) + { // start timer + if(timer==0) + { + timer=new QTimer(this); + connect(timer, SIGNAL(timeout()), SLOT(checkFileChanged())); + } + timer->start(1000); + } else { // stop timer + if(timer!=0) + { + timer->stop(); + } + } + // ############################################################################ +} + +bool +QConfigDB::CheckLockFile(const QString& file) +{ + register bool GUARD; GUARD=false; + // ############################################################################ + kdDebug(GUARD, KAB_KDEBUG_AREA) << "QConfigDB::CheckLockFile: called." << endl; + int pid; + // ----- + pid=IsLocked(file); + if(pid==0) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "QConfigDB::CheckLockFile: the file is " + "not locked." << endl; + return false; + } + if(pid>0) + { + if(kill(pid, 0)!=0) + { // ----- no such process, we may remove the lockfile: + return false; + } + } + if(pid<0) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "QConfigDB::CheckLockFile: the file has " + "not been created by QConfigDB::lock." << endl; + } + // ----- check system time and creation time of lockfile: + // WORK_TO_DO: not implemented + kdDebug(GUARD, KAB_KDEBUG_AREA) << "QConfigDB::CheckLockFile: done." << endl; + return true; + // ############################################################################ +} + +bool +QConfigDB::checkFileChanged() +{ + register bool GUARD; GUARD=false; + // ############################################################################ + // kdDebug(GUARD, KAB_KDEBUG_AREA) << "QConfigDB::checkFileChanged: called." << endl; + if(filename.isEmpty()) + { // ----- false, as file does not exist and thus may be stored anyway + kdDebug(GUARD, KAB_KDEBUG_AREA) << "QConfigDB::checkFileChanged: no filename." << endl; + return false; + } + QFileInfo file(filename); + // ----- + if(file.exists()) + { + if(file.lastModified() > *mtime) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "QConfigDB::checkFileChanged: file has been changed.n" << endl; + emit(fileChanged()); + return true; + } else { + return false; + } + } else { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "QConfigDB::checkFileChanged: could " + "not stat file, file does not exist." << endl; + if(!mtime->isValid()) + { // the file did never exist for us: + return false; // ... so it has not changed + } else { // it existed, and now it does no more + emit(fileChanged()); + return true; + } + } + // ############################################################################ +} + +bool +QConfigDB::storeFileAge() +{ + register bool GUARD; GUARD=false; + // ############################################################################ + kdDebug(GUARD, KAB_KDEBUG_AREA) << "QConfigDB::storeFileAge: called." << endl; + QFileInfo file(filename); + // ----- + if(file.exists()) + { + *mtime=file.lastModified(); + return true; + } else { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "QConfigDB::save: could not stat file." << endl; + *mtime=QDateTime(); // a null date + return false; + } + // ############################################################################ +} + + +bool +QConfigDB::setFileName(const QString& filename_, bool mustexist, bool readonly_) +{ + register bool GUARD; GUARD=false; + // ############################################################################ + kdDebug(GUARD, KAB_KDEBUG_AREA) << "QConfigDB::setFileName: setting filename " + "to \"" + << filename_ <<"\"" << (readonly_ ? " (read only)" : "") << endl; + // ----- remove previous lock: + if(locked) + { + if(!unlock()) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "QConfigDB::setFileName: cannot " + "release previous lock." << endl; + return false; + } + } + // ----- remove possible stale lockfile: + if(IsLocked(filename_)!=0 && !CheckLockFile(filename_)) + { // ----- it is stale: + if(::remove(QFile::encodeName(filename_+".lock"))==0) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "QConfigDB::setFileName: removed stale lockfile." << endl; + } else { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "QConfigDB::setFileName: cannot remove stale lockfile." << endl; + return false; + } + } + // ----- + if(mustexist) + { + if(access(QFile::encodeName(filename_), readonly_==true ? R_OK : W_OK | R_OK)==0) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "QConfigDB::setFileName: permission granted." << endl; + if(!readonly_) + { // we need r/w access: + if(lock(filename_)) + { + locked=true; + } else { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "QConfigDB::setFileName: " + "could not lock the file." << endl; + return false; + } + } + readonly=readonly_; + filename=filename_; + storeFileAge(); + return true; + } else { + kdDebug() << "QConfigDB::setFileName: permission denied, " << endl; + return false; + } + } else { + if(access(QFile::encodeName(filename_), F_OK)==0) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "QConfigDB::setFileName: file exists." << endl; + if(access(QFile::encodeName(filename_), W_OK | R_OK)==0) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "QConfigDB::setFileName: permission granted." << endl; + if(!readonly_) + { // we need r/w access: + if(lock(filename_)) + { + locked=true; + } else { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "QConfigDB::setFileName: " + "could not lock the file." << endl; + return false; + } + } + readonly=readonly_; + filename=filename_; + storeFileAge(); + return true; + } else { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "QConfigDB::setFileName: " + "permission denied, filename not set." << endl; + return false; + } + } else { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "QConfigDB::setFileName: permission granted, new file." << endl; + readonly=readonly_; + filename=filename_; + if(!readonly) + { + if(!lock()) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "QConfigDB::setFileName: could not lock the file." << endl; + return false; + } + } + storeFileAge(); + return true; + } + } + // ############################################################################ +} + +QString +QConfigDB::fileName() +{ + // ############################################################################ + return filename; + // ############################################################################ +} + +bool +QConfigDB::save(const char* header, bool force) +{ + register bool GUARD; GUARD=true; + // ############################################################################ + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "QConfigDB::save: saving database -->" << filename << "<--.\n"; + bool wasRO=false; + bool rc; + // ----- + if(checkFileChanged()) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "QConfigDB::save: file is newer, not saving." << endl; + return false; + } + if(force && isRO()) + { + if(setFileName(fileName(), true, false)) + { + wasRO=true; + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "QConfigDB::save: switched to (forced) r/w mode." << endl; + } else { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "QConfigDB::save: cannot switch to (forced) r/w mode." << endl; + return false; + } + } + // ----- now save it: + if(!isRO()) + { + QFile file(filename); + if(file.open(IO_WriteOnly)) + { + QTextStream stream(&file); + stream.setEncoding(QTextStream::Latin1); // no conversion + // ----- + if(header!=0) + { + stream << "# " << header << endl; + } + stream << '#' << " [File created by QConfigDB object " + << version() << "]" << endl; + if(!top.save(stream)) // traverse tree + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "QConfigDB::save: error saving subsections." << endl; + } + storeFileAge(); + file.close(); + rc=true; + } else { + kdDebug() << "QConfigDB::save: error opening file \"" + << filename << + "\" for writing.\n"; + rc=false; + } + } else { + rc=false; + } + // ----- reset r/o mode: + if(wasRO) // only true if we switched to forced r/w mode here + { + if(setFileName(fileName(), false, true)) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "QConfigDB::save: reset (forced) r/w mode." << endl; + } else { + kdDebug(GUARD, KAB_KDEBUG_AREA) << + "QConfigDB::save: cannot reset (forced) r/w mode." << endl; + rc=false; + } + } + // ----- + return rc; + // ############################################################################ +} + +bool +QConfigDB::load() +{ + register bool GUARD; GUARD=false ; + // ############################################################################ + QFile file(filename); + // ----- + if(file.open(IO_ReadOnly)) + { + kdDebug(GUARD, KAB_KDEBUG_AREA) << "QConfigDB::load: file access OK." << endl; + QTextStream stream(&file); + stream.setEncoding(QTextStream::Latin1); // no conversion + // ----- + clear(); + bool rc=top.readSection(stream, false); + storeFileAge(); + file.close(); + emit(changed(this)); + kdDebug(GUARD, KAB_KDEBUG_AREA) << "QConfigDB::load: done." << endl; + return rc; + } else { + kdDebug() << "QConfigDB::load: error opening file \"" << filename << "\" for reading." << endl; + return false; + } + // ############################################################################ +} diff --git a/kab/qconfigDB.h b/kab/qconfigDB.h new file mode 100644 index 000000000..7a71d901e --- /dev/null +++ b/kab/qconfigDB.h @@ -0,0 +1,779 @@ +/* -*- C++ -*- */ +#ifndef QCONFIGDB_H +#define QCONFIGDB_H + +/* the Configuration Database library, Version II + + the KDE addressbook + + $ Author: Mirko Boehm $ + $ Copyright: (C) 1996-2001, Mirko Boehm $ + $ Contact: mirko@kde.org + http://www.kde.org $ + $ License: GPL with the following explicit clarification: + This code may be linked against any version of the Qt toolkit + from Troll Tech, Norway. $ + + $Id$ +*/ + +namespace std { } +using namespace std; + +#include <list> +#include <map> +#include <qwidget.h> +#include <qcstring.h> +#include <qstrlist.h> + +class QTimer; +class QDate; +class QString; +class QDateTime; + +extern "C" { +#include <unistd.h> +} + +class QTextStream; + +/** + * This is some STL interna, a function object for use with STL + * container classes. Its only element function is the function + * operator that returns a comparison value of the both objects + * it is called with. + */ +struct QCStringLess + : public binary_function<const QCString&, const QCString&, bool> +{ + /** + * The function operator, inline. + */ + bool operator()(const QCString& x, const QCString& y) const + { + return x < (const char*)y; // make one Qt operator fit exactly + } +}; + +typedef map<QCString, QCString, QCStringLess> StringStringMap; + +/** + * The class KeyValueMap is used for managing key-value-pairs + * WITHOUT any hierarchical structure. Objects of it can be + * used as they are or in conjunction with the configuration + * database class. + * While the first version used the string class, this second + * uses the QCString class. + * The class uses pairs of methods for each datatype, they are + * called ::get and ::insert. Every overloaded version of this + * methods get the key of the settings and a reference to the + * value to set or to store it in. A boolean result reports if + * there where errors or if the key already existed. Keys must + * of course be unique. Please note that the map does NOT store type + * information for the keys. You may retrieve a boolean value for a string, + * it will work if the string is either "true" or "false". + * See the different get- and insert-methods for details. + * + * Capabilities of the class are: + * <OL> + * <LI> storing of any key-value-pair that is storable in + * string values (no binary objects currently), </LI> + * <LI> key-value-pairs are saved in human-readable text files + * when saving to disk, </LI> + * <LI> the values may contain newline and tabulator characters + * which will still be there after saving and rereading, </LI> + * <LI> supports the following datatypes: <OL> + * <LI> strings (of course), </LI> + * <LI> integers, </LI> + * <LI> floating point values and </LI> + * <LI> boolean states </LI> </OL> </LI> + * <LI> supports storing and retrieving of lists of values of the + * following datatypes: <OL> + * <LI> strings, </LI> + * <LI> integers and </LI> + * <LI> floating point values </LI> </OL> + * (boolean lists supported in future when requested) </LI> + * <LI> easy syntax of files, in general it is supposed to be a + * kind of guarantee (you know that free software never + * guarantees anything, don't you?) that every value that + * has been stored by one of the member functions of the + * class like <BR> + * <TT> insert(const QCString& key, [value&]); </TT> <BR> + * can also be retrieved using <BR> + * <TT> get(const QCString& key, [value&]);</TT> <BR> + * without being modified. <BR> + * (Please report anything that does not do so!) </LI> </OL> + * The class is used to implement the #QConfigDB class. + */ + +class KeyValueMap +{ + // ############################################################################ +protected: + /** + * A map storing the key-value-pairs. + */ + StringStringMap* data; + /** + * Transform a complex string into a normal string object. + * The values are not stored as they are, they are coded into + * complex string where control and non-printable characters get a readable + * representation. + * When retrieving, this strings are translated back by this method. + * \a orig contains the string read from the file, \a index the position from + * where to start the translation (need not be the beginning of the string), + * \a result contains the transformed string, \a noOfChars the number of + * characters used to parse the string. + * Returns true if there where no errors while parsing. + * @see makeComplexString + */ + bool parseComplexString(const QCString& orig, int index, + QCString& result, int& noOfChars) const; + /** + * Codes a normal string into a complex string. + * @see parseComplexString + */ + QCString makeComplexString(const QCString& orig); + /** + * Inserts a complex string into the map. + * The string must be coded already, no tests are performed. + * \a if force is false, an existing value will not be overridden. + */ + bool insertRaw(const QCString& key, const QCString& value, bool force=false); + /** + * Retrieves the undecoded value (a complex string) of the given key. + */ + bool getRaw(const QCString& key, QCString& value) const; + // ---------------------------------------------------------------------------- +public: + /** + * The default constructor. + */ + KeyValueMap(); + /** + * The copy constructor. + */ + KeyValueMap(const KeyValueMap&); + /** + * The virtual destructor. + */ + virtual ~KeyValueMap(); + /** + * The begin iterator. Use it to iterate over the keys in the map. + */ + StringStringMap::iterator begin(); + /** + * The end iterator. + */ + StringStringMap::iterator end(); + /** + * Debugging aid: returns true if object is OK. + */ + bool invariant(); + /** + * Returns the number of key-value-pairs in the map. + */ + unsigned int size() const; + /** + * Delete all entries. + */ + void clear(); + /** + * Fills the map with the files contents. + * If the parameter \a force is true, it overrides keys that are + * already declared in the database and are declared again in the file. + * If \a relax is true, the value of a string may be empty. + */ + bool fill(const QString&, bool force=false, bool relax=false); + /** + * Saves the database to a file. + * Only overrides existing files if force is true. + */ + bool save(const QString&, bool force=false); + /** + * Saves contents to an already open text stream. + * \a count spaces are inserted before each line. This method is + * called when save hierarchical databases. + * @see ::QConfigDB + */ + bool save(QTextStream& file, int count); + /** + * Get the value for the key as a string. + * \a key is the key to search for, \a value is a reference to the object + * the value for the key is assigned to. + */ + bool get(const QCString& key, QCString& value) const; + /** + * Insert a string value for the given key. + * If force is true, an existing value for this key will be overridden. + * The method returns false if the key exists and \a force is false. + */ + bool insert(const QCString& key, const QCString& value, bool force=false); + /** + * Insert a character pointer for the given key. + * pgcc treats character pointers as boolean objects, not as strings. + * If force is true, an existing value for this key will be overridden. + * The method returns false if the key exists and \a force is false. + */ + inline bool insert(const QCString& key, const char* value, bool force=false); + /** + * Insert a line like "key_a="Hallo!" into the map as a key-value-pair. + * If force is true existing keys will be overridden. + * If relax is true the value may be empty an empty string. + * If encode is false, the string will not be coded (do not use!). + */ + bool insertLine(QCString, bool force=false, bool relax=false, bool encode=true); + // --------------- + /** + * Get the value for the key as a long integer. + * \a key is the key to search for, \a value is a reference to the object + * the value for the key is assigned to. + */ + bool get(const QCString&, long&) const; + /** + * Insert a long integer value for the given key. + * If force is true, an existing value for this key will be overridden. + * The method returns false if the key exists and \a force is false. + */ + bool insert(const QCString&, const long&, bool force=false); + // --------------- + /** + * For insertion of UNICODE strings, a special method pair is created. The + * data will be translated to utf8 and inserted in the map as a QCString. + * This will probably be not fast, but this methods are not suited to save + * large amounts of data. For saving anything else than UNICODE strings, + * no such conversion is needed. + * \a key is the key to search for, \a value is a reference to the object + * the value for the key is assigned to. + */ + bool get(const QCString&, QString&) const; + /** + * Insert a UNICODE string value for the given key. + * If force is true, an existing value for this key will be overridden. + * The method returns false if the key exists and \a force is false. + */ + bool insert(const QCString&, const QString&, bool force=false); + // --------------- + /** + * Get the value for the key as a double. + * \a key is the key to search for, \a value is a reference to the object + * the value for the key is assigned to. + */ + bool get(const QCString&, double&) const; + /** + * Insert a double value for the given key. + * If force is true, an existing value for this key will be overridden. + * The method returns false if the key exists and \a force is false. + */ + bool insert(const QCString&, const double&, bool force=false); + // --------------- + /** + * Get the value for the key as a boolean value. + * \a key is the key to search for, \a value is a reference to the object + * the value for the key is assigned to. + */ + bool get(const QCString&, bool&) const; + /** + * Insert a boolean value for the given key. + * If force is true, an existing value for this key will be overridden. + * The method returns false if the key exists and \a force is false. + */ + bool insert(const QCString&, const bool&, bool force=false); + // --------------- + /** + * Get the value for the key as a list of strings. + * \a key is the key to search for, \a value is a reference to the object + * the value for the key is assigned to. + */ + bool get(const QCString&, list<QCString>&) const; + /** + * Insert a list of strings for the given key. + * If force is true, an existing value for this key will be overridden. + * The method returns false if the key exists and \a force is false. + */ + bool insert(const QCString&, const list<QCString>&, bool force=false); + // -------------- + /** + * Get the value for the key as a QStrList. + * \a key is the key to search for, \a value is a reference to the object + * the value for the key is assigned to. + */ + bool get(const QCString&, QStrList&) const; + /** + * Insert a QStrList for the given key. + * If force is true, an existing value for this key will be overridden. + * The method returns false if the key exists and \a force is false. + */ + bool insert(const QCString&, const QStrList&, bool force=false); + // -------------- + /** + * Get the value for the key as a QStringList. Beware of the difference - + * a QStringList is a list of QString objects, while QStrList handles + * char* like objects. + * \a key is the key to search for, \a value is a reference to the object + * the value for the key is assigned to. + */ + bool get(const QCString&, QStringList&) const; + /** + * Insert a QStringList for the given key. + * If force is true, an existing value for this key will be overridden. + * The method returns false if the key exists and \a force is false. + */ + bool insert(const QCString&, const QStringList&, bool force=false); + // -------------- + /** + * Get the value for the key as a list of long integers. + * \a key is the key to search for, \a value is a reference to the object + * the value for the key is assigned to. + */ + bool get(const QCString&, list<long>&) const; + /** + * Insert a list of long integers for the given key. + * If force is true, an existing value for this key will be overridden. + * The method returns false if the key exists and \a force is false. + */ + bool insert(const QCString&, const list<long>&, bool force=false); + // -------------- + /** + * Get the value for the key as a list of integers. + * \a key is the key to search for, \a value is a reference to the object + * the value for the key is assigned to. + */ + bool get(const QCString&, list<int>&) const; + /** + * Insert a list of integers for the given key. + * If force is true, an existing value for this key will be overridden. + * The method returns false if the key exists and \a force is false. + */ + bool insert(const QCString&, const list<int>&, bool force=false); + // -------------- some Qt high-level data types: + /** + * Get the value for the key as a QDate. + * The value will be parsed to a integer list that must be a \e valid + * date (see QDate documentation). \c false will be returned if the value + * is not valid or a null date. This situation might only happen in + * manually created files, since the insert-method for QDates rejects to + * insert inalid dates, it inserts null dates instead. + * @see get(const QCString&, QDate &) + */ + bool get(const QCString&, QDate &) const; + /** + * Insert a QDate for the given key. + * If force is true, an existing value for this key will be overridden. + * The method returns false if the key exists and \a force is false. + * \e Attention: If you insert an invalid date a null date will be used. + * A null date will also be returned when retrieving this value. + * You will not be able to store an invalid date and retrieve it using + * ::get! + */ + bool insert(const QCString&, const QDate&, bool force=false); + // -------------- + /** + * Get the value for the key as a list of doubles. + * \a key is the key to search for, \a value is a reference to the object + * the value for the key is assigned to. + */ + bool get(const QCString&, list<double>&) const; + /** + * Insert a list of doubles for the given key. + * If force is true, an existing value for this key will be overridden. + * The method returns false if the key exists and \a force is false. + */ + bool insert(const QCString&, const list<double>&, bool force=false); + // -------------- + // end of corresponding get-insert-pairs + /** + * Returns true if there are no keys declared in this map. + */ + bool empty(); + /** + * Erases all key-value-pairs in the map. + */ + bool erase(const QCString& key); + // ############################################################################ +}; + +/** + * A Section object manages one section of a configuration database. + * A configuration database consists of sections which in turn + * consist of other sections (recursive definition) and + * key-value-pairs. This file declares the Section class. An + * object of Section manages exactly one section during its + * lifetime. + */ + +class Section +{ + // ############################################################################ +public: + /** + * The StringSectionMap type is defined to make the code more readable. + */ + typedef map<QCString, Section*, QCStringLess> StringSectionMap; + // ---------------------------------------------------------------------------- +protected: + /** + * A map containing the subsections of this section. + */ + StringSectionMap sections; + /** + * The key-value-pairs of this section. + */ + KeyValueMap keys; + /** + * The number of spaces a subsection is indented in text files. + */ + static const int indent_width; + /** + * Insert the spaces for indention the lines of this section when saving. + */ + void insertIndentSpace(QTextStream& file, int level); + /** + * Check whether the string (one line of the file currently read) marks the + * beginning of a new subsection (usually [sectionname]). + */ + bool isBeginOfSection(QCString); + /** + * Check whether the string (one line of the file currently read) marks the + * end of a new subsection (usually [END]). + */ + bool isEndOfSection(QCString); + /** + * Extract the name of the section from the string. + * The string must contain the line that starts the section. + * @see ::isBeginOfSection + */ + QCString nameOfSection(const QCString&); + // ---------------------------------------------------------------------------- +public: + /** + * The default constructor. + */ + Section(); + /** + * Constructor that fills the keys with the given map entries. + */ + Section(const KeyValueMap&); + // handling sections: + /** + * Add an empty new section. + */ + bool add(const QCString&); + /** + * Add the section. + */ + bool add(const QCString&, Section*); + /** + * Search for the section, returning an iterator to it. + */ + bool find(const QCString&, StringSectionMap::iterator&); + /** + * Search for the section, returning a pointer to the section object. + */ + bool find(const QCString&, Section*&); + /** + * Remove this subsection. + */ + bool remove(const QCString&); + /** + * Return the key-value-pairs of this (!) section. + */ + KeyValueMap* getKeys(); + /** + * Save this section to the given output stream. + * Level is the position in section tree depth (the hierarchy level). + * It is used for indenting. + */ + bool save(QTextStream& stream, int level=0); + /** + * Read one section from the given input stream. + * The method does not expect the line that marks the begin of the + * section. If finish is false, the code does also not except the + * section to be ended with a line like [END]. + */ + bool readSection(QTextStream& file, bool finish=true); + /** + * Clears both subsections and keys. + */ + bool clear(); + /** + * Returns whether this section is empty. A section is empty if it has no + * subsections and no key-value-pairs. + */ + bool empty(); + // methods to allow iterating through the subsections + /** + * Return an iterator to the beginning of the subsections map. + */ + StringSectionMap::iterator sectionsBegin(); + /** + * Return an iterator to the end of the subsections map. + */ + StringSectionMap::iterator sectionsEnd(); + /** + * Return the number of subsections. + */ + unsigned int noOfSections(); + // ############################################################################ +}; + +/** + * The class QConfigDB is used to manage text-based data files + * with hierarchical structure. <BR> + * It is derived from ::QWidget, so it may be derived to display + * its contents. The basic implementation here does not display + * anything to make it a lean class. <BR> + * Some notes about the philosophy of the configuration + * database library: <OL> + * <LI> The tasks in managing the structure are shared between the three + * involved classes ::KeyValueMap, ::Section and QConfigDB. </LI> + * <LI> \a QConfigDB + * is used for retrieving sections or key-value-maps from the data + * hierarchy using keys. This keys are either pathes in UNIX style like + * "section_A/section_B/section_C", where C is a subsection of B which + * is in turn a subsection of A, or (STL) lists of strings in equivalent + * style (the first element of the list is treated as the first part of + * the path, and so on). </LI> + * <LI> Section objects are used to manipulate the tree structure below one + * particular section. </LI> + * <LI> KeyValueMap objects are used to retrieve and modify the + * key-value-pairs of one section, but not for its subsections. </LI> + * </OL> + * Thus, to use the keys of a specific section in the database, you first + * retrieve it using the ::get methods, and then manipulate the + * ::KeyValueMap you got. You may also retrieve a pointer to the whole + * section, if you need access to its subsections, for example. Although + * this sounds complex, it is a really easy and comprehensive way to write + * code using tree-structured text files. <BR> + * See the code examples provided with the library for details. + */ + +class QConfigDB : public QWidget +{ + // ############################################################################ + Q_OBJECT + // ---------------------------------------------------------------------------- +protected: + /** + * The toplevel section. + */ + Section top; + /** + * A timer pointer for watching the file. + */ + QTimer *timer; + // ---------------------------------------------------------------------------- +public: + /** + * The Qt standard constructor. + */ + QConfigDB(QWidget* parent=0, const char* name=0); + /** + * The virtual destructor. + */ + virtual ~QConfigDB(); + /** + * Get the key-value-map for the section referenced by \a key. + */ + bool get(const QCString& key, KeyValueMap*& map); + /** + * Get the key-value-map for the section referenced by \a key as key list. + */ + bool get(const list<QCString>& key, KeyValueMap*& map); + /** + * Get the address of the specified Section object by its path. + * Never delete the section returned to you. + */ + bool get(const QCString& key, Section*&); + /** + * Get the address of the specified Section object by a path list. + * Never delete the section returned to you. + */ + bool get(const list<QCString>& key, Section*&); + /** + * Get the keys of the toplevel section. + */ + KeyValueMap* get(); + /** + * Create the section with this path. + * All elements of the path that do not exist are created. + */ + bool createSection(const QCString& key); + /** + * Create the section with a path like the path list. + * All elements of the path that do not exist are created. + */ + bool createSection(const list<QCString>& key); + /** + * Load the file. + * @see ::setFileName + */ + bool load(); + /** + * Save the file. + * \a header will be the comment in the first line of the file. + * If \a force is \c true, a file opened read-only will be switched + * to read and write mode and back after saving. + * @see ::setFileName + */ + bool save(const char* header=0, bool force=false); + /** + * Set the current file name to \a name. + * Every QConfigDB object requires a file name to be set using + * this method before the file operations work. + * setFileName performs checks if the current user may use the file + * in the requested way. If \a ro is true, she must have + * permissions to read the file, if it is false, permission must be + * given to read and write the file. If \a mustexist is true, the file + * must have existed before, if not, it might be created. + * If any check failes, false is returned and the objects state is not + * altered. Subsequent calls may be used to check if a file already + * exists. + */ + bool setFileName(const QString& name, bool mustexist=true, bool ro=false); + /** + * Store the modification time of the file for later check of changes. + */ + bool storeFileAge(); + /** + * Give the current filename. + */ + QString fileName(); + /** + * Returns if the current file name is set for read only access. + */ + bool isRO(); + /** + * Clear the whole database. + */ + bool clear(); + /** + * Return whether the db is empty (e.g. the toplevel section is). + */ + bool empty(); + /** + * Return a string describing the version. + */ + static const char* version() { return "2.0 $Revision$"; } + /** + * Check wether the given file is locked. + * The method returns zero if not, a number > zero is the pid of the process + * locking the file, a number < zero reports an error and indicates + * that the file is locked. + */ + static int IsLocked(const QString& fn); + /** + * Check an existing lock file for its validity. + * \a fn is the name of the DATA file that is locked. + * As lockfiles often remain when a program crashes, this function + * checks certain conditions that show that a lockfile is not in + * use anymore, these are: + * ° there is no process with the pid in the lockfile, + * ° the systems boot-time is after the creation of the lockfile. + * The problem is that, if there is a process with the pid we have, + * this does not need to be the process that created the lockfile + * the method returns only false if it is shure that no such process + * exists. + * Returns false if the lockfile exists and is definitely stale or there + * is none, returns true if the lockfile seems to be really valid. + */ + static bool CheckLockFile(const QString& filename); + /** + * The static method CleanLockFiles removes all files in the list + * ::LockFiles when called. + * Thus this function should be installed as a handler for SIGINT, + * SIGQUIT, SIGKILL, SIGTERM and other program abortion signals or + * should be called by the respective handlers. + */ + static void CleanLockFiles(int); + /** + * Lock the current file. + * Locking is done by creating a file \<filename\> lock. + * QConfigDB-objects will reject opening a file for reading and + * writing if a lockfile for the filename exists. + */ + bool lock(); + /** + * Unlock the file. + */ + bool unlock(); + /** + * If \a watch is <TT> true </TT> the object watches its file for changes. + * A timer is started that checks the file age every second and emits + * #fileChanged if it has been overridden meanwhile. + */ + void watch(bool state); + // ---------------------------------------------------------------------------- +protected: + /** + * Transform a given path into a list of strings. + * All internal path handling is done with lists. + */ + list<QCString> stringToKeylist(const QCString&); + /** + * The current filename. + */ + QString filename; + /** + * The current file opening mode. + */ + bool readonly; + /** + * Whether this object locked the file or not. + */ + bool locked; + /** + * The modification time of the last file access. + * Used to recognize file changes, is a null date if the modification time is + * unknown, what usually means that the current file has not been created and + * does not exist by now. + * @see ::storeFileAge + */ + QDateTime *mtime; + /** + * Lock the file. + */ + bool lock(const QString& file); + /** + * Debugging aid, called from REQUIRE and ENSURE macros when the Nana library + * is used. + */ + bool invariant(); + /** + * All created lockfiles are notified in this list. + * The list contains the names of the lockfiles, not of the files itselfes. + */ + static list<QString> LockFiles; + // ---------------------------------------------------------------------------- +public slots: + /** + * Check for file changes. + * This method returns true if the file has been changed on disk + * after the last reading or saving. + */ + bool checkFileChanged(); + // ---------------------------------------------------------------------------- +signals: + /** + * This signal will be send when the database is cleared or reloaded. + * The notification might be needed if pointers or iterators are stored + * outside the database object as they get invalid after reloading. + * The signal hands over its \a this pointer. + */ + virtual void changed(QConfigDB*); + /** + * This signal will notify changes of the database <EM> file </EM>. The file + * will be monitored on disk if #watch has been activated. + */ + virtual void fileChanged(); + // ############################################################################ +}; + +// ----- inline functions: +bool KeyValueMap::insert(const QCString& key, const char* value, bool force) +{ + return insert(key, (QCString)value, force); +} +// ----- + +#endif // ! defined QCONFIGDB_H diff --git a/kab/template.config b/kab/template.config new file mode 100644 index 000000000..c925e407e --- /dev/null +++ b/kab/template.config @@ -0,0 +1,38 @@ +# This is the global template file that is copied to the users kab directory +# when the program is started the first time. +# Site administrators should prepare the interfaces to the mail +# clients according to the settings on this machine to help the users. +[config] + # a list of supported mail clients: + # MailCommand ... the shell command for starting the mailer + # MailParameters ... the parameters for handing over the mail address + # <person> will be replaced by a single address + # <persons> will be replaced by a list of the selected addresses, + # separated by commas + # <subject> will be replaced by a possible subject + [mailer] + [kmail] + MailCommand="kmail" + MailParameters="<person>\e-s\e<subject>\e" + [END] + [END] + # a list of supported talk clients: + # TalkCommand ... the shell command for starting the talk client + # TalkParameters ... the parameters for handing over the talk address + # <person> will be replaced by a single address + [talk] + [ktalk] + TalkCommand="ktalk" + TalkParameters="--autoexit\e<person>\e" + [END] + [END] + # some settings: + SaveOnChange="true" + QueryOnDelete="true" + QueryOnChange="true" + CreateBackup="true" + # the maximum number of files displayed in the "recent" menu: + MaxRecent="5" + # a list of integers with three elements: + KabVersion="1, 0, 0" +[END] diff --git a/kab/template.kab b/kab/template.kab new file mode 100644 index 000000000..d4ad1c1fa --- /dev/null +++ b/kab/template.kab @@ -0,0 +1,12 @@ +# Template file for the KDE addressbook, version 2. +# All new database files kab creates are copies of this file. +[config] + user_headline="(User fields)" + user_1="(User field 1)" + user_2="(User field 2)" + user_3="(User field 3)" + user_4="(User field 4)" +[END] +[entries] +[END] +# to be continued ... |