00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020 #include "kxmlguiclient.h"
00021 #include "kxmlguiversionhandler_p.h"
00022 #include "kxmlguifactory.h"
00023 #include "kxmlguibuilder.h"
00024
00025 #include <QtCore/QDir>
00026 #include <QtCore/QFile>
00027 #include <QtXml/QDomDocument>
00028 #include <QtCore/QTextIStream>
00029 #include <QtCore/QRegExp>
00030 #include <QtCore/QPointer>
00031
00032 #include <kcomponentdata.h>
00033 #include <kstandarddirs.h>
00034 #include <kdebug.h>
00035 #include <kauthorized.h>
00036
00037 #include "kaction.h"
00038 #include "kactioncollection.h"
00039
00040 #include <assert.h>
00041
00042 class KXMLGUIClientPrivate
00043 {
00044 public:
00045 KXMLGUIClientPrivate()
00046 {
00047 m_componentData = KGlobal::mainComponent();
00048 m_parent = 0L;
00049 m_builder = 0L;
00050 m_actionCollection = 0;
00051 }
00052 ~KXMLGUIClientPrivate()
00053 {
00054 }
00055
00056 bool mergeXML( QDomElement &base, QDomElement &additive,
00057 KActionCollection *actionCollection );
00058
00059 QDomElement findMatchingElement( const QDomElement &base,
00060 const QDomElement &additive );
00061
00062 KComponentData m_componentData;
00063
00064 QDomDocument m_doc;
00065 KActionCollection *m_actionCollection;
00066 QDomDocument m_buildDocument;
00067 QPointer<KXMLGUIFactory> m_factory;
00068 KXMLGUIClient *m_parent;
00069
00070 QList<KXMLGUIClient*> m_children;
00071 KXMLGUIBuilder *m_builder;
00072 QString m_xmlFile;
00073 QString m_localXMLFile;
00074
00075
00076 QMap<QString,KXMLGUIClient::StateChange> m_actionsStateMap;
00077 };
00078
00079
00080 KXMLGUIClient::KXMLGUIClient()
00081 : d( new KXMLGUIClientPrivate )
00082 {
00083 }
00084
00085 KXMLGUIClient::KXMLGUIClient( KXMLGUIClient *parent )
00086 : d( new KXMLGUIClientPrivate )
00087 {
00088 parent->insertChildClient( this );
00089 }
00090
00091 KXMLGUIClient::~KXMLGUIClient()
00092 {
00093 if ( d->m_parent ) {
00094 d->m_parent->removeChildClient( this );
00095 }
00096
00097 if ( d->m_factory ) {
00098 d->m_factory->forgetClient(this);
00099 }
00100
00101 foreach (KXMLGUIClient* client, d->m_children) {
00102 assert( client->d->m_parent == this );
00103 if (d->m_factory)
00104 d->m_factory->forgetClient(client);
00105 client->d->m_parent = 0;
00106 }
00107
00108 delete d->m_actionCollection;
00109 delete d;
00110 }
00111
00112 QAction *KXMLGUIClient::action( const char *name ) const
00113 {
00114 QAction* act = actionCollection()->action( name );
00115 if ( !act ) {
00116 foreach (KXMLGUIClient* client, d->m_children) {
00117 act = client->actionCollection()->action( name );
00118 if ( act )
00119 break;
00120 }
00121 }
00122 return act;
00123 }
00124
00125 KActionCollection *KXMLGUIClient::actionCollection() const
00126 {
00127 if ( !d->m_actionCollection )
00128 {
00129 d->m_actionCollection = new KActionCollection( this );
00130 d->m_actionCollection->setObjectName( "KXMLGUIClient-KActionCollection" );
00131 }
00132 return d->m_actionCollection;
00133 }
00134
00135 QAction *KXMLGUIClient::action( const QDomElement &element ) const
00136 {
00137 static const QString &attrName = KGlobal::staticQString( "name" );
00138 return actionCollection()->action( qPrintable(element.attribute( attrName )) );
00139 }
00140
00141 KComponentData KXMLGUIClient::componentData() const
00142 {
00143 return d->m_componentData;
00144 }
00145
00146 QDomDocument KXMLGUIClient::domDocument() const
00147 {
00148 return d->m_doc;
00149 }
00150
00151 QString KXMLGUIClient::xmlFile() const
00152 {
00153 return d->m_xmlFile;
00154 }
00155
00156 QString KXMLGUIClient::localXMLFile() const
00157 {
00158 if ( !d->m_localXMLFile.isEmpty() )
00159 return d->m_localXMLFile;
00160
00161 if ( !QDir::isRelativePath(d->m_xmlFile) )
00162 return QString();
00163
00164 return KStandardDirs::locateLocal( "data", componentData().componentName() + '/' + d->m_xmlFile );
00165 }
00166
00167
00168 void KXMLGUIClient::reloadXML()
00169 {
00170 QString file( xmlFile() );
00171 if ( !file.isEmpty() )
00172 setXMLFile( file );
00173 }
00174
00175 void KXMLGUIClient::setComponentData(const KComponentData &componentData)
00176 {
00177 d->m_componentData = componentData;
00178 actionCollection()->setComponentData( componentData );
00179 if ( d->m_builder )
00180 d->m_builder->setBuilderClient( this );
00181 }
00182
00183 void KXMLGUIClient::setXMLFile( const QString& _file, bool merge, bool setXMLDoc )
00184 {
00185
00186 if ( !_file.isNull() )
00187 d->m_xmlFile = _file;
00188
00189 if ( !setXMLDoc )
00190 return;
00191
00192 QString file = _file;
00193 if ( QDir::isRelativePath(file) )
00194 {
00195 QString doc;
00196
00197 const QString filter = componentData().componentName() + '/' + _file;
00198 const QStringList allFiles = componentData().dirs()->findAllResources("data", filter) +
00199 componentData().dirs()->findAllResources("data", _file);
00200
00201 if (!allFiles.isEmpty())
00202 file = findMostRecentXMLFile(allFiles, doc);
00203
00204 if ( file.isEmpty() )
00205 {
00206
00207
00208
00209
00210
00211
00212 if ( !_file.isEmpty() )
00213 kWarning() << "cannot find .rc file" << _file << "in" << filter;
00214
00215 setXML( QString(), true );
00216 return;
00217 }
00218 else if ( !doc.isEmpty() )
00219 {
00220 setXML( doc, merge );
00221 return;
00222 }
00223 }
00224
00225 QString xml = KXMLGUIFactory::readConfigFile( file );
00226 setXML( xml, merge );
00227 }
00228
00229 void KXMLGUIClient::setLocalXMLFile( const QString &file )
00230 {
00231 d->m_localXMLFile = file;
00232 }
00233
00234 void KXMLGUIClient::setXML( const QString &document, bool merge )
00235 {
00236 QDomDocument doc;
00237 QString errorMsg;
00238 int errorLine, errorColumn;
00239
00240
00241 bool result = document.isEmpty() || doc.setContent(document, &errorMsg, &errorLine, &errorColumn);
00242 if ( result ) {
00243 setDOMDocument( doc, merge );
00244 } else {
00245 #ifdef NDEBUG
00246 kError(240) << "Error parsing XML document:" << errorMsg << "at line" << errorLine << "column" << errorColumn;
00247 setDOMDocument(QDomDocument(), merge);
00248 #else
00249 kFatal() << "Error parsing XML document:" << errorMsg << "at line" << errorLine << "column" << errorColumn;
00250 #endif
00251 }
00252 }
00253
00254 void KXMLGUIClient::setDOMDocument( const QDomDocument &document, bool merge )
00255 {
00256 if ( merge && !d->m_doc.isNull() )
00257 {
00258 QDomElement base = d->m_doc.documentElement();
00259
00260 QDomElement e = document.documentElement();
00261
00262
00263 d->mergeXML(base, e, actionCollection());
00264
00265
00266
00267 base = d->m_doc.documentElement();
00268
00269
00270
00271
00272 if ( base.isNull() )
00273 d->m_doc = document;
00274 }
00275 else
00276 {
00277 d->m_doc = document;
00278 }
00279
00280 setXMLGUIBuildDocument( QDomDocument() );
00281 }
00282
00283
00284 static bool equalstr(const QString& a, const QString& b) {
00285 return a.compare(b, Qt::CaseInsensitive) == 0;
00286 }
00287
00288 bool KXMLGUIClientPrivate::mergeXML( QDomElement &base, QDomElement &additive, KActionCollection *actionCollection )
00289 {
00290 static const QString &tagAction = KGlobal::staticQString( "Action" );
00291 static const QString &tagMerge = KGlobal::staticQString( "Merge" );
00292 static const QString &tagSeparator = KGlobal::staticQString( "Separator" );
00293 static const QString &attrName = KGlobal::staticQString( "name" );
00294 static const QString &attrAppend = KGlobal::staticQString( "append" );
00295 static const QString &attrWeakSeparator = KGlobal::staticQString( "weakSeparator" );
00296 static const QString &tagMergeLocal = KGlobal::staticQString( "MergeLocal" );
00297 static const QString &tagText = KGlobal::staticQString( "text" );
00298 static const QString &attrAlreadyVisited = KGlobal::staticQString( "alreadyVisited" );
00299 static const QString &attrNoMerge = KGlobal::staticQString( "noMerge" );
00300 static const QString &attrOne = KGlobal::staticQString( "1" );
00301
00302
00303
00304
00305
00306
00307 if ( additive.attribute(attrNoMerge) == attrOne )
00308 {
00309 base.parentNode().replaceChild(additive, base);
00310 return true;
00311 } else {
00312
00313 QDomNode n = base.firstChild();
00314 while ( !n.isNull() )
00315 {
00316 QDomElement e = n.toElement();
00317 n = n.nextSibling();
00318 if (e.isNull())
00319 continue;
00320
00321 const QString tag = e.tagName();
00322
00323
00324
00325 if (equalstr(tag, tagAction)) {
00326 const QString name = e.attribute(attrName);
00327 if (!actionCollection->action(name) ||
00328 !KAuthorized::authorizeKAction(name))
00329 {
00330
00331 base.removeChild( e );
00332 continue;
00333 }
00334 }
00335
00336
00337
00338 else if (equalstr(tag, tagSeparator)) {
00339 e.setAttribute( attrWeakSeparator, (uint)1 );
00340
00341
00342
00343
00344 QDomElement prev = e.previousSibling().toElement();
00345 if (prev.isNull() ||
00346 (equalstr(prev.tagName(), tagSeparator) && !prev.attribute(attrWeakSeparator).isNull() ) ||
00347 (equalstr(prev.tagName(), tagText))) {
00348
00349 base.removeChild( e );
00350 continue;
00351 }
00352 }
00353
00354
00355
00356
00357 else if (equalstr(tag, tagMergeLocal)) {
00358 QDomNode it = additive.firstChild();
00359 while ( !it.isNull() )
00360 {
00361 QDomElement newChild = it.toElement();
00362 it = it.nextSibling();
00363 if (newChild.isNull() )
00364 continue;
00365
00366 if (equalstr(newChild.tagName(), tagText))
00367 continue;
00368
00369 if ( newChild.attribute( attrAlreadyVisited ) == attrOne )
00370 continue;
00371
00372 QString itAppend( newChild.attribute( attrAppend ) );
00373 QString elemName( e.attribute( attrName ) );
00374
00375 if ( ( itAppend.isNull() && elemName.isEmpty() ) ||
00376 ( itAppend == elemName ) )
00377 {
00378
00379
00380
00381 QDomElement matchingElement = findMatchingElement( newChild, base );
00382 if (matchingElement.isNull() || equalstr(newChild.tagName(), tagSeparator))
00383 base.insertBefore( newChild, e );
00384 }
00385 }
00386
00387 base.removeChild( e );
00388 continue;
00389 }
00390
00391 else if (equalstr(tag, tagText)) {
00392 continue;
00393 }
00394 else if (equalstr(tag, tagMerge)) {
00395 continue;
00396 }
00397
00398
00399
00400
00401
00402 else {
00403 QDomElement matchingElement = findMatchingElement( e, additive );
00404 if ( !matchingElement.isNull() )
00405 {
00406 matchingElement.setAttribute( attrAlreadyVisited, (uint)1 );
00407
00408 if ( mergeXML( e, matchingElement, actionCollection ) )
00409 {
00410 base.removeChild( e );
00411 additive.removeChild(matchingElement);
00412 continue;
00413 }
00414
00415
00416 const QDomNamedNodeMap attribs = matchingElement.attributes();
00417 const uint attribcount = attribs.count();
00418
00419 for(uint i = 0; i < attribcount; ++i)
00420 {
00421 const QDomNode node = attribs.item(i);
00422 e.setAttribute(node.nodeName(), node.nodeValue());
00423 }
00424
00425 continue;
00426 }
00427 else
00428 {
00429
00430
00431
00432
00433
00434 QDomElement dummy;
00435 if ( mergeXML( e, dummy, actionCollection ) )
00436 base.removeChild( e );
00437 continue;
00438 }
00439 }
00440 }
00441
00442
00443
00444 n = additive.firstChild();
00445 while ( !n.isNull() )
00446 {
00447 QDomElement e = n.toElement();
00448 n = n.nextSibling();
00449 if (e.isNull())
00450 continue;
00451
00452 QDomElement matchingElement = findMatchingElement( e, base );
00453
00454 if ( matchingElement.isNull() )
00455 {
00456 base.appendChild( e );
00457 }
00458 }
00459
00460
00461
00462 QDomElement last = base.lastChild().toElement();
00463 if (equalstr(last.tagName(), tagSeparator) &&
00464 (!last.attribute(attrWeakSeparator).isNull())) {
00465 base.removeChild( last );
00466 }
00467 }
00468
00469
00470
00471
00472 bool deleteMe = true;
00473
00474 QDomNode n = base.firstChild();
00475 while ( !n.isNull() )
00476 {
00477 QDomElement e = n.toElement();
00478 n = n.nextSibling();
00479 if (e.isNull())
00480 continue;
00481
00482 const QString tag = e.tagName();
00483
00484 if (equalstr(tag, tagAction)) {
00485
00486
00487
00488 if (actionCollection->action(e.attribute(attrName))) {
00489 deleteMe = false;
00490 break;
00491 }
00492 }
00493 else if (equalstr(tag, tagSeparator)) {
00494
00495
00496
00497 QString weakAttr = e.attribute( attrWeakSeparator );
00498 if ( weakAttr.isEmpty() || weakAttr.toInt() != 1 )
00499 {
00500 deleteMe = false;
00501 break;
00502 }
00503 }
00504
00505 else if (equalstr(tag, tagMerge)) {
00506 continue;
00507 }
00508
00509
00510 else if (equalstr(tag, tagText)) {
00511 continue;
00512 }
00513
00514
00515
00516
00517
00518 else
00519 {
00520 deleteMe = false;
00521 break;
00522 }
00523 }
00524
00525 return deleteMe;
00526 }
00527
00528 QDomElement KXMLGUIClientPrivate::findMatchingElement( const QDomElement &base, const QDomElement &additive )
00529 {
00530 static const QString &tagAction = KGlobal::staticQString( "Action" );
00531 static const QString &tagMergeLocal = KGlobal::staticQString( "MergeLocal" );
00532 static const QString &attrName = KGlobal::staticQString( "name" );
00533
00534 QDomNode n = additive.firstChild();
00535 while ( !n.isNull() )
00536 {
00537 QDomElement e = n.toElement();
00538 n = n.nextSibling();
00539 if (e.isNull())
00540 continue;
00541
00542 const QString tag = e.tagName();
00543
00544 if (equalstr(tag, tagAction)
00545 || equalstr(tag, tagMergeLocal)) {
00546 continue;
00547 }
00548
00549
00550 if (equalstr(tag, base.tagName()) &&
00551 e.attribute(attrName) == base.attribute(attrName)) {
00552 return e;
00553 }
00554 }
00555
00556
00557 return QDomElement();
00558 }
00559
00560 void KXMLGUIClient::setXMLGUIBuildDocument( const QDomDocument &doc )
00561 {
00562 d->m_buildDocument = doc;
00563 }
00564
00565 QDomDocument KXMLGUIClient::xmlguiBuildDocument() const
00566 {
00567 return d->m_buildDocument;
00568 }
00569
00570 void KXMLGUIClient::setFactory( KXMLGUIFactory *factory )
00571 {
00572 d->m_factory = factory;
00573 }
00574
00575 KXMLGUIFactory *KXMLGUIClient::factory() const
00576 {
00577 return d->m_factory;
00578 }
00579
00580 KXMLGUIClient *KXMLGUIClient::parentClient() const
00581 {
00582 return d->m_parent;
00583 }
00584
00585 void KXMLGUIClient::insertChildClient( KXMLGUIClient *child )
00586 {
00587 if ( child->d->m_parent )
00588 child->d->m_parent->removeChildClient( child );
00589 d->m_children.append( child );
00590 child->d->m_parent = this;
00591 }
00592
00593 void KXMLGUIClient::removeChildClient( KXMLGUIClient *child )
00594 {
00595 assert( d->m_children.contains( child ) );
00596 d->m_children.removeAll( child );
00597 child->d->m_parent = 0;
00598 }
00599
00600
00601
00602
00603
00604
00605
00606
00607
00608 QList<KXMLGUIClient*> KXMLGUIClient::childClients()
00609 {
00610 return d->m_children;
00611 }
00612
00613 void KXMLGUIClient::setClientBuilder( KXMLGUIBuilder *builder )
00614 {
00615 d->m_builder = builder;
00616 if ( builder )
00617 builder->setBuilderComponentData( componentData() );
00618 }
00619
00620 KXMLGUIBuilder *KXMLGUIClient::clientBuilder() const
00621 {
00622 return d->m_builder;
00623 }
00624
00625 void KXMLGUIClient::plugActionList( const QString &name, const QList<QAction*> &actionList )
00626 {
00627 if ( !d->m_factory )
00628 return;
00629
00630 d->m_factory->plugActionList( this, name, actionList );
00631 }
00632
00633 void KXMLGUIClient::unplugActionList( const QString &name )
00634 {
00635 if ( !d->m_factory )
00636 return;
00637
00638 d->m_factory->unplugActionList( this, name );
00639 }
00640
00641 QString KXMLGUIClient::findMostRecentXMLFile( const QStringList &files, QString &doc )
00642 {
00643 KXmlGuiVersionHandler versionHandler(files);
00644 doc = versionHandler.finalDocument();
00645 return versionHandler.finalFile();
00646 }
00647
00648 void KXMLGUIClient::addStateActionEnabled(const QString& state,
00649 const QString& action)
00650 {
00651 StateChange stateChange = getActionsToChangeForState(state);
00652
00653 stateChange.actionsToEnable.append( action );
00654
00655
00656 d->m_actionsStateMap.insert( state, stateChange );
00657 }
00658
00659
00660 void KXMLGUIClient::addStateActionDisabled(const QString& state,
00661 const QString& action)
00662 {
00663 StateChange stateChange = getActionsToChangeForState(state);
00664
00665 stateChange.actionsToDisable.append( action );
00666
00667
00668 d->m_actionsStateMap.insert( state, stateChange );
00669 }
00670
00671
00672 KXMLGUIClient::StateChange KXMLGUIClient::getActionsToChangeForState(const QString& state)
00673 {
00674 return d->m_actionsStateMap[state];
00675 }
00676
00677
00678 void KXMLGUIClient::stateChanged(const QString &newstate, KXMLGUIClient::ReverseStateChange reverse)
00679 {
00680 StateChange stateChange = getActionsToChangeForState(newstate);
00681
00682 bool setTrue = (reverse == StateNoReverse);
00683 bool setFalse = !setTrue;
00684
00685
00686
00687 for ( QStringList::const_iterator it = stateChange.actionsToEnable.constBegin();
00688 it != stateChange.actionsToEnable.constEnd(); ++it ) {
00689
00690 QAction *action = actionCollection()->action(qPrintable((*it)));
00691 if (action) action->setEnabled(setTrue);
00692 }
00693
00694
00695
00696 for ( QStringList::const_iterator it = stateChange.actionsToDisable.constBegin();
00697 it != stateChange.actionsToDisable.constEnd(); ++it ) {
00698
00699 QAction *action = actionCollection()->action(qPrintable((*it)));
00700 if (action) action->setEnabled(setFalse);
00701 }
00702
00703 }
00704
00705 void KXMLGUIClient::beginXMLPlug( QWidget* w )
00706 {
00707 actionCollection()->addAssociatedWidget( w );
00708 foreach (KXMLGUIClient* client, d->m_children)
00709 client->beginXMLPlug( w );
00710 }
00711
00712 void KXMLGUIClient::endXMLPlug()
00713 {
00714 }
00715
00716 void KXMLGUIClient::prepareXMLUnplug( QWidget * w )
00717 {
00718 actionCollection()->removeAssociatedWidget( w );
00719 foreach (KXMLGUIClient* client, d->m_children)
00720 client->prepareXMLUnplug( w );
00721 }
00722
00723 void KXMLGUIClient::virtual_hook( int, void* )
00724 { }