XML Mill  1.0.0
A GUI based XML editor with a memory.
gcsearchform.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 "gcsearchform.h"
00030 #include "ui_gcsearchform.h"
00031 #include "utils/gctreewidgetitem.h"
00032 
00033 #include <QMessageBox>
00034 #include <QTextBlock>
00035 
00036 /*-------------------------------- NON MEMBER FUNCTIONS --------------------------------*/
00037 
00038 bool lessThan( GCTreeWidgetItem* lhs, GCTreeWidgetItem* rhs )
00039 {
00040   return ( lhs->index() < rhs->index() );
00041 }
00042 
00043 /*--------------------------------------------------------------------------------------*/
00044 
00045 bool greaterThan( GCTreeWidgetItem* lhs, GCTreeWidgetItem* rhs )
00046 {
00047   return ( lhs->index() > rhs->index() );
00048 }
00049 
00050 /*---------------------------------- MEMBER FUNCTIONS ----------------------------------*/
00051 
00052 GCSearchForm::GCSearchForm( const QList< GCTreeWidgetItem* >& items, QPlainTextEdit* textEdit, QWidget* parent )
00053 : QDialog          ( parent ),
00054   ui               ( new Ui::GCSearchForm ),
00055   m_text           ( textEdit ),
00056   m_savedBackground(),
00057   m_savedForeground(),
00058   m_wasFound       ( false ),
00059   m_searchUp       ( false ),
00060   m_firstRun       ( true ),
00061   m_previousIndex  ( -1 ),
00062   m_searchFlags    ( 0 ),
00063   m_items          ( items )
00064 {
00065   ui->setupUi( this );
00066   ui->lineEdit->setFocus();
00067   //m_text->setText( docContents );
00068 
00069   connect( ui->searchButton, SIGNAL( clicked() ), this, SLOT( search() ) );
00070   connect( ui->closeButton, SIGNAL( clicked() ), this, SLOT( close() ) );
00071 
00072   connect( ui->caseSensitiveCheckBox, SIGNAL( clicked() ), this, SLOT( caseSensitive() ) );
00073   connect( ui->wholeWordsCheckBox, SIGNAL( clicked() ), this, SLOT( wholeWords() ) );
00074   connect( ui->searchUpCheckBox, SIGNAL( clicked() ), this, SLOT( searchUp() ) );
00075 
00076   setAttribute( Qt::WA_DeleteOnClose );
00077 }
00078 
00079 /*--------------------------------------------------------------------------------------*/
00080 
00081 GCSearchForm::~GCSearchForm()
00082 {
00083   /* The QDomDocument m_doc points at is owned externally. */
00084   delete ui;
00085 }
00086 
00087 /*--------------------------------------------------------------------------------------*/
00088 
00089 void GCSearchForm::search()
00090 {
00091   m_firstRun = false;
00092   QString searchText = ui->lineEdit->text();
00093   bool found = m_text->find( searchText, m_searchFlags );
00094 
00095   /* The first time we enter this function, if the text does not exist
00096     within the document, "found" and "m_wasFound" will both be false.
00097     However, if the text does exist, we wish to know that we found a match
00098     at least once so that, when we reach the end of the document and "found"
00099     is once more false, we can reset all indices and flags in order to start
00100     again from the beginning. */
00101   if( ( ( found != m_wasFound ) && m_wasFound ) ||
00102       ( !m_wasFound && m_searchUp ) )
00103   {
00104     resetCursor();
00105     found = m_text->find( searchText, m_searchFlags );
00106   }
00107 
00108   if( found )
00109   {
00110     m_wasFound = true;
00111 
00112     /* Highlight the entire node (element, attributes and attribute values)
00113       in which the match was found. */
00114     m_text->moveCursor( QTextCursor::StartOfLine );
00115     m_text->moveCursor( QTextCursor::EndOfBlock, QTextCursor::KeepAnchor );
00116 
00117     /* Find all tree widget items whose corresponding element node matches the
00118       highlighted text. */
00119     QString nodeText = m_text->textCursor().selectedText().trimmed();
00120     QList< GCTreeWidgetItem* > matchingItems = gatherMatchingItems( nodeText );
00121 
00122     if ( !matchingItems.empty() )
00123     {
00124       if( !m_searchUp )
00125       {
00126         /* Sort ascending. */
00127         qSort( matchingItems.begin(), matchingItems.end(), lessThan );
00128         findMatchingTreeItem( matchingItems, true );
00129       }
00130       else
00131       {
00132         /* Sort descending. */
00133         qSort( matchingItems.begin(), matchingItems.end(), greaterThan );
00134         findMatchingTreeItem( matchingItems, false );
00135       }
00136     }
00137     else
00138     {
00139       highlightFind();
00140     }
00141   }
00142   else
00143   {
00144     QMessageBox::information( this, "Not Found", QString( "Can't find the text:\"%1\"" ).arg( searchText ) );
00145   }
00146 }
00147 
00148 /*--------------------------------------------------------------------------------------*/
00149 
00150 void GCSearchForm::resetCursor()
00151 {
00152   /* Reset cursor so that we may keep cycling through the document content. */
00153   if( ui->searchUpCheckBox->isChecked() )
00154   {
00155     m_text->moveCursor( QTextCursor::End );
00156     QMessageBox::information( this, "Reached Top", "Search reached top, continuing at bottom." );
00157     m_previousIndex = 9999999;
00158   }
00159   else
00160   {
00161     m_text->moveCursor( QTextCursor::Start );
00162     QMessageBox::information( this, "Reached Bottom", "Search reached bottom, continuing at top." );
00163     m_previousIndex = -1;
00164   }
00165 }
00166 
00167 /*--------------------------------------------------------------------------------------*/
00168 
00169 void GCSearchForm::searchUp()
00170 {
00171   /* If the user ticks the "Search Up" box before anything else, we need to set the
00172     previous index to a large value to ensure we start at the very bottom. */
00173   if( m_firstRun )
00174   {
00175     m_previousIndex = 9999999;
00176   }
00177 
00178   if( ui->searchUpCheckBox->isChecked() )
00179   {
00180     m_searchFlags |= QTextDocument::FindBackward;
00181     m_searchUp = true;
00182   }
00183   else
00184   {
00185     m_searchFlags ^= QTextDocument::FindBackward;
00186     m_searchUp = false;
00187   }
00188 }
00189 
00190 /*--------------------------------------------------------------------------------------*/
00191 
00192 void GCSearchForm::caseSensitive()
00193 {
00194   /* Reset found flag every time the user changes the search options. */
00195   m_wasFound = false;
00196 
00197   if( ui->caseSensitiveCheckBox->isChecked() )
00198   {
00199     m_searchFlags |= QTextDocument::FindCaseSensitively;
00200   }
00201   else
00202   {
00203     m_searchFlags ^= QTextDocument::FindCaseSensitively;
00204   }
00205 }
00206 
00207 /*--------------------------------------------------------------------------------------*/
00208 
00209 void GCSearchForm::wholeWords()
00210 {
00211   /* Reset found flag every time the user changes the search options. */
00212   m_wasFound = false;
00213 
00214   if( ui->wholeWordsCheckBox->isChecked() )
00215   {
00216     m_searchFlags |= QTextDocument::FindWholeWords;
00217   }
00218   else
00219   {
00220     m_searchFlags ^= QTextDocument::FindWholeWords;
00221   }
00222 }
00223 
00224 /*--------------------------------------------------------------------------------------*/
00225 
00226 QList< GCTreeWidgetItem* > GCSearchForm::gatherMatchingItems( const QString& nodeText )
00227 {
00228   QList< GCTreeWidgetItem* > matchingItems;
00229 
00230   for( int i = 0; i < m_items.size(); ++i )
00231   {
00232     GCTreeWidgetItem* treeItem = m_items.at( i );
00233 
00234     if( treeItem->toString() == nodeText )
00235     {
00236       matchingItems.append( treeItem );
00237     }
00238   }
00239 
00240   return matchingItems;
00241 }
00242 
00243 /*--------------------------------------------------------------------------------------*/
00244 
00245 void GCSearchForm::findMatchingTreeItem( const QList< GCTreeWidgetItem* > matchingItems, bool ascending )
00246 {
00247   for( int i = 0; i < matchingItems.size(); ++i )
00248   {
00249     GCTreeWidgetItem* treeItem = matchingItems.at( i );
00250 
00251     if( ( ascending && treeItem->index() > m_previousIndex ) ||
00252         ( !ascending && treeItem->index() < m_previousIndex ) )
00253     {
00254       m_previousIndex = treeItem->index();
00255       resetHighlights();
00256       emit foundItem( treeItem );
00257       break;
00258     }
00259   }
00260 
00261   if( ui->searchButton->text() == "Search" )
00262   {
00263     ui->searchButton->setText( "Next" );
00264   }
00265 }
00266 
00267 /*--------------------------------------------------------------------------------------*/
00268 
00269 void GCSearchForm::resetHighlights()
00270 {
00271   QList< QTextEdit::ExtraSelection > extras = m_text->extraSelections();
00272 
00273   for( int i = 0; i < extras.size(); ++i )
00274   {
00275     extras[ i ].format.setProperty( QTextFormat::FullWidthSelection, true );
00276     extras[ i ].format.setBackground( m_savedBackground );
00277     extras[ i ].format.setForeground( m_savedForeground );
00278   }
00279 
00280   m_text->setExtraSelections( extras );
00281 }
00282 
00283 /*--------------------------------------------------------------------------------------*/
00284 
00285 void GCSearchForm::highlightFind()
00286 {
00287   /* First we reset all previous selections. */
00288   resetHighlights();
00289 
00290   /* Now we can set the highlighted text. */
00291   m_savedBackground = m_text->textCursor().blockCharFormat().background();
00292   m_savedForeground = m_text->textCursor().blockCharFormat().foreground();
00293 
00294   QTextEdit::ExtraSelection extra;
00295   extra.cursor = m_text->textCursor();
00296   extra.format.setBackground( QApplication::palette().highlight() );
00297   extra.format.setForeground( QApplication::palette().highlightedText() );
00298 
00299   QList< QTextEdit::ExtraSelection > extras;
00300   extras << extra;
00301   m_text->setExtraSelections( extras );
00302 }
00303 
00304 /*--------------------------------------------------------------------------------------*/