1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
11 ** Licensees holding valid Qt Commercial licenses may use this file in
12 ** accordance with the Qt Commercial License Agreement provided with the
13 ** Software or, alternatively, in accordance with the terms contained in
14 ** a written agreement between you and Nokia.
16 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 ** If you are unsure which license is appropriate for your use, please
26 ** contact the sales department at http://qt.nokia.com/contact.
28 **************************************************************************/
30 #include "searchresulttreemodel.h"
31 #include "searchresulttreeitems.h"
32 #include "searchresulttreeitemroles.h"
34 #include <QtGui/QApplication>
35 #include <QtGui/QFont>
36 #include <QtGui/QFontMetrics>
37 #include <QtGui/QColor>
38 #include <QtGui/QPalette>
39 #include <QtGui/QTextDocument>
40 #include <QtGui/QTextCursor>
41 #include <QtCore/QDir>
42 #include <QtCore/QDebug>
45 using namespace Find::Internal;
47 SearchResultTreeModel::SearchResultTreeModel(QObject *parent)
48 : QAbstractItemModel(parent)
50 , m_showReplaceUI(false)
52 m_rootItem = new SearchResultTreeItem;
53 m_textEditorFont = QFont(QLatin1String("Courier"));
56 SearchResultTreeModel::~SearchResultTreeModel()
61 void SearchResultTreeModel::setShowReplaceUI(bool show)
63 m_showReplaceUI = show;
66 void SearchResultTreeModel::setTextEditorFont(const QFont &font)
68 layoutAboutToBeChanged();
69 m_textEditorFont = font;
73 Qt::ItemFlags SearchResultTreeModel::flags(const QModelIndex &idx) const
75 Qt::ItemFlags flags = QAbstractItemModel::flags(idx);
78 if (const SearchResultTreeItem *item = treeItemAtIndex(idx)) {
79 if (item->isUserCheckable()) {
80 flags |= Qt::ItemIsUserCheckable;
88 QModelIndex SearchResultTreeModel::index(int row, int column,
89 const QModelIndex &parent) const
91 if (!hasIndex(row, column, parent))
94 const SearchResultTreeItem *parentItem;
96 if (!parent.isValid())
97 parentItem = m_rootItem;
99 parentItem = treeItemAtIndex(parent);
101 const SearchResultTreeItem *childItem = parentItem->childAt(row);
103 return createIndex(row, column, (void *)childItem);
105 return QModelIndex();
108 QModelIndex SearchResultTreeModel::index(SearchResultTreeItem *item) const
110 return createIndex(item->rowOfItem(), 0, (void *)item);
113 QModelIndex SearchResultTreeModel::parent(const QModelIndex &idx) const
116 return QModelIndex();
118 const SearchResultTreeItem *childItem = treeItemAtIndex(idx);
119 const SearchResultTreeItem *parentItem = childItem->parent();
121 if (parentItem == m_rootItem)
122 return QModelIndex();
124 return createIndex(parentItem->rowOfItem(), 0, (void *)parentItem);
127 int SearchResultTreeModel::rowCount(const QModelIndex &parent) const
129 if (parent.column() > 0)
132 const SearchResultTreeItem *parentItem;
134 if (!parent.isValid())
135 parentItem = m_rootItem;
137 parentItem = treeItemAtIndex(parent);
139 return parentItem->childrenCount();
142 int SearchResultTreeModel::columnCount(const QModelIndex &parent) const
148 SearchResultTreeItem *SearchResultTreeModel::treeItemAtIndex(const QModelIndex &idx) const
150 return static_cast<SearchResultTreeItem*>(idx.internalPointer());
153 QVariant SearchResultTreeModel::data(const QModelIndex &idx, int role) const
160 if (role == Qt::SizeHintRole) {
161 // TODO we should not use editor font height if that is not used by any item
162 const int appFontHeight = QApplication::fontMetrics().height();
163 const int editorFontHeight = QFontMetrics(m_textEditorFont).height();
164 result = QSize(0, qMax(appFontHeight, editorFontHeight));
166 result = data(treeItemAtIndex(idx), role);
172 bool SearchResultTreeModel::setData(const QModelIndex &idx, const QVariant &value, int role)
174 if (role == Qt::CheckStateRole) {
175 Qt::CheckState checkState = static_cast<Qt::CheckState>(value.toInt());
176 return setCheckState(idx, checkState);
178 return QAbstractItemModel::setData(idx, value, role);
181 bool SearchResultTreeModel::setCheckState(const QModelIndex &idx, Qt::CheckState checkState, bool firstCall)
183 SearchResultTreeItem *item = treeItemAtIndex(idx);
184 if (item->checkState() == checkState)
186 item->setCheckState(checkState);
188 emit dataChanged(idx, idx);
190 SearchResultTreeItem *currentItem = item;
191 QModelIndex currentIndex = idx;
192 while (SearchResultTreeItem *parent = currentItem->parent()) {
193 if (parent->isUserCheckable()) {
194 bool hasChecked = false;
195 bool hasUnchecked = false;
196 for (int i = 0; i < parent->childrenCount(); ++i) {
197 SearchResultTreeItem *child = parent->childAt(i);
198 if (!child->isUserCheckable())
200 if (child->checkState() == Qt::Checked) {
202 } else if (child->checkState() == Qt::Unchecked) {
204 } else if (child->checkState() == Qt::PartiallyChecked) {
205 hasChecked = hasUnchecked = true;
208 if (hasChecked && hasUnchecked)
209 parent->setCheckState(Qt::PartiallyChecked);
211 parent->setCheckState(Qt::Checked);
213 parent->setCheckState(Qt::Unchecked);
214 emit dataChanged(idx.parent(), idx.parent());
216 currentItem = parent;
217 currentIndex = idx.parent();
221 if (int children = item->childrenCount()) {
222 for (int i = 0; i < children; ++i) {
223 setCheckState(idx.child(i, 0), checkState, false);
225 emit dataChanged(idx.child(0, 0), idx.child(children-1, 0));
230 void setDataInternal(const QModelIndex &index, const QVariant &value, int role);
232 QVariant SearchResultTreeModel::data(const SearchResultTreeItem *row, int role) const
238 case Qt::CheckStateRole:
239 if (row->isUserCheckable())
240 result = row->checkState();
242 case Qt::ToolTipRole:
243 result = row->item.text.trimmed();
246 if (row->item.useTextEditorFont)
247 result = m_textEditorFont;
251 case ItemDataRoles::ResultLineRole:
252 case Qt::DisplayRole:
253 result = row->item.text;
255 case ItemDataRoles::ResultItemRole:
256 result = qVariantFromValue(row->item);
258 case ItemDataRoles::ResultLineNumberRole:
259 result = row->item.lineNumber;
261 case ItemDataRoles::ResultIconRole:
262 result = row->item.icon;
264 case ItemDataRoles::SearchTermStartRole:
265 result = row->item.textMarkPos;
267 case ItemDataRoles::SearchTermLengthRole:
268 result = row->item.textMarkLength;
270 case ItemDataRoles::IsGeneratedRole:
271 result = row->isGenerated();
273 // TODO this looks stupid in case of symbol tree, is it necessary?
274 // case Qt::BackgroundRole:
275 // if (row->parent() && row->parent()->parent())
276 // result = QVariant();
278 // result = QApplication::palette().base().color().darker(105);
288 QVariant SearchResultTreeModel::headerData(int section, Qt::Orientation orientation,
292 Q_UNUSED(orientation)
298 * Makes sure that the nodes for a specific path exist and sets
299 * m_currentParent to the last final
301 QSet<SearchResultTreeItem *> SearchResultTreeModel::addPath(const QStringList &path)
303 QSet<SearchResultTreeItem *> pathNodes;
304 SearchResultTreeItem *currentItem = m_rootItem;
305 QModelIndex currentItemIndex = QModelIndex();
306 SearchResultTreeItem *partItem = 0;
307 QStringList currentPath;
308 foreach (const QString &part, path) {
309 const int insertionIndex = currentItem->insertionIndex(part, &partItem);
311 SearchResultItem item;
312 item.path = currentPath;
314 partItem = new SearchResultTreeItem(item, currentItem);
315 if (m_showReplaceUI) {
316 partItem->setIsUserCheckable(true);
317 partItem->setCheckState(Qt::Checked);
319 partItem->setGenerated(true);
320 beginInsertRows(currentItemIndex, insertionIndex, insertionIndex);
321 currentItem->insertChild(insertionIndex, partItem);
324 pathNodes << partItem;
325 currentItemIndex = index(insertionIndex, 0, currentItemIndex);
326 currentItem = partItem;
330 m_currentParent = currentItem;
331 m_currentPath = currentPath;
332 m_currentIndex = currentItemIndex;
336 void SearchResultTreeModel::addResultsToCurrentParent(const QList<SearchResultItem> &items, SearchResultWindow::AddMode mode)
338 if (!m_currentParent)
341 if (mode == SearchResultWindow::AddOrdered) {
342 // this is the mode for e.g. text search
343 beginInsertRows(m_currentIndex, m_currentParent->childrenCount(), m_currentParent->childrenCount() + items.count());
344 foreach (const SearchResultItem &item, items) {
345 m_currentParent->appendChild(item);
348 } else if (mode == SearchResultWindow::AddSorted) {
349 foreach (const SearchResultItem &item, items) {
350 SearchResultTreeItem *existingItem;
351 const int insertionIndex = m_currentParent->insertionIndex(item, &existingItem);
353 existingItem->setGenerated(false);
354 existingItem->item = item;
355 QModelIndex itemIndex = m_currentIndex.child(insertionIndex, 0);
356 dataChanged(itemIndex, itemIndex);
358 beginInsertRows(m_currentIndex, insertionIndex, insertionIndex);
359 m_currentParent->insertChild(insertionIndex, item);
364 dataChanged(m_currentIndex, m_currentIndex); // Make sure that the number after the file name gets updated
367 static bool lessThanByPath(const SearchResultItem &a, const SearchResultItem &b)
369 if (a.path.size() < b.path.size())
371 if (a.path.size() > b.path.size())
373 for (int i = 0; i < a.path.size(); ++i) {
374 if (a.path.at(i) < b.path.at(i))
376 if (a.path.at(i) > b.path.at(i))
383 * Adds the search result to the list of results, creating nodes for the path when
386 QList<QModelIndex> SearchResultTreeModel::addResults(const QList<SearchResultItem> &items, SearchResultWindow::AddMode mode)
388 QSet<SearchResultTreeItem *> pathNodes;
389 QList<SearchResultItem> sortedItems = items;
390 qStableSort(sortedItems.begin(), sortedItems.end(), lessThanByPath);
391 QList<SearchResultItem> itemSet;
392 foreach (const SearchResultItem &item, sortedItems) {
393 if (!m_currentParent || (m_currentPath != item.path)) {
394 // first add all the items from before
395 if (!itemSet.isEmpty()) {
396 addResultsToCurrentParent(itemSet, mode);
400 pathNodes += addPath(item.path);
404 if (!itemSet.isEmpty()) {
405 addResultsToCurrentParent(itemSet, mode);
408 QList<QModelIndex> pathIndices;
409 foreach (SearchResultTreeItem *item, pathNodes)
410 pathIndices << index(item);
414 void SearchResultTreeModel::clear()
416 m_currentParent = NULL;
417 m_rootItem->clearChildren();
421 QModelIndex SearchResultTreeModel::nextIndex(const QModelIndex &idx) const
427 if (rowCount(idx) > 0) {
428 // node with children
429 return idx.child(0, 0);
432 QModelIndex nextIndex;
433 QModelIndex current = idx;
434 while (!nextIndex.isValid()) {
435 int row = current.row();
436 current = current.parent();
437 if (row + 1 < rowCount(current)) {
438 // Same parent has another child
439 nextIndex = index(row + 1, 0, current);
442 if (!current.isValid()) {
443 nextIndex = index(0, 0);
450 QModelIndex SearchResultTreeModel::next(const QModelIndex &idx, bool includeGenerated) const
452 QModelIndex value = idx;
454 value = nextIndex(value);
455 } while (value != idx && !includeGenerated && treeItemAtIndex(value)->isGenerated());
459 QModelIndex SearchResultTreeModel::prevIndex(const QModelIndex &idx) const
461 QModelIndex current = idx;
462 bool checkForChildren = true;
463 if (current.isValid()) {
464 int row = current.row();
466 current = index(row - 1, 0, current.parent());
468 current = current.parent();
469 checkForChildren = !current.isValid();
472 if (checkForChildren) {
473 // traverse down the hierarchy
474 while (int rc = rowCount(current)) {
475 current = index(rc - 1, 0, current);
481 QModelIndex SearchResultTreeModel::prev(const QModelIndex &idx, bool includeGenerated) const
483 QModelIndex value = idx;
485 value = prevIndex(value);
486 } while (value != idx && !includeGenerated && treeItemAtIndex(value)->isGenerated());
490 QModelIndex SearchResultTreeModel::find(const QRegExp &expr, const QModelIndex &index, QTextDocument::FindFlags flags)
492 QModelIndex resultIndex;
493 QModelIndex currentIndex = index;
494 bool backward = (flags & QTextDocument::FindBackward);
498 currentIndex = prev(currentIndex, true);
500 currentIndex = next(currentIndex, true);
501 if (currentIndex.isValid()) {
502 const QString &text = data(currentIndex, ItemDataRoles::ResultLineRole).toString();
503 if (expr.indexIn(text) != -1)
504 resultIndex = currentIndex;
506 } while (!resultIndex.isValid() && currentIndex.isValid() && currentIndex != index);
510 QModelIndex SearchResultTreeModel::find(const QString &term, const QModelIndex &index, QTextDocument::FindFlags flags)
512 QModelIndex resultIndex;
513 QModelIndex currentIndex = index;
514 bool backward = (flags & QTextDocument::FindBackward);
515 flags = (flags & (~QTextDocument::FindBackward)); // backward is handled by us ourselves
519 currentIndex = prev(currentIndex, true);
521 currentIndex = next(currentIndex, true);
522 if (currentIndex.isValid()) {
523 const QString &text = data(currentIndex, ItemDataRoles::ResultLineRole).toString();
524 QTextDocument doc(text);
525 if (!doc.find(term, 0, flags).isNull())
526 resultIndex = currentIndex;
528 } while (!resultIndex.isValid() && currentIndex.isValid() && currentIndex != index);