![]() |
XML Mill
1.0.0
A GUI based XML editor with a memory.
|
00001 /* Copyright (c) 2012 - 2013 by William Hallatt. 00002 * 00003 * This file forms part of "XML Mill". 00004 * 00005 * The official website for this project is <http://www.goblincoding.com> and, 00006 * although not compulsory, it would be appreciated if all works of whatever 00007 * nature using this source code (in whole or in part) include a reference to 00008 * this site. 00009 * 00010 * Should you wish to contact me for whatever reason, please do so via: 00011 * 00012 * <http://www.goblincoding.com/contact> 00013 * 00014 * This program is free software: you can redistribute it and/or modify it under 00015 * the terms of the GNU General Public License as published by the Free Software 00016 * Foundation, either version 3 of the License, or (at your option) any later 00017 * version. 00018 * 00019 * This program is distributed in the hope that it will be useful, but WITHOUT 00020 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 00021 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 00022 * 00023 * You should have received a copy of the GNU General Public License along with 00024 * this program (GNUGPL.txt). If not, see 00025 * 00026 * <http://www.gnu.org/licenses/> 00027 */ 00028 00029 #include "gcdomtreewidget.h" 00030 #include "gctreewidgetitem.h" 00031 #include "db/gcdatabaseinterface.h" 00032 #include "utils/gcmessagespace.h" 00033 #include "utils/gcglobalspace.h" 00034 00035 #include <QApplication> 00036 #include <QDomDocument> 00037 #include <QAction> 00038 #include <QMouseEvent> 00039 #include <QInputDialog> 00040 #include <QXmlInputSource> 00041 00042 /*--------------------------------------------------------------------------------------*/ 00043 00044 GCDomTreeWidget::GCDomTreeWidget( QWidget* parent ) 00045 : QTreeWidget ( parent ), 00046 m_activeItem ( NULL ), 00047 m_domDoc ( new QDomDocument ), 00048 m_commentNode (), 00049 m_isEmpty ( true ), 00050 m_busyIterating ( false ), 00051 m_itemBeingManipulated( false ), 00052 m_items (), 00053 m_comments () 00054 { 00055 setFont( QFont( GCGlobalSpace::FONT, GCGlobalSpace::FONTSIZE ) ); 00056 setSelectionMode( QAbstractItemView::SingleSelection ); 00057 setDragDropMode( QAbstractItemView::InternalMove ); 00058 00059 QAction* expand = new QAction( "Expand", this ); 00060 addAction( expand ); 00061 connect( expand, SIGNAL( triggered() ), this, SLOT( expand() ) ); 00062 00063 QAction* collapse = new QAction( "Collapse", this ); 00064 addAction( collapse ); 00065 connect( collapse, SIGNAL( triggered() ), this, SLOT( collapse() ) ); 00066 00067 QAction* separator = new QAction( this ); 00068 separator->setSeparator( true ); 00069 addAction( separator ); 00070 00071 QAction* rename = new QAction( "Rename element", this ); 00072 addAction( rename ); 00073 connect( rename, SIGNAL( triggered() ), this, SLOT( renameItem() ) ); 00074 00075 QAction* remove = new QAction( "Remove element", this ); 00076 addAction( remove ); 00077 connect( remove, SIGNAL( triggered() ), this, SLOT( removeItem() ) ); 00078 00079 separator = new QAction( this ); 00080 separator->setSeparator( true ); 00081 addAction( separator ); 00082 00083 QAction* stepUp = new QAction( "Move up one level", this ); 00084 addAction( stepUp ); 00085 connect( stepUp, SIGNAL( triggered() ), this, SLOT( stepUp() ) ); 00086 00087 QAction* stepDown = new QAction( "Move down one level", this ); 00088 addAction( stepDown ); 00089 connect( stepDown, SIGNAL( triggered() ), this, SLOT( stepDown() ) ); 00090 00091 setContextMenuPolicy( Qt::ActionsContextMenu ); 00092 00093 connect( this, SIGNAL( currentItemChanged( QTreeWidgetItem*,QTreeWidgetItem* ) ), this, SLOT( currentGcItemChanged( QTreeWidgetItem*,QTreeWidgetItem* ) ) ); 00094 connect( this, SIGNAL( itemClicked( QTreeWidgetItem*,int ) ), this, SLOT( emitGcCurrentItemSelected( QTreeWidgetItem*,int ) ) ); 00095 connect( this, SIGNAL( itemActivated( QTreeWidgetItem*, int ) ), this, SLOT( emitGcCurrentItemSelected( QTreeWidgetItem*, int ) ) ); 00096 connect( this, SIGNAL( itemChanged( QTreeWidgetItem*, int ) ), this, SLOT( emitGcCurrentItemChanged( QTreeWidgetItem*, int ) ) ); 00097 } 00098 00099 /*--------------------------------------------------------------------------------------*/ 00100 00101 GCDomTreeWidget::~GCDomTreeWidget() 00102 { 00103 delete m_domDoc; 00104 } 00105 00106 /*--------------------------------------------------------------------------------------*/ 00107 00108 GCTreeWidgetItem* GCDomTreeWidget::gcCurrentItem() const 00109 { 00110 return dynamic_cast< GCTreeWidgetItem* >( currentItem() ); 00111 } 00112 00113 /*--------------------------------------------------------------------------------------*/ 00114 00115 QDomNode GCDomTreeWidget::cloneDocument() const 00116 { 00117 return m_domDoc->documentElement().cloneNode(); 00118 } 00119 00120 /*--------------------------------------------------------------------------------------*/ 00121 00122 QString GCDomTreeWidget::toString() const 00123 { 00124 return m_domDoc->toString( 2 ); 00125 } 00126 00127 /*--------------------------------------------------------------------------------------*/ 00128 00129 QString GCDomTreeWidget::rootName() const 00130 { 00131 return m_domDoc->documentElement().tagName(); 00132 } 00133 00134 /*--------------------------------------------------------------------------------------*/ 00135 00136 QString GCDomTreeWidget::activeCommentValue() const 00137 { 00138 if( !m_commentNode.isNull() ) 00139 { 00140 /* Check if the comment is an actual comment or if it's valid XML that's been 00141 commented out. */ 00142 QDomDocument doc; 00143 00144 if( !doc.setContent( m_commentNode.nodeValue() ) ) 00145 { 00146 return m_commentNode.nodeValue(); 00147 } 00148 } 00149 00150 return QString(); 00151 } 00152 00153 /*--------------------------------------------------------------------------------------*/ 00154 00155 void GCDomTreeWidget::setActiveCommentValue( const QString& value ) 00156 { 00157 /* Check if we're editing an existing comment, or if we should add a new one. */ 00158 if( !m_commentNode.isNull() ) 00159 { 00160 if( !value.isEmpty() ) 00161 { 00162 m_commentNode.setNodeValue( value ); 00163 } 00164 else 00165 { 00166 m_comments.removeAll( m_commentNode ); 00167 m_commentNode.parentNode().removeChild( m_commentNode ); 00168 } 00169 } 00170 else 00171 { 00172 if( m_activeItem && 00173 !value.isEmpty() ) 00174 { 00175 QDomComment comment = m_domDoc->createComment( value ); 00176 m_activeItem->element().parentNode().insertBefore( comment, m_activeItem->element() ); 00177 m_comments.append( comment ); 00178 } 00179 } 00180 00181 emitGcCurrentItemChanged( m_activeItem, 0 ); 00182 } 00183 00184 /*--------------------------------------------------------------------------------------*/ 00185 00186 QList< GCTreeWidgetItem* > GCDomTreeWidget::includedTreeWidgetItems() const 00187 { 00188 QList< GCTreeWidgetItem* > includedItems; 00189 00190 for( int i = 0; i < m_items.size(); ++i ) 00191 { 00192 GCTreeWidgetItem* localItem = m_items.at( i ); 00193 00194 if( !localItem->elementExcluded() ) 00195 { 00196 includedItems.append( localItem ); 00197 } 00198 } 00199 00200 return includedItems; 00201 } 00202 00203 /*--------------------------------------------------------------------------------------*/ 00204 00205 const QList< GCTreeWidgetItem* >& GCDomTreeWidget::allTreeWidgetItems() const 00206 { 00207 return m_items; 00208 } 00209 00210 /*--------------------------------------------------------------------------------------*/ 00211 00212 int GCDomTreeWidget::itemPositionRelativeToIdenticalSiblings( const QString& nodeText, int itemIndex ) const 00213 { 00214 QList< int > indices; 00215 00216 if( !m_isEmpty ) 00217 { 00218 /* If there are multiple nodes with the same element name (more likely than not), check which 00219 of these nodes are exact duplicates with regards to attributes, values, etc. */ 00220 for( int i = 0; i < m_items.size(); ++i ) 00221 { 00222 GCTreeWidgetItem* treeItem = m_items.at( i ); 00223 00224 if( treeItem->toString() == nodeText ) 00225 { 00226 indices.append( treeItem->index() ); 00227 } 00228 } 00229 } 00230 00231 /* Now that we have a list of all the indices matching identical nodes (indices are a rough 00232 indication of an element's position in the DOM and closely matches the "line numbers" of the 00233 items in the tree widget), we can determine the position of the selected DOM element relative 00234 to its doppelgangers. */ 00235 qSort( indices.begin(), indices.end() ); 00236 00237 return indices.indexOf( itemIndex ); 00238 } 00239 00240 /*--------------------------------------------------------------------------------------*/ 00241 00242 bool GCDomTreeWidget::setContent( const QString& text, QString* errorMsg, int* errorLine, int* errorColumn ) 00243 { 00244 clearAndReset(); 00245 00246 QXmlInputSource source; 00247 source.setData( text ); 00248 QXmlSimpleReader reader; 00249 00250 if( !m_domDoc->setContent( &source, &reader, errorMsg, errorLine, errorColumn ) ) 00251 { 00252 return false; 00253 } 00254 00255 rebuildTreeWidget(); 00256 return true; 00257 } 00258 00259 /*--------------------------------------------------------------------------------------*/ 00260 00261 bool GCDomTreeWidget::empty() const 00262 { 00263 return m_isEmpty; 00264 } 00265 00266 /*--------------------------------------------------------------------------------------*/ 00267 00268 bool GCDomTreeWidget::currentItemIsRoot() const 00269 { 00270 if( gcCurrentItem() ) 00271 { 00272 return ( gcCurrentItem()->element() == m_domDoc->documentElement() ); 00273 } 00274 00275 return true; 00276 } 00277 00278 /*--------------------------------------------------------------------------------------*/ 00279 00280 bool GCDomTreeWidget::matchesRootName( const QString& elementName ) const 00281 { 00282 return ( elementName == m_domDoc->documentElement().tagName() ); 00283 } 00284 00285 /*--------------------------------------------------------------------------------------*/ 00286 00287 bool GCDomTreeWidget::documentCompatible() const 00288 { 00289 return GCDataBaseInterface::instance()->isDocumentCompatible( m_domDoc ); 00290 } 00291 00292 /*--------------------------------------------------------------------------------------*/ 00293 00294 bool GCDomTreeWidget::batchProcessSuccess() const 00295 { 00296 return GCDataBaseInterface::instance()->batchProcessDomDocument( m_domDoc ); 00297 } 00298 00299 /*--------------------------------------------------------------------------------------*/ 00300 00301 void GCDomTreeWidget::updateItemNames( const QString& oldName, const QString& newName ) 00302 { 00303 for( int i = 0; i < m_items.size(); ++i ) 00304 { 00305 if( m_items.at( i )->name() == oldName ) 00306 { 00307 GCTreeWidgetItem* item = const_cast< GCTreeWidgetItem* >( m_items.at( i ) ); 00308 item->rename( newName ); 00309 } 00310 } 00311 } 00312 00313 /*--------------------------------------------------------------------------------------*/ 00314 00315 void GCDomTreeWidget::rebuildTreeWidget() 00316 { 00317 clear(); // ONLY whack the tree widget items. 00318 m_items.clear(); 00319 00320 /* Set the document root as the first item in the tree. */ 00321 GCTreeWidgetItem* item = new GCTreeWidgetItem( m_domDoc->documentElement(), m_items.size() ); 00322 invisibleRootItem()->addChild( item ); // takes ownership 00323 m_items.append( item ); 00324 m_isEmpty = false; 00325 00326 processElement( item, item->element().firstChildElement() ); 00327 00328 m_comments.clear(); 00329 populateCommentList( m_domDoc->documentElement() ); 00330 00331 emitGcCurrentItemSelected( item, 0 ); 00332 } 00333 00334 /*--------------------------------------------------------------------------------------*/ 00335 00336 void GCDomTreeWidget::appendSnippet( GCTreeWidgetItem* parentItem, QDomElement childElement ) 00337 { 00338 parentItem->element().appendChild( childElement ); 00339 processElement( parentItem, childElement ); 00340 populateCommentList( childElement ); 00341 updateIndices(); 00342 emitGcCurrentItemSelected( currentItem(), 0 ); 00343 } 00344 00345 /*--------------------------------------------------------------------------------------*/ 00346 00347 void GCDomTreeWidget::replaceItemsWithComment( const QList< int >& indices, const QString& comment ) 00348 { 00349 QList< GCTreeWidgetItem* > itemsToDelete; 00350 GCTreeWidgetItem* commentParentItem = NULL; 00351 00352 for( int i = 0; i < m_items.size(); ++i ) 00353 { 00354 GCTreeWidgetItem* item = m_items.at( i ); 00355 00356 for( int j = 0; j < indices.size(); ++j ) 00357 { 00358 if( item->index() == indices.at( j ) ) 00359 { 00360 /* This works because the indices are always sorted from small to big, i.e. 00361 the item corresponding to the lowest index in indices will be the furthest up 00362 the node hierarchy. */ 00363 if( j == 0 && item->gcParent() ) 00364 { 00365 commentParentItem = item->gcParent(); 00366 } 00367 00368 /* Remove the element from the DOM first. */ 00369 QDomNode parentNode = item->element().parentNode(); 00370 parentNode.removeChild( item->element() ); 00371 00372 /* Now whack it. */ 00373 if( item->gcParent() ) 00374 { 00375 GCTreeWidgetItem* parentItem = item->gcParent(); 00376 parentItem->removeChild( item ); 00377 } 00378 else 00379 { 00380 invisibleRootItem()->removeChild( item ); 00381 } 00382 00383 /* Removing an item from another's child list does not delete it. */ 00384 itemsToDelete.append( item ); 00385 } 00386 } 00387 } 00388 00389 /* Delete the items here so that we may be sure that they are actually removed 00390 from the items list as well (deleting items in the loop above resulted in parent 00391 items deleting all their children, but obviously not updating the items list in 00392 the process). */ 00393 for( int i = 0; i < itemsToDelete.size(); ++ i ) 00394 { 00395 GCTreeWidgetItem* item = itemsToDelete.at( i ); 00396 removeFromList( item ); 00397 delete item; 00398 item = NULL; 00399 } 00400 00401 /* Create a comment node with the combined text of all the item nodes that were removed 00402 and insert it in the correct position in the DOM document. */ 00403 QDomComment newComment = m_domDoc->createComment( comment ); 00404 00405 if( commentParentItem ) 00406 { 00407 commentParentItem->element().appendChild( newComment ); 00408 } 00409 else 00410 { 00411 m_domDoc->appendChild( newComment ); 00412 } 00413 00414 m_comments.append( newComment ); 00415 m_isEmpty = m_items.isEmpty(); 00416 updateIndices(); 00417 } 00418 00419 /*--------------------------------------------------------------------------------------*/ 00420 00421 void GCDomTreeWidget::processElement( GCTreeWidgetItem* parentItem, QDomElement element ) 00422 { 00423 if( parentItem ) 00424 { 00425 while( !element.isNull() ) 00426 { 00427 GCTreeWidgetItem* item = new GCTreeWidgetItem( element, m_items.size() ); 00428 parentItem->addChild( item ); // takes ownership 00429 m_items.append( item ); 00430 00431 processElement( item, element.firstChildElement() ); 00432 element = element.nextSiblingElement(); 00433 } 00434 00435 qApp->processEvents( QEventLoop::ExcludeUserInputEvents ); 00436 } 00437 } 00438 00439 /*--------------------------------------------------------------------------------------*/ 00440 00441 void GCDomTreeWidget::populateFromDatabase( const QString& baseElementName ) 00442 { 00443 clearAndReset(); 00444 00445 if( baseElementName.isEmpty() ) 00446 { 00447 /* It is possible that there may be multiple document types saved to this profile. */ 00448 foreach( QString element, GCDataBaseInterface::instance()->knownRootElements() ) 00449 { 00450 m_isEmpty = true; // forces the new item to be added to the invisible root 00451 addItem( element ); 00452 processElementFromDatabase( element ); 00453 } 00454 } 00455 else 00456 { 00457 addItem( baseElementName ); 00458 processElementFromDatabase( baseElementName ); 00459 } 00460 00461 expandAll(); 00462 emitGcCurrentItemSelected( currentItem(), 0 ); 00463 } 00464 00465 /*--------------------------------------------------------------------------------------*/ 00466 00467 void GCDomTreeWidget::processElementFromDatabase( const QString& element ) 00468 { 00469 QStringList children = GCDataBaseInterface::instance()->children( element ); 00470 00471 foreach( QString child, children ) 00472 { 00473 addItem( child ); 00474 00475 /* Since it isn't illegal to have elements with children of the same name, we cannot 00476 block it in the DB, however, if we DO have elements with children of the same name, 00477 this recursive call enters an infinite loop, so we need to make sure that doesn't 00478 happen. */ 00479 GCTreeWidgetItem* newItem = gcCurrentItem(); 00480 00481 if( !parentTreeAlreadyContainsElement( newItem, child ) ) 00482 { 00483 processElementFromDatabase( child ); 00484 } 00485 else 00486 { 00487 setCurrentItem( m_activeItem->parent() ); // required to enforce sibling relationships 00488 } 00489 } 00490 00491 setCurrentItem( m_activeItem->parent() ); // required to enforce sibling relationships 00492 } 00493 00494 /*--------------------------------------------------------------------------------------*/ 00495 00496 bool GCDomTreeWidget::parentTreeAlreadyContainsElement( const GCTreeWidgetItem* item, const QString& element ) 00497 { 00498 while( item->gcParent() ) 00499 { 00500 if( item->gcParent()->name() == element ) 00501 { 00502 return true; 00503 } 00504 00505 item = item->gcParent(); 00506 return parentTreeAlreadyContainsElement( item, element ); 00507 } 00508 00509 return false; 00510 } 00511 00512 /*--------------------------------------------------------------------------------------*/ 00513 00514 void GCDomTreeWidget::addItem( const QString& element, bool toParent ) 00515 { 00516 if( m_activeItem ) 00517 { 00518 if( toParent ) 00519 { 00520 insertItem( element, m_activeItem->parent()->indexOfChild( m_activeItem ), toParent ); 00521 } 00522 else 00523 { 00524 insertItem( element, m_activeItem->childCount() - 1, toParent ); 00525 } 00526 } 00527 else 00528 { 00529 insertItem( element, 0, toParent ); 00530 } 00531 } 00532 00533 /*--------------------------------------------------------------------------------------*/ 00534 00535 void GCDomTreeWidget::insertItem( const QString& elementName, int index, bool toParent ) 00536 { 00537 QDomElement element = m_domDoc->createElement( elementName ); 00538 00539 /* Create all the possible attributes for the element here, they can be changed 00540 later on. */ 00541 QStringList attributeNames = GCDataBaseInterface::instance()->attributes( elementName ); 00542 00543 for( int i = 0; i < attributeNames.count(); ++i ) 00544 { 00545 element.setAttribute( attributeNames.at( i ), "" ); 00546 } 00547 00548 GCTreeWidgetItem* item = new GCTreeWidgetItem( element, m_items.size() ); 00549 m_items.append( item ); 00550 00551 if( m_isEmpty ) 00552 { 00553 invisibleRootItem()->addChild( item ); // takes ownership 00554 m_domDoc->appendChild( element ); 00555 m_isEmpty = false; 00556 } 00557 else 00558 { 00559 if( !toParent ) 00560 { 00561 m_activeItem->insertGcChild( index, item ); 00562 } 00563 else 00564 { 00565 m_activeItem->gcParent()->insertGcChild( index, item ); 00566 } 00567 } 00568 00569 /* I will have to rethink this approach if it turns out that it is too expensive to 00570 iterate through the tree on each and every addition...for now, this is the easiest 00571 solution, even if not the best. */ 00572 updateIndices(); 00573 00574 setCurrentItem( item ); 00575 } 00576 00577 /*--------------------------------------------------------------------------------------*/ 00578 00579 void GCDomTreeWidget::setCurrentItemFromIndex( int index ) 00580 { 00581 index = ( index < 0 ) ? 0 : index; 00582 00583 for( int i = 0; i < m_items.size(); ++i ) 00584 { 00585 if( m_items.at( i )->index() == index ) 00586 { 00587 emitGcCurrentItemSelected( m_items.at( i ), 0 ); 00588 break; 00589 } 00590 } 00591 } 00592 00593 /*--------------------------------------------------------------------------------------*/ 00594 00595 void GCDomTreeWidget::setAllCheckStates( Qt::CheckState state ) 00596 { 00597 m_busyIterating = true; 00598 00599 QTreeWidgetItemIterator iterator( this ); 00600 00601 while( *iterator ) 00602 { 00603 ( *iterator )->setCheckState( 0, state ); 00604 ++iterator; 00605 } 00606 00607 m_busyIterating = false; 00608 } 00609 00610 /*--------------------------------------------------------------------------------------*/ 00611 00612 void GCDomTreeWidget::setShowTreeItemsVerbose( bool verbose ) 00613 { 00614 m_busyIterating = true; 00615 00616 QTreeWidgetItemIterator iterator( this ); 00617 00618 while( *iterator ) 00619 { 00620 GCTreeWidgetItem* treeItem = dynamic_cast< GCTreeWidgetItem* >( *iterator ); 00621 treeItem->setVerbose( verbose ); 00622 ++iterator; 00623 } 00624 00625 m_busyIterating = false; 00626 } 00627 00628 /*--------------------------------------------------------------------------------------*/ 00629 00630 void GCDomTreeWidget::updateIndices() 00631 { 00632 m_busyIterating = true; 00633 00634 QTreeWidgetItemIterator iterator( this ); 00635 int index = 0; 00636 00637 while( *iterator ) 00638 { 00639 GCTreeWidgetItem* treeItem = dynamic_cast< GCTreeWidgetItem* >( *iterator ); 00640 treeItem->setIndex( index ); 00641 ++index; 00642 ++iterator; 00643 } 00644 00645 m_busyIterating = false; 00646 } 00647 00648 /*--------------------------------------------------------------------------------------*/ 00649 00650 GCTreeWidgetItem* GCDomTreeWidget::gcItemFromNode( QDomNode element ) 00651 { 00652 GCTreeWidgetItem* parentItem = NULL; 00653 00654 if( !element.isNull() ) 00655 { 00656 for( int i = 0; i < m_items.size(); ++i ) 00657 { 00658 if( m_items.at( i )->element() == element ) 00659 { 00660 parentItem = m_items.at( i ); 00661 break; 00662 } 00663 } 00664 } 00665 00666 return parentItem; 00667 } 00668 00669 /*--------------------------------------------------------------------------------------*/ 00670 00671 void GCDomTreeWidget::removeFromList( GCTreeWidgetItem* item ) 00672 { 00673 for( int i = 0; i < item->childCount(); ++i ) 00674 { 00675 GCTreeWidgetItem* childItem = item->gcChild( i ); 00676 removeFromList( childItem ); 00677 } 00678 00679 m_items.removeAll( item ); 00680 } 00681 00682 /*--------------------------------------------------------------------------------------*/ 00683 00684 void GCDomTreeWidget::populateCommentList( QDomNode node ) 00685 { 00686 QDomNode childNode = node.firstChild(); 00687 00688 while( !childNode.isNull() ) 00689 { 00690 if( childNode.isComment() ) 00691 { 00692 m_comments.append( childNode.toComment() ); 00693 } 00694 00695 populateCommentList( childNode ); 00696 childNode = childNode.nextSibling(); 00697 } 00698 } 00699 00700 /*--------------------------------------------------------------------------------------*/ 00701 00702 void GCDomTreeWidget::dropEvent( QDropEvent* event ) 00703 { 00704 m_itemBeingManipulated = true; 00705 00706 QTreeWidget::dropEvent( event ); 00707 DropIndicatorPosition indicatorPos = dropIndicatorPosition(); 00708 00709 if( m_activeItem ) 00710 { 00711 bool moveComment = false; 00712 00713 if( m_activeItem->element().previousSibling().isComment() ) 00714 { 00715 m_commentNode = m_activeItem->element().previousSibling().toComment(); 00716 moveComment = true; 00717 } 00718 00719 QDomElement previousParent = m_activeItem->element().parentNode().toElement(); 00720 previousParent.removeChild( m_activeItem->element() ); 00721 00722 GCTreeWidgetItem* parent = m_activeItem->gcParent(); 00723 00724 if( parent ) 00725 { 00726 if( indicatorPos == QAbstractItemView::OnItem ) 00727 { 00728 parent->element().appendChild( m_activeItem->element() ); 00729 } 00730 else if( indicatorPos == QAbstractItemView::AboveItem || 00731 indicatorPos == QAbstractItemView::BelowItem ) 00732 { 00733 GCTreeWidgetItem* sibling = NULL; 00734 int pos = parent->indexOfChild( m_activeItem ); 00735 00736 if( pos > 0 ) 00737 { 00738 sibling = parent->gcChild( pos - 1 ); 00739 00740 if( sibling ) 00741 { 00742 parent->element().insertAfter( m_activeItem->element(), sibling->element() ); 00743 } 00744 } 00745 else if( pos == 0 ) 00746 { 00747 sibling = parent->gcChild( pos + 1 ); 00748 00749 if( sibling ) 00750 { 00751 parent->element().insertBefore( m_activeItem->element(), sibling->element() ); 00752 } 00753 } 00754 else 00755 { 00756 parent->element().appendChild( m_activeItem->element() ); 00757 } 00758 } 00759 00760 /* Move the associated comment (if any). */ 00761 if( moveComment ) 00762 { 00763 m_commentNode.parentNode().removeChild( m_commentNode ); 00764 m_activeItem->element().parentNode().insertBefore( m_commentNode, m_activeItem->element() ); 00765 } 00766 00767 /* Update the database to reflect the re-parenting. */ 00768 GCDataBaseInterface::instance()->updateElementChildren( parent->name(), QStringList( m_activeItem->name() ) ); 00769 } 00770 00771 expandItem( parent ); 00772 } 00773 00774 updateIndices(); 00775 emitGcCurrentItemChanged( m_activeItem, 0 ); 00776 m_itemBeingManipulated = false; 00777 } 00778 00779 /*--------------------------------------------------------------------------------------*/ 00780 00781 void GCDomTreeWidget::keyPressEvent( QKeyEvent* event ) 00782 { 00783 if( event->key() == Qt::Key_Delete ) 00784 { 00785 removeItem(); 00786 } 00787 else if( event->key() == Qt::Key_Up || 00788 event->key() == Qt::Key_Down ) 00789 { 00790 QTreeWidget::keyPressEvent( event ); 00791 emitGcCurrentItemSelected( m_activeItem, 0 ); 00792 } 00793 else 00794 { 00795 QTreeWidget::keyPressEvent( event ); 00796 } 00797 } 00798 /*--------------------------------------------------------------------------------------*/ 00799 00800 void GCDomTreeWidget::currentGcItemChanged( QTreeWidgetItem* current, QTreeWidgetItem* previous ) 00801 { 00802 Q_UNUSED( previous ); 00803 00804 if( !m_itemBeingManipulated ) 00805 { 00806 m_activeItem = dynamic_cast< GCTreeWidgetItem* >( current ); 00807 } 00808 } 00809 00810 /*--------------------------------------------------------------------------------------*/ 00811 00812 void GCDomTreeWidget::emitGcCurrentItemSelected( QTreeWidgetItem* item, int column ) 00813 { 00814 if( !m_busyIterating ) 00815 { 00816 setCurrentItem( item, column ); 00817 00818 if( m_activeItem ) 00819 { 00820 /* Returns NULL object if not a comment. */ 00821 m_commentNode = m_activeItem->element().previousSibling().toComment(); 00822 } 00823 00824 emit gcCurrentItemSelected( m_activeItem, column ); 00825 } 00826 } 00827 00828 /*--------------------------------------------------------------------------------------*/ 00829 00830 void GCDomTreeWidget::emitGcCurrentItemChanged( QTreeWidgetItem* item, int column ) 00831 { 00832 if( !m_busyIterating ) 00833 { 00834 setCurrentItem( item, column ); 00835 00836 if( m_activeItem ) 00837 { 00838 /* Returns NULL object if not a comment. */ 00839 m_commentNode = m_activeItem->element().previousSibling().toComment(); 00840 } 00841 00842 emit gcCurrentItemChanged( m_activeItem, column ); 00843 } 00844 } 00845 00846 /*--------------------------------------------------------------------------------------*/ 00847 00848 void GCDomTreeWidget::renameItem() 00849 { 00850 QString newName = QInputDialog::getText( this, "Change element name", "Enter the element's new name:" ); 00851 00852 if( !newName.isEmpty() && m_activeItem ) 00853 { 00854 QString oldName = m_activeItem->name(); 00855 m_activeItem->rename( newName ); 00856 updateItemNames( oldName, newName ); 00857 00858 /* The name change may introduce a new element too so we can safely call "addElement" below as 00859 it doesn't do anything if the element already exists in the database, yet it will obviously 00860 add the element if it doesn't. In the latter case, the children and attributes associated with 00861 the old name will be assigned to the new element in the process. */ 00862 QStringList attributes = GCDataBaseInterface::instance()->attributes( oldName ); 00863 QStringList children = GCDataBaseInterface::instance()->children( oldName ); 00864 00865 if( !GCDataBaseInterface::instance()->addElement( newName, children, attributes ) ) 00866 { 00867 GCMessageSpace::showErrorMessageBox( this, GCDataBaseInterface::instance()->lastError() ); 00868 } 00869 00870 /* If we are, in fact, dealing with a new element, we also want the new element's associated attributes 00871 to be updated with the known values of these attributes. */ 00872 foreach( QString attribute, attributes ) 00873 { 00874 QStringList attributeValues = GCDataBaseInterface::instance()->attributeValues( oldName, attribute ); 00875 00876 if( !GCDataBaseInterface::instance()->updateAttributeValues( newName, attribute, attributeValues ) ) 00877 { 00878 GCMessageSpace::showErrorMessageBox( this, GCDataBaseInterface::instance()->lastError() ); 00879 } 00880 } 00881 00882 emitGcCurrentItemChanged( m_activeItem, 0 ); 00883 } 00884 } 00885 00886 /*--------------------------------------------------------------------------------------*/ 00887 00888 void GCDomTreeWidget::removeItem() 00889 { 00890 if( m_activeItem ) 00891 { 00892 m_itemBeingManipulated = true; 00893 00894 /* I think it is safe to assume that comment nodes will exist just above an element 00895 although it might not always be the case that a multi-line comment exists within 00896 a single set of comment tags. However, for those cases, it's the user's responsibility 00897 to clean them up as we cannot assume to know which of these comments will go with the 00898 element being removed. */ 00899 if( !m_commentNode.isNull() ) 00900 { 00901 /* Check if the comment is an actual comment or if it's valid XML that's been 00902 commented out (we don't want to remove snippets). */ 00903 QDomDocument doc; 00904 00905 if( !doc.setContent( m_commentNode.nodeValue() ) ) 00906 { 00907 m_comments.removeAll( m_commentNode ); 00908 m_commentNode.parentNode().removeChild( m_commentNode ); 00909 } 00910 } 00911 00912 /* Remove the element from the DOM first. */ 00913 QDomNode parentNode = m_activeItem->element().parentNode(); 00914 parentNode.removeChild( m_activeItem->element() ); 00915 00916 /* Now whack it. */ 00917 if( m_activeItem->gcParent() ) 00918 { 00919 GCTreeWidgetItem* parentItem = m_activeItem->gcParent(); 00920 parentItem->removeChild( m_activeItem ); 00921 } 00922 else 00923 { 00924 invisibleRootItem()->removeChild( m_activeItem ); 00925 } 00926 00927 removeFromList( m_activeItem ); 00928 m_isEmpty = m_items.isEmpty(); 00929 00930 delete m_activeItem; 00931 m_activeItem = gcCurrentItem(); 00932 00933 updateIndices(); 00934 emitGcCurrentItemChanged( m_activeItem, 0 ); 00935 m_itemBeingManipulated = false; 00936 } 00937 } 00938 00939 /*--------------------------------------------------------------------------------------*/ 00940 00941 void GCDomTreeWidget::stepUp() 00942 { 00943 if( m_activeItem ) 00944 { 00945 m_itemBeingManipulated = true; 00946 00947 GCTreeWidgetItem* parentItem = m_activeItem->gcParent(); 00948 00949 if( parentItem ) 00950 { 00951 GCTreeWidgetItem* grandParent = parentItem->gcParent(); 00952 00953 if( grandParent ) 00954 { 00955 parentItem->removeChild( m_activeItem ); 00956 grandParent->insertChild( grandParent->indexOfChild( parentItem ), m_activeItem ); 00957 grandParent->element().insertBefore( m_activeItem->element(), parentItem->element() ); 00958 00959 /* Update the database to reflect the re-parenting. */ 00960 GCDataBaseInterface::instance()->updateElementChildren( grandParent->name(), QStringList( m_activeItem->name() ) ); 00961 } 00962 00963 updateIndices(); 00964 emitGcCurrentItemChanged( m_activeItem, 0 ); 00965 } 00966 00967 m_itemBeingManipulated = false; 00968 } 00969 } 00970 00971 /*--------------------------------------------------------------------------------------*/ 00972 00973 void GCDomTreeWidget::stepDown() 00974 { 00975 if( m_activeItem ) 00976 { 00977 m_itemBeingManipulated = true; 00978 00979 GCTreeWidgetItem* parentItem = m_activeItem->gcParent(); 00980 GCTreeWidgetItem* siblingItem = gcItemFromNode( m_activeItem->element().previousSiblingElement() ); 00981 00982 /* Try again in the opposite direction. */ 00983 if( !siblingItem ) 00984 { 00985 siblingItem = gcItemFromNode( m_activeItem->element().nextSiblingElement() ); 00986 } 00987 00988 if( siblingItem && parentItem ) 00989 { 00990 parentItem->removeChild( m_activeItem ); 00991 siblingItem->insertChild( 0, m_activeItem ); 00992 siblingItem->element().insertBefore( m_activeItem->element(), siblingItem->element().firstChild() ); 00993 00994 /* Update the database to reflect the re-parenting. */ 00995 GCDataBaseInterface::instance()->updateElementChildren( siblingItem->name(), QStringList( m_activeItem->name() ) ); 00996 00997 updateIndices(); 00998 emitGcCurrentItemChanged( m_activeItem, 0 ); 00999 } 01000 01001 m_itemBeingManipulated = false; 01002 } 01003 } 01004 01005 /*--------------------------------------------------------------------------------------*/ 01006 01007 void GCDomTreeWidget::expand() 01008 { 01009 expandItem( m_activeItem ); 01010 } 01011 01012 /*--------------------------------------------------------------------------------------*/ 01013 01014 void GCDomTreeWidget::collapse() 01015 { 01016 collapseItem( m_activeItem ); 01017 } 01018 01019 /*--------------------------------------------------------------------------------------*/ 01020 01021 void GCDomTreeWidget::clearAndReset() 01022 { 01023 clear(); 01024 m_domDoc->clear(); 01025 m_items.clear(); 01026 m_isEmpty = true; 01027 } 01028 01029 /*--------------------------------------------------------------------------------------*/