OSDN Git Service

Support symbolic links in the file manager.
[qt-creator-jp/qt-creator-jp.git] / src / plugins / find / searchresulttreemodel.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
8 **
9 ** Commercial Usage
10 **
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.
15 **
16 ** GNU Lesser General Public License Usage
17 **
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.
24 **
25 ** If you are unsure which license is appropriate for your use, please
26 ** contact the sales department at http://qt.nokia.com/contact.
27 **
28 **************************************************************************/
29
30 #include "searchresulttreemodel.h"
31 #include "searchresulttreeitems.h"
32 #include "searchresulttreeitemroles.h"
33
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>
43
44 using namespace Find;
45 using namespace Find::Internal;
46
47 SearchResultTreeModel::SearchResultTreeModel(QObject *parent)
48     : QAbstractItemModel(parent)
49     , m_currentParent(0)
50     , m_showReplaceUI(false)
51 {
52     m_rootItem = new SearchResultTreeItem;
53     m_textEditorFont = QFont(QLatin1String("Courier"));
54 }
55
56 SearchResultTreeModel::~SearchResultTreeModel()
57 {
58     delete m_rootItem;
59 }
60
61 void SearchResultTreeModel::setShowReplaceUI(bool show)
62 {
63     m_showReplaceUI = show;
64 }
65
66 void SearchResultTreeModel::setTextEditorFont(const QFont &font)
67 {
68     layoutAboutToBeChanged();
69     m_textEditorFont = font;
70     layoutChanged();
71 }
72
73 Qt::ItemFlags SearchResultTreeModel::flags(const QModelIndex &idx) const
74 {
75     Qt::ItemFlags flags = QAbstractItemModel::flags(idx);
76
77     if (idx.isValid()) {
78         if (const SearchResultTreeItem *item = treeItemAtIndex(idx)) {
79             if (item->isUserCheckable()) {
80                 flags |= Qt::ItemIsUserCheckable;
81             }
82         }
83     }
84
85     return flags;
86 }
87
88 QModelIndex SearchResultTreeModel::index(int row, int column,
89                                          const QModelIndex &parent) const
90 {
91     if (!hasIndex(row, column, parent))
92         return QModelIndex();
93
94     const SearchResultTreeItem *parentItem;
95
96     if (!parent.isValid())
97         parentItem = m_rootItem;
98     else
99         parentItem = treeItemAtIndex(parent);
100
101     const SearchResultTreeItem *childItem = parentItem->childAt(row);
102     if (childItem)
103         return createIndex(row, column, (void *)childItem);
104     else
105         return QModelIndex();
106 }
107
108 QModelIndex SearchResultTreeModel::index(SearchResultTreeItem *item) const
109 {
110     return createIndex(item->rowOfItem(), 0, (void *)item);
111 }
112
113 QModelIndex SearchResultTreeModel::parent(const QModelIndex &idx) const
114 {
115     if (!idx.isValid())
116         return QModelIndex();
117
118     const SearchResultTreeItem *childItem = treeItemAtIndex(idx);
119     const SearchResultTreeItem *parentItem = childItem->parent();
120
121     if (parentItem == m_rootItem)
122         return QModelIndex();
123
124     return createIndex(parentItem->rowOfItem(), 0, (void *)parentItem);
125 }
126
127 int SearchResultTreeModel::rowCount(const QModelIndex &parent) const
128 {
129     if (parent.column() > 0)
130         return 0;
131
132     const SearchResultTreeItem *parentItem;
133
134     if (!parent.isValid())
135         parentItem = m_rootItem;
136     else
137         parentItem = treeItemAtIndex(parent);
138
139     return parentItem->childrenCount();
140 }
141
142 int SearchResultTreeModel::columnCount(const QModelIndex &parent) const
143 {
144     Q_UNUSED(parent)
145     return 1;
146 }
147
148 SearchResultTreeItem *SearchResultTreeModel::treeItemAtIndex(const QModelIndex &idx) const
149 {
150     return static_cast<SearchResultTreeItem*>(idx.internalPointer());
151 }
152
153 QVariant SearchResultTreeModel::data(const QModelIndex &idx, int role) const
154 {
155     if (!idx.isValid())
156         return QVariant();
157
158     QVariant result;
159
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));
165     } else {
166         result = data(treeItemAtIndex(idx), role);
167     }
168
169     return result;
170 }
171
172 bool SearchResultTreeModel::setData(const QModelIndex &idx, const QVariant &value, int role)
173 {
174     if (role == Qt::CheckStateRole) {
175         Qt::CheckState checkState = static_cast<Qt::CheckState>(value.toInt());
176         return setCheckState(idx, checkState);
177     }
178     return QAbstractItemModel::setData(idx, value, role);
179 }
180
181 bool SearchResultTreeModel::setCheckState(const QModelIndex &idx, Qt::CheckState checkState, bool firstCall)
182 {
183     SearchResultTreeItem *item = treeItemAtIndex(idx);
184     if (item->checkState() == checkState)
185         return false;
186     item->setCheckState(checkState);
187     if (firstCall) {
188         emit dataChanged(idx, idx);
189         // check parents
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())
199                         continue;
200                     if (child->checkState() == Qt::Checked) {
201                         hasChecked = true;
202                     } else if (child->checkState() == Qt::Unchecked) {
203                         hasUnchecked = true;
204                     } else if (child->checkState() == Qt::PartiallyChecked) {
205                         hasChecked = hasUnchecked = true;
206                     }
207                 }
208                 if (hasChecked && hasUnchecked)
209                     parent->setCheckState(Qt::PartiallyChecked);
210                 else if (hasChecked)
211                     parent->setCheckState(Qt::Checked);
212                 else
213                     parent->setCheckState(Qt::Unchecked);
214                 emit dataChanged(idx.parent(), idx.parent());
215             }
216             currentItem = parent;
217             currentIndex = idx.parent();
218         }
219     }
220     // check children
221     if (int children = item->childrenCount()) {
222         for (int i = 0; i < children; ++i) {
223             setCheckState(idx.child(i, 0), checkState, false);
224         }
225         emit dataChanged(idx.child(0, 0), idx.child(children-1, 0));
226     }
227     return true;
228 }
229
230 void setDataInternal(const QModelIndex &index, const QVariant &value, int role);
231
232 QVariant SearchResultTreeModel::data(const SearchResultTreeItem *row, int role) const
233 {
234     QVariant result;
235
236     switch (role)
237     {
238     case Qt::CheckStateRole:
239         if (row->isUserCheckable())
240             result = row->checkState();
241         break;
242     case Qt::ToolTipRole:
243         result = row->item.text.trimmed();
244         break;
245     case Qt::FontRole:
246         if (row->item.useTextEditorFont)
247             result = m_textEditorFont;
248         else
249             result = QVariant();
250         break;
251     case ItemDataRoles::ResultLineRole:
252     case Qt::DisplayRole:
253         result = row->item.text;
254         break;
255     case ItemDataRoles::ResultItemRole:
256         result = qVariantFromValue(row->item);
257         break;
258     case ItemDataRoles::ResultLineNumberRole:
259         result = row->item.lineNumber;
260         break;
261     case ItemDataRoles::ResultIconRole:
262         result = row->item.icon;
263         break;
264     case ItemDataRoles::SearchTermStartRole:
265         result = row->item.textMarkPos;
266         break;
267     case ItemDataRoles::SearchTermLengthRole:
268         result = row->item.textMarkLength;
269         break;
270     case ItemDataRoles::IsGeneratedRole:
271         result = row->isGenerated();
272         break;
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();
277 //        else
278 //            result = QApplication::palette().base().color().darker(105);
279 //        break;
280     default:
281         result = QVariant();
282         break;
283     }
284
285     return result;
286 }
287
288 QVariant SearchResultTreeModel::headerData(int section, Qt::Orientation orientation,
289                                            int role) const
290 {
291     Q_UNUSED(section)
292     Q_UNUSED(orientation)
293     Q_UNUSED(role)
294     return QVariant();
295 }
296
297 /**
298  * Makes sure that the nodes for a specific path exist and sets
299  * m_currentParent to the last final
300  */
301 QSet<SearchResultTreeItem *> SearchResultTreeModel::addPath(const QStringList &path)
302 {
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);
310         if (!partItem) {
311             SearchResultItem item;
312             item.path = currentPath;
313             item.text = part;
314             partItem = new SearchResultTreeItem(item, currentItem);
315             if (m_showReplaceUI) {
316                 partItem->setIsUserCheckable(true);
317                 partItem->setCheckState(Qt::Checked);
318             }
319             partItem->setGenerated(true);
320             beginInsertRows(currentItemIndex, insertionIndex, insertionIndex);
321             currentItem->insertChild(insertionIndex, partItem);
322             endInsertRows();
323         }
324         pathNodes << partItem;
325         currentItemIndex = index(insertionIndex, 0, currentItemIndex);
326         currentItem = partItem;
327         currentPath << part;
328     }
329
330     m_currentParent = currentItem;
331     m_currentPath = currentPath;
332     m_currentIndex = currentItemIndex;
333     return pathNodes;
334 }
335
336 void SearchResultTreeModel::addResultsToCurrentParent(const QList<SearchResultItem> &items, SearchResultWindow::AddMode mode)
337 {
338     if (!m_currentParent)
339         return;
340
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);
346         }
347         endInsertRows();
348     } else if (mode == SearchResultWindow::AddSorted) {
349         foreach (const SearchResultItem &item, items) {
350             SearchResultTreeItem *existingItem;
351             const int insertionIndex = m_currentParent->insertionIndex(item, &existingItem);
352             if (existingItem) {
353                 existingItem->setGenerated(false);
354                 existingItem->item = item;
355                 QModelIndex itemIndex = m_currentIndex.child(insertionIndex, 0);
356                 dataChanged(itemIndex, itemIndex);
357             } else {
358                 beginInsertRows(m_currentIndex, insertionIndex, insertionIndex);
359                 m_currentParent->insertChild(insertionIndex, item);
360                 endInsertRows();
361             }
362         }
363     }
364     dataChanged(m_currentIndex, m_currentIndex); // Make sure that the number after the file name gets updated
365 }
366
367 static bool lessThanByPath(const SearchResultItem &a, const SearchResultItem &b)
368 {
369     if (a.path.size() < b.path.size())
370         return true;
371     if (a.path.size() > b.path.size())
372         return false;
373     for (int i = 0; i < a.path.size(); ++i) {
374         if (a.path.at(i) < b.path.at(i))
375             return true;
376         if (a.path.at(i) > b.path.at(i))
377             return false;
378     }
379     return false;
380 }
381
382 /**
383  * Adds the search result to the list of results, creating nodes for the path when
384  * necessary.
385  */
386 QList<QModelIndex> SearchResultTreeModel::addResults(const QList<SearchResultItem> &items, SearchResultWindow::AddMode mode)
387 {
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);
397                 itemSet.clear();
398             }
399             // switch parent
400             pathNodes += addPath(item.path);
401         }
402         itemSet << item;
403     }
404     if (!itemSet.isEmpty()) {
405         addResultsToCurrentParent(itemSet, mode);
406         itemSet.clear();
407     }
408     QList<QModelIndex> pathIndices;
409     foreach (SearchResultTreeItem *item, pathNodes)
410         pathIndices << index(item);
411     return pathIndices;
412 }
413
414 void SearchResultTreeModel::clear()
415 {
416     m_currentParent = NULL;
417     m_rootItem->clearChildren();
418     reset();
419 }
420
421 QModelIndex SearchResultTreeModel::nextIndex(const QModelIndex &idx) const
422 {
423     // pathological
424     if (!idx.isValid())
425         return index(0, 0);
426
427     if (rowCount(idx) > 0) {
428         // node with children
429         return idx.child(0, 0);
430     }
431     // leaf node
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);
440         } else {
441             // go up one parent
442             if (!current.isValid()) {
443                 nextIndex = index(0, 0);
444             }
445         }
446     }
447     return nextIndex;
448 }
449
450 QModelIndex SearchResultTreeModel::next(const QModelIndex &idx, bool includeGenerated) const
451 {
452     QModelIndex value = idx;
453     do {
454         value = nextIndex(value);
455     } while (value != idx && !includeGenerated && treeItemAtIndex(value)->isGenerated());
456     return value;
457 }
458
459 QModelIndex SearchResultTreeModel::prevIndex(const QModelIndex &idx) const
460 {
461     QModelIndex current = idx;
462     bool checkForChildren = true;
463     if (current.isValid()) {
464         int row = current.row();
465         if (row > 0) {
466             current = index(row - 1, 0, current.parent());
467         } else {
468             current = current.parent();
469             checkForChildren = !current.isValid();
470         }
471     }
472     if (checkForChildren) {
473         // traverse down the hierarchy
474         while (int rc = rowCount(current)) {
475             current = index(rc - 1, 0, current);
476         }
477     }
478     return current;
479 }
480
481 QModelIndex SearchResultTreeModel::prev(const QModelIndex &idx, bool includeGenerated) const
482 {
483     QModelIndex value = idx;
484     do {
485         value = prevIndex(value);
486     } while (value != idx && !includeGenerated && treeItemAtIndex(value)->isGenerated());
487     return value;
488 }
489
490 QModelIndex SearchResultTreeModel::find(const QRegExp &expr, const QModelIndex &index, QTextDocument::FindFlags flags)
491 {
492     QModelIndex resultIndex;
493     QModelIndex currentIndex = index;
494     bool backward = (flags & QTextDocument::FindBackward);
495
496     do {
497         if (backward)
498             currentIndex = prev(currentIndex, true);
499         else
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;
505         }
506     } while (!resultIndex.isValid() && currentIndex.isValid() && currentIndex != index);
507     return resultIndex;
508 }
509
510 QModelIndex SearchResultTreeModel::find(const QString &term, const QModelIndex &index, QTextDocument::FindFlags flags)
511 {
512     QModelIndex resultIndex;
513     QModelIndex currentIndex = index;
514     bool backward = (flags & QTextDocument::FindBackward);
515     flags = (flags & (~QTextDocument::FindBackward)); // backward is handled by us ourselves
516
517     do {
518         if (backward)
519             currentIndex = prev(currentIndex, true);
520         else
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;
527         }
528     } while (!resultIndex.isValid() && currentIndex.isValid() && currentIndex != index);
529     return resultIndex;
530 }