searchresulttreemodel.cpp 16.69 KiB
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** 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.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
#include "searchresulttreemodel.h"
#include "searchresulttreeitems.h"
#include "searchresulttreeitemroles.h"
#include <QtGui/QApplication>
#include <QtGui/QFont>
#include <QtGui/QFontMetrics>
#include <QtGui/QColor>
#include <QtGui/QPalette>
#include <QtGui/QTextDocument>
#include <QtGui/QTextCursor>
#include <QtCore/QDir>
#include <QtCore/QDebug>
using namespace Find;
using namespace Find::Internal;
SearchResultTreeModel::SearchResultTreeModel(QObject *parent)
: QAbstractItemModel(parent)
, m_currentParent(0)
, m_showReplaceUI(false)
{
m_rootItem = new SearchResultTreeItem;
m_textEditorFont = QFont(QLatin1String("Courier"));
}
SearchResultTreeModel::~SearchResultTreeModel()
{
delete m_rootItem;
}
void SearchResultTreeModel::setShowReplaceUI(bool show)
{
m_showReplaceUI = show;
}
void SearchResultTreeModel::setTextEditorFont(const QFont &font)
{
layoutAboutToBeChanged();
m_textEditorFont = font;
layoutChanged();
}
Qt::ItemFlags SearchResultTreeModel::flags(const QModelIndex &idx) const
{
Qt::ItemFlags flags = QAbstractItemModel::flags(idx);
if (idx.isValid()) {
if (const SearchResultTreeItem *item = treeItemAtIndex(idx)) {
if (item->isUserCheckable()) {
flags |= Qt::ItemIsUserCheckable;
}
}
}
return flags;
}
QModelIndex SearchResultTreeModel::index(int row, int column,
const QModelIndex &parent) const
{
if (!hasIndex(row, column, parent))
return QModelIndex();
const SearchResultTreeItem *parentItem;
if (!parent.isValid())
parentItem = m_rootItem;
else
parentItem = treeItemAtIndex(parent);
const SearchResultTreeItem *childItem = parentItem->childAt(row);
if (childItem)
return createIndex(row, column, (void *)childItem);
else
return QModelIndex();
}
QModelIndex SearchResultTreeModel::index(SearchResultTreeItem *item) const
{
return createIndex(item->rowOfItem(), 0, (void *)item);
}
QModelIndex SearchResultTreeModel::parent(const QModelIndex &idx) const
{
if (!idx.isValid())
return QModelIndex();
const SearchResultTreeItem *childItem = treeItemAtIndex(idx);
const SearchResultTreeItem *parentItem = childItem->parent();
if (parentItem == m_rootItem)
return QModelIndex();
return createIndex(parentItem->rowOfItem(), 0, (void *)parentItem);
}
int SearchResultTreeModel::rowCount(const QModelIndex &parent) const
{
if (parent.column() > 0)
return 0;
const SearchResultTreeItem *parentItem;
if (!parent.isValid())
parentItem = m_rootItem;
else
parentItem = treeItemAtIndex(parent);
return parentItem->childrenCount();
}
int SearchResultTreeModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return 1;
}
SearchResultTreeItem *SearchResultTreeModel::treeItemAtIndex(const QModelIndex &idx) const
{
return static_cast<SearchResultTreeItem*>(idx.internalPointer());
}
QVariant SearchResultTreeModel::data(const QModelIndex &idx, int role) const
{
if (!idx.isValid())
return QVariant();
QVariant result;
if (role == Qt::SizeHintRole) {
// TODO we should not use editor font height if that is not used by any item
const int appFontHeight = QApplication::fontMetrics().height();
const int editorFontHeight = QFontMetrics(m_textEditorFont).height();
result = QSize(0, qMax(appFontHeight, editorFontHeight));
} else {
result = data(treeItemAtIndex(idx), role);
}
return result;
}
bool SearchResultTreeModel::setData(const QModelIndex &idx, const QVariant &value, int role)
{
if (role == Qt::CheckStateRole) {
Qt::CheckState checkState = static_cast<Qt::CheckState>(value.toInt());
return setCheckState(idx, checkState);
}
return QAbstractItemModel::setData(idx, value, role);
}
bool SearchResultTreeModel::setCheckState(const QModelIndex &idx, Qt::CheckState checkState, bool firstCall)
{
SearchResultTreeItem *item = treeItemAtIndex(idx);
if (item->checkState() == checkState)
return false;
item->setCheckState(checkState);
if (firstCall) {
emit dataChanged(idx, idx);
// check parents
SearchResultTreeItem *currentItem = item;
QModelIndex currentIndex = idx;
while (SearchResultTreeItem *parent = currentItem->parent()) {
if (parent->isUserCheckable()) {
bool hasChecked = false;
bool hasUnchecked = false;
for (int i = 0; i < parent->childrenCount(); ++i) {
SearchResultTreeItem *child = parent->childAt(i);
if (!child->isUserCheckable())
continue;
if (child->checkState() == Qt::Checked) {
hasChecked = true;
} else if (child->checkState() == Qt::Unchecked) {
hasUnchecked = true;
} else if (child->checkState() == Qt::PartiallyChecked) {
hasChecked = hasUnchecked = true;
}
}
if (hasChecked && hasUnchecked)
parent->setCheckState(Qt::PartiallyChecked);
else if (hasChecked)
parent->setCheckState(Qt::Checked);
else
parent->setCheckState(Qt::Unchecked);
emit dataChanged(idx.parent(), idx.parent());
}
currentItem = parent;
currentIndex = idx.parent();
}
}
// check children
if (int children = item->childrenCount()) {
for (int i = 0; i < children; ++i) {
setCheckState(idx.child(i, 0), checkState, false);
}
emit dataChanged(idx.child(0, 0), idx.child(children-1, 0));
}
return true;
}
void setDataInternal(const QModelIndex &index, const QVariant &value, int role);
QVariant SearchResultTreeModel::data(const SearchResultTreeItem *row, int role) const
{
QVariant result;
switch (role)
{
case Qt::CheckStateRole:
if (row->isUserCheckable())
result = row->checkState();
break;
case Qt::ToolTipRole:
result = row->item.text.trimmed();
break;
case Qt::FontRole:
if (row->item.useTextEditorFont)
result = m_textEditorFont;
else
result = QVariant();
break;
case ItemDataRoles::ResultLineRole:
case Qt::DisplayRole:
result = row->item.text;
break;
case ItemDataRoles::ResultItemRole:
result = qVariantFromValue(row->item);
break;
case ItemDataRoles::ResultLineNumberRole:
result = row->item.lineNumber;
break;
case ItemDataRoles::ResultIconRole:
result = row->item.icon;
break;
case ItemDataRoles::SearchTermStartRole:
result = row->item.textMarkPos;
break;
case ItemDataRoles::SearchTermLengthRole:
result = row->item.textMarkLength;
break;
case ItemDataRoles::IsGeneratedRole:
result = row->isGenerated();
break;
// TODO this looks stupid in case of symbol tree, is it necessary?
// case Qt::BackgroundRole:
// if (row->parent() && row->parent()->parent())
// result = QVariant();
// else
// result = QApplication::palette().base().color().darker(105);
// break;
default:
result = QVariant();
break;
}
return result;
}
QVariant SearchResultTreeModel::headerData(int section, Qt::Orientation orientation,
int role) const
{
Q_UNUSED(section)
Q_UNUSED(orientation)
Q_UNUSED(role)
return QVariant();
}
/**
* Makes sure that the nodes for a specific path exist and sets
* m_currentParent to the last final
*/
QSet<SearchResultTreeItem *> SearchResultTreeModel::addPath(const QStringList &path)
{
QSet<SearchResultTreeItem *> pathNodes;
SearchResultTreeItem *currentItem = m_rootItem;
QModelIndex currentItemIndex = QModelIndex();
SearchResultTreeItem *partItem = 0;
QStringList currentPath;
foreach (const QString &part, path) {
const int insertionIndex = currentItem->insertionIndex(part, &partItem);
if (!partItem) {
SearchResultItem item;
item.path = currentPath;
item.text = part;
partItem = new SearchResultTreeItem(item, currentItem);
if (m_showReplaceUI) {
partItem->setIsUserCheckable(true);
partItem->setCheckState(Qt::Checked);
}
partItem->setGenerated(true);
beginInsertRows(currentItemIndex, insertionIndex, insertionIndex);
currentItem->insertChild(insertionIndex, partItem);
endInsertRows();
}
pathNodes << partItem;
currentItemIndex = index(insertionIndex, 0, currentItemIndex);
currentItem = partItem;
currentPath << part;
}
m_currentParent = currentItem;
m_currentPath = currentPath;
m_currentIndex = currentItemIndex;
return pathNodes;
}
void SearchResultTreeModel::addResultsToCurrentParent(const QList<SearchResultItem> &items, SearchResultWindow::AddMode mode)
{
if (!m_currentParent)
return;
if (mode == SearchResultWindow::AddOrdered) {
// this is the mode for e.g. text search
beginInsertRows(m_currentIndex, m_currentParent->childrenCount(), m_currentParent->childrenCount() + items.count());
foreach (const SearchResultItem &item, items) {
m_currentParent->appendChild(item);
}
endInsertRows();
} else if (mode == SearchResultWindow::AddSorted) {
foreach (const SearchResultItem &item, items) {
SearchResultTreeItem *existingItem;
const int insertionIndex = m_currentParent->insertionIndex(item, &existingItem);
if (existingItem) {
existingItem->setGenerated(false);
existingItem->item = item;
QModelIndex itemIndex = m_currentIndex.child(insertionIndex, 0);
dataChanged(itemIndex, itemIndex);
} else {
beginInsertRows(m_currentIndex, insertionIndex, insertionIndex);
m_currentParent->insertChild(insertionIndex, item);
endInsertRows();
}
}
}
dataChanged(m_currentIndex, m_currentIndex); // Make sure that the number after the file name gets updated
}
static bool lessThanByPath(const SearchResultItem &a, const SearchResultItem &b)
{
if (a.path.size() < b.path.size())
return true;
if (a.path.size() > b.path.size())
return false;
for (int i = 0; i < a.path.size(); ++i) {
if (a.path.at(i) < b.path.at(i))
return true;
if (a.path.at(i) > b.path.at(i))
return false;
}
return false;
}
/**
* Adds the search result to the list of results, creating nodes for the path when
* necessary.
*/
QList<QModelIndex> SearchResultTreeModel::addResults(const QList<SearchResultItem> &items, SearchResultWindow::AddMode mode)
{
QSet<SearchResultTreeItem *> pathNodes;
QList<SearchResultItem> sortedItems = items;
qStableSort(sortedItems.begin(), sortedItems.end(), lessThanByPath);
QList<SearchResultItem> itemSet;
foreach (const SearchResultItem &item, sortedItems) {
if (!m_currentParent || (m_currentPath != item.path)) {
// first add all the items from before
if (!itemSet.isEmpty()) {
addResultsToCurrentParent(itemSet, mode);
itemSet.clear();
}
// switch parent
pathNodes += addPath(item.path);
}
itemSet << item;
}
if (!itemSet.isEmpty()) {
addResultsToCurrentParent(itemSet, mode);
itemSet.clear();
}
QList<QModelIndex> pathIndices;
foreach (SearchResultTreeItem *item, pathNodes)
pathIndices << index(item);
return pathIndices;
}
void SearchResultTreeModel::clear()
{
m_currentParent = NULL;
m_rootItem->clearChildren();
reset();
}
QModelIndex SearchResultTreeModel::nextIndex(const QModelIndex &idx) const
{
// pathological
if (!idx.isValid())
return index(0, 0);
if (rowCount(idx) > 0) {
// node with children
return idx.child(0, 0);
}
// leaf node
QModelIndex nextIndex;
QModelIndex current = idx;
while (!nextIndex.isValid()) {
int row = current.row();
current = current.parent();
if (row + 1 < rowCount(current)) {
// Same parent has another child
nextIndex = index(row + 1, 0, current);
} else {
// go up one parent
if (!current.isValid()) {
nextIndex = index(0, 0);
}
}
}
return nextIndex;
}
QModelIndex SearchResultTreeModel::next(const QModelIndex &idx, bool includeGenerated) const
{
QModelIndex value = idx;
do {
value = nextIndex(value);
} while (value != idx && !includeGenerated && treeItemAtIndex(value)->isGenerated());
return value;
}
QModelIndex SearchResultTreeModel::prevIndex(const QModelIndex &idx) const
{
QModelIndex current = idx;
bool checkForChildren = true;
if (current.isValid()) {
int row = current.row();
if (row > 0) {
current = index(row - 1, 0, current.parent());
} else {
current = current.parent();
checkForChildren = !current.isValid();
}
}
if (checkForChildren) {
// traverse down the hierarchy
while (int rc = rowCount(current)) {
current = index(rc - 1, 0, current);
}
}
return current;
}
QModelIndex SearchResultTreeModel::prev(const QModelIndex &idx, bool includeGenerated) const
{
QModelIndex value = idx;
do {
value = prevIndex(value);
} while (value != idx && !includeGenerated && treeItemAtIndex(value)->isGenerated());
return value;
}
QModelIndex SearchResultTreeModel::find(const QRegExp &expr, const QModelIndex &index, QTextDocument::FindFlags flags)
{
QModelIndex resultIndex;
QModelIndex currentIndex = index;
bool backward = (flags & QTextDocument::FindBackward);
do {
if (backward)
currentIndex = prev(currentIndex, true);
else
currentIndex = next(currentIndex, true);
if (currentIndex.isValid()) {
const QString &text = data(currentIndex, ItemDataRoles::ResultLineRole).toString();
if (expr.indexIn(text) != -1)
resultIndex = currentIndex;
}
} while (!resultIndex.isValid() && currentIndex.isValid() && currentIndex != index);
return resultIndex;
}
QModelIndex SearchResultTreeModel::find(const QString &term, const QModelIndex &index, QTextDocument::FindFlags flags)
{
QModelIndex resultIndex;
QModelIndex currentIndex = index;
bool backward = (flags & QTextDocument::FindBackward);
flags = (flags & (~QTextDocument::FindBackward)); // backward is handled by us ourselves
do {
if (backward)
currentIndex = prev(currentIndex, true);
else
currentIndex = next(currentIndex, true);
if (currentIndex.isValid()) {
const QString &text = data(currentIndex, ItemDataRoles::ResultLineRole).toString();
QTextDocument doc(text);
if (!doc.find(term, 0, flags).isNull())
resultIndex = currentIndex;
}
} while (!resultIndex.isValid() && currentIndex.isValid() && currentIndex != index);
return resultIndex;
}