XML Mill  1.0.0
A GUI based XML editor with a memory.
gcdomtreewidget.cpp
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 /*--------------------------------------------------------------------------------------*/