00001 /* This file is part of the KDE libraries
00002 Copyright (C) 2000 Simon Hausmann <hausmann@kde.org>
00003 Copyright (C) 2000 Kurt Granroth <granroth@kde.org>
00004
00005 This library is free software; you can redistribute it and/or
00006 modify it under the terms of the GNU Library General Public
00007 License version 2 as published by the Free Software Foundation.
00008
00009 This library is distributed in the hope that it will be useful,
00010 but WITHOUT ANY WARRANTY; without even the implied warranty of
00011 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
00012 Library General Public License for more details.
00013
00014 You should have received a copy of the GNU Library General Public License
00015 along with this library; see the file COPYING.LIB. If not, write to
00016 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00017 Boston, MA 02110-1301, USA.
00018 */
00019
00020 #include "kxmlguiclient.h"
00021 #include "kxmlguifactory.h"
00022 #include "kxmlguibuilder.h"
00023
00024 #include <qdir.h>
00025 #include <qfile.h>
00026 #include <qdom.h>
00027 #include <qtextstream.h>
00028 #include <qregexp.h>
00029 #include <qguardedptr.h>
00030
00031 #include <kinstance.h>
00032 #include <kstandarddirs.h>
00033 #include <kdebug.h>
00034 #include <kaction.h>
00035 #include <kapplication.h>
00036
00037 #include <assert.h>
00038
00039 class KXMLGUIClientPrivate
00040 {
00041 public:
00042 KXMLGUIClientPrivate()
00043 {
00044 m_instance = KGlobal::instance();
00045 m_parent = 0L;
00046 m_builder = 0L;
00047 m_actionCollection = 0;
00048 }
00049 ~KXMLGUIClientPrivate()
00050 {
00051 }
00052
00053 KInstance *m_instance;
00054
00055 QDomDocument m_doc;
00056 KActionCollection *m_actionCollection;
00057 QDomDocument m_buildDocument;
00058 QGuardedPtr<KXMLGUIFactory> m_factory;
00059 KXMLGUIClient *m_parent;
00060 //QPtrList<KXMLGUIClient> m_supers;
00061 QPtrList<KXMLGUIClient> m_children;
00062 KXMLGUIBuilder *m_builder;
00063 QString m_xmlFile;
00064 QString m_localXMLFile;
00065 };
00066
00067 KXMLGUIClient::KXMLGUIClient()
00068 {
00069 d = new KXMLGUIClientPrivate;
00070 }
00071
00072 KXMLGUIClient::KXMLGUIClient( KXMLGUIClient *parent )
00073 {
00074 d = new KXMLGUIClientPrivate;
00075 parent->insertChildClient( this );
00076 }
00077
00078 KXMLGUIClient::~KXMLGUIClient()
00079 {
00080 if ( d->m_parent )
00081 d->m_parent->removeChildClient( this );
00082
00083 QPtrListIterator<KXMLGUIClient> it( d->m_children );
00084 for ( ; it.current(); ++it ) {
00085 assert( it.current()->d->m_parent == this );
00086 it.current()->d->m_parent = 0;
00087 }
00088
00089 delete d->m_actionCollection;
00090 delete d;
00091 }
00092
00093 KAction *KXMLGUIClient::action( const char *name ) const
00094 {
00095 KAction* act = actionCollection()->action( name );
00096 if ( !act ) {
00097 QPtrListIterator<KXMLGUIClient> childIt( d->m_children );
00098 for (; childIt.current(); ++childIt ) {
00099 act = childIt.current()->actionCollection()->action( name );
00100 if ( act )
00101 break;
00102 }
00103 }
00104 return act;
00105 }
00106
00107 KActionCollection *KXMLGUIClient::actionCollection() const
00108 {
00109 if ( !d->m_actionCollection )
00110 {
00111 d->m_actionCollection = new KActionCollection(
00112 "KXMLGUIClient-KActionCollection", this );
00113 }
00114 return d->m_actionCollection;
00115 }
00116
00117 KAction *KXMLGUIClient::action( const QDomElement &element ) const
00118 {
00119 static const QString &attrName = KGlobal::staticQString( "name" );
00120 return actionCollection()->action( element.attribute( attrName ).latin1() );
00121 }
00122
00123 KInstance *KXMLGUIClient::instance() const
00124 {
00125 return d->m_instance;
00126 }
00127
00128 QDomDocument KXMLGUIClient::domDocument() const
00129 {
00130 return d->m_doc;
00131 }
00132
00133 QString KXMLGUIClient::xmlFile() const
00134 {
00135 return d->m_xmlFile;
00136 }
00137
00138 QString KXMLGUIClient::localXMLFile() const
00139 {
00140 if ( !d->m_localXMLFile.isEmpty() )
00141 return d->m_localXMLFile;
00142
00143 if ( !QDir::isRelativePath(d->m_xmlFile) )
00144 return QString::null; // can't save anything here
00145
00146 return locateLocal( "data", QString::fromLatin1( instance()->instanceName() + '/' ) + d->m_xmlFile );
00147 }
00148
00149
00150 void KXMLGUIClient::reloadXML()
00151 {
00152 QString file( xmlFile() );
00153 if ( !file.isEmpty() )
00154 setXMLFile( file );
00155 }
00156
00157 void KXMLGUIClient::setInstance( KInstance *instance )
00158 {
00159 d->m_instance = instance;
00160 actionCollection()->setInstance( instance );
00161 if ( d->m_builder )
00162 d->m_builder->setBuilderClient( this );
00163 }
00164
00165 void KXMLGUIClient::setXMLFile( const QString& _file, bool merge, bool setXMLDoc )
00166 {
00167 // store our xml file name
00168 if ( !_file.isNull() ) {
00169 d->m_xmlFile = _file;
00170 actionCollection()->setXMLFile( _file );
00171 }
00172
00173 if ( !setXMLDoc )
00174 return;
00175
00176 QString file = _file;
00177 if ( QDir::isRelativePath(file) )
00178 {
00179 QString doc;
00180
00181 QString filter = QString::fromLatin1( instance()->instanceName() + '/' ) + _file;
00182
00183 QStringList allFiles = instance()->dirs()->findAllResources( "data", filter ) + instance()->dirs()->findAllResources( "data", _file );
00184
00185 file = findMostRecentXMLFile( allFiles, doc );
00186
00187 if ( file.isEmpty() )
00188 {
00189 // this might or might not be an error. for the time being,
00190 // let's treat this as if it isn't a problem and the user just
00191 // wants the global standards file
00192
00193 // however if a non-empty file gets passed and we can't find it we might
00194 // inform the developer using some debug output
00195 if ( !_file.isEmpty() )
00196 kdWarning() << "KXMLGUIClient::setXMLFile: cannot find .rc file " << _file << endl;
00197
00198 setXML( QString::null, true );
00199 return;
00200 }
00201 else if ( !doc.isEmpty() )
00202 {
00203 setXML( doc, merge );
00204 return;
00205 }
00206 }
00207
00208 QString xml = KXMLGUIFactory::readConfigFile( file );
00209 setXML( xml, merge );
00210 }
00211
00212 void KXMLGUIClient::setLocalXMLFile( const QString &file )
00213 {
00214 d->m_localXMLFile = file;
00215 }
00216
00217 void KXMLGUIClient::setXML( const QString &document, bool merge )
00218 {
00219 QDomDocument doc;
00220 doc.setContent( document );
00221 setDOMDocument( doc, merge );
00222 }
00223
00224 void KXMLGUIClient::setDOMDocument( const QDomDocument &document, bool merge )
00225 {
00226 if ( merge )
00227 {
00228 QDomElement base = d->m_doc.documentElement();
00229
00230 QDomElement e = document.documentElement();
00231
00232 // merge our original (global) xml with our new one
00233 mergeXML(base, e, actionCollection());
00234
00235 // reassign our pointer as mergeXML might have done something
00236 // strange to it
00237 base = d->m_doc.documentElement();
00238
00239 // we want some sort of failsafe.. just in case
00240 if ( base.isNull() )
00241 d->m_doc = document;
00242 }
00243 else
00244 {
00245 d->m_doc = document;
00246 }
00247
00248 setXMLGUIBuildDocument( QDomDocument() );
00249 }
00250
00251 bool KXMLGUIClient::mergeXML( QDomElement &base, const QDomElement &additive, KActionCollection *actionCollection )
00252 {
00253 static const QString &tagAction = KGlobal::staticQString( "Action" );
00254 static const QString &tagMerge = KGlobal::staticQString( "Merge" );
00255 static const QString &tagSeparator = KGlobal::staticQString( "Separator" );
00256 static const QString &attrName = KGlobal::staticQString( "name" );
00257 static const QString &attrAppend = KGlobal::staticQString( "append" );
00258 static const QString &attrWeakSeparator = KGlobal::staticQString( "weakSeparator" );
00259 static const QString &tagMergeLocal = KGlobal::staticQString( "MergeLocal" );
00260 static const QString &tagText = KGlobal::staticQString( "text" );
00261 static const QString &attrAlreadyVisited = KGlobal::staticQString( "alreadyVisited" );
00262 static const QString &attrNoMerge = KGlobal::staticQString( "noMerge" );
00263 static const QString &attrOne = KGlobal::staticQString( "1" );
00264
00265 // there is a possibility that we don't want to merge in the
00266 // additive.. rather, we might want to *replace* the base with the
00267 // additive. this can be for any container.. either at a file wide
00268 // level or a simple container level. we look for the 'noMerge'
00269 // tag, in any event and just replace the old with the new
00270 if ( additive.attribute(attrNoMerge) == attrOne ) // ### use toInt() instead? (Simon)
00271 {
00272 base.parentNode().replaceChild(additive, base);
00273 return true;
00274 }
00275
00276 QString tag;
00277
00278 // iterate over all elements in the container (of the global DOM tree)
00279 QDomNode n = base.firstChild();
00280 while ( !n.isNull() )
00281 {
00282 QDomElement e = n.toElement();
00283 n = n.nextSibling(); // Advance now so that we can safely delete e
00284 if (e.isNull())
00285 continue;
00286
00287 tag = e.tagName();
00288
00289 // if there's an action tag in the global tree and the action is
00290 // not implemented, then we remove the element
00291 if ( tag == tagAction )
00292 {
00293 QCString name = e.attribute( attrName ).utf8(); // WABA
00294 if ( !actionCollection->action( name ) ||
00295 (kapp && !kapp->authorizeKAction(name)))
00296 {
00297 // remove this child as we aren't using it
00298 base.removeChild( e );
00299 continue;
00300 }
00301 }
00302
00303 // if there's a separator defined in the global tree, then add an
00304 // attribute, specifying that this is a "weak" separator
00305 else if ( tag == tagSeparator )
00306 {
00307 e.setAttribute( attrWeakSeparator, (uint)1 );
00308
00309 // okay, hack time. if the last item was a weak separator OR
00310 // this is the first item in a container, then we nuke the
00311 // current one
00312 QDomElement prev = e.previousSibling().toElement();
00313 if ( prev.isNull() ||
00314 ( prev.tagName() == tagSeparator && !prev.attribute( attrWeakSeparator ).isNull() ) ||
00315 ( prev.tagName() == tagText ) )
00316 {
00317 // the previous element was a weak separator or didn't exist
00318 base.removeChild( e );
00319 continue;
00320 }
00321 }
00322
00323 // the MergeLocal tag lets us specify where non-standard elements
00324 // of the local tree shall be merged in. After inserting the
00325 // elements we delete this element
00326 else if ( tag == tagMergeLocal )
00327 {
00328 QDomNode it = additive.firstChild();
00329 while ( !it.isNull() )
00330 {
00331 QDomElement newChild = it.toElement();
00332 it = it.nextSibling();
00333 if (newChild.isNull() )
00334 continue;
00335
00336 if ( newChild.tagName() == tagText )
00337 continue;
00338
00339 if ( newChild.attribute( attrAlreadyVisited ) == attrOne )
00340 continue;
00341
00342 QString itAppend( newChild.attribute( attrAppend ) );
00343 QString elemName( e.attribute( attrName ) );
00344
00345 if ( ( itAppend.isNull() && elemName.isEmpty() ) ||
00346 ( itAppend == elemName ) )
00347 {
00348 // first, see if this new element matches a standard one in
00349 // the global file. if it does, then we skip it as it will
00350 // be merged in, later
00351 QDomElement matchingElement = findMatchingElement( newChild, base );
00352 if ( matchingElement.isNull() || newChild.tagName() == tagSeparator )
00353 base.insertBefore( newChild, e );
00354 }
00355 }
00356
00357 base.removeChild( e );
00358 continue;
00359 }
00360
00361 // in this last case we check for a separator tag and, if not, we
00362 // can be sure that its a container --> proceed with child nodes
00363 // recursively and delete the just proceeded container item in
00364 // case its empty (if the recursive call returns true)
00365 else if ( tag != tagMerge )
00366 {
00367 // handle the text tag
00368 if ( tag == tagText )
00369 continue;
00370
00371 QDomElement matchingElement = findMatchingElement( e, additive );
00372
00373 if ( !matchingElement.isNull() )
00374 {
00375 matchingElement.setAttribute( attrAlreadyVisited, (uint)1 );
00376
00377 if ( mergeXML( e, matchingElement, actionCollection ) )
00378 {
00379 base.removeChild( e );
00380 continue;
00381 }
00382
00383 // Merge attributes
00384 const QDomNamedNodeMap attribs = matchingElement.attributes();
00385 const uint attribcount = attribs.count();
00386
00387 for(uint i = 0; i < attribcount; ++i)
00388 {
00389 const QDomNode node = attribs.item(i);
00390 e.setAttribute(node.nodeName(), node.nodeValue());
00391 }
00392
00393 continue;
00394 }
00395 else
00396 {
00397 // this is an important case here! We reach this point if the
00398 // "local" tree does not contain a container definition for
00399 // this container. However we have to call mergeXML recursively
00400 // and make it check if there are actions implemented for this
00401 // container. *If* none, then we can remove this container now
00402 if ( mergeXML( e, QDomElement(), actionCollection ) )
00403 base.removeChild( e );
00404 continue;
00405 }
00406 }
00407 }
00408
00409 //here we append all child elements which were not inserted
00410 //previously via the LocalMerge tag
00411 n = additive.firstChild();
00412 while ( !n.isNull() )
00413 {
00414 QDomElement e = n.toElement();
00415 n = n.nextSibling(); // Advance now so that we can safely delete e
00416 if (e.isNull())
00417 continue;
00418
00419 QDomElement matchingElement = findMatchingElement( e, base );
00420
00421 if ( matchingElement.isNull() )
00422 {
00423 base.appendChild( e );
00424 }
00425 }
00426
00427 // do one quick check to make sure that the last element was not
00428 // a weak separator
00429 QDomElement last = base.lastChild().toElement();
00430 if ( (last.tagName() == tagSeparator) && (!last.attribute( attrWeakSeparator ).isNull()) )
00431 {
00432 base.removeChild( last );
00433 }
00434
00435 // now we check if we are empty (in which case we return "true", to
00436 // indicate the caller that it can delete "us" (the base element
00437 // argument of "this" call)
00438 bool deleteMe = true;
00439
00440 n = base.firstChild();
00441 while ( !n.isNull() )
00442 {
00443 QDomElement e = n.toElement();
00444 n = n.nextSibling(); // Advance now so that we can safely delete e
00445 if (e.isNull())
00446 continue;
00447
00448 tag = e.tagName();
00449
00450 if ( tag == tagAction )
00451 {
00452 // if base contains an implemented action, then we must not get
00453 // deleted (note that the actionCollection contains both,
00454 // "global" and "local" actions
00455 if ( actionCollection->action( e.attribute( attrName ).utf8() ) )
00456 {
00457 deleteMe = false;
00458 break;
00459 }
00460 }
00461 else if ( tag == tagSeparator )
00462 {
00463 // if we have a separator which has *not* the weak attribute
00464 // set, then it must be owned by the "local" tree in which case
00465 // we must not get deleted either
00466 QString weakAttr = e.attribute( attrWeakSeparator );
00467 if ( weakAttr.isEmpty() || weakAttr.toInt() != 1 )
00468 {
00469 deleteMe = false;
00470 break;
00471 }
00472 }
00473
00474 // in case of a merge tag we have unlimited lives, too ;-)
00475 else if ( tag == tagMerge )
00476 {
00477 // deleteMe = false;
00478 // break;
00479 continue;
00480 }
00481
00482 // a text tag is NOT enough to spare this container
00483 else if ( tag == tagText )
00484 {
00485 continue;
00486 }
00487
00488 // what's left are non-empty containers! *don't* delete us in this
00489 // case (at this position we can be *sure* that the container is
00490 // *not* empty, as the recursive call for it was in the first loop
00491 // which deleted the element in case the call returned "true"
00492 else
00493 {
00494 deleteMe = false;
00495 break;
00496 }
00497 }
00498
00499 return deleteMe;
00500 }
00501
00502 QDomElement KXMLGUIClient::findMatchingElement( const QDomElement &base, const QDomElement &additive )
00503 {
00504 static const QString &tagAction = KGlobal::staticQString( "Action" );
00505 static const QString &tagMergeLocal = KGlobal::staticQString( "MergeLocal" );
00506 static const QString &attrName = KGlobal::staticQString( "name" );
00507
00508 QDomNode n = additive.firstChild();
00509 while ( !n.isNull() )
00510 {
00511 QDomElement e = n.toElement();
00512 n = n.nextSibling(); // Advance now so that we can safely delete e
00513 if (e.isNull())
00514 continue;
00515
00516 // skip all action and merge tags as we will never use them
00517 if ( ( e.tagName() == tagAction ) || ( e.tagName() == tagMergeLocal ) )
00518 {
00519 continue;
00520 }
00521
00522 // now see if our tags are equivalent
00523 if ( ( e.tagName() == base.tagName() ) &&
00524 ( e.attribute( attrName ) == base.attribute( attrName ) ) )
00525 {
00526 return e;
00527 }
00528 }
00529
00530 // nope, return a (now) null element
00531 return QDomElement();
00532 }
00533
00534 void KXMLGUIClient::conserveMemory()
00535 {
00536 d->m_doc = QDomDocument();
00537 d->m_buildDocument = QDomDocument();
00538 }
00539
00540 void KXMLGUIClient::setXMLGUIBuildDocument( const QDomDocument &doc )
00541 {
00542 d->m_buildDocument = doc;
00543 }
00544
00545 QDomDocument KXMLGUIClient::xmlguiBuildDocument() const
00546 {
00547 return d->m_buildDocument;
00548 }
00549
00550 void KXMLGUIClient::setFactory( KXMLGUIFactory *factory )
00551 {
00552 d->m_factory = factory;
00553 }
00554
00555 KXMLGUIFactory *KXMLGUIClient::factory() const
00556 {
00557 return d->m_factory;
00558 }
00559
00560 KXMLGUIClient *KXMLGUIClient::parentClient() const
00561 {
00562 return d->m_parent;
00563 }
00564
00565 void KXMLGUIClient::insertChildClient( KXMLGUIClient *child )
00566 {
00567 if ( child->d->m_parent )
00568 child->d->m_parent->removeChildClient( child );
00569 d->m_children.append( child );
00570 child->d->m_parent = this;
00571 }
00572
00573 void KXMLGUIClient::removeChildClient( KXMLGUIClient *child )
00574 {
00575 assert( d->m_children.containsRef( child ) );
00576 d->m_children.removeRef( child );
00577 child->d->m_parent = 0;
00578 }
00579
00580 /*bool KXMLGUIClient::addSuperClient( KXMLGUIClient *super )
00581 {
00582 if ( d->m_supers.contains( super ) )
00583 return false;
00584 d->m_supers.append( super );
00585 return true;
00586 }*/
00587
00588 const QPtrList<KXMLGUIClient> *KXMLGUIClient::childClients()
00589 {
00590 return &d->m_children;
00591 }
00592
00593 void KXMLGUIClient::setClientBuilder( KXMLGUIBuilder *builder )
00594 {
00595 d->m_builder = builder;
00596 if ( builder )
00597 builder->setBuilderInstance( instance() );
00598 }
00599
00600 KXMLGUIBuilder *KXMLGUIClient::clientBuilder() const
00601 {
00602 return d->m_builder;
00603 }
00604
00605 void KXMLGUIClient::plugActionList( const QString &name, const QPtrList<KAction> &actionList )
00606 {
00607 if ( !d->m_factory )
00608 return;
00609
00610 d->m_factory->plugActionList( this, name, actionList );
00611 }
00612
00613 void KXMLGUIClient::unplugActionList( const QString &name )
00614 {
00615 if ( !d->m_factory )
00616 return;
00617
00618 d->m_factory->unplugActionList( this, name );
00619 }
00620
00621 QString KXMLGUIClient::findMostRecentXMLFile( const QStringList &files, QString &doc )
00622 {
00623
00624 QValueList<DocStruct> allDocuments;
00625
00626 QStringList::ConstIterator it = files.begin();
00627 QStringList::ConstIterator end = files.end();
00628 for (; it != end; ++it )
00629 {
00630 //kdDebug() << "KXMLGUIClient::findMostRecentXMLFile " << *it << endl;
00631 QString data = KXMLGUIFactory::readConfigFile( *it );
00632 DocStruct d;
00633 d.file = *it;
00634 d.data = data;
00635 allDocuments.append( d );
00636 }
00637
00638 QValueList<DocStruct>::Iterator best = allDocuments.end();
00639 uint bestVersion = 0;
00640
00641 QValueList<DocStruct>::Iterator docIt = allDocuments.begin();
00642 QValueList<DocStruct>::Iterator docEnd = allDocuments.end();
00643 for (; docIt != docEnd; ++docIt )
00644 {
00645 QString versionStr = findVersionNumber( (*docIt).data );
00646 if ( versionStr.isEmpty() )
00647 continue;
00648
00649 bool ok = false;
00650 uint version = versionStr.toUInt( &ok );
00651 if ( !ok )
00652 continue;
00653 //kdDebug() << "FOUND VERSION " << version << endl;
00654
00655 if ( version > bestVersion )
00656 {
00657 best = docIt;
00658 //kdDebug() << "best version is now " << version << endl;
00659 bestVersion = version;
00660 }
00661 }
00662
00663 if ( best != docEnd )
00664 {
00665 if ( best != allDocuments.begin() )
00666 {
00667 QValueList<DocStruct>::Iterator local = allDocuments.begin();
00668
00669 // load the local document and extract the action properties
00670 QDomDocument document;
00671 document.setContent( (*local).data );
00672
00673 ActionPropertiesMap properties = extractActionProperties( document );
00674
00675 // in case the document has a ActionProperties section
00676 // we must not delete it but copy over the global doc
00677 // to the local and insert the ActionProperties section
00678 if ( !properties.isEmpty() )
00679 {
00680 // now load the global one with the higher version number
00681 // into memory
00682 document.setContent( (*best).data );
00683 // and store the properties in there
00684 storeActionProperties( document, properties );
00685
00686 (*local).data = document.toString();
00687 // make sure we pick up the new local doc, when we return later
00688 best = local;
00689
00690 // write out the new version of the local document
00691 QFile f( (*local).file );
00692 if ( f.open( IO_WriteOnly ) )
00693 {
00694 QCString utf8data = (*local).data.utf8();
00695 f.writeBlock( utf8data.data(), utf8data.length() );
00696 f.close();
00697 }
00698 }
00699 else
00700 {
00701 QString f = (*local).file;
00702 QString backup = f + QString::fromLatin1( ".backup" );
00703 QDir dir;
00704 dir.rename( f, backup );
00705 }
00706 }
00707 doc = (*best).data;
00708 return (*best).file;
00709 }
00710 else if ( files.count() > 0 )
00711 {
00712 //kdDebug() << "returning first one..." << endl;
00713 doc = (*allDocuments.begin()).data;
00714 return (*allDocuments.begin()).file;
00715 }
00716
00717 return QString::null;
00718 }
00719
00720
00721
00722 QString KXMLGUIClient::findVersionNumber( const QString &xml )
00723 {
00724 enum { ST_START, ST_AFTER_OPEN, ST_AFTER_GUI,
00725 ST_EXPECT_VERSION, ST_VERSION_NUM} state = ST_START;
00726 for (unsigned int pos = 0; pos < xml.length(); pos++)
00727 {
00728 switch (state)
00729 {
00730 case ST_START:
00731 if (xml[pos] == '<')
00732 state = ST_AFTER_OPEN;
00733 break;
00734 case ST_AFTER_OPEN:
00735 {
00736 //Jump to gui..
00737 int guipos = xml.find("gui", pos, false /*case-insensitive*/);
00738 if (guipos == -1)
00739 return QString::null; //Reject
00740
00741 pos = guipos + 2; //Position at i, so we're moved ahead to the next character by the ++;
00742 state = ST_AFTER_GUI;
00743 break;
00744 }
00745 case ST_AFTER_GUI:
00746 state = ST_EXPECT_VERSION;
00747 break;
00748 case ST_EXPECT_VERSION:
00749 {
00750 int verpos = xml.find("version=\"", pos, false /*case-insensitive*/);
00751 if (verpos == -1)
00752 return QString::null; //Reject
00753
00754 pos = verpos + 8; //v = 0, e = +1, r = +2, s = +3 , i = +4, o = +5, n = +6, = = +7, " = + 8
00755 state = ST_VERSION_NUM;
00756 break;
00757 }
00758 case ST_VERSION_NUM:
00759 {
00760 unsigned int endpos;
00761 for (endpos = pos; endpos < xml.length(); endpos++)
00762 {
00763 if (xml[endpos].unicode() >= '0' && xml[endpos].unicode() <= '9')
00764 continue; //Number..
00765 if (xml[endpos].unicode() == '"') //End of parameter
00766 break;
00767 else //This shouldn't be here..
00768 {
00769 endpos = xml.length();
00770 }
00771 }
00772
00773 if (endpos != pos && endpos < xml.length() )
00774 {
00775 QString matchCandidate = xml.mid(pos, endpos - pos); //Don't include " ".
00776 return matchCandidate;
00777 }
00778
00779 state = ST_EXPECT_VERSION; //Try to match a well-formed version..
00780 break;
00781 } //case..
00782 } //switch
00783 } //for
00784
00785 return QString::null;
00786 }
00787
00788 KXMLGUIClient::ActionPropertiesMap KXMLGUIClient::extractActionProperties( const QDomDocument &doc )
00789 {
00790 ActionPropertiesMap properties;
00791
00792 QDomElement actionPropElement = doc.documentElement().namedItem( "ActionProperties" ).toElement();
00793
00794 if ( actionPropElement.isNull() )
00795 return properties;
00796
00797 QDomNode n = actionPropElement.firstChild();
00798 while(!n.isNull())
00799 {
00800 QDomElement e = n.toElement();
00801 n = n.nextSibling(); // Advance now so that we can safely delete e
00802 if ( e.isNull() )
00803 continue;
00804
00805 if ( e.tagName().lower() != "action" )
00806 continue;
00807
00808 QString actionName = e.attribute( "name" );
00809
00810 if ( actionName.isEmpty() )
00811 continue;
00812
00813 QMap<QString, QMap<QString, QString> >::Iterator propIt = properties.find( actionName );
00814 if ( propIt == properties.end() )
00815 propIt = properties.insert( actionName, QMap<QString, QString>() );
00816
00817 const QDomNamedNodeMap attributes = e.attributes();
00818 const uint attributeslength = attributes.length();
00819
00820 for ( uint i = 0; i < attributeslength; ++i )
00821 {
00822 const QDomAttr attr = attributes.item( i ).toAttr();
00823
00824 if ( attr.isNull() )
00825 continue;
00826
00827 const QString name = attr.name();
00828
00829 if ( name == "name" || name.isEmpty() )
00830 continue;
00831
00832 (*propIt)[ name ] = attr.value();
00833 }
00834
00835 }
00836
00837 return properties;
00838 }
00839
00840 void KXMLGUIClient::storeActionProperties( QDomDocument &doc, const ActionPropertiesMap &properties )
00841 {
00842 QDomElement actionPropElement = doc.documentElement().namedItem( "ActionProperties" ).toElement();
00843
00844 if ( actionPropElement.isNull() )
00845 {
00846 actionPropElement = doc.createElement( "ActionProperties" );
00847 doc.documentElement().appendChild( actionPropElement );
00848 }
00849
00850 while ( !actionPropElement.firstChild().isNull() )
00851 actionPropElement.removeChild( actionPropElement.firstChild() );
00852
00853 ActionPropertiesMap::ConstIterator it = properties.begin();
00854 ActionPropertiesMap::ConstIterator end = properties.end();
00855 for (; it != end; ++it )
00856 {
00857 QDomElement action = doc.createElement( "Action" );
00858 action.setAttribute( "name", it.key() );
00859 actionPropElement.appendChild( action );
00860
00861 QMap<QString, QString> attributes = (*it);
00862 QMap<QString, QString>::ConstIterator attrIt = attributes.begin();
00863 QMap<QString, QString>::ConstIterator attrEnd = attributes.end();
00864 for (; attrIt != attrEnd; ++attrIt )
00865 action.setAttribute( attrIt.key(), attrIt.data() );
00866 }
00867 }
00868
00869 void KXMLGUIClient::addStateActionEnabled(const QString& state,
00870 const QString& action)
00871 {
00872 StateChange stateChange = getActionsToChangeForState(state);
00873
00874 stateChange.actionsToEnable.append( action );
00875 //kdDebug() << "KXMLGUIClient::addStateActionEnabled( " << state << ", " << action << ")" << endl;
00876
00877 m_actionsStateMap.replace( state, stateChange );
00878 }
00879
00880
00881 void KXMLGUIClient::addStateActionDisabled(const QString& state,
00882 const QString& action)
00883 {
00884 StateChange stateChange = getActionsToChangeForState(state);
00885
00886 stateChange.actionsToDisable.append( action );
00887 //kdDebug() << "KXMLGUIClient::addStateActionDisabled( " << state << ", " << action << ")" << endl;
00888
00889 m_actionsStateMap.replace( state, stateChange );
00890 }
00891
00892
00893 KXMLGUIClient::StateChange KXMLGUIClient::getActionsToChangeForState(const QString& state)
00894 {
00895 return m_actionsStateMap[state];
00896 }
00897
00898
00899 void KXMLGUIClient::stateChanged(const QString &newstate, KXMLGUIClient::ReverseStateChange reverse)
00900 {
00901 StateChange stateChange = getActionsToChangeForState(newstate);
00902
00903 bool setTrue = (reverse == StateNoReverse);
00904 bool setFalse = !setTrue;
00905
00906 // Enable actions which need to be enabled...
00907 //
00908 for ( QStringList::Iterator it = stateChange.actionsToEnable.begin();
00909 it != stateChange.actionsToEnable.end(); ++it ) {
00910
00911 KAction *action = actionCollection()->action((*it).latin1());
00912 if (action) action->setEnabled(setTrue);
00913 }
00914
00915 // and disable actions which need to be disabled...
00916 //
00917 for ( QStringList::Iterator it = stateChange.actionsToDisable.begin();
00918 it != stateChange.actionsToDisable.end(); ++it ) {
00919
00920 KAction *action = actionCollection()->action((*it).latin1());
00921 if (action) action->setEnabled(setFalse);
00922 }
00923
00924 }
00925
00926 void KXMLGUIClient::beginXMLPlug( QWidget *w )
00927 {
00928 actionCollection()->beginXMLPlug( w );
00929 QPtrListIterator<KXMLGUIClient> childIt( d->m_children );
00930 for (; childIt.current(); ++childIt )
00931 childIt.current()->actionCollection()->beginXMLPlug( w );
00932 }
00933
00934 void KXMLGUIClient::endXMLPlug()
00935 {
00936 actionCollection()->endXMLPlug();
00937 QPtrListIterator<KXMLGUIClient> childIt( d->m_children );
00938 for (; childIt.current(); ++childIt )
00939 childIt.current()->actionCollection()->endXMLPlug();
00940 }
00941
00942 void KXMLGUIClient::prepareXMLUnplug( QWidget * )
00943 {
00944 actionCollection()->prepareXMLUnplug();
00945 QPtrListIterator<KXMLGUIClient> childIt( d->m_children );
00946 for (; childIt.current(); ++childIt )
00947 childIt.current()->actionCollection()->prepareXMLUnplug();
00948 }
00949
00950 void KXMLGUIClient::virtual_hook( int, void* )
00951 { /*BASE::virtual_hook( id, data );*/ }