treeviewfind.cpp 10.2 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
2
**
3
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
hjk's avatar
hjk committed
4
** Contact: http://www.qt-project.org/legal
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
7
**
hjk's avatar
hjk committed
8
9
10
11
12
13
14
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
15
16
**
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
18
19
20
21
22
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23
**
hjk's avatar
hjk committed
24
25
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
26
27
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
28
****************************************************************************/
29
30
31

#include "treeviewfind.h"

32
33
34
#include <aggregation/aggregate.h>
#include <coreplugin/findplaceholder.h>

35
#include <QModelIndex>
36
37
38
#include <QTextCursor>
#include <QTreeView>
#include <QVBoxLayout>
39

40
namespace Core {
41

Aurindam Jana's avatar
Aurindam Jana committed
42
class ItemModelFindPrivate
43
{
Aurindam Jana's avatar
Aurindam Jana committed
44
public:
45
46
47
48
49
    explicit ItemModelFindPrivate(QTreeView *view, int role, TreeViewFind::FetchOption option)
        : m_view(view),
          m_incrementalWrappedState(false),
          m_role(role),
          m_option(option)
50
51
52
53
54
55
56
    {
    }

    QTreeView *m_view;
    QModelIndex m_incrementalFindStart;
    bool m_incrementalWrappedState;
    int m_role;
57
    TreeViewFind::FetchOption m_option;
58
59
};

60
61
TreeViewFind::TreeViewFind(QTreeView *view, int role, FetchOption option)
    : d(new ItemModelFindPrivate(view, role, option))
62
63
64
65
66
67
68
69
70
71
72
73
74
{
}

TreeViewFind::~TreeViewFind()
{
    delete d;
}

bool TreeViewFind::supportsReplace() const
{
    return false;
}

75
FindFlags TreeViewFind::supportedFindFlags() const
76
{
77
    return FindBackward | FindCaseSensitively | FindRegularExpression | FindWholeWords;
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
}

void TreeViewFind::resetIncrementalSearch()
{
    d->m_incrementalFindStart = QModelIndex();
    d->m_incrementalWrappedState = false;
}

void TreeViewFind::clearResults()
{
}

QString TreeViewFind::currentFindString() const
{
    return QString();
}

QString TreeViewFind::completedFindString() const
{
    return QString();
}

void TreeViewFind::highlightAll(const QString &/*txt*/, FindFlags /*findFlags*/)
{
}

104
IFindSupport::Result TreeViewFind::findIncremental(const QString &txt, FindFlags findFlags)
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
{
    if (!d->m_incrementalFindStart.isValid()) {
        d->m_incrementalFindStart = d->m_view->currentIndex();
        d->m_incrementalWrappedState = false;
    }
    d->m_view->setCurrentIndex(d->m_incrementalFindStart);
    bool wrapped = false;
    IFindSupport::Result result = find(txt, findFlags, true/*startFromCurrent*/,
                                       &wrapped);
    if (wrapped != d->m_incrementalWrappedState) {
        d->m_incrementalWrappedState = wrapped;
        showWrapIndicator(d->m_view);
    }
    return result;
}

121
IFindSupport::Result TreeViewFind::findStep(const QString &txt, FindFlags findFlags)
122
123
124
125
126
127
128
129
130
131
132
133
134
{
    bool wrapped = false;
    IFindSupport::Result result = find(txt, findFlags, false/*startFromNext*/,
                                       &wrapped);
    if (wrapped)
        showWrapIndicator(d->m_view);
    if (result == IFindSupport::Found) {
        d->m_incrementalFindStart = d->m_view->currentIndex();
        d->m_incrementalWrappedState = false;
    }
    return result;
}

135
QWidget *TreeViewFind::createSearchableWrapper(QTreeView *treeView, FetchOption option)
136
137
138
139
140
141
142
143
144
145
{
    QWidget *widget = new QWidget;
    QVBoxLayout *vbox = new QVBoxLayout(widget);
    vbox->setMargin(0);
    vbox->setSpacing(0);
    vbox->addWidget(treeView);
    vbox->addWidget(new Core::FindToolBarPlaceHolder(widget));

    Aggregation::Aggregate *agg = new Aggregation::Aggregate;
    agg->add(treeView);
146
    agg->add(new TreeViewFind(treeView, Qt::DisplayRole, option));
147
148
149
150

    return widget;
}

151
IFindSupport::Result TreeViewFind::find(const QString &searchTxt,
152
                                        FindFlags findFlags,
153
154
155
156
157
158
159
160
                                        bool startFromCurrentIndex,
                                        bool *wrapped)
{
    if (wrapped)
        *wrapped = false;
    if (searchTxt.isEmpty())
        return IFindSupport::NotFound;

161
    QTextDocument::FindFlags flags = textDocumentFlagsForFindFlags(findFlags);
162
163
164
    QModelIndex resultIndex;
    QModelIndex currentIndex = d->m_view->currentIndex();
    QModelIndex index = currentIndex;
165
166
    int currentRow = currentIndex.row();

167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
    bool sensitive = (findFlags & FindCaseSensitively);
    QRegExp searchExpr;
    if (findFlags & FindRegularExpression) {
        searchExpr = QRegExp(searchTxt,
                             (sensitive ? Qt::CaseSensitive :
                                          Qt::CaseInsensitive));
    } else if (findFlags & FindWholeWords) {
        const QString escapedSearchText = QRegExp::escape(searchTxt);
        const QString wordBoundary = QLatin1String("\b");
        searchExpr = QRegExp(wordBoundary + escapedSearchText + wordBoundary,
                             (sensitive ? Qt::CaseSensitive :
                                          Qt::CaseInsensitive));
    } else {
        searchExpr = QRegExp(searchTxt,
                             (sensitive ? Qt::CaseSensitive :
                                          Qt::CaseInsensitive),
                             QRegExp::FixedString);
    }


187
188
189
190
191
192
193
    bool backward = (flags & QTextDocument::FindBackward);
    if (wrapped)
        *wrapped = false;
    bool anyWrapped = false;
    bool stepWrapped = false;
    if (!startFromCurrentIndex)
        index = followingIndex(index, backward, &stepWrapped);
194
195
    else
        currentRow = -1;
196
197
198
199
200
    do {
        anyWrapped |= stepWrapped; // update wrapped state if we actually stepped to next/prev item
        if (index.isValid()) {
            const QString &text = d->m_view->model()->data(
                        index, d->m_role).toString();
201
202
203
204
            if (d->m_view->model()->flags(index) & Qt::ItemIsSelectable
                    && (index.row() != currentRow || index.parent() != currentIndex.parent())
                    && searchExpr.indexIn(text) != -1)
                resultIndex = index;
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
        }
        index = followingIndex(index, backward, &stepWrapped);
    } while (!resultIndex.isValid() && index.isValid() && index != currentIndex);

    if (resultIndex.isValid()) {
        d->m_view->setCurrentIndex(resultIndex);
        d->m_view->scrollTo(resultIndex);
        if (resultIndex.parent().isValid())
            d->m_view->expand(resultIndex.parent());
        if (wrapped)
            *wrapped = anyWrapped;
        return IFindSupport::Found;
    }
    return IFindSupport::NotFound;
}

QModelIndex TreeViewFind::nextIndex(const QModelIndex &idx, bool *wrapped) const
{
    if (wrapped)
        *wrapped = false;
    QAbstractItemModel *model = d->m_view->model();
    // pathological
    if (!idx.isValid())
        return model->index(0, 0);

230
231
232
    // same parent has more columns, go to next column
    if (idx.column() + 1 < model->columnCount(idx.parent()))
        return model->index(idx.row(), idx.column() + 1, idx.parent());
233

234
235
236
237
238
    // tree views have their children attached to first column
    // make sure we are at first column
    QModelIndex current = model->index(idx.row(), 0, idx.parent());

    // check for children
239
240
    if (d->m_option == FetchMoreWhileSearching && model->canFetchMore(current))
        model->fetchMore(current);
Orgad Shaneh's avatar
Orgad Shaneh committed
241
    if (model->rowCount(current) > 0)
242
243
244
        return current.child(0, 0);

    // no more children, go up and look for parent with more children
245
246
247
248
    QModelIndex nextIndex;
    while (!nextIndex.isValid()) {
        int row = current.row();
        current = current.parent();
249

250
251
        if (d->m_option == FetchMoreWhileSearching && model->canFetchMore(current))
            model->fetchMore(current);
252
253
254
        if (row + 1 < model->rowCount(current)) {
            // Same parent has another child
            nextIndex = model->index(row + 1, 0, current);
255
        } else {
256
257
258
259
260
261
            // go up one parent
            if (!current.isValid()) {
                // we start from the beginning
                if (wrapped)
                    *wrapped = true;
                nextIndex = model->index(0, 0);
262
263
264
265
266
267
268
269
270
271
            }
        }
    }
    return nextIndex;
}

QModelIndex TreeViewFind::prevIndex(const QModelIndex &idx, bool *wrapped) const
{
    if (wrapped)
        *wrapped = false;
272
273
274
275
276
    QAbstractItemModel *model = d->m_view->model();
    // if same parent has earlier columns, just move there
    if (idx.column() > 0)
        return model->index(idx.row(), idx.column() - 1, idx.parent());

277
278
279
280
    QModelIndex current = idx;
    bool checkForChildren = true;
    if (current.isValid()) {
        int row = current.row();
281
282
        if (row > 0) {
            current = model->index(row - 1, 0, current.parent());
283
        } else {
284
285
286
287
288
            current = current.parent();
            checkForChildren = !current.isValid();
            if (checkForChildren && wrapped) {
                // we start from the end
                *wrapped = true;
289
290
291
292
293
            }
        }
    }
    if (checkForChildren) {
        // traverse down the hierarchy
294
295
        if (d->m_option == FetchMoreWhileSearching && model->canFetchMore(current))
            model->fetchMore(current);
296
        while (int rc = model->rowCount(current)) {
297
            current = model->index(rc - 1, 0, current);
298
299
        }
    }
300
301
    // set to last column
    current = model->index(current.row(), model->columnCount(current.parent()) - 1, current.parent());
302
303
304
305
306
307
308
309
310
311
    return current;
}

QModelIndex TreeViewFind::followingIndex(const QModelIndex &idx, bool backward, bool *wrapped)
{
    if (backward)
        return prevIndex(idx, wrapped);
    return nextIndex(idx, wrapped);
}

312
} // namespace Core