tools/shared/findwidget/itemviewfindwidget.cpp
changeset 0 1918ee327afb
child 4 3b1da2848fc7
equal deleted inserted replaced
-1:000000000000 0:1918ee327afb
       
     1 /****************************************************************************
       
     2 **
       
     3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
       
     4 ** All rights reserved.
       
     5 ** Contact: Nokia Corporation (qt-info@nokia.com)
       
     6 **
       
     7 ** This file is part of the tools applications of the Qt Toolkit.
       
     8 **
       
     9 ** $QT_BEGIN_LICENSE:LGPL$
       
    10 ** No Commercial Usage
       
    11 ** This file contains pre-release code and may not be distributed.
       
    12 ** You may use this file in accordance with the terms and conditions
       
    13 ** contained in the Technology Preview License Agreement accompanying
       
    14 ** this package.
       
    15 **
       
    16 ** GNU Lesser General Public License Usage
       
    17 ** Alternatively, this file may be used under the terms of the GNU Lesser
       
    18 ** General Public License version 2.1 as published by the Free Software
       
    19 ** Foundation and appearing in the file LICENSE.LGPL included in the
       
    20 ** packaging of this file.  Please review the following information to
       
    21 ** ensure the GNU Lesser General Public License version 2.1 requirements
       
    22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
       
    23 **
       
    24 ** In addition, as a special exception, Nokia gives you certain additional
       
    25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
       
    26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
       
    27 **
       
    28 ** If you have questions regarding the use of this file, please contact
       
    29 ** Nokia at qt-info@nokia.com.
       
    30 **
       
    31 **
       
    32 **
       
    33 **
       
    34 **
       
    35 **
       
    36 **
       
    37 **
       
    38 ** $QT_END_LICENSE$
       
    39 **
       
    40 ****************************************************************************/
       
    41 
       
    42 /*! \class ItemViewFindWidget
       
    43 
       
    44     \brief A search bar that is commonly added below the searchable item view.
       
    45 
       
    46     \internal
       
    47 
       
    48     This widget implements a search bar which becomes visible when the user
       
    49     wants to start searching. It is a modern replacement for the commonly used
       
    50     search dialog. It is usually placed below a QAbstractItemView using a QVBoxLayout.
       
    51 
       
    52     The QAbstractItemView instance will need to be associated with this class using
       
    53     setItemView().
       
    54 
       
    55     The search is incremental and can be set to case sensitive or whole words
       
    56     using buttons available on the search bar.
       
    57 
       
    58     The item traversal order should fit QTreeView, QTableView and QListView alike.
       
    59     More complex tree structures will work as well, assuming the branch structure
       
    60     is painted left to the items, without crossing lines.
       
    61 
       
    62     \sa QAbstractItemView
       
    63  */
       
    64 
       
    65 #include "itemviewfindwidget.h"
       
    66 
       
    67 #include <QtGui/QAbstractItemView>
       
    68 #include <QtGui/QCheckBox>
       
    69 #include <QtGui/QTreeView>
       
    70 
       
    71 QT_BEGIN_NAMESPACE
       
    72 
       
    73 /*!
       
    74     Constructs a ItemViewFindWidget.
       
    75 
       
    76     \a flags is passed to the AbstractFindWidget constructor.
       
    77     \a parent is passed to the QWidget constructor.
       
    78  */
       
    79 ItemViewFindWidget::ItemViewFindWidget(FindFlags flags, QWidget *parent)
       
    80     : AbstractFindWidget(flags, parent)
       
    81     , m_itemView(0)
       
    82 {
       
    83 }
       
    84 
       
    85 /*!
       
    86     Associates a QAbstractItemView with this find widget. Searches done using this find
       
    87     widget will then apply to the given QAbstractItemView.
       
    88 
       
    89     An event filter is set on the QAbstractItemView which intercepts the ESC key while
       
    90     the find widget is active, and uses it to deactivate the find widget.
       
    91 
       
    92     If the find widget is already associated with a QAbstractItemView, the event filter
       
    93     is removed from this QAbstractItemView first.
       
    94 
       
    95     \a itemView may be NULL.
       
    96  */
       
    97 void ItemViewFindWidget::setItemView(QAbstractItemView *itemView)
       
    98 {
       
    99     if (m_itemView)
       
   100         m_itemView->removeEventFilter(this);
       
   101 
       
   102     m_itemView = itemView;
       
   103 
       
   104     if (m_itemView)
       
   105         m_itemView->installEventFilter(this);
       
   106 }
       
   107 
       
   108 /*!
       
   109     \reimp
       
   110  */
       
   111 void ItemViewFindWidget::deactivate()
       
   112 {
       
   113     if (m_itemView)
       
   114         m_itemView->setFocus();
       
   115 
       
   116     AbstractFindWidget::deactivate();
       
   117 }
       
   118 
       
   119 // Sorting is needed to find the start/end of the selection.
       
   120 // This is utter black magic. And it is damn slow.
       
   121 static bool indexLessThan(const QModelIndex &a, const QModelIndex &b)
       
   122 {
       
   123     // First determine the nesting of each index in the tree.
       
   124     QModelIndex aa = a;
       
   125     int aDepth = 0;
       
   126     while (aa.parent() != QModelIndex()) {
       
   127         // As a side effect, check if one of the items is the parent of the other.
       
   128         // Children are always displayed below their parents, so sort them further down.
       
   129         if (aa.parent() == b)
       
   130             return true;
       
   131         aa = aa.parent();
       
   132         aDepth++;
       
   133     }
       
   134     QModelIndex ba = b;
       
   135     int bDepth = 0;
       
   136     while (ba.parent() != QModelIndex()) {
       
   137         if (ba.parent() == a)
       
   138             return false;
       
   139         ba = ba.parent();
       
   140         bDepth++;
       
   141     }
       
   142     // Now find indices at comparable depth.
       
   143     for (aa = a; aDepth > bDepth; aDepth--)
       
   144         aa = aa.parent();
       
   145     for (ba = b; aDepth < bDepth; bDepth--)
       
   146         ba = ba.parent();
       
   147     // If they have the same parent, sort them within a top-to-bottom, left-to-right rectangle.
       
   148     if (aa.parent() == ba.parent()) {
       
   149         if (aa.row() < ba.row())
       
   150             return true;
       
   151         if (aa.row() > ba.row())
       
   152             return false;
       
   153         return aa.column() < ba.column();
       
   154     }
       
   155     // Now try to find indices that have the same grandparent. This ends latest at the root node.
       
   156     while (aa.parent().parent() != ba.parent().parent()) {
       
   157         aa = aa.parent();
       
   158         ba = ba.parent();
       
   159     }
       
   160     // A bigger row is always displayed further down.
       
   161     if (aa.parent().row() < ba.parent().row())
       
   162         return true;
       
   163     if (aa.parent().row() > ba.parent().row())
       
   164         return false;
       
   165     // Here's the trick: a child spawned from a bigger column is displayed further *up*.
       
   166     // That's because the tree lines are on the left and are supposed not to cross each other.
       
   167     // This case is mostly academical, as "all" models spawn children from the first column.
       
   168     return aa.parent().column() > ba.parent().column();
       
   169 }
       
   170 
       
   171 /*!
       
   172     \reimp
       
   173  */
       
   174 void ItemViewFindWidget::find(const QString &ttf, bool skipCurrent, bool backward, bool *found, bool *wrapped)
       
   175 {
       
   176     if (!m_itemView || !m_itemView->model()->hasChildren())
       
   177         return;
       
   178 
       
   179     QModelIndex idx;
       
   180     if (skipCurrent && m_itemView->selectionModel()->hasSelection()) {
       
   181         QModelIndexList il = m_itemView->selectionModel()->selectedIndexes();
       
   182         qSort(il.begin(), il.end(), indexLessThan);
       
   183         idx = backward ? il.first() : il.last();
       
   184     } else {
       
   185         idx = m_itemView->currentIndex();
       
   186     }
       
   187 
       
   188     *found = true;
       
   189     QModelIndex newIdx = idx;
       
   190 
       
   191     if (!ttf.isEmpty()) {
       
   192         if (newIdx.isValid()) {
       
   193             int column = newIdx.column();
       
   194             if (skipCurrent)
       
   195                 if (QTreeView *tv = qobject_cast<QTreeView *>(m_itemView))
       
   196                     if (tv->allColumnsShowFocus())
       
   197                         column = backward ? 0 : m_itemView->model()->columnCount(newIdx.parent()) - 1;
       
   198             newIdx = findHelper(ttf, skipCurrent, backward,
       
   199                                 newIdx.parent(), newIdx.row(), column);
       
   200         }
       
   201         if (!newIdx.isValid()) {
       
   202             int row = backward ? m_itemView->model()->rowCount() : 0;
       
   203             int column = backward ? 0 : -1;
       
   204             newIdx = findHelper(ttf, true, backward, m_itemView->rootIndex(), row, column);
       
   205             if (!newIdx.isValid()) {
       
   206                 *found = false;
       
   207                 newIdx = idx;
       
   208             } else {
       
   209                 *wrapped = true;
       
   210             }
       
   211         }
       
   212     }
       
   213 
       
   214     if (!isVisible())
       
   215         show();
       
   216 
       
   217     m_itemView->setCurrentIndex(newIdx);
       
   218 }
       
   219 
       
   220 // You are not expected to understand the following two functions.
       
   221 // The traversal order is described in the indexLessThan() comments above.
       
   222 
       
   223 static inline bool skipForward(const QAbstractItemModel *model, QModelIndex &parent, int &row, int &column)
       
   224 {
       
   225     forever {
       
   226         column++;
       
   227         if (column < model->columnCount(parent))
       
   228             return true;
       
   229         forever {
       
   230             while (--column >= 0) {
       
   231                 QModelIndex nIdx = model->index(row, column, parent);
       
   232                 if (nIdx.isValid()) {
       
   233                     if (model->hasChildren(nIdx)) {
       
   234                         row = 0;
       
   235                         column = 0;
       
   236                         parent = nIdx;
       
   237                         return true;
       
   238                     }
       
   239                 }
       
   240             }
       
   241             if (++row < model->rowCount(parent))
       
   242                 break;
       
   243             if (!parent.isValid())
       
   244                 return false;
       
   245             row = parent.row();
       
   246             column = parent.column();
       
   247             parent = parent.parent();
       
   248         }
       
   249     }
       
   250 }
       
   251 
       
   252 static inline bool skipBackward(const QAbstractItemModel *model, QModelIndex &parent, int &row, int &column)
       
   253 {
       
   254     column--;
       
   255     if (column == -1) {
       
   256         if (--row < 0) {
       
   257             if (!parent.isValid())
       
   258                 return false;
       
   259             row = parent.row();
       
   260             column = parent.column();
       
   261             parent = parent.parent();
       
   262         }
       
   263         while (++column < model->columnCount(parent)) {
       
   264             QModelIndex nIdx = model->index(row, column, parent);
       
   265             if (nIdx.isValid()) {
       
   266                 if (model->hasChildren(nIdx)) {
       
   267                     row = model->rowCount(nIdx) - 1;
       
   268                     column = -1;
       
   269                     parent = nIdx;
       
   270                 }
       
   271             }
       
   272         }
       
   273         column--;
       
   274     }
       
   275     return true;
       
   276 }
       
   277 
       
   278 // QAbstractItemModel::match() does not support backwards searching. Still using it would
       
   279 // be just a bit inefficient (not much worse than when no match is found).
       
   280 // The bigger problem is that QAbstractItemView does not provide a method to sort a
       
   281 // set of indices in traversal order (to find the start and end of the selection).
       
   282 // Consequently, we do everything by ourselves to be consistent. Of course, this puts
       
   283 // constraints on the allowable visualizations.
       
   284 QModelIndex ItemViewFindWidget::findHelper(const QString &textToFind, bool skipCurrent, bool backward,
       
   285     QModelIndex parent, int row, int column)
       
   286 {
       
   287     const QAbstractItemModel *model = m_itemView->model();
       
   288     forever {
       
   289         if (skipCurrent) {
       
   290             if (backward) {
       
   291                 if (!skipBackward(model, parent, row, column))
       
   292                     return QModelIndex();
       
   293             } else {
       
   294                 if (!skipForward(model, parent, row, column))
       
   295                     return QModelIndex();
       
   296             }
       
   297         }
       
   298 
       
   299         QModelIndex idx = model->index(row, column, parent);
       
   300         if (idx.isValid()) {
       
   301             Qt::CaseSensitivity cs = caseSensitive() ? Qt::CaseSensitive : Qt::CaseInsensitive;
       
   302 
       
   303             if (wholeWords()) {
       
   304                 QString rx = QLatin1String("\\b") + QRegExp::escape(textToFind) + QLatin1String("\\b");
       
   305                 if (idx.data().toString().indexOf(QRegExp(rx, cs)) >= 0)
       
   306                     return idx;
       
   307             } else {
       
   308                 if (idx.data().toString().indexOf(textToFind, 0, cs) >= 0)
       
   309                     return idx;
       
   310             }
       
   311         }
       
   312 
       
   313         skipCurrent = true;
       
   314     }
       
   315 }
       
   316 
       
   317 QT_END_NAMESPACE