XML Mill  1.0.0
A GUI based XML editor with a memory.
gcmainwindow.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 "gcmainwindow.h"
00030 #include "ui_gcmainwindow.h"
00031 #include "db/gcdatabaseinterface.h"
00032 #include "db/gcdbsessionmanager.h"
00033 #include "forms/gcadditemsform.h"
00034 #include "forms/gcremoveitemsform.h"
00035 #include "forms/gchelpdialog.h"
00036 #include "forms/gcsearchform.h"
00037 #include "forms/gcaddsnippetsform.h"
00038 #include "forms/gcrestorefilesform.h"
00039 #include "utils/gctreewidgetitem.h"
00040 #include "utils/gccombobox.h"
00041 #include "utils/gcmessagespace.h"
00042 #include "utils/gcglobalspace.h"
00043 
00044 #include <QDesktopServices>
00045 #include <QSignalMapper>
00046 #include <QDomDocument>
00047 #include <QTextBlock>
00048 #include <QMessageBox>
00049 #include <QFileDialog>
00050 #include <QTextStream>
00051 #include <QTextCursor>
00052 #include <QComboBox>
00053 #include <QTimer>
00054 #include <QLabel>
00055 #include <QUrl>
00056 #include <QCloseEvent>
00057 #include <QFont>
00058 #include <QScrollBar>
00059 #include <QMovie>
00060 #include <QSettings>
00061 
00062 /*--------------------------------------------------------------------------------------*/
00063 
00064 const QString EMPTY( "---" );
00065 const QString LEFTRIGHTBRACKETS( "\\[|\\]" );
00066 
00067 const qint64 DOMWARNING( 262144 );  // 0.25MB or ~7 500 lines
00068 const qint64 DOMLIMIT  ( 524288 );  // 0.5MB  or ~15 000 lines
00069 
00070 const int ATTRIBUTECOLUMN = 0;
00071 const int VALUESCOLUMN = 1;
00072 
00073 /*--------------------------------- MEMBER FUNCTIONS ----------------------------------*/
00074 
00075 GCMainWindow::GCMainWindow( QWidget* parent )
00076 : QMainWindow               ( parent ),
00077   ui                        ( new Ui::GCMainWindow ),
00078   m_signalMapper            ( new QSignalMapper( this ) ),
00079   m_activeAttribute         ( NULL ),
00080   m_currentCombo            ( NULL ),
00081   m_saveTimer               ( NULL ),
00082   m_activeProfileLabel      ( NULL ),
00083   m_progressLabel           ( NULL ),
00084   m_spinner                 ( NULL ),
00085   m_currentXMLFileName      ( "" ),
00086   m_activeAttributeName     ( "" ),
00087   m_wasTreeItemActivated    ( false ),
00088   m_newAttributeAdded       ( false ),
00089   m_busyImporting           ( false ),
00090   m_fileContentsChanged     ( false ),
00091   m_comboBoxes              ()
00092 {
00093   ui->setupUi( this );
00094   ui->showEmptyProfileHelpButton->setVisible( false );
00095   ui->tableWidget->setFont( QFont( GCGlobalSpace::FONT, GCGlobalSpace::FONTSIZE ) );
00096   ui->tableWidget->horizontalHeader()->setFont( QFont( GCGlobalSpace::FONT, GCGlobalSpace::FONTSIZE ) );
00097 
00098   /* XML File related. */
00099   connect( ui->actionNew, SIGNAL( triggered() ), this, SLOT( newXMLFile() ) );
00100   connect( ui->actionOpen, SIGNAL( triggered() ), this, SLOT( openXMLFile() ) );
00101   connect( ui->actionSave, SIGNAL( triggered() ), this, SLOT( saveXMLFile() ) );
00102   connect( ui->actionSaveAs, SIGNAL( triggered() ), this, SLOT( saveXMLFileAs() ) );
00103   connect( ui->actionCloseFile, SIGNAL( triggered() ), this, SLOT( closeXMLFile() ) );
00104 
00105   /* Build/Edit XML. */
00106   connect( ui->addChildElementButton, SIGNAL( clicked() ), this, SLOT( addElementToDocument() ) );
00107   connect( ui->addSnippetButton, SIGNAL( clicked() ), this, SLOT( addSnippetToDocument() ) );
00108 
00109   /* Various other actions. */
00110   connect( ui->actionExit, SIGNAL( triggered() ), this, SLOT( close() ) );
00111   connect( ui->actionFind, SIGNAL( triggered() ), this, SLOT( searchDocument() ) );
00112   connect( ui->actionForgetPreferences, SIGNAL( triggered() ), this, SLOT( forgetMessagePreferences() ) );
00113   connect( ui->actionHelpContents, SIGNAL( triggered() ), this, SLOT( showMainHelp() ) );
00114   connect( ui->actionVisitOfficialSite, SIGNAL( triggered() ), this, SLOT( goToSite() ) );
00115   connect( ui->expandAllCheckBox, SIGNAL( clicked( bool ) ), this, SLOT( collapseOrExpandTreeWidget( bool ) ) );
00116   connect( ui->commentLineEdit, SIGNAL( textEdited( QString ) ), this, SLOT( updateComment( QString ) ) );
00117   connect( ui->actionUseDarkTheme, SIGNAL( triggered( bool ) ), this, SLOT( useDarkTheme( bool ) ) );
00118 
00119   connect( ui->wrapTextCheckBox, SIGNAL( clicked( bool ) ), ui->dockWidgetTextEdit, SLOT( wrapText( bool ) ) );
00120   connect( ui->dockWidgetTextEdit, SIGNAL( selectedIndex( int ) ), ui->treeWidget, SLOT( setCurrentItemFromIndex( int ) ) );
00121   connect( ui->dockWidgetTextEdit, SIGNAL( commentOut( const QList< int >&, const QString& ) ), this, SLOT( commentOut( const QList< int >&, const QString& ) ) );
00122   connect( ui->dockWidgetTextEdit, SIGNAL( manualEditAccepted() ), this, SLOT( rebuild() ) );
00123 
00124   /* Help related. */
00125   connect( ui->actionShowHelpButtons, SIGNAL( triggered( bool ) ), this, SLOT( setShowHelpButtons( bool ) ) );
00126   connect( ui->showEmptyProfileHelpButton, SIGNAL( clicked() ), this, SLOT( showEmptyProfileHelp() ) );
00127   connect( ui->showAddElementHelpButton, SIGNAL( clicked() ), this, SLOT( showElementHelp() ) );
00128 
00129   /* Everything tree widget related. */
00130   connect( ui->treeWidget, SIGNAL( gcCurrentItemSelected( GCTreeWidgetItem*, int ) ), this, SLOT( elementSelected( GCTreeWidgetItem*, int ) ) );
00131   connect( ui->treeWidget, SIGNAL( gcCurrentItemChanged( GCTreeWidgetItem*, int ) ), this, SLOT( elementChanged( GCTreeWidgetItem*, int ) ) );
00132   connect( ui->treeWidget, SIGNAL( collapsed( QModelIndex ) ), this, SLOT( uncheckExpandAll() ) );
00133   connect( ui->actionShowTreeElementsVerbose, SIGNAL( triggered( bool ) ), this, SLOT( setShowTreeItemsVerbose( bool ) ) );
00134 
00135   /* Everything table widget related. */
00136   connect( ui->tableWidget, SIGNAL( itemClicked( QTableWidgetItem* ) ), this, SLOT( attributeSelected( QTableWidgetItem* ) ) );
00137   connect( ui->tableWidget, SIGNAL( itemChanged( QTableWidgetItem* ) ), this, SLOT( attributeChanged( QTableWidgetItem* ) ) );
00138 
00139   /* Database related. */
00140   connect( ui->actionAddItems, SIGNAL( triggered() ), this, SLOT( addItemsToDB() ) );
00141   connect( ui->actionRemoveItems, SIGNAL( triggered() ), this, SLOT( removeItemsFromDB() ) );
00142   connect( ui->actionImportXMLToDatabase, SIGNAL( triggered() ), this, SLOT( importXMLFromFile() ) );
00143   connect( ui->actionSwitchSessionDatabase, SIGNAL( triggered() ), this, SLOT( switchActiveDatabase() ) );
00144   connect( ui->actionAddNewDatabase, SIGNAL( triggered() ), this, SLOT( addNewDatabase() ) );
00145   connect( ui->actionAddExistingDatabase, SIGNAL( triggered() ), this, SLOT( addExistingDatabase() ) );
00146   connect( ui->actionRemoveDatabase, SIGNAL( triggered() ), this, SLOT( removeDatabase() ) );
00147 
00148   connect( m_signalMapper, SIGNAL( mapped( QWidget* ) ), this, SLOT( setCurrentComboBox( QWidget* ) ) );
00149 
00150   readSettings();
00151 
00152   /* Wait for the event loop to be initialised before calling this function. */
00153   QTimer::singleShot( 0, this, SLOT( initialise() ) );
00154 }
00155 
00156 /*--------------------------------------------------------------------------------------*/
00157 
00158 GCMainWindow::~GCMainWindow()
00159 {
00160   delete ui;
00161 }
00162 
00163 /*--------------------------------------------------------------------------------------*/
00164 
00165 void GCMainWindow::closeEvent( QCloseEvent* event )
00166 {
00167   if( m_fileContentsChanged )
00168   {
00169     QMessageBox::StandardButtons accept = QMessageBox::question( this,
00170                                                                  "Save File?",
00171                                                                  "Save changes before closing?",
00172                                                                  QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
00173                                                                  QMessageBox::Yes );
00174 
00175     if( accept == QMessageBox::Yes )
00176     {
00177       saveXMLFile();
00178     }
00179     else if( accept == QMessageBox::Cancel )
00180     {
00181       event->ignore();
00182       return;
00183     }
00184   }
00185 
00186   deleteTempFile();
00187   saveSettings();
00188   QMainWindow::closeEvent( event );
00189 }
00190 
00191 /*--------------------------------------------------------------------------------------*/
00192 
00193 void GCMainWindow::initialise()
00194 {
00195   /* Initialise the database interface and retrieve the list of database names (this will
00196     include the path references to the ".db" files). */
00197   if( !GCDataBaseInterface::instance()->isInitialised() )
00198   {
00199     GCMessageSpace::showErrorMessageBox( this, GCDataBaseInterface::instance()->lastError() );
00200     this->close();
00201   }
00202 
00203   /* If the interface was successfully initialised, prompt the user to choose a database
00204     connection for this session. */
00205   GCDBSessionManager* manager = createDBSessionManager();
00206   manager->selectActiveDatabase();
00207   QDialog::DialogCode result = static_cast< QDialog::DialogCode >( manager->result() );
00208 
00209   if( result == QDialog::Rejected )
00210   {
00211     this->close();    // also deletes manager ("this" is parent)
00212   }
00213 
00214   queryRestoreFiles();
00215 }
00216 
00217 /*--------------------------------------------------------------------------------------*/
00218 
00219 void GCMainWindow::elementChanged( GCTreeWidgetItem* item, int column )
00220 {
00221   if( !ui->treeWidget->empty() )
00222   {
00223     elementSelected( item, column );
00224     setTextEditContent( item );
00225   }
00226   else
00227   {
00228     resetDOM();
00229   }
00230 }
00231 
00232 /*--------------------------------------------------------------------------------------*/
00233 
00234 void GCMainWindow::elementSelected( GCTreeWidgetItem* item, int column )
00235 {
00236   Q_UNUSED( column );
00237 
00238   if( item )
00239   {
00240     /* This flag is set to prevent the functionality in "attributeChanged" (which is triggered
00241     by the population of the table widget) from being executed until this function exits. */
00242     m_wasTreeItemActivated = true;
00243 
00244     resetTableWidget();
00245 
00246     QDomElement element = item->element();      // shallow copy
00247     QString elementName = item->name();
00248     QStringList attributeNames = GCDataBaseInterface::instance()->attributes( elementName );
00249 
00250     /* Add all the associated attribute names to the first column of the table widget,
00251     create and populate combo boxes with the attributes' known values and insert the
00252     combo boxes into the second column of the table widget. Finally, insert an "empty"
00253     row so that the user may add additional attributes and values to the current element. */
00254     for( int i = 0; i < attributeNames.count(); ++i )
00255     {
00256       GCComboBox* attributeCombo = new GCComboBox;
00257       QTableWidgetItem* label = new QTableWidgetItem( attributeNames.at( i ) );
00258       label->setFlags( label->flags() | Qt::ItemIsUserCheckable );
00259 
00260       if( item->attributeIncluded( attributeNames.at( i ) ) )
00261       {
00262         label->setCheckState( Qt::Checked );
00263         attributeCombo->setEnabled( true );
00264       }
00265       else
00266       {
00267         label->setCheckState( Qt::Unchecked );
00268         attributeCombo->setEnabled( false );
00269       }
00270 
00271       ui->tableWidget->setRowCount( i + 1 );
00272       ui->tableWidget->setItem( i, 0, label );
00273 
00274       attributeCombo->addItems( GCDataBaseInterface::instance()->attributeValues( elementName, attributeNames.at( i ) ) );
00275       attributeCombo->setEditable( true );
00276 
00277       /* If we are still in the process of building the document, the attribute value will
00278       be empty since it has never been set before.  For this particular case, calling
00279       "findText" will result in a null pointer exception being thrown so we need to
00280       cater for this possibility here. */
00281       QString attributeValue = element.attribute( attributeNames.at( i ) );
00282 
00283       if( !attributeValue.isEmpty() )
00284       {
00285         attributeCombo->setCurrentIndex( attributeCombo->findText( attributeValue ) );
00286       }
00287       else
00288       {
00289         attributeCombo->setCurrentIndex( -1 );
00290       }
00291 
00292       /* Attempting the connection before we've set the current index above causes the
00293       "attributeValueChanged" slot to be called prematurely, resulting in a segmentation
00294       fault due to value conflicts/missing values (in short, we can't do the connect
00295       before we set the current index above). */
00296       connect( attributeCombo, SIGNAL( currentIndexChanged( QString ) ), this, SLOT( attributeValueChanged( QString ) ) );
00297 
00298       ui->tableWidget->setCellWidget( i, 1, attributeCombo );
00299       m_comboBoxes.insert( attributeCombo, i );
00300 
00301       /* This will point the current combo box member to the combo that's been activated
00302       in the table widget (used in "attributeValueChanged" to obtain the row number the
00303       combo box appears in in the table widget, etc, etc). */
00304       connect( attributeCombo, SIGNAL( activated( int ) ), m_signalMapper, SLOT( map() ) );
00305       m_signalMapper->setMapping( attributeCombo, attributeCombo );
00306     }
00307 
00308     /* Add the "empty" row as described above. */
00309     insertEmptyTableRow();
00310 
00311     /* Populate the "add child element" combo box with the known first level children of the
00312     current highlighted element (highlighted in the tree widget, of course). */
00313     ui->addElementComboBox->clear();
00314     ui->addElementComboBox->addItems( GCDataBaseInterface::instance()->children( elementName ) );
00315 
00316     /* The following will be used to allow the user to add an element of the current type to
00317     its parent (this should improve the user experience as they do not have to explicitly
00318     navigate back up to a parent when adding multiple items of the same type).  We also don't
00319     want the user to add a document root element to itself by accident. */
00320     if( !ui->treeWidget->matchesRootName( elementName ) )
00321     {
00322       ui->addElementComboBox->addItem( QString( "[%1]" ).arg( elementName ) );
00323     }
00324 
00325     toggleAddElementWidgets();
00326     highlightTextElement( item );
00327 
00328     /* The user must not be allowed to add an entire document as a "snippet". */
00329     if( ui->treeWidget->matchesRootName( ui->addElementComboBox->currentText() ) )
00330     {
00331       ui->addSnippetButton->setEnabled( false );
00332       ui->addChildElementButton->setText( "Add Root" );
00333     }
00334     else
00335     {
00336       ui->addSnippetButton->setEnabled( true );
00337       ui->addChildElementButton->setText( "Add Child" );
00338     }
00339 
00340     ui->commentLineEdit->setText( ui->treeWidget->activeCommentValue() );
00341 
00342     /* Unset flag. */
00343     m_wasTreeItemActivated = false;
00344   }
00345   else if( ui->treeWidget->empty() )
00346   {
00347     resetDOM();
00348   }
00349 }
00350 
00351 /*--------------------------------------------------------------------------------------*/
00352 
00353 void GCMainWindow::attributeChanged( QTableWidgetItem* tableItem )
00354 {
00355   /* Don't execute the logic if a tree widget item's activation is triggering
00356     a re-population of the table widget (which results in this slot being called). */
00357   if( !m_wasTreeItemActivated && !m_newAttributeAdded )
00358   {
00359     /* Also don't allow for empty attribute names. */
00360     if( tableItem->text().isEmpty() )
00361     {
00362       tableItem->setText( m_activeAttributeName );
00363       return;
00364     }
00365 
00366     /* When the check state of a table widget item changes, the "itemChanged" signal is emitted
00367       before the "itemClicked" one and this messes up the logic that follows completely.  Since
00368       I depend on knowing which attribute is currently active, I needed to insert the following
00369       odd little check (the other alternative is probably to subclass QTableWidgetItem but I'm
00370       worried about the added complications). */
00371     if( m_activeAttribute != tableItem )
00372     {
00373       m_activeAttributeName = tableItem->text();
00374     }
00375 
00376     /* All attribute name changes will be assumed to be additions, removing an attribute
00377       with a specific name has to be done explicitly. */
00378     GCTreeWidgetItem* treeItem = ui->treeWidget->gcCurrentItem();
00379 
00380     /* See if an existing attribute's name changed or if a new attribute was added. */
00381     if( tableItem->text() != m_activeAttributeName )
00382     {
00383       /* Add the new attribute's name to the current element's list of associated attributes. */
00384       if( GCDataBaseInterface::instance()->updateElementAttributes( treeItem->name(), QStringList( tableItem->text() ) ) )
00385       {
00386         /* Is this a name change? */
00387         if( m_activeAttributeName != EMPTY )
00388         {
00389           treeItem->excludeAttribute( m_activeAttributeName );
00390 
00391           /* Retrieve the list of values associated with the previous name and insert it against the new name. */
00392           QStringList attributeValues = GCDataBaseInterface::instance()->attributeValues( treeItem->name(),
00393                                                                                           m_activeAttributeName );
00394 
00395           if( !GCDataBaseInterface::instance()->updateAttributeValues( treeItem->name(), tableItem->text(), attributeValues ) )
00396           {
00397             GCMessageSpace::showErrorMessageBox( this, GCDataBaseInterface::instance()->lastError() );
00398           }
00399         }
00400         else
00401         {
00402           /* If this is an entirely new attribute, insert an "empty" row so that the user may add
00403             even more attributes if he/she wishes to do so. */
00404           m_newAttributeAdded = true;
00405           tableItem->setFlags( tableItem->flags() | Qt::ItemIsUserCheckable );
00406           tableItem->setCheckState( Qt::Checked );
00407           insertEmptyTableRow();
00408           m_newAttributeAdded = false;
00409         }
00410       }
00411       else
00412       {
00413         GCMessageSpace::showErrorMessageBox( this, GCDataBaseInterface::instance()->lastError() );
00414       }
00415     }
00416 
00417     m_activeAttributeName = tableItem->text();
00418 
00419     /* Is this attribute included or excluded? */
00420     GCComboBox* attributeValueCombo = dynamic_cast< GCComboBox* >( ui->tableWidget->cellWidget( tableItem->row(), VALUESCOLUMN ) );
00421 
00422     if( tableItem->checkState() == Qt::Checked )
00423     {
00424       attributeValueCombo->setEnabled( true );
00425       treeItem->includeAttribute( m_activeAttributeName, attributeValueCombo->currentText() );
00426     }
00427     else
00428     {
00429       attributeValueCombo->setEnabled( false );
00430       treeItem->excludeAttribute( m_activeAttributeName );
00431     }
00432 
00433     setTextEditContent( treeItem );
00434   }
00435 }
00436 
00437 /*--------------------------------------------------------------------------------------*/
00438 
00439 void GCMainWindow::attributeSelected( QTableWidgetItem* tableItem )
00440 {
00441   m_activeAttribute = tableItem;
00442   m_activeAttributeName = tableItem->text();
00443 }
00444 
00445 /*--------------------------------------------------------------------------------------*/
00446 
00447 void GCMainWindow::attributeValueChanged( const QString& value )
00448 {
00449   /* Don't execute the logic if a tree widget item's activation is triggering
00450     a re-population of the table widget (which results in this slot being called). */
00451   if( !m_wasTreeItemActivated && !value.isEmpty() )
00452   {
00453     GCTreeWidgetItem* treeItem = ui->treeWidget->gcCurrentItem();
00454     QString currentAttributeName = ui->tableWidget->item( m_comboBoxes.value( m_currentCombo ), ATTRIBUTECOLUMN )->text();
00455 
00456     /* If we don't know about this value, we need to add it to the DB. */
00457     QStringList attributeValues = GCDataBaseInterface::instance()->attributeValues( treeItem->name(), currentAttributeName );
00458 
00459     if( !attributeValues.contains( value ) )
00460     {
00461       if( !GCDataBaseInterface::instance()->updateAttributeValues( treeItem->name(),
00462                                                                    currentAttributeName,
00463                                                                    QStringList( value ) ) )
00464       {
00465         GCMessageSpace::showErrorMessageBox( this, GCDataBaseInterface::instance()->lastError() );
00466       }
00467     }
00468 
00469     treeItem->includeAttribute( currentAttributeName, value );
00470     setTextEditContent( treeItem );
00471   }
00472 }
00473 
00474 /*--------------------------------------------------------------------------------------*/
00475 
00476 void GCMainWindow::setCurrentComboBox( QWidget* combo )
00477 {
00478   m_currentCombo = combo;
00479 }
00480 
00481 /*--------------------------------------------------------------------------------------*/
00482 
00483 bool GCMainWindow::openXMLFile()
00484 {
00485   /* Can't open a file if there is no DB profile to describe it. */
00486   querySetActiveSession( QString( "No active profile set, please set one for this session." ) );
00487 
00488   if( !queryResetDOM( "Save document before continuing?" ) )
00489   {
00490     return false;
00491   }
00492 
00493   /* Start off where the user finished last. */
00494   QString fileName = QFileDialog::getOpenFileName( this, "Open File", GCGlobalSpace::lastUserSelectedDirectory(), "XML Files (*.*)" );
00495 
00496   /* If the user cancelled, we don't want to continue. */
00497   if( fileName.isEmpty() )
00498   {
00499     return false;
00500   }
00501 
00502   /* Note to future self: although the user would have explicitly saved (or not saved) the file
00503     by the time this functionality is encountered, we only reset the document once we have a new,
00504     active file to work with since users are fickle and may still change their minds.  In other
00505     words, do NOT move this code to before "getOpenFileName" as previously considered! */
00506   resetDOM();
00507   m_currentXMLFileName = "";
00508 
00509   QFile file( fileName );
00510 
00511   if( !file.open( QIODevice::ReadOnly | QIODevice::Text ) )
00512   {
00513     QString errorMsg = QString( "Failed to open file \"%1\": [%2]" )
00514       .arg( fileName )
00515       .arg( file.errorString() );
00516     GCMessageSpace::showErrorMessageBox( this, errorMsg );
00517     return false;
00518   }
00519 
00520   QTextStream inStream( &file );
00521   QString fileContent( inStream.readAll() );
00522   qint64 fileSize = file.size();
00523   file.close();
00524 
00525   /* This application isn't optimised for dealing with very large XML files (the entire point is that
00526     this suite should provide the functionality necessary for the manual manipulation of, e.g. XML config
00527     files normally set up by hand via copy and paste exercises), if this file is too large to be handled
00528     comfortably, we need to let the user know and also make sure that we don't try to set the DOM content
00529     as text in the QTextEdit (QTextEdit is optimised for paragraphs). */
00530   if( fileSize > DOMWARNING &&
00531       fileSize < DOMLIMIT )
00532   {
00533     QMessageBox::warning( this,
00534                           "Large file!",
00535                           "The file you just opened is pretty large. Response times may be slow." );
00536   }
00537   else if( fileSize > DOMLIMIT )
00538   {
00539     QMessageBox::critical( this,
00540                            "Very large file!",
00541                            "This file is too large to edit manually!" );
00542 
00543     return false;
00544   }
00545 
00546   QString xmlErr( "" );
00547   int line  ( -1 );
00548   int col   ( -1 );
00549 
00550   if( !ui->treeWidget->setContent( fileContent, &xmlErr, &line, &col ) )
00551   {
00552     QString errorMsg = QString( "XML is broken - Error [%1], line [%2], column [%3]" )
00553       .arg( xmlErr )
00554       .arg( line )
00555       .arg( col );
00556     GCMessageSpace::showErrorMessageBox( this, errorMsg );
00557     resetDOM();
00558     return false;
00559   }
00560 
00561   m_currentXMLFileName = fileName;
00562   m_fileContentsChanged = false;    // at first load, nothing has changed
00563 
00564   if( !m_busyImporting )
00565   {
00566     /* If the user is opening an XML file of a kind that isn't supported by the current active DB,
00567         we need to warn him/her of this fact and provide them with a couple of options. */
00568     if( !ui->treeWidget->documentCompatible() )
00569     {
00570       bool accepted = GCMessageSpace::userAccepted( "QueryImportXML",
00571                                                     "Import document?",
00572                                                     "Encountered unknown relationships - import differences to active profile?",
00573                                                     GCMessageSpace::YesNo,
00574                                                     GCMessageSpace::No,
00575                                                     GCMessageSpace::Question );
00576 
00577       if( accepted )
00578       {
00579         QTimer timer;
00580         timer.singleShot( 1000, this, SLOT( createSpinner() ) );
00581         qApp->processEvents( QEventLoop::ExcludeUserInputEvents );
00582 
00583         if( !ui->treeWidget->batchProcessSuccess() )
00584         {
00585           GCMessageSpace::showErrorMessageBox( this, GCDataBaseInterface::instance()->lastError() );
00586         }
00587         else
00588         {
00589           qApp->processEvents( QEventLoop::ExcludeUserInputEvents );
00590           processDOMDoc();
00591         }
00592 
00593         deleteSpinner();
00594       }
00595       else
00596       {
00597         /* If the user decided to rather abort the import, reset everything. */
00598         resetDOM();
00599         m_currentXMLFileName = "";
00600       }
00601     }
00602     else
00603     {
00604       /* If the user selected a database that knows of this particular XML profile,
00605           simply process the document. */
00606       processDOMDoc();
00607     }
00608   }
00609 
00610   /* Save whatever directory the user ended up in. */
00611   QFileInfo fileInfo( fileName );
00612   QString finalDirectory = fileInfo.dir().path();
00613   GCGlobalSpace::setLastUserSelectedDirectory( finalDirectory );
00614   return true;
00615 }
00616 
00617 /*--------------------------------------------------------------------------------------*/
00618 
00619 void GCMainWindow::newXMLFile()
00620 {
00621   if( queryResetDOM( "Save document before continuing?" ) )
00622   {
00623     resetDOM();
00624     m_currentXMLFileName = "";
00625     ui->actionCloseFile->setEnabled( true );
00626     ui->actionSaveAs->setEnabled( true );
00627     ui->actionSave->setEnabled( true );
00628   }
00629 }
00630 
00631 /*--------------------------------------------------------------------------------------*/
00632 
00633 bool GCMainWindow::saveXMLFile()
00634 {
00635   if( m_currentXMLFileName.isEmpty() )
00636   {
00637     return saveXMLFileAs();
00638   }
00639   else
00640   {
00641     QFile file( m_currentXMLFileName );
00642 
00643     if( !file.open( QIODevice::ReadWrite | QIODevice::Truncate | QIODevice::Text ) )
00644     {
00645       QString errMsg = QString( "Failed to save file \"%1\": [%2]." )
00646         .arg( m_currentXMLFileName )
00647         .arg( file.errorString() );
00648       GCMessageSpace::showErrorMessageBox( this, errMsg );
00649       return false;
00650     }
00651     else
00652     {
00653       QTextStream outStream( &file );
00654       outStream << ui->treeWidget->toString();
00655       file.close();
00656 
00657       m_fileContentsChanged = false;
00658       deleteTempFile();
00659       startSaveTimer();
00660     }
00661   }
00662 
00663   return true;
00664 }
00665 
00666 /*--------------------------------------------------------------------------------------*/
00667 
00668 bool GCMainWindow::saveXMLFileAs()
00669 {
00670   QString file = QFileDialog::getSaveFileName( this, "Save As", GCGlobalSpace::lastUserSelectedDirectory(), "XML Files (*.*)" );
00671 
00672   /* If the user clicked "OK". */
00673   if( !file.isEmpty() )
00674   {
00675     m_currentXMLFileName = file;
00676 
00677     /* Save the last visited directory. */
00678     QFileInfo fileInfo( m_currentXMLFileName );
00679     QString finalDirectory = fileInfo.dir().path();
00680     GCGlobalSpace::setLastUserSelectedDirectory( finalDirectory );
00681 
00682     return saveXMLFile();
00683   }
00684   else
00685   {
00686     /* Return false when the file save operation is cancelled so that "queryResetDom" does
00687       not inadvertently caused a file to be reset by accident when the user changes his/her mind. */
00688     return false;
00689   }
00690 }
00691 
00692 /*--------------------------------------------------------------------------------------*/
00693 
00694 void GCMainWindow::closeXMLFile()
00695 {
00696   if( queryResetDOM( "Save document before continuing?" ) )
00697   {
00698     resetDOM();
00699   }
00700 }
00701 
00702 /*--------------------------------------------------------------------------------------*/
00703 
00704 void GCMainWindow::importXMLFromFile()
00705 {
00706   /* This flag is used in "openXMLFile" to distinguish between an explicit import
00707     and a simple file opening operation. */
00708   m_busyImporting = true;
00709   ui->treeWidget->setVisible( false );
00710 
00711   if( openXMLFile() )
00712   {
00713     if( importXMLToDatabase() )
00714     {
00715       if( m_progressLabel )
00716       {
00717         m_progressLabel->hide();
00718       }
00719 
00720       QMessageBox::StandardButtons accept = QMessageBox::question( this,
00721                                                                    "Edit file",
00722                                                                    "Also open file for editing?",
00723                                                                    QMessageBox::Yes | QMessageBox::No,
00724                                                                    QMessageBox::Yes );
00725 
00726       if( accept == QMessageBox::Yes )
00727       {
00728         QTimer timer;
00729 
00730         if( m_progressLabel )
00731         {
00732           timer.singleShot( 1000, m_progressLabel, SLOT( show() ) );
00733         }
00734 
00735         qApp->processEvents( QEventLoop::ExcludeUserInputEvents );
00736         processDOMDoc();
00737       }
00738       else
00739       {
00740         /* DOM was set in the process of opening the XML file and loading its content.  If the user
00741             doesn't want to work with the file that was imported, we need to reset it here. */
00742         resetDOM();
00743         m_currentXMLFileName = "";
00744       }
00745     }
00746   }
00747 
00748   ui->treeWidget->setVisible( true );
00749   m_busyImporting = false;
00750 }
00751 
00752 /*--------------------------------------------------------------------------------------*/
00753 
00754 bool GCMainWindow::importXMLToDatabase()
00755 {
00756   createSpinner();
00757   qApp->processEvents( QEventLoop::ExcludeUserInputEvents );
00758 
00759   if( !ui->treeWidget->batchProcessSuccess() )
00760   {
00761     GCMessageSpace::showErrorMessageBox( this, GCDataBaseInterface::instance()->lastError() );
00762     deleteSpinner();
00763     return false;
00764   }
00765 
00766   deleteSpinner();
00767   return true;
00768 }
00769 
00770 /*--------------------------------------------------------------------------------------*/
00771 
00772 void GCMainWindow::addNewDatabase()
00773 {
00774   GCDBSessionManager* manager = createDBSessionManager();
00775 
00776   /* If we have an active DOM document, we need to pass the name of the root
00777     element through to the DB session manager which uses it to determine whether
00778     or not a user is on the verge of messing something up... */
00779   if( ui->treeWidget->empty() )
00780   {
00781     manager->addNewDatabase();
00782   }
00783   else
00784   {
00785     manager->addNewDatabase( ui->treeWidget->rootName() );
00786   }
00787 
00788   delete manager;
00789 
00790   /* Ensure that the user sets a database as active for this session. */
00791   querySetActiveSession( QString( "No active profile set, please set one for this session." ) );
00792 }
00793 
00794 /*--------------------------------------------------------------------------------------*/
00795 
00796 void GCMainWindow::addExistingDatabase()
00797 {
00798   GCDBSessionManager* manager = createDBSessionManager();
00799 
00800   /* If we have an active DOM document, we need to pass the name of the root
00801     element through to the DB session manager which uses it to determine whether
00802     or not a user is on the verge of messing something up... */
00803   if( ui->treeWidget->empty() )
00804   {
00805     manager->addExistingDatabase();
00806   }
00807   else
00808   {
00809     manager->addExistingDatabase( ui->treeWidget->rootName() );
00810   }
00811 
00812   delete manager;
00813 
00814   /* Ensure that the user sets a database as active for this session. */
00815   querySetActiveSession( QString( "No active profile set, please set one for this session." ) );
00816 }
00817 
00818 /*--------------------------------------------------------------------------------------*/
00819 
00820 void GCMainWindow::removeDatabase()
00821 {
00822   GCDBSessionManager* manager = createDBSessionManager();
00823 
00824   /* If we have an active DOM document, we need to pass the name of the root
00825     element through to the DB session manager which uses it to determine whether
00826     or not a user is on the verge of messing something up... */
00827   if( ui->treeWidget->empty() )
00828   {
00829     manager->removeDatabase();
00830   }
00831   else
00832   {
00833     manager->removeDatabase( ui->treeWidget->rootName() );
00834   }
00835 
00836   delete manager;
00837 
00838   /* If the user removed the active DB for this session, we need to know
00839     what he/she intends to replace it with. */
00840   querySetActiveSession( QString( "The active profile has been removed, please select another." ) );
00841 }
00842 
00843 /*--------------------------------------------------------------------------------------*/
00844 
00845 void GCMainWindow::switchActiveDatabase()
00846 {
00847   GCDBSessionManager* manager = createDBSessionManager();
00848 
00849   /* If we have an active DOM document, we need to pass the name of the root
00850     element through to the DB session manager which uses it to determine whether
00851     or not a user is on the verge of messing something up... */
00852   if( ui->treeWidget->empty() )
00853   {
00854     manager->selectActiveDatabase();
00855   }
00856   else
00857   {
00858     manager->selectActiveDatabase( ui->treeWidget->rootName() );
00859   }
00860 
00861   delete manager;
00862 }
00863 
00864 /*--------------------------------------------------------------------------------------*/
00865 
00866 void GCMainWindow::activeDatabaseChanged( QString dbName )
00867 {
00868   if( ui->treeWidget->empty() )
00869   {
00870     resetDOM();
00871   }
00872 
00873   /* If the user set an empty database, prompt to populate it.  This message must
00874     always be shown (i.e. we don't have to show the custom dialog box that provides
00875     the \"Don't show this again\" option). */
00876   if( GCDataBaseInterface::instance()->isProfileEmpty() )
00877   {
00878     QMessageBox::StandardButton accepted = QMessageBox::warning( this,
00879                                                                  "Empty Profile",
00880                                                                  "Empty profile selected. Import XML from file?",
00881                                                                  QMessageBox::Yes | QMessageBox::No,
00882                                                                  QMessageBox::Yes );
00883 
00884     if( accepted == QMessageBox::Yes )
00885     {
00886       importXMLFromFile();
00887     }
00888     else
00889     {
00890       addItemsToDB();
00891     }
00892   }
00893 
00894   if( !m_activeProfileLabel )
00895   {
00896     m_activeProfileLabel = new QLabel( QString( "Active Profile: %1" ).arg( dbName ) );
00897     statusBar()->addWidget( m_activeProfileLabel );
00898   }
00899   else
00900   {
00901     m_activeProfileLabel->setText( QString( "Active Profile: %1" ).arg( dbName ) );
00902   }
00903 }
00904 
00905 /*--------------------------------------------------------------------------------------*/
00906 
00907 void GCMainWindow::addElementToDocument()
00908 {
00909   QString elementName = ui->addElementComboBox->currentText();
00910   bool treeWasEmpty = ui->treeWidget->empty();
00911   bool addToParent = false;
00912 
00913   /* If the user selected the <element> option, we add a new sibling element
00914     of the same name as the current element to the current element's parent. */
00915   if( elementName.contains( QRegExp( LEFTRIGHTBRACKETS ) ) )
00916   {
00917     elementName = elementName.remove( QRegExp( LEFTRIGHTBRACKETS ) );
00918     addToParent = true;
00919   }
00920 
00921   /* There is probably no chance of this ever happening, but defensive programming FTW! */
00922   if( !elementName.isEmpty() )
00923   {
00924     /* Update the tree widget. */
00925     ui->treeWidget->addItem( elementName, addToParent );
00926 
00927     GCTreeWidgetItem* treeItem = ui->treeWidget->gcCurrentItem();
00928     treeItem->setFlags( treeItem->flags() | Qt::ItemIsEditable );
00929 
00930     /* If the user starts creating a DOM document without having explicitly asked for
00931     a new file to be created, do it automatically (we can't call "newXMLFile here" since
00932     it resets the DOM document). */
00933     if( treeWasEmpty )
00934     {
00935       m_currentXMLFileName = "";
00936       ui->actionCloseFile->setEnabled( true );
00937       ui->actionSave->setEnabled( true );
00938       ui->actionSaveAs->setEnabled( true );
00939       ui->addSnippetButton->setEnabled( true );
00940       ui->addChildElementButton->setText( "Add Child" );
00941     }
00942 
00943     /* Add all the known attributes associated with this element name to the new element. */
00944     QStringList attributes = GCDataBaseInterface::instance()->attributes( elementName );
00945 
00946     for( int i = 0; i < attributes.size(); ++i )
00947     {
00948       treeItem->element().setAttribute( attributes.at( i ), QString( "" ) );
00949     }
00950 
00951     setTextEditContent( treeItem );
00952     elementSelected( treeItem, 0 );
00953   }
00954 }
00955 
00956 /*--------------------------------------------------------------------------------------*/
00957 
00958 void GCMainWindow::addSnippetToDocument()
00959 {
00960   QString elementName = ui->addElementComboBox->currentText();
00961 
00962   /* Check if we're inserting snippets as children, or as siblings. */
00963   if( elementName.contains( QRegExp( LEFTRIGHTBRACKETS ) ) )
00964   {
00965     /* Qt::WA_DeleteOnClose flag set. */
00966     GCAddSnippetsForm* dialog = new GCAddSnippetsForm( elementName.remove( QRegExp( LEFTRIGHTBRACKETS ) ),
00967                                                        ui->treeWidget->gcCurrentItem()->gcParent(),
00968                                                        this );
00969     connect( dialog, SIGNAL( snippetAdded( GCTreeWidgetItem*, QDomElement ) ), this, SLOT( insertSnippet( GCTreeWidgetItem*, QDomElement ) ) );
00970     dialog->exec();
00971   }
00972   else
00973   {
00974     /* Qt::WA_DeleteOnClose flag set. */
00975     GCAddSnippetsForm* dialog = new GCAddSnippetsForm( elementName,
00976                                                        ui->treeWidget->gcCurrentItem(),
00977                                                        this );
00978     connect( dialog, SIGNAL( snippetAdded( GCTreeWidgetItem*, QDomElement ) ), this, SLOT( insertSnippet( GCTreeWidgetItem*, QDomElement ) ) );
00979     dialog->exec();
00980   }
00981 }
00982 
00983 /*--------------------------------------------------------------------------------------*/
00984 
00985 void GCMainWindow::insertSnippet( GCTreeWidgetItem* treeItem, QDomElement element )
00986 {
00987   ui->treeWidget->appendSnippet( treeItem, element );
00988   ui->treeWidget->expandAll();
00989   setTextEditContent();
00990   itemFound( treeItem->gcChild( treeItem->childCount() - 1 ) );
00991   m_fileContentsChanged = true;
00992 }
00993 
00994 /*--------------------------------------------------------------------------------------*/
00995 
00996 void GCMainWindow::removeItemsFromDB()
00997 {
00998   if( !GCDataBaseInterface::instance()->hasActiveSession() )
00999   {
01000     GCMessageSpace::showErrorMessageBox( this, "No active profile." );
01001     return;
01002   }
01003 
01004   if( GCDataBaseInterface::instance()->isProfileEmpty() )
01005   {
01006     QMessageBox::warning( this,
01007                           "Profile Empty",
01008                           "Active profile empty, nothing to remove." );
01009     return;
01010   }
01011 
01012   if( !ui->treeWidget->empty() )
01013   {
01014     GCMessageSpace::showErrorMessageBox( this, "For practical reasons, items cannot be removed when documents are open.\n"
01015                                          "Please \"Save\" and/or \"Close\" the current document before returning back here." );
01016     return;
01017   }
01018 
01019   /* Delete on close flag set (no clean-up needed). */
01020   GCRemoveItemsForm* dialog = new GCRemoveItemsForm( this );
01021   dialog->exec();
01022 }
01023 
01024 /*--------------------------------------------------------------------------------------*/
01025 
01026 void GCMainWindow::addItemsToDB()
01027 {
01028   if( !GCDataBaseInterface::instance()->hasActiveSession() )
01029   {
01030     GCMessageSpace::showErrorMessageBox( this, "No active profile, please set one (Edit->Switch Profile)." );
01031     return;
01032   }
01033 
01034   bool profileWasEmpty = GCDataBaseInterface::instance()->isProfileEmpty();
01035 
01036   /* Delete on close flag set (no clean-up needed). */
01037   GCAddItemsForm* form = new GCAddItemsForm( this );
01038   form->exec();
01039 
01040   /* If the active profile has just been populated with elements for the first time,
01041     make sure that we set the newly added root elements to the dropdown. */
01042   if( profileWasEmpty )
01043   {
01044     ui->addElementComboBox->addItems( GCDataBaseInterface::instance()->knownRootElements() );
01045     toggleAddElementWidgets();
01046   }
01047 }
01048 
01049 /*--------------------------------------------------------------------------------------*/
01050 
01051 void GCMainWindow::searchDocument()
01052 {
01053   /* Delete on close flag set (no clean-up needed). */
01054   GCSearchForm* form = new GCSearchForm( ui->treeWidget->allTreeWidgetItems(), ui->dockWidgetTextEdit, this );
01055   connect( form, SIGNAL( foundItem( GCTreeWidgetItem* ) ), this, SLOT( itemFound( GCTreeWidgetItem* ) ) );
01056   form->exec();
01057 }
01058 
01059 /*--------------------------------------------------------------------------------------*/
01060 
01061 void GCMainWindow::itemFound( GCTreeWidgetItem* item )
01062 {
01063   ui->treeWidget->expandAll();
01064   ui->treeWidget->setCurrentItem( item );
01065   elementSelected( item, 0 );
01066 }
01067 
01068 /*--------------------------------------------------------------------------------------*/
01069 
01070 void GCMainWindow::commentOut( const QList< int >& indices, const QString& comment )
01071 {
01072   m_fileContentsChanged = true;
01073   ui->treeWidget->replaceItemsWithComment( indices, comment );
01074 }
01075 
01076 /*--------------------------------------------------------------------------------------*/
01077 
01078 void GCMainWindow::rebuild()
01079 {
01080   /* No need to check if setContent is a success.  If this function gets called, the document
01081     content is already valid XML. The reason I reset the text edit's content is due to the Qt
01082     XML parser adding attributes in alphabetical order.  What this means is that the elements
01083     in the tree widget have all their attributes aligned alphabetically, and this may or may
01084     not add up with what is in the text edit, hence the reset (this way we ensure that the order
01085     of the attributes in the text edit matches exactly that of the tree widget's elements). */
01086   ui->treeWidget->setContent( ui->dockWidgetTextEdit->toPlainText() );
01087   ui->dockWidgetTextEdit->setContent( ui->treeWidget->toString() );
01088 
01089   ui->treeWidget->expandAll();
01090   m_fileContentsChanged = true;
01091 }
01092 
01093 /*--------------------------------------------------------------------------------------*/
01094 
01095 void GCMainWindow::updateComment( const QString& comment )
01096 {
01097   ui->treeWidget->setActiveCommentValue( comment );
01098 }
01099 
01100 /*--------------------------------------------------------------------------------------*/
01101 
01102 void GCMainWindow::collapseOrExpandTreeWidget( bool checked )
01103 {
01104   if( checked )
01105   {
01106     ui->treeWidget->expandAll();
01107   }
01108   else
01109   {
01110     ui->treeWidget->collapseAll();
01111   }
01112 }
01113 
01114 /*--------------------------------------------------------------------------------------*/
01115 
01116 void GCMainWindow::uncheckExpandAll()
01117 {
01118   ui->expandAllCheckBox->setChecked( false );
01119 }
01120 
01121 /*--------------------------------------------------------------------------------------*/
01122 
01123 void GCMainWindow::forgetMessagePreferences()
01124 {
01125   GCMessageSpace::forgetAllPreferences();
01126 }
01127 
01128 /*--------------------------------------------------------------------------------------*/
01129 
01130 void GCMainWindow::createSpinner()
01131 {
01132   /* Clean-up must be handled in the calling function, but just in case. */
01133   if( m_spinner || m_progressLabel )
01134   {
01135     deleteSpinner();
01136   }
01137 
01138   m_progressLabel = new QLabel( this, Qt::Popup );
01139   m_progressLabel->move( window()->frameGeometry().topLeft() + window()->rect().center() - m_progressLabel->rect().center() );
01140 
01141   m_spinner = new QMovie( ":/resources/spinner.gif" );
01142   m_spinner->start();
01143 
01144   m_progressLabel->setMovie( m_spinner );
01145   m_progressLabel->show();
01146 }
01147 
01148 /*--------------------------------------------------------------------------------------*/
01149 
01150 void GCMainWindow::deleteSpinner()
01151 {
01152   if( m_spinner )
01153   {
01154     delete m_spinner;
01155     m_spinner = NULL;
01156   }
01157 
01158   if( m_progressLabel )
01159   {
01160     delete m_progressLabel;
01161     m_progressLabel = NULL;
01162   }
01163 }
01164 
01165 /*--------------------------------------------------------------------------------------*/
01166 
01167 void GCMainWindow::resetDOM()
01168 {
01169   ui->treeWidget->clearAndReset();
01170   ui->dockWidgetTextEdit->clearAndReset();
01171 
01172   resetTableWidget();
01173 
01174   ui->addElementComboBox->clear();
01175   ui->addElementComboBox->addItems( GCDataBaseInterface::instance()->knownRootElements() );
01176   toggleAddElementWidgets();
01177 
01178   ui->addSnippetButton->setEnabled( false );
01179   ui->addChildElementButton->setText( "Add Root" );
01180 
01181   m_currentCombo = NULL;
01182   m_activeAttributeName = "";
01183   m_fileContentsChanged = false;
01184 
01185   /* The timer will be reactivated as soon as work starts again on a legitimate
01186     document and the user saves it for the first time. */
01187   if( m_saveTimer )
01188   {
01189     m_saveTimer->stop();
01190   }
01191 }
01192 
01193 /*--------------------------------------------------------------------------------------*/
01194 
01195 bool GCMainWindow::queryResetDOM( const QString& resetReason )
01196 {
01197   /* There are a number of places and opportunities for "resetDOM" to be called,
01198     if there is an active document, check if it's content has changed since the
01199     last time it had changed and make sure we don't accidentally delete anything. */
01200   if( m_fileContentsChanged )
01201   {
01202     QMessageBox::StandardButtons accept = QMessageBox::question( this,
01203                                                                  "Save file?",
01204                                                                  resetReason,
01205                                                                  QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
01206                                                                  QMessageBox::Yes );
01207 
01208     if( accept == QMessageBox::Yes )
01209     {
01210       return saveXMLFile();
01211     }
01212     else if( accept == QMessageBox::No )
01213     {
01214       m_fileContentsChanged = false;
01215     }
01216     else
01217     {
01218       return false;
01219     }
01220   }
01221 
01222   return true;
01223 }
01224 
01225 /*--------------------------------------------------------------------------------------*/
01226 
01227 void GCMainWindow::showEmptyProfileHelp()
01228 {
01229   QMessageBox::information( this,
01230                             "Empty Profile",
01231                             "The active profile is empty.  You can either import XML from file "
01232                             "via \"Edit -> Import XML to Profile\" or you can populate the "
01233                             "profile from scratch via \"Edit -> Edit Profile -> Add Items\"." );
01234 }
01235 
01236 /*--------------------------------------------------------------------------------------*/
01237 
01238 void GCMainWindow::showElementHelp()
01239 {
01240   QMessageBox::information( this,
01241                             "Adding Elements and Snippets",
01242                             "If the document is still empty, your first element will be the root element.\n\n"
01243                             "New elements are added as children of the element selected in the element tree.\n\n"
01244                             "\"Empty\" duplicate siblings are added by selecting the element name in the drop down "
01245                             "that matches that of the element selected in the element tree (these names will be "
01246                             "bracketed by \"[]\").\n\n"
01247                             "\"Snippets\" are fully formed XML segments consisting of the entire element hierarchy "
01248                             "with the element selected in the drop down combo box as base. Selecting this option will "
01249                             "provide you with the opportunity to provide default values for the snippet's attributes." );
01250 }
01251 
01252 /*--------------------------------------------------------------------------------------*/
01253 
01254 void GCMainWindow::showMainHelp()
01255 {
01256   QFile file( ":/resources/help/Help.txt" );
01257 
01258   if( !file.open( QIODevice::ReadOnly | QIODevice::Text ) )
01259   {
01260     GCMessageSpace::showErrorMessageBox( this, QString( "Failed to open \"Help\" file: [%1]" ).arg( file.errorString() ) );
01261   }
01262   else
01263   {
01264     QTextStream stream( &file );
01265     QString fileContent = stream.readAll();
01266     file.close();
01267 
01268     /* Qt::WA_DeleteOnClose flag set...no cleanup required. */
01269     GCHelpDialog* dialog = new GCHelpDialog( fileContent, this );
01270     dialog->show();
01271   }
01272 }
01273 
01274 /*--------------------------------------------------------------------------------------*/
01275 
01276 void GCMainWindow::goToSite()
01277 {
01278   QDesktopServices::openUrl( QUrl( "http://goblincoding.com" ) );
01279 }
01280 
01281 /*--------------------------------------------------------------------------------------*/
01282 
01283 void GCMainWindow::useDarkTheme( bool dark )
01284 {
01285   if( dark )
01286   {
01287     QFile file( ":resources/StyleSheet.txt" );
01288     file.open( QIODevice::ReadOnly | QIODevice::Text );
01289     QTextStream stream( &file );
01290 
01291     qApp->setStyleSheet( stream.readAll() );
01292     file.close();
01293   }
01294   else
01295   {
01296     qApp->setStyleSheet( QString() );
01297   }
01298 }
01299 
01300 /*--------------------------------------------------------------------------------------*/
01301 
01302 void GCMainWindow::setShowHelpButtons( bool show )
01303 {
01304   GCGlobalSpace::setShowHelpButtons( show );
01305   ui->showAddElementHelpButton->setVisible( show );
01306 }
01307 
01308 /*--------------------------------------------------------------------------------------*/
01309 
01310 void GCMainWindow::setShowTreeItemsVerbose( bool verbose )
01311 {
01312   GCGlobalSpace::setShowTreeItemsVerbose( verbose );
01313   ui->treeWidget->setShowTreeItemsVerbose( verbose );
01314 }
01315 
01316 /*--------------------------------------------------------------------------------------*/
01317 
01318 GCDBSessionManager* GCMainWindow::createDBSessionManager()
01319 {
01320   /* Clean-up is the responsibility of the calling function. */
01321   GCDBSessionManager* manager = new GCDBSessionManager( this );
01322   connect( manager, SIGNAL( reset() ), this, SLOT( resetDOM() ) );
01323   connect( manager, SIGNAL( activeDatabaseChanged( QString ) ), this, SLOT( activeDatabaseChanged( QString ) ) );
01324   return manager;
01325 }
01326 
01327 /*--------------------------------------------------------------------------------------*/
01328 
01329 void GCMainWindow::processDOMDoc()
01330 {
01331   ui->treeWidget->rebuildTreeWidget(); // also deletes current items
01332   resetTableWidget();
01333 
01334   ui->addSnippetButton->setEnabled( true );
01335   ui->addChildElementButton->setText( "Add Child" );
01336 
01337   /* Enable file save options. */
01338   ui->actionCloseFile->setEnabled( true );
01339   ui->actionSave->setEnabled( true );
01340   ui->actionSaveAs->setEnabled( true );
01341 
01342   /* Display the DOM content in the text edit. */
01343   setTextEditContent();
01344 
01345   /* Generally-speaking, we want the file contents changed flag to be set whenever
01346     the text edit content is set (this is done, not surprisingly, in "setTextEditContent"
01347     above).  However, whenever a DOM document is processed for the first time, nothing
01348     is changed in it, so to avoid the annoying "Save File" queries when nothing has
01349     been done yet, we unset the flag here. */
01350   m_fileContentsChanged = false;
01351 
01352   collapseOrExpandTreeWidget( ui->expandAllCheckBox->isChecked() );
01353 }
01354 
01355 /*--------------------------------------------------------------------------------------*/
01356 
01357 void GCMainWindow::setStatusBarMessage( const QString& message )
01358 {
01359   Q_UNUSED( message );
01360   // TODO.
01361 }
01362 
01363 /*--------------------------------------------------------------------------------------*/
01364 
01365 void GCMainWindow::setTextEditContent( GCTreeWidgetItem* item )
01366 {
01367   m_fileContentsChanged = true;
01368   ui->dockWidgetTextEdit->setContent( ui->treeWidget->toString() );
01369   highlightTextElement( item );
01370 }
01371 
01372 /*--------------------------------------------------------------------------------------*/
01373 
01374 void GCMainWindow::highlightTextElement( GCTreeWidgetItem* item )
01375 {
01376   if( item )
01377   {
01378     QString stringToMatch = item->toString();
01379     int pos = ui->treeWidget->itemPositionRelativeToIdenticalSiblings( stringToMatch, item->index() );
01380     ui->dockWidgetTextEdit->findTextRelativeToDuplicates( stringToMatch, pos );
01381   }
01382 }
01383 
01384 /*--------------------------------------------------------------------------------------*/
01385 
01386 void GCMainWindow::insertEmptyTableRow()
01387 {
01388   QTableWidgetItem* label = new QTableWidgetItem( EMPTY );
01389 
01390   int lastRow = ui->tableWidget->rowCount();
01391   ui->tableWidget->setRowCount( lastRow + 1 );
01392   ui->tableWidget->setItem( lastRow, 0, label );
01393 
01394   /* Create the combo box, but deactivate it until we have an associated attribute name. */
01395   GCComboBox* attributeCombo = new GCComboBox;
01396   attributeCombo->setEditable( true );
01397   attributeCombo->setEnabled( false );
01398 
01399   /* Only connect after inserting items or bad things will happen! */
01400   connect( attributeCombo, SIGNAL( currentIndexChanged( QString ) ), this, SLOT( attributeValueChanged( QString ) ) );
01401 
01402   ui->tableWidget->setCellWidget( lastRow, 1, attributeCombo );
01403   m_comboBoxes.insert( attributeCombo, lastRow );
01404 
01405   /* This will point the current combo box member to the combo that's been activated
01406     in the table widget (used in "attributeValueChanged" to obtain the row number the
01407     combo box appears in in the table widget, etc, etc). */
01408   connect( attributeCombo, SIGNAL( activated( int ) ), m_signalMapper, SLOT( map() ) );
01409   m_signalMapper->setMapping( attributeCombo, attributeCombo );
01410 }
01411 
01412 /*--------------------------------------------------------------------------------------*/
01413 
01414 void GCMainWindow::resetTableWidget()
01415 {
01416   /* Remove the currently visible/live combo boxes from the signal mapper's
01417     mappings and the combo box map before we whack them all. */
01418   for( int i = 0; i < m_comboBoxes.keys().size(); ++i )
01419   {
01420     m_signalMapper->removeMappings( m_comboBoxes.keys().at( i ) );
01421   }
01422 
01423   m_comboBoxes.clear();
01424 
01425   ui->tableWidget->clearContents();   // also deletes current items
01426   ui->tableWidget->setRowCount( 0 );
01427 }
01428 
01429 /*--------------------------------------------------------------------------------------*/
01430 
01431 void GCMainWindow::startSaveTimer()
01432 {
01433   /* Automatically save the file at five minute intervals. */
01434   if( !m_saveTimer )
01435   {
01436     m_saveTimer = new QTimer( this );
01437     connect( m_saveTimer, SIGNAL( timeout() ), this, SLOT( saveTempFile() ) );
01438     m_saveTimer->start( 300000 );
01439   }
01440   else
01441   {
01442     /* If the timer was stopped due to a DOM reset, start it again. */
01443     m_saveTimer->start( 300000 );
01444   }
01445 }
01446 
01447 /*--------------------------------------------------------------------------------------*/
01448 
01449 void GCMainWindow::toggleAddElementWidgets()
01450 {
01451   /* Make sure we don't inadvertently create "empty" elements. */
01452   if( ui->addElementComboBox->count() < 1 )
01453   {
01454     ui->addElementComboBox->setEnabled( false );
01455     ui->addChildElementButton->setEnabled( false );
01456     ui->commentLineEdit->setEnabled( false );
01457 
01458     /* Check if the element combo box is empty due to an empty profile
01459       being active. */
01460     if( GCDataBaseInterface::instance()->isProfileEmpty() )
01461     {
01462       ui->showEmptyProfileHelpButton->setVisible( true );
01463     }
01464     else
01465     {
01466       ui->showEmptyProfileHelpButton->setVisible( false );
01467     }
01468   }
01469   else
01470   {
01471     ui->addElementComboBox->setEnabled( true );
01472     ui->addChildElementButton->setEnabled( true );
01473     ui->commentLineEdit->setEnabled( true );
01474     ui->showEmptyProfileHelpButton->setVisible( false );
01475   }
01476 }
01477 
01478 /*--------------------------------------------------------------------------------------*/
01479 
01480 void GCMainWindow::querySetActiveSession( QString reason )
01481 {
01482   while( !GCDataBaseInterface::instance()->hasActiveSession() )
01483   {
01484     GCMessageSpace::showErrorMessageBox( this, reason );
01485     GCDBSessionManager* manager = createDBSessionManager();
01486     manager->selectActiveDatabase();
01487     delete manager;
01488   }
01489 }
01490 
01491 /*--------------------------------------------------------------------------------------*/
01492 
01493 void GCMainWindow::readSettings()
01494 {
01495   restoreGeometry( GCGlobalSpace::windowGeometry() );
01496   restoreState( GCGlobalSpace::windowState() );
01497 
01498   setShowHelpButtons( GCGlobalSpace::showHelpButtons() );
01499   setShowTreeItemsVerbose( GCGlobalSpace::showTreeItemsVerbose() );
01500 
01501   ui->actionRememberWindowGeometry->setChecked( GCGlobalSpace::useWindowSettings() );
01502   ui->actionUseDarkTheme->setChecked( GCGlobalSpace::useDarkTheme() );
01503   ui->actionShowHelpButtons->setChecked( GCGlobalSpace::showHelpButtons() );
01504 }
01505 
01506 /*--------------------------------------------------------------------------------------*/
01507 
01508 void GCMainWindow::saveSettings()
01509 {
01510   if( ui->actionRememberWindowGeometry->isChecked() )
01511   {
01512     GCGlobalSpace::setWindowGeometry( saveGeometry() );
01513     GCGlobalSpace::setWindowState( saveState() );
01514   }
01515   else
01516   {
01517     GCGlobalSpace::removeWindowInfo();
01518   }
01519 
01520   GCGlobalSpace::setUseDarkTheme( ui->actionUseDarkTheme->isChecked() );
01521   GCGlobalSpace::setUseWindowSettings( ui->actionRememberWindowGeometry->isChecked() );
01522 }
01523 
01524 /*--------------------------------------------------------------------------------------*/
01525 
01526 void GCMainWindow::queryRestoreFiles()
01527 {
01528   QDir dir = QDir::current();
01529   QStringList tempFiles = QDir::current()
01530     .entryList( QDir::Files )
01531     .filter( QString( "%1_temp" )
01532              .arg( GCDataBaseInterface::instance()->activeSessionName().remove( ".db" ) ) );
01533 
01534   if( !tempFiles.empty() )
01535   {
01536     QMessageBox::StandardButton accept = QMessageBox::information( this,
01537                                                                    "Found recovery files",
01538                                                                    "Found auto-recover files related to this profile. Would you like to view and save?",
01539                                                                    QMessageBox::Ok | QMessageBox::Cancel,
01540                                                                    QMessageBox::Ok );
01541 
01542     if( accept == QMessageBox::Ok )
01543     {
01544       /* "Delete on close" flag set. */
01545       GCRestoreFilesForm* restore = new GCRestoreFilesForm( tempFiles, this );
01546       restore->exec();
01547     }
01548   }
01549 }
01550 
01551 /*--------------------------------------------------------------------------------------*/
01552 
01553 void GCMainWindow::saveTempFile()
01554 {
01555   QFile file( QDir::currentPath() +
01556               QString( "/%1_%2_temp" )
01557               .arg( m_currentXMLFileName.split( "/" ).last() )
01558               .arg( GCDataBaseInterface::instance()->activeSessionName().remove( ".db" ) ) );
01559 
01560   /* Since this is an attempt at auto-saving, we aim for a "best case" scenario and don't display
01561     error messages if encountered. */
01562   if( file.open( QIODevice::ReadWrite | QIODevice::Truncate | QIODevice::Text ) )
01563   {
01564     QTextStream outStream( &file );
01565     outStream << ui->treeWidget->toString();
01566     file.close();
01567     startSaveTimer();
01568   }
01569 }
01570 
01571 /*--------------------------------------------------------------------------------------*/
01572 
01573 void GCMainWindow::deleteTempFile()
01574 {
01575   QFile file( QDir::currentPath() +
01576               QString( "/%1_%2_temp" )
01577               .arg( m_currentXMLFileName.split( "/" ).last() )
01578               .arg( GCDataBaseInterface::instance()->activeSessionName().remove( ".db" ) ) );
01579 
01580   file.remove();
01581 }
01582 
01583 /*--------------------------------------------------------------------------------------*/