Warning, file /office/calligra/libs/main/KoFindText.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /* This file is part of the KDE project
0002  *
0003  * Copyright (c) 2010 Arjen Hiemstra <ahiemstra@heimr.nl>
0004  * Copyright (C) 2011 Thorsten Zachmann <zachmann@kde.org>
0005  *
0006  * This library is free software; you can redistribute it and/or
0007  * modify it under the terms of the GNU Library General Public
0008  * License as published by the Free Software Foundation; either
0009  * version 2 of the License, or (at your option) any later version.
0010  *
0011  * This library is distributed in the hope that it will be useful,
0012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0014  * Library General Public License for more details.
0015  *
0016  * You should have received a copy of the GNU Library General Public License
0017  * along with this library; see the file COPYING.LIB.  If not, write to
0018  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0019  * Boston, MA 02110-1301, USA.
0020  */
0021 
0022 #include "KoFindText.h"
0023 #include "KoFindText_p.h"
0024 
0025 #include <QTextDocument>
0026 #include <QTextCursor>
0027 #include <QTextBlock>
0028 #include <QTextLayout>
0029 #include <QPalette>
0030 #include <QStyle>
0031 #include <QApplication>
0032 #include <QAbstractTextDocumentLayout>
0033 
0034 #include <MainDebug.h>
0035 #include <klocalizedstring.h>
0036 
0037 #include <KoText.h>
0038 #include <KoTextDocument.h>
0039 #include <KoShape.h>
0040 #include <KoShapeContainer.h>
0041 #include <KoTextShapeData.h>
0042 
0043 #include "KoFindOptionSet.h"
0044 #include "KoFindOption.h"
0045 #include "KoDocument.h"
0046 
0047 QTextCharFormat KoFindText::Private::highlightFormat;
0048 QTextCharFormat KoFindText::Private::currentMatchFormat;
0049 QTextCharFormat KoFindText::Private::currentSelectionFormat;
0050 QTextCharFormat KoFindText::Private::replacedFormat;
0051 bool KoFindText::Private::formatsInitialized = false;
0052 
0053 KoFindText::KoFindText(QObject* parent)
0054     : KoFindBase(parent), d(new Private(this))
0055 {
0056     d->initializeFormats();
0057 
0058     KoFindOptionSet *options = new KoFindOptionSet();
0059     options->addOption("caseSensitive", i18n("Case Sensitive"), i18n("Match cases when searching"), QVariant::fromValue<bool>(false));
0060     options->addOption("wholeWords", i18n("Whole Words Only"), i18n("Match only whole words"), QVariant::fromValue<bool>(false));
0061     options->addOption("fromCursor", i18n("Find from Cursor"), i18n("Start searching from the current cursor"), QVariant::fromValue<bool>(true));
0062     setOptions(options);
0063 }
0064 
0065 KoFindText::~KoFindText()
0066 {
0067     delete d;
0068 }
0069 
0070 void KoFindText::findImplementation(const QString &pattern, QList<KoFindMatch> & matchList)
0071 {
0072     KoFindOptionSet *opts = options();
0073     QTextDocument::FindFlags flags = 0;
0074 
0075     if(opts->option("caseSensitive")->value().toBool()) {
0076         flags |= QTextDocument::FindCaseSensitively;
0077     }
0078     if(opts->option("wholeWords")->value().toBool()) {
0079         flags |= QTextDocument::FindWholeWords;
0080     }
0081 
0082     int start = 0;
0083     bool findInSelection = false;
0084 
0085     if(d->documents.size() == 0) {
0086         qWarning() << "No document available for searching!";
0087         return;
0088     }
0089 
0090     bool before = opts->option("fromCursor")->value().toBool() && !d->currentCursor.isNull();
0091     QList<KoFindMatch> matchBefore;
0092     foreach(QTextDocument* document, d->documents) {
0093         QTextCursor cursor = document->find(pattern, start, flags);
0094         cursor.setKeepPositionOnInsert(true);
0095 
0096         QVector<QAbstractTextDocumentLayout::Selection> selections;
0097         while(!cursor.isNull()) {
0098             if(findInSelection && d->selectionEnd <= cursor.position()) {
0099                 break;
0100             }
0101 
0102             if (before && document == d->currentCursor.document() && d->currentCursor < cursor) {
0103                 before = false;
0104             }
0105 
0106             QAbstractTextDocumentLayout::Selection selection;
0107             selection.cursor = cursor;
0108             selection.format = d->highlightFormat;
0109             selections.append(selection);
0110 
0111             KoFindMatch match;
0112             match.setContainer(QVariant::fromValue(document));
0113             match.setLocation(QVariant::fromValue(cursor));
0114             if (before) {
0115                 matchBefore.append(match);
0116             }
0117             else {
0118                 matchList.append(match);
0119             }
0120 
0121             cursor = document->find(pattern, cursor, flags);
0122             cursor.setKeepPositionOnInsert(true);
0123         }
0124         if (before && document == d->currentCursor.document()) {
0125             before = false;
0126         }
0127         d->selections.insert(document, selections);
0128     }
0129     matchList.append(matchBefore);
0130 
0131     if (hasMatches()) {
0132         setCurrentMatch(0);
0133         d->updateCurrentMatch(0);
0134     }
0135 
0136     d->updateSelections();
0137 }
0138 
0139 void KoFindText::replaceImplementation(const KoFindMatch &match, const QVariant &value)
0140 {
0141     if (!match.isValid() || !match.location().canConvert<QTextCursor>() || !match.container().canConvert<QTextDocument*>()) {
0142         return;
0143     }
0144 
0145     QTextCursor cursor = match.location().value<QTextCursor>();
0146     cursor.setKeepPositionOnInsert(true);
0147 
0148     //Search for the selection matching this match.
0149     QVector<QAbstractTextDocumentLayout::Selection> selections = d->selections.value(match.container().value<QTextDocument*>());
0150     int index = 0;
0151     foreach(const QAbstractTextDocumentLayout::Selection &sel, selections) {
0152         if(sel.cursor == cursor) {
0153             break;
0154         }
0155         index++;
0156     }
0157 
0158     cursor.insertText(value.toString());
0159     cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor, value.toString().length());
0160 
0161     selections[index].cursor = cursor;
0162     selections[index].format = d->replacedFormat;
0163     d->selections.insert(match.container().value<QTextDocument*>(), selections);
0164 
0165     d->updateCurrentMatch(0);
0166     d->updateSelections();
0167 }
0168 
0169 void KoFindText::clearMatches()
0170 {
0171     d->selections.clear();
0172     foreach(QTextDocument* doc, d->documents) {
0173         d->selections.insert(doc, QVector<QAbstractTextDocumentLayout::Selection>());
0174     }
0175     d->updateSelections();
0176 
0177     d->selectionStart = -1;
0178     d->selectionEnd = -1;
0179 
0180     setCurrentMatch(0);
0181     d->currentMatch.first = 0;
0182 }
0183 
0184 QList< QTextDocument* > KoFindText::documents() const
0185 {
0186     return d->documents;
0187 }
0188 
0189 void KoFindText::findNext()
0190 {
0191     if(d->selections.size() == 0) {
0192         return;
0193     }
0194 
0195     KoFindBase::findNext();
0196     d->updateCurrentMatch(currentMatchIndex());
0197     d->updateSelections();
0198 }
0199 
0200 void KoFindText::findPrevious()
0201 {
0202     if(d->selections.size() == 0) {
0203         return;
0204     }
0205 
0206     KoFindBase::findPrevious();
0207     d->updateCurrentMatch(currentMatchIndex());
0208     d->updateSelections();
0209 }
0210 
0211 void KoFindText::setCurrentCursor(const QTextCursor &cursor)
0212 {
0213     d->currentCursor = cursor;
0214 }
0215 
0216 void KoFindText::setDocuments(const QList<QTextDocument*> &documents)
0217 {
0218     clearMatches();
0219     d->documents = documents;
0220     d->updateDocumentList();
0221 }
0222 
0223 void KoFindText::findTextInShapes(const QList<KoShape*> &shapes, QList<QTextDocument*> &append)
0224 {
0225     foreach(KoShape* shape, shapes) {
0226         KoShapeContainer *container = dynamic_cast<KoShapeContainer*>(shape);
0227         if(container) {
0228             findTextInShapes(container->shapes(), append);
0229         }
0230 
0231         KoTextShapeData *shapeData = dynamic_cast<KoTextShapeData*>(shape->userData());
0232         if (!shapeData)
0233             continue;
0234 
0235         if(shapeData->document()) {
0236             if(!append.contains(shapeData->document())) {
0237                 append.append(shapeData->document());
0238             }
0239         }
0240     }
0241 }
0242 
0243 void KoFindText::Private::updateSelections()
0244 {
0245     QHash< QTextDocument*, QVector<QAbstractTextDocumentLayout::Selection> >::ConstIterator itr;
0246     for(itr = selections.constBegin(); itr != selections.constEnd(); ++itr) {
0247         KoTextDocument doc(itr.key());
0248         doc.setSelections(itr.value());
0249     }
0250 }
0251 
0252 void KoFindText::Private::updateDocumentList()
0253 {
0254     foreach(QTextDocument *document, documents) {
0255         connect(document, SIGNAL(destroyed(QObject*)), q, SLOT(documentDestroyed(QObject*)), Qt::UniqueConnection);
0256     }
0257 }
0258 
0259 void KoFindText::Private::documentDestroyed(QObject *document)
0260 {
0261     QTextDocument* doc = qobject_cast<QTextDocument*>(document);
0262     if(doc) {
0263         selections.remove(doc);
0264         documents.removeOne(doc);
0265     }
0266 }
0267 
0268 void KoFindText::Private::updateCurrentMatch(int position)
0269 {
0270     Q_UNUSED(position);
0271     if (currentMatch.first != 0) {
0272         QVector<QAbstractTextDocumentLayout::Selection> sel = selections.value(currentMatch.first);
0273         Q_ASSERT(currentMatch.second < sel.count());
0274         if(sel[currentMatch.second].format == currentMatchFormat) {
0275             sel[currentMatch.second].format = highlightFormat;
0276         }
0277         selections.insert(currentMatch.first, sel);
0278     }
0279 
0280     const KoFindMatch match = q->currentMatch();
0281     if (match.isValid() && match.location().canConvert<QTextCursor>() && match.container().canConvert<QTextDocument*>()) {
0282         QTextCursor cursor = match.location().value<QTextCursor>();
0283         QTextDocument *document = match.container().value<QTextDocument*>();
0284         QVector<QAbstractTextDocumentLayout::Selection> sel = selections.value(document);
0285         for (int i = 0; i < sel.size(); ++i) {
0286             if (sel[i].cursor == cursor) {
0287                 sel[i].format = currentMatchFormat;
0288                 selections.insert(document, sel);
0289                 currentMatch.first = document;
0290                 currentMatch.second = i;
0291                 break;
0292             }
0293         }
0294     }
0295 }
0296 
0297 void KoFindText::Private::initializeFormats()
0298 {
0299     if (!formatsInitialized) {
0300         highlightFormat.setBackground(Qt::yellow);
0301         currentMatchFormat.setBackground(qApp->palette().highlight());
0302         currentMatchFormat.setForeground(qApp->palette().highlightedText());
0303         currentSelectionFormat.setBackground(qApp->palette().alternateBase());
0304         replacedFormat.setBackground(Qt::green);
0305         formatsInitialized = true;
0306     }
0307 }
0308 
0309 void KoFindText::setFormat(FormatType formatType, const QTextCharFormat &format)
0310 {
0311     KoFindText::Private::initializeFormats();
0312 
0313     switch (formatType) {
0314     case HighlightFormat:
0315         KoFindText::Private::highlightFormat = format;
0316         break;
0317     case CurrentMatchFormat:
0318         KoFindText::Private::currentMatchFormat = format;
0319         break;
0320     case SelectionFormat:
0321         KoFindText::Private::currentSelectionFormat = format;
0322         break;
0323     case ReplacedFormat:
0324         KoFindText::Private::replacedFormat = format;
0325         break;
0326     }
0327 }
0328 
0329 // have to include this because of Q_PRIVATE_SLOT
0330 #include "moc_KoFindText.cpp"