umbrello API Documentation

codegenerator.cpp

00001 /***************************************************************************
00002  *                                                                         *
00003  *   This program is free software; you can redistribute it and/or modify  *
00004  *   it under the terms of the GNU General Public License as published by  *
00005  *   the Free Software Foundation; either version 2 of the License, or     *
00006  *   (at your option) any later version.                                   *
00007  *                                                                         *
00008  *   copyright (C) 2004-2007                                               *
00009  *   Umbrello UML Modeller Authors <uml-devel@uml.sf.net>                  *
00010  ***************************************************************************/
00011 
00012 /*  This code generated by:
00013  *      Author : thomas
00014  *      Date   : Thu Jun 19 2003
00015  */
00016 
00017 // own header
00018 #include "codegenerator.h"
00019 
00020 // system includes
00021 #include <cstdlib> //to get the user name
00022 
00023 // qt includes
00024 #include <qdatetime.h>
00025 #include <qregexp.h>
00026 #include <qdir.h>
00027 #include <qtextstream.h>
00028 
00029 // kde includes
00030 #include <kdebug.h>
00031 #include <klocale.h>
00032 #include <kmessagebox.h>
00033 #include <kdialogbase.h>
00034 #include <kapplication.h>
00035 
00036 // app includes
00037 #include "dialogs/overwritedialogue.h"
00038 #include "dialogs/codeviewerdialog.h"
00039 #include "codegenerators/simplecodegenerator.h"
00040 #include "attribute.h"
00041 #include "association.h"
00042 #include "classifier.h"
00043 #include "classifiercodedocument.h"
00044 #include "codedocument.h"
00045 #include "codegenerationpolicy.h"
00046 #include "operation.h"
00047 #include "uml.h"
00048 #include "umldoc.h"
00049 #include "umlobject.h"
00050 #include "umlattributelist.h"
00051 #include "umloperationlist.h"
00052 #include "model_utils.h"
00053 
00054 // Constructors/Destructors
00055 //
00056 
00057 CodeGenerator::CodeGenerator ()
00058         : QObject (UMLApp::app()->getDocument())
00059 {
00060     initFields();
00061 }
00062 
00063 // FIX
00064 // hmm. this should be pure virtual so that implemented in sub-class
00065 CodeGenerator::CodeGenerator (QDomElement & element )
00066         : QObject (UMLApp::app()->getDocument()) {
00067     initFields();
00068     loadFromXMI(element); // hmm. cant call this here.. its 'pure' virtual
00069 }
00070 
00071 CodeGenerator::~CodeGenerator ( ) {
00072     // destroy all owned codedocuments
00073     CodeDocument *doc;
00074     for (CodeDocumentListIt it(m_codedocumentVector);
00075                      (doc = it.current()) != NULL; ++it)
00076         delete doc;
00077     m_codedocumentVector.clear();
00078 }
00079 
00080 //
00081 // Methods
00082 //
00083 
00084 
00085 // Accessor methods
00086 //
00087 
00088 QString CodeGenerator::getUniqueID(CodeDocument * codeDoc)
00089 {
00090 
00091     QString id = codeDoc->getID();
00092 
00093     // does this document already exist? then just return its present id
00094     if (!id.isEmpty() && findCodeDocumentByID(id))
00095         return id;
00096 
00097     // approach now differs by whether or not its a classifier code document
00098     ClassifierCodeDocument * classDoc = dynamic_cast<ClassifierCodeDocument*>(codeDoc);
00099     if(classDoc) {
00100         UMLClassifier *c = classDoc->getParentClassifier();
00101         id = ID2STR(c->getID()); // this is supposed to be unique already..
00102     } else {
00103 
00104         QString prefix = "doc";
00105         QString id = prefix + "_0";
00106         int number = lastIDIndex;
00107         for ( ; findCodeDocumentByID(id); number++) {
00108             id = prefix + '_' + QString::number(number);
00109         }
00110         lastIDIndex = number;
00111     }
00112 
00113     return id;
00114 }
00115 
00116 CodeDocument * CodeGenerator::findCodeDocumentByID( const QString &tag ) {
00117     //if we already know to which file this class was written/should be written, just return it.
00118     CodeDocument * doc = (CodeDocument*)NULL;
00119     if((doc = m_codeDocumentDictionary.find(tag)))
00120         return doc;
00121 
00122     return doc;
00123 }
00124 
00125 bool CodeGenerator::addCodeDocument ( CodeDocument * doc )
00126 {
00127     QString tag = doc->getID();
00128 
00129     // assign a tag if one doesn't already exist
00130     if(tag.isEmpty())
00131     {
00132         tag = getUniqueID(doc);
00133         doc->setID(tag);
00134     }
00135 
00136     if(m_codeDocumentDictionary.find(tag))
00137         return false; // return false, we already have some object with this tag in the list
00138     else
00139         m_codeDocumentDictionary.insert(tag, doc);
00140 
00141     m_codedocumentVector.append(doc);
00142     return true;
00143 }
00144 
00148 bool CodeGenerator::removeCodeDocument ( CodeDocument * remove_object ) {
00149     QString tag = remove_object->getID();
00150     if(!(tag.isEmpty()))
00151         m_codeDocumentDictionary.remove(tag);
00152     else
00153         return false;
00154 
00155     m_codedocumentVector.remove(remove_object);
00156     return true;
00157 }
00158 
00164 CodeDocumentList * CodeGenerator::getCodeDocumentList ( ) {
00165     return &m_codedocumentVector;
00166 }
00167 
00168 // the vanilla version
00169 CodeViewerDialog * CodeGenerator::getCodeViewerDialog ( QWidget* parent, CodeDocument *doc,
00170         Settings::CodeViewerState state)
00171 {
00172     return new CodeViewerDialog(parent, doc, state);
00173 }
00174 
00175 // Other methods
00176 //
00177 
00178 void CodeGenerator::loadFromXMI (QDomElement & qElement ) {
00179 
00180     // don't do anything for simple (compatability) code generators
00181     if(dynamic_cast<SimpleCodeGenerator*>(this))
00182         return;
00183 
00184     //now look for our particular child element
00185     QDomNode node = qElement.firstChild();
00186     QDomElement element = node.toElement();
00187     QString langType = Model_Utils::progLangToString( getLanguage() );
00188 
00189     if (qElement.tagName() != "codegenerator"
00190             || qElement.attribute("language", "UNKNOWN") != langType)
00191         return;
00192     // got our code generator element, now load
00193     // codedocuments
00194     QDomNode codeDocNode = qElement.firstChild();
00195     QDomElement codeDocElement = codeDocNode.toElement();
00196     while( !codeDocElement.isNull() ) {
00197 
00198         QString docTag = codeDocElement.tagName();
00199         if( docTag == "codedocument" ||
00200                 docTag == "classifiercodedocument"
00201           ) {
00202             QString id = codeDocElement.attribute( "id", "-1" );
00203             CodeDocument * codeDoc = findCodeDocumentByID(id);
00204             if(codeDoc)
00205                 codeDoc->loadFromXMI(codeDocElement);
00206             else {
00207                 kWarning()<<" loadFromXMI: missing code document w/ id:"<<id<<", plowing ahead with pre-generated one."<<endl;
00208             }
00209         } else
00210             kWarning()<<" loadFromXMI : got strange codegenerator child node:"<<docTag<<", ignoring."<<endl;
00211 
00212         codeDocNode = codeDocElement.nextSibling();
00213         codeDocElement = codeDocNode.toElement();
00214     }
00215 }
00216 
00217 void CodeGenerator::saveToXMI ( QDomDocument & doc, QDomElement & root ) {
00218     QString langType = Model_Utils::progLangToString( getLanguage() );
00219     QDomElement docElement = doc.createElement( "codegenerator" );
00220     docElement.setAttribute("language",langType);
00221 
00222     CodeDocumentList * docList = getCodeDocumentList();
00223     for (CodeDocument * codeDoc = docList->first(); codeDoc; codeDoc= docList->next())
00224         codeDoc->saveToXMI(doc, docElement);
00225 
00226     root.appendChild( docElement );
00227 }
00228 
00244 void CodeGenerator::initFromParentDocument( ) {
00245 
00246     // Walk through the document converting classifiers into
00247     // classifier code documents as needed (e.g only if doesn't exist)
00248     UMLClassifierList concepts = UMLApp::app()->getDocument()->getClassesAndInterfaces();
00249     for (UMLClassifier *c = concepts.first(); c; c = concepts.next())
00250     {
00251 
00252         // Doesn't exist? Then build one.
00253         CodeDocument * codeDoc = findCodeDocumentByClassifier(c);
00254         if (!codeDoc)
00255         {
00256             codeDoc = newClassifierCodeDocument(c);
00257             addCodeDocument(codeDoc); // this will also add a unique tag to the code document
00258         }
00259     }
00260 
00261 }
00262 
00268 void CodeGenerator::syncCodeToDocument ( ) {
00269     for (CodeDocument * doc = m_codedocumentVector.first(); doc; doc=m_codedocumentVector.next())
00270         doc->synchronize();
00271 }
00272 
00273 // in this 'vanilla' version, we only worry about adding classifier
00274 // documents
00275 void CodeGenerator::checkAddUMLObject (UMLObject * obj) {
00276     if (!obj)
00277         return;
00278 
00279     UMLClassifier * c = dynamic_cast<UMLClassifier*>(obj);
00280     if(c) {
00281         CodeDocument * cDoc = newClassifierCodeDocument(c);
00282         addCodeDocument(cDoc);
00283     }
00284 }
00285 
00286 void CodeGenerator::checkRemoveUMLObject (UMLObject * obj)
00287 {
00288 
00289     if (!obj)
00290         return;
00291 
00292     UMLClassifier * c = dynamic_cast<UMLClassifier*>(obj);
00293     if(c) {
00294         ClassifierCodeDocument * cDoc = (ClassifierCodeDocument*) findCodeDocumentByClassifier(c);
00295         if (cDoc)
00296             removeCodeDocument(cDoc);
00297     }
00298 
00299 }
00300 
00305 CodeDocument * CodeGenerator::findCodeDocumentByClassifier ( UMLClassifier * classifier ) {
00306     return findCodeDocumentByID(ID2STR(classifier->getID()));
00307 }
00308 
00309 
00313 void CodeGenerator::writeCodeToFile ( )
00314 {
00315     writeListedCodeDocsToFile(&m_codedocumentVector);
00316 }
00317 
00318 void CodeGenerator::writeCodeToFile ( UMLClassifierList & concepts) {
00319     CodeDocumentList docs;
00320     docs.setAutoDelete(false);
00321 
00322     for (UMLClassifier *concept= concepts.first(); concept; concept= concepts.next())
00323     {
00324         CodeDocument * doc = findCodeDocumentByClassifier(concept);
00325         if(doc)
00326             docs.append(doc);
00327     }
00328 
00329     writeListedCodeDocsToFile(&docs);
00330 }
00331 
00332 // Main method. Will write out passed code documents to file as appropriate.
00333 void CodeGenerator::writeListedCodeDocsToFile ( CodeDocumentList * docs ) {
00334 
00335     // iterate thru all code documents
00336     for (CodeDocument *doc = docs->first(); doc; doc = docs->next())
00337     {
00338 
00339         // we need this so we know when to emit a 'codeGenerated' signal
00340         ClassifierCodeDocument * cdoc = dynamic_cast<ClassifierCodeDocument *>(doc);
00341         bool codeGenSuccess = false;
00342 
00343         // we only write the document, if so requested
00344         if(doc->getWriteOutCode())
00345         {
00346             QString filename = findFileName(doc);
00347             // check that we may open that file for writing
00348             QFile file;
00349             if ( openFile(file,filename) ) {
00350                 QTextStream stream(&file);
00351                 stream<<doc->toString()<<endl;
00352                 file.close();
00353                 codeGenSuccess = true; // we wrote the code OK
00354             } else {
00355                 kWarning() << "Cannot open file :"<<filename<<" for writing " << endl;
00356             }
00357         }
00358 
00359         if(cdoc)
00360             emit codeGenerated(cdoc->getParentClassifier(), codeGenSuccess);
00361 
00362     }
00363 
00364 }
00365 
00370 CodeDocument * CodeGenerator::newCodeDocument ( ) {
00371     CodeDocument * newCodeDoc = new CodeDocument ();
00372     return newCodeDoc;
00373 }
00374 
00380 QString CodeGenerator::getHeadingFile( const QString &file ) {
00381     return UMLApp::app()->getCommonPolicy()->getHeadingFile(file);
00382 }
00383 
00389 QString CodeGenerator::overwritableName(const QString& name, const QString &extension ) {
00390 
00391     CodeGenerationPolicy *pol = UMLApp::app()->getCommonPolicy();
00392     QDir outputDirectory = pol->getOutputDirectory();
00393     QString filename = name + extension;
00394 
00395     if (!outputDirectory.exists(filename)) {
00396         return filename;
00397     }
00398 
00399     int suffix;
00400     OverwriteDialogue overwriteDialog( name, outputDirectory.absPath(),
00401                                          m_applyToAllRemaining, kapp -> mainWidget() );
00402     switch (pol->getOverwritePolicy()) {  //if it exists, check the OverwritePolicy we should use
00403     case CodeGenerationPolicy::Ok:              //ok to overwrite file
00404         filename = name + extension;
00405         break;
00406     case CodeGenerationPolicy::Ask:            //ask if we can overwrite
00407         switch(overwriteDialog.exec()) {
00408         case KDialogBase::Yes:  //overwrite file
00409             if ( overwriteDialog.applyToAllRemaining() ) {
00410                 pol->setOverwritePolicy(CodeGenerationPolicy::Ok);
00411                 filename = name + extension;
00412             } else {
00413                 m_applyToAllRemaining = false;
00414             }
00415             break;
00416         case KDialogBase::No: //generate similar name
00417             suffix = 1;
00418             while (1) {
00419                 filename = name + "__" + QString::number(suffix) + extension;
00420                 if (!outputDirectory.exists(filename))
00421                     break;
00422                 suffix++;
00423             }
00424             if ( overwriteDialog.applyToAllRemaining() ) {
00425                 pol->setOverwritePolicy(CodeGenerationPolicy::Never);
00426             } else {
00427                 m_applyToAllRemaining = false;
00428             }
00429             break;
00430         case KDialogBase::Cancel: //don't output anything
00431             if ( overwriteDialog.applyToAllRemaining() ) {
00432                 pol->setOverwritePolicy(CodeGenerationPolicy::Cancel);
00433             } else {
00434                 m_applyToAllRemaining = false;
00435             }
00436             return QString();
00437             break;
00438         }
00439 
00440         break;
00441     case CodeGenerationPolicy::Never: //generate similar name
00442         suffix = 1;
00443         while (1) {
00444             filename = name + "__" + QString::number(suffix) + extension;
00445             if (!outputDirectory.exists(filename))
00446                 break;
00447             suffix++;
00448         }
00449         break;
00450     case CodeGenerationPolicy::Cancel: //don't output anything
00451         return QString();
00452         break;
00453     }
00454 
00455     return filename;
00456 }
00457 
00458 
00464 bool CodeGenerator::openFile (QFile & file, const QString &fileName ) {
00465     //open files for writing.
00466     if(fileName.isEmpty()) {
00467         kWarning() << "cannot find a file name" << endl;
00468         return false;
00469     } else {
00470         QDir outputDirectory = UMLApp::app()->getCommonPolicy()->getOutputDirectory();
00471         file.setName(outputDirectory.absFilePath(fileName));
00472         if(!file.open(IO_WriteOnly)) {
00473             KMessageBox::sorry(0,i18n("Cannot open file %1 for writing. Please make sure the folder exists and you have permissions to write to it.").arg(file.name()),i18n("Cannot Open File"));
00474             return false;
00475         }
00476         return true;
00477     }
00478 
00479 }
00480 
00481 
00486 QString CodeGenerator::cleanName ( const QString &name ) {
00487     QString retval = name;
00488     retval.replace(QRegExp("\\W"), "_");
00489     return retval;
00490 }
00491 
00492 QString CodeGenerator::findFileName ( CodeDocument * codeDocument ) {
00493 
00494     //else, determine the "natural" file name
00495     QString name;
00496 
00497     // Get the path name
00498     QString path = codeDocument->getPath();
00499 
00500     // if path is given add this as a directory to the file name
00501     if (!path.isEmpty()) {
00502         path.replace(QRegExp("::"), "/"); // Simple hack!
00503         name = path + '/' + codeDocument->getFileName();
00504         path = '/' + path;
00505     } else {
00506         name = codeDocument->getFileName();
00507     }
00508 
00509     // Convert all "::" to "/" : Platform-specific path separator
00510     name.replace(QRegExp("::"), "/"); // Simple hack!
00511 
00512     // if a path name exists check the existence of the path directory
00513     if (!path.isEmpty()) {
00514         QDir outputDirectory = UMLApp::app()->getCommonPolicy()->getOutputDirectory();
00515         QDir pathDir(outputDirectory.absPath() + path);
00516 
00517         // does our complete output directory exist yet? if not, try to create it
00518         if (!pathDir.exists())
00519         {
00520             // ugh. dir separator here is UNIX specific..
00521             QStringList dirs = QStringList::split("/",pathDir.absPath());
00522             QString currentDir = "";
00523 
00524             QStringList::iterator end(dirs.end());
00525             for (QStringList::iterator dir(dirs.begin()); dir != end; ++dir)
00526             {
00527                 currentDir += '/' + *dir;
00528                 if (! (pathDir.exists(currentDir)
00529                         || pathDir.mkdir(currentDir) ) )
00530                 {
00531                     KMessageBox::error(0, i18n("Cannot create the folder:\n") +
00532                                        pathDir.absPath() + i18n("\nPlease check the access rights"),
00533                                        i18n("Cannot Create Folder"));
00534                     return NULL;
00535 
00536                 }
00537             }
00538         }
00539     }
00540 
00541     name.simplifyWhiteSpace();
00542     name.replace(QRegExp(" "),"_");
00543 
00544     return overwritableName( name, codeDocument->getFileExtension() );
00545 }
00546 
00547 void CodeGenerator::findObjectsRelated(UMLClassifier *c, UMLPackageList &cList) {
00548     UMLPackage *temp;
00549     UMLAssociationList associations = c->getAssociations();
00550 
00551     for (UMLAssociation *a = associations.first(); a; a = associations.next()) {
00552         temp = 0;
00553         switch (a->getAssocType()) {
00554         case Uml::at_Generalization:
00555         case Uml::at_Realization:
00556             // only the "b" end is seen by the "a" end, not other way around
00557             {
00558                 UMLObject *objB = a->getObject(Uml::B);
00559                 if (objB != c)
00560                     temp = (UMLPackage*)objB;
00561             }
00562             break;
00563         case Uml::at_Dependency:
00564         case Uml::at_UniAssociation:
00565             {
00566                 UMLObject *objA = a->getObject(Uml::A);
00567                 UMLObject *objB = a->getObject(Uml::B);
00568                 if (objA == c)
00569                     temp = static_cast<UMLPackage*>(objB);
00570             }
00571             break;
00572         case Uml::at_Aggregation:
00573         case Uml::at_Composition:
00574         case Uml::at_Association:
00575             {
00576                 UMLObject *objA = a->getObject(Uml::A);
00577                 UMLObject *objB = a->getObject(Uml::B);
00578                 if (objA == c && objB->getBaseType() != Uml::ot_Datatype)
00579                     temp = static_cast<UMLPackage*>(objB);
00580             }
00581             break;
00582         default: /* all others.. like for state diagrams..we currently don't use */
00583             break;
00584         }
00585 
00586         // now add in list ONLY if its not already there
00587         if(temp  && !cList.containsRef(temp))
00588             cList.append(temp);
00589     }
00590 
00591     //operations
00592     UMLOperationList opl(c->getOpList());
00593     for(UMLOperation *op = opl.first(); op ; op = opl.next()) {
00594         temp =0;
00595         //check return value
00596         temp =(UMLClassifier*) op->getType();
00597         if (temp && temp->getBaseType() != Uml::ot_Datatype && !cList.containsRef(temp))
00598             cList.append(temp);
00599         //check parameters
00600         UMLAttributeList atl = op->getParmList();
00601         for (UMLAttribute *at = atl.first(); at; at = atl.next()) {
00602             temp = (UMLClassifier*)at->getType();
00603             if (temp && temp->getBaseType() != Uml::ot_Datatype && !cList.containsRef(temp))
00604                 cList.append(temp);
00605         }
00606 
00607     }
00608 
00609     //attributes
00610     if (!c->isInterface()) {
00611         UMLAttributeList atl = c->getAttributeList();
00612         for (UMLAttribute *at = atl.first(); at; at = atl.next()) {
00613             temp=0;
00614             temp = (UMLClassifier*) at->getType();
00615             if (temp && temp->getBaseType() != Uml::ot_Datatype && !cList.containsRef(temp))
00616                 cList.append(temp);
00617         }
00618     }
00619 
00620 
00621 }
00622 
00630 QString CodeGenerator::formatDoc(const QString &text, const QString &linePrefix, int lineWidth) {
00631     QString output;
00632 
00633     const QString endLine = UMLApp::app()->getCommonPolicy()->getNewLineEndingChars();
00634     QStringList lines = QStringList::split(endLine, text);
00635     for (QStringList::ConstIterator lit = lines.begin(); lit != lines.end(); ++lit) {
00636         QString input = *lit;
00637         input.remove( QRegExp("\\s+$") );
00638         if (input.length() < (uint)lineWidth) {
00639             output += linePrefix + input + endLine;
00640             continue;
00641         }
00642         int index;
00643         while ((index = input.findRev(" ", lineWidth)) >= 0) {
00644             output += linePrefix + input.left(index) + endLine; // add line
00645             input.remove(0, index + 1); //and remove processed string, including
00646             // white space
00647         }
00648         if (!input.isEmpty())
00649             output += linePrefix + input + endLine;
00650     }
00651     return output;
00652 }
00653 
00654 void CodeGenerator::initFields() {
00655 
00656     m_document = UMLApp::app()->getDocument();
00657     m_codeDocumentDictionary.setAutoDelete(false);
00658     m_codedocumentVector.setAutoDelete(false);
00659     m_applyToAllRemaining = true;
00660     lastIDIndex = 0;
00661 
00662     // initial population of our project generator
00663     // CANT Be done here because we would call pure virtual method
00664     // of newClassifierDocument (bad!).
00665     // We should only call from the child
00666     // initFromParentDocument();
00667 
00668 }
00669 
00670 void CodeGenerator::connect_newcodegen_slots() {
00671     UMLDoc *doc = UMLApp::app()->getDocument();
00672     connect(doc, SIGNAL(sigObjectCreated(UMLObject*)),
00673             this, SLOT(checkAddUMLObject(UMLObject*)));
00674     connect(doc, SIGNAL(sigObjectRemoved(UMLObject*)),
00675             this, SLOT(checkRemoveUMLObject(UMLObject*)));
00676     CodeGenerationPolicy *commonPolicy = UMLApp::app()->getCommonPolicy();
00677     connect(commonPolicy, SIGNAL(modifiedCodeContent()),
00678             this, SLOT(syncCodeToDocument()));
00679 }
00680 
00681 // these are utility methods for accessing the default
00682 // code gen policy object and should go away when we
00683 // finally implement the CodeGenDialog class -b.t.
00684 void CodeGenerator::setForceDoc(bool f) {
00685     UMLApp::app()->getCommonPolicy()->setCodeVerboseDocumentComments(f);
00686 }
00687 
00688 bool CodeGenerator::forceDoc() const {
00689     return UMLApp::app()->getCommonPolicy()->getCodeVerboseDocumentComments();
00690 }
00691 
00692 void CodeGenerator::setForceSections(bool f) {
00693     UMLApp::app()->getCommonPolicy()->setCodeVerboseSectionComments(f);
00694 }
00695 
00696 bool CodeGenerator::forceSections() const {
00697     return UMLApp::app()->getCommonPolicy()->getCodeVerboseSectionComments();
00698 }
00699 
00700 QStringList CodeGenerator::defaultDatatypes()  {
00701     return QStringList();
00702     //empty by default, override in your code generator
00703 }
00704 
00705 bool CodeGenerator::isReservedKeyword(const QString & keyword) {
00706 
00707     const QStringList keywords = reservedKeywords();
00708 
00709     return keywords.contains(keyword);
00710 }
00711 
00712 const QStringList CodeGenerator::reservedKeywords() const {
00713     static QStringList emptyList;
00714 
00715     return emptyList;
00716 }
00717 
00718 void CodeGenerator::createDefaultStereotypes()  {
00719     //empty by default, override in your code generator
00720     //e.g.  m_document->createDefaultStereotypes("constructor");
00721 }
00722 
00723 #include "codegenerator.moc"
KDE Logo
This file is part of the documentation for umbrello Version 3.1.0.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Tue Jun 26 08:07:55 2007 by doxygen 1.4.1 written by Dimitri van Heesch, © 1997-2003