kxmlguifactory.cpp
00001 /* This file is part of the KDE libraries 00002 Copyright (C) 1999,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 as published by the Free Software Foundation; either 00008 version 2 of the License, or (at your option) any later version. 00009 00010 This library is distributed in the hope that it will be useful, 00011 but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00013 Library General Public License for more details. 00014 00015 You should have received a copy of the GNU Library General Public License 00016 along with this library; see the file COPYING.LIB. If not, write to 00017 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00018 Boston, MA 02110-1301, USA. 00019 */ 00020 00021 #include "kxmlguifactory.h" 00022 #include "kxmlguifactory_p.h" 00023 #include "kxmlguiclient.h" 00024 #include "kxmlguibuilder.h" 00025 00026 #include <assert.h> 00027 00028 #include <tqdir.h> 00029 #include <tqfile.h> 00030 #include <tqtextstream.h> 00031 #include <tqwidget.h> 00032 #include <tqdatetime.h> 00033 #include <tqvariant.h> 00034 00035 #include <kaction.h> 00036 #include <kdebug.h> 00037 #include <kinstance.h> 00038 #include <kglobal.h> 00039 #include <kshortcut.h> 00040 #include <kstandarddirs.h> 00041 #include <kkeydialog.h> 00042 00043 using namespace KXMLGUI; 00044 00045 /* 00046 * TODO: - make more use of TQValueList instead of QPtrList 00047 */ 00048 00049 class KXMLGUIFactoryPrivate : public BuildState 00050 { 00051 public: 00052 KXMLGUIFactoryPrivate() 00053 { 00054 static const TQString &defaultMergingName = KGlobal::staticQString( "<default>" ); 00055 static const TQString &actionList = KGlobal::staticQString( "actionlist" ); 00056 static const TQString &name = KGlobal::staticQString( "name" ); 00057 00058 m_rootNode = new ContainerNode( 0L, TQString::null, 0L ); 00059 m_defaultMergingName = defaultMergingName; 00060 tagActionList = actionList; 00061 attrName = name; 00062 } 00063 ~KXMLGUIFactoryPrivate() 00064 { 00065 delete m_rootNode; 00066 } 00067 00068 void pushState() 00069 { 00070 m_stateStack.push( *this ); 00071 } 00072 00073 void popState() 00074 { 00075 BuildState::operator=( m_stateStack.pop() ); 00076 } 00077 00078 ContainerNode *m_rootNode; 00079 00080 TQString m_defaultMergingName; 00081 00082 /* 00083 * Contains the container which is searched for in ::container . 00084 */ 00085 TQString m_containerName; 00086 00087 /* 00088 * List of all clients 00089 */ 00090 TQPtrList<KXMLGUIClient> m_clients; 00091 00092 TQString tagActionList; 00093 00094 TQString attrName; 00095 00096 BuildStateStack m_stateStack; 00097 }; 00098 00099 TQString KXMLGUIFactory::readConfigFile( const TQString &filename, const KInstance *instance ) 00100 { 00101 return readConfigFile( filename, false, instance ); 00102 } 00103 00104 TQString KXMLGUIFactory::readConfigFile( const TQString &filename, bool never_null, const KInstance *_instance ) 00105 { 00106 const KInstance *instance = _instance ? _instance : KGlobal::instance(); 00107 TQString xml_file; 00108 00109 if (!TQDir::isRelativePath(filename)) 00110 xml_file = filename; 00111 else 00112 { 00113 xml_file = locate("data", TQString::fromLatin1(instance->instanceName() + '/' ) + filename); 00114 if ( !TQFile::exists( xml_file ) ) 00115 xml_file = locate( "data", filename ); 00116 } 00117 00118 TQFile file( xml_file ); 00119 if ( !file.open( IO_ReadOnly ) ) 00120 { 00121 kdError(240) << "No such XML file " << filename << endl; 00122 if ( never_null ) 00123 return TQString::fromLatin1( "<!DOCTYPE kpartgui>\n<kpartgui name=\"empty\">\n</kpartgui>" ); 00124 else 00125 return TQString::null; 00126 } 00127 00128 #if QT_VERSION <= 0x030302 00129 // Work around bug in TQString::fromUtf8 (which calls strlen). 00130 TQByteArray buffer(file.size() + 1); 00131 buffer = file.readAll(); 00132 if(!buffer.isEmpty()) 00133 buffer[ buffer.size() - 1 ] = '\0'; 00134 else 00135 return TQString::null; 00136 #else 00137 TQByteArray buffer(file.readAll()); 00138 #endif 00139 return TQString::fromUtf8(buffer.data(), buffer.size()); 00140 } 00141 00142 bool KXMLGUIFactory::saveConfigFile( const TQDomDocument& doc, 00143 const TQString& filename, const KInstance *_instance ) 00144 { 00145 const KInstance *instance = _instance ? _instance : KGlobal::instance(); 00146 TQString xml_file(filename); 00147 00148 if (TQDir::isRelativePath(xml_file)) 00149 xml_file = locateLocal("data", TQString::fromLatin1( instance->instanceName() + '/' ) 00150 + filename); 00151 00152 TQFile file( xml_file ); 00153 if ( !file.open( IO_WriteOnly ) ) 00154 { 00155 kdError(240) << "Could not write to " << filename << endl; 00156 return false; 00157 } 00158 00159 // write out our document 00160 TQTextStream ts(&file); 00161 ts.setEncoding( TQTextStream::UnicodeUTF8 ); 00162 ts << doc; 00163 00164 file.close(); 00165 return true; 00166 } 00167 00168 TQString KXMLGUIFactory::documentToXML( const TQDomDocument& doc ) 00169 { 00170 TQString str; 00171 TQTextStream ts(&str, IO_WriteOnly); 00172 ts.setEncoding( TQTextStream::UnicodeUTF8 ); 00173 ts << doc; 00174 return str; 00175 } 00176 00177 TQString KXMLGUIFactory::elementToXML( const TQDomElement& elem ) 00178 { 00179 TQString str; 00180 TQTextStream ts(&str, IO_WriteOnly); 00181 ts.setEncoding( TQTextStream::UnicodeUTF8 ); 00182 ts << elem; 00183 return str; 00184 } 00185 00186 void KXMLGUIFactory::removeDOMComments( TQDomNode &node ) 00187 { 00188 TQDomNode n = node.firstChild(); 00189 while ( !n.isNull() ) 00190 { 00191 if ( n.nodeType() == TQDomNode::CommentNode ) 00192 { 00193 TQDomNode tmp = n; 00194 n = n.nextSibling(); 00195 node.removeChild( tmp ); 00196 } 00197 else 00198 { 00199 TQDomNode tmp = n; 00200 n = n.nextSibling(); 00201 removeDOMComments( tmp ); 00202 } 00203 } 00204 } 00205 00206 KXMLGUIFactory::KXMLGUIFactory( KXMLGUIBuilder *builder, TQObject *parent, const char *name ) 00207 : TQObject( parent, name ) 00208 { 00209 d = new KXMLGUIFactoryPrivate; 00210 d->builder = builder; 00211 d->guiClient = 0; 00212 if ( d->builder ) 00213 { 00214 d->builderContainerTags = d->builder->containerTags(); 00215 d->builderCustomTags = d->builder->customTags(); 00216 } 00217 } 00218 00219 KXMLGUIFactory::~KXMLGUIFactory() 00220 { 00221 delete d; 00222 } 00223 00224 void KXMLGUIFactory::addClient( KXMLGUIClient *client ) 00225 { 00226 kdDebug(1002) << "KXMLGUIFactory::addClient( " << client << " )" << endl; // ellis 00227 static const TQString &actionPropElementName = KGlobal::staticQString( "ActionProperties" ); 00228 00229 if ( client->factory() ) { 00230 if ( client->factory() == this ) 00231 return; 00232 else 00233 client->factory()->removeClient( client ); //just in case someone does stupid things ;-) 00234 } 00235 00236 d->pushState(); 00237 00238 // TQTime dt; dt.start(); 00239 00240 d->guiClient = client; 00241 00242 // add this client to our client list 00243 if ( !d->m_clients.containsRef( client ) ) 00244 d->m_clients.append( client ); 00245 else 00246 kdDebug(1002) << "XMLGUI client already added " << client << endl; 00247 00248 // Tell the client that plugging in is process and 00249 // let it know what builder widget its mainwindow shortcuts 00250 // should be attached to. 00251 client->beginXMLPlug( d->builder->widget() ); 00252 00253 // try to use the build document for building the client's GUI, as the build document 00254 // contains the correct container state information (like toolbar positions, sizes, etc.) . 00255 // if there is non available, then use the "real" document. 00256 TQDomDocument doc = client->xmlguiBuildDocument(); 00257 if ( doc.documentElement().isNull() ) 00258 doc = client->domDocument(); 00259 00260 TQDomElement docElement = doc.documentElement(); 00261 00262 d->m_rootNode->index = -1; 00263 00264 // cache some variables 00265 00266 d->clientName = docElement.attribute( d->attrName ); 00267 d->clientBuilder = client->clientBuilder(); 00268 00269 if ( d->clientBuilder ) 00270 { 00271 d->clientBuilderContainerTags = d->clientBuilder->containerTags(); 00272 d->clientBuilderCustomTags = d->clientBuilder->customTags(); 00273 } 00274 else 00275 { 00276 d->clientBuilderContainerTags.clear(); 00277 d->clientBuilderCustomTags.clear(); 00278 } 00279 00280 // process a possibly existing actionproperties section 00281 00282 TQDomElement actionPropElement = docElement.namedItem( actionPropElementName ).toElement(); 00283 if ( actionPropElement.isNull() ) 00284 actionPropElement = docElement.namedItem( actionPropElementName.lower() ).toElement(); 00285 00286 if ( !actionPropElement.isNull() ) 00287 applyActionProperties( actionPropElement ); 00288 00289 BuildHelper( *d, d->m_rootNode ).build( docElement ); 00290 00291 // let the client know that we built its GUI. 00292 client->setFactory( this ); 00293 00294 // call the finalizeGUI method, to fix up the positions of toolbars for example. 00295 // ### FIXME : obey client builder 00296 // --- Well, toolbars have a bool "positioned", so it doesn't really matter, 00297 // if we call positionYourself on all of them each time. (David) 00298 d->builder->finalizeGUI( d->guiClient ); 00299 00300 // reset some variables, for safety 00301 d->BuildState::reset(); 00302 00303 client->endXMLPlug(); 00304 00305 d->popState(); 00306 00307 emit clientAdded( client ); 00308 00309 // build child clients 00310 if ( client->childClients()->count() > 0 ) 00311 { 00312 const TQPtrList<KXMLGUIClient> *children = client->childClients(); 00313 TQPtrListIterator<KXMLGUIClient> childIt( *children ); 00314 for (; childIt.current(); ++childIt ) 00315 addClient( childIt.current() ); 00316 } 00317 00318 // kdDebug() << "addClient took " << dt.elapsed() << endl; 00319 } 00320 00321 void KXMLGUIFactory::removeClient( KXMLGUIClient *client ) 00322 { 00323 kdDebug(1002) << "KXMLGUIFactory::removeClient( " << client << " )" << endl; // ellis 00324 00325 // don't try to remove the client's GUI if we didn't build it 00326 if ( !client || client->factory() != this ) 00327 return; 00328 00329 // remove this client from our client list 00330 d->m_clients.removeRef( client ); 00331 00332 // remove child clients first 00333 if ( client->childClients()->count() > 0 ) 00334 { 00335 const TQPtrList<KXMLGUIClient> *children = client->childClients(); 00336 TQPtrListIterator<KXMLGUIClient> childIt( *children ); 00337 childIt.toLast(); 00338 for (; childIt.current(); --childIt ) 00339 removeClient( childIt.current() ); 00340 } 00341 00342 kdDebug(1002) << "KXMLGUIFactory::removeServant, calling removeRecursive" << endl; 00343 00344 d->pushState(); 00345 00346 // cache some variables 00347 00348 d->guiClient = client; 00349 d->clientName = client->domDocument().documentElement().attribute( d->attrName ); 00350 d->clientBuilder = client->clientBuilder(); 00351 00352 client->setFactory( 0L ); 00353 00354 // if we don't have a build document for that client, yet, then create one by 00355 // cloning the original document, so that saving container information in the 00356 // DOM tree does not touch the original document. 00357 TQDomDocument doc = client->xmlguiBuildDocument(); 00358 if ( doc.documentElement().isNull() ) 00359 { 00360 doc = client->domDocument().cloneNode( true ).toDocument(); 00361 client->setXMLGUIBuildDocument( doc ); 00362 } 00363 00364 d->m_rootNode->destruct( doc.documentElement(), *d ); 00365 00366 d->builder->finalizeGUI( d->guiClient ); //JoWenn 00367 00368 // reset some variables 00369 d->BuildState::reset(); 00370 00371 // This will destruct the KAccel object built around the given widget. 00372 client->prepareXMLUnplug( d->builder->widget() ); 00373 00374 d->popState(); 00375 00376 emit clientRemoved( client ); 00377 } 00378 00379 TQPtrList<KXMLGUIClient> KXMLGUIFactory::clients() const 00380 { 00381 return d->m_clients; 00382 } 00383 00384 TQWidget *KXMLGUIFactory::container( const TQString &containerName, KXMLGUIClient *client, 00385 bool useTagName ) 00386 { 00387 d->pushState(); 00388 d->m_containerName = containerName; 00389 d->guiClient = client; 00390 00391 TQWidget *result = findRecursive( d->m_rootNode, useTagName ); 00392 00393 d->guiClient = 0L; 00394 d->m_containerName = TQString::null; 00395 00396 d->popState(); 00397 00398 return result; 00399 } 00400 00401 TQPtrList<TQWidget> KXMLGUIFactory::containers( const TQString &tagName ) 00402 { 00403 return findRecursive( d->m_rootNode, tagName ); 00404 } 00405 00406 void KXMLGUIFactory::reset() 00407 { 00408 d->m_rootNode->reset(); 00409 00410 d->m_rootNode->clearChildren(); 00411 } 00412 00413 void KXMLGUIFactory::resetContainer( const TQString &containerName, bool useTagName ) 00414 { 00415 if ( containerName.isEmpty() ) 00416 return; 00417 00418 ContainerNode *container = d->m_rootNode->findContainer( containerName, useTagName ); 00419 00420 if ( !container ) 00421 return; 00422 00423 ContainerNode *parent = container->parent; 00424 if ( !parent ) 00425 return; 00426 00427 // resetInternal( container ); 00428 00429 parent->removeChild( container ); 00430 } 00431 00432 TQWidget *KXMLGUIFactory::findRecursive( KXMLGUI::ContainerNode *node, bool tag ) 00433 { 00434 if ( ( ( !tag && node->name == d->m_containerName ) || 00435 ( tag && node->tagName == d->m_containerName ) ) && 00436 ( !d->guiClient || node->client == d->guiClient ) ) 00437 return node->container; 00438 00439 TQPtrListIterator<ContainerNode> it( node->children ); 00440 for (; it.current(); ++it ) 00441 { 00442 TQWidget *cont = findRecursive( it.current(), tag ); 00443 if ( cont ) 00444 return cont; 00445 } 00446 00447 return 0L; 00448 } 00449 00450 TQPtrList<TQWidget> KXMLGUIFactory::findRecursive( KXMLGUI::ContainerNode *node, 00451 const TQString &tagName ) 00452 { 00453 TQPtrList<TQWidget> res; 00454 00455 if ( node->tagName == tagName.lower() ) 00456 res.append( node->container ); 00457 00458 TQPtrListIterator<KXMLGUI::ContainerNode> it( node->children ); 00459 for (; it.current(); ++it ) 00460 { 00461 TQPtrList<TQWidget> lst = findRecursive( it.current(), tagName ); 00462 TQPtrListIterator<TQWidget> wit( lst ); 00463 for (; wit.current(); ++wit ) 00464 res.append( wit.current() ); 00465 } 00466 00467 return res; 00468 } 00469 00470 void KXMLGUIFactory::plugActionList( KXMLGUIClient *client, const TQString &name, 00471 const TQPtrList<KAction> &actionList ) 00472 { 00473 d->pushState(); 00474 d->guiClient = client; 00475 d->actionListName = name; 00476 d->actionList = actionList; 00477 d->clientName = client->domDocument().documentElement().attribute( d->attrName ); 00478 00479 d->m_rootNode->plugActionList( *d ); 00480 00481 d->BuildState::reset(); 00482 d->popState(); 00483 } 00484 00485 void KXMLGUIFactory::unplugActionList( KXMLGUIClient *client, const TQString &name ) 00486 { 00487 d->pushState(); 00488 d->guiClient = client; 00489 d->actionListName = name; 00490 d->clientName = client->domDocument().documentElement().attribute( d->attrName ); 00491 00492 d->m_rootNode->unplugActionList( *d ); 00493 00494 d->BuildState::reset(); 00495 d->popState(); 00496 } 00497 00498 void KXMLGUIFactory::applyActionProperties( const TQDomElement &actionPropElement ) 00499 { 00500 static const TQString &tagAction = KGlobal::staticQString( "action" ); 00501 00502 for (TQDomNode n = actionPropElement.firstChild(); 00503 !n.isNull(); n = n.nextSibling() ) 00504 { 00505 TQDomElement e = n.toElement(); 00506 if ( e.tagName().lower() != tagAction ) 00507 continue; 00508 00509 KAction *action = d->guiClient->action( e ); 00510 if ( !action ) 00511 continue; 00512 00513 configureAction( action, e.attributes() ); 00514 } 00515 } 00516 00517 void KXMLGUIFactory::configureAction( KAction *action, const TQDomNamedNodeMap &attributes ) 00518 { 00519 for ( uint i = 0; i < attributes.length(); i++ ) 00520 { 00521 TQDomAttr attr = attributes.item( i ).toAttr(); 00522 if ( attr.isNull() ) 00523 continue; 00524 00525 configureAction( action, attr ); 00526 } 00527 } 00528 00529 void KXMLGUIFactory::configureAction( KAction *action, const TQDomAttr &attribute ) 00530 { 00531 static const TQString &attrShortcut = KGlobal::staticQString( "shortcut" ); 00532 00533 TQString attrName = attribute.name(); 00534 // If the attribute is a deprecated "accel", change to "shortcut". 00535 if ( attrName.lower() == "accel" ) 00536 attrName = attrShortcut; 00537 00538 TQVariant propertyValue; 00539 00540 TQVariant::Type propertyType = action->property( attrName.latin1() ).type(); 00541 00542 if ( propertyType == TQVariant::Int ) 00543 propertyValue = TQVariant( attribute.value().toInt() ); 00544 else if ( propertyType == TQVariant::UInt ) 00545 propertyValue = TQVariant( attribute.value().toUInt() ); 00546 else 00547 propertyValue = TQVariant( attribute.value() ); 00548 00549 action->setProperty( attrName.latin1(), propertyValue ); 00550 } 00551 00552 00553 int KXMLGUIFactory::configureShortcuts(bool bAllowLetterShortcuts , bool bSaveSettings ) 00554 { 00555 KKeyDialog dlg( bAllowLetterShortcuts, tqt_dynamic_cast<TQWidget*>(parent()) ); 00556 TQPtrListIterator<KXMLGUIClient> it( d->m_clients ); 00557 KXMLGUIClient *client; 00558 while( (client=it.current()) !=0 ) 00559 { 00560 ++it; 00561 if(!client->xmlFile().isEmpty()) 00562 dlg.insert( client->actionCollection() ); 00563 } 00564 return dlg.configure(bSaveSettings); 00565 } 00566 00567 TQDomElement KXMLGUIFactory::actionPropertiesElement( TQDomDocument& doc ) 00568 { 00569 const TQString tagActionProp = TQString::fromLatin1("ActionProperties"); 00570 // first, lets see if we have existing properties 00571 TQDomElement elem; 00572 TQDomNode it = doc.documentElement().firstChild(); 00573 for( ; !it.isNull(); it = it.nextSibling() ) { 00574 TQDomElement e = it.toElement(); 00575 if( e.tagName() == tagActionProp ) { 00576 elem = e; 00577 break; 00578 } 00579 } 00580 00581 // if there was none, create one 00582 if( elem.isNull() ) { 00583 elem = doc.createElement( tagActionProp ); 00584 doc.documentElement().appendChild( elem ); 00585 } 00586 return elem; 00587 } 00588 00589 TQDomElement KXMLGUIFactory::findActionByName( TQDomElement& elem, const TQString& sName, bool create ) 00590 { 00591 static const TQString& attrName = KGlobal::staticQString( "name" ); 00592 static const TQString& tagAction = KGlobal::staticQString( "Action" ); 00593 for( TQDomNode it = elem.firstChild(); !it.isNull(); it = it.nextSibling() ) { 00594 TQDomElement e = it.toElement(); 00595 if( e.attribute( attrName ) == sName ) 00596 return e; 00597 } 00598 00599 if( create ) { 00600 TQDomElement act_elem = elem.ownerDocument().createElement( tagAction ); 00601 act_elem.setAttribute( attrName, sName ); 00602 elem.appendChild( act_elem ); 00603 return act_elem; 00604 } 00605 return TQDomElement(); 00606 } 00607 00608 void KXMLGUIFactory::virtual_hook( int, void* ) 00609 { /*BASE::virtual_hook( id, data );*/ } 00610 00611 #include "kxmlguifactory.moc" 00612 00613 /* vim: et sw=4 00614 */